More work on bulk downloads

This commit is contained in:
Alex Cabal 2022-07-10 13:18:22 -05:00
parent fc1db3a3d4
commit 45221365b5
14 changed files with 156 additions and 68 deletions

View file

@ -245,8 +245,10 @@ Define webroot /standardebooks.org/web
RewriteRule ^/images/covers/(.+?)\-[a-z0-9]{8}\-(cover|hero)(@2x)?\.(jpg|avif)$ /images/covers/$1-$2$3.$4 RewriteRule ^/images/covers/(.+?)\-[a-z0-9]{8}\-(cover|hero)(@2x)?\.(jpg|avif)$ /images/covers/$1-$2$3.$4
RewriteRule ^/ebooks/([^\./]+?)$ /ebooks/author.php?url-path=$1 [QSA] RewriteRule ^/ebooks/([^\./]+?)$ /ebooks/author.php?url-path=$1 [QSA]
RewriteRule ^/tags/([^\./]+?)$ /ebooks/index.php?tags[]=$1 [QSA] RewriteRule ^/ebooks/([^\./]+?)/downloads$ /bulk-downloads/get.php?author=$1 [QSA]
RewriteRule ^/subjects/([^\./]+?)$ /ebooks/index.php?tags[]=$1 [QSA]
RewriteRule ^/collections/([^\./]+?)$ /ebooks/index.php?collection=$1 [QSA] RewriteRule ^/collections/([^\./]+?)$ /ebooks/index.php?collection=$1 [QSA]
RewriteRule ^/collections/([^/]+?)/downloads$ /bulk-downloads/get.php?collection=$1
# Prevent this rule from firing if we're getting a distribution file # Prevent this rule from firing if we're getting a distribution file
RewriteCond %{REQUEST_FILENAME} !^/ebooks/.+?/downloads/.+$ RewriteCond %{REQUEST_FILENAME} !^/ebooks/.+?/downloads/.+$

View file

@ -227,9 +227,10 @@ Define webroot /standardebooks.org/web
RewriteRule ^/images/covers/(.+?)\-[a-z0-9]{8}\-(cover|hero)(@2x)?\.(jpg|avif)$ /images/covers/$1-$2$3.$4 RewriteRule ^/images/covers/(.+?)\-[a-z0-9]{8}\-(cover|hero)(@2x)?\.(jpg|avif)$ /images/covers/$1-$2$3.$4
RewriteRule ^/ebooks/([^\./]+?)$ /ebooks/author.php?url-path=$1 [QSA] RewriteRule ^/ebooks/([^\./]+?)$ /ebooks/author.php?url-path=$1 [QSA]
RewriteRule ^/tags/([^\./]+?)$ /ebooks/index.php?tags[]=$1 [QSA] RewriteRule ^/ebooks/([^\./]+?)/downloads$ /bulk-downloads/get.php?author=$1 [QSA]
RewriteRule ^/subjects/([^\./]+?)$ /ebooks/index.php?tags[]=$1 [QSA]
RewriteRule ^/collections/([^\./]+?)$ /ebooks/index.php?collection=$1 [QSA] RewriteRule ^/collections/([^\./]+?)$ /ebooks/index.php?collection=$1 [QSA]
RewriteRule ^/collections/([^/]+?)/download$ /bulk-downloads/get.php?collection=$1 RewriteRule ^/collections/([^/]+?)/downloads$ /bulk-downloads/get.php?collection=$1
# Prevent this rule from firing if we're getting a distribution file # Prevent this rule from firing if we're getting a distribution file
RewriteCond %{REQUEST_FILENAME} !^/ebooks/.+?/downloads/.+$ RewriteCond %{REQUEST_FILENAME} !^/ebooks/.+?/downloads/.+$

View file

