mirror of
https://github.com/standardebooks/web.git
synced 2025-07-13 01:52:02 -04:00
Move HTTP auth to PHP
This commit is contained in:
parent
e290758a9a
commit
30442c0c62
11 changed files with 110 additions and 86 deletions
|
@ -6,7 +6,7 @@ PHP 7+ is required.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Install Apache, PHP, PHP-FPM, and various other dependencies.
|
# 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 libaprutil1-dbd-mysql attr libapache2-mod-xsendfile
|
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 attr libapache2-mod-xsendfile
|
||||||
|
|
||||||
# Create the site root and logs root and clone this repo into it.
|
# Create the site root and logs root and clone this repo into it.
|
||||||
sudo mkdir /standardebooks.org/
|
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
|
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.
|
# Enable the necessary Apache modules.
|
||||||
sudo a2enmod headers expires ssl rewrite proxy proxy_fcgi authn_dbd authn_socache xsendfile
|
sudo a2enmod headers expires ssl rewrite proxy proxy_fcgi xsendfile
|
||||||
|
|
||||||
# Link and enable the SE Apache configuration file.
|
# Link and enable the SE Apache configuration file.
|
||||||
sudo ln -s /standardebooks.org/web/config/apache/standardebooks.test.conf /etc/apache2/sites-available/
|
sudo ln -s /standardebooks.org/web/config/apache/standardebooks.test.conf /etc/apache2/sites-available/
|
||||||
|
|
|
@ -273,8 +273,8 @@ Define webroot /standardebooks.org/web
|
||||||
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
|
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
|
||||||
RewriteRule ^/polls/([^/\.]+)/votes$ /polls/votes/post.php?pollurlname=$1 [L]
|
RewriteRule ^/polls/([^/\.]+)/votes$ /polls/votes/post.php?pollurlname=$1 [L]
|
||||||
|
|
||||||
# Feeds
|
# Rewrite rules for feeds eeds
|
||||||
# Rewrite old links to feeds
|
# Redirect old feed URLs
|
||||||
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
|
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
|
||||||
|
|
||||||
# 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.
|
||||||
|
@ -283,31 +283,12 @@ Define webroot /standardebooks.org/web
|
||||||
|
|
||||||
RewriteRule ^/feeds/(atom|rss)/([^/\.]+)$ /feeds/collection.php?type=$1&name=$2
|
RewriteRule ^/feeds/(atom|rss)/([^/\.]+)$ /feeds/collection.php?type=$1&name=$2
|
||||||
|
|
||||||
|
RewriteRule ^/feeds/(.+\.xml)$ /feeds/download.php?path=$1
|
||||||
|
|
||||||
# Rewrite rules for bulk downloads
|
# Rewrite rules for bulk downloads
|
||||||
RewriteRule ^/bulk-downloads/(.+\.zip)$ /bulk-downloads/download.php?path=$1
|
RewriteRule ^/bulk-downloads/(.+\.zip)$ /bulk-downloads/download.php?path=$1
|
||||||
RewriteRule ^/bulk-downloads/([^/\.]+)$ /bulk-downloads/collection.php?name=$1
|
RewriteRule ^/bulk-downloads/([^/\.]+)$ /bulk-downloads/collection.php?name=$1
|
||||||
|
|
||||||
# Enable mod_authn_dbd
|
|
||||||
# DBDriver mysql
|
|
||||||
# DBDParams "dbname=se user=www-data"
|
|
||||||
# # HTTP Basic Auth configuration for /feeds
|
|
||||||
# <DirectoryMatch "^${webroot}/www/feeds/(opds|rss|atom)">
|
|
||||||
# AuthType Basic
|
|
||||||
# AuthName "Enter your Patrons Circle email address and leave the password empty."
|
|
||||||
# Require valid-user
|
|
||||||
|
|
||||||
# # 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 Users u inner join Benefits b using (UserId) where %s in (u.Email, u.Uuid) and b.CanAccessFeeds = true limit 1"
|
|
||||||
# </DirectoryMatch>
|
|
||||||
|
|
||||||
# Specific config for /bulk-downloads
|
# Specific config for /bulk-downloads
|
||||||
<DirectoryMatch "${webroot}/www/bulk-downloads">
|
<DirectoryMatch "${webroot}/www/bulk-downloads">
|
||||||
# Both directives are required
|
# Both directives are required
|
||||||
|
@ -316,22 +297,11 @@ Define webroot /standardebooks.org/web
|
||||||
</DirectoryMatch>
|
</DirectoryMatch>
|
||||||
|
|
||||||
# Specific config for /feeds
|
# Specific config for /feeds
|
||||||
<DirectoryMatch "^${webroot}/www/feeds/(opds|rss|atom)">
|
<DirectoryMatch "^${webroot}/www/feeds">
|
||||||
ErrorDocument 401 /feeds/401
|
# This must be defined at the top level /feeds/ directory
|
||||||
|
# Both directives are required
|
||||||
<FilesMatch "^(style\.php|new-releases\.xml|index\.php|index\.xml)$">
|
XSendFile on
|
||||||
# Disable HTTP Basic auth for the feed XSL stylesheet and the new releases feeds
|
XSendFilePath /standardebooks.org/web/www/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>
|
</DirectoryMatch>
|
||||||
|
|
||||||
# Emit content-types for RSS/Atom feeds
|
# Emit content-types for RSS/Atom feeds
|
||||||
|
|
|
@ -255,8 +255,8 @@ Define webroot /standardebooks.org/web
|
||||||
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
|
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
|
||||||
RewriteRule ^/polls/([^/\.]+)/votes$ /polls/votes/post.php?pollurlname=$1 [L]
|
RewriteRule ^/polls/([^/\.]+)/votes$ /polls/votes/post.php?pollurlname=$1 [L]
|
||||||
|
|
||||||
# Feeds
|
# Rewrite rules for feeds eeds
|
||||||
# Rewrite old links to feeds
|
# Redirect old feed URLs
|
||||||
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
|
RewriteRule ^/(opds|rss|atom)(.*)$ /feeds/$1$2 [R=301,L]
|
||||||
|
|
||||||
# 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.
|
||||||
|
@ -265,31 +265,12 @@ Define webroot /standardebooks.org/web
|
||||||
|
|
||||||
RewriteRule ^/feeds/(atom|rss)/([^/\.]+)$ /feeds/collection.php?type=$1&name=$2
|
RewriteRule ^/feeds/(atom|rss)/([^/\.]+)$ /feeds/collection.php?type=$1&name=$2
|
||||||
|
|
||||||
|
RewriteRule ^/feeds/(.+\.xml)$ /feeds/download.php?path=$1
|
||||||
|
|
||||||
# Rewrite rules for bulk downloads
|
# Rewrite rules for bulk downloads
|
||||||
RewriteRule ^/bulk-downloads/(.+\.zip)$ /bulk-downloads/download.php?path=$1
|
RewriteRule ^/bulk-downloads/(.+\.zip)$ /bulk-downloads/download.php?path=$1
|
||||||
RewriteRule ^/bulk-downloads/([^/\.]+)$ /bulk-downloads/collection.php?name=$1
|
RewriteRule ^/bulk-downloads/([^/\.]+)$ /bulk-downloads/collection.php?name=$1
|
||||||
|
|
||||||
# Enable mod_authn_dbd
|
|
||||||
DBDriver mysql
|
|
||||||
DBDParams "dbname=se user=www-data"
|
|
||||||
# HTTP Basic Auth configuration for /feeds
|
|
||||||
<DirectoryMatch "^${webroot}/www/feeds/(opds|rss|atom)">
|
|
||||||
AuthType Basic
|
|
||||||
AuthName "Enter your Patrons Circle email address and leave the password empty."
|
|
||||||
Require valid-user
|
|
||||||
|
|
||||||
# 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 Users u inner join Benefits b using (UserId) where %s in (u.Email, u.Uuid) and b.CanAccessFeeds = true limit 1"
|
|
||||||
</DirectoryMatch>
|
|
||||||
|
|
||||||
# Specific config for /bulk-downloads
|
# Specific config for /bulk-downloads
|
||||||
<DirectoryMatch "${webroot}/www/bulk-downloads">
|
<DirectoryMatch "${webroot}/www/bulk-downloads">
|
||||||
# Both directives are required
|
# Both directives are required
|
||||||
|
@ -298,22 +279,11 @@ Define webroot /standardebooks.org/web
|
||||||
</DirectoryMatch>
|
</DirectoryMatch>
|
||||||
|
|
||||||
# Specific config for /feeds
|
# Specific config for /feeds
|
||||||
<DirectoryMatch "^${webroot}/www/feeds/(opds|rss|atom)">
|
<DirectoryMatch "^${webroot}/www/feeds">
|
||||||
ErrorDocument 401 /feeds/401
|
# This must be defined at the top level /feeds/ directory
|
||||||
|
# Both directives are required
|
||||||
<FilesMatch "^(style\.php|new-releases\.xml|index\.php|index\.xml)$">
|
XSendFile on
|
||||||
# Disable HTTP Basic auth for the feed XSL stylesheet and the new releases feeds
|
XSendFilePath /standardebooks.org/web/www/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>
|
</DirectoryMatch>
|
||||||
|
|
||||||
# Emit content-types for RSS/Atom feeds
|
# Emit content-types for RSS/Atom feeds
|
||||||
|
|
6
config/sql/se/FeedUserAgents.sql
Normal file
6
config/sql/se/FeedUserAgents.sql
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
CREATE TABLE `FeedUserAgents` (
|
||||||
|
`UserAgentId` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`UserAgent` text NOT NULL,
|
||||||
|
`Created` datetime NOT NULL,
|
||||||
|
PRIMARY KEY (`UserAgentId`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
@ -37,6 +37,12 @@ if($GLOBALS['User'] === null){
|
||||||
// log them in while we're here.
|
// log them in while we're here.
|
||||||
|
|
||||||
$session = new Session();
|
$session = new Session();
|
||||||
$session->Create($httpBasicAuthLogin);
|
try{
|
||||||
|
$session->Create($httpBasicAuthLogin);
|
||||||
|
$GLOBALS['User'] = $session->User;
|
||||||
|
}
|
||||||
|
catch(Exception $ex){
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ try{
|
||||||
|
|
||||||
// Everything OK, serve the file using Apache.
|
// Everything OK, serve the file using Apache.
|
||||||
// The xsendfile Apache module tells Apache to serve the file, including not-modified or etag headers.
|
// The xsendfile Apache module tells Apache to serve the file, including not-modified or etag headers.
|
||||||
// Much more efficien than reading it in PHP and outputting it that way.
|
// Much more efficient than reading it in PHP and outputting it that way.
|
||||||
header('X-Sendfile: ' . WEB_ROOT . $path);
|
header('X-Sendfile: ' . WEB_ROOT . $path);
|
||||||
header('Content-Type: application/zip');
|
header('Content-Type: application/zip');
|
||||||
header('Content-Disposition: attachment; filename="' . basename($path) . '"');
|
header('Content-Disposition: attachment; filename="' . basename($path) . '"');
|
||||||
|
|
|
@ -17,7 +17,7 @@ require_once('Core.php');
|
||||||
<p>The fifteen latest Standard Ebooks, most-recently-released first.</p>
|
<p>The fifteen latest Standard Ebooks, most-recently-released first.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p><a href="<? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? } ?>/feeds/atom/all">All ebooks</a></p>
|
<p><a href="/feeds/atom/all">All ebooks</a></p>
|
||||||
<p class="url"><? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? }else{ ?><?= SITE_URL ?><? } ?>/feeds/atom/all</p>
|
<p class="url"><? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? }else{ ?><?= 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>
|
||||||
|
|
|
@ -68,7 +68,7 @@ catch(Safe\Exceptions\ApcuException $ex){
|
||||||
<ul class="feed">
|
<ul class="feed">
|
||||||
<? foreach($feeds as $feed){ ?>
|
<? foreach($feeds as $feed){ ?>
|
||||||
<li>
|
<li>
|
||||||
<p><a href="<? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? } ?><?= Formatter::ToPlainText($feed->Url) ?>"><?= Formatter::ToPlainText($feed->Label) ?></a></p>
|
<p><a href="<?= Formatter::ToPlainText($feed->Url) ?>"><?= Formatter::ToPlainText($feed->Label) ?></a></p>
|
||||||
<p class="url"><? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? }else{ ?><?= SITE_URL ?><? } ?><?= Formatter::ToPlainText($feed->Url) ?></p>
|
<p class="url"><? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? }else{ ?><?= SITE_URL ?><? } ?><?= Formatter::ToPlainText($feed->Url) ?></p>
|
||||||
</li>
|
</li>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
|
|
72
www/feeds/download.php
Normal file
72
www/feeds/download.php
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<?
|
||||||
|
require_once('Core.php');
|
||||||
|
|
||||||
|
use function Safe\preg_match;
|
||||||
|
|
||||||
|
// This page is blocked by HTTP Basic auth.
|
||||||
|
// Basic authorization is handled in Core.php. By the time we get here,
|
||||||
|
// a valid user has a session.
|
||||||
|
|
||||||
|
$path = HttpInput::Str(GET, 'path', false) ?? '';
|
||||||
|
$isUserAgentAllowed = false;
|
||||||
|
|
||||||
|
try{
|
||||||
|
$path = '/feeds/' . $path;
|
||||||
|
|
||||||
|
if(!is_file(WEB_ROOT . $path)){
|
||||||
|
throw new Exceptions\InvalidFileException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certain user agents may bypass login entirely
|
||||||
|
if(isset($_SERVER['HTTP_USER_AGENT'])){
|
||||||
|
$isUserAgentAllowed = (bool)Db::QueryInt('select count(*) from FeedUserAgents where instr(?, UserAgent) limit 1', [$_SERVER['HTTP_USER_AGENT']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$isUserAgentAllowed){
|
||||||
|
if($GLOBALS['User'] === null){
|
||||||
|
throw new Exceptions\LoginRequiredException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!preg_match('/\.xml$/ius', $path)){
|
||||||
|
throw new Exceptions\InvalidPermissionsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$GLOBALS['User']->Benefits->CanAccessFeeds){
|
||||||
|
throw new Exceptions\InvalidPermissionsException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything OK, serve the file using Apache.
|
||||||
|
// The xsendfile Apache module tells Apache to serve the file, including not-modified or etag headers.
|
||||||
|
// Much more efficient than reading it in PHP and outputting it that way.
|
||||||
|
header('X-Sendfile: ' . WEB_ROOT . $path);
|
||||||
|
|
||||||
|
if(preg_match('/^\/feeds\/opds/', $path)){
|
||||||
|
header('Content-Type: application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8');
|
||||||
|
|
||||||
|
if(preg_match('/\/index\.xml$/', $path)){
|
||||||
|
header('Content-Type: application/atom+xml;profile=opds-catalog;kind=navigation; charset=utf-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif(preg_match('/^\/feeds\/rss/', $path)){
|
||||||
|
header('Content-Type: application/rss+xml');
|
||||||
|
}
|
||||||
|
elseif(preg_match('/^\/feeds\/atom/', $path)){
|
||||||
|
header('Content-Type: application/atom+xml');
|
||||||
|
}
|
||||||
|
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
catch(Exceptions\LoginRequiredException $ex){
|
||||||
|
header('WWW-Authenticate: Basic realm="Enter your Patrons Circle email address and leave the password empty."');
|
||||||
|
http_response_code(401);
|
||||||
|
}
|
||||||
|
catch(Exceptions\InvalidPermissionsException $ex){
|
||||||
|
http_response_code(403);
|
||||||
|
}
|
||||||
|
catch(Exceptions\InvalidFileException $ex){
|
||||||
|
Template::Emit404();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the login info page
|
||||||
|
include(WEB_ROOT . '/feeds/401.php');
|
|
@ -64,7 +64,7 @@ catch(Exceptions\InvalidCollectionException $ex){
|
||||||
<? } ?>
|
<? } ?>
|
||||||
<ul class="feed">
|
<ul class="feed">
|
||||||
<li>
|
<li>
|
||||||
<p><a href="<? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? } ?>/feeds/<?= $type ?>/<?= $name ?>/<?= $target?>"><?= Formatter::ToPlainText($label) ?></a></p>
|
<p><a href="/feeds/<?= $type ?>/<?= $name ?>/<?= $target?>"><?= Formatter::ToPlainText($label) ?></a></p>
|
||||||
<p class="url"><? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? }else{ ?><?= SITE_URL ?><? } ?>/feeds/<?= $type ?>/<?= $name ?>/<?= $target?></p>
|
<p class="url"><? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? }else{ ?><?= SITE_URL ?><? } ?>/feeds/<?= $type ?>/<?= $name ?>/<?= $target?></p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -19,7 +19,7 @@ require_once('Core.php');
|
||||||
<p>To connect your ereading app to our catalog, enter the URL below when prompted by your app:</p>
|
<p>To connect your ereading app to our catalog, enter the URL below when prompted by your app:</p>
|
||||||
<ul class="feed">
|
<ul class="feed">
|
||||||
<li>
|
<li>
|
||||||
<p><a href="<? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? } ?>/feeds/opds">The Standard Ebooks OPDS feed</a></p>
|
<p><a href="/feeds/opds">The Standard Ebooks OPDS feed</a></p>
|
||||||
<p class="url"><? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? }else{ ?><?= SITE_URL ?><? } ?>/feeds/opds</p>
|
<p class="url"><? if($GLOBALS['User'] !== null){ ?>https://<?= rawurlencode($GLOBALS['User']->Email) ?>@<?= SITE_DOMAIN ?><? }else{ ?><?= SITE_URL ?><? } ?>/feeds/opds</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue