WordPress Plugin: Create A Simple Daily Theme Backup | Martech Zone

Over the last few months, I’ve been working hard to improve the performance of my WordPress child theme. Periodically, I’d introduce an issue in my child theme as I optimized it, and I needed to revert to an older copy of the code I’d updated in my theme. However, the daily backups on my host are over a gigabyte, so it takes time to download and search for the code I need to restore.

Losing your WordPress theme due to a mishap or malicious attack can be a nightmare. That’s why having a reliable backup system is crucial. Many plugins offer comprehensive backup solutions, but sometimes, you need a simple, targeted approach. This article delves into a custom WordPress plugin designed to automatically back up your active theme daily, providing an extra layer of protection for your site’s design and functionality.

NOTE: This is not a substitute for a full, offsite backup of your WordPress instance. It’s just an easy means to have a quickly accessible copy of your active theme. This saves up to 10 instances so that you don’t fill up your host with backups.

Daily Theme Backup WordPress Plugin

I created a Daily Theme Backup plugin that takes your active theme, zips it up, and puts it into a backup folder on your site. It also makes that folder inaccessible to external viewers so that you can ensure the files aren’t downloaded by anyone.

To use this, add a folder to your wp-content/plugins folder called daily-theme-backup and copy the following code into a file in that directory, daily-theme-backup.php.

<?php
/**
 * Plugin Name: Daily Theme Backup
 * Description: Creates a daily backup of your active theme and stores it in the wp-content/backup directory.
 * Version: 3.0
 * Author: Douglas Karr
 * Author URI: 
 */

// Create the backup directory if it doesn't exist.
if (!file_exists(WP_CONTENT_DIR . '/backup')) {
  mkdir(WP_CONTENT_DIR . '/backup', 0755);
}

// Function to create the .htaccess file for backup directory protection
function protect_backup_directory() {
  $htaccess_file = WP_CONTENT_DIR . '/backup/.htaccess';
  $htaccess_content="deny from all";

  if (!file_exists($htaccess_file)) {
    if (insert_with_markers($htaccess_file, 'Daily Theme Backup', $htaccess_content)) {
      // Optional: Log success or display a message
      error_log('Backup directory protected with .htaccess.');
    } else {
      // Optional: Log error or display a message
      error_log('Error creating .htaccess file for backup directory protection.');
    }
  }
}

// Run the protect_backup_directory function on plugin activation.
register_activation_hook( __FILE__, 'protect_backup_directory' );

// Schedule the daily backup.
if (!wp_next_scheduled('daily_theme_backup')) {
  wp_schedule_event(time(), 'daily', 'daily_theme_backup');
}

// Hook the backup function to the scheduled event.
add_action('daily_theme_backup', 'create_theme_backup');

// Run the backup function once on plugin activation.
register_activation_hook( __FILE__, 'create_theme_backup' ); 

// Add a "Backup Now" link to the plugin actions.
add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), 'add_theme_backup_action_link' );

function add_theme_backup_action_link( $links ) {
  $backup_link = '<a href="' . esc_url( add_query_arg( 'backup_now', 'true' ) ) . '">Backup Now</a>';
  array_unshift( $links, $backup_link ); // Add the link with a separator
  return $links;
}

// Check for the "backup_now" query parameter and trigger the backup.
if ( isset( $_GET['backup_now'] ) && $_GET['backup_now'] == 'true' ) {
  create_theme_backup();

  // Optionally, display a success message.
  add_action( 'admin_notices', 'display_backup_success_message' );
}

function display_backup_success_message() {
  echo '<div class="notice notice-success is-dismissible"><p>Theme backup created successfully!</p></div>';
}

function create_theme_backup() {
  // Get the active theme directory.
  $theme_dir = get_stylesheet_directory();
  $theme_name = basename($theme_dir);

  // Create the backup filename with timestamp if triggered manually.
  if ( isset( $_GET['backup_now'] ) && $_GET['backup_now'] == 'true' ) {
    $backup_file = WP_CONTENT_DIR . '/backup/' . $theme_name . '-' . date('Y-m-d-H-i-s') . '.zip';
  } else {
    $backup_file = WP_CONTENT_DIR . '/backup/' . $theme_name . '-' . date('Y-m-d') . '.zip';
  }

  // Create the zip archive.
  $zip = new ZipArchive();
  if ($zip->open($backup_file, ZipArchive::CREATE) === TRUE) {
    // Add the theme directory to the zip archive.
    $files = new RecursiveIteratorIterator(
      new RecursiveDirectoryIterator($theme_dir),
      RecursiveIteratorIterator::LEAVES_ONLY
    );

    foreach ($files as $name => $file) {
      // Skip directories (they would be added automatically)
      if (!$file->isDir()) {
        // Get real and relative path for current file
        $filePath = $file->getRealPath();
        $relativePath = substr($filePath, strlen($theme_dir) + 1);

        // Add current file to archive
        $zip->addFile($filePath, $relativePath);
      }
    }

    $zip->close();

    // Delete old backups.
    $backup_files = glob(WP_CONTENT_DIR . '/backup/' . $theme_name . '-*.zip');
    if (count($backup_files) > 10) {
      usort($backup_files, 'filemtime_compare');
      for ($i = 0; $i < count($backup_files) - 10; $i++) {
        unlink($backup_files[$i]);
      }
    }
  } else {
    // Log an error if the zip archive could not be created.
    error_log('Error creating theme backup.');
  }
}

