XCloner – Backup and Restore - Version 4.1.4

Version Description

  • thinkovi references replace
  • plugin pre auto update text changelog
  • author name and uri change
  • standalone library addon support
Download this release

Release Info

Developer watchful
Plugin Icon 128x128 XCloner – Backup and Restore
Version 4.1.4
Comparing to
See all releases

Code changes from version 4.1.4b to 4.1.4

Files changed (54) hide show
  1. README.md +1 -1
  2. README.txt +277 -36
  3. admin/class-xcloner-admin.php +1 -1
  4. admin/partials/xcloner_generate_backups_page.php +2 -2
  5. admin/partials/xcloner_init_page.php +1 -1
  6. api/api_test.html +129 -0
  7. api/expose_api.php +50 -0
  8. api/index.html +0 -0
  9. api/standalone_config.json +55 -0
  10. composer.lock +12 -12
  11. examples/XCloner.postman_collection.json +288 -0
  12. examples/cli_encrypt_backup.php +64 -0
  13. examples/index.html +0 -0
  14. examples/standalone-backup-trigger.php +16 -0
  15. examples/standalone_backup_trigger_config.json +54 -0
  16. includes/class-xcloner-activator.php +95 -96
  17. includes/class-xcloner-api.php +1152 -1157
  18. includes/class-xcloner-archive.php +693 -583
  19. includes/class-xcloner-database.php +641 -614
  20. includes/class-xcloner-deactivator.php +1 -1
  21. includes/class-xcloner-encryption.php +357 -359
  22. includes/class-xcloner-file-system.php +193 -199
  23. includes/class-xcloner-loader.php +0 -40
  24. includes/class-xcloner-logger.php +116 -115
  25. includes/class-xcloner-remote-storage.php +109 -91
  26. includes/class-xcloner-sanitization.php +114 -54
  27. includes/class-xcloner-scheduler.php +450 -433
  28. includes/class-xcloner-settings.php +1010 -808
  29. includes/class-xcloner-standalone.php +100 -0
  30. includes/class-xcloner.php +660 -581
  31. lib/class-wp-error.php +226 -0
  32. lib/index.html +0 -0
  33. lib/mock_wp_functions.php +452 -0
  34. lib/wp-db.php +3555 -0
  35. public/class-xcloner-public.php +1 -1
  36. public/partials/xcloner-public-display.php +1 -1
  37. uninstall.php +1 -1
  38. vendor/composer/autoload_namespaces.php +1 -1
  39. vendor/composer/autoload_static.php +1 -1
  40. vendor/composer/installed.json +14 -14
  41. vendor/splitbrain/php-archive/.gitignore +8 -0
  42. vendor/splitbrain/php-archive/src/Archive.php +16 -13
  43. vendor/splitbrain/php-archive/src/ArchiveCorruptedException.php +10 -0
  44. vendor/splitbrain/php-archive/src/ArchiveIOException.php +10 -0
  45. vendor/splitbrain/php-archive/src/ArchiveIllegalCompressionException.php +10 -0
  46. vendor/splitbrain/php-archive/src/FileInfo.php +4 -19
  47. vendor/splitbrain/php-archive/src/FileInfoException.php +10 -0
  48. vendor/splitbrain/php-archive/src/Tar.php +65 -217
  49. vendor/splitbrain/php-archive/src/Zip.php +19 -3
  50. vendor/splitbrain/php-archive/tests/FileInfoTest.php +3 -4
  51. vendor/splitbrain/php-archive/tests/TarTestCase.php +42 -17
  52. vendor/splitbrain/php-archive/tests/ZipTestCase.php +87 -69
  53. vendor/splitbrain/php-archive/tests/tar.test.php +0 -617
  54. xcloner.php +5 -5
README.md CHANGED
@@ -1,6 +1,6 @@
1
  # XCloner Wordpress Plugin - Backup and Restore
2
 
3
- [![Author](http://img.shields.io/badge/author-@thinkovi-blue.svg?style=flat-square)](https://twitter.com/thinkovi)
4
  [![Software License](https://img.shields.io/badge/license-GPL-brightgreen.svg?style=flat-square)](LICENSE.txt)
5
  [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/ovidiul/XCloner-Wordpress/badges/quality-score.png?b=dev)](https://scrutinizer-ci.com/g/ovidiul/XCloner-Wordpress/?branch=master)
6
  [![Build Status](https://scrutinizer-ci.com/g/ovidiul/XCloner-Wordpress/badges/build.png?b=dev)](https://scrutinizer-ci.com/g/ovidiul/XCloner-Wordpress/build-status/master)
1
  # XCloner Wordpress Plugin - Backup and Restore
2
 
3
+ [![Author](http://img.shields.io/badge/author-@thinkovi-blue.svg?style=flat-square)](https://twitter.com/WatchfulDashbrd)
4
  [![Software License](https://img.shields.io/badge/license-GPL-brightgreen.svg?style=flat-square)](LICENSE.txt)
5
  [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/ovidiul/XCloner-Wordpress/badges/quality-score.png?b=dev)](https://scrutinizer-ci.com/g/ovidiul/XCloner-Wordpress/?branch=master)
6
  [![Build Status](https://scrutinizer-ci.com/g/ovidiul/XCloner-Wordpress/badges/build.png?b=dev)](https://scrutinizer-ci.com/g/ovidiul/XCloner-Wordpress/build-status/master)
README.txt CHANGED
@@ -1,55 +1,60 @@
1
- === XCloner - Backup and Restore===
2
- Contributors: xcloner
3
- Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=AAPE8PLAE554S
4
- Tags: backup plugin, restore plugin, database backup, backup encryption, site backup, website cloner, wordpress backup, database restore, webdav, azure, ftp, sftp, amazon s3, dropbox, google drive, differential backup
5
  Requires at least: 3.0.1
6
- Tested up to: 5.3
7
- Stable tag: 4.1.2
8
 
9
- Backup your site, restore to any web location, encrypt and send your backups to Dropbox, Amazon S3, Azure, FTP, SFTP, WebDAV, Google Drive with XCloner plugin.
10
 
11
  == Description ==
12
 
13
- XCloner is a Backup and Restore plugin that is perfectly integrated with Wordpress. It is able to create complete and differentials backups of your site, manually or automatically through the built-in scheduler.
14
 
15
- [youtube http://www.youtube.com/watch?v=V9iWpPyG1EE]
16
 
17
- XCloner design was specifically created to Generate custom backups of any Wordpress website through custom admin inputs, and to be able to Restore the backup on any other location with the help of the automatic Restore script we provide!
18
 
19
- XCloner Backup tool uses Open Source standards like TAR, Mysql and CSV formats so you can rest assured your backups can be restored in a variety of ways, giving you more flexibility and full control.
 
20
 
21
- Project is actively maintained through github https://github.com/ovidiul/XCloner-Wordpress/ , all issues can be reported here https://github.com/ovidiul/XCloner-Wordpress/issues .
22
 
23
- <strong>Requirements:</strong>
24
 
25
- PHP 5.6+ with mod CURL installed
26
 
27
- <strong>Features:</strong>
 
28
 
29
- * Backup and Restore your Wordpress site easily
30
- * Create compressed and uncompressed backups using TAR open source format
31
- * Create encrypted backups archives with AES-128-CBC algorithm
32
- * Create automated backups from your Scheduled Backups Section
33
- * Received email notifications of created backups
34
- * Generate automatic backups based on cronjobs, it can run daily, weekly, monthly or even hourly
35
- * Restore your backups locally or to a remote location, XCloner will attempt to extract the backup archive files for you, as well as import the mysql dump and update the Wordpress config details
36
- * Upload your backups to Remote Storage locations supporting FTP, SFTP, Dropbox, AWS, Azure Blob, Backblaze, WebDAV, Google Drive and many more to come
37
- * Watch every step of XCloner through it's built in debugger
38
- * Althrough we have optimized XCloner to run properly on most hosts, we give Developers options to customize it's running speed and avoid backup timeouts, all from the XCloner Config-> System Options
39
- * Ability to split backups into multiple smaller parts if a certain size limit is reached
40
- * Generate Differential Backups so your backup will include only files modified after a certain date, giving you the option to decrease the total backup space disk usage
41
- * Generate automatic backups before a Wordpress automatic update
42
- * GDPR compliant by added encryption data
 
 
43
 
44
  == Installation ==
45
 
46
- 1. Upload the plugin directory to wp-content/plugins directory
47
- 2. Activate the plugin
48
- 3. Access the plugin Dashboard from the Admin Sidebar -> Site Backup Menu
 
49
 
50
  UPGRADE:
51
 
52
- You can do it easily from the Wordpress backend.
53
 
54
  == Frequently Asked Questions ==
55
 
@@ -108,11 +113,16 @@ Of course, schedules can be adjusted accordingly to how often you update your si
108
  10. Generate Backup Process
109
  11. Generate Backup Screen
110
 
111
- == DONORS ==
112
 
113
- Immigration Attorney Montana <a href="https://www.immigrationlawofmt.com" target='_blank'>https://www.immigrationlawofmt.com</a>
 
 
 
 
114
 
115
- == Changelog ==
 
116
 
117
  = 4.1.2 =
118
  * improved default backup storage path security
@@ -271,3 +281,234 @@ Immigration Attorney Montana <a href="https://www.immigrationlawofmt.com" target
271
 
272
  = 3.0.3 =
273
  Please check changelog!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Backup, Restore and Migrate WordPress Sites With the XCloner Plugin ===
2
+ Contributors: watchful,ovidiul
3
+ Donate link: http://www.xcloner.com
4
+ Tags: backup, database backup, cloud backup, WordPress backup, WordPress migration
5
  Requires at least: 3.0.1
6
+ Tested up to: 5.4
7
+ Stable tag: 4.1.4
8
 
9
+ XCloner is a backup plugin that allows you to safely back up and restore your WordPress sites. You can send site backups to SFTP, Dropbox, Amazon, Google Drive, Backblaze and other locations.
10
 
11
  == Description ==
12
 
13
+ [XCloner](https://www.xcloner.com) is a backup plugin that allows you to safely back up and restore your WordPress sites. You can send your site backups to SFTP, Dropbox, Amazon, Google Drive, Backblaze and other locations. You can create backups manually or automatically with XCloner’s built-in scheduler.
14
 
15
+ XCloner enables you to automatically generate backups with the built-in cron script. These cron jobs can run daily, weekly, monthly or even hourly.
16
 
17
+ XCloner allows you to generate custom backups of any WordPress site, and then restore the backup on any other location with the help of the automatic restore script we provide!
18
 
19
+ XCloner has many useful safety features. For example, XCloner will generate a core, plugins, themes or languages files backup before the automatic update of WordPress core, plugins, themes or languages files.
20
+ = Remote Storage for WordPress Backups =
21
 
22
+ XCloner allows you to send your backups to remote storage locations supporting FTP, SFTP, DropBox, Amazon S3, Google Drive, WebDAV, Backblaze, Azure and many more to come
23
 
24
+ You can generate “Differential Backups” so your backup will include only files modified after a certain date. This can decrease the space needed to store your backups.
25
 
26
+ XCloner also has safety features to make sure your backups are successful. One example: you have the ability to split backups into multiple smaller parts if a certain size limit is reached. Another example: XCloner can also store a local copy of the backup that it will then delete when the backup has been sent to the remote location.
27
 
28
+ = Secure, GDPR Compliant WordPress Backups =
29
+ XCloner is the best backup choice for people who care about security and privacy.
30
 
31
+ XCloner uses open source standards like TAR, MySQL and CSV formats so you can be sure that your backups can be restored in a variety of ways, giving you more flexibility and full control.
32
+
33
+ XCloner has a built-in security layer to protect your backups. You can create encrypted backup archives with AES-128-CBC algorithm. This encryption helps to ensure that your data is still GDPR compliant even if the backup fails.
34
+
35
+ = Restore Backups Anywhere =
36
+
37
+ You can restore backups on any location compatible with your website by using the XCloner restore feature. Your site clone can be restored on a totally different server, with new server and MySQL details.
38
+
39
+ XCloner will attempt to extract the backup archive files for you, as well as import the MySQL dump and update your WordPress configuration details.
40
+
41
+ The restore script is located inside the XCloner archive, in the /restore/ directory. XCloner can restore your original file and directory permissions. XCloner can also automatically update the new host settings to the configuration file.
42
+
43
+ XCloner has a variety of restoration options including: All Files, Only Plugins Files, Only Theme Files, Only Uploads Files, and Only Database Backup.
44
+
45
+ = XCloner works best with the Watchful dashboard =
46
+ [Watchful](https://watchful.net) is a web developers toolbox for remotely managing and monitoring multiple WordPress websites. Simply add all your production and staging sites into the Watchful Dashboard and use our tools to monitor uptime and site backups, plus updates to WordPress and core and plugins, and more. XCloner integrates smoothly with Watchful. You’ll be amazed at how much time and money you save managing your WordPress sites with [Watchful](https://wordpress.org/plugins/watchful/).
47
 
48
  == Installation ==
49
 
50
+ 1. In the WordPress backend, select Plugins > Add New.
51
+ 2. In the search bar enter `xcloner`.
52
+ 3. When the XCLoner listing is shown, click the 'Install` button.
53
+ 4. Following installation, click the `Activate` button.
54
 
55
  UPGRADE:
56
 
57
+ XLCloner can be updated from the plugins list in the WordPress backend.
58
 
59
  == Frequently Asked Questions ==
60
 
113
  10. Generate Backup Process
114
  11. Generate Backup Screen
115
 
116
+ == Changelog ==
117
 
118
+ = 4.1.4 =
119
+ * thinkovi references replace
120
+ * plugin pre auto update text changelog
121
+ * author name and uri change
122
+ * standalone library addon support
123
 
124
+ = 4.1.3 =
125
+ * database include tables fix
126
 
127
  = 4.1.2 =
128
  * improved default backup storage path security
281
 
282
  = 3.0.3 =
283
  Please check changelog!
284
+
285
+ == Installation ==
286
+
287
+ 1. In the WordPress backend, select Plugins > Add New.
288
+ 2. In the search bar enter `xcloner`.
289
+ 3. When the XCLoner listing is shown, click the 'Install` button.
290
+ 4. Following installation, click the `Activate` button.
291
+
292
+ UPGRADE:
293
+
294
+ XLCloner can be updated from the plugins list in the WordPress backend.
295
+
296
+ == Frequently Asked Questions ==
297
+
298
+ = Where does XCloner keep it's Database Backups? =
299
+
300
+ XCloner stores them in separate mysql dump files, inside a folder called xcloner-XXXXX inside the backup archive root path, where XXXXX is a hash number that is identical with the last 5 characters of the backup name,
301
+ so if the backup name is backup_localhost-2017-02-16_15-36-sql-1c6c6.tgz , the mysql backup file will be stored in xcloner-1c6c6/ folder.
302
+
303
+ = How do I Restore my Backup? =
304
+
305
+ XCloner provide an easy to use restore script available in the Site Backup -> Restore Backups menu, the process is being described there as well.
306
+
307
+ If the XCloner Restore option fails, you can manually restore your backup as follows:
308
+
309
+ 1. extract the backup archive files to your new location
310
+ 2. locate the xcloner-XXXXX folder inside your backup root folder, and look for the mysql backup in database-sql and import it through phpmyadmin
311
+ 3. update your wp-config.php file to reflect the new mysql details
312
+
313
+ = How do I know which Files were include in the Backup? =
314
+
315
+ The XCloner Manager Backups Panel provides an easy utility to view each backup content files list. It also stores a copy of the archived backup files inside the xcloner-XXXXX/backup_files.csv file in an easy to read CSV format.
316
+
317
+ = Do you have a Log for the created Backup? =
318
+
319
+ Yes, if XCloner Logger option is enabled, it will store a log file inside the xcloner-XXXXX folder inside the backup archive, file is named xcloner-xxxxx.log
320
+
321
+ = What are Differentials Backups? =
322
+
323
+ Differential Backups contain files modified after a certian period of time. So each time backup runs, modified files after that period of time are added to a new Backup archive.
324
+ Compared to Incremental Backups, which contain only modified files from the previous run, they use more space but are more reliable for files restore.
325
+ They will use considerably less space than a full backup however.
326
+
327
+ = Why Differential Backups and will you support Incremental Backups? =
328
+
329
+ The main difference comes from how reliable a backup set it. For instance, if something happens to one backup archive from the Incremental Backup set, then it is possible you will lose
330
+ the files changes in that period of time, however if the same case happens to a Differential Backup, then the files can easily be recovered from any of the other Differential Backups. The
331
+ storage difference between Incremental Backups and Differential Backups is not significant and considering the reliability of the Differential Set so we have decided, for now, to not implement
332
+ further Incremental Backups.
333
+
334
+ = What would a good Backup Procedure be with Differential Backups? =
335
+
336
+ As a general rule, I would recommend setting a weekly full site backup schedule and then a daily schedule for a differential backup. You can also include a daily backup of the database in the same Differential Backup.
337
+ Of course, schedules can be adjusted accordingly to how often you update your site, the size of it and the storage space available.
338
+
339
+ == Screenshots ==
340
+
341
+ 1. XCloner Dashboard
342
+ 2. General Backup Settings
343
+ 3. List Backup Content
344
+ 4. Cleanup Options for Local Storage
345
+ 5. Remote Storage Panel supporting ftp, sftp, dropbox, amazon s3, azure blob and many more to come
346
+ 6. Manage Scheduled Backups Panel
347
+ 7. Edit Scheduled Backup
348
+ 8. Generate Backup ->Files Options tab
349
+ 9. Restore Backup Panel
350
+ 10. Generate Backup Process
351
+ 11. Generate Backup Screen
352
+
353
+ == Changelog ==
354
+
355
+ = 4.1.3 =
356
+ * database include tables fix
357
+
358
+ = 4.1.2 =
359
+ * improved default backup storage path security
360
+ * improved remote storage security
361
+
362
+ = 4.1.2 =
363
+ * vendor lib updates
364
+ * flysystem azure storage Upgrade
365
+
366
+ = 4.1.1 =
367
+ * log tmp directories fix, tracking only ERROR reports from php
368
+ * security improvement backup log name
369
+ * database restore resume fix
370
+ * memory limit fix
371
+
372
+ = 4.1.0 =
373
+ * added AES-128-CBC backup encryption and decryption option
374
+ * manage backup fixes
375
+ * scheduled backup screen fixes and addon backup encryption option
376
+ * automated backups encryption option addon
377
+ * generate backups encrypt option addon
378
+
379
+ = 4.0.9 =
380
+ * remote storage password encryption addon for database
381
+ * vendor cleanup packages
382
+ * database silent fail fix
383
+ * copyright changes
384
+ * jstree fix database display
385
+ * microtime float div fix
386
+ * manage backups data order fix
387
+
388
+ = 4.0.8 =
389
+ * updated vendor library dependencies, AWS, phpseclib
390
+ * TAR compression fix
391
+ * 7.2 compatibility checks and fixes
392
+
393
+ = 4.0.7 =
394
+ * added log fixes for Wordpress cron
395
+ * remove storage fixes
396
+
397
+ = 4.0.6 =
398
+ * S3 prefix addon for defining folders
399
+ * S3 custom endpoint addon to support minio.io
400
+ * code fixes
401
+
402
+ = 4.0.5 =
403
+ * Dropbox API update to V2
404
+ * Code fixes and text changes
405
+
406
+ = 4.0.4 =
407
+ * remote storage view fix
408
+ * added automatic backups option before WP automatic update
409
+ * deactivate exception handling fix
410
+ * restore pages improvements
411
+ * old XCloner backup format compatibility fixes
412
+
413
+ = 4.0.3 =
414
+ * added differential backups with the option to only backup files modified after a certain date
415
+ * added localhost restore option with direct access to the restore restore
416
+ * added schedule name fixes
417
+ * added restore filter All Files, Only Plugins Files, Only Theme Files, Only Uploads Files, Only Database Backup
418
+ * added remote backup list archive option on restore page
419
+ * tmp directory cleanup on deactivate
420
+ * sftp text fixes
421
+
422
+ = 4.0.2 =
423
+ * added WebDAV remote storage support
424
+ * added Google Drive Suppor through XCloner-Google-Drive plugin
425
+ * added depedency injection code refactoring
426
+ * added TAR PAX support on restore
427
+ * improving code quality scrutinizer
428
+ * fixing phpversion requirement
429
+ * adding Backblaze remote storage support
430
+ * added Remote Storage Manage Backups dropdown selection
431
+ * fixed windows opendir error
432
+ * added total archived files to notifications email
433
+ * timezone scheduler fix
434
+ * added default error sending to admin when no notification email is set
435
+
436
+
437
+ = 4.0.1 =
438
+ * Code rewritten from ground up to make use of latest code standards
439
+ * Added support for Dropbox, Amazon S3, Azure Blob and SFTP storage
440
+ * Added a new restore script
441
+ * Added an improved backup and system logger
442
+ * New Setting Panel
443
+ * New Manage Backups Panel with the options to Delete, Transfer to Remote Storage, Download and List Backup archive contents
444
+ * Added mail notifications for scheduled backups
445
+ * Added a new Cron Scheduler to make use of Wordpress System Cron option
446
+ * Improved user input sanitization
447
+ * Improved recursive file scanning and archiving
448
+ * Improved Mysql Backup dump
449
+ * Added Multiple Cleanup options both for local storage and remote
450
+ * Added Improved Backup Compressing option
451
+
452
+ = 3.1.5 =
453
+ * Config variables save sanitization addon
454
+
455
+ = 3.1.4 =
456
+ * DropPHP DropBox library update, upload fixes for files larger than 150MB
457
+
458
+ = 3.1.3 =
459
+ * XSS fix
460
+
461
+ = 3.1.2 =
462
+ * vulnerability fix
463
+
464
+ = 3.1.1 =
465
+ * added CSRF protection
466
+
467
+ = 3.1.0 =
468
+ * added Wordpress login-less integration
469
+ * plugin settings are now saved to database
470
+ * security audit and hardening
471
+
472
+ = 3.0.8 =
473
+ * added russian language support
474
+
475
+ = 3.0.7 =
476
+ * added sftp support for backup transfer, thanks Todd Bluhm - dynamicts.com
477
+
478
+ = 3.0.6 =
479
+ * added php 5.4 compatibility
480
+
481
+ = 3.0.4 =
482
+ * LFI vulnerability fix
483
+
484
+ = 3.0.3 =
485
+ * added amazon ssl option box
486
+ * moved the compress option to the System tab, don't use it unless you know what you are doing!
487
+
488
+ = 3.0.1 =
489
+ * several important security and bug fixes
490
+
491
+ = 3.0 =
492
+ * incremental database backup
493
+ * incremental file system scan
494
+ * backup size limit and option to split it into additional archives, default 2GB
495
+ * exclude files larger than a certain size option
496
+ * incremental files restore
497
+ * JQuery Start interface
498
+
499
+ = 2.2.1 =
500
+ * Added JSON AJAX interface to the Generate Backup process
501
+ * Added incremental filesystem scan
502
+ * several bug fixes
503
+ * php >=5.2.0 version check
504
+
505
+ = 2.1.2 =
506
+ * Added Amazon S3 cron storage support
507
+
508
+ = 2.1 =
509
+ * Initial release
510
+
511
+ == Upgrade Notice ==
512
+
513
+ = 3.0.3 =
514
+ Please check changelog!
admin/class-xcloner-admin.php CHANGED
@@ -3,7 +3,7 @@
3
  /**
4
  * The admin-specific functionality of the plugin.
5
  *
6
- * @link http://www.thinkovi.com
7
  * @since 1.0.0
8
  *
9
  * @package Xcloner
3
  /**
4
  * The admin-specific functionality of the plugin.
5
  *
6
+ * @link https://watchful.net
7
  * @since 1.0.0
8
  *
9
  * @package Xcloner
admin/partials/xcloner_generate_backups_page.php CHANGED
@@ -293,8 +293,8 @@ $tab = 1;
293
  <div class="row">
294
  <h5><?php echo __("Thank you for using XCloner.", 'xcloner-backup-and-restore') ?></h5>
295
  <h6><?php echo sprintf(__("We would love to hear about your experience in the %s.", 'xcloner-backup-and-restore'), '<a href="https://wordpress.org/support/plugin/xcloner-backup-and-restore/reviews/" target="_blank">Wordpress XCloner Reviews Section</a>') ?></h6>
296
- <a class="twitter-follow-button" href="https://twitter.com/thinkovi"
297
- data-show-count="false">Follow @thinkovi</a>
298
  <script src="//platform.twitter.com/widgets.js" async="" charset="utf-8"></script>
299
 
300
  <br/>
293
  <div class="row">
294
  <h5><?php echo __("Thank you for using XCloner.", 'xcloner-backup-and-restore') ?></h5>
295
  <h6><?php echo sprintf(__("We would love to hear about your experience in the %s.", 'xcloner-backup-and-restore'), '<a href="https://wordpress.org/support/plugin/xcloner-backup-and-restore/reviews/" target="_blank">Wordpress XCloner Reviews Section</a>') ?></h6>
296
+ <a class="twitter-follow-button" href="https://twitter.com/WatchfulDashbrd"
297
+ data-show-count="false">Follow @WatchfulDashbrd</a>
298
  <script src="//platform.twitter.com/widgets.js" async="" charset="utf-8"></script>
299
 
300
  <br/>
admin/partials/xcloner_init_page.php CHANGED
@@ -5,7 +5,7 @@
5
  *
6
  * This file is used to markup the admin-facing aspects of the plugin.
7
  *
8
- * @link http://www.thinkovi.com
9
  * @since 1.0.0
10
  *
11
  * @package Xcloner
5
  *
6
  * This file is used to markup the admin-facing aspects of the plugin.
7
  *
8
+ * @link https://watchful.net
9
  * @since 1.0.0
10
  *
11
  * @package Xcloner
api/api_test.html ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <html>
2
+ <head>
3
+ <title>Incremental Backup Generator XCloner API</title>
4
+
5
+ <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
6
+ <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
7
+ <script>
8
+ var url = document.URL,
9
+ shortUrl = url.substring(0, url.lastIndexOf("/"));
10
+
11
+ var api_base_url = shortUrl + "/expose_api.php";
12
+
13
+ var scan_filesystem_url = api_base_url + "?action=scan_filesystem";
14
+ var backup_database_url = api_base_url + "?action=backup_database";
15
+ var backup_files_url = api_base_url + "?action=backup_files";
16
+
17
+ var global_backup_hash;
18
+ var backup_profile;
19
+
20
+ async function make_call(call, init, params) {
21
+ var data = new FormData();
22
+ data.append("init", init);
23
+ data.append("hash", global_backup_hash || "");
24
+ data.append("extra", JSON.stringify(params) || "");
25
+ data.append("standalone_api_key", jQuery('#standalone_api_key').val() || "");
26
+ data.append("backup_profile", JSON.stringify(backup_profile) || "");
27
+ return axios
28
+ .post(window[`${call}_url`], data, {
29
+ headers: {
30
+ "Content-Type": "application/json",
31
+ },
32
+ })
33
+ .then(function (response) {
34
+ // handle success
35
+ let data = response.data;
36
+ jQuery("#output").append(
37
+ `<b>${call}</b>: ${JSON.stringify(data)} <br />`
38
+ );
39
+ if (!Boolean(data.finished)) {
40
+ global_backup_hash = data.hash.substr(1, data.hash.length);
41
+ return make_call(call, 0, data.extra);
42
+ }
43
+ });
44
+ }
45
+
46
+ function start_backup() {
47
+ jQuery("#output").html("");
48
+ make_call("scan_filesystem", 1)
49
+ .then(function () {
50
+ return make_call("backup_database", 1);
51
+ })
52
+ .then(function () {
53
+ return make_call("backup_files", 1);
54
+ })
55
+ .then(function () {
56
+ jQuery("#loading").hide();
57
+ jQuery("#output").append(`<b>Backup Finished.</b>`);
58
+ }).catch(function(){
59
+ jQuery("#loading").hide();
60
+ });
61
+ }
62
+
63
+ jQuery(document).ready(function () {
64
+ jQuery("#button").on("click", function () {
65
+ try {
66
+ if (jQuery("#backup_profile").val()) {
67
+ backup_profile = JSON.parse(jQuery("#backup_profile").val());
68
+ }
69
+ } catch {
70
+ alert("Backup profile JSON data is invalid!");
71
+ return;
72
+ }
73
+ if ((api_url = jQuery("#api_url"))) {
74
+ api_base_url = api_url;
75
+ }
76
+
77
+ jQuery("#loading").show();
78
+ start_backup();
79
+ });
80
+ });
81
+ </script>
82
+ </head>
83
+ <body>
84
+ <input
85
+ type="text"
86
+ style="width: 350px;"
87
+ placeholder="XCloner Standalone API url"
88
+ id="api_url"
89
+ />
90
+ <button id="button">Start Backup</button> <br />
91
+ <input
92
+ type="text"
93
+ style="width: 350px;"
94
+ placeholder="API KEY = config xcloner_standalone_api_key"
95
+ id="standalone_api_key"> <br />
96
+ <textarea
97
+ id="backup_profile"
98
+ style="width: 350px; height: 150px;"
99
+ placeholder="Backup Profile JSON Config"
100
+ >
101
+ {
102
+ "extra": [],
103
+ "backup_params": {
104
+ "backup_name": "backup_[domain]_8888-[time]-sql",
105
+ "email_notification": "your@email.com",
106
+ "diff_start_date": "",
107
+ "profile_name": "test2",
108
+ "backup_encrypt": false,
109
+ "target_storage": ""
110
+ },
111
+ "database": {
112
+ "#": [
113
+ "wordpress" ]
114
+ },
115
+ "excluded_files": [
116
+ "wp-content",
117
+ "wp-includes"
118
+ ]
119
+ }</textarea
120
+ >
121
+
122
+ <div id="output"></div>
123
+ <img
124
+ src="https://i.gifer.com/4V0b.gif"
125
+ id="loading"
126
+ style="display: none;"
127
+ />
128
+ </body>
129
+ </html>
api/expose_api.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ define('WP_DEBUG', false);
3
+ define('WP_DEBUG_DISPLAY', false);
4
+
5
+ require_once(dirname(__DIR__) . '/includes/class-xcloner-standalone.php');
6
+
7
+ if (!is_admin()) {
8
+ die('Access denied');
9
+ }
10
+
11
+ //loading the default xcloner settings in format [{'option_name':'value', {'option_value': 'value'}}]
12
+ $json_config = json_decode(file_get_contents(__DIR__ . '/standalone_config.json'));
13
+
14
+ if (!$json_config) {
15
+ die('Could not parse default JSON config, i will shutdown for now...');
16
+ }
17
+
18
+ if (!is_localhost())
19
+ {
20
+ if (!isset($_REQUEST['standalone_api_key']) || !$_REQUEST['standalone_api_key'] || $config['xcloner_standalone_api_key'] != $_REQUEST['standalone_api_key']) {
21
+ die('Access denied, please check your standalone_api_key value');
22
+ }
23
+ }
24
+
25
+ $config = [];
26
+
27
+ foreach ($json_config as $item) {
28
+ $config[$item->option_name] = $item->option_value;
29
+ }
30
+
31
+ //we check to see if we have a backup_profile sent over post
32
+ if (isset($_POST['backup_profile']) && trim($_POST['backup_profile'])) {
33
+ $arr = json_decode(stripslashes($_POST['backup_profile']), true);
34
+ $config['profile'] = array_combine(array_keys($arr), $arr);
35
+ $config['profile']['processed'] = true;
36
+ $_POST['data'] = json_encode($config['profile']);
37
+ }
38
+
39
+ if (isset($_POST['extra'])) {
40
+ $_POST['data'] = json_decode(stripslashes($_POST['data']));
41
+ $_POST['data']->extra = json_decode(($_POST['extra']));
42
+ $_POST['data'] = json_encode($_POST['data']);
43
+ }
44
+
45
+ //pass json config to Xcloner_Standalone lib
46
+ $xcloner_backup = new Xcloner_Standalone($json_config);
47
+ ob_end_clean();
48
+
49
+ $xcloner_backup->define_ajax_hooks();
50
+ $xcloner_backup->run();
api/index.html ADDED
File without changes
api/standalone_config.json ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {"option_name":"xcloner_cleanup_retention_limit_archives","option_value":"100"},
3
+ {"option_name":"xcloner_cleanup_retention_limit_days","option_value":"60"},
4
+ {"option_name":"xcloner_cleanup_settings_page","option_value":""},
5
+ {"option_name":"xcloner_cron_settings_page","option_value":""},
6
+ {"option_name":"xcloner_database_records_per_request","option_value":"1000"},
7
+ {"option_name":"xcloner_db_version","option_value":"1.1.7"},
8
+ {"option_name":"xcloner_directories_to_scan_per_request","option_value":"1000"},
9
+ {"option_name":"xcloner_disable_email_notification","option_value":"1"},
10
+ {"option_name":"xcloner_enable_log","option_value":"1"},
11
+ {"option_name":"xcloner_enable_mysql_backup","option_value":"1"},
12
+ {"option_name":"xcloner_enable_pre_update_backup","option_value":""},
13
+ {"option_name":"xcloner_encryption_key","option_value":"FMo5vh6NCTo8zcmTsrzV88nnHj6BdoHGOAK"},
14
+ {"option_name":"xcloner_standalone_api_key","option_value":""},
15
+ {"option_name":"xcloner_exclude_files_larger_than_mb","option_value":"0"},
16
+ {"option_name":"xcloner_files_to_process_per_request","option_value":"328"},
17
+ {"option_name":"xcloner_force_tmp_path_site_root","option_value":"1"},
18
+ {"option_name":"xcloner_mysql_database","option_value":"wordpress"},
19
+ {"option_name":"xcloner_mysql_hostname","option_value":"localhost"},
20
+ {"option_name":"xcloner_mysql_password","option_value":"root"},
21
+ {"option_name":"xcloner_mysql_prefix","option_value":"wp_"},
22
+ {"option_name":"xcloner_mysql_settings_page","option_value":""},
23
+ {"option_name":"xcloner_mysql_username","option_value":"root"},
24
+ {"option_name":"xcloner_size_limit_per_request","option_value":"50"},
25
+ {"option_name":"xcloner_split_backup_limit","option_value":"2048"},
26
+ {"option_name":"xcloner_start_path","option_value":"\/Applications\/MAMP\/htdocs\/wordpress/"},
27
+ {"option_name":"xcloner_store_path","option_value":"\/Applications\/MAMP\/htdocs\/wordpress\/wp-content\/backups\/"},
28
+ {"option_name":"xcloner_system_settings_page","option_value":"100"},
29
+ {"option_name":"xcloner_text","option_value":"0"},
30
+ {"option_name":"profile", "option_value":{
31
+ "extra": [],
32
+ "backup_params": {
33
+ "backup_name": "backup_[domain]_8888-[time]-sql",
34
+ "email_notification": "info@thinkovi.com",
35
+ "diff_start_date": "",
36
+ "schedule_name": "test2",
37
+ "backup_encrypt": false,
38
+ "start_at": false,
39
+ "schedule_frequency": "daily",
40
+ "schedule_storage": ""
41
+ },
42
+ "database": {
43
+ "#": [
44
+ "wordpress",
45
+ "joomla"
46
+ ]
47
+ },
48
+ "excluded_files": [
49
+ "wp-content",
50
+ "wp-includes"
51
+ ]
52
+ }}
53
+ ]
54
+
55
+
composer.lock CHANGED
@@ -776,24 +776,24 @@
776
  "time": "2019-04-26T15:02:17+00:00"
777
  },
778
  {
779
- "name": "mikey179/vfsStream",
780
- "version": "v1.6.5",
781
  "source": {
782
  "type": "git",
783
  "url": "https://github.com/bovigo/vfsStream.git",
784
- "reference": "d5fec95f541d4d71c4823bb5e30cf9b9e5b96145"
785
  },
786
  "dist": {
787
  "type": "zip",
788
- "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/d5fec95f541d4d71c4823bb5e30cf9b9e5b96145",
789
- "reference": "d5fec95f541d4d71c4823bb5e30cf9b9e5b96145",
790
  "shasum": ""
791
  },
792
  "require": {
793
  "php": ">=5.3.0"
794
  },
795
  "require-dev": {
796
- "phpunit/phpunit": "~4.5"
797
  },
798
  "type": "library",
799
  "extra": {
@@ -819,7 +819,7 @@
819
  ],
820
  "description": "Virtual file system to mock the real file system in unit tests.",
821
  "homepage": "http://vfs.bovigo.org/",
822
- "time": "2017-08-01T08:02:14+00:00"
823
  },
824
  {
825
  "name": "monolog/monolog",
@@ -1591,16 +1591,16 @@
1591
  },
1592
  {
1593
  "name": "splitbrain/php-archive",
1594
- "version": "1.0.10",
1595
  "source": {
1596
  "type": "git",
1597
  "url": "https://github.com/splitbrain/php-archive.git",
1598
- "reference": "a46f3aaeb9f123fdb7db4e192b0600feebf7f773"
1599
  },
1600
  "dist": {
1601
  "type": "zip",
1602
- "url": "https://api.github.com/repos/splitbrain/php-archive/zipball/a46f3aaeb9f123fdb7db4e192b0600feebf7f773",
1603
- "reference": "a46f3aaeb9f123fdb7db4e192b0600feebf7f773",
1604
  "shasum": ""
1605
  },
1606
  "require": {
@@ -1641,7 +1641,7 @@
1641
  "unzip",
1642
  "zip"
1643
  ],
1644
- "time": "2018-05-01T08:03:56+00:00"
1645
  },
1646
  {
1647
  "name": "srmklive/flysystem-dropbox-v2",
776
  "time": "2019-04-26T15:02:17+00:00"
777
  },
778
  {
779
+ "name": "mikey179/vfsstream",
780
+ "version": "v1.6.8",
781
  "source": {
782
  "type": "git",
783
  "url": "https://github.com/bovigo/vfsStream.git",
784
+ "reference": "231c73783ebb7dd9ec77916c10037eff5a2b6efe"
785
  },
786
  "dist": {
787
  "type": "zip",
788
+ "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/231c73783ebb7dd9ec77916c10037eff5a2b6efe",
789
+ "reference": "231c73783ebb7dd9ec77916c10037eff5a2b6efe",
790
  "shasum": ""
791
  },
792
  "require": {
793
  "php": ">=5.3.0"
794
  },
795
  "require-dev": {
796
+ "phpunit/phpunit": "^4.5|^5.0"
797
  },
798
  "type": "library",
799
  "extra": {
819
  ],
820
  "description": "Virtual file system to mock the real file system in unit tests.",
821
  "homepage": "http://vfs.bovigo.org/",
822
+ "time": "2019-10-30T15:31:00+00:00"
823
  },
824
  {
825
  "name": "monolog/monolog",
1591
  },
1592
  {
1593
  "name": "splitbrain/php-archive",
1594
+ "version": "1.1.1",
1595
  "source": {
1596
  "type": "git",
1597
  "url": "https://github.com/splitbrain/php-archive.git",
1598
+ "reference": "10d89013572ba1f4d4ad7fcb74860242f4c3860b"
1599
  },
1600
  "dist": {
1601
  "type": "zip",
1602
+ "url": "https://api.github.com/repos/splitbrain/php-archive/zipball/10d89013572ba1f4d4ad7fcb74860242f4c3860b",
1603
+ "reference": "10d89013572ba1f4d4ad7fcb74860242f4c3860b",
1604
  "shasum": ""
1605
  },
1606
  "require": {
1641
  "unzip",
1642
  "zip"
1643
  ],
1644
+ "time": "2018-09-09T12:13:53+00:00"
1645
  },
1646
  {
1647
  "name": "srmklive/flysystem-dropbox-v2",
examples/XCloner.postman_collection.json ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "info": {
3
+ "_postman_id": "6cec0b62-e427-4d8c-a656-4ab81c389cd5",
4
+ "name": "XCloner",
5
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
6
+ },
7
+ "item": [
8
+ {
9
+ "name": "Scan Filesystem Incremental",
10
+ "request": {
11
+ "method": "POST",
12
+ "header": [
13
+ {
14
+ "key": "da",
15
+ "value": "dada",
16
+ "type": "text",
17
+ "disabled": true
18
+ }
19
+ ],
20
+ "body": {
21
+ "mode": "formdata",
22
+ "formdata": [
23
+ {
24
+ "key": "hash",
25
+ "value": "12345",
26
+ "description": "optional hash to set the backup UID",
27
+ "type": "text"
28
+ },
29
+ {
30
+ "key": "init",
31
+ "value": "1",
32
+ "description": "set to 1 on first request",
33
+ "type": "text",
34
+ "disabled": true
35
+ }
36
+ ]
37
+ },
38
+ "url": {
39
+ "raw": "http://localhost:8888/wordpress/wp-content/plugins/XCloner-Wordpress/examples/expose_api.php?action=scan_filesystem",
40
+ "protocol": "http",
41
+ "host": [
42
+ "localhost"
43
+ ],
44
+ "port": "8888",
45
+ "path": [
46
+ "wordpress",
47
+ "wp-content",
48
+ "plugins",
49
+ "XCloner-Wordpress",
50
+ "examples",
51
+ "expose_api.php"
52
+ ],
53
+ "query": [
54
+ {
55
+ "key": "init",
56
+ "value": "1",
57
+ "disabled": true
58
+ },
59
+ {
60
+ "key": "hash",
61
+ "value": "12345",
62
+ "disabled": true
63
+ },
64
+ {
65
+ "key": "data",
66
+ "value": "{\"value\":\"te\"}",
67
+ "disabled": true
68
+ },
69
+ {
70
+ "key": "action",
71
+ "value": "scan_filesystem"
72
+ }
73
+ ]
74
+ },
75
+ "description": "hash - optional parameter made of 5 chars to set the backup default hash\ninit = 1 - will trigger a backup initialization process"
76
+ },
77
+ "response": []
78
+ },
79
+ {
80
+ "name": "List Backup Files",
81
+ "request": {
82
+ "method": "POST",
83
+ "header": [],
84
+ "body": {
85
+ "mode": "formdata",
86
+ "formdata": [
87
+ {
88
+ "key": "file",
89
+ "value": "backup__8888-2020-04-01_13-59-sql-e506e.tar",
90
+ "description": "file name",
91
+ "type": "text"
92
+ },
93
+ {
94
+ "key": "start",
95
+ "value": "0",
96
+ "description": "position to start reading from",
97
+ "type": "text"
98
+ }
99
+ ]
100
+ },
101
+ "url": {
102
+ "raw": "http://localhost:8888/wordpress/wp-content/plugins/XCloner-Wordpress/examples/expose_api.php?action=list_backup_files",
103
+ "protocol": "http",
104
+ "host": [
105
+ "localhost"
106
+ ],
107
+ "port": "8888",
108
+ "path": [
109
+ "wordpress",
110
+ "wp-content",
111
+ "plugins",
112
+ "XCloner-Wordpress",
113
+ "examples",
114
+ "expose_api.php"
115
+ ],
116
+ "query": [
117
+ {
118
+ "key": "action",
119
+ "value": "list_backup_files"
120
+ }
121
+ ]
122
+ }
123
+ },
124
+ "response": []
125
+ },
126
+ {
127
+ "name": "Get Backup File List",
128
+ "request": {
129
+ "method": "GET",
130
+ "header": [],
131
+ "url": {
132
+ "raw": "http://localhost:8888/wordpress/wp-content/plugins/XCloner-Wordpress/examples/expose_api.php?action=get_manage_backups_list",
133
+ "protocol": "http",
134
+ "host": [
135
+ "localhost"
136
+ ],
137
+ "port": "8888",
138
+ "path": [
139
+ "wordpress",
140
+ "wp-content",
141
+ "plugins",
142
+ "XCloner-Wordpress",
143
+ "examples",
144
+ "expose_api.php"
145
+ ],
146
+ "query": [
147
+ {
148
+ "key": "action",
149
+ "value": "get_manage_backups_list"
150
+ },
151
+ {
152
+ "key": "storage_selection",
153
+ "value": "",
154
+ "disabled": true
155
+ }
156
+ ]
157
+ }
158
+ },
159
+ "response": []
160
+ },
161
+ {
162
+ "name": "Backup Database",
163
+ "request": {
164
+ "method": "POST",
165
+ "header": [],
166
+ "body": {
167
+ "mode": "formdata",
168
+ "formdata": [
169
+ {
170
+ "key": "data",
171
+ "value": "{\"table_params\":[{\"id\":\"wordpress\",\"parent\":\"#\"}],\"files_params\":[{\"id\":\"/\",\"parent\":\"#\"},{\"id\":\"wp-admin\",\"parent\":\"/\"},{\"id\":\"wp-content\",\"parent\":\"/\"},{\"id\":\"wp-includes\",\"parent\":\"/\"},{\"id\":\".htaccess\",\"parent\":\"/\"},{\"id\":\"index.php\",\"parent\":\"/\"},{\"id\":\"info.php\",\"parent\":\"/\"},{\"id\":\"license.txt\",\"parent\":\"/\"},{\"id\":\"readme.html\",\"parent\":\"/\"},{\"id\":\"test.log\",\"parent\":\"/\"},{\"id\":\"test_exec.php\",\"parent\":\"/\"},{\"id\":\"wp-activate.php\",\"parent\":\"/\"},{\"id\":\"wp-blog-header.php\",\"parent\":\"/\"},{\"id\":\"wp-comments-post.php\",\"parent\":\"/\"},{\"id\":\"wp-config-sample.php\",\"parent\":\"/\"},{\"id\":\"wp-config.php\",\"parent\":\"/\"},{\"id\":\"wp-config.php-e\",\"parent\":\"/\"},{\"id\":\"wp-config.php.old\",\"parent\":\"/\"},{\"id\":\"wp-cron.php\",\"parent\":\"/\"},{\"id\":\"wp-links-opml.php\",\"parent\":\"/\"},{\"id\":\"wp-load.php\",\"parent\":\"/\"},{\"id\":\"wp-login.php\",\"parent\":\"/\"},{\"id\":\"wp-mail.php\",\"parent\":\"/\"},{\"id\":\"wp-settings.php\",\"parent\":\"/\"},{\"id\":\"wp-signup.php\",\"parent\":\"/\"},{\"id\":\"wp-trackback.php\",\"parent\":\"/\"},{\"id\":\"xcloner_main_8ad98-2019-03-04.log\",\"parent\":\"/\"},{\"id\":\"xmlrpc.php\",\"parent\":\"/\"}],\"backup_params\":[{\"name\":\"backup_name\",\"value\":\"backup_[domain]_8888-[time]-sql\"},{\"name\":\"email_notification\",\"value\":\"info@thinkovi.com\"},{\"name\":\"diff_start_date\",\"value\":\"\"},{\"name\":\"backup_comments\",\"value\":\"\"},{\"name\":\"schedule_name\",\"value\":\"\"},{\"name\":\"schedule_start_date\",\"value\":\"\"},{\"name\":\"schedule_start_time\",\"value\":\"\"},{\"name\":\"schedule_storage\",\"value\":\"\"}]}",
172
+ "description": "initial config data to send for backup",
173
+ "type": "text"
174
+ },
175
+ {
176
+ "key": "init",
177
+ "value": "1",
178
+ "description": "send 1 on first request",
179
+ "type": "text",
180
+ "disabled": true
181
+ },
182
+ {
183
+ "key": "hash",
184
+ "value": "12345",
185
+ "description": "unique backup hash UID",
186
+ "type": "text"
187
+ },
188
+ {
189
+ "key": "extra",
190
+ "value": "",
191
+ "description": "last action call response extra parameter",
192
+ "type": "text"
193
+ }
194
+ ]
195
+ },
196
+ "url": {
197
+ "raw": "http://localhost:8888/wordpress/wp-content/plugins/XCloner-Wordpress/examples/expose_api.php?action=backup_database",
198
+ "protocol": "http",
199
+ "host": [
200
+ "localhost"
201
+ ],
202
+ "port": "8888",
203
+ "path": [
204
+ "wordpress",
205
+ "wp-content",
206
+ "plugins",
207
+ "XCloner-Wordpress",
208
+ "examples",
209
+ "expose_api.php"
210
+ ],
211
+ "query": [
212
+ {
213
+ "key": "action",
214
+ "value": "backup_database"
215
+ }
216
+ ]
217
+ }
218
+ },
219
+ "response": []
220
+ },
221
+ {
222
+ "name": "Backup Files",
223
+ "request": {
224
+ "method": "POST",
225
+ "header": [],
226
+ "body": {
227
+ "mode": "formdata",
228
+ "formdata": [
229
+ {
230
+ "key": "data",
231
+ "value": "{\"table_params\":[{\"id\":\"wordpress\",\"parent\":\"#\"}],\"files_params\":[{\"id\":\"/\",\"parent\":\"#\"},{\"id\":\"wp-admin\",\"parent\":\"/\"},{\"id\":\"wp-content\",\"parent\":\"/\"},{\"id\":\"wp-includes\",\"parent\":\"/\"},{\"id\":\".htaccess\",\"parent\":\"/\"},{\"id\":\"index.php\",\"parent\":\"/\"},{\"id\":\"info.php\",\"parent\":\"/\"},{\"id\":\"license.txt\",\"parent\":\"/\"},{\"id\":\"readme.html\",\"parent\":\"/\"},{\"id\":\"test.log\",\"parent\":\"/\"},{\"id\":\"test_exec.php\",\"parent\":\"/\"},{\"id\":\"wp-activate.php\",\"parent\":\"/\"},{\"id\":\"wp-blog-header.php\",\"parent\":\"/\"},{\"id\":\"wp-comments-post.php\",\"parent\":\"/\"},{\"id\":\"wp-config-sample.php\",\"parent\":\"/\"},{\"id\":\"wp-config.php\",\"parent\":\"/\"},{\"id\":\"wp-config.php-e\",\"parent\":\"/\"},{\"id\":\"wp-config.php.old\",\"parent\":\"/\"},{\"id\":\"wp-cron.php\",\"parent\":\"/\"},{\"id\":\"wp-links-opml.php\",\"parent\":\"/\"},{\"id\":\"wp-load.php\",\"parent\":\"/\"},{\"id\":\"wp-login.php\",\"parent\":\"/\"},{\"id\":\"wp-mail.php\",\"parent\":\"/\"},{\"id\":\"wp-settings.php\",\"parent\":\"/\"},{\"id\":\"wp-signup.php\",\"parent\":\"/\"},{\"id\":\"wp-trackback.php\",\"parent\":\"/\"},{\"id\":\"xcloner_main_8ad98-2019-03-04.log\",\"parent\":\"/\"},{\"id\":\"xmlrpc.php\",\"parent\":\"/\"}],\"backup_params\":[{\"name\":\"backup_name\",\"value\":\"backup_[domain]_8888-[time]-sql\"},{\"name\":\"email_notification\",\"value\":\"info@thinkovi.com\"},{\"name\":\"diff_start_date\",\"value\":\"\"},{\"name\":\"backup_comments\",\"value\":\"\"},{\"name\":\"schedule_name\",\"value\":\"\"},{\"name\":\"schedule_start_date\",\"value\":\"\"},{\"name\":\"schedule_start_time\",\"value\":\"\"},{\"name\":\"schedule_storage\",\"value\":\"\"}]}\n",
232
+ "description": "config data to send to the backup",
233
+ "type": "text"
234
+ },
235
+ {
236
+ "key": "init",
237
+ "value": "1",
238
+ "description": "send 1 on first action call",
239
+ "type": "text",
240
+ "disabled": true
241
+ },
242
+ {
243
+ "key": "hash",
244
+ "value": "12345",
245
+ "description": "unique backup hash",
246
+ "type": "text"
247
+ },
248
+ {
249
+ "key": "extra",
250
+ "value": "{\"backup_part\":\"0\",\"backup_init\":0,\"backup_archive_name\":\"backup__8888-2020-04-13_14-53-sql\",\"backup_archive_name_full\":\"backup__8888-2020-04-13_14-53-sql-12345.tar\",\"lines_total\":1194,\"backup_size\":21102080,\"processed_file\":\"Requests\\/Exception\\/HTTP\\/401.php\",\"processed_file_size\":390,\"start_at_line\":987,\"start_at_byte\":0}",
251
+ "description": "last action call response extra parameter",
252
+ "type": "text"
253
+ },
254
+ {
255
+ "key": "",
256
+ "value": "",
257
+ "type": "text"
258
+ }
259
+ ]
260
+ },
261
+ "url": {
262
+ "raw": "http://localhost:8888/wordpress/wp-content/plugins/XCloner-Wordpress/examples/expose_api.php?action=backup_files",
263
+ "protocol": "http",
264
+ "host": [
265
+ "localhost"
266
+ ],
267
+ "port": "8888",
268
+ "path": [
269
+ "wordpress",
270
+ "wp-content",
271
+ "plugins",
272
+ "XCloner-Wordpress",
273
+ "examples",
274
+ "expose_api.php"
275
+ ],
276
+ "query": [
277
+ {
278
+ "key": "action",
279
+ "value": "backup_files"
280
+ }
281
+ ]
282
+ }
283
+ },
284
+ "response": []
285
+ }
286
+ ],
287
+ "protocolProfileBehavior": {}
288
+ }
examples/cli_encrypt_backup.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * USAGE
4
+ * php cli_encrypt_backup.php BACKUP_NAME --encrypt(--decrypt) --key=ENCRYPTION_KEY_OPTIONAL
5
+ */
6
+
7
+ define('WP_DEBUG', true);
8
+ define('WP_DEBUG_DISPLAY', true);
9
+
10
+ if (isset($argv[1])) {
11
+ $backup_name = $argv[1];
12
+ }
13
+
14
+ if (isset($argv[2]) && $argv[2] == '--encrypt') {
15
+ $cmd = "encrypt";
16
+ }
17
+
18
+ if (isset($argv[2]) && $argv[2] == '--decrypt') {
19
+ $cmd = "decrypt";
20
+ }
21
+
22
+ $key = "";
23
+
24
+ if (isset($argv[3]) && substr($argv[3], 0, 6) == '--key=') {
25
+ $key = substr($argv[3], 6, strlen($argv[3]));
26
+ }
27
+
28
+ require_once(dirname(__DIR__) . '/includes/class-xcloner-standalone.php');
29
+
30
+ //loading the default xcloner settings in format [{'option_name':'value', {'option_value': 'value'}}]
31
+ $json_config = json_decode(file_get_contents(__DIR__ . '/standalone_backup_trigger_config.json'));
32
+
33
+ if (!$json_config) {
34
+ die('Could not parse default JSON config, i will shutdown for now...');
35
+ }
36
+
37
+ //pass json config to Xcloner_Standalone lib
38
+ $xcloner = new Xcloner_Standalone($json_config);
39
+
40
+ if (!$backup_name) {
41
+ $return = $xcloner->start();
42
+ $backup_name = $return['extra']['backup_archive_name_full'];
43
+ }
44
+
45
+ $backup_path = $xcloner->get_xcloner_filesystem()->get_storage_path_file_info($backup_name)->getPathName();
46
+ if (!file_exists($backup_path)) {
47
+ die(sprintf("Backup file %s does not exists.\n", $backup_path));
48
+ }
49
+
50
+ if (isset($cmd) && $cmd == "encrypt") {
51
+ $xcloner->get_xcloner_encryption()->encrypt_file($backup_name, "encrypted_".$backup_name, $key);
52
+ } elseif (isset($cmd) && $cmd == "decrypt") {
53
+ $xcloner->get_xcloner_encryption()->decrypt_file($backup_name, "decrypted_".$backup_name, $key);
54
+ }
55
+
56
+ if (!isset($cmd)) {
57
+ if ($xcloner->get_xcloner_encryption()->is_encrypted_file($backup_name)) {
58
+ echo sprintf("Backup %s is encrypted, i will try to decrypt it \n", $backup_name);
59
+ $xcloner->get_xcloner_encryption()->decrypt_file($backup_name, "decrypted_".$backup_name, $key);
60
+ } else {
61
+ echo sprintf("Backup %s is not encrypted, i will try to encrypt it \n", $backup_name);
62
+ $xcloner->get_xcloner_encryption()->encrypt_file($backup_name, "encrypted_".$backup_name, $key);
63
+ }
64
+ }
examples/index.html ADDED
File without changes
examples/standalone-backup-trigger.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ define('WP_DEBUG', true);
3
+ define('WP_DEBUG_DISPLAY', true);
4
+
5
+ require_once(dirname(__DIR__) . '/includes/class-xcloner-standalone.php');
6
+
7
+ //loading the default xcloner settings in format [{'option_name':'value', {'option_value': 'value'}}]
8
+ $json_config = json_decode(file_get_contents(__DIR__ . '/standalone_backup_trigger_config.json'));
9
+
10
+ if (!$json_config) {
11
+ die('Could not parse default JSON config, i will shutdown for now...');
12
+ }
13
+
14
+ //pass json config to Xcloner_Standalone lib
15
+ $xcloner_backup = new Xcloner_Standalone($json_config);
16
+ $xcloner_backup->start();
examples/standalone_backup_trigger_config.json ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {"option_name":"xcloner_cleanup_retention_limit_archives","option_value":"100"},
3
+ {"option_name":"xcloner_cleanup_retention_limit_days","option_value":"60"},
4
+ {"option_name":"xcloner_cleanup_settings_page","option_value":""},
5
+ {"option_name":"xcloner_cron_settings_page","option_value":""},
6
+ {"option_name":"xcloner_database_records_per_request","option_value":"1000"},
7
+ {"option_name":"xcloner_db_version","option_value":"1.1.7"},
8
+ {"option_name":"xcloner_directories_to_scan_per_request","option_value":"1000"},
9
+ {"option_name":"xcloner_disable_email_notification","option_value":"1"},
10
+ {"option_name":"xcloner_enable_log","option_value":"1"},
11
+ {"option_name":"xcloner_enable_mysql_backup","option_value":"1"},
12
+ {"option_name":"xcloner_enable_pre_update_backup","option_value":""},
13
+ {"option_name":"xcloner_encryption_key","option_value":"FMo5vh6NCTo8zcmTsrzV88nnHj6BdoHGOAK"},
14
+ {"option_name":"xcloner_standalone_api_key","option_value":""},
15
+ {"option_name":"xcloner_exclude_files_larger_than_mb","option_value":"0"},
16
+ {"option_name":"xcloner_files_to_process_per_request","option_value":"328"},
17
+ {"option_name":"xcloner_force_tmp_path_site_root","option_value":"1"},
18
+ {"option_name":"xcloner_mysql_database","option_value":"wordpress"},
19
+ {"option_name":"xcloner_mysql_hostname","option_value":"localhost"},
20
+ {"option_name":"xcloner_mysql_password","option_value":"root"},
21
+ {"option_name":"xcloner_mysql_prefix","option_value":"wp_"},
22
+ {"option_name":"xcloner_mysql_settings_page","option_value":""},
23
+ {"option_name":"xcloner_mysql_username","option_value":"root"},
24
+ {"option_name":"xcloner_size_limit_per_request","option_value":"50"},
25
+ {"option_name":"xcloner_split_backup_limit","option_value":"2048"},
26
+ {"option_name":"xcloner_start_path","option_value":"\/Applications\/MAMP\/htdocs\/wordpress/"},
27
+ {"option_name":"xcloner_store_path","option_value":"\/Applications\/MAMP\/htdocs\/wordpress\/wp-content\/backups\/"},
28
+ {"option_name":"xcloner_system_settings_page","option_value":"100"},
29
+ {"option_name":"xcloner_text","option_value":"0"},
30
+ {"option_name":"profile", "option_value":{
31
+ "extra": [],
32
+ "backup_params": {
33
+ "backup_name": "backup_[domain]_8888-[time]-sql",
34
+ "email_notification": "info@thinkovi.com",
35
+ "diff_start_date": "",
36
+ "schedule_name": "test2",
37
+ "backup_encrypt": false,
38
+ "start_at": false,
39
+ "schedule_frequency": "daily",
40
+ "schedule_storage": ""
41
+ },
42
+ "database": {
43
+ "#": [
44
+ "wordpress",
45
+ "joomla"
46
+ ]
47
+ },
48
+ "excluded_files": [
49
+ "wp-content",
50
+ "wp-includes"
51
+ ]
52
+ }}
53
+ ]
54
+
includes/class-xcloner-activator.php CHANGED
@@ -35,48 +35,49 @@
35
  * @package Xcloner
36
  * @subpackage Xcloner/includes
37
  * @author Liuta Ovidiu <info@thinkovi.com>
38
- * @link http://www.thinkovi.com
39
  */
40
- class Xcloner_Activator {
41
-
42
- /**
43
- * XCloner Database Version
44
- * @var string
45
- */
46
- const xcloner_db_version = '1.1.7';
47
- /**
48
- * Minimum required PHP version to run this plugin.
49
- * @var string
50
- */
51
- const xcloner_minimum_version = '5.6.0';
52
-
53
- /**
54
- * Triggered when XCloner is activated.
55
- *
56
- * This method will get trigger once XCloner plugin is activated.
57
- *
58
- * @since 1.0.0
59
- */
60
- public static function activate() {
61
-
62
- global $wpdb;
63
-
64
- if (version_compare(phpversion(), Xcloner_Activator::xcloner_minimum_version, '<')) {
65
- wp_die('<p>'.sprintf(__("XCloner requires minimum PHP version %s in order to run correctly. We have detected your version as %s"), Xcloner_Activator::xcloner_minimum_version, phpversion()).'</p>', __("XCloner Activation Error"), array('response' => 500,
66
- 'back_link' => true
67
- ));
68
- }
69
-
70
- $charset_collate = $wpdb->get_charset_collate();
71
-
72
- $installed_ver = get_option("xcloner_db_version");
73
-
74
- $xcloner_db_version = Xcloner_Activator::xcloner_db_version;
75
-
76
- $xcloner_scheduler_table = $wpdb->prefix."xcloner_scheduler";
77
-
78
- if ($installed_ver != $xcloner_db_version) {
79
- $xcloner_schedule_sql = "CREATE TABLE `".$xcloner_scheduler_table."` (
 
80
  `id` int(11) NOT NULL AUTO_INCREMENT,
81
  `name` varchar(255) NOT NULL,
82
  `recurrence` varchar(25) NOT NULL,
@@ -90,76 +91,74 @@ class Xcloner_Activator {
90
  ) " . $charset_collate.";
91
  ";
92
 
93
- require_once(ABSPATH.'wp-admin/includes/upgrade.php');
94
- dbDelta($xcloner_schedule_sql);
95
 
96
- update_option("xcloner_db_version", $xcloner_db_version);
97
- }
98
 
99
- if (get_option('xcloner_backup_compression_level') === false) {
100
- update_option('xcloner_backup_compression_level', 0);
101
- }
102
 
103
- if (get_option('xcloner_enable_log') === false) {
104
- update_option('xcloner_enable_log', 1);
105
- }
106
 
107
- if (get_option('xcloner_force_tmp_path_site_root') === false) {
108
- update_option('xcloner_force_tmp_path_site_root', 1);
109
- }
110
 
111
- if (get_option('xcloner_enable_mysql_backup') === false) {
112
- update_option('xcloner_enable_mysql_backup', 1);
113
- }
114
 
115
- if (get_option('xcloner_system_settings_page') === false) {
116
- update_option('xcloner_system_settings_page', 100);
117
- }
118
 
119
- if (get_option('xcloner_files_to_process_per_request') === false) {
120
- update_option('xcloner_files_to_process_per_request', 250);
121
- }
122
 
123
- if (get_option('xcloner_database_records_per_request') === false) {
124
- update_option('xcloner_database_records_per_request', 10000);
125
- }
126
 
127
- if (get_option('xcloner_exclude_files_larger_than_mb') === false) {
128
- update_option('xcloner_exclude_files_larger_than_mb', 0);
129
- }
130
 
131
- if (get_option('xcloner_split_backup_limit') === false) {
132
- update_option('xcloner_split_backup_limit', 2048);
133
- }
134
 
135
- if (get_option('xcloner_size_limit_per_request') === false) {
136
- update_option('xcloner_size_limit_per_request', 50);
137
- }
138
 
139
- if (get_option('xcloner_cleanup_retention_limit_days') === false) {
140
- update_option('xcloner_cleanup_retention_limit_days', 60);
141
- }
142
 
143
- if (get_option('xcloner_cleanup_retention_limit_archives') === false) {
144
- update_option('xcloner_cleanup_retention_limit_archives', 100);
145
- }
146
 
147
- if (get_option('xcloner_directories_to_scan_per_request') === false) {
148
- update_option('xcloner_directories_to_scan_per_request', 25);
149
- }
150
 
151
- /*if(!get_option('xcloner_diff_backup_recreate_period'))
152
- update_option('xcloner_diff_backup_recreate_period', 10);
153
- * */
154
 
155
- if (!get_option('xcloner_regex_exclude')) {
156
- update_option('xcloner_regex_exclude', "(wp-content\/updraft|wp-content\/uploads\/wp_all_backup)(.*)$".PHP_EOL."(.*)\.(svn|git)(.*)$".PHP_EOL."wp-content\/cache(.*)$".PHP_EOL."(.*)error_log$");
157
- }
158
-
159
- if (!get_option('xcloner_regex_exclude')) {
160
- update_option('xcloner_regex_exclude', "(wp-content\/updraft|wp-content\/uploads\/wp_all_backup)(.*)$".PHP_EOL."(.*)\.(svn|git)(.*)$".PHP_EOL."wp-content\/cache(.*)$".PHP_EOL."(.*)error_log$");
161
- }
162
-
163
- }
164
 
 
 
 
 
165
  }
35
  * @package Xcloner
36
  * @subpackage Xcloner/includes
37
  * @author Liuta Ovidiu <info@thinkovi.com>
38
+ * @link https://watchful.net
39
  */
40
+ class Xcloner_Activator
41
+ {
42
+
43
+ /**
44
+ * XCloner Database Version
45
+ * @var string
46
+ */
47
+ const xcloner_db_version = '1.1.7';
48
+ /**
49
+ * Minimum required PHP version to run this plugin.
50
+ * @var string
51
+ */
52
+ const xcloner_minimum_version = '5.6.0';
53
+
54
+ /**
55
+ * Triggered when XCloner is activated.
56
+ *
57
+ * This method will get trigger once XCloner plugin is activated.
58
+ *
59
+ * @since 1.0.0
60
+ */
61
+ public static function activate()
62
+ {
63
+ global $wpdb;
64
+
65
+ if (version_compare(phpversion(), Xcloner_Activator::xcloner_minimum_version, '<')) {
66
+ wp_die('<p>'.sprintf(__("XCloner requires minimum PHP version %s in order to run correctly. We have detected your version as %s"), Xcloner_Activator::xcloner_minimum_version, phpversion()).'</p>', __("XCloner Activation Error"), array('response' => 500,
67
+ 'back_link' => true
68
+ ));
69
+ }
70
+
71
+ $charset_collate = $wpdb->get_charset_collate();
72
+
73
+ $installed_ver = get_option("xcloner_db_version");
74
+
75
+ $xcloner_db_version = Xcloner_Activator::xcloner_db_version;
76
+
77
+ $xcloner_scheduler_table = $wpdb->prefix."xcloner_scheduler";
78
+
79
+ if ($installed_ver != $xcloner_db_version) {
80
+ $xcloner_schedule_sql = "CREATE TABLE `".$xcloner_scheduler_table."` (
81
  `id` int(11) NOT NULL AUTO_INCREMENT,
82
  `name` varchar(255) NOT NULL,
83
  `recurrence` varchar(25) NOT NULL,
91
  ) " . $charset_collate.";
92
  ";
93
 
94
+ require_once(ABSPATH.'wp-admin/includes/upgrade.php');
95
+ dbDelta($xcloner_schedule_sql);
96
 
97
+ update_option("xcloner_db_version", $xcloner_db_version);
98
+ }
99
 
100
+ if (get_option('xcloner_backup_compression_level') === false) {
101
+ update_option('xcloner_backup_compression_level', 0);
102
+ }
103
 
104
+ if (get_option('xcloner_enable_log') === false) {
105
+ update_option('xcloner_enable_log', 1);
106
+ }
107
 
108
+ if (get_option('xcloner_force_tmp_path_site_root') === false) {
109
+ update_option('xcloner_force_tmp_path_site_root', 1);
110
+ }
111
 
112
+ if (get_option('xcloner_enable_mysql_backup') === false) {
113
+ update_option('xcloner_enable_mysql_backup', 1);
114
+ }
115
 
116
+ if (get_option('xcloner_system_settings_page') === false) {
117
+ update_option('xcloner_system_settings_page', 100);
118
+ }
119
 
120
+ if (get_option('xcloner_files_to_process_per_request') === false) {
121
+ update_option('xcloner_files_to_process_per_request', 250);
122
+ }
123
 
124
+ if (get_option('xcloner_database_records_per_request') === false) {
125
+ update_option('xcloner_database_records_per_request', 10000);
126
+ }
127
 
128
+ if (get_option('xcloner_exclude_files_larger_than_mb') === false) {
129
+ update_option('xcloner_exclude_files_larger_than_mb', 0);
130
+ }
131
 
132
+ if (get_option('xcloner_split_backup_limit') === false) {
133
+ update_option('xcloner_split_backup_limit', 2048);
134
+ }
135
 
136
+ if (get_option('xcloner_size_limit_per_request') === false) {
137
+ update_option('xcloner_size_limit_per_request', 50);
138
+ }
139
 
140
+ if (get_option('xcloner_cleanup_retention_limit_days') === false) {
141
+ update_option('xcloner_cleanup_retention_limit_days', 60);
142
+ }
143
 
144
+ if (get_option('xcloner_cleanup_retention_limit_archives') === false) {
145
+ update_option('xcloner_cleanup_retention_limit_archives', 100);
146
+ }
147
 
148
+ if (get_option('xcloner_directories_to_scan_per_request') === false) {
149
+ update_option('xcloner_directories_to_scan_per_request', 25);
150
+ }
151
 
152
+ /*if(!get_option('xcloner_diff_backup_recreate_period'))
153
+ update_option('xcloner_diff_backup_recreate_period', 10);
154
+ * */
155
 
156
+ if (!get_option('xcloner_regex_exclude')) {
157
+ update_option('xcloner_regex_exclude', "(wp-content\/updraft|wp-content\/uploads\/wp_all_backup)(.*)$".PHP_EOL."(.*)\.(svn|git)(.*)$".PHP_EOL."wp-content\/cache(.*)$".PHP_EOL."(.*)error_log$");
158
+ }
 
 
 
 
 
 
159
 
160
+ if (!get_option('xcloner_regex_exclude')) {
161
+ update_option('xcloner_regex_exclude', "(wp-content\/updraft|wp-content\/uploads\/wp_all_backup)(.*)$".PHP_EOL."(.*)\.(svn|git)(.*)$".PHP_EOL."wp-content\/cache(.*)$".PHP_EOL."(.*)error_log$");
162
+ }
163
+ }
164
  }
includes/class-xcloner-api.php CHANGED
@@ -36,925 +36,925 @@ use splitbrain\PHPArchive\Zip;
36
  use splitbrain\PHPArchive\Archive;
37
  use splitbrain\PHPArchive\FileInfo;
38
 
39
-
40
  /**
41
  * XCloner Api Class
42
  */
43
  class Xcloner_Api
44
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
- private $xcloner_database;
47
- private $xcloner_settings;
48
- private $xcloner_file_system;
49
- private $xcloner_scheduler;
50
- private $xcloner_requirements;
51
- private $xcloner_sanitization;
52
- private $xcloner_encryption;
53
- private $xcloner_remote_storage;
54
- private $archive_system;
55
- private $form_params;
56
- private $logger;
57
- private $xcloner_container;
58
-
59
- /**
60
- * XCloner_Api construct class
61
- *
62
- * @param Xcloner $xcloner_container [description]
63
- */
64
- public function __construct(Xcloner $xcloner_container)
65
- {
66
- global $wpdb;
67
-
68
- if (WP_DEBUG) {
69
- error_reporting(0);
70
- }
71
-
72
- if (ob_get_length()) {
73
- ob_end_clean();
74
- }
75
- ob_start();
76
-
77
- $wpdb->show_errors = false;
78
-
79
- $this->xcloner_container = $xcloner_container;
80
-
81
- $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
82
- $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_api");
83
- $this->xcloner_file_system = $xcloner_container->get_xcloner_filesystem();
84
- $this->xcloner_sanitization = $xcloner_container->get_xcloner_sanitization();
85
- $this->xcloner_requirements = $xcloner_container->get_xcloner_requirements();
86
- $this->archive_system = $xcloner_container->get_archive_system();
87
- $this->xcloner_database = $xcloner_container->get_xcloner_database();
88
- $this->xcloner_scheduler = $xcloner_container->get_xcloner_scheduler();
89
- $this->xcloner_encryption = $xcloner_container->get_xcloner_encryption();
90
- $this->xcloner_remote_storage = $xcloner_container->get_xcloner_remote_storage();
91
-
92
- if (isset($_POST['API_ID'])) {
93
- $this->logger->info("Processing ajax request ID ".substr($this->xcloner_sanitization->sanitize_input_as_string($_POST['API_ID']),
94
- 0, 15));
95
- }
96
-
97
- }
98
-
99
- /**
100
- * Get XCloner Container
101
- * @return XCloner return the XCloner container
102
- */
103
- public function get_xcloner_container()
104
- {
105
- return $this->xcloner_container;
106
- }
107
-
108
-
109
- /**
110
- * Checks API access
111
- */
112
- private function check_access()
113
- {
114
- if (function_exists('current_user_can') && !current_user_can('manage_options')) {
115
- $this->send_response(json_encode("Not allowed access here!"));
116
- }
117
- }
118
-
119
- /**
120
- * Initialize the database connection
121
- */
122
- public function init_db()
123
- {
124
- return;
125
-
126
-
127
- $data['dbHostname'] = $this->xcloner_settings->get_db_hostname();
128
- $data['dbUsername'] = $this->xcloner_settings->get_db_username();
129
- $data['dbPassword'] = $this->xcloner_settings->get_db_password();
130
- $data['dbDatabase'] = $this->xcloner_settings->get_db_database();
131
-
132
-
133
- $data['recordsPerSession'] = $this->xcloner_settings->get_xcloner_option('xcloner_database_records_per_request');
134
- $data['TEMP_DBPROCESS_FILE'] = $this->xcloner_settings->get_xcloner_tmp_path().DS.".database";
135
- $data['TEMP_DUMP_FILE'] = $this->xcloner_settings->get_xcloner_tmp_path().DS."database-sql.sql";
136
-
137
- try {
138
- $this->xcloner_database->init($data);
139
-
140
- }catch (Exception $e) {
141
-
142
- $this->send_response($e->getMessage());
143
- $this->logger->error($e->getMessage());
144
-
145
- }
146
-
147
- return $this->xcloner_database;
148
-
149
-
150
- }
151
-
152
- /*
153
  * Save Schedule API
154
  */
155
- public function save_schedule()
156
- {
157
- global $wpdb;
158
-
159
- $this->check_access();
160
-
161
- $scheduler = $this->xcloner_scheduler;
162
- $params = array();
163
- $schedule = array();
164
- $response = array();
165
-
166
- if (isset($_POST['data'])) {
167
- $params = json_decode(stripslashes($_POST['data']));
168
- }
169
-
170
- $this->process_params($params);
171
-
172
- if (isset($_POST['id'])) {
173
-
174
- $this->form_params['backup_params']['backup_name'] = $this->xcloner_sanitization->sanitize_input_as_string($_POST['backup_name']);
175
- $this->form_params['backup_params']['email_notification'] = $this->xcloner_sanitization->sanitize_input_as_string($_POST['email_notification']);
176
- if ($_POST['diff_start_date']) {
177
- $this->form_params['backup_params']['diff_start_date'] = strtotime($this->xcloner_sanitization->sanitize_input_as_string($_POST['diff_start_date']));
178
- } else {
179
- $this->form_params['backup_params']['diff_start_date'] = "";
180
- }
181
- $this->form_params['backup_params']['schedule_name'] = $this->xcloner_sanitization->sanitize_input_as_string($_POST['schedule_name']);
182
- $this->form_params['backup_params']['backup_encrypt'] = $this->xcloner_sanitization->sanitize_input_as_int($_POST['backup_encrypt']);
183
- $this->form_params['backup_params']['start_at'] = strtotime($_POST['schedule_start_date']);
184
- $this->form_params['backup_params']['schedule_frequency'] = $this->xcloner_sanitization->sanitize_input_as_string($_POST['schedule_frequency']);
185
- $this->form_params['backup_params']['schedule_storage'] = $this->xcloner_sanitization->sanitize_input_as_string($_POST['schedule_storage']);
186
- $this->form_params['database'] = (stripslashes($this->xcloner_sanitization->sanitize_input_as_raw($_POST['table_params'])));
187
- $this->form_params['excluded_files'] = (stripslashes($this->xcloner_sanitization->sanitize_input_as_raw($_POST['excluded_files'])));
188
-
189
- //$this->form_params['backup_params']['backup_type'] = $this->xcloner_sanitization->sanitize_input_as_string($_POST['backup_type']);
190
-
191
- $tables = explode(PHP_EOL, $this->form_params['database']);
192
- $return = array();
193
-
194
- foreach ($tables as $table) {
195
- $table = str_replace("\r", "", $table);
196
- $data = explode(".", $table);
197
- if (isset($data[1])) {
198
- $return[$data[0]][] = $data[1];
199
- }
200
- }
201
-
202
- $this->form_params['database'] = ($return);
203
-
204
- $excluded_files = explode(PHP_EOL, $this->form_params['excluded_files']);
205
- $return = array();
206
-
207
- foreach ($excluded_files as $file) {
208
- $file = str_replace("\r", "", $file);
209
- if ($file) {
210
- $return[] = $file;
211
- }
212
- }
213
-
214
- $this->form_params['excluded_files'] = ($return);
215
-
216
- $schedule['start_at'] = $this->form_params['backup_params']['start_at'];
217
-
218
- if (!isset($_POST['status'])) {
219
- $schedule['status'] = 0;
220
- } else {
221
- $schedule['status'] = $this->xcloner_sanitization->sanitize_input_as_int($_POST['status']);
222
- }
223
- } else {
224
-
225
- $schedule['status'] = 1;
226
- $schedule['start_at'] = strtotime($this->form_params['backup_params']['schedule_start_date'].
227
- " ".$this->form_params['backup_params']['schedule_start_time']);
228
-
229
- if ($schedule['start_at'] <= time()) {
230
- $schedule['start_at'] = "";
231
- }
232
- }
233
-
234
- if (!$schedule['start_at']) {
235
- $schedule['start_at'] = date('Y-m-d H:i:s', time());
236
- } else {
237
- $schedule['start_at'] = date('Y-m-d H:i:s',
238
- $schedule['start_at'] - (get_option('gmt_offset') * HOUR_IN_SECONDS));
239
- }
240
-
241
- $schedule['name'] = $this->form_params['backup_params']['schedule_name'];
242
- $schedule['recurrence'] = $this->form_params['backup_params']['schedule_frequency'];
243
- if (!isset($this->form_params['backup_params']['schedule_storage'])) {
244
- $this->form_params['backup_params']['schedule_storage'] = "";
245
- }
246
- $schedule['remote_storage'] = $this->form_params['backup_params']['schedule_storage'];
247
- //$schedule['backup_type'] = $this->form_params['backup_params']['backup_type'];
248
- $schedule['params'] = json_encode($this->form_params);
249
-
250
- if (!isset($_POST['id'])) {
251
- $wpdb->insert(
252
- $wpdb->prefix.'xcloner_scheduler',
253
- $schedule,
254
- array(
255
- '%s',
256
- '%s'
257
- )
258
- );
259
- } else {
260
- $wpdb->update(
261
- $wpdb->prefix.'xcloner_scheduler',
262
- $schedule,
263
- array('id' => $_POST['id']),
264
- array(
265
- '%s',
266
- '%s'
267
- )
268
- );
269
- }
270
- if (isset($_POST['id'])) {
271
- $scheduler->update_cron_hook($_POST['id']);
272
- }
273
-
274
- if ($wpdb->last_error) {
275
- $response['error'] = 1;
276
- $response['error_message'] = $wpdb->last_error/*."--".$wpdb->last_query*/
277
- ;
278
-
279
- }
280
-
281
- $scheduler->update_wp_cron_hooks();
282
- $response['finished'] = 1;
283
-
284
- $this->send_response($response);
285
- }
286
-
287
- /*
288
  *
289
  * Backup Files API
290
  *
291
  */
292
- public function backup_files()
293
- {
294
- $return = array();
295
- $additional = array();
296
-
297
- $this->check_access();
298
-
299
- $params = json_decode(stripslashes($_POST['data']));
300
-
301
- $init = (int)$_POST['init'];
302
-
303
- if ($params === null) {
304
- return $this->send_response('{"status":false,"msg":"The post_data parameter must be valid JSON"}');
305
- }
306
-
307
- $this->process_params($params);
308
-
309
- $return['finished'] = 1;
310
-
311
- //$return = $this->archive_system->start_incremental_backup($this->form_params['backup_params'], $this->form_params['extra'], $init);
312
- try {
313
- $return = $this->archive_system->start_incremental_backup($this->form_params['backup_params'],
314
- $this->form_params['extra'], $init);
315
- }catch (Exception $e) {
316
- $return = array();
317
- $return['error'] = true;
318
- $return['status'] = 500;
319
- $return['error_message'] = $e->getMessage();
320
-
321
- return $this->send_response($return, $hash = 1);
322
- }
323
-
324
- if ($return['finished']) {
325
- $return['extra']['backup_parent'] = $this->archive_system->get_archive_name_with_extension();
326
- if ($this->xcloner_file_system->is_part($this->archive_system->get_archive_name_with_extension())) {
327
- $return['extra']['backup_parent'] = $this->archive_system->get_archive_name_multipart();
328
- }
329
- }
330
-
331
- $data = $return;
332
-
333
- //check if backup is finished
334
- if ($return['finished']) {
335
- if (isset($this->form_params['backup_params']['email_notification']) and $to = $this->form_params['backup_params']['email_notification']) {
336
- try {
337
- $from = "";
338
- $subject = "";
339
- $additional['lines_total'] = $return['extra']['lines_total'];
340
- $this->archive_system->send_notification($to, $from, $subject, $return['extra']['backup_parent'],
341
- $this->form_params, "", $additional);
342
- }catch (Exception $e) {
343
- $this->logger->error($e->getMessage());
344
- }
345
- }
346
- $this->xcloner_file_system->remove_tmp_filesystem();
347
- }
348
-
349
- return $this->send_response($data, $hash = 1);
350
- }
351
-
352
- /*
 
 
 
 
 
 
 
 
 
 
353
  *
354
  * Backup Database API
355
  *
356
  */
357
- public function backup_database()
358
- {
359
- $data = array();
360
 
361
- $this->check_access();
362
 
363
- $params = json_decode(stripslashes($_POST['data']));
364
 
365
- $init = (int)$_POST['init'];
366
 
367
- if ($params === null) {
368
- return $this->send_response('{"status":false,"msg":"The post_data parameter must be valid JSON"}');
369
- }
370
 
371
- $this->process_params($params);
372
 
373
- //$xcloner_database = $this->init_db();
374
- $return = $this->xcloner_database->start_database_recursion($this->form_params['database'],
375
- $this->form_params['extra'], $init);
 
 
 
376
 
377
- if (isset($return['error']) and $return['error']) {
378
- $data['finished'] = 1;
379
- } else {
380
- $data['finished'] = $return['finished'];
381
- }
382
 
383
- $data['extra'] = $return;
384
 
385
- return $this->send_response($data, $hash = 1);
386
- }
387
 
388
- /*
389
  *
390
  * Scan Filesystem API
391
  *
392
  */
393
- public function scan_filesystem()
394
- {
395
- $data = array();
396
 
397
- $this->check_access();
398
 
399
- $params = json_decode(stripslashes($_POST['data']));
400
- $init = (int)$_POST['init'];
401
 
402
- if ($params === null) {
403
- $this->send_response('{"status":false,"msg":"The post_data parameter must be valid JSON"}');
404
- }
405
 
406
- $this->process_params($params);
 
 
407
 
408
- $this->xcloner_file_system->set_excluded_files($this->form_params['excluded_files']);
409
 
410
- $return = $this->xcloner_file_system->start_file_recursion($init);
411
 
412
- $data["finished"] = !$return;
413
- $data["total_files_num"] = $this->xcloner_file_system->get_scanned_files_num();
414
- $data["last_logged_file"] = $this->xcloner_file_system->last_logged_file();
415
- $data["total_files_size"] = sprintf("%.2f",
416
- $this->xcloner_file_system->get_scanned_files_total_size() / (1024 * 1024));
417
 
418
- return $this->send_response($data, $hash = 1);
419
- }
 
 
 
 
 
420
 
421
- /*
 
 
 
422
  *
423
  * Process params sent by the user
424
  *
425
  */
426
- private function process_params($params)
427
- {
428
- if (isset($params->hash)) {
429
- $this->xcloner_settings->set_hash($params->hash);
430
- }
431
-
432
- $this->form_params['extra'] = array();
433
- $this->form_params['backup_params'] = array();
434
-
435
- $this->form_params['database'] = array();
436
-
437
- if (isset($params->backup_params)) {
438
- foreach ($params->backup_params as $param) {
439
- $this->form_params['backup_params'][$param->name] = $this->xcloner_sanitization->sanitize_input_as_string($param->value);
440
- $this->logger->debug("Adding form parameter ".$param->name.".".$param->value."\n", array(
441
- 'POST',
442
- 'fields filter'
443
- ));
444
- }
445
- }
446
-
447
- $this->form_params['database'] = array();
448
-
449
- if (isset($params->table_params)) {
450
- foreach ($params->table_params as $param) {
451
- $this->form_params['database'][$param->parent][] = $this->xcloner_sanitization->sanitize_input_as_raw($param->id);
452
- $this->logger->debug("Adding database filter ".$param->parent.".".$param->id."\n", array(
453
- 'POST',
454
- 'database filter'
455
- ));
456
- }
457
- }
458
-
459
- $this->form_params['excluded_files'] = array();
460
- if (isset($params->files_params)) {
461
- foreach ($params->files_params as $param) {
462
- $this->form_params['excluded_files'][] = $this->xcloner_sanitization->sanitize_input_as_relative_path($param->id);
463
- }
464
-
465
- $unique_exclude_files = array();
466
-
467
- foreach ($params->files_params as $key => $param) {
468
- if (!in_array($param->parent, $this->form_params['excluded_files'])) {
469
- //$this->form_params['excluded_files'][] = $this->xcloner_sanitization->sanitize_input_as_relative_path($param->id);
470
- $unique_exclude_files[] = $param->id;
471
- $this->logger->debug("Adding file filter ".$param->id."\n", array(
472
- 'POST',
473
- 'exclude files filter'
474
- ));
475
- }
476
- }
477
- $this->form_params['excluded_files'] = (array)$unique_exclude_files;
478
-
479
- }
480
-
481
- //$this->form_params['excluded_files'] = array_merge($this->form_params['excluded_files'], $this->exclude_files_by_default);
482
-
483
- if (isset($params->extra)) {
484
- foreach ($params->extra as $key => $value) {
485
- $this->form_params['extra'][$key] = $this->xcloner_sanitization->sanitize_input_as_raw($value);
486
- }
487
- }
488
-
489
- if (isset($this->form_params['backup_params']['diff_start_date']) and $this->form_params['backup_params']['diff_start_date']) {
490
- $this->form_params['backup_params']['diff_start_date'] = strtotime($this->form_params['backup_params']['diff_start_date']);
491
- $this->xcloner_file_system->set_diff_timestamp_start($this->form_params['backup_params']['diff_start_date']);
492
- }
493
-
494
- return $this->xcloner_settings->get_hash();
495
- }
496
-
497
- /*
 
 
 
498
  *
499
  * Get file list for tree view API
500
  *
501
  */
502
- public function get_file_system_action()
503
- {
504
- $this->check_access();
505
-
506
- $folder = $this->xcloner_sanitization->sanitize_input_as_relative_path($_POST['id']);
507
-
508
- $data = array();
509
-
510
- if ($folder == "#") {
511
-
512
- $folder = "/";
513
- $data[] = array(
514
- 'id' => $folder,
515
- 'parent' => '#',
516
- 'text' => $this->xcloner_settings->get_xcloner_start_path(),
517
- //'children' => true,
518
- 'state' => array('selected' => false, 'opened' => true),
519
- 'icon' => plugin_dir_url(dirname(__FILE__))."/admin/assets/file-icon-root.png"
520
- );
521
- }
522
-
523
- try {
524
- $files = $this->xcloner_file_system->list_directory($folder);
525
- }catch (Exception $e) {
526
-
527
- print $e->getMessage();
528
- $this->logger->error($e->getMessage());
529
-
530
- return;
531
- }
532
-
533
- $type = array();
534
- foreach ($files as $key => $row) {
535
- $type[$key] = $row['type'];
536
- }
537
- array_multisort($type, SORT_ASC, $files);
538
-
539
- foreach ($files as $file) {
540
- $children = false;
541
- $text = $file['basename'];
542
-
543
- if ($file['type'] == "dir") {
544
- $children = true;
545
- } else {
546
- $text .= " (".$this->xcloner_requirements->file_format_size($file['size']).")";
547
- }
548
-
549
- if ($this->xcloner_file_system->is_excluded($file)) {
550
- $selected = true;
551
- } else {
552
- $selected = false;
553
- }
554
-
555
- $data[] = array(
556
- 'id' => $file['path'],
557
- 'parent' => $folder,
558
- 'text' => $text,
559
- //'title' => "test",
560
- 'children' => $children,
561
- 'state' => array('selected' => $selected, 'opened' => false, "checkbox_disabled" => $selected),
562
- 'icon' => plugin_dir_url(dirname(__FILE__))."/admin/assets/file-icon-".strtolower(substr($file['type'],
563
- 0, 1)).".png"
564
- );
565
- }
566
-
567
-
568
- return $this->send_response($data, 0);
569
- }
570
-
571
- /*
 
572
  *
573
  * Get databases/tables list for frontend tree display API
574
  *
575
  */
576
- public function get_database_tables_action()
577
- {
578
- $this->check_access();
579
-
580
- $database = $this->xcloner_sanitization->sanitize_input_as_raw($_POST['id']);
581
-
582
- $data = array();
583
-
584
- $xcloner_backup_only_wp_tables = $this->xcloner_settings->get_xcloner_option('xcloner_backup_only_wp_tables');
585
-
586
- if ($database == "#") {
587
- try {
588
- $return = $this->xcloner_database->get_all_databases();
589
- }catch (Exception $e) {
590
- $this->logger->error($e->getMessage());
591
- }
592
-
593
- foreach ($return as $database) {
594
- if ($xcloner_backup_only_wp_tables and $database['name'] != $this->xcloner_settings->get_db_database()) {
595
- continue;
596
- }
597
-
598
- $state = array();
599
-
600
- if ($database['name'] == $this->xcloner_settings->get_db_database()) {
601
- $state['selected'] = true;
602
- if ($database['num_tables'] < 25) {
603
- $state['opened'] = false;
604
- }
605
- }
606
-
607
- $data[] = array(
608
- 'id' => $database['name'],
609
- 'parent' => '#',
610
- 'text' => $database['name']." (".(int)$database['num_tables'].")",
611
- 'children' => true,
612
- 'state' => $state,
613
- 'icon' => plugin_dir_url(dirname(__FILE__))."/admin/assets/database-icon.png"
614
- );
615
- }
616
-
617
- } else {
618
-
619
- try {
620
- $return = $this->xcloner_database->list_tables($database, "", 1);
621
- }catch (Exception $e) {
622
- $this->logger->error($e->getMessage());
623
- }
624
-
625
- foreach ($return as $table) {
626
- $state = array();
627
-
628
- if ($xcloner_backup_only_wp_tables and !stristr($table['name'],
629
- $this->xcloner_settings->get_table_prefix())) {
630
- continue;
631
- }
632
-
633
- if (isset($database['name']) and $database['name'] == $this->xcloner_settings->get_db_database()) {
634
- $state = array('selected' => true);
635
- }
636
-
637
- $data[] = array(
638
- 'id' => $database.".".$table['name'],
639
- 'parent' => $database,
640
- 'text' => $table['name']." (".(int)$table['records'].")",
641
- 'children' => false,
642
- 'state' => $state,
643
- 'icon' => plugin_dir_url(dirname(__FILE__))."/admin/assets/table-icon.png"
644
- );
645
- }
646
- }
647
-
648
- return $this->send_response($data, 0);
649
- }
650
-
651
- /*
652
  *
653
  * Get schedule by id API
654
  *
655
  */
656
- public function get_schedule_by_id()
657
- {
658
- $this->check_access();
659
-
660
- $schedule_id = $this->xcloner_sanitization->sanitize_input_as_int($_GET['id']);
661
- $scheduler = $this->xcloner_scheduler;
662
- $data = $scheduler->get_schedule_by_id($schedule_id);
663
-
664
- $data['start_at'] = date("Y-m-d H:i",
665
- strtotime($data['start_at']) + (get_option('gmt_offset') * HOUR_IN_SECONDS));
666
- if (isset($data['backup_params']->diff_start_date) && $data['backup_params']->diff_start_date != "") {
667
- $data['backup_params']->diff_start_date = date("Y-m-d", ($data['backup_params']->diff_start_date));
668
- }
669
-
670
- return $this->send_response($data);
671
- }
672
-
673
- /*
 
 
674
  *
675
  * Get Schedule list API
676
  *
677
  */
678
- public function get_scheduler_list()
679
- {
680
- $return = array();
681
 
682
- $this->check_access();
683
 
684
- $scheduler = $this->xcloner_scheduler;
685
- $data = $scheduler->get_scheduler_list();
686
- $return['data'] = array();
687
 
688
- foreach ($data as $res) {
689
- $action = "<a href=\"#".$res->id."\" class=\"edit\" title='Edit'> <i class=\"material-icons \">edit</i></a>
690
  <a href=\"#" . $res->id."\" class=\"delete\" title='Delete'><i class=\"material-icons \">delete</i></a>";
691
- if ($res->status) {
692
- $status = '<i class="material-icons active status">timer</i>';
693
- } else {
694
- $status = '<i class="material-icons status inactive">timer_off</i>';
695
- }
696
-
697
- $next_run_time = wp_next_scheduled('xcloner_scheduler_'.$res->id, array($res->id));
698
-
699
- $next_run = date(get_option('date_format')." ".get_option('time_format'), $next_run_time);
700
-
701
- $remote_storage = $res->remote_storage;
702
-
703
- if (!$next_run_time >= time()) {
704
- $next_run = " ";
705
- }
706
-
707
- if (trim($next_run)) {
708
- $date_text = date(get_option('date_format')." ".get_option('time_format'),
709
- $next_run_time + (get_option('gmt_offset') * HOUR_IN_SECONDS));
710
-
711
- if ($next_run_time >= time()) {
712
- $next_run = "in ".human_time_diff($next_run_time, time());
713
- } else {
714
- $next_run = __("executed", 'xcloner-backup-and-restore');
715
- }
716
-
717
- $next_run = "<a href='#' title='".$date_text."'>".$next_run."</a>";
718
- //$next_run .=" ($date_text)";
719
- }
720
-
721
- $backup_text = "";
722
- $backup_size = "";
723
- $backup_time = "";
724
-
725
- if ($res->last_backup) {
726
- if ($this->xcloner_file_system->get_storage_filesystem()->has($res->last_backup)) {
727
- $metadata = $this->xcloner_file_system->get_storage_filesystem()->getMetadata($res->last_backup);
728
- $backup_size = size_format($this->xcloner_file_system->get_backup_size($res->last_backup));
729
- $backup_time = date(get_option('date_format')." ".get_option('time_format'),
730
- $metadata['timestamp'] + (get_option('gmt_offset') * HOUR_IN_SECONDS));
731
- }
732
-
733
- $backup_text = "<span title='".$backup_time."' class='shorten_string'>".$res->last_backup." (".$backup_size.")</span>";
734
- }
735
-
736
- $schedules = wp_get_schedules();
737
-
738
- if (isset($schedules[$res->recurrence])) {
739
- $res->recurrence = $schedules[$res->recurrence]['display'];
740
- }
741
-
742
- $return['data'][] = array(
743
- $res->id,
744
- $res->name,
745
- $res->recurrence, /*$res->start_at,*/
746
- $next_run,
747
- $remote_storage,
748
- $backup_text,
749
- $status,
750
- $action
751
- );
752
- }
753
-
754
- return $this->send_response($return, 0);
755
- }
756
-
757
- /*
 
 
 
 
758
  *
759
  * Delete Schedule by ID API
760
  *
761
  */
762
- public function delete_schedule_by_id()
763
- {
764
- $data = array();
765
 
766
- $this->check_access();
767
 
768
- $schedule_id = $this->xcloner_sanitization->sanitize_input_as_int($_GET['id']);
769
- $scheduler = $this->xcloner_scheduler;
770
- $data['finished'] = $scheduler->delete_schedule_by_id($schedule_id);
771
 
772
- return $this->send_response($data);
773
- }
774
 
775
- /*
776
  *
777
  * Delete backup by name from the storage path
778
  *
779
  */
780
- public function delete_backup_by_name()
781
- {
782
- $data = array();
783
-
784
- $this->check_access();
785
-
786
- $backup_name = $this->xcloner_sanitization->sanitize_input_as_string($_POST['name']);
787
- $storage_selection = $this->xcloner_sanitization->sanitize_input_as_string($_POST['storage_selection']);
788
-
789
- $data['finished'] = $this->xcloner_file_system->delete_backup_by_name($backup_name, $storage_selection);
790
-
791
- return $this->send_response($data);
792
- }
793
-
794
- /**
795
- * API Incremental Backup Encryption Method
796
- */
797
- public function backup_encryption()
798
- {
799
- $this->check_access();
800
-
801
- $backup_parts = array();
802
- $return = array();
803
-
804
-
805
- if (isset($_POST['data'])) {
806
- $params = json_decode(stripslashes($_POST['data']));
807
-
808
- $this->process_params($params);
809
- $source_backup_file = $this->xcloner_sanitization->sanitize_input_as_string($this->form_params['extra']['backup_parent']);
810
-
811
- if (isset($this->form_params['extra']['start'])) {
812
- $start = $this->xcloner_sanitization->sanitize_input_as_int($this->form_params['extra']['start']);
813
- } else {
814
- $start = 0;
815
- }
816
-
817
- if (isset($this->form_params['extra']['iv'])) {
818
- $iv = $this->xcloner_sanitization->sanitize_input_as_raw($this->form_params['extra']['iv']);
819
- } else {
820
- $iv = "";
821
- }
822
-
823
- if (isset($this->form_params['extra']['part'])) {
824
- $return['part'] = (int)$this->xcloner_sanitization->sanitize_input_as_int($this->form_params['extra']['part']);
825
- } else {
826
- $return['part'] = 0;
827
- }
828
-
829
- } else {
830
- $source_backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
831
- $start = $this->xcloner_sanitization->sanitize_input_as_int($_POST['start']);
832
- $iv = $this->xcloner_sanitization->sanitize_input_as_raw($_POST['iv']);
833
- $return['part'] = (int)$this->xcloner_sanitization->sanitize_input_as_int($_POST['part']);
834
- }
835
-
836
- $backup_file = $source_backup_file;
837
-
838
- if ($this->xcloner_file_system->is_multipart($backup_file)) {
839
- $backup_parts = $this->xcloner_file_system->get_multipart_files($backup_file);
840
- $backup_file = $backup_parts[$return['part']];
841
- }
842
-
843
- $return['processing_file'] = $backup_file;
844
- $return['total_size'] = filesize($this->xcloner_settings->get_xcloner_store_path().DS.$backup_file);
845
-
846
- try {
847
- $this->logger->info(json_encode($_POST));
848
- $this->logger->info($iv);
849
- $return = array_merge($return,
850
- $this->xcloner_encryption->encrypt_file($backup_file, "", "", $start, base64_decode($iv)));
851
- }catch (\Exception $e) {
852
- $return['error'] = true;
853
- $return['message'] = $e->getMessage();
854
- $return['error_message'] = $e->getMessage();
855
- }
856
-
857
- //echo strlen($return['iv']);exit;
858
-
859
- if (isset($return['finished']) && $return['finished']) {
860
- if ($this->xcloner_file_system->is_multipart($source_backup_file)) {
861
- $return['start'] = 0;
862
-
863
- ++$return['part'];
864
-
865
- if ($return['part'] < sizeof($backup_parts)) {
866
- $return['finished'] = 0;
867
- }
868
-
869
- }
870
- }
871
-
872
- if (isset($_POST['data'])) {
873
- $return['extra'] = array_merge($this->form_params['extra'], $return);
874
- }
875
-
876
- $this->send_response($return, 0);
877
- }
878
 
879
- /**
880
- * API Incremental Backup Decryption Method
881
- */
882
- public function backup_decryption()
883
- {
884
- $this->check_access();
885
 
886
- $backup_parts = array();
887
- $return = array();
888
 
889
- $source_backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
890
- $start = $this->xcloner_sanitization->sanitize_input_as_int($_POST['start']);
891
- $iv = $this->xcloner_sanitization->sanitize_input_as_raw($_POST['iv']);
892
- $decryption_key = $this->xcloner_sanitization->sanitize_input_as_raw($_POST['decryption_key']); ;
893
- $return['part'] = $this->xcloner_sanitization->sanitize_input_as_int($_POST['part']);
894
 
895
- $backup_file = $source_backup_file;
 
896
 
897
- if ($this->xcloner_file_system->is_multipart($backup_file)) {
898
- $backup_parts = $this->xcloner_file_system->get_multipart_files($backup_file);
899
- $backup_file = $backup_parts[$return['part']];
900
- }
901
-
902
- $return['processing_file'] = $backup_file;
903
- $return['total_size'] = filesize($this->xcloner_settings->get_xcloner_store_path().DS.$backup_file);
904
-
905
- try {
906
- $return = array_merge($return,
907
- $this->xcloner_encryption->decrypt_file($backup_file, "", $decryption_key, $start, base64_decode($iv)));
908
- }catch (\Exception $e) {
909
- $return['error'] = true;
910
- $return['message'] = $e->getMessage();
911
- }
912
-
913
- if ($return['finished']) {
914
- if ($this->xcloner_file_system->is_multipart($source_backup_file)) {
915
- $return['start'] = 0;
916
-
917
- ++$return['part'];
918
-
919
- if ($return['part'] < sizeof($backup_parts)) {
920
- $return['finished'] = 0;
921
- }
922
-
923
- }
924
- }
925
-
926
- $this->send_response($return, 0);
927
- }
928
-
929
- public function get_manage_backups_list() {
930
-
931
- $this->check_access();
932
-
933
- $return = array();
934
- $storage_selection = "";
935
-
936
- if (isset($_GET['storage_selection']) and $_GET['storage_selection']) {
937
- $storage_selection = $this->xcloner_sanitization->sanitize_input_as_string($_GET['storage_selection']);
938
- }
939
- $available_storages = $this->xcloner_remote_storage->get_available_storages();
940
-
941
- $backup_list = $this->xcloner_file_system->get_backup_archives_list($storage_selection);
942
-
943
- $i = -1;
944
- foreach ($backup_list as $file_info):?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
945
  <?php
946
- if ($storage_selection == "gdrive") {
947
- $file_info['path'] = $file_info['filename'].".".$file_info['extension'];
948
- }
949
- $file_exists_on_local_storage = true;
950
-
951
- if ($storage_selection) {
952
- if (!$this->xcloner_file_system->get_storage_filesystem()->has($file_info['path'])) {
953
- $file_exists_on_local_storage = false;
954
- }
955
- }
956
-
957
- ?>
958
  <?php if (!isset($file_info['parent'])): ?>
959
 
960
  <?php ob_start(); ?>
@@ -964,21 +964,22 @@ class Xcloner_Api
964
  <label for="checkbox_<?php echo $i ?>">&nbsp;</label>
965
  </p>
966
  <?php
967
- $return['data'][$i][] = ob_get_contents();
968
- ob_end_clean();
969
- ?>
970
 
971
  <?php ob_start(); ?>
972
  <span class=""><?php echo $file_info['path'] ?></span>
973
  <?php if (!$file_exists_on_local_storage): ?>
974
  <a href="#"
975
- title="<?php echo __("File does not exists on local storage",
976
- "xcloner-backup-and-restore") ?>"><i
 
 
977
  class="material-icons backup_warning">warning</i></a>
978
  <?php endif ?>
979
  <?php
980
- if (isset($file_info['childs']) and is_array($file_info['childs'])):
981
- ?>
982
  <a href="#" title="expand" class="expand-multipart add"><i
983
  class="material-icons">add</i></a>
984
  <a href="#" title="collapse" class="expand-multipart remove"><i class="material-icons">remove</i></a>
@@ -987,17 +988,18 @@ class Xcloner_Api
987
  <li>
988
  <?php echo $child[0] ?> (<?php echo esc_html(size_format($child[2])) ?>)
989
  <?php
990
- $child_exists_on_local_storage = true;
991
- if ($storage_selection) {
992
- if (!$this->xcloner_file_system->get_storage_filesystem()->has($child[0])) {
993
- $child_exists_on_local_storage = false;
994
- }
995
- }
996
- ?>
997
  <?php if (!$child_exists_on_local_storage): ?>
998
  <a href="#"
999
- title="<?php echo __("File does not exists on local storage",
1000
- "xcloner-backup-and-restore") ?>"><i
 
 
1001
  class="material-icons backup_warning">warning</i></a>
1002
  <?php endif ?>
1003
  <?php if (!$storage_selection) : ?>
@@ -1011,8 +1013,10 @@ class Xcloner_Api
1011
  </a>
1012
  <?php else: ?>
1013
  <a href="#<?php echo $child[0] ?>" class="list-backup-content"
1014
- title="<?php echo __('List Backup Content',
1015
- 'xcloner-backup-and-restore') ?>"><i
 
 
1016
  class="material-icons">folder_open</i></a>
1017
 
1018
  <a href="#<?php echo $child[0] ?>" class="backup-encryption"
@@ -1023,8 +1027,10 @@ class Xcloner_Api
1023
 
1024
  <?php elseif ($storage_selection != "gdrive" && !$this->xcloner_file_system->get_storage_filesystem()->has($child[0])): ?>
1025
  <a href="#<?php echo $child[0] ?>" class="copy-remote-to-local"
1026
- title="<?php echo __('Push Backup To Local Storage',
1027
- 'xcloner-backup-and-restore') ?>"><i
 
 
1028
  class="material-icons">file_upload</i></a>
1029
  <?php endif ?>
1030
  </li>
@@ -1032,24 +1038,21 @@ class Xcloner_Api
1032
  </ul>
1033
  <?php endif; ?>
1034
  <?php
1035
- $return['data'][$i][] = ob_get_contents();
1036
- ob_end_clean();
1037
- ?>
1038
  <?php ob_start(); ?>
1039
- <?php if (isset($file_info['timestamp']))
1040
- echo date("Y-m-d H:i", $file_info['timestamp'])
1041
- ?>
1042
  <?php
1043
- $return['data'][$i][] = ob_get_contents();
1044
- ob_end_clean();
1045
- ?>
1046
 
1047
  <?php ob_start(); ?>
1048
  <?php echo esc_html(size_format($file_info['size'])) ?>
1049
  <?php
1050
- $return['data'][$i][] = ob_get_contents();
1051
- ob_end_clean();
1052
- ?>
1053
 
1054
  <?php ob_start(); ?>
1055
  <?php if (!$storage_selection): ?>
@@ -1059,15 +1062,17 @@ class Xcloner_Api
1059
 
1060
  <?php if (sizeof($available_storages)): ?>
1061
  <a href="#<?php echo $file_info['basename'] ?>" class="cloud-upload"
1062
- title="<?php echo __('Send Backup To Remote Storage',
1063
- 'xcloner-backup-and-restore') ?>"><i
 
 
1064
  class="material-icons">cloud_upload</i></a>
1065
  <?php endif ?>
1066
  <?php
1067
- $basename = $file_info['basename'];
1068
- if (isset($file_info['childs']) and sizeof($file_info['childs']))
1069
- $basename = $file_info['childs'][0][0];
1070
- ?>
1071
  <?php if ($this->xcloner_encryption->is_encrypted_file($basename)) :?>
1072
  <a href="#<?php echo $file_info['basename'] ?>" class="backup-decryption"
1073
  title="<?php echo __('Backup Decryption', 'xcloner-backup-and-restore') ?>">
@@ -1096,196 +1101,193 @@ class Xcloner_Api
1096
  <?php endif ?>
1097
 
1098
  <?php
1099
- $return['data'][$i][] = ob_get_contents();
1100
- ob_end_clean(); ?>
1101
 
1102
  <?php endif ?>
1103
  <?php endforeach ?>
1104
  <?php
1105
- $this->send_response($return, 0);
1106
- }
1107
-
1108
- /**
1109
- * API method to list internal backup files
1110
- */
1111
- public function list_backup_files()
1112
- {
1113
- $this->check_access();
1114
-
1115
- $backup_parts = array();
1116
- $return = array();
1117
-
1118
- $source_backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
1119
- $start = $this->xcloner_sanitization->sanitize_input_as_int($_POST['start']);
1120
- $return['part'] = $this->xcloner_sanitization->sanitize_input_as_int($_POST['part']);
1121
-
1122
- $backup_file = $source_backup_file;
1123
-
1124
- if ($this->xcloner_file_system->is_multipart($backup_file)) {
1125
- $backup_parts = $this->xcloner_file_system->get_multipart_files($backup_file);
1126
- $backup_file = $backup_parts[$return['part']];
1127
- }
1128
-
1129
- if ($this->xcloner_encryption->is_encrypted_file($backup_file)) {
1130
- $return['error'] = true;
1131
- $return['message'] = __("Backup archive is encrypted, please decrypt it first before you can list it's content.", "xcloner-backup-and-restore");
1132
- $this->send_response($return, 0);
1133
- }
1134
-
1135
- try {
1136
- $tar = new Tar();
1137
- $tar->open($this->xcloner_settings->get_xcloner_store_path().DS.$backup_file, $start);
1138
-
1139
- $data = $tar->contents(get_option('xcloner_files_to_process_per_request'));
1140
- }catch (Exception $e) {
1141
- $return['error'] = true;
1142
- $return['message'] = $e->getMessage();
1143
- $this->send_response($return, 0);
1144
- }
1145
-
1146
- $return['files'] = array();
1147
- $return['finished'] = 1;
1148
- $return['total_size'] = filesize($this->xcloner_settings->get_xcloner_store_path().DS.$backup_file);
1149
- $i = 0;
1150
-
1151
- if (isset($data['extracted_files']) and is_array($data['extracted_files'])) {
1152
- foreach ($data['extracted_files'] as $file) {
1153
- $return['files'][$i]['path'] = $file->getPath();
1154
- $return['files'][$i]['size'] = $file->getSize();
1155
- $return['files'][$i]['mtime'] = date(get_option('date_format')." ".get_option('time_format'),
1156
- $file->getMtime());
1157
-
1158
- $i++;
1159
- }
1160
- }
1161
-
1162
- if (isset($data['start'])) {
1163
- $return['start'] = $data['start'];
1164
- $return['finished'] = 0;
1165
- } else {
1166
- if ($this->xcloner_file_system->is_multipart($source_backup_file)) {
1167
- $return['start'] = 0;
1168
-
1169
- ++$return['part'];
1170
-
1171
- if ($return['part'] < sizeof($backup_parts)) {
1172
- $return['finished'] = 0;
1173
- }
1174
-
1175
- }
1176
- }
1177
-
1178
- $this->send_response($return, 0);
1179
- }
1180
-
1181
- /*
 
 
1182
  * Copy remote backup to local storage
1183
  */
1184
- public function copy_backup_remote_to_local()
1185
- {
1186
-
1187
- $this->check_access();
1188
-
1189
- $backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
1190
- $storage_type = $this->xcloner_sanitization->sanitize_input_as_string($_POST['storage_type']);
1191
-
1192
- $xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
1193
 
1194
- $return = array();
 
1195
 
1196
- try {
1197
- if (method_exists($xcloner_remote_storage, "copy_backup_remote_to_local")) {
1198
- $return = call_user_func_array(array(
1199
- $xcloner_remote_storage,
1200
- "copy_backup_remote_to_local"
1201
- ), array($backup_file, $storage_type));
1202
- }
1203
- }catch (Exception $e) {
1204
 
1205
- $return['error'] = 1;
1206
- $return['message'] = $e->getMessage();
1207
- }
1208
 
1209
- if (!$return) {
1210
- $return['error'] = 1;
1211
- $return['message'] = "Upload failed, please check the error log for more information!";
1212
- }
 
 
 
 
 
 
 
1213
 
 
 
 
 
1214
 
1215
- $this->send_response($return, 0);
1216
 
1217
- }
 
1218
 
1219
- /*
1220
  *
1221
  * Upload backup to remote API
1222
  *
1223
  */
1224
- public function upload_backup_to_remote()
1225
- {
1226
- $this->check_access();
1227
 
1228
- $return = array();
1229
 
1230
- $backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
1231
- $storage_type = $this->xcloner_sanitization->sanitize_input_as_string($_POST['storage_type']);
1232
 
1233
- $xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
1234
 
1235
- try {
1236
- if (method_exists($xcloner_remote_storage, "upload_backup_to_storage")) {
1237
- $return = call_user_func_array(array(
1238
- $xcloner_remote_storage,
1239
- "upload_backup_to_storage"
1240
- ), array($backup_file, $storage_type));
1241
- }
1242
- }catch (Exception $e) {
 
 
 
1243
 
1244
- $return['error'] = 1;
1245
- $return['message'] = $e->getMessage();
1246
- }
 
1247
 
1248
- if (!$return) {
1249
- $return['error'] = 1;
1250
- $return['message'] = "Upload failed, please check the error log for more information!";
1251
- }
1252
 
 
 
1253
 
1254
- $this->send_response($return, 0);
1255
-
1256
- }
1257
-
1258
- /*
1259
  *
1260
  * Remote Storage Status Save
1261
  *
1262
  */
1263
- public function remote_storage_save_status()
1264
- {
1265
- $this->check_access();
1266
 
1267
- $return = array();
1268
 
1269
- $xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
1270
 
1271
- $return['finished'] = $xcloner_remote_storage->change_storage_status($_POST['id'], $_POST['value']);
1272
 
1273
- $this->send_response($return, 0);
1274
- }
1275
 
1276
 
1277
- public function download_restore_script()
1278
- {
1279
- $this->check_access();
1280
 
1281
- ob_end_clean();
1282
 
1283
- $adapter = new Local(dirname(__DIR__), LOCK_EX, '0001');
1284
- $xcloner_plugin_filesystem = new Filesystem($adapter, new Config([
1285
- 'disable_asserts' => true,
1286
- ]));
1287
 
1288
- /* Generate PHAR FILE
1289
  $file = 'restore/vendor.built';
1290
 
1291
  if(file_exists($file))
@@ -1301,185 +1303,178 @@ class Xcloner_Api
1301
  $phar2->setStub($phar2->createDefaultStub('vendor/autoload.php', 'vendor/autoload.php'));
1302
  * */
1303
 
1304
- $tmp_file = $this->xcloner_settings->get_xcloner_tmp_path().DS."xcloner-restore.tgz";
1305
 
1306
- $tar = new Tar();
1307
- $tar->create($tmp_file);
1308
 
1309
- $tar->addFile(dirname(__DIR__)."/restore/vendor.build.txt", "vendor.phar");
1310
- //$tar->addFile(dirname(__DIR__)."/restore/vendor.tgz", "vendor.tgz");
1311
 
1312
- $files = $xcloner_plugin_filesystem->listContents("vendor/", true);
1313
- foreach ($files as $file) {
1314
- $tar->addFile(dirname(__DIR__).DS.$file['path'], $file['path']);
1315
- }
1316
 
1317
- $content = file_get_contents(dirname(__DIR__)."/restore/xcloner_restore.php");
1318
- $content = str_replace("define('AUTH_KEY', '');", "define('AUTH_KEY', '".md5(AUTH_KEY)."');", $content);
1319
 
1320
- $tar->addData("xcloner_restore.php", $content);
1321
 
1322
- $tar->close();
1323
 
1324
- if (file_exists($tmp_file)) {
1325
- header('Content-Description: File Transfer');
1326
- header('Content-Type: application/octet-stream');
1327
- header('Content-Disposition: attachment; filename="'.basename($tmp_file).'"');
1328
- header('Expires: 0');
1329
- header('Cache-Control: must-revalidate');
1330
- header('Pragma: public');
1331
- header('Content-Length: '.filesize($tmp_file));
1332
- readfile($tmp_file);
 
1333
 
1334
- }
 
 
 
 
1335
 
1336
- try {
1337
- unlink($tmp_file);
1338
- }catch (Exception $e) {
1339
- //We are not interested in the error here
1340
- }
1341
 
1342
- die();
1343
- }
1344
-
1345
- /*
1346
  *
1347
  * Download backup by Name from the Storage Path
1348
  *
1349
  */
1350
- public function download_backup_by_name()
1351
- {
1352
- $this->check_access();
1353
-
1354
- ob_end_clean();
1355
 
1356
- $backup_name = $this->xcloner_sanitization->sanitize_input_as_string($_GET['name']);
1357
 
 
1358
 
1359
- $metadata = $this->xcloner_file_system->get_storage_filesystem()->getMetadata($backup_name);
1360
- $read_stream = $this->xcloner_file_system->get_storage_filesystem()->readStream($backup_name);
1361
 
 
 
1362
 
1363
- header('Pragma: public');
1364
- header('Expires: 0');
1365
- header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
1366
- header('Cache-Control: private', false);
1367
- header('Content-Transfer-Encoding: binary');
1368
- header('Content-Disposition: attachment; filename="'.$metadata['path'].'";');
1369
- header('Content-Type: application/octet-stream');
1370
- header('Content-Length: '.$metadata['size']);
1371
 
1372
- ob_end_clean();
 
 
 
 
 
 
 
1373
 
1374
- $chunkSize = 1024 * 1024;
1375
- while (!feof($read_stream)) {
1376
- $buffer = fread($read_stream, $chunkSize);
1377
- echo $buffer;
1378
- }
1379
- fclose($read_stream);
1380
 
1381
- wp_die();
 
 
 
 
 
1382
 
1383
- }
 
1384
 
1385
- /*
1386
  * Restore upload backup
1387
  */
1388
- public function restore_upload_backup()
1389
- {
1390
- $this->check_access();
1391
 
1392
- $return = array();
1393
 
1394
- $return['part'] = 0;
1395
- $return['total_parts'] = 0;
1396
- $return['uploaded_size'] = 0;
1397
- $is_multipart = 0;
1398
 
1399
- $file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
1400
- $hash = $this->xcloner_sanitization->sanitize_input_as_string($_POST['hash']);
1401
 
1402
- if (isset($_POST['part'])) {
1403
- $return['part'] = $this->xcloner_sanitization->sanitize_input_as_int($_POST['part']);
1404
- }
1405
 
1406
- if (isset($_POST['uploaded_size'])) {
1407
- $return['uploaded_size'] = $this->xcloner_sanitization->sanitize_input_as_int($_POST['uploaded_size']);
1408
- }
1409
 
1410
- $start = $this->xcloner_sanitization->sanitize_input_as_string($_POST['start']);
1411
- $target_url = $this->xcloner_sanitization->sanitize_input_as_string($_POST['target_url']);
1412
 
1413
- $return['total_size'] = $this->xcloner_file_system->get_backup_size($file);
1414
 
1415
- if ($this->xcloner_file_system->is_multipart($file)) {
1416
- $backup_parts = $this->xcloner_file_system->get_multipart_files($file);
1417
 
1418
- $return['total_parts'] = sizeof($backup_parts) + 1;
1419
 
1420
- if ($return['part'] and isset($backup_parts[$return['part'] - 1])) {
1421
- $file = $backup_parts[$return['part'] - 1];
1422
- }
1423
 
1424
- $is_multipart = 1;
1425
- }
1426
 
1427
- try {
 
 
 
 
 
 
 
 
 
 
1428
 
1429
- $xcloner_file_transfer = $this->get_xcloner_container()->get_xcloner_file_transfer();
1430
- $xcloner_file_transfer->set_target($target_url);
1431
- $return['start'] = $xcloner_file_transfer->transfer_file($file, $start, $hash);
1432
 
1433
- }catch (Exception $e) {
 
 
 
 
1434
 
1435
- $return = array();
1436
- $return['error'] = true;
1437
- $return['status'] = 500;
1438
- $return['message'] = "CURL communication error with the restore host. ".$e->getMessage();
1439
- $this->send_response($return, 0);
1440
 
1441
- }
1442
-
1443
- $return['status'] = 200;
1444
-
1445
- //we have finished the upload
1446
- if (!$return['start'] and $is_multipart) {
1447
- $return['part']++;
1448
- $return['uploaded_size'] += $this->xcloner_file_system->get_storage_filesystem()->getSize($file);
1449
- }
1450
-
1451
- $this->send_response($return, 0);
1452
- }
1453
-
1454
- /*
1455
  * Restore backup
1456
  */
1457
- public function restore_backup()
1458
- {
1459
- $this->check_access();
1460
 
1461
- define("XCLONER_PLUGIN_ACCESS", 1);
1462
- include_once(dirname(__DIR__).DS."restore".DS."xcloner_restore.php");
1463
 
1464
- return;
1465
- }
1466
 
1467
- /*
1468
  *
1469
  * Send the json response back
1470
  *
1471
  */
1472
- private function send_response($data, $attach_hash = 1)
1473
- {
1474
-
1475
- if ($attach_hash and null !== $this->xcloner_settings->get_hash()) {
1476
- $data['hash'] = $this->xcloner_settings->get_hash();
1477
- }
1478
-
1479
- if (ob_get_length()) {
1480
- ob_clean();
1481
- }
1482
- wp_send_json($data);
1483
-
1484
- }
1485
  }
36
  use splitbrain\PHPArchive\Archive;
37
  use splitbrain\PHPArchive\FileInfo;
38
 
 
39
  /**
40
  * XCloner Api Class
41
  */
42
  class Xcloner_Api
43
  {
44
+ private $xcloner_database;
45
+ private $xcloner_settings;
46
+ private $xcloner_file_system;
47
+ private $xcloner_scheduler;
48
+ private $xcloner_requirements;
49
+ private $xcloner_sanitization;
50
+ private $xcloner_encryption;
51
+ private $xcloner_remote_storage;
52
+ private $archive_system;
53
+ private $form_params;
54
+ private $logger;
55
+ private $xcloner_container;
56
+
57
+ /**
58
+ * XCloner_Api construct class
59
+ *
60
+ * @param Xcloner $xcloner_container [description]
61
+ */
62
+ public function __construct(Xcloner $xcloner_container)
63
+ {
64
+ //global $wpdb;
65
+
66
+ if (defined('WP_DEBUG') && WP_DEBUG) {
67
+ error_reporting(0);
68
+ }
69
+
70
+ if (ob_get_length()) {
71
+ ob_end_clean();
72
+ }
73
+ ob_start();
74
+
75
+ $this->xcloner_container = $xcloner_container;
76
+
77
+ $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
78
+ $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_api");
79
+ $this->xcloner_file_system = $xcloner_container->get_xcloner_filesystem();
80
+ $this->xcloner_sanitization = $xcloner_container->get_xcloner_sanitization();
81
+ $this->xcloner_requirements = $xcloner_container->get_xcloner_requirements();
82
+ $this->archive_system = $xcloner_container->get_archive_system();
83
+ $this->xcloner_database = $xcloner_container->get_xcloner_database();
84
+ $this->xcloner_scheduler = $xcloner_container->get_xcloner_scheduler();
85
+ $this->xcloner_encryption = $xcloner_container->get_xcloner_encryption();
86
+ $this->xcloner_remote_storage = $xcloner_container->get_xcloner_remote_storage();
87
+
88
+ $this->xcloner_database->show_errors = false;
89
+
90
+ if (isset($_POST['API_ID'])) {
91
+ $this->logger->info("Processing ajax request ID ".substr(
92
+ $this->xcloner_sanitization->sanitize_input_as_string($_POST['API_ID']),
93
+ 0,
94
+ 15
95
+ ));
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Get XCloner Container
101
+ * @return XCloner return the XCloner container
102
+ */
103
+ public function get_xcloner_container()
104
+ {
105
+ return $this->xcloner_container;
106
+ }
107
+
108
+
109
+ /**
110
+ * Checks API access
111
+ */
112
+ private function check_access()
113
+ {
114
+ if (function_exists('current_user_can') && !current_user_can('manage_options')) {
115
+ $this->send_response(json_encode("Not allowed access here!"));
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Initialize the database connection
121
+ */
122
+ public function init_db()
123
+ {
124
+ return;
125
+ }
126
 
127
+ /*
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  * Save Schedule API
129
  */
130
+ public function save_schedule()
131
+ {
132
+ //global $wpdb;
133
+
134
+ $this->check_access();
135
+
136
+ $scheduler = $this->xcloner_scheduler;
137
+ $params = array();
138
+ $schedule = array();
139
+ $response = array();
140
+
141
+ if (isset($_POST['data'])) {
142
+ $params = json_decode(stripslashes($_POST['data']));
143
+ }
144
+
145
+ $this->process_params($params);
146
+
147
+ if (isset($_POST['id'])) {
148
+ $this->form_params['backup_params']['backup_name'] = $this->xcloner_sanitization->sanitize_input_as_string($_POST['backup_name']);
149
+ $this->form_params['backup_params']['email_notification'] = $this->xcloner_sanitization->sanitize_input_as_string($_POST['email_notification']);
150
+ if ($_POST['diff_start_date']) {
151
+ $this->form_params['backup_params']['diff_start_date'] = strtotime($this->xcloner_sanitization->sanitize_input_as_string($_POST['diff_start_date']));
152
+ } else {
153
+ $this->form_params['backup_params']['diff_start_date'] = "";
154
+ }
155
+ $this->form_params['backup_params']['schedule_name'] = $this->xcloner_sanitization->sanitize_input_as_string($_POST['schedule_name']);
156
+ $this->form_params['backup_params']['backup_encrypt'] = $this->xcloner_sanitization->sanitize_input_as_int($_POST['backup_encrypt']);
157
+ $this->form_params['backup_params']['start_at'] = strtotime($_POST['schedule_start_date']);
158
+ $this->form_params['backup_params']['schedule_frequency'] = $this->xcloner_sanitization->sanitize_input_as_string($_POST['schedule_frequency']);
159
+ $this->form_params['backup_params']['schedule_storage'] = $this->xcloner_sanitization->sanitize_input_as_string($_POST['schedule_storage']);
160
+ $this->form_params['database'] = (stripslashes($this->xcloner_sanitization->sanitize_input_as_raw($_POST['table_params'])));
161
+ $this->form_params['excluded_files'] = (stripslashes($this->xcloner_sanitization->sanitize_input_as_raw($_POST['excluded_files'])));
162
+
163
+ //$this->form_params['backup_params']['backup_type'] = $this->xcloner_sanitization->sanitize_input_as_string($_POST['backup_type']);
164
+
165
+ $tables = explode(PHP_EOL, $this->form_params['database']);
166
+ $return = array();
167
+
168
+ foreach ($tables as $table) {
169
+ $table = str_replace("\r", "", $table);
170
+ $data = explode(".", $table);
171
+ if (isset($data[1])) {
172
+ $return[$data[0]][] = $data[1];
173
+ }
174
+ }
175
+
176
+ $this->form_params['database'] = ($return);
177
+
178
+ $excluded_files = explode(PHP_EOL, $this->form_params['excluded_files']);
179
+ $return = array();
180
+
181
+ foreach ($excluded_files as $file) {
182
+ $file = str_replace("\r", "", $file);
183
+ if ($file) {
184
+ $return[] = $file;
185
+ }
186
+ }
187
+
188
+ $this->form_params['excluded_files'] = ($return);
189
+
190
+ $schedule['start_at'] = $this->form_params['backup_params']['start_at'];
191
+
192
+ if (!isset($_POST['status'])) {
193
+ $schedule['status'] = 0;
194
+ } else {
195
+ $schedule['status'] = $this->xcloner_sanitization->sanitize_input_as_int($_POST['status']);
196
+ }
197
+ } else {
198
+ $schedule['status'] = 1;
199
+ $schedule['start_at'] = strtotime($this->form_params['backup_params']['schedule_start_date'].
200
+ " ".$this->form_params['backup_params']['schedule_start_time']);
201
+
202
+ if ($schedule['start_at'] <= time()) {
203
+ $schedule['start_at'] = "";
204
+ }
205
+ }
206
+
207
+ if (!$schedule['start_at']) {
208
+ $schedule['start_at'] = date('Y-m-d H:i:s', time());
209
+ } else {
210
+ $schedule['start_at'] = date(
211
+ 'Y-m-d H:i:s',
212
+ $schedule['start_at'] - ($this->xcloner_settings->get_xcloner_option('gmt_offset') * HOUR_IN_SECONDS)
213
+ );
214
+ }
215
+
216
+ $schedule['name'] = $this->form_params['backup_params']['schedule_name'];
217
+ $schedule['recurrence'] = $this->form_params['backup_params']['schedule_frequency'];
218
+ if (!isset($this->form_params['backup_params']['schedule_storage'])) {
219
+ $this->form_params['backup_params']['schedule_storage'] = "";
220
+ }
221
+ $schedule['remote_storage'] = $this->form_params['backup_params']['schedule_storage'];
222
+ //$schedule['backup_type'] = $this->form_params['backup_params']['backup_type'];
223
+ $schedule['params'] = json_encode($this->form_params);
224
+
225
+ if (!isset($_POST['id'])) {
226
+ $this->xcloner_database->insert(
227
+ $this->xcloner_settings->get_table_prefix().'xcloner_scheduler',
228
+ $schedule,
229
+ array(
230
+ '%s',
231
+ '%s'
232
+ )
233
+ );
234
+ } else {
235
+ $this->xcloner_database->update(
236
+ $this->xcloner_settings->get_table_prefix().'xcloner_scheduler',
237
+ $schedule,
238
+ array('id' => $_POST['id']),
239
+ array(
240
+ '%s',
241
+ '%s'
242
+ )
243
+ );
244
+ }
245
+ if (isset($_POST['id'])) {
246
+ $scheduler->update_cron_hook($_POST['id']);
247
+ }
248
+
249
+ if ($this->xcloner_database->last_error) {
250
+ $response['error'] = 1;
251
+ $response['error_message'] = $this->xcloner_database->last_error/*."--".$this->xcloner_database->last_query*/
252
+ ;
253
+ }
254
+
255
+ $scheduler->update_wp_cron_hooks();
256
+ $response['finished'] = 1;
257
+
258
+ $this->send_response($response);
259
+ }
260
+
261
+ /*
 
262
  *
263
  * Backup Files API
264
  *
265
  */
266
+ public function backup_files()
267
+ {
268
+ $return = array();
269
+ $additional = array();
270
+
271
+ $this->check_access();
272
+
273
+ $params = json_decode(stripslashes($_POST['data']));
274
+
275
+ $init = (int)$_POST['init'];
276
+
277
+ if ($params === null) {
278
+ return $this->send_response('{"status":false,"msg":"The post_data parameter must be valid JSON"}');
279
+ }
280
+
281
+ $this->process_params($params);
282
+
283
+ $return['finished'] = 1;
284
+
285
+ //$return = $this->archive_system->start_incremental_backup($this->form_params['backup_params'], $this->form_params['extra'], $init);
286
+ try {
287
+ $return = $this->archive_system->start_incremental_backup(
288
+ $this->form_params['backup_params'],
289
+ $this->form_params['extra'],
290
+ $init
291
+ );
292
+ } catch (Exception $e) {
293
+ $return = array();
294
+ $return['error'] = true;
295
+ $return['status'] = 500;
296
+ $return['error_message'] = $e->getMessage();
297
+
298
+ return $this->send_response($return, $hash = 1);
299
+ }
300
+
301
+ if ($return['finished']) {
302
+ $return['extra']['backup_parent'] = $this->archive_system->get_archive_name_with_extension();
303
+ if ($this->xcloner_file_system->is_part($this->archive_system->get_archive_name_with_extension())) {
304
+ $return['extra']['backup_parent'] = $this->archive_system->get_archive_name_multipart();
305
+ }
306
+ }
307
+
308
+ $data = $return;
309
+
310
+ //check if backup is finished
311
+ if ($return['finished']) {
312
+ if (isset($this->form_params['backup_params']['email_notification']) and $to = $this->form_params['backup_params']['email_notification']) {
313
+ try {
314
+ $from = "";
315
+ $subject = "";
316
+ $additional['lines_total'] = $return['extra']['lines_total'];
317
+ $this->archive_system->send_notification(
318
+ $to,
319
+ $from,
320
+ $subject,
321
+ $return['extra']['backup_parent'],
322
+ $this->form_params,
323
+ "",
324
+ $additional
325
+ );
326
+ } catch (Exception $e) {
327
+ $this->logger->error($e->getMessage());
328
+ }
329
+ }
330
+ $this->xcloner_file_system->remove_tmp_filesystem();
331
+ }
332
+
333
+ return $this->send_response($data, $hash = 1);
334
+ }
335
+
336
+ /*
337
  *
338
  * Backup Database API
339
  *
340
  */
341
+ public function backup_database()
342
+ {
343
+ $data = array();
344
 
345
+ $this->check_access();
346
 
347
+ $params = json_decode(stripslashes($_POST['data']));
348
 
349
+ $init = (int)$_POST['init'];
350
 
351
+ if ($params === null) {
352
+ return $this->send_response('{"status":false,"msg":"The post_data parameter must be valid JSON"}');
353
+ }
354
 
355
+ $this->process_params($params);
356
 
357
+ //$xcloner_database = $this->init_db();
358
+ $return = $this->xcloner_database->start_database_recursion(
359
+ $this->form_params['database'],
360
+ $this->form_params['extra'],
361
+ $init
362
+ );
363
 
364
+ if (isset($return['error']) and $return['error']) {
365
+ $data['finished'] = 1;
366
+ } else {
367
+ $data['finished'] = $return['finished'];
368
+ }
369
 
370
+ $data['extra'] = $return;
371
 
372
+ return $this->send_response($data, $hash = 1);
373
+ }
374
 
375
+ /*
376
  *
377
  * Scan Filesystem API
378
  *
379
  */
380
+ public function scan_filesystem()
381
+ {
382
+ $data = array();
383
 
384
+ $this->check_access();
385
 
386
+ $params = json_decode(stripslashes($_POST['data']));
 
387
 
388
+ $init = (int)$_POST['init'];
 
 
389
 
390
+ if ($params === null) {
391
+ $this->send_response('{"status":false,"msg":"The post_data profile parameter must be valid JSON"}');
392
+ }
393
 
394
+ $this->process_params($params);
395
 
396
+ $this->xcloner_file_system->set_excluded_files($this->form_params['excluded_files']);
397
 
398
+ $return = $this->xcloner_file_system->start_file_recursion($init);
 
 
 
 
399
 
400
+ $data["finished"] = !$return;
401
+ $data["total_files_num"] = $this->xcloner_file_system->get_scanned_files_num();
402
+ $data["last_logged_file"] = $this->xcloner_file_system->last_logged_file();
403
+ $data["total_files_size"] = sprintf(
404
+ "%.2f",
405
+ $this->xcloner_file_system->get_scanned_files_total_size() / (1024 * 1024)
406
+ );
407
 
408
+ return $this->send_response($data, $hash = 1);
409
+ }
410
+
411
+ /*
412
  *
413
  * Process params sent by the user
414
  *
415
  */
416
+ private function process_params($params)
417
+ {
418
+ if($params->processed) {
419
+ $this->form_params = json_decode(json_encode((array)$params), true);
420
+ return;
421
+ }
422
+ if (isset($params->hash)) {
423
+ $this->xcloner_settings->set_hash($params->hash);
424
+ }
425
+
426
+ $this->form_params['extra'] = array();
427
+ $this->form_params['backup_params'] = array();
428
+
429
+ $this->form_params['database'] = array();
430
+
431
+ if (isset($params->backup_params)) {
432
+ foreach ($params->backup_params as $param) {
433
+ $this->form_params['backup_params'][$param->name] = $this->xcloner_sanitization->sanitize_input_as_string($param->value);
434
+ $this->logger->debug("Adding form parameter ".$param->name.".".$param->value."\n", array(
435
+ 'POST',
436
+ 'fields filter'
437
+ ));
438
+ }
439
+ }
440
+
441
+ $this->form_params['database'] = array();
442
+
443
+ if (isset($params->table_params)) {
444
+ foreach ($params->table_params as $param) {
445
+ $this->form_params['database'][$param->parent][] = $this->xcloner_sanitization->sanitize_input_as_raw($param->id);
446
+ $this->logger->debug("Adding database filter ".$param->parent.".".$param->id."\n", array(
447
+ 'POST',
448
+ 'database filter'
449
+ ));
450
+ }
451
+ }
452
+
453
+ $this->form_params['excluded_files'] = array();
454
+ if (isset($params->files_params)) {
455
+ foreach ($params->files_params as $param) {
456
+ $this->form_params['excluded_files'][] = $this->xcloner_sanitization->sanitize_input_as_relative_path($param->id);
457
+ }
458
+
459
+ $unique_exclude_files = array();
460
+
461
+ foreach ($params->files_params as $key => $param) {
462
+ if (!in_array($param->parent, $this->form_params['excluded_files'])) {
463
+ //$this->form_params['excluded_files'][] = $this->xcloner_sanitization->sanitize_input_as_relative_path($param->id);
464
+ $unique_exclude_files[] = $param->id;
465
+ $this->logger->debug("Adding file filter ".$param->id."\n", array(
466
+ 'POST',
467
+ 'exclude files filter'
468
+ ));
469
+ }
470
+ }
471
+ $this->form_params['excluded_files'] = (array)$unique_exclude_files;
472
+ }
473
+
474
+ //$this->form_params['excluded_files'] = array_merge($this->form_params['excluded_files'], $this->exclude_files_by_default);
475
+
476
+ if (isset($params->extra)) {
477
+ foreach ($params->extra as $key => $value) {
478
+ $this->form_params['extra'][$key] = $this->xcloner_sanitization->sanitize_input_as_raw($value);
479
+ }
480
+ }
481
+
482
+ if (isset($this->form_params['backup_params']['diff_start_date']) and $this->form_params['backup_params']['diff_start_date']) {
483
+ $this->form_params['backup_params']['diff_start_date'] = strtotime($this->form_params['backup_params']['diff_start_date']);
484
+ $this->xcloner_file_system->set_diff_timestamp_start($this->form_params['backup_params']['diff_start_date']);
485
+ }
486
+
487
+ return $this->xcloner_settings->get_hash();
488
+ }
489
+
490
+ /*
491
  *
492
  * Get file list for tree view API
493
  *
494
  */
495
+ public function get_file_system_action()
496
+ {
497
+ $this->check_access();
498
+
499
+ $folder = $this->xcloner_sanitization->sanitize_input_as_relative_path($_POST['id']);
500
+
501
+ $data = array();
502
+
503
+ if ($folder == "#") {
504
+ $folder = "/";
505
+ $data[] = array(
506
+ 'id' => $folder,
507
+ 'parent' => '#',
508
+ 'text' => $this->xcloner_settings->get_xcloner_start_path(),
509
+ //'children' => true,
510
+ 'state' => array('selected' => false, 'opened' => true),
511
+ 'icon' => plugin_dir_url(dirname(__FILE__))."/admin/assets/file-icon-root.png"
512
+ );
513
+ }
514
+
515
+ try {
516
+ $files = $this->xcloner_file_system->list_directory($folder);
517
+ } catch (Exception $e) {
518
+ print $e->getMessage();
519
+ $this->logger->error($e->getMessage());
520
+
521
+ return;
522
+ }
523
+
524
+ $type = array();
525
+ foreach ($files as $key => $row) {
526
+ $type[$key] = $row['type'];
527
+ }
528
+ array_multisort($type, SORT_ASC, $files);
529
+
530
+ foreach ($files as $file) {
531
+ $children = false;
532
+ $text = $file['basename'];
533
+
534
+ if ($file['type'] == "dir") {
535
+ $children = true;
536
+ } else {
537
+ $text .= " (".$this->xcloner_requirements->file_format_size($file['size']).")";
538
+ }
539
+
540
+ if ($this->xcloner_file_system->is_excluded($file)) {
541
+ $selected = true;
542
+ } else {
543
+ $selected = false;
544
+ }
545
+
546
+ $data[] = array(
547
+ 'id' => $file['path'],
548
+ 'parent' => $folder,
549
+ 'text' => $text,
550
+ //'title' => "test",
551
+ 'children' => $children,
552
+ 'state' => array('selected' => $selected, 'opened' => false, "checkbox_disabled" => $selected),
553
+ 'icon' => plugin_dir_url(dirname(__FILE__))."/admin/assets/file-icon-".strtolower(substr(
554
+ $file['type'],
555
+ 0,
556
+ 1
557
+ )).".png"
558
+ );
559
+ }
560
+
561
+
562
+ return $this->send_response($data, 0);
563
+ }
564
+
565
+ /*
566
  *
567
  * Get databases/tables list for frontend tree display API
568
  *
569
  */
570
+ public function get_database_tables_action()
571
+ {
572
+ $this->check_access();
573
+
574
+ $database = $this->xcloner_sanitization->sanitize_input_as_raw($_POST['id']);
575
+
576
+ $data = array();
577
+
578
+ $xcloner_backup_only_wp_tables = $this->xcloner_settings->get_xcloner_option('xcloner_backup_only_wp_tables');
579
+
580
+ if ($database == "#") {
581
+ try {
582
+ $return = $this->xcloner_database->get_all_databases();
583
+ } catch (Exception $e) {
584
+ $this->logger->error($e->getMessage());
585
+ }
586
+
587
+ foreach ($return as $database) {
588
+ if ($xcloner_backup_only_wp_tables and $database['name'] != $this->xcloner_settings->get_db_database()) {
589
+ continue;
590
+ }
591
+
592
+ $state = array();
593
+
594
+ if ($database['name'] == $this->xcloner_settings->get_db_database()) {
595
+ $state['selected'] = true;
596
+ if ($database['num_tables'] < 25) {
597
+ $state['opened'] = false;
598
+ }
599
+ }
600
+
601
+ $data[] = array(
602
+ 'id' => $database['name'],
603
+ 'parent' => '#',
604
+ 'text' => $database['name']." (".(int)$database['num_tables'].")",
605
+ 'children' => true,
606
+ 'state' => $state,
607
+ 'icon' => plugin_dir_url(dirname(__FILE__))."/admin/assets/database-icon.png"
608
+ );
609
+ }
610
+ } else {
611
+ try {
612
+ $return = $this->xcloner_database->list_tables($database, "", 1);
613
+ } catch (Exception $e) {
614
+ $this->logger->error($e->getMessage());
615
+ }
616
+
617
+ foreach ($return as $table) {
618
+ $state = array();
619
+
620
+ if ($xcloner_backup_only_wp_tables and !stristr(
621
+ $table['name'],
622
+ $this->xcloner_settings->get_table_prefix()
623
+ )) {
624
+ continue;
625
+ }
626
+
627
+ if (isset($database['name']) and $database['name'] == $this->xcloner_settings->get_db_database()) {
628
+ $state = array('selected' => true);
629
+ }
630
+
631
+ $data[] = array(
632
+ 'id' => $database.".".$table['name'],
633
+ 'parent' => $database,
634
+ 'text' => $table['name']." (".(int)$table['records'].")",
635
+ 'children' => false,
636
+ 'state' => $state,
637
+ 'icon' => plugin_dir_url(dirname(__FILE__))."/admin/assets/table-icon.png"
638
+ );
639
+ }
640
+ }
641
+
642
+ return $this->send_response($data, 0);
643
+ }
644
+
645
+ /*
646
  *
647
  * Get schedule by id API
648
  *
649
  */
650
+ public function get_schedule_by_id()
651
+ {
652
+ $this->check_access();
653
+
654
+ $schedule_id = $this->xcloner_sanitization->sanitize_input_as_int($_GET['id']);
655
+ $scheduler = $this->xcloner_scheduler;
656
+ $data = $scheduler->get_schedule_by_id($schedule_id);
657
+
658
+ $data['start_at'] = date(
659
+ "Y-m-d H:i",
660
+ strtotime($data['start_at']) + ($this->xcloner_settings->get_xcloner_option('gmt_offset') * HOUR_IN_SECONDS)
661
+ );
662
+ if (isset($data['backup_params']->diff_start_date) && $data['backup_params']->diff_start_date != "") {
663
+ $data['backup_params']->diff_start_date = date("Y-m-d", ($data['backup_params']->diff_start_date));
664
+ }
665
+
666
+ return $this->send_response($data);
667
+ }
668
+
669
+ /*
670
  *
671
  * Get Schedule list API
672
  *
673
  */
674
+ public function get_scheduler_list()
675
+ {
676
+ $return = array();
677
 
678
+ $this->check_access();
679
 
680
+ $scheduler = $this->xcloner_scheduler;
681
+ $data = $scheduler->get_scheduler_list();
682
+ $return['data'] = array();
683
 
684
+ foreach ($data as $res) {
685
+ $action = "<a href=\"#".$res->id."\" class=\"edit\" title='Edit'> <i class=\"material-icons \">edit</i></a>
686
  <a href=\"#" . $res->id."\" class=\"delete\" title='Delete'><i class=\"material-icons \">delete</i></a>";
687
+ if ($res->status) {
688
+ $status = '<i class="material-icons active status">timer</i>';
689
+ } else {
690
+ $status = '<i class="material-icons status inactive">timer_off</i>';
691
+ }
692
+
693
+ $next_run_time = wp_next_scheduled('xcloner_scheduler_'.$res->id, array($res->id));
694
+
695
+ $next_run = date($this->xcloner_settings->get_xcloner_option('date_format')." ".$this->xcloner_settings->get_xcloner_option('time_format'), $next_run_time);
696
+
697
+ $remote_storage = $res->remote_storage;
698
+
699
+ if (!$next_run_time >= time()) {
700
+ $next_run = " ";
701
+ }
702
+
703
+ if (trim($next_run)) {
704
+ $date_text = date(
705
+ $this->xcloner_settings->get_xcloner_option('date_format')." ".$this->xcloner_settings->get_xcloner_option('time_format'),
706
+ $next_run_time + ($this->xcloner_settings->get_xcloner_option('gmt_offset') * HOUR_IN_SECONDS)
707
+ );
708
+
709
+ if ($next_run_time >= time()) {
710
+ $next_run = "in ".human_time_diff($next_run_time, time());
711
+ } else {
712
+ $next_run = __("executed", 'xcloner-backup-and-restore');
713
+ }
714
+
715
+ $next_run = "<a href='#' title='".$date_text."'>".$next_run."</a>";
716
+ //$next_run .=" ($date_text)";
717
+ }
718
+
719
+ $backup_text = "";
720
+ $backup_size = "";
721
+ $backup_time = "";
722
+
723
+ if ($res->last_backup) {
724
+ if ($this->xcloner_file_system->get_storage_filesystem()->has($res->last_backup)) {
725
+ $metadata = $this->xcloner_file_system->get_storage_filesystem()->getMetadata($res->last_backup);
726
+ $backup_size = size_format($this->xcloner_file_system->get_backup_size($res->last_backup));
727
+ $backup_time = date(
728
+ $this->xcloner_settings->get_xcloner_option('date_format')." ".$this->xcloner_settings->get_xcloner_option('time_format'),
729
+ $metadata['timestamp'] + ($this->xcloner_settings->get_xcloner_option('gmt_offset') * HOUR_IN_SECONDS)
730
+ );
731
+ }
732
+
733
+ $backup_text = "<span title='".$backup_time."' class='shorten_string'>".$res->last_backup." (".$backup_size.")</span>";
734
+ }
735
+
736
+ $schedules = wp_get_schedules();
737
+
738
+ if (isset($schedules[$res->recurrence])) {
739
+ $res->recurrence = $schedules[$res->recurrence]['display'];
740
+ }
741
+
742
+ $return['data'][] = array(
743
+ $res->id,
744
+ $res->name,
745
+ $res->recurrence, /*$res->start_at,*/
746
+ $next_run,
747
+ $remote_storage,
748
+ $backup_text,
749
+ $status,
750
+ $action
751
+ );
752
+ }
753
+
754
+ return $this->send_response($return, 0);
755
+ }
756
+
757
+ /*
758
  *
759
  * Delete Schedule by ID API
760
  *
761
  */
762
+ public function delete_schedule_by_id()
763
+ {
764
+ $data = array();
765
 
766
+ $this->check_access();
767
 
768
+ $schedule_id = $this->xcloner_sanitization->sanitize_input_as_int($_GET['id']);
769
+ $scheduler = $this->xcloner_scheduler;
770
+ $data['finished'] = $scheduler->delete_schedule_by_id($schedule_id);
771
 
772
+ return $this->send_response($data);
773
+ }
774
 
775
+ /*
776
  *
777
  * Delete backup by name from the storage path
778
  *
779
  */
780
+ public function delete_backup_by_name()
781
+ {
782
+ $data = array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
783
 
784
+ $this->check_access();
 
 
 
 
 
785
 
786
+ $backup_name = $this->xcloner_sanitization->sanitize_input_as_string($_POST['name']);
787
+ $storage_selection = $this->xcloner_sanitization->sanitize_input_as_string($_POST['storage_selection']);
788
 
789
+ $data['finished'] = $this->xcloner_file_system->delete_backup_by_name($backup_name, $storage_selection);
 
 
 
 
790
 
791
+ return $this->send_response($data);
792
+ }
793
 
794
+ /**
795
+ * API Incremental Backup Encryption Method
796
+ */
797
+ public function backup_encryption()
798
+ {
799
+ $this->check_access();
800
+
801
+ $backup_parts = array();
802
+ $return = array();
803
+
804
+
805
+ if (isset($_POST['data'])) {
806
+ $params = json_decode(stripslashes($_POST['data']));
807
+
808
+ $this->process_params($params);
809
+ $source_backup_file = $this->xcloner_sanitization->sanitize_input_as_string($this->form_params['extra']['backup_parent']);
810
+
811
+ if (isset($this->form_params['extra']['start'])) {
812
+ $start = $this->xcloner_sanitization->sanitize_input_as_int($this->form_params['extra']['start']);
813
+ } else {
814
+ $start = 0;
815
+ }
816
+
817
+ if (isset($this->form_params['extra']['iv'])) {
818
+ $iv = $this->xcloner_sanitization->sanitize_input_as_raw($this->form_params['extra']['iv']);
819
+ } else {
820
+ $iv = "";
821
+ }
822
+
823
+ if (isset($this->form_params['extra']['part'])) {
824
+ $return['part'] = (int)$this->xcloner_sanitization->sanitize_input_as_int($this->form_params['extra']['part']);
825
+ } else {
826
+ $return['part'] = 0;
827
+ }
828
+ } else {
829
+ $source_backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
830
+ $start = $this->xcloner_sanitization->sanitize_input_as_int($_POST['start']);
831
+ $iv = $this->xcloner_sanitization->sanitize_input_as_raw($_POST['iv']);
832
+ $return['part'] = (int)$this->xcloner_sanitization->sanitize_input_as_int($_POST['part']);
833
+ }
834
+
835
+ $backup_file = $source_backup_file;
836
+
837
+ if ($this->xcloner_file_system->is_multipart($backup_file)) {
838
+ $backup_parts = $this->xcloner_file_system->get_multipart_files($backup_file);
839
+ $backup_file = $backup_parts[$return['part']];
840
+ }
841
+
842
+ $return['processing_file'] = $backup_file;
843
+ $return['total_size'] = filesize($this->xcloner_settings->get_xcloner_store_path().DS.$backup_file);
844
+
845
+ try {
846
+ $this->logger->info(json_encode($_POST));
847
+ $this->logger->info($iv);
848
+ $return = array_merge(
849
+ $return,
850
+ $this->xcloner_encryption->encrypt_file($backup_file, "", "", $start, base64_decode($iv))
851
+ );
852
+ } catch (\Exception $e) {
853
+ $return['error'] = true;
854
+ $return['message'] = $e->getMessage();
855
+ $return['error_message'] = $e->getMessage();
856
+ }
857
+
858
+ //echo strlen($return['iv']);exit;
859
+
860
+ if (isset($return['finished']) && $return['finished']) {
861
+ if ($this->xcloner_file_system->is_multipart($source_backup_file)) {
862
+ $return['start'] = 0;
863
+
864
+ ++$return['part'];
865
+
866
+ if ($return['part'] < sizeof($backup_parts)) {
867
+ $return['finished'] = 0;
868
+ }
869
+ }
870
+ }
871
+
872
+ if (isset($_POST['data'])) {
873
+ $return['extra'] = array_merge($this->form_params['extra'], $return);
874
+ }
875
+
876
+ $this->send_response($return, 0);
877
+ }
878
+
879
+ /**
880
+ * API Incremental Backup Decryption Method
881
+ */
882
+ public function backup_decryption()
883
+ {
884
+ $this->check_access();
885
+
886
+ $backup_parts = array();
887
+ $return = array();
888
+
889
+ $source_backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
890
+ $start = $this->xcloner_sanitization->sanitize_input_as_int($_POST['start']);
891
+ $iv = $this->xcloner_sanitization->sanitize_input_as_raw($_POST['iv']);
892
+ $decryption_key = $this->xcloner_sanitization->sanitize_input_as_raw($_POST['decryption_key']);
893
+ ;
894
+ $return['part'] = $this->xcloner_sanitization->sanitize_input_as_int($_POST['part']);
895
+
896
+ $backup_file = $source_backup_file;
897
+
898
+ if ($this->xcloner_file_system->is_multipart($backup_file)) {
899
+ $backup_parts = $this->xcloner_file_system->get_multipart_files($backup_file);
900
+ $backup_file = $backup_parts[$return['part']];
901
+ }
902
+
903
+ $return['processing_file'] = $backup_file;
904
+ $return['total_size'] = filesize($this->xcloner_settings->get_xcloner_store_path().DS.$backup_file);
905
+
906
+ try {
907
+ $return = array_merge(
908
+ $return,
909
+ $this->xcloner_encryption->decrypt_file($backup_file, "", $decryption_key, $start, base64_decode($iv))
910
+ );
911
+ } catch (\Exception $e) {
912
+ $return['error'] = true;
913
+ $return['message'] = $e->getMessage();
914
+ }
915
+
916
+ if ($return['finished']) {
917
+ if ($this->xcloner_file_system->is_multipart($source_backup_file)) {
918
+ $return['start'] = 0;
919
+
920
+ ++$return['part'];
921
+
922
+ if ($return['part'] < sizeof($backup_parts)) {
923
+ $return['finished'] = 0;
924
+ }
925
+ }
926
+ }
927
+
928
+ $this->send_response($return, 0);
929
+ }
930
+
931
+ public function get_manage_backups_list()
932
+ {
933
+ $this->check_access();
934
+
935
+ $return = array();
936
+ $storage_selection = "";
937
+
938
+ if (isset($_GET['storage_selection']) and $_GET['storage_selection']) {
939
+ $storage_selection = $this->xcloner_sanitization->sanitize_input_as_string($_GET['storage_selection']);
940
+ }
941
+ $available_storages = $this->xcloner_remote_storage->get_available_storages();
942
+
943
+ $backup_list = $this->xcloner_file_system->get_backup_archives_list($storage_selection);
944
+
945
+ $i = -1;
946
+ foreach ($backup_list as $file_info):?>
947
  <?php
948
+ if ($storage_selection == "gdrive") {
949
+ $file_info['path'] = $file_info['filename'].".".$file_info['extension'];
950
+ }
951
+ $file_exists_on_local_storage = true;
952
+
953
+ if ($storage_selection) {
954
+ if (!$this->xcloner_file_system->get_storage_filesystem()->has($file_info['path'])) {
955
+ $file_exists_on_local_storage = false;
956
+ }
957
+ } ?>
 
 
958
  <?php if (!isset($file_info['parent'])): ?>
959
 
960
  <?php ob_start(); ?>
964
  <label for="checkbox_<?php echo $i ?>">&nbsp;</label>
965
  </p>
966
  <?php
967
+ $return['data'][$i][] = ob_get_contents();
968
+ ob_end_clean(); ?>
 
969
 
970
  <?php ob_start(); ?>
971
  <span class=""><?php echo $file_info['path'] ?></span>
972
  <?php if (!$file_exists_on_local_storage): ?>
973
  <a href="#"
974
+ title="<?php echo __(
975
+ "File does not exists on local storage",
976
+ "xcloner-backup-and-restore"
977
+ ) ?>"><i
978
  class="material-icons backup_warning">warning</i></a>
979
  <?php endif ?>
980
  <?php
981
+ if (isset($file_info['childs']) and is_array($file_info['childs'])):
982
+ ?>
983
  <a href="#" title="expand" class="expand-multipart add"><i
984
  class="material-icons">add</i></a>
985
  <a href="#" title="collapse" class="expand-multipart remove"><i class="material-icons">remove</i></a>
988
  <li>
989
  <?php echo $child[0] ?> (<?php echo esc_html(size_format($child[2])) ?>)
990
  <?php
991
+ $child_exists_on_local_storage = true;
992
+ if ($storage_selection) {
993
+ if (!$this->xcloner_file_system->get_storage_filesystem()->has($child[0])) {
994
+ $child_exists_on_local_storage = false;
995
+ }
996
+ } ?>
 
997
  <?php if (!$child_exists_on_local_storage): ?>
998
  <a href="#"
999
+ title="<?php echo __(
1000
+ "File does not exists on local storage",
1001
+ "xcloner-backup-and-restore"
1002
+ ) ?>"><i
1003
  class="material-icons backup_warning">warning</i></a>
1004
  <?php endif ?>
1005
  <?php if (!$storage_selection) : ?>
1013
  </a>
1014
  <?php else: ?>
1015
  <a href="#<?php echo $child[0] ?>" class="list-backup-content"
1016
+ title="<?php echo __(
1017
+ 'List Backup Content',
1018
+ 'xcloner-backup-and-restore'
1019
+ ) ?>"><i
1020
  class="material-icons">folder_open</i></a>
1021
 
1022
  <a href="#<?php echo $child[0] ?>" class="backup-encryption"
1027
 
1028
  <?php elseif ($storage_selection != "gdrive" && !$this->xcloner_file_system->get_storage_filesystem()->has($child[0])): ?>
1029
  <a href="#<?php echo $child[0] ?>" class="copy-remote-to-local"
1030
+ title="<?php echo __(
1031
+ 'Push Backup To Local Storage',
1032
+ 'xcloner-backup-and-restore'
1033
+ ) ?>"><i
1034
  class="material-icons">file_upload</i></a>
1035
  <?php endif ?>
1036
  </li>
1038
  </ul>
1039
  <?php endif; ?>
1040
  <?php
1041
+ $return['data'][$i][] = ob_get_contents();
1042
+ ob_end_clean(); ?>
 
1043
  <?php ob_start(); ?>
1044
+ <?php if (isset($file_info['timestamp'])) {
1045
+ echo date("Y-m-d H:i", $file_info['timestamp']);
1046
+ } ?>
1047
  <?php
1048
+ $return['data'][$i][] = ob_get_contents();
1049
+ ob_end_clean(); ?>
 
1050
 
1051
  <?php ob_start(); ?>
1052
  <?php echo esc_html(size_format($file_info['size'])) ?>
1053
  <?php
1054
+ $return['data'][$i][] = ob_get_contents();
1055
+ ob_end_clean(); ?>
 
1056
 
1057
  <?php ob_start(); ?>
1058
  <?php if (!$storage_selection): ?>
1062
 
1063
  <?php if (sizeof($available_storages)): ?>
1064
  <a href="#<?php echo $file_info['basename'] ?>" class="cloud-upload"
1065
+ title="<?php echo __(
1066
+ 'Send Backup To Remote Storage',
1067
+ 'xcloner-backup-and-restore'
1068
+ ) ?>"><i
1069
  class="material-icons">cloud_upload</i></a>
1070
  <?php endif ?>
1071
  <?php
1072
+ $basename = $file_info['basename'];
1073
+ if (isset($file_info['childs']) and sizeof($file_info['childs'])) {
1074
+ $basename = $file_info['childs'][0][0];
1075
+ } ?>
1076
  <?php if ($this->xcloner_encryption->is_encrypted_file($basename)) :?>
1077
  <a href="#<?php echo $file_info['basename'] ?>" class="backup-decryption"
1078
  title="<?php echo __('Backup Decryption', 'xcloner-backup-and-restore') ?>">
1101
  <?php endif ?>
1102
 
1103
  <?php
1104
+ $return['data'][$i][] = ob_get_contents();
1105
+ ob_end_clean(); ?>
1106
 
1107
  <?php endif ?>
1108
  <?php endforeach ?>
1109
  <?php
1110
+ $this->send_response($return, 0);
1111
+ }
1112
+
1113
+ /**
1114
+ * API method to list internal backup files
1115
+ */
1116
+ public function list_backup_files()
1117
+ {
1118
+ $this->check_access();
1119
+
1120
+ $backup_parts = array();
1121
+ $return = array();
1122
+
1123
+ $source_backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
1124
+
1125
+ $start = $this->xcloner_sanitization->sanitize_input_as_int($_POST['start']);
1126
+ $return['part'] = $this->xcloner_sanitization->sanitize_input_as_int($_POST['part']);
1127
+
1128
+ $backup_file = $source_backup_file;
1129
+
1130
+ if ($this->xcloner_file_system->is_multipart($backup_file)) {
1131
+ $backup_parts = $this->xcloner_file_system->get_multipart_files($backup_file);
1132
+ $backup_file = $backup_parts[$return['part']];
1133
+ }
1134
+
1135
+ if ($this->xcloner_encryption->is_encrypted_file($backup_file)) {
1136
+ $return['error'] = true;
1137
+ $return['message'] = __("Backup archive is encrypted, please decrypt it first before you can list it's content.", "xcloner-backup-and-restore");
1138
+ $this->send_response($return, 0);
1139
+ }
1140
+
1141
+ try {
1142
+ $tar = $this->archive_system;
1143
+ $tar->open($this->xcloner_settings->get_xcloner_store_path().DS.$backup_file, $start);
1144
+
1145
+ $data = $tar->contents($this->xcloner_settings->get_xcloner_option('xcloner_files_to_process_per_request'));
1146
+ } catch (Exception $e) {
1147
+ $return['error'] = true;
1148
+ $return['message'] = $e->getMessage();
1149
+ $this->send_response($return, 0);
1150
+ }
1151
+
1152
+ $return['files'] = array();
1153
+ $return['finished'] = 1;
1154
+ $return['total_size'] = filesize($this->xcloner_settings->get_xcloner_store_path().DS.$backup_file);
1155
+ $i = 0;
1156
+
1157
+ if (isset($data['extracted_files']) and is_array($data['extracted_files'])) {
1158
+ foreach ($data['extracted_files'] as $file) {
1159
+ $return['files'][$i]['path'] = $file->getPath();
1160
+ $return['files'][$i]['size'] = $file->getSize();
1161
+ $return['files'][$i]['mtime'] = date(
1162
+ $this->xcloner_settings->get_xcloner_option('date_format')." ".$this->xcloner_settings->get_xcloner_option('time_format'),
1163
+ $file->getMtime()
1164
+ );
1165
+
1166
+ $i++;
1167
+ }
1168
+ }
1169
+
1170
+ if (isset($data['start'])) {
1171
+ $return['start'] = $data['start'];
1172
+ $return['finished'] = 0;
1173
+ } else {
1174
+ if ($this->xcloner_file_system->is_multipart($source_backup_file)) {
1175
+ $return['start'] = 0;
1176
+
1177
+ ++$return['part'];
1178
+
1179
+ if ($return['part'] < sizeof($backup_parts)) {
1180
+ $return['finished'] = 0;
1181
+ }
1182
+ }
1183
+ }
1184
+
1185
+ $this->send_response($return, 0);
1186
+ }
1187
+
1188
+ /*
1189
  * Copy remote backup to local storage
1190
  */
1191
+ public function copy_backup_remote_to_local()
1192
+ {
1193
+ $this->check_access();
 
 
 
 
 
 
1194
 
1195
+ $backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
1196
+ $storage_type = $this->xcloner_sanitization->sanitize_input_as_string($_POST['storage_type']);
1197
 
1198
+ $xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
 
 
 
 
 
 
 
1199
 
1200
+ $return = array();
 
 
1201
 
1202
+ try {
1203
+ if (method_exists($xcloner_remote_storage, "copy_backup_remote_to_local")) {
1204
+ $return = call_user_func_array(array(
1205
+ $xcloner_remote_storage,
1206
+ "copy_backup_remote_to_local"
1207
+ ), array($backup_file, $storage_type));
1208
+ }
1209
+ } catch (Exception $e) {
1210
+ $return['error'] = 1;
1211
+ $return['message'] = $e->getMessage();
1212
+ }
1213
 
1214
+ if (!$return) {
1215
+ $return['error'] = 1;
1216
+ $return['message'] = "Upload failed, please check the error log for more information!";
1217
+ }
1218
 
 
1219
 
1220
+ $this->send_response($return, 0);
1221
+ }
1222
 
1223
+ /*
1224
  *
1225
  * Upload backup to remote API
1226
  *
1227
  */
1228
+ public function upload_backup_to_remote()
1229
+ {
1230
+ $this->check_access();
1231
 
1232
+ $return = array();
1233
 
1234
+ $backup_file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
1235
+ $storage_type = $this->xcloner_sanitization->sanitize_input_as_string($_POST['storage_type']);
1236
 
1237
+ $xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
1238
 
1239
+ try {
1240
+ if (method_exists($xcloner_remote_storage, "upload_backup_to_storage")) {
1241
+ $return = call_user_func_array(array(
1242
+ $xcloner_remote_storage,
1243
+ "upload_backup_to_storage"
1244
+ ), array($backup_file, $storage_type));
1245
+ }
1246
+ } catch (Exception $e) {
1247
+ $return['error'] = 1;
1248
+ $return['message'] = $e->getMessage();
1249
+ }
1250
 
1251
+ if (!$return) {
1252
+ $return['error'] = 1;
1253
+ $return['message'] = "Upload failed, please check the error log for more information!";
1254
+ }
1255
 
 
 
 
 
1256
 
1257
+ $this->send_response($return, 0);
1258
+ }
1259
 
1260
+ /*
 
 
 
 
1261
  *
1262
  * Remote Storage Status Save
1263
  *
1264
  */
1265
+ public function remote_storage_save_status()
1266
+ {
1267
+ $this->check_access();
1268
 
1269
+ $return = array();
1270
 
1271
+ $xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
1272
 
1273
+ $return['finished'] = $xcloner_remote_storage->change_storage_status($_POST['id'], $_POST['value']);
1274
 
1275
+ $this->send_response($return, 0);
1276
+ }
1277
 
1278
 
1279
+ public function download_restore_script()
1280
+ {
1281
+ $this->check_access();
1282
 
1283
+ ob_end_clean();
1284
 
1285
+ $adapter = new Local(dirname(__DIR__), LOCK_EX, '0001');
1286
+ $xcloner_plugin_filesystem = new Filesystem($adapter, new Config([
1287
+ 'disable_asserts' => true,
1288
+ ]));
1289
 
1290
+ /* Generate PHAR FILE
1291
  $file = 'restore/vendor.built';
1292
 
1293
  if(file_exists($file))
1303
  $phar2->setStub($phar2->createDefaultStub('vendor/autoload.php', 'vendor/autoload.php'));
1304
  * */
1305
 
1306
+ $tmp_file = $this->xcloner_settings->get_xcloner_tmp_path().DS."xcloner-restore.tgz";
1307
 
1308
+ $tar = $this->archive_system;
1309
+ $tar->create($tmp_file);
1310
 
1311
+ $tar->addFile(dirname(__DIR__)."/restore/vendor.build.txt", "vendor.phar");
1312
+ //$tar->addFile(dirname(__DIR__)."/restore/vendor.tgz", "vendor.tgz");
1313
 
1314
+ $files = $xcloner_plugin_filesystem->listContents("vendor/", true);
1315
+ foreach ($files as $file) {
1316
+ $tar->addFile(dirname(__DIR__).DS.$file['path'], $file['path']);
1317
+ }
1318
 
1319
+ $content = file_get_contents(dirname(__DIR__)."/restore/xcloner_restore.php");
1320
+ $content = str_replace("define('AUTH_KEY', '');", "define('AUTH_KEY', '".md5(AUTH_KEY)."');", $content);
1321
 
1322
+ $tar->addData("xcloner_restore.php", $content);
1323
 
1324
+ $tar->close();
1325
 
1326
+ if (file_exists($tmp_file)) {
1327
+ header('Content-Description: File Transfer');
1328
+ header('Content-Type: application/octet-stream');
1329
+ header('Content-Disposition: attachment; filename="'.basename($tmp_file).'"');
1330
+ header('Expires: 0');
1331
+ header('Cache-Control: must-revalidate');
1332
+ header('Pragma: public');
1333
+ header('Content-Length: '.filesize($tmp_file));
1334
+ readfile($tmp_file);
1335
+ }
1336
 
1337
+ try {
1338
+ unlink($tmp_file);
1339
+ } catch (Exception $e) {
1340
+ //We are not interested in the error here
1341
+ }
1342
 
1343
+ die();
1344
+ }
 
 
 
1345
 
1346
+ /*
 
 
 
1347
  *
1348
  * Download backup by Name from the Storage Path
1349
  *
1350
  */
1351
+ public function download_backup_by_name()
1352
+ {
1353
+ $this->check_access();
 
 
1354
 
1355
+ ob_end_clean();
1356
 
1357
+ $backup_name = $this->xcloner_sanitization->sanitize_input_as_string($_GET['name']);
1358
 
 
 
1359
 
1360
+ $metadata = $this->xcloner_file_system->get_storage_filesystem()->getMetadata($backup_name);
1361
+ $read_stream = $this->xcloner_file_system->get_storage_filesystem()->readStream($backup_name);
1362
 
 
 
 
 
 
 
 
 
1363
 
1364
+ header('Pragma: public');
1365
+ header('Expires: 0');
1366
+ header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
1367
+ header('Cache-Control: private', false);
1368
+ header('Content-Transfer-Encoding: binary');
1369
+ header('Content-Disposition: attachment; filename="'.$metadata['path'].'";');
1370
+ header('Content-Type: application/octet-stream');
1371
+ header('Content-Length: '.$metadata['size']);
1372
 
1373
+ ob_end_clean();
 
 
 
 
 
1374
 
1375
+ $chunkSize = 1024 * 1024;
1376
+ while (!feof($read_stream)) {
1377
+ $buffer = fread($read_stream, $chunkSize);
1378
+ echo $buffer;
1379
+ }
1380
+ fclose($read_stream);
1381
 
1382
+ wp_die();
1383
+ }
1384
 
1385
+ /*
1386
  * Restore upload backup
1387
  */
1388
+ public function restore_upload_backup()
1389
+ {
1390
+ $this->check_access();
1391
 
1392
+ $return = array();
1393
 
1394
+ $return['part'] = 0;
1395
+ $return['total_parts'] = 0;
1396
+ $return['uploaded_size'] = 0;
1397
+ $is_multipart = 0;
1398
 
1399
+ $file = $this->xcloner_sanitization->sanitize_input_as_string($_POST['file']);
1400
+ $hash = $this->xcloner_sanitization->sanitize_input_as_string($_POST['hash']);
1401
 
1402
+ if (isset($_POST['part'])) {
1403
+ $return['part'] = $this->xcloner_sanitization->sanitize_input_as_int($_POST['part']);
1404
+ }
1405
 
1406
+ if (isset($_POST['uploaded_size'])) {
1407
+ $return['uploaded_size'] = $this->xcloner_sanitization->sanitize_input_as_int($_POST['uploaded_size']);
1408
+ }
1409
 
1410
+ $start = $this->xcloner_sanitization->sanitize_input_as_string($_POST['start']);
1411
+ $target_url = $this->xcloner_sanitization->sanitize_input_as_string($_POST['target_url']);
1412
 
1413
+ $return['total_size'] = $this->xcloner_file_system->get_backup_size($file);
1414
 
1415
+ if ($this->xcloner_file_system->is_multipart($file)) {
1416
+ $backup_parts = $this->xcloner_file_system->get_multipart_files($file);
1417
 
1418
+ $return['total_parts'] = sizeof($backup_parts) + 1;
1419
 
1420
+ if ($return['part'] and isset($backup_parts[$return['part'] - 1])) {
1421
+ $file = $backup_parts[$return['part'] - 1];
1422
+ }
1423
 
1424
+ $is_multipart = 1;
1425
+ }
1426
 
1427
+ try {
1428
+ $xcloner_file_transfer = $this->get_xcloner_container()->get_xcloner_file_transfer();
1429
+ $xcloner_file_transfer->set_target($target_url);
1430
+ $return['start'] = $xcloner_file_transfer->transfer_file($file, $start, $hash);
1431
+ } catch (Exception $e) {
1432
+ $return = array();
1433
+ $return['error'] = true;
1434
+ $return['status'] = 500;
1435
+ $return['message'] = "CURL communication error with the restore host. ".$e->getMessage();
1436
+ $this->send_response($return, 0);
1437
+ }
1438
 
1439
+ $return['status'] = 200;
 
 
1440
 
1441
+ //we have finished the upload
1442
+ if (!$return['start'] and $is_multipart) {
1443
+ $return['part']++;
1444
+ $return['uploaded_size'] += $this->xcloner_file_system->get_storage_filesystem()->getSize($file);
1445
+ }
1446
 
1447
+ $this->send_response($return, 0);
1448
+ }
 
 
 
1449
 
1450
+ /*
 
 
 
 
 
 
 
 
 
 
 
 
 
1451
  * Restore backup
1452
  */
1453
+ public function restore_backup()
1454
+ {
1455
+ $this->check_access();
1456
 
1457
+ define("XCLONER_PLUGIN_ACCESS", 1);
1458
+ include_once(dirname(__DIR__).DS."restore".DS."xcloner_restore.php");
1459
 
1460
+ return;
1461
+ }
1462
 
1463
+ /*
1464
  *
1465
  * Send the json response back
1466
  *
1467
  */
1468
+ private function send_response($data, $attach_hash = 1)
1469
+ {
1470
+ if ($attach_hash and null !== $this->xcloner_settings->get_hash()) {
1471
+ $data['hash'] = $this->xcloner_settings->get_hash();
1472
+ }
1473
+
1474
+ if (ob_get_length()) {
1475
+ //ob_clean();
1476
+ }
1477
+
1478
+ return wp_send_json($data);
1479
+ }
 
1480
  }
includes/class-xcloner-archive.php CHANGED
@@ -36,181 +36,179 @@ use splitbrain\PHPArchive\FileInfo;
36
  */
37
  class Xcloner_Archive extends Tar
38
  {
39
- /**
40
- * Process file size per API request
41
- * @var float|int
42
- */
43
- private $file_size_per_request_limit = 52428800; //50MB = 52428800; 1MB = 1048576
44
- /**
45
- * Files count to process per API request
46
- * @var int
47
- */
48
- private $files_to_process_per_request = 250; //block of 512 bytes
49
- /**
50
- * Compression level, 0-uncompressed, 9-maximum compression
51
- * @var int
52
- */
53
- private $compression_level = 0; //0-9 , 0 uncompressed
54
- /**
55
- * Split backup size limit
56
- * Create a new backup archive file once the set size is reached
57
- * @var float|int
58
- */
59
- private $xcloner_split_backup_limit = 2048; //2048MB
60
- /**
61
- * Number of processed bytes
62
- * @var int
63
- */
64
- private $processed_size_bytes = 0;
65
 
66
  /**
67
  * The backup name encryption suffix
68
  * @var string
69
  */
70
- private $encrypt_suffix = "-enc";
71
-
72
- /**
73
- * Archive name
74
- * @var string
75
- */
76
- private $archive_name;
77
- /**
78
- * @var Tar
79
- */
80
- private $backup_archive;
81
- /**
82
- * @var Xcloner_File_System
83
- */
84
- private $filesystem;
85
- /**
86
- * @var Xcloner_Logger
87
- */
88
- private $logger;
89
- /**
90
- * @var Xcloner_Settings
91
- */
92
- private $xcloner_settings;
93
-
94
- /**
95
- * [__construct description]
96
- * @param Xcloner $xcloner_container XCloner Container
97
- * @param string $archive_name Achive Name
98
- */
99
- public function __construct(Xcloner $xcloner_container, $archive_name = "")
100
- {
101
- $this->filesystem = $xcloner_container->get_xcloner_filesystem();
102
- $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_archive");
103
- $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
104
-
105
- if ($value = $this->xcloner_settings->get_xcloner_option('xcloner_size_limit_per_request')) {
106
- $this->file_size_per_request_limit = $value * 1024 * 1024;
107
- } //MB
108
-
109
- if ($value = $this->xcloner_settings->get_xcloner_option('xcloner_files_to_process_per_request')) {
110
- $this->files_to_process_per_request = $value;
111
- }
112
-
113
- if ($value = get_option('xcloner_backup_compression_level')) {
114
- $this->compression_level = $value;
115
- }
116
-
117
- if ($value = get_option('xcloner_split_backup_limit')) {
118
- $this->xcloner_split_backup_limit = $value;
119
- }
120
-
121
- $this->xcloner_split_backup_limit = $this->xcloner_split_backup_limit * 1024 * 1024; //transform to bytes
122
-
123
- if (isset($archive_name) && $archive_name) {
124
- $this->set_archive_name($archive_name);
125
- }
126
- }
127
-
128
- /*
129
  * Rename backup archive
130
  *
131
  * @param string $old_name
132
  * @param string $new_name
133
  *
134
  */
135
- public function rename_archive($old_name, $new_name)
136
- {
137
- $this->logger->info(sprintf("Renaming backup archive %s to %s", $old_name, $new_name));
138
- $storage_filesystem = $this->filesystem->get_storage_filesystem();
139
- $storage_filesystem->rename($old_name, $new_name);
140
- }
141
-
142
- /*
143
  *
144
  * Set the backup archive name
145
  *
146
  */
147
- public function set_archive_name($name = "", $part = 0, $encrypt_prefix = false)
148
- {
149
-
150
- $this->archive_name = $this->filesystem->process_backup_name($name);
151
 
152
- if($encrypt_prefix) {
153
- $this->archive_name .= $this->encrypt_suffix;
154
  }
155
 
156
- if ($diff_timestamp_start = $this->filesystem->get_diff_timestamp_start()) {
157
- //$this->archive_name = $this->archive_name."-diff-".date("Y-m-d_H-i",$diff_timestamp_start);
158
- $new_name = $this->archive_name;
159
-
160
- if (!stristr($new_name, "-diff")) {
161
- $new_name = $this->archive_name."-diff".date("Y-m-d_H-i", $diff_timestamp_start);
162
- }
163
 
164
- $this->archive_name = $new_name;
 
 
165
 
166
- }
 
167
 
168
- if (isset($part) and $part) {
169
- $new_name = preg_replace('/-part(\d*)/', "-part".$part, $this->archive_name);
170
- if (!stristr($new_name, "-part")) {
171
- $new_name = $this->archive_name."-part".$part;
172
- }
173
 
174
- $this->archive_name = $new_name;
175
- }
176
 
177
- return $this;
178
- }
179
 
180
- /*
181
  *
182
  * Returns the backup archive name
183
  *
184
  * @return string archive name
185
  */
186
- public function get_archive_name()
187
- {
188
- return $this->archive_name;
189
- }
190
 
191
- /*
192
  *
193
  * Returns the multipart naming for the backup archive
194
  *
195
  * @return string multi-part backup name
196
  */
197
- public function get_archive_name_multipart()
198
- {
199
- $new_name = preg_replace('/-part(\d*)/', "", $this->archive_name);
200
- return $new_name."-multipart".$this->xcloner_settings->get_backup_extension_name(".csv");
201
- }
202
 
203
- /*
204
  *
205
  * Returns the full backup name including extension
206
  *
207
  */
208
- public function get_archive_name_with_extension()
209
- {
210
- return $this->archive_name.$this->xcloner_settings->get_backup_extension_name();
211
- }
212
 
213
- /*
214
  *
215
  * Send notification error by E-Mail
216
  *
@@ -224,34 +222,33 @@ class Xcloner_Archive extends Tar
224
  * @return bool
225
  */
226
 
227
- /**
228
- * @param string $error_message
229
- */
230
- public function send_notification_error($to, $from, $subject, $backup_name, $params, $error_message)
231
- {
232
-
233
- $body = "";
234
- $body .= sprintf(__("Backup Site Url: %s"), get_site_url());
235
- $body .= "<br /><>";
236
 
237
- $body .= sprintf(__("Error Message: %s"), $error_message);
238
 
239
- $this->logger->info(sprintf("Sending backup error notification to %s", $to));
240
 
241
- $admin_email = get_option("admin_email");
242
 
243
- $headers = array('Content-Type: text/html; charset=UTF-8');
244
 
245
- if ($admin_email and $from) {
246
- $headers[] = 'From: '.$from.' <'.$admin_email.'>';
247
- }
248
 
249
- $return = wp_mail($to, $subject, $body, $headers);
250
 
251
- return $return;
252
- }
253
 
254
- /*
255
  *
256
  * Send backup archive notfication by E-Mail
257
  *
@@ -265,546 +262,562 @@ class Xcloner_Archive extends Tar
265
  *
266
  * @return bool
267
  */
268
- public function send_notification(
269
- $to,
270
- $from,
271
- $subject,
272
- $backup_name,
273
- $params,
274
- $error_message = "",
275
- $additional = array()
276
- ) {
277
- if (!$from) {
278
- $from = "XCloner Backup";
279
- }
280
 
281
- if (($error_message)) {
282
- return $this->send_notification_error($to, $from, $subject, $backup_name, $params, $error_message);
283
- }
284
 
285
- $params = (array)$params;
286
 
287
- if (!$subject) {
288
- $subject = sprintf(__("New backup generated %s"), $backup_name);
289
- }
290
 
291
- $body = sprintf(__("Generated Backup Size: %s"), size_format($this->filesystem->get_backup_size($backup_name)));
292
- $body .= "<br /><br />";
293
 
294
- if (isset($additional['lines_total'])) {
295
- $body .= sprintf(__("Total files added: %s"), $additional['lines_total']);
296
- $body .= "<br /><br />";
297
- }
298
 
299
- $backup_parts = $this->filesystem->get_multipart_files($backup_name);
300
 
301
- if (!$backups_counter = sizeof($backup_parts)) {
302
- $backups_counter = 1;
303
- }
304
 
305
- $body .= sprintf(__("Backup Parts: %s"), $backups_counter);
306
- $body .= "<br />";
307
 
308
- if (sizeof($backup_parts)) {
309
- $body .= implode("<br />", $backup_parts);
310
- $body .= "<br />";
311
- }
312
 
313
- $body .= "<br />";
314
 
315
- $body .= sprintf(__("Backup Site Url: %s"), get_site_url());
316
- $body .= "<br />";
317
 
318
- if (isset($params['backup_params']->backup_comments)) {
319
- $body .= __("Backup Comments: ").$params['backup_params']->backup_comments;
320
- $body .= "<br /><br />";
321
- }
322
 
323
- if ($this->xcloner_settings->get_xcloner_option('xcloner_enable_log')) {
324
- $body .= __("Latest 50 Log Lines: ")."<br />".implode("<br />\n",
325
- $this->logger->getLastDebugLines(50));
326
- }
 
 
327
 
328
- $attachments = $this->filesystem->get_backup_attachments();
329
 
330
- $attachments_archive = $this->xcloner_settings->get_xcloner_tmp_path().DS."info.tgz";
331
 
332
- $tar = new Tar();
333
- $tar->create($attachments_archive);
334
 
335
- foreach ($attachments as $key => $file) {
336
- $tar->addFile($file, basename($file));
337
- }
338
- $tar->close();
339
 
340
- $this->logger->info(sprintf("Sending backup notification to %s", $to));
341
 
342
- $admin_email = get_option("admin_email");
343
 
344
- $headers = array('Content-Type: text/html; charset=UTF-8', 'From: '.$from.' <'.$admin_email.'>');
345
 
346
- $return = wp_mail($to, $subject, $body, $headers, array($attachments_archive));
347
 
348
- return $return;
349
- }
350
 
351
- /*
352
  *
353
  * Incremental Backup method
354
  *
355
  */
356
- public function start_incremental_backup($backup_params, $extra_params, $init)
357
- {
358
- $return = array();
359
 
360
- if (!isset($extra_params['backup_part'])) {
361
- $extra_params['backup_part'] = 0;
362
- }
363
 
364
- $return['extra']['backup_part'] = $extra_params['backup_part'];
365
 
366
- if (isset($extra_params['backup_archive_name'])) {
367
- $this->set_archive_name($extra_params['backup_archive_name'], $return['extra']['backup_part']);
368
- } else {
369
- $encrypt = false;
370
- if(isset($backup_params['backup_encrypt']) && $backup_params['backup_encrypt']) {
371
  $encrypt = true;
372
  }
373
- $this->set_archive_name($backup_params['backup_name'], 0, $encrypt);
374
- }
375
-
376
- if (!$this->get_archive_name()) {
377
- $this->set_archive_name();
378
- }
379
-
380
- $this->backup_archive = new Tar();
381
- $this->backup_archive->setCompression($this->compression_level);
382
-
383
- $archive_info = $this->filesystem->get_storage_path_file_info($this->get_archive_name_with_extension());
384
-
385
- if ($init) {
386
- $this->logger->info(sprintf(__("Initializing the backup archive %s"), $this->get_archive_name()));
387
 
388
- $this->backup_archive->create($archive_info->getPath().DS.$archive_info->getFilename());
 
 
389
 
390
- $return['extra']['backup_init'] = 1;
 
391
 
392
- } else {
393
- $this->logger->info(sprintf(__("Opening for append the backup archive %s"), $this->get_archive_name()));
394
 
395
- $this->backup_archive->openForAppend($archive_info->getPath().DS.$archive_info->getFilename());
 
396
 
397
- $return['extra']['backup_init'] = 0;
398
 
399
- }
 
 
400
 
401
- $return['extra']['backup_archive_name'] = $this->get_archive_name();
402
- $return['extra']['backup_archive_name_full'] = $this->get_archive_name_with_extension();
403
 
404
- if (!isset($extra_params['start_at_line'])) {
405
- $extra_params['start_at_line'] = 0;
406
- }
407
 
408
- if (!isset($extra_params['start_at_byte'])) {
409
- $extra_params['start_at_byte'] = 0;
410
- }
411
 
412
- if (!$this->filesystem->get_tmp_filesystem()->has($this->filesystem->get_included_files_handler())) {
413
- $this->logger->error(sprintf("Missing the includes file handler %s, aborting...",
414
- $this->filesystem->get_included_files_handler()));
415
 
416
- $return['finished'] = 1;
417
- return $return;
418
- }
419
 
420
- $included_files_handler = $this->filesystem->get_included_files_handler(1);
 
 
 
 
421
 
422
- $file = new SplFileObject($included_files_handler);
 
 
423
 
424
- $file->seek(PHP_INT_MAX);
425
 
426
- $return['extra']['lines_total'] = ($file->key() - 1);
427
 
428
- //we skip the first CSV line with headers
429
- if (!$extra_params['start_at_line']) {
430
- $file->seek(1);
431
- } else {
432
- $file->seek($extra_params['start_at_line'] + 1);
433
- }
434
 
435
- $this->processed_size_bytes = 0;
436
 
437
- $counter = 0;
 
 
 
 
 
438
 
439
- $start_byte = $extra_params['start_at_byte'];
440
 
441
- $byte_limit = 0;
442
 
443
- while (!$file->eof() and $counter <= $this->files_to_process_per_request) {
444
- $current_line_str = $file->current();
445
 
446
- $line = str_getcsv($current_line_str);
447
 
448
- $relative_path = stripslashes($line[0]);
 
449
 
450
- $start_filesystem = "start_filesystem";
451
 
452
- if (isset($line[4])) {
453
- $start_filesystem = $line[4];
454
- }
455
 
456
- //$adapter = $this->filesystem->get_adapter($start_filesystem);
457
 
458
- if (!$relative_path || !$this->filesystem->get_filesystem($start_filesystem)->has($relative_path)) {
459
- if ($relative_path != "") {
460
- $this->logger->error(sprintf("Could not add file %b to backup archive, file not found",
461
- $relative_path));
462
- }
463
-
464
- $extra_params['start_at_line']++;
465
- $file->next();
466
- continue;
467
- }
468
 
469
- $file_info = $this->filesystem->get_filesystem($start_filesystem)->getMetadata($relative_path);
470
 
471
- if (!isset($file_info['size'])) {
472
- $file_info['size'] = 0;
473
- }
 
 
 
 
474
 
475
- if ($start_filesystem == "tmp_filesystem") {
476
- $file_info['archive_prefix_path'] = $this->xcloner_settings->get_xcloner_tmp_path_suffix();
477
- }
 
478
 
479
- $byte_limit = (int)$this->file_size_per_request_limit / 512;
480
 
481
- $append = 0;
 
 
482
 
483
- if ($file_info['size'] > $byte_limit * 512 or $start_byte) {
484
- $append = 1;
485
- }
486
 
487
- if (!isset($return['extra']['backup_size'])) {
488
- $return['extra']['backup_size'] = 0;
489
- }
490
 
491
- $return['extra']['backup_size'] = $archive_info->getSize();
492
 
493
- $estimated_new_size = $return['extra']['backup_size'] + $file_info['size'];
 
 
494
 
495
- //we create a new backup part if we reach the Split Achive Limit
496
- if ($this->xcloner_split_backup_limit and ($estimated_new_size > $this->xcloner_split_backup_limit) and (!$start_byte)) {
497
- $this->logger->info(sprintf("Backup size limit %s bytes reached, file add estimate %s, attempt to create a new archive ",
498
- $this->xcloner_split_backup_limit, $estimated_new_size));
499
- list($archive_info, $return['extra']['backup_part']) = $this->create_new_backup_part($return['extra']['backup_part']);
500
 
501
- if ($file_info['size'] > $this->xcloner_split_backup_limit) {
502
- $this->logger->info(sprintf("Excluding %s file as it's size(%s) is bigger than the backup split limit of %s and it won't fit a single backup file",
503
- $file_info['path'], $file_info['size'], $this->xcloner_split_backup_limit));
504
- $extra_params['start_at_line']++;
505
- }
506
 
507
- $return['extra']['start_at_line'] = $extra_params['start_at_line'];
508
- $return['extra']['start_at_byte'] = 0;
509
 
510
- $return['finished'] = 0;
 
 
 
 
 
 
 
511
 
512
- return $return;
513
- }
 
 
 
 
 
 
 
514
 
515
- list($bytes_wrote, $last_position) = $this->add_file_to_archive($file_info, $start_byte, $byte_limit,
516
- $append, $start_filesystem);
517
- $this->processed_size_bytes += $bytes_wrote;
518
-
519
- //echo" - processed ".$this->processed_size_bytes." bytes ".$this->file_size_per_request_limit." last_position:".$last_position." \n";
520
- $return['extra']['processed_file'] = $file_info['path'];
521
- $return['extra']['processed_file_size'] = $file_info['size'];
522
- $return['extra']['backup_size'] = $archive_info->getSize();
523
-
524
- if ($last_position > 0) {
525
- $start_byte = $last_position;
526
- } else {
527
- $extra_params['start_at_line']++;
528
- $file->next();
529
- $start_byte = 0;
530
- $counter++;
531
- }
532
 
533
- if ($this->processed_size_bytes >= $this->file_size_per_request_limit) {
534
- clearstatcache();
535
- $return['extra']['backup_size'] = $archive_info->getSize();
536
 
537
- $return['finished'] = 0;
538
- $return['extra']['start_at_line'] = $extra_params['start_at_line'];
539
- $return['extra']['start_at_byte'] = $last_position;
540
- $this->logger->info(sprintf("Reached the maximum %s request data limit, returning response",
541
- $this->file_size_per_request_limit));
542
- return $return;
543
- }
544
- }
545
-
546
- if (!$file->eof()) {
547
- clearstatcache();
548
- $return['extra']['backup_size'] = $archive_info->getSize();
549
 
550
- $return['finished'] = 0;
551
- $return['extra']['start_at_line'] = $extra_params['start_at_line'];
552
- $return['extra']['start_at_byte'] = $last_position;
553
- $this->logger->info(sprintf("We have reached the maximum files to process per request limit of %s, returning response",
554
- $this->files_to_process_per_request));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
555
 
556
- return $return;
557
- }
 
 
 
 
 
 
 
 
 
 
 
 
558
 
559
- //close the backup archive by adding 2*512 blocks of zero bytes
560
- $this->logger->info(sprintf("Closing the backup archive %s with 2*512 zero bytes blocks.",
561
- $this->get_archive_name_with_extension()));
562
- $this->backup_archive->close();
563
 
564
- /**
565
- * XCloner HOOK backup_archive_finished.
566
- *
567
- * This will get triggered when a backup archive is finished writing.
568
- */
569
- //do_action('backup_archive_finished', $this->backup_archive, $this);
 
570
 
571
- //updating archive_info
572
- $archive_info = $this->filesystem->get_storage_path_file_info($this->get_archive_name_with_extension());
573
 
574
- if ($return['extra']['backup_part']) {
575
- $this->write_multipart_file($this->get_archive_name_with_extension());
576
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
 
578
- $return['extra']['start_at_line'] = $extra_params['start_at_line'] - 1;
579
 
580
- if (isset($file_info)) {
581
- $return['extra']['processed_file'] = $file_info['path'];
582
- $return['extra']['processed_file_size'] = $file_info['size'];
583
- }
584
 
585
- clearstatcache();
586
- $return['extra']['backup_size'] = $archive_info->getSize();
587
 
588
- $return['finished'] = 1;
589
- return $return;
590
- }
591
 
592
- /*
593
  *
594
  * Write multipart file components
595
  *
596
  */
597
- private function write_multipart_file($path = "")
598
- {
599
- if (!$path) {
600
- $path = $this->get_archive_name_with_extension();
601
- }
602
 
603
- $file = $this->filesystem->get_filesystem("storage_filesystem_append")->getMetadata($path);
604
- //print_r($file_info);
605
- $line = '"'.$file['path'].'","'.$file['timestamp'].'","'.$file['size'].'"'.PHP_EOL;
606
 
607
 
608
- $this->filesystem->get_filesystem("storage_filesystem_append")
609
- ->write($this->get_archive_name_multipart(), $line);
610
- }
611
 
612
- /*
613
  *
614
  * Create a new backup part
615
  *
616
  */
617
- private function create_new_backup_part($part = 0)
618
- {
619
- //close the backup archive by adding 2*512 blocks of zero bytes
620
- $this->logger->info(sprintf("Closing the backup archive %s with 2*512 zero bytes blocks.",
621
- $this->get_archive_name_with_extension()));
622
- $this->backup_archive->close();
623
-
624
- if (!$part) {
625
- $old_name = $this->get_archive_name_with_extension();
626
- $this->set_archive_name($this->get_archive_name(), ++$part);
627
- $this->rename_archive($old_name, $this->get_archive_name_with_extension());
628
-
629
- if ($this->filesystem->get_storage_filesystem()->has($this->get_archive_name_multipart())) {
630
- $this->filesystem->get_storage_filesystem()->delete($this->get_archive_name_multipart());
631
- }
632
-
633
- $this->write_multipart_file($this->get_archive_name_with_extension());
634
-
635
- } else {
636
- $this->logger->info(sprintf("Creating new multipart info file %s",
637
- $this->get_archive_name_with_extension()));
638
- $this->write_multipart_file($this->get_archive_name_with_extension());
639
- }
640
 
641
- $this->set_archive_name($this->get_archive_name(), ++$part);
 
 
 
 
 
 
 
642
 
643
- $this->logger->info(sprintf("Creating new backup archive part %s", $this->get_archive_name_with_extension()));
644
 
645
- $this->backup_archive = new Tar();
646
- $this->backup_archive->setCompression($this->compression_level);
647
- $archive_info = $this->filesystem->get_storage_path_file_info($this->get_archive_name_with_extension());
648
- $this->backup_archive->create($archive_info->getPath().DS.$archive_info->getFilename());
649
 
650
- return array($archive_info, $part);
 
 
 
651
 
652
- }
 
653
 
654
- /*
655
  *
656
  * Add file to archive
657
  *
658
  */
659
 
660
- /**
661
- * @param integer $append
662
- */
663
- public function add_file_to_archive($file_info, $start_at_byte, $byte_limit = 0, $append, $filesystem)
664
- {
665
-
666
- $start_adapter = $this->filesystem->get_adapter($filesystem);
667
- $start_filesystem = $this->filesystem->get_adapter($filesystem);
668
-
669
- if (!$file_info['path']) {
670
- return;
671
- }
672
-
673
- if (isset($file_info['archive_prefix_path'])) {
674
- $file_info['target_path'] = $file_info['archive_prefix_path']."/".$file_info['path'];
675
- } else {
676
- $file_info['target_path'] = $file_info['path'];
677
- }
678
-
679
- $last_position = $start_at_byte;
680
-
681
- //$start_adapter = $this->filesystem->get_start_adapter();
682
-
683
- if (!$append) {
684
- $bytes_wrote = $file_info['size'];
685
- $this->logger->info(sprintf("Adding %s bytes of file %s to archive %s ", $bytes_wrote,
686
- $file_info['target_path'], $this->get_archive_name_with_extension()));
687
- $this->backup_archive->addFile($start_adapter->applyPathPrefix($file_info['path']),
688
- $file_info['target_path']);
689
- } else {
690
- $tmp_file = md5($file_info['path']);
691
-
692
- //we isolate file to tmp if we are at byte 0, the starting point of file reading
693
- if (!$start_at_byte) {
694
- $this->logger->info(sprintf("Copying %s file to tmp filesystem file %s to prevent reading changes",
695
- $file_info['path'], $tmp_file));
696
- $file_stream = $start_filesystem->readStream($file_info['path']);
697
-
698
- if (is_resource($file_stream['stream'])) {
699
- $this->filesystem->get_tmp_filesystem()->writeStream($tmp_file, $file_stream['stream']);
700
- }
701
- }
702
-
703
- if ($this->filesystem->get_tmp_filesystem()->has($tmp_file)) {
704
- $is_tmp = 1;
705
- $last_position = $this->backup_archive->appendFileData($this->filesystem->get_tmp_filesystem_adapter()
706
- ->applyPathPrefix($tmp_file),
707
- $file_info['target_path'], $start_at_byte, $byte_limit);
708
- } else {
709
- $is_tmp = 0;
710
- $last_position = $this->backup_archive->appendFileData($start_adapter->applyPathPrefix($file_info['path']),
711
- $file_info['target_path'], $start_at_byte, $byte_limit);
712
- }
713
-
714
-
715
- if ($last_position == -1) {
716
- $bytes_wrote = $file_info['size'] - $start_at_byte;
717
- } else {
718
- $bytes_wrote = $last_position - $start_at_byte;
719
- }
720
 
 
 
 
721
 
722
- if ($is_tmp) {
723
- $this->logger->info(sprintf("Appended %s bytes, starting position %s, of tmp file %s (%s) to archive %s ",
724
- $bytes_wrote, $start_at_byte, $tmp_file, $file_info['target_path'], $this->get_archive_name()));
725
- } else {
726
- $this->logger->info(sprintf("Appended %s bytes, starting position %s, of original file %s to archive %s ",
727
- $bytes_wrote, $start_at_byte, $file_info['target_path'], $tmp_file, $this->get_archive_name()));
728
- }
729
 
730
- //we delete here the isolated tmp file
731
- if ($last_position == -1) {
732
- if ($this->filesystem->get_tmp_filesystem_adapter()->has($tmp_file)) {
733
- $this->logger->info(sprintf("Deleting %s from the tmp filesystem", $tmp_file));
734
- $this->filesystem->get_tmp_filesystem_adapter()->delete($tmp_file);
735
- }
736
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
737
 
738
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
739
 
740
- return array($bytes_wrote, $last_position);
741
- }
742
 
743
- /**
744
- * Open a TAR archive and put the file cursor at the end for data appending
745
- *
746
- * If $file is empty, the tar file will be created in memory
747
- *
748
- * @param string $file
749
- * @throws ArchiveIOException
750
- */
751
- /*
752
- public function openForAppend($file = '')
753
- {
754
- $this->file = $file;
755
- $this->memory = '';
756
- $this->fh = 0;
757
-
758
- if ($this->file) {
759
- // determine compression
760
- if ($this->comptype == Archive::COMPRESS_AUTO) {
761
- $this->setCompression($this->complevel, $this->filetype($file));
762
  }
763
 
764
- if ($this->comptype === Archive::COMPRESS_GZIP) {
765
- $this->fh = @gzopen($this->file, 'ab'.$this->complevel);
766
- } elseif ($this->comptype === Archive::COMPRESS_BZIP) {
767
- $this->fh = @bzopen($this->file, 'a');
 
 
 
 
 
 
768
  } else {
769
- $this->fh = @fopen($this->file, 'ab');
 
 
 
 
 
 
 
770
  }
771
 
772
- if (!$this->fh) {
773
- throw new ArchiveIOException('Could not open file for writing: '.$this->file);
 
 
 
 
774
  }
775
  }
776
- $this->backup_archive->writeaccess = true;
777
- $this->backup_archive->closed = false;
778
  }
779
- */
780
 
781
- /**
782
- * Append data to a file to the current TAR archive using an existing file in the filesystem
783
- *
784
- * @param string $file path to the original file
785
- * @param int $start starting reading position in file
786
- * @param int $end end position in reading multiple with 512
787
- * @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with
788
- * all meta data, empty to take from original
789
- * @throws ArchiveIOException
790
- */
791
- /*
792
- * public function appendFileData($file, $fileinfo = '', $start = 0, $limit = 0)
793
  {
794
- $end = $start+($limit*512);
795
 
796
- //check to see if we are at the begining of writing the file
797
- if(!$start)
798
- {
799
- if (is_string($fileinfo)) {
800
- $fileinfo = FileInfo::fromPath($file, $fileinfo);
801
- }
802
- }
803
 
804
  if ($this->closed) {
805
  throw new ArchiveIOException('Archive has been closed, files can no longer be added');
806
  }
807
 
 
 
 
 
 
808
  $fp = fopen($file, 'rb');
809
 
810
  fseek($fp, $start);
@@ -814,12 +827,13 @@ class Xcloner_Archive extends Tar
814
  }
815
 
816
  // create file header
817
- if(!$start)
818
- $this->backup_archive->writeFileHeader($fileinfo);
 
819
 
820
- $bytes = 0;
821
  // write data
822
- while ($end >=ftell($fp) and !feof($fp) ) {
823
  $data = fread($fp, 512);
824
  if ($data === false) {
825
  break;
@@ -828,35 +842,131 @@ class Xcloner_Archive extends Tar
828
  break;
829
  }
830
  $packed = pack("a512", $data);
831
- $bytes += $this->backup_archive->writebytes($packed);
832
  }
833
 
834
 
835
 
836
  //if we are not at the end of file, we return the current position for incremental writing
837
- if(!feof($fp))
838
- $last_position = ftell($fp);
839
- else
840
- $last_position = -1;
 
841
 
842
  fclose($fp);
843
 
844
  return $last_position;
845
- }*/
846
-
847
- /**
848
- * Adds a file to a TAR archive by appending it's data
849
- *
850
- * @param string $archive name of the archive file
851
- * @param string $file name of the file to read data from
852
- * @param string $start start position from where to start reading data
853
- * @throws ArchiveIOException
854
- */
855
- /*public function addFileToArchive($archive, $file, $start = 0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
856
  {
857
  $this->openForAppend($archive);
858
  return $start = $this->appendFileData($file, $start, $this->file_size_per_request_limit);
859
  }
860
  */
861
-
862
  }
36
  */
37
  class Xcloner_Archive extends Tar
38
  {
39
+ /**
40
+ * Process file size per API request
41
+ * @var float|int
42
+ */
43
+ private $file_size_per_request_limit = 52428800; //50MB = 52428800; 1MB = 1048576
44
+ /**
45
+ * Files count to process per API request
46
+ * @var int
47
+ */
48
+ private $files_to_process_per_request = 250; //block of 512 bytes
49
+ /**
50
+ * Compression level, 0-uncompressed, 9-maximum compression
51
+ * @var int
52
+ */
53
+ private $compression_level = 0; //0-9 , 0 uncompressed
54
+ /**
55
+ * Split backup size limit
56
+ * Create a new backup archive file once the set size is reached
57
+ * @var float|int
58
+ */
59
+ private $xcloner_split_backup_limit = 2048; //2048MB
60
+ /**
61
+ * Number of processed bytes
62
+ * @var int
63
+ */
64
+ private $processed_size_bytes = 0;
65
 
66
  /**
67
  * The backup name encryption suffix
68
  * @var string
69
  */
70
+ private $encrypt_suffix = "-enc";
71
+
72
+ /**
73
+ * Archive name
74
+ * @var string
75
+ */
76
+ private $archive_name;
77
+ /**
78
+ * @var Tar
79
+ */
80
+ private $backup_archive;
81
+ /**
82
+ * @var Xcloner_File_System
83
+ */
84
+ private $filesystem;
85
+ /**
86
+ * @var Xcloner_Logger
87
+ */
88
+ private $logger;
89
+ /**
90
+ * @var Xcloner_Settings
91
+ */
92
+ private $xcloner_settings;
93
+
94
+ /**
95
+ * [__construct description]
96
+ * @param Xcloner $xcloner_container XCloner Container
97
+ * @param string $archive_name Achive Name
98
+ */
99
+ public function __construct(Xcloner $xcloner_container, $archive_name = "")
100
+ {
101
+ $this->filesystem = $xcloner_container->get_xcloner_filesystem();
102
+ $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_archive");
103
+ $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
104
+
105
+ if ($value = $this->xcloner_settings->get_xcloner_option('xcloner_size_limit_per_request')) {
106
+ $this->file_size_per_request_limit = $value * 1024 * 1024;
107
+ } //MB
108
+
109
+ if ($value = $this->xcloner_settings->get_xcloner_option('xcloner_files_to_process_per_request')) {
110
+ $this->files_to_process_per_request = $value;
111
+ }
112
+
113
+ if ($value = $this->xcloner_settings->get_xcloner_option('xcloner_backup_compression_level')) {
114
+ $this->compression_level = $value;
115
+ }
116
+
117
+ if ($value = $this->xcloner_settings->get_xcloner_option('xcloner_split_backup_limit')) {
118
+ $this->xcloner_split_backup_limit = $value;
119
+ }
120
+
121
+ $this->xcloner_split_backup_limit = $this->xcloner_split_backup_limit * 1024 * 1024; //transform to bytes
122
+
123
+ if (isset($archive_name) && $archive_name) {
124
+ $this->set_archive_name($archive_name);
125
+ }
126
+ }
127
+
128
+ /*
129
  * Rename backup archive
130
  *
131
  * @param string $old_name
132
  * @param string $new_name
133
  *
134
  */
135
+ public function rename_archive($old_name, $new_name)
136
+ {
137
+ $this->logger->info(sprintf("Renaming backup archive %s to %s", $old_name, $new_name));
138
+ $storage_filesystem = $this->filesystem->get_storage_filesystem();
139
+ $storage_filesystem->rename($old_name, $new_name);
140
+ }
141
+
142
+ /*
143
  *
144
  * Set the backup archive name
145
  *
146
  */
147
+ public function set_archive_name($name = "", $part = 0, $encrypt_prefix = false)
148
+ {
149
+ $this->archive_name = $this->filesystem->process_backup_name($name);
 
150
 
151
+ if ($encrypt_prefix) {
152
+ $this->archive_name .= $this->encrypt_suffix;
153
  }
154
 
155
+ if ($diff_timestamp_start = $this->filesystem->get_diff_timestamp_start()) {
156
+ //$this->archive_name = $this->archive_name."-diff-".date("Y-m-d_H-i",$diff_timestamp_start);
157
+ $new_name = $this->archive_name;
 
 
 
 
158
 
159
+ if (!stristr($new_name, "-diff")) {
160
+ $new_name = $this->archive_name."-diff".date("Y-m-d_H-i", $diff_timestamp_start);
161
+ }
162
 
163
+ $this->archive_name = $new_name;
164
+ }
165
 
166
+ if (isset($part) and $part) {
167
+ $new_name = preg_replace('/-part(\d*)/', "-part".$part, $this->archive_name);
168
+ if (!stristr($new_name, "-part")) {
169
+ $new_name = $this->archive_name."-part".$part;
170
+ }
171
 
172
+ $this->archive_name = $new_name;
173
+ }
174
 
175
+ return $this;
176
+ }
177
 
178
+ /*
179
  *
180
  * Returns the backup archive name
181
  *
182
  * @return string archive name
183
  */
184
+ public function get_archive_name()
185
+ {
186
+ return $this->archive_name;
187
+ }
188
 
189
+ /*
190
  *
191
  * Returns the multipart naming for the backup archive
192
  *
193
  * @return string multi-part backup name
194
  */
195
+ public function get_archive_name_multipart()
196
+ {
197
+ $new_name = preg_replace('/-part(\d*)/', "", $this->archive_name);
198
+ return $new_name."-multipart".$this->xcloner_settings->get_backup_extension_name(".csv");
199
+ }
200
 
201
+ /*
202
  *
203
  * Returns the full backup name including extension
204
  *
205
  */
206
+ public function get_archive_name_with_extension()
207
+ {
208
+ return $this->archive_name.$this->xcloner_settings->get_backup_extension_name();
209
+ }
210
 
211
+ /*
212
  *
213
  * Send notification error by E-Mail
214
  *
222
  * @return bool
223
  */
224
 
225
+ /**
226
+ * @param string $error_message
227
+ */
228
+ public function send_notification_error($to, $from, $subject, $backup_name, $params, $error_message)
229
+ {
230
+ $body = "";
231
+ $body .= sprintf(__("Backup Site Url: %s"), get_site_url());
232
+ $body .= "<br /><>";
 
233
 
234
+ $body .= sprintf(__("Error Message: %s"), $error_message);
235
 
236
+ $this->logger->info(sprintf("Sending backup error notification to %s", $to));
237
 
238
+ $admin_email = $this->xcloner_settings->get_xcloner_option("admin_email");
239
 
240
+ $headers = array('Content-Type: text/html; charset=UTF-8');
241
 
242
+ if ($admin_email and $from) {
243
+ $headers[] = 'From: '.$from.' <'.$admin_email.'>';
244
+ }
245
 
246
+ $return = wp_mail($to, $subject, $body, $headers);
247
 
248
+ return $return;
249
+ }
250
 
251
+ /*
252
  *
253
  * Send backup archive notfication by E-Mail
254
  *
262
  *
263
  * @return bool
264
  */
265
+ public function send_notification(
266
+ $to,
267
+ $from,
268
+ $subject,
269
+ $backup_name,
270
+ $params,
271
+ $error_message = "",
272
+ $additional = array()
273
+ ) {
274
+ if (!$from) {
275
+ $from = "XCloner Backup";
276
+ }
277
 
278
+ if (($error_message)) {
279
+ return $this->send_notification_error($to, $from, $subject, $backup_name, $params, $error_message);
280
+ }
281
 
282
+ $params = (array)$params;
283
 
284
+ if (!$subject) {
285
+ $subject = sprintf(__("New backup generated %s"), $backup_name);
286
+ }
287
 
288
+ $body = sprintf(__("Generated Backup Size: %s"), size_format($this->filesystem->get_backup_size($backup_name)));
289
+ $body .= "<br /><br />";
290
 
291
+ if (isset($additional['lines_total'])) {
292
+ $body .= sprintf(__("Total files added: %s"), $additional['lines_total']);
293
+ $body .= "<br /><br />";
294
+ }
295
 
296
+ $backup_parts = $this->filesystem->get_multipart_files($backup_name);
297
 
298
+ if (!$backups_counter = sizeof($backup_parts)) {
299
+ $backups_counter = 1;
300
+ }
301
 
302
+ $body .= sprintf(__("Backup Parts: %s"), $backups_counter);
303
+ $body .= "<br />";
304
 
305
+ if (sizeof($backup_parts)) {
306
+ $body .= implode("<br />", $backup_parts);
307
+ $body .= "<br />";
308
+ }
309
 
310
+ $body .= "<br />";
311
 
312
+ $body .= sprintf(__("Backup Site Url: %s"), get_site_url());
313
+ $body .= "<br />";
314
 
315
+ if (isset($params['backup_params']->backup_comments)) {
316
+ $body .= __("Backup Comments: ").$params['backup_params']->backup_comments;
317
+ $body .= "<br /><br />";
318
+ }
319
 
320
+ if ($this->xcloner_settings->get_xcloner_option('xcloner_enable_log')) {
321
+ $body .= __("Latest 50 Log Lines: ")."<br />".implode(
322
+ "<br />\n",
323
+ $this->logger->getLastDebugLines(50)
324
+ );
325
+ }
326
 
327
+ $attachments = $this->filesystem->get_backup_attachments();
328
 
329
+ $attachments_archive = $this->xcloner_settings->get_xcloner_tmp_path().DS."info.tgz";
330
 
331
+ $tar = $this;
332
+ $tar->create($attachments_archive);
333
 
334
+ foreach ($attachments as $key => $file) {
335
+ $tar->addFile($file, basename($file));
336
+ }
337
+ $tar->close();
338
 
339
+ $this->logger->info(sprintf("Sending backup notification to %s", $to));
340
 
341
+ $admin_email = $this->xcloner_settings->get_xcloner_option("admin_email");
342
 
343
+ $headers = array('Content-Type: text/html; charset=UTF-8', 'From: '.$from.' <'.$admin_email.'>');
344
 
345
+ $return = wp_mail($to, $subject, $body, $headers, array($attachments_archive));
346
 
347
+ return $return;
348
+ }
349
 
350
+ /*
351
  *
352
  * Incremental Backup method
353
  *
354
  */
355
+ public function start_incremental_backup($backup_params, $extra_params, $init)
356
+ {
357
+ $return = array();
358
 
359
+ if (!isset($extra_params['backup_part'])) {
360
+ $extra_params['backup_part'] = 0;
361
+ }
362
 
363
+ $return['extra']['backup_part'] = $extra_params['backup_part'];
364
 
365
+ if (isset($extra_params['backup_archive_name'])) {
366
+ $this->set_archive_name($extra_params['backup_archive_name'], $return['extra']['backup_part']);
367
+ } else {
368
+ $encrypt = false;
369
+ if (isset($backup_params['backup_encrypt']) && $backup_params['backup_encrypt']) {
370
  $encrypt = true;
371
  }
372
+ $this->set_archive_name($backup_params['backup_name'], 0, $encrypt);
373
+ }
 
 
 
 
 
 
 
 
 
 
 
 
374
 
375
+ if (!$this->get_archive_name()) {
376
+ $this->set_archive_name();
377
+ }
378
 
379
+ $this->backup_archive = $this;
380
+ $this->backup_archive->setCompression($this->compression_level);
381
 
382
+ $archive_info = $this->filesystem->get_storage_path_file_info($this->get_archive_name_with_extension());
 
383
 
384
+ if ($init) {
385
+ $this->logger->info(sprintf(__("Initializing the backup archive %s"), $this->get_archive_name()));
386
 
387
+ $this->backup_archive->create($archive_info->getPath().DS.$archive_info->getFilename());
388
 
389
+ $return['extra']['backup_init'] = 1;
390
+ } else {
391
+ $this->logger->info(sprintf(__("Opening for append the backup archive %s"), $this->get_archive_name()));
392
 
393
+ $this->backup_archive->openForAppend($archive_info->getPath().DS.$archive_info->getFilename());
 
394
 
395
+ $return['extra']['backup_init'] = 0;
396
+ }
 
397
 
398
+ $return['extra']['backup_archive_name'] = $this->get_archive_name();
399
+ $return['extra']['backup_archive_name_full'] = $this->get_archive_name_with_extension();
 
400
 
401
+ if (!isset($extra_params['start_at_line'])) {
402
+ $extra_params['start_at_line'] = 0;
403
+ }
404
 
405
+ if (!isset($extra_params['start_at_byte'])) {
406
+ $extra_params['start_at_byte'] = 0;
407
+ }
408
 
409
+ if (!$this->filesystem->get_tmp_filesystem()->has($this->filesystem->get_included_files_handler())) {
410
+ $this->logger->error(sprintf(
411
+ "Missing the includes file handler %s, aborting...",
412
+ $this->filesystem->get_included_files_handler()
413
+ ));
414
 
415
+ $return['finished'] = 1;
416
+ return $return;
417
+ }
418
 
419
+ $included_files_handler = $this->filesystem->get_included_files_handler(1);
420
 
421
+ $file = new SplFileObject($included_files_handler);
422
 
423
+ $file->seek(PHP_INT_MAX);
 
 
 
 
 
424
 
425
+ $return['extra']['lines_total'] = ($file->key() - 1);
426
 
427
+ //we skip the first CSV line with headers
428
+ if (!$extra_params['start_at_line']) {
429
+ $file->seek(1);
430
+ } else {
431
+ $file->seek($extra_params['start_at_line'] + 1);
432
+ }
433
 
434
+ $this->processed_size_bytes = 0;
435
 
436
+ $counter = 0;
437
 
438
+ $start_byte = $extra_params['start_at_byte'];
 
439
 
440
+ $byte_limit = 0;
441
 
442
+ while (!$file->eof() and $counter <= $this->files_to_process_per_request) {
443
+ $current_line_str = $file->current();
444
 
445
+ $line = str_getcsv($current_line_str);
446
 
447
+ $relative_path = stripslashes($line[0]);
 
 
448
 
449
+ $start_filesystem = "start_filesystem";
450
 
451
+ if (isset($line[4])) {
452
+ $start_filesystem = $line[4];
453
+ }
 
 
 
 
 
 
 
454
 
455
+ //$adapter = $this->filesystem->get_adapter($start_filesystem);
456
 
457
+ if (!$relative_path || !$this->filesystem->get_filesystem($start_filesystem)->has($relative_path)) {
458
+ if ($relative_path != "") {
459
+ $this->logger->error(sprintf(
460
+ "Could not add file %b to backup archive, file not found",
461
+ $relative_path
462
+ ));
463
+ }
464
 
465
+ $extra_params['start_at_line']++;
466
+ $file->next();
467
+ continue;
468
+ }
469
 
470
+ $file_info = $this->filesystem->get_filesystem($start_filesystem)->getMetadata($relative_path);
471
 
472
+ if (!isset($file_info['size'])) {
473
+ $file_info['size'] = 0;
474
+ }
475
 
476
+ if ($start_filesystem == "tmp_filesystem") {
477
+ $file_info['archive_prefix_path'] = $this->xcloner_settings->get_xcloner_tmp_path_suffix();
478
+ }
479
 
480
+ $byte_limit = (int)$this->file_size_per_request_limit / 512;
 
 
481
 
482
+ $append = 0;
483
 
484
+ if ($file_info['size'] > $byte_limit * 512 or $start_byte) {
485
+ $append = 1;
486
+ }
487
 
488
+ if (!isset($return['extra']['backup_size'])) {
489
+ $return['extra']['backup_size'] = 0;
490
+ }
 
 
491
 
492
+ $return['extra']['backup_size'] = $archive_info->getSize();
 
 
 
 
493
 
494
+ $estimated_new_size = $return['extra']['backup_size'] + $file_info['size'];
 
495
 
496
+ //we create a new backup part if we reach the Split Achive Limit
497
+ if ($this->xcloner_split_backup_limit and ($estimated_new_size > $this->xcloner_split_backup_limit) and (!$start_byte)) {
498
+ $this->logger->info(sprintf(
499
+ "Backup size limit %s bytes reached, file add estimate %s, attempt to create a new archive ",
500
+ $this->xcloner_split_backup_limit,
501
+ $estimated_new_size
502
+ ));
503
+ list($archive_info, $return['extra']['backup_part']) = $this->create_new_backup_part($return['extra']['backup_part']);
504
 
505
+ if ($file_info['size'] > $this->xcloner_split_backup_limit) {
506
+ $this->logger->info(sprintf(
507
+ "Excluding %s file as it's size(%s) is bigger than the backup split limit of %s and it won't fit a single backup file",
508
+ $file_info['path'],
509
+ $file_info['size'],
510
+ $this->xcloner_split_backup_limit
511
+ ));
512
+ $extra_params['start_at_line']++;
513
+ }
514
 
515
+ $return['extra']['start_at_line'] = $extra_params['start_at_line'];
516
+ $return['extra']['start_at_byte'] = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
 
518
+ $return['finished'] = 0;
 
 
519
 
520
+ return $return;
521
+ }
 
 
 
 
 
 
 
 
 
 
522
 
523
+ list($bytes_wrote, $last_position) = $this->add_file_to_archive(
524
+ $file_info,
525
+ $start_byte,
526
+ $byte_limit,
527
+ $append,
528
+ $start_filesystem
529
+ );
530
+ $this->processed_size_bytes += $bytes_wrote;
531
+
532
+ //echo" - processed ".$this->processed_size_bytes." bytes ".$this->file_size_per_request_limit." last_position:".$last_position." \n";
533
+ $return['extra']['processed_file'] = $file_info['path'];
534
+ $return['extra']['processed_file_size'] = $file_info['size'];
535
+ $return['extra']['backup_size'] = $archive_info->getSize();
536
+
537
+ if ($last_position > 0) {
538
+ $start_byte = $last_position;
539
+ } else {
540
+ $extra_params['start_at_line']++;
541
+ $file->next();
542
+ $start_byte = 0;
543
+ $counter++;
544
+ }
545
 
546
+ if ($this->processed_size_bytes >= $this->file_size_per_request_limit) {
547
+ clearstatcache();
548
+ $return['extra']['backup_size'] = $archive_info->getSize();
549
+
550
+ $return['finished'] = 0;
551
+ $return['extra']['start_at_line'] = $extra_params['start_at_line'];
552
+ $return['extra']['start_at_byte'] = $last_position;
553
+ $this->logger->info(sprintf(
554
+ "Reached the maximum %s request data limit, returning response",
555
+ $this->file_size_per_request_limit
556
+ ));
557
+ return $return;
558
+ }
559
+ }
560
 
561
+ if (!$file->eof()) {
562
+ clearstatcache();
563
+ $return['extra']['backup_size'] = $archive_info->getSize();
 
564
 
565
+ $return['finished'] = 0;
566
+ $return['extra']['start_at_line'] = $extra_params['start_at_line'];
567
+ $return['extra']['start_at_byte'] = $last_position;
568
+ $this->logger->info(sprintf(
569
+ "We have reached the maximum files to process per request limit of %s, returning response",
570
+ $this->files_to_process_per_request
571
+ ));
572
 
573
+ return $return;
574
+ }
575
 
576
+ //close the backup archive by adding 2*512 blocks of zero bytes
577
+ $this->logger->info(sprintf(
578
+ "Closing the backup archive %s with 2*512 zero bytes blocks.",
579
+ $this->get_archive_name_with_extension()
580
+ ));
581
+ $this->backup_archive->close();
582
+
583
+ /**
584
+ * XCloner HOOK backup_archive_finished.
585
+ *
586
+ * This will get triggered when a backup archive is finished writing.
587
+ */
588
+ //do_action('backup_archive_finished', $this->backup_archive, $this);
589
+
590
+ //updating archive_info
591
+ $archive_info = $this->filesystem->get_storage_path_file_info($this->get_archive_name_with_extension());
592
+
593
+ if ($return['extra']['backup_part']) {
594
+ $this->write_multipart_file($this->get_archive_name_with_extension());
595
+ }
596
 
597
+ $return['extra']['start_at_line'] = $extra_params['start_at_line'] - 1;
598
 
599
+ if (isset($file_info)) {
600
+ $return['extra']['processed_file'] = $file_info['path'];
601
+ $return['extra']['processed_file_size'] = $file_info['size'];
602
+ }
603
 
604
+ clearstatcache();
605
+ $return['extra']['backup_size'] = $archive_info->getSize();
606
 
607
+ $return['finished'] = 1;
608
+ return $return;
609
+ }
610
 
611
+ /*
612
  *
613
  * Write multipart file components
614
  *
615
  */
616
+ private function write_multipart_file($path = "")
617
+ {
618
+ if (!$path) {
619
+ $path = $this->get_archive_name_with_extension();
620
+ }
621
 
622
+ $file = $this->filesystem->get_filesystem("storage_filesystem_append")->getMetadata($path);
623
+ //print_r($file_info);
624
+ $line = '"'.$file['path'].'","'.$file['timestamp'].'","'.$file['size'].'"'.PHP_EOL;
625
 
626
 
627
+ $this->filesystem->get_filesystem("storage_filesystem_append")
628
+ ->write($this->get_archive_name_multipart(), $line);
629
+ }
630
 
631
+ /*
632
  *
633
  * Create a new backup part
634
  *
635
  */
636
+ private function create_new_backup_part($part = 0)
637
+ {
638
+ //close the backup archive by adding 2*512 blocks of zero bytes
639
+ $this->logger->info(sprintf(
640
+ "Closing the backup archive %s with 2*512 zero bytes blocks.",
641
+ $this->get_archive_name_with_extension()
642
+ ));
643
+ $this->backup_archive->close();
644
+
645
+ if (!$part) {
646
+ $old_name = $this->get_archive_name_with_extension();
647
+ $this->set_archive_name($this->get_archive_name(), ++$part);
648
+ $this->rename_archive($old_name, $this->get_archive_name_with_extension());
649
+
650
+ if ($this->filesystem->get_storage_filesystem()->has($this->get_archive_name_multipart())) {
651
+ $this->filesystem->get_storage_filesystem()->delete($this->get_archive_name_multipart());
652
+ }
 
 
 
 
 
 
653
 
654
+ $this->write_multipart_file($this->get_archive_name_with_extension());
655
+ } else {
656
+ $this->logger->info(sprintf(
657
+ "Creating new multipart info file %s",
658
+ $this->get_archive_name_with_extension()
659
+ ));
660
+ $this->write_multipart_file($this->get_archive_name_with_extension());
661
+ }
662
 
663
+ $this->set_archive_name($this->get_archive_name(), ++$part);
664
 
665
+ $this->logger->info(sprintf("Creating new backup archive part %s", $this->get_archive_name_with_extension()));
 
 
 
666
 
667
+ $this->backup_archive = $this;
668
+ $this->backup_archive->setCompression($this->compression_level);
669
+ $archive_info = $this->filesystem->get_storage_path_file_info($this->get_archive_name_with_extension());
670
+ $this->backup_archive->create($archive_info->getPath().DS.$archive_info->getFilename());
671
 
672
+ return array($archive_info, $part);
673
+ }
674
 
675
+ /*
676
  *
677
  * Add file to archive
678
  *
679
  */
680
 
681
+ /**
682
+ * @param integer $append
683
+ */
684
+ public function add_file_to_archive($file_info, $start_at_byte, $byte_limit = 0, $append, $filesystem)
685
+ {
686
+ $start_adapter = $this->filesystem->get_adapter($filesystem);
687
+ $start_filesystem = $this->filesystem->get_adapter($filesystem);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
688
 
689
+ if (!$file_info['path']) {
690
+ return;
691
+ }
692
 
693
+ if (isset($file_info['archive_prefix_path'])) {
694
+ $file_info['target_path'] = $file_info['archive_prefix_path']."/".$file_info['path'];
695
+ } else {
696
+ $file_info['target_path'] = $file_info['path'];
697
+ }
 
 
698
 
699
+ $last_position = $start_at_byte;
700
+
701
+ //$start_adapter = $this->filesystem->get_start_adapter();
702
+
703
+ if (!$append) {
704
+ $bytes_wrote = $file_info['size'];
705
+ $this->logger->info(sprintf(
706
+ "Adding %s bytes of file %s to archive %s ",
707
+ $bytes_wrote,
708
+ $file_info['target_path'],
709
+ $this->get_archive_name_with_extension()
710
+ ));
711
+ $this->backup_archive->addFile(
712
+ $start_adapter->applyPathPrefix($file_info['path']),
713
+ $file_info['target_path']
714
+ );
715
+ } else {
716
+ $tmp_file = md5($file_info['path']);
717
+
718
+ //we isolate file to tmp if we are at byte 0, the starting point of file reading
719
+ if (!$start_at_byte) {
720
+ $this->logger->info(sprintf(
721
+ "Copying %s file to tmp filesystem file %s to prevent reading changes",
722
+ $file_info['path'],
723
+ $tmp_file
724
+ ));
725
+ $file_stream = $start_filesystem->readStream($file_info['path']);
726
+
727
+ if (is_resource($file_stream['stream'])) {
728
+ $this->filesystem->get_tmp_filesystem()->writeStream($tmp_file, $file_stream['stream']);
729
+ }
730
+ }
731
 
732
+ if ($this->filesystem->get_tmp_filesystem()->has($tmp_file)) {
733
+ $is_tmp = 1;
734
+ $last_position = $this->backup_archive->appendFileData(
735
+ $this->filesystem->get_tmp_filesystem_adapter()
736
+ ->applyPathPrefix($tmp_file),
737
+ $file_info['target_path'],
738
+ $start_at_byte,
739
+ $byte_limit
740
+ );
741
+ } else {
742
+ $is_tmp = 0;
743
+ $last_position = $this->backup_archive->appendFileData(
744
+ $start_adapter->applyPathPrefix($file_info['path']),
745
+ $file_info['target_path'],
746
+ $start_at_byte,
747
+ $byte_limit
748
+ );
749
+ }
750
 
 
 
751
 
752
+ if ($last_position == -1) {
753
+ $bytes_wrote = $file_info['size'] - $start_at_byte;
754
+ } else {
755
+ $bytes_wrote = $last_position - $start_at_byte;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
756
  }
757
 
758
+
759
+ if ($is_tmp) {
760
+ $this->logger->info(sprintf(
761
+ "Appended %s bytes, starting position %s, of tmp file %s (%s) to archive %s ",
762
+ $bytes_wrote,
763
+ $start_at_byte,
764
+ $tmp_file,
765
+ $file_info['target_path'],
766
+ $this->get_archive_name()
767
+ ));
768
  } else {
769
+ $this->logger->info(sprintf(
770
+ "Appended %s bytes, starting position %s, of original file %s to archive %s ",
771
+ $bytes_wrote,
772
+ $start_at_byte,
773
+ $file_info['target_path'],
774
+ $tmp_file,
775
+ $this->get_archive_name()
776
+ ));
777
  }
778
 
779
+ //we delete here the isolated tmp file
780
+ if ($last_position == -1) {
781
+ if ($this->filesystem->get_tmp_filesystem_adapter()->has($tmp_file)) {
782
+ $this->logger->info(sprintf("Deleting %s from the tmp filesystem", $tmp_file));
783
+ $this->filesystem->get_tmp_filesystem_adapter()->delete($tmp_file);
784
+ }
785
  }
786
  }
787
+
788
+ return array($bytes_wrote, $last_position);
789
  }
 
790
 
791
+ /**
792
+ * Append data to a file to the current TAR archive using an existing file in the filesystem
793
+ *
794
+ * @param string $file path to the original file
795
+ * @param int $start starting reading position in file
796
+ * @param int $end end position in reading multiple with 512
797
+ * @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with
798
+ * all meta data, empty to take from original
799
+ * @throws ArchiveIOException
800
+ */
801
+ public function appendFileData($file, $fileinfo = '', $start = 0, $limit = 0)
 
802
  {
803
+ $end = $start+($limit*512);
804
 
805
+ //check to see if we are at the begining of writing the file
806
+ if (!$start) {
807
+ if (is_string($fileinfo)) {
808
+ $fileinfo = FileInfo::fromPath($file, $fileinfo);
809
+ }
810
+ }
 
811
 
812
  if ($this->closed) {
813
  throw new ArchiveIOException('Archive has been closed, files can no longer be added');
814
  }
815
 
816
+ if (is_dir($file)) {
817
+ $this->writeFileHeader($fileinfo);
818
+ return;
819
+ }
820
+
821
  $fp = fopen($file, 'rb');
822
 
823
  fseek($fp, $start);
827
  }
828
 
829
  // create file header
830
+ if (!$start) {
831
+ $this->writeFileHeader($fileinfo);
832
+ }
833
 
834
+ $bytes = 0;
835
  // write data
836
+ while ($end >=ftell($fp) and !feof($fp)) {
837
  $data = fread($fp, 512);
838
  if ($data === false) {
839
  break;
842
  break;
843
  }
844
  $packed = pack("a512", $data);
845
+ $bytes += $this->writebytes($packed);
846
  }
847
 
848
 
849
 
850
  //if we are not at the end of file, we return the current position for incremental writing
851
+ if (!feof($fp)) {
852
+ $last_position = ftell($fp);
853
+ } else {
854
+ $last_position = -1;
855
+ }
856
 
857
  fclose($fp);
858
 
859
  return $last_position;
860
+ }
861
+
862
+ public function open($file, $start_byte = 0)
863
+ {
864
+ parent::open($file);
865
+
866
+ if ($start_byte) {
867
+ fseek($this->fh, $start_byte);
868
+ }
869
+ }
870
+
871
+ /**
872
+ * Open a TAR archive and put the file cursor at the end for data appending
873
+ *
874
+ * If $file is empty, the tar file will be created in memory
875
+ *
876
+ * @param string $file
877
+ * @throws ArchiveIOException
878
+ */
879
+ public function openForAppend($file = '')
880
+ {
881
+ $this->file = $file;
882
+ $this->memory = '';
883
+ $this->fh = 0;
884
+
885
+ if ($this->file) {
886
+ // determine compression
887
+ if ($this->comptype == Archive::COMPRESS_AUTO) {
888
+ $this->setCompression($this->complevel, $this->filetype($file));
889
+ }
890
+
891
+ if ($this->comptype === Archive::COMPRESS_GZIP) {
892
+ $this->fh = @gzopen($this->file, 'ab'.$this->complevel);
893
+ } elseif ($this->comptype === Archive::COMPRESS_BZIP) {
894
+ $this->fh = @bzopen($this->file, 'a');
895
+ } else {
896
+ $this->fh = @fopen($this->file, 'ab');
897
+ }
898
+
899
+ if (!$this->fh) {
900
+ throw new ArchiveIOException('Could not open file for writing: '.$this->file);
901
+ }
902
+ }
903
+ $this->writeaccess = true;
904
+ $this->closed = false;
905
+ }
906
+
907
+ /**
908
+ * Read the contents of a TAR archive
909
+ *
910
+ * This function lists the files stored in the archive
911
+ *
912
+ * The archive is closed afer reading the contents, because rewinding is not possible in bzip2 streams.
913
+ * Reopen the file with open() again if you want to do additional operations
914
+ *
915
+ * @throws ArchiveIOException
916
+ * @returns FileInfo[]
917
+ */
918
+ public function contents($files_limit = 0)
919
+ {
920
+ if ($this->closed || !$this->file) {
921
+ throw new ArchiveIOException('Can not read from a closed archive');
922
+ }
923
+
924
+ $files_counter = 0;
925
+ $result = array();
926
+
927
+ while ($read = $this->readbytes(512)) {
928
+ $header = $this->parseHeader($read);
929
+ if (!is_array($header)) {
930
+ continue;
931
+ }
932
+
933
+ if($files_limit)
934
+ {
935
+ if(++$files_counter > $files_limit)
936
+ {
937
+ $return['extracted_files'] = $result;
938
+ $return['start'] = ftell($this->fh)-512;
939
+ return $return;
940
+ }
941
+ }
942
+
943
+ if($header['typeflag'] == 5)
944
+ $header['size'] = 0;
945
+
946
+ $this->skipbytes(ceil($header['size'] / 512) * 512);
947
+ $result[] = $this->header2fileinfo($header);
948
+ }
949
+
950
+ $return['extracted_files'] = $result;
951
+
952
+ $this->close();
953
+ return $return;
954
+ }
955
+
956
+
957
+
958
+ /**
959
+ * Adds a file to a TAR archive by appending it's data
960
+ *
961
+ * @param string $archive name of the archive file
962
+ * @param string $file name of the file to read data from
963
+ * @param string $start start position from where to start reading data
964
+ * @throws ArchiveIOException
965
+ */
966
+ /*public function addFileToArchive($archive, $file, $start = 0)
967
  {
968
  $this->openForAppend($archive);
969
  return $start = $this->appendFileData($file, $start, $this->file_size_per_request_limit);
970
  }
971
  */
 
972
  }
includes/class-xcloner-database.php CHANGED
@@ -1,4 +1,6 @@
1
  <?php
 
 
2
  /*
3
  * class-xcloner-database.php
4
  *
@@ -20,628 +22,653 @@
20
  * MA 02110-1301, USA.
21
  */
22
 
23
-
24
- class Xcloner_Database extends wpdb {
25
-
26
-
27
- public $debug = 0;
28
- public $recordsPerSession = 10000;
29
- public $dbCompatibility = "";
30
- public $dbDropSyntax = 1;
31
- public $countRecords = 0;
32
-
33
- private $link;
34
- private $db_selected;
35
- private $logger;
36
- private $xcloner_settings;
37
- private $fs;
38
-
39
- private $TEMP_DBPROCESS_FILE = ".database";
40
- private $TEMP_DUMP_FILE = "database-backup.sql";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
- public function __construct(Xcloner $xcloner_container, $wp_user = "", $wp_pass = "", $wp_db = "", $wp_host = "")
43
- {
44
- $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_database");
45
- $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
46
- $this->fs = $xcloner_container->get_xcloner_filesystem();
47
-
48
- if ($this->xcloner_settings->get_xcloner_option('xcloner_database_records_per_request'))
49
- $this->recordsPerSession = $this->xcloner_settings->get_xcloner_option('xcloner_database_records_per_request');
50
-
51
- if (!$this->recordsPerSession)
52
- $this->recordsPerSession = 100;
53
-
54
- if (!$wp_user && !$wp_pass && !$wp_host && !$wp_db)
55
- {
56
- $wp_host = $this->xcloner_settings->get_db_hostname();
57
- $wp_user = $this->xcloner_settings->get_db_username();
58
- $wp_pass = $this->xcloner_settings->get_db_password();
59
- $wp_db = $this->xcloner_settings->get_db_database();
60
- }
61
-
62
- parent::__construct($wp_user, $wp_pass, $wp_db, $wp_host);
63
-
64
- //$this->use_mysqli = true;
65
  }
66
- /*
67
- * Initialize the database connection
68
- *
69
- * name: init
70
- * @param array $data {'dbHostname', 'dbUsername', 'dbPassword', 'dbDatabase'}
71
- * @return
72
- */
73
- public function init($data, $start = 0)
74
- {
75
- if ($start and $this->fs->get_tmp_filesystem()->has($this->TEMP_DBPROCESS_FILE)) {
76
- $this->fs->get_tmp_filesystem()->delete($this->TEMP_DBPROCESS_FILE);
77
- }
78
-
79
- $this->headers();
80
-
81
- $this->suppress_errors = true;
82
- }
83
-
84
- public function start_database_recursion($params, $extra_params, $init = 0)
85
- {
86
- $tables = array();
87
- $return['finished'] = 0;
88
- $return['stats'] = array(
89
- "total_records"=>0,
90
- "tables_count"=>0,
91
- "database_count"=>0,
92
- );
93
-
94
- if (!$this->xcloner_settings->get_enable_mysql_backup())
95
- {
96
- $return['finished'] = 1;
97
- return $return;
98
- }
99
-
100
- $this->logger->debug(__("Starting database backup process"));
101
-
102
- $this->init($params, $init);
103
-
104
- if ($init)
105
- {
106
- $db_count = 0;
107
-
108
- if (isset($params['#']))
109
- {
110
- foreach ($params['#'] as $database)
111
- {
112
- if (!isset($params[$database]) or !is_array($params[$database]))
113
- $params[$database] = array();
114
- }
115
- $db_count = -1;
116
- }
117
-
118
- if (isset($params) and is_array($params))
119
- foreach ($params as $database=>$tables)
120
- {
121
- if ($database != "#")
122
- {
123
- $stats = $this->write_backup_process_list($database, $tables);
124
- $return['stats']['tables_count'] += $stats['tables_count'];
125
- $return['stats']['total_records'] += $stats['total_records'];
126
- }
127
- }
128
-
129
- if (sizeof($params))
130
- $return['stats']['database_count'] = sizeof($params) + $db_count;
131
- else
132
- $return['stats']['database_count'] = 0;
133
-
134
- return $return;
135
- }
136
-
137
- if (!isset($extra_params['startAtLine']))
138
- $extra_params['startAtLine'] = 0;
139
- if (!isset($extra_params['startAtRecord']))
140
- $extra_params['startAtRecord'] = 0;
141
- if (!isset($extra_params['dumpfile']))
142
- $extra_params['dumpfile'] = "";
143
-
144
- $return = $this->process_incremental($extra_params['startAtLine'], $extra_params['startAtRecord'], $extra_params['dumpfile']);
145
-
146
- return $return;
147
- }
148
-
149
- public function log($message = "")
150
- {
151
-
152
- if ($message) {
153
- $this->logger->info($message, array(""));
154
- } else {
155
- if ($this->last_query)
156
- $this->logger->debug($this->last_query, array(""));
157
- if ($this->last_error)
158
- $this->logger->error($this->last_error, array(""));
159
- }
160
-
161
- return;
162
- }
163
-
164
- /*
165
- *Return any error
166
- *
167
- * name: error
168
- * @param string $message
169
- * @return
170
- */
171
- public function error($message)
172
- {
173
- $this->logger->error($message, array(""));
174
-
175
- return;
176
- }
177
-
178
- /*
179
- * Send some special headers after the connection is initialized
180
- *
181
- * name: headers
182
- * @param
183
- */
184
- private function headers()
185
- {
186
- $this->logger->debug(__("Setting mysql headers"));
187
-
188
- $this->query("SET SQL_QUOTE_SHOW_CREATE=1;");
189
- //$this->log();
190
- $this->query("SET sql_mode = 0;");
191
- //$this->log();
192
-
193
- $this->set_charset($this->dbh, 'utf8');
194
- //$this->log();
195
-
196
 
197
- }
198
-
199
- public function get_database_num_tables($database)
200
- {
201
- $this->logger->debug(sprintf(__("Getting number of tables in %s"), $database));
202
-
203
- $query = "show tables in `".$database."`";
204
-
205
- $res = $this->get_results($query);
206
- $this->log();
207
-
208
- return count($res);
209
- }
210
-
211
- public function get_all_databases()
212
- {
213
- $this->logger->debug(("Getting all databases"));
214
-
215
- $query = "SHOW DATABASES;";
216
-
217
- $databases = $this->get_results($query);
218
- $this->log();
219
-
220
- $databases_list = array();
221
-
222
- $i = 0;
223
-
224
- $databases_list[$i]['name'] = $this->dbname;
225
- $databases_list[$i]['num_tables'] = $this->get_database_num_tables($this->dbname);
226
- $i++;
227
-
228
- if (is_array($databases))
229
- foreach ($databases as $db) {
230
- if ($db->Database != $this->dbname)
231
- {
232
- $databases_list[$i]['name'] = $db->Database;
233
- $databases_list[$i]['num_tables'] = $this->get_database_num_tables($db->Database);
234
- $i++;
235
- }
236
- }
237
-
238
- return $databases_list;
239
- }
240
-
241
- /*
242
- * Returns an array of tables from a database and mark $excluded ones
243
- *
244
- * name: list_tables
245
- * @param string $database
246
- * @param array $included
247
- * @param int $get_num_records
248
- * @return array $tablesList
249
- */
250
- public function list_tables($database = "", $included = array(), $get_num_records = 0)
251
- {
252
- $tablesList[0] = array( );
253
- $inc = 0;
254
-
255
- if (!$database)
256
- $database = $this->dbname;
257
-
258
- $this->logger->debug(sprintf(("Listing tables in %s database"), $database));
259
-
260
- $tables = $this->get_results("SHOW TABLES in `".$database."`");
261
- $this->log();
262
-
263
- foreach ($tables as $table) {
264
-
265
- $table = array_values((array)$table)[0];
266
-
267
- $tablesList[$inc]['name'] = $table;
268
- $tablesList[$inc]['database'] = $database;
269
-
270
- if ($get_num_records)
271
- {
272
- $records_num_result = $this->get_var("SELECT count(*) FROM `".$database."`.`".$table."`");
273
- $this->log();
274
-
275
- $tablesList[$inc]['records'] = $records_num_result;
276
- }
277
-
278
- $tablesList[$inc]['excluded'] = 0;
279
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  if (sizeof($included) and is_array($included)) {
281
  $dbTable = $database.".".$table;
282
  if (!in_array($table, $included) and !in_array($dbTable, $included)) {
283
  $tablesList[$inc]['excluded'] = 1;
284
  $this->log(sprintf(__("Excluding table %s.%s from backup"), $table, $database));
285
  }
286
- }
287
-
288
- $inc++;
289
- }
290
-
291
- return $tablesList;
292
-
293
- }
294
-
295
- public function write_backup_process_list($dbname, $incl_tables)
296
- {
297
- $return['total_records'] = 0;
298
- $return['tables_count'] = 0;
299
-
300
- $this->log(__("Preparing the database recursion file"));
301
-
302
- $tables = $this->list_tables($dbname, $incl_tables, 1);
303
-
304
- if ($this->dbname != $dbname)
305
- $dumpfile = $dbname."-backup.sql";
306
- else
307
- $dumpfile = $this->TEMP_DUMP_FILE;
308
-
309
- $line = sprintf("###newdump###\t%s\t%s\n", $dbname, $dumpfile);
310
- $this->fs->get_tmp_filesystem_append()->write($this->TEMP_DBPROCESS_FILE, $line);
311
-
312
- // write this to the class and write to $TEMP_DBPROCESS_FILE file as database.table records
313
- foreach ($tables as $key=>$table)
314
- if ($table != "" and !$tables[$key]['excluded']) {
315
-
316
- $line = sprintf("`%s`.`%s`\t%s\t%s\n", $dbname, $tables[$key]['name'], $tables[$key]['records'], $tables[$key]['excluded']);
317
- $this->fs->get_tmp_filesystem_append()->write($this->TEMP_DBPROCESS_FILE, $line);
318
- $return['tables_count']++;
319
- $return['total_records'] += $tables[$key]['records'];
320
- }
321
-
322
- $line = sprintf("###enddump###\t%s\t%s\n", $dbname, $dumpfile);
323
- $this->fs->get_tmp_filesystem_append()->write($this->TEMP_DBPROCESS_FILE, $line);
324
-
325
- return $return;
326
- }
327
-
328
- /*
329
- * Returns the number of records from a table
330
- *
331
- * name: countRecords
332
- * @param string $table - the source table
333
- * @return int $count
334
- */
335
- public function countRecords($table)
336
- {
337
-
338
- $table = "`".$this->dbname."`.`$table`";
339
-
340
- $result = $this->get_var("SELECT count(*) FROM $table;");
341
-
342
- return intval($result) ;// not max limit on 32 bit systems 2147483647; on 64 bit 9223372036854775807
343
-
344
- }
345
-
346
- /*
347
- * Processing the mysql backup incrementally
348
- *
349
- * name: processIncremental
350
- * @param
351
- * int $startAtLine - at which line from the perm.txt file to start reading
352
- * int startAtRecord - at which record to start from the table found at $startAtLine
353
- * string $dumpfie - where to save the data
354
- * string $dbCompatibility - MYSQL40, MYSQ32, none=default
355
- * int $dbDropSyntax - check if the DROP TABLE syntax should be added
356
- * @return array $return
357
- */
358
- public function process_incremental($startAtLine = 0, $startAtRecord = 0, $dumpfile = "", $dbCompatibility = "") {
359
-
360
- $count = 0;
361
- $return['finished'] = 0;
362
- $lines = array();
363
-
364
- if ($this->fs->get_tmp_filesystem()->has($this->TEMP_DBPROCESS_FILE))
365
- $lines = array_filter(explode("\n", $this->fs->get_tmp_filesystem()->read($this->TEMP_DBPROCESS_FILE)));
366
-
367
- foreach ($lines as $buffer) {
368
-
369
- if ($count == $startAtLine)
370
- {
371
-
372
- $tableInfo = explode("\t", $buffer);
373
-
374
- if ($tableInfo[0] == "###newdump###") {
375
- // we create a new mysql dump file
376
- if ($dumpfile != "") {
377
- // we finished a previous one and write the footers
378
- $return['dumpsize'] = $this->data_footers($dumpfile);
379
- }
380
-
381
- $dumpfile = $tableInfo[2];
382
-
383
- $this->log(sprintf(__("Starting new backup dump to file %s"), $dumpfile));
384
-
385
- $this->data_headers($dumpfile, $tableInfo[1]);
386
- $dumpfile = $tableInfo[2];
387
- $startAtLine++;
388
- $return['new_dump'] = 1;
389
- //break;
390
- } else {
391
- //we export the table
392
- if ($tableInfo[0] == "###enddump###")
393
- $return['endDump'] = 1;
394
-
395
- //table is excluded
396
- if ($tableInfo[2])
397
- continue;
398
-
399
- $next = $startAtRecord + $this->recordsPerSession;
400
-
401
- // $tableInfo[1] number of records in the table
402
- $table = explode("`.`", $tableInfo[0]);
403
- $tableName = str_replace("`", "", $table[1]);
404
- $databaseName = str_replace("`", "", $table[0]);
405
-
406
- //return something to the browser
407
- $return['databaseName'] = $databaseName;
408
- $return['tableName'] = $tableName;
409
- $return['totalRecords'] = $tableInfo[1];
410
-
411
- $processed_records = 0;
412
-
413
- if (trim($tableName) != "" and !$tableInfo[2])
414
- $processed_records = $this->export_table($databaseName, $tableName, $startAtRecord, $this->recordsPerSession, $dumpfile);
415
-
416
- $return['processedRecords'] = $startAtRecord + $processed_records;
417
-
418
- if ($next >= $tableInfo[1]) //we finished loading the records for next sessions, will go to the new record
419
- {
420
- $startAtLine++;
421
- $startAtRecord = 0;
422
- } else {
423
- $startAtRecord = $startAtRecord + $this->recordsPerSession;
424
- }
425
-
426
- //$return['dbCompatibility'] = self::$dbCompatibility;
427
-
428
- $return['startAtLine'] = $startAtLine;
429
- $return['startAtRecord'] = $startAtRecord;
430
- $return['dumpfile'] = $dumpfile;
431
- $return['dumpsize'] = $this->fs->get_tmp_filesystem_append()->getSize($dumpfile);
432
-
433
- return $return;
434
- break;
435
-
436
-
437
- }
438
-
439
- }
440
-
441
- $count++;
442
-
443
-
444
- }
445
-
446
- //while is finished, lets go home...
447
- if ($dumpfile != "") {
448
- // we finished a previous one and write the footers
449
- $return['dumpsize'] = $this->data_footers($dumpfile);
450
- $return['dumpfile'] = ($dumpfile);
451
- }
452
- $return['finished'] = 1;
453
- $return['startAtLine'] = $startAtLine;
454
-
455
- if ($this->fs->get_tmp_filesystem()->has($this->TEMP_DBPROCESS_FILE))
456
- $this->fs->get_tmp_filesystem()->delete($this->TEMP_DBPROCESS_FILE);
457
-
458
- $this->logger->debug(sprintf(("Database backup finished!")));
459
-
460
- return $return;
461
-
462
-
463
- }
464
-
465
-
466
- /*
467
- * Exporting the table records
468
- *
469
- * name: exportTable
470
- * @param
471
- * string $databaseName - database name of the table
472
- * string tableName - table name
473
- * int $start - where to start from
474
- * int $limit - how many records
475
- * handler $fd - file handler where to write the records
476
- * @return
477
- */
478
-
479
- /**
480
- * @param integer $start
481
- * @param integer $limit
482
- */
483
- public function export_table($databaseName, $tableName, $start, $limit, $dumpfile)
484
- {
485
- $this->logger->debug(sprintf(("Exporting table %s.%s data"), $databaseName, $tableName));
486
-
487
- $records = 0;
488
-
489
- if ($start == 0)
490
- $this->dump_structure($databaseName, $tableName, $dumpfile);
491
-
492
- $start = intval($start);
493
- $limit = intval($limit);
494
- //exporting the table content now
495
-
496
- $query = "SELECT * from `$databaseName`.`$tableName` Limit $start, $limit ;";
497
- if ($this->use_mysqli)
498
- {
499
- $result = mysqli_query($this->dbh, $query);
500
- $mysql_fetch_function = "mysqli_fetch_array";
501
-
502
- } else {
503
- $result = mysql_query($query, $this->dbh);
504
- $mysql_fetch_function = "mysqli_fetch_array";
505
- }
506
- //$result = $this->get_results($query, ARRAY_N);
507
- //print_r($result); exit;
508
-
509
- if ($result) {
510
- while ($row = $mysql_fetch_function($result, MYSQLI_ASSOC)) {
511
-
512
- $this->fs->get_tmp_filesystem_append()->write($dumpfile, "INSERT INTO `$tableName` VALUES (");
513
- $arr = $row;
514
- $buffer = "";
515
- $this->countRecords++;
516
-
517
- foreach ($arr as $key => $value) {
518
- if (!is_null($value)) {
519
- $value = $this->_real_escape($value);
520
-
521
- if (method_exists($this, 'remove_placeholder_escape')) {
522
- $value = $this->remove_placeholder_escape($value);
523
- }
524
- $buffer .= "'" . $value . "', ";
525
- } else {
526
- $buffer .= "null, ";
527
- }
528
- }
529
- $buffer = rtrim($buffer, ', ').");\n";
530
- $this->fs->get_tmp_filesystem_append()->write($dumpfile, $buffer);
531
- unset($buffer);
532
-
533
- $records++;
534
-
535
- }
536
- }
537
-
538
- $this->log(sprintf(__("Dumping %s records starting position %s from %s.%s table"), $records, $start, $databaseName, $tableName));
539
-
540
- return $records;
541
-
542
- }
543
-
544
- public function dump_structure($databaseName, $tableName, $dumpfile)
545
- {
546
- $this->log(sprintf(__("Dumping the structure for %s.%s table"), $databaseName, $tableName));
547
-
548
- $line = ("\n#\n# Table structure for table `$tableName`\n#\n\n");
549
- $this->fs->get_tmp_filesystem_append()->write($dumpfile, $line);
550
-
551
- if ($this->dbDropSyntax)
552
- {
553
- $line = ("\nDROP table IF EXISTS `$tableName`;\n");
554
- $this->fs->get_tmp_filesystem_append()->write($dumpfile, $line);
555
- }
556
-
557
- //$result = mysqli_query($this->dbh,"SHOW CREATE table `$databaseName`.`$tableName`;");
558
- $result = $this->get_row("SHOW CREATE table `$databaseName`.`$tableName`;", ARRAY_N);
559
- if ($result) {
560
- //$row = mysqli_fetch_row( $result);
561
- $line = ($result[1].";\n");
562
- $this->fs->get_tmp_filesystem_append()->write($dumpfile, $line);
563
- }
564
-
565
- $line = ("\n#\n# End Structure for table `$tableName`\n#\n\n");
566
- $line .= ("#\n# Dumping data for table `$tableName`\n#\n\n");
567
- $this->fs->get_tmp_filesystem_append()->write($dumpfile, $line);
568
-
569
- return;
570
-
571
- }
572
-
573
- public function data_footers($dumpfile)
574
- {
575
- $this->logger->debug(sprintf(("Writing dump footers in file"), $dumpfile));
576
- // we finished the dump file, not return the size of it
577
- $this->fs->get_tmp_filesystem_append()->write($dumpfile, "\n#\n# Finished at: ".date("M j, Y \a\\t H:i")."\n#");
578
- $size = $this->fs->get_tmp_filesystem_append()->getSize($dumpfile);
579
-
580
- $metadata_dumpfile = $this->fs->get_tmp_filesystem()->getMetadata($dumpfile);
581
-
582
- //adding dump file to the included files list
583
- $this->fs->store_file($metadata_dumpfile, 'tmp_filesystem');
584
-
585
- return $size;
586
-
587
- }
588
-
589
- public function resetcountRecords() {
590
-
591
- $this->countRecords = 0;
592
-
593
- return $this->countRecords;
594
-
595
- }
596
-
597
- public function getcountRecords() {
598
-
599
- return $this->countRecords;
600
-
601
- }
602
-
603
-
604
- public function data_headers($file, $database)
605
- {
606
- $this->logger->debug(sprintf(("Writing dump header for %s database in file"), $database, $file));
607
-
608
- $return = "";
609
-
610
- $return .= "#\n";
611
- $return .= "# Powered by XCloner Site Backup\n";
612
- $return .= "# http://www.xcloner.com\n";
613
- $return .= "#\n";
614
- $return .= "# Host: ".get_site_url()."\n";
615
- $return .= "# Generation Time: ".date("M j, Y \a\\t H:i")."\n";
616
- $return .= "# PHP Version: ".phpversion()."\n";
617
- $return .= "# Database Charset: ".$this->charset."\n";
618
-
619
- $results = $this->get_results("SHOW VARIABLES LIKE \"%version%\";", ARRAY_N);
620
- if (isset($results)) {
621
- foreach ($results as $result) {
622
-
623
- $return .= "# MYSQL ".$result[0].": ".$result[1]."\n";
624
-
625
- }
626
- }
627
-
628
- $results = $this->get_results("SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
629
  FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '".$database."';");
630
-
631
- if (isset($results[0])) {
632
-
633
- $return .= "# MYSQL DEFAULT_CHARACTER_SET_NAME: ".$results[0]->DEFAULT_CHARACTER_SET_NAME."\n";
634
- $return .= "# MYSQL SCHEMA_NAME: ".$results[0]->DEFAULT_COLLATION_NAME."\n";
635
- }
636
-
637
- $return .= "#\n# Database : `".$database."`\n# --------------------------------------------------------\n\n";
638
-
639
- $this->log(sprintf(__("Writing %s database dump headers"), $database));
640
-
641
- $return = $this->fs->get_tmp_filesystem()->write($file, $return);
642
- return $return['size'];
643
-
644
- }
645
-
646
-
647
  }
1
  <?php
2
+
3
+
4
  /*
5
  * class-xcloner-database.php
6
  *
22
  * MA 02110-1301, USA.
23
  */
24
 
25
+ if (!class_exists('wpdb')) {
26
+ require_once __DIR__ . "/../lib/wp-db.php";
27
+ }
28
+
29
+ class Xcloner_Database extends wpdb
30
+ {
31
+ public $debug = 0;
32
+ public $recordsPerSession = 10000;
33
+ public $dbCompatibility = "";
34
+ public $dbDropSyntax = 1;
35
+ public $countRecords = 0;
36
+
37
+ private $link;
38
+ private $db_selected;
39
+ private $logger;
40
+ private $xcloner_settings;
41
+ private $fs;
42
+
43
+ private $xcloner_dbhost;
44
+ private $xcloner_dbuser;
45
+ private $xcloner_dbpassword;
46
+ private $xcloner_dbname;
47
+ private $xcloner_prefix;
48
+
49
+ private $TEMP_DBPROCESS_FILE = ".database";
50
+ private $TEMP_DUMP_FILE = "database-backup.sql";
51
+
52
+ public function __construct(Xcloner $xcloner_container, $wp_user = "", $wp_pass = "", $wp_db = "", $wp_host = "")
53
+ {
54
+ $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_database");
55
+ $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
56
+ $this->fs = $xcloner_container->get_xcloner_filesystem();
57
+
58
+ if ($this->xcloner_settings->get_xcloner_option('xcloner_database_records_per_request')) {
59
+ $this->recordsPerSession = $this->xcloner_settings->get_xcloner_option('xcloner_database_records_per_request');
60
+ }
61
+
62
+ if (!$this->recordsPerSession) {
63
+ $this->recordsPerSession = 100;
64
+ }
65
+
66
+ $this->xcloner_dbhost = $this->xcloner_settings->get_db_hostname();
67
+ $this->xcloner_dbuser = $this->xcloner_settings->get_db_username();
68
+ $this->xcloner_dbpassword = $this->xcloner_settings->get_db_password();
69
+ $this->xcloner_dbname = $this->xcloner_settings->get_db_database();
70
+ $this->xcloner_prefix= "";
71
+
72
+ //fetch the default wordpress mysql credentials
73
+ if ( !$this->xcloner_dbuser || !$this->xcloner_dbhost || !$this->xcloner_dbname ) {
74
+ global $wpdb;
75
+
76
+ $this->xcloner_dbhost = $wpdb->dbhost;
77
+ update_option('xcloner_mysql_hostname', $this->xcloner_dbhost);
78
+ $this->xcloner_dbuser = $wpdb->dbuser;
79
+ update_option('xcloner_mysql_username', $this->xcloner_dbuser);
80
+ $this->xcloner_dbpassword = $wpdb->dbpassword;
81
+ update_option('xcloner_mysql_password', $this->xcloner_dbpassword);
82
+ $this->xcloner_dbname = $wpdb->dbname;
83
+ update_option('xcloner_mysql_database', $this->xcloner_dbname);
84
+ $this->xcloner_prefix = $wpdb->prefix;
85
+ update_option('xcloner_mysql_prefix', $this->xcloner_prefix);
86
+
87
+ }
88
+
89
+ parent::__construct($this->xcloner_dbuser, $this->xcloner_dbpassword, $this->xcloner_dbname, $this->xcloner_dbhost);
90
+
91
+ //$this->use_mysqli = true;
92
+ }
93
 
94
+ public function getPrefix() {
95
+ return $this->xcloner_prefix;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
+ public function getDbHost()
99
+ {
100
+ return $this->xcloner_dbhost;
101
+ }
102
+
103
+ public function getDbUser()
104
+ {
105
+ return $this->xcloner_dbuser;
106
+ }
107
+
108
+ public function getDbPassword()
109
+ {
110
+ return $this->xcloner_dbpassword;
111
+ }
112
+
113
+ public function getDbName()
114
+ {
115
+ return $this->xcloner_dbname;
116
+ }
117
+
118
+ /*
119
+ * Initialize the database connection
120
+ *
121
+ * name: init
122
+ * @param array $data {'dbHostname', 'dbUsername', 'dbPassword', 'dbDatabase'}
123
+ * @return
124
+ */
125
+ public function init($data, $start = 0)
126
+ {
127
+ if ($start and $this->fs->get_tmp_filesystem()->has($this->TEMP_DBPROCESS_FILE)) {
128
+ $this->fs->get_tmp_filesystem()->delete($this->TEMP_DBPROCESS_FILE);
129
+ }
130
+
131
+ $this->headers();
132
+
133
+ $this->suppress_errors = true;
134
+ }
135
+
136
+ public function start_database_recursion($params, $extra_params, $init = 0)
137
+ {
138
+ $tables = array();
139
+ $return['finished'] = 0;
140
+ $return['stats'] = array(
141
+ "total_records"=>0,
142
+ "tables_count"=>0,
143
+ "database_count"=>0,
144
+ );
145
+
146
+ if (!$this->xcloner_settings->get_enable_mysql_backup()) {
147
+ $return['finished'] = 1;
148
+ return $return;
149
+ }
150
+
151
+ $this->logger->debug(__("Starting database backup process"));
152
+
153
+ $this->init($params, $init);
154
+
155
+ if ($init) {
156
+ $db_count = 0;
157
+
158
+ if (isset($params['#'])) {
159
+ foreach ($params['#'] as $database) {
160
+ if (!isset($params[$database]) or !is_array($params[$database])) {
161
+ $params[$database] = array();
162
+ }
163
+ }
164
+ $db_count = -1;
165
+ }
166
+
167
+ if (isset($params) and is_array($params)) {
168
+ foreach ($params as $database=>$tables) {
169
+ if ($database != "#") {
170
+ $stats = $this->write_backup_process_list($database, $tables);
171
+ $return['stats']['tables_count'] += $stats['tables_count'];
172
+ $return['stats']['total_records'] += $stats['total_records'];
173
+ }
174
+ }
175
+ }
176
+
177
+ if (sizeof($params)) {
178
+ $return['stats']['database_count'] = sizeof($params) + $db_count;
179
+ } else {
180
+ $return['stats']['database_count'] = 0;
181
+ }
182
+
183
+ return $return;
184
+ }
185
+
186
+ if (!isset($extra_params['startAtLine'])) {
187
+ $extra_params['startAtLine'] = 0;
188
+ }
189
+ if (!isset($extra_params['startAtRecord'])) {
190
+ $extra_params['startAtRecord'] = 0;
191
+ }
192
+ if (!isset($extra_params['dumpfile'])) {
193
+ $extra_params['dumpfile'] = "";
194
+ }
195
+
196
+ $return = $this->process_incremental($extra_params['startAtLine'], $extra_params['startAtRecord'], $extra_params['dumpfile']);
197
+
198
+ return $return;
199
+ }
200
+
201
+ public function log($message = "")
202
+ {
203
+ if ($message) {
204
+ $this->logger->info($message, array(""));
205
+ } else {
206
+ if ($this->last_query) {
207
+ $this->logger->debug($this->last_query, array(""));
208
+ }
209
+ if ($this->last_error) {
210
+ $this->logger->error($this->last_error, array(""));
211
+ }
212
+ }
213
+
214
+ return;
215
+ }
216
+
217
+ /*
218
+ *Return any error
219
+ *
220
+ * name: error
221
+ * @param string $message
222
+ * @return
223
+ */
224
+ public function error($message)
225
+ {
226
+ $this->logger->error($message, array(""));
227
+
228
+ return;
229
+ }
230
+
231
+ /*
232
+ * Send some special headers after the connection is initialized
233
+ *
234
+ * name: headers
235
+ * @param
236
+ */
237
+ private function headers()
238
+ {
239
+ $this->logger->debug(__("Setting mysql headers"));
240
+
241
+ $this->query("SET SQL_QUOTE_SHOW_CREATE=1;");
242
+ //$this->log();
243
+ $this->query("SET sql_mode = 0;");
244
+ //$this->log();
245
+
246
+ $this->set_charset($this->dbh, 'utf8');
247
+ //$this->log();
248
+ }
249
+
250
+ public function get_database_num_tables($database)
251
+ {
252
+ $this->logger->debug(sprintf(__("Getting number of tables in %s"), $database));
253
+
254
+ $query = "show tables in `".$database."`";
255
+
256
+ $res = $this->get_results($query);
257
+ $this->log();
258
+
259
+ return count($res);
260
+ }
261
+
262
+ public function get_all_databases()
263
+ {
264
+ $this->logger->debug(("Getting all databases"));
265
+
266
+ $query = "SHOW DATABASES;";
267
+
268
+ $databases = $this->get_results($query);
269
+ $this->log();
270
+
271
+ $databases_list = array();
272
+
273
+ $i = 0;
274
+
275
+ $databases_list[$i]['name'] = $this->dbname;
276
+ $databases_list[$i]['num_tables'] = $this->get_database_num_tables($this->dbname);
277
+ $i++;
278
+
279
+ if (is_array($databases)) {
280
+ foreach ($databases as $db) {
281
+ if ($db->Database != $this->dbname) {
282
+ $databases_list[$i]['name'] = $db->Database;
283
+ $databases_list[$i]['num_tables'] = $this->get_database_num_tables($db->Database);
284
+ $i++;
285
+ }
286
+ }
287
+ }
288
+
289
+ return $databases_list;
290
+ }
291
+
292
+ /*
293
+ * Returns an array of tables from a database and mark $excluded ones
294
+ *
295
+ * name: list_tables
296
+ * @param string $database
297
+ * @param array $included
298
+ * @param int $get_num_records
299
+ * @return array $tablesList
300
+ */
301
+ public function list_tables($database = "", $included = array(), $get_num_records = 0)
302
+ {
303
+ $tablesList[0] = array( );
304
+ $inc = 0;
305
+
306
+ if (!$database) {
307
+ $database = $this->dbname;
308
+ }
309
+
310
+ $this->logger->debug(sprintf(("Listing tables in %s database"), $database));
311
+
312
+ $tables = $this->get_results("SHOW TABLES in `".$database."`");
313
+ $this->log();
314
+
315
+ foreach ($tables as $table) {
316
+ $table = array_values((array)$table)[0];
317
+
318
+ $tablesList[$inc]['name'] = $table;
319
+ $tablesList[$inc]['database'] = $database;
320
+
321
+ if ($get_num_records) {
322
+ $records_num_result = $this->get_var("SELECT count(*) FROM `".$database."`.`".$table."`");
323
+ $this->log();
324
+
325
+ $tablesList[$inc]['records'] = $records_num_result;
326
+ }
327
+
328
+ $tablesList[$inc]['excluded'] = 0;
329
+
330
  if (sizeof($included) and is_array($included)) {
331
  $dbTable = $database.".".$table;
332
  if (!in_array($table, $included) and !in_array($dbTable, $included)) {
333
  $tablesList[$inc]['excluded'] = 1;
334
  $this->log(sprintf(__("Excluding table %s.%s from backup"), $table, $database));
335
  }
336
+ }
337
+
338
+ $inc++;
339
+ }
340
+
341
+ return $tablesList;
342
+ }
343
+
344
+ public function write_backup_process_list($dbname, $incl_tables)
345
+ {
346
+ $return['total_records'] = 0;
347
+ $return['tables_count'] = 0;
348
+
349
+ $this->log(__("Preparing the database recursion file"));
350
+
351
+ $tables = $this->list_tables($dbname, $incl_tables, 1);
352
+
353
+ if ($this->dbname != $dbname) {
354
+ $dumpfile = $dbname."-backup.sql";
355
+ } else {
356
+ $dumpfile = $this->TEMP_DUMP_FILE;
357
+ }
358
+
359
+ $line = sprintf("###newdump###\t%s\t%s\n", $dbname, $dumpfile);
360
+ $this->fs->get_tmp_filesystem_append()->write($this->TEMP_DBPROCESS_FILE, $line);
361
+
362
+ // write this to the class and write to $TEMP_DBPROCESS_FILE file as database.table records
363
+ foreach ($tables as $key=>$table) {
364
+ if ($table != "" and !$tables[$key]['excluded']) {
365
+ $line = sprintf("`%s`.`%s`\t%s\t%s\n", $dbname, $tables[$key]['name'], $tables[$key]['records'], $tables[$key]['excluded']);
366
+ $this->fs->get_tmp_filesystem_append()->write($this->TEMP_DBPROCESS_FILE, $line);
367
+ $return['tables_count']++;
368
+ $return['total_records'] += $tables[$key]['records'];
369
+ }
370
+ }
371
+
372
+ $line = sprintf("###enddump###\t%s\t%s\n", $dbname, $dumpfile);
373
+ $this->fs->get_tmp_filesystem_append()->write($this->TEMP_DBPROCESS_FILE, $line);
374
+
375
+ return $return;
376
+ }
377
+
378
+ /*
379
+ * Returns the number of records from a table
380
+ *
381
+ * name: countRecords
382
+ * @param string $table - the source table
383
+ * @return int $count
384
+ */
385
+ public function countRecords($table)
386
+ {
387
+ $table = "`".$this->dbname."`.`$table`";
388
+
389
+ $result = $this->get_var("SELECT count(*) FROM $table;");
390
+
391
+ return intval($result) ;// not max limit on 32 bit systems 2147483647; on 64 bit 9223372036854775807
392
+ }
393
+
394
+ /*
395
+ * Processing the mysql backup incrementally
396
+ *
397
+ * name: processIncremental
398
+ * @param
399
+ * int $startAtLine - at which line from the perm.txt file to start reading
400
+ * int startAtRecord - at which record to start from the table found at $startAtLine
401
+ * string $dumpfie - where to save the data
402
+ * string $dbCompatibility - MYSQL40, MYSQ32, none=default
403
+ * int $dbDropSyntax - check if the DROP TABLE syntax should be added
404
+ * @return array $return
405
+ */
406
+ public function process_incremental($startAtLine = 0, $startAtRecord = 0, $dumpfile = "", $dbCompatibility = "")
407
+ {
408
+ $count = 0;
409
+ $return['finished'] = 0;
410
+ $lines = array();
411
+
412
+ if ($this->fs->get_tmp_filesystem()->has($this->TEMP_DBPROCESS_FILE)) {
413
+ $lines = array_filter(explode("\n", $this->fs->get_tmp_filesystem()->read($this->TEMP_DBPROCESS_FILE)));
414
+ }
415
+
416
+ foreach ($lines as $buffer) {
417
+ if ($count == $startAtLine) {
418
+ $tableInfo = explode("\t", $buffer);
419
+
420
+ if ($tableInfo[0] == "###newdump###") {
421
+ // we create a new mysql dump file
422
+ if ($dumpfile != "") {
423
+ // we finished a previous one and write the footers
424
+ $return['dumpsize'] = $this->data_footers($dumpfile);
425
+ }
426
+
427
+ $dumpfile = $tableInfo[2];
428
+
429
+ $this->log(sprintf(__("Starting new backup dump to file %s"), $dumpfile));
430
+
431
+ $this->data_headers($dumpfile, $tableInfo[1]);
432
+ $dumpfile = $tableInfo[2];
433
+ $startAtLine++;
434
+ $return['new_dump'] = 1;
435
+ //break;
436
+ } else {
437
+ //we export the table
438
+ if ($tableInfo[0] == "###enddump###") {
439
+ $return['endDump'] = 1;
440
+ }
441
+
442
+ //table is excluded
443
+ if ($tableInfo[2]) {
444
+ continue;
445
+ }
446
+
447
+ $next = $startAtRecord + $this->recordsPerSession;
448
+
449
+ // $tableInfo[1] number of records in the table
450
+ $table = explode("`.`", $tableInfo[0]);
451
+ $tableName = str_replace("`", "", $table[1]);
452
+ $databaseName = str_replace("`", "", $table[0]);
453
+
454
+ //return something to the browser
455
+ $return['databaseName'] = $databaseName;
456
+ $return['tableName'] = $tableName;
457
+ $return['totalRecords'] = $tableInfo[1];
458
+
459
+ $processed_records = 0;
460
+
461
+ if (trim($tableName) != "" and !$tableInfo[2]) {
462
+ $processed_records = $this->export_table($databaseName, $tableName, $startAtRecord, $this->recordsPerSession, $dumpfile);
463
+ }
464
+
465
+ $return['processedRecords'] = $startAtRecord + $processed_records;
466
+
467
+ if ($next >= $tableInfo[1]) { //we finished loading the records for next sessions, will go to the new record
468
+ $startAtLine++;
469
+ $startAtRecord = 0;
470
+ } else {
471
+ $startAtRecord = $startAtRecord + $this->recordsPerSession;
472
+ }
473
+
474
+ //$return['dbCompatibility'] = self::$dbCompatibility;
475
+
476
+ $return['startAtLine'] = $startAtLine;
477
+ $return['startAtRecord'] = $startAtRecord;
478
+ $return['dumpfile'] = $dumpfile;
479
+ $return['dumpsize'] = $this->fs->get_tmp_filesystem_append()->getSize($dumpfile);
480
+
481
+ return $return;
482
+ break;
483
+ }
484
+ }
485
+
486
+ $count++;
487
+ }
488
+
489
+ //while is finished, lets go home...
490
+ if ($dumpfile != "") {
491
+ // we finished a previous one and write the footers
492
+ $return['dumpsize'] = $this->data_footers($dumpfile);
493
+ $return['dumpfile'] = ($dumpfile);
494
+ }
495
+ $return['finished'] = 1;
496
+ $return['startAtLine'] = $startAtLine;
497
+
498
+ if ($this->fs->get_tmp_filesystem()->has($this->TEMP_DBPROCESS_FILE)) {
499
+ $this->fs->get_tmp_filesystem()->delete($this->TEMP_DBPROCESS_FILE);
500
+ }
501
+
502
+ $this->logger->debug(sprintf(("Database backup finished!")));
503
+
504
+ return $return;
505
+ }
506
+
507
+
508
+ /*
509
+ * Exporting the table records
510
+ *
511
+ * name: exportTable
512
+ * @param
513
+ * string $databaseName - database name of the table
514
+ * string tableName - table name
515
+ * int $start - where to start from
516
+ * int $limit - how many records
517
+ * handler $fd - file handler where to write the records
518
+ * @return
519
+ */
520
+
521
+ /**
522
+ * @param integer $start
523
+ * @param integer $limit
524
+ */
525
+ public function export_table($databaseName, $tableName, $start, $limit, $dumpfile)
526
+ {
527
+ $this->logger->debug(sprintf(("Exporting table %s.%s data"), $databaseName, $tableName));
528
+
529
+ $records = 0;
530
+
531
+ if ($start == 0) {
532
+ $this->dump_structure($databaseName, $tableName, $dumpfile);
533
+ }
534
+
535
+ $start = intval($start);
536
+ $limit = intval($limit);
537
+ //exporting the table content now
538
+
539
+ $query = "SELECT * from `$databaseName`.`$tableName` Limit $start, $limit ;";
540
+ if ($this->use_mysqli) {
541
+ $result = mysqli_query($this->dbh, $query);
542
+ $mysql_fetch_function = "mysqli_fetch_array";
543
+ } else {
544
+ $result = mysql_query($query, $this->dbh);
545
+ $mysql_fetch_function = "mysqli_fetch_array";
546
+ }
547
+ //$result = $this->get_results($query, ARRAY_N);
548
+ //print_r($result); exit;
549
+
550
+ if ($result) {
551
+ while ($row = $mysql_fetch_function($result, MYSQLI_ASSOC)) {
552
+ $this->fs->get_tmp_filesystem_append()->write($dumpfile, "INSERT INTO `$tableName` VALUES (");
553
+ $arr = $row;
554
+ $buffer = "";
555
+ $this->countRecords++;
556
+
557
+ foreach ($arr as $key => $value) {
558
+ if (!is_null($value)) {
559
+ $value = $this->_real_escape($value);
560
+
561
+ if (method_exists($this, 'remove_placeholder_escape')) {
562
+ $value = $this->remove_placeholder_escape($value);
563
+ }
564
+ $buffer .= "'" . $value . "', ";
565
+ } else {
566
+ $buffer .= "null, ";
567
+ }
568
+ }
569
+ $buffer = rtrim($buffer, ', ').");\n";
570
+ $this->fs->get_tmp_filesystem_append()->write($dumpfile, $buffer);
571
+ unset($buffer);
572
+
573
+ $records++;
574
+ }
575
+ }
576
+
577
+ $this->log(sprintf(__("Dumping %s records starting position %s from %s.%s table"), $records, $start, $databaseName, $tableName));
578
+
579
+ return $records;
580
+ }
581
+
582
+ public function dump_structure($databaseName, $tableName, $dumpfile)
583
+ {
584
+ $this->log(sprintf(__("Dumping the structure for %s.%s table"), $databaseName, $tableName));
585
+
586
+ $line = ("\n#\n# Table structure for table `$tableName`\n#\n\n");
587
+ $this->fs->get_tmp_filesystem_append()->write($dumpfile, $line);
588
+
589
+ if ($this->dbDropSyntax) {
590
+ $line = ("\nDROP table IF EXISTS `$tableName`;\n");
591
+ $this->fs->get_tmp_filesystem_append()->write($dumpfile, $line);
592
+ }
593
+
594
+ //$result = mysqli_query($this->dbh,"SHOW CREATE table `$databaseName`.`$tableName`;");
595
+ $result = $this->get_row("SHOW CREATE table `$databaseName`.`$tableName`;", ARRAY_N);
596
+ if ($result) {
597
+ //$row = mysqli_fetch_row( $result);
598
+ $line = ($result[1].";\n");
599
+ $this->fs->get_tmp_filesystem_append()->write($dumpfile, $line);
600
+ }
601
+
602
+ $line = ("\n#\n# End Structure for table `$tableName`\n#\n\n");
603
+ $line .= ("#\n# Dumping data for table `$tableName`\n#\n\n");
604
+ $this->fs->get_tmp_filesystem_append()->write($dumpfile, $line);
605
+
606
+ return;
607
+ }
608
+
609
+ public function data_footers($dumpfile)
610
+ {
611
+ $this->logger->debug(sprintf(("Writing dump footers in file"), $dumpfile));
612
+ // we finished the dump file, not return the size of it
613
+ $this->fs->get_tmp_filesystem_append()->write($dumpfile, "\n#\n# Finished at: ".date("M j, Y \a\\t H:i")."\n#");
614
+ $size = $this->fs->get_tmp_filesystem_append()->getSize($dumpfile);
615
+
616
+ $metadata_dumpfile = $this->fs->get_tmp_filesystem()->getMetadata($dumpfile);
617
+
618
+ //adding dump file to the included files list
619
+ $this->fs->store_file($metadata_dumpfile, 'tmp_filesystem');
620
+
621
+ return $size;
622
+ }
623
+
624
+ public function resetcountRecords()
625
+ {
626
+ $this->countRecords = 0;
627
+
628
+ return $this->countRecords;
629
+ }
630
+
631
+ public function getcountRecords()
632
+ {
633
+ return $this->countRecords;
634
+ }
635
+
636
+
637
+ public function data_headers($file, $database)
638
+ {
639
+ $this->logger->debug(sprintf(("Writing dump header for %s database in file"), $database, $file));
640
+
641
+ $return = "";
642
+
643
+ $return .= "#\n";
644
+ $return .= "# Powered by XCloner Site Backup\n";
645
+ $return .= "# http://www.xcloner.com\n";
646
+ $return .= "#\n";
647
+ $return .= "# Host: ".get_site_url()."\n";
648
+ $return .= "# Generation Time: ".date("M j, Y \a\\t H:i")."\n";
649
+ $return .= "# PHP Version: ".phpversion()."\n";
650
+ $return .= "# Database Charset: ".$this->charset."\n";
651
+
652
+ $results = $this->get_results("SHOW VARIABLES LIKE \"%version%\";", ARRAY_N);
653
+ if (isset($results)) {
654
+ foreach ($results as $result) {
655
+ $return .= "# MYSQL ".$result[0].": ".$result[1]."\n";
656
+ }
657
+ }
658
+
659
+ $results = $this->get_results("SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
660
  FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '".$database."';");
661
+
662
+ if (isset($results[0])) {
663
+ $return .= "# MYSQL DEFAULT_CHARACTER_SET_NAME: ".$results[0]->DEFAULT_CHARACTER_SET_NAME."\n";
664
+ $return .= "# MYSQL SCHEMA_NAME: ".$results[0]->DEFAULT_COLLATION_NAME."\n";
665
+ }
666
+
667
+ $return .= "#\n# Database : `".$database."`\n# --------------------------------------------------------\n\n";
668
+
669
+ $this->log(sprintf(__("Writing %s database dump headers"), $database));
670
+
671
+ $return = $this->fs->get_tmp_filesystem()->write($file, $return);
672
+ return $return['size'];
673
+ }
 
 
 
 
674
  }
includes/class-xcloner-deactivator.php CHANGED
@@ -35,7 +35,7 @@
35
  * @package Xcloner
36
  * @subpackage Xcloner/includes
37
  * @author Liuta Ovidiu <info@thinkovi.com>
38
- * @link http://www.thinkovi.com
39
  */
40
 
41
  class Xcloner_Deactivator {
35
  * @package Xcloner
36
  * @subpackage Xcloner/includes
37
  * @author Liuta Ovidiu <info@thinkovi.com>
38
+ * @link https://watchful.net
39
  */
40
 
41
  class Xcloner_Deactivator {
includes/class-xcloner-encryption.php CHANGED
@@ -8,390 +8,388 @@
8
 
9
  //namespace XCloner;
10
 
 
 
 
11
  class Xcloner_Encryption
12
  {
13
- const FILE_ENCRYPTION_BLOCKS = 1024 * 1024;
14
- const FILE_ENCRYPTION_SUFFIX = ".encrypted";
15
- const FILE_DECRYPTION_SUFFIX = ".decrypted";
16
-
17
- private $xcloner_settings;
18
- private $logger;
19
- private $xcloner_container;
20
- private $verification = false;
21
-
22
- /**
23
- * Xcloner_Encryption constructor.
24
- * @param Xcloner $xcloner_container
25
- */
26
- public function __construct(Xcloner $xcloner_container)
27
- {
28
- $this->xcloner_container = $xcloner_container;
29
- if (property_exists($xcloner_container, 'xcloner_settings')) {
30
- $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
31
- } else {
32
- $this->xcloner_settings = "";
33
- }
34
-
35
- if (property_exists($xcloner_container, 'xcloner_logger')) {
36
- $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_encryption");
37
- } else {
38
- $this->logger = "";
39
- }
40
- }
41
-
42
- /**
43
- * Returns the backup encryption key
44
- *
45
- * @return string|null
46
- */
47
- public function get_backup_encryption_key()
48
- {
49
- if (is_object($this->xcloner_settings)) {
50
- return $this->xcloner_settings->get_xcloner_encryption_key();
51
- }
52
-
53
- return null;
54
-
55
- }
56
-
57
- /**
58
- * Check if provided filename has encrypted suffix
59
- *
60
- * @param $filename
61
- * @return bool
62
- */
63
- public function is_encrypted_file($filename) {
64
- $fp = fopen($this->get_xcloner_path().$filename, 'r');
65
- if (is_resource($fp)) {
66
- $encryption_length = fread($fp, 16);
67
- fclose($fp);
68
- if (is_numeric($encryption_length)) {
69
- return true;
70
- }
71
- }
72
-
73
- return false;
74
-
75
- }
76
-
77
- /**
78
- * Returns the filename with encrypted suffix
79
- *
80
- * @param string $filename
81
- * @return string
82
- */
83
- public function get_encrypted_target_backup_file_name($filename) {
84
-
85
- return str_replace(self::FILE_DECRYPTION_SUFFIX, "", $filename).self::FILE_ENCRYPTION_SUFFIX;
86
- }
87
-
88
- /**
89
- * Returns the filename without encrypted suffix
90
- *
91
- * @param string $filename
92
- * @return string
93
- */
94
- public function get_decrypted_target_backup_file_name($filename) {
95
-
96
- return str_replace(self::FILE_ENCRYPTION_SUFFIX, "", $filename).self::FILE_DECRYPTION_SUFFIX;
97
- }
98
-
99
- /**
100
- * Encrypt the passed file and saves the result in a new file with ".enc" as suffix.
101
- *
102
- * @param string $source Path to file that should be encrypted
103
- * @param string $dest File name where the encryped file should be written to.
104
- * @param string $key The key used for the encryption
105
- * @param int $start Start position for reading when doing incremental mode.
106
- * @param string $iv The IV key to use.
107
- * @param bool $verification Weather we should we try to verify the decryption.
108
- * @return array|false Returns array or FALSE if an error occured
109
  * @throws Exception
110
- */
111
- public function encrypt_file($source, $dest = "", $key = "", $start = 0, $iv = 0, $verification = true, $recursive = false)
112
- {
113
- if (is_object($this->logger)) {
114
- $this->logger->info(sprintf('Encrypting file %s at position %d IV %s', $source, $start, base64_encode($iv)));
115
- }
116
-
117
- //$key = substr(sha1($key, true), 0, 16);
118
- if (!$key) {
119
- $key = $this->get_backup_encryption_key();
120
- }
121
- $key_digest = openssl_digest($key, "md5", true);
122
 
123
- $keep_local = 1;
124
- if (!$dest) {
125
- $dest = $this->get_encrypted_target_backup_file_name($source);
126
- $keep_local = 0;
127
- }
128
 
129
- if (!$iv || !$start) {
130
- //$iv = openssl_random_pseudo_bytes(16);
131
- $iv = str_pad(self::FILE_ENCRYPTION_BLOCKS, 16, "0000000000000000", STR_PAD_LEFT);
132
- }
 
133
 
134
- if (!$start) {
135
- $fpOut = fopen($this->get_xcloner_path().$dest, 'w');
136
- } else {
137
- $fpOut = fopen($this->get_xcloner_path().$dest, 'a');
138
- }
139
 
140
- if (is_resource($fpOut)) {
 
 
 
 
141
 
142
- // Put the initialization vector to the beginning of the file
143
- if (!$start) {
144
- fwrite($fpOut, $iv);
145
- }
146
 
147
- if (file_exists($this->get_xcloner_path().$source) &&
148
- $fpIn = fopen($this->get_xcloner_path().$source, 'rb')) {
 
 
149
 
150
- fseek($fpIn, (int)$start);
 
 
151
 
152
- if (!feof($fpIn)) {
 
 
153
 
154
- $plaintext = fread($fpIn, 16 * self::FILE_ENCRYPTION_BLOCKS);
155
- $ciphertext = openssl_encrypt($plaintext, 'AES-128-CBC', $key_digest, OPENSSL_RAW_DATA, $iv);
 
156
 
157
- // Use the first 16 bytes of the ciphertext as the next initialization vector
158
- $iv = substr($ciphertext, 0, 16);
159
- //$iv = openssl_random_pseudo_bytes(16);
160
 
161
- fwrite($fpOut, $ciphertext);
162
 
163
- $start = ftell($fpIn);
164
-
165
- fclose($fpOut);
166
 
167
  unset($ciphertext);
168
  unset($plaintext);
169
 
170
- if (!feof($fpIn)) {
171
- fclose($fpIn);
172
- //echo "\n NEW:".$key.md5($iv);
173
- //self::encryptFile($source, $dest, $key, $start, $iv);
174
- if ($recursive) {
175
- $this->encrypt_file($source, $dest, $key, $start, ($iv), $verification, $recursive);
176
- } else {
177
-
178
- if (($iv) != base64_decode(base64_encode($iv)))
179
- {
180
- throw new \Exception('Could not encode IV for transport');
181
- }
182
-
183
- return array(
184
- "start" => $start,
185
- "iv" => base64_encode($iv),
186
- "target_file" => $dest,
187
- "finished" => 0
188
- );
189
- }
190
- }
191
-
192
- }
193
- } else {
194
- if (is_object($this->logger)) {
195
- $this->logger->error('Unable to read source file for encryption.');
196
- }
197
- throw new \Exception("Unable to read source file for encryption.");
198
- }
199
- } else {
200
- if (is_object($this->logger)) {
201
- $this->logger->error('Unable to write destination file for encryption.');
202
- }
203
- throw new \Exception("Unable to write destination file for encryption.");
204
- }
205
-
206
- if ($verification) {
207
- $this->verify_encrypted_file($dest);
208
- }
209
-
210
- //we replace the original backup with the encrypted one
211
- if (!$keep_local && copy($this->get_xcloner_path().$dest,
212
- $this->get_xcloner_path().$source)) {
213
- unlink($this->get_xcloner_path().$dest);
214
- }
215
-
216
-
217
- return array("target_file" => $dest, "finished" => 1);
218
- }
219
-
220
- /**
221
- * @param string $file
222
- */
223
- public function verify_encrypted_file($file) {
224
- if (is_object($this->logger)) {
225
- $this->logger->info(sprintf('Verifying encrypted file %s', $file));
226
- }
227
-
228
- $this->verification = true;
229
- $this->decrypt_file($file);
230
- $this->verification = false;
231
- }
232
-
233
- /**
234
- * Dencrypt the passed file and saves the result in a new file, removing the
235
- * last 4 characters from file name.
236
- *
237
- * @param string $source Path to file that should be decrypted
238
- * @param string $dest File name where the decryped file should be written to.
239
- * @param string $key The key used for the decryption (must be the same as for encryption)
240
- * @param int $start Start position for reading when doing incremental mode.
241
- * @param string $iv The IV key to use.
242
- * @return array|false Returns array or FALSE if an error occured
243
  * @throws Exception
244
- */
245
- public function decrypt_file($source, $dest = "", $key = "", $start = 0, $iv = 0, $recursive = false)
246
- {
247
- if (is_object($this->logger)) {
248
- $this->logger->info(sprintf('Decrypting file %s at position %d with IV %s', $source, $start, base64_encode($iv)));
249
- }
250
-
251
- //$key = substr(sha1($key, true), 0, 16);
252
- if (!$key) {
253
- $key = $this->get_backup_encryption_key();
254
- }
255
-
256
- $key_digest = openssl_digest($key, "md5", true);
257
-
258
- $keep_local = 1;
259
- if (!$dest) {
260
- $dest = $this->get_decrypted_target_backup_file_name($source);
261
- $keep_local = 0;
262
- }
263
-
264
- if (!$start) {
265
- if ($this->verification) {
266
- $fpOut = fopen("php://stdout", 'w');
267
- } else {
268
- $fpOut = fopen($this->get_xcloner_path().$dest, 'w');
269
- }
270
- } else {
271
- if ($this->verification) {
272
- $fpOut = fopen("php://stdout", 'a');
273
- } else {
274
- $fpOut = fopen($this->get_xcloner_path().$dest, 'a');
275
- }
276
- }
277
-
278
- if (is_resource($fpOut)) {
279
- if (file_exists($this->get_xcloner_path().$source) &&
280
- $fpIn = fopen($this->get_xcloner_path().$source, 'rb')) {
281
-
282
- $encryption_length = (int)fread($fpIn, 16);
283
- if (!$encryption_length) {
284
- $encryption_length = self::FILE_ENCRYPTION_BLOCKS;
285
- }
286
-
287
- fseek($fpIn, (int)$start);
288
-
289
- // Get the initialzation vector from the beginning of the file
290
- if (!$iv) {
291
- $iv = fread($fpIn, 16);
292
- }
293
-
294
- if (!feof($fpIn)) {
295
-
296
- // we have to read one block more for decrypting than for encrypting
297
- $ciphertext = fread($fpIn, 16 * ($encryption_length + 1));
298
- $plaintext = openssl_decrypt($ciphertext, 'AES-128-CBC', $key_digest, OPENSSL_RAW_DATA, $iv);
299
-
300
- if (!$plaintext) {
301
- unlink($this->get_xcloner_path().$dest);
302
- if (is_object($this->logger)) {
303
- $this->logger->error('Backup decryption failed, please check your provided Encryption Key.');
304
- }
305
- throw new \Exception("Backup decryption failed, please check your provided Encryption Key.");
306
- }
307
-
308
- // Use the first 16 bytes of the ciphertext as the next initialization vector
309
- $iv = substr($ciphertext, 0, 16);
310
-
311
- if (!$this->verification) {
312
- fwrite($fpOut, $plaintext);
313
- }
314
-
315
- $start = ftell($fpIn);
316
-
317
- fclose($fpOut);
318
-
319
- if (!feof($fpIn)) {
320
- fclose($fpIn);
321
- if ($this->verification || $recursive) {
322
- unset($ciphertext);
323
- unset($plaintext);
324
- $this->decrypt_file($source, $dest, $key, $start, $iv, $recursive);
325
- } else {
326
- if (($iv) != base64_decode(base64_encode($iv)))
327
- {
328
- throw new \Exception('Could not encode IV for transport');
329
- }
330
-
331
- return array(
332
- "start" => $start,
333
- "encryption_length" => $encryption_length,
334
- "iv" => base64_encode($iv),
335
- "target_file" => $dest,
336
- "finished" => 0
337
- );
338
- }
339
- }
340
-
341
- }
342
- } else {
343
- if (is_object($this->logger)) {
344
- $this->logger->error('Unable to read source file for decryption');
345
- }
346
- throw new \Exception("Unable to read source file for decryption");
347
- }
348
- } else {
349
- if (is_object($this->logger)) {
350
- $this->logger->error('Unable to write destination file for decryption');
351
- }
352
- throw new \Exception("Unable to write destination file for decryption");
353
- }
354
-
355
- //we replace the original backup with the encrypted one
356
- if (!$keep_local && !$this->verification && copy($this->get_xcloner_path().$dest,
357
- $this->get_xcloner_path().$source)) {
358
- unlink($this->get_xcloner_path().$dest);
359
- }
360
-
361
- return array("target_file" => $dest, "finished" => 1);
362
  }
363
 
364
- public function get_xcloner_path() {
365
- if (is_object($this->xcloner_settings)) {
366
- return $this->xcloner_settings->get_xcloner_store_path().DS;
367
- }
368
-
369
- return null;
370
- }
371
 
 
 
372
  }
373
 
374
 
375
  try {
376
-
377
- if (isset($argv[1])) {
378
-
379
- class Xcloner {
380
- /**
381
- * Xcloner constructor.
382
- */
383
- public function __construct()
384
- {
385
- }
386
- }
387
- $xcloner_encryption = new Xcloner_Encryption(new Xcloner());
388
-
389
- if ($argv[1] == "-e") {
390
- $xcloner_encryption->encrypt_file($argv[2], $argv[2].".enc", $argv[4], 0, 0, false, true);
391
- } elseif ($argv[1] == "-d") {
392
- $xcloner_encryption->decrypt_file($argv[2], $argv[2].".dec", $argv[4], 0, 0, true);
393
- }
394
- }
395
- }catch (\Exception $e) {
396
- echo "CAUGHT: ".$e->getMessage();
397
  }
8
 
9
  //namespace XCloner;
10
 
11
+ /**
12
+ * Xcloner_Encryption class
13
+ */
14
  class Xcloner_Encryption
15
  {
16
+ const FILE_ENCRYPTION_BLOCKS = 1024 * 1024;
17
+ const FILE_ENCRYPTION_SUFFIX = ".encrypted";
18
+ const FILE_DECRYPTION_SUFFIX = ".decrypted";
19
+
20
+ private $xcloner_settings;
21
+ private $logger;
22
+ private $xcloner_container;
23
+ private $verification = false;
24
+
25
+ /**
26
+ * Xcloner_Encryption constructor.
27
+ * @param Xcloner $xcloner_container
28
+ */
29
+ public function __construct(Xcloner $xcloner_container)
30
+ {
31
+ $this->xcloner_container = $xcloner_container;
32
+ if (property_exists($xcloner_container, 'xcloner_settings')) {
33
+ $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
34
+ } else {
35
+ $this->xcloner_settings = "";
36
+ }
37
+
38
+ if (property_exists($xcloner_container, 'xcloner_logger')) {
39
+ $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_encryption");
40
+ } else {
41
+ $this->logger = "";
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Returns the backup encryption key
47
+ *
48
+ * @return string|null
49
+ */
50
+ public function get_backup_encryption_key()
51
+ {
52
+ if (is_object($this->xcloner_settings)) {
53
+ return $this->xcloner_settings->get_xcloner_encryption_key();
54
+ }
55
+
56
+ return null;
57
+ }
58
+
59
+ /**
60
+ * Check if provided filename has encrypted suffix
61
+ *
62
+ * @param $filename
63
+ * @return bool
64
+ */
65
+ public function is_encrypted_file($filename)
66
+ {
67
+ $fp = fopen($this->get_xcloner_path().$filename, 'r');
68
+ if (is_resource($fp)) {
69
+ $encryption_length = fread($fp, 16);
70
+ fclose($fp);
71
+ if (is_numeric($encryption_length)) {
72
+ return true;
73
+ }
74
+ }
75
+
76
+ return false;
77
+ }
78
+
79
+ /**
80
+ * Returns the filename with encrypted suffix
81
+ *
82
+ * @param string $filename
83
+ * @return string
84
+ */
85
+ public function get_encrypted_target_backup_file_name($filename)
86
+ {
87
+ return str_replace(self::FILE_DECRYPTION_SUFFIX, "", $filename).self::FILE_ENCRYPTION_SUFFIX;
88
+ }
89
+
90
+ /**
91
+ * Returns the filename without encrypted suffix
92
+ *
93
+ * @param string $filename
94
+ * @return string
95
+ */
96
+ public function get_decrypted_target_backup_file_name($filename)
97
+ {
98
+ return str_replace(self::FILE_ENCRYPTION_SUFFIX, "", $filename).self::FILE_DECRYPTION_SUFFIX;
99
+ }
100
+
101
+ /**
102
+ * Encrypt the passed file and saves the result in a new file with ".enc" as suffix.
103
+ *
104
+ * @param string $source Path to file that should be encrypted
105
+ * @param string $dest File name where the encryped file should be written to.
106
+ * @param string $key The key used for the encryption
107
+ * @param int $start Start position for reading when doing incremental mode.
108
+ * @param string $iv The IV key to use.
109
+ * @param bool $verification Weather we should we try to verify the decryption.
110
+ * @return array|false Returns array or FALSE if an error occured
 
111
  * @throws Exception
112
+ */
113
+ public function encrypt_file($source, $dest = "", $key = "", $start = 0, $iv = 0, $verification = true, $recursive = false)
114
+ {
115
+ if (is_object($this->logger)) {
116
+ $this->logger->info(sprintf('Encrypting file %s at position %d IV %s', $source, $start, base64_encode($iv)));
117
+ }
 
 
 
 
 
 
118
 
119
+ //$key = substr(sha1($key, true), 0, 16);
120
+ if (!$key) {
121
+ $key = $this->get_backup_encryption_key();
122
+ }
123
+ $key_digest = openssl_digest($key, "md5", true);
124
 
125
+ $keep_local = 1;
126
+ if (!$dest) {
127
+ $dest = $this->get_encrypted_target_backup_file_name($source);
128
+ $keep_local = 0;
129
+ }
130
 
131
+ if (!$iv || !$start) {
132
+ //$iv = openssl_random_pseudo_bytes(16);
133
+ $iv = str_pad(self::FILE_ENCRYPTION_BLOCKS, 16, "0000000000000000", STR_PAD_LEFT);
134
+ }
 
135
 
136
+ if (!$start) {
137
+ $fpOut = fopen($this->get_xcloner_path().$dest, 'w');
138
+ } else {
139
+ $fpOut = fopen($this->get_xcloner_path().$dest, 'a');
140
+ }
141
 
142
+ if (is_resource($fpOut)) {
 
 
 
143
 
144
+ // Put the initialization vector to the beginning of the file
145
+ if (!$start) {
146
+ fwrite($fpOut, $iv);
147
+ }
148
 
149
+ if (file_exists($this->get_xcloner_path().$source) &&
150
+ $fpIn = fopen($this->get_xcloner_path().$source, 'rb')) {
151
+ fseek($fpIn, (int)$start);
152
 
153
+ if (!feof($fpIn)) {
154
+ $plaintext = fread($fpIn, 16 * self::FILE_ENCRYPTION_BLOCKS);
155
+ $ciphertext = openssl_encrypt($plaintext, 'AES-128-CBC', $key_digest, OPENSSL_RAW_DATA, $iv);
156
 
157
+ // Use the first 16 bytes of the ciphertext as the next initialization vector
158
+ $iv = substr($ciphertext, 0, 16);
159
+ //$iv = openssl_random_pseudo_bytes(16);
160
 
161
+ fwrite($fpOut, $ciphertext);
 
 
162
 
163
+ $start = ftell($fpIn);
164
 
165
+ fclose($fpOut);
 
 
166
 
167
  unset($ciphertext);
168
  unset($plaintext);
169
 
170
+ if (!feof($fpIn)) {
171
+ fclose($fpIn);
172
+ //echo "\n NEW:".$key.md5($iv);
173
+ //self::encryptFile($source, $dest, $key, $start, $iv);
174
+ if ($recursive) {
175
+ $this->encrypt_file($source, $dest, $key, $start, ($iv), $verification, $recursive);
176
+ } else {
177
+ if (($iv) != base64_decode(base64_encode($iv))) {
178
+ throw new \Exception('Could not encode IV for transport');
179
+ }
180
+
181
+ return array(
182
+ "start" => $start,
183
+ "iv" => base64_encode($iv),
184
+ "target_file" => $dest,
185
+ "finished" => 0
186
+ );
187
+ }
188
+ }
189
+ }
190
+ } else {
191
+ if (is_object($this->logger)) {
192
+ $this->logger->error('Unable to read source file for encryption.');
193
+ }
194
+ throw new \Exception("Unable to read source file for encryption.");
195
+ }
196
+ } else {
197
+ if (is_object($this->logger)) {
198
+ $this->logger->error('Unable to write destination file for encryption.');
199
+ }
200
+ throw new \Exception("Unable to write destination file for encryption.");
201
+ }
202
+
203
+ if ($verification) {
204
+ $this->verify_encrypted_file($dest);
205
+ }
206
+
207
+ //we replace the original backup with the encrypted one
208
+ if (!$keep_local && copy(
209
+ $this->get_xcloner_path().$dest,
210
+ $this->get_xcloner_path().$source
211
+ )) {
212
+ unlink($this->get_xcloner_path().$dest);
213
+ }
214
+
215
+
216
+ return array("target_file" => $dest, "finished" => 1);
217
+ }
218
+
219
+ /**
220
+ * @param string $file
221
+ */
222
+ public function verify_encrypted_file($file)
223
+ {
224
+ if (is_object($this->logger)) {
225
+ $this->logger->info(sprintf('Verifying encrypted file %s', $file));
226
+ }
227
+
228
+ $this->verification = true;
229
+ $this->decrypt_file($file);
230
+ $this->verification = false;
231
+ }
232
+
233
+ /**
234
+ * Dencrypt the passed file and saves the result in a new file, removing the
235
+ * last 4 characters from file name.
236
+ *
237
+ * @param string $source Path to file that should be decrypted
238
+ * @param string $dest File name where the decryped file should be written to.
239
+ * @param string $key The key used for the decryption (must be the same as for encryption)
240
+ * @param int $start Start position for reading when doing incremental mode.
241
+ * @param string $iv The IV key to use.
242
+ * @return array|false Returns array or FALSE if an error occured
243
  * @throws Exception
244
+ */
245
+ public function decrypt_file($source, $dest = "", $key = "", $start = 0, $iv = 0, $recursive = false)
246
+ {
247
+ if (is_object($this->logger)) {
248
+ $this->logger->info(sprintf('Decrypting file %s at position %d with IV %s', $source, $start, base64_encode($iv)));
249
+ }
250
+
251
+ //$key = substr(sha1($key, true), 0, 16);
252
+ if (!$key) {
253
+ $key = $this->get_backup_encryption_key();
254
+ }
255
+
256
+ $key_digest = openssl_digest($key, "md5", true);
257
+
258
+ $keep_local = 1;
259
+ if (!$dest) {
260
+ $dest = $this->get_decrypted_target_backup_file_name($source);
261
+ $keep_local = 0;
262
+ }
263
+
264
+ if (!$start) {
265
+ if ($this->verification) {
266
+ $fpOut = fopen("php://stdout", 'w');
267
+ } else {
268
+ $fpOut = fopen($this->get_xcloner_path().$dest, 'w');
269
+ }
270
+ } else {
271
+ if ($this->verification) {
272
+ $fpOut = fopen("php://stdout", 'a');
273
+ } else {
274
+ $fpOut = fopen($this->get_xcloner_path().$dest, 'a');
275
+ }
276
+ }
277
+
278
+ if (is_resource($fpOut)) {
279
+ if (file_exists($this->get_xcloner_path().$source) &&
280
+ $fpIn = fopen($this->get_xcloner_path().$source, 'rb')) {
281
+ $encryption_length = (int)fread($fpIn, 16);
282
+ if (!$encryption_length) {
283
+ $encryption_length = self::FILE_ENCRYPTION_BLOCKS;
284
+ }
285
+
286
+ fseek($fpIn, (int)$start);
287
+
288
+ // Get the initialzation vector from the beginning of the file
289
+ if (!$iv) {
290
+ $iv = fread($fpIn, 16);
291
+ }
292
+
293
+ if (!feof($fpIn)) {
294
+
295
+ // we have to read one block more for decrypting than for encrypting
296
+ $ciphertext = fread($fpIn, 16 * ($encryption_length + 1));
297
+ $plaintext = openssl_decrypt($ciphertext, 'AES-128-CBC', $key_digest, OPENSSL_RAW_DATA, $iv);
298
+
299
+ if (!$plaintext) {
300
+ unlink($this->get_xcloner_path().$dest);
301
+ if (is_object($this->logger)) {
302
+ $this->logger->error('Backup decryption failed, please check your provided Encryption Key.');
303
+ }
304
+ throw new \Exception("Backup decryption failed, please check your provided Encryption Key.");
305
+ }
306
+
307
+ // Use the first 16 bytes of the ciphertext as the next initialization vector
308
+ $iv = substr($ciphertext, 0, 16);
309
+
310
+ if (!$this->verification) {
311
+ fwrite($fpOut, $plaintext);
312
+ }
313
+
314
+ $start = ftell($fpIn);
315
+
316
+ fclose($fpOut);
317
+
318
+ if (!feof($fpIn)) {
319
+ fclose($fpIn);
320
+ if ($this->verification || $recursive) {
321
+ unset($ciphertext);
322
+ unset($plaintext);
323
+ $this->decrypt_file($source, $dest, $key, $start, $iv, $recursive);
324
+ } else {
325
+ if (($iv) != base64_decode(base64_encode($iv))) {
326
+ throw new \Exception('Could not encode IV for transport');
327
+ }
328
+
329
+ return array(
330
+ "start" => $start,
331
+ "encryption_length" => $encryption_length,
332
+ "iv" => base64_encode($iv),
333
+ "target_file" => $dest,
334
+ "finished" => 0
335
+ );
336
+ }
337
+ }
338
+ }
339
+ } else {
340
+ if (is_object($this->logger)) {
341
+ $this->logger->error('Unable to read source file for decryption');
342
+ }
343
+ throw new \Exception("Unable to read source file for decryption");
344
+ }
345
+ } else {
346
+ if (is_object($this->logger)) {
347
+ $this->logger->error('Unable to write destination file for decryption');
348
+ }
349
+ throw new \Exception("Unable to write destination file for decryption");
350
+ }
351
+
352
+ //we replace the original backup with the encrypted one
353
+ if (!$keep_local && !$this->verification && copy(
354
+ $this->get_xcloner_path().$dest,
355
+ $this->get_xcloner_path().$source
356
+ )) {
357
+ unlink($this->get_xcloner_path().$dest);
358
+ }
359
+
360
+ return array("target_file" => $dest, "finished" => 1);
 
361
  }
362
 
363
+ public function get_xcloner_path()
364
+ {
365
+ if (is_object($this->xcloner_settings)) {
366
+ return $this->xcloner_settings->get_xcloner_store_path().DS;
367
+ }
 
 
368
 
369
+ return null;
370
+ }
371
  }
372
 
373
 
374
  try {
375
+ if (isset($argv[1])) {
376
+ class Xcloner
377
+ {
378
+ /**
379
+ * Xcloner constructor.
380
+ */
381
+ public function __construct()
382
+ {
383
+ }
384
+ }
385
+ $xcloner_encryption = new Xcloner_Encryption(new Xcloner());
386
+
387
+ if ($argv[1] == "-e") {
388
+ $xcloner_encryption->encrypt_file($argv[2], $argv[2].".enc", $argv[4], 0, 0, false, true);
389
+ } elseif ($argv[1] == "-d") {
390
+ $xcloner_encryption->decrypt_file($argv[2], $argv[2].".dec", $argv[4], 0, 0, true);
391
+ }
392
+ }
393
+ } catch (\Exception $e) {
394
+ echo "CAUGHT: ".$e->getMessage();
 
395
  }
includes/class-xcloner-file-system.php CHANGED
@@ -36,7 +36,6 @@ use League\Flysystem\Adapter\Local;
36
  */
37
  class Xcloner_File_System
38
  {
39
-
40
  private $excluded_files = "";
41
  private $additional_regex_patterns = array();
42
  private $excluded_files_by_default = array("administrator/backups", "wp-content/backups");
@@ -77,7 +76,6 @@ class Xcloner_File_System
77
  $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
78
 
79
  try {
80
-
81
  $this->start_adapter = new Local($this->xcloner_settings->get_xcloner_start_path(), LOCK_EX, '0001');
82
  $this->start_filesystem = new Filesystem($this->start_adapter, new Config([
83
  'disable_asserts' => true,
@@ -97,20 +95,22 @@ class Xcloner_File_System
97
  'disable_asserts' => true,
98
  ]));
99
 
100
- $this->storage_adapter = new Local($this->xcloner_settings->get_xcloner_store_path(), FILE_APPEND,
101
- '0001');
 
 
 
102
  $this->storage_filesystem_append = new Filesystem($this->storage_adapter, new Config([
103
  'disable_asserts' => true,
104
  ]));
105
- }catch (Exception $e) {
106
  $this->logger->error("Filesystem Initialization Error: ".$e->getMessage());
107
  }
108
 
109
 
110
- if ($value = get_option('xcloner_directories_to_scan_per_request')) {
111
  $this->folders_to_process_per_session = $value;
112
  }
113
-
114
  }
115
 
116
  /**
@@ -225,7 +225,6 @@ class Xcloner_File_System
225
  $spl_info = $this->getMetadataFull('tmp_adapter', $path);
226
 
227
  return $spl_info;
228
-
229
  }
230
 
231
  public function get_temp_dir_handler()
@@ -392,11 +391,12 @@ class Xcloner_File_System
392
  }
393
  }
394
  }
395
-
396
  }
397
 
398
- if ($file_info['type'] == 'file' and isset($file_info['extension']) and in_array($file_info['extension'],
399
- $this->backup_archive_extensions)) {
 
 
400
  $backup_files[$file_info['path']] = $file_info;
401
  }
402
  }
@@ -417,8 +417,10 @@ class Xcloner_File_System
417
  public function start_file_recursion($init = 0)
418
  {
419
  if ($init) {
420
- $this->logger->info(sprintf(__("Starting the filesystem scanner on root folder %s"),
421
- $this->xcloner_settings->get_xcloner_start_path()));
 
 
422
  $this->do_system_init();
423
  }
424
 
@@ -497,8 +499,10 @@ class Xcloner_File_System
497
  public function remove_tmp_filesystem()
498
  {
499
  //delete the temporary folder
500
- $this->logger->debug(sprintf("Deleting the temporary storage folder %s",
501
- $this->xcloner_settings->get_xcloner_tmp_path()));
 
 
502
 
503
  $contents = $this->get_tmp_filesystem()->listContents();
504
 
@@ -510,7 +514,7 @@ class Xcloner_File_System
510
 
511
  try {
512
  rmdir($this->xcloner_settings->get_xcloner_tmp_path());
513
- }catch (Exception $e) {
514
  //silent continue
515
  }
516
 
@@ -529,7 +533,6 @@ class Xcloner_File_System
529
  $contents = $tmp_filesystem->listContents();
530
 
531
  foreach ($contents as $file) {
532
-
533
  if (preg_match("/.xcloner-(.*)/", $file['path'])) {
534
  if ($file['timestamp'] < strtotime("-1days")) {
535
  $tmp_filesystem->deleteDir($file['path']);
@@ -589,7 +592,6 @@ class Xcloner_File_System
589
  }
590
 
591
  foreach ($excluded_files as $excl) {
592
-
593
  if ($this->is_regex($excl)) {
594
  $this->additional_regex_patterns[] = $excl;
595
  }
@@ -616,16 +618,16 @@ class Xcloner_File_System
616
 
617
  //if we start with the root folder(empty value), we initializa the file system
618
  if (!$folder) {
619
-
620
  }
621
 
622
  try {
623
-
624
  $files = $this->start_filesystem->listContents($folder);
625
  foreach ($files as $file) {
626
  if (!is_readable($this->xcloner_settings->get_xcloner_start_path().DS.$file['path'])) {
627
- $this->logger->info(sprintf(__("Excluding %s from the filesystem list, file not readable"),
628
- $file['path']), array(
 
 
629
  "FILESYSTEM SCAN",
630
  "NOT READABLE"
631
  ));
@@ -641,22 +643,20 @@ class Xcloner_File_System
641
  if (isset($file['size'])) {
642
  $this->files_size += $file['size'];
643
  }
644
-
645
  } else {
646
- $this->logger->info(sprintf(__("Excluding %s from the filesystem list, matching pattern %s"),
647
- $file['path'], $matching_pattern), array(
 
 
 
648
  "FILESYSTEM SCAN",
649
  "EXCLUDE"
650
  ));
651
  }
652
  }
653
-
654
- }catch (Exception $e) {
655
-
656
  $this->logger->error($e->getMessage());
657
-
658
  }
659
-
660
  }
661
 
662
  public function estimate_read_write_time()
@@ -677,11 +677,8 @@ class Xcloner_File_System
677
  $return['reading_time'] = $this->estimate_reading_time($tmp_file);
678
 
679
  $this->tmp_filesystem->delete($tmp_file);
680
-
681
- }catch (Exception $e) {
682
-
683
  $this->logger->error($e->getMessage());
684
-
685
  }
686
 
687
  return $return;
@@ -716,8 +713,7 @@ class Xcloner_File_System
716
  foreach ($_backup_files_list as $file) {
717
  //processing rule folder capacity
718
  if ($this->xcloner_settings->get_xcloner_option('xcloner_cleanup_capacity_limit') &&
719
- $_storage_size >= ($set_storage_limit = 1024 * 1024 * $this->xcloner_settings->get_xcloner_option('xcloner_cleanup_capacity_limit'))) //bytes
720
- {
721
  $this->storage_filesystem->delete($file['path']);
722
  $_storage_size -= $file['size'];
723
  $this->logger->info("Deleting backup ".$file['path']." matching rule", array(
@@ -744,10 +740,7 @@ class Xcloner_File_System
744
  $_backups_counter." >= ".$this->xcloner_settings->get_xcloner_option('xcloner_cleanup_retention_limit_archives')
745
  ));
746
  }
747
-
748
-
749
  }
750
-
751
  }
752
 
753
  /**
@@ -766,7 +759,6 @@ class Xcloner_File_System
766
  $end_time = microtime(true) - $start_time;
767
 
768
  return $end_time;
769
-
770
  }
771
 
772
  public function process_backup_name($name = "", $max_length = 100)
@@ -780,7 +772,7 @@ class Xcloner_File_System
780
  $name = str_replace($tag, date("Y-m-d_H-i"), $name);
781
  } elseif ($tag == '[hostname]') {
782
  $name = str_replace($tag, gethostname(), $name);
783
- }elseif ($tag == '[hash]') {
784
  $name = str_replace($tag, $this->xcloner_container->randomString(5), $name);
785
  } elseif ($tag == '[domain]') {
786
  $domain = parse_url(admin_url(), PHP_URL_HOST);
@@ -927,163 +919,165 @@ class Xcloner_File_System
927
  * exclude the backup folders
928
  * PATTERN: (^|^\/)(wp-content\/backups|administrator\/backups)(.*)$";
929
  */
930
- private function is_excluded_regex($file)
931
- {
932
- //$this->logger->debug(sprintf(("Checking if %s is excluded"), $file['path']));
933
-
934
- $regex_patterns = explode(PHP_EOL, $this->xcloner_settings->get_xcloner_option('xcloner_regex_exclude'));
935
-
936
- if (is_array($this->additional_regex_patterns)) {
937
- $regex_patterns = array_merge($regex_patterns, $this->additional_regex_patterns);
938
- }
939
-
940
- //print_r($regex_patterns);exit;
941
-
942
- if (is_array($regex_patterns)) {
943
- //$this->excluded_files = array();
944
- //$this->excluded_files[] ="(.*)\.(git)(.*)$";
945
- //$this->excluded_files[] ="wp-content\/backups(.*)$";
946
-
947
- foreach ($regex_patterns as $excluded_file_pattern) {
948
-
949
- if (substr($excluded_file_pattern, strlen($excluded_file_pattern) - 1,
950
- strlen($excluded_file_pattern)) == "\r") {
951
- $excluded_file_pattern = substr($excluded_file_pattern, 0, strlen($excluded_file_pattern) - 1);
952
- }
953
-
954
- if ($file['path'] == "/") {
955
- $needle = "/";
956
- } else {
957
- $needle = "/".$file['path'];
958
- }
959
- //echo $needle."---".$excluded_file_pattern."---\n";
960
-
961
- if (@preg_match("/(^|^\/)".$excluded_file_pattern."/i", $needle)) {
962
- return $excluded_file_pattern;
963
- }
964
- }
965
- }
966
-
967
- return false;
968
- }
969
-
970
- public function store_file($file, $storage = 'start_filesystem')
971
- {
972
- $this->logger->debug(sprintf("Storing %s in the backup list", $file['path']));
973
-
974
- if (!isset($file['size'])) {
975
- $file['size'] = 0;
976
- }
977
- if (!isset($file['visibility'])) {
978
- $file['visibility'] = "private";
979
- }
980
-
981
- $csv_filename = str_replace('"', '""', $file['path']);
982
-
983
- $line = '"'.($csv_filename).'","'.$file['timestamp'].'","'.$file['size'].'","'.$file['visibility'].'","'.$storage.'"'.PHP_EOL;
984
-
985
- $this->last_logged_file = $file['path'];
986
-
987
- if ($file['type'] == "dir") {
988
- try {
989
- $this->tmp_filesystem_append->write($this->get_temp_dir_handler(), $file['path']."\n");
990
- }catch (Exception $e) {
991
- $this->logger->error($e->getMessage());
992
- }
993
- }
994
-
995
- if ($this->get_diff_timestamp_start()) {
996
- if ($file['type'] != "file" && $response = $this->check_file_diff_time($file)) {
997
- $this->logger->info(sprintf("Directory %s archiving skipped on differential backup %s", $file['path'],
998
- $response), array(
999
- "FILESYSTEM SCAN",
1000
- "DIR DIFF"
1001
- ));
1002
-
1003
- return false;
1004
- }
1005
- }
1006
-
1007
- try {
1008
- if (!$this->tmp_filesystem_append->has($this->get_included_files_handler())) {
1009
- //adding fix for UTF-8 CSV preview
1010
- $start_line = "\xEF\xBB\xBF".'"Filename","Timestamp","Size","Visibility","Storage"'.PHP_EOL;
1011
- $this->tmp_filesystem_append->write($this->get_included_files_handler(), $start_line);
1012
- }
1013
-
1014
- $this->tmp_filesystem_append->write($this->get_included_files_handler(), $line);
1015
-
1016
- }catch (Exception $e) {
1017
-
1018
- $this->logger->error($e->getMessage());
1019
- }
1020
-
1021
- return true;
1022
- }
1023
-
1024
- public function get_fileystem_handler()
1025
- {
1026
- return $this;
1027
- }
1028
-
1029
- public function get_filesystem($system = "")
1030
- {
1031
- if ($system == "storage_filesystem_append") {
1032
- return $this->storage_filesystem_append;
1033
- } elseif ($system == "tmp_filesystem_append") {
1034
- return $this->tmp_filesystem_append;
1035
- } elseif ($system == "tmp_filesystem") {
1036
- return $this->tmp_filesystem;
1037
- } elseif ($system == "storage_filesystem") {
1038
- return $this->storage_filesystem;
1039
- } else {
1040
- return $this->start_filesystem;
1041
- }
1042
- }
1043
-
1044
- public function get_adapter($system)
1045
- {
1046
- if ($system == "tmp_filesystem") {
1047
- return $this->tmp_adapter;
1048
- } elseif ($system == "storage_filesystem") {
1049
- return $this->storage_adapter;
1050
- } else {
1051
- return $this->start_adapter;
1052
- }
1053
- }
1054
-
1055
- /**
1056
- * File scan finished
1057
- * Method called when file scan is finished
1058
- *
1059
- * @return bool
1060
- */
1061
- private function scan_finished()
1062
- {
1063
- if ($this->tmp_filesystem_append->has($this->get_temp_dir_handler()) &&
1064
- $this->tmp_filesystem_append->getSize($this->get_temp_dir_handler())) {
1065
- return false;
1066
- }
1067
-
1068
- if ($this->tmp_filesystem->has($this->get_temp_dir_handler())) {
1069
- $this->tmp_filesystem->delete($this->get_temp_dir_handler());
1070
- }
1071
-
1072
- $this->logger->debug(sprintf(("File scan finished")));
1073
-
1074
- return true;
1075
- }
1076
-
1077
- /**
1078
- * Calculate bytes from MB value
1079
- *
1080
- * @param int $mb_size
1081
- *
1082
- * @return float|int
1083
- */
1084
- private function calc_to_bytes($mb_size)
1085
- {
1086
- return $mb_size * (1024 * 1024);
1087
- }
1088
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1089
  }
36
  */
37
  class Xcloner_File_System
38
  {
 
39
  private $excluded_files = "";
40
  private $additional_regex_patterns = array();
41
  private $excluded_files_by_default = array("administrator/backups", "wp-content/backups");
76
  $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
77
 
78
  try {
 
79
  $this->start_adapter = new Local($this->xcloner_settings->get_xcloner_start_path(), LOCK_EX, '0001');
80
  $this->start_filesystem = new Filesystem($this->start_adapter, new Config([
81
  'disable_asserts' => true,
95
  'disable_asserts' => true,
96
  ]));
97
 
98
+ $this->storage_adapter = new Local(
99
+ $this->xcloner_settings->get_xcloner_store_path(),
100
+ FILE_APPEND,
101
+ '0001'
102
+ );
103
  $this->storage_filesystem_append = new Filesystem($this->storage_adapter, new Config([
104
  'disable_asserts' => true,
105
  ]));
106
+ } catch (Exception $e) {
107
  $this->logger->error("Filesystem Initialization Error: ".$e->getMessage());
108
  }
109
 
110
 
111
+ if ($value = $this->xcloner_settings->get_xcloner_option('xcloner_directories_to_scan_per_request')) {
112
  $this->folders_to_process_per_session = $value;
113
  }
 
114
  }
115
 
116
  /**
225
  $spl_info = $this->getMetadataFull('tmp_adapter', $path);
226
 
227
  return $spl_info;
 
228
  }
229
 
230
  public function get_temp_dir_handler()
391
  }
392
  }
393
  }
 
394
  }
395
 
396
+ if ($file_info['type'] == 'file' and isset($file_info['extension']) and in_array(
397
+ $file_info['extension'],
398
+ $this->backup_archive_extensions
399
+ )) {
400
  $backup_files[$file_info['path']] = $file_info;
401
  }
402
  }
417
  public function start_file_recursion($init = 0)
418
  {
419
  if ($init) {
420
+ $this->logger->info(sprintf(
421
+ __("Starting the filesystem scanner on root folder %s"),
422
+ $this->xcloner_settings->get_xcloner_start_path()
423
+ ));
424
  $this->do_system_init();
425
  }
426
 
499
  public function remove_tmp_filesystem()
500
  {
501
  //delete the temporary folder
502
+ $this->logger->debug(sprintf(
503
+ "Deleting the temporary storage folder %s",
504
+ $this->xcloner_settings->get_xcloner_tmp_path()
505
+ ));
506
 
507
  $contents = $this->get_tmp_filesystem()->listContents();
508
 
514
 
515
  try {
516
  rmdir($this->xcloner_settings->get_xcloner_tmp_path());
517
+ } catch (Exception $e) {
518
  //silent continue
519
  }
520
 
533
  $contents = $tmp_filesystem->listContents();
534
 
535
  foreach ($contents as $file) {
 
536
  if (preg_match("/.xcloner-(.*)/", $file['path'])) {
537
  if ($file['timestamp'] < strtotime("-1days")) {
538
  $tmp_filesystem->deleteDir($file['path']);
592
  }
593
 
594
  foreach ($excluded_files as $excl) {
 
595
  if ($this->is_regex($excl)) {
596
  $this->additional_regex_patterns[] = $excl;
597
  }
618
 
619
  //if we start with the root folder(empty value), we initializa the file system
620
  if (!$folder) {
 
621
  }
622
 
623
  try {
 
624
  $files = $this->start_filesystem->listContents($folder);
625
  foreach ($files as $file) {
626
  if (!is_readable($this->xcloner_settings->get_xcloner_start_path().DS.$file['path'])) {
627
+ $this->logger->info(sprintf(
628
+ __("Excluding %s from the filesystem list, file not readable"),
629
+ $file['path']
630
+ ), array(
631
  "FILESYSTEM SCAN",
632
  "NOT READABLE"
633
  ));
643
  if (isset($file['size'])) {
644
  $this->files_size += $file['size'];
645
  }
 
646
  } else {
647
+ $this->logger->info(sprintf(
648
+ __("Excluding %s from the filesystem list, matching pattern %s"),
649
+ $file['path'],
650
+ $matching_pattern
651
+ ), array(
652
  "FILESYSTEM SCAN",
653
  "EXCLUDE"
654
  ));
655
  }
656
  }
657
+ } catch (Exception $e) {
 
 
658
  $this->logger->error($e->getMessage());
 
659
  }
 
660
  }
661
 
662
  public function estimate_read_write_time()
677
  $return['reading_time'] = $this->estimate_reading_time($tmp_file);
678
 
679
  $this->tmp_filesystem->delete($tmp_file);
680
+ } catch (Exception $e) {
 
 
681
  $this->logger->error($e->getMessage());
 
682
  }
683
 
684
  return $return;
713
  foreach ($_backup_files_list as $file) {
714
  //processing rule folder capacity
715
  if ($this->xcloner_settings->get_xcloner_option('xcloner_cleanup_capacity_limit') &&
716
+ $_storage_size >= ($set_storage_limit = 1024 * 1024 * $this->xcloner_settings->get_xcloner_option('xcloner_cleanup_capacity_limit'))) { //bytes
 
717
  $this->storage_filesystem->delete($file['path']);
718
  $_storage_size -= $file['size'];
719
  $this->logger->info("Deleting backup ".$file['path']." matching rule", array(
740
  $_backups_counter." >= ".$this->xcloner_settings->get_xcloner_option('xcloner_cleanup_retention_limit_archives')
741
  ));
742
  }
 
 
743
  }
 
744
  }
745
 
746
  /**
759
  $end_time = microtime(true) - $start_time;
760
 
761
  return $end_time;
 
762
  }
763
 
764
  public function process_backup_name($name = "", $max_length = 100)
772
  $name = str_replace($tag, date("Y-m-d_H-i"), $name);
773
  } elseif ($tag == '[hostname]') {
774
  $name = str_replace($tag, gethostname(), $name);
775
+ } elseif ($tag == '[hash]') {
776
  $name = str_replace($tag, $this->xcloner_container->randomString(5), $name);
777
  } elseif ($tag == '[domain]') {
778
  $domain = parse_url(admin_url(), PHP_URL_HOST);
919
  * exclude the backup folders
920
  * PATTERN: (^|^\/)(wp-content\/backups|administrator\/backups)(.*)$";
921
  */
922
+ private function is_excluded_regex($file)
923
+ {
924
+ //$this->logger->debug(sprintf(("Checking if %s is excluded"), $file['path']));
925
+
926
+ $regex_patterns = explode(PHP_EOL, $this->xcloner_settings->get_xcloner_option('xcloner_regex_exclude'));
927
+
928
+ if (is_array($this->additional_regex_patterns)) {
929
+ $regex_patterns = array_merge($regex_patterns, $this->additional_regex_patterns);
930
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
931
 
932
+ //print_r($regex_patterns);exit;
933
+
934
+ if (is_array($regex_patterns)) {
935
+ //$this->excluded_files = array();
936
+ //$this->excluded_files[] ="(.*)\.(git)(.*)$";
937
+ //$this->excluded_files[] ="wp-content\/backups(.*)$";
938
+
939
+ foreach ($regex_patterns as $excluded_file_pattern) {
940
+ if (substr(
941
+ $excluded_file_pattern,
942
+ strlen($excluded_file_pattern) - 1,
943
+ strlen($excluded_file_pattern)
944
+ ) == "\r") {
945
+ $excluded_file_pattern = substr($excluded_file_pattern, 0, strlen($excluded_file_pattern) - 1);
946
+ }
947
+
948
+ if ($file['path'] == "/") {
949
+ $needle = "/";
950
+ } else {
951
+ $needle = "/".$file['path'];
952
+ }
953
+ //echo $needle."---".$excluded_file_pattern."---\n";
954
+
955
+ if (@preg_match("/(^|^\/)".$excluded_file_pattern."/i", $needle)) {
956
+ return $excluded_file_pattern;
957
+ }
958
+ }
959
+ }
960
+
961
+ return false;
962
+ }
963
+
964
+ public function store_file($file, $storage = 'start_filesystem')
965
+ {
966
+ $this->logger->debug(sprintf("Storing %s in the backup list", $file['path']));
967
+
968
+ if (!isset($file['size'])) {
969
+ $file['size'] = 0;
970
+ }
971
+ if (!isset($file['visibility'])) {
972
+ $file['visibility'] = "private";
973
+ }
974
+
975
+ $csv_filename = str_replace('"', '""', $file['path']);
976
+
977
+ $line = '"'.($csv_filename).'","'.$file['timestamp'].'","'.$file['size'].'","'.$file['visibility'].'","'.$storage.'"'.PHP_EOL;
978
+
979
+ $this->last_logged_file = $file['path'];
980
+
981
+ if ($file['type'] == "dir") {
982
+ try {
983
+ $this->tmp_filesystem_append->write($this->get_temp_dir_handler(), $file['path']."\n");
984
+ } catch (Exception $e) {
985
+ $this->logger->error($e->getMessage());
986
+ }
987
+ }
988
+
989
+ if ($this->get_diff_timestamp_start()) {
990
+ if ($file['type'] != "file" && $response = $this->check_file_diff_time($file)) {
991
+ $this->logger->info(sprintf(
992
+ "Directory %s archiving skipped on differential backup %s",
993
+ $file['path'],
994
+ $response
995
+ ), array(
996
+ "FILESYSTEM SCAN",
997
+ "DIR DIFF"
998
+ ));
999
+
1000
+ return false;
1001
+ }
1002
+ }
1003
+
1004
+ try {
1005
+ if (!$this->tmp_filesystem_append->has($this->get_included_files_handler())) {
1006
+ //adding fix for UTF-8 CSV preview
1007
+ $start_line = "\xEF\xBB\xBF".'"Filename","Timestamp","Size","Visibility","Storage"'.PHP_EOL;
1008
+ $this->tmp_filesystem_append->write($this->get_included_files_handler(), $start_line);
1009
+ }
1010
+
1011
+ $this->tmp_filesystem_append->write($this->get_included_files_handler(), $line);
1012
+ } catch (Exception $e) {
1013
+ $this->logger->error($e->getMessage());
1014
+ }
1015
+
1016
+ return true;
1017
+ }
1018
+
1019
+ public function get_fileystem_handler()
1020
+ {
1021
+ return $this;
1022
+ }
1023
+
1024
+ public function get_filesystem($system = "")
1025
+ {
1026
+ if ($system == "storage_filesystem_append") {
1027
+ return $this->storage_filesystem_append;
1028
+ } elseif ($system == "tmp_filesystem_append") {
1029
+ return $this->tmp_filesystem_append;
1030
+ } elseif ($system == "tmp_filesystem") {
1031
+ return $this->tmp_filesystem;
1032
+ } elseif ($system == "storage_filesystem") {
1033
+ return $this->storage_filesystem;
1034
+ } else {
1035
+ return $this->start_filesystem;
1036
+ }
1037
+ }
1038
+
1039
+ public function get_adapter($system)
1040
+ {
1041
+ if ($system == "tmp_filesystem") {
1042
+ return $this->tmp_adapter;
1043
+ } elseif ($system == "storage_filesystem") {
1044
+ return $this->storage_adapter;
1045
+ } else {
1046
+ return $this->start_adapter;
1047
+ }
1048
+ }
1049
+
1050
+ /**
1051
+ * File scan finished
1052
+ * Method called when file scan is finished
1053
+ *
1054
+ * @return bool
1055
+ */
1056
+ private function scan_finished()
1057
+ {
1058
+ if ($this->tmp_filesystem_append->has($this->get_temp_dir_handler()) &&
1059
+ $this->tmp_filesystem_append->getSize($this->get_temp_dir_handler())) {
1060
+ return false;
1061
+ }
1062
+
1063
+ if ($this->tmp_filesystem->has($this->get_temp_dir_handler())) {
1064
+ $this->tmp_filesystem->delete($this->get_temp_dir_handler());
1065
+ }
1066
+
1067
+ $this->logger->debug(sprintf(("File scan finished")));
1068
+
1069
+ return true;
1070
+ }
1071
+
1072
+ /**
1073
+ * Calculate bytes from MB value
1074
+ *
1075
+ * @param int $mb_size
1076
+ *
1077
+ * @return float|int
1078
+ */
1079
+ private function calc_to_bytes($mb_size)
1080
+ {
1081
+ return $mb_size * (1024 * 1024);
1082
+ }
1083
  }
includes/class-xcloner-loader.php CHANGED
@@ -86,45 +86,6 @@ class Xcloner_Loader
86
 
87
  }
88
 
89
- /**
90
- * Add XCloner to Admin Menu
91
- */
92
- public function xcloner_backup_add_admin_menu()
93
- {
94
- if (function_exists('add_menu_page')) {
95
- add_menu_page(__('Site Backup', 'xcloner-backup-and-restore'),
96
- __('Site Backup', 'xcloner-backup-and-restore'), 'manage_options', 'xcloner_init_page',
97
- array($this->xcloner_container, 'xcloner_display'), 'dashicons-backup');
98
- }
99
-
100
- if (function_exists('add_submenu_page')) {
101
-
102
- add_submenu_page('xcloner_init_page', __('XCloner Dashboard', 'xcloner-backup-and-restore'),
103
- __('Dashboard', 'xcloner-backup-and-restore'), 'manage_options', 'xcloner_init_page',
104
- array($this->xcloner_container, 'xcloner_display'));
105
- add_submenu_page('xcloner_init_page', __('XCloner Backup Settings', 'xcloner-backup-and-restore'),
106
- __('Settings', 'xcloner-backup-and-restore'), 'manage_options', 'xcloner_settings_page',
107
- array($this->xcloner_container, 'xcloner_display'));
108
- add_submenu_page('xcloner_init_page', __('Remote Storage Settings', 'xcloner-backup-and-restore'),
109
- __('Remote Storage', 'xcloner-backup-and-restore'), 'manage_options', 'xcloner_remote_storage_page',
110
- array($this->xcloner_container, 'xcloner_display'));
111
- add_submenu_page('xcloner_init_page', __('Manage Backups', 'xcloner-backup-and-restore'),
112
- __('Manage Backups', 'xcloner-backup-and-restore'), 'manage_options', 'xcloner_manage_backups_page',
113
- array($this->xcloner_container, 'xcloner_display'));
114
- add_submenu_page('xcloner_init_page', __('Scheduled Backups', 'xcloner-backup-and-restore'),
115
- __('Scheduled Backups', 'xcloner-backup-and-restore'), 'manage_options',
116
- 'xcloner_scheduled_backups_page', array($this->xcloner_container, 'xcloner_display'));
117
- add_submenu_page('xcloner_init_page', __('Generate Backups', 'xcloner-backup-and-restore'),
118
- __('Generate Backups', 'xcloner-backup-and-restore'), 'manage_options', 'xcloner_generate_backups_page',
119
- array($this->xcloner_container, 'xcloner_display'));
120
- add_submenu_page('xcloner_init_page', __('Restore Backups', 'xcloner-backup-and-restore'),
121
- __('Restore Backups', 'xcloner-backup-and-restore'), 'manage_options', 'xcloner_restore_page',
122
- array($this->xcloner_container, 'xcloner_display'));
123
- }
124
-
125
- }
126
-
127
-
128
  /**
129
  * Add a new action to the collection to be registered with WordPress.
130
  *
@@ -191,7 +152,6 @@ class Xcloner_Loader
191
  */
192
  public function run()
193
  {
194
-
195
  foreach ($this->filters as $hook) {
196
  add_filter($hook['hook'], array($hook['component'], $hook['callback']), $hook['priority'],
197
  $hook['accepted_args']);
86
 
87
  }
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  /**
90
  * Add a new action to the collection to be registered with WordPress.
91
  *
152
  */
153
  public function run()
154
  {
 
155
  foreach ($this->filters as $hook) {
156
  add_filter($hook['hook'], array($hook['component'], $hook['callback']), $hook['priority'],
157
  $hook['accepted_args']);
includes/class-xcloner-logger.php CHANGED
@@ -4,119 +4,120 @@ use Monolog\Logger;
4
  use Monolog\Handler\StreamHandler;
5
  use Monolog\Handler\RotatingFileHandler;
6
 
7
- class Xcloner_Logger extends Logger {
8
-
9
- private $logger_path;
10
- private $max_logger_files = 7;
11
- private $main_logger_url;
12
-
13
- /**
14
- * Xcloner_Logger constructor.
15
- * @param Xcloner $xcloner_container
16
- * @param string $logger_name
17
- * @throws Exception
18
- */
19
- public function __construct(Xcloner $xcloner_container, $logger_name = "xcloner_logger") {
20
- if (!$xcloner_container->get_xcloner_settings()) {
21
- $xcloner_settings = new Xcloner_Settings($xcloner_container);
22
- } else {
23
- $xcloner_settings = $xcloner_container->get_xcloner_settings();
24
- }
25
-
26
- $hash = $xcloner_settings->get_hash();
27
- if ($hash == "-".$xcloner_settings->get_server_unique_hash(5)) {
28
- $hash = "";
29
- }
30
-
31
- $logger_path = $xcloner_settings->get_xcloner_store_path().DS.$xcloner_settings->get_logger_filename();
32
- $logger_path_tmp = "";
33
-
34
- if ($hash) {
35
- $logger_path_tmp = $xcloner_settings->get_xcloner_tmp_path().DS.$xcloner_settings->get_logger_filename(1);
36
- }
37
-
38
- $this->logger_path = $logger_path;
39
-
40
- if (!is_dir($xcloner_settings->get_xcloner_store_path()) or !is_writable($xcloner_settings->get_xcloner_store_path())) {
41
- $logger_path = 'php://stderr';
42
- $logger_path_tmp = "";
43
- }
44
-
45
- if (!$xcloner_settings->get_xcloner_option('xcloner_enable_log')) {
46
- $logger_path = 'php://stderr';
47
- $logger_path_tmp = "";
48
- }
49
-
50
- // create a log channel
51
- parent::__construct($logger_name);
52
-
53
- $debug_level = Logger::INFO;
54
-
55
- if (WP_DEBUG) {
56
- $debug_level = Logger::DEBUG;
57
- }
58
-
59
-
60
- if ($logger_path) {
61
- if (!$xcloner_settings->get_xcloner_option('xcloner_enable_log')) {
62
- $stream = new StreamHandler($logger_path, $debug_level);
63
- } else {
64
- $stream = new RotatingFileHandler($logger_path, $this->max_logger_files, $debug_level);
65
- }
66
-
67
- $this->pushHandler($stream);
68
-
69
- $this->main_logger_url = $stream->getUrl();
70
- }
71
-
72
- if ($hash and $logger_path_tmp) {
73
- $this->pushHandler(new StreamHandler($logger_path_tmp, $debug_level));
74
- }
75
-
76
- //return $this;
77
- }
78
-
79
- /**
80
- * @return string|null
81
- */
82
- function get_main_logger_url() {
83
- return $this->main_logger_url;
84
- }
85
-
86
- /**
87
- * @param int $totalLines
88
- * @return array|bool
89
- */
90
- function getLastDebugLines($totalLines = 200) {
91
- $lines = array();
92
-
93
- if (!file_exists($this->main_logger_url) or !is_readable($this->main_logger_url)) {
94
- return false;
95
- }
96
-
97
- $fp = fopen($this->main_logger_url, 'r');
98
- fseek($fp, - 1, SEEK_END);
99
- $pos = ftell($fp);
100
- $lastLine = "";
101
-
102
- // Loop backword until we have our lines or we reach the start
103
- while ($pos > 0 && count($lines) < $totalLines) {
104
-
105
- $C = fgetc($fp);
106
- if ($C == "\n") {
107
- // skip empty lines
108
- if (trim($lastLine) != "") {
109
- $lines[] = $lastLine;
110
- }
111
- $lastLine = '';
112
- } else {
113
- $lastLine = $C.$lastLine;
114
- }
115
- fseek($fp, $pos--);
116
- }
117
-
118
- $lines = array_reverse($lines);
119
-
120
- return $lines;
121
- }
 
122
  }
4
  use Monolog\Handler\StreamHandler;
5
  use Monolog\Handler\RotatingFileHandler;
6
 
7
+ class Xcloner_Logger extends Logger
8
+ {
9
+ private $logger_path;
10
+ private $max_logger_files = 7;
11
+ private $main_logger_url;
12
+
13
+ /**
14
+ * Xcloner_Logger constructor.
15
+ * @param Xcloner $xcloner_container
16
+ * @param string $logger_name
17
+ * @throws Exception
18
+ */
19
+ public function __construct(Xcloner $xcloner_container, $logger_name = "xcloner_logger")
20
+ {
21
+ if (!$xcloner_container->get_xcloner_settings()) {
22
+ $xcloner_settings = new Xcloner_Settings($xcloner_container);
23
+ } else {
24
+ $xcloner_settings = $xcloner_container->get_xcloner_settings();
25
+ }
26
+
27
+ $hash = $xcloner_settings->get_hash();
28
+ if ($hash == "-".$xcloner_settings->get_server_unique_hash(5)) {
29
+ $hash = "";
30
+ }
31
+
32
+ $logger_path = $xcloner_settings->get_xcloner_store_path().DS.$xcloner_settings->get_logger_filename();
33
+ $logger_path_tmp = "";
34
+
35
+ if ($hash) {
36
+ $logger_path_tmp = $xcloner_settings->get_xcloner_tmp_path().DS.$xcloner_settings->get_logger_filename(1);
37
+ }
38
+
39
+ $this->logger_path = $logger_path;
40
+
41
+ if (!is_dir($xcloner_settings->get_xcloner_store_path()) or !is_writable($xcloner_settings->get_xcloner_store_path())) {
42
+ $logger_path = 'php://stderr';
43
+ $logger_path_tmp = "";
44
+ }
45
+
46
+ if (!$xcloner_settings->get_xcloner_option('xcloner_enable_log')) {
47
+ $logger_path = 'php://stderr';
48
+ $logger_path_tmp = "";
49
+ }
50
+
51
+ // create a log channel
52
+ parent::__construct($logger_name);
53
+
54
+ $debug_level = Logger::INFO;
55
+
56
+ if (defined('WP_DEBUG') && WP_DEBUG) {
57
+ $debug_level = Logger::DEBUG;
58
+ }
59
+
60
+ if ($logger_path) {
61
+ if (!$xcloner_settings->get_xcloner_option('xcloner_enable_log')) {
62
+ $stream = new StreamHandler($logger_path, $debug_level);
63
+ } else {
64
+ $stream = new RotatingFileHandler($logger_path, $this->max_logger_files, $debug_level);
65
+ }
66
+
67
+ $this->pushHandler($stream);
68
+
69
+ $this->main_logger_url = $stream->getUrl();
70
+ }
71
+
72
+ if ($hash and $logger_path_tmp) {
73
+ $this->pushHandler(new StreamHandler($logger_path_tmp, $debug_level));
74
+ }
75
+
76
+ //return $this;
77
+ }
78
+
79
+ /**
80
+ * @return string|null
81
+ */
82
+ public function get_main_logger_url()
83
+ {
84
+ return $this->main_logger_url;
85
+ }
86
+
87
+ /**
88
+ * @param int $totalLines
89
+ * @return array|bool
90
+ */
91
+ public function getLastDebugLines($totalLines = 200)
92
+ {
93
+ $lines = array();
94
+
95
+ if (!file_exists($this->main_logger_url) or !is_readable($this->main_logger_url)) {
96
+ return false;
97
+ }
98
+
99
+ $fp = fopen($this->main_logger_url, 'r');
100
+ fseek($fp, - 1, SEEK_END);
101
+ $pos = ftell($fp);
102
+ $lastLine = "";
103
+
104
+ // Loop backword until we have our lines or we reach the start
105
+ while ($pos > 0 && count($lines) < $totalLines) {
106
+ $C = fgetc($fp);
107
+ if ($C == "\n") {
108
+ // skip empty lines
109
+ if (trim($lastLine) != "") {
110
+ $lines[] = $lastLine;
111
+ }
112
+ $lastLine = '';
113
+ } else {
114
+ $lastLine = $C.$lastLine;
115
+ }
116
+ fseek($fp, $pos--);
117
+ }
118
+
119
+ $lines = array_reverse($lines);
120
+
121
+ return $lines;
122
+ }
123
  }
includes/class-xcloner-remote-storage.php CHANGED
@@ -53,7 +53,6 @@ use League\Flysystem\WebDAV\WebDAVAdapter;
53
  */
54
  class Xcloner_Remote_Storage
55
  {
56
-
57
  private $gdrive_app_name = "XCloner Backup and Restore";
58
 
59
  private $storage_fields = array(
@@ -160,6 +159,7 @@ class Xcloner_Remote_Storage
160
 
161
  private $xcloner_sanitization;
162
  private $xcloner_file_system;
 
163
  private $logger;
164
  private $xcloner;
165
 
@@ -171,33 +171,30 @@ class Xcloner_Remote_Storage
171
  {
172
  $this->xcloner_sanitization = $xcloner_container->get_xcloner_sanitization();
173
  $this->xcloner_file_system = $xcloner_container->get_xcloner_filesystem();
 
174
  $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_remote_storage");
175
  $this->xcloner = $xcloner_container;
176
 
177
  foreach ($this->storage_fields as $main_key => $array) {
178
-
179
  if (is_array($array)) {
180
  foreach ($array as $key => $type) {
181
-
182
  if ($type == "raw") {
183
- add_filter("pre_update_option_" . $this->storage_fields['option_prefix'] . $key,
 
184
  function ($value) {
185
-
186
  return $this->simple_crypt($value, 'e');
187
-
188
- }, 10, 1);
 
 
189
 
190
  add_filter("option_" . $this->storage_fields['option_prefix'] . $key, function ($value) {
191
-
192
  return $this->simple_crypt($value, 'd');
193
-
194
  }, 10, 1);
195
  }
196
-
197
  }
198
  }
199
  }
200
-
201
  }
202
 
203
  /**
@@ -248,7 +245,7 @@ class Xcloner_Remote_Storage
248
  $return = array();
249
  foreach ($this->storage_fields as $storage => $data) {
250
  $check_field = $this->storage_fields["option_prefix"] . $storage . "_enable";
251
- if (get_option($check_field)) {
252
  $return[$storage] = $data['text'];
253
  }
254
  }
@@ -267,13 +264,12 @@ class Xcloner_Remote_Storage
267
 
268
  if (is_array($this->storage_fields[$storage])) {
269
  foreach ($this->storage_fields[$storage] as $field => $validation) {
270
-
271
  $check_field = $this->storage_fields["option_prefix"] . $field;
272
  $sanitize_method = "sanitize_input_as_" . $validation;
273
 
274
 
275
  //we do not save empty encrypted credentials
276
- if($validation == "raw" && str_repeat('*', strlen($_POST[$check_field])) == $_POST[$check_field] ){
277
  continue;
278
  }
279
 
@@ -289,22 +285,30 @@ class Xcloner_Remote_Storage
289
  update_option($check_field, $sanitized_value);
290
  }
291
 
292
- $this->xcloner->trigger_message(__("%s storage settings saved.", 'xcloner-backup-and-restore'), "success",
293
- $this->storage_fields[$action]['text']);
 
 
 
294
  }
295
-
296
  }
297
 
298
  public function check($action = "ftp")
299
  {
300
  try {
301
  $this->verify_filesystem($action);
302
- $this->xcloner->trigger_message(__("%s connection is valid.", 'xcloner-backup-and-restore'), "success",
303
- $this->storage_fields[$action]['text']);
 
 
 
304
  $this->logger->debug(sprintf("Connection to remote storage %s is valid", strtoupper($action)));
305
  } catch (Exception $e) {
306
- $this->xcloner->trigger_message("%s connection error: " . $e->getMessage(), "error",
307
- $this->storage_fields[$action]['text']);
 
 
 
308
  }
309
  }
310
 
@@ -315,8 +319,10 @@ class Xcloner_Remote_Storage
315
  {
316
  $method = "get_" . $storage_type . "_filesystem";
317
 
318
- $this->logger->info(sprintf("Checking validity of the remote storage %s filesystem",
319
- strtoupper($storage_type)));
 
 
320
 
321
  if (!method_exists($this, $method)) {
322
  return false;
@@ -375,8 +381,10 @@ class Xcloner_Remote_Storage
375
  //doing remote storage cleaning here
376
  $this->clean_remote_storage($storage, $remote_storage_filesystem);
377
 
378
- $this->logger->info(sprintf("Transferring backup %s to remote storage %s", $file, strtoupper($storage)),
379
- array(""));
 
 
380
 
381
  /*if(!$this->xcloner_file_system->get_storage_filesystem()->has($file))
382
  {
@@ -396,8 +404,11 @@ class Xcloner_Remote_Storage
396
  $parts = $this->xcloner_file_system->get_multipart_files($file);
397
  if (is_array($parts)) {
398
  foreach ($parts as $part_file) {
399
- $this->logger->info(sprintf("Transferring backup %s to remote storage %s", $part_file,
400
- strtoupper($storage)), array(""));
 
 
 
401
 
402
  $backup_file_stream = $this->xcloner_file_system->get_storage_filesystem()->readStream($part_file);
403
  if (!$remote_storage_filesystem->writeStream($part_file, $backup_file_stream)) {
@@ -410,7 +421,6 @@ class Xcloner_Remote_Storage
410
  $this->logger->info(sprintf("Upload done, disconnecting from remote storage %s", strtoupper($storage)));
411
 
412
  return true;
413
-
414
  }
415
 
416
  public function copy_backup_remote_to_local($file, $storage)
@@ -436,8 +446,11 @@ class Xcloner_Remote_Storage
436
  $target_filename = $metadata['filename'] . "." . $metadata['extension'];
437
  }
438
 
439
- $this->logger->info(sprintf("Transferring backup %s to local storage from %s storage", $file,
440
- strtoupper($storage)), array(""));
 
 
 
441
 
442
  $backup_file_stream = $remote_storage_filesystem->readStream($file);
443
 
@@ -451,12 +464,17 @@ class Xcloner_Remote_Storage
451
  $parts = $this->xcloner_file_system->get_multipart_files($file, $storage);
452
  if (is_array($parts)) {
453
  foreach ($parts as $part_file) {
454
- $this->logger->info(sprintf("Transferring backup %s to local storage from %s storage", $part_file,
455
- strtoupper($storage)), array(""));
 
 
 
456
 
457
  $backup_file_stream = $remote_storage_filesystem->readStream($part_file);
458
- if (!$this->xcloner_file_system->get_storage_filesystem()->writeStream($part_file,
459
- $backup_file_stream)) {
 
 
460
  return false;
461
  }
462
  }
@@ -466,15 +484,17 @@ class Xcloner_Remote_Storage
466
  $this->logger->info(sprintf("Upload done, disconnecting from remote storage %s", strtoupper($storage)));
467
 
468
  return true;
469
-
470
  }
471
 
472
  public function clean_remote_storage($storage, $remote_storage_filesystem)
473
  {
474
  $check_field = $this->storage_fields["option_prefix"] . $storage . "_cleanup_days";
475
- if ($expire_days = get_option($check_field)) {
476
- $this->logger->info(sprintf("Doing %s remote storage cleanup for %s days limit", strtoupper($storage),
477
- $expire_days));
 
 
 
478
  $files = $remote_storage_filesystem->listContents();
479
 
480
  $current_timestamp = strtotime("-" . $expire_days . " days");
@@ -490,7 +510,6 @@ class Xcloner_Remote_Storage
490
  $file['timestamp'] . " =< " . $expire_days
491
  ));
492
  }
493
-
494
  }
495
  }
496
  }
@@ -510,13 +529,13 @@ class Xcloner_Remote_Storage
510
 
511
  $endpoint = sprintf(
512
  'DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s',
513
- get_option("xcloner_azure_account_name"),
514
- get_option("xcloner_azure_api_key")
515
  );
516
 
517
  $blobRestProxy = BlobRestProxy::createBlobService($endpoint);
518
 
519
- $adapter = new AzureBlobStorageAdapter($blobRestProxy, get_option("xcloner_azure_container"));
520
 
521
  $filesystem = new Filesystem($adapter, new Config([
522
  'disable_asserts' => true,
@@ -533,8 +552,8 @@ class Xcloner_Remote_Storage
533
  throw new Exception("DROPBOX requires PHP 5.6 to be installed!");
534
  }
535
 
536
- $client = new DropboxClient(get_option("xcloner_dropbox_access_token"));
537
- $adapter = new DropboxAdapter($client, get_option("xcloner_dropbox_prefix"));
538
 
539
  $filesystem = new Filesystem($adapter, new Config([
540
  'disable_asserts' => true,
@@ -558,25 +577,22 @@ class Xcloner_Remote_Storage
558
 
559
  $credentials = array(
560
  'credentials' => array(
561
- 'key' => get_option("xcloner_aws_key"),
562
- 'secret' => get_option("xcloner_aws_secret")
563
  ),
564
- 'region' => get_option("xcloner_aws_region"),
565
  'version' => 'latest',
566
  );
567
 
568
- if (get_option('xcloner_aws_endpoint') != "" && !get_option("xcloner_aws_region")) {
569
-
570
- $credentials['endpoint'] = get_option('xcloner_aws_endpoint');
571
  #$credentials['use_path_style_endpoint'] = true;
572
  #$credentials['bucket_endpoint'] = false;
573
-
574
-
575
  }
576
 
577
  $client = new S3Client($credentials);
578
 
579
- $adapter = new AwsS3Adapter($client, get_option("xcloner_aws_bucket_name"), get_option("xcloner_aws_prefix"));
580
  $filesystem = new Filesystem($adapter, new Config([
581
  'disable_asserts' => true,
582
  ]));
@@ -593,9 +609,11 @@ class Xcloner_Remote_Storage
593
  }
594
 
595
 
596
- $client = new B2Client(get_option("xcloner_backblaze_account_id"),
597
- get_option("xcloner_backblaze_application_key"));
598
- $adapter = new BackblazeAdapter($client, get_option("xcloner_backblaze_bucket_name"));
 
 
599
 
600
  $filesystem = new Filesystem($adapter, new Config([
601
  'disable_asserts' => true,
@@ -613,14 +631,14 @@ class Xcloner_Remote_Storage
613
  }
614
 
615
  $settings = array(
616
- 'baseUri' => get_option("xcloner_webdav_url"),
617
- 'userName' => get_option("xcloner_webdav_username"),
618
- 'password' => get_option("xcloner_webdav_password"),
619
  //'proxy' => 'locahost:8888',
620
  );
621
 
622
  $client = new SabreClient($settings);
623
- $adapter = new WebDAVAdapter($client, get_option("xcloner_webdav_target_folder"));
624
  $filesystem = new Filesystem($adapter, new Config([
625
  'disable_asserts' => true,
626
  ]));
@@ -641,8 +659,8 @@ class Xcloner_Remote_Storage
641
 
642
  $client = new \Google_Client();
643
  $client->setApplicationName($this->gdrive_app_name);
644
- $client->setClientId(get_option("xcloner_gdrive_client_id"));
645
- $client->setClientSecret(get_option("xcloner_gdrive_client_secret"));
646
 
647
  //$redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF']."?page=xcloner_remote_storage_page&action=set_gdrive_code";
648
  $redirect_uri = "urn:ietf:wg:oauth:2.0:oob";
@@ -682,14 +700,11 @@ class Xcloner_Remote_Storage
682
  update_option("xcloner_gdrive_access_token", $token['access_token']);
683
  update_option("xcloner_gdrive_refresh_token", $token['refresh_token']);
684
 
685
- $redirect_url = ('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'] . "?page=xcloner_remote_storage_page#gdrive");
686
-
687
- ?>
688
  <script>
689
  window.location = '<?php echo $redirect_url?>';
690
  </script>
691
  <?php
692
-
693
  }
694
 
695
  /*
@@ -698,7 +713,6 @@ class Xcloner_Remote_Storage
698
  */
699
  public function get_gdrive_filesystem()
700
  {
701
-
702
  if (version_compare(phpversion(), '5.6.0', '<')) {
703
  throw new Exception("Google Drive API requires PHP 5.6 to be installed!");
704
  }
@@ -713,19 +727,19 @@ class Xcloner_Remote_Storage
713
  throw new Exception($error_msg);
714
  }
715
 
716
- $client->refreshToken(get_option("xcloner_gdrive_refresh_token"));
717
 
718
  $service = new \Google_Service_Drive($client);
719
 
720
- if (get_option("xcloner_gdrive_empty_trash", 0)) {
721
  $this->logger->info(sprintf("Doing a Google Drive emptyTrash call"), array(""));
722
  $service->files->emptyTrash();
723
  }
724
 
725
  $parent = 'root';
726
- $dir = basename(get_option("xcloner_gdrive_target_folder"));
727
 
728
- $folderID = get_option("xcloner_gdrive_target_folder");
729
 
730
  $tmp = parse_url($folderID);
731
 
@@ -734,8 +748,11 @@ class Xcloner_Remote_Storage
734
  }
735
 
736
  if (stristr($folderID, "/")) {
737
- $query = sprintf('mimeType = \'application/vnd.google-apps.folder\' and \'%s\' in parents and name contains \'%s\'',
738
- $parent, $dir);
 
 
 
739
  $response = $service->files->listFiles([
740
  'pageSize' => 1,
741
  'q' => $query
@@ -746,8 +763,10 @@ class Xcloner_Remote_Storage
746
  $folderID = $obj->getId();
747
  }
748
  } else {
749
- $this->xcloner->trigger_message(sprintf(__("Could not find folder ID by name %s",
750
- 'xcloner-backup-and-restore'), $folderID), "error");
 
 
751
  }
752
  }
753
 
@@ -772,16 +791,16 @@ class Xcloner_Remote_Storage
772
  $this->logger->info(sprintf("Creating the FTP remote storage connection"), array(""));
773
 
774
  $adapter = new Adapter([
775
- 'host' => get_option("xcloner_ftp_hostname"),
776
- 'username' => get_option("xcloner_ftp_username"),
777
- 'password' => get_option("xcloner_ftp_password"),
778
 
779
  /** optional config settings */
780
- 'port' => get_option("xcloner_ftp_port", 21),
781
- 'root' => get_option("xcloner_ftp_path"),
782
- 'passive' => get_option("xcloner_ftp_transfer_mode"),
783
- 'ssl' => get_option("xcloner_ftp_ssl_mode"),
784
- 'timeout' => get_option("xcloner_ftp_timeout", 30),
785
  ]);
786
 
787
  $adapter->connect();
@@ -798,15 +817,15 @@ class Xcloner_Remote_Storage
798
  $this->logger->info(sprintf("Creating the SFTP remote storage connection"), array(""));
799
 
800
  $adapter = new SftpAdapter([
801
- 'host' => get_option("xcloner_sftp_hostname"),
802
- 'username' => get_option("xcloner_sftp_username"),
803
- 'password' => get_option("xcloner_sftp_password"),
804
 
805
  /** optional config settings */
806
- 'port' => get_option("xcloner_sftp_port", 22),
807
- 'root' => (get_option("xcloner_sftp_path")?get_option("xcloner_sftp_path"):'./'),
808
- 'privateKey' => get_option("xcloner_sftp_private_key"),
809
- 'timeout' => get_option("xcloner_ftp_timeout", 30),
810
  ]);
811
 
812
  $adapter->connect();
@@ -830,5 +849,4 @@ class Xcloner_Remote_Storage
830
  {
831
  return $this->aws_regions;
832
  }
833
-
834
  }
53
  */
54
  class Xcloner_Remote_Storage
55
  {
 
56
  private $gdrive_app_name = "XCloner Backup and Restore";
57
 
58
  private $storage_fields = array(
159
 
160
  private $xcloner_sanitization;
161
  private $xcloner_file_system;
162
+ private $xcloner_settings;
163
  private $logger;
164
  private $xcloner;
165
 
171
  {
172
  $this->xcloner_sanitization = $xcloner_container->get_xcloner_sanitization();
173
  $this->xcloner_file_system = $xcloner_container->get_xcloner_filesystem();
174
+ $this->xcloner_settings = $xcloner_container->get_xcloner_settings();
175
  $this->logger = $xcloner_container->get_xcloner_logger()->withName("xcloner_remote_storage");
176
  $this->xcloner = $xcloner_container;
177
 
178
  foreach ($this->storage_fields as $main_key => $array) {
 
179
  if (is_array($array)) {
180
  foreach ($array as $key => $type) {
 
181
  if ($type == "raw") {
182
+ add_filter(
183
+ "pre_update_option_" . $this->storage_fields['option_prefix'] . $key,
184
  function ($value) {
 
185
  return $this->simple_crypt($value, 'e');
186
+ },
187
+ 10,
188
+ 1
189
+ );
190
 
191
  add_filter("option_" . $this->storage_fields['option_prefix'] . $key, function ($value) {
 
192
  return $this->simple_crypt($value, 'd');
 
193
  }, 10, 1);
194
  }
 
195
  }
196
  }
197
  }
 
198
  }
199
 
200
  /**
245
  $return = array();
246
  foreach ($this->storage_fields as $storage => $data) {
247
  $check_field = $this->storage_fields["option_prefix"] . $storage . "_enable";
248
+ if ($this->xcloner_settings->get_xcloner_option($check_field)) {
249
  $return[$storage] = $data['text'];
250
  }
251
  }
264
 
265
  if (is_array($this->storage_fields[$storage])) {
266
  foreach ($this->storage_fields[$storage] as $field => $validation) {
 
267
  $check_field = $this->storage_fields["option_prefix"] . $field;
268
  $sanitize_method = "sanitize_input_as_" . $validation;
269
 
270
 
271
  //we do not save empty encrypted credentials
272
+ if ($validation == "raw" && str_repeat('*', strlen($_POST[$check_field])) == $_POST[$check_field]) {
273
  continue;
274
  }
275
 
285
  update_option($check_field, $sanitized_value);
286
  }
287
 
288
+ $this->xcloner->trigger_message(
289
+ __("%s storage settings saved.", 'xcloner-backup-and-restore'),
290
+ "success",
291
+ $this->storage_fields[$action]['text']
292
+ );
293
  }
 
294
  }
295
 
296
  public function check($action = "ftp")
297
  {
298
  try {
299
  $this->verify_filesystem($action);
300
+ $this->xcloner->trigger_message(
301
+ __("%s connection is valid.", 'xcloner-backup-and-restore'),
302
+ "success",
303
+ $this->storage_fields[$action]['text']
304
+ );
305
  $this->logger->debug(sprintf("Connection to remote storage %s is valid", strtoupper($action)));
306
  } catch (Exception $e) {
307
+ $this->xcloner->trigger_message(
308
+ "%s connection error: " . $e->getMessage(),
309
+ "error",
310
+ $this->storage_fields[$action]['text']
311
+ );
312
  }
313
  }
314
 
319
  {
320
  $method = "get_" . $storage_type . "_filesystem";
321
 
322
+ $this->logger->info(sprintf(
323
+ "Checking validity of the remote storage %s filesystem",
324
+ strtoupper($storage_type)
325
+ ));
326
 
327
  if (!method_exists($this, $method)) {
328
  return false;
381
  //doing remote storage cleaning here
382
  $this->clean_remote_storage($storage, $remote_storage_filesystem);
383
 
384
+ $this->logger->info(
385
+ sprintf("Transferring backup %s to remote storage %s", $file, strtoupper($storage)),
386
+ array("")
387
+ );
388
 
389
  /*if(!$this->xcloner_file_system->get_storage_filesystem()->has($file))
390
  {
404
  $parts = $this->xcloner_file_system->get_multipart_files($file);
405
  if (is_array($parts)) {
406
  foreach ($parts as $part_file) {
407
+ $this->logger->info(sprintf(
408
+ "Transferring backup %s to remote storage %s",
409
+ $part_file,
410
+ strtoupper($storage)
411
+ ), array(""));
412
 
413
  $backup_file_stream = $this->xcloner_file_system->get_storage_filesystem()->readStream($part_file);
414
  if (!$remote_storage_filesystem->writeStream($part_file, $backup_file_stream)) {
421
  $this->logger->info(sprintf("Upload done, disconnecting from remote storage %s", strtoupper($storage)));
422
 
423
  return true;
 
424
  }
425
 
426
  public function copy_backup_remote_to_local($file, $storage)
446
  $target_filename = $metadata['filename'] . "." . $metadata['extension'];
447
  }
448
 
449
+ $this->logger->info(sprintf(
450
+ "Transferring backup %s to local storage from %s storage",
451
+ $file,
452
+ strtoupper($storage)
453
+ ), array(""));
454
 
455
  $backup_file_stream = $remote_storage_filesystem->readStream($file);
456
 
464
  $parts = $this->xcloner_file_system->get_multipart_files($file, $storage);
465
  if (is_array($parts)) {
466
  foreach ($parts as $part_file) {
467
+ $this->logger->info(sprintf(
468
+ "Transferring backup %s to local storage from %s storage",
469
+ $part_file,
470
+ strtoupper($storage)
471
+ ), array(""));
472
 
473
  $backup_file_stream = $remote_storage_filesystem->readStream($part_file);
474
+ if (!$this->xcloner_file_system->get_storage_filesystem()->writeStream(
475
+ $part_file,
476
+ $backup_file_stream
477
+ )) {
478
  return false;
479
  }
480
  }
484
  $this->logger->info(sprintf("Upload done, disconnecting from remote storage %s", strtoupper($storage)));
485
 
486
  return true;
 
487
  }
488
 
489
  public function clean_remote_storage($storage, $remote_storage_filesystem)
490
  {
491
  $check_field = $this->storage_fields["option_prefix"] . $storage . "_cleanup_days";
492
+ if ($expire_days = $this->xcloner_settings->get_xcloner_option($check_field)) {
493
+ $this->logger->info(sprintf(
494
+ "Doing %s remote storage cleanup for %s days limit",
495
+ strtoupper($storage),
496
+ $expire_days
497
+ ));
498
  $files = $remote_storage_filesystem->listContents();
499
 
500
  $current_timestamp = strtotime("-" . $expire_days . " days");
510
  $file['timestamp'] . " =< " . $expire_days
511
  ));
512
  }
 
513
  }
514
  }
515
  }
529
 
530
  $endpoint = sprintf(
531
  'DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s',
532
+ $this->xcloner_settings->get_xcloner_option("xcloner_azure_account_name"),
533
+ $this->xcloner_settings->get_xcloner_option("xcloner_azure_api_key")
534
  );
535
 
536
  $blobRestProxy = BlobRestProxy::createBlobService($endpoint);
537
 
538
+ $adapter = new AzureBlobStorageAdapter($blobRestProxy, $this->xcloner_settings->get_xcloner_option("xcloner_azure_container"));
539
 
540
  $filesystem = new Filesystem($adapter, new Config([
541
  'disable_asserts' => true,
552
  throw new Exception("DROPBOX requires PHP 5.6 to be installed!");
553
  }
554
 
555
+ $client = new DropboxClient($this->xcloner_settings->get_xcloner_option("xcloner_dropbox_access_token"));
556
+ $adapter = new DropboxAdapter($client, $this->xcloner_settings->get_xcloner_option("xcloner_dropbox_prefix"));
557
 
558
  $filesystem = new Filesystem($adapter, new Config([
559
  'disable_asserts' => true,
577
 
578
  $credentials = array(
579
  'credentials' => array(
580
+ 'key' => $this->xcloner_settings->get_xcloner_option("xcloner_aws_key"),
581
+ 'secret' => $this->xcloner_settings->get_xcloner_option("xcloner_aws_secret")
582
  ),
583
+ 'region' => $this->xcloner_settings->get_xcloner_option("xcloner_aws_region"),
584
  'version' => 'latest',
585
  );
586
 
587
+ if ($this->xcloner_settings->get_xcloner_option('xcloner_aws_endpoint') != "" && !$this->xcloner_settings->get_xcloner_option("xcloner_aws_region")) {
588
+ $credentials['endpoint'] = $this->xcloner_settings->get_xcloner_option('xcloner_aws_endpoint');
 
589
  #$credentials['use_path_style_endpoint'] = true;
590
  #$credentials['bucket_endpoint'] = false;
 
 
591
  }
592
 
593
  $client = new S3Client($credentials);
594
 
595
+ $adapter = new AwsS3Adapter($client, $this->xcloner_settings->get_xcloner_option("xcloner_aws_bucket_name"), $this->xcloner_settings->get_xcloner_option("xcloner_aws_prefix"));
596
  $filesystem = new Filesystem($adapter, new Config([
597
  'disable_asserts' => true,
598
  ]));
609
  }
610
 
611
 
612
+ $client = new B2Client(
613
+ $this->xcloner_settings->get_xcloner_option("xcloner_backblaze_account_id"),
614
+ $this->xcloner_settings->get_xcloner_option("xcloner_backblaze_application_key")
615
+ );
616
+ $adapter = new BackblazeAdapter($client, $this->xcloner_settings->get_xcloner_option("xcloner_backblaze_bucket_name"));
617
 
618
  $filesystem = new Filesystem($adapter, new Config([
619
  'disable_asserts' => true,
631
  }
632
 
633
  $settings = array(
634
+ 'baseUri' => $this->xcloner_settings->get_xcloner_option("xcloner_webdav_url"),
635
+ 'userName' => $this->xcloner_settings->get_xcloner_option("xcloner_webdav_username"),
636
+ 'password' => $this->xcloner_settings->get_xcloner_option("xcloner_webdav_password"),
637
  //'proxy' => 'locahost:8888',
638
  );
639
 
640
  $client = new SabreClient($settings);
641
+ $adapter = new WebDAVAdapter($client, $this->xcloner_settings->get_xcloner_option("xcloner_webdav_target_folder"));
642
  $filesystem = new Filesystem($adapter, new Config([
643
  'disable_asserts' => true,
644
  ]));
659
 
660
  $client = new \Google_Client();
661
  $client->setApplicationName($this->gdrive_app_name);
662
+ $client->setClientId($this->xcloner_settings->get_xcloner_option("xcloner_gdrive_client_id"));
663
+ $client->setClientSecret($this->xcloner_settings->get_xcloner_option("xcloner_gdrive_client_secret"));
664
 
665
  //$redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF']."?page=xcloner_remote_storage_page&action=set_gdrive_code";
666
  $redirect_uri = "urn:ietf:wg:oauth:2.0:oob";
700
  update_option("xcloner_gdrive_access_token", $token['access_token']);
701
  update_option("xcloner_gdrive_refresh_token", $token['refresh_token']);
702
 
703
+ $redirect_url = ('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'] . "?page=xcloner_remote_storage_page#gdrive"); ?>
 
 
704
  <script>
705
  window.location = '<?php echo $redirect_url?>';
706
  </script>
707
  <?php
 
708
  }
709
 
710
  /*
713
  */
714
  public function get_gdrive_filesystem()
715
  {
 
716
  if (version_compare(phpversion(), '5.6.0', '<')) {
717
  throw new Exception("Google Drive API requires PHP 5.6 to be installed!");
718
  }
727
  throw new Exception($error_msg);
728
  }
729
 
730
+ $client->refreshToken($this->xcloner_settings->get_xcloner_option("xcloner_gdrive_refresh_token"));
731
 
732
  $service = new \Google_Service_Drive($client);
733
 
734
+ if ($this->xcloner_settings->get_xcloner_option("xcloner_gdrive_empty_trash", 0)) {
735
  $this->logger->info(sprintf("Doing a Google Drive emptyTrash call"), array(""));
736
  $service->files->emptyTrash();
737
  }
738
 
739
  $parent = 'root';
740
+ $dir = basename($this->xcloner_settings->get_xcloner_option("xcloner_gdrive_target_folder"));
741
 
742
+ $folderID = $this->xcloner_settings->get_xcloner_option("xcloner_gdrive_target_folder");
743
 
744
  $tmp = parse_url($folderID);
745
 
748
  }
749
 
750
  if (stristr($folderID, "/")) {
751
+ $query = sprintf(
752
+ 'mimeType = \'application/vnd.google-apps.folder\' and \'%s\' in parents and name contains \'%s\'',
753
+ $parent,
754
+ $dir
755
+ );
756
  $response = $service->files->listFiles([
757
  'pageSize' => 1,
758
  'q' => $query
763
  $folderID = $obj->getId();
764
  }
765
  } else {
766
+ $this->xcloner->trigger_message(sprintf(__(
767
+ "Could not find folder ID by name %s",
768
+ 'xcloner-backup-and-restore'
769
+ ), $folderID), "error");
770
  }
771
  }
772
 
791
  $this->logger->info(sprintf("Creating the FTP remote storage connection"), array(""));
792
 
793
  $adapter = new Adapter([
794
+ 'host' => $this->xcloner_settings->get_xcloner_option("xcloner_ftp_hostname"),
795
+ 'username' => $this->xcloner_settings->get_xcloner_option("xcloner_ftp_username"),
796
+ 'password' => $this->xcloner_settings->get_xcloner_option("xcloner_ftp_password"),
797
 
798
  /** optional config settings */
799
+ 'port' => $this->xcloner_settings->get_xcloner_option("xcloner_ftp_port", 21),
800
+ 'root' => $this->xcloner_settings->get_xcloner_option("xcloner_ftp_path"),
801
+ 'passive' => $this->xcloner_settings->get_xcloner_option("xcloner_ftp_transfer_mode"),
802
+ 'ssl' => $this->xcloner_settings->get_xcloner_option("xcloner_ftp_ssl_mode"),
803
+ 'timeout' => $this->xcloner_settings->get_xcloner_option("xcloner_ftp_timeout", 30),
804
  ]);
805
 
806
  $adapter->connect();
817
  $this->logger->info(sprintf("Creating the SFTP remote storage connection"), array(""));
818
 
819
  $adapter = new SftpAdapter([
820
+ 'host' => $this->xcloner_settings->get_xcloner_option("xcloner_sftp_hostname"),
821
+ 'username' => $this->xcloner_settings->get_xcloner_option("xcloner_sftp_username"),
822
+ 'password' => $this->xcloner_settings->get_xcloner_option("xcloner_sftp_password"),
823
 
824
  /** optional config settings */
825
+ 'port' => $this->xcloner_settings->get_xcloner_option("xcloner_sftp_port", 22),
826
+ 'root' => ($this->xcloner_settings->get_xcloner_option("xcloner_sftp_path")?$this->xcloner_settings->get_xcloner_option("xcloner_sftp_path"):'./'),
827
+ 'privateKey' => $this->xcloner_settings->get_xcloner_option("xcloner_sftp_private_key"),
828
+ 'timeout' => $this->xcloner_settings->get_xcloner_option("xcloner_ftp_timeout", 30),
829
  ]);
830
 
831
  $adapter->connect();
849
  {
850
  return $this->aws_regions;
851
  }
 
852
  }
includes/class-xcloner-sanitization.php CHANGED
@@ -2,58 +2,118 @@
2
 
3
  use League\Flysystem\Util;
4
 
5
- class Xcloner_Sanitization {
6
-
7
- public function __construct() {
8
- }
9
-
10
- public function sanitize_input_as_int($option) {
11
- return filter_var($option, FILTER_SANITIZE_NUMBER_INT);
12
- }
13
-
14
- public function sanitize_input_as_float($option) {
15
- return filter_var($option, FILTER_VALIDATE_FLOAT);
16
- }
17
-
18
- public function sanitize_input_as_string($option) {
19
- return filter_var($option, FILTER_SANITIZE_STRING);
20
- }
21
-
22
- public function sanitize_input_as_absolute_path($option) {
23
- $path = filter_var($option, FILTER_SANITIZE_URL);
24
-
25
- try {
26
- $option = Util::normalizePath($path);
27
- }catch (Exception $e) {
28
- add_settings_error('xcloner_error_message', '', __($e->getMessage()), 'error');
29
- }
30
-
31
- if ($path and !is_dir($path)) {
32
- add_settings_error('xcloner_error_message', '', __(sprintf('Invalid Server Path %s', $option)), 'error');
33
-
34
- return false;
35
- }
36
-
37
- return $path;
38
- }
39
-
40
- public function sanitize_input_as_path($option) {
41
- return filter_var($option, FILTER_SANITIZE_URL);
42
- }
43
-
44
- public function sanitize_input_as_relative_path($option) {
45
- $option = filter_var($option, FILTER_SANITIZE_URL);
46
- $option = str_replace("..", "", $option);
47
-
48
- return $option;
49
- }
50
-
51
- public function sanitize_input_as_email($option) {
52
- return filter_var($option, FILTER_SANITIZE_EMAIL);
53
- }
54
-
55
- public function sanitize_input_as_raw($option) {
56
- return filter_var($option, FILTER_UNSAFE_RAW);
57
- }
58
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
2
 
3
  use League\Flysystem\Util;
4
 
5
+ class Xcloner_Sanitization
6
+ {
7
+
8
+ /**
9
+ * Construct method
10
+ */
11
+ public function __construct()
12
+ {
13
+ }
14
+
15
+ /**
16
+ * Sanitize input as INT
17
+ *
18
+ * @param [type] $option
19
+ * @return void
20
+ */
21
+ public function sanitize_input_as_int($option)
22
+ {
23
+ return filter_var($option, FILTER_SANITIZE_NUMBER_INT);
24
+ }
25
+
26
+ /**
27
+ * Sanitize input as FLOAT
28
+ *
29
+ * @param [type] $option
30
+ * @return void
31
+ */
32
+ public function sanitize_input_as_float($option)
33
+ {
34
+ return filter_var($option, FILTER_VALIDATE_FLOAT);
35
+ }
36
+
37
+ /**
38
+ * Sanitize input as STRING
39
+ *
40
+ * @param [type] $option
41
+ * @return void
42
+ */
43
+ public function sanitize_input_as_string($option)
44
+ {
45
+ return filter_var($option, FILTER_SANITIZE_STRING);
46
+ }
47
+
48
+ /**
49
+ * Sanitize input as ABSOLUTE PATH
50
+ *
51
+ * @param [type] $option
52
+ * @return void
53
+ */
54
+ public function sanitize_input_as_absolute_path($option)
55
+ {
56
+ $path = filter_var($option, FILTER_SANITIZE_URL);
57
+
58
+ try {
59
+ $option = Util::normalizePath($path);
60
+ } catch (Exception $e) {
61
+ add_settings_error('xcloner_error_message', '', __($e->getMessage()), 'error');
62
+ }
63
+
64
+ if ($path and !is_dir($path)) {
65
+ add_settings_error('xcloner_error_message', '', __(sprintf('Invalid Server Path %s', $option)), 'error');
66
+
67
+ return false;
68
+ }
69
+
70
+ return $path;
71
+ }
72
+
73
+ /**
74
+ * Sanitize input as PATH
75
+ *
76
+ * @param [type] $option
77
+ * @return void
78
+ */
79
+ public function sanitize_input_as_path($option)
80
+ {
81
+ return filter_var($option, FILTER_SANITIZE_URL);
82
+ }
83
+
84
+ /**
85
+ * Sanitize input as RELATIVE PATH
86
+ *
87
+ * @param [type] $option
88
+ * @return void
89
+ */
90
+ public function sanitize_input_as_relative_path($option)
91
+ {
92
+ $option = filter_var($option, FILTER_SANITIZE_URL);
93
+ $option = str_replace("..", "", $option);
94
+
95
+ return $option;
96
+ }
97
+
98
+ /**
99
+ * Sanitize input as EMAIL
100
+ *
101
+ * @param [type] $option
102
+ * @return void
103
+ */
104
+ public function sanitize_input_as_email($option)
105
+ {
106
+ return filter_var($option, FILTER_SANITIZE_EMAIL);
107
+ }
108
+
109
+ /**
110
+ * Undocumented function as RAW
111
+ *
112
+ * @param [type] $option
113
+ * @return void
114
+ */
115
+ public function sanitize_input_as_raw($option)
116
+ {
117
+ return filter_var($option, FILTER_UNSAFE_RAW);
118
+ }
119
  }
includes/class-xcloner-scheduler.php CHANGED
@@ -1,438 +1,455 @@
1
  <?php
2
 
3
- class Xcloner_Scheduler {
4
-
5
- private $db;
6
- private $scheduler_table = "xcloner_scheduler";
7
-
8
- private $xcloner_remote_storage;
9
- private $archive_system;
10
- private $xcloner_database;
11
- private $xcloner_settings;
12
- private $logger;
13
- private $xcloner_file_system;
14
- private $xcloner_encryption;
15
- private $xcloner_container;
16
-
17
- private $allowed_schedules = array("hourly", "twicedaily", "daily", "weekly", "monthly");
18
-
19
- /*public function __call($method, $args) {
20
- echo "$method is not defined";
21
- }*/
22
-
23
- public function __construct(Xcloner $xcloner_container) {
24
- global $wpdb;
25
-
26
- $this->db = $wpdb;
27
- $wpdb->show_errors = false;
28
-
29
- $this->xcloner_container = $xcloner_container;
30
- $this->xcloner_settings = $this->xcloner_container->get_xcloner_settings();
31
-
32
- $this->scheduler_table = $this->db->prefix.$this->scheduler_table;
33
- }
34
-
35
- private function get_xcloner_container() {
36
- return $this->xcloner_container;
37
- }
38
-
39
- private function set_xcloner_container(Xcloner $container) {
40
- $this->xcloner_container = $container;
41
- }
42
-
43
- public function get_scheduler_list($return_only_enabled = 0) {
44
- $list = $this->db->get_results("SELECT * FROM ".$this->scheduler_table);
45
-
46
- if ($return_only_enabled) {
47
- $new_list = array();
48
-
49
- foreach ($list as $res) {
50
- if ($res->status) {
51
- $res->next_run_time = wp_next_scheduled('xcloner_scheduler_'.$res->id, array($res->id)) + (get_option('gmt_offset') * HOUR_IN_SECONDS);
52
- $new_list[] = $res;
53
- }
54
- }
55
- $list = $new_list;
56
- }
57
-
58
- return $list;
59
- }
60
-
61
- public function get_next_run_schedule( ) {
62
- $list = $this->get_scheduler_list($return_only_enabled = 1);
63
-
64
- return $list;
65
- }
66
-
67
- public function get_schedule_by_id_object($id) {
68
- $data = $this->db->get_row("SELECT * FROM ".$this->scheduler_table." WHERE id=".$id);
69
-
70
- return $data;
71
- }
72
-
73
- public function get_schedule_by_id($id) {
74
- $data = $this->db->get_row("SELECT * FROM ".$this->scheduler_table." WHERE id=".$id, ARRAY_A);
75
-
76
- if (!$data) {
77
- return false;
78
- }
79
-
80
- $params = json_decode($data['params']);
81
-
82
- //print_r($params);
83
- $data['params'] = "";
84
- $data['backup_params'] = $params->backup_params;
85
- $data['table_params'] = json_encode($params->database);
86
- $data['excluded_files'] = json_encode($params->excluded_files);
87
-
88
- return $data;
89
- }
90
-
91
- public function delete_schedule_by_id($id) {
92
- $hook = 'xcloner_scheduler_'.$id;
93
- wp_clear_scheduled_hook($hook, array($id));
94
-
95
- $data = $this->db->delete($this->scheduler_table, array('id' => $id));
96
-
97
- return $data;
98
- }
99
-
100
- public function deactivate_wp_cron_hooks() {
101
- $list = $this->get_scheduler_list();
102
-
103
- foreach ($list as $schedule) {
104
- $hook = 'xcloner_scheduler_'.$schedule->id;
105
-
106
- if ($timestamp = wp_next_scheduled($hook, array($schedule->id))) {
107
- wp_unschedule_event($timestamp, $hook, array($schedule->id));
108
- }
109
- }
110
- }
111
-
112
- public function update_wp_cron_hooks() {
113
- $list = $this->get_scheduler_list();
114
-
115
- foreach ($list as $schedule) {
116
- $hook = 'xcloner_scheduler_'.$schedule->id;
117
-
118
- //adding the xcloner_scheduler hook with xcloner_scheduler_callback callback
119
- add_action($hook, array($this, 'xcloner_scheduler_callback'), 10, 1);
120
-
121
- if (!wp_next_scheduled($hook, array($schedule->id)) and $schedule->status) {
122
-
123
- if ($schedule->recurrence == "single") {
124
- wp_schedule_single_event(strtotime($schedule->start_at), $hook, array($schedule->id));
125
- } else {
126
- wp_schedule_event(strtotime($schedule->start_at), $schedule->recurrence, $hook, array($schedule->id));
127
- }
128
-
129
- } elseif (!$schedule->status) {
130
- if ($timestamp = wp_next_scheduled($hook, array($schedule->id))) {
131
- wp_unschedule_event($timestamp, $hook, array($schedule->id));
132
- }
133
- }
134
- }
135
-
136
- }
137
-
138
- public function update_cron_hook($id) {
139
- $schedule = $this->get_schedule_by_id_object($id);
140
- $hook = 'xcloner_scheduler_'.$schedule->id;
141
-
142
- if ($timestamp = wp_next_scheduled($hook, array($schedule->id))) {
143
- wp_unschedule_event($timestamp, $hook, array($schedule->id));
144
- }
145
-
146
- if ($schedule->status) {
147
-
148
- if ($schedule->recurrence == "single") {
149
- wp_schedule_single_event(strtotime($schedule->start_at), $hook, array($schedule->id));
150
- } else {
151
- wp_schedule_event(strtotime($schedule->start_at), $schedule->recurrence, $hook, array($schedule->id));
152
- }
153
-
154
- }
155
- }
156
-
157
- public function disable_single_cron($schedule_id) {
158
- $schedule = array();
159
- $hook = 'xcloner_scheduler_'.$schedule_id;
160
-
161
- if ($timestamp = wp_next_scheduled($hook, array($schedule_id))) {
162
- wp_unschedule_event($timestamp, $hook, array($schedule_id));
163
- }
164
-
165
- $schedule['status'] = 0;
166
-
167
- $update = $this->db->update(
168
- $this->scheduler_table,
169
- $schedule,
170
- array('id' => $schedule_id),
171
- array(
172
- '%s',
173
- '%s'
174
- )
175
- );
176
-
177
- return $update;
178
- }
179
-
180
- public function update_hash($schedule_id, $hash) {
181
- $schedule = array();
182
-
183
- $schedule['hash'] = $hash;
184
-
185
- $update = $this->db->update(
186
- $this->scheduler_table,
187
- $schedule,
188
- array('id' => $schedule_id),
189
- array(
190
- '%s',
191
- '%s'
192
- )
193
- );
194
-
195
- return $update;
196
- }
197
-
198
- public function update_last_backup($schedule_id, $last_backup) {
199
- $schedule = array();
200
-
201
- $this->logger->info(sprintf('Updating last backup %s for schedule id #%s', $last_backup, $schedule_id));
202
-
203
- $schedule['last_backup'] = $last_backup;
204
-
205
- $update = $this->db->update(
206
- $this->scheduler_table,
207
- $schedule,
208
- array('id' => $schedule_id),
209
- array(
210
- '%s',
211
- '%s'
212
- )
213
- );
214
-
215
- return $update;
216
- }
217
-
218
- private function _xcloner_scheduler_callback($id, $schedule) {
219
- set_time_limit(0);
220
-
221
-
222
- $xcloner = new XCloner();
223
- $xcloner->init();
224
- $this->set_xcloner_container($xcloner);
225
- $return_encrypted = array();
226
- $return = array();
227
- $additional = array();
228
-
229
- #$hash = $this->xcloner_settings->get_hash();
230
- #$this->get_xcloner_container()->get_xcloner_settings()->set_hash($hash);
231
-
232
- //$this->xcloner_settings = $this->get_xcloner_container()->get_xcloner_settings();
233
- $this->xcloner_file_system = $this->get_xcloner_container()->get_xcloner_filesystem();
234
- $this->xcloner_encryption = $this->get_xcloner_container()->get_xcloner_encryption();
235
- $this->xcloner_database = $this->get_xcloner_container()->get_xcloner_database();
236
- $this->archive_system = $this->get_xcloner_container()->get_archive_system();
237
- $this->logger = $this->get_xcloner_container()->get_xcloner_logger()->withName("xcloner_scheduler");
238
- $this->xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
239
-
240
- $this->logger->info(sprintf("New schedule hash is %s", $this->xcloner_settings->get_hash()));
241
-
242
- if (isset($schedule['backup_params']->diff_start_date) && $schedule['backup_params']->diff_start_date) {
243
- $this->xcloner_file_system->set_diff_timestamp_start($schedule['backup_params']->diff_start_date);
244
- }
245
-
246
- if ($schedule['recurrence'] == "single") {
247
- $this->disable_single_cron($schedule['id']);
248
- }
249
-
250
- if (!$schedule) {
251
- $this->logger->info(sprintf("Could not load schedule with id'%s'", $id), array("CRON"));
252
-
253
- return;
254
- }
255
-
256
- //echo $this->get_xcloner_container()->get_xcloner_settings()->get_hash(); exit;
257
-
258
- $this->update_hash($schedule['id'], $this->xcloner_settings->get_hash());
259
-
260
- $this->logger->info(sprintf("Starting cron schedule '%s'", $schedule['name']), array("CRON"));
261
-
262
- $this->xcloner_file_system->set_excluded_files(json_decode($schedule['excluded_files']));
263
-
264
- $init = 1;
265
- $continue = 1;
266
-
267
- while ($continue) {
268
- $continue = $this->xcloner_file_system->start_file_recursion($init);
269
-
270
- $init = 0;
271
- }
272
-
273
- $this->logger->info(sprintf("File scan finished"), array("CRON"));
274
-
275
- $this->logger->info(sprintf("Starting the database backup"), array("CRON"));
276
-
277
- $init = 1;
278
- $return['finished'] = 0;
279
-
280
- while (!$return['finished']) {
281
- $return = $this->xcloner_database->start_database_recursion((array)json_decode($schedule['table_params']), $return, $init);
282
- $init = 0;
283
- }
284
-
285
- $this->logger->info(sprintf("Database backup done"), array("CRON"));
286
-
287
- $this->logger->info(sprintf("Starting file archive process"), array("CRON"));
288
-
289
- $init = 0;
290
- $return['finished'] = 0;
291
- $return['extra'] = array();
292
-
293
- while (!$return['finished']) {
294
- $return = $this->archive_system->start_incremental_backup((array)$schedule['backup_params'], $return['extra'], $init);
295
- $init = 0;
296
- }
297
- $this->logger->info(sprintf("File archive process FINISHED."), array("CRON"));
298
-
299
- //getting the last backup archive file
300
- $return['extra']['backup_parent'] = $this->archive_system->get_archive_name_with_extension();
301
- if ($this->xcloner_file_system->is_part($this->archive_system->get_archive_name_with_extension())) {
302
- $return['extra']['backup_parent'] = $this->archive_system->get_archive_name_multipart();
303
- }
304
-
305
- //Updating schedule last backup archive
306
- $this->update_last_backup($schedule['id'], $return['extra']['backup_parent']);
307
-
308
- //Encrypting the backup archive
309
- $return_encrypted['finished'] = 0;
310
- $return_encrypted['start'] = 0;
311
- $return_encrypted['iv'] = '';
312
- $return_encrypted['target_file'] = '';
313
- $part = 0;
314
- $backup_parts = array();
315
-
316
- if (isset($schedule['backup_params']->backup_encrypt) && $schedule['backup_params']->backup_encrypt) {
317
- $this->logger->info(sprintf("Encrypting backup archive %s.", $return['extra']['backup_parent']), array("CRON"));
318
-
319
- $backup_file = $return['extra']['backup_parent'];
320
-
321
- if ($this->xcloner_file_system->is_multipart($return['extra']['backup_parent'])) {
322
- $backup_parts = $this->xcloner_file_system->get_multipart_files($return['extra']['backup_parent']);
323
- $backup_file = $backup_parts[$part];
324
- }
325
-
326
- while (!$return_encrypted['finished']) {
327
- $return_encrypted = $this->xcloner_encryption->encrypt_file(
328
- $backup_file,
329
- "",
330
- "",
331
- "",
332
- "",
333
- true,
334
- true
335
- );
336
-
337
- if ($return_encrypted['finished']) {
338
- ++$part;
339
-
340
- if ($part < sizeof($backup_parts)) {
341
- $return_encrypted['finished'] = 0;
342
- $backup_file = $backup_parts[$part];
343
- }
344
- }
345
- }
346
- }
347
-
348
- //Sending backup to remote storage
349
- if (isset($schedule['remote_storage']) && $schedule['remote_storage'] && array_key_exists($schedule['remote_storage'], $this->xcloner_remote_storage->get_available_storages())) {
350
- $backup_file = $return['extra']['backup_parent'];
351
-
352
- $this->logger->info(sprintf("Transferring backup to remote storage %s", strtoupper($schedule['remote_storage'])), array("CRON"));
353
-
354
- if (method_exists($this->xcloner_remote_storage, "upload_backup_to_storage")) {
355
- call_user_func_array(array(
356
- $this->xcloner_remote_storage,
357
- "upload_backup_to_storage"
358
- ), array($backup_file, $schedule['remote_storage']));
359
- }
360
- }
361
-
362
- //Sending email notification
363
- if (isset($schedule['backup_params']->email_notification) and $to = $schedule['backup_params']->email_notification) {
364
- try {
365
- $from = "";
366
- $additional['lines_total'] = $return['extra']['lines_total'];
367
- $subject = sprintf(__("%s - new backup generated %s"), $schedule['name'], $return['extra']['backup_parent']);
368
-
369
- $this->archive_system->send_notification($to, $from, $subject, $return['extra']['backup_parent'], $schedule, "", $additional);
370
-
371
- }catch (Exception $e) {
372
- $this->logger->error($e->getMessage());
373
- }
374
- }
375
-
376
- //CHECK IF WE SHOULD DELETE BACKUP AFTER REMOTE TRANSFER IS DONE
377
- if ($schedule['remote_storage'] && $this->xcloner_settings->get_xcloner_option('xcloner_cleanup_delete_after_remote_transfer')) {
378
- $this->logger->info(sprintf("Deleting %s from local storage matching rule xcloner_cleanup_delete_after_remote_transfer", $return['extra']['backup_parent']));
379
- $this->xcloner_file_system->delete_backup_by_name($return['extra']['backup_parent']);
380
-
381
- }
382
-
383
- //Removing the tmp filesystem used for backup
384
- $this->xcloner_file_system->remove_tmp_filesystem();
385
-
386
- //Backup Storage Cleanup
387
- $this->xcloner_file_system->backup_storage_cleanup();
388
-
389
- //Filesystem Cleanup
390
- $this->xcloner_file_system->cleanup_tmp_directories();
391
- }
392
-
393
- public function xcloner_scheduler_callback($id, $schedule = "") {
394
- if ($id) {
395
- $schedule = $this->get_schedule_by_id($id);
396
- }
397
-
398
- try {
399
- if (get_option('xcloner_disable_email_notification')) {
400
- //we disable email notifications
401
- $schedule['backup_params']->email_notification = "";
402
- }
403
- $this->_xcloner_scheduler_callback($id, $schedule);
404
-
405
- }catch (Exception $e) {
406
-
407
- //send email to site admin if email notification is not set in the scheduler
408
- if (!isset($schedule['backup_params']->email_notification) || !$schedule['backup_params']->email_notification) {
409
- $schedule['backup_params']->email_notification = get_option('admin_email');
410
- }
411
-
412
- if (isset($schedule['backup_params']->email_notification) && $to = $schedule['backup_params']->email_notification) {
413
- $from = "";
414
- $this->archive_system->send_notification($to, $from, $schedule['name']." - backup error", "", "", $e->getMessage());
415
- }
416
-
417
- }
418
-
419
- }
420
-
421
- public function get_available_intervals() {
422
- $schedules = wp_get_schedules();
423
- $new_schedules = array();
424
-
425
- foreach ($schedules as $key => $row) {
426
- if (in_array($key, $this->allowed_schedules)) {
427
- $new_schedules[$key] = $row;
428
- $intervals[$key] = $row['interval'];
429
- }
430
- }
431
-
432
- array_multisort($intervals, SORT_ASC, $new_schedules);
433
-
434
- return $new_schedules;
435
- }
436
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  }
1
  <?php
2
 
3
+ class Xcloner_Scheduler
4
+ {
5
+ private $db;
6
+ private $scheduler_table = "xcloner_scheduler";
7
+
8
+ private $xcloner_remote_storage;
9
+ private $archive_system;
10
+ private $xcloner_database;
11
+ private $xcloner_settings;
12
+ private $logger;
13
+ private $xcloner_file_system;
14
+ private $xcloner_encryption;
15
+ private $xcloner_container;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
+ private $allowed_schedules = array("hourly", "twicedaily", "daily", "weekly", "monthly");
18
+
19
+ /*public function __call($method, $args) {
20
+ echo "$method is not defined";
21
+ }*/
22
+
23
+ public function __construct(Xcloner $xcloner_container)
24
+ {
25
+ //global $wpdb;
26
+
27
+ $this->xcloner_container = $xcloner_container;
28
+ $this->xcloner_database = $this->get_xcloner_container()->get_xcloner_database();
29
+ $this->xcloner_settings = $this->xcloner_container->get_xcloner_settings();
30
+
31
+ $this->db = $this->xcloner_database;
32
+ $this->db->show_errors = false;
33
 
34
+ $this->scheduler_table = $this->xcloner_settings->get_table_prefix().$this->scheduler_table;
35
+ }
36
+
37
+ private function get_xcloner_container()
38
+ {
39
+ return $this->xcloner_container;
40
+ }
41
+
42
+ private function set_xcloner_container(Xcloner $container)
43
+ {
44
+ $this->xcloner_container = $container;
45
+ }
46
+
47
+ public function get_scheduler_list($return_only_enabled = 0)
48
+ {
49
+ $list = $this->db->get_results("SELECT * FROM ".$this->scheduler_table);
50
+
51
+ if ($return_only_enabled) {
52
+ $new_list = array();
53
+
54
+ foreach ($list as $res) {
55
+ if ($res->status) {
56
+ $res->next_run_time = wp_next_scheduled('xcloner_scheduler_'.$res->id, array($res->id)) + ($this->xcloner_settings->get_xcloner_option('gmt_offset') * HOUR_IN_SECONDS);
57
+ $new_list[] = $res;
58
+ }
59
+ }
60
+ $list = $new_list;
61
+ }
62
+
63
+ return $list;
64
+ }
65
+
66
+ public function get_next_run_schedule()
67
+ {
68
+ $list = $this->get_scheduler_list($return_only_enabled = 1);
69
+
70
+ return $list;
71
+ }
72
+
73
+ public function get_schedule_by_id_object($id)
74
+ {
75
+ $data = $this->db->get_row("SELECT * FROM ".$this->scheduler_table." WHERE id=".$id);
76
+
77
+ return $data;
78
+ }
79
+
80
+ public function get_schedule_by_id($id)
81
+ {
82
+ $data = $this->db->get_row("SELECT * FROM ".$this->scheduler_table." WHERE id=".$id, ARRAY_A);
83
+
84
+ if (!$data) {
85
+ return false;
86
+ }
87
+
88
+ $params = json_decode($data['params']);
89
+
90
+ //print_r($params);
91
+ $data['params'] = "";
92
+ $data['backup_params'] = $params->backup_params;
93
+ $data['table_params'] = json_encode($params->database);
94
+ $data['excluded_files'] = json_encode($params->excluded_files);
95
+
96
+ return $data;
97
+ }
98
+
99
+ public function delete_schedule_by_id($id)
100
+ {
101
+ $hook = 'xcloner_scheduler_'.$id;
102
+ wp_clear_scheduled_hook($hook, array($id));
103
+
104
+ $data = $this->db->delete($this->scheduler_table, array('id' => $id));
105
+
106
+ return $data;
107
+ }
108
+
109
+ public function deactivate_wp_cron_hooks()
110
+ {
111
+ $list = $this->get_scheduler_list();
112
+
113
+ foreach ($list as $schedule) {
114
+ $hook = 'xcloner_scheduler_'.$schedule->id;
115
+
116
+ if ($timestamp = wp_next_scheduled($hook, array($schedule->id))) {
117
+ wp_unschedule_event($timestamp, $hook, array($schedule->id));
118
+ }
119
+ }
120
+ }
121
+
122
+ public function update_wp_cron_hooks()
123
+ {
124
+ $list = $this->get_scheduler_list();
125
+
126
+ foreach ($list as $schedule) {
127
+ $hook = 'xcloner_scheduler_'.$schedule->id;
128
+
129
+ //adding the xcloner_scheduler hook with xcloner_scheduler_callback callback
130
+ $this->xcloner_container->get_loader()->add_action($hook, $this, 'xcloner_scheduler_callback', 10, 1);
131
+
132
+ if (!wp_next_scheduled($hook, array($schedule->id)) and $schedule->status) {
133
+ if ($schedule->recurrence == "single") {
134
+ wp_schedule_single_event(strtotime($schedule->start_at), $hook, array($schedule->id));
135
+ } else {
136
+ wp_schedule_event(strtotime($schedule->start_at), $schedule->recurrence, $hook, array($schedule->id));
137
+ }
138
+ } elseif (!$schedule->status) {
139
+ if ($timestamp = wp_next_scheduled($hook, array($schedule->id))) {
140
+ wp_unschedule_event($timestamp, $hook, array($schedule->id));
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ public function update_cron_hook($id)
147
+ {
148
+ $schedule = $this->get_schedule_by_id_object($id);
149
+ $hook = 'xcloner_scheduler_'.$schedule->id;
150
+
151
+ if ($timestamp = wp_next_scheduled($hook, array($schedule->id))) {
152
+ wp_unschedule_event($timestamp, $hook, array($schedule->id));
153
+ }
154
+
155
+ if ($schedule->status) {
156
+ if ($schedule->recurrence == "single") {
157
+ wp_schedule_single_event(strtotime($schedule->start_at), $hook, array($schedule->id));
158
+ } else {
159
+ wp_schedule_event(strtotime($schedule->start_at), $schedule->recurrence, $hook, array($schedule->id));
160
+ }
161
+ }
162
+ }
163
+
164
+ public function disable_single_cron($schedule_id)
165
+ {
166
+ $schedule = array();
167
+ $hook = 'xcloner_scheduler_'.$schedule_id;
168
+
169
+ if ($timestamp = wp_next_scheduled($hook, array($schedule_id))) {
170
+ wp_unschedule_event($timestamp, $hook, array($schedule_id));
171
+ }
172
+
173
+ $schedule['status'] = 0;
174
+
175
+ $update = $this->db->update(
176
+ $this->scheduler_table,
177
+ $schedule,
178
+ array('id' => $schedule_id),
179
+ array(
180
+ '%s',
181
+ '%s'
182
+ )
183
+ );
184
+
185
+ return $update;
186
+ }
187
+
188
+ public function update_hash($schedule_id, $hash)
189
+ {
190
+ $schedule = array();
191
+
192
+ $schedule['hash'] = $hash;
193
+
194
+ $update = $this->db->update(
195
+ $this->scheduler_table,
196
+ $schedule,
197
+ array('id' => $schedule_id),
198
+ array(
199
+ '%s',
200
+ '%s'
201
+ )
202
+ );
203
+
204
+ return $update;
205
+ }
206
+
207
+ public function update_last_backup($schedule_id, $last_backup)
208
+ {
209
+ $schedule = array();
210
+
211
+ $this->logger->info(sprintf('Updating last backup %s for schedule id #%s', $last_backup, $schedule_id));
212
+
213
+ $schedule['last_backup'] = $last_backup;
214
+
215
+ $update = $this->db->update(
216
+ $this->scheduler_table,
217
+ $schedule,
218
+ array('id' => $schedule_id),
219
+ array(
220
+ '%s',
221
+ '%s'
222
+ )
223
+ );
224
+
225
+ return $update;
226
+ }
227
+
228
+ private function _xcloner_scheduler_callback($id, $schedule, $xcloner = "")
229
+ {
230
+ set_time_limit(0);
231
+
232
+ if (!$xcloner) {
233
+ $xcloner = new XCloner();
234
+ $xcloner->init();
235
+ }
236
+ $this->set_xcloner_container($xcloner);
237
+ $return_encrypted = array();
238
+ $return = array();
239
+ $additional = array();
240
+
241
+ #$hash = $this->xcloner_settings->get_hash();
242
+ #$this->get_xcloner_container()->get_xcloner_settings()->set_hash($hash);
243
+
244
+ //$this->xcloner_settings = $this->get_xcloner_container()->get_xcloner_settings();
245
+ $this->xcloner_file_system = $this->get_xcloner_container()->get_xcloner_filesystem();
246
+ $this->xcloner_encryption = $this->get_xcloner_container()->get_xcloner_encryption();
247
+ $this->xcloner_database = $this->get_xcloner_container()->get_xcloner_database();
248
+ $this->archive_system = $this->get_xcloner_container()->get_archive_system();
249
+ $this->logger = $this->get_xcloner_container()->get_xcloner_logger()->withName("xcloner_scheduler");
250
+ $this->xcloner_remote_storage = $this->get_xcloner_container()->get_xcloner_remote_storage();
251
+
252
+
253
+ $this->db = $this->xcloner_database;
254
+ $this->db->show_errors = false;
255
+
256
+ $this->logger->info(sprintf("New schedule hash is %s", $this->xcloner_settings->get_hash()));
257
+
258
+ if (isset($schedule['backup_params']->diff_start_date) && $schedule['backup_params']->diff_start_date) {
259
+ $this->xcloner_file_system->set_diff_timestamp_start($schedule['backup_params']->diff_start_date);
260
+ }
261
+
262
+
263
+ if ($schedule['recurrence'] == "single") {
264
+ $this->disable_single_cron($schedule['id']);
265
+ }
266
+
267
+ if (!$schedule) {
268
+ $this->logger->info(sprintf("Could not load schedule with id'%s'", $id), array("CRON"));
269
+
270
+ return;
271
+ }
272
+
273
+ //echo $this->get_xcloner_container()->get_xcloner_settings()->get_hash(); exit;
274
+ if (!$xcloner) {
275
+ //we update this only in WP mode
276
+ $this->update_hash($schedule['id'], $this->xcloner_settings->get_hash());
277
+ }
278
+
279
+ $this->logger->info(sprintf("Starting cron schedule '%s'", $schedule['name']), array("CRON"));
280
+
281
+ $this->xcloner_file_system->set_excluded_files(json_decode($schedule['excluded_files']));
282
+
283
+ $init = 1;
284
+ $continue = 1;
285
+
286
+ while ($continue) {
287
+ $continue = $this->xcloner_file_system->start_file_recursion($init);
288
+
289
+ $init = 0;
290
+ }
291
+
292
+ $this->logger->info(sprintf("File scan finished"), array("CRON"));
293
+
294
+ $this->logger->info(sprintf("Starting the database backup"), array("CRON"));
295
+
296
+ $init = 1;
297
+ $return['finished'] = 0;
298
+
299
+ while (!$return['finished']) {
300
+ $return = $this->xcloner_database->start_database_recursion((array)json_decode($schedule['table_params']), $return, $init);
301
+ $init = 0;
302
+ }
303
+
304
+ $this->logger->info(sprintf("Database backup done"), array("CRON"));
305
+
306
+ $this->logger->info(sprintf("Starting file archive process"), array("CRON"));
307
+
308
+ $init = 0;
309
+ $return['finished'] = 0;
310
+ $return['extra'] = array();
311
+
312
+ while (!$return['finished']) {
313
+ $return = $this->archive_system->start_incremental_backup((array)$schedule['backup_params'], $return['extra'], $init);
314
+ $init = 0;
315
+ }
316
+ $this->logger->info(sprintf("File archive process FINISHED."), array("CRON"));
317
+
318
+ //getting the last backup archive file
319
+ $return['extra']['backup_parent'] = $this->archive_system->get_archive_name_with_extension();
320
+ if ($this->xcloner_file_system->is_part($this->archive_system->get_archive_name_with_extension())) {
321
+ $return['extra']['backup_parent'] = $this->archive_system->get_archive_name_multipart();
322
+ }
323
+
324
+ //Updating schedule last backup archive
325
+ $this->update_last_backup($schedule['id'], $return['extra']['backup_parent']);
326
+
327
+ //Encrypting the backup archive
328
+ $return_encrypted['finished'] = 0;
329
+ $return_encrypted['start'] = 0;
330
+ $return_encrypted['iv'] = '';
331
+ $return_encrypted['target_file'] = '';
332
+ $part = 0;
333
+ $backup_parts = array();
334
+
335
+ if (isset($schedule['backup_params']->backup_encrypt) && $schedule['backup_params']->backup_encrypt) {
336
+ $this->logger->info(sprintf("Encrypting backup archive %s.", $return['extra']['backup_parent']), array("CRON"));
337
+
338
+ $backup_file = $return['extra']['backup_parent'];
339
+
340
+ if ($this->xcloner_file_system->is_multipart($return['extra']['backup_parent'])) {
341
+ $backup_parts = $this->xcloner_file_system->get_multipart_files($return['extra']['backup_parent']);
342
+ $backup_file = $backup_parts[$part];
343
+ }
344
+
345
+ while (!$return_encrypted['finished']) {
346
+ $return_encrypted = $this->xcloner_encryption->encrypt_file(
347
+ $backup_file,
348
+ "",
349
+ "",
350
+ "",
351
+ "",
352
+ true,
353
+ true
354
+ );
355
+
356
+ if ($return_encrypted['finished']) {
357
+ ++$part;
358
+
359
+ if ($part < sizeof($backup_parts)) {
360
+ $return_encrypted['finished'] = 0;
361
+ $backup_file = $backup_parts[$part];
362
+ }
363
+ }
364
+ }
365
+ }
366
+
367
+ //Sending backup to remote storage
368
+ if (isset($schedule['remote_storage']) && $schedule['remote_storage'] && array_key_exists($schedule['remote_storage'], $this->xcloner_remote_storage->get_available_storages())) {
369
+ $backup_file = $return['extra']['backup_parent'];
370
+
371
+ $this->logger->info(sprintf("Transferring backup to remote storage %s", strtoupper($schedule['remote_storage'])), array("CRON"));
372
+
373
+ if (method_exists($this->xcloner_remote_storage, "upload_backup_to_storage")) {
374
+ call_user_func_array(array(
375
+ $this->xcloner_remote_storage,
376
+ "upload_backup_to_storage"
377
+ ), array($backup_file, $schedule['remote_storage']));
378
+ }
379
+ }
380
+
381
+ //Sending email notification
382
+ if (isset($schedule['backup_params']->email_notification) and $to = $schedule['backup_params']->email_notification) {
383
+ try {
384
+ $from = "";
385
+ $additional['lines_total'] = $return['extra']['lines_total'];
386
+ $subject = sprintf(__("%s - new backup generated %s"), $schedule['name'], $return['extra']['backup_parent']);
387
+
388
+ $this->archive_system->send_notification($to, $from, $subject, $return['extra']['backup_parent'], $schedule, "", $additional);
389
+ } catch (Exception $e) {
390
+ $this->logger->error($e->getMessage());
391
+ }
392
+ }
393
+
394
+ //CHECK IF WE SHOULD DELETE BACKUP AFTER REMOTE TRANSFER IS DONE
395
+ if ($schedule['remote_storage'] && $this->xcloner_settings->get_xcloner_option('xcloner_cleanup_delete_after_remote_transfer')) {
396
+ $this->logger->info(sprintf("Deleting %s from local storage matching rule xcloner_cleanup_delete_after_remote_transfer", $return['extra']['backup_parent']));
397
+ $this->xcloner_file_system->delete_backup_by_name($return['extra']['backup_parent']);
398
+ }
399
+
400
+ //Backup Storage Cleanup
401
+ $this->xcloner_file_system->backup_storage_cleanup();
402
+
403
+ //Filesystem Cleanup
404
+ $this->xcloner_file_system->cleanup_tmp_directories();
405
+
406
+ //Removing the tmp filesystem used for backup
407
+ $this->xcloner_file_system->remove_tmp_filesystem();
408
+
409
+ return $return;
410
+ }
411
+
412
+ public function xcloner_scheduler_callback($id, $schedule = "", $xcloner = "")
413
+ {
414
+ if ($id) {
415
+ $schedule = $this->get_schedule_by_id($id);
416
+ }
417
+
418
+ try {
419
+ if ($this->xcloner_settings->get_xcloner_option('xcloner_disable_email_notification')) {
420
+ //we disable email notifications
421
+ $schedule['backup_params']->email_notification = "";
422
+ }
423
+
424
+ return $this->_xcloner_scheduler_callback($id, $schedule, $xcloner);
425
+ } catch (Exception $e) {
426
+
427
+ //send email to site admin if email notification is not set in the scheduler
428
+ if (!isset($schedule['backup_params']->email_notification) || !$schedule['backup_params']->email_notification) {
429
+ $schedule['backup_params']->email_notification = $this->xcloner_settings->get_xcloner_option('admin_email');
430
+ }
431
+
432
+ if (isset($schedule['backup_params']->email_notification) && $to = $schedule['backup_params']->email_notification) {
433
+ $from = "";
434
+ $this->archive_system->send_notification($to, $from, $schedule['name']." - backup error", "", "", $e->getMessage());
435
+ }
436
+ }
437
+ }
438
+
439
+ public function get_available_intervals()
440
+ {
441
+ $schedules = wp_get_schedules();
442
+ $new_schedules = array();
443
+
444
+ foreach ($schedules as $key => $row) {
445
+ if (in_array($key, $this->allowed_schedules)) {
446
+ $new_schedules[$key] = $row;
447
+ $intervals[$key] = $row['interval'];
448
+ }
449
+ }
450
+
451
+ array_multisort($intervals, SORT_ASC, $new_schedules);
452
+
453
+ return $new_schedules;
454
+ }
455
  }
includes/class-xcloner-settings.php CHANGED
@@ -1,765 +1,967 @@
1
  <?php
2
 
3
- class Xcloner_Settings {
4
- private $logger_file = "xcloner_main_%s.log";
5
- private $logger_file_hash = "xcloner%s.log";
6
- private $hash;
7
- private $xcloner_sanitization;
8
- private $xcloner_container;
9
-
10
- public function __construct(Xcloner $xcloner_container, $hash = "") {
11
- $this->xcloner_container = $xcloner_container;
12
- if (isset($hash)) {
13
- $this->set_hash($hash);
14
- }
15
- }
16
-
17
- private function get_xcloner_container() {
18
- return $this->xcloner_container;
19
- }
20
-
21
- public function get_logger_filename($include_hash = 0) {
22
- if ($include_hash) {
23
- $filename = sprintf($this->logger_file_hash, $this->get_hash());
24
- } else {
25
- $filename = sprintf($this->logger_file, $this->get_server_unique_hash(5));
26
- }
27
-
28
- return $filename;
29
- }
30
-
31
- public function get_xcloner_start_path() {
32
- if (!get_option('xcloner_start_path') or !is_dir(/** @scrutinizer ignore-type */get_option('xcloner_start_path'))) {
33
- $path = realpath(ABSPATH);
34
- } else {
35
- $path = get_option('xcloner_start_path');
36
- }
37
-
38
- return $path;
39
- }
40
-
41
- public function get_xcloner_dir_path($dir) {
42
- $path = $this->get_xcloner_start_path().DS.$dir;
43
-
44
- return $path;
45
- }
46
-
47
- public function get_xcloner_store_path() {
48
- if (!get_option('xcloner_store_path') or !is_dir(/** @scrutinizer ignore-type */get_option('xcloner_store_path'))) {
49
- $this->xcloner_container->check_dependencies();
50
- } else {
51
- $path = get_option('xcloner_store_path');
52
- }
53
-
54
- return $path;
55
- }
56
-
57
- public function get_xcloner_encryption_key() {
58
-
59
- if (!get_option('xcloner_encryption_key'))
60
- {
61
- $key = $this->xcloner_container->randomString(35);
62
- update_option('xcloner_encryption_key', $key);
63
- }
64
-
65
- return get_option('xcloner_encryption_key');
66
- }
67
-
68
- /**
69
- * Create a random string
70
- * @author XEWeb <>
71
- * @param $length the length of the string to create
72
- * @return string
73
- */
74
- /*public function randomString($length = 6) {
75
- $str = "";
76
- $characters = array_merge(range('A', 'Z'), range('a', 'z'), range('0', '9'));
77
- $max = count($characters) - 1;
78
- for ($i = 0; $i < $length; $i++) {
79
- $rand = mt_rand(0, $max);
80
- $str .= $characters[$rand];
81
- }
82
- return $str;
83
- }*/
84
-
85
- public function get_xcloner_tmp_path_suffix() {
86
- return "xcloner".$this->get_hash();
87
- }
88
-
89
-
90
- public function get_xcloner_tmp_path($suffix = true) {
91
- if (get_option('xcloner_force_tmp_path_site_root')) {
92
- $path = $this->get_xcloner_store_path();
93
- } else {
94
-
95
- $path = sys_get_temp_dir();
96
- if (!is_dir($path)) {
97
- try {
98
- mkdir($path);
99
- chmod($path, 0777);
100
- }catch(Exception $e){
101
- //silent catch
102
- }
103
- }
104
-
105
- if (!is_dir($path) or !is_writeable($path)) {
106
- $path = $this->get_xcloner_store_path();
107
- }
108
- }
109
-
110
- if ($suffix) {
111
- $path = $path.DS.".".$this->get_xcloner_tmp_path_suffix();
112
- }
113
-
114
- return $path;
115
- }
116
-
117
- public function get_enable_mysql_backup() {
118
- if (get_option('xcloner_enable_mysql_backup')) {
119
- return true;
120
- }
121
-
122
- return false;
123
- }
124
-
125
- public function get_backup_extension_name($ext = "") {
126
- if (!$ext) {
127
- if (get_option('xcloner_backup_compression_level')) {
128
- $ext = ".tgz";
129
- } else {
130
- $ext = ".tar";
131
- }
132
- }
133
-
134
- return ($this->get_hash()).$ext;
135
- }
136
-
137
- public function get_hash() {
138
- if (!$this->hash) {
139
- $this->set_hash("-".$this->get_server_unique_hash(5));
140
- }
141
-
142
- //echo $this->hash;
143
- return $this->hash;
144
- }
145
-
146
- public function generate_new_hash() {
147
- $hash = "-".md5(rand());
148
-
149
- $this->set_hash(substr($hash, 0, 6));
150
-
151
- return $hash;
152
- }
153
-
154
- public function set_hash($hash = "") {
155
- if (substr($hash, 0, 1) != "-" and strlen($hash)) {
156
- $hash = "-".$hash;
157
- }
158
-
159
- $this->hash = substr($hash, 0, 6);
160
-
161
- return $this;
162
- }
163
-
164
- public function get_default_backup_name() {
165
- $data = parse_url(get_site_url());
166
-
167
- $backup_name = "backup_[domain]".(isset($data['port']) ? "_".$data['port'] : "")."-[time]-".($this->get_enable_mysql_backup() ? "sql" : "nosql");
168
-
169
- return $backup_name;
170
- }
171
-
172
- public function get_db_hostname() {
173
- global $wpdb;
174
-
175
- if (!$data = get_option('xcloner_mysql_hostname')) {
176
- $data = $wpdb->dbhost;
177
- }
178
-
179
- return $data;
180
- }
181
-
182
- public function get_db_username() {
183
- global $wpdb;
184
-
185
- if (!$data = get_option('xcloner_mysql_username')) {
186
- $data = $wpdb->dbuser;
187
- }
188
-
189
- return $data;
190
- }
191
-
192
- public function get_db_password() {
193
- global $wpdb;
194
-
195
- if (!$data = get_option('xcloner_mysql_password')) {
196
- $data = $wpdb->dbpassword;
197
- }
198
-
199
- return $data;
200
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
- public function get_db_database() {
203
- global $wpdb;
204
 
205
- if (!$data = get_option('xcloner_mysql_database')) {
206
- $data = $wpdb->dbname;
207
- }
208
-
209
- return $data;
210
- }
211
-
212
- public function get_table_prefix() {
213
- global $wpdb;
214
-
215
- return $wpdb->prefix;
216
- }
217
-
218
- /**
219
- * @param string $option
220
- */
221
- public function get_xcloner_option($option) {
222
- $data = get_option($option);
223
-
224
- return $data;
225
- }
226
-
227
- public function get_server_unique_hash($strlen = 0) {
228
- $hash = md5(get_home_url().__DIR__.$this->get_xcloner_encryption_key());
229
-
230
- if ($strlen) {
231
- $hash = substr($hash, 0, $strlen);
232
- }
233
-
234
- return $hash;
235
- }
236
-
237
- public function settings_init() {
238
- global $wpdb;
239
- $this->xcloner_sanitization = $this->get_xcloner_container()->get_xcloner_sanitization();
240
-
241
- //ADDING MISSING OPTIONS
242
- if (false == get_option('xcloner_mysql_settings_page')) {
243
- add_option('xcloner_mysql_settings_page');
244
- } // end if
245
-
246
- if (false == get_option('xcloner_cron_settings_page')) {
247
- add_option('xcloner_cron_settings_page');
248
- } // end if
249
-
250
- if (false == get_option('xcloner_system_settings_page')) {
251
- add_option('xcloner_system_settings_page');
252
- } // end if
253
-
254
- if (false == get_option('xcloner_cleanup_settings_page')) {
255
- add_option('xcloner_cleanup_settings_page');
256
- } // end if
257
-
258
-
259
- //ADDING SETTING SECTIONS
260
- //GENERAL section
261
- add_settings_section(
262
- 'xcloner_general_settings_group',
263
- __(' '),
264
- array($this, 'xcloner_settings_section_cb'),
265
- 'xcloner_settings_page'
266
- );
267
- //MYSQL section
268
- add_settings_section(
269
- 'xcloner_mysql_settings_group',
270
- __(' '),
271
- array($this, 'xcloner_settings_section_cb'),
272
- 'xcloner_mysql_settings_page'
273
- );
274
-
275
- //SYSTEM section
276
- add_settings_section(
277
- 'xcloner_system_settings_group',
278
- __('These are advanced options recommended for developers!', 'xcloner-backup-and-restore'),
279
- array($this, 'xcloner_settings_section_cb'),
280
- 'xcloner_system_settings_page'
281
- );
282
-
283
- //CLEANUP section
284
- add_settings_section(
285
- 'xcloner_cleanup_settings_group',
286
- __(' '),
287
- array($this, 'xcloner_settings_section_cb'),
288
- 'xcloner_cleanup_settings_page'
289
- );
290
-
291
-
292
- //CRON section
293
- add_settings_section(
294
- 'xcloner_cron_settings_group',
295
- __(' '),
296
- array($this, 'xcloner_settings_section_cb'),
297
- 'xcloner_cron_settings_page'
298
- );
299
-
300
-
301
- //REGISTERING THE 'GENERAL SECTION' FIELDS
302
- register_setting('xcloner_general_settings_group', 'xcloner_backup_compression_level', array(
303
- $this->xcloner_sanitization,
304
- "sanitize_input_as_int"
305
- ));
306
- add_settings_field(
307
- 'xcloner_backup_compression_level',
308
- __('Backup Compression Level', 'xcloner-backup-and-restore'),
309
- array($this, 'do_form_range_field'),
310
- 'xcloner_settings_page',
311
- 'xcloner_general_settings_group',
312
- array(
313
- 'xcloner_backup_compression_level',
314
- __('Options between [0-9]. Value 0 means no compression, while 9 is maximum compression affecting cpu load', 'xcloner-backup-and-restore'),
315
- 0,
316
- 9
317
- )
318
- );
319
-
320
- register_setting('xcloner_general_settings_group', 'xcloner_start_path', array(
321
- $this->xcloner_sanitization,
322
- "sanitize_input_as_absolute_path"
323
- ));
324
- add_settings_field(
325
- 'xcloner_start_path',
326
- __('Backup Start Location', 'xcloner-backup-and-restore'),
327
- array($this, 'do_form_text_field'),
328
- 'xcloner_settings_page',
329
- 'xcloner_general_settings_group',
330
- array(
331
- 'xcloner_start_path',
332
- __('Base path location from where XCloner can start the Backup.', 'xcloner-backup-and-restore'),
333
- $this->get_xcloner_start_path(),
334
- //'disabled'
335
- )
336
- );
337
-
338
- register_setting('xcloner_general_settings_group', 'xcloner_store_path', array(
339
- $this->xcloner_sanitization,
340
- "sanitize_input_as_absolute_path"
341
- ));
342
- add_settings_field(
343
- 'xcloner_store_path',
344
- __('Backup Storage Location', 'xcloner-backup-and-restore'),
345
- array($this, 'do_form_text_field'),
346
- 'xcloner_settings_page',
347
- 'xcloner_general_settings_group',
348
- array(
349
- 'xcloner_store_path',
350
- __('Location where XCloner will store the Backup archives.', 'xcloner-backup-and-restore'),
351
- $this->get_xcloner_store_path(),
352
- //'disabled'
353
- )
354
- );
355
-
356
- register_setting('xcloner_general_settings_group', 'xcloner_encryption_key', array(
357
- $this->xcloner_sanitization,
358
- "sanitize_input_as_string"
359
- ));
360
- add_settings_field(
361
- 'xcloner_encryption_key',
362
- __('Backup Encryption Key', 'xcloner-backup-and-restore'),
363
- array($this, 'do_form_text_field'),
364
- 'xcloner_settings_page',
365
- 'xcloner_general_settings_group',
366
- array(
367
- 'xcloner_encryption_key',
368
- __('Backup Encryption Key used to Encrypt/Decrypt backups, you might want to save this somewhere else as well.', 'xcloner-backup-and-restore'),
369
- $this->get_xcloner_encryption_key(),
370
- //'disabled'
371
- )
372
- );
373
-
374
- register_setting('xcloner_general_settings_group', 'xcloner_enable_log', array(
375
- $this->xcloner_sanitization,
376
- "sanitize_input_as_int"
377
- ));
378
- add_settings_field(
379
- 'xcloner_enable_log',
380
- __('Enable XCloner Backup Log', 'xcloner-backup-and-restore'),
381
- array($this, 'do_form_switch_field'),
382
- 'xcloner_settings_page',
383
- 'xcloner_general_settings_group',
384
- array(
385
- 'xcloner_enable_log',
386
- sprintf(__('Enable the XCloner Backup log. You will find it stored unde the Backup Storage Location, file %s', 'xcloner-backup-and-restore'), $this->get_logger_filename())
387
- )
388
- );
389
-
390
- register_setting('xcloner_general_settings_group', 'xcloner_enable_pre_update_backup', array(
391
- $this->xcloner_sanitization,
392
- "sanitize_input_as_int"
393
- ));
394
- add_settings_field(
395
- 'xcloner_enable_pre_update_backup',
396
- __('Generate Backups before Automatic WP Upgrades', 'xcloner-backup-and-restore'),
397
- array($this, 'do_form_switch_field'),
398
- 'xcloner_settings_page',
399
- 'xcloner_general_settings_group',
400
- array(
401
- 'xcloner_enable_pre_update_backup',
402
- sprintf(__('Attempt to generate a core, plugins, themes or languages files backup before the automatic update of Wordpress core, plugins, themes or languages files.', 'xcloner-backup-and-restore'), $this->get_logger_filename())
403
- )
404
- );
405
-
406
- register_setting('xcloner_general_settings_group', 'xcloner_regex_exclude', array(
407
- $this->xcloner_sanitization,
408
- "sanitize_input_as_raw"
409
- ));
410
- add_settings_field(
411
- 'xcloner_regex_exclude',
412
- __('Regex Exclude Files', 'xcloner-backup-and-restore'),
413
- array($this, 'do_form_textarea_field'),
414
- 'xcloner_settings_page',
415
- 'xcloner_general_settings_group',
416
- array(
417
- 'xcloner_regex_exclude',
418
- __('Regular expression match to exclude files and folders, example patterns provided below, one pattern per line', 'xcloner-backup-and-restore'),
419
- //$this->get_xcloner_store_path(),
420
- //'disabled'
421
- )
422
- );
423
-
424
- //REGISTERING THE 'MYSQL SECTION' FIELDS
425
- register_setting('xcloner_mysql_settings_group', 'xcloner_enable_mysql_backup', array(
426
- $this->xcloner_sanitization,
427
- "sanitize_input_as_int"
428
- ));
429
- add_settings_field(
430
- 'xcloner_enable_mysql_backup',
431
- __('Enable Mysql Backup', 'xcloner-backup-and-restore'),
432
- array($this, 'do_form_switch_field'),
433
- 'xcloner_mysql_settings_page',
434
- 'xcloner_mysql_settings_group',
435
- array(
436
- 'xcloner_enable_mysql_backup',
437
- __('Enable Mysql Backup Option. If you don\'t want to backup the database, you can disable this.', 'xcloner-backup-and-restore')
438
- )
439
- );
440
-
441
- register_setting('xcloner_mysql_settings_group', 'xcloner_backup_only_wp_tables');
442
- add_settings_field(
443
- 'xcloner_backup_only_wp_tables',
444
- __('Backup only WP tables', 'xcloner-backup-and-restore'),
445
- array($this, 'do_form_switch_field'),
446
- 'xcloner_mysql_settings_page',
447
- 'xcloner_mysql_settings_group',
448
- array(
449
- 'xcloner_backup_only_wp_tables',
450
- sprintf(__('Enable this if you only want to Backup only tables starting with \'%s\' prefix', 'xcloner-backup-and-restore'), $this->get_table_prefix())
451
- )
452
- );
453
-
454
- register_setting('xcloner_mysql_settings_group', 'xcloner_mysql_hostname', array(
455
- $this->xcloner_sanitization,
456
- "sanitize_input_as_raw"
457
- ));
458
- add_settings_field(
459
- 'xcloner_mysql_hostname',
460
- __('Mysql Hostname', 'xcloner-backup-and-restore'),
461
- array($this, 'do_form_text_field'),
462
- 'xcloner_mysql_settings_page',
463
- 'xcloner_mysql_settings_group',
464
- array(
465
- 'xcloner_mysql_hostname',
466
- __('Wordpress mysql hostname', 'xcloner-backup-and-restore'),
467
- $this->get_db_hostname(),
468
- 'disabled'
469
- )
470
- );
471
-
472
- register_setting('xcloner_mysql_settings_group', 'xcloner_mysql_username', array(
473
- $this->xcloner_sanitization,
474
- "sanitize_input_as_raw"
475
- ));
476
- add_settings_field(
477
- 'xcloner_mysql_username',
478
- __('Mysql Username', 'xcloner-backup-and-restore'),
479
- array($this, 'do_form_text_field'),
480
- 'xcloner_mysql_settings_page',
481
- 'xcloner_mysql_settings_group',
482
- array(
483
- 'xcloner_mysql_username',
484
- __('Wordpress mysql username', 'xcloner-backup-and-restore'),
485
- $this->get_db_username(),
486
- 'disabled'
487
- )
488
- );
489
-
490
- register_setting('xcloner_mysql_settings_group', 'xcloner_mysql_database', array(
491
- $this->xcloner_sanitization,
492
- "sanitize_input_as_raw"
493
- ));
494
- add_settings_field(
495
- 'xcloner_mysql_database',
496
- __('Mysql Database', 'xcloner-backup-and-restore'),
497
- array($this, 'do_form_text_field'),
498
- 'xcloner_mysql_settings_page',
499
- 'xcloner_mysql_settings_group',
500
- array(
501
- 'xcloner_mysql_database',
502
- __('Wordpress mysql database', 'xcloner-backup-and-restore'),
503
- $this->get_db_database(),
504
- 'disabled'
505
- )
506
- );
507
-
508
- //REGISTERING THE 'SYSTEM SECTION' FIELDS
509
- register_setting('xcloner_system_settings_group', 'xcloner_size_limit_per_request', array(
510
- $this->xcloner_sanitization,
511
- "sanitize_input_as_int"
512
- ));
513
- add_settings_field(
514
- 'xcloner_size_limit_per_request',
515
- __('Data Size Limit Per Request', 'xcloner-backup-and-restore'),
516
- array($this, 'do_form_range_field'),
517
- 'xcloner_system_settings_page',
518
- 'xcloner_system_settings_group',
519
- array(
520
- 'xcloner_size_limit_per_request',
521
- __('Use this option to set how much file data can XCloner backup in one AJAX request. Range 0-1024 MB', 'xcloner-backup-and-restore'),
522
- 0,
523
- 1024
524
- )
525
- );
526
-
527
- register_setting('xcloner_system_settings_group', 'xcloner_files_to_process_per_request', array(
528
- $this->xcloner_sanitization,
529
- "sanitize_input_as_int"
530
- ));
531
- add_settings_field(
532
- 'xcloner_files_to_process_per_request',
533
- __('Files To Process Per Request', 'xcloner-backup-and-restore'),
534
- array($this, 'do_form_range_field'),
535
- 'xcloner_system_settings_page',
536
- 'xcloner_system_settings_group',
537
- array(
538
- 'xcloner_files_to_process_per_request',
539
- __('Use this option to set how many files XCloner should process at one time before doing another AJAX call', 'xcloner-backup-and-restore'),
540
- 0,
541
- 1000
542
- )
543
- );
544
-
545
- register_setting('xcloner_system_settings_group', 'xcloner_directories_to_scan_per_request', array(
546
- $this->xcloner_sanitization,
547
- "sanitize_input_as_int"
548
- ));
549
- add_settings_field(
550
- 'xcloner_directories_to_scan_per_request',
551
- __('Directories To Scan Per Request', 'xcloner-backup-and-restore'),
552
- array($this, 'do_form_range_field'),
553
- 'xcloner_system_settings_page',
554
- 'xcloner_system_settings_group',
555
- array(
556
- 'xcloner_directories_to_scan_per_request',
557
- __('Use this option to set how many directories XCloner should scan at one time before doing another AJAX call', 'xcloner-backup-and-restore'),
558
- 0,
559
- 1000
560
- )
561
- );
562
-
563
- register_setting('xcloner_system_settings_group', 'xcloner_database_records_per_request', array(
564
- $this->xcloner_sanitization,
565
- "sanitize_input_as_int"
566
- ));
567
- add_settings_field(
568
- 'xcloner_database_records_per_request',
569
- __('Database Records Per Request', 'xcloner-backup-and-restore'),
570
- array($this, 'do_form_range_field'),
571
- 'xcloner_system_settings_page',
572
- 'xcloner_system_settings_group',
573
- array(
574
- 'xcloner_database_records_per_request',
575
- __('Use this option to set how many database table records should be fetched per AJAX request, or set to 0 to fetch all. Range 0-100000 records', 'xcloner-backup-and-restore'),
576
- 0,
577
- 100000
578
- )
579
- );
580
-
581
- /*register_setting('xcloner_system_settings_group', 'xcloner_diff_backup_recreate_period', array($this->xcloner_sanitization, "sanitize_input_as_int"));
582
- add_settings_field(
583
- 'xcloner_diff_backup_recreate_period',
584
- __('Differetial Backups Max Days','xcloner-backup-and-restore'),
585
- array($this, 'do_form_number_field'),
586
- 'xcloner_system_settings_page',
587
- 'xcloner_system_settings_group',
588
- array('xcloner_diff_backup_recreate_period',
589
- __('Use this option to set when a full backup should be recreated if the scheduled backup type is set to \'Full Backup+Differential Backups\' ','xcloner-backup-and-restore'),
590
- )
591
- );*/
592
-
593
- register_setting('xcloner_system_settings_group', 'xcloner_exclude_files_larger_than_mb', array(
594
- $this->xcloner_sanitization,
595
- "sanitize_input_as_int"
596
- ));
597
- add_settings_field(
598
- 'xcloner_exclude_files_larger_than_mb',
599
- __('Exclude files larger than (MB)', 'xcloner-backup-and-restore'),
600
- array($this, 'do_form_number_field'),
601
- 'xcloner_system_settings_page',
602
- 'xcloner_system_settings_group',
603
- array(
604
- 'xcloner_exclude_files_larger_than_mb',
605
- __('Use this option to automatically exclude files larger than a certain size in MB, or set to 0 to include all. Range 0-1000 MB', 'xcloner-backup-and-restore'),
606
- )
607
- );
608
-
609
- register_setting('xcloner_system_settings_group', 'xcloner_split_backup_limit', array(
610
- $this->xcloner_sanitization,
611
- "sanitize_input_as_int"
612
- ));
613
- add_settings_field(
614
- 'xcloner_split_backup_limit',
615
- __('Split Backup Archive Limit (MB)', 'xcloner-backup-and-restore'),
616
- array($this, 'do_form_number_field'),
617
- 'xcloner_system_settings_page',
618
- 'xcloner_system_settings_group',
619
- array(
620
- 'xcloner_split_backup_limit',
621
- __('Use this option to automatically split the backup archive into smaller parts. Range 0-10000 MB', 'xcloner-backup-and-restore'),
622
- )
623
- );
624
-
625
- register_setting('xcloner_system_settings_group', 'xcloner_force_tmp_path_site_root');
626
- add_settings_field(
627
- 'xcloner_force_tmp_path_site_root',
628
- __('Force Temporary Path Within XCloner Storage', 'xcloner-backup-and-restore'),
629
- array($this, 'do_form_switch_field'),
630
- 'xcloner_system_settings_page',
631
- 'xcloner_system_settings_group',
632
- array(
633
- 'xcloner_force_tmp_path_site_root',
634
- sprintf(__('Enable this option if you want the XCloner Temporary Path to be within your XCloner Storage Location', 'xcloner-backup-and-restore'), $this->get_table_prefix())
635
- )
636
- );
637
-
638
- register_setting('xcloner_system_settings_group', 'xcloner_disable_email_notification');
639
- add_settings_field(
640
- 'xcloner_disable_email_notification',
641
- __('Disable Email Notifications', 'xcloner-backup-and-restore'),
642
- array($this, 'do_form_switch_field'),
643
- 'xcloner_system_settings_page',
644
- 'xcloner_system_settings_group',
645
- array(
646
- 'xcloner_disable_email_notification',
647
- sprintf(__('Enable this option if you want the XCloner to NOT send email notifications on successful backups', 'xcloner-backup-and-restore'), $this->get_table_prefix())
648
- )
649
- );
650
-
651
- //REGISTERING THE 'CLEANUP SECTION' FIELDS
652
- register_setting('xcloner_cleanup_settings_group', 'xcloner_cleanup_retention_limit_days', array(
653
- $this->xcloner_sanitization,
654
- "sanitize_input_as_int"
655
- ));
656
- add_settings_field(
657
- 'xcloner_cleanup_retention_limit_days',
658
- __('Cleanup by Date(days)', 'xcloner-backup-and-restore'),
659
- array($this, 'do_form_number_field'),
660
- 'xcloner_cleanup_settings_page',
661
- 'xcloner_cleanup_settings_group',
662
- array(
663
- 'xcloner_cleanup_retention_limit_days',
664
- __('Specify the maximum number of days a backup archive can be kept on the server. 0 disables this option', 'xcloner-backup-and-restore')
665
- )
666
- );
667
-
668
- register_setting('xcloner_cleanup_settings_group', 'xcloner_cleanup_retention_limit_archives', array(
669
- $this->xcloner_sanitization,
670
- "sanitize_input_as_int"
671
- ));
672
- add_settings_field(
673
- 'xcloner_cleanup_retention_limit_archives',
674
- __('Cleanup by Quantity', 'xcloner-backup-and-restore'),
675
- array($this, 'do_form_number_field'),
676
- 'xcloner_cleanup_settings_page',
677
- 'xcloner_cleanup_settings_group',
678
- array(
679
- 'xcloner_cleanup_retention_limit_archives',
680
- __('Specify the maximum number of backup archives to keep on the server. 0 disables this option', 'xcloner-backup-and-restore')
681
- )
682
- );
683
-
684
- register_setting('xcloner_cleanup_settings_group', 'xcloner_cleanup_capacity_limit', array(
685
- $this->xcloner_sanitization,
686
- "sanitize_input_as_int"
687
- ));
688
- add_settings_field(
689
- 'xcloner_cleanup_capacity_limit',
690
- __('Cleanup by Capacity(MB)', 'xcloner-backup-and-restore'),
691
- array($this, 'do_form_number_field'),
692
- 'xcloner_cleanup_settings_page',
693
- 'xcloner_cleanup_settings_group',
694
- array(
695
- 'xcloner_cleanup_capacity_limit',
696
- __('Remove oldest backups if all created backups exceed the configured limit in Megabytes. 0 disables this option', 'xcloner-backup-and-restore')
697
- )
698
- );
699
-
700
- register_setting('xcloner_cleanup_settings_group', 'xcloner_cleanup_delete_after_remote_transfer', array(
701
- $this->xcloner_sanitization,
702
- "sanitize_input_as_int"
703
- ));
704
- add_settings_field(
705
- 'xcloner_cleanup_delete_after_remote_transfer',
706
- __('Delete Backup After Remote Storage Transfer', 'xcloner-backup-and-restore'),
707
- array($this, 'do_form_switch_field'),
708
- 'xcloner_cleanup_settings_page',
709
- 'xcloner_cleanup_settings_group',
710
- array(
711
- 'xcloner_cleanup_delete_after_remote_transfer',
712
- __('Remove backup created automatically from local storage after sending the backup to Remote Storage', 'xcloner-backup-and-restore')
713
- )
714
- );
715
-
716
- //REGISTERING THE 'CRON SECTION' FIELDS
717
- register_setting('xcloner_cron_settings_group', 'xcloner_cron_frequency');
718
- add_settings_field(
719
- 'xcloner_cron_frequency',
720
- __('Cron frequency', 'xcloner-backup-and-restore'),
721
- array($this, 'do_form_text_field'),
722
- 'xcloner_cron_settings_page',
723
- 'xcloner_cron_settings_group',
724
- array(
725
- 'xcloner_cron_frequency',
726
- __('Cron frequency')
727
- )
728
- );
729
- }
730
-
731
-
732
-
733
-
734
- /**
735
- * callback functions
736
- */
737
-
738
- // section content cb
739
- public function xcloner_settings_section_cb() {
740
- //echo '<p>WPOrg Section Introduction.</p>';
741
- }
742
-
743
- // text field content cb
744
- public function do_form_text_field($params) {
745
- if (!isset($params['3'])) {
746
- $params[3] = 0;
747
- }
748
- if (!isset($params['2'])) {
749
- $params[2] = 0;
750
- }
751
-
752
- list($fieldname, $label, $value, $disabled) = $params;
753
-
754
- if (!$value) {
755
- $value = get_option($fieldname);
756
- }
757
- // output the field
758
- ?>
759
  <div class="row">
760
  <div class="input-field col s10 m10 l8">
761
  <input class="validate" <?php echo ($disabled) ? "disabled" : "" ?> name="<?php echo $fieldname ?>"
762
- id="<?php echo $fieldname ?>" type="text" class="validate"
763
  value="<?php echo isset($value) ? esc_attr($value) : ''; ?>">
764
  </div>
765
  <div class="col s2 m2 ">
@@ -770,24 +972,24 @@ class Xcloner_Settings {
770
 
771
 
772
  <?php
773
- }
774
-
775
- // textarea field content cb
776
- public function do_form_textarea_field($params) {
777
- if (!isset($params['3'])) {
778
- $params[3] = 0;
779
- }
780
- if (!isset($params['2'])) {
781
- $params[2] = 0;
782
- }
783
-
784
- list($fieldname, $label, $value, $disabled) = $params;
785
-
786
- if (!$value) {
787
- $value = get_option($fieldname);
788
- }
789
- // output the field
790
- ?>
791
  <div class="row">
792
  <div class="input-field col s10 m10 l8">
793
  <textarea class="validate" <?php echo ($disabled) ? "disabled" : "" ?> name="<?php echo $fieldname ?>"
@@ -840,24 +1042,24 @@ class Xcloner_Settings {
840
 
841
 
842
  <?php
843
- }
844
-
845
- // number field content cb
846
- public function do_form_number_field($params) {
847
- if (!isset($params['3'])) {
848
- $params[3] = 0;
849
- }
850
- if (!isset($params['2'])) {
851
- $params[2] = 0;
852
- }
853
-
854
- list($fieldname, $label, $value, $disabled) = $params;
855
-
856
- if (!$value) {
857
- $value = get_option($fieldname);
858
- }
859
- // output the field
860
- ?>
861
  <div class="row">
862
  <div class="input-field col s10 m5 l3">
863
  <input class="validate" <?php echo ($disabled) ? "disabled" : "" ?> name="<?php echo $fieldname ?>"
@@ -872,16 +1074,16 @@ class Xcloner_Settings {
872
 
873
 
874
  <?php
875
- }
876
 
877
- public function do_form_range_field($params) {
878
- if (!isset($params['4'])) {
879
- $params[4] = 0;
880
- }
 
881
 
882
- list($fieldname, $label, $range_start, $range_end, $disabled) = $params;
883
- $value = get_option($fieldname);
884
- ?>
885
  <div class="row">
886
  <div class="input-field col s10 m10 l8">
887
  <p class="range-field">
@@ -898,16 +1100,16 @@ class Xcloner_Settings {
898
  </div>
899
  </div>
900
  <?php
901
- }
902
 
903
 
904
- public function do_form_switch_field($params) {
905
- if (!isset($params['2'])) {
906
- $params[2] = 0;
907
- }
908
- list($fieldname, $label, $disabled) = $params;
909
- $value = get_option($fieldname);
910
- ?>
911
  <div class="row">
912
  <div class="input-field col s10 m5 l3">
913
  <div class="switch">
@@ -929,5 +1131,5 @@ class Xcloner_Settings {
929
  </div>
930
  </div>
931
  <?php
932
- }
933
  }
1
  <?php
2
 
3
+ class Xcloner_Settings
4
+ {
5
+ private $logger_file = "xcloner_main_%s.log";
6
+ private $logger_file_hash = "xcloner%s.log";
7
+ private $hash;
8
+ private $xcloner_sanitization;
9
+ private $xcloner_container;
10
+ private $xcloner_database;
11
+
12
+ /**
13
+ * XCloner General Settings Class
14
+ *
15
+ * @param Xcloner $xcloner_container
16
+ * @param string $hash
17
+ */
18
+ public function __construct(Xcloner $xcloner_container, $hash = "", $json_config = "")
19
+ {
20
+ if($json_config) {
21
+ foreach($json_config as $item) {
22
+ add_option($item->option_name, $item->option_value);
23
+ }
24
+ }
25
+
26
+ $this->xcloner_container = $xcloner_container;
27
+
28
+ $this->xcloner_database = $xcloner_container->get_xcloner_database();
29
+
30
+ if (isset($hash)) {
31
+ $this->set_hash($hash);
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Get XCloner Main Container
37
+ *
38
+ * @return void
39
+ */
40
+ private function get_xcloner_container()
41
+ {
42
+ return $this->xcloner_container;
43
+ }
44
+
45
+ /**
46
+ * Get Logger Filename Setting
47
+ *
48
+ * @param integer $include_hash
49
+ * @return void
50
+ */
51
+ public function get_logger_filename($include_hash = 0)
52
+ {
53
+ if ($include_hash) {
54
+ $filename = sprintf($this->logger_file_hash, $this->get_hash());
55
+ } else {
56
+ $filename = sprintf($this->logger_file, $this->get_server_unique_hash(5));
57
+ }
58
+
59
+ return $filename;
60
+ }
61
+
62
+ /**
63
+ * Get XCloner Backup Start Path Setting
64
+ *
65
+ * @return void
66
+ */
67
+ public function get_xcloner_start_path()
68
+ {
69
+ if (!$this->get_xcloner_option('xcloner_start_path') or !is_dir(/** @scrutinizer ignore-type */$this->get_xcloner_option('xcloner_start_path'))) {
70
+ $path = realpath(ABSPATH);
71
+ } else {
72
+ $path = $this->get_xcloner_option('xcloner_start_path');
73
+ }
74
+
75
+ return $path;
76
+ }
77
+
78
+ /**
79
+ * Get XCloner Start Path Setting , function is in legacy mode
80
+ *
81
+ * @param [type] $dir
82
+ * @return void
83
+ */
84
+ public function get_xcloner_dir_path($dir)
85
+ {
86
+ $path = $this->get_xcloner_start_path().DS.$dir;
87
+
88
+ return $path;
89
+ }
90
+
91
+ /**
92
+ * Get XCloner Backup Store Path Setting
93
+ *
94
+ * @return void
95
+ */
96
+ public function get_xcloner_store_path()
97
+ {
98
+ if (!$this->get_xcloner_option('xcloner_store_path') or !is_dir(/** @scrutinizer ignore-type */$this->get_xcloner_option('xcloner_store_path'))) {
99
+ return $this->xcloner_container->check_dependencies();
100
+ } else {
101
+ $path = $this->get_xcloner_option('xcloner_store_path');
102
+ }
103
+
104
+ return $path;
105
+ }
106
+
107
+ /**
108
+ * Get XCloner Encryption Key
109
+ *
110
+ * @return void
111
+ */
112
+ public function get_xcloner_encryption_key()
113
+ {
114
+ if (!$key = $this->get_xcloner_option('xcloner_encryption_key')) {
115
+ $key = $this->xcloner_container->randomString(35);
116
+ update_option('xcloner_encryption_key', $key);
117
+ }
118
+
119
+ return $key;
120
+ }
121
+
122
+ /**
123
+ * Create a random string
124
+ * @author XEWeb <>
125
+ * @param $length the length of the string to create
126
+ * @return string
127
+ */
128
+ /*public function randomString($length = 6) {
129
+ $str = "";
130
+ $characters = array_merge(range('A', 'Z'), range('a', 'z'), range('0', '9'));
131
+ $max = count($characters) - 1;
132
+ for ($i = 0; $i < $length; $i++) {
133
+ $rand = mt_rand(0, $max);
134
+ $str .= $characters[$rand];
135
+ }
136
+ return $str;
137
+ }*/
138
+
139
+ public function get_xcloner_tmp_path_suffix()
140
+ {
141
+ return "xcloner".$this->get_hash();
142
+ }
143
+
144
+ /**
145
+ * Get XCloner Temporary Path
146
+ *
147
+ * @param boolean $suffix
148
+ * @return void
149
+ */
150
+ public function get_xcloner_tmp_path($suffix = true)
151
+ {
152
+ if ($this->get_xcloner_option('xcloner_force_tmp_path_site_root')) {
153
+ $path = $this->get_xcloner_store_path();
154
+ } else {
155
+ $path = sys_get_temp_dir();
156
+ if (!is_dir($path)) {
157
+ try {
158
+ mkdir($path);
159
+ chmod($path, 0777);
160
+ } catch (Exception $e) {
161
+ //silent catch
162
+ }
163
+ }
164
+
165
+ if (!is_dir($path) or !is_writeable($path)) {
166
+ $path = $this->get_xcloner_store_path();
167
+ }
168
+ }
169
+
170
+ if ($suffix) {
171
+ $path = $path.DS.".".$this->get_xcloner_tmp_path_suffix();
172
+ }
173
+
174
+ return $path;
175
+ }
176
+
177
+ /**
178
+ * Get Enable Mysql Backup Option
179
+ *
180
+ * @return void
181
+ */
182
+ public function get_enable_mysql_backup()
183
+ {
184
+ if ($this->get_xcloner_option('xcloner_enable_mysql_backup')) {
185
+ return true;
186
+ }
187
+
188
+ return false;
189
+ }
190
+
191
+ /**
192
+ * Get Backup Extension Name
193
+ *
194
+ * @param string $ext
195
+ * @return string ( hash.(tar|tgz) )
196
+ */
197
+ public function get_backup_extension_name($ext = "")
198
+ {
199
+ if (!$ext) {
200
+ if ($this->get_xcloner_option('xcloner_backup_compression_level')) {
201
+ $ext = ".tgz";
202
+ } else {
203
+ $ext = ".tar";
204
+ }
205
+ }
206
+
207
+ return ($this->get_hash()).$ext;
208
+ }
209
+
210
+ /**
211
+ * Get Backup Hash
212
+ *
213
+ * @return void
214
+ */
215
+ public function get_hash($readonly = false )
216
+ {
217
+ if (!$this->hash && !$readonly) {
218
+ $this->set_hash("-".$this->get_server_unique_hash(5));
219
+ }
220
+
221
+ //echo $this->hash;
222
+ return $this->hash;
223
+ }
224
+
225
+ /**
226
+ * Generate New Hash
227
+ *
228
+ * @return void
229
+ */
230
+ public function generate_new_hash()
231
+ {
232
+ $hash = "-".md5(rand());
233
+
234
+ $hash = substr($hash, 0, 6);
235
+
236
+ $this->set_hash($hash);
237
+
238
+ return $hash;
239
+ }
240
+
241
+ /**
242
+ * Set New Hash
243
+ *
244
+ * @param string $hash
245
+ * @return void
246
+ */
247
+ public function set_hash($hash = "")
248
+ {
249
+ if (substr($hash, 0, 1) != "-" and strlen($hash)) {
250
+ $hash = "-".$hash;
251
+ }
252
+
253
+ $this->hash = substr($hash, 0, 6);
254
+
255
+ return $this;
256
+ }
257
+
258
+ /**
259
+ * Get Default Backup Name
260
+ *
261
+ * @return void
262
+ */
263
+ public function get_default_backup_name()
264
+ {
265
+ $data = parse_url(get_site_url());
266
+
267
+ $backup_name = "backup_[domain]".(isset($data['port']) ? "_".$data['port'] : "")."-[time]-".($this->get_enable_mysql_backup() ? "sql" : "nosql");
268
+
269
+ return $backup_name;
270
+ }
271
+
272
+ /**
273
+ * Get Database Hostname
274
+ *
275
+ * @return void
276
+ */
277
+ public function get_db_hostname()
278
+ {
279
+ if (!$data = $this->get_xcloner_option('xcloner_mysql_hostname')) {
280
+ // $data = $this->xcloner_database->getDbHost();
281
+ }
282
+
283
+ return $data;
284
+ }
285
+
286
+ /**
287
+ * Get Database Username
288
+ *
289
+ * @return void
290
+ */
291
+ public function get_db_username()
292
+ {
293
+ if (!$data = $this->get_xcloner_option('xcloner_mysql_username')) {
294
+ //$data = $this->xcloner_database->getDbUser();
295
+ }
296
+
297
+ return $data;
298
+ }
299
+
300
+ /**
301
+ * Get Database Password
302
+ *
303
+ * @return void
304
+ */
305
+ public function get_db_password()
306
+ {
307
+ if (!$data = $this->get_xcloner_option('xcloner_mysql_password')) {
308
+ //$data = $this->xcloner_database->getDbPassword();
309
+ }
310
+
311
+ return $data;
312
+ }
313
+
314
+ /**
315
+ * Get Database Name
316
+ *
317
+ * @return void
318
+ */
319
+ public function get_db_database()
320
+ {
321
+ if (!$data = $this->get_xcloner_option('xcloner_mysql_database')) {
322
+ //$data = $this->xcloner_database->getDbName();
323
+ }
324
+
325
+ return $data;
326
+ }
327
+
328
+ /**
329
+ * Get Database Tables Prefix
330
+ *
331
+ * @return void
332
+ */
333
+ public function get_table_prefix()
334
+ {
335
+ return $this->get_xcloner_option('xcloner_mysql_prefix');
336
+ }
337
+
338
+ /**
339
+ * @param string $option
340
+ */
341
+ public function get_xcloner_option($option = "")
342
+ {
343
+ $data = get_option($option);
344
+
345
+ return $data;
346
+ }
347
+
348
+ /**
349
+ * Get Server Unique Hash used to generate the unique backup name
350
+ *
351
+ * @param integer $strlen
352
+ * @return void
353
+ */
354
+ public function get_server_unique_hash($strlen = 0)
355
+ {
356
+ $hash = md5(get_home_url().__DIR__.$this->get_xcloner_encryption_key());
357
+
358
+ if ($strlen) {
359
+ $hash = substr($hash, 0, $strlen);
360
+ }
361
+
362
+ return $hash;
363
+ }
364
+
365
+ public function settings_init()
366
+ {
367
+ $this->xcloner_sanitization = $this->get_xcloner_container()->get_xcloner_sanitization();
368
+
369
+ //ADDING MISSING OPTIONS
370
+ if (false == $this->get_xcloner_option('xcloner_mysql_settings_page')) {
371
+ update_option('xcloner_mysql_settings_page', '');
372
+ } // end if
373
+
374
+ if (false == $this->get_xcloner_option('xcloner_cron_settings_page')) {
375
+ update_option('xcloner_cron_settings_page', '');
376
+ } // end if
377
+
378
+ if (false == $this->get_xcloner_option('xcloner_system_settings_page')) {
379
+ update_option('xcloner_system_settings_page', '');
380
+ } // end if
381
+
382
+ if (false == $this->get_xcloner_option('xcloner_cleanup_settings_page')) {
383
+ update_option('xcloner_cleanup_settings_page', '');
384
+ } // end if
385
+
386
+
387
+ //ADDING SETTING SECTIONS
388
+ //GENERAL section
389
+ add_settings_section(
390
+ 'xcloner_general_settings_group',
391
+ __(' '),
392
+ array($this, 'xcloner_settings_section_cb'),
393
+ 'xcloner_settings_page'
394
+ );
395
+ //MYSQL section
396
+ add_settings_section(
397
+ 'xcloner_mysql_settings_group',
398
+ __(' '),
399
+ array($this, 'xcloner_settings_section_cb'),
400
+ 'xcloner_mysql_settings_page'
401
+ );
402
+
403
+ //SYSTEM section
404
+ add_settings_section(
405
+ 'xcloner_system_settings_group',
406
+ __('These are advanced options recommended for developers!', 'xcloner-backup-and-restore'),
407
+ array($this, 'xcloner_settings_section_cb'),
408
+ 'xcloner_system_settings_page'
409
+ );
410
+
411
+ //CLEANUP section
412
+ add_settings_section(
413
+ 'xcloner_cleanup_settings_group',
414
+ __(' '),
415
+ array($this, 'xcloner_settings_section_cb'),
416
+ 'xcloner_cleanup_settings_page'
417
+ );
418
+
419
+
420
+ //CRON section
421
+ add_settings_section(
422
+ 'xcloner_cron_settings_group',
423
+ __(' '),
424
+ array($this, 'xcloner_settings_section_cb'),
425
+ 'xcloner_cron_settings_page'
426
+ );
427
+
428
+
429
+ //REGISTERING THE 'GENERAL SECTION' FIELDS
430
+ register_setting('xcloner_general_settings_group', 'xcloner_backup_compression_level', array(
431
+ $this->xcloner_sanitization,
432
+ "sanitize_input_as_int"
433
+ ));
434
+ add_settings_field(
435
+ 'xcloner_backup_compression_level',
436
+ __('Backup Compression Level', 'xcloner-backup-and-restore'),
437
+ array($this, 'do_form_range_field'),
438
+ 'xcloner_settings_page',
439
+ 'xcloner_general_settings_group',
440
+ array(
441
+ 'xcloner_backup_compression_level',
442
+ __('Options between [0-9]. Value 0 means no compression, while 9 is maximum compression affecting cpu load', 'xcloner-backup-and-restore'),
443
+ 0,
444
+ 9
445
+ )
446
+ );
447
+
448
+ register_setting('xcloner_general_settings_group', 'xcloner_start_path', array(
449
+ $this->xcloner_sanitization,
450
+ "sanitize_input_as_absolute_path"
451
+ ));
452
+ add_settings_field(
453
+ 'xcloner_start_path',
454
+ __('Backup Start Location', 'xcloner-backup-and-restore'),
455
+ array($this, 'do_form_text_field'),
456
+ 'xcloner_settings_page',
457
+ 'xcloner_general_settings_group',
458
+ array(
459
+ 'xcloner_start_path',
460
+ __('Base path location from where XCloner can start the Backup.', 'xcloner-backup-and-restore'),
461
+ $this->get_xcloner_start_path(),
462
+ //'disabled'
463
+ )
464
+ );
465
+
466
+ register_setting('xcloner_general_settings_group', 'xcloner_store_path', array(
467
+ $this->xcloner_sanitization,
468
+ "sanitize_input_as_absolute_path"
469
+ ));
470
+ add_settings_field(
471
+ 'xcloner_store_path',
472
+ __('Backup Storage Location', 'xcloner-backup-and-restore'),
473
+ array($this, 'do_form_text_field'),
474
+ 'xcloner_settings_page',
475
+ 'xcloner_general_settings_group',
476
+ array(
477
+ 'xcloner_store_path',
478
+ __('Location where XCloner will store the Backup archives.', 'xcloner-backup-and-restore'),
479
+ $this->get_xcloner_store_path(),
480
+ //'disabled'
481
+ )
482
+ );
483
+
484
+ register_setting('xcloner_general_settings_group', 'xcloner_encryption_key', array(
485
+ $this->xcloner_sanitization,
486
+ "sanitize_input_as_string"
487
+ ));
488
+ add_settings_field(
489
+ 'xcloner_encryption_key',
490
+ __('Backup Encryption Key', 'xcloner-backup-and-restore'),
491
+ array($this, 'do_form_text_field'),
492
+ 'xcloner_settings_page',
493
+ 'xcloner_general_settings_group',
494
+ array(
495
+ 'xcloner_encryption_key',
496
+ __('Backup Encryption Key used to Encrypt/Decrypt backups, you might want to save this somewhere else as well.', 'xcloner-backup-and-restore'),
497
+ $this->get_xcloner_encryption_key(),
498
+ //'disabled'
499
+ )
500
+ );
501
+
502
+ register_setting('xcloner_general_settings_group', 'xcloner_enable_log', array(
503
+ $this->xcloner_sanitization,
504
+ "sanitize_input_as_int"
505
+ ));
506
+ add_settings_field(
507
+ 'xcloner_enable_log',
508
+ __('Enable XCloner Backup Log', 'xcloner-backup-and-restore'),
509
+ array($this, 'do_form_switch_field'),
510
+ 'xcloner_settings_page',
511
+ 'xcloner_general_settings_group',
512
+ array(
513
+ 'xcloner_enable_log',
514
+ sprintf(__('Enable the XCloner Backup log. You will find it stored unde the Backup Storage Location, file %s', 'xcloner-backup-and-restore'), $this->get_logger_filename())
515
+ )
516
+ );
517
+
518
+ register_setting('xcloner_general_settings_group', 'xcloner_enable_pre_update_backup', array(
519
+ $this->xcloner_sanitization,
520
+ "sanitize_input_as_int"
521
+ ));
522
+ add_settings_field(
523
+ 'xcloner_enable_pre_update_backup',
524
+ __('Generate Backups before Automatic WP Upgrades', 'xcloner-backup-and-restore'),
525
+ array($this, 'do_form_switch_field'),
526
+ 'xcloner_settings_page',
527
+ 'xcloner_general_settings_group',
528
+ array(
529
+ 'xcloner_enable_pre_update_backup',
530
+ sprintf(__('Attempt to generate a full site backup before applying automatic core updates.', 'xcloner-backup-and-restore'), $this->get_logger_filename())
531
+ )
532
+ );
533
+
534
+ register_setting('xcloner_general_settings_group', 'xcloner_regex_exclude', array(
535
+ $this->xcloner_sanitization,
536
+ "sanitize_input_as_raw"
537
+ ));
538
+ add_settings_field(
539
+ 'xcloner_regex_exclude',
540
+ __('Regex Exclude Files', 'xcloner-backup-and-restore'),
541
+ array($this, 'do_form_textarea_field'),
542
+ 'xcloner_settings_page',
543
+ 'xcloner_general_settings_group',
544
+ array(
545
+ 'xcloner_regex_exclude',
546
+ __('Regular expression match to exclude files and folders, example patterns provided below, one pattern per line', 'xcloner-backup-and-restore'),
547
+ //$this->get_xcloner_store_path(),
548
+ //'disabled'
549
+ )
550
+ );
551
+
552
+ //REGISTERING THE 'MYSQL SECTION' FIELDS
553
+ register_setting('xcloner_mysql_settings_group', 'xcloner_enable_mysql_backup', array(
554
+ $this->xcloner_sanitization,
555
+ "sanitize_input_as_int"
556
+ ));
557
+ add_settings_field(
558
+ 'xcloner_enable_mysql_backup',
559
+ __('Enable Mysql Backup', 'xcloner-backup-and-restore'),
560
+ array($this, 'do_form_switch_field'),
561
+ 'xcloner_mysql_settings_page',
562
+ 'xcloner_mysql_settings_group',
563
+ array(
564
+ 'xcloner_enable_mysql_backup',
565
+ __('Enable Mysql Backup Option. If you don\'t want to backup the database, you can disable this.', 'xcloner-backup-and-restore')
566
+ )
567
+ );
568
+
569
+ register_setting('xcloner_mysql_settings_group', 'xcloner_backup_only_wp_tables');
570
+ add_settings_field(
571
+ 'xcloner_backup_only_wp_tables',
572
+ __('Backup only WP tables', 'xcloner-backup-and-restore'),
573
+ array($this, 'do_form_switch_field'),
574
+ 'xcloner_mysql_settings_page',
575
+ 'xcloner_mysql_settings_group',
576
+ array(
577
+ 'xcloner_backup_only_wp_tables',
578
+ sprintf(__('Enable this if you only want to Backup only tables starting with \'%s\' prefix', 'xcloner-backup-and-restore'), $this->get_table_prefix())
579
+ )
580
+ );
581
+
582
+ register_setting('xcloner_mysql_settings_group', 'xcloner_mysql_hostname', array(
583
+ $this->xcloner_sanitization,
584
+ "sanitize_input_as_raw"
585
+ ));
586
+ add_settings_field(
587
+ 'xcloner_mysql_hostname',
588
+ __('Mysql Hostname', 'xcloner-backup-and-restore'),
589
+ array($this, 'do_form_text_field'),
590
+ 'xcloner_mysql_settings_page',
591
+ 'xcloner_mysql_settings_group',
592
+ array(
593
+ 'xcloner_mysql_hostname',
594
+ __('Wordpress mysql hostname', 'xcloner-backup-and-restore'),
595
+ $this->get_db_hostname(),
596
+ //'disabled'
597
+ )
598
+ );
599
+
600
+ register_setting('xcloner_mysql_settings_group', 'xcloner_mysql_username', array(
601
+ $this->xcloner_sanitization,
602
+ "sanitize_input_as_raw"
603
+ ));
604
+ add_settings_field(
605
+ 'xcloner_mysql_username',
606
+ __('Mysql Username', 'xcloner-backup-and-restore'),
607
+ array($this, 'do_form_text_field'),
608
+ 'xcloner_mysql_settings_page',
609
+ 'xcloner_mysql_settings_group',
610
+ array(
611
+ 'xcloner_mysql_username',
612
+ __('Wordpress mysql username', 'xcloner-backup-and-restore'),
613
+ $this->get_db_username(),
614
+ //'disabled'
615
+ )
616
+ );
617
+
618
+ register_setting('xcloner_mysql_settings_group', 'xcloner_mysql_password', array(
619
+ $this->xcloner_sanitization,
620
+ "sanitize_input_as_raw"
621
+ ));
622
+ add_settings_field(
623
+ 'xcloner_mysql_password',
624
+ __('Mysql Password', 'xcloner-backup-and-restore'),
625
+ array($this, 'do_form_password_field'),
626
+ 'xcloner_mysql_settings_page',
627
+ 'xcloner_mysql_settings_group',
628
+ array(
629
+ 'xcloner_mysql_password',
630
+ __('Wordpress mysql password', 'xcloner-backup-and-restore'),
631
+ $this->get_db_username(),
632
+ //'disabled'
633
+ )
634
+ );
635
+
636
+ register_setting('xcloner_mysql_settings_group', 'xcloner_mysql_database', array(
637
+ $this->xcloner_sanitization,
638
+ "sanitize_input_as_raw"
639
+ ));
640
+ add_settings_field(
641
+ 'xcloner_mysql_database',
642
+ __('Mysql Database', 'xcloner-backup-and-restore'),
643
+ array($this, 'do_form_text_field'),
644
+ 'xcloner_mysql_settings_page',
645
+ 'xcloner_mysql_settings_group',
646
+ array(
647
+ 'xcloner_mysql_database',
648
+ __('Wordpress mysql database', 'xcloner-backup-and-restore'),
649
+ $this->get_db_database(),
650
+ //'disabled'
651
+ )
652
+ );
653
+
654
+ register_setting('xcloner_mysql_settings_group', 'xcloner_mysql_prefix', array(
655
+ $this->xcloner_sanitization,
656
+ "sanitize_input_as_raw"
657
+ ));
658
+ add_settings_field(
659
+ 'xcloner_mysql_prefix',
660
+ __('Mysql Tables Prefix', 'xcloner-backup-and-restore'),
661
+ array($this, 'do_form_text_field'),
662
+ 'xcloner_mysql_settings_page',
663
+ 'xcloner_mysql_settings_group',
664
+ array(
665
+ 'xcloner_mysql_prefix',
666
+ __('Wordpress mysql tables prefix', 'xcloner-backup-and-restore'),
667
+ $this->get_table_prefix(),
668
+ //'disabled'
669
+ )
670
+ );
671
+
672
+ //REGISTERING THE 'SYSTEM SECTION' FIELDS
673
+ register_setting('xcloner_system_settings_group', 'xcloner_size_limit_per_request', array(
674
+ $this->xcloner_sanitization,
675
+ "sanitize_input_as_int"
676
+ ));
677
+ add_settings_field(
678
+ 'xcloner_size_limit_per_request',
679
+ __('Data Size Limit Per Request', 'xcloner-backup-and-restore'),
680
+ array($this, 'do_form_range_field'),
681
+ 'xcloner_system_settings_page',
682
+ 'xcloner_system_settings_group',
683
+ array(
684
+ 'xcloner_size_limit_per_request',
685
+ __('Use this option to set how much file data can XCloner backup in one AJAX request. Range 0-1024 MB', 'xcloner-backup-and-restore'),
686
+ 0,
687
+ 1024
688
+ )
689
+ );
690
+
691
+ register_setting('xcloner_system_settings_group', 'xcloner_files_to_process_per_request', array(
692
+ $this->xcloner_sanitization,
693
+ "sanitize_input_as_int"
694
+ ));
695
+ add_settings_field(
696
+ 'xcloner_files_to_process_per_request',
697
+ __('Files To Process Per Request', 'xcloner-backup-and-restore'),
698
+ array($this, 'do_form_range_field'),
699
+ 'xcloner_system_settings_page',
700
+ 'xcloner_system_settings_group',
701
+ array(
702
+ 'xcloner_files_to_process_per_request',
703
+ __('Use this option to set how many files XCloner should process at one time before doing another AJAX call', 'xcloner-backup-and-restore'),
704
+ 0,
705
+ 1000
706
+ )
707
+ );
708
+
709
+ register_setting('xcloner_system_settings_group', 'xcloner_directories_to_scan_per_request', array(
710
+ $this->xcloner_sanitization,
711
+ "sanitize_input_as_int"
712
+ ));
713
+ add_settings_field(
714
+ 'xcloner_directories_to_scan_per_request',
715
+ __('Directories To Scan Per Request', 'xcloner-backup-and-restore'),
716
+ array($this, 'do_form_range_field'),
717
+ 'xcloner_system_settings_page',
718
+ 'xcloner_system_settings_group',
719
+ array(
720
+ 'xcloner_directories_to_scan_per_request',
721
+ __('Use this option to set how many directories XCloner should scan at one time before doing another AJAX call', 'xcloner-backup-and-restore'),
722
+ 0,
723
+ 1000
724
+ )
725
+ );
726
+
727
+ register_setting('xcloner_system_settings_group', 'xcloner_database_records_per_request', array(
728
+ $this->xcloner_sanitization,
729
+ "sanitize_input_as_int"
730
+ ));
731
+ add_settings_field(
732
+ 'xcloner_database_records_per_request',
733
+ __('Database Records Per Request', 'xcloner-backup-and-restore'),
734
+ array($this, 'do_form_range_field'),
735
+ 'xcloner_system_settings_page',
736
+ 'xcloner_system_settings_group',
737
+ array(
738
+ 'xcloner_database_records_per_request',
739
+ __('Use this option to set how many database table records should be fetched per AJAX request, or set to 0 to fetch all. Range 0-100000 records', 'xcloner-backup-and-restore'),
740
+ 0,
741
+ 100000
742
+ )
743
+ );
744
+
745
+ /*register_setting('xcloner_system_settings_group', 'xcloner_diff_backup_recreate_period', array($this->xcloner_sanitization, "sanitize_input_as_int"));
746
+ add_settings_field(
747
+ 'xcloner_diff_backup_recreate_period',
748
+ __('Differetial Backups Max Days','xcloner-backup-and-restore'),
749
+ array($this, 'do_form_number_field'),
750
+ 'xcloner_system_settings_page',
751
+ 'xcloner_system_settings_group',
752
+ array('xcloner_diff_backup_recreate_period',
753
+ __('Use this option to set when a full backup should be recreated if the scheduled backup type is set to \'Full Backup+Differential Backups\' ','xcloner-backup-and-restore'),
754
+ )
755
+ );*/
756
+
757
+ register_setting('xcloner_system_settings_group', 'xcloner_exclude_files_larger_than_mb', array(
758
+ $this->xcloner_sanitization,
759
+ "sanitize_input_as_int"
760
+ ));
761
+ add_settings_field(
762
+ 'xcloner_exclude_files_larger_than_mb',
763
+ __('Exclude files larger than (MB)', 'xcloner-backup-and-restore'),
764
+ array($this, 'do_form_number_field'),
765
+ 'xcloner_system_settings_page',
766
+ 'xcloner_system_settings_group',
767
+ array(
768
+ 'xcloner_exclude_files_larger_than_mb',
769
+ __('Use this option to automatically exclude files larger than a certain size in MB, or set to 0 to include all. Range 0-1000 MB', 'xcloner-backup-and-restore'),
770
+ )
771
+ );
772
+
773
+ register_setting('xcloner_system_settings_group', 'xcloner_split_backup_limit', array(
774
+ $this->xcloner_sanitization,
775
+ "sanitize_input_as_int"
776
+ ));
777
+ add_settings_field(
778
+ 'xcloner_split_backup_limit',
779
+ __('Split Backup Archive Limit (MB)', 'xcloner-backup-and-restore'),
780
+ array($this, 'do_form_number_field'),
781
+ 'xcloner_system_settings_page',
782
+ 'xcloner_system_settings_group',
783
+ array(
784
+ 'xcloner_split_backup_limit',
785
+ __('Use this option to automatically split the backup archive into smaller parts. Range 0-10000 MB', 'xcloner-backup-and-restore'),
786
+ )
787
+ );
788
+
789
+ register_setting('xcloner_system_settings_group', 'xcloner_force_tmp_path_site_root');
790
+ add_settings_field(
791
+ 'xcloner_force_tmp_path_site_root',
792
+ __('Force Temporary Path Within XCloner Storage', 'xcloner-backup-and-restore'),
793
+ array($this, 'do_form_switch_field'),
794
+ 'xcloner_system_settings_page',
795
+ 'xcloner_system_settings_group',
796
+ array(
797
+ 'xcloner_force_tmp_path_site_root',
798
+ sprintf(__('Enable this option if you want the XCloner Temporary Path to be within your XCloner Storage Location', 'xcloner-backup-and-restore'), $this->get_table_prefix())
799
+ )
800
+ );
801
+
802
+ register_setting('xcloner_system_settings_group', 'xcloner_disable_email_notification');
803
+ add_settings_field(
804
+ 'xcloner_disable_email_notification',
805
+ __('Disable Email Notifications', 'xcloner-backup-and-restore'),
806
+ array($this, 'do_form_switch_field'),
807
+ 'xcloner_system_settings_page',
808
+ 'xcloner_system_settings_group',
809
+ array(
810
+ 'xcloner_disable_email_notification',
811
+ sprintf(__('Enable this option if you want the XCloner to NOT send email notifications on successful backups', 'xcloner-backup-and-restore'), $this->get_table_prefix())
812
+ )
813
+ );
814
+
815
+ //REGISTERING THE 'CLEANUP SECTION' FIELDS
816
+ register_setting('xcloner_cleanup_settings_group', 'xcloner_cleanup_retention_limit_days', array(
817
+ $this->xcloner_sanitization,
818
+ "sanitize_input_as_int"
819
+ ));
820
+ add_settings_field(
821
+ 'xcloner_cleanup_retention_limit_days',
822
+ __('Cleanup by Date(days)', 'xcloner-backup-and-restore'),
823
+ array($this, 'do_form_number_field'),
824
+ 'xcloner_cleanup_settings_page',
825
+ 'xcloner_cleanup_settings_group',
826
+ array(
827
+ 'xcloner_cleanup_retention_limit_days',
828
+ __('Specify the maximum number of days a backup archive can be kept on the server. 0 disables this option', 'xcloner-backup-and-restore')
829
+ )
830
+ );
831
+
832
+ register_setting('xcloner_cleanup_settings_group', 'xcloner_cleanup_retention_limit_archives', array(
833
+ $this->xcloner_sanitization,
834
+ "sanitize_input_as_int"
835
+ ));
836
+ add_settings_field(
837
+ 'xcloner_cleanup_retention_limit_archives',
838
+ __('Cleanup by Quantity', 'xcloner-backup-and-restore'),
839
+ array($this, 'do_form_number_field'),
840
+ 'xcloner_cleanup_settings_page',
841
+ 'xcloner_cleanup_settings_group',
842
+ array(
843
+ 'xcloner_cleanup_retention_limit_archives',
844
+ __('Specify the maximum number of backup archives to keep on the server. 0 disables this option', 'xcloner-backup-and-restore')
845
+ )
846
+ );
847
+
848
+ register_setting('xcloner_cleanup_settings_group', 'xcloner_cleanup_capacity_limit', array(
849
+ $this->xcloner_sanitization,
850
+ "sanitize_input_as_int"
851
+ ));
852
+ add_settings_field(
853
+ 'xcloner_cleanup_capacity_limit',
854
+ __('Cleanup by Capacity(MB)', 'xcloner-backup-and-restore'),
855
+ array($this, 'do_form_number_field'),
856
+ 'xcloner_cleanup_settings_page',
857
+ 'xcloner_cleanup_settings_group',
858
+ array(
859
+ 'xcloner_cleanup_capacity_limit',
860
+ __('Remove oldest backups if all created backups exceed the configured limit in Megabytes. 0 disables this option', 'xcloner-backup-and-restore')
861
+ )
862
+ );
863
+
864
+ register_setting('xcloner_cleanup_settings_group', 'xcloner_cleanup_delete_after_remote_transfer', array(
865
+ $this->xcloner_sanitization,
866
+ "sanitize_input_as_int"
867
+ ));
868
+ add_settings_field(
869
+ 'xcloner_cleanup_delete_after_remote_transfer',
870
+ __('Delete Backup After Remote Storage Transfer', 'xcloner-backup-and-restore'),
871
+ array($this, 'do_form_switch_field'),
872
+ 'xcloner_cleanup_settings_page',
873
+ 'xcloner_cleanup_settings_group',
874
+ array(
875
+ 'xcloner_cleanup_delete_after_remote_transfer',
876
+ __('Remove backup created automatically from local storage after sending the backup to Remote Storage', 'xcloner-backup-and-restore')
877
+ )
878
+ );
879
+
880
+ //REGISTERING THE 'CRON SECTION' FIELDS
881
+ register_setting('xcloner_cron_settings_group', 'xcloner_cron_frequency');
882
+ add_settings_field(
883
+ 'xcloner_cron_frequency',
884
+ __('Cron frequency', 'xcloner-backup-and-restore'),
885
+ array($this, 'do_form_text_field'),
886
+ 'xcloner_cron_settings_page',
887
+ 'xcloner_cron_settings_group',
888
+ array(
889
+ 'xcloner_cron_frequency',
890
+ __('Cron frequency')
891
+ )
892
+ );
893
+ }
894
+
895
+
896
+
897
+
898
+ /**
899
+ * callback functions
900
+ */
901
+
902
+ // section content cb
903
+ public function xcloner_settings_section_cb()
904
+ {
905
+ //echo '<p>WPOrg Section Introduction.</p>';
906
+ }
907
+
908
+ // text field content cb
909
+ public function do_form_text_field($params)
910
+ {
911
+ if (!isset($params['3'])) {
912
+ $params[3] = 0;
913
+ }
914
+ if (!isset($params['2'])) {
915
+ $params[2] = 0;
916
+ }
917
+
918
+ list($fieldname, $label, $value, $disabled) = $params;
919
+
920
+ if (!$value) {
921
+ $value = $this->get_xcloner_option($fieldname);
922
+ }
923
+ // output the field?>
924
+ <div class="row">
925
+ <div class="input-field col s10 m10 l8">
926
+ <input class="validate" <?php echo ($disabled) ? "disabled" : "" ?> name="<?php echo $fieldname ?>"
927
+ id="<?php echo $fieldname ?>" type="text" class="validate"
928
+ value="<?php echo isset($value) ? esc_attr($value) : ''; ?>">
929
+ </div>
930
+ <div class="col s2 m2 ">
931
+ <a class="btn-floating tooltipped btn-small" data-position="left" data-delay="50"
932
+ data-tooltip="<?php echo $label ?>" data-tooltip-id=""><i class="material-icons">help_outline</i></a>
933
+ </div>
934
+ </div>
935
 
 
 
936
 
937
+ <?php
938
+ }
939
+
940
+ /**
941
+ * Password field UI
942
+ *
943
+ * @param [type] $params
944
+ * @return void
945
+ */
946
+ public function do_form_password_field($params)
947
+ {
948
+ if (!isset($params['3'])) {
949
+ $params[3] = 0;
950
+ }
951
+ if (!isset($params['2'])) {
952
+ $params[2] = 0;
953
+ }
954
+
955
+ list($fieldname, $label, $value, $disabled) = $params;
956
+
957
+ if (!$value) {
958
+ $value = $this->get_xcloner_option($fieldname);
959
+ }
960
+ // output the field?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
961
  <div class="row">
962
  <div class="input-field col s10 m10 l8">
963
  <input class="validate" <?php echo ($disabled) ? "disabled" : "" ?> name="<?php echo $fieldname ?>"
964
+ id="<?php echo $fieldname ?>" type="password" class="validate"
965
  value="<?php echo isset($value) ? esc_attr($value) : ''; ?>">
966
  </div>
967
  <div class="col s2 m2 ">
972
 
973
 
974
  <?php
975
+ }
976
+
977
+ // textarea field content cb
978
+ public function do_form_textarea_field($params)
979
+ {
980
+ if (!isset($params['3'])) {
981
+ $params[3] = 0;
982
+ }
983
+ if (!isset($params['2'])) {
984
+ $params[2] = 0;
985
+ }
986
+
987
+ list($fieldname, $label, $value, $disabled) = $params;
988
+
989
+ if (!$value) {
990
+ $value = $this->get_xcloner_option($fieldname);
991
+ }
992
+ // output the field?>
993
  <div class="row">
994
  <div class="input-field col s10 m10 l8">
995
  <textarea class="validate" <?php echo ($disabled) ? "disabled" : "" ?> name="<?php echo $fieldname ?>"
1042
 
1043
 
1044
  <?php
1045
+ }
1046
+
1047
+ // number field content cb
1048
+ public function do_form_number_field($params)
1049
+ {
1050
+ if (!isset($params['3'])) {
1051
+ $params[3] = 0;
1052
+ }
1053
+ if (!isset($params['2'])) {
1054
+ $params[2] = 0;
1055
+ }
1056
+
1057
+ list($fieldname, $label, $value, $disabled) = $params;
1058
+
1059
+ if (!$value) {
1060
+ $value = $this->get_xcloner_option($fieldname);
1061
+ }
1062
+ // output the field?>
1063
  <div class="row">
1064
  <div class="input-field col s10 m5 l3">
1065
  <input class="validate" <?php echo ($disabled) ? "disabled" : "" ?> name="<?php echo $fieldname ?>"
1074
 
1075
 
1076
  <?php
1077
+ }
1078
 
1079
+ public function do_form_range_field($params)
1080
+ {
1081
+ if (!isset($params['4'])) {
1082
+ $params[4] = 0;
1083
+ }
1084
 
1085
+ list($fieldname, $label, $range_start, $range_end, $disabled) = $params;
1086
+ $value = $this->get_xcloner_option($fieldname); ?>
 
1087
  <div class="row">
1088
  <div class="input-field col s10 m10 l8">
1089
  <p class="range-field">
1100
  </div>
1101
  </div>
1102
  <?php
1103
+ }
1104
 
1105
 
1106
+ public function do_form_switch_field($params)
1107
+ {
1108
+ if (!isset($params['2'])) {
1109
+ $params[2] = 0;
1110
+ }
1111
+ list($fieldname, $label, $disabled) = $params;
1112
+ $value = $this->get_xcloner_option($fieldname); ?>
1113
  <div class="row">
1114
  <div class="input-field col s10 m5 l3">
1115
  <div class="switch">
1131
  </div>
1132
  </div>
1133
  <?php
1134
+ }
1135
  }
includes/class-xcloner-standalone.php ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ define('XCLONER_STANDALONE_MODE', true);
4
+
5
+ require_once(__DIR__.'/../vendor/autoload.php');
6
+
7
+ use Monolog\Logger;
8
+ use Monolog\Handler\StreamHandler;
9
+ use Monolog\Handler\RotatingFileHandler;
10
+
11
+ include_once(__DIR__ ."/../lib/mock_wp_functions.php");
12
+ require_once(__DIR__ . '/../includes/class-xcloner.php');
13
+
14
+
15
+ class Xcloner_Standalone extends Xcloner
16
+ {
17
+ public function __construct($json_config)
18
+ {
19
+ if (WP_DEBUG && WP_DEBUG_DISPLAY) {
20
+ $this->log_php_errors();
21
+ }
22
+
23
+ $this->load_dependencies();
24
+
25
+ $this->define_plugin_settings($json_config);
26
+
27
+ if(!isset($_POST['hash']) || !$_POST['hash']){
28
+ $_POST['hash'] = "";
29
+ }
30
+ $this->xcloner_settings = new XCloner_Settings($this, $_POST['hash'], $json_config);
31
+
32
+
33
+ if( !$this->xcloner_settings->get_hash(true) ){
34
+ $this->xcloner_settings->generate_new_hash();
35
+ }
36
+
37
+
38
+ $this->define_plugin_settings();
39
+
40
+ $this->xcloner_logger = new XCloner_Logger($this, "xcloner_standalone");
41
+
42
+ if (WP_DEBUG && WP_DEBUG_DISPLAY) {
43
+ $this->xcloner_logger->pushHandler(new StreamHandler('php://stdout', Logger::INFO));
44
+ }
45
+
46
+ $this->xcloner_filesystem = new Xcloner_File_System($this);
47
+ $this->archive_system = new Xcloner_Archive($this);
48
+ $this->xcloner_database = new Xcloner_Database($this);
49
+ $this->xcloner_scheduler = new Xcloner_Scheduler($this);
50
+ $this->xcloner_remote_storage = new Xcloner_Remote_Storage($this);
51
+ $this->xcloner_file_transfer = new Xcloner_File_Transfer($this);
52
+ $this->xcloner_encryption = new Xcloner_Encryption($this);
53
+ $this->xcloner_sanitization = new Xcloner_Sanitization();
54
+
55
+ //$this->start();
56
+ }
57
+
58
+ /**
59
+ * Start backup process trigger method
60
+ *
61
+ * @return void
62
+ */
63
+ public function start()
64
+ {
65
+ $profile_config = ($this->xcloner_settings->get_xcloner_option('profile'));
66
+
67
+ $data['params'] = "";
68
+ $data['backup_params'] = $profile_config->backup_params;
69
+ $data['table_params'] = json_encode($profile_config->database);
70
+ $data['excluded_files'] = json_encode($profile_config->excluded_files);
71
+
72
+ return $this->xcloner_scheduler->xcloner_scheduler_callback(null, $data, $this);
73
+ }
74
+
75
+ /**
76
+ * Overwrite parent __call method
77
+ *
78
+ * @param [type] $property
79
+ * @param [type] $args
80
+ * @return void
81
+ */
82
+ public function __call($property, $args)
83
+ {
84
+ $property = str_replace("get_", "", $property);
85
+
86
+ if (property_exists($this, $property)) {
87
+ return $this->$property;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Get Xcloner Main Class Container
93
+ *
94
+ * @return void
95
+ */
96
+ private function get_xcloner_container()
97
+ {
98
+ return $this;
99
+ }
100
+ }
includes/class-xcloner.php CHANGED
@@ -39,80 +39,84 @@
39
  * @package Xcloner
40
  * @subpackage Xcloner/includes
41
  * @author Liuta Ovidiu <info@thinkovi.com>
42
- * @link http://www.thinkovi.com
43
  */
44
- class Xcloner {
45
-
46
- /**
47
- * The loader that's responsible for maintaining and registering all hooks that power
48
- * the plugin.
49
- *
50
- * @since 1.0.0
51
- * @access protected
52
- * @var Xcloner_Loader $loader Maintains and registers all hooks for the plugin.
53
- */
54
- protected $loader;
55
-
56
- /**
57
- * The unique identifier of this plugin.
58
- *
59
- * @since 1.0.0
60
- * @access protected
61
- * @var string $plugin_name The string used to uniquely identify this plugin.
62
- */
63
- protected $plugin_name;
64
-
65
- protected $plugin_admin;
66
-
67
- /**
68
- * The current version of the plugin.
69
- *
70
- * @since 1.0.0
71
- * @access protected
72
- * @var string $version The current version of the plugin.
73
- */
74
- protected $version;
75
-
76
- private $xcloner_settings;
77
- private $xcloner_logger;
78
- private $xcloner_sanitization;
79
- private $xcloner_requirements;
80
- private $xcloner_filesystem;
81
- private $archive_system;
82
- private $xcloner_database;
83
- private $xcloner_scheduler;
84
- private $xcloner_remote_storage;
85
- private $xcloner_file_transfer;
86
- private $xcloner_encryption;
87
-
88
- /**
89
- * Define the core functionality of the plugin.
90
- *
91
- * Set the plugin name and the plugin version that can be used throughout the plugin.
92
- * Load the dependencies, define the locale, and set the hooks for the admin area and
93
- * the public-facing side of the site.
94
- *
95
- * @since 1.0.0
96
- */
97
- public function init()
98
- {
99
- register_shutdown_function(array($this, 'exception_handler'));
100
-
101
- $this->plugin_name = 'xcloner';
102
- $this->version = '4.0.4';
103
-
104
- $this->load_dependencies();
105
- $this->set_locale();
106
- $this->define_admin_hooks();
107
- $this->define_public_hooks();
108
-
109
- $this->define_admin_menu();
110
- $this->define_plugin_settings();
111
-
112
- $this->define_ajax_hooks();
113
- $this->define_cron_hooks();
114
-
115
- }
 
 
 
 
116
 
117
  /**
118
  * Dynamic get of class methods get_
@@ -120,11 +124,11 @@ class Xcloner {
120
  * @param $args
121
  * @return mixed
122
  */
123
- public function __call($property, $args) {
124
-
125
  $property = str_replace("get_", "", $property);
126
 
127
- if(property_exists($this, $property)){
128
  return $this->$property;
129
  }
130
  }
@@ -135,7 +139,8 @@ class Xcloner {
135
  * @param int $length
136
  * @return string
137
  */
138
- public function randomString($length = 6) {
 
139
  $str = "";
140
  $characters = array_merge(range('A', 'Z'), range('a', 'z'), range('0', '9'));
141
  $max = count($characters) - 1;
@@ -146,551 +151,625 @@ class Xcloner {
146
  return $str;
147
  }
148
 
149
- public function check_dependencies() {
150
-
151
- $backup_storage_path = ( get_option('xcloner_store_path') );
152
-
153
- if(!$backup_storage_path) {
154
 
 
155
  $backup_storage_path = realpath(__DIR__ . DS . ".." . DS . ".." . DS . "..") . DS . "backups-" . $this->randomString('5') . DS;
 
156
 
157
- if (!is_dir($backup_storage_path)) {
158
- if (!@mkdir($backup_storage_path)) {
159
- $status = "error";
160
- $message = sprintf(__("Unable to create the Backup Storage Location Folder %s . Please fix this before starting the backup process."),
161
- $backup_storage_path);
162
- $this->trigger_message($message, $status, $backup_storage_path);
163
- return;
164
- }
165
- }
166
- if (!is_writable($backup_storage_path)) {
167
  $status = "error";
168
- $message = sprintf(__("Unable to write to the Backup Storage Location Folder %s . Please fix this before starting the backup process."),
169
- $backup_storage_path);
 
 
170
  $this->trigger_message($message, $status, $backup_storage_path);
171
-
172
  return;
173
  }
174
-
175
- update_option("xcloner_store_path", $backup_storage_path);
 
 
 
 
 
 
 
 
 
176
  }
177
 
 
 
178
 
 
 
 
 
 
179
 
180
- }
181
-
182
- public function trigger_message($message, $status = "error", $message_param1 = "", $message_param2 = "", $message_param3 = "")
183
- {
184
- $message = sprintf(__($message), $message_param1, $message_param2, $message_param3);
185
- add_action('xcloner_admin_notices', array($this, "trigger_message_notice"), 10, 2);
186
- do_action('xcloner_admin_notices', $message, $status);
187
- }
188
 
189
- public function trigger_message_notice($message, $status = "success")
190
- {
191
- ?>
192
  <div class="notice notice-<?php echo $status?> is-dismissible">
193
  <p><?php _e($message, 'xcloner-backup-and-restore'); ?></p>
194
  </div>
195
  <?php
196
- }
197
-
198
- /**
199
- * Load the required dependencies for this plugin.
200
- *
201
- * Include the following files that make up the plugin:
202
- *
203
- * - Xcloner_Loader. Orchestrates the hooks of the plugin.
204
- * - Xcloner_i18n. Defines internationalization functionality.
205
- * - Xcloner_Admin. Defines all hooks for the admin area.
206
- * - Xcloner_Public. Defines all hooks for the public side of the site.
207
- *
208
- * Create an instance of the loader which will be used to register the hooks
209
- * with WordPress.
210
- *
211
- * @since 1.0.0
212
- * @access private
213
- */
214
- private function load_dependencies() {
215
-
216
- /**
217
- * The class responsible for orchestrating the actions and filters of the
218
- * core plugin.
219
- */
220
- require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-loader.php';
221
-
222
- /**
223
- * The class responsible for defining internationalization functionality
224
- * of the plugin.
225
- */
226
- require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-i18n.php';
227
-
228
- /**
229
- * The class responsible for defining all actions that occur in the admin area.
230
- */
231
- require_once plugin_dir_path(dirname(__FILE__)).'admin/class-xcloner-admin.php';
232
-
233
- /**
234
- * The class responsible for debugging XCloner.
235
- */
236
- require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-logger.php';
237
-
238
- /**
239
- * The class responsible for defining the admin settings area.
240
- */
241
- require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-settings.php';
242
-
243
- /**
244
- * The class responsible for defining the Remote Storage settings area.
245
- */
246
- require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-remote-storage.php';
247
-
248
- /**
249
- * The class responsible for implementing the database backup methods.
250
- */
251
- require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-database.php';
252
-
253
- /**
254
- * The class responsible for sanitization of users input.
255
- */
256
- require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-sanitization.php';
257
-
258
- /**
259
- * The class responsible for XCloner system requirements validation.
260
- */
261
- require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-requirements.php';
262
-
263
- /**
264
- * The class responsible for XCloner backup archive creation.
265
- */
266
- require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-archive.php';
267
-
268
- /**
269
- * The class responsible for XCloner API requests.
270
- */
271
- require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-api.php';
272
-
273
- /**
274
- * The class responsible for the XCloner File System methods.
275
- */
276
- require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-file-system.php';
277
-
278
- /**
279
- * The class responsible for the XCloner File Transfer methods.
280
- */
281
- require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-file-transfer.php';
282
-
283
- /**
284
- * The class responsible for the XCloner Scheduler methods.
285
- */
286
- require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-scheduler.php';
287
-
288
- /**
289
- * The class responsible for the XCloner Encryption methods.
290
- */
291
- require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-encryption.php';
292
-
293
- /**
294
- * The class responsible for defining all actions that occur in the public-facing
295
- * side of the site.
296
- */
297
- require_once plugin_dir_path(dirname(__FILE__)).'public/class-xcloner-public.php';
298
-
299
- $this->loader = new Xcloner_Loader($this);
300
-
301
- }
302
-
303
- /**
304
- * Define the locale for this plugin for internationalization.
305
- *
306
- * Uses the Xcloner_i18n class in order to set the domain and to register the hook
307
- * with WordPress.
308
- *
309
- * @since 1.0.0
310
- * @access private
311
- */
312
- private function set_locale() {
313
-
314
- $plugin_i18n = new Xcloner_i18n();
315
-
316
- $this->loader->add_action('plugins_loaded', $plugin_i18n, 'load_plugin_textdomain');
317
-
318
- //wp_localize_script( 'ajax-script', 'my_ajax_object',
319
- // array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
320
-
321
- }
322
-
323
- /**
324
- * Register all of the hooks related to the admin area functionality
325
- * of the plugin.
326
- *
327
- * @since 1.0.0
328
- * @access private
329
- */
330
- private function define_admin_hooks() {
331
-
332
- $plugin_admin = new Xcloner_Admin($this);
333
- $this->plugin_admin = $plugin_admin;
334
-
335
- $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_styles');
336
- $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts');
337
-
338
- add_action('backup_archive_finished', array($this, 'do_action_after_backup_finished'), 10, 2);
339
- }
340
-
341
- /**
342
- * Register the Admin Sidebar menu
343
- *
344
- * @access private
345
- *
346
- */
347
- private function define_admin_menu() {
348
-
349
- add_action('admin_menu', array($this->loader, 'xcloner_backup_add_admin_menu'));
350
-
351
- }
352
-
353
- private function define_plugin_settings() {
354
- /**
355
- * register wporg_settings_init to the admin_init action hook
356
- */
357
-
358
- $this->xcloner_settings = new XCloner_Settings($this);
359
-
360
- if (defined('DOING_CRON') || isset($_POST['hash'])) {
361
-
362
- if (defined('DOING_CRON') || $_POST['hash'] == "generate_hash") {
363
- $this->xcloner_settings->generate_new_hash();
364
- } else {
365
- $this->xcloner_settings->set_hash($_POST['hash']);
366
- }
367
- }
368
-
369
- if (defined('DOING_CRON') || !isset($_POST['hash']))
370
- {
371
- add_action('shutdown', function() {
372
- $this->xcloner_filesystem = new Xcloner_File_System($this);
373
- $this->xcloner_filesystem->remove_tmp_filesystem();
374
- });
375
- }
376
-
377
- $this->xcloner_sanitization = new Xcloner_Sanitization();
378
- $this->xcloner_requirements = new Xcloner_Requirements($this);
379
-
380
- add_action('admin_init', array($this->xcloner_settings, 'settings_init'));
381
-
382
- //adding links to the Manage Plugins Wordpress page for XCloner
383
- add_filter('plugin_action_links', array($this, 'add_plugin_action_links'), 10, 2);
384
-
385
-
386
-
387
- }
388
-
389
- /*
390
- * @method static $this get_xcloner_logger()
391
- * @method static $this get_xcloner_settings()
392
- * type = core|plugin|theme|translation
393
- */
394
- public function pre_auto_update($type, $item, $context)
395
- {
396
- if (!$type)
397
- {
398
- return false;
399
- }
400
-
401
- $exclude_files = array();
402
- $regex = "";
403
- $data = "";
404
-
405
- $this->get_xcloner_logger()->info(sprintf("Doing automatic backup before %s upgrade, pre_auto_update hook.", $type));
406
 
407
- $content_dir = str_replace(ABSPATH, "", WP_CONTENT_DIR);
408
- $plugins_dir = str_replace(ABSPATH, "", WP_PLUGIN_DIR);
409
- $langs_dir = $content_dir.DS."languages";
410
- $themes_dir = $content_dir.DS."themes";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
 
412
- switch ($type) {
413
- case 'core':
414
- $exclude_files = array(
415
- "^(?!(wp-admin|wp-includes|(?!.*\/.*.php)))(.*)$",
416
- );
417
- break;
418
- case 'plugin':
 
 
 
 
 
419
 
420
- $dir_array = explode(DS, $plugins_dir);
421
-
422
- foreach ($dir_array as $dir)
423
- {
424
- $data .= "\/".$dir;
425
- $regex .= $data."$|";
426
- }
427
 
428
- $regex .= "\/".implode("\/", $dir_array);
 
 
429
 
430
- $exclude_files = array(
431
- "^(?!(".$regex."))(.*)$",
432
- );
433
- break;
434
- case 'theme':
 
 
 
 
 
 
435
 
436
- $dir_array = explode(DS, $themes_dir);
 
437
 
438
- foreach ($dir_array as $dir)
439
- {
440
- $data .= "\/".$dir;
441
- $regex .= $data."$|";
442
- }
443
 
444
- $regex .= "\/".implode("\/", $dir_array);
 
 
 
 
 
 
 
 
 
445
 
446
- $exclude_files = array(
447
- "^(?!(".$regex."))(.*)$",
448
- );
449
- break;
450
- case 'translation':
 
 
 
 
 
 
 
 
 
451
 
452
- $dir_array = explode(DS, $langs_dir);
453
-
454
- foreach ($dir_array as $dir)
455
- {
456
- $data .= "\/".$dir;
457
- $regex .= $data."$|";
458
- }
459
 
460
- $regex .= "\/".implode("\/", $dir_array);
461
-
462
- $exclude_files = array(
463
- "^(?!(".$regex."))(.*)$",
464
- );
465
- break;
466
- }
467
 
468
- $schedule = array();
469
-
470
- $schedule['id'] = 0;
471
- $schedule['name'] = "pre_auto_update";
472
- $schedule['recurrence'] = "single";
473
- $schedule['excluded_files'] = json_encode($exclude_files);
474
- $schedule['table_params'] = json_encode(array("#" => array($this->get_xcloner_settings()->get_db_database())));
475
 
476
- $schedule['backup_params'] = new stdClass();
477
- $schedule['backup_params']->email_notification = get_option('admin_email');
478
- $schedule['backup_params']->backup_name = "backup_pre_auto_update_".$type."_[domain]-[time]-sql";
479
 
480
- try {
481
- $this->xcloner_scheduler->xcloner_scheduler_callback(0, $schedule);
482
- }catch (Exception $e) {
483
- $this->get_xcloner_logger()->error($e->getMessage());
484
- }
485
-
486
- }
487
-
488
- /**
489
- * Register all of the hooks related to the public-facing functionality
490
- * of the plugin.
491
- *
492
- * @since 1.0.0
493
- * @access private
494
- */
495
- private function define_public_hooks() {
496
-
497
- $plugin_public = new Xcloner_Public($this);
498
-
499
- $this->loader->add_action('wp_enqueue_scripts', $plugin_public, 'enqueue_styles');
500
- $this->loader->add_action('wp_enqueue_scripts', $plugin_public, 'enqueue_scripts');
501
-
502
- }
503
 
504
- public function exception_handler() {
505
-
506
- $logger = new XCloner_Logger($this, "php_system");
507
- $error = error_get_last();
508
-
509
- if ($error['type'] and $error['type'] === E_ERROR and $logger)
510
- {
511
- $logger->error($this->friendly_error_type($error['type']).": ".var_export($error, true));
512
- }elseif ($error['type'] and $logger)
513
- {
514
- $logger->debug($this->friendly_error_type($error['type']).": ".var_export($error, true));
515
  }
516
 
517
- }
 
 
518
 
519
- public function friendly_error_type($type) {
520
- static $levels = null;
521
- if ($levels === null) {
522
- $levels = [];
523
- foreach (get_defined_constants() as $key=>$value) {
524
- if (strpos($key, 'E_') !== 0) {continue; }
525
- $levels[$value] = $key; //substr($key,2);
526
- }
527
- }
528
- return (isset($levels[$type]) ? $levels[$type] : "Error #{$type}");
529
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
530
 
531
  /**
532
  * @method get_xcloner_settings()
533
  * @throws Exception
534
  */
535
- private function define_ajax_hooks()
536
- {
537
- //adding the pre-update hook
538
-
539
- if (is_admin() || defined('DOING_CRON'))
540
- {
541
- $this->xcloner_logger = new XCloner_Logger($this, "xcloner_api");
542
- $this->xcloner_filesystem = new Xcloner_File_System($this);
543
-
544
- //$this->xcloner_filesystem->set_diff_timestamp_start (strtotime("-15 days"));
545
-
546
- $this->archive_system = new Xcloner_Archive($this);
547
- $this->xcloner_database = new Xcloner_Database($this);
548
- $this->xcloner_scheduler = new Xcloner_Scheduler($this);
549
- $this->xcloner_remote_storage = new Xcloner_Remote_Storage($this);
550
- $this->xcloner_file_transfer = new Xcloner_File_Transfer($this);
551
- $this->xcloner_encryption = new Xcloner_Encryption($this);
552
-
553
- $xcloner_api = new Xcloner_Api($this);
554
-
555
- add_action('wp_ajax_get_database_tables_action', array($xcloner_api, 'get_database_tables_action'));
556
- add_action('wp_ajax_get_file_system_action', array($xcloner_api, 'get_file_system_action'));
557
- add_action('wp_ajax_scan_filesystem', array($xcloner_api, 'scan_filesystem'));
558
- add_action('wp_ajax_backup_database', array($xcloner_api, 'backup_database'));
559
- add_action('wp_ajax_backup_files', array($xcloner_api, 'backup_files'));
560
- add_action('wp_ajax_save_schedule', array($xcloner_api, 'save_schedule'));
561
- add_action('wp_ajax_get_schedule_by_id', array($xcloner_api, 'get_schedule_by_id'));
562
- add_action('wp_ajax_get_scheduler_list', array($xcloner_api, 'get_scheduler_list'));
563
- add_action('wp_ajax_delete_schedule_by_id', array($xcloner_api, 'delete_schedule_by_id'));
564
- add_action('wp_ajax_delete_backup_by_name', array($xcloner_api, 'delete_backup_by_name'));
565
- add_action('wp_ajax_download_backup_by_name', array($xcloner_api, 'download_backup_by_name'));
566
- add_action('wp_ajax_remote_storage_save_status', array($xcloner_api, 'remote_storage_save_status'));
567
- add_action('wp_ajax_upload_backup_to_remote', array($xcloner_api, 'upload_backup_to_remote'));
568
- add_action('wp_ajax_list_backup_files', array($xcloner_api, 'list_backup_files'));
569
- add_action('wp_ajax_restore_upload_backup', array($xcloner_api, 'restore_upload_backup'));
570
- add_action('wp_ajax_download_restore_script', array($xcloner_api, 'download_restore_script'));
571
- add_action('wp_ajax_copy_backup_remote_to_local', array($xcloner_api, 'copy_backup_remote_to_local'));
572
- add_action('wp_ajax_restore_backup', array($xcloner_api, 'restore_backup'));
573
- add_action('wp_ajax_backup_encryption', array($xcloner_api, 'backup_encryption'));
574
- add_action('wp_ajax_backup_decryption', array($xcloner_api, 'backup_decryption'));
575
- add_action('wp_ajax_get_manage_backups_list', array($xcloner_api, 'get_manage_backups_list'));
576
- add_action('admin_notices', array($this, 'xcloner_error_admin_notices'));
577
-
578
- }
579
-
580
- //Do a pre-update backup of targeted files
581
- if ($this->get_xcloner_settings()->get_xcloner_option('xcloner_enable_pre_update_backup'))
582
- {
583
- add_action("pre_auto_update", array($this, "pre_auto_update"), 1, 3);
584
- }
585
- }
586
-
587
- public function add_plugin_action_links($links, $file) {
588
- if ($file == plugin_basename(dirname(dirname(__FILE__)).'/xcloner.php'))
589
- {
590
- $links[] = '<a href="admin.php?page=xcloner_settings_page">'.__('Settings', 'xcloner-backup-and-restore').'</a>';
591
- $links[] = '<a href="admin.php?page=xcloner_generate_backups_page">'.__('Generate Backup', 'xcloner-backup-and-restore').'</a>';
592
- }
593
-
594
- return $links;
595
- }
596
-
597
- public function xcloner_error_admin_notices() {
598
- settings_errors('xcloner_error_message');
599
- }
600
 
601
  /**
602
  * @method get_xcloner_scheduler()
603
  */
604
- public function define_cron_hooks()
605
- {
606
- //registering new schedule intervals
607
- add_filter('cron_schedules', array($this, 'add_new_intervals'));
608
-
609
-
610
- $xcloner_scheduler = $this->get_xcloner_scheduler();
611
- $xcloner_scheduler->update_wp_cron_hooks();
612
-
613
- }
614
-
615
- /**
616
- * @param $schedules
617
- * @return mixed
618
- */
619
- public function add_new_intervals($schedules)
620
- {
621
- //weekly scheduler interval
622
- $schedules['weekly'] = array(
623
- 'interval' => 604800,
624
- 'display' => __('Once Weekly', 'xcloner-backup-and-restore')
625
- );
626
-
627
- //monthly scheduler interval
628
- $schedules['monthly'] = array(
629
- 'interval' => 2635200,
630
- 'display' => __('Once Monthly', 'xcloner-backup-and-restore')
631
- );
632
-
633
- //monthly scheduler interval
634
- $schedules['twicedaily'] = array(
635
- 'interval' => 43200,
636
- 'display' => __('Twice Daily', 'xcloner-backup-and-restore')
637
- );
638
-
639
- return $schedules;
640
- }
641
-
642
-
643
- /**
644
- * Run the loader to execute all of the hooks with WordPress.
645
- *
646
- * @since 1.0.0
647
- */
648
- public function run() {
649
- $this->loader->run();
650
- }
651
-
652
- /**
653
- * The name of the plugin used to uniquely identify it within the context of
654
- * WordPress and to define internationalization functionality.
655
- *
656
- * @since 1.0.0
657
- * @return string The name of the plugin.
658
- */
659
- public function get_plugin_name() {
660
- return $this->plugin_name;
661
- }
662
-
663
- /**
664
- * The reference to the class that orchestrates the hooks with the plugin.
665
- *
666
- * @since 1.0.0
667
- * @return Xcloner_Loader Orchestrates the hooks of the plugin.
668
- */
669
- public function get_loader() {
670
- return $this->loader;
671
- }
672
-
673
- public function xcloner_display()
674
- {
675
- // check user capabilities
676
- if (!current_user_can('manage_options')) {
677
- return;
678
- }
679
-
680
- $page = sanitize_key($_GET['page']);
681
-
682
- if ($page)
683
- {
684
- $this->display($page);
685
- }
686
-
687
- }
688
-
689
- public function display($page)
690
- {
691
- $plugin_admin = new Xcloner_Admin($this);
692
- $this->plugin_admin = $plugin_admin;
693
-
694
- call_user_func_array(array($this->plugin_admin, $page), array());
695
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
696
  }
39
  * @package Xcloner
40
  * @subpackage Xcloner/includes
41
  * @author Liuta Ovidiu <info@thinkovi.com>
42
+ * @link https://watchful.net
43
  */
44
+ class Xcloner
45
+ {
46
+
47
+ /**
48
+ * The loader that's responsible for maintaining and registering all hooks that power
49
+ * the plugin.
50
+ *
51
+ * @since 1.0.0
52
+ * @access protected
53
+ * @var Xcloner_Loader $loader Maintains and registers all hooks for the plugin.
54
+ */
55
+ protected $loader;
56
+
57
+ /**
58
+ * The unique identifier of this plugin.
59
+ *
60
+ * @since 1.0.0
61
+ * @access protected
62
+ * @var string $plugin_name The string used to uniquely identify this plugin.
63
+ */
64
+ protected $plugin_name;
65
+
66
+ protected $plugin_admin;
67
+
68
+ /**
69
+ * The current version of the plugin.
70
+ *
71
+ * @since 1.0.0
72
+ * @access protected
73
+ * @var string $version The current version of the plugin.
74
+ */
75
+ protected $version;
76
+
77
+ private $xcloner_settings;
78
+ private $xcloner_logger;
79
+ private $xcloner_sanitization;
80
+ private $xcloner_requirements;
81
+ private $xcloner_filesystem;
82
+ private $archive_system;
83
+ private $xcloner_database;
84
+ private $xcloner_scheduler;
85
+ private $xcloner_remote_storage;
86
+ private $xcloner_file_transfer;
87
+ private $xcloner_encryption;
88
+
89
+ /**
90
+ * Define the core functionality of the plugin.
91
+ *
92
+ * Set the plugin name and the plugin version that can be used throughout the plugin.
93
+ * Load the dependencies, define the locale, and set the hooks for the admin area and
94
+ * the public-facing side of the site.
95
+ *
96
+ * @since 1.0.0
97
+ */
98
+ public function init()
99
+ {
100
+ $this->log_php_errors();
101
+
102
+ $this->plugin_name = 'xcloner';
103
+ $this->version = '4.0.4';
104
+
105
+ $this->load_dependencies();
106
+ $this->set_locale();
107
+ $this->define_admin_hooks();
108
+ $this->define_public_hooks();
109
+
110
+ $this->define_admin_menu();
111
+ $this->define_plugin_settings();
112
+
113
+ $this->define_ajax_hooks();
114
+ $this->define_cron_hooks();
115
+ }
116
+
117
+ public function log_php_errors(){
118
+ register_shutdown_function(array($this, 'exception_handler'));
119
+ }
120
 
121
  /**
122
  * Dynamic get of class methods get_
124
  * @param $args
125
  * @return mixed
126
  */
127
+ public function __call($property, $args)
128
+ {
129
  $property = str_replace("get_", "", $property);
130
 
131
+ if (property_exists($this, $property)) {
132
  return $this->$property;
133
  }
134
  }
139
  * @param int $length
140
  * @return string
141
  */
142
+ public function randomString($length = 6)
143
+ {
144
  $str = "";
145
  $characters = array_merge(range('A', 'Z'), range('a', 'z'), range('0', '9'));
146
  $max = count($characters) - 1;
151
  return $str;
152
  }
153
 
154
+ public function check_dependencies()
155
+ {
156
+ $backup_storage_path = (get_option('xcloner_store_path'));
 
 
157
 
158
+ if (!$backup_storage_path) {
159
  $backup_storage_path = realpath(__DIR__ . DS . ".." . DS . ".." . DS . "..") . DS . "backups-" . $this->randomString('5') . DS;
160
+ }
161
 
162
+ if (!is_dir($backup_storage_path)) {
163
+ if (!@mkdir($backup_storage_path)) {
 
 
 
 
 
 
 
 
164
  $status = "error";
165
+ $message = sprintf(
166
+ __("Unable to create the Backup Storage Location Folder %s . Please fix this before starting the backup process."),
167
+ $backup_storage_path
168
+ );
169
  $this->trigger_message($message, $status, $backup_storage_path);
 
170
  return;
171
  }
172
+ }
173
+
174
+ if (!is_writable($backup_storage_path)) {
175
+ $status = "error";
176
+ $message = sprintf(
177
+ __("Unable to write to the Backup Storage Location Folder %s . Please fix this before starting the backup process."),
178
+ $backup_storage_path
179
+ );
180
+ $this->trigger_message($message, $status, $backup_storage_path);
181
+
182
+ return;
183
  }
184
 
185
+ update_option("xcloner_store_path", $backup_storage_path);
186
+ }
187
 
188
+ public function trigger_message($message, $status = "error", $message_param1 = "", $message_param2 = "", $message_param3 = "")
189
+ {
190
+ $message = sprintf(__($message), $message_param1, $message_param2, $message_param3);
191
+ add_action('xcloner_admin_notices', array($this, "trigger_message_notice"), 10, 2);
192
+ do_action('xcloner_admin_notices', $message, $status);
193
 
194
+ if (defined(XCLONER_STANDALONE_MODE) && XCLONER_STANDALONE_MODE) {
195
+ throw new Error($message);
196
+ }
197
+ }
 
 
 
 
198
 
199
+ public function trigger_message_notice($message, $status = "success")
200
+ {
201
+ ?>
202
  <div class="notice notice-<?php echo $status?> is-dismissible">
203
  <p><?php _e($message, 'xcloner-backup-and-restore'); ?></p>
204
  </div>
205
  <?php
206
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
+ /**
209
+ * Load the required dependencies for this plugin.
210
+ *
211
+ * Include the following files that make up the plugin:
212
+ *
213
+ * - Xcloner_Loader. Orchestrates the hooks of the plugin.
214
+ * - Xcloner_i18n. Defines internationalization functionality.
215
+ * - Xcloner_Admin. Defines all hooks for the admin area.
216
+ * - Xcloner_Public. Defines all hooks for the public side of the site.
217
+ *
218
+ * Create an instance of the loader which will be used to register the hooks
219
+ * with WordPress.
220
+ *
221
+ * @since 1.0.0
222
+ * @access private
223
+ */
224
+ public function load_dependencies()
225
+ {
226
+
227
+ /**
228
+ * The class responsible for orchestrating the actions and filters of the
229
+ * core plugin.
230
+ */
231
+ require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-loader.php';
232
+
233
+ /**
234
+ * The class responsible for defining internationalization functionality
235
+ * of the plugin.
236
+ */
237
+ require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-i18n.php';
238
+
239
+ /**
240
+ * The class responsible for defining all actions that occur in the admin area.
241
+ */
242
+ require_once plugin_dir_path(dirname(__FILE__)).'admin/class-xcloner-admin.php';
243
+
244
+ /**
245
+ * The class responsible for debugging XCloner.
246
+ */
247
+ require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-logger.php';
248
+
249
+ /**
250
+ * The class responsible for defining the admin settings area.
251
+ */
252
+ require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-settings.php';
253
+
254
+ /**
255
+ * The class responsible for defining the Remote Storage settings area.
256
+ */
257
+ require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-remote-storage.php';
258
+
259
+ /**
260
+ * The class responsible for implementing the database backup methods.
261
+ */
262
+ require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-database.php';
263
+
264
+ /**
265
+ * The class responsible for sanitization of users input.
266
+ */
267
+ require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-sanitization.php';
268
+
269
+ /**
270
+ * The class responsible for XCloner system requirements validation.
271
+ */
272
+ require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-requirements.php';
273
+
274
+ /**
275
+ * The class responsible for XCloner backup archive creation.
276
+ */
277
+ require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-archive.php';
278
+
279
+ /**
280
+ * The class responsible for XCloner API requests.
281
+ */
282
+ require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-api.php';
283
+
284
+ /**
285
+ * The class responsible for the XCloner File System methods.
286
+ */
287
+ require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-file-system.php';
288
+
289
+ /**
290
+ * The class responsible for the XCloner File Transfer methods.
291
+ */
292
+ require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-file-transfer.php';
293
+
294
+ /**
295
+ * The class responsible for the XCloner Scheduler methods.
296
+ */
297
+ require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-scheduler.php';
298
+
299
+ /**
300
+ * The class responsible for the XCloner Encryption methods.
301
+ */
302
+ require_once plugin_dir_path(dirname(__FILE__)).'includes/class-xcloner-encryption.php';
303
+
304
+ /**
305
+ * The class responsible for defining all actions that occur in the public-facing
306
+ * side of the site.
307
+ */
308
+ require_once plugin_dir_path(dirname(__FILE__)).'public/class-xcloner-public.php';
309
+
310
+ $this->loader = new Xcloner_Loader($this);
311
+ }
312
 
313
+ /**
314
+ * Define the locale for this plugin for internationalization.
315
+ *
316
+ * Uses the Xcloner_i18n class in order to set the domain and to register the hook
317
+ * with WordPress.
318
+ *
319
+ * @since 1.0.0
320
+ * @access private
321
+ */
322
+ private function set_locale()
323
+ {
324
+ $plugin_i18n = new Xcloner_i18n();
325
 
326
+ $this->loader->add_action('plugins_loaded', $plugin_i18n, 'load_plugin_textdomain');
 
 
 
 
 
 
327
 
328
+ //wp_localize_script( 'ajax-script', 'my_ajax_object',
329
+ // array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
330
+ }
331
 
332
+ /**
333
+ * Register all of the hooks related to the admin area functionality
334
+ * of the plugin.
335
+ *
336
+ * @since 1.0.0
337
+ * @access private
338
+ */
339
+ private function define_admin_hooks()
340
+ {
341
+ $plugin_admin = new Xcloner_Admin($this);
342
+ $this->plugin_admin = $plugin_admin;
343
 
344
+ $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_styles');
345
+ $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts');
346
 
347
+ $this->loader->add_action('backup_archive_finished', $this, 'do_action_after_backup_finished', 10, 2);
348
+ }
 
 
 
349
 
350
+ /**
351
+ * Register the Admin Sidebar menu
352
+ *
353
+ * @access private
354
+ *
355
+ */
356
+ private function define_admin_menu()
357
+ {
358
+ $this->loader->add_action('admin_menu', $this, 'xcloner_backup_add_admin_menu');
359
+ }
360
 
361
+ public function define_plugin_settings()
362
+ {
363
+ /**
364
+ * register wporg_settings_init to the admin_init action hook
365
+ */
366
+ $this->xcloner_settings = new XCloner_Settings($this);
367
+
368
+ if (defined('DOING_CRON') || isset($_POST['hash'])) {
369
+ if (defined('DOING_CRON') || $_POST['hash'] == "generate_hash") {
370
+ $this->xcloner_settings->generate_new_hash();
371
+ } else {
372
+ $this->xcloner_settings->set_hash($_POST['hash']);
373
+ }
374
+ }
375
 
376
+ if (defined('DOING_CRON') || !isset($_POST['hash'])) {
377
+ $this->loader->add_action('shutdown', $this, 'do_shutdown');
378
+ }
 
 
 
 
379
 
380
+ $this->xcloner_sanitization = new Xcloner_Sanitization();
381
+ $this->xcloner_requirements = new Xcloner_Requirements($this);
 
 
 
 
 
382
 
383
+ $this->loader->add_action('admin_init', $this->xcloner_settings, 'settings_init');
 
 
 
 
 
 
384
 
385
+ //adding links to the Manage Plugins Wordpress page for XCloner
386
+ $this->loader->add_filter('plugin_action_links', $this, 'add_plugin_action_links', 10, 2);
387
+ }
388
 
389
+ /**
390
+ * Shutdown actions
391
+ *
392
+ * @return void
393
+ */
394
+ public function do_shutdown()
395
+ {
396
+ $this->xcloner_filesystem = new Xcloner_File_System($this);
397
+ $this->xcloner_filesystem->remove_tmp_filesystem();
398
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
399
 
400
+ /*
401
+ * @method static $this get_xcloner_logger()
402
+ * @method static $this get_xcloner_settings()
403
+ * type = core|plugin|theme|translation
404
+ */
405
+ public function pre_auto_update($type, $item, $context)
406
+ {
407
+ if (!$type) {
408
+ return false;
 
 
409
  }
410
 
411
+ $exclude_files = array();
412
+ $regex = "";
413
+ $data = "";
414
 
415
+ $this->get_xcloner_logger()->info(sprintf("Doing automatic backup before %s upgrade, pre_auto_update hook.", $type));
416
+
417
+ $content_dir = str_replace(ABSPATH, "", WP_CONTENT_DIR);
418
+ $plugins_dir = str_replace(ABSPATH, "", WP_PLUGIN_DIR);
419
+ $langs_dir = $content_dir.DS."languages";
420
+ $themes_dir = $content_dir.DS."themes";
421
+
422
+ switch ($type) {
423
+ case 'core':
424
+ $exclude_files = array(
425
+ "^(?!(wp-admin|wp-includes|(?!.*\/.*.php)))(.*)$",
426
+ );
427
+ break;
428
+ case 'plugin':
429
+
430
+ $dir_array = explode(DS, $plugins_dir);
431
+
432
+ foreach ($dir_array as $dir) {
433
+ $data .= "\/".$dir;
434
+ $regex .= $data."$|";
435
+ }
436
+
437
+ $regex .= "\/".implode("\/", $dir_array);
438
+
439
+ $exclude_files = array(
440
+ "^(?!(".$regex."))(.*)$",
441
+ );
442
+ break;
443
+ case 'theme':
444
+
445
+ $dir_array = explode(DS, $themes_dir);
446
+
447
+ foreach ($dir_array as $dir) {
448
+ $data .= "\/".$dir;
449
+ $regex .= $data."$|";
450
+ }
451
+
452
+ $regex .= "\/".implode("\/", $dir_array);
453
+
454
+ $exclude_files = array(
455
+ "^(?!(".$regex."))(.*)$",
456
+ );
457
+ break;
458
+ case 'translation':
459
+
460
+ $dir_array = explode(DS, $langs_dir);
461
+
462
+ foreach ($dir_array as $dir) {
463
+ $data .= "\/".$dir;
464
+ $regex .= $data."$|";
465
+ }
466
+
467
+ $regex .= "\/".implode("\/", $dir_array);
468
+
469
+ $exclude_files = array(
470
+ "^(?!(".$regex."))(.*)$",
471
+ );
472
+ break;
473
+ }
474
+
475
+ $schedule = array();
476
+
477
+ $schedule['id'] = 0;
478
+ $schedule['name'] = "pre_auto_update";
479
+ $schedule['recurrence'] = "single";
480
+ $schedule['excluded_files'] = json_encode($exclude_files);
481
+ $schedule['table_params'] = json_encode(array("#" => array($this->get_xcloner_settings()->get_db_database())));
482
+
483
+ $schedule['backup_params'] = new stdClass();
484
+ $schedule['backup_params']->email_notification = get_option('admin_email');
485
+ $schedule['backup_params']->backup_name = "backup_pre_auto_update_".$type."_[domain]-[time]-sql";
486
+
487
+ try {
488
+ $this->xcloner_scheduler->xcloner_scheduler_callback(0, $schedule);
489
+ } catch (Exception $e) {
490
+ $this->get_xcloner_logger()->error($e->getMessage());
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Register all of the hooks related to the public-facing functionality
496
+ * of the plugin.
497
+ *
498
+ * @since 1.0.0
499
+ * @access private
500
+ */
501
+ private function define_public_hooks()
502
+ {
503
+ $plugin_public = new Xcloner_Public($this);
504
+
505
+ $this->loader->add_action('wp_enqueue_scripts', $plugin_public, 'enqueue_styles');
506
+ $this->loader->add_action('wp_enqueue_scripts', $plugin_public, 'enqueue_scripts');
507
+ }
508
+
509
+ public function exception_handler()
510
+ {
511
+ $logger = new XCloner_Logger($this, "php_system");
512
+ $error = error_get_last();
513
+
514
+ if ($error['type'] and $error['type'] === E_ERROR and $logger) {
515
+ $logger->error($this->friendly_error_type($error['type']).": ".var_export($error, true));
516
+ } elseif ($error['type'] and $logger) {
517
+ $logger->debug($this->friendly_error_type($error['type']).": ".var_export($error, true));
518
+ }
519
+ }
520
+
521
+ public function friendly_error_type($type)
522
+ {
523
+ static $levels = null;
524
+ if ($levels === null) {
525
+ $levels = [];
526
+ foreach (get_defined_constants() as $key=>$value) {
527
+ if (strpos($key, 'E_') !== 0) {
528
+ continue;
529
+ }
530
+ $levels[$value] = $key; //substr($key,2);
531
+ }
532
+ }
533
+ return (isset($levels[$type]) ? $levels[$type] : "Error #{$type}");
534
+ }
535
 
536
  /**
537
  * @method get_xcloner_settings()
538
  * @throws Exception
539
  */
540
+ public function define_ajax_hooks()
541
+ {
542
+ //adding the pre-update hook
543
+
544
+ if (is_admin() || defined('DOING_CRON')) {
545
+ $this->xcloner_logger = new XCloner_Logger($this, "xcloner_api");
546
+ $this->xcloner_filesystem = new Xcloner_File_System($this);
547
+
548
+ //$this->xcloner_filesystem->set_diff_timestamp_start (strtotime("-15 days"));
549
+
550
+ $this->archive_system = new Xcloner_Archive($this);
551
+ $this->xcloner_database = new Xcloner_Database($this);
552
+ $this->xcloner_scheduler = new Xcloner_Scheduler($this);
553
+ $this->xcloner_remote_storage = new Xcloner_Remote_Storage($this);
554
+ $this->xcloner_file_transfer = new Xcloner_File_Transfer($this);
555
+ $this->xcloner_encryption = new Xcloner_Encryption($this);
556
+
557
+ $xcloner_api = new Xcloner_Api($this);
558
+
559
+
560
+ $this->loader->add_action('wp_ajax_get_database_tables_action', $xcloner_api, 'get_database_tables_action');
561
+ $this->loader->add_action('wp_ajax_get_file_system_action', $xcloner_api, 'get_file_system_action');
562
+ $this->loader->add_action('wp_ajax_scan_filesystem', $xcloner_api, 'scan_filesystem');
563
+ $this->loader->add_action('wp_ajax_backup_database', $xcloner_api, 'backup_database');
564
+ $this->loader->add_action('wp_ajax_backup_files', $xcloner_api, 'backup_files');
565
+ $this->loader->add_action('wp_ajax_save_schedule', $xcloner_api, 'save_schedule');
566
+ $this->loader->add_action('wp_ajax_get_schedule_by_id', $xcloner_api, 'get_schedule_by_id');
567
+ $this->loader->add_action('wp_ajax_get_scheduler_list', $xcloner_api, 'get_scheduler_list');
568
+ $this->loader->add_action('wp_ajax_delete_schedule_by_id', $xcloner_api, 'delete_schedule_by_id');
569
+ $this->loader->add_action('wp_ajax_delete_backup_by_name', $xcloner_api, 'delete_backup_by_name');
570
+ $this->loader->add_action('wp_ajax_download_backup_by_name', $xcloner_api, 'download_backup_by_name');
571
+ $this->loader->add_action('wp_ajax_remote_storage_save_status', $xcloner_api, 'remote_storage_save_status');
572
+ $this->loader->add_action('wp_ajax_upload_backup_to_remote', $xcloner_api, 'upload_backup_to_remote');
573
+ $this->loader->add_action('wp_ajax_list_backup_files', $xcloner_api, 'list_backup_files');
574
+ $this->loader->add_action('wp_ajax_restore_upload_backup', $xcloner_api, 'restore_upload_backup');
575
+ $this->loader->add_action('wp_ajax_download_restore_script', $xcloner_api, 'download_restore_script');
576
+ $this->loader->add_action('wp_ajax_copy_backup_remote_to_local', $xcloner_api, 'copy_backup_remote_to_local');
577
+ $this->loader->add_action('wp_ajax_restore_backup', $xcloner_api, 'restore_backup');
578
+ $this->loader->add_action('wp_ajax_backup_encryption', $xcloner_api, 'backup_encryption');
579
+ $this->loader->add_action('wp_ajax_backup_decryption', $xcloner_api, 'backup_decryption');
580
+ $this->loader->add_action('wp_ajax_get_manage_backups_list', $xcloner_api, 'get_manage_backups_list');
581
+ $this->loader->add_action('admin_notices', $this, 'xcloner_error_admin_notices');
582
+ }
583
+
584
+ //Do a pre-update backup of targeted files
585
+ if ($this->get_xcloner_settings()->get_xcloner_option('xcloner_enable_pre_update_backup')) {
586
+ $this->loader->add_action("pre_auto_update", $this, "pre_auto_update", 1, 3);
587
+ }
588
+ }
589
+
590
+ public function add_plugin_action_links($links, $file)
591
+ {
592
+ if ($file == plugin_basename(dirname(dirname(__FILE__)).'/xcloner.php')) {
593
+ $links[] = '<a href="admin.php?page=xcloner_settings_page">'.__('Settings', 'xcloner-backup-and-restore').'</a>';
594
+ $links[] = '<a href="admin.php?page=xcloner_generate_backups_page">'.__('Generate Backup', 'xcloner-backup-and-restore').'</a>';
595
+ }
596
+
597
+ return $links;
598
+ }
599
+
600
+ public function xcloner_error_admin_notices()
601
+ {
602
+ settings_errors('xcloner_error_message');
603
+ }
 
604
 
605
  /**
606
  * @method get_xcloner_scheduler()
607
  */
608
+ public function define_cron_hooks()
609
+ {
610
+ //registering new schedule intervals
611
+ add_filter('cron_schedules', array($this, 'add_new_intervals'));
612
+
613
+
614
+ $xcloner_scheduler = $this->get_xcloner_scheduler();
615
+ $xcloner_scheduler->update_wp_cron_hooks();
616
+ }
617
+
618
+ /**
619
+ * @param $schedules
620
+ * @return mixed
621
+ */
622
+ public function add_new_intervals($schedules)
623
+ {
624
+ //weekly scheduler interval
625
+ $schedules['weekly'] = array(
626
+ 'interval' => 604800,
627
+ 'display' => __('Once Weekly', 'xcloner-backup-and-restore')
628
+ );
629
+
630
+ //monthly scheduler interval
631
+ $schedules['monthly'] = array(
632
+ 'interval' => 2635200,
633
+ 'display' => __('Once Monthly', 'xcloner-backup-and-restore')
634
+ );
635
+
636
+ //monthly scheduler interval
637
+ $schedules['twicedaily'] = array(
638
+ 'interval' => 43200,
639
+ 'display' => __('Twice Daily', 'xcloner-backup-and-restore')
640
+ );
641
+
642
+ return $schedules;
643
+ }
644
+
645
+ /**
646
+ * Add XCloner to Admin Menu
647
+ */
648
+ public function xcloner_backup_add_admin_menu()
649
+ {
650
+ if (function_exists('add_menu_page')) {
651
+ add_menu_page(
652
+ __('Site Backup', 'xcloner-backup-and-restore'),
653
+ __('Site Backup', 'xcloner-backup-and-restore'),
654
+ 'manage_options',
655
+ 'xcloner_init_page',
656
+ array($this, 'xcloner_display'),
657
+ 'dashicons-backup'
658
+ );
659
+ }
660
+
661
+ if (function_exists('add_submenu_page')) {
662
+ add_submenu_page(
663
+ 'xcloner_init_page',
664
+ __('XCloner Dashboard', 'xcloner-backup-and-restore'),
665
+ __('Dashboard', 'xcloner-backup-and-restore'),
666
+ 'manage_options',
667
+ 'xcloner_init_page',
668
+ array($this, 'xcloner_display')
669
+ );
670
+ add_submenu_page(
671
+ 'xcloner_init_page',
672
+ __('XCloner Backup Settings', 'xcloner-backup-and-restore'),
673
+ __('Settings', 'xcloner-backup-and-restore'),
674
+ 'manage_options',
675
+ 'xcloner_settings_page',
676
+ array($this, 'xcloner_display')
677
+ );
678
+ add_submenu_page(
679
+ 'xcloner_init_page',
680
+ __('Remote Storage Settings', 'xcloner-backup-and-restore'),
681
+ __('Remote Storage', 'xcloner-backup-and-restore'),
682
+ 'manage_options',
683
+ 'xcloner_remote_storage_page',
684
+ array($this, 'xcloner_display')
685
+ );
686
+ add_submenu_page(
687
+ 'xcloner_init_page',
688
+ __('Manage Backups', 'xcloner-backup-and-restore'),
689
+ __('Manage Backups', 'xcloner-backup-and-restore'),
690
+ 'manage_options',
691
+ 'xcloner_manage_backups_page',
692
+ array($this, 'xcloner_display')
693
+ );
694
+ add_submenu_page(
695
+ 'xcloner_init_page',
696
+ __('Scheduled Backups', 'xcloner-backup-and-restore'),
697
+ __('Scheduled Backups', 'xcloner-backup-and-restore'),
698
+ 'manage_options',
699
+ 'xcloner_scheduled_backups_page',
700
+ array($this, 'xcloner_display')
701
+ );
702
+ add_submenu_page(
703
+ 'xcloner_init_page',
704
+ __('Generate Backups', 'xcloner-backup-and-restore'),
705
+ __('Generate Backups', 'xcloner-backup-and-restore'),
706
+ 'manage_options',
707
+ 'xcloner_generate_backups_page',
708
+ array($this, 'xcloner_display')
709
+ );
710
+ add_submenu_page(
711
+ 'xcloner_init_page',
712
+ __('Restore Backups', 'xcloner-backup-and-restore'),
713
+ __('Restore Backups', 'xcloner-backup-and-restore'),
714
+ 'manage_options',
715
+ 'xcloner_restore_page',
716
+ array($this, 'xcloner_display')
717
+ );
718
+ }
719
+ }
720
+
721
+ /**
722
+ * Run the loader to execute all of the hooks with WordPress.
723
+ *
724
+ * @since 1.0.0
725
+ */
726
+ public function run()
727
+ {
728
+ $this->loader->run();
729
+ }
730
+
731
+ /**
732
+ * The name of the plugin used to uniquely identify it within the context of
733
+ * WordPress and to define internationalization functionality.
734
+ *
735
+ * @since 1.0.0
736
+ * @return string The name of the plugin.
737
+ */
738
+ public function get_plugin_name()
739
+ {
740
+ return $this->plugin_name;
741
+ }
742
+
743
+ /**
744
+ * The reference to the class that orchestrates the hooks with the plugin.
745
+ *
746
+ * @since 1.0.0
747
+ * @return Xcloner_Loader Orchestrates the hooks of the plugin.
748
+ */
749
+ public function get_loader()
750
+ {
751
+ return $this->loader;
752
+ }
753
+
754
+ public function xcloner_display()
755
+ {
756
+ // check user capabilities
757
+ if (!current_user_can('manage_options')) {
758
+ return;
759
+ }
760
+
761
+ $page = sanitize_key($_GET['page']);
762
+
763
+ if ($page) {
764
+ $this->display($page);
765
+ }
766
+ }
767
+
768
+ public function display($page)
769
+ {
770
+ $plugin_admin = new Xcloner_Admin($this);
771
+ $this->plugin_admin = $plugin_admin;
772
+
773
+ call_user_func_array(array($this->plugin_admin, $page), array());
774
+ }
775
  }
lib/class-wp-error.php ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WordPress Error API.
4
+ *
5
+ * Contains the WP_Error class and the is_wp_error() function.
6
+ *
7
+ * @package WordPress
8
+ */
9
+
10
+ /**
11
+ * WordPress Error class.
12
+ *
13
+ * Container for checking for WordPress errors and error messages. Return
14
+ * WP_Error and use is_wp_error() to check if this class is returned. Many
15
+ * core WordPress functions pass this class in the event of an error and
16
+ * if not handled properly will result in code errors.
17
+ *
18
+ * @since 2.1.0
19
+ */
20
+ class WP_Error {
21
+ /**
22
+ * Stores the list of errors.
23
+ *
24
+ * @since 2.1.0
25
+ * @var array
26
+ */
27
+ public $errors = array();
28
+
29
+ /**
30
+ * Stores the list of data for error codes.
31
+ *
32
+ * @since 2.1.0
33
+ * @var array
34
+ */
35
+ public $error_data = array();
36
+
37
+ /**
38
+ * Initialize the error.
39
+ *
40
+ * If `$code` is empty, the other parameters will be ignored.
41
+ * When `$code` is not empty, `$message` will be used even if
42
+ * it is empty. The `$data` parameter will be used only if it
43
+ * is not empty.
44
+ *
45
+ * Though the class is constructed with a single error code and
46
+ * message, multiple codes can be added using the `add()` method.
47
+ *
48
+ * @since 2.1.0
49
+ *
50
+ * @param string|int $code Error code
51
+ * @param string $message Error message
52
+ * @param mixed $data Optional. Error data.
53
+ */
54
+ public function __construct( $code = '', $message = '', $data = '' ) {
55
+ if ( empty( $code ) ) {
56
+ return;
57
+ }
58
+
59
+ $this->errors[ $code ][] = $message;
60
+
61
+ if ( ! empty( $data ) ) {
62
+ $this->error_data[ $code ] = $data;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Retrieve all error codes.
68
+ *
69
+ * @since 2.1.0
70
+ *
71
+ * @return array List of error codes, if available.
72
+ */
73
+ public function get_error_codes() {
74
+ if ( ! $this->has_errors() ) {
75
+ return array();
76
+ }
77
+
78
+ return array_keys( $this->errors );
79
+ }
80
+
81
+ /**
82
+ * Retrieve first error code available.
83
+ *
84
+ * @since 2.1.0
85
+ *
86
+ * @return string|int Empty string, if no error codes.
87
+ */
88
+ public function get_error_code() {
89
+ $codes = $this->get_error_codes();
90
+
91
+ if ( empty( $codes ) ) {
92
+ return '';
93
+ }
94
+
95
+ return $codes[0];
96
+ }
97
+
98
+ /**
99
+ * Retrieve all error messages or error messages matching code.
100
+ *
101
+ * @since 2.1.0
102
+ *
103
+ * @param string|int $code Optional. Retrieve messages matching code, if exists.
104
+ * @return array Error strings on success, or empty array on failure (if using code parameter).
105
+ */
106
+ public function get_error_messages( $code = '' ) {
107
+ // Return all messages if no code specified.
108
+ if ( empty( $code ) ) {
109
+ $all_messages = array();
110
+ foreach ( (array) $this->errors as $code => $messages ) {
111
+ $all_messages = array_merge( $all_messages, $messages );
112
+ }
113
+
114
+ return $all_messages;
115
+ }
116
+
117
+ if ( isset( $this->errors[ $code ] ) ) {
118
+ return $this->errors[ $code ];
119
+ } else {
120
+ return array();
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Get single error message.
126
+ *
127
+ * This will get the first message available for the code. If no code is
128
+ * given then the first code available will be used.
129
+ *
130
+ * @since 2.1.0
131
+ *
132
+ * @param string|int $code Optional. Error code to retrieve message.
133
+ * @return string
134
+ */
135
+ public function get_error_message( $code = '' ) {
136
+ if ( empty( $code ) ) {
137
+ $code = $this->get_error_code();
138
+ }
139
+ $messages = $this->get_error_messages( $code );
140
+ if ( empty( $messages ) ) {
141
+ return '';
142
+ }
143
+ return $messages[0];
144
+ }
145
+
146
+ /**
147
+ * Retrieve error data for error code.
148
+ *
149
+ * @since 2.1.0
150
+ *
151
+ * @param string|int $code Optional. Error code.
152
+ * @return mixed Error data, if it exists.
153
+ */
154
+ public function get_error_data( $code = '' ) {
155
+ if ( empty( $code ) ) {
156
+ $code = $this->get_error_code();
157
+ }
158
+
159
+ if ( isset( $this->error_data[ $code ] ) ) {
160
+ return $this->error_data[ $code ];
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Verify if the instance contains errors.
166
+ *
167
+ * @since 5.1.0
168
+ *
169
+ * @return bool
170
+ */
171
+ public function has_errors() {
172
+ if ( ! empty( $this->errors ) ) {
173
+ return true;
174
+ }
175
+ return false;
176
+ }
177
+
178
+ /**
179
+ * Add an error or append additional message to an existing error.
180
+ *
181
+ * @since 2.1.0
182
+ *
183
+ * @param string|int $code Error code.
184
+ * @param string $message Error message.
185
+ * @param mixed $data Optional. Error data.
186
+ */
187
+ public function add( $code, $message, $data = '' ) {
188
+ $this->errors[ $code ][] = $message;
189
+ if ( ! empty( $data ) ) {
190
+ $this->error_data[ $code ] = $data;
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Add data for error code.
196
+ *
197
+ * The error code can only contain one error data.
198
+ *
199
+ * @since 2.1.0
200
+ *
201
+ * @param mixed $data Error data.
202
+ * @param string|int $code Error code.
203
+ */
204
+ public function add_data( $data, $code = '' ) {
205
+ if ( empty( $code ) ) {
206
+ $code = $this->get_error_code();
207
+ }
208
+
209
+ $this->error_data[ $code ] = $data;
210
+ }
211
+
212
+ /**
213
+ * Removes the specified error.
214
+ *
215
+ * This function removes all error messages associated with the specified
216
+ * error code, along with any error data for that code.
217
+ *
218
+ * @since 4.1.0
219
+ *
220
+ * @param string|int $code Error code.
221
+ */
222
+ public function remove( $code ) {
223
+ unset( $this->errors[ $code ] );
224
+ unset( $this->error_data[ $code ] );
225
+ }
226
+ }
lib/index.html ADDED
File without changes
lib/mock_wp_functions.php ADDED
@@ -0,0 +1,452 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ $wp_settings_errors = "";
3
+
4
+ //NONCE_KEY
5
+ //NONCE_SALT
6
+ //ABSPATH
7
+
8
+ if (!defined('WP_DEBUG')) {
9
+ define('WP_DEBUG', false);
10
+ }
11
+
12
+ if (!defined('WPINC')) {
13
+ define('WPINC', false);
14
+ }
15
+
16
+ if (!defined('DOING_CRON')) {
17
+ define('DOING_CRON', false);
18
+ }
19
+
20
+ if (!defined('WP_DEBUG_DISPLAY')) {
21
+ define('WP_DEBUG_DISPLAY', false);
22
+ }
23
+
24
+ if (!defined('WP_CONTENT_DIR')) {
25
+ define('WP_CONTENT_DIR', __DIR__);
26
+ }
27
+
28
+ if (!defined('DS')) {
29
+ define('DS', DIRECTORY_SEPARATOR);
30
+ }
31
+
32
+ if (!defined('MINUTE_IN_SECONDS')) {
33
+ define('MINUTE_IN_SECONDS', 60);
34
+ }
35
+
36
+ if (!defined('MINUTE_IN_SECONDS')) {
37
+ define('HOUR_IN_SECONDS', 60 * MINUTE_IN_SECONDS);
38
+ }
39
+
40
+ if (!defined('MINUTE_IN_SECONDS')) {
41
+ define('DAY_IN_SECONDS', 24 * HOUR_IN_SECONDS);
42
+ }
43
+
44
+ if (!defined('MINUTE_IN_SECONDS')) {
45
+ define('WEEK_IN_SECONDS', 7 * DAY_IN_SECONDS);
46
+ }
47
+
48
+ if (!defined('MINUTE_IN_SECONDS')) {
49
+ define('MONTH_IN_SECONDS', 30 * DAY_IN_SECONDS);
50
+ }
51
+
52
+ if (!defined('MINUTE_IN_SECONDS')) {
53
+ define('YEAR_IN_SECONDS', 365 * DAY_IN_SECONDS);
54
+ }
55
+
56
+ if (!defined('MINUTE_IN_SECONDS')) {
57
+ define('WPINC', true);
58
+ }
59
+
60
+ if(!class_exists('WP_Error')) {
61
+ require_once(__DIR__ . '/class-wp-error.php');
62
+ }
63
+
64
+ if (!function_exists('is_wp_error')) {
65
+ function is_wp_error($thing)
66
+ {
67
+ return ($thing instanceof WP_Error);
68
+ }
69
+ }
70
+
71
+ if (!function_exists('get_site_url')) {
72
+ function get_site_url()
73
+ {
74
+ return __DIR__;
75
+ }
76
+ }
77
+
78
+ if (!function_exists('admin_url')) {
79
+ function admin_url()
80
+ {
81
+ return __DIR__;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Sanitize plugin dir path
87
+ *
88
+ * @param [type] $path
89
+ * @return void
90
+ */
91
+ if (!function_exists('plugin_dir_path')) {
92
+ function plugin_dir_path($path)
93
+ {
94
+ return dirname($path).DS;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Undocumented function
100
+ *
101
+ * @param [type] $setting
102
+ * @param [type] $code
103
+ * @param [type] $message
104
+ * @param string $type
105
+ * @return void
106
+ */
107
+ if (!function_exists('add_settings_error')) {
108
+ function add_settings_error($setting, $code, $message, $type = 'error')
109
+ {
110
+ global $wp_settings_errors;
111
+
112
+ $wp_settings_errors[] = array(
113
+ 'setting' => $setting,
114
+ 'code' => $code,
115
+ 'message' => $message,
116
+ 'type' => $type,
117
+ );
118
+ }
119
+ }
120
+
121
+ if (!function_exists('has_filter')) {
122
+ function has_filter($tag, $function_to_check = false)
123
+ {
124
+ return false;
125
+ }
126
+ }
127
+
128
+ if (!function_exists('mbstring_binary_safe_encoding')) {
129
+ function mbstring_binary_safe_encoding($reset = false)
130
+ {
131
+ static $encodings = array();
132
+ static $overloaded = null;
133
+
134
+ if (is_null($overloaded)) {
135
+ $overloaded = function_exists('mb_internal_encoding') && (ini_get('mbstring.func_overload') & 2);
136
+ }
137
+
138
+ if (false === $overloaded) {
139
+ return;
140
+ }
141
+
142
+ if (! $reset) {
143
+ $encoding = mb_internal_encoding();
144
+ array_push($encodings, $encoding);
145
+ mb_internal_encoding('ISO-8859-1');
146
+ }
147
+
148
+ if ($reset && $encodings) {
149
+ $encoding = array_pop($encodings);
150
+ mb_internal_encoding($encoding);
151
+ }
152
+ }
153
+ }
154
+
155
+ if (!function_exists('reset_mbstring_encoding')) {
156
+ function reset_mbstring_encoding()
157
+ {
158
+ mbstring_binary_safe_encoding(true);
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Get option
164
+ *
165
+ * @param [type] $option_name
166
+ * @return void
167
+ */
168
+ if (!function_exists('get_option')) {
169
+ function get_option($option_name = "")
170
+ {
171
+ if (!$option_name) {
172
+ return $GLOBALS['xcloner_settings'];
173
+ }
174
+
175
+ if(!isset($GLOBALS['xcloner_settings'][$option_name])){
176
+ return null;
177
+ }
178
+
179
+ return $GLOBALS['xcloner_settings'][$option_name];
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Add option
185
+ *
186
+ * @param [type] $option_name
187
+ * @param string $value
188
+ * @return void
189
+ */
190
+ if (!function_exists('add_option')) {
191
+ function add_option($option_name, $value="")
192
+ {
193
+ return $GLOBALS['xcloner_settings'][$option_name] = $value;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Update option or create if it doesn't exist
199
+ *
200
+ * @param [type] $option_name
201
+ * @param string $value
202
+ * @return void
203
+ */
204
+ if (!function_exists('update_option')) {
205
+ function update_option($option_name, $value="")
206
+ {
207
+ return add_option($option_name, $value);
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Die script
213
+ */
214
+ if (!function_exists('wp_die')) {
215
+ function wp_die($msg)
216
+ {
217
+ die($msg);
218
+ }
219
+ }
220
+
221
+ /**
222
+ *
223
+ */
224
+ if (!function_exists('dbDelta')) {
225
+ function dbDelta($sql)
226
+ {
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Custom Watchfull backend check
232
+ */
233
+ if (!function_exists('is_admin')) {
234
+ function is_admin()
235
+ {
236
+ return true;
237
+ }
238
+ }
239
+
240
+ /**
241
+ *
242
+ */
243
+ if (!function_exists('register_activation_hook')) {
244
+ function register_activation_hook($path, $hook)
245
+ {
246
+ }
247
+ }
248
+
249
+ /**
250
+ *
251
+ */
252
+ if (!function_exists('register_deactivation_hook')) {
253
+ function register_deactivation_hook($path, $hook)
254
+ {
255
+ }
256
+ }
257
+
258
+ /**
259
+ *
260
+ */
261
+ if (!function_exists('deactivate_plugins')) {
262
+ function deactivate_plugins($path)
263
+ {
264
+ }
265
+ }
266
+
267
+ /**
268
+ *
269
+ */
270
+ if (!function_exists('wp_deregister_script')) {
271
+ function wp_deregister_script($path)
272
+ {
273
+ }
274
+ }
275
+
276
+
277
+ /**
278
+ *
279
+ */
280
+ if (!function_exists('add_action')) {
281
+ function add_action($hook, $callback)
282
+ {
283
+ if(substr($hook, 0, 8) == "wp_ajax_") {
284
+ $request = "wp_ajax_".$_REQUEST['action'];
285
+ if($request === $hook){
286
+ //print_r($callback[1]);
287
+ //exit;
288
+ return call_user_func($callback);
289
+ }
290
+ }
291
+ }
292
+ }
293
+
294
+ /**
295
+ *
296
+ */
297
+ if (!function_exists('do_action')) {
298
+ function do_action($hook)
299
+ {
300
+
301
+ }
302
+ }
303
+
304
+ /**
305
+ *
306
+ */
307
+ if (!function_exists('add_filter')) {
308
+ function add_filter($hook)
309
+ {
310
+ //echo $hook;
311
+ }
312
+ }
313
+
314
+ /**
315
+ *
316
+ */
317
+ if (!function_exists('apply_filters')) {
318
+ function apply_filters($tag, $value)
319
+ {
320
+ return $value;
321
+ }
322
+ }
323
+
324
+ /**
325
+ *
326
+ */
327
+ if (!function_exists('do_filter')) {
328
+ function do_filter2($hook)
329
+ {
330
+ }
331
+ }
332
+
333
+ /**
334
+ *
335
+ */
336
+ if (!function_exists('_e')) {
337
+ function _e($str)
338
+ {
339
+ return $str;
340
+ }
341
+ }
342
+
343
+ /**
344
+ *
345
+ */
346
+ if (!function_exists('plugin_basename')) {
347
+ function plugin_basename($path)
348
+ {
349
+ return $path;
350
+ }
351
+ }
352
+
353
+ /**
354
+ *
355
+ */
356
+ if (!function_exists('settings_error')) {
357
+ function settings_error($error)
358
+ {
359
+ return $error;
360
+ }
361
+ }
362
+
363
+ /**
364
+ *
365
+ */
366
+ if (!function_exists('add_menu_page')) {
367
+ function add_menu_page()
368
+ {
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Get Home Url
374
+ *
375
+ * @return path
376
+ */
377
+ if (!function_exists('get_home_url')) {
378
+ function get_home_url()
379
+ {
380
+ return __DIR__;
381
+ }
382
+ }
383
+
384
+ if (!function_exists('wp_load_translations_early')) {
385
+ function wp_load_translations_early()
386
+ {
387
+ return null;
388
+ }
389
+ }
390
+
391
+ /**
392
+ * Translate string if available
393
+ *
394
+ * @return string
395
+ */
396
+ if (!function_exists('__')) {
397
+ function __($string)
398
+ {
399
+ return $string;
400
+ }
401
+ }
402
+
403
+ if (!function_exists('wp_send_json')) {
404
+ function wp_send_json( $response, $status_code = null ) {
405
+ //return $response;
406
+ die(json_encode($response));
407
+ }
408
+ }
409
+
410
+ if (!function_exists('is_localhost')) {
411
+ function is_localhost($whitelist = ['127.0.0.1', '::1']) {
412
+ return in_array($_SERVER['REMOTE_ADDR'], $whitelist);
413
+ }
414
+ }
415
+
416
+ if (!function_exists('esc_html')) {
417
+ function esc_html( $text ) {
418
+ return $text;
419
+ }
420
+ }
421
+
422
+ if (!function_exists('size_format')) {
423
+ function size_format( $bytes, $decimals = 0 ) {
424
+ return $bytes;
425
+ }
426
+ }
427
+ if (!function_exists('wp_mail')) {
428
+ function wp_mail(){
429
+
430
+ }
431
+ }
432
+
433
+ // function current_user_can(){}
434
+ // function sanitize_key(){}
435
+ // function plugin_dir_url() {}
436
+ // function human_time_diff() {}
437
+ // function size_format(){}
438
+ // function wp_get_schedules() {}
439
+ // function wp_send_json() {}
440
+ // function get_site_url() {}
441
+ // function get_home_url() {}
442
+ // function wp_mail() {}
443
+ // function __(){}
444
+ // function admin_url(){}
445
+ // function load_plugin_textdomain() {}
446
+ // function wp_clear_scheduled_hook() {}
447
+ // function wp_unschedule_event() {}
448
+ // function wp_schedule_event() {}
449
+ // function wp_next_scheduled() {}
450
+ // function wp_schedule_single_event() {}
451
+ // function add_settings_section() {}
452
+ // function register_setting() {}
lib/wp-db.php ADDED
@@ -0,0 +1,3555 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WordPress DB Class
4
+ *
5
+ * Original code from {@link http://php.justinvincent.com Justin Vincent (justin@visunet.ie)}
6
+ *
7
+ * @package WordPress
8
+ * @subpackage Database
9
+ * @since 0.71
10
+ */
11
+
12
+ /**
13
+ * @since 0.71
14
+ */
15
+ define( 'EZSQL_VERSION', 'WP1.25' );
16
+
17
+ /**
18
+ * @since 0.71
19
+ */
20
+ define( 'OBJECT', 'OBJECT' );
21
+ // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ConstantNotUpperCase
22
+ define( 'object', 'OBJECT' ); // Back compat.
23
+
24
+ /**
25
+ * @since 2.5.0
26
+ */
27
+ define( 'OBJECT_K', 'OBJECT_K' );
28
+
29
+ /**
30
+ * @since 0.71
31
+ */
32
+ define( 'ARRAY_A', 'ARRAY_A' );
33
+
34
+ /**
35
+ * @since 0.71
36
+ */
37
+ define( 'ARRAY_N', 'ARRAY_N' );
38
+
39
+ /**
40
+ * WordPress Database Access Abstraction Object
41
+ *
42
+ * It is possible to replace this class with your own
43
+ * by setting the $wpdb global variable in wp-content/db.php
44
+ * file to your class. The wpdb class will still be included,
45
+ * so you can extend it or simply use your own.
46
+ *
47
+ * @link https://codex.wordpress.org/Function_Reference/wpdb_Class
48
+ *
49
+ * @since 0.71
50
+ */
51
+ class wpdb {
52
+
53
+ /**
54
+ * Whether to show SQL/DB errors.
55
+ *
56
+ * Default behavior is to show errors if both WP_DEBUG and WP_DEBUG_DISPLAY
57
+ * evaluated to true.
58
+ *
59
+ * @since 0.71
60
+ * @var bool
61
+ */
62
+ var $show_errors = false;
63
+
64
+ /**
65
+ * Whether to suppress errors during the DB bootstrapping.
66
+ *
67
+ * @since 2.5.0
68
+ * @var bool
69
+ */
70
+ var $suppress_errors = false;
71
+
72
+ /**
73
+ * The last error during query.
74
+ *
75
+ * @since 2.5.0
76
+ * @var string
77
+ */
78
+ public $last_error = '';
79
+
80
+ /**
81
+ * Amount of queries made
82
+ *
83
+ * @since 1.2.0
84
+ * @var int
85
+ */
86
+ public $num_queries = 0;
87
+
88
+ /**
89
+ * Count of rows returned by previous query
90
+ *
91
+ * @since 0.71
92
+ * @var int
93
+ */
94
+ public $num_rows = 0;
95
+
96
+ /**
97
+ * Count of affected rows by previous query
98
+ *
99
+ * @since 0.71
100
+ * @var int
101
+ */
102
+ var $rows_affected = 0;
103
+
104
+ /**
105
+ * The ID generated for an AUTO_INCREMENT column by the previous query (usually INSERT).
106
+ *
107
+ * @since 0.71
108
+ * @var int
109
+ */
110
+ public $insert_id = 0;
111
+
112
+ /**
113
+ * Last query made
114
+ *
115
+ * @since 0.71
116
+ * @var array
117
+ */
118
+ var $last_query;
119
+
120
+ /**
121
+ * Results of the last query made
122
+ *
123
+ * @since 0.71
124
+ * @var array|null
125
+ */
126
+ var $last_result;
127
+
128
+ /**
129
+ * MySQL result, which is either a resource or boolean.
130
+ *
131
+ * @since 0.71
132
+ * @var mixed
133
+ */
134
+ protected $result;
135
+
136
+ /**
137
+ * Cached column info, for sanity checking data before inserting
138
+ *
139
+ * @since 4.2.0
140
+ * @var array
141
+ */
142
+ protected $col_meta = array();
143
+
144
+ /**
145
+ * Calculated character sets on tables
146
+ *
147
+ * @since 4.2.0
148
+ * @var array
149
+ */
150
+ protected $table_charset = array();
151
+
152
+ /**
153
+ * Whether text fields in the current query need to be sanity checked.
154
+ *
155
+ * @since 4.2.0
156
+ * @var bool
157
+ */
158
+ protected $check_current_query = true;
159
+
160
+ /**
161
+ * Flag to ensure we don't run into recursion problems when checking the collation.
162
+ *
163
+ * @since 4.2.0
164
+ * @see wpdb::check_safe_collation()
165
+ * @var bool
166
+ */
167
+ private $checking_collation = false;
168
+
169
+ /**
170
+ * Saved info on the table column
171
+ *
172
+ * @since 0.71
173
+ * @var array
174
+ */
175
+ protected $col_info;
176
+
177
+ /**
178
+ * Log of queries that were executed, for debugging purposes.
179
+ *
180
+ * @since 1.5.0
181
+ * @since 2.5.0 The third element in each query log was added to record the calling functions.
182
+ * @since 5.1.0 The fourth element in each query log was added to record the start time.
183
+ *
184
+ * @var array[] {
185
+ * Array of queries that were executed.
186
+ *
187
+ * @type array ...$0 {
188
+ * Data for each query.
189
+ *
190
+ * @type string $0 The query's SQL.
191
+ * @type float $1 Total time spent on the query, in seconds.
192
+ * @type string $2 Comma separated list of the calling functions.
193
+ * @type float $3 Unix timestamp of the time at the start of the query.
194
+ * }
195
+ * }
196
+ */
197
+ var $queries;
198
+
199
+ /**
200
+ * The number of times to retry reconnecting before dying.
201
+ *
202
+ * @since 3.9.0
203
+ * @see wpdb::check_connection()
204
+ * @var int
205
+ */
206
+ protected $reconnect_retries = 5;
207
+
208
+ /**
209
+ * WordPress table prefix
210
+ *
211
+ * You can set this to have multiple WordPress installations
212
+ * in a single database. The second reason is for possible
213
+ * security precautions.
214
+ *
215
+ * @since 2.5.0
216
+ * @var string
217
+ */
218
+ public $prefix = '';
219
+
220
+ /**
221
+ * WordPress base table prefix.
222
+ *
223
+ * @since 3.0.0
224
+ * @var string
225
+ */
226
+ public $base_prefix;
227
+
228
+ /**
229
+ * Whether the database queries are ready to start executing.
230
+ *
231
+ * @since 2.3.2
232
+ * @var bool
233
+ */
234
+ var $ready = false;
235
+
236
+ /**
237
+ * Blog ID.
238
+ *
239
+ * @since 3.0.0
240
+ * @var int
241
+ */
242
+ public $blogid = 0;
243
+
244
+ /**
245
+ * Site ID.
246
+ *
247
+ * @since 3.0.0
248
+ * @var int
249
+ */
250
+ public $siteid = 0;
251
+
252
+ /**
253
+ * List of WordPress per-blog tables
254
+ *
255
+ * @since 2.5.0
256
+ * @see wpdb::tables()
257
+ * @var array
258
+ */
259
+ var $tables = array(
260
+ 'posts',
261
+ 'comments',
262
+ 'links',
263
+ 'options',
264
+ 'postmeta',
265
+ 'terms',
266
+ 'term_taxonomy',
267
+ 'term_relationships',
268
+ 'termmeta',
269
+ 'commentmeta',
270
+ );
271
+
272
+ /**
273
+ * List of deprecated WordPress tables
274
+ *
275
+ * categories, post2cat, and link2cat were deprecated in 2.3.0, db version 5539
276
+ *
277
+ * @since 2.9.0
278
+ * @see wpdb::tables()
279
+ * @var array
280
+ */
281
+ var $old_tables = array( 'categories', 'post2cat', 'link2cat' );
282
+
283
+ /**
284
+ * List of WordPress global tables
285
+ *
286
+ * @since 3.0.0
287
+ * @see wpdb::tables()
288
+ * @var array
289
+ */
290
+ var $global_tables = array( 'users', 'usermeta' );
291
+
292
+ /**
293
+ * List of Multisite global tables
294
+ *
295
+ * @since 3.0.0
296
+ * @see wpdb::tables()
297
+ * @var array
298
+ */
299
+ var $ms_global_tables = array(
300
+ 'blogs',
301
+ 'blogmeta',
302
+ 'signups',
303
+ 'site',
304
+ 'sitemeta',
305
+ 'sitecategories',
306
+ 'registration_log',
307
+ 'blog_versions',
308
+ );
309
+
310
+ /**
311
+ * WordPress Comments table
312
+ *
313
+ * @since 1.5.0
314
+ * @var string
315
+ */
316
+ public $comments;
317
+
318
+ /**
319
+ * WordPress Comment Metadata table
320
+ *
321
+ * @since 2.9.0
322
+ * @var string
323
+ */
324
+ public $commentmeta;
325
+
326
+ /**
327
+ * WordPress Links table
328
+ *
329
+ * @since 1.5.0
330
+ * @var string
331
+ */
332
+ public $links;
333
+
334
+ /**
335
+ * WordPress Options table
336
+ *
337
+ * @since 1.5.0
338
+ * @var string
339
+ */
340
+ public $options;
341
+
342
+ /**
343
+ * WordPress Post Metadata table
344
+ *
345
+ * @since 1.5.0
346
+ * @var string
347
+ */
348
+ public $postmeta;
349
+
350
+ /**
351
+ * WordPress Posts table
352
+ *
353
+ * @since 1.5.0
354
+ * @var string
355
+ */
356
+ public $posts;
357
+
358
+ /**
359
+ * WordPress Terms table
360
+ *
361
+ * @since 2.3.0
362
+ * @var string
363
+ */
364
+ public $terms;
365
+
366
+ /**
367
+ * WordPress Term Relationships table
368
+ *
369
+ * @since 2.3.0
370
+ * @var string
371
+ */
372
+ public $term_relationships;
373
+
374
+ /**
375
+ * WordPress Term Taxonomy table
376
+ *
377
+ * @since 2.3.0
378
+ * @var string
379
+ */
380
+ public $term_taxonomy;
381
+
382
+ /**
383
+ * WordPress Term Meta table.
384
+ *
385
+ * @since 4.4.0
386
+ * @var string
387
+ */
388
+ public $termmeta;
389
+
390
+ //
391
+ // Global and Multisite tables
392
+ //
393
+
394
+ /**
395
+ * WordPress User Metadata table
396
+ *
397
+ * @since 2.3.0
398
+ * @var string
399
+ */
400
+ public $usermeta;
401
+
402
+ /**
403
+ * WordPress Users table
404
+ *
405
+ * @since 1.5.0
406
+ * @var string
407
+ */
408
+ public $users;
409
+
410
+ /**
411
+ * Multisite Blogs table
412
+ *
413
+ * @since 3.0.0
414
+ * @var string
415
+ */
416
+ public $blogs;
417
+
418
+ /**
419
+ * Multisite Blog Metadata table
420
+ *
421
+ * @since 5.1.0
422
+ * @var string
423
+ */
424
+ public $blogmeta;
425
+
426
+ /**
427
+ * Multisite Blog Versions table
428
+ *
429
+ * @since 3.0.0
430
+ * @var string
431
+ */
432
+ public $blog_versions;
433
+
434
+ /**
435
+ * Multisite Registration Log table
436
+ *
437
+ * @since 3.0.0
438
+ * @var string
439
+ */
440
+ public $registration_log;
441
+
442
+ /**
443
+ * Multisite Signups table
444
+ *
445
+ * @since 3.0.0
446
+ * @var string
447
+ */
448
+ public $signups;
449
+
450
+ /**
451
+ * Multisite Sites table
452
+ *
453
+ * @since 3.0.0
454
+ * @var string
455
+ */
456
+ public $site;
457
+
458
+ /**
459
+ * Multisite Sitewide Terms table
460
+ *
461
+ * @since 3.0.0
462
+ * @var string
463
+ */
464
+ public $sitecategories;
465
+
466
+ /**
467
+ * Multisite Site Metadata table
468
+ *
469
+ * @since 3.0.0
470
+ * @var string
471
+ */
472
+ public $sitemeta;
473
+
474
+ /**
475
+ * Format specifiers for DB columns. Columns not listed here default to %s. Initialized during WP load.
476
+ *
477
+ * Keys are column names, values are format types: 'ID' => '%d'
478
+ *
479
+ * @since 2.8.0
480
+ * @see wpdb::prepare()
481
+ * @see wpdb::insert()
482
+ * @see wpdb::update()
483
+ * @see wpdb::delete()
484
+ * @see wp_set_wpdb_vars()
485
+ * @var array
486
+ */
487
+ public $field_types = array();
488
+
489
+ /**
490
+ * Database table columns charset
491
+ *
492
+ * @since 2.2.0
493
+ * @var string
494
+ */
495
+ public $charset;
496
+
497
+ /**
498
+ * Database table columns collate
499
+ *
500
+ * @since 2.2.0
501
+ * @var string
502
+ */
503
+ public $collate;
504
+
505
+ /**
506
+ * Database Username
507
+ *
508
+ * @since 2.9.0
509
+ * @var string
510
+ */
511
+ protected $dbuser;
512
+
513
+ /**
514
+ * Database Password
515
+ *
516
+ * @since 3.1.0
517
+ * @var string
518
+ */
519
+ protected $dbpassword;
520
+
521
+ /**
522
+ * Database Name
523
+ *
524
+ * @since 3.1.0
525
+ * @var string
526
+ */
527
+ protected $dbname;
528
+
529
+ /**
530
+ * Database Host
531
+ *
532
+ * @since 3.1.0
533
+ * @var string
534
+ */
535
+ protected $dbhost;
536
+
537
+ /**
538
+ * Database Handle
539
+ *
540
+ * @since 0.71
541
+ * @var string
542
+ */
543
+ protected $dbh;
544
+
545
+ /**
546
+ * A textual description of the last query/get_row/get_var call
547
+ *
548
+ * @since 3.0.0
549
+ * @var string
550
+ */
551
+ public $func_call;
552
+
553
+ /**
554
+ * Whether MySQL is used as the database engine.
555
+ *
556
+ * Set in WPDB::db_connect() to true, by default. This is used when checking
557
+ * against the required MySQL version for WordPress. Normally, a replacement
558
+ * database drop-in (db.php) will skip these checks, but setting this to true
559
+ * will force the checks to occur.
560
+ *
561
+ * @since 3.3.0
562
+ * @var bool
563
+ */
564
+ public $is_mysql = null;
565
+
566
+ /**
567
+ * A list of incompatible SQL modes.
568
+ *
569
+ * @since 3.9.0
570
+ * @var array
571
+ */
572
+ protected $incompatible_modes = array(
573
+ 'NO_ZERO_DATE',
574
+ 'ONLY_FULL_GROUP_BY',
575
+ 'STRICT_TRANS_TABLES',
576
+ 'STRICT_ALL_TABLES',
577
+ 'TRADITIONAL',
578
+ );
579
+
580
+ /**
581
+ * Whether to use mysqli over mysql.
582
+ *
583
+ * @since 3.9.0
584
+ * @var bool
585
+ */
586
+ private $use_mysqli = false;
587
+
588
+ /**
589
+ * Whether we've managed to successfully connect at some point
590
+ *
591
+ * @since 3.9.0
592
+ * @var bool
593
+ */
594
+ private $has_connected = false;
595
+
596
+ /**
597
+ * Connects to the database server and selects a database
598
+ *
599
+ * PHP5 style constructor for compatibility with PHP5. Does
600
+ * the actual setting up of the class properties and connection
601
+ * to the database.
602
+ *
603
+ * @link https://core.trac.wordpress.org/ticket/3354
604
+ * @since 2.0.8
605
+ *
606
+ * @global string $wp_version
607
+ *
608
+ * @param string $dbuser MySQL database user
609
+ * @param string $dbpassword MySQL database password
610
+ * @param string $dbname MySQL database name
611
+ * @param string $dbhost MySQL database host
612
+ */
613
+ public function __construct( $dbuser, $dbpassword, $dbname, $dbhost ) {
614
+ register_shutdown_function( array( $this, '__destruct' ) );
615
+
616
+ if ( WP_DEBUG && WP_DEBUG_DISPLAY ) {
617
+ $this->show_errors();
618
+ }
619
+
620
+ // Use ext/mysqli if it exists unless WP_USE_EXT_MYSQL is defined as true
621
+ if ( function_exists( 'mysqli_connect' ) ) {
622
+ $this->use_mysqli = true;
623
+
624
+ if ( defined( 'WP_USE_EXT_MYSQL' ) ) {
625
+ $this->use_mysqli = ! WP_USE_EXT_MYSQL;
626
+ }
627
+ }
628
+
629
+ $this->dbuser = $dbuser;
630
+ $this->dbpassword = $dbpassword;
631
+ $this->dbname = $dbname;
632
+ $this->dbhost = $dbhost;
633
+
634
+ // wp-config.php creation will manually connect when ready.
635
+ if ( defined( 'WP_SETUP_CONFIG' ) ) {
636
+ return;
637
+ }
638
+
639
+ $this->db_connect();
640
+ }
641
+
642
+ /**
643
+ * PHP5 style destructor and will run when database object is destroyed.
644
+ *
645
+ * @see wpdb::__construct()
646
+ * @since 2.0.8
647
+ * @return true
648
+ */
649
+ public function __destruct() {
650
+ return true;
651
+ }
652
+
653
+ /**
654
+ * Makes private properties readable for backward compatibility.
655
+ *
656
+ * @since 3.5.0
657
+ *
658
+ * @param string $name The private member to get, and optionally process
659
+ * @return mixed The private member
660
+ */
661
+ public function __get( $name ) {
662
+ if ( 'col_info' === $name ) {
663
+ $this->load_col_info();
664
+ }
665
+
666
+ return $this->$name;
667
+ }
668
+
669
+ /**
670
+ * Makes private properties settable for backward compatibility.
671
+ *
672
+ * @since 3.5.0
673
+ *
674
+ * @param string $name The private member to set
675
+ * @param mixed $value The value to set
676
+ */
677
+ public function __set( $name, $value ) {
678
+ $protected_members = array(
679
+ 'col_meta',
680
+ 'table_charset',
681
+ 'check_current_query',
682
+ );
683
+ if ( in_array( $name, $protected_members, true ) ) {
684
+ return;
685
+ }
686
+ $this->$name = $value;
687
+ }
688
+
689
+ /**
690
+ * Makes private properties check-able for backward compatibility.
691
+ *
692
+ * @since 3.5.0
693
+ *
694
+ * @param string $name The private member to check
695
+ *
696
+ * @return bool If the member is set or not
697
+ */
698
+ public function __isset( $name ) {
699
+ return isset( $this->$name );
700
+ }
701
+
702
+ /**
703
+ * Makes private properties un-settable for backward compatibility.
704
+ *
705
+ * @since 3.5.0
706
+ *
707
+ * @param string $name The private member to unset
708
+ */
709
+ public function __unset( $name ) {
710
+ unset( $this->$name );
711
+ }
712
+
713
+ /**
714
+ * Set $this->charset and $this->collate
715
+ *
716
+ * @since 3.1.0
717
+ */
718
+ public function init_charset() {
719
+ $charset = '';
720
+ $collate = '';
721
+
722
+ if ( function_exists( 'is_multisite' ) && is_multisite() ) {
723
+ $charset = 'utf8';
724
+ if ( defined( 'DB_COLLATE' ) && DB_COLLATE ) {
725
+ $collate = DB_COLLATE;
726
+ } else {
727
+ $collate = 'utf8_general_ci';
728
+ }
729
+ } elseif ( defined( 'DB_COLLATE' ) ) {
730
+ $collate = DB_COLLATE;
731
+ }
732
+
733
+ if ( defined( 'DB_CHARSET' ) ) {
734
+ $charset = DB_CHARSET;
735
+ }
736
+
737
+ $charset_collate = $this->determine_charset( $charset, $collate );
738
+
739
+ $this->charset = $charset_collate['charset'];
740
+ $this->collate = $charset_collate['collate'];
741
+ }
742
+
743
+ /**
744
+ * Determines the best charset and collation to use given a charset and collation.
745
+ *
746
+ * For example, when able, utf8mb4 should be used instead of utf8.
747
+ *
748
+ * @since 4.6.0
749
+ *
750
+ * @param string $charset The character set to check.
751
+ * @param string $collate The collation to check.
752
+ * @return array The most appropriate character set and collation to use.
753
+ */
754
+ public function determine_charset( $charset, $collate ) {
755
+ if ( ( $this->use_mysqli && ! ( $this->dbh instanceof mysqli ) ) || empty( $this->dbh ) ) {
756
+ return compact( 'charset', 'collate' );
757
+ }
758
+
759
+ if ( 'utf8' === $charset && $this->has_cap( 'utf8mb4' ) ) {
760
+ $charset = 'utf8mb4';
761
+ }
762
+
763
+ if ( 'utf8mb4' === $charset && ! $this->has_cap( 'utf8mb4' ) ) {
764
+ $charset = 'utf8';
765
+ $collate = str_replace( 'utf8mb4_', 'utf8_', $collate );
766
+ }
767
+
768
+ if ( 'utf8mb4' === $charset ) {
769
+ // _general_ is outdated, so we can upgrade it to _unicode_, instead.
770
+ if ( ! $collate || 'utf8_general_ci' === $collate ) {
771
+ $collate = 'utf8mb4_unicode_ci';
772
+ } else {
773
+ $collate = str_replace( 'utf8_', 'utf8mb4_', $collate );
774
+ }
775
+ }
776
+
777
+ // _unicode_520_ is a better collation, we should use that when it's available.
778
+ if ( $this->has_cap( 'utf8mb4_520' ) && 'utf8mb4_unicode_ci' === $collate ) {
779
+ $collate = 'utf8mb4_unicode_520_ci';
780
+ }
781
+
782
+ return compact( 'charset', 'collate' );
783
+ }
784
+
785
+ /**
786
+ * Sets the connection's character set.
787
+ *
788
+ * @since 3.1.0
789
+ *
790
+ * @param resource $dbh The resource given by mysql_connect
791
+ * @param string $charset Optional. The character set. Default null.
792
+ * @param string $collate Optional. The collation. Default null.
793
+ */
794
+ public function set_charset( $dbh, $charset = null, $collate = null ) {
795
+ if ( ! isset( $charset ) ) {
796
+ $charset = $this->charset;
797
+ }
798
+ if ( ! isset( $collate ) ) {
799
+ $collate = $this->collate;
800
+ }
801
+ if ( $this->has_cap( 'collation' ) && ! empty( $charset ) ) {
802
+ $set_charset_succeeded = true;
803
+
804
+ if ( $this->use_mysqli ) {
805
+ if ( function_exists( 'mysqli_set_charset' ) && $this->has_cap( 'set_charset' ) ) {
806
+ $set_charset_succeeded = mysqli_set_charset( $dbh, $charset );
807
+ }
808
+
809
+ if ( $set_charset_succeeded ) {
810
+ $query = $this->prepare( 'SET NAMES %s', $charset );
811
+ if ( ! empty( $collate ) ) {
812
+ $query .= $this->prepare( ' COLLATE %s', $collate );
813
+ }
814
+ mysqli_query( $dbh, $query );
815
+ }
816
+ } else {
817
+ if ( function_exists( 'mysql_set_charset' ) && $this->has_cap( 'set_charset' ) ) {
818
+ $set_charset_succeeded = mysql_set_charset( $charset, $dbh );
819
+ }
820
+ if ( $set_charset_succeeded ) {
821
+ $query = $this->prepare( 'SET NAMES %s', $charset );
822
+ if ( ! empty( $collate ) ) {
823
+ $query .= $this->prepare( ' COLLATE %s', $collate );
824
+ }
825
+ mysql_query( $query, $dbh );
826
+ }
827
+ }
828
+ }
829
+ }
830
+
831
+ /**
832
+ * Change the current SQL mode, and ensure its WordPress compatibility.
833
+ *
834
+ * If no modes are passed, it will ensure the current MySQL server
835
+ * modes are compatible.
836
+ *
837
+ * @since 3.9.0
838
+ *
839
+ * @param array $modes Optional. A list of SQL modes to set.
840
+ */
841
+ public function set_sql_mode( $modes = array() ) {
842
+ if ( empty( $modes ) ) {
843
+ if ( $this->use_mysqli ) {
844
+ $res = mysqli_query( $this->dbh, 'SELECT @@SESSION.sql_mode' );
845
+ } else {
846
+ $res = mysql_query( 'SELECT @@SESSION.sql_mode', $this->dbh );
847
+ }
848
+
849
+ if ( empty( $res ) ) {
850
+ return;
851
+ }
852
+
853
+ if ( $this->use_mysqli ) {
854
+ $modes_array = mysqli_fetch_array( $res );
855
+ if ( empty( $modes_array[0] ) ) {
856
+ return;
857
+ }
858
+ $modes_str = $modes_array[0];
859
+ } else {
860
+ $modes_str = mysql_result( $res, 0 );
861
+ }
862
+
863
+ if ( empty( $modes_str ) ) {
864
+ return;
865
+ }
866
+
867
+ $modes = explode( ',', $modes_str );
868
+ }
869
+
870
+ $modes = array_change_key_case( $modes, CASE_UPPER );
871
+
872
+ /**
873
+ * Filters the list of incompatible SQL modes to exclude.
874
+ *
875
+ * @since 3.9.0
876
+ *
877
+ * @param array $incompatible_modes An array of incompatible modes.
878
+ */
879
+ $incompatible_modes = (array) apply_filters( 'incompatible_sql_modes', $this->incompatible_modes );
880
+
881
+ foreach ( $modes as $i => $mode ) {
882
+ if ( in_array( $mode, $incompatible_modes ) ) {
883
+ unset( $modes[ $i ] );
884
+ }
885
+ }
886
+
887
+ $modes_str = implode( ',', $modes );
888
+
889
+ if ( $this->use_mysqli ) {
890
+ mysqli_query( $this->dbh, "SET SESSION sql_mode='$modes_str'" );
891
+ } else {
892
+ mysql_query( "SET SESSION sql_mode='$modes_str'", $this->dbh );
893
+ }
894
+ }
895
+
896
+ /**
897
+ * Sets the table prefix for the WordPress tables.
898
+ *
899
+ * @since 2.5.0
900
+ *
901
+ * @param string $prefix Alphanumeric name for the new prefix.
902
+ * @param bool $set_table_names Optional. Whether the table names, e.g. wpdb::$posts, should be updated or not.
903
+ * @return string|WP_Error Old prefix or WP_Error on error
904
+ */
905
+ public function set_prefix( $prefix, $set_table_names = true ) {
906
+
907
+ if ( preg_match( '|[^a-z0-9_]|i', $prefix ) ) {
908
+ return new WP_Error( 'invalid_db_prefix', 'Invalid database prefix' );
909
+ }
910
+
911
+ $old_prefix = is_multisite() ? '' : $prefix;
912
+
913
+ if ( isset( $this->base_prefix ) ) {
914
+ $old_prefix = $this->base_prefix;
915
+ }
916
+
917
+ $this->base_prefix = $prefix;
918
+
919
+ if ( $set_table_names ) {
920
+ foreach ( $this->tables( 'global' ) as $table => $prefixed_table ) {
921
+ $this->$table = $prefixed_table;
922
+ }
923
+
924
+ if ( is_multisite() && empty( $this->blogid ) ) {
925
+ return $old_prefix;
926
+ }
927
+
928
+ $this->prefix = $this->get_blog_prefix();
929
+
930
+ foreach ( $this->tables( 'blog' ) as $table => $prefixed_table ) {
931
+ $this->$table = $prefixed_table;
932
+ }
933
+
934
+ foreach ( $this->tables( 'old' ) as $table => $prefixed_table ) {
935
+ $this->$table = $prefixed_table;
936
+ }
937
+ }
938
+ return $old_prefix;
939
+ }
940
+
941
+ /**
942
+ * Sets blog id.
943
+ *
944
+ * @since 3.0.0
945
+ *
946
+ * @param int $blog_id
947
+ * @param int $network_id Optional.
948
+ * @return int previous blog id
949
+ */
950
+ public function set_blog_id( $blog_id, $network_id = 0 ) {
951
+ if ( ! empty( $network_id ) ) {
952
+ $this->siteid = $network_id;
953
+ }
954
+
955
+ $old_blog_id = $this->blogid;
956
+ $this->blogid = $blog_id;
957
+
958
+ $this->prefix = $this->get_blog_prefix();
959
+
960
+ foreach ( $this->tables( 'blog' ) as $table => $prefixed_table ) {
961
+ $this->$table = $prefixed_table;
962
+ }
963
+
964
+ foreach ( $this->tables( 'old' ) as $table => $prefixed_table ) {
965
+ $this->$table = $prefixed_table;
966
+ }
967
+
968
+ return $old_blog_id;
969
+ }
970
+
971
+ /**
972
+ * Gets blog prefix.
973
+ *
974
+ * @since 3.0.0
975
+ * @param int $blog_id Optional.
976
+ * @return string Blog prefix.
977
+ */
978
+ public function get_blog_prefix( $blog_id = null ) {
979
+ if ( is_multisite() ) {
980
+ if ( null === $blog_id ) {
981
+ $blog_id = $this->blogid;
982
+ }
983
+ $blog_id = (int) $blog_id;
984
+ if ( defined( 'MULTISITE' ) && ( 0 == $blog_id || 1 == $blog_id ) ) {
985
+ return $this->base_prefix;
986
+ } else {
987
+ return $this->base_prefix . $blog_id . '_';
988
+ }
989
+ } else {
990
+ return $this->base_prefix;
991
+ }
992
+ }
993
+
994
+ /**
995
+ * Returns an array of WordPress tables.
996
+ *
997
+ * Also allows for the CUSTOM_USER_TABLE and CUSTOM_USER_META_TABLE to
998
+ * override the WordPress users and usermeta tables that would otherwise
999
+ * be determined by the prefix.
1000
+ *
1001
+ * The scope argument can take one of the following:
1002
+ *
1003
+ * 'all' - returns 'all' and 'global' tables. No old tables are returned.
1004
+ * 'blog' - returns the blog-level tables for the queried blog.
1005
+ * 'global' - returns the global tables for the installation, returning multisite tables only if running multisite.
1006
+ * 'ms_global' - returns the multisite global tables, regardless if current installation is multisite.
1007
+ * 'old' - returns tables which are deprecated.
1008
+ *
1009
+ * @since 3.0.0
1010
+ * @uses wpdb::$tables
1011
+ * @uses wpdb::$old_tables
1012
+ * @uses wpdb::$global_tables
1013
+ * @uses wpdb::$ms_global_tables
1014
+ *
1015
+ * @param string $scope Optional. Can be all, global, ms_global, blog, or old tables. Defaults to all.
1016
+ * @param bool $prefix Optional. Whether to include table prefixes. Default true. If blog
1017
+ * prefix is requested, then the custom users and usermeta tables will be mapped.
1018
+ * @param int $blog_id Optional. The blog_id to prefix. Defaults to wpdb::$blogid. Used only when prefix is requested.
1019
+ * @return array Table names. When a prefix is requested, the key is the unprefixed table name.
1020
+ */
1021
+ public function tables( $scope = 'all', $prefix = true, $blog_id = 0 ) {
1022
+ switch ( $scope ) {
1023
+ case 'all':
1024
+ $tables = array_merge( $this->global_tables, $this->tables );
1025
+ if ( is_multisite() ) {
1026
+ $tables = array_merge( $tables, $this->ms_global_tables );
1027
+ }
1028
+ break;
1029
+ case 'blog':
1030
+ $tables = $this->tables;
1031
+ break;
1032
+ case 'global':
1033
+ $tables = $this->global_tables;
1034
+ if ( is_multisite() ) {
1035
+ $tables = array_merge( $tables, $this->ms_global_tables );
1036
+ }
1037
+ break;
1038
+ case 'ms_global':
1039
+ $tables = $this->ms_global_tables;
1040
+ break;
1041
+ case 'old':
1042
+ $tables = $this->old_tables;
1043
+ break;
1044
+ default:
1045
+ return array();
1046
+ }
1047
+
1048
+ if ( $prefix ) {
1049
+ if ( ! $blog_id ) {
1050
+ $blog_id = $this->blogid;
1051
+ }
1052
+ $blog_prefix = $this->get_blog_prefix( $blog_id );
1053
+ $base_prefix = $this->base_prefix;
1054
+ $global_tables = array_merge( $this->global_tables, $this->ms_global_tables );
1055
+ foreach ( $tables as $k => $table ) {
1056
+ if ( in_array( $table, $global_tables ) ) {
1057
+ $tables[ $table ] = $base_prefix . $table;
1058
+ } else {
1059
+ $tables[ $table ] = $blog_prefix . $table;
1060
+ }
1061
+ unset( $tables[ $k ] );
1062
+ }
1063
+
1064
+ if ( isset( $tables['users'] ) && defined( 'CUSTOM_USER_TABLE' ) ) {
1065
+ $tables['users'] = CUSTOM_USER_TABLE;
1066
+ }
1067
+
1068
+ if ( isset( $tables['usermeta'] ) && defined( 'CUSTOM_USER_META_TABLE' ) ) {
1069
+ $tables['usermeta'] = CUSTOM_USER_META_TABLE;
1070
+ }
1071
+ }
1072
+
1073
+ return $tables;
1074
+ }
1075
+
1076
+ /**
1077
+ * Selects a database using the current database connection.
1078
+ *
1079
+ * The database name will be changed based on the current database
1080
+ * connection. On failure, the execution will bail and display an DB error.
1081
+ *
1082
+ * @since 0.71
1083
+ *
1084
+ * @param string $db MySQL database name
1085
+ * @param resource|null $dbh Optional link identifier.
1086
+ */
1087
+ public function select( $db, $dbh = null ) {
1088
+ if ( is_null( $dbh ) ) {
1089
+ $dbh = $this->dbh;
1090
+ }
1091
+
1092
+ if ( $this->use_mysqli ) {
1093
+ $success = mysqli_select_db( $dbh, $db );
1094
+ } else {
1095
+ $success = mysql_select_db( $db, $dbh );
1096
+ }
1097
+ if ( ! $success ) {
1098
+ $this->ready = false;
1099
+ if ( ! did_action( 'template_redirect' ) ) {
1100
+ wp_load_translations_early();
1101
+
1102
+ $message = '<h1>' . __( 'Can&#8217;t select database' ) . "</h1>\n";
1103
+
1104
+ $message .= '<p>' . sprintf(
1105
+ /* translators: %s: database name */
1106
+ __( 'We were able to connect to the database server (which means your username and password is okay) but not able to select the %s database.' ),
1107
+ '<code>' . htmlspecialchars( $db, ENT_QUOTES ) . '</code>'
1108
+ ) . "</p>\n";
1109
+
1110
+ $message .= "<ul>\n";
1111
+ $message .= '<li>' . __( 'Are you sure it exists?' ) . "</li>\n";
1112
+
1113
+ $message .= '<li>' . sprintf(
1114
+ /* translators: 1: database user, 2: database name */
1115
+ __( 'Does the user %1$s have permission to use the %2$s database?' ),
1116
+ '<code>' . htmlspecialchars( $this->dbuser, ENT_QUOTES ) . '</code>',
1117
+ '<code>' . htmlspecialchars( $db, ENT_QUOTES ) . '</code>'
1118
+ ) . "</li>\n";
1119
+
1120
+ $message .= '<li>' . sprintf(
1121
+ /* translators: %s: database name */
1122
+ __( 'On some systems the name of your database is prefixed with your username, so it would be like <code>username_%1$s</code>. Could that be the problem?' ),
1123
+ htmlspecialchars( $db, ENT_QUOTES )
1124
+ ) . "</li>\n";
1125
+
1126
+ $message .= "</ul>\n";
1127
+
1128
+ $message .= '<p>' . sprintf(
1129
+ /* translators: %s: support forums URL */
1130
+ __( 'If you don&#8217;t know how to set up a database you should <strong>contact your host</strong>. If all else fails you may find help at the <a href="%s">WordPress Support Forums</a>.' ),
1131
+ __( 'https://wordpress.org/support/forums/' )
1132
+ ) . "</p>\n";
1133
+
1134
+ $this->bail( $message, 'db_select_fail' );
1135
+ }
1136
+ }
1137
+ }
1138
+
1139
+ /**
1140
+ * Do not use, deprecated.
1141
+ *
1142
+ * Use esc_sql() or wpdb::prepare() instead.
1143
+ *
1144
+ * @since 2.8.0
1145
+ * @deprecated 3.6.0 Use wpdb::prepare()
1146
+ * @see wpdb::prepare
1147
+ * @see esc_sql()
1148
+ *
1149
+ * @param string $string
1150
+ * @return string
1151
+ */
1152
+ function _weak_escape( $string ) {
1153
+ if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) {
1154
+ _deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' );
1155
+ }
1156
+ return addslashes( $string );
1157
+ }
1158
+
1159
+ /**
1160
+ * Real escape, using mysqli_real_escape_string() or mysql_real_escape_string()
1161
+ *
1162
+ * @see mysqli_real_escape_string()
1163
+ * @see mysql_real_escape_string()
1164
+ * @since 2.8.0
1165
+ *
1166
+ * @param string $string to escape
1167
+ * @return string escaped
1168
+ */
1169
+ function _real_escape( $string ) {
1170
+ if ( $this->dbh ) {
1171
+ if ( $this->use_mysqli ) {
1172
+ $escaped = mysqli_real_escape_string( $this->dbh, $string );
1173
+ } else {
1174
+ $escaped = mysql_real_escape_string( $string, $this->dbh );
1175
+ }
1176
+ } else {
1177
+ $class = get_class( $this );
1178
+ if ( function_exists( '__' ) ) {
1179
+ /* translators: %s: database access abstraction class, usually wpdb or a class extending wpdb */
1180
+ _doing_it_wrong( $class, sprintf( __( '%s must set a database connection for use with escaping.' ), $class ), '3.6.0' );
1181
+ } else {
1182
+ _doing_it_wrong( $class, sprintf( '%s must set a database connection for use with escaping.', $class ), '3.6.0' );
1183
+ }
1184
+ $escaped = addslashes( $string );
1185
+ }
1186
+
1187
+ return $this->add_placeholder_escape( $escaped );
1188
+ }
1189
+
1190
+ /**
1191
+ * Escape data. Works on arrays.
1192
+ *
1193
+ * @uses wpdb::_real_escape()
1194
+ * @since 2.8.0
1195
+ *
1196
+ * @param string|array $data
1197
+ * @return string|array escaped
1198
+ */
1199
+ public function _escape( $data ) {
1200
+ if ( is_array( $data ) ) {
1201
+ foreach ( $data as $k => $v ) {
1202
+ if ( is_array( $v ) ) {
1203
+ $data[ $k ] = $this->_escape( $v );
1204
+ } else {
1205
+ $data[ $k ] = $this->_real_escape( $v );
1206
+ }
1207
+ }
1208
+ } else {
1209
+ $data = $this->_real_escape( $data );
1210
+ }
1211
+
1212
+ return $data;
1213
+ }
1214
+
1215
+ /**
1216
+ * Do not use, deprecated.
1217
+ *
1218
+ * Use esc_sql() or wpdb::prepare() instead.
1219
+ *
1220
+ * @since 0.71
1221
+ * @deprecated 3.6.0 Use wpdb::prepare()
1222
+ * @see wpdb::prepare()
1223
+ * @see esc_sql()
1224
+ *
1225
+ * @param mixed $data
1226
+ * @return mixed
1227
+ */
1228
+ public function escape( $data ) {
1229
+ if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) {
1230
+ _deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' );
1231
+ }
1232
+ if ( is_array( $data ) ) {
1233
+ foreach ( $data as $k => $v ) {
1234
+ if ( is_array( $v ) ) {
1235
+ $data[ $k ] = $this->escape( $v, 'recursive' );
1236
+ } else {
1237
+ $data[ $k ] = $this->_weak_escape( $v, 'internal' );
1238
+ }
1239
+ }
1240
+ } else {
1241
+ $data = $this->_weak_escape( $data, 'internal' );
1242
+ }
1243
+
1244
+ return $data;
1245
+ }
1246
+
1247
+ /**
1248
+ * Escapes content by reference for insertion into the database, for security
1249
+ *
1250
+ * @uses wpdb::_real_escape()
1251
+ *
1252
+ * @since 2.3.0
1253
+ *
1254
+ * @param string $string to escape
1255
+ */
1256
+ public function escape_by_ref( &$string ) {
1257
+ if ( ! is_float( $string ) ) {
1258
+ $string = $this->_real_escape( $string );
1259
+ }
1260
+ }
1261
+
1262
+ /**
1263
+ * Prepares a SQL query for safe execution. Uses sprintf()-like syntax.
1264
+ *
1265
+ * The following placeholders can be used in the query string:
1266
+ * %d (integer)
1267
+ * %f (float)
1268
+ * %s (string)
1269
+ *
1270
+ * All placeholders MUST be left unquoted in the query string. A corresponding argument MUST be passed for each placeholder.
1271
+ *
1272
+ * For compatibility with old behavior, numbered or formatted string placeholders (eg, %1$s, %5s) will not have quotes
1273
+ * added by this function, so should be passed with appropriate quotes around them for your usage.
1274
+ *
1275
+ * Literal percentage signs (%) in the query string must be written as %%. Percentage wildcards (for example,
1276
+ * to use in LIKE syntax) must be passed via a substitution argument containing the complete LIKE string, these
1277
+ * cannot be inserted directly in the query string. Also see wpdb::esc_like().
1278
+ *
1279
+ * Arguments may be passed as individual arguments to the method, or as a single array containing all arguments. A combination
1280
+ * of the two is not supported.
1281
+ *
1282
+ * Examples:
1283
+ * $wpdb->prepare( "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d OR `other_field` LIKE %s", array( 'foo', 1337, '%bar' ) );
1284
+ * $wpdb->prepare( "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s", 'foo' );
1285
+ *
1286
+ * @link https://secure.php.net/sprintf Description of syntax.
1287
+ * @since 2.3.0
1288
+ *
1289
+ * @param string $query Query statement with sprintf()-like placeholders
1290
+ * @param array|mixed $args The array of variables to substitute into the query's placeholders if being called with an array of arguments,
1291
+ * or the first variable to substitute into the query's placeholders if being called with individual arguments.
1292
+ * @param mixed $args,... further variables to substitute into the query's placeholders if being called wih individual arguments.
1293
+ * @return string|void Sanitized query string, if there is a query to prepare.
1294
+ */
1295
+ public function prepare( $query, $args ) {
1296
+ if ( is_null( $query ) ) {
1297
+ return;
1298
+ }
1299
+
1300
+ // This is not meant to be foolproof -- but it will catch obviously incorrect usage.
1301
+ if ( strpos( $query, '%' ) === false ) {
1302
+ wp_load_translations_early();
1303
+ _doing_it_wrong( 'wpdb::prepare', sprintf( __( 'The query argument of %s must have a placeholder.' ), 'wpdb::prepare()' ), '3.9.0' );
1304
+ }
1305
+
1306
+ $args = func_get_args();
1307
+ array_shift( $args );
1308
+
1309
+ // If args were passed as an array (as in vsprintf), move them up.
1310
+ $passed_as_array = false;
1311
+ if ( is_array( $args[0] ) && count( $args ) == 1 ) {
1312
+ $passed_as_array = true;
1313
+ $args = $args[0];
1314
+ }
1315
+
1316
+ foreach ( $args as $arg ) {
1317
+ if ( ! is_scalar( $arg ) && ! is_null( $arg ) ) {
1318
+ wp_load_translations_early();
1319
+ _doing_it_wrong( 'wpdb::prepare', sprintf( __( 'Unsupported value type (%s).' ), gettype( $arg ) ), '4.8.2' );
1320
+ }
1321
+ }
1322
+
1323
+ /*
1324
+ * Specify the formatting allowed in a placeholder. The following are allowed:
1325
+ *
1326
+ * - Sign specifier. eg, $+d
1327
+ * - Numbered placeholders. eg, %1$s
1328
+ * - Padding specifier, including custom padding characters. eg, %05s, %'#5s
1329
+ * - Alignment specifier. eg, %05-s
1330
+ * - Precision specifier. eg, %.2f
1331
+ */
1332
+ $allowed_format = '(?:[1-9][0-9]*[$])?[-+0-9]*(?: |0|\'.)?[-+0-9]*(?:\.[0-9]+)?';
1333
+
1334
+ /*
1335
+ * If a %s placeholder already has quotes around it, removing the existing quotes and re-inserting them
1336
+ * ensures the quotes are consistent.
1337
+ *
1338
+ * For backward compatibility, this is only applied to %s, and not to placeholders like %1$s, which are frequently
1339
+ * used in the middle of longer strings, or as table name placeholders.
1340
+ */
1341
+ $query = str_replace( "'%s'", '%s', $query ); // Strip any existing single quotes.
1342
+ $query = str_replace( '"%s"', '%s', $query ); // Strip any existing double quotes.
1343
+ $query = preg_replace( '/(?<!%)%s/', "'%s'", $query ); // Quote the strings, avoiding escaped strings like %%s.
1344
+
1345
+ $query = preg_replace( "/(?<!%)(%($allowed_format)?f)/", '%\\2F', $query ); // Force floats to be locale unaware.
1346
+
1347
+ $query = preg_replace( "/%(?:%|$|(?!($allowed_format)?[sdF]))/", '%%\\1', $query ); // Escape any unescaped percents.
1348
+
1349
+ // Count the number of valid placeholders in the query.
1350
+ $placeholders = preg_match_all( "/(^|[^%]|(%%)+)%($allowed_format)?[sdF]/", $query, $matches );
1351
+
1352
+ if ( count( $args ) !== $placeholders ) {
1353
+ if ( 1 === $placeholders && $passed_as_array ) {
1354
+ // If the passed query only expected one argument, but the wrong number of arguments were sent as an array, bail.
1355
+ wp_load_translations_early();
1356
+ _doing_it_wrong( 'wpdb::prepare', __( 'The query only expected one placeholder, but an array of multiple placeholders was sent.' ), '4.9.0' );
1357
+
1358
+ return;
1359
+ } else {
1360
+ /*
1361
+ * If we don't have the right number of placeholders, but they were passed as individual arguments,
1362
+ * or we were expecting multiple arguments in an array, throw a warning.
1363
+ */
1364
+ wp_load_translations_early();
1365
+ _doing_it_wrong(
1366
+ 'wpdb::prepare',
1367
+ /* translators: 1: number of placeholders, 2: number of arguments passed */
1368
+ sprintf(
1369
+ __( 'The query does not contain the correct number of placeholders (%1$d) for the number of arguments passed (%2$d).' ),
1370
+ $placeholders,
1371
+ count( $args )
1372
+ ),
1373
+ '4.8.3'
1374
+ );
1375
+ }
1376
+ }
1377
+
1378
+ array_walk( $args, array( $this, 'escape_by_ref' ) );
1379
+ $query = @vsprintf( $query, $args );
1380
+
1381
+ return $this->add_placeholder_escape( $query );
1382
+ }
1383
+
1384
+ /**
1385
+ * First half of escaping for LIKE special characters % and _ before preparing for MySQL.
1386
+ *
1387
+ * Use this only before wpdb::prepare() or esc_sql(). Reversing the order is very bad for security.
1388
+ *
1389
+ * Example Prepared Statement:
1390
+ *
1391
+ * $wild = '%';
1392
+ * $find = 'only 43% of planets';
1393
+ * $like = $wild . $wpdb->esc_like( $find ) . $wild;
1394
+ * $sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE %s", $like );
1395
+ *
1396
+ * Example Escape Chain:
1397
+ *
1398
+ * $sql = esc_sql( $wpdb->esc_like( $input ) );
1399
+ *
1400
+ * @since 4.0.0
1401
+ *
1402
+ * @param string $text The raw text to be escaped. The input typed by the user should have no
1403
+ * extra or deleted slashes.
1404
+ * @return string Text in the form of a LIKE phrase. The output is not SQL safe. Call $wpdb::prepare()
1405
+ * or real_escape next.
1406
+ */
1407
+ public function esc_like( $text ) {
1408
+ return addcslashes( $text, '_%\\' );
1409
+ }
1410
+
1411
+ /**
1412
+ * Print SQL/DB error.
1413
+ *
1414
+ * @since 0.71
1415
+ * @global array $EZSQL_ERROR Stores error information of query and error string
1416
+ *
1417
+ * @param string $str The error to display
1418
+ * @return false|void False if the showing of errors is disabled.
1419
+ */
1420
+ public function print_error( $str = '' ) {
1421
+ global $EZSQL_ERROR;
1422
+
1423
+ if ( ! $str ) {
1424
+ if ( $this->use_mysqli ) {
1425
+ $str = mysqli_error( $this->dbh );
1426
+ } else {
1427
+ $str = mysql_error( $this->dbh );
1428
+ }
1429
+ }
1430
+ $EZSQL_ERROR[] = array(
1431
+ 'query' => $this->last_query,
1432
+ 'error_str' => $str,
1433
+ );
1434
+
1435
+ if ( $this->suppress_errors ) {
1436
+ return false;
1437
+ }
1438
+
1439
+ wp_load_translations_early();
1440
+
1441
+ if ( $caller = $this->get_caller() ) {
1442
+ /* translators: 1: Database error message, 2: SQL query, 3: Name of the calling function */
1443
+ $error_str = sprintf( __( 'WordPress database error %1$s for query %2$s made by %3$s' ), $str, $this->last_query, $caller );
1444
+ } else {
1445
+ /* translators: 1: Database error message, 2: SQL query */
1446
+ $error_str = sprintf( __( 'WordPress database error %1$s for query %2$s' ), $str, $this->last_query );
1447
+ }
1448
+
1449
+ error_log( $error_str );
1450
+
1451
+ // Are we showing errors?
1452
+ if ( ! $this->show_errors ) {
1453
+ return false;
1454
+ }
1455
+
1456
+ // If there is an error then take note of it
1457
+ if ( is_multisite() ) {
1458
+ $msg = sprintf(
1459
+ "%s [%s]\n%s\n",
1460
+ __( 'WordPress database error:' ),
1461
+ $str,
1462
+ $this->last_query
1463
+ );
1464
+
1465
+ if ( defined( 'ERRORLOGFILE' ) ) {
1466
+ error_log( $msg, 3, ERRORLOGFILE );
1467
+ }
1468
+ if ( defined( 'DIEONDBERROR' ) ) {
1469
+ wp_die( $msg );
1470
+ }
1471
+ } else {
1472
+ $str = htmlspecialchars( $str, ENT_QUOTES );
1473
+ $query = htmlspecialchars( $this->last_query, ENT_QUOTES );
1474
+
1475
+ printf(
1476
+ '<div id="error"><p class="wpdberror"><strong>%s</strong> [%s]<br /><code>%s</code></p></div>',
1477
+ __( 'WordPress database error:' ),
1478
+ $str,
1479
+ $query
1480
+ );
1481
+ }
1482
+ }
1483
+
1484
+ /**
1485
+ * Enables showing of database errors.
1486
+ *
1487
+ * This function should be used only to enable showing of errors.
1488
+ * wpdb::hide_errors() should be used instead for hiding of errors. However,
1489
+ * this function can be used to enable and disable showing of database
1490
+ * errors.
1491
+ *
1492
+ * @since 0.71
1493
+ * @see wpdb::hide_errors()
1494
+ *
1495
+ * @param bool $show Whether to show or hide errors
1496
+ * @return bool Old value for showing errors.
1497
+ */
1498
+ public function show_errors( $show = true ) {
1499
+ $errors = $this->show_errors;
1500
+ $this->show_errors = $show;
1501
+ return $errors;
1502
+ }
1503
+
1504
+ /**
1505
+ * Disables showing of database errors.
1506
+ *
1507
+ * By default database errors are not shown.
1508
+ *
1509
+ * @since 0.71
1510
+ * @see wpdb::show_errors()
1511
+ *
1512
+ * @return bool Whether showing of errors was active
1513
+ */
1514
+ public function hide_errors() {
1515
+ $show = $this->show_errors;
1516
+ $this->show_errors = false;
1517
+ return $show;
1518
+ }
1519
+
1520
+ /**
1521
+ * Whether to suppress database errors.
1522
+ *
1523
+ * By default database errors are suppressed, with a simple
1524
+ * call to this function they can be enabled.
1525
+ *
1526
+ * @since 2.5.0
1527
+ * @see wpdb::hide_errors()
1528
+ * @param bool $suppress Optional. New value. Defaults to true.
1529
+ * @return bool Old value
1530
+ */
1531
+ public function suppress_errors( $suppress = true ) {
1532
+ $errors = $this->suppress_errors;
1533
+ $this->suppress_errors = (bool) $suppress;
1534
+ return $errors;
1535
+ }
1536
+
1537
+ /**
1538
+ * Kill cached query results.
1539
+ *
1540
+ * @since 0.71
1541
+ */
1542
+ public function flush() {
1543
+ $this->last_result = array();
1544
+ $this->col_info = null;
1545
+ $this->last_query = null;
1546
+ $this->rows_affected = $this->num_rows = 0;
1547
+ $this->last_error = '';
1548
+
1549
+ if ( $this->use_mysqli && $this->result instanceof mysqli_result ) {
1550
+ mysqli_free_result( $this->result );
1551
+ $this->result = null;
1552
+
1553
+ // Sanity check before using the handle
1554
+ if ( empty( $this->dbh ) || ! ( $this->dbh instanceof mysqli ) ) {
1555
+ return;
1556
+ }
1557
+
1558
+ // Clear out any results from a multi-query
1559
+ while ( mysqli_more_results( $this->dbh ) ) {
1560
+ mysqli_next_result( $this->dbh );
1561
+ }
1562
+ } elseif ( is_resource( $this->result ) ) {
1563
+ mysql_free_result( $this->result );
1564
+ }
1565
+ }
1566
+
1567
+ /**
1568
+ * Connect to and select database.
1569
+ *
1570
+ * If $allow_bail is false, the lack of database connection will need
1571
+ * to be handled manually.
1572
+ *
1573
+ * @since 3.0.0
1574
+ * @since 3.9.0 $allow_bail parameter added.
1575
+ *
1576
+ * @param bool $allow_bail Optional. Allows the function to bail. Default true.
1577
+ * @return bool True with a successful connection, false on failure.
1578
+ */
1579
+ public function db_connect( $allow_bail = true ) {
1580
+ $this->is_mysql = true;
1581
+
1582
+ /*
1583
+ * Deprecated in 3.9+ when using MySQLi. No equivalent
1584
+ * $new_link parameter exists for mysqli_* functions.
1585
+ */
1586
+ $new_link = defined( 'MYSQL_NEW_LINK' ) ? MYSQL_NEW_LINK : true;
1587
+ $client_flags = defined( 'MYSQL_CLIENT_FLAGS' ) ? MYSQL_CLIENT_FLAGS : 0;
1588
+
1589
+ if ( $this->use_mysqli ) {
1590
+ $this->dbh = mysqli_init();
1591
+
1592
+ $host = $this->dbhost;
1593
+ $port = null;
1594
+ $socket = null;
1595
+ $is_ipv6 = false;
1596
+
1597
+ if ( $host_data = $this->parse_db_host( $this->dbhost ) ) {
1598
+ list( $host, $port, $socket, $is_ipv6 ) = $host_data;
1599
+ }
1600
+
1601
+ /*
1602
+ * If using the `mysqlnd` library, the IPv6 address needs to be
1603
+ * enclosed in square brackets, whereas it doesn't while using the
1604
+ * `libmysqlclient` library.
1605
+ * @see https://bugs.php.net/bug.php?id=67563
1606
+ */
1607
+ if ( $is_ipv6 && extension_loaded( 'mysqlnd' ) ) {
1608
+ $host = "[$host]";
1609
+ }
1610
+
1611
+ if ( WP_DEBUG ) {
1612
+ mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags );
1613
+ } else {
1614
+ @mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags );
1615
+ }
1616
+
1617
+ if ( $this->dbh->connect_errno ) {
1618
+ $this->dbh = null;
1619
+
1620
+ /*
1621
+ * It's possible ext/mysqli is misconfigured. Fall back to ext/mysql if:
1622
+ * - We haven't previously connected, and
1623
+ * - WP_USE_EXT_MYSQL isn't set to false, and
1624
+ * - ext/mysql is loaded.
1625
+ */
1626
+ $attempt_fallback = true;
1627
+
1628
+ if ( $this->has_connected ) {
1629
+ $attempt_fallback = false;
1630
+ } elseif ( defined( 'WP_USE_EXT_MYSQL' ) && ! WP_USE_EXT_MYSQL ) {
1631
+ $attempt_fallback = false;
1632
+ } elseif ( ! function_exists( 'mysql_connect' ) ) {
1633
+ $attempt_fallback = false;
1634
+ }
1635
+
1636
+ if ( $attempt_fallback ) {
1637
+ $this->use_mysqli = false;
1638
+ return $this->db_connect( $allow_bail );
1639
+ }
1640
+ }
1641
+ } else {
1642
+ if ( WP_DEBUG ) {
1643
+ $this->dbh = mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, $new_link, $client_flags );
1644
+ } else {
1645
+ $this->dbh = @mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, $new_link, $client_flags );
1646
+ }
1647
+ }
1648
+
1649
+ if ( ! $this->dbh && $allow_bail ) {
1650
+ wp_load_translations_early();
1651
+
1652
+ // Load custom DB error template, if present.
1653
+ if ( file_exists( WP_CONTENT_DIR . '/db-error.php' ) ) {
1654
+ require_once( WP_CONTENT_DIR . '/db-error.php' );
1655
+ die();
1656
+ }
1657
+
1658
+ $message = '<h1>' . __( 'Error establishing a database connection' ) . "</h1>\n";
1659
+
1660
+ $message .= '<p>' . sprintf(
1661
+ /* translators: 1: wp-config.php, 2: database host */
1662
+ __( 'This either means that the username and password information in your %1$s file is incorrect or we can&#8217;t contact the database server at %2$s. This could mean your host&#8217;s database server is down.' ),
1663
+ '<code>wp-config.php</code>',
1664
+ '<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>'
1665
+ ) . "</p>\n";
1666
+
1667
+ $message .= "<ul>\n";
1668
+ $message .= '<li>' . __( 'Are you sure you have the correct username and password?' ) . "</li>\n";
1669
+ $message .= '<li>' . __( 'Are you sure that you have typed the correct hostname?' ) . "</li>\n";
1670
+ $message .= '<li>' . __( 'Are you sure that the database server is running?' ) . "</li>\n";
1671
+ $message .= "</ul>\n";
1672
+
1673
+ $message .= '<p>' . sprintf(
1674
+ /* translators: %s: support forums URL */
1675
+ __( 'If you&#8217;re unsure what these terms mean you should probably contact your host. If you still need help you can always visit the <a href="%s">WordPress Support Forums</a>.' ),
1676
+ __( 'https://wordpress.org/support/forums/' )
1677
+ ) . "</p>\n";
1678
+
1679
+ $this->bail( $message, 'db_connect_fail' );
1680
+
1681
+ return false;
1682
+ } elseif ( $this->dbh ) {
1683
+ if ( ! $this->has_connected ) {
1684
+ $this->init_charset();
1685
+ }
1686
+
1687
+ $this->has_connected = true;
1688
+
1689
+ $this->set_charset( $this->dbh );
1690
+
1691
+ $this->ready = true;
1692
+ $this->set_sql_mode();
1693
+ $this->select( $this->dbname, $this->dbh );
1694
+
1695
+ return true;
1696
+ }
1697
+
1698
+ return false;
1699
+ }
1700
+
1701
+ /**
1702
+ * Parse the DB_HOST setting to interpret it for mysqli_real_connect.
1703
+ *
1704
+ * mysqli_real_connect doesn't support the host param including a port or
1705
+ * socket like mysql_connect does. This duplicates how mysql_connect detects
1706
+ * a port and/or socket file.
1707
+ *
1708
+ * @since 4.9.0
1709
+ *
1710
+ * @param string $host The DB_HOST setting to parse.
1711
+ * @return array|bool Array containing the host, the port, the socket and whether
1712
+ * it is an IPv6 address, in that order. If $host couldn't be parsed,
1713
+ * returns false.
1714
+ */
1715
+ public function parse_db_host( $host ) {
1716
+ $port = null;
1717
+ $socket = null;
1718
+ $is_ipv6 = false;
1719
+
1720
+ // First peel off the socket parameter from the right, if it exists.
1721
+ $socket_pos = strpos( $host, ':/' );
1722
+ if ( $socket_pos !== false ) {
1723
+ $socket = substr( $host, $socket_pos + 1 );
1724
+ $host = substr( $host, 0, $socket_pos );
1725
+ }
1726
+
1727
+ // We need to check for an IPv6 address first.
1728
+ // An IPv6 address will always contain at least two colons.
1729
+ if ( substr_count( $host, ':' ) > 1 ) {
1730
+ $pattern = '#^(?:\[)?(?P<host>[0-9a-fA-F:]+)(?:\]:(?P<port>[\d]+))?#';
1731
+ $is_ipv6 = true;
1732
+ } else {
1733
+ // We seem to be dealing with an IPv4 address.
1734
+ $pattern = '#^(?P<host>[^:/]*)(?::(?P<port>[\d]+))?#';
1735
+ }
1736
+
1737
+ $matches = array();
1738
+ $result = preg_match( $pattern, $host, $matches );
1739
+
1740
+ if ( 1 !== $result ) {
1741
+ // Couldn't parse the address, bail.
1742
+ return false;
1743
+ }
1744
+
1745
+ $host = '';
1746
+ foreach ( array( 'host', 'port' ) as $component ) {
1747
+ if ( ! empty( $matches[ $component ] ) ) {
1748
+ $$component = $matches[ $component ];
1749
+ }
1750
+ }
1751
+
1752
+ return array( $host, $port, $socket, $is_ipv6 );
1753
+ }
1754
+
1755
+ /**
1756
+ * Checks that the connection to the database is still up. If not, try to reconnect.
1757
+ *
1758
+ * If this function is unable to reconnect, it will forcibly die, or if after the
1759
+ * the {@see 'template_redirect'} hook has been fired, return false instead.
1760
+ *
1761
+ * If $allow_bail is false, the lack of database connection will need
1762
+ * to be handled manually.
1763
+ *
1764
+ * @since 3.9.0
1765
+ *
1766
+ * @param bool $allow_bail Optional. Allows the function to bail. Default true.
1767
+ * @return bool|void True if the connection is up.
1768
+ */
1769
+ public function check_connection( $allow_bail = true ) {
1770
+ if ( $this->use_mysqli ) {
1771
+ if ( ! empty( $this->dbh ) && mysqli_ping( $this->dbh ) ) {
1772
+ return true;
1773
+ }
1774
+ } else {
1775
+ if ( ! empty( $this->dbh ) && mysql_ping( $this->dbh ) ) {
1776
+ return true;
1777
+ }
1778
+ }
1779
+
1780
+ $error_reporting = false;
1781
+
1782
+ // Disable warnings, as we don't want to see a multitude of "unable to connect" messages
1783
+ if ( WP_DEBUG ) {
1784
+ $error_reporting = error_reporting();
1785
+ error_reporting( $error_reporting & ~E_WARNING );
1786
+ }
1787
+
1788
+ for ( $tries = 1; $tries <= $this->reconnect_retries; $tries++ ) {
1789
+ // On the last try, re-enable warnings. We want to see a single instance of the
1790
+ // "unable to connect" message on the bail() screen, if it appears.
1791
+ if ( $this->reconnect_retries === $tries && WP_DEBUG ) {
1792
+ error_reporting( $error_reporting );
1793
+ }
1794
+
1795
+ if ( $this->db_connect( false ) ) {
1796
+ if ( $error_reporting ) {
1797
+ error_reporting( $error_reporting );
1798
+ }
1799
+
1800
+ return true;
1801
+ }
1802
+
1803
+ sleep( 1 );
1804
+ }
1805
+
1806
+ // If template_redirect has already happened, it's too late for wp_die()/dead_db().
1807
+ // Let's just return and hope for the best.
1808
+ if ( did_action( 'template_redirect' ) ) {
1809
+ return false;
1810
+ }
1811
+
1812
+ if ( ! $allow_bail ) {
1813
+ return false;
1814
+ }
1815
+
1816
+ wp_load_translations_early();
1817
+
1818
+ $message = '<h1>' . __( 'Error reconnecting to the database' ) . "</h1>\n";
1819
+
1820
+ $message .= '<p>' . sprintf(
1821
+ /* translators: %s: database host */
1822
+ __( 'This means that we lost contact with the database server at %s. This could mean your host&#8217;s database server is down.' ),
1823
+ '<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>'
1824
+ ) . "</p>\n";
1825
+
1826
+ $message .= "<ul>\n";
1827
+ $message .= '<li>' . __( 'Are you sure that the database server is running?' ) . "</li>\n";
1828
+ $message .= '<li>' . __( 'Are you sure that the database server is not under particularly heavy load?' ) . "</li>\n";
1829
+ $message .= "</ul>\n";
1830
+
1831
+ $message .= '<p>' . sprintf(
1832
+ /* translators: %s: support forums URL */
1833
+ __( 'If you&#8217;re unsure what these terms mean you should probably contact your host. If you still need help you can always visit the <a href="%s">WordPress Support Forums</a>.' ),
1834
+ __( 'https://wordpress.org/support/forums/' )
1835
+ ) . "</p>\n";
1836
+
1837
+ // We weren't able to reconnect, so we better bail.
1838
+ $this->bail( $message, 'db_connect_fail' );
1839
+
1840
+ // Call dead_db() if bail didn't die, because this database is no more. It has ceased to be (at least temporarily).
1841
+ dead_db();
1842
+ }
1843
+
1844
+ /**
1845
+ * Perform a MySQL database query, using current database connection.
1846
+ *
1847
+ * More information can be found on the codex page.
1848
+ *
1849
+ * @since 0.71
1850
+ *
1851
+ * @param string $query Database query
1852
+ * @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. Number of rows
1853
+ * affected/selected for all other queries. Boolean false on error.
1854
+ */
1855
+ public function query( $query ) {
1856
+ if ( ! $this->ready ) {
1857
+ $this->check_current_query = true;
1858
+ return false;
1859
+ }
1860
+
1861
+ /**
1862
+ * Filters the database query.
1863
+ *
1864
+ * Some queries are made before the plugins have been loaded,
1865
+ * and thus cannot be filtered with this method.
1866
+ *
1867
+ * @since 2.1.0
1868
+ *
1869
+ * @param string $query Database query.
1870
+ */
1871
+ $query = apply_filters( 'query', $query );
1872
+
1873
+ $this->flush();
1874
+
1875
+ // Log how the function was called
1876
+ $this->func_call = "\$db->query(\"$query\")";
1877
+
1878
+ // If we're writing to the database, make sure the query will write safely.
1879
+ if ( $this->check_current_query && ! $this->check_ascii( $query ) ) {
1880
+ $stripped_query = $this->strip_invalid_text_from_query( $query );
1881
+ // strip_invalid_text_from_query() can perform queries, so we need
1882
+ // to flush again, just to make sure everything is clear.
1883
+ $this->flush();
1884
+ if ( $stripped_query !== $query ) {
1885
+ $this->insert_id = 0;
1886
+ return false;
1887
+ }
1888
+ }
1889
+
1890
+ $this->check_current_query = true;
1891
+
1892
+ // Keep track of the last query for debug.
1893
+ $this->last_query = $query;
1894
+
1895
+ $this->_do_query( $query );
1896
+
1897
+ // MySQL server has gone away, try to reconnect.
1898
+ $mysql_errno = 0;
1899
+ if ( ! empty( $this->dbh ) ) {
1900
+ if ( $this->use_mysqli ) {
1901
+ if ( $this->dbh instanceof mysqli ) {
1902
+ $mysql_errno = mysqli_errno( $this->dbh );
1903
+ } else {
1904
+ // $dbh is defined, but isn't a real connection.
1905
+ // Something has gone horribly wrong, let's try a reconnect.
1906
+ $mysql_errno = 2006;
1907
+ }
1908
+ } else {
1909
+ if ( is_resource( $this->dbh ) ) {
1910
+ $mysql_errno = mysql_errno( $this->dbh );
1911
+ } else {
1912
+ $mysql_errno = 2006;
1913
+ }
1914
+ }
1915
+ }
1916
+
1917
+ if ( empty( $this->dbh ) || 2006 == $mysql_errno ) {
1918
+ if ( $this->check_connection() ) {
1919
+ $this->_do_query( $query );
1920
+ } else {
1921
+ $this->insert_id = 0;
1922
+ return false;
1923
+ }
1924
+ }
1925
+
1926
+ // If there is an error then take note of it.
1927
+ if ( $this->use_mysqli ) {
1928
+ if ( $this->dbh instanceof mysqli ) {
1929
+ $this->last_error = mysqli_error( $this->dbh );
1930
+ } else {
1931
+ $this->last_error = __( 'Unable to retrieve the error message from MySQL' );
1932
+ }
1933
+ } else {
1934
+ if ( is_resource( $this->dbh ) ) {
1935
+ $this->last_error = mysql_error( $this->dbh );
1936
+ } else {
1937
+ $this->last_error = __( 'Unable to retrieve the error message from MySQL' );
1938
+ }
1939
+ }
1940
+
1941
+ if ( $this->last_error ) {
1942
+ // Clear insert_id on a subsequent failed insert.
1943
+ if ( $this->insert_id && preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
1944
+ $this->insert_id = 0;
1945
+ }
1946
+
1947
+ $this->print_error();
1948
+ return false;
1949
+ }
1950
+
1951
+ if ( preg_match( '/^\s*(create|alter|truncate|drop)\s/i', $query ) ) {
1952
+ $return_val = $this->result;
1953
+ } elseif ( preg_match( '/^\s*(insert|delete|update|replace)\s/i', $query ) ) {
1954
+ if ( $this->use_mysqli ) {
1955
+ $this->rows_affected = mysqli_affected_rows( $this->dbh );
1956
+ } else {
1957
+ $this->rows_affected = mysql_affected_rows( $this->dbh );
1958
+ }
1959
+ // Take note of the insert_id
1960
+ if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
1961
+ if ( $this->use_mysqli ) {
1962
+ $this->insert_id = mysqli_insert_id( $this->dbh );
1963
+ } else {
1964
+ $this->insert_id = mysql_insert_id( $this->dbh );
1965
+ }
1966
+ }
1967
+ // Return number of rows affected
1968
+ $return_val = $this->rows_affected;
1969
+ } else {
1970
+ $num_rows = 0;
1971
+ if ( $this->use_mysqli && $this->result instanceof mysqli_result ) {
1972
+ while ( $row = mysqli_fetch_object( $this->result ) ) {
1973
+ $this->last_result[ $num_rows ] = $row;
1974
+ $num_rows++;
1975
+ }
1976
+ } elseif ( is_resource( $this->result ) ) {
1977
+ while ( $row = mysql_fetch_object( $this->result ) ) {
1978
+ $this->last_result[ $num_rows ] = $row;
1979
+ $num_rows++;
1980
+ }
1981
+ }
1982
+
1983
+ // Log number of rows the query returned
1984
+ // and return number of rows selected
1985
+ $this->num_rows = $num_rows;
1986
+ $return_val = $num_rows;
1987
+ }
1988
+
1989
+ return $return_val;
1990
+ }
1991
+
1992
+ /**
1993
+ * Internal function to perform the mysql_query() call.
1994
+ *
1995
+ * @since 3.9.0
1996
+ *
1997
+ * @see wpdb::query()
1998
+ *
1999
+ * @param string $query The query to run.
2000
+ */
2001
+ private function _do_query( $query ) {
2002
+ if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
2003
+ $this->timer_start();
2004
+ }
2005
+
2006
+ if ( ! empty( $this->dbh ) && $this->use_mysqli ) {
2007
+ $this->result = mysqli_query( $this->dbh, $query );
2008
+ } elseif ( ! empty( $this->dbh ) ) {
2009
+ $this->result = mysql_query( $query, $this->dbh );
2010
+ }
2011
+ $this->num_queries++;
2012
+
2013
+ if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
2014
+ $this->queries[] = array(
2015
+ $query,
2016
+ $this->timer_stop(),
2017
+ $this->get_caller(),
2018
+ $this->time_start,
2019
+ );
2020
+ }
2021
+ }
2022
+
2023
+ /**
2024
+ * Generates and returns a placeholder escape string for use in queries returned by ::prepare().
2025
+ *
2026
+ * @since 4.8.3
2027
+ *
2028
+ * @return string String to escape placeholders.
2029
+ */
2030
+ public function placeholder_escape() {
2031
+ static $placeholder;
2032
+
2033
+ if ( ! $placeholder ) {
2034
+ // If ext/hash is not present, compat.php's hash_hmac() does not support sha256.
2035
+ $algo = function_exists( 'hash' ) ? 'sha256' : 'sha1';
2036
+ // Old WP installs may not have AUTH_SALT defined.
2037
+ $salt = defined( 'AUTH_SALT' ) && AUTH_SALT ? AUTH_SALT : (string) rand();
2038
+
2039
+ $placeholder = '{' . hash_hmac( $algo, uniqid( $salt, true ), $salt ) . '}';
2040
+ }
2041
+
2042
+ /*
2043
+ * Add the filter to remove the placeholder escaper. Uses priority 0, so that anything
2044
+ * else attached to this filter will receive the query with the placeholder string removed.
2045
+ */
2046
+ if ( ! has_filter( 'query', array( $this, 'remove_placeholder_escape' ) ) ) {
2047
+ add_filter( 'query', array( $this, 'remove_placeholder_escape' ), 0 );
2048
+ }
2049
+
2050
+ return $placeholder;
2051
+ }
2052
+
2053
+ /**
2054
+ * Adds a placeholder escape string, to escape anything that resembles a printf() placeholder.
2055
+ *
2056
+ * @since 4.8.3
2057
+ *
2058
+ * @param string $query The query to escape.
2059
+ * @return string The query with the placeholder escape string inserted where necessary.
2060
+ */
2061
+ public function add_placeholder_escape( $query ) {
2062
+ /*
2063
+ * To prevent returning anything that even vaguely resembles a placeholder,
2064
+ * we clobber every % we can find.
2065
+ */
2066
+ return str_replace( '%', $this->placeholder_escape(), $query );
2067
+ }
2068
+
2069
+ /**
2070
+ * Removes the placeholder escape strings from a query.
2071
+ *
2072
+ * @since 4.8.3
2073
+ *
2074
+ * @param string $query The query from which the placeholder will be removed.
2075
+ * @return string The query with the placeholder removed.
2076
+ */
2077
+ public function remove_placeholder_escape( $query ) {
2078
+ return str_replace( $this->placeholder_escape(), '%', $query );
2079
+ }
2080
+
2081
+ /**
2082
+ * Insert a row into a table.
2083
+ *
2084
+ * wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 'bar' ) )
2085
+ * wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( '%s', '%d' ) )
2086
+ *
2087
+ * @since 2.5.0
2088
+ * @see wpdb::prepare()
2089
+ * @see wpdb::$field_types
2090
+ * @see wp_set_wpdb_vars()
2091
+ *
2092
+ * @param string $table Table name
2093
+ * @param array $data Data to insert (in column => value pairs).
2094
+ * Both $data columns and $data values should be "raw" (neither should be SQL escaped).
2095
+ * Sending a null value will cause the column to be set to NULL - the corresponding format is ignored in this case.
2096
+ * @param array|string $format Optional. An array of formats to be mapped to each of the value in $data.
2097
+ * If string, that format will be used for all of the values in $data.
2098
+ * A format is one of '%d', '%f', '%s' (integer, float, string).
2099
+ * If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
2100
+ * @return int|false The number of rows inserted, or false on error.
2101
+ */
2102
+ public function insert( $table, $data, $format = null ) {
2103
+ return $this->_insert_replace_helper( $table, $data, $format, 'INSERT' );
2104
+ }
2105
+
2106
+ /**
2107
+ * Replace a row into a table.
2108
+ *
2109
+ * wpdb::replace( 'table', array( 'column' => 'foo', 'field' => 'bar' ) )
2110
+ * wpdb::replace( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( '%s', '%d' ) )
2111
+ *
2112
+ * @since 3.0.0
2113
+ * @see wpdb::prepare()
2114
+ * @see wpdb::$field_types
2115
+ * @see wp_set_wpdb_vars()
2116
+ *
2117
+ * @param string $table Table name
2118
+ * @param array $data Data to insert (in column => value pairs).
2119
+ * Both $data columns and $data values should be "raw" (neither should be SQL escaped).
2120
+ * Sending a null value will cause the column to be set to NULL - the corresponding format is ignored in this case.
2121
+ * @param array|string $format Optional. An array of formats to be mapped to each of the value in $data.
2122
+ * If string, that format will be used for all of the values in $data.
2123
+ * A format is one of '%d', '%f', '%s' (integer, float, string).
2124
+ * If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
2125
+ * @return int|false The number of rows affected, or false on error.
2126
+ */
2127
+ public function replace( $table, $data, $format = null ) {
2128
+ return $this->_insert_replace_helper( $table, $data, $format, 'REPLACE' );
2129
+ }
2130
+
2131
+ /**
2132
+ * Helper function for insert and replace.
2133
+ *
2134
+ * Runs an insert or replace query based on $type argument.
2135
+ *
2136
+ * @since 3.0.0
2137
+ * @see wpdb::prepare()
2138
+ * @see wpdb::$field_types
2139
+ * @see wp_set_wpdb_vars()
2140
+ *
2141
+ * @param string $table Table name
2142
+ * @param array $data Data to insert (in column => value pairs).
2143
+ * Both $data columns and $data values should be "raw" (neither should be SQL escaped).
2144
+ * Sending a null value will cause the column to be set to NULL - the corresponding format is ignored in this case.
2145
+ * @param array|string $format Optional. An array of formats to be mapped to each of the value in $data.
2146
+ * If string, that format will be used for all of the values in $data.
2147
+ * A format is one of '%d', '%f', '%s' (integer, float, string).
2148
+ * If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
2149
+ * @param string $type Optional. What type of operation is this? INSERT or REPLACE. Defaults to INSERT.
2150
+ * @return int|false The number of rows affected, or false on error.
2151
+ */
2152
+ function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) {
2153
+ $this->insert_id = 0;
2154
+
2155
+ if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) ) {
2156
+ return false;
2157
+ }
2158
+
2159
+ $data = $this->process_fields( $table, $data, $format );
2160
+ if ( false === $data ) {
2161
+ return false;
2162
+ }
2163
+
2164
+ $formats = $values = array();
2165
+ foreach ( $data as $value ) {
2166
+ if ( is_null( $value['value'] ) ) {
2167
+ $formats[] = 'NULL';
2168
+ continue;
2169
+ }
2170
+
2171
+ $formats[] = $value['format'];
2172
+ $values[] = $value['value'];
2173
+ }
2174
+
2175
+ $fields = '`' . implode( '`, `', array_keys( $data ) ) . '`';
2176
+ $formats = implode( ', ', $formats );
2177
+
2178
+ $sql = "$type INTO `$table` ($fields) VALUES ($formats)";
2179
+
2180
+ $this->check_current_query = false;
2181
+ return $this->query( $this->prepare( $sql, $values ) );
2182
+ }
2183
+
2184
+ /**
2185
+ * Update a row in the table
2186
+ *
2187
+ * wpdb::update( 'table', array( 'column' => 'foo', 'field' => 'bar' ), array( 'ID' => 1 ) )
2188
+ * wpdb::update( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( 'ID' => 1 ), array( '%s', '%d' ), array( '%d' ) )
2189
+ *
2190
+ * @since 2.5.0
2191
+ * @see wpdb::prepare()
2192
+ * @see wpdb::$field_types
2193
+ * @see wp_set_wpdb_vars()
2194
+ *
2195
+ * @param string $table Table name
2196
+ * @param array $data Data to update (in column => value pairs).
2197
+ * Both $data columns and $data values should be "raw" (neither should be SQL escaped).
2198
+ * Sending a null value will cause the column to be set to NULL - the corresponding
2199
+ * format is ignored in this case.
2200
+ * @param array $where A named array of WHERE clauses (in column => value pairs).
2201
+ * Multiple clauses will be joined with ANDs.
2202
+ * Both $where columns and $where values should be "raw".
2203
+ * Sending a null value will create an IS NULL comparison - the corresponding format will be ignored in this case.
2204
+ * @param array|string $format Optional. An array of formats to be mapped to each of the values in $data.
2205
+ * If string, that format will be used for all of the values in $data.
2206
+ * A format is one of '%d', '%f', '%s' (integer, float, string).
2207
+ * If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
2208
+ * @param array|string $where_format Optional. An array of formats to be mapped to each of the values in $where.
2209
+ * If string, that format will be used for all of the items in $where.
2210
+ * A format is one of '%d', '%f', '%s' (integer, float, string).
2211
+ * If omitted, all values in $where will be treated as strings.
2212
+ * @return int|false The number of rows updated, or false on error.
2213
+ */
2214
+ public function update( $table, $data, $where, $format = null, $where_format = null ) {
2215
+ if ( ! is_array( $data ) || ! is_array( $where ) ) {
2216
+ return false;
2217
+ }
2218
+
2219
+ $data = $this->process_fields( $table, $data, $format );
2220
+ if ( false === $data ) {
2221
+ return false;
2222
+ }
2223
+ $where = $this->process_fields( $table, $where, $where_format );
2224
+ if ( false === $where ) {
2225
+ return false;
2226
+ }
2227
+
2228
+ $fields = $conditions = $values = array();
2229
+ foreach ( $data as $field => $value ) {
2230
+ if ( is_null( $value['value'] ) ) {
2231
+ $fields[] = "`$field` = NULL";
2232
+ continue;
2233
+ }
2234
+
2235
+ $fields[] = "`$field` = " . $value['format'];
2236
+ $values[] = $value['value'];
2237
+ }
2238
+ foreach ( $where as $field => $value ) {
2239
+ if ( is_null( $value['value'] ) ) {
2240
+ $conditions[] = "`$field` IS NULL";
2241
+ continue;
2242
+ }
2243
+
2244
+ $conditions[] = "`$field` = " . $value['format'];
2245
+ $values[] = $value['value'];
2246
+ }
2247
+
2248
+ $fields = implode( ', ', $fields );
2249
+ $conditions = implode( ' AND ', $conditions );
2250
+
2251
+ $sql = "UPDATE `$table` SET $fields WHERE $conditions";
2252
+
2253
+ $this->check_current_query = false;
2254
+ return $this->query( $this->prepare( $sql, $values ) );
2255
+ }
2256
+
2257
+ /**
2258
+ * Delete a row in the table
2259
+ *
2260
+ * wpdb::delete( 'table', array( 'ID' => 1 ) )
2261
+ * wpdb::delete( 'table', array( 'ID' => 1 ), array( '%d' ) )
2262
+ *
2263
+ * @since 3.4.0
2264
+ * @see wpdb::prepare()
2265
+ * @see wpdb::$field_types
2266
+ * @see wp_set_wpdb_vars()
2267
+ *
2268
+ * @param string $table Table name
2269
+ * @param array $where A named array of WHERE clauses (in column => value pairs).
2270
+ * Multiple clauses will be joined with ANDs.
2271
+ * Both $where columns and $where values should be "raw".
2272
+ * Sending a null value will create an IS NULL comparison - the corresponding format will be ignored in this case.
2273
+ * @param array|string $where_format Optional. An array of formats to be mapped to each of the values in $where.
2274
+ * If string, that format will be used for all of the items in $where.
2275
+ * A format is one of '%d', '%f', '%s' (integer, float, string).
2276
+ * If omitted, all values in $where will be treated as strings unless otherwise specified in wpdb::$field_types.
2277
+ * @return int|false The number of rows updated, or false on error.
2278
+ */
2279
+ public function delete( $table, $where, $where_format = null ) {
2280
+ if ( ! is_array( $where ) ) {
2281
+ return false;
2282
+ }
2283
+
2284
+ $where = $this->process_fields( $table, $where, $where_format );
2285
+ if ( false === $where ) {
2286
+ return false;
2287
+ }
2288
+
2289
+ $conditions = $values = array();
2290
+ foreach ( $where as $field => $value ) {
2291
+ if ( is_null( $value['value'] ) ) {
2292
+ $conditions[] = "`$field` IS NULL";
2293
+ continue;
2294
+ }
2295
+
2296
+ $conditions[] = "`$field` = " . $value['format'];
2297
+ $values[] = $value['value'];
2298
+ }
2299
+
2300
+ $conditions = implode( ' AND ', $conditions );
2301
+
2302
+ $sql = "DELETE FROM `$table` WHERE $conditions";
2303
+
2304
+ $this->check_current_query = false;
2305
+ return $this->query( $this->prepare( $sql, $values ) );
2306
+ }
2307
+
2308
+ /**
2309
+ * Processes arrays of field/value pairs and field formats.
2310
+ *
2311
+ * This is a helper method for wpdb's CRUD methods, which take field/value
2312
+ * pairs for inserts, updates, and where clauses. This method first pairs
2313
+ * each value with a format. Then it determines the charset of that field,
2314
+ * using that to determine if any invalid text would be stripped. If text is
2315
+ * stripped, then field processing is rejected and the query fails.
2316
+ *
2317
+ * @since 4.2.0
2318
+ *
2319
+ * @param string $table Table name.
2320
+ * @param array $data Field/value pair.
2321
+ * @param mixed $format Format for each field.
2322
+ * @return array|false Returns an array of fields that contain paired values
2323
+ * and formats. Returns false for invalid values.
2324
+ */
2325
+ protected function process_fields( $table, $data, $format ) {
2326
+ $data = $this->process_field_formats( $data, $format );
2327
+ if ( false === $data ) {
2328
+ return false;
2329
+ }
2330
+
2331
+ $data = $this->process_field_charsets( $data, $table );
2332
+ if ( false === $data ) {
2333
+ return false;
2334
+ }
2335
+
2336
+ $data = $this->process_field_lengths( $data, $table );
2337
+ if ( false === $data ) {
2338
+ return false;
2339
+ }
2340
+
2341
+ $converted_data = $this->strip_invalid_text( $data );
2342
+
2343
+ if ( $data !== $converted_data ) {
2344
+ return false;
2345
+ }
2346
+
2347
+ return $data;
2348
+ }
2349
+
2350
+ /**
2351
+ * Prepares arrays of value/format pairs as passed to wpdb CRUD methods.
2352
+ *
2353
+ * @since 4.2.0
2354
+ *
2355
+ * @param array $data Array of fields to values.
2356
+ * @param mixed $format Formats to be mapped to the values in $data.
2357
+ * @return array Array, keyed by field names with values being an array
2358
+ * of 'value' and 'format' keys.
2359
+ */
2360
+ protected function process_field_formats( $data, $format ) {
2361
+ $formats = $original_formats = (array) $format;
2362
+
2363
+ foreach ( $data as $field => $value ) {
2364
+ $value = array(
2365
+ 'value' => $value,
2366
+ 'format' => '%s',
2367
+ );
2368
+
2369
+ if ( ! empty( $format ) ) {
2370
+ $value['format'] = array_shift( $formats );
2371
+ if ( ! $value['format'] ) {
2372
+ $value['format'] = reset( $original_formats );
2373
+ }
2374
+ } elseif ( isset( $this->field_types[ $field ] ) ) {
2375
+ $value['format'] = $this->field_types[ $field ];
2376
+ }
2377
+
2378
+ $data[ $field ] = $value;
2379
+ }
2380
+
2381
+ return $data;
2382
+ }
2383
+
2384
+ /**
2385
+ * Adds field charsets to field/value/format arrays generated by
2386
+ * the wpdb::process_field_formats() method.
2387
+ *
2388
+ * @since 4.2.0
2389
+ *
2390
+ * @param array $data As it comes from the wpdb::process_field_formats() method.
2391
+ * @param string $table Table name.
2392
+ * @return array|false The same array as $data with additional 'charset' keys.
2393
+ */
2394
+ protected function process_field_charsets( $data, $table ) {
2395
+ foreach ( $data as $field => $value ) {
2396
+ if ( '%d' === $value['format'] || '%f' === $value['format'] ) {
2397
+ /*
2398
+ * We can skip this field if we know it isn't a string.
2399
+ * This checks %d/%f versus ! %s because its sprintf() could take more.
2400
+ */
2401
+ $value['charset'] = false;
2402
+ } else {
2403
+ $value['charset'] = $this->get_col_charset( $table, $field );
2404
+ if ( is_wp_error( $value['charset'] ) ) {
2405
+ return false;
2406
+ }
2407
+ }
2408
+
2409
+ $data[ $field ] = $value;
2410
+ }
2411
+
2412
+ return $data;
2413
+ }
2414
+
2415
+ /**
2416
+ * For string fields, record the maximum string length that field can safely save.
2417
+ *
2418
+ * @since 4.2.1
2419
+ *
2420
+ * @param array $data As it comes from the wpdb::process_field_charsets() method.
2421
+ * @param string $table Table name.
2422
+ * @return array|false The same array as $data with additional 'length' keys, or false if
2423
+ * any of the values were too long for their corresponding field.
2424
+ */
2425
+ protected function process_field_lengths( $data, $table ) {
2426
+ foreach ( $data as $field => $value ) {
2427
+ if ( '%d' === $value['format'] || '%f' === $value['format'] ) {
2428
+ /*
2429
+ * We can skip this field if we know it isn't a string.
2430
+ * This checks %d/%f versus ! %s because its sprintf() could take more.
2431
+ */
2432
+ $value['length'] = false;
2433
+ } else {
2434
+ $value['length'] = $this->get_col_length( $table, $field );
2435
+ if ( is_wp_error( $value['length'] ) ) {
2436
+ return false;
2437
+ }
2438
+ }
2439
+
2440
+ $data[ $field ] = $value;
2441
+ }
2442
+
2443
+ return $data;
2444
+ }
2445
+
2446
+ /**
2447
+ * Retrieve one variable from the database.
2448
+ *
2449
+ * Executes a SQL query and returns the value from the SQL result.
2450
+ * If the SQL result contains more than one column and/or more than one row, this function returns the value in the column and row specified.
2451
+ * If $query is null, this function returns the value in the specified column and row from the previous SQL result.
2452
+ *
2453
+ * @since 0.71
2454
+ *
2455
+ * @param string|null $query Optional. SQL query. Defaults to null, use the result from the previous query.
2456
+ * @param int $x Optional. Column of value to return. Indexed from 0.
2457
+ * @param int $y Optional. Row of value to return. Indexed from 0.
2458
+ * @return string|null Database query result (as string), or null on failure
2459
+ */
2460
+ public function get_var( $query = null, $x = 0, $y = 0 ) {
2461
+ $this->func_call = "\$db->get_var(\"$query\", $x, $y)";
2462
+
2463
+ if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
2464
+ $this->check_current_query = false;
2465
+ }
2466
+
2467
+ if ( $query ) {
2468
+ $this->query( $query );
2469
+ }
2470
+
2471
+ // Extract var out of cached results based x,y vals
2472
+ if ( ! empty( $this->last_result[ $y ] ) ) {
2473
+ $values = array_values( get_object_vars( $this->last_result[ $y ] ) );
2474
+ }
2475
+
2476
+ // If there is a value return it else return null
2477
+ return ( isset( $values[ $x ] ) && $values[ $x ] !== '' ) ? $values[ $x ] : null;
2478
+ }
2479
+
2480
+ /**
2481
+ * Retrieve one row from the database.
2482
+ *
2483
+ * Executes a SQL query and returns the row from the SQL result.
2484
+ *
2485
+ * @since 0.71
2486
+ *
2487
+ * @param string|null $query SQL query.
2488
+ * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
2489
+ * an stdClass object, an associative array, or a numeric array, respectively. Default OBJECT.
2490
+ * @param int $y Optional. Row to return. Indexed from 0.
2491
+ * @return array|object|null|void Database query result in format specified by $output or null on failure
2492
+ */
2493
+ public function get_row( $query = null, $output = OBJECT, $y = 0 ) {
2494
+ $this->func_call = "\$db->get_row(\"$query\",$output,$y)";
2495
+
2496
+ if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
2497
+ $this->check_current_query = false;
2498
+ }
2499
+
2500
+ if ( $query ) {
2501
+ $this->query( $query );
2502
+ } else {
2503
+ return null;
2504
+ }
2505
+
2506
+ if ( ! isset( $this->last_result[ $y ] ) ) {
2507
+ return null;
2508
+ }
2509
+
2510
+ if ( $output == OBJECT ) {
2511
+ return $this->last_result[ $y ] ? $this->last_result[ $y ] : null;
2512
+ } elseif ( $output == ARRAY_A ) {
2513
+ return $this->last_result[ $y ] ? get_object_vars( $this->last_result[ $y ] ) : null;
2514
+ } elseif ( $output == ARRAY_N ) {
2515
+ return $this->last_result[ $y ] ? array_values( get_object_vars( $this->last_result[ $y ] ) ) : null;
2516
+ } elseif ( strtoupper( $output ) === OBJECT ) {
2517
+ // Back compat for OBJECT being previously case insensitive.
2518
+ return $this->last_result[ $y ] ? $this->last_result[ $y ] : null;
2519
+ } else {
2520
+ $this->print_error( ' $db->get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N' );
2521
+ }
2522
+ }
2523
+
2524
+ /**
2525
+ * Retrieve one column from the database.
2526
+ *
2527
+ * Executes a SQL query and returns the column from the SQL result.
2528
+ * If the SQL result contains more than one column, this function returns the column specified.
2529
+ * If $query is null, this function returns the specified column from the previous SQL result.
2530
+ *
2531
+ * @since 0.71
2532
+ *
2533
+ * @param string|null $query Optional. SQL query. Defaults to previous query.
2534
+ * @param int $x Optional. Column to return. Indexed from 0.
2535
+ * @return array Database query result. Array indexed from 0 by SQL result row number.
2536
+ */
2537
+ public function get_col( $query = null, $x = 0 ) {
2538
+ if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
2539
+ $this->check_current_query = false;
2540
+ }
2541
+
2542
+ if ( $query ) {
2543
+ $this->query( $query );
2544
+ }
2545
+
2546
+ $new_array = array();
2547
+ // Extract the column values
2548
+ if ( $this->last_result ) {
2549
+ for ( $i = 0, $j = count( $this->last_result ); $i < $j; $i++ ) {
2550
+ $new_array[ $i ] = $this->get_var( null, $x, $i );
2551
+ }
2552
+ }
2553
+ return $new_array;
2554
+ }
2555
+
2556
+ /**
2557
+ * Retrieve an entire SQL result set from the database (i.e., many rows)
2558
+ *
2559
+ * Executes a SQL query and returns the entire SQL result.
2560
+ *
2561
+ * @since 0.71
2562
+ *
2563
+ * @param string $query SQL query.
2564
+ * @param string $output Optional. Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants.
2565
+ * With one of the first three, return an array of rows indexed from 0 by SQL result row number.
2566
+ * Each row is an associative array (column => value, ...), a numerically indexed array (0 => value, ...), or an object. ( ->column = value ), respectively.
2567
+ * With OBJECT_K, return an associative array of row objects keyed by the value of each row's first column's value.
2568
+ * Duplicate keys are discarded.
2569
+ * @return array|object|null Database query results
2570
+ */
2571
+ public function get_results( $query = null, $output = OBJECT ) {
2572
+ $this->func_call = "\$db->get_results(\"$query\", $output)";
2573
+
2574
+ if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
2575
+ $this->check_current_query = false;
2576
+ }
2577
+
2578
+ if ( $query ) {
2579
+ $this->query( $query );
2580
+ } else {
2581
+ return null;
2582
+ }
2583
+
2584
+ $new_array = array();
2585
+ if ( $output == OBJECT ) {
2586
+ // Return an integer-keyed array of row objects
2587
+ return $this->last_result;
2588
+ } elseif ( $output == OBJECT_K ) {
2589
+ // Return an array of row objects with keys from column 1
2590
+ // (Duplicates are discarded)
2591
+ if ( $this->last_result ) {
2592
+ foreach ( $this->last_result as $row ) {
2593
+ $var_by_ref = get_object_vars( $row );
2594
+ $key = array_shift( $var_by_ref );
2595
+ if ( ! isset( $new_array[ $key ] ) ) {
2596
+ $new_array[ $key ] = $row;
2597
+ }
2598
+ }
2599
+ }
2600
+ return $new_array;
2601
+ } elseif ( $output == ARRAY_A || $output == ARRAY_N ) {
2602
+ // Return an integer-keyed array of...
2603
+ if ( $this->last_result ) {
2604
+ foreach ( (array) $this->last_result as $row ) {
2605
+ if ( $output == ARRAY_N ) {
2606
+ // ...integer-keyed row arrays
2607
+ $new_array[] = array_values( get_object_vars( $row ) );
2608
+ } else {
2609
+ // ...column name-keyed row arrays
2610
+ $new_array[] = get_object_vars( $row );
2611
+ }
2612
+ }
2613
+ }
2614
+ return $new_array;
2615
+ } elseif ( strtoupper( $output ) === OBJECT ) {
2616
+ // Back compat for OBJECT being previously case insensitive.
2617
+ return $this->last_result;
2618
+ }
2619
+ return null;
2620
+ }
2621
+
2622
+ /**
2623
+ * Retrieves the character set for the given table.
2624
+ *
2625
+ * @since 4.2.0
2626
+ *
2627
+ * @param string $table Table name.
2628
+ * @return string|WP_Error Table character set, WP_Error object if it couldn't be found.
2629
+ */
2630
+ protected function get_table_charset( $table ) {
2631
+ $tablekey = strtolower( $table );
2632
+
2633
+ /**
2634
+ * Filters the table charset value before the DB is checked.
2635
+ *
2636
+ * Passing a non-null value to the filter will effectively short-circuit
2637
+ * checking the DB for the charset, returning that value instead.
2638
+ *
2639
+ * @since 4.2.0
2640
+ *
2641
+ * @param string $charset The character set to use. Default null.
2642
+ * @param string $table The name of the table being checked.
2643
+ */
2644
+ $charset = apply_filters( 'pre_get_table_charset', null, $table );
2645
+ if ( null !== $charset ) {
2646
+ return $charset;
2647
+ }
2648
+
2649
+ if ( isset( $this->table_charset[ $tablekey ] ) ) {
2650
+ return $this->table_charset[ $tablekey ];
2651
+ }
2652
+
2653
+ $charsets = $columns = array();
2654
+
2655
+ $table_parts = explode( '.', $table );
2656
+ $table = '`' . implode( '`.`', $table_parts ) . '`';
2657
+ $results = $this->get_results( "SHOW FULL COLUMNS FROM $table" );
2658
+ if ( ! $results ) {
2659
+ return new WP_Error( 'wpdb_get_table_charset_failure' );
2660
+ }
2661
+
2662
+ foreach ( $results as $column ) {
2663
+ $columns[ strtolower( $column->Field ) ] = $column;
2664
+ }
2665
+
2666
+ $this->col_meta[ $tablekey ] = $columns;
2667
+
2668
+ foreach ( $columns as $column ) {
2669
+ if ( ! empty( $column->Collation ) ) {
2670
+ list( $charset ) = explode( '_', $column->Collation );
2671
+
2672
+ // If the current connection can't support utf8mb4 characters, let's only send 3-byte utf8 characters.
2673
+ if ( 'utf8mb4' === $charset && ! $this->has_cap( 'utf8mb4' ) ) {
2674
+ $charset = 'utf8';
2675
+ }
2676
+
2677
+ $charsets[ strtolower( $charset ) ] = true;
2678
+ }
2679
+
2680
+ list( $type ) = explode( '(', $column->Type );
2681
+
2682
+ // A binary/blob means the whole query gets treated like this.
2683
+ if ( in_array( strtoupper( $type ), array( 'BINARY', 'VARBINARY', 'TINYBLOB', 'MEDIUMBLOB', 'BLOB', 'LONGBLOB' ) ) ) {
2684
+ $this->table_charset[ $tablekey ] = 'binary';
2685
+ return 'binary';
2686
+ }
2687
+ }
2688
+
2689
+ // utf8mb3 is an alias for utf8.
2690
+ if ( isset( $charsets['utf8mb3'] ) ) {
2691
+ $charsets['utf8'] = true;
2692
+ unset( $charsets['utf8mb3'] );
2693
+ }
2694
+
2695
+ // Check if we have more than one charset in play.
2696
+ $count = count( $charsets );
2697
+ if ( 1 === $count ) {
2698
+ $charset = key( $charsets );
2699
+ } elseif ( 0 === $count ) {
2700
+ // No charsets, assume this table can store whatever.
2701
+ $charset = false;
2702
+ } else {
2703
+ // More than one charset. Remove latin1 if present and recalculate.
2704
+ unset( $charsets['latin1'] );
2705
+ $count = count( $charsets );
2706
+ if ( 1 === $count ) {
2707
+ // Only one charset (besides latin1).
2708
+ $charset = key( $charsets );
2709
+ } elseif ( 2 === $count && isset( $charsets['utf8'], $charsets['utf8mb4'] ) ) {
2710
+ // Two charsets, but they're utf8 and utf8mb4, use utf8.
2711
+ $charset = 'utf8';
2712
+ } else {
2713
+ // Two mixed character sets. ascii.
2714
+ $charset = 'ascii';
2715
+ }
2716
+ }
2717
+
2718
+ $this->table_charset[ $tablekey ] = $charset;
2719
+ return $charset;
2720
+ }
2721
+
2722
+ /**
2723
+ * Retrieves the character set for the given column.
2724
+ *
2725
+ * @since 4.2.0
2726
+ *
2727
+ * @param string $table Table name.
2728
+ * @param string $column Column name.
2729
+ * @return string|false|WP_Error Column character set as a string. False if the column has no
2730
+ * character set. WP_Error object if there was an error.
2731
+ */
2732
+ public function get_col_charset( $table, $column ) {
2733
+ $tablekey = strtolower( $table );
2734
+ $columnkey = strtolower( $column );
2735
+
2736
+ /**
2737
+ * Filters the column charset value before the DB is checked.
2738
+ *
2739
+ * Passing a non-null value to the filter will short-circuit
2740
+ * checking the DB for the charset, returning that value instead.
2741
+ *
2742
+ * @since 4.2.0
2743
+ *
2744
+ * @param string $charset The character set to use. Default null.
2745
+ * @param string $table The name of the table being checked.
2746
+ * @param string $column The name of the column being checked.
2747
+ */
2748
+ $charset = apply_filters( 'pre_get_col_charset', null, $table, $column );
2749
+ if ( null !== $charset ) {
2750
+ return $charset;
2751
+ }
2752
+
2753
+ // Skip this entirely if this isn't a MySQL database.
2754
+ if ( empty( $this->is_mysql ) ) {
2755
+ return false;
2756
+ }
2757
+
2758
+ if ( empty( $this->table_charset[ $tablekey ] ) ) {
2759
+ // This primes column information for us.
2760
+ $table_charset = $this->get_table_charset( $table );
2761
+ if ( is_wp_error( $table_charset ) ) {
2762
+ return $table_charset;
2763
+ }
2764
+ }
2765
+
2766
+ // If still no column information, return the table charset.
2767
+ if ( empty( $this->col_meta[ $tablekey ] ) ) {
2768
+ return $this->table_charset[ $tablekey ];
2769
+ }
2770
+
2771
+ // If this column doesn't exist, return the table charset.
2772
+ if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
2773
+ return $this->table_charset[ $tablekey ];
2774
+ }
2775
+
2776
+ // Return false when it's not a string column.
2777
+ if ( empty( $this->col_meta[ $tablekey ][ $columnkey ]->Collation ) ) {
2778
+ return false;
2779
+ }
2780
+
2781
+ list( $charset ) = explode( '_', $this->col_meta[ $tablekey ][ $columnkey ]->Collation );
2782
+ return $charset;
2783
+ }
2784
+
2785
+ /**
2786
+ * Retrieve the maximum string length allowed in a given column.
2787
+ * The length may either be specified as a byte length or a character length.
2788
+ *
2789
+ * @since 4.2.1
2790
+ *
2791
+ * @param string $table Table name.
2792
+ * @param string $column Column name.
2793
+ * @return array|false|WP_Error array( 'length' => (int), 'type' => 'byte' | 'char' )
2794
+ * false if the column has no length (for example, numeric column)
2795
+ * WP_Error object if there was an error.
2796
+ */
2797
+ public function get_col_length( $table, $column ) {
2798
+ $tablekey = strtolower( $table );
2799
+ $columnkey = strtolower( $column );
2800
+
2801
+ // Skip this entirely if this isn't a MySQL database.
2802
+ if ( empty( $this->is_mysql ) ) {
2803
+ return false;
2804
+ }
2805
+
2806
+ if ( empty( $this->col_meta[ $tablekey ] ) ) {
2807
+ // This primes column information for us.
2808
+ $table_charset = $this->get_table_charset( $table );
2809
+ if ( is_wp_error( $table_charset ) ) {
2810
+ return $table_charset;
2811
+ }
2812
+ }
2813
+
2814
+ if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
2815
+ return false;
2816
+ }
2817
+
2818
+ $typeinfo = explode( '(', $this->col_meta[ $tablekey ][ $columnkey ]->Type );
2819
+
2820
+ $type = strtolower( $typeinfo[0] );
2821
+ if ( ! empty( $typeinfo[1] ) ) {
2822
+ $length = trim( $typeinfo[1], ')' );
2823
+ } else {
2824
+ $length = false;
2825
+ }
2826
+
2827
+ switch ( $type ) {
2828
+ case 'char':
2829
+ case 'varchar':
2830
+ return array(
2831
+ 'type' => 'char',
2832
+ 'length' => (int) $length,
2833
+ );
2834
+
2835
+ case 'binary':
2836
+ case 'varbinary':
2837
+ return array(
2838
+ 'type' => 'byte',
2839
+ 'length' => (int) $length,
2840
+ );
2841
+
2842
+ case 'tinyblob':
2843
+ case 'tinytext':
2844
+ return array(
2845
+ 'type' => 'byte',
2846
+ 'length' => 255, // 2^8 - 1
2847
+ );
2848
+
2849
+ case 'blob':
2850
+ case 'text':
2851
+ return array(
2852
+ 'type' => 'byte',
2853
+ 'length' => 65535, // 2^16 - 1
2854
+ );
2855
+
2856
+ case 'mediumblob':
2857
+ case 'mediumtext':
2858
+ return array(
2859
+ 'type' => 'byte',
2860
+ 'length' => 16777215, // 2^24 - 1
2861
+ );
2862
+
2863
+ case 'longblob':
2864
+ case 'longtext':
2865
+ return array(
2866
+ 'type' => 'byte',
2867
+ 'length' => 4294967295, // 2^32 - 1
2868
+ );
2869
+
2870
+ default:
2871
+ return false;
2872
+ }
2873
+ }
2874
+
2875
+ /**
2876
+ * Check if a string is ASCII.
2877
+ *
2878
+ * The negative regex is faster for non-ASCII strings, as it allows
2879
+ * the search to finish as soon as it encounters a non-ASCII character.
2880
+ *
2881
+ * @since 4.2.0
2882
+ *
2883
+ * @param string $string String to check.
2884
+ * @return bool True if ASCII, false if not.
2885
+ */
2886
+ protected function check_ascii( $string ) {
2887
+ if ( function_exists( 'mb_check_encoding' ) ) {
2888
+ if ( mb_check_encoding( $string, 'ASCII' ) ) {
2889
+ return true;
2890
+ }
2891
+ } elseif ( ! preg_match( '/[^\x00-\x7F]/', $string ) ) {
2892
+ return true;
2893
+ }
2894
+
2895
+ return false;
2896
+ }
2897
+
2898
+ /**
2899
+ * Check if the query is accessing a collation considered safe on the current version of MySQL.
2900
+ *
2901
+ * @since 4.2.0
2902
+ *
2903
+ * @param string $query The query to check.
2904
+ * @return bool True if the collation is safe, false if it isn't.
2905
+ */
2906
+ protected function check_safe_collation( $query ) {
2907
+ if ( $this->checking_collation ) {
2908
+ return true;
2909
+ }
2910
+
2911
+ // We don't need to check the collation for queries that don't read data.
2912
+ $query = ltrim( $query, "\r\n\t (" );
2913
+ if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $query ) ) {
2914
+ return true;
2915
+ }
2916
+
2917
+ // All-ASCII queries don't need extra checking.
2918
+ if ( $this->check_ascii( $query ) ) {
2919
+ return true;
2920
+ }
2921
+
2922
+ $table = $this->get_table_from_query( $query );
2923
+ if ( ! $table ) {
2924
+ return false;
2925
+ }
2926
+
2927
+ $this->checking_collation = true;
2928
+ $collation = $this->get_table_charset( $table );
2929
+ $this->checking_collation = false;
2930
+
2931
+ // Tables with no collation, or latin1 only, don't need extra checking.
2932
+ if ( false === $collation || 'latin1' === $collation ) {
2933
+ return true;
2934
+ }
2935
+
2936
+ $table = strtolower( $table );
2937
+ if ( empty( $this->col_meta[ $table ] ) ) {
2938
+ return false;
2939
+ }
2940
+
2941
+ // If any of the columns don't have one of these collations, it needs more sanity checking.
2942
+ foreach ( $this->col_meta[ $table ] as $col ) {
2943
+ if ( empty( $col->Collation ) ) {
2944
+ continue;
2945
+ }
2946
+
2947
+ if ( ! in_array( $col->Collation, array( 'utf8_general_ci', 'utf8_bin', 'utf8mb4_general_ci', 'utf8mb4_bin' ), true ) ) {
2948
+ return false;
2949
+ }
2950
+ }
2951
+
2952
+ return true;
2953
+ }
2954
+
2955
+ /**
2956
+ * Strips any invalid characters based on value/charset pairs.
2957
+ *
2958
+ * @since 4.2.0
2959
+ *
2960
+ * @param array $data Array of value arrays. Each value array has the keys
2961
+ * 'value' and 'charset'. An optional 'ascii' key can be
2962
+ * set to false to avoid redundant ASCII checks.
2963
+ * @return array|WP_Error The $data parameter, with invalid characters removed from
2964
+ * each value. This works as a passthrough: any additional keys
2965
+ * such as 'field' are retained in each value array. If we cannot
2966
+ * remove invalid characters, a WP_Error object is returned.
2967
+ */
2968
+ protected function strip_invalid_text( $data ) {
2969
+ $db_check_string = false;
2970
+
2971
+ foreach ( $data as &$value ) {
2972
+ $charset = $value['charset'];
2973
+
2974
+ if ( is_array( $value['length'] ) ) {
2975
+ $length = $value['length']['length'];
2976
+ $truncate_by_byte_length = 'byte' === $value['length']['type'];
2977
+ } else {
2978
+ $length = false;
2979
+ // Since we have no length, we'll never truncate.
2980
+ // Initialize the variable to false. true would take us
2981
+ // through an unnecessary (for this case) codepath below.
2982
+ $truncate_by_byte_length = false;
2983
+ }
2984
+
2985
+ // There's no charset to work with.
2986
+ if ( false === $charset ) {
2987
+ continue;
2988
+ }
2989
+
2990
+ // Column isn't a string.
2991
+ if ( ! is_string( $value['value'] ) ) {
2992
+ continue;
2993
+ }
2994
+
2995
+ $needs_validation = true;
2996
+ if (
2997
+ // latin1 can store any byte sequence
2998
+ 'latin1' === $charset
2999
+ ||
3000
+ // ASCII is always OK.
3001
+ ( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) )
3002
+ ) {
3003
+ $truncate_by_byte_length = true;
3004
+ $needs_validation = false;
3005
+ }
3006
+
3007
+ if ( $truncate_by_byte_length ) {
3008
+ mbstring_binary_safe_encoding();
3009
+ if ( false !== $length && strlen( $value['value'] ) > $length ) {
3010
+ $value['value'] = substr( $value['value'], 0, $length );
3011
+ }
3012
+ reset_mbstring_encoding();
3013
+
3014
+ if ( ! $needs_validation ) {
3015
+ continue;
3016
+ }
3017
+ }
3018
+
3019
+ // utf8 can be handled by regex, which is a bunch faster than a DB lookup.
3020
+ if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) {
3021
+ $regex = '/
3022
+ (
3023
+ (?: [\x00-\x7F] # single-byte sequences 0xxxxxxx
3024
+ | [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx
3025
+ | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2
3026
+ | [\xE1-\xEC][\x80-\xBF]{2}
3027
+ | \xED[\x80-\x9F][\x80-\xBF]
3028
+ | [\xEE-\xEF][\x80-\xBF]{2}';
3029
+
3030
+ if ( 'utf8mb4' === $charset ) {
3031
+ $regex .= '
3032
+ | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3
3033
+ | [\xF1-\xF3][\x80-\xBF]{3}
3034
+ | \xF4[\x80-\x8F][\x80-\xBF]{2}
3035
+ ';
3036
+ }
3037
+
3038
+ $regex .= '){1,40} # ...one or more times
3039
+ )
3040
+ | . # anything else
3041
+ /x';
3042
+ $value['value'] = preg_replace( $regex, '$1', $value['value'] );
3043
+
3044
+ if ( false !== $length && mb_strlen( $value['value'], 'UTF-8' ) > $length ) {
3045
+ $value['value'] = mb_substr( $value['value'], 0, $length, 'UTF-8' );
3046
+ }
3047
+ continue;
3048
+ }
3049
+
3050
+ // We couldn't use any local conversions, send it to the DB.
3051
+ $value['db'] = $db_check_string = true;
3052
+ }
3053
+ unset( $value ); // Remove by reference.
3054
+
3055
+ if ( $db_check_string ) {
3056
+ $queries = array();
3057
+ foreach ( $data as $col => $value ) {
3058
+ if ( ! empty( $value['db'] ) ) {
3059
+ // We're going to need to truncate by characters or bytes, depending on the length value we have.
3060
+ if ( 'byte' === $value['length']['type'] ) {
3061
+ // Using binary causes LEFT() to truncate by bytes.
3062
+ $charset = 'binary';
3063
+ } else {
3064
+ $charset = $value['charset'];
3065
+ }
3066
+
3067
+ if ( $this->charset ) {
3068
+ $connection_charset = $this->charset;
3069
+ } else {
3070
+ if ( $this->use_mysqli ) {
3071
+ $connection_charset = mysqli_character_set_name( $this->dbh );
3072
+ } else {
3073
+ $connection_charset = mysql_client_encoding();
3074
+ }
3075
+ }
3076
+
3077
+ if ( is_array( $value['length'] ) ) {
3078
+ $length = sprintf( '%.0f', $value['length']['length'] );
3079
+ $queries[ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING $charset ), $length ) USING $connection_charset )", $value['value'] );
3080
+ } elseif ( 'binary' !== $charset ) {
3081
+ // If we don't have a length, there's no need to convert binary - it will always return the same result.
3082
+ $queries[ $col ] = $this->prepare( "CONVERT( CONVERT( %s USING $charset ) USING $connection_charset )", $value['value'] );
3083
+ }
3084
+
3085
+ unset( $data[ $col ]['db'] );
3086
+ }
3087
+ }
3088
+
3089
+ $sql = array();
3090
+ foreach ( $queries as $column => $query ) {
3091
+ if ( ! $query ) {
3092
+ continue;
3093
+ }
3094
+
3095
+ $sql[] = $query . " AS x_$column";
3096
+ }
3097
+
3098
+ $this->check_current_query = false;
3099
+ $row = $this->get_row( 'SELECT ' . implode( ', ', $sql ), ARRAY_A );
3100
+ if ( ! $row ) {
3101
+ return new WP_Error( 'wpdb_strip_invalid_text_failure' );
3102
+ }
3103
+
3104
+ foreach ( array_keys( $data ) as $column ) {
3105
+ if ( isset( $row[ "x_$column" ] ) ) {
3106
+ $data[ $column ]['value'] = $row[ "x_$column" ];
3107
+ }
3108
+ }
3109
+ }
3110
+
3111
+ return $data;
3112
+ }
3113
+
3114
+ /**
3115
+ * Strips any invalid characters from the query.
3116
+ *
3117
+ * @since 4.2.0
3118
+ *
3119
+ * @param string $query Query to convert.
3120
+ * @return string|WP_Error The converted query, or a WP_Error object if the conversion fails.
3121
+ */
3122
+ protected function strip_invalid_text_from_query( $query ) {
3123
+ // We don't need to check the collation for queries that don't read data.
3124
+ $trimmed_query = ltrim( $query, "\r\n\t (" );
3125
+ if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $trimmed_query ) ) {
3126
+ return $query;
3127
+ }
3128
+
3129
+ $table = $this->get_table_from_query( $query );
3130
+ if ( $table ) {
3131
+ $charset = $this->get_table_charset( $table );
3132
+ if ( is_wp_error( $charset ) ) {
3133
+ return $charset;
3134
+ }
3135
+
3136
+ // We can't reliably strip text from tables containing binary/blob columns
3137
+ if ( 'binary' === $charset ) {
3138
+ return $query;
3139
+ }
3140
+ } else {
3141
+ $charset = $this->charset;
3142
+ }
3143
+
3144
+ $data = array(
3145
+ 'value' => $query,
3146
+ 'charset' => $charset,
3147
+ 'ascii' => false,
3148
+ 'length' => false,
3149
+ );
3150
+
3151
+ $data = $this->strip_invalid_text( array( $data ) );
3152
+ if ( is_wp_error( $data ) ) {
3153
+ return $data;
3154
+ }
3155
+
3156
+ return $data[0]['value'];
3157
+ }
3158
+
3159
+ /**
3160
+ * Strips any invalid characters from the string for a given table and column.
3161
+ *
3162
+ * @since 4.2.0
3163
+ *
3164
+ * @param string $table Table name.
3165
+ * @param string $column Column name.
3166
+ * @param string $value The text to check.
3167
+ * @return string|WP_Error The converted string, or a WP_Error object if the conversion fails.
3168
+ */
3169
+ public function strip_invalid_text_for_column( $table, $column, $value ) {
3170
+ if ( ! is_string( $value ) ) {
3171
+ return $value;
3172
+ }
3173
+
3174
+ $charset = $this->get_col_charset( $table, $column );
3175
+ if ( ! $charset ) {
3176
+ // Not a string column.
3177
+ return $value;
3178
+ } elseif ( is_wp_error( $charset ) ) {
3179
+ // Bail on real errors.
3180
+ return $charset;
3181
+ }
3182
+
3183
+ $data = array(
3184
+ $column => array(
3185
+ 'value' => $value,
3186
+ 'charset' => $charset,
3187
+ 'length' => $this->get_col_length( $table, $column ),
3188
+ ),
3189
+ );
3190
+
3191
+ $data = $this->strip_invalid_text( $data );
3192
+ if ( is_wp_error( $data ) ) {
3193
+ return $data;
3194
+ }
3195
+
3196
+ return $data[ $column ]['value'];
3197
+ }
3198
+
3199
+ /**
3200
+ * Find the first table name referenced in a query.
3201
+ *
3202
+ * @since 4.2.0
3203
+ *
3204
+ * @param string $query The query to search.
3205
+ * @return string|false $table The table name found, or false if a table couldn't be found.
3206
+ */
3207
+ protected function get_table_from_query( $query ) {
3208
+ // Remove characters that can legally trail the table name.
3209
+ $query = rtrim( $query, ';/-#' );
3210
+
3211
+ // Allow (select...) union [...] style queries. Use the first query's table name.
3212
+ $query = ltrim( $query, "\r\n\t (" );
3213
+
3214
+ // Strip everything between parentheses except nested selects.
3215
+ $query = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', $query );
3216
+
3217
+ // Quickly match most common queries.
3218
+ if ( preg_match(
3219
+ '/^\s*(?:'
3220
+ . 'SELECT.*?\s+FROM'
3221
+ . '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?'
3222
+ . '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?'
3223
+ . '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?'
3224
+ . '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:.+?FROM)?'
3225
+ . ')\s+((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)/is',
3226
+ $query,
3227
+ $maybe
3228
+ ) ) {
3229
+ return str_replace( '`', '', $maybe[1] );
3230
+ }
3231
+
3232
+ // SHOW TABLE STATUS and SHOW TABLES WHERE Name = 'wp_posts'
3233
+ if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES).+WHERE\s+Name\s*=\s*("|\')((?:[0-9a-zA-Z$_.-]|[\xC2-\xDF][\x80-\xBF])+)\\1/is', $query, $maybe ) ) {
3234
+ return $maybe[2];
3235
+ }
3236
+
3237
+ // SHOW TABLE STATUS LIKE and SHOW TABLES LIKE 'wp\_123\_%'
3238
+ // This quoted LIKE operand seldom holds a full table name.
3239
+ // It is usually a pattern for matching a prefix so we just
3240
+ // strip the trailing % and unescape the _ to get 'wp_123_'
3241
+ // which drop-ins can use for routing these SQL statements.
3242
+ if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES)\s+(?:WHERE\s+Name\s+)?LIKE\s*("|\')((?:[\\\\0-9a-zA-Z$_.-]|[\xC2-\xDF][\x80-\xBF])+)%?\\1/is', $query, $maybe ) ) {
3243
+ return str_replace( '\\_', '_', $maybe[2] );
3244
+ }
3245
+
3246
+ // Big pattern for the rest of the table-related queries.
3247
+ if ( preg_match(
3248
+ '/^\s*(?:'
3249
+ . '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM'
3250
+ . '|DESCRIBE|DESC|EXPLAIN|HANDLER'
3251
+ . '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?'
3252
+ . '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|REPAIR).*\s+TABLE'
3253
+ . '|TRUNCATE(?:\s+TABLE)?'
3254
+ . '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?'
3255
+ . '|ALTER(?:\s+IGNORE)?\s+TABLE'
3256
+ . '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?'
3257
+ . '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON'
3258
+ . '|DROP\s+INDEX.*\s+ON'
3259
+ . '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE'
3260
+ . '|(?:GRANT|REVOKE).*ON\s+TABLE'
3261
+ . '|SHOW\s+(?:.*FROM|.*TABLE)'
3262
+ . ')\s+\(*\s*((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)\s*\)*/is',
3263
+ $query,
3264
+ $maybe
3265
+ ) ) {
3266
+ return str_replace( '`', '', $maybe[1] );
3267
+ }
3268
+
3269
+ return false;
3270
+ }
3271
+
3272
+ /**
3273
+ * Load the column metadata from the last query.
3274
+ *
3275
+ * @since 3.5.0
3276
+ */
3277
+ protected function load_col_info() {
3278
+ if ( $this->col_info ) {
3279
+ return;
3280
+ }
3281
+
3282
+ if ( $this->use_mysqli ) {
3283
+ $num_fields = mysqli_num_fields( $this->result );
3284
+ for ( $i = 0; $i < $num_fields; $i++ ) {
3285
+ $this->col_info[ $i ] = mysqli_fetch_field( $this->result );
3286
+ }
3287
+ } else {
3288
+ $num_fields = mysql_num_fields( $this->result );
3289
+ for ( $i = 0; $i < $num_fields; $i++ ) {
3290
+ $this->col_info[ $i ] = mysql_fetch_field( $this->result, $i );
3291
+ }
3292
+ }
3293
+ }
3294
+
3295
+ /**
3296
+ * Retrieve column metadata from the last query.
3297
+ *
3298
+ * @since 0.71
3299
+ *
3300
+ * @param string $info_type Optional. Type one of name, table, def, max_length, not_null, primary_key, multiple_key, unique_key, numeric, blob, type, unsigned, zerofill
3301
+ * @param int $col_offset Optional. 0: col name. 1: which table the col's in. 2: col's max length. 3: if the col is numeric. 4: col's type
3302
+ * @return mixed Column Results
3303
+ */
3304
+ public function get_col_info( $info_type = 'name', $col_offset = -1 ) {
3305
+ $this->load_col_info();
3306
+
3307
+ if ( $this->col_info ) {
3308
+ if ( $col_offset == -1 ) {
3309
+ $i = 0;
3310
+ $new_array = array();
3311
+ foreach ( (array) $this->col_info as $col ) {
3312
+ $new_array[ $i ] = $col->{$info_type};
3313
+ $i++;
3314
+ }
3315
+ return $new_array;
3316
+ } else {
3317
+ return $this->col_info[ $col_offset ]->{$info_type};
3318
+ }
3319
+ }
3320
+ }
3321
+
3322
+ /**
3323
+ * Starts the timer, for debugging purposes.
3324
+ *
3325
+ * @since 1.5.0
3326
+ *
3327
+ * @return true
3328
+ */
3329
+ public function timer_start() {
3330
+ $this->time_start = microtime( true );
3331
+ return true;
3332
+ }
3333
+
3334
+ /**
3335
+ * Stops the debugging timer.
3336
+ *
3337
+ * @since 1.5.0
3338
+ *
3339
+ * @return float Total time spent on the query, in seconds
3340
+ */
3341
+ public function timer_stop() {
3342
+ return ( microtime( true ) - $this->time_start );
3343
+ }
3344
+
3345
+ /**
3346
+ * Wraps errors in a nice header and footer and dies.
3347
+ *
3348
+ * Will not die if wpdb::$show_errors is false.
3349
+ *
3350
+ * @since 1.5.0
3351
+ *
3352
+ * @param string $message The Error message
3353
+ * @param string $error_code Optional. A Computer readable string to identify the error.
3354
+ * @return false|void
3355
+ */
3356
+ public function bail( $message, $error_code = '500' ) {
3357
+ if ( $this->show_errors ) {
3358
+ $error = '';
3359
+
3360
+ if ( $this->use_mysqli ) {
3361
+ if ( $this->dbh instanceof mysqli ) {
3362
+ $error = mysqli_error( $this->dbh );
3363
+ } elseif ( mysqli_connect_errno() ) {
3364
+ $error = mysqli_connect_error();
3365
+ }
3366
+ } else {
3367
+ if ( is_resource( $this->dbh ) ) {
3368
+ $error = mysql_error( $this->dbh );
3369
+ } else {
3370
+ $error = mysql_error();
3371
+ }
3372
+ }
3373
+
3374
+ if ( $error ) {
3375
+ $message = '<p><code>' . $error . "</code></p>\n" . $message;
3376
+ }
3377
+
3378
+ wp_die( $message );
3379
+ } else {
3380
+ if ( class_exists( 'WP_Error', false ) ) {
3381
+ $this->error = new WP_Error( $error_code, $message );
3382
+ } else {
3383
+ $this->error = $message;
3384
+ }
3385
+
3386
+ return false;
3387
+ }
3388
+ }
3389
+
3390
+
3391
+ /**
3392
+ * Closes the current database connection.
3393
+ *
3394
+ * @since 4.5.0
3395
+ *
3396
+ * @return bool True if the connection was successfully closed, false if it wasn't,
3397
+ * or the connection doesn't exist.
3398
+ */
3399
+ public function close() {
3400
+ if ( ! $this->dbh ) {
3401
+ return false;
3402
+ }
3403
+
3404
+ if ( $this->use_mysqli ) {
3405
+ $closed = mysqli_close( $this->dbh );
3406
+ } else {
3407
+ $closed = mysql_close( $this->dbh );
3408
+ }
3409
+
3410
+ if ( $closed ) {
3411
+ $this->dbh = null;
3412
+ $this->ready = false;
3413
+ $this->has_connected = false;
3414
+ }
3415
+
3416
+ return $closed;
3417
+ }
3418
+
3419
+ /**
3420
+ * Whether MySQL database is at least the required minimum version.
3421
+ *
3422
+ * @since 2.5.0
3423
+ *
3424
+ * @global string $wp_version
3425
+ * @global string $required_mysql_version
3426
+ *
3427
+ * @return WP_Error|void
3428
+ */
3429
+ public function check_database_version() {
3430
+ global $wp_version, $required_mysql_version;
3431
+ // Make sure the server has the required MySQL version
3432
+ if ( version_compare( $this->db_version(), $required_mysql_version, '<' ) ) {
3433
+ /* translators: 1: WordPress version number, 2: Minimum required MySQL version number */
3434
+ return new WP_Error( 'database_version', sprintf( __( '<strong>ERROR</strong>: WordPress %1$s requires MySQL %2$s or higher' ), $wp_version, $required_mysql_version ) );
3435
+ }
3436
+ }
3437
+
3438
+ /**
3439
+ * Whether the database supports collation.
3440
+ *
3441
+ * Called when WordPress is generating the table scheme.
3442
+ *
3443
+ * Use `wpdb::has_cap( 'collation' )`.
3444
+ *
3445
+ * @since 2.5.0
3446
+ * @deprecated 3.5.0 Use wpdb::has_cap()
3447
+ *
3448
+ * @return bool True if collation is supported, false if version does not
3449
+ */
3450
+ public function supports_collation() {
3451
+ _deprecated_function( __FUNCTION__, '3.5.0', 'wpdb::has_cap( \'collation\' )' );
3452
+ return $this->has_cap( 'collation' );
3453
+ }
3454
+
3455
+ /**
3456
+ * The database character collate.
3457
+ *
3458
+ * @since 3.5.0
3459
+ *
3460
+ * @return string The database character collate.
3461
+ */
3462
+ public function get_charset_collate() {
3463
+ $charset_collate = '';
3464
+
3465
+ if ( ! empty( $this->charset ) ) {
3466
+ $charset_collate = "DEFAULT CHARACTER SET $this->charset";
3467
+ }
3468
+ if ( ! empty( $this->collate ) ) {
3469
+ $charset_collate .= " COLLATE $this->collate";
3470
+ }
3471
+
3472
+ return $charset_collate;
3473
+ }
3474
+
3475
+ /**
3476
+ * Determine if a database supports a particular feature.
3477
+ *
3478
+ * @since 2.7.0
3479
+ * @since 4.1.0 Added support for the 'utf8mb4' feature.
3480
+ * @since 4.6.0 Added support for the 'utf8mb4_520' feature.
3481
+ *
3482
+ * @see wpdb::db_version()
3483
+ *
3484
+ * @param string $db_cap The feature to check for. Accepts 'collation',
3485
+ * 'group_concat', 'subqueries', 'set_charset',
3486
+ * 'utf8mb4', or 'utf8mb4_520'.
3487
+ * @return int|false Whether the database feature is supported, false otherwise.
3488
+ */
3489
+ public function has_cap( $db_cap ) {
3490
+ $version = $this->db_version();
3491
+
3492
+ switch ( strtolower( $db_cap ) ) {
3493
+ case 'collation': // @since 2.5.0
3494
+ case 'group_concat': // @since 2.7.0
3495
+ case 'subqueries': // @since 2.7.0
3496
+ return version_compare( $version, '4.1', '>=' );
3497
+ case 'set_charset':
3498
+ return version_compare( $version, '5.0.7', '>=' );
3499
+ case 'utf8mb4': // @since 4.1.0
3500
+ if ( version_compare( $version, '5.5.3', '<' ) ) {
3501
+ return false;
3502
+ }
3503
+ if ( $this->use_mysqli ) {
3504
+ $client_version = mysqli_get_client_info();
3505
+ } else {
3506
+ $client_version = mysql_get_client_info();
3507
+ }
3508
+
3509
+ /*
3510
+ * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server.
3511
+ * mysqlnd has supported utf8mb4 since 5.0.9.
3512
+ */
3513
+ if ( false !== strpos( $client_version, 'mysqlnd' ) ) {
3514
+ $client_version = preg_replace( '/^\D+([\d.]+).*/', '$1', $client_version );
3515
+ return version_compare( $client_version, '5.0.9', '>=' );
3516
+ } else {
3517
+ return version_compare( $client_version, '5.5.3', '>=' );
3518
+ }
3519
+ case 'utf8mb4_520': // @since 4.6.0
3520
+ return version_compare( $version, '5.6', '>=' );
3521
+ }
3522
+
3523
+ return false;
3524
+ }
3525
+
3526
+ /**
3527
+ * Retrieve the name of the function that called wpdb.
3528
+ *
3529
+ * Searches up the list of functions until it reaches
3530
+ * the one that would most logically had called this method.
3531
+ *
3532
+ * @since 2.5.0
3533
+ *
3534
+ * @return string Comma separated list of the calling functions.
3535
+ */
3536
+ public function get_caller() {
3537
+ return wp_debug_backtrace_summary( __CLASS__ );
3538
+ }
3539
+
3540
+ /**
3541
+ * Retrieves the MySQL server version.
3542
+ *
3543
+ * @since 2.7.0
3544
+ *
3545
+ * @return null|string Null on failure, version number on success.
3546
+ */
3547
+ public function db_version() {
3548
+ if ( $this->use_mysqli ) {
3549
+ $server_info = mysqli_get_server_info( $this->dbh );
3550
+ } else {
3551
+ $server_info = mysql_get_server_info( $this->dbh );
3552
+ }
3553
+ return preg_replace( '/[^0-9.].*/', '', $server_info );
3554
+ }
3555
+ }
public/class-xcloner-public.php CHANGED
@@ -3,7 +3,7 @@
3
  /**
4
  * The public-facing functionality of the plugin.
5
  *
6
- * @link http://www.thinkovi.com
7
  * @since 1.0.0
8
  *
9
  * @package Xcloner
3
  /**
4
  * The public-facing functionality of the plugin.
5
  *
6
+ * @link https://watchful.net
7
  * @since 1.0.0
8
  *
9
  * @package Xcloner
public/partials/xcloner-public-display.php CHANGED
@@ -5,7 +5,7 @@
5
  *
6
  * This file is used to markup the public-facing aspects of the plugin.
7
  *
8
- * @link http://www.thinkovi.com
9
  * @since 1.0.0
10
  *
11
  * @package Xcloner
5
  *
6
  * This file is used to markup the public-facing aspects of the plugin.
7
  *
8
+ * @link https://watchful.net
9
  * @since 1.0.0
10
  *
11
  * @package Xcloner
uninstall.php CHANGED
@@ -19,7 +19,7 @@
19
  * For more information, see the following discussion:
20
  * https://github.com/tommcfarlin/WordPress-Plugin-Boilerplate/pull/123#issuecomment-28541913
21
  *
22
- * @link http://www.thinkovi.com
23
  * @since 1.0.0
24
  *
25
  * @package Xcloner
19
  * For more information, see the following discussion:
20
  * https://github.com/tommcfarlin/WordPress-Plugin-Boilerplate/pull/123#issuecomment-28541913
21
  *
22
+ * @link https://watchful.net
23
  * @since 1.0.0
24
  *
25
  * @package Xcloner
vendor/composer/autoload_namespaces.php CHANGED
@@ -6,5 +6,5 @@ $vendorDir = dirname(dirname(__FILE__));
6
  $baseDir = dirname($vendorDir);
7
 
8
  return array(
9
- 'org\\bovigo\\vfs\\' => array($vendorDir . '/mikey179/vfsStream/src/main/php'),
10
  );
6
  $baseDir = dirname($vendorDir);
7
 
8
  return array(
9
+ 'org\\bovigo\\vfs\\' => array($vendorDir . '/mikey179/vfsstream/src/main/php'),
10
  );
vendor/composer/autoload_static.php CHANGED
@@ -209,7 +209,7 @@ class ComposerStaticInit571f9d19802717f7be61d57b40d60b28
209
  array (
210
  'org\\bovigo\\vfs\\' =>
211
  array (
212
- 0 => __DIR__ . '/..' . '/mikey179/vfsStream/src/main/php',
213
  ),
214
  ),
215
  );
209
  array (
210
  'org\\bovigo\\vfs\\' =>
211
  array (
212
+ 0 => __DIR__ . '/..' . '/mikey179/vfsstream/src/main/php',
213
  ),
214
  ),
215
  );
vendor/composer/installed.json CHANGED
@@ -797,27 +797,27 @@
797
  ]
798
  },
799
  {
800
- "name": "mikey179/vfsStream",
801
- "version": "v1.6.5",
802
- "version_normalized": "1.6.5.0",
803
  "source": {
804
  "type": "git",
805
  "url": "https://github.com/bovigo/vfsStream.git",
806
- "reference": "d5fec95f541d4d71c4823bb5e30cf9b9e5b96145"
807
  },
808
  "dist": {
809
  "type": "zip",
810
- "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/d5fec95f541d4d71c4823bb5e30cf9b9e5b96145",
811
- "reference": "d5fec95f541d4d71c4823bb5e30cf9b9e5b96145",
812
  "shasum": ""
813
  },
814
  "require": {
815
  "php": ">=5.3.0"
816
  },
817
  "require-dev": {
818
- "phpunit/phpunit": "~4.5"
819
  },
820
- "time": "2017-08-01T08:02:14+00:00",
821
  "type": "library",
822
  "extra": {
823
  "branch-alias": {
@@ -1638,17 +1638,17 @@
1638
  },
1639
  {
1640
  "name": "splitbrain/php-archive",
1641
- "version": "1.0.10",
1642
- "version_normalized": "1.0.10.0",
1643
  "source": {
1644
  "type": "git",
1645
  "url": "https://github.com/splitbrain/php-archive.git",
1646
- "reference": "a46f3aaeb9f123fdb7db4e192b0600feebf7f773"
1647
  },
1648
  "dist": {
1649
  "type": "zip",
1650
- "url": "https://api.github.com/repos/splitbrain/php-archive/zipball/a46f3aaeb9f123fdb7db4e192b0600feebf7f773",
1651
- "reference": "a46f3aaeb9f123fdb7db4e192b0600feebf7f773",
1652
  "shasum": ""
1653
  },
1654
  "require": {
@@ -1664,7 +1664,7 @@
1664
  "ext-iconv": "Used for proper filename encode handling",
1665
  "ext-mbstring": "Can be used alternatively for handling filename encoding"
1666
  },
1667
- "time": "2018-05-01T08:03:56+00:00",
1668
  "type": "library",
1669
  "installation-source": "source",
1670
  "autoload": {
797
  ]
798
  },
799
  {
800
+ "name": "mikey179/vfsstream",
801
+ "version": "v1.6.8",
802
+ "version_normalized": "1.6.8.0",
803
  "source": {
804
  "type": "git",
805
  "url": "https://github.com/bovigo/vfsStream.git",
806
+ "reference": "231c73783ebb7dd9ec77916c10037eff5a2b6efe"
807
  },
808
  "dist": {
809
  "type": "zip",
810
+ "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/231c73783ebb7dd9ec77916c10037eff5a2b6efe",
811
+ "reference": "231c73783ebb7dd9ec77916c10037eff5a2b6efe",
812
  "shasum": ""
813
  },
814
  "require": {
815
  "php": ">=5.3.0"
816
  },
817
  "require-dev": {
818
+ "phpunit/phpunit": "^4.5|^5.0"
819
  },
820
+ "time": "2019-10-30T15:31:00+00:00",
821
  "type": "library",
822
  "extra": {
823
  "branch-alias": {
1638
  },
1639
  {
1640
  "name": "splitbrain/php-archive",
1641
+ "version": "1.1.1",
1642
+ "version_normalized": "1.1.1.0",
1643
  "source": {
1644
  "type": "git",
1645
  "url": "https://github.com/splitbrain/php-archive.git",
1646
+ "reference": "10d89013572ba1f4d4ad7fcb74860242f4c3860b"
1647
  },
1648
  "dist": {
1649
  "type": "zip",
1650
+ "url": "https://api.github.com/repos/splitbrain/php-archive/zipball/10d89013572ba1f4d4ad7fcb74860242f4c3860b",
1651
+ "reference": "10d89013572ba1f4d4ad7fcb74860242f4c3860b",
1652
  "shasum": ""
1653
  },
1654
  "require": {
1664
  "ext-iconv": "Used for proper filename encode handling",
1665
  "ext-mbstring": "Can be used alternatively for handling filename encoding"
1666
  },
1667
+ "time": "2018-09-09T12:13:53+00:00",
1668
  "type": "library",
1669
  "installation-source": "source",
1670
  "autoload": {
vendor/splitbrain/php-archive/.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ *.iml
2
+ .idea/
3
+ composer.phar
4
+ vendor/
5
+ composer.lock
6
+ apigen.phar
7
+ docs/
8
+ nbproject/
vendor/splitbrain/php-archive/src/Archive.php CHANGED
@@ -10,12 +10,15 @@ abstract class Archive
10
  const COMPRESS_GZIP = 1;
11
  const COMPRESS_BZIP = 2;
12
 
 
 
 
13
  /**
14
  * Set the compression level and type
15
  *
16
  * @param int $level Compression level (0 to 9)
17
  * @param int $type Type of compression to use (use COMPRESS_* constants)
18
- * @return mixed
19
  */
20
  abstract public function setCompression($level = 9, $type = Archive::COMPRESS_AUTO);
21
 
@@ -117,16 +120,16 @@ abstract class Archive
117
  */
118
  abstract public function save($file);
119
 
120
- }
121
-
122
- class ArchiveIOException extends \Exception
123
- {
124
- }
125
-
126
- class ArchiveIllegalCompressionException extends \Exception
127
- {
128
- }
129
-
130
- class ArchiveCorruptedException extends \Exception
131
- {
132
  }
10
  const COMPRESS_GZIP = 1;
11
  const COMPRESS_BZIP = 2;
12
 
13
+ /** @var callable */
14
+ protected $callback;
15
+
16
  /**
17
  * Set the compression level and type
18
  *
19
  * @param int $level Compression level (0 to 9)
20
  * @param int $type Type of compression to use (use COMPRESS_* constants)
21
+ * @throws ArchiveIllegalCompressionException
22
  */
23
  abstract public function setCompression($level = 9, $type = Archive::COMPRESS_AUTO);
24
 
120
  */
121
  abstract public function save($file);
122
 
123
+ /**
124
+ * Set a callback function to be called whenever a file is added or extracted.
125
+ *
126
+ * The callback is called with a FileInfo object as parameter. You can use this to show progress
127
+ * info during an operation.
128
+ *
129
+ * @param callable $callback
130
+ */
131
+ public function setCallback($callback)
132
+ {
133
+ $this->callback = $callback;
134
+ }
135
  }
vendor/splitbrain/php-archive/src/ArchiveCorruptedException.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace splitbrain\PHPArchive;
4
+
5
+ /**
6
+ * The archive is unreadable
7
+ */
8
+ class ArchiveCorruptedException extends \Exception
9
+ {
10
+ }
vendor/splitbrain/php-archive/src/ArchiveIOException.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace splitbrain\PHPArchive;
4
+
5
+ /**
6
+ * Read/Write Errors
7
+ */
8
+ class ArchiveIOException extends \Exception
9
+ {
10
+ }
vendor/splitbrain/php-archive/src/ArchiveIllegalCompressionException.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace splitbrain\PHPArchive;
4
+
5
+ /**
6
+ * Bad or unsupported compression settings requested
7
+ */
8
+ class ArchiveIllegalCompressionException extends \Exception
9
+ {
10
+ }
vendor/splitbrain/php-archive/src/FileInfo.php CHANGED
@@ -55,18 +55,13 @@ class FileInfo
55
 
56
  $stat = stat($path);
57
  $file = new FileInfo();
58
-
59
- if(is_dir($path))
60
- $size = 0;
61
- else
62
- $size = filesize($path);
63
 
64
  $file->setPath($path);
65
  $file->setIsdir(is_dir($path));
66
  $file->setMode(fileperms($path));
67
  $file->setOwner(fileowner($path));
68
  $file->setGroup(filegroup($path));
69
- $file->setSize($size);
70
  $file->setUid($stat['uid']);
71
  $file->setGid($stat['gid']);
72
  $file->setMtime($stat['mtime']);
@@ -79,13 +74,11 @@ class FileInfo
79
  }
80
 
81
  /**
82
- * @return int
83
  */
84
  public function getSize()
85
  {
86
- if($this->isdir)
87
- return 0;
88
-
89
  return $this->size;
90
  }
91
 
@@ -295,7 +288,6 @@ class FileInfo
295
  * the prefix will be stripped. It is recommended to give prefixes with a trailing slash.
296
  *
297
  * @param int|string $strip
298
- * @return FileInfo
299
  */
300
  public function strip($strip)
301
  {
@@ -334,22 +326,15 @@ class FileInfo
334
  */
335
  public function match($include = '', $exclude = '')
336
  {
337
- //echo $include;
338
-
339
- //echo $this->getPath()."--".preg_match($include, $this->getPath())."\n";
340
  $extract = true;
341
  if ($include && !preg_match($include, $this->getPath())) {
342
  $extract = false;
343
-
344
  }
345
  if ($exclude && preg_match($exclude, $this->getPath())) {
346
  $extract = false;
347
  }
348
-
349
  return $extract;
350
  }
351
  }
352
 
353
- class FileInfoException extends \Exception
354
- {
355
- }
55
 
56
  $stat = stat($path);
57
  $file = new FileInfo();
 
 
 
 
 
58
 
59
  $file->setPath($path);
60
  $file->setIsdir(is_dir($path));
61
  $file->setMode(fileperms($path));
62
  $file->setOwner(fileowner($path));
63
  $file->setGroup(filegroup($path));
64
+ $file->setSize(filesize($path));
65
  $file->setUid($stat['uid']);
66
  $file->setGid($stat['gid']);
67
  $file->setMtime($stat['mtime']);
74
  }
75
 
76
  /**
77
+ * @return int the filesize. always 0 for directories
78
  */
79
  public function getSize()
80
  {
81
+ if($this->isdir) return 0;
 
 
82
  return $this->size;
83
  }
84
 
288
  * the prefix will be stripped. It is recommended to give prefixes with a trailing slash.
289
  *
290
  * @param int|string $strip
 
291
  */
292
  public function strip($strip)
293
  {
326
  */
327
  public function match($include = '', $exclude = '')
328
  {
 
 
 
329
  $extract = true;
330
  if ($include && !preg_match($include, $this->getPath())) {
331
  $extract = false;
 
332
  }
333
  if ($exclude && preg_match($exclude, $this->getPath())) {
334
  $extract = false;
335
  }
336
+
337
  return $extract;
338
  }
339
  }
340
 
 
 
 
vendor/splitbrain/php-archive/src/FileInfoException.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace splitbrain\PHPArchive;
4
+
5
+ /**
6
+ * File meta data problems
7
+ */
8
+ class FileInfoException extends \Exception
9
+ {
10
+ }
vendor/splitbrain/php-archive/src/Tar.php CHANGED
@@ -28,12 +28,15 @@ class Tar extends Archive
28
  * Sets the compression to use
29
  *
30
  * @param int $level Compression level (0 to 9)
31
- * @param int $type Type of compression to use (use COMPRESS_* constants)
32
- * @return mixed
33
  */
34
  public function setCompression($level = 9, $type = Archive::COMPRESS_AUTO)
35
  {
36
  $this->compressioncheck($type);
 
 
 
37
  $this->comptype = $type;
38
  $this->complevel = $level;
39
  if($level == 0) $this->comptype = Archive::COMPRESS_NONE;
@@ -45,8 +48,9 @@ class Tar extends Archive
45
  *
46
  * @param string $file
47
  * @throws ArchiveIOException
 
48
  */
49
- public function open($file, $start_byte = 0)
50
  {
51
  $this->file = $file;
52
 
@@ -68,9 +72,6 @@ class Tar extends Archive
68
  throw new ArchiveIOException('Could not open file for reading: '.$this->file);
69
  }
70
  $this->closed = false;
71
-
72
- if($start_byte)
73
- fseek($this->fh, $start_byte);
74
  }
75
 
76
  /**
@@ -82,44 +83,28 @@ class Tar extends Archive
82
  * Reopen the file with open() again if you want to do additional operations
83
  *
84
  * @throws ArchiveIOException
 
85
  * @returns FileInfo[]
86
  */
87
- public function contents($files_limit = 0)
88
  {
89
  if ($this->closed || !$this->file) {
90
  throw new ArchiveIOException('Can not read from a closed archive');
91
  }
92
 
93
- $files_counter = 0;
94
  $result = array();
95
-
96
  while ($read = $this->readbytes(512)) {
97
  $header = $this->parseHeader($read);
98
  if (!is_array($header)) {
99
  continue;
100
  }
101
 
102
- if($files_limit)
103
- {
104
- if(++$files_counter > $files_limit)
105
- {
106
- $return['extracted_files'] = $result;
107
- $return['start'] = ftell($this->fh)-512;
108
- return $return;
109
- }
110
- }
111
-
112
- if($header['typeflag'] == 5)
113
- $header['size'] = 0;
114
-
115
  $this->skipbytes(ceil($header['size'] / 512) * 512);
116
  $result[] = $this->header2fileinfo($header);
117
  }
118
 
119
- $return['extracted_files'] = $result;
120
-
121
  $this->close();
122
- return $return;
123
  }
124
 
125
  /**
@@ -140,35 +125,26 @@ class Tar extends Archive
140
  * The archive is closed afer reading the contents, because rewinding is not possible in bzip2 streams.
141
  * Reopen the file with open() again if you want to do additional operations
142
  *
143
- * @param string $outdir the target directory for extracting
144
- * @param int|string $strip either the number of path components or a fixed prefix to strip
145
- * @param string $exclude a regular expression of files to exclude
146
- * @param string $include a regular expression of files to include
147
  * @throws ArchiveIOException
 
148
  * @return FileInfo[]
149
  */
150
- public function extract($outdir, $strip = '', $exclude = '', $include = '', $files_limit = 0)
151
  {
152
-
153
  if ($this->closed || !$this->file) {
154
  throw new ArchiveIOException('Can not read from a closed archive');
155
  }
156
 
157
  $outdir = rtrim($outdir, '/');
158
- if(!is_dir($outdir))
159
- @mkdir($outdir, 0755, true);
160
- else
161
- @chmod($outdir, 0777);
162
-
163
- //@mkdir($outdir, 0777, true);
164
-
165
  if (!is_dir($outdir)) {
166
  throw new ArchiveIOException("Could not create directory '$outdir'");
167
  }
168
 
169
- $files_counter = 0;
170
- $return = array();
171
-
172
  $extracted = array();
173
  while ($dat = $this->readbytes(512)) {
174
  // read the file header
@@ -176,17 +152,6 @@ class Tar extends Archive
176
  if (!is_array($header)) {
177
  continue;
178
  }
179
-
180
- if($files_limit)
181
- {
182
- if(++$files_counter > $files_limit)
183
- {
184
- $return['extracted_files'] = $extracted;
185
- $return['start'] = ftell($this->fh)-512;
186
- return $return;
187
- }
188
- }
189
-
190
  $fileinfo = $this->header2fileinfo($header);
191
 
192
  // apply strip rules
@@ -201,17 +166,11 @@ class Tar extends Archive
201
  // create output directory
202
  $output = $outdir.'/'.$fileinfo->getPath();
203
  $directory = ($fileinfo->getIsdir()) ? $output : dirname($output);
204
- if(!is_dir($directory))
205
- @mkdir($directory, 0755, true);
206
- else
207
- @chmod($directory, 0755);
208
 
209
  // extract data
210
  if (!$fileinfo->getIsdir()) {
211
- if(file_exists($output))
212
- unlink($output);
213
-
214
- $fp = fopen($output, "wb");
215
  if (!$fp) {
216
  throw new ArchiveIOException('Could not open file for writing: '.$output);
217
  }
@@ -225,21 +184,20 @@ class Tar extends Archive
225
  }
226
 
227
  fclose($fp);
228
- touch($output, $fileinfo->getMtime());
229
- chmod($output, $fileinfo->getMode());
230
  } else {
231
- //$this->skipbytes(ceil($header['size'] / 512) * 512); // the size is usually 0 for directories
232
- $this->skipbytes(ceil(0 / 512) * 512); // the size is usually 0 for directories
233
  }
234
 
 
 
 
235
  $extracted[] = $fileinfo;
236
  }
237
 
238
  $this->close();
239
-
240
- $return['extracted_files'] = $extracted;
241
-
242
- return $return;
243
  }
244
 
245
  /**
@@ -249,6 +207,7 @@ class Tar extends Archive
249
  *
250
  * @param string $file
251
  * @throws ArchiveIOException
 
252
  */
253
  public function create($file = '')
254
  {
@@ -281,9 +240,11 @@ class Tar extends Archive
281
  /**
282
  * Add a file to the current TAR archive using an existing file in the filesystem
283
  *
284
- * @param string $file path to the original file
285
  * @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with all meta data, empty to take from original
286
- * @throws ArchiveIOException
 
 
287
  */
288
  public function addFile($file, $fileinfo = '')
289
  {
@@ -295,27 +256,19 @@ class Tar extends Archive
295
  throw new ArchiveIOException('Archive has been closed, files can no longer be added');
296
  }
297
 
298
- if(is_dir($file))
299
- {
300
- $this->writeFileHeader($fileinfo);
301
- return;
302
- }
303
-
304
- $fp = fopen($file, 'rb');
305
  if (!$fp) {
306
  throw new ArchiveIOException('Could not open file for reading: '.$file);
307
  }
308
 
309
  // create file header
310
- if(is_resource($this->fh))
311
- {
312
- $archive_header_position = ftell($this->fh);
313
- }
314
  $this->writeFileHeader($fileinfo);
315
 
316
  // write data
 
317
  while (!feof($fp)) {
318
  $data = fread($fp, 512);
 
319
  if ($data === false) {
320
  break;
321
  }
@@ -325,23 +278,16 @@ class Tar extends Archive
325
  $packed = pack("a512", $data);
326
  $this->writebytes($packed);
327
  }
 
328
 
329
- $file_offset = ftell($fp);
330
-
331
- //rewrite header with new size if file size changed while reading
332
- if(is_resource($this->fh) && $file_offset && $file_offset != $fileinfo->getSize())
333
- {
334
- $archive_current_position = ftell($this->fh);
335
- fseek($this->fh, $archive_header_position);
336
-
337
- $fileinfo->setSize(ftell($fp));
338
- $this->writeFileHeader($fileinfo);
339
-
340
- fseek($this->fh, $archive_current_position);
341
-
342
- }
343
 
344
- fclose($fp);
 
 
345
  }
346
 
347
  /**
@@ -368,6 +314,10 @@ class Tar extends Archive
368
  for ($s = 0; $s < $len; $s += 512) {
369
  $this->writebytes(pack("a512", substr($data, $s, 512)));
370
  }
 
 
 
 
371
  }
372
 
373
  /**
@@ -380,6 +330,7 @@ class Tar extends Archive
380
  * consists of two 512 blocks of zero bytes"
381
  *
382
  * @link http://www.gnu.org/software/tar/manual/html_chapter/tar_8.html#SEC134
 
383
  */
384
  public function close()
385
  {
@@ -415,6 +366,7 @@ class Tar extends Archive
415
  * Returns the created in-memory archive data
416
  *
417
  * This implicitly calls close() on the Archive
 
418
  */
419
  public function getArchive()
420
  {
@@ -441,6 +393,7 @@ class Tar extends Archive
441
  *
442
  * @param string $file
443
  * @throws ArchiveIOException
 
444
  */
445
  public function save($file)
446
  {
@@ -448,7 +401,7 @@ class Tar extends Archive
448
  $this->setCompression($this->complevel, $this->filetype($file));
449
  }
450
 
451
- if (!file_put_contents($file, $this->getArchive())) {
452
  throw new ArchiveIOException('Could not write to file: '.$file);
453
  }
454
  }
@@ -492,103 +445,9 @@ class Tar extends Archive
492
  if ($written === false) {
493
  throw new ArchiveIOException('Failed to write to archive stream');
494
  }
495
-
496
  return $written;
497
  }
498
 
499
- public function appendFileData($file, $fileinfo = '', $start = 0, $limit = 0)
500
- {
501
- $end = $start+($limit*512);
502
-
503
- //check to see if we are at the begining of writing the file
504
- if(!$start)
505
- {
506
- if (is_string($fileinfo)) {
507
- $fileinfo = FileInfo::fromPath($file, $fileinfo);
508
- }
509
- }
510
-
511
- if ($this->closed) {
512
- throw new ArchiveIOException('Archive has been closed, files can no longer be added');
513
- }
514
-
515
- if(is_dir($file))
516
- {
517
- $this->writeFileHeader($fileinfo);
518
- return;
519
- }
520
-
521
- $fp = fopen($file, 'rb');
522
-
523
- fseek($fp, $start);
524
-
525
- if (!$fp) {
526
- throw new ArchiveIOException('Could not open file for reading: '.$file);
527
- }
528
-
529
- // create file header
530
- if(!$start)
531
- {
532
- $this->writeFileHeader($fileinfo);
533
- }
534
-
535
- $bytes = 0;
536
- // write data
537
- while ($end >=ftell($fp) and !feof($fp) ) {
538
- $data = fread($fp, 512);
539
- if ($data === false) {
540
- break;
541
- }
542
- if ($data === '') {
543
- break;
544
- }
545
- $packed = pack("a512", $data);
546
- $bytes += $this->writebytes($packed);
547
- }
548
-
549
-
550
-
551
- //if we are not at the end of file, we return the current position for incremental writing
552
- if(!feof($fp))
553
- $last_position = ftell($fp);
554
- else
555
- $last_position = -1;
556
-
557
- fclose($fp);
558
-
559
- return $last_position;
560
- }
561
-
562
- public function openForAppend($file = '')
563
- {
564
-
565
- $this->file = $file;
566
- $this->memory = '';
567
- $this->fh = 0;
568
-
569
- if ($this->file) {
570
- // determine compression
571
- if ($this->comptype == Archive::COMPRESS_AUTO) {
572
- $this->setCompression($this->complevel, $this->filetype($file));
573
- }
574
-
575
- if ($this->comptype === Archive::COMPRESS_GZIP) {
576
- $this->fh = @gzopen($this->file, 'ab'.$this->complevel);
577
- } elseif ($this->comptype === Archive::COMPRESS_BZIP) {
578
- $this->fh = @bzopen($this->file, 'a');
579
- } else {
580
- $this->fh = @fopen($this->file, 'ab');
581
- }
582
-
583
- if (!$this->fh) {
584
- throw new ArchiveIOException('Could not open file for writing: '.$this->file);
585
- }
586
- }
587
- $this->writeaccess = true;
588
- $this->closed = false;
589
-
590
- }
591
-
592
  /**
593
  * Skip forward in the open file pointer
594
  *
@@ -596,7 +455,7 @@ class Tar extends Archive
596
  *
597
  * @param int $bytes seek to this position
598
  */
599
- function skipbytes($bytes)
600
  {
601
  if ($this->comptype === Archive::COMPRESS_GZIP) {
602
  @gzseek($this->fh, $bytes, SEEK_CUR);
@@ -614,9 +473,10 @@ class Tar extends Archive
614
  }
615
 
616
  /**
617
- * Write the given file metat data as header
618
  *
619
  * @param FileInfo $fileinfo
 
620
  */
621
  protected function writeFileHeader(FileInfo $fileinfo)
622
  {
@@ -635,12 +495,13 @@ class Tar extends Archive
635
  * Write a file header to the stream
636
  *
637
  * @param string $name
638
- * @param int $uid
639
- * @param int $gid
640
- * @param int $perm
641
- * @param int $size
642
- * @param int $mtime
643
  * @param string $typeflag Set to '5' for directories
 
644
  */
645
  protected function writeRawFileHeader($name, $uid, $gid, $perm, $size, $mtime, $typeflag = '')
646
  {
@@ -716,20 +577,13 @@ class Tar extends Archive
716
  "a100filename/a8perm/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix",
717
  $block
718
  );
719
-
720
- if(DecOct($header['typeflag']) == DecOct(''))
721
- {
722
- $header['typeflag'] = (string)0;
723
- }
724
-
725
  if (!$header) {
726
  throw new ArchiveCorruptedException('Failed to parse header');
727
  }
728
- $return['checksum'] = OctDec(trim($header['checksum']));
729
 
 
730
  if ($return['checksum'] != $chks) {
731
- //print_r($header);exit;
732
- throw new ArchiveCorruptedException('Header does not match it\'s checksum for ');
733
  }
734
 
735
  $return['filename'] = trim($header['filename']);
@@ -748,22 +602,15 @@ class Tar extends Archive
748
  $return['filename'] = trim($header['prefix']).'/'.$return['filename'];
749
  }
750
 
751
- // Handle Long-Link and PAX entries from GNU Tar
752
  if ($return['typeflag'] == 'L') {
753
  // following data block(s) is the filename
754
  $filename = trim($this->readbytes(ceil($return['size'] / 512) * 512));
755
  // next block is the real header
756
  $block = $this->readbytes(512);
757
  $return = $this->parseHeader($block);
758
-
759
  // overwrite the filename
760
- $return['filename'] = $filename;
761
- }elseif ($return['typeflag'] == 'x') {
762
- // following data block(s) is the filename
763
- $filename = trim($this->readbytes(ceil($return['size'] / 512) * 512));
764
- // next block is the real header
765
- $block = $this->readbytes(512);
766
- $return = $this->parseHeader($block);
767
  }
768
 
769
  return $return;
@@ -822,7 +669,7 @@ class Tar extends Archive
822
  {
823
  // for existing files, try to read the magic bytes
824
  if(file_exists($file) && is_readable($file) && filesize($file) > 5) {
825
- $fh = fopen($file, 'rb');
826
  if(!$fh) return false;
827
  $magic = fread($fh, 5);
828
  fclose($fh);
@@ -841,4 +688,5 @@ class Tar extends Archive
841
 
842
  return Archive::COMPRESS_NONE;
843
  }
 
844
  }
28
  * Sets the compression to use
29
  *
30
  * @param int $level Compression level (0 to 9)
31
+ * @param int $type Type of compression to use (use COMPRESS_* constants)
32
+ * @throws ArchiveIllegalCompressionException
33
  */
34
  public function setCompression($level = 9, $type = Archive::COMPRESS_AUTO)
35
  {
36
  $this->compressioncheck($type);
37
+ if ($level < -1 || $level > 9) {
38
+ throw new ArchiveIllegalCompressionException('Compression level should be between -1 and 9');
39
+ }
40
  $this->comptype = $type;
41
  $this->complevel = $level;
42
  if($level == 0) $this->comptype = Archive::COMPRESS_NONE;
48
  *
49
  * @param string $file
50
  * @throws ArchiveIOException
51
+ * @throws ArchiveIllegalCompressionException
52
  */
53
+ public function open($file)
54
  {
55
  $this->file = $file;
56
 
72
  throw new ArchiveIOException('Could not open file for reading: '.$this->file);
73
  }
74
  $this->closed = false;
 
 
 
75
  }
76
 
77
  /**
83
  * Reopen the file with open() again if you want to do additional operations
84
  *
85
  * @throws ArchiveIOException
86
+ * @throws ArchiveCorruptedException
87
  * @returns FileInfo[]
88
  */
89
+ public function contents()
90
  {
91
  if ($this->closed || !$this->file) {
92
  throw new ArchiveIOException('Can not read from a closed archive');
93
  }
94
 
 
95
  $result = array();
 
96
  while ($read = $this->readbytes(512)) {
97
  $header = $this->parseHeader($read);
98
  if (!is_array($header)) {
99
  continue;
100
  }
101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  $this->skipbytes(ceil($header['size'] / 512) * 512);
103
  $result[] = $this->header2fileinfo($header);
104
  }
105
 
 
 
106
  $this->close();
107
+ return $result;
108
  }
109
 
110
  /**
125
  * The archive is closed afer reading the contents, because rewinding is not possible in bzip2 streams.
126
  * Reopen the file with open() again if you want to do additional operations
127
  *
128
+ * @param string $outdir the target directory for extracting
129
+ * @param int|string $strip either the number of path components or a fixed prefix to strip
130
+ * @param string $exclude a regular expression of files to exclude
131
+ * @param string $include a regular expression of files to include
132
  * @throws ArchiveIOException
133
+ * @throws ArchiveCorruptedException
134
  * @return FileInfo[]
135
  */
136
+ public function extract($outdir, $strip = '', $exclude = '', $include = '')
137
  {
 
138
  if ($this->closed || !$this->file) {
139
  throw new ArchiveIOException('Can not read from a closed archive');
140
  }
141
 
142
  $outdir = rtrim($outdir, '/');
143
+ @mkdir($outdir, 0777, true);
 
 
 
 
 
 
144
  if (!is_dir($outdir)) {
145
  throw new ArchiveIOException("Could not create directory '$outdir'");
146
  }
147
 
 
 
 
148
  $extracted = array();
149
  while ($dat = $this->readbytes(512)) {
150
  // read the file header
152
  if (!is_array($header)) {
153
  continue;
154
  }
 
 
 
 
 
 
 
 
 
 
 
155
  $fileinfo = $this->header2fileinfo($header);
156
 
157
  // apply strip rules
166
  // create output directory
167
  $output = $outdir.'/'.$fileinfo->getPath();
168
  $directory = ($fileinfo->getIsdir()) ? $output : dirname($output);
169
+ @mkdir($directory, 0777, true);
 
 
 
170
 
171
  // extract data
172
  if (!$fileinfo->getIsdir()) {
173
+ $fp = @fopen($output, "wb");
 
 
 
174
  if (!$fp) {
175
  throw new ArchiveIOException('Could not open file for writing: '.$output);
176
  }
184
  }
185
 
186
  fclose($fp);
187
+ @touch($output, $fileinfo->getMtime());
188
+ @chmod($output, $fileinfo->getMode());
189
  } else {
190
+ $this->skipbytes(ceil($header['size'] / 512) * 512); // the size is usually 0 for directories
 
191
  }
192
 
193
+ if(is_callable($this->callback)) {
194
+ call_user_func($this->callback, $fileinfo);
195
+ }
196
  $extracted[] = $fileinfo;
197
  }
198
 
199
  $this->close();
200
+ return $extracted;
 
 
 
201
  }
202
 
203
  /**
207
  *
208
  * @param string $file
209
  * @throws ArchiveIOException
210
+ * @throws ArchiveIllegalCompressionException
211
  */
212
  public function create($file = '')
213
  {
240
  /**
241
  * Add a file to the current TAR archive using an existing file in the filesystem
242
  *
243
+ * @param string $file path to the original file
244
  * @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with all meta data, empty to take from original
245
+ * @throws ArchiveCorruptedException when the file changes while reading it, the archive will be corrupt and should be deleted
246
+ * @throws ArchiveIOException there was trouble reading the given file, it was not added
247
+ * @throws FileInfoException trouble reading file info, it was not added
248
  */
249
  public function addFile($file, $fileinfo = '')
250
  {
256
  throw new ArchiveIOException('Archive has been closed, files can no longer be added');
257
  }
258
 
259
+ $fp = @fopen($file, 'rb');
 
 
 
 
 
 
260
  if (!$fp) {
261
  throw new ArchiveIOException('Could not open file for reading: '.$file);
262
  }
263
 
264
  // create file header
 
 
 
 
265
  $this->writeFileHeader($fileinfo);
266
 
267
  // write data
268
+ $read = 0;
269
  while (!feof($fp)) {
270
  $data = fread($fp, 512);
271
+ $read += strlen($data);
272
  if ($data === false) {
273
  break;
274
  }
278
  $packed = pack("a512", $data);
279
  $this->writebytes($packed);
280
  }
281
+ fclose($fp);
282
 
283
+ if($read != $fileinfo->getSize()) {
284
+ $this->close();
285
+ throw new ArchiveCorruptedException("The size of $file changed while reading, archive corrupted. read $read expected ".$fileinfo->getSize());
286
+ }
 
 
 
 
 
 
 
 
 
 
287
 
288
+ if(is_callable($this->callback)) {
289
+ call_user_func($this->callback, $fileinfo);
290
+ }
291
  }
292
 
293
  /**
314
  for ($s = 0; $s < $len; $s += 512) {
315
  $this->writebytes(pack("a512", substr($data, $s, 512)));
316
  }
317
+
318
+ if (is_callable($this->callback)) {
319
+ call_user_func($this->callback, $fileinfo);
320
+ }
321
  }
322
 
323
  /**
330
  * consists of two 512 blocks of zero bytes"
331
  *
332
  * @link http://www.gnu.org/software/tar/manual/html_chapter/tar_8.html#SEC134
333
+ * @throws ArchiveIOException
334
  */
335
  public function close()
336
  {
366
  * Returns the created in-memory archive data
367
  *
368
  * This implicitly calls close() on the Archive
369
+ * @throws ArchiveIOException
370
  */
371
  public function getArchive()
372
  {
393
  *
394
  * @param string $file
395
  * @throws ArchiveIOException
396
+ * @throws ArchiveIllegalCompressionException
397
  */
398
  public function save($file)
399
  {
401
  $this->setCompression($this->complevel, $this->filetype($file));
402
  }
403
 
404
+ if (!@file_put_contents($file, $this->getArchive())) {
405
  throw new ArchiveIOException('Could not write to file: '.$file);
406
  }
407
  }
445
  if ($written === false) {
446
  throw new ArchiveIOException('Failed to write to archive stream');
447
  }
 
448
  return $written;
449
  }
450
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
  /**
452
  * Skip forward in the open file pointer
453
  *
455
  *
456
  * @param int $bytes seek to this position
457
  */
458
+ protected function skipbytes($bytes)
459
  {
460
  if ($this->comptype === Archive::COMPRESS_GZIP) {
461
  @gzseek($this->fh, $bytes, SEEK_CUR);
473
  }
474
 
475
  /**
476
+ * Write the given file meta data as header
477
  *
478
  * @param FileInfo $fileinfo
479
+ * @throws ArchiveIOException
480
  */
481
  protected function writeFileHeader(FileInfo $fileinfo)
482
  {
495
  * Write a file header to the stream
496
  *
497
  * @param string $name
498
+ * @param int $uid
499
+ * @param int $gid
500
+ * @param int $perm
501
+ * @param int $size
502
+ * @param int $mtime
503
  * @param string $typeflag Set to '5' for directories
504
+ * @throws ArchiveIOException
505
  */
506
  protected function writeRawFileHeader($name, $uid, $gid, $perm, $size, $mtime, $typeflag = '')
507
  {
577
  "a100filename/a8perm/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix",
578
  $block
579
  );
 
 
 
 
 
 
580
  if (!$header) {
581
  throw new ArchiveCorruptedException('Failed to parse header');
582
  }
 
583
 
584
+ $return['checksum'] = OctDec(trim($header['checksum']));
585
  if ($return['checksum'] != $chks) {
586
+ throw new ArchiveCorruptedException('Header does not match it\'s checksum');
 
587
  }
588
 
589
  $return['filename'] = trim($header['filename']);
602
  $return['filename'] = trim($header['prefix']).'/'.$return['filename'];
603
  }
604
 
605
+ // Handle Long-Link entries from GNU Tar
606
  if ($return['typeflag'] == 'L') {
607
  // following data block(s) is the filename
608
  $filename = trim($this->readbytes(ceil($return['size'] / 512) * 512));
609
  // next block is the real header
610
  $block = $this->readbytes(512);
611
  $return = $this->parseHeader($block);
 
612
  // overwrite the filename
613
+ $return['filename'] = $filename;
 
 
 
 
 
 
614
  }
615
 
616
  return $return;
669
  {
670
  // for existing files, try to read the magic bytes
671
  if(file_exists($file) && is_readable($file) && filesize($file) > 5) {
672
+ $fh = @fopen($file, 'rb');
673
  if(!$fh) return false;
674
  $magic = fread($fh, 5);
675
  fclose($fh);
688
 
689
  return Archive::COMPRESS_NONE;
690
  }
691
+
692
  }
vendor/splitbrain/php-archive/src/Zip.php CHANGED
@@ -34,10 +34,13 @@ class Zip extends Archive
34
  *
35
  * @param int $level Compression level (0 to 9)
36
  * @param int $type Type of compression to use ignored for ZIP
37
- * @return mixed
38
  */
39
  public function setCompression($level = 9, $type = Archive::COMPRESS_AUTO)
40
  {
 
 
 
41
  $this->complevel = $level;
42
  }
43
 
@@ -152,6 +155,9 @@ class Zip extends Archive
152
 
153
  // nothing more to do for directories
154
  if ($fileinfo->getIsdir()) {
 
 
 
155
  continue;
156
  }
157
 
@@ -226,8 +232,11 @@ class Zip extends Archive
226
  unlink($extractto); // remove temporary gz file
227
  }
228
 
229
- touch($output, $fileinfo->getMtime());
230
  //FIXME what about permissions?
 
 
 
231
  }
232
 
233
  $this->close();
@@ -271,9 +280,10 @@ class Zip extends Archive
271
  /**
272
  * Add a file to the current archive using an existing file in the filesystem
273
  *
274
- * @param string $file path to the original file
275
  * @param string|FileInfo $fileinfo either the name to use in archive (string) or a FileInfo oject with all meta data, empty to take from original
276
  * @throws ArchiveIOException
 
277
  */
278
  public function addFile($file, $fileinfo = '')
279
  {
@@ -350,6 +360,10 @@ class Zip extends Archive
350
  $name,
351
  (bool) $this->complevel
352
  );
 
 
 
 
353
  }
354
 
355
  /**
@@ -357,6 +371,7 @@ class Zip extends Archive
357
  *
358
  * After a call to this function no more data can be added to the archive, for
359
  * read access no reading is allowed anymore
 
360
  */
361
  public function close()
362
  {
@@ -400,6 +415,7 @@ class Zip extends Archive
400
  * Returns the created in-memory archive data
401
  *
402
  * This implicitly calls close() on the Archive
 
403
  */
404
  public function getArchive()
405
  {
34
  *
35
  * @param int $level Compression level (0 to 9)
36
  * @param int $type Type of compression to use ignored for ZIP
37
+ * @throws ArchiveIllegalCompressionException
38
  */
39
  public function setCompression($level = 9, $type = Archive::COMPRESS_AUTO)
40
  {
41
+ if ($level < -1 || $level > 9) {
42
+ throw new ArchiveIllegalCompressionException('Compression level should be between -1 and 9');
43
+ }
44
  $this->complevel = $level;
45
  }
46
 
155
 
156
  // nothing more to do for directories
157
  if ($fileinfo->getIsdir()) {
158
+ if(is_callable($this->callback)) {
159
+ call_user_func($this->callback, $fileinfo);
160
+ }
161
  continue;
162
  }
163
 
232
  unlink($extractto); // remove temporary gz file
233
  }
234
 
235
+ @touch($output, $fileinfo->getMtime());
236
  //FIXME what about permissions?
237
+ if(is_callable($this->callback)) {
238
+ call_user_func($this->callback, $fileinfo);
239
+ }
240
  }
241
 
242
  $this->close();
280
  /**
281
  * Add a file to the current archive using an existing file in the filesystem
282
  *
283
+ * @param string $file path to the original file
284
  * @param string|FileInfo $fileinfo either the name to use in archive (string) or a FileInfo oject with all meta data, empty to take from original
285
  * @throws ArchiveIOException
286
+ * @throws FileInfoException
287
  */
288
  public function addFile($file, $fileinfo = '')
289
  {
360
  $name,
361
  (bool) $this->complevel
362
  );
363
+
364
+ if(is_callable($this->callback)) {
365
+ call_user_func($this->callback, $fileinfo);
366
+ }
367
  }
368
 
369
  /**
371
  *
372
  * After a call to this function no more data can be added to the archive, for
373
  * read access no reading is allowed anymore
374
+ * @throws ArchiveIOException
375
  */
376
  public function close()
377
  {
415
  * Returns the created in-memory archive data
416
  *
417
  * This implicitly calls close() on the Archive
418
+ * @throws ArchiveIOException
419
  */
420
  public function getArchive()
421
  {
vendor/splitbrain/php-archive/tests/FileInfoTest.php CHANGED
@@ -1,8 +1,7 @@
1
- <?php
2
 
3
  namespace splitbrain\PHPArchive;
4
 
5
- use splitbrain\PHPArchive\FileInfo;
6
  use PHPUnit\Framework\TestCase;
7
 
8
  class FileInfoTest extends TestCase
@@ -101,10 +100,10 @@ class FileInfoTest extends TestCase
101
  }
102
 
103
  /**
104
- * @expectedException splitbrain\PHPArchive\FileInfoException
105
  */
106
  public function testFromPathWithFileNotExisted()
107
  {
108
- $fileinfo = FileInfo::fromPath('invalid_file_path');
109
  }
110
  }
1
+ <?php /** @noinspection PhpUnhandledExceptionInspection */
2
 
3
  namespace splitbrain\PHPArchive;
4
 
 
5
  use PHPUnit\Framework\TestCase;
6
 
7
  class FileInfoTest extends TestCase
100
  }
101
 
102
  /**
103
+ * @expectedException \splitbrain\PHPArchive\FileInfoException
104
  */
105
  public function testFromPathWithFileNotExisted()
106
  {
107
+ FileInfo::fromPath('invalid_file_path');
108
  }
109
  }
vendor/splitbrain/php-archive/tests/TarTestCase.php CHANGED
@@ -1,18 +1,21 @@
1
- <?php
2
 
3
  namespace splitbrain\PHPArchive;
4
 
5
- use splitbrain\PHPArchive\Tar;
6
- use PHPUnit\Framework\TestCase;
7
  use org\bovigo\vfs\vfsStream;
 
8
 
9
  class TarTestCase extends TestCase
10
  {
 
 
 
11
  /**
12
  * file extensions that several tests use
13
  */
14
  protected $extensions = array('tar');
15
 
 
16
  protected function setUp()
17
  {
18
  parent::setUp();
@@ -27,12 +30,22 @@ class TarTestCase extends TestCase
27
  vfsStream::setup('home_root_path');
28
  }
29
 
 
30
  protected function tearDown()
31
  {
32
  parent::tearDown();
33
  $this->extensions[] = null;
34
  }
35
 
 
 
 
 
 
 
 
 
 
36
  /*
37
  * dependency for tests needing zlib extension to pass
38
  */
@@ -50,7 +63,7 @@ class TarTestCase extends TestCase
50
  }
51
 
52
  /**
53
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
54
  */
55
  public function testTarFileIsNotExisted()
56
  {
@@ -149,6 +162,7 @@ class TarTestCase extends TestCase
149
  $file = "$dir/test.$ext";
150
 
151
  $tar->open($file);
 
152
  $content = $tar->contents();
153
 
154
  $this->assertCount(4, $content, "Contents of $file");
@@ -170,7 +184,9 @@ class TarTestCase extends TestCase
170
  $archive = sys_get_temp_dir() . '/dwtartest' . md5(time()) . '.' . $ext;
171
  $extract = sys_get_temp_dir() . '/dwtartest' . md5(time() + 1);
172
 
 
173
  $tar = new Tar();
 
174
  $tar->create($archive);
175
  foreach ($input as $path) {
176
  $file = basename($path);
@@ -178,8 +194,11 @@ class TarTestCase extends TestCase
178
  }
179
  $tar->close();
180
  $this->assertFileExists($archive);
 
181
 
 
182
  $tar = new Tar();
 
183
  $tar->open($archive);
184
  $tar->extract($extract, '', '/FileInfo\\.php/', '/.*\\.php/');
185
 
@@ -187,6 +206,8 @@ class TarTestCase extends TestCase
187
  $this->assertFileExists("$extract/Zip.php");
188
  $this->assertFileNotExists("$extract/FileInfo.php");
189
 
 
 
190
  $this->nativeCheck($archive, $ext);
191
 
192
  self::RDelete($extract);
@@ -575,7 +596,8 @@ class TarTestCase extends TestCase
575
  try {
576
  $phar = new \PharData($archive);
577
  $phar->extractTo($extract);
578
- } catch (\Exception $e) {};
 
579
 
580
  $this->assertFileExists("$extract/Tar.php");
581
  $this->assertFileExists("$extract/Zip.php");
@@ -588,7 +610,7 @@ class TarTestCase extends TestCase
588
  }
589
 
590
  /**
591
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
592
  */
593
  public function testContentsWithInvalidArchiveStream()
594
  {
@@ -597,7 +619,7 @@ class TarTestCase extends TestCase
597
  }
598
 
599
  /**
600
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
601
  */
602
  public function testExtractWithInvalidOutDir()
603
  {
@@ -611,7 +633,7 @@ class TarTestCase extends TestCase
611
  }
612
 
613
  /**
614
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
615
  */
616
  public function testExtractWithArchiveStreamIsClosed()
617
  {
@@ -626,7 +648,7 @@ class TarTestCase extends TestCase
626
  }
627
 
628
  /**
629
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
630
  */
631
  public function testCreateWithInvalidFile()
632
  {
@@ -638,7 +660,7 @@ class TarTestCase extends TestCase
638
  }
639
 
640
  /**
641
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
642
  */
643
  public function testAddFileWithArchiveStreamIsClosed()
644
  {
@@ -651,7 +673,7 @@ class TarTestCase extends TestCase
651
  }
652
 
653
  /**
654
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
655
  */
656
  public function testAddFileWithInvalidFile()
657
  {
@@ -663,7 +685,7 @@ class TarTestCase extends TestCase
663
  }
664
 
665
  /**
666
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
667
  */
668
  public function testAddDataWithArchiveStreamIsClosed()
669
  {
@@ -683,7 +705,8 @@ class TarTestCase extends TestCase
683
  $tar->create($archive);
684
  $tar->close();
685
 
686
- $this->assertNull($tar->close());
 
687
  }
688
 
689
  /**
@@ -709,11 +732,12 @@ class TarTestCase extends TestCase
709
  $tar->create();
710
  $tar->addFile("$dir/zero.txt", 'zero.txt');
711
 
712
- $this->assertNull($tar->save(vfsStream::url('home_root_path/archive_file')));
 
713
  }
714
 
715
- /**
716
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
717
  */
718
  public function testSaveWithInvalidDestinationFile()
719
  {
@@ -723,7 +747,8 @@ class TarTestCase extends TestCase
723
  $tar->create();
724
  $tar->addFile("$dir/zero.txt", 'zero.txt');
725
 
726
- $this->assertNull($tar->save(vfsStream::url('archive_file')));
 
727
  }
728
 
729
  /**
1
+ <?php /** @noinspection PhpUnhandledExceptionInspection */
2
 
3
  namespace splitbrain\PHPArchive;
4
 
 
 
5
  use org\bovigo\vfs\vfsStream;
6
+ use PHPUnit\Framework\TestCase;
7
 
8
  class TarTestCase extends TestCase
9
  {
10
+ /** @var int callback counter */
11
+ protected $counter = 0;
12
+
13
  /**
14
  * file extensions that several tests use
15
  */
16
  protected $extensions = array('tar');
17
 
18
+ /** @inheritdoc */
19
  protected function setUp()
20
  {
21
  parent::setUp();
30
  vfsStream::setup('home_root_path');
31
  }
32
 
33
+ /** @inheritdoc */
34
  protected function tearDown()
35
  {
36
  parent::tearDown();
37
  $this->extensions[] = null;
38
  }
39
 
40
+ /**
41
+ * Callback check function
42
+ * @param FileInfo $fileinfo
43
+ */
44
+ public function increaseCounter($fileinfo) {
45
+ $this->assertInstanceOf('\\splitbrain\\PHPArchive\\FileInfo', $fileinfo);
46
+ $this->counter++;
47
+ }
48
+
49
  /*
50
  * dependency for tests needing zlib extension to pass
51
  */
63
  }
64
 
65
  /**
66
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
67
  */
68
  public function testTarFileIsNotExisted()
69
  {
162
  $file = "$dir/test.$ext";
163
 
164
  $tar->open($file);
165
+ /** @var FileInfo[] $content */
166
  $content = $tar->contents();
167
 
168
  $this->assertCount(4, $content, "Contents of $file");
184
  $archive = sys_get_temp_dir() . '/dwtartest' . md5(time()) . '.' . $ext;
185
  $extract = sys_get_temp_dir() . '/dwtartest' . md5(time() + 1);
186
 
187
+ $this->counter = 0;
188
  $tar = new Tar();
189
+ $tar->setCallback(array($this, 'increaseCounter'));
190
  $tar->create($archive);
191
  foreach ($input as $path) {
192
  $file = basename($path);
194
  }
195
  $tar->close();
196
  $this->assertFileExists($archive);
197
+ $this->assertEquals(count($input), $this->counter);
198
 
199
+ $this->counter = 0;
200
  $tar = new Tar();
201
+ $tar->setCallback(array($this, 'increaseCounter'));
202
  $tar->open($archive);
203
  $tar->extract($extract, '', '/FileInfo\\.php/', '/.*\\.php/');
204
 
206
  $this->assertFileExists("$extract/Zip.php");
207
  $this->assertFileNotExists("$extract/FileInfo.php");
208
 
209
+ $this->assertEquals(count($input) - 1, $this->counter);
210
+
211
  $this->nativeCheck($archive, $ext);
212
 
213
  self::RDelete($extract);
596
  try {
597
  $phar = new \PharData($archive);
598
  $phar->extractTo($extract);
599
+ } catch(\Exception $e) {
600
+ };
601
 
602
  $this->assertFileExists("$extract/Tar.php");
603
  $this->assertFileExists("$extract/Zip.php");
610
  }
611
 
612
  /**
613
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
614
  */
615
  public function testContentsWithInvalidArchiveStream()
616
  {
619
  }
620
 
621
  /**
622
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
623
  */
624
  public function testExtractWithInvalidOutDir()
625
  {
633
  }
634
 
635
  /**
636
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
637
  */
638
  public function testExtractWithArchiveStreamIsClosed()
639
  {
648
  }
649
 
650
  /**
651
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
652
  */
653
  public function testCreateWithInvalidFile()
654
  {
660
  }
661
 
662
  /**
663
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
664
  */
665
  public function testAddFileWithArchiveStreamIsClosed()
666
  {
673
  }
674
 
675
  /**
676
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
677
  */
678
  public function testAddFileWithInvalidFile()
679
  {
685
  }
686
 
687
  /**
688
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
689
  */
690
  public function testAddDataWithArchiveStreamIsClosed()
691
  {
705
  $tar->create($archive);
706
  $tar->close();
707
 
708
+ $tar->close();
709
+ $this->assertTrue(true); // succeed if no exception, yet
710
  }
711
 
712
  /**
732
  $tar->create();
733
  $tar->addFile("$dir/zero.txt", 'zero.txt');
734
 
735
+ $tar->save(vfsStream::url('home_root_path/archive_file'));
736
+ $this->assertTrue(true); // succeed if no exception, yet
737
  }
738
 
739
+ /**
740
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
741
  */
742
  public function testSaveWithInvalidDestinationFile()
743
  {
747
  $tar->create();
748
  $tar->addFile("$dir/zero.txt", 'zero.txt');
749
 
750
+ $tar->save(vfsStream::url('archive_file'));
751
+ $this->assertTrue(true); // succeed if no exception, yet
752
  }
753
 
754
  /**
vendor/splitbrain/php-archive/tests/ZipTestCase.php CHANGED
@@ -1,18 +1,30 @@
1
- <?php
2
 
3
  namespace splitbrain\PHPArchive;
4
 
5
- use splitbrain\PHPArchive\Zip;
6
- use PHPUnit\Framework\TestCase;
7
  use org\bovigo\vfs\vfsStream;
 
8
 
9
  class ZipTestCase extends TestCase
10
  {
 
 
 
 
11
  protected function setUp()
12
  {
13
  vfsStream::setup('home_root_path');
14
  }
15
 
 
 
 
 
 
 
 
 
 
16
  /*
17
  * dependency for tests needing zip extension to pass
18
  */
@@ -22,7 +34,7 @@ class ZipTestCase extends TestCase
22
  }
23
 
24
  /**
25
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
26
  */
27
  public function testMissing()
28
  {
@@ -41,7 +53,7 @@ class ZipTestCase extends TestCase
41
  {
42
  $zip = new Zip();
43
 
44
- $dir = dirname(__FILE__).'/zip';
45
  $tdir = ltrim($dir, '/');
46
 
47
  $zip->create();
@@ -81,9 +93,9 @@ class ZipTestCase extends TestCase
81
  {
82
  $zip = new Zip();
83
 
84
- $dir = dirname(__FILE__).'/zip';
85
  $tdir = ltrim($dir, '/');
86
- $tmp = vfsStream::url('home_root_path/test.zip');
87
 
88
  $zip->create($tmp);
89
  $zip->setCompression(0);
@@ -114,27 +126,25 @@ class ZipTestCase extends TestCase
114
  }
115
 
116
  /**
117
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
118
  */
119
  public function testCreateWithInvalidFilePath()
120
  {
121
  $zip = new Zip();
122
 
123
- $dir = dirname(__FILE__).'/zip';
124
- $tmp = vfsStream::url('invalid_root_path/test.zip');
125
 
126
  $zip->create($tmp);
127
  }
128
 
129
  /**
130
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
131
  */
132
  public function testAddFileWithArchiveStreamIsClosed()
133
  {
134
  $zip = new Zip();
135
 
136
- $dir = dirname(__FILE__).'/zip';
137
- $tmp = vfsStream::url('home_root_path/test.zip');
138
 
139
  $zip->setCompression(0);
140
  $zip->close();
@@ -142,13 +152,13 @@ class ZipTestCase extends TestCase
142
  }
143
 
144
  /**
145
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
146
  */
147
  public function testAddFileWithInvalidFile()
148
  {
149
  $zip = new Zip();
150
 
151
- $tmp = vfsStream::url('home_root_path/test.zip');
152
 
153
  $zip->create($tmp);
154
  $zip->setCompression(0);
@@ -162,9 +172,9 @@ class ZipTestCase extends TestCase
162
  */
163
  public function testZipContent()
164
  {
165
- $dir = dirname(__FILE__).'/zip';
166
 
167
- $zip = new Zip();
168
  $file = "$dir/test.zip";
169
 
170
  $zip->open($file);
@@ -179,18 +189,18 @@ class ZipTestCase extends TestCase
179
  }
180
 
181
  /**
182
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
183
  */
184
  public function testZipContentWithArchiveStreamIsClosed()
185
  {
186
- $dir = dirname(__FILE__).'/zip';
187
 
188
- $zip = new Zip();
189
  $file = "$dir/test.zip";
190
 
191
  $zip->open($file);
192
  $zip->close();
193
- $content = $zip->contents();
194
  }
195
 
196
  /**
@@ -203,16 +213,21 @@ class ZipTestCase extends TestCase
203
  $archive = sys_get_temp_dir() . '/dwziptest' . md5(time()) . '.zip';
204
  $extract = sys_get_temp_dir() . '/dwziptest' . md5(time() + 1);
205
 
 
206
  $zip = new Zip();
 
207
  $zip->create($archive);
208
- foreach($input as $path) {
209
  $file = basename($path);
210
  $zip->addFile($path, $file);
211
  }
212
  $zip->close();
213
  $this->assertFileExists($archive);
 
214
 
 
215
  $zip = new Zip();
 
216
  $zip->open($archive);
217
  $zip->extract($extract, '', '/FileInfo\\.php/', '/.*\\.php/');
218
 
@@ -220,6 +235,8 @@ class ZipTestCase extends TestCase
220
  $this->assertFileExists("$extract/Zip.php");
221
  $this->assertFileNotExists("$extract/FileInfo.php");
222
 
 
 
223
  $this->nativeCheck($archive);
224
  $this->native7ZipCheck($archive);
225
 
@@ -246,8 +263,8 @@ class ZipTestCase extends TestCase
246
  $zip->open($archive);
247
  $zip->extract($extract);
248
 
249
- $this->assertFileExists($extract.'/tüst.txt');
250
- $this->assertFileExists($extract.'/snowy☃.txt');
251
 
252
  $this->nativeCheck($archive);
253
  $this->native7ZipCheck($archive);
@@ -257,7 +274,7 @@ class ZipTestCase extends TestCase
257
  }
258
 
259
  /**
260
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
261
  */
262
  public function testAddDataWithArchiveStreamIsClosed()
263
  {
@@ -277,7 +294,8 @@ class ZipTestCase extends TestCase
277
  $zip->create($archive);
278
  $zip->close();
279
 
280
- $this->assertNull($zip->close());
 
281
  }
282
 
283
  public function testSaveArchiveFile()
@@ -288,11 +306,12 @@ class ZipTestCase extends TestCase
288
  $zip->create();
289
  $zip->addFile("$dir/zero.txt", 'zero.txt');
290
 
291
- $this->assertNull($zip->save(vfsStream::url('home_root_path/archive_file')));
 
292
  }
293
 
294
  /**
295
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
296
  */
297
  public function testSaveWithInvalidFilePath()
298
  {
@@ -353,10 +372,10 @@ class ZipTestCase extends TestCase
353
  */
354
  public function testZipExtract()
355
  {
356
- $dir = dirname(__FILE__).'/zip';
357
- $out = sys_get_temp_dir().'/dwziptest'.md5(time());
358
 
359
- $zip = new Zip();
360
  $file = "$dir/test.zip";
361
 
362
  $zip->open($file);
@@ -364,28 +383,28 @@ class ZipTestCase extends TestCase
364
 
365
  clearstatcache();
366
 
367
- $this->assertFileExists($out.'/zip/testdata1.txt', "Extracted $file");
368
- $this->assertEquals(13, filesize($out.'/zip/testdata1.txt'), "Extracted $file");
369
 
370
- $this->assertFileExists($out.'/zip/foobar/testdata2.txt', "Extracted $file");
371
- $this->assertEquals(13, filesize($out.'/zip/foobar/testdata2.txt'), "Extracted $file");
372
 
373
- $this->assertFileExists($out.'/zip/compressable.txt', "Extracted $file");
374
- $this->assertEquals(1836, filesize($out.'/zip/compressable.txt'), "Extracted $file");
375
- $this->assertFileNotExists($out.'/zip/compressable.txt.gz', "Extracted $file");
376
 
377
  self::RDelete($out);
378
  }
379
 
380
  /**
381
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
382
  */
383
  public function testZipExtractWithArchiveStreamIsClosed()
384
  {
385
- $dir = dirname(__FILE__).'/zip';
386
- $out = sys_get_temp_dir().'/dwziptest'.md5(time());
387
 
388
- $zip = new Zip();
389
  $file = "$dir/test.zip";
390
 
391
  $zip->open($file);
@@ -399,10 +418,10 @@ class ZipTestCase extends TestCase
399
  */
400
  public function testCompStripExtract()
401
  {
402
- $dir = dirname(__FILE__).'/zip';
403
- $out = sys_get_temp_dir().'/dwziptest'.md5(time());
404
 
405
- $zip = new Zip();
406
  $file = "$dir/test.zip";
407
 
408
  $zip->open($file);
@@ -410,11 +429,11 @@ class ZipTestCase extends TestCase
410
 
411
  clearstatcache();
412
 
413
- $this->assertFileExists($out.'/testdata1.txt', "Extracted $file");
414
- $this->assertEquals(13, filesize($out.'/testdata1.txt'), "Extracted $file");
415
 
416
- $this->assertFileExists($out.'/foobar/testdata2.txt', "Extracted $file");
417
- $this->assertEquals(13, filesize($out.'/foobar/testdata2.txt'), "Extracted $file");
418
 
419
  self::RDelete($out);
420
  }
@@ -425,10 +444,10 @@ class ZipTestCase extends TestCase
425
  */
426
  public function testPrefixStripExtract()
427
  {
428
- $dir = dirname(__FILE__).'/zip';
429
- $out = sys_get_temp_dir().'/dwziptest'.md5(time());
430
 
431
- $zip = new Zip();
432
  $file = "$dir/test.zip";
433
 
434
  $zip->open($file);
@@ -436,11 +455,11 @@ class ZipTestCase extends TestCase
436
 
437
  clearstatcache();
438
 
439
- $this->assertFileExists($out.'/zip/testdata1.txt', "Extracted $file");
440
- $this->assertEquals(13, filesize($out.'/zip/testdata1.txt'), "Extracted $file");
441
 
442
- $this->assertFileExists($out.'/testdata2.txt', "Extracted $file");
443
- $this->assertEquals(13, filesize($out.'/testdata2.txt'), "Extracted $file");
444
 
445
  self::RDelete($out);
446
  }
@@ -451,10 +470,10 @@ class ZipTestCase extends TestCase
451
  */
452
  public function testIncludeExtract()
453
  {
454
- $dir = dirname(__FILE__).'/zip';
455
- $out = sys_get_temp_dir().'/dwziptest'.md5(time());
456
 
457
- $zip = new Zip();
458
  $file = "$dir/test.zip";
459
 
460
  $zip->open($file);
@@ -462,10 +481,10 @@ class ZipTestCase extends TestCase
462
 
463
  clearstatcache();
464
 
465
- $this->assertFileNotExists($out.'/zip/testdata1.txt', "Extracted $file");
466
 
467
- $this->assertFileExists($out.'/zip/foobar/testdata2.txt', "Extracted $file");
468
- $this->assertEquals(13, filesize($out.'/zip/foobar/testdata2.txt'), "Extracted $file");
469
 
470
  self::RDelete($out);
471
  }
@@ -476,10 +495,10 @@ class ZipTestCase extends TestCase
476
  */
477
  public function testExcludeExtract()
478
  {
479
- $dir = dirname(__FILE__).'/zip';
480
- $out = sys_get_temp_dir().'/dwziptest'.md5(time());
481
 
482
- $zip = new Zip();
483
  $file = "$dir/test.zip";
484
 
485
  $zip->open($file);
@@ -487,10 +506,10 @@ class ZipTestCase extends TestCase
487
 
488
  clearstatcache();
489
 
490
- $this->assertFileExists($out.'/zip/testdata1.txt', "Extracted $file");
491
- $this->assertEquals(13, filesize($out.'/zip/testdata1.txt'), "Extracted $file");
492
 
493
- $this->assertFileNotExists($out.'/zip/foobar/testdata2.txt', "Extracted $file");
494
 
495
  self::RDelete($out);
496
  }
@@ -521,7 +540,6 @@ class ZipTestCase extends TestCase
521
  $this->assertFileExists("$out/täst.txt");
522
  }
523
 
524
-
525
  /**
526
  * recursive rmdir()/unlink()
527
  *
1
+ <?php /** @noinspection PhpUnhandledExceptionInspection */
2
 
3
  namespace splitbrain\PHPArchive;
4
 
 
 
5
  use org\bovigo\vfs\vfsStream;
6
+ use PHPUnit\Framework\TestCase;
7
 
8
  class ZipTestCase extends TestCase
9
  {
10
+ /** @var int callback counter */
11
+ protected $counter = 0;
12
+
13
+ /** @inheritdoc */
14
  protected function setUp()
15
  {
16
  vfsStream::setup('home_root_path');
17
  }
18
 
19
+ /**
20
+ * Callback check function
21
+ * @param FileInfo $fileinfo
22
+ */
23
+ public function increaseCounter($fileinfo) {
24
+ $this->assertInstanceOf('\\splitbrain\\PHPArchive\\FileInfo', $fileinfo);
25
+ $this->counter++;
26
+ }
27
+
28
  /*
29
  * dependency for tests needing zip extension to pass
30
  */
34
  }
35
 
36
  /**
37
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
38
  */
39
  public function testMissing()
40
  {
53
  {
54
  $zip = new Zip();
55
 
56
+ $dir = dirname(__FILE__) . '/zip';
57
  $tdir = ltrim($dir, '/');
58
 
59
  $zip->create();
93
  {
94
  $zip = new Zip();
95
 
96
+ $dir = dirname(__FILE__) . '/zip';
97
  $tdir = ltrim($dir, '/');
98
+ $tmp = vfsStream::url('home_root_path/test.zip');
99
 
100
  $zip->create($tmp);
101
  $zip->setCompression(0);
126
  }
127
 
128
  /**
129
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
130
  */
131
  public function testCreateWithInvalidFilePath()
132
  {
133
  $zip = new Zip();
134
 
135
+ $tmp = vfsStream::url('invalid_root_path/test.zip');
 
136
 
137
  $zip->create($tmp);
138
  }
139
 
140
  /**
141
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
142
  */
143
  public function testAddFileWithArchiveStreamIsClosed()
144
  {
145
  $zip = new Zip();
146
 
147
+ $dir = dirname(__FILE__) . '/zip';
 
148
 
149
  $zip->setCompression(0);
150
  $zip->close();
152
  }
153
 
154
  /**
155
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
156
  */
157
  public function testAddFileWithInvalidFile()
158
  {
159
  $zip = new Zip();
160
 
161
+ $tmp = vfsStream::url('home_root_path/test.zip');
162
 
163
  $zip->create($tmp);
164
  $zip->setCompression(0);
172
  */
173
  public function testZipContent()
174
  {
175
+ $dir = dirname(__FILE__) . '/zip';
176
 
177
+ $zip = new Zip();
178
  $file = "$dir/test.zip";
179
 
180
  $zip->open($file);
189
  }
190
 
191
  /**
192
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
193
  */
194
  public function testZipContentWithArchiveStreamIsClosed()
195
  {
196
+ $dir = dirname(__FILE__) . '/zip';
197
 
198
+ $zip = new Zip();
199
  $file = "$dir/test.zip";
200
 
201
  $zip->open($file);
202
  $zip->close();
203
+ $zip->contents();
204
  }
205
 
206
  /**
213
  $archive = sys_get_temp_dir() . '/dwziptest' . md5(time()) . '.zip';
214
  $extract = sys_get_temp_dir() . '/dwziptest' . md5(time() + 1);
215
 
216
+ $this->counter = 0;
217
  $zip = new Zip();
218
+ $zip->setCallback(array($this, 'increaseCounter'));
219
  $zip->create($archive);
220
+ foreach ($input as $path) {
221
  $file = basename($path);
222
  $zip->addFile($path, $file);
223
  }
224
  $zip->close();
225
  $this->assertFileExists($archive);
226
+ $this->assertEquals(count($input), $this->counter);
227
 
228
+ $this->counter = 0;
229
  $zip = new Zip();
230
+ $zip->setCallback(array($this, 'increaseCounter'));
231
  $zip->open($archive);
232
  $zip->extract($extract, '', '/FileInfo\\.php/', '/.*\\.php/');
233
 
235
  $this->assertFileExists("$extract/Zip.php");
236
  $this->assertFileNotExists("$extract/FileInfo.php");
237
 
238
+ $this->assertEquals(count($input) - 1, $this->counter);
239
+
240
  $this->nativeCheck($archive);
241
  $this->native7ZipCheck($archive);
242
 
263
  $zip->open($archive);
264
  $zip->extract($extract);
265
 
266
+ $this->assertFileExists($extract . '/tüst.txt');
267
+ $this->assertFileExists($extract . '/snowy☃.txt');
268
 
269
  $this->nativeCheck($archive);
270
  $this->native7ZipCheck($archive);
274
  }
275
 
276
  /**
277
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
278
  */
279
  public function testAddDataWithArchiveStreamIsClosed()
280
  {
294
  $zip->create($archive);
295
  $zip->close();
296
 
297
+ $zip->close();
298
+ $this->assertTrue(true); // succeed if no exception, yet
299
  }
300
 
301
  public function testSaveArchiveFile()
306
  $zip->create();
307
  $zip->addFile("$dir/zero.txt", 'zero.txt');
308
 
309
+ $zip->save(vfsStream::url('home_root_path/archive_file'));
310
+ $this->assertTrue(true); // succeed if no exception, yet
311
  }
312
 
313
  /**
314
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
315
  */
316
  public function testSaveWithInvalidFilePath()
317
  {
372
  */
373
  public function testZipExtract()
374
  {
375
+ $dir = dirname(__FILE__) . '/zip';
376
+ $out = sys_get_temp_dir() . '/dwziptest' . md5(time());
377
 
378
+ $zip = new Zip();
379
  $file = "$dir/test.zip";
380
 
381
  $zip->open($file);
383
 
384
  clearstatcache();
385
 
386
+ $this->assertFileExists($out . '/zip/testdata1.txt', "Extracted $file");
387
+ $this->assertEquals(13, filesize($out . '/zip/testdata1.txt'), "Extracted $file");
388
 
389
+ $this->assertFileExists($out . '/zip/foobar/testdata2.txt', "Extracted $file");
390
+ $this->assertEquals(13, filesize($out . '/zip/foobar/testdata2.txt'), "Extracted $file");
391
 
392
+ $this->assertFileExists($out . '/zip/compressable.txt', "Extracted $file");
393
+ $this->assertEquals(1836, filesize($out . '/zip/compressable.txt'), "Extracted $file");
394
+ $this->assertFileNotExists($out . '/zip/compressable.txt.gz', "Extracted $file");
395
 
396
  self::RDelete($out);
397
  }
398
 
399
  /**
400
+ * @expectedException \splitbrain\PHPArchive\ArchiveIOException
401
  */
402
  public function testZipExtractWithArchiveStreamIsClosed()
403
  {
404
+ $dir = dirname(__FILE__) . '/zip';
405
+ $out = sys_get_temp_dir() . '/dwziptest' . md5(time());
406
 
407
+ $zip = new Zip();
408
  $file = "$dir/test.zip";
409
 
410
  $zip->open($file);
418
  */
419
  public function testCompStripExtract()
420
  {
421
+ $dir = dirname(__FILE__) . '/zip';
422
+ $out = sys_get_temp_dir() . '/dwziptest' . md5(time());
423
 
424
+ $zip = new Zip();
425
  $file = "$dir/test.zip";
426
 
427
  $zip->open($file);
429
 
430
  clearstatcache();
431
 
432
+ $this->assertFileExists($out . '/testdata1.txt', "Extracted $file");
433
+ $this->assertEquals(13, filesize($out . '/testdata1.txt'), "Extracted $file");
434
 
435
+ $this->assertFileExists($out . '/foobar/testdata2.txt', "Extracted $file");
436
+ $this->assertEquals(13, filesize($out . '/foobar/testdata2.txt'), "Extracted $file");
437
 
438
  self::RDelete($out);
439
  }
444
  */
445
  public function testPrefixStripExtract()
446
  {
447
+ $dir = dirname(__FILE__) . '/zip';
448
+ $out = sys_get_temp_dir() . '/dwziptest' . md5(time());
449
 
450
+ $zip = new Zip();
451
  $file = "$dir/test.zip";
452
 
453
  $zip->open($file);
455
 
456
  clearstatcache();
457
 
458
+ $this->assertFileExists($out . '/zip/testdata1.txt', "Extracted $file");
459
+ $this->assertEquals(13, filesize($out . '/zip/testdata1.txt'), "Extracted $file");
460
 
461
+ $this->assertFileExists($out . '/testdata2.txt', "Extracted $file");
462
+ $this->assertEquals(13, filesize($out . '/testdata2.txt'), "Extracted $file");
463
 
464
  self::RDelete($out);
465
  }
470
  */
471
  public function testIncludeExtract()
472
  {
473
+ $dir = dirname(__FILE__) . '/zip';
474
+ $out = sys_get_temp_dir() . '/dwziptest' . md5(time());
475
 
476
+ $zip = new Zip();
477
  $file = "$dir/test.zip";
478
 
479
  $zip->open($file);
481
 
482
  clearstatcache();
483
 
484
+ $this->assertFileNotExists($out . '/zip/testdata1.txt', "Extracted $file");
485
 
486
+ $this->assertFileExists($out . '/zip/foobar/testdata2.txt', "Extracted $file");
487
+ $this->assertEquals(13, filesize($out . '/zip/foobar/testdata2.txt'), "Extracted $file");
488
 
489
  self::RDelete($out);
490
  }
495
  */
496
  public function testExcludeExtract()
497
  {
498
+ $dir = dirname(__FILE__) . '/zip';
499
+ $out = sys_get_temp_dir() . '/dwziptest' . md5(time());
500
 
501
+ $zip = new Zip();
502
  $file = "$dir/test.zip";
503
 
504
  $zip->open($file);
506
 
507
  clearstatcache();
508
 
509
+ $this->assertFileExists($out . '/zip/testdata1.txt', "Extracted $file");
510
+ $this->assertEquals(13, filesize($out . '/zip/testdata1.txt'), "Extracted $file");
511
 
512
+ $this->assertFileNotExists($out . '/zip/foobar/testdata2.txt', "Extracted $file");
513
 
514
  self::RDelete($out);
515
  }
540
  $this->assertFileExists("$out/täst.txt");
541
  }
542
 
 
543
  /**
544
  * recursive rmdir()/unlink()
545
  *
vendor/splitbrain/php-archive/tests/tar.test.php DELETED
@@ -1,617 +0,0 @@
1
- <?php
2
-
3
- use splitbrain\PHPArchive\Tar;
4
-
5
- class Tar_TestCase extends PHPUnit_Framework_TestCase
6
- {
7
- /**
8
- * file extensions that several tests use
9
- */
10
- protected $extensions = array('tar');
11
-
12
- public function setUp()
13
- {
14
- parent::setUp();
15
- if (extension_loaded('zlib')) {
16
- $this->extensions[] = 'tgz';
17
- $this->extensions[] = 'tar.gz';
18
- }
19
- if (extension_loaded('bz2')) {
20
- $this->extensions[] = 'tbz';
21
- $this->extensions[] = 'tar.bz2';
22
- }
23
- }
24
-
25
- /*
26
- * dependency for tests needing zlib extension to pass
27
- */
28
- public function test_ext_zlib()
29
- {
30
- if (!extension_loaded('zlib')) {
31
- $this->markTestSkipped('skipping all zlib tests. Need zlib extension');
32
- }
33
- }
34
-
35
- /*
36
- * dependency for tests needing zlib extension to pass
37
- */
38
- public function test_ext_bz2()
39
- {
40
- if (!extension_loaded('bz2')) {
41
- $this->markTestSkipped('skipping all bzip2 tests. Need bz2 extension');
42
- }
43
- }
44
-
45
- /**
46
- * @expectedException splitbrain\PHPArchive\ArchiveIOException
47
- */
48
- public function test_missing()
49
- {
50
- $tar = new Tar();
51
- $tar->open('nope.tar');
52
- }
53
-
54
- /**
55
- * simple test that checks that the given filenames and contents can be grepped from
56
- * the uncompressed tar stream
57
- *
58
- * No check for format correctness
59
- */
60
- public function test_createdynamic()
61
- {
62
- $tar = new Tar();
63
-
64
- $dir = dirname(__FILE__) . '/tar';
65
- $tdir = ltrim($dir, '/');
66
-
67
- $tar->create();
68
- $tar->AddFile("$dir/testdata1.txt");
69
- $tar->AddFile("$dir/foobar/testdata2.txt", 'noway/testdata2.txt');
70
- $tar->addData('another/testdata3.txt', 'testcontent3');
71
-
72
- $data = $tar->getArchive();
73
-
74
- $this->assertTrue(strpos($data, 'testcontent1') !== false, 'Content in TAR');
75
- $this->assertTrue(strpos($data, 'testcontent2') !== false, 'Content in TAR');
76
- $this->assertTrue(strpos($data, 'testcontent3') !== false, 'Content in TAR');
77
-
78
- // fullpath might be too long to be stored as full path FS#2802
79
- $this->assertTrue(strpos($data, "$tdir") !== false, 'Path in TAR');
80
- $this->assertTrue(strpos($data, "testdata1.txt") !== false, 'File in TAR');
81
-
82
- $this->assertTrue(strpos($data, 'noway/testdata2.txt') !== false, 'Path in TAR');
83
- $this->assertTrue(strpos($data, 'another/testdata3.txt') !== false, 'Path in TAR');
84
-
85
- // fullpath might be too long to be stored as full path FS#2802
86
- $this->assertTrue(strpos($data, "$tdir/foobar") === false, 'Path not in TAR');
87
- $this->assertTrue(strpos($data, "foobar.txt") === false, 'File not in TAR');
88
-
89
- $this->assertTrue(strpos($data, "foobar") === false, 'Path not in TAR');
90
- }
91
-
92
- /**
93
- * simple test that checks that the given filenames and contents can be grepped from the
94
- * uncompressed tar file
95
- *
96
- * No check for format correctness
97
- */
98
- public function test_createfile()
99
- {
100
- $tar = new Tar();
101
-
102
- $dir = dirname(__FILE__) . '/tar';
103
- $tdir = ltrim($dir, '/');
104
- $tmp = tempnam(sys_get_temp_dir(), 'dwtartest');
105
-
106
- $tar->create($tmp);
107
- $tar->AddFile("$dir/testdata1.txt");
108
- $tar->AddFile("$dir/foobar/testdata2.txt", 'noway/testdata2.txt');
109
- $tar->addData('another/testdata3.txt', 'testcontent3');
110
- $tar->close();
111
-
112
- $this->assertTrue(filesize($tmp) > 30); //arbitrary non-zero number
113
- $data = file_get_contents($tmp);
114
-
115
- $this->assertTrue(strpos($data, 'testcontent1') !== false, 'Content in TAR');
116
- $this->assertTrue(strpos($data, 'testcontent2') !== false, 'Content in TAR');
117
- $this->assertTrue(strpos($data, 'testcontent3') !== false, 'Content in TAR');
118
-
119
- // fullpath might be too long to be stored as full path FS#2802
120
- $this->assertTrue(strpos($data, "$tdir") !== false, "Path in TAR '$tdir'");
121
- $this->assertTrue(strpos($data, "testdata1.txt") !== false, 'File in TAR');
122
-
123
- $this->assertTrue(strpos($data, 'noway/testdata2.txt') !== false, 'Path in TAR');
124
- $this->assertTrue(strpos($data, 'another/testdata3.txt') !== false, 'Path in TAR');
125
-
126
- // fullpath might be too long to be stored as full path FS#2802
127
- $this->assertTrue(strpos($data, "$tdir/foobar") === false, 'Path not in TAR');
128
- $this->assertTrue(strpos($data, "foobar.txt") === false, 'File not in TAR');
129
-
130
- $this->assertTrue(strpos($data, "foobar") === false, 'Path not in TAR');
131
-
132
- @unlink($tmp);
133
- }
134
-
135
- /**
136
- * List the contents of the prebuilt TAR files
137
- */
138
- public function test_tarcontent()
139
- {
140
- $dir = dirname(__FILE__) . '/tar';
141
-
142
- foreach ($this->extensions as $ext) {
143
- $tar = new Tar();
144
- $file = "$dir/test.$ext";
145
-
146
- $tar->open($file);
147
- $content = $tar->contents();
148
-
149
- $this->assertCount(4, $content, "Contents of $file");
150
- $this->assertEquals('tar/testdata1.txt', $content[1]->getPath(), "Contents of $file");
151
- $this->assertEquals(13, $content[1]->getSize(), "Contents of $file");
152
-
153
- $this->assertEquals('tar/foobar/testdata2.txt', $content[3]->getPath(), "Contents of $file");
154
- $this->assertEquals(13, $content[3]->getSize(), "Contents of $file");
155
- }
156
- }
157
-
158
- /**
159
- * Create an archive and unpack it again
160
- */
161
- public function test_dogfood()
162
- {
163
- foreach ($this->extensions as $ext) {
164
- $input = glob(dirname(__FILE__) . '/../src/*');
165
- $archive = sys_get_temp_dir() . '/dwtartest' . md5(time()) . '.' . $ext;
166
- $extract = sys_get_temp_dir() . '/dwtartest' . md5(time() + 1);
167
-
168
- $tar = new Tar();
169
- $tar->create($archive);
170
- foreach ($input as $path) {
171
- $file = basename($path);
172
- $tar->addFile($path, $file);
173
- }
174
- $tar->close();
175
- $this->assertFileExists($archive);
176
-
177
- $tar = new Tar();
178
- $tar->open($archive);
179
- $tar->extract($extract, '', '/FileInfo\\.php/', '/.*\\.php/');
180
-
181
- $this->assertFileExists("$extract/Tar.php");
182
- $this->assertFileExists("$extract/Zip.php");
183
- $this->assertFileNotExists("$extract/FileInfo.php");
184
-
185
- $this->nativeCheck($archive, $ext);
186
-
187
- self::rdelete($extract);
188
- unlink($archive);
189
- }
190
- }
191
-
192
- /**
193
- * Test the given archive with a native tar installation (if available)
194
- *
195
- * @param $archive
196
- * @param $ext
197
- */
198
- protected function nativeCheck($archive, $ext)
199
- {
200
- if (!is_executable('/usr/bin/tar')) {
201
- return;
202
- }
203
-
204
- $switch = array(
205
- 'tar' => '-tf',
206
- 'tgz' => '-tzf',
207
- 'tar.gz' => '-tzf',
208
- 'tbz' => '-tjf',
209
- 'tar.bz2' => '-tjf',
210
- );
211
- $arg = $switch[$ext];
212
- $archive = escapeshellarg($archive);
213
-
214
- $return = 0;
215
- $output = array();
216
- $ok = exec("/usr/bin/tar $arg $archive 2>&1 >/dev/null", $output, $return);
217
- $output = join("\n", $output);
218
-
219
- $this->assertNotFalse($ok, "native tar execution for $archive failed:\n$output");
220
- $this->assertSame(0, $return, "native tar execution for $archive had non-zero exit code $return:\n$output");
221
- $this->assertSame('', $output, "native tar execution for $archive had non-empty output:\n$output");
222
- }
223
-
224
- /**
225
- * Extract the prebuilt tar files
226
- */
227
- public function test_tarextract()
228
- {
229
- $dir = dirname(__FILE__) . '/tar';
230
- $out = sys_get_temp_dir() . '/dwtartest' . md5(time());
231
-
232
- foreach ($this->extensions as $ext) {
233
- $tar = new Tar();
234
- $file = "$dir/test.$ext";
235
-
236
- $tar->open($file);
237
- $tar->extract($out);
238
-
239
- clearstatcache();
240
-
241
- $this->assertFileExists($out . '/tar/testdata1.txt', "Extracted $file");
242
- $this->assertEquals(13, filesize($out . '/tar/testdata1.txt'), "Extracted $file");
243
-
244
- $this->assertFileExists($out . '/tar/foobar/testdata2.txt', "Extracted $file");
245
- $this->assertEquals(13, filesize($out . '/tar/foobar/testdata2.txt'), "Extracted $file");
246
-
247
- self::rdelete($out);
248
- }
249
- }
250
-
251
- /**
252
- * Extract the prebuilt tar files with component stripping
253
- */
254
- public function test_compstripextract()
255
- {
256
- $dir = dirname(__FILE__) . '/tar';
257
- $out = sys_get_temp_dir() . '/dwtartest' . md5(time());
258
-
259
- foreach ($this->extensions as $ext) {
260
- $tar = new Tar();
261
- $file = "$dir/test.$ext";
262
-
263
- $tar->open($file);
264
- $tar->extract($out, 1);
265
-
266
- clearstatcache();
267
-
268
- $this->assertFileExists($out . '/testdata1.txt', "Extracted $file");
269
- $this->assertEquals(13, filesize($out . '/testdata1.txt'), "Extracted $file");
270
-
271
- $this->assertFileExists($out . '/foobar/testdata2.txt', "Extracted $file");
272
- $this->assertEquals(13, filesize($out . '/foobar/testdata2.txt'), "Extracted $file");
273
-
274
- self::rdelete($out);
275
- }
276
- }
277
-
278
- /**
279
- * Extract the prebuilt tar files with prefix stripping
280
- */
281
- public function test_prefixstripextract()
282
- {
283
- $dir = dirname(__FILE__) . '/tar';
284
- $out = sys_get_temp_dir() . '/dwtartest' . md5(time());
285
-
286
- foreach ($this->extensions as $ext) {
287
- $tar = new Tar();
288
- $file = "$dir/test.$ext";
289
-
290
- $tar->open($file);
291
- $tar->extract($out, 'tar/foobar/');
292
-
293
- clearstatcache();
294
-
295
- $this->assertFileExists($out . '/tar/testdata1.txt', "Extracted $file");
296
- $this->assertEquals(13, filesize($out . '/tar/testdata1.txt'), "Extracted $file");
297
-
298
- $this->assertFileExists($out . '/testdata2.txt', "Extracted $file");
299
- $this->assertEquals(13, filesize($out . '/testdata2.txt'), "Extracted $file");
300
-
301
- self::rdelete($out);
302
- }
303
- }
304
-
305
- /**
306
- * Extract the prebuilt tar files with include regex
307
- */
308
- public function test_includeextract()
309
- {
310
- $dir = dirname(__FILE__) . '/tar';
311
- $out = sys_get_temp_dir() . '/dwtartest' . md5(time());
312
-
313
- foreach ($this->extensions as $ext) {
314
- $tar = new Tar();
315
- $file = "$dir/test.$ext";
316
-
317
- $tar->open($file);
318
- $tar->extract($out, '', '', '/\/foobar\//');
319
-
320
- clearstatcache();
321
-
322
- $this->assertFileNotExists($out . '/tar/testdata1.txt', "Extracted $file");
323
-
324
- $this->assertFileExists($out . '/tar/foobar/testdata2.txt', "Extracted $file");
325
- $this->assertEquals(13, filesize($out . '/tar/foobar/testdata2.txt'), "Extracted $file");
326
-
327
- self::rdelete($out);
328
- }
329
- }
330
-
331
- /**
332
- * Extract the prebuilt tar files with exclude regex
333
- */
334
- public function test_excludeextract()
335
- {
336
- $dir = dirname(__FILE__) . '/tar';
337
- $out = sys_get_temp_dir() . '/dwtartest' . md5(time());
338
-
339
- foreach ($this->extensions as $ext) {
340
- $tar = new Tar();
341
- $file = "$dir/test.$ext";
342
-
343
- $tar->open($file);
344
- $tar->extract($out, '', '/\/foobar\//');
345
-
346
- clearstatcache();
347
-
348
- $this->assertFileExists($out . '/tar/testdata1.txt', "Extracted $file");
349
- $this->assertEquals(13, filesize($out . '/tar/testdata1.txt'), "Extracted $file");
350
-
351
- $this->assertFileNotExists($out . '/tar/foobar/testdata2.txt', "Extracted $file");
352
-
353
- self::rdelete($out);
354
- }
355
- }
356
-
357
- /**
358
- * Check the extension to compression guesser
359
- */
360
- public function test_filetype()
361
- {
362
- $tar = new Tar();
363
- $this->assertEquals(Tar::COMPRESS_NONE, $tar->filetype('foo'));
364
- $this->assertEquals(Tar::COMPRESS_GZIP, $tar->filetype('foo.tgz'));
365
- $this->assertEquals(Tar::COMPRESS_GZIP, $tar->filetype('foo.tGZ'));
366
- $this->assertEquals(Tar::COMPRESS_GZIP, $tar->filetype('foo.tar.GZ'));
367
- $this->assertEquals(Tar::COMPRESS_GZIP, $tar->filetype('foo.tar.gz'));
368
- $this->assertEquals(Tar::COMPRESS_BZIP, $tar->filetype('foo.tbz'));
369
- $this->assertEquals(Tar::COMPRESS_BZIP, $tar->filetype('foo.tBZ'));
370
- $this->assertEquals(Tar::COMPRESS_BZIP, $tar->filetype('foo.tar.BZ2'));
371
- $this->assertEquals(Tar::COMPRESS_BZIP, $tar->filetype('foo.tar.bz2'));
372
-
373
- $dir = dirname(__FILE__) . '/tar';
374
- $this->assertEquals(Tar::COMPRESS_NONE, $tar->filetype("$dir/test.tar"));
375
- $this->assertEquals(Tar::COMPRESS_GZIP, $tar->filetype("$dir/test.tgz"));
376
- $this->assertEquals(Tar::COMPRESS_BZIP, $tar->filetype("$dir/test.tbz"));
377
- $this->assertEquals(Tar::COMPRESS_NONE, $tar->filetype("$dir/test.tar.guess"));
378
- $this->assertEquals(Tar::COMPRESS_GZIP, $tar->filetype("$dir/test.tgz.guess"));
379
- $this->assertEquals(Tar::COMPRESS_BZIP, $tar->filetype("$dir/test.tbz.guess"));
380
- }
381
-
382
- /**
383
- * @depends test_ext_zlib
384
- */
385
- public function test_longpathextract()
386
- {
387
- $dir = dirname(__FILE__) . '/tar';
388
- $out = sys_get_temp_dir() . '/dwtartest' . md5(time());
389
-
390
- foreach (array('ustar', 'gnu') as $format) {
391
- $tar = new Tar();
392
- $tar->open("$dir/longpath-$format.tgz");
393
- $tar->extract($out);
394
-
395
- $this->assertFileExists(
396
- $out . '/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/test.txt'
397
- );
398
-
399
- self::rdelete($out);
400
- }
401
- }
402
-
403
- // FS#1442
404
- public function test_createlongfile()
405
- {
406
- $tar = new Tar();
407
- $tar->setCompression(0);
408
- $tmp = tempnam(sys_get_temp_dir(), 'dwtartest');
409
-
410
- $path = '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt';
411
-
412
- $tar->create($tmp);
413
- $tar->addData($path, 'testcontent1');
414
- $tar->close();
415
-
416
- $this->assertTrue(filesize($tmp) > 30); //arbitrary non-zero number
417
- $data = file_get_contents($tmp);
418
-
419
- // We should find the complete path and a longlink entry
420
- $this->assertTrue(strpos($data, 'testcontent1') !== false, 'content in TAR');
421
- $this->assertTrue(strpos($data, $path) !== false, 'path in TAR');
422
- $this->assertTrue(strpos($data, '@LongLink') !== false, '@LongLink in TAR');
423
-
424
- @unlink($tmp);
425
- }
426
-
427
- public function test_createlongpathustar()
428
- {
429
- $tar = new Tar();
430
- $tar->setCompression(0);
431
- $tmp = tempnam(sys_get_temp_dir(), 'dwtartest');
432
-
433
- $path = '';
434
- for ($i = 0; $i < 11; $i++) {
435
- $path .= '1234567890/';
436
- }
437
- $path = rtrim($path, '/');
438
-
439
- $tar->create($tmp);
440
- $tar->addData("$path/test.txt", 'testcontent1');
441
- $tar->close();
442
-
443
- $this->assertTrue(filesize($tmp) > 30); //arbitrary non-zero number
444
- $data = file_get_contents($tmp);
445
-
446
- // We should find the path and filename separated, no longlink entry
447
- $this->assertTrue(strpos($data, 'testcontent1') !== false, 'content in TAR');
448
- $this->assertTrue(strpos($data, 'test.txt') !== false, 'filename in TAR');
449
- $this->assertTrue(strpos($data, $path) !== false, 'path in TAR');
450
- $this->assertFalse(strpos($data, "$path/test.txt") !== false, 'full filename in TAR');
451
- $this->assertFalse(strpos($data, '@LongLink') !== false, '@LongLink in TAR');
452
-
453
- @unlink($tmp);
454
- }
455
-
456
- public function test_createlongpathgnu()
457
- {
458
- $tar = new Tar();
459
- $tar->setCompression(0);
460
- $tmp = tempnam(sys_get_temp_dir(), 'dwtartest');
461
-
462
- $path = '';
463
- for ($i = 0; $i < 20; $i++) {
464
- $path .= '1234567890/';
465
- }
466
- $path = rtrim($path, '/');
467
-
468
- $tar->create($tmp);
469
- $tar->addData("$path/test.txt", 'testcontent1');
470
- $tar->close();
471
-
472
- $this->assertTrue(filesize($tmp) > 30); //arbitrary non-zero number
473
- $data = file_get_contents($tmp);
474
-
475
- // We should find the complete path/filename and a longlink entry
476
- $this->assertTrue(strpos($data, 'testcontent1') !== false, 'content in TAR');
477
- $this->assertTrue(strpos($data, 'test.txt') !== false, 'filename in TAR');
478
- $this->assertTrue(strpos($data, $path) !== false, 'path in TAR');
479
- $this->assertTrue(strpos($data, "$path/test.txt") !== false, 'full filename in TAR');
480
- $this->assertTrue(strpos($data, '@LongLink') !== false, '@LongLink in TAR');
481
-
482
- @unlink($tmp);
483
- }
484
-
485
- /**
486
- * Extract a tarbomomb
487
- * @depends test_ext_zlib
488
- */
489
- public function test_tarbomb()
490
- {
491
- $dir = dirname(__FILE__) . '/tar';
492
- $out = sys_get_temp_dir() . '/dwtartest' . md5(time());
493
-
494
- $tar = new Tar();
495
-
496
- $tar->open("$dir/tarbomb.tgz");
497
- $tar->extract($out);
498
-
499
- clearstatcache();
500
-
501
- $this->assertFileExists(
502
- $out . '/AAAAAAAAAAAAAAAAA/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.txt'
503
- );
504
-
505
- self::rdelete($out);
506
- }
507
-
508
- /**
509
- * A single zero file should be just a header block + the footer
510
- */
511
- public function test_zerofile()
512
- {
513
- $dir = dirname(__FILE__) . '/tar';
514
- $tar = new Tar();
515
- $tar->setCompression(0);
516
- $tar->create();
517
- $tar->addFile("$dir/zero.txt", 'zero.txt');
518
- $file = $tar->getArchive();
519
-
520
- $this->assertEquals(512 * 3, strlen($file)); // 1 header block + 2 footer blocks
521
- }
522
-
523
- public function test_zerodata()
524
- {
525
- $tar = new Tar();
526
- $tar->setCompression(0);
527
- $tar->create();
528
- $tar->addData('zero.txt', '');
529
- $file = $tar->getArchive();
530
-
531
- $this->assertEquals(512 * 3, strlen($file)); // 1 header block + 2 footer blocks
532
- }
533
-
534
- /**
535
- * A file of exactly one block should be just a header block + data block + the footer
536
- */
537
- public function test_blockfile()
538
- {
539
- $dir = dirname(__FILE__) . '/tar';
540
- $tar = new Tar();
541
- $tar->setCompression(0);
542
- $tar->create();
543
- $tar->addFile("$dir/block.txt", 'block.txt');
544
- $file = $tar->getArchive();
545
-
546
- $this->assertEquals(512 * 4, strlen($file)); // 1 header block + data block + 2 footer blocks
547
- }
548
-
549
- public function test_blockdata()
550
- {
551
- $tar = new Tar();
552
- $tar->setCompression(0);
553
- $tar->create();
554
- $tar->addData('block.txt', str_pad('', 512, 'x'));
555
- $file = $tar->getArchive();
556
-
557
- $this->assertEquals(512 * 4, strlen($file)); // 1 header block + data block + 2 footer blocks
558
- }
559
-
560
- /**
561
- * @depends test_ext_zlib
562
- */
563
- public function test_gzipisvalid()
564
- {
565
- foreach (['tgz', 'tar.gz'] as $ext) {
566
- $input = glob(dirname(__FILE__) . '/../src/*');
567
- $archive = sys_get_temp_dir() . '/dwtartest' . md5(time()) . '.' . $ext;
568
- $extract = sys_get_temp_dir() . '/dwtartest' . md5(time() + 1);
569
-
570
- $tar = new Tar();
571
- $tar->setCompression(9, Tar::COMPRESS_GZIP);
572
- $tar->create();
573
- foreach ($input as $path) {
574
- $file = basename($path);
575
- $tar->addFile($path, $file);
576
- }
577
- $tar->save($archive);
578
- $this->assertFileExists($archive);
579
-
580
- try {
581
- $phar = new PharData($archive);
582
- $phar->extractTo($extract);
583
- } catch (\Exception $e) {};
584
-
585
- $this->assertFileExists("$extract/Tar.php");
586
- $this->assertFileExists("$extract/Zip.php");
587
-
588
- $this->nativeCheck($archive, $ext);
589
-
590
- self::rdelete($extract);
591
- unlink($archive);
592
- }
593
- }
594
-
595
- /**
596
- * recursive rmdir()/unlink()
597
- *
598
- * @static
599
- * @param $target string
600
- */
601
- public static function rdelete($target)
602
- {
603
- if (!is_dir($target)) {
604
- unlink($target);
605
- } else {
606
- $dh = dir($target);
607
- while (false !== ($entry = $dh->read())) {
608
- if ($entry == '.' || $entry == '..') {
609
- continue;
610
- }
611
- self::rdelete("$target/$entry");
612
- }
613
- $dh->close();
614
- rmdir($target);
615
- }
616
- }
617
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
xcloner.php CHANGED
@@ -7,17 +7,17 @@
7
  * registers the activation and deactivation functions, and defines a function
8
  * that starts the plugin.
9
  *
10
- * @link http://www.thinkovi.com
11
  * @since 1.0.0
12
  * @package Xcloner
13
  *
14
  * @wordpress-plugin
15
  * Plugin Name: XCloner - Site Backup and Restore
16
- * Plugin URI: http://www.xcloner.com
17
  * Description: XCloner is a tool that will help you manage your website backups, generate/restore/move so your website will be always secured! With XCloner you will be able to clone your site to any other location with just a few clicks, as well as transfer the backup archives to remote FTP, SFTP, DropBox, Amazon S3, Google Drive, WebDAV, Backblaze, Azure accounts.
18
- * Version: 4.1.2
19
- * Author: Liuta Ovidiu
20
- * Author URI: http://www.thinkovi.com
21
  * License: GPL-2.0+
22
  * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
23
  * Text Domain: xcloner-backup-and-restore
7
  * registers the activation and deactivation functions, and defines a function
8
  * that starts the plugin.
9
  *
10
+ * @link https://watchful.net
11
  * @since 1.0.0
12
  * @package Xcloner
13
  *
14
  * @wordpress-plugin
15
  * Plugin Name: XCloner - Site Backup and Restore
16
+ * Plugin URI: https://xcloner.com/
17
  * Description: XCloner is a tool that will help you manage your website backups, generate/restore/move so your website will be always secured! With XCloner you will be able to clone your site to any other location with just a few clicks, as well as transfer the backup archives to remote FTP, SFTP, DropBox, Amazon S3, Google Drive, WebDAV, Backblaze, Azure accounts.
18
+ * Version: 4.1.4
19
+ * Author: watchful
20
+ * Author URI: https://watchful.net/
21
  * License: GPL-2.0+
22
  * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
23
  * Text Domain: xcloner-backup-and-restore