updraftplus
Last commit date
addons
13 years ago
css
10 years ago
images
10 years ago
includes
10 years ago
languages
10 years ago
methods
10 years ago
vendor
10 years ago
admin.php
10 years ago
backup.php
10 years ago
class-updraftplus.php
10 years ago
class-zip.php
10 years ago
clean-composer.sh
10 years ago
composer.json
10 years ago
composer.lock
10 years ago
example-decrypt.php
10 years ago
index.html
10 years ago
options.php
10 years ago
readme.txt
10 years ago
restorer.php
10 years ago
updraftplus.php
10 years ago
class-zip.php
326 lines
| 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 | public 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; |
| 66 | if (defined('UPDRAFTPLUS_BINZIP_OPTS') && UPDRAFTPLUS_BINZIP_OPTS) $exec .= ' '.UPDRAFTPLUS_BINZIP_OPTS; |
| 67 | $exec .= " -v -@ ".escapeshellarg($this->path); |
| 68 | |
| 69 | $last_recorded_alive = time(); |
| 70 | $something_useful_happened = $updraftplus->something_useful_happened; |
| 71 | $orig_size = file_exists($this->path) ? filesize($this->path) : 0; |
| 72 | $last_size = $orig_size; |
| 73 | clearstatcache(); |
| 74 | |
| 75 | $added_dirs_yet = false; |
| 76 | |
| 77 | # If there are no files to add, but there are empty directories, then we need to make sure the directories actually get added |
| 78 | if (0 == count($this->addfiles) && 0 < count($this->adddirs)) { |
| 79 | $dir = realpath($updraftplus_backup->make_zipfile_source); |
| 80 | $this->addfiles[$dir] = '././.'; |
| 81 | } |
| 82 | // Loop over each destination directory name |
| 83 | foreach ($this->addfiles as $rdirname => $files) { |
| 84 | |
| 85 | $process = proc_open($exec, $descriptorspec, $pipes, $rdirname); |
| 86 | |
| 87 | if (!is_resource($process)) { |
| 88 | $updraftplus->log('BinZip error: proc_open failed'); |
| 89 | $this->last_error = 'BinZip error: proc_open failed'; |
| 90 | return false; |
| 91 | } |
| 92 | |
| 93 | if (!$added_dirs_yet) { |
| 94 | # Add the directories - (in fact, with binzip, non-empty directories automatically have their entries added; but it doesn't hurt to add them explicitly) |
| 95 | foreach ($this->adddirs as $dir) { |
| 96 | fwrite($pipes[0], $dir."/\n"); |
| 97 | } |
| 98 | $added_dirs_yet=true; |
| 99 | } |
| 100 | |
| 101 | $read = array($pipes[1], $pipes[2]); |
| 102 | $except = null; |
| 103 | |
| 104 | if (!is_array($files) || 0 == count($files)) { |
| 105 | fclose($pipes[0]); |
| 106 | $write = array(); |
| 107 | } else { |
| 108 | $write = array($pipes[0]); |
| 109 | } |
| 110 | |
| 111 | while ((!feof($pipes[1]) || !feof($pipes[2]) || (is_array($files) && count($files)>0)) && false !== ($changes = @stream_select($read, $write, $except, 0, 200000))) { |
| 112 | |
| 113 | if (is_array($write) && in_array($pipes[0], $write) && is_array($files) && count($files)>0) { |
| 114 | $file = array_pop($files); |
| 115 | // Send the list of files on stdin |
| 116 | fwrite($pipes[0], $file."\n"); |
| 117 | if (0 == count($files)) fclose($pipes[0]); |
| 118 | } |
| 119 | |
| 120 | if (is_array($read) && in_array($pipes[1], $read)) { |
| 121 | $w = fgets($pipes[1]); |
| 122 | // Logging all this really slows things down; use debug to mitigate |
| 123 | if ($w && $updraftplus_backup->debug) $updraftplus->log("Output from zip: ".trim($w), 'debug'); |
| 124 | if (time() > $last_recorded_alive + 5) { |
| 125 | $updraftplus->record_still_alive(); |
| 126 | $last_recorded_alive = time(); |
| 127 | } |
| 128 | if (file_exists($this->path)) { |
| 129 | $new_size = @filesize($this->path); |
| 130 | if (!$something_useful_happened && $new_size > $orig_size + 20) { |
| 131 | $updraftplus->something_useful_happened(); |
| 132 | $something_useful_happened = true; |
| 133 | } |
| 134 | clearstatcache(); |
| 135 | # Log when 20% bigger or at least every 50Mb |
| 136 | if ($new_size > $last_size*1.2 || $new_size > $last_size + 52428800) { |
| 137 | $updraftplus->log(basename($this->path).sprintf(": size is now: %.2f Mb", round($new_size/1048576,1))); |
| 138 | $last_size = $new_size; |
| 139 | } |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | if (is_array($read) && in_array($pipes[2], $read)) { |
| 144 | $last_error = fgets($pipes[2]); |
| 145 | if (!empty($last_error)) $this->last_error = rtrim($last_error); |
| 146 | } |
| 147 | |
| 148 | // Re-set |
| 149 | $read = array($pipes[1], $pipes[2]); |
| 150 | $write = (is_array($files) && count($files) >0) ? array($pipes[0]) : array(); |
| 151 | $except = null; |
| 152 | |
| 153 | } |
| 154 | |
| 155 | fclose($pipes[1]); |
| 156 | fclose($pipes[2]); |
| 157 | |
| 158 | $ret = proc_close($process); |
| 159 | |
| 160 | if ($ret != 0 && $ret != 12) { |
| 161 | if ($ret < 128) { |
| 162 | $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)"); |
| 163 | } else { |
| 164 | $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)"); |
| 165 | } |
| 166 | if (!empty($w) && !$updraftplus_backup->debug) $updraftplus->log("Last output from zip: ".trim($w), 'debug'); |
| 167 | return false; |
| 168 | } |
| 169 | |
| 170 | unset($this->addfiles[$rdirname]); |
| 171 | } |
| 172 | |
| 173 | return true; |
| 174 | } |
| 175 | |
| 176 | } |
| 177 | |
| 178 | # A ZipArchive compatibility layer, with behaviour sufficient for our usage of ZipArchive |
| 179 | class UpdraftPlus_PclZip { |
| 180 | |
| 181 | protected $pclzip; |
| 182 | protected $path; |
| 183 | protected $addfiles; |
| 184 | protected $adddirs; |
| 185 | private $statindex; |
| 186 | private $include_mtime = false; |
| 187 | public $last_error; |
| 188 | |
| 189 | public function __construct() { |
| 190 | $this->addfiles = array(); |
| 191 | $this->adddirs = array(); |
| 192 | // Put this in a non-backed-up, writeable location, to make sure that huge temporary files aren't created and then added to the backup - and that we have somewhere writable |
| 193 | global $updraftplus; |
| 194 | if (!defined('PCLZIP_TEMPORARY_DIR')) define('PCLZIP_TEMPORARY_DIR', trailingslashit($updraftplus->backups_dir_location())); |
| 195 | } |
| 196 | |
| 197 | # Used to include mtime in statindex (by default, not done - to save memory; probably a bit paranoid) |
| 198 | public function ud_include_mtime() { |
| 199 | $this->include_mtime = true; |
| 200 | } |
| 201 | |
| 202 | public function __get($name) { |
| 203 | if ($name != 'numFiles') return null; |
| 204 | |
| 205 | if (empty($this->pclzip)) return false; |
| 206 | |
| 207 | $statindex = $this->pclzip->listContent(); |
| 208 | |
| 209 | if (empty($statindex)) { |
| 210 | $this->statindex=array(); |
| 211 | return 0; |
| 212 | } |
| 213 | |
| 214 | $result = array(); |
| 215 | foreach ($statindex as $i => $file) { |
| 216 | if (!isset($statindex[$i]['folder']) || 0 == $statindex[$i]['folder']) { |
| 217 | $result[] = $file; |
| 218 | } |
| 219 | unset($statindex[$i]); |
| 220 | } |
| 221 | |
| 222 | $this->statindex=$result; |
| 223 | |
| 224 | return count($this->statindex); |
| 225 | |
| 226 | } |
| 227 | |
| 228 | public function statIndex($i) { |
| 229 | if (empty($this->statindex[$i])) return array('name' => null, 'size' => 0); |
| 230 | $v = array('name' => $this->statindex[$i]['filename'], 'size' => $this->statindex[$i]['size']); |
| 231 | if ($this->include_mtime) $v['mtime'] = $this->statindex[$i]['mtime']; |
| 232 | return $v; |
| 233 | } |
| 234 | |
| 235 | public function open($path, $flags = 0) { |
| 236 | if(!class_exists('PclZip')) include_once(ABSPATH.'/wp-admin/includes/class-pclzip.php'); |
| 237 | if(!class_exists('PclZip')) { |
| 238 | $this->last_error = "No PclZip class was found"; |
| 239 | return false; |
| 240 | } |
| 241 | |
| 242 | # Route around PHP bug (exact version with the problem not known) |
| 243 | $ziparchive_create_match = (version_compare(PHP_VERSION, '5.2.12', '>') && defined('ZIPARCHIVE::CREATE')) ? ZIPARCHIVE::CREATE : 1; |
| 244 | |
| 245 | if ($flags == $ziparchive_create_match && file_exists($path)) @unlink($path); |
| 246 | |
| 247 | $this->pclzip = new PclZip($path); |
| 248 | if (empty($this->pclzip)) { |
| 249 | $this->last_error = 'Could not get a PclZip object'; |
| 250 | return false; |
| 251 | } |
| 252 | |
| 253 | # Make the empty directory we need to implement addEmptyDir() |
| 254 | global $updraftplus; |
| 255 | $updraft_dir = $updraftplus->backups_dir_location(); |
| 256 | if (!is_dir($updraft_dir.'/emptydir') && !mkdir($updraft_dir.'/emptydir')) { |
| 257 | $this->last_error = "Could not create empty directory ($updraft_dir/emptydir)"; |
| 258 | return false; |
| 259 | } |
| 260 | |
| 261 | $this->path = $path; |
| 262 | |
| 263 | return true; |
| 264 | |
| 265 | } |
| 266 | |
| 267 | # Do the actual write-out - it is assumed that close() is where this is done. Needs to return true/false |
| 268 | public function close() { |
| 269 | if (empty($this->pclzip)) { |
| 270 | $this->last_error = 'Zip file was not opened'; |
| 271 | return false; |
| 272 | } |
| 273 | |
| 274 | global $updraftplus; |
| 275 | $updraft_dir = $updraftplus->backups_dir_location(); |
| 276 | |
| 277 | $activity = false; |
| 278 | |
| 279 | # Add the empty directories |
| 280 | foreach ($this->adddirs as $dir) { |
| 281 | if (false == $this->pclzip->add($updraft_dir.'/emptydir', PCLZIP_OPT_REMOVE_PATH, $updraft_dir.'/emptydir', PCLZIP_OPT_ADD_PATH, $dir)) { |
| 282 | $this->last_error = $this->pclzip->errorInfo(true); |
| 283 | return false; |
| 284 | } |
| 285 | $activity = true; |
| 286 | } |
| 287 | |
| 288 | foreach ($this->addfiles as $rdirname => $adirnames) { |
| 289 | foreach ($adirnames as $adirname => $files) { |
| 290 | if (false == $this->pclzip->add($files, PCLZIP_OPT_REMOVE_PATH, $rdirname, PCLZIP_OPT_ADD_PATH, $adirname)) { |
| 291 | $this->last_error = $this->pclzip->errorInfo(true); |
| 292 | return false; |
| 293 | } |
| 294 | $activity = true; |
| 295 | } |
| 296 | unset($this->addfiles[$rdirname]); |
| 297 | } |
| 298 | |
| 299 | $this->pclzip = false; |
| 300 | $this->addfiles = array(); |
| 301 | $this->adddirs = array(); |
| 302 | |
| 303 | clearstatcache(); |
| 304 | if ($activity && filesize($this->path) < 50) { |
| 305 | $this->last_error = "Write failed - unknown cause (check your file permissions)"; |
| 306 | return false; |
| 307 | } |
| 308 | |
| 309 | return true; |
| 310 | } |
| 311 | |
| 312 | # 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. |
| 313 | public function addFile($file, $add_as) { |
| 314 | # 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). |
| 315 | $rdirname = dirname($file); |
| 316 | $adirname = dirname($add_as); |
| 317 | $this->addfiles[$rdirname][$adirname][] = $file; |
| 318 | } |
| 319 | |
| 320 | # PclZip doesn't have a direct way to do this |
| 321 | public function addEmptyDir($dir) { |
| 322 | $this->adddirs[] = $dir; |
| 323 | } |
| 324 | |
| 325 | } |
| 326 |