mirror of
https://github.com/standardebooks/web.git
synced 2025-07-07 07:10:29 -04:00
Add a CollectionMembership class
This commit is contained in:
parent
a25660bc8b
commit
ee29c526f8
7 changed files with 184 additions and 62 deletions
|
@ -7,9 +7,9 @@ use function Safe\preg_replace;
|
|||
class Collection{
|
||||
use Traits\Accessor;
|
||||
|
||||
public int $CollectionId;
|
||||
public string $Name;
|
||||
public string $UrlName;
|
||||
public ?int $SequenceNumber = null;
|
||||
public ?string $Type = null;
|
||||
protected ?string $_Url = null;
|
||||
|
||||
|
@ -28,7 +28,77 @@ class Collection{
|
|||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exceptions\CollectionNotFoundException
|
||||
*/
|
||||
public static function Get(?int $collectionId): Collection{
|
||||
if($collectionId === null){
|
||||
throw new Exceptions\CollectionNotFoundException();
|
||||
}
|
||||
|
||||
$result = Db::Query('
|
||||
SELECT *
|
||||
from Collections
|
||||
where CollectionId = ?
|
||||
', [$collectionId], Collection::class);
|
||||
|
||||
return $result[0] ?? throw new Exceptions\CollectionNotFoundException();;
|
||||
}
|
||||
|
||||
public function GetSortedName(): string{
|
||||
return preg_replace('/^(the|and|a|)\s/ius', '', $this->Name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exceptions\ValidationException
|
||||
*/
|
||||
public function Validate(): void{
|
||||
$error = new Exceptions\ValidationException();
|
||||
|
||||
if(strlen($this->Name) > EBOOKS_MAX_STRING_LENGTH){
|
||||
$error->Add(new Exceptions\StringTooLongException('Collection name: '. $this->Name));
|
||||
}
|
||||
|
||||
if($this->Type !== null && ($this->Type != 'series' && $this->Type != 'set')){
|
||||
$error->Add(new Exceptions\InvalidCollectionTypeException($this->Type));
|
||||
}
|
||||
|
||||
if($error->HasExceptions){
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exceptions\ValidationException
|
||||
*/
|
||||
public function Create(): void{
|
||||
$this->Validate();
|
||||
|
||||
Db::Query('
|
||||
INSERT into Collections (Name, UrlName, Type)
|
||||
values (?,
|
||||
?,
|
||||
?)
|
||||
', [$this->Name, $this->UrlName, $this->Type]);
|
||||
$this->CollectionId = Db::GetLastInsertedId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exceptions\ValidationException
|
||||
*/
|
||||
public function GetByUrlNameOrCreate(string $urlName): Collection{
|
||||
$result = Db::Query('
|
||||
SELECT *
|
||||
from Collections
|
||||
where UrlName = ?
|
||||
', [$urlName], Collection::class);
|
||||
|
||||
if(isset($result[0])){
|
||||
return $result[0];
|
||||
}
|
||||
else{
|
||||
$this->Create();
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
12
lib/CollectionMembership.php
Normal file
12
lib/CollectionMembership.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?
|
||||
use function Safe\preg_replace;
|
||||
|
||||
/**
|
||||
* @property Collection $Collection
|
||||
*/
|
||||
class CollectionMembership{
|
||||
use Traits\Accessor;
|
||||
|
||||
public ?int $SequenceNumber = null;
|
||||
protected ?Collection $_Collection = null;
|
||||
}
|
121
lib/Ebook.php
121
lib/Ebook.php
|
@ -15,7 +15,7 @@ use function Safe\shell_exec;
|
|||
* @property array<GitCommit> $GitCommits
|
||||
* @property array<EbookTag> $Tags
|
||||
* @property array<LocSubject> $LocSubjects
|
||||
* @property array<Collection> $Collections
|
||||
* @property array<CollectionMembership> $CollectionMemberships
|
||||
* @property array<EbookSource> $Sources
|
||||
* @property array<Contributor> $Authors
|
||||
* @property array<Contributor> $Illustrators
|
||||
|
@ -76,8 +76,8 @@ class Ebook{
|
|||
protected $_Tags = null;
|
||||
/** @var array<LocSubject> $_LocSubjects */
|
||||
protected $_LocSubjects = null;
|
||||
/** @var array<Collection> $_Collections */
|
||||
protected $_Collections = null;
|
||||
/** @var array<CollectionMembership> $_CollectionMemberships */
|
||||
protected $_CollectionMemberships = null;
|
||||
/** @var array<EbookSource> $_Sources */
|
||||
protected $_Sources = null;
|
||||
/** @var array<Contributor> $_Authors */
|
||||
|
@ -167,18 +167,19 @@ class Ebook{
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array<Collection>
|
||||
* @return array<CollectionMembership>
|
||||
*/
|
||||
protected function GetCollections(): array{
|
||||
if($this->_Collections === null){
|
||||
$this->_Collections = Db::Query('
|
||||
protected function GetCollectionMemberships(): array{
|
||||
if($this->_CollectionMemberships === null){
|
||||
$this->_CollectionMemberships = Db::Query('
|
||||
SELECT *
|
||||
from Collections
|
||||
from CollectionEbooks
|
||||
where EbookId = ?
|
||||
', [$this->EbookId], Collection::class);
|
||||
order by CollectionEbookId
|
||||
', [$this->EbookId], CollectionMembership::class);
|
||||
}
|
||||
|
||||
return $this->_Collections;
|
||||
return $this->_CollectionMemberships;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -549,8 +550,8 @@ class Ebook{
|
|||
|
||||
$this->_IndexableText .= ' ' . $this->AlternateTitle;
|
||||
|
||||
foreach($this->Collections as $collection){
|
||||
$this->_IndexableText .= ' ' . $collection->Name;
|
||||
foreach($this->CollectionMemberships as $collectionMembership){
|
||||
$this->_IndexableText .= ' ' . $collectionMembership->Collection->Name;
|
||||
}
|
||||
|
||||
foreach($this->Authors as $author){
|
||||
|
@ -749,20 +750,21 @@ class Ebook{
|
|||
}
|
||||
|
||||
// Get SE collections
|
||||
$collections = [];
|
||||
$collectionMemberships = [];
|
||||
foreach($xml->xpath('/package/metadata/meta[@property="belongs-to-collection"]') ?: [] as $collection){
|
||||
$c = Collection::FromName($collection);
|
||||
$id = $collection->attributes()->id ?? '';
|
||||
$cm = new CollectionMembership();
|
||||
$cm->Collection = Collection::FromName($collection);
|
||||
|
||||
$id = $collection->attributes()->id ?? '';
|
||||
foreach($xml->xpath('/package/metadata/meta[@refines="#' . $id . '"][@property="group-position"]') ?: [] as $s){
|
||||
$c->SequenceNumber = (int)$s;
|
||||
$cm->SequenceNumber = (int)$s;
|
||||
}
|
||||
foreach($xml->xpath('/package/metadata/meta[@refines="#' . $id . '"][@property="collection-type"]') ?: [] as $s){
|
||||
$c->Type = (string)$s;
|
||||
$cm->Collection->Type = (string)$s;
|
||||
}
|
||||
$collections[] = $c;
|
||||
$collectionMemberships[] = $cm;
|
||||
}
|
||||
$ebookFromFilesystem->Collections = $collections;
|
||||
$ebookFromFilesystem->CollectionMemberships = $collectionMemberships;
|
||||
|
||||
// Get LoC tags
|
||||
$locSubjects = [];
|
||||
|
@ -1125,10 +1127,25 @@ class Ebook{
|
|||
$this->LocSubjects = $subjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exceptions\ValidationException
|
||||
*/
|
||||
private function InsertCollections(): void{
|
||||
$collectionMemberships = [];
|
||||
foreach($this->CollectionMemberships as $collectionMembership){
|
||||
$collection = $collectionMembership->Collection;
|
||||
// The updated collection has the CollectionId set for newly-created Collection objects.
|
||||
$updatedCollection = $collection->GetByUrlNameOrCreate($collection->UrlName);
|
||||
$collectionMembership->Collection = $updatedCollection;
|
||||
$collectionMemberships[] = $collectionMembership;
|
||||
}
|
||||
$this->CollectionMemberships = $collectionMemberships;
|
||||
}
|
||||
|
||||
public function GetCollectionPosition(Collection $collection): ?int{
|
||||
foreach($this->Collections as $c){
|
||||
if($c->Name == $collection->Name){
|
||||
return $c->SequenceNumber;
|
||||
foreach($this->CollectionMemberships as $cm){
|
||||
if($cm->Collection->Name == $collection->Name){
|
||||
return $cm->SequenceNumber;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1143,8 +1160,8 @@ class Ebook{
|
|||
|
||||
$searchString .= ' ' . $this->AlternateTitle;
|
||||
|
||||
foreach($this->Collections as $collection){
|
||||
$searchString .= ' ' . $collection->Name;
|
||||
foreach($this->CollectionMemberships as $collectionMembership){
|
||||
$searchString .= ' ' . $collectionMembership->Collection->Name;
|
||||
}
|
||||
|
||||
foreach($this->Authors as $author){
|
||||
|
@ -1414,8 +1431,8 @@ class Ebook{
|
|||
}
|
||||
|
||||
public function IsInCollection(string $collection): bool{
|
||||
foreach($this->Collections as $c){
|
||||
if(strtolower(Formatter::RemoveDiacritics($c->Name)) == strtolower(Formatter::RemoveDiacritics($collection))){
|
||||
foreach($this->CollectionMemberships as $cm){
|
||||
if(strtolower(Formatter::RemoveDiacritics($cm->Collection->Name)) == strtolower(Formatter::RemoveDiacritics($collection))){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1456,6 +1473,7 @@ class Ebook{
|
|||
|
||||
$this->InsertTagStrings();
|
||||
$this->InsertLocSubjectStrings();
|
||||
$this->InsertCollections();
|
||||
|
||||
Db::Query('
|
||||
INSERT into Ebooks (Identifier, WwwFilesystemPath, RepoFilesystemPath, KindleCoverUrl, EpubUrl,
|
||||
|
@ -1495,8 +1513,8 @@ class Ebook{
|
|||
|
||||
$this->InsertTags();
|
||||
$this->InsertLocSubjects();
|
||||
$this->InsertCollectionMemberships();
|
||||
$this->InsertGitCommits();
|
||||
$this->InsertCollections();
|
||||
$this->InsertSources();
|
||||
$this->InsertContributors();
|
||||
$this->InsertTocEntries();
|
||||
|
@ -1510,6 +1528,7 @@ class Ebook{
|
|||
|
||||
$this->InsertTagStrings();
|
||||
$this->InsertLocSubjectStrings();
|
||||
$this->InsertCollections();
|
||||
|
||||
Db::Query('
|
||||
UPDATE Ebooks
|
||||
|
@ -1552,12 +1571,12 @@ class Ebook{
|
|||
$this->DeleteLocSubjects();
|
||||
$this->InsertLocSubjects();
|
||||
|
||||
$this->DeleteCollectionMemberships();
|
||||
$this->InsertCollectionMemberships();
|
||||
|
||||
$this->DeleteGitCommits();
|
||||
$this->InsertGitCommits();
|
||||
|
||||
$this->DeleteCollections();
|
||||
$this->InsertCollections();
|
||||
|
||||
$this->DeleteSources();
|
||||
$this->InsertSources();
|
||||
|
||||
|
@ -1606,6 +1625,26 @@ class Ebook{
|
|||
}
|
||||
}
|
||||
|
||||
private function DeleteCollectionMemberships(): void{
|
||||
Db::Query('
|
||||
DELETE from CollectionEbooks
|
||||
where
|
||||
EbookId = ?
|
||||
', [$this->EbookId]
|
||||
);
|
||||
}
|
||||
|
||||
private function InsertCollectionMemberships(): void{
|
||||
foreach($this->CollectionMemberships as $collectionMembership){
|
||||
Db::Query('
|
||||
INSERT into CollectionEbooks (EbookId, CollectionId, SequenceNumber)
|
||||
values (?,
|
||||
?,
|
||||
?)
|
||||
', [$this->EbookId, $collectionMembership->Collection->CollectionId, $collectionMembership->SequenceNumber]);
|
||||
}
|
||||
}
|
||||
|
||||
private function DeleteGitCommits(): void{
|
||||
Db::Query('
|
||||
DELETE from GitCommits
|
||||
|
@ -1627,28 +1666,6 @@ class Ebook{
|
|||
}
|
||||
}
|
||||
|
||||
private function DeleteCollections(): void{
|
||||
Db::Query('
|
||||
DELETE from Collections
|
||||
where
|
||||
EbookId = ?
|
||||
', [$this->EbookId]
|
||||
);
|
||||
}
|
||||
|
||||
private function InsertCollections(): void{
|
||||
foreach($this->Collections as $collection){
|
||||
Db::Query('
|
||||
INSERT into Collections (EbookId, Name, UrlName, SequenceNumber, Type)
|
||||
values (?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?)
|
||||
', [$this->EbookId, $collection->Name, $collection->UrlName, $collection->SequenceNumber, $collection->Type]);
|
||||
}
|
||||
}
|
||||
|
||||
private function DeleteSources(): void{
|
||||
Db::Query('
|
||||
DELETE from EbookSources
|
||||
|
|
16
lib/Exceptions/InvalidCollectionTypeException.php
Normal file
16
lib/Exceptions/InvalidCollectionTypeException.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Exceptions;
|
||||
|
||||
class InvalidCollectionTypeException extends AppException{
|
||||
/** @var string $message */
|
||||
protected $message = 'Collection type should be `series` or `set` according to the EPUB specification.';
|
||||
|
||||
public function __construct(?string $collectionType){
|
||||
if($collectionType !== null && trim($collectionType) != ''){
|
||||
$this->message .= ' Type provided: ' . $collectionType;
|
||||
}
|
||||
|
||||
parent::__construct($this->message);
|
||||
}
|
||||
}
|
|
@ -163,9 +163,11 @@ class Library{
|
|||
public static function GetEbooksByCollection(string $collection): array{
|
||||
$ebooks = Db::Query('
|
||||
SELECT e.*
|
||||
from Ebooks e inner join Collections c using (EbookId)
|
||||
from Ebooks e
|
||||
inner join CollectionEbooks ce using (EbookId)
|
||||
inner join Collections c using (CollectionId)
|
||||
where c.UrlName = ?
|
||||
order by c.SequenceNumber, e.Title
|
||||
order by ce.SequenceNumber, e.EbookCreated desc
|
||||
', [$collection], Ebook::class);
|
||||
|
||||
return $ebooks;
|
||||
|
@ -708,7 +710,9 @@ class Library{
|
|||
$ebooks[$ebookWwwFilesystemPath] = $ebook;
|
||||
|
||||
// Create the collections cache
|
||||
foreach($ebook->Collections as $collection){
|
||||
foreach($ebook->CollectionMemberships as $collectionMembership){
|
||||
$collection = $collectionMembership->Collection;
|
||||
$sequenceNumber = $collectionMembership->SequenceNumber;
|
||||
$urlSafeCollection = Formatter::MakeUrlSafe($collection->Name);
|
||||
if(!array_key_exists($urlSafeCollection, $ebooksByCollection)){
|
||||
$ebooksByCollection[$urlSafeCollection] = [];
|
||||
|
@ -721,8 +725,8 @@ class Library{
|
|||
// then later we sort by that instead of by array index.
|
||||
$sortItem = new stdClass();
|
||||
$sortItem->Ebook = $ebook;
|
||||
if($collection->SequenceNumber !== null){
|
||||
$sortItem->Ordinal = $collection->SequenceNumber;
|
||||
if($sequenceNumber !== null){
|
||||
$sortItem->Ordinal = $sequenceNumber;
|
||||
}
|
||||
else{
|
||||
$sortItem->Ordinal = 1;
|
||||
|
|
|
@ -10,7 +10,8 @@ try{
|
|||
$ebooks = Library::GetEbooksByCollection($collection);
|
||||
// Get the *actual* name of the collection, in case there are accent marks (like "Arsène Lupin")
|
||||
if(sizeof($ebooks) > 0){
|
||||
foreach($ebooks[0]->Collections as $c){
|
||||
foreach($ebooks[0]->CollectionMemberships as $cm){
|
||||
$c = $cm->Collection;
|
||||
if($collection == Formatter::MakeUrlSafe($c->Name)){
|
||||
$collectionObject = $c;
|
||||
}
|
||||
|
|
|
@ -148,9 +148,11 @@ catch(Exceptions\EbookNotFoundException){
|
|||
<? if($ebook->ContributorsHtml !== null){ ?>
|
||||
<p><?= $ebook->ContributorsHtml ?></p>
|
||||
<? } ?>
|
||||
<? if(sizeof($ebook->Collections) > 0){ ?>
|
||||
<? foreach($ebook->Collections as $collection){ ?>
|
||||
<p><? if($collection->SequenceNumber !== null){ ?>№ <?= number_format($collection->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(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), -strlen(mb_strtolower($collection->Type))) !== 0){ ?>
|
||||
<?= $collection->Type ?>.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue