mirror of
https://github.com/standardebooks/web.git
synced 2025-07-07 15:20:32 -04:00
Add placeholders for ebooks
This commit is contained in:
parent
cf5f488cae
commit
1ab95df084
52 changed files with 1192 additions and 237 deletions
|
@ -7,6 +7,7 @@ CREATE TABLE IF NOT EXISTS `Benefits` (
|
|||
`CanReviewArtwork` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
`CanReviewOwnArtwork` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
`CanEditUsers` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
`CanCreateEbookPlaceholders` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`UserId`),
|
||||
KEY `idxBenefits` (`CanAccessFeeds`,`CanVote`,`CanBulkDownload`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
|
11
config/sql/se/EbookPlaceholders.sql
Normal file
11
config/sql/se/EbookPlaceholders.sql
Normal file
|
@ -0,0 +1,11 @@
|
|||
CREATE TABLE IF NOT EXISTS `EbookPlaceholders` (
|
||||
`EbookId` int(10) unsigned NOT NULL,
|
||||
`YearPublished` smallint unsigned NULL,
|
||||
`Status` enum('wanted', 'in_progress') NULL,
|
||||
`Difficulty` enum('beginner', 'intermediate', 'advanced') NULL,
|
||||
`TranscriptionUrl` varchar(511) NULL,
|
||||
`IsWanted` boolean NOT NULL DEFAULT FALSE,
|
||||
`IsPatron` boolean NOT NULL DEFAULT FALSE,
|
||||
`Notes` TEXT NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`EbookId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
@ -3,8 +3,8 @@ CREATE TABLE IF NOT EXISTS `Ebooks` (
|
|||
`Identifier` varchar(511) NOT NULL,
|
||||
`Created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`Updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`WwwFilesystemPath` varchar(511) NOT NULL,
|
||||
`RepoFilesystemPath` varchar(511) NOT NULL,
|
||||
`WwwFilesystemPath` varchar(511) NULL,
|
||||
`RepoFilesystemPath` varchar(511) NULL,
|
||||
`KindleCoverUrl` varchar(511) NULL,
|
||||
`EpubUrl` varchar(511) NULL,
|
||||
`AdvancedEpubUrl` varchar(511) NULL,
|
||||
|
@ -14,16 +14,16 @@ CREATE TABLE IF NOT EXISTS `Ebooks` (
|
|||
`Title` varchar(255) NOT NULL,
|
||||
`FullTitle` varchar(255) NULL,
|
||||
`AlternateTitle` varchar(255) NULL,
|
||||
`Description` text NOT NULL,
|
||||
`LongDescription` text NOT NULL,
|
||||
`Language` varchar(10) NOT NULL,
|
||||
`WordCount` int(10) unsigned NOT NULL,
|
||||
`ReadingEase` float NOT NULL,
|
||||
`Description` text NULL,
|
||||
`LongDescription` text NULL,
|
||||
`Language` varchar(10) NULL,
|
||||
`WordCount` int(10) unsigned NULL,
|
||||
`ReadingEase` float NULL,
|
||||
`GitHubUrl` varchar(255) NULL,
|
||||
`WikipediaUrl` varchar(255) NULL,
|
||||
`EbookCreated` datetime NOT NULL,
|
||||
`EbookUpdated` datetime NOT NULL,
|
||||
`TextSinglePageByteCount` bigint unsigned NOT NULL,
|
||||
`EbookCreated` datetime NULL,
|
||||
`EbookUpdated` datetime NULL,
|
||||
`TextSinglePageByteCount` bigint unsigned NULL,
|
||||
`IndexableText` text NOT NULL,
|
||||
PRIMARY KEY (`EbookId`),
|
||||
UNIQUE KEY `index1` (`Identifier`),
|
||||
|
|
|
@ -15,6 +15,7 @@ class Benefits{
|
|||
public bool $CanReviewArtwork = false;
|
||||
public bool $CanReviewOwnArtwork = false;
|
||||
public bool $CanEditUsers = false;
|
||||
public bool $CanCreateEbookPlaceholders = false;
|
||||
|
||||
protected bool $_HasBenefits;
|
||||
|
||||
|
@ -27,6 +28,8 @@ class Benefits{
|
|||
$this->CanReviewOwnArtwork
|
||||
||
|
||||
$this->CanEditUsers
|
||||
||
|
||||
$this->CanCreateEbookPlaceholders
|
||||
){
|
||||
return true;
|
||||
}
|
||||
|
@ -58,18 +61,18 @@ class Benefits{
|
|||
|
||||
public function Create(): void{
|
||||
Db::Query('
|
||||
INSERT into Benefits (UserId, CanAccessFeeds, CanVote, CanBulkDownload, CanUploadArtwork, CanReviewArtwork, CanReviewOwnArtwork, CanEditUsers)
|
||||
values (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
', [$this->UserId, $this->CanAccessFeeds, $this->CanVote, $this->CanBulkDownload, $this->CanUploadArtwork, $this->CanReviewArtwork, $this->CanReviewOwnArtwork, $this->CanEditUsers]);
|
||||
INSERT into Benefits (UserId, CanAccessFeeds, CanVote, CanBulkDownload, CanUploadArtwork, CanReviewArtwork, CanReviewOwnArtwork, CanEditUsers, CanCreateEbookPlaceholders)
|
||||
values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
', [$this->UserId, $this->CanAccessFeeds, $this->CanVote, $this->CanBulkDownload, $this->CanUploadArtwork, $this->CanReviewArtwork, $this->CanReviewOwnArtwork, $this->CanEditUsers, $this->CanCreateEbookPlaceholders]);
|
||||
}
|
||||
|
||||
public function Save(): void{
|
||||
Db::Query('
|
||||
UPDATE Benefits
|
||||
set CanAccessFeeds = ?, CanVote = ?, CanBulkDownload = ?, CanUploadArtwork = ?, CanReviewArtwork = ?, CanReviewOwnArtwork = ?, CanEditUsers = ?
|
||||
set CanAccessFeeds = ?, CanVote = ?, CanBulkDownload = ?, CanUploadArtwork = ?, CanReviewArtwork = ?, CanReviewOwnArtwork = ?, CanEditUsers = ?, CanCreateEbookPlaceholders = ?
|
||||
where
|
||||
UserId = ?
|
||||
', [$this->CanAccessFeeds, $this->CanVote, $this->CanBulkDownload, $this->CanUploadArtwork, $this->CanReviewArtwork, $this->CanReviewOwnArtwork, $this->CanEditUsers, $this->UserId]);
|
||||
', [$this->CanAccessFeeds, $this->CanVote, $this->CanBulkDownload, $this->CanUploadArtwork, $this->CanReviewArtwork, $this->CanReviewOwnArtwork, $this->CanEditUsers, $this->CanCreateEbookPlaceholders, $this->UserId]);
|
||||
}
|
||||
|
||||
public function FillFromHttpPost(): void{
|
||||
|
@ -80,5 +83,6 @@ class Benefits{
|
|||
$this->PropertyFromHttp('CanReviewArtwork');
|
||||
$this->PropertyFromHttp('CanReviewOwnArtwork');
|
||||
$this->PropertyFromHttp('CanEditUsers');
|
||||
$this->PropertyFromHttp('CanCreateEbookPlaceholders');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,8 @@ const MANUAL_PATH = WEB_ROOT . '/manual';
|
|||
const EBOOKS_DIST_PATH = WEB_ROOT . '/ebooks/';
|
||||
const COVER_ART_UPLOAD_PATH = '/images/cover-uploads/';
|
||||
|
||||
const EBOOKS_IDENTIFIER_PREFIX = 'url:https://standardebooks.org/ebooks/';
|
||||
const EBOOKS_IDENTIFIER_ROOT = 'url:https://standardebooks.org';
|
||||
const EBOOKS_IDENTIFIER_PREFIX = EBOOKS_IDENTIFIER_ROOT . '/ebooks/';
|
||||
|
||||
const DATABASE_DEFAULT_DATABASE = 'se';
|
||||
const DATABASE_DEFAULT_HOST = 'localhost';
|
||||
|
|
|
@ -41,6 +41,28 @@ class Contributor{
|
|||
// METHODS
|
||||
// *******
|
||||
|
||||
/**
|
||||
* @return array<Contributor>
|
||||
*/
|
||||
public static function GetAllAuthorNames(): array{
|
||||
return Db::Query('
|
||||
SELECT DISTINCT Name
|
||||
from Contributors
|
||||
where MarcRole = "aut"
|
||||
order by Name asc', [], Contributor::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Contributor>
|
||||
*/
|
||||
public static function GetAllTranslatorNames(): array{
|
||||
return Db::Query('
|
||||
SELECT DISTINCT Name
|
||||
from Contributors
|
||||
where MarcRole = "trl"
|
||||
order by Name asc', [], Contributor::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exceptions\ValidationException
|
||||
*/
|
||||
|
|
259
lib/Ebook.php
259
lib/Ebook.php
|
@ -41,14 +41,15 @@ use function Safe\shell_exec;
|
|||
* @property string $TextSinglePageUrl
|
||||
* @property string $TextSinglePageSizeFormatted
|
||||
* @property string $IndexableText
|
||||
* @property EbookPlaceholder $EbookPlaceholder
|
||||
*/
|
||||
class Ebook{
|
||||
use Traits\Accessor;
|
||||
|
||||
public int $EbookId;
|
||||
public string $Identifier;
|
||||
public string $WwwFilesystemPath;
|
||||
public string $RepoFilesystemPath;
|
||||
public ?string $WwwFilesystemPath = null;
|
||||
public ?string $RepoFilesystemPath = null;
|
||||
public ?string $KindleCoverUrl = null;
|
||||
public ?string $EpubUrl = null;
|
||||
public ?string $AdvancedEpubUrl = null;
|
||||
|
@ -58,17 +59,17 @@ class Ebook{
|
|||
public string $Title;
|
||||
public ?string $FullTitle = null;
|
||||
public ?string $AlternateTitle = null;
|
||||
public string $Description;
|
||||
public string $LongDescription;
|
||||
public string $Language;
|
||||
public int $WordCount;
|
||||
public float $ReadingEase;
|
||||
public ?string $Description = null;
|
||||
public ?string $LongDescription = null;
|
||||
public ?string $Language = null;
|
||||
public ?int $WordCount = null;
|
||||
public ?float $ReadingEase = null;
|
||||
public ?string $GitHubUrl = null;
|
||||
public ?string $WikipediaUrl = null;
|
||||
/** When the ebook was published. */
|
||||
public DateTimeImmutable $EbookCreated;
|
||||
public ?DateTimeImmutable $EbookCreated = null;
|
||||
/** When the ebook was updated. */
|
||||
public DateTimeImmutable $EbookUpdated;
|
||||
public ?DateTimeImmutable $EbookUpdated = null;
|
||||
/** When the database row was created. */
|
||||
public DateTimeImmutable $Created;
|
||||
/** When the database row was updated. */
|
||||
|
@ -116,6 +117,7 @@ class Ebook{
|
|||
protected string $_TextSinglePageUrl;
|
||||
protected string $_TextSinglePageSizeFormatted;
|
||||
protected string $_IndexableText;
|
||||
protected ?EbookPlaceholder $_EbookPlaceholder = null;
|
||||
|
||||
// *******
|
||||
// GETTERS
|
||||
|
@ -318,7 +320,7 @@ class Ebook{
|
|||
|
||||
protected function GetUrl(): string{
|
||||
if(!isset($this->_Url)){
|
||||
$this->_Url = str_replace(WEB_ROOT, '', $this->WwwFilesystemPath);
|
||||
$this->_Url = str_replace(EBOOKS_IDENTIFIER_ROOT, '', $this->Identifier);
|
||||
}
|
||||
|
||||
return $this->_Url;
|
||||
|
@ -554,7 +556,7 @@ class Ebook{
|
|||
protected function GetTextSinglePageSizeFormatted(): string{
|
||||
if(!isset($this->_TextSinglePageSizeFormatted)){
|
||||
$bytes = $this->TextSinglePageByteCount;
|
||||
$sizes = array('B', 'KB', 'MB', 'GB', 'TB', 'PB');
|
||||
$sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
|
||||
$index = 0;
|
||||
while($bytes >= 1024 && $index < count($sizes) - 1){
|
||||
|
@ -608,6 +610,18 @@ class Ebook{
|
|||
return $this->_IndexableText;
|
||||
}
|
||||
|
||||
protected function GetEbookPlaceholder(): ?EbookPlaceholder{
|
||||
if(!isset($this->_EbookPlaceholder)){
|
||||
$this->_EbookPlaceholder = Db::Query('
|
||||
SELECT *
|
||||
from EbookPlaceholders
|
||||
where EbookId = ?
|
||||
', [$this->EbookId], EbookPlaceholder::class)[0] ?? null;
|
||||
}
|
||||
|
||||
return $this->_EbookPlaceholder;
|
||||
}
|
||||
|
||||
|
||||
// ***********
|
||||
// ORM METHODS
|
||||
|
@ -970,6 +984,59 @@ class Ebook{
|
|||
return $ebook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins the `Name` properites of `Contributor` objects as a URL slug, e.g.,
|
||||
*
|
||||
* ```
|
||||
* ([0] => Contributor Object ([Name] => William Wordsworth), ([1] => Contributor Object ([Name] => Samuel Coleridge)))
|
||||
* ```
|
||||
*
|
||||
* returns `william-wordsworth_samuel-taylor-coleridge`.
|
||||
*
|
||||
* @param array<Contributor> $contributors
|
||||
*/
|
||||
protected static function GetContributorsUrlSlug(array $contributors): string{
|
||||
return implode('_', array_map('Formatter::MakeUrlSafe', array_column($contributors, 'Name')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the `Identifier` property based on the `Title`, `Authors`, `Translators`, and `Illustrators`. Used when creating ebook placeholders.
|
||||
*
|
||||
* @throws Exceptions\InvalidEbookIdentifierException
|
||||
*/
|
||||
public function FillIdentifierFromTitleAndContributors(): void{
|
||||
if(!isset($this->Authors) || sizeof($this->Authors) == 0){
|
||||
throw new Exceptions\InvalidEbookIdentifierException('Authors required');
|
||||
}
|
||||
|
||||
if(!isset($this->Title)){
|
||||
throw new Exceptions\InvalidEbookIdentifierException('Title required');
|
||||
}
|
||||
|
||||
$authorString = Ebook::GetContributorsUrlSlug($this->Authors);
|
||||
$titleString = Formatter::MakeUrlSafe($this->Title);
|
||||
$translatorString = '';
|
||||
$illustratorString = '';
|
||||
|
||||
if(isset($this->Translators) && sizeof($this->Translators) > 0){
|
||||
$translatorString = Ebook::GetContributorsUrlSlug($this->Translators);
|
||||
}
|
||||
|
||||
if(isset($this->Illustrators) && sizeof($this->Illustrators) > 0){
|
||||
$illustratorString = Ebook::GetContributorsUrlSlug($this->Illustrators);
|
||||
}
|
||||
|
||||
$this->Identifier = EBOOKS_IDENTIFIER_PREFIX . $authorString . '/' . $titleString;
|
||||
|
||||
if($translatorString != ''){
|
||||
$this->Identifier .= '/' . $translatorString;
|
||||
}
|
||||
|
||||
if($illustratorString != ''){
|
||||
$this->Identifier .= '/' . $illustratorString;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// *******
|
||||
// METHODS
|
||||
|
@ -996,13 +1063,12 @@ class Ebook{
|
|||
$error->Add(new Exceptions\EbookIdentifierRequiredException());
|
||||
}
|
||||
|
||||
$this->WwwFilesystemPath = trim($this->WwwFilesystemPath ?? '');
|
||||
if($this->WwwFilesystemPath == ''){
|
||||
$this->WwwFilesystemPath = null;
|
||||
}
|
||||
|
||||
if(isset($this->WwwFilesystemPath)){
|
||||
$this->WwwFilesystemPath = trim($this->WwwFilesystemPath);
|
||||
|
||||
if($this->WwwFilesystemPath == ''){
|
||||
$error->Add(new Exceptions\EbookWwwFilesystemPathRequiredException());
|
||||
}
|
||||
|
||||
if(strlen($this->WwwFilesystemPath) > EBOOKS_MAX_LONG_STRING_LENGTH){
|
||||
$error->Add(new Exceptions\StringTooLongException('Ebook WwwFilesystemPath'));
|
||||
}
|
||||
|
@ -1011,17 +1077,13 @@ class Ebook{
|
|||
$error->Add(new Exceptions\InvalidEbookWwwFilesystemPathException($this->WwwFilesystemPath));
|
||||
}
|
||||
}
|
||||
else{
|
||||
$error->Add(new Exceptions\EbookWwwFilesystemPathRequiredException());
|
||||
|
||||
$this->RepoFilesystemPath = trim($this->RepoFilesystemPath ?? '');
|
||||
if($this->RepoFilesystemPath == ''){
|
||||
$this->RepoFilesystemPath = null;
|
||||
}
|
||||
|
||||
if(isset($this->RepoFilesystemPath)){
|
||||
$this->RepoFilesystemPath = trim($this->RepoFilesystemPath);
|
||||
|
||||
if($this->RepoFilesystemPath == ''){
|
||||
$error->Add(new Exceptions\EbookRepoFilesystemPathRequiredException());
|
||||
}
|
||||
|
||||
if(strlen($this->RepoFilesystemPath) > EBOOKS_MAX_LONG_STRING_LENGTH){
|
||||
$error->Add(new Exceptions\StringTooLongException('Ebook RepoFilesystemPath'));
|
||||
}
|
||||
|
@ -1030,9 +1092,6 @@ class Ebook{
|
|||
$error->Add(new Exceptions\InvalidEbookRepoFilesystemPathException($this->RepoFilesystemPath));
|
||||
}
|
||||
}
|
||||
else{
|
||||
$error->Add(new Exceptions\EbookRepoFilesystemPathRequiredException());
|
||||
}
|
||||
|
||||
$this->KindleCoverUrl = trim($this->KindleCoverUrl ?? '');
|
||||
if($this->KindleCoverUrl == ''){
|
||||
|
@ -1157,51 +1216,36 @@ class Ebook{
|
|||
$error->Add(new Exceptions\StringTooLongException('Ebook AlternateTitle'));
|
||||
}
|
||||
|
||||
if(isset($this->Description)){
|
||||
$this->Description = trim($this->Description);
|
||||
|
||||
if($this->Description == ''){
|
||||
$error->Add(new Exceptions\EbookDescriptionRequiredException());
|
||||
}
|
||||
}
|
||||
else{
|
||||
$error->Add(new Exceptions\EbookDescriptionRequiredException());
|
||||
$this->Description = trim($this->Description ?? '');
|
||||
if($this->Description == ''){
|
||||
$this->Description = null;
|
||||
}
|
||||
|
||||
if(isset($this->LongDescription)){
|
||||
$this->LongDescription = trim($this->LongDescription);
|
||||
|
||||
if($this->LongDescription == ''){
|
||||
$error->Add(new Exceptions\EbookLongDescriptionRequiredException());
|
||||
}
|
||||
if(isset($this->Description) && strlen($this->Description) > EBOOKS_MAX_STRING_LENGTH){
|
||||
$error->Add(new Exceptions\StringTooLongException('Ebook Description'));
|
||||
}
|
||||
else{
|
||||
$error->Add(new Exceptions\EbookLongDescriptionRequiredException());
|
||||
|
||||
$this->LongDescription = trim($this->LongDescription ?? '');
|
||||
if($this->LongDescription == ''){
|
||||
$this->LongDescription = null;
|
||||
}
|
||||
|
||||
$this->Language = trim($this->Language ?? '');
|
||||
if($this->Language == ''){
|
||||
$this->Language = null;
|
||||
}
|
||||
|
||||
if(isset($this->Language)){
|
||||
$this->Language = trim($this->Language);
|
||||
|
||||
if($this->Language == ''){
|
||||
$error->Add(new Exceptions\EbookLanguageRequiredException());
|
||||
}
|
||||
|
||||
if(strlen($this->Language) > 10){
|
||||
$error->Add(new Exceptions\StringTooLongException('Ebook Language: ' . $this->Language));
|
||||
}
|
||||
}
|
||||
else{
|
||||
$error->Add(new Exceptions\EbookLanguageRequiredException());
|
||||
}
|
||||
|
||||
if(isset($this->WordCount)){
|
||||
if($this->WordCount <= 0){
|
||||
$error->Add(new Exceptions\InvalidEbookWordCountException('Invalid Ebook WordCount: ' . $this->WordCount));
|
||||
}
|
||||
}
|
||||
else{
|
||||
$error->Add(new Exceptions\EbookWordCountRequiredException());
|
||||
}
|
||||
|
||||
if(isset($this->ReadingEase)){
|
||||
// In theory, Flesch reading ease can be negative, but in practice it's positive.
|
||||
|
@ -1209,9 +1253,6 @@ class Ebook{
|
|||
$error->Add(new Exceptions\InvalidEbookReadingEaseException('Invalid Ebook ReadingEase: ' . $this->ReadingEase));
|
||||
}
|
||||
}
|
||||
else{
|
||||
$error->Add(new Exceptions\EbookReadingEaseRequiredException());
|
||||
}
|
||||
|
||||
$this->GitHubUrl = trim($this->GitHubUrl ?? '');
|
||||
if($this->GitHubUrl == ''){
|
||||
|
@ -1248,9 +1289,6 @@ class Ebook{
|
|||
$error->Add(new Exceptions\InvalidEbookCreatedDatetimeException($this->EbookCreated));
|
||||
}
|
||||
}
|
||||
else{
|
||||
$error->Add(new Exceptions\EbookCreatedDatetimeRequiredException());
|
||||
}
|
||||
|
||||
if(isset($this->EbookUpdated)){
|
||||
if($this->EbookUpdated > NOW){
|
||||
|
@ -1258,18 +1296,12 @@ class Ebook{
|
|||
|
||||
}
|
||||
}
|
||||
else{
|
||||
$error->Add(new Exceptions\EbookUpdatedDatetimeRequiredException());
|
||||
}
|
||||
|
||||
if(isset($this->TextSinglePageByteCount)){
|
||||
if($this->TextSinglePageByteCount <= 0){
|
||||
$error->Add(new Exceptions\InvalidEbookTextSinglePageByteCountException('Invalid Ebook TextSinglePageByteCount: ' . $this->TextSinglePageByteCount));
|
||||
}
|
||||
}
|
||||
else{
|
||||
$error->Add(new Exceptions\EbookTextSinglePageByteCountRequiredException());
|
||||
}
|
||||
|
||||
if(isset($this->IndexableText)){
|
||||
$this->IndexableText = trim($this->IndexableText ?? '');
|
||||
|
@ -1282,6 +1314,23 @@ class Ebook{
|
|||
$error->Add(new Exceptions\EbookIndexableTextRequiredException());
|
||||
}
|
||||
|
||||
if(isset($this->EbookPlaceholder)){
|
||||
try{
|
||||
$this->EbookPlaceholder->Validate();
|
||||
}
|
||||
catch(Exceptions\ValidationException $ex){
|
||||
$error->Add($ex);
|
||||
}
|
||||
}
|
||||
|
||||
if($this->IsPlaceholder() && !isset($this->EbookPlaceholder)){
|
||||
$error->Add(new Exceptions\EbookMissingPlaceholderException());
|
||||
}
|
||||
|
||||
if(!$this->IsPlaceholder() && isset($this->EbookPlaceholder)){
|
||||
$error->Add(new Exceptions\EbookUnexpectedPlaceholderException());
|
||||
}
|
||||
|
||||
if($error->HasExceptions){
|
||||
throw $error;
|
||||
}
|
||||
|
@ -1289,6 +1338,7 @@ class Ebook{
|
|||
|
||||
/**
|
||||
* @throws Exceptions\ValidationException
|
||||
* @throws Exceptions\DuplicateEbookException
|
||||
*/
|
||||
public function CreateOrUpdate(): void{
|
||||
try{
|
||||
|
@ -1550,6 +1600,10 @@ class Ebook{
|
|||
return $string;
|
||||
}
|
||||
|
||||
public function IsPlaceholder(): bool{
|
||||
return $this->WwwFilesystemPath === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given list of elements has an element that is not `''`, return that value; otherwise, return `null`.
|
||||
*
|
||||
|
@ -1572,10 +1626,19 @@ class Ebook{
|
|||
|
||||
/**
|
||||
* @throws Exceptions\ValidationException
|
||||
* @throws Exceptions\DuplicateEbookException If an `Ebook` with the given identifier already exists.
|
||||
*/
|
||||
public function Create(): void{
|
||||
$this->Validate();
|
||||
|
||||
try{
|
||||
Ebook::GetByIdentifier($this->Identifier);
|
||||
throw new Exceptions\DuplicateEbookException($this->Identifier);
|
||||
}
|
||||
catch(Exceptions\EbookNotFoundException){
|
||||
// Pass.
|
||||
}
|
||||
|
||||
$this->CreateTags();
|
||||
$this->CreateLocSubjects();
|
||||
$this->CreateCollections();
|
||||
|
@ -1623,6 +1686,7 @@ class Ebook{
|
|||
$this->AddSources();
|
||||
$this->AddContributors();
|
||||
$this->AddTocEntries();
|
||||
$this->AddEbookPlaceholder();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1690,6 +1754,9 @@ class Ebook{
|
|||
|
||||
$this->RemoveTocEntries();
|
||||
$this->AddTocEntries();
|
||||
|
||||
$this->RemoveEbookPlaceholder();
|
||||
$this->AddEbookPlaceholder();
|
||||
}
|
||||
|
||||
private function RemoveTags(): void{
|
||||
|
@ -1848,6 +1915,24 @@ class Ebook{
|
|||
}
|
||||
}
|
||||
|
||||
private function RemoveEbookPlaceholder(): void{
|
||||
Db::Query('
|
||||
DELETE from EbookPlaceholders
|
||||
where EbookId = ?
|
||||
', [$this->EbookId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exceptions\ValidationException
|
||||
*/
|
||||
private function AddEbookPlaceholder(): void{
|
||||
if(isset($this->EbookPlaceholder)){
|
||||
$this->EbookPlaceholder->EbookId = $this->EbookId;
|
||||
$this->EbookPlaceholder->Create();
|
||||
}
|
||||
}
|
||||
|
||||
// ***********
|
||||
// ORM METHODS
|
||||
// ***********
|
||||
|
@ -1929,6 +2014,9 @@ class Ebook{
|
|||
}
|
||||
|
||||
/**
|
||||
* Queries for books in a collection.
|
||||
*
|
||||
* Puts ebooks without a `SequenceNumber` at the end of the list, which is more common in a collection with both published and placeholder ebooks.
|
||||
* @return array<Ebook>
|
||||
*/
|
||||
public static function GetAllByCollection(string $collection): array{
|
||||
|
@ -1938,13 +2026,16 @@ class Ebook{
|
|||
inner join CollectionEbooks ce using (EbookId)
|
||||
inner join Collections c using (CollectionId)
|
||||
where c.UrlName = ?
|
||||
order by ce.SequenceNumber, e.EbookCreated desc
|
||||
order by ce.SequenceNumber is null, ce.SequenceNumber, e.EbookCreated desc
|
||||
', [$collection], Ebook::class);
|
||||
|
||||
return $ebooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries for related to books to be shown, e.g., in a carousel.
|
||||
*
|
||||
* Filters out placeholder books because they are not useful for browsing.
|
||||
* @return array<Ebook>
|
||||
*/
|
||||
public static function GetAllByRelated(Ebook $ebook, int $count, ?EbookTag $relatedTag): array{
|
||||
|
@ -1955,6 +2046,7 @@ class Ebook{
|
|||
inner join EbookTags et using (EbookId)
|
||||
where et.TagId = ?
|
||||
and et.EbookId != ?
|
||||
and e.WwwFilesystemPath is not null
|
||||
order by rand()
|
||||
limit ?
|
||||
', [$relatedTag->TagId, $ebook->EbookId, $count], Ebook::class);
|
||||
|
@ -1964,6 +2056,7 @@ class Ebook{
|
|||
SELECT *
|
||||
from Ebooks
|
||||
where EbookId != ?
|
||||
and WwwFilesystemPath is not null
|
||||
order by rand()
|
||||
limit ?
|
||||
', [$ebook->EbookId, $count], Ebook::class);
|
||||
|
@ -1977,25 +2070,43 @@ class Ebook{
|
|||
*
|
||||
* @return array{ebooks: array<Ebook>, ebooksCount: int}
|
||||
*/
|
||||
public static function GetAllByFilter(string $query = null, array $tags = [], Enums\EbookSortType $sort = null, int $page = 1, int $perPage = EBOOKS_PER_PAGE): array{
|
||||
public static function GetAllByFilter(string $query = null, array $tags = [], Enums\EbookSortType $sort = null, int $page = 1, int $perPage = EBOOKS_PER_PAGE, Enums\EbookReleaseStatusFilter $releaseStatusFilter = Enums\EbookReleaseStatusFilter::All): array{
|
||||
$limit = $perPage;
|
||||
$offset = (($page - 1) * $perPage);
|
||||
$joinContributors = '';
|
||||
$joinTags = '';
|
||||
$params = [];
|
||||
$whereCondition = 'where true';
|
||||
|
||||
switch($releaseStatusFilter){
|
||||
case Enums\EbookReleaseStatusFilter::Released:
|
||||
$whereCondition = 'where e.WwwFilesystemPath is not null';
|
||||
break;
|
||||
case Enums\EbookReleaseStatusFilter::Placeholder:
|
||||
$whereCondition = 'where e.WwwFilesystemPath is null';
|
||||
break;
|
||||
case Enums\EbookReleaseStatusFilter::All:
|
||||
default:
|
||||
if($query !== null && $query != ''){
|
||||
// If the query is present, show both released and placeholder ebooks.
|
||||
$whereCondition = 'where true';
|
||||
}else{
|
||||
// If there is no query, hide placeholder ebooks.
|
||||
$whereCondition = 'where e.WwwFilesystemPath is not null';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$orderBy = 'e.EbookCreated desc';
|
||||
if($sort == Enums\EbookSortType::AuthorAlpha){
|
||||
$joinContributors = 'inner join Contributors con using (EbookId)';
|
||||
$whereCondition .= ' and con.MarcRole = "aut"';
|
||||
$orderBy = 'con.SortName, e.EbookCreated desc';
|
||||
$orderBy = 'e.WwwFilesystemPath is null, con.SortName, e.EbookCreated desc'; // Put placeholders at the end
|
||||
}
|
||||
elseif($sort == Enums\EbookSortType::ReadingEase){
|
||||
$orderBy = 'e.ReadingEase desc';
|
||||
}
|
||||
elseif($sort == Enums\EbookSortType::Length){
|
||||
$orderBy = 'e.WordCount';
|
||||
$orderBy = 'e.WwwFilesystemPath is null, e.WordCount'; // Put placeholders at the end
|
||||
}
|
||||
|
||||
if(sizeof($tags) > 0 && !in_array('all', $tags)){ // 0 tags means "all ebooks"
|
||||
|
|
88
lib/EbookPlaceholder.php
Normal file
88
lib/EbookPlaceholder.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?
|
||||
|
||||
/**
|
||||
* @property bool $IsPublicDomain
|
||||
*/
|
||||
class EbookPlaceholder{
|
||||
use Traits\Accessor;
|
||||
use Traits\PropertyFromHttp;
|
||||
|
||||
public int $EbookId;
|
||||
public ?int $YearPublished = null;
|
||||
public bool $IsWanted = false;
|
||||
public ?Enums\EbookPlaceholderStatus $Status = null;
|
||||
public ?Enums\EbookPlaceholderDifficulty $Difficulty = null;
|
||||
public ?string $TranscriptionUrl = null;
|
||||
public bool $IsPatron = false;
|
||||
public ?string $Notes = null;
|
||||
|
||||
protected bool $_IsPublicDomain;
|
||||
|
||||
protected function GetIsPublicDomain(): bool{
|
||||
if(!isset($this->_IsPublicDomain)){
|
||||
$this->_IsPublicDomain = $this->YearPublished === null ? true : $this->YearPublished <= PD_YEAR;
|
||||
}
|
||||
|
||||
return $this->_IsPublicDomain;
|
||||
}
|
||||
|
||||
public function FillFromHttpPost(): void{
|
||||
$this->PropertyFromHttp('YearPublished');
|
||||
$this->PropertyFromHttp('IsWanted');
|
||||
|
||||
// These properties apply only to books on the SE wanted list.
|
||||
if($this->IsWanted){
|
||||
$this->PropertyFromHttp('Status');
|
||||
$this->PropertyFromHttp('Difficulty');
|
||||
$this->PropertyFromHttp('TranscriptionUrl');
|
||||
$this->PropertyFromHttp('IsPatron');
|
||||
$this->PropertyFromHttp('Notes');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exceptions\ValidationException
|
||||
*/
|
||||
public function Validate(): void{
|
||||
$thisYear = intval(NOW->format('Y'));
|
||||
$error = new Exceptions\ValidationException();
|
||||
|
||||
if(isset($this->YearPublished) && ($this->YearPublished <= 0 || $this->YearPublished > $thisYear)){
|
||||
$error->Add(new Exceptions\InvalidEbookPlaceholderYearPublishedException());
|
||||
}
|
||||
|
||||
$this->TranscriptionUrl = trim($this->TranscriptionUrl ?? '');
|
||||
if($this->TranscriptionUrl == ''){
|
||||
$this->TranscriptionUrl = null;
|
||||
}
|
||||
|
||||
$this->Notes = trim($this->Notes ?? '');
|
||||
if($this->Notes == ''){
|
||||
$this->Notes = null;
|
||||
}
|
||||
|
||||
if($error->HasExceptions){
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exceptions\ValidationException
|
||||
*/
|
||||
public function Create(): void{
|
||||
$this->Validate();
|
||||
Db::Query('
|
||||
INSERT into EbookPlaceholders (EbookId, YearPublished, Status, Difficulty, TranscriptionUrl,
|
||||
IsWanted, IsPatron, Notes)
|
||||
values (?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?)
|
||||
', [$this->EbookId, $this->YearPublished, $this->Status, $this->Difficulty, $this->TranscriptionUrl,
|
||||
$this->IsWanted, $this->IsPatron, $this->Notes]);
|
||||
}
|
||||
}
|
8
lib/Enums/EbookPlaceholderDifficulty.php
Normal file
8
lib/Enums/EbookPlaceholderDifficulty.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?
|
||||
namespace Enums;
|
||||
|
||||
enum EbookPlaceholderDifficulty: string{
|
||||
case Beginner = 'beginner';
|
||||
case Intermediate = 'intermediate';
|
||||
case Advanced = 'advanced';
|
||||
}
|
7
lib/Enums/EbookPlaceholderStatus.php
Normal file
7
lib/Enums/EbookPlaceholderStatus.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?
|
||||
namespace Enums;
|
||||
|
||||
enum EbookPlaceholderStatus: string{
|
||||
case Wanted = 'wanted';
|
||||
case InProgress = 'in_progress';
|
||||
}
|
8
lib/Enums/EbookReleaseStatusFilter.php
Normal file
8
lib/Enums/EbookReleaseStatusFilter.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?
|
||||
namespace Enums;
|
||||
|
||||
enum EbookReleaseStatusFilter{
|
||||
case All;
|
||||
case Placeholder;
|
||||
case Released;
|
||||
}
|
10
lib/Exceptions/DuplicateEbookException.php
Normal file
10
lib/Exceptions/DuplicateEbookException.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class DuplicateEbookException extends AppException{
|
||||
public function __construct(string $identifier){
|
||||
$this->message = 'Ebook already exists with identifier: ' . $identifier;
|
||||
|
||||
parent::__construct($this->message);
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class EbookCreatedDatetimeRequiredException extends AppException{
|
||||
/** @var string $message */
|
||||
protected $message = 'EbookCreated datetime required.';
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class EbookDescriptionRequiredException extends AppException{
|
||||
/** @var string $message */
|
||||
protected $message = 'Ebook Description required.';
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class EbookLanguageRequiredException extends AppException{
|
||||
/** @var string $message */
|
||||
protected $message = 'Ebook language required.';
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class EbookLongDescriptionRequiredException extends AppException{
|
||||
/** @var string $message */
|
||||
protected $message = 'Ebook LongDescription required.';
|
||||
}
|
7
lib/Exceptions/EbookMissingPlaceholderException.php
Normal file
7
lib/Exceptions/EbookMissingPlaceholderException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class EbookMissingPlaceholderException extends AppException{
|
||||
/** @var string $message */
|
||||
protected $message = 'Ebook is a placeholder, but has no placeholder object.';
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class EbookReadingEaseRequiredException extends AppException{
|
||||
/** @var string $message */
|
||||
protected $message = 'Ebook ReadingEase required.';
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class EbookRepoFilesystemPathRequiredException extends AppException{
|
||||
/** @var string $message */
|
||||
protected $message = 'Ebook RepoFilesystemPath required.';
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class EbookTextSinglePageByteCountRequiredException extends AppException{
|
||||
/** @var string $message */
|
||||
protected $message = 'Ebook TextSinglePageByteCount required.';
|
||||
}
|
7
lib/Exceptions/EbookUnexpectedPlaceholderException.php
Normal file
7
lib/Exceptions/EbookUnexpectedPlaceholderException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class EbookUnexpectedPlaceholderException extends AppException{
|
||||
/** @var string $message */
|
||||
protected $message = 'Ebook is not a placeholder, but has a placeholder object.';
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class EbookUpdatedDatetimeRequiredException extends AppException{
|
||||
/** @var string $message */
|
||||
protected $message = 'EbookUpdated datetime required.';
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class EbookWordCountRequiredException extends AppException{
|
||||
/** @var string $message */
|
||||
protected $message = 'Ebook WordCount required.';
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class EbookWwwFilesystemPathRequiredException extends AppException{
|
||||
/** @var string $message */
|
||||
protected $message = 'Ebook WwwFilesystemPath required.';
|
||||
}
|
5
lib/Exceptions/InvalidEbookIdentifierException.php
Normal file
5
lib/Exceptions/InvalidEbookIdentifierException.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class InvalidEbookIdentifierException extends AppException{
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?
|
||||
namespace Exceptions;
|
||||
|
||||
class InvalidEbookPlaceholderYearPublishedException extends AppException{
|
||||
/** @var string $message */
|
||||
protected $message = 'Invalid ebook placeholder year published.';
|
||||
}
|
|
@ -106,6 +106,10 @@ function CreateZip(string $filePath, array $ebooks, string $type, string $webRoo
|
|||
|
||||
// Iterate over all ebooks and arrange them by publication month.
|
||||
foreach(Ebook::GetAll() as $ebook){
|
||||
if($ebook->IsPlaceholder()){
|
||||
continue;
|
||||
}
|
||||
|
||||
$timestamp = $ebook->EbookCreated->format('Y-m');
|
||||
$updatedTimestamp = $ebook->EbookUpdated->getTimestamp();
|
||||
|
||||
|
|
|
@ -100,6 +100,10 @@ foreach($dirs as $dir){
|
|||
|
||||
// Iterate over all ebooks to build the various feeds.
|
||||
foreach(Ebook::GetAll() as $ebook){
|
||||
if($ebook->IsPlaceholder()){
|
||||
continue;
|
||||
}
|
||||
|
||||
$allEbooks[] = $ebook;
|
||||
$newestEbooks[] = $ebook;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ $isEditForm = $isEditForm ?? false;
|
|||
?>
|
||||
<fieldset>
|
||||
<legend>Artist details</legend>
|
||||
<label class="user">
|
||||
<label class="icon user">
|
||||
<span>Name</span>
|
||||
<span>For existing artists, leave the year of death blank.</span>
|
||||
<datalist id="artist-names">
|
||||
|
@ -31,7 +31,7 @@ $isEditForm = $isEditForm ?? false;
|
|||
value="<?= Formatter::EscapeHtml($artwork->Artist->Name) ?>"
|
||||
/>
|
||||
</label>
|
||||
<label class="year">
|
||||
<label class="icon year">
|
||||
<span>Year of death</span>
|
||||
<span>If circa or unknown, enter the latest possible year.</span>
|
||||
<? /* Not using `<input type="number">` for now, see <https://technology.blog.gov.uk/2020/02/24/why-the-gov-uk-design-system-team-changed-the-input-type-for-numbers/>. */ ?>
|
||||
|
@ -46,13 +46,13 @@ $isEditForm = $isEditForm ?? false;
|
|||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Artwork details</legend>
|
||||
<label class="picture">
|
||||
<label class="icon picture">
|
||||
Name
|
||||
<input type="text" name="artwork-name" required="required"
|
||||
value="<?= Formatter::EscapeHtml($artwork->Name) ?>"/>
|
||||
</label>
|
||||
<fieldset>
|
||||
<label class="year">
|
||||
<label class="icon year">
|
||||
Year of completion
|
||||
<input
|
||||
type="text"
|
||||
|
@ -70,7 +70,7 @@ $isEditForm = $isEditForm ?? false;
|
|||
/> Year is circa
|
||||
</label>
|
||||
</fieldset>
|
||||
<label class="tags">
|
||||
<label class="icon tags">
|
||||
<span>Tags</span>
|
||||
<span>A list of comma-separated tags.</span>
|
||||
<input
|
||||
|
@ -116,7 +116,7 @@ $isEditForm = $isEditForm ?? false;
|
|||
<span>This book was published in the U.S.</span>
|
||||
<span>Yes, if a U.S. city appears anywhere near the publication year or rights statement.</span>
|
||||
</label>
|
||||
<label class="year">
|
||||
<label class="icon year">
|
||||
Year of publication
|
||||
<input
|
||||
type="text"
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<? if($artwork->Ebook !== null && $artwork->Ebook->Url !== null){ ?>
|
||||
<i>
|
||||
<a href="<?= $artwork->Ebook->Url ?>"><?= Formatter::EscapeHtml($artwork->Ebook->Title) ?></a>
|
||||
</i>
|
||||
</i><? if($artwork->Ebook->IsPlaceholder()){ ?>(unreleased)<? } ?>
|
||||
<? }else{ ?>
|
||||
<code><?= Formatter::EscapeHtml($artwork->EbookUrl) ?></code> (unreleased)
|
||||
<? } ?>
|
||||
|
|
|
@ -12,17 +12,21 @@ $collection = $collection ?? null;
|
|||
<meta property="schema:name" content="<?= Formatter::EscapeHtml($collection->Name) ?>"/>
|
||||
<? } ?>
|
||||
<? foreach($ebooks as $ebook){ ?>
|
||||
<li typeof="schema:Book"<? if($collection !== null){ ?> resource="<?= $ebook->Url ?>" property="schema:hasPart"<? if($ebook->GetCollectionPosition($collection) !== null){ ?> value="<?= $ebook->GetCollectionPosition($collection) ?>"<? } ?><? }else{ ?> about="<?= $ebook->Url ?>"<? } ?>>
|
||||
<li typeof="schema:Book"<? if($collection !== null){ ?> resource="<?= $ebook->Url ?>" property="schema:hasPart"<? if($ebook->GetCollectionPosition($collection) !== null){ ?> value="<?= $ebook->GetCollectionPosition($collection) ?>"<? } ?><? }else{ ?> about="<?= $ebook->Url ?>"<? } ?><? if($ebook->EbookPlaceholder?->IsWanted){ ?> class="wanted"<? } ?>>
|
||||
<? if($collection !== null && $ebook->GetCollectionPosition($collection) !== null){ ?>
|
||||
<meta property="schema:position" content="<?= $ebook->GetCollectionPosition($collection) ?>"/>
|
||||
<? } ?>
|
||||
<div class="thumbnail-container" aria-hidden="true"><? /* We need a container in case the thumb is shorter than the description, so that the focus outline doesn't take up the whole grid space */ ?>
|
||||
<a href="<?= $ebook->Url ?>" tabindex="-1" property="schema:url"<? if($collection !== null && $ebook->GetCollectionPosition($collection) !== null){ ?> data-ordinal="<?= $ebook->GetCollectionPosition($collection) ?>"<? } ?>>
|
||||
<picture>
|
||||
<? if($ebook->CoverImage2xAvifUrl !== null){ ?><source srcset="<?= $ebook->CoverImage2xAvifUrl ?> 2x, <?= $ebook->CoverImageAvifUrl ?> 1x" type="image/avif"/><? } ?>
|
||||
<source srcset="<?= $ebook->CoverImage2xUrl ?> 2x, <?= $ebook->CoverImageUrl ?> 1x" type="image/jpg"/>
|
||||
<img src="<?= $ebook->CoverImage2xUrl ?>" alt="" property="schema:image" height="335" width="224"/>
|
||||
</picture>
|
||||
<? if($ebook->IsPlaceholder()){ ?>
|
||||
<div class="placeholder-cover"></div><? /* Don't self-close as this changes how Chrome renders */ ?>
|
||||
<? }else{ ?>
|
||||
<picture>
|
||||
<? if($ebook->CoverImage2xAvifUrl !== null){ ?><source srcset="<?= $ebook->CoverImage2xAvifUrl ?> 2x, <?= $ebook->CoverImageAvifUrl ?> 1x" type="image/avif"/><? } ?>
|
||||
<source srcset="<?= $ebook->CoverImage2xUrl ?> 2x, <?= $ebook->CoverImageUrl ?> 1x" type="image/jpg"/>
|
||||
<img src="<?= $ebook->CoverImage2xUrl ?>" alt="" property="schema:image" height="335" width="224"/>
|
||||
</picture>
|
||||
<? } ?>
|
||||
</a>
|
||||
</div>
|
||||
<p><a href="<?= $ebook->Url ?>" property="schema:url"><span property="schema:name"><?= Formatter::EscapeHtml($ebook->Title) ?></span></a></p>
|
||||
|
@ -42,8 +46,16 @@ $collection = $collection ?? null;
|
|||
<p><?= rtrim($ebook->ContributorsHtml, '.') ?></p>
|
||||
</div>
|
||||
<? } ?>
|
||||
<p><?= number_format($ebook->WordCount) ?> words • <?= $ebook->ReadingEase ?> reading ease</p>
|
||||
<ul class="tags"><? foreach($ebook->Tags as $tag){ ?><li><a href="<?= $tag->Url ?>"><?= Formatter::EscapeHtml($tag->Name) ?></a></li><? } ?></ul>
|
||||
<? if(!$ebook->IsPlaceholder()){ ?>
|
||||
<p><?= number_format($ebook->WordCount) ?> words • <?= $ebook->ReadingEase ?> reading ease</p>
|
||||
<ul class="tags">
|
||||
<? foreach($ebook->Tags as $tag){ ?>
|
||||
<li>
|
||||
<a href="<?= $tag->Url ?>"><?= Formatter::EscapeHtml($tag->Name) ?></a>
|
||||
</li>
|
||||
<? } ?>
|
||||
</ul>
|
||||
<? } ?>
|
||||
</div>
|
||||
<? } ?>
|
||||
</li>
|
||||
|
|
223
templates/EbookPlaceholderForm.php
Normal file
223
templates/EbookPlaceholderForm.php
Normal file
|
@ -0,0 +1,223 @@
|
|||
<?
|
||||
/**
|
||||
* @var ?Ebook $ebook
|
||||
*/
|
||||
$ebook = $ebook ?? new Ebook();
|
||||
|
||||
?>
|
||||
<fieldset>
|
||||
<legend>Contributors</legend>
|
||||
<label class="icon user">
|
||||
<span>Author</span>
|
||||
<datalist id="author-names">
|
||||
<? foreach(Contributor::GetAllAuthorNames() as $author){ ?>
|
||||
<option value="<?= Formatter::EscapeHtml($author->Name) ?>"><?= Formatter::EscapeHtml($author->Name) ?></option>
|
||||
<? } ?>
|
||||
</datalist>
|
||||
<input
|
||||
type="text"
|
||||
name="author-name-1"
|
||||
list="author-names"
|
||||
required="required"
|
||||
value="<? if(isset($ebook->Authors) && sizeof($ebook->Authors) > 0){ ?><?= Formatter::EscapeHtml($ebook->Authors[0]->Name) ?><? } ?>"
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
<details>
|
||||
<summary>Additional contributors</summary>
|
||||
<fieldset>
|
||||
<label class="icon user">
|
||||
<span>Second author</span>
|
||||
<input
|
||||
type="text"
|
||||
name="author-name-2"
|
||||
list="author-names"
|
||||
value="<? if(isset($ebook->Authors) && sizeof($ebook->Authors) > 1){ ?><?= Formatter::EscapeHtml($ebook->Authors[1]->Name) ?><? } ?>"
|
||||
/>
|
||||
</label>
|
||||
<label class="icon user">
|
||||
<span>Third author</span>
|
||||
<input
|
||||
type="text"
|
||||
name="author-name-3"
|
||||
list="author-names"
|
||||
value="<? if(isset($ebook->Authors) && sizeof($ebook->Authors) > 2){ ?><?= Formatter::EscapeHtml($ebook->Authors[2]->Name) ?><? } ?>"
|
||||
/>
|
||||
</label>
|
||||
<label class="icon user">
|
||||
<span>Translator</span>
|
||||
<datalist id="translator-names">
|
||||
<? foreach(Contributor::GetAllTranslatorNames() as $translator){ ?>
|
||||
<option value="<?= Formatter::EscapeHtml($translator->Name) ?>"><?= Formatter::EscapeHtml($translator->Name) ?></option>
|
||||
<? } ?>
|
||||
</datalist>
|
||||
<input
|
||||
type="text"
|
||||
name="translator-name-1"
|
||||
list="translator-names"
|
||||
value="<? if(isset($ebook->Translators) && sizeof($ebook->Translators) > 0){ ?><?= Formatter::EscapeHtml($ebook->Translators[0]->Name) ?><? } ?>"
|
||||
/>
|
||||
</label>
|
||||
<label class="icon user">
|
||||
<span>Second translator</span>
|
||||
<input
|
||||
type="text"
|
||||
name="translator-name-2"
|
||||
list="translator-names"
|
||||
value="<? if(isset($ebook->Translators) && sizeof($ebook->Translators) > 1){ ?><?= Formatter::EscapeHtml($ebook->Translators[1]->Name) ?><? } ?>"
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
</details>
|
||||
<fieldset>
|
||||
<legend>Ebook metadata</legend>
|
||||
<label class="icon book">
|
||||
<span>Title</span>
|
||||
<input type="text" name="ebook-title" required="required"
|
||||
value="<? if(isset($ebook->Title)){ ?><?= Formatter::EscapeHtml($ebook->Title) ?><? } ?>"/>
|
||||
</label>
|
||||
<fieldset>
|
||||
<label class="icon year">
|
||||
Year published
|
||||
<input
|
||||
type="text"
|
||||
name="ebook-placeholder-year-published"
|
||||
inputmode="numeric"
|
||||
pattern="[0-9]{1,4}"
|
||||
value="<? if(isset($ebook->EbookPlaceholder)){ ?><?= Formatter::EscapeHtml((string)$ebook->EbookPlaceholder->YearPublished) ?><? } ?>"
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
<label class="icon collection">
|
||||
<span>Collection</span>
|
||||
<datalist id="collection-names">
|
||||
<? foreach(Collection::GetAll() as $collection){ ?>
|
||||
<option value="<?= Formatter::EscapeHtml($collection->Name) ?>"><?= Formatter::EscapeHtml($collection->Name) ?></option>
|
||||
<? } ?>
|
||||
</datalist>
|
||||
<input
|
||||
type="text"
|
||||
name="collection-name-1"
|
||||
list="collection-names"
|
||||
value="<? if(isset($ebook->CollectionMemberships)){ ?><?= Formatter::EscapeHtml($ebook->CollectionMemberships[0]->Collection->Name) ?><? } ?>"
|
||||
/>
|
||||
</label>
|
||||
<fieldset>
|
||||
<label class="icon ordered-list">
|
||||
<span>Number in collection</span>
|
||||
<input
|
||||
type="text"
|
||||
name="sequence-number-collection-name-1"
|
||||
inputmode="numeric"
|
||||
pattern="[0-9]{1,3}"
|
||||
value="<? if(isset($ebook->CollectionMemberships) && sizeof($ebook->CollectionMemberships) > 0){ ?><?= Formatter::EscapeHtml((string)$ebook->CollectionMemberships[0]->SequenceNumber) ?><? } ?>"
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
<details>
|
||||
<summary>Additional collections</summary>
|
||||
<fieldset>
|
||||
<label class="icon collection">
|
||||
<span>Second Collection</span>
|
||||
<input
|
||||
type="text"
|
||||
name="collection-name-2"
|
||||
list="collection-names"
|
||||
value="<? if(isset($ebook->CollectionMemberships) && sizeof($ebook->CollectionMemberships) > 1){ ?><?= Formatter::EscapeHtml($ebook->CollectionMemberships[1]->Collection->Name) ?><? } ?>"
|
||||
/>
|
||||
</label>
|
||||
<fieldset>
|
||||
<label class="icon ordered-list">
|
||||
<span>Number in collection</span>
|
||||
<input
|
||||
type="text"
|
||||
name="sequence-number-collection-name-2"
|
||||
inputmode="numeric"
|
||||
pattern="[0-9]{1,3}"
|
||||
value="<? if(isset($ebook->CollectionMemberships) && sizeof($ebook->CollectionMemberships) > 1){ ?><?= Formatter::EscapeHtml((string)$ebook->CollectionMemberships[1]->SequenceNumber) ?><? } ?>"
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label class="icon collection">
|
||||
<span>Third Collection</span>
|
||||
<input
|
||||
type="text"
|
||||
name="collection-name-3"
|
||||
list="collection-names"
|
||||
value="<? if(isset($ebook->CollectionMemberships) && sizeof($ebook->CollectionMemberships) > 2){ ?><?= Formatter::EscapeHtml($ebook->CollectionMemberships[2]->Collection->Name) ?><? } ?>"
|
||||
/>
|
||||
</label>
|
||||
<fieldset>
|
||||
<label class="icon ordered-list">
|
||||
<span>Number in collection</span>
|
||||
<input
|
||||
type="text"
|
||||
name="sequence-number-collection-name-3"
|
||||
inputmode="numeric"
|
||||
pattern="[0-9]{1,3}"
|
||||
value="<? if(isset($ebook->CollectionMemberships) && sizeof($ebook->CollectionMemberships) > 2){ ?><?= Formatter::EscapeHtml((string)$ebook->CollectionMemberships[2]->SequenceNumber) ?><? } ?>"
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
</details>
|
||||
<fieldset>
|
||||
<legend>Wanted list</legend>
|
||||
<label class="controls-following-fieldset">
|
||||
<span>On the wanted list?</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="ebook-placeholder-is-wanted"
|
||||
<? if(isset($ebook->EbookPlaceholder) && $ebook->EbookPlaceholder->IsWanted){ ?>checked="checked"<? } ?>
|
||||
/>
|
||||
</label>
|
||||
<fieldset>
|
||||
<label>
|
||||
<span>Did a Patron request this book?</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="ebook-placeholder-is-patron"
|
||||
<? if(isset($ebook->EbookPlaceholder) && $ebook->EbookPlaceholder->IsPatron){ ?>checked="checked"<? } ?>
|
||||
/>
|
||||
</label>
|
||||
<label class="icon meter">
|
||||
<span>Difficulty</span>
|
||||
<span>
|
||||
<select name="ebook-placeholder-difficulty">
|
||||
<option value=""></option>
|
||||
<option value="<?= Enums\EbookPlaceholderDifficulty::Beginner->value ?>"<? if(isset($ebook->EbookPlaceholder) && $ebook->EbookPlaceholder->Difficulty == Enums\EbookPlaceholderDifficulty::Beginner){ ?> selected="selected"<? } ?>>Beginner</option>
|
||||
<option value="<?= Enums\EbookPlaceholderDifficulty::Intermediate->value ?>"<? if(isset($ebook->EbookPlaceholder) && $ebook->EbookPlaceholder->Difficulty == Enums\EbookPlaceholderDifficulty::Intermediate){ ?> selected="selected"<? } ?>>Intermediate</option>
|
||||
<option value="<?= Enums\EbookPlaceholderDifficulty::Advanced->value ?>"<? if(isset($ebook->EbookPlaceholder) && $ebook->EbookPlaceholder->Difficulty == Enums\EbookPlaceholderDifficulty::Advanced){ ?> selected="selected"<? } ?>>Advanced</option>
|
||||
</select>
|
||||
</span>
|
||||
</label>
|
||||
<label class="icon hourglass">
|
||||
<span>Wanted list status</span>
|
||||
<span>
|
||||
<select name="ebook-placeholder-status">
|
||||
<option value="<?= Enums\EbookPlaceholderStatus::Wanted->value ?>"<? if(isset($ebook->EbookPlaceholder) && $ebook->EbookPlaceholder->Status == Enums\EbookPlaceholderStatus::Wanted){ ?> selected="selected"<? } ?>>Wanted</option>
|
||||
<option value="<?= Enums\EbookPlaceholderStatus::InProgress->value ?>"<? if(isset($ebook->EbookPlaceholder) && $ebook->EbookPlaceholder->Status == Enums\EbookPlaceholderStatus::InProgress){ ?> selected="selected"<? } ?>>In progress</option>
|
||||
</select>
|
||||
</span>
|
||||
</label>
|
||||
<label>
|
||||
<span>Transcription URL</span>
|
||||
<input
|
||||
type="url"
|
||||
name="ebook-placeholder-transcription-url"
|
||||
value="<? if(isset($ebook->EbookPlaceholder)){ ?><?= Formatter::EscapeHtml($ebook->EbookPlaceholder->TranscriptionUrl) ?><? } ?>"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>Notes</span>
|
||||
<span>Markdown accepted.</span>
|
||||
<textarea maxlength="1024" name="ebook-placeholder-notes"><? if(isset($ebook->EbookPlaceholder)){ ?><?= Formatter::EscapeHtml($ebook->EbookPlaceholder->Notes) ?><? } ?></textarea>
|
||||
</label>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
<div class="footer">
|
||||
<button>Submit</button>
|
||||
</div>
|
|
@ -9,12 +9,12 @@
|
|||
$isAllSelected = sizeof($tags) == 0 || in_array('all', $tags);
|
||||
?>
|
||||
<form action="/ebooks" method="get" rel="search">
|
||||
<label class="tags">Subjects
|
||||
<label class="icon tags">Subjects
|
||||
<select <? if(!Template::IsEreaderBrowser()){ ?> multiple="multiple"<? } ?> name="tags[]" size="1">
|
||||
<option value="all">All</option>
|
||||
<? foreach(EbookTag::GetAll() as $tag){ ?>
|
||||
<option value="<?= $tag->UrlName ?>"<? if(!$isAllSelected && in_array($tag->UrlName, $tags)){ ?> selected="selected"<? } ?>><?= Formatter::EscapeHtml($tag->Name) ?></option>
|
||||
<? } ?>
|
||||
<? foreach(EbookTag::GetAll() as $tag){ ?>
|
||||
<option value="<?= $tag->UrlName ?>"<? if(!$isAllSelected && in_array($tag->UrlName, $tags)){ ?> selected="selected"<? } ?>><?= Formatter::EscapeHtml($tag->Name) ?></option>
|
||||
<? } ?>
|
||||
</select>
|
||||
</label>
|
||||
<label>Keywords
|
||||
|
|
|
@ -15,7 +15,7 @@ $passwordAction = $passwordAction ?? Enums\PasswordActionType::None;
|
|||
/>
|
||||
</label>
|
||||
|
||||
<label class="user">
|
||||
<label class="icon user">
|
||||
Name
|
||||
<input
|
||||
type="text"
|
||||
|
@ -135,6 +135,13 @@ $passwordAction = $passwordAction ?? Enums\PasswordActionType::None;
|
|||
Can edit users
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="hidden" name="benefits-can-create-ebook-placeholder" value="false" />
|
||||
<input type="checkbox" name="benefits-can-create-ebook-placeholder" value="true"<? if($user->Benefits->CanCreateEbookPlaceholders){ ?> checked="checked"<? } ?> />
|
||||
Can create ebook placeholders
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
|
||||
|
|
|
@ -102,7 +102,15 @@ catch(Exceptions\InvalidPermissionsException){
|
|||
</tr>
|
||||
<tr>
|
||||
<td>Tags</td>
|
||||
<td><ul class="tags"><? foreach($artwork->Tags as $tag){ ?><li><a href="<?= $tag->Url ?>"><?= Formatter::EscapeHtml($tag->Name) ?></a></li><? } ?></ul></td>
|
||||
<td>
|
||||
<ul class="tags">
|
||||
<? foreach($artwork->Tags as $tag){ ?>
|
||||
<li>
|
||||
<a href="<?= $tag->Url ?>"><?= Formatter::EscapeHtml($tag->Name) ?></a>
|
||||
</li>
|
||||
<? } ?>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dimensions</td>
|
||||
|
|
|
@ -16,15 +16,14 @@ try{
|
|||
throw new Exceptions\InvalidPermissionsException();
|
||||
}
|
||||
|
||||
// We got here because an artwork was successfully submitted.
|
||||
if($isCreated){
|
||||
// We got here because an `Artwork` was successfully submitted.
|
||||
http_response_code(Enums\HttpCode::Created->value);
|
||||
$artwork = null;
|
||||
session_unset();
|
||||
}
|
||||
|
||||
// We got here because an artwork submission had errors and the user has to try again.
|
||||
if($exception){
|
||||
elseif($exception){
|
||||
// We got here because an `Artwork` submission had errors and the user has to try again.
|
||||
http_response_code(Enums\HttpCode::UnprocessableContent->value);
|
||||
session_unset();
|
||||
}
|
||||
|
|
|
@ -166,6 +166,10 @@ form[action^="/artworks/"]{
|
|||
grid-column: 1 / span 4;
|
||||
}
|
||||
|
||||
.year + label:has(input[type="checkbox"]){
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
label.picture::before{
|
||||
bottom: 2.6rem; /* Fix alignment caused by "circa" checkbox */
|
||||
}
|
||||
|
@ -226,17 +230,6 @@ form.create-update-artwork fieldset p:first-of-type{
|
|||
margin-top: 0;
|
||||
}
|
||||
|
||||
form.create-update-artwork legend{
|
||||
font-size: 1.4rem;
|
||||
font-family: "League Spartan", Arial, sans-serif;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
form.create-update-artwork label{
|
||||
display: block;
|
||||
}
|
||||
|
|
183
www/css/core.css
183
www/css/core.css
|
@ -840,7 +840,8 @@ label:has(input[type="checkbox"]):focus-within,
|
|||
select:focus,
|
||||
button:focus,
|
||||
nav a[rel]:focus,
|
||||
textarea:focus{
|
||||
textarea:focus,
|
||||
summary:focus{
|
||||
outline: 1px dashed var(--input-outline);
|
||||
}
|
||||
|
||||
|
@ -1572,6 +1573,19 @@ ol.ebooks-list > li p.author a{
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.placeholder-cover{
|
||||
background-color: transparent;
|
||||
border-radius: .25rem;
|
||||
border: 2px dashed var(--sub-text);
|
||||
height: 331px;
|
||||
width: 221px;
|
||||
}
|
||||
|
||||
ol.ebooks-list.list .placeholder-cover{
|
||||
height: 191px;
|
||||
width: 126px;
|
||||
}
|
||||
|
||||
article nav ol,
|
||||
main nav.pagination ol{
|
||||
list-style: none;
|
||||
|
@ -1790,12 +1804,9 @@ input[type="file"]{
|
|||
label:has(input[type="email"]) input,
|
||||
label:has(input[type="search"]) input,
|
||||
label:has(input[type="url"]) input,
|
||||
label.captcha input,
|
||||
label.year input,
|
||||
label.tags input,
|
||||
label.picture input,
|
||||
label.user input,
|
||||
label:has(input[type="password"]) input{
|
||||
label.icon input,
|
||||
label:has(input[type="password"]) input,
|
||||
label.icon select{
|
||||
padding-left: 2.5rem;
|
||||
}
|
||||
|
||||
|
@ -1850,8 +1861,8 @@ label:has(input[type="radio"]):has(> span) input,
|
|||
label:has(input[type="checkbox"]):has(> span) input{
|
||||
grid-row: 1 / span 2;
|
||||
justify-self: center;
|
||||
align-self: start;
|
||||
margin-top: 10px;
|
||||
align-self: center;
|
||||
margin: 0;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
|
@ -1895,11 +1906,7 @@ label:has(select) > span + span,
|
|||
label:has(input[type="email"]),
|
||||
label:has(input[type="search"]),
|
||||
label:has(input[type="url"]),
|
||||
label.captcha,
|
||||
label.year,
|
||||
label.tags,
|
||||
label.picture,
|
||||
label.user,
|
||||
label.icon,
|
||||
label:has(input[type="password"]){
|
||||
display: block;
|
||||
position: relative;
|
||||
|
@ -1908,11 +1915,7 @@ label:has(input[type="password"]){
|
|||
|
||||
label:has(input[type="email"])::before,
|
||||
label:has(input[type="search"])::before,
|
||||
label.captcha::before,
|
||||
label.year::before,
|
||||
label.tags::before,
|
||||
label.picture::before,
|
||||
label.user::before,
|
||||
label.icon::before,
|
||||
label:has(input[type="url"])::before,
|
||||
label:has(input[type="password"])::before{
|
||||
display: block;
|
||||
|
@ -1954,6 +1957,26 @@ label.year::before{
|
|||
content: "\f073";
|
||||
}
|
||||
|
||||
label.meter::before{
|
||||
content: "\f0e4";
|
||||
}
|
||||
|
||||
label.collection::before{
|
||||
content: "\f0e8";
|
||||
}
|
||||
|
||||
label.book::before{
|
||||
content: "\f02d";
|
||||
}
|
||||
|
||||
label.ordered-list::before{
|
||||
content: "\f0cb";
|
||||
}
|
||||
|
||||
label.hourglass::before{
|
||||
content: "\f254";
|
||||
}
|
||||
|
||||
label.tags:not(:has(select))::before{
|
||||
content: "\f02c";
|
||||
}
|
||||
|
@ -2489,15 +2512,37 @@ form[action="/newsletter/subscriptions"] fieldset{
|
|||
|
||||
fieldset{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
fieldset p,
|
||||
fieldset legend{
|
||||
label.controls-following-fieldset + fieldset,
|
||||
details summary ~ *{
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
fieldset p{
|
||||
border-bottom: 1px dashed var(--input-border);
|
||||
}
|
||||
|
||||
fieldset legend,
|
||||
fieldset legend{
|
||||
font-size: 1.4rem;
|
||||
font-family: "League Spartan", Arial, sans-serif;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
summary{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
summary + fieldset{
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
fieldset p:has(+ ul){
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@ -2604,8 +2649,8 @@ aside header{
|
|||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.meter,
|
||||
.progress{
|
||||
div.meter,
|
||||
div.progress{
|
||||
position: relative;
|
||||
font-size: 0;
|
||||
}
|
||||
|
@ -2677,13 +2722,13 @@ aside header{
|
|||
border-top: 1px solid #000;
|
||||
}
|
||||
|
||||
.progress + p{
|
||||
div.progress + p{
|
||||
margin-top: 2rem;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
.meter p,
|
||||
.progress p{
|
||||
div.meter p,
|
||||
div.progress p{
|
||||
font-size: 1rem;
|
||||
font-family: "League Spartan", Arial, sans-serif;
|
||||
left: 0;
|
||||
|
@ -2701,7 +2746,7 @@ aside header{
|
|||
margin-top: 0;
|
||||
}
|
||||
|
||||
.progress p.start{
|
||||
div.progress p.start{
|
||||
margin-right: auto;
|
||||
margin-left: 1rem;
|
||||
border: none;
|
||||
|
@ -2710,7 +2755,7 @@ aside header{
|
|||
font-size: .75rem;
|
||||
}
|
||||
|
||||
.progress p.target{
|
||||
div.progress p.target{
|
||||
margin-left: auto;
|
||||
margin-right: 1rem;
|
||||
border: none;
|
||||
|
@ -2719,8 +2764,8 @@ aside header{
|
|||
font-size: .75rem;
|
||||
}
|
||||
|
||||
.meter > div,
|
||||
.progress > div{
|
||||
div.meter > div,
|
||||
div.progress > div{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -3121,6 +3166,74 @@ form[action="/settings"] label{
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
ol.ebooks-list > li a.wanted,
|
||||
ol.ebooks-list > li.wanted .placeholder-cover{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ol.ebooks-list > li.wanted .placeholder-cover::before,
|
||||
ol.ebooks-list > li.wanted a::after{
|
||||
/* Ribbon */
|
||||
font-family: "League Spartan", sans-serif;
|
||||
position: absolute;
|
||||
left: -.5rem;
|
||||
top: calc(.25rem + 3px);
|
||||
padding: .5rem 1rem .5rem .5rem;
|
||||
line-height: 1;
|
||||
color: #ffffff;
|
||||
text-shadow: 1px 1px 0 rgba(0, 0, 0, .75);
|
||||
text-transform: uppercase;
|
||||
font-size: .5rem;
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
clip-path: polygon(0 0, 100% 0, 85% 100%, 0 100%);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
ol.ebooks-list > li[property="schema:hasPart"][value].wanted .placeholder-cover::before,
|
||||
ol.ebooks-list > li[property="schema:hasPart"][value].wanted a::after{
|
||||
top: calc(.25rem + 3px + 2rem);
|
||||
}
|
||||
|
||||
ol.ebooks-list > li.wanted .placeholder-cover::after{
|
||||
/* Ribbon bottom wrap-around */
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: calc(.5rem + .5rem + .5rem + .5rem - 2px);
|
||||
height: .5rem;
|
||||
width: .5rem;
|
||||
left: -.5rem;
|
||||
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 0);
|
||||
z-index: 1;
|
||||
filter: brightness(50%);
|
||||
}
|
||||
|
||||
ol.ebooks-list > li[property="schema:hasPart"][value].wanted .placeholder-cover::after{
|
||||
top: calc(.25rem + 3px + 2rem + .5rem + .5rem + .5rem);
|
||||
}
|
||||
|
||||
ol.ebooks-list > li.wanted a::after{
|
||||
/* Ribbon shadow */
|
||||
left: calc(-.5rem + 4px);
|
||||
top: calc(.5rem + 2px);
|
||||
background: rgba(0, 0, 0, .4);
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
ol.ebooks-list > li[property="schema:hasPart"][value].wanted a::after{
|
||||
top: calc(.25rem + 3px + 2rem + 4px);
|
||||
}
|
||||
|
||||
ol.ebooks-list > li.wanted .placeholder-cover::before,
|
||||
ol.ebooks-list > li.wanted .placeholder-cover::after{
|
||||
background: #861d1d;
|
||||
}
|
||||
|
||||
ol.ebooks-list > li.wanted .placeholder-cover::before,
|
||||
ol.ebooks-list > li.wanted a::after{
|
||||
content: "wanted";
|
||||
}
|
||||
|
||||
@media (hover: none) and (pointer: coarse){ /* target ipads and smartphones without a mouse */
|
||||
/* For iPad, unset the height so it matches the other elements */
|
||||
select[multiple]{
|
||||
|
@ -3717,13 +3830,13 @@ form[action="/settings"] label{
|
|||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.progress p.start,
|
||||
.progress p.target,
|
||||
.progress p.stretch-base{
|
||||
div.progress p.start,
|
||||
div.progress p.target,
|
||||
div.progress p.stretch-base{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress p{
|
||||
div.progress p{
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
|
92
www/css/ebook-placeholder.css
Normal file
92
www/css/ebook-placeholder.css
Normal file
|
@ -0,0 +1,92 @@
|
|||
form.create-update-ebook-placeholder fieldset{
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
form.create-update-ebook-placeholder details + fieldset,
|
||||
form.create-update-ebook-placeholder fieldset + fieldset{
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
form.create-update-ebook-placeholder > fieldset:nth-of-type(1),
|
||||
form.create-update-ebook-placeholder details:nth-of-type(1) fieldset{
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
form.create-update-ebook-placeholder > fieldset:nth-of-type(2),
|
||||
form.create-update-ebook-placeholder details:nth-of-type(2) fieldset{
|
||||
grid-template-columns: 1fr 200px;
|
||||
}
|
||||
|
||||
form.create-update-ebook-placeholder fieldset label:has(input[name="ebook-placeholder-transcription-url"]),
|
||||
form.create-update-ebook-placeholder fieldset label:has(textarea[name="ebook-placeholder-notes"]){
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
form.create-update-ebook-placeholder fieldset fieldset:has(input[name="sequence-number-collection-name-1"]),
|
||||
form.create-update-ebook-placeholder fieldset fieldset:has(input[name="sequence-number-collection-name-2"]),
|
||||
form.create-update-ebook-placeholder fieldset fieldset:has(input[name="sequence-number-collection-name-3"]),
|
||||
form.create-update-ebook-placeholder fieldset fieldset:has(input[name="ebook-placeholder-year-published"]){
|
||||
display: grid;
|
||||
grid-template-columns: 200px 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
form.create-update-ebook-placeholder fieldset label:has(input[type="checkbox"]){
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
form.create-update-ebook-placeholder details{
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
form.create-update-ebook-placeholder summary{
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
form.create-update-ebook-placeholder fieldset p{
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
form.create-update-ebook-placeholder fieldset p:first-of-type{
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
form.create-update-ebook-placeholder legend{
|
||||
font-size: 1.4rem;
|
||||
font-family: "League Spartan", Arial, sans-serif;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
form.create-update-ebook-placeholder label{
|
||||
display: block;
|
||||
}
|
||||
|
||||
form div.footer{
|
||||
margin-top: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Hide the next fieldset unless the ebook-placeholder-is-wanted checkbox is checked. */
|
||||
form.create-update-ebook-placeholder fieldset:has(input[name="ebook-placeholder-is-wanted"]) fieldset{
|
||||
display: none;
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
form.create-update-ebook-placeholder fieldset:has(input[name="ebook-placeholder-is-wanted"]:checked) fieldset{
|
||||
display: grid;
|
||||
}
|
||||
|
||||
article.ebook.ebook-placeholder > header{
|
||||
border: 2px dashed var(--sub-text);
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
article.ebook.ebook-placeholder .placeholder-details{
|
||||
padding-top: 2rem;
|
||||
}
|
|
@ -349,7 +349,6 @@ code.full .utf{
|
|||
}
|
||||
|
||||
.step-by-step-guide summary{
|
||||
cursor: pointer;
|
||||
/* reproduce H2 styling: */
|
||||
font-size: 1.4rem;
|
||||
font-family: "League Spartan", Arial, sans-serif;
|
||||
|
|
88
www/ebook-placeholders/get.php
Normal file
88
www/ebook-placeholders/get.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?
|
||||
|
||||
use function Safe\preg_replace;
|
||||
|
||||
/** @var string $identifier Passed from script this is included from. */
|
||||
$ebook = null;
|
||||
|
||||
try{
|
||||
$ebook = Ebook::GetByIdentifier($identifier);
|
||||
}
|
||||
catch(Exceptions\EbookNotFoundException){
|
||||
Template::Emit404();
|
||||
}
|
||||
?><?= Template::Header(
|
||||
[
|
||||
'title' => strip_tags($ebook->TitleWithCreditsHtml),
|
||||
'css' => ['/css/ebook-placeholder.css'],
|
||||
'highlight' => 'ebooks',
|
||||
'canonicalUrl' => SITE_URL . $ebook->Url
|
||||
])
|
||||
?>
|
||||
<main>
|
||||
<article class="ebook ebook-placeholder" typeof="schema:Book" about="<?= $ebook->Url ?>">
|
||||
<header>
|
||||
<hgroup>
|
||||
<h1 property="schema:name"><?= Formatter::EscapeHtml($ebook->Title) ?></h1>
|
||||
<? foreach($ebook->Authors as $author){ ?>
|
||||
<?
|
||||
/* We include the `resource` attr here because we can have multiple authors, and in that case their href URLs will link to their combined corpus.
|
||||
|
||||
For example, William Wordsworth & Samuel Coleridge will both link to `/ebooks/william-wordsworth_samuel-taylor-coleridge`.
|
||||
|
||||
But, each author is an individual, so we have to differentiate them in RDFa with `resource`.
|
||||
*/ ?>
|
||||
<? if($author->Name != 'Anonymous'){ ?>
|
||||
<h2>
|
||||
<a property="schema:author" typeof="schema:Person" href="<?= Formatter::EscapeHtml($ebook->AuthorsUrl) ?>" resource="<?= '/ebooks/' . $author->UrlName ?>">
|
||||
<span property="schema:name"><?= Formatter::EscapeHtml($author->Name) ?></span>
|
||||
<meta property="schema:url" content="<?= SITE_URL . Formatter::EscapeHtml($ebook->AuthorsUrl) ?>"/>
|
||||
<? if($author->NacoafUrl){ ?>
|
||||
<meta property="schema:sameAs" content="<?= Formatter::EscapeHtml($author->NacoafUrl) ?>"/>
|
||||
<? } ?>
|
||||
<? if($author->WikipediaUrl){ ?>
|
||||
<meta property="schema:sameAs" content="<?= Formatter::EscapeHtml($author->WikipediaUrl) ?>"/>
|
||||
<? } ?>
|
||||
</a>
|
||||
</h2>
|
||||
<? } ?>
|
||||
<? } ?>
|
||||
</hgroup>
|
||||
</header>
|
||||
|
||||
<aside id="reading-ease">
|
||||
<? if($ebook->ContributorsHtml != ''){ ?>
|
||||
<p><?= $ebook->ContributorsHtml ?></p>
|
||||
<? } ?>
|
||||
<? if(sizeof($ebook->CollectionMemberships) > 0){ ?>
|
||||
<? foreach($ebook->CollectionMemberships as $collectionMembership){ ?>
|
||||
<? $collection = $collectionMembership->Collection; ?>
|
||||
<? $sequenceNumber = $collectionMembership->SequenceNumber; ?>
|
||||
<p>
|
||||
<? 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->Type !== null){ ?>
|
||||
<? if(substr_compare(mb_strtolower($collection->Name), mb_strtolower($collection->Type->value), -strlen(mb_strtolower($collection->Type->value))) !== 0){ ?>
|
||||
<?= $collection->Type->value ?>.
|
||||
<? } ?>
|
||||
<? }else{ ?>
|
||||
collection.
|
||||
<? } ?>
|
||||
</p>
|
||||
<? } ?>
|
||||
<? } ?>
|
||||
</aside>
|
||||
|
||||
<section class="placeholder-details">
|
||||
<? if($ebook->EbookPlaceholder->IsPublicDomain){ ?>
|
||||
<p>We don’t have this ebook in our catalog yet.</p>
|
||||
<p>You can <a href="/donate#sponsor-an-ebook">sponsor the production of this ebook</a> and we’ll get working on it immediately!</p>
|
||||
<? }elseif($ebook->EbookPlaceholder->YearPublished !== null){ ?>
|
||||
<p>This book was published in <?= $ebook->EbookPlaceholder->YearPublished ?>, and will therefore enter the U.S. public domain on <b>January 1, <?= $ebook->EbookPlaceholder->YearPublished + 96 ?>.</b></p>
|
||||
<p>We can’t work on it any earlier than that.</p>
|
||||
<? }else{ ?>
|
||||
<p>This book is not yet in the U.S. public domain. We can’t offer it until it is.</p>
|
||||
<? } ?>
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
<?= Template::Footer() ?>
|
62
www/ebook-placeholders/new.php
Normal file
62
www/ebook-placeholders/new.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?
|
||||
use function Safe\session_unset;
|
||||
|
||||
session_start();
|
||||
|
||||
$isCreated = HttpInput::Bool(SESSION, 'is-ebook-placeholder-created') ?? false;
|
||||
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
|
||||
$ebook = HttpInput::SessionObject('ebook', Ebook::class);
|
||||
|
||||
try{
|
||||
if(Session::$User === null){
|
||||
throw new Exceptions\LoginRequiredException();
|
||||
}
|
||||
|
||||
if(!Session::$User->Benefits->CanCreateEbookPlaceholders){
|
||||
throw new Exceptions\InvalidPermissionsException();
|
||||
}
|
||||
|
||||
if($isCreated){
|
||||
// We got here because an `Ebook` was successfully created.
|
||||
http_response_code(Enums\HttpCode::Created->value);
|
||||
$createdEbook = $ebook;
|
||||
$ebook = null;
|
||||
session_unset();
|
||||
}
|
||||
elseif($exception){
|
||||
// We got here because an `Ebook` submission had errors and the user has to try again.
|
||||
http_response_code(Enums\HttpCode::UnprocessableContent->value);
|
||||
session_unset();
|
||||
}
|
||||
}
|
||||
catch(Exceptions\LoginRequiredException){
|
||||
Template::RedirectToLogin();
|
||||
}
|
||||
catch(Exceptions\InvalidPermissionsException){
|
||||
Template::Emit403(); // No permissions to create an ebook placeholder.
|
||||
}
|
||||
?>
|
||||
<?= Template::Header(
|
||||
[
|
||||
'title' => 'Create an Ebook Placeholder',
|
||||
'css' => ['/css/ebook-placeholder.css'],
|
||||
'highlight' => '',
|
||||
'description' => 'Create a placeholder for an ebook not yet in the collection.'
|
||||
]
|
||||
) ?>
|
||||
<main>
|
||||
<section class="narrow">
|
||||
<h1>Create an Ebook Placeholder</h1>
|
||||
|
||||
<?= Template::Error(['exception' => $exception]) ?>
|
||||
|
||||
<? if($isCreated && isset($createdEbook)){ ?>
|
||||
<p class="message success">Ebook Placeholder created: <a href="<?= $createdEbook->Url ?>"><?= Formatter::EscapeHtml($createdEbook->Title) ?></a></p>
|
||||
<? } ?>
|
||||
|
||||
<form class="create-update-ebook-placeholder" method="<?= Enums\HttpMethod::Post->value ?>" action="/ebook-placeholders" autocomplete="off">
|
||||
<?= Template::EbookPlaceholderForm(['ebook' => $ebook]) ?>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
<?= Template::Footer() ?>
|
111
www/ebook-placeholders/post.php
Normal file
111
www/ebook-placeholders/post.php
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?
|
||||
|
||||
try{
|
||||
session_start();
|
||||
$httpMethod = HttpInput::ValidateRequestMethod([Enums\HttpMethod::Post]);
|
||||
$exceptionRedirectUrl = '/ebook-placeholders/new';
|
||||
|
||||
if(Session::$User === null){
|
||||
throw new Exceptions\LoginRequiredException();
|
||||
}
|
||||
|
||||
// POSTing a new ebook placeholder.
|
||||
if($httpMethod == Enums\HttpMethod::Post){
|
||||
if(!Session::$User->Benefits->CanCreateEbookPlaceholders){
|
||||
throw new Exceptions\InvalidPermissionsException();
|
||||
}
|
||||
|
||||
$ebook = new Ebook();
|
||||
|
||||
$title = HttpInput::Str(POST, 'ebook-title');
|
||||
if(isset($title)){
|
||||
$ebook->Title = $title;
|
||||
}
|
||||
|
||||
$authors = [];
|
||||
$authorFields = ['author-name-1', 'author-name-2', 'author-name-3'];
|
||||
foreach($authorFields as $authorField){
|
||||
$authorName = HttpInput::Str(POST, $authorField);
|
||||
if(!isset($authorName)){
|
||||
continue;
|
||||
}
|
||||
$author = new Contributor();
|
||||
$author->Name = $authorName;
|
||||
$author->UrlName = Formatter::MakeUrlSafe($author->Name);
|
||||
$author->MarcRole = Enums\MarcRole::Author;
|
||||
$authors[] = $author;
|
||||
}
|
||||
$ebook->Authors = $authors;
|
||||
|
||||
$translators = [];
|
||||
$translatorFields = ['translator-name-1', 'translator-name-2'];
|
||||
foreach($translatorFields as $translatorField){
|
||||
$translatorName = HttpInput::Str(POST, $translatorField);
|
||||
if(!isset($translatorName)){
|
||||
continue;
|
||||
}
|
||||
$translator = new Contributor();
|
||||
$translator->Name = $translatorName;
|
||||
$translator->UrlName = Formatter::MakeUrlSafe($translator->Name);
|
||||
$translator->MarcRole = Enums\MarcRole::Translator;
|
||||
$translators[] = $translator;
|
||||
}
|
||||
$ebook->Translators = $translators;
|
||||
|
||||
$collectionMemberships = [];
|
||||
$collectionNameFields = ['collection-name-1', 'collection-name-2', 'collection-name-3'];
|
||||
foreach($collectionNameFields as $collectionNameField){
|
||||
$collectionName = HttpInput::Str(POST, $collectionNameField);
|
||||
if(!isset($collectionName)){
|
||||
continue;
|
||||
}
|
||||
$collectionSequenceNumber = HttpInput::Int(POST, 'sequence-number-' . $collectionNameField);
|
||||
$collection = Collection::FromName($collectionName);
|
||||
|
||||
$cm = new CollectionMembership();
|
||||
$cm->Collection = $collection;
|
||||
$cm->SequenceNumber = $collectionSequenceNumber;
|
||||
$collectionMemberships[] = $cm;
|
||||
}
|
||||
$ebook->CollectionMemberships = $collectionMemberships;
|
||||
|
||||
$ebookPlaceholder = new EbookPlaceholder();
|
||||
$ebookPlaceholder->FillFromHttpPost();
|
||||
$ebook->EbookPlaceholder = $ebookPlaceholder;
|
||||
|
||||
$ebook->FillIdentifierFromTitleAndContributors();
|
||||
try{
|
||||
$existingEbook = Ebook::GetByIdentifier($ebook->Identifier);
|
||||
throw new Exceptions\DuplicateEbookException($ebook->Identifier);
|
||||
}
|
||||
catch(Exceptions\EbookNotFoundException){
|
||||
// Pass and create the placeholder. There is no existing ebook with this identifier.
|
||||
}
|
||||
|
||||
// These properties must be set before calling `Ebook::Create()` to prevent the getters from triggering DB queries or accessing `Ebook::$EbookId` before it is set.
|
||||
$ebook->Tags = [];
|
||||
$ebook->LocSubjects = [];
|
||||
$ebook->Illustrators = [];
|
||||
$ebook->Contributors = [];
|
||||
$ebook->Create();
|
||||
|
||||
$_SESSION['ebook'] = $ebook;
|
||||
$_SESSION['is-ebook-placeholder-created'] = true;
|
||||
|
||||
http_response_code(Enums\HttpCode::SeeOther->value);
|
||||
header('Location: /ebook-placeholders/new');
|
||||
}
|
||||
}
|
||||
catch(Exceptions\LoginRequiredException){
|
||||
Template::RedirectToLogin();
|
||||
}
|
||||
catch(Exceptions\InvalidPermissionsException | Exceptions\InvalidHttpMethodException | Exceptions\HttpMethodNotAllowedException){
|
||||
Template::Emit403();
|
||||
}
|
||||
catch(Exceptions\AppException $ex){
|
||||
$_SESSION['ebook'] = $ebook;
|
||||
$_SESSION['exception'] = $ex;
|
||||
|
||||
http_response_code(Enums\HttpCode::SeeOther->value);
|
||||
header('Location: ' . $exceptionRedirectUrl);
|
||||
}
|
|
@ -13,6 +13,10 @@ try{
|
|||
$identifier = EBOOKS_IDENTIFIER_PREFIX . $urlPath;
|
||||
$ebook = Ebook::GetByIdentifier($identifier);
|
||||
|
||||
if($ebook->IsPlaceholder()){
|
||||
throw new Exceptions\InvalidFileException();
|
||||
}
|
||||
|
||||
$format = Enums\EbookFormatType::tryFrom(HttpInput::Str(GET, 'format') ?? '') ?? Enums\EbookFormatType::Epub;
|
||||
switch($format){
|
||||
case Enums\EbookFormatType::Kepub:
|
||||
|
|
|
@ -19,6 +19,11 @@ try{
|
|||
|
||||
$ebook = Ebook::GetByIdentifier($identifier);
|
||||
|
||||
if($ebook->IsPlaceholder()){
|
||||
require('/standardebooks.org/web/www/ebook-placeholders/get.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Divide our sources into transcriptions and scans.
|
||||
foreach($ebook->Sources as $source){
|
||||
switch($source->Type){
|
||||
|
|
|
@ -34,7 +34,7 @@ try{
|
|||
$tags = [];
|
||||
}
|
||||
|
||||
$result = Ebook::GetAllByFilter($query != '' ? $query : null, $tags, $sort, $page, $perPage);
|
||||
$result = Ebook::GetAllByFilter($query != '' ? $query : null, $tags, $sort, $page, $perPage, Enums\EbookReleaseStatusFilter::All);
|
||||
$ebooks = $result['ebooks'];
|
||||
$totalEbooks = $result['ebooksCount'];
|
||||
$pageTitle = 'Browse Standard Ebooks';
|
||||
|
|
|
@ -7,7 +7,7 @@ try{
|
|||
$count = HttpInput::Int(GET, 'per-page') ?? EBOOKS_PER_PAGE;
|
||||
|
||||
if($query !== ''){
|
||||
$ebooks = Ebook::GetAllByFilter($query, [], Enums\EbookSortType::Newest, $startPage, $count)['ebooks'];
|
||||
$ebooks = Ebook::GetAllByFilter($query, [], Enums\EbookSortType::Newest, $startPage, $count, Enums\EbookReleaseStatusFilter::Released)['ebooks'];
|
||||
}
|
||||
}
|
||||
catch(\Exception){
|
||||
|
|
|
@ -7,7 +7,7 @@ try{
|
|||
$count = HttpInput::Int(GET, 'per-page') ?? EBOOKS_PER_PAGE;
|
||||
|
||||
if($query !== ''){
|
||||
$ebooks = Ebook::GetAllByFilter($query, [], Enums\EbookSortType::Newest, $startPage, $count)['ebooks'];
|
||||
$ebooks = Ebook::GetAllByFilter($query, [], Enums\EbookSortType::Newest, $startPage, $count, Enums\EbookReleaseStatusFilter::Released)['ebooks'];
|
||||
}
|
||||
}
|
||||
catch(\Exception){
|
||||
|
|
|
@ -7,7 +7,7 @@ try{
|
|||
$count = HttpInput::Int(GET, 'per-page') ?? EBOOKS_PER_PAGE;
|
||||
|
||||
if($query !== ''){
|
||||
$ebooks = Ebook::GetAllByFilter($query, [], Enums\EbookSortType::Newest, $startPage, $count)['ebooks'];
|
||||
$ebooks = Ebook::GetAllByFilter($query, [], Enums\EbookSortType::Newest, $startPage, $count, Enums\EbookReleaseStatusFilter::Released)['ebooks'];
|
||||
}
|
||||
}
|
||||
catch(\Exception){
|
||||
|
|
Binary file not shown.
|
@ -36,7 +36,7 @@ if($exception){
|
|||
<label>Your email address
|
||||
<input type="email" name="email" value="<?= Formatter::EscapeHtml($subscription->User->Email ?? '') ?>" maxlength="80" required="required" />
|
||||
</label>
|
||||
<label class="captcha">
|
||||
<label class="icon captcha">
|
||||
Type the letters in the <abbr class="acronym">CAPTCHA</abbr> image
|
||||
<div>
|
||||
<input type="text" name="captcha" required="required" autocomplete="off" />
|
||||
|
|
|
@ -169,6 +169,10 @@ catch(Exceptions\SeeOtherException $ex){
|
|||
<td>Can edit users:</td>
|
||||
<td><? if($user->Benefits->CanEditUsers){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Can create ebook placeholders:</td>
|
||||
<td><? if($user->Benefits->CanCreateEbookPlaceholders){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||
</tr>
|
||||
<? } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue