From 87090d8ae7582708d20f3c09fb37d780af860bcd Mon Sep 17 00:00:00 2001 From: tcit Date: Thu, 24 Apr 2014 03:04:02 +0200 Subject: Added epub export function --- .../libraries/PHPePub/EPub.HtmlEntities.php | 266 +++ inc/3rdparty/libraries/PHPePub/EPub.NCX.php | 782 +++++++ inc/3rdparty/libraries/PHPePub/EPub.OPF.php | 1226 ++++++++++ inc/3rdparty/libraries/PHPePub/EPub.php | 2429 ++++++++++++++++++++ .../libraries/PHPePub/EPubChapterSplitter.php | 201 ++ inc/3rdparty/libraries/PHPePub/Logger.php | 92 + inc/3rdparty/libraries/PHPePub/Zip.php | 818 +++++++ .../libraries/PHPePub/lib.uuid.LICENCE.txt | 31 + inc/3rdparty/libraries/PHPePub/lib.uuid.php | 314 +++ inc/poche/Poche.class.php | 83 + inc/poche/global.inc.php | 5 + index.php | 2 + themes/baggy/config.twig | 3 + themes/baggy/view.twig | 1 + 14 files changed, 6253 insertions(+) create mode 100644 inc/3rdparty/libraries/PHPePub/EPub.HtmlEntities.php create mode 100644 inc/3rdparty/libraries/PHPePub/EPub.NCX.php create mode 100644 inc/3rdparty/libraries/PHPePub/EPub.OPF.php create mode 100644 inc/3rdparty/libraries/PHPePub/EPub.php create mode 100644 inc/3rdparty/libraries/PHPePub/EPubChapterSplitter.php create mode 100644 inc/3rdparty/libraries/PHPePub/Logger.php create mode 100644 inc/3rdparty/libraries/PHPePub/Zip.php create mode 100644 inc/3rdparty/libraries/PHPePub/lib.uuid.LICENCE.txt create mode 100644 inc/3rdparty/libraries/PHPePub/lib.uuid.php diff --git a/inc/3rdparty/libraries/PHPePub/EPub.HtmlEntities.php b/inc/3rdparty/libraries/PHPePub/EPub.HtmlEntities.php new file mode 100644 index 00000000..376b6133 --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/EPub.HtmlEntities.php @@ -0,0 +1,266 @@ + \ No newline at end of file diff --git a/inc/3rdparty/libraries/PHPePub/EPub.NCX.php b/inc/3rdparty/libraries/PHPePub/EPub.NCX.php new file mode 100644 index 00000000..e5da05cd --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/EPub.NCX.php @@ -0,0 +1,782 @@ + + * @copyright 2009-2014 A. Grandt + * @license GNU LGPL, Attribution required for commercial implementations, requested for everything else. + * @version 3.20 + */ +class Ncx { + const _VERSION = 3.20; + + const MIMETYPE = "application/x-dtbncx+xml"; + + private $bookVersion = EPub::BOOK_VERSION_EPUB2; + + private $navMap = NULL; + private $uid = NULL; + private $meta = array(); + private $docTitle = NULL; + private $docAuthor = NULL; + + private $currentLevel = NULL; + private $lastLevel = NULL; + + private $languageCode = "en"; + private $writingDirection = EPub::DIRECTION_LEFT_TO_RIGHT; + + public $chapterList = array(); + public $referencesTitle = "Guide"; + public $referencesClass = "references"; + public $referencesId = "references"; + public $referencesList = array(); + public $referencesName = array(); + public $referencesOrder = NULL; + + /** + * Class constructor. + * + * @param string $uid + * @param string $docTitle + * @param string $docAuthor + * @param string $languageCode + * @param string $writingDirection + */ + function __construct($uid = NULL, $docTitle = NULL, $docAuthor = NULL, $languageCode = "en", $writingDirection = EPub::DIRECTION_LEFT_TO_RIGHT) { + $this->navMap = new NavMap($writingDirection); + $this->currentLevel = $this->navMap; + $this->setUid($uid); + $this->setDocTitle($docTitle); + $this->setDocAuthor($docAuthor); + $this->setLanguageCode($languageCode); + $this->setWritingDirection($writingDirection); + } + + /** + * Class destructor + * + * @return void + */ + function __destruct() { + unset($this->bookVersion, $this->navMap, $this->uid, $this->meta); + unset($this->docTitle, $this->docAuthor, $this->currentLevel, $this->lastLevel); + unset($this->languageCode, $this->writingDirection, $this->chapterList, $this->referencesTitle); + unset($this->referencesClass, $this->referencesId, $this->referencesList, $this->referencesName); + unset($this->referencesOrder); + } + + /** + * + * Enter description here ... + * + * @param string $bookVersion + */ + function setVersion($bookVersion) { + $this->bookVersion = is_string($bookVersion) ? trim($bookVersion) : EPub::BOOK_VERSION_EPUB2; + } + + /** + * + * @return bool TRUE if the book is set to type ePub 2 + */ + function isEPubVersion2() { + return $this->bookVersion === EPub::BOOK_VERSION_EPUB2; + } + + /** + * + * Enter description here ... + * + * @param string $uid + */ + function setUid($uid) { + $this->uid = is_string($uid) ? trim($uid) : NULL; + } + + /** + * + * Enter description here ... + * + * @param string $docTitle + */ + function setDocTitle($docTitle) { + $this->docTitle = is_string($docTitle) ? trim($docTitle) : NULL; + } + + /** + * + * Enter description here ... + * + * @param string $docAuthor + */ + function setDocAuthor($docAuthor) { + $this->docAuthor = is_string($docAuthor) ? trim($docAuthor) : NULL; + } + + /** + * + * Enter description here ... + * + * @param string $languageCode + */ + function setLanguageCode($languageCode) { + $this->languageCode = is_string($languageCode) ? trim($languageCode) : "en"; + } + + /** + * + * Enter description here ... + * + * @param string $writingDirection + */ + function setWritingDirection($writingDirection) { + $this->writingDirection = is_string($writingDirection) ? trim($writingDirection) : EPub::DIRECTION_LEFT_TO_RIGHT; + } + + /** + * + * Enter description here ... + * + * @param NavMap $navMap + */ + function setNavMap($navMap) { + if ($navMap != NULL && is_object($navMap) && get_class($navMap) === "NavMap") { + $this->navMap = $navMap; + } + } + + /** + * Add one chapter level. + * + * Subsequent chapters will be added to this level. + * + * @param string $navTitle + * @param string $navId + * @param string $navClass + * @param string $isNavHidden + * @param string $writingDirection + * @return NavPoint + */ + function subLevel($navTitle = NULL, $navId = NULL, $navClass = NULL, $isNavHidden = FALSE, $writingDirection = NULL) { + $navPoint = FALSE; + if (isset($navTitle) && isset($navClass)) { + $navPoint = new NavPoint($navTitle, NULL, $navId, $navClass, $isNavHidden, $writingDirection); + $this->addNavPoint($navPoint); + } + if ($this->lastLevel !== NULL) { + $this->currentLevel = $this->lastLevel; + } + return $navPoint; + } + + /** + * Step back one chapter level. + * + * Subsequent chapters will be added to this chapters parent level. + */ + function backLevel() { + $this->lastLevel = $this->currentLevel; + $this->currentLevel = $this->currentLevel->getParent(); + } + + /** + * Step back to the root level. + * + * Subsequent chapters will be added to the rooot NavMap. + */ + function rootLevel() { + $this->lastLevel = $this->currentLevel; + $this->currentLevel = $this->navMap; + } + + /** + * 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) { + if ($newLevel <= 1) { + $this->rootLevel(); + } else { + while ($this->currentLevel->getLevel() > $newLevel) { + $this->backLevel(); + } + } + } + + /** + * Get current level count. + * The indentation of the current structure point. + * + * @return current level count; + */ + function getCurrentLevel() { + return $this->currentLevel->getLevel(); + } + + /** + * Add child NavPoints to current level. + * + * @param NavPoint $navPoint + */ + function addNavPoint($navPoint) { + $this->lastLevel = $this->currentLevel->addNavPoint($navPoint); + } + + /** + * + * Enter description here ... + * + * @return NavMap + */ + function getNavMap() { + return $this->navMap; + } + + /** + * + * Enter description here ... + * + * @param string $name + * @param string $content + */ + function addMetaEntry($name, $content) { + $name = is_string($name) ? trim($name) : NULL; + $content = is_string($content) ? trim($content) : NULL; + + if ($name != NULL && $content != NULL) { + $this->meta[] = array($name => $content); + } + } + + /** + * + * Enter description here ... + * + * @return string + */ + function finalize() { + $nav = $this->navMap->finalize(); + + $ncx = "\n"; + if ($this->isEPubVersion2()) { + $ncx .= "\n"; + } + $ncx .= "languageCode . "\" dir=\"" . $this->writingDirection . "\">\n" + . "\t\n" + . "\t\tuid . "\" />\n" + . "\t\tnavMap->getNavLevels() . "\" />\n" + . "\t\t\n" + . "\t\t\n"; + + if (sizeof($this->meta)) { + foreach ($this->meta as $metaEntry) { + list($name, $content) = each($metaEntry); + $ncx .= "\t\t\n"; + } + } + + $ncx .= "\t\n\n\t\n\t\t" + . $this->docTitle + . "\n\t\n\n\t\n\t\t" + . $this->docAuthor + . "\n\t\n\n" + . $nav; + + return $ncx . "\n"; + } + + /** + * + * @param string $title + * @param string $cssFileName + * @return string + */ + function finalizeEPub3($title = "Table of Contents", $cssFileName = NULL) { + $end = "\n" + . "languageCode . "\" lang=\"" . $this->languageCode . "\" dir=\"" . $this->writingDirection . "\">\n" + . "\t\n" + . "\t\t" . $this->docTitle . "\n" + . "\t\t\n"; + if ($cssFileName !== NULL) { + $end .= "\t\t\n"; + } + $end .= "\t\n" + . "\t\n" + . "\t\t
\n" + . "\t\t\t

" . $title . "

\n" + . "\t\t
\n" + . $this->navMap->finalizeEPub3() + . $this->finalizeEPub3Landmarks() + . "\t\n" + . "\n"; + + return $end; + } + + /** + * Build the references for the ePub 2 toc. + * These are merely reference pages added to the end of the navMap though. + * + * @return string + */ + function finalizeReferences() { + if (isset($this->referencesList) && sizeof($this->referencesList) > 0) { + $this->rootLevel(); + $this->subLevel($this->referencesTitle, $this->referencesId, $this->referencesClass); + $refId = 1; + while (list($item, $descriptive) = each($this->referencesOrder)) { + if (array_key_exists($item, $this->referencesList)) { + $name = (empty($this->referencesName[$item]) ? $descriptive : $this->referencesName[$item]); + $navPoint = new NavPoint($name, $this->referencesList[$item], "ref-" . $refId++); + $this->addNavPoint($navPoint); + } + } + } + } + + /** + * Build the landmarks for the ePub 3 toc. + * @return string + */ + function finalizeEPub3Landmarks() { + $lm = ""; + if (isset($this->referencesList) && sizeof($this->referencesList) > 0) { + $lm = "\t\t\t\n"; + } + return $lm; + } +} + +/** + * ePub NavMap class + */ +class NavMap { + const _VERSION = 3.00; + + private $navPoints = array(); + private $navLevels = 0; + private $writingDirection = NULL; + + /** + * Class constructor. + * + * @return void + */ + function __construct($writingDirection = NULL) { + $this->setWritingDirection($writingDirection); + } + + /** + * Class destructor + * + * @return void + */ + function __destruct() { + unset($this->navPoints, $this->navLevels, $this->writingDirection); + } + + /** + * Set the writing direction to be used for this NavPoint. + * + * @param string $writingDirection + */ + function setWritingDirection($writingDirection) { + $this->writingDirection = isset($writingDirection) && is_string($writingDirection) ? trim($writingDirection) : NULL; + } + + function getWritingDirection() { + return $this->writingDirection; + } + + /** + * Add a navPoint to the root of the NavMap. + * + * @param NavPoint $navPoint + * @return NavMap + */ + function addNavPoint($navPoint) { + if ($navPoint != NULL && is_object($navPoint) && get_class($navPoint) === "NavPoint") { + $navPoint->setParent($this); + if ($navPoint->getWritingDirection() == NULL) { + $navPoint->setWritingDirection($this->writingDirection); + } + $this->navPoints[] = $navPoint; + return $navPoint; + } + return $this; + } + + /** + * The final max depth for the "dtb:depth" meta attribute + * Only available after finalize have been called. + * + * @return number + */ + function getNavLevels() { + return $this->navLevels+1; + } + + function getLevel() { + return 1; + } + + function getParent() { + return $this; + } + + /** + * Finalize the navMap, the final max depth for the "dtb:depth" meta attribute can be retrieved with getNavLevels after finalization + * + */ + function finalize() { + $playOrder = 0; + $this->navLevels = 0; + + $nav = "\t\n"; + if (sizeof($this->navPoints) > 0) { + $this->navLevels++; + foreach ($this->navPoints as $navPoint) { + $retLevel = $navPoint->finalize($nav, $playOrder, 0); + if ($retLevel > $this->navLevels) { + $this->navLevels = $retLevel; + } + } + } + return $nav . "\t\n"; + } + + /** + * Finalize the navMap, the final max depth for the "dtb:depth" meta attribute can be retrieved with getNavLevels after finalization + * + */ + function finalizeEPub3() { + $playOrder = 0; + $level = 0; + $this->navLevels = 0; + + $nav = "\t\t\n"; + } +} + +/** + * ePub NavPoint class + */ +class NavPoint { + const _VERSION = 3.00; + + private $label = NULL; + private $contentSrc = NULL; + private $id = NULL; + private $navClass = NULL; + private $isNavHidden = FALSE; + private $navPoints = array(); + private $parent = NULL; + + /** + * Class constructor. + * + * All three attributes are mandatory, though if ID is set to null (default) the value will be generated. + * + * @param string $label + * @param string $contentSrc + * @param string $id + * @param string $navClass + * @param bool $isNavHidden + * @param string $writingDirection + */ + function __construct($label, $contentSrc = NULL, $id = NULL, $navClass = NULL, $isNavHidden = FALSE, $writingDirection = NULL) { + $this->setLabel($label); + $this->setContentSrc($contentSrc); + $this->setId($id); + $this->setNavClass($navClass); + $this->setNavHidden($isNavHidden); + $this->setWritingDirection($writingDirection); + } + + /** + * Class destructor + * + * @return void + */ + function __destruct() { + unset($this->label, $this->contentSrc, $this->id, $this->navClass); + unset($this->isNavHidden, $this->navPoints, $this->parent); + } + + /** + * Set the Text label for the NavPoint. + * + * The label is mandatory. + * + * @param string $label + */ + function setLabel($label) { + $this->label = is_string($label) ? trim($label) : NULL; + } + + /** + * Get the Text label for the NavPoint. + * + * @return string Label + */ + function getLabel() { + return $this->label; + } + + /** + * Set the src reference for the NavPoint. + * + * The src is mandatory for ePub 2. + * + * @param string $contentSrc + */ + function setContentSrc($contentSrc) { + $this->contentSrc = isset($contentSrc) && is_string($contentSrc) ? trim($contentSrc) : NULL; + } + + /** + * Get the src reference for the NavPoint. + * + * @return string content src url. + */ + function getContentSrc() { + return $this->contentSrc; + } + /** + * Set the parent for this NavPoint. + * + * @param NavPoint or NavMap $parent + */ + function setParent($parent) { + if ($parent != NULL && is_object($parent) && + (get_class($parent) === "NavPoint" || get_class($parent) === "NavMap") ) { + $this->parent = $parent; + } + } + + /** + * Get the parent to this NavPoint. + * + * @return NavPoint, or NavMap if the parent is the root. + */ + function getParent() { + return $this->parent; + } + + /** + * Get the current level. 1 = document root. + * + * @return int level + */ + function getLevel() { + return $this->parent === NULL ? 1 : $this->parent->getLevel()+1; + } + + /** + * Set the id for the NavPoint. + * + * The id must be unique, and is mandatory. + * + * @param string $id + */ + function setId($id) { + $this->id = is_string($id) ? trim($id) : NULL; + } + + /** + * Set the class to be used for this NavPoint. + * + * @param string $navClass + */ + function setNavClass($navClass) { + $this->navClass = isset($navClass) && is_string($navClass) ? trim($navClass) : NULL; + } + + /** + * Set the class to be used for this NavPoint. + * + * @param string $navClass + */ + function setNavHidden($isNavHidden) { + $this->isNavHidden = $isNavHidden === TRUE; + } + + /** + * Set the writing direction to be used for this NavPoint. + * + * @param string $writingDirection + */ + function setWritingDirection($writingDirection) { + $this->writingDirection = isset($writingDirection) && is_string($writingDirection) ? trim($writingDirection) : NULL; + } + + function getWritingDirection() { + return $this->writingDirection; + } + + /** + * Add child NavPoints for multi level NavMaps. + * + * @param NavPoint $navPoint + */ + function addNavPoint($navPoint) { + if ($navPoint != NULL && is_object($navPoint) && get_class($navPoint) === "NavPoint") { + $navPoint->setParent($this); + if ($navPoint->getWritingDirection() == NULL) { + $navPoint->setWritingDirection($this->writingDirection); + } + $this->navPoints[] = $navPoint; + return $navPoint; + } + return $this; + } + + /** + * + * Enter description here ... + * + * @param string $nav + * @param int $playOrder + * @param int $level + * @return int + */ + function finalize(&$nav = "", &$playOrder = 0, $level = 0) { + $maxLevel = $level; + $levelAdjust = 0; + + if ($this->isNavHidden) { + return $maxLevel; + } + + if (isset($this->contentSrc)) { + $playOrder++; + + if ($this->id == NULL) { + $this->id = "navpoint-" . $playOrder; + } + $nav .= str_repeat("\t", $level) . "\t\tid . "\" playOrder=\"" . $playOrder . "\">\n" + . str_repeat("\t", $level) . "\t\t\t\n" + . str_repeat("\t", $level) . "\t\t\t\t" . $this->label . "\n" + . str_repeat("\t", $level) . "\t\t\t\n" + . str_repeat("\t", $level) . "\t\t\tcontentSrc . "\" />\n"; + } else { + $levelAdjust++; + } + + if (sizeof($this->navPoints) > 0) { + $maxLevel++; + foreach ($this->navPoints as $navPoint) { + $retLevel = $navPoint->finalize($nav, $playOrder, ($level+1+$levelAdjust)); + if ($retLevel > $maxLevel) { + $maxLevel = $retLevel; + } + } + } + + if (isset($this->contentSrc)) { + $nav .= str_repeat("\t", $level) . "\t\t\n"; + } + + return $maxLevel; + } + + /** + * + * Enter description here ... + * + * @param string $nav + * @param int $playOrder + * @param int $level + * @return int + */ + function finalizeEPub3(&$nav = "", &$playOrder = 0, $level = 0, $subLevelClass = NULL, $subLevelHidden = FALSE) { + $maxLevel = $level; + + if ($this->id == NULL) { + $this->id = "navpoint-" . $playOrder; + } + $indent = str_repeat("\t", $level) . "\t\t\t\t"; + + $nav .= $indent . "
  • id . "\""; + if (isset($this->writingDirection)) { + $nav .= " dir=\"" . $this->writingDirection . "\""; + } + $nav .= ">\n"; + + if (isset($this->contentSrc)) { + $nav .= $indent . "\tcontentSrc . "\">" . $this->label . "\n"; + } else { + $nav .= $indent . "\t" . $this->label . "\n"; + } + + if (sizeof($this->navPoints) > 0) { + $maxLevel++; + + $nav .= $indent . "\t
      navPoints as $navPoint) { + $retLevel = $navPoint->finalizeEPub3($nav, $playOrder, ($level+2), $subLevelClass, $subLevelHidden); + if ($retLevel > $maxLevel) { + $maxLevel = $retLevel; + } + } + $nav .= $indent . "\t
    \n"; + } + + $nav .= $indent . "
  • \n"; + + return $maxLevel; + } +} +?> \ No newline at end of file diff --git a/inc/3rdparty/libraries/PHPePub/EPub.OPF.php b/inc/3rdparty/libraries/PHPePub/EPub.OPF.php new file mode 100644 index 00000000..803a2108 --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/EPub.OPF.php @@ -0,0 +1,1226 @@ + + * @copyright 2009-2014 A. Grandt + * @license GNU LGPL, Attribution required for commercial implementations, requested for everything else. + * @version 3.20 + */ +class Opf { + const _VERSION = 3.20; + + /* Core Media types. + * These types are the only guaranteed mime types any ePub reader must understand. + * Any other type muse define a fall back whose fallback chain will end in one of these. + */ + const TYPE_GIF = "image/gif"; + const TYPE_JPEG = "image/jpeg"; + const TYPE_PNG = "image/png"; + const TYPE_SVG = "image/svg+xml"; + const TYPE_XHTML = "application/xhtml+xml"; + const TYPE_DTBOOK = "application/x-dtbook+xml"; + const TYPE_CSS = "text/css"; + const TYPE_XML = "application/xml"; + const TYPE_OEB1_DOC = "text/x-oeb1-document"; // Deprecated + const TYPE_OEB1_CSS = "text/x-oeb1-css"; // Deprecated + const TYPE_NCX = "application/x-dtbncx+xml"; + + private $bookVersion = EPub::BOOK_VERSION_EPUB2; + private $ident = "BookId"; + + public $date = NULL; + public $metadata = NULL; + public $manifest = NULL; + public $spine = NULL; + public $guide = NULL; + + /** + * Class constructor. + * + * @return void + */ + function __construct($ident = "BookId", $bookVersion = EPub::BOOK_VERSION_EPUB2) { + $this->setIdent($ident); + $this->setVersion($bookVersion); + $this->metadata = new Metadata(); + $this->manifest = new Manifest(); + $this->spine = new Spine(); + $this->guide = new Guide(); + } + + /** + * Class destructor + * + * @return void + */ + function __destruct() { + unset ($this->bookVersion, $this->ident, $this->date, $this->metadata, $this->manifest, $this->spine, $this->guide); + } + + /** + * + * Enter description here ... + * + * @param string $ident + */ + function setVersion($bookVersion) { + $this->bookVersion = is_string($bookVersion) ? trim($bookVersion) : EPub::BOOK_VERSION_EPUB2; + } + + function isEPubVersion2() { + return $this->bookVersion === EPub::BOOK_VERSION_EPUB2; + } + + /** + * + * Enter description here ... + * + * @param string $ident + */ + function setIdent($ident = "BookId") { + $this->ident = is_string($ident) ? trim($ident) : "BookId"; + } + + /** + * + * Enter description here ... + * + * @return string + */ + function finalize() { + $opf = "\n" + . "ident . "\" version=\"" . $this->bookVersion . "\">\n"; + + $opf .= $this->metadata->finalize($this->bookVersion, $this->date); + $opf .= $this->manifest->finalize($this->bookVersion); + $opf .= $this->spine->finalize(); + + if ($this->guide->length() > 0) { + $opf .= $this->guide->finalize(); + } + + return $opf . "\n"; + } + + // Convenience functions: + + /** + * + * Enter description here ... + * + * @param string $title + * @param string $language + * @param string $identifier + * @param string $identifierScheme + */ + function initialize($title, $language, $identifier, $identifierScheme) { + $this->metadata->addDublinCore(new DublinCore("title", $title)); + $this->metadata->addDublinCore(new DublinCore("language", $language)); + + $dc = new DublinCore("identifier", $identifier); + $dc->addAttr("id", $this->ident); + $dc->addOpfAttr("scheme", $identifierScheme); + $this->metadata->addDublinCore($dc); + } + + /** + * + * Enter description here ... + * + * @param string $id + * @param string $href + * @param string $mediaType + */ + function addItem($id, $href, $mediaType, $properties = NULL) { + $this->manifest->addItem(new Item($id, $href, $mediaType, $properties)); + } + + /** + * + * Enter description here ... + * + * @param string $idref + * @param bool $linear + */ + function addItemRef($idref, $linear = TRUE) { + $this->spine->addItemref(new Itemref($idref, $linear)); + } + + /** + * + * Enter description here ... + * + * @param string $type + * @param string $title + * @param string $href + */ + function addReference($type, $title, $href) { + $this->guide->addReference(new Reference($type, $title, $href)); + } + + /** + * + * Enter description here ... + * + * @param string $name + * @param string $value + */ + function addDCMeta($name, $value) { + $this->metadata->addDublinCore(new DublinCore($name, $value)); + } + + /** + * + * Enter description here ... + * + * @param string $name + * @param string $content + */ + function addMeta($name, $content) { + $this->metadata->addMeta($name, $content); + } + + /** + * + * Enter description here ... + * + * @param string $name + * @param string $fileAs + * @param string $role Use the MarcCode constants + */ + function addCreator($name, $fileAs = NULL, $role = NULL) { + $dc = new DublinCore(DublinCore::CREATOR, trim($name)); + + if ($fileAs !== NULL) { + $dc->addOpfAttr("file-as", trim($fileAs)); + } + + if ($role !== NULL) { + $dc->addOpfAttr("role", trim($role)); + } + + $this->metadata->addDublinCore($dc); + } + + /** + * + * Enter description here ... + * + * @param string $name + * @param string $fileAs + * @param string $role Use the MarcCode constants + */ + function addColaborator($name, $fileAs = NULL, $role = NULL) { + $dc = new DublinCore(DublinCore::CONTRIBUTOR, trim($name)); + + if ($fileAs !== NULL) { + $dc->addOpfAttr("file-as", trim($fileAs)); + } + + if ($role !== NULL) { + $dc->addOpfAttr("role", trim($role)); + } + + $this->metadata->addDublinCore($dc); + } +} + +/** + * ePub OPF Metadata structures + */ +class Metadata { + const _VERSION = 3.00; + + private $dc = array(); + private $meta = array(); + + /** + * Class constructor. + * + * @return void + */ + function __construct() { + } + + /** + * Class destructor + * + * @return void + */ + function __destruct() { + unset ($this->dc, $this->meta); + } + + /** + * + * Enter description here ... + * + * @param DublinCore $dc + */ + function addDublinCore($dc) { + if ($dc != NULL && is_object($dc) && get_class($dc) === "DublinCore") { + $this->dc[] = $dc; + } + } + + /** + * + * Enter description here ... + * + * @param string $name + * @param string $content + */ + function addMeta($name, $content) { + $name = is_string($name) ? trim($name) : NULL; + if (isset($name)) { + $content = is_string($content) ? trim($content) : NULL; + } + if (isset($content)) { + $this->meta[] = array ($name => $content); + } + } + + /** + * + * @param string $bookVersion + * @param int $date + * @return string + */ + function finalize($bookVersion = EPub::BOOK_VERSION_EPUB2, $date = NULL) { + $metadata = "\t\n"; + } else { + $metadata .= "\t\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"; + if (!isset($date)) { + $date = time(); + } + $metadata .= "\t\t" . gmdate("Y-m-d\TH:i:s\Z", $date) . "\n"; + } + + foreach ($this->dc as $dc) { + $metadata .= $dc->finalize($bookVersion); + } + + foreach ($this->meta as $data) { + list($name, $content) = each($data); + $metadata .= "\t\t\n"; + } + + return $metadata . "\t\n"; + } +} + +/** + * ePub OPF Dublin Core (dc:) Metadata structures + */ +class DublinCore { + const _VERSION = 3.00; + + const CONTRIBUTOR = "contributor"; + const COVERAGE = "coverage"; + const CREATOR = "creator"; + const DATE = "date"; + const DESCRIPTION = "description"; + const FORMAT = "format"; + const IDENTIFIER = "identifier"; + const LANGUAGE = "language"; + const PUBLISHER = "publisher"; + const RELATION = "relation"; + const RIGHTS = "rights"; + const SOURCE = "source"; + const SUBJECT = "subject"; + const TITLE = "title"; + const TYPE = "type"; + + private $dcName = NULL; + private $dcValue = NULL; + private $attr = array(); + private $opfAttr = array(); + + /** + * Class constructor. + * + * @return void + */ + function __construct($name, $value) { + $this->setDc($name, $value); + } + + /** + * Class destructor + * + * @return void + */ + function __destruct() { + unset ($this->dcName, $this->dcValue, $this->attr, $this->opfAttr); + } + + /** + * + * Enter description here ... + * + * @param string $name + * @param string $value + */ + function setDc($name, $value) { + $this->dcName = is_string($name) ? trim($name) : NULL; + if (isset($this->dcName)) { + $this->dcValue = isset($value) ? (string)$value : NULL; + } + if (! isset($this->dcValue)) { + $this->dcName = NULL; + } + } + + /** + * + * Enter description here ... + * + * @param string $attrName + * @param string $attrValue + */ + function addAttr($attrName, $attrValue) { + $attrName = is_string($attrName) ? trim($attrName) : NULL; + if (isset($attrName)) { + $attrValue = is_string($attrValue) ? trim($attrValue) : NULL; + } + if (isset($attrValue)) { + $this->attr[$attrName] = $attrValue; + } + } + + /** + * + * Enter description here ... + * + * @param string $opfAttrName + * @param string $opfAttrValue + */ + function addOpfAttr($opfAttrName, $opfAttrValue) { + $opfAttrName = is_string($opfAttrName) ? trim($opfAttrName) : NULL; + if (isset($opfAttrName)) { + $opfAttrValue = is_string($opfAttrValue) ? trim($opfAttrValue) : NULL; + } + if (isset($opfAttrValue)) { + $this->opfAttr[$opfAttrName] = $opfAttrValue; + } + } + + + /** + * + * @param string $bookVersion + * @return string + */ + function finalize($bookVersion = EPub::BOOK_VERSION_EPUB2) { + $dc = "\t\tdcName; + + if (sizeof($this->attr) > 0) { + while (list($name, $content) = each($this->attr)) { + $dc .= " " . $name . "=\"" . $content . "\""; + } + } + + if ($bookVersion === EPub::BOOK_VERSION_EPUB2 && sizeof($this->opfAttr) > 0) { + while (list($name, $content) = each($this->opfAttr)) { + $dc .= " opf:" . $name . "=\"" . $content . "\""; + } + } + + return $dc . ">" . $this->dcValue . "dcName . ">\n"; + } +} + +/** + * ePub OPF Manifest structure + */ +class Manifest { + const _VERSION = 3.00; + + private $items = array(); + + /** + * Class constructor. + * + * @return void + */ + function __construct() { + } + + /** + * Class destructor + * + * @return void + */ + function __destruct() { + unset ($this->items); + } + + /** + * + * Enter description here ... + * + * @param Item $item + */ + function addItem($item) { + if ($item != NULL && is_object($item) && get_class($item) === "Item") { + $this->items[] = $item; + } + } + + /** + * + * @param string $bookVersion + * @return string + */ + function finalize($bookVersion = EPub::BOOK_VERSION_EPUB2) { + $manifest = "\n\t\n"; + foreach ($this->items as $item) { + $manifest .= $item->finalize($bookVersion); + } + return $manifest . "\t\n"; + } +} + +/** + * ePub OPF Item structure + */ +class Item { + const _VERSION = 3.00; + + private $id = NULL; + private $href = NULL; + private $mediaType = NULL; + private $properties = NULL; + private $requiredNamespace = NULL; + private $requiredModules = NULL; + private $fallback = NULL; + private $fallbackStyle = NULL; + + /** + * Class constructor. + * + * @return void + */ + function __construct($id, $href, $mediaType, $properties = NULL) { + $this->setId($id); + $this->setHref($href); + $this->setMediaType($mediaType); + $this->setProperties($properties); + } + + /** + * Class destructor + * + * @return void + */ + function __destruct() { + unset ($this->id, $this->href, $this->mediaType); + unset ($this->properties, $this->requiredNamespace, $this->requiredModules, $this->fallback, $this->fallbackStyle); + } + + /** + * + * Enter description here ... + * + * @param string $id + */ + function setId($id) { + $this->id = is_string($id) ? trim($id) : NULL; + } + + /** + * + * Enter description here ... + * + * @param string $href + */ + function setHref($href) { + $this->href = is_string($href) ? trim($href) : NULL; + } + + /** + * + * Enter description here ... + * + * @param string $mediaType + */ + function setMediaType($mediaType) { + $this->mediaType = is_string($mediaType) ? trim($mediaType) : NULL; + } + + /** + * + * Enter description here ... + * + * @param string $properties + */ + function setProperties($properties) { + $this->properties = is_string($properties) ? trim($properties) : NULL; + } + + /** + * + * Enter description here ... + * + * @param string $requiredNamespace + */ + function setRequiredNamespace($requiredNamespace) { + $this->requiredNamespace = is_string($requiredNamespace) ? trim($requiredNamespace) : NULL; + } + + /** + * + * Enter description here ... + * + * @param string $requiredModules + */ + function setRequiredModules($requiredModules) { + $this->requiredModules = is_string($requiredModules) ? trim($requiredModules) : NULL; + } + + /** + * + * Enter description here ... + * + * @param string $fallback + */ + function setfallback($fallback) { + $this->fallback = is_string($fallback) ? trim($fallback) : NULL; + } + + /** + * + * Enter description here ... + * + * @param string $fallbackStyle + */ + function setFallbackStyle($fallbackStyle) { + $this->fallbackStyle = is_string($fallbackStyle) ? trim($fallbackStyle) : NULL; + } + + /** + * + * @param string $bookVersion + * @return string + */ + function finalize($bookVersion = EPub::BOOK_VERSION_EPUB2) { + $item = "\t\tid . "\" href=\"" . $this->href . "\" media-type=\"" . $this->mediaType . "\" "; + if ($bookVersion === EPub::BOOK_VERSION_EPUB3 && isset($this->properties)) { + $item .= "properties=\"" . $this->properties . "\" "; + } + if (isset($this->requiredNamespace)) { + $item .= "\n\t\t\trequired-namespace=\"" . $this->requiredNamespace . "\" "; + if (isset($this->requiredModules)) { + $item .= "required-modules=\"" . $this->requiredModules . "\" "; + } + } + if (isset($this->fallback)) { + $item .= "\n\t\t\tfallback=\"" . $this->fallback . "\" "; + } + if (isset($this->fallbackStyle)) { + $item .= "\n\t\t\tfallback-style=\"" . $this->fallbackStyle . "\" "; + } + return $item . "/>\n"; + } +} + +/** + * ePub OPF Spine structure + */ +class Spine { + const _VERSION = 1.00; + + private $itemrefs = array(); + private $toc = NULL; + + /** + * Class constructor. + * + * @return void + */ + function __construct($toc = "ncx") { + $this->setToc($toc); + } + + /** + * Class destructor + * + * @return void + */ + function __destruct() { + unset ($this->itemrefs, $this->toc); + } + + /** + * + * Enter description here ... + * + * @param string $toc + */ + function setToc($toc) { + $this->toc = is_string($toc) ? trim($toc) : NULL; + } + + /** + * + * Enter description here ... + * + * @param Itemref $itemref + */ + function addItemref($itemref) { + if ($itemref != NULL + && is_object($itemref) + && get_class($itemref) === "Itemref" + && !isset($this->itemrefs[$itemref->getIdref()])) { + $this->itemrefs[$itemref->getIdref()] = $itemref; + } + } + + /** + * + * Enter description here ... + * + * @return string + */ + function finalize() { + $spine = "\n\ttoc . "\">\n"; + foreach ($this->itemrefs as $itemref) { + $spine .= $itemref->finalize(); + } + return $spine . "\t\n"; + } +} + +/** + * ePub OPF ItemRef structure + */ +class Itemref { + const _VERSION = 3.00; + + private $idref = NULL; + private $linear = TRUE; + + /** + * Class constructor. + * + * @return void + */ + function __construct($idref, $linear = TRUE) { + $this->setIdref($idref); + $this->setLinear($linear); + } + + /** + * Class destructor + * + * @return void + */ + function __destruct() { + unset ($this->idref, $this->linear); + } + + /** + * + * Enter description here ... + * + * @param string $idref + */ + function setIdref($idref) { + $this->idref = is_string($idref) ? trim($idref) : NULL; + } + + /** + * + * Enter description here ... + * + * @return string $idref + */ + function getIdref() { + return $this->idref; + } + + /** + * + * Enter description here ... + * + * @param bool $linear + */ + function setLinear($linear = TRUE) { + $this->linear = $linear === TRUE; + } + + /** + * + * Enter description here ... + * + * @return string + */ + function finalize() { + $itemref = "\t\tidref . "\""; + if ($this->linear == FALSE) { + return $itemref .= " linear=\"no\" />\n"; + } + return $itemref . " />\n"; + } +} + +/** + * ePub OPF Guide structure + */ +class Guide { + const _VERSION = 3.00; + + private $references = array(); + + /** + * Class constructor. + * + * @return void + */ + function __construct() { + } + + /** + * Class destructor + * + * @return void + */ + function __destruct() { + unset ($this->references); + } + + /** + * + * Enter description here ... + * + */ + function length() { + return sizeof($this->references); + } + + /** + * + * Enter description here ... + * + * @param Reference $reference + */ + function addReference($reference) { + if ($reference != NULL && is_object($reference) && get_class($reference) === "Reference") { + $this->references[] = $reference; + } + } + + /** + * + * Enter description here ... + * + * @return string + */ + function finalize() { + $ref = ""; + if (sizeof($this->references) > 0) { + $ref = "\n\t\n"; + foreach ($this->references as $reference) { + $ref .= $reference->finalize(); + } + $ref .= "\t\n"; + } + return $ref; + } +} + +/** + * Reference constants + */ +class Reference { + const _VERSION = 1.00; + + /* REFERENCE types are derived from the "Chicago Manual of Style" + */ + + /** Acknowledgements page */ + const ACKNOWLEDGEMENTS = "acknowledgements"; + + /** Bibliography page */ + const BIBLIOGRAPHY = "bibliography"; + + /** Colophon page */ + const COLOPHON = "colophon"; + + /** Copyright page */ + const COPYRIGHT_PAGE = "copyright-page"; + + /** Dedication */ + const DEDICATION = "dedication"; + + /** Epigraph */ + const EPIGRAPH = "epigraph"; + + /** Foreword */ + const FOREWORD = "foreword"; + + /** Glossary page */ + const GLOSSARY = "glossary"; + + /** back-of-book style index */ + const INDEX = "index"; + + /** List of illustrations */ + const LIST_OF_ILLUSTRATIONS = "loi"; + + /** List of tables */ + const LIST_OF_TABLES = "lot"; + + /** Notes page */ + const NOTES = "notes"; + + /** Preface page */ + const PREFACE = "preface"; + + /** Table of contents */ + const TABLE_OF_CONTENTS = "toc"; + + /** Page with possibly title, author, publisher, and other metadata */ + const TITLE_PAGE = "titlepage"; + + /** First page of the book, ie. first page of the first chapter */ + const TEXT = "text"; + + // ****************** + // ePub3 constants + // ****************** + + // Document partitions + /** The publications cover(s), jacket information, etc. This is officially in ePub3, but works for ePub 2 as well */ + const COVER = "cover"; + + /** Preliminary material to the content body, such as tables of contents, dedications, etc. */ + const FRONTMATTER = "frontmatter"; + + /** The main (body) content of a document. */ + const BODYMATTER = "bodymatter"; + + /** Ancillary material occurring after the document body, such as indices, appendices, etc. */ + const BACKMATTER = "backmatter"; + + + private $type = NULL; + private $title = NULL; + private $href = NULL; + + /** + * Class constructor. + * + * @param string $type + * @param string $title + * @param string $href + */ + function __construct($type, $title, $href) { + $this->setType($type); + $this->setTitle($title); + $this->setHref($href); + } + + /** + * Class destructor + * + * @return void + */ + function __destruct() { + unset ($this->type, $this->title, $this->href); + } + + /** + * + * Enter description here ... + * + * @param string $type + */ + function setType($type) { + $this->type = is_string($type) ? trim($type) : NULL; + } + + /** + * + * Enter description here ... + * + * @param string $title + */ + function setTitle($title) { + $this->title = is_string($title) ? trim($title) : NULL; + } + + /** + * + * Enter description here ... + * + * @param string $href + */ + function setHref($href) { + $this->href = is_string($href) ? trim($href) : NULL; + } + + /** + * + * Enter description here ... + * + * @return string + */ + function finalize() { + return "\t\ttype . "\" title=\"" . $this->title . "\" href=\"" . $this->href . "\" />\n"; + } +} + +/** + * Common Marc codes. + * Ref: http://www.loc.gov/marc/relators/ + */ +class MarcCode { + const _VERSION = 3.00; + + /** + * Adapter + * + * Use for a person who + * 1) reworks a musical composition, usually for a different medium, or + * 2) rewrites novels or stories for motion pictures or other audiovisual medium. + */ + const ADAPTER = "adp"; + + /** + * Annotator + * + * Use for a person who writes manuscript annotations on a printed item. + */ + const ANNOTATOR = "ann"; + + /** + * Arranger + * + * Use for a person who transcribes a musical composition, usually for a different + * medium from that of the original; in an arrangement the musical substance remains + * essentially unchanged. + */ + const ARRANGER = "arr"; + + /** + * Artist + * + * Use for a person (e.g., a painter) who conceives, and perhaps also implements, + * an original graphic design or work of art, if specific codes (e.g., [egr], + * [etr]) are not desired. For book illustrators, prefer Illustrator [ill]. + */ + const ARTIST = "art"; + + /** + * Associated name + * + * Use as a general relator for a name associated with or found in an item or + * collection, or which cannot be determined to be that of a Former owner [fmo] + * or other designated relator indicative of provenance. + */ + const ASSOCIATED_NAME = "asn"; + + /** + * Author + * + * Use for a person or corporate body chiefly responsible for the intellectual + * or artistic content of a work. This term may also be used when more than one + * person or body bears such responsibility. + */ + const AUTHOR = "aut"; + + /** + * Author in quotations or text extracts + * + * Use for a person whose work is largely quoted or extracted in a works to which + * he or she did not contribute directly. Such quotations are found particularly + * in exhibition catalogs, collections of photographs, etc. + */ + const AUTHOR_IN_QUOTES = "aqt"; + + /** + * Author of afterword, colophon, etc. + * + * Use for a person or corporate body responsible for an afterword, postface, + * colophon, etc. but who is not the chief author of a work. + */ + const AUTHOR_OF_AFTERWORD = "aft"; + + /** + * Author of introduction, etc. + * + * Use for a person or corporate body responsible for an introduction, preface, + * foreword, or other critical matter, but who is not the chief author. + */ + const AUTHOR_OF_INTRO = "aui"; + + /** + * Bibliographic antecedent + * + * Use for the author responsible for a work upon which the work represented by + * the catalog record is based. This can be appropriate for adaptations, sequels, + * continuations, indexes, etc. + */ + const BIB_ANTECEDENT = "ant"; + + /** + * Book producer + * + * Use for the person or firm responsible for the production of books and other + * print media, if specific codes (e.g., [bkd], [egr], [tyd], [prt]) are not desired. + */ + const BOOK_PRODUCER = "bkp"; + + /** + * Collaborator + * + * Use for a person or corporate body that takes a limited part in the elaboration + * of a work of another author or that brings complements (e.g., appendices, notes) + * to the work of another author. + */ + const COLABORATOR = "clb"; + + /** + * Commentator + * + * Use for a person who provides interpretation, analysis, or a discussion of the + * subject matter on a recording, motion picture, or other audiovisual medium. + * Compiler [com] Use for a person who produces a work or publication by selecting + * and putting together material from the works of various persons or bodies. + */ + const COMMENTATOR = "cmm"; + + /** + * Designer + * + * Use for a person or organization responsible for design if specific codes (e.g., + * [bkd], [tyd]) are not desired. + */ + const DESIGNER = "dsr"; + + /** + * Editor + * + * Use for a person who prepares for publication a work not primarily his/her own, + * such as by elucidating text, adding introductory or other critical matter, or + * technically directing an editorial staff. + */ + const EDITORT = "edt"; + + /** + * Illustrator + * + * Use for the person who conceives, and perhaps also implements, a design or + * illustration, usually to accompany a written text. + */ + const ILLUSTRATOR = "ill"; + + /** + * Lyricist + * + * Use for the writer of the text of a song. + */ + const LYRICIST = "lyr"; + + /** + * Metadata contact + * + * Use for the person or organization primarily responsible for compiling and + * maintaining the original description of a metadata set (e.g., geospatial + * metadata set). + */ + const METADATA_CONTACT = "mdc"; + + /** + * Musician + * + * Use for the person who performs music or contributes to the musical content + * of a work when it is not possible or desirable to identify the function more + * precisely. + */ + const MUSICIAN = "mus"; + + /** + * Narrator + * + * Use for the speaker who relates the particulars of an act, occurrence, or + * course of events. + */ + const NARRATOR = "nrt"; + + /** + * Other + * + * Use for relator codes from other lists which have no equivalent in the MARC + * list or for terms which have not been assigned a code. + */ + const OTHER = "oth"; + + /** + * Photographer + * + * Use for the person or organization responsible for taking photographs, whether + * they are used in their original form or as reproductions. + */ + const PHOTOGRAPHER = "pht"; + + /** + * Printer + * + * Use for the person or organization who prints texts, whether from type or plates. + */ + const PRINTER = "prt"; + + /** + * Redactor + * + * Use for a person who writes or develops the framework for an item without + * being intellectually responsible for its content. + */ + const REDACTOR = "red"; + + /** + * Reviewer + * + * Use for a person or corporate body responsible for the review of book, motion + * picture, performance, etc. + */ + const REVIEWER = "rev"; + + /** + * Sponsor + * + * Use for the person or agency that issued a contract, or under whose auspices + * a work has been written, printed, published, etc. + */ + const SPONSOR = "spn"; + + /** + * Thesis advisor + * + * Use for the person under whose supervision a degree candidate develops and + * presents a thesis, memoir, or text of a dissertation. + */ + const THESIS_ADVISOR = "ths"; + + /** + * Transcriber + * + * Use for a person who prepares a handwritten or typewritten copy from original + * material, including from dictated or orally recorded material. + */ + const TRANSCRIBER = "trc"; + + /** + * Translator + * + * Use for a person who renders a text from one language into another, or from + * an older form of a language into the modern form. + */ + const TRANSLATOR = "trl"; +} +?> diff --git a/inc/3rdparty/libraries/PHPePub/EPub.php b/inc/3rdparty/libraries/PHPePub/EPub.php new file mode 100644 index 00000000..836c0512 --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/EPub.php @@ -0,0 +1,2429 @@ + + * @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; + + 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 = "\n\n\n\n\n\n\n"; + private $htmlContentFooter = "\n\n"; + + /** + * Class constructor. + * + * @return void + */ + function __construct($bookVersion = EPub::BOOK_VERSION_EPUB2, $languageCode = "en", $writingDirection = EPub::DIRECTION_LEFT_TO_RIGHT) { + include_once("Zip.php"); + include_once("Logger.php"); + + $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("

    EPub version " . self::VERSION . " requires Zip.php at version " . self::REQ_ZIP_VERSION . " or higher.
    You can obtain the latest version from http://www.phpclasses.org/browse/package/6110.html.

    "); + } + + 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 = "\n\n\t\n\t\tbookRoot . "book.opf\" media-type=\"application/oebps-package+xml\" />\n\t\n\n"; + + if (!$this->isEPubVersion2()) { + $this->htmlContentHeader = "\n" + . "\n" + . "" + . "\n" + . "\n" + . "\n" + . "\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 processCSSExternalReferences for explanation. Default is EPub::EXTERNAL_REF_IGNORE. + * @param string $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE. + * + * @return bool $success + */ + function addCSSFile($fileName, $fileId, $fileData, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") { + if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) { + return FALSE; + } + $fileName = 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 processChapterExternalReferences 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); + + $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 processChapterExternalReferences 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) { + 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 = "\n" + . "\n" + . "\n" + . "\t\n" + . "\t\t\n" + . "\t\tCover Image\n" + . "\t\t\n" + . "\t\n" + . "\t\n" + . "\t\t
    \n" + . "\t\t\t\"Cover\n" + . "\t\t
    \n" + . "\t\n" + . "\n"; + } else { + $coverPage = "\n" + . "\n" + . "" + . "\t\n" + . "\t\tCover Image\n" + . "\t\t\n" + . "\t\n" + . "\t\n" + . "\t\t
    \n" + . "\t\t\t\"Cover\n" + . "\t\t
    \n" + . "\t\n" + . "\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 <img src="../images/image.png"/> + * + * $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("\n\n\n\n"); + $html = $xml2Doc->getElementsByTagName("html")->item(0); + $html->appendChild($xml2Doc->importNode($headNode->item(0), TRUE)); + $html->appendChild($xml2Doc->importNode($bodyNode->item(0), TRUE)); + + // 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*\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, "[" . $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->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
    , 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 = "\n"; + + if ($this->isEPubVersion2()) { + $tocData .= "\n" + . "\n" + . "\n\n"; + } else { + $tocData .= "\n" + . "\n\n"; + } + + if (!empty($this->tocCssFileName)) { + $tocData .= "tocCssFileName . "\" />\n"; + } + + $tocData .= "" . $this->tocTitle . "\n" + . "\n" + . "\n" + . "

    " . $this->tocTitle . "

    \ntocCSSClass)) { + $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

    " . str_repeat("      ", $level) . "" . $chapterName . "

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

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

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

    " . $this->tocTitle . "

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

    " . $descriptive . "

    \n"; + } + } + } + $tocData .= "
    \n\n\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*\s*~i', "\n", $string); + $string = preg_replace('~\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(); + } +} diff --git a/inc/3rdparty/libraries/PHPePub/EPubChapterSplitter.php b/inc/3rdparty/libraries/PHPePub/EPubChapterSplitter.php new file mode 100644 index 00000000..1d44f238 --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/EPubChapterSplitter.php @@ -0,0 +1,201 @@ + + * @copyright 2009-2014 A. Grandt + * @license GNU LGPL 2.1 + * @link http://www.phpclasses.org/package/6115 + * @link https://github.com/Grandt/PHPePub + * @version 3.20 + */ +class EPubChapterSplitter { + const VERSION = 3.20; + + private $splitDefaultSize = 250000; + private $bookVersion = EPub::BOOK_VERSION_EPUB2; + + /** + * + * Enter description here ... + * + * @param unknown_type $ident + */ + function setVersion($bookVersion) { + $this->bookVersion = is_string($bookVersion) ? trim($bookVersion) : EPub::BOOK_VERSION_EPUB2; + } + + /** + * Set default chapter target size. + * Default is 250000 bytes, and minimum is 10240 bytes. + * + * @param $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; + } + + /** + * Split $chapter into multiple parts. + * + * The search string can either be a regular string or a PHP PECL Regular Expression pattern as defined here: http://www.php.net/manual/en/pcre.pattern.php + * If the search string is a regular string, the matching will be for lines in the HTML starting with the string given + * + * @param String $chapter XHTML file + * @param Bool $splitOnSearchString Split on chapter boundaries, Splitting on search strings disables the split size check. + * @param String $searchString Chapter string to search for can be fixed text, or a regular expression pattern. + * + * @return array with 1 or more parts + */ + function splitChapter($chapter, $splitOnSearchString = false, $searchString = '/^Chapter\\ /i') { + $chapterData = array(); + $isSearchRegexp = $splitOnSearchString && (preg_match('#^(\D|\S|\W).+\1[imsxeADSUXJu]*$#m', $searchString) == 1); + if ($splitOnSearchString && !$isSearchRegexp) { + $searchString = '#^<.+?>' . preg_quote($searchString, '#') . "#"; + } + + if (!$splitOnSearchString && strlen($chapter) <= $this->splitDefaultSize) { + return array($chapter); + } + + $xmlDoc = new DOMDocument(); + @$xmlDoc->loadHTML($chapter); + + $head = $xmlDoc->getElementsByTagName("head"); + $body = $xmlDoc->getElementsByTagName("body"); + + $htmlPos = stripos($chapter, "", $htmlPos); + $newXML = substr($chapter, 0, $htmlEndPos+1) . "\n"; + if (strpos(trim($newXML), "\n" . $newXML; + } + $headerLength = strlen($newXML); + + $files = array(); + $chapterNames = array(); + $domDepth = 0; + $domPath = array(); + $domClonedPath = array(); + + $curFile = $xmlDoc->createDocumentFragment(); + $files[] = $curFile; + $curParent = $curFile; + $curSize = 0; + + $bodyLen = strlen($xmlDoc->saveXML($body->item(0))); + $headLen = strlen($xmlDoc->saveXML($head->item(0))) + $headerLength; + + $partSize = $this->splitDefaultSize - $headLen; + + if ($bodyLen > $partSize) { + $parts = ceil($bodyLen / $partSize); + $partSize = ($bodyLen / $parts) - $headLen; + } + + $node = $body->item(0)->firstChild; + + do { + $nodeData = $xmlDoc->saveXML($node); + $nodeLen = strlen($nodeData); + + if ($nodeLen > $partSize && $node->hasChildNodes()) { + $domPath[] = $node; + $domClonedPath[] = $node->cloneNode(false); + $domDepth++; + + $node = $node->firstChild; + } + + $node2 = $node->nextSibling; + + if ($node != null && $node->nodeName != "#text") { + $doSplit = false; + if ($splitOnSearchString) { + $doSplit = preg_match($searchString, $nodeData) == 1; + if ($doSplit) { + $chapterNames[] = trim($nodeData); + } + } + + if ($curSize > 0 && ($doSplit || (!$splitOnSearchString && $curSize + $nodeLen > $partSize))) { + $curFile = $xmlDoc->createDocumentFragment(); + $files[] = $curFile; + $curParent = $curFile; + if ($domDepth > 0) { + reset($domPath); + reset($domClonedPath); + $oneDomClonedPath = each($domClonedPath); + while ($oneDomClonedPath) { + list($k, $v) = $oneDomClonedPath; + $newParent = $v->cloneNode(false); + $curParent->appendChild($newParent); + $curParent = $newParent; + $oneDomClonedPath = each($domClonedPath); + } + } + $curSize = strlen($xmlDoc->saveXML($curFile)); + } + $curParent->appendChild($node->cloneNode(true)); + $curSize += $nodeLen; + } + + $node = $node2; + while ($node == null && $domDepth > 0) { + $domDepth--; + $node = end($domPath)->nextSibling; + array_pop($domPath); + array_pop($domClonedPath); + $curParent = $curParent->parentNode; + } + } while ($node != null); + + $curFile = null; + $curSize = 0; + + $xml = new DOMDocument('1.0', $xmlDoc->xmlEncoding); + $xml->lookupPrefix("http://www.w3.org/1999/xhtml"); + $xml->preserveWhiteSpace = false; + $xml->formatOutput = true; + + for ($idx = 0; $idx < count($files); $idx++) { + $xml2Doc = new DOMDocument('1.0', $xmlDoc->xmlEncoding); + $xml2Doc->lookupPrefix("http://www.w3.org/1999/xhtml"); + $xml2Doc->loadXML($newXML); + $html = $xml2Doc->getElementsByTagName("html")->item(0); + $html->appendChild($xml2Doc->importNode($head->item(0), true)); + $body = $xml2Doc->createElement("body"); + $html->appendChild($body); + $body->appendChild($xml2Doc->importNode($files[$idx], true)); + + // force pretty printing and correct formatting, should not be needed, but it is. + $xml->loadXML($xml2Doc->saveXML()); + + $doc = $xml->saveXML(); + + if ($this->bookVersion === EPub::BOOK_VERSION_EPUB3) { + $doc = preg_replace('#^\s*\s*#im', '', $doc); + } + + $chapterData[$splitOnSearchString ? $chapterNames[$idx] : $idx] = $doc; + } + + return $chapterData; + } +} +?> diff --git a/inc/3rdparty/libraries/PHPePub/Logger.php b/inc/3rdparty/libraries/PHPePub/Logger.php new file mode 100644 index 00000000..314019cb --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/Logger.php @@ -0,0 +1,92 @@ + + * @copyright 2012-2013 A. Grandt + * @license GNU LGPL, Attribution required for commercial implementations, requested for everything else. + * @version 1.00 + */ +class Logger { + const VERSION = 1.00; + + private $log = ""; + private $tStart; + private $tLast; + private $name = NULL; + private $isLogging = FALSE; + private $isDebugging = FALSE; + + /** + * Class constructor. + * + * @return void + */ + function __construct($name = NULL, $isLogging = FALSE) { + if ($name === NULL) { + $this->name = ""; + } else { + $this->name = $name . " : "; + } + $this->isLogging = $isLogging; + $this->start(); + } + + /** + * Class destructor + * + * @return void + * @TODO make sure elements in the destructor match the current class elements + */ + function __destruct() { + unset($this->log); + } + + function start() { + /* Prepare Logging. Just in case it's used. later */ + if ($this->isLogging) { + $this->tStart = gettimeofday(); + $this->tLast = $this->tStart; + $this->log = "

    Log: " . $this->name . "

    \n
    Started: " . gmdate("D, d M Y H:i:s T", $this->tStart['sec']) . "\n Δ Start ;  Δ Last  ;";
    +			$this->logLine("Start");
    +		}
    +    }
    +
    +    function dumpInstalledModules() {
    +        if ($this->isLogging) {
    +            $isCurlInstalled = extension_loaded('curl') && function_exists('curl_version');
    +            $isGdInstalled = extension_loaded('gd') && function_exists('gd_info');
    +            $isExifInstalled = extension_loaded('exif') && function_exists('exif_imagetype');
    +            $isFileGetContentsInstalled = function_exists('file_get_contents');
    +            $isFileGetContentsExtInstalled = $isFileGetContentsInstalled && ini_get('allow_url_fopen');
    +
    +            $this->logLine("isCurlInstalled...............: " . ($isCurlInstalled ? "Yes" : "No"));
    +            $this->logLine("isGdInstalled.................: " . ($isGdInstalled ? "Yes" : "No"));
    +            $this->logLine("isExifInstalled...............: " . ($isExifInstalled ? "Yes" : "No"));
    +            $this->logLine("isFileGetContentsInstalled....: " . ($isFileGetContentsInstalled ? "Yes" : "No"));
    +            $this->logLine("isFileGetContentsExtInstalled.: " . ($isFileGetContentsExtInstalled ? "Yes" : "No"));
    +        }
    +    }
    +
    +    function logLine($line) {
    +        if ($this->isLogging) {
    +            $tTemp = gettimeofday();
    +            $tS = $this->tStart['sec'] + (((int)($this->tStart['usec']/100))/10000);
    +            $tL = $this->tLast['sec'] + (((int)($this->tLast['usec']/100))/10000);
    +            $tT = $tTemp['sec'] + (((int)($tTemp['usec']/100))/10000);
    +
    +			$logline = sprintf("\n+%08.04f; +%08.04f; ", ($tT-$tS), ($tT-$tL)) . $this->name . $line;
    +            $this->log .= $logline;
    +            $this->tLast = $tTemp;
    +
    +		    if ($this->isDebugging) {
    +				echo "
    " . $logline . "\n
    \n"; + } + } + } + + function getLog() { + return $this->log; + } +} +?> \ No newline at end of file diff --git a/inc/3rdparty/libraries/PHPePub/Zip.php b/inc/3rdparty/libraries/PHPePub/Zip.php new file mode 100644 index 00000000..01e03566 --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/Zip.php @@ -0,0 +1,818 @@ + + * @copyright 2009-2014 A. Grandt + * @license GNU LGPL 2.1 + * @link http://www.phpclasses.org/package/6110 + * @link https://github.com/Grandt/PHPZip + * @version 1.60 + */ +class Zip { + const VERSION = 1.60; + + const ZIP_LOCAL_FILE_HEADER = "\x50\x4b\x03\x04"; // Local file header signature + const ZIP_CENTRAL_FILE_HEADER = "\x50\x4b\x01\x02"; // Central file header signature + const ZIP_END_OF_CENTRAL_DIRECTORY = "\x50\x4b\x05\x06\x00\x00\x00\x00"; //end of Central directory record + + const EXT_FILE_ATTR_DIR = 010173200020; // Permission 755 drwxr-xr-x = (((S_IFDIR | 0755) << 16) | S_DOS_D); + const EXT_FILE_ATTR_FILE = 020151000040; // Permission 644 -rw-r--r-- = (((S_IFREG | 0644) << 16) | S_DOS_A); + + const ATTR_VERSION_TO_EXTRACT = "\x14\x00"; // Version needed to extract + const ATTR_MADE_BY_VERSION = "\x1E\x03"; // Made By Version + + // Unix file types + const S_IFIFO = 0010000; // named pipe (fifo) + const S_IFCHR = 0020000; // character special + const S_IFDIR = 0040000; // directory + const S_IFBLK = 0060000; // block special + const S_IFREG = 0100000; // regular + const S_IFLNK = 0120000; // symbolic link + const S_IFSOCK = 0140000; // socket + + // setuid/setgid/sticky bits, the same as for chmod: + + const S_ISUID = 0004000; // set user id on execution + const S_ISGID = 0002000; // set group id on execution + const S_ISTXT = 0001000; // sticky bit + + // And of course, the other 12 bits are for the permissions, the same as for chmod: + // When addding these up, you can also just write the permissions as a simgle octal number + // ie. 0755. The leading 0 specifies octal notation. + const S_IRWXU = 0000700; // RWX mask for owner + const S_IRUSR = 0000400; // R for owner + const S_IWUSR = 0000200; // W for owner + const S_IXUSR = 0000100; // X for owner + const S_IRWXG = 0000070; // RWX mask for group + const S_IRGRP = 0000040; // R for group + const S_IWGRP = 0000020; // W for group + const S_IXGRP = 0000010; // X for group + const S_IRWXO = 0000007; // RWX mask for other + const S_IROTH = 0000004; // R for other + const S_IWOTH = 0000002; // W for other + const S_IXOTH = 0000001; // X for other + const S_ISVTX = 0001000; // save swapped text even after use + + // Filetype, sticky and permissions are added up, and shifted 16 bits left BEFORE adding the DOS flags. + + // DOS file type flags, we really only use the S_DOS_D flag. + + const S_DOS_A = 0000040; // DOS flag for Archive + const S_DOS_D = 0000020; // DOS flag for Directory + const S_DOS_V = 0000010; // DOS flag for Volume + const S_DOS_S = 0000004; // DOS flag for System + const S_DOS_H = 0000002; // DOS flag for Hidden + const S_DOS_R = 0000001; // DOS flag for Read Only + + private $zipMemoryThreshold = 1048576; // Autocreate tempfile if the zip data exceeds 1048576 bytes (1 MB) + + private $zipData = NULL; + private $zipFile = NULL; + private $zipComment = NULL; + private $cdRec = array(); // central directory + private $offset = 0; + private $isFinalized = FALSE; + private $addExtraField = TRUE; + + private $streamChunkSize = 65536; + private $streamFilePath = NULL; + private $streamTimestamp = NULL; + private $streamFileComment = NULL; + private $streamFile = NULL; + private $streamData = NULL; + private $streamFileLength = 0; + private $streamExtFileAttr = null; + + /** + * Constructor. + * + * @param boolean $useZipFile Write temp zip data to tempFile? Default FALSE + */ + function __construct($useZipFile = FALSE) { + if ($useZipFile) { + $this->zipFile = tmpfile(); + } else { + $this->zipData = ""; + } + } + + function __destruct() { + if (is_resource($this->zipFile)) { + fclose($this->zipFile); + } + $this->zipData = NULL; + } + + /** + * Extra fields on the Zip directory records are Unix time codes needed for compatibility on the default Mac zip archive tool. + * These are enabled as default, as they do no harm elsewhere and only add 26 bytes per file added. + * + * @param bool $setExtraField TRUE (default) will enable adding of extra fields, anything else will disable it. + */ + function setExtraField($setExtraField = TRUE) { + $this->addExtraField = ($setExtraField === TRUE); + } + + /** + * Set Zip archive comment. + * + * @param string $newComment New comment. NULL to clear. + * @return bool $success + */ + public function setComment($newComment = NULL) { + if ($this->isFinalized) { + return FALSE; + } + $this->zipComment = $newComment; + + return TRUE; + } + + /** + * Set zip file to write zip data to. + * This will cause all present and future data written to this class to be written to this file. + * This can be used at any time, even after the Zip Archive have been finalized. Any previous file will be closed. + * Warning: If the given file already exists, it will be overwritten. + * + * @param string $fileName + * @return bool $success + */ + public function setZipFile($fileName) { + if (is_file($fileName)) { + unlink($fileName); + } + $fd=fopen($fileName, "x+b"); + if (is_resource($this->zipFile)) { + rewind($this->zipFile); + while (!feof($this->zipFile)) { + fwrite($fd, fread($this->zipFile, $this->streamChunkSize)); + } + + fclose($this->zipFile); + } else { + fwrite($fd, $this->zipData); + $this->zipData = NULL; + } + $this->zipFile = $fd; + + return TRUE; + } + + /** + * Add an empty directory entry to the zip archive. + * Basically this is only used if an empty directory is added. + * + * @param string $directoryPath Directory Path and name to be added to the archive. + * @param int $timestamp (Optional) Timestamp for the added directory, if omitted or set to 0, the current time will be used. + * @param string $fileComment (Optional) Comment to be added to the archive for this directory. To use fileComment, timestamp must be given. + * @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this. + * @return bool $success + */ + public function addDirectory($directoryPath, $timestamp = 0, $fileComment = NULL, $extFileAttr = self::EXT_FILE_ATTR_DIR) { + if ($this->isFinalized) { + return FALSE; + } + $directoryPath = str_replace("\\", "/", $directoryPath); + $directoryPath = rtrim($directoryPath, "/"); + + if (strlen($directoryPath) > 0) { + $this->buildZipEntry($directoryPath.'/', $fileComment, "\x00\x00", "\x00\x00", $timestamp, "\x00\x00\x00\x00", 0, 0, $extFileAttr); + return TRUE; + } + return FALSE; + } + + /** + * Add a file to the archive at the specified location and file name. + * + * @param string $data File data. + * @param string $filePath Filepath and name to be used in the archive. + * @param int $timestamp (Optional) Timestamp for the added file, if omitted or set to 0, the current time will be used. + * @param string $fileComment (Optional) Comment to be added to the archive for this file. To use fileComment, timestamp must be given. + * @param bool $compress (Optional) Compress file, if set to FALSE the file will only be stored. Default TRUE. + * @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this. + * @return bool $success + */ + public function addFile($data, $filePath, $timestamp = 0, $fileComment = NULL, $compress = TRUE, $extFileAttr = self::EXT_FILE_ATTR_FILE) { + if ($this->isFinalized) { + return FALSE; + } + + if (is_resource($data) && get_resource_type($data) == "stream") { + $this->addLargeFile($data, $filePath, $timestamp, $fileComment, $extFileAttr); + return FALSE; + } + + $gzData = ""; + $gzType = "\x08\x00"; // Compression type 8 = deflate + $gpFlags = "\x00\x00"; // General Purpose bit flags for compression type 8 it is: 0=Normal, 1=Maximum, 2=Fast, 3=super fast compression. + $dataLength = strlen($data); + $fileCRC32 = pack("V", crc32($data)); + + if ($compress) { + $gzTmp = gzcompress($data); + $gzData = substr(substr($gzTmp, 0, strlen($gzTmp) - 4), 2); // gzcompress adds a 2 byte header and 4 byte CRC we can't use. + // The 2 byte header does contain useful data, though in this case the 2 parameters we'd be interrested in will always be 8 for compression type, and 2 for General purpose flag. + $gzLength = strlen($gzData); + } else { + $gzLength = $dataLength; + } + + if ($gzLength >= $dataLength) { + $gzLength = $dataLength; + $gzData = $data; + $gzType = "\x00\x00"; // Compression type 0 = stored + $gpFlags = "\x00\x00"; // Compression type 0 = stored + } + + if (!is_resource($this->zipFile) && ($this->offset + $gzLength) > $this->zipMemoryThreshold) { + $this->zipflush(); + } + + $this->buildZipEntry($filePath, $fileComment, $gpFlags, $gzType, $timestamp, $fileCRC32, $gzLength, $dataLength, $extFileAttr); + + $this->zipwrite($gzData); + + return TRUE; + } + + /** + * Add the content to a directory. + * + * @author Adam Schmalhofer + * @author A. Grandt + * + * @param string $realPath Path on the file system. + * @param string $zipPath Filepath and name to be used in the archive. + * @param bool $recursive Add content recursively, default is TRUE. + * @param bool $followSymlinks Follow and add symbolic links, if they are accessible, default is TRUE. + * @param array &$addedFiles Reference to the added files, this is used to prevent duplicates, efault is an empty array. + * If you start the function by parsing an array, the array will be populated with the realPath + * and zipPath kay/value pairs added to the archive by the function. + * @param bool $overrideFilePermissions Force the use of the file/dir permissions set in the $extDirAttr + * and $extFileAttr parameters. + * @param int $extDirAttr Permissions for directories. + * @param int $extFileAttr Permissions for files. + */ + public function addDirectoryContent($realPath, $zipPath, $recursive = TRUE, $followSymlinks = TRUE, &$addedFiles = array(), + $overrideFilePermissions = FALSE, $extDirAttr = self::EXT_FILE_ATTR_DIR, $extFileAttr = self::EXT_FILE_ATTR_FILE) { + if (file_exists($realPath) && !isset($addedFiles[realpath($realPath)])) { + if (is_dir($realPath)) { + if ($overrideFilePermissions) { + $this->addDirectory($zipPath, 0, null, $extDirAttr); + } else { + $this->addDirectory($zipPath, 0, null, self::getFileExtAttr($realPath)); + } + } + + $addedFiles[realpath($realPath)] = $zipPath; + + $iter = new DirectoryIterator($realPath); + foreach ($iter as $file) { + if ($file->isDot()) { + continue; + } + $newRealPath = $file->getPathname(); + $newZipPath = self::pathJoin($zipPath, $file->getFilename()); + + if (file_exists($newRealPath) && ($followSymlinks === TRUE || !is_link($newRealPath))) { + if ($file->isFile()) { + $addedFiles[realpath($newRealPath)] = $newZipPath; + if ($overrideFilePermissions) { + $this->addLargeFile($newRealPath, $newZipPath, 0, null, $extFileAttr); + } else { + $this->addLargeFile($newRealPath, $newZipPath, 0, null, self::getFileExtAttr($newRealPath)); + } + } else if ($recursive === TRUE) { + $this->addDirectoryContent($newRealPath, $newZipPath, $recursive, $followSymlinks, $addedFiles, $overrideFilePermissions, $extDirAttr, $extFileAttr); + } else { + if ($overrideFilePermissions) { + $this->addDirectory($zipPath, 0, null, $extDirAttr); + } else { + $this->addDirectory($zipPath, 0, null, self::getFileExtAttr($newRealPath)); + } + } + } + } + } + } + + /** + * Add a file to the archive at the specified location and file name. + * + * @param string $dataFile File name/path. + * @param string $filePath Filepath and name to be used in the archive. + * @param int $timestamp (Optional) Timestamp for the added file, if omitted or set to 0, the current time will be used. + * @param string $fileComment (Optional) Comment to be added to the archive for this file. To use fileComment, timestamp must be given. + * @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this. + * @return bool $success + */ + public function addLargeFile($dataFile, $filePath, $timestamp = 0, $fileComment = NULL, $extFileAttr = self::EXT_FILE_ATTR_FILE) { + if ($this->isFinalized) { + return FALSE; + } + + if (is_string($dataFile) && is_file($dataFile)) { + $this->processFile($dataFile, $filePath, $timestamp, $fileComment, $extFileAttr); + } else if (is_resource($dataFile) && get_resource_type($dataFile) == "stream") { + $fh = $dataFile; + $this->openStream($filePath, $timestamp, $fileComment, $extFileAttr); + + while (!feof($fh)) { + $this->addStreamData(fread($fh, $this->streamChunkSize)); + } + $this->closeStream($this->addExtraField); + } + return TRUE; + } + + /** + * Create a stream to be used for large entries. + * + * @param string $filePath Filepath and name to be used in the archive. + * @param int $timestamp (Optional) Timestamp for the added file, if omitted or set to 0, the current time will be used. + * @param string $fileComment (Optional) Comment to be added to the archive for this file. To use fileComment, timestamp must be given. + * @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this. + * @return bool $success + */ + public function openStream($filePath, $timestamp = 0, $fileComment = null, $extFileAttr = self::EXT_FILE_ATTR_FILE) { + if (!function_exists('sys_get_temp_dir')) { + die ("ERROR: Zip " . self::VERSION . " requires PHP version 5.2.1 or above if large files are used."); + } + + if ($this->isFinalized) { + return FALSE; + } + + $this->zipflush(); + + if (strlen($this->streamFilePath) > 0) { + $this->closeStream(); + } + + $this->streamFile = tempnam(sys_get_temp_dir(), 'Zip'); + $this->streamData = fopen($this->streamFile, "wb"); + $this->streamFilePath = $filePath; + $this->streamTimestamp = $timestamp; + $this->streamFileComment = $fileComment; + $this->streamFileLength = 0; + $this->streamExtFileAttr = $extFileAttr; + + return TRUE; + } + + /** + * Add data to the open stream. + * + * @param string $data + * @return mixed length in bytes added or FALSE if the archive is finalized or there are no open stream. + */ + public function addStreamData($data) { + if ($this->isFinalized || strlen($this->streamFilePath) == 0) { + return FALSE; + } + + $length = fwrite($this->streamData, $data, strlen($data)); + if ($length != strlen($data)) { + die ("

    Length mismatch

    \n"); + } + $this->streamFileLength += $length; + + return $length; + } + + /** + * Close the current stream. + * + * @return bool $success + */ + public function closeStream() { + if ($this->isFinalized || strlen($this->streamFilePath) == 0) { + return FALSE; + } + + fflush($this->streamData); + fclose($this->streamData); + + $this->processFile($this->streamFile, $this->streamFilePath, $this->streamTimestamp, $this->streamFileComment, $this->streamExtFileAttr); + + $this->streamData = null; + $this->streamFilePath = null; + $this->streamTimestamp = null; + $this->streamFileComment = null; + $this->streamFileLength = 0; + $this->streamExtFileAttr = null; + + // Windows is a little slow at times, so a millisecond later, we can unlink this. + unlink($this->streamFile); + + $this->streamFile = null; + + return TRUE; + } + + private function processFile($dataFile, $filePath, $timestamp = 0, $fileComment = null, $extFileAttr = self::EXT_FILE_ATTR_FILE) { + if ($this->isFinalized) { + return FALSE; + } + + $tempzip = tempnam(sys_get_temp_dir(), 'ZipStream'); + + $zip = new ZipArchive; + if ($zip->open($tempzip) === TRUE) { + $zip->addFile($dataFile, 'file'); + $zip->close(); + } + + $file_handle = fopen($tempzip, "rb"); + $stats = fstat($file_handle); + $eof = $stats['size']-72; + + fseek($file_handle, 6); + + $gpFlags = fread($file_handle, 2); + $gzType = fread($file_handle, 2); + fread($file_handle, 4); + $fileCRC32 = fread($file_handle, 4); + $v = unpack("Vval", fread($file_handle, 4)); + $gzLength = $v['val']; + $v = unpack("Vval", fread($file_handle, 4)); + $dataLength = $v['val']; + + $this->buildZipEntry($filePath, $fileComment, $gpFlags, $gzType, $timestamp, $fileCRC32, $gzLength, $dataLength, $extFileAttr); + + fseek($file_handle, 34); + $pos = 34; + + while (!feof($file_handle) && $pos < $eof) { + $datalen = $this->streamChunkSize; + if ($pos + $this->streamChunkSize > $eof) { + $datalen = $eof-$pos; + } + $data = fread($file_handle, $datalen); + $pos += $datalen; + + $this->zipwrite($data); + } + + fclose($file_handle); + + unlink($tempzip); + } + + /** + * Close the archive. + * A closed archive can no longer have new files added to it. + * + * @return bool $success + */ + public function finalize() { + if (!$this->isFinalized) { + if (strlen($this->streamFilePath) > 0) { + $this->closeStream(); + } + $cd = implode("", $this->cdRec); + + $cdRecSize = pack("v", sizeof($this->cdRec)); + $cdRec = $cd . self::ZIP_END_OF_CENTRAL_DIRECTORY + . $cdRecSize . $cdRecSize + . pack("VV", strlen($cd), $this->offset); + if (!empty($this->zipComment)) { + $cdRec .= pack("v", strlen($this->zipComment)) . $this->zipComment; + } else { + $cdRec .= "\x00\x00"; + } + + $this->zipwrite($cdRec); + + $this->isFinalized = TRUE; + $this->cdRec = NULL; + + return TRUE; + } + return FALSE; + } + + /** + * Get the handle ressource for the archive zip file. + * If the zip haven't been finalized yet, this will cause it to become finalized + * + * @return zip file handle + */ + public function getZipFile() { + if (!$this->isFinalized) { + $this->finalize(); + } + + $this->zipflush(); + + rewind($this->zipFile); + + return $this->zipFile; + } + + /** + * Get the zip file contents + * If the zip haven't been finalized yet, this will cause it to become finalized + * + * @return zip data + */ + public function getZipData() { + if (!$this->isFinalized) { + $this->finalize(); + } + if (!is_resource($this->zipFile)) { + return $this->zipData; + } else { + rewind($this->zipFile); + $filestat = fstat($this->zipFile); + return fread($this->zipFile, $filestat['size']); + } + } + + /** + * Send the archive as a zip download + * + * @param String $fileName The name of the Zip archive, in ISO-8859-1 (or ASCII) encoding, ie. "archive.zip". Optional, defaults to NULL, which means that no ISO-8859-1 encoded file name will be specified. + * @param String $contentType Content mime type. Optional, defaults to "application/zip". + * @param String $utf8FileName The name of the Zip archive, in UTF-8 encoding. Optional, defaults to NULL, which means that no UTF-8 encoded file name will be specified. + * @param bool $inline Use Content-Disposition with "inline" instead of "attached". Optional, defaults to FALSE. + * @return bool $success + */ + function sendZip($fileName = null, $contentType = "application/zip", $utf8FileName = null, $inline = false) { + if (!$this->isFinalized) { + $this->finalize(); + } + + $headerFile = null; + $headerLine = null; + if (!headers_sent($headerFile, $headerLine) or die("

    Error: Unable to send file $fileName. HTML Headers have already been sent from $headerFile in line $headerLine

    ")) { + if ((ob_get_contents() === FALSE || ob_get_contents() == '') or die("\n

    Error: Unable to send file $fileName. Output buffer contains the following text (typically warnings or errors):
    " . htmlentities(ob_get_contents()) . "

    ")) { + if (ini_get('zlib.output_compression')) { + ini_set('zlib.output_compression', 'Off'); + } + + header("Pragma: public"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s T")); + header("Expires: 0"); + header("Accept-Ranges: bytes"); + header("Connection: close"); + header("Content-Type: " . $contentType); + $cd = "Content-Disposition: "; + if ($inline) { + $cd .= "inline"; + } else{ + $cd .= "attached"; + } + if ($fileName) { + $cd .= '; filename="' . $fileName . '"'; + } + if ($utf8FileName) { + $cd .= "; filename*=UTF-8''" . rawurlencode($utf8FileName); + } + header($cd); + header("Content-Length: ". $this->getArchiveSize()); + + if (!is_resource($this->zipFile)) { + echo $this->zipData; + } else { + rewind($this->zipFile); + + while (!feof($this->zipFile)) { + echo fread($this->zipFile, $this->streamChunkSize); + } + } + } + return TRUE; + } + return FALSE; + } + + /** + * Return the current size of the archive + * + * @return $size Size of the archive + */ + public function getArchiveSize() { + if (!is_resource($this->zipFile)) { + return strlen($this->zipData); + } + $filestat = fstat($this->zipFile); + + return $filestat['size']; + } + + /** + * Calculate the 2 byte dostime used in the zip entries. + * + * @param int $timestamp + * @return 2-byte encoded DOS Date + */ + private function getDosTime($timestamp = 0) { + $timestamp = (int)$timestamp; + $oldTZ = @date_default_timezone_get(); + date_default_timezone_set('UTC'); + $date = ($timestamp == 0 ? getdate() : getdate($timestamp)); + date_default_timezone_set($oldTZ); + if ($date["year"] >= 1980) { + return pack("V", (($date["mday"] + ($date["mon"] << 5) + (($date["year"]-1980) << 9)) << 16) | + (($date["seconds"] >> 1) + ($date["minutes"] << 5) + ($date["hours"] << 11))); + } + return "\x00\x00\x00\x00"; + } + + /** + * Build the Zip file structures + * + * @param string $filePath + * @param string $fileComment + * @param string $gpFlags + * @param string $gzType + * @param int $timestamp + * @param string $fileCRC32 + * @param int $gzLength + * @param int $dataLength + * @param int $extFileAttr Use self::EXT_FILE_ATTR_FILE for files, self::EXT_FILE_ATTR_DIR for Directories. + */ + private function buildZipEntry($filePath, $fileComment, $gpFlags, $gzType, $timestamp, $fileCRC32, $gzLength, $dataLength, $extFileAttr) { + $filePath = str_replace("\\", "/", $filePath); + $fileCommentLength = (empty($fileComment) ? 0 : strlen($fileComment)); + $timestamp = (int)$timestamp; + $timestamp = ($timestamp == 0 ? time() : $timestamp); + + $dosTime = $this->getDosTime($timestamp); + $tsPack = pack("V", $timestamp); + + $ux = "\x75\x78\x0B\x00\x01\x04\xE8\x03\x00\x00\x04\x00\x00\x00\x00"; + + if (!isset($gpFlags) || strlen($gpFlags) != 2) { + $gpFlags = "\x00\x00"; + } + + $isFileUTF8 = mb_check_encoding($filePath, "UTF-8") && !mb_check_encoding($filePath, "ASCII"); + $isCommentUTF8 = !empty($fileComment) && mb_check_encoding($fileComment, "UTF-8") && !mb_check_encoding($fileComment, "ASCII"); + if ($isFileUTF8 || $isCommentUTF8) { + $flag = 0; + $gpFlagsV = unpack("vflags", $gpFlags); + if (isset($gpFlagsV['flags'])) { + $flag = $gpFlagsV['flags']; + } + $gpFlags = pack("v", $flag | (1 << 11)); + } + + $header = $gpFlags . $gzType . $dosTime. $fileCRC32 + . pack("VVv", $gzLength, $dataLength, strlen($filePath)); // File name length + + $zipEntry = self::ZIP_LOCAL_FILE_HEADER; + $zipEntry .= self::ATTR_VERSION_TO_EXTRACT; + $zipEntry .= $header; + $zipEntry .= pack("v", ($this->addExtraField ? 28 : 0)); // Extra field length + $zipEntry .= $filePath; // FileName + // Extra fields + if ($this->addExtraField) { + $zipEntry .= "\x55\x54\x09\x00\x03" . $tsPack . $tsPack . $ux; + } + $this->zipwrite($zipEntry); + + $cdEntry = self::ZIP_CENTRAL_FILE_HEADER; + $cdEntry .= self::ATTR_MADE_BY_VERSION; + $cdEntry .= ($dataLength === 0 ? "\x0A\x00" : self::ATTR_VERSION_TO_EXTRACT); + $cdEntry .= $header; + $cdEntry .= pack("v", ($this->addExtraField ? 24 : 0)); // Extra field length + $cdEntry .= pack("v", $fileCommentLength); // File comment length + $cdEntry .= "\x00\x00"; // Disk number start + $cdEntry .= "\x00\x00"; // internal file attributes + $cdEntry .= pack("V", $extFileAttr); // External file attributes + $cdEntry .= pack("V", $this->offset); // Relative offset of local header + $cdEntry .= $filePath; // FileName + // Extra fields + if ($this->addExtraField) { + $cdEntry .= "\x55\x54\x05\x00\x03" . $tsPack . $ux; + } + if (!empty($fileComment)) { + $cdEntry .= $fileComment; // Comment + } + + $this->cdRec[] = $cdEntry; + $this->offset += strlen($zipEntry) + $gzLength; + } + + private function zipwrite($data) { + if (!is_resource($this->zipFile)) { + $this->zipData .= $data; + } else { + fwrite($this->zipFile, $data); + fflush($this->zipFile); + } + } + + private function zipflush() { + if (!is_resource($this->zipFile)) { + $this->zipFile = tmpfile(); + fwrite($this->zipFile, $this->zipData); + $this->zipData = NULL; + } + } + + /** + * Join $file to $dir path, and clean up any excess slashes. + * + * @param string $dir + * @param string $file + */ + public static function pathJoin($dir, $file) { + if (empty($dir) || empty($file)) { + return self::getRelativePath($dir . $file); + } + return self::getRelativePath($dir . '/' . $file); + } + + /** + * Clean up a path, removing any unnecessary elements such as /./, // or redundant ../ segments. + * If the path starts with a "/", it is deemed an absolute path and any /../ in the beginning is stripped off. + * The returned path will not end in a "/". + * + * Sometimes, when a path is generated from multiple fragments, + * you can get something like "../data/html/../images/image.jpeg" + * This will normalize that example path to "../data/images/image.jpeg" + * + * @param string $path The path to clean up + * @return string the clean path + */ + public static function getRelativePath($path) { + $path = preg_replace("#/+\.?/+#", "/", str_replace("\\", "/", $path)); + $dirs = explode("/", rtrim(preg_replace('#^(?:\./)+#', '', $path), '/')); + + $offset = 0; + $sub = 0; + $subOffset = 0; + $root = ""; + + if (empty($dirs[0])) { + $root = "/"; + $dirs = array_splice($dirs, 1); + } else if (preg_match("#[A-Za-z]:#", $dirs[0])) { + $root = strtoupper($dirs[0]) . "/"; + $dirs = array_splice($dirs, 1); + } + + $newDirs = array(); + foreach ($dirs as $dir) { + if ($dir !== "..") { + $subOffset--; + $newDirs[++$offset] = $dir; + } else { + $subOffset++; + if (--$offset < 0) { + $offset = 0; + if ($subOffset > $sub) { + $sub++; + } + } + } + } + + if (empty($root)) { + $root = str_repeat("../", $sub); + } + return $root . implode("/", array_slice($newDirs, 0, $offset)); + } + + /** + * Create the file permissions for a file or directory, for use in the extFileAttr parameters. + * + * @param int $owner Unix permisions for owner (octal from 00 to 07) + * @param int $group Unix permisions for group (octal from 00 to 07) + * @param int $other Unix permisions for others (octal from 00 to 07) + * @param bool $isFile + * @return EXTRERNAL_REF field. + */ + public static function generateExtAttr($owner = 07, $group = 05, $other = 05, $isFile = true) { + $fp = $isFile ? self::S_IFREG : self::S_IFDIR; + $fp |= (($owner & 07) << 6) | (($group & 07) << 3) | ($other & 07); + + return ($fp << 16) | ($isFile ? self::S_DOS_A : self::S_DOS_D); + } + + /** + * Get the file permissions for a file or directory, for use in the extFileAttr parameters. + * + * @param string $filename + * @return external ref field, or FALSE if the file is not found. + */ + public static function getFileExtAttr($filename) { + if (file_exists($filename)) { + $fp = fileperms($filename) << 16; + return $fp | (is_dir($filename) ? self::S_DOS_D : self::S_DOS_A); + } + return FALSE; + } +} +?> diff --git a/inc/3rdparty/libraries/PHPePub/lib.uuid.LICENCE.txt b/inc/3rdparty/libraries/PHPePub/lib.uuid.LICENCE.txt new file mode 100644 index 00000000..9424a83e --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/lib.uuid.LICENCE.txt @@ -0,0 +1,31 @@ + DrUUID RFC4122 library for PHP5 + by J. King (http://jkingweb.ca/) + Licensed under MIT license + + See http://jkingweb.ca/code/php/lib.uuid/ + for documentation + + Last revised 2010-02-15 + +Copyright (c) 2009 J. King + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/inc/3rdparty/libraries/PHPePub/lib.uuid.php b/inc/3rdparty/libraries/PHPePub/lib.uuid.php new file mode 100644 index 00000000..c6a8de52 --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/lib.uuid.php @@ -0,0 +1,314 @@ +string; + } + + public function __get($var) { + switch($var) { + case "bytes": + return $this->bytes; + case "hex": + return bin2hex($this->bytes); + case "string": + return $this->__toString(); + case "urn": + return "urn:uuid:".$this->__toString(); + case "version": + return ord($this->bytes[6]) >> 4; + case "variant": + $byte = ord($this->bytes[8]); + if ($byte >= self::varRes) { + return 3; + } + if ($byte >= self::varMS) { + return 2; + } + if ($byte >= self::varRFC) { + return 1; + } + return 0; + case "node": + if (ord($this->bytes[6])>>4==1) { + return bin2hex(substr($this->bytes,10)); + } else { + return NULL; + } + case "time": + if (ord($this->bytes[6])>>4==1) { + // Restore contiguous big-endian byte order + $time = bin2hex($this->bytes[6].$this->bytes[7].$this->bytes[4].$this->bytes[5].$this->bytes[0].$this->bytes[1].$this->bytes[2].$this->bytes[3]); + // Clear version flag + $time[0] = "0"; + // Do some reverse arithmetic to get a Unix timestamp + $time = (hexdec($time) - self::interval) / 10000000; + return $time; + } else { + return NULL; + } + default: + return NULL; + } + } + + protected function __construct($uuid) { + if (strlen($uuid) != 16) { + throw new UUIDException("Input must be a 128-bit integer."); + } + $this->bytes = $uuid; + // Optimize the most common use + $this->string = + bin2hex(substr($uuid,0,4))."-". + bin2hex(substr($uuid,4,2))."-". + bin2hex(substr($uuid,6,2))."-". + bin2hex(substr($uuid,8,2))."-". + bin2hex(substr($uuid,10,6)); + } + + protected static function mintTime($node = NULL) { + /* Generates a Version 1 UUID. + These are derived from the time at which they were generated. */ + // Get time since Gregorian calendar reform in 100ns intervals + // This is exceedingly difficult because of PHP's (and pack()'s) + // integer size limits. + // Note that this will never be more accurate than to the microsecond. + $time = microtime(1) * 10000000 + self::interval; + // Convert to a string representation + $time = sprintf("%F", $time); + preg_match("/^\d+/", $time, $time); //strip decimal point + // And now to a 64-bit binary representation + $time = base_convert($time[0], 10, 16); + $time = pack("H*", str_pad($time, 16, "0", STR_PAD_LEFT)); + // Reorder bytes to their proper locations in the UUID + $uuid = $time[4].$time[5].$time[6].$time[7].$time[2].$time[3].$time[0].$time[1]; + // Generate a random clock sequence + $uuid .= self::randomBytes(2); + // set variant + $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); + // set version + $uuid[6] = chr(ord($uuid[6]) & self::clearVer | self::version1); + // Set the final 'node' parameter, a MAC address + if ($node) { + $node = self::makeBin($node, 6); + } + if (!$node) { + // If no node was provided or if the node was invalid, + // generate a random MAC address and set the multicast bit + $node = self::randomBytes(6); + $node[0] = pack("C", ord($node[0]) | 1); + } + $uuid .= $node; + return $uuid; + } + + protected static function mintRand() { + /* Generate a Version 4 UUID. + These are derived soly from random numbers. */ + // generate random fields + $uuid = self::randomBytes(16); + // set variant + $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); + // set version + $uuid[6] = chr(ord($uuid[6]) & self::clearVer | self::version4); + return $uuid; + } + + protected static function mintName($ver, $node, $ns) { + /* Generates a Version 3 or Version 5 UUID. + These are derived from a hash of a name and its namespace, in binary form. */ + if (!$node) { + throw new UUIDException("A name-string is required for Version 3 or 5 UUIDs."); + } + // if the namespace UUID isn't binary, make it so + $ns = self::makeBin($ns, 16); + if (!$ns) { + throw new UUIDException("A binary namespace is required for Version 3 or 5 UUIDs."); + } + $uuid = null; + $version = self::version3; + switch($ver) { + case self::MD5: + $version = self::version3; + $uuid = md5($ns.$node,1); + break; + case self::SHA1: + $version = self::version5; + $uuid = substr(sha1($ns.$node,1),0, 16); + break; + } + // set variant + $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); + // set version + $uuid[6] = chr(ord($uuid[6]) & self::clearVer | $version); + return ($uuid); + } + + protected static function makeBin($str, $len) { + /* Insure that an input string is either binary or hexadecimal. + Returns binary representation, or false on failure. */ + if ($str instanceof self) { + return $str->bytes; + } + if (strlen($str)==$len) { + return $str; + } else { + $str = preg_replace("/^urn:uuid:/is", "", $str); // strip URN scheme and namespace + } + $str = preg_replace("/[^a-f0-9]/is", "", $str); // strip non-hex characters + if (strlen($str) != ($len * 2)) { + return FALSE; + } else { + return pack("H*", $str); + } + } + + public static function initRandom() { + /* Look for a system-provided source of randomness, which is usually crytographically secure. + /dev/urandom is tried first simply out of bias for Linux systems. */ + if (is_readable('/dev/urandom')) { + self::$randomSource = fopen('/dev/urandom', 'rb'); + self::$randomFunc = 'randomFRead'; + } + else if (class_exists('COM', 0)) { + try { + self::$randomSource = new COM('CAPICOM.Utilities.1'); // See http://msdn.microsoft.com/en-us/library/aa388182(VS.85).aspx + self::$randomFunc = 'randomCOM'; + } + catch(Exception $e) { + } + } + return self::$randomFunc; + } + + public static function randomBytes($bytes) { + return call_user_func(array('self', self::$randomFunc), $bytes); + } + + protected static function randomTwister($bytes) { + /* Get the specified number of random bytes, using mt_rand(). + Randomness is returned as a string of bytes. */ + $rand = ""; + for ($a = 0; $a < $bytes; $a++) { + $rand .= chr(mt_rand(0, 255)); + } + return $rand; + } + + protected static function randomFRead($bytes) { + /* Get the specified number of random bytes using a file handle + previously opened with UUID::initRandom(). + Randomness is returned as a string of bytes. */ + return fread(self::$randomSource, $bytes); + } + + protected static function randomCOM($bytes) { + /* Get the specified number of random bytes using Windows' + randomness source via a COM object previously created by UUID::initRandom(). + Randomness is returned as a string of bytes. */ + return base64_decode(self::$randomSource->GetRandom($bytes,0)); // straight binary mysteriously doesn't work, hence the base64 + } +} + +class UUIDException extends Exception { +} diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php index 7e3e6afe..e5539468 100755 --- a/inc/poche/Poche.class.php +++ b/inc/poche/Poche.class.php @@ -1131,4 +1131,87 @@ class Poche return new HTMLPurifier($config); } + + /** + * handle epub + */ + public function createEpub() { + + if (isset($_GET['epub']) && isset($_GET['id'])) { + if ($_GET['id'] == "all") { // we put all entries in the file + $entries = $this->store->retrieveAll($this->user->getId()); + } + else { // we put only one entry in the file + $entryID = filter_var($_GET['id'],FILTER_SANITIZE_NUMBER_INT); + $entry = $this->store->retrieveOneById($entryID, $this->user->getId()); + $entries = array($entry); + } + } + $content_start = + "\n" + . "\n" + . "\n" + . "" + . "\n" + . "\n" + . "Test Book\n" + . "\n" + . "\n"; + + $bookEnd = "\n\n"; + + $log = new Logger($entryID, TRUE); + $fileDir = CACHE; + + + $book = new EPub(); + $log->logLine("new EPub()"); + $log->logLine("EPub class version: " . EPub::VERSION); + $log->logLine("EPub Req. Zip version: " . EPub::REQ_ZIP_VERSION); + $log->logLine("Zip version: " . Zip::VERSION); + $log->logLine("getCurrentServerURL: " . $book->getCurrentServerURL()); + $log->logLine("getCurrentPageURL..: " . $book->getCurrentPageURL()); + + $book->setTitle("wallabag's articles"); + $book->setIdentifier("http://$_SERVER[HTTP_HOST]", EPub::IDENTIFIER_URI); // Could also be the ISBN number, prefered for published books, or a UUID. + //$book->setLanguage("en"); // Not needed, but included for the example, Language is mandatory, but EPub defaults to "en". Use RFC3066 Language codes, such as "en", "da", "fr" etc. + $book->setDescription("Some articles saved on my wallabag"); + $book->setAuthor("wallabag","wallabag"); + $book->setPublisher("wallabag","wallabag"); // I hope this is a non existant address :) + $book->setDate(time()); // Strictly not needed as the book date defaults to time(). + //$book->setRights("Copyright and licence information specific for the book."); // As this is generated, this _could_ contain the name or licence information of the user who purchased the book, if needed. If this is used that way, the identifier must also be made unique for the book. + $book->setSourceURL("http://$_SERVER[HTTP_HOST]"); + + $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "PHP"); + + $cssData = "body {\n margin-left: .5em;\n margin-right: .5em;\n text-align: justify;\n}\n\np {\n font-family: serif;\n font-size: 10pt;\n text-align: justify;\n text-indent: 1em;\n margin-top: 0px;\n margin-bottom: 1ex;\n}\n\nh1, h2 {\n font-family: sans-serif;\n font-style: italic;\n text-align: center;\n background-color: #6b879c;\n color: white;\n width: 100%;\n}\n\nh1 {\n margin-bottom: 2px;\n}\n\nh2 {\n margin-top: -2px;\n margin-bottom: 2px;\n}\n"; + $cover = $content_start . "

    My articles on wallabag

    \n

    As seen on : http://$_SERVER[HTTP_HOST]

    \n" . $bookEnd; + $book->addChapter("Notices", "Cover.html", $cover); + $book->buildTOC(NULL, "toc", "Table of Contents", TRUE, TRUE); + + foreach ($entries as $entry) { + $tags = $this->store->retrieveTagsByEntry($entry['id']); + foreach ($tags as $tag) { + $book->setSubject($tag); + } + + $log->logLine("Set up parameters"); + + + + $chapter = $content_start . $entry['content'] . $bookEnd; + $book->addChapter("Chapter " . $entry['id'] . ": " . $entry['title'], htmlspecialchars($entry['title']) . ".html", $chapter, true, EPub::EXTERNAL_REF_ADD); + } + + + if (true) { + $epuplog = $book->getLog(); + $book->addChapter("Log", "Log.html", $content_start . $log->getLog() . "\n
    " . $bookEnd); // generation log + // Only used in case we need to debug EPub.php. + //$book->addChapter("ePubLog", "ePubLog.html", $content_start . $epuplog . "\n" . $bookEnd); + } + $book->finalize(); + $zipData = $book->sendBook("wallabag's articles"); + } } diff --git a/inc/poche/global.inc.php b/inc/poche/global.inc.php index 14e9dd93..8cf86d03 100755 --- a/inc/poche/global.inc.php +++ b/inc/poche/global.inc.php @@ -31,6 +31,11 @@ require_once INCLUDES . '/3rdparty/FlattrItem.class.php'; require_once INCLUDES . '/3rdparty/htmlpurifier/HTMLPurifier.auto.php'; +# epub library +require_once INCLUDES . '/3rdparty/libraries/PHPePub/Logger.php'; +require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPub.php'; +require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPubChapterSplitter.php'; + # Composer its autoloader for automatically loading Twig if (! file_exists(ROOT . '/vendor/autoload.php')) { Poche::$canRenderTemplates = false; diff --git a/index.php b/index.php index 9c943b1d..79838ed9 100755 --- a/index.php +++ b/index.php @@ -70,6 +70,8 @@ if (isset($_GET['login'])) { $poche->createNewUser(); } elseif (isset($_GET['deluser'])) { $poche->deleteUser(); +} elseif (isset($_GET['epub'])) { + $poche->createEpub(); } elseif (isset($_GET['import'])) { $import = $poche->import(); $tpl_vars = array_merge($tpl_vars, $import); diff --git a/themes/baggy/config.twig b/themes/baggy/config.twig index 29d9e048..f43f6d01 100755 --- a/themes/baggy/config.twig +++ b/themes/baggy/config.twig @@ -124,6 +124,9 @@ {% if constant('STORAGE') == 'sqlite' %}

    {% trans "Click here" %} {% trans "to download your database." %}

    {% endif %}

    {% trans "Click here" %} {% trans "to export your wallabag data." %}

    + +

    Fancy a ebook ?

    + Click on this link to get all your articles in one ebook (ePub).

    {% trans "Cache" %}

    {% trans "Click here" %} {% trans "to delete cache." %}

    diff --git a/themes/baggy/view.twig b/themes/baggy/view.twig index 62af2516..0dff4e27 100755 --- a/themes/baggy/view.twig +++ b/themes/baggy/view.twig @@ -16,6 +16,7 @@ {% if constant('SHARE_SHAARLI') == 1 %}
  • {% trans "shaarli" %}
  • {% endif %} {% if constant('FLATTR') == 1 %}{% if flattr.status == constant('FLATTRABLE') %}
  • {% trans "flattr" %}
  • {% elseif flattr.status == constant('FLATTRED') %}
  • {% trans "flattr" %} ({{ flattr.numflattrs }})
  • {% endif %}{% endif %} {% if constant('SHOW_PRINTLINK') == 1 %}
  • {% trans "Print" %}
  • {% endif %} +
  • EPUB
  • {% trans "Does this article appear wrong?" %}
  • -- cgit v1.2.3 From 72a857158c187206dae2eed08143d5743322cb0c Mon Sep 17 00:00:00 2001 From: tcit Date: Thu, 24 Apr 2014 03:08:31 +0200 Subject: Fixed a bug into PHPePub with special caracters --- inc/3rdparty/libraries/PHPePub/EPub.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inc/3rdparty/libraries/PHPePub/EPub.php b/inc/3rdparty/libraries/PHPePub/EPub.php index 836c0512..0260ce4f 100644 --- a/inc/3rdparty/libraries/PHPePub/EPub.php +++ b/inc/3rdparty/libraries/PHPePub/EPub.php @@ -343,6 +343,7 @@ class EPub { } $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) { @@ -1699,7 +1700,7 @@ class EPub { while (list($chapterName, $navPoint) = each($this->ncx->chapterList)) { $fileName = $navPoint->getContentSrc(); $level = $navPoint->getLevel() -2; - $tocData .= "\t

    " . str_repeat("      ", $level) . "" . $chapterName . "

    \n"; + $tocData .= "\t

    " . str_repeat("      ", $level) . "sanitizeFileName($fileName) . "\">" . $chapterName . "

    \n"; } } else if ($this->tocAddReferences === TRUE) { if (array_key_exists($item, $this->ncx->referencesList)) { -- cgit v1.2.3 From 7ec445b06e05d8caa5219c5802007d897c48ab4e Mon Sep 17 00:00:00 2001 From: tcit Date: Fri, 25 Apr 2014 16:20:25 +0200 Subject: Big changes for epub export. Now possible to do it from a tag, a category and a search. Also, improved ebook rendering. --- inc/poche/Poche.class.php | 56 ++++++++++++++++++++++++++++++++--------------- themes/baggy/config.twig | 2 +- themes/baggy/home.twig | 5 +++++ themes/baggy/view.twig | 2 +- 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php index e5539468..c476df3e 100755 --- a/inc/poche/Poche.class.php +++ b/inc/poche/Poche.class.php @@ -1136,36 +1136,52 @@ class Poche * handle epub */ public function createEpub() { - - if (isset($_GET['epub']) && isset($_GET['id'])) { - if ($_GET['id'] == "all") { // we put all entries in the file - $entries = $this->store->retrieveAll($this->user->getId()); - } - else { // we put only one entry in the file + + switch ($_GET['method']) { + case 'id': $entryID = filter_var($_GET['id'],FILTER_SANITIZE_NUMBER_INT); $entry = $this->store->retrieveOneById($entryID, $this->user->getId()); $entries = array($entry); - } + break; + case 'all': + $entries = $this->store->retrieveAll($this->user->getId()); + break; + case 'tag': + $tag = filter_var($_GET['tag'],FILTER_SANITIZE_STRING); + $tags_id = $this->store->retrieveAllTags($this->user->getId(),$tag); + $tag_id = $tags_id[0]["id"]; // we take the first result, which is supposed to match perfectly. There must be a workaround. + $entries = $this->store->retrieveEntriesByTag($tag_id,$this->user->getId()); + break; + case 'category': + $category = filter_var($_GET['category'],FILTER_SANITIZE_STRING); + $entries = $this->store->getEntriesByView($category,$this->user->getId()); + break; + case 'search': + $search = filter_var($_GET['search'],FILTER_SANITIZE_STRING); + $entries = $this->store->search($search,$this->user->getId()); + break; + case 'default': + die(_('Uh, there is a problem while generating epub.')); + } + $content_start = "\n" - . "\n" - . "\n" - . "" - . "\n" + . "\n" + . "\n" + . "\n" . "\n" - . "Test Book\n" + . "wallabag article\n" . "\n" . "\n"; $bookEnd = "\n\n"; - $log = new Logger($entryID, TRUE); + $log = new Logger("wallabag", TRUE); $fileDir = CACHE; - $book = new EPub(); + $book = new EPub(EPub::BOOK_VERSION_EPUB3); $log->logLine("new EPub()"); $log->logLine("EPub class version: " . EPub::VERSION); $log->logLine("EPub Req. Zip version: " . EPub::REQ_ZIP_VERSION); @@ -1186,6 +1202,10 @@ class Poche $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "PHP"); $cssData = "body {\n margin-left: .5em;\n margin-right: .5em;\n text-align: justify;\n}\n\np {\n font-family: serif;\n font-size: 10pt;\n text-align: justify;\n text-indent: 1em;\n margin-top: 0px;\n margin-bottom: 1ex;\n}\n\nh1, h2 {\n font-family: sans-serif;\n font-style: italic;\n text-align: center;\n background-color: #6b879c;\n color: white;\n width: 100%;\n}\n\nh1 {\n margin-bottom: 2px;\n}\n\nh2 {\n margin-top: -2px;\n margin-bottom: 2px;\n}\n"; + + $log->logLine("Add Cover Image"); + $book->setCoverImage("Cover.png", file_get_contents("themes/baggy/img/apple-touch-icon-152.png"), "image/png"); + $cover = $content_start . "

    My articles on wallabag

    \n

    As seen on : http://$_SERVER[HTTP_HOST]

    \n" . $bookEnd; $book->addChapter("Notices", "Cover.html", $cover); $book->buildTOC(NULL, "toc", "Table of Contents", TRUE, TRUE); @@ -1193,7 +1213,7 @@ class Poche foreach ($entries as $entry) { $tags = $this->store->retrieveTagsByEntry($entry['id']); foreach ($tags as $tag) { - $book->setSubject($tag); + $book->setSubject($tag['value']); } $log->logLine("Set up parameters"); @@ -1201,11 +1221,11 @@ class Poche $chapter = $content_start . $entry['content'] . $bookEnd; - $book->addChapter("Chapter " . $entry['id'] . ": " . $entry['title'], htmlspecialchars($entry['title']) . ".html", $chapter, true, EPub::EXTERNAL_REF_ADD); + $book->addChapter($entry['title'], htmlspecialchars($entry['title']) . ".html", $chapter, true, EPub::EXTERNAL_REF_ADD); } - if (true) { + if (DEBUG_POCHE) { $epuplog = $book->getLog(); $book->addChapter("Log", "Log.html", $content_start . $log->getLog() . "\n" . $bookEnd); // generation log // Only used in case we need to debug EPub.php. diff --git a/themes/baggy/config.twig b/themes/baggy/config.twig index f43f6d01..1b8b8648 100755 --- a/themes/baggy/config.twig +++ b/themes/baggy/config.twig @@ -126,7 +126,7 @@

    {% trans "Click here" %} {% trans "to export your wallabag data." %}

    Fancy a ebook ?

    - Click on this link to get all your articles in one ebook (ePub). + Click on this link to get all your articles in one ebook (ePub).

    {% trans "Cache" %}

    {% trans "Click here" %} {% trans "to delete cache." %}

    diff --git a/themes/baggy/home.twig b/themes/baggy/home.twig index 5dd91307..301f353a 100755 --- a/themes/baggy/home.twig +++ b/themes/baggy/home.twig @@ -57,6 +57,11 @@ {% endfor %} {% if view == 'home' %}{% if nb_results > 1 %}{{ "Mark all the entries as read" }}{% endif %}{% endif %} + + {% if tag %}{% trans "Download the articles from this tag in an epub" %} + {% elseif search_term is defined %}{% trans "Download the articles from this search in an epub" %} + {% else %}{% trans "Download the articles from this category in an epub" %}{% endif %} + {% endif %} {{ block('pager') }} {% endblock %} diff --git a/themes/baggy/view.twig b/themes/baggy/view.twig index 0dff4e27..af97407d 100755 --- a/themes/baggy/view.twig +++ b/themes/baggy/view.twig @@ -16,7 +16,7 @@ {% if constant('SHARE_SHAARLI') == 1 %}
  • {% trans "shaarli" %}
  • {% endif %} {% if constant('FLATTR') == 1 %}{% if flattr.status == constant('FLATTRABLE') %}
  • {% trans "flattr" %}
  • {% elseif flattr.status == constant('FLATTRED') %}
  • {% trans "flattr" %} ({{ flattr.numflattrs }})
  • {% endif %}{% endif %} {% if constant('SHOW_PRINTLINK') == 1 %}
  • {% trans "Print" %}
  • {% endif %} -
  • EPUB
  • +
  • EPUB
  • {% trans "Does this article appear wrong?" %}
  • -- cgit v1.2.3 From 2395a3802a821667b1bdd84a848d193d0d27ad2a Mon Sep 17 00:00:00 2001 From: tcit Date: Tue, 29 Apr 2014 19:57:59 +0200 Subject: Changed template organisation --- themes/baggy/home.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/baggy/home.twig b/themes/baggy/home.twig index 301f353a..ef2e097c 100755 --- a/themes/baggy/home.twig +++ b/themes/baggy/home.twig @@ -56,6 +56,7 @@ {% endfor %} + {{ block('pager') }} {% if view == 'home' %}{% if nb_results > 1 %}{{ "Mark all the entries as read" }}{% endif %}{% endif %} {% if tag %}{% trans "Download the articles from this tag in an epub" %} @@ -63,5 +64,4 @@ {% else %}{% trans "Download the articles from this category in an epub" %}{% endif %} {% endif %} - {{ block('pager') }} {% endblock %} -- cgit v1.2.3 From 4877836b12cde621a9c6200ec460ce025384ea35 Mon Sep 17 00:00:00 2001 From: tcit Date: Wed, 7 May 2014 12:40:09 +0200 Subject: Many improvements to epub produced : better cover, better tags --- inc/3rdparty/libraries/PHPePub/EPub.php | 12 +++++++----- inc/poche/Poche.class.php | 23 +++++++++++++++-------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/inc/3rdparty/libraries/PHPePub/EPub.php b/inc/3rdparty/libraries/PHPePub/EPub.php index 0260ce4f..e120b341 100644 --- a/inc/3rdparty/libraries/PHPePub/EPub.php +++ b/inc/3rdparty/libraries/PHPePub/EPub.php @@ -574,7 +574,7 @@ class EPub { * @param string $mimetype Image mimetype, such as "image/jpeg" or "image/png". * @return bool $success */ - function setCoverImage($fileName, $imageData = NULL, $mimetype = NULL) { + function setCover($fileName, $imageData = NULL, $mimetype = NULL, $coverText=NULL) { if ($this->isFinalized || $this->isCoverImageSet || array_key_exists("CoverPage.html", $this->fileList)) { return FALSE; } @@ -621,12 +621,13 @@ class EPub { . "\n" . "\t\n" . "\t\t\n" - . "\t\tCover Image\n" + . "\t\tCover\n" . "\t\t\n" . "\t\n" . "\t\n" + . "\t\t" . $coverText . "\n" . "\t\t
    \n" - . "\t\t\t\"Cover\n" + . "\t\t\t\"Cover\n" . "\t\t
    \n" . "\t\n" . "\n"; @@ -635,12 +636,13 @@ class EPub { . "\n" . "" . "\t\n" - . "\t\tCover Image\n" + . "\t\tCover\n" . "\t\t\n" . "\t\n" . "\t\n" . "\t\t
    \n" - . "\t\t\t\"Cover\n" + . "\t\t" . $coverText . "\n" + . "\t\t\t\"Cover\n" . "\t\t
    \n" . "\t\n" . "\n"; diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php index c476df3e..c99bfcba 100755 --- a/inc/poche/Poche.class.php +++ b/inc/poche/Poche.class.php @@ -1200,30 +1200,37 @@ class Poche $book->setSourceURL("http://$_SERVER[HTTP_HOST]"); $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "PHP"); + $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "wallabag"); $cssData = "body {\n margin-left: .5em;\n margin-right: .5em;\n text-align: justify;\n}\n\np {\n font-family: serif;\n font-size: 10pt;\n text-align: justify;\n text-indent: 1em;\n margin-top: 0px;\n margin-bottom: 1ex;\n}\n\nh1, h2 {\n font-family: sans-serif;\n font-style: italic;\n text-align: center;\n background-color: #6b879c;\n color: white;\n width: 100%;\n}\n\nh1 {\n margin-bottom: 2px;\n}\n\nh2 {\n margin-top: -2px;\n margin-bottom: 2px;\n}\n"; - $log->logLine("Add Cover Image"); - $book->setCoverImage("Cover.png", file_get_contents("themes/baggy/img/apple-touch-icon-152.png"), "image/png"); + $log->logLine("Add Cover"); + if (count($entries)>1){ + $cover = "

    " . $entries[0]['title'] . " and " . count($entries) . " other articles

    "; + } else { + $cover = "

    " . $entries[0]['title'] . "

    "; + } + $book->setCover("Cover.png", file_get_contents("themes/baggy/img/apple-touch-icon-152.png"), "image/png", $cover); + - $cover = $content_start . "

    My articles on wallabag

    \n

    As seen on : http://$_SERVER[HTTP_HOST]

    \n" . $bookEnd; - $book->addChapter("Notices", "Cover.html", $cover); + $book->setCover($cover); + //$book->addChapter("Notices", "Cover.html", $cover); $book->buildTOC(NULL, "toc", "Table of Contents", TRUE, TRUE); + $subject = ""; foreach ($entries as $entry) { $tags = $this->store->retrieveTagsByEntry($entry['id']); foreach ($tags as $tag) { - $book->setSubject($tag['value']); + $subject =. $tag['value'] . ','; } $log->logLine("Set up parameters"); - - $chapter = $content_start . $entry['content'] . $bookEnd; $book->addChapter($entry['title'], htmlspecialchars($entry['title']) . ".html", $chapter, true, EPub::EXTERNAL_REF_ADD); + $log->logLine("Added chapter " . $entry['title']); } - + $book->setSubject($subject); if (DEBUG_POCHE) { $epuplog = $book->getLog(); -- cgit v1.2.3 From 34acb02cbbc700f0d73ac340e906d3179932ab2b Mon Sep 17 00:00:00 2001 From: tcit Date: Wed, 7 May 2014 12:48:46 +0200 Subject: Added translation capabilities for epub system --- inc/poche/Poche.class.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php index c99bfcba..c59973f3 100755 --- a/inc/poche/Poche.class.php +++ b/inc/poche/Poche.class.php @@ -1189,10 +1189,10 @@ class Poche $log->logLine("getCurrentServerURL: " . $book->getCurrentServerURL()); $log->logLine("getCurrentPageURL..: " . $book->getCurrentPageURL()); - $book->setTitle("wallabag's articles"); + $book->setTitle(_('wallabag\'s articles')); $book->setIdentifier("http://$_SERVER[HTTP_HOST]", EPub::IDENTIFIER_URI); // Could also be the ISBN number, prefered for published books, or a UUID. //$book->setLanguage("en"); // Not needed, but included for the example, Language is mandatory, but EPub defaults to "en". Use RFC3066 Language codes, such as "en", "da", "fr" etc. - $book->setDescription("Some articles saved on my wallabag"); + $book->setDescription(_("Some articles saved on my wallabag")); $book->setAuthor("wallabag","wallabag"); $book->setPublisher("wallabag","wallabag"); // I hope this is a non existant address :) $book->setDate(time()); // Strictly not needed as the book date defaults to time(). @@ -1206,16 +1206,16 @@ class Poche $log->logLine("Add Cover"); if (count($entries)>1){ - $cover = "

    " . $entries[0]['title'] . " and " . count($entries) . " other articles

    "; + $cover = sprintf(_('

    %s and %s other articles

    '), $entries[0]['title'], count($entries)); } else { - $cover = "

    " . $entries[0]['title'] . "

    "; + $cover = sprintf(_('

    %s

    '), $entries[0]['title']); } $book->setCover("Cover.png", file_get_contents("themes/baggy/img/apple-touch-icon-152.png"), "image/png", $cover); $book->setCover($cover); //$book->addChapter("Notices", "Cover.html", $cover); - $book->buildTOC(NULL, "toc", "Table of Contents", TRUE, TRUE); + $book->buildTOC(NULL, "toc", _('Table of Contents'), TRUE, TRUE); $subject = ""; foreach ($entries as $entry) { @@ -1239,6 +1239,6 @@ class Poche //$book->addChapter("ePubLog", "ePubLog.html", $content_start . $epuplog . "\n" . $bookEnd); } $book->finalize(); - $zipData = $book->sendBook("wallabag's articles"); + $zipData = $book->sendBook(_('wallabag\'s articles')); } } -- cgit v1.2.3 From 1829b362fcfff09f91ad62f8d3c215aa794f2cd4 Mon Sep 17 00:00:00 2001 From: m-r-r Date: Sat, 10 May 2014 20:11:00 +0200 Subject: Updated the french translation --- locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.mo | Bin 12863 -> 16403 bytes locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.po | 566 ++++++++++++++++++---------- 2 files changed, 374 insertions(+), 192 deletions(-) diff --git a/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.mo b/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.mo index 2015f615..fd0e23f6 100755 Binary files a/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.mo and b/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.mo differ diff --git a/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.po b/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.po index 904a4178..0b2a87dc 100755 --- a/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.po +++ b/locale/fr_FR.utf8/LC_MESSAGES/fr_FR.utf8.po @@ -1,19 +1,19 @@ msgid "" msgstr "" -"Project-Id-Version: wallabag 1.6.0\n" +"Project-Id-Version: wallabag 1.6.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-02-25 18:33+0300\n" +"POT-Creation-Date: 2014-05-10 20:09+0100\n" "PO-Revision-Date: \n" -"Last-Translator: Amaury Carrade \n" +"Last-Translator: Mickaël RAYBAUD-ROIG \n" "Language-Team: \n" +"Language: fr_FR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-KeywordsList: _;gettext;gettext_noop\n" "X-Poedit-SourceCharset: UTF-8\n" -"X-Generator: Poedit 1.6.4\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" -"Language: fr_FR\n" +"X-Generator: Poedit 1.5.4\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" msgid "wallabag, a read it later open source system" msgstr "wallabag, un système open source de lecture différé" @@ -21,9 +21,97 @@ msgstr "wallabag, un système open source de lecture différé" msgid "login failed: user doesn't exist" msgstr "échec de l'identification : cet utilisateur n'existe pas" -msgid "return home" +msgid "save link!" +msgstr "enregistrer le lien !" + +msgid "plop" +msgstr "plop" + +msgid "powered by" +msgstr "propulsé par" + +msgid "debug mode is on so cache is off." +msgstr "le mode de debug est actif, le cache est donc désactivé." + +msgid "your wallabag version:" +msgstr "votre version de wallabag :" + +msgid "storage:" +msgstr "stockage :" + +msgid "login to your wallabag" +msgstr "se connecter à votre wallabag" + +msgid "Login to wallabag" +msgstr "Se connecter à wallabag" + +msgid "you are in demo mode, some features may be disabled." +msgstr "" +"vous êtes en mode démo, certaines fonctionnalités peuvent être désactivées." + +msgid "Username" +msgstr "Nom d'utilisateur" + +msgid "Password" +msgstr "Mot de passe" + +msgid "Stay signed in" +msgstr "Rester connecté" + +msgid "(Do not check on public computers)" +msgstr "(Ne pas cocher sur un ordinateur public)" + +msgid "Sign in" +msgstr "Se connecter" + +msgid "back to home" msgstr "retour à l'accueil" +msgid "favorites" +msgstr "favoris" + +msgid "archive" +msgstr "archive" + +msgid "unread" +msgstr "non lus" + +msgid "Tag" +msgstr "Tag" + +msgid "No articles found." +msgstr "Aucun article trouvé." + +msgid "estimated reading time:" +msgstr "temps de lecture estimé :" + +msgid "estimated reading time :" +msgstr "temps de lecture estimé :" + +msgid "Toggle mark as read" +msgstr "Marquer comme lu / non lu" + +msgid "toggle favorite" +msgstr "marquer / enlever comme favori" + +msgid "delete" +msgstr "supprimer" + +msgid "original" +msgstr "original" + +msgid "Mark all the entries as read" +msgstr "Marquer tous les articles comme lus" + +msgid "results" +msgstr "résultats" + +msgid " found for « " +msgstr "trouvé pour « " + +msgid "Only one result found for " +msgstr "Seulement un résultat trouvé pour " + msgid "config" msgstr "configuration" @@ -75,17 +163,29 @@ msgstr "Une version stable plus récente est disponible." msgid "You are up to date." msgstr "Vous êtes à jour." +msgid "Last check:" +msgstr "Dernière vérification: " + msgid "Latest dev version" msgstr "Dernière version de développement" msgid "A more recent development version is available." msgstr "Une version de développement plus récente est disponible." +msgid "You can clear cache to check the latest release." +msgstr "" +"Vous pouvez vider le cache pour vérifier que vous avez la dernière version." + msgid "Feeds" msgstr "Flux" -msgid "Your feed token is currently empty and must first be generated to enable feeds. Click here to generate it." -msgstr "Votre jeton de flux est actuellement vide et doit d'abord être généré pour activer les flux. Cliquez ici pour le générer." +msgid "" +"Your feed token is currently empty and must first be generated to enable " +"feeds. Click here to generate it." +msgstr "" +"Votre jeton de flux est actuellement vide et doit d'abord être généré pour " +"activer les flux. Cliquez ici pour " +"le générer." msgid "Unread feed" msgstr "Flux des non lus" @@ -102,8 +202,12 @@ msgstr "Votre jeton :" msgid "Your user id:" msgstr "Votre ID utilisateur :" -msgid "You can regenerate your token: generate!." -msgstr "Vous pouvez regénérer votre jeton : génération !." +msgid "" +"You can regenerate your token: generate!" +"." +msgstr "" +"Vous pouvez regénérer votre jeton : génération !." msgid "Change your theme" msgstr "Changer votre thème" @@ -126,36 +230,40 @@ msgstr "Modifier votre mot de passe" msgid "New password:" msgstr "Nouveau mot de passe :" -msgid "Password" -msgstr "Mot de passe" - msgid "Repeat your new password:" msgstr "Répétez votre nouveau mot de passe :" msgid "Import" msgstr "Importer" -msgid "Please execute the import script locally as it can take a very long time." -msgstr "Merci d'exécuter le script d'importation en local car cela peut prendre du temps." - -msgid "More info in the official documentation:" -msgstr "Plus d'infos dans la documentation officielle :" - -msgid "Import from Pocket" -msgstr "Import depuis Pocket" - -#, php-format -msgid "(you must have a %s file on your server)" -msgstr "(le fichier %s doit être présent sur le serveur)" - -msgid "Import from Readability" -msgstr "Importer depuis Readability" - -msgid "Import from Instapaper" -msgstr "Importer depuis Instapaper" +msgid "" +"You can import your Pocket, Readability, Instapaper, Wallabag or any data in " +"appropriate json or html format." +msgstr "" +"Vous pouvez importer depuis Pocket, Readability, Instapaper, Wallabag, ou " +"n'importe quel fichier au format JSON ou HTML approprié." -msgid "Import from wallabag" -msgstr "Importer depuis wallabag" +msgid "" +"Please select export file on your computer and press \"Import\" button below." +"
    Wallabag will parse your file, insert all URLs and start fetching of " +"articles if required.
    Fetching process is controlled by two constants in " +"your config file: IMPORT_LIMIT (how many articles are fetched at once) and " +"IMPORT_DELAY (delay between fetch of next batch of articles)." +msgstr "" +"Sélectionner le fichier à importer sur votre disque dur, et pressez la " +"bouton « Importer » ci-dessous.
    Wallabag analysera votre fichier, " +"ajoutera toutes les URL trouvées et commencera à télécharger les contenus si " +"nécessaire.
    Le processus de téléchargement est contrôlé par deux " +"constantes dans votre fichier de configuration:IMPORT_LIMIT (nombre d'éléments téléchargés à la fois) et IMPORT_DELAY " +"(le délai d'attente entre deux séquences de téléchargement)." + +msgid "File:" +msgstr "Fichier: " + +msgid "You can click here to fetch content for articles with no content." +msgstr "" +"Vous pouvez cliquer ici pour télécharger le contenu des articles vides." msgid "Export your wallabag data" msgstr "Exporter vos données de wallabag" @@ -175,110 +283,50 @@ msgstr "Cache" msgid "to delete cache." msgstr "pour effacer le cache." -msgid "You can enter multiple tags, separated by commas." -msgstr "Vous pouvez entrer plusieurs tags, séparés par des virgules." - -msgid "return to article" -msgstr "retourner à l'article" - -msgid "plop" -msgstr "plop" - -msgid "You can check your configuration here." -msgstr "Vous pouvez vérifier votre configuration ici." +msgid "Add user" +msgstr "Ajouter un utilisateur" -msgid "favoris" -msgstr "favoris" +msgid "Add a new user :" +msgstr "Ajouter un nouvel utilisateur: " -msgid "archive" -msgstr "archive" - -msgid "unread" -msgstr "non lus" - -msgid "by date asc" -msgstr "par date asc" - -msgid "by date" -msgstr "par date" - -msgid "by date desc" -msgstr "par date desc" - -msgid "by title asc" -msgstr "par titre asc" - -msgid "by title" -msgstr "par titre" - -msgid "by title desc" -msgstr "par titre desc" - -msgid "Tag" -msgstr "Tag" - -msgid "No articles found." -msgstr "Aucun article trouvé." - -msgid "Toggle mark as read" -msgstr "Marquer comme lu / non lu" - -msgid "toggle favorite" -msgstr "marquer / enlever comme favori" - -msgid "delete" -msgstr "supprimer" - -msgid "original" -msgstr "original" - -msgid "estimated reading time:" -msgstr "temps de lecture estimé :" - -msgid "mark all the entries as read" -msgstr "marquer tous les articles comme lus" - -msgid "results" -msgstr "résultats" - -msgid "installation" -msgstr "installation" - -msgid "install your wallabag" -msgstr "installez votre wallabag" - -msgid "wallabag is still not installed. Please fill the below form to install it. Don't hesitate to read the documentation on wallabag website." -msgstr "wallabag n'est pas encore installé. Merci de remplir le formulaire suivant pour l'installer. N'hésitez pas à lire la documentation sur le site de wallabag." +msgid "Login for new user" +msgstr "Identifiant du nouvel utilisateur" msgid "Login" msgstr "Nom d'utilisateur" -msgid "Repeat your password" -msgstr "Répétez votre mot de passe" +msgid "Password for new user" +msgstr "Mot de passe du nouvel utilisateur: " -msgid "Install" -msgstr "Installer" +msgid "Send" +msgstr "Imvoyer" -msgid "login to your wallabag" -msgstr "se connecter à votre wallabag" +msgid "Delete account" +msgstr "Supprimer le compte" -msgid "Login to wallabag" -msgstr "Se connecter à wallabag" +msgid "You can delete your account by entering your password and validating." +msgstr "" +"Vous pouvez supprimer votre compte en entrant votre mot de passe et en " +"validant." -msgid "you are in demo mode, some features may be disabled." -msgstr "vous êtes en mode démo, certaines fonctionnalités peuvent être désactivées." +msgid "Be careful, data will be erased forever (that is a very long time)." +msgstr "Attention, les données seront perdues pour toujours." -msgid "Username" -msgstr "Nom d'utilisateur" +msgid "Type here your password" +msgstr "Entrez votre mot de passe ici" -msgid "Stay signed in" -msgstr "Rester connecté" +msgid "You are the only user, you cannot delete your own account." +msgstr "" +"Vous êtes l'unique utilisateur, vous ne pouvez pas supprimer votre compte." -msgid "(Do not check on public computers)" -msgstr "(Ne pas cocher sur un ordinateur public)" +msgid "" +"To completely remove wallabag, delete the wallabag folder on your web server." +msgstr "" +"Pour déinstaller complètement Wallabag, supprimez le répertoire " +"wallabag de votre serveur Web." -msgid "Sign in" -msgstr "Se connecter" +msgid "Save a link" +msgstr "Ajouter un lien" msgid "Return home" msgstr "Retour accueil" @@ -310,6 +358,9 @@ msgstr "Shaarli" msgid "flattr" msgstr "Flattr" +msgid "Print" +msgstr "Imprimer" + msgid "Does this article appear wrong?" msgstr "Cet article s'affiche mal ?" @@ -319,54 +370,18 @@ msgstr "tags :" msgid "Edit tags" msgstr "Modifier les tags" -msgid "save link!" -msgstr "enregistrer le lien !" - -msgid "powered by" -msgstr "propulsé par" - -msgid "debug mode is on so cache is off." -msgstr "le mode de debug est actif, le cache est donc désactivé." - -msgid "your wallabag version:" -msgstr "votre version de wallabag :" - -msgid "storage:" -msgstr "stockage :" - -msgid "home" -msgstr "accueil" - -msgid "favorites" +msgid "favoris" msgstr "favoris" -msgid "tags" -msgstr "tags" +msgid "mark all the entries as read" +msgstr "marquer tous les articles comme lus" -msgid "save a link" -msgstr "sauver un lien" +msgid "toggle view mode" +msgstr "changer de mode de visualisation" -msgid "logout" -msgstr "déconnexion" - -msgid "back to home" +msgid "return home" msgstr "retour à l'accueil" -msgid "toggle mark as read" -msgstr "marquer comme lu / non lu" - -msgid "tweet" -msgstr "tweet" - -msgid "email" -msgstr "e-mail" - -msgid "this article appears wrong?" -msgstr "cet article s'affiche mal ?" - -msgid "No link available here!" -msgstr "Aucun lien n'est disponible ici !" - msgid "Poching a link" msgstr "Enregistrer un lien" @@ -395,7 +410,9 @@ msgid "a more recent development version is available." msgstr "une version de développement plus récente est disponible." msgid "Please execute the import script locally, it can take a very long time." -msgstr "Merci d'exécuter le script d'importation en local car cela peut prendre du temps." +msgstr "" +"Merci d'exécuter le script d'importation en local car cela peut prendre du " +"temps." msgid "More infos in the official doc:" msgstr "Plus d'infos dans la documentation officielle :" @@ -403,21 +420,146 @@ msgstr "Plus d'infos dans la documentation officielle :" msgid "import from Pocket" msgstr "importation depuis Pocket" +#, php-format +msgid "(you must have a %s file on your server)" +msgstr "(le fichier %s doit être présent sur le serveur)" + msgid "import from Readability" msgstr "importation depuis Readability" msgid "import from Instapaper" msgstr "importation depuis Instapaper" -msgid "estimated reading time :" -msgstr "temps de lecture estimé :" +msgid "Start typing for auto complete." +msgstr "Commencez à taper pour activer l'auto-complétion." -msgid "Mark all the entries as read" -msgstr "Marquer tous les articles comme lus" +msgid "You can enter multiple tags, separated by commas." +msgstr "Vous pouvez entrer plusieurs tags, séparés par des virgules." + +msgid "return to article" +msgstr "retourner à l'article" + +msgid "by date asc" +msgstr "par date asc" + +msgid "by date" +msgstr "par date" + +msgid "by date desc" +msgstr "par date desc" + +msgid "by title asc" +msgstr "par titre asc" + +msgid "by title" +msgstr "par titre" + +msgid "by title desc" +msgstr "par titre desc" + +msgid "home" +msgstr "accueil" + +msgid "tags" +msgstr "tags" + +msgid "save a link" +msgstr "sauver un lien" + +msgid "search" +msgstr "rechercher" + +msgid "logout" +msgstr "déconnexion" + +msgid "installation" +msgstr "installation" + +msgid "install your wallabag" +msgstr "installez votre wallabag" + +msgid "" +"wallabag is still not installed. Please fill the below form to install it. " +"Don't hesitate to read the documentation " +"on wallabag website." +msgstr "" +"wallabag n'est pas encore installé. Merci de remplir le formulaire suivant " +"pour l'installer. N'hésitez pas à lire la " +"documentation sur le site de wallabag." + +msgid "Repeat your password" +msgstr "Répétez votre mot de passe" + +msgid "Install" +msgstr "Installer" + +msgid "" +"You can check your configuration " +"here." +msgstr "" +"Vous pouvez vérifier votre configuration ici." msgid "Tags" msgstr "Tags" +msgid "No link available here!" +msgstr "Aucun lien n'est disponible ici !" + +msgid "toggle mark as read" +msgstr "marquer comme lu / non lu" + +msgid "tweet" +msgstr "tweet" + +msgid "email" +msgstr "e-mail" + +msgid "this article appears wrong?" +msgstr "cet article s'affiche mal ?" + +msgid "Search" +msgstr "Rechercher" + +msgid "Download required for " +msgstr "Téléchargement requis pour " + +msgid "records" +msgstr " éléments." + +msgid "Downloading next " +msgstr "Téléchargement des " + +msgid "articles, please wait" +msgstr " prochains éléments, veuillez patienter" + +msgid "Enter your search here" +msgstr "Entrez votre recherche ici" + +#, php-format +msgid "" +"The new user %s has been installed. Do you want to logout ?" +msgstr "" +"Le nouvel utilisateur « %s » a été ajouté. Voulez-vous vous déconnecter ?" + +#, php-format +msgid "Error : An user with the name %s already exists !" +msgstr "Erreur: Un utilisateur avec le nom « %s » existe déjà." + +#, php-format +msgid "User %s has been successfully deleted !" +msgstr "L'utilisateur « %s » a bien été supprimé !" + +msgid "Error : The password is wrong !" +msgstr "Erreur: Le mot de passe est incorrect !" + +msgid "Error : You are the only user, you cannot delete your account !" +msgstr "" +"Erreur: Vous êtes l'unique utilisateur, vous ne pouvez pas supprimer votre " +"compte !" + msgid "Untitled" msgstr "Sans titre" @@ -448,11 +590,15 @@ msgstr "en mode démo, vous ne pouvez pas mettre à jour le mot de passe" msgid "your password has been updated" msgstr "votre mot de passe a été mis à jour" -msgid "the two fields have to be filled & the password must be the same in the two fields" -msgstr "les deux champs doivent être remplis & le mot de passe doit être le même dans les deux" +msgid "" +"the two fields have to be filled & the password must be the same in the two " +"fields" +msgstr "" +"les deux champs doivent être remplis & le mot de passe doit être le même " +"dans les deux" msgid "still using the \"" -msgstr "utilise encore \"" +msgstr "Vous utilisez toujours \"" msgid "that theme does not seem to be installed" msgstr "ce thème ne semble pas installé" @@ -475,26 +621,26 @@ msgstr "bienvenue dans votre wallabag" msgid "login failed: bad login or password" msgstr "échec de l'identification : mauvais identifiant ou mot de passe" -msgid "import from instapaper completed" -msgstr "Importation depuis Instapaper achevée" +msgid "Untitled - Import - " +msgstr "Sans titre - Importer - " -msgid "import from pocket completed" -msgstr "Importation depuis Pocket achevée" +msgid "click to finish import" +msgstr "cliquez pour terminer l'importation" -msgid "import from Readability completed. " -msgstr "Importation depuis Readability achevée" +msgid "Articles inserted: " +msgstr "Articles ajoutés: " -msgid "import from Poche completed. " -msgstr "Importation depuis Pocket achevée" +msgid ". Please note, that some may be marked as \"read\"." +msgstr ". Notez que certains pourraient être marqués comme lus." -msgid "Unknown import provider." -msgstr "Format d'importation inconnu." +msgid "Import finished." +msgstr "Importation terminée." -msgid "Incomplete inc/poche/define.inc.php file, please define \"" -msgstr "Fichier inc/poche/define.inc.php incomplet, merci de définir \"" +msgid "Undefined" +msgstr "Non définit" -msgid "Could not find required \"" -msgstr "Impossible de trouver \"" +msgid "User with this id (" +msgstr "Utilisateur avec cet identifiant (" msgid "Uh, there is a problem while generating feeds." msgstr "Hum, il y a un problème lors de la génération des flux." @@ -505,11 +651,47 @@ msgstr "Cache effacé." msgid "Oops, it seems you don't have PHP 5." msgstr "Oups, vous ne semblez pas avoir PHP 5." -msgid "search" -msgstr "rechercher" +#~ msgid "" +#~ "Please execute the import script locally as it can take a very long time." +#~ msgstr "" +#~ "Merci d'exécuter le script d'importation en local car cela peut prendre " +#~ "du temps." -msgid "Search" -msgstr "Rechercher" +#~ msgid "More info in the official documentation:" +#~ msgstr "Plus d'infos dans la documentation officielle :" + +#~ msgid "Import from Pocket" +#~ msgstr "Import depuis Pocket" + +#~ msgid "Import from Readability" +#~ msgstr "Importer depuis Readability" + +#~ msgid "Import from Instapaper" +#~ msgstr "Importer depuis Instapaper" + +#~ msgid "Import from wallabag" +#~ msgstr "Importer depuis wallabag" + +#~ msgid "import from instapaper completed" +#~ msgstr "Importation depuis Instapaper achevée" + +#~ msgid "import from pocket completed" +#~ msgstr "Importation depuis Pocket achevée" + +#~ msgid "import from Readability completed. " +#~ msgstr "Importation depuis Readability achevée" + +#~ msgid "import from Poche completed. " +#~ msgstr "Importation depuis Pocket achevée" + +#~ msgid "Unknown import provider." +#~ msgstr "Format d'importation inconnu." + +#~ msgid "Incomplete inc/poche/define.inc.php file, please define \"" +#~ msgstr "Fichier inc/poche/define.inc.php incomplet, merci de définir \"" + +#~ msgid "Could not find required \"" +#~ msgstr "Impossible de trouver \"" #~ msgid "poche it!" #~ msgstr "pochez-le !" -- cgit v1.2.3 From f2b6b4e23064c40cde9e2ad5327499589dee503b Mon Sep 17 00:00:00 2001 From: tcit Date: Wed, 14 May 2014 22:03:16 +0200 Subject: Fix bugs and improved epub rendering --- inc/3rdparty/libraries/PHPePub/EPub.php | 14 ++++++------ inc/poche/Poche.class.php | 40 +++++++++++++++++---------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/inc/3rdparty/libraries/PHPePub/EPub.php b/inc/3rdparty/libraries/PHPePub/EPub.php index e120b341..f1f41bd5 100644 --- a/inc/3rdparty/libraries/PHPePub/EPub.php +++ b/inc/3rdparty/libraries/PHPePub/EPub.php @@ -574,7 +574,7 @@ class EPub { * @param string $mimetype Image mimetype, such as "image/jpeg" or "image/png". * @return bool $success */ - function setCover($fileName, $imageData = NULL, $mimetype = NULL, $coverText=NULL) { + function setCoverImage($fileName, $imageData = NULL, $mimetype = NULL,$bookTitle) { if ($this->isFinalized || $this->isCoverImageSet || array_key_exists("CoverPage.html", $this->fileList)) { return FALSE; } @@ -621,13 +621,13 @@ class EPub { . "\n" . "\t\n" . "\t\t\n" - . "\t\tCover\n" + . "\t\tCover Image\n" . "\t\t\n" . "\t\n" . "\t\n" - . "\t\t" . $coverText . "\n" + . "\t" . $bookTitle . "\n" . "\t\t
    \n" - . "\t\t\t\"Cover\n" + . "\t\t\t\"Cover\n" . "\t\t
    \n" . "\t\n" . "\n"; @@ -636,13 +636,13 @@ class EPub { . "\n" . "" . "\t\n" - . "\t\tCover\n" + . "\t\tCover Image\n" . "\t\t\n" . "\t\n" . "\t\n" . "\t\t
    \n" - . "\t\t" . $coverText . "\n" - . "\t\t\t\"Cover\n" + . "\t" . $bookTitle . "\n" + . "\t\t\t\"Cover\n" . "\t\t
    \n" . "\t\n" . "\n"; diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php index 85dd3848..99d2989b 100755 --- a/inc/poche/Poche.class.php +++ b/inc/poche/Poche.class.php @@ -1142,23 +1142,28 @@ class Poche $entryID = filter_var($_GET['id'],FILTER_SANITIZE_NUMBER_INT); $entry = $this->store->retrieveOneById($entryID, $this->user->getId()); $entries = array($entry); + $bookTitle = $entry['title']; break; case 'all': $entries = $this->store->retrieveAll($this->user->getId()); + $bookTitle = _('All my articles'); break; case 'tag': $tag = filter_var($_GET['tag'],FILTER_SANITIZE_STRING); $tags_id = $this->store->retrieveAllTags($this->user->getId(),$tag); $tag_id = $tags_id[0]["id"]; // we take the first result, which is supposed to match perfectly. There must be a workaround. $entries = $this->store->retrieveEntriesByTag($tag_id,$this->user->getId()); + $bookTitle = sprintf(_('Articles related to %s'),$tag); break; case 'category': $category = filter_var($_GET['category'],FILTER_SANITIZE_STRING); $entries = $this->store->getEntriesByView($category,$this->user->getId()); + $bookTitle = sprintf(_('All my articles in category %s'), $category); break; case 'search': $search = filter_var($_GET['search'],FILTER_SANITIZE_STRING); $entries = $this->store->search($search,$this->user->getId()); + $bookTitle = sprintf(_('All my articles for search %s'), $search); break; case 'default': die(_('Uh, there is a problem while generating epub.')); @@ -1166,12 +1171,11 @@ class Poche } $content_start = - "\n" - . "\n" - . "\n" - . "\n" - . "\n" - . "wallabag article\n" + "\n" + . "\n" + . "" + . "\n" + . "wallabag articles book\n" . "\n" . "\n"; @@ -1205,23 +1209,22 @@ class Poche $cssData = "body {\n margin-left: .5em;\n margin-right: .5em;\n text-align: justify;\n}\n\np {\n font-family: serif;\n font-size: 10pt;\n text-align: justify;\n text-indent: 1em;\n margin-top: 0px;\n margin-bottom: 1ex;\n}\n\nh1, h2 {\n font-family: sans-serif;\n font-style: italic;\n text-align: center;\n background-color: #6b879c;\n color: white;\n width: 100%;\n}\n\nh1 {\n margin-bottom: 2px;\n}\n\nh2 {\n margin-top: -2px;\n margin-bottom: 2px;\n}\n"; $log->logLine("Add Cover"); - if (count($entries)>1){ - $cover = sprintf(_('

    %s and %s other articles

    '), $entries[0]['title'], count($entries)); - } else { - $cover = sprintf(_('

    %s

    '), $entries[0]['title']); - } - $book->setCover("Cover.png", file_get_contents("themes/baggy/img/apple-touch-icon-152.png"), "image/png", $cover); + $fullTitle = "

    " . $bookTitle . "

    \n"; + + $book->setCoverImage("Cover.png", file_get_contents("themes/baggy/img/apple-touch-icon-152.png"), "image/png", $fullTitle); - $book->setCover($cover); - //$book->addChapter("Notices", "Cover.html", $cover); - $book->buildTOC(NULL, "toc", _('Table of Contents'), TRUE, TRUE); - $subject = ""; + $cover = $content_start . _('Produced by wallabag with PHPePub') . $bookEnd; + + //$book->addChapter("Table of Contents", "TOC.xhtml", NULL, false, EPub::EXTERNAL_REF_IGNORE); + $book->addChapter("Notices", "Cover2.html", $cover); + + $book->buildTOC(); foreach ($entries as $entry) { $tags = $this->store->retrieveTagsByEntry($entry['id']); foreach ($tags as $tag) { - $subject =. $tag['value'] . ','; + $book->setSubject($tag['value']); } $log->logLine("Set up parameters"); @@ -1229,8 +1232,7 @@ class Poche $chapter = $content_start . $entry['content'] . $bookEnd; $book->addChapter($entry['title'], htmlspecialchars($entry['title']) . ".html", $chapter, true, EPub::EXTERNAL_REF_ADD); $log->logLine("Added chapter " . $entry['title']); - } - $book->setSubject($subject); + } if (DEBUG_POCHE) { $epuplog = $book->getLog(); -- cgit v1.2.3 From f3f0b11393dd7f11bc1e63df9488c3930c0bc06f Mon Sep 17 00:00:00 2001 From: tcit Date: Thu, 15 May 2014 15:42:36 +0200 Subject: Better names for epub files and epub in all themes now --- inc/poche/Poche.class.php | 15 ++++++++++----- themes/baggy/config.twig | 5 +++-- themes/courgette/_view.twig | 1 + themes/courgette/config.twig | 3 +++ themes/courgette/home.twig | 9 ++++++++- themes/default/config.twig | 4 ++++ themes/default/home.twig | 9 ++++++++- themes/default/view.twig | 1 + 8 files changed, 38 insertions(+), 9 deletions(-) diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php index 99d2989b..bce7d651 100755 --- a/inc/poche/Poche.class.php +++ b/inc/poche/Poche.class.php @@ -1143,27 +1143,32 @@ class Poche $entry = $this->store->retrieveOneById($entryID, $this->user->getId()); $entries = array($entry); $bookTitle = $entry['title']; + $bookFileName = substr($bookTitle, 0, 200); break; case 'all': $entries = $this->store->retrieveAll($this->user->getId()); - $bookTitle = _('All my articles'); + $bookTitle = sprintf(_('All my articles on '), date(_('d.m.y'))); #translatable because each country has it's own date format system + $bookFileName = _('Allarticles') . date(_('dmY')); break; case 'tag': $tag = filter_var($_GET['tag'],FILTER_SANITIZE_STRING); $tags_id = $this->store->retrieveAllTags($this->user->getId(),$tag); $tag_id = $tags_id[0]["id"]; // we take the first result, which is supposed to match perfectly. There must be a workaround. $entries = $this->store->retrieveEntriesByTag($tag_id,$this->user->getId()); - $bookTitle = sprintf(_('Articles related to %s'),$tag); + $bookTitle = sprintf(_('Articles tagged %s'),$tag); + $bookFileName = substr(sprintf(_('Tag %s'),$tag), 0, 200); break; case 'category': $category = filter_var($_GET['category'],FILTER_SANITIZE_STRING); $entries = $this->store->getEntriesByView($category,$this->user->getId()); - $bookTitle = sprintf(_('All my articles in category %s'), $category); + $bookTitle = sprintf(_('All articles in category %s'), $category); + $bookFileName = substr(sprintf(_('Category %s'),$category), 0, 200); break; case 'search': $search = filter_var($_GET['search'],FILTER_SANITIZE_STRING); $entries = $this->store->search($search,$this->user->getId()); - $bookTitle = sprintf(_('All my articles for search %s'), $search); + $bookTitle = sprintf(_('All articles for search %s'), $search); + $bookFileName = substr(sprintf(_('Search %s'), $search), 0, 200); break; case 'default': die(_('Uh, there is a problem while generating epub.')); @@ -1241,6 +1246,6 @@ class Poche //$book->addChapter("ePubLog", "ePubLog.html", $content_start . $epuplog . "\n" . $bookEnd); } $book->finalize(); - $zipData = $book->sendBook(_('wallabag\'s articles')); + $zipData = $book->sendBook($bookFileName); } } diff --git a/themes/baggy/config.twig b/themes/baggy/config.twig index 1b8b8648..46735f07 100755 --- a/themes/baggy/config.twig +++ b/themes/baggy/config.twig @@ -125,8 +125,9 @@

    {% trans "Click here" %} {% trans "to download your database." %}

    {% endif %}

    {% trans "Click here" %} {% trans "to export your wallabag data." %}

    -

    Fancy a ebook ?

    - Click on this link to get all your articles in one ebook (ePub). +

    {% trans "Fancy an E-Book ?" %}

    +

    {% trans "Click on this link to get all your articles in one ebook (ePub 3 format)." %} +
    {% trans "This can take a while and can even fail if you have too many articles, depending on your server configuration." %}

    {% trans "Cache" %}

    {% trans "Click here" %} {% trans "to delete cache." %}

    diff --git a/themes/courgette/_view.twig b/themes/courgette/_view.twig index 9f9ea4f6..25479a3d 100755 --- a/themes/courgette/_view.twig +++ b/themes/courgette/_view.twig @@ -12,6 +12,7 @@ {% if constant('SHARE_MAIL') == 1 %}
  • {% endif %} {% if constant('SHARE_SHAARLI') == 1 %}
  • {% trans "shaarli" %}
  • {% endif %} {% if constant('FLATTR') == 1 %}{% if flattr.status == constant('FLATTRABLE') %}
  • {% trans "flattr" %}
  • {% elseif flattr.status == constant('FLATTRED') %}
  • {% trans "flattr" %}{{ flattr.numflattrs }}
  • {% endif %}{% endif %} +
  • EPUB
  • {% trans "this article appears wrong?" %}
  • diff --git a/themes/courgette/config.twig b/themes/courgette/config.twig index a022d733..9ab58461 100755 --- a/themes/courgette/config.twig +++ b/themes/courgette/config.twig @@ -81,6 +81,9 @@

    {% trans "Export your wallabag data" %}

    {% trans "Click here" %} {% trans "to export your wallabag data." %}

    +

    {% trans "Fancy an E-Book ?" %}

    +

    {% trans "Click on this link to get all your articles in one ebook (ePub 3 format)." %} +
    {% trans "This can take a while and can even fail if you have too many articles, depending on your server configuration." %}

    {% trans 'Add user' %}

    {% trans 'Add a new user :' %}

    diff --git a/themes/courgette/home.twig b/themes/courgette/home.twig index 6ba72d35..401f3f20 100755 --- a/themes/courgette/home.twig +++ b/themes/courgette/home.twig @@ -50,6 +50,13 @@

    {{ entry.content|striptags|slice(0, 300) }}...

    {% endfor %} - {% endif %} + {{ block('pager') }} + + {% if tag %}{% trans "Download the articles from this tag in an epub" %} + {% elseif search_term is defined %}{% trans "Download the articles from this search in an epub" %} + {% else %}{% trans "Download the articles from this category in an epub" %}{% endif %} + + {% endif %} + {% endblock %} \ No newline at end of file diff --git a/themes/default/config.twig b/themes/default/config.twig index 5ed9d80f..160f6046 100755 --- a/themes/default/config.twig +++ b/themes/default/config.twig @@ -127,6 +127,10 @@

    {% trans "Cache" %}

    {% trans "Click here" %} {% trans "to delete cache." %}

    +

    {% trans "Fancy an E-Book ?" %}

    +

    {% trans "Click on this link to get all your articles in one ebook (ePub 3 format)." %} +
    {% trans "This can take a while and can even fail if you have too many articles, depending on your server configuration." %}

    +

    {% trans 'Add user' %}

    {% trans 'Add a new user :' %}

    diff --git a/themes/default/home.twig b/themes/default/home.twig index d6cb98e8..e6c781f5 100755 --- a/themes/default/home.twig +++ b/themes/default/home.twig @@ -55,7 +55,14 @@

    {{ entry.content|striptags|slice(0, 300) }}...

    {% endfor %} - {% endif %} + {{ block('pager') }} + {% if view == 'home' %}{% if nb_results > 1 %}{% trans "mark all the entries as read" %}{% endif %}{% endif %} + + {% if tag %}{% trans "Download the articles from this tag in an epub" %} + {% elseif search_term is defined %}{% trans "Download the articles from this search in an epub" %} + {% else %}{% trans "Download the articles from this category in an epub" %}{% endif %} + + {% endif %} {% endblock %} diff --git a/themes/default/view.twig b/themes/default/view.twig index 983cc1cd..b7d48c00 100755 --- a/themes/default/view.twig +++ b/themes/default/view.twig @@ -15,6 +15,7 @@ {% if constant('SHARE_SHAARLI') == 1 %}
  • {% trans "shaarli" %}
  • {% endif %} {% if constant('FLATTR') == 1 %}{% if flattr.status == constant('FLATTRABLE') %}
  • {% trans "flattr" %}
  • {% elseif flattr.status == constant('FLATTRED') %}
  • {% trans "flattr" %}{{ flattr.numflattrs }}
  • {% endif %}{% endif %} {% if constant('SHOW_PRINTLINK') == 1 %}
  • {% trans "Print" %}
  • {% endif %} +
  • EPUB
  • {% trans "Does this article appear wrong?" %}
  • {% if constant('SHOW_READPERCENT') == 1 %}
  • 0%
  • {% endif %} -- cgit v1.2.3