parent
11f4efb51d
commit
60df6f1c18
|
@ -5,6 +5,7 @@
|
||||||
* Better handling of content type for book. Reported by Morg.
|
* Better handling of content type for book. Reported by Morg.
|
||||||
* Upped the size of thumbnails for OPDS. They look way better with Mantano.
|
* Upped the size of thumbnails for OPDS. They look way better with Mantano.
|
||||||
* Add language in OPDS feed (show in Mantano for example).
|
* Add language in OPDS feed (show in Mantano for example).
|
||||||
|
* Update metadata on downloaded epub. Disabled by default (check config item cops_update_epub-metadata).
|
||||||
|
|
||||||
0.2.3 - 20121205
|
0.2.3 - 20121205
|
||||||
* Add a .htaccess to make it easier to use with Apache
|
* Add a .htaccess to make it easier to use with Apache
|
||||||
|
|
5
README
5
README
|
@ -147,11 +147,14 @@ It also seems to work with Stanza.
|
||||||
* Locale message handling is inspired of http://www.mind-it.info/2010/02/22/a-simple-approach-to-localization-in-php/
|
* Locale message handling is inspired of http://www.mind-it.info/2010/02/22/a-simple-approach-to-localization-in-php/
|
||||||
* str_format function come from http://tmont.com/blargh/2010/1/string-format-in-php
|
* str_format function come from http://tmont.com/blargh/2010/1/string-format-in-php
|
||||||
* All icons come from the package Web0 by naf1971 : http://naf1971.deviantart.com/art/Web0-182067054
|
* All icons come from the package Web0 by naf1971 : http://naf1971.deviantart.com/art/Web0-182067054
|
||||||
* All testers
|
* Thanks to all testers
|
||||||
|
|
||||||
External libraries used :
|
External libraries used :
|
||||||
* JQuery : http://jquery.com/
|
* JQuery : http://jquery.com/
|
||||||
* Fancyapps : http://fancyapps.com/fancybox/
|
* Fancyapps : http://fancyapps.com/fancybox/
|
||||||
|
* Php-epub-meta : https://github.com/splitbrain/php-epub-meta with some modification by me
|
||||||
|
https://github.com/seblucas/php-epub-meta
|
||||||
|
* TbsZip : http://www.tinybutstrong.com/apps/tbszip/tbszip_help.html
|
||||||
|
|
||||||
= Copyright & License =
|
= Copyright & License =
|
||||||
|
|
||||||
|
|
31
book.php
31
book.php
|
@ -11,6 +11,7 @@ require_once('serie.php');
|
||||||
require_once('author.php');
|
require_once('author.php');
|
||||||
require_once('tag.php');
|
require_once('tag.php');
|
||||||
require_once('data.php');
|
require_once('data.php');
|
||||||
|
require_once('php-epub-meta/epub.php');
|
||||||
|
|
||||||
// Silly thing because PHP forbid string concatenation in class const
|
// Silly thing because PHP forbid string concatenation in class const
|
||||||
define ('SQL_BOOKS_LEFT_JOIN', "left outer join comments on comments.book = books.id
|
define ('SQL_BOOKS_LEFT_JOIN', "left outer join comments on comments.book = books.id
|
||||||
|
@ -259,6 +260,36 @@ class Book extends Base {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getUpdatedEpub ($idData)
|
||||||
|
{
|
||||||
|
$data = $this->getDataById ($idData);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$epub = new EPub ($data->getLocalPath ());
|
||||||
|
|
||||||
|
$epub->Title ($this->title);
|
||||||
|
$authorArray = array ();
|
||||||
|
foreach ($this->getAuthors() as $author) {
|
||||||
|
$authorArray [$author->sort] = $author->name;
|
||||||
|
}
|
||||||
|
$epub->Authors ($authorArray);
|
||||||
|
$epub->Language ($this->getLanguages ());
|
||||||
|
$epub->Description ($this->getComment (false));
|
||||||
|
$epub->Subjects ($this->getTagsName ());
|
||||||
|
$se = $this->getSerie ();
|
||||||
|
if (!is_null ($se)) {
|
||||||
|
$epub->Serie ($se->name);
|
||||||
|
$epub->SerieIndex ($this->seriesIndex);
|
||||||
|
}
|
||||||
|
$epub->download ($data->getFilename ());
|
||||||
|
}
|
||||||
|
catch (Exception $e)
|
||||||
|
{
|
||||||
|
echo "Exception : " . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function getLinkArray ()
|
public function getLinkArray ()
|
||||||
{
|
{
|
||||||
global $config;
|
global $config;
|
||||||
|
|
|
@ -127,4 +127,11 @@
|
||||||
* 0 : No
|
* 0 : No
|
||||||
*/
|
*/
|
||||||
$config['cops_use_fancyapps'] = "1";
|
$config['cops_use_fancyapps'] = "1";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Update Epub metadata before download
|
||||||
|
* 1 : Yes (enable)
|
||||||
|
* 0 : No
|
||||||
|
*/
|
||||||
|
$config['cops_update_epub-metadata'] = "1";
|
||||||
?>
|
?>
|
2
data.php
2
data.php
|
@ -100,7 +100,7 @@ class Data extends Base {
|
||||||
$textData = "&data=" . $idData;
|
$textData = "&data=" . $idData;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match ('/^\//', $config['calibre_directory']))
|
if (preg_match ('/^\//', $config['calibre_directory']) || ($type == "epub" && $config['cops_update_epub-metadata']))
|
||||||
{
|
{
|
||||||
if ($type != "jpg") $textData .= "&type=" . $type;
|
if ($type != "jpg") $textData .= "&type=" . $type;
|
||||||
return new Link ("fetch.php?id=$book->id" . $textData, $mime, $rel, $title);
|
return new Link ("fetch.php?id=$book->id" . $textData, $mime, $rel, $title);
|
||||||
|
|
|
@ -87,6 +87,12 @@
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$file = $book->getFilePath ($type, $idData, true);
|
$file = $book->getFilePath ($type, $idData, true);
|
||||||
|
if ($type == "epub" && $config['cops_update_epub-metadata'])
|
||||||
|
{
|
||||||
|
$book->getUpdatedEpub ($idData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
header('Content-Disposition: attachment; filename="' . basename ($file) . '"');
|
header('Content-Disposition: attachment; filename="' . basename ($file) . '"');
|
||||||
header ($config['cops_x_accel_redirect'] . ": " . $config['calibre_internal_directory'] . $file);
|
header ($config['cops_x_accel_redirect'] . ": " . $config['calibre_internal_directory'] . $file);
|
||||||
?>
|
?>
|
19
php-epub-meta/LICENSE
Normal file
19
php-epub-meta/LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2012 Andreas Gohr <andi@splitbrain.org>
|
||||||
|
|
||||||
|
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.
|
608
php-epub-meta/epub.php
Normal file
608
php-epub-meta/epub.php
Normal file
|
@ -0,0 +1,608 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* PHP EPub Meta library
|
||||||
|
*
|
||||||
|
* @author Andreas Gohr <andi@splitbrain.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once('tbszip.php');
|
||||||
|
|
||||||
|
define ("METADATA_FILE", "META-INF/container.xml");
|
||||||
|
|
||||||
|
class EPub {
|
||||||
|
public $xml; //FIXME change to protected, later
|
||||||
|
protected $xpath;
|
||||||
|
protected $file;
|
||||||
|
protected $meta;
|
||||||
|
protected $zip;
|
||||||
|
protected $namespaces;
|
||||||
|
protected $imagetoadd='';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param string $file path to epub file to work on
|
||||||
|
* @throws Exception if metadata could not be loaded
|
||||||
|
*/
|
||||||
|
public function __construct($file){
|
||||||
|
// open file
|
||||||
|
$this->file = $file;
|
||||||
|
$this->zip = new clsTbsZip();
|
||||||
|
if(!$this->zip->Open($this->file)){
|
||||||
|
throw new Exception('Failed to read epub file');
|
||||||
|
}
|
||||||
|
|
||||||
|
// read container data
|
||||||
|
if (!$this->zip->FileExists(METADATA_FILE)) {
|
||||||
|
throw new Exception ("Unable to find metadata.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->zip->FileRead(METADATA_FILE);
|
||||||
|
if($data == false){
|
||||||
|
throw new Exception('Failed to access epub container data');
|
||||||
|
}
|
||||||
|
$xml = new DOMDocument();
|
||||||
|
$xml->registerNodeClass('DOMElement','EPubDOMElement');
|
||||||
|
$xml->loadXML($data);
|
||||||
|
$xpath = new EPubDOMXPath($xml);
|
||||||
|
$nodes = $xpath->query('//n:rootfiles/n:rootfile[@media-type="application/oebps-package+xml"]');
|
||||||
|
$this->meta = $nodes->item(0)->attr('full-path');
|
||||||
|
|
||||||
|
// load metadata
|
||||||
|
if (!$this->zip->FileExists($this->meta)) {
|
||||||
|
throw new Exception ("Unable to find " . $this->meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $this->zip->FileRead($this->meta);
|
||||||
|
if(!$data){
|
||||||
|
throw new Exception('Failed to access epub metadata');
|
||||||
|
}
|
||||||
|
$this->xml = new DOMDocument();
|
||||||
|
$this->xml->registerNodeClass('DOMElement','EPubDOMElement');
|
||||||
|
$this->xml->loadXML($data);
|
||||||
|
$this->xml->formatOutput = true;
|
||||||
|
$this->xpath = new EPubDOMXPath($this->xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* file name getter
|
||||||
|
*/
|
||||||
|
public function file(){
|
||||||
|
return $this->file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the epub file
|
||||||
|
*/
|
||||||
|
public function close (){
|
||||||
|
$this->zip->FileCancelModif($this->meta);
|
||||||
|
// TODO : Add cancelation of cover image
|
||||||
|
$this->zip->Close ();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes back all meta data changes
|
||||||
|
* TODO update
|
||||||
|
*/
|
||||||
|
public function save(){
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
$res = @$zip->open($this->file, ZipArchive::CREATE);
|
||||||
|
if($res === false){
|
||||||
|
throw new Exception('Failed to write back metadata');
|
||||||
|
}
|
||||||
|
$zip->addFromString($this->meta,$this->xml->saveXML());
|
||||||
|
// add the cover image
|
||||||
|
if($this->imagetoadd){
|
||||||
|
$path = dirname('/'.$this->meta).'/php-epub-meta-cover.img'; // image path is relative to meta file
|
||||||
|
$path = ltrim($path,'/');
|
||||||
|
|
||||||
|
$zip->addFromString($path,file_get_contents($this->imagetoadd));
|
||||||
|
$this->imagetoadd='';
|
||||||
|
}
|
||||||
|
$zip->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the updated epub
|
||||||
|
*/
|
||||||
|
public function download($file){
|
||||||
|
$this->zip->FileReplace($this->meta,$this->xml->saveXML());
|
||||||
|
// add the cover image
|
||||||
|
if($this->imagetoadd){
|
||||||
|
$path = dirname('/'.$this->meta).'/php-epub-meta-cover.img'; // image path is relative to meta file
|
||||||
|
$path = ltrim($path,'/');
|
||||||
|
|
||||||
|
$this->zip->FileReplace($path,file_get_contents($this->imagetoadd));
|
||||||
|
$this->imagetoadd='';
|
||||||
|
}
|
||||||
|
$this->zip->Flush(TBSZIP_DOWNLOAD, $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set the book author(s)
|
||||||
|
*
|
||||||
|
* Authors should be given with a "file-as" and a real name. The file as
|
||||||
|
* is used for sorting in e-readers.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* array(
|
||||||
|
* 'Pratchett, Terry' => 'Terry Pratchett',
|
||||||
|
* 'Simpson, Jacqeline' => 'Jacqueline Simpson',
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* @params array $authors
|
||||||
|
*/
|
||||||
|
public function Authors($authors=false){
|
||||||
|
// set new data
|
||||||
|
if($authors !== false){
|
||||||
|
// Author where given as a comma separated list
|
||||||
|
if(is_string($authors)){
|
||||||
|
if($authors == ''){
|
||||||
|
$authors = array();
|
||||||
|
}else{
|
||||||
|
$authors = explode(',',$authors);
|
||||||
|
$authors = array_map('trim',$authors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete existing nodes
|
||||||
|
$nodes = $this->xpath->query('//opf:metadata/dc:creator[@opf:role="aut"]');
|
||||||
|
foreach($nodes as $node) $node->delete();
|
||||||
|
|
||||||
|
// add new nodes
|
||||||
|
$parent = $this->xpath->query('//opf:metadata')->item(0);
|
||||||
|
foreach($authors as $as => $name){
|
||||||
|
if(is_int($as)) $as = $name; //numeric array given
|
||||||
|
$node = $parent->newChild('dc:creator',$name);
|
||||||
|
$node->attr('opf:role', 'aut');
|
||||||
|
$node->attr('opf:file-as', $as);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->reparse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// read current data
|
||||||
|
$rolefix = false;
|
||||||
|
$authors = array();
|
||||||
|
$nodes = $this->xpath->query('//opf:metadata/dc:creator[@opf:role="aut"]');
|
||||||
|
if($nodes->length == 0){
|
||||||
|
// no nodes where found, let's try again without role
|
||||||
|
$nodes = $this->xpath->query('//opf:metadata/dc:creator');
|
||||||
|
$rolefix = true;
|
||||||
|
}
|
||||||
|
foreach($nodes as $node){
|
||||||
|
$name = $node->nodeValue;
|
||||||
|
$as = $node->attr('opf:file-as');
|
||||||
|
if(!$as){
|
||||||
|
$as = $name;
|
||||||
|
$node->attr('opf:file-as',$as);
|
||||||
|
}
|
||||||
|
if($rolefix){
|
||||||
|
$node->attr('opf:role','aut');
|
||||||
|
}
|
||||||
|
$authors[$as] = $name;
|
||||||
|
}
|
||||||
|
return $authors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or get the book title
|
||||||
|
*
|
||||||
|
* @param string $title
|
||||||
|
*/
|
||||||
|
public function Title($title=false){
|
||||||
|
return $this->getset('dc:title',$title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or get the book's language
|
||||||
|
*
|
||||||
|
* @param string $lang
|
||||||
|
*/
|
||||||
|
public function Language($lang=false){
|
||||||
|
return $this->getset('dc:language',$lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or get the book' publisher info
|
||||||
|
*
|
||||||
|
* @param string $publisher
|
||||||
|
*/
|
||||||
|
public function Publisher($publisher=false){
|
||||||
|
return $this->getset('dc:publisher',$publisher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or get the book's copyright info
|
||||||
|
*
|
||||||
|
* @param string $rights
|
||||||
|
*/
|
||||||
|
public function Copyright($rights=false){
|
||||||
|
return $this->getset('dc:rights',$rights);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or get the book's description
|
||||||
|
*
|
||||||
|
* @param string $description
|
||||||
|
*/
|
||||||
|
public function Description($description=false){
|
||||||
|
return $this->getset('dc:description',$description);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or get the book's ISBN number
|
||||||
|
*
|
||||||
|
* @param string $isbn
|
||||||
|
*/
|
||||||
|
public function ISBN($isbn=false){
|
||||||
|
return $this->getset('dc:identifier',$isbn,'opf:scheme','ISBN');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or get the Google Books ID
|
||||||
|
*
|
||||||
|
* @param string $google
|
||||||
|
*/
|
||||||
|
public function Google($google=false){
|
||||||
|
return $this->getset('dc:identifier',$google,'opf:scheme','GOOGLE');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or get the Amazon ID of the book
|
||||||
|
*
|
||||||
|
* @param string $amazon
|
||||||
|
*/
|
||||||
|
public function Amazon($amazon=false){
|
||||||
|
return $this->getset('dc:identifier',$amazon,'opf:scheme','AMAZON');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or get the Serie of the book
|
||||||
|
*
|
||||||
|
* @param string $serie
|
||||||
|
*/
|
||||||
|
public function Serie($serie=false){
|
||||||
|
return $this->getset('opf:meta',$serie,'name','calibre:series','content');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or get the Serie Index of the book
|
||||||
|
*
|
||||||
|
* @param string $serieIndex
|
||||||
|
*/
|
||||||
|
public function SerieIndex($serieIndex=false){
|
||||||
|
return $this->getset('opf:meta',$serieIndex,'name','calibre:series_index','content');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or get the book's subjects (aka. tags)
|
||||||
|
*
|
||||||
|
* Subject should be given as array, but a comma separated string will also
|
||||||
|
* be accepted.
|
||||||
|
*
|
||||||
|
* @param array $subjects
|
||||||
|
*/
|
||||||
|
public function Subjects($subjects=false){
|
||||||
|
// setter
|
||||||
|
if($subjects !== false){
|
||||||
|
if(is_string($subjects)){
|
||||||
|
if($subjects === ''){
|
||||||
|
$subjects = array();
|
||||||
|
}else{
|
||||||
|
$subjects = explode(',',$subjects);
|
||||||
|
$subjects = array_map('trim',$subjects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete previous
|
||||||
|
$nodes = $this->xpath->query('//opf:metadata/dc:subject');
|
||||||
|
foreach($nodes as $node){
|
||||||
|
$node->delete();
|
||||||
|
}
|
||||||
|
// add new ones
|
||||||
|
$parent = $this->xpath->query('//opf:metadata')->item(0);
|
||||||
|
foreach($subjects as $subj){
|
||||||
|
$node = $this->xml->createElement('dc:subject',htmlspecialchars($subj));
|
||||||
|
$node = $parent->appendChild($node);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->reparse();
|
||||||
|
}
|
||||||
|
|
||||||
|
//getter
|
||||||
|
$subjects = array();
|
||||||
|
$nodes = $this->xpath->query('//opf:metadata/dc:subject');
|
||||||
|
foreach($nodes as $node){
|
||||||
|
$subjects[] = $node->nodeValue;
|
||||||
|
}
|
||||||
|
return $subjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the cover data
|
||||||
|
*
|
||||||
|
* Returns an associative array with the following keys:
|
||||||
|
*
|
||||||
|
* mime - filetype (usually image/jpeg)
|
||||||
|
* data - the binary image data
|
||||||
|
* found - the internal path, or false if no image is set in epub
|
||||||
|
*
|
||||||
|
* When no image is set in the epub file, the binary data for a transparent
|
||||||
|
* GIF pixel is returned.
|
||||||
|
*
|
||||||
|
* When adding a new image this function return no or old data because the
|
||||||
|
* image contents are not in the epub file, yet. The image will be added when
|
||||||
|
* the save() method is called.
|
||||||
|
*
|
||||||
|
* @param string $path local filesystem path to a new cover image
|
||||||
|
* @param string $mime mime type of the given file
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function Cover($path=false, $mime=false){
|
||||||
|
// set cover
|
||||||
|
if($path !== false){
|
||||||
|
// remove current pointer
|
||||||
|
$nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]');
|
||||||
|
foreach($nodes as $node) $node->delete();
|
||||||
|
// remove previous manifest entries if they where made by us
|
||||||
|
$nodes = $this->xpath->query('//opf:manifest/opf:item[@id="php-epub-meta-cover"]');
|
||||||
|
foreach($nodes as $node) $node->delete();
|
||||||
|
|
||||||
|
if($path){
|
||||||
|
// add pointer
|
||||||
|
$parent = $this->xpath->query('//opf:metadata')->item(0);
|
||||||
|
$node = $parent->newChild('opf:meta');
|
||||||
|
$node->attr('opf:name','cover');
|
||||||
|
$node->attr('opf:content','php-epub-meta-cover');
|
||||||
|
|
||||||
|
// add manifest
|
||||||
|
$parent = $this->xpath->query('//opf:manifest')->item(0);
|
||||||
|
$node = $parent->newChild('opf:item');
|
||||||
|
$node->attr('id','php-epub-meta-cover');
|
||||||
|
$node->attr('opf:href','php-epub-meta-cover.img');
|
||||||
|
$node->attr('opf:media-type',$mime);
|
||||||
|
|
||||||
|
// remember path for save action
|
||||||
|
$this->imagetoadd = $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->reparse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// load cover
|
||||||
|
$nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]');
|
||||||
|
if(!$nodes->length) return $this->no_cover();
|
||||||
|
$coverid = (String) $nodes->item(0)->attr('opf:content');
|
||||||
|
if(!$coverid) return $this->no_cover();
|
||||||
|
|
||||||
|
$nodes = $this->xpath->query('//opf:manifest/opf:item[@id="'.$coverid.'"]');
|
||||||
|
if(!$nodes->length) return $this->no_cover();
|
||||||
|
$mime = $nodes->item(0)->attr('opf:media-type');
|
||||||
|
$path = $nodes->item(0)->attr('opf:href');
|
||||||
|
$path = dirname('/'.$this->meta).'/'.$path; // image path is relative to meta file
|
||||||
|
$path = ltrim($path,'/');
|
||||||
|
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
if(!@$zip->open($this->file)){
|
||||||
|
throw new Exception('Failed to read epub file');
|
||||||
|
}
|
||||||
|
$data = $zip->getFromName($path);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'mime' => $mime,
|
||||||
|
'data' => $data,
|
||||||
|
'found' => $path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple getter/setter for simple meta attributes
|
||||||
|
*
|
||||||
|
* It should only be used for attributes that are expected to be unique
|
||||||
|
*
|
||||||
|
* @param string $item XML node to set/get
|
||||||
|
* @param string $value New node value
|
||||||
|
* @param string $att Attribute name
|
||||||
|
* @param string $aval Attribute value
|
||||||
|
* @param string $datt Destination attribute
|
||||||
|
*/
|
||||||
|
protected function getset($item,$value=false,$att=false,$aval=false,$datt=false){
|
||||||
|
// construct xpath
|
||||||
|
$xpath = '//opf:metadata/'.$item;
|
||||||
|
if($att){
|
||||||
|
$xpath .= "[@$att=\"$aval\"]";
|
||||||
|
}
|
||||||
|
|
||||||
|
// set value
|
||||||
|
if($value !== false){
|
||||||
|
$value = htmlspecialchars($value);
|
||||||
|
$nodes = $this->xpath->query($xpath);
|
||||||
|
if($nodes->length == 1 ){
|
||||||
|
if($value === ''){
|
||||||
|
// the user want's to empty this value -> delete the node
|
||||||
|
$nodes->item(0)->delete();
|
||||||
|
}else{
|
||||||
|
// replace value
|
||||||
|
if ($datt){
|
||||||
|
$nodes->item(0)->attr ($datt, $value);
|
||||||
|
}else{
|
||||||
|
$nodes->item(0)->nodeValue = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
// if there are multiple matching nodes for some reason delete
|
||||||
|
// them. we'll replace them all with our own single one
|
||||||
|
foreach($nodes as $n) $n->delete();
|
||||||
|
// readd them
|
||||||
|
if($value){
|
||||||
|
$parent = $this->xpath->query('//opf:metadata')->item(0);
|
||||||
|
|
||||||
|
$node = $parent->newChild ($item);
|
||||||
|
if($att) $node->attr($att,$aval);
|
||||||
|
if ($datt){
|
||||||
|
$node->attr ($datt, $value);
|
||||||
|
}else{
|
||||||
|
$node->nodeValue = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->reparse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get value
|
||||||
|
$nodes = $this->xpath->query($xpath);
|
||||||
|
if($nodes->length){
|
||||||
|
if ($datt){
|
||||||
|
return $nodes->item(0)->attr ($datt);
|
||||||
|
}else{
|
||||||
|
return $nodes->item(0)->nodeValue;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a not found response for Cover()
|
||||||
|
*/
|
||||||
|
protected function no_cover(){
|
||||||
|
return array(
|
||||||
|
'data' => base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7'),
|
||||||
|
'mime' => 'image/gif',
|
||||||
|
'found' => false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reparse the DOM tree
|
||||||
|
*
|
||||||
|
* I had to rely on this because otherwise xpath failed to find the newly
|
||||||
|
* added nodes
|
||||||
|
*/
|
||||||
|
protected function reparse() {
|
||||||
|
$this->xml->loadXML($this->xml->saveXML());
|
||||||
|
$this->xpath = new EPubDOMXPath($this->xml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EPubDOMXPath extends DOMXPath {
|
||||||
|
public function __construct(DOMDocument $doc){
|
||||||
|
parent::__construct($doc);
|
||||||
|
|
||||||
|
if(is_a($doc->documentElement, 'EPubDOMElement')){
|
||||||
|
foreach($doc->documentElement->namespaces as $ns => $url){
|
||||||
|
$this->registerNamespace($ns,$url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EPubDOMElement extends DOMElement {
|
||||||
|
public $namespaces = array(
|
||||||
|
'n' => 'urn:oasis:names:tc:opendocument:xmlns:container',
|
||||||
|
'opf' => 'http://www.idpf.org/2007/opf',
|
||||||
|
'dc' => 'http://purl.org/dc/elements/1.1/'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct($name, $value='', $namespaceURI=''){
|
||||||
|
list($ns,$name) = $this->splitns($name);
|
||||||
|
$value = htmlspecialchars($value);
|
||||||
|
if(!$namespaceURI && $ns){
|
||||||
|
$namespaceURI = $this->namespaces[$ns];
|
||||||
|
}
|
||||||
|
parent::__construct($name, $value, $namespaceURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and append a new child
|
||||||
|
*
|
||||||
|
* Works with our epub namespaces and omits default namespaces
|
||||||
|
*/
|
||||||
|
public function newChild($name, $value=''){
|
||||||
|
list($ns,$local) = $this->splitns($name);
|
||||||
|
if($ns){
|
||||||
|
$nsuri = $this->namespaces[$ns];
|
||||||
|
if($this->isDefaultNamespace($nsuri)){
|
||||||
|
$name = $local;
|
||||||
|
$nsuri = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this doesn't call the construcor: $node = $this->ownerDocument->createElement($name,$value);
|
||||||
|
$node = new EPubDOMElement($name,$value,$nsuri);
|
||||||
|
return $this->appendChild($node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split given name in namespace prefix and local part
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @return array (namespace, name)
|
||||||
|
*/
|
||||||
|
public function splitns($name){
|
||||||
|
$list = explode(':',$name,2);
|
||||||
|
if(count($list) < 2) array_unshift($list,'');
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple EPub namespace aware attribute accessor
|
||||||
|
*/
|
||||||
|
public function attr($attr,$value=null){
|
||||||
|
list($ns,$attr) = $this->splitns($attr);
|
||||||
|
|
||||||
|
$nsuri = '';
|
||||||
|
if($ns){
|
||||||
|
$nsuri = $this->namespaces[$ns];
|
||||||
|
if(!$this->namespaceURI){
|
||||||
|
if($this->isDefaultNamespace($nsuri)){
|
||||||
|
$nsuri = '';
|
||||||
|
}
|
||||||
|
}elseif($this->namespaceURI == $nsuri){
|
||||||
|
$nsuri = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!is_null($value)){
|
||||||
|
if($value === false){
|
||||||
|
// delete if false was given
|
||||||
|
if($nsuri){
|
||||||
|
$this->removeAttributeNS($nsuri,$attr);
|
||||||
|
}else{
|
||||||
|
$this->removeAttribute($attr);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
// modify if value was given
|
||||||
|
if($nsuri){
|
||||||
|
$this->setAttributeNS($nsuri,$attr,$value);
|
||||||
|
}else{
|
||||||
|
$this->setAttribute($attr,$value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
// return value if none was given
|
||||||
|
if($nsuri){
|
||||||
|
return $this->getAttributeNS($nsuri,$attr);
|
||||||
|
}else{
|
||||||
|
return $this->getAttribute($attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove this node from the DOM
|
||||||
|
*/
|
||||||
|
public function delete(){
|
||||||
|
$this->parentNode->removeChild($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
883
php-epub-meta/tbszip.php
Normal file
883
php-epub-meta/tbszip.php
Normal file
|
@ -0,0 +1,883 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
TbsZip version 2.11 (2012-02-14)
|
||||||
|
Author : Skrol29 (email: http://www.tinybutstrong.com/onlyyou.html)
|
||||||
|
Licence : LGPL
|
||||||
|
This class is independent from any other classes and has been originally created for the OpenTbs plug-in
|
||||||
|
for TinyButStrong Template Engine (TBS). OpenTbs makes TBS able to merge OpenOffice and Ms Office documents.
|
||||||
|
Visit http://www.tinybutstrong.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
define('TBSZIP_DOWNLOAD',1); // download (default)
|
||||||
|
define('TBSZIP_NOHEADER',4); // option to use with DOWNLOAD: no header is sent
|
||||||
|
define('TBSZIP_FILE',8); // output to file , or add from file
|
||||||
|
define('TBSZIP_STRING',32); // output to string, or add from string
|
||||||
|
|
||||||
|
class clsTbsZip {
|
||||||
|
|
||||||
|
function __construct() {
|
||||||
|
$this->Meth8Ok = extension_loaded('zlib'); // check if Zlib extension is available. This is need for compress and uncompress with method 8.
|
||||||
|
$this->DisplayError = true;
|
||||||
|
$this->ArchFile = '';
|
||||||
|
$this->Error = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CreateNew($ArchName='new.zip') {
|
||||||
|
// Create a new virtual empty archive, the name will be the default name when the archive is flushed.
|
||||||
|
if (!isset($this->Meth8Ok)) $this->__construct(); // for PHP 4 compatibility
|
||||||
|
$this->Close(); // note that $this->ArchHnd is set to false here
|
||||||
|
$this->Error = false;
|
||||||
|
$this->ArchFile = $ArchName;
|
||||||
|
$this->ArchIsNew = true;
|
||||||
|
$bin = 'PK'.chr(05).chr(06).str_repeat(chr(0), 18);
|
||||||
|
$this->CdEndPos = strlen($bin) - 4;
|
||||||
|
$this->CdInfo = array('disk_num_curr'=>0, 'disk_num_cd'=>0, 'file_nbr_curr'=>0, 'file_nbr_tot'=>0, 'l_cd'=>0, 'p_cd'=>0, 'l_comm'=>0, 'v_comm'=>'', 'bin'=>$bin);
|
||||||
|
$this->CdPos = $this->CdInfo['p_cd'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function Open($ArchFile) {
|
||||||
|
// Open the zip archive
|
||||||
|
if (!isset($this->Meth8Ok)) $this->__construct(); // for PHP 4 compatibility
|
||||||
|
$this->Close(); // close handle and init info
|
||||||
|
$this->Error = false;
|
||||||
|
$this->ArchFile = $ArchFile;
|
||||||
|
$this->ArchIsNew = false;
|
||||||
|
// open the file
|
||||||
|
$this->ArchHnd = fopen($ArchFile, 'rb');
|
||||||
|
$ok = !($this->ArchHnd===false);
|
||||||
|
if ($ok) $ok = $this->CentralDirRead();
|
||||||
|
return $ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Close() {
|
||||||
|
if (isset($this->ArchHnd) and ($this->ArchHnd!==false)) fclose($this->ArchHnd);
|
||||||
|
$this->ArchFile = '';
|
||||||
|
$this->ArchHnd = false;
|
||||||
|
$this->CdInfo = array();
|
||||||
|
$this->CdFileLst = array();
|
||||||
|
$this->CdFileNbr = 0;
|
||||||
|
$this->CdFileByName = array();
|
||||||
|
$this->VisFileLst = array();
|
||||||
|
$this->ArchCancelModif();
|
||||||
|
}
|
||||||
|
|
||||||
|
function ArchCancelModif() {
|
||||||
|
$this->LastReadComp = false; // compression of the last read file (1=compressed, 0=stored not compressed, -1= stored compressed but read uncompressed)
|
||||||
|
$this->LastReadIdx = false; // index of the last file read
|
||||||
|
$this->ReplInfo = array();
|
||||||
|
$this->ReplByPos = array();
|
||||||
|
$this->AddInfo = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileAdd($Name, $Data, $DataType=TBSZIP_STRING, $Compress=true) {
|
||||||
|
|
||||||
|
if ($Data===false) return $this->FileCancelModif($Name, false); // Cancel a previously added file
|
||||||
|
|
||||||
|
// Save information for adding a new file into the archive
|
||||||
|
$Diff = 30 + 46 + 2*strlen($Name); // size of the header + cd info
|
||||||
|
$Ref = $this->_DataCreateNewRef($Data, $DataType, $Compress, $Diff, $Name);
|
||||||
|
if ($Ref===false) return false;
|
||||||
|
$Ref['name'] = $Name;
|
||||||
|
$this->AddInfo[] = $Ref;
|
||||||
|
return $Ref['res'];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function CentralDirRead() {
|
||||||
|
$cd_info = 'PK'.chr(05).chr(06); // signature of the Central Directory
|
||||||
|
$cd_pos = -22;
|
||||||
|
$this->_MoveTo($cd_pos, SEEK_END);
|
||||||
|
$b = $this->_ReadData(4);
|
||||||
|
if ($b!==$cd_info) return $this->RaiseError('The footer of the Central Directory is not found.');
|
||||||
|
|
||||||
|
$this->CdEndPos = ftell($this->ArchHnd) - 4;
|
||||||
|
$this->CdInfo = $this->CentralDirRead_End($cd_info);
|
||||||
|
$this->CdFileLst = array();
|
||||||
|
$this->CdFileNbr = $this->CdInfo['file_nbr_curr'];
|
||||||
|
$this->CdPos = $this->CdInfo['p_cd'];
|
||||||
|
|
||||||
|
if ($this->CdFileNbr<=0) return $this->RaiseError('No file found in the Central Directory.');
|
||||||
|
if ($this->CdPos<=0) return $this->RaiseError('No position found for the Central Directory listing.');
|
||||||
|
|
||||||
|
$this->_MoveTo($this->CdPos);
|
||||||
|
for ($i=0;$i<$this->CdFileNbr;$i++) {
|
||||||
|
$x = $this->CentralDirRead_File($i);
|
||||||
|
if ($x!==false) {
|
||||||
|
$this->CdFileLst[$i] = $x;
|
||||||
|
$this->CdFileByName[$x['v_name']] = $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CentralDirRead_End($cd_info) {
|
||||||
|
$b = $cd_info.$this->_ReadData(18);
|
||||||
|
$x = array();
|
||||||
|
$x['disk_num_curr'] = $this->_GetDec($b,4,2); // number of this disk
|
||||||
|
$x['disk_num_cd'] = $this->_GetDec($b,6,2); // number of the disk with the start of the central directory
|
||||||
|
$x['file_nbr_curr'] = $this->_GetDec($b,8,2); // total number of entries in the central directory on this disk
|
||||||
|
$x['file_nbr_tot'] = $this->_GetDec($b,10,2); // total number of entries in the central directory
|
||||||
|
$x['l_cd'] = $this->_GetDec($b,12,4); // size of the central directory
|
||||||
|
$x['p_cd'] = $this->_GetDec($b,16,4); // offset of start of central directory with respect to the starting disk number
|
||||||
|
$x['l_comm'] = $this->_GetDec($b,20,2); // .ZIP file comment length
|
||||||
|
$x['v_comm'] = $this->_ReadData($x['l_comm']); // .ZIP file comment
|
||||||
|
$x['bin'] = $b.$x['v_comm'];
|
||||||
|
return $x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CentralDirRead_File($idx) {
|
||||||
|
|
||||||
|
$b = $this->_ReadData(46);
|
||||||
|
|
||||||
|
$x = $this->_GetHex($b,0,4);
|
||||||
|
if ($x!=='h:02014b50') return $this->RaiseError('Signature of file information not found in the Central Directory in position '.(ftell($this->ArchHnd)-46).' for file #'.$idx.'.');
|
||||||
|
|
||||||
|
$x = array();
|
||||||
|
$x['vers_used'] = $this->_GetDec($b,4,2);
|
||||||
|
$x['vers_necess'] = $this->_GetDec($b,6,2);
|
||||||
|
$x['purp'] = $this->_GetBin($b,8,2);
|
||||||
|
$x['meth'] = $this->_GetDec($b,10,2);
|
||||||
|
$x['time'] = $this->_GetDec($b,12,2);
|
||||||
|
$x['date'] = $this->_GetDec($b,14,2);
|
||||||
|
$x['crc32'] = $this->_GetDec($b,16,4);
|
||||||
|
$x['l_data_c'] = $this->_GetDec($b,20,4);
|
||||||
|
$x['l_data_u'] = $this->_GetDec($b,24,4);
|
||||||
|
$x['l_name'] = $this->_GetDec($b,28,2);
|
||||||
|
$x['l_fields'] = $this->_GetDec($b,30,2);
|
||||||
|
$x['l_comm'] = $this->_GetDec($b,32,2);
|
||||||
|
$x['disk_num'] = $this->_GetDec($b,34,2);
|
||||||
|
$x['int_file_att'] = $this->_GetDec($b,36,2);
|
||||||
|
$x['ext_file_att'] = $this->_GetDec($b,38,4);
|
||||||
|
$x['p_loc'] = $this->_GetDec($b,42,4);
|
||||||
|
$x['v_name'] = $this->_ReadData($x['l_name']);
|
||||||
|
$x['v_fields'] = $this->_ReadData($x['l_fields']);
|
||||||
|
$x['v_comm'] = $this->_ReadData($x['l_comm']);
|
||||||
|
|
||||||
|
$x['bin'] = $b.$x['v_name'].$x['v_fields'].$x['v_comm'];
|
||||||
|
|
||||||
|
return $x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RaiseError($Msg) {
|
||||||
|
if ($this->DisplayError) echo '<strong>'.get_class($this).' ERROR :</strong> '.$Msg.'<br>'."\r\n";
|
||||||
|
$this->Error = $Msg;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Debug($FileHeaders=false) {
|
||||||
|
|
||||||
|
$this->DisplayError = true;
|
||||||
|
|
||||||
|
echo "<br />\r\n";
|
||||||
|
echo "------------------<br/>\r\n";
|
||||||
|
echo "Central Directory:<br/>\r\n";
|
||||||
|
echo "------------------<br/>\r\n";
|
||||||
|
print_r($this->CdInfo);
|
||||||
|
|
||||||
|
echo "<br />\r\n";
|
||||||
|
echo "-----------------------------------<br/>\r\n";
|
||||||
|
echo "File List in the Central Directory:<br/>\r\n";
|
||||||
|
echo "-----------------------------------<br/>\r\n";
|
||||||
|
print_r($this->CdFileLst);
|
||||||
|
|
||||||
|
if ($FileHeaders) {
|
||||||
|
echo "<br/>\r\n";
|
||||||
|
echo "------------------------------<br/>\r\n";
|
||||||
|
echo "File List in the Data Section:<br/>\r\n";
|
||||||
|
echo "------------------------------<br/>\r\n";
|
||||||
|
$idx = 0;
|
||||||
|
$pos = 0;
|
||||||
|
$this->_MoveTo($pos);
|
||||||
|
while ($ok = $this->_ReadFile($idx,false)) {
|
||||||
|
$this->VisFileLst[$idx]['debug_pos'] = $pos;
|
||||||
|
$pos = ftell($this->ArchHnd);
|
||||||
|
$idx++;
|
||||||
|
}
|
||||||
|
print_r($this->VisFileLst);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileExists($NameOrIdx) {
|
||||||
|
return ($this->FileGetIdx($NameOrIdx)!==false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileGetIdx($NameOrIdx) {
|
||||||
|
// Check if a file name, or a file index exists in the Central Directory, and return its index
|
||||||
|
if (is_string($NameOrIdx)) {
|
||||||
|
if (isset($this->CdFileByName[$NameOrIdx])) {
|
||||||
|
return $this->CdFileByName[$NameOrIdx];
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isset($this->CdFileLst[$NameOrIdx])) {
|
||||||
|
return $NameOrIdx;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileGetIdxAdd($Name) {
|
||||||
|
// Check if a file name exists in the list of file to add, and return its index
|
||||||
|
if (!is_string($Name)) return false;
|
||||||
|
$idx_lst = array_keys($this->AddInfo);
|
||||||
|
foreach ($idx_lst as $idx) {
|
||||||
|
if ($this->AddInfo[$idx]['name']===$Name) return $idx;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileRead($NameOrIdx, $Uncompress=true) {
|
||||||
|
|
||||||
|
$this->LastReadComp = false; // means the file is not found
|
||||||
|
$this->LastReadIdx = false;
|
||||||
|
|
||||||
|
$idx = $this->FileGetIdx($NameOrIdx);
|
||||||
|
if ($idx===false) return $this->RaiseError('File "'.$NameOrIdx.'" is not found in the Central Directory.');
|
||||||
|
|
||||||
|
$pos = $this->CdFileLst[$idx]['p_loc'];
|
||||||
|
$this->_MoveTo($pos);
|
||||||
|
|
||||||
|
$this->LastReadIdx = $idx; // Can be usefull to get the idx
|
||||||
|
|
||||||
|
$Data = $this->_ReadFile($idx, true);
|
||||||
|
|
||||||
|
// Manage uncompression
|
||||||
|
$Comp = 1; // means the contents stays compressed
|
||||||
|
$meth = $this->CdFileLst[$idx]['meth'];
|
||||||
|
if ($meth==8) {
|
||||||
|
if ($Uncompress) {
|
||||||
|
if ($this->Meth8Ok) {
|
||||||
|
$Data = gzinflate($Data);
|
||||||
|
$Comp = -1; // means uncompressed
|
||||||
|
} else {
|
||||||
|
$this->RaiseError('Unable to uncompress file "'.$NameOrIdx.'" because extension Zlib is not installed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif($meth==0) {
|
||||||
|
$Comp = 0; // means stored without compression
|
||||||
|
} else {
|
||||||
|
if ($Uncompress) $this->RaiseError('Unable to uncompress file "'.$NameOrIdx.'" because it is compressed with method '.$meth.'.');
|
||||||
|
}
|
||||||
|
$this->LastReadComp = $Comp;
|
||||||
|
|
||||||
|
return $Data;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function _ReadFile($idx, $ReadData) {
|
||||||
|
// read the file header (and maybe the data ) in the archive, assuming the cursor in at a new file position
|
||||||
|
|
||||||
|
$b = $this->_ReadData(30);
|
||||||
|
|
||||||
|
$x = $this->_GetHex($b,0,4);
|
||||||
|
if ($x!=='h:04034b50') return $this->RaiseError('Signature of file information not found in the Data Section in position '.(ftell($this->ArchHnd)-30).' for file #'.$idx.'.');
|
||||||
|
|
||||||
|
$x = array();
|
||||||
|
$x['vers'] = $this->_GetDec($b,4,2);
|
||||||
|
$x['purp'] = $this->_GetBin($b,6,2);
|
||||||
|
$x['meth'] = $this->_GetDec($b,8,2);
|
||||||
|
$x['time'] = $this->_GetDec($b,10,2);
|
||||||
|
$x['date'] = $this->_GetDec($b,12,2);
|
||||||
|
$x['crc32'] = $this->_GetDec($b,14,4);
|
||||||
|
$x['l_data_c'] = $this->_GetDec($b,18,4);
|
||||||
|
$x['l_data_u'] = $this->_GetDec($b,22,4);
|
||||||
|
$x['l_name'] = $this->_GetDec($b,26,2);
|
||||||
|
$x['l_fields'] = $this->_GetDec($b,28,2);
|
||||||
|
$x['v_name'] = $this->_ReadData($x['l_name']);
|
||||||
|
$x['v_fields'] = $this->_ReadData($x['l_fields']);
|
||||||
|
|
||||||
|
$x['bin'] = $b.$x['v_name'].$x['v_fields'];
|
||||||
|
|
||||||
|
// Read Data
|
||||||
|
$len_cd = $this->CdFileLst[$idx]['l_data_c'];
|
||||||
|
if ($x['l_data_c']==0) {
|
||||||
|
// Sometimes, the size is not specified in the local information.
|
||||||
|
$len = $len_cd;
|
||||||
|
} else {
|
||||||
|
$len = $x['l_data_c'];
|
||||||
|
if ($len!=$len_cd) {
|
||||||
|
//echo "TbsZip Warning: Local information for file #".$idx." says len=".$len.", while Central Directory says len=".$len_cd.".";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ReadData) {
|
||||||
|
$Data = $this->_ReadData($len);
|
||||||
|
} else {
|
||||||
|
$this->_MoveTo($len, SEEK_CUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description information
|
||||||
|
$desc_ok = ($x['purp'][2+3]=='1');
|
||||||
|
if ($desc_ok) {
|
||||||
|
$b = $this->_ReadData(16);
|
||||||
|
$x['desc_bin'] = $b;
|
||||||
|
$x['desc_sign'] = $this->_GetHex($b,0,4); // not specified in the documentation sign=h:08074b50
|
||||||
|
$x['desc_crc32'] = $this->_GetDec($b,4,4);
|
||||||
|
$x['desc_l_data_c'] = $this->_GetDec($b,8,4);
|
||||||
|
$x['desc_l_data_u'] = $this->_GetDec($b,12,4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save file info without the data
|
||||||
|
$this->VisFileLst[$idx] = $x;
|
||||||
|
|
||||||
|
// Return the info
|
||||||
|
if ($ReadData) {
|
||||||
|
return $Data;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileReplace($NameOrIdx, $Data, $DataType=TBSZIP_STRING, $Compress=true) {
|
||||||
|
// Store replacement information.
|
||||||
|
|
||||||
|
$idx = $this->FileGetIdx($NameOrIdx);
|
||||||
|
if ($idx===false) return $this->RaiseError('File "'.$NameOrIdx.'" is not found in the Central Directory.');
|
||||||
|
|
||||||
|
$pos = $this->CdFileLst[$idx]['p_loc'];
|
||||||
|
|
||||||
|
if ($Data===false) {
|
||||||
|
// file to delete
|
||||||
|
$this->ReplInfo[$idx] = false;
|
||||||
|
$Result = true;
|
||||||
|
} else {
|
||||||
|
// file to replace
|
||||||
|
$Diff = - $this->CdFileLst[$idx]['l_data_c'];
|
||||||
|
$Ref = $this->_DataCreateNewRef($Data, $DataType, $Compress, $Diff, $NameOrIdx);
|
||||||
|
if ($Ref===false) return false;
|
||||||
|
$this->ReplInfo[$idx] = $Ref;
|
||||||
|
$Result = $Ref['res'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->ReplByPos[$pos] = $idx;
|
||||||
|
|
||||||
|
return $Result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileCancelModif($NameOrIdx, $ReplacedAndDeleted=true) {
|
||||||
|
// cancel added, modified or deleted modifications on a file in the archive
|
||||||
|
// return the number of cancels
|
||||||
|
|
||||||
|
$nbr = 0;
|
||||||
|
|
||||||
|
if ($ReplacedAndDeleted) {
|
||||||
|
// replaced or deleted files
|
||||||
|
$idx = $this->FileGetIdx($NameOrIdx);
|
||||||
|
if ($idx!==false) {
|
||||||
|
if (isset($this->ReplInfo[$idx])) {
|
||||||
|
$pos = $this->CdFileLst[$idx]['p_loc'];
|
||||||
|
unset($this->ReplByPos[$pos]);
|
||||||
|
unset($this->ReplInfo[$idx]);
|
||||||
|
$nbr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// added files
|
||||||
|
$idx = $this->FileGetIdxAdd($NameOrIdx);
|
||||||
|
if ($idx!==false) {
|
||||||
|
unset($this->AddInfo[$idx]);
|
||||||
|
$nbr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $nbr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function Flush($Render=TBSZIP_DOWNLOAD, $File='', $ContentType='') {
|
||||||
|
|
||||||
|
if ( ($File!=='') && ($this->ArchFile===$File)) {
|
||||||
|
$this->RaiseError('Method Flush() cannot overwrite the current opened archive: \''.$File.'\''); // this makes corrupted zip archives without PHP error.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ArchPos = 0;
|
||||||
|
$Delta = 0;
|
||||||
|
$FicNewPos = array();
|
||||||
|
$DelLst = array(); // idx of deleted files
|
||||||
|
$DeltaCdLen = 0; // delta of the CD's size
|
||||||
|
|
||||||
|
$now = time();
|
||||||
|
$date = $this->_MsDos_Date($now);
|
||||||
|
$time = $this->_MsDos_Time($now);
|
||||||
|
|
||||||
|
if (!$this->OutputOpen($Render, $File, $ContentType)) return false;
|
||||||
|
|
||||||
|
// output modified zipped files and unmodified zipped files that are beetween them
|
||||||
|
ksort($this->ReplByPos);
|
||||||
|
foreach ($this->ReplByPos as $ReplPos => $ReplIdx) {
|
||||||
|
// output data from the zip archive which is before the data to replace
|
||||||
|
$this->OutputFromArch($ArchPos, $ReplPos);
|
||||||
|
// get current file information
|
||||||
|
if (!isset($this->VisFileLst[$ReplIdx])) $this->_ReadFile($ReplIdx, false);
|
||||||
|
$FileInfo =& $this->VisFileLst[$ReplIdx];
|
||||||
|
$b1 = $FileInfo['bin'];
|
||||||
|
if (isset($FileInfo['desc_bin'])) {
|
||||||
|
$b2 = $FileInfo['desc_bin'];
|
||||||
|
} else {
|
||||||
|
$b2 = '';
|
||||||
|
}
|
||||||
|
$info_old_len = strlen($b1) + $this->CdFileLst[$ReplIdx]['l_data_c'] + strlen($b2); // $FileInfo['l_data_c'] may have a 0 value in some archives
|
||||||
|
// get replacement information
|
||||||
|
$ReplInfo =& $this->ReplInfo[$ReplIdx];
|
||||||
|
if ($ReplInfo===false) {
|
||||||
|
// The file is to be deleted
|
||||||
|
$Delta = $Delta - $info_old_len; // headers and footers are also deleted
|
||||||
|
$DelLst[$ReplIdx] = true;
|
||||||
|
} else {
|
||||||
|
// prepare the header of the current file
|
||||||
|
$this->_DataPrepare($ReplInfo); // get data from external file if necessary
|
||||||
|
$this->_PutDec($b1, $time, 10, 2); // time
|
||||||
|
$this->_PutDec($b1, $date, 12, 2); // date
|
||||||
|
$this->_PutDec($b1, $ReplInfo['crc32'], 14, 4); // crc32
|
||||||
|
$this->_PutDec($b1, $ReplInfo['len_c'], 18, 4); // l_data_c
|
||||||
|
$this->_PutDec($b1, $ReplInfo['len_u'], 22, 4); // l_data_u
|
||||||
|
if ($ReplInfo['meth']!==false) $this->_PutDec($b1, $ReplInfo['meth'], 8, 2); // meth
|
||||||
|
// prepare the bottom description if the zipped file, if any
|
||||||
|
if ($b2!=='') {
|
||||||
|
$this->_PutDec($b2, $ReplInfo['crc32'], 4, 4); // crc32
|
||||||
|
$this->_PutDec($b2, $ReplInfo['len_c'], 8, 4); // l_data_c
|
||||||
|
$this->_PutDec($b2, $ReplInfo['len_u'], 12, 4); // l_data_u
|
||||||
|
}
|
||||||
|
// output data
|
||||||
|
$this->OutputFromString($b1.$ReplInfo['data'].$b2);
|
||||||
|
unset($ReplInfo['data']); // save PHP memory
|
||||||
|
$Delta = $Delta + $ReplInfo['diff'] + $ReplInfo['len_c'];
|
||||||
|
}
|
||||||
|
// Update the delta of positions for zipped files which are physically after the currently replaced one
|
||||||
|
for ($i=0;$i<$this->CdFileNbr;$i++) {
|
||||||
|
if ($this->CdFileLst[$i]['p_loc']>$ReplPos) {
|
||||||
|
$FicNewPos[$i] = $this->CdFileLst[$i]['p_loc'] + $Delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update the current pos in the archive
|
||||||
|
$ArchPos = $ReplPos + $info_old_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ouput all the zipped files that remain before the Central Directory listing
|
||||||
|
if ($this->ArchHnd!==false) $this->OutputFromArch($ArchPos, $this->CdPos); // ArchHnd is false if CreateNew() has been called
|
||||||
|
$ArchPos = $this->CdPos;
|
||||||
|
|
||||||
|
// Output file to add
|
||||||
|
$AddNbr = count($this->AddInfo);
|
||||||
|
$AddDataLen = 0; // total len of added data (inlcuding file headers)
|
||||||
|
if ($AddNbr>0) {
|
||||||
|
$AddPos = $ArchPos + $Delta; // position of the start
|
||||||
|
$AddLst = array_keys($this->AddInfo);
|
||||||
|
foreach ($AddLst as $idx) {
|
||||||
|
$n = $this->_DataOuputAddedFile($idx, $AddPos);
|
||||||
|
$AddPos += $n;
|
||||||
|
$AddDataLen += $n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modifiy file information in the Central Directory for replaced files
|
||||||
|
$b2 = '';
|
||||||
|
$old_cd_len = 0;
|
||||||
|
for ($i=0;$i<$this->CdFileNbr;$i++) {
|
||||||
|
$b1 = $this->CdFileLst[$i]['bin'];
|
||||||
|
$old_cd_len += strlen($b1);
|
||||||
|
if (!isset($DelLst[$i])) {
|
||||||
|
if (isset($FicNewPos[$i])) $this->_PutDec($b1, $FicNewPos[$i], 42, 4); // p_loc
|
||||||
|
if (isset($this->ReplInfo[$i])) {
|
||||||
|
$ReplInfo =& $this->ReplInfo[$i];
|
||||||
|
$this->_PutDec($b1, $time, 12, 2); // time
|
||||||
|
$this->_PutDec($b1, $date, 14, 2); // date
|
||||||
|
$this->_PutDec($b1, $ReplInfo['crc32'], 16, 4); // crc32
|
||||||
|
$this->_PutDec($b1, $ReplInfo['len_c'], 20, 4); // l_data_c
|
||||||
|
$this->_PutDec($b1, $ReplInfo['len_u'], 24, 4); // l_data_u
|
||||||
|
if ($ReplInfo['meth']!==false) $this->_PutDec($b1, $ReplInfo['meth'], 10, 2); // meth
|
||||||
|
}
|
||||||
|
$b2 .= $b1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->OutputFromString($b2);
|
||||||
|
$ArchPos += $old_cd_len;
|
||||||
|
$DeltaCdLen = $DeltaCdLen + strlen($b2) - $old_cd_len;
|
||||||
|
|
||||||
|
// Output until Central Directory footer
|
||||||
|
if ($this->ArchHnd!==false) $this->OutputFromArch($ArchPos, $this->CdEndPos); // ArchHnd is false if CreateNew() has been called
|
||||||
|
|
||||||
|
// Output file information of the Central Directory for added files
|
||||||
|
if ($AddNbr>0) {
|
||||||
|
$b2 = '';
|
||||||
|
foreach ($AddLst as $idx) {
|
||||||
|
$b2 .= $this->AddInfo[$idx]['bin'];
|
||||||
|
}
|
||||||
|
$this->OutputFromString($b2);
|
||||||
|
$DeltaCdLen += strlen($b2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output Central Directory footer
|
||||||
|
$b2 = $this->CdInfo['bin'];
|
||||||
|
$DelNbr = count($DelLst);
|
||||||
|
if ( ($AddNbr>0) or ($DelNbr>0) ) {
|
||||||
|
// total number of entries in the central directory on this disk
|
||||||
|
$n = $this->_GetDec($b2, 8, 2);
|
||||||
|
$this->_PutDec($b2, $n + $AddNbr - $DelNbr, 8, 2);
|
||||||
|
// total number of entries in the central directory
|
||||||
|
$n = $this->_GetDec($b2, 10, 2);
|
||||||
|
$this->_PutDec($b2, $n + $AddNbr - $DelNbr, 10, 2);
|
||||||
|
// size of the central directory
|
||||||
|
$n = $this->_GetDec($b2, 12, 4);
|
||||||
|
$this->_PutDec($b2, $n + $DeltaCdLen, 12, 4);
|
||||||
|
$Delta = $Delta + $AddDataLen;
|
||||||
|
}
|
||||||
|
$this->_PutDec($b2, $this->CdPos+$Delta , 16, 4); // p_cd (offset of start of central directory with respect to the starting disk number)
|
||||||
|
$this->OutputFromString($b2);
|
||||||
|
|
||||||
|
$this->OutputClose();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
// output functions
|
||||||
|
// ----------------
|
||||||
|
|
||||||
|
function OutputOpen($Render, $File, $ContentType) {
|
||||||
|
|
||||||
|
if (($Render & TBSZIP_FILE)==TBSZIP_FILE) {
|
||||||
|
$this->OutputMode = TBSZIP_FILE;
|
||||||
|
if (''.$File=='') $File = basename($this->ArchFile).'.zip';
|
||||||
|
$this->OutputHandle = @fopen($File, 'w');
|
||||||
|
if ($this->OutputHandle===false) {
|
||||||
|
$this->RaiseError('Method Flush() cannot overwrite the target file \''.$File.'\'. This may not be a valid file path or the file may be locked by another process or because of a denied permission.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} elseif (($Render & TBSZIP_STRING)==TBSZIP_STRING) {
|
||||||
|
$this->OutputMode = TBSZIP_STRING;
|
||||||
|
$this->OutputSrc = '';
|
||||||
|
} elseif (($Render & TBSZIP_DOWNLOAD)==TBSZIP_DOWNLOAD) {
|
||||||
|
$this->OutputMode = TBSZIP_DOWNLOAD;
|
||||||
|
// Output the file
|
||||||
|
if (''.$File=='') $File = basename($this->ArchFile);
|
||||||
|
if (($Render & TBSZIP_NOHEADER)==TBSZIP_NOHEADER) {
|
||||||
|
} else {
|
||||||
|
header ('Pragma: no-cache');
|
||||||
|
if ($ContentType!='') header ('Content-Type: '.$ContentType);
|
||||||
|
header('Content-Disposition: attachment; filename="'.$File.'"');
|
||||||
|
header('Expires: 0');
|
||||||
|
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
|
||||||
|
header('Cache-Control: public');
|
||||||
|
header('Content-Description: File Transfer');
|
||||||
|
header('Content-Transfer-Encoding: binary');
|
||||||
|
$Len = $this->_EstimateNewArchSize();
|
||||||
|
if ($Len!==false) header('Content-Length: '.$Len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function OutputFromArch($pos, $pos_stop) {
|
||||||
|
$len = $pos_stop - $pos;
|
||||||
|
if ($len<0) return;
|
||||||
|
$this->_MoveTo($pos);
|
||||||
|
$block = 1024;
|
||||||
|
while ($len>0) {
|
||||||
|
$l = min($len, $block);
|
||||||
|
$x = $this->_ReadData($l);
|
||||||
|
$this->OutputFromString($x);
|
||||||
|
$len = $len - $l;
|
||||||
|
}
|
||||||
|
unset($x);
|
||||||
|
}
|
||||||
|
|
||||||
|
function OutputFromString($data) {
|
||||||
|
if ($this->OutputMode===TBSZIP_DOWNLOAD) {
|
||||||
|
echo $data; // donwload
|
||||||
|
} elseif ($this->OutputMode===TBSZIP_STRING) {
|
||||||
|
$this->OutputSrc .= $data; // to string
|
||||||
|
} elseif (TBSZIP_FILE) {
|
||||||
|
fwrite($this->OutputHandle, $data); // to file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function OutputClose() {
|
||||||
|
if ( ($this->OutputMode===TBSZIP_FILE) && ($this->OutputHandle!==false) ) {
|
||||||
|
fclose($this->OutputHandle);
|
||||||
|
$this->OutputHandle = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
// Reading functions
|
||||||
|
// ----------------
|
||||||
|
|
||||||
|
function _MoveTo($pos, $relative = SEEK_SET) {
|
||||||
|
fseek($this->ArchHnd, $pos, $relative);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _ReadData($len) {
|
||||||
|
if ($len>0) {
|
||||||
|
$x = fread($this->ArchHnd, $len);
|
||||||
|
return $x;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
// Take info from binary data
|
||||||
|
// ----------------
|
||||||
|
|
||||||
|
function _GetDec($txt, $pos, $len) {
|
||||||
|
$x = substr($txt, $pos, $len);
|
||||||
|
$z = 0;
|
||||||
|
for ($i=0;$i<$len;$i++) {
|
||||||
|
$asc = ord($x[$i]);
|
||||||
|
if ($asc>0) $z = $z + $asc*pow(256,$i);
|
||||||
|
}
|
||||||
|
return $z;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _GetHex($txt, $pos, $len) {
|
||||||
|
$x = substr($txt, $pos, $len);
|
||||||
|
return 'h:'.bin2hex(strrev($x));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _GetBin($txt, $pos, $len) {
|
||||||
|
$x = substr($txt, $pos, $len);
|
||||||
|
$z = '';
|
||||||
|
for ($i=0;$i<$len;$i++) {
|
||||||
|
$asc = ord($x[$i]);
|
||||||
|
if (isset($x[$i])) {
|
||||||
|
for ($j=0;$j<8;$j++) {
|
||||||
|
$z .= ($asc & pow(2,$j)) ? '1' : '0';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$z .= '00000000';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'b:'.$z;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
// Put info into binary data
|
||||||
|
// ----------------
|
||||||
|
|
||||||
|
function _PutDec(&$txt, $val, $pos, $len) {
|
||||||
|
$x = '';
|
||||||
|
for ($i=0;$i<$len;$i++) {
|
||||||
|
if ($val==0) {
|
||||||
|
$z = 0;
|
||||||
|
} else {
|
||||||
|
$z = intval($val % 256);
|
||||||
|
if (($val<0) && ($z!=0)) { // ($z!=0) is very important, example: val=-420085702
|
||||||
|
// special opration for negative value. If the number id too big, PHP stores it into a signed integer. For example: crc32('coucou') => -256185401 instead of 4038781895. NegVal = BigVal - (MaxVal+1) = BigVal - 256^4
|
||||||
|
$val = ($val - $z)/256 -1;
|
||||||
|
$z = 256 + $z;
|
||||||
|
} else {
|
||||||
|
$val = ($val - $z)/256;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$x .= chr($z);
|
||||||
|
}
|
||||||
|
$txt = substr_replace($txt, $x, $pos, $len);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _MsDos_Date($Timestamp = false) {
|
||||||
|
// convert a date-time timstamp into the MS-Dos format
|
||||||
|
$d = ($Timestamp===false) ? getdate() : getdate($Timestamp);
|
||||||
|
return (($d['year']-1980)*512) + ($d['mon']*32) + $d['mday'];
|
||||||
|
}
|
||||||
|
function _MsDos_Time($Timestamp = false) {
|
||||||
|
// convert a date-time timstamp into the MS-Dos format
|
||||||
|
$d = ($Timestamp===false) ? getdate() : getdate($Timestamp);
|
||||||
|
return ($d['hours']*2048) + ($d['minutes']*32) + intval($d['seconds']/2); // seconds are rounded to an even number in order to save 1 bit
|
||||||
|
}
|
||||||
|
|
||||||
|
function _MsDos_Debug($date, $time) {
|
||||||
|
// Display the formated date and time. Just for debug purpose.
|
||||||
|
// date end time are encoded on 16 bits (2 bytes) : date = yyyyyyymmmmddddd , time = hhhhhnnnnnssssss
|
||||||
|
$y = ($date & 65024)/512 + 1980;
|
||||||
|
$m = ($date & 480)/32;
|
||||||
|
$d = ($date & 31);
|
||||||
|
$h = ($time & 63488)/2048;
|
||||||
|
$i = ($time & 1984)/32;
|
||||||
|
$s = ($time & 31) * 2; // seconds have been rounded to an even number in order to save 1 bit
|
||||||
|
return $y.'-'.str_pad($m,2,'0',STR_PAD_LEFT).'-'.str_pad($d,2,'0',STR_PAD_LEFT).' '.str_pad($h,2,'0',STR_PAD_LEFT).':'.str_pad($i,2,'0',STR_PAD_LEFT).':'.str_pad($s,2,'0',STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _DataOuputAddedFile($Idx, $PosLoc) {
|
||||||
|
|
||||||
|
$Ref =& $this->AddInfo[$Idx];
|
||||||
|
$this->_DataPrepare($Ref); // get data from external file if necessary
|
||||||
|
|
||||||
|
// Other info
|
||||||
|
$now = time();
|
||||||
|
$date = $this->_MsDos_Date($now);
|
||||||
|
$time = $this->_MsDos_Time($now);
|
||||||
|
$len_n = strlen($Ref['name']);
|
||||||
|
$purp = 2048 ; // purpose // +8 to indicates that there is an extended local header
|
||||||
|
|
||||||
|
// Header for file in the data section
|
||||||
|
$b = 'PK'.chr(03).chr(04).str_repeat(' ',26); // signature
|
||||||
|
$this->_PutDec($b,20,4,2); //vers = 20
|
||||||
|
$this->_PutDec($b,$purp,6,2); // purp
|
||||||
|
$this->_PutDec($b,$Ref['meth'],8,2); // meth
|
||||||
|
$this->_PutDec($b,$time,10,2); // time
|
||||||
|
$this->_PutDec($b,$date,12,2); // date
|
||||||
|
$this->_PutDec($b,$Ref['crc32'],14,4); // crc32
|
||||||
|
$this->_PutDec($b,$Ref['len_c'],18,4); // l_data_c
|
||||||
|
$this->_PutDec($b,$Ref['len_u'],22,4); // l_data_u
|
||||||
|
$this->_PutDec($b,$len_n,26,2); // l_name
|
||||||
|
$this->_PutDec($b,0,28,2); // l_fields
|
||||||
|
$b .= $Ref['name']; // name
|
||||||
|
$b .= ''; // fields
|
||||||
|
|
||||||
|
// Output the data
|
||||||
|
$this->OutputFromString($b.$Ref['data']);
|
||||||
|
$OutputLen = strlen($b) + $Ref['len_c']; // new position of the cursor
|
||||||
|
unset($Ref['data']); // save PHP memory
|
||||||
|
|
||||||
|
// Information for file in the Central Directory
|
||||||
|
$b = 'PK'.chr(01).chr(02).str_repeat(' ',42); // signature
|
||||||
|
$this->_PutDec($b,20,4,2); // vers_used = 20
|
||||||
|
$this->_PutDec($b,20,6,2); // vers_necess = 20
|
||||||
|
$this->_PutDec($b,$purp,8,2); // purp
|
||||||
|
$this->_PutDec($b,$Ref['meth'],10,2); // meth
|
||||||
|
$this->_PutDec($b,$time,12,2); // time
|
||||||
|
$this->_PutDec($b,$date,14,2); // date
|
||||||
|
$this->_PutDec($b,$Ref['crc32'],16,4); // crc32
|
||||||
|
$this->_PutDec($b,$Ref['len_c'],20,4); // l_data_c
|
||||||
|
$this->_PutDec($b,$Ref['len_u'],24,4); // l_data_u
|
||||||
|
$this->_PutDec($b,$len_n,28,2); // l_name
|
||||||
|
$this->_PutDec($b,0,30,2); // l_fields
|
||||||
|
$this->_PutDec($b,0,32,2); // l_comm
|
||||||
|
$this->_PutDec($b,0,34,2); // disk_num
|
||||||
|
$this->_PutDec($b,0,36,2); // int_file_att
|
||||||
|
$this->_PutDec($b,0,38,4); // ext_file_att
|
||||||
|
$this->_PutDec($b,$PosLoc,42,4); // p_loc
|
||||||
|
$b .= $Ref['name']; // v_name
|
||||||
|
$b .= ''; // v_fields
|
||||||
|
$b .= ''; // v_comm
|
||||||
|
|
||||||
|
$Ref['bin'] = $b;
|
||||||
|
|
||||||
|
return $OutputLen;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function _DataCreateNewRef($Data, $DataType, $Compress, $Diff, $NameOrIdx) {
|
||||||
|
|
||||||
|
if (is_array($Compress)) {
|
||||||
|
$result = 2;
|
||||||
|
$meth = $Compress['meth'];
|
||||||
|
$len_u = $Compress['len_u'];
|
||||||
|
$crc32 = $Compress['crc32'];
|
||||||
|
$Compress = false;
|
||||||
|
} elseif ($Compress and ($this->Meth8Ok)) {
|
||||||
|
$result = 1;
|
||||||
|
$meth = 8;
|
||||||
|
$len_u = false; // means unknown
|
||||||
|
$crc32 = false;
|
||||||
|
} else {
|
||||||
|
$result = ($Compress) ? -1 : 0;
|
||||||
|
$meth = 0;
|
||||||
|
$len_u = false;
|
||||||
|
$crc32 = false;
|
||||||
|
$Compress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($DataType==TBSZIP_STRING) {
|
||||||
|
$path = false;
|
||||||
|
if ($Compress) {
|
||||||
|
// we compress now in order to save PHP memory
|
||||||
|
$len_u = strlen($Data);
|
||||||
|
$crc32 = crc32($Data);
|
||||||
|
$Data = gzdeflate($Data);
|
||||||
|
$len_c = strlen($Data);
|
||||||
|
} else {
|
||||||
|
$len_c = strlen($Data);
|
||||||
|
if ($len_u===false) {
|
||||||
|
$len_u = $len_c;
|
||||||
|
$crc32 = crc32($Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$path = $Data;
|
||||||
|
$Data = false;
|
||||||
|
if (file_exists($path)) {
|
||||||
|
$fz = filesize($path);
|
||||||
|
if ($len_u===false) $len_u = $fz;
|
||||||
|
$len_c = ($Compress) ? false : $fz;
|
||||||
|
} else {
|
||||||
|
return $this->RaiseError("Cannot add the file '".$path."' because it is not found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// at this step $Data and $crc32 can be false only in case of external file, and $len_c is false only in case of external file to compress
|
||||||
|
return array('data'=>$Data, 'path'=>$path, 'meth'=>$meth, 'len_u'=>$len_u, 'len_c'=>$len_c, 'crc32'=>$crc32, 'diff'=>$Diff, 'res'=>$result);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function _DataPrepare(&$Ref) {
|
||||||
|
// returns the real size of data
|
||||||
|
if ($Ref['path']!==false) {
|
||||||
|
$Ref['data'] = file_get_contents($Ref['path']);
|
||||||
|
if ($Ref['crc32']===false) $Ref['crc32'] = crc32($Ref['data']);
|
||||||
|
if ($Ref['len_c']===false) {
|
||||||
|
// means the data must be compressed
|
||||||
|
$Ref['data'] = gzdeflate($Ref['data']);
|
||||||
|
$Ref['len_c'] = strlen($Ref['data']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _EstimateNewArchSize($Optim=true) {
|
||||||
|
// Return the size of the new archive, or false if it cannot be calculated (because of external file that must be compressed before to be insered)
|
||||||
|
|
||||||
|
if ($this->ArchIsNew) {
|
||||||
|
$Len = strlen($this->CdInfo['bin']);
|
||||||
|
} else {
|
||||||
|
$Len = filesize($this->ArchFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// files to replace or delete
|
||||||
|
foreach ($this->ReplByPos as $i) {
|
||||||
|
$Ref =& $this->ReplInfo[$i];
|
||||||
|
if ($Ref===false) {
|
||||||
|
// file to delete
|
||||||
|
$Info =& $this->CdFileLst[$i];
|
||||||
|
if (!isset($this->VisFileLst[$i])) {
|
||||||
|
if ($Optim) return false; // if $Optimization is set to true, then we d'ont rewind to read information
|
||||||
|
$this->_MoveTo($Info['p_loc']);
|
||||||
|
$this->_ReadFile($i, false);
|
||||||
|
}
|
||||||
|
$Vis =& $this->VisFileLst[$i];
|
||||||
|
$Len += -strlen($Vis['bin']) -strlen($Info['bin']) - $Info['l_data_c'];
|
||||||
|
if (isset($Vis['desc_bin'])) $Len += -strlen($Vis['desc_bin']);
|
||||||
|
} elseif ($Ref['len_c']===false) {
|
||||||
|
return false; // information not yet known
|
||||||
|
} else {
|
||||||
|
// file to replace
|
||||||
|
$Len += $Ref['len_c'] + $Ref['diff'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// files to add
|
||||||
|
$i_lst = array_keys($this->AddInfo);
|
||||||
|
foreach ($i_lst as $i) {
|
||||||
|
$Ref =& $this->AddInfo[$i];
|
||||||
|
if ($Ref['len_c']===false) {
|
||||||
|
return false; // information not yet known
|
||||||
|
} else {
|
||||||
|
$Len += $Ref['len_c'] + $Ref['diff'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $Len;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue