PageRenderTime 70ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://bitbucket.org/nathancorbier/wastark.com
PHP | 2438 lines | 1898 code | 265 blank | 275 comment | 496 complexity | 472068cefb4b0eec666fc93e24832b40 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /*
  3. Plugin Name: UpdraftPlus - Backup/Restore
  4. Plugin URI: http://updraftplus.com
  5. Description: Backup and restore: your content and database can be automatically backed up to Amazon S3, Dropbox, Google Drive, FTP or email, on separate schedules.
  6. Author: David Anderson
  7. Version: 1.4.14
  8. Donate link: http://david.dw-perspective.org.uk/donate
  9. License: GPLv3 or later
  10. Author URI: http://wordshell.net
  11. */
  12. /*
  13. TODO
  14. //Put in old-WP-version warning, and point them to where they can get help
  15. //Add SFTP, Box.Net, SugarSync and Microsoft Skydrive support??
  16. //The restorer has a hard-coded wp-content - fix
  17. //Change DB encryption to not require whole gzip in memory (twice)
  18. //improve error reporting / pretty up return messages in admin area. One thing: have a "backup is now finished" flag. Otherwise with the resuming things get ambiguous/confusing. See http://wordpress.org/support/topic/backup-status - user was not aware that backup completely failed. Maybe a "backup status" field for each nonce that gets updated? (Even via AJAX?)
  19. //?? On 'backup now', open up a Lightbox, count down 5 seconds, then start examining the log file (if it can be found)
  20. //Should make clear in dashboard what is a non-fatal error (i.e. can be retried) - leads to unnecessary bug reports
  21. // 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.
  22. // 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??
  23. // Warn the user if their zip-file creation is slooowww...
  24. // Create a "Want Support?" button/console, that leads them through what is needed, and performs some basic tests...
  25. // Resuming partial FTP uploads
  26. // 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
  27. // Multiple jobs
  28. // Change FTP to use SSL by default
  29. // Chunked downloading of material for resumption + do resume it
  30. // Disk free-space display
  31. // Multisite add-on should allow restoring of each blog individually
  32. // When looking for files to delete, is the current encryption setting used? Should not be.
  33. // Create single zip, containing even WordPress itself
  34. // When a new backup starts, AJAX-update the 'Last backup' display in the admin page.
  35. // Remove the recurrence of admin notices when settings are saved due to _wp_referer
  36. // Auto-detect what the real execution time is (max_execution_time is just one of the upper limits, there can be others, some insivible directly), and tweak our resumption time accordingly
  37. Encrypt filesystem, if memory allows (and have option for abort if not); split up into multiple zips when needed
  38. // Does not delete old custom directories upon a restore?
  39. // New sub-module to verify that the backups are there, independently of backup thread
  40. */
  41. /* Portions copyright 2010 Paul Kehrer
  42. Portions copyright 2011-13 David Anderson
  43. Other portions copyright as indicated authors in the relevant files
  44. Particular thanks to Sorin Iclanzan, author of the "Backup" plugin, from which much Google Drive code was taken under the GPLv3+
  45. This program is free software; you can redistribute it and/or modify
  46. it under the terms of the GNU General Public License as published by
  47. the Free Software Foundation; either version 3 of the License, or
  48. (at your option) any later version.
  49. This program is distributed in the hope that it will be useful,
  50. but WITHOUT ANY WARRANTY; without even the implied warranty of
  51. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  52. GNU General Public License for more details.
  53. You should have received a copy of the GNU General Public License
  54. along with this program; if not, write to the Free Software
  55. Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  56. */
  57. // 15 minutes
  58. @set_time_limit(900);
  59. define('UPDRAFTPLUS_DIR', dirname(__FILE__));
  60. define('UPDRAFTPLUS_URL', plugins_url('', __FILE__));
  61. define('UPDRAFT_DEFAULT_OTHERS_EXCLUDE','upgrade,cache,updraft,index.php,backup,backups');
  62. // This is used in various places, based on our assumption of the maximum time any job should take. May need lengthening in future if we get reports which show enormous sets hitting the limit.
  63. // Also one section requires at least 1% progress each run, so on a 5-minute schedule, that equals just under 9 hours - then an extra allowance takes it just over
  64. define('UPDRAFT_TRANSTIME', 3600*9+5);
  65. // Load add-ons
  66. if (is_file(UPDRAFTPLUS_DIR.'/premium.php')) require_once(UPDRAFTPLUS_DIR.'/premium.php');
  67. if ($dir_handle = opendir(UPDRAFTPLUS_DIR.'/addons')) {
  68. while ($e = readdir($dir_handle)) {
  69. if (is_file(UPDRAFTPLUS_DIR.'/addons/'.$e) && preg_match('/\.php$/', $e)) {
  70. include_once(UPDRAFTPLUS_DIR.'/addons/'.$e);
  71. }
  72. }
  73. @closedir($dir_handle);
  74. }
  75. if (!isset($updraftplus)) $updraftplus = new UpdraftPlus();
  76. if (!$updraftplus->memory_check(192)) {
  77. # TODO: Better solution is to split the backup set into manageable chunks based on this limit
  78. @ini_set('memory_limit', '192M'); //up the memory limit for large backup files
  79. }
  80. if (!class_exists('UpdraftPlus_Options')) require_once(UPDRAFTPLUS_DIR.'/options.php');
  81. class UpdraftPlus {
  82. var $version;
  83. var $plugin_title = 'UpdraftPlus Backup/Restore';
  84. // Choices will be shown in the admin menu in the order used here
  85. var $backup_methods = array (
  86. "s3" => "Amazon S3",
  87. "dropbox" => "Dropbox",
  88. "googledrive" => "Google Drive",
  89. "ftp" => "FTP",
  90. "email" => "Email"
  91. );
  92. var $dbhandle;
  93. var $dbhandle_isgz;
  94. var $errors = array();
  95. var $nonce;
  96. var $logfile_name = "";
  97. var $logfile_handle = false;
  98. var $backup_time;
  99. var $opened_log_time;
  100. var $backup_dir;
  101. var $jobdata;
  102. // Used to schedule resumption attempts beyond the tenth, if needed
  103. var $current_resumption;
  104. var $newresumption_scheduled = false;
  105. var $zipfiles_added;
  106. var $zipfiles_existingfiles;
  107. var $zipfiles_dirbatched;
  108. var $zipfiles_batched;
  109. var $zip_preferpcl = false;
  110. function __construct() {
  111. // Initialisation actions - takes place on plugin load
  112. if ($fp = fopen( __FILE__, 'r')) {
  113. $file_data = fread( $fp, 1024 );
  114. if (preg_match("/Version: ([\d\.]+)(\r|\n)/", $file_data, $matches)) {
  115. $this->version = $matches[1];
  116. }
  117. fclose( $fp );
  118. }
  119. # Create admin page
  120. add_action('admin_init', array($this, 'admin_init'));
  121. add_action('updraft_backup', array($this,'backup_files'));
  122. add_action('updraft_backup_database', array($this,'backup_database'));
  123. # backup_all is used by the manual "Backup Now" button
  124. add_action('updraft_backup_all', array($this,'backup_all'));
  125. # this is our runs-after-backup event, whose purpose is to see if it succeeded or failed, and resume/mom-up etc.
  126. add_action('updraft_backup_resume', array($this,'backup_resume'), 10, 3);
  127. add_action('wp_ajax_updraft_download_backup', array($this, 'updraft_download_backup'));
  128. add_action('wp_ajax_updraft_ajax', array($this, 'updraft_ajax_handler'));
  129. # http://codex.wordpress.org/Plugin_API/Filter_Reference/cron_schedules
  130. add_filter('cron_schedules', array($this,'modify_cron_schedules'));
  131. add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
  132. add_action('init', array($this, 'handle_url_actions'));
  133. }
  134. // Handle actions passed on to method plugins; e.g. Google OAuth 2.0 - ?page=updraftplus&action=updraftmethod-googledrive-auth
  135. // Also handle action=downloadlog
  136. function handle_url_actions() {
  137. // First, basic security check: must be an admin page, with ability to manage options, with the right parameters
  138. if ( UpdraftPlus_Options::user_can_manage() && isset( $_GET['page'] ) && $_GET['page'] == 'updraftplus' && isset($_GET['action']) ) {
  139. if (preg_match("/^updraftmethod-([a-z]+)-([a-z]+)$/", $_GET['action'], $matches) && file_exists(UPDRAFTPLUS_DIR.'/methods/'.$matches[1].'.php')) {
  140. $method = $matches[1];
  141. require_once(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php');
  142. $call_class = "UpdraftPlus_BackupModule_".$method;
  143. $call_method = "action_".$matches[2];
  144. if (method_exists($call_class, $call_method)) call_user_func(array($call_class,$call_method));
  145. } elseif ($_GET['action'] == 'downloadlog' && isset($_GET['updraftplus_backup_nonce']) && preg_match("/^[0-9a-f]{12}$/",$_GET['updraftplus_backup_nonce'])) {
  146. $updraft_dir = $this->backups_dir_location();
  147. $log_file = $updraft_dir.'/log.'.$_GET['updraftplus_backup_nonce'].'.txt';
  148. if (is_readable($log_file)) {
  149. header('Content-type: text/plain');
  150. readfile($log_file);
  151. exit;
  152. } else {
  153. add_action('admin_notices', array($this,'show_admin_warning_unreadablelog') );
  154. }
  155. }
  156. }
  157. }
  158. // Cleans up temporary files found in the updraft directory
  159. function clean_temporary_files() {
  160. $updraft_dir = $this->backups_dir_location();
  161. if ($handle = opendir($updraft_dir)) {
  162. $now_time=time();
  163. while (false !== ($entry = readdir($handle))) {
  164. if (preg_match('/\.tmp(\.gz)?$/', $entry) && is_file($updraft_dir.'/'.$entry) && $now_time-filemtime($updraft_dir.'/'.$entry)>86400) {
  165. $this->log("Deleting old temporary file: $entry");
  166. @unlink($updraft_dir.'/'.$entry);
  167. }
  168. }
  169. @closedir($handle);
  170. }
  171. }
  172. # Adds the settings link under the plugin on the plugin screen.
  173. function plugin_action_links($links, $file) {
  174. if ($file == plugin_basename(__FILE__)){
  175. $settings_link = '<a href="'.site_url().'/wp-admin/options-general.php?page=updraftplus">'.__("Settings", "UpdraftPlus").'</a>';
  176. array_unshift($links, $settings_link);
  177. $settings_link = '<a href="http://david.dw-perspective.org.uk/donate">'.__("Donate","UpdraftPlus").'</a>';
  178. array_unshift($links, $settings_link);
  179. $settings_link = '<a href="http://updraftplus.com">'.__("Add-Ons / Pro Support","UpdraftPlus").'</a>';
  180. array_unshift($links, $settings_link);
  181. }
  182. return $links;
  183. }
  184. function backup_time_nonce() {
  185. $this->backup_time = time();
  186. $nonce = substr(md5(time().rand()), 20);
  187. $this->nonce = $nonce;
  188. }
  189. function logfile_open($nonce) {
  190. //set log file name and open log file
  191. $updraft_dir = $this->backups_dir_location();
  192. $this->logfile_name = $updraft_dir. "/log.$nonce.txt";
  193. // Use append mode in case it already exists
  194. $this->logfile_handle = fopen($this->logfile_name, 'a');
  195. $this->opened_log_time = microtime(true);
  196. $this->log("Opened log file at time: ".date('r'));
  197. global $wp_version;
  198. $logline = "UpdraftPlus: ".$this->version." WordPress: ".$wp_version." PHP: ".phpversion()." (".php_uname().") PHP Max Execution Time: ".@ini_get("max_execution_time")." ZipArchive::addFile exists: ";
  199. // method_exists causes some faulty PHP installations to segfault, leading to support requests
  200. if (version_compare(phpversion(), '5.2.0', '>=') && extension_loaded('zip')) {
  201. $logline .= 'Y';
  202. } else {
  203. $logline .= (method_exists('ZipArchive', 'addFile')) ? "Y" : "N";
  204. }
  205. $this->log($logline);
  206. }
  207. # Logs the given line, adding (relative) time stamp and newline
  208. function log($line) {
  209. if ($this->logfile_handle) fwrite($this->logfile_handle, sprintf("%08.03f", round(microtime(true)-$this->opened_log_time, 3))." ".$line."\n");
  210. UpdraftPlus_Options::update_updraft_option("updraft_lastmessage", $line." (".date('M d H:i:s').")");
  211. }
  212. // 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
  213. function record_uploaded_chunk($percent, $extra) {
  214. // Log it
  215. $service = $this->jobdata_get('service');
  216. $log = ucfirst($service)." chunked upload: $percent % uploaded";
  217. if ($extra) $log .= " ($extra)";
  218. $this->log($log);
  219. // If we are on an 'overtime' resumption run, and we are still meainingfully uploading, then schedule a new resumption
  220. // Our definition of meaningful is that we must maintain an overall average of at least 1% per run, after allowing 9 runs for everything else to get going
  221. // i.e. Max 109 runs = 545 minutes = 9 hrs 05
  222. // If they get 2 minutes on each run, and the file is 1Gb, then that equals 10.2Mb/120s = minimum 87Kb/s upload speed required
  223. if ($this->current_resumption >= 9 && $this->newresumption_scheduled == false && $percent > ( $this->current_resumption - 9)) {
  224. $resume_interval = $this->jobdata_get('resume_interval');
  225. if (!is_numeric($resume_interval) || $resume_interval<200) { $resume_interval = 200; }
  226. $schedule_for = time()+$resume_interval;
  227. $this->newresumption_scheduled = $schedule_for;
  228. $this->log("This is resumption ".$this->current_resumption.", but meaningful uploading is still taking place; so a new one will be scheduled");
  229. wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($this->current_resumption + 1, $this->nonce));
  230. }
  231. }
  232. function backup_resume($resumption_no, $bnonce) {
  233. @ignore_user_abort(true);
  234. // This is scheduled for 5 minutes after a backup job starts
  235. // Restore state
  236. if ($resumption_no > 0) {
  237. $this->nonce = $bnonce;
  238. $this->backup_time = $this->jobdata_get('backup_time');
  239. $this->logfile_open($bnonce);
  240. }
  241. $btime = $this->backup_time;
  242. $this->log("Backup run: resumption=$resumption_no, nonce=$bnonce, begun at=$btime");
  243. $this->current_resumption = $resumption_no;
  244. // Schedule again, to run in 5 minutes again, in case we again fail
  245. // The actual interval can be increased (for future resumptions) by other code, if it detects apparent overlapping
  246. $resume_interval = $this->jobdata_get('resume_interval');
  247. if (!is_numeric($resume_interval) || $resume_interval<200) $resume_interval = 200;
  248. // A different argument than before is needed otherwise the event is ignored
  249. $next_resumption = $resumption_no+1;
  250. if ($next_resumption < 10) {
  251. $this->log("Scheduling a resumption ($next_resumption) in case this run gets aborted");
  252. $schedule_for = time()+$resume_interval;
  253. wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($next_resumption, $bnonce));
  254. $this->newresumption_scheduled = $schedule_for;
  255. } else {
  256. $this->log("The current run is our tenth attempt - will not schedule a further attempt until we see something useful happening");
  257. }
  258. // This should be always called; if there were no files in this run, it returns us an empty array
  259. $backup_array = $this->resumable_backup_of_files($resumption_no);
  260. // This save, if there was something, is then immediately picked up again
  261. if (is_array($backup_array)) $this->save_backup_history($backup_array);
  262. // Returns an array, most recent first, of backup sets
  263. $backup_history = $this->get_backup_history();
  264. if (!isset($backup_history[$btime])) {
  265. $this->log("Could not find a record in the database of a backup with this timestamp");
  266. }
  267. $our_files=$backup_history[$btime];
  268. if (!is_array($our_files)) $our_files = array();
  269. $undone_files = array();
  270. $backup_database = $this->jobdata_get('backup_database');
  271. // The transient is read and written below (instead of using the existing variable) so that we can copy-and-paste this part as needed.
  272. if ($backup_database == "begun" || $backup_database == 'finished' || $backup_database == 'encrypted') {
  273. if ($backup_database == "begun") {
  274. if ($resumption_no > 0) {
  275. $this->log("Resuming creation of database dump");
  276. } else {
  277. $this->log("Beginning creation of database dump");
  278. }
  279. } elseif ($backup_database == 'encrypted') {
  280. $this->log("Database dump: Creation and encryption were completed already");
  281. } else {
  282. $this->log("Database dump: Creation was completed already");
  283. }
  284. $db_backup = $this->backup_db($backup_database);
  285. if(is_array($our_files) && is_string($db_backup)) $our_files['db'] = $db_backup;
  286. if ($backup_database != 'encrypted') $this->jobdata_set("backup_database", 'finished');
  287. } else {
  288. $this->log("Unrecognised data when trying to ascertain if the database was backed up ($backup_database)");
  289. }
  290. // Save this to our history so we can track backups for the retain feature
  291. $this->log("Saving backup history");
  292. // This is done before cloud despatch, because we want a record of what *should* be in the backup. Whether it actually makes it there or not is not yet known.
  293. $this->save_backup_history($our_files);
  294. // Potentially encrypt the database if it is not already
  295. if (isset($our_files['db']) && !preg_match("/\.crypt$/", $our_files['db'])) {
  296. $our_files['db'] = $this->encrypt_file($our_files['db']);
  297. $this->save_backup_history($our_files);
  298. if (preg_match("/\.crypt$/", $our_files['db'])) $this->jobdata_set("backup_database", 'encrypted');
  299. }
  300. foreach ($our_files as $key => $file) {
  301. // Only continue if the stored info was about a dump
  302. if ($key != 'plugins' && $key != 'themes' && $key != 'others' && $key != 'uploads' && $key != 'db') continue;
  303. $hash = md5($file);
  304. $fullpath = $this->backups_dir_location().'/'.$file;
  305. if ($this->jobdata_get("uploaded_$hash") === "yes") {
  306. $this->log("$file: $key: This file has already been successfully uploaded");
  307. } elseif (is_file($fullpath)) {
  308. $this->log("$file: $key: This file has not yet been successfully uploaded: will queue");
  309. $undone_files[$key] = $file;
  310. } else {
  311. $this->log("$file: Note: This file was not marked as successfully uploaded, but does not exist on the local filesystem");
  312. $this->uploaded_file($file);
  313. }
  314. }
  315. if (count($undone_files) == 0) {
  316. $this->log("There were no more files that needed uploading; backup job is complete");
  317. // No email, as the user probably already got one if something else completed the run
  318. $this->backup_finish($next_resumption, true, false, $resumption_no);
  319. return;
  320. }
  321. $this->log("Requesting backup of the files that were not successfully uploaded");
  322. $this->cloud_backup($undone_files);
  323. $this->log("Resume backup ($bnonce, $resumption_no): finish run");
  324. if (is_array($our_files)) $this->save_last_backup($our_files);
  325. $this->backup_finish($next_resumption, true, true, $resumption_no);
  326. }
  327. function backup_all() {
  328. $this->boot_backup(true,true);
  329. }
  330. function backup_files() {
  331. # Note that the "false" for database gets over-ridden automatically if they turn out to have the same schedules
  332. $this->boot_backup(true,false);
  333. }
  334. function backup_database() {
  335. # Note that nothing will happen if the file backup had the same schedule
  336. $this->boot_backup(false,true);
  337. }
  338. function jobdata_set($key, $value) {
  339. if (is_array($this->jobdata)) {
  340. $this->jobdata[$key] = $value;
  341. } else {
  342. $this->jobdata = array($key => $value);
  343. }
  344. set_transient("updraft_jobdata_".$this->nonce, $this->jobdata, 14400);
  345. }
  346. function jobdata_get($key) {
  347. if (!is_array($this->jobdata)) {
  348. $this->jobdata = get_transient("updraft_jobdata_".$this->nonce);
  349. if (!is_array($this->jobdata)) return false;
  350. }
  351. return (isset($this->jobdata[$key])) ? $this->jobdata[$key] : false;
  352. }
  353. // This uses a transient; its only purpose is to indicate *total* completion; there is no actual danger, just wasted time, in resuming when it was not needed. So the transient just helps save resources.
  354. function resumable_backup_of_files($resumption_no) {
  355. //backup directories and return a numerically indexed array of file paths to the backup files
  356. $transient_status = $this->jobdata_get('backup_files');
  357. if ($transient_status == 'finished') {
  358. $this->log("Creation of backups of directories: already finished");
  359. } elseif ($transient_status == "begun") {
  360. if ($resumption_no>0) {
  361. $this->log("Creation of backups of directories: had begun; will resume");
  362. } else {
  363. $this->log("Creation of backups of directories: beginning");
  364. }
  365. } else {
  366. # This is not necessarily a backup run which is meant to contain files at all
  367. $this->log("This backup run is not intended for files - skipping");
  368. return array();
  369. }
  370. // We want this array, even if already finished
  371. $backup_array = $this->backup_dirs($transient_status);
  372. // This can get over-written later
  373. $this->jobdata_set('backup_files', 'finished');
  374. return $backup_array;
  375. }
  376. // This procedure initiates a backup run
  377. function boot_backup($backup_files, $backup_database) {
  378. @ignore_user_abort(true);
  379. //generate backup information
  380. $this->backup_time_nonce();
  381. $this->logfile_open($this->nonce);
  382. // Some house-cleaning
  383. $this->clean_temporary_files();
  384. // Log some information that may be helpful
  385. $this->log("Tasks: Backup files: $backup_files (schedule: ".UpdraftPlus_Options::get_updraft_option('updraft_interval', 'unset').") Backup DB: $backup_database (schedule: ".UpdraftPlus_Options::get_updraft_option('updraft_interval_database', 'unset').")");
  386. # If the files and database schedules are the same, and if this the file one, then we rope in database too.
  387. # On the other hand, if the schedules were the same and this was the database run, then there is nothing to do.
  388. if (UpdraftPlus_Options::get_updraft_option('updraft_interval') == UpdraftPlus_Options::get_updraft_option('updraft_interval_database') || UpdraftPlus_Options::get_updraft_option('updraft_interval_database','xyz') == 'xyz' ) {
  389. $backup_database = ($backup_files == true) ? true : false;
  390. }
  391. $this->log("Processed schedules. Tasks now: Backup files: $backup_files Backup DB: $backup_database");
  392. # If nothing to be done, then just finish
  393. if (!$backup_files && !$backup_database) {
  394. $this->backup_finish(1, false, false, 0);
  395. return;
  396. }
  397. // Save what *should* be done, to make it resumable from this point on
  398. if ($backup_database) $this->jobdata_set("backup_database", 'begun');
  399. if ($backup_files) $this->jobdata_set('backup_files', 'begun');
  400. $this->jobdata_set('service', UpdraftPlus_Options::get_updraft_option('updraft_service'));
  401. // This can be adapted if we see a need
  402. $this->jobdata_set('resume_interval', 300);
  403. $this->jobdata_set('backup_time', $this->backup_time);
  404. // Everthing is now set up; now go
  405. $this->backup_resume(0, $this->nonce);
  406. }
  407. // Encrypts the file if the option is set; returns the basename of the file (according to whether it was encrypted or nto)
  408. function encrypt_file($file) {
  409. $encryption = UpdraftPlus_Options::get_updraft_option('updraft_encryptionphrase');
  410. if (strlen($encryption) > 0) {
  411. $this->log("$file: applying encryption");
  412. $encryption_error = 0;
  413. $microstart = microtime(true);
  414. require_once(UPDRAFTPLUS_DIR.'/includes/Rijndael.php');
  415. $rijndael = new Crypt_Rijndael();
  416. $rijndael->setKey($encryption);
  417. $updraft_dir = $this->backups_dir_location();
  418. $file_size = @filesize($updraft_dir.'/'.$file)/1024;
  419. if (false === file_put_contents($updraft_dir.'/'.$file.'.crypt' , $rijndael->encrypt(file_get_contents($updraft_dir.'/'.$file)))) {$encryption_error = 1;}
  420. if (0 == $encryption_error) {
  421. $time_taken = max(0.000001, microtime(true)-$microstart);
  422. $this->log("$file: encryption successful: ".round($file_size,1)."Kb in ".round($time_taken,1)."s (".round($file_size/$time_taken, 1)."Kb/s)");
  423. # Delete unencrypted file
  424. @unlink($updraft_dir.'/'.$file);
  425. return basename($file.'.crypt');
  426. } else {
  427. $this->log("Encryption error occurred when encrypting database. Encryption aborted.");
  428. $this->error("Encryption error occurred when encrypting database. Encryption aborted.");
  429. return basename($file);
  430. }
  431. } else {
  432. return basename($file);
  433. }
  434. }
  435. function backup_finish($cancel_event, $clear_nonce_transient, $allow_email, $resumption_no) {
  436. // In fact, leaving the hook to run (if debug is set) is harmless, as the resume job should only do tasks that were left unfinished, which at this stage is none.
  437. if (empty($this->errors)) {
  438. if ($clear_nonce_transient) {
  439. $this->log("There were no errors in the uploads, so the 'resume' event is being unscheduled");
  440. wp_clear_scheduled_hook('updraft_backup_resume', array($cancel_event, $this->nonce));
  441. // TODO: Delete the job transient (is presently useful for debugging, and only lasts 4 hours)
  442. }
  443. } else {
  444. $this->log("There were errors in the uploads, so the 'resume' event is remaining scheduled");
  445. }
  446. // Send the results email if appropriate, which means:
  447. // - The caller allowed it (which is not the case in an 'empty' run)
  448. // - And: An email address was set (which must be so in email mode)
  449. // And one of:
  450. // - Debug mode
  451. // - There were no errors (which means we completed and so this is the final run - time for the final report)
  452. // - It was the tenth resumption; everything failed
  453. $send_an_email = false;
  454. // Make sure that the final status is shown
  455. if (empty($this->errors)) {
  456. $send_an_email = true;
  457. $final_message = "The backup apparently succeeded and is now complete";
  458. } elseif ($this->newresumption_scheduled == false) {
  459. $send_an_email = true;
  460. $final_message = "The backup attempt has finished, apparently unsuccessfully";
  461. } else {
  462. // There are errors, but a resumption will be attempted
  463. $final_message = "The backup has not finished; a resumption is scheduled within 5 minutes";
  464. }
  465. // Now over-ride the decision to send an email, if needed
  466. if (UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) {
  467. $send_an_email = true;
  468. $this->log("An email has been scheduled for this job, because we are in debug mode");
  469. }
  470. // If there's no email address, or the set was empty, that is the final over-ride: don't send
  471. if (!$allow_email) {
  472. $send_an_email = false;
  473. $this->log("No email will be sent - this backup set was empty.");
  474. } elseif (UpdraftPlus_Options::get_updraft_option('updraft_email') == '') {
  475. $send_an_email = false;
  476. $this->log("No email will/can be sent - the user has not configured an email address.");
  477. }
  478. if ($send_an_email) $this->send_results_email($final_message);
  479. $this->log($final_message);
  480. @fclose($this->logfile_handle);
  481. // Don't delete the log file now; delete it upon rotation
  482. //if (!UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) @unlink($this->logfile_name);
  483. }
  484. function send_results_email($final_message) {
  485. $debug_mode = UpdraftPlus_Options::get_updraft_option('updraft_debug_mode');
  486. $sendmail_to = UpdraftPlus_Options::get_updraft_option('updraft_email');
  487. $backup_files = $this->jobdata_get('backup_files');
  488. $backup_db = $this->jobdata_get("backup_database");
  489. if ($backup_files == 'finished' && ( $backup_db == 'finished' || $backup_db == 'encrypted' ) ) {
  490. $backup_contains = "Files and database";
  491. } elseif ($backup_files == 'finished') {
  492. $backup_contains = ($backup_db == "begun") ? "Files (database backup has not completed)" : "Files only (database was not part of this particular schedule)";
  493. } elseif ($backup_db == 'finished' || $backup_db == 'encrypted') {
  494. $backup_contains = ($backup_files == "begun") ? "Database (files backup has not completed)" : "Database only (files were not part of this particular schedule)";
  495. } else {
  496. $backup_contains = "Unknown/unexpected error - please raise a support request";
  497. }
  498. $this->log("Sending email ('$backup_contains') report to: ".substr($sendmail_to, 0, 5)."...");
  499. $append_log = ($debug_mode && $this->logfile_name != "") ? "\r\nLog contents:\r\n".file_get_contents($this->logfile_name) : "" ;
  500. wp_mail($sendmail_to,'Backed up: '.get_bloginfo('name').' (UpdraftPlus '.$this->version.') '.date('Y-m-d H:i',time()),'Site: '.site_url()."\r\nUpdraftPlus WordPress backup is complete.\r\nBackup contains: ".$backup_contains."\r\nLatest status: $final_message\r\n\r\n".$this->wordshell_random_advert(0)."\r\n".$append_log);
  501. }
  502. function save_last_backup($backup_array) {
  503. $success = (empty($this->errors)) ? 1 : 0;
  504. $last_backup = array('backup_time'=>$this->backup_time, 'backup_array'=>$backup_array, 'success'=>$success, 'errors'=>$this->errors, 'backup_nonce' => $this->nonce);
  505. UpdraftPlus_Options::update_updraft_option('updraft_last_backup', $last_backup);
  506. }
  507. // This should be called whenever a file is successfully uploaded
  508. function uploaded_file($file, $id = false) {
  509. $hash = md5($file);
  510. $this->log("Recording as successfully uploaded: $file ($hash)");
  511. $this->jobdata_set("uploaded_$hash", "yes");
  512. if ($id) {
  513. $ids = UpdraftPlus_Options::get_updraft_option('updraft_file_ids', array() );
  514. $ids[$file] = $id;
  515. UpdraftPlus_Options::update_updraft_option('updraft_file_ids',$ids);
  516. $this->log("Stored file<->id correlation in database ($file <-> $id)");
  517. }
  518. // Delete local files immediately if the option is set
  519. // Where we are only backing up locally, only the "prune" function should do deleting
  520. if ($this->jobdata_get('service') != '' && $this->jobdata_get('service') != 'none') $this->delete_local($file);
  521. }
  522. // Dispatch to the relevant function
  523. function cloud_backup($backup_array) {
  524. $service = $this->jobdata_get('service');
  525. $this->log("Cloud backup selection: ".$service);
  526. @set_time_limit(900);
  527. $method_include = UPDRAFTPLUS_DIR.'/methods/'.$service.'.php';
  528. if (file_exists($method_include)) require_once($method_include);
  529. if ($service == "none") {
  530. $this->log("No remote despatch: user chose no remote backup service");
  531. } else {
  532. $this->log("Beginning dispatch of backup to remote");
  533. }
  534. $objname = "UpdraftPlus_BackupModule_${service}";
  535. if (method_exists($objname, "backup")) {
  536. // New style - external, allowing more plugability
  537. $remote_obj = new $objname;
  538. $remote_obj->backup($backup_array);
  539. } elseif ($service == "none") {
  540. $this->prune_retained_backups("none", null, null);
  541. }
  542. }
  543. function prune_file($service, $dofile, $method_object = null, $object_passback = null ) {
  544. $this->log("Delete this file: $dofile, service=$service");
  545. $fullpath = $this->backups_dir_location().'/'.$dofile;
  546. // delete it if it's locally available
  547. if (file_exists($fullpath)) {
  548. $this->log("Deleting local copy ($fullpath)");
  549. @unlink($fullpath);
  550. }
  551. // Despatch to the particular method's deletion routine
  552. if (!is_null($method_object)) $method_object->delete($dofile, $object_passback);
  553. }
  554. // Carries out retain behaviour. Pass in a valid S3 or FTP object and path if relevant.
  555. function prune_retained_backups($service, $backup_method_object = null, $backup_passback = null) {
  556. // If they turned off deletion on local backups, then there is nothing to do
  557. if (UpdraftPlus_Options::get_updraft_option('updraft_delete_local') == 0 && $service == 'none') {
  558. $this->log("Prune old backups from local store: nothing to do, since the user disabled local deletion and we are using local backups");
  559. return;
  560. }
  561. $this->log("Retain: beginning examination of existing backup sets");
  562. // Number of backups to retain - files
  563. $updraft_retain = UpdraftPlus_Options::get_updraft_option('updraft_retain', 1);
  564. $updraft_retain = (is_numeric($updraft_retain)) ? $updraft_retain : 1;
  565. $this->log("Retain files: user setting: number to retain = $updraft_retain");
  566. // Number of backups to retain - db
  567. $updraft_retain_db = UpdraftPlus_Options::get_updraft_option('updraft_retain_db', $updraft_retain);
  568. $updraft_retain_db = (is_numeric($updraft_retain_db)) ? $updraft_retain_db : 1;
  569. $this->log("Retain db: user setting: number to retain = $updraft_retain_db");
  570. // Returns an array, most recent first, of backup sets
  571. $backup_history = $this->get_backup_history();
  572. $db_backups_found = 0;
  573. $file_backups_found = 0;
  574. $this->log("Number of backup sets in history: ".count($backup_history));
  575. foreach ($backup_history as $backup_datestamp => $backup_to_examine) {
  576. // $backup_to_examine is an array of file names, keyed on db/plugins/themes/uploads
  577. // The new backup_history array is saved afterwards, so remember to unset the ones that are to be deleted
  578. $this->log("Examining backup set with datestamp: $backup_datestamp");
  579. if (isset($backup_to_examine['db'])) {
  580. $db_backups_found++;
  581. $this->log("$backup_datestamp: this set includes a database (".$backup_to_examine['db']."); db count is now $db_backups_found");
  582. if ($db_backups_found > $updraft_retain_db) {
  583. $this->log("$backup_datestamp: over retain limit ($updraft_retain_db); will delete this database");
  584. $dofile = $backup_to_examine['db'];
  585. if (!empty($dofile)) $this->prune_file($service, $dofile, $backup_method_object, $backup_passback);
  586. unset($backup_to_examine['db']);
  587. }
  588. }
  589. if (isset($backup_to_examine['plugins']) || isset($backup_to_examine['themes']) || isset($backup_to_examine['uploads']) || isset($backup_to_examine['others'])) {
  590. $file_backups_found++;
  591. $this->log("$backup_datestamp: this set includes files; fileset count is now $file_backups_found");
  592. if ($file_backups_found > $updraft_retain) {
  593. $this->log("$backup_datestamp: over retain limit ($updraft_retain); will delete this file set");
  594. $file = isset($backup_to_examine['plugins']) ? $backup_to_examine['plugins'] : "";
  595. $file2 = isset($backup_to_examine['themes']) ? $backup_to_examine['themes'] : "";
  596. $file3 = isset($backup_to_examine['uploads']) ? $backup_to_examine['uploads'] : "";
  597. $file4 = isset($backup_to_examine['others']) ? $backup_to_examine['others'] : "";
  598. foreach (array($file, $file2, $file3, $file4) as $dofile) {
  599. if (!empty($dofile)) $this->prune_file($service, $dofile, $backup_method_object, $backup_passback);
  600. }
  601. unset($backup_to_examine['plugins']);
  602. unset($backup_to_examine['themes']);
  603. unset($backup_to_examine['uploads']);
  604. unset($backup_to_examine['others']);
  605. }
  606. }
  607. // Delete backup set completely if empty, o/w just remove DB
  608. if (count($backup_to_examine) == 0 || (count($backup_to_examine) == 1 && isset($backup_to_examine['nonce']))) {
  609. $this->log("$backup_datestamp: this backup set is now empty; will remove from history");
  610. unset($backup_history[$backup_datestamp]);
  611. if (isset($backup_to_examine['nonce'])) {
  612. $fullpath = $this->backups_dir_location().'/log.'.$backup_to_examine['nonce'].'.txt';
  613. if (is_file($fullpath)) {
  614. $this->log("$backup_datestamp: deleting log file (log.".$backup_to_examine['nonce'].".txt)");
  615. @unlink($fullpath);
  616. } else {
  617. $this->log("$backup_datestamp: corresponding log file not found - must have already been deleted");
  618. }
  619. } else {
  620. $this->log("$backup_datestamp: no nonce record found in the backup set, so cannot delete any remaining log file");
  621. }
  622. } else {
  623. $this->log("$backup_datestamp: this backup set remains non-empty; will retain in history");
  624. $backup_history[$backup_datestamp] = $backup_to_examine;
  625. }
  626. }
  627. $this->log("Retain: saving new backup history (sets now: ".count($backup_history).") and finishing retain operation");
  628. UpdraftPlus_Options::update_updraft_option('updraft_backup_history',$backup_history);
  629. }
  630. function delete_local($file) {
  631. if(UpdraftPlus_Options::get_updraft_option('updraft_delete_local')) {
  632. $this->log("Deleting local file: $file");
  633. //need error checking so we don't delete what isn't successfully uploaded?
  634. $fullpath = $this->backups_dir_location().'/'.$file;
  635. return unlink($fullpath);
  636. }
  637. return true;
  638. }
  639. function reschedule($how_far_ahead) {
  640. // Reschedule - remove presently scheduled event
  641. wp_clear_scheduled_hook('updraft_backup_resume', array($this->current_resumption + 1, $this->nonce));
  642. // Add new event
  643. if ($how_far_ahead < 200) $how_far_ahead=200;
  644. $schedule_for = time() + $how_far_ahead;
  645. wp_schedule_single_event($schedule_for, 'updraft_backup_resume', array($this->current_resumption + 1, $this->nonce));
  646. $this->newresumption_scheduled = $schedule_for;
  647. }
  648. function increase_resume_and_reschedule($howmuch = 120) {
  649. $resume_interval = $this->jobdata_get('resume_interval');
  650. if (!is_numeric($resume_interval) || $resume_interval<200) { $resume_interval = 200; }
  651. if ($this->newresumption_scheduled != false) $this->reschedule($resume_interval+$howmuch);
  652. $this->jobdata_set('resume_interval', $resume_interval+$howmuch);
  653. $this->log("To decrease the likelihood of overlaps, increasing resumption interval to: ".($resume_interval+$howmuch));
  654. }
  655. function create_zip($create_from_dir, $whichone, $create_in_dir, $backup_file_basename) {
  656. // Note: $create_from_dir can be an array or a string
  657. @set_time_limit(900);
  658. if ($whichone != "others") $this->log("Beginning creation of dump of $whichone");
  659. $full_path = $create_in_dir.'/'.$backup_file_basename.'-'.$whichone.'.zip';
  660. if (file_exists($full_path)) {
  661. $this->log("$backup_file_basename-$whichone.zip: this file has already been created");
  662. return basename($full_path);
  663. }
  664. // Temporary file, to be able to detect actual completion (upon which, it is renamed)
  665. // Firstly, make sure that the temporary file is not already being written to - which can happen if a resumption takes place whilst an old run is still active
  666. $zip_name = $full_path.'.tmp';
  667. $time_now = time();
  668. $time_mod = (int)@filemtime($zip_name);
  669. if (file_exists($zip_name) && $time_mod>100 && ($time_now-$time_mod)<30) {
  670. $file_size = filesize($zip_name);
  671. $this->log("Terminate: the temporary file $zip_name already exists, and was modified within the last 30 seconds (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod).", size=$file_size). This likely means that another UpdraftPlus run is still at work; so we will exit.");
  672. $this->increase_resume_and_reschedule(120);
  673. die;
  674. } elseif (file_exists($zip_name)) {
  675. $this->log("File exists ($zip_name), but was apparently not modified within the last 30 seconds, so we assume that any previous run has now terminated (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod).")");
  676. }
  677. $microtime_start = microtime(true);
  678. # The paths in the zip should then begin with '$whichone', having removed WP_CONTENT_DIR from the front
  679. $zipcode = $this->make_zipfile($create_from_dir, $zip_name);
  680. if ($zipcode !== true) {
  681. $this->log("ERROR: Zip failure: /*Could not create*/ $whichone zip: code=$zipcode");
  682. $this->error("Could not create $whichone zip: code $zipcode. Consult the log file for more information.");
  683. return false;
  684. } else {
  685. rename($full_path.'.tmp', $full_path);
  686. $timetaken = max(microtime(true)-$microtime_start, 0.000001);
  687. $kbsize = filesize($full_path)/1024;
  688. $rate = round($kbsize/$timetaken, 1);
  689. $this->log("Created $whichone zip - file size is ".round($kbsize,1)." Kb in ".round($timetaken,1)." s ($rate Kb/s)");
  690. }
  691. return basename($full_path);
  692. }
  693. // This function is resumable
  694. function backup_dirs($transient_status) {
  695. if(!$this->backup_time) $this->backup_time_nonce();
  696. $updraft_dir = $this->backups_dir_location();
  697. if(!is_writable($updraft_dir)) {
  698. $this->log("Backup directory ($updraft_dir) is not writable, or does not exist");
  699. $this->error("Backup directory ($updraft_dir) is not writable, or does not exist.");
  700. return array();
  701. }
  702. //get the blog name and rip out all non-alphanumeric chars other than _
  703. $blog_name = str_replace(' ','_',substr(get_bloginfo(), 0, 96));
  704. $blog_name = preg_replace('/[^A-Za-z0-9_]/','', $blog_name);
  705. if(!$blog_name) $blog_name = 'non_alpha_name';
  706. $backup_file_basename = 'backup_'.date('Y-m-d-Hi', $this->backup_time).'_'.$blog_name.'_'.$this->nonce;
  707. $backup_array = array();
  708. $wp_themes_dir = WP_CONTENT_DIR.'/themes';
  709. $wp_upload_dir = wp_upload_dir();
  710. $wp_upload_dir = $wp_upload_dir['basedir'];
  711. $wp_plugins_dir = WP_PLUGIN_DIR;
  712. $possible_backups = array ('plugins' => $wp_plugins_dir, 'themes' => $wp_themes_dir, 'uploads' => $wp_upload_dir);
  713. # Plugins, themes, uploads
  714. foreach ($possible_backups as $youwhat => $whichdir) {
  715. if (UpdraftPlus_Options::get_updraft_option("updraft_include_$youwhat", true)) {
  716. if ($transient_status == 'finished') {
  717. $backup_array[$youwhat] = $backup_file_basename.'-'.$youwhat.'.zip';
  718. } else {
  719. $created = $this->create_zip($whichdir, $youwhat, $updraft_dir, $backup_file_basename);
  720. if ($created) $backup_array[$youwhat] = $created;
  721. }
  722. } else {
  723. $this->log("No backup of $youwhat: excluded by user's options");
  724. }
  725. }
  726. # Others
  727. if (UpdraftPlus_Options::get_updraft_option('updraft_include_others', true)) {
  728. if ($transient_status == 'finished') {
  729. $backup_array['others'] = $backup_file_basename.'-others.zip';
  730. } else {
  731. $this->log("Beginning backup of other directories found in the content directory");
  732. // http://www.phpconcept.net/pclzip/user-guide/53
  733. /* First parameter to create is:
  734. An array of filenames or dirnames,
  735. or
  736. A string containing the filename or a dirname,
  737. or
  738. A string containing a list of filename or dirname separated by a comma.
  739. */
  740. # Initialise
  741. $other_dirlist = array();
  742. $others_skip = preg_split("/,/",UpdraftPlus_Options::get_updraft_option('updraft_include_others_exclude', UPDRAFT_DEFAULT_OTHERS_EXCLUDE));
  743. # Make the values into the keys
  744. $others_skip = array_flip($others_skip);
  745. $this->log('Looking for candidates to back up in: '.WP_CONTENT_DIR);
  746. if ($handle = opendir(WP_CONTENT_DIR)) {
  747. while (false !== ($entry = readdir($handle))) {
  748. $candidate = WP_CONTENT_DIR.'/'.$entry;
  749. if ($entry == "." || $entry == "..") { ; }
  750. elseif ($candidate == $updraft_dir) { $this->log("others: $entry: skipping: this is the updraft directory"); }
  751. elseif ($candidate == $wp_themes_dir) { $this->log("others: $entry: skipping: this is the themes directory"); }
  752. elseif ($candidate == $wp_upload_dir) { $this->log("others: $entry: skipping: this is the uploads directory"); }
  753. elseif ($candidate == $wp_plugins_dir) { $this->log("others: $entry: skipping: this is the plugins directory"); }
  754. elseif (isset($others_skip[$entry])) { $this->log("others: $entry: skipping: excluded by options"); }
  755. else { $this->log("others: $entry: adding to list"); array_push($other_dirlist, $candidate); }
  756. }
  757. @closedir($handle);
  758. } else {
  759. $this->log('ERROR: Could not read the content directory: '.WP_CONTENT_DIR);
  760. $this->error('Could not read the content directory: '.WP_CONTENT_DIR);
  761. }
  762. if (count($other_dirlist)>0) {
  763. $created = $this->create_zip($other_dirlist, 'others', $updraft_dir, $backup_file_basename);
  764. if ($created) $backup_array['others'] = $created;
  765. } else {
  766. $this->log("No backup of other directories: there was nothing found to back up");
  767. }
  768. # If we are not already finished
  769. }
  770. } else {
  771. $this->log("No backup of other directories: excluded by user's options");
  772. }
  773. return $backup_array;
  774. }
  775. function save_backup_history($backup_array) {
  776. if(is_array($backup_array)) {
  777. $backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history');
  778. $backup_history = (is_array($backup_history)) ? $backup_history : array();
  779. $backup_array['nonce'] = $this->nonce;
  780. $backup_history[$this->backup_time] = $backup_array;
  781. UpdraftPlus_Options::update_updraft_option('updraft_backup_history',$backup_history);
  782. } else {
  783. $this->log('Could not save backup history because we have no backup array. Backup probably failed.');
  784. $this->error('Could not save backup history because we have no backup array. Backup probably failed.');
  785. }
  786. }
  787. function get_backup_history() {
  788. //$backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history');
  789. //by doing a raw DB query to get the most up-to-date data from this option we slightly narrow the window for the multiple-cron race condition
  790. global $wpdb;
  791. $backup_history = @unserialize($wpdb->get_var($wpdb->prepare("SELECT option_value from $wpdb->options WHERE option_name='updraft_backup_history'")));
  792. if(is_array($backup_history)) {
  793. krsort($backup_history); //reverse sort so earliest backup is last on the array. Then we can array_pop.
  794. } else {
  795. $backup_history = array();
  796. }
  797. return $backup_history;
  798. }
  799. // Open a file, store its filehandle
  800. function backup_db_open($file, $allow_gz = true) {
  801. if (function_exists('gzopen') && $allow_gz == true) {
  802. $this->dbhandle = @gzopen($file, 'w');
  803. $this->dbhandle_isgz = true;
  804. } else {
  805. $this->dbhandle = @fopen($file, 'w');
  806. $this->dbhandle_isgz = false;
  807. }
  808. if(!$this->dbhandle) {
  809. $this->log("ERROR: $file: Could not open the backup file for writing");
  810. $this->error("$file: Could not open the backup file for writing");
  811. }
  812. }
  813. function backup_db_header() {
  814. //Begin new backup of MySql
  815. $this->stow("# " . 'WordPress MySQL database backup' . "\n");
  816. $this->stow("#\n");
  817. $this->stow("# " . sprintf(__('Generated: %s','wp-db-backup'),date("l j. F Y H:i T")) . "\n");
  818. $this->stow("# " . sprintf(__('Hostname: %s','wp-db-backup'),DB_HOST) . "\n");
  819. $this->stow("# " . sprintf(__('Database: %s','wp-db-backup'),$this->backquote(DB_NAME)) . "\n");
  820. $this->stow("# --------------------------------------------------------\n");
  821. if (defined("DB_CHARSET")) {
  822. $this->stow("/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n");
  823. $this->stow("/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n");
  824. $this->stow("/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n");
  825. $this->stow("/*!40101 SET NAMES " . DB_CHARSET . " */;\n");
  826. }
  827. $this->stow("/*!40101 SET foreign_key_checks = 0 */;\n");
  828. }
  829. /* This function is resumable, using the following method:
  830. - Each table is written out to ($final_filename).table.tmp
  831. - When the writing finishes, it is renamed to ($final_filename).table
  832. - When all tables are finished, they are concatenated into the final file
  833. */
  834. function backup_db($already_done = "begun") {
  835. // Get the file prefix
  836. $updraft_dir = $this->backups_dir_location();
  837. if(!$this->backup_time) $this->backup_time_nonce();
  838. if (!$this->opened_log_time) $this->logfile_open($this->nonce);
  839. // Get the blog name and rip out all non-alphanumeric chars other than _
  840. $blog_name = preg_replace('/[^A-Za-z0-9_]/','', str_replace(' ','_', substr(get_bloginfo(), 0, 96)));
  841. if (!$blog_name) $blog_name = 'non_alpha_name';
  842. $file_base = 'backup_'.date('Y-m-d-Hi',$this->backup_time).'_'.$blog_name.'_'.$this->nonce;
  843. $backup_file_base = $updraft_dir.'/'.$file_base;
  844. if ('finished' == $already_done) return basename($backup_file_base.'-db.gz');
  845. if ('encrypted' == $already_done) return basename($backup_file_base.'-db.gz.crypt');
  846. $total_tables = 0;
  847. global $table_prefix, $wpdb;
  848. $all_tables = $wpdb->get_results("SHOW TABLES", ARRAY_N);
  849. $all_tables = array_map(create_function('$a', 'return $a[0];'), $all_tables);
  850. if (!is_writable($updraft_dir)) {
  851. $this->log("The backup directory ($updraft_dir) is not writable.");
  852. $this->error("The backup directory ($updraft_dir) is not writable.");
  853. return false;
  854. }
  855. $stitch_files = array();
  856. foreach ($all_tables as $table) {
  857. $total_tables++;
  858. // Increase script execution time-limit to 15 min for every table.
  859. if ( !@ini_get('safe_mode') || strtolower(@ini_get('safe_mode')) == "off") @set_time_limit(15*60);
  860. // The table file may already exist if we have produced it on a previous run
  861. $table_file_prefix = $file_base.'-db-table-'.$table.'.table';
  862. if (file_exists($updraft_dir.'/'.$table_file_prefix.'.gz')) {
  863. $this->log("Table $table: corresponding file already exists; moving on");
  864. } else {
  865. // Open file, store the handle
  866. $this->backup_db_open($updraft_dir.'/'.$table_file_prefix.'.tmp.gz', true);
  867. # === is needed, otherwise 'false' matches (i.e. prefix does not match)
  868. if ( strpos($table, $table_prefix) === 0 ) {
  869. // Create the SQL statements
  870. $this->stow("# --------------------------------------------------------\n");
  871. $this->stow("# " . sprintf(__('Table: %s','wp-db-backup'),$this->backquote($table)) . "\n");
  872. $this->stow("# --------------------------------------------------------\n");
  873. $this->backup_table($table);
  874. } else {
  875. $this->stow("# --------------------------------------------------------\n");
  876. $this->stow("# " . sprintf(__('Skipping non-WP table: %s','wp-db-backup'),$this->backquote($table)) . "\n");
  877. $this->stow("# --------------------------------------------------------\n");
  878. }
  879. // Close file
  880. $this->close($this->dbhandle);
  881. $this->log("Table $table: finishing file (${table_file_prefix}.gz)");
  882. rename($updraft_dir.'/'.$table_file_prefix.'.tmp.gz', $updraft_dir.'/'.$table_file_prefix.'.gz');
  883. }
  884. $stitch_files[] = $table_file_prefix;
  885. }
  886. // Race detection - with zip files now being resumable, these can more easily occur, with two running side-by-side
  887. $backup_final_file_name = $backup_file_base.'-db.gz';
  888. $time_now = time();
  889. $time_mod = (int)@filemtime($backup_final_file_name);
  890. if (file_exists($backup_final_file_name) && $time_mod>100 && ($time_now-$time_mod)<20) {
  891. $file_size = filesize($backup_final_file_name);
  892. $this->log("Terminate: the final database file ($backup_final_file_name) exists, and was modified within the last 20 seconds (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod).", size=$file_size). This likely means that another UpdraftPlus run is at work; so we will exit.");
  893. $this->increase_resume_and_reschedule(120);
  894. die;
  895. } elseif (file_exists($backup_final_file_name)) {
  896. $this->log("The final database file ($backup_final_file_name) exists, but was apparently not modified within the last 20 seconds (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod)."). Thus we assume that another UpdraftPlus terminated; thus we will continue.");
  897. }
  898. // Finally, stitch the files together
  899. $this->backup_db_open($backup_final_file_name, true);
  900. $this->backup_db_header();
  901. // We delay the unlinking because if two runs go concurrently and fail to detect each other (should not happen, but there's no harm in assuming the detection failed) then that leads to files missing from the db dump
  902. $unlink_files = array();
  903. foreach ($stitch_files as $table_file) {
  904. $this->log("{$table_file}.gz: adding to final database dump");
  905. if (!$handle = gzopen($updraft_dir.'/'.$table_file.'.gz', "r")) {
  906. $this->log("Error: Failed to open database file for reading: ${table_file}.gz");
  907. $this->error(" Failed to open database file for reading: ${table_file}.gz");
  908. } else {
  909. while ($line = gzgets($handle, 2048)) { $this->stow($line); }
  910. gzclose($handle);
  911. $unlink_files[] = $updraft_dir.'/'.$table_file.'.gz';
  912. }
  913. }
  914. if (defined("DB_CHARSET")) {
  915. $this->stow("/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n");
  916. $this->stow("/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n");
  917. $this->stow("/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n");
  918. }
  919. $this->log($file_base.'-db.gz: finished writing out complete database file ('.round(filesize($backup_final_file_name)/1024,1).' Kb)');
  920. $this->close($this->dbhandle);
  921. foreach ($unlink_files as $unlink_file) {
  922. @unlink($unlink_file);
  923. }
  924. if (count($this->errors)) {
  925. return false;
  926. } else {
  927. # We no longer encrypt here - because the operation can take long, we made it resumable and moved it to the upload loop
  928. $this->log("Total database tables backed up: $total_tables");
  929. return basename($backup_file_base.'-db.gz');
  930. }
  931. } //wp_db_backup
  932. /**
  933. * Taken partially from phpMyAdmin and partially from
  934. * Alain Wolf, Zurich - Switzerland
  935. * Website: http://restkultur.ch/personal/wolf/scripts/db_backup/
  936. * Modified by Scott Merrill (http://www.skippy.net/)
  937. * to use the WordPress $wpdb object
  938. * @param string $table
  939. * @param string $segment
  940. * @return void
  941. */
  942. function backup_table($table, $segment = 'none') {
  943. global $wpdb;
  944. $microtime = microtime(true);
  945. $total_rows = 0;
  946. $table_structure = $wpdb->get_results("DESCRIBE $table");
  947. if (! $table_structure) {
  948. //$this->error(__('Error getting table details','wp-db-backup') . ": $table");
  949. return false;
  950. }
  951. if(($segment == 'none') || ($segment == 0)) {
  952. // Add SQL statement to drop existing table
  953. $this->stow("\n\n");
  954. $this->stow("#\n");
  955. $this->stow("# " . sprintf(__('Delete any existing table %s','wp-db-backup'),$this->backquote($table)) . "\n");
  956. $this->stow("#\n");
  957. $this->stow("\n");
  958. $this->stow("DROP TABLE IF EXISTS " . $this->backquote($table) . ";\n");
  959. // Table structure
  960. // Comment in SQL-file
  961. $this->stow("\n\n");
  962. $this->stow("#\n");
  963. $this->stow("# " . sprintf(__('Table structure of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
  964. $this->stow("#\n");
  965. $this->stow("\n");
  966. $create_table = $wpdb->get_results("SHOW CREATE TABLE $table", ARRAY_N);
  967. if (false === $create_table) {
  968. $err_msg = sprintf(__('Error with SHOW CREATE TABLE for %s.','wp-db-backup'), $table);
  969. //$this->error($err_msg);
  970. $this->stow("#\n# $err_msg\n#\n");
  971. }
  972. $this->stow($create_table[0][1] . ' ;');
  973. if (false === $table_structure) {
  974. $err_msg = sprintf(__('Error getting table structure of %s','wp-db-backup'), $table);
  975. //$this->error($err_msg);
  976. $this->stow("#\n# $err_msg\n#\n");
  977. }
  978. // Comment in SQL-file
  979. $this->stow("\n\n#\n# " . sprintf(__('Data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n#\n");
  980. }
  981. // In UpdraftPlus, segment is always 'none'
  982. if(($segment == 'none') || ($segment >= 0)) {
  983. $defs = array();
  984. $integer_fields = array();
  985. // $table_structure was from "DESCRIBE $table"
  986. foreach ($table_structure as $struct) {
  987. if ( (0 === strpos($struct->Type, 'tinyint')) || (0 === strpos(strtolower($struct->Type), 'smallint')) ||
  988. (0 === strpos(strtolower($struct->Type), 'mediumint')) || (0 === strpos(strtolower($struct->Type), 'int')) || (0 === strpos(strtolower($struct->Type), 'bigint')) ) {
  989. $defs[strtolower($struct->Field)] = ( null === $struct->Default ) ? 'NULL' : $struct->Default;
  990. $integer_fields[strtolower($struct->Field)] = "1";
  991. }
  992. }
  993. if($segment == 'none') {
  994. $row_start = 0;
  995. $row_inc = 100;
  996. } else {
  997. $row_start = $segment * 100;
  998. $row_inc = 100;
  999. }
  1000. do {
  1001. if ( !@ini_get('safe_mode') || strtolower(@ini_get('safe_mode')) == "off") @set_time_limit(15*60);
  1002. $table_data = $wpdb->get_results("SELECT * FROM $table LIMIT {$row_start}, {$row_inc}", ARRAY_A);
  1003. $entries = 'INSERT INTO ' . $this->backquote($table) . ' VALUES (';
  1004. // \x08\\x09, not required
  1005. $search = array("\x00", "\x0a", "\x0d", "\x1a");
  1006. $replace = array('\0', '\n', '\r', '\Z');
  1007. if($table_data) {
  1008. foreach ($table_data as $row) {
  1009. $total_rows++;
  1010. $values = array();
  1011. foreach ($row as $key => $value) {
  1012. if (isset($integer_fields[strtolower($key)])) {
  1013. // make sure there are no blank spots in the insert syntax,
  1014. // yet try to avoid quotation marks around integers
  1015. $value = ( null === $value || '' === $value) ? $defs[strtolower($key)] : $value;
  1016. $values[] = ( '' === $value ) ? "''" : $value;
  1017. } else {
  1018. $values[] = "'" . str_replace($search, $replace, str_replace('\'', '\\\'', str_replace('\\', '\\\\', $value))) . "'";
  1019. }
  1020. }
  1021. $this->stow(" \n" . $entries . implode(', ', $values) . ');');
  1022. }
  1023. $row_start += $row_inc;
  1024. }
  1025. } while((count($table_data) > 0) and ($segment=='none'));
  1026. }
  1027. if(($segment == 'none') || ($segment < 0)) {
  1028. // Create footer/closing comment in SQL-file
  1029. $this->stow("\n");
  1030. $this->stow("#\n");
  1031. $this->stow("# " . sprintf(__('End of data contents of table %s','wp-db-backup'),$this->backquote($table)) . "\n");
  1032. $this->stow("# --------------------------------------------------------\n");
  1033. $this->stow("\n");
  1034. }
  1035. $this->log("Table $table: Total rows added: $total_rows in ".sprintf("%.02f",max(microtime(true)-$microtime,0.00001))." seconds");
  1036. } // end backup_table()
  1037. function stow($query_line) {
  1038. if ($this->dbhandle_isgz) {
  1039. if(! @gzwrite($this->dbhandle, $query_line)) {
  1040. //$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg);
  1041. }
  1042. } else {
  1043. if(false === @fwrite($this->dbhandle, $query_line)) {
  1044. //$this->error(__('There was an error writing a line to the backup script:','wp-db-backup') . ' ' . $query_line . ' ' . $php_errormsg);
  1045. }
  1046. }
  1047. }
  1048. function close($handle) {
  1049. if ($this->dbhandle_isgz) {
  1050. gzclose($handle);
  1051. } else {
  1052. fclose($handle);
  1053. }
  1054. }
  1055. function error($error) {
  1056. if (count($this->errors) == 0) $this->log("An error condition has occurred for the first time on this run");
  1057. $this->errors[] = $error;
  1058. return true;
  1059. }
  1060. /**
  1061. * Add backquotes to tables and db-names in
  1062. * SQL queries. Taken from phpMyAdmin.
  1063. */
  1064. function backquote($a_name) {
  1065. if (!empty($a_name) && $a_name != '*') {
  1066. if (is_array($a_name)) {
  1067. $result = array();
  1068. reset($a_name);
  1069. while(list($key, $val) = each($a_name))
  1070. $result[$key] = '`' . $val . '`';
  1071. return $result;
  1072. } else {
  1073. return '`' . $a_name . '`';
  1074. }
  1075. } else {
  1076. return $a_name;
  1077. }
  1078. }
  1079. /*END OF WP-DB-BACKUP BLOCK */
  1080. function hourminute($pot) {
  1081. if (preg_match("/^[0-2][0-9]:[0-5][0-9]$/", $pot)) return $pot;
  1082. if ('' == $pot) return date('H:i', time()+300);
  1083. return '00:00';
  1084. }
  1085. /*
  1086. this function is both the backup scheduler and ostensibly a filter callback for saving the option.
  1087. it is called in the register_setting for the updraft_interval, which means when the admin settings
  1088. are saved it is called. it returns the actual result from wp_filter_nohtml_kses (a sanitization filter)
  1089. so the option can be properly saved.
  1090. */
  1091. function schedule_backup($interval) {
  1092. //clear schedule and add new so we don't stack up scheduled backups
  1093. wp_clear_scheduled_hook('updraft_backup');
  1094. switch($interval) {
  1095. case 'every4hours':
  1096. case 'every8hours':
  1097. case 'twicedaily':
  1098. case 'daily':
  1099. case 'weekly':
  1100. case 'fortnightly':
  1101. case 'monthly':
  1102. $first_time = apply_filters('updraftplus_schedule_start_files', time()+30);
  1103. wp_schedule_event($first_time, $interval, 'updraft_backup');
  1104. break;
  1105. }
  1106. return wp_filter_nohtml_kses($interval);
  1107. }
  1108. // Acts as a WordPress options filter
  1109. function googledrive_clientid_checkchange($client_id) {
  1110. if (UpdraftPlus_Options::get_updraft_option('updraft_googledrive_token') != '' && UpdraftPlus_Options::get_updraft_option('updraft_googledrive_token') != $client_id) {
  1111. require_once(UPDRAFTPLUS_DIR.'/methods/googledrive.php');
  1112. UpdraftPlus_BackupModule_googledrive::gdrive_auth_revoke(true);
  1113. }
  1114. return $client_id;
  1115. }
  1116. function schedule_backup_database($interval) {
  1117. //clear schedule and add new so we don't stack up scheduled backups
  1118. wp_clear_scheduled_hook('updraft_backup_database');
  1119. switch($interval) {
  1120. case 'every4hours':
  1121. case 'every8hours':
  1122. case 'twicedaily':
  1123. case 'daily':
  1124. case 'weekly':
  1125. case 'fortnightly':
  1126. case 'monthly':
  1127. $first_time = apply_filters('updraftplus_schedule_start_db', time()+30);
  1128. wp_schedule_event($first_time, $interval, 'updraft_backup_database');
  1129. break;
  1130. }
  1131. return wp_filter_nohtml_kses($interval);
  1132. }
  1133. //wp-cron only has hourly, daily and twicedaily, so we need to add some of our own
  1134. function modify_cron_schedules($schedules) {
  1135. $schedules['weekly'] = array( 'interval' => 604800, 'display' => 'Once Weekly' );
  1136. $schedules['fortnightly'] = array( 'interval' => 1209600, 'display' => 'Once Each Fortnight' );
  1137. $schedules['monthly'] = array( 'interval' => 2592000, 'display' => 'Once Monthly' );
  1138. $schedules['every4hours'] = array( 'interval' => 14400, 'display' => 'Every 4 hours' );
  1139. $schedules['every8hours'] = array( 'interval' => 28800, 'display' => 'Every 8 hours' );
  1140. return $schedules;
  1141. }
  1142. function backups_dir_location() {
  1143. if (!empty($this->backup_dir)) return $this->backup_dir;
  1144. $updraft_dir = untrailingslashit(UpdraftPlus_Options::get_updraft_option('updraft_dir'));
  1145. $default_backup_dir = WP_CONTENT_DIR.'/updraft';
  1146. //if the option isn't set, default it to /backups inside the upload dir
  1147. $updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
  1148. //check for the existence of the dir and an enumeration preventer.
  1149. if(!is_dir($updraft_dir) || !is_file($updraft_dir.'/index.html') || !is_file($updraft_dir.'/.htaccess')) {
  1150. @mkdir($updraft_dir, 0775, true);
  1151. @file_put_contents($updraft_dir.'/index.html','Nothing to see here.');
  1152. @file_put_contents($updraft_dir.'/.htaccess','deny from all');
  1153. }
  1154. $this->backup_dir = $updraft_dir;
  1155. return $updraft_dir;
  1156. }
  1157. // Called via AJAX
  1158. function updraft_ajax_handler() {
  1159. // Test the nonce (probably not needed, since we're presumably admin-authed, but there's no harm)
  1160. $nonce = (empty($_REQUEST['nonce'])) ? "" : $_REQUEST['nonce'];
  1161. if (! wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce') || empty($_REQUEST['subaction'])) die('Security check');
  1162. if ('lastlog' == $_GET['subaction']) {
  1163. echo htmlspecialchars(UpdraftPlus_Options::get_updraft_option('updraft_lastmessage', '(Nothing yet logged)'));
  1164. } elseif ($_POST['subaction'] == 'credentials_test') {
  1165. $method = (preg_match("/^[a-z0-9]+$/", $_POST['method'])) ? $_POST['method'] : "";
  1166. // Test the credentials, return a code
  1167. require_once(UPDRAFTPLUS_DIR."/methods/$method.php");
  1168. $objname = "UpdraftPlus_BackupModule_${method}";
  1169. if (method_exists($objname, "credentials_test")) call_user_func(array('UpdraftPlus_BackupModule_'.$method, 'credentials_test'));
  1170. }
  1171. die;
  1172. }
  1173. function updraft_download_backup() {
  1174. $type = $_POST['type'];
  1175. $timestamp = (int)$_POST['timestamp'];
  1176. $backup_history = $this->get_backup_history();
  1177. $file = $backup_history[$timestamp][$type];
  1178. $fullpath = $this->backups_dir_location().'/'.$file;
  1179. if(!is_readable($fullpath)) {
  1180. //if the file doesn't exist and they're using one of the cloud options, fetch it down from the cloud.
  1181. $this->download_backup($file);
  1182. }
  1183. if(@is_readable($fullpath) && is_file($fullpath)) {
  1184. $len = filesize($fullpath);
  1185. $filearr = explode('.',$file);
  1186. // //we've only got zip and gz...for now
  1187. $file_ext = array_pop($filearr);
  1188. if($file_ext == 'zip') {
  1189. header('Content-type: application/zip');
  1190. } else {
  1191. // This catches both when what was popped was 'crypt' (*-db.gz.crypt) and when it was 'gz' (unencrypted)
  1192. header('Content-type: application/x-gzip');
  1193. }
  1194. header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
  1195. header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
  1196. header("Content-Length: $len;");
  1197. if ($file_ext == 'crypt') {
  1198. header("Content-Disposition: attachment; filename=\"".substr($file,0,-6)."\";");
  1199. } else {
  1200. header("Content-Disposition: attachment; filename=\"$file\";");
  1201. }
  1202. ob_end_flush();
  1203. if ($file_ext == 'crypt') {
  1204. $encryption = UpdraftPlus_Options::get_updraft_option('updraft_encryptionphrase');
  1205. if ($encryption == "") {
  1206. $this->error('Decryption of database failed: the database file is encrypted, but you have no encryption key entered.');
  1207. } else {
  1208. require_once(dirname(__FILE__).'/includes/Rijndael.php');
  1209. $rijndael = new Crypt_Rijndael();
  1210. $rijndael->setKey($encryption);
  1211. $in_handle = fopen($fullpath,'r');
  1212. $ciphertext = "";
  1213. while (!feof ($in_handle)) {
  1214. $ciphertext .= fread($in_handle, 16384);
  1215. }
  1216. fclose ($in_handle);
  1217. print $rijndael->decrypt($ciphertext);
  1218. }
  1219. } else {
  1220. readfile($fullpath);
  1221. }
  1222. $this->delete_local($file);
  1223. exit; //we exit immediately because otherwise admin-ajax appends an additional zero to the end
  1224. } else {
  1225. echo 'Download failed. File '.$fullpath.' did not exist or was unreadable. If you delete local backups then remote retrieval may have failed.';
  1226. }
  1227. }
  1228. function download_backup($file) {
  1229. $service = UpdraftPlus_Options::get_updraft_option('updraft_service');
  1230. $method_include = UPDRAFTPLUS_DIR.'/methods/'.$service.'.php';
  1231. if (file_exists($method_include)) require_once($method_include);
  1232. $objname = "UpdraftPlus_BackupModule_${service}";
  1233. if (method_exists($objname, "download")) {
  1234. $remote_obj = new $objname;
  1235. $remote_obj->download($file);
  1236. } else {
  1237. $this->error("Automatic backup restoration is not available with the method: $service.");
  1238. }
  1239. }
  1240. function restore_backup($timestamp) {
  1241. global $wp_filesystem;
  1242. $backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history');
  1243. if(!is_array($backup_history[$timestamp])) {
  1244. echo '<p>This backup does not exist in the backup history - restoration aborted. Timestamp: '.$timestamp.'</p><br/>';
  1245. return false;
  1246. }
  1247. $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_restore&backup_timestamp=$timestamp");
  1248. WP_Filesystem($credentials);
  1249. if ( $wp_filesystem->errors->get_error_code() ) {
  1250. foreach ( $wp_filesystem->errors->get_error_messages() as $message )
  1251. show_message($message);
  1252. exit;
  1253. }
  1254. //if we make it this far then WP_Filesystem has been instantiated and is functional (tested with ftpext, what about suPHP and other situations where direct may work?)
  1255. echo '<span style="font-weight:bold">Restoration Progress</span><div id="updraft-restore-progress">';
  1256. $updraft_dir = $this->backups_dir_location().'/';
  1257. foreach($backup_history[$timestamp] as $type => $file) {
  1258. if ($type == 'nonce') continue;
  1259. $fullpath = $updraft_dir.$file;
  1260. if(!is_readable($fullpath) && $type != 'db') {
  1261. $this->download_backup($file);
  1262. }
  1263. # Types: uploads, themes, plugins, others, db
  1264. if(is_readable($fullpath) && $type != 'db') {
  1265. if(!class_exists('WP_Upgrader')) require_once(ABSPATH . 'wp-admin/includes/class-wp-upgrader.php');
  1266. require_once(UPDRAFTPLUS_DIR.'/includes/updraft-restorer.php');
  1267. $restorer = new Updraft_Restorer();
  1268. $val = $restorer->restore_backup($fullpath, $type);
  1269. if(is_wp_error($val)) {
  1270. print_r($val);
  1271. echo '</div>'; //close the updraft_restore_progress div even if we error
  1272. return false;
  1273. }
  1274. }
  1275. }
  1276. echo '</div>'; //close the updraft_restore_progress div
  1277. # The 'off' check is for badly configured setups - http://wordpress.org/support/topic/plugin-wp-super-cache-warning-php-safe-mode-enabled-but-safe-mode-is-off
  1278. if(@ini_get('safe_mode') && strtolower(@ini_get('safe_mode')) != "off") {
  1279. echo "<p>DB could not be restored because PHP safe_mode is active on your server. You will need to manually restore the file via phpMyAdmin or another method.</p><br/>";
  1280. return false;
  1281. }
  1282. return true;
  1283. }
  1284. //deletes the -old directories that are created when a backup is restored.
  1285. function delete_old_dirs() {
  1286. global $wp_filesystem;
  1287. $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_delete_old_dirs");
  1288. WP_Filesystem($credentials);
  1289. if ( $wp_filesystem->errors->get_error_code() ) {
  1290. foreach ( $wp_filesystem->errors->get_error_messages() as $message )
  1291. show_message($message);
  1292. exit;
  1293. }
  1294. $to_delete = array('themes-old','plugins-old','uploads-old','others-old');
  1295. foreach($to_delete as $name) {
  1296. //recursively delete
  1297. if(!$wp_filesystem->delete(WP_CONTENT_DIR.'/'.$name, true)) {
  1298. return false;
  1299. }
  1300. }
  1301. return true;
  1302. }
  1303. //scans the content dir to see if any -old dirs are present
  1304. function scan_old_dirs() {
  1305. $dirArr = scandir(WP_CONTENT_DIR);
  1306. foreach($dirArr as $dir) {
  1307. if(strpos($dir,'-old') !== false) {
  1308. return true;
  1309. }
  1310. }
  1311. return false;
  1312. }
  1313. function retain_range($input) {
  1314. $input = (int)$input;
  1315. if($input > 0 && $input < 3650) {
  1316. return $input;
  1317. } else {
  1318. return 1;
  1319. }
  1320. }
  1321. function create_backup_dir() {
  1322. global $wp_filesystem;
  1323. $credentials = request_filesystem_credentials("options-general.php?page=updraftplus&action=updraft_create_backup_dir");
  1324. WP_Filesystem($credentials);
  1325. if ( $wp_filesystem->errors->get_error_code() ) {
  1326. foreach ( $wp_filesystem->errors->get_error_messages() as $message ) show_message($message);
  1327. exit;
  1328. }
  1329. $updraft_dir = $this->backups_dir_location();
  1330. $default_backup_dir = WP_CONTENT_DIR.'/updraft';
  1331. $updraft_dir = ($updraft_dir)?$updraft_dir:$default_backup_dir;
  1332. //chmod the backup dir to 0777. ideally we'd rather chgrp it but i'm not sure if it's possible to detect the group apache is running under (or what if it's not apache...)
  1333. if(!$wp_filesystem->mkdir($updraft_dir, 0777)) return false;
  1334. return true;
  1335. }
  1336. function memory_check_current() {
  1337. # Returns in megabytes
  1338. $memory_limit = ini_get('memory_limit');
  1339. $memory_unit = $memory_limit[strlen($memory_limit)-1];
  1340. $memory_limit = substr($memory_limit,0,strlen($memory_limit)-1);
  1341. switch($memory_unit) {
  1342. case 'K':
  1343. $memory_limit = $memory_limit/1024;
  1344. break;
  1345. case 'G':
  1346. $memory_limit = $memory_limit*1024;
  1347. break;
  1348. case 'M':
  1349. //assumed size, no change needed
  1350. break;
  1351. }
  1352. return $memory_limit;
  1353. }
  1354. function memory_check($memory) {
  1355. $memory_limit = $this->memory_check_current();
  1356. return ($memory_limit >= $memory)?true:false;
  1357. }
  1358. function execution_time_check($time) {
  1359. $setting = ini_get('max_execution_time');
  1360. return ( $setting==0 || $setting >= $time) ? true : false;
  1361. }
  1362. function admin_init() {
  1363. if(UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) {
  1364. @ini_set('display_errors',1);
  1365. @error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
  1366. @ini_set('track_errors',1);
  1367. }
  1368. wp_enqueue_script('jquery');
  1369. if (UpdraftPlus_Options::user_can_manage() && UpdraftPlus_Options::get_updraft_option('updraft_service') == "googledrive" && UpdraftPlus_Options::get_updraft_option('updraft_googledrive_clientid','') != '' && UpdraftPlus_Options::get_updraft_option('updraft_googledrive_token','') == '') {
  1370. add_action('admin_notices', array($this,'show_admin_warning_googledrive') );
  1371. }
  1372. if (UpdraftPlus_Options::user_can_manage() && UpdraftPlus_Options::get_updraft_option('updraft_service') == "dropbox" && UpdraftPlus_Options::get_updraft_option('updraft_dropboxtk_request_token','') == '') {
  1373. add_action('admin_notices', array($this,'show_admin_warning_dropbox') );
  1374. }
  1375. }
  1376. function url_start($urls,$url) {
  1377. return ($urls) ? '<a href="http://'.$url.'">' : "";
  1378. }
  1379. function url_end($urls,$url) {
  1380. return ($urls) ? '</a>' : " (http://$url)";
  1381. }
  1382. function wordshell_random_advert($urls) {
  1383. if (defined('UPDRAFTPLUS_PREMIUM')) return "";
  1384. $rad = rand(0,8);
  1385. switch ($rad) {
  1386. case 0:
  1387. return $this->url_start($urls,'updraftplus.com')."Want more features or paid, guaranteed support? Check out UpdraftPlus.Com".$this->url_end($urls,'updraftplus.com');
  1388. break;
  1389. case 1:
  1390. return "Find UpdraftPlus useful? ".$this->url_start($urls,'david.dw-perspective.org.uk/donate')."Please make a donation.".$this->url_end($urls,'david.dw-perspective.org.uk/donate');
  1391. case 2:
  1392. return $this->url_start($urls,'wordshell.net')."Check out WordShell".$this->url_end($urls,'www.wordshell.net')." - manage WordPress from the command line - huge time-saver";
  1393. break;
  1394. case 3:
  1395. return "Want some more useful plugins? ".$this->url_start($urls,'profiles.wordpress.org/DavidAnderson/')."See my WordPress profile page for others.".$this->url_end($urls,'profiles.wordpress.org/DavidAnderson/');
  1396. break;
  1397. case 4:
  1398. return $this->url_start($urls,'www.simbahosting.co.uk')."Need high-quality WordPress hosting from WordPress specialists? (Including automatic backups and 1-click installer). Get it from the creators of UpdraftPlus.".$this->url_end($urls,'www.simbahosting.co.uk');
  1399. break;
  1400. case 5:
  1401. if (!defined('UPDRAFTPLUS_PREMIUM')) {
  1402. return $this->url_start($urls,'updraftplus.com')."Need even more features and support? Check out UpdraftPlus Premium".$this->url_end($urls,'updraftplus.com');
  1403. } else {
  1404. return "Thanks for being an UpdraftPlus premium user. Keep visiting ".$this->url_start($urls,'www.updraftplus.com')."updraftplus.com".$this->url_end($urls,'www.updraftplus.com')." to see what's going on.";
  1405. }
  1406. break;
  1407. case 6:
  1408. return "Need custom WordPress services from experts (including bespoke development)?".$this->url_start($urls,'www.simbahosting.co.uk/s3/products-and-services/wordpress-experts/')." Get them from the creators of UpdraftPlus.".$this->url_end($urls,'www.simbahosting.co.uk/s3/products-and-services/wordpress-experts/');
  1409. break;
  1410. case 7:
  1411. return $this->url_start($urls,'www.updraftplus.com')."Check out UpdraftPlus.Com for help, add-ons and support".$this->url_end($urls,'www.updraftplus.com');
  1412. break;
  1413. case 8:
  1414. return "Want to say thank-you for UpdraftPlus? ".$this->url_start($urls,'updraftplus.com/shop/')." Please buy our very cheap 'no adverts' add-on.".$this->url_end($urls,'updraftplus.com/shop/');
  1415. break;
  1416. }
  1417. }
  1418. function settings_formcontents() {
  1419. $updraft_dir = $this->backups_dir_location();
  1420. ?>
  1421. <table class="form-table" style="width:850px;">
  1422. <tr>
  1423. <th>File backup intervals:</th>
  1424. <td><select name="updraft_interval">
  1425. <?php
  1426. $intervals = array ("manual" => "Manual", 'every4hours' => "Every 4 hours", 'every8hours' => "Every 8 hours", 'twicedaily' => "Every 12 hours", 'daily' => "Daily", 'weekly' => "Weekly", 'fortnightly' => "Fortnightly", 'monthly' => "Monthly");
  1427. foreach ($intervals as $cronsched => $descrip) {
  1428. echo "<option value=\"$cronsched\" ";
  1429. if ($cronsched == UpdraftPlus_Options::get_updraft_option('updraft_interval','manual')) echo 'selected="selected"';
  1430. echo ">$descrip</option>\n";
  1431. }
  1432. ?>
  1433. </select> <?php echo apply_filters('updraftplus_schedule_showfileconfig', '<input type="hidden" name="updraftplus_starttime_files" value="">'); ?>
  1434. and retain this many backups: <?php
  1435. $updraft_retain = UpdraftPlus_Options::get_updraft_option('updraft_retain', 1);
  1436. $updraft_retain = ((int)$updraft_retain > 0) ? (int)$updraft_retain : 1;
  1437. ?> <input type="text" name="updraft_retain" value="<?php echo $updraft_retain ?>" style="width:40px;" />
  1438. </td>
  1439. </tr>
  1440. <tr>
  1441. <th>Database backup intervals:</th>
  1442. <td><select name="updraft_interval_database">
  1443. <?php
  1444. foreach ($intervals as $cronsched => $descrip) {
  1445. echo "<option value=\"$cronsched\" ";
  1446. if ($cronsched == UpdraftPlus_Options::get_updraft_option('updraft_interval_database', UpdraftPlus_Options::get_updraft_option('updraft_interval'))) echo 'selected="selected"';
  1447. echo ">$descrip</option>\n";
  1448. }
  1449. ?>
  1450. </select> <?php echo apply_filters('updraftplus_schedule_showdbconfig', '<input type="hidden" name="updraftplus_starttime_db" value="">'); ?>
  1451. and retain this many backups: <?php
  1452. $updraft_retain_db = UpdraftPlus_Options::get_updraft_option('updraft_retain_db', $updraft_retain);
  1453. $updraft_retain_db = ((int)$updraft_retain_db > 0) ? (int)$updraft_retain_db : 1;
  1454. ?> <input type="text" name="updraft_retain_db" value="<?php echo $updraft_retain_db ?>" style="width:40px" />
  1455. </td>
  1456. </tr>
  1457. <tr class="backup-interval-description">
  1458. <td></td><td><p>If you would like to automatically schedule backups, choose schedules from the dropdowns above. Backups will occur at the intervals specified. If the two schedules are the same, then the two backups will take place together. If you choose &quot;manual&quot; then you must click the &quot;Backup Now!&quot; button whenever you wish a backup to occur.</p>
  1459. <?php echo apply_filters('updraftplus_fixtime_advert', '<p><strong>To fix the time at which a backup should take place, </strong> (e.g. if your server is busy at day and you want to run overnight), <a href="http://updraftplus.com/shop/fix-time/">use the &quot;Fix Time&quot; add-on</a></p>'); ?>
  1460. </td>
  1461. </tr>
  1462. <?php
  1463. # The true (default value if non-existent) here has the effect of forcing a default of on.
  1464. $include_themes = (UpdraftPlus_Options::get_updraft_option('updraft_include_themes',true)) ? 'checked="checked"' : "";
  1465. $include_plugins = (UpdraftPlus_Options::get_updraft_option('updraft_include_plugins',true)) ? 'checked="checked"' : "";
  1466. $include_uploads = (UpdraftPlus_Options::get_updraft_option('updraft_include_uploads',true)) ? 'checked="checked"' : "";
  1467. $include_others = (UpdraftPlus_Options::get_updraft_option('updraft_include_others',true)) ? 'checked="checked"' : "";
  1468. $include_others_exclude = UpdraftPlus_Options::get_updraft_option('updraft_include_others_exclude',UPDRAFT_DEFAULT_OTHERS_EXCLUDE);
  1469. ?>
  1470. <tr>
  1471. <th>Include in files backup:</th>
  1472. <td>
  1473. <input type="checkbox" name="updraft_include_plugins" value="1" <?php echo $include_plugins; ?> /> Plugins<br>
  1474. <input type="checkbox" name="updraft_include_themes" value="1" <?php echo $include_themes; ?> /> Themes<br>
  1475. <input type="checkbox" name="updraft_include_uploads" value="1" <?php echo $include_uploads; ?> /> Uploads<br>
  1476. <input type="checkbox" name="updraft_include_others" value="1" <?php echo $include_others; ?> /> Any other directories found inside wp-content <?php if (is_multisite()) echo "(which on a multisite install includes users' blog contents) "; ?>- but exclude these directories: <input type="text" name="updraft_include_others_exclude" size="44" value="<?php echo htmlspecialchars($include_others_exclude); ?>"/><br>
  1477. Include all of these, unless you are backing them up outside of UpdraftPlus. The above directories are usually everything (except for WordPress core itself which you can download afresh from WordPress.org). But if you have made customised modifications outside of these directories, you need to back them up another way. (<a href="http://wordshell.net">Use WordShell</a> for automatic backup, version control and patching).<br></td>
  1478. </td>
  1479. </tr>
  1480. <tr>
  1481. <th>Email:</th>
  1482. <td><input type="text" style="width:260px" name="updraft_email" value="<?php echo UpdraftPlus_Options::get_updraft_option('updraft_email'); ?>" /> <br>Enter an address here to have a report sent (and the whole backup, if you choose) to it.</td>
  1483. </tr>
  1484. <tr>
  1485. <th>Database encryption phrase:</th>
  1486. <?php
  1487. $updraft_encryptionphrase = UpdraftPlus_Options::get_updraft_option('updraft_encryptionphrase');
  1488. ?>
  1489. <td><input type="text" name="updraft_encryptionphrase" value="<?php echo $updraft_encryptionphrase ?>" style="width:132px" /></td>
  1490. </tr>
  1491. <tr class="backup-crypt-description">
  1492. <td></td><td>If you enter text here, it is used to encrypt backups (Rijndael). <strong>Do make a separate record of it and do not lose it, or all your backups <em>will</em> be useless.</strong> Presently, only the database file is encrypted. This is also the key used to decrypt backups from this admin interface (so if you change it, then automatic decryption will not work until you change it back). You can also use the file example-decrypt.php from inside the UpdraftPlus plugin directory to decrypt manually.</td>
  1493. </tr>
  1494. </table>
  1495. <h2>Copying Your Backup To Remote Storage</h2>
  1496. <table class="form-table" style="width:850px;">
  1497. <tr>
  1498. <th>Choose your remote storage:</th>
  1499. <td><select name="updraft_service" id="updraft-service">
  1500. <?php
  1501. $debug_mode = (UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) ? 'checked="checked"' : "";
  1502. $set = 'selected="selected"';
  1503. // Should be one of s3, dropbox, ftp, googledrive, email, or whatever else is added
  1504. $active_service = UpdraftPlus_Options::get_updraft_option('updraft_service');
  1505. ?>
  1506. <option value="none" <?php
  1507. if ($active_service == "none") echo $set; ?>>None</option>
  1508. <?php
  1509. foreach ($this->backup_methods as $method => $description) {
  1510. echo "<option value=\"$method\"";
  1511. if ($active_service == $method) echo ' '.$set;
  1512. echo '>'.$description;
  1513. echo "</option>\n";
  1514. }
  1515. ?>
  1516. </select></td>
  1517. </tr>
  1518. <?php
  1519. foreach ($this->backup_methods as $method => $description) {
  1520. require_once(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php');
  1521. $call_method = "UpdraftPlus_BackupModule_$method";
  1522. call_user_func(array($call_method, 'config_print'));
  1523. }
  1524. ?>
  1525. </table>
  1526. <script type="text/javascript">
  1527. /* <![CDATA[ */
  1528. var lastlog_lastmessage = "";
  1529. var lastlog_sdata = {
  1530. action: 'updraft_ajax',
  1531. subaction: 'lastlog',
  1532. nonce: '<?php echo wp_create_nonce('updraftplus-credentialtest-nonce'); ?>'
  1533. };
  1534. function updraft_showlastlog(){
  1535. jQuery.get(ajaxurl, lastlog_sdata, function(response) {
  1536. nexttimer = 1500;
  1537. if (lastlog_lastmessage == response) { nexttimer = 4500; }
  1538. window.setTimeout(function(){updraft_showlastlog()}, nexttimer);
  1539. jQuery('#updraft_lastlogcontainer').html(response);
  1540. lastlog_lastmessage = response;
  1541. });
  1542. }
  1543. jQuery(document).ready(function() {
  1544. jQuery('#enableexpertmode').click(function() {
  1545. jQuery('.expertmode').fadeIn();
  1546. return false;
  1547. });
  1548. <?php if (!is_writable($updraft_dir)) echo "jQuery('.backupdirrow').show();\n"; ?>
  1549. window.setTimeout(function(){updraft_showlastlog()}, 1200);
  1550. jQuery('.updraftplusmethod').hide();
  1551. <?php
  1552. if ($active_service) echo "jQuery('.${active_service}').show();";
  1553. foreach ($this->backup_methods as $method => $description) {
  1554. // already done: require_once(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php');
  1555. $call_method = "UpdraftPlus_BackupModule_$method";
  1556. if (method_exists($call_method, 'config_print_javascript_onready')) call_user_func(array($call_method, 'config_print_javascript_onready'));
  1557. }
  1558. ?>
  1559. });
  1560. /* ]]> */
  1561. </script>
  1562. <table class="form-table" style="width:850px;">
  1563. <tr>
  1564. <td colspan="2"><h2>Advanced / Debugging Settings</h2></td>
  1565. </tr>
  1566. <tr>
  1567. <th>Debug mode:</th>
  1568. <td><input type="checkbox" name="updraft_debug_mode" value="1" <?php echo $debug_mode; ?> /> <br>Check this to receive more information and emails on the backup process - useful if something is going wrong. You <strong>must</strong> send me this log if you are filing a bug report.</td>
  1569. </tr>
  1570. <tr>
  1571. <th>Expert settings:</th>
  1572. <td><a id="enableexpertmode" href="#">Show expert settings</a> - click this to show some further options; don't bother with this unless you have a problem or are curious.</td>
  1573. </tr>
  1574. <?php
  1575. $delete_local = UpdraftPlus_Options::get_updraft_option('updraft_delete_local', 1);
  1576. ?>
  1577. <tr class="deletelocal expertmode" style="display:none;">
  1578. <th>Delete local backup:</th>
  1579. <td><input type="checkbox" name="updraft_delete_local" value="1" <?php if ($delete_local) echo 'checked="checked"'; ?>> <br>Uncheck this to prevent deletion of any superfluous backup files from your server after the backup run finishes (i.e. any files despatched remotely will also remain locally, and any files being kept locally will not be subject to the retention limits).</td>
  1580. </tr>
  1581. <tr class="expertmode backupdirrow" style="display:none;">
  1582. <th>Backup directory:</th>
  1583. <td><input type="text" name="updraft_dir" style="width:525px" value="<?php echo htmlspecialchars($updraft_dir); ?>" /></td>
  1584. </tr>
  1585. <tr class="expertmode backupdirrow" style="display:none;">
  1586. <td></td><td><?php
  1587. if(is_writable($updraft_dir)) {
  1588. $dir_info = '<span style="color:green">Backup directory specified is writable, which is good.</span>';
  1589. } else {
  1590. $dir_info = '<span style="color:red">Backup directory specified is <b>not</b> writable, or does not exist. <span style="font-size:110%;font-weight:bold"><a href="options-general.php?page=updraftplus&action=updraft_create_backup_dir">Click here</a></span> to attempt to create the directory and set the permissions. If that is unsuccessful check the permissions on your server or change it to another directory that is writable by your web server process.</span>';
  1591. }
  1592. echo $dir_info ?> This is where UpdraftPlus will write the zip files it creates initially. This directory must be writable by your web server. Typically you'll want to have it inside your wp-content folder (this is the default). <b>Do not</b> place it inside your uploads dir, as that will cause recursion issues (backups of backups of backups of...).</td>
  1593. </tr>
  1594. <tr>
  1595. <td></td>
  1596. <td>
  1597. <?php
  1598. $ws_ad = $this->wordshell_random_advert(1);
  1599. if ($ws_ad) {
  1600. ?>
  1601. <p style="margin: 10px 0; padding: 10px; font-size: 140%; background-color: lightYellow; border-color: #E6DB55; border: 1px solid; border-radius: 4px;">
  1602. <?php echo $ws_ad; ?>
  1603. </p>
  1604. <?php
  1605. }
  1606. ?>
  1607. </td>
  1608. </tr>
  1609. <tr>
  1610. <td></td>
  1611. <td>
  1612. <input type="hidden" name="action" value="update" />
  1613. <input type="submit" class="button-primary" value="Save Changes" />
  1614. </td>
  1615. </tr>
  1616. </table>
  1617. <?php
  1618. }
  1619. function settings_output() {
  1620. /*
  1621. we use request here because the initial restore is triggered by a POSTed form. we then may need to obtain credentials
  1622. for the WP_Filesystem. to do this WP outputs a form that we can't insert variables into (apparently). So the values are
  1623. passed back in as GET parameters. REQUEST covers both GET and POST so this weird logic works.
  1624. */
  1625. if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_restore' && isset($_REQUEST['backup_timestamp'])) {
  1626. $backup_success = $this->restore_backup($_REQUEST['backup_timestamp']);
  1627. if(empty($this->errors) && $backup_success == true) {
  1628. echo '<p>Restore successful!</p><br/>';
  1629. echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus&updraft_restore_success=true">Return to Updraft Configuration</a>.';
  1630. return;
  1631. } else {
  1632. echo '<p>Restore failed...</p><ul>';
  1633. foreach ($this->errors as $err) {
  1634. echo "<li>";
  1635. if (is_string($err)) { echo htmlspecialchars($err); } else {
  1636. print_r($err);
  1637. }
  1638. echo "</li>";
  1639. }
  1640. echo '</ul><b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
  1641. return;
  1642. }
  1643. //uncomment the below once i figure out how i want the flow of a restoration to work.
  1644. //echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
  1645. }
  1646. $deleted_old_dirs = false;
  1647. if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'updraft_delete_old_dirs') {
  1648. if($this->delete_old_dirs()) {
  1649. $deleted_old_dirs = true;
  1650. } else {
  1651. echo '<p>Old directory removal failed for some reason. You may want to do this manually.</p><br/>';
  1652. }
  1653. echo '<p>Old directories successfully removed.</p><br/>';
  1654. echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
  1655. return;
  1656. }
  1657. if(isset($_GET['error'])) {
  1658. $this->show_admin_warning(htmlspecialchars($_GET['error']), 'error');
  1659. }
  1660. if(isset($_GET['message'])) {
  1661. $this->show_admin_warning(htmlspecialchars($_GET['message']));
  1662. }
  1663. if(isset($_GET['action']) && $_GET['action'] == 'updraft_create_backup_dir') {
  1664. if(!$this->create_backup_dir()) {
  1665. echo '<p>Backup directory could not be created...</p><br/>';
  1666. }
  1667. echo '<p>Backup directory successfully created.</p><br/>';
  1668. echo '<b>Actions:</b> <a href="options-general.php?page=updraftplus">Return to Updraft Configuration</a>.';
  1669. return;
  1670. }
  1671. if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup') {
  1672. echo '<div class="updated fade" style="max-width: 800px; font-size:140%; line-height: 140%; padding:14px; clear:left;"><strong>Schedule backup:</strong> ';
  1673. if (wp_schedule_single_event(time()+5, 'updraft_backup_all') === false) {
  1674. $this->log("A backup run failed to schedule");
  1675. echo "Failed.";
  1676. } else {
  1677. echo "OK. Now load any page from your site to make sure the schedule can trigger.";
  1678. $this->log("A backup run has been scheduled");
  1679. }
  1680. echo '</div>';
  1681. }
  1682. // updraft_file_ids is not deleted
  1683. if(isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_all') { $this->boot_backup(true,true); }
  1684. elseif (isset($_POST['action']) && $_POST['action'] == 'updraft_backup_debug_db') { $this->backup_db(); }
  1685. elseif (isset($_POST['action']) && $_POST['action'] == 'updraft_wipesettings') {
  1686. $settings = array('updraft_interval', 'updraft_interval_database', 'updraft_retain', 'updraft_retain_db', 'updraft_encryptionphrase', 'updraft_service', 'updraft_s3_login', 'updraft_s3_pass', 'updraft_s3_remote_path', 'updraft_dropbox_appkey', 'updraft_dropbox_secret', 'updraft_dropbox_folder', 'updraft_googledrive_clientid', 'updraft_googledrive_secret', 'updraft_googledrive_remotepath', 'updraft_ftp_login', 'updraft_ftp_pass', 'updraft_ftp_remote_path', 'updraft_server_address', 'updraft_dir', 'updraft_email', 'updraft_delete_local', 'updraft_debug_mode', 'updraft_include_plugins', 'updraft_include_themes', 'updraft_include_uploads', 'updraft_include_others', 'updraft_include_others_exclude', 'updraft_lastmessage', 'updraft_googledrive_clientid', 'updraft_googledrive_token', 'updraft_dropboxtk_request_token', 'updraft_dropboxtk_access_token', 'updraft_dropbox_folder', 'updraft_last_backup', 'updraft_starttime_files', 'updraft_starttime_db');
  1687. foreach ($settings as $s) {
  1688. UpdraftPlus_Options::delete_updraft_option($s);
  1689. }
  1690. $this->show_admin_warning("Your settings have been wiped.");
  1691. }
  1692. ?>
  1693. <div class="wrap">
  1694. <h1><?php echo $this->plugin_title; ?></h1>
  1695. Maintained by <b>David Anderson</b> (<a href="http://updraftplus.com">UpdraftPlus.Com</a> | <a href="http://david.dw-perspective.org.uk">Author Homepage</a> | <?php if (!defined('UPDRAFTPLUS_PREMIUM')) { ?><a href="http://wordshell.net">WordShell - WordPress command line</a> | <a href="http://david.dw-perspective.org.uk/donate">Donate</a> | <?php } ?><a href="http://wordpress.org/extend/plugins/updraftplus/faq/">FAQs</a> | <a href="http://profiles.wordpress.org/davidanderson/">My other WordPress plugins</a>). Version: <?php echo $this->version; ?>
  1696. <br>
  1697. <?php
  1698. if(isset($_GET['updraft_restore_success'])) {
  1699. echo "<div style=\"color:blue\">Your backup has been restored. Your old themes, uploads, and plugins directories have been retained with \"-old\" appended to their name. Remove them when you are satisfied that the backup worked properly. At this time Updraft does not automatically restore your DB. You will need to use an external tool like phpMyAdmin to perform that task.</div>";
  1700. }
  1701. $ws_advert = $this->wordshell_random_advert(1);
  1702. if ($ws_advert) { echo '<div class="updated fade" style="max-width: 800px; font-size:140%; line-height: 140%; padding:14px; clear:left;">'.$ws_advert.'</div>'; }
  1703. if($deleted_old_dirs) echo '<div style="color:blue">Old directories successfully deleted.</div>';
  1704. if(!$this->memory_check(96)) {?>
  1705. <div style="color:orange">Your PHP memory limit is too low. UpdraftPlus attempted to raise it but was unsuccessful. This plugin may not work properly with a memory limit of less than 96 Mb (though on the other hand, it has been used successfully with a 32Mb limit - your mileage may vary, but don't blame us!). Current limit is: <?php echo $this->memory_check_current(); ?> Mb</div>
  1706. <?php
  1707. }
  1708. if(!$this->execution_time_check(300)) {?>
  1709. <div style="color:orange">Your PHP max_execution_time is less than 300 seconds. This probably means you're running in safe_mode. Either disable safe_mode or modify your php.ini to set max_execution_time to a higher number. If you do not, there is a chance Updraft will be unable to complete a backup. Present limit is: <?php echo ini_get('max_execution_time'); ?> seconds.</div>
  1710. <?php
  1711. }
  1712. if($this->scan_old_dirs()) {?>
  1713. <div style="color:orange">You have old directories from a previous backup. Click to delete them after you have verified that the restoration worked.</div>
  1714. <form method="post" action="<?php echo remove_query_arg(array('updraft_restore_success','action')) ?>">
  1715. <input type="hidden" name="action" value="updraft_delete_old_dirs" />
  1716. <input type="submit" class="button-primary" value="Delete Old Dirs" onclick="return(confirm('Are you sure you want to delete the old directories? This cannot be undone.'))" />
  1717. </form>
  1718. <?php
  1719. }
  1720. if(!empty($this->errors)) {
  1721. foreach($this->errors as $error) {
  1722. // ignoring severity
  1723. echo '<div style="color:red">'.$error['error'].'</div>';
  1724. }
  1725. }
  1726. ?>
  1727. <h2 style="clear:left;">Existing Schedule And Backups</h2>
  1728. <table class="form-table" style="float:left; clear: both; width:545px;">
  1729. <tr>
  1730. <?php
  1731. $updraft_dir = $this->backups_dir_location();
  1732. // UNIX timestamp
  1733. $next_scheduled_backup = wp_next_scheduled('updraft_backup');
  1734. if ($next_scheduled_backup) {
  1735. // Convert to GMT
  1736. $next_scheduled_backup_gmt = gmdate('Y-m-d H:i:s', $next_scheduled_backup);
  1737. // Convert to blog time zone
  1738. $next_scheduled_backup = get_date_from_gmt($next_scheduled_backup_gmt, 'D, F j, Y H:i T');
  1739. } else {
  1740. $next_scheduled_backup = 'No backups are scheduled at this time.';
  1741. }
  1742. $next_scheduled_backup_database = wp_next_scheduled('updraft_backup_database');
  1743. if (UpdraftPlus_Options::get_updraft_option('updraft_interval_database',UpdraftPlus_Options::get_updraft_option('updraft_interval')) == UpdraftPlus_Options::get_updraft_option('updraft_interval')) {
  1744. $next_scheduled_backup_database = "Will take place at the same time as the files backup.";
  1745. } else {
  1746. if ($next_scheduled_backup_database) {
  1747. // Convert to GMT
  1748. $next_scheduled_backup_database_gmt = gmdate('Y-m-d H:i:s', $next_scheduled_backup_database);
  1749. // Convert to blog time zone
  1750. $next_scheduled_backup_database = get_date_from_gmt($next_scheduled_backup_database_gmt, 'D, F j, Y H:i T');
  1751. } else {
  1752. $next_scheduled_backup_database = 'No backups are scheduled at this time.';
  1753. }
  1754. }
  1755. $current_time = get_date_from_gmt(gmdate('Y-m-d H:i:s'), 'D, F j, Y H:i T');
  1756. $updraft_last_backup = UpdraftPlus_Options::get_updraft_option('updraft_last_backup');
  1757. if($updraft_last_backup) {
  1758. if ($updraft_last_backup['success']) {
  1759. // Convert to GMT, then to blog time
  1760. $last_backup = get_date_from_gmt(gmdate('Y-m-d H:i:s', $updraft_last_backup['backup_time']), 'D, F j, Y H:i T');
  1761. } else {
  1762. $last_backup = implode("<br>",$updraft_last_backup['errors']);
  1763. }
  1764. $last_backup_color = ($updraft_last_backup['success']) ? 'green' : 'red';
  1765. if (!empty($updraft_last_backup['backup_nonce'])) {
  1766. $potential_log_file = $updraft_dir."/log.".$updraft_last_backup['backup_nonce'].".txt";
  1767. if (is_readable($potential_log_file)) $last_backup .= "<br><a href=\"?page=updraftplus&action=downloadlog&updraftplus_backup_nonce=".$updraft_last_backup['backup_nonce']."\">Download log file</a>";
  1768. }
  1769. } else {
  1770. $last_backup = 'No backup has been completed.';
  1771. $last_backup_color = 'blue';
  1772. }
  1773. if(is_writable($updraft_dir)) {
  1774. $backup_disabled = "";
  1775. } else {
  1776. $backup_disabled = 'disabled="disabled"';
  1777. }
  1778. ?>
  1779. <th>Time now:</th>
  1780. <td style="color:blue"><?php echo $current_time?></td>
  1781. </tr>
  1782. <tr>
  1783. <th>Next scheduled files backup:</th>
  1784. <td style="color:blue"><?php echo $next_scheduled_backup?></td>
  1785. </tr>
  1786. <tr>
  1787. <th>Next scheduled DB backup:</th>
  1788. <td style="color:blue"><?php echo $next_scheduled_backup_database?></td>
  1789. </tr>
  1790. <tr>
  1791. <th>Last backup:</th>
  1792. <td style="color:<?php echo $last_backup_color ?>"><?php echo $last_backup?></td>
  1793. </tr>
  1794. </table>
  1795. <div style="float:left; width:200px; padding-top: 40px;">
  1796. <form method="post" action="">
  1797. <input type="hidden" name="action" value="updraft_backup" />
  1798. <p><input type="submit" <?php echo $backup_disabled ?> class="button-primary" value="Backup Now!" style="padding-top:2px;padding-bottom:2px;font-size:22px !important" onclick="return(confirm('This will schedule a one-time backup. To trigger the backup you should go ahead, then wait 10 seconds, then visit any page on your site. WordPress should then start the backup running in the background.'))"></p>
  1799. </form>
  1800. <div style="position:relative">
  1801. <div style="position:absolute;top:0;left:0">
  1802. <?php
  1803. $backup_history = UpdraftPlus_Options::get_updraft_option('updraft_backup_history');
  1804. $backup_history = (is_array($backup_history))?$backup_history:array();
  1805. $restore_disabled = (count($backup_history) == 0) ? 'disabled="disabled"' : "";
  1806. ?>
  1807. <input type="button" class="button-primary" <?php echo $restore_disabled ?> value="Restore" style="padding-top:2px;padding-bottom:2px;font-size:22px !important" onclick="jQuery('#backup-restore').fadeIn('slow');jQuery(this).parent().fadeOut('slow')">
  1808. </div>
  1809. <div style="display:none;position:absolute;top:0;left:0" id="backup-restore">
  1810. <form method="post" action="">
  1811. <b>Choose: </b>
  1812. <select name="backup_timestamp" style="display:inline">
  1813. <?php
  1814. foreach($backup_history as $key=>$value) {
  1815. echo "<option value='$key'>".date('Y-m-d G:i',$key)."</option>\n";
  1816. }
  1817. ?>
  1818. </select>
  1819. <input type="hidden" name="action" value="updraft_restore" />
  1820. <input type="submit" <?php echo $restore_disabled ?> class="button-primary" value="Restore Now!" style="padding-top:7px;margin-top:5px;padding-bottom:7px;font-size:24px !important" onclick="return(confirm('Restoring from backup will replace this site\'s themes, plugins, uploads and other content directories (according to what is contained in the backup set which you select). Database restoration cannot be done through this process - you must download the database and import yourself (e.g. through PHPMyAdmin). Do you wish to continue with the restoration process?'))" />
  1821. </form>
  1822. </div>
  1823. </div>
  1824. </div>
  1825. <br style="clear:both" />
  1826. <table class="form-table">
  1827. <tr>
  1828. <th>Last backup log message:</th>
  1829. <td id="updraft_lastlogcontainer"><?php echo htmlspecialchars(UpdraftPlus_Options::get_updraft_option('updraft_lastmessage', '(Nothing yet logged)')); ?></td>
  1830. </tr>
  1831. <tr>
  1832. <th>Download backups and logs:</th>
  1833. <td><a href="#" title="Click to see available backups" onclick="jQuery('.download-backups').toggle();return false;"><?php echo count($backup_history)?> available</a></td>
  1834. </tr>
  1835. <tr>
  1836. <td></td><td class="download-backups" style="display:none">
  1837. <em>Click on a button to download the corresponding file to your computer. If you are using the <a href="http://opera.com">Opera web browser</a> then you should turn Turbo mode off. <strong>Note</strong> - if you use remote storage (e.g. Amazon, Dropbox, FTP, Google Drive), then pressing a button will make UpdraftPlus try to bring a backup file back from the remote storage to your webserver, and from there to your computer. If the backup file is very big, then likely you will run out of time using this method. In that case you should get the file directly (i.e. visit Amazon S3's or Dropbox's website, etc.).</em>
  1838. <table>
  1839. <?php
  1840. foreach($backup_history as $key=>$value) {
  1841. ?>
  1842. <tr>
  1843. <td><b><?php echo date('Y-m-d G:i',$key)?></b></td>
  1844. <td>
  1845. <?php if (isset($value['db'])) { ?>
  1846. <form action="admin-ajax.php" method="post">
  1847. <input type="hidden" name="action" value="updraft_download_backup" />
  1848. <input type="hidden" name="type" value="db" />
  1849. <input type="hidden" name="timestamp" value="<?php echo $key?>" />
  1850. <input type="submit" value="Database" />
  1851. </form>
  1852. <?php } else { echo "(No database)"; } ?>
  1853. </td>
  1854. <td>
  1855. <?php if (isset($value['plugins'])) { ?>
  1856. <form action="admin-ajax.php" method="post">
  1857. <input type="hidden" name="action" value="updraft_download_backup" />
  1858. <input type="hidden" name="type" value="plugins" />
  1859. <input type="hidden" name="timestamp" value="<?php echo $key?>" />
  1860. <input type="submit" value="Plugins" />
  1861. </form>
  1862. <?php } else { echo "(No plugins)"; } ?>
  1863. </td>
  1864. <td>
  1865. <?php if (isset($value['themes'])) { ?>
  1866. <form action="admin-ajax.php" method="post">
  1867. <input type="hidden" name="action" value="updraft_download_backup" />
  1868. <input type="hidden" name="type" value="themes" />
  1869. <input type="hidden" name="timestamp" value="<?php echo $key?>" />
  1870. <input type="submit" value="Themes" />
  1871. </form>
  1872. <?php } else { echo "(No themes)"; } ?>
  1873. </td>
  1874. <td>
  1875. <?php if (isset($value['uploads'])) { ?>
  1876. <form action="admin-ajax.php" method="post">
  1877. <input type="hidden" name="action" value="updraft_download_backup" />
  1878. <input type="hidden" name="type" value="uploads" />
  1879. <input type="hidden" name="timestamp" value="<?php echo $key?>" />
  1880. <input type="submit" value="Uploads" />
  1881. </form>
  1882. <?php } else { echo "(No uploads)"; } ?>
  1883. </td>
  1884. <td>
  1885. <?php if (isset($value['others'])) { ?>
  1886. <form action="admin-ajax.php" method="post">
  1887. <input type="hidden" name="action" value="updraft_download_backup" />
  1888. <input type="hidden" name="type" value="others" />
  1889. <input type="hidden" name="timestamp" value="<?php echo $key?>" />
  1890. <input type="submit" value="Others" />
  1891. </form>
  1892. <?php } else { echo "(No others)"; } ?>
  1893. </td>
  1894. <td>
  1895. <?php if (isset($value['nonce']) && preg_match("/^[0-9a-f]{12}$/",$value['nonce']) && is_readable($updraft_dir.'/log.'.$value['nonce'].'.txt')) { ?>
  1896. <form action="options-general.php" method="get">
  1897. <input type="hidden" name="action" value="downloadlog" />
  1898. <input type="hidden" name="page" value="updraftplus" />
  1899. <input type="hidden" name="updraftplus_backup_nonce" value="<?php echo $value['nonce']; ?>" />
  1900. <input type="submit" value="Backup Log" />
  1901. </form>
  1902. <?php } else { echo "(No backup log)"; } ?>
  1903. </td>
  1904. </tr>
  1905. <?php }?>
  1906. </table>
  1907. </td>
  1908. </tr>
  1909. </table>
  1910. <?php
  1911. if (!defined('UPDRAFTPLUS_PREMIUM') && is_multisite()) {
  1912. ?>
  1913. <h2>UpdraftPlus Premium</h2>
  1914. <table>
  1915. <tr>
  1916. <td>
  1917. <p style="max-width:800px;">Do you need WordPress Multisite support? Please check out <a href="http://updraftplus.com">UpdraftPlus Premium</a> - and in coming weeks, it will add even more premium features. Why not support UpdraftPlus development?</p>
  1918. </td>
  1919. </tr>
  1920. </table>
  1921. <?php } ?>
  1922. <h2>Configure Backup Contents And Schedule</h2>
  1923. <?php UpdraftPlus_Options::options_form_begin(); ?>
  1924. <?php $this->settings_formcontents(); ?>
  1925. </form>
  1926. <div style="padding-top: 40px; display:none;" class="expertmode">
  1927. <hr>
  1928. <h3>Debug Information And Expert Options</h3>
  1929. <p>
  1930. <?php
  1931. $peak_memory_usage = memory_get_peak_usage(true)/1024/1024;
  1932. $memory_usage = memory_get_usage(true)/1024/1024;
  1933. echo 'Peak memory usage: '.$peak_memory_usage.' MB<br/>';
  1934. echo 'Current memory usage: '.$memory_usage.' MB<br/>';
  1935. echo 'PHP memory limit: '.ini_get('memory_limit').' <br/>';
  1936. ?>
  1937. </p>
  1938. <p style="max-width: 600px;">The buttons below will immediately execute a backup run, independently of WordPress's scheduler. If these work whilst your scheduled backups and the &quot;Backup Now&quot; button do absolutely nothing (i.e. not even produce a log file), then it means that your scheduler is broken. You should then disable all your other plugins, and try the &quot; Backup Now&quot; button. If that fails, then contact your web hosting company and ask them if they have disabled wp-cron. If it succeeds, then re-activate your other plugins one-by-one, and find the one that is the problem and report a bug to them.</p>
  1939. <form method="post">
  1940. <input type="hidden" name="action" value="updraft_backup_debug_all" />
  1941. <p><input type="submit" class="button-primary" <?php echo $backup_disabled ?> value="Debug Full Backup" onclick="return(confirm('This will cause an immediate backup. The page will stall loading until it finishes (ie, unscheduled).'))" /></p>
  1942. </form>
  1943. <form method="post">
  1944. <input type="hidden" name="action" value="updraft_backup_debug_db" />
  1945. <p><input type="submit" class="button-primary" <?php echo $backup_disabled ?> value="Debug DB Backup" onclick="return(confirm('This will cause an immediate DB backup. The page will stall loading until it finishes (ie, unscheduled). The backup may well run out of time; really this button is only helpful for checking that the backup is able to get through the initial stages, or for small WordPress sites.'))" /></p>
  1946. </form>
  1947. <h3>Wipe Settings</h3>
  1948. <p style="max-width: 600px;">This button will delete all UpdraftPlus settings (but not any of your existing backups from your cloud storage). You will then need to enter all your settings again. You can also do this before deactivating/deinstalling UpdraftPlus if you wish.</p>
  1949. <form method="post">
  1950. <input type="hidden" name="action" value="updraft_wipesettings" />
  1951. <p><input type="submit" class="button-primary" value="Wipe All Settings" onclick="return(confirm('This will delete all your UpdraftPlus settings - are you sure you want to do this?'))" /></p>
  1952. </form>
  1953. </div>
  1954. <script type="text/javascript">
  1955. /* <![CDATA[ */
  1956. jQuery(document).ready(function() {
  1957. jQuery('#updraft-service').change(function() {
  1958. jQuery('.updraftplusmethod').hide();
  1959. var active_class = jQuery(this).val();
  1960. jQuery('.'+active_class).show();
  1961. })
  1962. })
  1963. jQuery(window).load(function() {
  1964. //this is for hiding the restore progress at the top after it is done
  1965. setTimeout('jQuery("#updraft-restore-progress").toggle(1000)',3000)
  1966. jQuery('#updraft-restore-progress-toggle').click(function() {
  1967. jQuery('#updraft-restore-progress').toggle(500)
  1968. })
  1969. })
  1970. /* ]]> */
  1971. </script>
  1972. <?php
  1973. }
  1974. function show_admin_warning($message, $class = "updated") {
  1975. echo '<div id="updraftmessage" class="'.$class.' fade">'."<p>$message</p></div>";
  1976. }
  1977. function show_admin_warning_unreadablelog() {
  1978. $this->show_admin_warning('<strong>UpdraftPlus notice:</strong> The log file could not be read.');
  1979. }
  1980. function show_admin_warning_dropbox() {
  1981. $this->show_admin_warning('<strong>UpdraftPlus notice:</strong> <a href="options-general.php?page=updraftplus&action=updraftmethod-dropbox-auth&updraftplus_dropboxauth=doit">Click here to authenticate your Dropbox account (you will not be able to back up to Dropbox without it).</a>');
  1982. }
  1983. function show_admin_warning_googledrive() {
  1984. $this->show_admin_warning('<strong>UpdraftPlus notice:</strong> <a href="options-general.php?page=updraftplus&action=updraftmethod-googledrive-auth&updraftplus_googleauth=doit">Click here to authenticate your Google Drive account (you will not be able to back up to Google Drive without it).</a>');
  1985. }
  1986. // Caution: $source is allowed to be an array, not just a filename
  1987. function make_zipfile($source, $destination) {
  1988. // When to prefer PCL:
  1989. // - We were asked to
  1990. // - No zip extension present and no relevant method present
  1991. // The zip extension check is not redundant, because method_exists segfaults some PHP installs, leading to support requests
  1992. // Fallback to PclZip - which my tests show is 25% slower
  1993. if ($this->zip_preferpcl || (!extension_loaded('zip') && !method_exists('ZipArchive', 'AddFile'))) {
  1994. if(!class_exists('PclZip')) require_once(ABSPATH.'/wp-admin/includes/class-pclzip.php');
  1995. $zip_object = new PclZip($destination);
  1996. $zipcode = $zip_object->create($source, PCLZIP_OPT_REMOVE_PATH, WP_CONTENT_DIR);
  1997. if ($zipcode == 0 ) {
  1998. $this->log("PclZip Error: ".$zip_object->errorName());
  1999. return $zip_object->errorCode();
  2000. } else {
  2001. return true;
  2002. }
  2003. }
  2004. $this->existing_files = array();
  2005. // If the file exists, then we should grab its index of files inside, and sizes
  2006. // Then, when we come to write a file, we should check if it's already there, and only add if it is not
  2007. if (file_exists($destination) && is_readable($destination)) {
  2008. $zip = new ZipArchive;
  2009. $zip->open($destination);
  2010. $this->log(basename($destination).": Zip file already exists, with ".$zip->numFiles." files");
  2011. for ($i=0; $i<$zip->numFiles; $i++) {
  2012. $si = $zip->statIndex($i);
  2013. $name = $si['name'];
  2014. $this->existing_files[$name] = $si['size'];
  2015. }
  2016. } elseif (file_exists($destination)) {
  2017. $this->log("Zip file already exists, but is not readable; will remove: $destination");
  2018. @unlink($destination);
  2019. }
  2020. $this->zipfiles_added = 0;
  2021. $this->zipfiles_dirbatched = array();
  2022. $this->zipfiles_batched = array();
  2023. $last_error = -1;
  2024. if (is_array($source)) {
  2025. foreach ($source as $element) {
  2026. $howmany = $this->makezip_recursive_add($destination, $element, basename($element), $element);
  2027. if ($howmany < 0) {
  2028. $last_error = $howmany;
  2029. }
  2030. }
  2031. } else {
  2032. $howmany = $this->makezip_recursive_add($destination, $source, basename($source), $source);
  2033. if ($howmany < 0) {
  2034. $last_error = $howmany;
  2035. }
  2036. }
  2037. // Any not yet dispatched?
  2038. if (count($this->zipfiles_dirbatched)>0 || count($this->zipfiles_batched)>0) {
  2039. $howmany = $this->makezip_addfiles($destination);
  2040. if ($howmany < 0) {
  2041. $last_error = $howmany;
  2042. }
  2043. }
  2044. if ($this->zipfiles_added > 0) {
  2045. // ZipArchive::addFile sometimes fails
  2046. if (filesize($destination) < 100) {
  2047. // Retry with PclZip
  2048. $this->log("Zip::addFile apparently failed - retrying with PclZip");
  2049. $this->zip_preferpcl = true;
  2050. return $this->make_zipfile($source, $destination);
  2051. }
  2052. return true;
  2053. } else {
  2054. return $last_error;
  2055. }
  2056. }
  2057. // Q. Why don't we only open and close the zip file just once?
  2058. // A. Because apparently PHP doesn't write out until the final close, and it will return an error if anything file has vanished in the meantime. So going directory-by-directory reduces our chances of hitting an error if the filesystem is changing underneath us (which is very possible if dealing with e.g. 1Gb of files)
  2059. // We batch up the files, rather than do them one at a time. So we are more efficient than open,one-write,close.
  2060. function makezip_addfiles($zipfile) {
  2061. $zip = new ZipArchive();
  2062. if (file_exists($zipfile)) {
  2063. $opencode = $zip->open($zipfile);
  2064. } else {
  2065. $opencode = $zip->open($zipfile, ZIPARCHIVE::CREATE);
  2066. }
  2067. if ($opencode !== true) return array($opencode, 0);
  2068. // Make sure all directories are created before we start creating files
  2069. while ($dir = array_pop($this->zipfiles_dirbatched)) {
  2070. $zip->addEmptyDir($dir);
  2071. }
  2072. foreach ($this->zipfiles_batched as $file => $add_as) {
  2073. if (!isset($this->existing_files[$add_as]) || $this->existing_files[$add_as] != filesize($file)) {
  2074. $zip->addFile($file, $add_as);
  2075. }
  2076. $this->zipfiles_added++;
  2077. if ($this->zipfiles_added % 100 == 0) $this->log("Zip: ".basename($zipfile).": ".$this->zipfiles_added." files added (size: ".round(filesize($zipfile)/1024,1)." Kb)");
  2078. }
  2079. // Reset the array
  2080. $this->zipfiles_batched = array();
  2081. return $zip->close();
  2082. }
  2083. // This function recursively packs the zip, dereferencing symlinks but packing into a single-parent tree for universal unpacking
  2084. function makezip_recursive_add($zipfile, $fullpath, $use_path_when_storing, $original_fullpath) {
  2085. // De-reference
  2086. $fullpath = realpath($fullpath);
  2087. // Is the place we've ended up above the original base? That leads to infinite recursion
  2088. if (($fullpath !== $original_fullpath && strpos($original_fullpath, $fullpath) === 0) || ($original_fullpath == $fullpath && strpos($use_path_when_storing, '/') !== false) ) {
  2089. $this->log("Infinite recursion: symlink lead us to $fullpath, which is within $original_fullpath");
  2090. $this->error("Infinite recursion: consult your log for more information");
  2091. return false;
  2092. }
  2093. if(is_file($fullpath)) {
  2094. if (is_readable($fullpath)) {
  2095. $key = $use_path_when_storing.'/'.basename($fullpath);
  2096. $this->zipfiles_batched[$fullpath] = $use_path_when_storing.'/'.basename($fullpath);
  2097. @touch($zipfile);
  2098. } else {
  2099. $this->log("$fullpath: unreadable file");
  2100. $this->error("$fullpath: unreadable file");
  2101. }
  2102. } elseif (is_dir($fullpath)) {
  2103. if (!isset($this->existing_files[$use_path_when_storing])) $this->zipfiles_dirbatched[] = $use_path_when_storing;
  2104. if (!$dir_handle = @opendir($fullpath)) {
  2105. $this->log("Failed to open directory: $fullpath");
  2106. $this->error("Failed to open directory: $fullpath");
  2107. return;
  2108. }
  2109. while ($e = readdir($dir_handle)) {
  2110. if ($e != '.' && $e != '..') {
  2111. if (is_link($fullpath.'/'.$e)) {
  2112. $deref = realpath($fullpath.'/'.$e);
  2113. if (is_file($deref)) {
  2114. if (is_readable($deref)) {
  2115. $this->zipfiles_batched[$deref] = $use_path_when_storing.'/'.$e;
  2116. @touch($zipfile);
  2117. } else {
  2118. $this->log("$deref: unreadable file");
  2119. $this->error("$deref: unreadable file");
  2120. }
  2121. } elseif (is_dir($deref)) {
  2122. $this->makezip_recursive_add($zipfile, $deref, $use_path_when_storing.'/'.$e, $original_fullpath);
  2123. }
  2124. } elseif (is_file($fullpath.'/'.$e)) {
  2125. if (is_readable($fullpath.'/'.$e)) {
  2126. $this->zipfiles_batched[$fullpath.'/'.$e] = $use_path_when_storing.'/'.$e;
  2127. @touch($zipfile);
  2128. } else {
  2129. $this->log("$fullpath/$e: unreadable file");
  2130. $this->error("$fullpath/$e: unreadable file");
  2131. }
  2132. } elseif (is_dir($fullpath.'/'.$e)) {
  2133. // no need to addEmptyDir here, as it gets done when we recurse
  2134. $this->makezip_recursive_add($zipfile, $fullpath.'/'.$e, $use_path_when_storing.'/'.$e, $original_fullpath);
  2135. }
  2136. }
  2137. }
  2138. closedir($dir_handle);
  2139. }
  2140. // We don't want to touch the zip file on every single file, so we batch them up
  2141. // We go every 25 files, because if you wait too much longer, the contents may have changed from under you
  2142. // And for some redundancy (redundant because of the touches going on anyway), we try to touch the file after 20 seconds, to help with the "recently modified" check on resumption (we saw a case where the file went for 155 seconds without being touched and so the other runner was not detected)
  2143. if (count($this->zipfiles_batched) > 25 || (file_exists($zipfile) && ((time()-filemtime($zipfile)) > 20) )) {
  2144. $ret = $this->makezip_addfiles($zipfile);
  2145. } else {
  2146. $ret = true;
  2147. }
  2148. return $ret;
  2149. }
  2150. }
  2151. ?>