Move feeds under new /feeds root

This commit is contained in:
Alex Cabal 2022-06-24 12:54:57 -05:00
parent ee545502af
commit c109c565cb
22 changed files with 136 additions and 99 deletions

12
.gitignore vendored
View file

@ -1,12 +1,12 @@
ebooks/* ebooks/*
www/ebooks/* www/ebooks/*
www/images/covers/* www/images/covers/*
www/opds/*.xml www/feeds/opds/*.xml
www/opds/subjects/*.xml www/feeds/opds/subjects/*.xml
www/rss/*.xml www/feeds/rss/*.xml
www/rss/subjects/*.xml www/feeds/rss/subjects/*.xml
www/atom/*.xml www/feeds/atom/*.xml
www/atom/subjects/*.xml www/feeds/atom/subjects/*.xml
vendor/ vendor/
composer.lock composer.lock
.vagrant/ .vagrant/

View file

@ -38,9 +38,6 @@ sudo ln -s /standardebooks.org/web/config/php/fpm/standardebooks.test.ini /etc/p
sudo ln -s /standardebooks.org/web/config/php/fpm/standardebooks.test.ini /etc/php/*/fpm/conf.d/ sudo ln -s /standardebooks.org/web/config/php/fpm/standardebooks.test.ini /etc/php/*/fpm/conf.d/
sudo ln -s /standardebooks.org/web/config/php/fpm/standardebooks.test.conf /etc/php/*/fpm/pool.d/ sudo ln -s /standardebooks.org/web/config/php/fpm/standardebooks.test.conf /etc/php/*/fpm/pool.d/
sudo systemctl restart "php*-fpm.service" sudo systemctl restart "php*-fpm.service"
# Download the OPDS index template
wget -O /standardebooks.org/web/www/opds/index.xml https://standardebooks.org/opds
``` ```
If everything went well you should now be able to open your web browser and visit `https://standardebooks.test/`. However, you wont see any ebooks if you visit `https://standardebooks.test/ebooks/`. To install some ebooks, first you have to clone their source from GitHub, then deploy them to your local website using the `./scripts/deploy-ebook-to-www` script: If everything went well you should now be able to open your web browser and visit `https://standardebooks.test/`. However, you wont see any ebooks if you visit `https://standardebooks.test/ebooks/`. To install some ebooks, first you have to clone their source from GitHub, then deploy them to your local website using the `./scripts/deploy-ebook-to-www` script:

View file

@ -102,22 +102,22 @@ Define webroot /standardebooks.org/web
Header set Content-Type "text/plain" Header set Content-Type "text/plain"
</location> </location>
# application/xml allows the page to be displayed in a browser. application/atom+xml will cause it to be downloaded. <Location ~ ^/feeds/opds>
<Location ~ ^/opds.+?$>
DirectoryIndex index.xml DirectoryIndex index.xml
Header set Content-Type "application/xml"
</location> </location>
# application/xml allows the page to be displayed in a browser and the encoding to be # text/xml allows the page to be displayed in a browser and the encoding to be
# determined from the document and not the HTTP headers. application/rss+xml will cause it to be downloaded. # determined from the document and not the HTTP headers. application/rss+xml will cause it to be downloaded.
<Location ~ ^/(rss|atom)/.*$> <Directory ~ ${webroot}/www/feeds/>
Header set Content-Type "application/xml" <Files ~ (\.xml|search\.php)$>
</Location> Header set Content-Type "text/xml; charset=utf-8"
</Files>
</Directory>
# Enable HTTP CORS so that browser-based readers like Readium can access opds and ebooks # Enable HTTP CORS so that browser-based readers like Readium can access opds and ebooks
# Allow fonts for newsletter emails # Allow fonts for newsletter emails
# See https://github.com/standardebooks/tools/issues/2 # See https://github.com/standardebooks/tools/issues/2
<Location ~ /(ebooks|opds|fonts)> <Location ~ /(ebooks|feeds/opds|fonts)>
Header set Access-Control-Allow-Origin "*" Header set Access-Control-Allow-Origin "*"
</Location> </Location>
@ -250,7 +250,10 @@ Define webroot /standardebooks.org/web
# If we ask for /opds/all?query=xyz, rewrite that to the search page. # If we ask for /opds/all?query=xyz, rewrite that to the search page.
RewriteCond %{QUERY_STRING} ^query= RewriteCond %{QUERY_STRING} ^query=
RewriteRule ^/opds/all.xml$ /opds/search.php [QSA] RewriteRule ^/feeds/opds/all.xml$ /feeds/opds/search.php [QSA]
# Rewrite old links to feeds
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
# Newsletter # Newsletter
RewriteRule ^/newsletter$ /newsletter/subscribers/new.php RewriteRule ^/newsletter$ /newsletter/subscribers/new.php

View file

@ -101,22 +101,22 @@ Define webroot /standardebooks.org/web
Header set Content-Type "text/plain" Header set Content-Type "text/plain"
</location> </location>
# application/xml allows the page to be displayed in a browser. application/atom+xml will cause it to be downloaded. <Location ~ ^/feeds/opds>
<Location ~ ^/opds.+?$>
DirectoryIndex index.xml DirectoryIndex index.xml
Header set Content-Type "application/xml"
</location> </location>
# application/xml allows the page to be displayed in a browser and the encoding to be # text/xml allows the page to be displayed in a browser and the encoding to be
# determined from the document and not the HTTP headers. application/rss+xml will cause it to be downloaded. # determined from the document and not the HTTP headers. application/rss+xml will cause it to be downloaded.
<Location ~ ^/(rss|atom)/.*$> <Directory ~ ${webroot}/www/feeds/>
Header set Content-Type "application/xml" <Files ~ (\.xml|search\.php)$>
</Location> Header set Content-Type "text/xml; charset=utf-8"
</Files>
</Directory>
# Enable HTTP CORS so that browser-based readers like Readium can access opds and ebooks # Enable HTTP CORS so that browser-based readers like Readium can access opds and ebooks
# Allow fonts for newsletter emails # Allow fonts for newsletter emails
# See https://github.com/standardebooks/tools/issues/2 # See https://github.com/standardebooks/tools/issues/2
<Location ~ /(ebooks|opds|fonts)> <Location ~ /(ebooks|feeds/opds|fonts)>
Header set Access-Control-Allow-Origin "*" Header set Access-Control-Allow-Origin "*"
</Location> </Location>
@ -249,7 +249,10 @@ Define webroot /standardebooks.org/web
# If we ask for /opds/all?query=xyz, rewrite that to the search page. # If we ask for /opds/all?query=xyz, rewrite that to the search page.
RewriteCond %{QUERY_STRING} ^query= RewriteCond %{QUERY_STRING} ^query=
RewriteRule ^/opds/all.xml$ /opds/search.php [QSA] RewriteRule ^/feeds/opds/all.xml$ /feeds/opds/search.php [QSA]
# Rewrite old links to feeds
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
# Newsletter # Newsletter
RewriteRule ^/newsletter$ /newsletter/subscribers/new.php RewriteRule ^/newsletter$ /newsletter/subscribers/new.php

View file

@ -15,7 +15,7 @@ class AtomFeed extends Feed{
parent::__construct($title, $url, $path, $entries); parent::__construct($title, $url, $path, $entries);
$this->Subtitle = $subtitle; $this->Subtitle = $subtitle;
$this->Id = $url; $this->Id = $url;
$this->Stylesheet = '/atom/style'; $this->Stylesheet = '/feeds/atom/style';
} }
protected function GetXmlString(): string{ protected function GetXmlString(): string{

View file

@ -7,7 +7,7 @@ class OpdsFeed extends AtomFeed{
public function __construct(string $title, string $subtitle, string $url, string $path, array $entries, ?OpdsNavigationFeed $parent){ public function __construct(string $title, string $subtitle, string $url, string $path, array $entries, ?OpdsNavigationFeed $parent){
parent::__construct($title, $subtitle, $url, $path, $entries); parent::__construct($title, $subtitle, $url, $path, $entries);
$this->Parent = $parent; $this->Parent = $parent;
$this->Stylesheet = '/opds/style'; $this->Stylesheet = '/feeds/opds/style';
} }
protected function SaveUpdatedTimestamp(string $entryId, DateTime $updatedTimestamp): void{ protected function SaveUpdatedTimestamp(string $entryId, DateTime $updatedTimestamp): void{

View file

@ -5,7 +5,7 @@ class RssFeed extends Feed{
public function __construct(string $title, string $description, string $url, string $path, array $entries){ public function __construct(string $title, string $description, string $url, string $path, array $entries){
parent::__construct($title, $url, $path, $entries); parent::__construct($title, $url, $path, $entries);
$this->Description = $description; $this->Description = $description;
$this->Stylesheet = '/rss/style'; $this->Stylesheet = '/feeds/rss/style';
} }
protected function GetXmlString(): string{ protected function GetXmlString(): string{

View file

@ -33,16 +33,16 @@ $subjects = [];
$ebooksBySubject = []; $ebooksBySubject = [];
$ebooksPerNewestEbooksFeed = 30; $ebooksPerNewestEbooksFeed = 30;
if(!is_dir(WEB_ROOT . '/opds/subjects')){ if(!is_dir(WEB_ROOT . '/feeds/opds/subjects')){
mkdir(WEB_ROOT . '/opds/subjects'); mkdir(WEB_ROOT . '/feeds/opds/subjects');
} }
if(!is_dir(WEB_ROOT . '/rss/subjects')){ if(!is_dir(WEB_ROOT . '/feeds/rss/subjects')){
mkdir(WEB_ROOT . '/rss/subjects'); mkdir(WEB_ROOT . '/feeds/rss/subjects');
} }
if(!is_dir(WEB_ROOT . '/atom/subjects')){ if(!is_dir(WEB_ROOT . '/feeds/atom/subjects')){
mkdir(WEB_ROOT . '/atom/subjects'); mkdir(WEB_ROOT . '/feeds/atom/subjects');
} }
// Iterate over all ebooks to build the various feeds // Iterate over all ebooks to build the various feeds
@ -86,7 +86,7 @@ $opdsRootEntries = [
new OpdsNavigationEntry( new OpdsNavigationEntry(
'Newest ' . number_format($ebooksPerNewestEbooksFeed) . ' Standard Ebooks', 'Newest ' . number_format($ebooksPerNewestEbooksFeed) . ' Standard Ebooks',
'The ' . number_format($ebooksPerNewestEbooksFeed) . ' latest Standard Ebooks, most-recently-released first.', 'The ' . number_format($ebooksPerNewestEbooksFeed) . ' latest Standard Ebooks, most-recently-released first.',
'/opds/new-releases', '/feeds/opds/new-releases',
$now, $now,
'http://opds-spec.org/sort/new', 'http://opds-spec.org/sort/new',
'acquisition' 'acquisition'
@ -94,78 +94,78 @@ $opdsRootEntries = [
new OpdsNavigationEntry( new OpdsNavigationEntry(
'Standard Ebooks by Subject', 'Standard Ebooks by Subject',
'Browse Standard Ebooks by subject.', 'Browse Standard Ebooks by subject.',
'/opds/subjects', '/feeds/opds/subjects',
$now, $now,
'subsection', 'subsection',
'navigation'), 'navigation'),
new OpdsNavigationEntry( new OpdsNavigationEntry(
'All Standard Ebooks', 'All Standard Ebooks',
'All Standard Ebooks, most-recently-updated first. This is a Complete Acquisition Feed as defined in OPDS 1.2 §2.5.', 'All Standard Ebooks, most-recently-updated first. This is a Complete Acquisition Feed as defined in OPDS 1.2 §2.5.',
'/opds/all', '/feeds/opds/all',
$now, $now,
'http://opds-spec.org/crawlable', 'http://opds-spec.org/crawlable',
'acquisition') 'acquisition')
]; ];
$opdsRoot = new OpdsNavigationFeed('Standard Ebooks', 'The navigation root for the Standard Ebooks OPDS feed.', '/opds', WEB_ROOT . '/opds/index.xml', $opdsRootEntries, null); $opdsRoot = new OpdsNavigationFeed('Standard Ebooks', 'The navigation root for the Standard Ebooks OPDS feed.', '/feeds/opds', WEB_ROOT . '/feeds/opds/index.xml', $opdsRootEntries, null);
SaveFeed($opdsRoot, $force, $now); SaveFeed($opdsRoot, $force, $now);
// Create the subjects navigation document // Create the subjects navigation document
sort($subjects); sort($subjects);
$subjectNavigationEntries = []; $subjectNavigationEntries = [];
foreach($subjects as $subject){ foreach($subjects as $subject){
$subjectNavigationEntries[] = new OpdsNavigationEntry($subject, 'Standard Ebooks tagged with “' . strtolower($subject) . ',” most-recently-released first.', '/opds/subjects/' . Formatter::MakeUrlSafe($subject), $now, 'subsection', 'navigation'); $subjectNavigationEntries[] = new OpdsNavigationEntry($subject, 'Standard Ebooks tagged with “' . strtolower($subject) . ',” most-recently-released first.', '/feeds/opds/subjects/' . Formatter::MakeUrlSafe($subject), $now, 'subsection', 'navigation');
} }
$subjectsFeed = new OpdsNavigationFeed('Standard Ebooks by Subject', 'Browse Standard Ebooks by subject.', '/opds/subjects', WEB_ROOT . '/opds/subjects/index.xml', $subjectNavigationEntries, $opdsRoot); $subjectsFeed = new OpdsNavigationFeed('Standard Ebooks by Subject', 'Browse Standard Ebooks by subject.', '/feeds/opds/subjects', WEB_ROOT . '/feeds/opds/subjects/index.xml', $subjectNavigationEntries, $opdsRoot);
$subjectsFeed->Subtitle = 'Browse Standard Ebooks by subject.'; $subjectsFeed->Subtitle = 'Browse Standard Ebooks by subject.';
SaveFeed($subjectsFeed, $force, $now); SaveFeed($subjectsFeed, $force, $now);
// Now generate each individual subject feed // Now generate each individual subject feed
foreach($subjectNavigationEntries as $subjectNavigationEntry){ foreach($subjectNavigationEntries as $subjectNavigationEntry){
krsort($ebooksBySubject[$subjectNavigationEntry->Title]); krsort($ebooksBySubject[$subjectNavigationEntry->Title]);
$subjectFeed = new OpdsAcquisitionFeed($subjectNavigationEntry->Title . ' Ebooks', $subjectNavigationEntry->Description, '/opds/subjects/' . Formatter::MakeUrlSafe($subjectNavigationEntry->Title), WEB_ROOT . '/opds/subjects/' . Formatter::MakeUrlSafe($subjectNavigationEntry->Title) . '.xml', $ebooksBySubject[$subjectNavigationEntry->Title], $subjectsFeed); $subjectFeed = new OpdsAcquisitionFeed($subjectNavigationEntry->Title . ' Ebooks', $subjectNavigationEntry->Description, '/feeds/opds/subjects/' . Formatter::MakeUrlSafe($subjectNavigationEntry->Title), WEB_ROOT . '/feeds/opds/subjects/' . Formatter::MakeUrlSafe($subjectNavigationEntry->Title) . '.xml', $ebooksBySubject[$subjectNavigationEntry->Title], $subjectsFeed);
SaveFeed($subjectFeed, $force, $now); SaveFeed($subjectFeed, $force, $now);
} }
// Create the 'all' feed // Create the 'all' feed
krsort($allEbooks); krsort($allEbooks);
$allFeed = new OpdsAcquisitionFeed('All Standard Ebooks', 'All Standard Ebooks, most-recently-updated first. This is a Complete Acquisition Feed as defined in OPDS 1.2 §2.5.', '/opds/all', WEB_ROOT . '/opds/all.xml', $allEbooks, $opdsRoot, true); $allFeed = new OpdsAcquisitionFeed('All Standard Ebooks', 'All Standard Ebooks, most-recently-updated first. This is a Complete Acquisition Feed as defined in OPDS 1.2 §2.5.', '/feeds/opds/all', WEB_ROOT . '/feeds/opds/all.xml', $allEbooks, $opdsRoot, true);
SaveFeed($allFeed, $force, $now); SaveFeed($allFeed, $force, $now);
// Create the 'newest' feed // Create the 'newest' feed
$newestFeed = new OpdsAcquisitionFeed('Newest ' . number_format($ebooksPerNewestEbooksFeed) . ' Standard Ebooks', 'The ' . number_format($ebooksPerNewestEbooksFeed) . ' latest Standard Ebooks, most-recently-released first.', '/opds/new-releases', WEB_ROOT . '/opds/new-releases.xml', $newestEbooks, $opdsRoot); $newestFeed = new OpdsAcquisitionFeed('Newest ' . number_format($ebooksPerNewestEbooksFeed) . ' Standard Ebooks', 'The ' . number_format($ebooksPerNewestEbooksFeed) . ' latest Standard Ebooks, most-recently-released first.', '/feeds/opds/new-releases', WEB_ROOT . '/feeds/opds/new-releases.xml', $newestEbooks, $opdsRoot);
SaveFeed($newestFeed, $force, $now); SaveFeed($newestFeed, $force, $now);
// Now create RSS feeds // Now create RSS feeds
// Create the 'newest' feed // Create the 'newest' feed
$newestRssFeed = new RssFeed('Standard Ebooks - Newest Ebooks', 'The ' . number_format($ebooksPerNewestEbooksFeed) . ' latest Standard Ebooks, most-recently-released first.', '/rss/new-releases', WEB_ROOT . '/rss/new-releases.xml', $newestEbooks); $newestRssFeed = new RssFeed('Standard Ebooks - Newest Ebooks', 'The ' . number_format($ebooksPerNewestEbooksFeed) . ' latest Standard Ebooks, most-recently-released first.', '/feeds/rss/new-releases', WEB_ROOT . '/feeds/rss/new-releases.xml', $newestEbooks);
SaveFeed($newestRssFeed, $force); SaveFeed($newestRssFeed, $force);
// Create the 'all' feed // Create the 'all' feed
$allRssFeed = new RssFeed('Standard Ebooks - All Ebooks', 'All Standard Ebooks, most-recently-released first.', '/rss/all', WEB_ROOT . '/rss/all.xml', $allEbooks); $allRssFeed = new RssFeed('Standard Ebooks - All Ebooks', 'All Standard Ebooks, most-recently-released first.', '/feeds/rss/all', WEB_ROOT . '/feeds/rss/all.xml', $allEbooks);
SaveFeed($allRssFeed, $force); SaveFeed($allRssFeed, $force);
// Generate each individual subject feed // Generate each individual subject feed
foreach($ebooksBySubject as $subject => $ebooks){ foreach($ebooksBySubject as $subject => $ebooks){
krsort($ebooks); krsort($ebooks);
$subjectRssFeed = new RssFeed('Standard Ebooks - ' . (string)$subject . ' Ebooks', 'Standard Ebooks tagged with “' . strtolower($subject) . ',” most-recently-released first.', '/rss/subjects/' . Formatter::MakeUrlSafe((string)$subject), WEB_ROOT . '/rss/subjects/' . Formatter::MakeUrlSafe((string)$subject) . '.xml', $ebooks); $subjectRssFeed = new RssFeed('Standard Ebooks - ' . (string)$subject . ' Ebooks', 'Standard Ebooks tagged with “' . strtolower($subject) . ',” most-recently-released first.', '/feeds/rss/subjects/' . Formatter::MakeUrlSafe((string)$subject), WEB_ROOT . '/feeds/rss/subjects/' . Formatter::MakeUrlSafe((string)$subject) . '.xml', $ebooks);
SaveFeed($subjectRssFeed, $force); SaveFeed($subjectRssFeed, $force);
} }
// Now create the Atom feeds // Now create the Atom feeds
// Create the 'newest' feed // Create the 'newest' feed
$newestAtomFeed = new AtomFeed('Standard Ebooks - Newest Ebooks', 'The ' . number_format($ebooksPerNewestEbooksFeed) . ' latest Standard Ebooks, most-recently-released first.', '/atom/new-releases', WEB_ROOT . '/atom/new-releases.xml', $newestEbooks); $newestAtomFeed = new AtomFeed('Standard Ebooks - Newest Ebooks', 'The ' . number_format($ebooksPerNewestEbooksFeed) . ' latest Standard Ebooks, most-recently-released first.', '/feeds/atom/new-releases', WEB_ROOT . '/feeds/atom/new-releases.xml', $newestEbooks);
SaveFeed($newestAtomFeed, $force, $now); SaveFeed($newestAtomFeed, $force, $now);
// Create the 'all' feed // Create the 'all' feed
$allAtomFeed = new AtomFeed('Standard Ebooks - All Ebooks', 'All Standard Ebooks, most-recently-released first.', '/atom/all', WEB_ROOT . '/atom/all.xml', $allEbooks); $allAtomFeed = new AtomFeed('Standard Ebooks - All Ebooks', 'All Standard Ebooks, most-recently-released first.', '/feeds/atom/all', WEB_ROOT . '/feeds/atom/all.xml', $allEbooks);
SaveFeed($allAtomFeed, $force, $now); SaveFeed($allAtomFeed, $force, $now);
// Generate each individual subject feed // Generate each individual subject feed
foreach($ebooksBySubject as $subject => $ebooks){ foreach($ebooksBySubject as $subject => $ebooks){
krsort($ebooks); krsort($ebooks);
$subjectAtomFeed = new AtomFeed('Standard Ebooks - ' . (string)$subject . ' Ebooks', 'Standard Ebooks tagged with “' . strtolower($subject) . ',” most-recently-released first.', '/atom/subjects/' . Formatter::MakeUrlSafe((string)$subject), WEB_ROOT . '/atom/subjects/' . Formatter::MakeUrlSafe((string)$subject) . '.xml', $ebooks); $subjectAtomFeed = new AtomFeed('Standard Ebooks - ' . (string)$subject . ' Ebooks', 'Standard Ebooks tagged with “' . strtolower($subject) . ',” most-recently-released first.', '/feeds/atom/subjects/' . Formatter::MakeUrlSafe((string)$subject), WEB_ROOT . '/feeds/atom/subjects/' . Formatter::MakeUrlSafe((string)$subject) . '.xml', $ebooks);
SaveFeed($subjectAtomFeed, $force, $now); SaveFeed($subjectAtomFeed, $force, $now);
} }
?> ?>

View file

@ -40,7 +40,9 @@ if($xmlDeclaration){
<link href="/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/> <link href="/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/>
<link href="/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/> <link href="/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/>
<link href="/manifest.json" rel="manifest"/> <link href="/manifest.json" rel="manifest"/>
<link rel="alternate" type="application/rss+xml" title="Standard Ebooks - New Releases" href="https://standardebooks.org/rss/new-releases"/> <link rel="alternate" type="application/atom+xml" title="Standard Ebooks - New Releases" href="https://standardebooks.org/feeds/atom/new-releases"/>
<link rel="alternate" type="application/atom+xml;profile=opds-catalog;kind=acquisition" title="Standard Ebooks - New Releases" href="https://standardebooks.org/feeds/opds/new-releases"/>
<link rel="alternate" type="application/rss+xml" title="Standard Ebooks - New Releases" href="https://standardebooks.org/feeds/rss/new-releases"/>
<meta content="#394451" name="theme-color"/> <meta content="#394451" name="theme-color"/>
<meta content="<? if($title != ''){ ?><?= Formatter::ToPlainText($title) ?><? }else{ ?>Standard Ebooks<? } ?>" property="og:title"/> <meta content="<? if($title != ''){ ?><?= Formatter::ToPlainText($title) ?><? }else{ ?>Standard Ebooks<? } ?>" property="og:title"/>
<meta content="<?= $ogType ?? 'website' ?>" property="og:type"/> <meta content="<?= $ogType ?? 'website' ?>" property="og:type"/>

View file

@ -2,7 +2,7 @@
/* Notes: /* Notes:
- *All* OPDS feeds must contain a rel="http://opds-spec.org/crawlable" link pointing to the /opds/all feed - *All* OPDS feeds must contain a rel="http://opds-spec.org/crawlable" link pointing to the /feeds/opds/all feed
- The <fh:complete/> element is required to note this as a "Complete Acquisition Feeds"; see https://specs.opds.io/opds-1.2#25-complete-acquisition-feeds - The <fh:complete/> element is required to note this as a "Complete Acquisition Feeds"; see https://specs.opds.io/opds-1.2#25-complete-acquisition-feeds
@ -19,9 +19,9 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:schema="http://schema.org/"<? if($isCrawlable){ ?> xmlns:fh="http://purl.org/syndication/history/1.0"<? } ?>> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:schema="http://schema.org/"<? if($isCrawlable){ ?> xmlns:fh="http://purl.org/syndication/history/1.0"<? } ?>>
<id><?= SITE_URL . Formatter::ToPlainXmlText($id) ?></id> <id><?= SITE_URL . Formatter::ToPlainXmlText($id) ?></id>
<link href="<?= SITE_URL . Formatter::ToPlainXmlText($url) ?>" rel="self" type="application/atom+xml;profile=opds-catalog;kind=acquisition"/> <link href="<?= SITE_URL . Formatter::ToPlainXmlText($url) ?>" rel="self" type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<link href="<?= SITE_URL ?>/opds" rel="start" type="application/atom+xml;profile=opds-catalog;kind=navigation"/> <link href="<?= SITE_URL ?>/feeds/opds" rel="start" type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<link href="<?= SITE_URL ?><?= Formatter::ToPlainXmlText($parentUrl) ?>" rel="up" type="application/atom+xml;profile=opds-catalog;kind=navigation"/> <link href="<?= SITE_URL ?><?= Formatter::ToPlainXmlText($parentUrl) ?>" rel="up" type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<link href="<?= SITE_URL ?>/opds/all" rel="http://opds-spec.org/crawlable" type="application/atom+xml;profile=opds-catalog;kind=acquisition"/> <link href="<?= SITE_URL ?>/feeds/opds/all" rel="http://opds-spec.org/crawlable" type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml"/> <link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml"/>
<title><?= Formatter::ToPlainXmlText($title) ?></title> <title><?= Formatter::ToPlainXmlText($title) ?></title>
<? if($subtitle !== null){ ?><subtitle><?= Formatter::ToPlainXmlText($subtitle) ?></subtitle><? } ?> <? if($subtitle !== null){ ?><subtitle><?= Formatter::ToPlainXmlText($subtitle) ?></subtitle><? } ?>

View file

@ -10,8 +10,8 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/"> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
<id><?= SITE_URL . Formatter::ToPlainXmlText($id) ?></id> <id><?= SITE_URL . Formatter::ToPlainXmlText($id) ?></id>
<link href="<?= SITE_URL . Formatter::ToPlainXmlText($url) ?>" rel="self" type="application/atom+xml;profile=opds-catalog;kind=navigation"/> <link href="<?= SITE_URL . Formatter::ToPlainXmlText($url) ?>" rel="self" type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<link href="<?= SITE_URL ?>/opds" rel="start" type="application/atom+xml;profile=opds-catalog;kind=navigation"/> <link href="<?= SITE_URL ?>/feeds/opds" rel="start" type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<link href="<?= SITE_URL ?>/opds/all" rel="http://opds-spec.org/crawlable" type="application/atom+xml;profile=opds-catalog;kind=acquisition"/> <link href="<?= SITE_URL ?>/feeds/opds/all" rel="http://opds-spec.org/crawlable" type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml"/> <link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml"/>
<? if($parentUrl !== null){ ?><link href="<?= SITE_URL ?><?= Formatter::ToPlainXmlText($parentUrl) ?>" rel="up" type="application/atom+xml;profile=opds-catalog;kind=navigation"/><? } ?> <? if($parentUrl !== null){ ?><link href="<?= SITE_URL ?><?= Formatter::ToPlainXmlText($parentUrl) ?>" rel="up" type="application/atom+xml;profile=opds-catalog;kind=navigation"/><? } ?>
<title><?= Formatter::ToPlainXmlText($title) ?></title> <title><?= Formatter::ToPlainXmlText($title) ?></title>

View file

@ -7,7 +7,7 @@
<SyndicationRight>open</SyndicationRight> <SyndicationRight>open</SyndicationRight>
<OutputEncoding>UTF-8</OutputEncoding> <OutputEncoding>UTF-8</OutputEncoding>
<InputEncoding>UTF-8</InputEncoding> <InputEncoding>UTF-8</InputEncoding>
<Url type="text/html" template="https://standardebooks.org/ebooks?query={searchTerms}&amp;per-page={count}&amp;page={startPage}"/> <Url type="application/xhtml+xml" template="https://standardebooks.org/ebooks?query={searchTerms}&amp;per-page={count}&amp;page={startPage}"/>
<Url type="application/atom+xml;profile=opds-catalog;kind=acquisition" template="https://standardebooks.org/opds/all?query={searchTerms}"/> <Url type="application/atom+xml;profile=opds-catalog;kind=acquisition" template="https://standardebooks.org/feeds/opds/all?query={searchTerms}"/>
<Query role="example" searchTerms="fiction" startPage="1" count="12"/> <Query role="example" searchTerms="fiction" startPage="1" count="12"/>
</OpenSearchDescription> </OpenSearchDescription>

View file

@ -9,13 +9,13 @@ require_once('Core.php');
<p>Note that some RSS readers may show these feeds ordered by when an ebook was last updated, even though the feeds are ordered by when an ebook was first released. You should be able to change the sort order in your RSS reader.</p> <p>Note that some RSS readers may show these feeds ordered by when an ebook was last updated, even though the feeds are ordered by when an ebook was first released. You should be able to change the sort order in your RSS reader.</p>
<ul class="feed"> <ul class="feed">
<li> <li>
<p><a href="/atom/new-releases">New releases</a></p> <p><a href="/feeds/atom/new-releases">New releases</a></p>
<p class="url"><?= SITE_URL ?>/atom/new-releases</p> <p class="url"><?= SITE_URL ?>/feeds/atom/new-releases</p>
<p>The thirty latest Standard Ebooks, most-recently-released first.</p> <p>The thirty latest Standard Ebooks, most-recently-released first.</p>
</li> </li>
<li> <li>
<p><a href="/atom/all">All ebooks</a></p> <p><a href="/feeds/atom/all">All ebooks</a></p>
<p class="url"><?= SITE_URL ?>/atom/all</p> <p class="url"><?= SITE_URL ?>/feeds/atom/all</p>
<p>All Standard Ebooks, most-recently-released first.</p> <p>All Standard Ebooks, most-recently-released first.</p>
</li> </li>
</ul> </ul>
@ -24,9 +24,8 @@ require_once('Core.php');
<ul class="feed"> <ul class="feed">
<? foreach(SE_SUBJECTS as $subject){ ?> <? foreach(SE_SUBJECTS as $subject){ ?>
<li> <li>
<p><a href="/atom/subjects/<?= Formatter::MakeUrlSafe($subject) ?>"><?= Formatter::ToPlainText($subject) ?></a></p> <p><a href="/feeds/atom/subjects/<?= Formatter::MakeUrlSafe($subject) ?>"><?= Formatter::ToPlainText($subject) ?></a></p>
<p><a href="/atom/subjects/<?= Formatter::MakeUrlSafe($subject) ?>"></a></p> <p class="url"><?= SITE_URL ?>/feeds/atom/subjects/<?= Formatter::MakeUrlSafe($subject) ?></p>
<p class="url"><?= SITE_URL ?>/atom/subjects/<?= Formatter::MakeUrlSafe($subject) ?></p>
</li> </li>
<? } ?> <? } ?>
</ul> </ul>

View file

@ -2,7 +2,7 @@
require_once('Core.php'); require_once('Core.php');
// `text/xsl` is the only mime type recognized by Chrome for XSL stylesheets // `text/xsl` is the only mime type recognized by Chrome for XSL stylesheets
header('Content-Type: text/xsl'); header('Content-Type: text/xsl; charset=utf-8');
print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n") print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
?> ?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"> <xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">

View file

@ -0,0 +1,18 @@
<?
require_once('Core.php');
?><?= Template::Header(['title' => 'Atom Ebook Feeds by Subject', 'description' => 'A list of available Atom 1.0 feeds of Standard Ebooks ebooks by subject.']) ?>
<main>
<article>
<h1>Atom 1.0 Feeds by Subject</h1>
<ul class="feed">
<? foreach(SE_SUBJECTS as $subject){ ?>
<li>
<p><a href="/feeds/atom/subjects/<?= Formatter::MakeUrlSafe($subject) ?>"><?= Formatter::ToPlainText($subject) ?></a></p>
<p class="url"><?= SITE_URL ?>/feeds/atom/subjects/<?= Formatter::MakeUrlSafe($subject) ?></p>
</li>
<? } ?>
</ul>
</article>
</main>
<?= Template::Footer() ?>

View file

@ -18,8 +18,8 @@ require_once('Core.php');
<p><a href="https://en.wikipedia.org/wiki/Open_Publication_Distribution_System">OPDS feeds</a> are designed for use with ereading systems like <a href="http://koreader.rocks/">KOreader</a> or <a href="https://calibre-ebook.com">Calibre</a>, or with ereaders like <a href="https://johnfactotum.github.io/foliate/">Foliate</a>. They allow you to search, browse, and download from our catalog, directly in your ereader. Theyre also perfect for organizations who wish to download and process our catalog efficiently.</p> <p><a href="https://en.wikipedia.org/wiki/Open_Publication_Distribution_System">OPDS feeds</a> are designed for use with ereading systems like <a href="http://koreader.rocks/">KOreader</a> or <a href="https://calibre-ebook.com">Calibre</a>, or with ereaders like <a href="https://johnfactotum.github.io/foliate/">Foliate</a>. They allow you to search, browse, and download from our catalog, directly in your ereader. Theyre also perfect for organizations who wish to download and process our catalog efficiently.</p>
<ul class="feed"> <ul class="feed">
<li> <li>
<p><a href="/opds">The Standard Ebooks OPDS feed</a></p> <p><a href="/feeds/opds">The Standard Ebooks OPDS feed</a></p>
<p class="url"><?= SITE_URL ?>/opds</p> <p class="url"><?= SITE_URL ?>/feeds/opds</p>
</li> </li>
</ul> </ul>
<section> <section>
@ -40,13 +40,13 @@ require_once('Core.php');
<p>Note that some RSS readers may show these feeds ordered by when an ebook was last updated, even though the feeds are ordered by when an ebook was first released. You should be able to change the sort order in your RSS reader.</p> <p>Note that some RSS readers may show these feeds ordered by when an ebook was last updated, even though the feeds are ordered by when an ebook was first released. You should be able to change the sort order in your RSS reader.</p>
<ul class="feed"> <ul class="feed">
<li> <li>
<p><a href="/atom/new-releases">New releases</a></p> <p><a href="/feeds/atom/new-releases">New releases</a></p>
<p class="url"><?= SITE_URL ?>/atom/new-releases</p> <p class="url"><?= SITE_URL ?>/feeds/atom/new-releases</p>
<p>The thirty latest Standard Ebooks, most-recently-released first.</p> <p>The thirty latest Standard Ebooks, most-recently-released first.</p>
</li> </li>
<li> <li>
<p><a href="/atom/all">All ebooks</a></p> <p><a href="/feeds/atom/all">All ebooks</a></p>
<p class="url"><?= SITE_URL ?>/atom/all</p> <p class="url"><?= SITE_URL ?>/feeds/atom/all</p>
<p>All Standard Ebooks, most-recently-released first.</p> <p>All Standard Ebooks, most-recently-released first.</p>
</li> </li>
</ul> </ul>
@ -55,9 +55,8 @@ require_once('Core.php');
<ul class="feed"> <ul class="feed">
<? foreach(SE_SUBJECTS as $subject){ ?> <? foreach(SE_SUBJECTS as $subject){ ?>
<li> <li>
<p><a href="/atom/subjects/<?= Formatter::MakeUrlSafe($subject) ?>"><?= Formatter::ToPlainText($subject) ?></a></p> <p><a href="/feeds/atom/subjects/<?= Formatter::MakeUrlSafe($subject) ?>"><?= Formatter::ToPlainText($subject) ?></a></p>
<p><a href="/atom/subjects/<?= Formatter::MakeUrlSafe($subject) ?>"></a></p> <p class="url"><?= SITE_URL ?>/feeds/atom/subjects/<?= Formatter::MakeUrlSafe($subject) ?></p>
<p class="url"><?= SITE_URL ?>/atom/subjects/<?= Formatter::MakeUrlSafe($subject) ?></p>
</li> </li>
<? } ?> <? } ?>
</ul> </ul>
@ -68,13 +67,13 @@ require_once('Core.php');
<p>RSS feeds are an alternative to Atom feeds. They contain less information than Atom feeds, but might be better supported by some RSS readers.</p> <p>RSS feeds are an alternative to Atom feeds. They contain less information than Atom feeds, but might be better supported by some RSS readers.</p>
<ul class="feed"> <ul class="feed">
<li> <li>
<p><a href="/rss/new-releases">New releases</a></p> <p><a href="/feeds/rss/new-releases">New releases</a></p>
<p class="url"><?= SITE_URL ?>/rss/new-releases</p> <p class="url"><?= SITE_URL ?>/feeds/rss/new-releases</p>
<p>The thirty latest Standard Ebooks, most-recently-released first.</p> <p>The thirty latest Standard Ebooks, most-recently-released first.</p>
</li> </li>
<li> <li>
<p><a href="/rss/all">All ebooks</a></p> <p><a href="/feeds/rss/all">All ebooks</a></p>
<p class="url"><?= SITE_URL ?>/rss/all</p> <p class="url"><?= SITE_URL ?>/feeds/rss/all</p>
<p>All Standard Ebooks, most-recently-released first.</p> <p>All Standard Ebooks, most-recently-released first.</p>
</li> </li>
</ul> </ul>
@ -83,9 +82,8 @@ require_once('Core.php');
<ul class="feed"> <ul class="feed">
<? foreach(SE_SUBJECTS as $subject){ ?> <? foreach(SE_SUBJECTS as $subject){ ?>
<li> <li>
<p><a href="/rss/subjects/<?= Formatter::MakeUrlSafe($subject) ?>"><?= Formatter::ToPlainText($subject) ?></a></p> <p><a href="/feeds/rss/subjects/<?= Formatter::MakeUrlSafe($subject) ?>"><?= Formatter::ToPlainText($subject) ?></a></p>
<p><a href="/rss/subjects/<?= Formatter::MakeUrlSafe($subject) ?>"></a></p> <p class="url"><?= SITE_URL ?>/feeds/rss/subjects/<?= Formatter::MakeUrlSafe($subject) ?></p>
<p class="url"><?= SITE_URL ?>/rss/subjects/<?= Formatter::MakeUrlSafe($subject) ?></p>
</li> </li>
<? } ?> <? } ?>
</ul> </ul>

View file

@ -16,14 +16,14 @@ catch(\Exception $ex){
include(WEB_ROOT . '/404.php'); include(WEB_ROOT . '/404.php');
exit(); exit();
} }
print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<?xml-stylesheet href=\"/opds/style\" type=\"text/xsl\"?>\n"); print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<?xml-stylesheet href=\"/feeds/opds/style\" type=\"text/xsl\"?>\n");
?> ?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:schema="http://schema.org/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:schema="http://schema.org/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">
<id><?= SITE_URL ?>/opds/all?query=<?= urlencode($query) ?></id> <id><?= SITE_URL ?>/feeds/opds/all?query=<?= urlencode($query) ?></id>
<link href="<?= SITE_URL ?>/opds/all?query=<?= urlencode($query) ?>" rel="self" type="application/atom+xml;profile=opds-catalog"/> <link href="<?= SITE_URL ?>/feeds/opds/all?query=<?= urlencode($query) ?>" rel="self" type="application/atom+xml;profile=opds-catalog"/>
<link href="<?= SITE_URL ?>/ebooks/ebooks?query=<?= urlencode($query) ?>" rel="alternate" type="text/html"/> <link href="<?= SITE_URL ?>/ebooks/ebooks?query=<?= urlencode($query) ?>" rel="alternate" type="text/html"/>
<link href="<?= SITE_URL ?>/opds" rel="start" type="application/atom+xml;profile=opds-catalog;kind=navigation"/> <link href="<?= SITE_URL ?>/feeds/opds" rel="start" type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<link href="<?= SITE_URL ?>/opds/all" rel="http://opds-spec.org/crawlable" type="application/atom+xml;profile=opds-catalog;kind=acquisition"/> <link href="<?= SITE_URL ?>/feeds/opds/all" rel="http://opds-spec.org/crawlable" type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml"/> <link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml"/>
<title>Search Results</title> <title>Search Results</title>
<subtitle>Results for <?= Formatter::ToPlainXmlText($query) ?>”.</subtitle> <subtitle>Results for <?= Formatter::ToPlainXmlText($query) ?>”.</subtitle>

View file

@ -2,7 +2,7 @@
require_once('Core.php'); require_once('Core.php');
// `text/xsl` is the only mime type recognized by Chrome for XSL stylesheets // `text/xsl` is the only mime type recognized by Chrome for XSL stylesheets
header('Content-Type: text/xsl'); header('Content-Type: text/xsl; charset=utf-8');
print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n") print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
?> ?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:atom="http://www.w3.org/2005/Atom"> <xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:atom="http://www.w3.org/2005/Atom">
@ -36,7 +36,7 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
<div class="thumbnail-container"> <div class="thumbnail-container">
<a tabindex="-1"> <a tabindex="-1">
<xsl:attribute name="href"> <xsl:attribute name="href">
<xsl:value-of select="atom:link[@rel='related']/@href"/> <xsl:value-of select="atom:link[@rel='alternate']/@href"/>
</xsl:attribute> </xsl:attribute>
<img alt="" width="224" height="335"> <img alt="" width="224" height="335">
<xsl:attribute name="src"> <xsl:attribute name="src">
@ -48,7 +48,7 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
<p> <p>
<a> <a>
<xsl:attribute name="href"> <xsl:attribute name="href">
<xsl:value-of select="atom:link[@rel='related']/@href"/> <xsl:value-of select="atom:link[@rel='alternate']/@href"/>
</xsl:attribute> </xsl:attribute>
<xsl:value-of select="atom:title"/> <xsl:value-of select="atom:title"/>
</a> </a>

View file

@ -8,13 +8,13 @@ require_once('Core.php');
<p>RSS feeds are an alternative to Atom feeds. They contain less information than Atom feeds, but might be better supported by some RSS readers.</p> <p>RSS feeds are an alternative to Atom feeds. They contain less information than Atom feeds, but might be better supported by some RSS readers.</p>
<ul class="feed"> <ul class="feed">
<li> <li>
<p><a href="/rss/new-releases">New releases</a></p> <p><a href="/feeds/rss/new-releases">New releases</a></p>
<p class="url"><?= SITE_URL ?>/rss/new-releases</p> <p class="url"><?= SITE_URL ?>/feeds/rss/new-releases</p>
<p>The thirty latest Standard Ebooks, most-recently-released first.</p> <p>The thirty latest Standard Ebooks, most-recently-released first.</p>
</li> </li>
<li> <li>
<p><a href="/rss/all">All ebooks</a></p> <p><a href="/feeds/rss/all">All ebooks</a></p>
<p class="url"><?= SITE_URL ?>/rss/all</p> <p class="url"><?= SITE_URL ?>/feeds/rss/all</p>
<p>All Standard Ebooks, most-recently-released first.</p> <p>All Standard Ebooks, most-recently-released first.</p>
</li> </li>
</ul> </ul>
@ -23,9 +23,8 @@ require_once('Core.php');
<ul class="feed"> <ul class="feed">
<? foreach(SE_SUBJECTS as $subject){ ?> <? foreach(SE_SUBJECTS as $subject){ ?>
<li> <li>
<p><a href="/rss/subjects/<?= Formatter::MakeUrlSafe($subject) ?>"><?= Formatter::ToPlainText($subject) ?></a></p> <p><a href="/feeds/rss/subjects/<?= Formatter::MakeUrlSafe($subject) ?>"><?= Formatter::ToPlainText($subject) ?></a></p>
<p><a href="/rss/subjects/<?= Formatter::MakeUrlSafe($subject) ?>"></a></p> <p class="url"><?= SITE_URL ?>/feeds/rss/subjects/<?= Formatter::MakeUrlSafe($subject) ?></p>
<p class="url"><?= SITE_URL ?>/rss/subjects/<?= Formatter::MakeUrlSafe($subject) ?></p>
</li> </li>
<? } ?> <? } ?>
</ul> </ul>

View file

@ -2,7 +2,7 @@
require_once('Core.php'); require_once('Core.php');
// `text/xsl` is the only mime type recognized by Chrome for XSL stylesheets // `text/xsl` is the only mime type recognized by Chrome for XSL stylesheets
header('Content-Type: text/xsl'); header('Content-Type: text/xsl; charset=utf-8');
print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n") print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
?> ?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"> <xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">

View file

@ -0,0 +1,18 @@
<?
require_once('Core.php');
?><?= Template::Header(['title' => 'RSS Ebook Feeds by Subject', 'description' => 'A list of available RSS 2.0 feeds of Standard Ebooks ebooks by subject.']) ?>
<main>
<article>
<h1>RSS 2.0 Feeds by Subject</h1>
<ul class="feed">
<? foreach(SE_SUBJECTS as $subject){ ?>
<li>
<p><a href="/feeds/rss/subjects/<?= Formatter::MakeUrlSafe($subject) ?>"><?= Formatter::ToPlainText($subject) ?></a></p>
<p class="url"><?= SITE_URL ?>/feeds/rss/subjects/<?= Formatter::MakeUrlSafe($subject) ?></p>
</li>
<? } ?>
</ul>
</article>
</main>
<?= Template::Footer() ?>