Add opensearch directives to Atom and RSS feeds

This commit is contained in:
Alex Cabal 2022-06-24 13:26:15 -05:00
parent c109c565cb
commit 1b237265c7
13 changed files with 161 additions and 49 deletions

View file

@ -250,7 +250,7 @@ Define webroot /standardebooks.org/web
# If we ask for /opds/all?query=xyz, rewrite that to the search page.
RewriteCond %{QUERY_STRING} ^query=
RewriteRule ^/feeds/opds/all.xml$ /feeds/opds/search.php [QSA]
RewriteRule ^/feeds/(opds|atom|rss)/all.xml$ /feeds/$1/search.php [QSA]
# Rewrite old links to feeds
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]

View file

@ -249,7 +249,7 @@ Define webroot /standardebooks.org/web
# If we ask for /opds/all?query=xyz, rewrite that to the search page.
RewriteCond %{QUERY_STRING} ^query=
RewriteRule ^/feeds/opds/all.xml$ /feeds/opds/search.php [QSA]
RewriteRule ^/feeds/(opds|atom|rss)/all.xml$ /feeds/$1/search.php [QSA]
# Rewrite old links to feeds
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]

View file

@ -84,7 +84,7 @@ $now = new DateTime();
// Create OPDS feeds
$opdsRootEntries = [
new OpdsNavigationEntry(
'Newest ' . number_format($ebooksPerNewestEbooksFeed) . ' Standard Ebooks',
'Newest Standard Ebooks',
'The ' . number_format($ebooksPerNewestEbooksFeed) . ' latest Standard Ebooks, most-recently-released first.',
'/feeds/opds/new-releases',
$now,
@ -133,7 +133,7 @@ $allFeed = new OpdsAcquisitionFeed('All Standard Ebooks', 'All Standard Ebooks,
SaveFeed($allFeed, $force, $now);
// Create the 'newest' feed
$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);
$newestFeed = new OpdsAcquisitionFeed('Newest 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);
// Now create RSS feeds

View file

@ -17,34 +17,8 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
<name>Standard Ebooks</name>
<uri><?= SITE_URL ?></uri>
</author>
<link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml" />
<? foreach($entries as $entry){ ?>
<entry>
<id><?= SITE_URL . $entry->Url ?></id>
<title><?= Formatter::ToPlainXmlText($entry->Title) ?></title>
<? foreach($entry->Authors as $author){ ?>
<author>
<name><?= Formatter::ToPlainXmlText($author->Name) ?></name>
<uri><?= SITE_URL . Formatter::ToPlainXmlText($entry->AuthorsUrl) ?></uri>
</author>
<? } ?>
<published><?= $entry->Timestamp->format('Y-m-d\TH:i:s\Z') ?></published>
<updated><?= $entry->ModifiedTimestamp->format('Y-m-d\TH:i:s\Z') ?></updated>
<rights>Public domain in the United States. Users located outside of the United States must check their local laws before using this ebook. Original content released to the public domain via the Creative Commons CC0 1.0 Universal Public Domain Dedication.</rights>
<summary type="text"><?= Formatter::ToPlainXmlText($entry->Description) ?></summary>
<content type="html"><?= Formatter::ToPlainXmlText($entry->LongDescription) ?></content>
<? foreach($entry->LocTags as $subject){ ?>
<category scheme="http://purl.org/dc/terms/LCSH" term="<?= Formatter::ToPlainXmlText($subject) ?>"/>
<? } ?>
<? foreach($entry->Tags as $subject){ ?>
<category scheme="https://standardebooks.org/vocab/subjects" term="<?= Formatter::ToPlainXmlText($subject->Name) ?>"/>
<? } ?>
<media:thumbnail url="<?= SITE_URL . $entry->Url ?>/downloads/cover-thumbnail.jpg" height="525" width="350"/>
<link href="<?= SITE_URL . $entry->Url ?>" rel="alternate" title="This ebooks page at Standard Ebooks" type="application/xhtml+xml"/>
<? if(file_exists(WEB_ROOT . $entry->EpubUrl)){ ?><link href="<?= SITE_URL . $entry->EpubUrl ?>" length="<?= filesize(WEB_ROOT . $entry->EpubUrl) ?>" rel="enclosure" title="Recommended compatible epub" type="application/epub+zip" /><? } ?>
<? if(file_exists(WEB_ROOT . $entry->AdvancedEpubUrl)){ ?><link href="<?= SITE_URL . $entry->AdvancedEpubUrl ?>" length="<?= filesize(WEB_ROOT . $entry->AdvancedEpubUrl) ?>" rel="enclosure" title="Advanced epub" type="application/epub+zip" /><? } ?>
<? if(file_exists(WEB_ROOT . $entry->KepubUrl)){ ?><link href="<?= SITE_URL . $entry->KepubUrl ?>" length="<?= filesize(WEB_ROOT . $entry->KepubUrl) ?>" rel="enclosure" title="Kobo Kepub epub" type="application/kepub+zip" /><? } ?>
<? if(file_exists(WEB_ROOT . $entry->Azw3Url)){ ?><link href="<?= SITE_URL . $entry->Azw3Url ?>" length="<?= filesize(WEB_ROOT . $entry->Azw3Url) ?>" rel="enclosure" title="Amazon Kindle azw3" type="application/x-mobipocket-ebook" /><? } ?>
<? if(file_exists(WEB_ROOT . $entry->TextSinglePageUrl)){ ?><link href="<?= SITE_URL . $entry->TextSinglePageUrl ?>" length="<?= filesize(WEB_ROOT . $entry->TextSinglePageUrl) ?>" rel="enclosure" title="XHTML" type="application/xhtml+xml" /><? } ?>
</entry>
<?= Template::AtomFeedEntry(['entry' => $entry]) ?>
<? } ?>
</feed>

View file

@ -0,0 +1,28 @@
<entry>
<id><?= SITE_URL . $entry->Url ?></id>
<title><?= Formatter::ToPlainXmlText($entry->Title) ?></title>
<? foreach($entry->Authors as $author){ ?>
<author>
<name><?= Formatter::ToPlainXmlText($author->Name) ?></name>
<uri><?= SITE_URL . Formatter::ToPlainXmlText($entry->AuthorsUrl) ?></uri>
</author>
<? } ?>
<published><?= $entry->Timestamp->format('Y-m-d\TH:i:s\Z') ?></published>
<updated><?= $entry->ModifiedTimestamp->format('Y-m-d\TH:i:s\Z') ?></updated>
<rights>Public domain in the United States. Users located outside of the United States must check their local laws before using this ebook. Original content released to the public domain via the Creative Commons CC0 1.0 Universal Public Domain Dedication.</rights>
<summary type="text"><?= Formatter::ToPlainXmlText($entry->Description) ?></summary>
<content type="html"><?= Formatter::ToPlainXmlText($entry->LongDescription) ?></content>
<? foreach($entry->LocTags as $subject){ ?>
<category scheme="http://purl.org/dc/terms/LCSH" term="<?= Formatter::ToPlainXmlText($subject) ?>"/>
<? } ?>
<? foreach($entry->Tags as $subject){ ?>
<category scheme="https://standardebooks.org/vocab/subjects" term="<?= Formatter::ToPlainXmlText($subject->Name) ?>"/>
<? } ?>
<media:thumbnail url="<?= SITE_URL . $entry->Url ?>/downloads/cover-thumbnail.jpg" height="525" width="350"/>
<link href="<?= SITE_URL . $entry->Url ?>" rel="alternate" title="This ebooks page at Standard Ebooks" type="application/xhtml+xml"/>
<? if(file_exists(WEB_ROOT . $entry->EpubUrl)){ ?><link href="<?= SITE_URL . $entry->EpubUrl ?>" length="<?= filesize(WEB_ROOT . $entry->EpubUrl) ?>" rel="enclosure" title="Recommended compatible epub" type="application/epub+zip" /><? } ?>
<? if(file_exists(WEB_ROOT . $entry->AdvancedEpubUrl)){ ?><link href="<?= SITE_URL . $entry->AdvancedEpubUrl ?>" length="<?= filesize(WEB_ROOT . $entry->AdvancedEpubUrl) ?>" rel="enclosure" title="Advanced epub" type="application/epub+zip" /><? } ?>
<? if(file_exists(WEB_ROOT . $entry->KepubUrl)){ ?><link href="<?= SITE_URL . $entry->KepubUrl ?>" length="<?= filesize(WEB_ROOT . $entry->KepubUrl) ?>" rel="enclosure" title="Kobo Kepub epub" type="application/kepub+zip" /><? } ?>
<? if(file_exists(WEB_ROOT . $entry->Azw3Url)){ ?><link href="<?= SITE_URL . $entry->Azw3Url ?>" length="<?= filesize(WEB_ROOT . $entry->Azw3Url) ?>" rel="enclosure" title="Amazon Kindle azw3" type="application/x-mobipocket-ebook" /><? } ?>
<? if(file_exists(WEB_ROOT . $entry->TextSinglePageUrl)){ ?><link href="<?= SITE_URL . $entry->TextSinglePageUrl ?>" length="<?= filesize(WEB_ROOT . $entry->TextSinglePageUrl) ?>" rel="enclosure" title="XHTML" type="application/xhtml+xml" /><? } ?>
</entry>

14
templates/RssEntry.php Normal file
View file

@ -0,0 +1,14 @@
<item>
<title><?= Formatter::ToPlainXmlText($entry->Title) ?>, by <?= Formatter::ToPlainXmlText(strip_tags($entry->AuthorsHtml)) ?></title>
<link><?= SITE_URL . Formatter::ToPlainXmlText($entry->Url) ?></link>
<description><?= Formatter::ToPlainXmlText($entry->Description) ?></description>
<pubDate><?= $entry->Timestamp->format('r') ?></pubDate>
<guid><?= Formatter::ToPlainXmlText(preg_replace('/^url:/ius', '', $entry->Identifier)) ?></guid>
<? foreach($entry->Tags as $tag){ ?>
<category domain="https://standardebooks.org/vocab/subjects"><?= Formatter::ToPlainXmlText($tag->Name) ?></category>
<? } ?>
<media:thumbnail url="<?= SITE_URL . $entry->Url ?>/downloads/cover-thumbnail.jpg" height="525" width="350"/>
<? if($entry->EpubUrl !== null){ ?>
<enclosure url="<?= SITE_URL . Formatter::ToPlainXmlText($entry->EpubUrl) ?>" length="<?= filesize(WEB_ROOT . $entry->EpubUrl) ?>" type="application/epub+zip" /> <? /* Only one <enclosure> is allowed */ ?>
<? } ?>
</item>

View file

@ -15,6 +15,7 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
<lastBuildDate><?= $updatedTimestamp ?></lastBuildDate>
<docs>http://blogs.law.harvard.edu/tech/rss</docs>
<atom:link href="<?= SITE_URL . Formatter::ToPlainXmlText($url) ?>" rel="self" type="application/rss+xml"/>
<atom:link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml" />
<image>
<url><?= SITE_URL ?>/images/logo-rss.png</url>
<title><?= Formatter::ToPlainXmlText($title) ?></title> <? /* must be identical to channel title */ ?>
@ -24,20 +25,7 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
<width>144</width>
</image>
<? foreach($entries as $entry){ ?>
<item>
<title><?= Formatter::ToPlainXmlText($entry->Title) ?>, by <?= Formatter::ToPlainXmlText(strip_tags($entry->AuthorsHtml)) ?></title>
<link><?= SITE_URL . Formatter::ToPlainXmlText($entry->Url) ?></link>
<description><?= Formatter::ToPlainXmlText($entry->Description) ?></description>
<pubDate><?= $entry->Timestamp->format('r') ?></pubDate>
<guid><?= Formatter::ToPlainXmlText(preg_replace('/^url:/ius', '', $entry->Identifier)) ?></guid>
<? foreach($entry->Tags as $tag){ ?>
<category domain="https://standardebooks.org/vocab/subjects"><?= Formatter::ToPlainXmlText($tag->Name) ?></category>
<? } ?>
<media:thumbnail url="<?= SITE_URL . $entry->Url ?>/downloads/cover-thumbnail.jpg" height="525" width="350"/>
<? if($entry->EpubUrl !== null){ ?>
<enclosure url="<?= SITE_URL . Formatter::ToPlainXmlText($entry->EpubUrl) ?>" length="<?= filesize(WEB_ROOT . $entry->EpubUrl) ?>" type="application/epub+zip" /> <? /* Only one <enclosure> is allowed */ ?>
<? } ?>
</item>
<?= Template::RssEntry(['entry' => $entry]) ?>
<? } ?>
</channel>
</rss>

View file

@ -8,6 +8,8 @@
<OutputEncoding>UTF-8</OutputEncoding>
<InputEncoding>UTF-8</InputEncoding>
<Url type="application/xhtml+xml" template="https://standardebooks.org/ebooks?query={searchTerms}&amp;per-page={count}&amp;page={startPage}"/>
<Url type="application/rss+xml" template="https://standardebooks.org/feeds/rss/all?query={searchTerms}"/>
<Url type="application/atom+xml" template="https://standardebooks.org/feeds/atom/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"/>
</OpenSearchDescription>

38
www/feeds/atom/search.php Normal file
View file

@ -0,0 +1,38 @@
<?
require_once('Core.php');
use Safe\DateTime;
$ebooks = [];
try{
$query = HttpInput::Str(GET, 'query', false);
if($query !== null){
$ebooks = Library::Search($query);
}
}
catch(\Exception $ex){
http_response_code(500);
include(WEB_ROOT . '/404.php');
exit();
}
print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<?xml-stylesheet href=\"/feeds/atom/style\" type=\"text/xsl\"?>\n");
?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">
<id><?= SITE_URL ?>/feeds/atom/all?query=<?= urlencode($query) ?></id>
<link href="<?= SITE_URL ?>/feeds/atom/all?query=<?= urlencode($query) ?>" rel="self" type="application/atom+xml"/>
<link href="<?= SITE_URL ?>/ebooks/ebooks?query=<?= urlencode($query) ?>" rel="alternate" type="text/html"/>
<link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml"/>
<title>Search Results</title>
<subtitle>Results for <?= Formatter::ToPlainXmlText($query) ?>”.</subtitle>
<icon><?= SITE_URL ?>/images/logo.png</icon>
<updated><?= (new Datetime())->Format('Y-m-d\TH:i:s\Z') ?></updated>
<author>
<name>Standard Ebooks</name>
<uri><?= SITE_URL ?></uri>
</author>
<opensearch:totalResults><?= sizeof($ebooks) ?></opensearch:totalResults>
<? foreach($ebooks as $ebook){ ?>
<?= Template::AtomFeedEntry(['entry' => $ebook]) ?>
<? } ?>
</feed>

View file

@ -10,7 +10,15 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
<xsl:template match="/">
<?= Template::Header(['xmlDeclaration' => false]) ?>
<main class="opds">
<xsl:choose>
<xsl:when test="contains(/atom:feed/atom:title, 'Standard Ebooks - ')">
<h1><xsl:value-of select="substring-after(/atom:feed/atom:title, 'Standard Ebooks - ')"/></h1>
</xsl:when>
<xsl:otherwise>
<h1><xsl:value-of select="/atom:feed/atom:title"/></h1>
</xsl:otherwise>
</xsl:choose>
<p><xsl:value-of select="/atom:feed/atom:subtitle"/></p>
<p>This page is an Atom 1.0 feed. The URL in your browsers address bar (<a class="url"><xsl:attribute name="href"><xsl:value-of select="/atom:feed/atom:link[@rel='self']/@href"/></xsl:attribute><xsl:value-of select="/atom:feed/atom:link[@rel='self']/@href"/></a>) can be used in any Atom client.</p>
<ol class="ebooks-list list">
<xsl:for-each select="/atom:feed/atom:entry">

View file

@ -10,7 +10,15 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
<xsl:template match="/">
<?= Template::Header(['xmlDeclaration' => false]) ?>
<main class="opds">
<xsl:choose>
<xsl:when test="contains(/atom:feed/atom:title, 'Standard Ebooks - ')">
<h1><xsl:value-of select="substring-after(/atom:feed/atom:title, 'Standard Ebooks - ')"/></h1>
</xsl:when>
<xsl:otherwise>
<h1><xsl:value-of select="/atom:feed/atom:title"/></h1>
</xsl:otherwise>
</xsl:choose>
<p><xsl:value-of select="/atom:feed/atom:subtitle"/></p>
<p>This page is an OPDS 1.2 feed. The URL in your browsers address bar (<a class="url"><xsl:attribute name="href"><xsl:value-of select="/atom:feed/atom:link[@rel='self']/@href"/></xsl:attribute><xsl:value-of select="/atom:feed/atom:link[@rel='self']/@href"/></a>) can be used in any OPDS client.</p>
<xsl:if test="/atom:feed/atom:entry[./atom:link[starts-with(@type, 'application/atom+xml;profile=opds-catalog;kind=')]]">
<ol class="rss">

45
www/feeds/rss/search.php Normal file
View file

@ -0,0 +1,45 @@
<?
require_once('Core.php');
use Safe\DateTime;
$ebooks = [];
try{
$query = HttpInput::Str(GET, 'query', false);
if($query !== null){
$ebooks = Library::Search($query);
}
}
catch(\Exception $ex){
http_response_code(500);
include(WEB_ROOT . '/404.php');
exit();
}
print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<?xml-stylesheet href=\"/feeds/rss/style\" type=\"text/xsl\"?>\n");
?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">
<channel>
<title>Search Results</title>
<link><?= SITE_URL ?></link>
<description>Results for <?= Formatter::ToPlainXmlText($query) ?>”.</description>
<language>en-US</language>
<copyright>https://creativecommons.org/publicdomain/zero/1.0/</copyright>
<lastBuildDate><?= (new DateTime())->format('r') ?></lastBuildDate>
<docs>http://blogs.law.harvard.edu/tech/rss</docs>
<atom:link href="<?= SITE_URL ?>/feeds/rss/all?query=<?= urlencode($query) ?>" rel="self" type="application/rss+xml"/>
<atom:link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml" />
<image>
<url><?= SITE_URL ?>/images/logo-rss.png</url>
<title>Search Results</title> <? /* must be identical to channel title */ ?>
<description>The Standard Ebooks logo</description>
<link><?= SITE_URL ?></link>
<height>144</height>
<width>144</width>
</image>
<opensearch:totalResults><?= sizeof($ebooks) ?></opensearch:totalResults>
<? foreach($ebooks as $ebook){ ?>
<?= Template::RssEntry(['entry' => $ebook]) ?>
<? } ?>
</channel>
</rss>

View file

@ -10,7 +10,14 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
<xsl:template match="/">
<?= Template::Header(['xmlDeclaration' => false]) ?>
<main class="opds">
<xsl:choose>
<xsl:when test="contains(/rss/channel/title, 'Standard Ebooks - ')">
<h1><xsl:value-of select="substring-after(/rss/channel/title, 'Standard Ebooks - ')"/></h1>
</xsl:when>
<xsl:otherwise>
<h1><xsl:value-of select="/rss/channel/title"/></h1>
</xsl:otherwise>
</xsl:choose>
<p><xsl:value-of select="/rss/channel/description"/></p>
<p>This page is an RSS 2.0 feed. The URL in your browsers address bar (<a class="url"><xsl:attribute name="href"><xsl:value-of select="/rss/channel/atom:link/@href"/></xsl:attribute><xsl:value-of select="/rss/channel/atom:link/@href"/></a>) can be used in any RSS reader.</p>
<ol class="ebooks-list list rss">