// Helper function to compare file modification times.
function filemtime_compare($a, $b) {
  return filemtime($a) - filemtime($b);
}
?>

The Code: A Breakdown

This plugin utilizes PHP and WordPress’s built-in functions to achieve its goal. Let’s break down the key components:

1. Setting the Stage:

// Create the backup directory if it doesn't exist.
if (!file_exists(WP_CONTENT_DIR . '/backup')) {
  mkdir(WP_CONTENT_DIR . '/backup', 0755);
}

// Function to create the .htaccess file for backup directory protection
function protect_backup_directory() {
  $htaccess_file = WP_CONTENT_DIR . '/backup/.htaccess';
  $htaccess_content="deny from all";

  if (!file_exists($htaccess_file)) {
    if (insert_with_markers($htaccess_file, 'Daily Theme Backup', $htaccess_content)) {
      error_log('Backup directory protected with .htaccess.');
    } else {
      error_log('Error creating .htaccess file for backup directory protection.');
    }
  }
}

// Run the protect_backup_directory function on plugin activation.
register_activation_hook( __FILE__, 'protect_backup_directory' );
  • The code starts by creating a dedicated “backup” directory within wp-content if it doesn’t already exist. This is where our theme backups will be stored.
  • Security is paramount. The protect_backup_directory() function utilizes an .htaccess file to restrict access to this directory. This prevents anyone from directly downloading your backup files via a web browser. This function is hooked to the plugin’s activation, ensuring the backup directory is secured immediately.

2. Scheduling the Backup:

// Schedule the daily backup.
if (!wp_next_scheduled('daily_theme_backup')) {
  wp_schedule_event(time(), 'daily', 'daily_theme_backup');
}

// Hook the backup function to the scheduled event.
add_action('daily_theme_backup', 'create_theme_backup');
  • WordPress offers a handy scheduling mechanism. wp_schedule_event is used to schedule the create_theme_backup function to run daily. This ensures your theme is backed up automatically each day.

3. The Backup Process:

function create_theme_backup() {
  // Get the active theme directory.
  $theme_dir = get_stylesheet_directory();
  $theme_name = basename($theme_dir);

  // Create the backup filename with timestamp if triggered manually.
  if ( isset( $_GET['backup_now'] ) && $_GET['backup_now'] == 'true' ) {
    $backup_file = WP_CONTENT_DIR . '/backup/' . $theme_name . '-' . date('Y-m-d-H-i-s') . '.zip';
  } else {
    $backup_file = WP_CONTENT_DIR . '/backup/' . $theme_name . '-' . date('Y-m-d') . '.zip';
  }

  // Create the zip archive.
  $zip = new ZipArchive();
  if ($zip->open($backup_file, ZipArchive::CREATE) === TRUE) {
    // Add the theme directory to the zip archive.
    $files = new RecursiveIteratorIterator(
      new RecursiveDirectoryIterator($theme_dir),
      RecursiveIteratorIterator::LEAVES_ONLY
    );

    foreach ($files as $name => $file) {
      if (!$file->isDir()) {
        $filePath = $file->getRealPath();
        $relativePath = substr($filePath, strlen($theme_dir) + 1);
        $zip->addFile($filePath, $relativePath);
      }
    }

    $zip->close();

    // Delete old backups.
    $backup_files = glob(WP_CONTENT_DIR . '/backup/' . $theme_name . '-*.zip');
    if (count($backup_files) > 10) {
      usort($backup_files, 'filemtime_compare');
      for ($i = 0; $i < count($backup_files) - 10; $i++) {
        unlink($backup_files[$i]);
      }
    }
  } else {
    error_log('Error creating theme backup.');
  }
}

// Helper function to compare file modification times.
function filemtime_compare($a, $b) {
  return filemtime($a) - filemtime($b);
}
  • The heart of the plugin is the create_theme_backup() function. It identifies the active theme, creates a zip archive, and adds all theme files meticulously.
  • The code also includes a cleanup mechanism to avoid clutter. It keeps only the last ten backups, deleting older ones to conserve space.

4. On-Demand Backups:

// Add a "Backup Now" link to the plugin actions.
add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), 'add_theme_backup_action_link' );

function add_theme_backup_action_link( $links ) {
  $backup_link = '<a href="' . esc_url( add_query_arg( 'backup_now', 'true' ) ) . '">Backup Now</a>';
  array_unshift( $links, $backup_link . ' | ' ); 
  return $links;
}

// Check for the "backup_now" query parameter and trigger the backup.
if ( isset( $_GET['backup_now'] ) && $_GET['backup_now'] == 'true' ) {
  create_theme_backup();
  add_action( 'admin_notices', 'display_backup_success_message' );
}

function display_backup_success_message() {
  echo '<div class="notice notice-success is-dismissible"><p>Theme backup created successfully!</p></div>';
}
  • Flexibility is key. The plugin adds a Backup Now link to its page in the WordPress admin area. This allows you to create a backup on demand in addition to the scheduled daily backups. A timestamp is added to these manual backups to prevent overwriting the daily ones.

Why This Matters

This plugin provides a focused solution for safeguarding your WordPress theme. Automating the backup process and securing the backup files offers peace of mind and a quick recovery option in case of unexpected issues. Understanding this code empowers you to customize it further, adapting it to your specific needs or expanding it to back up other crucial elements of your WordPress site.


Source: martech.zone