2016-11-28 21:52:15 -08:00
< ? php
class AIOWPSecurity_Backup
{
var $last_backup_file_name ; //Stores the name of the last backup file when execute_backup function is called
var $last_backup_file_path ;
var $last_backup_file_dir_multisite ;
function __construct ()
{
add_action ( 'aiowps_perform_scheduled_backup_tasks' , array ( & $this , 'aiowps_scheduled_backup_handler' ));
add_action ( 'aiowps_perform_db_cleanup_tasks' , array ( & $this , 'aiowps_scheduled_db_cleanup_handler' ));
}
/**
* Add slashes , sanitize end - of - line characters ( ? ), wrap $value in quotation marks .
* @ param string $value
* @ return string
*/
function sanitize_db_field ( $value ) {
return '"' . preg_replace ( " / " . PHP_EOL . " / " , " \n " , addslashes ( $value ) ) . '"' ;
}
/**
* Write sql dump of $tables to backup file identified by $handle .
* @ global wpdb $wpdb WordPress database abstraction object .
* @ global AIO_WP_Security $aio_wp_security
* @ param resource $handle
* @ param array $tables
* @ return boolean True , if database tables dump have been successfully written to the backup file , false otherwise .
*/
function write_db_backup_file ( $handle , $tables )
{
global $wpdb , $aio_wp_security ;
$preamble
= " -- All In One WP Security & Firewall { $aio_wp_security -> version } " . PHP_EOL
. '-- MySQL dump' . PHP_EOL
. '-- ' . date ( 'Y-m-d H:i:s' ) . PHP_EOL . PHP_EOL
// When importing the backup, tell database server that our data is in UTF-8...
. " SET NAMES utf8; " . PHP_EOL
// ...and that foreign key checks should be ignored.
. " SET foreign_key_checks = 0; " . PHP_EOL . PHP_EOL
;
if ( !@ fwrite ( $handle , $preamble ) ) { return false ; }
// Loop through each table
foreach ( $tables as $table )
{
$table_name = $table [ 0 ];
$result_create_table = $wpdb -> get_row ( 'SHOW CREATE TABLE `' . $table_name . '`;' , ARRAY_N );
if ( empty ( $result_create_table ) ) {
$aio_wp_security -> debug_logger -> log_debug ( __METHOD__ . " - get_row returned NULL for table: " . $table_name , 4 );
return false ; // Avoid incomplete backups
}
// Drop/create table preamble
$drop_and_create = 'DROP TABLE IF EXISTS `' . $table_name . '`;' . PHP_EOL . PHP_EOL
. $result_create_table [ 1 ] . " ; " . PHP_EOL . PHP_EOL
;
if ( !@ fwrite ( $handle , $drop_and_create ) ) { return false ; }
// Dump table contents
// Fetch results as row of objects to spare memory.
$result = $wpdb -> get_results ( 'SELECT * FROM `' . $table_name . '`;' , OBJECT );
foreach ( $result as $object_row )
{
// Convert object row to array row: this is what $wpdb->get_results()
// internally does when invoked with ARRAY_N param, but in the process
// it creates new copy of entire results array that eats a lot of memory.
$row = array_values ( get_object_vars ( $object_row ));
// Start INSERT statement
if ( !@ fwrite ( $handle , 'INSERT INTO `' . $table_name . '` VALUES(' ) ) { return false ; }
// Loop through all fields and echo them out
foreach ( $row as $idx => $field ) {
// Echo fields separator (except for first loop)
if ( ( $idx > 0 ) && !@ fwrite ( $handle , ',' ) ) { return false ; }
// Echo field content (sanitized)
if ( !@ fwrite ( $handle , $this -> sanitize_db_field ( $field ) ) ) { return false ; }
}
// Finish INSERT statement
if ( !@ fwrite ( $handle , " ); " . PHP_EOL ) ) { return false ; }
}
// Place two-empty lines after table data
if ( !@ fwrite ( $handle , PHP_EOL . PHP_EOL ) ) { return false ; }
}
return true ;
}
/**
* This function will perform a database backup
*/
function execute_backup ()
{
global $wpdb , $aio_wp_security ;
$is_multi_site = function_exists ( 'is_multisite' ) && is_multisite ();
@ ini_set ( 'auto_detect_line_endings' , true );
@ ini_set ( 'memory_limit' , '512M' );
if ( $is_multi_site )
{
//Let's get the current site's table prefix
$site_pref = esc_sql ( $wpdb -> prefix );
$db_query = " SHOW TABLES LIKE ' " . $site_pref . " %' " ;
$tables = $wpdb -> get_results ( $db_query , ARRAY_N );
}
else
{
//get all of the tables
$tables = $wpdb -> get_results ( 'SHOW TABLES' , ARRAY_N );
}
if ( empty ( $tables ) ) {
$aio_wp_security -> debug_logger -> log_debug ( __METHOD__ . " - no tables found! " , 4 );
return false ;
}
//Check to see if the main "backups" directory exists - create it otherwise
$aiowps_backup_dir = WP_CONTENT_DIR . '/' . AIO_WP_SECURITY_BACKUPS_DIR_NAME ;
if ( ! AIOWPSecurity_Utility_File :: create_dir ( $aiowps_backup_dir ))
{
$aio_wp_security -> debug_logger -> log_debug ( __METHOD__ . " - Creation of DB backup directory failed! " , 4 );
return false ;
}
//Generate a random prefix for more secure filenames
$random_suffix = AIOWPSecurity_Utility :: generate_alpha_numeric_random_string ( 10 );
if ( $is_multi_site )
{
global $current_blog ;
$blog_id = $current_blog -> blog_id ;
//Get the current site name string for use later
$site_name = get_bloginfo ( 'name' );
$site_name = strtolower ( $site_name );
//make alphanumeric
$site_name = preg_replace ( " /[^a-z0-9_ \ s-]/ " , " " , $site_name );
//Cleanup multiple instances of dashes or whitespaces
$site_name = preg_replace ( " /[ \ s-]+/ " , " " , $site_name );
//Convert whitespaces and underscore to dash
$site_name = preg_replace ( " /[ \ s_]/ " , " - " , $site_name );
$file = 'database-backup-site-name-' . $site_name . '-' . current_time ( 'Ymd-His' ) . '-' . $random_suffix ;
//We will create a sub dir for the blog using its blog id
$dirpath = $aiowps_backup_dir . '/blogid_' . $blog_id ;
//Create a subdirectory for this blog_id
if ( ! AIOWPSecurity_Utility_File :: create_dir ( $dirpath ))
{
$aio_wp_security -> debug_logger -> log_debug ( " Creation failed of DB backup directory for the following multisite blog ID: " . $blog_id , 4 );
return false ;
}
}
else
{
$dirpath = $aiowps_backup_dir ;
$file = 'database-backup-' . current_time ( 'Ymd-His' ) . '-' . $random_suffix ;
}
$handle = @ fopen ( $dirpath . '/' . $file . '.sql' , 'w+' );
if ( $handle === false ) {
$aio_wp_security -> debug_logger -> log_debug ( " Cannot create DB backup file: { $dirpath } / { $file } .sql " , 4 );
return false ;
}
// Delete old backup files now to avoid polluting backups directory
// with incomplete backups on websites where max execution time is too
// low for database content to be written to a file:
// https://github.com/Arsenal21/all-in-one-wordpress-security/issues/62
$this -> aiowps_delete_backup_files ( $dirpath );
$fw_res = $this -> write_db_backup_file ( $handle , $tables );
@ fclose ( $handle );
if ( ! $fw_res )
{
@ unlink ( $dirpath . '/' . $file . '.sql' );
$aio_wp_security -> debug_logger -> log_debug ( __METHOD__ . " - Write to DB backup file failed " , 4 );
return false ;
}
//zip the file
if ( class_exists ( 'ZipArchive' ) )
{
$zip = new ZipArchive ();
$archive = $zip -> open ( $dirpath . '/' . $file . '.zip' , ZipArchive :: CREATE );
$zip -> addFile ( $dirpath . '/' . $file . '.sql' , $file . '.sql' );
$zip -> close ();
//delete .sql and keep zip
@ unlink ( $dirpath . '/' . $file . '.sql' );
$fileext = '.zip' ;
} else
{
$fileext = '.sql' ;
}
$this -> last_backup_file_name = $file . $fileext ; //database-backup-YYYYMMDD-HHIISS-<random-string>.zip or database-backup-YYYYMMDD-HHIISS-<random-string>.sql
$this -> last_backup_file_path = $dirpath . '/' . $file . $fileext ;
if ( $is_multi_site )
{
$this -> last_backup_file_dir_multisite = $aiowps_backup_dir . '/blogid_' . $blog_id ;
}
$this -> aiowps_send_backup_email (); //Send backup file via email if applicable
return true ;
}
function aiowps_send_backup_email ()
{
global $aio_wp_security ;
if ( $aio_wp_security -> configs -> get_value ( 'aiowps_send_backup_email_address' ) == '1' )
{
//Get the right email address.
if ( is_email ( $aio_wp_security -> configs -> get_value ( 'aiowps_backup_email_address' ) ) )
{
$toaddress = $aio_wp_security -> configs -> get_value ( 'aiowps_backup_email_address' );
} else
{
$toaddress = get_site_option ( 'admin_email' );
}
$to = $toaddress ;
$site_title = get_bloginfo ( 'name' );
$from_name = empty ( $site_title ) ? 'WordPress' : $site_title ;
$headers = 'From: ' . $from_name . ' <' . get_option ( 'admin_email' ) . '>' . PHP_EOL ;
$subject = __ ( 'All In One WP Security - Site Database Backup' , 'all-in-one-wp-security-and-firewall' ) . ' ' . date ( 'l, F jS, Y \a\\t g:i a' , current_time ( 'timestamp' ) );
$attachment = array ( $this -> last_backup_file_path );
$message = __ ( 'Attached is your latest DB backup file for site URL' , 'all-in-one-wp-security-and-firewall' ) . ' ' . get_option ( 'siteurl' ) . __ ( ' generated on' , 'all-in-one-wp-security-and-firewall' ) . ' ' . date ( 'l, F jS, Y \a\\t g:i a' , current_time ( 'timestamp' ) );
$sendMail = wp_mail ( $to , $subject , $message , $headers , $attachment );
if ( FALSE === $sendMail ){
$aio_wp_security -> debug_logger -> log_debug ( " Backup notification email failed to send to " . $to , 4 );
}
}
}
function aiowps_delete_backup_files ( $backups_dir )
{
global $aio_wp_security ;
$files_to_keep = absint ( $aio_wp_security -> configs -> get_value ( 'aiowps_backup_files_stored' ));
if ( $files_to_keep > 0 )
{
$aio_wp_security -> debug_logger -> log_debug ( sprintf ( 'DB Backup - Deleting all but %d latest backup file(s) in %s directory.' , $files_to_keep , $backups_dir ));
$files = AIOWPSecurity_Utility_File :: scan_dir_sort_date ( $backups_dir );
$count = 0 ;
foreach ( $files as $file )
{
if ( strpos ( $file , 'database-backup' ) !== false )
{
if ( $count >= $files_to_keep )
{
@ unlink ( $backups_dir . '/' . $file );
}
$count ++ ;
}
}
}
else
{
$aio_wp_security -> debug_logger -> log_debug ( 'DB Backup - Backup configuration prevents removal of old backup files!' , 3 );
}
}
function aiowps_scheduled_backup_handler ()
{
global $aio_wp_security ;
if ( $aio_wp_security -> configs -> get_value ( 'aiowps_enable_automated_backups' ) == '1' )
{
$aio_wp_security -> debug_logger -> log_debug_cron ( " DB Backup - Scheduled backup is enabled. Checking if a backup needs to be done now... " );
2018-01-26 15:50:15 +01:00
$time_now = current_time ( 'mysql' );
2016-11-28 21:52:15 -08:00
$current_time = strtotime ( $time_now );
$backup_frequency = $aio_wp_security -> configs -> get_value ( 'aiowps_db_backup_frequency' ); //Number of hours or days or months interval per backup
$interval_setting = $aio_wp_security -> configs -> get_value ( 'aiowps_db_backup_interval' ); //Hours/Days/Months
switch ( $interval_setting )
{
case '0' :
$interval = 'hours' ;
break ;
case '1' :
$interval = 'days' ;
break ;
case '2' :
$interval = 'weeks' ;
break ;
default :
// Fall back to default value, if config is corrupted for some reason.
$interval = 'weeks' ;
break ;
}
$last_backup_time = $aio_wp_security -> configs -> get_value ( 'aiowps_last_backup_time' );
if ( $last_backup_time != NULL )
{
$last_backup_time = strtotime ( $aio_wp_security -> configs -> get_value ( 'aiowps_last_backup_time' ));
$next_backup_time = strtotime ( " + " . abs ( $backup_frequency ) . $interval , $last_backup_time );
if ( $next_backup_time <= $current_time )
{
//It's time to do a backup
$result = $this -> execute_backup ();
if ( $result )
{
$aio_wp_security -> configs -> set_value ( 'aiowps_last_backup_time' , $time_now );
$aio_wp_security -> configs -> save_config ();
$aio_wp_security -> debug_logger -> log_debug_cron ( " DB Backup - Scheduled backup was successfully completed. " );
}
else
{
$aio_wp_security -> debug_logger -> log_debug_cron ( " DB Backup - Scheduled backup operation failed! " , 4 );
}
}
}
else
{
//Set the last backup time to now so it can trigger for the next scheduled period
$aio_wp_security -> configs -> set_value ( 'aiowps_last_backup_time' , $time_now );
$aio_wp_security -> configs -> save_config ();
}
}
}
function aiowps_scheduled_db_cleanup_handler ()
{
//Check the events table because this can grow quite large especially when 404 events are being logged
$events_table_name = AIOWPSEC_TBL_EVENTS ;
$max_rows_event_table = '5000' ; //Keep a max of 5000 rows in the events table
$max_rows_event_table = apply_filters ( 'aiowps_max_rows_event_table' , $max_rows_event_table );
AIOWPSecurity_Utility :: cleanup_table ( $events_table_name , $max_rows_event_table );
//Check the failed logins table
$failed_logins_table_name = AIOWPSEC_TBL_FAILED_LOGINS ;
$max_rows_failed_logins_table = '5000' ; //Keep a max of 5000 rows in the events table
$max_rows_failed_logins_table = apply_filters ( 'aiowps_max_rows_failed_logins_table' , $max_rows_failed_logins_table );
AIOWPSecurity_Utility :: cleanup_table ( $failed_logins_table_name , $max_rows_failed_logins_table );
//Check the login activity table
$login_activity_table_name = AIOWPSEC_TBL_USER_LOGIN_ACTIVITY ;
$max_rows_login_activity_table = '5000' ; //Keep a max of 5000 rows in the events table
$max_rows_login_activity_table = apply_filters ( 'aiowps_max_rows_login_attempts_table' , $max_rows_login_activity_table );
AIOWPSecurity_Utility :: cleanup_table ( $login_activity_table_name , $max_rows_login_activity_table );
//Check the global meta table
$global_meta_table_name = AIOWPSEC_TBL_GLOBAL_META_DATA ;
$max_rows_global_meta_table = '5000' ; //Keep a max of 5000 rows in this table
$max_rows_global_meta_table = apply_filters ( 'aiowps_max_rows_global_meta_table' , $max_rows_global_meta_table );
AIOWPSecurity_Utility :: cleanup_table ( $global_meta_table_name , $max_rows_global_meta_table );
//Delete any expired _aiowps_captcha_string_info_xxxx transients
AIOWPSecurity_Utility :: delete_expired_captcha_transients ();
//Keep adding other DB cleanup tasks as they arise...
}
}