PageRenderTime 50ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://gitlab.com/endomorphosis/falkenstein
PHP | 1119 lines | 875 code | 157 blank | 87 comment | 264 complexity | cca8e75544e52dca3e10c02014d9ea3a MD5 | raw file
  1. <?php
  2. if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed');
  3. if (!class_exists('UpdraftPlus_PclZip')) require(UPDRAFTPLUS_DIR.'/class-zip.php');
  4. // This file contains functions that are only needed/loaded when a backup is running (reduces memory usage on other site pages)
  5. class UpdraftPlus_Backup {
  6. public $index = 0;
  7. private $zipfiles_added;
  8. private $zipfiles_added_thisrun = 0;
  9. private $zipfiles_dirbatched;
  10. private $zipfiles_batched;
  11. private $zip_split_every = 838860800; # 800Mb
  12. private $zip_last_ratio = 1;
  13. private $whichone;
  14. private $zip_basename = '';
  15. private $zipfiles_lastwritetime;
  16. // 0 = unknown; false = failed
  17. public $binzip = 0;
  18. private $dbhandle;
  19. private $dbhandle_isgz;
  20. private $use_zip_object = 'UpdraftPlus_ZipArchive';
  21. public $debug = false;
  22. private $updraft_dir;
  23. private $job_file_entities = array();
  24. public function __construct($backup_files) {
  25. global $updraftplus;
  26. # Decide which zip engine to begin with
  27. $this->debug = UpdraftPlus_Options::get_updraft_option('updraft_debug_mode');
  28. $this->updraft_dir = $updraftplus->backups_dir_location();
  29. if ('no' === $backup_files) {
  30. $this->use_zip_object = 'UpdraftPlus_PclZip';
  31. return;
  32. }
  33. // false means 'tried + failed'; whereas 0 means 'not yet tried'
  34. // Disallow binzip on OpenVZ when we're not sure there's plenty of memory
  35. if ($this->binzip === 0 && (!defined('UPDRAFTPLUS_PREFERPCLZIP') || UPDRAFTPLUS_PREFERPCLZIP != true) && (!defined('UPDRAFTPLUS_NO_BINZIP') || !UPDRAFTPLUS_NO_BINZIP) && $updraftplus->current_resumption <9) {
  36. if (@file_exists('/proc/user_beancounters') && @file_exists('/proc/meminfo') && @is_readable('/proc/meminfo')) {
  37. $meminfo = @file_get_contents('/proc/meminfo', false, null, -1, 200);
  38. if (is_string($meminfo) && preg_match('/MemTotal:\s+(\d+) kB/', $meminfo, $matches)) {
  39. $memory_mb = $matches[1]/1024;
  40. # If the report is of a large amount, then we're probably getting the total memory on the hypervisor (this has been observed), and don't really know the VPS's memory
  41. $vz_log = "OpenVZ; reported memory: ".round($memory_mb, 1)." Mb";
  42. if ($memory_mb < 1024 || $memory_mb > 8192) {
  43. $openvz_lowmem = true;
  44. $vz_log .= " (will not use BinZip)";
  45. }
  46. $updraftplus->log($vz_log);
  47. }
  48. }
  49. if (empty($openvz_lowmem)) {
  50. $updraftplus->log('Checking if we have a zip executable available');
  51. $binzip = $updraftplus->find_working_bin_zip();
  52. if (is_string($binzip)) {
  53. $updraftplus->log("Zip engine: found/will use a binary zip: $binzip");
  54. $this->binzip = $binzip;
  55. $this->use_zip_object = 'UpdraftPlus_BinZip';
  56. }
  57. }
  58. }
  59. # In tests, PclZip was found to be 25% slower than ZipArchive
  60. if ($this->use_zip_object != 'UpdraftPlus_PclZip' && empty($this->binzip) && ((defined('UPDRAFTPLUS_PREFERPCLZIP') && UPDRAFTPLUS_PREFERPCLZIP == true) || !class_exists('ZipArchive') || !class_exists('UpdraftPlus_ZipArchive') || (!extension_loaded('zip') && !method_exists('ZipArchive', 'AddFile')))) {
  61. global $updraftplus;
  62. $updraftplus->log("Zip engine: ZipArchive is not available or is disabled (will use PclZip if needed)");
  63. $this->use_zip_object = 'UpdraftPlus_PclZip';
  64. }
  65. }
  66. public function create_zip($create_from_dir, $whichone, $backup_file_basename, $index) {
  67. // Note: $create_from_dir can be an array or a string
  68. @set_time_limit(900);
  69. $original_index = $index;
  70. $this->index = $index;
  71. $this->whichone = $whichone;
  72. global $updraftplus;
  73. $this->zip_split_every = max((int)$updraftplus->jobdata_get('split_every'), UPDRAFTPLUS_SPLIT_MIN)*1048576;
  74. if ('others' != $whichone) $updraftplus->log("Beginning creation of dump of $whichone (split every: ".round($this->zip_split_every/1048576,1)." Mb)");
  75. if (is_string($create_from_dir) && !file_exists($create_from_dir)) {
  76. $flag_error = true;
  77. $updraftplus->log("Does not exist: $create_from_dir");
  78. if ('mu-plugins' == $whichone) {
  79. if (!function_exists('get_mu_plugins')) require_once(ABSPATH.'wp-admin/includes/plugin.php');
  80. $mu_plugins = get_mu_plugins();
  81. if (count($mu_plugins) == 0) {
  82. $updraftplus->log("There appear to be no mu-plugins to back up. Will not raise an error.");
  83. $flag_error = false;
  84. }
  85. }
  86. if ($flag_error) $updraftplus->log(sprintf(__("%s - could not back this entity up; the corresponding directory does not exist (%s)", 'updraftplus'), $whichone, $create_from_dir), 'error');
  87. return false;
  88. }
  89. $itext = (empty($index)) ? '' : ($index+1);
  90. $base_path = $backup_file_basename.'-'.$whichone.$itext.'.zip';
  91. $full_path = $this->updraft_dir.'/'.$base_path;
  92. $time_now = time();
  93. if (file_exists($full_path)) {
  94. # Gather any further files that may also exist
  95. $files_existing = array();
  96. while (file_exists($full_path)) {
  97. $files_existing[] = $base_path;
  98. $time_mod = (int)@filemtime($full_path);
  99. $updraftplus->log($base_path.": this file has already been created (age: ".round($time_now-$time_mod,1)." s)");
  100. if ($time_mod>100 && ($time_now-$time_mod)<30) {
  101. $updraftplus->terminate_due_to_activity($base_path, $time_now, $time_mod);
  102. }
  103. $index++;
  104. $base_path = $backup_file_basename.'-'.$whichone.$index.'.zip';
  105. $full_path = $this->updraft_dir.'/'.$base_path;
  106. }
  107. }
  108. // Temporary file, to be able to detect actual completion (upon which, it is renamed)
  109. // New (Jun-13) - be more aggressive in removing temporary files from earlier attempts - anything >=600 seconds old of this kind
  110. $updraftplus->clean_temporary_files('_'.$updraftplus->nonce."-$whichone", 600);
  111. // 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
  112. $zip_name = $full_path.'.tmp';
  113. $time_mod = (int)@filemtime($zip_name);
  114. if (file_exists($zip_name) && $time_mod>100 && ($time_now-$time_mod)<30) {
  115. $updraftplus->terminate_due_to_activity($zip_name, $time_now, $time_mod);
  116. } elseif (file_exists($zip_name)) {
  117. $updraftplus->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).")");
  118. }
  119. // Now, check for other forms of temporary file, which would indicate that some activity is going on (even if it hasn't made it into the main zip file yet)
  120. // Note: this doesn't catch PclZip temporary files
  121. $d = dir($this->updraft_dir);
  122. $match = '_'.$updraftplus->nonce."-".$whichone;
  123. while (false !== ($e = $d->read())) {
  124. if ('.' == $e || '..' == $e || !is_file($this->updraft_dir.'/'.$e)) continue;
  125. $ziparchive_match = preg_match("/$match([0-9]+)?\.zip\.tmp\.([A-Za-z0-9]){6}?$/i", $e);
  126. $binzip_match = preg_match("/^zi([A-Za-z0-9]){6}$/", $e);
  127. if ($time_now-filemtime($this->updraft_dir.'/'.$e) < 30 && ($ziparchive_match || (0 != $updraftplus->current_resumption && $binzip_match))) {
  128. $updraftplus->terminate_due_to_activity($this->updraft_dir.'/'.$e, $time_now, filemtime($this->updraft_dir.'/'.$e));
  129. }
  130. }
  131. @$d->close();
  132. clearstatcache();
  133. if (isset($files_existing)) {
  134. # Because of zip-splitting, the mere fact that files exist is not enough to indicate that the entity is finished. For that, we need to also see that no subsequent file has been started.
  135. # Q. What if the previous runner died in between zips, and it is our job to start the next one? A. The next temporary file is created before finishing the former zip, so we are safe (and we are also safe-guarded by the updated value of the index being stored in the database).
  136. return $files_existing;
  137. }
  138. $this->log_account_space();
  139. $this->zip_microtime_start = microtime(true);
  140. # The paths in the zip should then begin with '$whichone', having removed WP_CONTENT_DIR from the front
  141. $zipcode = $this->make_zipfile($create_from_dir, $backup_file_basename, $whichone);
  142. if ($zipcode !== true) {
  143. $updraftplus->log("ERROR: Zip failure: Could not create $whichone zip (".$this->index." / $index)");
  144. $updraftplus->log(sprintf(__("Could not create %s zip. Consult the log file for more information.",'updraftplus'),$whichone), 'error');
  145. # The caller is required to update $index from $this->index
  146. return false;
  147. } else {
  148. $itext = (empty($this->index)) ? '' : ($this->index+1);
  149. $full_path = $this->updraft_dir.'/'.$backup_file_basename.'-'.$whichone.$itext.'.zip';
  150. if (file_exists($full_path.'.tmp')) {
  151. if (@filesize($full_path.'.tmp') === 0) {
  152. $updraftplus->log("Did not create $whichone zip (".$this->index.") - not needed");
  153. @unlink($full_path.'.tmp');
  154. } else {
  155. $sha = sha1_file($full_path.'.tmp');
  156. $updraftplus->jobdata_set('sha1-'.$whichone.$this->index, $sha);
  157. @rename($full_path.'.tmp', $full_path);
  158. $timetaken = max(microtime(true)-$this->zip_microtime_start, 0.000001);
  159. $kbsize = filesize($full_path)/1024;
  160. $rate = round($kbsize/$timetaken, 1);
  161. $updraftplus->log("Created $whichone zip (".$this->index.") - ".round($kbsize,1)." Kb in ".round($timetaken,1)." s ($rate Kb/s) (SHA1 checksum: $sha)");
  162. // We can now remove any left-over temporary files from this job
  163. }
  164. } elseif ($this->index > $original_index) {
  165. $updraftplus->log("Did not create $whichone zip (".$this->index.") - not needed");
  166. # Added 12-Feb-2014 (to help multiple morefiles)
  167. $this->index--;
  168. } else {
  169. $updraftplus->log("Looked-for $whichone zip (".$this->index.") was not found (".basename($full_path).".tmp)", 'warning');
  170. }
  171. $updraftplus->clean_temporary_files('_'.$updraftplus->nonce."-$whichone", 0);
  172. }
  173. # Create the results array to send back (just the new ones, not any prior ones)
  174. $files_existing = array();
  175. $res_index = 0;
  176. for ($i = $original_index; $i<= $this->index; $i++) {
  177. $itext = (empty($i)) ? '' : ($i+1);
  178. $full_path = $this->updraft_dir.'/'.$backup_file_basename.'-'.$whichone.$itext.'.zip';
  179. if (file_exists($full_path)) {
  180. $files_existing[$res_index] = $backup_file_basename.'-'.$whichone.$itext.'.zip';
  181. }
  182. $res_index++;
  183. }
  184. return $files_existing;
  185. }
  186. // Dispatch to the relevant function
  187. public function cloud_backup($backup_array) {
  188. global $updraftplus;
  189. $services = $updraftplus->just_one($updraftplus->jobdata_get('service'));
  190. if (!is_array($services)) $services = array($services);
  191. $updraftplus->jobdata_set('jobstatus', 'clouduploading');
  192. add_action('http_api_curl', array($updraftplus, 'add_curl_capath'));
  193. $upload_status = $updraftplus->jobdata_get('uploading_substatus');
  194. if (!is_array($upload_status) || !isset($upload_status['t'])) {
  195. $upload_status = array('i' => 0, 'p' => 0, 't' => max(1, count($services))*count($backup_array));
  196. $updraftplus->jobdata_set('uploading_substatus', $upload_status);
  197. }
  198. $do_prune = array();
  199. # If there was no check-in last time, then attempt a different service first - in case a time-out on the attempted service leads to no activity and everything stopping
  200. if (count($services) >1 && !empty($updraftplus->no_checkin_last_time)) {
  201. $updraftplus->log('No check-in last time: will try a different remote service first');
  202. array_push($services, array_shift($services));
  203. if (1 == ($updraftplus->current_resumption % 2) && count($services)>2) array_push($services, array_shift($services));
  204. }
  205. $errors_before_uploads = $updraftplus->error_count();
  206. foreach ($services as $ind => $service) {
  207. # Used for logging by record_upload_chunk()
  208. $this->current_service = $service;
  209. # Used when deciding whether to delete the local file
  210. $this->last_service = ($ind+1 >= count($services) && $errors_before_uploads == $updraftplus->error_count()) ? true : false;
  211. $updraftplus->log("Cloud backup selection: ".$service);
  212. @set_time_limit(900);
  213. $method_include = UPDRAFTPLUS_DIR.'/methods/'.$service.'.php';
  214. if (file_exists($method_include)) require_once($method_include);
  215. if ($service == "none" || $service == "") {
  216. $updraftplus->log("No remote despatch: user chose no remote backup service");
  217. $this->prune_retained_backups(array("none" => array(null, null)));
  218. } else {
  219. $updraftplus->log("Beginning dispatch of backup to remote ($service)");
  220. $sarray = array();
  221. foreach ($backup_array as $bind => $file) {
  222. if ($updraftplus->is_uploaded($file, $service)) {
  223. $updraftplus->log("Already uploaded to $service: $file");
  224. } else {
  225. $sarray[$bind] = $file;
  226. }
  227. }
  228. if (count($sarray)>0) {
  229. $objname = "UpdraftPlus_BackupModule_${service}";
  230. if (class_exists($objname)) {
  231. $remote_obj = new $objname;
  232. $pass_to_prune = $remote_obj->backup($backup_array);
  233. $do_prune[$service] = array($remote_obj, $pass_to_prune);
  234. } else {
  235. $updraftplus->log("Unexpected error: no class '$objname' was found ($method_include)");
  236. $updraftplus->log(__("Unexpected error: no class '$objname' was found (your UpdraftPlus installation seems broken - try re-installing)",'updraftplus'), 'error');
  237. }
  238. }
  239. }
  240. }
  241. if (!empty($do_prune)) $this->prune_retained_backups($do_prune);
  242. remove_action('http_api_curl', array($updraftplus, 'add_curl_capath'));
  243. }
  244. // Carries out retain behaviour. Pass in a valid S3 or FTP object and path if relevant.
  245. // Services *must* be an array
  246. public function prune_retained_backups($services) {
  247. global $updraftplus;
  248. // If they turned off deletion on local backups, then there is nothing to do
  249. if (UpdraftPlus_Options::get_updraft_option('updraft_delete_local') == 0 && count($services) == 1 && in_array('none', $services)) {
  250. $updraftplus->log("Prune old backups from local store: nothing to do, since the user disabled local deletion and we are using local backups");
  251. return;
  252. }
  253. $updraftplus->jobdata_set('jobstatus', 'pruning');
  254. // Number of backups to retain - files
  255. $updraft_retain = UpdraftPlus_Options::get_updraft_option('updraft_retain', 2);
  256. $updraft_retain = (is_numeric($updraft_retain)) ? $updraft_retain : 1;
  257. // Number of backups to retain - db
  258. $updraft_retain_db = UpdraftPlus_Options::get_updraft_option('updraft_retain_db', $updraft_retain);
  259. $updraft_retain_db = (is_numeric($updraft_retain_db)) ? $updraft_retain_db : 1;
  260. $updraftplus->log("Retain: beginning examination of existing backup sets; user setting: retain_files=$updraft_retain, retain_db=$updraft_retain_db");
  261. // Returns an array, most recent first, of backup sets
  262. $backup_history = $updraftplus->get_backup_history();
  263. $db_backups_found = 0;
  264. $file_backups_found = 0;
  265. $updraftplus->log("Number of backup sets in history: ".count($backup_history));
  266. $backupable_entities = $updraftplus->get_backupable_file_entities(true);
  267. $database_backups_found = array();
  268. $file_entities_backups_found = array();
  269. foreach ($backupable_entities as $entity => $info) {
  270. $file_entities_backups_found[$entity] = 0;
  271. }
  272. foreach ($backup_history as $backup_datestamp => $backup_to_examine) {
  273. $files_to_prune = array();
  274. // $backup_to_examine is an array of file names, keyed on db/plugins/themes/uploads
  275. // The new backup_history array is saved afterwards, so remember to unset the ones that are to be deleted
  276. $updraftplus->log(sprintf("Examining backup set with datestamp: %s (%s)", $backup_datestamp, gmdate('M d Y H:i:s', $backup_datestamp)));
  277. # Databases
  278. foreach ($backup_to_examine as $key => $data) {
  279. if ('db' != strtolower(substr($key, 0, 2)) || '-size' == substr($key, -5, 5)) continue;
  280. $database_backups_found[$key] = (empty($database_backups_found[$key])) ? 1 : $database_backups_found[$key] + 1;
  281. $fname = (is_string($data)) ? $data : $data[0];
  282. $updraftplus->log("$backup_datestamp: $key: this set includes a database (".$fname."); db count is now ".$database_backups_found[$key]);
  283. if ($database_backups_found[$key] > $updraft_retain_db) {
  284. $updraftplus->log("$backup_datestamp: $key: over retain limit ($updraft_retain_db); will delete this database");
  285. if (!empty($data)) {
  286. foreach ($services as $service => $sd) $this->prune_file($service, $data, $sd[0], $sd[1]);
  287. }
  288. unset($backup_to_examine[$key]);
  289. $updraftplus->record_still_alive();
  290. }
  291. }
  292. foreach ($backupable_entities as $entity => $info) {
  293. if (!empty($backup_to_examine[$entity])) {
  294. $file_entities_backups_found[$entity]++;
  295. if ($file_entities_backups_found[$entity] > $updraft_retain) {
  296. $prune_this = $backup_to_examine[$entity];
  297. if (is_string($prune_this)) $prune_this = array($prune_this);
  298. foreach ($prune_this as $prune_file) {
  299. $updraftplus->log("$entity: $backup_datestamp: over retain limit ($updraft_retain); will delete this file ($prune_file)");
  300. $files_to_prune[] = $prune_file;
  301. }
  302. unset($backup_to_examine[$entity]);
  303. }
  304. }
  305. }
  306. # Actually delete the files
  307. foreach ($services as $service => $sd) {
  308. $this->prune_file($service, $files_to_prune, $sd[0], $sd[1]);
  309. $updraftplus->record_still_alive();
  310. }
  311. // Get new result, post-deletion; anything left in this set?
  312. $contains_files = 0;
  313. foreach ($backupable_entities as $entity => $info) {
  314. if (isset($backup_to_examine[$entity])) {
  315. $contains_files = 1;
  316. break;
  317. }
  318. }
  319. $contains_db = 0;
  320. foreach ($backup_to_examine as $key => $data) {
  321. if ('db' == strtolower(substr($key, 0, 2)) && '-size' != substr($key, -5, 5)) {
  322. $contains_db = 1;
  323. break;
  324. }
  325. }
  326. // Delete backup set completely if empty, o/w just remove DB
  327. // We search on the four keys which represent data, allowing other keys to be used to track other things
  328. if (!$contains_files && !$contains_db) {
  329. $updraftplus->log("$backup_datestamp: this backup set is now empty; will remove from history");
  330. unset($backup_history[$backup_datestamp]);
  331. if (isset($backup_to_examine['nonce'])) {
  332. $fullpath = $this->updraft_dir.'/log.'.$backup_to_examine['nonce'].'.txt';
  333. if (is_file($fullpath)) {
  334. $updraftplus->log("$backup_datestamp: deleting log file (log.".$backup_to_examine['nonce'].".txt)");
  335. @unlink($fullpath);
  336. } else {
  337. $updraftplus->log("$backup_datestamp: corresponding log file not found - must have already been deleted");
  338. }
  339. } else {
  340. $updraftplus->log("$backup_datestamp: no nonce record found in the backup set, so cannot delete any remaining log file");
  341. }
  342. } else {
  343. $updraftplus->log("$backup_datestamp: this backup set remains non-empty ($contains_files/$contains_db); will retain in history");
  344. $backup_history[$backup_datestamp] = $backup_to_examine;
  345. }
  346. # Loop over backup sets
  347. }
  348. $updraftplus->log("Retain: saving new backup history (sets now: ".count($backup_history).") and finishing retain operation");
  349. UpdraftPlus_Options::update_updraft_option('updraft_backup_history', $backup_history, false);
  350. }
  351. # $dofiles: An array of files (or a single string for one file)
  352. private function prune_file($service, $dofiles, $method_object = null, $object_passback = null) {
  353. global $updraftplus;
  354. if (!is_array($dofiles)) $dofiles=array($dofiles);
  355. foreach ($dofiles as $dofile) {
  356. if (empty($dofile)) continue;
  357. $updraftplus->log("Delete file: $dofile, service=$service");
  358. $fullpath = $this->updraft_dir.'/'.$dofile;
  359. // delete it if it's locally available
  360. if (file_exists($fullpath)) {
  361. $updraftplus->log("Deleting local copy ($dofile)");
  362. @unlink($fullpath);
  363. }
  364. }
  365. // Despatch to the particular method's deletion routine
  366. if (!is_null($method_object)) $method_object->delete($dofiles, $object_passback);
  367. }
  368. public function send_results_email($final_message) {
  369. global $updraftplus;
  370. $debug_mode = UpdraftPlus_Options::get_updraft_option('updraft_debug_mode');
  371. $sendmail_to = $updraftplus->just_one_email(UpdraftPlus_Options::get_updraft_option('updraft_email'));
  372. if (is_string($sendmail_to)) $sendmail_to = array($sendmail_to);
  373. $backup_files = $updraftplus->jobdata_get('backup_files');
  374. $backup_db = $updraftplus->jobdata_get('backup_database');
  375. if ('finished' == $backup_files && ('finished' == $backup_db || 'encrypted' == $backup_db)) {
  376. $backup_contains = __("Files and database", 'updraftplus');
  377. } elseif ('finished' == $backup_files) {
  378. $backup_contains = ($backup_db == "begun") ? __("Files (database backup has not completed)", 'updraftplus') : __("Files only (database was not part of this particular schedule)", 'updraftplus');
  379. } elseif ($backup_db == 'finished' || $backup_db == 'encrypted') {
  380. $backup_contains = ($backup_files == "begun") ? __("Database (files backup has not completed)", 'updraftplus') : __("Database only (files were not part of this particular schedule)", 'updraftplus');
  381. } else {
  382. $backup_contains = __("Unknown/unexpected error - please raise a support request", 'updraftplus');
  383. }
  384. $append_log = '';
  385. $attachments = array();
  386. $error_count = 0;
  387. if ($updraftplus->error_count() > 0) {
  388. $append_log .= __('Errors encountered:', 'updraftplus')."\r\n";
  389. $attachments[0] = $updraftplus->logfile_name;
  390. foreach ($updraftplus->errors as $err) {
  391. if (is_wp_error($err)) {
  392. foreach ($err->get_error_messages() as $msg) {
  393. $append_log .= "* ".rtrim($msg)."\r\n";
  394. }
  395. } elseif (is_array($err) && 'error' == $err['level']) {
  396. $append_log .= "* ".rtrim($err['message'])."\r\n";
  397. } elseif (is_string($err)) {
  398. $append_log .= "* ".rtrim($err)."\r\n";
  399. }
  400. $error_count++;
  401. }
  402. $append_log.="\r\n";
  403. }
  404. $warnings = $updraftplus->jobdata_get('warnings');
  405. if (is_array($warnings) && count($warnings) >0) {
  406. $append_log .= __('Warnings encountered:', 'updraftplus')."\r\n";
  407. $attachments[0] = $updraftplus->logfile_name;
  408. foreach ($warnings as $err) {
  409. $append_log .= "* ".rtrim($err)."\r\n";
  410. }
  411. $append_log.="\r\n";
  412. }
  413. if ($debug_mode && '' != $updraftplus->logfile_name && !in_array($updraftplus->logfile_name, $attachments)) {
  414. $append_log .= "\r\n".__('The log file has been attached to this email.', 'updraftplus');
  415. $attachments[0] = $updraftplus->logfile_name;
  416. }
  417. // We have to use the action in order to set the MIME type on the attachment - by default, WordPress just puts application/octet-stream
  418. $subject = apply_filters('updraft_report_subject', sprintf(__('Backed up: %s', 'updraftplus'), get_bloginfo('name')).' (UpdraftPlus '.$updraftplus->version.') '.get_date_from_gmt(gmdate('Y-m-d H:i:s', time()), 'Y-m-d H:i'), $error_count, count($warnings));
  419. $body = apply_filters('updraft_report_body', __('Backup of:').' '.site_url()."\r\nUpdraftPlus ".__('WordPress backup is complete','updraftplus').".\r\n".__('Backup contains:','updraftplus').' '.$backup_contains."\r\n".__('Latest status:', 'updraftplus').' '.$final_message."\r\n\r\n".$updraftplus->wordshell_random_advert(0)."\r\n".$append_log, $final_message, $backup_contains, $updraftplus->errors, $warnings);
  420. $this->attachments = apply_filters('updraft_report_attachments', $attachments);
  421. if (count($this->attachments)>0) add_action('phpmailer_init', array($this, 'phpmailer_init'));
  422. $attach_size = 0;
  423. $unlink_files = array();
  424. foreach ($this->attachments as $ind => $attach) {
  425. if ($attach == $updraftplus->logfile_name && filesize($attach) > 6*1048576) {
  426. $updraftplus->log("Log file is large (".round(filesize($attach)/1024, 1)." Kb): will compress before e-mailing");
  427. if (!$handle = fopen($attach, "r")) {
  428. $updraftplus->log("Error: Failed to open log file for reading: ".$attach);
  429. } else {
  430. if (!$whandle = gzopen($attach.'.gz', 'w')) {
  431. $updraftplus->log("Error: Failed to open log file for reading: ".$attach.".gz");
  432. } else {
  433. while (false !== ($line = @stream_get_line($handle, 131072, "\n"))) {
  434. @gzwrite($whandle, $line."\n");
  435. }
  436. fclose($handle);
  437. gzclose($whandle);
  438. $this->attachments[$ind] = $attach.'.gz';
  439. $unlink_files[] = $attach.'.gz';
  440. }
  441. }
  442. }
  443. $attach_size += filesize($this->attachments[$ind]);
  444. }
  445. foreach ($sendmail_to as $ind => $mailto) {
  446. if (false === apply_filters('updraft_report_sendto', true, $mailto, $error_count, count($warnings), $ind)) continue;
  447. foreach (explode(',', $mailto) as $sendmail_addr) {
  448. $updraftplus->log("Sending email ('$backup_contains') report (attachments: ".count($attachments).", size: ".round($attach_size/1024, 1)." Kb) to: ".substr($sendmail_addr, 0, 5)."...");
  449. wp_mail(trim($sendmail_addr), $subject, $body);
  450. }
  451. }
  452. foreach ($unlink_files as $file) @unlink($file);
  453. do_action('updraft_report_finished');
  454. if (count($this->attachments)>0) remove_action('phpmailer_init', array($this, 'phpmailer_init'));
  455. }
  456. // The purpose of this function is to make sure that the options table is put in the database first, then the users table, then the usermeta table; and after that the core WP tables - so that when restoring we restore the core tables first
  457. private function backup_db_sorttables($a, $b) {
  458. global $updraftplus, $wpdb;
  459. if ($a == $b) return 0;
  460. $our_table_prefix = $this->table_prefix;
  461. if ($a == $our_table_prefix.'options') return -1;
  462. if ($b == $our_table_prefix.'options') return 1;
  463. if ($a == $our_table_prefix.'users') return -1;
  464. if ($b == $our_table_prefix.'users') return 1;
  465. if ($a == $our_table_prefix.'usermeta') return -1;
  466. if ($b == $our_table_prefix.'usermeta') return 1;
  467. if (empty($our_table_prefix)) return strcmp($a, $b);
  468. try {
  469. $core_tables = array_merge($wpdb->tables, $wpdb->global_tables, $wpdb->ms_global_tables);
  470. } catch (Exception $e) {
  471. }
  472. if (empty($core_tables)) $core_tables = array('terms', 'term_taxonomy', 'term_relationships', 'commentmeta', 'comments', 'links', 'postmeta', 'posts', 'site', 'sitemeta', 'blogs', 'blogversions');
  473. global $updraftplus;
  474. $na = $updraftplus->str_replace_once($our_table_prefix, '', $a);
  475. $nb = $updraftplus->str_replace_once($our_table_prefix, '', $b);
  476. if (in_array($na, $core_tables) && !in_array($nb, $core_tables)) return -1;
  477. if (!in_array($na, $core_tables) && in_array($nb, $core_tables)) return 1;
  478. return strcmp($a, $b);
  479. }
  480. private function log_account_space() {
  481. # Don't waste time if space is huge
  482. if (!empty($this->account_space_oodles)) return;
  483. global $updraftplus;
  484. $hosting_bytes_free = $updraftplus->get_hosting_disk_quota_free();
  485. if (is_array($hosting_bytes_free)) {
  486. $perc = round(100*$hosting_bytes_free[1]/(max($hosting_bytes_free[2], 1)), 1);
  487. $updraftplus->log(sprintf('Free disk space in account: %s (%s used)', round($hosting_bytes_free[3]/1048576, 1)." Mb", "$perc %"));
  488. }
  489. }
  490. // This function is resumable
  491. public function backup_dirs($job_status) {
  492. global $updraftplus;
  493. if(!$updraftplus->backup_time) $updraftplus->backup_time_nonce();
  494. //get the blog name and rip out all non-alphanumeric chars other than _
  495. $blog_name = preg_replace('/[^A-Za-z0-9_]/','', str_replace(' ','_', substr(get_bloginfo(), 0, 32)));
  496. if (!$blog_name) $blog_name = 'non_alpha_name';
  497. $blog_name = apply_filters('updraftplus_blog_name', $blog_name);
  498. $backup_file_basename = 'backup_'.get_date_from_gmt(gmdate('Y-m-d H:i:s', $updraftplus->backup_time), 'Y-m-d-Hi').'_'.$blog_name.'_'.$updraftplus->nonce;
  499. $backup_array = array();
  500. $possible_backups = $updraftplus->get_backupable_file_entities(true);
  501. // Was there a check-in last time? If not, then reduce the amount of data attempted
  502. if ($job_status != 'finished' && $updraftplus->current_resumption >= 2 && $updraftplus->current_resumption<=10) {
  503. $maxzipbatch = $updraftplus->jobdata_get('maxzipbatch', 26214400);
  504. if ((int)$maxzipbatch < 1) $maxzipbatch = 26214400;
  505. # NOTYET: Possible amendment to original algorithm; not just no check-in, but if the check in was very early (can happen if we get a very early checkin for some trivial operation, then attempt something too big)
  506. if (!empty($updraftplus->no_checkin_last_time)) {
  507. if ($updraftplus->current_resumption - $updraftplus->last_successful_resumption > 2) {
  508. $this->try_split = true;
  509. } else {
  510. $new_maxzipbatch = max(floor($maxzipbatch * 0.75), 20971520);
  511. if ($new_maxzipbatch < $maxzipbatch) {
  512. $updraftplus->log("No check-in was detected on the previous run - as a result, we are reducing the batch amount (old=$maxzipbatch, new=$new_maxzipbatch)");
  513. $updraftplus->jobdata_set('maxzipbatch', $new_maxzipbatch);
  514. $updraftplus->jobdata_set('maxzipbatch_ceiling', $new_maxzipbatch);
  515. }
  516. }
  517. }
  518. }
  519. if($job_status != 'finished' && !$updraftplus->really_is_writable($this->updraft_dir)) {
  520. $updraftplus->log("Backup directory (".$this->updraft_dir.") is not writable, or does not exist");
  521. $updraftplus->log(sprintf(__("Backup directory (%s) is not writable, or does not exist.", 'updraftplus'), $this->updraft_dir), 'error');
  522. return array();
  523. }
  524. $this->job_file_entities = $updraftplus->jobdata_get('job_file_entities');
  525. # This is just used for the visual feedback (via the 'substatus' key)
  526. $which_entity = 0;
  527. # e.g. plugins, themes, uploads, others
  528. # $whichdir might be an array (if $youwhat is 'more')
  529. foreach ($possible_backups as $youwhat => $whichdir) {
  530. if (isset($this->job_file_entities[$youwhat])) {
  531. $index = (int)$this->job_file_entities[$youwhat]['index'];
  532. if (empty($index)) $index=0;
  533. $indextext = (0 == $index) ? '' : (1+$index);
  534. $zip_file = $this->updraft_dir.'/'.$backup_file_basename.'-'.$youwhat.$indextext.'.zip';
  535. # Split needed?
  536. $split_every=max((int)$updraftplus->jobdata_get('split_every'), 250);
  537. if (file_exists($zip_file) && filesize($zip_file) > $split_every*1048576) {
  538. $index++;
  539. $this->job_file_entities[$youwhat]['index'] = $index;
  540. $updraftplus->jobdata_set('job_file_entities', $this->job_file_entities);
  541. }
  542. // Populate prior parts of array, if we're on a subsequent zip file
  543. if ($index >0) {
  544. for ($i=0; $i<$index; $i++) {
  545. $itext = (0 == $i) ? '' : ($i+1);
  546. $backup_array[$youwhat][$i] = $backup_file_basename.'-'.$youwhat.$itext.'.zip';
  547. $z = $this->updraft_dir.'/'.$backup_file_basename.'-'.$youwhat.$itext.'.zip';
  548. $itext = (0 == $i) ? '' : $i;
  549. if (file_exists($z)) $backup_array[$youwhat.$itext.'-size'] = filesize($z);
  550. }
  551. }
  552. if ('finished' == $job_status) {
  553. // Add the final part of the array
  554. if ($index >0) {
  555. $fbase = $backup_file_basename.'-'.$youwhat.($index+1).'.zip';
  556. $z = $this->updraft_dir.'/'.$fbase;
  557. if (file_exists($z)) {
  558. $backup_array[$youwhat][$index] = $fbase;
  559. $backup_array[$youwhat.$index.'-size'] = filesize($z);
  560. }
  561. } else {
  562. $backup_array[$youwhat] = $backup_file_basename.'-'.$youwhat.'.zip';
  563. if (file_exists($zip_file)) $backup_array[$youwhat.'-size'] = filesize($zip_file);
  564. }
  565. } else {
  566. $which_entity++;
  567. $updraftplus->jobdata_set('filecreating_substatus', array('e' => $youwhat, 'i' => $which_entity, 't' => count($this->job_file_entities)));
  568. if ('others' == $youwhat) $updraftplus->log("Beginning backup of other directories found in the content directory (index: $index)");
  569. # Apply a filter to allow add-ons to provide their own method for creating a zip of the entity
  570. $created = apply_filters('updraftplus_backup_makezip_'.$youwhat, $whichdir, $backup_file_basename, $index);
  571. # If the filter did not lead to something being created, then use the default method
  572. if ($created === $whichdir) {
  573. // http://www.phpconcept.net/pclzip/user-guide/53
  574. /* First parameter to create is:
  575. An array of filenames or dirnames,
  576. or
  577. A string containing the filename or a dirname,
  578. or
  579. A string containing a list of filename or dirname separated by a comma.
  580. */
  581. if ('others' == $youwhat) {
  582. $dirlist = $updraftplus->backup_others_dirlist(true);
  583. } elseif ('uploads' == $youwhat) {
  584. $dirlist = $updraftplus->backup_uploads_dirlist(true);
  585. } else {
  586. $dirlist = $whichdir;
  587. if (is_array($dirlist)) $dirlist=array_shift($dirlist);
  588. }
  589. if (count($dirlist)>0) {
  590. $created = $this->create_zip($dirlist, $youwhat, $backup_file_basename, $index);
  591. # Now, store the results
  592. if (!is_string($created) && !is_array($created)) $updraftplus->log("$youwhat: create_zip returned an error");
  593. } else {
  594. $updraftplus->log("No backup of $youwhat: there was nothing found to back up");
  595. }
  596. }
  597. if ($created != $whichdir && (is_string($created) || is_array($created))) {
  598. if (is_string($created)) $created=array($created);
  599. foreach ($created as $findex => $fname) {
  600. $backup_array[$youwhat][$index] = $fname;
  601. $itext = ($index == 0) ? '' : $index;
  602. $index++;
  603. $backup_array[$youwhat.$itext.'-size'] = filesize($this->updraft_dir.'/'.$fname);
  604. }
  605. }
  606. $this->job_file_entities[$youwhat]['index'] = $this->index;
  607. $updraftplus->jobdata_set('job_file_entities', $this->job_file_entities);
  608. }
  609. } else {
  610. $updraftplus->log("No backup of $youwhat: excluded by user's options");
  611. }
  612. }
  613. return $backup_array;
  614. }
  615. // This uses a saved status indicator; 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 saved status indicator just helps save resources.
  616. public function resumable_backup_of_files($resumption_no) {
  617. global $updraftplus;
  618. //backup directories and return a numerically indexed array of file paths to the backup files
  619. $bfiles_status = $updraftplus->jobdata_get('backup_files');
  620. if ('finished' == $bfiles_status) {
  621. $updraftplus->log("Creation of backups of directories: already finished");
  622. $backup_array = $updraftplus->jobdata_get('backup_files_array');
  623. if (!is_array($backup_array)) $backup_array = array();
  624. # Check for recent activity
  625. foreach ($backup_array as $files) {
  626. if (!is_array($files)) $files=array($files);
  627. foreach ($files as $file) $updraftplus->check_recent_modification($this->updraft_dir.'/'.$file);
  628. }
  629. } elseif ('begun' == $bfiles_status) {
  630. if ($resumption_no>0) {
  631. $updraftplus->log("Creation of backups of directories: had begun; will resume");
  632. } else {
  633. $updraftplus->log("Creation of backups of directories: beginning");
  634. }
  635. $updraftplus->jobdata_set('jobstatus', 'filescreating');
  636. $backup_array = $this->backup_dirs($bfiles_status);
  637. $updraftplus->jobdata_set('backup_files_array', $backup_array);
  638. $updraftplus->jobdata_set('backup_files', 'finished');
  639. $updraftplus->jobdata_set('jobstatus', 'filescreated');
  640. } else {
  641. # This is not necessarily a backup run which is meant to contain files at all
  642. $updraftplus->log('This backup run is not intended for files - skipping');
  643. return array();
  644. }
  645. /*
  646. // DOES NOT WORK: there is no crash-safe way to do this here - have to be renamed at cloud-upload time instead
  647. $new_backup_array = array();
  648. foreach ($backup_array as $entity => $files) {
  649. if (!is_array($files)) $files=array($files);
  650. $outof = count($files);
  651. foreach ($files as $ind => $file) {
  652. $nval = $file;
  653. if (preg_match('/^(backup_[\-0-9]{15}_.*_[0-9a-f]{12}-[\-a-z]+)([0-9]+)?\.zip$/i', $file, $matches)) {
  654. $num = max((int)$matches[2],1);
  655. $new = $matches[1].$num.'of'.$outof.'.zip';
  656. if (file_exists($this->updraft_dir.'/'.$file)) {
  657. if (@rename($this->updraft_dir.'/'.$file, $this->updraft_dir.'/'.$new)) {
  658. $updraftplus->log(sprintf("Renaming: %s to %s", $file, $new));
  659. $nval = $new;
  660. }
  661. } elseif (file_exists($this->updraft_dir.'/'.$new)) {
  662. $nval = $new;
  663. }
  664. }
  665. $new_backup_array[$entity][$ind] = $nval;
  666. }
  667. }
  668. */
  669. return $backup_array;
  670. }
  671. /* This function is resumable, using the following method:
  672. - Each table is written out to ($final_filename).table.tmp
  673. - When the writing finishes, it is renamed to ($final_filename).table
  674. - When all tables are finished, they are concatenated into the final file
  675. */
  676. public function backup_db($already_done = 'begun') {
  677. global $updraftplus, $wpdb;
  678. $this->table_prefix = $updraftplus->get_table_prefix(true);
  679. $this->table_prefix_raw = $updraftplus->get_table_prefix(false);
  680. $errors = 0;
  681. if (!$updraftplus->backup_time) $updraftplus->backup_time_nonce();
  682. if (!$updraftplus->opened_log_time) $updraftplus->logfile_open($updraftplus->nonce);
  683. // Get the blog name and rip out all non-alphanumeric chars other than _
  684. $blog_name = preg_replace('/[^A-Za-z0-9_]/','', str_replace(' ','_', substr(get_bloginfo(), 0, 32)));
  685. if (!$blog_name) $blog_name = 'non_alpha_name';
  686. $blog_name = apply_filters('updraftplus_blog_name', $blog_name);
  687. $file_base = 'backup_'.get_date_from_gmt(gmdate('Y-m-d H:i:s', $updraftplus->backup_time), 'Y-m-d-Hi').'_'.$blog_name.'_'.$updraftplus->nonce;
  688. $backup_file_base = $this->updraft_dir.'/'.$file_base;
  689. if ('finished' == $already_done) return basename($backup_file_base.'-db.gz');
  690. if ('encrypted' == $already_done) return basename($backup_file_base.'-db.gz.crypt');
  691. $updraftplus->jobdata_set('jobstatus', 'dbcreating');
  692. $binsqldump = $updraftplus->find_working_sqldump();
  693. $total_tables = 0;
  694. $all_tables = $wpdb->get_results("SHOW TABLES", ARRAY_N);
  695. $all_tables = array_map(create_function('$a', 'return $a[0];'), $all_tables);
  696. if (0 == count($all_tables)) {
  697. $extra = ($updraftplus->newresumption_scheduled) ? ' - '.__('please wait for the rescheduled attempt', 'updraftplus') : '';
  698. $updraftplus->log("Error: No database tables found (SHOW TABLES returned nothing)".$extra);
  699. $updraftplus->log(__("No database tables found", 'updraftplus').$extra, 'error');
  700. die;
  701. }
  702. // Put the options table first
  703. usort($all_tables, array($this, 'backup_db_sorttables'));
  704. if (!$updraftplus->really_is_writable($this->updraft_dir)) {
  705. $updraftplus->log("The backup directory (".$this->updraft_dir.") is not writable.");
  706. $updraftplus->log($this->updraft_dir.": ".__('The backup directory is not writable - the database backup is expected to shortly fail.','updraftplus'), 'warning');
  707. # Why not just fail now? We saw a bizarre case when the results of really_is_writable() changed during the run.
  708. }
  709. $stitch_files = array();
  710. $how_many_tables = count($all_tables);
  711. $found_options_table = false;
  712. foreach ($all_tables as $table) {
  713. $manyrows_warning = false;
  714. $total_tables++;
  715. // Increase script execution time-limit to 15 min for every table.
  716. @set_time_limit(900);
  717. // The table file may already exist if we have produced it on a previous run
  718. $table_file_prefix = $file_base.'-db-table-'.$table.'.table';
  719. if ($this->table_prefix_raw.'options' == $table) $found_options_table = true;
  720. if (file_exists($this->updraft_dir.'/'.$table_file_prefix.'.gz')) {
  721. $updraftplus->log("Table $table: corresponding file already exists; moving on");
  722. $stitch_files[] = $table_file_prefix;
  723. } else {
  724. # === is needed, otherwise 'false' matches (i.e. prefix does not match)
  725. if (empty($this->table_prefix) || strpos($table, $this->table_prefix) === 0 ) {
  726. // Open file, store the handle
  727. $opened = $this->backup_db_open($this->updraft_dir.'/'.$table_file_prefix.'.tmp.gz', true);
  728. if (false === $opened) return false;
  729. // Create the SQL statements
  730. $this->stow("# " . sprintf('Table: %s' ,$updraftplus->backquote($table)) . "\n");
  731. $updraftplus->jobdata_set('dbcreating_substatus', array('t' => $table, 'i' => $total_tables, 'a' => $how_many_tables));
  732. $table_status = $wpdb->get_row("SHOW TABLE STATUS WHERE Name='$table'");
  733. if (isset($table_status->Rows)) {
  734. $rows = $table_status->Rows;
  735. $updraftplus->log("Table $table: Total expected rows (approximate): ".$rows);
  736. $this->stow("# Approximate rows expected in table: $rows\n");
  737. if ($rows > UPDRAFTPLUS_WARN_DB_ROWS) {
  738. $manyrows_warning = true;
  739. $updraftplus->log(sprintf(__("Table %s has very many rows (%s) - we hope your web hosting company gives you enough resources to dump out that table in the backup", 'updraftplus'), $table, $rows), 'warning', 'manyrows_'.$table);
  740. }
  741. }
  742. # Don't include the job data for any backups - so that when the database is restored, it doesn't continue an apparently incomplete backup
  743. if (!empty($this->table_prefix) && $this->table_prefix.'sitemeta' == $table) {
  744. $where = 'meta_key NOT LIKE "updraft_jobdata_%"';
  745. } elseif (!empty($this->table_prefix) && $this->table_prefix.'options' == $table) {
  746. $where = 'option_name NOT LIKE "updraft_jobdata_%"';
  747. } else {
  748. $where = '';
  749. }
  750. # TODO: If no check-in last time, then try the other method (but - any point in retrying slow method on large tables??)
  751. # TODO: Lower this from 10,000 if the feedback is good
  752. $bindump = (isset($rows) && $rows>10000 && is_string($binsqldump)) ? $this->backup_table_bindump($binsqldump, $table, $where) : false;
  753. if (true !== $bindump) $this->backup_table($table, $where);
  754. if (!empty($manyrows_warning)) $updraftplus->log_removewarning('manyrows_'.$table);
  755. // Close file
  756. $updraftplus->log("Table $table: finishing file (${table_file_prefix}.gz - ".round(filesize($this->updraft_dir.'/'.$table_file_prefix.'.tmp.gz')/1024,1)." Kb)");
  757. $this->close($this->dbhandle);
  758. rename($this->updraft_dir.'/'.$table_file_prefix.'.tmp.gz', $this->updraft_dir.'/'.$table_file_prefix.'.gz');
  759. $updraftplus->something_useful_happened();
  760. $stitch_files[] = $table_file_prefix;
  761. } else {
  762. $total_tables--;
  763. $updraftplus->log("Skipping table (lacks our prefix (".$this->table_prefix.")): $table");
  764. }
  765. }
  766. }
  767. if (!$found_options_table) {
  768. $updraftplus->log(__('The database backup appears to have failed - the options table was not found', 'updraftplus'), 'warning', 'optstablenotfound');
  769. $time_this_run = time()-$updraftplus->opened_log_time;
  770. if ($time_this_run > 2000) {
  771. # Have seen this happen; not sure how, but it was apparently deterministic; if the current process had been running for a long time, then apparently all database commands silently failed.
  772. # If we have been running that long, then the resumption may be far off; bring it closer
  773. $updraftplus->reschedule(60);
  774. $updraftplus->log("Have been running very long, and it seems the database went away; terminating");
  775. $updraftplus->record_still_alive();
  776. die;
  777. }
  778. } else {
  779. $updraftplus->log_removewarning('optstablenotfound');
  780. }
  781. // Race detection - with zip files now being resumable, these can more easily occur, with two running side-by-side
  782. $backup_final_file_name = $backup_file_base.'-db.gz';
  783. $time_now = time();
  784. $time_mod = (int)@filemtime($backup_final_file_name);
  785. if (file_exists($backup_final_file_name) && $time_mod>100 && ($time_now-$time_mod)<30) {
  786. $updraftplus->terminate_due_to_activity($backup_final_file_name, $time_now, $time_mod);
  787. } elseif (file_exists($backup_final_file_name)) {
  788. $updraftplus->log("The final database file ($backup_final_file_name) exists, but was apparently not modified within the last 30 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.");
  789. }
  790. // Finally, stitch the files together
  791. $opendb = $this->backup_db_open($backup_final_file_name, true);
  792. if (false === $opendb) return false;
  793. $this->backup_db_header();
  794. // 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
  795. $unlink_files = array();
  796. $sind = 1;
  797. foreach ($stitch_files as $table_file) {
  798. $updraftplus->log("{$table_file}.gz ($sind/$how_many_tables): adding to final database dump");
  799. if (!$handle = gzopen($this->updraft_dir.'/'.$table_file.'.gz', "r")) {
  800. $updraftplus->log("Error: Failed to open database file for reading: ${table_file}.gz");
  801. $updraftplus->log(__("Failed to open database file for reading:", 'updraftplus').' '.$table_file.'.gz', 'error');
  802. $errors++;
  803. } else {
  804. while ($line = gzgets($handle, 2048)) { $this->stow($line); }
  805. gzclose($handle);
  806. $unlink_files[] = $this->updraft_dir.'/'.$table_file.'.gz';
  807. }
  808. $sind++;
  809. }
  810. if (defined("DB_CHARSET")) {
  811. $this->stow("/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n");
  812. }
  813. $updraftplus->log($file_base.'-db.gz: finished writing out complete database file ('.round(filesize($backup_final_file_name)/1024,1).' Kb)');
  814. if (!$this->close($this->dbhandle)) {
  815. $updraftplus->log('An error occurred whilst closing the final database file');
  816. $updraftplus->log(__('An error occurred whilst closing the final database file', 'updraftplus'), 'error');
  817. $errors++;
  818. }
  819. foreach ($unlink_files as $unlink_file) @unlink($unlink_file);
  820. if ($errors > 0) {
  821. return false;
  822. } else {
  823. # We no longer encrypt here - because the operation can take long, we made it resumable and moved it to the upload loop
  824. $updraftplus->jobdata_set('jobstatus', 'dbcreated');
  825. $sha = sha1_file($backup_final_file_name);
  826. $updraftplus->jobdata_set('sha1-db0', $sha);
  827. $updraftplus->log("Total database tables backed up: $total_tables (".basename($backup_final_file_name).": checksum (SHA1): $sha)");
  828. return basename($backup_file_base.'-db.gz');
  829. }
  830. } //wp_db_backup
  831. private function backup_table_bindump($potsql, $table_name, $where) {
  832. $microtime = microtime(true);
  833. global $updraftplus;
  834. $pfile = md5(time().rand()).'.tmp';
  835. file_put_contents($this->updraft_dir.'/'.$pfile, "[mysqldump]\npassword=".DB_PASSWORD."\n");
  836. if ($where) $where="--where='".escapeshellarg($where)."'";
  837. $exec = "cd ".escapeshellarg($this->updraft_dir)."; $potsql --defaults-file=$pfile $where --max_allowed_packet=1M --quote-names --add-drop-table --skip-comments --skip-set-charset --allow-keywords --dump-date --extended-insert --user=".escapeshellarg(DB_USER)." --host=".escapeshellarg(DB_HOST)." ".DB_NAME." ".escapeshellarg($table_name);
  838. $ret = false;
  839. $any_output = false;
  840. $writes = 0;
  841. $handle = popen($exec, "r");
  842. if ($handle) {
  843. while (!feof($handle)) {
  844. $w = fgets($handle);
  845. if ($w) {
  846. $this->stow($w);
  847. $writes++;
  848. $any_output = true;
  849. }
  850. }
  851. $ret = pclose($handle);
  852. if ($ret != 0) {
  853. $updraftplus->log("Binary mysqldump: error (code: $ret)");
  854. // Keep counter of failures? Change value of binsqldump?
  855. } else {
  856. if ($any_output) {
  857. $updraftplus->log("Table $table_name: binary mysqldump finished (writes: $writes) in ".sprintf("%.02f",max(microtime(true)-$microtime,0.00001))." seconds");
  858. $ret = true;
  859. }
  860. }
  861. } else {
  862. $updraftplus->log("Binary mysqldump error: bindump popen failed");
  863. }
  864. # Clean temporary files
  865. @unlink($this->updraft_dir.'/'.$pfile);
  866. return $ret;
  867. }
  868. /**
  869. * Taken partially from phpMyAdmin and partially from
  870. * Alain Wolf, Zurich - Switzerland
  871. * Website: http://restkultur.ch/personal/wolf/scripts/db_backup/
  872. * Modified by Scott Merrill (http://www.skippy.net/)
  873. * to use the WordPress $wpdb object
  874. * @param string $table
  875. * @param string $segment
  876. * @return void
  877. */
  878. private function backup_table($table, $where = '', $segment = 'none') {
  879. global $wpdb, $updraftplus;
  880. $microtime = microtime(true);
  881. $total_rows = 0;
  882. $table_structure = $wpdb->get_results("DESCRIBE $table");
  883. if (! $table_structure) {
  884. //$updraftplus->log(__('Error getting table details','wp-db-backup') . ": $table", 'error');
  885. return false;
  886. }
  887. if($segment == 'none' || $segment == 0) {
  888. // Add SQL statement to drop existing table
  889. $this->stow("\n# " . sprintf(__('Delete any existing table %s','wp-db-backup'),$updraftplus->backquote($table)) . "\n\n");
  890. $this->stow("DROP TABLE IF EXISTS " . $updraftplus->backquote($table) . ";\n");
  891. // Table structure
  892. // Comment in SQL-file
  893. $this->stow("\n# " . sprintf(__('Table structure of table %s','wp-db-backup'),$updraftplus->backquote($table)) . "\n\n");
  894. $create_table = $wpdb->get_results("SHOW CREATE TABLE `$table`", ARRAY_N);
  895. if (false === $create_table) {
  896. $err_msg = sprintf(__('Error with SHOW CREATE TABLE for %s.','wp-db-backup'), $table);
  897. //$updraftplus->log($err_msg, 'error');
  898. $this->stow("#\n# $err_msg\n#\n");
  899. }
  900. $create_line = $updraftplus->str_lreplace('TYPE=', 'ENGINE=', $create_table[0][1]);
  901. # Remove PAGE_CHECKSUM parameter from MyISAM - was internal, undocumented, later removed (so causes errors on import)
  902. if (preg_match('/ENGINE=([^\s;]+)/', $create_line, $eng_match)) {
  903. $engine = $eng_match[1];
  904. if ('myisam' == strtolower($engine)) {
  905. $create_line = preg_replace('/PAGE_CHECKSUM=\d\s?/', '', $create_line, 1);
  906. }
  907. }
  908. $this->stow($create_line.' ;');
  909. if (false === $table_structure) {
  910. $err_msg = sprintf('Error getting table structure of %s', $table);
  911. $this->stow("#\n# $err_msg\n#\n");
  912. }
  913. // Comment in SQL-file
  914. $this->stow("\n\n# " . sprintf('Data contents of table %s',$updraftplus->backquote($table)) . "\n\n");
  915. }
  916. # Some tables have optional data, and should be skipped if they do not work
  917. $table_sans_prefix = substr($table, strlen($this->table_prefix_raw));
  918. $data_optional_tables = apply_filters('updraftplus_data_optional_tables', explode(',', UPDRAFTPLUS_DATA_OPTIONAL_TABLES));
  919. if (in_array($table_sans_prefix, $data_optional_tables)) {
  920. if (!$updraftplus->something_useful_happened && !empty($updraftplus->current_resumption) && ($updraftplus->current_resumption - $updraftplus->last_successful_resumption > 2)) {
  921. $updraftplus->log("Table $table: Data skipped (previous attempts failed, and table is marked as n