Last active
September 5, 2024 01:54
-
-
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] ..
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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 [email protected] | |
* | |
* 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