1) Исправления в связи со сменой API MySQL
[openlib.git] / www / resources / php-epub-meta / tbszip.php
1 <?php
2
3 /*
4 TbsZip version 2.12
5 Date    : 2013-03-16
6 Author  : Skrol29 (email: http://www.tinybutstrong.com/onlyyou.html)
7 Licence : LGPL
8 This class is independent from any other classes and has been originally created for the OpenTbs plug-in
9 for TinyButStrong Template Engine (TBS). OpenTbs makes TBS able to merge OpenOffice and Ms Office documents.
10 Visit http://www.tinybutstrong.com
11 */
12
13 define('TBSZIP_DOWNLOAD',1);   // download (default)
14 define('TBSZIP_NOHEADER',4);   // option to use with DOWNLOAD: no header is sent
15 define('TBSZIP_FILE',8);       // output to file  , or add from file
16 define('TBSZIP_STRING',32);    // output to string, or add from string
17
18 class clsTbsZip {
19
20         function __construct() {
21                 $this->Meth8Ok = extension_loaded('zlib'); // check if Zlib extension is available. This is need for compress and uncompress with method 8.
22                 $this->DisplayError = true;
23                 $this->ArchFile = '';
24                 $this->Error = false;
25         }
26
27         function CreateNew($ArchName='new.zip') {
28         // Create a new virtual empty archive, the name will be the default name when the archive is flushed.
29                 if (!isset($this->Meth8Ok)) $this->__construct();  // for PHP 4 compatibility
30                 $this->Close(); // note that $this->ArchHnd is set to false here
31                 $this->Error = false;
32                 $this->ArchFile = $ArchName;
33                 $this->ArchIsNew = true;
34                 $bin = 'PK'.chr(05).chr(06).str_repeat(chr(0), 18);
35                 $this->CdEndPos = strlen($bin) - 4;
36                 $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);
37                 $this->CdPos = $this->CdInfo['p_cd'];
38         }
39
40         function Open($ArchFile, $UseIncludePath=false) {
41         // Open the zip archive
42                 if (!isset($this->Meth8Ok)) $this->__construct();  // for PHP 4 compatibility
43                 $this->Close(); // close handle and init info
44                 $this->Error = false;
45                 $this->ArchFile = $ArchFile;
46                 $this->ArchIsNew = false;
47                 // open the file
48                 $this->ArchHnd = fopen($ArchFile, 'rb', $UseIncludePath);
49                 $ok = !($this->ArchHnd===false);
50                 if ($ok) $ok = $this->CentralDirRead();
51                 return $ok;
52         }
53
54         function Close() {
55                 if (isset($this->ArchHnd) and ($this->ArchHnd!==false)) fclose($this->ArchHnd);
56                 $this->ArchFile = '';
57                 $this->ArchHnd = false;
58                 $this->CdInfo = array();
59                 $this->CdFileLst = array();
60                 $this->CdFileNbr = 0;
61                 $this->CdFileByName = array();
62                 $this->VisFileLst = array();
63                 $this->ArchCancelModif();
64         }
65
66         function ArchCancelModif() {
67                 $this->LastReadComp = false; // compression of the last read file (1=compressed, 0=stored not compressed, -1= stored compressed but read uncompressed)
68                 $this->LastReadIdx = false;  // index of the last file read
69                 $this->ReplInfo = array();
70                 $this->ReplByPos = array();
71                 $this->AddInfo = array();
72         }
73
74         function FileAdd($Name, $Data, $DataType=TBSZIP_STRING, $Compress=true) {
75
76                 if ($Data===false) return $this->FileCancelModif($Name, false); // Cancel a previously added file
77
78                 // Save information for adding a new file into the archive
79                 $Diff = 30 + 46 + 2*strlen($Name); // size of the header + cd info
80                 $Ref = $this->_DataCreateNewRef($Data, $DataType, $Compress, $Diff, $Name);
81                 if ($Ref===false) return false;
82                 $Ref['name'] = $Name;
83                 $this->AddInfo[] = $Ref;
84                 return $Ref['res'];
85
86         }
87
88         function CentralDirRead() {
89                 $cd_info = 'PK'.chr(05).chr(06); // signature of the Central Directory
90                 $cd_pos = -22;
91                 $this->_MoveTo($cd_pos, SEEK_END);
92                 $b = $this->_ReadData(4);
93                 if ($b!==$cd_info) return $this->RaiseError('The End of Central Rirectory Record is not found.');
94
95                 $this->CdEndPos = ftell($this->ArchHnd) - 4;
96                 $this->CdInfo = $this->CentralDirRead_End($cd_info);
97                 $this->CdFileLst = array();
98                 $this->CdFileNbr = $this->CdInfo['file_nbr_curr'];
99                 $this->CdPos = $this->CdInfo['p_cd'];
100
101                 if ($this->CdFileNbr<=0) return $this->RaiseError('No header found in the Central Directory.');
102                 if ($this->CdPos<=0) return $this->RaiseError('No position found for the Central Directory.');
103
104                 $this->_MoveTo($this->CdPos);
105                 for ($i=0;$i<$this->CdFileNbr;$i++) {
106                         $x = $this->CentralDirRead_File($i);
107                         if ($x!==false) {
108                                 $this->CdFileLst[$i] = $x;
109                                 $this->CdFileByName[$x['v_name']] = $i;
110                         }
111                 }
112                 return true;
113         }
114
115         function CentralDirRead_End($cd_info) {
116                 $b = $cd_info.$this->_ReadData(18);
117                 $x = array();
118                 $x['disk_num_curr'] = $this->_GetDec($b,4,2);  // number of this disk
119                 $x['disk_num_cd'] = $this->_GetDec($b,6,2);    // number of the disk with the start of the central directory
120                 $x['file_nbr_curr'] = $this->_GetDec($b,8,2);  // total number of entries in the central directory on this disk
121                 $x['file_nbr_tot'] = $this->_GetDec($b,10,2);  // total number of entries in the central directory
122                 $x['l_cd'] = $this->_GetDec($b,12,4);          // size of the central directory
123                 $x['p_cd'] = $this->_GetDec($b,16,4);          // position of start of central directory with respect to the starting disk number
124                 $x['l_comm'] = $this->_GetDec($b,20,2);        // .ZIP file comment length
125                 $x['v_comm'] = $this->_ReadData($x['l_comm']); // .ZIP file comment
126                 $x['bin'] = $b.$x['v_comm'];
127                 return $x;
128         }
129
130         function CentralDirRead_File($idx) {
131
132                 $b = $this->_ReadData(46);
133
134                 $x = $this->_GetHex($b,0,4);
135                 if ($x!=='h:02014b50') return $this->RaiseError("Signature of Central Directory Header #".$idx." (file information) expected but not found at position ".$this->_TxtPos(ftell($this->ArchHnd) - 46).".");
136
137                 $x = array();
138                 $x['vers_used'] = $this->_GetDec($b,4,2);
139                 $x['vers_necess'] = $this->_GetDec($b,6,2);
140                 $x['purp'] = $this->_GetBin($b,8,2);
141                 $x['meth'] = $this->_GetDec($b,10,2);
142                 $x['time'] = $this->_GetDec($b,12,2);
143                 $x['date'] = $this->_GetDec($b,14,2);
144                 $x['crc32'] = $this->_GetDec($b,16,4);
145                 $x['l_data_c'] = $this->_GetDec($b,20,4);
146                 $x['l_data_u'] = $this->_GetDec($b,24,4);
147                 $x['l_name'] = $this->_GetDec($b,28,2);
148                 $x['l_fields'] = $this->_GetDec($b,30,2);
149                 $x['l_comm'] = $this->_GetDec($b,32,2);
150                 $x['disk_num'] = $this->_GetDec($b,34,2);
151                 $x['int_file_att'] = $this->_GetDec($b,36,2);
152                 $x['ext_file_att'] = $this->_GetDec($b,38,4);
153                 $x['p_loc'] = $this->_GetDec($b,42,4);
154                 $x['v_name'] = $this->_ReadData($x['l_name']);
155                 $x['v_fields'] = $this->_ReadData($x['l_fields']);
156                 $x['v_comm'] = $this->_ReadData($x['l_comm']);
157
158                 $x['bin'] = $b.$x['v_name'].$x['v_fields'].$x['v_comm'];
159
160                 return $x;
161         }
162
163         function RaiseError($Msg) {
164                 if ($this->DisplayError) echo '<strong>'.get_class($this).' ERROR :</strong> '.$Msg.'<br>'."\r\n";
165                 $this->Error = $Msg;
166                 return false;
167         }
168
169         function Debug($FileHeaders=false) {
170
171                 $this->DisplayError = true;
172
173                 if ($FileHeaders) {
174                         // Calculations first in order to have error messages before other information
175                         $idx = 0;
176                         $pos = 0;
177                         $pos_stop = $this->CdInfo['p_cd'];
178                         $this->_MoveTo($pos);
179                         while ( ($pos<$pos_stop) && ($ok = $this->_ReadFile($idx,false)) ) {
180                                 $this->VisFileLst[$idx]['p_this_header (debug_mode only)'] = $pos;
181                                 $pos = ftell($this->ArchHnd);
182                                 $idx++;
183                         }
184                 }
185                 
186                 $nl = "\r\n";
187                 echo "<pre>";
188                 
189                 echo "-------------------------------".$nl;
190                 echo "End of Central Directory record".$nl;
191                 echo "-------------------------------".$nl;
192                 print_r($this->DebugArray($this->CdInfo));
193
194                 echo $nl;
195                 echo "-------------------------".$nl;
196                 echo "Central Directory headers".$nl;
197                 echo "-------------------------".$nl;
198                 print_r($this->DebugArray($this->CdFileLst));
199
200                 if ($FileHeaders) {
201                         echo $nl;
202                         echo "------------------".$nl;
203                         echo "Local File headers".$nl;
204                         echo "------------------".$nl;
205                         print_r($this->DebugArray($this->VisFileLst));
206                 }
207
208                 echo "</pre>";
209                 
210         }
211
212         function DebugArray($arr) {
213                 foreach ($arr as $k=>$v) {
214                         if (is_array($v)) {
215                                 $arr[$k] = $this->DebugArray($v);
216                         } elseif (substr($k,0,2)=='p_') {
217                                 $arr[$k] = $this->_TxtPos($v);
218                         }
219                 }
220                 return $arr;
221         }
222         
223         function FileExists($NameOrIdx) {
224                 return ($this->FileGetIdx($NameOrIdx)!==false);
225         }
226
227         function FileGetIdx($NameOrIdx) {
228         // Check if a file name, or a file index exists in the Central Directory, and return its index
229                 if (is_string($NameOrIdx)) {
230                         if (isset($this->CdFileByName[$NameOrIdx])) {
231                                 return $this->CdFileByName[$NameOrIdx];
232                         } else {
233                                 return false;
234                         }
235                 } else {
236                         if (isset($this->CdFileLst[$NameOrIdx])) {
237                                 return $NameOrIdx;
238                         } else {
239                                 return false;
240                         }
241                 }
242         }
243
244         function FileGetIdxAdd($Name) {
245         // Check if a file name exists in the list of file to add, and return its index
246                 if (!is_string($Name)) return false;
247                 $idx_lst = array_keys($this->AddInfo);
248                 foreach ($idx_lst as $idx) {
249                         if ($this->AddInfo[$idx]['name']===$Name) return $idx;
250                 }
251                 return false;
252         }
253
254         function FileRead($NameOrIdx, $Uncompress=true) {
255
256                 $this->LastReadComp = false; // means the file is not found
257                 $this->LastReadIdx = false;
258
259                 $idx = $this->FileGetIdx($NameOrIdx);
260                 if ($idx===false) return $this->RaiseError('File "'.$NameOrIdx.'" is not found in the Central Directory.');
261
262                 $pos = $this->CdFileLst[$idx]['p_loc'];
263                 $this->_MoveTo($pos);
264
265                 $this->LastReadIdx = $idx; // Can be usefull to get the idx
266
267                 $Data = $this->_ReadFile($idx, true);
268
269                 // Manage uncompression
270                 $Comp = 1; // means the contents stays compressed
271                 $meth = $this->CdFileLst[$idx]['meth'];
272                 if ($meth==8) {
273                         if ($Uncompress) {
274                                 if ($this->Meth8Ok) {
275                                         $Data = gzinflate($Data);
276                                         $Comp = -1; // means uncompressed
277                                 } else {
278                                         $this->RaiseError('Unable to uncompress file "'.$NameOrIdx.'" because extension Zlib is not installed.');
279                                 }
280                         }
281                 } elseif($meth==0) {
282                         $Comp = 0; // means stored without compression
283                 } else {
284                         if ($Uncompress) $this->RaiseError('Unable to uncompress file "'.$NameOrIdx.'" because it is compressed with method '.$meth.'.');
285                 }
286                 $this->LastReadComp = $Comp;
287
288                 return $Data;
289
290         }
291
292         function _ReadFile($idx, $ReadData) {
293         // read the file header (and maybe the data ) in the archive, assuming the cursor in at a new file position
294
295                 $b = $this->_ReadData(30);
296                 
297                 $x = $this->_GetHex($b,0,4);
298                 if ($x!=='h:04034b50') return $this->RaiseError("Signature of Local File Header #".$idx." (data section) expected but not found at position ".$this->_TxtPos(ftell($this->ArchHnd)-30).".");
299
300                 $x = array();
301                 $x['vers'] = $this->_GetDec($b,4,2);
302                 $x['purp'] = $this->_GetBin($b,6,2);
303                 $x['meth'] = $this->_GetDec($b,8,2);
304                 $x['time'] = $this->_GetDec($b,10,2);
305                 $x['date'] = $this->_GetDec($b,12,2);
306                 $x['crc32'] = $this->_GetDec($b,14,4);
307                 $x['l_data_c'] = $this->_GetDec($b,18,4);
308                 $x['l_data_u'] = $this->_GetDec($b,22,4);
309                 $x['l_name'] = $this->_GetDec($b,26,2);
310                 $x['l_fields'] = $this->_GetDec($b,28,2);
311                 $x['v_name'] = $this->_ReadData($x['l_name']);
312                 $x['v_fields'] = $this->_ReadData($x['l_fields']);
313
314                 $x['bin'] = $b.$x['v_name'].$x['v_fields'];
315
316                 // Read Data
317                 if (isset($this->CdFileLst[$idx])) {
318                         $len_cd = $this->CdFileLst[$idx]['l_data_c'];
319                         if ($x['l_data_c']==0) {
320                                 // Sometimes, the size is not specified in the local information.
321                                 $len = $len_cd;
322                         } else {
323                                 $len = $x['l_data_c'];
324                                 if ($len!=$len_cd) {
325                                         //echo "TbsZip Warning: Local information for file #".$idx." says len=".$len.", while Central Directory says len=".$len_cd.".";
326                                 }
327                         }
328                 } else {
329                         $len = $x['l_data_c'];
330                         if ($len==0) $this->RaiseError("File Data #".$idx." cannt be read because no length is specified in the Local File Header and its Central Directory information has not been found.");
331                 }
332
333                 if ($ReadData) {
334                         $Data = $this->_ReadData($len);
335                 } else {
336                         $this->_MoveTo($len, SEEK_CUR);
337                 }
338                 
339                 // Description information
340                 $desc_ok = ($x['purp'][2+3]=='1');
341                 if ($desc_ok) {
342                         $b = $this->_ReadData(12);
343                         $s = $this->_GetHex($b,0,4);
344                         $d = 0;
345                         // the specification says the signature may or may not be present
346                         if ($s=='h:08074b50') {
347                                 $b .= $this->_ReadData(4); 
348                                 $d = 4;
349                                 $x['desc_bin'] = $b;
350                                 $x['desc_sign'] = $s;
351                         } else {
352                                 $x['desc_bin'] = $b;
353                         }
354                         $x['desc_crc32']    = $this->_GetDec($b,0+$d,4);
355                         $x['desc_l_data_c'] = $this->_GetDec($b,4+$d,4);
356                         $x['desc_l_data_u'] = $this->_GetDec($b,8+$d,4);
357                 }
358
359                 // Save file info without the data
360                 $this->VisFileLst[$idx] = $x;
361
362                 // Return the info
363                 if ($ReadData) {
364                         return $Data;
365                 } else {
366                         return true;
367                 }
368
369         }
370
371         function FileReplace($NameOrIdx, $Data, $DataType=TBSZIP_STRING, $Compress=true) {
372         // Store replacement information.
373
374                 $idx = $this->FileGetIdx($NameOrIdx);
375                 if ($idx===false) return $this->RaiseError('File "'.$NameOrIdx.'" is not found in the Central Directory.');
376
377                 $pos = $this->CdFileLst[$idx]['p_loc'];
378
379                 if ($Data===false) {
380                         // file to delete
381                         $this->ReplInfo[$idx] = false;
382                         $Result = true;
383                 } else {
384                         // file to replace
385                         $Diff = - $this->CdFileLst[$idx]['l_data_c'];
386                         $Ref = $this->_DataCreateNewRef($Data, $DataType, $Compress, $Diff, $NameOrIdx);
387                         if ($Ref===false) return false;
388                         $this->ReplInfo[$idx] = $Ref;
389                         $Result = $Ref['res'];
390                 }
391
392                 $this->ReplByPos[$pos] = $idx;
393
394                 return $Result;
395
396         }
397
398         function FileCancelModif($NameOrIdx, $ReplacedAndDeleted=true) {
399         // cancel added, modified or deleted modifications on a file in the archive
400         // return the number of cancels
401
402                 $nbr = 0;
403
404                 if ($ReplacedAndDeleted) {
405                         // replaced or deleted files
406                         $idx = $this->FileGetIdx($NameOrIdx);
407                         if ($idx!==false) {
408                                 if (isset($this->ReplInfo[$idx])) {
409                                         $pos = $this->CdFileLst[$idx]['p_loc'];
410                                         unset($this->ReplByPos[$pos]);
411                                         unset($this->ReplInfo[$idx]);
412                                         $nbr++;
413                                 }
414                         }
415                 }
416
417                 // added files
418                 $idx = $this->FileGetIdxAdd($NameOrIdx);
419                 if ($idx!==false) {
420                         unset($this->AddInfo[$idx]);
421                         $nbr++;
422                 }
423
424                 return $nbr;
425
426         }
427
428         function Flush($Render=TBSZIP_DOWNLOAD, $File='', $ContentType='') {
429
430                 if ( ($File!=='') && ($this->ArchFile===$File)) {
431                         $this->RaiseError('Method Flush() cannot overwrite the current opened archive: \''.$File.'\''); // this makes corrupted zip archives without PHP error.
432                         return false;
433                 }
434
435                 $ArchPos = 0;
436                 $Delta = 0;
437                 $FicNewPos = array();
438                 $DelLst = array(); // idx of deleted files
439                 $DeltaCdLen = 0; // delta of the CD's size
440
441                 $now = time();
442                 $date  = $this->_MsDos_Date($now);
443                 $time  = $this->_MsDos_Time($now);
444
445                 if (!$this->OutputOpen($Render, $File, $ContentType)) return false;
446
447                 // output modified zipped files and unmodified zipped files that are beetween them
448                 ksort($this->ReplByPos);
449                 foreach ($this->ReplByPos as $ReplPos => $ReplIdx) {
450                         // output data from the zip archive which is before the data to replace
451                         $this->OutputFromArch($ArchPos, $ReplPos);
452                         // get current file information
453                         if (!isset($this->VisFileLst[$ReplIdx])) $this->_ReadFile($ReplIdx, false);
454                         $FileInfo =& $this->VisFileLst[$ReplIdx];
455                         $b1 = $FileInfo['bin'];
456                         if (isset($FileInfo['desc_bin'])) {
457                                 $b2 = $FileInfo['desc_bin'];
458                         } else {
459                                 $b2 = '';
460                         }
461                         $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
462                         // get replacement information
463                         $ReplInfo =& $this->ReplInfo[$ReplIdx];
464                         if ($ReplInfo===false) {
465                                 // The file is to be deleted
466                                 $Delta = $Delta - $info_old_len; // headers and footers are also deleted
467                                 $DelLst[$ReplIdx] = true;
468                         } else {
469                                 // prepare the header of the current file
470                                 $this->_DataPrepare($ReplInfo); // get data from external file if necessary
471                                 $this->_PutDec($b1, $time, 10, 2); // time
472                                 $this->_PutDec($b1, $date, 12, 2); // date
473                                 $this->_PutDec($b1, $ReplInfo['crc32'], 14, 4); // crc32
474                                 $this->_PutDec($b1, $ReplInfo['len_c'], 18, 4); // l_data_c
475                                 $this->_PutDec($b1, $ReplInfo['len_u'], 22, 4); // l_data_u
476                                 if ($ReplInfo['meth']!==false) $this->_PutDec($b1, $ReplInfo['meth'], 8, 2); // meth
477                                 // prepare the bottom description if the zipped file, if any
478                                 if ($b2!=='') {
479                                         $d = (strlen($b2)==16) ? 4 : 0; // offset because of the signature if any
480                                         $this->_PutDec($b2, $ReplInfo['crc32'], $d+0, 4); // crc32
481                                         $this->_PutDec($b2, $ReplInfo['len_c'], $d+4, 4); // l_data_c
482                                         $this->_PutDec($b2, $ReplInfo['len_u'], $d+8, 4); // l_data_u
483                                 }
484                                 // output data
485                                 $this->OutputFromString($b1.$ReplInfo['data'].$b2);
486                                 unset($ReplInfo['data']); // save PHP memory
487                                 $Delta = $Delta + $ReplInfo['diff'] + $ReplInfo['len_c'];
488                         }
489                         // Update the delta of positions for zipped files which are physically after the currently replaced one
490                         for ($i=0;$i<$this->CdFileNbr;$i++) {
491                                 if ($this->CdFileLst[$i]['p_loc']>$ReplPos) {
492                                         $FicNewPos[$i] = $this->CdFileLst[$i]['p_loc'] + $Delta;
493                                 }
494                         }
495                         // Update the current pos in the archive
496                         $ArchPos = $ReplPos + $info_old_len;
497                 }
498
499                 // Ouput all the zipped files that remain before the Central Directory listing
500                 if ($this->ArchHnd!==false) $this->OutputFromArch($ArchPos, $this->CdPos); // ArchHnd is false if CreateNew() has been called
501                 $ArchPos = $this->CdPos;
502
503                 // Output file to add
504                 $AddNbr = count($this->AddInfo);
505                 $AddDataLen = 0; // total len of added data (inlcuding file headers)
506                 if ($AddNbr>0) {
507                         $AddPos = $ArchPos + $Delta; // position of the start
508                         $AddLst = array_keys($this->AddInfo);
509                         foreach ($AddLst as $idx) {
510                                 $n = $this->_DataOuputAddedFile($idx, $AddPos);
511                                 $AddPos += $n;
512                                 $AddDataLen += $n;
513                         }
514                 }
515
516                 // Modifiy file information in the Central Directory for replaced files
517                 $b2 = '';
518                 $old_cd_len = 0;
519                 for ($i=0;$i<$this->CdFileNbr;$i++) {
520                         $b1 = $this->CdFileLst[$i]['bin'];
521                         $old_cd_len += strlen($b1);
522                         if (!isset($DelLst[$i])) {
523                                 if (isset($FicNewPos[$i])) $this->_PutDec($b1, $FicNewPos[$i], 42, 4);   // p_loc
524                                 if (isset($this->ReplInfo[$i])) {
525                                         $ReplInfo =& $this->ReplInfo[$i];
526                                         $this->_PutDec($b1, $time, 12, 2); // time
527                                         $this->_PutDec($b1, $date, 14, 2); // date
528                                         $this->_PutDec($b1, $ReplInfo['crc32'], 16, 4); // crc32
529                                         $this->_PutDec($b1, $ReplInfo['len_c'], 20, 4); // l_data_c
530                                         $this->_PutDec($b1, $ReplInfo['len_u'], 24, 4); // l_data_u
531                                         if ($ReplInfo['meth']!==false) $this->_PutDec($b1, $ReplInfo['meth'], 10, 2); // meth
532                                 }
533                                 $b2 .= $b1;
534                         }
535                 }
536                 $this->OutputFromString($b2);
537                 $ArchPos += $old_cd_len;
538                 $DeltaCdLen =  $DeltaCdLen + strlen($b2) - $old_cd_len;
539  
540                 // Output until "end of central directory record"
541                 if ($this->ArchHnd!==false) $this->OutputFromArch($ArchPos, $this->CdEndPos); // ArchHnd is false if CreateNew() has been called
542
543                 // Output file information of the Central Directory for added files
544                 if ($AddNbr>0) {
545                         $b2 = '';
546                         foreach ($AddLst as $idx) {
547                                 $b2 .= $this->AddInfo[$idx]['bin'];
548                         }
549                         $this->OutputFromString($b2);
550                         $DeltaCdLen += strlen($b2);
551                 }
552
553                 // Output "end of central directory record"
554                 $b2 = $this->CdInfo['bin'];
555                 $DelNbr = count($DelLst);
556                 if ( ($AddNbr>0) or ($DelNbr>0) ) {
557                         // total number of entries in the central directory on this disk
558                         $n = $this->_GetDec($b2, 8, 2);
559                         $this->_PutDec($b2, $n + $AddNbr - $DelNbr,  8, 2);
560                         // total number of entries in the central directory
561                         $n = $this->_GetDec($b2, 10, 2);
562                         $this->_PutDec($b2, $n + $AddNbr - $DelNbr, 10, 2);
563                         // size of the central directory
564                         $n = $this->_GetDec($b2, 12, 4);
565                         $this->_PutDec($b2, $n + $DeltaCdLen, 12, 4);
566                         $Delta = $Delta + $AddDataLen;
567                 }
568                 $this->_PutDec($b2, $this->CdPos+$Delta , 16, 4); // p_cd  (offset of start of central directory with respect to the starting disk number)
569                 $this->OutputFromString($b2);
570
571                 $this->OutputClose();
572
573                 return true;
574
575         }
576
577         // ----------------
578         // output functions
579         // ----------------
580
581         function OutputOpen($Render, $File, $ContentType) {
582
583                 if (($Render & TBSZIP_FILE)==TBSZIP_FILE) {
584                         $this->OutputMode = TBSZIP_FILE;
585                         if (''.$File=='') $File = basename($this->ArchFile).'.zip';
586                         $this->OutputHandle = @fopen($File, 'w');
587                         if ($this->OutputHandle===false) {
588                                 $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.');
589                                 return false;
590                         }
591                 } elseif (($Render & TBSZIP_STRING)==TBSZIP_STRING) {
592                         $this->OutputMode = TBSZIP_STRING;
593                         $this->OutputSrc = '';
594                 } elseif (($Render & TBSZIP_DOWNLOAD)==TBSZIP_DOWNLOAD) {
595                         $this->OutputMode = TBSZIP_DOWNLOAD;
596                         // Output the file
597                         if (''.$File=='') $File = basename($this->ArchFile);
598                         if (($Render & TBSZIP_NOHEADER)==TBSZIP_NOHEADER) {
599                         } else {
600                                 header ('Pragma: no-cache');
601                                 if ($ContentType!='') header ('Content-Type: '.$ContentType);
602                                 header('Content-Disposition: attachment; filename="'.$File.'"');
603                                 header('Expires: 0');
604                                 header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
605                                 header('Cache-Control: public');
606                                 header('Content-Description: File Transfer'); 
607                                 header('Content-Transfer-Encoding: binary');
608                                 $Len = $this->_EstimateNewArchSize();
609                                 if ($Len!==false) header('Content-Length: '.$Len); 
610                         }
611                 }
612
613                 return true;
614
615         }
616
617         function OutputFromArch($pos, $pos_stop) {
618                 $len = $pos_stop - $pos;
619                 if ($len<0) return;
620                 $this->_MoveTo($pos);
621                 $block = 1024;
622                 while ($len>0) {
623                         $l = min($len, $block);
624                         $x = $this->_ReadData($l);
625                         $this->OutputFromString($x);
626                         $len = $len - $l;
627                 }
628                 unset($x);
629         }
630
631         function OutputFromString($data) {
632                 if ($this->OutputMode===TBSZIP_DOWNLOAD) {
633                         echo $data; // donwload
634                 } elseif ($this->OutputMode===TBSZIP_STRING) {
635                         $this->OutputSrc .= $data; // to string
636                 } elseif (TBSZIP_FILE) {
637                         fwrite($this->OutputHandle, $data); // to file
638                 }
639         }
640
641         function OutputClose() {
642                 if ( ($this->OutputMode===TBSZIP_FILE) && ($this->OutputHandle!==false) ) {
643                         fclose($this->OutputHandle);
644                         $this->OutputHandle = false;
645                 }
646         }
647
648         // ----------------
649         // Reading functions
650         // ----------------
651
652         function _MoveTo($pos, $relative = SEEK_SET) {
653                 fseek($this->ArchHnd, $pos, $relative);
654         }
655
656         function _ReadData($len) {
657                 if ($len>0) {
658                         $x = fread($this->ArchHnd, $len);
659                         return $x;
660                 } else {
661                         return '';
662                 }
663         }
664
665         // ----------------
666         // Take info from binary data
667         // ----------------
668
669         function _GetDec($txt, $pos, $len) {
670                 $x = substr($txt, $pos, $len);
671                 $z = 0;
672                 for ($i=0;$i<$len;$i++) {
673                         $asc = ord($x[$i]);
674                         if ($asc>0) $z = $z + $asc*pow(256,$i);
675                 }
676                 return $z;
677         }
678
679         function _GetHex($txt, $pos, $len) {
680                 $x = substr($txt, $pos, $len);
681                 return 'h:'.bin2hex(strrev($x));
682         }
683
684         function _GetBin($txt, $pos, $len) {
685                 $x = substr($txt, $pos, $len);
686                 $z = '';
687                 for ($i=0;$i<$len;$i++) {
688                         $asc = ord($x[$i]);
689                         if (isset($x[$i])) {
690                                 for ($j=0;$j<8;$j++) {
691                                         $z .= ($asc & pow(2,$j)) ? '1' : '0';
692                                 }
693                         } else {
694                                 $z .= '00000000';
695                         }
696                 }
697                 return 'b:'.$z;
698         }
699
700         // ----------------
701         // Put info into binary data
702         // ----------------
703
704         function _PutDec(&$txt, $val, $pos, $len) {
705                 $x = '';
706                 for ($i=0;$i<$len;$i++) {
707                         if ($val==0) {
708                                 $z = 0;
709                         } else {
710                                 $z = intval($val % 256);
711                                 if (($val<0) && ($z!=0)) { // ($z!=0) is very important, example: val=-420085702
712                                         // 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
713                                         $val = ($val - $z)/256 -1;
714                                         $z = 256 + $z;
715                                 } else {
716                                         $val = ($val - $z)/256;
717                                 }
718                         }
719                         $x .= chr($z);
720                 }
721                 $txt = substr_replace($txt, $x, $pos, $len);
722         }
723
724         function _MsDos_Date($Timestamp = false) {
725                 // convert a date-time timstamp into the MS-Dos format
726                 $d = ($Timestamp===false) ? getdate() : getdate($Timestamp);
727                 return (($d['year']-1980)*512) + ($d['mon']*32) + $d['mday'];
728         }
729         function _MsDos_Time($Timestamp = false) {
730                 // convert a date-time timstamp into the MS-Dos format
731                 $d = ($Timestamp===false) ? getdate() : getdate($Timestamp);
732                 return ($d['hours']*2048) + ($d['minutes']*32) + intval($d['seconds']/2); // seconds are rounded to an even number in order to save 1 bit
733         }
734
735         function _MsDos_Debug($date, $time) {
736                 // Display the formated date and time. Just for debug purpose.
737                 // date end time are encoded on 16 bits (2 bytes) : date = yyyyyyymmmmddddd , time = hhhhhnnnnnssssss
738                 $y = ($date & 65024)/512 + 1980;
739                 $m = ($date & 480)/32;
740                 $d = ($date & 31);
741                 $h = ($time & 63488)/2048;
742                 $i = ($time & 1984)/32;
743                 $s = ($time & 31) * 2; // seconds have been rounded to an even number in order to save 1 bit
744                 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);
745         }
746
747         function _TxtPos($pos) {
748                 // Return the human readable position in both decimal and hexa
749                 return $pos." (h:".dechex($pos).")";
750         }
751         
752         function _DataOuputAddedFile($Idx, $PosLoc) {
753
754                 $Ref =& $this->AddInfo[$Idx];
755                 $this->_DataPrepare($Ref); // get data from external file if necessary
756
757                 // Other info
758                 $now = time();
759                 $date  = $this->_MsDos_Date($now);
760                 $time  = $this->_MsDos_Time($now);
761                 $len_n = strlen($Ref['name']);
762                 $purp  = 2048 ; // purpose // +8 to indicates that there is an extended local header 
763
764                 // Header for file in the data section 
765                 $b = 'PK'.chr(03).chr(04).str_repeat(' ',26); // signature
766                 $this->_PutDec($b,20,4,2); //vers = 20
767                 $this->_PutDec($b,$purp,6,2); // purp
768                 $this->_PutDec($b,$Ref['meth'],8,2);  // meth
769                 $this->_PutDec($b,$time,10,2); // time
770                 $this->_PutDec($b,$date,12,2); // date
771                 $this->_PutDec($b,$Ref['crc32'],14,4); // crc32
772                 $this->_PutDec($b,$Ref['len_c'],18,4); // l_data_c
773                 $this->_PutDec($b,$Ref['len_u'],22,4); // l_data_u
774                 $this->_PutDec($b,$len_n,26,2); // l_name
775                 $this->_PutDec($b,0,28,2); // l_fields
776                 $b .= $Ref['name']; // name
777                 $b .= ''; // fields
778
779                 // Output the data
780                 $this->OutputFromString($b.$Ref['data']);
781                 $OutputLen = strlen($b) + $Ref['len_c']; // new position of the cursor
782                 unset($Ref['data']); // save PHP memory
783
784                 // Information for file in the Central Directory
785                 $b = 'PK'.chr(01).chr(02).str_repeat(' ',42); // signature
786                 $this->_PutDec($b,20,4,2);  // vers_used = 20
787                 $this->_PutDec($b,20,6,2);  // vers_necess = 20
788                 $this->_PutDec($b,$purp,8,2);  // purp
789                 $this->_PutDec($b,$Ref['meth'],10,2); // meth
790                 $this->_PutDec($b,$time,12,2); // time
791                 $this->_PutDec($b,$date,14,2); // date
792                 $this->_PutDec($b,$Ref['crc32'],16,4); // crc32
793                 $this->_PutDec($b,$Ref['len_c'],20,4); // l_data_c
794                 $this->_PutDec($b,$Ref['len_u'],24,4); // l_data_u
795                 $this->_PutDec($b,$len_n,28,2); // l_name
796                 $this->_PutDec($b,0,30,2); // l_fields
797                 $this->_PutDec($b,0,32,2); // l_comm
798                 $this->_PutDec($b,0,34,2); // disk_num
799                 $this->_PutDec($b,0,36,2); // int_file_att
800                 $this->_PutDec($b,0,38,4); // ext_file_att
801                 $this->_PutDec($b,$PosLoc,42,4); // p_loc
802                 $b .= $Ref['name']; // v_name
803                 $b .= ''; // v_fields
804                 $b .= ''; // v_comm
805
806                 $Ref['bin'] = $b;
807
808                 return $OutputLen;
809
810         }
811
812         function _DataCreateNewRef($Data, $DataType, $Compress, $Diff, $NameOrIdx) {
813
814                 if (is_array($Compress)) {
815                         $result = 2;
816                         $meth = $Compress['meth'];
817                         $len_u = $Compress['len_u'];
818                         $crc32 = $Compress['crc32'];
819                         $Compress = false;
820                 } elseif ($Compress and ($this->Meth8Ok)) {
821                         $result = 1;
822                         $meth = 8;
823                         $len_u = false; // means unknown
824                         $crc32 = false;
825                 } else {
826                         $result = ($Compress) ? -1 : 0;
827                         $meth = 0;
828                         $len_u = false;
829                         $crc32 = false;
830                         $Compress = false;
831                 }
832
833                 if ($DataType==TBSZIP_STRING) {
834                         $path = false;
835                         if ($Compress) {
836                                 // we compress now in order to save PHP memory
837                                 $len_u = strlen($Data);
838                                 $crc32 = crc32($Data);
839                                 $Data = gzdeflate($Data);
840                                 $len_c = strlen($Data);
841                         } else {
842                                 $len_c = strlen($Data);
843                                 if ($len_u===false) {
844                                         $len_u = $len_c;
845                                         $crc32 = crc32($Data);
846                                 }
847                         }
848                 } else {
849                         $path = $Data;
850                         $Data = false;
851                         if (file_exists($path)) {
852                                 $fz = filesize($path);
853                                 if ($len_u===false) $len_u = $fz;
854                                 $len_c = ($Compress) ? false : $fz;
855                         } else {
856                                 return $this->RaiseError("Cannot add the file '".$path."' because it is not found.");
857                         }
858                 }
859
860                 // 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
861                 return array('data'=>$Data, 'path'=>$path, 'meth'=>$meth, 'len_u'=>$len_u, 'len_c'=>$len_c, 'crc32'=>$crc32, 'diff'=>$Diff, 'res'=>$result);
862
863         }
864
865         function _DataPrepare(&$Ref) {
866         // returns the real size of data
867                 if ($Ref['path']!==false) {
868                         $Ref['data'] = file_get_contents($Ref['path']);
869                         if ($Ref['crc32']===false) $Ref['crc32'] = crc32($Ref['data']);
870                         if ($Ref['len_c']===false) {
871                                 // means the data must be compressed
872                                 $Ref['data'] = gzdeflate($Ref['data']);
873                                 $Ref['len_c'] = strlen($Ref['data']);
874                         }
875                 }
876         }
877
878         function _EstimateNewArchSize($Optim=true) {
879         // 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)
880
881                 if ($this->ArchIsNew) {
882                         $Len = strlen($this->CdInfo['bin']);
883                 } else {
884                         $Len = filesize($this->ArchFile);
885                 }
886
887                 // files to replace or delete
888                 foreach ($this->ReplByPos as $i) {
889                         $Ref =& $this->ReplInfo[$i];
890                         if ($Ref===false) {
891                                 // file to delete
892                                 $Info =& $this->CdFileLst[$i];
893                                 if (!isset($this->VisFileLst[$i])) {
894                                         if ($Optim) return false; // if $Optimization is set to true, then we d'ont rewind to read information
895                                         $this->_MoveTo($Info['p_loc']);
896                                         $this->_ReadFile($i, false);
897                                 }
898                                 $Vis =& $this->VisFileLst[$i];
899                                 $Len += -strlen($Vis['bin']) -strlen($Info['bin']) - $Info['l_data_c'];
900                                 if (isset($Vis['desc_bin'])) $Len += -strlen($Vis['desc_bin']);
901                         } elseif ($Ref['len_c']===false) {
902                                 return false; // information not yet known
903                         } else {
904                                 // file to replace
905                                 $Len += $Ref['len_c'] + $Ref['diff'];
906                         }
907                 }
908
909                 // files to add
910                 $i_lst = array_keys($this->AddInfo);
911                 foreach ($i_lst as $i) {
912                         $Ref =& $this->AddInfo[$i];
913                         if ($Ref['len_c']===false) {
914                                 return false; // information not yet known
915                         } else {
916                                 $Len += $Ref['len_c'] + $Ref['diff'];
917                         }
918                 }
919
920                 return $Len;
921
922         }
923
924 }