WordPress and many of her plugins store data in serialized arrays. Plugin authors do this to save coding time, as it is minimal coding to create you option pages with array-based name inputs. For example:
<input type=”text” name=”myplugin[its_name]” />
<input type=”text” name=”myplugin[its_version]” />
<select name=”myplugin[is_cool]>…</select>
Well, before the Settings API was introduced, there was:
<?php wp_nonce_field(‘update-options’); ?>
<input type=”hidden” name=”action” value=”update” />
<input type=”hidden” name=”page_options” value=”myplugin” />
See, page_options was a comma-separated list of values you would be posting to the server. This made for quick coding of options pages, but also made for easy getting the options via $options = get_option(‘myplugin’,get_myplugin_defaults()); Then you could use $options['its_name'] just like that.
The problem with serializing these options is that many plugins also serialize the URL of the website as one of the options. This can be problemmatic if you wish to move the location of your website. Your serialized option may look something like this: a:2:{i:0;s:13:"yoursite.com";i:1;a:2:{i:0;s:13:"yoursite.com";i:1;R:3;}}
The s:13 above indicates yoursite.com is 13 char[]. Now when moving websites you think that you might just be able to run MySQL UPDATE queries using the MySQL REPLACE() when doing a search and replace… Or you may believe you will use sed or perl -pi -w -e 's/search/replace' or whatever search & replace you can think of (I am sure there are more) to replace a mysqldump file. Well, unless you new domain has the same number of characters that does not work very well. Your serialized string becomes: a:2:{i:0;s:13:"yournewsite.com";i:1;a:2:{i:0;s:13:"yournewsite.com";i:1;R:3;}}
Where’s the light at the end of the tunnel? Below is a script I created (well, modified) to include my previously-posted script in a way that will find & replace all instances (serialized or otherwise) in a MySQL Database. You can hard code credentials directly into the script under the setup/default or you can pass it as an argument via command line. You can also use -v for verbosity that will tell you how long the script took to run, how many items were replaced, etc. You can use –help for more info
#!/usr/local/bin/php -q
<?php
// Start TIMER
$stimer = explode( ' ', microtime() );
$stimer = $stimer[1] + $stimer[0];
include_once 'include/commandline.class.php';
$args = CommandLine::parseArgs($argv);
if ($args['help'] || $args['?']){
echo <<<HELP
F&R Database
by David Coveney
Command Line Interface with arguments by:
Chris Heney
Options
--db-name Name of the database to be used in the search
--db-host Location of the host (default: localhost)
--db-user Username to use in connecting to the database
--db-pass Password to connect to the database
--find *Required - The term to search for to be replaced
--replace-with *Required - Term for replacing the find term
--verbose,-v Be noisy
HELP;
die();
}
if (!$args['find'] || !$args['replace-with'] || !$args['db-name'])
die("You need to at least supply a --find=\"Search Term\", a --replace-with=\"Replace Term\" and a --db-name=\"mydatabase\". Type --help for details.\n");
$defaults['db-host'] = 'localhost';
$defaults['db-user'] = 'cheney';
$defaults['db-pass'] = 'Atmr815T@%';
$defaults['verbose'] = false;
$options = array_merge($defaults,$args);
if ($args['v'])
$options['verbose'] = true;
/**
* START HELPER FUNCTIONS
**/
function recursive_array_replace($find, $replace, &$data) {
if (is_array($data)) {
foreach ($data as $key => $value) {
if (is_array($value)) {
recursive_array_replace($find, $replace, $data[$key]);
} else {
// have to check if it's string to ensure no switching to string for booleans/numbers/nulls - don't need any nasty conversions
if (is_string($value)) $data[$key] = str_replace($find, $replace, $value);
}
}
} else {
if (is_string($data)) $data = str_replace($find, $replace, $data);
}
}
/**
* END HELPER FUNCTIONS
**/
/**
* DO NOT EDIT PAST THIS POINT UNLESS UR L33t
**/
$cid = mysql_connect($options['db-host'],$options['db-user'],$options['db-pass']) or die("Connecting to DB Error: " . mysql_error() . "\n");
mysql_select_db($options['db-name'],$cid) or die("Connecting to DB Error: " . mysql_error() . "\n");
// First, get a list of tables
$SQL = "SHOW TABLES";
$tables_list = mysql_query($SQL,$cid);
if (!$tables_list) {
if ($options['verbose'])
echo("\nERROR: " . mysql_error() . "\n$SQL\n");
die(); exit;
}
// Loop through the tables
while ($table_rows = mysql_fetch_array($tables_list)) {
$count_tables_checked++;
$table = $table_rows['Tables_in_'.$options['db-name']];
$SQL = "DESCRIBE ".$table ; // fetch the table description so we know what to do with it
$fields_list = mysql_query($SQL,$cid);
// Make a simple array of field column names
$index_fields = ""; // reset fields for each table.
$column_name = "";
$table_index = "";
$i = 0;
while ($field_rows = mysql_fetch_array($fields_list)) {
$column_name[$i++] = $field_rows['Field'];
if ($field_rows['Key'] == 'PRI') $table_index[$i] = true ;
}
// now let's get the data and do search and replaces on it...
$SQL = "SELECT * FROM ".$table; // fetch the table contents
$data = mysql_query($SQL,$cid);
if (!$data) {
if ($options['verbose'])
echo("ERROR: " . mysql_error() . "\n$SQL\n");
die(); exit;
}
while ($row = mysql_fetch_array($data)) {
// Initialise the UPDATE string we're going to build, and we don't do an update for each damn column...
$need_to_update = false;
$UPDATE_SQL = 'UPDATE '.$table. ' SET ';
$WHERE_SQL = ' WHERE ';
$j = 0;
while ($row = mysql_fetch_array($data)) {
// Initialise the UPDATE string we're going to build, and we don't do an update for each damn column...
$need_to_update = false;
$UPDATE_SQL = 'UPDATE '.$table. ' SET ';
$WHERE_SQL = ' WHERE ';
$j = 0;
foreach ($column_name as $current_column) {
$j++;
$count_items_checked++;
$data_to_fix = $row[$current_column];
// set the same now - if they're different later we know we need to update
$edited_data = $data_to_fix;
// unserialise - if false returned we don't try to process it as serialised
$unserialized = unserialize($data_to_fix);
if ($unserialized) {
recursive_array_replace($options['find'], $options['replace-with'], $unserialized);
$edited_data = serialize($unserialized);
} else {
if (is_string($data_to_fix)) $edited_data = str_replace($options['find'],$options['replace-with'],$data_to_fix);
}
// If they're not the same, we need to add them to the update string
if ($data_to_fix != $edited_data) {
$count_items_changed++;
// if this isn't our first time here, add a comma
if ($need_to_update != false) $UPDATE_SQL = $UPDATE_SQL.',';
$UPDATE_SQL = $UPDATE_SQL.' '.$current_column.' = "'.mysql_real_escape_string($edited_data).'"' ;
// only set if we need to update - avoids wasted UPDATE statements
$need_to_update = true;
}
if ($table_index[$j]){
$WHERE_SQL = $WHERE_SQL.$current_column.' = "'.$row[$current_column].'" AND ';
}
}
if ($need_to_update) {
$count_updates_run;
// the following strips off the excess AND - the easiest way to code this without extra flags, etc.
$WHERE_SQL = substr($WHERE_SQL,0,-4);
$UPDATE_SQL = $UPDATE_SQL.$WHERE_SQL;
$result = mysql_query($UPDATE_SQL,$cid);
if (!$result && $options['verbose']) {
echo("ERROR: " . mysql_error() . "\n$UPDATE_SQL\n");
}
}
}
}
// Report
$report = $count_tables_checked." tables checked; ".$count_items_checked." items checked; ".$count_items_changed." items changed;";
if ($options['verbose'])
echo "Database Find & Replace Reports: ".$report."\n\n";
mysql_close($cid);
// End TIMER
// ---------
$etimer = explode( ' ', microtime() );
$etimer = $etimer[1] + $etimer[0];
if ($options['verbose'])
printf( "Script timer: %f seconds.\n\n", ($etimer-$stimer) );
?>
