function DBError($file, $line, $friendly = false) { $this->Log_Event(WPONLINEBACKUP_EVENT_ERROR, __('A database operation failed.', 'wponlinebackup') . PHP_EOL . __('Please try reinstalling the plugin - in most cases this will repair the database.', 'wponlinebackup') . PHP_EOL . __('Please contact support if the issue persists, providing the complete event log for the activity. Diagnostic information follows:', 'wponlinebackup') . PHP_EOL . PHP_EOL . 'Failed at: ' . $file . '(' . $line . ')' . PHP_EOL . WPOnlineBackup::Get_WPDB_Last_Error()); if ($friendly === false) { $friendly = __('A database operation failed.', 'wponlinebackup'); } return $friendly; }
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; }