aboutsummaryrefslogtreecommitdiffstats
path: root/Zotlabs
diff options
context:
space:
mode:
Diffstat (limited to 'Zotlabs')
-rw-r--r--Zotlabs/Thumbs/Epubthumb.php148
1 files changed, 138 insertions, 10 deletions
diff --git a/Zotlabs/Thumbs/Epubthumb.php b/Zotlabs/Thumbs/Epubthumb.php
index b50583e30..af372e85c 100644
--- a/Zotlabs/Thumbs/Epubthumb.php
+++ b/Zotlabs/Thumbs/Epubthumb.php
@@ -2,8 +2,11 @@
namespace Zotlabs\Thumbs;
-use SebLucas\EPubMeta\EPub;
+use DOMDocument;
+use DOMElement;
+use DOMXPath;
use GdImage;
+use ZipArchive;
/**
* Thumbnail creation for epub files.
@@ -24,20 +27,21 @@ class Epubthumb {
* Create the thumbnail if the Epub has a cover.
*
* @param array $attach
- * @param number $preview_style unused
- * @param number $height (optional) default 300
- * @param number $width (optional) default 300
+ * @param int $preview_style unused
+ * @param int $height (optional) default 300
+ * @param int $width (optional) default 300
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ * phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
*/
- function Thumb($attach, $preview_style, $height = 300, $width = 300) {
+ function Thumb($attach, $preview_style, $height = 300, $width = 300): void {
$file = dbunescbin($attach['content']);
if (!$file) {
return;
}
- $image = $this->getCover($file);
+ $image = $this->getCoverFromEpub($file);
if ($image) {
$srcwidth = imagesx($image);
@@ -56,15 +60,139 @@ class Epubthumb {
}
}
- private function getCover(string $filename): GdImage|false {
- $epub = new EPub($filename);
- $cover = $epub->getCover();
+ /**
+ * Fetch the cover from the epub archive, if it's present.
+ *
+ * There's a few limitations here: This will only work if the cover
+ * is a raster image of a supported format. SVG does not work, neither
+ * will other schemes sometimes used for cover/front page.
+ *
+ * @param string $filename The local filename of the epub archive.
+ *
+ * @return GdImage|false If a cover is found, it is returned as a
+ * GdImage object. Otherwise return false.
+ */
+ private function getCoverFromEpub(string $filename): GdImage|false {
+ $epub = new ZipArchive();
+ $rc = $epub->open($filename, ZipArchive::RDONLY);
+
+ if ($rc !== true) {
+ logger("Error opening file '{$filename}': rc = ${rc}.", LOGGER_DEBUG, LOG_DEBUG);
+ return false;
+ }
+
+ $cover = false;
+ $cover_name = $this->parseEpub($epub);
+ if ($cover_name !== false) {
+ $cover = $epub->getFromName($cover_name);
+ if ($cover === false) {
+ logger("File '{$cover_name}' not found in EPUB.", LOGGER_DEBUG, LOG_DEBUG);
+ }
+ }
+
+ $epub->close();
- if (! empty($cover)) {
+ if ($cover !== false && !empty($cover)) {
return imagecreatefromstring($cover);
} else {
return false;
}
}
+
+ /**
+ * Parse the epub to find the path of the cover image.
+ *
+ * @param ZipArchive $epub An opened epub ZipArchive.
+ *
+ * @return string|false The path to the cover image or false.
+ */
+ private function parseEpub(ZipArchive $epub): string|false {
+ $packagePath = $this->getEpubPackagePath($epub);
+ if ($packagePath !== false) {
+ $package = $epub->getFromName($packagePath);
+ if ($package === false || empty($package)) {
+ logger("Package file '${packagePath}' not found in EPUB", LOGGER_DEBUG, LOG_DEBUG);
+ return false;
+ }
+
+ $domdoc = new DOMDocument();
+ $domdoc->loadXML($package);
+ $xpath = new DOMXPath($domdoc);
+ $xpath->registerNamespace("n", "http://www.idpf.org/2007/opf");
+ $nodes = $xpath->query('/n:package/n:manifest/n:item[@properties="cover-image"]');
+
+ if ($nodes->count() === 0) {
+ logger('No cover found in EPUB manifest.', LOGGER_DEBUG, LOG_DEBUG);
+ return false;
+ }
+
+ $node = $nodes->item(0);
+ if ($node === null) {
+ logger('No nodes in non-empty node list?', LOGGER_DEBUG, LOG_DEBUG);
+ return false;
+ }
+
+ if (is_a($node, DOMElement::class)) {
+ // The URL's in the package file is relative to the subdirectory
+ // within the epub archive where it is located. See
+ // https://www.w3.org/TR/epub-33/#sec-parsing-urls-metainf
+ return dirname($packagePath) . '/' . $node->getAttribute('href');
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Locate the package file within the epub.
+ *
+ * The package file in an epub archive contains the manifest
+ * that again may contain a reference to the cover for the
+ * epub.
+ *
+ * @param ZipArchive $epub An opened epub archive.
+ *
+ * @return string|false The full pathname of the package file or false.
+ */
+ private function getEpubPackagePath(ZipArchive $epub): string|false {
+ //
+ // The only mandatory known file within the archive is the
+ // container file, so we fetch it to find the reference to
+ // the package file.
+ //
+ // See: https://www.w3.org/TR/epub-33/#sec-container-metainf
+ //
+ $container = $epub->getFromName('META-INF/container.xml');
+
+ if ($container === false || empty($container)) {
+ logger('No container in archive, probably not an EPUB.', LOGGER_DEBUG, LOG_DEBUG);
+ return false;
+ }
+
+ $domdoc = new DOMDocument();
+ $domdoc->loadXML($container);
+ $nodes = $domdoc->getElementsByTagName('rootfile');
+
+ if ($nodes->count() == 0) {
+ logger('EPUB rootfile not found, is this an epub?', LOGGER_DEBUG, LOG_DEBUG);
+ return false;
+ }
+
+ $packageNode = $nodes->item(0);
+ if ($packageNode === null || !is_a($packageNode, DOMElement::class)) {
+ logger('EPUB rootfile element missing or invalid.', LOGGER_DEBUG, LOG_DEBUG);
+ return false;
+ }
+
+ $packagePath = $packageNode->getAttribute('full-path');
+ $packageMediaType = $packageNode->getAttribute('media-type');
+
+ if (empty($packagePath) || $packageMediaType !== 'application/oebps-package+xml') {
+ logger('EPUB package path missing or incorrect media type.', LOGGER_DEBUG, LOG_DEBUG);
+ return false;
+ }
+
+ return $packagePath;
+ }
}