mirror of
https://github.com/standardebooks/web.git
synced 2025-07-05 14:20:29 -04:00
Enable strict exception type hint checking in PHPStan and add exception type hints
This commit is contained in:
parent
559e4a5e05
commit
c4c8e7353f
26 changed files with 300 additions and 82 deletions
98
composer.lock
generated
98
composer.lock
generated
|
@ -557,16 +557,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/finder",
|
"name": "symfony/finder",
|
||||||
"version": "v6.4.0",
|
"version": "v6.4.7",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/finder.git",
|
"url": "https://github.com/symfony/finder.git",
|
||||||
"reference": "11d736e97f116ac375a81f96e662911a34cd50ce"
|
"reference": "511c48990be17358c23bf45c5d71ab85d40fb764"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/finder/zipball/11d736e97f116ac375a81f96e662911a34cd50ce",
|
"url": "https://api.github.com/repos/symfony/finder/zipball/511c48990be17358c23bf45c5d71ab85d40fb764",
|
||||||
"reference": "11d736e97f116ac375a81f96e662911a34cd50ce",
|
"reference": "511c48990be17358c23bf45c5d71ab85d40fb764",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -601,7 +601,7 @@
|
||||||
"description": "Finds files and directories via an intuitive fluent interface",
|
"description": "Finds files and directories via an intuitive fluent interface",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/finder/tree/v6.4.0"
|
"source": "https://github.com/symfony/finder/tree/v6.4.7"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -617,20 +617,20 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-10-31T17:30:12+00:00"
|
"time": "2024-04-23T10:36:43+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-ctype",
|
"name": "symfony/polyfill-ctype",
|
||||||
"version": "v1.28.0",
|
"version": "v1.29.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
|
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
|
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||||
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
|
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -644,9 +644,6 @@
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
|
||||||
"dev-main": "1.28-dev"
|
|
||||||
},
|
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
"url": "https://github.com/symfony/polyfill"
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
@ -683,7 +680,7 @@
|
||||||
"portable"
|
"portable"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
|
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -699,20 +696,20 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-01-26T09:26:14+00:00"
|
"time": "2024-01-29T20:11:03+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-mbstring",
|
"name": "symfony/polyfill-mbstring",
|
||||||
"version": "v1.28.0",
|
"version": "v1.29.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
"reference": "42292d99c55abe617799667f454222c54c60e229"
|
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||||
"reference": "42292d99c55abe617799667f454222c54c60e229",
|
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -726,9 +723,6 @@
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
|
||||||
"dev-main": "1.28-dev"
|
|
||||||
},
|
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
"url": "https://github.com/symfony/polyfill"
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
@ -766,7 +760,7 @@
|
||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
|
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -782,20 +776,20 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-07-28T09:04:16+00:00"
|
"time": "2024-01-29T20:11:03+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php80",
|
"name": "symfony/polyfill-php80",
|
||||||
"version": "v1.28.0",
|
"version": "v1.29.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||||
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5"
|
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
|
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
||||||
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
|
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -803,9 +797,6 @@
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
|
||||||
"dev-main": "1.28-dev"
|
|
||||||
},
|
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
"url": "https://github.com/symfony/polyfill"
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
@ -849,7 +840,7 @@
|
||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0"
|
"source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -865,20 +856,20 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-01-26T09:26:14+00:00"
|
"time": "2024-01-29T20:11:03+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php81",
|
"name": "symfony/polyfill-php81",
|
||||||
"version": "v1.28.0",
|
"version": "v1.29.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-php81.git",
|
"url": "https://github.com/symfony/polyfill-php81.git",
|
||||||
"reference": "7581cd600fa9fd681b797d00b02f068e2f13263b"
|
"reference": "c565ad1e63f30e7477fc40738343c62b40bc672d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b",
|
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d",
|
||||||
"reference": "7581cd600fa9fd681b797d00b02f068e2f13263b",
|
"reference": "c565ad1e63f30e7477fc40738343c62b40bc672d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -886,9 +877,6 @@
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
|
||||||
"dev-main": "1.28-dev"
|
|
||||||
},
|
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"name": "symfony/polyfill",
|
||||||
"url": "https://github.com/symfony/polyfill"
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
@ -928,7 +916,7 @@
|
||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0"
|
"source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -944,20 +932,20 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-01-26T09:26:14+00:00"
|
"time": "2024-01-29T20:11:03+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/process",
|
"name": "symfony/process",
|
||||||
"version": "v6.4.2",
|
"version": "v6.4.7",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/process.git",
|
"url": "https://github.com/symfony/process.git",
|
||||||
"reference": "c4b1ef0bc80533d87a2e969806172f1c2a980241"
|
"reference": "cdb1c81c145fd5aa9b0038bab694035020943381"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/process/zipball/c4b1ef0bc80533d87a2e969806172f1c2a980241",
|
"url": "https://api.github.com/repos/symfony/process/zipball/cdb1c81c145fd5aa9b0038bab694035020943381",
|
||||||
"reference": "c4b1ef0bc80533d87a2e969806172f1c2a980241",
|
"reference": "cdb1c81c145fd5aa9b0038bab694035020943381",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -989,7 +977,7 @@
|
||||||
"description": "Executes commands in sub-processes",
|
"description": "Executes commands in sub-processes",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/process/tree/v6.4.2"
|
"source": "https://github.com/symfony/process/tree/v6.4.7"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -1005,7 +993,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-12-22T16:42:54+00:00"
|
"time": "2024-04-18T09:22:46+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "thecodingmachine/safe",
|
"name": "thecodingmachine/safe",
|
||||||
|
@ -1150,16 +1138,16 @@
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan",
|
"name": "phpstan/phpstan",
|
||||||
"version": "1.10.56",
|
"version": "1.10.67",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan.git",
|
"url": "https://github.com/phpstan/phpstan.git",
|
||||||
"reference": "27816a01aea996191ee14d010f325434c0ee76fa"
|
"reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/27816a01aea996191ee14d010f325434c0ee76fa",
|
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/16ddbe776f10da6a95ebd25de7c1dbed397dc493",
|
||||||
"reference": "27816a01aea996191ee14d010f325434c0ee76fa",
|
"reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -1202,13 +1190,9 @@
|
||||||
{
|
{
|
||||||
"url": "https://github.com/phpstan",
|
"url": "https://github.com/phpstan",
|
||||||
"type": "github"
|
"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",
|
"name": "thecodingmachine/phpstan-safe-rule",
|
||||||
|
|
|
@ -30,3 +30,14 @@ parameters:
|
||||||
- Emit404
|
- Emit404
|
||||||
- Emit403
|
- Emit403
|
||||||
- RedirectToLogin
|
- RedirectToLogin
|
||||||
|
exceptions:
|
||||||
|
check:
|
||||||
|
missingCheckedExceptionInThrows: true
|
||||||
|
tooWideThrowType: true
|
||||||
|
uncheckedExceptionRegexes:
|
||||||
|
-'#^Safe\\#'
|
||||||
|
uncheckedExceptionClasses:
|
||||||
|
- 'Exceptions\DatabaseQueryException'
|
||||||
|
- 'PDOException'
|
||||||
|
- 'TypeError'
|
||||||
|
- 'ValueError'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
CREATE TABLE `PollVotes` (
|
CREATE TABLE `PollVotes` (
|
||||||
`UserId` int(10) unsigned NOT NULL,
|
`UserId` int(10) unsigned NOT NULL,
|
||||||
`PollItemId` 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`)
|
UNIQUE KEY `idxUnique` (`PollItemId`,`UserId`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
|
@ -89,6 +89,7 @@ class Artist{
|
||||||
* @throws Exceptions\InvalidArtistException
|
* @throws Exceptions\InvalidArtistException
|
||||||
*/
|
*/
|
||||||
public function Validate(): void{
|
public function Validate(): void{
|
||||||
|
/** @throws void */
|
||||||
$now = new DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
$thisYear = intval($now->format('Y'));
|
$thisYear = intval($now->format('Y'));
|
||||||
|
|
||||||
|
@ -136,6 +137,9 @@ class Artist{
|
||||||
return $result[0] ?? throw new Exceptions\ArtistNotFoundException();;
|
return $result[0] ?? throw new Exceptions\ArtistNotFoundException();;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\InvalidArtistException
|
||||||
|
*/
|
||||||
public function Create(): void{
|
public function Create(): void{
|
||||||
$this->Validate();
|
$this->Validate();
|
||||||
Db::Query('
|
Db::Query('
|
||||||
|
@ -149,7 +153,7 @@ class Artist{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exceptions\ValidationException
|
* @throws Exceptions\InvalidArtistException
|
||||||
*/
|
*/
|
||||||
public static function GetOrCreate(Artist $artist): Artist{
|
public static function GetOrCreate(Artist $artist): Artist{
|
||||||
$result = Db::Query('
|
$result = Db::Query('
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
<?
|
<?
|
||||||
|
|
||||||
|
use Exceptions\InvalidImageUploadException;
|
||||||
|
use Exceptions\InvalidPageScanUrlException;
|
||||||
|
use Exceptions\InvalidUrlException;
|
||||||
use Safe\DateTimeImmutable;
|
use Safe\DateTimeImmutable;
|
||||||
|
use Safe\Exceptions\ExecException;
|
||||||
|
use Safe\Exceptions\FilesystemException;
|
||||||
|
use Safe\Exceptions\PcreException;
|
||||||
|
|
||||||
use function Safe\copy;
|
use function Safe\copy;
|
||||||
use function Safe\exec;
|
use function Safe\exec;
|
||||||
use function Safe\getimagesize;
|
use function Safe\getimagesize;
|
||||||
|
@ -160,6 +168,9 @@ class Artwork{
|
||||||
return $this->_Tags;
|
return $this->_Tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\InvalidUrlException
|
||||||
|
*/
|
||||||
public function GetMuseum(): ?Museum{
|
public function GetMuseum(): ?Museum{
|
||||||
if($this->_Museum === null){
|
if($this->_Museum === null){
|
||||||
try{
|
try{
|
||||||
|
@ -252,6 +263,9 @@ class Artwork{
|
||||||
return $this->_Dimensions;
|
return $this->_Dimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\AppException
|
||||||
|
*/
|
||||||
protected function GetEbook(): ?Ebook{
|
protected function GetEbook(): ?Ebook{
|
||||||
if($this->_Ebook === null){
|
if($this->_Ebook === null){
|
||||||
if($this->EbookUrl === 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{
|
protected function Validate(?string $imagePath = null, bool $isImageRequired = true): void{
|
||||||
|
/** @throws void */
|
||||||
$now = new DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
$thisYear = intval($now->format('Y'));
|
$thisYear = intval($now->format('Y'));
|
||||||
$error = new Exceptions\InvalidArtworkException();
|
$error = new Exceptions\InvalidArtworkException();
|
||||||
|
@ -509,6 +524,10 @@ class Artwork{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\InvalidUrlException
|
||||||
|
* @throws Exceptions\InvalidPageScanUrlException
|
||||||
|
*/
|
||||||
public static function NormalizePageScanUrl(string $url): string{
|
public static function NormalizePageScanUrl(string $url): string{
|
||||||
$outputUrl = $url;
|
$outputUrl = $url;
|
||||||
|
|
||||||
|
@ -621,6 +640,9 @@ class Artwork{
|
||||||
return $outputUrl;
|
return $outputUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\InvalidImageUploadException
|
||||||
|
*/
|
||||||
private function WriteImageAndThumbnails(string $imagePath): void{
|
private function WriteImageAndThumbnails(string $imagePath): void{
|
||||||
exec('exiftool -quiet -overwrite_original -all= ' . escapeshellarg($imagePath));
|
exec('exiftool -quiet -overwrite_original -all= ' . escapeshellarg($imagePath));
|
||||||
copy($imagePath, $this->ImageFsPath);
|
copy($imagePath, $this->ImageFsPath);
|
||||||
|
@ -645,6 +667,7 @@ class Artwork{
|
||||||
|
|
||||||
$this->Validate($imagePath, true);
|
$this->Validate($imagePath, true);
|
||||||
|
|
||||||
|
/** @throws void */
|
||||||
$this->Created = new DateTimeImmutable();
|
$this->Created = new DateTimeImmutable();
|
||||||
|
|
||||||
$tags = [];
|
$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{
|
public function Save(?string $imagePath = null): void{
|
||||||
$this->_UrlName = null;
|
$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
|
// Manually set the updated timestamp, because if we only update the image and nothing else, the row's
|
||||||
// updated timestamp won't change automatically.
|
// updated timestamp won't change automatically.
|
||||||
|
/** @throws void */
|
||||||
$this->Updated = new DateTimeImmutable();
|
$this->Updated = new DateTimeImmutable();
|
||||||
$this->_ImageUrl = null;
|
$this->_ImageUrl = null;
|
||||||
$this->_ThumbUrl = null;
|
$this->_ThumbUrl = null;
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
<?
|
<?
|
||||||
|
|
||||||
|
use Exceptions\InvalidArtworkTagException;
|
||||||
|
|
||||||
use function Safe\preg_match;
|
use function Safe\preg_match;
|
||||||
use function Safe\preg_replace;
|
use function Safe\preg_replace;
|
||||||
|
|
||||||
|
@ -20,7 +23,7 @@ class ArtworkTag extends Tag{
|
||||||
// *******
|
// *******
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exceptions\ValidationException
|
* @throws Exceptions\InvalidArtworkTagException
|
||||||
*/
|
*/
|
||||||
public function Validate(): void{
|
public function Validate(): void{
|
||||||
$error = new Exceptions\InvalidArtworkTagException($this->Name);
|
$error = new Exceptions\InvalidArtworkTagException($this->Name);
|
||||||
|
@ -46,6 +49,9 @@ class ArtworkTag extends Tag{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArtworkTagException
|
||||||
|
*/
|
||||||
public function Create(): void{
|
public function Create(): void{
|
||||||
$this->Validate();
|
$this->Validate();
|
||||||
|
|
||||||
|
@ -57,7 +63,7 @@ class ArtworkTag extends Tag{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exceptions\ValidationException
|
* @throws Exceptions\InvalidArtworkTagException
|
||||||
*/
|
*/
|
||||||
public static function GetOrCreate(ArtworkTag $artworkTag): ArtworkTag{
|
public static function GetOrCreate(ArtworkTag $artworkTag): ArtworkTag{
|
||||||
$result = Db::Query('
|
$result = Db::Query('
|
||||||
|
|
|
@ -39,6 +39,7 @@ class AtomFeed extends Feed{
|
||||||
// Did we actually update the feed? If so, write to file and update the index
|
// Did we actually update the feed? If so, write to file and update the index
|
||||||
if($this->HasChanged($this->Path)){
|
if($this->HasChanged($this->Path)){
|
||||||
// Files don't match, save the file
|
// Files don't match, save the file
|
||||||
|
/** @throws void */
|
||||||
$this->Updated = new DateTimeImmutable();
|
$this->Updated = new DateTimeImmutable();
|
||||||
$this->Save();
|
$this->Save();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -188,6 +188,7 @@ class DbConnection{
|
||||||
switch($metadata[$i]['native_type'] ?? null){
|
switch($metadata[$i]['native_type'] ?? null){
|
||||||
case 'DATETIME':
|
case 'DATETIME':
|
||||||
case 'TIMESTAMP':
|
case 'TIMESTAMP':
|
||||||
|
/** @throws void */
|
||||||
$object->{$metadata[$i]['name']} = new DateTimeImmutable($row[$i], new DateTimeZone('UTC'));
|
$object->{$metadata[$i]['name']} = new DateTimeImmutable($row[$i], new DateTimeZone('UTC'));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<?
|
<?
|
||||||
|
|
||||||
use Safe\DateTimeImmutable;
|
use Safe\DateTimeImmutable;
|
||||||
|
|
||||||
use function Safe\file_get_contents;
|
use function Safe\file_get_contents;
|
||||||
use function Safe\filesize;
|
use function Safe\filesize;
|
||||||
use function Safe\json_encode;
|
use function Safe\json_encode;
|
||||||
|
@ -75,6 +77,11 @@ class Ebook{
|
||||||
public ?string $TextSinglePageSizeUnit = null;
|
public ?string $TextSinglePageSizeUnit = null;
|
||||||
public $TocEntries = null; // A list of non-Roman ToC entries ONLY IF the work has the 'se:is-a-collection' metadata element, null otherwise
|
public $TocEntries = null; // A list of non-Roman ToC entries ONLY IF the work has the 'se:is-a-collection' metadata element, null otherwise
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\EbookNotFoundException
|
||||||
|
* @throws Exceptions\EbookParsingException
|
||||||
|
* @throws Exceptions\InvalidGitCommitException
|
||||||
|
*/
|
||||||
public function __construct(?string $wwwFilesystemPath = null){
|
public function __construct(?string $wwwFilesystemPath = null){
|
||||||
if($wwwFilesystemPath === null){
|
if($wwwFilesystemPath === null){
|
||||||
return;
|
return;
|
||||||
|
@ -88,7 +95,7 @@ class Ebook{
|
||||||
try{
|
try{
|
||||||
$this->RepoFilesystemPath = preg_replace('/\.git$/ius', '', $this->RepoFilesystemPath);
|
$this->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
|
// 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);
|
throw new Exceptions\EbookNotFoundException('Invalid repo filesystem path: ' . $this->RepoFilesystemPath);
|
||||||
}
|
}
|
||||||
|
@ -128,11 +135,16 @@ class Ebook{
|
||||||
$bytes = @filesize($this->WwwFilesystemPath . '/text/single-page.xhtml');
|
$bytes = @filesize($this->WwwFilesystemPath . '/text/single-page.xhtml');
|
||||||
$sizes = 'BKMGTP';
|
$sizes = 'BKMGTP';
|
||||||
$factor = intval(floor((strlen((string)$bytes) - 1) / 3));
|
$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->TextSinglePageSizeUnit = $sizes[$factor] ?? '';
|
||||||
$this->TextSinglePageUrl = $this->Url . '/text/single-page';
|
$this->TextSinglePageUrl = $this->Url . '/text/single-page';
|
||||||
}
|
}
|
||||||
catch(Exception){
|
catch(\Exception){
|
||||||
// Single page file doesn't exist, just pass
|
// Single page file doesn't exist, just pass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,7 +218,13 @@ class Ebook{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now do some heavy XML lifting!
|
// 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/');
|
$xml->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/');
|
||||||
|
|
||||||
$this->Title = $this->NullIfEmpty($xml->xpath('/package/metadata/dc:title'));
|
$this->Title = $this->NullIfEmpty($xml->xpath('/package/metadata/dc:title'));
|
||||||
|
@ -222,11 +240,13 @@ class Ebook{
|
||||||
|
|
||||||
$date = $xml->xpath('/package/metadata/dc:date') ?: [];
|
$date = $xml->xpath('/package/metadata/dc:date') ?: [];
|
||||||
if($date !== false && sizeof($date) > 0){
|
if($date !== false && sizeof($date) > 0){
|
||||||
|
/** @throws void */
|
||||||
$this->Created = new DateTimeImmutable((string)$date[0]);
|
$this->Created = new DateTimeImmutable((string)$date[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$modifiedDate = $xml->xpath('/package/metadata/meta[@property="dcterms:modified"]') ?: [];
|
$modifiedDate = $xml->xpath('/package/metadata/meta[@property="dcterms:modified"]') ?: [];
|
||||||
if($modifiedDate !== false && sizeof($modifiedDate) > 0){
|
if($modifiedDate !== false && sizeof($modifiedDate) > 0){
|
||||||
|
/** @throws void */
|
||||||
$this->Updated = new DateTimeImmutable((string)$modifiedDate[0]);
|
$this->Updated = new DateTimeImmutable((string)$modifiedDate[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,7 +260,12 @@ class Ebook{
|
||||||
// Fill the ToC if necessary
|
// Fill the ToC if necessary
|
||||||
if($includeToc){
|
if($includeToc){
|
||||||
$this->TocEntries = [];
|
$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');
|
$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){
|
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;
|
$this->TocEntries[] = (string)$item;
|
||||||
|
|
5
lib/Exceptions/InvalidGitCommitException.php
Normal file
5
lib/Exceptions/InvalidGitCommitException.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?
|
||||||
|
namespace Exceptions;
|
||||||
|
|
||||||
|
class InvalidGitCommitException extends AppException{
|
||||||
|
}
|
|
@ -6,8 +6,16 @@ class GitCommit{
|
||||||
public string $Message;
|
public string $Message;
|
||||||
public string $Hash;
|
public string $Hash;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\InvalidGitCommitException
|
||||||
|
*/
|
||||||
public function __construct(string $unixTimestamp, string $hash, string $message){
|
public function __construct(string $unixTimestamp, string $hash, string $message){
|
||||||
$this->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->Message = $message;
|
||||||
$this->Hash = $hash;
|
$this->Hash = $hash;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
<?
|
<?
|
||||||
|
|
||||||
|
use Safe\Exceptions\ImageException;
|
||||||
|
use Exceptions\InvalidImageUploadException;
|
||||||
|
|
||||||
use function Safe\exec;
|
use function Safe\exec;
|
||||||
use function Safe\imagecopyresampled;
|
use function Safe\imagecopyresampled;
|
||||||
use function Safe\imagecreatetruecolor;
|
use function Safe\imagecreatetruecolor;
|
||||||
|
@ -69,19 +73,37 @@ class Image{
|
||||||
return $handle;
|
return $handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\InvalidImageUploadException
|
||||||
|
*/
|
||||||
public function Resize(string $destImagePath, int $width, int $height): void{
|
public function Resize(string $destImagePath, int $width, int $height): void{
|
||||||
$imageDimensions = getimagesize($this->Path);
|
try{
|
||||||
|
$imageDimensions = getimagesize($this->Path);
|
||||||
|
}
|
||||||
|
catch(\Safe\Exceptions\ImageException $ex){
|
||||||
|
throw new Exceptions\InvalidImageUploadException($ex->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
$imageWidth = $imageDimensions[0];
|
$imageWidth = $imageDimensions[0];
|
||||||
$imageHeight = $imageDimensions[1];
|
$imageHeight = $imageDimensions[1];
|
||||||
|
|
||||||
if($imageHeight > $imageWidth){
|
if($imageHeight > $imageWidth){
|
||||||
$destinationHeight = $height;
|
$destinationHeight = $height;
|
||||||
$destinationWidth = intval($destinationHeight * ($imageWidth / $imageHeight));
|
try{
|
||||||
|
$destinationWidth = intval($destinationHeight * ($imageWidth / $imageHeight));
|
||||||
|
}
|
||||||
|
catch(\DivisionByZeroError){
|
||||||
|
$destinationWidth = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
$destinationWidth = $width;
|
$destinationWidth = $width;
|
||||||
$destinationHeight = intval($destinationWidth * ($imageHeight / $imageWidth));
|
try{
|
||||||
|
$destinationHeight = intval($destinationWidth * ($imageHeight / $imageWidth));
|
||||||
|
}
|
||||||
|
catch(\DivisionByZeroError){
|
||||||
|
$destinationHeight = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$srcImageHandle = $this->GetImageHandle();
|
$srcImageHandle = $this->GetImageHandle();
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
<?
|
<?
|
||||||
|
|
||||||
|
use Exceptions\AppException;
|
||||||
|
use Exceptions\ArtistNotFoundException;
|
||||||
use Safe\DateTimeImmutable;
|
use Safe\DateTimeImmutable;
|
||||||
|
use Safe\Exceptions\ExecException;
|
||||||
|
use Safe\Exceptions\PcreException;
|
||||||
|
use Safe\Exceptions\FilesystemException;
|
||||||
|
use Safe\Exceptions\DatetimeException;
|
||||||
|
use Safe\Exceptions\ArrayException;
|
||||||
|
use Safe\Exceptions\MiscException;
|
||||||
|
|
||||||
use function Safe\apcu_fetch;
|
use function Safe\apcu_fetch;
|
||||||
use function Safe\exec;
|
use function Safe\exec;
|
||||||
use function Safe\filemtime;
|
use function Safe\filemtime;
|
||||||
|
@ -18,6 +28,7 @@ class Library{
|
||||||
* @param array<string> $tags
|
* @param array<string> $tags
|
||||||
* @param EbookSort $sort
|
* @param EbookSort $sort
|
||||||
* @return array<Ebook>
|
* @return array<Ebook>
|
||||||
|
* @throws Exceptions\AppException
|
||||||
*/
|
*/
|
||||||
public static function FilterEbooks(string $query = null, array $tags = [], EbookSort $sort = null){
|
public static function FilterEbooks(string $query = null, array $tags = [], EbookSort $sort = null){
|
||||||
$ebooks = Library::GetEbooks();
|
$ebooks = Library::GetEbooks();
|
||||||
|
@ -109,6 +120,7 @@ class Library{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<Ebook>
|
* @return array<Ebook>
|
||||||
|
* @throws Exceptions\AppException
|
||||||
*/
|
*/
|
||||||
public static function GetEbooks(): array{
|
public static function GetEbooks(): array{
|
||||||
// Get all ebooks, unsorted.
|
// Get all ebooks, unsorted.
|
||||||
|
@ -117,6 +129,7 @@ class Library{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<Ebook>
|
* @return array<Ebook>
|
||||||
|
* @throws Exceptions\AppException
|
||||||
*/
|
*/
|
||||||
public static function GetEbooksByAuthor(string $wwwFilesystemPath): array{
|
public static function GetEbooksByAuthor(string $wwwFilesystemPath): array{
|
||||||
return self::GetFromApcu('author-' . $wwwFilesystemPath);
|
return self::GetFromApcu('author-' . $wwwFilesystemPath);
|
||||||
|
@ -136,6 +149,7 @@ class Library{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, Collection>
|
* @return array<string, Collection>
|
||||||
|
* @throws Exceptions\AppException
|
||||||
*/
|
*/
|
||||||
public static function GetEbookCollections(): array{
|
public static function GetEbookCollections(): array{
|
||||||
return self::GetFromApcu('collections');
|
return self::GetFromApcu('collections');
|
||||||
|
@ -143,6 +157,7 @@ class Library{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<Ebook>
|
* @return array<Ebook>
|
||||||
|
* @throws Exceptions\AppException
|
||||||
*/
|
*/
|
||||||
public static function GetEbooksByCollection(string $collection): array{
|
public static function GetEbooksByCollection(string $collection): array{
|
||||||
// Do we have the tag's ebooks cached?
|
// Do we have the tag's ebooks cached?
|
||||||
|
@ -151,6 +166,7 @@ class Library{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<Tag>
|
* @return array<Tag>
|
||||||
|
* @throws Exceptions\AppException
|
||||||
*/
|
*/
|
||||||
public static function GetTags(): array{
|
public static function GetTags(): array{
|
||||||
return self::GetFromApcu('tags');
|
return self::GetFromApcu('tags');
|
||||||
|
@ -303,8 +319,10 @@ class Library{
|
||||||
return ['artworks' => $artworks, 'artworksCount' => $artworksCount];
|
return ['artworks' => $artworks, 'artworksCount' => $artworksCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<Artwork>
|
* @return array<Artwork>
|
||||||
|
* @throws Exceptions\ArtistNotFoundException
|
||||||
*/
|
*/
|
||||||
public static function GetArtworksByArtist(?string $artistUrlName, ?string $status, ?int $submitterUserId): array{
|
public static function GetArtworksByArtist(?string $artistUrlName, ?string $status, ?int $submitterUserId): array{
|
||||||
if($artistUrlName === null){
|
if($artistUrlName === null){
|
||||||
|
@ -346,8 +364,10 @@ class Library{
|
||||||
return $artworks;
|
return $artworks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<mixed>
|
* @return array<mixed>
|
||||||
|
* @throws Exceptions\AppException
|
||||||
*/
|
*/
|
||||||
private static function GetFromApcu(string $variable): array{
|
private static function GetFromApcu(string $variable): array{
|
||||||
$results = [];
|
$results = [];
|
||||||
|
@ -389,6 +409,7 @@ class Library{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<Ebook>
|
* @return array<Ebook>
|
||||||
|
* @throws Exceptions\AppException
|
||||||
*/
|
*/
|
||||||
public static function Search(string $query): array{
|
public static function Search(string $query): array{
|
||||||
$ebooks = Library::GetEbooks();
|
$ebooks = Library::GetEbooks();
|
||||||
|
@ -432,6 +453,8 @@ class Library{
|
||||||
|
|
||||||
private static function FillBulkDownloadObject(string $dir, string $downloadType, string $urlRoot): stdClass{
|
private static function FillBulkDownloadObject(string $dir, string $downloadType, string $urlRoot): stdClass{
|
||||||
$obj = new stdClass();
|
$obj = new stdClass();
|
||||||
|
|
||||||
|
/** @throws void */
|
||||||
$now = new DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
|
|
||||||
// The count of ebooks in each file is stored as a filesystem attribute
|
// The count of ebooks in each file is stored as a filesystem attribute
|
||||||
|
@ -479,6 +502,7 @@ class Library{
|
||||||
$obj->ZipFiles[] = $zipFile;
|
$obj->ZipFiles[] = $zipFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @throws void */
|
||||||
$obj->Updated = new DateTimeImmutable('@' . filemtime($files[0]));
|
$obj->Updated = new DateTimeImmutable('@' . filemtime($files[0]));
|
||||||
$obj->UpdatedString = $obj->Updated->format('M j');
|
$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)
|
// 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<string, array<int|string, array<int|string, mixed>>>
|
* @return array<string, array<int|string, array<int|string, mixed>>>
|
||||||
|
* @throws Exceptions\AppException
|
||||||
*/
|
*/
|
||||||
public static function RebuildBulkDownloadsCache(): array{
|
public static function RebuildBulkDownloadsCache(): array{
|
||||||
$collator = Collator::create('en_US'); // Used for sorting letters with diacritics like in author names
|
$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){
|
foreach($dirs as $dir){
|
||||||
$obj = self::FillBulkDownloadObject($dir, 'months', '/months');
|
$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');
|
$year = $date->format('Y');
|
||||||
$month = $date->format('F');
|
$month = $date->format('F');
|
||||||
|
|
||||||
|
@ -588,6 +618,7 @@ class Library{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, array<int|string, array<int|string, mixed>>>
|
* @return array<string, array<int|string, array<int|string, mixed>>>
|
||||||
|
* @throws Exceptions\AppException
|
||||||
*/
|
*/
|
||||||
public static function RebuildFeedsCache(?string $returnType = null, ?string $returnClass = null): ?array{
|
public static function RebuildFeedsCache(?string $returnType = null, ?string $returnClass = null): ?array{
|
||||||
$feedTypes = ['opds', 'atom', 'rss'];
|
$feedTypes = ['opds', 'atom', 'rss'];
|
||||||
|
@ -634,6 +665,9 @@ class Library{
|
||||||
return $retval;
|
return $retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\AppException
|
||||||
|
*/
|
||||||
public static function GetEbook(?string $ebookWwwFilesystemPath): ?Ebook{
|
public static function GetEbook(?string $ebookWwwFilesystemPath): ?Ebook{
|
||||||
if($ebookWwwFilesystemPath === null){
|
if($ebookWwwFilesystemPath === null){
|
||||||
return null;
|
return null;
|
||||||
|
@ -649,6 +683,9 @@ class Library{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\AppException
|
||||||
|
*/
|
||||||
public static function RebuildCache(): void{
|
public static function RebuildCache(): void{
|
||||||
// We check a lockfile because this can be a long-running command.
|
// 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.
|
// We don't want to queue up a bunch of these in case someone is refreshing the index constantly.
|
||||||
|
|
|
@ -33,6 +33,7 @@ class Log{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @throws void */
|
||||||
$now = new DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
|
|
||||||
fwrite($fp, $now->format('Y-m-d H:i:s') . "\t" . $this->RequestId . "\t" . $text . "\n");
|
fwrite($fp, $now->format('Y-m-d H:i:s') . "\t" . $this->RequestId . "\t" . $text . "\n");
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
<?
|
<?
|
||||||
|
|
||||||
|
use Exceptions\InvalidUrlException;
|
||||||
|
use Safe\Exceptions\PcreException;
|
||||||
|
use Exceptions\InvalidMuseumUrlException;
|
||||||
|
use Exceptions\InvalidPageScanUrlException;
|
||||||
|
|
||||||
use function Safe\parse_url;
|
use function Safe\parse_url;
|
||||||
use function Safe\preg_match;
|
use function Safe\preg_match;
|
||||||
use function Safe\preg_replace;
|
use function Safe\preg_replace;
|
||||||
|
@ -10,6 +16,11 @@ class Museum{
|
||||||
public string $Name;
|
public string $Name;
|
||||||
public string $Domain;
|
public string $Domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\InvalidUrlException
|
||||||
|
* @throws Exceptions\InvalidMuseumUrlException
|
||||||
|
* @throws Exceptions\InvalidPageScanUrlException
|
||||||
|
*/
|
||||||
public static function NormalizeUrl(string $url): string{
|
public static function NormalizeUrl(string $url): string{
|
||||||
$outputUrl = $url;
|
$outputUrl = $url;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<?
|
<?
|
||||||
|
|
||||||
|
use Exceptions\InvalidNewsletterSubscription;
|
||||||
|
use Exceptions\NewsletterSubscriptionExistsException;
|
||||||
use Safe\DateTimeImmutable;
|
use Safe\DateTimeImmutable;
|
||||||
|
use Safe\Exceptions\ErrorfuncException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property User $User
|
* @property User $User
|
||||||
|
@ -33,6 +37,10 @@ class NewsletterSubscription{
|
||||||
// METHODS
|
// METHODS
|
||||||
// *******
|
// *******
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\InvalidNewsletterSubscription
|
||||||
|
* @throws Exceptions\NewsletterSubscriptionExistsException
|
||||||
|
*/
|
||||||
public function Create(?string $expectedCaptcha = null, ?string $receivedCaptcha = null): void{
|
public function Create(?string $expectedCaptcha = null, ?string $receivedCaptcha = null): void{
|
||||||
$this->Validate($expectedCaptcha, $receivedCaptcha);
|
$this->Validate($expectedCaptcha, $receivedCaptcha);
|
||||||
|
|
||||||
|
@ -42,10 +50,18 @@ class NewsletterSubscription{
|
||||||
}
|
}
|
||||||
catch(Exceptions\UserNotFoundException){
|
catch(Exceptions\UserNotFoundException){
|
||||||
// User doesn't exist, create the user
|
// 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;
|
$this->UserId = $this->User->UserId;
|
||||||
|
|
||||||
|
/** @throws void */
|
||||||
$this->Created = new DateTimeImmutable();
|
$this->Created = new DateTimeImmutable();
|
||||||
|
|
||||||
try{
|
try{
|
||||||
|
@ -66,6 +82,9 @@ class NewsletterSubscription{
|
||||||
$this->SendConfirmationEmail();
|
$this->SendConfirmationEmail();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\InvalidNewsletterSubscription
|
||||||
|
*/
|
||||||
public function Save(): void{
|
public function Save(): void{
|
||||||
$this->Validate();
|
$this->Validate();
|
||||||
|
|
||||||
|
@ -107,6 +126,10 @@ class NewsletterSubscription{
|
||||||
', [$this->UserId]);
|
', [$this->UserId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\InvalidNewsletterSubscription
|
||||||
|
*/
|
||||||
public function Validate(?string $expectedCaptcha = null, ?string $receivedCaptcha = null): void{
|
public function Validate(?string $expectedCaptcha = null, ?string $receivedCaptcha = null): void{
|
||||||
$error = new Exceptions\InvalidNewsletterSubscription();
|
$error = new Exceptions\InvalidNewsletterSubscription();
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ class OpdsFeed extends AtomFeed{
|
||||||
if($this->HasChanged($this->Path)){
|
if($this->HasChanged($this->Path)){
|
||||||
// Files don't match, save the file and update the parent navigation feed with the last updated timestamp
|
// Files don't match, save the file and update the parent navigation feed with the last updated timestamp
|
||||||
|
|
||||||
|
/** @throws void */
|
||||||
$this->Updated = new DateTimeImmutable();
|
$this->Updated = new DateTimeImmutable();
|
||||||
|
|
||||||
if($this->Parent !== null){
|
if($this->Parent !== null){
|
||||||
|
|
|
@ -21,6 +21,7 @@ class Patron{
|
||||||
// *******
|
// *******
|
||||||
|
|
||||||
public function Create(): void{
|
public function Create(): void{
|
||||||
|
/** @throws void */
|
||||||
$this->Created = new DateTimeImmutable();
|
$this->Created = new DateTimeImmutable();
|
||||||
Db::Query('
|
Db::Query('
|
||||||
INSERT into Patrons (Created, UserId, IsAnonymous, AlternateName, IsSubscribedToEmails)
|
INSERT into Patrons (Created, UserId, IsAnonymous, AlternateName, IsSubscribedToEmails)
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
<?
|
<?
|
||||||
|
|
||||||
|
use Exceptions\UserExistsException;
|
||||||
|
use Exceptions\PaymentExistsException;
|
||||||
use Safe\DateTimeImmutable;
|
use Safe\DateTimeImmutable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,6 +26,9 @@ class Payment{
|
||||||
// METHODS
|
// METHODS
|
||||||
// *******
|
// *******
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\PaymentExistsException
|
||||||
|
*/
|
||||||
public function Create(): void{
|
public function Create(): void{
|
||||||
if($this->UserId === null){
|
if($this->UserId === null){
|
||||||
// Check if we have to create a new user in the database
|
// Check if we have to create a new user in the database
|
||||||
|
@ -45,7 +51,13 @@ class Payment{
|
||||||
}
|
}
|
||||||
catch(Exceptions\UserNotFoundException){
|
catch(Exceptions\UserNotFoundException){
|
||||||
// User doesn't exist, create it now
|
// 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;
|
$this->UserId = $this->User->UserId;
|
||||||
|
|
|
@ -87,6 +87,7 @@ class Poll{
|
||||||
// *******
|
// *******
|
||||||
|
|
||||||
public function IsActive(): bool{
|
public function IsActive(): bool{
|
||||||
|
/** @throws void */
|
||||||
$now = new DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
if( ($this->Start !== null && $this->Start > $now) || ($this->End !== null && $this->End < $now)){
|
if( ($this->Start !== null && $this->Start > $now) || ($this->End !== null && $this->End < $now)){
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
<?
|
<?
|
||||||
|
|
||||||
|
use Exceptions\PollItemNotFoundException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $VoteCount
|
* @property int $VoteCount
|
||||||
* @property Poll $Poll
|
* @property Poll $Poll
|
||||||
|
@ -37,7 +40,7 @@ class PollItem{
|
||||||
// ***********
|
// ***********
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exceptions\PollNotFoundException
|
* @throws Exceptions\PollItemNotFoundException
|
||||||
*/
|
*/
|
||||||
public static function Get(?int $pollItemId): PollItem{
|
public static function Get(?int $pollItemId): PollItem{
|
||||||
if($pollItemId === null ){
|
if($pollItemId === null ){
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<?
|
<?
|
||||||
|
|
||||||
|
use Exceptions\InvalidPollVoteException;
|
||||||
use Safe\DateTimeImmutable;
|
use Safe\DateTimeImmutable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,6 +88,9 @@ class PollVote{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\InvalidPollVoteException
|
||||||
|
*/
|
||||||
public function Create(?string $email = null): void{
|
public function Create(?string $email = null): void{
|
||||||
if($email !== null){
|
if($email !== null){
|
||||||
try{
|
try{
|
||||||
|
@ -103,13 +108,11 @@ class PollVote{
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->Validate();
|
$this->Validate();
|
||||||
$this->Created = new DateTimeImmutable();
|
|
||||||
Db::Query('
|
Db::Query('
|
||||||
INSERT into PollVotes (UserId, PollItemId, Created)
|
INSERT into PollVotes (UserId, PollItemId)
|
||||||
values (?,
|
values (?,
|
||||||
?,
|
|
||||||
?)
|
?)
|
||||||
', [$this->UserId, $this->PollItemId, $this->Created]);
|
', [$this->UserId, $this->PollItemId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<?
|
<?
|
||||||
use Safe\DateTimeImmutable;
|
use Safe\DateTimeImmutable;
|
||||||
|
use Safe\Exceptions\DatetimeException;
|
||||||
|
use Safe\Exceptions\FilesystemException;
|
||||||
|
use Safe\Exceptions\ExecException;
|
||||||
|
|
||||||
use function Safe\file_get_contents;
|
use function Safe\file_get_contents;
|
||||||
use function Safe\filesize;
|
use function Safe\filesize;
|
||||||
use function Safe\preg_replace;
|
use function Safe\preg_replace;
|
||||||
|
@ -27,7 +31,9 @@ class RssFeed extends Feed{
|
||||||
|
|
||||||
protected function GetXmlString(): string{
|
protected function GetXmlString(): string{
|
||||||
if($this->XmlString === null){
|
if($this->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);
|
$this->XmlString = $this->CleanXmlString($feed);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
<?
|
<?
|
||||||
use Exceptions\InvalidLoginException;
|
use Exceptions\InvalidLoginException;
|
||||||
|
use Exceptions\PasswordRequiredException;
|
||||||
use Ramsey\Uuid\Uuid;
|
use Ramsey\Uuid\Uuid;
|
||||||
use Safe\DateTimeImmutable;
|
use Safe\DateTimeImmutable;
|
||||||
|
use Safe\Exceptions\DatetimeException;
|
||||||
|
|
||||||
use function Safe\strtotime;
|
use function Safe\strtotime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,6 +38,10 @@ class Session{
|
||||||
// METHODS
|
// METHODS
|
||||||
// *******
|
// *******
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exceptions\InvalidLoginException
|
||||||
|
* @throws Exceptions\PasswordRequiredException
|
||||||
|
*/
|
||||||
public function Create(?string $email = null, ?string $password = null): void{
|
public function Create(?string $email = null, ?string $password = null): void{
|
||||||
try{
|
try{
|
||||||
$this->User = User::GetIfRegistered($email, $password);
|
$this->User = User::GetIfRegistered($email, $password);
|
||||||
|
@ -54,6 +61,8 @@ class Session{
|
||||||
else{
|
else{
|
||||||
$uuid = Uuid::uuid4();
|
$uuid = Uuid::uuid4();
|
||||||
$this->SessionId = $uuid->toString();
|
$this->SessionId = $uuid->toString();
|
||||||
|
|
||||||
|
/** @throws void */
|
||||||
$this->Created = new DateTimeImmutable();
|
$this->Created = new DateTimeImmutable();
|
||||||
Db::Query('
|
Db::Query('
|
||||||
INSERT into Sessions (UserId, SessionId, Created)
|
INSERT into Sessions (UserId, SessionId, Created)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<?
|
<?
|
||||||
|
|
||||||
|
use Exceptions\UserExistsException;
|
||||||
use Ramsey\Uuid\Uuid;
|
use Ramsey\Uuid\Uuid;
|
||||||
use Safe\DateTimeImmutable;
|
use Safe\DateTimeImmutable;
|
||||||
|
|
||||||
|
@ -77,9 +79,14 @@ class User{
|
||||||
// METHODS
|
// METHODS
|
||||||
// *******
|
// *******
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws UserExistsException
|
||||||
|
*/
|
||||||
public function Create(?string $password = null): void{
|
public function Create(?string $password = null): void{
|
||||||
$uuid = Uuid::uuid4();
|
$uuid = Uuid::uuid4();
|
||||||
$this->Uuid = $uuid->toString();
|
$this->Uuid = $uuid->toString();
|
||||||
|
|
||||||
|
/** @throws void */
|
||||||
$this->Created = new DateTimeImmutable();
|
$this->Created = new DateTimeImmutable();
|
||||||
|
|
||||||
$this->PasswordHash = null;
|
$this->PasswordHash = null;
|
||||||
|
@ -145,6 +152,7 @@ class User{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exceptions\UserNotFoundException
|
* @throws Exceptions\UserNotFoundException
|
||||||
|
* @throws Exceptions\PasswordRequiredException
|
||||||
*/
|
*/
|
||||||
public static function GetIfRegistered(?string $identifier, ?string $password = null): User{
|
public static function GetIfRegistered(?string $identifier, ?string $password = null): User{
|
||||||
// We consider a user "registered" if they have a row in the Benefits table.
|
// We consider a user "registered" if they have a row in the Benefits table.
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
use function Safe\preg_replace;
|
use function Safe\preg_replace;
|
||||||
|
|
||||||
$page = HttpInput::Int(GET, 'page') ?? 1;
|
$page = HttpInput::Int(GET, 'page') ?? 1;
|
||||||
|
$pages = 0;
|
||||||
$perPage = HttpInput::Int(GET, 'per-page') ?? EBOOKS_PER_PAGE;
|
$perPage = HttpInput::Int(GET, 'per-page') ?? EBOOKS_PER_PAGE;
|
||||||
$query = HttpInput::Str(GET, 'query') ?? '';
|
$query = HttpInput::Str(GET, 'query') ?? '';
|
||||||
$tags = HttpInput::GetArray('tags') ?? [];
|
$tags = HttpInput::GetArray('tags') ?? [];
|
||||||
|
@ -9,6 +10,7 @@ $view = ViewType::tryFrom(HttpInput::Str(GET, 'view') ?? '');
|
||||||
$sort = EbookSort::tryFrom(HttpInput::Str(GET, 'sort') ?? '');
|
$sort = EbookSort::tryFrom(HttpInput::Str(GET, 'sort') ?? '');
|
||||||
$queryString = '';
|
$queryString = '';
|
||||||
$queryStringParams = [];
|
$queryStringParams = [];
|
||||||
|
$queryStringWithoutPage = '';
|
||||||
|
|
||||||
try{
|
try{
|
||||||
if($page <= 0){
|
if($page <= 0){
|
||||||
|
@ -96,6 +98,12 @@ catch(Exceptions\PageOutOfBoundsException){
|
||||||
header('Location: ' . $url);
|
header('Location: ' . $url);
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
catch(Exceptions\AppException $ex){
|
||||||
|
// Something very unexpected happened, log and emit 500
|
||||||
|
http_response_code(500); // Internal server error
|
||||||
|
Log::WriteErrorLogEntry($ex);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
?><?= Template::Header(['title' => $pageTitle, 'highlight' => 'ebooks', 'description' => $pageDescription, 'canonicalUrl' => $canonicalUrl]) ?>
|
?><?= Template::Header(['title' => $pageTitle, 'highlight' => 'ebooks', 'description' => $pageDescription, 'canonicalUrl' => $canonicalUrl]) ?>
|
||||||
<main class="ebooks">
|
<main class="ebooks">
|
||||||
<h1><?= $pageHeader ?></h1>
|
<h1><?= $pageHeader ?></h1>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue