mirror of
https://github.com/standardebooks/web.git
synced 2025-07-05 22:30:30 -04:00
Add poll system for Patrons Circle
This commit is contained in:
parent
3555d53615
commit
2ef5ce6551
44 changed files with 717 additions and 98 deletions
|
@ -258,6 +258,16 @@ Define webroot /standardebooks.org/web
|
||||||
# Newsletter
|
# Newsletter
|
||||||
RewriteRule ^/newsletter$ /newsletter/subscribers/new.php
|
RewriteRule ^/newsletter$ /newsletter/subscribers/new.php
|
||||||
RewriteRule ^/newsletter/subscribers/([^\./]+?)/(delete|confirm)$ /newsletter/subscribers/$2.php?uuid=$1
|
RewriteRule ^/newsletter/subscribers/([^\./]+?)/(delete|confirm)$ /newsletter/subscribers/$2.php?uuid=$1
|
||||||
|
|
||||||
|
# Polls
|
||||||
|
RewriteRule ^/patrons-circle/polls/([^/]+)$ /patrons-circle/polls/get.php?pollurlname=$1
|
||||||
|
RewriteRule ^/patrons-circle/polls/([^/]+)/votes/(new|success)$ /patrons-circle/polls/votes/$2.php?pollurlname=$1
|
||||||
|
|
||||||
|
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^get$/"
|
||||||
|
RewriteRule ^/patrons-circle/polls/([^/]+)/votes$ /patrons-circle/polls/votes/index.php?pollurlname=$1
|
||||||
|
|
||||||
|
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
|
||||||
|
RewriteRule ^/patrons-circle/polls/([^/]+)/votes$ /patrons-circle/polls/votes/post.php?pollurlname=$1
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
|
|
||||||
<VirtualHost *:80>
|
<VirtualHost *:80>
|
||||||
|
|
|
@ -257,4 +257,14 @@ Define webroot /standardebooks.org/web
|
||||||
# Newsletter
|
# Newsletter
|
||||||
RewriteRule ^/newsletter$ /newsletter/subscribers/new.php
|
RewriteRule ^/newsletter$ /newsletter/subscribers/new.php
|
||||||
RewriteRule ^/newsletter/subscribers/([^\./]+?)/(delete|confirm)$ /newsletter/subscribers/$2.php?uuid=$1
|
RewriteRule ^/newsletter/subscribers/([^\./]+?)/(delete|confirm)$ /newsletter/subscribers/$2.php?uuid=$1
|
||||||
|
|
||||||
|
# Polls
|
||||||
|
RewriteRule ^/patrons-circle/polls/([^/]+)$ /patrons-circle/polls/get.php?pollurlname=$1
|
||||||
|
RewriteRule ^/patrons-circle/polls/([^/]+)/votes/(new|success)$ /patrons-circle/polls/votes/$2.php?pollurlname=$1
|
||||||
|
|
||||||
|
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^get$/"
|
||||||
|
RewriteRule ^/patrons-circle/polls/([^/]+)/votes$ /patrons-circle/polls/votes/index.php?pollurlname=$1
|
||||||
|
|
||||||
|
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
|
||||||
|
RewriteRule ^/patrons-circle/polls/([^/]+)/votes$ /patrons-circle/polls/votes/post.php?pollurlname=$1
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
|
|
|
@ -19,6 +19,12 @@ parameters:
|
||||||
- '#Method Ebook::NullIfEmpty\(\) has parameter \$elements 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 no return type specified.#'
|
||||||
- '#Method HttpInput::GetHttpVar\(\) has parameter \$default with no 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:
|
level:
|
||||||
7
|
7
|
||||||
paths:
|
paths:
|
||||||
|
|
8
config/sql/se/PollItems.sql
Normal file
8
config/sql/se/PollItems.sql
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
CREATE TABLE `PollItems` (
|
||||||
|
`PollItemId` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`PollId` int(10) unsigned NOT NULL,
|
||||||
|
`Name` varchar(255) NOT NULL,
|
||||||
|
`Description` text DEFAULT NULL,
|
||||||
|
`SortOrder` tinyint(3) unsigned NOT NULL,
|
||||||
|
PRIMARY KEY (`PollItemId`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
11
config/sql/se/Polls.sql
Normal file
11
config/sql/se/Polls.sql
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
CREATE TABLE `Polls` (
|
||||||
|
`PollId` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`Created` datetime NOT NULL,
|
||||||
|
`Name` varchar(255) NOT NULL,
|
||||||
|
`UrlName` varchar(255) NOT NULL,
|
||||||
|
`Description` text DEFAULT NULL,
|
||||||
|
`Start` datetime DEFAULT NULL,
|
||||||
|
`End` datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`PollId`),
|
||||||
|
UNIQUE KEY `idxUnique` (`UrlName`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
6
config/sql/se/Votes.sql
Normal file
6
config/sql/se/Votes.sql
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
CREATE TABLE `Votes` (
|
||||||
|
`UserId` int(11) unsigned NOT NULL,
|
||||||
|
`PollItemId` int(11) unsigned NOT NULL,
|
||||||
|
`Created` datetime NOT NULL,
|
||||||
|
UNIQUE KEY `idxUnique` (`PollItemId`,`UserId`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
@ -59,6 +59,13 @@ const HTTP_VAR_BOOL = 2;
|
||||||
const HTTP_VAR_DEC = 3;
|
const HTTP_VAR_DEC = 3;
|
||||||
const HTTP_VAR_ARRAY = 4;
|
const HTTP_VAR_ARRAY = 4;
|
||||||
|
|
||||||
|
const HTTP_GET = 0;
|
||||||
|
const HTTP_POST = 1;
|
||||||
|
const HTTP_PATCH = 2;
|
||||||
|
const HTTP_PUT = 3;
|
||||||
|
const HTTP_DELETE = 4;
|
||||||
|
const HTTP_HEAD = 5;
|
||||||
|
|
||||||
const VIEW_GRID = 'grid';
|
const VIEW_GRID = 'grid';
|
||||||
const VIEW_LIST = 'list';
|
const VIEW_LIST = 'list';
|
||||||
|
|
||||||
|
|
6
lib/Exceptions/InvalidPatronException.php
Normal file
6
lib/Exceptions/InvalidPatronException.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?
|
||||||
|
namespace Exceptions;
|
||||||
|
|
||||||
|
class InvalidPatronException extends SeException{
|
||||||
|
protected $message = 'We couldn’t locate you in the Patrons Circle.';
|
||||||
|
}
|
6
lib/Exceptions/InvalidPollException.php
Normal file
6
lib/Exceptions/InvalidPollException.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?
|
||||||
|
namespace Exceptions;
|
||||||
|
|
||||||
|
class InvalidPollException extends SeException{
|
||||||
|
protected $message = 'We couldn’t locate that poll.';
|
||||||
|
}
|
6
lib/Exceptions/InvalidPollItemException.php
Normal file
6
lib/Exceptions/InvalidPollItemException.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?
|
||||||
|
namespace Exceptions;
|
||||||
|
|
||||||
|
class InvalidPollItemException extends SeException{
|
||||||
|
protected $message = 'We couldn’t locate that poll item.';
|
||||||
|
}
|
6
lib/Exceptions/InvalidUserException.php
Normal file
6
lib/Exceptions/InvalidUserException.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?
|
||||||
|
namespace Exceptions;
|
||||||
|
|
||||||
|
class InvalidUserException extends SeException{
|
||||||
|
protected $message = 'We couldn’t locate you in our system.';
|
||||||
|
}
|
6
lib/Exceptions/PollClosedException.php
Normal file
6
lib/Exceptions/PollClosedException.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?
|
||||||
|
namespace Exceptions;
|
||||||
|
|
||||||
|
class PollClosedException extends SeException{
|
||||||
|
protected $message = 'This poll is not open to voting right now.';
|
||||||
|
}
|
6
lib/Exceptions/PollItemRequiredException.php
Normal file
6
lib/Exceptions/PollItemRequiredException.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?
|
||||||
|
namespace Exceptions;
|
||||||
|
|
||||||
|
class PollItemRequiredException extends SeException{
|
||||||
|
protected $message = 'You must select an item to vote on.';
|
||||||
|
}
|
|
@ -8,6 +8,12 @@ class ValidationException extends SeException{
|
||||||
public $HasExceptions = false;
|
public $HasExceptions = false;
|
||||||
public $IsFatal = false;
|
public $IsFatal = false;
|
||||||
|
|
||||||
|
public function __construct(?\Exception $exception = null){
|
||||||
|
if($exception !== null){
|
||||||
|
$this->Add($exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function __toString(): string{
|
public function __toString(): string{
|
||||||
$output = '';
|
$output = '';
|
||||||
foreach($this->Exceptions as $exception){
|
foreach($this->Exceptions as $exception){
|
||||||
|
|
6
lib/Exceptions/VoteExistsException.php
Normal file
6
lib/Exceptions/VoteExistsException.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?
|
||||||
|
namespace Exceptions;
|
||||||
|
|
||||||
|
class VoteExistsException extends SeException{
|
||||||
|
protected $message = 'You’ve already voted in this poll.';
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
<?
|
<?
|
||||||
|
use Safe\DateTime;
|
||||||
use function Safe\file_get_contents;
|
use function Safe\file_get_contents;
|
||||||
use function Safe\file_put_contents;
|
use function Safe\file_put_contents;
|
||||||
use function Safe\tempnam;
|
use function Safe\tempnam;
|
||||||
|
|
|
@ -1,5 +1,30 @@
|
||||||
<?
|
<?
|
||||||
|
use function Safe\preg_match;
|
||||||
|
|
||||||
class HttpInput{
|
class HttpInput{
|
||||||
|
public static function RequestMethod(): int{
|
||||||
|
$method = $_POST['_method'] ?? $_SERVER['REQUEST_METHOD'];
|
||||||
|
|
||||||
|
switch($method){
|
||||||
|
case 'POST':
|
||||||
|
return HTTP_POST;
|
||||||
|
case 'PUT':
|
||||||
|
return HTTP_PUT;
|
||||||
|
case 'DELETE':
|
||||||
|
return HTTP_DELETE;
|
||||||
|
case 'PATCH':
|
||||||
|
return HTTP_PATCH;
|
||||||
|
case 'HEAD':
|
||||||
|
return HTTP_HEAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HTTP_GET;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function RequestType(): int{
|
||||||
|
return preg_match('/\btext\/html\b/ius', $_SERVER['HTTP_ACCEPT']) ? WEB : REST;
|
||||||
|
}
|
||||||
|
|
||||||
public static function Str(string $type, string $variable, bool $allowEmptyString = true, string $default = null): ?string{
|
public static function Str(string $type, string $variable, bool $allowEmptyString = true, string $default = null): ?string{
|
||||||
$var = self::GetHttpVar($variable, HTTP_VAR_STR, $type, $default);
|
$var = self::GetHttpVar($variable, HTTP_VAR_STR, $type, $default);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?
|
<?
|
||||||
|
use Safe\DateTime;
|
||||||
use Ramsey\Uuid\Uuid;
|
use Ramsey\Uuid\Uuid;
|
||||||
|
|
||||||
class NewsletterSubscriber extends PropertiesBase{
|
class NewsletterSubscriber extends PropertiesBase{
|
||||||
|
@ -26,9 +27,10 @@ class NewsletterSubscriber extends PropertiesBase{
|
||||||
|
|
||||||
$uuid = Uuid::uuid4();
|
$uuid = Uuid::uuid4();
|
||||||
$this->Uuid = $uuid->toString();
|
$this->Uuid = $uuid->toString();
|
||||||
|
$this->Timestamp = new DateTime();
|
||||||
|
|
||||||
try{
|
try{
|
||||||
Db::Query('insert into NewsletterSubscribers (Email, Uuid, FirstName, LastName, IsConfirmed, IsSubscribedToNewsletter, IsSubscribedToSummary, Timestamp) values (?, ?, ?, ?, ?, ?, ?, utc_timestamp());', [$this->Email, $this->Uuid, $this->FirstName, $this->LastName, false, $this->IsSubscribedToNewsletter, $this->IsSubscribedToSummary]);
|
Db::Query('INSERT into NewsletterSubscribers (Email, Uuid, FirstName, LastName, IsConfirmed, IsSubscribedToNewsletter, IsSubscribedToSummary, Timestamp) values (?, ?, ?, ?, ?, ?, ?, ?);', [$this->Email, $this->Uuid, $this->FirstName, $this->LastName, false, $this->IsSubscribedToNewsletter, $this->IsSubscribedToSummary, $this->Timestamp]);
|
||||||
}
|
}
|
||||||
catch(PDOException $ex){
|
catch(PDOException $ex){
|
||||||
if($ex->errorInfo[1] == 1062){
|
if($ex->errorInfo[1] == 1062){
|
||||||
|
@ -53,11 +55,11 @@ class NewsletterSubscriber extends PropertiesBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function Confirm(): void{
|
public function Confirm(): void{
|
||||||
Db::Query('update NewsletterSubscribers set IsConfirmed = true where NewsletterSubscriberId = ?;', [$this->NewsletterSubscriberId]);
|
Db::Query('UPDATE NewsletterSubscribers set IsConfirmed = true where NewsletterSubscriberId = ?;', [$this->NewsletterSubscriberId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function Delete(): void{
|
public function Delete(): void{
|
||||||
Db::Query('delete from NewsletterSubscribers where NewsletterSubscriberId = ?;', [$this->NewsletterSubscriberId]);
|
Db::Query('DELETE from NewsletterSubscribers where NewsletterSubscriberId = ?;', [$this->NewsletterSubscriberId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function Validate(): void{
|
public function Validate(): void{
|
||||||
|
@ -77,11 +79,7 @@ class NewsletterSubscriber extends PropertiesBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function Get(string $uuid): NewsletterSubscriber{
|
public static function Get(string $uuid): NewsletterSubscriber{
|
||||||
if($uuid == ''){
|
$subscribers = Db::Query('SELECT * from NewsletterSubscribers where Uuid = ?;', [$uuid], 'NewsletterSubscriber');
|
||||||
throw new Exceptions\InvalidNewsletterSubscriberException();
|
|
||||||
}
|
|
||||||
|
|
||||||
$subscribers = Db::Query('select * from NewsletterSubscribers where Uuid = ?;', [$uuid], 'NewsletterSubscriber');
|
|
||||||
|
|
||||||
if(sizeof($subscribers) == 0){
|
if(sizeof($subscribers) == 0){
|
||||||
throw new Exceptions\InvalidNewsletterSubscriberException();
|
throw new Exceptions\InvalidNewsletterSubscriberException();
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?
|
<?
|
||||||
|
use Safe\DateTime;
|
||||||
use function Safe\file_put_contents;
|
use function Safe\file_put_contents;
|
||||||
|
|
||||||
class OpdsFeed extends AtomFeed{
|
class OpdsFeed extends AtomFeed{
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
<?
|
|
||||||
use Safe\DateTime;
|
|
||||||
use function Safe\substr;
|
|
||||||
|
|
||||||
abstract class OrmBase{
|
|
||||||
final public function __construct(){
|
|
||||||
// Satisfy PHPStan and prevent child classes from having their own constructor
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function FillObject(Object $object, array $row): Object{
|
|
||||||
foreach($row as $property => $value){
|
|
||||||
if(substr($property, strlen($property) - 5) == 'Cache'){
|
|
||||||
$property = substr($property, 0, strlen($property) - 5);
|
|
||||||
$object->$property = $value;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
$object->$property = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $object;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function FromRow(array $row): Object{
|
|
||||||
return self::FillObject(new static(), $row);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,22 +10,30 @@ class Patron extends PropertiesBase{
|
||||||
public $Timestamp = null;
|
public $Timestamp = null;
|
||||||
public $DeactivatedTimestamp = null;
|
public $DeactivatedTimestamp = null;
|
||||||
|
|
||||||
public static function Get(int $userId): ?Patron{
|
public static function Get(?int $userId): Patron{
|
||||||
$result = Db::Query('select * from Patrons where UserId = ?', [$userId], 'Patron');
|
$result = Db::Query('SELECT * from Patrons where UserId = ?', [$userId], 'Patron');
|
||||||
|
|
||||||
return $result[0] ?? null;
|
if(sizeof($result) == 0){
|
||||||
}
|
throw new Exceptions\InvalidPatronException();
|
||||||
|
|
||||||
protected function GetUser(): ?User{
|
|
||||||
if($this->User === null && $this->UserId !== null){
|
|
||||||
$this->User = User::Get($this->UserId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->User;
|
return $result[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function GetByEmail(?string $email): Patron{
|
||||||
|
$result = Db::Query('SELECT p.* from Patrons p inner join Users u on p.UserId = u.UserId where u.Email = ?', [$email], 'Patron');
|
||||||
|
|
||||||
|
if(sizeof($result) == 0){
|
||||||
|
throw new Exceptions\InvalidPatronException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function Create(bool $sendEmail = true): void{
|
public function Create(bool $sendEmail = true): void{
|
||||||
Db::Query('insert into Patrons (Timestamp, UserId, IsAnonymous, AlternateName, IsSubscribedToEmail) values(utc_timestamp(), ?, ?, ?, ?);', [$this->UserId, $this->IsAnonymous, $this->AlternateName, $this->IsSubscribedToEmail]);
|
$this->Timestamp = new DateTime();
|
||||||
|
|
||||||
|
Db::Query('INSERT into Patrons (Timestamp, UserId, IsAnonymous, AlternateName, IsSubscribedToEmail) values(?, ?, ?, ?, ?);', [$this->Timestamp, $this->UserId, $this->IsAnonymous, $this->AlternateName, $this->IsSubscribedToEmail]);
|
||||||
|
|
||||||
if($sendEmail){
|
if($sendEmail){
|
||||||
$this->SendWelcomeEmail();
|
$this->SendWelcomeEmail();
|
||||||
|
@ -33,7 +41,7 @@ class Patron extends PropertiesBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function Reactivate(bool $sendEmail = true): void{
|
public function Reactivate(bool $sendEmail = true): void{
|
||||||
Db::Query('update Patrons set Timestamp = utc_timestamp(), DeactivatedTimestamp = null, IsAnonymous = ?, IsSubscribedToEmail = ?, AlternateName = ? where UserId = ?;', [$this->IsAnonymous, $this->IsSubscribedToEmail, $this->AlternateName, $this->UserId]);
|
Db::Query('UPDATE Patrons set Timestamp = utc_timestamp(), DeactivatedTimestamp = null, IsAnonymous = ?, IsSubscribedToEmail = ?, AlternateName = ? where UserId = ?;', [$this->IsAnonymous, $this->IsSubscribedToEmail, $this->AlternateName, $this->UserId]);
|
||||||
$this->Timestamp = new DateTime();
|
$this->Timestamp = new DateTime();
|
||||||
$this->DeactivatedTimestamp = null;
|
$this->DeactivatedTimestamp = null;
|
||||||
|
|
||||||
|
@ -43,7 +51,7 @@ class Patron extends PropertiesBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
private function SendWelcomeEmail(): void{
|
private function SendWelcomeEmail(): void{
|
||||||
$this->GetUser();
|
$this->__get('User');
|
||||||
if($this->User !== null){
|
if($this->User !== null){
|
||||||
$em = new Email();
|
$em = new Email();
|
||||||
$em->To = $this->User->Email;
|
$em->To = $this->User->Email;
|
||||||
|
|
|
@ -11,21 +11,13 @@ class Payment extends PropertiesBase{
|
||||||
public $Fee;
|
public $Fee;
|
||||||
public $IsRecurring;
|
public $IsRecurring;
|
||||||
|
|
||||||
protected function GetUser(): ?User{
|
|
||||||
if($this->User === null && $this->UserId !== null){
|
|
||||||
$this->User = User::Get($this->UserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->User;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
// If the User object isn't null, then check if we already have this user in our system
|
// If the User object isn't null, then check if we already have this user in our system
|
||||||
if($this->User !== null && $this->User->Email !== null){
|
if($this->User !== null && $this->User->Email !== null){
|
||||||
$result = Db::Query('select * from Users where Email = ?', [$this->User->Email], 'User');
|
$result = Db::Query('SELECT * from Users where Email = ?', [$this->User->Email], 'User');
|
||||||
|
|
||||||
if(sizeof($result) == 0){
|
if(sizeof($result) == 0){
|
||||||
// User doesn't exist, create it now
|
// User doesn't exist, create it now
|
||||||
|
@ -41,7 +33,7 @@ class Payment extends PropertiesBase{
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
Db::Query('insert into Payments (UserId, Timestamp, ChannelId, TransactionId, Amount, Fee, IsRecurring) values(?, ?, ?, ?, ?, ?, ?);', [$this->UserId, $this->Timestamp, $this->ChannelId, $this->TransactionId, $this->Amount, $this->Fee, $this->IsRecurring]);
|
Db::Query('INSERT into Payments (UserId, Timestamp, ChannelId, TransactionId, Amount, Fee, IsRecurring) values(?, ?, ?, ?, ?, ?, ?);', [$this->UserId, $this->Timestamp, $this->ChannelId, $this->TransactionId, $this->Amount, $this->Fee, $this->IsRecurring]);
|
||||||
}
|
}
|
||||||
catch(PDOException $ex){
|
catch(PDOException $ex){
|
||||||
if($ex->errorInfo[1] == 1062){
|
if($ex->errorInfo[1] == 1062){
|
||||||
|
|
88
lib/Poll.php
Normal file
88
lib/Poll.php
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
<?
|
||||||
|
use Safe\DateTime;
|
||||||
|
use function Safe\usort;
|
||||||
|
|
||||||
|
class Poll extends PropertiesBase{
|
||||||
|
public $PollId;
|
||||||
|
public $Name;
|
||||||
|
public $UrlName;
|
||||||
|
public $Description;
|
||||||
|
public $Created;
|
||||||
|
public $Start;
|
||||||
|
public $End;
|
||||||
|
protected $Url = null;
|
||||||
|
protected $PollItems = null;
|
||||||
|
protected $PollItemsByWinner = null;
|
||||||
|
protected $VoteCount = null;
|
||||||
|
|
||||||
|
protected function GetUrl(): string{
|
||||||
|
if($this->Url === null){
|
||||||
|
$this->Url = '/patrons-circle/polls/' . $this->UrlName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->Url;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function GetVoteCount(): int{
|
||||||
|
if($this->VoteCount === null){
|
||||||
|
$this->VoteCount = (Db::Query('select count(*) as VoteCount from Votes v inner join PollItems pi on v.PollItemId = pi.PollItemId where pi.PollId = ?', [$this->PollId]))[0]->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');
|
||||||
|
}
|
||||||
|
|
||||||
|
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; });
|
||||||
|
|
||||||
|
$this->PollItemsByWinner = array_reverse($this->PollItemsByWinner);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->PollItemsByWinner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function IsActive(): bool{
|
||||||
|
$now = new DateTime();
|
||||||
|
if( ($this->Start !== null && $this->Start > $now) || ($this->End !== null && $this->End < $now)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function Get(?int $pollId): Poll{
|
||||||
|
$result = Db::Query('SELECT * from Polls where PollId = ?', [$pollId], 'Poll');
|
||||||
|
|
||||||
|
if(sizeof($result) == 0){
|
||||||
|
throw new Exceptions\InvalidPollException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function GetByUrlName(?string $urlName): Poll{
|
||||||
|
$result = Db::Query('SELECT * from Polls where UrlName = ?', [$urlName], 'Poll');
|
||||||
|
|
||||||
|
if(sizeof($result) == 0){
|
||||||
|
throw new Exceptions\InvalidPollException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result[0];
|
||||||
|
}
|
||||||
|
}
|
27
lib/PollItem.php
Normal file
27
lib/PollItem.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?
|
||||||
|
class PollItem extends PropertiesBase{
|
||||||
|
public $PollItemId;
|
||||||
|
public $PollId;
|
||||||
|
public $Name;
|
||||||
|
public $Description;
|
||||||
|
protected $VoteCount = null;
|
||||||
|
protected $Poll = null;
|
||||||
|
|
||||||
|
protected function GetVoteCount(): int{
|
||||||
|
if($this->VoteCount === null){
|
||||||
|
$this->VoteCount = (Db::Query('select count(*) as VoteCount from Votes v inner join PollItems pi on v.PollItemId = pi.PollItemId where pi.PollItemId = ?', [$this->PollItemId]))[0]->VoteCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->VoteCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function Get(?int $pollItemId): PollItem{
|
||||||
|
$result = Db::Query('SELECT * from PollItems where PollItemId = ?', [$pollItemId], 'PollItem');
|
||||||
|
|
||||||
|
if(sizeof($result) == 0){
|
||||||
|
throw new Exceptions\InvalidPollItemException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result[0];
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
<?
|
<?
|
||||||
use function Safe\substr;
|
use function Safe\substr;
|
||||||
|
|
||||||
abstract class PropertiesBase extends OrmBase{
|
abstract class PropertiesBase{
|
||||||
/**
|
/**
|
||||||
* @param mixed $var
|
* @param mixed $var
|
||||||
* @return mixed
|
* @return mixed
|
||||||
|
@ -12,6 +12,15 @@ abstract class PropertiesBase extends OrmBase{
|
||||||
if(method_exists($this, $function)){
|
if(method_exists($this, $function)){
|
||||||
return $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'});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->$var;
|
||||||
|
}
|
||||||
elseif(substr($var, 0, 7) == 'Display'){
|
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 we're asked for a DisplayXXX property and the getter doesn't exist, format as escaped HTML.
|
||||||
if($this->$var === null){
|
if($this->$var === null){
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
<?
|
<?
|
||||||
|
use Safe\DateTime;
|
||||||
|
use function Safe\file_get_contents;
|
||||||
|
use function Safe\filesize;
|
||||||
|
use function Safe\preg_replace;
|
||||||
|
|
||||||
class RssFeed extends Feed{
|
class RssFeed extends Feed{
|
||||||
public $Description;
|
public $Description;
|
||||||
|
|
||||||
|
|
24
lib/User.php
24
lib/User.php
|
@ -1,5 +1,6 @@
|
||||||
<?
|
<?
|
||||||
use Ramsey\Uuid\Uuid;
|
use Ramsey\Uuid\Uuid;
|
||||||
|
use Safe\DateTime;
|
||||||
|
|
||||||
class User extends PropertiesBase{
|
class User extends PropertiesBase{
|
||||||
public $UserId;
|
public $UserId;
|
||||||
|
@ -14,10 +15,24 @@ class User extends PropertiesBase{
|
||||||
public $Timestamp;
|
public $Timestamp;
|
||||||
public $Uuid;
|
public $Uuid;
|
||||||
|
|
||||||
public static function Get(int $userId): ?User{
|
public static function Get(?int $userId): User{
|
||||||
$result = Db::Query('select * from Users where UserId = ?', [$userId], 'User');
|
$result = Db::Query('SELECT * from Users where UserId = ?', [$userId], 'User');
|
||||||
|
|
||||||
return $result[0] ?? null;
|
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function GetName(): string{
|
protected function GetName(): string{
|
||||||
|
@ -31,9 +46,10 @@ class User extends PropertiesBase{
|
||||||
public function Create(): void{
|
public function Create(): void{
|
||||||
$uuid = Uuid::uuid4();
|
$uuid = Uuid::uuid4();
|
||||||
$this->Uuid = $uuid->toString();
|
$this->Uuid = $uuid->toString();
|
||||||
|
$this->Timestamp = new DateTime();
|
||||||
|
|
||||||
try{
|
try{
|
||||||
Db::Query('insert into Users (Email, Name, Uuid, Timestamp) values (?, ?, ?, utc_timestamp());', [$this->Email, $this->Name, $this->Uuid]);
|
Db::Query('INSERT into Users (Email, Name, Uuid, Timestamp) values (?, ?, ?, ?);', [$this->Email, $this->Name, $this->Uuid, $this->Timestamp]);
|
||||||
}
|
}
|
||||||
catch(PDOException $ex){
|
catch(PDOException $ex){
|
||||||
if($ex->errorInfo[1] == 1062){
|
if($ex->errorInfo[1] == 1062){
|
||||||
|
|
87
lib/Vote.php
Normal file
87
lib/Vote.php
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
<?
|
||||||
|
use Safe\DateTime;
|
||||||
|
|
||||||
|
class Vote extends PropertiesBase{
|
||||||
|
public $VoteId;
|
||||||
|
public $UserId;
|
||||||
|
protected $User = null;
|
||||||
|
public $Created;
|
||||||
|
public $PollItemId;
|
||||||
|
protected $PollItem = null;
|
||||||
|
protected $Url = null;
|
||||||
|
|
||||||
|
protected function GetUrl(): string{
|
||||||
|
if($this->Url === null){
|
||||||
|
$this->Url = '/patrons-circle/polls/' . $this->PollItem->Poll->Url . '/votes/' . $this->UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->Url;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function Validate(): void{
|
||||||
|
$error = new Exceptions\ValidationException();
|
||||||
|
|
||||||
|
if($this->UserId === null){
|
||||||
|
$error->Add(new Exceptions\InvalidPatronException());
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->PollItemId === null){
|
||||||
|
$error->Add(new Exceptions\PollItemRequiredException());
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$this->__get('PollItem');
|
||||||
|
if($this->PollItem === null){
|
||||||
|
$error->Add(new Exceptions\InvalidPollException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$this->PollItem->Poll->IsActive()){
|
||||||
|
$error->Add(new Exceptions\PollClosedException());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$error->HasExceptions){
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// Do we already have a vote for this poll, from this user?
|
||||||
|
if( (Db::Query('
|
||||||
|
SELECT count(*) as VoteCount from Votes v inner join
|
||||||
|
(select PollItemId from PollItems pi inner join Polls p on pi.PollId = p.PollId) x
|
||||||
|
on v.PollItemId = x.PollItemId where v.UserId = ?', [$this->UserId]))[0]->VoteCount > 0){
|
||||||
|
$error->Add(new Exceptions\VoteExistsException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($error->HasExceptions){
|
||||||
|
throw $error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Create(?string $email = null): void{
|
||||||
|
if($email !== null){
|
||||||
|
try{
|
||||||
|
$patron = Patron::GetByEmail($email);
|
||||||
|
$this->UserId = $patron->UserId;
|
||||||
|
$this->User = $patron->User;
|
||||||
|
}
|
||||||
|
catch(Exceptions\InvalidPatronException $ex){
|
||||||
|
// Can't validate patron email - do nothing for now,
|
||||||
|
// this will be caught later when we validate the vote during creation.
|
||||||
|
// Save the email in the User object in case we want it later,
|
||||||
|
// for example prefilling the 'create' form after an error is returned.
|
||||||
|
$this->User = new User();
|
||||||
|
$this->User->Email = $email;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->Validate();
|
||||||
|
$this->Created = new DateTime();
|
||||||
|
Db::Query('INSERT into Votes (UserId, PollItemId, Created) values (?, ?, ?)', [$this->UserId, $this->PollItemId, $this->Created]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,5 +3,5 @@
|
||||||
require_once('/standardebooks.org/web/lib/Core.php');
|
require_once('/standardebooks.org/web/lib/Core.php');
|
||||||
|
|
||||||
// Delete unconfirmed newsletter subscribers who are more than a week old
|
// Delete unconfirmed newsletter subscribers who are more than a week old
|
||||||
Db::Query('delete from NewsletterSubscribers where IsConfirmed = false and datediff(utc_timestamp(), Timestamp) >= 7');
|
Db::Query('DELETE from NewsletterSubscribers where IsConfirmed = false and datediff(utc_timestamp(), Timestamp) >= 7');
|
||||||
?>
|
?>
|
||||||
|
|
|
@ -160,9 +160,10 @@ try{
|
||||||
if(($payment->IsRecurring && $payment->Amount >= 10 && $payment->Timestamp >= $lastMonth) || ($payment->Amount >= 100 && $payment->Timestamp >= $lastYear)){
|
if(($payment->IsRecurring && $payment->Amount >= 10 && $payment->Timestamp >= $lastMonth) || ($payment->Amount >= 100 && $payment->Timestamp >= $lastYear)){
|
||||||
// This payment is eligible for the Patrons Circle.
|
// This payment is eligible for the Patrons Circle.
|
||||||
// Are we already a patron?
|
// Are we already a patron?
|
||||||
$patron = Patron::Get($payment->UserId);
|
try{
|
||||||
|
$patron = Patron::Get($payment->UserId);
|
||||||
if($patron === null){
|
}
|
||||||
|
catch(Exceptions\InvalidPatronException $ex){
|
||||||
// Not a patron yet, add them to the Patrons Circle
|
// Not a patron yet, add them to the Patrons Circle
|
||||||
$patron = new Patron();
|
$patron = new Patron();
|
||||||
$patron->UserId = $payment->UserId;
|
$patron->UserId = $payment->UserId;
|
||||||
|
@ -194,7 +195,7 @@ try{
|
||||||
else{
|
else{
|
||||||
// Not a patron; send a thank you email anyway, but only if this is a non-recurring donation,
|
// Not a patron; send a thank you email anyway, but only if this is a non-recurring donation,
|
||||||
// or if it's their very first recurring donation
|
// or if it's their very first recurring donation
|
||||||
$previousPaymentCount = (Db::Query('select count(*) as PreviousPaymentCount from Payments where UserId = ? and IsRecurring = true', [$payment->UserId]))[0]->PreviousPaymentCount;
|
$previousPaymentCount = (Db::Query('SELECT count(*) as PreviousPaymentCount from Payments where UserId = ? and IsRecurring = true', [$payment->UserId]))[0]->PreviousPaymentCount;
|
||||||
|
|
||||||
// We just added a payment to the system, so if this is their very first recurring payment, we expect the count to be exactly 1
|
// We just added a payment to the system, so if this is their very first recurring payment, we expect the count to be exactly 1
|
||||||
if(!$payment->IsRecurring || $previousPaymentCount == 1){
|
if(!$payment->IsRecurring || $previousPaymentCount == 1){
|
||||||
|
@ -210,7 +211,7 @@ try{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Db::Query('delete from PendingPayments where TransactionId = ?;', [$pendingPayment->TransactionId]);
|
Db::Query('DELETE from PendingPayments where TransactionId = ?;', [$pendingPayment->TransactionId]);
|
||||||
|
|
||||||
$log->Write('Donation processed.');
|
$log->Write('Donation processed.');
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ $startDate = new DateTime('2022-07-01');
|
||||||
$endDate = new DateTime('2022-07-31');
|
$endDate = new DateTime('2022-07-31');
|
||||||
$autoHide = $autoHide ?? true;
|
$autoHide = $autoHide ?? true;
|
||||||
$showDonateButton = $showDonateButton ?? true;
|
$showDonateButton = $showDonateButton ?? true;
|
||||||
$current = (Db::Query('select count(*) as PatronCount from Patrons where Timestamp >= ?', [$startDate]))[0]->PatronCount;
|
$current = (Db::Query('SELECT count(*) as PatronCount from Patrons where Timestamp >= ?', [$startDate]))[0]->PatronCount;
|
||||||
$target = 70;
|
$target = 70;
|
||||||
$stretchCurrent = 0;
|
$stretchCurrent = 0;
|
||||||
$stretchTarget = 20;
|
$stretchTarget = 20;
|
||||||
|
|
|
@ -7,7 +7,7 @@ $anonymousPatronCount = 0;
|
||||||
// Get the Patrons Circle and try to sort by last name ascending
|
// Get the Patrons Circle and try to sort by last name ascending
|
||||||
// See <https://mariadb.com/kb/en/pcre/#unicode-character-properties> for Unicode character properties
|
// See <https://mariadb.com/kb/en/pcre/#unicode-character-properties> for Unicode character properties
|
||||||
|
|
||||||
$patronsCircle = Db::Query('select if(p.AlternateName is not null, p.AlternateName, u.Name) as SortedName
|
$patronsCircle = Db::Query('SELECT if(p.AlternateName is not null, p.AlternateName, u.Name) as SortedName
|
||||||
from Patrons p inner join Users u
|
from Patrons p inner join Users u
|
||||||
on p.UserId = u.UserId
|
on p.UserId = u.UserId
|
||||||
where
|
where
|
||||||
|
@ -16,7 +16,7 @@ $patronsCircle = Db::Query('select if(p.AlternateName is not null, p.AlternateNa
|
||||||
order by regexp_substr(SortedName, "[\\\p{Lu}][\\\p{L}\-]+$") asc;
|
order by regexp_substr(SortedName, "[\\\p{Lu}][\\\p{L}\-]+$") asc;
|
||||||
');
|
');
|
||||||
|
|
||||||
$anonymousPatronCount = Db::Query('select sum(cnt) as AnonymousPatronCount
|
$anonymousPatronCount = Db::Query('SELECT sum(cnt) as AnonymousPatronCount
|
||||||
from
|
from
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
|
|
|
@ -1836,6 +1836,16 @@ main.ebooks nav ol li.highlighted:nth-last-child(2)::after{
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-row.narrow{
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row:last-child{
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.masthead h2 + section > h3{
|
.masthead h2 + section > h3{
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
@ -2047,14 +2057,16 @@ article.ebook h2 + section > h3:first-of-type{
|
||||||
left: -5000px;
|
left: -5000px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form[action*="/polls/"],
|
||||||
form[action="/newsletter/subscribers"]{
|
form[action="/newsletter/subscribers"]{
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 1rem;
|
grid-gap: 2rem;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form[action*="/polls/"] label.email,
|
||||||
form[action="/newsletter/subscribers"] label.email,
|
form[action="/newsletter/subscribers"] label.email,
|
||||||
form[action="/newsletter/subscribers"] label.captcha{
|
form[action="/newsletter/subscribers"] label.captcha{
|
||||||
grid-column: 1 / span 2;
|
grid-column: 1 / span 2;
|
||||||
|
@ -2070,18 +2082,19 @@ form[action="/newsletter/subscribers"] label.captcha div input{
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
form[action="/newsletter/subscribers"] ul{
|
form fieldset ul{
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form[action*="/polls/"] button,
|
||||||
form[action="/newsletter/subscribers"] button{
|
form[action="/newsletter/subscribers"] button{
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form[action*="/polls/"] fieldset,
|
||||||
form[action="/newsletter/subscribers"] fieldset{
|
form[action="/newsletter/subscribers"] fieldset{
|
||||||
margin-top: 1rem;
|
|
||||||
grid-column: 1 / span 2;
|
grid-column: 1 / span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2096,15 +2109,25 @@ fieldset p{
|
||||||
|
|
||||||
label.checkbox{
|
label.checkbox{
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.checkbox input{
|
label.checkbox input{
|
||||||
margin-right: .25rem;
|
margin-right: .25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label.checkbox span{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.checkbox span > span{
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-top: .25rem;
|
||||||
|
}
|
||||||
|
|
||||||
article.step-by-step-guide ol ol{
|
article.step-by-step-guide ol ol{
|
||||||
margin-left: 1.2rem;
|
margin-left: 1.2rem;
|
||||||
list-style: decimal;
|
list-style: decimal;
|
||||||
|
@ -2130,19 +2153,12 @@ aside header{
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.meter,
|
||||||
.progress{
|
.progress{
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 0;
|
font-size: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress > div{
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.donation a.button{
|
.donation a.button{
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
@ -2215,6 +2231,7 @@ aside header{
|
||||||
hyphens: auto;
|
hyphens: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.meter p,
|
||||||
.progress p{
|
.progress p{
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-family: "League Spartan", Arial, sans-serif;
|
font-family: "League Spartan", Arial, sans-serif;
|
||||||
|
@ -2251,7 +2268,14 @@ aside header{
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.meter > div,
|
||||||
.progress > div{
|
.progress > div{
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
/* Animate the div instead of the bar itself, because animating the bar triggers an
|
/* Animate the div instead of the bar itself, because animating the bar triggers an
|
||||||
FF bug that causes infinite requsts to stripes.svg */
|
FF bug that causes infinite requsts to stripes.svg */
|
||||||
background: url("/images/stripes.svg") transparent;
|
background: url("/images/stripes.svg") transparent;
|
||||||
|
@ -2260,6 +2284,7 @@ aside header{
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meter,
|
||||||
progress{
|
progress{
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
|
@ -2280,6 +2305,7 @@ progress::-webkit-progress-value{
|
||||||
box-shadow: 1px 0 1px rgba(0, 0, 0, .25);
|
box-shadow: 1px 0 1px rgba(0, 0, 0, .25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meter::-moz-meter-bar,
|
||||||
progress::-moz-progress-bar{
|
progress::-moz-progress-bar{
|
||||||
background: var(--button);
|
background: var(--button);
|
||||||
box-shadow: 1px 0 1px rgba(0, 0, 0, .25);
|
box-shadow: 1px 0 1px rgba(0, 0, 0, .25);
|
||||||
|
@ -2507,6 +2533,33 @@ ul.feed p{
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.center-notice{
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.votes{
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.votes td:first-child{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.votes td{
|
||||||
|
width: 50%;
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.votes tr:first-child td{
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.votes tr:last-child td{
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@media (hover: none) and (pointer: coarse){ /* target ipads and smartphones without a mouse */
|
@media (hover: none) and (pointer: coarse){ /* target ipads and smartphones without a mouse */
|
||||||
/* For iPad, unset the height so it matches the other elements */
|
/* For iPad, unset the height so it matches the other elements */
|
||||||
select[multiple]{
|
select[multiple]{
|
||||||
|
@ -3016,6 +3069,17 @@ ul.feed p{
|
||||||
form[action="/settings"] select{
|
form[action="/settings"] select{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.votes tr,
|
||||||
|
.votes tr td{
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.votes tr + tr{
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-width: 470px){
|
@media(max-width: 470px){
|
||||||
|
|
|
@ -3,11 +3,11 @@ require_once('Core.php');
|
||||||
|
|
||||||
use function Safe\preg_match;
|
use function Safe\preg_match;
|
||||||
|
|
||||||
$requestType = preg_match('/\btext\/html\b/ius', $_SERVER['HTTP_ACCEPT']) ? WEB : REST;
|
$requestType = HttpInput::RequestType();
|
||||||
|
|
||||||
try{
|
try{
|
||||||
// We may use GET if we're called from an unsubscribe link in an email
|
// We may use GET if we're called from an unsubscribe link in an email
|
||||||
if(!in_array($_SERVER['REQUEST_METHOD'], ['DELETE', 'GET'])){
|
if(!in_array(HttpInput::RequestMethod(), [HTTP_DELETE, HTTP_GET])){
|
||||||
throw new Exceptions\InvalidRequestException();
|
throw new Exceptions\InvalidRequestException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,14 +4,14 @@ require_once('Core.php');
|
||||||
use function Safe\preg_match;
|
use function Safe\preg_match;
|
||||||
use function Safe\session_unset;
|
use function Safe\session_unset;
|
||||||
|
|
||||||
if($_SERVER['REQUEST_METHOD'] != 'POST'){
|
if(HttpInput::RequestMethod() != HTTP_POST){
|
||||||
http_response_code(405);
|
http_response_code(405);
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
$requestType = preg_match('/\btext\/html\b/ius', $_SERVER['HTTP_ACCEPT']) ? WEB : REST;
|
$requestType = HttpInput::RequestType();
|
||||||
|
|
||||||
$subscriber = new NewsletterSubscriber();
|
$subscriber = new NewsletterSubscriber();
|
||||||
|
|
||||||
|
@ -39,11 +39,7 @@ try{
|
||||||
$captcha = $_SESSION['captcha'] ?? null;
|
$captcha = $_SESSION['captcha'] ?? null;
|
||||||
|
|
||||||
if($captcha === null || mb_strtolower($captcha) !== mb_strtolower(HttpInput::Str(POST, 'captcha', false))){
|
if($captcha === null || mb_strtolower($captcha) !== mb_strtolower(HttpInput::Str(POST, 'captcha', false))){
|
||||||
$error = new Exceptions\ValidationException();
|
throw new Exceptions\ValidationException(new Exceptions\InvalidCaptchaException());
|
||||||
|
|
||||||
$error->Add(new Exceptions\InvalidCaptchaException());
|
|
||||||
|
|
||||||
throw $error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$subscriber->Create();
|
$subscriber->Create();
|
||||||
|
|
3
www/patrons-circle/index.php
Normal file
3
www/patrons-circle/index.php
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<?
|
||||||
|
header('Location: /donate#patrons-circle');
|
||||||
|
exit();
|
40
www/patrons-circle/polls/get.php
Normal file
40
www/patrons-circle/polls/get.php
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<?
|
||||||
|
require_once('Core.php');
|
||||||
|
|
||||||
|
use Safe\DateTime;
|
||||||
|
|
||||||
|
$poll = null;
|
||||||
|
|
||||||
|
try{
|
||||||
|
$poll = Poll::GetByUrlName(HttpInput::Str(GET, 'pollurlname', false));
|
||||||
|
}
|
||||||
|
catch(Exceptions\SeException $ex){
|
||||||
|
http_response_code(404);
|
||||||
|
include(WEB_ROOT . '/404.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
?><?= Template::Header(['title' => $poll->Name, 'highlight' => '', 'description' => $poll->Description]) ?>
|
||||||
|
<main>
|
||||||
|
<article>
|
||||||
|
<h1><?= Formatter::ToPlainText($poll->Name) ?></h1>
|
||||||
|
<p><?= $poll->Description ?></p>
|
||||||
|
<? if($poll->IsActive()){ ?>
|
||||||
|
<? if($poll->End !== null){ ?>
|
||||||
|
<p class="center-notice">This poll closes on <?= $poll->End->format('F j, Y g:i A') ?>.</p>
|
||||||
|
<? } ?>
|
||||||
|
<p class="button-row narrow">
|
||||||
|
<a href="<?= $poll->Url ?>/votes/new" class="button">Vote now</a>
|
||||||
|
<a href="<?= $poll->Url ?>/votes" class="button">View results</a>
|
||||||
|
</p>
|
||||||
|
<? }else{ ?>
|
||||||
|
<? if($poll->Start !== null && $poll->Start > new DateTime()){ ?>
|
||||||
|
<p class="center-notice">This poll opens on <?= $poll->Start->format('F j, Y g:i A') ?>.</p>
|
||||||
|
<? }else{ ?>
|
||||||
|
<p class="center-notice">This poll closed on <?= $poll->End->format('F j, Y g:i A') ?>.</p>
|
||||||
|
<p class="button-row narrow"><a href="<?= $poll->Url ?>/votes" class="button">View results</a></p>
|
||||||
|
<? } ?>
|
||||||
|
<? } ?>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
<?= Template::Footer() ?>
|
46
www/patrons-circle/polls/votes/index.php
Normal file
46
www/patrons-circle/polls/votes/index.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?
|
||||||
|
require_once('Core.php');
|
||||||
|
|
||||||
|
$poll = null;
|
||||||
|
|
||||||
|
try{
|
||||||
|
$poll = Poll::GetByUrlName(HttpInput::Str(GET, 'pollurlname', false));
|
||||||
|
}
|
||||||
|
catch(Exceptions\SeException $ex){
|
||||||
|
http_response_code(404);
|
||||||
|
include(WEB_ROOT . '/404.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
?><?= Template::Header(['title' => 'Results for the ' . $poll->Name . ' poll', 'highlight' => '', 'description' => 'The voting results for the ' . $poll->Name . ' poll.']) ?>
|
||||||
|
<main>
|
||||||
|
<article>
|
||||||
|
<h1>Results for the <?= Formatter::ToPlainText($poll->Name) ?> Poll</h1>
|
||||||
|
<p class="center-notice">Total votes: <?= number_format($poll->VoteCount) ?></p>
|
||||||
|
<? if($poll->IsActive()){ ?>
|
||||||
|
<? if($poll->End !== null){ ?>
|
||||||
|
<p class="center-notice">This poll closes on <?= $poll->End->format('F j, Y g:i A') ?>.</p>
|
||||||
|
<? } ?>
|
||||||
|
<? }elseif($poll->End !== null){ ?>
|
||||||
|
<p class="center-notice">This poll closed on <?= $poll->End->format('F j, Y g:i A') ?>.</p>
|
||||||
|
<? } ?>
|
||||||
|
<table class="votes">
|
||||||
|
<tbody>
|
||||||
|
<? foreach($poll->PollItemsByWinner as $pollItem){ ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= Formatter::ToPlainText($pollItem->Name) ?></td>
|
||||||
|
<td>
|
||||||
|
<div class="meter">
|
||||||
|
<div aria-hidden="true">
|
||||||
|
<p><?= number_format($pollItem->VoteCount) ?></p>
|
||||||
|
</div>
|
||||||
|
<meter min="0" max="<?= $poll->VoteCount ?>" value="<?= $pollItem->VoteCount ?>"></meter>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<? } ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
<?= Template::Footer() ?>
|
58
www/patrons-circle/polls/votes/new.php
Normal file
58
www/patrons-circle/polls/votes/new.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?
|
||||||
|
require_once('Core.php');
|
||||||
|
|
||||||
|
use function Safe\session_unset;
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
$vote = $_SESSION['vote'] ?? new Vote();
|
||||||
|
$exception = $_SESSION['exception'] ?? null;
|
||||||
|
|
||||||
|
$poll = null;
|
||||||
|
|
||||||
|
try{
|
||||||
|
$poll = Poll::GetByUrlName(HttpInput::Str(GET, 'pollurlname', false));
|
||||||
|
}
|
||||||
|
catch(Exceptions\SeException $ex){
|
||||||
|
http_response_code(404);
|
||||||
|
include(WEB_ROOT . '/404.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($exception){
|
||||||
|
http_response_code(400);
|
||||||
|
session_unset();
|
||||||
|
}
|
||||||
|
|
||||||
|
?><?= Template::Header(['title' => $poll->Name . ' - Vote Now', 'highlight' => '', 'description' => 'Vote in the ' . $poll->Name . ' poll']) ?>
|
||||||
|
<main>
|
||||||
|
<article>
|
||||||
|
<h1>Vote in the <?= Formatter::ToPlainText($poll->Name) ?> Poll</h1>
|
||||||
|
<?= Template::Error(['exception' => $exception]) ?>
|
||||||
|
<form method="post" action="<?= Formatter::ToPlainText($poll->Url) ?>/votes">
|
||||||
|
<label class="email">Your email address
|
||||||
|
<input type="email" name="email" value="<? if($vote->User !== null){ ?><?= Formatter::ToPlainText($vote->User->Email) ?><? } ?>" maxlength="80" required="required" />
|
||||||
|
</label>
|
||||||
|
<fieldset>
|
||||||
|
<p>Select one of these options</p>
|
||||||
|
<ul>
|
||||||
|
<? foreach($poll->PollItems as $pollItem){ ?>
|
||||||
|
<li>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="radio" value="<?= $pollItem->PollItemId ?>" name="pollitemid" required="required"<? if($vote->PollItemId == $pollItem->PollItemId){ ?> checked="checked"<? } ?>/>
|
||||||
|
<span>
|
||||||
|
<b><?= Formatter::ToPlainText($pollItem->Name) ?></b>
|
||||||
|
<? if($pollItem->Description !== null){ ?>
|
||||||
|
<span><?= Formatter::ToPlainText($pollItem->Description) ?></span>
|
||||||
|
<? } ?>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<? } ?>
|
||||||
|
</ul>
|
||||||
|
</fieldset>
|
||||||
|
<button>Vote</button>
|
||||||
|
</form>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
<?= Template::Footer() ?>
|
51
www/patrons-circle/polls/votes/post.php
Normal file
51
www/patrons-circle/polls/votes/post.php
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<?
|
||||||
|
require_once('Core.php');
|
||||||
|
|
||||||
|
use function Safe\preg_match;
|
||||||
|
use function Safe\session_unset;
|
||||||
|
|
||||||
|
if(HttpInput::RequestMethod() != HTTP_POST){
|
||||||
|
http_response_code(405);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
$requestType = HttpInput::RequestType();
|
||||||
|
|
||||||
|
$vote = new Vote();
|
||||||
|
|
||||||
|
try{
|
||||||
|
$error = new Exceptions\ValidationException();
|
||||||
|
|
||||||
|
$vote->PollItemId = HttpInput::Int(POST, 'pollitemid');
|
||||||
|
|
||||||
|
$vote->Create(HttpInput::Str(POST, 'email', false));
|
||||||
|
|
||||||
|
session_unset();
|
||||||
|
|
||||||
|
if($requestType == WEB){
|
||||||
|
http_response_code(303);
|
||||||
|
header('Location: ' . $vote->PollItem->Poll->Url . '/votes/success');
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// Access via REST api; 201 CREATED with location
|
||||||
|
http_response_code(201);
|
||||||
|
header('Location: ' . $vote->Url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exceptions\SeException $ex){
|
||||||
|
// Validation failed
|
||||||
|
if($requestType == WEB){
|
||||||
|
$_SESSION['vote'] = $vote;
|
||||||
|
$_SESSION['exception'] = $ex;
|
||||||
|
|
||||||
|
// Access via form; 303 redirect to the form, which will emit a 400 BAD REQUEST
|
||||||
|
http_response_code(303);
|
||||||
|
header('Location: /patrons-circle/polls/' . HttpInput::Str(GET, 'pollurlname', false) . '/votes/new');
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// Access via REST api; 400 BAD REQUEST
|
||||||
|
http_response_code(400);
|
||||||
|
}
|
||||||
|
}
|
23
www/patrons-circle/polls/votes/success.php
Normal file
23
www/patrons-circle/polls/votes/success.php
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?
|
||||||
|
require_once('Core.php');
|
||||||
|
|
||||||
|
$poll = null;
|
||||||
|
|
||||||
|
try{
|
||||||
|
$poll = Poll::GetByUrlName(HttpInput::Str(GET, 'pollurlname', false));
|
||||||
|
}
|
||||||
|
catch(Exceptions\SeException $ex){
|
||||||
|
http_response_code(404);
|
||||||
|
include(WEB_ROOT . '/404.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
?><?= Template::Header(['title' => 'Thank you for voting!', 'highlight' => 'newsletter', 'description' => 'Thank you for voting in a Standard Ebooks poll!']) ?>
|
||||||
|
<main>
|
||||||
|
<article>
|
||||||
|
<h1>Thank you for voting!</h1>
|
||||||
|
<p class="center-notice">Your vote in the <?= Formatter::ToPlainText($poll->Name) ?> poll has been recorded.</p>
|
||||||
|
<p class="button-row narrow"><a class="button" href="<?= $poll->Url ?>/votes"> view results</a></p>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
<?= Template::Footer() ?>
|
|
@ -17,7 +17,7 @@ $lastPushHashFlag = '';
|
||||||
try{
|
try{
|
||||||
$log->Write('Received GitHub webhook.');
|
$log->Write('Received GitHub webhook.');
|
||||||
|
|
||||||
if($_SERVER['REQUEST_METHOD'] != 'POST'){
|
if(HttpInput::RequestMethod() != HTTP_POST){
|
||||||
throw new Exceptions\WebhookException('Expected HTTP POST.');
|
throw new Exceptions\WebhookException('Expected HTTP POST.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ try{
|
||||||
|
|
||||||
$log->Write('Received Postmark webhook.');
|
$log->Write('Received Postmark webhook.');
|
||||||
|
|
||||||
if($_SERVER['REQUEST_METHOD'] != 'POST'){
|
if(HttpInput::RequestMethod() != HTTP_POST){
|
||||||
throw new Exceptions\WebhookException('Expected HTTP POST.');
|
throw new Exceptions\WebhookException('Expected HTTP POST.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ try{
|
||||||
// Received when a user marks an email as spam
|
// Received when a user marks an email as spam
|
||||||
$log->Write('Event type: spam complaint.');
|
$log->Write('Event type: spam complaint.');
|
||||||
|
|
||||||
Db::Query('delete from NewsletterSubscribers where Email = ?', [$post->Email]);
|
Db::Query('DELETE from NewsletterSubscribers where Email = ?', [$post->Email]);
|
||||||
}
|
}
|
||||||
elseif($post->RecordType == 'SubscriptionChange' && $post->SuppressSending){
|
elseif($post->RecordType == 'SubscriptionChange' && $post->SuppressSending){
|
||||||
// Received when a user clicks Postmark's "Unsubscribe" link in a newsletter email
|
// Received when a user clicks Postmark's "Unsubscribe" link in a newsletter email
|
||||||
|
@ -45,7 +45,7 @@ try{
|
||||||
$email = $post->Recipient;
|
$email = $post->Recipient;
|
||||||
|
|
||||||
// Remove the email from our newsletter list
|
// Remove the email from our newsletter list
|
||||||
Db::Query('delete from NewsletterSubscribers where Email = ?', [$email]);
|
Db::Query('DELETE from NewsletterSubscribers where Email = ?', [$email]);
|
||||||
|
|
||||||
// Remove the suppression from Postmark, since we deleted it from our own list we will never email them again anyway
|
// Remove the suppression from Postmark, since we deleted it from our own list we will never email them again anyway
|
||||||
$handle = curl_init();
|
$handle = curl_init();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?
|
<?
|
||||||
require_once('Core.php');
|
require_once('Core.php');
|
||||||
|
|
||||||
|
use Safe\DateTime;
|
||||||
use function Safe\file_get_contents;
|
use function Safe\file_get_contents;
|
||||||
use function Safe\preg_match;
|
use function Safe\preg_match;
|
||||||
use function Safe\preg_replace;
|
use function Safe\preg_replace;
|
||||||
|
@ -15,7 +16,7 @@ $log = new Log(ZOHO_WEBHOOK_LOG_FILE_PATH);
|
||||||
try{
|
try{
|
||||||
$log->Write('Received Zoho webhook.');
|
$log->Write('Received Zoho webhook.');
|
||||||
|
|
||||||
if($_SERVER['REQUEST_METHOD'] != 'POST'){
|
if(HttpInput::RequestMethod() != HTTP_POST){
|
||||||
throw new Exceptions\WebhookException('Expected HTTP POST.');
|
throw new Exceptions\WebhookException('Expected HTTP POST.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ try{
|
||||||
$payment->Create();
|
$payment->Create();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
Db::Query('insert into PendingPayments (Timestamp, ChannelId, TransactionId) values (utc_timestamp(), ?, ?);', [PAYMENT_CHANNEL_FA, $transactionId]);
|
Db::Query('INSERT into PendingPayments (Timestamp, ChannelId, TransactionId) values (utc_timestamp(), ?, ?);', [PAYMENT_CHANNEL_FA, $transactionId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$log->Write('Donation ID: ' . $transactionId);
|
$log->Write('Donation ID: ' . $transactionId);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue