Browse Source

Allow metadata updating for epub. Disabled by default for now.

fix #18
master
Sébastien Lucas 11 years ago
parent
commit
60df6f1c18
9 changed files with 1560 additions and 2 deletions
  1. +1
    -0
      CHANGELOG
  2. +4
    -1
      README
  3. +31
    -0
      book.php
  4. +7
    -0
      config_default.php
  5. +1
    -1
      data.php
  6. +6
    -0
      fetch.php
  7. +19
    -0
      php-epub-meta/LICENSE
  8. +608
    -0
      php-epub-meta/epub.php
  9. +883
    -0
      php-epub-meta/tbszip.php

+ 1
- 0
CHANGELOG View File

@@ -5,6 +5,7 @@
* Better handling of content type for book. Reported by Morg.
* Upped the size of thumbnails for OPDS. They look way better with Mantano.
* 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
* Add a .htaccess to make it easier to use with Apache


+ 4
- 1
README View File

@@ -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/
* 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 testers
* Thanks to all testers
External libraries used :
* JQuery : http://jquery.com/
* 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 =



+ 31
- 0
book.php View File

@@ -11,6 +11,7 @@ require_once('serie.php');
require_once('author.php');
require_once('tag.php');
require_once('data.php');
require_once('php-epub-meta/epub.php');

// Silly thing because PHP forbid string concatenation in class const
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 ()
{
global $config;


+ 7
- 0
config_default.php View File

@@ -127,4 +127,11 @@
* 0 : No
*/
$config['cops_use_fancyapps'] = "1";
/*
* Update Epub metadata before download
* 1 : Yes (enable)
* 0 : No
*/
$config['cops_update_epub-metadata'] = "1";
?>

+ 1
- 1
data.php View File

@@ -100,7 +100,7 @@ class Data extends Base {
$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;
return new Link ("fetch.php?id=$book->id" . $textData, $mime, $rel, $title);


+ 6
- 0
fetch.php View File

@@ -87,6 +87,12 @@
break;
}
$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 ($config['cops_x_accel_redirect'] . ": " . $config['calibre_internal_directory'] . $file);
?>

+ 19
- 0
php-epub-meta/LICENSE View 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
- 0
php-epub-meta/epub.php View 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
- 0
php-epub-meta/tbszip.php View 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…
Cancel
Save