From a256f20b5e36a4a27576ee7eb629159be89cd5ed Mon Sep 17 00:00:00 2001
From: Julio Ruiz
You can obtain the latest version from http://www.phpclasses.org/browse/package/6110.html.
processCSSExternalReferences for explanation. Default is EPub::EXTERNAL_REF_IGNORE.
+ * @param String $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE.
+ *
+ * @return bool $success
+ */
+ function addCSSFile($fileName, $fileId, $fileData, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") {
+ if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) {
+ return FALSE;
+ }
+ $fileName = preg_replace('#\\\#i', "/", $fileName);
+ $fileName = preg_replace('#^[/\.]+#i', "", $fileName);
+
+ $cssDir = pathinfo($fileName);
+ $cssDir = preg_replace('#^[/\.]+#i', "", $cssDir["dirname"] . "/");
+ if (!empty($cssDir)) {
+ $cssDir = preg_replace('#[^/]+/#i', "../", $cssDir);
+ }
+
+ if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) {
+ $this->processCSSExternalReferences($fileData, $externalReferences, $baseDir, $cssDir);
+ }
+
+ $this->zip->addFile($fileData, $fileName);
+ $this->fileList[$fileName] = $fileName;
+ $this->opf_manifest .= "\t\tprocessChapterExternalReferences for explanation. Default is EPub::EXTERNAL_REF_IGNORE.
+ * @param String $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE.
+ * @return bool $success
+ */
+ function addChapter($chapterName, $fileName, $chapterData, $autoSplit = FALSE, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") {
+ if ($this->isFinalized) {
+ return FALSE;
+ }
+ $fileName = preg_replace('#\\\#i', "/", $fileName);
+ $fileName = preg_replace('#^[/\.]+#i', "", $fileName);
+
+ $htmlDir = pathinfo($fileName);
+ $htmlDir = preg_replace('#^[/\.]+#i', "", $htmlDir["dirname"] . "/");
+
+ $chapter = $chapterData;
+ if ($autoSplit && is_string($chapterData) && mb_strlen($chapterData) > $this->splitDefaultSize) {
+ $splitter = new EPubChapterSplitter();
+
+ $chapterArray = $splitter->splitChapter($chapterData);
+ if (count($chapterArray) > 1) {
+ $chapter = $chapterArray;
+ }
+ }
+
+ if (!empty($chapter) && is_string($chapter)) {
+ if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) {
+ $this->processChapterExternalReferences($chapter, $externalReferences, $baseDir, $htmlDir);
+ }
+
+ $this->zip->addFile($chapter, $fileName);
+ $this->fileList[$fileName] = $fileName;
+ $this->chapterCount++;
+ $this->opf_manifest .= "\t\t<img src="../images/image.png"/>
+ *
+ * $externalReferences determins how the function will handle external references.
+ *
+ * @param mixed $doc (referenced)
+ * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
+ * @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 FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
+ */
+ protected function processChapterExternalReferences(&$doc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "") {
+ if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) {
+ return FALSE;
+ }
+
+ $backPath = preg_replace('#[^/]+/#i', "../", $htmlDir);
+ $isDocAString = is_string($doc);
+ $xmlDoc = NULL;
+
+ if ($isDocAString) {
+ $xmlDoc = new DOMDocument();
+ @$xmlDoc->loadHTML($doc);
+ } else {
+ $xmlDoc = $doc;
+ }
+
+ $this->processChapterStyles($xmlDoc, $externalReferences, $baseDir, $htmlDir);
+ $this->processChapterLinks($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath);
+ $this->processChapterImages($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath);
+
+ if ($isDocAString) {
+ $html = $xmlDoc->saveXML();
+
+ $head = $xmlDoc->getElementsByTagName("head");
+ $body = $xmlDoc->getElementsByTagName("body");
+
+ $xml = new DOMDocument('1.0', "utf-8");
+ $xml->lookupPrefix("http://www.w3.org/1999/xhtml");
+ $xml->preserveWhiteSpace = FALSE;
+ $xml->formatOutput = TRUE;
+
+ $xml2Doc = new DOMDocument('1.0', "utf-8");
+ $xml2Doc->lookupPrefix("http://www.w3.org/1999/xhtml");
+ $xml2Doc->loadXML("\n\n\n\n");
+ $html = $xml2Doc->getElementsByTagName("html")->item(0);
+ $html->appendChild($xml2Doc->importNode($head->item(0), TRUE));
+ $html->appendChild($xml2Doc->importNode($body->item(0), TRUE));
+
+ // force pretty printing and correct formatting, should not be needed, but it is.
+ $xml->loadXML($xml2Doc->saveXML());
+ $doc = $xml->saveXML();
+ }
+ return TRUE;
+ }
+
+ /**
+ * Process images referenced from an CSS file to the book.
+ *
+ * $externalReferences determins how the function will handle external references.
+ *
+ * @param String $cssFile (referenced)
+ * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
+ * @param String $baseDir Default is "", meaning it is pointing to the document root.
+ * @param String $cssDir The of the CSS file's directory from the root of the archive.
+ *
+ * @return Bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
+ */
+ protected function processCSSExternalReferences(&$cssFile, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $cssDir = "") {
+ if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) {
+ return FALSE;
+ }
+
+ $backPath = preg_replace('#[^/]+/#i', "../", $cssDir);
+ preg_match_all('#url\s*\([\'\"\s]*(.+?)[\'\"\s]*\)#im', $cssFile, $imgs, PREG_SET_ORDER);
+
+ $itemCount = count($imgs);
+ 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);
+ } else {
+ $source = $img[1];
+
+ $pathData = pathinfo($source);
+ $internalSrc = $pathData['basename'];
+ $internalPath = "";
+ $isSourceExternal = FALSE;
+
+ if ($this->resolveImage($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $cssDir, $backPath)) {
+ $cssFile = str_replace($img[0], "url('" . $backPath . $internalPath . "')", $cssFile);
+ } else if ($isSourceExternal) {
+ $cssFile = str_replace($img[0], "", $cssFile); // External image is missing
+ } // else do nothing, if the image is local, and missing, assume it's been generated.
+ }
+ }
+ return TRUE;
+ }
+
+ /**
+ * Process style tags in a DOMDocument. Styles will be passed as CSS files and reinserted into the document.
+ *
+ * @param DOMDocument $xmlDoc (referenced)
+ * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
+ * @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 FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
+ */
+ protected function processChapterStyles(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "") {
+ 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++) {
+ $style = $styles->item($styleIdx);
+ $styleData = $style->nodeValue;
+
+ $styleData = preg_replace('#[/\*\s]*\<\!\[CDATA\[[\s\*/]*#im', "", $styleData);
+ $styleData = preg_replace('#[/\*\s]*\]\]\>[\s\*/]*#im', "", $styleData);
+
+ $this->processCSSExternalReferences($styleData, $externalReferences, $baseDir, $htmlDir);
+ $style->nodeValue = "\n" . trim($styleData) . "\n";
+ }
+ return TRUE;
+ }
+
+ /**
+ * Process link tags in a DOMDocument. Linked files will be loaded into the archive, and the link src will be rewritten to point to that location.
+ * Link types text/css will be passed as CSS files.
+ *
+ * @param DOMDocument $xmlDoc (referenced)
+ * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
+ * @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.
+ * @param String $backPath The path to get back to the root of the archive from $htmlDir.
+ *
+ * @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 = "") {
+ 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++) {
+ $link = $links->item($linkIdx);
+ $source = $link->attributes->getNamedItem("href")->nodeValue;
+ $sourceData = NULL;
+
+ $pathData = pathinfo($source);
+ $internalSrc = $pathData['basename'];
+
+ if (preg_match('#^(http|ftp)s?://#i', $source) == 1) {
+ $urlinfo = parse_url($source);
+
+ if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) {
+ $internalSrc = substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") + strlen($basedir) + 1);
+ }
+
+ @$sourceData = file_get_contents($source);
+ } else if (strpos($source, "/") === 0) {
+ @$sourceData = file_get_contents($this->docRoot . $source);
+ } else {
+ @$sourceData = file_get_contents($this->docRoot . $baseDir . "/" . $source);
+ }
+
+ if (!empty($sourceData)) {
+ if (!array_key_exists($internalSrc, $this->fileList)) {
+ $mime = $link->attributes->getNamedItem("type")->nodeValue;
+ 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);
+ $link->setAttribute("href", $backPath . $internalSrc);
+ } else {
+ $this->addFile($internalSrc, $internalSrc, $sourceData, $mime);
+ }
+ $this->fileList[$internalSrc] = $source;
+ } else {
+ $link->setAttribute("href", $backPath . $internalSrc);
+ }
+ } // else do nothing, if the link is local, and missing, assume it's been generated.
+ }
+ return TRUE;
+ }
+
+ /**
+ * Process img tags in a DOMDocument.
+ * $externalReferences will determine what will happen to these images, and the img src will be rewritten accordingly.
+ *
+ * @param DOMDocument $xmlDoc (referenced)
+ * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
+ * @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.
+ * @param String $backPath The path to get back to the root of the archive from $htmlDir.
+ *
+ * @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 = "") {
+ if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) {
+ return FALSE;
+ }
+ // process img tags.
+ $postProcDomElememts = array();
+ $images = $xmlDoc->getElementsByTagName("img");
+ $itemCount = $images->length;
+ for ($idx = 0; $idx < $itemCount; $idx++) {
+ $img = $images->item($idx);
+ if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES) {
+ $postProcDomElememts[] = $img;
+ } else if ($externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) {
+ $postProcDomElememts[] = array($img, $this->createDomFragment($xmlDoc, "[image]"));
+ } else {
+ $source = $img->attributes->getNamedItem("src")->nodeValue;
+
+ $pathData = pathinfo($source);
+ $internalSrc = $pathData['basename'];
+ $internalPath = "";
+ $isSourceExternal = FALSE;
+
+ if ($this->resolveImage($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $htmlDir, $backPath)) {
+ $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.
+ }
+ }
+
+ foreach ($postProcDomElememts as $target) {
+ if (is_array($target)) {
+ $target[0]->parentNode->replaceChild($target[1], $target[0]);
+ } else {
+ $target->parentNode->removeChild($target);
+ }
+ }
+ return TRUE;
+ }
+
+ /**
+ * Resolve an image src and determine it's target location and add it to the book.
+ *
+ * @param String $source Image Source link.
+ * @param String $internalPath (referenced) Return value, will be set to the target path and name in the book.
+ * @param String $internalSrc (referenced) Return value, will be set to the target name in the book.
+ * @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.
+ * @param String $backPath The path to get back to the root of the archive from $htmlDir.
+ */
+ protected function resolveImage($source, &$internalPath, &$internalSrc, &$isSourceExternal, $baseDir = "", $htmlDir = "", $backPath = "") {
+ if ($this->isFinalized) {
+ return FALSE;
+ }
+ $imageData = NULL;
+
+ if (preg_match('#^(http|ftp)s?://#i', $source) == 1) {
+ $urlinfo = parse_url($source);
+
+ if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) {
+ $internalSrc = substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") + strlen($basedir) + 1);
+ }
+ $internalPath = $urlinfo["scheme"] . "/" . $urlinfo["host"] . "/" . pathinfo($urlinfo["path"], PATHINFO_DIRNAME);
+ $isSourceExternal = TRUE;
+ $imageData = $this->getImage($source);
+ } else if (strpos($source, "/") === 0) {
+ $internalPath = pathinfo($source, PATHINFO_DIRNAME);
+ $imageData = $this->getImage($this->docRoot . $source);
+ } else {
+ $internalPath = $htmlDir . "/" . preg_replace('#^[/\.]+#', '', pathinfo($source, PATHINFO_DIRNAME));
+ $imageData = $this->getImage($this->docRoot . $baseDir . "/" . $source);
+ }
+ if ($imageData !== FALSE) {
+ $internalPath = Zip::getRelativePath("images/" . $internalPath . "/" . $internalSrc);
+ if (!array_key_exists($internalPath, $this->fileList)) {
+ $this->addFile($internalPath, "i_" . $internalSrc, $imageData['image'], $imageData['mime']);
+ $this->fileList[$internalPath] = $source;
+ }
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * Add a cover image to the book.
+ *
+ * The styling and structure of the generated XHTML is heavily inspired by the XHTML generated by Calibre.
+ *
+ * @param String $fileName Filename to use for the image, must be unique for the book.
+ * @param String $imageData Binary image data
+ * @param String $mimetype Image mimetype, such as "image/jpeg" or "image/png".
+ * @return bool $success
+ */
+ function setCoverImage($fileName, $imageData = NULL, $mimetype = NULL) {
+ if ($this->isFinalized || $this->isCoverImageSet || array_key_exists("CoverPage.html", $this->fileList)) {
+ return FALSE;
+ }
+
+ if ($imageData == NULL) { // assume $fileName is the valig file path.
+ $image = $this->getImage($this->docRoot . $fileName);
+ $imageData = $image['image'];
+ $mimetype = $image['mime'];
+ }
+ $path = pathinfo($this->docRoot . $fileName);
+ $imgPath = "images/" . $path["basename"];
+
+ $coverPage = "\n\n\t\n\t\t\n\t\tLength mismatch
\n"; + } + $this->streamFileLength += $length; + + return $length; + } + + /** + * Close the current stream. + * + * @return bool $success + */ + public function closeStream() { + if ($this->isFinalized || strlen($this->streamFilePath) == 0) { + return FALSE; + } + + fflush($this->streamData); + gzclose($this->streamData); + + $gzType = "\x08\x00"; // Compression type 8 = deflate + $gpFlags = "\x02\x00"; // General Purpose bit flags for compression type 8 it is: 0=Normal, 1=Maximum, 2=Fast, 3=super fast compression. + + $file_handle = fopen($this->streamFile, "rb"); + $stats = fstat($file_handle); + $eof = $stats['size']; + + fseek($file_handle, $eof-8); + $fileCRC32 = fread($file_handle, 4); + $dataLength = $this->streamFileLength;//$gzl[1]; + + $gzLength = $eof-10; + $eof -= 9; + + fseek($file_handle, 10); + + $this->buildZipEntry($this->streamFilePath, $this->streamFileComment, $gpFlags, $gzType, $this->streamTimestamp, $fileCRC32, $gzLength, $dataLength, 32); + while(!feof($file_handle)) { + fwrite($this->zipFile, fread($file_handle, $this->streamChunkSize)); + } + + unlink($this->streamFile); + $this->streamFile = NULL; + $this->streamData = NULL; + $this->streamFilePath = NULL; + $this->streamTimestamp = NULL; + $this->streamFileComment = NULL; + $this->streamFileLength = 0; + + return TRUE; + } + + /** + * Close the archive. + * A closed archive can no longer have new files added to it. + * + * @return bool $success + */ + public function finalize() { + if(!$this->isFinalized) { + if (strlen($this->streamFilePath) > 0) { + $this->closeStream(); + } + $cd = implode("", $this->cdRec); + + $cdRec = $cd . $this->endOfCentralDirectory + . pack("v", sizeof($this->cdRec)) + . pack("v", sizeof($this->cdRec)) + . pack("V", strlen($cd)) + . pack("V", $this->offset); + if (!is_null($this->zipComment)) { + $cdRec .= pack("v", strlen($this->zipComment)) . $this->zipComment; + } else { + $cdRec .= "\x00\x00"; + } + + if (is_null($this->zipFile)) { + $this->zipData .= $cdRec; + } else { + fwrite($this->zipFile, $cdRec); + fflush($this->zipFile); + } + $this->isFinalized = TRUE; + $cd = NULL; + $this->cdRec = NULL; + + return TRUE; + } + return FALSE; + } + + /** + * Get the handle ressource for the archive zip file. + * If the zip haven't been finalized yet, this will cause it to become finalized + * + * @return zip file handle + */ + public function getZipFile() { + if(!$this->isFinalized) { + $this->finalize(); + } + if (is_null($this->zipFile)) { + $this->zipFile = tmpfile(); + fwrite($this->zipFile, $this->zipData); + $this->zipData = NULL; + } + rewind($this->zipFile); + + return $this->zipFile; + } + + /** + * Get the zip file contents + * If the zip haven't been finalized yet, this will cause it to become finalized + * + * @return zip data + */ + public function getZipData() { + if(!$this->isFinalized) { + $this->finalize(); + } + if (is_null($this->zipFile)) { + return $this->zipData; + } else { + rewind($this->zipFile); + $filestat = fstat($this->zipFile); + return fread($this->zipFile, $filestat['size']); + } + } + + /** + * Send the archive as a zip download + * + * @param String $fileName The name of the Zip archive, ie. "archive.zip". + * @param String $contentType Content mime type. Optional, defailts to "application/zip". + * @return bool $success + */ + function sendZip($fileName, $contentType = "application/zip") { + if(!$this->isFinalized) { + $this->finalize(); + } + + if (!headers_sent($headerFile, $headerLine) or die("Error: Unable to send file $fileName. HTML Headers have already been sent from $headerFile in line $headerLine
")) { + if ((ob_get_contents() === FALSE || ob_get_contents() == '') or die("\nError: Unable to send file $fileName.epub. Output buffer contains the following text (typically warnings or errors):
" . ob_get_contents() . "