Add automatic donation drives

This commit is contained in:
Alex Cabal 2024-10-17 11:55:03 -05:00
parent d8ed44e20e
commit 19cf14c1aa
10 changed files with 396 additions and 296 deletions

View file

@ -15,10 +15,8 @@ parameters:
- %rootDir%/../../../scripts
dynamicConstantNames:
- SITE_STATUS
- DONATION_HOLIDAY_ALERT_ON
- DONATION_ALERT_ON
- DONATION_DRIVE_ON
- DONATION_DRIVE_COUNTER_ON
- DONATION_DRIVE_ENABLED
- DONATION_DRIVE_COUNTER_ENABLED
earlyTerminatingMethodCalls:
Template:
- Emit403

View file

@ -74,15 +74,26 @@ const ARTWORK_UPLOADS_LOG_FILE_PATH = '/var/log/local/artwork-uploads.log'; // M
define('PD_YEAR', intval((new DateTimeImmutable('now', new DateTimeZone('America/Juneau')))->format('Y')) - 96); // Latest continental US time zone.
define('PD_STRING', 'January 1, ' . (PD_YEAR + 1));
define('DONATION_HOLIDAY_ALERT_ON', NOW > new DateTimeImmutable('November 15, ' . NOW->format('Y')) || NOW < new DateTimeImmutable('January 7, ' . NOW->add(new DateInterval('P1Y'))->format('Y')));
define('DONATION_ALERT_ON', DONATION_HOLIDAY_ALERT_ON || rand(1, 4) == 2);
// Controls the progress bar donation dialog.
const DONATION_DRIVES_ENABLED = true; // **`TRUE`** to enable automatic donation drives; **`FALSE`** to disable all donation drives.
const DONATION_DRIVE_DATES = [
new DonationDrive(
'Spring drive',
new DateTimeImmutable('Second Monday of May'),
new DateTimeImmutable('Second Monday of May +2 weeks'),
50,
20
),
new DonationDrive(
'Holiday drive',
NOW < new DateTimeImmutable('November 15') ? new DateTimeImmutable('November 15') : new DateTimeImmutable('November 15 -1 year'),
NOW < new DateTimeImmutable('January 7') ? new DateTimeImmutable('January 7') : new DateTimeImmutable('January 7 +1 year'),
50,
20
)
];
// Controls the progress bar donation dialog
const DONATION_DRIVE_ON = true;
const DONATION_DRIVE_START = new DateTimeImmutable('May 20, 2024 00:00:00 America/New_York');
const DONATION_DRIVE_END = new DateTimeImmutable('June 3, 2024 23:59:00 America/New_York');
// Controls the countdown donation dialog
const DONATION_DRIVE_COUNTER_ON = false;
// Controls the countdown donation dialog, basically unused right now.
const DONATION_DRIVE_COUNTER_ENABLED = false;
const DONATION_DRIVE_COUNTER_START = new DateTimeImmutable('May 2, 2022 00:00:00 America/New_York');
const DONATION_DRIVE_COUNTER_END = new DateTimeImmutable('May 8, 2022 23:59:00 America/New_York');

95
lib/DonationDrive.php Normal file
View file

