PluginProbe
UpdraftPlus: WP Backup & Migration Plugin / 1.9.31
UpdraftPlus: WP Backup & Migration Plugin v1.9.31
1.26.7 1.26.6 1.26.5 1.26.4 1.26.3 1.9.19 1.9.25 1.9.26 1.9.30 1.9.31 1.9.32 1.9.4 1.9.40 1.9.41 1.9.42 1.9.43 1.9.44 1.9.45 1.9.46 1.9.5 1.9.50 1.9.51 1.9.60 1.9.62 1.9.63 All 371 releases
updraftplus / class-zip.php

class-zip.php in UpdraftPlus: WP Backup & Migration Plugin 1.9.31, at class-zip.php

320 lines 10.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined ('ABSPATH')) die('No direct access allowed');
4
5 if (class_exists('ZipArchive')):
6 # We just add a last_error variable for comaptibility with our UpdraftPlus_PclZip object
7 class UpdraftPlus_ZipArchive extends ZipArchive {
8 public $last_error = 'Unknown: ZipArchive does not return error messages';
9 }
10 endif;
11
12 class UpdraftPlus_BinZip extends UpdraftPlus_PclZip {
13
14 private $binzip;
15
16 function __construct() {
17 global $updraftplus_backup;
18 $this->binzip = $updraftplus_backup->binzip;
19 if (!is_string($this->binzip)) {
20 $this->last_error = "No binary zip was found";
21 return false;
22 }
23 return parent::__construct();
24 }
25
26 public function addFile($file, $add_as) {
27
28 global $updraftplus;
29 # Get the directory that $add_as is relative to
30 $base = $updraftplus->str_lreplace($add_as, '', $file);
31
32 if ($file == $base) {
33 // Shouldn't happen; but see: https://bugs.php.net/bug.php?id=62119
34 $updraftplus->log("File skipped due to unexpected name mismatch (locale: ".setlocale(LC_CTYPE, "0")."): $file", 'notice', false, true);
35 } else {
36 $rdirname = untrailingslashit($base);
37 # Note: $file equals $rdirname/$add_as
38 $this->addfiles[$rdirname][] = $add_as;
39 }
40
41 }
42
43 # The standard zip binary cannot list; so we use PclZip for that
44 # Do the actual write-out - it is assumed that close() is where this is done. Needs to return true/false
45 public function close() {
46
47 if (empty($this->pclzip)) {
48 $this->last_error = 'Zip file was not opened';
49 return false;
50 }
51
52 global $updraftplus, $updraftplus_backup;
53 $updraft_dir = $updraftplus->backups_dir_location();
54
55 $activity = false;
56
57 # BinZip does not like zero-sized zip files
58 if (file_exists($this->path) && 0 == filesize($this->path)) @unlink($this->path);
59
60 $descriptorspec = array(
61 0 => array('pipe', 'r'),
62 1 => array('pipe', 'w'),
63 2 => array('pipe', 'w')
64 );
65 $exec = $this->binzip." -v -@ ".escapeshellarg($this->path);
66 $last_recorded_alive = time();
67 $something_useful_happened = $updraftplus->something_useful_happened;
68 $orig_size = file_exists($this->path) ? filesize($this->path) : 0;
69 $last_size = $orig_size;
70 clearstatcache();
71
72 $added_dirs_yet = false;
73
74 # If there are no files to add, but there are empty directories, then we need to make sure the directories actually get added
75 if (0 == count($this->addfiles) && 0 < count($this->adddirs)) {
76 $dir = realpath($updraftplus_backup->make_zipfile_source);
77 $this->addfiles[$dir] = '././.';
78 }
79 // Loop over each destination directory name
80 foreach ($this->addfiles as $rdirname => $files) {
81
82 $process = proc_open($exec, $descriptorspec, $pipes, $rdirname);
83
84 if (!is_resource($process)) {
85 $updraftplus->log('BinZip error: proc_open failed');
86 $this->last_error = 'BinZip error: proc_open failed';
87 return false;
88 }
89
90 if (!$added_dirs_yet) {
91 # Add the directories - (in fact, with binzip, non-empty directories automatically have their entries added; but it doesn't hurt to add them explicitly)
92 foreach ($this->adddirs as $dir) {
93 fwrite($pipes[0], $dir."/\n");
94 }
95 $added_dirs_yet=true;
96 }
97
98 $read = array($pipes[1], $pipes[2]);
99 $except = null;
100
101 if (!is_array($files) || 0 == count($files)) {
102 fclose($pipes[0]);
103 $write = array();
104 } else {
105 $write = array($pipes[0]);
106 }
107
108 while ((!feof($pipes[1]) || !feof($pipes[2]) || (is_array($files) && count($files)>0)) && false !== ($changes = @stream_select($read, $write, $except, 0, 200000))) {
109
110 if (is_array($write) && in_array($pipes[0], $write) && is_array($files) && count($files)>0) {
111 $file = array_pop($files);
112 // Send the list of files on stdin
113 fwrite($pipes[0], $file."\n");
114 if (0 == count($files)) fclose($pipes[0]);
115 }
116
117 if (is_array($read) && in_array($pipes[1], $read)) {
118 $w = fgets($pipes[1]);
119 // Logging all this really slows things down; use debug to mitigate
120 if ($w && $updraftplus_backup->debug) $updraftplus->log("Output from zip: ".trim($w), 'debug');
121 if (time() > $last_recorded_alive + 5) {
122 $updraftplus->record_still_alive();
123 $last_recorded_alive = time();
124 }
125 if (file_exists($this->path)) {
126 $new_size = @filesize($this->path);
127 if (!$something_useful_happened && $new_size > $orig_size + 20) {
128 $updraftplus->something_useful_happened();
129 $something_useful_happened = true;
130 }
131 clearstatcache();
132 # Log when 20% bigger or at least every 50Mb
133 if ($new_size > $last_size*1.2 || $new_size > $last_size + 52428800) {
134 $updraftplus->log(basename($this->path).sprintf(": size is now: %.2f Mb", round($new_size/1048576,1)));
135 $last_size = $new_size;
136 }
137 }
138 }
139
140 if (is_array($read) && in_array($pipes[2], $read)) {
141 $last_error = fgets($pipes[2]);
142 if (!empty($last_error)) $this->last_error = rtrim($last_error);
143 }
144
145 // Re-set
146 $read = array($pipes[1], $pipes[2]);
147 $write = (is_array($files) && count($files) >0) ? array($pipes[0]) : array();
148 $except = null;
149
150 }
151
152 fclose($pipes[1]);
153 fclose($pipes[2]);
154
155 $ret = proc_close($process);
156
157 if ($ret != 0 && $ret != 12) {
158 if ($ret < 128) {
159 $updraftplus->log("Binary zip: error (code: $ret - look it up in the Diagnostics section of the zip manual at http://www.info-zip.org/mans/zip.html for interpretation... and also check that your hosting account quota is not full)");
160 } else {
161 $updraftplus->log("Binary zip: error (code: $ret - a code above 127 normally means that the zip process was deliberately killed ... and also check that your hosting account quota is not full)");
162 }
163 if (!empty($w) && !$updraftplus_backup->debug) $updraftplus->log("Last output from zip: ".trim($w), 'debug');
164 return false;
165 }
166
167 unset($this->addfiles[$rdirname]);
168 }
169
170 return true;
171 }
172
173 }
174
175 # A ZipArchive compatibility layer, with behaviour sufficient for our usage of ZipArchive
176 class UpdraftPlus_PclZip {
177
178 protected $pclzip;
179 protected $path;
180 protected $addfiles;
181 protected $adddirs;
182 private $statindex;
183 private $include_mtime = false;
184 public $last_error;
185
186 public function __construct() {
187 $this->addfiles = array();
188 $this->adddirs = array();
189 }
190
191 # Used to include mtime in statindex (by default, not done - to save memory; probably a bit paranoid)
192 public function ud_include_mtime() {
193 $this->include_mtime = true;
194 }
195
196 public function __get($name) {
197 if ($name != 'numFiles') return null;
198
199 if (empty($this->pclzip)) return false;
200
201 $statindex = $this->pclzip->listContent();
202
203 if (empty($statindex)) {
204 $this->statindex=array();
205 return 0;
206 }
207
208 $result = array();
209 foreach ($statindex as $i => $file) {
210 if (!isset($statindex[$i]['folder']) || 0 == $statindex[$i]['folder']) {
211 $result[] = $file;
212 }
213 unset($statindex[$i]);
214 }
215
216 $this->statindex=$result;
217
218 return count($this->statindex);
219
220 }
221
222 public function statIndex($i) {
223 if (empty($this->statindex[$i])) return array('name' => null, 'size' => 0);
224 $v = array('name' => $this->statindex[$i]['filename'], 'size' => $this->statindex[$i]['size']);
225 if ($this->include_mtime) $v['mtime'] = $this->statindex[$i]['mtime'];
226 return $v;
227 }
228
229 public function open($path, $flags = 0) {
230 if(!class_exists('PclZip')) include_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
231 if(!class_exists('PclZip')) {
232 $this->last_error = "No PclZip class was found";
233 return false;
234 }
235
236 # Route around PHP bug (exact version with the problem not known)
237 $ziparchive_create_match = (version_compare(PHP_VERSION, '5.2.6', '>') && defined('ZIPARCHIVE::CREATE')) ? ZIPARCHIVE::CREATE : 1;
238
239 if ($flags == $ziparchive_create_match && file_exists($path)) @unlink($path);
240
241 $this->pclzip = new PclZip($path);
242 if (empty($this->pclzip)) {
243 $this->last_error = 'Could not get a PclZip object';
244 return false;
245 }
246
247 # Make the empty directory we need to implement addEmptyDir()
248 global $updraftplus;
249 $updraft_dir = $updraftplus->backups_dir_location();
250 if (!is_dir($updraft_dir.'/emptydir') && !mkdir($updraft_dir.'/emptydir')) {
251 $this->last_error = "Could not create empty directory ($updraft_dir/emptydir)";
252 return false;
253 }
254
255 $this->path = $path;
256
257 return true;
258
259 }
260
261 # Do the actual write-out - it is assumed that close() is where this is done. Needs to return true/false
262 public function close() {
263 if (empty($this->pclzip)) {
264 $this->last_error = 'Zip file was not opened';
265 return false;
266 }
267
268 global $updraftplus;
269 $updraft_dir = $updraftplus->backups_dir_location();
270
271 $activity = false;
272
273 # Add the empty directories
274 foreach ($this->adddirs as $dir) {
275 if (false == $this->pclzip->add($updraft_dir.'/emptydir', PCLZIP_OPT_REMOVE_PATH, $updraft_dir.'/emptydir', PCLZIP_OPT_ADD_PATH, $dir)) {
276 $this->last_error = $this->pclzip->errorInfo(true);
277 return false;
278 }
279 $activity = true;
280 }
281
282 foreach ($this->addfiles as $rdirname => $adirnames) {
283 foreach ($adirnames as $adirname => $files) {
284 if (false == $this->pclzip->add($files, PCLZIP_OPT_REMOVE_PATH, $rdirname, PCLZIP_OPT_ADD_PATH, $adirname)) {
285 $this->last_error = $this->pclzip->errorInfo(true);
286 return false;
287 }
288 $activity = true;
289 }
290 unset($this->addfiles[$rdirname]);
291 }
292
293 $this->pclzip = false;
294 $this->addfiles = array();
295 $this->adddirs = array();
296
297 clearstatcache();
298 if ($activity && filesize($this->path) < 50) {
299 $this->last_error = "Write failed - unknown cause (check your file permissions)";
300 return false;
301 }
302
303 return true;
304 }
305
306 # Note: basename($add_as) is irrelevant; that is, it is actually basename($file) that will be used. But these are always identical in our usage.
307 public function addFile($file, $add_as) {
308 # Add the files. PclZip appears to do the whole (copy zip to temporary file, add file, move file) cycle for each file - so batch them as much as possible. We have to batch by dirname(). On a test with 1000 files of 25Kb each in the same directory, this reduced the time needed on that directory from 120s to 15s (or 5s with primed caches).
309 $rdirname = dirname($file);
310 $adirname = dirname($add_as);
311 $this->addfiles[$rdirname][$adirname][] = $file;
312 }
313
314 # PclZip doesn't have a direct way to do this
315 public function addEmptyDir($dir) {
316 $this->adddirs[] = $dir;
317 }
318
319 }
320