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;
 }