Switch from JSON-LD to RDFa for internal metadata

This commit is contained in:
Alex Cabal 2020-12-26 14:55:13 -06:00
parent a12200c1ac
commit 99f1c1537a
7 changed files with 125 additions and 36 deletions

View file

@ -25,3 +25,7 @@ parameters:
- %rootDir%/../../../lib
- %rootDir%/../../../www
- %rootDir%/../../../scripts
dynamicConstantNames:
- DONATION_ALERT_ON
- DONATION_ALERT_ALWAYS_ON
- DONATION_ALERT_ON_DURING_HOLIDAYS

View file

@ -1,5 +1,7 @@
<?
// Auto-included by Composer in composer.json to satisfy PHPStan
use function Safe\define;
use function Safe\strtotime;
const EBOOKS_PER_PAGE = 12;
const SORT_NEWEST = 'newest';
@ -36,7 +38,7 @@ define('PD_YEAR', intval(gmdate('Y')) - 96);
const DONATION_ALERT_ALWAYS_ON = false;
const DONATION_ALERT_ON_DURING_HOLIDAYS = true;
define('DONATION_ALERT_ON', DONATION_ALERT_ALWAYS_ON | (DONATION_ALERT_ON_DURING_HOLIDAYS && (strtotime('December 1, ' . gmdate('Y')) < time() && time() < strtotime('January 7, ' . (intval(gmdate('Y') + 1))))));
define('DONATION_ALERT_ON', DONATION_ALERT_ALWAYS_ON | (DONATION_ALERT_ON_DURING_HOLIDAYS && (strtotime('December 1, ' . gmdate('Y')) < time() && time() < strtotime('January 7, ' . (intval(gmdate('Y')) + 1)))));
// No trailing slash on any of the below constants.
const SITE_URL = 'https://standardebooks.org';
const SITE_ROOT = '/standardebooks.org';

View file

