diff --git a/.gitignore b/.gitignore index b2932df..6089c48 100644 --- a/.gitignore +++ b/.gitignore @@ -83,4 +83,6 @@ legacy/vendor legacy/vendor/* testData -testData/* \ No newline at end of file +testData/* + +/ExampleBook*.epub \ No newline at end of file diff --git a/rector.php b/rector.php index c2fe993..85fc8a6 100644 --- a/rector.php +++ b/rector.php @@ -5,13 +5,18 @@ use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector; use Rector\Config\RectorConfig; use Rector\Set\ValueObject\LevelSetList; +use Rector\Set\ValueObject\SetList; return static function (RectorConfig $rectorConfig): void { $rectorConfig->paths([ __DIR__ . '/src', ]); $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); - $rectorConfig->sets([ - LevelSetList::UP_TO_PHP_82 + $rectorConfig->sets([SetList::CODE_QUALITY, + SetList::DEAD_CODE, + SetList::EARLY_RETURN, + SetList::TYPE_DECLARATION, + LevelSetList::UP_TO_PHP_81, + SetList::CODING_STYLE ]); }; diff --git a/src/PHPePub/Core/EPub.php b/src/PHPePub/Core/EPub.php index bf50f81..15c6e4d 100644 --- a/src/PHPePub/Core/EPub.php +++ b/src/PHPePub/Core/EPub.php @@ -2,7 +2,6 @@ namespace PHPePub\Core; -use com\grandt\BinStringStatic; use Masterminds\HTML5; use DOMDocument; use DOMXPath; @@ -42,25 +41,33 @@ class EPub final public const VERSION = '4.0.6'; final public const IDENTIFIER_UUID = 'UUID'; + final public const IDENTIFIER_URI = 'URI'; + final public const IDENTIFIER_ISBN = 'ISBN'; /** Ignore all external references, and do not process the file for these */ final public const EXTERNAL_REF_IGNORE = 0; + /** Process the file for external references and add them to the book */ final public const EXTERNAL_REF_ADD = 1; + /** Process the file for external references and add them to the book, but remove images, and img tags */ final public const EXTERNAL_REF_REMOVE_IMAGES = 2; + /** Process the file for external references and add them to the book, but replace images, and img tags with [image] */ final public const EXTERNAL_REF_REPLACE_IMAGES = 3; final public const DIRECTION_LEFT_TO_RIGHT = 'ltr'; + final public const DIRECTION_RIGHT_TO_LEFT = 'rtl'; final public const BOOK_VERSION_EPUB2 = '2.0'; + final public const BOOK_VERSION_EPUB3 = '3.0'; final public const FORMAT_XHTML = 'xhtml'; + final public const FORMAT_HTML5 = 'html5'; public $viewportMap = ["small" => ['width' => 600, 'height' => 800], "medium" => ['width' => 720, 'height' => 1280], "720p" => ['width' => 720, 'height' => 1280], "ipad" => ['width' => 768, 'height' => 1024], "large" => ['width' => 1080, 'height' => 1920], "2k" => ['width' => 1080, 'height' => 1920], "1080p" => ['width' => 1080, 'height' => 1920], "ipad3" => ['width' => 1536, 'height' => 2048], "4k" => ['width' => 2160, 'height' => 3840]]; @@ -68,7 +75,9 @@ class EPub public $splitDefaultSize = 250000; public $maxImageWidth = 768; + public $maxImageHeight = 1024; + /** * Gifs can crash some early ADE based readers, and are disabled by default. * getImage will convert these if it can, unless this is set to TRUE. @@ -76,63 +85,105 @@ class EPub public $isGifImagesEnabled = false; public $isReferencesAddedToToc = true; + /** * Used for building the TOC. * If this list is overwritten it MUST contain at least "text" as an element. */ - public $referencesOrder = null; + public $referencesOrder; public $pluginDir = 'extLib'; public $isLogging = true; + public $encodeHTML = false; + /** @var $Zip Zip */ - private $zip; - private $title = ''; - private $language = 'en'; - private $identifier = ''; - private $identifierType = ''; - private $description = ''; - private $author = ''; - private $authorSortKey = ''; - private $publisherName = ''; - private $publisherURL = ''; - private $date = 0; - private $rights = ''; - private $coverage = ''; - private $relation = ''; - private $sourceURL = ''; - private $chapterCount = 0; + private \PHPZip\Zip\File\Zip $zip; + + private string $title = ''; + + private string $language = 'en'; + + private string $identifier = ''; + + private string $identifierType = ''; + + private string $description = ''; + + private string $author = ''; + + private string $authorSortKey = ''; + + private string $publisherName = ''; + + private string $publisherURL = ''; + + private int $date = 0; + + private string $rights = ''; + + private string $coverage = ''; + + private string $relation = ''; + + private string $sourceURL = ''; + + private int $chapterCount = 0; + /** @var $opf Opf */ - private $opf = null; + private ?\PHPePub\Core\Structure\Opf $opf = null; + /** @var $ncx Ncx */ - private $ncx = null; - private $isFinalized = false; - private $isInitialized = false; - private $isCoverImageSet = false; - private $buildTOC = false; // ISO 8601 long - private $tocTitle = null; // short date format to placate ePubChecker. - private $tocFileName = null; - private $tocNavAdded = false; - private $tocCSSClass = null; - private $tocAddReferences = false; - private $tocCssFileName = null; - private $fileList = []; - private $dateformat = 'Y-m-d\TH:i:s.000000P'; - private $dateformatShort = 'Y-m-d'; - private $headerDateFormat = "D, d M Y H:i:s T"; - private $docRoot = null; - private $bookRoot = 'OEBPS/'; - private $EPubMark = true; - private $generator = ''; - private $log = null; - private $htmlContentHeader = "\n\n\n\n\n\n\n"; - private $htmlContentFooter = "\n\n"; - - /** @var array $viewport */ - private $viewport = null; - - private $dangermode = false; + private ?\PHPePub\Core\Structure\Ncx $ncx = null; + + private bool $isFinalized = false; + + private bool $isInitialized = false; + + private bool $isCoverImageSet = false; + + private bool $buildTOC = false; + + // ISO 8601 long + private $tocTitle; + + // short date format to placate ePubChecker. + private ?string $tocFileName = null; + + private bool $tocNavAdded = false; + + private $tocCSSClass; + + private bool $tocAddReferences = false; + + private ?string $tocCssFileName = null; + + private array $fileList = []; + + private string $dateformat = 'Y-m-d\TH:i:s.000000P'; + + private string $dateformatShort = 'Y-m-d'; + + private string $headerDateFormat = "D, d M Y H:i:s T"; + + private ?string $docRoot = null; + + private string $bookRoot = 'OEBPS/'; + + private bool $EPubMark = true; + + private string $generator = ''; + + private ?\PHPePub\Core\Logger $log = null; + + private string $htmlContentHeader = "\n\n\n\n\n\n\n"; + + private string $htmlContentFooter = "\n\n"; + + private ?array $viewport = null; + + private bool $dangermode = false; /** * Class constructor. @@ -155,10 +206,11 @@ public function __construct( $this->log->logLine('EPub class version....: ' . self::VERSION); $this->log->dumpInstalledModules(); } + $this->setUp(); } - private function setUp() + private function setUp(): void { $this->referencesOrder = [Reference::COVER => 'Cover Page', Reference::TITLE_PAGE => 'Title Page', Reference::ACKNOWLEDGEMENTS => 'Acknowledgements', Reference::BIBLIOGRAPHY => 'Bibliography', Reference::COLOPHON => 'Colophon', Reference::COPYRIGHT_PAGE => 'Copyright', Reference::DEDICATION => 'Dedication', Reference::EPIGRAPH => 'Epigraph', Reference::FOREWORD => 'Foreword', Reference::TABLE_OF_CONTENTS => 'Table of Contents', Reference::NOTES => 'Notes', Reference::PREFACE => 'Preface', Reference::TEXT => 'First Page', Reference::LIST_OF_ILLUSTRATIONS => 'List of Illustrations', Reference::LIST_OF_TABLES => 'List of Tables', Reference::GLOSSARY => 'Glossary', Reference::INDEX => 'Index']; @@ -213,13 +265,15 @@ public function __destruct() * * @return mixed $success FALSE if the addition failed, else the new NavPoint. */ - public function addChapter($chapterName, $fileName, $chapterData = null, $autoSplit = false, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") + public function addChapter($chapterName, $fileName, $chapterData = null, $autoSplit = false, $externalReferences = EPub::EXTERNAL_REF_IGNORE, string $baseDir = ""): bool|\PHPePub\Core\Structure\NCX\NavPoint { if ($this->isFinalized) { return false; } + $fileName = RelativePath::getRelativePath($fileName); $fileName = preg_replace('#^[/\.]+#i', "", $fileName); + $navPoint = false; $chapter = $chapterData; @@ -244,7 +298,7 @@ public function addChapter($chapterName, $fileName, $chapterData = null, $autoSp $chapter = StringHelper::encodeHtml($chapter); } - $this->chapterCount++; + ++$this->chapterCount; $this->addFile($fileName, "chapter" . $this->chapterCount, $chapter, "application/xhtml+xml"); $this->extractIdAttributes("chapter" . $this->chapterCount, $chapter); @@ -254,13 +308,13 @@ public function addChapter($chapterName, $fileName, $chapterData = null, $autoSp $this->ncx->addNavPoint($navPoint); $this->ncx->chapterList[$chapterName] = $navPoint; } elseif (is_array($chapter)) { - $this->log->logLine("addChapter: \$chapterName: $chapterName ; \$fileName: $fileName ; "); + $this->log->logLine(sprintf('addChapter: $chapterName: %s ; $fileName: %s ; ', $chapterName, $fileName)); $fileNameParts = pathinfo($fileName); $extension = $fileNameParts['extension']; $name = $fileNameParts['filename']; $partCount = 0; - $this->chapterCount++; + ++$this->chapterCount; foreach ($chapter as $oneChapter) { if ($this->encodeHTML === true) { @@ -270,13 +324,15 @@ public function addChapter($chapterName, $fileName, $chapterData = null, $autoSp if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) { $this->processChapterExternalReferences($oneChapter, $externalReferences, $baseDir); } - $partCount++; + + ++$partCount; $partName = $name . "_" . $partCount; $this->addFile($partName . "." . $extension, $partName, $oneChapter, "application/xhtml+xml"); $this->extractIdAttributes($partName, $oneChapter); $this->opf->addItemRef($partName); } + $partName = $name . "_1." . $extension; $navPoint = new NavPoint(StringHelper::decodeHtmlEntities($chapterName), $partName, $partName); @@ -284,7 +340,7 @@ public function addChapter($chapterName, $fileName, $chapterData = null, $autoSp $this->ncx->chapterList[$chapterName] = $navPoint; } elseif (!isset($chapterData) && strpos($fileName, "#") > 0) { - $this->chapterCount++; + ++$this->chapterCount; //$this->opf->addItemRef("chapter" . $this->chapterCount); $id = preg_split("/[#]/", $fileName); @@ -312,7 +368,7 @@ public function addChapter($chapterName, $fileName, $chapterData = null, $autoSp $this->ncx->addNavPoint($navPoint); $this->ncx->chapterList[$chapterName] = $navPoint; } elseif (!isset($chapterData) && $fileName == "TOC.xhtml") { - $this->chapterCount++; + ++$this->chapterCount; $this->opf->addItemRef("toc"); $navPoint = new NavPoint(StringHelper::decodeHtmlEntities($chapterName), $fileName, "chapter" . $this->chapterCount); @@ -328,9 +384,8 @@ public function addChapter($chapterName, $fileName, $chapterData = null, $autoSp * find all id attributes in the html document. * * @param string $chapterData - * @return array */ - public function findIdAttributes($chapterData) + public function findIdAttributes($chapterData): array { switch ($this->htmlFormat) { case EPub::FORMAT_HTML5: @@ -359,7 +414,7 @@ public function findIdAttributes($chapterData) * @param string $partName * @param string $chapterData */ - public function extractIdAttributes($partName, $chapterData) + public function extractIdAttributes($partName, $chapterData): void { $item = $this->opf->getItemById($partName); $ids = $this->findIdAttributes($chapterData); @@ -392,7 +447,7 @@ public function extractIdAttributes($partName, $chapterData) * * @return bool false if unsuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). */ - protected function processChapterExternalReferences(mixed &$doc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "") + protected function processChapterExternalReferences(mixed &$doc, $externalReferences = EPub::EXTERNAL_REF_ADD, string $baseDir = "", $htmlDir = ""): bool { if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { return false; @@ -433,12 +488,12 @@ protected function processChapterExternalReferences(mixed &$doc, $externalRefere $bodyNode = $xmlDoc->getElementsByTagName("body"); $htmlNS = ""; - for ($index = 0; $index < $htmlNode->item(0)->attributes->length; $index++) { + for ($index = 0; $index < $htmlNode->item(0)->attributes->length; ++$index) { $nodeName = $htmlNode->item(0)->attributes->item($index)->nodeName; $nodeValue = $htmlNode->item(0)->attributes->item($index)->nodeValue; if ($nodeName != "xmlns") { - $htmlNS .= " $nodeName=\"$nodeValue\""; + $htmlNS .= sprintf(' %s="%s"', $nodeName, $nodeValue); } } @@ -451,7 +506,7 @@ protected function processChapterExternalReferences(mixed &$doc, $externalRefere $xml2Doc->lookupPrefix("http://www.w3.org/1999/xhtml"); $xml2Doc->loadXML("\n\n" - . "\n\n"); + . '\n\n"); $html = $xml2Doc->getElementsByTagName("html")->item(0); $html->appendChild($xml2Doc->importNode($headNode->item(0), true)); $html->appendChild($xml2Doc->importNode($bodyNode->item(0), true)); @@ -478,15 +533,16 @@ protected function processChapterExternalReferences(mixed &$doc, $externalRefere * * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). */ - protected function processChapterStyles(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "") + protected function processChapterStyles(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = ""): bool { if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { return false; } + // process inlined CSS styles in style tags. $styles = $xmlDoc->getElementsByTagName("style"); $styleCount = $styles->length; - for ($styleIdx = 0; $styleIdx < $styleCount; $styleIdx++) { + for ($styleIdx = 0; $styleIdx < $styleCount; ++$styleIdx) { $style = $styles->item($styleIdx); $styleData = preg_replace('#[/\*\s]*\<\!\[CDATA\[[\s\*/]*#im', "", $style->nodeValue); @@ -511,7 +567,7 @@ protected function processChapterStyles(&$xmlDoc, $externalReferences = EPub::EX * * @return bool FALSE if unsuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). */ - protected function processCSSExternalReferences(&$cssFile, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $cssDir = "") + protected function processCSSExternalReferences(&$cssFile, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $cssDir = ""): bool { if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { return false; @@ -522,7 +578,7 @@ protected function processCSSExternalReferences(&$cssFile, $externalReferences = preg_match_all('#url\s*\([\'\"\s]*(.+?)[\'\"\s]*\)#im', $cssFile, $imgs, PREG_SET_ORDER); $itemCount = count($imgs); - for ($idx = 0; $idx < $itemCount; $idx++) { + for ($idx = 0; $idx < $itemCount; ++$idx) { $img = $imgs[$idx]; if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES || $externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) { $cssFile = str_replace($img[0], "", $cssFile); @@ -554,52 +610,50 @@ protected function processCSSExternalReferences(&$cssFile, $externalReferences = * @param string &$isSourceExternal (referenced) Return value, will be set to TRUE if the image originated from a full URL. * @param string $baseDir Default is "", meaning it is pointing to the document root. * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. - * - * @return bool */ - protected function resolveImage($source, &$internalPath, &$internalSrc, &$isSourceExternal, $baseDir = "", $htmlDir = "") + protected function resolveImage(string $source, &$internalPath, &$internalSrc, &$isSourceExternal, string $baseDir = "", string $htmlDir = ""): bool { if ($this->isFinalized) { return false; } + $imageData = null; if (preg_match('#^(http|ftp)s?://#i', $source) == 1) { $urlinfo = parse_url($source); - if (str_contains($urlinfo['path'], $baseDir . "/")) { $internalSrc = FileHelper::sanitizeFileName(urldecode(substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir . "/") + strlen($baseDir) + 1))); } + $internalPath = $urlinfo["scheme"] . "/" . $urlinfo["host"] . "/" . pathinfo($urlinfo["path"], PATHINFO_DIRNAME); $isSourceExternal = true; $imageData = ImageHelper::getImage($this, $source); - } else { - if (str_starts_with($source, "/")) { - $internalPath = pathinfo($source, PATHINFO_DIRNAME); - - $path = $source; - if (!file_exists($path)) { - $path = $this->docRoot . $path; - } - - $imageData = ImageHelper::getImage($this, $path); - } else { - $internalPath = $htmlDir . "/" . preg_replace('#^[/\.]+#', '', pathinfo($source, PATHINFO_DIRNAME)); + } elseif (str_starts_with($source, "/")) { + $internalPath = pathinfo($source, PATHINFO_DIRNAME); + $path = $source; + if (!file_exists($path)) { + $path = $this->docRoot . $path; + } - $path = $baseDir . "/" . $source; - if (!file_exists($path)) { - $path = $this->docRoot . $path; - } + $imageData = ImageHelper::getImage($this, $path); + } else { + $internalPath = $htmlDir . "/" . preg_replace('#^[/\.]+#', '', pathinfo($source, PATHINFO_DIRNAME)); - $imageData = ImageHelper::getImage($this, $path); + $path = $baseDir . "/" . $source; + if (!file_exists($path)) { + $path = $this->docRoot . $path; } + + $imageData = ImageHelper::getImage($this, $path); } + if ($imageData !== false) { $iSrcInfo = pathinfo((string) $internalSrc); if (!empty($imageData['ext']) && (!isset($iSrcInfo['extension']) || $imageData['ext'] != $iSrcInfo['extension'])) { $internalSrc = $iSrcInfo['filename'] . "." . $imageData['ext']; } + $internalPath = RelativePath::getRelativePath("images/" . $internalPath . "/" . $internalSrc); if (!array_key_exists($internalPath, $this->fileList)) { $this->addFile($internalPath, "i_" . $internalSrc, $imageData['image'], $imageData['mime']); @@ -621,14 +675,16 @@ protected function resolveImage($source, &$internalPath, &$internalSrc, &$isSour * * @return bool $success */ - public function addFileToMETAINF($fileName, $fileData) + public function addFileToMETAINF($fileName, $fileData): bool { if ($this->isFinalized) { return false; } + if (!$this->isInitialized) { $this->initialize(); } + $fileName = FileHelper::normalizeFileName($fileName); $this->zip->addFile($fileData, "META-INF/" . $fileName); @@ -646,14 +702,16 @@ public function addFileToMETAINF($fileName, $fileData) * * @return bool $success */ - public function addFile($fileName, $fileId, $fileData, $mimetype) + public function addFile($fileName, $fileId, $fileData, $mimetype): bool { if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) { return false; } + if (!$this->isInitialized) { $this->initialize(); } + $fileName = FileHelper::normalizeFileName($fileName); $compress = (!str_starts_with($mimetype, "image/")); @@ -677,15 +735,16 @@ public function addFile($fileName, $fileId, $fileData, $mimetype) * * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). */ - protected function processChapterLinks(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", $backPath = "") + protected function processChapterLinks(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, string $baseDir = "", $htmlDir = "", string $backPath = ""): bool { if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { return false; } + // process link tags. $links = $xmlDoc->getElementsByTagName("link"); $linkCount = $links->length; - for ($linkIdx = 0; $linkIdx < $linkCount; $linkIdx++) { + for ($linkIdx = 0; $linkIdx < $linkCount; ++$linkIdx) { /** @var $link \DOMElement */ $link = $links->item($linkIdx); $source = $link->attributes->getNamedItem("href")->nodeValue; @@ -696,18 +755,15 @@ protected function processChapterLinks(&$xmlDoc, $externalReferences = EPub::EXT if (preg_match('#^(http|ftp)s?://#i', $source) == 1) { $urlinfo = parse_url($source); - if (str_contains($urlinfo['path'], $baseDir . "/")) { $internalSrc = substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir . "/") + strlen($baseDir) + 1); } @$sourceData = FileHelper::getFileContents($source); + } elseif (str_starts_with($source, "/")) { + @$sourceData = file_get_contents($this->docRoot . $source); } else { - if (str_starts_with($source, "/")) { - @$sourceData = file_get_contents($this->docRoot . $source); - } else { - @$sourceData = file_get_contents($this->docRoot . $baseDir . "/" . $source); - } + @$sourceData = file_get_contents($this->docRoot . $baseDir . "/" . $source); } if (!empty($sourceData)) { @@ -716,6 +772,7 @@ protected function processChapterLinks(&$xmlDoc, $externalReferences = EPub::EXT if (empty($mime)) { $mime = "text/plain"; } + if ($mime == "text/css") { $this->processCSSExternalReferences($sourceData, $externalReferences, $baseDir, $htmlDir); $this->addCSSFile($internalSrc, $internalSrc, $sourceData, EPub::EXTERNAL_REF_IGNORE, $baseDir); @@ -723,6 +780,7 @@ protected function processChapterLinks(&$xmlDoc, $externalReferences = EPub::EXT } else { $this->addFile($internalSrc, $internalSrc, $sourceData, $mime); } + $this->fileList[$internalSrc] = $source; } else { $link->setAttribute("href", $backPath . $internalSrc); @@ -744,11 +802,12 @@ protected function processChapterLinks(&$xmlDoc, $externalReferences = EPub::EXT * * @return bool $success */ - public function addCSSFile($fileName, $fileId, $fileData, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") + public function addCSSFile($fileName, string $fileId, $fileData, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = ""): bool { if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) { return false; } + $fileName = RelativePath::getRelativePath($fileName); $fileName = preg_replace('#^[/\.]+#i', "", $fileName); @@ -779,46 +838,45 @@ public function addCSSFile($fileName, $fileId, $fileData, $externalReferences = * * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). */ - protected function processChapterImages(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", $backPath = "") + protected function processChapterImages(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", string $backPath = ""): bool { if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { return false; } + // process img tags. $postProcDomElememts = []; $images = $xmlDoc->getElementsByTagName("img"); $itemCount = $images->length; - for ($idx = 0; $idx < $itemCount; $idx++) { + for ($idx = 0; $idx < $itemCount; ++$idx) { /** @var $img \DOMElement */ $img = $images->item($idx); if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES) { $postProcDomElememts[] = $img; + } elseif ($externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) { + $altNode = $img->attributes->getNamedItem("alt"); + $alt = "image"; + if ($altNode !== null && strlen($altNode->nodeValue) > 0) { + $alt = $altNode->nodeValue; + } + + $postProcDomElememts[] = [$img, StringHelper::createDomFragment($xmlDoc, "[" . $alt . "]")]; } else { - if ($externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) { - $altNode = $img->attributes->getNamedItem("alt"); - $alt = "image"; - if ($altNode !== null && strlen($altNode->nodeValue) > 0) { - $alt = $altNode->nodeValue; - } - $postProcDomElememts[] = [$img, StringHelper::createDomFragment($xmlDoc, "[" . $alt . "]")]; - } else { - $source = $img->attributes->getNamedItem("src")->nodeValue; + $source = $img->attributes->getNamedItem("src")->nodeValue; - $parsedSource = parse_url($source); - $internalSrc = FileHelper::sanitizeFileName(urldecode(pathinfo($parsedSource['path'], PATHINFO_BASENAME))); - $internalPath = ""; - $isSourceExternal = false; + $parsedSource = parse_url($source); + $internalSrc = FileHelper::sanitizeFileName(urldecode(pathinfo($parsedSource['path'], PATHINFO_BASENAME))); + $internalPath = ""; + $isSourceExternal = false; - if ($this->resolveImage($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $htmlDir)) { - $img->setAttribute("src", $backPath . $internalPath); - } else { - if ($isSourceExternal) { - $postProcDomElememts[] = $img; // External image is missing - } - } // else do nothing, if the image is local, and missing, assume it's been generated. - } + if ($this->resolveImage($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $htmlDir)) { + $img->setAttribute("src", $backPath . $internalPath); + } elseif ($isSourceExternal) { + $postProcDomElememts[] = $img; + // External image is missing + } // else do nothing, if the image is local, and missing, assume it's been generated. } } @@ -845,7 +903,7 @@ protected function processChapterImages(&$xmlDoc, $externalReferences = EPub::EX * * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). */ - protected function processChapterSources(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", $backPath = "") + protected function processChapterSources(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", string $backPath = ""): bool { if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { return false; @@ -859,35 +917,33 @@ protected function processChapterSources(&$xmlDoc, $externalReferences = EPub::E $postProcDomElememts = []; $images = $xmlDoc->getElementsByTagName("source"); $itemCount = $images->length; - for ($idx = 0; $idx < $itemCount; $idx++) { + for ($idx = 0; $idx < $itemCount; ++$idx) { /** @var $img \DOMElement */ $img = $images->item($idx); if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES) { $postProcDomElememts[] = $img; + } elseif ($externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) { + $altNode = $img->attributes->getNamedItem("alt"); + $alt = "image"; + if ($altNode !== null && strlen($altNode->nodeValue) > 0) { + $alt = $altNode->nodeValue; + } + + $postProcDomElememts[] = [$img, StringHelper::createDomFragment($xmlDoc, "[" . $alt . "]")]; } else { - if ($externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) { - $altNode = $img->attributes->getNamedItem("alt"); - $alt = "image"; - if ($altNode !== null && strlen($altNode->nodeValue) > 0) { - $alt = $altNode->nodeValue; - } - $postProcDomElememts[] = [$img, StringHelper::createDomFragment($xmlDoc, "[" . $alt . "]")]; - } else { - $source = $img->attributes->getNamedItem("src")->nodeValue; + $source = $img->attributes->getNamedItem("src")->nodeValue; - $parsedSource = parse_url($source); - $internalSrc = FileHelper::sanitizeFileName(urldecode(pathinfo($parsedSource['path'], PATHINFO_BASENAME))); - $internalPath = ""; - $isSourceExternal = false; + $parsedSource = parse_url($source); + $internalSrc = FileHelper::sanitizeFileName(urldecode(pathinfo($parsedSource['path'], PATHINFO_BASENAME))); + $internalPath = ""; + $isSourceExternal = false; - if ($this->resolveMedia($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $htmlDir)) { - $img->setAttribute("src", $backPath . $internalPath); - } else { - if ($isSourceExternal) { - $postProcDomElememts[] = $img; // External image is missing - } - } // else do nothing, if the image is local, and missing, assume it's been generated. - } + if ($this->resolveMedia($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $htmlDir)) { + $img->setAttribute("src", $backPath . $internalPath); + } elseif ($isSourceExternal) { + $postProcDomElememts[] = $img; + // External image is missing + } // else do nothing, if the image is local, and missing, assume it's been generated. } } @@ -903,42 +959,38 @@ protected function processChapterSources(&$xmlDoc, $externalReferences = EPub::E * @param string $isSourceExternal (referenced) Return value, will be set to TRUE if the image originated from a full URL. * @param string $baseDir Default is "", meaning it is pointing to the document root. * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. - * - * @return bool */ - protected function resolveMedia($source, &$internalPath, &$internalSrc, &$isSourceExternal, $baseDir = "", $htmlDir = "") + protected function resolveMedia(string $source, &$internalPath, &$internalSrc, &$isSourceExternal, string $baseDir = "", string $htmlDir = ""): bool { if ($this->isFinalized) { return false; } + $mediaPath = null; $tmpFile = null; if (preg_match('#^(http|ftp)s?://#i', $source) == 1) { $urlInfo = parse_url($source); - if (str_contains($urlInfo['path'], $baseDir . "/")) { $internalSrc = substr($urlInfo['path'], strpos($urlInfo['path'], $baseDir . "/") + strlen($baseDir) + 1); } + $internalPath = $urlInfo["scheme"] . "/" . $urlInfo["host"] . "/" . pathinfo($urlInfo["path"], PATHINFO_DIRNAME); $isSourceExternal = true; $mediaPath = FileHelper::getFileContents($source, true); $tmpFile = $mediaPath; + } elseif (str_starts_with($source, "/")) { + $internalPath = pathinfo($source, PATHINFO_DIRNAME); + $mediaPath = $source; + if (!file_exists($mediaPath)) { + $mediaPath = $this->docRoot . $mediaPath; + } } else { - if (str_starts_with($source, "/")) { - $internalPath = pathinfo($source, PATHINFO_DIRNAME); - - $mediaPath = $source; - if (!file_exists($mediaPath)) { - $mediaPath = $this->docRoot . $mediaPath; - } - } else { - $internalPath = $htmlDir . "/" . preg_replace('#^[/\.]+#', '', pathinfo($source, PATHINFO_DIRNAME)); + $internalPath = $htmlDir . "/" . preg_replace('#^[/\.]+#', '', pathinfo($source, PATHINFO_DIRNAME)); - $mediaPath = $baseDir . "/" . $source; - if (!file_exists($mediaPath)) { - $mediaPath = $this->docRoot . $mediaPath; - } + $mediaPath = $baseDir . "/" . $source; + if (!file_exists($mediaPath)) { + $mediaPath = $this->docRoot . $mediaPath; } } @@ -951,6 +1003,7 @@ protected function resolveMedia($source, &$internalPath, &$internalSrc, &$isSour ) { $this->fileList[$internalPath] = $source; } + if (isset($tmpFile)) { unlink($tmpFile); } @@ -971,14 +1024,16 @@ protected function resolveMedia($source, &$internalPath, &$internalSrc, &$isSour * * @return bool $success */ - public function addLargeFile($fileName, $fileId, $filePath, $mimetype) + public function addLargeFile($fileName, $fileId, $filePath, $mimetype): bool { if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) { return false; } + if (!$this->isInitialized) { $this->initialize(); } + $fileName = FileHelper::normalizeFileName($fileName); if ($this->zip->addLargeFile($filePath, $this->bookRoot . $fileName)) { @@ -994,12 +1049,13 @@ public function addLargeFile($fileName, $fileId, $filePath, $mimetype) /** * initialize defaults. */ - private function initialize() + private function initialize(): void { if ($this->isInitialized) { return; } - if (strlen((string) $this->bookRoot) != 0 && $this->bookRoot != 'OEBPS/') { + + if (strlen($this->bookRoot) != 0 && $this->bookRoot != 'OEBPS/') { $this->setBookRoot($this->bookRoot); } @@ -1032,26 +1088,23 @@ private function initialize() * * @param string $bookRoot */ - public function setBookRoot($bookRoot) + public function setBookRoot($bookRoot): void { if ($this->isInitialized) { die("bookRoot can't be set after book initialization (first file added)."); } + $bookRoot = trim($bookRoot); if (strlen($bookRoot) <= 1 || $bookRoot == '/') { $bookRoot = ''; - } else { - if (!BinStringStatic::endsWith($bookRoot, '/')) { - $bookRoot .= '/'; - } + } elseif (!str_ends_with($bookRoot, '/')) { + $bookRoot .= '/'; } + $this->bookRoot = $bookRoot; } - /** - * @return bool - */ - public function isEPubVersion2() + public function isEPubVersion2(): bool { return $this->bookVersion === EPub::BOOK_VERSION_EPUB2; } @@ -1069,7 +1122,7 @@ public function isEPubVersion2() * * @return bool|NavPoint The new NavPoint for that level. */ - public function subLevel($navTitle = null, $navId = null, $navClass = null, $isNavHidden = false, $writingDirection = null) + public function subLevel($navTitle = null, $navId = null, $navClass = null, $isNavHidden = false, $writingDirection = null): \PHPePub\Core\Structure\NCX\NavPoint|bool { return $this->ncx->subLevel(StringHelper::decodeHtmlEntities($navTitle), $navId, $navClass, $isNavHidden, $writingDirection); } @@ -1079,7 +1132,7 @@ public function subLevel($navTitle = null, $navId = null, $navClass = null, $isN * * Subsequent chapters will be added to this chapters parent level. */ - public function backLevel() + public function backLevel(): void { $this->ncx->backLevel(); } @@ -1089,7 +1142,7 @@ public function backLevel() * * Subsequent chapters will be added to the rooot NavMap. */ - public function rootLevel() + public function rootLevel(): void { $this->ncx->rootLevel(); } @@ -1101,7 +1154,7 @@ public function rootLevel() * * @param int $newLevel */ - public function setCurrentLevel($newLevel) + public function setCurrentLevel($newLevel): void { $this->ncx->setCurrentLevel($newLevel); } @@ -1121,7 +1174,7 @@ public function getCurrentLevel() * @param string $nsName * @param string $nsURI */ - public function addCustomNamespace($nsName, $nsURI) + public function addCustomNamespace($nsName, $nsURI): void { if ($this->isFinalized) { return; @@ -1138,7 +1191,7 @@ public function addCustomNamespace($nsName, $nsURI) * @param string $name * @param string $URI */ - public function addCustomPrefix($name, $URI) + public function addCustomPrefix($name, $URI): void { if ($this->isFinalized) { return; @@ -1157,7 +1210,7 @@ public function addCustomPrefix($name, $URI) * * @param MetaValue $value */ - public function addCustomMetaValue($value) + public function addCustomMetaValue($value): void { if ($this->isFinalized) { return; @@ -1177,7 +1230,7 @@ public function addCustomMetaValue($value) * @param string $name property name, including the namespace declaration, ie. "dcterms:modified" * @param string $content */ - public function addCustomMetaProperty($name, $content) + public function addCustomMetaProperty($name, $content): void { if ($this->isFinalized) { return; @@ -1185,6 +1238,7 @@ public function addCustomMetaProperty($name, $content) $this->opf->addMetaProperty($name, $content); } + /** * Add custom metadata to the book. * @@ -1193,7 +1247,7 @@ public function addCustomMetaProperty($name, $content) * @param string $name * @param string $content */ - public function addCustomMetadata($name, $content) + public function addCustomMetadata($name, $content): void { if ($this->isFinalized) { return; @@ -1210,7 +1264,7 @@ public function addCustomMetadata($name, $content) * @param string $dublinCoreConstant name * @param string $value */ - public function addDublinCoreMetadata($dublinCoreConstant, $value) + public function addDublinCoreMetadata($dublinCoreConstant, $value): void { if ($this->isFinalized) { return; @@ -1231,7 +1285,7 @@ public function addDublinCoreMetadata($dublinCoreConstant, $value) * * @return bool $success */ - public function setCoverImage($fileName, $imageData = null, $mimetype = null) + public function setCoverImage($fileName, $imageData = null, $mimetype = null): bool { if ($this->isFinalized || $this->isCoverImageSet || array_key_exists("CoverPage.xhtml", $this->fileList)) { return false; @@ -1248,10 +1302,12 @@ public function setCoverImage($fileName, $imageData = null, $mimetype = null) $fileName = $rp; } } + $image = ImageHelper::getImage($this, $fileName); if (false === $image) { return false; } + $imageData = $image['image']; $mimetype = $image['mime']; $fileName = preg_replace('#\.[^\.]+$#', "." . $image['ext'], $fileName); @@ -1265,17 +1321,20 @@ public function setCoverImage($fileName, $imageData = null, $mimetype = null) [$width, $height, $type, $attr] = getimagesize($fileName); $mimetype = image_type_to_mime_type($type); } + if (empty($mimetype)) { $ext = strtolower($path['extension']); if ($ext == "jpg") { $ext = "jpeg"; } + $mimetype = "image/" . $ext; } if ($this->isEPubVersion2()) { - $coverPage = "\n" - . " +\n" . "\n" . "\t\n" @@ -1305,12 +1364,16 @@ public function setCoverImage($fileName, $imageData = null, $mimetype = null) . "\t\n" . "\n"; } - $coverPageCss = "@page, body, div, img {\n" - . "\tpadding: 0pt;\n" - . "\tmargin:0pt;\n" - . "}\n\nbody {\n" - . "\ttext-align: center;\n" - . "}\n"; + + $coverPageCss = '@page, body, div, img { + padding: 0pt; + margin:0pt; +} + +body { + text-align: center; +} +'; $this->addCSSFile("Styles/CoverPage.css", "CoverPageCss", $coverPageCss); $this->addFile($imgPath, "CoverImage", $imageData, $mimetype); @@ -1335,11 +1398,12 @@ public function setCoverImage($fileName, $imageData = null, $mimetype = null) * * @return bool $success */ - public function addReferencePage($pageName, $fileName, $pageData, $reference, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") + public function addReferencePage($pageName, $fileName, $pageData, string $reference, $externalReferences = EPub::EXTERNAL_REF_IGNORE, string $baseDir = ""): bool { if ($this->isFinalized) { return false; } + $fileName = RelativePath::getRelativePath($fileName); $fileName = preg_replace('#^[/\.]+#i', "", $fileName); @@ -1380,7 +1444,7 @@ public function addReferencePage($pageName, $fileName, $pageData, $reference, $e * * @return string $content */ - private function wrapChapter($content) + private function wrapChapter(string $content): string { return $this->htmlContentHeader . "\n" . $content . "\n" . $this->htmlContentFooter; } @@ -1391,7 +1455,7 @@ private function wrapChapter($content) * @access public * @return number of chapters */ - public function getChapterCount() + public function getChapterCount(): int { return $this->chapterCount; } @@ -1402,7 +1466,7 @@ public function getChapterCount() * @access public * @return string $title */ - public function getTitle() + public function getTitle(): string { return $this->title; } @@ -1412,16 +1476,16 @@ public function getTitle() * * Used for the dc:title metadata parameter in the OPF file as well as the DocTitle attribute in the NCX file. * - * @param string $title * * @access public * @return bool $success */ - public function setTitle($title) + public function setTitle(string $title): bool { if ($this->isFinalized) { return false; } + $this->title = $title; return true; @@ -1433,7 +1497,7 @@ public function setTitle($title) * @access public * @return string $language */ - public function getLanguage() + public function getLanguage(): string { return $this->language; } @@ -1451,11 +1515,12 @@ public function getLanguage() * @access public * @return bool $success */ - public function setLanguage($language) + public function setLanguage($language): bool { - if ($this->isFinalized || 0 === preg_match('/^((?([A-Za-z]{2,3}(-(?[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?[A-Za-z]{2}|[0-9]{3}))?)$/', $language)) { + if ($this->isFinalized || 0 === preg_match('/^((?([A-Za-z]{2,3}(-(?[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?[A-Za-z]{2}|\d{3}))?)$/', $language)) { return false; } + $this->language = $language; return true; @@ -1467,7 +1532,7 @@ public function setLanguage($language) * @access public * @return string $identifier */ - public function getIdentifier() + public function getIdentifier(): string { return $this->identifier; } @@ -1489,17 +1554,17 @@ public function getIdentifier() * EPub::IDENTIFIER_ISBN * EPub::IDENTIFIER_UUID * - * @param string $identifier * @param string $identifierType * * @access public * @return bool $success */ - public function setIdentifier($identifier, $identifierType) + public function setIdentifier(string $identifier, $identifierType): bool { if ($this->isFinalized || ($identifierType !== EPub::IDENTIFIER_URI && $identifierType !== EPub::IDENTIFIER_ISBN && $identifierType !== EPub::IDENTIFIER_UUID)) { return false; } + $this->identifier = $identifier; $this->identifierType = $identifierType; @@ -1512,7 +1577,7 @@ public function setIdentifier($identifier, $identifierType) * @access public * @return string $identifierType */ - public function getIdentifierType() + public function getIdentifierType(): string { return $this->identifierType; } @@ -1523,7 +1588,7 @@ public function getIdentifierType() * @access public * @return string $description */ - public function getDescription() + public function getDescription(): string { return $this->description; } @@ -1539,16 +1604,16 @@ public function getDescription() * * Used for the dc:source metadata parameter in the OPF file * - * @param string $description * * @access public * @return bool $success */ - public function setDescription($description) + public function setDescription(string $description): bool { if ($this->isFinalized) { return false; } + $this->description = $description; return true; @@ -1560,7 +1625,7 @@ public function setDescription($description) * @access public * @return string $author */ - public function getAuthor() + public function getAuthor(): string { return $this->author; } @@ -1580,17 +1645,16 @@ public function getAuthor() * docAuthor attribure in the NCX file. * The sort key is used for the opf:file-as attribute in dc:creator. * - * @param string $author - * @param string $authorSortKey * * @access public * @return bool $success */ - public function setAuthor($author, $authorSortKey) + public function setAuthor(string $author, string $authorSortKey): bool { if ($this->isFinalized) { return false; } + $this->author = $author; $this->authorSortKey = $authorSortKey; @@ -1607,17 +1671,16 @@ public function setAuthor($author, $authorSortKey) * * Used for the dc:publisher and dc:relation metadata parameters in the OPF file. * - * @param string $publisherName - * @param string $publisherURL * * @access public * @return bool $success */ - public function setPublisher($publisherName, $publisherURL) + public function setPublisher(string $publisherName, string $publisherURL): bool { if ($this->isFinalized) { return false; } + $this->publisherName = $publisherName; $this->publisherURL = $publisherURL; @@ -1630,7 +1693,7 @@ public function setPublisher($publisherName, $publisherURL) * @access public * @return string $publisherName */ - public function getPublisherName() + public function getPublisherName(): string { return $this->publisherName; } @@ -1641,7 +1704,7 @@ public function getPublisherName() * @access public * @return string $publisherURL */ - public function getPublisherURL() + public function getPublisherURL(): string { return $this->publisherURL; } @@ -1652,7 +1715,7 @@ public function getPublisherURL() * @access public * @return string $date */ - public function getDate() + public function getDate(): int { return $this->date; } @@ -1670,16 +1733,16 @@ public function getDate() * * Used for the dc:date metadata parameter in the OPF file * - * @param int $timestamp * * @access public * @return bool $success */ - public function setDate($timestamp) + public function setDate(int $timestamp): bool { if ($this->isFinalized) { return false; } + $this->date = $timestamp; $this->opf->date = $timestamp; @@ -1692,7 +1755,7 @@ public function setDate($timestamp) * @access public * @return string $rights */ - public function getRights() + public function getRights(): string { return $this->rights; } @@ -1708,16 +1771,16 @@ public function getRights() * * Used for the dc:rights metadata parameter in the OPF file * - * @param string $rightsText * * @access public * @return bool $success */ - public function setRights($rightsText) + public function setRights(string $rightsText): bool { if ($this->isFinalized) { return false; } + $this->rights = $rightsText; return true; @@ -1735,11 +1798,12 @@ public function setRights($rightsText) * * @param string $subject */ - public function setSubject($subject) + public function setSubject($subject): void { if ($this->isFinalized) { return; } + $this->opf->addDCMeta(DublinCore::SUBJECT, StringHelper::decodeHtmlEntities($subject)); } @@ -1749,7 +1813,7 @@ public function setSubject($subject) * @access public * @return string $sourceURL */ - public function getSourceURL() + public function getSourceURL(): string { return $this->sourceURL; } @@ -1765,16 +1829,16 @@ public function getSourceURL() * * Used for the dc:source metadata parameter in the OPF file * - * @param string $sourceURL * * @access public * @return bool $success */ - public function setSourceURL($sourceURL) + public function setSourceURL(string $sourceURL): bool { if ($this->isFinalized) { return false; } + $this->sourceURL = $sourceURL; return true; @@ -1786,7 +1850,7 @@ public function setSourceURL($sourceURL) * @access public * @return string $coverage */ - public function getCoverage() + public function getCoverage(): string { return $this->coverage; } @@ -1810,16 +1874,16 @@ public function getCoverage() * * Same as ->addDublinCoreMetadata(DublinCore::COVERAGE, $coverage); * - * @param string $coverage * * @access public * @return bool $success */ - public function setCoverage($coverage) + public function setCoverage(string $coverage): bool { if ($this->isFinalized) { return false; } + $this->coverage = $coverage; return true; @@ -1830,7 +1894,7 @@ public function setCoverage($coverage) * * @return string The relation. */ - public function getRelation() + public function getRelation(): string { return $this->relation; } @@ -1842,14 +1906,13 @@ public function getRelation() * * Recommended best practice is to identify the related resource by means * of a string conforming to a formal identification system. - * - * @param string $relation */ - public function setRelation($relation) + public function setRelation(string $relation): void { if ($this->isFinalized) { return; } + $this->relation = $relation; } @@ -1858,7 +1921,7 @@ public function setRelation($relation) * * @return string The generator identity string. */ - public function getGenerator() + public function getGenerator(): string { return $this->generator; } @@ -1868,14 +1931,13 @@ public function getGenerator() * * The generator is a meta tag added to the ncx file, it is not visible * from within the book, but is a kind of electronic watermark. - * - * @param string $generator */ - public function setGenerator($generator) + public function setGenerator(string $generator): void { if ($this->isFinalized) { return; } + $this->generator = $generator; } @@ -1889,11 +1951,12 @@ public function setGenerator($generator) * @access public * @return bool $success */ - public function setShortDateFormat() + public function setShortDateFormat(): bool { if ($this->isFinalized) { return false; } + $this->dateformat = $this->dateformatShort; return true; @@ -1905,14 +1968,13 @@ public function setShortDateFormat() * @param string $referencesTitle * @param string $referencesId * @param string $referencesClass - * - * @return bool */ - public function setReferencesTitle($referencesTitle = "Guide", $referencesId = "", $referencesClass = "references") + public function setReferencesTitle($referencesTitle = "Guide", $referencesId = "", $referencesClass = "references"): bool { if ($this->isFinalized) { return false; } + $this->ncx->referencesTitle = is_string($referencesTitle) ? trim($referencesTitle) : "Guide"; $this->ncx->referencesId = is_string($referencesId) ? trim($referencesId) : "references"; $this->ncx->referencesClass = is_string($referencesClass) ? trim($referencesClass) : "references"; @@ -1924,14 +1986,13 @@ public function setReferencesTitle($referencesTitle = "Guide", $referencesId = " * Set the references title for the ePub 3 landmarks section * * @param bool $isReferencesAddedToToc - * - * @return bool */ - public function setisReferencesAddedToToc($isReferencesAddedToToc = true) + public function setisReferencesAddedToToc($isReferencesAddedToToc = true): bool { if ($this->isFinalized) { return false; } + $this->isReferencesAddedToToc = $isReferencesAddedToToc === true; return true; @@ -1941,9 +2002,8 @@ public function setisReferencesAddedToToc($isReferencesAddedToToc = true) * Get Book status. * * @access public - * @return bool */ - public function isFinalized() + public function isFinalized(): bool { return $this->isFinalized; } @@ -1957,20 +2017,20 @@ public function isFinalized() * @param bool $addReferences include reference pages in the TOC, using the $referencesOrder array to determine the order of the pages in the TOC. Default is TRUE. * @param bool $addToIndex Add the TOC to the NCX index at the current leve/position. Default is FALSE * @param string $tocFileName Change the default name of the TOC file. The default is "TOC.xhtml" - * - * @return bool */ - public function buildTOC($cssFileName = null, $tocCSSClass = "toc", $title = "Table of Contents", $addReferences = true, $addToIndex = false, $tocFileName = "TOC.xhtml") + public function buildTOC($cssFileName = null, $tocCSSClass = "toc", $title = "Table of Contents", bool $addReferences = true, $addToIndex = false, $tocFileName = "TOC.xhtml"): bool { if ($this->isFinalized) { return false; } + $this->buildTOC = true; $this->tocTitle = $title; $this->tocFileName = FileHelper::normalizeFileName($tocFileName); if (!empty($cssFileName)) { $this->tocCssFileName = FileHelper::normalizeFileName($cssFileName); } + $this->tocCSSClass = $tocCSSClass; $this->tocAddReferences = $addReferences; @@ -1986,18 +2046,17 @@ public function buildTOC($cssFileName = null, $tocCSSClass = "toc", $title = "Ta $this->ncx->referencesName[Reference::TABLE_OF_CONTENTS] = $title; } } + return true; } /** * Save the ePub file to local disk. * - * @param string $fileName * @param string $baseDir If empty baseDir is absolute to server path, if omitted it's relative to script path - * * @return string The sent file name if successful, FALSE if it failed. */ - public function saveBook($fileName, $baseDir = '.') + public function saveBook(string $fileName, string $baseDir = '.'): string|bool { // Make fileName safe @@ -2008,7 +2067,7 @@ public function saveBook($fileName, $baseDir = '.') $this->finalize(); } - if (!BinStringStatic::endsWith($fileName, ".epub")) { + if (!str_ends_with($fileName, ".epub")) { $fileName .= ".epub"; } @@ -2016,7 +2075,7 @@ public function saveBook($fileName, $baseDir = '.') $fh = fopen($baseDir . '/' . $fileName, "w"); if ($fh) { - fputs($fh, $this->getBook()); + fwrite($fh, $this->getBook()); fclose($fh); // if file is written return TRUE @@ -2033,13 +2092,13 @@ public function saveBook($fileName, $baseDir = '.') * * @return bool $success */ - public function finalize() + public function finalize(): bool { - if ($this->isFinalized || $this->chapterCount == 0 || empty($this->title) || empty($this->language)) { + if ($this->isFinalized || $this->chapterCount == 0 || ($this->title === '' || $this->title === '0') || ($this->language === '' || $this->language === '0')) { return false; } - if (empty($this->identifier) || empty($this->identifierType)) { + if ($this->identifier === '' || $this->identifier === '0' || ($this->identifierType === '' || $this->identifierType === '0')) { $this->setIdentifier(StringHelper::createUUID(4), EPub::IDENTIFIER_UUID); } @@ -2047,11 +2106,11 @@ public function finalize() $this->date = time(); } - if (empty($this->sourceURL)) { + if ($this->sourceURL === '' || $this->sourceURL === '0') { $this->sourceURL = URLHelper::getCurrentPageURL(); } - if (empty($this->publisherURL)) { + if ($this->publisherURL === '' || $this->publisherURL === '0') { $this->sourceURL = URLHelper::getCurrentServerURL(); } @@ -2061,39 +2120,40 @@ public function finalize() $DCdate = new DublinCore(DublinCore::DATE, gmdate($this->dateformat, $this->date)); $DCdate->addOpfAttr("event", "publication"); + $this->opf->metadata->addDublinCore($DCdate); - if (!empty($this->description)) { + if ($this->description !== '' && $this->description !== '0') { $this->opf->addDCMeta(DublinCore::DESCRIPTION, StringHelper::decodeHtmlEntities($this->description)); } - if (!empty($this->publisherName)) { + if ($this->publisherName !== '' && $this->publisherName !== '0') { $this->opf->addDCMeta(DublinCore::PUBLISHER, StringHelper::decodeHtmlEntities($this->publisherName)); } - if (!empty($this->publisherURL)) { + if ($this->publisherURL !== '' && $this->publisherURL !== '0') { $this->opf->addDCMeta(DublinCore::RELATION, StringHelper::decodeHtmlEntities($this->publisherURL)); } - if (!empty($this->author)) { + if ($this->author !== '' && $this->author !== '0') { $author = StringHelper::decodeHtmlEntities($this->author); $this->opf->addCreator($author, StringHelper::decodeHtmlEntities($this->authorSortKey), MarcCode::AUTHOR); $this->ncx->setDocAuthor($author); } - if (!empty($this->rights)) { + if ($this->rights !== '' && $this->rights !== '0') { $this->opf->addDCMeta(DublinCore::RIGHTS, StringHelper::decodeHtmlEntities($this->rights)); } - if (!empty($this->coverage)) { + if ($this->coverage !== '' && $this->coverage !== '0') { $this->opf->addDCMeta(DublinCore::COVERAGE, StringHelper::decodeHtmlEntities($this->coverage)); } - if (!empty($this->sourceURL)) { + if ($this->sourceURL !== '' && $this->sourceURL !== '0') { $this->opf->addDCMeta(DublinCore::SOURCE, $this->sourceURL); } - if (!empty($this->relation)) { + if ($this->relation !== '' && $this->relation !== '0') { $this->opf->addDCMeta(DublinCore::RELATION, StringHelper::decodeHtmlEntities($this->relation)); } @@ -2101,7 +2161,7 @@ public function finalize() $this->opf->addMeta("cover", "CoverImage"); } - if (!empty($this->generator)) { + if ($this->generator !== '' && $this->generator !== '0') { $gen = StringHelper::decodeHtmlEntities($this->generator); $this->opf->addMeta("generator", $gen); $this->ncx->addMetaEntry("dtb:generator", $gen); @@ -2158,10 +2218,8 @@ public function finalize() /** * Finalize and build final ePub structures. - * - * @return bool */ - private function finalizeTOC() + private function finalizeTOC(): bool { if (!$this->buildTOC) { return false; @@ -2187,6 +2245,7 @@ private function finalizeTOC() $tocData .= "\n" . "\n\n"; } + $tocData .= $this->getViewportMetaLine(); $tocData .= "\n"; - if (!empty($this->tocCssFileName)) { - $tocData .= "tocCssFileName . "\" />\n"; + if ($this->tocCssFileName !== null && $this->tocCssFileName !== '' && $this->tocCssFileName !== '0') { + $tocData .= '\n"; } $tocData .= "" . $this->tocTitle . "\n" @@ -2208,8 +2267,9 @@ private function finalizeTOC() . "

