mirror of
https://github.com/standardebooks/web.git
synced 2025-07-12 01:22:23 -04:00
Update PropertiesBase to new patterns and improve static analysis checks
This commit is contained in:
parent
5f0b57f7e9
commit
6c8414f844
33 changed files with 335 additions and 148 deletions
|
@ -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
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
<?
|
||||
require_once('/standardebooks.org/web/lib/Core.php');
|
||||
|
||||
//file_get_contents('/home/alex/donations.csv');
|
||||
|
||||
$csv = array_map( 'str_getcsv', file( '/home/alex/donations.csv') );
|
||||
vdd($csv);
|
|
@ -12,6 +12,13 @@ class AtomFeed extends Feed{
|
|||
public $Updated = null;
|
||||
public $Subtitle = null;
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
* @param string $subtitle
|
||||
* @param string $url
|
||||
* @param string $path
|
||||
* @param array<Ebook> $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]);
|
||||
|
|
|
@ -6,6 +6,9 @@ class Db{
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
* @param array<mixed> $args
|
||||
* @param string $class
|
||||
* @return Array<mixed>
|
||||
*/
|
||||
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<mixed> $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(*).
|
||||
|
||||
|
|
|
@ -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<mixed> $params
|
||||
* @param string $class
|
||||
* @return Array<mixed>
|
||||
*/
|
||||
public function Query(string $sql, array $params = [], string $class = 'stdClass'): array{
|
||||
|
|
|
@ -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){
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
11
lib/Feed.php
11
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<Ebook> $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);
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,8 +52,10 @@ class HttpInput{
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array<string>
|
||||
*/
|
||||
* @param string $variable
|
||||
* @param array<mixed> $default
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function GetArray(string $variable, array $default = null): ?array{
|
||||
return self::GetHttpVar($variable, HTTP_VAR_ARRAY, GET, $default);
|
||||
}
|
||||
|
|
|
@ -8,8 +8,11 @@ use function Safe\usort;
|
|||
|
||||
class Library{
|
||||
/**
|
||||
* @return array<Ebook>
|
||||
*/
|
||||
* @param string $query
|
||||
* @param array<string> $tags
|
||||
* @param string $sort
|
||||
* @return array<Ebook>
|
||||
*/
|
||||
public static function FilterEbooks(string $query = null, array $tags = [], string $sort = null){
|
||||
$ebooks = Library::GetEbooks();
|
||||
$matches = $ebooks;
|
||||
|
|
|
@ -16,6 +16,11 @@ class Log{
|
|||
$this->LogFilePath = $logFilePath;
|
||||
}
|
||||
|
||||
|
||||
// *******
|
||||
// METHODS
|
||||
// *******
|
||||
|
||||
public function Write(string $text): void{
|
||||
if($this->LogFilePath === null){
|
||||
self::WriteErrorLogEntry($text);
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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]));
|
||||
|
|
|
@ -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<Ebook> $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){
|
||||
|
|
|
@ -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<Ebook> $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]));
|
||||
|
|
|
@ -1,15 +1,51 @@
|
|||
<?
|
||||
use Safe\DateTime;
|
||||
|
||||
/**
|
||||
* @property User $User
|
||||
*/
|
||||
class Patron extends PropertiesBase{
|
||||
protected $User = null;
|
||||
public $UserId = null;
|
||||
protected $User = null;
|
||||
public $IsAnonymous;
|
||||
public $AlternateName;
|
||||
public $IsSubscribedToEmails;
|
||||
public $Created = null;
|
||||
public $Ended = null;
|
||||
|
||||
|
||||
// *******
|
||||
// METHODS
|
||||
// *******
|
||||
|
||||
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{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
<?
|
||||
|
||||
/**
|
||||
* @property User $User
|
||||
*/
|
||||
class Payment extends PropertiesBase{
|
||||
public $PaymentId;
|
||||
protected $User = null;
|
||||
public $UserId = null;
|
||||
protected $_User = null;
|
||||
public $Created;
|
||||
public $ChannelId;
|
||||
public $TransactionId;
|
||||
|
@ -11,6 +14,11 @@ class Payment extends PropertiesBase{
|
|||
public $Fee;
|
||||
public $IsRecurring;
|
||||
|
||||
|
||||
// *******
|
||||
// METHODS
|
||||
// *******
|
||||
|
||||
public function Create(): void{
|
||||
if($this->UserId === null){
|
||||
// Check if we have to create a new user in the database
|
||||
|
|
58
lib/Poll.php
58
lib/Poll.php
|
@ -2,6 +2,12 @@
|
|||
use Safe\DateTime;
|
||||
use function Safe\usort;
|
||||
|
||||
/**
|
||||
* @property string $Url
|
||||
* @property array<PollItem> $PollItems
|
||||
* @property array<PollItem> $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<PollItem>
|
||||
*/
|
||||
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<PollItem>
|
||||
*/
|
||||
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');
|
||||
|
||||
|
|
|
@ -1,20 +1,35 @@
|
|||
<?
|
||||
|
||||
/**
|
||||
* @property int $VoteCount
|
||||
* @property Poll $Poll
|
||||
*/
|
||||
class PollItem extends PropertiesBase{
|
||||
public $PollItemId;
|
||||
public $PollId;
|
||||
public $Name;
|
||||
public $Description;
|
||||
protected $VoteCount = null;
|
||||
protected $Poll = null;
|
||||
protected $_VoteCount = null;
|
||||
protected $_Poll = null;
|
||||
|
||||
|
||||
// *******
|
||||
// GETTERS
|
||||
// *******
|
||||
|
||||
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.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');
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<Ebook> $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')]);
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
use function Safe\ob_end_clean;
|
||||
|
||||
class Template{
|
||||
/**
|
||||
* @param string $templateName
|
||||
* @param array<mixed> $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<mixed> $arguments
|
||||
*/
|
||||
public static function __callStatic(string $function, array $arguments): string{
|
||||
if(isset($arguments[0])){
|
||||
return self::Get($function, $arguments[0]);
|
||||
|
|
55
lib/User.php
55
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];
|
||||
}
|
||||
}
|
||||
|
|
29
lib/Vote.php
29
lib/Vote.php
|
@ -1,23 +1,38 @@
|
|||
<?
|
||||
use Safe\DateTime;
|
||||
|
||||
/**
|
||||
* @property User $User
|
||||
* @property PollItem $PollItem
|
||||
* @property string $Url
|
||||
*/
|
||||
class Vote extends PropertiesBase{
|
||||
public $VoteId;
|
||||
public $UserId;
|
||||
protected $User = null;
|
||||
protected $_User = null;
|
||||
public $Created;
|
||||
public $PollItemId;
|
||||
protected $PollItem = null;
|
||||
protected $Url = null;
|
||||
protected $_PollItem = null;
|
||||
protected $_Url = null;
|
||||
|
||||
|
||||
// *******
|
||||
// GETTERS
|
||||
// *******
|
||||
|
||||
protected function GetUrl(): string{
|
||||
if($this->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());
|
||||
}
|
||||
|
|
|
@ -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)){
|
||||
|
|
|
@ -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){
|
||||
|
|
|
@ -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){
|
|||
<a<? if($page < ceil($totalEbooks / $perPage)){ ?> href="/ebooks/?page=<?= $page + 1 ?><? if($queryString != ''){ ?>&<?= $queryString ?><? } ?>" rel="next"<? }else{ ?> aria-disabled="true"<? } ?>>Next</a>
|
||||
</nav>
|
||||
<? } ?>
|
||||
<? if(sizeof($ebooks) > 0 && $query === null && sizeof($tags) == 0 && $collection === null && $page == 1){ ?>
|
||||
<? if(sizeof($ebooks) > 0 && $query == '' && sizeof($tags) == 0 && $collection === null && $page == 1){ ?>
|
||||
<?= Template::ContributeAlert() ?>
|
||||
<? } ?>
|
||||
</main>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue