mirror of
https://github.com/standardebooks/web.git
synced 2025-07-05 14:20:29 -04:00
Bump PHPStan check level to max and add final round of type hints
This commit is contained in:
parent
110c091a7b
commit
70ae877dd8
15 changed files with 86 additions and 52 deletions
|
@ -8,7 +8,7 @@ parameters:
|
|||
# Ignore errors caused by Template static class reflection
|
||||
- '#Call to an undefined static method Template::[a-zA-Z0-9\\_]+\(\)\.#'
|
||||
level:
|
||||
8
|
||||
9
|
||||
paths:
|
||||
- %rootDir%/../../../lib
|
||||
- %rootDir%/../../../www
|
||||
|
|
|
@ -9,10 +9,11 @@ class Db{
|
|||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param string $query
|
||||
* @param array<mixed> $args
|
||||
* @param string $class
|
||||
* @return Array<mixed>
|
||||
* @param class-string<T> $class
|
||||
* @return Array<T>
|
||||
*/
|
||||
public static function Query(string $query, array $args = [], string $class = 'stdClass'): array{
|
||||
return $GLOBALS['DbConnection']->Query($query, $args, $class);
|
||||
|
|
|
@ -41,21 +41,23 @@ class DbConnection{
|
|||
$connectionString .= ';dbname=' . $defaultDatabase;
|
||||
}
|
||||
|
||||
// We can't use persistent connections (connection pooling) because we would have race condition problems with last_insert_id()
|
||||
$params = [\PDO::ATTR_EMULATE_PREPARES => false, \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_PERSISTENT => false];
|
||||
|
||||
if($forceUtf8){
|
||||
$params[\PDO::MYSQL_ATTR_INIT_COMMAND] = 'set names utf8mb4 collate utf8mb4_unicode_ci;';
|
||||
}
|
||||
|
||||
// We can't use persistent connections (connection pooling) because we would have race condition problems with last_insert_id()
|
||||
$this->_link = new \PDO($connectionString, $user, $password, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a generic query in the database.
|
||||
* @template T
|
||||
* @param string $sql The SQL query to execute.
|
||||
* @param array<mixed> $params An array of parameters to bind to the SQL statement.
|
||||
* @param string $class The type of object to return in the return array.
|
||||
* @return Array<mixed>
|
||||
* @param class-string<T> $class The type of object to return in the return array.
|
||||
* @return Array<T>
|
||||
* @throws Exceptions\DuplicateDatabaseKeyException When a unique key constraint has been violated.
|
||||
* @throws Exceptions\DatabaseQueryException When an error occurs during execution of the query.
|
||||
*/
|
||||
|
@ -131,17 +133,9 @@ class DbConnection{
|
|||
}
|
||||
|
||||
/**
|
||||
* @param \PDOException $ex The exception to create details from.
|
||||
* @param string $sql The prepared SQL that caused the exception.
|
||||
* @param array<mixed> $params The parameters passed to the prepared SQL.
|
||||
*/
|
||||
private function CreateDetailedException(\PDOException $ex, string $sql, array $params): Exceptions\DatabaseQueryException{
|
||||
// Throw a custom exception that includes more information on the query and paramaters
|
||||
return new Exceptions\DatabaseQueryException('Error when executing query: ' . $ex->getMessage() . '. Query: ' . $sql . '. Parameters: ' . vds($params));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Array<mixed>
|
||||
* @template T
|
||||
* @param class-string<T> $class
|
||||
* @return Array<T>
|
||||
* @throws \PDOException When an error occurs during execution of the query.
|
||||
*/
|
||||
private function ExecuteQuery(\PDOStatement $handle, string $class = 'stdClass'): array{
|
||||
|
@ -287,4 +281,14 @@ class DbConnection{
|
|||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \PDOException $ex The exception to create details from.
|
||||
* @param string $sql The prepared SQL that caused the exception.
|
||||
* @param array<mixed> $params The parameters passed to the prepared SQL.
|
||||
*/
|
||||
private function CreateDetailedException(\PDOException $ex, string $sql, array $params): Exceptions\DatabaseQueryException{
|
||||
// Throw a custom exception that includes more information on the query and paramaters
|
||||
return new Exceptions\DatabaseQueryException('Error when executing query: ' . $ex->getMessage() . '. Query: ' . $sql . '. Parameters: ' . vds($params));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ class Ebook{
|
|||
public string $WwwFilesystemPath;
|
||||
public string $RepoFilesystemPath;
|
||||
public string $Url;
|
||||
public string $KindleCoverUrl;
|
||||
public ?string $KindleCoverUrl;
|
||||
public string $EpubUrl;
|
||||
public string $AdvancedEpubUrl;
|
||||
public string $KepubUrl;
|
||||
|
@ -68,8 +68,8 @@ class Ebook{
|
|||
public string $TitleWithCreditsHtml = '';
|
||||
public DateTimeImmutable $Created;
|
||||
public DateTimeImmutable $Updated;
|
||||
public string $TextUrl;
|
||||
public string $TextSinglePageUrl;
|
||||
public ?string $TextUrl;
|
||||
public ?string $TextSinglePageUrl;
|
||||
public ?string $TextSinglePageSizeNumber = null;
|
||||
public ?string $TextSinglePageSizeUnit = null;
|
||||
/** @var ?array<string> $TocEntries */
|
||||
|
|
|
@ -84,18 +84,22 @@ class HttpInput{
|
|||
return null;
|
||||
}
|
||||
|
||||
/** @var ?string $var */
|
||||
return $var;
|
||||
}
|
||||
|
||||
public static function Int(HttpVariableSource $set, string $variable): ?int{
|
||||
/** @var ?int */
|
||||
return self::GetHttpVar($variable, HttpVariableType::Integer, $set);
|
||||
}
|
||||
|
||||
public static function Bool(HttpVariableSource $set, string $variable): ?bool{
|
||||
/** @var ?bool */
|
||||
return self::GetHttpVar($variable, HttpVariableType::Boolean, $set);
|
||||
}
|
||||
|
||||
public static function Dec(HttpVariableSource $set, string $variable): ?float{
|
||||
/** @var ?float */
|
||||
return self::GetHttpVar($variable, HttpVariableType::Decimal, $set);
|
||||
}
|
||||
|
||||
|
@ -104,9 +108,13 @@ class HttpInput{
|
|||
* @return array<string>
|
||||
*/
|
||||
public static function Array(HttpVariableSource $set, string $variable): ?array{
|
||||
/** @var array<string> */
|
||||
return self::GetHttpVar($variable, HttpVariableType::Array, $set);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string>|array<int>|array<float>|array<bool>|string|int|float|bool|null
|
||||
*/
|
||||
private static function GetHttpVar(string $variable, HttpVariableType $type, HttpVariableSource $set): mixed{
|
||||
$vars = [];
|
||||
|
||||
|
|
|
@ -113,6 +113,7 @@ class Library{
|
|||
*/
|
||||
public static function GetEbooks(): array{
|
||||
// Get all ebooks, unsorted.
|
||||
/** @var array<Ebook> */
|
||||
return self::GetFromApcu('ebooks');
|
||||
}
|
||||
|
||||
|
@ -121,6 +122,7 @@ class Library{
|
|||
* @throws Exceptions\AppException
|
||||
*/
|
||||
public static function GetEbooksByAuthor(string $wwwFilesystemPath): array{
|
||||
/** @var array<Ebook> */
|
||||
return self::GetFromApcu('author-' . $wwwFilesystemPath);
|
||||
}
|
||||
|
||||
|
@ -129,6 +131,7 @@ class Library{
|
|||
*/
|
||||
public static function GetEbooksByTag(string $tag): array{
|
||||
try{
|
||||
/** @var array<Ebook> */
|
||||
return apcu_fetch('tag-' . $tag) ?? [];
|
||||
}
|
||||
catch(Safe\Exceptions\ApcuException){
|
||||
|
@ -141,6 +144,7 @@ class Library{
|
|||
* @throws Exceptions\AppException
|
||||
*/
|
||||
public static function GetEbookCollections(): array{
|
||||
/** @var array<string, Collection> */
|
||||
return self::GetFromApcu('collections');
|
||||
}
|
||||
|
||||
|
@ -150,6 +154,7 @@ class Library{
|
|||
*/
|
||||
public static function GetEbooksByCollection(string $collection): array{
|
||||
// Do we have the tag's ebooks cached?
|
||||
/** @var array<Ebook> */
|
||||
return self::GetFromApcu('collection-' . $collection);
|
||||
}
|
||||
|
||||
|
@ -158,6 +163,7 @@ class Library{
|
|||
* @throws Exceptions\AppException
|
||||
*/
|
||||
public static function GetTags(): array{
|
||||
/** @var array<Tag> */
|
||||
return self::GetFromApcu('tags');
|
||||
}
|
||||
|
||||
|
@ -538,7 +544,7 @@ class Library{
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<int|string, array<int|string, mixed>>>
|
||||
* @return array<string, array<int|string, array<int|string, stdClass>>>
|
||||
* @throws Exceptions\AppException
|
||||
*/
|
||||
public static function RebuildBulkDownloadsCache(): array{
|
||||
|
@ -662,6 +668,7 @@ class Library{
|
|||
return null;
|
||||
}
|
||||
|
||||
/** @var array<Ebook> $result */
|
||||
$result = self::GetFromApcu('ebook-' . $ebookWwwFilesystemPath);
|
||||
|
||||
if(sizeof($result) > 0){
|
||||
|
|
|
@ -25,7 +25,7 @@ class Template{
|
|||
* @param array<mixed> $arguments
|
||||
*/
|
||||
public static function __callStatic(string $function, array $arguments): string{
|
||||
if(isset($arguments[0])){
|
||||
if(isset($arguments[0]) && is_array($arguments[0])){
|
||||
return self::Get($function, $arguments[0]);
|
||||
}
|
||||
else{
|
||||
|
|
|
@ -16,10 +16,12 @@ if($GLOBALS['User'] !== null && $GLOBALS['User']->Benefits->CanBulkDownload){
|
|||
$collection = [];
|
||||
|
||||
try{
|
||||
/** @var array<string, array<string, stdClass>> $collection */
|
||||
$collection = apcu_fetch('bulk-downloads-' . $class);
|
||||
}
|
||||
catch(Safe\Exceptions\ApcuException){
|
||||
$result = Library::RebuildBulkDownloadsCache();
|
||||
/** @var array<string, array<string, stdClass>> $collection */
|
||||
$collection = $result[$class];
|
||||
}
|
||||
|
||||
|
|
|
@ -17,10 +17,13 @@ try{
|
|||
|
||||
// Get all collections and then find the specific one we're looking for
|
||||
try{
|
||||
/** @var array<stdClass> $collections */
|
||||
$collections = apcu_fetch('bulk-downloads-collections');
|
||||
}
|
||||
catch(Safe\Exceptions\ApcuException){
|
||||
$result = Library::RebuildBulkDownloadsCache();
|
||||
|
||||
/** @var array<stdClass> $collections */
|
||||
$collections = $result['collections'];
|
||||
}
|
||||
|
||||
|
@ -41,10 +44,13 @@ try{
|
|||
|
||||
// Get all authors and then find the specific one we're looking for
|
||||
try{
|
||||
/** @var array<stdClass> $collections */
|
||||
$collections = apcu_fetch('bulk-downloads-authors');
|
||||
}
|
||||
catch(Safe\Exceptions\ApcuException){
|
||||
$result = Library::RebuildBulkDownloadsCache();
|
||||
|
||||
/** @var array<stdClass> $collections */
|
||||
$collections = $result['authors'];
|
||||
}
|
||||
|
||||
|
@ -70,7 +76,7 @@ catch(Exceptions\CollectionNotFoundException){
|
|||
?><?= Template::Header(['title' => 'Download ', 'highlight' => '', 'description' => 'Download zip files containing all of the Standard Ebooks released in a given month.']) ?>
|
||||
<main>
|
||||
<section class="bulk-downloads">
|
||||
<h1>Download the <?= $collection->Label ?> Collection</h1>
|
||||
<h1>Download the <?= $collection?->Label ?> Collection</h1>
|
||||
<? if($canDownload){ ?>
|
||||
<p>Select the ebook format in which you’d like to download this collection.</p>
|
||||
<p>You can also read about <a href="/help/how-to-use-our-ebooks#which-file-to-download">which ebook format to download</a>.</p>
|
||||
|
|
|
@ -16,16 +16,13 @@ try{
|
|||
|
||||
// Do we have the ebook cached?
|
||||
try{
|
||||
/** @var Ebook $ebook */
|
||||
$ebook = apcu_fetch('ebook-' . $wwwFilesystemPath);
|
||||
}
|
||||
catch(Safe\Exceptions\ApcuException){
|
||||
$ebook = new Ebook($wwwFilesystemPath);
|
||||
}
|
||||
|
||||
if($ebook === null){
|
||||
throw new Exceptions\InvalidFileException();
|
||||
}
|
||||
|
||||
switch($format){
|
||||
case EbookFormat::Kepub:
|
||||
$downloadUrl = $ebook->KepubUrl;
|
||||
|
|
|
@ -28,6 +28,7 @@ try{
|
|||
// https://standardebooks.org/ebooks/omar-khayyam/the-rubaiyat-of-omar-khayyam/edward-fitzgerald/edmund-dulac
|
||||
// We can tell because if so, the dir we are passed will exist, but there will be no 'src' folder.
|
||||
if(is_dir($wwwFilesystemPath) && !is_dir($wwwFilesystemPath . '/src')){
|
||||
/** @var DirectoryIterator $file */
|
||||
foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($wwwFilesystemPath)) as $file){
|
||||
// This iterator will do a deep scan on the directory. When we hit another directory, the filename will be "." and the path will contain the directory path.
|
||||
// We want to find where the "src" directory is, and the directory directly below that will be the final web URL we're looking for.
|
||||
|
@ -39,6 +40,7 @@ try{
|
|||
|
||||
// Do we have the ebook cached?
|
||||
try{
|
||||
/** @var Ebook $ebook */
|
||||
$ebook = apcu_fetch('ebook-' . $wwwFilesystemPath);
|
||||
}
|
||||
catch(Safe\Exceptions\ApcuException){
|
||||
|
@ -211,7 +213,7 @@ catch(Exceptions\EbookNotFoundException){
|
|||
<p class="us-pd-warning">This ebook is thought to be free of copyright restrictions in the United States. It may still be under copyright in other countries. If you’re not located in the United States, you must check your local laws to verify that this ebook is free of copyright restrictions in the country you’re located in before accessing, downloading, or using it.</p>
|
||||
|
||||
<div class="downloads-container">
|
||||
<figure class="<? if($ebook->WordCount < 100000){ ?>small<? }elseif($ebook->WordCount >= 100000 && $ebook->WordCount < 200000){ ?>medium<? }elseif($ebook->WordCount >= 200000 && $ebook->WordCount <= 300000){ ?>large<? }elseif($ebook->WordCount >= 300000 && $ebook->WordCount < 400000){ ?>xlarge<? }elseif($ebook->WordCount >= 400000){ ?>xxlarge<? } ?>">
|
||||
<figure class="<? if($ebook->WordCount < 100000){ ?>small<? }elseif($ebook->WordCount < 200000){ ?>medium<? }elseif($ebook->WordCount <= 300000){ ?>large<? }elseif($ebook->WordCount < 400000){ ?>xlarge<? }else{ ?>xxlarge<? } ?>">
|
||||
<picture>
|
||||
<source srcset="<?= $ebook->CoverImage2xAvifUrl ?> 2x, <?= $ebook->CoverImageAvifUrl ?> 1x" type="image/avif"/>
|
||||
<source srcset="<?= $ebook->CoverImage2xUrl ?> 2x, <?= $ebook->CoverImageUrl ?> 1x" type="image/jpg"/>
|
||||
|
|
|
@ -23,9 +23,11 @@ if($type === 'atom'){
|
|||
}
|
||||
|
||||
try{
|
||||
/** @var array<stdClass> $feeds */
|
||||
$feeds = apcu_fetch('feeds-index-' . $type . '-' . $class);
|
||||
}
|
||||
catch(Safe\Exceptions\ApcuException){
|
||||
/** @var array<stdClass> $feeds */
|
||||
$feeds = Library::RebuildFeedsCache($type, $class);
|
||||
}
|
||||
?><?= Template::Header(['title' => $ucType . ' Ebook Feeds by ' . $ucTitle, 'description' => 'A list of available ' . $ucType . ' feeds of Standard Ebooks ebooks by ' . $lcTitle . '.']) ?>
|
||||
|
|
|
@ -35,6 +35,7 @@ try{
|
|||
throw new Exceptions\WebhookException('Couldn\'t understand HTTP request.', $post);
|
||||
}
|
||||
|
||||
/** @var array<array<string>> $data */
|
||||
$data = json_decode($post, true);
|
||||
|
||||
// Decide what event we just received.
|
||||
|
|
|
@ -21,13 +21,16 @@ try{
|
|||
throw new Exceptions\InvalidCredentialsException();
|
||||
}
|
||||
|
||||
$post = json_decode(file_get_contents('php://input'));
|
||||
$post = file_get_contents('php://input');
|
||||
|
||||
if(!$post || !property_exists($post, 'RecordType')){
|
||||
/** @var stdClass $data */
|
||||
$data = json_decode($post);
|
||||
|
||||
if(!property_exists($data, 'RecordType')){
|
||||
throw new Exceptions\WebhookException('Couldn\'t understand HTTP request.', $post);
|
||||
}
|
||||
|
||||
if($post->RecordType == 'SpamComplaint'){
|
||||
if($data->RecordType == 'SpamComplaint'){
|
||||
// Received when a user marks an email as spam
|
||||
$log->Write('Event type: spam complaint.');
|
||||
|
||||
|
@ -36,13 +39,13 @@ try{
|
|||
from NewsletterSubscriptions ns
|
||||
inner join Users u using(UserId)
|
||||
where u.Email = ?
|
||||
', [$post->Email]);
|
||||
', [$data->Email]);
|
||||
}
|
||||
elseif($post->RecordType == 'SubscriptionChange' && $post->SuppressSending){
|
||||
elseif($data->RecordType == 'SubscriptionChange' && $data->SuppressSending){
|
||||
// Received when a user clicks Postmark's "Unsubscribe" link in a newsletter email
|
||||
$log->Write('Event type: unsubscribe.');
|
||||
|
||||
$email = $post->Recipient;
|
||||
$email = $data->Recipient;
|
||||
|
||||
// Remove the email from our newsletter list
|
||||
Db::Query('
|
||||
|
@ -54,18 +57,18 @@ try{
|
|||
|
||||
// Remove the suppression from Postmark, since we deleted it from our own list we will never email them again anyway
|
||||
$handle = curl_init();
|
||||
curl_setopt($handle, CURLOPT_URL, 'https://api.postmarkapp.com/message-streams/' . $post->MessageStream . '/suppressions/delete');
|
||||
curl_setopt($handle, CURLOPT_URL, 'https://api.postmarkapp.com/message-streams/' . $data->MessageStream . '/suppressions/delete');
|
||||
curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($handle, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Accept: application/json', 'X-Postmark-Server-Token: ' . $smtpUsername]);
|
||||
curl_setopt($handle, CURLOPT_CUSTOMREQUEST, "POST");
|
||||
curl_setopt($handle, CURLOPT_POSTFIELDS, '{"Suppressions": [{"EmailAddress": "' . $email . '"}]}');
|
||||
curl_exec($handle);
|
||||
}
|
||||
elseif($post->RecordType == 'SubscriptionChange' && $post->SuppressionReason === null){
|
||||
elseif($data->RecordType == 'SubscriptionChange' && $data->SuppressionReason === null){
|
||||
$log->Write('Event type: suppression deletion.');
|
||||
}
|
||||
else{
|
||||
$log->Write('Unrecognized event: ' . $post->RecordType);
|
||||
$log->Write('Unrecognized event: ' . $data->RecordType);
|
||||
}
|
||||
|
||||
$log->Write('Event processed.');
|
||||
|
|
|
@ -23,6 +23,7 @@ try{
|
|||
throw new Exceptions\InvalidCredentialsException();
|
||||
}
|
||||
|
||||
/** @var stdClass $data */
|
||||
$data = json_decode($post);
|
||||
|
||||
if($data->fromAddress == 'support@fracturedatlas.org' && strpos($data->subject, 'NOTICE:') !== false){
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue