PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 2.1.7
Backup Migration v2.1.7
2.1.7 2.1.6 2.1.5.2 trunk 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.6.1 1.4.7 1.4.8 1.4.9 1.4.9.1 2.0.0 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.5.1
backup-backup / includes / zipper / zipping.php
backup-backup / includes / zipper Last commit date
src 2 weeks ago zipping.php 2 weeks ago
zipping.php
477 lines
1 <?php
2
3 // Namespace
4 namespace BMI\Plugin\Zipper;
5
6 // Use
7 use BMI\Plugin\Backup_Migration_Plugin as BMP;
8 use BMI\Plugin\BMI_Logger as Logger;
9 use BMI\Plugin\Progress\BMI_ZipProgress as Progress;
10 use BMI\Plugin\BMI_Zip_Explorer as ZipExplorer;
11 use BMI\Plugin\Scanner\BMI_BackupsScanner as Backups;
12
13 // Exit on direct access
14 if (!defined('ABSPATH')) {
15 exit;
16 }
17
18 /**
19 * BMI_Zipper
20 */
21 class BMI_Zipper {
22 public function makeZIP($files, $output, $name, &$zip_progress, $cron = false) {
23
24 // Verbose
25 Logger::log(__("Creating backup ", 'backup-backup'));
26 Logger::log(__("Found ", 'backup-backup') . sizeof($files) . __(" files to backup.", 'backup-backup'));
27
28 // Require Universal Zip Library
29 require_once BMI_INCLUDES . '/zipper/src/zip.php';
30
31 // Start microtime for ZIP Process
32 $start = microtime(true);
33
34 // Logs
35 $zip_progress->log(__("Preparing map of files...", 'backup-backup'), 'step');
36
37 // Try to catch error
38 try {
39
40 // Create new ZIP
41 $zip = new Zip();
42 $zip->zip_start($output, $files, $name, $zip_progress, $start);
43
44 // Logs
45 $zip_progress->log(__("Files prepared.", 'backup-backup'), 'success');
46 $zip_progress->log(__("Starting compression process...", 'backup-backup'), 'info');
47
48 // Close ZIP and Save
49 $lala = $zip->zip_end(2, $cron);
50 if (!$lala) {
51 $zip_progress->log(__("Something went wrong – removing backup files...", 'backup-backup'), 'error');
52
53 return false;
54 }
55
56 return $lala;
57 } catch (\Throwable $e) {
58
59 // Error print
60 $zip_progress->log(__("Reverting backup, removing file...", 'backup-backup'), 'step');
61 $zip_progress->log(__("There was an error during backup...", 'backup-backup'), 'error');
62 $zip_progress->log($e->getMessage(), 'error');
63
64 return false;
65 } catch (\Exception $e) {
66
67 // Error print
68 $zip_progress->log(__("Reverting backup, removing file...", 'backup-backup'), 'step');
69 $zip_progress->log(__("There was an error during backup...", 'backup-backup'), 'error');
70 $zip_progress->log($e->getMessage(), 'error');
71
72 return false;
73 }
74
75 return true;
76 }
77
78 public function getZipFileContent($zipname, $filename) {
79 if (class_exists('ZipArchive') || class_exists('\ZipArchive')) {
80 $zip = new \ZipArchive();
81
82 if ($zip->open($zipname) === true) {
83 if ($content = $zip->getFromName($filename)) {
84 return json_decode($content);
85 } else {
86 return false;
87 }
88 } else {
89 return false;
90 }
91 } else {
92 if (!class_exists('PclZip')) {
93 if (!defined('PCLZIP_TEMPORARY_DIR')) {
94 $bmi_tmp_dir = BMI_TMP;
95 if (!file_exists($bmi_tmp_dir)) {
96 @mkdir($bmi_tmp_dir, 0775, true);
97 }
98 define('PCLZIP_TEMPORARY_DIR', $bmi_tmp_dir . DIRECTORY_SEPARATOR . 'bmi-');
99 }
100 if (defined('BMI_PRO_PCLZIP') && file_exists(BMI_PRO_PCLZIP)) {
101 require_once BMI_PRO_PCLZIP;
102 } else {
103 require_once trailingslashit(ABSPATH) . 'wp-admin/includes/class-pclzip.php';
104 }
105 }
106 $lib = new \PclZip($zipname);
107 $content = $lib->extract(PCLZIP_OPT_BY_NAME, $filename, PCLZIP_OPT_EXTRACT_AS_STRING);
108 if (isset($content[0]) && isset($content[0]['content'])) {
109 return json_decode($content[0]['content']);
110 } else {
111 return false;
112 }
113 }
114 }
115
116 public function getZipContentList($zippath, $savepath) {
117
118 if (class_exists('ZipArchive') || class_exists('\ZipArchive')) {
119
120 $zip = new \ZipArchive();
121 $zip->open($zippath);
122
123 if (!isset($zip->numFiles) || $zip->numFiles == 0 || $zip->numFiles === false) {
124 $zip->close();
125 return false;
126 }
127
128 $tmpf = fopen($savepath, 'a+');
129 $totalAmount = $zip->numFiles;
130
131 for ($i = 0; $i < $zip->numFiles; ++$i) {
132
133 $stat = $zip->statIndex($i);
134 fwrite($tmpf, $stat['name'] . "\n");
135 unset($stat);
136
137 }
138
139 fclose($tmpf);
140 $zip->close();
141
142 return $totalAmount;
143
144 } else {
145
146 if (!class_exists('PclZip')) {
147 if (!defined('PCLZIP_TEMPORARY_DIR')) {
148 $bmi_tmp_dir = BMI_TMP;
149 if (!file_exists($bmi_tmp_dir)) {
150 @mkdir($bmi_tmp_dir, 0775, true);
151 }
152 define('PCLZIP_TEMPORARY_DIR', $bmi_tmp_dir . DIRECTORY_SEPARATOR . 'bmi-');
153 }
154 if (defined('BMI_PRO_PCLZIP') && file_exists(BMI_PRO_PCLZIP)) {
155 require_once BMI_PRO_PCLZIP;
156 } else {
157 require_once trailingslashit(ABSPATH) . 'wp-admin/includes/class-pclzip.php';
158 }
159 }
160
161 $zip = new \PclZip($zippath);
162 $list = $zip->listContent();
163 if ($list == 0) {
164 return false;
165 }
166
167 $tmpf = fopen($savepath, 'a+');
168
169 $totalAmount = sizeof($list);
170 for ($i = 0; $i < $totalAmount; ++$i) {
171
172 fwrite($tmpf, $list[$i]['filename'] . "\n");
173
174 }
175
176 fclose($tmpf);
177
178 return $totalAmount;
179
180 }
181
182 }
183
184 public function getPartsToRestore($zippath, $savepath) {
185 if (!defined('BMI_PRO_INC') || !file_exists(BMI_PRO_INC . '/zip-explorer.php')) {
186 return false;
187 }
188
189 require_once BMI_PRO_INC . '/zip-explorer.php';
190
191 $restorePartsFile = BMI_TMP . DIRECTORY_SEPARATOR . 'restore_parts.json';
192 if (!file_exists($restorePartsFile)) {
193 return false;
194 }
195
196 $restorePartsContent = json_decode(file_get_contents($restorePartsFile), true);
197 if (!$restorePartsContent || !isset($restorePartsContent['backupName'])) {
198 return false;
199 }
200
201 if ($restorePartsContent['backupName'] !== basename($zippath)) {
202 Logger::log('Ignoring restore parts file, because it does not match the backup name.');
203 @unlink($restorePartsFile);
204 return false;
205 }
206
207 $zipExplorer = new ZipExplorer($zippath);
208 $amount = 0;
209 if (isset($restorePartsContent['dirs']) && is_array($restorePartsContent['dirs'])) {
210 foreach ($restorePartsContent['dirs'] as $dir) {
211 $amount += $zipExplorer->scanDir($dir, $savepath);
212 }
213
214 }
215
216 if (isset($restorePartsContent['files']) && is_array($restorePartsContent['files'])) {
217 $amount += sizeof($restorePartsContent['files']);
218 $files = implode("\n", $restorePartsContent['files']);
219 file_put_contents($savepath, $files, FILE_APPEND);
220 }
221
222 file_put_contents($savepath, "\nbmi_backup_manifest.json", FILE_APPEND);
223
224
225 return $amount;
226 }
227
228 public function getZipFileContentPlain($zipname, $filename) {
229 if (class_exists('ZipArchive')) {
230 $zip = new \ZipArchive();
231
232 if ($zip->open($zipname) === true) {
233 if ($content = $zip->getFromName($filename)) {
234 return $content;
235 } else {
236 return false;
237 }
238 } else {
239 return false;
240 }
241 } else {
242 if (!class_exists('PclZip')) {
243 if (!defined('PCLZIP_TEMPORARY_DIR')) {
244 $bmi_tmp_dir = BMI_TMP;
245 if (!file_exists($bmi_tmp_dir)) {
246 @mkdir($bmi_tmp_dir, 0775, true);
247 }
248 define('PCLZIP_TEMPORARY_DIR', $bmi_tmp_dir . DIRECTORY_SEPARATOR . 'bmi-');
249 }
250 if (defined('BMI_PRO_PCLZIP') && file_exists(BMI_PRO_PCLZIP)) {
251 require_once BMI_PRO_PCLZIP;
252 } else {
253 require_once trailingslashit(ABSPATH) . 'wp-admin/includes/class-pclzip.php';
254 }
255 }
256 $lib = new \PclZip($zipname);
257
258 $content = $lib->extract(PCLZIP_OPT_BY_NAME, $filename, PCLZIP_OPT_EXTRACT_AS_STRING);
259 if (sizeof($content) > 0) {
260 return $content[0]['content'];
261 } else {
262 return false;
263 }
264 }
265 }
266
267 public function lock_zip($zippath, $unlock = false) {
268
269 // Require Universal Zip Library
270 require_once BMI_INCLUDES . '/zipper/src/zip.php';
271 require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'scanner' . DIRECTORY_SEPARATOR . 'backups.php';
272
273 try {
274
275 // Path to lock file
276 $filename = '.lock';
277
278 // Load lib
279 if (!class_exists('PclZip')) {
280 if (!defined('PCLZIP_TEMPORARY_DIR')) {
281 $bmi_tmp_dir = BMI_TMP;
282 if (!file_exists($bmi_tmp_dir)) {
283 @mkdir($bmi_tmp_dir, 0775, true);
284 }
285 define('PCLZIP_TEMPORARY_DIR', $bmi_tmp_dir . DIRECTORY_SEPARATOR . 'bmi-');
286 }
287 if (defined('BMI_PRO_PCLZIP') && file_exists(BMI_PRO_PCLZIP)) {
288 require_once BMI_PRO_PCLZIP;
289 } else {
290 require_once trailingslashit(ABSPATH) . 'wp-admin/includes/class-pclzip.php';
291 }
292 }
293 $lib = new \PclZip($zippath);
294 $backups = Backups::getInstance();
295 $backups->deleteBackupManifest(basename($zippath));
296
297 // Unlocking case
298 if ($unlock) {
299 if ($this->is_locked_zip($zippath)) {
300 $lib->delete(PCLZIP_OPT_BY_NAME, $filename);
301 } else {
302 return true;
303 }
304 } else {
305 if (!$this->is_locked_zip($zippath)) {
306
307 // Locking case
308 $content = json_encode(['locked' => 'true']);
309 $lib->add([[PCLZIP_ATT_FILE_NAME => $filename, PCLZIP_ATT_FILE_CONTENT => $content]]);
310 }
311 }
312
313 return true;
314 } catch (\Exception $e) {
315 Logger::error($e);
316
317 return false;
318 } catch (\Throwable $e) {
319 Logger::error($e);
320
321 return false;
322 }
323 }
324
325 public function is_locked_zip($zippath) {
326 $lock = $this->getZipFileContent($zippath, '.lock');
327 if ($lock) {
328 if (isset($lock->locked) && $lock->locked == 'true') {
329 return true;
330 } else {
331 return false;
332 }
333 } else {
334 return false;
335 }
336 }
337
338 /**
339 * Checks if a ZIP archive is protected with a password/encrypted.
340 *
341 * @param string $filepath Absolute path to the ZIP file.
342 * @return bool True if protected/encrypted, false otherwise.
343 */
344 public static function isZipProtected($filepath) {
345 if (!file_exists($filepath) || !is_readable($filepath)) {
346 return false;
347 }
348
349 if (class_exists('ZipArchive')) {
350 $zip = new \ZipArchive();
351 if ($zip->open($filepath) === true) {
352 $isProtected = false;
353
354 for ($i = 0; $i < $zip->numFiles; $i++) {
355 $stat = $zip->statIndex($i);
356 // 'encryption_method' was introduced in PHP 7.2 / libzip 1.2.0
357 if (isset($stat['encryption_method']) && $stat['encryption_method'] !== \ZipArchive::EM_NONE) {
358 $isProtected = true;
359 break;
360 }
361 }
362 $zip->close();
363
364 if ($isProtected) {
365 return true;
366 }
367 }
368 }
369 return false;
370 }
371
372 /**
373 * Validates a password for a given ZIP file path, safely ignoring empty files.
374 *
375 * @param string $zipPath The absolute or relative path to the ZIP file.
376 * @param string $password The password to test.
377 * @return bool True if the password is correct, false otherwise.
378 * @throws \Exception If the file cannot be opened or lacks testable files.
379 */
380 public static function validateZipPassword(string $zipPath, string $password): bool {
381 $zip = new \ZipArchive();
382
383 $res = $zip->open($zipPath);
384 if ($res !== true) {
385 throw new \Exception("Failed to open ZIP file. Error Code: " . $res);
386 }
387
388 $zip->setPassword($password);
389
390 if ($zip->locateName('.backup_name') !== false) {
391 $stat = $zip->statName('.backup_name');
392 if ($stat && isset($stat['encryption_method']) && $stat['encryption_method'] !== \ZipArchive::EM_NONE) {
393 $content = $zip->getFromName('.backup_name');
394 $zip->close();
395 if ($content !== false && trim($content) === basename($zipPath)) {
396 return true;
397 }
398 return false;
399 }
400 }
401
402 $minFileSize = PHP_INT_MAX;
403 $minFileIndex = -1;
404
405 for ($i = 0; $i < $zip->numFiles; $i++) {
406 $stat = $zip->statIndex($i);
407
408 if ($stat === false || $stat['size'] === 0) {
409 continue;
410 }
411
412 $isEncrypted = isset($stat['encryption_method']) && $stat['encryption_method'] !== \ZipArchive::EM_NONE;
413
414 if ($isEncrypted && $stat['size'] > 0) {
415 if ($stat['size'] < $minFileSize) {
416 $minFileSize = $stat['size'];
417 $minFileIndex = $i;
418 if ($minFileSize < 5 * 1024 * 1024) {
419 break;
420 }
421 }
422 }
423 }
424
425 if ($minFileIndex !== -1) {
426 $content = $zip->getFromIndex($minFileIndex);
427 $zip->close();
428 if ($content === false || strlen($content) === 0) {
429 return false;
430 }
431 return true;
432 }
433
434 $zip->close();
435 throw new \Exception("ZIP archive contains no non-empty encrypted files. Cannot validate password.");
436 }
437
438 public function isFileExists($zippath, $filename) {
439 if (class_exists('ZipArchive')) {
440 $zip = new \ZipArchive();
441
442 if ($zip->open($zippath) === true) {
443 if ($zip->locateName($filename) !== false) {
444 return true;
445 } else {
446 return false;
447 }
448 } else {
449 return false;
450 }
451 } else {
452 if (!class_exists('PclZip')) {
453 if (!defined('PCLZIP_TEMPORARY_DIR')) {
454 $bmi_tmp_dir = BMI_TMP;
455 if (!file_exists($bmi_tmp_dir)) {
456 @mkdir($bmi_tmp_dir, 0775, true);
457 }
458 define('PCLZIP_TEMPORARY_DIR', $bmi_tmp_dir . DIRECTORY_SEPARATOR . 'bmi-');
459 }
460 if (defined('BMI_PRO_PCLZIP') && file_exists(BMI_PRO_PCLZIP)) {
461 require_once BMI_PRO_PCLZIP;
462 } else {
463 require_once trailingslashit(ABSPATH) . 'wp-admin/includes/class-pclzip.php';
464 }
465 }
466 $lib = new \PclZip($zippath);
467
468 $content = $lib->extract(PCLZIP_OPT_BY_NAME, $filename, PCLZIP_OPT_EXTRACT_AS_STRING);
469 if (sizeof($content) > 0) {
470 return true;
471 } else {
472 return false;
473 }
474 }
475 }
476 }
477