@ -225,7 +225,7 @@ class Library{
return $ebooks; return $ebooks;
} }
private static function FillBulkDownloadObject(string $dir, string $downloadType): stdClass{ private static function FillBulkDownloadObject(string $dir, string $downloadType, string $urlRoot): stdClass{
$obj = new stdClass(); $obj = new stdClass();
// The count of ebooks in each file is stored as a filesystem attribute // The count of ebooks in each file is stored as a filesystem attribute
@ -243,7 +243,12 @@ class Library{
$obj->Label = basename($dir); $obj->Label = basename($dir);
} }
$obj->UrlLabel = exec('attr -g se-url-label ' . escapeshellarg($dir)) ?: null;
if($obj->UrlLabel === null){
$obj->UrlLabel = Formatter::MakeUrlSafe($obj->Label); $obj->UrlLabel = Formatter::MakeUrlSafe($obj->Label);
}
$obj->Url = $urlRoot . '/' . $obj->UrlLabel;
$obj->LabelSort = exec('attr -g se-label-sort ' . escapeshellarg($dir)) ?: null; $obj->LabelSort = exec('attr -g se-label-sort ' . escapeshellarg($dir)) ?: null;
if($obj->LabelSort === null){ if($obj->LabelSort === null){
@ -329,7 +334,7 @@ class Library{
rsort($dirs); rsort($dirs);
foreach($dirs as $dir){ foreach($dirs as $dir){
$obj = self::FillBulkDownloadObject($dir, 'months'); $obj = self::FillBulkDownloadObject($dir, 'months', '/months');
$date = new DateTime($obj->Label . '-01'); $date = new DateTime($obj->Label . '-01');
$year = $date->format('Y'); $year = $date->format('Y');
@ -346,7 +351,7 @@ class Library{
// Generate bulk downloads by subject // Generate bulk downloads by subject
foreach(glob(WEB_ROOT . '/bulk-downloads/subjects/*/', GLOB_NOSORT) as $dir){ foreach(glob(WEB_ROOT . '/bulk-downloads/subjects/*/', GLOB_NOSORT) as $dir){
$subjects[] = self::FillBulkDownloadObject($dir, 'subjects'); $subjects[] = self::FillBulkDownloadObject($dir, 'subjects', '/subjects');
} }
usort($subjects, function($a, $b){ return $a->LabelSort <=> $b->LabelSort; }); usort($subjects, function($a, $b){ return $a->LabelSort <=> $b->LabelSort; });
@ -354,7 +359,7 @@ class Library{
// Generate bulk downloads by collection // Generate bulk downloads by collection
foreach(glob(WEB_ROOT . '/bulk-downloads/collections/*/', GLOB_NOSORT) as $dir){ foreach(glob(WEB_ROOT . '/bulk-downloads/collections/*/', GLOB_NOSORT) as $dir){
$collections[] = self::FillBulkDownloadObject($dir, 'collections'); $collections[] = self::FillBulkDownloadObject($dir, 'collections', '/collections');
} }
usort($collections, function($a, $b){ return $a->LabelSort <=> $b->LabelSort; }); usort($collections, function($a, $b){ return $a->LabelSort <=> $b->LabelSort; });
@ -362,7 +367,7 @@ class Library{
// Generate bulk downloads by authors // Generate bulk downloads by authors
foreach(glob(WEB_ROOT . '/bulk-downloads/authors/*/', GLOB_NOSORT) as $dir){ foreach(glob(WEB_ROOT . '/bulk-downloads/authors/*/', GLOB_NOSORT) as $dir){
$authors[] = self::FillBulkDownloadObject($dir, 'authors'); $authors[] = self::FillBulkDownloadObject($dir, 'authors', '/ebooks');
} }
usort($authors, function($a, $b){ return $a->LabelSort <=> $b->LabelSort; }); usort($authors, function($a, $b){ return $a->LabelSort <=> $b->LabelSort; });

View file

@ -7,6 +7,6 @@ class Tag{
public function __construct(string $name){ public function __construct(string $name){
$this->Name = $name; $this->Name = $name;
$this->UrlName = Formatter::MakeUrlSafe($this->Name); $this->UrlName = Formatter::MakeUrlSafe($this->Name);
$this->Url = '/tags/' . $this->UrlName; $this->Url = '/subjects/' . $this->UrlName;
} }
} }

View file

@ -13,6 +13,25 @@ $groups = ['collections', 'subjects', 'authors', 'months'];
$ebooksByGroup = []; $ebooksByGroup = [];
$updatedByGroup = []; $updatedByGroup = [];
function rrmdir($src){
// See https://www.php.net/manual/en/function.rmdir.php#117354
$dir = opendir($src);
while(false !== ($file = readdir($dir))) {
if (($file != '.') && ($file != '..')){
$full = $src . '/' . $file;
if(is_dir($full)){
rrmdir($full);
}
else{
unlink($full);
}
}
}
closedir($dir);
rmdir($src);
}
function CreateZip(string $filePath, array $ebooks, string $type, string $webRoot): void{ function CreateZip(string $filePath, array $ebooks, string $type, string $webRoot): void{
$tempFilename = tempnam(sys_get_temp_dir(), "se-ebooks"); $tempFilename = tempnam(sys_get_temp_dir(), "se-ebooks");
@ -71,6 +90,7 @@ foreach(Library::GetEbooksFromFilesystem($webRoot) as $ebook){
$obj = new stdClass(); $obj = new stdClass();
$obj->Label = $timestamp; $obj->Label = $timestamp;
$obj->LabelSort = $timestamp; $obj->LabelSort = $timestamp;
$obj->UrlLabel = Formatter::MakeUrlSafe($obj->Label);
$obj->Updated = $updatedTimestamp; $obj->Updated = $updatedTimestamp;
$obj->Ebooks = [$ebook]; $obj->Ebooks = [$ebook];
@ -89,6 +109,7 @@ foreach(Library::GetEbooksFromFilesystem($webRoot) as $ebook){
$obj = new stdClass(); $obj = new stdClass();
$obj->Label = $tag->Name; $obj->Label = $tag->Name;
$obj->LabelSort = $tag->Name; $obj->LabelSort = $tag->Name;
$obj->UrlLabel = Formatter::MakeUrlSafe($obj->Label);
$obj->Updated = $updatedTimestamp; $obj->Updated = $updatedTimestamp;
$obj->Ebooks = [$ebook]; $obj->Ebooks = [$ebook];
@ -108,6 +129,7 @@ foreach(Library::GetEbooksFromFilesystem($webRoot) as $ebook){
$obj = new stdClass(); $obj = new stdClass();
$obj->Label = $collection->Name; $obj->Label = $collection->Name;
$obj->LabelSort = $collection->GetSortedName(); $obj->LabelSort = $collection->GetSortedName();
$obj->UrlLabel = Formatter::MakeUrlSafe($obj->Label);
$obj->Updated = $updatedTimestamp; $obj->Updated = $updatedTimestamp;
$obj->Ebooks = [$ebook]; $obj->Ebooks = [$ebook];
@ -122,29 +144,46 @@ foreach(Library::GetEbooksFromFilesystem($webRoot) as $ebook){
} }
// Add to the 'books by author' list // Add to the 'books by author' list
foreach($ebook->Authors as $author){ // We have to index by UrlName for cases like `Samuel Butler` whose UrlName is `samuel-butler-1612-1680`.
if(!isset($ebooksByGroup['authors'][$author->Name])){ $authorsUrl = preg_replace('|^/ebooks/|', '', $ebook->AuthorsUrl);
if(!isset($ebooksByGroup['authors'][$authorsUrl])){
$obj = new stdClass(); $obj = new stdClass();
$obj->Label = $author->Name; $obj->Label = strip_tags($ebook->AuthorsHtml);
$obj->LabelSort = $author->SortName; $obj->LabelSort = $ebook->Authors[0]->SortName;
$obj->UrlLabel = $authorsUrl;
$obj->Updated = $updatedTimestamp; $obj->Updated = $updatedTimestamp;
$obj->Ebooks = [$ebook]; $obj->Ebooks = [$ebook];
$ebooksByGroup['authors'][$author->Name] = $obj; $ebooksByGroup['authors'][$authorsUrl] = $obj;
} }
else{ else{
$ebooksByGroup['authors'][$author->Name]->Ebooks[] = $ebook; $ebooksByGroup['authors'][$authorsUrl]->Ebooks[] = $ebook;
if($updatedTimestamp > $ebooksByGroup['authors'][$author->Name]->Updated){ if($updatedTimestamp > $ebooksByGroup['authors'][$authorsUrl]->Updated){
$ebooksByGroup['authors'][$author->Name]->Updated = $updatedTimestamp; $ebooksByGroup['authors'][$authorsUrl]->Updated = $updatedTimestamp;
}
} }
} }
} }
foreach($groups as $group){ foreach($groups as $group){
// First delete any orphan directories that we don't expect to be here, for example a collection that was later renamed
foreach(glob($webRoot . '/bulk-downloads/' . $group . '/*/') as $dir){
$expected = false;
foreach($ebooksByGroup[$group] as $collection){ foreach($ebooksByGroup[$group] as $collection){
$urlSafeCollection = Formatter::MakeUrlSafe($collection->Label); if($collection->UrlLabel == basename($dir)){
$parentDir = $webRoot . '/bulk-downloads/' . $group . '/' . $urlSafeCollection; $expected = true;
break;
}
}
if(!$expected){
print('Removing ' . $dir . "\n");
rrmdir($dir);
}
}
// Now create the zip files!
foreach($ebooksByGroup[$group] as $collection){
$parentDir = $webRoot . '/bulk-downloads/' . $group . '/' . $collection->UrlLabel;
if(!is_dir($parentDir)){ if(!is_dir($parentDir)){
mkdir($parentDir, 0775, true); mkdir($parentDir, 0775, true);
@ -154,8 +193,11 @@ foreach($groups as $group){
exec('attr -q -s se-label -V ' . escapeshellarg($collection->Label) . ' ' . escapeshellarg($parentDir)); exec('attr -q -s se-label -V ' . escapeshellarg($collection->Label) . ' ' . escapeshellarg($parentDir));
exec('attr -q -s se-label-sort -V ' . escapeshellarg($collection->LabelSort) . ' ' . escapeshellarg($parentDir)); exec('attr -q -s se-label-sort -V ' . escapeshellarg($collection->LabelSort) . ' ' . escapeshellarg($parentDir));
// We also need to save the URL label for author edge cases like `Samuel Butler` -> `samuel-butler-1612-1680` or `Karl Marx and Freidrich Engels` -> `karl-marx_friedrich-engels`
exec('attr -q -s se-url-label -V ' . escapeshellarg($collection->UrlLabel) . ' ' . escapeshellarg($parentDir));
foreach($types as $type){ foreach($types as $type){
$filePath = $parentDir . '/se-ebooks-' . $urlSafeCollection . '-' . $type . '.zip'; $filePath = $parentDir . '/se-ebooks-' . $collection->UrlLabel . '-' . $type . '.zip';
// If the file doesn't exist, or if the content.opf last updated time is newer than the file modification time // If the file doesn't exist, or if the content.opf last updated time is newer than the file modification time
if(!file_exists($filePath) || filemtime($filePath) < $collection->Updated){ if(!file_exists($filePath) || filemtime($filePath) < $collection->Updated){

View file

@ -10,7 +10,7 @@
<tbody> <tbody>
<? foreach($collections as $collection){ ?> <? foreach($collections as $collection){ ?>
<tr> <tr>
<td class="row-header"><a href="/collections/<?= Formatter::MakeUrlSafe($collection->Label) ?>"><?= Formatter::ToPlainText($collection->Label) ?></a></td> <td class="row-header"><a href="<?= $collection->Url ?>"><?= Formatter::ToPlainText($collection->Label) ?></a></td>
<td class="number"><?= Formatter::ToPlainText(number_format($collection->EbookCount)) ?></td> <td class="number"><?= Formatter::ToPlainText(number_format($collection->EbookCount)) ?></td>
<td class="number"><?= Formatter::ToPlainText($collection->UpdatedString) ?></td> <td class="number"><?= Formatter::ToPlainText($collection->UpdatedString) ?></td>

View file

@ -29,7 +29,7 @@ catch(Safe\Exceptions\ApcuException $ex){
<?= Template::Error(['exception' => $forbiddenException]) ?> <?= Template::Error(['exception' => $forbiddenException]) ?>
<? } ?> <? } ?>
<p><a href="/about#patrons-circle">Patrons circle members</a> can download zip files containing all of the ebooks that were released in a given month of Standard Ebooks history. You can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature.</p> <p><a href="/about#patrons-circle">Patrons circle members</a> can download zip files containing all of the ebooks that were released in a given month of Standard Ebooks history. You can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature.</p>
<p>These zip files contain each ebook in every format we offer, and are updated once daily with the latest versions of each ebook.</p> <p>These zip files contain each ebook in every format we offer, and are updated once daily with the latest versions of each ebook. Read about <a href="/help/how-to-use-our-ebooks#which-file-to-download">which file format to download</a>.</p>
<p>If youre a Patrons Circle member, when prompted enter your email address and leave the password field blank to download these files.</p> <p>If youre a Patrons Circle member, when prompted enter your email address and leave the password field blank to download these files.</p>
<?= Template::BulkDownloadTable(['label' => 'Author', 'collections' => $authors]); ?> <?= Template::BulkDownloadTable(['label' => 'Author', 'collections' => $authors]); ?>
</section> </section>

View file

@ -29,7 +29,7 @@ catch(Safe\Exceptions\ApcuException $ex){
<?= Template::Error(['exception' => $forbiddenException]) ?> <?= Template::Error(['exception' => $forbiddenException]) ?>
<? } ?> <? } ?>
<p><a href="/about#patrons-circle">Patrons circle members</a> can download zip files containing all of the ebooks that were released in a given month of Standard Ebooks history. You can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature.</p> <p><a href="/about#patrons-circle">Patrons circle members</a> can download zip files containing all of the ebooks that were released in a given month of Standard Ebooks history. You can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature.</p>
<p>These zip files contain each ebook in every format we offer, and are updated once daily with the latest versions of each ebook.</p> <p>These zip files contain each ebook in every format we offer, and are updated once daily with the latest versions of each ebook. Read about <a href="/help/how-to-use-our-ebooks#which-file-to-download">which file format to download</a>.</p>
<p>If youre a Patrons Circle member, when prompted enter your email address and leave the password field blank to download these files.</p> <p>If youre a Patrons Circle member, when prompted enter your email address and leave the password field blank to download these files.</p>
<?= Template::BulkDownloadTable(['label' => 'Collection', 'collections' => $collections]); ?> <?= Template::BulkDownloadTable(['label' => 'Collection', 'collections' => $collections]); ?>
</section> </section>

View file

@ -3,7 +3,10 @@ require_once('Core.php');
use function Safe\apcu_fetch; use function Safe\apcu_fetch;
$bulkDownloadCollection = null; $collection = null;
$collectionUrlName = HttpInput::Str(GET, 'collection', false);
$collection = null;
$authorUrlName = HttpInput::Str(GET, 'author', false);
$exception = null; $exception = null;
$user = null; $user = null;
@ -11,16 +14,11 @@ try{
if(isset($_SERVER['PHP_AUTH_USER'])){ if(isset($_SERVER['PHP_AUTH_USER'])){
$user = User::GetByPatronIdentifier($_SERVER['PHP_AUTH_USER']); $user = User::GetByPatronIdentifier($_SERVER['PHP_AUTH_USER']);
} }
}
catch(Exceptions\InvalidUserException $ex){
$exception = new Exceptions\InvalidPatronException();
}
try{ if($collectionUrlName !== null){
$collection = HttpInput::Str(GET, 'collection', false) ?? '';
$collections = []; $collections = [];
// Get all collections and then find the specific one we're looking for
try{ try{
$collections = apcu_fetch('bulk-downloads-collections'); $collections = apcu_fetch('bulk-downloads-collections');
} }
@ -29,11 +27,47 @@ try{
$collections = $result['collections']; $collections = $result['collections'];
} }
if(!isset($collections[$collection]) || sizeof($collections[$collection]) == 0){ foreach($collections as $c){
throw new Exceptions\InvalidCollectionException(); if($c->UrlLabel == $collectionUrlName){
$collection = $c;
break;
}
} }
$bulkDownloadCollection = $collections[$collection]; if($collection === null){
throw new Exceptions\InvalidCollectionException();
}
}
if($authorUrlName !== null){
$authors = [];
// Get all authors and then find the specific one we're looking for
try{
$collections = apcu_fetch('bulk-downloads-authors');
}
catch(Safe\Exceptions\ApcuException $ex){
$result = Library::RebuildBulkDownloadsCache();
$collections = $result['authors'];
}
foreach($collections as $c){
if($c->UrlLabel == $authorUrlName){
$collection = $c;
break;
}
}
if($collection === null){
throw new Exceptions\InvalidAuthorException();
}
}
}
catch(Exceptions\InvalidUserException $ex){
$exception = new Exceptions\InvalidPatronException();
}
catch(Exceptions\InvalidCollectionException $ex){
Template::Emit404();
} }
catch(Exceptions\InvalidCollectionException $ex){ catch(Exceptions\InvalidCollectionException $ex){
Template::Emit404(); Template::Emit404();
@ -42,7 +76,7 @@ catch(Exceptions\InvalidCollectionException $ex){
?><?= Template::Header(['title' => 'Download ', 'highlight' => '', 'description' => 'Download zip files containing all of the Standard Ebooks released in a given month.']) ?> ?><?= Template::Header(['title' => 'Download ', 'highlight' => '', 'description' => 'Download zip files containing all of the Standard Ebooks released in a given month.']) ?>
<main> <main>
<section class="bulk-downloads"> <section class="bulk-downloads">
<h1>Download the <?= $bulkDownloadCollection[0]->Label ?> Collection</h1> <h1>Download the <?= $collection->Label ?> Collection</h1>
<?= Template::Error(['exception' => $exception]) ?> <?= Template::Error(['exception' => $exception]) ?>
<? if($user === null){ ?> <? if($user === null){ ?>
<p><a href="/about#patrons-circle">Patrons circle members</a> can download zip files containing all of the ebooks in a collection. You can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature.</p> <p><a href="/about#patrons-circle">Patrons circle members</a> can download zip files containing all of the ebooks in a collection. You can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature.</p>
@ -51,7 +85,7 @@ catch(Exceptions\InvalidCollectionException $ex){
<p>Select the ebook format in which youd like to download this collection.</p> <p>Select the ebook format in which youd like to download this collection.</p>
<p>You can also read about <a href="/help/how-to-use-our-ebooks#which-file-to-download">which ebook format to download</a>.</p> <p>You can also read about <a href="/help/how-to-use-our-ebooks#which-file-to-download">which ebook format to download</a>.</p>
<? } ?> <? } ?>
<?= Template::BulkDownloadTable(['label' => 'Collection', 'collections' => [$bulkDownloadCollection]]); ?> <?= Template::BulkDownloadTable(['label' => 'Collection', 'collections' => [$collection]]); ?>
</section> </section>
</main> </main>
<?= Template::Footer() ?> <?= Template::Footer() ?>

View file

@ -29,7 +29,7 @@ catch(Safe\Exceptions\ApcuException $ex){
<?= Template::Error(['exception' => $forbiddenException]) ?> <?= Template::Error(['exception' => $forbiddenException]) ?>
<? } ?> <? } ?>
<p><a href="/about#patrons-circle">Patrons circle members</a> can download zip files containing all of the ebooks that were released in a given month of Standard Ebooks history. You can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature.</p> <p><a href="/about#patrons-circle">Patrons circle members</a> can download zip files containing all of the ebooks that were released in a given month of Standard Ebooks history. You can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature.</p>
<p>These zip files contain each ebook in every format we offer, and are updated once daily with the latest versions of each ebook.</p> <p>These zip files contain each ebook in every format we offer, and are updated once daily with the latest versions of each ebook. Read about <a href="/help/how-to-use-our-ebooks#which-file-to-download">which file format to download</a>.</p>
<p>If youre a Patrons Circle member, when prompted enter your email address and leave the password field blank to download these files.</p> <p>If youre a Patrons Circle member, when prompted enter your email address and leave the password field blank to download these files.</p>
<table class="download-list"> <table class="download-list">
<tbody> <tbody>

View file

@ -29,7 +29,7 @@ catch(Safe\Exceptions\ApcuException $ex){
<?= Template::Error(['exception' => $forbiddenException]) ?> <?= Template::Error(['exception' => $forbiddenException]) ?>
<? } ?> <? } ?>
<p><a href="/about#patrons-circle">Patrons circle members</a> can download zip files containing all of the ebooks that were released in a given month of Standard Ebooks history. You can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature.</p> <p><a href="/about#patrons-circle">Patrons circle members</a> can download zip files containing all of the ebooks that were released in a given month of Standard Ebooks history. You can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature.</p>
<p>These zip files contain each ebook in every format we offer, and are updated once daily with the latest versions of each ebook.</p> <p>These zip files contain each ebook in every format we offer, and are updated once daily with the latest versions of each ebook. Read about <a href="/help/how-to-use-our-ebooks#which-file-to-download">which file format to download</a>.</p>
<p>If youre a Patrons Circle member, when prompted enter your email address and leave the password field blank to download these files.</p> <p>If youre a Patrons Circle member, when prompted enter your email address and leave the password field blank to download these files.</p>
<?= Template::BulkDownloadTable(['label' => 'Subject', 'collections' => $subjects]); ?> <?= Template::BulkDownloadTable(['label' => 'Subject', 'collections' => $subjects]); ?>
</section> </section>

View file

@ -2301,10 +2301,17 @@ article.step-by-step-guide ol ol{
width: 100%; width: 100%;
} }
h1.is-collection{
margin-bottom: 1rem;
}
.download-collection{ .download-collection{
display: flex; display: flex;
justify-content: center; justify-content: center;
margin-bottom: 2rem; margin-bottom: 4rem;
border-bottom: 1px dashed var(--sub-text);
padding-bottom: 1rem;
font-style: italic;
} }
abbr.acronym{ abbr.acronym{
@ -3359,6 +3366,7 @@ ul.feed p{
body > header ul li:nth-child(2) ~ li, body > header ul li:nth-child(2) ~ li,
body > header ul li + li{ body > header ul li + li{
margin-top: 1rem; margin-top: 1rem;
padding-top: 0;
} }
body > header ul li, body > header ul li,

View file

@ -23,8 +23,12 @@ catch(Exceptions\InvalidAuthorException $ex){
} }
?><?= 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">
<h1>Ebooks by <?= $ebooks[0]->AuthorsHtml ?></h1> <h1<? if(sizeof($ebooks) > 1){ ?> class="is-collection"<? } ?>>Ebooks by <?= $ebooks[0]->AuthorsHtml ?></h1>
<? if(sizeof($ebooks) > 1){ ?>
<p class="download-collection"><a href="<?= Formatter::ToPlainText($ebooks[0]->AuthorsUrl) ?>/downloads">Download all ebooks in this collection</a></p>
<? } ?>
<?= Template::EbookGrid(['ebooks' => $ebooks, 'view' => VIEW_GRID]) ?> <?= Template::EbookGrid(['ebooks' => $ebooks, 'view' => VIEW_GRID]) ?>
<p class="feeds-alert">We also have <a href="/bulk-downloads">bulk ebook downloads</a> available, as well as <a href="/feeds">ebook catalog feeds</a> for use directly in your ereader app or RSS reader.</p>
<?= Template::ContributeAlert() ?> <?= Template::ContributeAlert() ?>
</main> </main>
<?= Template::Footer() ?> <?= Template::Footer() ?>

View file

@ -65,17 +65,9 @@ try{
$collectionName = preg_replace('/^The /ius', '', $collectionObject->Name); $collectionName = preg_replace('/^The /ius', '', $collectionObject->Name);
$collectionType = $collectionObject->Type ?? 'collection'; $collectionType = $collectionObject->Type ?? 'collection';
# This is a kind of .endswith() test $pageTitle = 'Browse free ebooks in the ' . Formatter::ToPlainText($collectionName) . ' ' . $collectionType;
if(substr_compare(mb_strtolower($collectionObject->Name), mb_strtolower($collectionObject->Type), -strlen(mb_strtolower($collectionObject->Type))) !== 0){
$collectionType = ' ' . $collectionType;
}
else{
$collectionType = '';
}
$pageTitle = 'Browse free ebooks in the ' . Formatter::ToPlainText($collectionName) . $collectionType;
$pageDescription = 'A list of free ebooks in the ' . Formatter::ToPlainText($collectionName) . ' ' . $collectionType; $pageDescription = 'A list of free ebooks in the ' . Formatter::ToPlainText($collectionName) . ' ' . $collectionType;
$pageHeader = 'Free ebooks in the ' . Formatter::ToPlainText($collectionName) . ' ' . $collectionType; $pageHeader = 'Free Ebooks in the ' . Formatter::ToPlainText($collectionName) . ' ' . ucfirst($collectionType);
} }
else{ else{
throw new Exceptions\InvalidCollectionException(); throw new Exceptions\InvalidCollectionException();
@ -126,7 +118,7 @@ catch(Exceptions\InvalidCollectionException $ex){
} }
?><?= Template::Header(['title' => $pageTitle, 'highlight' => 'ebooks', 'description' => $pageDescription]) ?> ?><?= Template::Header(['title' => $pageTitle, 'highlight' => 'ebooks', 'description' => $pageDescription]) ?>
<main class="ebooks"> <main class="ebooks">
<h1><?= $pageHeader ?></h1> <h1<? if($collection !== null && sizeof($ebooks) > 1){ ?> class="is-collection"<? } ?>><?= $pageHeader ?></h1>
<?= Template::DonationCounter() ?> <?= Template::DonationCounter() ?>
<?= Template::DonationProgress() ?> <?= Template::DonationProgress() ?>
<? if(!DONATION_DRIVE_ON && !DONATION_DRIVE_COUNTER_ON && DONATION_HOLIDAY_ALERT_ON){ ?> <? if(!DONATION_DRIVE_ON && !DONATION_DRIVE_COUNTER_ON && DONATION_HOLIDAY_ALERT_ON){ ?>
@ -136,7 +128,7 @@ catch(Exceptions\InvalidCollectionException $ex){
<?= Template::SearchForm(['query' => $query, 'tags' => $tags, 'sort' => $sort, 'view' => $view, 'perPage' => $perPage]) ?> <?= Template::SearchForm(['query' => $query, 'tags' => $tags, 'sort' => $sort, 'view' => $view, 'perPage' => $perPage]) ?>
<? } ?> <? } ?>
<? if($collection !== null && sizeof($ebooks) > 1){ ?> <? if($collection !== null && sizeof($ebooks) > 1){ ?>
<p class="download-collection"><a class="button" href="/collections/<?= Formatter::ToPlainText($collection) ?>/download">Download entire collection</a></p> <p class="download-collection"><a href="/collections/<?= Formatter::ToPlainText($collection) ?>/downloads">Download all ebooks in this collection</a></p>
<? } ?> <? } ?>
<? if(sizeof($ebooks) == 0){ ?> <? 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> <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>