" . $this->tocTitle . "

\ntocCSSClass)) { - $tocData .= " class=\"" . $this->tocCSSClass . "\""; + $tocData .= ' class="' . $this->tocCSSClass . '"'; } + $tocData .= ">\n"; foreach ($this->referencesOrder as $item => $descriptive) { @@ -2220,24 +2280,19 @@ private function finalizeTOC() $level = $navPoint->getLevel() - 2; $tocData .= "\t

" /* . str_repeat("      ", $level) . */ - . "" . $chapterName . "

\n"; + . '' . $chapterName . "

\n"; } - } else { - if ($this->tocAddReferences === true) { - if (array_key_exists($item, $this->ncx->referencesList)) { - $tocData .= "\t

ncx->referencesList[$item] . "\">" . $descriptive . "

\n"; - } else { - if ($item === "toc") { - $tocData .= "\t

" . $this->tocTitle . "

\n"; - } else { - if ($item === "cover" && $this->isCoverImageSet) { - $tocData .= "\t

" . $descriptive . "

\n"; - } - } - } + } elseif ($this->tocAddReferences) { + if (array_key_exists($item, $this->ncx->referencesList)) { + $tocData .= "\t

ncx->referencesList[$item] . '">' . $descriptive . "

\n"; + } elseif ($item === "toc") { + $tocData .= "\t

" . $this->tocTitle . "

\n"; + } elseif ($item === "cover" && $this->isCoverImageSet) { + $tocData .= "\t

" . $descriptive . "

\n"; } } } + $tocData .= "\n\n\n"; $this->addReferencePage($this->tocTitle, $this->tocFileName, $tocData, Reference::TABLE_OF_CONTENTS); @@ -2248,14 +2303,13 @@ private function finalizeTOC() /** * @param string $fileName * @param string $tocData - * - * @return bool */ - public function addEPub3TOC($fileName, $tocData) + public function addEPub3TOC($fileName, $tocData): bool { if ($this->isEPubVersion2() || $this->isFinalized || array_key_exists($fileName, $this->fileList)) { return false; } + if (!$this->isInitialized) { $this->initialize(); } @@ -2274,10 +2328,8 @@ public function addEPub3TOC($fileName, $tocData) /** * @param string $cssFileName * @param string $title - * - * @return string */ - public function buildEPub3TOC($cssFileName = null, $title = "Table of Contents") + public function buildEPub3TOC($cssFileName = null, $title = "Table of Contents"): string { $this->ncx->referencesOrder = $this->referencesOrder; $this->ncx->setDocTitle(StringHelper::decodeHtmlEntities($this->title)); @@ -2324,13 +2376,13 @@ public function getBookSize() * * @return string|bool The sent file name if successful, FALSE if it failed. */ - public function sendBook($fileName) + public function sendBook(string $fileName): string|bool { if (!$this->isFinalized) { $this->finalize(); } - if (!BinStringStatic::endsWith($fileName, ".epub")) { + if (!str_ends_with($fileName, ".epub")) { $fileName .= ".epub"; } @@ -2348,7 +2400,7 @@ public function sendBook($fileName) * * @return array file list */ - public function getFileList() + public function getFileList(): array { return $this->fileList; } @@ -2358,10 +2410,8 @@ public function getFileList() * Default is 250000 bytes, and minimum is 10240 bytes. * * @param int $size segment size in bytes - * - * @return void */ - public function setSplitSize($size) + public function setSplitSize($size): void { $this->splitDefaultSize = (int)$size; if ($size < 10240) { @@ -2379,10 +2429,7 @@ public function getSplitSize() return $this->splitDefaultSize; } - /** - * @return string - */ - public function getLog() + public function getLog(): string { return $this->log->getLog(); } @@ -2396,16 +2443,18 @@ public function getLog() * @param int|string $width integer for the width, or a string referencing an entry in the $viewportMap. * @param int $height */ - public function setViewport($width = null, $height = null) + public function setViewport($width = null, $height = null): void { if ($width == null) { unset($this->viewport); } + if (is_string($width) && in_array($width, $this->viewportMap)) { $vp = $this->viewportMap[$width]; $width = $vp['width']; $height = $vp['height']; } + $this->viewport = ['width' => $width, 'height' => $height]; } @@ -2414,9 +2463,21 @@ public function setViewport($width = null, $height = null) * * @return string the meta data line, or an empty string if no viewport is defined. */ - public function getViewportMetaLine() + public function getViewportMetaLine(): string { - if (empty($this->viewport)) { + if (is_null($this->viewport)) { + return ""; + } + + if ($this->viewport === []) { + return ""; + } + + if (!array_key_exists("width", $this->viewport)) { + return ""; + } + + if (!array_key_exists("height", $this->viewport)) { return ""; } @@ -2431,7 +2492,7 @@ public function getViewportMetaLine() * * @param bool $dangermode */ - public function setDangermode($dangermode) + public function setDangermode($dangermode): void { $this->dangermode = $dangermode === true; } @@ -2441,7 +2502,7 @@ public function setDangermode($dangermode) * * @return null|Opf the Opf structure class. */ - public function DANGER_getOpf() + public function DANGER_getOpf(): ?\PHPePub\Core\Structure\Opf { return $this->dangermode ? $this->opf : null; } @@ -2451,7 +2512,7 @@ public function DANGER_getOpf() * * @return null|Ncx The Ncx Navigation class */ - public function DANGER_getNcx() + public function DANGER_getNcx(): ?\PHPePub\Core\Structure\Ncx { return $this->dangermode ? $this->ncx : null; } @@ -2465,7 +2526,7 @@ public function DANGER_getNcx() * * @return null|Zip The actual zip file. */ - public function DANGER_getZip() + public function DANGER_getZip(): ?\PHPZip\Zip\File\Zip { return $this->dangermode ? $this->zip : null; } diff --git a/src/PHPePub/Core/EPubChapterSplitter.php b/src/PHPePub/Core/EPubChapterSplitter.php index 6d6164b..23f1065 100644 --- a/src/PHPePub/Core/EPubChapterSplitter.php +++ b/src/PHPePub/Core/EPubChapterSplitter.php @@ -18,8 +18,10 @@ */ class EPubChapterSplitter { - private $splitDefaultSize = 250000; - private $bookVersion = EPub::BOOK_VERSION_EPUB2; + private int $splitDefaultSize = 250000; + + private string $bookVersion = EPub::BOOK_VERSION_EPUB2; + private $htmlFormat = EPub::FORMAT_XHTML; /** @@ -38,7 +40,7 @@ public function __construct($htmlFormat = EPub::FORMAT_XHTML) * * @param string $bookVersion */ - public function setVersion($bookVersion) + public function setVersion($bookVersion): void { $this->bookVersion = is_string($bookVersion) ? trim($bookVersion) : EPub::BOOK_VERSION_EPUB2; } @@ -48,7 +50,7 @@ public function setVersion($bookVersion) * * @param string $htmlFormat */ - public function setHtmlFormat($htmlFormat) + public function setHtmlFormat($htmlFormat): void { $this->htmlFormat = in_array($htmlFormat, [EPub::FORMAT_XHTML, EPub::FORMAT_HTML5]) ? $htmlFormat @@ -60,10 +62,8 @@ public function setHtmlFormat($htmlFormat) * Default is 250000 bytes, and minimum is 10240 bytes. * * @param int $size segment size in bytes - * - * @return void */ - public function setSplitSize($size) + public function setSplitSize($size): void { $this->splitDefaultSize = (int)$size; if ($size < 10240) { @@ -76,7 +76,7 @@ public function setSplitSize($size) * * @return int $size */ - public function getSplitSize() + public function getSplitSize(): int { return $this->splitDefaultSize; } @@ -93,7 +93,7 @@ public function getSplitSize() * * @return array with 1 or more parts */ - public function splitChapter($chapter, $splitOnSearchString = false, $searchString = '/^Chapter\\ /i') + public function splitChapter($chapter, $splitOnSearchString = false, $searchString = '/^Chapter\\ /i'): array { $chapterData = []; $isSearchRegexp = $splitOnSearchString && (preg_match('#^(\D|\S|\W).+\1[imsxeADSUXJu]*$#m', $searchString) == 1); @@ -126,6 +126,7 @@ public function splitChapter($chapter, $splitOnSearchString = false, $searchStri if (!str_contains(trim($newXML), "\n" . $newXML; } + $headerLength = strlen($newXML); $files = []; @@ -158,7 +159,7 @@ public function splitChapter($chapter, $splitOnSearchString = false, $searchStri if ($nodeLen > $partSize && $node->hasChildNodes()) { $domPath[] = $node; $domClonedPath[] = $node->cloneNode(false); - $domDepth++; + ++$domDepth; $node = $node->firstChild; } @@ -185,15 +186,17 @@ public function splitChapter($chapter, $splitOnSearchString = false, $searchStri $curParent = $newParent; } } + $curSize = strlen($xmlDoc->saveXML($curFile)); } + $curParent->appendChild($node->cloneNode(true)); $curSize += $nodeLen; } $node = $node2; while ($node == null && $domDepth > 0) { - $domDepth--; + --$domDepth; $node = end($domPath)->nextSibling; array_pop($domPath); array_pop($domClonedPath); @@ -201,14 +204,14 @@ public function splitChapter($chapter, $splitOnSearchString = false, $searchStri } } while ($node != null); - $curFile = null; - $xml = new DOMDocument('1.0', $xmlDoc->xmlEncoding); $xml->lookupPrefix("http://www.w3.org/1999/xhtml"); + $xml->preserveWhiteSpace = false; $xml->formatOutput = true; + $counter = count($files); - for ($idx = 0; $idx < count($files); $idx++) { + for ($idx = 0; $idx < $counter; ++$idx) { $xml2Doc = new DOMDocument('1.0', $xmlDoc->xmlEncoding); $xml2Doc->lookupPrefix("http://www.w3.org/1999/xhtml"); $xml2Doc->loadXML($newXML); diff --git a/src/PHPePub/Core/Logger.php b/src/PHPePub/Core/Logger.php index 57d660d..4989c69 100644 --- a/src/PHPePub/Core/Logger.php +++ b/src/PHPePub/Core/Logger.php @@ -11,11 +11,15 @@ */ class Logger { - private $log = ""; - private $tStart; + private string $log = ""; + + private array|float|null $tStart = null; + private $tLast; - private $name = null; - private $isDebugging = false; + + private ?string $name = null; + + private bool $isDebugging = false; /** * Class constructor. @@ -25,15 +29,11 @@ class Logger */ public function __construct($name = null, private $isLogging = false) { - if ($name === null) { - $this->name = ""; - } else { - $this->name = $name . " : "; - } + $this->name = $name === null ? "" : $name . " : "; $this->start(); } - public function start() + public function start(): void { /* Prepare Logging. Just in case it's used. later */ if ($this->isLogging) { @@ -44,7 +44,7 @@ public function start() } } - public function logLine($line) + public function logLine(string $line): void { if ($this->isLogging) { $tTemp = gettimeofday(); @@ -73,7 +73,7 @@ public function __destruct() unset($this->log); } - public function dumpInstalledModules() + public function dumpInstalledModules(): void { if ($this->isLogging) { $isCurlInstalled = extension_loaded('curl') && function_exists('curl_version'); @@ -90,17 +90,15 @@ public function dumpInstalledModules() } } - public function getLog() + public function getLog(): string { return $this->log; } /** * @param $isCurlInstalled - * - * @return string */ - public function boolYN($isCurlInstalled) + public function boolYN($isCurlInstalled): string { return ($isCurlInstalled ? "Yes" : "No"); } diff --git a/src/PHPePub/Core/StaticData.php b/src/PHPePub/Core/StaticData.php index 49b793e..c1d4844 100644 --- a/src/PHPePub/Core/StaticData.php +++ b/src/PHPePub/Core/StaticData.php @@ -12,7 +12,7 @@ class StaticData { public static $htmlEntities = [ - """ => "\x22", + """ => '"', // " ((double) quotation mark) "&" => "\x26", // & (ampersand) @@ -526,7 +526,7 @@ class StaticData public static $opsContentTypes = ["application/xhtml+xml", "application/x-dtbook+xml", "application/xml", "application/x-dtbncx+xml", "text/x-oeb1-document"]; - public static $forbiddenCharacters = ["?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}", "%"]; + public static $forbiddenCharacters = ["?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", '"', "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}", "%"]; public static $namespaces = ["xsi" => "http://www.w3.org/2001/XMLSchema-instance", "opf" => "http://www.idpf.org/2007/opf", "dcterms" => "http://purl.org/dc/terms/", "dc" => "http://purl.org/dc/elements/1.1/"]; } diff --git a/src/PHPePub/Core/Structure/NCX/NavMap.php b/src/PHPePub/Core/Structure/NCX/NavMap.php index eecbe2d..43dd294 100644 --- a/src/PHPePub/Core/Structure/NCX/NavMap.php +++ b/src/PHPePub/Core/Structure/NCX/NavMap.php @@ -13,9 +13,11 @@ class NavMap extends AbstractNavEntry { final public const _VERSION = 3.30; - private $navPoints = []; - private $navLevels = 0; - private $writingDirection = null; + private array $navPoints = []; + + private int $navLevels = 0; + + private ?string $writingDirection = null; /** * Class constructor. @@ -37,7 +39,7 @@ public function __destruct() unset($this->navPoints, $this->navLevels, $this->writingDirection); } - public function getWritingDirection() + public function getWritingDirection(): ?string { return $this->writingDirection; } @@ -47,7 +49,7 @@ public function getWritingDirection() * * @param string $writingDirection */ - public function setWritingDirection($writingDirection) + public function setWritingDirection($writingDirection): void { $this->writingDirection = isset($writingDirection) && is_string($writingDirection) ? trim($writingDirection) : null; } @@ -59,13 +61,14 @@ public function setWritingDirection($writingDirection) * * @return NavMap */ - public function addNavPoint($navPoint) + public function addNavPoint($navPoint): \PHPePub\Core\Structure\NCX\NavPoint|static { if ($navPoint != null && is_object($navPoint) && $navPoint instanceof NavPoint) { $navPoint->setParent($this); if ($navPoint->getWritingDirection() == null) { $navPoint->setWritingDirection($this->writingDirection); } + $this->navPoints[] = $navPoint; return $navPoint; @@ -80,12 +83,12 @@ public function addNavPoint($navPoint) * * @return number */ - public function getNavLevels() + public function getNavLevels(): int|float { return $this->navLevels + 1; } - public function getLevel() + public function getLevel(): int { return 1; } @@ -93,7 +96,7 @@ public function getLevel() /** * @return AbstractNavEntry this */ - public function getParent() + public function getParent(): static { return $this; } @@ -102,14 +105,14 @@ public function getParent() * Finalize the navMap, the final max depth for the "dtb:depth" meta attribute can be retrieved with getNavLevels after finalization * */ - public function finalize() + public function finalize(): string { $playOrder = 0; $this->navLevels = 0; $nav = "\t\n"; - if (count($this->navPoints) > 0) { - $this->navLevels++; + if ($this->navPoints !== []) { + ++$this->navLevels; foreach ($this->navPoints as $navPoint) { /** @var $navPoint NavPoint */ $retLevel = $navPoint->finalize($nav, $playOrder, 0); @@ -126,7 +129,7 @@ public function finalize() * Finalize the navMap, the final max depth for the "dtb:depth" meta attribute can be retrieved with getNavLevels after finalization * */ - public function finalizeEPub3() + public function finalizeEPub3(): string { $playOrder = 0; $level = 0; @@ -134,8 +137,8 @@ public function finalizeEPub3() $nav = "\t\t