Completely type hint template functions and switch to named arguments

This commit is contained in:
Alex Cabal 2025-03-04 16:08:55 -06:00
parent 6108b5e53d
commit 124e8343fc
125 changed files with 542 additions and 450 deletions

View file

@ -9,10 +9,6 @@ parameters:
checkInternalClassCaseSensitivity: true checkInternalClassCaseSensitivity: true
checkTooWideReturnTypesInProtectedAndPublicMethods: true checkTooWideReturnTypesInProtectedAndPublicMethods: true
ignoreErrors:
# Ignore errors caused by `Template` static class reflection.
- '#Call to an undefined static method Template::[a-zA-Z0-9\\_]+\(\)\.#'
bootstrapFiles: bootstrapFiles:
- %rootDir%/../../../lib/Constants.php - %rootDir%/../../../lib/Constants.php

View file

@ -26,7 +26,9 @@ class AtomFeed extends Feed{
protected function GetXmlString(): string{ protected function GetXmlString(): string{
if(!isset($this->_XmlString)){ if(!isset($this->_XmlString)){
$feed = Template::AtomFeed(['id' => $this->Id, 'url' => $this->Url, 'title' => $this->Title, 'subtitle' => $this->Subtitle, 'updated' => $this->Updated, 'entries' => $this->Entries]); /** @var array<Ebook> $entries */
$entries = $this->Entries;
$feed = Template::AtomFeed(id: $this->Id, url: $this->Url, title: $this->Title, subtitle: $this->Subtitle, updated: $this->Updated, entries: $entries);
$this->_XmlString = $this->CleanXmlString($feed); $this->_XmlString = $this->CleanXmlString($feed);
} }

View file

@ -98,8 +98,8 @@ class NewsletterSubscription{
$em->ToName = $this->User->Name; $em->ToName = $this->User->Name;
} }
$em->Subject = 'Action required: confirm your newsletter subscription'; $em->Subject = 'Action required: confirm your newsletter subscription';
$em->Body = Template::EmailNewsletterConfirmation(['subscription' => $this, 'isSubscribedToSummary' => $this->IsSubscribedToSummary, 'isSubscribedToNewsletter' => $this->IsSubscribedToNewsletter]); $em->Body = Template::EmailNewsletterConfirmation(subscription: $this, isSubscribedToSummary: $this->IsSubscribedToSummary, isSubscribedToNewsletter: $this->IsSubscribedToNewsletter);
$em->TextBody = Template::EmailNewsletterConfirmationText(['subscription' => $this, 'isSubscribedToSummary' => $this->IsSubscribedToSummary, 'isSubscribedToNewsletter' => $this->IsSubscribedToNewsletter]); $em->TextBody = Template::EmailNewsletterConfirmationText(subscription: $this, isSubscribedToSummary: $this->IsSubscribedToSummary, isSubscribedToNewsletter: $this->IsSubscribedToNewsletter);
$em->Send(); $em->Send();
} }

View file

