]>
Commit | Line | Data |
---|---|---|
87090d8a | 1 | <?php |
2 | /** | |
3 | * Create an ePub compatible book file. | |
4 | * | |
5 | * Please note, once finalized a book can no longer have chapters of data added or changed. | |
6 | * | |
7 | * License: GNU LGPL, Attribution required for commercial implementations, requested for everything else. | |
8 | * | |
9 | * Thanks to: Adam Schmalhofer and Kirstyn Fox for invaluable input and for "nudging" me in the right direction :) | |
10 | * | |
11 | * @author A. Grandt <php@grandt.com> | |
12 | * @copyright 2009-2014 A. Grandt | |
13 | * @license GNU LGPL 2.1 | |
14 | * @version 3.20 | |
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 | |
18 | */ | |
19 | class EPub { | |
20 | const VERSION = 3.20; | |
21 | const REQ_ZIP_VERSION = 1.60; | |
22 | ||
23 | const IDENTIFIER_UUID = 'UUID'; | |
24 | const IDENTIFIER_URI = 'URI'; | |
25 | const IDENTIFIER_ISBN = 'ISBN'; | |
26 | ||
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; | |
35 | ||
36 | const DIRECTION_LEFT_TO_RIGHT = "ltr"; | |
37 | const DIRECTION_RIGHT_TO_LEFT = "rtl"; | |
38 | ||
39 | const BOOK_VERSION_EPUB2 = "2.0"; | |
40 | const BOOK_VERSION_EPUB3 = "3.0"; | |
41 | ||
42 | private $bookVersion = EPub::BOOK_VERSION_EPUB2; | |
43 | ||
44 | public $maxImageWidth = 768; | |
45 | public $maxImageHeight = 1024; | |
46 | ||
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. | |
50 | */ | |
51 | public $isGifImagesEnabled = FALSE; | |
52 | public $isReferencesAddedToToc = TRUE; | |
53 | ||
54 | private $zip; | |
55 | ||
56 | private $title = ""; | |
57 | private $language = "en"; | |
58 | private $identifier = ""; | |
59 | private $identifierType = ""; | |
60 | private $description = ""; | |
61 | private $author = ""; | |
62 | private $authorSortKey = ""; | |
63 | private $publisherName = ""; | |
64 | private $publisherURL = ""; | |
65 | private $date = 0; | |
66 | private $rights = ""; | |
67 | private $coverage = ""; | |
68 | private $relation = ""; | |
69 | private $sourceURL = ""; | |
70 | ||
71 | private $chapterCount = 0; | |
72 | private $opf = NULL; | |
73 | private $ncx = NULL; | |
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; | |
82 | ||
83 | private $fileList = array(); | |
84 | private $writingDirection = EPub::DIRECTION_LEFT_TO_RIGHT; | |
85 | private $languageCode = "en"; | |
86 | ||
87 | /** | |
88 | * Used for building the TOC. | |
89 | * If this list is overwritten it MUST contain at least "text" as an element. | |
90 | */ | |
91 | public $referencesOrder = NULL; | |
92 | ||
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"; | |
96 | ||
97 | protected $isCurlInstalled; | |
98 | protected $isGdInstalled; | |
99 | protected $isExifInstalled; | |
100 | protected $isFileGetContentsInstalled; | |
101 | protected $isFileGetContentsExtInstalled; | |
102 | ||
103 | private $bookRoot = "OEBPS/"; | |
104 | private $docRoot = NULL; | |
105 | private $EPubMark = TRUE; | |
106 | private $generator = ""; | |
107 | ||
108 | private $log = NULL; | |
109 | public $isLogging = TRUE; | |
110 | ||
111 | public $encodeHTML = FALSE; | |
112 | ||
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"); | |
119 | ||
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"); | |
122 | ||
123 | private $opsContentTypes = array("application/xhtml+xml", "application/x-dtbook+xml", "application/xml", "application/x-dtbncx+xml", "text/x-oeb1-document"); | |
124 | ||
125 | private $forbiddenCharacters = array("?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}", "%"); | |
126 | ||
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"; | |
129 | ||
130 | /** | |
131 | * Class constructor. | |
132 | * | |
133 | * @return void | |
134 | */ | |
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"); | |
138 | ||
139 | $this->bookVersion = $bookVersion; | |
140 | $this->writingDirection = $writingDirection; | |
141 | $this->languageCode = $languageCode; | |
142 | ||
143 | $this->log = new Logger("EPub", $this->isLogging); | |
144 | ||
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(); | |
151 | } | |
152 | ||
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>"); | |
155 | } | |
156 | ||
157 | include_once("EPubChapterSplitter.php"); | |
158 | include_once("EPub.HtmlEntities.php"); | |
159 | include_once("EPub.NCX.php"); | |
160 | include_once("EPub.OPF.php"); | |
161 | ||
162 | $this->initialize(); | |
163 | } | |
164 | ||
165 | /** | |
166 | * Class destructor | |
167 | * | |
168 | * @return void | |
169 | * @TODO make sure elements in the destructor match the current class elements | |
170 | */ | |
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); | |
187 | } | |
188 | ||
189 | /** | |
190 | * initialize defaults. | |
191 | */ | |
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"); | |
211 | ||
212 | $this->docRoot = filter_input(INPUT_SERVER, "DOCUMENT_ROOT") . "/"; | |
213 | ||
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'); | |
219 | ||
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"); | |
225 | ||
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"; | |
227 | ||
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" | |
231 | . "<head>" | |
232 | . "<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n" | |
233 | . "<title></title>\n" | |
234 | . "</head>\n" | |
235 | . "<body>\n"; | |
236 | } | |
237 | ||
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; | |
246 | } | |
247 | ||
248 | /** | |
249 | * Add dynamically generated data as a file to the book. | |
250 | * | |
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 | |
256 | */ | |
257 | function addFile($fileName, $fileId, $fileData, $mimetype) { | |
258 | if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) { | |
259 | return FALSE; | |
260 | } | |
261 | ||
262 | $fileName = $this->normalizeFileName($fileName); | |
263 | ||
264 | $compress = (strpos($mimetype, "image/") !== 0); | |
265 | ||
266 | $this->zip->addFile($fileData, $this->bookRoot.$fileName, 0, NULL, $compress); | |
267 | $this->fileList[$fileName] = $fileName; | |
268 | $this->opf->addItem($fileId, $fileName, $mimetype); | |
269 | return TRUE; | |
270 | } | |
271 | ||
272 | /** | |
273 | * Add a large file directly from the filestystem to the book. | |
274 | * | |
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 | |
280 | */ | |
281 | function addLargeFile($fileName, $fileId, $filePath, $mimetype) { | |
282 | if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) { | |
283 | return FALSE; | |
284 | } | |
285 | $fileName = $this->normalizeFileName($fileName); | |
286 | ||
287 | if ($this->zip->addLargeFile($filePath, $this->bookRoot.$fileName)) { | |
288 | $this->fileList[$fileName] = $fileName; | |
289 | $this->opf->addItem($fileId, $fileName, $mimetype); | |
290 | return TRUE; | |
291 | } | |
292 | return FALSE; | |
293 | } | |
294 | ||
295 | /** | |
296 | * Add a CSS file to the book. | |
297 | * | |
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. | |
303 | * | |
304 | * @return bool $success | |
305 | */ | |
306 | function addCSSFile($fileName, $fileId, $fileData, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") { | |
307 | if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) { | |
308 | return FALSE; | |
309 | } | |
310 | $fileName = Zip::getRelativePath($fileName); | |
311 | $fileName = preg_replace('#^[/\.]+#i', "", $fileName); | |
312 | ||
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); | |
318 | } | |
319 | ||
320 | $this->processCSSExternalReferences($fileData, $externalReferences, $baseDir, $cssDir); | |
321 | } | |
322 | ||
323 | $this->addFile($fileName, "css_" . $fileId, $fileData, "text/css"); | |
324 | ||
325 | return TRUE; | |
326 | } | |
327 | ||
328 | /** | |
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. | |
331 | * | |
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. | |
339 | */ | |
340 | function addChapter($chapterName, $fileName, $chapterData = NULL, $autoSplit = FALSE, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") { | |
341 | if ($this->isFinalized) { | |
342 | return FALSE; | |
343 | } | |
344 | $fileName = Zip::getRelativePath($fileName); | |
345 | $fileName = preg_replace('#^[/\.]+#i', "", $fileName); | |
72a85715 | 346 | $fileName = $this->sanitizeFileName($fileName); |
87090d8a | 347 | |
348 | $chapter = $chapterData; | |
349 | if ($autoSplit && is_string($chapterData) && mb_strlen($chapterData) > $this->splitDefaultSize) { | |
350 | $splitter = new EPubChapterSplitter(); | |
351 | ||
352 | $chapterArray = $splitter->splitChapter($chapterData); | |
353 | if (count($chapterArray) > 1) { | |
354 | $chapter = $chapterArray; | |
355 | } | |
356 | } | |
357 | ||
358 | if (!empty($chapter) && is_string($chapter)) { | |
359 | if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) { | |
360 | $htmlDirInfo = pathinfo($fileName); | |
361 | $htmlDir = preg_replace('#^[/\.]+#i', "", $htmlDirInfo["dirname"] . "/"); | |
362 | $this->processChapterExternalReferences($chapter, $externalReferences, $baseDir, $htmlDir); | |
363 | } | |
364 | ||
365 | if ($this->encodeHTML === TRUE) { | |
366 | $chapter = $this->encodeHtml($chapter); | |
367 | } | |
368 | ||
369 | $this->chapterCount++; | |
370 | $this->addFile($fileName, "chapter" . $this->chapterCount, $chapter, "application/xhtml+xml"); | |
371 | $this->opf->addItemRef("chapter" . $this->chapterCount); | |
372 | ||
373 | $navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $fileName, "chapter" . $this->chapterCount); | |
374 | $this->ncx->addNavPoint($navPoint); | |
375 | $this->ncx->chapterList[$chapterName] = $navPoint; | |
376 | } else if (is_array($chapter)) { | |
377 | $fileNameParts = pathinfo($fileName); | |
378 | $extension = $fileNameParts['extension']; | |
379 | $name = $fileNameParts['filename']; | |
380 | ||
381 | $partCount = 0; | |
382 | $this->chapterCount++; | |
383 | ||
384 | $oneChapter = each($chapter); | |
385 | while ($oneChapter) { | |
386 | list($k, $v) = $oneChapter; | |
387 | if ($this->encodeHTML === TRUE) { | |
388 | $v = $this->encodeHtml($v); | |
389 | } | |
390 | ||
391 | if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) { | |
392 | $this->processChapterExternalReferences($v, $externalReferences, $baseDir); | |
393 | } | |
394 | $partCount++; | |
395 | $partName = $name . "_" . $partCount; | |
396 | $this->addFile($partName . "." . $extension, $partName, $v, "application/xhtml+xml"); | |
397 | $this->opf->addItemRef($partName); | |
398 | ||
399 | $oneChapter = each($chapter); | |
400 | } | |
401 | $partName = $name . "_1." . $extension; | |
402 | $navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $partName, $partName); | |
403 | $this->ncx->addNavPoint($navPoint); | |
404 | ||
405 | $this->ncx->chapterList[$chapterName] = $navPoint; | |
406 | } else if (!isset($chapterData) && strpos($fileName, "#") > 0) { | |
407 | $this->chapterCount++; | |
408 | //$this->opf->addItemRef("chapter" . $this->chapterCount); | |
409 | ||
410 | $navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $fileName, "chapter" . $this->chapterCount); | |
411 | $this->ncx->addNavPoint($navPoint); | |
412 | $this->ncx->chapterList[$chapterName] = $navPoint; | |
413 | } else if (!isset($chapterData) && $fileName=="TOC.xhtml") { | |
414 | $this->chapterCount++; | |
415 | $this->opf->addItemRef("toc"); | |
416 | ||
417 | $navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $fileName, "chapter" . $this->chapterCount); | |
418 | $this->ncx->addNavPoint($navPoint); | |
419 | $this->ncx->chapterList[$chapterName] = $navPoint; | |
420 | } | |
421 | return $navPoint; | |
422 | } | |
423 | ||
424 | /** | |
425 | * Add one chapter level. | |
426 | * | |
427 | * Subsequent chapters will be added to this level. | |
428 | * | |
429 | * @param string $navTitle | |
430 | * @param string $navId | |
431 | * @param string $navClass | |
432 | * @param int $isNavHidden | |
433 | * @param string $writingDirection | |
434 | * @return NavPoint The new NavPoint for that level. | |
435 | */ | |
436 | function subLevel($navTitle = NULL, $navId = NULL, $navClass = NULL, $isNavHidden = FALSE, $writingDirection = NULL) { | |
437 | return $this->ncx->subLevel($this->decodeHtmlEntities($navTitle), $navId, $navClass, $isNavHidden, $writingDirection); | |
438 | } | |
439 | ||
440 | /** | |
441 | * Step back one chapter level. | |
442 | * | |
443 | * Subsequent chapters will be added to this chapters parent level. | |
444 | */ | |
445 | function backLevel() { | |
446 | $this->ncx->backLevel(); | |
447 | } | |
448 | ||
449 | /** | |
450 | * Step back to the root level. | |
451 | * | |
452 | * Subsequent chapters will be added to the rooot NavMap. | |
453 | */ | |
454 | function rootLevel() { | |
455 | $this->ncx->rootLevel(); | |
456 | } | |
457 | ||
458 | /** | |
459 | * Step back to the given level. | |
460 | * Useful for returning to a previous level from deep within the structure. | |
461 | * Values below 2 will have the same effect as rootLevel() | |
462 | * | |
463 | * @param int $newLevel | |
464 | */ | |
465 | function setCurrentLevel($newLevel) { | |
466 | $this->ncx->setCurrentLevel($newLevel); | |
467 | } | |
468 | ||
469 | /** | |
470 | * Get current level count. | |
471 | * The indentation of the current structure point. | |
472 | * | |
473 | * @return current level count; | |
474 | */ | |
475 | function getCurrentLevel() { | |
476 | return $this->ncx->getCurrentLevel(); | |
477 | } | |
478 | ||
479 | /** | |
480 | * Wrap ChapterContent with Head and Footer | |
481 | * | |
482 | * @param $content | |
483 | * @return string $content | |
484 | */ | |
485 | private function wrapChapter($content) { | |
486 | return $this->htmlContentHeader . "\n" . $content . "\n" . $this->htmlContentFooter; | |
487 | } | |
488 | ||
489 | /** | |
490 | * Reference pages is usually one or two pages for items such as Table of Contents, reference lists, Author notes or Acknowledgements. | |
491 | * These do not show up in the regular navigation list. | |
492 | * | |
493 | * As they are supposed to be short. | |
494 | * | |
495 | * @param string $pageName Name of the chapter, will be use din the TOC | |
496 | * @param string $fileName Filename to use for the chapter, must be unique for the book. | |
497 | * @param string $pageData Page content in XHTML. File should NOT exceed 250kB. | |
498 | * @param string $reference Reference key | |
499 | * @param int $externalReferences How to handle external references. See documentation for <code>processChapterExternalReferences</code> for explanation. Default is EPub::EXTERNAL_REF_IGNORE. | |
500 | * @param string $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE. | |
501 | * @return bool $success | |
502 | */ | |
503 | function addReferencePage($pageName, $fileName, $pageData, $reference, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") { | |
504 | if ($this->isFinalized) { | |
505 | return FALSE; | |
506 | } | |
507 | $fileName = Zip::getRelativePath($fileName); | |
508 | $fileName = preg_replace('#^[/\.]+#i', "", $fileName); | |
509 | ||
510 | ||
511 | if (!empty($pageData) && is_string($pageData)) { | |
512 | if ($this->encodeHTML === TRUE) { | |
513 | $pageData = $this->encodeHtml($pageData); | |
514 | } | |
515 | ||
516 | $this->wrapChapter($pageData); | |
517 | ||
518 | if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) { | |
519 | $htmlDirInfo = pathinfo($fileName); | |
520 | $htmlDir = preg_replace('#^[/\.]+#i', "", $htmlDirInfo["dirname"] . "/"); | |
521 | $this->processChapterExternalReferences($pageData, $externalReferences, $baseDir, $htmlDir); | |
522 | } | |
523 | ||
524 | $this->addFile($fileName, "ref_" . $reference, $pageData, "application/xhtml+xml"); | |
525 | ||
526 | if ($reference !== Reference::TABLE_OF_CONTENTS || !isset($this->ncx->referencesList[$reference])) { | |
527 | $this->opf->addItemRef("ref_" . $reference, FALSE); | |
528 | $this->opf->addReference($reference, $pageName, $fileName); | |
529 | ||
530 | $this->ncx->referencesList[$reference] = $fileName; | |
531 | $this->ncx->referencesName[$reference] = $pageName; | |
532 | } | |
533 | return TRUE; | |
534 | } | |
535 | return TRUE; | |
536 | } | |
537 | ||
538 | /** | |
539 | * Add custom metadata to the book. | |
540 | * | |
541 | * It is up to the builder to make sure there are no collisions. Metadata are just key value pairs. | |
542 | * | |
543 | * @param string $name | |
544 | * @param string $content | |
545 | */ | |
546 | function addCustomMetadata($name, $content) { | |
547 | $this->opf->addMeta($name, $content); | |
548 | } | |
549 | ||
550 | /** | |
551 | * Add DublinCore metadata to the book | |
552 | * | |
553 | * Use the DublinCore constants included in EPub, ie DublinCore::DATE | |
554 | * | |
555 | * @param string $dublinCore name | |
556 | * @param string $value | |
557 | */ | |
558 | function addDublinCoreMetadata($dublinCoreConstant, $value) { | |
559 | if ($this->isFinalized) { | |
560 | return; | |
561 | } | |
562 | ||
563 | $this->opf->addDCMeta($dublinCoreConstant, $this->decodeHtmlEntities($value)); | |
564 | } | |
565 | ||
566 | /** | |
567 | * Add a cover image to the book. | |
568 | * If the $imageData is not set, the function assumes the $fileName is the path to the image file. | |
569 | * | |
570 | * The styling and structure of the generated XHTML is heavily inspired by the XHTML generated by Calibre. | |
571 | * | |
572 | * @param string $fileName Filename to use for the image, must be unique for the book. | |
573 | * @param string $imageData Binary image data | |
574 | * @param string $mimetype Image mimetype, such as "image/jpeg" or "image/png". | |
575 | * @return bool $success | |
576 | */ | |
4877836b | 577 | function setCover($fileName, $imageData = NULL, $mimetype = NULL, $coverText=NULL) { |
87090d8a | 578 | if ($this->isFinalized || $this->isCoverImageSet || array_key_exists("CoverPage.html", $this->fileList)) { |
579 | return FALSE; | |
580 | } | |
581 | ||
582 | if ($imageData == NULL) { | |
583 | // assume $fileName is the valid file path. | |
584 | if (!file_exists($fileName)) { | |
585 | // Attempt to locate the file using the doc root. | |
586 | $rp = realpath($this->docRoot . "/" . $fileName); | |
587 | ||
588 | if ($rp !== FALSE) { | |
589 | // only assign the docroot path if it actually exists there. | |
590 | $fileName = $rp; | |
591 | } | |
592 | } | |
593 | $image = $this->getImage($fileName); | |
594 | $imageData = $image['image']; | |
595 | $mimetype = $image['mime']; | |
596 | $fileName = preg_replace("#\.[^\.]+$#", "." . $image['ext'], $fileName); | |
597 | } | |
598 | ||
599 | ||
600 | $path = pathinfo($fileName); | |
601 | $imgPath = "images/" . $path["basename"]; | |
602 | ||
603 | if (empty($mimetype) && file_exists($fileName)) { | |
604 | list($width, $height, $type, $attr) = getimagesize($fileName); | |
605 | $mimetype = image_type_to_mime_type($type); | |
606 | } | |
607 | if (empty($mimetype)) { | |
608 | $ext = strtolower($path['extension']); | |
609 | if ($ext == "jpg") { | |
610 | $ext = "jpeg"; | |
611 | } | |
612 | $mimetype = "image/" . $ext; | |
613 | } | |
614 | ||
615 | $coverPage = ""; | |
616 | ||
617 | if ($this->isEPubVersion2()) { | |
618 | $coverPage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" | |
619 | . "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n" | |
620 | . " \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n" | |
621 | . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\" xml:lang=\"en\">\n" | |
622 | . "\t<head>\n" | |
623 | . "\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n" | |
4877836b | 624 | . "\t\t<title>Cover</title>\n" |
87090d8a | 625 | . "\t\t<link type=\"text/css\" rel=\"stylesheet\" href=\"Styles/CoverPage.css\" />\n" |
626 | . "\t</head>\n" | |
627 | . "\t<body>\n" | |
4877836b | 628 | . "\t\t" . $coverText . "\n" |
87090d8a | 629 | . "\t\t<div>\n" |
4877836b | 630 | . "\t\t\t<img src=\"" . $imgPath . "\" alt=\"Cover image\" style=\"height: 40%\"/>\n" |
87090d8a | 631 | . "\t\t</div>\n" |
632 | . "\t</body>\n" | |
633 | . "</html>\n"; | |
634 | } else { | |
635 | $coverPage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" | |
636 | . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n" | |
637 | . "<head>" | |
638 | . "\t<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n" | |
4877836b | 639 | . "\t\t<title>Cover</title>\n" |
87090d8a | 640 | . "\t\t<link type=\"text/css\" rel=\"stylesheet\" href=\"Styles/CoverPage.css\" />\n" |
641 | . "\t</head>\n" | |
642 | . "\t<body>\n" | |
643 | . "\t\t<section epub:type=\"cover\">\n" | |
4877836b | 644 | . "\t\t" . $coverText . "\n" |
645 | . "\t\t\t<img src=\"" . $imgPath . "\" alt=\"Cover image\" style=\"height: 40%\"/>\n" | |
87090d8a | 646 | . "\t\t</section>\n" |
647 | . "\t</body>\n" | |
648 | . "</html>\n"; | |
649 | } | |
650 | $coverPageCss = "@page, body, div, img {\n" | |
651 | . "\tpadding: 0pt;\n" | |
652 | . "\tmargin:0pt;\n" | |
653 | . "}\n\nbody {\n" | |
654 | . "\ttext-align: center;\n" | |
655 | . "}\n"; | |
656 | ||
657 | $this->addCSSFile("Styles/CoverPage.css", "CoverPageCss", $coverPageCss); | |
658 | $this->addFile($imgPath, "CoverImage", $imageData, $mimetype); | |
659 | $this->addReferencePage("CoverPage", "CoverPage.xhtml", $coverPage, "cover"); | |
660 | $this->isCoverImageSet = TRUE; | |
661 | return TRUE; | |
662 | } | |
663 | ||
664 | /** | |
665 | * Process external references from a HTML to the book. The chapter itself is not stored. | |
666 | * the HTML is scanned for <link..., <style..., and <img tags. | |
667 | * Embedded CSS styles and links will also be processed. | |
668 | * Script tags are not processed, as scripting should be avoided in e-books. | |
669 | * | |
670 | * EPub keeps track of added files, and duplicate files referenced across multiple | |
671 | * chapters, are only added once. | |
672 | * | |
673 | * If the $doc is a string, it is assumed to be the content of an HTML file, | |
674 | * else is it assumes to be a DOMDocument. | |
675 | * | |
676 | * Basedir is the root dir the HTML is supposed to "live" in, used to resolve | |
677 | * relative references such as <code><img src="../images/image.png"/></code> | |
678 | * | |
679 | * $externalReferences determines how the function will handle external references. | |
680 | * | |
681 | * @param mixed &$doc (referenced) | |
682 | * @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. | |
683 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | |
684 | * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. | |
685 | * | |
686 | * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). | |
687 | */ | |
688 | protected function processChapterExternalReferences(&$doc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "") { | |
689 | if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { | |
690 | return FALSE; | |
691 | } | |
692 | ||
693 | $backPath = preg_replace('#[^/]+/#i', "../", $htmlDir); | |
694 | $isDocAString = is_string($doc); | |
695 | $xmlDoc = NULL; | |
696 | ||
697 | if ($isDocAString) { | |
698 | $xmlDoc = new DOMDocument(); | |
699 | @$xmlDoc->loadHTML($doc); | |
700 | } else { | |
701 | $xmlDoc = $doc; | |
702 | } | |
703 | ||
704 | $this->processChapterStyles($xmlDoc, $externalReferences, $baseDir, $htmlDir); | |
705 | $this->processChapterLinks($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath); | |
706 | $this->processChapterImages($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath); | |
707 | $this->processChapterSources($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath); | |
708 | ||
709 | if ($isDocAString) { | |
710 | //$html = $xmlDoc->saveXML(); | |
711 | ||
712 | $htmlNode = $xmlDoc->getElementsByTagName("html"); | |
713 | $headNode = $xmlDoc->getElementsByTagName("head"); | |
714 | $bodyNode = $xmlDoc->getElementsByTagName("body"); | |
715 | ||
716 | $htmlNS = ""; | |
717 | for ($index = 0; $index < $htmlNode->item(0)->attributes->length; $index++) { | |
718 | $nodeName = $htmlNode->item(0)->attributes->item($index)->nodeName; | |
719 | $nodeValue = $htmlNode->item(0)->attributes->item($index)->nodeValue; | |
720 | ||
721 | if ($nodeName != "xmlns") { | |
722 | $htmlNS .= " $nodeName=\"$nodeValue\""; | |
723 | } | |
724 | } | |
725 | ||
726 | $xml = new DOMDocument('1.0', "utf-8"); | |
727 | $xml->lookupPrefix("http://www.w3.org/1999/xhtml"); | |
728 | $xml->preserveWhiteSpace = FALSE; | |
729 | $xml->formatOutput = TRUE; | |
730 | ||
731 | $xml2Doc = new DOMDocument('1.0', "utf-8"); | |
732 | $xml2Doc->lookupPrefix("http://www.w3.org/1999/xhtml"); | |
733 | $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"); | |
734 | $html = $xml2Doc->getElementsByTagName("html")->item(0); | |
735 | $html->appendChild($xml2Doc->importNode($headNode->item(0), TRUE)); | |
736 | $html->appendChild($xml2Doc->importNode($bodyNode->item(0), TRUE)); | |
737 | ||
738 | // force pretty printing and correct formatting, should not be needed, but it is. | |
739 | $xml->loadXML($xml2Doc->saveXML()); | |
740 | $doc = $xml->saveXML(); | |
741 | ||
742 | if (!$this->isEPubVersion2()) { | |
743 | $doc = preg_replace('#^\s*<!DOCTYPE\ .+?>\s*#im', '', $doc); | |
744 | } | |
745 | } | |
746 | return TRUE; | |
747 | } | |
748 | ||
749 | /** | |
750 | * Process images referenced from an CSS file to the book. | |
751 | * | |
752 | * $externalReferences determins how the function will handle external references. | |
753 | * | |
754 | * @param string &$cssFile (referenced) | |
755 | * @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. | |
756 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | |
757 | * @param string $cssDir The of the CSS file's directory from the root of the archive. | |
758 | * | |
759 | * @return bool FALSE if unsuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). | |
760 | */ | |
761 | protected function processCSSExternalReferences(&$cssFile, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $cssDir = "") { | |
762 | if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { | |
763 | return FALSE; | |
764 | } | |
765 | ||
766 | $backPath = preg_replace('#[^/]+/#i', "../", $cssDir); | |
767 | $imgs = null; | |
768 | preg_match_all('#url\s*\([\'\"\s]*(.+?)[\'\"\s]*\)#im', $cssFile, $imgs, PREG_SET_ORDER); | |
769 | ||
770 | $itemCount = count($imgs); | |
771 | for ($idx = 0; $idx < $itemCount; $idx++) { | |
772 | $img = $imgs[$idx]; | |
773 | if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES || $externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) { | |
774 | $cssFile = str_replace($img[0], "", $cssFile); | |
775 | } else { | |
776 | $source = $img[1]; | |
777 | ||
778 | $pathData = pathinfo($source); | |
779 | $internalSrc = $pathData['basename']; | |
780 | $internalPath = ""; | |
781 | $isSourceExternal = FALSE; | |
782 | ||
783 | if ($this->resolveImage($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $cssDir, $backPath)) { | |
784 | $cssFile = str_replace($img[0], "url('" . $backPath . $internalPath . "')", $cssFile); | |
785 | } else if ($isSourceExternal) { | |
786 | $cssFile = str_replace($img[0], "", $cssFile); // External image is missing | |
787 | } // else do nothing, if the image is local, and missing, assume it's been generated. | |
788 | } | |
789 | } | |
790 | return TRUE; | |
791 | } | |
792 | ||
793 | /** | |
794 | * Process style tags in a DOMDocument. Styles will be passed as CSS files and reinserted into the document. | |
795 | * | |
796 | * @param DOMDocument &$xmlDoc (referenced) | |
797 | * @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. | |
798 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | |
799 | * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. | |
800 | * | |
801 | * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). | |
802 | */ | |
803 | protected function processChapterStyles(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "") { | |
804 | if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { | |
805 | return FALSE; | |
806 | } | |
807 | // process inlined CSS styles in style tags. | |
808 | $styles = $xmlDoc->getElementsByTagName("style"); | |
809 | $styleCount = $styles->length; | |
810 | for ($styleIdx = 0; $styleIdx < $styleCount; $styleIdx++) { | |
811 | $style = $styles->item($styleIdx); | |
812 | ||
813 | $styleData = preg_replace('#[/\*\s]*\<\!\[CDATA\[[\s\*/]*#im', "", $style->nodeValue); | |
814 | $styleData = preg_replace('#[/\*\s]*\]\]\>[\s\*/]*#im', "", $styleData); | |
815 | ||
816 | $this->processCSSExternalReferences($styleData, $externalReferences, $baseDir, $htmlDir); | |
817 | $style->nodeValue = "\n" . trim($styleData) . "\n"; | |
818 | } | |
819 | return TRUE; | |
820 | } | |
821 | ||
822 | /** | |
823 | * 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. | |
824 | * Link types text/css will be passed as CSS files. | |
825 | * | |
826 | * @param DOMDocument &$xmlDoc (referenced) | |
827 | * @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. | |
828 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | |
829 | * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. | |
830 | * @param string $backPath The path to get back to the root of the archive from $htmlDir. | |
831 | * | |
832 | * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). | |
833 | */ | |
834 | protected function processChapterLinks(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", $backPath = "") { | |
835 | if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { | |
836 | return FALSE; | |
837 | } | |
838 | // process link tags. | |
839 | $links = $xmlDoc->getElementsByTagName("link"); | |
840 | $linkCount = $links->length; | |
841 | for ($linkIdx = 0; $linkIdx < $linkCount; $linkIdx++) { | |
842 | $link = $links->item($linkIdx); | |
843 | $source = $link->attributes->getNamedItem("href")->nodeValue; | |
844 | $sourceData = NULL; | |
845 | ||
846 | $pathData = pathinfo($source); | |
847 | $internalSrc = $pathData['basename']; | |
848 | ||
849 | if (preg_match('#^(http|ftp)s?://#i', $source) == 1) { | |
850 | $urlinfo = parse_url($source); | |
851 | ||
852 | if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) { | |
853 | $internalSrc = substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") + strlen($baseDir) + 1); | |
854 | } | |
855 | ||
856 | @$sourceData = getFileContents($source); | |
857 | } else if (strpos($source, "/") === 0) { | |
858 | @$sourceData = file_get_contents($this->docRoot . $source); | |
859 | } else { | |
860 | @$sourceData = file_get_contents($this->docRoot . $baseDir . "/" . $source); | |
861 | } | |
862 | ||
863 | if (!empty($sourceData)) { | |
864 | if (!array_key_exists($internalSrc, $this->fileList)) { | |
865 | $mime = $link->attributes->getNamedItem("type")->nodeValue; | |
866 | if (empty($mime)) { | |
867 | $mime = "text/plain"; | |
868 | } | |
869 | if ($mime == "text/css") { | |
870 | $this->processCSSExternalReferences($sourceData, $externalReferences, $baseDir, $htmlDir); | |
871 | $this->addCSSFile($internalSrc, $internalSrc, $sourceData, EPub::EXTERNAL_REF_IGNORE, $baseDir); | |
872 | $link->setAttribute("href", $backPath . $internalSrc); | |
873 | } else { | |
874 | $this->addFile($internalSrc, $internalSrc, $sourceData, $mime); | |
875 | } | |
876 | $this->fileList[$internalSrc] = $source; | |
877 | } else { | |
878 | $link->setAttribute("href", $backPath . $internalSrc); | |
879 | } | |
880 | } // else do nothing, if the link is local, and missing, assume it's been generated. | |
881 | } | |
882 | return TRUE; | |
883 | } | |
884 | ||
885 | /** | |
886 | * Process img tags in a DOMDocument. | |
887 | * $externalReferences will determine what will happen to these images, and the img src will be rewritten accordingly. | |
888 | * | |
889 | * @param DOMDocument &$xmlDoc (referenced) | |
890 | * @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. | |
891 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | |
892 | * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. | |
893 | * @param string $backPath The path to get back to the root of the archive from $htmlDir. | |
894 | * | |
895 | * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). | |
896 | */ | |
897 | protected function processChapterImages(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", $backPath = "") { | |
898 | if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { | |
899 | return FALSE; | |
900 | } | |
901 | // process img tags. | |
902 | $postProcDomElememts = array(); | |
903 | $images = $xmlDoc->getElementsByTagName("img"); | |
904 | $itemCount = $images->length; | |
905 | ||
906 | for ($idx = 0; $idx < $itemCount; $idx++) { | |
907 | $img = $images->item($idx); | |
908 | ||
909 | if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES) { | |
910 | $postProcDomElememts[] = $img; | |
911 | } else if ($externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) { | |
912 | $altNode = $img->attributes->getNamedItem("alt"); | |
913 | $alt = "image"; | |
914 | if ($altNode !== NULL && strlen($altNode->nodeValue) > 0) { | |
915 | $alt = $altNode->nodeValue; | |
916 | } | |
917 | $postProcDomElememts[] = array($img, $this->createDomFragment($xmlDoc, "<em>[" . $alt . "]</em>")); | |
918 | } else { | |
919 | $source = $img->attributes->getNamedItem("src")->nodeValue; | |
920 | ||
921 | $parsedSource = parse_url($source); | |
922 | $internalSrc = $this->sanitizeFileName(urldecode(pathinfo($parsedSource['path'], PATHINFO_BASENAME))); | |
923 | $internalPath = ""; | |
924 | $isSourceExternal = FALSE; | |
925 | ||
926 | if ($this->resolveImage($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $htmlDir, $backPath)) { | |
927 | $img->setAttribute("src", $backPath . $internalPath); | |
928 | } else if ($isSourceExternal) { | |
929 | $postProcDomElememts[] = $img; // External image is missing | |
930 | } // else do nothing, if the image is local, and missing, assume it's been generated. | |
931 | } | |
932 | } | |
933 | ||
934 | foreach ($postProcDomElememts as $target) { | |
935 | if (is_array($target)) { | |
936 | $target[0]->parentNode->replaceChild($target[1], $target[0]); | |
937 | } else { | |
938 | $target->parentNode->removeChild($target); | |
939 | } | |
940 | } | |
941 | return TRUE; | |
942 | } | |
943 | ||
944 | /** | |
945 | * Process source tags in a DOMDocument. | |
946 | * $externalReferences will determine what will happen to these images, and the img src will be rewritten accordingly. | |
947 | * | |
948 | * @param DOMDocument &$xmlDoc (referenced) | |
949 | * @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. | |
950 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | |
951 | * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. | |
952 | * @param string $backPath The path to get back to the root of the archive from $htmlDir. | |
953 | * | |
954 | * @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE). | |
955 | */ | |
956 | protected function processChapterSources(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", $backPath = "") { | |
957 | if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) { | |
958 | return FALSE; | |
959 | } | |
960 | ||
961 | if ($this->bookVersion !== EPub::BOOK_VERSION_EPUB3) { | |
962 | // ePub 2 does not support multimedia formats, and they must be removed. | |
963 | $externalReferences = EPub::EXTERNAL_REF_REMOVE_IMAGES; | |
964 | } | |
965 | ||
966 | $postProcDomElememts = array(); | |
967 | $images = $xmlDoc->getElementsByTagName("source"); | |
968 | $itemCount = $images->length; | |
969 | for ($idx = 0; $idx < $itemCount; $idx++) { | |
970 | $img = $images->item($idx); | |
971 | if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES) { | |
972 | $postProcDomElememts[] = $img; | |
973 | } else if ($externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) { | |
974 | $altNode = $img->attributes->getNamedItem("alt"); | |
975 | $alt = "image"; | |
976 | if ($altNode !== NULL && strlen($altNode->nodeValue) > 0) { | |
977 | $alt = $altNode->nodeValue; | |
978 | } | |
979 | $postProcDomElememts[] = array($img, $this->createDomFragment($xmlDoc, "[" . $alt . "]")); | |
980 | } else { | |
981 | $source = $img->attributes->getNamedItem("src")->nodeValue; | |
982 | ||
983 | $parsedSource = parse_url($source); | |
984 | $internalSrc = $this->sanitizeFileName(urldecode(pathinfo($parsedSource['path'], PATHINFO_BASENAME))); | |
985 | $internalPath = ""; | |
986 | $isSourceExternal = FALSE; | |
987 | ||
988 | if ($this->resolveMedia($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $htmlDir, $backPath)) { | |
989 | $img->setAttribute("src", $backPath . $internalPath); | |
990 | } else if ($isSourceExternal) { | |
991 | $postProcDomElememts[] = $img; // External image is missing | |
992 | } // else do nothing, if the image is local, and missing, assume it's been generated. | |
993 | } | |
994 | } | |
995 | } | |
996 | ||
997 | /** | |
998 | * Resolve an image src and determine it's target location and add it to the book. | |
999 | * | |
1000 | * @param string $source Image Source link. | |
1001 | * @param string &$internalPath (referenced) Return value, will be set to the target path and name in the book. | |
1002 | * @param string &$internalSrc (referenced) Return value, will be set to the target name in the book. | |
1003 | * @param string &$isSourceExternal (referenced) Return value, will be set to TRUE if the image originated from a full URL. | |
1004 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | |
1005 | * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. | |
1006 | * @param string $backPath The path to get back to the root of the archive from $htmlDir. | |
1007 | */ | |
1008 | protected function resolveImage($source, &$internalPath, &$internalSrc, &$isSourceExternal, $baseDir = "", $htmlDir = "", $backPath = "") { | |
1009 | if ($this->isFinalized) { | |
1010 | return FALSE; | |
1011 | } | |
1012 | $imageData = NULL; | |
1013 | ||
1014 | if (preg_match('#^(http|ftp)s?://#i', $source) == 1) { | |
1015 | $urlinfo = parse_url($source); | |
1016 | $urlPath = pathinfo($urlinfo['path']); | |
1017 | ||
1018 | if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) { | |
1019 | $internalSrc = $this->sanitizeFileName(urldecode(substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") + strlen($baseDir) + 1))); | |
1020 | } | |
1021 | $internalPath = $urlinfo["scheme"] . "/" . $urlinfo["host"] . "/" . pathinfo($urlinfo["path"], PATHINFO_DIRNAME); | |
1022 | $isSourceExternal = TRUE; | |
1023 | $imageData = $this->getImage($source); | |
1024 | } else if (strpos($source, "/") === 0) { | |
1025 | $internalPath = pathinfo($source, PATHINFO_DIRNAME); | |
1026 | ||
1027 | $path = $source; | |
1028 | if (!file_exists($path)) { | |
1029 | $path = $this->docRoot . $path; | |
1030 | } | |
1031 | ||
1032 | $imageData = $this->getImage($path); | |
1033 | } else { | |
1034 | $internalPath = $htmlDir . "/" . preg_replace('#^[/\.]+#', '', pathinfo($source, PATHINFO_DIRNAME)); | |
1035 | ||
1036 | $path = $baseDir . "/" . $source; | |
1037 | if (!file_exists($path)) { | |
1038 | $path = $this->docRoot . $path; | |
1039 | } | |
1040 | ||
1041 | $imageData = $this->getImage($path); | |
1042 | } | |
1043 | if ($imageData !== FALSE) { | |
1044 | $iSrcInfo = pathinfo($internalSrc); | |
1045 | if (!empty($imageData['ext']) && $imageData['ext'] != $iSrcInfo['extension']) { | |
1046 | $internalSrc = $iSrcInfo['filename'] . "." . $imageData['ext']; | |
1047 | } | |
1048 | $internalPath = Zip::getRelativePath("images/" . $internalPath . "/" . $internalSrc); | |
1049 | if (!array_key_exists($internalPath, $this->fileList)) { | |
1050 | $this->addFile($internalPath, "i_" . $internalSrc, $imageData['image'], $imageData['mime']); | |
1051 | $this->fileList[$internalPath] = $source; | |
1052 | } | |
1053 | return TRUE; | |
1054 | } | |
1055 | return FALSE; | |
1056 | } | |
1057 | ||
1058 | /** | |
1059 | * Resolve a media src and determine it's target location and add it to the book. | |
1060 | * | |
1061 | * @param string $source Source link. | |
1062 | * @param string $internalPath (referenced) Return value, will be set to the target path and name in the book. | |
1063 | * @param string $internalSrc (referenced) Return value, will be set to the target name in the book. | |
1064 | * @param string $isSourceExternal (referenced) Return value, will be set to TRUE if the image originated from a full URL. | |
1065 | * @param string $baseDir Default is "", meaning it is pointing to the document root. | |
1066 | * @param string $htmlDir The path to the parent HTML file's directory from the root of the archive. | |
1067 | * @param string $backPath The path to get back to the root of the archive from $htmlDir. | |
1068 | */ | |
1069 | protected function resolveMedia($source, &$internalPath, &$internalSrc, &$isSourceExternal, $baseDir = "", $htmlDir = "", $backPath = "") { | |
1070 | if ($this->isFinalized) { | |
1071 | return FALSE; | |
1072 | } | |
1073 | $mediaPath = NULL; | |
1074 | $tmpFile; | |
1075 | ||
1076 | if (preg_match('#^(http|ftp)s?://#i', $source) == 1) { | |
1077 | $urlinfo = parse_url($source); | |
1078 | ||
1079 | if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) { | |
1080 | $internalSrc = substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") + strlen($baseDir) + 1); | |
1081 | } | |
1082 | $internalPath = $urlinfo["scheme"] . "/" . $urlinfo["host"] . "/" . pathinfo($urlinfo["path"], PATHINFO_DIRNAME); | |
1083 | $isSourceExternal = TRUE; | |
1084 | $mediaPath = $this->getFileContents($source, true); | |
1085 | $tmpFile = $mediaPath; | |
1086 | } else if (strpos($source, "/") === 0) { | |
1087 | $internalPath = pathinfo($source, PATHINFO_DIRNAME); | |
1088 | ||
1089 | $mediaPath = $source; | |
1090 | if (!file_exists($mediaPath)) { | |
1091 | $mediaPath = $this->docRoot . $mediaPath; | |
1092 | } | |
1093 | } else { | |
1094 | $internalPath = $htmlDir . "/" . preg_replace('#^[/\.]+#', '', pathinfo($source, PATHINFO_DIRNAME)); | |
1095 | ||
1096 | $mediaPath = $baseDir . "/" . $source; | |
1097 | if (!file_exists($mediaPath)) { | |
1098 | $mediaPath = $this->docRoot . $mediaPath; | |
1099 | } | |
1100 | } | |
1101 | ||
1102 | if ($mediaPath !== FALSE) { | |
1103 | $mime = $this->getMime($source); | |
1104 | $internalPath = Zip::getRelativePath("media/" . $internalPath . "/" . $internalSrc); | |
1105 | ||
1106 | if (!array_key_exists($internalPath, $this->fileList) && | |
1107 | $this->addLargeFile($internalPath, "m_" . $internalSrc, $mediaPath, $mime)) { | |
1108 | $this->fileList[$internalPath] = $source; | |
1109 | } | |
1110 | if (isset($tmpFile)) { | |
1111 | unlink($tmpFile); | |
1112 | } | |
1113 | return TRUE; | |
1114 | } | |
1115 | return FALSE; | |
1116 | } | |
1117 | ||
1118 | /** | |
1119 | * Get Book Chapter count. | |
1120 | * | |
1121 | * @access public | |
1122 | * @return number of chapters | |
1123 | */ | |
1124 | function getChapterCount() { | |
1125 | return $this->chapterCount; | |
1126 | } | |
1127 | ||
1128 | /** | |
1129 | * Book title, mandatory. | |
1130 | * | |
1131 | * Used for the dc:title metadata parameter in the OPF file as well as the DocTitle attribute in the NCX file. | |
1132 | * | |
1133 | * @param string $title | |
1134 | * @access public | |
1135 | * @return bool $success | |
1136 | */ | |
1137 | function setTitle($title) { | |
1138 | if ($this->isFinalized) { | |
1139 | return FALSE; | |
1140 | } | |
1141 | $this->title = $title; | |
1142 | return TRUE; | |
1143 | } | |
1144 | ||
1145 | /** | |
1146 | * Get Book title. | |
1147 | * | |
1148 | * @access public | |
1149 | * @return $title | |
1150 | */ | |
1151 | function getTitle() { | |
1152 | return $this->title; | |
1153 | } | |
1154 | ||
1155 | /** | |
1156 | * Book language, mandatory | |
1157 | * | |
1158 | * Use the RFC3066 Language codes, such as "en", "da", "fr" etc. | |
1159 | * Defaults to "en". | |
1160 | * | |
1161 | * Used for the dc:language metadata parameter in the OPF file. | |
1162 | * | |
1163 | * @param string $language | |
1164 | * @access public | |
1165 | * @return bool $success | |
1166 | */ | |
1167 | function setLanguage($language) { | |
1168 | if ($this->isFinalized || mb_strlen($language) != 2) { | |
1169 | return FALSE; | |
1170 | } | |
1171 | $this->language = $language; | |
1172 | return TRUE; | |
1173 | } | |
1174 | ||
1175 | /** | |
1176 | * Get Book language. | |
1177 | * | |
1178 | * @access public | |
1179 | * @return $language | |
1180 | */ | |
1181 | function getLanguage() { | |
1182 | return $this->language; | |
1183 | } | |
1184 | ||
1185 | /** | |
1186 | * Unique book identifier, mandatory. | |
1187 | * Use the URI, or ISBN if available. | |
1188 | * | |
1189 | * An unambiguous reference to the resource within a given context. | |
1190 | * | |
1191 | * Recommended best practice is to identify the resource by means of a | |
1192 | * string conforming to a formal identification system. | |
1193 | * | |
1194 | * Used for the dc:identifier metadata parameter in the OPF file, as well | |
1195 | * as dtb:uid in the NCX file. | |
1196 | * | |
1197 | * Identifier type should only be: | |
1198 | * EPub::IDENTIFIER_URI | |
1199 | * EPub::IDENTIFIER_ISBN | |
1200 | * EPub::IDENTIFIER_UUID | |
1201 | * | |
1202 | * @param string $identifier | |
1203 | * @param string $identifierType | |
1204 | * @access public | |
1205 | * @return bool $success | |
1206 | */ | |
1207 | function setIdentifier($identifier, $identifierType) { | |
1208 | if ($this->isFinalized || ($identifierType !== EPub::IDENTIFIER_URI && $identifierType !== EPub::IDENTIFIER_ISBN && $identifierType !== EPub::IDENTIFIER_UUID)) { | |
1209 | return FALSE; | |
1210 | } | |
1211 | $this->identifier = $identifier; | |
1212 | $this->identifierType = $identifierType; | |
1213 | return TRUE; | |
1214 | } | |
1215 | ||
1216 | /** | |
1217 | * Get Book identifier. | |
1218 | * | |
1219 | * @access public | |
1220 | * @return $identifier | |
1221 | */ | |
1222 | function getIdentifier() { | |
1223 | return $this->identifier; | |
1224 | } | |
1225 | ||
1226 | /** | |
1227 | * Get Book identifierType. | |
1228 | * | |
1229 | * @access public | |
1230 | * @return $identifierType | |
1231 | */ | |
1232 | function getIdentifierType() { | |
1233 | return $this->identifierType; | |
1234 | } | |
1235 | ||
1236 | /** | |
1237 | * Book description, optional. | |
1238 | * | |
1239 | * An account of the resource. | |
1240 | * | |
1241 | * Description may include but is not limited to: an abstract, a table of | |
1242 | * contents, a graphical representation, or a free-text account of the | |
1243 | * resource. | |
1244 | * | |
1245 | * Used for the dc:source metadata parameter in the OPF file | |
1246 | * | |
1247 | * @param string $description | |
1248 | * @access public | |
1249 | * @return bool $success | |
1250 | */ | |
1251 | function setDescription($description) { | |
1252 | if ($this->isFinalized) { | |
1253 | return FALSE; | |
1254 | } | |
1255 | $this->description = $description; | |
1256 | return TRUE; | |
1257 | } | |
1258 | ||
1259 | /** | |
1260 | * Get Book description. | |
1261 | * | |
1262 | * @access public | |
1263 | * @return $description | |
1264 | */ | |
1265 | function getDescription() { | |
1266 | return $this->description; | |
1267 | } | |
1268 | ||
1269 | /** | |
1270 | * Book author or creator, optional. | |
1271 | * The $authorSortKey is basically how the name is to be sorted, usually | |
1272 | * it's "Lastname, First names" where the $author is the straight | |
1273 | * "Firstnames Lastname" | |
1274 | * | |
1275 | * An entity primarily responsible for making the resource. | |
1276 | * | |
1277 | * Examples of a Creator include a person, an organization, or a service. | |
1278 | * Typically, the name of a Creator should be used to indicate the entity. | |
1279 | * | |
1280 | * Used for the dc:creator metadata parameter in the OPF file and the | |
1281 | * docAuthor attribure in the NCX file. | |
1282 | * The sort key is used for the opf:file-as attribute in dc:creator. | |
1283 | * | |
1284 | * @param string $author | |
1285 | * @param string $authorSortKey | |
1286 | * @access public | |
1287 | * @return bool $success | |
1288 | */ | |
1289 | function setAuthor($author, $authorSortKey) { | |
1290 | if ($this->isFinalized) { | |
1291 | return FALSE; | |
1292 | } | |
1293 | $this->author = $author; | |
1294 | $this->authorSortKey = $authorSortKey; | |
1295 | return TRUE; | |
1296 | } | |
1297 | ||
1298 | /** | |
1299 | * Get Book author. | |
1300 | * | |
1301 | * @access public | |
1302 | * @return $author | |
1303 | */ | |
1304 | function getAuthor() { | |
1305 | return $this->author; | |
1306 | } | |
1307 | ||
1308 | /** | |
1309 | * Publisher Information, optional. | |
1310 | * | |
1311 | * An entity responsible for making the resource available. | |
1312 | * | |
1313 | * Examples of a Publisher include a person, an organization, or a service. | |
1314 | * Typically, the name of a Publisher should be used to indicate the entity. | |
1315 | * | |
1316 | * Used for the dc:publisher and dc:relation metadata parameters in the OPF file. | |
1317 | * | |
1318 | * @param string $publisherName | |
1319 | * @param string $publisherURL | |
1320 | * @access public | |
1321 | * @return bool $success | |
1322 | */ | |
1323 | function setPublisher($publisherName, $publisherURL) { | |
1324 | if ($this->isFinalized) { | |
1325 | return FALSE; | |
1326 | } | |
1327 | $this->publisherName = $publisherName; | |
1328 | $this->publisherURL = $publisherURL; | |
1329 | return TRUE; | |
1330 | } | |
1331 | ||
1332 | /** | |
1333 | * Get Book publisherName. | |
1334 | * | |
1335 | * @access public | |
1336 | * @return $publisherName | |
1337 | */ | |
1338 | function getPublisherName() { | |
1339 | return $this->publisherName; | |
1340 | } | |
1341 | ||
1342 | /** | |
1343 | * Get Book publisherURL. | |
1344 | * | |
1345 | * @access public | |
1346 | * @return $publisherURL | |
1347 | */ | |
1348 | function getPublisherURL() { | |
1349 | return $this->publisherURL; | |
1350 | } | |
1351 | ||
1352 | /** | |
1353 | * Release date, optional. If left blank, the time of the finalization will | |
1354 | * be used. | |
1355 | * | |
1356 | * A point or period of time associated with an event in the lifecycle of | |
1357 | * the resource. | |
1358 | * | |
1359 | * Date may be used to express temporal information at any level of | |
1360 | * granularity. Recommended best practice is to use an encoding scheme, | |
1361 | * such as the W3CDTF profile of ISO 8601 [W3CDTF]. | |
1362 | * | |
1363 | * Used for the dc:date metadata parameter in the OPF file | |
1364 | * | |
1365 | * @param long $timestamp | |
1366 | * @access public | |
1367 | * @return bool $success | |
1368 | */ | |
1369 | function setDate($timestamp) { | |
1370 | if ($this->isFinalized) { | |
1371 | return FALSE; | |
1372 | } | |
1373 | $this->date = $timestamp; | |
1374 | $this->opf->date = $timestamp; | |
1375 | return TRUE; | |
1376 | } | |
1377 | ||
1378 | /** | |
1379 | * Get Book date. | |
1380 | * | |
1381 | * @access public | |
1382 | * @return $date | |
1383 | */ | |
1384 | function getDate() { | |
1385 | return $this->date; | |
1386 | } | |
1387 | ||
1388 | /** | |
1389 | * Book (copy)rights, optional. | |
1390 | * | |
1391 | * Information about rights held in and over the resource. | |
1392 | * | |
1393 | * Typically, rights information includes a statement about various | |
1394 | * property rights associated with the resource, including intellectual | |
1395 | * property rights. | |
1396 | * | |
1397 | * Used for the dc:rights metadata parameter in the OPF file | |
1398 | * | |
1399 | * @param string $rightsText | |
1400 | * @access public | |
1401 | * @return bool $success | |
1402 | */ | |
1403 | function setRights($rightsText) { | |
1404 | if ($this->isFinalized) { | |
1405 | return FALSE; | |
1406 | } | |
1407 | $this->rights = $rightsText; | |
1408 | return TRUE; | |
1409 | } | |
1410 | ||
1411 | /** | |
1412 | * Get Book rights. | |
1413 | * | |
1414 | * @access public | |
1415 | * @return $rights | |
1416 | */ | |
1417 | function getRights() { | |
1418 | return $this->rights; | |
1419 | } | |
1420 | ||
1421 | /** | |
1422 | * Add book Subject. | |
1423 | * | |
1424 | * The topic of the resource. | |
1425 | * | |
1426 | * Typically, the subject will be represented using keywords, key phrases, | |
1427 | * or classification codes. Recommended best practice is to use a | |
1428 | * controlled vocabulary. To describe the spatial or temporal topic of the | |
1429 | * resource, use the Coverage element. | |
1430 | * | |
1431 | * @param string $subject | |
1432 | */ | |
1433 | function setSubject($subject) { | |
1434 | if ($this->isFinalized) { | |
1435 | return; | |
1436 | } | |
1437 | $this->opf->addDCMeta(DublinCore::SUBJECT, $this->decodeHtmlEntities($subject)); | |
1438 | } | |
1439 | ||
1440 | /** | |
1441 | * Book source URL, optional. | |
1442 | * | |
1443 | * A related resource from which the described resource is derived. | |
1444 | * | |
1445 | * The described resource may be derived from the related resource in whole | |
1446 | * or in part. Recommended best practice is to identify the related | |
1447 | * resource by means of a string conforming to a formal identification system. | |
1448 | * | |
1449 | * Used for the dc:source metadata parameter in the OPF file | |
1450 | * | |
1451 | * @param string $sourceURL | |
1452 | * @access public | |
1453 | * @return bool $success | |
1454 | */ | |
1455 | function setSourceURL($sourceURL) { | |
1456 | if ($this->isFinalized) { | |
1457 | return FALSE; | |
1458 | } | |
1459 | $this->sourceURL = $sourceURL; | |
1460 | return TRUE; | |
1461 | } | |
1462 | ||
1463 | /** | |
1464 | * Get Book sourceURL. | |
1465 | * | |
1466 | * @access public | |
1467 | * @return $sourceURL | |
1468 | */ | |
1469 | function getSourceURL() { | |
1470 | return $this->sourceURL; | |
1471 | } | |
1472 | ||
1473 | /** | |
1474 | * Coverage, optional. | |
1475 | * | |
1476 | * The spatial or temporal topic of the resource, the spatial applicability | |
1477 | * of the resource, or the jurisdiction under which the resource is relevant. | |
1478 | * | |
1479 | * Spatial topic and spatial applicability may be a named place or a location | |
1480 | * specified by its geographic coordinates. Temporal topic may be a named | |
1481 | * period, date, or date range. A jurisdiction may be a named administrative | |
1482 | * entity or a geographic place to which the resource applies. Recommended | |
1483 | * best practice is to use a controlled vocabulary such as the Thesaurus of | |
1484 | * Geographic Names [TGN]. Where appropriate, named places or time periods | |
1485 | * can be used in preference to numeric identifiers such as sets of | |
1486 | * coordinates or date ranges. | |
1487 | * | |
1488 | * Used for the dc:coverage metadata parameter in the OPF file | |
1489 | * | |
1490 | * Same as ->addDublinCoreMetadata(DublinCore::COVERAGE, $coverage); | |
1491 | * | |
1492 | * @param string $coverage | |
1493 | * @access public | |
1494 | * @return bool $success | |
1495 | */ | |
1496 | function setCoverage($coverage) { | |
1497 | if ($this->isFinalized) { | |
1498 | return FALSE; | |
1499 | } | |
1500 | $this->coverage = $coverage; | |
1501 | return TRUE; | |
1502 | } | |
1503 | ||
1504 | /** | |
1505 | * Get Book coverage. | |
1506 | * | |
1507 | * @access public | |
1508 | * @return $coverage | |
1509 | */ | |
1510 | function getCoverage() { | |
1511 | return $this->coverage; | |
1512 | } | |
1513 | ||
1514 | /** | |
1515 | * Set book Relation. | |
1516 | * | |
1517 | * A related resource. | |
1518 | * | |
1519 | * Recommended best practice is to identify the related resource by means | |
1520 | * of a string conforming to a formal identification system. | |
1521 | * | |
1522 | * @param string $relation | |
1523 | */ | |
1524 | function setRelation($relation) { | |
1525 | if ($this->isFinalized) { | |
1526 | return; | |
1527 | } | |
1528 | $this->relation = $relation; | |
1529 | } | |
1530 | ||
1531 | /** | |
1532 | * Get the book relation. | |
1533 | * | |
1534 | * @return string The relation. | |
1535 | */ | |
1536 | function getRelation() { | |
1537 | return $this->relation; | |
1538 | } | |
1539 | ||
1540 | /** | |
1541 | * Set book Generator. | |
1542 | * | |
1543 | * The generator is a meta tag added to the ncx file, it is not visible | |
1544 | * from within the book, but is a kind of electronic watermark. | |
1545 | * | |
1546 | * @param string $generator | |
1547 | */ | |
1548 | function setGenerator($generator) { | |
1549 | if ($this->isFinalized) { | |
1550 | return; | |
1551 | } | |
1552 | $this->generator = $generator; | |
1553 | } | |
1554 | ||
1555 | /** | |
1556 | * Get the book relation. | |
1557 | * | |
1558 | * @return string The generator identity string. | |
1559 | */ | |
1560 | function getGenerator() { | |
1561 | return $this->generator; | |
1562 | } | |
1563 | ||
1564 | /** | |
1565 | * Set ePub date formate to the short yyyy-mm-dd form, for compliance with | |
1566 | * a bug in EpubCheck, prior to its version 1.1. | |
1567 | * | |
1568 | * The latest version of ePubCheck can be obtained here: | |
1569 | * http://code.google.com/p/epubcheck/ | |
1570 | * | |
1571 | * @access public | |
1572 | * @return bool $success | |
1573 | */ | |
1574 | function setShortDateFormat() { | |
1575 | if ($this->isFinalized) { | |
1576 | return FALSE; | |
1577 | } | |
1578 | $this->dateformat = $this->dateformatShort; | |
1579 | return TRUE; | |
1580 | } | |
1581 | ||
1582 | /** | |
1583 | * @Deprecated | |
1584 | */ | |
1585 | function setIgnoreEmptyBuffer($ignoreEmptyBuffer = TRUE) { | |
1586 | die ("Function was deprecated, functionality is no longer needed."); | |
1587 | } | |
1588 | ||
1589 | /** | |
1590 | * Set the references title for the ePub 3 landmarks section | |
1591 | * | |
1592 | * @param string $referencesTitle | |
1593 | * @param string $referencesId | |
1594 | * @param string $referencesClass | |
1595 | * @return bool | |
1596 | */ | |
1597 | function setReferencesTitle($referencesTitle = "Guide", $referencesId = "", $referencesClass = "references") { | |
1598 | if ($this->isFinalized) { | |
1599 | return FALSE; | |
1600 | } | |
1601 | $this->ncx->referencesTitle = is_string($referencesTitle) ? trim($referencesTitle) : "Guide"; | |
1602 | $this->ncx->referencesId = is_string($referencesId) ? trim($referencesId) : "references"; | |
1603 | $this->ncx->referencesClass = is_string($referencesClass) ? trim($referencesClass) : "references"; | |
1604 | return TRUE; | |
1605 | } | |
1606 | ||
1607 | /** | |
1608 | * Set the references title for the ePub 3 landmarks section | |
1609 | * | |
1610 | * @param bool $referencesTitle | |
1611 | */ | |
1612 | function setisReferencesAddedToToc($isReferencesAddedToToc = TRUE) { | |
1613 | if ($this->isFinalized) { | |
1614 | return FALSE; | |
1615 | } | |
1616 | $this->isReferencesAddedToToc = $isReferencesAddedToToc === TRUE; | |
1617 | return TRUE; | |
1618 | } | |
1619 | ||
1620 | /** | |
1621 | * Get Book status. | |
1622 | * | |
1623 | * @access public | |
1624 | * @return bool | |
1625 | */ | |
1626 | function isFinalized() { | |
1627 | return $this->isFinalized; | |
1628 | } | |
1629 | ||
1630 | /** | |
1631 | * Build the Table of Contents. This is not strictly necessary, as most eReaders will build it from the navigation structure in the .ncx file. | |
1632 | * | |
1633 | * @param string $cssFileName Include a link to this css file in the TOC html. | |
1634 | * @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". | |
1635 | * @param string $title Title of the Table of contents. Default is "Table of Contents". Use this for ie. languages other than English. | |
1636 | * @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. | |
1637 | * @param bool $addToIndex Add the TOC to the NCX index at the current leve/position. Default is FALSE | |
1638 | * @param string $tocFileName Change teh default name of the TOC file. The default is "TOC.xhtml" | |
1639 | */ | |
1640 | function buildTOC($cssFileName = NULL, $tocCSSClass = "toc", $title = "Table of Contents", $addReferences = TRUE, $addToIndex = FALSE, $tocFileName = "TOC.xhtml") { | |
1641 | if ($this->isFinalized) { | |
1642 | return FALSE; | |
1643 | } | |
1644 | $this->buildTOC = TRUE; | |
1645 | $this->tocTitle = $title; | |
1646 | $this->tocFileName = $this->normalizeFileName($tocFileName); | |
1647 | if (!empty($cssFileName)) { | |
1648 | $this->tocCSSFileName = $this->normalizeFileName($cssFileName); | |
1649 | } | |
1650 | $this->tocCSSClass = $tocCSSClass; | |
1651 | $this->tocAddReferences = $addReferences; | |
1652 | ||
1653 | $this->opf->addItemRef("ref_" . Reference::TABLE_OF_CONTENTS, FALSE); | |
1654 | $this->opf->addReference(Reference::TABLE_OF_CONTENTS, $title, $this->tocFileName); | |
1655 | ||
1656 | if ($addToIndex) { | |
1657 | $navPoint = new NavPoint($this->decodeHtmlEntities($title), $this->tocFileName, "ref_" . Reference::TABLE_OF_CONTENTS); | |
1658 | $this->ncx->addNavPoint($navPoint); | |
1659 | } else { | |
1660 | $this->ncx->referencesList[Reference::TABLE_OF_CONTENTS] = $this->tocFileName; | |
1661 | $this->ncx->referencesName[Reference::TABLE_OF_CONTENTS] = $title; | |
1662 | } | |
1663 | } | |
1664 | ||
1665 | private function finalizeTOC() { | |
1666 | if (!$this->buildTOC) { | |
1667 | return FALSE; | |
1668 | } | |
1669 | ||
1670 | if (empty($this->tocTitle)) { | |
1671 | $this->tocTitle = "Table of Contents"; | |
1672 | } | |
1673 | ||
1674 | $tocData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; | |
1675 | ||
1676 | if ($this->isEPubVersion2()) { | |
1677 | $tocData .= "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n" | |
1678 | . " \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n" | |
1679 | . "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" | |
1680 | . "<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"; | |
1681 | } else { | |
1682 | $tocData .= "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n" | |
1683 | . "<head>\n<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n"; | |
1684 | } | |
1685 | ||
1686 | if (!empty($this->tocCssFileName)) { | |
1687 | $tocData .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" . $this->tocCssFileName . "\" />\n"; | |
1688 | } | |
1689 | ||
1690 | $tocData .= "<title>" . $this->tocTitle . "</title>\n" | |
1691 | . "</head>\n" | |
1692 | . "<body>\n" | |
1693 | . "<h3>" . $this->tocTitle . "</h3>\n<div"; | |
1694 | ||
1695 | if (!empty($this->tocCSSClass)) { | |
1696 | $tocData .= " class=\"" . $this->tocCSSClass . "\""; | |
1697 | } | |
1698 | $tocData .= ">\n"; | |
1699 | ||
1700 | while (list($item, $descriptive) = each($this->referencesOrder)) { | |
1701 | if ($item === "text") { | |
1702 | while (list($chapterName, $navPoint) = each($this->ncx->chapterList)) { | |
1703 | $fileName = $navPoint->getContentSrc(); | |
1704 | $level = $navPoint->getLevel() -2; | |
72a85715 | 1705 | $tocData .= "\t<p>" . str_repeat("      ", $level) . "<a href=\"" . $this->sanitizeFileName($fileName) . "\">" . $chapterName . "</a></p>\n"; |
87090d8a | 1706 | } |
1707 | } else if ($this->tocAddReferences === TRUE) { | |
1708 | if (array_key_exists($item, $this->ncx->referencesList)) { | |
1709 | $tocData .= "\t<p><a href=\"" . $this->ncx->referencesList[$item] . "\">" . $descriptive . "</a></p>\n"; | |
1710 | } else if ($item === "toc") { | |
1711 | $tocData .= "\t<p><a href=\"TOC.xhtml\">" . $this->tocTitle . "</a></p>\n"; | |
1712 | } else if ($item === "cover" && $this->isCoverImageSet) { | |
1713 | $tocData .= "\t<p><a href=\"CoverPage.xhtml\">" . $descriptive . "</a></p>\n"; | |
1714 | } | |
1715 | } | |
1716 | } | |
1717 | $tocData .= "</div>\n</body>\n</html>\n"; | |
1718 | ||
1719 | $this->addReferencePage($this->tocTitle, $this->tocFileName, $tocData, Reference::TABLE_OF_CONTENTS); | |
1720 | ||
1721 | } | |
1722 | ||
1723 | /** | |
1724 | * @return bool | |
1725 | */ | |
1726 | function isEPubVersion2() { | |
1727 | return $this->bookVersion === EPub::BOOK_VERSION_EPUB2; | |
1728 | } | |
1729 | ||
1730 | /** | |
1731 | * @param string $cssFileName | |
1732 | * @param string $title | |
1733 | * @return string | |
1734 | */ | |
1735 | function buildEPub3TOC($cssFileName = NULL, $title = "Table of Contents") { | |
1736 | $this->ncx->referencesOrder = $this->referencesOrder; | |
1737 | $this->ncx->setDocTitle($this->decodeHtmlEntities($this->title)); | |
1738 | return $this->ncx->finalizeEPub3($title, $cssFileName); | |
1739 | } | |
1740 | ||
1741 | /** | |
1742 | * @param string $fileName | |
1743 | * @param string $tocData | |
1744 | * @return bool | |
1745 | */ | |
1746 | function addEPub3TOC($fileName, $tocData) { | |
1747 | if ($this->isEPubVersion2() || $this->isFinalized || array_key_exists($fileName, $this->fileList)) { | |
1748 | return FALSE; | |
1749 | } | |
1750 | $fileName = Zip::getRelativePath($fileName); | |
1751 | $fileName = preg_replace('#^[/\.]+#i', "", $fileName); | |
1752 | ||
1753 | $this->zip->addFile($tocData, $this->bookRoot.$fileName); | |
1754 | ||
1755 | $this->fileList[$fileName] = $fileName; | |
1756 | $this->opf->addItem("toc", $fileName, "application/xhtml+xml", "nav"); | |
1757 | return TRUE; | |
1758 | } | |
1759 | ||
1760 | /** | |
1761 | * Check for mandatory parameters and finalize the e-book. | |
1762 | * Once finalized, the book is locked for further additions. | |
1763 | * | |
1764 | * @return bool $success | |
1765 | */ | |
1766 | function finalize() { | |
1767 | if ($this->isFinalized || $this->chapterCount == 0 || empty($this->title) || empty($this->language)) { | |
1768 | return FALSE; | |
1769 | } | |
1770 | ||
1771 | if (empty($this->identifier) || empty($this->identifierType)) { | |
1772 | $this->setIdentifier($this->createUUID(4), EPub::IDENTIFIER_UUID); | |
1773 | } | |
1774 | ||
1775 | if ($this->date == 0) { | |
1776 | $this->date = time(); | |
1777 | } | |
1778 | ||
1779 | if (empty($this->sourceURL)) { | |
1780 | $this->sourceURL = $this->getCurrentPageURL(); | |
1781 | } | |
1782 | ||
1783 | if (empty($this->publisherURL)) { | |
1784 | $this->sourceURL = $this->getCurrentServerURL(); | |
1785 | } | |
1786 | ||
1787 | // Generate OPF data: | |
1788 | $this->opf->setIdent("BookId"); | |
1789 | $this->opf->initialize($this->title, $this->language, $this->identifier, $this->identifierType); | |
1790 | ||
1791 | $DCdate = new DublinCore(DublinCore::DATE, gmdate($this->dateformat, $this->date)); | |
1792 | $DCdate->addOpfAttr("event", "publication"); | |
1793 | $this->opf->metadata->addDublinCore($DCdate); | |
1794 | ||
1795 | if (!empty($this->description)) { | |
1796 | $this->opf->addDCMeta(DublinCore::DESCRIPTION, $this->decodeHtmlEntities($this->description)); | |
1797 | } | |
1798 | ||
1799 | if (!empty($this->publisherName)) { | |
1800 | $this->opf->addDCMeta(DublinCore::PUBLISHER, $this->decodeHtmlEntities($this->publisherName)); | |
1801 | } | |
1802 | ||
1803 | if (!empty($this->publisherURL)) { | |
1804 | $this->opf->addDCMeta(DublinCore::RELATION, $this->decodeHtmlEntities($this->publisherURL)); | |
1805 | } | |
1806 | ||
1807 | if (!empty($this->author)) { | |
1808 | $author = $this->decodeHtmlEntities($this->author); | |
1809 | $this->opf->addCreator($author, $this->decodeHtmlEntities($this->authorSortKey), MarcCode::AUTHOR); | |
1810 | $this->ncx->setDocAuthor($author); | |
1811 | } | |
1812 | ||
1813 | if (!empty($this->rights)) { | |
1814 | $this->opf->addDCMeta(DublinCore::RIGHTS, $this->decodeHtmlEntities($this->rights)); | |
1815 | } | |
1816 | ||
1817 | if (!empty($this->coverage)) { | |
1818 | $this->opf->addDCMeta(DublinCore::COVERAGE, $this->decodeHtmlEntities($this->coverage)); | |
1819 | } | |
1820 | ||
1821 | if (!empty($this->sourceURL)) { | |
1822 | $this->opf->addDCMeta(DublinCore::SOURCE, $this->sourceURL); | |
1823 | } | |
1824 | ||
1825 | if (!empty($this->relation)) { | |
1826 | $this->opf->addDCMeta(DublinCore::RELATION, $this->decodeHtmlEntities($this->relation)); | |
1827 | } | |
1828 | ||
1829 | if ($this->isCoverImageSet) { | |
1830 | $this->opf->addMeta("cover", "coverImage"); | |
1831 | } | |
1832 | ||
1833 | if (!empty($this->generator)) { | |
1834 | $gen = $this->decodeHtmlEntities($this->generator); | |
1835 | $this->opf->addMeta("generator", $gen); | |
1836 | $this->ncx->addMetaEntry("dtb:generator", $gen); | |
1837 | } | |
1838 | ||
1839 | if ($this->EPubMark) { | |
1840 | $this->opf->addMeta("generator", "EPub (Version " . self::VERSION . ") by A. Grandt, http://www.phpclasses.org/package/6115"); | |
1841 | } | |
1842 | ||
1843 | reset($this->ncx->chapterList); | |
1844 | list($firstChapterName, $firstChapterNavPoint) = each($this->ncx->chapterList); | |
1845 | $firstChapterFileName = $firstChapterNavPoint->getContentSrc(); | |
1846 | $this->opf->addReference(Reference::TEXT, $this->decodeHtmlEntities($firstChapterName), $firstChapterFileName); | |
1847 | ||
1848 | $this->ncx->setUid($this->identifier); | |
1849 | ||
1850 | $this->ncx->setDocTitle($this->decodeHtmlEntities($this->title)); | |
1851 | ||
1852 | $this->ncx->referencesOrder = $this->referencesOrder; | |
1853 | if ($this->isReferencesAddedToToc) { | |
1854 | $this->ncx->finalizeReferences(); | |
1855 | } | |
1856 | ||
1857 | $this->finalizeTOC(); | |
1858 | ||
1859 | if (!$this->isEPubVersion2()) { | |
1860 | $this->addEPub3TOC("epub3toc.xhtml", $this->buildEPub3TOC()); | |
1861 | } | |
1862 | ||
1863 | $opfFinal = $this->fixEncoding($this->opf->finalize()); | |
1864 | $ncxFinal = $this->fixEncoding($this->ncx->finalize()); | |
1865 | ||
1866 | if (mb_detect_encoding($opfFinal, 'UTF-8', true) === "UTF-8") { | |
1867 | $this->zip->addFile($opfFinal, $this->bookRoot."book.opf"); | |
1868 | } else { | |
1869 | $this->zip->addFile(mb_convert_encoding($opfFinal, "UTF-8"), $this->bookRoot."book.opf"); | |
1870 | } | |
1871 | ||
1872 | if (mb_detect_encoding($ncxFinal, 'UTF-8', true) === "UTF-8") { | |
1873 | $this->zip->addFile($ncxFinal, $this->bookRoot."book.ncx"); | |
1874 | } else { | |
1875 | $this->zip->addFile(mb_convert_encoding($ncxFinal, "UTF-8"), $this->bookRoot."book.ncx"); | |
1876 | } | |
1877 | ||
1878 | $this->opf = NULL; | |
1879 | $this->ncx = NULL; | |
1880 | ||
1881 | $this->isFinalized = TRUE; | |
1882 | return TRUE; | |
1883 | } | |
1884 | ||
1885 | /** | |
1886 | * Ensure the encoded string is a valid UTF-8 string. | |
1887 | * | |
1888 | * 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. | |
1889 | * | |
1890 | * @link: http://snippetdb.com/php/convert-string-to-utf-8-for-mysql | |
1891 | * @param string $in_str | |
1892 | * @return string converted string. | |
1893 | */ | |
1894 | function fixEncoding($in_str) { | |
1895 | if (mb_detect_encoding($in_str) == "UTF-8" && mb_check_encoding($in_str,"UTF-8")) { | |
1896 | return $in_str; | |
1897 | } else { | |
1898 | return utf8_encode($in_str); | |
1899 | } | |
1900 | } | |
1901 | ||
1902 | /** | |
1903 | * Return the finalized book. | |
1904 | * | |
1905 | * @return string with the book in binary form. | |
1906 | */ | |
1907 | function getBook() { | |
1908 | if (!$this->isFinalized) { | |
1909 | $this->finalize(); | |
1910 | } | |
1911 | ||
1912 | return $this->zip->getZipData(); | |
1913 | } | |
1914 | ||
1915 | /** | |
1916 | * Remove disallowed characters from string to get a nearly safe filename | |
1917 | * | |
1918 | * @param string $fileName | |
1919 | * @return mixed|string | |
1920 | */ | |
1921 | function sanitizeFileName($fileName) { | |
1922 | $fileName1 = str_replace($this->forbiddenCharacters, '', $fileName); | |
1923 | $fileName2 = preg_replace('/[\s-]+/', '-', $fileName1); | |
1924 | return trim($fileName2, '.-_'); | |
1925 | ||
1926 | } | |
1927 | ||
1928 | /** | |
1929 | * Cleanup the filepath, and remove leading . and / characters. | |
1930 | * | |
1931 | * Sometimes, when a path is generated from multiple fragments, | |
1932 | * you can get something like "../data/html/../images/image.jpeg" | |
1933 | * ePub files don't work well with that, this will normalize that | |
1934 | * example path to "data/images/image.jpeg" | |
1935 | * | |
1936 | * @param string $fileName | |
1937 | * @return string normalized filename | |
1938 | */ | |
1939 | function normalizeFileName($fileName) { | |
1940 | return preg_replace('#^[/\.]+#i', "", Zip::getRelativePath($fileName)); | |
1941 | } | |
1942 | ||
1943 | /** | |
1944 | * Save the ePub file to local disk. | |
1945 | * | |
1946 | * @param string $fileName | |
1947 | * @param string $baseDir If empty baseDir is absolute to server path, if omitted it's relative to script path | |
1948 | * @return The sent file name if successfull, FALSE if it failed. | |
1949 | */ | |
1950 | function saveBook($fileName, $baseDir = '.') { | |
1951 | ||
1952 | // Make fileName safe | |
1953 | $fileName = $this->sanitizeFileName($fileName); | |
1954 | ||
1955 | // Finalize book, if it's not done already | |
1956 | if (!$this->isFinalized) { | |
1957 | $this->finalize(); | |
1958 | } | |
1959 | ||
1960 | if (stripos(strrev($fileName), "bupe.") !== 0) { | |
1961 | $fileName .= ".epub"; | |
1962 | } | |
1963 | ||
1964 | // Try to open file access | |
1965 | $fh = fopen($baseDir.'/'.$fileName, "w"); | |
1966 | ||
1967 | if ($fh) { | |
1968 | fputs($fh, $this->getBook()); | |
1969 | fclose($fh); | |
1970 | ||
1971 | // if file is written return TRUE | |
1972 | return $fileName; | |
1973 | } | |
1974 | ||
1975 | // return FALSE by default | |
1976 | return FALSE; | |
1977 | } | |
1978 | ||
1979 | /** | |
1980 | * Return the finalized book size. | |
1981 | * | |
1982 | * @return string | |
1983 | */ | |
1984 | function getBookSize() { | |
1985 | if (!$this->isFinalized) { | |
1986 | $this->finalize(); | |
1987 | } | |
1988 | ||
1989 | return $this->zip->getArchiveSize(); | |
1990 | } | |
1991 | ||
1992 | /** | |
1993 | * Send the book as a zip download | |
1994 | * | |
1995 | * Sending will fail if the output buffer is in use. You can override this limit by | |
1996 | * calling setIgnoreEmptyBuffer(TRUE), though the function will still fail if that | |
1997 | * buffer is not empty. | |
1998 | * | |
1999 | * @param string $fileName The name of the book without the .epub at the end. | |
2000 | * @return The sent file name if successfull, FALSE if it failed. | |
2001 | */ | |
2002 | function sendBook($fileName) { | |
2003 | if (!$this->isFinalized) { | |
2004 | $this->finalize(); | |
2005 | } | |
2006 | ||
2007 | if (stripos(strrev($fileName), "bupe.") !== 0) { | |
2008 | $fileName .= ".epub"; | |
2009 | } | |
2010 | ||
2011 | if (TRUE === $this->zip->sendZip($fileName, "application/epub+zip")) { | |
2012 | return $fileName; | |
2013 | } | |
2014 | return FALSE; | |
2015 | } | |
2016 | ||
2017 | /** | |
2018 | * Generates an UUID. | |
2019 | * | |
2020 | * Default version (4) will generate a random UUID, version 3 will URL based UUID. | |
2021 | * | |
2022 | * Added for convinience | |
2023 | * | |
2024 | * @param int $bookVersion UUID version to retrieve, See lib.uuid.manual.html for details. | |
2025 | * @param string $url | |
2026 | * @return string The formatted uuid | |
2027 | */ | |
2028 | function createUUID($bookVersion = 4, $url = NULL) { | |
2029 | include_once("lib.uuid.php"); | |
2030 | return UUID::mint($bookVersion, $url, UUID::nsURL); | |
2031 | } | |
2032 | ||
2033 | /** | |
2034 | * Get the url of the current page. | |
2035 | * Example use: Default Source URL | |
2036 | * | |
2037 | * $return string Page URL. | |
2038 | */ | |
2039 | function getCurrentPageURL() { | |
2040 | $pageURL = $this->getCurrentServerURL() . filter_input(INPUT_SERVER, "REQUEST_URI"); | |
2041 | return $pageURL; | |
2042 | } | |
2043 | ||
2044 | /** | |
2045 | * Get the url of the server. | |
2046 | * Example use: Default Publisher URL | |
2047 | * | |
2048 | * $return string Server URL. | |
2049 | */ | |
2050 | function getCurrentServerURL() { | |
2051 | $serverURL = 'http'; | |
2052 | $https = filter_input(INPUT_SERVER, "HTTPS"); | |
2053 | $port = filter_input(INPUT_SERVER, "SERVER_PORT"); | |
2054 | ||
2055 | if ($https === "on") { | |
2056 | $serverURL .= "s"; | |
2057 | } | |
2058 | $serverURL .= "://" . filter_input(INPUT_SERVER, "SERVER_NAME"); | |
2059 | if ($port != "80") { | |
2060 | $serverURL .= ":" . $port; | |
2061 | } | |
2062 | return $serverURL . '/'; | |
2063 | } | |
2064 | ||
2065 | /** | |
2066 | * Try to determine the mimetype of the file path. | |
2067 | * | |
2068 | * @param string $source Path | |
2069 | * @return string mimetype, or FALSE. | |
2070 | */ | |
2071 | function getMime($source) { | |
2072 | return $this->mimetypes[pathinfo($source, PATHINFO_EXTENSION)]; | |
2073 | } | |
2074 | ||
2075 | /** | |
2076 | * Get an image from a file or url, return it resized if the image exceeds the $maxImageWidth or $maxImageHeight directives. | |
2077 | * | |
2078 | * The return value is an array. | |
2079 | * ['width'] is the width of the image. | |
2080 | * ['height'] is the height of the image. | |
2081 | * ['mime'] is the mime type of the image. Resized images are always in jpeg format. | |
2082 | * ['image'] is the image data. | |
2083 | * ['ext'] is the extension of the image file. | |
2084 | * | |
2085 | * @param string $source path or url to file. | |
2086 | * $return array | |
2087 | */ | |
2088 | function getImage($source) { | |
2089 | $width = -1; | |
2090 | $height = -1; | |
2091 | $mime = "application/octet-stream"; | |
2092 | $type = FALSE; | |
2093 | $ext = ""; | |
2094 | ||
2095 | ||
2096 | $image = $this->getFileContents($source); | |
2097 | ||
2098 | if ($image !== FALSE && strlen($image) > 0) { | |
2099 | $imageFile = imagecreatefromstring($image); | |
2100 | if ($imageFile !== false) { | |
2101 | $width = ImageSX($imageFile); | |
2102 | $height = ImageSY($imageFile); | |
2103 | } | |
2104 | if ($this->isExifInstalled) { | |
2105 | @$type = exif_imagetype($source); | |
2106 | $mime = image_type_to_mime_type($type); | |
2107 | } | |
2108 | if ($mime === "application/octet-stream") { | |
2109 | $mime = $this->image_file_type_from_binary($image); | |
2110 | } | |
2111 | if ($mime === "application/octet-stream") { | |
2112 | $mime = $this->getMimeTypeFromUrl($source); | |
2113 | } | |
2114 | } else { | |
2115 | return FALSE; | |
2116 | } | |
2117 | ||
2118 | if ($width <= 0 || $height <= 0) { | |
2119 | return FALSE; | |
2120 | } | |
2121 | ||
2122 | $ratio = 1; | |
2123 | ||
2124 | if ($this->isGdInstalled) { | |
2125 | if ($width > $this->maxImageWidth) { | |
2126 | $ratio = $this->maxImageWidth/$width; | |
2127 | } | |
2128 | if ($height*$ratio > $this->maxImageHeight) { | |
2129 | $ratio = $this->maxImageHeight/$height; | |
2130 | } | |
2131 | ||
2132 | if ($ratio < 1 || empty($mime) || ($this->isGifImagesEnabled !== FALSE && $mime == "image/gif")) { | |
2133 | $image_o = imagecreatefromstring($image); | |
2134 | $image_p = imagecreatetruecolor($width*$ratio, $height*$ratio); | |
2135 | ||
2136 | if ($mime == "image/png") { | |
2137 | imagealphablending($image_p, false); | |
2138 | imagesavealpha($image_p, true); | |
2139 | imagealphablending($image_o, true); | |
2140 | ||
2141 | imagecopyresampled($image_p, $image_o, 0, 0, 0, 0, ($width*$ratio), ($height*$ratio), $width, $height); | |
2142 | ob_start(); | |
2143 | imagepng($image_p, NULL, 9); | |
2144 | $image = ob_get_contents(); | |
2145 | ob_end_clean(); | |
2146 | ||
2147 | $ext = "png"; | |
2148 | } else { | |
2149 | imagecopyresampled($image_p, $image_o, 0, 0, 0, 0, ($width*$ratio), ($height*$ratio), $width, $height); | |
2150 | ob_start(); | |
2151 | imagejpeg($image_p, NULL, 80); | |
2152 | $image = ob_get_contents(); | |
2153 | ob_end_clean(); | |
2154 | ||
2155 | $mime = "image/jpeg"; | |
2156 | $ext = "jpg"; | |
2157 | } | |
2158 | imagedestroy($image_o); | |
2159 | imagedestroy($image_p); | |
2160 | } | |
2161 | } | |
2162 | ||
2163 | if ($ext === "") { | |
2164 | static $mimeToExt = array ( | |
2165 | 'image/jpeg' => 'jpg', | |
2166 | 'image/gif' => 'gif', | |
2167 | 'image/png' => 'png' | |
2168 | ); | |
2169 | ||
2170 | if (isset($mimeToExt[$mime])) { | |
2171 | $ext = $mimeToExt[$mime]; | |
2172 | } | |
2173 | } | |
2174 | ||
2175 | $rv = array(); | |
2176 | $rv['width'] = $width*$ratio; | |
2177 | $rv['height'] = $height*$ratio; | |
2178 | $rv['mime'] = $mime; | |
2179 | $rv['image'] = $image; | |
2180 | $rv['ext'] = $ext; | |
2181 | ||
2182 | return $rv; | |
2183 | } | |
2184 | ||
2185 | /** | |
2186 | * Get file contents, using curl if available, else file_get_contents | |
2187 | * | |
2188 | * @param string $source | |
2189 | * @return bool | |
2190 | */ | |
2191 | function getFileContents($source, $toTempFile = FALSE) { | |
2192 | $isExternal = preg_match('#^(http|ftp)s?://#i', $source) == 1; | |
2193 | ||
2194 | if ($isExternal && $this->isCurlInstalled) { | |
2195 | $ch = curl_init(); | |
2196 | $outFile = NULL; | |
2197 | $fp = NULL; | |
2198 | $res = FALSE; | |
2199 | $info = array('http_code' => 500); | |
2200 | ||
2201 | curl_setopt($ch, CURLOPT_HEADER, 0); | |
2202 | curl_setopt($ch, CURLOPT_URL, str_replace(" ","%20",$source)); | |
2203 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
2204 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); | |
2205 | curl_setopt($ch, CURLOPT_BUFFERSIZE, 4096); | |
2206 | ||
2207 | if ($toTempFile) { | |
2208 | $outFile = tempnam(sys_get_temp_dir(), "EPub_v" . EPub::VERSION . "_"); | |
2209 | $fp = fopen($outFile, "w+b"); | |
2210 | curl_setopt($ch, CURLOPT_FILE, $fp); | |
2211 | ||
2212 | $res = curl_exec($ch); | |
2213 | $info = curl_getinfo($ch); | |
2214 | ||
2215 | curl_close($ch); | |
2216 | fclose($fp); | |
2217 | } else { | |
2218 | $res = curl_exec($ch); | |
2219 | $info = curl_getinfo($ch); | |
2220 | ||
2221 | curl_close($ch); | |
2222 | } | |
2223 | ||
2224 | if ($info['http_code'] == 200 && $res != false) { | |
2225 | if ($toTempFile) { | |
2226 | return $outFile; | |
2227 | } | |
2228 | return $res; | |
2229 | } | |
2230 | return FALSE; | |
2231 | } | |
2232 | ||
2233 | if ($this->isFileGetContentsInstalled && (!$isExternal || $this->isFileGetContentsExtInstalled)) { | |
2234 | @$data = file_get_contents($source); | |
2235 | return $data; | |
2236 | } | |
2237 | return FALSE; | |
2238 | } | |
2239 | ||
2240 | /** | |
2241 | * get mime type from image data | |
2242 | * | |
2243 | * By fireweasel found on http://stackoverflow.com/questions/2207095/get-image-mimetype-from-resource-in-php-gd | |
2244 | * @staticvar array $type | |
2245 | * @param object $binary | |
2246 | * @return string | |
2247 | */ | |
2248 | function image_file_type_from_binary($binary) { | |
2249 | $hits = 0; | |
2250 | if (!preg_match( | |
2251 | '/\A(?:(\xff\xd8\xff)|(GIF8[79]a)|(\x89PNG\x0d\x0a)|(BM)|(\x49\x49(?:\x2a\x00|\x00\x4a))|(FORM.{4}ILBM))/', | |
2252 | $binary, $hits)) { | |
2253 | return 'application/octet-stream'; | |
2254 | } | |
2255 | static $type = array ( | |
2256 | 1 => 'image/jpeg', | |
2257 | 2 => 'image/gif', | |
2258 | 3 => 'image/png', | |
2259 | 4 => 'image/x-windows-bmp', | |
2260 | 5 => 'image/tiff', | |
2261 | 6 => 'image/x-ilbm', | |
2262 | ); | |
2263 | return $type[count($hits) - 1]; | |
2264 | } | |
2265 | ||
2266 | /** | |
2267 | * @param string $source URL Source | |
2268 | * @return string MimeType | |
2269 | */ | |
2270 | function getMimeTypeFromUrl($source) { | |
2271 | $ext = FALSE; | |
2272 | ||
2273 | $srev = strrev($source); | |
2274 | $pos = strpos($srev, "?"); | |
2275 | if ($pos !== FALSE) { | |
2276 | $srev = substr($srev, $pos+1); | |
2277 | } | |
2278 | ||
2279 | $pos = strpos($srev, "."); | |
2280 | if ($pos !== FALSE) { | |
2281 | $ext = strtolower(strrev(substr($srev, 0, $pos))); | |
2282 | } | |
2283 | ||
2284 | if ($ext !== FALSE) { | |
2285 | return $this->getMimeTypeFromExtension($ext); | |
2286 | } | |
2287 | return "application/octet-stream"; | |
2288 | } | |
2289 | ||
2290 | /** | |
2291 | * @param string $ext Extension | |
2292 | * @return string MimeType | |
2293 | */ | |
2294 | function getMimeTypeFromExtension($ext) { | |
2295 | switch ($ext) { | |
2296 | case "jpg": | |
2297 | case "jpe": | |
2298 | case "jpeg": | |
2299 | return 'image/jpeg'; | |
2300 | case "gif": | |
2301 | return 'image/gif'; | |
2302 | case "png": | |
2303 | return 'image/png'; | |
2304 | case "bmp": | |
2305 | return 'image/x-windows-bmp'; | |
2306 | case "tif": | |
2307 | case "tiff": | |
2308 | case "cpt": | |
2309 | return 'image/tiff'; | |
2310 | case "lbm": | |
2311 | case "ilbm": | |
2312 | return 'image/x-ilbm'; | |
2313 | default: | |
2314 | return "application/octet-stream"; | |
2315 | } | |
2316 | } | |
2317 | ||
2318 | /** | |
2319 | * Encode html code to use html entities, safeguarding it from potential character encoding peoblems | |
2320 | * This function is a bit different from the vanilla htmlentities function in that it does not encode html tags. | |
2321 | * | |
2322 | * The regexp is taken from the PHP Manual discussion, it was written by user "busbyjon". | |
2323 | * http://www.php.net/manual/en/function.htmlentities.php#90111 | |
2324 | * | |
2325 | * @param string $string string to encode. | |
2326 | */ | |
2327 | public function encodeHtml($string) { | |
2328 | $string = strtr($string, $this->html_encoding_characters); | |
2329 | ||
2330 | //return preg_replace("/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,5};)/", "&\\1", $string); | |
2331 | //return preg_replace("/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,5};)/", "&", $string); | |
2332 | return $string; | |
2333 | } | |
2334 | ||
2335 | /** | |
2336 | * Helper function to create a DOM fragment with given markup. | |
2337 | * | |
2338 | * @author Adam Schmalhofer | |
2339 | * | |
2340 | * @param DOMDocument $dom | |
2341 | * @param string $markup | |
2342 | * @return DOMNode fragment in a node. | |
2343 | */ | |
2344 | protected function createDomFragment($dom, $markup) { | |
2345 | $node = $dom->createDocumentFragment(); | |
2346 | $node->appendXML($markup); | |
2347 | return $node; | |
2348 | } | |
2349 | ||
2350 | /** | |
2351 | * Retrieve an array of file names currently added to the book. | |
2352 | * $key is the filename used in the book | |
2353 | * $value is the original filename, will be the same as $key for most entries | |
2354 | * | |
2355 | * @return array file list | |
2356 | */ | |
2357 | function getFileList() { | |
2358 | return $this->fileList; | |
2359 | } | |
2360 | ||
2361 | /** | |
2362 | * @deprecated Use Zip::getRelativePath($relPath) instead. | |
2363 | */ | |
2364 | function relPath($relPath) { | |
2365 | die ("Function was deprecated, use Zip::getRelativePath(\$relPath); instead"); | |
2366 | } | |
2367 | ||
2368 | /** | |
2369 | * Set default chapter target size. | |
2370 | * Default is 250000 bytes, and minimum is 10240 bytes. | |
2371 | * | |
2372 | * @param int $size segment size in bytes | |
2373 | * @return void | |
2374 | */ | |
2375 | function setSplitSize($size) { | |
2376 | $this->splitDefaultSize = (int)$size; | |
2377 | if ($size < 10240) { | |
2378 | $this->splitDefaultSize = 10240; // Making the file smaller than 10k is not a good idea. | |
2379 | } | |
2380 | } | |
2381 | ||
2382 | /** | |
2383 | * Get the chapter target size. | |
2384 | * | |
2385 | * @return $size | |
2386 | */ | |
2387 | function getSplitSize() { | |
2388 | return $this->splitDefaultSize; | |
2389 | } | |
2390 | ||
2391 | /** | |
2392 | * Remove all non essential html tags and entities. | |
2393 | * | |
2394 | * @global type $htmlEntities | |
2395 | * @param string $string | |
2396 | * @return string with the stripped entities. | |
2397 | */ | |
2398 | function decodeHtmlEntities($string) { | |
2399 | global $htmlEntities; | |
2400 | ||
2401 | $string = preg_replace('~\s*<br\s*/*\s*>\s*~i', "\n", $string); | |
2402 | $string = preg_replace('~\s*</(p|div)\s*>\s*~i', "\n\n", $string); | |
2403 | $string = preg_replace('~<[^>]*>~', '', $string); | |
2404 | ||
2405 | $string = strtr($string, $htmlEntities); | |
2406 | ||
2407 | $string = str_replace('&', '&', $string); | |
2408 | $string = str_replace('&amp;', '&', $string); | |
2409 | $string = preg_replace('~&(#x*[a-fA-F0-9]+;)~', '&\1', $string); | |
2410 | $string = str_replace('<', '<', $string); | |
2411 | $string = str_replace('>', '>', $string); | |
2412 | ||
2413 | return $string; | |
2414 | } | |
2415 | ||
2416 | /** | |
2417 | * Simply remove all HTML tags, brute force and no finesse. | |
2418 | * | |
2419 | * @param string $string html | |
2420 | * @return string | |
2421 | */ | |
2422 | function html2text($string) { | |
2423 | return preg_replace('~<[^>]*>~', '', $string); | |
2424 | } | |
2425 | ||
2426 | /** | |
2427 | * @return string | |
2428 | */ | |
2429 | function getLog() { | |
2430 | return $this->log->getLog(); | |
2431 | } | |
2432 | } |