Add a CollectionMembership class

This commit is contained in:
Mike Colagrosso 2024-06-25 16:26:36 -06:00 committed by Alex Cabal
parent a25660bc8b
commit ee29c526f8
7 changed files with 184 additions and 62 deletions

View file

@ -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;
}
}
}

View 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;
}

View file

@ -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

View 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);
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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 ?>.