@ -0,0 +1,95 @@
<?
use Safe\DateTimeImmutable;
/**
* @property int $DonationCount
* @property int $StretchDonationCount
* @property bool $IsStretchEnabled
* @property int $TargetDonationCount
*/
class DonationDrive{
use Traits\Accessor;
protected int $_DonationCount;
protected int $_StretchDonationCount;
protected bool $_IsStretchEnabled;
protected int $_TargetDonationCount;
public function __construct(public string $Name, public DateTimeImmutable $Start, public DateTimeImmutable $End, public int $BaseTargetDonationCount, public int $StretchTargetDonationCount){
}
public static function GetByIsRunning(): ?DonationDrive{
foreach(DONATION_DRIVE_DATES as $donationDrive){
if(NOW > $donationDrive->Start && NOW < $donationDrive->End){
return $donationDrive;
}
}
return null;
}
protected function GetDonationCount(): int{
if(!isset($this->_DonationCount)){
$this->_DonationCount = Db::QueryInt('
SELECT sum(cnt)
from
(
(
# Anonymous patrons, i.e. from AOGF
select count(*) cnt from Payments
where
UserId is null
and
(
(IsRecurring = true and Amount >= 10 and Created >= ?)
or
(IsRecurring = false and Amount >= 100 and Created >= ?)
)
)
union all
(
# All non-anonymous patrons
select count(*) as cnt from Patrons
where Created >= ?
)
) x
', [$this->Start, $this->Start, $this->Start]);
}
return $this->_DonationCount;
}
protected function GetTargetDonationCount(): int{
if(!isset($this->_TargetDonationCount)){
$this->_TargetDonationCount = $this->BaseTargetDonationCount;
if($this->DonationCount > $this->BaseTargetDonationCount){
$this->_TargetDonationCount = $this->_TargetDonationCount + $this->StretchTargetDonationCount;
}
}
return $this->_TargetDonationCount;
}
protected function GetStretchDonationCount(): int{
if(!isset($this->_StretchDonationCount)){
$this->_StretchDonationCount = $this->DonationCount - $this->BaseTargetDonationCount;
if($this->_StretchDonationCount < 0){
$this->_StretchDonationCount = 0;
}
}
return $this->_StretchDonationCount;
}
protected function GetIsStretchEnabled(): bool{
if(!isset($this->_IsStretchEnabled)){
$this->_IsStretchEnabled = false;
if($this->StretchTargetDonationCount > 0 && $this->DonationCount >= $this->BaseTargetDonationCount){
$this->_IsStretchEnabled = true;
}
}
return $this->_IsStretchEnabled;
}
}

View file

@ -1,5 +1,20 @@
<? if($GLOBALS['User'] === null){
// The Kindle browsers renders <aside> as an undismissable popup. Serve a <div> to Kindle instead. See https://github.com/standardebooks/web/issues/204
<?
// Hide this alert if...
$donationDrive = DonationDrive::GetByIsRunning();
if(
$GLOBALS['User'] !== null // If a user is logged in.
||
$donationDrive !== null // There is a currently-running donation drive.
||
rand(1, 5) <= 3 // A 3-in-5 chance occurs.
){
return;
}
if($GLOBALS['User'] === null){
// The Kindle browsers renders `<aside>` as an undismissable popup. Serve a `<div>` to Kindle instead.
// See <https://github.com/standardebooks/web/issues/204>.
$element = 'aside';
if(stripos($_SERVER['HTTP_USER_AGENT'] ?? '', 'kindle') !== false){

View file

@ -1,6 +1,6 @@
<?
// Hide the alert if the user has closed it.
if(!DONATION_DRIVE_COUNTER_ON || ($autoHide ?? $_COOKIE['hide-donation-alert'] ?? false) || NOW > DONATION_DRIVE_COUNTER_END){
if(!DONATION_DRIVE_COUNTER_ENABLED || ($autoHide ?? $_COOKIE['hide-donation-alert'] ?? false) || NOW > DONATION_DRIVE_COUNTER_END){
return;
}

View file

@ -1,54 +1,23 @@
<?
$totalCurrent = 0;
$baseTarget = 50;
$stretchCurrent = 0;
$stretchTarget = 20;
$donationDrive = DonationDrive::GetByIsRunning();
// Hide the alert if...
if(
!DONATION_DRIVE_ON // The drive isn't running
!DONATION_DRIVES_ENABLED // Drives aren't enabled.
||
($autoHide ?? $_COOKIE['hide-donation-alert'] ?? false) // If the user has hidden the box
($autoHide ?? $_COOKIE['hide-donation-alert'] ?? false) // If the user has hidden the box.
||
$GLOBALS['User'] !== null // If a user is logged in
$GLOBALS['User'] !== null // If a user is logged in.
||
DONATION_DRIVE_START > NOW // If the drive hasn't started yet
||
NOW > DONATION_DRIVE_END // If the drive has ended
$donationDrive === null // There is no donation drive running right now.
){
return;
}
$autoHide = $autoHide ?? true;
$showDonateButton = $showDonateButton ?? true;
$totalCurrent = Db::QueryInt('
SELECT sum(cnt)
from
(
(
# Anonymous patrons, i.e. from AOGF
select count(*) cnt from Payments
where
UserId is null
and
(
(IsRecurring = true and Amount >= 10 and Created >= ?)
or
(IsRecurring = false and Amount >= 100 and Created >= ?)
)
)
union all
(
# All non-anonymous patrons
select count(*) as cnt from Patrons
where Created >= ?
)
) x
', [DONATION_DRIVE_START, DONATION_DRIVE_START, DONATION_DRIVE_START]);
$totalTarget = $baseTarget;
$deadline = DONATION_DRIVE_END->format('F j');
$timeLeft = NOW->diff(DONATION_DRIVE_END);
$deadline = $donationDrive->End->format('F j');
$timeLeft = NOW->diff($donationDrive->End);
$timeString = '';
if($timeLeft->days < 1 && $timeLeft->h < 20){
$timeString = 'Just hours';
@ -71,13 +40,6 @@ else{
$timeString = 'Only ' . $timeString;
}
}
$stretchOn = false;
if($stretchTarget > 0 && $totalCurrent >= $baseTarget){
$stretchOn = true;
$stretchCurrent = $totalCurrent - $baseTarget;
$totalTarget = $baseTarget + $stretchTarget;
}
?>
<aside class="donation closable">
<? if($autoHide){ ?>
@ -86,38 +48,38 @@ if($stretchTarget > 0 && $totalCurrent >= $baseTarget){
<button class="close" title="Close this box">Close this box</button>
</form>
<? } ?>
<? if(!$stretchOn){ ?>
<? if(!$donationDrive->IsStretchEnabled){ ?>
<header>
<? if($timeLeft->days > 5){ ?>
<p>Help us reach <?= number_format($baseTarget) ?> new patrons by <?= $deadline ?></p>
<p>Help us reach <?= number_format($donationDrive->TargetDonationCount) ?> new patrons by <?= $deadline ?></p>
<? }else{ ?>
<p><?= $timeString ?> left to help us reach <?= number_format($baseTarget) ?> new patrons!</p>
<p><?= $timeString ?> left to help us reach <?= number_format($donationDrive->TargetDonationCount) ?> new patrons!</p>
<? } ?>
</header>
<? }else{ ?>
<header>
<p>Help us meet our stretch goal of<br/> <?= number_format($totalTarget) ?> new patrons by <?= $deadline ?></p>
<p>Help us meet our stretch goal of<br/> <?= number_format($donationDrive->TargetDonationCount) ?> new patrons by <?= $deadline ?></p>
</header>
<? } ?>
<div class="progress">
<div aria-hidden="true">
<p class="start">0</p>
<p><?= number_format($totalCurrent) ?>/<?= number_format($totalTarget) ?></p>
<? if($stretchOn){ ?>
<p class="stretch-base"><?= number_format($baseTarget) ?></p>
<p><?= number_format($donationDrive->DonationCount) ?>/<?= number_format($donationDrive->TargetDonationCount) ?></p>
<? if($donationDrive->IsStretchEnabled){ ?>
<p class="stretch-base"><?= number_format($donationDrive->BaseTargetDonationCount) ?></p>
<? } ?>
<p class="target"><?= number_format($totalTarget) ?></p>
<p class="target"><?= number_format($donationDrive->TargetDonationCount) ?></p>
</div>
<progress max="<?= $baseTarget ?>" value="<?= $totalCurrent - $stretchCurrent ?>"></progress>
<? if($stretchOn){ ?>
<progress class="stretch" max="<?= $stretchTarget ?>" value="<?= $stretchCurrent ?>"></progress>
<progress max="<?= $donationDrive->TargetDonationCount ?>" value="<?= $donationDrive->DonationCount - $donationDrive->StretchDonationCount ?>"></progress>
<? if($donationDrive->IsStretchEnabled){ ?>
<progress class="stretch" max="<?= $donationDrive->StretchTargetDonationCount ?>" value="<?= $donationDrive->StretchDonationCount ?>"></progress>
<? } ?>
</div>
<? if($stretchOn){ ?>
<p>When we started this drive, we set a goal of <?= number_format($baseTarget) ?> Patrons Circle members by <?= $deadline ?>. Thanks to the incredible generosity of literature lovers like you, we hit that goal!</p>
<p>Since theres still some time left in our drive, we thought wed challenge our readers to help us reach our stretch goal of <?= number_format($totalTarget) ?> patrons, so that we can continue on a rock-solid financial footing. Will you help us with a donation, and support free and unrestricted digital literature?</p>
<? if($donationDrive->IsStretchEnabled){ ?>
<p>When we started this drive, we set a goal of <?= number_format($donationDrive->BaseTargetDonationCount) ?> Patrons Circle members by <?= $deadline ?>. Thanks to the incredible generosity of literature lovers like you, we hit that goal!</p>
<p>Since theres still some time left in our drive, we thought wed challenge our readers to help us reach our stretch goal of <?= number_format($donationDrive->TargetDonationCount) ?> patrons, so that we can continue on a rock-solid financial footing. Will you help us with a donation, and support free and unrestricted digital literature?</p>
<? }else{ ?>
<p>It takes a huge amount of resources and highly-skilled work to create and distribute each of our free ebooks, and we need your support to keep it up. Thats why we want to welcome <?= number_format($baseTarget) ?> new patrons by <?= $deadline ?>. Its our patrons who keep us on the stable financial footing we need to continue producing and giving away beautiful ebooks.</p>
<p>It takes a huge amount of resources and highly-skilled work to create and distribute each of our free ebooks, and we need your support to keep it up. Thats why we want to welcome <?= number_format($donationDrive->TargetDonationCount) ?> new patrons by <?= $deadline ?>. Its our patrons who keep us on the stable financial footing we need to continue producing and giving away beautiful ebooks.</p>
<p>Will you become a patron, and support free and unrestricted digital literature?</p>
<? } ?>
<? if($showDonateButton){ ?>

View file

@ -39,9 +39,9 @@ catch(Exceptions\CollectionNotFoundException){
<h1 class="is-collection"><?= $pageHeader ?></h1>
<?= Template::DonationCounter() ?>
<?= Template::DonationProgress() ?>
<? if(!DONATION_DRIVE_ON && !DONATION_DRIVE_COUNTER_ON && DONATION_HOLIDAY_ALERT_ON){ ?>
<?= Template::DonationAlert() ?>
<? } ?>
<p class="ebooks-toolbar">
<a class="button" href="/collections/<?= Formatter::EscapeHtml($collection) ?>/downloads">Download collection</a>
<a class="button" href="/collections/<?= Formatter::EscapeHtml($collection) ?>/feeds">Collection feeds</a>

View file

@ -2314,7 +2314,7 @@ h1 + ul.message,
.masthead ol li p{
margin-top: 0;
text-align: left;
display: inline-block; /* Prevent a p from breaking across column division */
display: inline-block; /* Prevent a `<p>` from breaking across column division */
}
.masthead hgroup * + *{
@ -2326,7 +2326,6 @@ h1 + ul.message,
}
.donate aside{
border-top: 1px dashed var(--sub-text);
margin-top: 1rem;
padding-top: 1rem;
font-style: italic;

View file

@ -129,15 +129,21 @@ catch(Exceptions\EbookNotFoundException){
<h2><a property="schema:author" typeof="schema:Person" href="<?= Formatter::EscapeHtml($ebook->AuthorsUrl) ?>" resource="<?= '/ebooks/' . $author->UrlName ?>">
<span property="schema:name"><?= Formatter::EscapeHtml($author->Name) ?></span>
<meta property="schema:url" content="<?= SITE_URL . Formatter::EscapeHtml($ebook->AuthorsUrl) ?>"/>
<? if($author->NacoafUrl){ ?><meta property="schema:sameAs" content="<?= Formatter::EscapeHtml($author->NacoafUrl) ?>"/><? } ?>
<? if($author->WikipediaUrl){ ?><meta property="schema:sameAs" content="<?= Formatter::EscapeHtml($author->WikipediaUrl) ?>"/><? } ?>
<? if($author->NacoafUrl){ ?>
<meta property="schema:sameAs" content="<?= Formatter::EscapeHtml($author->NacoafUrl) ?>"/>
<? } ?>
<? if($author->WikipediaUrl){ ?>
<meta property="schema:sameAs" content="<?= Formatter::EscapeHtml($author->WikipediaUrl) ?>"/>
<? } ?>
</a>
</h2>
<? } ?>
<? } ?>
</hgroup>
<picture>
<? if($ebook->HeroImage2xAvifUrl !== null){ ?><source srcset="<?= $ebook->HeroImage2xAvifUrl ?> 2x, <?= $ebook->HeroImageAvifUrl ?> 1x" type="image/avif"/><? } ?>
<? if($ebook->HeroImage2xAvifUrl !== null){ ?>
<source srcset="<?= $ebook->HeroImage2xAvifUrl ?> 2x, <?= $ebook->HeroImageAvifUrl ?> 1x" type="image/avif"/>
<? } ?>
<source srcset="<?= $ebook->HeroImage2xUrl ?> 2x, <?= $ebook->HeroImageUrl ?> 1x" type="image/jpg"/>
<img src="<?= $ebook->HeroImage2xUrl ?>" alt="" height="439" width="1318" />
</picture>
@ -169,9 +175,9 @@ catch(Exceptions\EbookNotFoundException){
<h2>Description</h2>
<?= Template::DonationCounter() ?>
<?= Template::DonationProgress() ?>
<? if(!DONATION_DRIVE_ON && !DONATION_DRIVE_COUNTER_ON && DONATION_ALERT_ON){ ?>
<?= Template::DonationAlert() ?>
<? } ?>
<? if($ebook->LongDescription === null){ ?>
<p><i>Theres no description for this ebook yet.</i></p>
<? }else{ ?>
@ -340,11 +346,16 @@ catch(Exceptions\EbookNotFoundException){
<? foreach($transcriptionSources as $source){ ?>
<li>
<p>
<? if($source->Type == EbookSourceType::ProjectGutenberg){ ?><a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="project-gutenberg">Transcription at Project Gutenberg</a>
<? }elseif($source->Type == EbookSourceType::ProjectGutenbergAustralia){ ?><a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="project-gutenberg">Transcription at Project Gutenberg Australia</a>
<? }elseif($source->Type == EbookSourceType::ProjectGutenbergCanada){ ?><a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="project-gutenberg">Transcription at Project Gutenberg Canada</a>
<? }elseif($source->Type == EbookSourceType::Wikisource){ ?><a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="wikisource">Transcription at Wikisource</a>
<? }elseif($source->Type == EbookSourceType::FadedPage){ ?><a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="globe">Transcription at Faded Page</a>
<? if($source->Type == EbookSourceType::ProjectGutenberg){ ?>
<a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="project-gutenberg">Transcription at Project Gutenberg</a>
<? }elseif($source->Type == EbookSourceType::ProjectGutenbergAustralia){ ?>
<a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="project-gutenberg">Transcription at Project Gutenberg Australia</a>
<? }elseif($source->Type == EbookSourceType::ProjectGutenbergCanada){ ?>
<a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="project-gutenberg">Transcription at Project Gutenberg Canada</a>
<? }elseif($source->Type == EbookSourceType::Wikisource){ ?>
<a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="wikisource">Transcription at Wikisource</a>
<? }elseif($source->Type == EbookSourceType::FadedPage){ ?>
<a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="globe">Transcription at Faded Page</a>
<? }else{?>
<a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="globe">Transcription</a>
<? } ?>
@ -361,10 +372,15 @@ catch(Exceptions\EbookNotFoundException){
<? foreach($scanSources as $source){ ?>
<li>
<p>
<? if($source->Type == EbookSourceType::InternetArchive){ ?><a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="internet-archive">Page scans at the Internet Archive</a>
<? }elseif($source->Type == EbookSourceType::HathiTrust){ ?><a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="hathitrust">Page scans at HathiTrust</a>
<? }elseif($source->Type == EbookSourceType::GoogleBooks){ ?><a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="google">Page scans at Google Books</a>
<? }else{ ?><a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="globe">Page scans</a><? } ?>
<? if($source->Type == EbookSourceType::InternetArchive){ ?>
<a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="internet-archive">Page scans at the Internet Archive</a>
<? }elseif($source->Type == EbookSourceType::HathiTrust){ ?>
<a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="hathitrust">Page scans at HathiTrust</a>
<? }elseif($source->Type == EbookSourceType::GoogleBooks){ ?>
<a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="google">Page scans at Google Books</a>
<? }else{ ?>
<a href="<?= Formatter::EscapeHtml($source->Url) ?>" class="globe">Page scans</a>
<? } ?>
</p>
</li>
<? } ?>
@ -392,7 +408,9 @@ catch(Exceptions\EbookNotFoundException){
<h2>Improve this ebook</h2>
<p>Anyone can contribute to make a Standard Ebook better for everyone!</p>
<p>To report typos, typography errors, or other corrections, see <a href="/contribute/report-errors">how to report errors</a>.</p>
<? if($ebook->GitHubUrl !== null){ ?><p>If youre comfortable with technology and want to contribute directly, check out <a href="<?= Formatter::EscapeHtml($ebook->GitHubUrl) ?>">this ebooks GitHub repository</a> and our <a href="/contribute">contributors section</a>.</p><? } ?>
<? if($ebook->GitHubUrl !== null){ ?>
<p>If youre comfortable with technology and want to contribute directly, check out <a href="<?= Formatter::EscapeHtml($ebook->GitHubUrl) ?>">this ebooks GitHub repository</a> and our <a href="/contribute">contributors section</a>.</p>
<? } ?>
<p>You can also <a href="/donate">donate to Standard Ebooks</a> to help fund continuing improvement of this and other ebooks.</p>
</section>

View file

@ -109,9 +109,9 @@ catch(Exceptions\AppException $ex){
<h1><?= $pageHeader ?></h1>
<?= Template::DonationCounter() ?>
<?= Template::DonationProgress() ?>
<? if(!DONATION_DRIVE_ON && !DONATION_DRIVE_COUNTER_ON && DONATION_HOLIDAY_ALERT_ON){ ?>
<?= Template::DonationAlert() ?>
<? } ?>
<?= Template::SearchForm(['query' => $query, 'tags' => $tags, 'sort' => $sort, 'view' => $view, 'perPage' => $perPage]) ?>
<? if(sizeof($ebooks) == 0){ ?>
<p class="no-results">No ebooks matched your filters. You can try different filters, or <a href="/ebooks">browse all of our ebooks</a>.</p>
@ -123,7 +123,9 @@ catch(Exceptions\AppException $ex){
<a<? if($page > 1){ ?> href="/ebooks?page=<?= $page - 1 ?><? if($queryStringWithoutPage != ''){ ?>&amp;<?= Formatter::EscapeHtml($queryStringWithoutPage) ?><? } ?>" rel="prev"<? }else{ ?> aria-disabled="true"<? } ?>>Back</a>
<ol>
<? for($i = 1; $i < $pages + 1; $i++){ ?>
<li<? if($page == $i){ ?> class="highlighted"<? } ?>><a href="/ebooks?page=<?= $i ?><? if($queryStringWithoutPage != ''){ ?>&amp;<?= Formatter::EscapeHtml($queryStringWithoutPage) ?><? } ?>"><?= $i ?></a></li>
<li<? if($page == $i){ ?> class="highlighted"<? } ?>>
<a href="/ebooks?page=<?= $i ?><? if($queryStringWithoutPage != ''){ ?>&amp;<?= Formatter::EscapeHtml($queryStringWithoutPage) ?><? } ?>"><?= $i ?></a>
</li>
<? } ?>
</ol>
<a<? if($page < ceil($totalEbooks / $perPage)){ ?> href="/ebooks?page=<?= $page + 1 ?><? if($queryStringWithoutPage != ''){ ?>&amp;<?= Formatter::EscapeHtml($queryStringWithoutPage) ?><? } ?>" rel="next"<? }else{ ?> aria-disabled="true"<? } ?>>Next</a>