diff --git a/lib/Collection.php b/lib/Collection.php index 87b97e06..8c6b8a95 100644 --- a/lib/Collection.php +++ b/lib/Collection.php @@ -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; + } + } } diff --git a/lib/CollectionMembership.php b/lib/CollectionMembership.php new file mode 100644 index 00000000..7ded2833 --- /dev/null +++ b/lib/CollectionMembership.php @@ -0,0 +1,12 @@ + $GitCommits * @property array $Tags * @property array $LocSubjects - * @property array $Collections + * @property array $CollectionMemberships * @property array $Sources * @property array $Authors * @property array $Illustrators @@ -76,8 +76,8 @@ class Ebook{ protected $_Tags = null; /** @var array $_LocSubjects */ protected $_LocSubjects = null; - /** @var array $_Collections */ - protected $_Collections = null; + /** @var array $_CollectionMemberships */ + protected $_CollectionMemberships = null; /** @var array $_Sources */ protected $_Sources = null; /** @var array $_Authors */ @@ -167,18 +167,19 @@ class Ebook{ } /** - * @return array + * @return array */ - 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 diff --git a/lib/Exceptions/InvalidCollectionTypeException.php b/lib/Exceptions/InvalidCollectionTypeException.php new file mode 100644 index 00000000..7d7f3334 --- /dev/null +++ b/lib/Exceptions/InvalidCollectionTypeException.php @@ -0,0 +1,16 @@ +message .= ' Type provided: ' . $collectionType; + } + + parent::__construct($this->message); + } +} diff --git a/lib/Library.php b/lib/Library.php index 435bad62..a8193204 100644 --- a/lib/Library.php +++ b/lib/Library.php @@ -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; diff --git a/www/collections/get.php b/www/collections/get.php index 0124169a..5d7a808c 100644 --- a/www/collections/get.php +++ b/www/collections/get.php @@ -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; } diff --git a/www/ebooks/ebook.php b/www/ebooks/ebook.php index 8612719a..766c3668 100644 --- a/www/ebooks/ebook.php +++ b/www/ebooks/ebook.php @@ -148,9 +148,11 @@ catch(Exceptions\EbookNotFoundException){ ContributorsHtml !== null){ ?>

ContributorsHtml ?>

- Collections) > 0){ ?> - Collections as $collection){ ?> -

SequenceNumber !== null){ ?>№ SequenceNumber) ?> in thePart of the Name)) ?> + CollectionMemberships) > 0){ ?> + CollectionMemberships as $collectionMembership){ ?> + Collection; ?> + SequenceNumber; ?> +

in thePart of the Name)) ?> Type !== null){ ?> Name), mb_strtolower($collection->Type), -strlen(mb_strtolower($collection->Type))) !== 0){ ?> Type ?>.