diff --git a/config/phpstan/phpstan.neon b/config/phpstan/phpstan.neon index a1916667..73328637 100644 --- a/config/phpstan/phpstan.neon +++ b/config/phpstan/phpstan.neon @@ -11,22 +11,16 @@ parameters: # Ignore errors caused by no type hints on class properties, as that's not available till PHP 7.4 - '#Property .+? has no type specified.#' - # Ignore errors caused by missing phpdoc strings for arrays - - '#Method .+? has parameter .+? with no value type specified in iterable type array.#' - # Ignore errors caused by type hints that should be union types. Union types are not yet supported in PHP. - '#Function vd(s|d)?\(\) has parameter \$var with no type specified.#' - '#Method Ebook::NullIfEmpty\(\) has parameter \$elements with no type specified.#' - '#Method HttpInput::GetHttpVar\(\) has no return type specified.#' - '#Method HttpInput::GetHttpVar\(\) has parameter \$default with no type specified.#' - # Ignore errors caused by access to our PropertiesBase pattern - - '#Access to protected property .+#' - # Ignore symbols that PHPStan can't find - '#Constant EMAIL_SMTP_USERNAME not found.#' level: - 7 + 8 paths: - %rootDir%/../../../lib - %rootDir%/../../../www diff --git a/import.php b/import.php deleted file mode 100644 index 063347e9..00000000 --- a/import.php +++ /dev/null @@ -1,7 +0,0 @@ - $entries + */ public function __construct(string $title, string $subtitle, string $url, string $path, array $entries){ parent::__construct($title, $url, $path, $entries); $this->Subtitle = $subtitle; @@ -19,6 +26,11 @@ class AtomFeed extends Feed{ $this->Stylesheet = '/feeds/atom/style'; } + + // ******* + // METHODS + // ******* + protected function GetXmlString(): string{ if($this->XmlString === null){ $feed = Template::AtomFeed(['id' => $this->Id, 'url' => $this->Url, 'title' => $this->Title, 'subtitle' => $this->Subtitle, 'updated' => $this->Updated, 'entries' => $this->Entries]); diff --git a/lib/Db.php b/lib/Db.php index 6d803dd3..d879b088 100644 --- a/lib/Db.php +++ b/lib/Db.php @@ -6,6 +6,9 @@ class Db{ } /** + * @param string $query + * @param array $args + * @param string $class * @return Array */ public static function Query(string $query, array $args = [], string $class = 'stdClass'): array{ @@ -20,6 +23,10 @@ class Db{ return $GLOBALS['DbConnection']->Query($query, $args, $class); } + /** + * @param string $query + * @param array $args + */ public static function QueryInt(string $query, array $args = []): int{ // Useful for queries that return a single integer as a result, like count(*) or sum(*). diff --git a/lib/DbConnection.php b/lib/DbConnection.php index 3a98f08d..9f5e9f53 100644 --- a/lib/DbConnection.php +++ b/lib/DbConnection.php @@ -72,6 +72,9 @@ class DbConnection{ // array $params = an array of parameters to bind to the SQL statement // Returns: a resource record or null on error /** + * @param string $sql + * @param array $params + * @param string $class * @return Array */ public function Query(string $sql, array $params = [], string $class = 'stdClass'): array{ diff --git a/lib/Ebook.php b/lib/Ebook.php index d7e6ddf4..b9a6f61d 100644 --- a/lib/Ebook.php +++ b/lib/Ebook.php @@ -184,12 +184,12 @@ class Ebook{ $this->AlternateTitle = $this->NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:alternate-title"]')); - $date = $xml->xpath('/package/metadata/dc:date'); + $date = $xml->xpath('/package/metadata/dc:date') ?: []; if($date !== false && sizeof($date) > 0){ $this->Created = new DateTime((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){ $this->Updated = new DateTime((string)$modifiedDate[0]); } @@ -214,10 +214,12 @@ class Ebook{ // Get SE collections foreach($xml->xpath('/package/metadata/meta[@property="belongs-to-collection"]') ?: [] as $collection){ $c = new Collection($collection); - foreach($xml->xpath('/package/metadata/meta[@refines="#' . $collection->attributes()->id . '"][@property="group-position"]') ?: [] as $s){ + $id = $collection->attributes()->id ?? ''; + + foreach($xml->xpath('/package/metadata/meta[@refines="#' . $id . '"][@property="group-position"]') ?: [] as $s){ $c->SequenceNumber = (int)$s; } - foreach($xml->xpath('/package/metadata/meta[@refines="#' . $collection->attributes()->id . '"][@property="collection-type"]') ?: [] as $s){ + foreach($xml->xpath('/package/metadata/meta[@refines="#' . $id . '"][@property="collection-type"]') ?: [] as $s){ $c->Type = (string)$s; } $this->Collections[] = $c; @@ -237,7 +239,7 @@ class Ebook{ } $fileAs = null; - $fileAsElement = $xml->xpath('/package/metadata/meta[@property="file-as"][@refines="#' . $id . '"]'); + $fileAsElement = $xml->xpath('/package/metadata/meta[@property="file-as"][@refines="#' . $id . '"]') ?: []; if($fileAsElement !== false && sizeof($fileAsElement) > 0){ $fileAs = (string)$fileAsElement[0]; } @@ -449,6 +451,11 @@ class Ebook{ $this->TitleWithCreditsHtml = Formatter::ToPlainText($this->Title) . ', by ' . str_replace('&', '&', $this->AuthorsHtml . $titleContributors); } + + // ******* + // METHODS + // ******* + public function GetCollectionPosition(Collection $collection): ?int{ foreach($this->Collections as $c){ if($c->Name == $collection->Name){ diff --git a/lib/Email.php b/lib/Email.php index 6c1d721a..fad1bdf3 100644 --- a/lib/Email.php +++ b/lib/Email.php @@ -18,6 +18,19 @@ class Email{ public $Attachments = array(); public $PostmarkStream = null; + public function __construct(bool $isNoReplyEmail = false){ + if($isNoReplyEmail){ + $this->From = NO_REPLY_EMAIL_ADDRESS; + $this->FromName = 'Standard Ebooks'; + $this->ReplyTo = NO_REPLY_EMAIL_ADDRESS; + } + } + + + // ******* + // METHODS + // ******* + public function Send(): bool{ if($this->ReplyTo == ''){ $this->ReplyTo = $this->From; @@ -82,12 +95,4 @@ class Email{ return true; } - - public function __construct(bool $isNoReplyEmail = false){ - if($isNoReplyEmail){ - $this->From = NO_REPLY_EMAIL_ADDRESS; - $this->FromName = 'Standard Ebooks'; - $this->ReplyTo = NO_REPLY_EMAIL_ADDRESS; - } - } } diff --git a/lib/Feed.php b/lib/Feed.php index 06fd0d30..3060a9a5 100644 --- a/lib/Feed.php +++ b/lib/Feed.php @@ -13,6 +13,12 @@ class Feed{ public $Stylesheet = null; protected $XmlString = null; + /** + * @param string $title + * @param string $url + * @param string $path + * @param array $entries + */ public function __construct(string $title, string $url, string $path, array $entries){ $this->Url = $url; $this->Title = $title; @@ -20,6 +26,11 @@ class Feed{ $this->Entries = $entries; } + + // ******* + // METHODS + // ******* + protected function CleanXmlString(string $xmlString): string{ $tempFilename = tempnam('/tmp/', 'se-'); file_put_contents($tempFilename, $xmlString); diff --git a/lib/Formatter.php b/lib/Formatter.php index a05dfb34..a932258a 100644 --- a/lib/Formatter.php +++ b/lib/Formatter.php @@ -33,10 +33,10 @@ class Formatter{ } public static function ToPlainText(?string $text): string{ - return htmlspecialchars(trim($text), ENT_QUOTES, 'utf-8'); + return htmlspecialchars(trim($text ?? ''), ENT_QUOTES, 'utf-8'); } public static function ToPlainXmlText(?string $text): string{ - return htmlspecialchars(trim($text), ENT_QUOTES|ENT_XML1, 'utf-8'); + return htmlspecialchars(trim($text ?? ''), ENT_QUOTES|ENT_XML1, 'utf-8'); } } diff --git a/lib/HttpInput.php b/lib/HttpInput.php index b52caded..9ec3fa17 100644 --- a/lib/HttpInput.php +++ b/lib/HttpInput.php @@ -52,8 +52,10 @@ class HttpInput{ } /** - * @return array - */ + * @param string $variable + * @param array $default + * @return array + */ public static function GetArray(string $variable, array $default = null): ?array{ return self::GetHttpVar($variable, HTTP_VAR_ARRAY, GET, $default); } diff --git a/lib/Library.php b/lib/Library.php index c18a9736..5d1b38ea 100644 --- a/lib/Library.php +++ b/lib/Library.php @@ -8,8 +8,11 @@ use function Safe\usort; class Library{ /** - * @return array - */ + * @param string $query + * @param array $tags + * @param string $sort + * @return array + */ public static function FilterEbooks(string $query = null, array $tags = [], string $sort = null){ $ebooks = Library::GetEbooks(); $matches = $ebooks; diff --git a/lib/Log.php b/lib/Log.php index 4e1eee1a..f268aed9 100644 --- a/lib/Log.php +++ b/lib/Log.php @@ -16,6 +16,11 @@ class Log{ $this->LogFilePath = $logFilePath; } + + // ******* + // METHODS + // ******* + public function Write(string $text): void{ if($this->LogFilePath === null){ self::WriteErrorLogEntry($text); diff --git a/lib/NewsletterSubscriber.php b/lib/NewsletterSubscriber.php index a2a2f9b5..b9a6de28 100644 --- a/lib/NewsletterSubscriber.php +++ b/lib/NewsletterSubscriber.php @@ -2,6 +2,9 @@ use Safe\DateTime; use Ramsey\Uuid\Uuid; +/** + * @property string $Url + */ class NewsletterSubscriber extends PropertiesBase{ public $NewsletterSubscriberId; public $Uuid; @@ -14,6 +17,10 @@ class NewsletterSubscriber extends PropertiesBase{ public $Created; protected $Url = null; + // ******* + // GETTERS + // ******* + protected function GetUrl(): string{ if($this->Url === null){ $this->Url = SITE_URL . '/newsletter/subscribers/' . $this->Uuid; @@ -22,6 +29,11 @@ class NewsletterSubscriber extends PropertiesBase{ return $this->Url; } + + // ******* + // METHODS + // ******* + public function Create(): void{ $this->Validate(); @@ -78,6 +90,11 @@ class NewsletterSubscriber extends PropertiesBase{ } } + + // *********** + // ORM METHODS + // *********** + public static function Get(string $uuid): NewsletterSubscriber{ $subscribers = Db::Query('SELECT * from NewsletterSubscribers where Uuid = ?;', [$uuid], 'NewsletterSubscriber'); diff --git a/lib/OpdsAcquisitionFeed.php b/lib/OpdsAcquisitionFeed.php index 0c5b8a00..ef5f46c4 100644 --- a/lib/OpdsAcquisitionFeed.php +++ b/lib/OpdsAcquisitionFeed.php @@ -9,6 +9,11 @@ class OpdsAcquisitionFeed extends OpdsFeed{ $this->IsCrawlable = $isCrawlable; } + + // ******* + // METHODS + // ******* + protected function GetXmlString(): string{ if($this->XmlString === null){ $this->XmlString = $this->CleanXmlString(Template::OpdsAcquisitionFeed(['id' => $this->Id, 'url' => $this->Url, 'title' => $this->Title, 'parentUrl' => $this->Parent ? $this->Parent->Url : null, 'updated' => $this->Updated, 'isCrawlable' => $this->IsCrawlable, 'subtitle' => $this->Subtitle, 'entries' => $this->Entries])); diff --git a/lib/OpdsFeed.php b/lib/OpdsFeed.php index fa7f7505..e25b8230 100644 --- a/lib/OpdsFeed.php +++ b/lib/OpdsFeed.php @@ -5,12 +5,25 @@ use function Safe\file_put_contents; class OpdsFeed extends AtomFeed{ public $Parent = null; // OpdsNavigationFeed class + /** + * @param string $title + * @param string $subtitle + * @param string $url + * @param string $path + * @param array $entries + * @param OpdsNavigationFeed $parent + */ public function __construct(string $title, string $subtitle, string $url, string $path, array $entries, ?OpdsNavigationFeed $parent){ parent::__construct($title, $subtitle, $url, $path, $entries); $this->Parent = $parent; $this->Stylesheet = '/feeds/opds/style'; } + + // ******* + // METHODS + // ******* + protected function SaveUpdated(string $entryId, DateTime $updated): void{ // Only save the updated timestamp for the given entry ID in this file foreach($this->Entries as $entry){ diff --git a/lib/OpdsNavigationFeed.php b/lib/OpdsNavigationFeed.php index 4e077398..bdb7cca6 100644 --- a/lib/OpdsNavigationFeed.php +++ b/lib/OpdsNavigationFeed.php @@ -4,6 +4,14 @@ use Safe\DateTime; use function Safe\file_get_contents; class OpdsNavigationFeed extends OpdsFeed{ + /** + * @param string $title + * @param string $subtitle + * @param string $url + * @param string $path + * @param array $entries + * @param OpdsNavigationFeed $parent + */ public function __construct(string $title, string $subtitle, string $url, string $path, array $entries, ?OpdsNavigationFeed $parent){ parent::__construct($title, $subtitle, $url, $path, $entries, $parent); @@ -29,6 +37,11 @@ class OpdsNavigationFeed extends OpdsFeed{ } } + + // ******* + // METHODS + // ******* + protected function GetXmlString(): string{ if($this->XmlString === null){ $this->XmlString = $this->CleanXmlString(Template::OpdsNavigationFeed(['id' => $this->Id, 'url' => $this->Url, 'title' => $this->Title, 'parentUrl' => $this->Parent ? $this->Parent->Url : null, 'updated' => $this->Updated, 'subtitle' => $this->Subtitle, 'entries' => $this->Entries])); diff --git a/lib/Patron.php b/lib/Patron.php index 6519f6d8..6cd30bc5 100644 --- a/lib/Patron.php +++ b/lib/Patron.php @@ -1,15 +1,51 @@ Created = new DateTime(); + Db::Query('INSERT into Patrons (Created, UserId, IsAnonymous, AlternateName, IsSubscribedToEmails) values(?, ?, ?, ?, ?);', [$this->Created, $this->UserId, $this->IsAnonymous, $this->AlternateName, $this->IsSubscribedToEmails]); + + // If this is a patron for the first time, send the first-time patron email. + // Otherwise, send the returning patron email. + $isReturning = Db::QueryInt('SELECT count(*) from Patrons where UserId = ?', [$this->UserId]) > 1; + + $this->SendWelcomeEmail($isReturning); + } + + private function SendWelcomeEmail(bool $isReturning): void{ + if($this->User !== null){ + $em = new Email(); + $em->To = $this->User->Email; + $em->From = EDITOR_IN_CHIEF_EMAIL_ADDRESS; + $em->Subject = 'Thank you for supporting Standard Ebooks!'; + $em->Body = Template::EmailPatronsCircleWelcome(['isAnonymous' => $this->IsAnonymous, 'isReturning' => $isReturning]); + $em->TextBody = Template::EmailPatronsCircleWelcomeText(['isAnonymous' => $this->IsAnonymous, 'isReturning' => $isReturning]); + $em->Send(); + } + } + + + // *********** + // ORM METHODS + // *********** + public static function Get(?int $userId): Patron{ $result = Db::Query('SELECT * from Patrons where UserId = ?', [$userId], 'Patron'); @@ -29,28 +65,4 @@ class Patron extends PropertiesBase{ return $result[0]; } - - public function Create(): void{ - $this->Created = new DateTime(); - Db::Query('INSERT into Patrons (Created, UserId, IsAnonymous, AlternateName, IsSubscribedToEmails) values(?, ?, ?, ?, ?);', [$this->Created, $this->UserId, $this->IsAnonymous, $this->AlternateName, $this->IsSubscribedToEmails]); - - // If this is a patron for the first time, send the first-time patron email. - // Otherwise, send the returning patron email. - $isReturning = Db::QueryInt('SELECT count(*) from Patrons where UserId = ?', [$this->UserId]) > 1; - - $this->SendWelcomeEmail($isReturning); - } - - private function SendWelcomeEmail(bool $isReturning): void{ - $this->__get('User'); - if($this->User !== null){ - $em = new Email(); - $em->To = $this->User->Email; - $em->From = EDITOR_IN_CHIEF_EMAIL_ADDRESS; - $em->Subject = 'Thank you for supporting Standard Ebooks!'; - $em->Body = Template::EmailPatronsCircleWelcome(['isAnonymous' => $this->IsAnonymous, 'isReturning' => $isReturning]); - $em->TextBody = Template::EmailPatronsCircleWelcomeText(['isAnonymous' => $this->IsAnonymous, 'isReturning' => $isReturning]); - $em->Send(); - } - } } diff --git a/lib/Payment.php b/lib/Payment.php index f8c3dc1a..1154f4da 100644 --- a/lib/Payment.php +++ b/lib/Payment.php @@ -1,9 +1,12 @@ UserId === null){ // Check if we have to create a new user in the database diff --git a/lib/Poll.php b/lib/Poll.php index 0009e0ea..a720d547 100644 --- a/lib/Poll.php +++ b/lib/Poll.php @@ -2,6 +2,12 @@ use Safe\DateTime; use function Safe\usort; +/** + * @property string $Url + * @property array $PollItems + * @property array $PollItemsByWinner + * @property int $VoteCount + */ class Poll extends PropertiesBase{ public $PollId; public $Name; @@ -10,53 +16,62 @@ class Poll extends PropertiesBase{ public $Created; public $Start; public $End; - protected $Url = null; - protected $PollItems = null; - protected $PollItemsByWinner = null; - protected $VoteCount = null; + protected $_Url = null; + protected $_PollItems = null; + protected $_PollItemsByWinner = null; + protected $_VoteCount = null; + + + // ******* + // GETTERS + // ******* protected function GetUrl(): string{ - if($this->Url === null){ - $this->Url = '/patrons-circle/polls/' . $this->UrlName; + if($this->_Url === null){ + $this->_Url = '/patrons-circle/polls/' . $this->UrlName; } - return $this->Url; + return $this->_Url; } protected function GetVoteCount(): int{ - if($this->VoteCount === null){ - $this->VoteCount = Db::QueryInt('select count(*) from Votes v inner join PollItems pi on v.PollItemId = pi.PollItemId where pi.PollId = ?', [$this->PollId]); + if($this->_VoteCount === null){ + $this->_VoteCount = Db::QueryInt('select count(*) from Votes v inner join PollItems pi on v.PollItemId = pi.PollItemId where pi.PollId = ?', [$this->PollId]); } - return $this->VoteCount; + return $this->_VoteCount; } /** * @return array */ protected function GetPollItems(): array{ - if($this->PollItems === null){ - $this->PollItems = Db::Query('SELECT * from PollItems where PollId = ? order by SortOrder asc', [$this->PollId], 'PollItem'); + if($this->_PollItems === null){ + $this->_PollItems = Db::Query('SELECT * from PollItems where PollId = ? order by SortOrder asc', [$this->PollId], 'PollItem'); } - return $this->PollItems; + return $this->_PollItems; } /** * @return array */ protected function GetPollItemsByWinner(): array{ - if($this->PollItemsByWinner === null){ - $this->__get('PollItems'); - $this->PollItemsByWinner = $this->PollItems; - usort($this->PollItemsByWinner, function(PollItem $a, PollItem $b){ return $a->VoteCount <=> $b->VoteCount; }); + if($this->_PollItemsByWinner === null){ + $this->_PollItemsByWinner = $this->PollItems; + usort($this->_PollItemsByWinner, function(PollItem $a, PollItem $b){ return $a->VoteCount <=> $b->VoteCount; }); - $this->PollItemsByWinner = array_reverse($this->PollItemsByWinner); + $this->_PollItemsByWinner = array_reverse($this->_PollItemsByWinner); } - return $this->PollItemsByWinner; + return $this->_PollItemsByWinner; } + + // ******* + // METHODS + // ******* + public function IsActive(): bool{ $now = new DateTime(); if( ($this->Start !== null && $this->Start > $now) || ($this->End !== null && $this->End < $now)){ @@ -66,6 +81,11 @@ class Poll extends PropertiesBase{ return true; } + + // *********** + // ORM METHODS + // *********** + public static function Get(?int $pollId): Poll{ $result = Db::Query('SELECT * from Polls where PollId = ?', [$pollId], 'Poll'); diff --git a/lib/PollItem.php b/lib/PollItem.php index 688983a6..66d0c7ef 100644 --- a/lib/PollItem.php +++ b/lib/PollItem.php @@ -1,20 +1,35 @@ VoteCount === null){ - $this->VoteCount = Db::QueryInt('select count(*) from Votes v inner join PollItems pi on v.PollItemId = pi.PollItemId where pi.PollItemId = ?', [$this->PollItemId]); + if($this->_VoteCount === null){ + $this->_VoteCount = Db::QueryInt('select count(*) from Votes v inner join PollItems pi on v.PollItemId = pi.PollItemId where pi.PollItemId = ?', [$this->PollItemId]); } - return $this->VoteCount; + return $this->_VoteCount; } + + // *********** + // ORM METHODS + // *********** + public static function Get(?int $pollItemId): PollItem{ $result = Db::Query('SELECT * from PollItems where PollItemId = ?', [$pollItemId], 'PollItem'); diff --git a/lib/PropertiesBase.php b/lib/PropertiesBase.php index 9de57b49..3eb64f3a 100644 --- a/lib/PropertiesBase.php +++ b/lib/PropertiesBase.php @@ -8,27 +8,24 @@ abstract class PropertiesBase{ */ public function __get($var){ $function = 'Get' . $var; + $privateVar = '_' . $var; if(method_exists($this, $function)){ return $this->$function(); } - elseif(property_exists($this, $var . 'Id') && method_exists($var, 'Get')){ - // If our object has an VarId attribute, and the Var class also has a ::Get method, - // call it and return the result - if($this->$var === null && $this->{$var . 'Id'} !== null){ - $this->$var = $var::Get($this->{$var . 'Id'}); + elseif(property_exists($this, $var . 'Id') && property_exists($this, $privateVar) && method_exists($var, 'Get')){ + // If we're asking for a private `_Var` property, + // and we have a public `VarId` property, + // and the `Var` class also has a `Var::Get` method, + // call that method and return the result. + if($this->$privateVar === null && $this->{$var . 'Id'} !== null){ + $this->$privateVar = $var::Get($this->{$var . 'Id'}); } - return $this->$var; + return $this->$privateVar; } - elseif(substr($var, 0, 7) == 'Display'){ - // If we're asked for a DisplayXXX property and the getter doesn't exist, format as escaped HTML. - if($this->$var === null){ - $target = substr($var, 7, strlen($var)); - $this->$var = Formatter::ToPlainText($this->$target); - } - - return $this->$var; + elseif(property_exists($this, $privateVar)){ + return $this->{$privateVar}; } else{ return $this->$var; @@ -41,9 +38,14 @@ abstract class PropertiesBase{ */ public function __set(string $var, $val){ $function = 'Set' . $var; + $privateVar = '_' . $var; + if(method_exists($this, $function)){ $this->$function($val); } + elseif(property_exists($this, $privateVar)){ + $this->$privateVar = $val; + } else{ $this->$var = $val; } diff --git a/lib/RssFeed.php b/lib/RssFeed.php index 836baf80..b1069dd7 100644 --- a/lib/RssFeed.php +++ b/lib/RssFeed.php @@ -7,12 +7,24 @@ use function Safe\preg_replace; class RssFeed extends Feed{ public $Description; + /** + * @param string $title + * @param string $description + * @param string $url + * @param string $path + * @param array $entries + */ public function __construct(string $title, string $description, string $url, string $path, array $entries){ parent::__construct($title, $url, $path, $entries); $this->Description = $description; $this->Stylesheet = '/feeds/rss/style'; } + + // ******* + // METHODS + // ******* + protected function GetXmlString(): string{ if($this->XmlString === null){ $feed = Template::RssFeed(['url' => $this->Url, 'description' => $this->Description, 'title' => $this->Title, 'entries' => $this->Entries, 'updated' => (new DateTime())->format('r')]); diff --git a/lib/Template.php b/lib/Template.php index c60e47ca..5216b9ae 100644 --- a/lib/Template.php +++ b/lib/Template.php @@ -2,6 +2,10 @@ use function Safe\ob_end_clean; class Template{ + /** + * @param string $templateName + * @param array $arguments + */ protected static function Get(string $templateName, array $arguments = []): string{ // Expand the passed variables to make them available to the included template. // We use these funny names so that we can use 'name' and 'value' as template variables if we want to. @@ -17,6 +21,10 @@ class Template{ return $contents; } + /** + * @param string $function + * @param array $arguments + */ public static function __callStatic(string $function, array $arguments): string{ if(isset($arguments[0])){ return self::Get($function, $arguments[0]); diff --git a/lib/User.php b/lib/User.php index c6729437..ff17ed70 100644 --- a/lib/User.php +++ b/lib/User.php @@ -5,35 +5,16 @@ use Safe\DateTime; class User extends PropertiesBase{ public $UserId; public $FirstName; - protected $DisplayFirstName = null; public $LastName; - protected $DisplayLastName = null; protected $Name = null; - protected $DisplayName = null; public $Email; - protected $DisplayEmail; public $Created; public $Uuid; - public static function Get(?int $userId): User{ - $result = Db::Query('SELECT * from Users where UserId = ?', [$userId], 'User'); - if(sizeof($result) == 0){ - throw new Exceptions\InvalidUserException(); - } - - return $result[0]; - } - - public static function GetByEmail(?string $email): User{ - $result = Db::Query('SELECT * from Users where Email = ?', [$email], 'User'); - - if(sizeof($result) == 0){ - throw new Exceptions\InvalidUserException(); - } - - return $result[0]; - } + // ******* + // GETTERS + // ******* protected function GetName(): string{ if($this->Name === null){ @@ -43,6 +24,11 @@ class User extends PropertiesBase{ return $this->Name; } + + // ******* + // METHODS + // ******* + public function Create(): void{ $uuid = Uuid::uuid4(); $this->Uuid = $uuid->toString(); @@ -63,4 +49,29 @@ class User extends PropertiesBase{ $this->UserId = Db::GetLastInsertedId(); } + + + // *********** + // ORM METHODS + // *********** + + public static function Get(?int $userId): User{ + $result = Db::Query('SELECT * from Users where UserId = ?', [$userId], 'User'); + + if(sizeof($result) == 0){ + throw new Exceptions\InvalidUserException(); + } + + return $result[0]; + } + + public static function GetByEmail(?string $email): User{ + $result = Db::Query('SELECT * from Users where Email = ?', [$email], 'User'); + + if(sizeof($result) == 0){ + throw new Exceptions\InvalidUserException(); + } + + return $result[0]; + } } diff --git a/lib/Vote.php b/lib/Vote.php index fea7507c..f651d66b 100644 --- a/lib/Vote.php +++ b/lib/Vote.php @@ -1,23 +1,38 @@ Url === null){ - $this->Url = '/patrons-circle/polls/' . $this->PollItem->Poll->Url . '/votes/' . $this->UserId; + if($this->_Url === null){ + $this->_Url = '/patrons-circle/polls/' . $this->PollItem->Poll->Url . '/votes/' . $this->UserId; } - return $this->Url; + return $this->_Url; } + + // ******* + // METHODS + // ******* + protected function Validate(): void{ $error = new Exceptions\ValidationException(); @@ -29,7 +44,6 @@ class Vote extends PropertiesBase{ $error->Add(new Exceptions\PollItemRequiredException()); } else{ - $this->__get('PollItem'); if($this->PollItem === null){ $error->Add(new Exceptions\InvalidPollException()); } @@ -43,7 +57,6 @@ class Vote extends PropertiesBase{ // Basic sanity checks done, now check if we've already voted // in this poll - $this->__get('User'); if($this->User === null){ $error->Add(new Exceptions\InvalidPatronException()); } diff --git a/www/ebooks/author.php b/www/ebooks/author.php index 84e44deb..df90b342 100644 --- a/www/ebooks/author.php +++ b/www/ebooks/author.php @@ -2,7 +2,7 @@ require_once('Core.php'); try{ - $urlPath = trim(str_replace('.', '', HttpInput::Str(GET, 'url-path', true, '')), '/'); // Contains the portion of the URL (without query string) that comes after https://standardebooks.org/ebooks/ + $urlPath = trim(str_replace('.', '', HttpInput::Str(GET, 'url-path', true) ?? ''), '/'); // Contains the portion of the URL (without query string) that comes after https://standardebooks.org/ebooks/ $wwwFilesystemPath = EBOOKS_DIST_PATH . $urlPath; // Path to the deployed WWW files for this ebook if($urlPath == '' || mb_stripos($wwwFilesystemPath, EBOOKS_DIST_PATH) !== 0 || !is_dir($wwwFilesystemPath)){ diff --git a/www/ebooks/ebook.php b/www/ebooks/ebook.php index 77c08f65..7322e3f9 100644 --- a/www/ebooks/ebook.php +++ b/www/ebooks/ebook.php @@ -9,7 +9,7 @@ use function Safe\apcu_fetch; use function Safe\shuffle; try{ - $urlPath = trim(str_replace('.', '', HttpInput::Str(GET, 'url-path', true, '')), '/'); // Contains the portion of the URL (without query string) that comes after https://standardebooks.org/ebooks/ + $urlPath = trim(str_replace('.', '', HttpInput::Str(GET, 'url-path', true) ?? ''), '/'); // Contains the portion of the URL (without query string) that comes after https://standardebooks.org/ebooks/ $wwwFilesystemPath = EBOOKS_DIST_PATH . $urlPath; // Path to the deployed WWW files for this ebook if($urlPath == '' || mb_stripos($wwwFilesystemPath, EBOOKS_DIST_PATH) !== 0){ diff --git a/www/ebooks/index.php b/www/ebooks/index.php index b656e30d..34cda0ba 100644 --- a/www/ebooks/index.php +++ b/www/ebooks/index.php @@ -4,10 +4,10 @@ require_once('Core.php'); use function Safe\preg_replace; try{ - $page = HttpInput::Int(GET, 'page', 1); - $perPage = HttpInput::Int(GET, 'per-page', EBOOKS_PER_PAGE); - $query = HttpInput::Str(GET, 'query', false); - $tags = HttpInput::GetArray('tags', []); + $page = HttpInput::Int(GET, 'page') ?? 1; + $perPage = HttpInput::Int(GET, 'per-page') ?? EBOOKS_PER_PAGE; + $query = HttpInput::Str(GET, 'query', false) ?? ''; + $tags = HttpInput::GetArray('tags') ?? []; $collection = HttpInput::Str(GET, 'collection', false); $view = HttpInput::Str(GET, 'view', false); $sort = HttpInput::Str(GET, 'sort', false); @@ -41,10 +41,6 @@ try{ $sort = null; } - if($query === ''){ - $query = null; - } - if(sizeof($tags) == 1 && mb_strtolower($tags[0]) == 'all'){ $tags = []; } @@ -82,7 +78,7 @@ try{ } } else{ - $ebooks = Library::FilterEbooks($query, $tags, $sort); + $ebooks = Library::FilterEbooks($query != '' ? $query : null, $tags, $sort); $pageTitle = 'Browse Standard Ebooks'; $pageHeader = 'Browse Ebooks'; $pages = ceil(sizeof($ebooks) / $perPage); @@ -154,7 +150,7 @@ catch(Exceptions\InvalidCollectionException $ex){ href="/ebooks/?page=&" rel="next" aria-disabled="true">Next - 0 && $query === null && sizeof($tags) == 0 && $collection === null && $page == 1){ ?> + 0 && $query == '' && sizeof($tags) == 0 && $collection === null && $page == 1){ ?> diff --git a/www/feeds/atom/search.php b/www/feeds/atom/search.php index b44f5753..5a32d511 100644 --- a/www/feeds/atom/search.php +++ b/www/feeds/atom/search.php @@ -5,9 +5,9 @@ use Safe\DateTime; $ebooks = []; try{ - $query = HttpInput::Str(GET, 'query', false); + $query = HttpInput::Str(GET, 'query', false) ?? ''; - if($query !== null){ + if($query !== ''){ $ebooks = Library::Search($query); } } diff --git a/www/feeds/opds/search.php b/www/feeds/opds/search.php index 8babe049..cc42410f 100644 --- a/www/feeds/opds/search.php +++ b/www/feeds/opds/search.php @@ -5,9 +5,9 @@ use Safe\DateTime; $ebooks = []; try{ - $query = HttpInput::Str(GET, 'query', false); + $query = HttpInput::Str(GET, 'query', false) ?? ''; - if($query !== null){ + if($query !== ''){ $ebooks = Library::Search($query); } } diff --git a/www/feeds/rss/search.php b/www/feeds/rss/search.php index 8dacdbb3..57bf5665 100644 --- a/www/feeds/rss/search.php +++ b/www/feeds/rss/search.php @@ -5,9 +5,9 @@ use Safe\DateTime; $ebooks = []; try{ - $query = HttpInput::Str(GET, 'query', false); + $query = HttpInput::Str(GET, 'query', false) ?? ''; - if($query !== null){ + if($query !== ''){ $ebooks = Library::Search($query); } } diff --git a/www/manual/index.php b/www/manual/index.php index 853c96f2..fe1ff054 100644 --- a/www/manual/index.php +++ b/www/manual/index.php @@ -8,7 +8,7 @@ use function Safe\sort; $currentManual = Manual::GetLatestVersion(); -$url = HttpInput::Str(GET, 'url', true, ''); +$url = HttpInput::Str(GET, 'url', true) ?? ''; $url = preg_replace('|^/|ius', '', $url); $url = preg_replace('|\.php$|ius', '', $url); $url = preg_replace('|/$|ius', '', $url); diff --git a/www/newsletter/subscribers/post.php b/www/newsletter/subscribers/post.php index 73785509..34610006 100644 --- a/www/newsletter/subscribers/post.php +++ b/www/newsletter/subscribers/post.php @@ -36,9 +36,9 @@ try{ $subscriber->IsSubscribedToNewsletter = HttpInput::Bool(POST, 'newsletter', false); $subscriber->IsSubscribedToSummary = HttpInput::Bool(POST, 'monthlysummary', false); - $captcha = $_SESSION['captcha'] ?? null; + $captcha = $_SESSION['captcha'] ?? ''; - if($captcha === null || mb_strtolower($captcha) !== mb_strtolower(HttpInput::Str(POST, 'captcha', false))){ + if($captcha === '' || mb_strtolower($captcha) !== mb_strtolower(HttpInput::Str(POST, 'captcha', false) ?? '')){ throw new Exceptions\ValidationException(new Exceptions\InvalidCaptchaException()); }