mirror of
https://github.com/wallabag/wallabag.git
synced 2024-11-18 15:11:15 +00:00
2438 lines
89 KiB
PHP
2438 lines
89 KiB
PHP
<?php
|
|
/**
|
|
* Create an ePub compatible book file.
|
|
*
|
|
* Please note, once finalized a book can no longer have chapters of data added or changed.
|
|
*
|
|
* License: GNU LGPL, Attribution required for commercial implementations, requested for everything else.
|
|
*
|
|
* Thanks to: Adam Schmalhofer and Kirstyn Fox for invaluable input and for "nudging" me in the right direction :)
|
|
*
|
|
* @author A. Grandt <php@grandt.com>
|
|
* @copyright 2009-2014 A. Grandt
|
|
* @license GNU LGPL 2.1
|
|
* @version 3.20
|
|
* @link http://www.phpclasses.org/package/6115
|
|
* @link https://github.com/Grandt/PHPePub
|
|
* @uses Zip.php version 1.50; http://www.phpclasses.org/browse/package/6110.html or https://github.com/Grandt/PHPZip
|
|
*/
|
|
class EPub {
|
|
const VERSION = 3.20;
|
|
const REQ_ZIP_VERSION = 1.60;
|
|
|
|
const IDENTIFIER_UUID = 'UUID';
|
|
const IDENTIFIER_URI = 'URI';
|
|
const IDENTIFIER_ISBN = 'ISBN';
|
|
|
|
/** Ignore all external references, and do not process the file for these */
|
|
const EXTERNAL_REF_IGNORE = 0;
|
|
/** Process the file for external references and add them to the book */
|
|
const EXTERNAL_REF_ADD = 1;
|
|
/** Process the file for external references and add them to the book, but remove images, and img tags */
|
|
const EXTERNAL_REF_REMOVE_IMAGES = 2;
|
|
/** Process the file for external references and add them to the book, but replace images, and img tags with [image] */
|
|
const EXTERNAL_REF_REPLACE_IMAGES = 3;
|
|
|
|
const DIRECTION_LEFT_TO_RIGHT = "ltr";
|
|
const DIRECTION_RIGHT_TO_LEFT = "rtl";
|
|
|
|
const BOOK_VERSION_EPUB2 = "2.0";
|
|
const BOOK_VERSION_EPUB3 = "3.0";
|
|
|
|
private $bookVersion = EPub::BOOK_VERSION_EPUB2;
|
|
|
|
private $debugInside = FALSE;
|
|
|
|
public $maxImageWidth = 768;
|
|
public $maxImageHeight = 1024;
|
|
|
|
public $splitDefaultSize = 250000;
|
|
/** Gifs can crash some early ADE based readers, and are disabled by default.
|
|
* getImage will convert these if it can, unless this is set to TRUE.
|
|
*/
|
|
public $isGifImagesEnabled = FALSE;
|
|
public $isReferencesAddedToToc = TRUE;
|
|
|
|
private $zip;
|
|
|
|
private $title = "";
|
|
private $language = "en";
|
|
private $identifier = "";
|
|
private $identifierType = "";
|
|
private $description = "";
|
|
private $author = "";
|
|
private $authorSortKey = "";
|
|
private $publisherName = "";
|
|
private $publisherURL = "";
|
|
private $date = 0;
|
|
private $rights = "";
|
|
private $coverage = "";
|
|
private $relation = "";
|
|
private $sourceURL = "";
|
|
|
|
private $chapterCount = 0;
|
|
private $opf = NULL;
|
|
private $ncx = NULL;
|
|
private $isFinalized = FALSE;
|
|
private $isCoverImageSet = FALSE;
|
|
private $buildTOC = FALSE;
|
|
private $tocTitle = NULL;
|
|
private $tocFileName = NULL;
|
|
private $tocCSSClass = NULL;
|
|
private $tocAddReferences = FALSE;
|
|
private $tocCssFileName = NULL;
|
|
|
|
private $fileList = array();
|
|
private $writingDirection = EPub::DIRECTION_LEFT_TO_RIGHT;
|
|
private $languageCode = "en";
|
|
|
|
/**
|
|
* Used for building the TOC.
|
|
* If this list is overwritten it MUST contain at least "text" as an element.
|
|
*/
|
|
public $referencesOrder = NULL;
|
|
|
|
private $dateformat = 'Y-m-d\TH:i:s.000000P'; // ISO 8601 long
|
|
private $dateformatShort = 'Y-m-d'; // short date format to placate ePubChecker.
|
|
private $headerDateFormat = "D, d M Y H:i:s T";
|
|
|
|
protected $isCurlInstalled;
|
|
protected $isGdInstalled;
|
|
protected $isExifInstalled;
|
|
protected $isFileGetContentsInstalled;
|
|
protected $isFileGetContentsExtInstalled;
|
|
|
|
private $bookRoot = "OEBPS/";
|
|
private $docRoot = NULL;
|
|
private $EPubMark = TRUE;
|
|
private $generator = "";
|
|
|
|
private $log = NULL;
|
|
public $isLogging = TRUE;
|
|
|
|
public $encodeHTML = FALSE;
|
|
|
|
private $mimetypes = array(
|
|
"js" => "application/x-javascript", "swf" => "application/x-shockwave-flash", "xht" => "application/xhtml+xml", "xhtml" => "application/xhtml+xml", "zip" => "application/zip",
|
|
"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",
|
|
"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",
|
|
"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",
|
|
"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");
|
|
|
|
// 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.
|
|
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");
|
|
|
|
private $opsContentTypes = array("application/xhtml+xml", "application/x-dtbook+xml", "application/xml", "application/x-dtbncx+xml", "text/x-oeb1-document");
|
|
|
|
private $forbiddenCharacters = array("?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}", "%");
|
|
|
|
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";
|
|
private $htmlContentFooter = "</body>\n</html>\n";
|
|
|
|
/**
|
|
* Class constructor.
|
|
*
|
|
* @return void
|
|
*/
|
|
function __construct($bookVersion = EPub::BOOK_VERSION_EPUB2, $debugInside = FALSE, $languageCode = "en", $writingDirection = EPub::DIRECTION_LEFT_TO_RIGHT) {
|
|
include_once("Zip.php");
|
|
include_once("Logger.php");
|
|
|
|
if (!$debugInside) {
|
|
error_reporting(E_ERROR | E_PARSE);
|
|
}
|
|
|
|
$this->bookVersion = $bookVersion;
|
|
$this->writingDirection = $writingDirection;
|
|
$this->languageCode = $languageCode;
|
|
|
|
$this->log = new Logger("EPub", $this->isLogging);
|
|
|
|
/* Prepare Logging. Just in case it's used. later */
|
|
if ($this->isLogging) {
|
|
$this->log->logLine("EPub class version....: " . self::VERSION);
|
|
$this->log->logLine("EPub req. Zip version.: " . self::REQ_ZIP_VERSION);
|
|
$this->log->logLine("Zip version...........: " . Zip::VERSION);
|
|
$this->log->dumpInstalledModules();
|
|
}
|
|
|
|
if (!defined("Zip::VERSION") || Zip::VERSION < self::REQ_ZIP_VERSION) {
|
|
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>");
|
|
}
|
|
|
|
include_once("EPubChapterSplitter.php");
|
|
include_once("EPub.HtmlEntities.php");
|
|
include_once("EPub.NCX.php");
|
|
include_once("EPub.OPF.php");
|
|
|
|
$this->initialize();
|
|
}
|
|
|
|
/**
|
|
* Class destructor
|
|
*
|
|
* @return void
|
|
* @TODO make sure elements in the destructor match the current class elements
|
|
*/
|
|
function __destruct() {
|
|
unset($this->bookVersion, $this->maxImageWidth, $this->maxImageHeight);
|
|
unset($this->splitDefaultSize, $this->isGifImagesEnabled, $this->isReferencesAddedToToc);
|
|
unset($this->zip, $this->title, $this->language, $this->identifier, $this->identifierType);
|
|
unset($this->description, $this->author, $this->authorSortKey, $this->publisherName);
|
|
unset($this->publisherURL, $this->date, $this->rights, $this->coverage, $this->relation);
|
|
unset($this->sourceURL, $this->chapterCount, $this->opf, $this->ncx, $this->isFinalized);
|
|
unset($this->isCoverImageSet, $this->fileList, $this->writingDirection, $this->languageCode);
|
|
unset($this->referencesOrder, $this->dateformat, $this->dateformatShort, $this->headerDateFormat);
|
|
unset($this->isCurlInstalled, $this->isGdInstalled, $this->isExifInstalled);
|
|
unset($this->isFileGetContentsInstalled, $this->isFileGetContentsExtInstalled, $this->bookRoot);
|
|
unset($this->docRoot, $this->EPubMark, $this->generator, $this->log, $this->isLogging);
|
|
unset($this->encodeHTML, $this->mimetypes, $this->coreMediaTypes, $this->opsContentTypes);
|
|
unset($this->forbiddenCharacters, $this->htmlContentHeader, $this->htmlContentFooter);
|
|
unset($this->buildTOC, $this->tocTitle, $this->tocCSSClass, $this->tocAddReferences);
|
|
unset($this->tocFileName, $this->tocCssFileName);
|
|
}
|
|
|
|
/**
|
|
* initialize defaults.
|
|
*/
|
|
private function initialize() {
|
|
$this->referencesOrder = array(
|
|
Reference::COVER => "Cover Page",
|
|
Reference::TITLE_PAGE => "Title Page",
|
|
Reference::ACKNOWLEDGEMENTS => "Acknowledgements",
|
|
Reference::BIBLIOGRAPHY => "Bibliography",
|
|
Reference::COLOPHON => "Colophon",
|
|
Reference::COPYRIGHT_PAGE => "Copyright",
|
|
Reference::DEDICATION => "Dedication",
|
|
Reference::EPIGRAPH => "Epigraph",
|
|
Reference::FOREWORD => "Foreword",
|
|
Reference::TABLE_OF_CONTENTS => "Table of Contents",
|
|
Reference::NOTES => "Notes",
|
|
Reference::PREFACE => "Preface",
|
|
Reference::TEXT => "First Page",
|
|
Reference::LIST_OF_ILLUSTRATIONS => "List of Illustrations",
|
|
Reference::LIST_OF_TABLES => "List of Tables",
|
|
Reference::GLOSSARY => "Glossary",
|
|
Reference::INDEX => "Index");
|
|
|
|
$this->docRoot = filter_input(INPUT_SERVER, "DOCUMENT_ROOT") . "/";
|
|
|
|
$this->isCurlInstalled = extension_loaded('curl') && function_exists('curl_version');
|
|
$this->isGdInstalled = extension_loaded('gd') && function_exists('gd_info');
|
|
$this->isExifInstalled = extension_loaded('exif') && function_exists('exif_imagetype');
|
|
$this->isFileGetContentsInstalled = function_exists('file_get_contents');
|
|
$this->isFileGetContentsExtInstalled = $this->isFileGetContentsInstalled && ini_get('allow_url_fopen');
|
|
|
|
$this->zip = new Zip();
|
|
$this->zip->setExtraField(FALSE);
|
|
$this->zip->addFile("application/epub+zip", "mimetype");
|
|
$this->zip->setExtraField(TRUE);
|
|
$this->zip->addDirectory("META-INF");
|
|
|
|
$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";
|
|
|
|
if (!$this->isEPubVersion2()) {
|
|
$this->htmlContentHeader = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
|
. "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n"
|
|
. "<head>"
|
|
. "<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n"
|
|
. "<title></title>\n"
|
|
. "</head>\n"
|
|
. "<body>\n";
|
|
}
|
|
|
|
$this->zip->addFile($this->content, "META-INF/container.xml", 0, NULL, FALSE);
|
|
$this->content = NULL;
|
|
$this->ncx = new Ncx(NULL, NULL, NULL, $this->languageCode, $this->writingDirection);
|
|
$this->opf = new Opf();
|
|
$this->ncx->setVersion($this->bookVersion);
|
|
$this->opf->setVersion($this->bookVersion);
|
|
$this->opf->addItem("ncx", "book.ncx", Ncx::MIMETYPE);
|
|
$this->chapterCount = 0;
|
|
}
|
|
|
|
/**
|
|
* Add dynamically generated data as a file to the book.
|
|
*
|
|
* @param string $fileName Filename to use for the file, must be unique for the book.
|
|
* @param string $fileId Unique identifier for the file.
|
|
* @param string $fileData File data
|
|
* @param string $mimetype file mime type
|
|
* @return bool $success
|
|
*/
|
|
function addFile($fileName, $fileId, $fileData, $mimetype) {
|
|
if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) {
|
|
return FALSE;
|
|
}
|
|
|
|
$fileName = $this->normalizeFileName($fileName);
|
|
|
|
$compress = (strpos($mimetype, "image/") !== 0);
|
|
|
|
$this->zip->addFile($fileData, $this->bookRoot.$fileName, 0, NULL, $compress);
|
|
$this->fileList[$fileName] = $fileName;
|
|
$this->opf->addItem($fileId, $fileName, $mimetype);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Add a large file directly from the filestystem to the book.
|
|
*
|
|
* @param string $fileName Filename to use for the file, must be unique for the book.
|
|
* @param string $fileId Unique identifier for the file.
|
|
* @param string $filePath File path
|
|
* @param string $mimetype file mime type
|
|
* @return bool $success
|
|
*/
|
|
function addLargeFile($fileName, $fileId, $filePath, $mimetype) {
|
|
if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) {
|
|
return FALSE;
|
|
}
|
|
$fileName = $this->normalizeFileName($fileName);
|
|
|
|
if ($this->zip->addLargeFile($filePath, $this->bookRoot.$fileName)) {
|
|
$this->fileList[$fileName] = $fileName;
|
|
$this->opf->addItem($fileId, $fileName, $mimetype);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Add a CSS file to the book.
|
|
*
|
|
* @param string $fileName Filename to use for the CSS file, must be unique for the book.
|
|
* @param string $fileId Unique identifier for the file.
|
|
* @param string $fileData CSS data
|
|
* @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.
|
|
* @param string $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE.
|
|
*
|
|
* @return bool $success
|
|
*/
|
|
function addCSSFile($fileName, $fileId, $fileData, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") {
|
|
if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) {
|
|
return FALSE;
|
|
}
|
|
$fileName = Zip::getRelativePath($fileName);
|
|
$fileName = preg_replace('#^[/\.]+#i', "", $fileName);
|
|
|
|
if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) {
|
|
$cssDir = pathinfo($fileName);
|
|
$cssDir = preg_replace('#^[/\.]+#i', "", $cssDir["dirname"] . "/");
|
|
if (!empty($cssDir)) {
|
|
$cssDir = preg_replace('#[^/]+/#i', "../", $cssDir);
|
|
}
|
|
|
|
$this->processCSSExternalReferences($fileData, $externalReferences, $baseDir, $cssDir);
|
|
}
|
|
|
|
$this->addFile($fileName, "css_" . $fileId, $fileData, "text/css");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Add a chapter to the book, as a chapter should not exceed 250kB, you can parse an array with multiple parts as $chapterData.
|
|
* These will still only show up as a single chapter in the book TOC.
|
|
*
|
|
* @param string $chapterName Name of the chapter, will be use din the TOC
|
|
* @param string $fileName Filename to use for the chapter, must be unique for the book.
|
|
* @param string $chapter Chapter text in XHTML or array $chapterData valid XHTML data for the chapter. File should NOT exceed 250kB.
|
|
* @param bool $autoSplit Should the chapter be split if it exceeds the default split size? Default=FALSE, only used if $chapterData is a string.
|
|
* @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.
|
|
* @param string $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE.
|
|
* @return mixed $success FALSE if the addition failed, else the new NavPoint.
|
|
*/
|
|
function addChapter($chapterName, $fileName, $chapterData = NULL, $autoSplit = FALSE, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$fileName = Zip::getRelativePath($fileName);
|
|
$fileName = preg_replace('#^[/\.]+#i', "", $fileName);
|
|
$fileName = $this->sanitizeFileName($fileName);
|
|
|
|
$chapter = $chapterData;
|
|
if ($autoSplit && is_string($chapterData) && mb_strlen($chapterData) > $this->splitDefaultSize) {
|
|
$splitter = new EPubChapterSplitter();
|
|
|
|
$chapterArray = $splitter->splitChapter($chapterData);
|
|
if (count($chapterArray) > 1) {
|
|
$chapter = $chapterArray;
|
|
}
|
|
}
|
|
|
|
if (!empty($chapter) && is_string($chapter)) {
|
|
if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) {
|
|
$htmlDirInfo = pathinfo($fileName);
|
|
$htmlDir = preg_replace('#^[/\.]+#i', "", $htmlDirInfo["dirname"] . "/");
|
|
$this->processChapterExternalReferences($chapter, $externalReferences, $baseDir, $htmlDir);
|
|
}
|
|
|
|
if ($this->encodeHTML === TRUE) {
|
|
$chapter = $this->encodeHtml($chapter);
|
|
}
|
|
|
|
$this->chapterCount++;
|
|
$this->addFile($fileName, "chapter" . $this->chapterCount, $chapter, "application/xhtml+xml");
|
|
$this->opf->addItemRef("chapter" . $this->chapterCount);
|
|
|
|
$navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $fileName, "chapter" . $this->chapterCount);
|
|
$this->ncx->addNavPoint($navPoint);
|
|
$this->ncx->chapterList[$chapterName] = $navPoint;
|
|
} else if (is_array($chapter)) {
|
|
$fileNameParts = pathinfo($fileName);
|
|
$extension = $fileNameParts['extension'];
|
|
$name = $fileNameParts['filename'];
|
|
|
|
$partCount = 0;
|
|
$this->chapterCount++;
|
|
|
|
$oneChapter = each($chapter);
|
|
while ($oneChapter) {
|
|
list($k, $v) = $oneChapter;
|
|
if ($this->encodeHTML === TRUE) {
|
|
$v = $this->encodeHtml($v);
|
|
}
|
|
|
|
if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) {
|
|
$this->processChapterExternalReferences($v, $externalReferences, $baseDir);
|
|
}
|
|
$partCount++;
|
|
$partName = $name . "_" . $partCount;
|
|
$this->addFile($partName . "." . $extension, $partName, $v, "application/xhtml+xml");
|
|
$this->opf->addItemRef($partName);
|
|
|
|
$oneChapter = each($chapter);
|
|
}
|
|
$partName = $name . "_1." . $extension;
|
|
$navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $partName, $partName);
|
|
$this->ncx->addNavPoint($navPoint);
|
|
|
|
$this->ncx->chapterList[$chapterName] = $navPoint;
|
|
} else if (!isset($chapterData) && strpos($fileName, "#") > 0) {
|
|
$this->chapterCount++;
|
|
//$this->opf->addItemRef("chapter" . $this->chapterCount);
|
|
|
|
$navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $fileName, "chapter" . $this->chapterCount);
|
|
$this->ncx->addNavPoint($navPoint);
|
|
$this->ncx->chapterList[$chapterName] = $navPoint;
|
|
} else if (!isset($chapterData) && $fileName=="TOC.xhtml") {
|
|
$this->chapterCount++;
|
|
$this->opf->addItemRef("toc");
|
|
|
|
$navPoint = new NavPoint($this->decodeHtmlEntities($chapterName), $fileName, "chapter" . $this->chapterCount);
|
|
$this->ncx->addNavPoint($navPoint);
|
|
$this->ncx->chapterList[$chapterName] = $navPoint;
|
|
}
|
|
return $navPoint;
|
|
}
|
|
|
|
/**
|
|
* Add one chapter level.
|
|
*
|
|
* Subsequent chapters will be added to this level.
|
|
*
|
|
* @param string $navTitle
|
|
* @param string $navId
|
|
* @param string $navClass
|
|
* @param int $isNavHidden
|
|
* @param string $writingDirection
|
|
* @return NavPoint The new NavPoint for that level.
|
|
*/
|
|
function subLevel($navTitle = NULL, $navId = NULL, $navClass = NULL, $isNavHidden = FALSE, $writingDirection = NULL) {
|
|
return $this->ncx->subLevel($this->decodeHtmlEntities($navTitle), $navId, $navClass, $isNavHidden, $writingDirection);
|
|
}
|
|
|
|
/**
|
|
* Step back one chapter level.
|
|
*
|
|
* Subsequent chapters will be added to this chapters parent level.
|
|
*/
|
|
function backLevel() {
|
|
$this->ncx->backLevel();
|
|
}
|
|
|
|
/**
|
|
* Step back to the root level.
|
|
*
|
|
* Subsequent chapters will be added to the rooot NavMap.
|
|
*/
|
|
function rootLevel() {
|
|
$this->ncx->rootLevel();
|
|
}
|
|
|
|
/**
|
|
* Step back to the given level.
|
|
* Useful for returning to a previous level from deep within the structure.
|
|
* Values below 2 will have the same effect as rootLevel()
|
|
*
|
|
* @param int $newLevel
|
|
*/
|
|
function setCurrentLevel($newLevel) {
|
|
$this->ncx->setCurrentLevel($newLevel);
|
|
}
|
|
|
|
/**
|
|
* Get current level count.
|
|
* The indentation of the current structure point.
|
|
*
|
|
* @return current level count;
|
|
*/
|
|
function getCurrentLevel() {
|
|
return $this->ncx->getCurrentLevel();
|
|
}
|
|
|
|
/**
|
|
* Wrap ChapterContent with Head and Footer
|
|
*
|
|
* @param $content
|
|
* @return string $content
|
|
*/
|
|
private function wrapChapter($content) {
|
|
return $this->htmlContentHeader . "\n" . $content . "\n" . $this->htmlContentFooter;
|
|
}
|
|
|
|
/**
|
|
* Reference pages is usually one or two pages for items such as Table of Contents, reference lists, Author notes or Acknowledgements.
|
|
* These do not show up in the regular navigation list.
|
|
*
|
|
* As they are supposed to be short.
|
|
*
|
|
* @param string $pageName Name of the chapter, will be use din the TOC
|
|
* @param string $fileName Filename to use for the chapter, must be unique for the book.
|
|
* @param string $pageData Page content in XHTML. File should NOT exceed 250kB.
|
|
* @param string $reference Reference key
|
|
* @param int $externalReferences How to handle external references. See documentation for <code>processChapterExternalReferences</code> for explanation. Default is EPub::EXTERNAL_REF_IGNORE.
|
|
* @param string $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE.
|
|
* @return bool $success
|
|
*/
|
|
function addReferencePage($pageName, $fileName, $pageData, $reference, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$fileName = Zip::getRelativePath($fileName);
|
|
$fileName = preg_replace('#^[/\.]+#i', "", $fileName);
|
|
|
|
|
|
if (!empty($pageData) && is_string($pageData)) {
|
|
if ($this->encodeHTML === TRUE) {
|
|
$pageData = $this->encodeHtml($pageData);
|
|
}
|
|
|
|
$this->wrapChapter($pageData);
|
|
|
|
if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) {
|
|
$htmlDirInfo = pathinfo($fileName);
|
|
$htmlDir = preg_replace('#^[/\.]+#i', "", $htmlDirInfo["dirname"] . "/");
|
|
$this->processChapterExternalReferences($pageData, $externalReferences, $baseDir, $htmlDir);
|
|
}
|
|
|
|
$this->addFile($fileName, "ref_" . $reference, $pageData, "application/xhtml+xml");
|
|
|
|
if ($reference !== Reference::TABLE_OF_CONTENTS || !isset($this->ncx->referencesList[$reference])) {
|
|
$this->opf->addItemRef("ref_" . $reference, FALSE);
|
|
$this->opf->addReference($reference, $pageName, $fileName);
|
|
|
|
$this->ncx->referencesList[$reference] = $fileName;
|
|
$this->ncx->referencesName[$reference] = $pageName;
|
|
}
|
|
return TRUE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Add custom metadata to the book.
|
|
*
|
|
* It is up to the builder to make sure there are no collisions. Metadata are just key value pairs.
|
|
*
|
|
* @param string $name
|
|
* @param string $content
|
|
*/
|
|
function addCustomMetadata($name, $content) {
|
|
$this->opf->addMeta($name, $content);
|
|
}
|
|
|
|
/**
|
|
* Add DublinCore metadata to the book
|
|
*
|
|
* Use the DublinCore constants included in EPub, ie DublinCore::DATE
|
|
*
|
|
* @param string $dublinCore name
|
|
* @param string $value
|
|
*/
|
|
function addDublinCoreMetadata($dublinCoreConstant, $value) {
|
|
if ($this->isFinalized) {
|
|
return;
|
|
}
|
|
|
|
$this->opf->addDCMeta($dublinCoreConstant, $this->decodeHtmlEntities($value));
|
|
}
|
|
|
|
/**
|
|
* Add a cover image to the book.
|
|
* If the $imageData is not set, the function assumes the $fileName is the path to the image file.
|
|
*
|
|
* The styling and structure of the generated XHTML is heavily inspired by the XHTML generated by Calibre.
|
|
*
|
|
* @param string $fileName Filename to use for the image, must be unique for the book.
|
|
* @param string $imageData Binary image data
|
|
* @param string $mimetype Image mimetype, such as "image/jpeg" or "image/png".
|
|
* @return bool $success
|
|
*/
|
|
function setCoverImage($fileName, $imageData = NULL, $mimetype = NULL,$bookTitle) {
|
|
if ($this->isFinalized || $this->isCoverImageSet || array_key_exists("CoverPage.html", $this->fileList)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if ($imageData == NULL) {
|
|
// assume $fileName is the valid file path.
|
|
if (!file_exists($fileName)) {
|
|
// Attempt to locate the file using the doc root.
|
|
$rp = realpath($this->docRoot . "/" . $fileName);
|
|
|
|
if ($rp !== FALSE) {
|
|
// only assign the docroot path if it actually exists there.
|
|
$fileName = $rp;
|
|
}
|
|
}
|
|
$image = $this->getImage($fileName);
|
|
$imageData = $image['image'];
|
|
$mimetype = $image['mime'];
|
|
$fileName = preg_replace("#\.[^\.]+$#", "." . $image['ext'], $fileName);
|
|
}
|
|
|
|
|
|
$path = pathinfo($fileName);
|
|
$imgPath = "images/" . $path["basename"];
|
|
|
|
if (empty($mimetype) && file_exists($fileName)) {
|
|
list($width, $height, $type, $attr) = getimagesize($fileName);
|
|
$mimetype = image_type_to_mime_type($type);
|
|
}
|
|
if (empty($mimetype)) {
|
|
$ext = strtolower($path['extension']);
|
|
if ($ext == "jpg") {
|
|
$ext = "jpeg";
|
|
}
|
|
$mimetype = "image/" . $ext;
|
|
}
|
|
|
|
$coverPage = "";
|
|
|
|
if ($this->isEPubVersion2()) {
|
|
$coverPage = "<?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\" xmlns:epub=\"http://www.idpf.org/2007/ops\" xml:lang=\"en\">\n"
|
|
. "\t<head>\n"
|
|
. "\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n"
|
|
. "\t\t<title>Cover Image</title>\n"
|
|
. "\t\t<link type=\"text/css\" rel=\"stylesheet\" href=\"Styles/CoverPage.css\" />\n"
|
|
. "\t</head>\n"
|
|
. "\t<body>\n"
|
|
. "\t" . $bookTitle . "\n"
|
|
. "\t\t<div>\n"
|
|
. "\t\t\t<img src=\"" . $imgPath . "\" alt=\"Cover image\" style=\"height: 100%\"/>\n"
|
|
. "\t\t</div>\n"
|
|
. "\t</body>\n"
|
|
. "</html>\n";
|
|
} else {
|
|
$coverPage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
|
. "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n"
|
|
. "<head>"
|
|
. "\t<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n"
|
|
. "\t\t<title>Cover Image</title>\n"
|
|
. "\t\t<link type=\"text/css\" rel=\"stylesheet\" href=\"Styles/CoverPage.css\" />\n"
|
|
. "\t</head>\n"
|
|
. "\t<body>\n"
|
|
. "\t\t<section epub:type=\"cover\">\n"
|
|
. "\t" . $bookTitle . "\n"
|
|
. "\t\t\t<img src=\"" . $imgPath . "\" alt=\"Cover image\" style=\"height: 30%\"/>\n"
|
|
. "\t\t</section>\n"
|
|
. "\t</body>\n"
|
|
. "</html>\n";
|
|
}
|
|
$coverPageCss = "@page, body, div, img {\n"
|
|
. "\tpadding: 0pt;\n"
|
|
. "\tmargin:0pt;\n"
|
|
. "}\n\nbody {\n"
|
|
. "\ttext-align: center;\n"
|
|
. "}\n";
|
|
|
|
$this->addCSSFile("Styles/CoverPage.css", "CoverPageCss", $coverPageCss);
|
|
$this->addFile($imgPath, "CoverImage", $imageData, $mimetype);
|
|
$this->addReferencePage("CoverPage", "CoverPage.xhtml", $coverPage, "cover");
|
|
$this->isCoverImageSet = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Process external references from a HTML to the book. The chapter itself is not stored.
|
|
* the HTML is scanned for <link..., <style..., and <img tags.
|
|
* Embedded CSS styles and links will also be processed.
|
|
* Script tags are not processed, as scripting should be avoided in e-books.
|
|
*
|
|
* EPub keeps track of added files, and duplicate files referenced across multiple
|
|
* chapters, are only added once.
|
|
*
|
|
* If the $doc is a string, it is assumed to be the content of an HTML file,
|
|
* else is it assumes to be a DOMDocument.
|
|
*
|
|
* Basedir is the root dir the HTML is supposed to "live" in, used to resolve
|
|
* relative references such as <code><img src="../images/image.png"/></code>
|
|
*
|
|
* $externalReferences determines how the function will handle external references.
|
|
*
|
|
* @param mixed &$doc (referenced)
|
|
* @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
|
|
* @param string $baseDir Default is "", meaning it is pointing to the document root.
|
|
* @param string $htmlDir The path to the parent HTML file's directory from the root of the archive.
|
|
*
|
|
* @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
|
|
*/
|
|
protected function processChapterExternalReferences(&$doc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "") {
|
|
if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) {
|
|
return FALSE;
|
|
}
|
|
|
|
$backPath = preg_replace('#[^/]+/#i', "../", $htmlDir);
|
|
$isDocAString = is_string($doc);
|
|
$xmlDoc = NULL;
|
|
|
|
if ($isDocAString) {
|
|
$xmlDoc = new DOMDocument();
|
|
@$xmlDoc->loadHTML($doc);
|
|
} else {
|
|
$xmlDoc = $doc;
|
|
}
|
|
|
|
$this->processChapterStyles($xmlDoc, $externalReferences, $baseDir, $htmlDir);
|
|
$this->processChapterLinks($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath);
|
|
$this->processChapterImages($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath);
|
|
$this->processChapterSources($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath);
|
|
|
|
if ($isDocAString) {
|
|
//$html = $xmlDoc->saveXML();
|
|
|
|
$htmlNode = $xmlDoc->getElementsByTagName("html");
|
|
$headNode = $xmlDoc->getElementsByTagName("head");
|
|
$bodyNode = $xmlDoc->getElementsByTagName("body");
|
|
|
|
$htmlNS = "";
|
|
for ($index = 0; $index < $htmlNode->item(0)->attributes->length; $index++) {
|
|
$nodeName = $htmlNode->item(0)->attributes->item($index)->nodeName;
|
|
$nodeValue = $htmlNode->item(0)->attributes->item($index)->nodeValue;
|
|
|
|
if ($nodeName != "xmlns") {
|
|
$htmlNS .= " $nodeName=\"$nodeValue\"";
|
|
}
|
|
}
|
|
|
|
$xml = new DOMDocument('1.0', "utf-8");
|
|
$xml->lookupPrefix("http://www.w3.org/1999/xhtml");
|
|
$xml->preserveWhiteSpace = FALSE;
|
|
$xml->formatOutput = TRUE;
|
|
|
|
$xml2Doc = new DOMDocument('1.0', "utf-8");
|
|
$xml2Doc->lookupPrefix("http://www.w3.org/1999/xhtml");
|
|
$xml2Doc->loadXML("<?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");
|
|
$html = $xml2Doc->getElementsByTagName("html")->item(0);
|
|
$html->appendChild($xml2Doc->importNode($headNode->item(0), TRUE));
|
|
$html->appendChild($xml2Doc->importNode($bodyNode->item(0), TRUE));
|
|
|
|
// force pretty printing and correct formatting, should not be needed, but it is.
|
|
$xml->loadXML($xml2Doc->saveXML());
|
|
$doc = $xml->saveXML();
|
|
|
|
if (!$this->isEPubVersion2()) {
|
|
$doc = preg_replace('#^\s*<!DOCTYPE\ .+?>\s*#im', '', $doc);
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Process images referenced from an CSS file to the book.
|
|
*
|
|
* $externalReferences determins how the function will handle external references.
|
|
*
|
|
* @param string &$cssFile (referenced)
|
|
* @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
|
|
* @param string $baseDir Default is "", meaning it is pointing to the document root.
|
|
* @param string $cssDir The of the CSS file's directory from the root of the archive.
|
|
*
|
|
* @return bool FALSE if unsuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
|
|
*/
|
|
protected function processCSSExternalReferences(&$cssFile, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $cssDir = "") {
|
|
if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) {
|
|
return FALSE;
|
|
}
|
|
|
|
$backPath = preg_replace('#[^/]+/#i', "../", $cssDir);
|
|
$imgs = null;
|
|
preg_match_all('#url\s*\([\'\"\s]*(.+?)[\'\"\s]*\)#im', $cssFile, $imgs, PREG_SET_ORDER);
|
|
|
|
$itemCount = count($imgs);
|
|
for ($idx = 0; $idx < $itemCount; $idx++) {
|
|
$img = $imgs[$idx];
|
|
if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES || $externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) {
|
|
$cssFile = str_replace($img[0], "", $cssFile);
|
|
} else {
|
|
$source = $img[1];
|
|
|
|
$pathData = pathinfo($source);
|
|
$internalSrc = $pathData['basename'];
|
|
$internalPath = "";
|
|
$isSourceExternal = FALSE;
|
|
|
|
if ($this->resolveImage($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $cssDir, $backPath)) {
|
|
$cssFile = str_replace($img[0], "url('" . $backPath . $internalPath . "')", $cssFile);
|
|
} else if ($isSourceExternal) {
|
|
$cssFile = str_replace($img[0], "", $cssFile); // External image is missing
|
|
} // else do nothing, if the image is local, and missing, assume it's been generated.
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Process style tags in a DOMDocument. Styles will be passed as CSS files and reinserted into the document.
|
|
*
|
|
* @param DOMDocument &$xmlDoc (referenced)
|
|
* @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
|
|
* @param string $baseDir Default is "", meaning it is pointing to the document root.
|
|
* @param string $htmlDir The path to the parent HTML file's directory from the root of the archive.
|
|
*
|
|
* @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
|
|
*/
|
|
protected function processChapterStyles(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "") {
|
|
if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) {
|
|
return FALSE;
|
|
}
|
|
// process inlined CSS styles in style tags.
|
|
$styles = $xmlDoc->getElementsByTagName("style");
|
|
$styleCount = $styles->length;
|
|
for ($styleIdx = 0; $styleIdx < $styleCount; $styleIdx++) {
|
|
$style = $styles->item($styleIdx);
|
|
|
|
$styleData = preg_replace('#[/\*\s]*\<\!\[CDATA\[[\s\*/]*#im', "", $style->nodeValue);
|
|
$styleData = preg_replace('#[/\*\s]*\]\]\>[\s\*/]*#im', "", $styleData);
|
|
|
|
$this->processCSSExternalReferences($styleData, $externalReferences, $baseDir, $htmlDir);
|
|
$style->nodeValue = "\n" . trim($styleData) . "\n";
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Process link tags in a DOMDocument. Linked files will be loaded into the archive, and the link src will be rewritten to point to that location.
|
|
* Link types text/css will be passed as CSS files.
|
|
*
|
|
* @param DOMDocument &$xmlDoc (referenced)
|
|
* @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
|
|
* @param string $baseDir Default is "", meaning it is pointing to the document root.
|
|
* @param string $htmlDir The path to the parent HTML file's directory from the root of the archive.
|
|
* @param string $backPath The path to get back to the root of the archive from $htmlDir.
|
|
*
|
|
* @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
|
|
*/
|
|
protected function processChapterLinks(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", $backPath = "") {
|
|
if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) {
|
|
return FALSE;
|
|
}
|
|
// process link tags.
|
|
$links = $xmlDoc->getElementsByTagName("link");
|
|
$linkCount = $links->length;
|
|
for ($linkIdx = 0; $linkIdx < $linkCount; $linkIdx++) {
|
|
$link = $links->item($linkIdx);
|
|
$source = $link->attributes->getNamedItem("href")->nodeValue;
|
|
$sourceData = NULL;
|
|
|
|
$pathData = pathinfo($source);
|
|
$internalSrc = $pathData['basename'];
|
|
|
|
if (preg_match('#^(http|ftp)s?://#i', $source) == 1) {
|
|
$urlinfo = parse_url($source);
|
|
|
|
if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) {
|
|
$internalSrc = substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") + strlen($baseDir) + 1);
|
|
}
|
|
|
|
@$sourceData = getFileContents($source);
|
|
} else if (strpos($source, "/") === 0) {
|
|
@$sourceData = file_get_contents($this->docRoot . $source);
|
|
} else {
|
|
@$sourceData = file_get_contents($this->docRoot . $baseDir . "/" . $source);
|
|
}
|
|
|
|
if (!empty($sourceData)) {
|
|
if (!array_key_exists($internalSrc, $this->fileList)) {
|
|
$mime = $link->attributes->getNamedItem("type")->nodeValue;
|
|
if (empty($mime)) {
|
|
$mime = "text/plain";
|
|
}
|
|
if ($mime == "text/css") {
|
|
$this->processCSSExternalReferences($sourceData, $externalReferences, $baseDir, $htmlDir);
|
|
$this->addCSSFile($internalSrc, $internalSrc, $sourceData, EPub::EXTERNAL_REF_IGNORE, $baseDir);
|
|
$link->setAttribute("href", $backPath . $internalSrc);
|
|
} else {
|
|
$this->addFile($internalSrc, $internalSrc, $sourceData, $mime);
|
|
}
|
|
$this->fileList[$internalSrc] = $source;
|
|
} else {
|
|
$link->setAttribute("href", $backPath . $internalSrc);
|
|
}
|
|
} // else do nothing, if the link is local, and missing, assume it's been generated.
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Process img tags in a DOMDocument.
|
|
* $externalReferences will determine what will happen to these images, and the img src will be rewritten accordingly.
|
|
*
|
|
* @param DOMDocument &$xmlDoc (referenced)
|
|
* @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
|
|
* @param string $baseDir Default is "", meaning it is pointing to the document root.
|
|
* @param string $htmlDir The path to the parent HTML file's directory from the root of the archive.
|
|
* @param string $backPath The path to get back to the root of the archive from $htmlDir.
|
|
*
|
|
* @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
|
|
*/
|
|
protected function processChapterImages(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", $backPath = "") {
|
|
if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) {
|
|
return FALSE;
|
|
}
|
|
// process img tags.
|
|
$postProcDomElememts = array();
|
|
$images = $xmlDoc->getElementsByTagName("img");
|
|
$itemCount = $images->length;
|
|
|
|
for ($idx = 0; $idx < $itemCount; $idx++) {
|
|
$img = $images->item($idx);
|
|
|
|
if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES) {
|
|
$postProcDomElememts[] = $img;
|
|
} else if ($externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) {
|
|
$altNode = $img->attributes->getNamedItem("alt");
|
|
$alt = "image";
|
|
if ($altNode !== NULL && strlen($altNode->nodeValue) > 0) {
|
|
$alt = $altNode->nodeValue;
|
|
}
|
|
$postProcDomElememts[] = array($img, $this->createDomFragment($xmlDoc, "<em>[" . $alt . "]</em>"));
|
|
} else {
|
|
$source = $img->attributes->getNamedItem("src")->nodeValue;
|
|
|
|
$parsedSource = parse_url($source);
|
|
$internalSrc = $this->sanitizeFileName(urldecode(pathinfo($parsedSource['path'], PATHINFO_BASENAME)));
|
|
$internalPath = "";
|
|
$isSourceExternal = FALSE;
|
|
|
|
if ($this->resolveImage($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $htmlDir, $backPath)) {
|
|
$img->setAttribute("src", $backPath . $internalPath);
|
|
} else if ($isSourceExternal) {
|
|
$postProcDomElememts[] = $img; // External image is missing
|
|
} // else do nothing, if the image is local, and missing, assume it's been generated.
|
|
}
|
|
}
|
|
|
|
foreach ($postProcDomElememts as $target) {
|
|
if (is_array($target)) {
|
|
$target[0]->parentNode->replaceChild($target[1], $target[0]);
|
|
} else {
|
|
$target->parentNode->removeChild($target);
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Process source tags in a DOMDocument.
|
|
* $externalReferences will determine what will happen to these images, and the img src will be rewritten accordingly.
|
|
*
|
|
* @param DOMDocument &$xmlDoc (referenced)
|
|
* @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
|
|
* @param string $baseDir Default is "", meaning it is pointing to the document root.
|
|
* @param string $htmlDir The path to the parent HTML file's directory from the root of the archive.
|
|
* @param string $backPath The path to get back to the root of the archive from $htmlDir.
|
|
*
|
|
* @return bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
|
|
*/
|
|
protected function processChapterSources(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", $backPath = "") {
|
|
if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) {
|
|
return FALSE;
|
|
}
|
|
|
|
if ($this->bookVersion !== EPub::BOOK_VERSION_EPUB3) {
|
|
// ePub 2 does not support multimedia formats, and they must be removed.
|
|
$externalReferences = EPub::EXTERNAL_REF_REMOVE_IMAGES;
|
|
}
|
|
|
|
$postProcDomElememts = array();
|
|
$images = $xmlDoc->getElementsByTagName("source");
|
|
$itemCount = $images->length;
|
|
for ($idx = 0; $idx < $itemCount; $idx++) {
|
|
$img = $images->item($idx);
|
|
if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES) {
|
|
$postProcDomElememts[] = $img;
|
|
} else if ($externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) {
|
|
$altNode = $img->attributes->getNamedItem("alt");
|
|
$alt = "image";
|
|
if ($altNode !== NULL && strlen($altNode->nodeValue) > 0) {
|
|
$alt = $altNode->nodeValue;
|
|
}
|
|
$postProcDomElememts[] = array($img, $this->createDomFragment($xmlDoc, "[" . $alt . "]"));
|
|
} else {
|
|
$source = $img->attributes->getNamedItem("src")->nodeValue;
|
|
|
|
$parsedSource = parse_url($source);
|
|
$internalSrc = $this->sanitizeFileName(urldecode(pathinfo($parsedSource['path'], PATHINFO_BASENAME)));
|
|
$internalPath = "";
|
|
$isSourceExternal = FALSE;
|
|
|
|
if ($this->resolveMedia($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $htmlDir, $backPath)) {
|
|
$img->setAttribute("src", $backPath . $internalPath);
|
|
} else if ($isSourceExternal) {
|
|
$postProcDomElememts[] = $img; // External image is missing
|
|
} // else do nothing, if the image is local, and missing, assume it's been generated.
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve an image src and determine it's target location and add it to the book.
|
|
*
|
|
* @param string $source Image Source link.
|
|
* @param string &$internalPath (referenced) Return value, will be set to the target path and name in the book.
|
|
* @param string &$internalSrc (referenced) Return value, will be set to the target name in the book.
|
|
* @param string &$isSourceExternal (referenced) Return value, will be set to TRUE if the image originated from a full URL.
|
|
* @param string $baseDir Default is "", meaning it is pointing to the document root.
|
|
* @param string $htmlDir The path to the parent HTML file's directory from the root of the archive.
|
|
* @param string $backPath The path to get back to the root of the archive from $htmlDir.
|
|
*/
|
|
protected function resolveImage($source, &$internalPath, &$internalSrc, &$isSourceExternal, $baseDir = "", $htmlDir = "", $backPath = "") {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$imageData = NULL;
|
|
|
|
if (preg_match('#^(http|ftp)s?://#i', $source) == 1) {
|
|
$urlinfo = parse_url($source);
|
|
$urlPath = pathinfo($urlinfo['path']);
|
|
|
|
if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) {
|
|
$internalSrc = $this->sanitizeFileName(urldecode(substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") + strlen($baseDir) + 1)));
|
|
}
|
|
$internalPath = $urlinfo["scheme"] . "/" . $urlinfo["host"] . "/" . pathinfo($urlinfo["path"], PATHINFO_DIRNAME);
|
|
$isSourceExternal = TRUE;
|
|
$imageData = $this->getImage($source);
|
|
} else if (strpos($source, "/") === 0) {
|
|
$internalPath = pathinfo($source, PATHINFO_DIRNAME);
|
|
|
|
$path = $source;
|
|
if (!file_exists($path)) {
|
|
$path = $this->docRoot . $path;
|
|
}
|
|
|
|
$imageData = $this->getImage($path);
|
|
} else {
|
|
$internalPath = $htmlDir . "/" . preg_replace('#^[/\.]+#', '', pathinfo($source, PATHINFO_DIRNAME));
|
|
|
|
$path = $baseDir . "/" . $source;
|
|
if (!file_exists($path)) {
|
|
$path = $this->docRoot . $path;
|
|
}
|
|
|
|
$imageData = $this->getImage($path);
|
|
}
|
|
if ($imageData !== FALSE) {
|
|
$iSrcInfo = pathinfo($internalSrc);
|
|
if (!empty($imageData['ext']) && $imageData['ext'] != $iSrcInfo['extension']) {
|
|
$internalSrc = $iSrcInfo['filename'] . "." . $imageData['ext'];
|
|
}
|
|
$internalPath = Zip::getRelativePath("images/" . $internalPath . "/" . $internalSrc);
|
|
if (!array_key_exists($internalPath, $this->fileList)) {
|
|
$this->addFile($internalPath, "i_" . $internalSrc, $imageData['image'], $imageData['mime']);
|
|
$this->fileList[$internalPath] = $source;
|
|
}
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Resolve a media src and determine it's target location and add it to the book.
|
|
*
|
|
* @param string $source Source link.
|
|
* @param string $internalPath (referenced) Return value, will be set to the target path and name in the book.
|
|
* @param string $internalSrc (referenced) Return value, will be set to the target name in the book.
|
|
* @param string $isSourceExternal (referenced) Return value, will be set to TRUE if the image originated from a full URL.
|
|
* @param string $baseDir Default is "", meaning it is pointing to the document root.
|
|
* @param string $htmlDir The path to the parent HTML file's directory from the root of the archive.
|
|
* @param string $backPath The path to get back to the root of the archive from $htmlDir.
|
|
*/
|
|
protected function resolveMedia($source, &$internalPath, &$internalSrc, &$isSourceExternal, $baseDir = "", $htmlDir = "", $backPath = "") {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$mediaPath = NULL;
|
|
$tmpFile;
|
|
|
|
if (preg_match('#^(http|ftp)s?://#i', $source) == 1) {
|
|
$urlinfo = parse_url($source);
|
|
|
|
if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) {
|
|
$internalSrc = substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") + strlen($baseDir) + 1);
|
|
}
|
|
$internalPath = $urlinfo["scheme"] . "/" . $urlinfo["host"] . "/" . pathinfo($urlinfo["path"], PATHINFO_DIRNAME);
|
|
$isSourceExternal = TRUE;
|
|
$mediaPath = $this->getFileContents($source, true);
|
|
$tmpFile = $mediaPath;
|
|
} else if (strpos($source, "/") === 0) {
|
|
$internalPath = pathinfo($source, PATHINFO_DIRNAME);
|
|
|
|
$mediaPath = $source;
|
|
if (!file_exists($mediaPath)) {
|
|
$mediaPath = $this->docRoot . $mediaPath;
|
|
}
|
|
} else {
|
|
$internalPath = $htmlDir . "/" . preg_replace('#^[/\.]+#', '', pathinfo($source, PATHINFO_DIRNAME));
|
|
|
|
$mediaPath = $baseDir . "/" . $source;
|
|
if (!file_exists($mediaPath)) {
|
|
$mediaPath = $this->docRoot . $mediaPath;
|
|
}
|
|
}
|
|
|
|
if ($mediaPath !== FALSE) {
|
|
$mime = $this->getMime($source);
|
|
$internalPath = Zip::getRelativePath("media/" . $internalPath . "/" . $internalSrc);
|
|
|
|
if (!array_key_exists($internalPath, $this->fileList) &&
|
|
$this->addLargeFile($internalPath, "m_" . $internalSrc, $mediaPath, $mime)) {
|
|
$this->fileList[$internalPath] = $source;
|
|
}
|
|
if (isset($tmpFile)) {
|
|
unlink($tmpFile);
|
|
}
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Get Book Chapter count.
|
|
*
|
|
* @access public
|
|
* @return number of chapters
|
|
*/
|
|
function getChapterCount() {
|
|
return $this->chapterCount;
|
|
}
|
|
|
|
/**
|
|
* Book title, mandatory.
|
|
*
|
|
* Used for the dc:title metadata parameter in the OPF file as well as the DocTitle attribute in the NCX file.
|
|
*
|
|
* @param string $title
|
|
* @access public
|
|
* @return bool $success
|
|
*/
|
|
function setTitle($title) {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$this->title = $title;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Get Book title.
|
|
*
|
|
* @access public
|
|
* @return $title
|
|
*/
|
|
function getTitle() {
|
|
return $this->title;
|
|
}
|
|
|
|
/**
|
|
* Book language, mandatory
|
|
*
|
|
* Use the RFC3066 Language codes, such as "en", "da", "fr" etc.
|
|
* Defaults to "en".
|
|
*
|
|
* Used for the dc:language metadata parameter in the OPF file.
|
|
*
|
|
* @param string $language
|
|
* @access public
|
|
* @return bool $success
|
|
*/
|
|
function setLanguage($language) {
|
|
if ($this->isFinalized || mb_strlen($language) != 2) {
|
|
return FALSE;
|
|
}
|
|
$this->language = $language;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Get Book language.
|
|
*
|
|
* @access public
|
|
* @return $language
|
|
*/
|
|
function getLanguage() {
|
|
return $this->language;
|
|
}
|
|
|
|
/**
|
|
* Unique book identifier, mandatory.
|
|
* Use the URI, or ISBN if available.
|
|
*
|
|
* An unambiguous reference to the resource within a given context.
|
|
*
|
|
* Recommended best practice is to identify the resource by means of a
|
|
* string conforming to a formal identification system.
|
|
*
|
|
* Used for the dc:identifier metadata parameter in the OPF file, as well
|
|
* as dtb:uid in the NCX file.
|
|
*
|
|
* Identifier type should only be:
|
|
* EPub::IDENTIFIER_URI
|
|
* EPub::IDENTIFIER_ISBN
|
|
* EPub::IDENTIFIER_UUID
|
|
*
|
|
* @param string $identifier
|
|
* @param string $identifierType
|
|
* @access public
|
|
* @return bool $success
|
|
*/
|
|
function setIdentifier($identifier, $identifierType) {
|
|
if ($this->isFinalized || ($identifierType !== EPub::IDENTIFIER_URI && $identifierType !== EPub::IDENTIFIER_ISBN && $identifierType !== EPub::IDENTIFIER_UUID)) {
|
|
return FALSE;
|
|
}
|
|
$this->identifier = $identifier;
|
|
$this->identifierType = $identifierType;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Get Book identifier.
|
|
*
|
|
* @access public
|
|
* @return $identifier
|
|
*/
|
|
function getIdentifier() {
|
|
return $this->identifier;
|
|
}
|
|
|
|
/**
|
|
* Get Book identifierType.
|
|
*
|
|
* @access public
|
|
* @return $identifierType
|
|
*/
|
|
function getIdentifierType() {
|
|
return $this->identifierType;
|
|
}
|
|
|
|
/**
|
|
* Book description, optional.
|
|
*
|
|
* An account of the resource.
|
|
*
|
|
* Description may include but is not limited to: an abstract, a table of
|
|
* contents, a graphical representation, or a free-text account of the
|
|
* resource.
|
|
*
|
|
* Used for the dc:source metadata parameter in the OPF file
|
|
*
|
|
* @param string $description
|
|
* @access public
|
|
* @return bool $success
|
|
*/
|
|
function setDescription($description) {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$this->description = $description;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Get Book description.
|
|
*
|
|
* @access public
|
|
* @return $description
|
|
*/
|
|
function getDescription() {
|
|
return $this->description;
|
|
}
|
|
|
|
/**
|
|
* Book author or creator, optional.
|
|
* The $authorSortKey is basically how the name is to be sorted, usually
|
|
* it's "Lastname, First names" where the $author is the straight
|
|
* "Firstnames Lastname"
|
|
*
|
|
* An entity primarily responsible for making the resource.
|
|
*
|
|
* Examples of a Creator include a person, an organization, or a service.
|
|
* Typically, the name of a Creator should be used to indicate the entity.
|
|
*
|
|
* Used for the dc:creator metadata parameter in the OPF file and the
|
|
* docAuthor attribure in the NCX file.
|
|
* The sort key is used for the opf:file-as attribute in dc:creator.
|
|
*
|
|
* @param string $author
|
|
* @param string $authorSortKey
|
|
* @access public
|
|
* @return bool $success
|
|
*/
|
|
function setAuthor($author, $authorSortKey) {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$this->author = $author;
|
|
$this->authorSortKey = $authorSortKey;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Get Book author.
|
|
*
|
|
* @access public
|
|
* @return $author
|
|
*/
|
|
function getAuthor() {
|
|
return $this->author;
|
|
}
|
|
|
|
/**
|
|
* Publisher Information, optional.
|
|
*
|
|
* An entity responsible for making the resource available.
|
|
*
|
|
* Examples of a Publisher include a person, an organization, or a service.
|
|
* Typically, the name of a Publisher should be used to indicate the entity.
|
|
*
|
|
* Used for the dc:publisher and dc:relation metadata parameters in the OPF file.
|
|
*
|
|
* @param string $publisherName
|
|
* @param string $publisherURL
|
|
* @access public
|
|
* @return bool $success
|
|
*/
|
|
function setPublisher($publisherName, $publisherURL) {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$this->publisherName = $publisherName;
|
|
$this->publisherURL = $publisherURL;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Get Book publisherName.
|
|
*
|
|
* @access public
|
|
* @return $publisherName
|
|
*/
|
|
function getPublisherName() {
|
|
return $this->publisherName;
|
|
}
|
|
|
|
/**
|
|
* Get Book publisherURL.
|
|
*
|
|
* @access public
|
|
* @return $publisherURL
|
|
*/
|
|
function getPublisherURL() {
|
|
return $this->publisherURL;
|
|
}
|
|
|
|
/**
|
|
* Release date, optional. If left blank, the time of the finalization will
|
|
* be used.
|
|
*
|
|
* A point or period of time associated with an event in the lifecycle of
|
|
* the resource.
|
|
*
|
|
* Date may be used to express temporal information at any level of
|
|
* granularity. Recommended best practice is to use an encoding scheme,
|
|
* such as the W3CDTF profile of ISO 8601 [W3CDTF].
|
|
*
|
|
* Used for the dc:date metadata parameter in the OPF file
|
|
*
|
|
* @param long $timestamp
|
|
* @access public
|
|
* @return bool $success
|
|
*/
|
|
function setDate($timestamp) {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$this->date = $timestamp;
|
|
$this->opf->date = $timestamp;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Get Book date.
|
|
*
|
|
* @access public
|
|
* @return $date
|
|
*/
|
|
function getDate() {
|
|
return $this->date;
|
|
}
|
|
|
|
/**
|
|
* Book (copy)rights, optional.
|
|
*
|
|
* Information about rights held in and over the resource.
|
|
*
|
|
* Typically, rights information includes a statement about various
|
|
* property rights associated with the resource, including intellectual
|
|
* property rights.
|
|
*
|
|
* Used for the dc:rights metadata parameter in the OPF file
|
|
*
|
|
* @param string $rightsText
|
|
* @access public
|
|
* @return bool $success
|
|
*/
|
|
function setRights($rightsText) {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$this->rights = $rightsText;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Get Book rights.
|
|
*
|
|
* @access public
|
|
* @return $rights
|
|
*/
|
|
function getRights() {
|
|
return $this->rights;
|
|
}
|
|
|
|
/**
|
|
* Add book Subject.
|
|
*
|
|
* The topic of the resource.
|
|
*
|
|
* Typically, the subject will be represented using keywords, key phrases,
|
|
* or classification codes. Recommended best practice is to use a
|
|
* controlled vocabulary. To describe the spatial or temporal topic of the
|
|
* resource, use the Coverage element.
|
|
*
|
|
* @param string $subject
|
|
*/
|
|
function setSubject($subject) {
|
|
if ($this->isFinalized) {
|
|
return;
|
|
}
|
|
$this->opf->addDCMeta(DublinCore::SUBJECT, $this->decodeHtmlEntities($subject));
|
|
}
|
|
|
|
/**
|
|
* Book source URL, optional.
|
|
*
|
|
* A related resource from which the described resource is derived.
|
|
*
|
|
* The described resource may be derived from the related resource in whole
|
|
* or in part. Recommended best practice is to identify the related
|
|
* resource by means of a string conforming to a formal identification system.
|
|
*
|
|
* Used for the dc:source metadata parameter in the OPF file
|
|
*
|
|
* @param string $sourceURL
|
|
* @access public
|
|
* @return bool $success
|
|
*/
|
|
function setSourceURL($sourceURL) {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$this->sourceURL = $sourceURL;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Get Book sourceURL.
|
|
*
|
|
* @access public
|
|
* @return $sourceURL
|
|
*/
|
|
function getSourceURL() {
|
|
return $this->sourceURL;
|
|
}
|
|
|
|
/**
|
|
* Coverage, optional.
|
|
*
|
|
* The spatial or temporal topic of the resource, the spatial applicability
|
|
* of the resource, or the jurisdiction under which the resource is relevant.
|
|
*
|
|
* Spatial topic and spatial applicability may be a named place or a location
|
|
* specified by its geographic coordinates. Temporal topic may be a named
|
|
* period, date, or date range. A jurisdiction may be a named administrative
|
|
* entity or a geographic place to which the resource applies. Recommended
|
|
* best practice is to use a controlled vocabulary such as the Thesaurus of
|
|
* Geographic Names [TGN]. Where appropriate, named places or time periods
|
|
* can be used in preference to numeric identifiers such as sets of
|
|
* coordinates or date ranges.
|
|
*
|
|
* Used for the dc:coverage metadata parameter in the OPF file
|
|
*
|
|
* Same as ->addDublinCoreMetadata(DublinCore::COVERAGE, $coverage);
|
|
*
|
|
* @param string $coverage
|
|
* @access public
|
|
* @return bool $success
|
|
*/
|
|
function setCoverage($coverage) {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$this->coverage = $coverage;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Get Book coverage.
|
|
*
|
|
* @access public
|
|
* @return $coverage
|
|
*/
|
|
function getCoverage() {
|
|
return $this->coverage;
|
|
}
|
|
|
|
/**
|
|
* Set book Relation.
|
|
*
|
|
* A related resource.
|
|
*
|
|
* Recommended best practice is to identify the related resource by means
|
|
* of a string conforming to a formal identification system.
|
|
*
|
|
* @param string $relation
|
|
*/
|
|
function setRelation($relation) {
|
|
if ($this->isFinalized) {
|
|
return;
|
|
}
|
|
$this->relation = $relation;
|
|
}
|
|
|
|
/**
|
|
* Get the book relation.
|
|
*
|
|
* @return string The relation.
|
|
*/
|
|
function getRelation() {
|
|
return $this->relation;
|
|
}
|
|
|
|
/**
|
|
* Set book Generator.
|
|
*
|
|
* The generator is a meta tag added to the ncx file, it is not visible
|
|
* from within the book, but is a kind of electronic watermark.
|
|
*
|
|
* @param string $generator
|
|
*/
|
|
function setGenerator($generator) {
|
|
if ($this->isFinalized) {
|
|
return;
|
|
}
|
|
$this->generator = $generator;
|
|
}
|
|
|
|
/**
|
|
* Get the book relation.
|
|
*
|
|
* @return string The generator identity string.
|
|
*/
|
|
function getGenerator() {
|
|
return $this->generator;
|
|
}
|
|
|
|
/**
|
|
* Set ePub date formate to the short yyyy-mm-dd form, for compliance with
|
|
* a bug in EpubCheck, prior to its version 1.1.
|
|
*
|
|
* The latest version of ePubCheck can be obtained here:
|
|
* http://code.google.com/p/epubcheck/
|
|
*
|
|
* @access public
|
|
* @return bool $success
|
|
*/
|
|
function setShortDateFormat() {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$this->dateformat = $this->dateformatShort;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @Deprecated
|
|
*/
|
|
function setIgnoreEmptyBuffer($ignoreEmptyBuffer = TRUE) {
|
|
die ("Function was deprecated, functionality is no longer needed.");
|
|
}
|
|
|
|
/**
|
|
* Set the references title for the ePub 3 landmarks section
|
|
*
|
|
* @param string $referencesTitle
|
|
* @param string $referencesId
|
|
* @param string $referencesClass
|
|
* @return bool
|
|
*/
|
|
function setReferencesTitle($referencesTitle = "Guide", $referencesId = "", $referencesClass = "references") {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$this->ncx->referencesTitle = is_string($referencesTitle) ? trim($referencesTitle) : "Guide";
|
|
$this->ncx->referencesId = is_string($referencesId) ? trim($referencesId) : "references";
|
|
$this->ncx->referencesClass = is_string($referencesClass) ? trim($referencesClass) : "references";
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Set the references title for the ePub 3 landmarks section
|
|
*
|
|
* @param bool $referencesTitle
|
|
*/
|
|
function setisReferencesAddedToToc($isReferencesAddedToToc = TRUE) {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$this->isReferencesAddedToToc = $isReferencesAddedToToc === TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Get Book status.
|
|
*
|
|
* @access public
|
|
* @return bool
|
|
*/
|
|
function isFinalized() {
|
|
return $this->isFinalized;
|
|
}
|
|
|
|
/**
|
|
* Build the Table of Contents. This is not strictly necessary, as most eReaders will build it from the navigation structure in the .ncx file.
|
|
*
|
|
* @param string $cssFileName Include a link to this css file in the TOC html.
|
|
* @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".
|
|
* @param string $title Title of the Table of contents. Default is "Table of Contents". Use this for ie. languages other than English.
|
|
* @param bool $addReferences include reference pages in the TOC, using the $referencesOrder array to determine the order of the pages in the TOC. Default is TRUE.
|
|
* @param bool $addToIndex Add the TOC to the NCX index at the current leve/position. Default is FALSE
|
|
* @param string $tocFileName Change teh default name of the TOC file. The default is "TOC.xhtml"
|
|
*/
|
|
function buildTOC($cssFileName = NULL, $tocCSSClass = "toc", $title = "Table of Contents", $addReferences = TRUE, $addToIndex = FALSE, $tocFileName = "TOC.xhtml") {
|
|
if ($this->isFinalized) {
|
|
return FALSE;
|
|
}
|
|
$this->buildTOC = TRUE;
|
|
$this->tocTitle = $title;
|
|
$this->tocFileName = $this->normalizeFileName($tocFileName);
|
|
if (!empty($cssFileName)) {
|
|
$this->tocCSSFileName = $this->normalizeFileName($cssFileName);
|
|
}
|
|
$this->tocCSSClass = $tocCSSClass;
|
|
$this->tocAddReferences = $addReferences;
|
|
|
|
$this->opf->addItemRef("ref_" . Reference::TABLE_OF_CONTENTS, FALSE);
|
|
$this->opf->addReference(Reference::TABLE_OF_CONTENTS, $title, $this->tocFileName);
|
|
|
|
if ($addToIndex) {
|
|
$navPoint = new NavPoint($this->decodeHtmlEntities($title), $this->tocFileName, "ref_" . Reference::TABLE_OF_CONTENTS);
|
|
$this->ncx->addNavPoint($navPoint);
|
|
} else {
|
|
$this->ncx->referencesList[Reference::TABLE_OF_CONTENTS] = $this->tocFileName;
|
|
$this->ncx->referencesName[Reference::TABLE_OF_CONTENTS] = $title;
|
|
}
|
|
}
|
|
|
|
private function finalizeTOC() {
|
|
if (!$this->buildTOC) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (empty($this->tocTitle)) {
|
|
$this->tocTitle = "Table of Contents";
|
|
}
|
|
|
|
$tocData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
|
|
|
|
if ($this->isEPubVersion2()) {
|
|
$tocData .= "<!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>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
|
|
} else {
|
|
$tocData .= "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n"
|
|
. "<head>\n<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n";
|
|
}
|
|
|
|
if (!empty($this->tocCssFileName)) {
|
|
$tocData .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" . $this->tocCssFileName . "\" />\n";
|
|
}
|
|
|
|
$tocData .= "<title>" . $this->tocTitle . "</title>\n"
|
|
. "</head>\n"
|
|
. "<body>\n"
|
|
. "<h3>" . $this->tocTitle . "</h3>\n<div";
|
|
|
|
if (!empty($this->tocCSSClass)) {
|
|
$tocData .= " class=\"" . $this->tocCSSClass . "\"";
|
|
}
|
|
$tocData .= ">\n";
|
|
|
|
while (list($item, $descriptive) = each($this->referencesOrder)) {
|
|
if ($item === "text") {
|
|
while (list($chapterName, $navPoint) = each($this->ncx->chapterList)) {
|
|
$fileName = $navPoint->getContentSrc();
|
|
$level = $navPoint->getLevel() -2;
|
|
$tocData .= "\t<p>" . str_repeat("      ", $level) . "<a href=\"" . $this->sanitizeFileName($fileName) . "\">" . $chapterName . "</a></p>\n";
|
|
}
|
|
} else if ($this->tocAddReferences === TRUE) {
|
|
if (array_key_exists($item, $this->ncx->referencesList)) {
|
|
$tocData .= "\t<p><a href=\"" . $this->ncx->referencesList[$item] . "\">" . $descriptive . "</a></p>\n";
|
|
} else if ($item === "toc") {
|
|
$tocData .= "\t<p><a href=\"TOC.xhtml\">" . $this->tocTitle . "</a></p>\n";
|
|
} else if ($item === "cover" && $this->isCoverImageSet) {
|
|
$tocData .= "\t<p><a href=\"CoverPage.xhtml\">" . $descriptive . "</a></p>\n";
|
|
}
|
|
}
|
|
}
|
|
$tocData .= "</div>\n</body>\n</html>\n";
|
|
|
|
$this->addReferencePage($this->tocTitle, $this->tocFileName, $tocData, Reference::TABLE_OF_CONTENTS);
|
|
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
function isEPubVersion2() {
|
|
return $this->bookVersion === EPub::BOOK_VERSION_EPUB2;
|
|
}
|
|
|
|
/**
|
|
* @param string $cssFileName
|
|
* @param string $title
|
|
* @return string
|
|
*/
|
|
function buildEPub3TOC($cssFileName = NULL, $title = "Table of Contents") {
|
|
$this->ncx->referencesOrder = $this->referencesOrder;
|
|
$this->ncx->setDocTitle($this->decodeHtmlEntities($this->title));
|
|
return $this->ncx->finalizeEPub3($title, $cssFileName);
|
|
}
|
|
|
|
/**
|
|
* @param string $fileName
|
|
* @param string $tocData
|
|
* @return bool
|
|
*/
|
|
function addEPub3TOC($fileName, $tocData) {
|
|
if ($this->isEPubVersion2() || $this->isFinalized || array_key_exists($fileName, $this->fileList)) {
|
|
return FALSE;
|
|
}
|
|
$fileName = Zip::getRelativePath($fileName);
|
|
$fileName = preg_replace('#^[/\.]+#i', "", $fileName);
|
|
|
|
$this->zip->addFile($tocData, $this->bookRoot.$fileName);
|
|
|
|
$this->fileList[$fileName] = $fileName;
|
|
$this->opf->addItem("toc", $fileName, "application/xhtml+xml", "nav");
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Check for mandatory parameters and finalize the e-book.
|
|
* Once finalized, the book is locked for further additions.
|
|
*
|
|
* @return bool $success
|
|
*/
|
|
function finalize() {
|
|
if ($this->isFinalized || $this->chapterCount == 0 || empty($this->title) || empty($this->language)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (empty($this->identifier) || empty($this->identifierType)) {
|
|
$this->setIdentifier($this->createUUID(4), EPub::IDENTIFIER_UUID);
|
|
}
|
|
|
|
if ($this->date == 0) {
|
|
$this->date = time();
|
|
}
|
|
|
|
if (empty($this->sourceURL)) {
|
|
$this->sourceURL = $this->getCurrentPageURL();
|
|
}
|
|
|
|
if (empty($this->publisherURL)) {
|
|
$this->sourceURL = $this->getCurrentServerURL();
|
|
}
|
|
|
|
// Generate OPF data:
|
|
$this->opf->setIdent("BookId");
|
|
$this->opf->initialize($this->title, $this->language, $this->identifier, $this->identifierType);
|
|
|
|
$DCdate = new DublinCore(DublinCore::DATE, gmdate($this->dateformat, $this->date));
|
|
$DCdate->addOpfAttr("event", "publication");
|
|
$this->opf->metadata->addDublinCore($DCdate);
|
|
|
|
if (!empty($this->description)) {
|
|
$this->opf->addDCMeta(DublinCore::DESCRIPTION, $this->decodeHtmlEntities($this->description));
|
|
}
|
|
|
|
if (!empty($this->publisherName)) {
|
|
$this->opf->addDCMeta(DublinCore::PUBLISHER, $this->decodeHtmlEntities($this->publisherName));
|
|
}
|
|
|
|
if (!empty($this->publisherURL)) {
|
|
$this->opf->addDCMeta(DublinCore::RELATION, $this->decodeHtmlEntities($this->publisherURL));
|
|
}
|
|
|
|
if (!empty($this->author)) {
|
|
$author = $this->decodeHtmlEntities($this->author);
|
|
$this->opf->addCreator($author, $this->decodeHtmlEntities($this->authorSortKey), MarcCode::AUTHOR);
|
|
$this->ncx->setDocAuthor($author);
|
|
}
|
|
|
|
if (!empty($this->rights)) {
|
|
$this->opf->addDCMeta(DublinCore::RIGHTS, $this->decodeHtmlEntities($this->rights));
|
|
}
|
|
|
|
if (!empty($this->coverage)) {
|
|
$this->opf->addDCMeta(DublinCore::COVERAGE, $this->decodeHtmlEntities($this->coverage));
|
|
}
|
|
|
|
if (!empty($this->sourceURL)) {
|
|
$this->opf->addDCMeta(DublinCore::SOURCE, $this->sourceURL);
|
|
}
|
|
|
|
if (!empty($this->relation)) {
|
|
$this->opf->addDCMeta(DublinCore::RELATION, $this->decodeHtmlEntities($this->relation));
|
|
}
|
|
|
|
if ($this->isCoverImageSet) {
|
|
$this->opf->addMeta("cover", "coverImage");
|
|
}
|
|
|
|
if (!empty($this->generator)) {
|
|
$gen = $this->decodeHtmlEntities($this->generator);
|
|
$this->opf->addMeta("generator", $gen);
|
|
$this->ncx->addMetaEntry("dtb:generator", $gen);
|
|
}
|
|
|
|
if ($this->EPubMark) {
|
|
$this->opf->addMeta("generator", "EPub (Version " . self::VERSION . ") by A. Grandt, http://www.phpclasses.org/package/6115");
|
|
}
|
|
|
|
reset($this->ncx->chapterList);
|
|
list($firstChapterName, $firstChapterNavPoint) = each($this->ncx->chapterList);
|
|
$firstChapterFileName = $firstChapterNavPoint->getContentSrc();
|
|
$this->opf->addReference(Reference::TEXT, $this->decodeHtmlEntities($firstChapterName), $firstChapterFileName);
|
|
|
|
$this->ncx->setUid($this->identifier);
|
|
|
|
$this->ncx->setDocTitle($this->decodeHtmlEntities($this->title));
|
|
|
|
$this->ncx->referencesOrder = $this->referencesOrder;
|
|
if ($this->isReferencesAddedToToc) {
|
|
$this->ncx->finalizeReferences();
|
|
}
|
|
|
|
$this->finalizeTOC();
|
|
|
|
if (!$this->isEPubVersion2()) {
|
|
$this->addEPub3TOC("epub3toc.xhtml", $this->buildEPub3TOC());
|
|
}
|
|
|
|
$opfFinal = $this->fixEncoding($this->opf->finalize());
|
|
$ncxFinal = $this->fixEncoding($this->ncx->finalize());
|
|
|
|
if (mb_detect_encoding($opfFinal, 'UTF-8', true) === "UTF-8") {
|
|
$this->zip->addFile($opfFinal, $this->bookRoot."book.opf");
|
|
} else {
|
|
$this->zip->addFile(mb_convert_encoding($opfFinal, "UTF-8"), $this->bookRoot."book.opf");
|
|
}
|
|
|
|
if (mb_detect_encoding($ncxFinal, 'UTF-8', true) === "UTF-8") {
|
|
$this->zip->addFile($ncxFinal, $this->bookRoot."book.ncx");
|
|
} else {
|
|
$this->zip->addFile(mb_convert_encoding($ncxFinal, "UTF-8"), $this->bookRoot."book.ncx");
|
|
}
|
|
|
|
$this->opf = NULL;
|
|
$this->ncx = NULL;
|
|
|
|
$this->isFinalized = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Ensure the encoded string is a valid UTF-8 string.
|
|
*
|
|
* 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.
|
|
*
|
|
* @link: http://snippetdb.com/php/convert-string-to-utf-8-for-mysql
|
|
* @param string $in_str
|
|
* @return string converted string.
|
|
*/
|
|
function fixEncoding($in_str) {
|
|
if (mb_detect_encoding($in_str) == "UTF-8" && mb_check_encoding($in_str,"UTF-8")) {
|
|
return $in_str;
|
|
} else {
|
|
return utf8_encode($in_str);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the finalized book.
|
|
*
|
|
* @return string with the book in binary form.
|
|
*/
|
|
function getBook() {
|
|
if (!$this->isFinalized) {
|
|
$this->finalize();
|
|
}
|
|
|
|
return $this->zip->getZipData();
|
|
}
|
|
|
|
/**
|
|
* Remove disallowed characters from string to get a nearly safe filename
|
|
*
|
|
* @param string $fileName
|
|
* @return mixed|string
|
|
*/
|
|
function sanitizeFileName($fileName) {
|
|
$fileName1 = str_replace($this->forbiddenCharacters, '', $fileName);
|
|
$fileName2 = preg_replace('/[\s-]+/', '-', $fileName1);
|
|
return trim($fileName2, '.-_');
|
|
|
|
}
|
|
|
|
/**
|
|
* Cleanup the filepath, and remove leading . and / characters.
|
|
*
|
|
* Sometimes, when a path is generated from multiple fragments,
|
|
* you can get something like "../data/html/../images/image.jpeg"
|
|
* ePub files don't work well with that, this will normalize that
|
|
* example path to "data/images/image.jpeg"
|
|
*
|
|
* @param string $fileName
|
|
* @return string normalized filename
|
|
*/
|
|
function normalizeFileName($fileName) {
|
|
return preg_replace('#^[/\.]+#i', "", Zip::getRelativePath($fileName));
|
|
}
|
|
|
|
/**
|
|
* Save the ePub file to local disk.
|
|
*
|
|
* @param string $fileName
|
|
* @param string $baseDir If empty baseDir is absolute to server path, if omitted it's relative to script path
|
|
* @return The sent file name if successfull, FALSE if it failed.
|
|
*/
|
|
function saveBook($fileName, $baseDir = '.') {
|
|
|
|
// Make fileName safe
|
|
$fileName = $this->sanitizeFileName($fileName);
|
|
|
|
// Finalize book, if it's not done already
|
|
if (!$this->isFinalized) {
|
|
$this->finalize();
|
|
}
|
|
|
|
if (stripos(strrev($fileName), "bupe.") !== 0) {
|
|
$fileName .= ".epub";
|
|
}
|
|
|
|
// Try to open file access
|
|
$fh = fopen($baseDir.'/'.$fileName, "w");
|
|
|
|
if ($fh) {
|
|
fputs($fh, $this->getBook());
|
|
fclose($fh);
|
|
|
|
// if file is written return TRUE
|
|
return $fileName;
|
|
}
|
|
|
|
// return FALSE by default
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Return the finalized book size.
|
|
*
|
|
* @return string
|
|
*/
|
|
function getBookSize() {
|
|
if (!$this->isFinalized) {
|
|
$this->finalize();
|
|
}
|
|
|
|
return $this->zip->getArchiveSize();
|
|
}
|
|
|
|
/**
|
|
* Send the book as a zip download
|
|
*
|
|
* Sending will fail if the output buffer is in use. You can override this limit by
|
|
* calling setIgnoreEmptyBuffer(TRUE), though the function will still fail if that
|
|
* buffer is not empty.
|
|
*
|
|
* @param string $fileName The name of the book without the .epub at the end.
|
|
* @return The sent file name if successfull, FALSE if it failed.
|
|
*/
|
|
function sendBook($fileName) {
|
|
if (!$this->isFinalized) {
|
|
$this->finalize();
|
|
}
|
|
|
|
if (stripos(strrev($fileName), "bupe.") !== 0) {
|
|
$fileName .= ".epub";
|
|
}
|
|
|
|
if (TRUE === $this->zip->sendZip($fileName, "application/epub+zip")) {
|
|
return $fileName;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Generates an UUID.
|
|
*
|
|
* Default version (4) will generate a random UUID, version 3 will URL based UUID.
|
|
*
|
|
* Added for convinience
|
|
*
|
|
* @param int $bookVersion UUID version to retrieve, See lib.uuid.manual.html for details.
|
|
* @param string $url
|
|
* @return string The formatted uuid
|
|
*/
|
|
function createUUID($bookVersion = 4, $url = NULL) {
|
|
include_once("lib.uuid.php");
|
|
return UUID::mint($bookVersion, $url, UUID::nsURL);
|
|
}
|
|
|
|
/**
|
|
* Get the url of the current page.
|
|
* Example use: Default Source URL
|
|
*
|
|
* $return string Page URL.
|
|
*/
|
|
function getCurrentPageURL() {
|
|
$pageURL = $this->getCurrentServerURL() . filter_input(INPUT_SERVER, "REQUEST_URI");
|
|
return $pageURL;
|
|
}
|
|
|
|
/**
|
|
* Get the url of the server.
|
|
* Example use: Default Publisher URL
|
|
*
|
|
* $return string Server URL.
|
|
*/
|
|
function getCurrentServerURL() {
|
|
$serverURL = 'http';
|
|
$https = filter_input(INPUT_SERVER, "HTTPS");
|
|
$port = filter_input(INPUT_SERVER, "SERVER_PORT");
|
|
|
|
if ($https === "on") {
|
|
$serverURL .= "s";
|
|
}
|
|
$serverURL .= "://" . filter_input(INPUT_SERVER, "SERVER_NAME");
|
|
if ($port != "80") {
|
|
$serverURL .= ":" . $port;
|
|
}
|
|
return $serverURL . '/';
|
|
}
|
|
|
|
/**
|
|
* Try to determine the mimetype of the file path.
|
|
*
|
|
* @param string $source Path
|
|
* @return string mimetype, or FALSE.
|
|
*/
|
|
function getMime($source) {
|
|
return $this->mimetypes[pathinfo($source, PATHINFO_EXTENSION)];
|
|
}
|
|
|
|
/**
|
|
* Get an image from a file or url, return it resized if the image exceeds the $maxImageWidth or $maxImageHeight directives.
|
|
*
|
|
* The return value is an array.
|
|
* ['width'] is the width of the image.
|
|
* ['height'] is the height of the image.
|
|
* ['mime'] is the mime type of the image. Resized images are always in jpeg format.
|
|
* ['image'] is the image data.
|
|
* ['ext'] is the extension of the image file.
|
|
*
|
|
* @param string $source path or url to file.
|
|
* $return array
|
|
*/
|
|
function getImage($source) {
|
|
$width = -1;
|
|
$height = -1;
|
|
$mime = "application/octet-stream";
|
|
$type = FALSE;
|
|
$ext = "";
|
|
|
|
|
|
$image = $this->getFileContents($source);
|
|
|
|
if ($image !== FALSE && strlen($image) > 0) {
|
|
$imageFile = imagecreatefromstring($image);
|
|
if ($imageFile !== false) {
|
|
$width = ImageSX($imageFile);
|
|
$height = ImageSY($imageFile);
|
|
}
|
|
if ($this->isExifInstalled) {
|
|
@$type = exif_imagetype($source);
|
|
$mime = image_type_to_mime_type($type);
|
|
}
|
|
if ($mime === "application/octet-stream") {
|
|
$mime = $this->image_file_type_from_binary($image);
|
|
}
|
|
if ($mime === "application/octet-stream") {
|
|
$mime = $this->getMimeTypeFromUrl($source);
|
|
}
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
|
|
if ($width <= 0 || $height <= 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
$ratio = 1;
|
|
|
|
if ($this->isGdInstalled) {
|
|
if ($width > $this->maxImageWidth) {
|
|
$ratio = $this->maxImageWidth/$width;
|
|
}
|
|
if ($height*$ratio > $this->maxImageHeight) {
|
|
$ratio = $this->maxImageHeight/$height;
|
|
}
|
|
|
|
if ($ratio < 1 || empty($mime) || ($this->isGifImagesEnabled !== FALSE && $mime == "image/gif")) {
|
|
$image_o = imagecreatefromstring($image);
|
|
$image_p = imagecreatetruecolor($width*$ratio, $height*$ratio);
|
|
|
|
if ($mime == "image/png") {
|
|
imagealphablending($image_p, false);
|
|
imagesavealpha($image_p, true);
|
|
imagealphablending($image_o, true);
|
|
|
|
imagecopyresampled($image_p, $image_o, 0, 0, 0, 0, ($width*$ratio), ($height*$ratio), $width, $height);
|
|
ob_start();
|
|
imagepng($image_p, NULL, 9);
|
|
$image = ob_get_contents();
|
|
ob_end_clean();
|
|
|
|
$ext = "png";
|
|
} else {
|
|
imagecopyresampled($image_p, $image_o, 0, 0, 0, 0, ($width*$ratio), ($height*$ratio), $width, $height);
|
|
ob_start();
|
|
imagejpeg($image_p, NULL, 80);
|
|
$image = ob_get_contents();
|
|
ob_end_clean();
|
|
|
|
$mime = "image/jpeg";
|
|
$ext = "jpg";
|
|
}
|
|
imagedestroy($image_o);
|
|
imagedestroy($image_p);
|
|
}
|
|
}
|
|
|
|
if ($ext === "") {
|
|
static $mimeToExt = array (
|
|
'image/jpeg' => 'jpg',
|
|
'image/gif' => 'gif',
|
|
'image/png' => 'png'
|
|
);
|
|
|
|
if (isset($mimeToExt[$mime])) {
|
|
$ext = $mimeToExt[$mime];
|
|
}
|
|
}
|
|
|
|
$rv = array();
|
|
$rv['width'] = $width*$ratio;
|
|
$rv['height'] = $height*$ratio;
|
|
$rv['mime'] = $mime;
|
|
$rv['image'] = $image;
|
|
$rv['ext'] = $ext;
|
|
|
|
return $rv;
|
|
}
|
|
|
|
/**
|
|
* Get file contents, using curl if available, else file_get_contents
|
|
*
|
|
* @param string $source
|
|
* @return bool
|
|
*/
|
|
function getFileContents($source, $toTempFile = FALSE) {
|
|
$isExternal = preg_match('#^(http|ftp)s?://#i', $source) == 1;
|
|
|
|
if ($isExternal && $this->isCurlInstalled) {
|
|
$ch = curl_init();
|
|
$outFile = NULL;
|
|
$fp = NULL;
|
|
$res = FALSE;
|
|
$info = array('http_code' => 500);
|
|
|
|
curl_setopt($ch, CURLOPT_HEADER, 0);
|
|
curl_setopt($ch, CURLOPT_URL, str_replace(" ","%20",$source));
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
|
curl_setopt($ch, CURLOPT_BUFFERSIZE, 4096);
|
|
|
|
if ($toTempFile) {
|
|
$outFile = tempnam(sys_get_temp_dir(), "EPub_v" . EPub::VERSION . "_");
|
|
$fp = fopen($outFile, "w+b");
|
|
curl_setopt($ch, CURLOPT_FILE, $fp);
|
|
|
|
$res = curl_exec($ch);
|
|
$info = curl_getinfo($ch);
|
|
|
|
curl_close($ch);
|
|
fclose($fp);
|
|
} else {
|
|
$res = curl_exec($ch);
|
|
$info = curl_getinfo($ch);
|
|
|
|
curl_close($ch);
|
|
}
|
|
|
|
if ($info['http_code'] == 200 && $res != false) {
|
|
if ($toTempFile) {
|
|
return $outFile;
|
|
}
|
|
return $res;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
if ($this->isFileGetContentsInstalled && (!$isExternal || $this->isFileGetContentsExtInstalled)) {
|
|
@$data = file_get_contents($source);
|
|
return $data;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* get mime type from image data
|
|
*
|
|
* By fireweasel found on http://stackoverflow.com/questions/2207095/get-image-mimetype-from-resource-in-php-gd
|
|
* @staticvar array $type
|
|
* @param object $binary
|
|
* @return string
|
|
*/
|
|
function image_file_type_from_binary($binary) {
|
|
$hits = 0;
|
|
if (!preg_match(
|
|
'/\A(?:(\xff\xd8\xff)|(GIF8[79]a)|(\x89PNG\x0d\x0a)|(BM)|(\x49\x49(?:\x2a\x00|\x00\x4a))|(FORM.{4}ILBM))/',
|
|
$binary, $hits)) {
|
|
return 'application/octet-stream';
|
|
}
|
|
static $type = array (
|
|
1 => 'image/jpeg',
|
|
2 => 'image/gif',
|
|
3 => 'image/png',
|
|
4 => 'image/x-windows-bmp',
|
|
5 => 'image/tiff',
|
|
6 => 'image/x-ilbm',
|
|
);
|
|
return $type[count($hits) - 1];
|
|
}
|
|
|
|
/**
|
|
* @param string $source URL Source
|
|
* @return string MimeType
|
|
*/
|
|
function getMimeTypeFromUrl($source) {
|
|
$ext = FALSE;
|
|
|
|
$srev = strrev($source);
|
|
$pos = strpos($srev, "?");
|
|
if ($pos !== FALSE) {
|
|
$srev = substr($srev, $pos+1);
|
|
}
|
|
|
|
$pos = strpos($srev, ".");
|
|
if ($pos !== FALSE) {
|
|
$ext = strtolower(strrev(substr($srev, 0, $pos)));
|
|
}
|
|
|
|
if ($ext !== FALSE) {
|
|
return $this->getMimeTypeFromExtension($ext);
|
|
}
|
|
return "application/octet-stream";
|
|
}
|
|
|
|
/**
|
|
* @param string $ext Extension
|
|
* @return string MimeType
|
|
*/
|
|
function getMimeTypeFromExtension($ext) {
|
|
switch ($ext) {
|
|
case "jpg":
|
|
case "jpe":
|
|
case "jpeg":
|
|
return 'image/jpeg';
|
|
case "gif":
|
|
return 'image/gif';
|
|
case "png":
|
|
return 'image/png';
|
|
case "bmp":
|
|
return 'image/x-windows-bmp';
|
|
case "tif":
|
|
case "tiff":
|
|
case "cpt":
|
|
return 'image/tiff';
|
|
case "lbm":
|
|
case "ilbm":
|
|
return 'image/x-ilbm';
|
|
default:
|
|
return "application/octet-stream";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Encode html code to use html entities, safeguarding it from potential character encoding peoblems
|
|
* This function is a bit different from the vanilla htmlentities function in that it does not encode html tags.
|
|
*
|
|
* The regexp is taken from the PHP Manual discussion, it was written by user "busbyjon".
|
|
* http://www.php.net/manual/en/function.htmlentities.php#90111
|
|
*
|
|
* @param string $string string to encode.
|
|
*/
|
|
public function encodeHtml($string) {
|
|
$string = strtr($string, $this->html_encoding_characters);
|
|
|
|
//return preg_replace("/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,5};)/", "&\\1", $string);
|
|
//return preg_replace("/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,5};)/", "&", $string);
|
|
return $string;
|
|
}
|
|
|
|
/**
|
|
* Helper function to create a DOM fragment with given markup.
|
|
*
|
|
* @author Adam Schmalhofer
|
|
*
|
|
* @param DOMDocument $dom
|
|
* @param string $markup
|
|
* @return DOMNode fragment in a node.
|
|
*/
|
|
protected function createDomFragment($dom, $markup) {
|
|
$node = $dom->createDocumentFragment();
|
|
$node->appendXML($markup);
|
|
return $node;
|
|
}
|
|
|
|
/**
|
|
* Retrieve an array of file names currently added to the book.
|
|
* $key is the filename used in the book
|
|
* $value is the original filename, will be the same as $key for most entries
|
|
*
|
|
* @return array file list
|
|
*/
|
|
function getFileList() {
|
|
return $this->fileList;
|
|
}
|
|
|
|
/**
|
|
* @deprecated Use Zip::getRelativePath($relPath) instead.
|
|
*/
|
|
function relPath($relPath) {
|
|
die ("Function was deprecated, use Zip::getRelativePath(\$relPath); instead");
|
|
}
|
|
|
|
/**
|
|
* Set default chapter target size.
|
|
* Default is 250000 bytes, and minimum is 10240 bytes.
|
|
*
|
|
* @param int $size segment size in bytes
|
|
* @return void
|
|
*/
|
|
function setSplitSize($size) {
|
|
$this->splitDefaultSize = (int)$size;
|
|
if ($size < 10240) {
|
|
$this->splitDefaultSize = 10240; // Making the file smaller than 10k is not a good idea.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the chapter target size.
|
|
*
|
|
* @return $size
|
|
*/
|
|
function getSplitSize() {
|
|
return $this->splitDefaultSize;
|
|
}
|
|
|
|
/**
|
|
* Remove all non essential html tags and entities.
|
|
*
|
|
* @global type $htmlEntities
|
|
* @param string $string
|
|
* @return string with the stripped entities.
|
|
*/
|
|
function decodeHtmlEntities($string) {
|
|
global $htmlEntities;
|
|
|
|
$string = preg_replace('~\s*<br\s*/*\s*>\s*~i', "\n", $string);
|
|
$string = preg_replace('~\s*</(p|div)\s*>\s*~i', "\n\n", $string);
|
|
$string = preg_replace('~<[^>]*>~', '', $string);
|
|
|
|
$string = strtr($string, $htmlEntities);
|
|
|
|
$string = str_replace('&', '&', $string);
|
|
$string = str_replace('&amp;', '&', $string);
|
|
$string = preg_replace('~&(#x*[a-fA-F0-9]+;)~', '&\1', $string);
|
|
$string = str_replace('<', '<', $string);
|
|
$string = str_replace('>', '>', $string);
|
|
|
|
return $string;
|
|
}
|
|
|
|
/**
|
|
* Simply remove all HTML tags, brute force and no finesse.
|
|
*
|
|
* @param string $string html
|
|
* @return string
|
|
*/
|
|
function html2text($string) {
|
|
return preg_replace('~<[^>]*>~', '', $string);
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
function getLog() {
|
|
return $this->log->getLog();
|
|
}
|
|
}
|