mirror of
https://github.com/standardebooks/web.git
synced 2025-07-08 15:50:29 -04:00
Convert newsletter to use Users table as base
This commit is contained in:
parent
b48a788430
commit
011cd747f1
34 changed files with 444 additions and 307 deletions
|
@ -256,18 +256,19 @@ Define webroot /standardebooks.org/web
|
||||||
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
|
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
|
||||||
|
|
||||||
# Newsletter
|
# Newsletter
|
||||||
RewriteRule ^/newsletter$ /newsletter/subscribers/new.php
|
RewriteRule ^/newsletter$ /newsletter/subscriptions/new.php [L]
|
||||||
RewriteRule ^/newsletter/subscribers/([^\./]+?)/(delete|confirm)$ /newsletter/subscribers/$2.php?uuid=$1
|
RewriteRule ^/newsletter/subscriptions/([^/\.]+?)$ /newsletter/subscriptions/get.php?uuid=$1 [L]
|
||||||
|
RewriteRule ^/newsletter/subscriptions/([^/\.]+?)/(confirm|delete|success)$ /newsletter/subscriptions/$2.php?uuid=$1 [L]
|
||||||
|
|
||||||
# Polls
|
# Polls
|
||||||
RewriteRule ^/patrons-circle/polls/([^/]+)$ /patrons-circle/polls/get.php?pollurlname=$1
|
RewriteRule ^/patrons-circle/polls/([^/\.]+)$ /patrons-circle/polls/get.php?pollurlname=$1 [L]
|
||||||
RewriteRule ^/patrons-circle/polls/([^/]+)/votes/(new|success)$ /patrons-circle/polls/votes/$2.php?pollurlname=$1
|
RewriteRule ^/patrons-circle/polls/([^/\.]+)/votes/(new|success)$ /patrons-circle/polls/votes/$2.php?pollurlname=$1 [L]
|
||||||
|
|
||||||
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^get$/"
|
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^get$/"
|
||||||
RewriteRule ^/patrons-circle/polls/([^/]+)/votes$ /patrons-circle/polls/votes/index.php?pollurlname=$1
|
RewriteRule ^/patrons-circle/polls/([^/\.]+)/votes$ /patrons-circle/polls/votes/index.php?pollurlname=$1 [L]
|
||||||
|
|
||||||
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
|
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
|
||||||
RewriteRule ^/patrons-circle/polls/([^/]+)/votes$ /patrons-circle/polls/votes/post.php?pollurlname=$1
|
RewriteRule ^/patrons-circle/polls/([^/\.]+)/votes$ /patrons-circle/polls/votes/post.php?pollurlname=$1 [L]
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
|
|
||||||
<VirtualHost *:80>
|
<VirtualHost *:80>
|
||||||
|
|
|
@ -255,16 +255,17 @@ Define webroot /standardebooks.org/web
|
||||||
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
|
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
|
||||||
|
|
||||||
# Newsletter
|
# Newsletter
|
||||||
RewriteRule ^/newsletter$ /newsletter/subscribers/new.php
|
RewriteRule ^/newsletter$ /newsletter/subscriptions/new.php [L]
|
||||||
RewriteRule ^/newsletter/subscribers/([^\./]+?)/(delete|confirm)$ /newsletter/subscribers/$2.php?uuid=$1
|
RewriteRule ^/newsletter/subscriptions/([^/\.]+?)$ /newsletter/subscriptions/get.php?uuid=$1 [L]
|
||||||
|
RewriteRule ^/newsletter/subscriptions/([^/\.]+?)/(confirm|delete|success)$ /newsletter/subscriptions/$2.php?uuid=$1 [L]
|
||||||
|
|
||||||
# Polls
|
# Polls
|
||||||
RewriteRule ^/patrons-circle/polls/([^/]+)$ /patrons-circle/polls/get.php?pollurlname=$1
|
RewriteRule ^/patrons-circle/polls/([^/\.]+)$ /patrons-circle/polls/get.php?pollurlname=$1 [L]
|
||||||
RewriteRule ^/patrons-circle/polls/([^/]+)/votes/(new|success)$ /patrons-circle/polls/votes/$2.php?pollurlname=$1
|
RewriteRule ^/patrons-circle/polls/([^/\.]+)/votes/(new|success)$ /patrons-circle/polls/votes/$2.php?pollurlname=$1 [L]
|
||||||
|
|
||||||
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^get$/"
|
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^get$/"
|
||||||
RewriteRule ^/patrons-circle/polls/([^/]+)/votes$ /patrons-circle/polls/votes/index.php?pollurlname=$1
|
RewriteRule ^/patrons-circle/polls/([^/\.]+)/votes$ /patrons-circle/polls/votes/index.php?pollurlname=$1 [L]
|
||||||
|
|
||||||
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
|
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
|
||||||
RewriteRule ^/patrons-circle/polls/([^/]+)/votes$ /patrons-circle/polls/votes/post.php?pollurlname=$1
|
RewriteRule ^/patrons-circle/polls/([^/\.]+)/votes$ /patrons-circle/polls/votes/post.php?pollurlname=$1 [L]
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
CREATE TABLE `NewsletterSubscribers` (
|
|
||||||
`NewsletterSubscriberId` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`Email` varchar(80) NOT NULL,
|
|
||||||
`Uuid` char(36) NOT NULL,
|
|
||||||
`FirstName` varchar(80) DEFAULT NULL,
|
|
||||||
`LastName` varchar(80) DEFAULT NULL,
|
|
||||||
`IsConfirmed` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
|
||||||
`IsSubscribedToNewsletter` tinyint(1) unsigned NOT NULL DEFAULT 1,
|
|
||||||
`IsSubscribedToSummary` tinyint(1) unsigned NOT NULL DEFAULT 1,
|
|
||||||
`Created` datetime NOT NULL,
|
|
||||||
PRIMARY KEY (`NewsletterSubscriberId`),
|
|
||||||
UNIQUE KEY `Uuid_UNIQUE` (`Uuid`),
|
|
||||||
UNIQUE KEY `Email_UNIQUE` (`Email`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
8
config/sql/se/NewsletterSubscriptions.sql
Normal file
8
config/sql/se/NewsletterSubscriptions.sql
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
CREATE TABLE `NewsletterSubscriptions` (
|
||||||
|
`UserId` int(10) unsigned NOT NULL,
|
||||||
|
`IsConfirmed` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||||
|
`IsSubscribedToNewsletter` tinyint(1) unsigned NOT NULL DEFAULT 1,
|
||||||
|
`IsSubscribedToSummary` tinyint(1) unsigned NOT NULL DEFAULT 1,
|
||||||
|
`Created` datetime NOT NULL,
|
||||||
|
PRIMARY KEY (`UserId`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
@ -5,6 +5,10 @@ class Db{
|
||||||
return $GLOBALS['DbConnection']->GetLastInsertedId();
|
return $GLOBALS['DbConnection']->GetLastInsertedId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function GetAffectedRowCount(): int{
|
||||||
|
return $GLOBALS['DbConnection']->LastQueryAffectedRowCount;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $query
|
* @param string $query
|
||||||
* @param array<mixed> $args
|
* @param array<mixed> $args
|
||||||
|
|
|
@ -6,6 +6,7 @@ class DbConnection{
|
||||||
private $_link = null;
|
private $_link = null;
|
||||||
public $IsConnected = false;
|
public $IsConnected = false;
|
||||||
public $QueryCount = 0;
|
public $QueryCount = 0;
|
||||||
|
public $LastQueryAffectedRowCount = 0;
|
||||||
|
|
||||||
public function __construct(?string $defaultDatabase = null, string $host = 'localhost', ?string $user = null, string$password = '', bool $forceUtf8 = true, bool $require = true){
|
public function __construct(?string $defaultDatabase = null, string $host = 'localhost', ?string $user = null, string$password = '', bool $forceUtf8 = true, bool $require = true){
|
||||||
if($user === null){
|
if($user === null){
|
||||||
|
@ -160,6 +161,8 @@ class DbConnection{
|
||||||
private function ExecuteQuery(PDOStatement $handle, string $class = 'stdClass'): array{
|
private function ExecuteQuery(PDOStatement $handle, string $class = 'stdClass'): array{
|
||||||
$handle->execute();
|
$handle->execute();
|
||||||
|
|
||||||
|
$this->LastQueryAffectedRowCount = $handle->rowCount();
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
do{
|
do{
|
||||||
try{
|
try{
|
||||||
|
|
|
@ -59,7 +59,11 @@ class Ebook{
|
||||||
public $TextSinglePageUrl;
|
public $TextSinglePageUrl;
|
||||||
public $TocEntries = null; // A list of non-Roman ToC entries ONLY IF the work has the 'se:is-a-collection' metadata element, null otherwise
|
public $TocEntries = null; // A list of non-Roman ToC entries ONLY IF the work has the 'se:is-a-collection' metadata element, null otherwise
|
||||||
|
|
||||||
public function __construct(string $wwwFilesystemPath){
|
public function __construct(?string $wwwFilesystemPath = null){
|
||||||
|
if($wwwFilesystemPath === null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// First, construct a source repo path from our WWW filesystem path.
|
// First, construct a source repo path from our WWW filesystem path.
|
||||||
$this->RepoFilesystemPath = str_replace(EBOOKS_DIST_PATH, '', $wwwFilesystemPath);
|
$this->RepoFilesystemPath = str_replace(EBOOKS_DIST_PATH, '', $wwwFilesystemPath);
|
||||||
$this->RepoFilesystemPath = SITE_ROOT . '/ebooks/' . str_replace('/', '_', $this->RepoFilesystemPath) . '.git';
|
$this->RepoFilesystemPath = SITE_ROOT . '/ebooks/' . str_replace('/', '_', $this->RepoFilesystemPath) . '.git';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?
|
<?
|
||||||
namespace Exceptions;
|
namespace Exceptions;
|
||||||
|
|
||||||
class InvalidNewsletterSubscriberException extends SeException{
|
class InvalidNewsletterSubscriptionException extends SeException{
|
||||||
protected $message = 'We couldn’t find you in our newsletter subscribers list.';
|
protected $message = 'We couldn’t find you in our newsletter subscribers list.';
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
<?
|
<?
|
||||||
namespace Exceptions;
|
namespace Exceptions;
|
||||||
|
|
||||||
class NewsletterSubscriberExistsException extends SeException{
|
class NewsletterSubscriptionExistsException extends SeException{
|
||||||
protected $message = 'You’re already subscribed to the newsletter.';
|
protected $message = 'You’re already subscribed to the newsletter.';
|
||||||
}
|
}
|
|
@ -1,107 +0,0 @@
|
||||||
<?
|
|
||||||
use Safe\DateTime;
|
|
||||||
use Ramsey\Uuid\Uuid;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @property string $Url
|
|
||||||
*/
|
|
||||||
class NewsletterSubscriber extends PropertiesBase{
|
|
||||||
public $NewsletterSubscriberId;
|
|
||||||
public $Uuid;
|
|
||||||
public $Email;
|
|
||||||
public $FirstName;
|
|
||||||
public $LastName;
|
|
||||||
public $IsConfirmed = false;
|
|
||||||
public $IsSubscribedToSummary = true;
|
|
||||||
public $IsSubscribedToNewsletter = true;
|
|
||||||
public $Created;
|
|
||||||
protected $_Url = null;
|
|
||||||
|
|
||||||
// *******
|
|
||||||
// GETTERS
|
|
||||||
// *******
|
|
||||||
|
|
||||||
protected function GetUrl(): string{
|
|
||||||
if($this->_Url === null){
|
|
||||||
$this->_Url = SITE_URL . '/newsletter/subscribers/' . $this->Uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->_Url;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// *******
|
|
||||||
// METHODS
|
|
||||||
// *******
|
|
||||||
|
|
||||||
public function Create(): void{
|
|
||||||
$this->Validate();
|
|
||||||
|
|
||||||
$uuid = Uuid::uuid4();
|
|
||||||
$this->Uuid = $uuid->toString();
|
|
||||||
$this->Created = new DateTime();
|
|
||||||
|
|
||||||
try{
|
|
||||||
Db::Query('INSERT into NewsletterSubscribers (Email, Uuid, FirstName, LastName, IsConfirmed, IsSubscribedToNewsletter, IsSubscribedToSummary, Created) values (?, ?, ?, ?, ?, ?, ?, ?);', [$this->Email, $this->Uuid, $this->FirstName, $this->LastName, false, $this->IsSubscribedToNewsletter, $this->IsSubscribedToSummary, $this->Created]);
|
|
||||||
}
|
|
||||||
catch(PDOException $ex){
|
|
||||||
if($ex->errorInfo[1] == 1062){
|
|
||||||
// Duplicate unique key; email already in use
|
|
||||||
throw new Exceptions\NewsletterSubscriberExistsException();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
throw $ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->NewsletterSubscriberId = Db::GetLastInsertedId();
|
|
||||||
|
|
||||||
// Send the double opt-in confirmation email
|
|
||||||
$em = new Email(true);
|
|
||||||
$em->PostmarkStream = EMAIL_POSTMARK_STREAM_BROADCAST;
|
|
||||||
$em->To = $this->Email;
|
|
||||||
$em->Subject = 'Action required: confirm your newsletter subscription';
|
|
||||||
$em->Body = Template::EmailNewsletterConfirmation(['subscriber' => $this, 'isSubscribedToSummary' => $this->IsSubscribedToSummary, 'isSubscribedToNewsletter' => $this->IsSubscribedToNewsletter]);
|
|
||||||
$em->TextBody = Template::EmailNewsletterConfirmationText(['subscriber' => $this, 'isSubscribedToSummary' => $this->IsSubscribedToSummary, 'isSubscribedToNewsletter' => $this->IsSubscribedToNewsletter]);
|
|
||||||
$em->Send();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function Confirm(): void{
|
|
||||||
Db::Query('UPDATE NewsletterSubscribers set IsConfirmed = true where NewsletterSubscriberId = ?;', [$this->NewsletterSubscriberId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function Delete(): void{
|
|
||||||
Db::Query('DELETE from NewsletterSubscribers where NewsletterSubscriberId = ?;', [$this->NewsletterSubscriberId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function Validate(): void{
|
|
||||||
$error = new Exceptions\ValidationException();
|
|
||||||
|
|
||||||
if($this->Email == '' || !filter_var($this->Email, FILTER_VALIDATE_EMAIL)){
|
|
||||||
$error->Add(new Exceptions\InvalidEmailException());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!$this->IsSubscribedToSummary && !$this->IsSubscribedToNewsletter){
|
|
||||||
$error->Add(new Exceptions\NewsletterRequiredException());
|
|
||||||
}
|
|
||||||
|
|
||||||
if($error->HasExceptions){
|
|
||||||
throw $error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ***********
|
|
||||||
// ORM METHODS
|
|
||||||
// ***********
|
|
||||||
|
|
||||||
public static function Get(string $uuid): NewsletterSubscriber{
|
|
||||||
$subscribers = Db::Query('SELECT * from NewsletterSubscribers where Uuid = ?;', [$uuid], 'NewsletterSubscriber');
|
|
||||||
|
|
||||||
if(sizeof($subscribers) == 0){
|
|
||||||
throw new Exceptions\InvalidNewsletterSubscriberException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $subscribers[0];
|
|
||||||
}
|
|
||||||
}
|
|
123
lib/NewsletterSubscription.php
Normal file
123
lib/NewsletterSubscription.php
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
<?
|
||||||
|
use Safe\DateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property User $User
|
||||||
|
* @property string $Url
|
||||||
|
*/
|
||||||
|
class NewsletterSubscription extends PropertiesBase{
|
||||||
|
public $IsConfirmed = false;
|
||||||
|
public $IsSubscribedToSummary;
|
||||||
|
public $IsSubscribedToNewsletter;
|
||||||
|
public $UserId;
|
||||||
|
protected $_User;
|
||||||
|
public $Created;
|
||||||
|
protected $_Url = null;
|
||||||
|
|
||||||
|
// *******
|
||||||
|
// GETTERS
|
||||||
|
// *******
|
||||||
|
|
||||||
|
protected function GetUrl(): string{
|
||||||
|
if($this->_Url === null){
|
||||||
|
$this->_Url = '/newsletter/subscriptions/' . $this->User->Uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_Url;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// *******
|
||||||
|
// METHODS
|
||||||
|
// *******
|
||||||
|
|
||||||
|
public function Create(): void{
|
||||||
|
$this->Validate();
|
||||||
|
|
||||||
|
// Do we need to create a user?
|
||||||
|
try{
|
||||||
|
$this->User = User::GetByEmail($this->User->Email);
|
||||||
|
}
|
||||||
|
catch(Exceptions\InvalidUserException $ex){
|
||||||
|
// User doesn't exist, create the user
|
||||||
|
$this->User->Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->UserId = $this->User->UserId;
|
||||||
|
$this->Created = new DateTime();
|
||||||
|
|
||||||
|
try{
|
||||||
|
Db::Query('INSERT into NewsletterSubscriptions (UserId, IsConfirmed, IsSubscribedToNewsletter, IsSubscribedToSummary, Created) values (?, ?, ?, ?, ?);', [$this->User->UserId, false, $this->IsSubscribedToNewsletter, $this->IsSubscribedToSummary, $this->Created]);
|
||||||
|
}
|
||||||
|
catch(PDOException $ex){
|
||||||
|
if($ex->errorInfo[1] == 1062){
|
||||||
|
// Duplicate unique key; email already in use
|
||||||
|
throw new Exceptions\NewsletterSubscriptionExistsException();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the double opt-in confirmation email
|
||||||
|
$this->SendConfirmationEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Save(): void{
|
||||||
|
$this->Validate();
|
||||||
|
|
||||||
|
Db::Query('UPDATE NewsletterSubscriptions set IsConfirmed = ?, IsSubscribedToNewsletter = ?, IsSubscribedToSummary = ? where UserId = ?', [$this->IsConfirmed, $this->IsSubscribedToNewsletter, $this->IsSubscribedToSummary, $this->UserId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function SendConfirmationEmail(): void{
|
||||||
|
$em = new Email(true);
|
||||||
|
$em->PostmarkStream = EMAIL_POSTMARK_STREAM_BROADCAST;
|
||||||
|
$em->To = $this->User->Email;
|
||||||
|
if($this->User->Name != ''){
|
||||||
|
$em->ToName = $this->User->Name;
|
||||||
|
}
|
||||||
|
$em->Subject = 'Action required: confirm your newsletter subscription';
|
||||||
|
$em->Body = Template::EmailNewsletterConfirmation(['subscription' => $this, 'isSubscribedToSummary' => $this->IsSubscribedToSummary, 'isSubscribedToNewsletter' => $this->IsSubscribedToNewsletter]);
|
||||||
|
$em->TextBody = Template::EmailNewsletterConfirmationText(['subscription' => $this, 'isSubscribedToSummary' => $this->IsSubscribedToSummary, 'isSubscribedToNewsletter' => $this->IsSubscribedToNewsletter]);
|
||||||
|
$em->Send();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Confirm(): void{
|
||||||
|
Db::Query('UPDATE NewsletterSubscriptions set IsConfirmed = true where UserId = ?;', [$this->UserId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Delete(): void{
|
||||||
|
Db::Query('DELETE from NewsletterSubscriptions where UserId = ?;', [$this->UserId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Validate(): void{
|
||||||
|
$error = new Exceptions\ValidationException();
|
||||||
|
|
||||||
|
if($this->User === null || $this->User->Email == '' || !filter_var($this->User->Email, FILTER_VALIDATE_EMAIL)){
|
||||||
|
$error->Add(new Exceptions\InvalidEmailException());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$this->IsSubscribedToSummary && !$this->IsSubscribedToNewsletter){
|
||||||
|
$error->Add(new Exceptions\NewsletterRequiredException());
|
||||||
|
}
|
||||||
|
|
||||||
|
if($error->HasExceptions){
|
||||||
|
throw $error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ***********
|
||||||
|
// ORM METHODS
|
||||||
|
// ***********
|
||||||
|
|
||||||
|
public static function Get(string $uuid): NewsletterSubscription{
|
||||||
|
$result = Db::Query('SELECT ns.* from NewsletterSubscriptions ns inner join Users u on ns.UserId = u.UserId where u.Uuid = ?', [$uuid], 'NewsletterSubscription');
|
||||||
|
|
||||||
|
if(sizeof($result) == 0){
|
||||||
|
throw new Exceptions\InvalidNewsletterSubscriptionException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result[0];
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,4 +33,10 @@ class Template{
|
||||||
return self::Get($function, $arguments);
|
return self::Get($function, $arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function Emit404(): void{
|
||||||
|
http_response_code(404);
|
||||||
|
include(WEB_ROOT . '/404.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(), Created) >= 7');
|
Db::Query('DELETE from NewsletterSubscriptions where IsConfirmed = false and datediff(utc_timestamp(), Created) >= 7');
|
||||||
?>
|
?>
|
||||||
|
|
|
@ -79,6 +79,7 @@ $letterhead = $letterhead ?? false;
|
||||||
h2{
|
h2{
|
||||||
font-family: "League Spartan", "Helvetica", "Arial", sans-serif;
|
font-family: "League Spartan", "Helvetica", "Arial", sans-serif;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
hyphens: none;
|
||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
@ -110,7 +111,6 @@ $letterhead = $letterhead ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
address{
|
address{
|
||||||
font-size: .75em;
|
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +124,7 @@ $letterhead = $letterhead ?? false;
|
||||||
|
|
||||||
.footer{
|
.footer{
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
|
font-size: .75em;
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
padding-top: 2em;
|
padding-top: 2em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -135,7 +136,7 @@ $letterhead = $letterhead ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer img{
|
.footer img{
|
||||||
margin-top: 1em;
|
margin-top: 2em;
|
||||||
max-width: 55px;
|
max-width: 55px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
<p>Please use the button below to confirm your subscription—you won’t receive email from us until you do.</p>
|
<p>Please use the button below to confirm your subscription—you won’t receive email from us until you do.</p>
|
||||||
<p class="button-row">
|
<p class="button-row">
|
||||||
<a href="<?= $subscriber->Url ?>/confirm" class="button">Yes, confirm my subscription</a>
|
<a href="<?= SITE_URL . $subscription->Url ?>/confirm" class="button">Yes, confirm my subscription</a>
|
||||||
</p>
|
</p>
|
||||||
<p>If you didn’t subscribe, or you’re not sure why you received this email, you can safely delete it and you won’t receive any more email from us.</p>
|
<p>If you didn’t subscribe, or you’re not sure why you received this email, you can safely delete it and you won’t receive any more email from us.</p>
|
||||||
<?= Template::EmailFooter() ?>
|
<?= Template::EmailFooter() ?>
|
||||||
|
|
|
@ -8,9 +8,10 @@ You subscribed to:
|
||||||
<? } ?>
|
<? } ?>
|
||||||
<? if($isSubscribedToSummary){ ?>- A monthly summary of new ebook releases
|
<? if($isSubscribedToSummary){ ?>- A monthly summary of new ebook releases
|
||||||
<? } ?>
|
<? } ?>
|
||||||
|
|
||||||
Please use the link below to confirm your subscription—you won’t receive email from us until you do.
|
Please use the link below to confirm your subscription—you won’t receive email from us until you do.
|
||||||
|
|
||||||
<<?= $subscriber->Url ?>/confirm>
|
<<?= SITE_URL . $subscription->Url ?>/confirm>
|
||||||
|
|
||||||
If you didn’t subscribe, or you’re not sure why you received this email, you can safely delete it and you won’t receive any more email from us.
|
If you didn’t subscribe, or you’re not sure why you received this email, you can safely delete it and you won’t receive any more email from us.
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ else{
|
||||||
$exceptions[] = $exception;
|
$exceptions[] = $exception;
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<ul class="error">
|
<ul class="message error">
|
||||||
<? foreach($exceptions as $ex){ ?>
|
<? foreach($exceptions as $ex){ ?>
|
||||||
<li>
|
<li>
|
||||||
<p><? $message = $ex->getMessage(); if($message == ''){ $message = 'An error occurred.'; } ?><?= str_replace('CAPTCHA', '<abbr class="acronym">CAPTCHA</abbr>', Formatter::ToPlainText($message)) ?></p>
|
<p><? $message = $ex->getMessage(); if($message == ''){ $message = 'An error occurred.'; } ?><?= str_replace('CAPTCHA', '<abbr class="acronym">CAPTCHA</abbr>', Formatter::ToPlainText($message)) ?></p>
|
||||||
|
|
|
@ -625,17 +625,29 @@ input[type="checkbox"]:focus{
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.error{
|
.message{
|
||||||
border: 2px solid #c33b3b;
|
border: 2px solid rgba(0, 0, 0, .25);
|
||||||
border-radius: .25rem;
|
border-radius: .25rem;
|
||||||
background: #e14646;
|
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-shadow: 1px 1px 0px rgba(0, 0, 0, .75);
|
text-shadow: 1px 1px 0px rgba(0, 0, 0, .75);
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.error li:only-child{
|
.message.error{
|
||||||
|
background: #c45d5d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.success{
|
||||||
|
background: #5dc47c;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.message.error li{
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.message.error li:only-child{
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ebooks nav li.highlighted a:focus,
|
.ebooks nav li.highlighted a:focus,
|
||||||
|
@ -2058,7 +2070,7 @@ article.ebook h2 + section > h3:first-of-type{
|
||||||
}
|
}
|
||||||
|
|
||||||
form[action*="/polls/"],
|
form[action*="/polls/"],
|
||||||
form[action="/newsletter/subscribers"]{
|
form[action="/newsletter/subscriptions"]{
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 2rem;
|
grid-gap: 2rem;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
|
@ -2067,18 +2079,18 @@ form[action="/newsletter/subscribers"]{
|
||||||
}
|
}
|
||||||
|
|
||||||
form[action*="/polls/"] label.email,
|
form[action*="/polls/"] label.email,
|
||||||
form[action="/newsletter/subscribers"] label.email,
|
form[action="/newsletter/subscriptions"] label.email,
|
||||||
form[action="/newsletter/subscribers"] label.captcha{
|
form[action="/newsletter/subscriptions"] label.captcha{
|
||||||
grid-column: 1 / span 2;
|
grid-column: 1 / span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
form[action="/newsletter/subscribers"] label.captcha div{
|
form[action="/newsletter/subscriptions"] label.captcha div{
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 1rem;
|
grid-gap: 1rem;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
form[action="/newsletter/subscribers"] label.captcha div input{
|
form[action="/newsletter/subscriptions"] label.captcha div input{
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2087,14 +2099,14 @@ form fieldset ul{
|
||||||
}
|
}
|
||||||
|
|
||||||
form[action*="/polls/"] button,
|
form[action*="/polls/"] button,
|
||||||
form[action="/newsletter/subscribers"] button{
|
form[action="/newsletter/subscriptions"] 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*="/polls/"] fieldset,
|
||||||
form[action="/newsletter/subscribers"] fieldset{
|
form[action="/newsletter/subscriptions"] fieldset{
|
||||||
grid-column: 1 / span 2;
|
grid-column: 1 / span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2967,9 +2979,9 @@ ul.feed p{
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
form[action="/newsletter/subscribers"] label.text,
|
form[action="/newsletter/subscriptions"] label.text,
|
||||||
form[action="/newsletter/subscribers"] label.captcha,
|
form[action="/newsletter/subscriptions"] label.captcha,
|
||||||
form[action="/newsletter/subscribers"] fieldset{
|
form[action="/newsletter/subscriptions"] fieldset{
|
||||||
grid-column: 1 / span 2;
|
grid-column: 1 / span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<?
|
<?
|
||||||
require_once('Core.php');
|
require_once('Core.php');
|
||||||
|
|
||||||
|
$ebooks = [];
|
||||||
|
|
||||||
try{
|
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
|
$wwwFilesystemPath = EBOOKS_DIST_PATH . $urlPath; // Path to the deployed WWW files for this ebook
|
||||||
|
@ -17,9 +19,7 @@ try{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(Exceptions\InvalidAuthorException $ex){
|
catch(Exceptions\InvalidAuthorException $ex){
|
||||||
http_response_code(404);
|
Template::Emit404();
|
||||||
include(WEB_ROOT . '/404.php');
|
|
||||||
exit();
|
|
||||||
}
|
}
|
||||||
?><?= Template::Header(['title' => 'Ebooks by ' . strip_tags($ebooks[0]->AuthorsHtml), 'highlight' => 'ebooks', 'description' => 'All of the Standard Ebooks ebooks by ' . strip_tags($ebooks[0]->AuthorsHtml)]) ?>
|
?><?= Template::Header(['title' => 'Ebooks by ' . strip_tags($ebooks[0]->AuthorsHtml), 'highlight' => 'ebooks', 'description' => 'All of the Standard Ebooks ebooks by ' . strip_tags($ebooks[0]->AuthorsHtml)]) ?>
|
||||||
<main class="ebooks">
|
<main class="ebooks">
|
||||||
|
|
|
@ -8,6 +8,13 @@ use function Safe\preg_replace;
|
||||||
use function Safe\apcu_fetch;
|
use function Safe\apcu_fetch;
|
||||||
use function Safe\shuffle;
|
use function Safe\shuffle;
|
||||||
|
|
||||||
|
$ebook = new Ebook();
|
||||||
|
$transcriptionSources = [];
|
||||||
|
$scanSources = [];
|
||||||
|
$otherSources = [];
|
||||||
|
$carousel = [];
|
||||||
|
$carouselTag = null;
|
||||||
|
|
||||||
try{
|
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
|
$wwwFilesystemPath = EBOOKS_DIST_PATH . $urlPath; // Path to the deployed WWW files for this ebook
|
||||||
|
@ -42,9 +49,6 @@ try{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Divide our sources into transcriptions and scans
|
// Divide our sources into transcriptions and scans
|
||||||
$transcriptionSources = [];
|
|
||||||
$scanSources = [];
|
|
||||||
$otherSources = [];
|
|
||||||
foreach($ebook->Sources as $source){
|
foreach($ebook->Sources as $source){
|
||||||
switch($source->Type){
|
switch($source->Type){
|
||||||
case SOURCE_PROJECT_GUTENBERG:
|
case SOURCE_PROJECT_GUTENBERG:
|
||||||
|
@ -69,9 +73,7 @@ try{
|
||||||
|
|
||||||
// Generate the bottom carousel.
|
// Generate the bottom carousel.
|
||||||
// Pick a random tag from this ebook, and get ebooks in the same tag
|
// Pick a random tag from this ebook, and get ebooks in the same tag
|
||||||
$carousel = [];
|
|
||||||
$ebooks = [];
|
$ebooks = [];
|
||||||
$carouselTag = null;
|
|
||||||
if(sizeof($ebook->Tags) > 0){
|
if(sizeof($ebook->Tags) > 0){
|
||||||
$carouselTag = $ebook->Tags[rand(0, sizeof($ebook->Tags) - 1)];
|
$carouselTag = $ebook->Tags[rand(0, sizeof($ebook->Tags) - 1)];
|
||||||
$ebooks = Library::GetEbooksByTag(strtolower($carouselTag->Name));
|
$ebooks = Library::GetEbooksByTag(strtolower($carouselTag->Name));
|
||||||
|
@ -104,9 +106,7 @@ catch(Exceptions\SeeOtherEbookException $ex){
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
catch(Exceptions\InvalidEbookException $ex){
|
catch(Exceptions\InvalidEbookException $ex){
|
||||||
http_response_code(404);
|
Template::Emit404();
|
||||||
include(WEB_ROOT . '/404.php');
|
|
||||||
exit();
|
|
||||||
}
|
}
|
||||||
?><?= Template::Header(['title' => strip_tags($ebook->TitleWithCreditsHtml) . ' - Free ebook download', 'ogType' => 'book', 'coverUrl' => $ebook->DistCoverUrl, 'highlight' => 'ebooks', 'description' => 'Free epub ebook download of the Standard Ebooks edition of ' . $ebook->Title . ': ' . $ebook->Description]) ?>
|
?><?= Template::Header(['title' => strip_tags($ebook->TitleWithCreditsHtml) . ' - Free ebook download', 'ogType' => 'book', 'coverUrl' => $ebook->DistCoverUrl, 'highlight' => 'ebooks', 'description' => 'Free epub ebook download of the Standard Ebooks edition of ' . $ebook->Title . ': ' . $ebook->Description]) ?>
|
||||||
<main>
|
<main>
|
||||||
|
|
|
@ -14,6 +14,10 @@ try{
|
||||||
$pages = 0;
|
$pages = 0;
|
||||||
$totalEbooks = 0;
|
$totalEbooks = 0;
|
||||||
$collectionObject = null;
|
$collectionObject = null;
|
||||||
|
$pageDescription = '';
|
||||||
|
$pageTitle = '';
|
||||||
|
$pageHeader = '';
|
||||||
|
$queryString = '';
|
||||||
|
|
||||||
if($page <= 0){
|
if($page <= 0){
|
||||||
$page = 1;
|
$page = 1;
|
||||||
|
@ -90,9 +94,8 @@ try{
|
||||||
if($page > 1){
|
if($page > 1){
|
||||||
$pageTitle .= ', page ' . $page;
|
$pageTitle .= ', page ' . $page;
|
||||||
}
|
}
|
||||||
$pageDescription = 'Page ' . $page . ' of the Standard Ebooks free ebook library';
|
|
||||||
|
|
||||||
$queryString = '';
|
$pageDescription = 'Page ' . $page . ' of the Standard Ebooks free ebook library';
|
||||||
|
|
||||||
if($collection === null){
|
if($collection === null){
|
||||||
if($query != ''){
|
if($query != ''){
|
||||||
|
@ -119,9 +122,7 @@ try{
|
||||||
$queryString = preg_replace('/^&/ius', '', $queryString);
|
$queryString = preg_replace('/^&/ius', '', $queryString);
|
||||||
}
|
}
|
||||||
catch(Exceptions\InvalidCollectionException $ex){
|
catch(Exceptions\InvalidCollectionException $ex){
|
||||||
http_response_code(404);
|
Template::Emit404();
|
||||||
include(WEB_ROOT . '/404.php');
|
|
||||||
exit();
|
|
||||||
}
|
}
|
||||||
?><?= Template::Header(['title' => $pageTitle, 'highlight' => 'ebooks', 'description' => $pageDescription]) ?>
|
?><?= Template::Header(['title' => $pageTitle, 'highlight' => 'ebooks', 'description' => $pageDescription]) ?>
|
||||||
<main class="ebooks">
|
<main class="ebooks">
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?
|
|
||||||
require_once('Core.php');
|
|
||||||
|
|
||||||
try{
|
|
||||||
$subscriber = NewsletterSubscriber::Get(HttpInput::Str(GET, 'uuid') ?? '');
|
|
||||||
$subscriber->Confirm();
|
|
||||||
}
|
|
||||||
catch(Exceptions\InvalidNewsletterSubscriberException $ex){
|
|
||||||
http_response_code(404);
|
|
||||||
include(WEB_ROOT . '/404.php');
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
?><?= Template::Header(['title' => 'Your subscription to the Standard Ebooks newsletter has been confirmed', 'highlight' => 'newsletter', 'description' => 'Your subscription to the Standard Ebooks newsletter has been confirmed.']) ?>
|
|
||||||
<main>
|
|
||||||
<article>
|
|
||||||
<h1>Your subscription is confirmed!</h1>
|
|
||||||
<p>Thank you! You’ll now receive Standard Ebooks email newsletters.</p>
|
|
||||||
<p>To unsubscribe, simply follow the link at the bottom of any of our newsletters, or <a href="<?= $subscriber->Url ?>/delete">click here</a>.</p>
|
|
||||||
</article>
|
|
||||||
</main>
|
|
||||||
<?= Template::Footer() ?>
|
|
|
@ -1,73 +0,0 @@
|
||||||
<?
|
|
||||||
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();
|
|
||||||
|
|
||||||
$subscriber = new NewsletterSubscriber();
|
|
||||||
|
|
||||||
if(HttpInput::Str(POST, 'automationtest', false)){
|
|
||||||
// A bot filled out this form field, which should always be empty. Pretend like we succeeded.
|
|
||||||
if($requestType == WEB){
|
|
||||||
http_response_code(303);
|
|
||||||
header('Location: /newsletter/subscribers/success');
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
// Access via REST api; 201 CREATED with location
|
|
||||||
http_response_code(201);
|
|
||||||
header('Location: ' . $subscriber->Url);
|
|
||||||
}
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
$subscriber->FirstName = HttpInput::Str(POST, 'firstname', false);
|
|
||||||
$subscriber->LastName = HttpInput::Str(POST, 'lastname', false);
|
|
||||||
$subscriber->Email = HttpInput::Str(POST, 'email', false);
|
|
||||||
$subscriber->IsSubscribedToNewsletter = HttpInput::Bool(POST, 'newsletter', false);
|
|
||||||
$subscriber->IsSubscribedToSummary = HttpInput::Bool(POST, 'monthlysummary', false);
|
|
||||||
|
|
||||||
$captcha = $_SESSION['captcha'] ?? '';
|
|
||||||
|
|
||||||
if($captcha === '' || mb_strtolower($captcha) !== mb_strtolower(HttpInput::Str(POST, 'captcha', false) ?? '')){
|
|
||||||
throw new Exceptions\ValidationException(new Exceptions\InvalidCaptchaException());
|
|
||||||
}
|
|
||||||
|
|
||||||
$subscriber->Create();
|
|
||||||
|
|
||||||
session_unset();
|
|
||||||
|
|
||||||
if($requestType == WEB){
|
|
||||||
http_response_code(303);
|
|
||||||
header('Location: /newsletter/subscribers/success');
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
// Access via REST api; 201 CREATED with location
|
|
||||||
http_response_code(201);
|
|
||||||
header('Location: ' . $subscriber->Url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(Exceptions\SeException $ex){
|
|
||||||
// Validation failed
|
|
||||||
if($requestType == WEB){
|
|
||||||
$_SESSION['subscriber'] = $subscriber;
|
|
||||||
$_SESSION['exception'] = $ex;
|
|
||||||
|
|
||||||
// Access via form; 303 redirect to the form, which will emit a 400 BAD REQUEST
|
|
||||||
http_response_code(303);
|
|
||||||
header('Location: /newsletter/subscribers/new');
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
// Access via REST api; 400 BAD REQUEST
|
|
||||||
http_response_code(400);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
<?
|
|
||||||
require_once('Core.php');
|
|
||||||
|
|
||||||
?><?= Template::Header(['title' => 'Subscribe to the Standard Ebooks newsletter', 'highlight' => 'newsletter', 'description' => 'Subscribe to the Standard Ebooks newsletter to receive occasional updates about the project.']) ?>
|
|
||||||
<main>
|
|
||||||
<article>
|
|
||||||
<h1>Almost done!</h1>
|
|
||||||
<p>Please check your email inbox for a confirmation email containing a link to finalize your subscription to our newsletter.</p>
|
|
||||||
<p>Your subscription won’t be activated until you click that link—this helps us prevent spam. Thank you!</p>
|
|
||||||
</article>
|
|
||||||
</main>
|
|
||||||
<?= Template::Footer() ?>
|
|
21
www/newsletter/subscriptions/confirm.php
Normal file
21
www/newsletter/subscriptions/confirm.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?
|
||||||
|
require_once('Core.php');
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
$subscription = new NewsletterSubscription();
|
||||||
|
|
||||||
|
try{
|
||||||
|
$subscription = NewsletterSubscription::Get(HttpInput::Str(GET, 'uuid') ?? '');
|
||||||
|
|
||||||
|
if(!$subscription->IsConfirmed){
|
||||||
|
$subscription->Confirm();
|
||||||
|
$_SESSION['subscription-confirmed'] = $subscription->UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(303);
|
||||||
|
header('Location: ' . $subscription->Url);
|
||||||
|
}
|
||||||
|
catch(Exceptions\InvalidNewsletterSubscriptionException $ex){
|
||||||
|
Template::Emit404();
|
||||||
|
}
|
|
@ -11,8 +11,8 @@ try{
|
||||||
throw new Exceptions\InvalidRequestException();
|
throw new Exceptions\InvalidRequestException();
|
||||||
}
|
}
|
||||||
|
|
||||||
$subscriber = NewsletterSubscriber::Get(HttpInput::Str(GET, 'uuid') ?? '');
|
$subscription = NewsletterSubscription::Get(HttpInput::Str(GET, 'uuid') ?? '');
|
||||||
$subscriber->Delete();
|
$subscription->Delete();
|
||||||
|
|
||||||
if($requestType == REST){
|
if($requestType == REST){
|
||||||
exit();
|
exit();
|
||||||
|
@ -22,12 +22,14 @@ catch(Exceptions\InvalidRequestException $ex){
|
||||||
http_response_code(405);
|
http_response_code(405);
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
catch(Exceptions\InvalidNewsletterSubscriberException $ex){
|
catch(Exceptions\InvalidNewsletterSubscriptionException $ex){
|
||||||
http_response_code(404);
|
|
||||||
if($requestType == WEB){
|
if($requestType == WEB){
|
||||||
include(WEB_ROOT . '/404.php');
|
Template::Emit404();
|
||||||
}
|
}
|
||||||
|
else{
|
||||||
|
http_response_code(404);
|
||||||
exit();
|
exit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
?><?= Template::Header(['title' => 'You’ve unsubscribed from the Standard Ebooks newsletter', 'highlight' => 'newsletter', 'description' => 'You’ve unsubscribed from the Standard Ebooks newsletter.']) ?>
|
?><?= Template::Header(['title' => 'You’ve unsubscribed from the Standard Ebooks newsletter', 'highlight' => 'newsletter', 'description' => 'You’ve unsubscribed from the Standard Ebooks newsletter.']) ?>
|
70
www/newsletter/subscriptions/get.php
Normal file
70
www/newsletter/subscriptions/get.php
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<?
|
||||||
|
require_once('Core.php');
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
$subscription = new NewsletterSubscription();
|
||||||
|
$created = false;
|
||||||
|
$updated = false;
|
||||||
|
$confirmed = false;
|
||||||
|
|
||||||
|
try{
|
||||||
|
if(isset($_SESSION['subscription-created']) && $_SESSION['subscription-created'] == 0){
|
||||||
|
$created = true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$subscription = NewsletterSubscription::Get(HttpInput::Str(GET, 'uuid', false) ?? '');
|
||||||
|
|
||||||
|
if(isset($_SESSION['subscription-created']) && $_SESSION['subscription-created'] == $subscription->UserId){
|
||||||
|
$created = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($_SESSION['subscription-updated']) && $_SESSION['subscription-updated'] == $subscription->UserId){
|
||||||
|
$updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($_SESSION['subscription-confirmed']) && $_SESSION['subscription-confirmed'] == $subscription->UserId){
|
||||||
|
$confirmed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($created || $updated || $confirmed){
|
||||||
|
session_unset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($created){
|
||||||
|
// HTTP 201 Created
|
||||||
|
http_response_code(201);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exceptions\SeException $ex){
|
||||||
|
Template::Emit404();
|
||||||
|
}
|
||||||
|
|
||||||
|
?><?= Template::Header(['title' => 'Your subscription to the Standard Ebooks newsletter', 'highlight' => 'newsletter', 'description' => 'Your subscription to the Standard Ebooks newsletter.']) ?>
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
<? if($subscription->IsConfirmed){ ?>
|
||||||
|
<h1>Your Standard Ebooks Newsletter Subscription</h1>
|
||||||
|
<? if($updated){ ?>
|
||||||
|
<p class="message success">Your settings have been saved!</p>
|
||||||
|
<? } ?>
|
||||||
|
<? if($confirmed){ ?>
|
||||||
|
<p class="message success">Your subscription has been confirmed!</p>
|
||||||
|
<? } ?>
|
||||||
|
<p>You’re set to receive the following newsletters:</p>
|
||||||
|
<ul>
|
||||||
|
<? if($subscription->IsSubscribedToSummary){ ?><li><p>A monthly summary of new ebook releases</p></li><? } ?>
|
||||||
|
<? if($subscription->IsSubscribedToNewsletter){ ?><li><p>The occasional Standard Ebooks newsletter</p></li><? } ?>
|
||||||
|
</ul>
|
||||||
|
<p class="button-row narrow">
|
||||||
|
<a href="<?= $subscription->Url ?>/delete" class="button">Unsubscribe</a>
|
||||||
|
</p>
|
||||||
|
<? }else{ ?>
|
||||||
|
<h1>Almost done!</h1>
|
||||||
|
<p>Please check your email inbox for a confirmation email containing a link to finalize your subscription to our newsletter.</p>
|
||||||
|
<p>Your subscription won’t be activated until you click that link—this helps us prevent spam. Thank you!</p>
|
||||||
|
<? } ?>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<?= Template::Footer() ?>
|
|
@ -5,7 +5,7 @@ use function Safe\session_unset;
|
||||||
|
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
$subscriber = $_SESSION['subscriber'] ?? new NewsletterSubscriber();
|
$subscription = $_SESSION['subscription'] ?? new NewsletterSubscription();
|
||||||
$exception = $_SESSION['exception'] ?? null;
|
$exception = $_SESSION['exception'] ?? null;
|
||||||
|
|
||||||
if($exception){
|
if($exception){
|
||||||
|
@ -29,12 +29,12 @@ if($exception){
|
||||||
|
|
||||||
<?= Template::Error(['exception' => $exception]) ?>
|
<?= Template::Error(['exception' => $exception]) ?>
|
||||||
|
|
||||||
<form action="/newsletter/subscribers" method="post">
|
<form action="/newsletter/subscriptions" method="post">
|
||||||
<label class="automation-test"><? /* Test for spam bots filling out all fields */ ?>
|
<label class="automation-test"><? /* Test for spam bots filling out all fields */ ?>
|
||||||
<input type="text" name="automationtest" value="" maxlength="80" />
|
<input type="text" name="automationtest" value="" maxlength="80" />
|
||||||
</label>
|
</label>
|
||||||
<label class="email">Your email address
|
<label class="email">Your email address
|
||||||
<input type="email" name="email" value="<?= Formatter::ToPlainText($subscriber->Email) ?>" maxlength="80" required="required" />
|
<input type="email" name="email" value="<? if($subscription->User !== null){ ?><?= Formatter::ToPlainText($subscription->User->Email) ?><? } ?>" maxlength="80" required="required" />
|
||||||
</label>
|
</label>
|
||||||
<label class="captcha">
|
<label class="captcha">
|
||||||
Type the letters in the <abbr class="acronym">CAPTCHA</abbr> image
|
Type the letters in the <abbr class="acronym">CAPTCHA</abbr> image
|
||||||
|
@ -47,10 +47,10 @@ if($exception){
|
||||||
<p>What kind of email would you like to receive?</p>
|
<p>What kind of email would you like to receive?</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<label class="checkbox"><input type="checkbox" value="1" name="newsletter"<? if($subscriber->IsSubscribedToNewsletter){ ?> checked="checked"<? } ?> />The occasional Standard Ebooks newsletter</label>
|
<label class="checkbox"><input type="checkbox" value="1" name="issubscribedtonewsletter"<? if($subscription->IsSubscribedToNewsletter){ ?> checked="checked"<? } ?> />The occasional Standard Ebooks newsletter</label>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label class="checkbox"><input type="checkbox" value="1" name="monthlysummary"<? if($subscriber->IsSubscribedToSummary){ ?> checked="checked"<? } ?> />A monthly summary of new ebook releases</label>
|
<label class="checkbox"><input type="checkbox" value="1" name="issubscribedtosummary"<? if($subscription->IsSubscribedToSummary){ ?> checked="checked"<? } ?> />A monthly summary of new ebook releases</label>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</fieldset>
|
</fieldset>
|
114
www/newsletter/subscriptions/post.php
Normal file
114
www/newsletter/subscriptions/post.php
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
<?
|
||||||
|
require_once('Core.php');
|
||||||
|
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
use function Safe\session_unset;
|
||||||
|
|
||||||
|
if(HttpInput::RequestMethod() != HTTP_POST){
|
||||||
|
http_response_code(405);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
$requestType = HttpInput::RequestType();
|
||||||
|
|
||||||
|
$subscription = new NewsletterSubscription();
|
||||||
|
|
||||||
|
if(HttpInput::Str(POST, 'automationtest', false)){
|
||||||
|
// A bot filled out this form field, which should always be empty. Pretend like we succeeded.
|
||||||
|
if($requestType == WEB){
|
||||||
|
http_response_code(303);
|
||||||
|
$uuid = Uuid::uuid4();
|
||||||
|
$subscription->User = new User();
|
||||||
|
$subscription->User->Uuid = $uuid->toString();
|
||||||
|
$_SESSION['subscription-created'] = 0; // 0 means 'bot'
|
||||||
|
header('Location: ' . $subscription->Url);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// Access via REST api; 201 CREATED with location
|
||||||
|
http_response_code(201);
|
||||||
|
header('Location: ' . $subscription->Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
$subscription->User = new User();
|
||||||
|
$subscription->User->Email = HttpInput::Str(POST, 'email', false);
|
||||||
|
$subscription->IsSubscribedToNewsletter = HttpInput::Bool(POST, 'issubscribedtonewsletter', false);
|
||||||
|
$subscription->IsSubscribedToSummary = HttpInput::Bool(POST, 'issubscribedtosummary', false);
|
||||||
|
|
||||||
|
$captcha = $_SESSION['captcha'] ?? '';
|
||||||
|
|
||||||
|
$exception = new Exceptions\ValidationException();
|
||||||
|
|
||||||
|
try{
|
||||||
|
$subscription->Validate();
|
||||||
|
}
|
||||||
|
catch(Exceptions\ValidationException $ex){
|
||||||
|
$exception->Add($ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($captcha === '' || mb_strtolower($captcha) !== mb_strtolower(HttpInput::Str(POST, 'captcha', false) ?? '')){
|
||||||
|
$exception->Add(new Exceptions\InvalidCaptchaException());
|
||||||
|
}
|
||||||
|
|
||||||
|
if($exception->HasExceptions){
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
$subscription->Create();
|
||||||
|
|
||||||
|
session_unset();
|
||||||
|
|
||||||
|
if($requestType == WEB){
|
||||||
|
http_response_code(303);
|
||||||
|
$_SESSION['subscription-created'] = $subscription->UserId;
|
||||||
|
header('Location: ' . $subscription->Url);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// Access via REST api; 201 CREATED with location
|
||||||
|
http_response_code(201);
|
||||||
|
header('Location: ' . $subscription->Url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exceptions\NewsletterSubscriptionExistsException $ex){
|
||||||
|
// Subscription exists.
|
||||||
|
if($requestType == WEB){
|
||||||
|
// If we're accessing from the web, update the subscription,
|
||||||
|
// re-sending the confirmation email if the user isn't yet confirmed
|
||||||
|
$existingSubscription = NewsletterSubscription::Get($subscription->User->Uuid);
|
||||||
|
$subscription->IsConfirmed = $existingSubscription->IsConfirmed;
|
||||||
|
$subscription->Save();
|
||||||
|
|
||||||
|
// Don't re-send the email after all, to prevent spam
|
||||||
|
// if(!$subscription->IsConfirmed){
|
||||||
|
// $subscription->SendConfirmationEmail();
|
||||||
|
// }
|
||||||
|
|
||||||
|
http_response_code(303);
|
||||||
|
$_SESSION['subscription-updated'] = $subscription->UserId;
|
||||||
|
header('Location: ' . $subscription->Url);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// Access via REST api; 409 CONFLICT
|
||||||
|
http_response_code(409);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exceptions\SeException $ex){
|
||||||
|
// Validation failed
|
||||||
|
if($requestType == WEB){
|
||||||
|
$_SESSION['subscription'] = $subscription;
|
||||||
|
$_SESSION['exception'] = $ex;
|
||||||
|
|
||||||
|
// Access via form; 303 redirect to the form, which will emit a 400 BAD REQUEST
|
||||||
|
http_response_code(303);
|
||||||
|
header('Location: /newsletter/subscriptions/new');
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// Access via REST api; 400 BAD REQUEST
|
||||||
|
http_response_code(400);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,15 +3,13 @@ require_once('Core.php');
|
||||||
|
|
||||||
use Safe\DateTime;
|
use Safe\DateTime;
|
||||||
|
|
||||||
$poll = null;
|
$poll = new Poll();
|
||||||
|
|
||||||
try{
|
try{
|
||||||
$poll = Poll::GetByUrlName(HttpInput::Str(GET, 'pollurlname', false));
|
$poll = Poll::GetByUrlName(HttpInput::Str(GET, 'pollurlname', false));
|
||||||
}
|
}
|
||||||
catch(Exceptions\SeException $ex){
|
catch(Exceptions\SeException $ex){
|
||||||
http_response_code(404);
|
Template::Emit404();
|
||||||
include(WEB_ROOT . '/404.php');
|
|
||||||
exit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
?><?= Template::Header(['title' => $poll->Name, 'highlight' => '', 'description' => $poll->Description]) ?>
|
?><?= Template::Header(['title' => $poll->Name, 'highlight' => '', 'description' => $poll->Description]) ?>
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
<?
|
<?
|
||||||
require_once('Core.php');
|
require_once('Core.php');
|
||||||
|
|
||||||
$poll = null;
|
$poll = new Poll();
|
||||||
|
|
||||||
try{
|
try{
|
||||||
$poll = Poll::GetByUrlName(HttpInput::Str(GET, 'pollurlname', false));
|
$poll = Poll::GetByUrlName(HttpInput::Str(GET, 'pollurlname', false));
|
||||||
}
|
}
|
||||||
catch(Exceptions\SeException $ex){
|
catch(Exceptions\SeException $ex){
|
||||||
http_response_code(404);
|
Template::Emit404();
|
||||||
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.']) ?>
|
?><?= Template::Header(['title' => 'Results for the ' . $poll->Name . ' poll', 'highlight' => '', 'description' => 'The voting results for the ' . $poll->Name . ' poll.']) ?>
|
||||||
|
|
|
@ -8,15 +8,13 @@ session_start();
|
||||||
$vote = $_SESSION['vote'] ?? new Vote();
|
$vote = $_SESSION['vote'] ?? new Vote();
|
||||||
$exception = $_SESSION['exception'] ?? null;
|
$exception = $_SESSION['exception'] ?? null;
|
||||||
|
|
||||||
$poll = null;
|
$poll = new Poll();
|
||||||
|
|
||||||
try{
|
try{
|
||||||
$poll = Poll::GetByUrlName(HttpInput::Str(GET, 'pollurlname', false));
|
$poll = Poll::GetByUrlName(HttpInput::Str(GET, 'pollurlname', false));
|
||||||
}
|
}
|
||||||
catch(Exceptions\SeException $ex){
|
catch(Exceptions\SeException $ex){
|
||||||
http_response_code(404);
|
Template::Emit404();
|
||||||
include(WEB_ROOT . '/404.php');
|
|
||||||
exit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if($exception){
|
if($exception){
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
<?
|
<?
|
||||||
require_once('Core.php');
|
require_once('Core.php');
|
||||||
|
|
||||||
$poll = null;
|
$poll = new Poll();
|
||||||
|
|
||||||
try{
|
try{
|
||||||
$poll = Poll::GetByUrlName(HttpInput::Str(GET, 'pollurlname', false));
|
$poll = Poll::GetByUrlName(HttpInput::Str(GET, 'pollurlname', false));
|
||||||
}
|
}
|
||||||
catch(Exceptions\SeException $ex){
|
catch(Exceptions\SeException $ex){
|
||||||
http_response_code(404);
|
Template::Emit404();
|
||||||
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!']) ?>
|
?><?= Template::Header(['title' => 'Thank you for voting!', 'highlight' => 'newsletter', 'description' => 'Thank you for voting in a Standard Ebooks poll!']) ?>
|
||||||
|
|
|
@ -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 ns.* from NewsletterSubscriptions ns inner join Users u on ns.UserId = u.UserId where u.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 ns.* from NewsletterSubscriptions ns inner join Users u on ns.UserId = u.UserId where u.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();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue