3 * Create an ePub compatible book file.
5 * Please note, once finalized a book can no longer have chapters of data added or changed.
7 * License: GNU LGPL, Attribution required for commercial implementations, requested for everything else.
9 * Thanks to: Adam Schmalhofer and Kirstyn Fox for invaluable input and for "nudging" me in the right direction :)
11 * @author A. Grandt <php@grandt.com>
12 * @copyright 2009-2014 A. Grandt
13 * @license GNU LGPL 2.1
15 * @link http://www.phpclasses.org/package/6115
16 * @link https://github.com/Grandt/PHPePub
17 * @uses Zip.php version 1.50; http://www.phpclasses.org/browse/package/6110.html or https://github.com/Grandt/PHPZip
21 const REQ_ZIP_VERSION
= 1.60;
23 const IDENTIFIER_UUID
= 'UUID';
24 const IDENTIFIER_URI
= 'URI';
25 const IDENTIFIER_ISBN
= 'ISBN';
27 /** Ignore all external references, and do not process the file for these */
28 const EXTERNAL_REF_IGNORE
= 0;
29 /** Process the file for external references and add them to the book */
30 const EXTERNAL_REF_ADD
= 1;
31 /** Process the file for external references and add them to the book, but remove images, and img tags */
32 const EXTERNAL_REF_REMOVE_IMAGES
= 2;
33 /** Process the file for external references and add them to the book, but replace images, and img tags with [image] */
34 const EXTERNAL_REF_REPLACE_IMAGES
= 3;
36 const DIRECTION_LEFT_TO_RIGHT
= "ltr";
37 const DIRECTION_RIGHT_TO_LEFT
= "rtl";
39 const BOOK_VERSION_EPUB2
= "2.0";
40 const BOOK_VERSION_EPUB3
= "3.0";
42 private $bookVersion = EPub
::BOOK_VERSION_EPUB2
;
44 public $maxImageWidth = 768;
45 public $maxImageHeight = 1024;
47 public $splitDefaultSize = 250000;
48 /** Gifs can crash some early ADE based readers, and are disabled by default.
49 * getImage will convert these if it can, unless this is set to TRUE.
51 public $isGifImagesEnabled = FALSE;
52 public $isReferencesAddedToToc = TRUE;
57 private $language = "en";
58 private $identifier = "";
59 private $identifierType = "";
60 private $description = "";
62 private $authorSortKey = "";
63 private $publisherName = "";
64 private $publisherURL = "";
67 private $coverage = "";
68 private $relation = "";
69 private $sourceURL = "";
71 private $chapterCount = 0;
74 private $isFinalized = FALSE;
75 private $isCoverImageSet = FALSE;
76 private $buildTOC = FALSE;
77 private $tocTitle = NULL;
78 private $tocFileName = NULL;
79 private $tocCSSClass = NULL;
80 private $tocAddReferences = FALSE;
81 private $tocCssFileName = NULL;
83 private $fileList = array();
84 private $writingDirection = EPub
::DIRECTION_LEFT_TO_RIGHT
;
85 private $languageCode = "en";
88 * Used for building the TOC.
89 * If this list is overwritten it MUST contain at least "text" as an element.
91 public $referencesOrder = NULL;
93 private $dateformat = 'Y-m-d\TH:i:s.000000P'; // ISO 8601 long
94 private $dateformatShort = 'Y-m-d'; // short date format to placate ePubChecker.
95 private $headerDateFormat = "D, d M Y H:i:s T";
97 protected $isCurlInstalled;
98 protected $isGdInstalled;
99 protected $isExifInstalled;
100 protected $isFileGetContentsInstalled;
101 protected $isFileGetContentsExtInstalled;
103 private $bookRoot = "OEBPS/";
104 private $docRoot = NULL;
105 private $EPubMark = TRUE;
106 private $generator = "";
109 public $isLogging = TRUE;
111 public $encodeHTML = FALSE;
113 private $mimetypes = array(
114 "js" => "application/x-javascript", "swf" => "application/x-shockwave-flash", "xht" => "application/xhtml+xml", "xhtml" => "application/xhtml+xml", "zip" => "application/zip",
115 "aif" => "audio/x-aiff", "aifc" => "audio/x-aiff", "aiff" => "audio/x-aiff", "au" => "audio/basic", "kar" => "audio/midi", "m3u" => "audio/x-mpegurl", "mid" => "audio/midi", "midi" => "audio/midi", "mp2" => "audio/mpeg", "mp3" => "audio/mpeg", "mpga" => "audio/mpeg", "oga" => "audio/ogg", "ogg" => "audio/ogg", "ra" => "audio/x-realaudio", "ram" => "audio/x-pn-realaudio", "rm" => "audio/x-pn-realaudio", "rpm" => "audio/x-pn-realaudio-plugin", "snd" => "audio/basic", "wav" => "audio/x-wav",
116 "bmp" => "image/bmp", "djv" => "image/vnd.djvu", "djvu" => "image/vnd.djvu", "gif" => "image/gif", "ief" => "image/ief", "jpe" => "image/jpeg", "jpeg" => "image/jpeg", "jpg" => "image/jpeg", "pbm" => "image/x-portable-bitmap", "pgm" => "image/x-portable-graymap", "png" => "image/png", "pnm" => "image/x-portable-anymap", "ppm" => "image/x-portable-pixmap", "ras" => "image/x-cmu-raster", "rgb" => "image/x-rgb", "tif" => "image/tif", "tiff" => "image/tiff", "wbmp" => "image/vnd.wap.wbmp", "xbm" => "image/x-xbitmap", "xpm" => "image/x-xpixmap", "xwd" => "image/x-windowdump",
117 "asc" => "text/plain", "css" => "text/css", "etx" => "text/x-setext", "htm" => "text/html", "html" => "text/html", "rtf" => "text/rtf", "rtx" => "text/richtext", "sgm" => "text/sgml", "sgml" => "text/sgml", "tsv" => "text/tab-seperated-values", "txt" => "text/plain", "wml" => "text/vnd.wap.wml", "wmls" => "text/vnd.wap.wmlscript", "xml" => "text/xml", "xsl" => "text/xml",
118 "avi" => "video/x-msvideo", "mov" => "video/quicktime", "movie" => "video/x-sgi-movie", "mp4" => "video/mp4", "mpe" => "video/mpeg", "mpeg" => "video/mpeg", "mpg" => "video/mpeg", "mxu" => "video/vnd.mpegurl", "ogv" => "video/ogg", "qt" => "video/quicktime", "webm" => "video/webm");
120 // These are the ONLY allowed types in that these are the ones ANY reader must support, any other MUST have the fallback attribute pointing to one of these.
121 private $coreMediaTypes = array("image/gif", "image/jpeg", "image/png", "image/svg+xml", "application/xhtml+xml", "application/x-dtbook+xml", "application/xml", "application/x-dtbncx+xml", "text/css", "text/x-oeb1-css", "text/x-oeb1-document");
123 private $opsContentTypes = array("application/xhtml+xml", "application/x-dtbook+xml", "application/xml", "application/x-dtbncx+xml", "text/x-oeb1-document");
125 private $forbiddenCharacters = array("?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}", "%");
127 private $htmlContentHeader = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n<title></title>\n</head>\n<body>\n";
128 private $htmlContentFooter = "</body>\n</html>\n";
135 function __construct($bookVersion = EPub
::BOOK_VERSION_EPUB2
, $languageCode = "en", $writingDirection = EPub
::DIRECTION_LEFT_TO_RIGHT
) {
136 include_once("Zip.php");
137 include_once("Logger.php");
139 $this->bookVersion
= $bookVersion;
140 $this->writingDirection
= $writingDirection;
141 $this->languageCode
= $languageCode;
143 $this->log
= new Logger("EPub", $this->isLogging
);
145 /* Prepare Logging. Just in case it's used. later */
146 if ($this->isLogging
) {
147 $this->log
->logLine("EPub class version....: " . self
::VERSION
);
148 $this->log
->logLine("EPub req. Zip version.: " . self
::REQ_ZIP_VERSION
);
149 $this->log
->logLine("Zip version...........: " . Zip
::VERSION
);
150 $this->log
->dumpInstalledModules();
153 if (!defined("Zip::VERSION") || Zip
::VERSION
< self
::REQ_ZIP_VERSION
) {
154 die("<p>EPub version " . self
::VERSION
. " requires Zip.php at version " . self
::REQ_ZIP_VERSION
. " or higher.<br />You can obtain the latest version from <a href=\"http://www.phpclasses.org/browse/package/6110.html\">http://www.phpclasses.org/browse/package/6110.html</a>.</p>");
157 include_once("EPubChapterSplitter.php");
158 include_once("EPub.HtmlEntities.php");
159 include_once("EPub.NCX.php");
160 include_once("EPub.OPF.php");
169 * @TODO make sure elements in the destructor match the current class elements
171 function __destruct() {
172 unset($this->bookVersion
, $this->maxImageWidth
, $this->maxImageHeight
);
173 unset($this->splitDefaultSize
, $this->isGifImagesEnabled
, $this->isReferencesAddedToToc
);
174 unset($this->zip
, $this->title
, $this->language
, $this->identifier
, $this->identifierType
);
175 unset($this->description
, $this->author
, $this->authorSortKey
, $this->publisherName
);
176 unset($this->publisherURL
, $this->date
, $this->rights
, $this->coverage
, $this->relation
);
177 unset($this->sourceURL
, $this->chapterCount
, $this->opf
, $this->ncx
, $this->isFinalized
);
178 unset($this->isCoverImageSet
, $this->fileList
, $this->writingDirection
, $this->languageCode
);
179 unset($this->referencesOrder
, $this->dateformat
, $this->dateformatShort
, $this->headerDateFormat
);
180 unset($this->isCurlInstalled
, $this->isGdInstalled
, $this->isExifInstalled
);
181 unset($this->isFileGetContentsInstalled
, $this->isFileGetContentsExtInstalled
, $this->bookRoot
);
182 unset($this->docRoot
, $this->EPubMark
, $this->generator
, $this->log
, $this->isLogging
);
183 unset($this->encodeHTML
, $this->mimetypes
, $this->coreMediaTypes
, $this->opsContentTypes
);
184 unset($this->forbiddenCharacters
, $this->htmlContentHeader
, $this->htmlContentFooter
);
185 unset($this->buildTOC
, $this->tocTitle
, $this->tocCSSClass
, $this->tocAddReferences
);
186 unset($this->tocFileName
, $this->tocCssFileName
);
190 * initialize defaults.
192 private function initialize() {
193 $this->referencesOrder
= array(
194 Reference
::COVER
=> "Cover Page",
195 Reference
::TITLE_PAGE
=> "Title Page",
196 Reference
::ACKNOWLEDGEMENTS
=> "Acknowledgements",
197 Reference
::BIBLIOGRAPHY
=> "Bibliography",
198 Reference
::COLOPHON
=> "Colophon",
199 Reference
::COPYRIGHT_PAGE
=> "Copyright",
200 Reference
::DEDICATION
=> "Dedication",
201 Reference
::EPIGRAPH
=> "Epigraph",
202 Reference
::FOREWORD
=> "Foreword",
203 Reference
::TABLE_OF_CONTENTS
=> "Table of Contents",
204 Reference
::NOTES
=> "Notes",
205 Reference
::PREFACE
=> "Preface",
206 Reference
::TEXT
=> "First Page",
207 Reference
::LIST_OF_ILLUSTRATIONS
=> "List of Illustrations",
208 Reference
::LIST_OF_TABLES
=> "List of Tables",
209 Reference
::GLOSSARY
=> "Glossary",
210 Reference
::INDEX
=> "Index");
212 $this->docRoot
= filter_input(INPUT_SERVER
, "DOCUMENT_ROOT") . "/";
214 $this->isCurlInstalled
= extension_loaded('curl') && function_exists('curl_version');
215 $this->isGdInstalled
= extension_loaded('gd') && function_exists('gd_info');
216 $this->isExifInstalled
= extension_loaded('exif') && function_exists('exif_imagetype');
217 $this->isFileGetContentsInstalled
= function_exists('file_get_contents');
218 $this->isFileGetContentsExtInstalled
= $this->isFileGetContentsInstalled
&& ini_get('allow_url_fopen');
220 $this->zip
= new Zip();
221 $this->zip
->setExtraField(FALSE);
222 $this->zip
->addFile("application/epub+zip", "mimetype");
223 $this->zip
->setExtraField(TRUE);
224 $this->zip
->addDirectory("META-INF");
226 $this->content
= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">\n\t<rootfiles>\n\t\t<rootfile full-path=\"" . $this->bookRoot
. "book.opf\" media-type=\"application/oebps-package+xml\" />\n\t</rootfiles>\n</container>\n";
228 if (!$this->isEPubVersion2()) {
229 $this->htmlContentHeader
= "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
230 . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n"
232 . "<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n"
233 . "<title></title>\n"
238 $this->zip
->addFile($this->content
, "META-INF/container.xml", 0, NULL, FALSE);
239 $this->content
= NULL;
240 $this->ncx
= new Ncx(NULL, NULL, NULL, $this->languageCode
, $this->writingDirection
);
241 $this->opf
= new Opf();
242 $this->ncx
->setVersion($this->bookVersion
);
243 $this->opf
->setVersion($this->bookVersion
);
244 $this->opf
->addItem("ncx", "book.ncx", Ncx
::MIMETYPE
);
245 $this->chapterCount
= 0;
249 * Add dynamically generated data as a file to the book.
251 * @param string $fileName Filename to use for the file, must be unique for the book.
252 * @param string $fileId Unique identifier for the file.
253 * @param string $fileData File data
254 * @param string $mimetype file mime type
255 * @return bool $success
257 function addFile($fileName, $fileId, $fileData, $mimetype) {
258 if ($this->isFinalized
|| array_key_exists($fileName, $this->fileList
)) {
262 $fileName = $this->normalizeFileName($fileName);
264 $compress = (strpos($mimetype, "image/") !== 0);
266 $this->zip
->addFile($fileData, $this->bookRoot
.$fileName, 0, NULL, $compress);
267 $this->fileList
[$fileName] = $fileName;
268 $this->opf
->addItem($fileId, $fileName, $mimetype);
273 * Add a large file directly from the filestystem to the book.
275 * @param string $fileName Filename to use for the file, must be unique for the book.
276 * @param string $fileId Unique identifier for the file.
277 * @param string $filePath File path
278 * @param string $mimetype file mime type
279 * @return bool $success
281 function addLargeFile($fileName, $fileId, $filePath, $mimetype) {
282 if ($this->isFinalized
|| array_key_exists($fileName, $this->fileList
)) {
285 $fileName = $this->normalizeFileName($fileName);
287 if ($this->zip
->addLargeFile($filePath, $this->bookRoot
.$fileName)) {
288 $this->fileList
[$fileName] = $fileName;
289 $this->opf
->addItem($fileId, $fileName, $mimetype);
296 * Add a CSS file to the book.
298 * @param string $fileName Filename to use for the CSS file, must be unique for the book.
299 * @param string $fileId Unique identifier for the file.
300 * @param string $fileData CSS data
301 * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? See documentation for <code>processCSSExternalReferences</code> for explanation. Default is EPub::EXTERNAL_REF_IGNORE.
302 * @param string $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE.
304 * @return bool $success
306 function addCSSFile($fileName, $fileId, $fileData, $externalReferences = EPub
::EXTERNAL_REF_IGNORE
, $baseDir = "") {
307 if ($this->isFinalized
|| array_key_exists($fileName, $this->fileList
)) {
310 $fileName = Zip
::getRelativePath($fileName);
311 $fileName = preg_replace('#^[/\.]+#i', "", $fileName);
313 if ($externalReferences !== EPub
::EXTERNAL_REF_IGNORE
) {
314 $cssDir = pathinfo($fileName);
315 $cssDir = preg_replace('#^[/\.]+#i', "", $cssDir["dirname"] . "/");
316 if (!empty($cssDir)) {
317 $cssDir = preg_replace('#[^/]+/#i', "../", $cssDir);
320 $this->processCSSExternalReferences($fileData, $externalReferences, $baseDir, $cssDir);
323 $this->addFile($fileName, "css_" . $fileId, $fileData, "text/css");
329 * Add a chapter to the book, as a chapter should not exceed 250kB, you can parse an array with multiple parts as $chapterData.
330 * These will still only show up as a single chapter in the book TOC.
332 * @param string $chapterName Name of the chapter, will be use din the TOC
333 * @param string $fileName Filename to use for the chapter, must be unique for the book.
334 * @param string $chapter Chapter text in XHTML or array $chapterData valid XHTML data for the chapter. File should NOT exceed 250kB.
335 * @param bool $autoSplit Should the chapter be split if it exceeds the default split size? Default=FALSE, only used if $chapterData is a string.
336 * @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? See documentation for <code>processChapterExternalReferences</code> for explanation. Default is EPub::EXTERNAL_REF_IGNORE.
337 * @param string $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE.
338 * @return mixed $success FALSE if the addition failed, else the new NavPoint.
340 function addChapter($chapterName, $fileName, $chapterData = NULL, $autoSplit = FALSE, $externalReferences = EPub
::EXTERNAL_REF_IGNORE
, $baseDir = "") {
341 if ($this->isFinalized
) {
344 $fileName = Zip
::getRelativePath($fileName);
345 $fileName = preg_replace('#^[/\.]+#i', "", $fileName);
347 $chapter = $chapterData;
348 if ($autoSplit && is_string($chapterData) && mb_strlen($chapterData) > $this->splitDefaultSize
) {
349 $splitter = new EPubChapterSplitter();
351 $chapterArray = $splitter->splitChapter($chapterData);
352 if (count($chapterArray) > 1) {
353 $chapter = $chapterArray;
357 if (!empty($chapter) && is_string($chapter)) {
358 if ($externalReferences !== EPub
::EXTERNAL_REF_IGNORE
) {
359 $htmlDirInfo = pathinfo($fileName);
360 $htmlDir = preg_replace('#^[/\.]+#i', "", $htmlDirInfo["dirname"] . "/");
361 $this->processChapterExternalReferences($chapter, $externalReferences, $baseDir, $htmlDir);
364 if ($this->encodeHTML
=== TRUE) {
365 $chapter = $this->encodeHtml($chapter);
368 $this->chapterCount++
;
369 $this->addFile($fileName, "chapter" . $this->chapterCount
, $chapter, "application/xhtml+xml");
370 $this->opf
->addItemRef("chapter" . $this->chapterCount
);
372 $navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $fileName, "chapter" . $this->chapterCount
);
373 $this->ncx
->addNavPoint($navPoint);
374 $this->ncx
->chapterList
[$chapterName] = $navPoint;
375 } else if (is_array($chapter)) {
376 $fileNameParts = pathinfo($fileName);
377 $extension = $fileNameParts['extension'];
378 $name = $fileNameParts['filename'];
381 $this->chapterCount++
;
383 $oneChapter = each($chapter);
384 while ($oneChapter) {
385 list($k, $v) = $oneChapter;
386 if ($this->encodeHTML
=== TRUE) {
387 $v = $this->encodeHtml($v);
390 if ($externalReferences !== EPub
::EXTERNAL_REF_IGNORE
) {
391 $this->processChapterExternalReferences($v, $externalReferences, $baseDir);
394 $partName = $name . "_" . $partCount;
395 $this->addFile($partName . "." . $extension, $partName, $v, "application/xhtml+xml");
396 $this->opf
->addItemRef($partName);
398 $oneChapter = each($chapter);
400 $partName = $name . "_1." . $extension;
401 $navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $partName, $partName);
402 $this->ncx
->addNavPoint($navPoint);
404 $this->ncx
->chapterList
[$chapterName] = $navPoint;
405 } else if (!isset($chapterData) && strpos($fileName, "#") > 0) {
406 $this->chapterCount++
;
407 //$this->opf->addItemRef("chapter" . $this->chapterCount);
409 $navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $fileName, "chapter" . $this->chapterCount
);
410 $this->ncx
->addNavPoint($navPoint);
411 $this->ncx
->chapterList
[$chapterName] = $navPoint;
412 } else if (!isset($chapterData) && $fileName=="TOC.xhtml") {
413 $this->chapterCount++
;
414 $this->opf
->addItemRef("toc");
416 $navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $fileName, "chapter" . $this->chapterCount
);
417 $this->ncx
->addNavPoint($navPoint);
418 $this->ncx
->chapterList
[$chapterName] = $navPoint;
424 * Add one chapter level.
426 * Subsequent chapters will be added to this level.
428 * @param string $navTitle
429 * @param string $navId
430 * @param string $navClass
431 * @param int $isNavHidden
432 * @param string $writingDirection
433 * @return NavPoint The new NavPoint for that level.
435 function subLevel($navTitle = NULL, $navId = NULL, $navClass = NULL, $isNavHidden = FALSE, $writingDirection = NULL) {
436 return $this->ncx
->subLevel($this->decodeHtmlEntities($navTitle), $navId, $navClass, $isNavHidden, $writingDirection);
440 * Step back one chapter level.
442 * Subsequent chapters will be added to this chapters parent level.
444 function backLevel() {
445 $this->ncx
->backLevel();
449 * Step back to the root level.
451 * Subsequent chapters will be added to the rooot NavMap.
453 function rootLevel() {
454 $this->ncx
->rootLevel();
458 * Step back to the given level.
459 * Useful for returning to a previous level from deep within the structure.
460 * Values below 2 will have the same effect as rootLevel()
462 * @param int $newLevel
464 function setCurrentLevel($newLevel) {
465 $this->ncx
->setCurrentLevel($newLevel);
469 * Get current level count.
470 * The indentation of the current structure point.
472 * @return current level count;
474 function getCurrentLevel() {
475 return $this->ncx
->getCurrentLevel();
479 * Wrap ChapterContent with Head and Footer
482 * @return string $content
484 private function wrapChapter($content) {
485 return $this->htmlContentHeader
. "\n" . $content . "\n" . $this->htmlContentFooter
;
489 * Reference pages is usually one or two pages for items such as Table of Contents, reference lists, Author notes or Acknowledgements.
490 * These do not show up in the regular navigation list.
492 * As they are supposed to be short.
494 * @param string $pageName Name of the chapter, will be use din the TOC
495 * @param string $fileName Filename to use for the chapter, must be unique for the book.
496 * @param string $pageData Page content in XHTML. File should NOT exceed 250kB.
497 * @param string $reference Reference key
498 * @param int $externalReferences How to handle external references. See documentation for <code>processChapterExternalReferences</code> for explanation. Default is EPub::EXTERNAL_REF_IGNORE.
499 * @param string $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE.
500 * @return bool $success
502 function addReferencePage($pageName, $fileName, $pageData, $reference, $externalReferences = EPub
::EXTERNAL_REF_IGNORE
, $baseDir = "") {
503 if ($this->isFinalized
) {
506 $fileName = Zip
::getRelativePath($fileName);
507 $fileName = preg_replace('#^[/\.]+#i', "", $fileName);
510 if (!empty($pageData) && is_string($pageData)) {
511 if ($this->encodeHTML
=== TRUE) {
512 $pageData = $this->encodeHtml($pageData);
515 $this->wrapChapter($pageData);
517 if ($externalReferences !== EPub
::EXTERNAL_REF_IGNORE
) {
518 $htmlDirInfo = pathinfo($fileName);
519 $htmlDir = preg_replace('#^[/\.]+#i', "", $htmlDirInfo["dirname"] . "/");
520 $this->processChapterExternalReferences($pageData, $externalReferences, $baseDir, $htmlDir);
523 $this->addFile($fileName, "ref_" . $reference, $pageData, "application/xhtml+xml");
525 if ($reference !== Reference
::TABLE_OF_CONTENTS
|| !isset($this->ncx
->referencesList
[$reference])) {
526 $this->opf
->addItemRef("ref_" . $reference, FALSE);
527 $this->opf
->addReference($reference, $pageName, $fileName);
529 $this->ncx
->referencesList
[$reference] = $fileName;
530 $this->ncx
->referencesName
[$reference] = $pageName;
538 * Add custom metadata to the book.
540 * It is up to the builder to make sure there are no collisions. Metadata are just key value pairs.
542 * @param string $name
543 * @param string $content
545 function addCustomMetadata($name, $content) {
546 $this->opf
->addMeta($name, $content);
550 * Add DublinCore metadata to the book
552 * Use the DublinCore constants included in EPub, ie DublinCore::DATE
554 * @param string $dublinCore name
555 * @param string $value
557 function addDublinCoreMetadata($dublinCoreConstant, $value) {
558 if ($this->isFinalized
) {
562 $this->opf
->addDCMeta($dublinCoreConstant, $this->decodeHtmlEntities($value));
566 * Add a cover image to the book.
567 * If the $imageData is not set, the function assumes the $fileName is the path to the image file.
569 * The styling and structure of the generated XHTML is heavily inspired by the XHTML generated by Calibre.
571 * @param string $fileName Filename to use for the image, must be unique for the book.
572 * @param string $imageData Binary image data
573 * @param string $mimetype Image mimetype, such as "image/jpeg" or "image/png".
574 * @return bool $success
576 function setCoverImage($fileName, $imageData = NULL, $mimetype = NULL) {
577 if ($this->isFinalized
|| $this->isCoverImageSet
|| array_key_exists("CoverPage.html", $this->fileList
)) {
581 if ($imageData == NULL) {
582 // assume $fileName is the valid file path.
583 if (!file_exists($fileName)) {
584 // Attempt to locate the file using the doc root.
585 $rp = realpath($this->docRoot
. "/" . $fileName);
588 // only assign the docroot path if it actually exists there.
592 $image = $this->getImage($fileName);
593 $imageData = $image['image'];
594 $mimetype = $image['mime'];
595 $fileName = preg_replace("#\.[^\.]+$#", "." . $image['ext'], $fileName);
599 $path = pathinfo($fileName);
600 $imgPath = "images/" . $path["basename"];
602 if (empty($mimetype) && file_exists($fileName)) {
603 list($width, $height, $type, $attr) = getimagesize($fileName);
604 $mimetype = image_type_to_mime_type($type);
606 if (empty($mimetype)) {
607 $ext = strtolower($path['extension']);
611 $mimetype = "image/" . $ext;
616 if ($this->isEPubVersion2()) {
617 $coverPage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
618 . "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n"
619 . " \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
620 . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\" xml:lang=\"en\">\n"
622 . "\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n"
623 . "\t\t<title>Cover Image</title>\n"
624 . "\t\t<link type=\"text/css\" rel=\"stylesheet\" href=\"Styles/CoverPage.css\" />\n"
628 . "\t\t\t<img src=\"" . $imgPath . "\" alt=\"Cover image\" style=\"height: 100%\"/>\n"
633 $coverPage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
634 . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n"
636 . "\t<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n"
637 . "\t\t<title>Cover Image</title>\n"
638 . "\t\t<link type=\"text/css\" rel=\"stylesheet\" href=\"Styles/CoverPage.css\" />\n"
641 . "\t\t<section epub:type=\"cover\">\n"
642 . "\t\t\t<img src=\"" . $imgPath . "\" alt=\"Cover image\" style=\"height: 100%\"/>\n"
647 $coverPageCss = "@page, body, div, img {\n"
648 . "\tpadding: 0pt;\n"
651 . "\ttext-align: center;\n"
654 $this->addCSSFile("Styles/CoverPage.css", "CoverPageCss", $coverPageCss);
655 $this->addFile($imgPath, "CoverImage", $imageData, $mimetype);
656 $this->addReferencePage("CoverPage", "CoverPage.xhtml", $coverPage, "cover");
657 $this->isCoverImageSet
= TRUE;
662 * Process external references from a HTML to the book. The chapter itself is not stored.
663 * the HTML is scanned for <link..., <style..., and <img tags.
664 * Embedded CSS styles and links will also be processed.
665 * Script tags are not processed, as scripting should be avoided in e-books.
667 * EPub keeps track of added files, and duplicate files referenced across multiple
668 * chapters, are only added once.
670 * If the $doc is a string, it is assumed to be the content of an HTML file,
671 * else is it assumes to be a DOMDocument.
673 * Basedir is the root dir the HTML is supposed to "live" in, used to resolve
674 * relative references such as <code><img src="../images/image.png"/></code>
676 * $externalReferences determines how the function will handle external references.
678 * @param mixed &$doc (referenced)
679 * @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.
680 * @param string $baseDir Default is "", meaning it is pointing to the document root.
681 * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive.
683 * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
685 protected function processChapterExternalReferences(&$doc, $externalReferences = EPub
::EXTERNAL_REF_ADD
, $baseDir = "", $htmlDir = "") {
686 if ($this->isFinalized
|| $externalReferences === EPub
::EXTERNAL_REF_IGNORE
) {
690 $backPath = preg_replace('#[^/]+/#i', "../", $htmlDir);
691 $isDocAString = is_string($doc);
695 $xmlDoc = new DOMDocument();
696 @$xmlDoc->loadHTML($doc);
701 $this->processChapterStyles($xmlDoc, $externalReferences, $baseDir, $htmlDir);
702 $this->processChapterLinks($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath);
703 $this->processChapterImages($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath);
704 $this->processChapterSources($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath);
707 //$html = $xmlDoc->saveXML();
709 $htmlNode = $xmlDoc->getElementsByTagName("html");
710 $headNode = $xmlDoc->getElementsByTagName("head");
711 $bodyNode = $xmlDoc->getElementsByTagName("body");
714 for ($index = 0; $index < $htmlNode->item(0)->attributes
->length
; $index++
) {
715 $nodeName = $htmlNode->item(0)->attributes
->item($index)->nodeName
;
716 $nodeValue = $htmlNode->item(0)->attributes
->item($index)->nodeValue
;
718 if ($nodeName != "xmlns") {
719 $htmlNS .= " $nodeName=\"$nodeValue\"";
723 $xml = new DOMDocument('1.0', "utf-8");
724 $xml->lookupPrefix("http://www.w3.org/1999/xhtml");
725 $xml->preserveWhiteSpace
= FALSE;
726 $xml->formatOutput
= TRUE;
728 $xml2Doc = new DOMDocument('1.0', "utf-8");
729 $xml2Doc->lookupPrefix("http://www.w3.org/1999/xhtml");
730 $xml2Doc->loadXML("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\"$htmlNS>\n</html>\n");
731 $html = $xml2Doc->getElementsByTagName("html")->item(0);
732 $html->appendChild($xml2Doc->importNode($headNode->item(0), TRUE));
733 $html->appendChild($xml2Doc->importNode($bodyNode->item(0), TRUE));
735 // force pretty printing and correct formatting, should not be needed, but it is.
736 $xml->loadXML($xml2Doc->saveXML());
737 $doc = $xml->saveXML();
739 if (!$this->isEPubVersion2()) {
740 $doc = preg_replace('#^\s*<!DOCTYPE\ .+?>\s*#im', '', $doc);
747 * Process images referenced from an CSS file to the book.
749 * $externalReferences determins how the function will handle external references.
751 * @param string &$cssFile (referenced)
752 * @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.
753 * @param string $baseDir Default is "", meaning it is pointing to the document root.
754 * @param string $cssDir The of the CSS file's directory from the root of the archive.
756 * @return bool FALSE if unsuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
758 protected function processCSSExternalReferences(&$cssFile, $externalReferences = EPub
::EXTERNAL_REF_ADD
, $baseDir = "", $cssDir = "") {
759 if ($this->isFinalized
|| $externalReferences === EPub
::EXTERNAL_REF_IGNORE
) {
763 $backPath = preg_replace('#[^/]+/#i', "../", $cssDir);
765 preg_match_all('#url\s*\([\'\"\s]*(.+?)[\'\"\s]*\)#im', $cssFile, $imgs, PREG_SET_ORDER
);
767 $itemCount = count($imgs);
768 for ($idx = 0; $idx < $itemCount; $idx++
) {
770 if ($externalReferences === EPub
::EXTERNAL_REF_REMOVE_IMAGES
|| $externalReferences === EPub
::EXTERNAL_REF_REPLACE_IMAGES
) {
771 $cssFile = str_replace($img[0], "", $cssFile);
775 $pathData = pathinfo($source);
776 $internalSrc = $pathData['basename'];
778 $isSourceExternal = FALSE;
780 if ($this->resolveImage($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $cssDir, $backPath)) {
781 $cssFile = str_replace($img[0], "url('" . $backPath . $internalPath . "')", $cssFile);
782 } else if ($isSourceExternal) {
783 $cssFile = str_replace($img[0], "", $cssFile); // External image is missing
784 } // else do nothing, if the image is local, and missing, assume it's been generated.
791 * Process style tags in a DOMDocument. Styles will be passed as CSS files and reinserted into the document.
793 * @param DOMDocument &$xmlDoc (referenced)
794 * @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.
795 * @param string $baseDir Default is "", meaning it is pointing to the document root.
796 * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive.
798 * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
800 protected function processChapterStyles(&$xmlDoc, $externalReferences = EPub
::EXTERNAL_REF_ADD
, $baseDir = "", $htmlDir = "") {
801 if ($this->isFinalized
|| $externalReferences === EPub
::EXTERNAL_REF_IGNORE
) {
804 // process inlined CSS styles in style tags.
805 $styles = $xmlDoc->getElementsByTagName("style");
806 $styleCount = $styles->length
;
807 for ($styleIdx = 0; $styleIdx < $styleCount; $styleIdx++
) {
808 $style = $styles->item($styleIdx);
810 $styleData = preg_replace('#[/\*\s]*\<\!\[CDATA\[[\s\*/]*#im', "", $style->nodeValue
);
811 $styleData = preg_replace('#[/\*\s]*\]\]\>[\s\*/]*#im', "", $styleData);
813 $this->processCSSExternalReferences($styleData, $externalReferences, $baseDir, $htmlDir);
814 $style->nodeValue
= "\n" . trim($styleData) . "\n";
820 * 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.
821 * Link types text/css will be passed as CSS files.
823 * @param DOMDocument &$xmlDoc (referenced)
824 * @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.
825 * @param string $baseDir Default is "", meaning it is pointing to the document root.
826 * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive.
827 * @param string $backPath The path to get back to the root of the archive from $htmlDir.
829 * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
831 protected function processChapterLinks(&$xmlDoc, $externalReferences = EPub
::EXTERNAL_REF_ADD
, $baseDir = "", $htmlDir = "", $backPath = "") {
832 if ($this->isFinalized
|| $externalReferences === EPub
::EXTERNAL_REF_IGNORE
) {
835 // process link tags.
836 $links = $xmlDoc->getElementsByTagName("link");
837 $linkCount = $links->length
;
838 for ($linkIdx = 0; $linkIdx < $linkCount; $linkIdx++
) {
839 $link = $links->item($linkIdx);
840 $source = $link->attributes
->getNamedItem("href")->nodeValue
;
843 $pathData = pathinfo($source);
844 $internalSrc = $pathData['basename'];
846 if (preg_match('#^(http|ftp)s?://#i', $source) == 1) {
847 $urlinfo = parse_url($source);
849 if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) {
850 $internalSrc = substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") +
strlen($baseDir) +
1);
853 @$sourceData = getFileContents($source);
854 } else if (strpos($source, "/") === 0) {
855 @$sourceData = file_get_contents($this->docRoot
. $source);
857 @$sourceData = file_get_contents($this->docRoot
. $baseDir . "/" . $source);
860 if (!empty($sourceData)) {
861 if (!array_key_exists($internalSrc, $this->fileList
)) {
862 $mime = $link->attributes
->getNamedItem("type")->nodeValue
;
864 $mime = "text/plain";
866 if ($mime == "text/css") {
867 $this->processCSSExternalReferences($sourceData, $externalReferences, $baseDir, $htmlDir);
868 $this->addCSSFile($internalSrc, $internalSrc, $sourceData, EPub
::EXTERNAL_REF_IGNORE
, $baseDir);
869 $link->setAttribute("href", $backPath . $internalSrc);
871 $this->addFile($internalSrc, $internalSrc, $sourceData, $mime);
873 $this->fileList
[$internalSrc] = $source;
875 $link->setAttribute("href", $backPath . $internalSrc);
877 } // else do nothing, if the link is local, and missing, assume it's been generated.
883 * Process img tags in a DOMDocument.
884 * $externalReferences will determine what will happen to these images, and the img src will be rewritten accordingly.
886 * @param DOMDocument &$xmlDoc (referenced)
887 * @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.
888 * @param string $baseDir Default is "", meaning it is pointing to the document root.
889 * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive.
890 * @param string $backPath The path to get back to the root of the archive from $htmlDir.
892 * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
894 protected function processChapterImages(&$xmlDoc, $externalReferences = EPub
::EXTERNAL_REF_ADD
, $baseDir = "", $htmlDir = "", $backPath = "") {
895 if ($this->isFinalized
|| $externalReferences === EPub
::EXTERNAL_REF_IGNORE
) {
899 $postProcDomElememts = array();
900 $images = $xmlDoc->getElementsByTagName("img");
901 $itemCount = $images->length
;
903 for ($idx = 0; $idx < $itemCount; $idx++
) {
904 $img = $images->item($idx);
906 if ($externalReferences === EPub
::EXTERNAL_REF_REMOVE_IMAGES
) {
907 $postProcDomElememts[] = $img;
908 } else if ($externalReferences === EPub
::EXTERNAL_REF_REPLACE_IMAGES
) {
909 $altNode = $img->attributes
->getNamedItem("alt");
911 if ($altNode !== NULL && strlen($altNode->nodeValue
) > 0) {
912 $alt = $altNode->nodeValue
;
914 $postProcDomElememts[] = array($img, $this->createDomFragment($xmlDoc, "<em>[" . $alt . "]</em>"));
916 $source = $img->attributes
->getNamedItem("src")->nodeValue
;
918 $parsedSource = parse_url($source);
919 $internalSrc = $this->sanitizeFileName(urldecode(pathinfo($parsedSource['path'], PATHINFO_BASENAME
)));
921 $isSourceExternal = FALSE;
923 if ($this->resolveImage($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $htmlDir, $backPath)) {
924 $img->setAttribute("src", $backPath . $internalPath);
925 } else if ($isSourceExternal) {
926 $postProcDomElememts[] = $img; // External image is missing
927 } // else do nothing, if the image is local, and missing, assume it's been generated.
931 foreach ($postProcDomElememts as $target) {
932 if (is_array($target)) {
933 $target[0]->parentNode
->replaceChild($target[1], $target[0]);
935 $target->parentNode
->removeChild($target);
942 * Process source tags in a DOMDocument.
943 * $externalReferences will determine what will happen to these images, and the img src will be rewritten accordingly.
945 * @param DOMDocument &$xmlDoc (referenced)
946 * @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.
947 * @param string $baseDir Default is "", meaning it is pointing to the document root.
948 * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive.
949 * @param string $backPath The path to get back to the root of the archive from $htmlDir.
951 * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
953 protected function processChapterSources(&$xmlDoc, $externalReferences = EPub
::EXTERNAL_REF_ADD
, $baseDir = "", $htmlDir = "", $backPath = "") {
954 if ($this->isFinalized
|| $externalReferences === EPub
::EXTERNAL_REF_IGNORE
) {
958 if ($this->bookVersion
!== EPub
::BOOK_VERSION_EPUB3
) {
959 // ePub 2 does not support multimedia formats, and they must be removed.
960 $externalReferences = EPub
::EXTERNAL_REF_REMOVE_IMAGES
;
963 $postProcDomElememts = array();
964 $images = $xmlDoc->getElementsByTagName("source");
965 $itemCount = $images->length
;
966 for ($idx = 0; $idx < $itemCount; $idx++
) {
967 $img = $images->item($idx);
968 if ($externalReferences === EPub
::EXTERNAL_REF_REMOVE_IMAGES
) {
969 $postProcDomElememts[] = $img;
970 } else if ($externalReferences === EPub
::EXTERNAL_REF_REPLACE_IMAGES
) {
971 $altNode = $img->attributes
->getNamedItem("alt");
973 if ($altNode !== NULL && strlen($altNode->nodeValue
) > 0) {
974 $alt = $altNode->nodeValue
;
976 $postProcDomElememts[] = array($img, $this->createDomFragment($xmlDoc, "[" . $alt . "]"));
978 $source = $img->attributes
->getNamedItem("src")->nodeValue
;
980 $parsedSource = parse_url($source);
981 $internalSrc = $this->sanitizeFileName(urldecode(pathinfo($parsedSource['path'], PATHINFO_BASENAME
)));
983 $isSourceExternal = FALSE;
985 if ($this->resolveMedia($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $htmlDir, $backPath)) {
986 $img->setAttribute("src", $backPath . $internalPath);
987 } else if ($isSourceExternal) {
988 $postProcDomElememts[] = $img; // External image is missing
989 } // else do nothing, if the image is local, and missing, assume it's been generated.
995 * Resolve an image src and determine it's target location and add it to the book.
997 * @param string $source Image Source link.
998 * @param string &$internalPath (referenced) Return value, will be set to the target path and name in the book.
999 * @param string &$internalSrc (referenced) Return value, will be set to the target name in the book.
1000 * @param string &$isSourceExternal (referenced) Return value, will be set to TRUE if the image originated from a full URL.
1001 * @param string $baseDir Default is "", meaning it is pointing to the document root.
1002 * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive.
1003 * @param string $backPath The path to get back to the root of the archive from $htmlDir.
1005 protected function resolveImage($source, &$internalPath, &$internalSrc, &$isSourceExternal, $baseDir = "", $htmlDir = "", $backPath = "") {
1006 if ($this->isFinalized
) {
1011 if (preg_match('#^(http|ftp)s?://#i', $source) == 1) {
1012 $urlinfo = parse_url($source);
1013 $urlPath = pathinfo($urlinfo['path']);
1015 if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) {
1016 $internalSrc = $this->sanitizeFileName(urldecode(substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") +
strlen($baseDir) +
1)));
1018 $internalPath = $urlinfo["scheme"] . "/" . $urlinfo["host"] . "/" . pathinfo($urlinfo["path"], PATHINFO_DIRNAME
);
1019 $isSourceExternal = TRUE;
1020 $imageData = $this->getImage($source);
1021 } else if (strpos($source, "/") === 0) {
1022 $internalPath = pathinfo($source, PATHINFO_DIRNAME
);
1025 if (!file_exists($path)) {
1026 $path = $this->docRoot
. $path;
1029 $imageData = $this->getImage($path);
1031 $internalPath = $htmlDir . "/" . preg_replace('#^[/\.]+#', '', pathinfo($source, PATHINFO_DIRNAME
));
1033 $path = $baseDir . "/" . $source;
1034 if (!file_exists($path)) {
1035 $path = $this->docRoot
. $path;
1038 $imageData = $this->getImage($path);
1040 if ($imageData !== FALSE) {
1041 $iSrcInfo = pathinfo($internalSrc);
1042 if (!empty($imageData['ext']) && $imageData['ext'] != $iSrcInfo['extension']) {
1043 $internalSrc = $iSrcInfo['filename'] . "." . $imageData['ext'];
1045 $internalPath = Zip
::getRelativePath("images/" . $internalPath . "/" . $internalSrc);
1046 if (!array_key_exists($internalPath, $this->fileList
)) {
1047 $this->addFile($internalPath, "i_" . $internalSrc, $imageData['image'], $imageData['mime']);
1048 $this->fileList
[$internalPath] = $source;
1056 * Resolve a media src and determine it's target location and add it to the book.
1058 * @param string $source Source link.
1059 * @param string $internalPath (referenced) Return value, will be set to the target path and name in the book.
1060 * @param string $internalSrc (referenced) Return value, will be set to the target name in the book.
1061 * @param string $isSourceExternal (referenced) Return value, will be set to TRUE if the image originated from a full URL.
1062 * @param string $baseDir Default is "", meaning it is pointing to the document root.
1063 * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive.
1064 * @param string $backPath The path to get back to the root of the archive from $htmlDir.
1066 protected function resolveMedia($source, &$internalPath, &$internalSrc, &$isSourceExternal, $baseDir = "", $htmlDir = "", $backPath = "") {
1067 if ($this->isFinalized
) {
1073 if (preg_match('#^(http|ftp)s?://#i', $source) == 1) {
1074 $urlinfo = parse_url($source);
1076 if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) {
1077 $internalSrc = substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") +
strlen($baseDir) +
1);
1079 $internalPath = $urlinfo["scheme"] . "/" . $urlinfo["host"] . "/" . pathinfo($urlinfo["path"], PATHINFO_DIRNAME
);
1080 $isSourceExternal = TRUE;
1081 $mediaPath = $this->getFileContents($source, true);
1082 $tmpFile = $mediaPath;
1083 } else if (strpos($source, "/") === 0) {
1084 $internalPath = pathinfo($source, PATHINFO_DIRNAME
);
1086 $mediaPath = $source;
1087 if (!file_exists($mediaPath)) {
1088 $mediaPath = $this->docRoot
. $mediaPath;
1091 $internalPath = $htmlDir . "/" . preg_replace('#^[/\.]+#', '', pathinfo($source, PATHINFO_DIRNAME
));
1093 $mediaPath = $baseDir . "/" . $source;
1094 if (!file_exists($mediaPath)) {
1095 $mediaPath = $this->docRoot
. $mediaPath;
1099 if ($mediaPath !== FALSE) {
1100 $mime = $this->getMime($source);
1101 $internalPath = Zip
::getRelativePath("media/" . $internalPath . "/" . $internalSrc);
1103 if (!array_key_exists($internalPath, $this->fileList
) &&
1104 $this->addLargeFile($internalPath, "m_" . $internalSrc, $mediaPath, $mime)) {
1105 $this->fileList
[$internalPath] = $source;
1107 if (isset($tmpFile)) {
1116 * Get Book Chapter count.
1119 * @return number of chapters
1121 function getChapterCount() {
1122 return $this->chapterCount
;
1126 * Book title, mandatory.
1128 * Used for the dc:title metadata parameter in the OPF file as well as the DocTitle attribute in the NCX file.
1130 * @param string $title
1132 * @return bool $success
1134 function setTitle($title) {
1135 if ($this->isFinalized
) {
1138 $this->title
= $title;
1148 function getTitle() {
1149 return $this->title
;
1153 * Book language, mandatory
1155 * Use the RFC3066 Language codes, such as "en", "da", "fr" etc.
1158 * Used for the dc:language metadata parameter in the OPF file.
1160 * @param string $language
1162 * @return bool $success
1164 function setLanguage($language) {
1165 if ($this->isFinalized
|| mb_strlen($language) != 2) {
1168 $this->language
= $language;
1173 * Get Book language.
1178 function getLanguage() {
1179 return $this->language
;
1183 * Unique book identifier, mandatory.
1184 * Use the URI, or ISBN if available.
1186 * An unambiguous reference to the resource within a given context.
1188 * Recommended best practice is to identify the resource by means of a
1189 * string conforming to a formal identification system.
1191 * Used for the dc:identifier metadata parameter in the OPF file, as well
1192 * as dtb:uid in the NCX file.
1194 * Identifier type should only be:
1195 * EPub::IDENTIFIER_URI
1196 * EPub::IDENTIFIER_ISBN
1197 * EPub::IDENTIFIER_UUID
1199 * @param string $identifier
1200 * @param string $identifierType
1202 * @return bool $success
1204 function setIdentifier($identifier, $identifierType) {
1205 if ($this->isFinalized
|| ($identifierType !== EPub
::IDENTIFIER_URI
&& $identifierType !== EPub
::IDENTIFIER_ISBN
&& $identifierType !== EPub
::IDENTIFIER_UUID
)) {
1208 $this->identifier
= $identifier;
1209 $this->identifierType
= $identifierType;
1214 * Get Book identifier.
1217 * @return $identifier
1219 function getIdentifier() {
1220 return $this->identifier
;
1224 * Get Book identifierType.
1227 * @return $identifierType
1229 function getIdentifierType() {
1230 return $this->identifierType
;
1234 * Book description, optional.
1236 * An account of the resource.
1238 * Description may include but is not limited to: an abstract, a table of
1239 * contents, a graphical representation, or a free-text account of the
1242 * Used for the dc:source metadata parameter in the OPF file
1244 * @param string $description
1246 * @return bool $success
1248 function setDescription($description) {
1249 if ($this->isFinalized
) {
1252 $this->description
= $description;
1257 * Get Book description.
1260 * @return $description
1262 function getDescription() {
1263 return $this->description
;
1267 * Book author or creator, optional.
1268 * The $authorSortKey is basically how the name is to be sorted, usually
1269 * it's "Lastname, First names" where the $author is the straight
1270 * "Firstnames Lastname"
1272 * An entity primarily responsible for making the resource.
1274 * Examples of a Creator include a person, an organization, or a service.
1275 * Typically, the name of a Creator should be used to indicate the entity.
1277 * Used for the dc:creator metadata parameter in the OPF file and the
1278 * docAuthor attribure in the NCX file.
1279 * The sort key is used for the opf:file-as attribute in dc:creator.
1281 * @param string $author
1282 * @param string $authorSortKey
1284 * @return bool $success
1286 function setAuthor($author, $authorSortKey) {
1287 if ($this->isFinalized
) {
1290 $this->author
= $author;
1291 $this->authorSortKey
= $authorSortKey;
1301 function getAuthor() {
1302 return $this->author
;
1306 * Publisher Information, optional.
1308 * An entity responsible for making the resource available.
1310 * Examples of a Publisher include a person, an organization, or a service.
1311 * Typically, the name of a Publisher should be used to indicate the entity.
1313 * Used for the dc:publisher and dc:relation metadata parameters in the OPF file.
1315 * @param string $publisherName
1316 * @param string $publisherURL
1318 * @return bool $success
1320 function setPublisher($publisherName, $publisherURL) {
1321 if ($this->isFinalized
) {
1324 $this->publisherName
= $publisherName;
1325 $this->publisherURL
= $publisherURL;
1330 * Get Book publisherName.
1333 * @return $publisherName
1335 function getPublisherName() {
1336 return $this->publisherName
;
1340 * Get Book publisherURL.
1343 * @return $publisherURL
1345 function getPublisherURL() {
1346 return $this->publisherURL
;
1350 * Release date, optional. If left blank, the time of the finalization will
1353 * A point or period of time associated with an event in the lifecycle of
1356 * Date may be used to express temporal information at any level of
1357 * granularity. Recommended best practice is to use an encoding scheme,
1358 * such as the W3CDTF profile of ISO 8601 [W3CDTF].
1360 * Used for the dc:date metadata parameter in the OPF file
1362 * @param long $timestamp
1364 * @return bool $success
1366 function setDate($timestamp) {
1367 if ($this->isFinalized
) {
1370 $this->date
= $timestamp;
1371 $this->opf
->date
= $timestamp;
1381 function getDate() {
1386 * Book (copy)rights, optional.
1388 * Information about rights held in and over the resource.
1390 * Typically, rights information includes a statement about various
1391 * property rights associated with the resource, including intellectual
1394 * Used for the dc:rights metadata parameter in the OPF file
1396 * @param string $rightsText
1398 * @return bool $success
1400 function setRights($rightsText) {
1401 if ($this->isFinalized
) {
1404 $this->rights
= $rightsText;
1414 function getRights() {
1415 return $this->rights
;
1421 * The topic of the resource.
1423 * Typically, the subject will be represented using keywords, key phrases,
1424 * or classification codes. Recommended best practice is to use a
1425 * controlled vocabulary. To describe the spatial or temporal topic of the
1426 * resource, use the Coverage element.
1428 * @param string $subject
1430 function setSubject($subject) {
1431 if ($this->isFinalized
) {
1434 $this->opf
->addDCMeta(DublinCore
::SUBJECT
, $this->decodeHtmlEntities($subject));
1438 * Book source URL, optional.
1440 * A related resource from which the described resource is derived.
1442 * The described resource may be derived from the related resource in whole
1443 * or in part. Recommended best practice is to identify the related
1444 * resource by means of a string conforming to a formal identification system.
1446 * Used for the dc:source metadata parameter in the OPF file
1448 * @param string $sourceURL
1450 * @return bool $success
1452 function setSourceURL($sourceURL) {
1453 if ($this->isFinalized
) {
1456 $this->sourceURL
= $sourceURL;
1461 * Get Book sourceURL.
1464 * @return $sourceURL
1466 function getSourceURL() {
1467 return $this->sourceURL
;
1471 * Coverage, optional.
1473 * The spatial or temporal topic of the resource, the spatial applicability
1474 * of the resource, or the jurisdiction under which the resource is relevant.
1476 * Spatial topic and spatial applicability may be a named place or a location
1477 * specified by its geographic coordinates. Temporal topic may be a named
1478 * period, date, or date range. A jurisdiction may be a named administrative
1479 * entity or a geographic place to which the resource applies. Recommended
1480 * best practice is to use a controlled vocabulary such as the Thesaurus of
1481 * Geographic Names [TGN]. Where appropriate, named places or time periods
1482 * can be used in preference to numeric identifiers such as sets of
1483 * coordinates or date ranges.
1485 * Used for the dc:coverage metadata parameter in the OPF file
1487 * Same as ->addDublinCoreMetadata(DublinCore::COVERAGE, $coverage);
1489 * @param string $coverage
1491 * @return bool $success
1493 function setCoverage($coverage) {
1494 if ($this->isFinalized
) {
1497 $this->coverage
= $coverage;
1502 * Get Book coverage.
1507 function getCoverage() {
1508 return $this->coverage
;
1512 * Set book Relation.
1514 * A related resource.
1516 * Recommended best practice is to identify the related resource by means
1517 * of a string conforming to a formal identification system.
1519 * @param string $relation
1521 function setRelation($relation) {
1522 if ($this->isFinalized
) {
1525 $this->relation
= $relation;
1529 * Get the book relation.
1531 * @return string The relation.
1533 function getRelation() {
1534 return $this->relation
;
1538 * Set book Generator.
1540 * The generator is a meta tag added to the ncx file, it is not visible
1541 * from within the book, but is a kind of electronic watermark.
1543 * @param string $generator
1545 function setGenerator($generator) {
1546 if ($this->isFinalized
) {
1549 $this->generator
= $generator;
1553 * Get the book relation.
1555 * @return string The generator identity string.
1557 function getGenerator() {
1558 return $this->generator
;
1562 * Set ePub date formate to the short yyyy-mm-dd form, for compliance with
1563 * a bug in EpubCheck, prior to its version 1.1.
1565 * The latest version of ePubCheck can be obtained here:
1566 * http://code.google.com/p/epubcheck/
1569 * @return bool $success
1571 function setShortDateFormat() {
1572 if ($this->isFinalized
) {
1575 $this->dateformat
= $this->dateformatShort
;
1582 function setIgnoreEmptyBuffer($ignoreEmptyBuffer = TRUE) {
1583 die ("Function was deprecated, functionality is no longer needed.");
1587 * Set the references title for the ePub 3 landmarks section
1589 * @param string $referencesTitle
1590 * @param string $referencesId
1591 * @param string $referencesClass
1594 function setReferencesTitle($referencesTitle = "Guide", $referencesId = "", $referencesClass = "references") {
1595 if ($this->isFinalized
) {
1598 $this->ncx
->referencesTitle
= is_string($referencesTitle) ? trim($referencesTitle) : "Guide";
1599 $this->ncx
->referencesId
= is_string($referencesId) ? trim($referencesId) : "references";
1600 $this->ncx
->referencesClass
= is_string($referencesClass) ? trim($referencesClass) : "references";
1605 * Set the references title for the ePub 3 landmarks section
1607 * @param bool $referencesTitle
1609 function setisReferencesAddedToToc($isReferencesAddedToToc = TRUE) {
1610 if ($this->isFinalized
) {
1613 $this->isReferencesAddedToToc
= $isReferencesAddedToToc === TRUE;
1623 function isFinalized() {
1624 return $this->isFinalized
;
1628 * Build the Table of Contents. This is not strictly necessary, as most eReaders will build it from the navigation structure in the .ncx file.
1630 * @param string $cssFileName Include a link to this css file in the TOC html.
1631 * @param string $tocCSSClass The TOC is a <div>, if you need special formatting, you can add a css class for that div. Default is "toc".
1632 * @param string $title Title of the Table of contents. Default is "Table of Contents". Use this for ie. languages other than English.
1633 * @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.
1634 * @param bool $addToIndex Add the TOC to the NCX index at the current leve/position. Default is FALSE
1635 * @param string $tocFileName Change teh default name of the TOC file. The default is "TOC.xhtml"
1637 function buildTOC($cssFileName = NULL, $tocCSSClass = "toc", $title = "Table of Contents", $addReferences = TRUE, $addToIndex = FALSE, $tocFileName = "TOC.xhtml") {
1638 if ($this->isFinalized
) {
1641 $this->buildTOC
= TRUE;
1642 $this->tocTitle
= $title;
1643 $this->tocFileName
= $this->normalizeFileName($tocFileName);
1644 if (!empty($cssFileName)) {
1645 $this->tocCSSFileName
= $this->normalizeFileName($cssFileName);
1647 $this->tocCSSClass
= $tocCSSClass;
1648 $this->tocAddReferences
= $addReferences;
1650 $this->opf
->addItemRef("ref_" . Reference
::TABLE_OF_CONTENTS
, FALSE);
1651 $this->opf
->addReference(Reference
::TABLE_OF_CONTENTS
, $title, $this->tocFileName
);
1654 $navPoint = new NavPoint($this->decodeHtmlEntities($title), $this->tocFileName
, "ref_" . Reference
::TABLE_OF_CONTENTS
);
1655 $this->ncx
->addNavPoint($navPoint);
1657 $this->ncx
->referencesList
[Reference
::TABLE_OF_CONTENTS
] = $this->tocFileName
;
1658 $this->ncx
->referencesName
[Reference
::TABLE_OF_CONTENTS
] = $title;
1662 private function finalizeTOC() {
1663 if (!$this->buildTOC
) {
1667 if (empty($this->tocTitle
)) {
1668 $this->tocTitle
= "Table of Contents";
1671 $tocData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1673 if ($this->isEPubVersion2()) {
1674 $tocData .= "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n"
1675 . " \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
1676 . "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
1677 . "<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
1679 $tocData .= "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n"
1680 . "<head>\n<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n";
1683 if (!empty($this->tocCssFileName
)) {
1684 $tocData .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" . $this->tocCssFileName
. "\" />\n";
1687 $tocData .= "<title>" . $this->tocTitle
. "</title>\n"
1690 . "<h3>" . $this->tocTitle
. "</h3>\n<div";
1692 if (!empty($this->tocCSSClass
)) {
1693 $tocData .= " class=\"" . $this->tocCSSClass
. "\"";
1697 while (list($item, $descriptive) = each($this->referencesOrder
)) {
1698 if ($item === "text") {
1699 while (list($chapterName, $navPoint) = each($this->ncx
->chapterList
)) {
1700 $fileName = $navPoint->getContentSrc();
1701 $level = $navPoint->getLevel() -2;
1702 $tocData .= "\t<p>" . str_repeat("      ", $level) . "<a href=\"" . $fileName . "\">" . $chapterName . "</a></p>\n";
1704 } else if ($this->tocAddReferences
=== TRUE) {
1705 if (array_key_exists($item, $this->ncx
->referencesList
)) {
1706 $tocData .= "\t<p><a href=\"" . $this->ncx
->referencesList
[$item] . "\">" . $descriptive . "</a></p>\n";
1707 } else if ($item === "toc") {
1708 $tocData .= "\t<p><a href=\"TOC.xhtml\">" . $this->tocTitle
. "</a></p>\n";
1709 } else if ($item === "cover" && $this->isCoverImageSet
) {
1710 $tocData .= "\t<p><a href=\"CoverPage.xhtml\">" . $descriptive . "</a></p>\n";
1714 $tocData .= "</div>\n</body>\n</html>\n";
1716 $this->addReferencePage($this->tocTitle
, $this->tocFileName
, $tocData, Reference
::TABLE_OF_CONTENTS
);
1723 function isEPubVersion2() {
1724 return $this->bookVersion
=== EPub
::BOOK_VERSION_EPUB2
;
1728 * @param string $cssFileName
1729 * @param string $title
1732 function buildEPub3TOC($cssFileName = NULL, $title = "Table of Contents") {
1733 $this->ncx
->referencesOrder
= $this->referencesOrder
;
1734 $this->ncx
->setDocTitle($this->decodeHtmlEntities($this->title
));
1735 return $this->ncx
->finalizeEPub3($title, $cssFileName);
1739 * @param string $fileName
1740 * @param string $tocData
1743 function addEPub3TOC($fileName, $tocData) {
1744 if ($this->isEPubVersion2() || $this->isFinalized
|| array_key_exists($fileName, $this->fileList
)) {
1747 $fileName = Zip
::getRelativePath($fileName);
1748 $fileName = preg_replace('#^[/\.]+#i', "", $fileName);
1750 $this->zip
->addFile($tocData, $this->bookRoot
.$fileName);
1752 $this->fileList
[$fileName] = $fileName;
1753 $this->opf
->addItem("toc", $fileName, "application/xhtml+xml", "nav");
1758 * Check for mandatory parameters and finalize the e-book.
1759 * Once finalized, the book is locked for further additions.
1761 * @return bool $success
1763 function finalize() {
1764 if ($this->isFinalized
|| $this->chapterCount
== 0 || empty($this->title
) || empty($this->language
)) {
1768 if (empty($this->identifier
) || empty($this->identifierType
)) {
1769 $this->setIdentifier($this->createUUID(4), EPub
::IDENTIFIER_UUID
);
1772 if ($this->date
== 0) {
1773 $this->date
= time();
1776 if (empty($this->sourceURL
)) {
1777 $this->sourceURL
= $this->getCurrentPageURL();
1780 if (empty($this->publisherURL
)) {
1781 $this->sourceURL
= $this->getCurrentServerURL();
1784 // Generate OPF data:
1785 $this->opf
->setIdent("BookId");
1786 $this->opf
->initialize($this->title
, $this->language
, $this->identifier
, $this->identifierType
);
1788 $DCdate = new DublinCore(DublinCore
::DATE
, gmdate($this->dateformat
, $this->date
));
1789 $DCdate->addOpfAttr("event", "publication");
1790 $this->opf
->metadata
->addDublinCore($DCdate);
1792 if (!empty($this->description
)) {
1793 $this->opf
->addDCMeta(DublinCore
::DESCRIPTION
, $this->decodeHtmlEntities($this->description
));
1796 if (!empty($this->publisherName
)) {
1797 $this->opf
->addDCMeta(DublinCore
::PUBLISHER
, $this->decodeHtmlEntities($this->publisherName
));
1800 if (!empty($this->publisherURL
)) {
1801 $this->opf
->addDCMeta(DublinCore
::RELATION
, $this->decodeHtmlEntities($this->publisherURL
));
1804 if (!empty($this->author
)) {
1805 $author = $this->decodeHtmlEntities($this->author
);
1806 $this->opf
->addCreator($author, $this->decodeHtmlEntities($this->authorSortKey
), MarcCode
::AUTHOR
);
1807 $this->ncx
->setDocAuthor($author);
1810 if (!empty($this->rights
)) {
1811 $this->opf
->addDCMeta(DublinCore
::RIGHTS
, $this->decodeHtmlEntities($this->rights
));
1814 if (!empty($this->coverage
)) {
1815 $this->opf
->addDCMeta(DublinCore
::COVERAGE
, $this->decodeHtmlEntities($this->coverage
));
1818 if (!empty($this->sourceURL
)) {
1819 $this->opf
->addDCMeta(DublinCore
::SOURCE
, $this->sourceURL
);
1822 if (!empty($this->relation
)) {
1823 $this->opf
->addDCMeta(DublinCore
::RELATION
, $this->decodeHtmlEntities($this->relation
));
1826 if ($this->isCoverImageSet
) {
1827 $this->opf
->addMeta("cover", "coverImage");
1830 if (!empty($this->generator
)) {
1831 $gen = $this->decodeHtmlEntities($this->generator
);
1832 $this->opf
->addMeta("generator", $gen);
1833 $this->ncx
->addMetaEntry("dtb:generator", $gen);
1836 if ($this->EPubMark
) {
1837 $this->opf
->addMeta("generator", "EPub (Version " . self
::VERSION
. ") by A. Grandt, http://www.phpclasses.org/package/6115");
1840 reset($this->ncx
->chapterList
);
1841 list($firstChapterName, $firstChapterNavPoint) = each($this->ncx
->chapterList
);
1842 $firstChapterFileName = $firstChapterNavPoint->getContentSrc();
1843 $this->opf
->addReference(Reference
::TEXT
, $this->decodeHtmlEntities($firstChapterName), $firstChapterFileName);
1845 $this->ncx
->setUid($this->identifier
);
1847 $this->ncx
->setDocTitle($this->decodeHtmlEntities($this->title
));
1849 $this->ncx
->referencesOrder
= $this->referencesOrder
;
1850 if ($this->isReferencesAddedToToc
) {
1851 $this->ncx
->finalizeReferences();
1854 $this->finalizeTOC();
1856 if (!$this->isEPubVersion2()) {
1857 $this->addEPub3TOC("epub3toc.xhtml", $this->buildEPub3TOC());
1860 $opfFinal = $this->fixEncoding($this->opf
->finalize());
1861 $ncxFinal = $this->fixEncoding($this->ncx
->finalize());
1863 if (mb_detect_encoding($opfFinal, 'UTF-8', true) === "UTF-8") {
1864 $this->zip
->addFile($opfFinal, $this->bookRoot
."book.opf");
1866 $this->zip
->addFile(mb_convert_encoding($opfFinal, "UTF-8"), $this->bookRoot
."book.opf");
1869 if (mb_detect_encoding($ncxFinal, 'UTF-8', true) === "UTF-8") {
1870 $this->zip
->addFile($ncxFinal, $this->bookRoot
."book.ncx");
1872 $this->zip
->addFile(mb_convert_encoding($ncxFinal, "UTF-8"), $this->bookRoot
."book.ncx");
1878 $this->isFinalized
= TRUE;
1883 * Ensure the encoded string is a valid UTF-8 string.
1885 * Note, that a mb_detect_encoding on the returned string will still return ASCII if the entire string is comprized of characters in the 1-127 range.
1887 * @link: http://snippetdb.com/php/convert-string-to-utf-8-for-mysql
1888 * @param string $in_str
1889 * @return string converted string.
1891 function fixEncoding($in_str) {
1892 if (mb_detect_encoding($in_str) == "UTF-8" && mb_check_encoding($in_str,"UTF-8")) {
1895 return utf8_encode($in_str);
1900 * Return the finalized book.
1902 * @return string with the book in binary form.
1904 function getBook() {
1905 if (!$this->isFinalized
) {
1909 return $this->zip
->getZipData();
1913 * Remove disallowed characters from string to get a nearly safe filename
1915 * @param string $fileName
1916 * @return mixed|string
1918 function sanitizeFileName($fileName) {
1919 $fileName1 = str_replace($this->forbiddenCharacters
, '', $fileName);
1920 $fileName2 = preg_replace('/[\s-]+/', '-', $fileName1);
1921 return trim($fileName2, '.-_');
1926 * Cleanup the filepath, and remove leading . and / characters.
1928 * Sometimes, when a path is generated from multiple fragments,
1929 * you can get something like "../data/html/../images/image.jpeg"
1930 * ePub files don't work well with that, this will normalize that
1931 * example path to "data/images/image.jpeg"
1933 * @param string $fileName
1934 * @return string normalized filename
1936 function normalizeFileName($fileName) {
1937 return preg_replace('#^[/\.]+#i', "", Zip
::getRelativePath($fileName));
1941 * Save the ePub file to local disk.
1943 * @param string $fileName
1944 * @param string $baseDir If empty baseDir is absolute to server path, if omitted it's relative to script path
1945 * @return The sent file name if successfull, FALSE if it failed.
1947 function saveBook($fileName, $baseDir = '.') {
1949 // Make fileName safe
1950 $fileName = $this->sanitizeFileName($fileName);
1952 // Finalize book, if it's not done already
1953 if (!$this->isFinalized
) {
1957 if (stripos(strrev($fileName), "bupe.") !== 0) {
1958 $fileName .= ".epub";
1961 // Try to open file access
1962 $fh = fopen($baseDir.'/'.$fileName, "w");
1965 fputs($fh, $this->getBook());
1968 // if file is written return TRUE
1972 // return FALSE by default
1977 * Return the finalized book size.
1981 function getBookSize() {
1982 if (!$this->isFinalized
) {
1986 return $this->zip
->getArchiveSize();
1990 * Send the book as a zip download
1992 * Sending will fail if the output buffer is in use. You can override this limit by
1993 * calling setIgnoreEmptyBuffer(TRUE), though the function will still fail if that
1994 * buffer is not empty.
1996 * @param string $fileName The name of the book without the .epub at the end.
1997 * @return The sent file name if successfull, FALSE if it failed.
1999 function sendBook($fileName) {
2000 if (!$this->isFinalized
) {
2004 if (stripos(strrev($fileName), "bupe.") !== 0) {
2005 $fileName .= ".epub";
2008 if (TRUE === $this->zip
->sendZip($fileName, "application/epub+zip")) {
2015 * Generates an UUID.
2017 * Default version (4) will generate a random UUID, version 3 will URL based UUID.
2019 * Added for convinience
2021 * @param int $bookVersion UUID version to retrieve, See lib.uuid.manual.html for details.
2022 * @param string $url
2023 * @return string The formatted uuid
2025 function createUUID($bookVersion = 4, $url = NULL) {
2026 include_once("lib.uuid.php");
2027 return UUID
::mint($bookVersion, $url, UUID
::nsURL
);
2031 * Get the url of the current page.
2032 * Example use: Default Source URL
2034 * $return string Page URL.
2036 function getCurrentPageURL() {
2037 $pageURL = $this->getCurrentServerURL() . filter_input(INPUT_SERVER
, "REQUEST_URI");
2042 * Get the url of the server.
2043 * Example use: Default Publisher URL
2045 * $return string Server URL.
2047 function getCurrentServerURL() {
2048 $serverURL = 'http';
2049 $https = filter_input(INPUT_SERVER
, "HTTPS");
2050 $port = filter_input(INPUT_SERVER
, "SERVER_PORT");
2052 if ($https === "on") {
2055 $serverURL .= "://" . filter_input(INPUT_SERVER
, "SERVER_NAME");
2056 if ($port != "80") {
2057 $serverURL .= ":" . $port;
2059 return $serverURL . '/';
2063 * Try to determine the mimetype of the file path.
2065 * @param string $source Path
2066 * @return string mimetype, or FALSE.
2068 function getMime($source) {
2069 return $this->mimetypes
[pathinfo($source, PATHINFO_EXTENSION
)];
2073 * Get an image from a file or url, return it resized if the image exceeds the $maxImageWidth or $maxImageHeight directives.
2075 * The return value is an array.
2076 * ['width'] is the width of the image.
2077 * ['height'] is the height of the image.
2078 * ['mime'] is the mime type of the image. Resized images are always in jpeg format.
2079 * ['image'] is the image data.
2080 * ['ext'] is the extension of the image file.
2082 * @param string $source path or url to file.
2085 function getImage($source) {
2088 $mime = "application/octet-stream";
2093 $image = $this->getFileContents($source);
2095 if ($image !== FALSE && strlen($image) > 0) {
2096 $imageFile = imagecreatefromstring($image);
2097 if ($imageFile !== false) {
2098 $width = ImageSX($imageFile);
2099 $height = ImageSY($imageFile);
2101 if ($this->isExifInstalled
) {
2102 @$type = exif_imagetype($source);
2103 $mime = image_type_to_mime_type($type);
2105 if ($mime === "application/octet-stream") {
2106 $mime = $this->image_file_type_from_binary($image);
2108 if ($mime === "application/octet-stream") {
2109 $mime = $this->getMimeTypeFromUrl($source);
2115 if ($width <= 0 || $height <= 0) {
2121 if ($this->isGdInstalled
) {
2122 if ($width > $this->maxImageWidth
) {
2123 $ratio = $this->maxImageWidth
/$width;
2125 if ($height*$ratio > $this->maxImageHeight
) {
2126 $ratio = $this->maxImageHeight
/$height;
2129 if ($ratio < 1 || empty($mime) || ($this->isGifImagesEnabled
!== FALSE && $mime == "image/gif")) {
2130 $image_o = imagecreatefromstring($image);
2131 $image_p = imagecreatetruecolor($width*$ratio, $height*$ratio);
2133 if ($mime == "image/png") {
2134 imagealphablending($image_p, false);
2135 imagesavealpha($image_p, true);
2136 imagealphablending($image_o, true);
2138 imagecopyresampled($image_p, $image_o, 0, 0, 0, 0, ($width*$ratio), ($height*$ratio), $width, $height);
2140 imagepng($image_p, NULL, 9);
2141 $image = ob_get_contents();
2146 imagecopyresampled($image_p, $image_o, 0, 0, 0, 0, ($width*$ratio), ($height*$ratio), $width, $height);
2148 imagejpeg($image_p, NULL, 80);
2149 $image = ob_get_contents();
2152 $mime = "image/jpeg";
2155 imagedestroy($image_o);
2156 imagedestroy($image_p);
2161 static $mimeToExt = array (
2162 'image/jpeg' => 'jpg',
2163 'image/gif' => 'gif',
2164 'image/png' => 'png'
2167 if (isset($mimeToExt[$mime])) {
2168 $ext = $mimeToExt[$mime];
2173 $rv['width'] = $width*$ratio;
2174 $rv['height'] = $height*$ratio;
2175 $rv['mime'] = $mime;
2176 $rv['image'] = $image;
2183 * Get file contents, using curl if available, else file_get_contents
2185 * @param string $source
2188 function getFileContents($source, $toTempFile = FALSE) {
2189 $isExternal = preg_match('#^(http|ftp)s?://#i', $source) == 1;
2191 if ($isExternal && $this->isCurlInstalled
) {
2196 $info = array('http_code' => 500);
2198 curl_setopt($ch, CURLOPT_HEADER
, 0);
2199 curl_setopt($ch, CURLOPT_URL
, str_replace(" ","%20",$source));
2200 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, true);
2201 curl_setopt($ch, CURLOPT_FOLLOWLOCATION
, true);
2202 curl_setopt($ch, CURLOPT_BUFFERSIZE
, 4096);
2205 $outFile = tempnam(sys_get_temp_dir(), "EPub_v" . EPub
::VERSION
. "_");
2206 $fp = fopen($outFile, "w+b");
2207 curl_setopt($ch, CURLOPT_FILE
, $fp);
2209 $res = curl_exec($ch);
2210 $info = curl_getinfo($ch);
2215 $res = curl_exec($ch);
2216 $info = curl_getinfo($ch);
2221 if ($info['http_code'] == 200 && $res != false) {
2230 if ($this->isFileGetContentsInstalled
&& (!$isExternal || $this->isFileGetContentsExtInstalled
)) {
2231 @$data = file_get_contents($source);
2238 * get mime type from image data
2240 * By fireweasel found on http://stackoverflow.com/questions/2207095/get-image-mimetype-from-resource-in-php-gd
2241 * @staticvar array $type
2242 * @param object $binary
2245 function image_file_type_from_binary($binary) {
2248 '/\A(?:(\xff\xd8\xff)|(GIF8[79]a)|(\x89PNG\x0d\x0a)|(BM)|(\x49\x49(?:\x2a\x00|\x00\x4a))|(FORM.{4}ILBM))/',
2250 return 'application/octet-stream';
2252 static $type = array (
2256 4 => 'image/x-windows-bmp',
2258 6 => 'image/x-ilbm',
2260 return $type[count($hits) - 1];
2264 * @param string $source URL Source
2265 * @return string MimeType
2267 function getMimeTypeFromUrl($source) {
2270 $srev = strrev($source);
2271 $pos = strpos($srev, "?");
2272 if ($pos !== FALSE) {
2273 $srev = substr($srev, $pos+
1);
2276 $pos = strpos($srev, ".");
2277 if ($pos !== FALSE) {
2278 $ext = strtolower(strrev(substr($srev, 0, $pos)));
2281 if ($ext !== FALSE) {
2282 return $this->getMimeTypeFromExtension($ext);
2284 return "application/octet-stream";
2288 * @param string $ext Extension
2289 * @return string MimeType
2291 function getMimeTypeFromExtension($ext) {
2296 return 'image/jpeg';
2302 return 'image/x-windows-bmp';
2306 return 'image/tiff';
2309 return 'image/x-ilbm';
2311 return "application/octet-stream";
2316 * Encode html code to use html entities, safeguarding it from potential character encoding peoblems
2317 * This function is a bit different from the vanilla htmlentities function in that it does not encode html tags.
2319 * The regexp is taken from the PHP Manual discussion, it was written by user "busbyjon".
2320 * http://www.php.net/manual/en/function.htmlentities.php#90111
2322 * @param string $string string to encode.
2324 public function encodeHtml($string) {
2325 $string = strtr($string, $this->html_encoding_characters
);
2327 //return preg_replace("/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,5};)/", "&\\1", $string);
2328 //return preg_replace("/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,5};)/", "&", $string);
2333 * Helper function to create a DOM fragment with given markup.
2335 * @author Adam Schmalhofer
2337 * @param DOMDocument $dom
2338 * @param string $markup
2339 * @return DOMNode fragment in a node.
2341 protected function createDomFragment($dom, $markup) {
2342 $node = $dom->createDocumentFragment();
2343 $node->appendXML($markup);
2348 * Retrieve an array of file names currently added to the book.
2349 * $key is the filename used in the book
2350 * $value is the original filename, will be the same as $key for most entries
2352 * @return array file list
2354 function getFileList() {
2355 return $this->fileList
;
2359 * @deprecated Use Zip::getRelativePath($relPath) instead.
2361 function relPath($relPath) {
2362 die ("Function was deprecated, use Zip::getRelativePath(\$relPath); instead");
2366 * Set default chapter target size.
2367 * Default is 250000 bytes, and minimum is 10240 bytes.
2369 * @param int $size segment size in bytes
2372 function setSplitSize($size) {
2373 $this->splitDefaultSize
= (int)$size;
2374 if ($size < 10240) {
2375 $this->splitDefaultSize
= 10240; // Making the file smaller than 10k is not a good idea.
2380 * Get the chapter target size.
2384 function getSplitSize() {
2385 return $this->splitDefaultSize
;
2389 * Remove all non essential html tags and entities.
2391 * @global type $htmlEntities
2392 * @param string $string
2393 * @return string with the stripped entities.
2395 function decodeHtmlEntities($string) {
2396 global $htmlEntities;
2398 $string = preg_replace('~\s*<br\s*/*\s*>\s*~i', "\n", $string);
2399 $string = preg_replace('~\s*</(p|div)\s*>\s*~i', "\n\n", $string);
2400 $string = preg_replace('~<[^>]*>~', '', $string);
2402 $string = strtr($string, $htmlEntities);
2404 $string = str_replace('&', '&', $string);
2405 $string = str_replace('&amp;', '&', $string);
2406 $string = preg_replace('~&(#x*[a-fA-F0-9]+;)~', '&\1', $string);
2407 $string = str_replace('<', '<', $string);
2408 $string = str_replace('>', '>', $string);
2414 * Simply remove all HTML tags, brute force and no finesse.
2416 * @param string $string html
2419 function html2text($string) {
2420 return preg_replace('~<[^>]*>~', '', $string);
2427 return $this->log
->getLog();