Skip to content

Instantly share code, notes, and snippets.

@SGudbrandsson
Last active June 10, 2023 01:11
Show Gist options
  • Star 25 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save SGudbrandsson/3bd9d9ef38a7d1cb48c1 to your computer and use it in GitHub Desktop.
Save SGudbrandsson/3bd9d9ef38a7d1cb48c1 to your computer and use it in GitHub Desktop.
Create a WordPress staging area from your live wordpress setup with a Click-of-a-button [TM] ..
<?php
/**
*
* This script will copy your wordpress from public_html (or wherever)
* and place it in a staging folder.
* It will then clone the database, reconfigure the config file
* and replace URL's from the original URL to your staging URL.
* It will then make sure to NOT allow search engines to index the page.
*
* Use this script to clone your main wp in order to test maintenance work
* like updating the site, removing plugins, adding new plugins
* and so on..
*
* If the staging area already exists, this script will remove the staging
* area and install a fresh copy.
*
* There really should be some error handling and testing .. I may add that in later
* but for now, this works for me so I'm not adding any safeguards unless YOU let me
* know of something that broke.
*
* Feel free to hack this code, fix it or jazz it.
* If you find a bug - let me know .. maybe I'll turn this into a repo and accept PR's if I'm up for it.
*
* If you need to contact me, you can reach me at github/twitter @sigginet or siggy@quickfalcon.com
*
* If this helped you and you feel the need to repay me - buy me a book! I love to read! :)
* http://www.amazon.com/gp/registry/wishlist/1HSVVQQ3YGNGY/
*
*
*
****************
* INSTRUCTIONS *
****************
*
* ASSUMPTIONS/REQUIREMENTS: (skip this if you want to fail)
* 1. You've created a DNS record (or modified your hosts file) for the staging URL.
* 2. You've created an empty folder for your staging area.
* 3. You've mapped your staging URL to the staging area on your server.
* 4. You've created an empty staging database that the live database user has full control over.
* 5. You're running PHP 5.3+. If not - BE ASHAMED OF YOURSELF AND UPDATE! Use HHVM if you want better performance.
* a. If you're using 5.3 then you REALLY need to update - it went out of date 14 Aug 2014 but
* major hosting companies STILL use this php version on their hosts.
* 6. You've either got PDO mysql or mysqli. The script will die otherwise.
*
* INSTRUCTIONS:
* 1. Read the assumptions part above and make sure you've met the minimum requirements
* 2. Create a staging area folder (see my example structure below)
* 3. Make sure that the HTTP server can read and write to that folder
* 4. Create a staging database
* 5. Make sure the LIVE database user has full access to the staging database
* 6. Create a staging URL (it can be staging.example.com)
* 7. Make sure the staging URL points to the staging folder
* 8. Create a "staging" subfolder under your LIVE website
* 9. Configure the variables below
* 10. Save this file as index.php in the "staging" subfolder.
* 11. Visit http://yourlivesite.com/staging/ and press the button .. This might take a minute.
* 12. Enjoy your new staging area.
* 13. Feel free to visit http://yourlivesite.com/staging/ every time you need to refresh the staging area.
*
*
*
* MY EXAMPLE FOLDER STRUCTURE:
* - home
* '- public_html (WP is here)
* '- staging (Put the staging script here, call it index.php)
* '- staging_area (Staging area for WP)
*
* MY EXAMPLE DATABASE STRUCTURE:
* - live_db (the live WP database)
* - staging_db (the staging WP database - will be truncated and overwritten)
*
*
**/
//
// Set the variables
//
// $protocol - the transfer protocol used for both staging and the live site
// $livesite - the live website URL
// $stagingsite - the staging site URL
// $livedb - the database name of the live site - grabbed from wp-config.php's DB_NAME definition
// $stagingdb - the database name of the staging site - the one you created. We assume the same DB user.
// $homedir - currently points to the parent folder (expects to be 1st level subfolder inside the WP directory)
// $subfolder - the subfolder of the live site where the staging php application sits (the folder containing this file)
// $stagingdir - the folder that should contain staging area - usually a subfolder or sidefolder of public_html
$protocol = 'http';
$livesite = 'www.example.com';
$livesiteproto = 'http://';
$stagingsite = 'staging.example.com';
$stagingsiteproto = 'http://';
$livedb = DB_NAME;
$stagingdb = 'example_staging';
$homedir = dirname(__DIR__);
$subfolder = 'staging';
$stagingdir = dirname($homedir) . '/staging_area';
//
// DO NOT EDIT THE CODE BELOW (unless you know what you're doing)
//
$mysqli_available = class_exists( 'mysqli' );
$pdo_available = class_exists( 'PDO' );
$connection_type = '';
if ( $mysqli_available ) {
$connection_type = 'mysqli';
}
if ( $pdo_available ) {
$mysql_driver_present = in_array( 'mysql', pdo_drivers() );
if ( $mysql_driver_present ) {
$connection_type = 'pdo';
}
}
// Abort if mysqli and PDO are both broken.
if ( '' === $connection_type ) {
die("Could not find any MySQL database drivers. (MySQLi or PDO required.)");
}
include('../wp-load.php');
//
// Re-direct not-logged-in users to holding page
//
if(!is_user_logged_in()) {
wp_redirect( $protocol.'://'.$livesite.'/wp-login.php?redirect_to='.$protocol.'://'.$livesite.'/'.$subfolder.'/', 302 );
exit;
}
//
// Check if the user is an administrator
//
if ( ! current_user_can( 'manage_options' )) {
die("Sorry buddy, you don't have permission to be here..");
}
//
// Great! Now, let's check if we're asked to create the staging area
//
if ( !empty($_POST['createstagingarea']) ) {
// Create the staging area
echo_immediately("<html><body>");
// Set up the database
if ($connection_type == 'pdo') {
$dbconn = new PDO("mysql:host=".DB_HOST.";dbname=".$stagingdb, DB_USER, DB_PASSWORD);
} elseif ($connection_type == 'mysqli') {
$dbconn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, $stagingdb);
}
global $dbconn;
global $stagingdb;
global $livedb;
// Remove the staging folder contents
echo_immediately("Removing the staging area files<br>");
rrmdir($stagingdir);
// Drop the database
echo_immediately("Removing the old database<br>");
cleandb();
// Copy the new database
echo_immediately("Copying the database to the staging database<br>");
clonedb($livedb, $stagingdb);
// Copy the files
echo_immediately("Copying the files to the staging area<br>");
rcopy($homedir, $stagingdir);
// Modify the config file, change the database name
echo_immediately("Modifying the config file, pointing it to the new database<br>");
$configfile = $stagingdir.DIRECTORY_SEPARATOR.'wp-config.php';
$config_data = file($configfile);
$config_data = array_map('replace_a_line', $config_data);
file_put_contents($configfile, implode('', $config_data));
// Modify the database, replace the old URL with the new URL
echo_immediately("Modifying the database - replacing the live URL with the staging URL.<br>");
// Start by downloading and including Search-Replace-DB
if (file_exists(dirname(__FILE__).DIRECTORY_SEPARATOR.'srdb.class.php') === FALSE) {
file_put_contents(dirname(__FILE__).DIRECTORY_SEPARATOR.'srdb.class.php', file_get_contents('https://raw.githubusercontent.com/interconnectit/Search-Replace-DB/master/srdb.class.php'));
}
include 'srdb.class.php';
$srdb_args = array(
'name' => $stagingdb,
'user' => DB_USER,
'pass' => DB_PASSWORD,
'host' => DB_HOST,
'search' => $livesiteproto . $livesite,
'replace' => $stagingsiteproto . $stagingsite,
'dry_run' => FALSE);
$search_replace_results = new icit_srdb($srdb_args);
// Awesomesauce .. let's link to the new site
echo "<br><br>AWESOME! We're done here. Go <a href='".$protocol."://".$stagingsite."/'>check out the staging area</a> or <a href='".$protocol."://".$stagingsite."/wp-admin/'>visit the admin area</a> and do your modifications.<br></body></html>";
// We're done here. Kill the php process and end everything.
die();
}
//
// Alright, no proper _POST data .. so let's offer the user to create a new staging area.
//
?>
<html>
<body>
<h1>Staging Area Creator</h1>
<h2>If you want to create a new staging area for this website, then press the button below.</h2>
<p>Please note, the existing staging area files and database will be removed and the live website will be copied when you press the button.</p>
<form method="POST">
<input type="hidden" name="createstagingarea" value="goforit">
<input type="checkbox" name="symlinkuploads" value="yes" checked> Do not copy the wp-content/uploads directory, only create a symlink to the original. (unchecked = copy everything) <br>
<input type="submit" value="Create a new staging area">
</form>
</body>
</html><?php
//
// Alright .. now we can insert our FUNCTIONS
// Thankfully, you can call a function before you declare it in PHP, making for such a nicely readable code
//
/**
* function rrmdir($dir)
*
* Deletes a directory's content recursively except for the directory itself
*
**/
function rrmdir($dir)
{
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $fileinfo) {
$todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
$todo($fileinfo->getRealPath());
}
}
/**
* function rcopy($dir)
*
* Copies a directory's content recursively except for the directory itself
*
**/
function rcopy($origdir, $destdir)
{
$symlink_upload = false;
if (!empty($_POST["symlinkuploads"])) {
$symlink_upload = true;
$files = new RecursiveIteratorIterator(
new MyRecursiveFilterIterator(
new RecursiveDirectoryIterator($origdir, RecursiveDirectoryIterator::SKIP_DOTS)),
RecursiveIteratorIterator::SELF_FIRST
);
} else {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($origdir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
}
foreach ($files as $fileinfo) {
if ($fileinfo->isDir()) {
mkdir($destdir . DIRECTORY_SEPARATOR . $files->getSubPathName());
} else {
copy($fileinfo, $destdir . DIRECTORY_SEPARATOR . $files->getSubPathName());
}
}
if ($symlink_upload) {
symlink($origdir.'/wp-content/uploads', $destdir.'/wp-content/uploads');
}
}
/**
* function cleandb()
*
* Drops all the tables in a database
*
**/
function cleandb()
{
global $dbconn, $connection_type;
$dbconn->query('SET foreign_key_checks = 0');
if ($result = $dbconn->query("SHOW TABLES"))
{
if ($connection_type == 'pdo') {
echo_immediately("DB Connection is PDO<br>");
while($row = $result->fetch(PDO::FETCH_ASSOC))
{
$dbconn->query('DROP TABLE IF EXISTS '.array_values($row)[0]);
}
} elseif ($connection_type == 'mysqli') {
echo_immediately("DB Connection is MySQLi<br>");
while($row = $result->fetch_array(MYSQLI_NUM))
{
$dbconn->query('DROP TABLE IF EXISTS '.$row[0]);
}
} else {
echo_immediately("DB Connection is not available<br>");
}
}
$dbconn->query('SET foreign_key_checks = 1');
}
/**
* function clonedb($origdb, $destdb)
*
* Clones all tables from one database to another
*
**/
function clonedb($origdb, $destdb)
{
global $dbconn;
global $table_prefix;
global $connection_type;
if ($result = $dbconn->query("SHOW TABLES FROM ".$origdb))
{
if ($connection_type == 'pdo') {
while($row = $result->fetch(PDO::FETCH_ASSOC))
{
$dbconn->query('CREATE TABLE '.array_values($row)[0].' LIKE '.$origdb.'.'.array_values($row)[0]);
$dbconn->query('INSERT INTO '.array_values($row)[0].' SELECT * FROM '.$origdb.'.'.array_values($row)[0]);
}
} elseif ($connection_type == 'mysqli') {
while($row = $result->fetch_array(MYSQLI_NUM))
{
$dbconn->query('CREATE TABLE '.$row[0].' LIKE '.$origdb.'.'.$row[0]);
$dbconn->query('INSERT INTO '.$row[0].' SELECT * FROM '.$origdb.'.'.$row[0]);
}
}
// Make sure to discourage indexing
$dbconn->query('UPDATE '.$table_prefix.'options SET option_value=\'0\' WHERE option_name=\'blog_public\'');
}
}
/**
* function replace_a_line($data)
*
* Makes sure to only replace the line we want .. the DB_NAME line
* not DB_USER DB_HOST or any other configuration line.
*
**/
function replace_a_line($data) {
global $stagingdb;
global $livedb;
if (stristr($data, 'DB_NAME')) {
return str_replace($livedb, $stagingdb, $data);
}
return $data;
}
/**
* Prints the output immediately
*/
function echo_immediately($string) {
echo $string;
ob_flush();
flush();
return;
}
/**
* Filter for the uploads directory.
* Only if you want to symlink the uploads directory (multiple files, large files, etc.)
*
*/
class MyRecursiveFilterIterator extends RecursiveFilterIterator {
public static $FILTERS = array(
'uploads',
);
public function accept() {
return !in_array(
$this->current()->getFilename(),
self::$FILTERS,
true
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment