bookmark_borderHow to clone a WordPress site

So how do you clone a WordPress site?

At work, we are in the beta test phase of our new website, and for the first time ever, we are using WordPress as our platform.

I had been trying to figure out the right way to set up a staging server, so that we can test changes there before going live. Sounds reasonable enough. But it wasn’t turning out to be a simple task.

The procedure I came up on my first try looked like this:

1. Run UpDraftPlus Backup
2. Download backup files to my computer
3. Copy zip files
4. Unzip database file
5. Open database file in Sublime editor
6. Find/Replace all occurrences of website address to correct address
7. Find/Replace all occurrences of c:\\path\\WordPress\\_site_folder_
8. Find/Replace all occurrences of c:\\\\path\\\\WordPress\\\\_site_folder_\\\\
9. Copy db script and run it on staging db server
10. Test

Simple enough, right?

But there’s a problem! Apparently, this method will not work reliably, due to the presence of serialized data!

This problem first reared it’s ugly head with our Custom Post Types. We were using a plugin we found that makes it easy to create and maintain Custom Post Types. However, they use the options table to store their data, so when I cloned the site the first time using this method, the new version of the site didn’t see any of our Custom Post Types. I did some digging, and figured out that the cause was the function call trying to deserialize the data from the options table. At that point I thought that the issue was that the plugin used serialization. But…

Today I learned that the problem is with steps 6,7, and 8 in my first procedure. You cannot just do a blind find/replace in a WordPress DB. This is due to the presence of serialized data.

To understand why, let’s look at how serialization works.

When serializing a string in PHP, you get something that looks like this: s:size:value; Or, with actual data, it looks like this: s:5:"hello";  The s stands for string, the number is the number of characters in the string, and then finally, we have the string itself.

So the problem is that if we have s:17:”http://google.com”; and do a find/replace to make it look like this: s:17:”http://bing.com”; we will no longer be able to deserialize that string. Why? Because the new string is no longer 17 characters long!

What to do?

Enter the DATABASE SEARCH AND REPLACE SCRIPT IN PHP. This little gem will do the search and replace for you, but it will also unserialize the data first, then reserialize it afterward. That way, the string lengths will be updated too.

After running that, the correct data was in my staging DB, and all looked well. However, when I went to the front page, I got the dreaded “White screen of death”. Just a blank white screen staring at me. I could still get to the backend, and everything there looked just fine.

I tried going to the permalinks page, to see if that might do anything, but it didn’t.  Next I went to the plugins page where I was immediately greeted with three red boxes. Apparently, three plugins had been disabled because their files were missing. I’m not sure how that happened, but the fix was easy enough. I copied the missing items in, and reactivated the plugins.

Still, a white screen staring me down.  Nothing I seemed to do would make it work. The back end was now behaving as if all was well, but something was still not right.

Sigh

Ok, the only thing I could think to do next was to delete the whole thing and start over.  You see, I had already cloned the site once before, and manually modified the db, so at this point I was thinking I’d better start from scratch.

So, here’s what I did:

  1. Delete all files on staging server
  2. Delete all DB tables on staging server db
  3. Copy fresh WordPress files to staging server
  4. Copy wp-config.php from live site to staging server
  5. Copy web.conf file from live to staging server
  6. Copy additional folders and files, that we added manually, from root of live
  7. Backup live site
  8. Copy zip files over to a folder on my desktop
  9. Unzip all zip files
  10. Copy plugins, themes, uploads folders over to staging server wp-content folder, selecting to replace all existing content when prompted
  11. Copy “others” folder over to staging server wp-content folder
  12. Run DB Script on staging server db
  13. Copy searchreplacedb2.php (the script liked to above) to the root of the staging server folder (!! Not the root of the entire server!!)
  14. Run Find/Replace Script, replacing whatever you need to
  15. Test

I left out a couple details, such as what to find and replace. Basically, you want to convert from the live domain, to the staging one. But it’s not just http: paths we are concerned with here. There’s also local file paths to change. By searching for our domain name in the db, I found three different forms which needed to be changed. I’ll show an example of each:

  1. http://livesite.com —> http://stagingsite.com
  2. c:/webpath/Wordpress/livesite.com —> c:/webpath/Wordpress/stagingsite.com
  3. c://webpath//Wordpress//livesite.com —> c://webpath//Wordpress//stagingsite.com

There was only one occurrance of the one shown in #3, but I resisted the temptation to change it manually in the db. I let the script do it in case it was serialized.

Also, when I entered the find and replace terms for #2 and #3 in the script, I needed to double the forward slashes – to escape them. So #2 looked like c://webpath//Wordpress…  and #3 looked like c:////webpath////Wordpress… I know – crazy, but it didn’t work otherwise.

Once I had done all this, the staging server was a clone of the live site, at the moment of the backup at least. You want to make the backup as late as possible.

This procedure worked perfectly. If I learn anything new, or find anything I missed, I’ll post it here.

Update

We are now using Akeeba Backup for WordPress. This free tool does just what we want, and abstracts away most of the details.

 

bookmark_borderCustom Admin Logo for WordPress 3.3.1 and newer

I found an article today with a bunch of tips for customizing WordPress, and tried to implement one to change the logo at the top left corner of the admin screen. There is normally a WordPress logo there. However, the code didn’t work. After poking around a bit, it became apparent that I must be using an out of date technique. After some further searching I found http://paulbredenberg.com/2012/01/changing-and-overriding-the-wordpress-admin-logo-since-3-3-1/

