diff --git a/composer.lock b/composer.lock index 331792f7..422c53d6 100644 --- a/composer.lock +++ b/composer.lock @@ -557,16 +557,16 @@ }, { "name": "symfony/finder", - "version": "v6.4.0", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "11d736e97f116ac375a81f96e662911a34cd50ce" + "reference": "511c48990be17358c23bf45c5d71ab85d40fb764" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/11d736e97f116ac375a81f96e662911a34cd50ce", - "reference": "11d736e97f116ac375a81f96e662911a34cd50ce", + "url": "https://api.github.com/repos/symfony/finder/zipball/511c48990be17358c23bf45c5d71ab85d40fb764", + "reference": "511c48990be17358c23bf45c5d71ab85d40fb764", "shasum": "" }, "require": { @@ -601,7 +601,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.0" + "source": "https://github.com/symfony/finder/tree/v6.4.7" }, "funding": [ { @@ -617,20 +617,20 @@ "type": "tidelift" } ], - "time": "2023-10-31T17:30:12+00:00" + "time": "2024-04-23T10:36:43+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", "shasum": "" }, "require": { @@ -644,9 +644,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -683,7 +680,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" }, "funding": [ { @@ -699,20 +696,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", "shasum": "" }, "require": { @@ -726,9 +723,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -766,7 +760,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" }, "funding": [ { @@ -782,20 +776,20 @@ "type": "tidelift" } ], - "time": "2023-07-28T09:04:16+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { @@ -803,9 +797,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -849,7 +840,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -865,20 +856,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", - "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d", "shasum": "" }, "require": { @@ -886,9 +877,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -928,7 +916,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0" }, "funding": [ { @@ -944,20 +932,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/process", - "version": "v6.4.2", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "c4b1ef0bc80533d87a2e969806172f1c2a980241" + "reference": "cdb1c81c145fd5aa9b0038bab694035020943381" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c4b1ef0bc80533d87a2e969806172f1c2a980241", - "reference": "c4b1ef0bc80533d87a2e969806172f1c2a980241", + "url": "https://api.github.com/repos/symfony/process/zipball/cdb1c81c145fd5aa9b0038bab694035020943381", + "reference": "cdb1c81c145fd5aa9b0038bab694035020943381", "shasum": "" }, "require": { @@ -989,7 +977,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.2" + "source": "https://github.com/symfony/process/tree/v6.4.7" }, "funding": [ { @@ -1005,7 +993,7 @@ "type": "tidelift" } ], - "time": "2023-12-22T16:42:54+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "thecodingmachine/safe", @@ -1150,16 +1138,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.10.56", + "version": "1.10.67", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "27816a01aea996191ee14d010f325434c0ee76fa" + "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/27816a01aea996191ee14d010f325434c0ee76fa", - "reference": "27816a01aea996191ee14d010f325434c0ee76fa", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/16ddbe776f10da6a95ebd25de7c1dbed397dc493", + "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493", "shasum": "" }, "require": { @@ -1202,13 +1190,9 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2024-01-15T10:43:00+00:00" + "time": "2024-04-16T07:22:02+00:00" }, { "name": "thecodingmachine/phpstan-safe-rule", diff --git a/config/phpstan/phpstan.neon b/config/phpstan/phpstan.neon index 50227612..f2612e52 100644 --- a/config/phpstan/phpstan.neon +++ b/config/phpstan/phpstan.neon @@ -30,3 +30,14 @@ parameters: - Emit404 - Emit403 - RedirectToLogin + exceptions: + check: + missingCheckedExceptionInThrows: true + tooWideThrowType: true + uncheckedExceptionRegexes: + -'#^Safe\\#' + uncheckedExceptionClasses: + - 'Exceptions\DatabaseQueryException' + - 'PDOException' + - 'TypeError' + - 'ValueError' diff --git a/config/sql/se/PollVotes.sql b/config/sql/se/PollVotes.sql index 1686d131..fd2229ad 100644 --- a/config/sql/se/PollVotes.sql +++ b/config/sql/se/PollVotes.sql @@ -1,6 +1,6 @@ CREATE TABLE `PollVotes` ( `UserId` int(10) unsigned NOT NULL, `PollItemId` int(10) unsigned NOT NULL, - `Created` datetime NOT NULL, + `Created` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), UNIQUE KEY `idxUnique` (`PollItemId`,`UserId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/lib/Artist.php b/lib/Artist.php index 93fadcf6..904f73f1 100644 --- a/lib/Artist.php +++ b/lib/Artist.php @@ -89,6 +89,7 @@ class Artist{ * @throws Exceptions\InvalidArtistException */ public function Validate(): void{ + /** @throws void */ $now = new DateTimeImmutable(); $thisYear = intval($now->format('Y')); @@ -136,6 +137,9 @@ class Artist{ return $result[0] ?? throw new Exceptions\ArtistNotFoundException();; } + /** + * @throws Exceptions\InvalidArtistException + */ public function Create(): void{ $this->Validate(); Db::Query(' @@ -149,7 +153,7 @@ class Artist{ } /** - * @throws Exceptions\ValidationException + * @throws Exceptions\InvalidArtistException */ public static function GetOrCreate(Artist $artist): Artist{ $result = Db::Query(' diff --git a/lib/Artwork.php b/lib/Artwork.php index 07482984..af45573f 100644 --- a/lib/Artwork.php +++ b/lib/Artwork.php @@ -1,5 +1,13 @@ _Tags; } + /** + * @throws Exceptions\InvalidUrlException + */ public function GetMuseum(): ?Museum{ if($this->_Museum === null){ try{ @@ -252,6 +263,9 @@ class Artwork{ return $this->_Dimensions; } + /** + * @throws Exceptions\AppException + */ protected function GetEbook(): ?Ebook{ if($this->_Ebook === null){ if($this->EbookUrl === null){ @@ -318,9 +332,10 @@ class Artwork{ } /** - * @throws Exceptions\ValidationException + * @throws Exceptions\InvalidArtworkException */ protected function Validate(?string $imagePath = null, bool $isImageRequired = true): void{ + /** @throws void */ $now = new DateTimeImmutable(); $thisYear = intval($now->format('Y')); $error = new Exceptions\InvalidArtworkException(); @@ -509,6 +524,10 @@ class Artwork{ } } + /** + * @throws Exceptions\InvalidUrlException + * @throws Exceptions\InvalidPageScanUrlException + */ public static function NormalizePageScanUrl(string $url): string{ $outputUrl = $url; @@ -621,6 +640,9 @@ class Artwork{ return $outputUrl; } + /** + * @throws Exceptions\InvalidImageUploadException + */ private function WriteImageAndThumbnails(string $imagePath): void{ exec('exiftool -quiet -overwrite_original -all= ' . escapeshellarg($imagePath)); copy($imagePath, $this->ImageFsPath); @@ -645,6 +667,7 @@ class Artwork{ $this->Validate($imagePath, true); + /** @throws void */ $this->Created = new DateTimeImmutable(); $tags = []; @@ -700,7 +723,10 @@ class Artwork{ } /** - * @throws Exceptions\ValidationException + * @throws Exceptions\InvalidArtworkException + * @throws Exceptions\InvalidArtistException + * @throws Exceptions\InvalidArtworkTagException + * @throws Exceptions\InvalidImageUploadException */ public function Save(?string $imagePath = null): void{ $this->_UrlName = null; @@ -710,6 +736,7 @@ class Artwork{ // Manually set the updated timestamp, because if we only update the image and nothing else, the row's // updated timestamp won't change automatically. + /** @throws void */ $this->Updated = new DateTimeImmutable(); $this->_ImageUrl = null; $this->_ThumbUrl = null; diff --git a/lib/ArtworkTag.php b/lib/ArtworkTag.php index b74cdffd..cfef1db0 100644 --- a/lib/ArtworkTag.php +++ b/lib/ArtworkTag.php @@ -1,4 +1,7 @@ Name); @@ -46,6 +49,9 @@ class ArtworkTag extends Tag{ } } + /** + * @throws InvalidArtworkTagException + */ public function Create(): void{ $this->Validate(); @@ -57,7 +63,7 @@ class ArtworkTag extends Tag{ } /** - * @throws Exceptions\ValidationException + * @throws Exceptions\InvalidArtworkTagException */ public static function GetOrCreate(ArtworkTag $artworkTag): ArtworkTag{ $result = Db::Query(' diff --git a/lib/AtomFeed.php b/lib/AtomFeed.php index d6b73fcb..52ba67f6 100644 --- a/lib/AtomFeed.php +++ b/lib/AtomFeed.php @@ -39,6 +39,7 @@ class AtomFeed extends Feed{ // Did we actually update the feed? If so, write to file and update the index if($this->HasChanged($this->Path)){ // Files don't match, save the file + /** @throws void */ $this->Updated = new DateTimeImmutable(); $this->Save(); return true; diff --git a/lib/DbConnection.php b/lib/DbConnection.php index cfefe638..49f95c2b 100644 --- a/lib/DbConnection.php +++ b/lib/DbConnection.php @@ -188,6 +188,7 @@ class DbConnection{ switch($metadata[$i]['native_type'] ?? null){ case 'DATETIME': case 'TIMESTAMP': + /** @throws void */ $object->{$metadata[$i]['name']} = new DateTimeImmutable($row[$i], new DateTimeZone('UTC')); break; diff --git a/lib/Ebook.php b/lib/Ebook.php index 6b185d11..3adf0b22 100644 --- a/lib/Ebook.php +++ b/lib/Ebook.php @@ -1,5 +1,7 @@ RepoFilesystemPath = preg_replace('/\.git$/ius', '', $this->RepoFilesystemPath); } - catch(Exception){ + catch(\Exception){ // We may get an exception from preg_replace if the passed repo wwwFilesystemPath contains invalid UTF-8 characters, whichis a common injection attack vector throw new Exceptions\EbookNotFoundException('Invalid repo filesystem path: ' . $this->RepoFilesystemPath); } @@ -128,11 +135,16 @@ class Ebook{ $bytes = @filesize($this->WwwFilesystemPath . '/text/single-page.xhtml'); $sizes = 'BKMGTP'; $factor = intval(floor((strlen((string)$bytes) - 1) / 3)); - $this->TextSinglePageSizeNumber = sprintf('%.1f', $bytes / pow(1024, $factor)); + try{ + $this->TextSinglePageSizeNumber = sprintf('%.1f', $bytes / pow(1024, $factor)); + } + catch(\DivisionByZeroError){ + $this->TextSinglePageSizeNumber = '0'; + } $this->TextSinglePageSizeUnit = $sizes[$factor] ?? ''; $this->TextSinglePageUrl = $this->Url . '/text/single-page'; } - catch(Exception){ + catch(\Exception){ // Single page file doesn't exist, just pass } @@ -206,7 +218,13 @@ class Ebook{ } // Now do some heavy XML lifting! - $xml = new SimpleXMLElement(str_replace('xmlns=', 'ns=', $rawMetadata)); + try{ + $xml = new SimpleXMLElement(str_replace('xmlns=', 'ns=', $rawMetadata)); + } + catch(\Exception $ex){ + throw new Exceptions\EbookParsingException($ex->getMessage()); + } + $xml->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/'); $this->Title = $this->NullIfEmpty($xml->xpath('/package/metadata/dc:title')); @@ -222,11 +240,13 @@ class Ebook{ $date = $xml->xpath('/package/metadata/dc:date') ?: []; if($date !== false && sizeof($date) > 0){ + /** @throws void */ $this->Created = new DateTimeImmutable((string)$date[0]); } $modifiedDate = $xml->xpath('/package/metadata/meta[@property="dcterms:modified"]') ?: []; if($modifiedDate !== false && sizeof($modifiedDate) > 0){ + /** @throws void */ $this->Updated = new DateTimeImmutable((string)$modifiedDate[0]); } @@ -240,7 +260,12 @@ class Ebook{ // Fill the ToC if necessary if($includeToc){ $this->TocEntries = []; - $tocDom = new SimpleXMLElement(str_replace('xmlns=', 'ns=', file_get_contents($wwwFilesystemPath . '/toc.xhtml'))); + try{ + $tocDom = new SimpleXMLElement(str_replace('xmlns=', 'ns=', file_get_contents($wwwFilesystemPath . '/toc.xhtml'))); + } + catch(\Exception $ex){ + throw new Exceptions\EbookParsingException($ex->getMessage()); + } $tocDom->registerXPathNamespace('epub', 'http://www.idpf.org/2007/ops'); foreach($tocDom->xpath('/html/body//nav[@epub:type="toc"]//a[not(contains(@epub:type, "z3998:roman")) and not(text() = "Titlepage" or text() = "Imprint" or text() = "Colophon" or text() = "Endnotes" or text() = "Uncopyright") and not(contains(@href, "halftitle"))]') ?: [] as $item){ $this->TocEntries[] = (string)$item; diff --git a/lib/Exceptions/InvalidGitCommitException.php b/lib/Exceptions/InvalidGitCommitException.php new file mode 100644 index 00000000..425dfee3 --- /dev/null +++ b/lib/Exceptions/InvalidGitCommitException.php @@ -0,0 +1,5 @@ +Created = new DateTimeImmutable('@' . $unixTimestamp); + try{ + $this->Created = new DateTimeImmutable('@' . $unixTimestamp); + } + catch(\Exception){ + throw new Exceptions\InvalidGitCommitException('Invalid timestamp for Git commit.'); + } $this->Message = $message; $this->Hash = $hash; } diff --git a/lib/Image.php b/lib/Image.php index 1c2f806b..fee13f0c 100644 --- a/lib/Image.php +++ b/lib/Image.php @@ -1,4 +1,8 @@ Path); + try{ + $imageDimensions = getimagesize($this->Path); + } + catch(\Safe\Exceptions\ImageException $ex){ + throw new Exceptions\InvalidImageUploadException($ex->getMessage()); + } $imageWidth = $imageDimensions[0]; $imageHeight = $imageDimensions[1]; if($imageHeight > $imageWidth){ $destinationHeight = $height; - $destinationWidth = intval($destinationHeight * ($imageWidth / $imageHeight)); + try{ + $destinationWidth = intval($destinationHeight * ($imageWidth / $imageHeight)); + } + catch(\DivisionByZeroError){ + $destinationWidth = 0; + } } else{ $destinationWidth = $width; - $destinationHeight = intval($destinationWidth * ($imageHeight / $imageWidth)); + try{ + $destinationHeight = intval($destinationWidth * ($imageHeight / $imageWidth)); + } + catch(\DivisionByZeroError){ + $destinationHeight = 0; + } } $srcImageHandle = $this->GetImageHandle(); diff --git a/lib/Library.php b/lib/Library.php index 2b9f9cd7..d09ac04a 100644 --- a/lib/Library.php +++ b/lib/Library.php @@ -1,5 +1,15 @@ $tags * @param EbookSort $sort * @return array + * @throws Exceptions\AppException */ public static function FilterEbooks(string $query = null, array $tags = [], EbookSort $sort = null){ $ebooks = Library::GetEbooks(); @@ -109,6 +120,7 @@ class Library{ /** * @return array + * @throws Exceptions\AppException */ public static function GetEbooks(): array{ // Get all ebooks, unsorted. @@ -117,6 +129,7 @@ class Library{ /** * @return array + * @throws Exceptions\AppException */ public static function GetEbooksByAuthor(string $wwwFilesystemPath): array{ return self::GetFromApcu('author-' . $wwwFilesystemPath); @@ -136,6 +149,7 @@ class Library{ /** * @return array + * @throws Exceptions\AppException */ public static function GetEbookCollections(): array{ return self::GetFromApcu('collections'); @@ -143,6 +157,7 @@ class Library{ /** * @return array + * @throws Exceptions\AppException */ public static function GetEbooksByCollection(string $collection): array{ // Do we have the tag's ebooks cached? @@ -151,6 +166,7 @@ class Library{ /** * @return array + * @throws Exceptions\AppException */ public static function GetTags(): array{ return self::GetFromApcu('tags'); @@ -303,8 +319,10 @@ class Library{ return ['artworks' => $artworks, 'artworksCount' => $artworksCount]; } + /** - * @return array + * @return array + * @throws Exceptions\ArtistNotFoundException */ public static function GetArtworksByArtist(?string $artistUrlName, ?string $status, ?int $submitterUserId): array{ if($artistUrlName === null){ @@ -346,8 +364,10 @@ class Library{ return $artworks; } + /** * @return array + * @throws Exceptions\AppException */ private static function GetFromApcu(string $variable): array{ $results = []; @@ -389,6 +409,7 @@ class Library{ /** * @return array + * @throws Exceptions\AppException */ public static function Search(string $query): array{ $ebooks = Library::GetEbooks(); @@ -432,6 +453,8 @@ class Library{ private static function FillBulkDownloadObject(string $dir, string $downloadType, string $urlRoot): stdClass{ $obj = new stdClass(); + + /** @throws void */ $now = new DateTimeImmutable(); // The count of ebooks in each file is stored as a filesystem attribute @@ -479,6 +502,7 @@ class Library{ $obj->ZipFiles[] = $zipFile; } + /** @throws void */ $obj->Updated = new DateTimeImmutable('@' . filemtime($files[0])); $obj->UpdatedString = $obj->Updated->format('M j'); // Add a period to the abbreviated month, but not if it's May (the only 3-letter month) @@ -526,6 +550,7 @@ class Library{ /** * @return array>> + * @throws Exceptions\AppException */ public static function RebuildBulkDownloadsCache(): array{ $collator = Collator::create('en_US'); // Used for sorting letters with diacritics like in author names @@ -546,7 +571,12 @@ class Library{ foreach($dirs as $dir){ $obj = self::FillBulkDownloadObject($dir, 'months', '/months'); - $date = new DateTimeImmutable($obj->Label . '-01'); + try{ + $date = new DateTimeImmutable($obj->Label . '-01'); + } + catch(\Exception){ + throw new Exceptions\AppException('Couldn\'t parse date on bulk download object.'); + } $year = $date->format('Y'); $month = $date->format('F'); @@ -588,6 +618,7 @@ class Library{ /** * @return array>> + * @throws Exceptions\AppException */ public static function RebuildFeedsCache(?string $returnType = null, ?string $returnClass = null): ?array{ $feedTypes = ['opds', 'atom', 'rss']; @@ -634,6 +665,9 @@ class Library{ return $retval; } + /** + * @throws Exceptions\AppException + */ public static function GetEbook(?string $ebookWwwFilesystemPath): ?Ebook{ if($ebookWwwFilesystemPath === null){ return null; @@ -649,6 +683,9 @@ class Library{ } } + /** + * @throws Exceptions\AppException + */ public static function RebuildCache(): void{ // We check a lockfile because this can be a long-running command. // We don't want to queue up a bunch of these in case someone is refreshing the index constantly. diff --git a/lib/Log.php b/lib/Log.php index ad55a2c9..201dcda4 100644 --- a/lib/Log.php +++ b/lib/Log.php @@ -33,6 +33,7 @@ class Log{ return; } + /** @throws void */ $now = new DateTimeImmutable(); fwrite($fp, $now->format('Y-m-d H:i:s') . "\t" . $this->RequestId . "\t" . $text . "\n"); diff --git a/lib/Museum.php b/lib/Museum.php index a1ae8edb..8dbb5902 100644 --- a/lib/Museum.php +++ b/lib/Museum.php @@ -1,4 +1,10 @@ Validate($expectedCaptcha, $receivedCaptcha); @@ -42,10 +50,18 @@ class NewsletterSubscription{ } catch(Exceptions\UserNotFoundException){ // User doesn't exist, create the user - $this->User->Create(); + + try{ + $this->User->Create(); + } + catch(Exceptions\UserExistsException){ + // User exists, pass + } } $this->UserId = $this->User->UserId; + + /** @throws void */ $this->Created = new DateTimeImmutable(); try{ @@ -66,6 +82,9 @@ class NewsletterSubscription{ $this->SendConfirmationEmail(); } + /** + * @throws Exceptions\InvalidNewsletterSubscription + */ public function Save(): void{ $this->Validate(); @@ -107,6 +126,10 @@ class NewsletterSubscription{ ', [$this->UserId]); } + + /** + * @throws Exceptions\InvalidNewsletterSubscription + */ public function Validate(?string $expectedCaptcha = null, ?string $receivedCaptcha = null): void{ $error = new Exceptions\InvalidNewsletterSubscription(); diff --git a/lib/OpdsFeed.php b/lib/OpdsFeed.php index f4d5ba8c..00dda257 100644 --- a/lib/OpdsFeed.php +++ b/lib/OpdsFeed.php @@ -50,6 +50,7 @@ class OpdsFeed extends AtomFeed{ if($this->HasChanged($this->Path)){ // Files don't match, save the file and update the parent navigation feed with the last updated timestamp + /** @throws void */ $this->Updated = new DateTimeImmutable(); if($this->Parent !== null){ diff --git a/lib/Patron.php b/lib/Patron.php index 92c4f391..81501089 100644 --- a/lib/Patron.php +++ b/lib/Patron.php @@ -21,6 +21,7 @@ class Patron{ // ******* public function Create(): void{ + /** @throws void */ $this->Created = new DateTimeImmutable(); Db::Query(' INSERT into Patrons (Created, UserId, IsAnonymous, AlternateName, IsSubscribedToEmails) diff --git a/lib/Payment.php b/lib/Payment.php index bf518714..e8ac4e81 100644 --- a/lib/Payment.php +++ b/lib/Payment.php @@ -1,4 +1,7 @@ UserId === null){ // Check if we have to create a new user in the database @@ -45,7 +51,13 @@ class Payment{ } catch(Exceptions\UserNotFoundException){ // User doesn't exist, create it now - $this->User->Create(); + + try{ + $this->User->Create(); + } + catch(Exceptions\UserExistsException){ + // User already exists, pass + } } $this->UserId = $this->User->UserId; diff --git a/lib/Poll.php b/lib/Poll.php index 47c97db5..de4c8bc4 100644 --- a/lib/Poll.php +++ b/lib/Poll.php @@ -87,6 +87,7 @@ class Poll{ // ******* public function IsActive(): bool{ + /** @throws void */ $now = new DateTimeImmutable(); 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 b0abb723..206906b5 100644 --- a/lib/PollItem.php +++ b/lib/PollItem.php @@ -1,4 +1,7 @@ Validate(); - $this->Created = new DateTimeImmutable(); Db::Query(' - INSERT into PollVotes (UserId, PollItemId, Created) + INSERT into PollVotes (UserId, PollItemId) values (?, - ?, ?) - ', [$this->UserId, $this->PollItemId, $this->Created]); + ', [$this->UserId, $this->PollItemId]); } /** diff --git a/lib/RssFeed.php b/lib/RssFeed.php index 8960944c..039289d1 100644 --- a/lib/RssFeed.php +++ b/lib/RssFeed.php @@ -1,5 +1,9 @@ XmlString === null){ - $feed = Template::RssFeed(['url' => $this->Url, 'description' => $this->Description, 'title' => $this->Title, 'entries' => $this->Entries, 'updated' => (new DateTimeImmutable())->format('r')]); + /** @throws void */ + $timestamp = (new DateTimeImmutable())->format('r'); + $feed = Template::RssFeed(['url' => $this->Url, 'description' => $this->Description, 'title' => $this->Title, 'entries' => $this->Entries, 'updated' => $timestamp]); $this->XmlString = $this->CleanXmlString($feed); } diff --git a/lib/Session.php b/lib/Session.php index 81121ed6..981fb604 100644 --- a/lib/Session.php +++ b/lib/Session.php @@ -1,7 +1,10 @@ User = User::GetIfRegistered($email, $password); @@ -54,6 +61,8 @@ class Session{ else{ $uuid = Uuid::uuid4(); $this->SessionId = $uuid->toString(); + + /** @throws void */ $this->Created = new DateTimeImmutable(); Db::Query(' INSERT into Sessions (UserId, SessionId, Created) diff --git a/lib/User.php b/lib/User.php index e4401131..2c6f21b2 100644 --- a/lib/User.php +++ b/lib/User.php @@ -1,4 +1,6 @@ Uuid = $uuid->toString(); + + /** @throws void */ $this->Created = new DateTimeImmutable(); $this->PasswordHash = null; @@ -145,6 +152,7 @@ class User{ /** * @throws Exceptions\UserNotFoundException + * @throws Exceptions\PasswordRequiredException */ public static function GetIfRegistered(?string $identifier, ?string $password = null): User{ // We consider a user "registered" if they have a row in the Benefits table. diff --git a/www/ebooks/index.php b/www/ebooks/index.php index 5cda083b..d9fe63f2 100644 --- a/www/ebooks/index.php +++ b/www/ebooks/index.php @@ -2,6 +2,7 @@ use function Safe\preg_replace; $page = HttpInput::Int(GET, 'page') ?? 1; +$pages = 0; $perPage = HttpInput::Int(GET, 'per-page') ?? EBOOKS_PER_PAGE; $query = HttpInput::Str(GET, 'query') ?? ''; $tags = HttpInput::GetArray('tags') ?? []; @@ -9,6 +10,7 @@ $view = ViewType::tryFrom(HttpInput::Str(GET, 'view') ?? ''); $sort = EbookSort::tryFrom(HttpInput::Str(GET, 'sort') ?? ''); $queryString = ''; $queryStringParams = []; +$queryStringWithoutPage = ''; try{ if($page <= 0){ @@ -96,6 +98,12 @@ catch(Exceptions\PageOutOfBoundsException){ header('Location: ' . $url); exit(); } +catch(Exceptions\AppException $ex){ + // Something very unexpected happened, log and emit 500 + http_response_code(500); // Internal server error + Log::WriteErrorLogEntry($ex); + exit(); +} ?> $pageTitle, 'highlight' => 'ebooks', 'description' => $pageDescription, 'canonicalUrl' => $canonicalUrl]) ?>