diff --git a/config/sql/se/Artworks.sql b/config/sql/se/Artworks.sql index cb1991fd..45f8cdb2 100644 --- a/config/sql/se/Artworks.sql +++ b/config/sql/se/Artworks.sql @@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS `Artworks` ( `CompletedYearIsCirca` boolean NOT NULL DEFAULT FALSE, `Created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `Updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `Status` enum('unverified', 'approved', 'declined', 'in_use') DEFAULT 'unverified', + `Status` enum('unverified', 'approved', 'declined', 'in_use') NOT NULL DEFAULT 'unverified', `SubmitterUserId` int(10) unsigned NULL, `ReviewerUserId` int(10) unsigned NULL, `MuseumUrl` varchar(255) NULL, diff --git a/config/sql/se/EbookSources.sql b/config/sql/se/EbookSources.sql index 46892730..4fd758b2 100644 --- a/config/sql/se/EbookSources.sql +++ b/config/sql/se/EbookSources.sql @@ -1,6 +1,6 @@ CREATE TABLE IF NOT EXISTS `EbookSources` ( `EbookId` int(10) unsigned NOT NULL, - `Type` enum('project_gutenberg', 'project_gutenberg_australia', 'project_gutenberg_canada', 'internet_archive', 'hathi_trust', 'wikisource', 'google_books', 'faded_page', 'other') DEFAULT 'other', + `Type` enum('project_gutenberg', 'project_gutenberg_australia', 'project_gutenberg_canada', 'internet_archive', 'hathi_trust', 'wikisource', 'google_books', 'faded_page', 'other') NOT NULL DEFAULT 'other', `Url` varchar(255) NOT NULL, `SortOrder` tinyint(3) unsigned NOT NULL, KEY `index1` (`EbookId`) diff --git a/lib/Artist.php b/lib/Artist.php index 67b9467b..ec849f97 100644 --- a/lib/Artist.php +++ b/lib/Artist.php @@ -9,49 +9,39 @@ use Safe\DateTimeImmutable; */ class Artist{ use Traits\Accessor; + use Traits\PropertyFromHttp; - public ?int $ArtistId = null; - public ?string $Name = null; - public ?DateTimeImmutable $Created = null; - public ?DateTimeImmutable $Updated = null; + public int $ArtistId; + public string $Name = ''; + public DateTimeImmutable $Created; + public DateTimeImmutable $Updated; + public ?int $DeathYear = null; - protected ?int $_DeathYear = null; - protected ?string $_UrlName = null; - protected ?string $_Url = null; - /** @var ?array $_AlternateNames */ - protected $_AlternateNames = null; + protected string $_UrlName; + protected string $_Url; + /** @var array $_AlternateNames */ + protected array $_AlternateNames; - // ******* - // SETTERS - // ******* - - protected function SetDeathYear(?int $deathYear): void{ - if($this->Name == 'Anonymous'){ - $this->_DeathYear = null; - } - else { - $this->_DeathYear = $deathYear; - } - } // ******* // GETTERS // ******* protected function GetUrlName(): string{ - if($this->Name === null || $this->Name == ''){ - return ''; - } - - if($this->_UrlName === null){ - $this->_UrlName = Formatter::MakeUrlSafe($this->Name); + if(!isset($this->_UrlName)){ + if(!isset($this->Name) || $this->Name == ''){ + $this->_UrlName = ''; + } + else{ + $this->_UrlName = Formatter::MakeUrlSafe($this->Name); + } } return $this->_UrlName; } protected function GetUrl(): string{ - if($this->_Url === null){ + if(!isset($this->_Url)){ $this->_Url = '/artworks/' . $this->UrlName; } @@ -62,7 +52,7 @@ class Artist{ * @return array */ protected function GetAlternateNames(): array{ - if($this->_AlternateNames === null){ + if(!isset($this->_AlternateNames)){ $this->_AlternateNames = []; $result = Db::Query(' @@ -79,6 +69,7 @@ class Artist{ return $this->_AlternateNames; } + // ******* // METHODS // ******* @@ -91,16 +82,15 @@ class Artist{ $error = new Exceptions\InvalidArtistException(); - if($this->Name === null || $this->Name == ''){ + if(!isset($this->Name) || $this->Name == ''){ $error->Add(new Exceptions\ArtistNameRequiredException()); } - - if($this->Name !== null && strlen($this->Name) > ARTWORK_MAX_STRING_LENGTH){ + elseif(strlen($this->Name) > ARTWORK_MAX_STRING_LENGTH){ $error->Add(new Exceptions\StringTooLongException('Artist Name')); } if($this->Name == 'Anonymous' && $this->DeathYear !== null){ - $this->_DeathYear = null; + $this->DeathYear = null; } if($this->DeathYear !== null && ($this->DeathYear <= 0 || $this->DeathYear > $thisYear + 50)){ @@ -111,11 +101,16 @@ class Artist{ throw $error; } } + + public function FillFromHttpPost(): void{ + $this->PropertyFromHttp('Name'); + $this->PropertyFromHttp('DeathYear'); + } + // *********** // ORM METHODS // *********** - /** * @throws Exceptions\ArtistNotFoundException */ @@ -124,13 +119,11 @@ class Artist{ throw new Exceptions\ArtistNotFoundException(); } - $result = Db::Query(' + return Db::Query(' SELECT * from Artists where ArtistId = ? - ', [$artistId], Artist::class); - - return $result[0] ?? throw new Exceptions\ArtistNotFoundException(); + ', [$artistId], Artist::class)[0] ?? throw new Exceptions\ArtistNotFoundException(); } /** @@ -141,15 +134,13 @@ class Artist{ throw new Exceptions\ArtistNotFoundException(); } - $result = Db::Query(' + return Db::Query(' SELECT a.* from Artists a left outer join ArtistAlternateNames aan using (ArtistId) where aan.UrlName = ? limit 1 - ', [$urlName], Artist::class); - - return $result[0] ?? throw new Exceptions\ArtistNotFoundException(); + ', [$urlName], Artist::class)[0] ?? throw new Exceptions\ArtistNotFoundException(); } /** diff --git a/lib/Artwork.php b/lib/Artwork.php index ed4cffd8..a3e8ee9b 100644 --- a/lib/Artwork.php +++ b/lib/Artwork.php @@ -7,6 +7,7 @@ use function Safe\getimagesize; use function Safe\parse_url; use function Safe\preg_match; use function Safe\preg_replace; +use function Safe\unlink; /** * @property string $UrlName @@ -24,19 +25,20 @@ use function Safe\preg_replace; * @property string $Dimensions * @property Ebook $Ebook * @property Museum $Museum - * @property User $Submitter - * @property User $Reviewer + * @property ?User $Submitter + * @property ?User $Reviewer */ class Artwork{ use Traits\Accessor; + use Traits\PropertyFromHttp; - public ?string $Name = null; - public ?int $ArtworkId = null; - public ?int $ArtistId = null; + public int $ArtworkId; + public string $Name = ''; + public int $ArtistId; public ?int $CompletedYear = null; public bool $CompletedYearIsCirca = false; - public ?DateTimeImmutable $Created = null; - public ?DateTimeImmutable $Updated = null; + public DateTimeImmutable $Created; + public DateTimeImmutable $Updated; public ?string $EbookUrl = null; public ?int $SubmitterUserId = null; public ?int $ReviewerUserId = null; @@ -45,26 +47,27 @@ class Artwork{ public ?string $PublicationYearPageUrl = null; public ?string $CopyrightPageUrl = null; public ?string $ArtworkPageUrl = null; - public ?bool $IsPublishedInUs = null; + public bool $IsPublishedInUs = false; public ?string $Exception = null; public ?string $Notes = null; - public ?Enums\ImageMimeType $MimeType = null; - public ?Enums\ArtworkStatusType $Status = null; + public Enums\ImageMimeType $MimeType; + public Enums\ArtworkStatusType $Status = Enums\ArtworkStatusType::Unverified; + + protected string $_UrlName; + protected string $_Url; + protected string $_EditUrl; + /** @var array $_Tags */ + protected array $_Tags; + protected Artist $_Artist; + protected string $_ImageUrl; + protected string $_ThumbUrl; + protected string $_Thumb2xUrl; + protected string $_Dimensions ; + protected ?Ebook $_Ebook; + protected ?Museum $_Museum; + protected ?User $_Submitter; + protected ?User $_Reviewer; - protected ?string $_UrlName = null; - protected ?string $_Url = null; - protected ?string $_EditUrl = null; - /** @var ?array $_Tags */ - protected $_Tags = null; - protected ?Artist $_Artist = null; - protected ?string $_ImageUrl = null; - protected ?string $_ThumbUrl = null; - protected ?string $_Thumb2xUrl = null; - protected ?string $_Dimensions = null; - protected ?Ebook $_Ebook = null; - protected ?Museum $_Museum = null; - protected ?User $_Submitter = null; - protected ?User $_Reviewer = null; // ******* // SETTERS @@ -74,45 +77,54 @@ class Artwork{ * @param string|null|array $tags */ protected function SetTags(null|string|array $tags): void{ - if($tags === null || is_array($tags)){ + if(is_array($tags)){ $this->_Tags = $tags; } - elseif(is_string($tags)){ - $tags = array_map('trim', explode(',', $tags)); - $tags = array_values(array_filter($tags)); - $tags = array_unique($tags); + else{ + $tags = trim($tags ?? ''); - $this->_Tags = array_map(function ($str){ - $tag = new ArtworkTag(); - $tag->Name = $str; - return $tag; - }, $tags); + if($tags === ''){ + $this->_Tags = []; + } + else{ + $tags = array_map('trim', explode(',', $tags)); + $tags = array_values(array_filter($tags)); + $tags = array_unique($tags); + + $this->_Tags = array_map(function ($str): ArtworkTag{ + $tag = new ArtworkTag(); + $tag->Name = $str; + return $tag; + }, $tags); + } } } + // ******* // GETTERS // ******* protected function GetUrlName(): string{ - if($this->Name === null || $this->Name == ''){ - return ''; - } - - if($this->_UrlName === null){ - $this->_UrlName = Formatter::MakeUrlSafe($this->Name); + if(!isset($this->_UrlName)){ + if(!isset($this->Name) || $this->Name == ''){ + $this->_UrlName = ''; + } + else{ + $this->_UrlName = Formatter::MakeUrlSafe($this->Name); + } } return $this->_UrlName; } protected function GetSubmitter(): ?User{ - if($this->_Submitter === null){ + if(!isset($this->_Submitter)){ try{ $this->_Submitter = User::Get($this->SubmitterUserId); } catch(Exceptions\UserNotFoundException){ - // Return null + $this->Submitter = null; } } @@ -120,12 +132,12 @@ class Artwork{ } protected function GetReviewer(): ?User{ - if($this->_Reviewer === null){ + if(!isset($this->_Reviewer)){ try{ $this->_Reviewer = User::Get($this->ReviewerUserId); } catch(Exceptions\UserNotFoundException){ - // Return null + $this->_Reviewer = null; } } @@ -133,7 +145,7 @@ class Artwork{ } protected function GetUrl(): string{ - if($this->_Url === null){ + if(!isset($this->_Url)){ $this->_Url = '/artworks/' . $this->Artist->UrlName . '/' . $this->UrlName; } @@ -141,7 +153,7 @@ class Artwork{ } protected function GetEditUrl(): string{ - if($this->_EditUrl === null){ + if(!isset($this->_EditUrl)){ $this->_EditUrl = $this->Url . '/edit'; } @@ -152,7 +164,7 @@ class Artwork{ * @return array */ protected function GetTags(): array{ - if($this->_Tags === null){ + if(!isset($this->_Tags)){ $this->_Tags = Db::Query(' SELECT t.* from Tags t @@ -168,34 +180,28 @@ class Artwork{ * @throws Exceptions\InvalidUrlException */ public function GetMuseum(): ?Museum{ - if($this->_Museum === null){ + if(!isset($this->_Museum)){ try{ $this->_Museum = Museum::GetByUrl($this->MuseumUrl); } catch(Exceptions\MuseumNotFoundException){ - // Pass + // Pass. } } return $this->_Museum; } - public function ImplodeTags(): string{ - $tags = $this->Tags ?? []; - $tags = array_column($tags, 'Name'); - return trim(implode(', ', $tags)); - } - /** * @throws Exceptions\InvalidArtworkException */ protected function GetImageUrl(): string{ - if($this->_ImageUrl === null){ - if($this->ArtworkId === null || $this->MimeType === null){ + if(!isset($this->_ImageUrl)){ + if(!isset($this->ArtworkId) || !isset($this->MimeType)){ throw new Exceptions\InvalidArtworkException(); } - $this->_ImageUrl = COVER_ART_UPLOAD_PATH . $this->ArtworkId . $this->MimeType->GetFileExtension() . '?ts=' . $this->Updated?->getTimestamp(); + $this->_ImageUrl = COVER_ART_UPLOAD_PATH . $this->ArtworkId . $this->MimeType->GetFileExtension() . '?ts=' . $this->Updated->getTimestamp(); } return $this->_ImageUrl; @@ -205,12 +211,12 @@ class Artwork{ * @throws Exceptions\ArtworkNotFoundException */ protected function GetThumbUrl(): string{ - if($this->_ThumbUrl === null){ - if($this->ArtworkId === null){ + if(!isset($this->_ThumbUrl)){ + if(!isset($this->ArtworkId)){ throw new Exceptions\ArtworkNotFoundException(); } - $this->_ThumbUrl = COVER_ART_UPLOAD_PATH . $this->ArtworkId . '-thumb.jpg' . '?ts=' . $this->Updated?->getTimestamp(); + $this->_ThumbUrl = COVER_ART_UPLOAD_PATH . $this->ArtworkId . '-thumb.jpg' . '?ts=' . $this->Updated->getTimestamp(); } return $this->_ThumbUrl; @@ -220,12 +226,12 @@ class Artwork{ * @throws Exceptions\ArtworkNotFoundException */ protected function GetThumb2xUrl(): string{ - if($this->_Thumb2xUrl === null){ - if($this->ArtworkId === null){ + if(!isset($this->_Thumb2xUrl)){ + if(!isset($this->ArtworkId)){ throw new Exceptions\ArtworkNotFoundException(); } - $this->_Thumb2xUrl = COVER_ART_UPLOAD_PATH . $this->ArtworkId . '-thumb@2x.jpg' . '?ts=' . $this->Updated?->getTimestamp(); + $this->_Thumb2xUrl = COVER_ART_UPLOAD_PATH . $this->ArtworkId . '-thumb@2x.jpg' . '?ts=' . $this->Updated->getTimestamp(); } return $this->_Thumb2xUrl; @@ -244,22 +250,24 @@ class Artwork{ } protected function GetDimensions(): string{ - $this->_Dimensions = ''; - try{ - list($imageWidth, $imageHeight) = getimagesize($this->ImageFsPath); - if($imageWidth && $imageHeight){ - $this->_Dimensions = number_format($imageWidth) . ' × ' . number_format($imageHeight); + if(!isset($this->Dimensions)){ + $this->_Dimensions = ''; + try{ + list($imageWidth, $imageHeight) = getimagesize($this->ImageFsPath); + if($imageWidth && $imageHeight){ + $this->_Dimensions = number_format($imageWidth) . ' × ' . number_format($imageHeight); + } + } + catch(Exception){ + // Image doesn't exist, return a blank string. } - } - catch(Exception){ - // Image doesn't exist, return blank string } return $this->_Dimensions; } protected function GetEbook(): ?Ebook{ - if($this->_Ebook === null){ + if(!isset($this->_Ebook)){ if($this->EbookUrl === null){ return null; } @@ -277,9 +285,11 @@ class Artwork{ return $this->_Ebook; } + // ******* // METHODS // ******* + public function CanBeEditedBy(?User $user): bool{ if($user === null){ return false; @@ -336,15 +346,16 @@ class Artwork{ $thisYear = intval(NOW->format('Y')); $error = new Exceptions\InvalidArtworkException(); - if($this->Artist === null){ + if(!isset($this->Artist)){ $error->Add(new Exceptions\InvalidArtistException()); } - - try{ - $this->Artist->Validate(); - } - catch(Exceptions\ValidationException $ex){ - $error->Add($ex); + else{ + try{ + $this->Artist->Validate(); + } + catch(Exceptions\ValidationException $ex){ + $error->Add($ex); + } } if($this->Exception !== null && trim($this->Exception) == ''){ @@ -355,12 +366,17 @@ class Artwork{ $this->Notes = null; } - if($this->Name === null || $this->Name == ''){ - $error->Add(new Exceptions\ArtworkNameRequiredException()); - } + if(isset($this->Name)){ + if($this->Name == ''){ + $error->Add(new Exceptions\ArtworkNameRequiredException()); + } - if($this->Name !== null && strlen($this->Name) > ARTWORK_MAX_STRING_LENGTH){ - $error->Add(new Exceptions\StringTooLongException('Artwork Name')); + if(strlen($this->Name) > ARTWORK_MAX_STRING_LENGTH){ + $error->Add(new Exceptions\StringTooLongException('Artwork Name')); + } + } + else{ + $error->Add(new Exceptions\ArtworkNameRequiredException()); } if($this->CompletedYear !== null && ($this->CompletedYear <= 0 || $this->CompletedYear > $thisYear)){ @@ -375,27 +391,32 @@ class Artwork{ $error->Add(new Exceptions\InvalidPublicationYearException()); } - if($this->Status === null){ + if(!isset($this->Status)){ $error->Add(new Exceptions\InvalidArtworkException('Invalid status.')); } - if(count($this->Tags) == 0){ + if(isset($this->Tags)){ + if(count($this->Tags) == 0){ + $error->Add(new Exceptions\TagsRequiredException()); + } + + if(count($this->Tags) > ARTWORK_MAX_TAGS){ + $error->Add(new Exceptions\TooManyTagsException()); + } + + foreach($this->Tags as $tag){ + try{ + $tag->Validate(); + } + catch(Exceptions\ValidationException $ex){ + $error->Add($ex); + } + } + } + else{ $error->Add(new Exceptions\TagsRequiredException()); } - if(count($this->Tags) > ARTWORK_MAX_TAGS){ - $error->Add(new Exceptions\TooManyTagsException()); - } - - foreach($this->Tags as $tag){ - try{ - $tag->Validate(); - } - catch(Exceptions\ValidationException $ex){ - $error->Add($ex); - } - } - if($this->MuseumUrl !== null){ if(strlen($this->MuseumUrl) > ARTWORK_MAX_STRING_LENGTH){ $error->Add(new Exceptions\StringTooLongException('Link to an approved museum page')); @@ -482,36 +503,38 @@ class Artwork{ } } - // Check for existing Artwork objects with the same URL but different Artwork IDs. - try{ - $existingArtwork = Artwork::GetByUrl($this->Artist->UrlName, $this->UrlName); - if($existingArtwork->ArtworkId != $this->ArtworkId){ - // Duplicate found, alert the user - $error->Add(new Exceptions\ArtworkAlreadyExistsException()); + // Check for existing `Artwork` objects with the same URL but different `ArtworkID`s. + if(isset($this->ArtworkId)){ + try{ + $existingArtwork = Artwork::GetByUrl($this->Artist->UrlName, $this->UrlName); + if($existingArtwork->ArtworkId != $this->ArtworkId){ + // Duplicate found, alert the user. + $error->Add(new Exceptions\ArtworkAlreadyExistsException()); + } } - } - catch(Exceptions\ArtworkNotFoundException){ - // No duplicates found, continue - } - - if($isImageRequired && $imagePath === null){ - $error->Add(new Exceptions\InvalidImageUploadException('An image is required.')); - } - - if($imagePath !== null && $this->MimeType !== null){ - if(!is_writable(WEB_ROOT . COVER_ART_UPLOAD_PATH)){ - $error->Add(new Exceptions\InvalidImageUploadException('Upload path not writable.')); - } - - // Check for minimum dimensions - list($imageWidth, $imageHeight) = getimagesize($imagePath); - if(!$imageWidth || !$imageHeight || $imageWidth < ARTWORK_IMAGE_MINIMUM_WIDTH || $imageHeight < ARTWORK_IMAGE_MINIMUM_HEIGHT){ - $error->Add(new Exceptions\ArtworkImageDimensionsTooSmallException()); + catch(Exceptions\ArtworkNotFoundException){ + // No duplicates found, continue. } } - if($imagePath !== null && $this->MimeType === null && !$error->Has('Exceptions\InvalidImageUploadException')){ - // Only notify of wrong mimetype if there are no other problem with the uploaded image + if($isImageRequired){ + if($imagePath === null){ + $error->Add(new Exceptions\InvalidImageUploadException('An image is required.')); + } + else{ + if(!is_writable(WEB_ROOT . COVER_ART_UPLOAD_PATH)){ + $error->Add(new Exceptions\InvalidImageUploadException('Upload path not writable.')); + } + + // Check for minimum dimensions. + list($imageWidth, $imageHeight) = getimagesize($imagePath); + if(!$imageWidth || !$imageHeight || $imageWidth < ARTWORK_IMAGE_MINIMUM_WIDTH || $imageHeight < ARTWORK_IMAGE_MINIMUM_HEIGHT){ + $error->Add(new Exceptions\ArtworkImageDimensionsTooSmallException()); + } + } + } + + if(!isset($this->MimeType)){ $error->Add(new Exceptions\InvalidMimeTypeException()); } @@ -520,6 +543,12 @@ class Artwork{ } } + public function ImplodeTags(): string{ + $tags = $this->Tags ?? []; + $tags = array_column($tags, 'Name'); + return trim(implode(', ', $tags)); + } + /** * @throws Exceptions\InvalidUrlException * @throws Exceptions\InvalidPageScanUrlException @@ -661,11 +690,12 @@ class Artwork{ * @throws Exceptions\InvalidImageUploadException */ public function Create(?string $imagePath = null): void{ - $this->MimeType = Enums\ImageMimeType::FromFile($imagePath); + $this->MimeType = Enums\ImageMimeType::FromFile($imagePath) ?? throw new Exceptions\InvalidImageUploadException(); $this->Validate($imagePath, true); $this->Created = NOW; + $this->Updated = NOW; $tags = []; foreach($this->Tags as $artworkTag){ @@ -677,7 +707,7 @@ class Artwork{ Db::Query(' INSERT into - Artworks (ArtistId, Name, UrlName, CompletedYear, CompletedYearIsCirca, Created, Status, SubmitterUserId, ReviewerUserId, MuseumUrl, + Artworks (ArtistId, Name, UrlName, CompletedYear, CompletedYearIsCirca, Created, Updated, Status, SubmitterUserId, ReviewerUserId, MuseumUrl, PublicationYear, PublicationYearPageUrl, CopyrightPageUrl, ArtworkPageUrl, IsPublishedInUs, EbookUrl, MimeType, Exception, Notes) values (?, @@ -698,9 +728,10 @@ class Artwork{ ?, ?, ?, + ?, ?) ', [$this->Artist->ArtistId, $this->Name, $this->UrlName, $this->CompletedYear, $this->CompletedYearIsCirca, - $this->Created, $this->Status, $this->SubmitterUserId, $this->ReviewerUserId, $this->MuseumUrl, $this->PublicationYear, $this->PublicationYearPageUrl, + $this->Created, $this->Updated, $this->Status, $this->SubmitterUserId, $this->ReviewerUserId, $this->MuseumUrl, $this->PublicationYear, $this->PublicationYearPageUrl, $this->CopyrightPageUrl, $this->ArtworkPageUrl, $this->IsPublishedInUs, $this->EbookUrl, $this->MimeType, $this->Exception, $this->Notes] ); @@ -726,16 +757,16 @@ class Artwork{ * @throws Exceptions\InvalidImageUploadException */ public function Save(?string $imagePath = null): void{ - $this->_UrlName = null; + unset($this->_UrlName); if($imagePath !== null){ - $this->MimeType = Enums\ImageMimeType::FromFile($imagePath); + $this->MimeType = Enums\ImageMimeType::FromFile($imagePath) ?? throw new Exceptions\InvalidImageUploadException(); // Manually set the updated timestamp, because if we only update the image and nothing else, the row's updated timestamp won't change automatically. $this->Updated = NOW; - $this->_ImageUrl = null; - $this->_ThumbUrl = null; - $this->_Thumb2xUrl = null; + unset($this->_ImageUrl); + unset($this->_ThumbUrl); + unset($this->_Thumb2xUrl); } $this->Validate($imagePath, false); @@ -828,8 +859,30 @@ class Artwork{ from Artworks where ArtworkId = ? ', [$this->ArtworkId]); + + try{ + unlink($this->ImageFsPath); + } + catch(\Safe\Exceptions\FilesystemException){ + // Pass. + } + + try{ + unlink($this->ThumbFsPath); + } + catch(\Safe\Exceptions\FilesystemException){ + // Pass. + } + + try{ + unlink($this->Thumb2xFsPath); + } + catch(\Safe\Exceptions\FilesystemException){ + // Pass. + } } + // *********** // ORM METHODS // *********** @@ -871,14 +924,10 @@ class Artwork{ public static function FromHttpPost(): Artwork{ $artwork = new Artwork(); - $artwork->Artist = new Artist(); - $artwork->Artist->Name = HttpInput::Str(POST, 'artist-name'); - $artwork->Artist->DeathYear = HttpInput::Int(POST, 'artist-year-of-death'); - - $artwork->Name = HttpInput::Str(POST, 'artwork-name'); + $artwork->Name = HttpInput::Str(POST, 'artwork-name') ?? ''; $artwork->CompletedYear = HttpInput::Int(POST, 'artwork-year'); - $artwork->CompletedYearIsCirca = HttpInput::Bool(POST, 'artwork-year-is-circa') ?? false; + $artwork->CompletedYearIsCirca = HttpInput::Bool(POST, 'artwork-completed-year-is-circa') ?? false; $artwork->Tags = HttpInput::Str(POST, 'artwork-tags') ?? []; $artwork->Status = Enums\ArtworkStatusType::tryFrom(HttpInput::Str(POST, 'artwork-status') ?? '') ?? Enums\ArtworkStatusType::Unverified; $artwork->EbookUrl = HttpInput::Str(POST, 'artwork-ebook-url'); @@ -893,4 +942,28 @@ class Artwork{ return $artwork; } + + public function FillFromHttpPost(): void{ + if(!isset($this->Artist)){ + $this->Artist = new Artist(); + } + + $this->Artist->FillFromHttpPost(); + + $this->PropertyFromHttp('Name'); + $this->PropertyFromHttp('CompletedYear'); + $this->PropertyFromHttp('CompletedYearIsCirca'); + $this->PropertyFromHttp('Status'); + $this->PropertyFromHttp('EbookUrl'); + $this->PropertyFromHttp('IsPublishedInUs'); + $this->PropertyFromHttp('PublicationYear'); + $this->PropertyFromHttp('PublicationYearPageUrl'); + $this->PropertyFromHttp('CopyrightPageUrl'); + $this->PropertyFromHttp('ArtworkPageUrl'); + $this->PropertyFromHttp('MuseumUrl'); + $this->PropertyFromHttp('Exception'); + $this->PropertyFromHttp('Notes'); + + $this->Tags = HttpInput::Str(POST, 'artwork-tags') ?? ''; // Converted from a string to an array via a setter. + } } diff --git a/lib/ArtworkTag.php b/lib/ArtworkTag.php index e3313bb6..0e3ae556 100644 --- a/lib/ArtworkTag.php +++ b/lib/ArtworkTag.php @@ -7,18 +7,20 @@ class ArtworkTag extends Tag{ $this->Type = Enums\TagType::Artwork; } + // ******* // GETTERS // ******* protected function GetUrl(): string{ - if($this->_Url === null){ + if(!isset($this->_Url)){ $this->_Url = '/artworks?query=' . Formatter::MakeUrlSafe($this->Name); } return $this->_Url; } + // ******* // METHODS // ******* @@ -29,20 +31,27 @@ class ArtworkTag extends Tag{ public function Validate(): void{ $error = new Exceptions\InvalidArtworkTagException($this->Name); - $this->Name = mb_strtolower(trim($this->Name)); - // Collapse spaces into one - $this->Name = preg_replace('/[\s]+/ius', ' ', $this->Name); + if(isset($this->Name)){ + $this->Name = mb_strtolower(trim($this->Name)); + // Collapse spaces into one + $this->Name = preg_replace('/[\s]+/ius', ' ', $this->Name); - if(strlen($this->Name) == 0){ - $error->Add(new Exceptions\InvalidArtworkTagNameException()); + if(strlen($this->Name) == 0){ + $error->Add(new Exceptions\InvalidArtworkTagNameException()); + } + + if(strlen($this->Name) > ARTWORK_MAX_STRING_LENGTH){ + $error->Add(new Exceptions\StringTooLongException('Artwork tag: '. $this->Name)); + } + + if(preg_match('/[^\sa-z0-9]/ius', $this->Name)){ + $error->Add(new Exceptions\InvalidArtworkTagNameException()); + } + + $this->UrlName = Formatter::MakeUrlSafe($this->Name); } - - if(strlen($this->Name) > ARTWORK_MAX_STRING_LENGTH){ - $error->Add(new Exceptions\StringTooLongException('Artwork tag: '. $this->Name)); - } - - if(preg_match('/[^\sa-z0-9]/ius', $this->Name)){ - $error->Add(new Exceptions\InvalidArtworkTagNameException()); + else{ + $error->Add(new Exceptions\ArtworkTagNameRequiredException()); } if($this->Type != Enums\TagType::Artwork){ @@ -61,10 +70,11 @@ class ArtworkTag extends Tag{ $this->Validate(); Db::Query(' - INSERT into Tags (Name, Type) + INSERT into Tags (Name, UrlName, Type) values (?, + ?, ?) - ', [$this->Name, $this->Type]); + ', [$this->Name, $this->UrlName, $this->Type]); $this->TagId = Db::GetLastInsertedId(); } diff --git a/lib/AtomFeed.php b/lib/AtomFeed.php index f3aa0887..1b2a8e02 100644 --- a/lib/AtomFeed.php +++ b/lib/AtomFeed.php @@ -19,18 +19,19 @@ class AtomFeed extends Feed{ $this->Stylesheet = SITE_URL . '/feeds/atom/style'; } + // ******* // METHODS // ******* protected function GetXmlString(): string{ - if($this->XmlString === null){ + if(!isset($this->_XmlString)){ $feed = Template::AtomFeed(['id' => $this->Id, 'url' => $this->Url, 'title' => $this->Title, 'subtitle' => $this->Subtitle, 'updated' => $this->Updated, 'entries' => $this->Entries]); - $this->XmlString = $this->CleanXmlString($feed); + $this->_XmlString = $this->CleanXmlString($feed); } - return $this->XmlString; + return $this->_XmlString; } public function SaveIfChanged(): bool{ @@ -58,7 +59,7 @@ class AtomFeed extends Feed{ $obj->Id = SITE_URL . $entry->Url; } else{ - $obj->Updated = $entry->Updated !== null ? $entry->Updated->format(Enums\DateTimeFormat::Iso->value) : ''; + $obj->Updated = $entry->Updated->format(Enums\DateTimeFormat::Iso->value); $obj->Id = $entry->Id; } $currentEntries[] = $obj; diff --git a/lib/Collection.php b/lib/Collection.php index bb6af2a2..faf8bfbe 100644 --- a/lib/Collection.php +++ b/lib/Collection.php @@ -13,6 +13,11 @@ class Collection{ public ?Enums\CollectionType $Type = null; protected ?string $_Url = null; + + // ******* + // GETTERS + // ******* + protected function GetUrl(): string{ if($this->_Url === null){ $this->_Url = '/collections/' . $this->UrlName; @@ -21,6 +26,11 @@ class Collection{ return $this->_Url; } + + // *********** + // ORM METHODS + // *********** + public static function FromName(string $name): Collection{ $instance = new Collection(); $instance->Name = $name; @@ -45,6 +55,11 @@ class Collection{ return $result[0] ?? throw new Exceptions\CollectionNotFoundException();; } + + // ******* + // METHODS + // ******* + public function GetSortedName(): string{ return preg_replace('/^(the|and|a|)\s/ius', '', $this->Name); } diff --git a/lib/CollectionMembership.php b/lib/CollectionMembership.php index 8df0b276..d13bb00c 100644 --- a/lib/CollectionMembership.php +++ b/lib/CollectionMembership.php @@ -5,9 +5,10 @@ class CollectionMembership{ use Traits\Accessor; - public ?int $EbookId = null; - public ?int $CollectionId = null; + public int $EbookId; + public int $CollectionId; public ?int $SequenceNumber = null; - public ?int $SortOrder = null; - protected ?Collection $_Collection = null; + public int $SortOrder; + + protected Collection $_Collection; } diff --git a/lib/Contributor.php b/lib/Contributor.php index f02a72e9..1f69b20f 100644 --- a/lib/Contributor.php +++ b/lib/Contributor.php @@ -1,11 +1,8 @@ Name = str_replace('\'', '’', $name); - $instance->UrlName = Formatter::MakeUrlSafe($name); - $instance->SortName = $sortName; - $instance->FullName = $fullName; - $instance->WikipediaUrl = $wikipediaUrl; - $instance->MarcRole = $marcRole; - $instance->NacoafUrl = $nacoafUrl; - return $instance; - } + // ******* + // METHODS + // ******* /** * @throws Exceptions\ValidationException diff --git a/lib/DonationDrive.php b/lib/DonationDrive.php index 0840a6b0..9e689b3b 100644 --- a/lib/DonationDrive.php +++ b/lib/DonationDrive.php @@ -18,15 +18,10 @@ class DonationDrive{ public function __construct(public string $Name, public DateTimeImmutable $Start, public DateTimeImmutable $End, public int $BaseTargetDonationCount, public int $StretchTargetDonationCount){ } - public static function GetByIsRunning(): ?DonationDrive{ - foreach(DONATION_DRIVE_DATES as $donationDrive){ - if(NOW > $donationDrive->Start && NOW < $donationDrive->End){ - return $donationDrive; - } - } - return null; - } + // ******* + // GETTERS + // ******* protected function GetDonationCount(): int{ if(!isset($this->_DonationCount)){ @@ -92,4 +87,19 @@ class DonationDrive{ return $this->_IsStretchEnabled; } + + + // *********** + // ORM METHODS + // *********** + + public static function GetByIsRunning(): ?DonationDrive{ + foreach(DONATION_DRIVE_DATES as $donationDrive){ + if(NOW > $donationDrive->Start && NOW < $donationDrive->End){ + return $donationDrive; + } + } + + return null; + } } diff --git a/lib/Ebook.php b/lib/Ebook.php index e4c77a8f..46105bf1 100644 --- a/lib/Ebook.php +++ b/lib/Ebook.php @@ -46,7 +46,7 @@ use function Safe\shell_exec; class Ebook{ use Traits\Accessor; - public ?int $EbookId = null; + public int $EbookId; public string $Identifier; public string $WwwFilesystemPath; public string $RepoFilesystemPath; @@ -56,12 +56,12 @@ class Ebook{ public ?string $KepubUrl = null; public ?string $Azw3Url = null; public ?string $DistCoverUrl = null; - public ?string $Title = null; + public string $Title; public ?string $FullTitle = null; public ?string $AlternateTitle = null; - public ?string $Description = null; - public ?string $LongDescription = null; - public ?string $Language = null; + public string $Description; + public string $LongDescription; + public string $Language; public int $WordCount; public float $ReadingEase; public ?string $GitHubUrl = null; @@ -71,47 +71,48 @@ class Ebook{ public DateTimeImmutable $Created; public DateTimeImmutable $Updated; public ?int $TextSinglePageByteCount = null; + /** @var array $_GitCommits */ - protected $_GitCommits = null; + protected array $_GitCommits; /** @var array $_Tags */ - protected $_Tags = null; + protected array $_Tags; /** @var array $_LocSubjects */ - protected $_LocSubjects = null; + protected array $_LocSubjects; /** @var array $_CollectionMemberships */ - protected $_CollectionMemberships = null; + protected array $_CollectionMemberships; /** @var array $_Sources */ - protected $_Sources = null; + protected array $_Sources; /** @var array $_Authors */ - protected $_Authors = null; + protected array $_Authors; /** @var array $_Illustrators */ - protected $_Illustrators = null; + protected array $_Illustrators; /** @var array $_Translators */ - protected $_Translators = null; + protected array$_Translators; /** @var array $_Contributors */ - protected $_Contributors = null; + protected array $_Contributors; /** @var ?array $_TocEntries */ - protected $_TocEntries = null; // A list of non-Roman ToC entries ONLY IF the work has the 'se:is-a-collection' metadata element, null otherwise. - protected ?string $_Url = null; - protected ?bool $_HasDownloads = null; - protected ?string $_UrlSafeIdentifier = null; - protected ?string $_HeroImageUrl = null; - protected ?string $_HeroImageAvifUrl = null; - protected ?string $_HeroImage2xUrl = null; - protected ?string $_HeroImage2xAvifUrl = null; - protected ?string $_CoverImageUrl = null; - protected ?string $_CoverImageAvifUrl = null; - protected ?string $_CoverImage2xUrl = null; - protected ?string $_CoverImage2xAvifUrl = null; - protected ?string $_ReadingEaseDescription = null; - protected ?string $_ReadingTime = null; - protected ?string $_AuthorsHtml = null; - protected ?string $_AuthorsUrl = null; // This is a single URL even if there are multiple authors; for example, /ebooks/karl-marx_friedrich-engels/ - protected ?string $_ContributorsHtml = null; - protected ?string $_TitleWithCreditsHtml = null; - protected ?string $_TextUrl = null; - protected ?string $_TextSinglePageUrl = null; - protected ?string $_TextSinglePageSizeFormatted = null; - protected ?string $_IndexableText = null; + protected ?array $_TocEntries = null; // A list of non-Roman ToC entries *only if* the work has the `se:is-a-collection` metadata element; `null` otherwise. + protected string $_Url; + protected bool $_HasDownloads; + protected string $_UrlSafeIdentifier; + protected string $_HeroImageUrl; + protected string $_HeroImageAvifUrl; + protected string $_HeroImage2xUrl; + protected string $_HeroImage2xAvifUrl; + protected string $_CoverImageUrl; + protected string $_CoverImageAvifUrl; + protected string $_CoverImage2xUrl; + protected string $_CoverImage2xAvifUrl; + protected string $_ReadingEaseDescription; + protected string $_ReadingTime; + protected string $_AuthorsHtml; + protected string $_AuthorsUrl; // This is a single URL even if there are multiple authors; for example, `/ebooks/karl-marx_friedrich-engels/`. + protected string $_ContributorsHtml; + protected string $_TitleWithCreditsHtml; + protected string $_TextUrl; + protected string $_TextSinglePageUrl; + protected string $_TextSinglePageSizeFormatted; + protected string $_IndexableText; // ******* // GETTERS @@ -121,7 +122,7 @@ class Ebook{ * @return array */ protected function GetGitCommits(): array{ - if($this->_GitCommits === null){ + if(!isset($this->_GitCommits)){ $this->_GitCommits = Db::Query(' SELECT * from GitCommits @@ -137,7 +138,7 @@ class Ebook{ * @return array */ protected function GetTags(): array{ - if($this->_Tags === null){ + if(!isset($this->_Tags)){ $this->_Tags = Db::Query(' SELECT t.* from Tags t @@ -154,7 +155,7 @@ class Ebook{ * @return array */ protected function GetLocSubjects(): array{ - if($this->_LocSubjects === null){ + if(!isset($this->_LocSubjects)){ $this->_LocSubjects = Db::Query(' SELECT l.* from LocSubjects l @@ -171,7 +172,7 @@ class Ebook{ * @return array */ protected function GetCollectionMemberships(): array{ - if($this->_CollectionMemberships === null){ + if(!isset($this->_CollectionMemberships)){ $this->_CollectionMemberships = Db::Query(' SELECT * from CollectionEbooks @@ -187,7 +188,7 @@ class Ebook{ * @return array */ protected function GetSources(): array{ - if($this->_Sources === null){ + if(!isset($this->_Sources)){ $this->_Sources = Db::Query(' SELECT * from EbookSources @@ -203,7 +204,7 @@ class Ebook{ * @return array */ protected function GetAuthors(): array{ - if($this->_Authors === null){ + if(!isset($this->_Authors)){ $this->_Authors = Db::Query(' SELECT * from Contributors @@ -220,7 +221,7 @@ class Ebook{ * @return array */ protected function GetIllustrators(): array{ - if($this->_Illustrators === null){ + if(!isset($this->_Illustrators)){ $this->_Illustrators = Db::Query(' SELECT * from Contributors @@ -237,7 +238,7 @@ class Ebook{ * @return array */ protected function GetTranslators(): array{ - if($this->_Translators === null){ + if(!isset($this->_Translators)){ $this->_Translators = Db::Query(' SELECT * from Contributors @@ -254,7 +255,7 @@ class Ebook{ * @return array */ protected function GetContributors(): array{ - if($this->_Contributors === null){ + if(!isset($this->_Contributors)){ $this->_Contributors = Db::Query(' SELECT * from Contributors @@ -268,10 +269,10 @@ class Ebook{ } /** - * @return array + * @return ?array */ - protected function GetTocEntries(): array{ - if($this->_TocEntries === null){ + protected function GetTocEntries(): ?array{ + if(!isset($this->_TocEntries)){ $this->_TocEntries = []; $result = Db::Query(' @@ -279,18 +280,22 @@ class Ebook{ from TocEntries where EbookId = ? order by SortOrder asc - ', [$this->EbookId], stdClass::class); + ', [$this->EbookId]); foreach($result as $row){ $this->_TocEntries[] = $row->TocEntry; } + + if(sizeof($this->_TocEntries) == 0){ + $this->_TocEntries = null; + } } return $this->_TocEntries; } protected function GetUrl(): string{ - if($this->_Url === null){ + if(!isset($this->_Url)){ $this->_Url = str_replace(WEB_ROOT, '', $this->WwwFilesystemPath); } @@ -298,7 +303,7 @@ class Ebook{ } protected function GetHasDownloads(): bool{ - if($this->_HasDownloads === null){ + if(!isset($this->_HasDownloads)){ $this->_HasDownloads = $this->EpubUrl || $this->AdvancedEpubUrl || $this->KepubUrl || $this->Azw3Url; } @@ -306,7 +311,7 @@ class Ebook{ } protected function GetUrlSafeIdentifier(): string{ - if($this->_UrlSafeIdentifier === null){ + if(!isset($this->_UrlSafeIdentifier)){ $this->_UrlSafeIdentifier = str_replace(['url:https://standardebooks.org/ebooks/', '/'], ['', '_'], $this->Identifier); } @@ -318,7 +323,7 @@ class Ebook{ } protected function GetHeroImageUrl(): string{ - if($this->_HeroImageUrl === null){ + if(!isset($this->_HeroImageUrl)){ $this->_HeroImageUrl = '/images/covers/' . $this->UrlSafeIdentifier . '-' . $this->GetLatestCommitHash() . '-hero.jpg'; } @@ -326,7 +331,7 @@ class Ebook{ } protected function GetHeroImageAvifUrl(): ?string{ - if($this->_HeroImageAvifUrl === null){ + if(!isset($this->_HeroImageAvifUrl)){ if(file_exists(WEB_ROOT . '/images/covers/' . $this->UrlSafeIdentifier . '-hero.avif')){ $this->_HeroImageAvifUrl = '/images/covers/' . $this->UrlSafeIdentifier . '-' . $this->GetLatestCommitHash() . '-hero.avif'; } @@ -336,7 +341,7 @@ class Ebook{ } protected function GetHeroImage2xUrl(): string{ - if($this->_HeroImage2xUrl === null){ + if(!isset($this->_HeroImage2xUrl)){ $this->_HeroImage2xUrl = '/images/covers/' . $this->UrlSafeIdentifier . '-' . $this->GetLatestCommitHash() . '-hero@2x.jpg'; } @@ -344,7 +349,7 @@ class Ebook{ } protected function GetHeroImage2xAvifUrl(): ?string{ - if($this->_HeroImage2xAvifUrl === null){ + if(!isset($this->_HeroImage2xAvifUrl)){ if(file_exists(WEB_ROOT . '/images/covers/' . $this->UrlSafeIdentifier . '-hero@2x.avif')){ $this->_HeroImage2xAvifUrl = '/images/covers/' . $this->UrlSafeIdentifier . '-' . $this->GetLatestCommitHash() . '-hero@2x.avif'; } @@ -354,7 +359,7 @@ class Ebook{ } protected function GetCoverImageUrl(): string{ - if($this->_CoverImageUrl === null){ + if(!isset($this->_CoverImageUrl)){ $this->_CoverImageUrl = '/images/covers/' . $this->UrlSafeIdentifier . '-' . $this->GetLatestCommitHash() . '-cover.jpg'; } @@ -362,7 +367,7 @@ class Ebook{ } protected function GetCoverImageAvifUrl(): ?string{ - if($this->_CoverImageAvifUrl === null){ + if(!isset($this->_CoverImageAvifUrl)){ if(file_exists(WEB_ROOT . '/images/covers/' . $this->UrlSafeIdentifier . '-cover.avif')){ $this->_CoverImageAvifUrl = '/images/covers/' . $this->UrlSafeIdentifier . '-' . $this->GetLatestCommitHash() . '-cover.avif'; } @@ -372,7 +377,7 @@ class Ebook{ } protected function GetCoverImage2xUrl(): string{ - if($this->_CoverImage2xUrl === null){ + if(!isset($this->_CoverImage2xUrl)){ $this->_CoverImage2xUrl = '/images/covers/' . $this->UrlSafeIdentifier . '-' . $this->GetLatestCommitHash() . '-cover@2x.jpg'; } @@ -380,7 +385,7 @@ class Ebook{ } protected function GetCoverImage2xAvifUrl(): ?string{ - if($this->_CoverImage2xAvifUrl === null){ + if(!isset($this->_CoverImage2xAvifUrl)){ if(file_exists(WEB_ROOT . '/images/covers/' . $this->UrlSafeIdentifier . '-cover@2x.avif')){ $this->_CoverImage2xAvifUrl = '/images/covers/' . $this->UrlSafeIdentifier . '-' . $this->GetLatestCommitHash() . '-cover@2x.avif'; } @@ -390,7 +395,7 @@ class Ebook{ } protected function GetReadingEaseDescription(): string{ - if($this->_ReadingEaseDescription === null){ + if(!isset($this->_ReadingEaseDescription)){ if($this->ReadingEase > 89){ $this->_ReadingEaseDescription = 'very easy'; } @@ -418,7 +423,7 @@ class Ebook{ } protected function GetReadingTime(): string{ - if($this->_ReadingTime === null){ + if(!isset($this->_ReadingTime)){ $readingTime = ceil($this->WordCount / AVERAGE_READING_WORDS_PER_MINUTE); $this->_ReadingTime = (string)$readingTime; @@ -449,7 +454,7 @@ class Ebook{ } protected function GetAuthorsHtml(): string{ - if($this->_AuthorsHtml === null){ + if(!isset($this->_AuthorsHtml)){ $this->_AuthorsHtml = Ebook::GenerateContributorList($this->Authors, true); } @@ -457,7 +462,7 @@ class Ebook{ } protected function GetAuthorsUrl(): string{ - if($this->_AuthorsUrl === null){ + if(!isset($this->_AuthorsUrl)){ $this->_AuthorsUrl = preg_replace('|url:https://standardebooks.org/ebooks/([^/]+)/.*|ius', '/ebooks/\1', $this->Identifier); } @@ -465,7 +470,7 @@ class Ebook{ } protected function GetContributorsHtml(): string{ - if($this->_ContributorsHtml === null){ + if(!isset($this->_ContributorsHtml)){ $this->_ContributorsHtml = ''; if(sizeof($this->Contributors) > 0){ $this->_ContributorsHtml .= ' with ' . Ebook::GenerateContributorList($this->Contributors, false) . ';'; @@ -492,7 +497,7 @@ class Ebook{ } protected function GetTitleWithCreditsHtml(): string{ - if($this->_TitleWithCreditsHtml === null){ + if(!isset($this->_TitleWithCreditsHtml)){ $titleContributors = ''; if(sizeof($this->Contributors) > 0){ $titleContributors .= '. With ' . Ebook::GenerateContributorList($this->Contributors, false); @@ -513,7 +518,7 @@ class Ebook{ } protected function GetTextUrl(): string{ - if($this->_TextUrl === null){ + if(!isset($this->_TextUrl)){ $this->_TextUrl = $this->Url . '/text'; } @@ -521,7 +526,7 @@ class Ebook{ } protected function GetTextSinglePageUrl(): string{ - if($this->_TextSinglePageUrl === null){ + if(!isset($this->_TextSinglePageUrl)){ $this->_TextSinglePageUrl = $this->Url . '/text/single-page'; } @@ -529,7 +534,7 @@ class Ebook{ } protected function GetTextSinglePageSizeFormatted(): string{ - if($this->_TextSinglePageSizeFormatted === null){ + if(!isset($this->_TextSinglePageSizeFormatted)){ $bytes = $this->TextSinglePageByteCount; $sizes = array('B', 'KB', 'MB', 'GB', 'TB', 'PB'); @@ -551,7 +556,7 @@ class Ebook{ } protected function GetIndexableText(): string{ - if($this->_IndexableText === null){ + if(!isset($this->_IndexableText)){ $this->_IndexableText = $this->FullTitle ?? $this->Title; $this->_IndexableText .= ' ' . $this->AlternateTitle; @@ -585,6 +590,11 @@ class Ebook{ return $this->_IndexableText; } + + // *********** + // ORM METHODS + // *********** + /** * Construct an Ebook from a filesystem path. * @@ -714,8 +724,8 @@ class Ebook{ $xml->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/'); - $ebook->Title = Ebook::NullIfEmpty($xml->xpath('/package/metadata/dc:title')); - if($ebook->Title === null){ + $ebook->Title = trim(Ebook::NullIfEmpty($xml->xpath('/package/metadata/dc:title')) ?? ''); + if($ebook->Title == ''){ throw new Exceptions\EbookParsingException('Invalid element.'); } @@ -808,14 +818,16 @@ class Ebook{ $fileAs = (string)$author; } - $authors[] = Contributor::FromProperties( - (string)$author, - $fileAs, - Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:name.person.full-name"][@refines="#' . $id . '"]')), - Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.encyclopedia.wikipedia"][@refines="#' . $id . '"]')), - 'aut', - Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.authority.nacoaf"][@refines="#' . $id . '"]')) - ); + $contributor = new Contributor(); + $contributor->Name = (string)$author; + $contributor->UrlName = Formatter::MakeUrlSafe($contributor->Name); + $contributor->SortName = $fileAs; + $contributor->FullName = Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:name.person.full-name"][@refines="#' . $id . '"]')); + $contributor->WikipediaUrl = Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.encyclopedia.wikipedia"][@refines="#' . $id . '"]')); + $contributor->MarcRole = 'aut'; + $contributor->NacoafUrl = Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.authority.nacoaf"][@refines="#' . $id . '"]')); + + $authors[] = $contributor; } if(sizeof($authors) == 0){ throw new Exceptions\EbookParsingException('Invalid element.'); @@ -833,14 +845,14 @@ class Ebook{ } foreach($xml->xpath('/package/metadata/meta[ (@property="role" or @property="se:role") and @refines="#' . $id . '"]') ?: [] as $role){ - $c = Contributor::FromProperties( - (string)$contributor, - Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="file-as"][@refines="#' . $id . '"]')), - Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:name.person.full-name"][@refines="#' . $id . '"]')), - Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.encyclopedia.wikipedia"][@refines="#' . $id . '"]')), - $role, - Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.authority.nacoaf"][@refines="#' . $id . '"]')) - ); + $c = new Contributor(); + $c->Name = (string)$contributor; + $c->UrlName = Formatter::MakeUrlSafe($contributor->Name); + $c->SortName = Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="file-as"][@refines="#' . $id . '"]')); + $c->FullName = Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:name.person.full-name"][@refines="#' . $id . '"]')); + $c->WikipediaUrl = Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.encyclopedia.wikipedia"][@refines="#' . $id . '"]')); + $c->MarcRole = $role; + $c->NacoafUrl = Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.authority.nacoaf"][@refines="#' . $id . '"]')); // A display-sequence of 0 indicates that we don't want to process this contributor. $displaySequence = Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="display-seq"][@refines="#' . $id . '"]')); @@ -875,9 +887,9 @@ class Ebook{ $ebook->Contributors = $contributors; // Some basic data. - $ebook->Description = Ebook::NullIfEmpty($xml->xpath('/package/metadata/dc:description')); - $ebook->Language = Ebook::NullIfEmpty($xml->xpath('/package/metadata/dc:language')); - $ebook->LongDescription = Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:long-description"]')); + $ebook->Description = Ebook::NullIfEmpty($xml->xpath('/package/metadata/dc:description')) ?? ''; + $ebook->LongDescription = Ebook::NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:long-description"]')) ?? ''; + $ebook->Language = Ebook::NullIfEmpty($xml->xpath('/package/metadata/dc:language')) ?? ''; $wordCount = 0; $wordCountElement = $xml->xpath('/package/metadata/meta[@property="se:word-count"]'); @@ -1048,7 +1060,6 @@ class Ebook{ } } - $this->KepubUrl = trim($this->KepubUrl ?? ''); if($this->KepubUrl == ''){ $this->KepubUrl = null; @@ -1095,7 +1106,7 @@ class Ebook{ } if(isset($this->Title)){ - $this->Title = trim($this->Title ?? ''); + $this->Title = trim($this->Title); if($this->Title == ''){ $error->Add(new Exceptions\EbookTitleRequiredException()); @@ -1128,7 +1139,7 @@ class Ebook{ } if(isset($this->Description)){ - $this->Description = trim($this->Description ?? ''); + $this->Description = trim($this->Description); if($this->Description == ''){ $error->Add(new Exceptions\EbookDescriptionRequiredException()); @@ -1139,7 +1150,7 @@ class Ebook{ } if(isset($this->LongDescription)){ - $this->LongDescription = trim($this->LongDescription ?? ''); + $this->LongDescription = trim($this->LongDescription); if($this->LongDescription == ''){ $error->Add(new Exceptions\EbookLongDescriptionRequiredException()); @@ -1150,7 +1161,7 @@ class Ebook{ } if(isset($this->Language)){ - $this->Language = trim($this->Language ?? ''); + $this->Language = trim($this->Language); if($this->Language == ''){ $error->Add(new Exceptions\EbookLanguageRequiredException()); @@ -1520,8 +1531,9 @@ class Ebook{ return $string; } - /** + * If the given list of elements has an element that is not `''`, return that value; otherwise, return `null`. + * * @param array|false|null $elements */ private static function NullIfEmpty($elements): ?string{ @@ -1529,8 +1541,6 @@ class Ebook{ return null; } - // Helper function when getting values from SimpleXml. - // Checks if the result is set, and returns the value if so; if the value is the empty string, return null. if(isset($elements[0])){ $str = (string)$elements[0]; if($str !== ''){ @@ -1541,31 +1551,6 @@ class Ebook{ return null; } - // *********** - // ORM METHODS - // *********** - - /** - * @throws Exceptions\EbookNotFoundException - */ - public static function GetByIdentifier(?string $identifier): Ebook{ - if($identifier === null){ - throw new Exceptions\EbookNotFoundException('Invalid identifier: ' . $identifier); - } - - $result = Db::Query(' - SELECT * - from Ebooks - where Identifier = ? - ', [$identifier], Ebook::class); - - if(sizeof($result) == 0){ - throw new Exceptions\EbookNotFoundException('Invalid identifier: ' . $identifier); - } - - return $result[0]; - } - /** * @throws Exceptions\ValidationException */ @@ -1843,4 +1828,29 @@ class Ebook{ } } } + + // *********** + // ORM METHODS + // *********** + + /** + * @throws Exceptions\EbookNotFoundException + */ + public static function GetByIdentifier(?string $identifier): Ebook{ + if($identifier === null){ + throw new Exceptions\EbookNotFoundException('Invalid identifier: ' . $identifier); + } + + $result = Db::Query(' + SELECT * + from Ebooks + where Identifier = ? + ', [$identifier], Ebook::class); + + if(sizeof($result) == 0){ + throw new Exceptions\EbookNotFoundException('Invalid identifier: ' . $identifier); + } + + return $result[0]; + } } diff --git a/lib/EbookSource.php b/lib/EbookSource.php index e417c409..46b1e9d1 100644 --- a/lib/EbookSource.php +++ b/lib/EbookSource.php @@ -3,18 +3,20 @@ use Safe\DateTimeImmutable; class EbookSource{ - public ?int $EbookId = null; + public int $EbookId; public Enums\EbookSourceType $Type; public string $Url; - public ?int $SortOrder = null; + public int $SortOrder; + + + // ******* + // METHODS + // ******* /** * @throws Exceptions\ValidationException */ public function Validate(): void{ - /** @throws void */ - $now = new DateTimeImmutable(); - $error = new Exceptions\ValidationException(); if(!isset($this->EbookId)){ diff --git a/lib/EbookTag.php b/lib/EbookTag.php index c7814332..703e869b 100644 --- a/lib/EbookTag.php +++ b/lib/EbookTag.php @@ -4,25 +4,20 @@ class EbookTag extends Tag{ $this->Type = Enums\TagType::Ebook; } + // ******* // GETTERS // ******* - protected function GetUrlName(): string{ - if($this->_UrlName === null){ - $this->_UrlName = Formatter::MakeUrlSafe($this->Name); - } - - return $this->_UrlName; - } protected function GetUrl(): string{ - if($this->_Url === null){ + if(!isset($this->_Url)){ $this->_Url = '/subjects/' . $this->UrlName; } return $this->_Url; } + // ******* // METHODS // ******* @@ -43,6 +38,8 @@ class EbookTag extends Tag{ if(strlen($this->Name) > EBOOKS_MAX_STRING_LENGTH){ $error->Add(new Exceptions\StringTooLongException('Ebook tag: '. $this->Name)); } + + $this->UrlName = Formatter::MakeUrlSafe($this->Name); } else{ $error->Add(new Exceptions\EbookTagNameRequiredException()); @@ -72,6 +69,11 @@ class EbookTag extends Tag{ $this->TagId = Db::GetLastInsertedId(); } + + // *********** + // ORM METHODS + // *********** + /** * @throws Exceptions\ValidationException */ diff --git a/lib/Exceptions/ArtworkTagNameRequiredException.php b/lib/Exceptions/ArtworkTagNameRequiredException.php new file mode 100644 index 00000000..0380cf89 --- /dev/null +++ b/lib/Exceptions/ArtworkTagNameRequiredException.php @@ -0,0 +1,7 @@ +GetXmlString(); diff --git a/lib/GitCommit.php b/lib/GitCommit.php index 9ea2fa8f..01927938 100644 --- a/lib/GitCommit.php +++ b/lib/GitCommit.php @@ -2,11 +2,16 @@ use Safe\DateTimeImmutable; class GitCommit{ - public ?int $EbookId = null; + public int $EbookId; public DateTimeImmutable $Created; public string $Message; public string $Hash; + + // *********** + // ORM METHODS + // *********** + /** * @throws Exceptions\InvalidGitCommitException */ @@ -26,6 +31,11 @@ class GitCommit{ return $instance; } + + // ******* + // METHODS + // ******* + /** * @throws Exceptions\ValidationException */ diff --git a/lib/Image.php b/lib/Image.php index 253ab758..67c2f05b 100644 --- a/lib/Image.php +++ b/lib/Image.php @@ -62,7 +62,7 @@ class Image{ unlink($tempFilename); } catch(Exception){ - // Pass if file doesn't exist + // Pass if file doesn't exist. } } diff --git a/lib/Library.php b/lib/Library.php index 8fdda6fe..dcafc582 100644 --- a/lib/Library.php +++ b/lib/Library.php @@ -5,15 +5,13 @@ use function Safe\exec; use function Safe\filemtime; use function Safe\filesize; use function Safe\glob; -use function Safe\ksort; use function Safe\preg_replace; use function Safe\preg_split; -use function Safe\sprintf; -use function Safe\usort; class Library{ /** * @param array $tags + * * @return array{ebooks: array, ebooksCount: int} */ public static function FilterEbooks(string $query = null, array $tags = [], Enums\EbookSortType $sort = null, int $page = 1, int $perPage = EBOOKS_PER_PAGE): array{ @@ -102,11 +100,11 @@ class Library{ ', [$urlPath], Ebook::class); } else{ - // Multiple authors, e.g., karl-marx_friedrich-engels + // Multiple authors, e.g., `karl-marx_friedrich-engels`. $authors = explode('_', $urlPath); $params = $authors; - $params[] = sizeof($authors); // The number of authors in the URL must match the number of Contributor records. + $params[] = sizeof($authors); // The number of authors in the URL must match the number of `Contributor` records. return Db::Query(' SELECT e.* @@ -123,6 +121,7 @@ class Library{ /** * @return array + * * @throws Exceptions\AppException */ public static function GetEbookCollections(): array{ @@ -135,7 +134,10 @@ class Library{ if($collator === null){ throw new Exceptions\AppException('Couldn\'t create collator object when getting collections.'); } - usort($collections, function($a, $b) use($collator){ return $collator->compare($a->GetSortedName(), $b->GetSortedName()); }); + usort($collections, function(Collection $a, Collection $b) use($collator){ + $result = $collator->compare($a->GetSortedName(), $b->GetSortedName()); + return $result === false ? 0 : $result; + }); return $collections; } @@ -344,6 +346,7 @@ class Library{ /** * @return array + * * @throws Exceptions\ArtistNotFoundException */ public static function GetArtworksByArtist(?string $artistUrlName, ?string $status, ?int $submitterUserId): array{ @@ -451,7 +454,8 @@ class Library{ /** * @param array $items - * @return array>> + * + * @return array */ private static function SortBulkDownloads(array $items): array{ // This sorts our items in a special order, epub first and advanced epub last @@ -481,7 +485,8 @@ class Library{ } /** - * @return array>> + * @return array{'months': array>, 'subjects': array, 'collections': array, 'authors': array} + * * @throws Exceptions\AppException */ public static function RebuildBulkDownloadsCache(): array{ @@ -489,6 +494,7 @@ class Library{ if($collator === null){ throw new Exceptions\AppException('Couldn\'t create collator object when rebuilding bulk download cache.'); } + /** @var array> $months */ $months = []; $subjects = []; $collections = []; @@ -509,6 +515,8 @@ class Library{ catch(\Exception){ throw new Exceptions\AppException('Couldn\'t parse date on bulk download object.'); } + + /** @var string $year Required to satisfy PHPStan */ $year = $date->format('Y'); $month = $date->format('F'); @@ -533,7 +541,10 @@ class Library{ foreach(glob(WEB_ROOT . '/bulk-downloads/collections/*/', GLOB_NOSORT) as $dir){ $collections[] = self::FillBulkDownloadObject($dir, 'collections', '/collections'); } - usort($collections, function($a, $b) use($collator){ return $collator->compare($a->LabelSort, $b->LabelSort); }); + usort($collections, function(stdClass $a, stdClass $b) use($collator): int{ + $result = $collator->compare($a->LabelSort, $b->LabelSort); + return $result === false ? 0 : $result; + }); apcu_store('bulk-downloads-collections', $collections, 43200); // 12 hours @@ -541,7 +552,10 @@ class Library{ foreach(glob(WEB_ROOT . '/bulk-downloads/authors/*/', GLOB_NOSORT) as $dir){ $authors[] = self::FillBulkDownloadObject($dir, 'authors', '/ebooks'); } - usort($authors, function($a, $b) use($collator){ return $collator->compare($a->LabelSort, $b->LabelSort); }); + usort($authors, function(stdClass $a, stdClass $b) use($collator): int{ + $result = $collator->compare($a->LabelSort, $b->LabelSort); + return $result === false ? 0 : $result; + }); apcu_store('bulk-downloads-authors', $authors, 43200); // 12 hours @@ -549,7 +563,8 @@ class Library{ } /** - * @return array>> + * @return array + * * @throws Exceptions\AppException */ public static function RebuildFeedsCache(?string $returnType = null, ?string $returnClass = null): ?array{ @@ -584,7 +599,10 @@ class Library{ $feeds[] = $obj; } - usort($feeds, function($a, $b) use($collator){ return $collator->compare($a->LabelSort, $b->LabelSort); }); + usort($feeds, function(stdClass $a, stdClass $b) use($collator): int{ + $result = $collator->compare($a->LabelSort, $b->LabelSort); + return $result === false ? 0 : $result; + }); if($type == $returnType && $class == $returnClass){ $retval = $feeds; diff --git a/lib/LocSubject.php b/lib/LocSubject.php index ce161e18..c7354630 100644 --- a/lib/LocSubject.php +++ b/lib/LocSubject.php @@ -3,6 +3,10 @@ class LocSubject{ public int $LocSubjectId; public string $Name; + // ******* + // METHODS + // ******* + /** * @throws Exceptions\ValidationException */ diff --git a/lib/Log.php b/lib/Log.php index 4c805597..d3204e88 100644 --- a/lib/Log.php +++ b/lib/Log.php @@ -1,5 +1,4 @@ _Url === null){ + if(!isset($this->_Url)){ $this->_Url = '/newsletter/subscriptions/' . $this->User->Uuid; } diff --git a/lib/OpdsAcquisitionFeed.php b/lib/OpdsAcquisitionFeed.php index ac4c79fa..9c775b5e 100644 --- a/lib/OpdsAcquisitionFeed.php +++ b/lib/OpdsAcquisitionFeed.php @@ -13,10 +13,10 @@ class OpdsAcquisitionFeed extends OpdsFeed{ // ******* protected function GetXmlString(): string{ - if($this->XmlString === null){ - $this->XmlString = $this->CleanXmlString(Template::OpdsAcquisitionFeed(['id' => $this->Id, 'url' => $this->Url, 'title' => $this->Title, 'parentUrl' => $this->Parent ? $this->Parent->Url : null, 'updated' => $this->Updated, 'isCrawlable' => $this->IsCrawlable, 'subtitle' => $this->Subtitle, 'entries' => $this->Entries])); + if(!isset($this->_XmlString)){ + $this->_XmlString = $this->CleanXmlString(Template::OpdsAcquisitionFeed(['id' => $this->Id, 'url' => $this->Url, 'title' => $this->Title, 'parentUrl' => $this->Parent ? $this->Parent->Url : null, 'updated' => $this->Updated, 'isCrawlable' => $this->IsCrawlable, 'subtitle' => $this->Subtitle, 'entries' => $this->Entries])); } - return $this->XmlString; + return $this->_XmlString; } } diff --git a/lib/OpdsFeed.php b/lib/OpdsFeed.php index 66c699f0..d400122e 100644 --- a/lib/OpdsFeed.php +++ b/lib/OpdsFeed.php @@ -36,7 +36,7 @@ abstract class OpdsFeed extends AtomFeed{ $this->Updated = $updated; - $this->XmlString = null; + unset($this->_XmlString); file_put_contents($this->Path, $this->GetXmlString()); // Do we have any parents of our own to update? diff --git a/lib/OpdsNavigationEntry.php b/lib/OpdsNavigationEntry.php index 44ccea13..5fd6dcf6 100644 --- a/lib/OpdsNavigationEntry.php +++ b/lib/OpdsNavigationEntry.php @@ -6,12 +6,12 @@ class OpdsNavigationEntry{ public string $Url; public string $Rel; public string $Type; - public ?DateTimeImmutable $Updated = null; + public DateTimeImmutable $Updated; public string $Description; public string $Title; public string $SortTitle; - public function __construct(string $title, string $description, string $url, ?DateTimeImmutable $updated, string $rel, string $type){ + public function __construct(string $title, string $description, string $url, DateTimeImmutable $updated, string $rel, string $type){ $this->Id = SITE_URL . $url; $this->Url = $url; $this->Rel = $rel; diff --git a/lib/OpdsNavigationFeed.php b/lib/OpdsNavigationFeed.php index 03aec048..f6a0c4da 100644 --- a/lib/OpdsNavigationFeed.php +++ b/lib/OpdsNavigationFeed.php @@ -38,10 +38,10 @@ class OpdsNavigationFeed extends OpdsFeed{ // ******* protected function GetXmlString(): string{ - if($this->XmlString === null){ - $this->XmlString = $this->CleanXmlString(Template::OpdsNavigationFeed(['id' => $this->Id, 'url' => $this->Url, 'title' => $this->Title, 'parentUrl' => $this->Parent ? $this->Parent->Url : null, 'updated' => $this->Updated, 'subtitle' => $this->Subtitle, 'entries' => $this->Entries])); + if(!isset($this->_XmlString)){ + $this->_XmlString = $this->CleanXmlString(Template::OpdsNavigationFeed(['id' => $this->Id, 'url' => $this->Url, 'title' => $this->Title, 'parentUrl' => $this->Parent ? $this->Parent->Url : null, 'updated' => $this->Updated, 'subtitle' => $this->Subtitle, 'entries' => $this->Entries])); } - return $this->XmlString; + return $this->_XmlString; } } diff --git a/lib/Patron.php b/lib/Patron.php index 381224b1..1d95137a 100644 --- a/lib/Patron.php +++ b/lib/Patron.php @@ -7,14 +7,15 @@ use Safe\DateTimeImmutable; class Patron{ use Traits\Accessor; - public ?int $UserId = null; + public int $UserId; public bool $IsAnonymous; public ?string $AlternateName = null; public bool $IsSubscribedToEmails; - public ?DateTimeImmutable $Created = null; + public DateTimeImmutable $Created; public ?DateTimeImmutable $Ended = null; - protected ?User $_User = null; + protected User $_User; + // ******* // METHODS diff --git a/lib/Payment.php b/lib/Payment.php index e39a94ff..ee983797 100644 --- a/lib/Payment.php +++ b/lib/Payment.php @@ -19,6 +19,11 @@ class Payment{ protected ?User $_User = null; + + // ******* + // GETTERS + // ******* + /** * @throws Exceptions\UserNotFoundException */ diff --git a/lib/Poll.php b/lib/Poll.php index 061aff5d..830e71fd 100644 --- a/lib/Poll.php +++ b/lib/Poll.php @@ -1,6 +1,5 @@ $_PollItems */ - protected $_PollItems = null; - /** @var ?array $_PollItemsByWinner */ - protected $_PollItemsByWinner = null; - protected ?int $_VoteCount = null; + + protected string $_Url; + /** @var array $_PollItems */ + protected array $_PollItems; + /** @var array $_PollItemsByWinner */ + protected array $_PollItemsByWinner; + protected int $_VoteCount; // ******* @@ -31,7 +31,7 @@ class Poll{ // ******* protected function GetUrl(): string{ - if($this->_Url === null){ + if(!isset($this->_Url)){ $this->_Url = '/polls/' . $this->UrlName; } @@ -39,7 +39,7 @@ class Poll{ } protected function GetVoteCount(): int{ - if($this->_VoteCount === null){ + if(!isset($this->_VoteCount)){ $this->_VoteCount = Db::QueryInt(' SELECT count(*) from PollVotes pv @@ -55,7 +55,7 @@ class Poll{ * @return array */ protected function GetPollItems(): array{ - if($this->_PollItems === null){ + if(!isset($this->_PollItems)){ $this->_PollItems = Db::Query(' SELECT * from PollItems @@ -71,11 +71,11 @@ class Poll{ * @return array */ protected function GetPollItemsByWinner(): array{ - if($this->_PollItemsByWinner === null){ + if(!isset($this->_PollItemsByWinner)){ $this->_PollItemsByWinner = $this->PollItems; usort($this->_PollItemsByWinner, function(PollItem $a, PollItem $b){ return $a->VoteCount <=> $b->VoteCount; }); - $this->_PollItemsByWinner = array_reverse($this->_PollItemsByWinner ?? []); + $this->_PollItemsByWinner = array_reverse($this->_PollItemsByWinner); } return $this->_PollItemsByWinner; @@ -87,7 +87,6 @@ class Poll{ // ******* public function IsActive(): bool{ - /** @throws void */ if( ($this->Start !== null && $this->Start > NOW) || ($this->End !== null && $this->End < NOW)){ return false; } diff --git a/lib/PollItem.php b/lib/PollItem.php index 7dc27dee..1a841a9e 100644 --- a/lib/PollItem.php +++ b/lib/PollItem.php @@ -1,7 +1,4 @@ _VoteCount === null){ + if(!isset($this->_VoteCount)){ $this->_VoteCount = Db::QueryInt(' SELECT count(*) from PollVotes pv diff --git a/lib/PollVote.php b/lib/PollVote.php index c300ef43..b40a845e 100644 --- a/lib/PollVote.php +++ b/lib/PollVote.php @@ -8,14 +8,15 @@ use Safe\DateTimeImmutable; */ class PollVote{ use Traits\Accessor; + use Traits\PropertyFromHttp; - public ?int $UserId = null; + public int $UserId; public DateTimeImmutable $Created; - public ?int $PollItemId = null; + public int $PollItemId; - protected ?User $_User = null; - protected ?PollItem $_PollItem = null; - protected ?string $_Url = null; + protected User $_User; + protected PollItem $_PollItem; + protected string $_Url; // ******* @@ -23,7 +24,7 @@ class PollVote{ // ******* protected function GetUrl(): string{ - if($this->_Url === null){ + if(!isset($this->_Url)){ $this->_Url = $this->PollItem->Poll->Url . '/votes/' . $this->UserId; } @@ -41,26 +42,30 @@ class PollVote{ protected function Validate(): void{ $error = new Exceptions\InvalidPollVoteException(); - if($this->User === null){ + if(!isset($this->UserId)){ $error->Add(new Exceptions\UserNotFoundException()); } + else{ + try{ + // Attempt to get the `User`. + User::Get($this->UserId); + } + catch(Exceptions\UserNotFoundException $ex){ + $error->Add($ex); + } + } - if($this->PollItemId === null){ + if(!isset($this->PollItemId)){ $error->Add(new Exceptions\PollItemRequiredException()); } else{ - if($this->PollItem === null){ - $error->Add(new Exceptions\PollNotFoundException()); + try{ + if(!$this->PollItem->Poll->IsActive()){ + $error->Add(new Exceptions\PollClosedException()); + } } - else{ - if($this->PollItem->Poll === null){ - $error->Add(new Exceptions\PollNotFoundException()); - } - else{ - if(!$this->PollItem->Poll->IsActive()){ - $error->Add(new Exceptions\PollClosedException()); - } - } + catch(Exceptions\PollItemNotFoundException | Exceptions\PollNotFoundException){ + $error->Add(new Exceptions\PollNotFoundException()); } } @@ -97,10 +102,8 @@ class PollVote{ $this->UserId = $this->User->UserId; } catch(Exceptions\UserNotFoundException){ - // Can't validate patron email - do nothing for now, - // this will be caught later when we validate the vote during creation. - // Save the email in the User object in case we want it later, - // for example prefilling the 'create' form after an error is returned. + // Can't validate patron email - do nothing for now, this will be caught later when we validate the vote during creation. + // Save the email in the User object in case we want it later, for example prefilling the 'create' form after an error is returned. $this->User = new User(); $this->User->Email = $email; } @@ -135,4 +138,8 @@ class PollVote{ return $result[0] ?? throw new Exceptions\PollVoteNotFoundException(); } + + public function FillFromHttpPost(): void{ + $this->PropertyFromHttp('PollItemId'); + } } diff --git a/lib/RssFeed.php b/lib/RssFeed.php index 5783bab6..f12c3c05 100644 --- a/lib/RssFeed.php +++ b/lib/RssFeed.php @@ -21,13 +21,13 @@ class RssFeed extends Feed{ // ******* protected function GetXmlString(): string{ - if($this->XmlString === null){ + if(!isset($this->_XmlString)){ $feed = Template::RssFeed(['url' => $this->Url, 'description' => $this->Description, 'title' => $this->Title, 'entries' => $this->Entries, 'updated' => NOW]); - $this->XmlString = $this->CleanXmlString($feed); + $this->_XmlString = $this->CleanXmlString($feed); } - return $this->XmlString; + return $this->_XmlString; } public function SaveIfChanged(): bool{ diff --git a/lib/Session.php b/lib/Session.php index 3ca8a9bc..a04cec31 100644 --- a/lib/Session.php +++ b/lib/Session.php @@ -13,8 +13,8 @@ class Session{ public DateTimeImmutable $Created; public string $SessionId; - protected ?User $_User = null; - public ?string $_Url = null; + protected User $_User; + public string $_Url; // ******* @@ -22,7 +22,7 @@ class Session{ // ******* protected function GetUrl(): string{ - if($this->_Url === null){ + if(!isset($this->_Url)){ $this->_Url = '/sessions/' . $this->SessionId; } @@ -101,6 +101,11 @@ class Session{ setcookie('sessionid', $sessionId, ['expires' => intval((new DateTimeImmutable('+1 week'))->format(Enums\DateTimeFormat::UnixTimestamp->value)), 'path' => '/', 'domain' => SITE_DOMAIN, 'secure' => true, 'httponly' => false, 'samesite' => 'Lax']); // Expires in two weeks } + + // *********** + // ORM METHODS + // *********** + /** * @throws Exceptions\SessionNotFoundException */ diff --git a/lib/Tag.php b/lib/Tag.php index e8a0394b..b36e392a 100644 --- a/lib/Tag.php +++ b/lib/Tag.php @@ -1,14 +1,14 @@ User = new User(); * * isset($t->User); // true + * ``` * * @see */ diff --git a/lib/Traits/PropertyFromHttp.php b/lib/Traits/PropertyFromHttp.php new file mode 100644 index 00000000..3cf629c6 --- /dev/null +++ b/lib/Traits/PropertyFromHttp.php @@ -0,0 +1,154 @@ + 'bar']`: + * + * No changes + * + * - POST `['test-id' => '123']`: + * + * `$Id`: set to `123` + * + * - POST `['test-id' => '']`: + * + * `$Id`: unchanged, because it is not nullable + * + * - POST `['test-name' => 'bob']`: + * + * `$Name`: set to `"bob"` + * + * - POST `['test-name' => '']`: + * + * `$Name`: set to `""`, because it is not nullable + * + * - POST `['test-description' => 'abc']`: + * + * `$Description`: set to `abc` + * + * - POST `['test-description' => '']`: + * + * `$Description`: set to `null`, because it is nullable + * + * - POST `['test-chapter-number' => '456']`: + * + * `$ChapterNumber`: set to `456` + * + * - POST `['test-chapter-number' => '']`: + * + * `$ChapterNumber`: set to `null`, because an empty string sets nullable properties to `null`. + */ + public function PropertyFromHttp(string $property, \Enums\HttpVariableSource $set = POST, ?string $httpName = null): void{ + try{ + $rp = new \ReflectionProperty($this, $property); + } + catch(\ReflectionException){ + return; + } + + /** @var ?\ReflectionNamedType $propertyType */ + $propertyType = $rp->getType(); + if($propertyType !== null){ + if($httpName === null){ + $httpName = mb_strtolower(preg_replace('/([^^])([A-Z])/u', '\1-\2', $this::class . $property)); + } + + $vars = []; + + switch($set){ + case \Enums\HttpVariableSource::Get: + $vars = $_GET; + break; + case \Enums\HttpVariableSource::Post: + $vars = $_POST; + break; + case \Enums\HttpVariableSource::Cookie: + $vars = $_COOKIE; + break; + case \Enums\HttpVariableSource::Session: + $vars = $_SESSION; + break; + } + + // If the variable was not passed to us, don't change the property. + if(!isset($vars[$httpName])){ + return; + } + + $type = $propertyType->getName(); + $isPropertyNullable = $propertyType->allowsNull(); + $isPropertyEnum = is_a($type, 'BackedEnum', true); + $postValue = null; + + if($isPropertyEnum){ + $postValue = $type::tryFrom(\HttpInput::Str($set, $httpName) ?? ''); + } + else{ + switch($type){ + case 'int': + $postValue = \HttpInput::Int($set, $httpName); + break; + case 'bool': + $postValue = \HttpInput::Bool($set, $httpName); + break; + case 'float': + $postValue = \HttpInput::Dec($set, $httpName); + break; + case 'DateTimeImmutable': + case 'Safe\DateTimeImmutable': + $postValue = \HttpInput::Date($set, $httpName); + break; + case 'array': + $postValue = \HttpInput::Array($set, $httpName); + break; + case 'string': + $postValue = \HttpInput::Str($set, $httpName, true); + break; + } + } + + if($type == 'string'){ + if($isPropertyNullable){ + if($postValue == ''){ + $this->{$property} = null; + } + else{ + $this->{$property} = $postValue; + } + } + elseif($postValue !== null){ + $this->{$property} = $postValue; + } + } + elseif($isPropertyNullable || $postValue !== null){ + $this->{$property} = $postValue; + } + } + } +} diff --git a/lib/User.php b/lib/User.php index 78f85189..3574518e 100644 --- a/lib/User.php +++ b/lib/User.php @@ -17,10 +17,11 @@ class User{ public string $Uuid; public ?string $PasswordHash = null; - protected ?bool $_IsRegistered = null; - /** @var ?array $_Payments */ - protected $_Payments = null; - protected ?Benefits $_Benefits = null; + protected bool $_IsRegistered; + /** @var array $_Payments */ + protected array $_Payments; + protected Benefits $_Benefits; + // ******* // GETTERS @@ -30,7 +31,7 @@ class User{ * @return array */ protected function GetPayments(): array{ - if($this->_Payments === null){ + if(!isset($this->_Payments)){ $this->_Payments = Db::Query(' SELECT * from Payments @@ -43,7 +44,7 @@ class User{ } protected function GetBenefits(): Benefits{ - if($this->_Benefits === null){ + if(!isset($this->_Benefits)){ $result = Db::Query(' SELECT * from Benefits @@ -64,7 +65,7 @@ class User{ } protected function GetIsRegistered(): ?bool{ - if($this->_IsRegistered === null){ + if(!isset($this->_IsRegistered)){ // A user is "registered" if they have a benefits entry in the table. // This function will fill it out for us. $this->GetBenefits(); diff --git a/scripts/generate-feeds b/scripts/generate-feeds index 1bc6885b..aefc2a19 100755 --- a/scripts/generate-feeds +++ b/scripts/generate-feeds @@ -46,12 +46,7 @@ function CreateOpdsCollectionFeed(string $name, string $url, string $description usort($collections, function($a, $b) use($collator){ $result = $collator->compare($a['sortedname'], $b['sortedname']); - if($result === false){ - return 0; - } - else{ - return $result; - } + return $result === false ? 0 : $result; }); // Create the collections navigation document. diff --git a/scripts/process-pending-payments b/scripts/process-pending-payments index fc742b81..6be87dbf 100755 --- a/scripts/process-pending-payments +++ b/scripts/process-pending-payments @@ -208,7 +208,7 @@ try{ ) ){ // This payment is eligible for the Patrons Circle! - if($payment->User !== null){ + if($payment->UserId !== null && $payment->User !== null){ // Are we already a patron? if(!Db::QueryBool(' SELECT exists( diff --git a/templates/ArtworkForm.php b/templates/ArtworkForm.php index 6d40e958..64e42e7d 100644 --- a/templates/ArtworkForm.php +++ b/templates/ArtworkForm.php @@ -34,10 +34,10 @@ $isEditForm = $isEditForm ?? false; @@ -172,28 +172,28 @@ $isEditForm = $isEditForm ?? false; CanStatusBeChangedBy($GLOBALS['User'] ?? null) || $artwork->CanEbookUrlBeChangedBy($GLOBALS['User'] ?? null)){ ?> -
- Editor options - CanStatusBeChangedBy($GLOBALS['User'] ?? null)){ ?> - - - CanEbookUrlBeChangedBy($GLOBALS['User'] ?? null)){ ?> - - -
+
+ Editor options + CanStatusBeChangedBy($GLOBALS['User'] ?? null)){ ?> + + + CanEbookUrlBeChangedBy($GLOBALS['User'] ?? null)){ ?> + + +