mirror of
https://github.com/standardebooks/web.git
synced 2025-07-21 14:55:13 -04:00
Allow adjustment of Patrons Circle cost
This commit is contained in:
parent
a4d1e9d724
commit
9a2b095b70
8 changed files with 153 additions and 123 deletions
|
@ -3,6 +3,8 @@ CREATE TABLE IF NOT EXISTS `Patrons` (
|
||||||
`IsAnonymous` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
`IsAnonymous` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||||
`AlternateName` varchar(80) DEFAULT NULL,
|
`AlternateName` varchar(80) DEFAULT NULL,
|
||||||
`IsSubscribedToEmails` tinyint(1) NOT NULL DEFAULT 1,
|
`IsSubscribedToEmails` tinyint(1) NOT NULL DEFAULT 1,
|
||||||
|
`BaseCost` DECIMAL(5,2) UNSIGNED NULL DEFAULT,
|
||||||
|
`CycleType` enum('monthly','yearly','unlimited') NULL DEFAULT NULL,
|
||||||
`Created` datetime NOT NULL,
|
`Created` datetime NOT NULL,
|
||||||
`Ended` datetime DEFAULT NULL,
|
`Ended` datetime DEFAULT NULL,
|
||||||
KEY `index2` (`IsAnonymous`,`Ended`),
|
KEY `index2` (`IsAnonymous`,`Ended`),
|
||||||
|
|
|
@ -50,6 +50,9 @@ const ARTWORK_IMAGE_MINIMUM_HEIGHT = 300;
|
||||||
const CAPTCHA_IMAGE_HEIGHT = 72;
|
const CAPTCHA_IMAGE_HEIGHT = 72;
|
||||||
const CAPTCHA_IMAGE_WIDTH = 230;
|
const CAPTCHA_IMAGE_WIDTH = 230;
|
||||||
|
|
||||||
|
const PATRONS_CIRCLE_MONTHLY_COST = 15;
|
||||||
|
const PATRONS_CIRCLE_YEARLY_COST = 150;
|
||||||
|
|
||||||
// These are defined for convenience, so that getting HTTP input isn't so wordy.
|
// These are defined for convenience, so that getting HTTP input isn't so wordy.
|
||||||
const GET = Enums\HttpVariableSource::Get;
|
const GET = Enums\HttpVariableSource::Get;
|
||||||
const POST = Enums\HttpVariableSource::Post;
|
const POST = Enums\HttpVariableSource::Post;
|
||||||
|
|
8
lib/Enums/CycleType.php
Normal file
8
lib/Enums/CycleType.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?
|
||||||
|
namespace Enums;
|
||||||
|
|
||||||
|
enum CycleType: string{
|
||||||
|
case Monthly = 'monthly';
|
||||||
|
case Unlimited = 'unlimited';
|
||||||
|
case Yearly = 'yearly';
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ use Safe\DateTimeImmutable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property User $User
|
* @property User $User
|
||||||
|
* @property ?Payment $LastPayment
|
||||||
*/
|
*/
|
||||||
class Patron{
|
class Patron{
|
||||||
use Traits\Accessor;
|
use Traits\Accessor;
|
||||||
|
@ -13,10 +14,32 @@ class Patron{
|
||||||
public bool $IsSubscribedToEmails;
|
public bool $IsSubscribedToEmails;
|
||||||
public DateTimeImmutable $Created;
|
public DateTimeImmutable $Created;
|
||||||
public ?DateTimeImmutable $Ended = null;
|
public ?DateTimeImmutable $Ended = null;
|
||||||
|
public ?float $BaseCost = null;
|
||||||
|
public ?Enums\CycleType $CycleType = null;
|
||||||
|
|
||||||
|
protected ?Payment $_LastPayment = null;
|
||||||
protected User $_User;
|
protected User $_User;
|
||||||
|
|
||||||
|
|
||||||
|
// *******
|
||||||
|
// GETTERS
|
||||||
|
// *******
|
||||||
|
|
||||||
|
protected function GetLastPayment(): ?Payment{
|
||||||
|
if(!isset($this->_LastPayment)){
|
||||||
|
$this->_LastPayment = Db::Query('
|
||||||
|
SELECT *
|
||||||
|
from Payments
|
||||||
|
where UserId = ?
|
||||||
|
order by Created desc
|
||||||
|
limit 1
|
||||||
|
', [$this->UserId], Payment::class)[0] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_LastPayment;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// *******
|
// *******
|
||||||
// METHODS
|
// METHODS
|
||||||
// *******
|
// *******
|
||||||
|
@ -24,13 +47,14 @@ class Patron{
|
||||||
public function Create(): void{
|
public function Create(): void{
|
||||||
$this->Created = NOW;
|
$this->Created = NOW;
|
||||||
Db::Query('
|
Db::Query('
|
||||||
INSERT into Patrons (Created, UserId, IsAnonymous, AlternateName, IsSubscribedToEmails)
|
INSERT into Patrons (Created, UserId, IsAnonymous, AlternateName, IsSubscribedToEmails, BaseCost, CycleType)
|
||||||
values(?,
|
values(?,
|
||||||
?,
|
?,
|
||||||
?,
|
?,
|
||||||
?,
|
?,
|
||||||
|
?,
|
||||||
?)
|
?)
|
||||||
', [$this->Created, $this->UserId, $this->IsAnonymous, $this->AlternateName, $this->IsSubscribedToEmails]);
|
', [$this->Created, $this->UserId, $this->IsAnonymous, $this->AlternateName, $this->IsSubscribedToEmails, $this->BaseCost, $this->CycleType]);
|
||||||
|
|
||||||
Db::Query('
|
Db::Query('
|
||||||
INSERT into Benefits (UserId, CanVote, CanAccessFeeds, CanBulkDownload)
|
INSERT into Benefits (UserId, CanVote, CanAccessFeeds, CanBulkDownload)
|
||||||
|
@ -76,6 +100,49 @@ class Patron{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function End(?int $ebooksThisYear): void{
|
||||||
|
if($ebooksThisYear === null){
|
||||||
|
$ebooksThisYear = Db::QueryInt('SELECT count(*) from Ebooks where EbookCreated >= ? - interval 1 year', [NOW]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Db::Query('
|
||||||
|
UPDATE Patrons
|
||||||
|
set Ended = ?
|
||||||
|
where UserId = ?
|
||||||
|
', [NOW, $this->UserId]);
|
||||||
|
|
||||||
|
Db::Query('
|
||||||
|
UPDATE Benefits
|
||||||
|
set CanAccessFeeds = false,
|
||||||
|
CanVote = false,
|
||||||
|
CanBulkDownload = false
|
||||||
|
where UserId = ?
|
||||||
|
', [$this->UserId]);
|
||||||
|
|
||||||
|
// Email the patron to notify them their term has ended.
|
||||||
|
if($this->LastPayment !== null && $this->User->Email !== null){
|
||||||
|
$em = new Email();
|
||||||
|
$em->From = EDITOR_IN_CHIEF_EMAIL_ADDRESS;
|
||||||
|
$em->FromName = EDITOR_IN_CHIEF_NAME;
|
||||||
|
$em->To = $this->User->Email;
|
||||||
|
$em->ToName = $this->User->Name ?? '';
|
||||||
|
$em->Subject = 'Will you continue to help us make free, beautiful digital literature?';
|
||||||
|
|
||||||
|
if($this->CycleType == Enums\CycleType::Monthly){
|
||||||
|
// Email recurring donors who have lapsed.
|
||||||
|
$em->Body = Template::EmailPatronsCircleRecurringCompleted();
|
||||||
|
$em->TextBody = Template::EmailPatronsCircleRecurringCompletedText();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// Email one time donors who have expired after one year.
|
||||||
|
$em->Body = Template::EmailPatronsCircleCompleted(['ebooksThisYear' => $ebooksThisYear]);
|
||||||
|
$em->TextBody = Template::EmailPatronsCircleCompletedText(['ebooksThisYear' => $ebooksThisYear]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$em->Send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ***********
|
// ***********
|
||||||
// ORM METHODS
|
// ORM METHODS
|
||||||
|
@ -93,6 +160,8 @@ class Patron{
|
||||||
SELECT *
|
SELECT *
|
||||||
from Patrons
|
from Patrons
|
||||||
where UserId = ?
|
where UserId = ?
|
||||||
|
order by Created desc
|
||||||
|
limit 1
|
||||||
', [$userId], Patron::class);
|
', [$userId], Patron::class);
|
||||||
|
|
||||||
return $result[0] ?? throw new Exceptions\PatronNotFoundException();;
|
return $result[0] ?? throw new Exceptions\PatronNotFoundException();;
|
||||||
|
@ -111,6 +180,8 @@ class Patron{
|
||||||
from Patrons p
|
from Patrons p
|
||||||
inner join Users u using(UserId)
|
inner join Users u using(UserId)
|
||||||
where u.Email = ?
|
where u.Email = ?
|
||||||
|
order by p.Created desc
|
||||||
|
limit 1
|
||||||
', [$email], Patron::class);
|
', [$email], Patron::class);
|
||||||
|
|
||||||
return $result[0] ?? throw new Exceptions\PatronNotFoundException();
|
return $result[0] ?? throw new Exceptions\PatronNotFoundException();
|
||||||
|
|
|
@ -37,12 +37,12 @@ $faUsername = get_cfg_var('se.secrets.fractured_atlas.username');
|
||||||
$faPassword = get_cfg_var('se.secrets.fractured_atlas.password');
|
$faPassword = get_cfg_var('se.secrets.fractured_atlas.password');
|
||||||
|
|
||||||
// Test donations
|
// Test donations
|
||||||
// fa000cbf-af6f-4c14-8919-da6cf81a27ea Regular donation, patrons, public, recurring
|
// `fa000cbf-af6f-4c14-8919-da6cf81a27ea` Regular donation, patrons, public, recurring.
|
||||||
// a010dcaf-d2ab-49da-878c-cb447b12152e Regular donation, non-patrons, private, one time
|
// `a010dcaf-d2ab-49da-878c-cb447b12152e` Regular donation, non-patrons, private, one time.
|
||||||
// 5a544447-708d-43da-a7b8-7bd8d9804652 AOGF donation, patrons, public, one time
|
// `5a544447-708d-43da-a7b8-7bd8d9804652` AOGF donation, patrons, public, one time.
|
||||||
// e097c777-e2d8-4b21-b99c-e83da8696af8 AOGF donation, non-patrons, anonymous, one time
|
// `e097c777-e2d8-4b21-b99c-e83da8696af8` AOGF donation, non-patrons, anonymous, one time.
|
||||||
// 946554ca-ffc0-4259-bcc6-be6c844fbbdc Regular donation, patrons, private, recurring
|
// `946554ca-ffc0-4259-bcc6-be6c844fbbdc` Regular donation, patrons, private, recurring.
|
||||||
// 416608c6-cbf5-4153-8956-cb9051bb849e Regular donation, patrons, public, one time, in memory of
|
// `416608c6-cbf5-4153-8956-cb9051bb849e` Regular donation, patrons, public, one time, in memory of.
|
||||||
|
|
||||||
Db::Query('start transaction');
|
Db::Query('start transaction');
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ try{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until the page finishes loading.
|
// Wait until the page finishes loading.
|
||||||
// We have to expand the row before we can select its contents, so click the 'expand' button once it's visible.
|
// We have to expand the row before we can select its contents, so click the "expand" button once it's visible.
|
||||||
try{
|
try{
|
||||||
/** @var WebDriverElement $toggleButton */
|
/** @var WebDriverElement $toggleButton */
|
||||||
$toggleButton = $driver->wait(20, 250)->until(WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::xpath('//button[contains(@class, "button-toggle")]')));
|
$toggleButton = $driver->wait(20, 250)->until(WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::xpath('//button[contains(@class, "button-toggle")]')));
|
||||||
|
@ -132,7 +132,7 @@ try{
|
||||||
$payment->Processor = $pendingPayment->Processor;
|
$payment->Processor = $pendingPayment->Processor;
|
||||||
$hasSoftCredit = false;
|
$hasSoftCredit = false;
|
||||||
try{
|
try{
|
||||||
// If the donation is via a foundation (like American Online Giving Foundation) then there will be a 'soft credit' <th> element.
|
// If the donation is via a foundation (like American Online Giving Foundation) then there will be a "soft credit" `<th>` element.
|
||||||
if(sizeof($detailsRow->findElements(WebDriverBy::xpath('//th[normalize-space(.) = "Soft Credit Donor Info"]'))) > 0){
|
if(sizeof($detailsRow->findElements(WebDriverBy::xpath('//th[normalize-space(.) = "Soft Credit Donor Info"]'))) > 0){
|
||||||
// We're a foundation donation
|
// We're a foundation donation
|
||||||
$payment->User->Name = trim($detailsRow->findElement(WebDriverBy::xpath('//td[preceding-sibling::th[normalize-space(.) = "Name"] and (ancestor::tbody[1])[(./preceding-sibling::thead[1])//th[normalize-space(.) = "Soft Credit Donor Info"]]]'))->getText());
|
$payment->User->Name = trim($detailsRow->findElement(WebDriverBy::xpath('//td[preceding-sibling::th[normalize-space(.) = "Name"] and (ancestor::tbody[1])[(./preceding-sibling::thead[1])//th[normalize-space(.) = "Soft Credit Donor Info"]]]'))->getText());
|
||||||
|
@ -147,7 +147,7 @@ try{
|
||||||
|
|
||||||
// These donations are typically (always?) employer matches.
|
// These donations are typically (always?) employer matches.
|
||||||
// FA does not provide a way to connect the original donation with the employer match.
|
// FA does not provide a way to connect the original donation with the employer match.
|
||||||
// Example bbf87b83-d341-426f-b6c9-9091e3222e57
|
// See donation `bbf87b83-d341-426f-b6c9-9091e3222e57`.
|
||||||
if($payment->User->Name == 'American Online Giving Foundation'){
|
if($payment->User->Name == 'American Online Giving Foundation'){
|
||||||
$payment->IsMatchingDonation = true;
|
$payment->IsMatchingDonation = true;
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,7 @@ try{
|
||||||
$payment->TransactionId = trim($transactionId);
|
$payment->TransactionId = trim($transactionId);
|
||||||
|
|
||||||
// We might also get a case where the donation is on behalf of a company match, but there's not really a way to distinguish that. Do a rough check.
|
// We might also get a case where the donation is on behalf of a company match, but there's not really a way to distinguish that. Do a rough check.
|
||||||
// See donation 00b60a22-eafa-44cb-9850-54bef9763e8d
|
// See donation `00b60a22-eafa-44cb-9850-54bef9763e8d`.
|
||||||
if($payment->User !== null && !$hasSoftCredit && preg_match('/\b(L\.?L\.?C\.?|Foundation|President|Fund|Charitable)\b/ius', $payment->User->Name ?? '')){
|
if($payment->User !== null && !$hasSoftCredit && preg_match('/\b(L\.?L\.?C\.?|Foundation|President|Fund|Charitable)\b/ius', $payment->User->Name ?? '')){
|
||||||
$payment->User = null;
|
$payment->User = null;
|
||||||
}
|
}
|
||||||
|
@ -194,7 +194,7 @@ try{
|
||||||
(
|
(
|
||||||
$payment->IsRecurring
|
$payment->IsRecurring
|
||||||
&&
|
&&
|
||||||
$payment->Amount >= 10
|
$payment->Amount >= PATRONS_CIRCLE_MONTHLY_COST
|
||||||
&&
|
&&
|
||||||
$payment->Created >= $lastMonth
|
$payment->Created >= $lastMonth
|
||||||
)
|
)
|
||||||
|
@ -202,22 +202,17 @@ try{
|
||||||
(
|
(
|
||||||
!$payment->IsRecurring
|
!$payment->IsRecurring
|
||||||
&&
|
&&
|
||||||
$payment->Amount >= 100
|
$payment->Amount >= PATRONS_CIRCLE_YEARLY_COST
|
||||||
&&
|
&&
|
||||||
$payment->Created >= $lastYear
|
$payment->Created >= $lastYear
|
||||||
)
|
)
|
||||||
){
|
){
|
||||||
// This payment is eligible for the Patrons Circle!
|
// This payment is eligible for the Patrons Circle!
|
||||||
if($payment->UserId !== null && $payment->User !== null){
|
if($payment->UserId !== null && $payment->User !== null){
|
||||||
|
$patron = Db::Query('SELECT * from Patrons where UserId = ? and Ended is null', [$payment->UserId], Patron::class)[0] ?? null;
|
||||||
|
|
||||||
// Are we already a patron?
|
// Are we already a patron?
|
||||||
if(!Db::QueryBool('
|
if($patron === null){
|
||||||
SELECT exists(
|
|
||||||
select *
|
|
||||||
from Patrons
|
|
||||||
where UserId = ?
|
|
||||||
and Ended is null
|
|
||||||
)
|
|
||||||
', [$payment->UserId])){
|
|
||||||
// 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();
|
||||||
|
@ -231,25 +226,39 @@ try{
|
||||||
$patron->AlternateName = trim($detailsRow->findElement(WebDriverBy::xpath('//td[preceding-sibling::th[normalize-space(.) = "Attribution Text"]]'))->getText());
|
$patron->AlternateName = trim($detailsRow->findElement(WebDriverBy::xpath('//td[preceding-sibling::th[normalize-space(.) = "Attribution Text"]]'))->getText());
|
||||||
}
|
}
|
||||||
catch(Exception){
|
catch(Exception){
|
||||||
|
// Pass.
|
||||||
|
}
|
||||||
|
|
||||||
|
if($payment->IsRecurring){
|
||||||
|
$patron->BaseCost = PATRONS_CIRCLE_MONTHLY_COST;
|
||||||
|
$patron->CycleType == Enums\CycleType::Monthly;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$patron->BaseCost = PATRONS_CIRCLE_YEARLY_COST;
|
||||||
|
$patron->CycleType == Enums\CycleType::Yearly;
|
||||||
}
|
}
|
||||||
|
|
||||||
$log->Write('Adding donor as patron ...');
|
$log->Write('Adding donor as patron ...');
|
||||||
$patron->Create();
|
$patron->Create();
|
||||||
}
|
}
|
||||||
elseif(!$payment->IsRecurring && !$payment->IsMatchingDonation){
|
else{
|
||||||
// User is already a patron, but they made another non-recurring, non-matching donation.
|
// User is already a patron.
|
||||||
// Send a thank-you email.
|
// We may get a case where an existing Patron makes another donation that
|
||||||
|
if(!$payment->IsRecurring && !$payment->IsMatchingDonation){
|
||||||
|
// User is already a Patron, but they made another non-recurring, non-matching donation.
|
||||||
|
// Send a thank-you email.
|
||||||
|
|
||||||
$log->Write('Sending thank you email to patron donor donating extra.');
|
$log->Write('Sending thank you email to patron donor donating extra.');
|
||||||
$em = new Email();
|
$em = new Email();
|
||||||
$em->To = $payment->User->Email ?? '';
|
$em->To = $payment->User->Email ?? '';
|
||||||
$em->ToName = $payment->User->Name ?? '';
|
$em->ToName = $payment->User->Name ?? '';
|
||||||
$em->From = EDITOR_IN_CHIEF_EMAIL_ADDRESS;
|
$em->From = EDITOR_IN_CHIEF_EMAIL_ADDRESS;
|
||||||
$em->FromName = EDITOR_IN_CHIEF_NAME;
|
$em->FromName = EDITOR_IN_CHIEF_NAME;
|
||||||
$em->Subject = 'Thank you for supporting Standard Ebooks!';
|
$em->Subject = 'Thank you for supporting Standard Ebooks!';
|
||||||
$em->Body = Template::EmailDonationThankYou();
|
$em->Body = Template::EmailDonationThankYou();
|
||||||
$em->TextBody = Template::EmailDonationThankYouText();
|
$em->TextBody = Template::EmailDonationThankYouText();
|
||||||
$em->Send();
|
$em->Send();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elseif(!$payment->IsRecurring && !$payment->IsMatchingDonation){
|
elseif(!$payment->IsRecurring && !$payment->IsMatchingDonation){
|
||||||
|
|
|
@ -1,98 +1,35 @@
|
||||||
#!/usr/bin/php
|
#!/usr/bin/php
|
||||||
<?
|
<?
|
||||||
|
/**
|
||||||
|
* Get a list of payments that are within 1 year / 45 days of today, and deactivate Patrons Circle members who aren't in that list.
|
||||||
|
* We give a 15 day grace period to Patrons Circle members because sometimes FA can be delayed in charging.
|
||||||
|
*/
|
||||||
|
|
||||||
require_once('/standardebooks.org/web/lib/Core.php');
|
require_once('/standardebooks.org/web/lib/Core.php');
|
||||||
|
|
||||||
use Safe\DateTimeImmutable;
|
|
||||||
|
|
||||||
use function Safe\file_get_contents;
|
|
||||||
use function Safe\preg_match_all;
|
|
||||||
use function Safe\shell_exec;
|
|
||||||
|
|
||||||
// Get a list of payments that are within 1 year / 45 days of today, and deactivate Patrons Circle members who aren't in that list.
|
|
||||||
// We give a 15 day grace period to Patrons Circle members because sometimes FA can be delayed in charging.
|
|
||||||
|
|
||||||
$lastYear = new DateTimeImmutable('-1 year');
|
|
||||||
|
|
||||||
$expiredPatrons = Db::Query('
|
$expiredPatrons = Db::Query('
|
||||||
SELECT * from Patrons
|
SELECT * from Patrons where
|
||||||
where
|
Ended is null
|
||||||
Ended is null and
|
and
|
||||||
UserId not in
|
UserId not in
|
||||||
(
|
(
|
||||||
select distinct UserId from Payments where
|
select distinct p.UserId from Patrons p
|
||||||
UserId is not null
|
inner join Payments py
|
||||||
and
|
using (UserId)
|
||||||
(
|
where
|
||||||
(IsRecurring = true and Amount >= 10 and Created > ? - interval 45 day)
|
p.Ended is null and
|
||||||
or
|
(
|
||||||
(IsRecurring = false and Amount >= 100 and Created > ? - interval 1 year)
|
(IsRecurring = true and CycleType = ? and Amount >= p.BaseCost and py.Created > ? - interval 45 day)
|
||||||
)
|
or
|
||||||
|
(IsRecurring = false and CycleType = ? and Amount >= p.BaseCost and py.Created > ? - interval 1 year)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
', [NOW, NOW], Patron::class);
|
', [Enums\CycleType::Monthly, NOW, Enums\CycleType::Yearly, NOW], Patron::class);
|
||||||
|
|
||||||
if(sizeof($expiredPatrons) > 0){
|
if(sizeof($expiredPatrons) > 0){
|
||||||
$ebooksThisYear = 0;
|
$ebooksThisYear = Db::QueryInt('SELECT count(*) from Ebooks where EbookCreated >= ? - interval 1 year', [NOW]);
|
||||||
|
|
||||||
// We can't use the Library class to get ebooks because this script is typically run via cron or CLI, which doesn't have access PHP-FMP's APCu cache.
|
|
||||||
foreach(explode("\n", trim(shell_exec('find ' . EBOOKS_DIST_PATH . ' -name "content.opf"'))) as $filename){
|
|
||||||
$metadata = file_get_contents($filename);
|
|
||||||
|
|
||||||
// Don't create a new Ebook object because that's very slow. Just do a regex match for speed.
|
|
||||||
preg_match_all('/<dc:date>(.+?)<\/dc:date>/iu', $metadata, $matches);
|
|
||||||
|
|
||||||
if(sizeof($matches) > 0){
|
|
||||||
$created = new DateTimeImmutable($matches[1][0]);
|
|
||||||
if($created >= $lastYear){
|
|
||||||
$ebooksThisYear++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach($expiredPatrons as $patron){
|
foreach($expiredPatrons as $patron){
|
||||||
Db::Query('
|
$patron->End($ebooksThisYear);
|
||||||
UPDATE Patrons
|
|
||||||
set Ended = ?
|
|
||||||
where UserId = ?
|
|
||||||
', [NOW, $patron->UserId]);
|
|
||||||
|
|
||||||
Db::Query('
|
|
||||||
UPDATE Benefits
|
|
||||||
set CanAccessFeeds = false,
|
|
||||||
CanVote = false,
|
|
||||||
CanBulkDownload = false
|
|
||||||
where UserId = ?
|
|
||||||
', [$patron->UserId]);
|
|
||||||
|
|
||||||
// Email the patron to notify them their term has ended.
|
|
||||||
// Is the patron a recurring subscriber?
|
|
||||||
$lastPayment = Db::Query('
|
|
||||||
SELECT *
|
|
||||||
from Payments
|
|
||||||
where UserId = ?
|
|
||||||
order by Created desc
|
|
||||||
limit 1
|
|
||||||
', [$patron->UserId], Payment::class);
|
|
||||||
|
|
||||||
if(sizeof($lastPayment) > 0 && $patron->User->Email !== null){
|
|
||||||
$em = new Email();
|
|
||||||
$em->From = EDITOR_IN_CHIEF_EMAIL_ADDRESS;
|
|
||||||
$em->FromName = EDITOR_IN_CHIEF_NAME;
|
|
||||||
$em->To = $patron->User->Email;
|
|
||||||
$em->ToName = $patron->User->Name ?? '';
|
|
||||||
$em->Subject = 'Will you still help us make free, beautiful digital literature?';
|
|
||||||
|
|
||||||
if($lastPayment[0]->IsRecurring){
|
|
||||||
// Email recurring donors who have lapsed.
|
|
||||||
$em->Body = Template::EmailPatronsCircleRecurringCompleted();
|
|
||||||
$em->TextBody = Template::EmailPatronsCircleRecurringCompletedText();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
// Email one time donors who have expired after one year.
|
|
||||||
$em->Body = Template::EmailPatronsCircleCompleted(['ebooksThisYear' => $ebooksThisYear]);
|
|
||||||
$em->TextBody = Template::EmailPatronsCircleCompletedText(['ebooksThisYear' => $ebooksThisYear]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$em->Send();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ $digits = str_split(str_pad((string)$current, 3, "0", STR_PAD_LEFT))
|
||||||
</div>
|
</div>
|
||||||
<p>Our fiscal sponsor, <a href="https://www.fracturedatlas.org">Fractured Atlas</a>, is celebrating the twenty-year anniversary of their fiscal sponsorship program by <a href="https://media.fracturedatlas.org/what-would-you-do-with-an-extra-1000">distributing $1,000 to twenty different projects</a>.</p>
|
<p>Our fiscal sponsor, <a href="https://www.fracturedatlas.org">Fractured Atlas</a>, is celebrating the twenty-year anniversary of their fiscal sponsorship program by <a href="https://media.fracturedatlas.org/what-would-you-do-with-an-extra-1000">distributing $1,000 to twenty different projects</a>.</p>
|
||||||
<p><strong>Each one-time donation of any amount to Standard Ebooks through <?= $deadline ?> gives us one entry in this $1,000 giveaway.</strong> The more donations we receive through <?= $deadline ?>, the more chances we have to win!</p>
|
<p><strong>Each one-time donation of any amount to Standard Ebooks through <?= $deadline ?> gives us one entry in this $1,000 giveaway.</strong> The more donations we receive through <?= $deadline ?>, the more chances we have to win!</p>
|
||||||
<p><strong>This is a great time to <a href="/donate#patrons-circle">join our Patrons Circle</a> with a one-time donation of $100.</strong> Not only will your donation support us directly, but it’ll give us one more entry in this big giveaway.</p>
|
<p><strong>This is a great time to <a href="/donate#patrons-circle">join our Patrons Circle</a> with a donation of $<?= number_format(PATRONS_CIRCLE_YEARLY_COST) ?>.</strong> Not only will your donation support us directly, but it’ll give us one more entry in this big giveaway.</p>
|
||||||
<p>Will you show your support for free, beautiful digital literature?</p>
|
<p>Will you show your support for free, beautiful digital literature?</p>
|
||||||
<? if($showDonateButton){ ?>
|
<? if($showDonateButton){ ?>
|
||||||
<p class="donate-button">
|
<p class="donate-button">
|
||||||
|
|
|
@ -34,10 +34,10 @@ $newsletterSubscriberCount = floor(Db::QueryInt('
|
||||||
<p>Membership in the Patrons Circle is limited to individuals only. Organizations, please see <a href="#corporate-sponsors">corporate sponsorship</a> instead.</p>
|
<p>Membership in the Patrons Circle is limited to individuals only. Organizations, please see <a href="#corporate-sponsors">corporate sponsorship</a> instead.</p>
|
||||||
<div class="join-patrons-circle-callout">
|
<div class="join-patrons-circle-callout">
|
||||||
<h3>Join now</h3>
|
<h3>Join now</h3>
|
||||||
<p><i>Join the Patrons Circle by starting a recurring donation of $10/month or more, or join for one year with a one-time donation of $100 or more.</i></p>
|
<p><i>Join the Patrons Circle by starting a recurring donation of $<?= number_format(PATRONS_CIRCLE_MONTHLY_COST) ?>/month or more, or join for one year with a one-time donation of $<?= number_format(PATRONS_CIRCLE_YEARLY_COST) ?> or more.</i></p>
|
||||||
<p class="button-row">
|
<p class="button-row">
|
||||||
<a href="https://fundraising.fracturedatlas.org/standard-ebooks/monthly_support" class="button">Donate $10/month or more</a>
|
<a href="https://fundraising.fracturedatlas.org/standard-ebooks/monthly_support" class="button">Donate $<?= number_format(PATRONS_CIRCLE_MONTHLY_COST) ?>/month or more</a>
|
||||||
<a href="https://fundraising.fracturedatlas.org/standard-ebooks/general_support" class="button">Donate $100 or more</a>
|
<a href="https://fundraising.fracturedatlas.org/standard-ebooks/general_support" class="button">Donate $<?= number_format(PATRONS_CIRCLE_YEARLY_COST) ?> or more</a>
|
||||||
</p>
|
</p>
|
||||||
<p><strong>Important:</strong> We need to know your email address to be able to log you in to the Patrons Circle. Make sure to select either “List my name publicly” or “Don’t list publicly, but reveal to project” during checkout to be able to log in to the Patrons Circle.</p>
|
<p><strong>Important:</strong> We need to know your email address to be able to log you in to the Patrons Circle. Make sure to select either “List my name publicly” or “Don’t list publicly, but reveal to project” during checkout to be able to log in to the Patrons Circle.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue