tigerstyle-heat/includes/backup/class-backup-scheduler.php
Ryan Malloy 0028738e33 Initial commit: TigerStyle Heat v2.0.0
Make your WordPress site irresistible. Natural SEO attraction with:
- robots.txt management
- sitemap.xml generation
- LLMs.txt support
- Google integration (Analytics, Search Console, Tag Manager)
- Schema.org structured data
- Open Graph / Twitter Card meta tags
- AMP support
- Visual elements gallery
- Built-in backup/restore module

Includes build.sh and .distignore for WordPress-installable release ZIPs.
2026-05-27 13:41:35 -06:00

548 lines
18 KiB
PHP

<?php
/**
* TigerStyle SEO Backup Scheduler
*
* Handles scheduled backups, retention policies,
* and automated backup management.
*
* @package TigerStyleSEO
* @subpackage BackupScheduler
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
class TigerStyleSEO_Backup_Scheduler {
/**
* Logger
*/
private $logger;
/**
* Settings
*/
private $settings;
/**
* Constructor
*/
public function __construct() {
$this->logger = TigerStyleSEO_Backup_Logger::instance();
$this->settings = get_option('tigerstyle_backup_settings', array());
$this->setup_hooks();
}
/**
* Setup WordPress hooks
*/
private function setup_hooks() {
// Scheduled backup hook
add_action('tigerstyle_backup_scheduled', array($this, 'run_scheduled_backup'));
// Cleanup hook
add_action('tigerstyle_backup_cleanup', array($this, 'cleanup_old_backups'));
// Admin hooks
add_action('admin_init', array($this, 'maybe_setup_schedules'));
// Plugin deactivation cleanup
register_deactivation_hook(TIGERSTYLE_HEAT_PLUGIN_FILE, array($this, 'clear_schedules'));
}
/**
* Setup schedules if needed
*/
public function maybe_setup_schedules() {
// Setup cleanup schedule if not exists
if (!wp_next_scheduled('tigerstyle_backup_cleanup')) {
wp_schedule_event(time() + 3600, 'daily', 'tigerstyle_backup_cleanup');
}
// Setup backup schedule based on settings
$this->update_backup_schedule();
}
/**
* Update backup schedule based on settings
*/
public function update_backup_schedule() {
// Clear existing schedule
wp_clear_scheduled_hook('tigerstyle_backup_scheduled');
// Setup new schedule if enabled
if (!empty($this->settings['schedule_enabled'])) {
$frequency = $this->settings['schedule_frequency'] ?? 'daily';
$start_time = $this->calculate_next_backup_time($frequency);
wp_schedule_event($start_time, $frequency, 'tigerstyle_backup_scheduled');
$this->logger->info('Backup schedule updated', array(
'frequency' => $frequency,
'next_run' => date('Y-m-d H:i:s', $start_time)
));
}
}
/**
* Calculate next backup time
*/
private function calculate_next_backup_time($frequency) {
$current_time = time();
$preferred_hour = $this->settings['schedule_time'] ?? '02:00';
// Parse preferred time
list($hour, $minute) = explode(':', $preferred_hour);
$hour = intval($hour);
$minute = intval($minute);
// Calculate next run time
switch ($frequency) {
case 'hourly':
return $current_time + HOUR_IN_SECONDS;
case 'daily':
$next_time = mktime($hour, $minute, 0);
if ($next_time <= $current_time) {
$next_time += DAY_IN_SECONDS;
}
return $next_time;
case 'weekly':
$preferred_day = $this->settings['schedule_day'] ?? 'sunday';
$day_number = $this->get_day_number($preferred_day);
$next_time = strtotime("next {$preferred_day} {$hour}:{$minute}:00");
return $next_time;
case 'monthly':
$preferred_date = $this->settings['schedule_date'] ?? 1;
$next_time = mktime($hour, $minute, 0, date('n'), $preferred_date);
if ($next_time <= $current_time) {
$next_time = mktime($hour, $minute, 0, date('n') + 1, $preferred_date);
}
return $next_time;
default:
return $current_time + DAY_IN_SECONDS;
}
}
/**
* Run scheduled backup
*/
public function run_scheduled_backup() {
if (!$this->should_run_backup()) {
$this->logger->info('Scheduled backup skipped', array(
'reason' => 'Conditions not met'
));
return;
}
try {
$this->logger->info('Starting scheduled backup');
$backup_engine = new TigerStyleSEO_Backup_Engine();
$backup_options = array(
'type' => 'scheduled',
'compression' => $this->settings['compression'] ?? 'zip',
'storage_location' => $this->settings['storage_location'] ?? 'local',
'include_files' => $this->settings['include_files'] ?? true,
'include_database' => $this->settings['include_database'] ?? true,
'description' => $this->generate_scheduled_backup_description()
);
$backup_id = $backup_engine->create_backup($backup_options);
$this->logger->info('Scheduled backup completed successfully', array(
'backup_id' => $backup_id
));
// Send notification if enabled
if (!empty($this->settings['email_notifications'])) {
$this->send_backup_notification($backup_id, true);
}
// Update last backup time
update_option('tigerstyle_last_scheduled_backup', time());
} catch (Exception $e) {
$this->logger->error('Scheduled backup failed: ' . $e->getMessage());
// Send failure notification
if (!empty($this->settings['email_notifications'])) {
$this->send_backup_notification(null, false, $e->getMessage());
}
// Record failure
$this->record_backup_failure($e->getMessage());
}
}
/**
* Check if backup should run
*/
private function should_run_backup() {
// Check if backups are enabled
if (empty($this->settings['schedule_enabled'])) {
return false;
}
// Check system load (don't backup during high load)
if ($this->is_system_under_high_load()) {
$this->logger->warning('Backup skipped due to high system load');
return false;
}
// Check disk space
if (!$this->has_sufficient_disk_space()) {
$this->logger->warning('Backup skipped due to insufficient disk space');
return false;
}
// Check if another backup is running
if ($this->is_backup_running()) {
$this->logger->warning('Backup skipped because another backup is running');
return false;
}
return true;
}
/**
* Check if system is under high load
*/
private function is_system_under_high_load() {
if (!function_exists('sys_getloadavg')) {
return false;
}
$load = sys_getloadavg();
$max_load = $this->settings['max_load_average'] ?? 2.0;
return $load && $load[0] > $max_load;
}
/**
* Check if there's sufficient disk space
*/
private function has_sufficient_disk_space() {
$required_space_mb = $this->settings['min_disk_space'] ?? 1024; // 1GB default
$required_space = $required_space_mb * 1024 * 1024;
$available_space = disk_free_space(ABSPATH);
return $available_space === false || $available_space > $required_space;
}
/**
* Check if backup is currently running
*/
private function is_backup_running() {
global $wpdb;
// Check for active backup processes
$active_backups = get_transient('tigerstyle_active_backup_processes');
if ($active_backups && count($active_backups) > 0) {
// Clean up stale processes (older than 2 hours)
$current_time = time();
foreach ($active_backups as $key => $timestamp) {
if ($current_time - $timestamp > 7200) {
unset($active_backups[$key]);
}
}
set_transient('tigerstyle_active_backup_processes', $active_backups, 3600);
return count($active_backups) > 0;
}
return false;
}
/**
* Generate scheduled backup description
*/
private function generate_scheduled_backup_description() {
$frequency = $this->settings['schedule_frequency'] ?? 'daily';
$timestamp = current_time('mysql');
return sprintf(
__('Scheduled %s backup - %s', 'tigerstyle-heat'),
$frequency,
$timestamp
);
}
/**
* Send backup notification email
*/
private function send_backup_notification($backup_id, $success, $error_message = '') {
if (empty($this->settings['notification_email'])) {
return;
}
$site_name = get_bloginfo('name');
$site_url = home_url();
if ($success) {
$subject = sprintf(__('[%s] Scheduled Backup Completed Successfully', 'tigerstyle-heat'), $site_name);
$message = sprintf(
__("Your scheduled backup has completed successfully.\n\nBackup ID: %s\nSite: %s\nCompleted: %s\n\nYou can manage your backups from the WordPress admin panel.", 'tigerstyle-heat'),
$backup_id,
$site_url,
current_time('Y-m-d H:i:s')
);
} else {
$subject = sprintf(__('[%s] Scheduled Backup Failed', 'tigerstyle-heat'), $site_name);
$message = sprintf(
__("Your scheduled backup has failed.\n\nSite: %s\nFailed: %s\nError: %s\n\nPlease check your backup settings and try again.", 'tigerstyle-heat'),
$site_url,
current_time('Y-m-d H:i:s'),
$error_message
);
}
wp_mail($this->settings['notification_email'], $subject, $message);
}
/**
* Record backup failure
*/
private function record_backup_failure($error_message) {
$failures = get_option('tigerstyle_scheduled_backup_failures', array());
$failures[] = array(
'timestamp' => time(),
'error' => $error_message
);
// Keep only last 10 failures
$failures = array_slice($failures, -10);
update_option('tigerstyle_scheduled_backup_failures', $failures);
}
/**
* Cleanup old backups
*/
public function cleanup_old_backups() {
$retention_days = $this->settings['retention_days'] ?? 30;
if ($retention_days <= 0) {
return; // Unlimited retention
}
try {
$storage_manager = new TigerStyleSEO_Storage_Manager();
$deleted_count = $storage_manager->cleanup_old_backups($retention_days);
$this->logger->info('Old backups cleanup completed', array(
'retention_days' => $retention_days,
'deleted_count' => $deleted_count
));
} catch (Exception $e) {
$this->logger->error('Backup cleanup failed: ' . $e->getMessage());
}
}
/**
* Get next scheduled backup time
*/
public function get_next_backup_time() {
$timestamp = wp_next_scheduled('tigerstyle_backup_scheduled');
if (!$timestamp) {
return null;
}
return array(
'timestamp' => $timestamp,
'formatted' => date('Y-m-d H:i:s', $timestamp),
'human' => human_time_diff($timestamp, current_time('timestamp'))
);
}
/**
* Get backup schedule status
*/
public function get_schedule_status() {
$status = array(
'enabled' => !empty($this->settings['schedule_enabled']),
'frequency' => $this->settings['schedule_frequency'] ?? 'daily',
'next_run' => $this->get_next_backup_time(),
'last_run' => null,
'last_success' => null,
'failure_count' => 0
);
// Get last backup time
$last_backup_time = get_option('tigerstyle_last_scheduled_backup');
if ($last_backup_time) {
$status['last_run'] = array(
'timestamp' => $last_backup_time,
'formatted' => date('Y-m-d H:i:s', $last_backup_time),
'human' => human_time_diff($last_backup_time, current_time('timestamp')) . __(' ago', 'tigerstyle-heat')
);
}
// Get failure information
$failures = get_option('tigerstyle_scheduled_backup_failures', array());
$status['failure_count'] = count($failures);
if (!empty($failures)) {
$last_failure = end($failures);
$status['last_failure'] = array(
'timestamp' => $last_failure['timestamp'],
'formatted' => date('Y-m-d H:i:s', $last_failure['timestamp']),
'error' => $last_failure['error']
);
}
return $status;
}
/**
* Enable scheduled backups
*/
public function enable_schedule($frequency = 'daily', $time = '02:00') {
$this->settings['schedule_enabled'] = true;
$this->settings['schedule_frequency'] = $frequency;
$this->settings['schedule_time'] = $time;
update_option('tigerstyle_backup_settings', $this->settings);
$this->update_backup_schedule();
$this->logger->info('Backup schedule enabled', array(
'frequency' => $frequency,
'time' => $time
));
}
/**
* Disable scheduled backups
*/
public function disable_schedule() {
$this->settings['schedule_enabled'] = false;
update_option('tigerstyle_backup_settings', $this->settings);
wp_clear_scheduled_hook('tigerstyle_backup_scheduled');
$this->logger->info('Backup schedule disabled');
}
/**
* Run backup immediately
*/
public function run_backup_now() {
// Mark as manual backup
$backup_engine = new TigerStyleSEO_Backup_Engine();
$backup_options = array(
'type' => 'manual',
'compression' => $this->settings['compression'] ?? 'zip',
'storage_location' => $this->settings['storage_location'] ?? 'local',
'include_files' => $this->settings['include_files'] ?? true,
'include_database' => $this->settings['include_database'] ?? true,
'description' => __('Manual backup - ', 'tigerstyle-heat') . current_time('mysql')
);
return $backup_engine->create_backup($backup_options);
}
/**
* Get day number for date calculations
*/
private function get_day_number($day_name) {
$days = array(
'sunday' => 0,
'monday' => 1,
'tuesday' => 2,
'wednesday' => 3,
'thursday' => 4,
'friday' => 5,
'saturday' => 6
);
return $days[strtolower($day_name)] ?? 0;
}
/**
* Clear all schedules (for plugin deactivation)
*/
public function clear_schedules() {
wp_clear_scheduled_hook('tigerstyle_backup_scheduled');
wp_clear_scheduled_hook('tigerstyle_backup_cleanup');
$this->logger->info('All backup schedules cleared');
}
/**
* Get backup statistics
*/
public function get_backup_statistics($days = 30) {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_backup_metadata';
$since_date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
$stats = $wpdb->get_row(
$wpdb->prepare(
"SELECT
COUNT(*) as total_backups,
SUM(CASE WHEN backup_id LIKE 'backup_%scheduled%' THEN 1 ELSE 0 END) as scheduled_backups,
SUM(CASE WHEN backup_id LIKE 'backup_%manual%' THEN 1 ELSE 0 END) as manual_backups,
AVG(file_size) as average_size,
SUM(file_size) as total_size
FROM {$table_name}
WHERE created_at >= %s",
$since_date
),
ARRAY_A
);
if ($stats) {
$stats['average_size_formatted'] = size_format($stats['average_size']);
$stats['total_size_formatted'] = size_format($stats['total_size']);
$stats['success_rate'] = $this->calculate_success_rate($days);
}
return $stats ?: array();
}
/**
* Calculate backup success rate
*/
private function calculate_success_rate($days) {
$failures = get_option('tigerstyle_scheduled_backup_failures', array());
$recent_failures = array_filter($failures, function($failure) use ($days) {
return $failure['timestamp'] > strtotime("-{$days} days");
});
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_backup_metadata';
$since_date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
$total_attempts = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$table_name} WHERE created_at >= %s AND backup_id LIKE '%scheduled%'",
$since_date
)
);
$total_attempts += count($recent_failures);
if ($total_attempts === 0) {
return 100;
}
$success_rate = (($total_attempts - count($recent_failures)) / $total_attempts) * 100;
return round($success_rate, 2);
}
}