@ -1,7 +1,13 @@
<? <?
/**
* @property array<Ebook> $Entries
*/
class OpdsAcquisitionFeed extends OpdsFeed{ class OpdsAcquisitionFeed extends OpdsFeed{
public bool $IsCrawlable; public bool $IsCrawlable;
/**
* @param array<Ebook> $entries
*/
public function __construct(string $title, string $subtitle, string $url, string $path, array $entries, ?OpdsNavigationFeed $parent, bool $isCrawlable = false){ public function __construct(string $title, string $subtitle, string $url, string $path, array $entries, ?OpdsNavigationFeed $parent, bool $isCrawlable = false){
parent::__construct($title, $subtitle, $url, $path, $entries, $parent); parent::__construct($title, $subtitle, $url, $path, $entries, $parent);
$this->IsCrawlable = $isCrawlable; $this->IsCrawlable = $isCrawlable;
@ -13,6 +19,6 @@ class OpdsAcquisitionFeed extends OpdsFeed{
// ******* // *******
protected function GetXmlString(): string{ protected function GetXmlString(): string{
return $this->_XmlString ??= $this->CleanXmlString(Template::OpdsAcquisitionFeed(['id' => $this->Id, 'url' => $this->Url, 'title' => $this->Title, 'parentUrl' => $this->Parent ? $this->Parent->Url : null, 'updated' => $this->Updated, 'isCrawlable' => $this->IsCrawlable, 'subtitle' => $this->Subtitle, 'entries' => $this->Entries])); return $this->_XmlString ??= $this->CleanXmlString(Template::OpdsAcquisitionFeed(id: $this->Id, url: $this->Url, title: $this->Title, parentUrl: $this->Parent->Url ?? '', updated: $this->Updated, isCrawlable: $this->IsCrawlable, subtitle: $this->Subtitle, entries: $this->Entries));
} }
} }

View file

@ -2,6 +2,9 @@
use Safe\DateTimeImmutable; use Safe\DateTimeImmutable;
use function Safe\file_put_contents; use function Safe\file_put_contents;
/**
* @property array<Ebook|OpdsNavigationEntry> $Entries
*/
abstract class OpdsFeed extends AtomFeed{ abstract class OpdsFeed extends AtomFeed{
public ?OpdsNavigationFeed $Parent = null; public ?OpdsNavigationFeed $Parent = null;

View file

@ -2,6 +2,9 @@
use Safe\DateTimeImmutable; use Safe\DateTimeImmutable;
use function Safe\file_get_contents; use function Safe\file_get_contents;
/**
* @property array<OpdsNavigationEntry> $Entries
*/
class OpdsNavigationFeed extends OpdsFeed{ class OpdsNavigationFeed extends OpdsFeed{
/** /**
* @param array<OpdsNavigationEntry> $entries * @param array<OpdsNavigationEntry> $entries
@ -38,6 +41,6 @@ class OpdsNavigationFeed extends OpdsFeed{
// ******* // *******
protected function GetXmlString(): string{ protected function GetXmlString(): string{
return $this->_XmlString ??= $this->CleanXmlString(Template::OpdsNavigationFeed(['id' => $this->Id, 'url' => $this->Url, 'title' => $this->Title, 'parentUrl' => $this->Parent ? $this->Parent->Url : null, 'updated' => $this->Updated, 'subtitle' => $this->Subtitle, 'entries' => $this->Entries])); return $this->_XmlString ??= $this->CleanXmlString(Template::OpdsNavigationFeed(id: $this->Id, url: $this->Url, title: $this->Title, parentUrl: $this->Parent->Url ?? '', updated: $this->Updated, subtitle: $this->Subtitle, entries: $this->Entries));
} }
} }

View file

@ -83,8 +83,8 @@ class Patron{
$em->From = EDITOR_IN_CHIEF_EMAIL_ADDRESS; $em->From = EDITOR_IN_CHIEF_EMAIL_ADDRESS;
$em->FromName = EDITOR_IN_CHIEF_NAME; $em->FromName = EDITOR_IN_CHIEF_NAME;
$em->Subject = 'Thank you for supporting Standard Ebooks!'; $em->Subject = 'Thank you for supporting Standard Ebooks!';
$em->Body = Template::EmailPatronsCircleWelcome(['isAnonymous' => $this->IsAnonymous, 'isReturning' => $isReturning]); $em->Body = Template::EmailPatronsCircleWelcome(isAnonymous: $this->IsAnonymous, isReturning: $isReturning);
$em->TextBody = Template::EmailPatronsCircleWelcomeText(['isAnonymous' => $this->IsAnonymous, 'isReturning' => $isReturning]); $em->TextBody = Template::EmailPatronsCircleWelcomeText(isAnonymous: $this->IsAnonymous, isReturning: $isReturning);
$em->Send(); $em->Send();
if(!$isReturning){ if(!$isReturning){
@ -92,8 +92,8 @@ class Patron{
$em->To = ADMIN_EMAIL_ADDRESS; $em->To = ADMIN_EMAIL_ADDRESS;
$em->From = ADMIN_EMAIL_ADDRESS; $em->From = ADMIN_EMAIL_ADDRESS;
$em->Subject = 'New Patrons Circle member'; $em->Subject = 'New Patrons Circle member';
$em->Body = Template::EmailAdminNewPatron(['patron' => $this, 'payment' => $this->User->Payments[0]]); $em->Body = Template::EmailAdminNewPatron(patron: $this, payment: $this->User->Payments[0]);
$em->TextBody = Template::EmailAdminNewPatronText(['patron' => $this, 'payment' => $this->User->Payments[0]]);; $em->TextBody = Template::EmailAdminNewPatronText(patron: $this, payment: $this->User->Payments[0]);;
$em->Send(); $em->Send();
} }
} }
@ -134,8 +134,8 @@ class Patron{
} }
else{ else{
// Email one time donors who have expired after one year. // Email one time donors who have expired after one year.
$em->Body = Template::EmailPatronsCircleCompleted(['ebooksThisYear' => $ebooksThisYear]); $em->Body = Template::EmailPatronsCircleCompleted(ebooksThisYear: $ebooksThisYear);
$em->TextBody = Template::EmailPatronsCircleCompletedText(['ebooksThisYear' => $ebooksThisYear]); $em->TextBody = Template::EmailPatronsCircleCompletedText(ebooksThisYear: $ebooksThisYear);
} }
$em->Send(); $em->Send();

View file

@ -397,8 +397,8 @@ final class Project{
$em->From = ADMIN_EMAIL_ADDRESS; $em->From = ADMIN_EMAIL_ADDRESS;
$em->To = $this->Manager->Email; $em->To = $this->Manager->Email;
$em->Subject = 'New ebook project to manage and review'; $em->Subject = 'New ebook project to manage and review';
$em->Body = Template::EmailManagerNewProject(['project' => $this, 'role' => 'manage and review', 'user' => $this->Manager]); $em->Body = Template::EmailManagerNewProject(project: $this, role: 'manage and review', user: $this->Manager);
$em->TextBody = Template::EmailManagerNewProjectText(['project' => $this, 'role' => 'manage and review', 'user' => $this->Manager]); $em->TextBody = Template::EmailManagerNewProjectText(project: $this, role: 'manage and review', user: $this->Manager);
$em->Send(); $em->Send();
} }
} }
@ -409,8 +409,8 @@ final class Project{
$em->From = ADMIN_EMAIL_ADDRESS; $em->From = ADMIN_EMAIL_ADDRESS;
$em->To = $this->Manager->Email; $em->To = $this->Manager->Email;
$em->Subject = 'New ebook project to manage'; $em->Subject = 'New ebook project to manage';
$em->Body = Template::EmailManagerNewProject(['project' => $this, 'role' => 'manage', 'user' => $this->Manager]); $em->Body = Template::EmailManagerNewProject(project: $this, role: 'manage', user: $this->Manager);
$em->TextBody = Template::EmailManagerNewProjectText(['project' => $this, 'role' => 'manage', 'user' => $this->Manager]); $em->TextBody = Template::EmailManagerNewProjectText(project: $this, role: 'manage', user: $this->Manager);
$em->Send(); $em->Send();
} }
@ -420,8 +420,8 @@ final class Project{
$em->From = ADMIN_EMAIL_ADDRESS; $em->From = ADMIN_EMAIL_ADDRESS;
$em->To = $this->Reviewer->Email; $em->To = $this->Reviewer->Email;
$em->Subject = 'New ebook project to review'; $em->Subject = 'New ebook project to review';
$em->Body = Template::EmailManagerNewProject(['project' => $this, 'role' => 'review', 'user' => $this->Reviewer]); $em->Body = Template::EmailManagerNewProject(project: $this, role: 'review', user: $this->Reviewer);
$em->TextBody = Template::EmailManagerNewProjectText(['project' => $this, 'role' => 'review', 'user' => $this->Reviewer]); $em->TextBody = Template::EmailManagerNewProjectText(project: $this, role: 'review', user: $this->Reviewer);
$em->Send(); $em->Send();
} }
} }

View file

@ -3,6 +3,9 @@ use function Safe\file_get_contents;
use function Safe\filesize; use function Safe\filesize;
use function Safe\preg_replace; use function Safe\preg_replace;
/**
* @property array<Ebook> $Entries
*/
class RssFeed extends Feed{ class RssFeed extends Feed{
public string $Description; public string $Description;
@ -21,7 +24,7 @@ class RssFeed extends Feed{
// ******* // *******
protected function GetXmlString(): string{ protected function GetXmlString(): string{
return $this->_XmlString ??= $this->CleanXmlString(Template::RssFeed(['url' => $this->Url, 'description' => $this->Description, 'title' => $this->Title, 'entries' => $this->Entries, 'updated' => NOW])); return $this->_XmlString ??= $this->CleanXmlString(Template::RssFeed(url: $this->Url, description: $this->Description, title: $this->Title, entries: $this->Entries, updated: NOW));
} }
public function SaveIfChanged(): bool{ public function SaveIfChanged(): bool{

View file

@ -1,62 +1,65 @@
<? <?
use function Safe\ob_end_clean; use Safe\DateTimeImmutable;
use function Safe\ob_start;
class Template{ /**
/** * @method static string ArtworkForm(Artwork $artwork, $isEditForm = false)
* @param array<mixed> $arguments * @method static string ArtworkList(array<Artwork> $artworks)
* @method static string AtomFeed(string $id, string $url, string $title, ?string $subtitle = null, DateTimeImmutable $updated, array<Ebook> $entries)
* @method static string AtomFeedEntry(Ebook $entry)
* @method static string BulkDownloadTable(string $label, array<stdClass> $collections)
* @method static string CollectionDescriptor(?CollectionMembership $collectionMembership)
* @method static string ContributeAlert()
* @method static string DonationAlert()
* @method static string DonationCounter(bool $autoHide = true, bool $showDonateButton = true)
* @method static string DonationProgress(bool $autoHide = true, bool $showDonateButton = true)
* @method static string EbookCarousel(array<Ebook> $carousel, bool $isMultiSize = false)
* @method static string EbookGrid(array<Ebook> $ebooks, ?Collection $collection = null, Enums\ViewType $view = Enums\ViewType::Grid)
* @method static string EbookMetadata(Ebook $ebook, bool $showPlaceholderMetadata = false)
* @method static string EbookPlaceholderForm(Ebook $ebook, bool $isEditForm = false, bool $showProjectForm = true)
* @method static string EmailAdminNewPatron(Patron $patron, Payment $payment)
* @method static string EmailAdminNewPatronText(Patron $patron, Payment $payment)
* @method static string EmailAdminUnprocessedDonations()
* @method static string EmailAdminUnprocessedDonationsText()
* @method static string EmailDonationProcessingFailed(string $exception)
* @method static string EmailDonationProcessingFailedText(string $exception)
* @method static string EmailDonationThankYou()
* @method static string EmailDonationThankYouText()
* @method static string EmailFooter(bool $includeLinks = true)
* @method static string EmailFooterText()
* @method static string EmailHeader(?string $preheader = null, bool $hasLetterhead = false, bool $hasAdminTable = false)
* @method static string EmailManagerNewProject(Project $project, string $role, User $user)
* @method static string EmailManagerNewProjectText(Project $project, string $role, User $user)
* @method static string EmailNewsletterConfirmation(bool $isSubscribedToNewsletter, bool $isSubscribedToSummary, NewsletterSubscription $subscription)
* @method static string EmailNewsletterConfirmationText(bool $isSubscribedToNewsletter, bool $isSubscribedToSummary, NewsletterSubscription $subscription)
* @method static string EmailPatronsCircleCompleted(int $ebooksThisYear)
* @method static string EmailPatronsCircleCompletedText(int $ebooksThisYear)
* @method static string EmailPatronsCircleRecurringCompleted()
* @method static string EmailPatronsCircleRecurringCompletedText()
* @method static string EmailPatronsCircleWelcome(bool $isAnonymous, bool $isReturning)
* @method static string EmailPatronsCircleWelcomeText(bool $isAnonymous, bool $isReturning)
* @method static string EmailProjectAbandoned()
* @method static string EmailProjectAbandonedText()
* @method static string EmailProjectStalled()
* @method static string EmailProjectStalledText()
* @method static string Error(?Exception $exception)
* @method static string FeedHowTo()
* @method static string Footer()
* @method static string Header(?string $title = null, ?string $highlight = null, ?string $description = null, bool $isManual = false, bool $isXslt = false, ?string $feedUrl = null, ?string $feedTitle = null, bool $isErrorPage = false, ?string $downloadUrl = null, ?string $canonicalUrl = null, ?string $coverUrl = null, string $ogType = 'website', array<string> $css = [])
* @method static string ImageCopyrightNotice()
* @method static string OpdsAcquisitionEntry(Ebook $entry)
* @method static string OpdsAcquisitionFeed(string $id, string $url, string $parentUrl, string $title, ?string $subtitle, DateTimeImmutable $updated, array<Ebook> $entries, bool $isCrawlable = false)
* @method static string OpdsNavigationFeed(string $id, string $url, ?string $parentUrl, string $title, ?string $subtitle, DateTimeImmutable $updated, array<OpdsNavigationEntry> $entries)
* @method static string ProjectDetailsTable(Project $project, bool $useFullyQualifiedUrls = false, bool $showTitle = true, bool $showArtworkStatus = true)
* @method static string ProjectForm(Project $project, $areFieldsRequired = true, $isEditForm = false)
* @method static string ProjectsTable(array<Project> $projects, bool $includeTitle = true, bool $includeStatus = true, bool $showEditButton = false)
* @method static string RealisticEbook(Ebook $ebook)
* @method static string RssEntry(Ebook $entry)
* @method static string RssFeed(string $title, string $description, DateTimeImmutable $updated, string $url, array<Ebook> $entries)
* @method static string SearchForm(string $query, array<string> $tags, Enums\EbookSortType $sort, Enums\ViewType $view, int $perPage)
* @method static string UserForm(User $user, Enums\PasswordActionType $passwordAction, bool $generateNewUuid, bool $isEditForm = false)
* @method static string WantedEbooksList(array<Ebook> $ebooks, bool $showPlaceholderMetadata)
*/ */
protected static function Get(string $templateName, array $arguments = []): string{ class Template extends TemplateBase{
// Expand the passed variables to make them available to the included template.
// We use these funny names so that we can use 'name' and 'value' as template variables if we want to.
foreach($arguments as $innerName => $innerValue){
$$innerName = $innerValue;
}
ob_start();
include(TEMPLATES_PATH . '/' . $templateName . '.php');
$contents = ob_get_contents() ?: '';
ob_end_clean();
return $contents;
}
/**
* @param array<mixed> $arguments
*/
public static function __callStatic(string $function, array $arguments): string{
if(isset($arguments[0]) && is_array($arguments[0])){
return self::Get($function, $arguments[0]);
}
else{
return self::Get($function, $arguments);
}
}
/**
* Exit the script while outputting the given HTTP code.
*
* @param bool $showPage If **`TRUE`**, show a special page given the HTTP code (like a 404 page).
*
* @return never
*/
public static function ExitWithCode(Enums\HttpCode $httpCode, bool $showPage = true, Enums\HttpRequestType $requestType = Enums\HttpRequestType::Web): void{
http_response_code($httpCode->value);
if($requestType == Enums\HttpRequestType::Web && $showPage){
switch($httpCode){
case Enums\HttpCode::Forbidden:
include(WEB_ROOT . '/403.php');
break;
case Enums\HttpCode::NotFound:
include(WEB_ROOT . '/404.php');
break;
}
}
exit();
}
/** /**
* Redirect the user to the login page. * Redirect the user to the login page.
* *

80
lib/TemplateBase.php Normal file
View file

@ -0,0 +1,80 @@
<?
use function Safe\ob_end_clean;
use function Safe\ob_start;
/**
* A simple templating class that reads directly from PHP files.
*
* The `Template` class must extend the `TemplateBase` class. Methods corresponding to each template file are annotated using PHPDoc on the `Template` class.
*
* Place template files in `TEMPLATES_PATH`. Calling `Template::MyTemplateFilename(variable: value ...)` will expand passed in variables, execute `TEMPLATES_PATH/MyTemplateFilename.php`, and output the string contents of the executed PHP. For example:
*
* ````php
* <?
* // Outputs the contents of ``TEMPLATES_PATH`/Header.php`. Inside that file, the variable `$title` will be available.
* print(Template::Header(title: 'My Title'));
* ````
*
* # Template conventions
*
* At the top of each template file, use PHPDoc to define required variables and nullable optional variables that are `null` by default. Next, optional variables with default values are defined with the `$varName ??= $defaultValue;` pattern. For example:
*
* ````php
* <?
* // TEMPLATES_PATH/Header.php
*
* // @var string $title Required.
* // @var ?string $url Optional but nullable, we define the type of `string` here.
*
* $url ??= null; // Optional and nullable. The type was defined in the above PHPDoc, and we set the default as `null` here.
* $isFeed ??= false; // Optional and not nullable. Both the type and the default value are set here.
* ?>
* <?= $title ?>
* <? if($isFeed){ ?>
* <?= $url ?>
* <? } ?>
* ````
*/
abstract class TemplateBase{
/**
* @param array<string, mixed> $arguments
*/
public static function __callStatic(string $function, array $arguments): string{
// Expand the passed variables to make them available to the included template.
// We use these funny names so that we can use `name` and `value` as template variables if we want to.
foreach($arguments as $innerName => $innerValue){
$$innerName = $innerValue;
}
ob_start();
include(TEMPLATES_PATH . '/' . $function . '.php');
$contents = ob_get_contents() ?: '';
ob_end_clean();
return $contents;
}
/**
* Exit the script while outputting the given HTTP code.
*
* @param bool $showPage If **`TRUE`**, show a special page given the HTTP code (like a 404 page).
*
* @return never
*/
public static function ExitWithCode(Enums\HttpCode $httpCode, bool $showPage = true, Enums\HttpRequestType $requestType = Enums\HttpRequestType::Web): void{
http_response_code($httpCode->value);
if($requestType == Enums\HttpRequestType::Web && $showPage){
switch($httpCode){
case Enums\HttpCode::Forbidden:
include(WEB_ROOT . '/403.php');
break;
case Enums\HttpCode::NotFound:
include(WEB_ROOT . '/404.php');
break;
}
}
exit();
}
}

View file

@ -217,8 +217,8 @@ catch(Exception $ex){
$em = new Email(true); $em = new Email(true);
$em->To = ADMIN_EMAIL_ADDRESS; $em->To = ADMIN_EMAIL_ADDRESS;
$em->Subject = 'Ingesting FA donations failed'; $em->Subject = 'Ingesting FA donations failed';
$em->Body = Template::EmailDonationProcessingFailed(['exception' => preg_replace('/^/m', "\t", $exceptionString)]); $em->Body = Template::EmailDonationProcessingFailed(exception: preg_replace('/^/m', "\t", $exceptionString));
$em->TextBody = Template::EmailDonationProcessingFailedText(['exception' => preg_replace('/^/m', "\t", $exceptionString)]); $em->TextBody = Template::EmailDonationProcessingFailedText(exception: preg_replace('/^/m', "\t", $exceptionString));
$em->Send(); $em->Send();
throw $ex; throw $ex;

View file

@ -277,8 +277,8 @@ try{
$em->To = ADMIN_EMAIL_ADDRESS; $em->To = ADMIN_EMAIL_ADDRESS;
$em->From = ADMIN_EMAIL_ADDRESS; $em->From = ADMIN_EMAIL_ADDRESS;
$em->Subject = 'New Patrons Circle member'; $em->Subject = 'New Patrons Circle member';
$em->Body = Template::EmailAdminNewPatron(['patron' => $patron, 'payment' => $payment]); $em->Body = Template::EmailAdminNewPatron(patron: $patron, payment: $payment);
$em->TextBody = Template::EmailAdminNewPatronText(['patron' => $patron, 'payment' => $payment]);; $em->TextBody = Template::EmailAdminNewPatronText(patron: $patron, payment: $payment);;
$em->Send(); $em->Send();
} }
} }
@ -324,8 +324,8 @@ catch(Exception $ex){
$em = new Email(true); $em = new Email(true);
$em->To = ADMIN_EMAIL_ADDRESS; $em->To = ADMIN_EMAIL_ADDRESS;
$em->Subject = 'Donation processing failed'; $em->Subject = 'Donation processing failed';
$em->Body = Template::EmailDonationProcessingFailed(['exception' => preg_replace('/^/m', "\t", $exceptionString)]); $em->Body = Template::EmailDonationProcessingFailed(exception: preg_replace('/^/m', "\t", $exceptionString));
$em->TextBody = Template::EmailDonationProcessingFailedText(['exception' => preg_replace('/^/m', "\t", $exceptionString)]); $em->TextBody = Template::EmailDonationProcessingFailedText(exception: preg_replace('/^/m', "\t", $exceptionString));
$em->Send(); $em->Send();
throw $ex; throw $ex;

View file

@ -1,14 +1,9 @@
<? <?
/** /**
* @var ?Artwork $artwork * @var Artwork $artwork
*/ */
if($artwork === null){ $isEditForm ??= false;
$artwork = new Artwork();
$artwork->Artist = new Artist();
}
$isEditForm = $isEditForm ?? false;
?> ?>
<fieldset> <fieldset>
<legend>Artist details</legend> <legend>Artist details</legend>

View file

@ -4,7 +4,7 @@
*/ */
?> ?>
<ol class="artwork-list"> <ol class="artwork-list">
<? foreach($artworks as $artwork){ ?> <? foreach($artworks as $artwork){ ?>
<? <?
$class = ''; $class = '';
@ -32,5 +32,5 @@
</picture> </picture>
</a> </a>
</li> </li>
<? } ?> <? } ?>
</ol> </ol>

View file

@ -1,16 +0,0 @@
<?
/**
* @var Artwork $artwork
*/
?>
<?= ucfirst($artwork->Status->value) ?>
<? if($artwork->EbookUrl !== null){ ?>
in use by
<? if($artwork->Ebook !== null && $artwork->Ebook->Url !== null){ ?>
<i>
<a href="<?= $artwork->Ebook->Url ?>"><?= Formatter::EscapeHtml($artwork->Ebook->Title) ?></a>
</i><? if($artwork->Ebook->IsPlaceholder()){ ?>(unreleased)<? } ?>
<? }else{ ?>
<code><?= Formatter::EscapeHtml($artwork->EbookUrl) ?></code> (unreleased)
<? } ?>
<? } ?>

View file

@ -8,7 +8,7 @@
* @var array<Ebook> $entries * @var array<Ebook> $entries
*/ */
$subtitle = $subtitle ?? null; $subtitle ??= null;
// Note that the XSL stylesheet gets stripped during `se clean` when we generate the feed. // Note that the XSL stylesheet gets stripped during `se clean` when we generate the feed.
// `se clean` will also start adding empty namespaces everywhere if we include the stylesheet declaration first. // `se clean` will also start adding empty namespaces everywhere if we include the stylesheet declaration first.
@ -28,6 +28,6 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
</author> </author>
<link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml" /> <link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml" />
<? foreach($entries as $entry){ ?> <? foreach($entries as $entry){ ?>
<?= Template::AtomFeedEntry(['entry' => $entry]) ?> <?= Template::AtomFeedEntry(entry: $entry) ?>
<? } ?> <? } ?>
</feed> </feed>

View file

@ -1,12 +1,17 @@
<? <?
use function Safe\preg_replace; use function Safe\preg_replace;
$collectionMembership = $collectionMembership ?? null; /**
* @var ?CollectionMembership $collectionMembership
*/
$collection = $collectionMembership?->Collection; $collection = $collectionMembership?->Collection;
$sequenceNumber = $collectionMembership?->SequenceNumber; $sequenceNumber = $collectionMembership?->SequenceNumber;
?> ?>
<? if($sequenceNumber !== null){ ?>№ <?= number_format($sequenceNumber) ?> in the<? }else{ ?>Part of the<? } ?> <a href="<?= $collection->Url ?>" property="schema:isPartOf"><?= Formatter::EscapeHtml(preg_replace('/^The /ius', '', (string)$collection->Name)) ?></a> <? if($collection !== null){ ?>
<? if($collection->Type !== null){ ?> <? if($sequenceNumber !== null){ ?>№ <?= number_format($sequenceNumber) ?> in the<? }else{ ?>Part of the<? } ?> <a href="<?= $collection->Url ?>" property="schema:isPartOf"><?= Formatter::EscapeHtml(preg_replace('/^The /ius', '', $collection->Name)) ?></a>
<? } ?>
<? if($collection?->Type !== null){ ?>
<? if(substr_compare(mb_strtolower($collection->Name), mb_strtolower($collection->Type->value), -strlen(mb_strtolower($collection->Type->value))) !== 0){ ?><?= $collection->Type->value ?><? } ?> <? if(substr_compare(mb_strtolower($collection->Name), mb_strtolower($collection->Type->value), -strlen(mb_strtolower($collection->Type->value))) !== 0){ ?><?= $collection->Type->value ?><? } ?>
<? }else{ ?> <? }else{ ?>
collection collection

View file

@ -4,8 +4,8 @@ if(!DONATION_DRIVE_COUNTER_ENABLED || ($autoHide ?? (HttpInput::Bool(COOKIE, 'hi
return; return;
} }
$autoHide = $autoHide ?? true; $autoHide ??= true;
$showDonateButton = $showDonateButton ?? true; $showDonateButton ??= true;
$current = 0; $current = 0;
if(NOW < DONATION_DRIVE_COUNTER_START || NOW > DONATION_DRIVE_COUNTER_END){ if(NOW < DONATION_DRIVE_COUNTER_START || NOW > DONATION_DRIVE_COUNTER_END){

View file

@ -13,8 +13,8 @@ if(
return; return;
} }
$autoHide = $autoHide ?? true; $autoHide ??= true;
$showDonateButton = $showDonateButton ?? true; $showDonateButton ??= true;
$deadline = $donationDrive->End->format('F j'); $deadline = $donationDrive->End->format('F j');
$timeLeft = NOW->diff($donationDrive->End); $timeLeft = NOW->diff($donationDrive->End);

View file

@ -3,7 +3,7 @@
* @var array<Ebook> $carousel * @var array<Ebook> $carousel
*/ */
$isMultiSize = $isMultiSize ?? false; $isMultiSize ??= false;
?> ?>
<? if(sizeof($carousel) > 0){ ?> <? if(sizeof($carousel) > 0){ ?>
<ul class="ebook-carousel<? if($isMultiSize){ ?> multi-size<? } ?>"> <ul class="ebook-carousel<? if($isMultiSize){ ?> multi-size<? } ?>">

View file

@ -1,11 +1,11 @@
<? <?
/** /**
* @var ?Collection $collection
* @var array<Ebook> $ebooks * @var array<Ebook> $ebooks
* @var ?Collection $collection
*/ */
$view = $view ?? Enums\ViewType::Grid; $view ??= Enums\ViewType::Grid;
$collection = $collection ?? null; $collection ??= null;
?> ?>
<ol class="ebooks-list<? if($view == Enums\ViewType::List){ ?> list<? }else{ ?> grid<? } ?>"<? if($collection !== null){ ?> typeof="schema:BookSeries" about="<?= $collection->Url ?>"<? } ?>> <ol class="ebooks-list<? if($view == Enums\ViewType::List){ ?> list<? }else{ ?> grid<? } ?>"<? if($collection !== null){ ?> typeof="schema:BookSeries" about="<?= $collection->Url ?>"<? } ?>>
<? if($collection !== null){ ?> <? if($collection !== null){ ?>

View file

@ -3,7 +3,7 @@
* @var Ebook $ebook * @var Ebook $ebook
*/ */
$showPlaceholderMetadata = $showPlaceholderMetadata ?? false; $showPlaceholderMetadata ??= false;
?> ?>
<section id="metadata"> <section id="metadata">
<h2>Metadata</h2> <h2>Metadata</h2>

View file

@ -1,7 +1,10 @@
<? <?
$ebook = $ebook ?? new Ebook(); /**
$isEditForm = $isEditForm ?? false; * @var Ebook $ebook
$showProjectForm = $showProjectForm ?? true; */
$isEditForm ??= false;
$showProjectForm ??= true;
?> ?>
<fieldset> <fieldset>
<legend>Contributors</legend> <legend>Contributors</legend>
@ -204,7 +207,7 @@ $showProjectForm = $showProjectForm ?? true;
/> />
</label> </label>
<fieldset class="project-form"> <fieldset class="project-form">
<?= Template::ProjectForm(['project' => $ebook->ProjectInProgress, 'areFieldsRequired' => false]) ?> <?= Template::ProjectForm(project: $ebook->ProjectInProgress ?? new Project(), areFieldsRequired: false) ?>
</fieldset> </fieldset>
</fieldset> </fieldset>
<? } ?> <? } ?>

View file

@ -1,5 +1,5 @@
<? <?
$includeLinks = $includeLinks ?? true; $includeLinks ??= true;
?> ?>
<div class="footer<? if(!$includeLinks){ ?> no-links<? } ?>"> <div class="footer<? if(!$includeLinks){ ?> no-links<? } ?>">
<? if($includeLinks){ ?> <? if($includeLinks){ ?>

View file

@ -1,7 +1,11 @@
<? <?
$preheader = $preheader ?? null; /**
$letterhead = $letterhead ?? false; * @var ?string $preheader
$hasAdminTable = $hasAdminTable ?? false; */
$preheader ??= null;
$hasLetterhead ??= false;
$hasAdminTable ??= false;
?><!DOCTYPE html> ?><!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -38,7 +42,7 @@ $hasAdminTable = $hasAdminTable ?? false;
-webkit-text-size-adjust: none; -webkit-text-size-adjust: none;
} }
<? if($letterhead){ ?> <? if($hasLetterhead){ ?>
div.body.letterhead{ div.body.letterhead{
background-image: url("https://standardebooks.org/images/logo-email.png"); background-image: url("https://standardebooks.org/images/logo-email.png");
background-position: top 2em center; background-position: top 2em center;
@ -222,7 +226,7 @@ $hasAdminTable = $hasAdminTable ?? false;
color: #4f9d85; color: #4f9d85;
} }
<? if($letterhead){ ?> <? if($hasLetterhead){ ?>
div.body.letterhead{ div.body.letterhead{
background-image: url("https://standardebooks.org/images/logo-email-dark.png"); background-image: url("https://standardebooks.org/images/logo-email-dark.png");
} }
@ -235,7 +239,7 @@ $hasAdminTable = $hasAdminTable ?? false;
</style> </style>
</head> </head>
<body> <body>
<div class="body<? if($letterhead){ ?> letterhead<? } ?>"> <div class="body<? if($hasLetterhead){ ?> letterhead<? } ?>">
<? if($preheader){ ?> <? if($preheader){ ?>
<p class="preheader"><?= Formatter::EscapeHtml($preheader) ?><? for($i = 0; $i < 150 - strlen($preheader); $i++){ ?>&zwnj;&nbsp;<? } ?></p> <p class="preheader"><?= Formatter::EscapeHtml($preheader) ?><? for($i = 0; $i < 150 - strlen($preheader); $i++){ ?>&zwnj;&nbsp;<? } ?></p>
<? } ?> <? } ?>

View file

@ -4,9 +4,9 @@
* @var string $role * @var string $role
* @var User $user * @var User $user
*/ */
?><?= Template::EmailHeader(['hasAdminTable' => true, 'letterhead' => true]) ?> ?><?= Template::EmailHeader(hasAdminTable: true, hasLetterhead: true) ?>
<p>Youve been assigned a new ebook project to <strong><?= $role ?></strong>:</p> <p>Youve been assigned a new ebook project to <strong><?= $role ?></strong>:</p>
<?= Template::ProjectDetailsTable(['project' => $project, 'useFullyQualifiedUrls' => true, 'showArtworkStatus' => false]) ?> <?= Template::ProjectDetailsTable(project: $project, useFullyQualifiedUrls: true, showArtworkStatus: false) ?>
<p>If youre unable to <?= $role ?> this ebook project, <a href="mailto:<?= EDITOR_IN_CHIEF_EMAIL_ADDRESS ?>">email the Editor-in-Chief</a> and well reassign it.</p> <p>If youre unable to <?= $role ?> this ebook project, <a href="mailto:<?= EDITOR_IN_CHIEF_EMAIL_ADDRESS ?>">email the Editor-in-Chief</a> and well reassign it.</p>
<ul> <ul>
<li> <li>
@ -20,4 +20,4 @@
</p> </p>
</li> </li>
</ul> </ul>
<?= Template::EmailFooter(['includeLinks' => false]) ?> <?= Template::EmailFooter(includeLinks: false) ?>

View file

@ -2,7 +2,7 @@
/** /**
* @var int $ebooksThisYear * @var int $ebooksThisYear
*/ */
?><?= Template::EmailHeader(['letterhead' => true]) ?> ?><?= Template::EmailHeader(hasLetterhead: true) ?>
<p>Hello,</p> <p>Hello,</p>
<p>Last year, your generous donation to <a href="<?= SITE_URL ?>">Standard Ebooks</a> made it possible for us to continue producing beautiful ebook editions for free distribution.</p> <p>Last year, your generous donation to <a href="<?= SITE_URL ?>">Standard Ebooks</a> made it possible for us to continue producing beautiful ebook editions for free distribution.</p>
<p>It also allowed me to add you to our <a href="<?= SITE_URL ?>/about#patrons-circle">Patrons Circle</a>, a group of donors who are honored on our masthead, and who have a direct voice in the future of our <a href="<?= SITE_URL ?>/ebooks">ebook catalog</a>, for one year.</p> <p>It also allowed me to add you to our <a href="<?= SITE_URL ?>/about#patrons-circle">Patrons Circle</a>, a group of donors who are honored on our masthead, and who have a direct voice in the future of our <a href="<?= SITE_URL ?>/ebooks">ebook catalog</a>, for one year.</p>

View file

@ -1,4 +1,4 @@
<?= Template::EmailHeader(['letterhead' => true]) ?> <?= Template::EmailHeader(hasLetterhead: true) ?>
<p>Hello,</p> <p>Hello,</p>
<p>I couldnt help but notice that your monthly donation to Standard Ebooks has recently ended. Your generous donation allowed me to add you to our <a href="<?= SITE_URL ?>/about#patrons-circle">Patrons Circle</a>, a group of donors who are honored on our masthead, and who have a direct voice in the future of our <a href="<?= SITE_URL ?>/ebooks">ebook catalog</a>.</p> <p>I couldnt help but notice that your monthly donation to Standard Ebooks has recently ended. Your generous donation allowed me to add you to our <a href="<?= SITE_URL ?>/about#patrons-circle">Patrons Circle</a>, a group of donors who are honored on our masthead, and who have a direct voice in the future of our <a href="<?= SITE_URL ?>/ebooks">ebook catalog</a>.</p>
<p>Oftentimes credit cards will expire, or recurring charges will get accidentally canceled for any number of nebulous administrative reasons. If you didnt mean to cancel your recurring donation—and thus your Patrons Circle membership—nows a great time to <a href="<?= SITE_URL ?>/donate#patrons-circle">renew it</a>.</p> <p>Oftentimes credit cards will expire, or recurring charges will get accidentally canceled for any number of nebulous administrative reasons. If you didnt mean to cancel your recurring donation—and thus your Patrons Circle membership—nows a great time to <a href="<?= SITE_URL ?>/donate#patrons-circle">renew it</a>.</p>

View file

@ -2,18 +2,32 @@
use Safe\DateTimeImmutable; use Safe\DateTimeImmutable;
use function Safe\filemtime; use function Safe\filemtime;
$title = $title ?? ''; /**
$highlight = $highlight ?? ''; * @var ?string $title
$description = $description ?? ''; * @var ?string $highlight
$manual = $manual ?? false; * @var ?string $description
* @var ?string $feedUrl
* @var ?string $feedTitle
* @var ?string $downloadUrl
* @var ?string $canonicalUrl
* @var ?string $coverUrl
*/
$title ??= null;
$highlight ??= null;
$description ??= null;
$feedUrl ??= null;
$feedTitle ??= null;
$downloadUrl ??= null;
$canonicalUrl ??= null;
$coverUrl ??= null;
$css ??= [];
$isManual ??= false;
$isXslt ??= false;
$isErrorPage ??= false;
$ogType ??= 'website';
$colorScheme = Enums\ColorSchemeType::tryFrom(HttpInput::Str(COOKIE, 'color-scheme') ?? Enums\ColorSchemeType::Auto->value); $colorScheme = Enums\ColorSchemeType::tryFrom(HttpInput::Str(COOKIE, 'color-scheme') ?? Enums\ColorSchemeType::Auto->value);
$isXslt = $isXslt ?? false;
$feedUrl = $feedUrl ?? null;
$feedTitle = $feedTitle ?? '';
$isErrorPage = $isErrorPage ?? false;
$downloadUrl = $downloadUrl ?? null;
$canonicalUrl = $canonicalUrl ?? null;
$css = $css ?? [];
$showPublicDomainDayBanner = PD_NOW > new DateTimeImmutable('January 1, 8:00 AM', SITE_TZ) && PD_NOW < new DateTimeImmutable('January 14', LATEST_CONTINENTAL_US_TZ) && !(HttpInput::Bool(COOKIE, 'hide-public-domain-day-banner') ?? false); $showPublicDomainDayBanner = PD_NOW > new DateTimeImmutable('January 1, 8:00 AM', SITE_TZ) && PD_NOW < new DateTimeImmutable('January 14', LATEST_CONTINENTAL_US_TZ) && !(HttpInput::Bool(COOKIE, 'hide-public-domain-day-banner') ?? false);
// As of Sep. 2022, all versions of Safari have a bug where if the page is served as XHTML, then `<picture>` elements download all `<source>`s instead of the first supported match. // As of Sep. 2022, all versions of Safari have a bug where if the page is served as XHTML, then `<picture>` elements download all `<source>`s instead of the first supported match.
@ -41,8 +55,8 @@ if(!$isXslt){
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">
<head prefix="twitter: https://twitter.com/ schema: http://schema.org/"><? /* The `og` RDFa prefix is part of the RDFa spec */ ?> <head prefix="twitter: https://twitter.com/ schema: http://schema.org/"><? /* The `og` RDFa prefix is part of the RDFa spec */ ?>
<meta charset="utf-8"/> <meta charset="utf-8"/>
<title><? if($title != ''){ ?><?= Formatter::EscapeHtml($title) ?> - <? } ?>Standard Ebooks: Free and liberated ebooks, carefully produced for the true book lover</title> <title><? if($title !== null){ ?><?= Formatter::EscapeHtml($title) ?> - <? } ?>Standard Ebooks: Free and liberated ebooks, carefully produced for the true book lover</title>
<? if($description != ''){ ?> <? if($description !== null){ ?>
<meta content="<?= Formatter::EscapeHtml($description) ?>" name="description"/> <meta content="<?= Formatter::EscapeHtml($description) ?>" name="description"/>
<? } ?> <? } ?>
<meta content="width=device-width, initial-scale=1" name="viewport"/> <meta content="width=device-width, initial-scale=1" name="viewport"/>
@ -59,7 +73,7 @@ if(!$isXslt){
<link href="/css/dark.css?version=<?= filemtime(WEB_ROOT . '/css/dark.css') ?>" media="screen<? if($colorScheme == Enums\ColorSchemeType::Auto){ ?> and (prefers-color-scheme: dark)<? } ?>" rel="stylesheet" type="text/css"/> <link href="/css/dark.css?version=<?= filemtime(WEB_ROOT . '/css/dark.css') ?>" media="screen<? if($colorScheme == Enums\ColorSchemeType::Auto){ ?> and (prefers-color-scheme: dark)<? } ?>" rel="stylesheet" type="text/css"/>
<? } ?> <? } ?>
<? } ?> <? } ?>
<? if($manual){ ?> <? if($isManual){ ?>
<link href="/css/manual.css?version=<?= filemtime(WEB_ROOT . '/css/manual.css') ?>" media="screen" rel="stylesheet" type="text/css"/> <link href="/css/manual.css?version=<?= filemtime(WEB_ROOT . '/css/manual.css') ?>" media="screen" rel="stylesheet" type="text/css"/>
<? if($colorScheme == Enums\ColorSchemeType::Auto || $colorScheme == Enums\ColorSchemeType::Dark){ ?> <? if($colorScheme == Enums\ColorSchemeType::Auto || $colorScheme == Enums\ColorSchemeType::Dark){ ?>
<link href="/css/manual-dark.css?version=<?= filemtime(WEB_ROOT . '/css/manual-dark.css') ?>" media="screen<? if($colorScheme == Enums\ColorSchemeType::Auto){ ?> and (prefers-color-scheme: dark)<? } ?>" rel="stylesheet" type="text/css"/> <link href="/css/manual-dark.css?version=<?= filemtime(WEB_ROOT . '/css/manual-dark.css') ?>" media="screen<? if($colorScheme == Enums\ColorSchemeType::Auto){ ?> and (prefers-color-scheme: dark)<? } ?>" rel="stylesheet" type="text/css"/>
@ -68,7 +82,7 @@ if(!$isXslt){
<? foreach($css as $url){ ?> <? foreach($css as $url){ ?>
<link href="<?= Formatter::EscapeHtml($url) ?>?version=<?= filemtime(WEB_ROOT . $url) ?>" media="screen" rel="stylesheet" type="text/css"/> <link href="<?= Formatter::EscapeHtml($url) ?>?version=<?= filemtime(WEB_ROOT . $url) ?>" media="screen" rel="stylesheet" type="text/css"/>
<? } ?> <? } ?>
<? if($canonicalUrl){ ?> <? if($canonicalUrl !== null){ ?>
<link rel="canonical" href="<?= Formatter::EscapeHtml($canonicalUrl) ?>" /> <link rel="canonical" href="<?= Formatter::EscapeHtml($canonicalUrl) ?>" />
<? } ?> <? } ?>
<link href="/apple-touch-icon-120x120.png" rel="apple-touch-icon" sizes="120x120"/> <link href="/apple-touch-icon-120x120.png" rel="apple-touch-icon" sizes="120x120"/>
@ -90,8 +104,8 @@ if(!$isXslt){
<link rel="search" href="/ebooks/opensearch" type="application/opensearchdescription+xml; charset=utf-8"/> <link rel="search" href="/ebooks/opensearch" type="application/opensearchdescription+xml; charset=utf-8"/>
<? if(!$isErrorPage){ ?> <? if(!$isErrorPage){ ?>
<meta content="#394451" name="theme-color"/> <meta content="#394451" name="theme-color"/>
<meta content="<? if($title != ''){ ?><?= Formatter::EscapeHtml($title) ?><? }else{ ?>Standard Ebooks<? } ?>" property="og:title"/> <meta content="<? if($title !== null){ ?><?= Formatter::EscapeHtml($title) ?><? }else{ ?>Standard Ebooks<? } ?>" property="og:title"/>
<meta content="<?= $ogType ?? 'website' ?>" property="og:type"/> <meta content="<?= $ogType ?>" property="og:type"/>
<meta content="<?= $pageUrl ?>" property="og:url"/> <meta content="<?= $pageUrl ?>" property="og:url"/>
<meta content="<?= SITE_URL . ($coverUrl ?? '/images/logo.png') ?>" property="og:image"/> <meta content="<?= SITE_URL . ($coverUrl ?? '/images/logo.png') ?>" property="og:image"/>
<meta content="summary_large_image" name="twitter:card"/> <meta content="summary_large_image" name="twitter:card"/>

View file

@ -3,7 +3,7 @@
* Notes: * Notes:
* *
* - *All* OPDS feeds must contain a `rel="http://opds-spec.org/crawlable"` link pointing to the `/feeds/opds/all` feed. * - *All* OPDS feeds must contain a `rel="http://opds-spec.org/crawlable"` link pointing to the `/feeds/opds/all` feed.
* - The `<fh:complete/>` element is required to note this as a "Complete Acquisition Feeds"; see <https://specs.opds.io/opds-1.2#25-complete-acquisition-feeds>. * - The `<fh:complete/>` element is required to note this as a "Complete Acquisition Feed"; see <https://specs.opds.io/opds-1.2#25-complete-acquisition-feeds>.
*/ */
/** /**
@ -16,8 +16,8 @@
* @var array<Ebook> $entries * @var array<Ebook> $entries
*/ */
$isCrawlable = $isCrawlable ?? false; $isCrawlable ??= false;
$subtitle = $subtitle ?? null; $subtitle ??= null;
// Note that the XSL stylesheet gets stripped during `se clean` when we generate the feed. // Note that the XSL stylesheet gets stripped during `se clean` when we generate the feed.
// `se clean` will also start adding empty namespaces everywhere if we include the stylesheet declaration first. // `se clean` will also start adding empty namespaces everywhere if we include the stylesheet declaration first.
@ -45,6 +45,6 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
<uri><?= SITE_URL ?></uri> <uri><?= SITE_URL ?></uri>
</author> </author>
<? foreach($entries as $entry){ ?> <? foreach($entries as $entry){ ?>
<?= Template::OpdsAcquisitionEntry(['entry' => $entry]) ?> <?= Template::OpdsAcquisitionEntry(entry: $entry) ?>
<? } ?> <? } ?>
</feed> </feed>

View file

@ -9,7 +9,7 @@
* @var array<OpdsNavigationEntry> $entries * @var array<OpdsNavigationEntry> $entries
*/ */
$subtitle = $subtitle ?? null; $subtitle ??= null;
// Note that the XSL stylesheet gets stripped during `se clean` when we generate the feed. // Note that the XSL stylesheet gets stripped during `se clean` when we generate the feed.
// `se clean` will also start adding empty namespaces everywhere if we include the stylesheet declaration first. // `se clean` will also start adding empty namespaces everywhere if we include the stylesheet declaration first.

View file

@ -1,13 +1,13 @@
<? <?
use Enums\HttpMethod;
/** /**
* @var Project $project * @var Project $project
*/ */
use Enums\HttpMethod; $useFullyQualifiedUrls ??= false;
$showTitle ??= true;
$useFullyQualifiedUrls = $useFullyQualifiedUrls ?? false; $showArtworkStatus ??= true;
$showTitle = $showTitle ?? true;
$showArtworkStatus = $showArtworkStatus ?? true;
?> ?>
<table class="admin-table"> <table class="admin-table">
<tbody> <tbody>

View file

@ -1,10 +1,14 @@
<? <?
$project = $project ?? new Project(); /**
* @var Project $project
*/
$areFieldsRequired ??= true;
$isEditForm ??= false;
$managers = User::GetAllByCanManageProjects(); $managers = User::GetAllByCanManageProjects();
$reviewers = User::GetAllByCanReviewProjects(); $reviewers = User::GetAllByCanReviewProjects();
$pastProducers = User::GetNamesByHasProducedProject(); $pastProducers = User::GetNamesByHasProducedProject();
$areFieldsRequired = $areFieldsRequired ?? true;
$isEditForm = $isEditForm ?? false;
?> ?>
<? if(!$isEditForm){ ?> <? if(!$isEditForm){ ?>
<input type="hidden" name="project-ebook-id" value="<?= $project->EbookId ?? '' ?>" /> <input type="hidden" name="project-ebook-id" value="<?= $project->EbookId ?? '' ?>" />

View file

@ -3,9 +3,9 @@
* @var array<Project> $projects * @var array<Project> $projects
*/ */
$includeTitle = $includeTitle ?? true; $includeTitle ??= true;
$includeStatus = $includeStatus ?? true; $includeStatus ??= true;
$showEditButton = $showEditButton ?? false; $showEditButton ??= false;
?> ?>
<table class="data-table"> <table class="data-table">
<caption aria-hidden="true">Scroll right </caption> <caption aria-hidden="true">Scroll right </caption>

View file

@ -31,7 +31,7 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
<width>144</width> <width>144</width>
</image> </image>
<? foreach($entries as $entry){ ?> <? foreach($entries as $entry){ ?>
<?= Template::RssEntry(['entry' => $entry]) ?> <?= Template::RssEntry(entry: $entry) ?>
<? } ?> <? } ?>
</channel> </channel>
</rss> </rss>

View file

@ -1,5 +1,6 @@
<? <?
/** /**
* @var string $query
* @var array<string> $tags * @var array<string> $tags
* @var Enums\EbookSortType $sort * @var Enums\EbookSortType $sort
* @var Enums\ViewType $view * @var Enums\ViewType $view
@ -18,13 +19,13 @@ $isAllSelected = sizeof($tags) == 0 || in_array('all', $tags);
</select> </select>
</label> </label>
<label>Keywords <label>Keywords
<input type="search" name="query" value="<?= Formatter::EscapeHtml($query ?? '') ?>"/> <input type="search" name="query" value="<?= Formatter::EscapeHtml($query) ?>"/>
</label> </label>
<label class="sort"> <label class="sort">
<span>Sort</span> <span>Sort</span>
<span> <span>
<select name="sort"> <select name="sort">
<? if(isset($query) && $query != ''){ ?> <? if($query != ''){ ?>
<option value="<?= Enums\EbookSortType::Relevance->value ?>"<? if($sort == Enums\EbookSortType::Relevance){ ?> selected="selected"<? } ?>>Relevance</option> <option value="<?= Enums\EbookSortType::Relevance->value ?>"<? if($sort == Enums\EbookSortType::Relevance){ ?> selected="selected"<? } ?>>Relevance</option>
<option value="<?= Enums\EbookSortType::Newest->value ?>"<? if($sort == Enums\EbookSortType::Newest){ ?> selected="selected"<? } ?>>S.E. release date (new &#x2192; old)</option> <option value="<?= Enums\EbookSortType::Newest->value ?>"<? if($sort == Enums\EbookSortType::Newest){ ?> selected="selected"<? } ?>>S.E. release date (new &#x2192; old)</option>
<? }else{ ?> <? }else{ ?>

View file

@ -1,8 +1,11 @@
<? <?
$user = $user ?? new User(); /**
$isEditForm = $isEditForm ?? false; * @var User $user
$generateNewUuid = $generateNewUuid ?? false; * @var Enums\PasswordActionType $passwordAction;
$passwordAction = $passwordAction ?? Enums\PasswordActionType::None; * @var bool $generateNewUuid
*/
$isEditForm ??= false;
?> ?>
<label class="email"> <label class="email">

View file

@ -1,9 +1,8 @@
<? <?
/** /**
* @var array<Ebook> $ebooks * @var array<Ebook> $ebooks
* @var bool $showPlaceholderMetadata
*/ */
$showPlaceholderMetadata = $showPlaceholderMetadata ?? false;
?> ?>
<ul class="wanted-list"> <ul class="wanted-list">
<? foreach($ebooks as $ebook){ ?> <? foreach($ebooks as $ebook){ ?>
@ -15,7 +14,7 @@ $showPlaceholderMetadata = $showPlaceholderMetadata ?? false;
by <?= Formatter::EscapeHtml($ebook->AuthorsString) ?>. <?= $ebook->ContributorsHtml ?> by <?= Formatter::EscapeHtml($ebook->AuthorsString) ?>. <?= $ebook->ContributorsHtml ?>
<? foreach($ebook->CollectionMemberships as $index => $collectionMembership){ ?> <? foreach($ebook->CollectionMemberships as $index => $collectionMembership){ ?>
<? if($index == 0){ ?><?= Template::CollectionDescriptor(['collectionMembership' => $collectionMembership]) ?><? }else{ ?><?= lcfirst(Template::CollectionDescriptor(['collectionMembership' => $collectionMembership])) ?><? } ?><? if($index < sizeof($ebook->CollectionMemberships) - 1){ ?>, <? } ?><? if($index == sizeof($ebook->CollectionMemberships) - 1){ ?>.<? } ?> <? if($index == 0){ ?><?= Template::CollectionDescriptor(collectionMembership: $collectionMembership) ?><? }else{ ?><?= lcfirst(Template::CollectionDescriptor(collectionMembership: $collectionMembership)) ?><? } ?><? if($index < sizeof($ebook->CollectionMemberships) - 1){ ?>, <? } ?><? if($index == sizeof($ebook->CollectionMemberships) - 1){ ?>.<? } ?>
<? } ?> <? } ?>
<? if(isset($ebook->EbookPlaceholder->Notes)){ ?> <? if(isset($ebook->EbookPlaceholder->Notes)){ ?>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'You Dont Have Permission to View This Page', 'highlight' => '', 'description' => 'You dont have permission to view this page.', 'isErrorPage' => true]) ?> <?= Template::Header(title: 'You Dont Have Permission to View This Page', description: 'You dont have permission to view this page.', isErrorPage: true) ?>
<main> <main>
<section class="narrow has-hero"> <section class="narrow has-hero">
<hgroup> <hgroup>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'We Couldnt Find That Document', 'highlight' => '', 'description' => 'We couldnt find that document.', 'isErrorPage' => true]) ?> <?= Template::Header(title: 'We Couldnt Find That Document', description: 'We couldnt find that document.', isErrorPage: true) ?>
<main> <main>
<section class="narrow has-hero"> <section class="narrow has-hero">
<hgroup> <hgroup>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'This Ebook Is No Longer Available', 'highlight' => '', 'description' => 'This ebook is unavailable due to legal reasons.']) ?> <?= Template::Header(title: 'This Ebook Is No Longer Available', description: 'This ebook is unavailable due to legal reasons.') ?>
<main> <main>
<section class="narrow has-hero"> <section class="narrow has-hero">
<hgroup> <hgroup>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Accessibility', 'highlight' => 'about', 'description' => 'How we make Standard Ebooks accessible.']) ?> <?= Template::Header(title: 'Accessibility', highlight: 'about', description: 'How we make Standard Ebooks accessible.') ?>
<main> <main>
<section class="accessibility narrow"> <section class="accessibility narrow">
<h1>Accessibility</h1> <h1>Accessibility</h1>

View file

@ -35,7 +35,7 @@ $anonymousPatronCount = Db::QueryInt('
) x ) x
'); ');
?><?= Template::Header(['title' => 'About Standard Ebooks', 'highlight' => 'about', 'description' => 'Standard Ebooks is a volunteer-driven effort to produce a collection of high quality, carefully formatted, accessible, open source, and free public domain ebooks that meet or exceed the quality of commercially produced ebooks. The text and cover art in our ebooks is already believed to be in the public domain, and Standard Ebook dedicates its own work to the public domain, thus releasing the entirety of each ebook file into the public domain.']) ?> ?><?= Template::Header(title: 'About Standard Ebooks', highlight: 'about', description: 'Standard Ebooks is a volunteer-driven effort to produce a collection of high quality, carefully formatted, accessible, open source, and free public domain ebooks that meet or exceed the quality of commercially produced ebooks. The text and cover art in our ebooks is already believed to be in the public domain, and Standard Ebook dedicates its own work to the public domain, thus releasing the entirety of each ebook file into the public domain.') ?>
<main> <main>
<article> <article>
<h1>About Standard Ebooks</h1> <h1>About Standard Ebooks</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Our Goals', 'highlight' => 'about', 'description' => 'The goals of Standard Ebooks.']) ?> <?= Template::Header(title: 'Our Goals', highlight: 'about', description: 'The goals of Standard Ebooks.') ?>
<main> <main>
<article> <article>
<h1>Our Goals</h1> <h1>Our Goals</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Standard Ebooks and the Public Domain', 'highlight' => 'about', 'description' => 'The Standard Ebooks philosophy on copyright and the public domain.']) ?> <?= Template::Header(title: 'Standard Ebooks and the Public Domain', highlight: 'about', description: 'The Standard Ebooks philosophy on copyright and the public domain.') ?>
<main> <main>
<article> <article>
<h1>Standard Ebooks and the Public Domain</h1> <h1>Standard Ebooks and the Public Domain</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'What Makes Standard Ebooks Different', 'highlight' => 'about', 'description' => 'How Standard Ebooks differs from other free ebook projects.']) ?> <?= Template::Header(title: 'What Makes Standard Ebooks Different', highlight: 'about', description: 'How Standard Ebooks differs from other free ebook projects.') ?>
<main> <main>
<article> <article>
<h1>What Makes Standard Ebooks Different</h1> <h1>What Makes Standard Ebooks Different</h1>

View file

@ -23,14 +23,14 @@ try{
catch(Exceptions\ArtistNotFoundException){ catch(Exceptions\ArtistNotFoundException){
Template::ExitWithCode(Enums\HttpCode::NotFound); Template::ExitWithCode(Enums\HttpCode::NotFound);
} }
?><?= Template::Header(['title' => 'Artwork by ' . $artworks[0]->Artist->Name, 'css' => ['/css/artwork.css']]) ?> ?><?= Template::Header(title: 'Artwork by ' . $artworks[0]->Artist->Name, css: ['/css/artwork.css']) ?>
<main class="artworks"> <main class="artworks">
<section class="narrow"> <section class="narrow">
<h1>Artwork by <?= Formatter::EscapeHtml($artworks[0]->Artist->Name) ?></h1> <h1>Artwork by <?= Formatter::EscapeHtml($artworks[0]->Artist->Name) ?></h1>
<?= Template::ImageCopyrightNotice() ?> <?= Template::ImageCopyrightNotice() ?>
<?= Template::ArtworkList(['artworks' => $artworks]) ?> <?= Template::ArtworkList(artworks: $artworks) ?>
</section> </section>
</main> </main>
<?= Template::Footer() ?> <?= Template::Footer() ?>

View file

@ -1,16 +1,16 @@
<? <?
use function Safe\session_unset; use function Safe\session_unset;
session_start();
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
$artwork = HttpInput::SessionObject('artwork', Artwork::class);
try{ try{
if(Session::$User === null){ if(Session::$User === null){
throw new Exceptions\LoginRequiredException(); throw new Exceptions\LoginRequiredException();
} }
session_start();
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
$artwork = HttpInput::SessionObject('artwork', Artwork::class);
if($artwork === null){ if($artwork === null){
$artwork = Artwork::GetByUrl(HttpInput::Str(GET, 'artist-url-name'), HttpInput::Str(GET, 'artwork-url-name')); $artwork = Artwork::GetByUrl(HttpInput::Str(GET, 'artist-url-name'), HttpInput::Str(GET, 'artwork-url-name'));
} }
@ -34,21 +34,17 @@ catch(Exceptions\LoginRequiredException){
catch(Exceptions\InvalidPermissionsException){ catch(Exceptions\InvalidPermissionsException){
Template::ExitWithCode(Enums\HttpCode::Forbidden); // No permissions to edit artwork. Template::ExitWithCode(Enums\HttpCode::Forbidden); // No permissions to edit artwork.
} }
?> ?>
<?= Template::Header( <?= Template::Header(
[ title: 'Edit ' . $artwork->Name . ', by ' . $artwork->Artist->Name,
'title' => 'Edit ' . $artwork->Name . ', by ' . $artwork->Artist->Name, css: ['/css/artwork.css'],
'css' => ['/css/artwork.css'], description: 'Edit ' . $artwork->Name . ', by ' . $artwork->Artist->Name . ' in the Standard Ebooks cover art database.'
'highlight' => '',
'description' => 'Edit ' . $artwork->Name . ', by ' . $artwork->Artist->Name . ' in the Standard Ebooks cover art database.'
]
) ?> ) ?>
<main> <main>
<section class="narrow"> <section class="narrow">
<h1>Edit Artwork</h1> <h1>Edit Artwork</h1>
<?= Template::Error(['exception' => $exception]) ?> <?= Template::Error(exception: $exception) ?>
<picture> <picture>
<source srcset="<?= $artwork->Thumb2xUrl ?> 2x, <?= $artwork->ThumbUrl ?> 1x" type="image/jpg"/> <source srcset="<?= $artwork->Thumb2xUrl ?> 2x, <?= $artwork->ThumbUrl ?> 1x" type="image/jpg"/>
@ -57,7 +53,7 @@ catch(Exceptions\InvalidPermissionsException){
<form class="create-update-artwork" method="<?= Enums\HttpMethod::Post->value ?>" action="<?= $artwork->Url ?>" enctype="multipart/form-data" autocomplete="off"> <form class="create-update-artwork" method="<?= Enums\HttpMethod::Post->value ?>" action="<?= $artwork->Url ?>" enctype="multipart/form-data" autocomplete="off">
<input type="hidden" name="_method" value="<?= Enums\HttpMethod::Put->value ?>" /> <input type="hidden" name="_method" value="<?= Enums\HttpMethod::Put->value ?>" />
<?= Template::ArtworkForm(['artwork' => $artwork, 'isEditForm' => true]) ?> <?= Template::ArtworkForm(artwork: $artwork, isEditForm: true) ?>
</form> </form>
</section> </section>
</main> </main>

View file

@ -64,12 +64,12 @@ catch(Exceptions\InvalidPermissionsException){
Template::ExitWithCode(Enums\HttpCode::Forbidden); Template::ExitWithCode(Enums\HttpCode::Forbidden);
} }
?><?= Template::Header(['title' => $artwork->Name, 'css' => ['/css/artwork.css']]) ?> ?><?= Template::Header(title: $artwork->Name, css: ['/css/artwork.css']) ?>
<main class="artworks"> <main class="artworks">
<section class="narrow"> <section class="narrow">
<h1><?= Formatter::EscapeHtml($artwork->Name) ?></h1> <h1><?= Formatter::EscapeHtml($artwork->Name) ?></h1>
<?= Template::Error(['exception' => $exception]) ?> <?= Template::Error(exception: $exception) ?>
<? if($isSaved){ ?> <? if($isSaved){ ?>
<p class="message success">Artwork saved!</p> <p class="message success">Artwork saved!</p>
@ -118,7 +118,19 @@ catch(Exceptions\InvalidPermissionsException){
</tr> </tr>
<tr> <tr>
<td>Status:</td> <td>Status:</td>
<td><?= Template::ArtworkStatus(['artwork' => $artwork]) ?></td> <td>
<?= ucfirst($artwork->Status->value) ?>
<? if($artwork->EbookUrl !== null){ ?>
in use by
<? if($artwork->Ebook !== null && $artwork->Ebook->Url !== null){ ?>
<i>
<a href="<?= $artwork->Ebook->Url ?>"><?= Formatter::EscapeHtml($artwork->Ebook->Title) ?></a>
</i><? if($artwork->Ebook->IsPlaceholder()){ ?>(unreleased)<? } ?>
<? }else{ ?>
<code><?= Formatter::EscapeHtml($artwork->EbookUrl) ?></code> (unreleased)
<? } ?>
<? } ?>
</td>
</tr> </tr>
<? if($isReviewerView){ ?> <? if($isReviewerView){ ?>
<tr> <tr>

View file

@ -136,7 +136,7 @@ catch(Exceptions\PageOutOfBoundsException){
header('Location: ' . $url); header('Location: ' . $url);
exit(); exit();
} }
?><?= Template::Header(['title' => $pageTitle, 'css' => ['/css/artwork.css'], 'description' => $pageDescription, 'canonicalUrl' => $canonicalUrl]) ?> ?><?= Template::Header(title: $pageTitle, css: ['/css/artwork.css'], description: $pageDescription, canonicalUrl: $canonicalUrl) ?>
<main class="artworks"> <main class="artworks">
<section class="narrow"> <section class="narrow">
<h1>Browse U.S. Public Domain Artwork</h1> <h1>Browse U.S. Public Domain Artwork</h1>
@ -199,7 +199,7 @@ catch(Exceptions\PageOutOfBoundsException){
<? if($totalArtworkCount == 0){ ?> <? if($totalArtworkCount == 0){ ?>
<p class="no-results">No artwork matched your filters. You can try different filters, or <a href="/artworks">browse all artwork</a>.</p> <p class="no-results">No artwork matched your filters. You can try different filters, or <a href="/artworks">browse all artwork</a>.</p>
<? }else{ ?> <? }else{ ?>
<?= Template::ArtworkList(['artworks' => $artworks]) ?> <?= Template::ArtworkList(artworks: $artworks) ?>
<? } ?> <? } ?>
<? if($totalArtworkCount > 0){ ?> <? if($totalArtworkCount > 0){ ?>

View file

@ -1,17 +1,17 @@
<? <?
use function Safe\session_unset; use function Safe\session_unset;
session_start();
$isCreated = HttpInput::Bool(SESSION, 'is-artwork-created') ?? false;
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
$artwork = HttpInput::SessionObject('artwork', Artwork::class);
try{ try{
if(Session::$User === null){ if(Session::$User === null){
throw new Exceptions\LoginRequiredException(); throw new Exceptions\LoginRequiredException();
} }
session_start();
$isCreated = HttpInput::Bool(SESSION, 'is-artwork-created') ?? false;
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
$artwork = HttpInput::SessionObject('artwork', Artwork::class);
if(!Session::$User->Benefits->CanUploadArtwork){ if(!Session::$User->Benefits->CanUploadArtwork){
throw new Exceptions\InvalidPermissionsException(); throw new Exceptions\InvalidPermissionsException();
} }
@ -46,25 +46,22 @@ catch(Exceptions\InvalidPermissionsException){
?> ?>
<?= Template::Header( <?= Template::Header(
[ title: 'Submit an Artwork',
'title' => 'Submit an Artwork', css: ['/css/artwork.css'],
'css' => ['/css/artwork.css'], description: 'Submit public domain artwork to the database for use as cover art.'
'highlight' => '',
'description' => 'Submit public domain artwork to the database for use as cover art.'
]
) ?> ) ?>
<main> <main>
<section class="narrow"> <section class="narrow">
<h1>Submit an Artwork</h1> <h1>Submit an Artwork</h1>
<?= Template::Error(['exception' => $exception]) ?> <?= Template::Error(exception: $exception) ?>
<? if($isCreated){ ?> <? if($isCreated){ ?>
<p class="message success">Artwork submitted!</p> <p class="message success">Artwork submitted!</p>
<? } ?> <? } ?>
<form class="create-update-artwork" method="<?= Enums\HttpMethod::Post->value ?>" action="/artworks" enctype="multipart/form-data" autocomplete="off"> <form class="create-update-artwork" method="<?= Enums\HttpMethod::Post->value ?>" action="/artworks" enctype="multipart/form-data" autocomplete="off">
<?= Template::ArtworkForm(['artwork' => $artwork]) ?> <?= Template::ArtworkForm(artwork: $artwork) ?>
</form> </form>
</section> </section>
</main> </main>

View file

@ -31,7 +31,7 @@ try{
catch(Exceptions\AuthorNotFoundException){ catch(Exceptions\AuthorNotFoundException){
Template::ExitWithCode(Enums\HttpCode::NotFound); Template::ExitWithCode(Enums\HttpCode::NotFound);
} }
?><?= Template::Header(['title' => 'Ebooks by ' . $author, 'feedUrl' => str_replace('/ebooks/', '/authors/', $authorUrl), 'feedTitle' => 'Standard Ebooks - Ebooks by ' . $author, 'highlight' => 'ebooks', 'description' => 'All of the Standard Ebooks ebooks by ' . $author, 'canonicalUrl' => SITE_URL . $authorUrl]) ?> ?><?= Template::Header(title: 'Ebooks by ' . $author, feedUrl: str_replace('/ebooks/', '/authors/', $authorUrl), feedTitle: 'Standard Ebooks - Ebooks by ' . $author, highlight: 'ebooks', description: 'All of the Standard Ebooks ebooks by ' . $author, canonicalUrl: SITE_URL . $authorUrl) ?>
<main class="ebooks"> <main class="ebooks">
<h1 class="is-collection">Ebooks by <?= $ebooks[0]->AuthorsHtml ?></h1> <h1 class="is-collection">Ebooks by <?= $ebooks[0]->AuthorsHtml ?></h1>
<? if($showLinks){ ?> <? if($showLinks){ ?>
@ -40,7 +40,7 @@ catch(Exceptions\AuthorNotFoundException){
<a class="button" href="<?= Formatter::EscapeHtml($authorUrl) ?>/feeds">Feeds for this author</a> <a class="button" href="<?= Formatter::EscapeHtml($authorUrl) ?>/feeds">Feeds for this author</a>
</p> </p>
<? } ?> <? } ?>
<?= Template::EbookGrid(['ebooks' => $ebooks, 'view' => Enums\ViewType::Grid]) ?> <?= Template::EbookGrid(ebooks: $ebooks, view: Enums\ViewType::Grid) ?>
<p class="feeds-alert">We also have <a href="/bulk-downloads">bulk ebook downloads</a> and a <a href="/collections">list of collections</a> available, as well as <a href="/feeds">ebook catalog feeds</a> for use directly in your ereader app or RSS reader.</p> <p class="feeds-alert">We also have <a href="/bulk-downloads">bulk ebook downloads</a> and a <a href="/collections">list of collections</a> available, as well as <a href="/feeds">ebook catalog feeds</a> for use directly in your ereader app or RSS reader.</p>
<?= Template::ContributeAlert() ?> <?= Template::ContributeAlert() ?>
</main> </main>

View file

@ -2,7 +2,7 @@
$ebookIds = [1085, 1052]; $ebookIds = [1085, 1052];
$carousel = Db::Query('SELECT * from Ebooks where EbookId in ' . Db::CreateSetSql($ebookIds), $ebookIds, Ebook::class); $carousel = Db::Query('SELECT * from Ebooks where EbookId in ' . Db::CreateSetSql($ebookIds), $ebookIds, Ebook::class);
?> ?>
<?= Template::Header(['title' => 'Death and Beauty in the Alps', 'css' => ['/css/blog.css'], 'highlight' => '', 'description' => '']) ?> <?= Template::Header(title: 'Death and Beauty in the Alps', css: ['/css/blog.css']) ?>
<main> <main>
<section class="narrow blog"> <section class="narrow blog">
<nav class="breadcrumbs"><a href="/blog">Blog</a> </nav> <nav class="breadcrumbs"><a href="/blog">Blog</a> </nav>
@ -31,7 +31,7 @@ $carousel = Db::Query('SELECT * from Ebooks where EbookId in ' . Db::CreateSetSq
<p><em>Scrambles Amongst the Alps</em> has something of both “darkling thrush” and “darkling plain”: an “eternal note of sadness” following terrible loss, but also real, if fleeting, notes of joy in laboring for, and realizing, a hope widely believed impossible.</p> <p><em>Scrambles Amongst the Alps</em> has something of both “darkling thrush” and “darkling plain”: an “eternal note of sadness” following terrible loss, but also real, if fleeting, notes of joy in laboring for, and realizing, a hope widely believed impossible.</p>
<p>The woe of its best-known story is confounded by lesser-known, brighter elements—even if theyre only “thin atomies” in comparison—praising the worth of the endeavour and the value of the toil it required, despite the cruel hand the explorers were dealt.</p> <p>The woe of its best-known story is confounded by lesser-known, brighter elements—even if theyre only “thin atomies” in comparison—praising the worth of the endeavour and the value of the toil it required, despite the cruel hand the explorers were dealt.</p>
<h2 id="ebooks-in-this-newsletter">Free ebooks in this post</h2> <h2 id="ebooks-in-this-newsletter">Free ebooks in this post</h2>
<?= Template::EbookCarousel(['carousel' => $carousel]) ?> <?= Template::EbookCarousel(carousel: $carousel) ?>
</section> </section>
</main> </main>
<?= Template::Footer() ?> <?= Template::Footer() ?>

View file

@ -2,7 +2,7 @@
$ebookIds = [288, 485, 289, 908, 565, 2114]; $ebookIds = [288, 485, 289, 908, 565, 2114];
$carousel = Db::Query('SELECT * from Ebooks where EbookId in ' . Db::CreateSetSql($ebookIds), $ebookIds, Ebook::class); $carousel = Db::Query('SELECT * from Ebooks where EbookId in ' . Db::CreateSetSql($ebookIds), $ebookIds, Ebook::class);
?> ?>
<?= Template::Header(['title' => 'Edith Whartons Vision of Literary Art', 'css' => ['/css/blog.css'], 'highlight' => '', 'description' => '']) ?> <?= Template::Header(title: 'Edith Whartons Vision of Literary Art', css: ['/css/blog.css']) ?>
<main> <main>
<section class="narrow blog"> <section class="narrow blog">
<nav class="breadcrumbs"><a href="/blog">Blog</a> </nav> <nav class="breadcrumbs"><a href="/blog">Blog</a> </nav>
@ -33,7 +33,7 @@ $carousel = Db::Query('SELECT * from Ebooks where EbookId in ' . Db::CreateSetSq
<p><em>Hudson River Bracketed</em> is long out of print. Wharton patently lost the critical and commercial <em>Wettgesang</em> of the 1930s; even her sympathizers tend to admit that shes in no way a star of that period, so the analogy to the Prologue in Heaven falls (or sounds) very flat.</p> <p><em>Hudson River Bracketed</em> is long out of print. Wharton patently lost the critical and commercial <em>Wettgesang</em> of the 1930s; even her sympathizers tend to admit that shes in no way a star of that period, so the analogy to the Prologue in Heaven falls (or sounds) very flat.</p>
<p>But whether Whartons second-last work falls entirely flat too is something that can be judged, if at all, only in the old way, by reading it. This wasnt very easy to do until January 1, but now you can read our <a href="https://standardebooks.org/ebooks/edith-wharton/hudson-river-bracketed">new ebook edition for free</a> at Standard Ebooks.</p> <p>But whether Whartons second-last work falls entirely flat too is something that can be judged, if at all, only in the old way, by reading it. This wasnt very easy to do until January 1, but now you can read our <a href="https://standardebooks.org/ebooks/edith-wharton/hudson-river-bracketed">new ebook edition for free</a> at Standard Ebooks.</p>
<h2 id="ebooks-in-this-newsletter">Free ebooks in this post</h2> <h2 id="ebooks-in-this-newsletter">Free ebooks in this post</h2>
<?= Template::EbookCarousel(['carousel' => $carousel]) ?> <?= Template::EbookCarousel(carousel: $carousel) ?>
</section> </section>
</main> </main>
<?= Template::Footer() ?> <?= Template::Footer() ?>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Blog', 'highlight' => '', 'description' => 'The Standard Ebooks blog.']) ?> <?= Template::Header(title: 'Blog', description: 'The Standard Ebooks blog.') ?>
<main> <main>
<section class="narrow"> <section class="narrow">
<h1>Blog</h1> <h1>Blog</h1>

View file

@ -2,7 +2,7 @@
$ebookIds = [565, 778, 561, 1059]; $ebookIds = [565, 778, 561, 1059];
$carousel = Db::Query('SELECT * from Ebooks where EbookId in ' . Db::CreateSetSql($ebookIds), $ebookIds, Ebook::class); $carousel = Db::Query('SELECT * from Ebooks where EbookId in ' . Db::CreateSetSql($ebookIds), $ebookIds, Ebook::class);
?> ?>
<?= Template::Header(['title' => 'Joyces Ulysses, the Rubáiyát, and “Yes”', 'css' => ['/css/blog.css'], 'highlight' => '', 'description' => '']) ?> <?= Template::Header(title: 'Joyces Ulysses, the Rubáiyát, and “Yes”', css: ['/css/blog.css']) ?>
<main> <main>
<section class="narrow blog"> <section class="narrow blog">
<nav class="breadcrumbs"><a href="/blog">Blog</a> </nav> <nav class="breadcrumbs"><a href="/blog">Blog</a> </nav>
@ -44,7 +44,7 @@ $carousel = Db::Query('SELECT * from Ebooks where EbookId in ' . Db::CreateSetSq
<p>Speaking of consonance: Brown says, citing Ellmann, that the last record Joyce heard before he died was a performance of Lehmanns setting of Fitzgeralds Omar. As is often the case with Ellmann, this might not be true, but its not absurd to suppose that it could be.</p> <p>Speaking of consonance: Brown says, citing Ellmann, that the last record Joyce heard before he died was a performance of Lehmanns setting of Fitzgeralds Omar. As is often the case with Ellmann, this might not be true, but its not absurd to suppose that it could be.</p>
<p>And as very often with <i>Ulysses</i>, what first seems like nothing, or like material for a joke, may also turn out to be something else too, even something that matters. If <i>Ulysses</i> doesnt entirely affirm life, then it does, in this respect at least, reflect it.</p> <p>And as very often with <i>Ulysses</i>, what first seems like nothing, or like material for a joke, may also turn out to be something else too, even something that matters. If <i>Ulysses</i> doesnt entirely affirm life, then it does, in this respect at least, reflect it.</p>
<h2 id="ebooks-in-this-newsletter">Free ebooks in this post</h2> <h2 id="ebooks-in-this-newsletter">Free ebooks in this post</h2>
<?= Template::EbookCarousel(['carousel' => $carousel]) ?> <?= Template::EbookCarousel(carousel: $carousel) ?>
</section> </section>
</main> </main>
<?= Template::Footer() ?> <?= Template::Footer() ?>

View file

@ -100,7 +100,7 @@ foreach($ebooks as $ebook){
ksort($ebooksWithDescriptions); ksort($ebooksWithDescriptions);
?><?= Template::Header(['title' => 'Public Domain Day 2025 in Literature - Blog', 'highlight' => '', 'description' => 'Read about the new ebooks Standard Ebooks is releasing for Public Domain Day 2025!', 'css' => ['/css/public-domain-day.css']]) ?> ?><?= Template::Header(title: 'Public Domain Day 2025 in Literature - Blog', description: 'Read about the new ebooks Standard Ebooks is releasing for Public Domain Day 2025!', css: ['/css/public-domain-day.css']) ?>
<main> <main>
<section class="narrow blog has-hero"> <section class="narrow blog has-hero">
<nav class="breadcrumbs"><a href="/blog">Blog</a> </nav> <nav class="breadcrumbs"><a href="/blog">Blog</a> </nav>
@ -133,7 +133,7 @@ ksort($ebooksWithDescriptions);
<li> <li>
<div> <div>
<a href="<?= $ebookGroup['ebook']->Url ?>"> <a href="<?= $ebookGroup['ebook']->Url ?>">
<?= Template::RealisticEbook(['ebook' => $ebookGroup['ebook']]) ?> <?= Template::RealisticEbook(ebook: $ebookGroup['ebook']) ?>
</a> </a>
</div> </div>
<div> <div>

View file

@ -27,7 +27,7 @@ catch(Safe\Exceptions\ApcuException){
$title = preg_replace('/s$/', '', ucfirst($class)); $title = preg_replace('/s$/', '', ucfirst($class));
?><?= Template::Header(['title' => 'Downloads by ' . $title, 'highlight' => '', 'description' => 'Download zip files containing all of the Standard Ebooks in a given collection.']) ?> ?><?= Template::Header(title: 'Downloads by ' . $title, description: 'Download zip files containing all of the Standard Ebooks in a given collection.') ?>
<main> <main>
<section class="bulk-downloads"> <section class="bulk-downloads">
<h1>Down­loads by <?= $title ?></h1> <h1>Down­loads by <?= $title ?></h1>
@ -80,7 +80,10 @@ $title = preg_replace('/s$/', '', ucfirst($class));
</tbody> </tbody>
</table> </table>
<? }else{ ?> <? }else{ ?>
<?= Template::BulkDownloadTable(['label' => $title, 'collections' => $collection]); ?> <?
/** @var array<stdClass> $collection */
?>
<?= Template::BulkDownloadTable(label: $title, collections: $collection); ?>
<? } ?> <? } ?>
</section> </section>
</main> </main>

View file

@ -54,7 +54,7 @@ catch(Exceptions\InvalidFileException){
Template::ExitWithCode(Enums\HttpCode::NotFound); Template::ExitWithCode(Enums\HttpCode::NotFound);
} }
?><?= Template::Header(['title' => 'Downloading Ebook Collections', 'highlight' => '', 'description' => 'Download zip files containing all of the Standard Ebooks released in a given month.']) ?> ?><?= Template::Header(title: 'Downloading Ebook Collections', description: 'Download zip files containing all of the Standard Ebooks released in a given month.') ?>
<main> <main>
<section class="narrow"> <section class="narrow">
<h1>Downloading Ebook Collections</h1> <h1>Downloading Ebook Collections</h1>

View file

@ -1,13 +1,12 @@
<? <?
use function Safe\apcu_fetch; use function Safe\apcu_fetch;
$collection = null;
$collectionUrlName = HttpInput::Str(GET, 'collection');
$collection = null;
$authorUrlName = HttpInput::Str(GET, 'author');
$canDownload = false;
try{ try{
$collection = null;
$collectionUrlName = HttpInput::Str(GET, 'collection');
$authorUrlName = HttpInput::Str(GET, 'author');
$canDownload = false;
if(Session::$User?->Benefits->CanBulkDownload){ if(Session::$User?->Benefits->CanBulkDownload){
$canDownload = true; $canDownload = true;
} }
@ -33,10 +32,6 @@ try{
break; break;
} }
} }
if($collection === null){
throw new Exceptions\CollectionNotFoundException();
}
} }
if($authorUrlName !== null){ if($authorUrlName !== null){
@ -65,6 +60,10 @@ try{
throw new Exceptions\AuthorNotFoundException(); throw new Exceptions\AuthorNotFoundException();
} }
} }
if($collection === null){
throw new Exceptions\CollectionNotFoundException();
}
} }
catch(Exceptions\AuthorNotFoundException){ catch(Exceptions\AuthorNotFoundException){
Template::ExitWithCode(Enums\HttpCode::NotFound); Template::ExitWithCode(Enums\HttpCode::NotFound);
@ -73,17 +72,17 @@ catch(Exceptions\CollectionNotFoundException){
Template::ExitWithCode(Enums\HttpCode::NotFound); Template::ExitWithCode(Enums\HttpCode::NotFound);
} }
?><?= Template::Header(['title' => 'Download ', 'highlight' => '', 'description' => 'Download zip files containing all of the Standard Ebooks released in a given month.']) ?> ?><?= Template::Header(title: 'Download ', description: 'Download zip files containing all of the Standard Ebooks released in a given month.') ?>
<main> <main>
<section class="bulk-downloads"> <section class="bulk-downloads">
<h1>Download the <?= $collection?->Label ?> Collection</h1> <h1>Download the <?= $collection->Label ?> Collection</h1>
<? if($canDownload){ ?> <? if($canDownload){ ?>
<p>Select the ebook format in which youd like to download this collection.</p> <p>Select the ebook format in which youd like to download this collection.</p>
<p>You can also read about <a href="/help/how-to-use-our-ebooks#which-file-to-download">which ebook format to download</a>.</p> <p>You can also read about <a href="/help/how-to-use-our-ebooks#which-file-to-download">which ebook format to download</a>.</p>
<? }else{ ?> <? }else{ ?>
<p><a href="/about#patrons-circle">Patrons circle members</a> can download zip files containing all of the ebooks in a collection. You can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature.</p> <p><a href="/about#patrons-circle">Patrons circle members</a> can download zip files containing all of the ebooks in a collection. You can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature.</p>
<? } ?> <? } ?>
<?= Template::BulkDownloadTable(['label' => 'Collection', 'collections' => [$collection]]); ?> <?= Template::BulkDownloadTable(label: 'Collection', collections: [$collection]); ?>
</section> </section>
</main> </main>
<?= Template::Footer() ?> <?= Template::Footer() ?>

View file

@ -4,7 +4,7 @@ if(Session::$User?->Benefits->CanBulkDownload){
$canDownload = true; $canDownload = true;
} }
?><?= Template::Header(['title' => 'Bulk Ebook Downloads', 'highlight' => '', 'description' => 'Download zip files containing all of the Standard Ebooks released in a given month.']) ?> ?><?= Template::Header(title: 'Bulk Ebook Downloads', description: 'Download zip files containing all of the Standard Ebooks released in a given month.') ?>
<main> <main>
<section class="narrow has-hero"> <section class="narrow has-hero">
<h1>Bulk Ebook Down­loads</h1> <h1>Bulk Ebook Down­loads</h1>

View file

@ -16,7 +16,7 @@ try{
catch(Exceptions\CollectionNotFoundException){ catch(Exceptions\CollectionNotFoundException){
Template::ExitWithCode(Enums\HttpCode::NotFound); Template::ExitWithCode(Enums\HttpCode::NotFound);
} }
?><?= Template::Header(['title' => $pageTitle, 'feedUrl' => $feedUrl, 'feedTitle' => $feedTitle, 'highlight' => 'ebooks', 'description' => $pageDescription]) ?> ?><?= Template::Header(title: $pageTitle, feedUrl: $feedUrl, feedTitle: $feedTitle, highlight: 'ebooks', description: $pageDescription) ?>
<main class="ebooks"> <main class="ebooks">
<h1 class="is-collection"><?= $pageHeader ?></h1> <h1 class="is-collection"><?= $pageHeader ?></h1>
<?= Template::DonationCounter() ?> <?= Template::DonationCounter() ?>
@ -32,7 +32,7 @@ catch(Exceptions\CollectionNotFoundException){
<? if(sizeof($collection->Ebooks) == 0){ ?> <? if(sizeof($collection->Ebooks) == 0){ ?>
<p class="no-results">No ebooks matched your filters. You can try different filters, or <a href="/ebooks">browse all of our ebooks</a>.</p> <p class="no-results">No ebooks matched your filters. You can try different filters, or <a href="/ebooks">browse all of our ebooks</a>.</p>
<? }else{ ?> <? }else{ ?>
<?= Template::EbookGrid(['ebooks' => $collection->Ebooks, 'view' => Enums\ViewType::Grid, 'collection' => $collection]) ?> <?= Template::EbookGrid(ebooks: $collection->Ebooks, view: Enums\ViewType::Grid, collection: $collection) ?>
<? } ?> <? } ?>
<? if(Session::$User?->Benefits->CanEditCollections){ ?> <? if(Session::$User?->Benefits->CanEditCollections){ ?>

View file

@ -1,7 +1,7 @@
<? <?
$collections = Collection::GetAll(); $collections = Collection::GetAll();
?><?= Template::Header(['title' => 'Ebook Collections', 'highlight' => '', 'description' => 'Browse collections of Standard Ebooks.']) ?> ?><?= Template::Header(title: 'Ebook Collections', description: 'Browse collections of Standard Ebooks.') ?>
<main> <main>
<section class="narrow has-hero"> <section class="narrow has-hero">
<h1>Ebook Collections</h1> <h1>Ebook Collections</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'A Basic Standard Ebooks Source Folder', 'manual' => true, 'highlight' => 'contribute', 'description' => 'All Standard Ebooks source folders have the same basic structure, described here.']) ?> <?= Template::Header(title: 'A Basic Standard Ebooks Source Folder', isManual: true, highlight: 'contribute', description: 'All Standard Ebooks source folders have the same basic structure, described here.') ?>
<main> <main>
<article id="a-basic-standard-ebooks-source-folder"> <article id="a-basic-standard-ebooks-source-folder">
<h1>A Basic Standard Ebooks Source Folder</h1> <h1>A Basic Standard Ebooks Source Folder</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Collections Policy', 'highlight' => 'contribute', 'description' => 'Standard Ebooks only accepts certain kinds of ebooks for production and hosting. This is the full list.']) ?> <?= Template::Header(title: 'Collections Policy', highlight: 'contribute', description: 'Standard Ebooks only accepts certain kinds of ebooks for production and hosting. This is the full list.') ?>
<main> <main>
<article> <article>
<hgroup> <hgroup>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Common Issues When Working on Public Domain Ebooks', 'manual' => true, 'highlight' => 'contribute', 'description' => 'A list of common issues encountered when converting from public domain transcriptions.']) ?> <?= Template::Header(title: 'Common Issues When Working on Public Domain Ebooks', isManual: true, highlight: 'contribute', description: 'A list of common issues encountered when converting from public domain transcriptions.') ?>
<main> <main>
<article> <article>
<h1>Common Issues When Working on Public Domain Ebooks</h1> <h1>Common Issues When Working on Public Domain Ebooks</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'How to Choose and Create a Cover Image', 'manual' => true, 'highlight' => 'contribute', 'description' => 'A guide to choosing, clearing, and formatting your cover image.']) ?> <?= Template::Header(title: 'How to Choose and Create a Cover Image', isManual: true, highlight: 'contribute', description: 'A guide to choosing, clearing, and formatting your cover image.') ?>
<main class="manual"> <main class="manual">
<article class="step-by-step-guide"> <article class="step-by-step-guide">
<h1>How to Choose and Create a Cover Image</h1> <h1>How to Choose and Create a Cover Image</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'How to Conquer Complex Drama Formatting', 'manual' => true, 'highlight' => 'contribute', 'description' => 'A guide to formatting any complex plays or dramatic dialog sections.']) ?> <?= Template::Header(title: 'How to Conquer Complex Drama Formatting', isManual: true, highlight: 'contribute', description: 'A guide to formatting any complex plays or dramatic dialog sections.') ?>
<main class="manual"> <main class="manual">
<article class="step-by-step-guide"> <article class="step-by-step-guide">
<h1>How to Conquer Complex Drama Formatting</h1> <h1>How to Conquer Complex Drama Formatting</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'How to Create Figures for Music Scores', 'manual' => true, 'highlight' => 'contribute', 'description' => 'A guide to producing SVG figures of music notation.']) ?> <?= Template::Header(title: 'How to Create Figures for Music Scores', isManual: true, highlight: 'contribute', description: 'A guide to producing SVG figures of music notation.') ?>
<main class="manual"> <main class="manual">
<article class="step-by-step-guide"> <article class="step-by-step-guide">
<h1>How to Create Figures for Music Scores</h1> <h1>How to Create Figures for Music Scores</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'How to Create SVGs from Maps with Several Colors', 'manual' => true, 'highlight' => 'contribute', 'description' => 'A guide to producing SVG from images such as maps with more than a single color.']) ?> <?= Template::Header(title: 'How to Create SVGs from Maps with Several Colors', isManual: true, highlight: 'contribute', description: 'A guide to producing SVG from images such as maps with more than a single color.') ?>
<main class="manual"> <main class="manual">
<article class="step-by-step-guide"> <article class="step-by-step-guide">
<h1>How to Create SVGs from Maps with Several Colors</h1> <h1>How to Create SVGs from Maps with Several Colors</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'How to Review an Ebook Production for Publication', 'manual' => true, 'highlight' => 'contribute', 'description' => 'A guide to proofread and review an ebook production for publication.']) ?> <?= Template::Header(title: 'How to Review an Ebook Production for Publication', isManual: true, highlight: 'contribute', description: 'A guide to proofread and review an ebook production for publication.') ?>
<main class="manual"> <main class="manual">
<article class="step-by-step-guide"> <article class="step-by-step-guide">
<h1>How to Review an Ebook Production for Publication</h1> <h1>How to Review an Ebook Production for Publication</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'How to Structure and Style Large Poetic Productions', 'manual' => true, 'highlight' => 'contribute', 'description' => 'A guide to formatting poetry collections, long narrative poems, and unusual poetic features.']) ?> <?= Template::Header(title: 'How to Structure and Style Large Poetic Productions', isManual: true, highlight: 'contribute', description: 'A guide to formatting poetry collections, long narrative poems, and unusual poetic features.') ?>
<main class="manual"> <main class="manual">
<article class="step-by-step-guide"> <article class="step-by-step-guide">
<h1>How to Structure and Style Large Poetic Productions</h1> <h1>How to Structure and Style Large Poetic Productions</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'How-to Guides For Difficult Productions', 'manual' => true, 'highlight' => 'contribute', 'description' => 'Guides on how to produce more difficult productions.']) ?> <?= Template::Header(title: 'How-to Guides For Difficult Productions', isManual: true, highlight: 'contribute', description: 'Guides on how to produce more difficult productions.') ?>
<main> <main>
<article> <article>
<h1>How-to Guides</h1> <h1>How-to Guides</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Things to Look Out For When Proofreading', 'manual' => true, 'highlight' => 'contribute', 'description' => 'A list of things to look out for when proofreading.']) ?> <?= Template::Header(title: 'Things to Look Out For When Proofreading', isManual: true, highlight: 'contribute', description: 'A list of things to look out for when proofreading.') ?>
<main> <main>
<article> <article>
<h1>Things to Look Out For When Proofreading</h1> <h1>Things to Look Out For When Proofreading</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Get Involved', 'highlight' => 'contribute', 'description' => 'Details on how to contribute your time and talent to the volunteer-driven Standard Ebooks project.']) ?> <?= Template::Header(title: 'Get Involved', highlight: 'contribute', description: 'Details on how to contribute your time and talent to the volunteer-driven Standard Ebooks project.') ?>
<main> <main>
<article class="has-hero"> <article class="has-hero">
<hgroup> <hgroup>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Producing an Ebook for Standard Ebooks', 'highlight' => 'contribute', 'description' => 'A high-level outline of the process of producing an ebook for Standard Ebooks.']) ?> <?= Template::Header(title: 'Producing an Ebook for Standard Ebooks', highlight: 'contribute', description: 'A high-level outline of the process of producing an ebook for Standard Ebooks.') ?>
<main> <main>
<article> <article>
<h1>Producing an Ebook for Standard Ebooks</h1> <h1>Producing an Ebook for Standard Ebooks</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Producing an Ebook, Step by Step', 'manual' => true, 'highlight' => 'contribute', 'description' => 'A detailed step-by-step description of the complete process of producing an ebook for Standard Ebooks, start to finish.']) ?> <?= Template::Header(title: 'Producing an Ebook, Step by Step', isManual: true, highlight: 'contribute', description: 'A detailed step-by-step description of the complete process of producing an ebook for Standard Ebooks, start to finish.') ?>
<main class="manual"> <main class="manual">
<article class="step-by-step-guide"> <article class="step-by-step-guide">
<h1>Producing an Ebook, Step by Step</h1> <h1>Producing an Ebook, Step by Step</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Report Errors Upstream', 'highlight' => 'contribute', 'description' => 'Our guide to reporting errors to Gutenberg and other sources.']) ?> <?= Template::Header(title: 'Report Errors Upstream', highlight: 'contribute', description: 'Our guide to reporting errors to Gutenberg and other sources.') ?>
<main> <main>
<article> <article>
<h1>Report Errors Upstream</h1> <h1>Report Errors Upstream</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Report Errors', 'highlight' => 'contribute', 'description' => 'How to report a typo or error youve found in a Standard Ebooks ebook.']) ?> <?= Template::Header(title: 'Report Errors', highlight: 'contribute', description: 'How to report a typo or error youve found in a Standard Ebooks ebook.') ?>
<main> <main>
<article> <article>
<h1>Report Errors</h1> <h1>Report Errors</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Research Spreadsheets', 'highlight' => 'contribute', 'description' => 'A list of spreadsheets created and used by Standard Ebooks producers.']) ?> <?= Template::Header(title: 'Research Spreadsheets', highlight: 'contribute', description: 'A list of spreadsheets created and used by Standard Ebooks producers.') ?>
<main> <main>
<article> <article>
<h1>Research Spreadsheets</h1> <h1>Research Spreadsheets</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Tips for Editors and Proofreaders', 'manual' => true, 'highlight' => 'contribute', 'description' => 'A list of tips and tricks for people whod like to proofread a Standard Ebooks ebook.']) ?> <?= Template::Header(title: 'Tips for Editors and Proofreaders', isManual: true, highlight: 'contribute', description: 'A list of tips and tricks for people whod like to proofread a Standard Ebooks ebook.') ?>
<main> <main>
<article> <article>
<h1>Tips for Editors and Proofreaders</h1> <h1>Tips for Editors and Proofreaders</h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Uncategorized Art Resources', 'highlight' => 'contribute', 'description' => 'A list of US-PD art books for use when conducting cover art research.']) ?> <?= Template::Header(title: 'Uncategorized Art Resources', highlight: 'contribute', description: 'A list of US-PD art books for use when conducting cover art research.') ?>
<main> <main>
<article id="a-basic-standard-ebooks-source-folder"> <article id="a-basic-standard-ebooks-source-folder">
<h1>Uncategorized Art Resources</h1> <h1>Uncategorized Art Resources</h1>

View file

@ -3,7 +3,7 @@ $beginnerEbooks = Ebook::GetByIsWantedAndDifficulty(Enums\EbookPlaceholderDiffic
$intermediateEbooks = Ebook::GetByIsWantedAndDifficulty(Enums\EbookPlaceholderDifficulty::Intermediate); $intermediateEbooks = Ebook::GetByIsWantedAndDifficulty(Enums\EbookPlaceholderDifficulty::Intermediate);
$advancedEbooks = Ebook::GetByIsWantedAndDifficulty(Enums\EbookPlaceholderDifficulty::Advanced); $advancedEbooks = Ebook::GetByIsWantedAndDifficulty(Enums\EbookPlaceholderDifficulty::Advanced);
?> ?>
<?= Template::Header(['title' => 'Wanted Ebooks', 'highlight' => 'contribute', 'description' => 'A list of ebooks the Standard Ebooks editor would like to see produced, including suggestions for first-time producers.']) ?> <?= Template::Header(title: 'Wanted Ebooks', highlight: 'contribute', description: 'A list of ebooks the Standard Ebooks editor would like to see produced, including suggestions for first-time producers.') ?>
<main> <main>
<article> <article>
<h1>Wanted Ebooks</h1> <h1>Wanted Ebooks</h1>
@ -18,13 +18,13 @@ $advancedEbooks = Ebook::GetByIsWantedAndDifficulty(Enums\EbookPlaceholderDiffic
<h2>For your first production</h2> <h2>For your first production</h2>
<p>If nothing on the list below interests you, you can pitch us something else youd like to work on.</p> <p>If nothing on the list below interests you, you can pitch us something else youd like to work on.</p>
<p>First productions should be on the shorter side (less than 100,000 words maximum) and without too many complex formatting issues like illustrations, significant endnotes, letters, poems, etc. Most short plain fiction novels fall in this category.</p> <p>First productions should be on the shorter side (less than 100,000 words maximum) and without too many complex formatting issues like illustrations, significant endnotes, letters, poems, etc. Most short plain fiction novels fall in this category.</p>
<?= Template::WantedEbooksList(['ebooks' => $beginnerEbooks, 'showPlaceholderMetadata' => Session::$User?->Benefits->CanEditEbookPlaceholders]) ?> <?= Template::WantedEbooksList(ebooks: $beginnerEbooks, showPlaceholderMetadata: Session::$User->Benefits->CanEditEbookPlaceholders ?? false) ?>
<h2>Moderate-difficulty productions</h2> <h2>Moderate-difficulty productions</h2>
<?= Template::WantedEbooksList(['ebooks' => $intermediateEbooks, 'showPlaceholderMetadata' => Session::$User?->Benefits->CanEditEbookPlaceholders]) ?> <?= Template::WantedEbooksList(ebooks: $intermediateEbooks, showPlaceholderMetadata: Session::$User->Benefits->CanEditEbookPlaceholders ?? false) ?>
<h2>Advanced productions</h2> <h2>Advanced productions</h2>
<?= Template::WantedEbooksList(['ebooks' => $advancedEbooks, 'showPlaceholderMetadata' => Session::$User?->Benefits->CanEditEbookPlaceholders]) ?> <?= Template::WantedEbooksList(ebooks: $advancedEbooks, showPlaceholderMetadata: Session::$User->Benefits->CanEditEbookPlaceholders ?? false) ?>
<h2 id="verne">Jules Verne</h2> <h2 id="verne">Jules Verne</h2>
<p>Verne has a complex publication and translation history. Please review these notes before starting any Verne books.</p> <p>Verne has a complex publication and translation history. Please review these notes before starting any Verne books.</p>

View file

@ -5,15 +5,15 @@ $newsletterSubscriberCount = floor(Db::QueryInt('
where IsConfirmed = true where IsConfirmed = true
') / 100) * 100; ') / 100) * 100;
?><?= Template::Header(['title' => 'Donate', 'highlight' => 'donate', 'description' => 'Donate to Standard Ebooks.']) ?> ?><?= Template::Header(title: 'Donate', highlight: 'donate', description: 'Donate to Standard Ebooks.') ?>
<main> <main>
<section class="donate narrow has-hero"> <section class="donate narrow has-hero">
<hgroup> <hgroup>
<h1>Donate to Standard Ebooks</h1> <h1>Donate to Standard Ebooks</h1>
<p>and help bring the beauty of literature to the digital age</p> <p>and help bring the beauty of literature to the digital age</p>
</hgroup> </hgroup>
<?= Template::DonationCounter(['autoHide' => false, 'showDonateButton' => false]) ?> <?= Template::DonationCounter(autoHide: false, showDonateButton: false) ?>
<?= Template::DonationProgress(['autoHide' => false, 'showDonateButton' => false]) ?> <?= Template::DonationProgress(autoHide: false, showDonateButton: false) ?>
<picture data-caption="The Quiet Hour. Albert Chevallier Tayler, 1925"> <picture data-caption="The Quiet Hour. Albert Chevallier Tayler, 1925">
<source srcset="/images/the-quiet-hour@2x.avif 2x, /images/the-quiet-hour.avif 1x" type="image/avif"/> <source srcset="/images/the-quiet-hour@2x.avif 2x, /images/the-quiet-hour.avif 1x" type="image/avif"/>
<source srcset="/images/the-quiet-hour@2x.jpg 2x, /images/the-quiet-hour.jpg 1x" type="image/jpg"/> <source srcset="/images/the-quiet-hour@2x.jpg 2x, /images/the-quiet-hour.jpg 1x" type="image/jpg"/>

View file

@ -40,12 +40,9 @@ catch(Exceptions\InvalidPermissionsException){
} }
?> ?>
<?= Template::Header( <?= Template::Header(
[ title: 'Delete ' . $ebook->Title,
'title' => 'Delete ' . $ebook->Title, css: ['/css/ebook-placeholder.css'],
'css' => ['/css/ebook-placeholder.css'], description: 'Delete ' . $ebook->Title
'highlight' => '',
'description' => 'Delete ' . $ebook->Title
]
) ?> ) ?>
<main> <main>
<section class="narrow"> <section class="narrow">
@ -55,7 +52,7 @@ catch(Exceptions\InvalidPermissionsException){
</nav> </nav>
<h1>Delete</h1> <h1>Delete</h1>
<?= Template::Error(['exception' => $exception]) ?> <?= Template::Error(exception: $exception) ?>
<form method="<?= Enums\HttpMethod::Post->value ?>" action="<?= $ebook->Url ?>"> <form method="<?= Enums\HttpMethod::Post->value ?>" action="<?= $ebook->Url ?>">
<input type="hidden" name="_method" value="<?= Enums\HttpMethod::Delete->value ?>" /> <input type="hidden" name="_method" value="<?= Enums\HttpMethod::Delete->value ?>" />

View file

@ -40,12 +40,9 @@ catch(Exceptions\InvalidPermissionsException){
} }
?> ?>
<?= Template::Header( <?= Template::Header(
[ title: 'Edit ' . $ebook->Title,
'title' => 'Edit ' . $ebook->Title, css: ['/css/ebook-placeholder.css'],
'css' => ['/css/ebook-placeholder.css'], description: 'Edit ' . $ebook->Title
'highlight' => '',
'description' => 'Edit ' . $ebook->Title
]
) ?> ) ?>
<main> <main>
<section class="narrow"> <section class="narrow">
@ -55,11 +52,11 @@ catch(Exceptions\InvalidPermissionsException){
</nav> </nav>
<h1>Edit</h1> <h1>Edit</h1>
<?= Template::Error(['exception' => $exception]) ?> <?= Template::Error(exception: $exception) ?>
<form class="create-update-ebook-placeholder" method="<?= Enums\HttpMethod::Post->value ?>" action="<?= $ebook->Url ?>" autocomplete="off"> <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 ?>" /> <input type="hidden" name="_method" value="<?= Enums\HttpMethod::Put->value ?>" />
<?= Template::EbookPlaceholderForm(['ebook' => $ebook, 'isEditForm' => true]) ?> <?= Template::EbookPlaceholderForm(ebook: $ebook, isEditForm: true) ?>
<div class="footer"> <div class="footer">
<button>Save</button> <button>Save</button>
</div> </div>

View file

@ -24,12 +24,11 @@ catch(Exceptions\EbookNotFoundException){
Template::ExitWithCode(Enums\HttpCode::NotFound); Template::ExitWithCode(Enums\HttpCode::NotFound);
} }
?><?= Template::Header( ?><?= Template::Header(
[ title: strip_tags($ebook->TitleWithCreditsHtml),
'title' => strip_tags($ebook->TitleWithCreditsHtml), css: ['/css/ebook-placeholder.css'],
'css' => ['/css/ebook-placeholder.css'], highlight: 'ebooks',
'highlight' => 'ebooks', canonicalUrl: SITE_URL . $ebook->Url
'canonicalUrl' => SITE_URL . $ebook->Url )
])
?> ?>
<main> <main>
<article class="ebook ebook-placeholder" typeof="schema:Book" about="<?= $ebook->Url ?>"> <article class="ebook ebook-placeholder" typeof="schema:Book" about="<?= $ebook->Url ?>">
@ -77,7 +76,7 @@ catch(Exceptions\EbookNotFoundException){
<? if(sizeof($ebook->CollectionMemberships) > 0){ ?> <? if(sizeof($ebook->CollectionMemberships) > 0){ ?>
<? foreach($ebook->CollectionMemberships as $collectionMembership){ ?> <? foreach($ebook->CollectionMemberships as $collectionMembership){ ?>
<p> <p>
<?= Template::CollectionDescriptor(['collectionMembership' => $collectionMembership]) ?>. <?= Template::CollectionDescriptor(collectionMembership: $collectionMembership) ?>.
</p> </p>
<? } ?> <? } ?>
<? } ?> <? } ?>
@ -111,7 +110,7 @@ catch(Exceptions\EbookNotFoundException){
</section> </section>
<? if(Session::$User?->Benefits->CanEditEbooks || Session::$User?->Benefits->CanEditEbookPlaceholders){ ?> <? if(Session::$User?->Benefits->CanEditEbooks || Session::$User?->Benefits->CanEditEbookPlaceholders){ ?>
<?= Template::EbookMetadata(['ebook' => $ebook, 'showPlaceholderMetadata' => 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){ ?> <? if(Session::$User?->Benefits->CanEditProjects || Session::$User?->Benefits->CanManageProjects || Session::$User?->Benefits->CanReviewProjects){ ?>
@ -123,7 +122,7 @@ catch(Exceptions\EbookNotFoundException){
<a href="<?= $ebook->ProjectInProgress->EditUrl ?>">Edit project</a> <a href="<?= $ebook->ProjectInProgress->EditUrl ?>">Edit project</a>
</p> </p>
<? } ?> <? } ?>
<?= Template::ProjectDetailsTable(['project' => $ebook->ProjectInProgress, 'showTitle' => false]) ?> <?= Template::ProjectDetailsTable(project: $ebook->ProjectInProgress, showTitle: false) ?>
</section> </section>
<? } ?> <? } ?>
@ -137,7 +136,7 @@ catch(Exceptions\EbookNotFoundException){
<? if(sizeof($ebook->PastProjects) == 0){ ?> <? if(sizeof($ebook->PastProjects) == 0){ ?>
<p class="empty-notice">None.</p> <p class="empty-notice">None.</p>
<? }else{ ?> <? }else{ ?>
<?= Template::ProjectsTable(['projects' => $ebook->PastProjects, 'includeTitle' => false, 'showEditButton' => Session::$User->Benefits->CanEditProjects]) ?> <?= Template::ProjectsTable(projects: $ebook->PastProjects, includeTitle: false, showEditButton: Session::$User->Benefits->CanEditProjects) ?>
<? } ?> <? } ?>
</section> </section>
<? } ?> <? } ?>

View file

@ -1,15 +1,6 @@
<? <?
use function Safe\session_unset; use function Safe\session_unset;
session_start();
$isCreated = HttpInput::Bool(SESSION, 'is-ebook-placeholder-created') ?? false;
$isOnlyProjectCreated = HttpInput::Bool(SESSION, 'is-only-ebook-project-created') ?? false;
$isDeleted = HttpInput::Bool(SESSION, 'is-ebook-placeholder-deleted') ?? false;
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
$ebook = HttpInput::SessionObject('ebook', Ebook::class);
$project = HttpInput::SessionObject('project', Project::class);
$deletedEbookTitle = '';
try{ try{
if(Session::$User === null){ if(Session::$User === null){
@ -20,6 +11,16 @@ try{
throw new Exceptions\InvalidPermissionsException(); throw new Exceptions\InvalidPermissionsException();
} }
session_start();
$isCreated = HttpInput::Bool(SESSION, 'is-ebook-placeholder-created') ?? false;
$isOnlyProjectCreated = HttpInput::Bool(SESSION, 'is-only-ebook-project-created') ?? false;
$isDeleted = HttpInput::Bool(SESSION, 'is-ebook-placeholder-deleted') ?? false;
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
$ebook = HttpInput::SessionObject('ebook', Ebook::class);
$project = HttpInput::SessionObject('project', Project::class);
$deletedEbookTitle = '';
if($isCreated || $isOnlyProjectCreated){ if($isCreated || $isOnlyProjectCreated){
// We got here because an `Ebook` was successfully created. // We got here because an `Ebook` was successfully created.
http_response_code(Enums\HttpCode::Created->value); http_response_code(Enums\HttpCode::Created->value);
@ -70,18 +71,15 @@ catch(Exceptions\InvalidPermissionsException){
} }
?> ?>
<?= Template::Header( <?= Template::Header(
[ title: 'Create an Ebook Placeholder',
'title' => 'Create an Ebook Placeholder', css: ['/css/ebook-placeholder.css', '/css/project.css'],
'css' => ['/css/ebook-placeholder.css', '/css/project.css'], description: 'Create a placeholder for an ebook not yet in the collection.'
'highlight' => '',
'description' => 'Create a placeholder for an ebook not yet in the collection.'
]
) ?> ) ?>
<main> <main>
<section class="narrow"> <section class="narrow">
<h1>Create an Ebook Placeholder</h1> <h1>Create an Ebook Placeholder</h1>
<?= Template::Error(['exception' => $exception]) ?> <?= Template::Error(exception: $exception) ?>
<? if(isset($createdEbook)){ ?> <? if(isset($createdEbook)){ ?>
<? if($isOnlyProjectCreated){ ?> <? if($isOnlyProjectCreated){ ?>
@ -94,7 +92,7 @@ catch(Exceptions\InvalidPermissionsException){
<? } ?> <? } ?>
<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">
<?= Template::EbookPlaceholderForm(['ebook' => $ebook]) ?> <?= Template::EbookPlaceholderForm(ebook: $ebook ?? new Ebook()) ?>
<div class="footer"> <div class="footer">
<button>Submit</button> <button>Submit</button>
</div> </div>

View file

@ -61,7 +61,7 @@ try{
catch(Exceptions\InvalidFileException | Exceptions\EbookNotFoundException){ catch(Exceptions\InvalidFileException | Exceptions\EbookNotFoundException){
Template::ExitWithCode(Enums\HttpCode::NotFound); Template::ExitWithCode(Enums\HttpCode::NotFound);
} }
?><?= Template::Header(['title' => 'Your Download Has Started!', 'downloadUrl' => $downloadUrl]) ?> ?><?= Template::Header(title: 'Your Download Has Started!', downloadUrl: $downloadUrl) ?>
<main class="donate"> <main class="donate">
<h1>Your Download Has Started!</h1> <h1>Your Download Has Started!</h1>
<div class="thank-you-container"> <div class="thank-you-container">

View file

@ -71,7 +71,7 @@ catch(Exceptions\EbookNotFoundException){
Template::ExitWithCode(Enums\HttpCode::NotFound); Template::ExitWithCode(Enums\HttpCode::NotFound);
} }
?><?= Template::Header(['title' => strip_tags($ebook->TitleWithCreditsHtml) . ' - Free ebook download', 'ogType' => 'book', 'coverUrl' => $ebook->DistCoverUrl, 'highlight' => 'ebooks', 'description' => 'Free epub ebook download of the Standard Ebooks edition of ' . $ebook->Title . ': ' . $ebook->Description, 'canonicalUrl' => SITE_URL . $ebook->Url]) ?> ?><?= Template::Header(title: strip_tags($ebook->TitleWithCreditsHtml) . ' - Free ebook download', ogType: 'book', coverUrl: $ebook->DistCoverUrl, highlight: 'ebooks', description: 'Free epub ebook download of the Standard Ebooks edition of ' . $ebook->Title . ': ' . $ebook->Description, canonicalUrl: SITE_URL . $ebook->Url) ?>
<main> <main>
<article class="ebook" typeof="schema:Book" about="<?= $ebook->Url ?>"> <article class="ebook" typeof="schema:Book" about="<?= $ebook->Url ?>">
<meta property="schema:description" content="<?= Formatter::EscapeHtml($ebook->Description) ?>"/> <meta property="schema:description" content="<?= Formatter::EscapeHtml($ebook->Description) ?>"/>
@ -126,7 +126,7 @@ catch(Exceptions\EbookNotFoundException){
<? if(sizeof($ebook->CollectionMemberships) > 0){ ?> <? if(sizeof($ebook->CollectionMemberships) > 0){ ?>
<? foreach($ebook->CollectionMemberships as $collectionMembership){ ?> <? foreach($ebook->CollectionMemberships as $collectionMembership){ ?>
<p> <p>
<?= Template::CollectionDescriptor(['collectionMembership' => $collectionMembership]) ?>. <?= Template::CollectionDescriptor(collectionMembership: $collectionMembership) ?>.
</p> </p>
<? } ?> <? } ?>
<? } ?> <? } ?>
@ -188,7 +188,7 @@ catch(Exceptions\EbookNotFoundException){
<p class="us-pd-warning">This ebook is thought to be free of copyright restrictions in the United States. It may still be under copyright in other countries. If youre not located in the United States, you must check your local laws to verify that this ebook is free of copyright restrictions in the country youre located in before accessing, downloading, or using it.</p> <p class="us-pd-warning">This ebook is thought to be free of copyright restrictions in the United States. It may still be under copyright in other countries. If youre not located in the United States, you must check your local laws to verify that this ebook is free of copyright restrictions in the country youre located in before accessing, downloading, or using it.</p>
<div class="downloads-container"> <div class="downloads-container">
<?= Template::RealisticEbook(['ebook' => $ebook]) ?> <?= Template::RealisticEbook(ebook: $ebook) ?>
<div> <div>
<section id="download"> <section id="download">
<h3>Download for ereaders</h3> <h3>Download for ereaders</h3>
@ -383,13 +383,13 @@ catch(Exceptions\EbookNotFoundException){
</section> </section>
<? if(Session::$User?->Benefits->CanEditEbooks){ ?> <? if(Session::$User?->Benefits->CanEditEbooks){ ?>
<?= Template::EbookMetadata(['ebook' => $ebook]) ?> <?= Template::EbookMetadata(ebook: $ebook) ?>
<? } ?> <? } ?>
<? if(sizeof($carousel) > 0){ ?> <? if(sizeof($carousel) > 0){ ?>
<aside id="more-ebooks"> <aside id="more-ebooks">
<h2>More free<? if($carouselTag !== null){ ?> <?= strtolower($carouselTag->Name) ?><? } ?> ebooks</h2> <h2>More free<? if($carouselTag !== null){ ?> <?= strtolower($carouselTag->Name) ?><? } ?> ebooks</h2>
<?= Template::EbookCarousel(['carousel' => $carousel, 'isMultiSize' => true]) ?> <?= Template::EbookCarousel(carousel: $carousel, isMultiSize: true) ?>
</aside> </aside>
<? } ?> <? } ?>
</article> </article>

View file

@ -7,8 +7,8 @@ $pages = 0;
$perPage = HttpInput::Int(GET, 'per-page') ?? EBOOKS_PER_PAGE; $perPage = HttpInput::Int(GET, 'per-page') ?? EBOOKS_PER_PAGE;
$query = HttpInput::Str(GET, 'query') ?? ''; $query = HttpInput::Str(GET, 'query') ?? '';
$tags = HttpInput::Array(GET, 'tags') ?? []; $tags = HttpInput::Array(GET, 'tags') ?? [];
$view = Enums\ViewType::tryFrom(HttpInput::Str(GET, 'view') ?? ''); $view = Enums\ViewType::tryFrom(HttpInput::Str(GET, 'view') ?? '') ?? Enums\ViewType::Grid;
$sort = Enums\EbookSortType::tryFrom(HttpInput::Str(GET, 'sort') ?? ''); $sort = Enums\EbookSortType::tryFrom(HttpInput::Str(GET, 'sort') ?? '') ?? Enums\EbookSortType::Default;
$queryString = ''; $queryString = '';
$queryStringParams = []; $queryStringParams = [];
$queryStringWithoutPage = ''; $queryStringWithoutPage = '';
@ -38,13 +38,8 @@ try{
$sort = Enums\EbookSortType::Newest; $sort = Enums\EbookSortType::Newest;
} }
// If we're passed string values that are the same as the defaults, set them to null so that we can have cleaner query strings in the navigation footer.
if($view === Enums\ViewType::Grid){
$view = null;
}
if(($sort == Enums\EbookSortType::Newest && $query == '') || ($sort == Enums\EbookSortType::Relevance && $query != '')){ if(($sort == Enums\EbookSortType::Newest && $query == '') || ($sort == Enums\EbookSortType::Relevance && $query != '')){
$sort = null; $sort = Enums\EbookSortType::Default;
} }
if(sizeof($tags) == 1 && mb_strtolower($tags[0]) == 'all'){ if(sizeof($tags) == 1 && mb_strtolower($tags[0]) == 'all'){
@ -61,11 +56,12 @@ try{
$queryStringParams['tags'] = $tags; $queryStringParams['tags'] = $tags;
} }
if($view !== null){ // If we're passed string values that are the same as the defaults, don't include them in the query string so that we can have cleaner query strings in the navigation footer.
if($view != Enums\ViewType::Grid){
$queryStringParams['view'] = $view->value; $queryStringParams['view'] = $view->value;
} }
if($sort !== null){ if($sort != Enums\EbookSortType::Default){
$queryStringParams['sort'] = $sort->value; $queryStringParams['sort'] = $sort->value;
} }
@ -134,7 +130,7 @@ catch(Exceptions\PageOutOfBoundsException){
header('Location: ' . $url); header('Location: ' . $url);
exit(); exit();
} }
?><?= Template::Header(['title' => $pageTitle, 'highlight' => 'ebooks', 'description' => $pageDescription, 'canonicalUrl' => $canonicalUrl]) ?> ?><?= Template::Header(title: $pageTitle, highlight: 'ebooks', description: $pageDescription, canonicalUrl: $canonicalUrl) ?>
<main class="ebooks"> <main class="ebooks">
<h1><?= $pageHeader ?></h1> <h1><?= $pageHeader ?></h1>
<?= Template::DonationCounter() ?> <?= Template::DonationCounter() ?>
@ -142,11 +138,12 @@ catch(Exceptions\PageOutOfBoundsException){
<?= Template::DonationAlert() ?> <?= Template::DonationAlert() ?>
<?= Template::SearchForm(['query' => $query, 'tags' => $tags, 'sort' => $sort, 'view' => $view, 'perPage' => $perPage]) ?> <?= Template::SearchForm(query: $query, tags: $tags, sort: $sort, view: $view, perPage: $perPage) ?>
<? if(sizeof($ebooks) == 0){ ?> <? if(sizeof($ebooks) == 0){ ?>
<p class="no-results">No ebooks matched your filters. You can try different filters, or <a href="/ebooks">browse all of our ebooks</a>.</p> <p class="no-results">No ebooks matched your filters. You can try different filters, or <a href="/ebooks">browse all of our ebooks</a>.</p>
<? }else{ ?> <? }else{ ?>
<?= Template::EbookGrid(['ebooks' => $ebooks, 'view' => $view]) ?> <?= Template::EbookGrid(ebooks: $ebooks, view: $view) ?>
<? } ?> <? } ?>
<? if(sizeof($ebooks) > 0){ ?> <? if(sizeof($ebooks) > 0){ ?>
<nav class="pagination"> <nav class="pagination">

View file

@ -23,7 +23,7 @@ if($feedType == Enums\FeedType::Atom){
$title = 'Standard Ebooks Atom Feeds'; $title = 'Standard Ebooks Atom Feeds';
} }
?><?= Template::Header(['title' => 'The Standard Ebooks OPDS feed', 'highlight' => '', 'description' => 'Get access to the Standard Ebooks OPDS feed for use in ereading programs in scripting.']) ?> ?><?= Template::Header(title: 'The Standard Ebooks OPDS feed', description: 'Get access to the Standard Ebooks OPDS feed for use in ereading programs in scripting.') ?>
<main> <main>
<section class="narrow has-hero"> <section class="narrow has-hero">
<? if($feedType == Enums\FeedType::Opds){ ?> <? if($feedType == Enums\FeedType::Opds){ ?>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Atom 1.0 Ebook Feeds', 'description' => 'A list of available Atom 1.0 feeds of Standard Ebooks ebooks.']) ?> <?= Template::Header(title: 'Atom 1.0 Ebook Feeds', description: 'A list of available Atom 1.0 feeds of Standard Ebooks ebooks.') ?>
<main> <main>
<section class="narrow"> <section class="narrow">
<h1>Atom 1.0 Ebook Feeds</h1> <h1>Atom 1.0 Ebook Feeds</h1>

View file

@ -24,7 +24,7 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<?xml-stylesheet href=\"" . S
<uri><?= SITE_URL ?></uri> <uri><?= SITE_URL ?></uri>
</author> </author>
<opensearch:totalResults><?= sizeof($ebooks) ?></opensearch:totalResults> <opensearch:totalResults><?= sizeof($ebooks) ?></opensearch:totalResults>
<? foreach($ebooks as $ebook){ ?> <? foreach($ebooks as $ebook){ ?>
<?= Template::AtomFeedEntry(['entry' => $ebook]) ?> <?= Template::AtomFeedEntry(entry: $ebook) ?>
<? } ?> <? } ?>
</feed> </feed>

View file

@ -15,7 +15,7 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"> <xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
<xsl:output method="html" html-version="5.0" encoding="utf-8" indent="yes" doctype-system="about:legacy-compat"/> <? /* doctype-system outputs the HTML5 doctype */ ?> <xsl:output method="html" html-version="5.0" encoding="utf-8" indent="yes" doctype-system="about:legacy-compat"/> <? /* doctype-system outputs the HTML5 doctype */ ?>
<xsl:template match="/"> <xsl:template match="/">
<?= Template::Header(['isXslt' => true]) ?> <?= Template::Header(isXslt: true) ?>
<main class="opds"> <main class="opds">
<xsl:choose> <xsl:choose>
<xsl:when test="contains(/atom:feed/atom:title, 'Standard Ebooks - ')"> <xsl:when test="contains(/atom:feed/atom:title, 'Standard Ebooks - ')">

View file

@ -29,7 +29,7 @@ catch(Safe\Exceptions\ApcuException){
Template::ExitWithCode(Enums\HttpCode::NotFound); Template::ExitWithCode(Enums\HttpCode::NotFound);
} }
} }
?><?= Template::Header(['title' => $type->GetDisplayName() . ' Ebook Feeds by ' . $ucTitle, 'description' => 'A list of available ' . $type->GetDisplayName() . ' feeds of Standard Ebooks ebooks by ' . $lcTitle . '.']) ?> ?><?= Template::Header(title: $type->GetDisplayName() . ' Ebook Feeds by ' . $ucTitle, description: 'A list of available ' . $type->GetDisplayName() . ' feeds of Standard Ebooks ebooks by ' . $lcTitle . '.') ?>
<main> <main>
<article> <article>
<h1><?= $type->GetDisplayName() ?> Ebook Feeds by <?= $ucTitle ?></h1> <h1><?= $type->GetDisplayName() ?> Ebook Feeds by <?= $ucTitle ?></h1>

View file

@ -55,7 +55,7 @@ try{
catch(Exceptions\CollectionNotFoundException){ catch(Exceptions\CollectionNotFoundException){
Template::ExitWithCode(Enums\HttpCode::NotFound); Template::ExitWithCode(Enums\HttpCode::NotFound);
} }
?><?= Template::Header(['title' => $title, 'feedTitle' => $feedTitle, 'feedUrl' => $feedUrl, 'description' => $description]) ?> ?><?= Template::Header(title: $title, feedTitle: $feedTitle, feedUrl: $feedUrl, description: $description) ?>
<main> <main>
<article> <article>
<h1>Ebook Feeds for <?= Formatter::EscapeHtml($label) ?></h1> <h1>Ebook Feeds for <?= Formatter::EscapeHtml($label) ?></h1>

View file

@ -1,4 +1,4 @@
<?= Template::Header(['title' => 'Ebook Feeds', 'description' => 'A list of available feeds of Standard Ebooks ebooks.']) ?> <?= Template::Header(title: 'Ebook Feeds', description: 'A list of available feeds of Standard Ebooks ebooks.') ?>
<main> <main>
<section class="narrow has-hero"> <section class="narrow has-hero">
<h1>Ebook Feeds</h1> <h1>Ebook Feeds</h1>

View file

@ -27,6 +27,6 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<?xml-stylesheet href=\"". SI
</author> </author>
<opensearch:totalResults><?= sizeof($ebooks) ?></opensearch:totalResults> <opensearch:totalResults><?= sizeof($ebooks) ?></opensearch:totalResults>
<? foreach($ebooks as $ebook){ ?> <? foreach($ebooks as $ebook){ ?>
<?= Template::OpdsAcquisitionEntry(['entry' => $ebook]) ?> <?= Template::OpdsAcquisitionEntry(entry: $ebook) ?>
<? } ?> <? } ?>
</feed> </feed>

Some files were not shown because too many files have changed in this diff Show more