PageRenderTime 67ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-content/plugins/updraftplus/updraftplus.php

https://github.com/tjworks/mongoing
PHP | 2610 lines | 1792 code | 376 blank | 442 comment | 666 complexity | e74b0c70da6434e710abff8c9bd3b97a MD5 | raw file
Possible License(s): GPL-2.0, GPL-3.0, Apache-2.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. Plugin Name: UpdraftPlus - Backup/Restore
  4. Plugin URI: http://updraftplus.com
  5. Description: Backup and restore: take backups locally, or backup to Amazon S3, Dropbox, Google Drive, Rackspace, (S)FTP, WebDAV & email, on automatic schedules.
  6. Author: UpdraftPlus.Com, DavidAnderson
  7. Version: 1.9.13
  8. Donate link: http://david.dw-perspective.org.uk/donate
  9. License: GPLv3 or later
  10. Text Domain: updraftplus
  11. Domain Path: /languages
  12. Author URI: http://updraftplus.com
  13. */
  14. /*
  15. TODO - some of these are out of date/done, needs pruning
  16. // On free version, add note to restore page/to "delete-old-dirs" section
  17. // Make SFTP chunked (there is a new stream wrapper)
  18. // Store/show current Dropbox account
  19. // On plugins restore, don't let UD over-write itself - because this usually means a down-grade. Since upgrades are db-compatible, there's no reason to downgrade.
  20. // Renewal links should redirect to login and redirect to relevant page after
  21. // Alert user if they enter http(s):(etc) as their Dropbox path - seen one user do it
  22. // Schedule a task to report on failure
  23. // Copy.Com, Box
  24. // Switch 'Backup Now' to call the WP action via AJAX instead of via Cron - then test on hosts who deny all cron (e.g. Heart)
  25. // Get something to parse the 'Backups in progress' data, and if the 'next resumption' is far negative, and if also cron jobs appear to be not running, then call the action directly.
  26. // If ionice is available, then use it to limit I/O usage
  27. // Check the timestamps used in filenames - they should be UTC
  28. // Get user to confirm if they check both the search/replace and wp-config boxes
  29. // Tweak the display so that users seeing resumption messages don't think it's stuck
  30. // A search/replace console without needing to restore
  31. // On restore, check for some 'standard' PHP modules (prevents support requests related to them) -e.g. GD, Curl
  32. // Recognise known huge non-core tables on restore, and postpone them to the end (AJAX method?)
  33. // Add a cart notice if people have DBSF=quantity1
  34. // Pre-restore actually unpack the zips if they are not insanely big (to prevent the restore crashing at this stage if there's a problem)
  35. // Include in email report the list of "more" directories: http://updraftplus.com/forums/support-forum-group1/paid-support-forum-forum2/wordpress-multi-sites-thread121/
  36. // Integrate jstree for a nice files-chooser; use https://wordpress.org/plugins/dropbox-photo-sideloader/ to see how it's done
  37. // Verify that attempting to bring back a MS backup on a non-MS install warns the user
  38. // Pre-schedule resumptions that we know will be scheduled later
  39. // Change add-ons screen, to be less confusing for people who haven't yet updated but have connected
  40. // Change migrate window: 1) Retain link to article 2) Have selector to choose which backup set to migrate - or a fresh one 3) Have option for FTP/SFTP/SCP despatch 4) Have big "Go" button. Have some indication of what happens next. Test the login first. Have the remote site auto-scan its directory + pick up new sets. Have a way of querying the remote site for its UD-dir. Have a way of saving the settings as a 'profile'. Or just save the last set of settings (since mostly will be just one place to send to). Implement an HTTP/JSON method for sending files too.
  41. // Post restore, do an AJAX get for the site; if this results in a 500, then auto-turn-on WP_DEBUG
  42. // Place in maintenance mode during restore - ?
  43. // Test Azure: https://blogs.technet.com/b/blainbar/archive/2013/08/07/article-create-a-wordpress-site-using-windows-azure-read-on.aspx?Redirected=true
  44. // Seen during autobackup on 1.8.2: Warning: Invalid argument supplied for foreach() in /home/infinite/public_html/new/wp-content/plugins/updraftplus/updraftplus.php on line 1652
  45. // Add some kind of automated scan for post content (e.g. images) that has the same URL base, but is not part of WP. There's an example of such a site in tmp-rich.
  46. // Free/premium comparison page
  47. // Complete the tweak to bring the delete-old-dirs within a dialog (just needed to deal wtih case of needing credentials more elegantly).
  48. // Add note to support page requesting that non-English be translated
  49. // More locking: lock the resumptions too (will need to manage keys to make sure junk data is not left behind)
  50. // See: ftp-logins.log - would help if we retry FTP logins after 10 second delay (not on testing), to lessen chances of 'too many users - try again later' being terminal. Also, can we log the login error?
  51. // Deal with missing plugins/themes/uploads directory when installing
  52. // Bring down interval if we are already in upload time (since zip delays are no longer possible). See: options-general-11-23.txt
  53. // Add FAQ - can I get it to save automatically to my computer?
  54. // Pruner assumes storage is same as current - ?
  55. // Include blog feed in basic email report
  56. // Detect, and show prominent error in admin area, if the slug is not updraftplus/updraftplus.php (one Mac user in the wild managed to upload as updraftplus-2).
  57. // Pre-schedule future resumptions that we know will be scheduled; helps deal with WP's dodgy scheduler skipping some. (Then need to un-schedule if job finishes).
  58. // Dates in the progress box are apparently untranslated
  59. // Add-on descriptions are not internationalised
  60. // Nicer in-dashboard log: show log + option to download; also (if 'reporting' add-on available) show the HTML report from that
  61. // Take a look at logfile-to-examine.txt (stored), and the pattern of detection of zipfile contents
  62. // http://www.phpclasses.org/package/8269-PHP-Send-MySQL-database-backup-files-to-Ubuntu-One.html
  63. // Put the -old directories in updraft_dir instead of present location. Prevents file perms issues, and also will be automatically excluded from backups.
  64. // Test restores via cloud service for small $??? (Relevant: http://browshot.com/features) (per-day? per-install?)
  65. // Warn/prevent if trying to migrate between sub-domain/sub-folder based multisites
  66. // Don't perform pruning when doing auto-backup?
  67. // Post-migrate, notify the user if on Apache but without mod_rewrite (has been seen in the wild)
  68. // Pre-check the search/replace box if migration detected
  69. // Can some tables be omitted from the search/replace on a migrate? i.e. Special knowledge?
  70. // Put a 'what do I get if I upgrade?' link into the mix
  71. // Add to admin bar (and make it something that can be turned off)
  72. // If migrated database from somewhere else, then add note about revising UD settings
  73. // Strategy for what to do if the updraft_dir contains untracked backups. Automatically rescan?
  74. // MySQL manual: See Section 8.2.2.1, Speed of INSERT Statements.
  75. // Exempt UD itself from a plugins restore? (will options be out-of-sync? exempt options too?)
  76. // Post restore/migrate, check updraft_dir, and reset if non-existent
  77. // Auto-empty caches post-restore/post-migration (prevent support requests from people with state/wrong cacheing data)
  78. // Show 'Migrate' instead of 'Restore' on the button if relevant
  79. // Test with: http://wordpress.org/plugins/wp-db-driver/
  80. // Backup notes
  81. // Automatically re-count folder usage after doing a delete
  82. // Switch zip engines earlier if no progress - see log.cfd793337563_hostingfails.txt
  83. // The delete-em at the end needs to be made resumable. And to only run on last run-through (i.e. no errors, or no resumption)
  84. // Incremental - can leverage some of the multi-zip work???
  85. // Put in a help link to explain what WordPress core (including any additions to your WordPress root directory) does (was asked for support)
  86. // More databases
  87. // Multiple files in more-files
  88. // On multisite, the settings should be in the network panel. Connection settings need migrating into site options.
  89. // On restore, raise a warning for ginormous zips
  90. // Detect double-compressed files when they are uploaded (need a way to detect gz compression in general)
  91. // Log migrations/restores, and have an option for auto-emailing the log
  92. # Email backup method should be able to force split limit down to something manageable - or at least, should make the option display. (Put it in email class. Tweak the storage dropdown to not hide stuff also in expert class if expert is shown).
  93. // What happens if you restore with a database that then changes the setting for updraft_dir ? Should be safe, as the setting is cached during a run: double-check.
  94. // Multi-site manager at updraftplus.com
  95. // Import/slurp backups from other sites. See: http://www.skyverge.com/blog/extending-the-wordpress-xml-rpc-api/
  96. // More sophisticated options for retaining/deleting (e.g. 4/day for X days, then 7/week for Z weeks, then 1/month for Y months)
  97. // Unpack zips via AJAX? Do bit-by-bit to allow enormous opens a better chance? (have a huge one in Dropbox)
  98. // Put in a maintenance-mode detector
  99. // Add update warning if they've got an add-on but not connected account
  100. // Detect CloudFlare output in attempts to connect - detecting cloudflare.com should be sufficient
  101. // Bring multisite shop page up to date
  102. // Re-do pricing + support packages
  103. // More files: back up multiple directories, not just one
  104. // Give a help page to go with the message: A zip error occurred - check your log for more details (reduce support requests)
  105. // Exclude .git and .svn by default from wpcore
  106. // Add option to add, not just replace entities on restore/migrate
  107. // Add warning to backup run at beginning if -old dirs exist
  108. // Auto-alert if disk usage passes user-defined threshold / or an automatically computed one. Auto-alert if more backups are known than should be (usually a sign of incompleteness). Actually should just delete unknown backups over a certain age.
  109. // Generic S3 provider: add page to site. S3-compatible storage providers: http://www.dragondisk.com/s3-storage-providers.html
  110. // Importer - import backup sets from another WP site directly via HTTP
  111. // Option to create new user for self post-restore
  112. // Auto-disable certain cacheing/minifying plugins post-restore
  113. // Add note post-DB backup: you will need to log in using details from newly-imported DB
  114. // Make search+replace two-pass to deal with moving between exotic non-default moved-directory setups
  115. // Get link - http://www.rackspace.com/knowledge_center/article/how-to-use-updraftplus-to-back-up-cloud-sites-to-cloud-files
  116. // 'Delete from your webserver' should trigger a rescan if the backup was local-only
  117. // Option for additive restores - i.e. add content (themes, plugins,...) instead of replacing
  118. // Testing framework - automated testing of all file upload / download / deletion methods
  119. // Ginormous tables - need to make sure we "touch" the being-written-out-file (and double-check that we check for that) every 15 seconds - https://friendpaste.com/697eKEcWib01o6zT1foFIn
  120. // With ginormous tables, log how many times they've been attempted: after 3rd attempt, log a warning and move on. But first, batch ginormous tables (resumable)
  121. // Import single site into a multisite: http://codex.wordpress.org/Migrating_Multiple_Blogs_into_WordPress_3.0_Multisite, http://wordpress.org/support/topic/single-sites-to-multisite?replies=5, http://wpmu.org/import-export-wordpress-sites-multisite/
  122. // Selective restores - some resources
  123. // When you migrate/restore, if there is a .htaccess, warn/give option about it.
  124. // 'Show log' should be done in a nice pop-out, with a button to download the raw
  125. // delete_old_dirs() needs to use WP_Filesystem in a more user-friendly way when errors occur
  126. // Bulk download of entire set at once (not have to click 7 times).
  127. // Restoration should also clear all common cache locations (or just not back them up)
  128. // Deal with gigantic database tables - e.g. those over a million rows on cheap hosting.
  129. // When restoring core, need an option to retain database settings / exclude wp-config.php
  130. // If migrating, warn about consequences of over-writing wp-config.php
  131. // Produce a command-line version of the restorer (so that people with shell access are immune from server-enforced timeouts)
  132. // Restorations should be logged also
  133. // Migrator - list+download from remote, kick-off backup remotely
  134. // Search for other TODO-s in the code
  135. // Opt-in non-personal stats + link to aggregated results
  136. // Stand-alone installer - take a look at this: http://wordpress.org/extend/plugins/duplicator/screenshots/
  137. // More DB add-on (other non-WP tables; even other databases)
  138. // Unlimited customers should be auto-emailed each time they add a site (security)
  139. // Update all-features page at updraftplus.com (not updated after 1.5.5)
  140. // Save database encryption key inside backup history on per-db basis, so that if it changes we can still decrypt
  141. // AJAX-ify restoration
  142. // Warn Premium users before de-activating not to update whilst inactive
  143. // Ability to re-scan existing cloud storage
  144. // Dropbox uses one mcrypt function - port to phpseclib for more portability
  145. // Store meta-data on which version of UD the backup was made with (will help if we ever introduce quirks that need ironing)
  146. // Send the user an email upon their first backup with tips on what to do (e.g. support/improve) (include legacy check to not bug existing users)
  147. // Rackspace folders
  148. //Do an automated test periodically for the success of loop-back connections
  149. //When a manual backup is run, use a timer to update the 'Download backups and logs' section, just like 'Last finished backup run'. Beware of over-writing anything that's in there from a resumable downloader.
  150. //Change DB encryption to not require whole gzip in memory (twice) http://www.frostjedi.com/phpbb3/viewtopic.php?f=46&t=168508&p=391881&e=391881
  151. //Add YouSendIt/Hightail, Copy.Com, Box.Net, SugarSync, Me.Ga support??
  152. //Make it easier to find add-ons
  153. // On restore, move in data, not the whole directory (gives more flexibility on file permissions)
  154. // Move the inclusion, cloud and retention data into the backup job (i.e. don't read current config, make it an attribute of each job). In fact, everything should be. So audit all code for where get_option is called inside a backup run: it shouldn't happen.
  155. // Should we resume if the only errors were upon deletion (i.e. the backup itself was fine?) Presently we do, but it displays errors for the user to confuse them. Perhaps better to make pruning a separate scheuled task??
  156. // Create a "Want Support?" button/console, that leads them through what is needed, and performs some basic tests...
  157. // Add-on to check integrity of backups
  158. // Add-on to manage all your backups from a single dashboard
  159. // Provide backup/restoration for UpdraftPlus's settings, to allow 'bootstrap' on a fresh WP install - some kind of single-use code which a remote UpdraftPlus can use to authenticate
  160. // Multiple schedules
  161. // Allow connecting to remote storage, scanning + populating backup history from it
  162. // Multisite add-on should allow restoring of each blog individually
  163. // Remove the recurrence of admin notices when settings are saved due to _wp_referer
  164. // New sub-module to verify that the backups are there, independently of backup thread
  165. */
  166. /*
  167. Portions copyright 2011-14 David Anderson
  168. Portions copyright 2010 Paul Kehrer
  169. Other portions copyright as indicated authors in the relevant files
  170. This program is free software; you can redistribute it and/or modify
  171. it under the terms of the GNU General Public License as published by
  172. the Free Software Foundation; either version 3 of the License, or
  173. (at your option) any later version.
  174. This program is distributed in the hope that it will be useful,
  175. but WITHOUT ANY WARRANTY; without even the implied warranty of
  176. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  177. GNU General Public License for more details.
  178. You should have received a copy of the GNU General Public License
  179. along with this program; if not, write to the Free Software
  180. Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  181. */
  182. define('UPDRAFTPLUS_DIR', dirname(__FILE__));
  183. define('UPDRAFTPLUS_URL', plugins_url('', __FILE__));
  184. define('UPDRAFT_DEFAULT_OTHERS_EXCLUDE','upgrade,cache,updraft,backup*,*backups');
  185. define('UPDRAFT_DEFAULT_UPLOADS_EXCLUDE','backup*,*backups,backwpup*,wp-clone');
  186. # The following can go in your wp-config.php
  187. # Tables whose data can be safed without significant loss, if (and only if) the attempt to back them up fails (e.g. bwps_log, from WordPress Better Security, is log data; but individual entries can be huge and cause out-of-memory fatal errors on low-resource environments). Comma-separate the table names (without the WordPress table prefix).
  188. if (!defined('UPDRAFTPLUS_DATA_OPTIONAL_TABLES')) define('UPDRAFTPLUS_DATA_OPTIONAL_TABLES', 'bwps_log,statpress,slim_stats,redirection_logs,Counterize,Counterize_Referers,Counterize_UserAgents');
  189. if (!defined('UPDRAFTPLUS_ZIP_EXECUTABLE')) define('UPDRAFTPLUS_ZIP_EXECUTABLE', "/usr/bin/zip,/bin/zip,/usr/local/bin/zip,/usr/sfw/bin/zip,/usr/xdg4/bin/zip,/opt/bin/zip");
  190. if (!defined('UPDRAFTPLUS_MYSQLDUMP_EXECUTABLE')) define('UPDRAFTPLUS_MYSQLDUMP_EXECUTABLE', "/usr/bin/mysqldump,/bin/mysqldump,/usr/local/bin/mysqldump,/usr/sfw/bin/mysqldump,/usr/xdg4/bin/mysqldump,/opt/bin/mysqldump");
  191. # If any individual file size is greater than this, then a warning is given
  192. if (!defined('UPDRAFTPLUS_WARN_FILE_SIZE')) define('UPDRAFTPLUS_WARN_FILE_SIZE', 1024*1024*250);
  193. # On a test on a Pentium laptop, 100,000 rows needed ~ 1 minute to write out - so 150,000 is around the CPanel default of 90 seconds execution time.
  194. if (!defined('UPDRAFTPLUS_WARN_DB_ROWS')) define('UPDRAFTPLUS_WARN_DB_ROWS', 150000);
  195. # The smallest value (in megabytes) that the "split zip files at" setting is allowed to be set to
  196. if (!defined('UPDRAFTPLUS_SPLIT_MIN')) define('UPDRAFTPLUS_SPLIT_MIN', 25);
  197. # The maximum number of files to batch at one time when writing to the backup archive. You'd only be likely to want to raise (not lower) this.
  198. if (!defined('UPDRAFTPLUS_MAXBATCHFILES')) define('UPDRAFTPLUS_MAXBATCHFILES', 500);
  199. // Load add-ons and various files that may or may not be present, depending on where the plugin was distributed
  200. if (is_file(UPDRAFTPLUS_DIR.'/premium.php')) require_once(UPDRAFTPLUS_DIR.'/premium.php');
  201. if (is_file(UPDRAFTPLUS_DIR.'/autoload.php')) require_once(UPDRAFTPLUS_DIR.'/autoload.php');
  202. if (is_file(UPDRAFTPLUS_DIR.'/udaddons/updraftplus-addons.php')) include_once(UPDRAFTPLUS_DIR.'/udaddons/updraftplus-addons.php');
  203. $updraftplus_have_addons = 0;
  204. if (is_dir(UPDRAFTPLUS_DIR.'/addons') && $dir_handle = opendir(UPDRAFTPLUS_DIR.'/addons')) {
  205. while (false !== ($e = readdir($dir_handle))) {
  206. if (is_file(UPDRAFTPLUS_DIR.'/addons/'.$e) && preg_match('/\.php$/', $e)) {
  207. $header = file_get_contents(UPDRAFTPLUS_DIR.'/addons/'.$e, false, null, -1, 1024);
  208. $phprequires = (preg_match("/RequiresPHP: (\d[\d\.]+)/", $header, $matches)) ? $matches[1] : false;
  209. $phpinclude = (preg_match("/IncludePHP: (\S+)/", $header, $matches)) ? $matches[1] : false;
  210. if (false === $phprequires || version_compare(PHP_VERSION, $phprequires, '>=')) {
  211. $updraftplus_have_addons++;
  212. if ($phpinclude) require_once(UPDRAFTPLUS_DIR.'/'.$phpinclude);
  213. include_once(UPDRAFTPLUS_DIR.'/addons/'.$e);
  214. }
  215. }
  216. }
  217. @closedir($dir_handle);
  218. }
  219. $updraftplus = new UpdraftPlus();
  220. $updraftplus->have_addons = $updraftplus_have_addons;
  221. if (!$updraftplus->memory_check(192)) {
  222. // Experience appears to show that the memory limit is only likely to be hit (unless it is very low) by single files that are larger than available memory (when compressed)
  223. # Add sanity checks - found someone who'd set WP_MAX_MEMORY_LIMIT to 256K !
  224. if (!$updraftplus->memory_check($updraftplus->memory_check_current(WP_MAX_MEMORY_LIMIT))) {
  225. $new = absint($updraftplus->memory_check_current(WP_MAX_MEMORY_LIMIT));
  226. if ($new>32 && $new<100000) {
  227. @ini_set('memory_limit', $new.'M'); //up the memory limit to the maximum WordPress is allowing for large backup files
  228. }
  229. }
  230. }
  231. if (!class_exists('UpdraftPlus_Options')) require_once(UPDRAFTPLUS_DIR.'/options.php');
  232. class UpdraftPlus {
  233. public $version;
  234. public $plugin_title = 'UpdraftPlus Backup/Restore';
  235. // Choices will be shown in the admin menu in the order used here
  236. public $backup_methods = array(
  237. 's3' => 'Amazon S3',
  238. 'dropbox' => 'Dropbox',
  239. 'cloudfiles' => 'Rackspace Cloud Files',
  240. 'googledrive' => 'Google Drive',
  241. 'ftp' => 'FTP',
  242. 'sftp' => 'SFTP / SCP',
  243. 'webdav' => 'WebDAV',
  244. 'bitcasa' => 'Bitcasa',
  245. 's3generic' => 'S3-Compatible (Generic)',
  246. 'openstack' => 'OpenStack (Swift)',
  247. 'dreamobjects' => 'DreamObjects',
  248. 'email' => 'Email'
  249. );
  250. public $errors = array();
  251. public $nonce;
  252. public $logfile_name = "";
  253. public $logfile_handle = false;
  254. public $backup_time;
  255. public $job_time_ms;
  256. public $opened_log_time;
  257. private $backup_dir;
  258. private $jobdata;
  259. public $something_useful_happened = false;
  260. public $have_addons = false;
  261. // Used to schedule resumption attempts beyond the tenth, if needed
  262. public $current_resumption;
  263. public $newresumption_scheduled = false;
  264. public function __construct() {
  265. // Initialisation actions - takes place on plugin load
  266. if ($fp = fopen(__FILE__, 'r')) {
  267. $file_data = fread( $fp, 1024 );
  268. if (preg_match("/Version: ([\d\.]+)(\r|\n)/", $file_data, $matches)) {
  269. $this->version = $matches[1];
  270. }
  271. fclose($fp);
  272. }
  273. # Create admin page
  274. add_action('init', array($this, 'handle_url_actions'));
  275. // Run earlier than default - hence earlier than other components
  276. // admin_menu runs earlier, and we need it because options.php wants to use $updraftplus_admin before admin_init happens
  277. add_action(apply_filters('updraft_admin_menu_hook', 'admin_menu'), array($this, 'admin_menu'), 9);
  278. # Not a mistake: admin-ajax.php calls only admin_init and not admin_menu
  279. add_action('admin_init', array($this, 'admin_menu'), 9);
  280. add_action('updraft_backup', array($this, 'backup_files'));
  281. add_action('updraft_backup_database', array($this, 'backup_database'));
  282. add_action('updraft_backupnow_backup', array($this, 'backupnow_files'));
  283. add_action('updraft_backupnow_backup_database', array($this, 'backupnow_database'));
  284. add_action('updraft_backupnow_backup_all', array($this, 'backup_all'));
  285. # backup_all as an action is legacy (Oct 2013) - there may be some people who wrote cron scripts to use it
  286. add_action('updraft_backup_all', array($this, 'backup_all'));
  287. # this is our runs-after-backup event, whose purpose is to see if it succeeded or failed, and resume/mom-up etc.
  288. add_action('updraft_backup_resume', array($this, 'backup_resume'), 10, 3);
  289. # http://codex.wordpress.org/Plugin_API/Filter_Reference/cron_schedules. Raised priority because some plugins wrongly over-write all prior schedule changes (including BackupBuddy!)
  290. add_filter('cron_schedules', array($this, 'modify_cron_schedules'), 30);
  291. add_action('plugins_loaded', array($this, 'load_translations'));
  292. # Prevent iThemes Security from telling people that they have no backups (and advertising them another product on that basis!)
  293. add_filter('itsec_has_external_backup', array($this, 'return_true'), 999);
  294. add_filter('itsec_external_backup_link', array($this, 'itsec_external_backup_link'), 999);
  295. add_filter('itsec_scheduled_external_backup', array($this, 'itsec_scheduled_external_backup'), 999);
  296. register_deactivation_hook(__FILE__, array($this, 'deactivation'));
  297. }
  298. public function itsec_scheduled_external_backup($x) { return (!wp_next_scheduled('updraft_backup')) ? false : true; }
  299. public function itsec_external_backup_link($x) { return UpdraftPlus_Options::admin_page_url().'?page=updraftplus'; }
  300. public function return_true($x) { return true; }
  301. public function ensure_phpseclib($class = false, $class_path = false) {
  302. if ($class && class_exists($class)) return;
  303. if (false === strpos(get_include_path(), UPDRAFTPLUS_DIR.'/includes/phpseclib')) set_include_path(get_include_path().PATH_SEPARATOR.UPDRAFTPLUS_DIR.'/includes/phpseclib');
  304. if ($class_path) require_once(UPDRAFTPLUS_DIR.'/includes/phpseclib/'.$class_path.'.php');
  305. }
  306. // Returns the number of bytes free, if it can be detected; otherwise, false
  307. // Presently, we only detect CPanel. If you know of others, then feel free to contribute!
  308. public function get_hosting_disk_quota_free() {
  309. if (!@is_dir('/usr/local/cpanel') || $this->detect_safe_mode() || !function_exists('popen') || (!@is_executable('/usr/local/bin/perl') && !@is_executable('/usr/local/cpanel/3rdparty/bin/perl'))) return false;
  310. $perl = (@is_executable('/usr/local/cpanel/3rdparty/bin/perl')) ? '/usr/local/cpanel/3rdparty/bin/perl' : '/usr/local/bin/perl';
  311. $exec = "UPDRAFTPLUSKEY=updraftplus $perl ".UPDRAFTPLUS_DIR."/includes/get-cpanel-quota-usage.pl";
  312. $handle = @popen($exec, 'r');
  313. if (!is_resource($handle)) return false;
  314. $found = false;
  315. $lines = 0;
  316. while (false === $found && !feof($handle) && $lines<100) {
  317. $lines++;
  318. $w = fgets($handle);
  319. # Used, limit, remain
  320. if (preg_match('/RESULT: (\d+) (\d+) (\d+) /', $w, $matches)) { $found = true; }
  321. }
  322. $ret = pclose($handle);
  323. if (false === $found ||$ret != 0) return false;
  324. if ((int)$matches[2]<100 || ($matches[1] + $matches[3] != $matches[2])) return false;
  325. return $matches;
  326. }
  327. // This function may get called multiple times, so write accordingly
  328. public function admin_menu() {
  329. // We are in the admin area: now load all that code
  330. global $updraftplus_admin;
  331. if (empty($updraftplus_admin)) require_once(UPDRAFTPLUS_DIR.'/admin.php');
  332. if (isset($_GET['wpnonce']) && isset($_GET['page']) && isset($_GET['action']) && $_GET['page'] == 'updraftplus' && $_GET['action'] == 'downloadlatestmodlog' && wp_verify_nonce($_GET['wpnonce'], 'updraftplus_download')) {
  333. $updraft_dir = $this->backups_dir_location();
  334. $log_file = '';
  335. $mod_time = 0;
  336. if ($handle = @opendir($updraft_dir)) {
  337. while (false !== ($entry = readdir($handle))) {
  338. // The latter match is for files created internally by zipArchive::addFile
  339. if (preg_match('/^log\.[a-z0-9]+\.txt$/i', $entry)) {
  340. $mtime = filemtime($updraft_dir.'/'.$entry);
  341. if ($mtime > $mod_time) {
  342. $mod_time = $mtime;
  343. $log_file = $updraft_dir.'/'.$entry;
  344. }
  345. }
  346. }
  347. @closedir($handle);
  348. }
  349. if ($mod_time >0) {
  350. if (is_readable($log_file)) {
  351. header('Content-type: text/plain');
  352. readfile($log_file);
  353. exit;
  354. } else {
  355. add_action('all_admin_notices', array($this,'show_admin_warning_unreadablelog') );
  356. }
  357. } else {
  358. add_action('all_admin_notices', array($this,'show_admin_warning_nolog') );
  359. }
  360. }
  361. }
  362. public function add_curl_capath($handle) {
  363. if (!UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts')) curl_setopt($handle, CURLOPT_CAINFO, UPDRAFTPLUS_DIR.'/includes/cacert.pem' );
  364. }
  365. // Handle actions passed on to method plugins; e.g. Google OAuth 2.0 - ?action=updraftmethod-googledrive-auth&page=updraftplus
  366. // Nov 2013: Google's new cloud console, for reasons as yet unknown, only allows you to enter a redirect_uri with a single URL parameter... thus, we put page second, and re-add it if necessary. Apr 2014: Bitcasa already do this, so perhaps it is part of the OAuth2 standard or best practice somewhere.
  367. // Also handle action=downloadlog
  368. public function handle_url_actions() {
  369. // First, basic security check: must be an admin page, with ability to manage options, with the right parameters
  370. // Also, only on GET because WordPress on the options page repeats parameters sometimes when POST-ing via the _wp_referer field
  371. if (isset($_SERVER['REQUEST_METHOD']) && 'GET' == $_SERVER['REQUEST_METHOD'] && isset($_GET['action'])) {
  372. if (preg_match("/^updraftmethod-([a-z]+)-([a-z]+)$/", $_GET['action'], $matches) && file_exists(UPDRAFTPLUS_DIR.'/methods/'.$matches[1].'.php') && UpdraftPlus_Options::user_can_manage()) {
  373. $_GET['page'] = 'updraftplus';
  374. $_REQUEST['page'] = 'updraftplus';
  375. $method = $matches[1];
  376. require_once(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php');
  377. $call_class = "UpdraftPlus_BackupModule_".$method;
  378. $call_method = "action_".$matches[2];
  379. $backup_obj = new $call_class;
  380. add_action('http_api_curl', array($this, 'add_curl_capath'));
  381. try {
  382. if (method_exists($backup_obj, $call_method)) {
  383. call_user_func(array($backup_obj, $call_method));
  384. } elseif (method_exists($backup_obj, 'action_handler')) {
  385. call_user_func(array($backup_obj, 'action_handler'), $matches[2]);
  386. }
  387. } catch (Exception $e) {
  388. $this->log(sprintf(__("%s error: %s", 'updraftplus'), $method, $e->getMessage().' ('.$e->getCode().')', 'error'));
  389. }
  390. remove_action('http_api_curl', array($this, 'add_curl_capath'));
  391. } elseif (isset( $_GET['page'] ) && $_GET['page'] == 'updraftplus' && $_GET['action'] == 'downloadlog' && isset($_GET['updraftplus_backup_nonce']) && preg_match("/^[0-9a-f]{12}$/",$_GET['updraftplus_backup_nonce']) && UpdraftPlus_Options::user_can_manage()) {
  392. // No WordPress nonce is needed here or for the next, since the backup is already nonce-based
  393. $updraft_dir = $this->backups_dir_location();
  394. $log_file = $updraft_dir.'/log.'.$_GET['updraftplus_backup_nonce'].'.txt';
  395. if (is_readable($log_file)) {
  396. header('Content-type: text/plain');
  397. readfile($log_file);
  398. exit;
  399. } else {
  400. add_action('all_admin_notices', array($this,'show_admin_warning_unreadablelog') );
  401. }
  402. } elseif (isset( $_GET['page'] ) && $_GET['page'] == 'updraftplus' && $_GET['action'] == 'downloadfile' && isset($_GET['updraftplus_file']) && preg_match('/^backup_([\-0-9]{15})_.*_([0-9a-f]{12})-db([0-9]+)?+\.(gz\.crypt)$/i', $_GET['updraftplus_file']) && UpdraftPlus_Options::user_can_manage()) {
  403. $updraft_dir = $this->backups_dir_location();
  404. $spool_file = $updraft_dir.'/'.basename($_GET['updraftplus_file']);
  405. if (is_readable($spool_file)) {
  406. $dkey = (isset($_GET['decrypt_key'])) ? $_GET['decrypt_key'] : "";
  407. $this->spool_file('db', $spool_file, $dkey);
  408. exit;
  409. } else {
  410. add_action('all_admin_notices', array($this,'show_admin_warning_unreadablefile') );
  411. }
  412. }
  413. }
  414. }
  415. public function get_table_prefix($allow_override = false) {
  416. global $wpdb;
  417. if (is_multisite() && !defined('MULTISITE')) {
  418. # In this case (which should only be possible on installs upgraded from pre WP 3.0 WPMU), $wpdb->get_blog_prefix() cannot be made to return the right thing. $wpdb->base_prefix is not explicitly marked as public, so we prefer to use get_blog_prefix if we can, for future compatibility.
  419. $prefix = $wpdb->base_prefix;
  420. } else {
  421. $prefix = $wpdb->get_blog_prefix(0);
  422. }
  423. return ($allow_override) ? apply_filters('updraftplus_get_table_prefix', $prefix) : $prefix;
  424. }
  425. public function show_admin_warning_unreadablelog() {
  426. global $updraftplus_admin;
  427. $updraftplus_admin->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> '.__('The log file could not be read.','updraftplus'));
  428. }
  429. public function show_admin_warning_nolog() {
  430. global $updraftplus_admin;
  431. $updraftplus_admin->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> '.__('No log files were found.','updraftplus'));
  432. }
  433. public function show_admin_warning_unreadablefile() {
  434. global $updraftplus_admin;
  435. $updraftplus_admin->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> '.__('The given file could not be read.','updraftplus'));
  436. }
  437. public function load_translations() {
  438. // Tell WordPress where to find the translations
  439. load_plugin_textdomain('updraftplus', false, basename(dirname(__FILE__)).'/languages/');
  440. # The Google Analyticator plugin does something horrible: loads an old version of the Google SDK on init, always - which breaks us
  441. if ((defined('DOING_CRON') && DOING_CRON) || (isset($_GET['page']) && $_GET['page'] == 'updraftplus')) {
  442. remove_action('init', 'ganalyticator_stats_init');
  443. # Appointments+ does the same; but providers a cleaner way to disable it
  444. define('APP_GCAL_DISABLE', true);
  445. }
  446. }
  447. // Cleans up temporary files found in the updraft directory (and some in the site root - pclzip)
  448. // Always cleans up temporary files over 12 hours old.
  449. // With parameters, also cleans up those.
  450. // Also cleans out old job data older than 12 hours old (immutable value)
  451. public function clean_temporary_files($match = '', $older_than = 43200) {
  452. # Clean out old job data
  453. if ($older_than >10000) {
  454. global $wpdb;
  455. $all_jobs = $wpdb->get_results("SELECT option_name, option_value FROM $wpdb->options WHERE option_name LIKE 'updraft_jobdata_%'", ARRAY_A);
  456. foreach ($all_jobs as $job) {
  457. $val = maybe_unserialize($job['option_value']);
  458. # TODO: Can simplify this after a while (now all jobs use job_time_ms) - 1 Jan 2014
  459. # TODO: This will need changing when incremental backups are introduced
  460. if (!empty($val['backup_time_ms']) && time() > $val['backup_time_ms'] + 86400) {
  461. delete_option($job['option_name']);
  462. } elseif (!empty($val['job_time_ms']) && time() > $val['job_time_ms'] + 86400) {
  463. delete_option($job['option_name']);
  464. } elseif (empty($val['backup_time_ms']) && empty($val['job_time_ms']) && !empty($val['job_type']) && $val['job_type'] != 'backup') {
  465. delete_option($job['option_name']);
  466. }
  467. }
  468. }
  469. $updraft_dir = $this->backups_dir_location();
  470. $now_time=time();
  471. if ($handle = opendir($updraft_dir)) {
  472. while (false !== ($entry = readdir($handle))) {
  473. // This match is for files created internally by zipArchive::addFile
  474. $ziparchive_match = preg_match("/$match([0-9]+)?\.zip\.tmp\.([A-Za-z0-9]){6}?$/i", $entry);
  475. // zi followed by 6 characters is the pattern used by /usr/bin/zip on Linux systems. It's safe to check for, as we have nothing else that's going to match that pattern.
  476. $binzip_match = preg_match("/^zi([A-Za-z0-9]){6}$/", $entry);
  477. # Temporary files from the database dump process - not needed, as is caught by the catch-all
  478. # $table_match = preg_match("/${match}-table-(.*)\.table(\.tmp)?\.gz$/i", $entry);
  479. # The gz goes in with the txt, because we *don't* want to reap the raw .txt files
  480. if ((preg_match("/$match\.(tmp|table|txt\.gz)(\.gz)?$/i", $entry) || $ziparchive_match || $binzip_match) && is_file($updraft_dir.'/'.$entry)) {
  481. // We delete if a parameter was specified (and either it is a ZipArchive match or an order to delete of whatever age), or if over 12 hours old
  482. if (($match && ($ziparchive_match || $binzip_match || 0 == $older_than) && $now_time-filemtime($updraft_dir.'/'.$entry) >= $older_than) || $now_time-filemtime($updraft_dir.'/'.$entry)>43200) {
  483. $this->log("Deleting old temporary file: $entry");
  484. @unlink($updraft_dir.'/'.$entry);
  485. }
  486. }
  487. }
  488. @closedir($handle);
  489. }
  490. # Depending on the PHP setup, the current working directory could be ABSPATH or wp-admin - scan both
  491. foreach (array(ABSPATH, ABSPATH.'wp-admin/') as $path) {
  492. if ($handle = opendir($path)) {
  493. while (false !== ($entry = readdir($handle))) {
  494. # With the old pclzip temporary files, there is no need to keep them around after they're not in use - so we don't use $older_than here - just go for 15 minutes
  495. if (preg_match("/^pclzip-[a-z0-9]+.tmp$/", $entry) && $now_time-filemtime($path.$entry) >= 900) {
  496. $this->log("Deleting old PclZip temporary file: $entry");
  497. @unlink($path.$entry);
  498. }
  499. }
  500. @closedir($handle);
  501. }
  502. }
  503. }
  504. public function backup_time_nonce($nonce = false) {
  505. $this->job_time_ms = microtime(true);
  506. $this->backup_time = time();
  507. if (false === $nonce) $nonce = substr(md5(time().rand()), 20);
  508. $this->nonce = $nonce;
  509. }
  510. public function logfile_open($nonce) {
  511. //set log file name and open log file
  512. $updraft_dir = $this->backups_dir_location();
  513. $this->logfile_name = $updraft_dir."/log.$nonce.txt";
  514. if (file_exists($this->logfile_name)) {
  515. $seek_to = max((filesize($this->logfile_name) - 340), 1);
  516. $handle = fopen($this->logfile_name, 'r');
  517. if (is_resource($handle)) {
  518. # Returns 0 on success
  519. if (0 === @fseek($handle, $seek_to)) {
  520. $bytes_back = filesize($this->logfile_name) - $seek_to;
  521. # Return to the end of the file
  522. $read_recent = fread($handle, $bytes_back);
  523. # Move to end of file - ought to be redundant
  524. if (false !== strpos($read_recent, 'The backup apparently succeeded') && false !== strpos($read_recent, 'and is now complete')) {
  525. $this->backup_is_already_complete = true;
  526. }
  527. }
  528. fclose($handle);
  529. }
  530. }
  531. $this->logfile_handle = fopen($this->logfile_name, 'a');
  532. $this->opened_log_time = microtime(true);
  533. $this->log('Opened log file at time: '.date('r').' on '.site_url());
  534. global $wp_version;
  535. @include(ABSPATH.'wp-includes/version.php');
  536. // Will need updating when WP stops being just plain MySQL
  537. $mysql_version = (function_exists('mysql_get_server_info')) ? @mysql_get_server_info() : '?';
  538. $safe_mode = $this->detect_safe_mode();
  539. $memory_limit = ini_get('memory_limit');
  540. $memory_usage = round(@memory_get_usage(false)/1048576, 1);
  541. $memory_usage2 = round(@memory_get_usage(true)/1048576, 1);
  542. # Attempt to raise limit to avoid false positives
  543. @set_time_limit(900);
  544. $max_execution_time = (int)@ini_get("max_execution_time");
  545. $logline = "UpdraftPlus WordPress backup plugin (http://updraftplus.com): ".$this->version." WP: ".$wp_version." PHP: ".phpversion()." (".@php_uname().") MySQL: $mysql_version Server: ".$_SERVER["SERVER_SOFTWARE"]." safe_mode: $safe_mode max_execution_time: $max_execution_time memory_limit: $memory_limit (used: ${memory_usage}M | ${memory_usage2}M) multisite: ".((is_multisite()) ? 'Y' : 'N')." mcrypt: ".((function_exists('mcrypt_encrypt')) ? 'Y' : 'N')." ZipArchive::addFile: ";
  546. // method_exists causes some faulty PHP installations to segfault, leading to support requests
  547. if (version_compare(phpversion(), '5.2.0', '>=') && extension_loaded('zip')) {
  548. $logline .= 'Y';
  549. } else {
  550. $logline .= (class_exists('ZipArchive') && method_exists('ZipArchive', 'addFile')) ? "Y" : "N";
  551. }
  552. $w3oc = 'N';
  553. if (0 === $this->current_resumption) {
  554. $memlim = $this->memory_check_current();
  555. if ($memlim<65) {
  556. $this->log(sprintf(__('The amount of memory (RAM) allowed for PHP is very low (%s Mb) - you should increase it to avoid failures due to insufficient memory (consult your web hosting company for more help)', 'updraftplus'), round($memlim, 1)), 'warning', 'lowram');
  557. }
  558. if ($max_execution_time>0 && $max_execution_time<20) {
  559. $this->log(sprintf(__('The amount of time allowed for WordPress plugins to run is very low (%s seconds) - you should increase it to avoid backup failures due to time-outs (consult your web hosting company for more help - it is the max_execution_time PHP setting; the recommended value is %s seconds or more)', 'updraftplus'), $max_execution_time, 90), 'warning', 'lowmaxexecutiontime');
  560. }
  561. if (defined('W3TC') && W3TC == true && function_exists('w3_instance')) {
  562. $modules = w3_instance('W3_ModuleStatus');
  563. if ($modules->is_enabled('objectcache')) {
  564. $w3oc = 'Y';
  565. }
  566. }
  567. $logline .= " W3TC/ObjectCache: $w3oc";
  568. }
  569. $this->log($logline);
  570. $hosting_bytes_free = $this->get_hosting_disk_quota_free();
  571. if (is_array($hosting_bytes_free)) {
  572. $perc = round(100*$hosting_bytes_free[1]/(max($hosting_bytes_free[2], 1)), 1);
  573. $quota_free = ' / '.sprintf('Free disk space in account: %s (%s used)', round($hosting_bytes_free[3]/1048576, 1)." Mb", "$perc %");
  574. if ($hosting_bytes_free[3] < 1048576*50) {
  575. $quota_free_mb = round($hosting_bytes_free[3]/1048576, 1);
  576. $this->log(sprintf(__('Your free space in your hosting account is very low - only %s Mb remain', 'updraftplus'), $quota_free_mb), 'warning', 'lowaccountspace'.$quota_free_mb);
  577. }
  578. } else {
  579. $quota_free = '';
  580. }
  581. $disk_free_space = @disk_free_space($updraft_dir);
  582. if ($disk_free_space === false) {
  583. $this->log("Free space on disk containing Updraft's temporary directory: Unknown".$quota_free);
  584. } else {
  585. $this->log("Free space on disk containing Updraft's temporary directory: ".round($disk_free_space/1048576,1)." Mb".$quota_free);
  586. $disk_free_mb = round($disk_free_space/1048576, 1);
  587. if ($disk_free_space < 50*1048576) $this->log(sprintf(__('Your free disk space is very low - only %s Mb remain', 'updraftplus'), round($disk_free_space/1048576, 1)), 'warning', 'lowdiskspace'.$disk_free_mb);
  588. }
  589. }
  590. /* Logs the given line, adding (relative) time stamp and newline
  591. Note these subtleties of log handling:
  592. - Messages at level 'error' are not logged to file - it is assumed that a separate call to log() at another level will take place. This is because at level 'error', messages are translated; whereas the log file is for developers who may not know the translated language. Messages at level 'error' are for the user.
  593. - Messages at level 'error' do not persist through the job (they are only saved with save_backup_history(), and never restored from there - so only the final save_backup_history() errors persist); we presume that either a) they will be cleared on the next attempt, or b) they will occur again on the final attempt (at which point they will go to the user). But...
  594. - ... messages at level 'warning' persist. These are conditions that are unlikely to be cleared, not-fatal, but the user should be informed about. The $uniq_id field (which should not be numeric) can then be used for warnings that should only be logged once
  595. $skip_dblog = true is suitable when there's a risk of excessive logging, and the information is not important for the user to see in the browser on the settings page
  596. */
  597. public function log($line, $level = 'notice', $uniq_id = false, $skip_dblog = false) {
  598. if ('error' == $level || 'warning' == $level) {
  599. if ('error' == $level && 0 == $this->error_count()) $this->log('An error condition has occurred for the first time during this job');
  600. if ($uniq_id) {
  601. $this->errors[$uniq_id] = array('level' => $level, 'message' => $line);
  602. } else {
  603. $this->errors[] = array('level' => $level, 'message' => $line);
  604. }
  605. # Errors are logged separately
  606. if ('error' == $level) return;
  607. # It's a warning
  608. $warnings = $this->jobdata_get('warnings');
  609. if (!is_array($warnings)) $warnings=array();
  610. if ($uniq_id) {
  611. $warnings[$uniq_id] = $line;
  612. } else {
  613. $warnings[] = $line;
  614. }
  615. $this->jobdata_set('warnings', $warnings);
  616. }
  617. do_action('updraftplus_logline', $line, $this->nonce, $level, $uniq_id);
  618. if ($this->logfile_handle) {
  619. # Record log file times relative to the backup start, if possible
  620. $rtime = (!empty($this->job_time_ms)) ? microtime(true)-$this->job_time_ms : microtime(true)-$this->opened_log_time;
  621. fwrite($this->logfile_handle, sprintf("%08.03f", round($rtime, 3))." (".$this->current_resumption.") ".(('notice' != $level) ? '['.ucfirst($level).'] ' : '').$line."\n");
  622. }
  623. switch ($this->jobdata_get('job_type')) {
  624. case 'download':
  625. // Download messages are keyed on the job (since they could be running several), and type
  626. // The values of the POST array were checked before
  627. $findex = (!empty($_POST['findex'])) ? $_POST['findex'] : 0;
  628. $this->jobdata_set('dlmessage_'.$_POST['timestamp'].'_'.$_POST['type'].'_'.$findex, $line);
  629. break;
  630. case 'restore':
  631. #if ('debug' != $level) echo $line."\n";
  632. break;
  633. default:
  634. if (!$skip_dblog && 'debug' != $level) UpdraftPlus_Options::update_updraft_option('updraft_lastmessage', $line." (".date_i18n('M d H:i:s').")", false);
  635. break;
  636. }
  637. if (defined('UPDRAFTPLUS_CONSOLELOG')) print $line."\n";
  638. if (defined('UPDRAFTPLUS_BROWSERLOG')) print htmlentities($line)."<br>\n";
  639. }
  640. public function log_removewarning($uniq_id) {
  641. $warnings = $this->jobdata_get('warnings');
  642. if (!is_array($warnings)) $warnings=array();
  643. unset($warnings[$uniq_id]);
  644. $this->jobdata_set('warnings', $warnings);
  645. unset($this->errors[$uniq_id]);
  646. }
  647. # For efficiency, you can also feed false or a string into this function
  648. public function log_wp_error($err, $echo = false, $logerror = false) {
  649. if (false === $err) return false;
  650. if (is_string($err)) {
  651. $this->log("Error message: $err");
  652. if ($echo) echo sprintf(__('Error: %s', 'updraftplus'), htmlspecialchars($err))."<br>";
  653. if ($logerror) $this->log($err, 'error');
  654. return false;
  655. }
  656. foreach ($err->get_error_messages() as $msg) {
  657. $this->log("Error message: $msg");
  658. if ($echo) echo sprintf(__('Error: %s', 'updraftplus'), htmlspecialchars($msg))."<br>";
  659. if ($logerror) $this->log($msg, 'error');
  660. }
  661. $codes = $err->get_error_codes();
  662. if (is_array($codes)) {
  663. foreach ($codes as $code) {
  664. $data = $err->get_error_data($code);
  665. if (!empty($data)) {
  666. $ll = (is_string($data)) ? $data : serialize($data);
  667. $this->log("Error data (".$code."): ".$ll);
  668. }
  669. }
  670. }
  671. # Returns false so that callers can return with false more efficiently if they wish
  672. return false;
  673. }
  674. public function get_max_packet_size() {
  675. global $wpdb, $updraftplus;
  676. $mp = (int)$wpdb->get_var("SELECT @@session.max_allowed_packet");
  677. # Default to 1Mb
  678. $mp = (is_numeric($mp) && $mp > 0) ? $mp : 1048576;
  679. # 32Mb
  680. if ($mp < 33554432) {
  681. $save = $wpdb->show_errors(false);
  682. $req = $wpdb->query("SET GLOBAL max_allowed_packet=33554432");
  683. $wpdb->show_errors($save);
  684. if (!$req) $updraftplus->log("Tried to raise max_allowed_packet from ".round($mp/1048576,1)." Mb to 32 Mb, but failed (".$wpdb->last_error.", ".serialize($req).")");
  685. $mp = (int)$wpdb->get_var("SELECT @@session.max_allowed_packet");
  686. # Default to 1Mb
  687. $mp = (is_numeric($mp) && $mp > 0) ? $mp : 1048576;
  688. }
  689. $updraftplus->log("Max packet size: ".round($mp/1048576, 1)." Mb");
  690. return $mp;
  691. }
  692. # Q. Why is this abstracted into a separate function? A. To allow poedit and other parsers to pick up the need to translate strings passed to it (and not pick up all of those passed to log()).
  693. # 1st argument = the line to be logged (obligatory)
  694. # Further arguments = parameters for sprintf()
  695. public function log_e() {
  696. $args = func_get_args();
  697. # Get first argument
  698. $pre_line = array_shift($args);
  699. # Log it whilst still in English
  700. if (is_wp_error($pre_line)) {
  701. $this->log_wp_error($pre_line);
  702. } else {
  703. # Now run (v)sprintf on it, using any remaining arguments. vsprintf = sprintf but takes an array instead of individual arguments
  704. $this->log(vsprintf($pre_line, $args));
  705. echo vsprintf(__($pre_line, 'updraftplus'), $args).'<br>';
  706. }
  707. }
  708. // This function is used by cloud methods to provide standardised logging, but more importantly to help us detect that meaningful activity took place during a resumption run, so that we can schedule further resumptions if it is worthwhile
  709. public function record_uploaded_chunk($percent, $extra = '', $file_path = false) {
  710. // Touch the original file, which helps prevent overlapping runs
  711. if ($file_path) touch($file_path);
  712. // What this means in effect is that at least one of the files touched during the run must reach this percentage (so lapping round from 100 is OK)
  713. if ($percent > 0.7 * ($this->current_resumption - max($this->jobdata_get('uploaded_lastreset'), 9))) $this->something_useful_happened();
  714. // Log it
  715. global $updraftplus_backup;
  716. $log = (!empty($updraftplus_backup->current_service)) ? ucfirst($updraftplus_backup->current_service)." chunked upload: $percent % uploaded" : '';
  717. if ($log) $this->log($log.(($extra) ? " ($extra)" : ''));
  718. // If we are on an 'overtime' resumption run, and we are still meaningfully uploading, then schedule a new resumption
  719. // Our definition of meaningful is that we must maintain an overall average of at least 0.7% per run, after allowing 9 runs for everything else to get going
  720. // i.e. Max 100/.7 + 9 = 150 runs = 760 minutes = 12 hrs 40, if spaced at 5 minute intervals. However, our algorithm now decreases the intervals if it can, so this should not really come into play
  721. // If they get 2 minutes on each run, and the file is 1Gb, then that equals 10.2Mb/120s = minimum 59Kb/s upload speed required
  722. $upload_status = $this->jobdata_get('uploading_substatus');
  723. if (is_array($upload_status)) {
  724. $upload_status['p'] = $percent/100;
  725. $this->jobdata_set('uploading_substatus', $upload_status);
  726. }
  727. }
  728. function chunked_upload($caller, $file, $cloudpath, $logname, $chunk_size, $uploaded_size) {
  729. $fullpath = $this->backups_dir_location().'/'.$file;
  730. $orig_file_size = filesize($fullpath);
  731. if ($uploaded_size >= $orig_file_size) return true;
  732. $fp = @fopen($fullpath, 'rb');
  733. if (!$fp) {
  734. $this->log("$logname: failed to open file: $fullpath");
  735. $this->log("$file: ".sprintf(__('%s Error: Failed to open local file','updraftplus'), $logname), 'error');
  736. return false;
  737. }
  738. $chunks = floor($orig_file_size / $chunk_size);
  739. // There will be a remnant unless the file size was exactly on a 5Mb boundary
  740. if ($orig_file_size % $chunk_size > 0 ) $chunks++;
  741. $this->log("$logname upload: $file (chunks: $chunks) -> $cloudpath ($uploaded_size)");
  742. if ($chunks < 2) {
  743. return 1;
  744. } else {
  745. $errors_so_far = 0;
  746. for ($i = 1 ; $i <= $chunks; $i++) {
  747. $upload_start = ($i-1)*$chunk_size;
  748. // The file size -1 equals the byte offset of the final byte
  749. $upload_end = min($i*$chunk_size-1, $orig_file_size-1);
  750. // Don't forget the +1; otherwise the last byte is omitted
  751. $upload_size = $upload_end - $upload_start + 1;
  752. fseek($fp, $upload_start);
  753. $uploaded = $caller->chunked_upload($file, $fp, $i, $upload_size, $upload_start, $upload_end);
  754. if ($uploaded) {
  755. $perc = round(100*((($i-1) * $chunk_size) + $upload_size)/max($orig_file_size, 1), 1);
  756. # $perc = round(100*$i/$chunks,1); # Takes no notice of last chunk likely being smaller
  757. $this->record_uploaded_chunk($perc, $i, $fullpath);
  758. } else {
  759. $errors_so_far++;
  760. if ($errors_so_far>=3) return false;
  761. }
  762. }
  763. if ($errors_so_far) return false;
  764. // All chunks are uploaded - now combine the chunks
  765. $ret = true;
  766. if (method_exists($caller, 'chunked_upload_finish')) {
  767. $ret = $caller->chunked_upload_finish($file);
  768. if (!$ret) {
  769. $this->log("$logname - failed to re-assemb…

Large files files are truncated, but you can click here to view the full file