@ -239,16 +239,19 @@ class Ebook{
$id = $author->attributes()->id;
}
$refines = null;
$refinesElement = $xml->xpath('/package/metadata/meta[@property="file-as"][@refines="#' . $id . '"]');
if($refinesElement !== false && sizeof($refinesElement) > 0){
$refines = (string)$refinesElement[0];
$fileAs = null;
$fileAsElement = $xml->xpath('/package/metadata/meta[@property="file-as"][@refines="#' . $id . '"]');
if($fileAsElement !== false && sizeof($fileAsElement) > 0){
$fileAs = (string)$fileAsElement[0];
}
$this->Authors[] = new Contributor( (string)$author,
$refines,
$this->Authors[] = new Contributor(
(string)$author,
$fileAs,
$this->NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:name.person.full-name"][@refines="#' . $id . '"]')),
$this->NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.encyclopedia.wikipedia"][@refines="#' . $id . '"]'))
$this->NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.encyclopedia.wikipedia"][@refines="#' . $id . '"]')),
'aut',
$this->NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.authority.nacoaf"][@refines="#' . $id . '"]'))
);
}
@ -256,7 +259,7 @@ class Ebook{
throw new EbookParsingException('Invalid <dc:creator> element.');
}
$this->AuthorsUrl = preg_replace('|url:https://standardebooks.org/ebooks/([^/]+)/.*|ius', '/ebooks/\1/', $this->Identifier);
$this->AuthorsUrl = preg_replace('|url:https://standardebooks.org/ebooks/([^/]+)/.*|ius', '/ebooks/\1', $this->Identifier);
foreach($xml->xpath('/package/metadata/dc:contributor') ?: [] as $contributor){
$id = '';
@ -270,6 +273,7 @@ class Ebook{
$this->NullIfEmpty($xml->xpath('/package/metadata/meta[@property="file-as"][@refines="#' . $id . '"]')),
$this->NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:name.person.full-name"][@refines="#' . $id . '"]')),
$this->NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.encyclopedia.wikipedia"][@refines="#' . $id . '"]')),
$role,
$this->NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.authority.nacoaf"][@refines="#' . $id . '"]'))
);
@ -437,6 +441,16 @@ class Ebook{
$this->TitleWithCreditsHtml = Formatter::ToPlainText($this->Title) . ', by ' . str_replace('&amp;', '&', $this->AuthorsHtml . $titleContributors);
}
public function GetCollectionPosition(Collection $collection): ?int{
foreach($this->Collections as $c){
if($c->Name == $collection->Name){
return $c->SequenceNumber;
}
}
return null;
}
public function Contains(string $query): bool{
// When searching an ebook, we search the title, alternate title, author(s), SE tags, series data, and LoC tags.
// Also, if the ebook is shorts or poetry, search the ToC as well.
@ -587,11 +601,34 @@ class Ebook{
$string = '';
$i = 0;
foreach($contributors as $contributor){
$role = 'schema:contributor';
switch($contributor->MarcRole){
case 'trl':
$role = 'schema:translator';
break;
case 'ill':
$role = 'schema:illustrator';
break;
}
if($contributor->WikipediaUrl){
$string .= '<a href="' . Formatter::ToPlainText($contributor->WikipediaUrl) .'">' . Formatter::ToPlainText($contributor->Name) . '</a>';
$string .= '<a property="' . $role . '" typeof="schema:Person" href="' . Formatter::ToPlainText($contributor->WikipediaUrl) .'"><span property="schema:name">' . Formatter::ToPlainText($contributor->Name) . '</span>';
if($contributor->NacoafUrl){
$string .= '<meta property="schema:sameAs" content="' . Formatter::ToPlainText($contributor->NacoafUrl) . '"/>';
}
$string .= '</a>';
}
else{
$string .= Formatter::ToPlainText($contributor->Name);
$string .= '<span property="' . $role . '" typeof="schema:Person"><span property="schema:name">' . Formatter::ToPlainText($contributor->Name) . '</span>';
if($contributor->NacoafUrl){
$string .= '<meta property="schema:sameAs" content="' . Formatter::ToPlainText($contributor->NacoafUrl) . '"/>';
}
$string .= '</span>';
}
if($i == sizeof($contributors) - 2 && sizeof($contributors) > 2){

View file

@ -8,20 +8,26 @@ if(!isset($ebooks)){
$ebooks = [];
}
?>
<ol<? if($view == VIEW_LIST){ ?> class="list"<? } ?>>
<ol<? if($view == VIEW_LIST){ ?> class="list"<? } ?><? if($collection !== null){ ?> typeof="schema:BookSeries" about="<?= $collection->Url ?>"<? } ?>>
<? if($collection !== null){ ?>
<meta property="schema:name" content="<?= Formatter::ToPlainText($collection->Name) ?>"/>
<? } ?>
<? foreach($ebooks as $ebook){ ?>
<li>
<a href="<?= $ebook->Url ?>" tabindex="-1">
<li typeof="schema:Book"<? if($collection !== null){ ?> resource="<?= $ebook->Url ?>" property="schema:hasPart" value="<?= $ebook->GetCollectionPosition($collection) ?>"<? }else{ ?> about="<?= $ebook->Url ?>"<? } ?>>
<? if($collection !== null){ ?>
<meta property="schema:position" content="<?= $ebook->GetCollectionPosition($collection) ?>"/>
<? } ?>
<a href="<?= $ebook->Url ?>" tabindex="-1" property="schema:url">
<picture>
<? if($ebook->CoverImage2xAvifUrl !== null){ ?><source srcset="<?= $ebook->CoverImage2xAvifUrl ?> 2x, <?= $ebook->CoverImageAvifUrl ?> 1x" type="image/avif"/><? } ?>
<source srcset="<?= $ebook->CoverImage2xUrl ?> 2x, <?= $ebook->CoverImageUrl ?> 1x" type="image/jpg"/>
<img src="<?= $ebook->CoverImage2xUrl ?>" title="<?= Formatter::ToPlainText($ebook->Title) ?>" alt="The cover for the Standard Ebooks edition of <?= Formatter::ToPlainText(strip_tags($ebook->TitleWithCreditsHtml)) ?>"/>
<img src="<?= $ebook->CoverImage2xUrl ?>" title="<?= Formatter::ToPlainText($ebook->Title) ?>" alt="The cover for the Standard Ebooks edition of <?= Formatter::ToPlainText(strip_tags($ebook->TitleWithCreditsHtml)) ?>" property="schema:image"/>
</picture>
</a>
<p><a href="<?= $ebook->Url ?>"><?= Formatter::ToPlainText($ebook->Title) ?></a></p>
<p><a href="<?= $ebook->Url ?>" property="schema:url"><span property="schema:name"><?= Formatter::ToPlainText($ebook->Title) ?></span></a></p>
<? if($view == VIEW_GRID){ ?>
<? foreach($ebook->Authors as $author){ ?>
<p class="author"><? if($author->Name != 'Anonymous'){ ?><a href="<?= Formatter::ToPlainText($ebook->AuthorsUrl) ?>"><?= Formatter::ToPlainText($author->Name) ?></a><? } ?></p>
<p class="author" typeof="schema:Person" property="schema:author" resource="<?= $ebook->AuthorsUrl ?>"><? if($author->Name != 'Anonymous'){ ?><a href="<?= Formatter::ToPlainText(SITE_URL . $ebook->AuthorsUrl) ?>" property="schema:url"><span property="schema:name"><?= Formatter::ToPlainText($author->Name) ?></span></a><? } ?></p>
<? } ?>
<? }else{ ?>
<div>

View file

@ -23,7 +23,7 @@ print('<?xml version="1.0" encoding="utf-8"?>');
print("\n");
?><!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">
<head>
<head prefix="twitter: https://twitter.com schema: https://schema.org"><? /* the og RDFa prefix is part of the RDFa spec */ ?>
<meta charset="utf-8"/>
<title><? if($title != ''){ ?><?= Formatter::ToPlainText($title) ?> - <? } ?>Standard Ebooks: Free and liberated ebooks, carefully produced for the true book lover.</title>
<? if($description != ''){ ?><meta content="<?= Formatter::ToPlainText($description) ?>" name="description"/><? } ?>
@ -49,11 +49,6 @@ print("\n");
<meta content="summary_large_image" name="twitter:card"/>
<meta content="@standardebooks" name="twitter:site"/>
<meta content="@standardebooks" name="twitter:creator"/>
<? if(isset($jsonld)){ ?>
<script type="application/ld+json">
<?= $jsonld ?>
</script>
<? } ?>
</head>
<body>
<header>

View file

@ -68,22 +68,45 @@ catch(\Exception $ex){
include(WEB_ROOT . '/404.php');
exit();
}
?><?= Template::Header(['title' => strip_tags($ebook->TitleWithCreditsHtml), 'ogType' => 'book', 'coverUrl' => $ebook->DistCoverUrl, 'highlight' => 'ebooks', 'description' => 'The Standard Ebooks edition of ' . $ebook->Title . ': ' . $ebook->Description, 'jsonld' => htmlentities($ebook->GenerateJsonLd(), ENT_NOQUOTES)]) ?>
?><?= Template::Header(['title' => strip_tags($ebook->TitleWithCreditsHtml), 'ogType' => 'book', 'coverUrl' => $ebook->DistCoverUrl, 'highlight' => 'ebooks', 'description' => 'The Standard Ebooks edition of ' . $ebook->Title . ': ' . $ebook->Description]) ?>
<main>
<article class="ebook">
<article class="ebook" typeof="schema:Book" about="<?= $ebook->Url ?>">
<meta property="schema:bookFormat" content="EBook"/>
<meta property="schema:image" content="<?= Formatter::ToPlainText(SITE_URL . $ebook->DistCoverUrl) ?>"/>
<meta property="schema:thumnailUrl" content="<?= Formatter::ToPlainText(SITE_URL . $ebook->Url . '/downloads/cover-thumbnail.jpg') ?>"/>
<meta property="schema:description" content="<?= Formatter::ToPlainText($ebook->Description) ?>"/>
<meta property="schema:url" content="<?= SITE_URL . Formatter::ToPlainText($ebook->Url) ?>"/>
<meta property="schema:license" content="https://creativecommons.org/publicdomain/zero/1.0/"/>
<meta property="schema:inLanguage" content="<?= Formatter::ToPlainText($ebook->Language) ?>"/>
<? if($ebook->WikipediaUrl){ ?>
<meta property="schema:sameAs" content="<?= Formatter::ToPlainText($ebook->WikipediaUrl) ?>"/>
<? } ?>
<div property="schema:publisher" typeof="schema:Organization">
<meta property="schema:name" content="Standard Ebooks"/>
<meta property="schema:logo" content="https://standardebooks.org/images/logo-full.svg"/>
<meta property="schema:url" content="https://standardebooks.org"/>
</div>
<header>
<hgroup>
<h1><?= Formatter::ToPlainText($ebook->Title) ?></h1>
<h1 property="schema:name"><?= Formatter::ToPlainText($ebook->Title) ?></h1>
<? foreach($ebook->Authors as $author){ ?>
<? /* We include the `resource` attr here because we can have multiple authors, and in that case their href URLs will link to their combined corpus.
For example, William Wordsworth & Samuel Coleridge will both link to /ebooks/william-wordsworth_samuel-taylor-coleridge
But, each author is an individual, so we have to differentiate them in RDFa with `resource` */ ?>
<? if($author->Name != 'Anonymous'){ ?>
<h2><a href="<?= Formatter::ToPlainText($ebook->AuthorsUrl) ?>"><?= Formatter::ToPlainText($author->Name) ?></a></h2>
<h2><a property="schema:author" typeof="schema:Person" href="<?= Formatter::ToPlainText($ebook->AuthorsUrl) ?>" resource="<?= '/ebooks/' . $author->UrlName ?>">
<span property="schema:name"><?= Formatter::ToPlainText($author->Name) ?></span>
<meta property="schema:url" content="<?= SITE_URL . Formatter::ToPlainText($ebook->AuthorsUrl) ?>"/>
<? if($author->NacoafUrl){ ?><meta property="schema:sameAs" content="<?= Formatter::ToPlainText($author->NacoafUrl) ?>"/><? } ?>
</a>
</h2>
<? } ?>
<? } ?>
</hgroup>
<picture>
<? if($ebook->HeroImage2xAvifUrl !== null){ ?><source srcset="<?= $ebook->HeroImage2xAvifUrl ?> 2x, <?= $ebook->HeroImageAvifUrl ?> 1x" type="image/avif"/><? } ?>
<source srcset="<?= $ebook->HeroImage2xUrl ?> 2x, <?= $ebook->HeroImageUrl ?> 1x" type="image/jpg"/>
<img src="<?= $ebook->HeroImage2xUrl ?>" alt="The cover for the Standard Ebooks edition of <?= Formatter::ToPlainText(strip_tags($ebook->TitleWithCreditsHtml)) ?>"/>
<img src="<?= $ebook->HeroImage2xUrl ?>" role="presentation" alt=""/>
</picture>
</header>
@ -94,7 +117,7 @@ catch(\Exception $ex){
<? } ?>
<? if(sizeof($ebook->Collections) > 0){ ?>
<? foreach($ebook->Collections as $collection){ ?>
<p><? if($collection->SequenceNumber !== null){ ?>№ <?= number_format($collection->SequenceNumber) ?> in the<? }else{ ?>Part of the<? } ?> <a href="<?= $collection->Url ?>"><?= Formatter::ToPlainText(preg_replace('/^The /ius', '', (string)$collection->Name) ?? '') ?></a>
<p><? if($collection->SequenceNumber !== null){ ?>№ <?= number_format($collection->SequenceNumber) ?> in the<? }else{ ?>Part of the<? } ?> <a href="<?= $collection->Url ?>" property="schema:isPartOf"><?= Formatter::ToPlainText(preg_replace('/^The /ius', '', (string)$collection->Name) ?? '') ?></a>
<? if($collection->Type !== null){ ?>
<? if(substr_compare(mb_strtolower($collection->Name), mb_strtolower($collection->Type), -strlen(mb_strtolower($collection->Type))) !== 0){ ?>
<?= $collection->Type ?>.
@ -128,22 +151,38 @@ catch(\Exception $ex){
<h3>Download for ereaders</h3>
<ul>
<? if($ebook->EpubUrl !== null){ ?>
<li><p><span><a href="<?= $ebook->EpubUrl ?>" class="epub">Compatible epub</a> </span><span></span> <span>All devices and apps except Kindles and Kobos.</span></p>
<li property="schema:encoding" typeof="schema:MediaObject"><p>
<span><a property="schema:contentUrl" href="<?= $ebook->EpubUrl ?>" class="epub">
Compatible epub
<meta property="schema:encodingFormat" content="application/epub+zip"/>
</a></span> <span></span> <span>All devices and apps except Kindles and Kobos.</span></p>
</li>
<? } ?>
<? if($ebook->Azw3Url !== null){ ?>
<li><p><span><a href="<?= $ebook->Azw3Url ?>" class="amazon">azw3</a></span> <span></span> <span>Kindle devices and apps.<? if($ebook->KindleCoverUrl !== null){ ?> Also download the <a href="<?= $ebook->KindleCoverUrl ?>">Kindle cover thumbnail</a> to see the cover in your Kindles library.<? } ?></span></p>
<li property="schema:encoding" typeof="schema:MediaObject"><p>
<span><a property="schema:contentUrl" href="<?= $ebook->Azw3Url ?>" class="amazon">
azw3
<meta property="schema:encodingFormat" content="application/x-mobipocket-ebook"/>
</a></span> <span></span> <span>Kindle devices and apps.<? if($ebook->KindleCoverUrl !== null){ ?> Also download the <a href="<?= $ebook->KindleCoverUrl ?>">Kindle cover thumbnail</a> to see the cover in your Kindles library.<? } ?></span></p>
</li>
<? } ?>
<? if($ebook->KepubUrl !== null){ ?>
<li><p><span><a href="<?= $ebook->KepubUrl ?>" class="kobo">kepub</a> </span><span></span> <span>Kobo devices and apps.</span></p>
<li property="schema:encoding" typeof="schema:MediaObject"><p>
<span><a property="schema:contentUrl" href="<?= $ebook->KepubUrl ?>" class="kobo">
kepub
<meta property="schema:encodingFormat" content="application/kepub+zip"/>
</a></span> <span></span> <span>Kobo devices and apps.</span></p>
</li>
<? } ?>
<? if($ebook->AdvancedEpubUrl !== null){ ?>
<li><p><span><a href="<?= $ebook->AdvancedEpubUrl ?>" class="epub">Advanced epub</a></span> <span></span> <span>An advanced format not yet fully compatible with most ereaders.</span></p>
<li property="schema:encoding" typeof="schema:MediaObject"><p>
<span><a property="schema:contentUrl" href="<?= $ebook->AdvancedEpubUrl ?>" class="epub">
Advanced epub
<meta property="schema:encodingFormat" content="application/epub+zip"/>
</a></span> <span></span> <span>An advanced format not yet fully compatible with most ereaders.</span></p>
</li>
<? } ?>
</ul>
@ -159,7 +198,12 @@ catch(\Exception $ex){
<li><p><a href="<?= $ebook->TextUrl ?>" class="list">Start from the table of contents</a></p></li>
<? } ?>
<? if($ebook->TextSinglePageUrl !== null){ ?>
<li><p><a href="<?= $ebook->TextSinglePageUrl ?>" class="page">Read on one page</a></p></li>
<li property="schema:encoding" typeof="schema:mediaObject"><p>
<a property="schema:contentUrl" href="<?= $ebook->TextSinglePageUrl ?>" class="page">
Read on one page
<meta property="schema:encodingFormat" content="application/xhtml+xml"/>
</a>
</p></li>
<? } ?>
</ul>
</section>

View file

@ -13,6 +13,7 @@ try{
$sort = HttpInput::GetString('sort', false);
$pages = 0;
$totalEbooks = 0;
$collectionObject = null;
if($page <= 0){
$page = 1;
@ -51,7 +52,7 @@ try{
// Are we looking at a collection?
if($collection !== null){
$ebooks = Library::GetEbooksByCollection($collection);
$collectionObject = null;
// Get the *actual* name of the collection, in case there are accent marks (like "Arsène Lupin")
if(sizeof($ebooks) > 0){
foreach($ebooks[0]->Collections as $c){
@ -138,7 +139,7 @@ catch(\Exception $ex){
<? if(sizeof($ebooks) == 0){ ?>
<p class="no-results">No ebooks matched your filters. You can try different filters, or <a href="/ebooks">browse all of our ebooks</a>.</p>
<? }else{ ?>
<?= Template::EbookGrid(['ebooks' => $ebooks, 'view' => $view]) ?>
<?= Template::EbookGrid(['ebooks' => $ebooks, 'view' => $view, 'collection' => $collectionObject]) ?>
<? } ?>
<? if(sizeof($ebooks) > 0 && $collection === null){ ?>
<nav>