Improve handling of returning patrons

This commit is contained in:
Alex Cabal 2022-06-29 18:05:49 -05:00
parent dbefba6b94
commit 32206f3cd7
10 changed files with 61 additions and 55 deletions

View file

@ -19,4 +19,24 @@ class Db{
return $GLOBALS['DbConnection']->Query($query, $args, $class);
}
public static function QueryInt(string $query, array $args = []): int{
// Useful for queries that return a single integer as a result, like count(*) or sum(*).
if(!isset($GLOBALS['DbConnection'])){
$GLOBALS['DbConnection'] = new DbConnection(DATABASE_DEFAULT_DATABASE, DATABASE_DEFAULT_HOST);
}
if(!is_array($args)){
$args = [$args];
}
$result = $GLOBALS['DbConnection']->Query($query, $args);
if(sizeof($result) > 0){
return current((Array)$result[0]);
}
return 0;
}
}

View file

@ -6,7 +6,7 @@ class Patron extends PropertiesBase{
public $UserId = null;
public $IsAnonymous;
public $AlternateName;
public $IsSubscribedToEmail;
public $IsSubscribedToEmails;
public $Created = null;
public $Ended = null;
@ -30,35 +30,26 @@ class Patron extends PropertiesBase{
return $result[0];
}
public function Create(bool $sendEmail = true): void{
public function Create(): void{
$this->Created = new DateTime();
Db::Query('INSERT into Patrons (Created, UserId, IsAnonymous, AlternateName, IsSubscribedToEmails) values(?, ?, ?, ?, ?);', [$this->Created, $this->UserId, $this->IsAnonymous, $this->AlternateName, $this->IsSubscribedToEmails]);
Db::Query('INSERT into Patrons (Created, UserId, IsAnonymous, AlternateName, IsSubscribedToEmail) values(?, ?, ?, ?, ?);', [$this->Created, $this->UserId, $this->IsAnonymous, $this->AlternateName, $this->IsSubscribedToEmail]);
// If this is a patron for the first time, send the first-time patron email.
// Otherwise, send the returning patron email.
$isReturning = Db::QueryInt('SELECT count(*) from Patrons where UserId = ?', [$this->UserId]) > 1;
if($sendEmail){
$this->SendWelcomeEmail();
}
$this->SendWelcomeEmail($isReturning);
}
public function Reactivate(bool $sendEmail = true): void{
Db::Query('UPDATE Patrons set Created = utc_timestamp(), Ended = null, IsAnonymous = ?, IsSubscribedToEmail = ?, AlternateName = ? where UserId = ?;', [$this->IsAnonymous, $this->IsSubscribedToEmail, $this->AlternateName, $this->UserId]);
$this->Created = new DateTime();
$this->Ended = null;
if($sendEmail){
$this->SendWelcomeEmail();
}
}
private function SendWelcomeEmail(): void{
private function SendWelcomeEmail(bool $isReturning): void{
$this->__get('User');
if($this->User !== null){
$em = new Email();
$em->To = $this->User->Email;
$em->From = EDITOR_IN_CHIEF_EMAIL_ADDRESS;
$em->Subject = 'Thank you for supporting Standard Ebooks!';
$em->Body = Template::EmailPatronsCircleWelcome(['isAnonymous' => $this->IsAnonymous]);
$em->TextBody = Template::EmailPatronsCircleWelcomeText(['isAnonymous' => $this->IsAnonymous]);
$em->Body = Template::EmailPatronsCircleWelcome(['isAnonymous' => $this->IsAnonymous, 'isReturning' => $isReturning]);
$em->TextBody = Template::EmailPatronsCircleWelcomeText(['isAnonymous' => $this->IsAnonymous, 'isReturning' => $isReturning]);
$em->Send();
}
}

View file

@ -25,7 +25,7 @@ class Poll extends PropertiesBase{
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;
$this->VoteCount = Db::QueryInt('select count(*) from Votes v inner join PollItems pi on v.PollItemId = pi.PollItemId where pi.PollId = ?', [$this->PollId]);
}
return $this->VoteCount;

View file

@ -9,7 +9,7 @@ class PollItem extends PropertiesBase{
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;
$this->VoteCount = Db::QueryInt('select count(*) from Votes v inner join PollItems pi on v.PollItemId = pi.PollItemId where pi.PollItemId = ?', [$this->PollItemId]);
}
return $this->VoteCount;

View file

@ -49,10 +49,10 @@ class Vote extends PropertiesBase{
}
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
if(Db::QueryInt('
SELECT count(*) 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){
on v.PollItemId = x.PollItemId where v.UserId = ?', [$this->UserId]) > 0){
$error->Add(new Exceptions\VoteExistsException());
}
}

View file

@ -68,6 +68,11 @@ try{
foreach($pendingPayments as $pendingPayment){
if($pendingPayment->ChannelId == PAYMENT_CHANNEL_FA){
if(Db::QueryInt('SELECT count(*) from Payments where TransactionId = ? limit 1', [$pendingPayment->TransactionId]) > 0){
$log->Write('Donation already exists in database.');
continue;
}
$log->Write('Processing donation ' . $pendingPayment->TransactionId . ' ...');
$driver->get('https://fundraising.fracturedatlas.org/admin/donations?query=' . $pendingPayment->TransactionId);
@ -160,21 +165,14 @@ try{
if(($payment->IsRecurring && $payment->Amount >= 10 && $payment->Created >= $lastMonth) || ($payment->Amount >= 100 && $payment->Created >= $lastYear)){
// This payment is eligible for the Patrons Circle.
// Are we already a patron?
try{
$patron = Patron::Get($payment->UserId);
}
catch(Exceptions\InvalidPatronException $ex){
if(!Db::QueryInt('SELECT count(*) from Patrons where UserId = ? and Ended is not null', [$payment->UserId])){
// Not a patron yet, add them to the Patrons Circle
$patron = new Patron();
$patron->UserId = $payment->UserId;
$patron->User = $payment->User;
}
if($patron->Created === null || $patron->Ended !== null){
// If we're a new patron, or an old patron that was deactivated,
// re-enable them as a patron in the system
$patron->IsAnonymous = (trim($detailsRow->findElement(WebDriverBy::xpath('//td[preceding-sibling::td[normalize-space(.) = "Attribution"]]'))->getText()) == 'Private');
$patron->IsSubscribedToEmail = $patron->User !== null && $patron->User->Email !== null;
$patron->IsSubscribedToEmails = $patron->User !== null && $patron->User->Email !== null;
try{
$patron->AlternateName = trim($detailsRow->findElement(WebDriverBy::xpath('//td[preceding-sibling::td[normalize-space(.) = "Attribution Text"]]'))->getText());
@ -182,20 +180,14 @@ try{
catch(Exception $ex){
}
if($patron->Created === null){
$log->Write('Adding donor as patron ...');
$patron->Create();
}
elseif($patron->Ended !== null){
$log->Write('Reactivating donor as patron ...');
$patron->Reactivate();
}
}
}
else{
// Not a patron; send a thank you email anyway, but only if this is a non-recurring donation,
// Not eligible to be 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
$previousPaymentCount = (Db::Query('SELECT count(*) as PreviousPaymentCount from Payments where UserId = ? and IsRecurring = true', [$payment->UserId]))[0]->PreviousPaymentCount;
$previousPaymentCount = Db::QueryInt('SELECT count(*) from Payments where UserId = ? and IsRecurring = true', [$payment->UserId]);
// 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){

View file

@ -9,7 +9,7 @@ $startDate = new DateTime('2022-07-01');
$endDate = new DateTime('2022-07-31');
$autoHide = $autoHide ?? true;
$showDonateButton = $showDonateButton ?? true;
$current = (Db::Query('SELECT count(*) as PatronCount from Patrons where Created >= ?', [$startDate]))[0]->PatronCount;
$current = Db::QueryInt('SELECT count(*) from Patrons where Created >= ?', [$startDate]);
$target = 70;
$stretchCurrent = 0;
$stretchTarget = 20;

View file

@ -7,13 +7,13 @@
<p>Hello,</p>
<p>I wanted to thank you personally for your recent donation to Standard Ebooks. Your donation will go towards continuing our mission of producing and distributing high-quality ebooks that are free of cost and free of copyright restrictions. Donations like yours help ensure that the world's literature is available in beautiful editions made for the digital age.</p>
<? if($isAnonymous){ ?>
<p>I'm pleased to be able to include you in our Patrons Circle. Since you indicated you want your donation to remain anonymous, I haven't listed your name on our masthead. If you do prefer to have your name listed, just let me know.</p>
<p>I'm pleased to be able to <? if($isReturning){ ?>welcome you back to<? }else{ ?>include you in<? } ?> our Patrons Circle. Since you indicated you want your donation to remain anonymous, I haven't listed your name on our masthead. If you do prefer to have your name listed, just let me know.</p>
<? }else{ ?>
<p>I'm pleased to be able to include you in our Patrons Circle, with your name listed on our masthead for the duration of your donation. If you'd like to use a different name than the one you entered on our donation form, just let me know.</p>
<p>I'm pleased to be able to <? if($isReturning){ ?>welcome you back to<? }else{ ?>include you in<? } ?> our Patrons Circle, with your name listed on our masthead for the duration of your donation. If you'd like to use a different name than the one you entered on our donation form, just let me know.</p>
<? } ?>
<p>As a Patron, once per quarter you may suggest a book for inclusion in our Wanted Ebooks list. Before submitting a suggestion, please review our <a href="https://standardebooks.org/contribute/collections-policy">collections policy</a>; then you can contact me directly at <a href="mailto:<?= EDITOR_IN_CHIEF_EMAIL_ADDRESS ?>"><?= EDITOR_IN_CHIEF_EMAIL_ADDRESS ?></a> with your selection.</p>
<p>If I may ask, how did you hear about Standard Ebooks? Having an idea of where our readers and supporters find out about us is extremely helpful.</p>
<p>Please don't hesitate to contact me if you have questions or suggestions. Thanks again for your donation, and for supporting the literate arts!</p>
<? if(!$isReturning){ ?><p>If I may ask, how did you hear about Standard Ebooks? Having an idea of where our readers and supporters find out about us is extremely helpful.</p><? } ?>
<p><? if($isReturning){ ?>As always, please<? }else{ ?>Please<? } ?> don't hesitate to contact me if you have questions or suggestions. Thanks again for your donation, and for supporting the literate arts!</p>
<footer style="margin-top: 2em;">
<p>Alex Cabal</p>
<p>S.E. Editor-in-Chief</p>

View file

@ -1,16 +1,19 @@
Hello,
I wanted to thank you personally for your recent donation to Standard Ebooks. Your donation will go towards continuing our mission of producing and distributing high-quality ebooks that are free of cost and free of copyright restrictions. Donations like yours help ensure that the world's literature is available in beautiful editions made for the digital age.
<? if($isAnonymous){ ?>
I'm pleased to be able to include you in our Patrons Circle. Since you indicated you want your donation to remain anonymous, I haven't listed your name on our masthead. If you do prefer to have your name listed, just let me know.
I'm pleased to be able to <? if($isReturning){ ?>welcome you back to<? }else{ ?>include you in<? } ?> our Patrons Circle. Since you indicated you want your donation to remain anonymous, I haven't listed your name on our masthead. If you do prefer to have your name listed, just let me know.
<? }else{ ?>
I'm pleased to be able to include you in our Patrons Circle, with your name listed on our masthead for the duration of your donation. If you'd like to use a different name than the one you entered on our donation form, just let me know.
I'm pleased to be able to <? if($isReturning){ ?>welcome you back to<? }else{ ?>include you in<? } ?> our Patrons Circle, with your name listed on our masthead for the duration of your donation. If you'd like to use a different name than the one you entered on our donation form, just let me know.
<? } ?>
As a Patron, once per quarter you may suggest a book for inclusion in our Wanted Ebooks list. Before submitting a suggestion, please review our collections policy, at <https://standardebooks.org/contribute/collections-policy>; then you can contact me directly at <?= EDITOR_IN_CHIEF_EMAIL_ADDRESS ?> with your selection.
<? if(!$isReturning){ ?>
If I may ask, how did you hear about Standard Ebooks? Having an idea of where our readers and supporters find out about us is extremely helpful.
If I may ask, how did you hear about Standard Ebooks? Having an idea of where our readers and supporters find out about us is extremely helpful.<? } ?>
Please don't hesitate to contact me if you have questions or suggestions. Thanks again for your donation, and for supporting the literate arts!
<? if($isReturning){ ?>As always, please<? }else{ ?>Please<? } ?> don't hesitate to contact me if you have questions or suggestions. Thanks again for your donation, and for supporting the literate arts!
Alex Cabal

View file

@ -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;
');
$anonymousPatronCount = Db::Query('SELECT sum(cnt) as AnonymousPatronCount
$anonymousPatronCount = Db::QueryInt('SELECT sum(cnt)
from
(
(
@ -39,7 +39,7 @@ $anonymousPatronCount = Db::Query('SELECT sum(cnt) as AnonymousPatronCount
Ended is null
)
) x
')[0]->AnonymousPatronCount;
');
?><?= Template::Header(['title' => 'About Standard Ebooks', 'highlight' => 'about', 'description' => 'Standard Ebooks is a volunteer-driven effort to produce a collection of high quality, carefully formatted, accessible, open source, and free public domain ebooks that meet or exceed the quality of commercially produced ebooks. The text and cover art in our ebooks is already believed to be in the public domain, and Standard Ebook dedicates its own work to the public domain, thus releasing the entirety of each ebook file into the public domain.']) ?>
<main>