function Backup_Table() { global $wpdb; // We backup each table and jump back to bootstrap after each table for a forced update // We may improve this to work same as files in future and loop forever in here and decide on our own forced updates while ($this->job['progress'] != 100) { $where = array(); if (count($this->job['primary'])) { // We have a primary or unique key, add an order by clause $extra = ' ORDER BY ' . WPOnlineBackup_Backup_Tables::Implode_Backquote(' ASC, ', $this->job['primary']) . ' ASC'; // Calculate the where clause based on the key if we already have the table information // If we don't have the table information yet, we don't give a WHERE so we can get the first set of rows if (!is_null($this->job['total'])) { $previous = array(); // We search for records with ID higher than the last id. // For multi-column, we check for where the first ID is higher, or the first ID is the same and the second ID is higher, and so on foreach ($this->job['primary'] as $index => $column) { $value = $this->job['last_id'][$index]; // Calls _real_escape if it exists - escape() seems to call _weak_escape() instead $wpdb->escape_by_ref($value); if (count($previous)) { $where[] = '(' . implode(' AND ', $previous) . ' AND `' . $column . '` > \'' . $value . '\')'; } else { $where[] = '`' . $column . '` > \'' . $value . '\''; } $previous[] = '`' . $column . '` = \'' . $value . '\''; } if (count($where) > 1) { $where = array('(' . implode(' OR ', $where) . ')'); } } // When using a key, we don't need a start offset, as we calculate it based on IDs $start = ''; } else { $extra = ''; // No primary or unique key available, so we failback to setting a start offset if (!is_null($this->job['total'])) { $start = $this->job['done'] . ', '; } else { $start = ''; } } if ($is_comments = preg_match($this->multisite_prefix_regex . 'comments$#', $this->job['table'])) { if ($this->WPOnlineBackup->Get_Setting('ignore_spam_comments')) { $where[] = '`comment_approved` <> \'spam\''; } if ($this->WPOnlineBackup->Get_Setting('ignore_trash_comments')) { $where[] = '`comment_approved` <> \'trash\''; } } else { if ($this->job['table'] == $this->db_prefix . 'options') { // Remove this option - it will trigger our tables to be created on restore $where[] = '`option_name` <> \'wponlinebackup_check_tables\''; } } $where = implode(' AND ', $where); if ($where) { $where = ' WHERE ' . $where; } if (is_null($this->job['total'])) { $this->progress['message'] = sprintf(__('Backing up %s...', 'wponlinebackup'), $this->job['table']); $drop = 'DROP TABLE IF EXISTS `' . $this->job['table'] . '`;' . WPONLINEBACKUP_EOL . WPONLINEBACKUP_EOL; // Table information doesn't exist, so let's gather it, first by getting the CREATE TABLE dump $wpdb->query('SET sql_quote_show_create = 1'); $create = $wpdb->get_var('SHOW CREATE TABLE `' . $this->job['table'] . '`', 1); // SHOW CREATE TABLE should always return a row, so 0 rows (null) or error (null) both are considered an error if (is_null($create)) { // Failed to gather table information - report, and skip the table $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_ERROR, sprintf(__('Failed to retrieve information for table \'%s\': %s. The table will be skipped.', 'wponlinebackup'), $this->job['table'], WPOnlineBackup::Get_WPDB_Last_Error())); $this->job['progress'] = 100; // Breaking will throw us all the way out back into bootstrap for a forced update that will prevent the event log duplicating break; } // Normalise line-endings $create = preg_replace('/\\r\\n?|\\n/', WPONLINEBACKUP_EOL, $create); $create .= ';' . WPONLINEBACKUP_EOL . WPONLINEBACKUP_EOL; $this->progress['rsize'] += strlen($create); if (($ret = $this->stream->Write_Stream($drop . $create)) !== true) { return $ret; } // Get the total number of rows in the table, so we can provide progress information if required $this->job['total'] = $wpdb->get_var('SELECT COUNT(*) FROM `' . $this->job['table'] . '`' . $where); // SELECT COUNT(*) should always return a row, so 0 rows (null) or error (null) both are considered an error if (is_null($this->job['total'])) { // Failed to gather row count - report, and skip the table $this->bootstrap->Log_Event(WPONLINEBACKUP_EVENT_ERROR, sprintf(__('Failed to retrieve row count for table \'%s\': %s. The table will be skipped.', 'wponlinebackup'), $this->job['table'], WPOnlineBackup::Get_WPDB_Last_Error())); $this->job['progress'] = 100; // Breaking will throw us all the way out back into bootstrap for a forced update that will prevent the event log duplicating break; } $this->bootstrap->Tick(); } $fields = WPOnlineBackup_Backup_Tables::Implode_Backquote(',', $this->job['fields']); // Begin retrieving data if (($result = $this->Query($fields, $this->job['table'], $where, $extra, $start)) === false) { // Failure is logged inside Query() $this->job['progress'] = 100; // Breaking here throws back to bootstrap for a forced update - this will prevent duplicating the event log entries Query() wrote break; } $this->bootstrap->Tick(); // Create a fully escaped insert statement $insert = ''; $row_count = 0; $insert_size = 0; while (false !== ($next_row = $this->Fetch_Row($result))) { $row = $next_row; $values = array(); $row_count++; foreach ($row as $index => $value) { $insert_size += strlen($value); // If we're not the first row and our insert has got too big, write the insert and start another // This prevents our insert getting rediculously big if ($row_count > 1 && $insert_size > $this->max_block_size) { $row_count--; break 2; } if (is_null($value)) { $value = 'NULL'; } else { if (!$this->Requires_Quotes($value)) { } else { // escape_by_ref uses _real_escape - preferred. escape() appears to only use _weak_escape() $wpdb->escape_by_ref($value); $value = '\'' . $value . '\''; } } $values[] = $value; } $insert .= ($row_count == 1 ? 'INSERT INTO `' . $this->job['table'] . '` (' . $fields . ') VALUES' . WPONLINEBACKUP_EOL : ',' . WPONLINEBACKUP_EOL) . '(' . implode(',', $values) . ')'; } unset($values); unset($value); $this->Free_Result($result); // If 0 rows were returned, we reached the end of the dataset // We couldn't use num_rows or anything as we may be an unbuffered query result if ($row_count == 0) { $this->job['progress'] = 100; break; } // Finish the statement $insert .= ';' . WPONLINEBACKUP_EOL . WPONLINEBACKUP_EOL; $this->job['done'] += $row_count; if ($this->job['done'] >= $this->job['total']) { $this->job['progress'] = 99; } else { $this->job['progress'] = floor($this->job['done'] * 99 / $this->job['total']); if ($this->job['progress'] >= 100) { $this->job['progress'] = 99; } } $this->progress['message'] = sprintf(__('Backing up %s; %d of %d rows...', 'wponlinebackup'), $this->job['table'], $this->job['done'], $this->job['total']); // If we are tracking using a key, update the last_id fields if (count($this->job['primary'])) { foreach ($this->job['primary'] as $index => $column) { $this->job['last_id'][$index] = $row[array_search($column, $this->job['fields'])]; } } // Add to the dump $this->progress['rsize'] += strlen($insert); if (($ret = $this->stream->Write_Stream($insert)) !== true) { return $ret; } $this->bootstrap->Tick(); } return true; }
function Initialise_Backup(&$bootstrap, &$progress, &$next_step) { global $wpdb; // First of all, clean up everything from last backup if ($progress['initialise'] < ++$next_step) { $this->CleanUp(); $progress['initialise'] = $next_step; $bootstrap->Tick(); } // First of all, clear back activity logs if ($progress['initialise'] < ++$next_step) { do { if (($ret = $wpdb->query('DELETE a, e FROM `' . $this->db_prefix . 'wponlinebackup_activity_log` a ' . 'LEFT JOIN `' . $this->db_prefix . 'wponlinebackup_event_log` e ON (e.activity_id = a.activity_id) ' . 'WHERE a.start < ' . strtotime('-' . $this->WPOnlineBackup->Get_Setting('max_log_age') . ' months', $progress['start_time']))) === false) { return $bootstrap->DBError(__LINE__, __FILE__); } // Before the Tick so we skip completely if we're done if (!$ret) { $progress['initialise'] = $next_step; } $bootstrap->Tick(); } while ($ret); } if ($progress['initialise'] < ++$next_step) { // If online, add a synchronisation job if ($progress['config']['target'] == 'online') { $progress['jobs'][] = array('processor' => 'transmission', 'progress' => 0, 'progresslen' => 5, 'retries' => 0, 'action' => 'synchronise', 'total_items' => 0, 'total_generations' => 0, 'done_items' => 0, 'done_generations' => 0); } $progress['initialise'] = $next_step; $bootstrap->Tick(); } if ($progress['initialise'] < ++$next_step) { // Are we backing up the database? if ($progress['config']['backup_database']) { require_once WPONLINEBACKUP_PATH . '/include/tables.php'; $tables = new WPOnlineBackup_Backup_Tables($this->WPOnlineBackup, $this->db_force_master); // Initialise - pass ourself so we can log events, and also pass the progress and its tracker if (true !== ($ret = $tables->Initialise($this, $progress))) { return $ret; } } $progress['initialise'] = $next_step; $bootstrap->Tick(); } if ($progress['initialise'] < ++$next_step) { // Are we backing up the filesystem? if ($progress['config']['backup_filesystem']) { require_once WPONLINEBACKUP_PATH . '/include/files.php'; $files = new WPOnlineBackup_Backup_Files($this->WPOnlineBackup, $this->db_force_master); // Initialise - pass ourselves so we can log events, and also pass the progress and its tracker if (true !== ($ret = $files->Initialise($this, $progress))) { return $ret; } } $progress['initialise'] = $next_step; $bootstrap->Tick(); } if ($progress['initialise'] < ++$next_step) { // Reconstruct the data files $progress['jobs'][] = array('processor' => 'reconstruct', 'progress' => 0, 'progresslen' => 5); if ($progress['config']['target'] == 'online') { // Online needs to transmit $progress['jobs'][] = array('processor' => 'transmission', 'progress' => 0, 'progresslen' => 10, 'retries' => 0, 'action' => 'transmit', 'total' => 0, 'done' => 0, 'done_retention' => 0, 'retention_size' => 0, 'new_bsn' => 0, 'wait' => false); } else { if ($progress['config']['target'] == 'email') { // Email needs to email $progress['jobs'][] = array('processor' => 'email', 'progress' => 0, 'progresslen' => 10, 'retries' => 0); } } // Add the cleanups to the end of the job list so they happen only after the main backup jobs have finished $progress['jobs'] = array_merge($progress['jobs'], $progress['cleanups']); $progress['cleanups'] = array(); // Add on the file cleanup - this places local backups in their correct place and deletes files for online backup etc. $progress['jobs'][] = array('processor' => 'cleanupfiles', 'progress' => 0, 'progresslen' => 5); // Retention for local backups should be done AFTER cleanupfiles, since cleanupfiles adds to the database the current backup and retention should take that into account if ($progress['config']['target'] == 'download') { // Local needs retention $progress['jobs'][] = array('processor' => 'localretention', 'progress' => 0, 'progresslen' => 5, 'deleted_gens' => 0, 'deleted_storage' => 0, 'delete_error' => 0); } // Total up the jobcount as total of all progresslens - makes calculating progress % easier foreach ($progress['jobs'] as $job) { $progress['jobcount'] += $job['progresslen']; } $progress['initialise'] = $next_step; $bootstrap->Tick(); } if ($progress['initialise'] < ++$next_step) { if ($progress['config']['target'] == 'download') { // Create the local backup directory if (true !== ($ret = $bootstrap->Create_Dir_Local_Backup())) { return $ret; } } // Try to create the temporary backup directory if (true !== ($ret = $bootstrap->Create_Dir_Temporary())) { return $ret; } $progress['initialise'] = $next_step; // Force a save here since we did some file operation checks and it'll save us doing it again $bootstrap->Tick(false, true); } // Prepare the stream configuration // - the streams use this configuration instead of the central configuration so we can use different settings in different streams $config = array('designated_path' => $progress['cache']['local_tmp_dir'], 'compression' => $this->WPOnlineBackup->Get_Env('deflate_available') ? 'DEFLATE' : 'store', 'encryption' => $progress['cache']['enc_type'], 'encryption_key' => $progress['cache']['enc_key']); // Set up the required stream if ($progress['config']['target'] == 'online') { $stream_type = 'Stream_Delta'; } else { $stream_type = 'Stream_Full'; } require_once WPONLINEBACKUP_PATH . '/include/' . strtolower($stream_type) . '.php'; $name = 'WPOnlineBackup_' . $stream_type; $stream = new $name($this->WPOnlineBackup); // Open the file - suppress errors in html_entity_decode as PHP4 will flood out warnings about multibyte characters if (($ret = $stream->Open($config, @html_entity_decode(get_bloginfo('name'), ENT_QUOTES, get_bloginfo('charset')), @html_entity_decode(get_bloginfo('description'), ENT_QUOTES, get_bloginfo('charset')))) !== true) { return $bootstrap->Create_Failure(); } if ($progress['config']['target'] == 'email') { // Check we aren't too big to process. Add 50% to the filesize to allow for MIME encoding and headers etc, and take 5MB from Memory_Limit for processing $max = floor((($memory_limit = $this->WPOnlineBackup->Memory_Limit()) - 5 * 1024 * 1024) / 2.5); } // Store the steam - we cannot store it in this->stream until it is Opened because we call CleanUp in On_Shutdown if an error occurs and CleanUp can not be called until after Open or Load $bootstrap->Set_Stream($stream_type, $stream); return true; }
function Print_Settings_Backup() { // Include the tables backup processor require_once WPONLINEBACKUP_PATH . '/include/tables.php'; require_once WPONLINEBACKUP_PATH . '/include/files.php'; // Create an instance of tables and files $tables = new WPOnlineBackup_Backup_Tables($this->WPOnlineBackup); $files = new WPOnlineBackup_Backup_Files($this->WPOnlineBackup); // Grab the available table list list($core, $custom) = $tables->Fetch_Available(); // Convert to HTMLEntities foreach ($core as $entry => $display) { $core[$entry] = htmlentities($display, ENT_QUOTES); } foreach ($custom as $entry => $display) { $custom[$entry] = htmlentities($display, ENT_QUOTES); } // Find the last custom item so we know when to stop placing line breaks end($custom); $last = key($custom); // Get the uploads directory information $uploads = wp_upload_dir(); // Resolve the parent folder path so we can display it list($root, $parent_root, $wordpress_root) = $files->Get_Roots(); // Regex to strip root from a path $strip_root = '#^' . preg_quote($root, '#') . '/#'; ?> <p style="float: left; margin: 5px"><img src="<?php echo WPONLINEBACKUP_URL; ?> /images/settings.png" alt=""></p> <p><?php _e('These settings affect the behaviour of the backups. If you have custom/plugin database tables you can choose whether to back them up or not, or only choose to backup certain ones. You can also exclude comments and trash.', 'wponlinebackup'); ?> </p> <p><?php _e('For filesystem backups you can also specify whether to include themes and plugins or not.', 'wponlinebackup'); ?> </p> <table class="form-table" style="clear: left; border-top: 1px solid #DFDFDF"> <tr valign="top"> <th scope="row" style="text-align: right; padding: 15px"><label style="font-weight: bold"><?php _e('Database backup behaviour:', 'wponlinebackup'); ?> </label></th> <td><?php // If we have detected core tables, display them so we know they are going to be backed up if (count($core)) { ?> <p><?php _e('Core WordPress tables will always be backed up when the database is included.', 'wponlinebackup'); ?> </p> <?php } ?> <p><input type="radio" name="selection_method" id="selection_method_include" value="include"<?php // Mark as checked if we selected this option if ($this->WPOnlineBackup->Get_Setting('selection_method') == 'include') { ?> checked="checked"<?php } ?> > <label for="selection_method_include"><?php _e('ONLY backup the custom/plugin tables selected below (new tables will not be backed up until explicitly selected.)', 'wponlinebackup'); ?> </label><br> <input type="radio" name="selection_method" id="selection_method_exclude" value="exclude"<?php // Mark as checked if we selected this option if ($this->WPOnlineBackup->Get_Setting('selection_method') != 'include') { ?> checked="checked"<?php } ?> > <label for="selection_method_exclude"><?php _e('Backup all custom/plugin tables EXCEPT those selected below (new tables will automatically be backed up until explicitly selected.) [Recommended]', 'wponlinebackup'); ?> </label></p> <p><?php // If we have custom tables, list them here, or display a message saying none found if (count($custom)) { foreach ($custom as $entry => $display) { ?> <input type="checkbox" name="selection_list[]" id="selection_list_<?php echo $display; ?> " value="<?php echo $display; ?> "<?php // Check the box if we have this table selected if (in_array($entry, $this->WPOnlineBackup->Get_Setting('selection_list'))) { ?> checked="checked"<?php } ?> > <label for="selection_list_<?php echo $display; ?> "><?php echo $display; ?> </label><?php // If not the last, add a new line if ($entry != $last) { ?> <br> <?php } } } else { ?> <i><?php _e('No non-default tables currently exist.', 'wponlinebackup'); ?> </i><?php } ?> </p></td> </tr> <tr valign="top"> <th scope="row" style="text-align: right; padding: 15px"><label style="font-weight: bold"><?php _e('Database excludes:', 'wponlinebackup'); ?> </label></th> <td><p><input name="ignore_trash_comments" type="checkbox" id="ignore_trash_comments"<?php // Is this enabled? if ($this->WPOnlineBackup->Get_Setting('ignore_trash_comments')) { ?> checked="checked"<?php } ?> value="1"> <label for="ignore_trash_comments"><?php _e('Exclude comments in the trash.', 'wponlinebackup'); ?> </label><br> <input name="ignore_spam_comments" type="checkbox" id="ignore_spam_comments"<?php // Is this enabled? if ($this->WPOnlineBackup->Get_Setting('ignore_spam_comments')) { ?> checked="checked"<?php } ?> value="1"> <label for="ignore_spam_comments"><?php _e('Exclude comments that are marked as spam.', 'wponlinebackup'); ?> </label></p></td> </tr> <tr valign="top"> <th scope="row" style="text-align: right; padding: 15px"><label style="font-weight: bold"><?php _e('Filesystem excludes:', 'wponlinebackup'); ?> </label></th> <td><p><?php _e('Files that are part of the default WordPress installation will always be backed up when the filesystem is included.', 'wponlinebackup'); ?> </p> <p><input name="filesystem_plugins" type="checkbox" id="filesystem_plugins"<?php // Is this enabled? if (!$this->WPOnlineBackup->Get_Setting('filesystem_plugins')) { ?> checked="checked"<?php } ?> value="1"> <label for="filesystem_plugins"><?php printf(__('Exclude plugins (%s)', 'wponlinebackup'), implode('; ', array(preg_replace($strip_root, '', $files->Normalise_Path(WP_PLUGIN_DIR)), preg_replace($strip_root, '', $files->Normalise_Path(WPMU_PLUGIN_DIR))))); ?> </label><br> <input name="filesystem_themes" type="checkbox" id="filesystem_themes"<?php // Is this enabled? if (!$this->WPOnlineBackup->Get_Setting('filesystem_themes')) { ?> checked="checked"<?php } ?> value="1"> <label for="filesystem_themes"><?php printf(__('Exclude themes (%s)', 'wponlinebackup'), preg_replace($strip_root, '', $files->Normalise_Path(get_theme_root()))); ?> </label><br> <input name="filesystem_uploads" type="checkbox" id="filesystem_uploads"<?php // Is this enabled? if (!$this->WPOnlineBackup->Get_Setting('filesystem_uploads')) { ?> checked="checked"<?php } ?> value="1"> <label for="filesystem_uploads"><?php printf(__('Exclude uploads (%s)', 'wponlinebackup'), preg_replace($strip_root, '', $files->Normalise_Path($uploads['basedir']))); ?> </label></p> <p><?php printf(__('Custom filesystem excludes can be specified here, one per line, relative to the following folder: %s', 'wponlinebackup'), $root); ?> <br> <?php printf(__('For example, to exclude %s, enter %s into the box below on its own line. Always use forward slashes even if your server runs Windows.'), $root . '/folder/cache', 'folder/cache'); ?> <br> <textarea rows="10" cols="60" name="filesystem_excludes" id="filesystem_excludes"><?php echo esc_html($this->WPOnlineBackup->Get_Setting('filesystem_excludes')); ?> </textarea></p></td> </tr> <tr valign="top"> <th scope="row" style="text-align: right; padding: 15px"><label style="font-weight: bold"><?php _e('WordPress in a subdirectory:', 'wponlinebackup'); ?> </label></th> <td><p><?php _e('By default, only the WordPress folder is backed up. However, if your WordPress folder is installed as a subdirectory within a larger website, and you wish to backup that website also, this option will allow you to do just that.', 'wponlinebackup'); ?> <br> <b><?php _e('If you are not sure or have not installed WordPress as a subdirectory, you should leave this option disabled as you may end up backing up significantly more than expected.', 'wponlinebackup'); ?> </b><br> <?php _e('You can exclude items from this level by prefixing the filesystem exclude entry with "../" (dot dot forward-slash) like this: ../exclude/from/parent'); ?> </p> <?php if (false === ($d = @opendir($parent_root))) { // Couldn't resolve, let the user know this option will not work, but don't hide the option so it can be disabled if it was enabled ?> <p><span style="padding: 4px; display: inline-block; text-align: left; border: 1px dashed #000; background: #E9E9E9"> <img src="<?php echo WPONLINEBACKUP_URL; ?> /images/information.png" style="width: 16px; height: 16px; vertical-align: middle" alt=""> <b><?php _e('WordPress looks like it is NOT installed as a subdirectory so this option SHOULD be ignored. If you believe this to be wrong, you can enable the option anyway and any problems will be reported in the event log during backup.', 'wponlinebackup'); ?> </b> </span></p> <?php $upone_parent = false; } else { $upone_parent = true; @closedir($d); } ?> <p><input name="filesystem_upone" type="checkbox" id="filesystem_upone"<?php // Is this enabled? if ($this->WPOnlineBackup->Get_Setting('filesystem_upone')) { ?> checked="checked"<?php } ?> value="1"> <label for="filesystem_upone"><?php if ($upone_parent) { printf(__('Backup the parent directory as well as the WordPress directory: %s', 'wponlinebackup'), $parent_root); } else { _e('Backup the parent directory as well as the WordPress directory.', 'wponlinebackup'); } ?> </label></p></td> </tr> <tr valign="top"> <th scope="row" style="text-align: right; padding: 18px"><label for="local_min_gens" style="font-weight: bold"><?php _e('Minimum Local Backups:', 'wponlinebackup'); ?> </label></th> <td><p><?php _e('The minimum number of Local Backups that should be kept, regardless of their size. We recommend 2 so you always have at least the last 2 backups available.', 'wponlinebackup'); ?> </p> <p><input id="local_min_gens" name="local_min_gens" type="text" pattern="\d*" value="<?php echo esc_attr($this->WPOnlineBackup->Get_Setting('local_min_gens')); ?> " /></p></td> </tr> <tr valign="top"> <th scope="row" style="text-align: right; padding: 18px"><label for="local_max_storage" style="font-weight: bold"><?php _e('Maximum Local Backups Storage:', 'wponlinebackup'); ?> </label></th> <td><p><?php _e('The maximum storage space Local Backups should utilise in MiB. Older Local Backups will automatically be deleted when storage exceeds this. This settings is ignored if you have less backups available than the Minimum Local Backups.', 'wponlinebackup'); ?> </p> <p><input id="local_max_storage" name="local_max_storage" type="text" pattern="\d*" value="<?php echo esc_attr($this->WPOnlineBackup->Get_Setting('local_max_storage')); ?> " /></p></td> </tr> <tr valign="top"> <th scope="row" style="text-align: right; padding: 18px"><label for="max_log_age" style="font-weight: bold"><?php _e('Activity and event logs:', 'wponlinebackup'); ?> </label></th> <td><p><?php _e('This is for how long activity and event logs should be kept. We recommend 6 months.', 'wponlinebackup'); ?> </p> <p><select id="max_log_age" name="max_log_age"> <?php for ($i = 1; $i <= 12; $i++) { ?> <option value="<?php echo $i; ?> "<?php if ($this->WPOnlineBackup->Get_Setting('max_log_age') == $i) { ?> selected="selected"<?php } ?> ><?php if ($i == 6) { printf(__('%d months [Recommended]', 'wponlinebackup'), $i); } else { printf(_n('%d month', '%d months', $i, 'wponlinebackup'), $i); } ?> </option> <?php } ?> </select></p></td> </tr> </table> <?php }