1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4/**
5 * ZIP archive writer
6 *
7 * PHP versions 4 and 5
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330,Boston,MA 02111-1307 USA
22 *
23 * @category   File Formats
24 * @package    File_Archive
25 * @author     Vincent Lascaux <vincentlascaux@php.net>
26 * @copyright  1997-2005 The PHP Group
27 * @license    http://www.gnu.org/copyleft/lesser.html  LGPL
28 * @version    CVS: $Id$
29 * @link       http://pear.php.net/package/File_Archive
30 */
31
32require_once "File/Archive/Writer/MemoryArchive.php";
33
34/**
35 * ZIP archive writer
36 */
37class File_Archive_Writer_Zip extends File_Archive_Writer_MemoryArchive
38{
39    /**
40     * @var int Compression level
41     * @access private
42     */
43    var $compressionLevel;
44
45    /**
46     * @var int Current position in the writer
47     * @access private
48     */
49    var $offset = 0;
50
51    /**
52     * @var string Optionnal comment to add to the zip
53     * @access private
54     */
55    var $comment = "";
56
57    /**
58     * @var string Data written at the end of the ZIP file
59     * @access private
60     */
61    var $central = "";
62
63    function File_Archive_Writer_Zip($filename, &$innerWriter,
64                                     $stat=array(), $autoClose = true)
65    {
66        global $_File_Archive_Options;
67        parent::File_Archive_Writer_MemoryArchive(
68                    $filename, $innerWriter, $stat, $autoClose
69                );
70
71        $this->compressionLevel = File_Archive::getOption('zipCompressionLevel', 9);
72    }
73
74    /**
75     * Change the level of the compression. This may be done between two files
76     *
77     * @param Int $compressionLevel New compression level from 0 to 9
78     */
79    function setCompressionLevel($compressionLevel)
80    {
81        $this->compressionLevel = $compressionLevel;
82    }
83
84    /**
85     * Set a comment on the ZIP file
86     */
87    function setComment($comment) { $this->comment = $comment; }
88
89    /**
90     * @param int $time Unix timestamp of the date to convert
91     * @return the date formated as a ZIP date
92     */
93    function getMTime($time)
94    {
95        $mtime = ($time !== null ? getdate($time) : getdate());
96        $mtime = preg_replace(
97                     "/(..){1}(..){1}(..){1}(..){1}/",
98                     "\\x\\4\\x\\3\\x\\2\\x\\1",
99                     dechex(($mtime['year']-1980<<25)|
100                            ($mtime['mon'    ]<<21)|
101                            ($mtime['mday'   ]<<16)|
102                            ($mtime['hours'  ]<<11)|
103                            ($mtime['minutes']<<5)|
104                            ($mtime['seconds']>>1)));
105        eval('$mtime = "'.$mtime.'";');
106        return $mtime;
107    }
108
109    /**
110     * Inform the archive that $filename is present.
111     * Consequences are the same as appendFileData, but no data is output
112     * to the inner writer.
113     * This is used by File_Archive_Reader_Zip::makeWriter()
114     *
115     * @param string $filename name of the file
116     * @param array $stat stats of the file, indexes 9 and 7 must be present
117     * @param int $crc32 checksum of the file
118     * @param int $compLength length of the compressed data
119     */
120    function alreadyWrittenFile($filename, $stat, $crc32, $complength)
121    {
122        $filename = preg_replace("/^(\.{1,2}(\/|\\\))+/","",$filename);
123
124        $mtime = $this->getMTime(isset($stat[9]) ? $stat[9] : null);
125        $normlength = $stat[7];
126
127        $this->nbFiles++;
128
129        $this->central .= "\x50\x4b\x01\x02\x00\x00\x14\x00\x00\x00\x08\x00".
130                   $mtime.
131                   pack("VVVvvvvvVV",
132                       $crc32, $complength, $normlength,
133                       strlen($filename), 0x00,0x00,0x00,0x00,
134                       0x0000,$this->offset).
135                   $filename;
136
137        $this->offset += 30 + strlen($filename) + $complength;
138    }
139
140    /**
141     * @see    File_Archive_Writer_MemoryArchive::appendFileData()
142     * @access protected
143     */
144    function appendFileData($filename, $stat, $data)
145    {
146        $crc32 = crc32($data);
147        $normlength = strlen($data);
148        $data = gzcompress($data,$this->compressionLevel);
149        $data = substr($data,2,strlen($data)-6);
150
151        return $this->appendCompressedData($filename, $stat, $data, $crc32, $normlength);
152    }
153
154    function appendCompressedData($filename, $stat, $data, $crc32, $normlength)
155    {
156        $filename = preg_replace("/^(\.{1,2}(\/|\\\))+/","",$filename);
157        $mtime = $this->getMTime(isset($stat[9]) ? $stat[9] : null);
158
159        $complength = strlen($data);
160
161        $zipData = "\x50\x4b\x03\x04\x14\x00\x00\x00\x08\x00".
162                   $mtime.
163                   pack("VVVvv",
164                        $crc32,
165                        $complength,
166                        $normlength,
167                        strlen($filename),
168                        0x00).
169                   $filename.
170                   $data;
171
172        $error = $this->innerWriter->writeData($zipData);
173        if (PEAR::isError($error)) {
174            return $error;
175        }
176
177        $this->central .= "\x50\x4b\x01\x02\x00\x00\x14\x00\x00\x00\x08\x00".
178                   $mtime.
179                   pack("VVVvvvvvVV",
180                       $crc32, $complength, $normlength,
181                       strlen($filename), 0x00,0x00,0x00,0x00,
182                       0x0000,$this->offset).
183                   $filename;
184
185        $this->offset += strlen($zipData);
186    }
187
188    function appendFile($filename, $dataFilename)
189    {
190        //Try to read from the cache
191        $cache = File_Archive::getOption('cache', null);
192        if ($cache !== null && $this->compressionLevel > 0) {
193            $id = realpath($dataFilename);
194            $id = urlencode($id);
195            $id = str_replace('_', '%5F', $id);
196
197            $group = 'FileArchiveZip'.$this->compressionLevel;
198            $mtime = filemtime($dataFilename);
199
200            //Tries to read from cache
201            if (($data = $cache->get($id, $group)) !== false) {
202                $info = unpack('Vmtime/Vcrc/Vnlength', substr($data, 0, 12));
203                $data = substr($data, 12);
204            }
205
206            //If cache failed or file modified since then
207            if ($data === false ||
208                $info['mtime'] != $mtime) {
209
210                $data = file_get_contents($dataFilename);
211
212                $info = array(
213                        'crc' => crc32($data),
214                        'nlength' => strlen($data),
215                        'mtime' => $mtime
216                       );
217
218                $data = gzcompress($data,$this->compressionLevel);
219                $data = substr($data,2,strlen($data)-6);
220                $data = pack('VVV', $info['mtime'], $info['crc'], $info['nlength']).$data;
221                $cache->save($data, $id, $group);
222            }
223
224            return $this->appendCompressedData(
225                                    $filename,
226                                    stat($dataFilename),
227                                    $data,
228                                    $info['crc'],
229                                    $info['nlength']
230                   );
231
232        }
233
234        //If no cache system, use the standard way
235        return parent::appendFile($filename, $dataFilename);
236    }
237
238    /**
239     * @see    File_Archive_Writer_MemoryArchive::sendFooter()
240     * @access protected
241     */
242    function sendFooter()
243    {
244        return $this->innerWriter->writeData(
245            $this->central.
246            "\x50\x4b\x05\x06\x00\x00\x00\x00".
247            pack("vvVVv",
248                $this->nbFiles,$this->nbFiles,
249                strlen($this->central),$this->offset,
250                strlen($this->comment)).
251            $this->comment
252        );
253    }
254    /**
255     * @see File_Archive_Writer::getMime()
256     */
257    function getMime() { return "application/zip"; }
258}
259
260?>