Feature 5388: Print Invoices (documents) list gets too long. Fixed by default 180...
[fa-stable.git] / includes / archive.inc
1 <?php
2 /*--------------------------------------------------
3  | TAR/GZIP/BZIP2/ZIP ARCHIVE CLASSES 2.1
4  | By Devin Doucette
5  | Copyright (c) 2005 Devin Doucette
6  | Email: darksnoopy@shaw.ca
7  +--------------------------------------------------
8  | Email bugs/suggestions to darksnoopy@shaw.ca
9  +--------------------------------------------------
10  | This script has been created and released under
11  | the GNU GPL and is free to use and redistribute
12  | only if this copyright statement is not removed
13  +--------------------------------------------------
14  | FrontAccounting bugfixes/changes added.
15  +--------------------------------------------------*/
16
17 class archive
18 {
19         function __construct($name)
20         {
21                 $this->options = array (
22                         'basedir' => ".",
23                         'name' => $name,
24                         'prepend' => "",
25                         'inmemory' => 0,
26                         'overwrite' => 0,
27                         'recurse' => 1,
28                         'storepaths' => 1,
29                         'followlinks' => 0,
30                         'level' => 3,
31                         'method' => 1,
32                         'sfx' => "",
33                         'type' => "",
34                         'comment' => ""
35                 );
36                 $this->files = array ();
37                 $this->exclude = array ();
38                 $this->storeonly = array ();
39                 $this->error = array ();
40         }
41
42         function set_options($options)
43         {
44                 foreach ($options as $key => $value)
45                         $this->options[$key] = $value;
46                 if (!empty ($this->options['basedir']))
47                 {
48                         $this->options['basedir'] = str_replace("\\", "/", $this->options['basedir']);
49                         $this->options['basedir'] = preg_replace("/\/+/", "/", $this->options['basedir']);
50                         $this->options['basedir'] = preg_replace("/\/$/", "", $this->options['basedir']);
51                 }
52                 if (!empty ($this->options['name']))
53                 {
54                         $this->options['name'] = str_replace("\\", "/", $this->options['name']);
55 //                      $this->options['name'] = preg_replace("/\/+/", "/", $this->options['name']);
56                 }
57                 if (!empty ($this->options['prepend']))
58                 {
59                         $this->options['prepend'] = str_replace("\\", "/", $this->options['prepend']);
60                         $this->options['prepend'] = preg_replace("/^(\.*\/+)+/", "", $this->options['prepend']);
61                         $this->options['prepend'] = preg_replace("/\/+/", "/", $this->options['prepend']);
62                         $this->options['prepend'] = preg_replace("/\/$/", "", $this->options['prepend']) . "/";
63                 }
64         }
65
66         function create_archive()
67         {
68                 $this->make_list();
69
70                 if ($this->options['inmemory'] == 0)
71                 {
72                         if ($this->options['overwrite'] == 0 && 
73                                 file_exists($this->options['name'] 
74                                 . ($this->options['type'] == "pkg" || $this->options['type'] == "gzip" ||$this->options['type'] == "bzip" ? ".tmp" : "")))
75                         {
76                                 $this->error[] = "File {$this->options['name']} already exists.";
77                                 return 0;
78                         }
79                         else if (!($this->archive = @fopen($this->options['name'].".tmp", "wb+")))
80                         {
81                                 $this->error[] = "Could not open {$this->options['name']} for writing.".':'.getcwd();
82                                 return 0;
83                         }
84                 }
85                 else
86                         $this->archive = "";
87
88                 if (!$this->create_tar())
89                 {
90                         $this->error[] = "Could not create package file.";
91                         return 0;
92                 }
93                 if (!$this->create_pkg())
94                 {
95                         $this->error[] = "Could not compress package file.";
96                         return 0;
97                 }
98
99                 if ($this->options['inmemory'] == 0)
100                 {
101                         fclose($this->archive);
102                         unlink($this->options['name'] . ".tmp");
103                 }
104         }
105
106         function add_data($data)
107         {
108                 if ($this->options['inmemory'] == 0)
109                         fwrite($this->archive, $data);
110                 else
111                         $this->archive .= $data;
112         }
113
114         function make_list()
115         {
116                 if (!empty ($this->exclude)) {
117                         foreach ($this->files as $key => $value)
118                                 foreach ($this->exclude as $current)
119                                         if ($value['name'] == $current['name']) 
120                                                 unset ($this->files[$key]);
121                 }
122                 if (!empty ($this->storeonly))
123                         foreach ($this->files as $key => $value)
124                                 foreach ($this->storeonly as $current)
125                                         if ($value['name'] == $current['name'])
126                                                 $this->files[$key]['method'] = 0;
127                 unset ($this->exclude, $this->storeonly);
128         }
129
130         function add_files($list)
131         {
132                 $temp = $this->list_files($list);
133                 foreach ($temp as $current)
134                         $this->files[] = $current;
135         }
136
137         function exclude_files($list)
138         {
139                 $temp = $this->list_files($list);
140                 foreach ($temp as $current)
141                         $this->exclude[] = $current;
142         }
143
144         function store_files($list)
145         {
146                 $temp = $this->list_files($list);
147                 foreach ($temp as $current)
148                         $this->storeonly[] = $current;
149         }
150
151         function list_files($list)
152         {
153                 if (!is_array ($list))
154                 {
155                         $temp = $list;
156                         $list = array ($temp);
157                         unset ($temp);
158                 }
159
160                 $files = array ();
161
162                 $pwd = getcwd();
163                 chdir($this->options['basedir']);
164
165                 foreach ($list as $current)
166                 {
167                         $current = str_replace("\\", "/", $current);
168                         $current = preg_replace("/\/+/", "/", $current);
169                         $current = preg_replace("/\/$/", "", $current);
170                         if (strstr($current, "*"))
171                         {
172                                 $regex = preg_replace("/([\\\^\$\.\[\]\|\(\)\?\+\{\}\/])/", "\\\\\\1", $current);
173                                 $regex = str_replace("*", ".*", $regex);
174                                 $dir = strstr($current, "/") ? substr($current, 0, strrpos($current, "/")) : ".";
175                                 $temp = $this->parse_dir($dir);
176                                 foreach ($temp as $current2)
177                                         if (preg_match("/^{$regex}$/i", $current2['name']))
178                                                 $files[] = $current2;
179                                 unset ($regex, $dir, $temp, $current);
180                         }
181                         else if (@is_dir($current))
182                         {
183                                 $temp = $this->parse_dir($current);
184                                 foreach ($temp as $file)
185                                         $files[] = $file;
186                                 unset ($temp, $file);
187                         }
188                         else if (@file_exists($current))
189                                 $files[] = array ('name' => $current, 'name2' => $this->options['prepend'] .
190                                         preg_replace("/(\.+\/+)+/", "", ($this->options['storepaths'] == 0 && strstr($current, "/")) ?
191                                         substr($current, strrpos($current, "/") + 1) : $current),
192                                         'type' => @is_link($current) && $this->options['followlinks'] == 0 ? 2 : 0,
193                                         'ext' => substr($current, strrpos($current, ".")), 'stat' => stat($current));
194                 }
195
196                 chdir($pwd);
197
198                 unset ($current, $pwd);
199                 return $files;
200         }
201
202         function parse_dir($dirname)
203         {
204                 if ($this->options['storepaths'] == 1 && !preg_match("/^(\.+\/*)+$/", $dirname))
205                         $files = array (array ('name' => $dirname, 'name2' => $this->options['prepend'] .
206                                 preg_replace("/(\.+\/+)+/", "", ($this->options['storepaths'] == 0 && strstr($dirname, "/")) ?
207                                 substr($dirname, strrpos($dirname, "/") + 1) : $dirname), 'type' => 5, 'stat' => stat($dirname)));
208                 else
209                         $files = array ();
210                 $dir = @opendir($dirname);
211
212                 while ($file = @readdir($dir))
213                 {
214                         $fullname = $dirname . "/" . $file;
215                         if ($file == "." || $file == "..")
216                                 continue;
217                         else if (@is_dir($fullname))
218                         {
219                                 if (empty ($this->options['recurse']))
220                                         continue;
221                                 $temp = $this->parse_dir($fullname);
222                                 foreach ($temp as $file2)
223                                         $files[] = $file2;
224                         }
225                         else if (@file_exists($fullname))
226                                 $files[] = array ('name' => $fullname, 'name2' => $this->options['prepend'] .
227                                         preg_replace("/(\.+\/+)+/", "", ($this->options['storepaths'] == 0 && strstr($fullname, "/")) ?
228                                         substr($fullname, strrpos($fullname, "/") + 1) : $fullname),
229                                         'type' => @is_link($fullname) && $this->options['followlinks'] == 0 ? 2 : 0,
230                                         'ext' => substr($file, strrpos($file, ".")), 'stat' => stat($fullname));
231                 }
232
233                 @closedir($dir);
234
235                 return $files;
236         }
237 }
238
239 class tar_file extends archive
240 {
241         function __construct($name)
242         {
243                 parent::__construct($name);
244                 $this->options['type'] = "tar";
245         }
246
247         function create_tar()
248         {
249                 $pwd = getcwd();
250                 chdir($this->options['basedir']);
251                 $files = $this->files;
252                 foreach ($files as $current)
253                 {
254                         if ($current['name'] == $this->options['name'])
255                                 continue;
256                         if (strlen($current['name2']) > 99)
257                         {
258                                 $path = substr($current['name2'], 0, strpos($current['name2'], "/", strlen($current['name2']) - 100) + 1);
259                                 $current['name2'] = substr($current['name2'], strlen($path));
260                                 if (strlen($path) > 154 || strlen($current['name2']) > 99)
261                                 {
262                                         $this->error[] = "Could not add {$path}{$current['name2']} to archive because the filename is too long.";
263                                         continue;
264                                 }
265                         }
266                         $block = pack("a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12", $current['name2'], sprintf("%07o", 
267                                 $current['stat'][2]), sprintf("%07o", $current['stat'][4]), sprintf("%07o", $current['stat'][5]), 
268                                 sprintf("%011o", $current['type'] == 2 ? 0 : $current['stat'][7]), sprintf("%011o", $current['stat'][9]), 
269                                 "        ", $current['type'], $current['type'] == 2 ? @readlink($current['name']) : "", "ustar ", " ", 
270                                 "Unknown", "Unknown", "", "", !empty ($path) ? $path : "", "");
271
272                         $checksum = 0;
273                         for ($i = 0; $i < 512; $i++)
274                                 $checksum += ord(substr($block, $i, 1));
275                         $checksum = pack("a8", sprintf("%07o", $checksum));
276                         $block = substr_replace($block, $checksum, 148, 8);
277
278                         if ($current['type'] == 2 || $current['stat'][7] == 0)
279                                 { $this->add_data($block); 
280                                 }
281                         else if ($fp = @fopen($current['name'], "rb"))
282                         {
283                                 $this->add_data($block);
284
285                                 while ($temp = fread($fp, 1048576)) {
286                                         $this->add_data($temp);
287                                 }
288                                 if ($current['stat'][7] % 512 > 0)
289                                 {
290                                         $temp = "";
291                                         for ($i = 0; $i < 512 - $current['stat'][7] % 512; $i++)
292                                                 $temp .= "\0";
293                                         $this->add_data($temp);
294                                 }
295                                 fclose($fp);
296                         }
297                         else
298                                 $this->error[] = "Could not open file {$current['name']} for reading. It was not added.";
299                 }
300
301                 $this->add_data(pack("a1024", ""));
302
303                 chdir($pwd);
304
305                 return 1;
306         }
307         /*
308                 Extract files to base directory.
309                 When $dry_run!=0 no file is written,
310                 but list of existing files to be written is returned.
311         */
312         function extract_files($dry_run = false)
313         {
314         $flist = array();
315                 if ($fp = $this->open_archive())
316                 {
317                         $pwd = getcwd();
318                         // display_error($pwd.':'.$this->options['basedir']);
319                         chdir($this->options['basedir']);
320                         if ($this->options['inmemory'] == 1)
321                                 $this->files = array();
322
323                         while ($block = fread($fp, 512))
324                         {
325                                 if (version_compare(PHP_VERSION, '5.5.0') >= 0)
326                                         $temp = unpack("Z100name/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/Z8checksum/Z1type/Z100symlink/Z6magic/Z2temp/Z32temp/Z32temp/Z8temp/Z8temp/Z155prefix/Z12temp", $block);
327                                 else
328                     $temp = unpack("a100name/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100symlink/a6magic/a2temp/a32temp/a32temp/a8temp/a8temp/a155prefix/a12temp", $block);                                       
329                                 $file = array (
330                                         'name' => rtrim($temp['prefix']) . rtrim($temp['name']),
331                                         'stat' => array (
332                                                 2 => octdec($temp['mode']),
333                                                 4 => octdec($temp['uid']),
334                                                 5 => octdec($temp['gid']),
335                                                 7 => octdec($temp['size']),
336                                                 9 => octdec($temp['mtime']),
337                                         ),
338                                         'checksum' => octdec($temp['checksum']),
339                                         'type' => $temp['type'],
340                                         'magic' => $temp['magic'],
341                                 );
342                                 if ($file['checksum'] == 0x00000000)
343                                         break;
344                                 else if (substr($file['magic'], 0, 5) != "ustar")
345                                 {
346                                         $this->error[] = "This script does not support extracting this type of tar file.";
347                                         break;
348                                 }
349                                 $block = substr_replace($block, "        ", 148, 8);
350                                 $checksum = 0;
351                                 for ($i = 0; $i < 512; $i++)
352                                         $checksum += ord(substr($block, $i, 1));
353                                 if ($file['checksum'] != $checksum) {
354                                         $this->error[] = "Could not extract from {$this->options['name']}, it is corrupt.";
355                                         break;
356                                 }
357                                 if ($dry_run || $this->options['inmemory'] == 1)
358                                 {
359                                         if ($file['type'] == 0) {
360                                                 if($dry_run) {
361                                                         $tst = check_write($file['name']);
362                                                         if (!$tst) {
363                                                                 $this->error[] = "Could not open {$this->options['basedir']}/{$file['name']} for writing.";
364                                                                 break;
365                                                         } else {
366                                                                 $flist[] = $file['name'];
367                                                                 if ( $tst < 0 && $this->options['overwrite'] == 0)
368                                                                         $this->error[] = "{$file['name']} already exists.";
369                                                         }
370                                                         fseek($fp, ($file['stat'][7] + 511) & ~511, SEEK_CUR); // skip data
371                                                 } else {
372                                                         $dat = fread($fp, $file['stat'][7]);
373                                                         $tail = $file['stat'][7] % 512;
374                                                         if ($tail)
375                                                                 fread($fp, 512 - $tail);
376                                                         $file['data'] = $dat;
377                                                 }
378                                         }
379                                         unset ($file['checksum'], $file['magic']);
380                                         $this->files[] = $file; 
381                                 }
382                                 else if ($file['type'] == 5)
383                                 {
384                                         if (!is_dir($file['name'])) {
385                                                 mkdir($file['name'], $file['stat'][2]);
386                                         }
387                                 }
388                                 else if ($this->options['overwrite'] == 0 && file_exists($file['name']))
389                                 {
390                                         $this->error[] = "{$file['name']} already exists.";
391                                         continue;
392                                 }
393                                 else if ($file['type'] == 2)
394                                 {
395                                         symlink($temp['symlink'], $file['name']);
396                                         @chmod($file['name'], $file['stat'][2]);
397                                 }
398                                 else if ($new = @fopen($file['name'], "wb"))
399                                 {
400                                         fwrite($new, fread($fp, $file['stat'][7]));
401                                         if ($file['stat'][7] % 512)
402                                                 fread($fp, 512 - $file['stat'][7] % 512);
403                                         fclose($new);
404                                         @chmod($file['name'], $file['stat'][2]);
405                                 }
406                                 else
407                                 {
408                                         $this->error[] = "Could not open {$file['name']} for writing.";
409                                         continue;
410                                 }
411 //                              @chown($file['name'], $file['stat'][4]);
412 //                              @chgrp($file['name'], $file['stat'][5]);
413                                 if (!$dry_run) {
414                                         @touch($file['name'], $file['stat'][9]);
415                                 }
416                                 unset ($file);
417                         }
418                         chdir($pwd);
419                 }
420                 else
421                         $this->error[] = "Could not open file {$this->options['name']}";
422
423                 return $flist;
424         }
425
426         function open_archive()
427         {
428                 return @fopen($this->options['name'], "rb");
429         }
430 }
431
432 class gzip_file extends tar_file
433 {
434         function __construct($name)
435         {
436                 parent::__construct($name);
437                 $this->options['type'] = "gzip";
438         }
439
440         function create_gzip()
441         {
442                 if ($this->options['inmemory'] == 0)
443                 {
444                         if ($fp = gzopen($this->options['name'], "wb{$this->options['level']}"))
445                         {
446                                 fseek($this->archive, 0);
447                                 while ($temp = fread($this->archive, 1048576))
448                                         gzwrite($fp, $temp);
449                                 gzclose($fp);
450                         }
451                         else
452                         {
453                                 $this->error[] = "Could not open {$this->options['name']} for writing.";
454                                 return 0;
455                         }
456                 }
457                 else
458                         $this->archive = gzencode($this->archive, $this->options['level']);
459
460                 return 1;
461         }
462
463         function open_archive()
464         {
465                 return @gzopen($this->options['name'], "rb");
466         }
467 }
468