Move feeds behind Patrons Circle login

This commit is contained in:
Alex Cabal 2022-07-08 12:01:45 -05:00
parent 0a32b295b0
commit 6c603016bd
20 changed files with 367 additions and 183 deletions

View file

@ -6,7 +6,7 @@ PHP 7+ is required.
```shell
# Install Apache, PHP, PHP-FPM, and various other dependencies.
sudo apt install -y git composer php-fpm php-cli php-gd php-xml php-apcu php-mbstring php-intl php-curl php-zip apache2 apache2-utils libfcgi0ldbl task-spooler ipv6calc mariadb-server
sudo apt install -y git composer php-fpm php-cli php-gd php-xml php-apcu php-mbstring php-intl php-curl php-zip apache2 apache2-utils libfcgi0ldbl task-spooler ipv6calc mariadb-server libaprutil1-dbd-mysql
# Create the site root and logs root and clone this repo into it.
sudo mkdir /standardebooks.org/
@ -26,7 +26,7 @@ echo -e "127.0.0.1\tstandardebooks.test" | sudo tee -a /etc/hosts
openssl req -x509 -nodes -days 99999 -newkey rsa:4096 -subj "/CN=standardebooks.test" -keyout /standardebooks.org/web/config/ssl/standardebooks.test.key -sha256 -out /standardebooks.org/web/config/ssl/standardebooks.test.crt
# Enable the necessary Apache modules.
sudo a2enmod headers expires ssl rewrite proxy proxy_fcgi
sudo a2enmod headers expires ssl rewrite proxy proxy_fcgi authn_dbd
# Link and enable the SE Apache configuration file.
sudo ln -s /standardebooks.org/web/config/apache/standardebooks.test.conf /etc/apache2/sites-available/

View file

@ -50,6 +50,23 @@ SSLStaplingReturnResponderErrors off
Define domain standardebooks.org
Define webroot /standardebooks.org/web
<VirtualHost *:80>
ServerName standardebooks.com
ServerAlias www.standardebooks.com
RedirectPermanent / https://${domain}/
</VirtualHost>
<VirtualHost *:443>
ServerName standardebooks.com
ServerAlias www.standardebooks.com
RedirectPermanent / https://${domain}/
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/standardebooks.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/standardebooks.com/privkey.pem
Header always set Strict-Transport-Security "max-age=15768000"
</VirtualHost>
<VirtualHost *:80>
ServerName ${domain}
ServerAlias www.${domain}
@ -72,7 +89,7 @@ Define webroot /standardebooks.org/web
Header always set Strict-Transport-Security "max-age=15768000"
Header set Content-Security-Policy "default-src 'self';"
<Directory ${webroot}/www/>
<Directory "${webroot}/www/">
# Disable .htaccess files
AllowOverride none
@ -88,43 +105,31 @@ Define webroot /standardebooks.org/web
AddType application/x-mobi8-ebook .azw3
<Location ~ ^/ebooks/.+?/downloads/.+$>
<LocationMatch "^/ebooks/.+?/downloads/.+$">
# Serve distributables using the "download" dialog instead of opening in-browser
# Note: the trailing e in the Header directive is required
# In modern browsers this is handled with the a@download attribute, we keep this here for compatibility
SetEnvIf Request_URI ^/ebooks/.+?/downloads/(.+)$ FILENAME=$1
Header set Content-Disposition "attachment; filename=%{FILENAME}e"
</Location>
</LocationMatch>
# We explicitly set the content-type for items in the /vocab/ directory, because Apache doesn't set it for us,
# and we need a content-type header when using the "nosniff" header. See https://bugzilla.mozilla.org/show_bug.cgi?id=1547076
<Location ~ ^/vocab/.+$>
<LocationMatch "^/vocab/.+$">
Header set Content-Type "text/plain"
</location>
<Location ~ ^/feeds/opds>
DirectoryIndex index.xml
</location>
# 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.
<Directory ~ ${webroot}/www/feeds/>
<Files ~ (\.xml|search\.php)$>
Header set Content-Type "text/xml; charset=utf-8"
</Files>
</Directory>
</LocationMatch>
# Enable HTTP CORS so that browser-based readers like Readium can access opds and ebooks
# Allow fonts for newsletter emails
# See https://github.com/standardebooks/tools/issues/2
<Location ~ /(ebooks|feeds/opds|fonts)>
<LocationMatch "/(ebooks|feeds/opds|fonts)">
Header set Access-Control-Allow-Origin "*"
</Location>
</LocationMatch>
# We use a different CSP policy for single-page files because our default one doesn't allow inline images or CSS
<Location ~ /text/single-page$>
<LocationMatch "/text/single-page$">
Header set Content-Security-Policy "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline';"
</Location>
</LocationMatch>
# Remove www from requests
RewriteCond %{HTTP_HOST} ^www\.(.+) [NC]
@ -137,13 +142,13 @@ Define webroot /standardebooks.org/web
SetEnv proxy-sendcl 1
# Forward all PHP requests to the php-fpm pool for this domain.
<FilesMatch \.php$>
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php/${domain}.sock|fcgi://${domain}"
Header set Cache-Control "no-store"
</FilesMatch>
# Set some proxy properties.
<Proxy fcgi://${domain}>
<Proxy "fcgi://${domain}">
ProxySet connectiontimeout=5 timeout=240
</Proxy>
@ -190,11 +195,11 @@ Define webroot /standardebooks.org/web
# Redirect index pages
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}/index.php -f
RewriteRule (.*) /$1/index.php
RewriteRule (.*) $1/index.php
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}/index.xml -f
RewriteRule (.*) /$1/index.xml
RewriteRule (.*) $1/index.xml
# Remove newline characters inserted by accident in some email clients
RewriteRule ^(.*)\r\n[\ ]?(.*)$ $1$2 [R=301,N]
@ -248,13 +253,6 @@ Define webroot /standardebooks.org/web
RewriteCond %{REQUEST_FILENAME} !^/ebooks/.+?/text.*$
RewriteRule ^/ebooks/([^\.]+?)$ /ebooks/ebook.php?url-path=$1
# If we ask for /opds/all?query=xyz, rewrite that to the search page.
RewriteCond %{QUERY_STRING} ^query=
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]
# Newsletter
RewriteRule ^/newsletter$ /newsletter/subscriptions/new.php [L]
RewriteRule ^/newsletter/subscriptions/([^/\.]+?)$ /newsletter/subscriptions/get.php?uuid=$1 [L]
@ -270,21 +268,65 @@ Define webroot /standardebooks.org/web
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
RewriteRule ^/patrons-circle/polls/([^/\.]+)/votes$ /patrons-circle/polls/votes/post.php?pollurlname=$1 [L]
</VirtualHost>
<VirtualHost *:80>
ServerName standardebooks.com
ServerAlias www.standardebooks.com
RedirectPermanent / https://${domain}/
</VirtualHost>
# Feeds
# Rewrite old links to feeds
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
<VirtualHost *:443>
ServerName standardebooks.com
ServerAlias www.standardebooks.com
RedirectPermanent / https://${domain}/
# If we ask for /opds/all?query=xyz, rewrite that to the search page.
RewriteCond %{QUERY_STRING} \bquery=
RewriteRule ^/feeds/(opds|atom|rss)/all.xml$ /feeds/$1/search.php [QSA]
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/standardebooks.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/standardebooks.com/privkey.pem
Header always set Strict-Transport-Security "max-age=15768000"
# Enable mod_authn_dbd
DBDriver mysql
DBDParams "dbname=se user=www-data"
<DirectoryMatch "^${webroot}/www/feeds/(opds|rss|atom).*">
# Enable HTTP Basic auth for feeds
AuthType Basic
AuthName "Enter your Patrons Circle email address or your API key, and a blank password."
Require valid-user
ErrorDocument 401 /feeds/401
# Credentials caching to prevent slamming the DB. socache must be ahead of dbd
AuthBasicProvider socache dbd
AuthnCacheProvideFor dbd
AuthnCacheContext ${domain}
# mod_authn_dbd SQL query to authenticate a user
# The hash is simply the hash of a blank password. We're only interested in the username/API key.
# We have to do this tortured query instead of a cleaner one, because the AuthDBDUserPWQuery
# function will only replace %s EXACTLY ONCE. We cannot have more than one %s in the query string.
AuthDBDUserPWQuery "\
select '$apr1$13q1pnGf$vQnIj94BXP1EPdL/4ISba.' from \
( \
select Email, Uuid from Patrons p inner join Users u using (UserId) where p.Ended is null \
union \
select Email, Uuid from FeedUsers fu inner join Users u using (UserId) where fu.Ended is null \
) x where %s in (Email, Uuid) limit 1 \
"
<FilesMatch "^(style\.php|new-releases\.xml|index\.php|index\.xml)$">
# Disable HTTP Basic auth for the feed XSL stylesheet and the new releases feeds
Require all granted
</FilesMatch>
</DirectoryMatch>
# Emit content-types for OPDS feeds, as some clients require a strictly correct content-type in order to work
<DirectoryMatch "^${webroot}/www/feeds/opds">
Header set Content-Type "application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8"
<FilesMatch "^index\.xml$">
Header set Content-Type "application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8"
</FilesMatch>
</DirectoryMatch>
# Emit content-types for RSS/Atom feeds
<DirectoryMatch "^${webroot}/www/feeds/(?<feedtype>rss|atom)/.+">
# Note the trailing e is required to complete the environmental variable reference
Header set Content-Type "application/%{MATCH_FEEDTYPE}e+xml; charset=utf-8"
<FilesMatch "^index\.php$">
Header set Content-Type "application/xhtml+xml; charset=utf-8"
</FilesMatch>
</DirectoryMatch>
</VirtualHost>

View file

@ -71,7 +71,7 @@ Define webroot /standardebooks.org/web
Header always set Strict-Transport-Security "max-age=15768000"
Header set Content-Security-Policy "default-src 'self';"
<Directory ${webroot}/www/>
<Directory "${webroot}/www/">
# Disable .htaccess files
AllowOverride none
@ -87,43 +87,31 @@ Define webroot /standardebooks.org/web
AddType application/x-mobi8-ebook .azw3
<Location ~ ^/ebooks/.+?/downloads/.+$>
<LocationMatch "^/ebooks/.+?/downloads/.+$">
# Serve distributables using the "download" dialog instead of opening in-browser
# Note: the trailing e in the Header directive is required
# In modern browsers this is handled with the a@download attribute, we keep this here for compatibility
SetEnvIf Request_URI ^/ebooks/.+?/downloads/(.+)$ FILENAME=$1
Header set Content-Disposition "attachment; filename=%{FILENAME}e"
</Location>
</LocationMatch>
# We explicitly set the content-type for items in the /vocab/ directory, because Apache doesn't set it for us,
# and we need a content-type header when using the "nosniff" header. See https://bugzilla.mozilla.org/show_bug.cgi?id=1547076
<Location ~ ^/vocab/.+$>
<LocationMatch "^/vocab/.+$">
Header set Content-Type "text/plain"
</location>
<Location ~ ^/feeds/opds>
DirectoryIndex index.xml
</location>
# 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.
<Directory ~ ${webroot}/www/feeds/>
<Files ~ (\.xml|search\.php)$>
Header set Content-Type "text/xml; charset=utf-8"
</Files>
</Directory>
</LocationMatch>
# Enable HTTP CORS so that browser-based readers like Readium can access opds and ebooks
# Allow fonts for newsletter emails
# See https://github.com/standardebooks/tools/issues/2
<Location ~ /(ebooks|feeds/opds|fonts)>
<LocationMatch "/(ebooks|feeds/opds|fonts)">
Header set Access-Control-Allow-Origin "*"
</Location>
</LocationMatch>
# We use a different CSP policy for single-page files because our default one doesn't allow inline images or CSS
<Location ~ /text/single-page$>
<LocationMatch "/text/single-page$">
Header set Content-Security-Policy "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline';"
</Location>
</LocationMatch>
# Remove www from requests
RewriteCond %{HTTP_HOST} ^www\.(.+) [NC]
@ -136,13 +124,13 @@ Define webroot /standardebooks.org/web
SetEnv proxy-sendcl 1
# Forward all PHP requests to the php-fpm pool for this domain.
<FilesMatch \.php$>
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php/${domain}.sock|fcgi://${domain}"
Header set Cache-Control "no-store"
</FilesMatch>
# Set some proxy properties.
<Proxy fcgi://${domain}>
<Proxy "fcgi://${domain}">
ProxySet connectiontimeout=5 timeout=240
</Proxy>
@ -189,11 +177,11 @@ Define webroot /standardebooks.org/web
# Redirect index pages
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}/index.php -f
RewriteRule (.*) /$1/index.php
RewriteRule (.*) $1/index.php
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}/index.xml -f
RewriteRule (.*) /$1/index.xml
RewriteRule (.*) $1/index.xml
# Remove newline characters inserted by accident in some email clients
RewriteRule ^(.*)\r\n[\ ]?(.*)$ $1$2 [R=301,N]
@ -247,13 +235,6 @@ Define webroot /standardebooks.org/web
RewriteCond %{REQUEST_FILENAME} !^/ebooks/.+?/text.*$
RewriteRule ^/ebooks/([^\.]+?)$ /ebooks/ebook.php?url-path=$1
# If we ask for /opds/all?query=xyz, rewrite that to the search page.
RewriteCond %{QUERY_STRING} ^query=
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]
# Newsletter
RewriteRule ^/newsletter$ /newsletter/subscriptions/new.php [L]
RewriteRule ^/newsletter/subscriptions/([^/\.]+?)$ /newsletter/subscriptions/get.php?uuid=$1 [L]
@ -269,4 +250,65 @@ Define webroot /standardebooks.org/web
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
RewriteRule ^/patrons-circle/polls/([^/\.]+)/votes$ /patrons-circle/polls/votes/post.php?pollurlname=$1 [L]
# Feeds
# Rewrite old links to feeds
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
# If we ask for /opds/all?query=xyz, rewrite that to the search page.
RewriteCond %{QUERY_STRING} \bquery=
RewriteRule ^/feeds/(opds|atom|rss)/all.xml$ /feeds/$1/search.php [QSA]
# Enable mod_authn_dbd
DBDriver mysql
DBDParams "dbname=se user=www-data"
<DirectoryMatch "^${webroot}/www/feeds/(opds|rss|atom).*">
# Enable HTTP Basic auth for feeds
AuthType Basic
AuthName "Enter your Patrons Circle email address or your API key, and a blank password."
Require valid-user
ErrorDocument 401 /feeds/401
# Credentials caching to prevent slamming the DB. socache must be ahead of dbd
AuthBasicProvider socache dbd
AuthnCacheProvideFor dbd
AuthnCacheContext ${domain}
# mod_authn_dbd SQL query to authenticate a user
# The hash is simply the hash of a blank password. We're only interested in the username/API key.
# We have to do this tortured query instead of a cleaner one, because the AuthDBDUserPWQuery
# function will only replace %s EXACTLY ONCE. We cannot have more than one %s in the query string.
AuthDBDUserPWQuery "\
select '$apr1$13q1pnGf$vQnIj94BXP1EPdL/4ISba.' from \
( \
select Email, Uuid from Patrons p inner join Users u using (UserId) where p.Ended is null \
union \
select Email, Uuid from FeedUsers fu inner join Users u using (UserId) where fu.Ended is null \
) x where %s in (Email, Uuid) limit 1 \
"
<FilesMatch "^(style\.php|new-releases\.xml|index\.php|index\.xml)$">
# Disable HTTP Basic auth for the feed XSL stylesheet and the new releases feeds
Require all granted
</FilesMatch>
</DirectoryMatch>
# Emit content-types for OPDS feeds, as some clients require a strictly correct content-type in order to work
<DirectoryMatch "^${webroot}/www/feeds/opds">
Header set Content-Type "application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8"
<FilesMatch "^index\.xml$">
Header set Content-Type "application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8"
</FilesMatch>
</DirectoryMatch>
# Emit content-types for RSS/Atom feeds
<DirectoryMatch "^${webroot}/www/feeds/(?<feedtype>rss|atom)/.+">
# Note the trailing e is required to complete the environmental variable reference
Header set Content-Type "application/%{MATCH_FEEDTYPE}e+xml; charset=utf-8"
<FilesMatch "^index\.php$">
Header set Content-Type "application/xhtml+xml; charset=utf-8"
</FilesMatch>
</DirectoryMatch>
</VirtualHost>

View file

@ -0,0 +1,7 @@
CREATE TABLE `FeedUsers` (
`UserId` int(10) unsigned NOT NULL,
`Created` datetime NOT NULL,
`Ended` datetime DEFAULT NULL,
`Notes` text DEFAULT NULL,
KEY `idxUserId` (`UserId`,`Ended`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View file

@ -5,6 +5,6 @@ CREATE TABLE `Patrons` (
`IsSubscribedToEmails` tinyint(1) NOT NULL DEFAULT 1,
`Created` datetime NOT NULL,
`Ended` datetime DEFAULT NULL,
PRIMARY KEY (`UserId`),
KEY `index2` (`IsAnonymous`,`Ended`)
KEY `index2` (`IsAnonymous`,`Ended`),
KEY `index1` (`UserId`,`Ended`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View file

@ -107,7 +107,7 @@ $opdsRootEntries = [
'acquisition')
];
$opdsRoot = new OpdsNavigationFeed('Standard Ebooks', 'The navigation root for the Standard Ebooks OPDS feed.', '/feeds/opds', WEB_ROOT . '/feeds/opds/index.xml', $opdsRootEntries, null);
$opdsRoot = new OpdsNavigationFeed('Standard Ebooks', 'The Standard Ebooks catalog.', '/feeds/opds', WEB_ROOT . '/feeds/opds/index.xml', $opdsRootEntries, null);
SaveFeed($opdsRoot, $force, $now);
// Create the subjects navigation document

19
templates/FeedHowTo.php Normal file
View file

@ -0,0 +1,19 @@
<section id="accessing-the-feeds">
<h2>Accessing the feeds</h2>
<p>Our New Releases feeds are accessible by the public. Access to our other, more detailed feeds is available to our supporters in the <a href="/donate#patrons-circle">Patrons Circle</a>.</p>
<section id="individuals">
<h3>Individuals</h3>
<ul>
<li><p>Join the <a href="/donate#patrons-circle">Patrons Circle</a> by making a donation to get access to all of our ebook feeds for the duration of your gift.</p></li>
<li><p><a href="/contribute">Produce an ebook</a> for Standard Ebooks to get lifetime access to our ebook feeds. If youve already done that, <a href="/about#editor-in-chief">contact us</a> to enable your access.</p></li>
</ul>
<p>To access a feed, when prompted enter the email address you used to make your donation or on the S.E. mailing list, and leave the password field blank.</p>
</section>
<section id="organizations-and-projects">
<h3>Organizations and projects</h3>
<ul>
<li><p><a href="/donate#corporate-sponsors">Corporate sponsors</a> get access to all of our ebook feeds for the duration of their sponsorship. <a href="/about#editor-in-chief">Contact us</a> to chat about having your organization sponsor our mission.</p></li>
<li><p>Software projects that are open-source and not-for-profit may be granted free access to our feeds. <a href="/about#editor-in-chief">Contact us</a> to inquire.</p></li>
</ul>
</section>
</section>

View file

@ -8,7 +8,7 @@ $colorScheme = $_COOKIE['color-scheme'] ?? 'auto';
$xmlDeclaration = $xmlDeclaration ?? true;
if($xmlDeclaration){
header('content-type: application/xhtml+xml');
header('content-type: application/xhtml+xml; charset=utf-8');
print('<?xml version="1.0" encoding="utf-8"?>');
print("\n");
print("<!DOCTYPE html>\n");

View file

@ -32,5 +32,5 @@
<? if(file_exists(WEB_ROOT . $entry->AdvancedEpubUrl)){ ?><link href="<?= SITE_URL . $entry->AdvancedEpubUrl ?>" length="<?= filesize(WEB_ROOT . $entry->AdvancedEpubUrl) ?>" rel="http://opds-spec.org/acquisition/open-access" 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="http://opds-spec.org/acquisition/open-access" 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="http://opds-spec.org/acquisition/open-access" 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="http://opds-spec.org/acquisition/open-access" title="XHTML" type="application/xhtml+xml" /><? } ?>
<? if(file_exists(WEB_ROOT . $entry->TextSinglePageUrl)){ ?><link href="<?= SITE_URL . $entry->TextSinglePageUrl ?>" length="<?= filesize(WEB_ROOT . $entry->TextSinglePageUrl) ?>" rel="http://opds-spec.org/acquisition/open-access" title="XHTML" type="application/xhtml+xml; charset=utf-8" /><? } ?>
</entry>

View file

@ -18,11 +18,11 @@ 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"<? } ?>>
<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 ?>/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 ?>/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 . Formatter::ToPlainXmlText($url) ?>" rel="self" type="application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8"/>
<link href="<?= SITE_URL ?>/feeds/opds" rel="start" type="application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8"/>
<link href="<?= SITE_URL ?><?= Formatter::ToPlainXmlText($parentUrl) ?>" rel="up" type="application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8"/>
<link href="<?= SITE_URL ?>/feeds/opds/all" rel="http://opds-spec.org/crawlable" type="application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8"/>
<link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml; charset=utf-8"/>
<title><?= Formatter::ToPlainXmlText($title) ?></title>
<? if($subtitle !== null){ ?><subtitle><?= Formatter::ToPlainXmlText($subtitle) ?></subtitle><? } ?>
<icon><?= SITE_URL ?>/images/logo.png</icon>

View file

@ -9,11 +9,11 @@ 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/">
<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 ?>/feeds/opds" rel="start" type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<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"/>
<? if($parentUrl !== null){ ?><link href="<?= SITE_URL ?><?= Formatter::ToPlainXmlText($parentUrl) ?>" rel="up" 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; charset=utf-8"/>
<link href="<?= SITE_URL ?>/feeds/opds" rel="start" type="application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8"/>
<link href="<?= SITE_URL ?>/feeds/opds/all" rel="http://opds-spec.org/crawlable" type="application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8"/>
<link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml; charset=utf-8"/>
<? if($parentUrl !== null){ ?><link href="<?= SITE_URL ?><?= Formatter::ToPlainXmlText($parentUrl) ?>" rel="up" type="application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8"/><? } ?>
<title><?= Formatter::ToPlainXmlText($title) ?></title>
<? if($subtitle !== null){ ?><subtitle><?= Formatter::ToPlainXmlText($subtitle) ?></subtitle><? } ?>
<icon><?= SITE_URL ?>/images/logo.png</icon>
@ -25,7 +25,7 @@ print("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
<? foreach($entries as $entry){ ?>
<entry>
<title><?= Formatter::ToPlainXmlText($entry->Title) ?></title>
<link href="<?= SITE_URL . Formatter::ToPlainXmlText($entry->Url) ?>" rel="<?= Formatter::ToPlainXmlText($entry->Rel) ?>" type="application/atom+xml;profile=opds-catalog;kind=<?= $entry->Type ?>"/>
<link href="<?= SITE_URL . Formatter::ToPlainXmlText($entry->Url) ?>" rel="<?= Formatter::ToPlainXmlText($entry->Rel) ?>" type="application/atom+xml;profile=opds-catalog;kind=<?= $entry->Type ?>; charset=utf-8"/>
<updated><? if($entry->Updated !== null){ ?><?= $entry->Updated->format('Y-m-d\TH:i:s\Z') ?><? } ?></updated>
<id><?= Formatter::ToPlainXmlText($entry->Id) ?></id>
<content type="text"><?= Formatter::ToPlainXmlText($entry->Description) ?></content>

View file

@ -1,7 +1,7 @@
FROM ubuntu:20.04
RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y composer php-fpm php-cli php-gd php-xml php-apcu php-mbstring php-intl apache2 apache2-utils libfcgi0ldbl task-spooler
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y composer php-fpm php-cli php-gd php-xml php-apcu php-mbstring php-intl apache2 apache2-utils libfcgi0ldbl task-spooler libaprutil1-dbd-mysql
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y sudo imagemagick openjdk-8-jre python3 pip calibre
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
@ -14,7 +14,7 @@ RUN sudo usermod -g committers se
RUN mkdir -p /standardebooks.org/web
RUN mkdir /var/log/local
RUN a2enmod headers expires ssl rewrite proxy proxy_fcgi
RUN a2enmod headers expires ssl rewrite proxy proxy_fcgi authn_dbd
# Disable opcaching for dynamic PHP reloading
RUN echo "opcache.enable=0" >> /etc/php/7.4/fpm/php.ini

View file

@ -27,15 +27,30 @@ require_once('Core.php');
<h2>Join the Patrons Circle</h2>
<p>Members of the Patrons Circle are steadfast supporters of free, unrestricted, and beautifully presented digital literature. Besides helping support the creation of free, gorgeous ebooks, they also have a direct voice in shaping the future of the Standard Ebooks catalog.</p>
<p><i>Join the Patrons Circle with a donation of $10/month or more, or join for one year with a one-time donation of $100 or more.</i></p>
<p>During your time as a member of the Patrons Circle, you get:</p>
<ul>
<li>
<p>Patrons Circle members have their name <a href="/about#patrons-circle">listed on our masthead</a> for the duration of their gift.</p>
<p>Your name <a href="/about#patrons-circle">listed on our masthead</a>.</p>
</li>
<li>
<p>Once per quarter, Patrons Circle members may submit a book for inclusion on our <a href="/contribute/wanted-ebooks">Wanted Ebooks list</a>. (Submissions must conform to our <a href="/contribute/collections-policy">collections policy</a> and are subject to approval.)</p>
<p>Access to our various <a href="/feeds">ebook feeds</a>:</p>
<ul>
<li>
<p>Browse and download from the entire Standard Ebooks catalog directly in your ereading app using our <a href="/feeds/opds">OPDS feed</a>.</p>
</li>
<li>
<p><strong>Periodically, members of the Patrons Circle vote on a selection from our <a href="/contribute/wanted-ebooks">Wanted Ebooks list</a> to choose an ebook for immediate production.</strong> The resulting ebook will be a permanent addition to our <a href="/ebooks">online catalog of free digital literature</a>.</p>
<p>Get notified of new ebooks in your news client with our <a href="/feeds/atom">Atom</a> or <a href="/feeds/rss">RSS</a> feeds.</p>
</li>
<li>
<p>Parse and process the feeds to use our ebooks in your personal software projects. (Organizations, see <a href="#corporate-sponsors">corporate sponsorship</a> instead.)</p>
</li>
</ul>
</li>
<li>
<p>The ability to submit a book for inclusion on our <a href="/contribute/wanted-ebooks">Wanted Ebooks list</a>, once per quarter. (Submissions must conform to our <a href="/contribute/collections-policy">collections policy</a> and are subject to approval.)</p>
</li>
<li>
<p><strong>The right to periodically vote on a selection from our <a href="/contribute/wanted-ebooks">Wanted Ebooks list</a> to choose an ebook for immediate production.</strong> The resulting ebook will be a permanent addition to our <a href="/ebooks">online catalog of free digital literature</a>.</p>
</li>
</ul>
<p class="button-row">
@ -118,13 +133,13 @@ require_once('Core.php');
</section>
<section id="corporate-sponsors">
<h2>Corporate sponsorships</h2>
<p>Sponsorships at the corporate level are a great way to show your organizations commitment to the support of the literate arts.</p>
<p>Sponsorships at the corporate level are a great way to show your organizations commitment to supporting the literate arts.</p>
<ul>
<li>
<p>Your organizations logo and a link will be <a href="/about#corporate-sponsors">listed on our masthead</a>, which is prominently linked to on the Standard Ebooks websites header and footer.</p>
</li>
<li>
<p>Get customized <abbr class="acronym">ONIX</abbr>, OPDS, or RSS feeds of our ebook catalog for use by your organization for the duration of your sponsorship.</p>
<p>Get access to our OPDS, Atom, and RSS <a href="/feeds">ebook feeds</a> for use by your organization for the duration of your sponsorship. We can also produce different kinds of feeds to meet your needs, like <abbr class="acronym">ONIX</abbr> feeds.</p>
</li>
</ul>
<p>To inquire about sponsorship options, contact the <a href="/about#editor-in-chief">Standard Ebooks Editor-in-Chief</a>.</p>

46
www/feeds/401.php Normal file
View file

@ -0,0 +1,46 @@
<?
require_once('Core.php');
use function Safe\preg_match;
$type = '';
preg_match('/^\/feeds\/(opds|rss|atom)/ius', $_SERVER['REQUEST_URI'], $matches);
if(sizeof($matches) > 0){
$type = $matches[1];
}
$title = 'Standard Ebooks Ebook Feeds';
if($type == 'opds'){
$title = 'The Standard Ebooks OPDS Feed';
}
if($type == 'rss'){
$title = 'Standard Ebooks RSS Feeds';
}
if($type == 'atom'){
$title = 'Standard Ebooks Atom Feeds';
}
?><?= Template::Header(['title' => 'The Standard Ebooks OPDS feed', 'highlight' => '', 'description' => 'Get access to the Standard Ebooks OPDS feed for use in ereading programs in scripting.']) ?>
<main>
<section class="narrow">
<? if($type == 'opds'){ ?>
<h1>The Standard Ebooks OPDS Feed</h1>
<p><a href="https://en.wikipedia.org/wiki/Open_Publication_Distribution_System">OPDS feeds</a> are designed for use with ereading apps on your phone or tablet, or with ereading systems like <a href="http://koreader.rocks/">KOreader</a>. Add our OPDS feed to your ereading app to search, browse, and download from our entire catalog, directly in your ereader.</p>
<p>Theyre also perfect for scripting, or for libraries or other organizations who wish to download and process our catalog of ebooks.</p>
<? }elseif($type == 'rss'){ ?>
<h1>Standard Ebooks RSS Feeds</h1>
<p>RSS feeds are an alternative to <a href="/feeds/atom">Atom feeds</a>. They contain less information than Atom feeds, but might be better supported by some RSS readers.</p>
<? }elseif($type == 'atom'){ ?>
<h1>Standard Ebooks Atom Feeds</h1>
<p>Atom feeds can be read by one of the many <a href="https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators">RSS clients</a> available for download, like <a href="https://www.thunderbird.net/en-US/">Thunderbird</a>. They contain more information than regular RSS feeds. Most RSS clients can read both Atom and RSS feeds.</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>
<? }else{ ?>
<h1>Standard Ebooks Ebook Feeds</h1>
<? } ?>
<?= Template::FeedHowTo() ?>
</section>
</main>
<?= Template::Footer() ?>

View file

@ -1,17 +1,20 @@
<?
require_once('Core.php');
?><?= Template::Header(['title' => 'Atom Ebook Feeds', 'description' => 'A list of available Atom 1.0 feeds of Standard Ebooks ebooks.']) ?>
?><?= Template::Header(['title' => 'Atom 1.0 Ebook Feeds', 'description' => 'A list of available Atom 1.0 feeds of Standard Ebooks ebooks.']) ?>
<main>
<article>
<h1>Atom 1.0 Feeds</h1>
<p>Atom feeds can be read by one of the many <a href="https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators">RSS clients</a> available for download, like <a href="https://www.thunderbird.net/en-US/">Thunderbird</a>. They contain more information than regular RSS feeds. Most RSS clients can read both Atom and RSS feeds.</p>
<section class="narrow">
<h1>Atom 1.0 Ebook Feeds</h1>
<p>Atom feeds can be read by one of the many <a href="https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators">RSS clients</a> available for download, like <a href="https://www.thunderbird.net/en-US/">Thunderbird</a>. They contain more information than regular <a href="/feeds/rss">RSS feeds</a>. Most RSS clients can read both Atom and RSS feeds.</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>
<?= Template::FeedHowTo() ?>
<section id="atom-feeds">
<h2>Atom Feeds</h2>
<ul class="feed">
<li>
<p><a href="/feeds/atom/new-releases">New releases</a></p>
<p><a href="/feeds/atom/new-releases">New releases</a> (Public)</p>
<p class="url"><?= SITE_URL ?>/feeds/atom/new-releases</p>
<p>The thirty latest Standard Ebooks, most-recently-released first.</p>
<p>The fifteen latest Standard Ebooks, most-recently-released first.</p>
</li>
<li>
<p><a href="/feeds/atom/all">All ebooks</a></p>
@ -30,6 +33,7 @@ require_once('Core.php');
<? } ?>
</ul>
</section>
</article>
</section>
</section>
</main>
<?= Template::Footer() ?>

View file

@ -1,10 +1,13 @@
<?
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.']) ?>
?><?= Template::Header(['title' => 'Atom 1.0 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>
<h1>Atom 1.0 Ebook Feeds by Subject</h1>
<?= Template::FeedHowTo() ?>
<section id="atom-feeds">
<h2>Atom Feeds</h2>
<ul class="feed">
<? foreach(SE_SUBJECTS as $subject){ ?>
<li>
@ -13,6 +16,7 @@ require_once('Core.php');
</li>
<? } ?>
</ul>
</section>
</article>
</main>
<?= Template::Footer() ?>

View file

@ -10,19 +10,21 @@ require_once('Core.php');
?><?= Template::Header(['title' => 'Ebook Feeds', 'description' => 'A list of available feeds of Standard Ebooks ebooks.']) ?>
<main>
<article>
<section class="narrow">
<h1>Ebook Feeds</h1>
<p>We offers several feeds that you can use to get notified about new ebooks, or to browse and download from our catalog directly in your ereader.</p>
<p>We offer several ebook feeds that you can use in your ereading app to browse, search, and download from our catalog. You can also add our feeds to your RSS client to get notified of new ebooks as theyre released.</p>
<?= Template::FeedHowTo() ?>
<section id="opds-feeds">
<h2>OPDS 1.2 feeds</h2>
<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 apps on your phone or tablet, or with ereading systems like <a href="http://koreader.rocks/">KOreader</a>. Add our OPDS feed to your ereading app to search, browse, and download from our entire catalog, directly in your ereader.</p>
<p>Theyre also perfect for scripting, or for libraries or other organizations who wish to download and process our catalog of ebooks.</p>
<ul class="feed">
<li>
<p><a href="/feeds/opds">The Standard Ebooks OPDS feed</a></p>
<p class="url"><?= SITE_URL ?>/feeds/opds</p>
</li>
</ul>
<section>
<section id="opds-how-tos-and-resources">
<h3>OPDS how-tos and resources</h3>
<ul>
<li>
@ -40,9 +42,9 @@ 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>
<ul class="feed">
<li>
<p><a href="/feeds/atom/new-releases">New releases</a></p>
<p><a href="/feeds/atom/new-releases">New releases</a> (Public)</p>
<p class="url"><?= SITE_URL ?>/feeds/atom/new-releases</p>
<p>The thirty latest Standard Ebooks, most-recently-released first.</p>
<p>The fifteen latest Standard Ebooks, most-recently-released first.</p>
</li>
<li>
<p><a href="/feeds/atom/all">All ebooks</a></p>
@ -67,9 +69,9 @@ 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>
<ul class="feed">
<li>
<p><a href="/feeds/rss/new-releases">New releases</a></p>
<p><a href="/feeds/rss/new-releases">New releases</a> (Public)</p>
<p class="url"><?= SITE_URL ?>/feeds/rss/new-releases</p>
<p>The thirty latest Standard Ebooks, most-recently-released first.</p>
<p>The fifteen latest Standard Ebooks, most-recently-released first.</p>
</li>
<li>
<p><a href="/feeds/rss/all">All ebooks</a></p>
@ -89,6 +91,6 @@ require_once('Core.php');
</ul>
</section>
</section>
</article>
</section>
</main>
<?= Template::Footer() ?>

View file

@ -13,18 +13,17 @@ try{
}
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/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/">
<id><?= SITE_URL ?>/feeds/opds/all?query=<?= urlencode($query) ?></id>
<link href="<?= SITE_URL ?>/feeds/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; charset=utf-8"/>
<link href="<?= SITE_URL ?>/ebooks/ebooks?query=<?= urlencode($query) ?>" rel="alternate" type="text/html"/>
<link href="<?= SITE_URL ?>/feeds/opds" rel="start" type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<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 ?>/feeds/opds" rel="start" type="application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8"/>
<link href="<?= SITE_URL ?>/feeds/opds/all" rel="http://opds-spec.org/crawlable" type="application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8"/>
<link href="<?= SITE_URL ?>/ebooks/opensearch" rel="search" type="application/opensearchdescription+xml; charset=utf-8"/>
<title>Search Results</title>
<subtitle>Results for <?= Formatter::ToPlainXmlText($query) ?>”.</subtitle>
<icon><?= SITE_URL ?>/images/logo.png</icon>

View file

@ -1,16 +1,19 @@
<?
require_once('Core.php');
?><?= Template::Header(['title' => 'RSS Ebook Feeds', 'description' => 'A list of available RSS 2.0 feeds of Standard Ebooks ebooks.']) ?>
?><?= Template::Header(['title' => 'RSS 2.0 Ebook Feeds', 'description' => 'A list of available RSS 2.0 feeds of Standard Ebooks ebooks.']) ?>
<main>
<article>
<h1>RSS 2.0 feeds</h1>
<section class="narrow">
<h1>RSS 2.0 Ebook Feeds</h1>
<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>
<?= Template::FeedHowTo() ?>
<section id="rss-feeds">
<h2>RSS Feeds</h2>
<ul class="feed">
<li>
<p><a href="/feeds/rss/new-releases">New releases</a></p>
<p><a href="/feeds/rss/new-releases">New releases</a> (Public)</p>
<p class="url"><?= SITE_URL ?>/feeds/rss/new-releases</p>
<p>The thirty latest Standard Ebooks, most-recently-released first.</p>
<p>The fifteen latest Standard Ebooks, most-recently-released first.</p>
</li>
<li>
<p><a href="/feeds/rss/all">All ebooks</a></p>
@ -29,6 +32,7 @@ require_once('Core.php');
<? } ?>
</ul>
</section>
</article>
</section>
</section>
</main>
<?= Template::Footer() ?>

View file

@ -1,10 +1,10 @@
<?
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.']) ?>
?><?= Template::Header(['title' => 'RSS 2.0 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>
<h1>RSS 2.0 Ebook Feeds by Subject</h1>
<ul class="feed">
<? foreach(SE_SUBJECTS as $subject){ ?>
<li>