Here is what it looks like:

function my_custom_logo() {
echo '<style type="text/css">
#wp-admin-bar-wp-logo > .ab-item .ab-icon { 
	background-image: url(path to image) !important; 
	background-position: 0 0;
	}
#wpadminbar #wp-admin-bar-wp-logo.hover > .ab-item .ab-icon {
	background-position: 0 0;
	}	
</style>';
}

//hook into the administrative header output
add_action('admin_head', 'my_custom_logo');

Thanks to Paul for providing updated code for the original solution found on wpbeginner.com.

bookmark_borderWordPress Plugin Hooks: Plugin Finished Loading

Now that I have a plugin that can be activated and deactivated, it might be nice to know when the plugin has completed loading.

In the code below, I have added a function for this (line 43) , and an action hook (line 48). I’ve also added some comments to help explain things better. Also notice the echo statement (line 44). It’s only there for development of course, but it proves that the function is being called.

<?php
/*
Plugin Name: My Plugin
Plugin URI: http://blog.kennyray.com
Description: This is a plugin
Version: 1.0
Author: Kenny Ray
Author URI: http://blog.kennyray.com
License: GPL2
*/

/*  Copyright 2013  Kenny Ray  (email : kenny@blog.kennyray.com)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License, version 2, as 
    published by the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

// Runs upon plugin activation
function activate_my_plugin(){

}

// Runs upon plugin deactivation
function deactivate_my_plugin(){

}

// Registers the plugin activation and deactivation hooks
register_activation_hook( __FILE__, 'activate_my_plugin' );
register_deactivation_hook(__FILE__, 'deactivate_my_plugin' );

// Runs after all activated plugins have completed loading
function my_plugin_finished_loading() {
    echo "My Plugin Finished Loading";
}

// Causes the function above to run after all activated plugins finish loading
add_action( 'plugins_loaded', 'my_plugin_finished_loading' );

?>

It is interesting to note that this action fires after *all* activated functions are finished loading. This could come in handy if you need to reference a different plugin from this one.

API reference: http://codex.wordpress.org/Plugin_API/Action_Reference/plugins_loaded

bookmark_borderWordPress plugin Hooks: Activation and Deactivation

Ok, so now I’ve got the beginnings of a plugin.

Time to learn about hooks!

However, first, notice that I’ve added text related to the actual license. Turns out that’s a proper practice, so I’ve added it as follows:

<?php
/*
Plugin Name: My Plugin
Plugin URI: http://blog.kennyray.com
Description: This is a plugin
Version: 1.0
Author: Kenny Ray
Author URI: http://blog.kennyray.com
License: GPL2
*/

/*  Copyright 2013  Kenny Ray  (email : kenny@blog.kennyray.com)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License, version 2, as 
    published by the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
?>

Ok, so now what I want to do is make the plugin do something.

The first thing that the user is going to do is activate the plugin. So I need a “hook” for that:

<?php
/*
Plugin Name: My Plugin
Plugin URI: http://blog.kennyray.com
Description: This is a plugin
Version: 1.0
Author: Kenny Ray
Author URI: http://blog.kennyray.com
License: GPL2
*/

/*  Copyright 2013  Kenny Ray  (email : kenny@blog.kennyray.com)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License, version 2, as 
    published by the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

function activate_my_plugin(){

}

register_activation_hook( __FILE__, 'activate_my_plugin' );

?>

What I’ve done here is add a function at line 28 that will be called when the user clicks the activate button.

The code on line 32 is the hook. It tells WordPress to call the function at line 28 when the activate button is pressed.

Of course, if the user wants to remove the plugin, I’ll need to provide a way to undo any setup that might be done in the activation handler. That is accomplished with another function and hook:

 

<?php
/*
Plugin Name: My Plugin
Plugin URI: http://blog.kennyray.com
Description: This is a plugin
Version: 1.0
Author: Kenny Ray
Author URI: http://blog.kennyray.com
License: GPL2
*/

/*  Copyright 2013  Kenny Ray  (email : kenny@blog.kennyray.com)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License, version 2, as 
    published by the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

function activate_my_plugin(){

}

function deactivate_my_plugin(){

}

register_activation_hook( __FILE__, 'activate_my_plugin' );
register_deactivation_hook(__FILE__, 'deactivate_my_plugin' );

?>

On line 32, there is a function which will run upon deactivation. The hook is on line 37.

 

Conclusion

At this point, I have a basic starting point for a plugin. The plugin should activate and deactivate successfully, and if there was code in either function, it would be executed at the appropriate time.

 

API Reference: http://codex.wordpress.org/Function_Reference/register_activation_hookhttp://codex.wordpress.org/Function_Reference/register_deactivation_hook

bookmark_borderWordPress plugins: Where to begin

Here is one place you can start if you are looking for basic info on creating a plugin:

http://codex.wordpress.org/Writing_a_Plugin

 

Create a file in the plugins folder. Call it whatever you’d like.

 

Place a comment block at the top like this, filling in the info specific to your plugin:

<?php
/*
Plugin Name: My Plugin
Plugin URI: http://blog.kennyray.com
Description: This is a plugin
Version: 1.0
Author: Kenny Ray
Author URI: http://blog.kennyray.com
License: GPL2
*/
?>

At this point, you will see your Plugin in the plugins section of the site admin.