Initial Commit

This commit is contained in:
bmen 2017-01-16 17:05:08 +01:00
commit 74008c64e0
11 changed files with 6707 additions and 0 deletions

148
README.md Executable file
View File

@ -0,0 +1,148 @@
# Search Replace DB
This script was made to aid the process of migrating PHP and MySQL based websites. It has additional features for WordPress and Drupal but works for most other similar CMSes.
If you find a problem let us know in the issues area and if you can improve the code then please fork the repository and send us a pull request :)
## Warnings & Limitations
1. Three character UTF8 seems to break in certain cases.
2. We can't test every possible case, though we do our best. Backups and verifications are important.
3. The license for this script is GPL v3 and no longer WTFPL. Please bear this in mind if contributing or branching.
4. You use this script at your own risk and we have no responsibility for any problems it may cause.
5. *Do backups.*
## Usage
1. *Do backups.*
2. Migrate all your website files.
3. Upload the script folder to your web root or higher.
4. Browse to the script folder URL in your web browser.
5. Fill in the fields as needed.
6. Choose the `Dry run` button to do a dry run without searching/replacing.
## Installation
If you would like Search Replace DB to detect your WordPress installation, you should install it within a new subfolder within your WordPress installation.
For example, if you have
/website.com/index.php
/website.com/wp-config.php
/website.com/wordpress/
/website.com/wordpress/index.php
/website.com/wordpress/wp-settings.php
etc.
You can copy Search Replace DB into the following location:
/website.com/wordpress/Search-Replace-DB/
/website.com/wordpress/Search-Replace-DB/index.php
/website.com/wordpress/Search-Replace-DB/srdb.class.php
/website.com/wordpress/Search-Replace-DB/srdb.cli.php
etc.
### CLI script
To invoke the script, nagivate in your shell to the directory to where you installed Search Replace DB.
Type `php srdb.cli.php` to run the program. Type `php srdb.cli.php --help` for usage information:
-h, --host
Required. The hostname of the database server.
-n, --name
Required. Database name.
-u, --user
Required. Database user.
-p, --pass
Required. Database user's password.
--port
Optional. Port on database server to connect to.
The default is 3306. (MySQL default port).
-s, --search
String to search for or `preg_replace()` style regular
expression.
-r, --replace
None empty string to replace search with or
`preg_replace()` style replacement.
-t, --tables
If set only runs the script on the specified table, comma
separate for multiple values.
-i, --include-cols
If set only runs the script on the specified columns, comma
separate for multiple values.
-x, --exclude-cols
If set excludes the specified columns, comma separate for
multiple values.
-g, --regex [no value]
Treats value for -s or --search as a regular expression and
-r or --replace as a regular expression replacement.
-l, --pagesize
How many rows to fetch at a time from a table.
-z, --dry-run [no value]
Prevents any updates happening so you can preview the number
of changes to be made
-e, --alter-engine
Changes the database table to the specified database engine
eg. InnoDB or MyISAM. If specified search/replace arguments
are ignored. They will not be run simultaneously.
-a, --alter-collation
Changes the database table to the specified collation
eg. utf8_unicode_ci. If specified search/replace arguments
are ignored. They will not be run simultaneously.
-v, --verbose [true|false]
Defaults to true, can be set to false to run script silently.
--help
Displays this help message ;)
## Troubleshooting
### I get a popup saying there was an AJAX error
This happens occasionally and could be for a couple of reasons:
* When the script starts, it attempts to start your WordPress or Drupal installation to auto-detect your username and password settings. If this fails, you will see a message informing you that auto-detection failed. You will have to enter your details manually.
* Script was unable to set the timeout so PHP closed the connection before the table could be processed, this can happen on some server configurations.
* When using php-fpm (as you have with VVV) make sure that the socket is owned by the server user `chown www-data:www-data /var/run/php5-fpm.sock`.

16
composer.json Executable file
View File

@ -0,0 +1,16 @@
{
"name": "interconnectit/search-replace-db",
"description": "A PHP search replace tool for quickly modifying a string throughout a database. Useful for changing the base URL when migrating a WordPress site from development to production.",
"license": "GPL-3.0",
"homepage": "https://github.com/interconnectit/Search-Replace-DB",
"require": {
"php": ">=5.3.0"
},
"support": {
"issues": "https://github.com/interconnectit/Search-Replace-DB/issues"
},
"bin": [ "srdb.cli.php" ],
"autoload": {
"files": [ "srdb.class.php" ]
}
}

2908
index.php Executable file

File diff suppressed because one or more lines are too long

21
package.json Executable file
View File

@ -0,0 +1,21 @@
{
"name": "search-replace-db",
"version": "3.1.0",
"description": "A PHP search replace tool for quickly modifying a string throughout a database. Useful for changing the base URL when migrating a WordPress site from development to production.",
"main": "srdb.cli.php",
"directories": {
"test": "tests"
},
"scripts": {
},
"repository": {
"type": "git",
"url": "git://github.com/interconnectit/Search-Replace-DB.git"
},
"author": "",
"license": "GPL-v3",
"bugs": {
"url": "https://github.com/interconnectit/Search-Replace-DB/issues"
}
}

1559
srdb-tests/DataSet.xml Executable file

File diff suppressed because it is too large Load Diff

81
srdb-tests/DataSetGenerator.php Executable file
View File

@ -0,0 +1,81 @@
<?php
$content = '<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, <a href="http://example.com/~~~/">eaque ipsa quae</a> ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt
explicabo.</p>
<p>Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur
magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor
sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore
magnam aliquam quaerat voluptatem.</p>
<div class="image">
<img src="http://example.com/assets/image.jpg" alt="Image" />
</div>
<p>Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis
suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea
voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
pariatur?</p>
<div class="image">
<img src="http://site-^^^.example.com/assets/image.jpg" alt="Image" />
</div>
<p>Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis
suscipit laboriosam, <a href="http://site-^^^.example.com/blog/~~~/">nisi ut aliquid ex ea commodi consequatur?</a> Quis autem vel eum iure reprehenderit qui in ea
voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
pariatur?</p>';
$serialised = array(
'number' => 123,
'float' => 12.345,
'string' => 'serialised string',
'accented' => 'föó ßåŗ',
'unicode' => '❤ ☀ ☆ ☂ ☻ ♞ ☯ 😸 😹',
'url' => 'http://example.com/'
);
$serialised[ 'nested' ] = $serialised;
$numbers = range( 1, 100 );
$letters = range( 'a', 'z' );
//var_dump( $letters );
//mb_internal_encoding( 'UTF-8' );
header( 'Content-type: text/xml' );
header( 'Charset: UTF-8' );
//var_dump( unserialize( serialize( $serialised ) ) );
echo '<?xml version="1.0" encoding="UTF-8" ?>
<dataset>
<table name="posts">
<column>id</column>
<column>content</column>
<column>url</column>
<column>serialised</column>';
for( $i = 1; $i < 51; $i++ ) {
$s = $serialised;
$s[ 'url' ] .= $numbers[ array_rand( $numbers, 1 ) ] . '/';
$row_content = str_replace( '~~~', $numbers[ array_rand( $numbers, 1 ) ], $content );
$row_content = str_replace( '^^^', $letters[ array_rand( $letters, 1 ) ], $row_content );
echo '
<row>
<value>' . $i . '</value>
<value>
<![CDATA[
' . $row_content . '
]]>
</value>
<value>http://example.com/' . $i . '/</value>
<value>' . serialize( $s ) . '</value>
</row>
';
//var_dump( unserialize( serialize( $s ) ) );
}
echo '
</table>
</dataset>';

381
srdb-tests/SrdbTest.php Executable file
View File

@ -0,0 +1,381 @@
<?php
/**
* Search Replace class unit tests
* Written for PHPUnit
*
* Requires a mysql database with the following schema and utf8_unicode_ci collation:
*
* posts
* id INT
* content VARCHAR(2000)
* url VARCHAR(2000)
* serialised VARCHAR(2000)
*
*/
date_default_timezone_set( 'Europe/London' );
class SrdbTest extends PHPUnit_Extensions_Database_TestCase {
static private $pdo;
private $conn;
public $testdb = array(
'host' => '127.0.0.1',
'name' => 'srdbtest',
'user' => 'root',
'pass' => '123',
'table'=> 'posts'
);
public function setUp() {
parent::setUp();
// get class to test
require_once( dirname( __FILE__ ) . '/../srdb.class.php' );
}
public function tearDown() {
parent::tearDown();
}
/**
* @return PHPUnit_Extensions_Database_DB_IDatabaseConnection
*/
public function getConnection() {
if ( $this->conn === null ) {
if ( self::$pdo == null )
self::$pdo = new PDO( "mysql:host={$this->testdb['host']}",
$this->testdb[ 'user' ],
$this->testdb[ 'pass' ],
array( PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4' ) );
self::$pdo->query( "CREATE DATABASE IF NOT EXISTS `{$this->testdb[ 'name' ]}` CHARACTER SET = 'utf8mb4' COLLATE = 'utf8mb4_general_ci';" );
self::$pdo->query( "CREATE TABLE IF NOT EXISTS `{$this->testdb[ 'name' ]}`.`{$this->testdb[ 'table' ]}` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`content` blob,
`url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`serialised` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;" );
self::$pdo->query( "USE `{$this->testdb[ 'name' ]}`;" );
$this->conn = $this->createDefaultDBConnection( self::$pdo, $this->testdb[ 'name' ] );
// Get the charset of the table.
$charset = $this->get_table_character_set( );
if ( $charset )
self::$pdo->query( "SET NAMES {$charset};" );
}
return $this->conn;
}
public function get_table_character_set( ) {
$charset = self::$pdo->query( "SELECT c.`character_set_name`
FROM information_schema.`TABLES` t
LEFT JOIN information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` c
ON (t.`TABLE_COLLATION` = c.`COLLATION_NAME`)
WHERE t.table_schema = '{$this->testdb[ 'name' ]}'
AND t.table_name = '{$this->testdb[ 'table' ]}'
LIMIT 1;" );
$encoding = false;
if ( $charset ) {
$result = $charset->fetch( );
if ( isset( $result[ 'character_set_name' ] ) )
$encoding = $result[ 'character_set_name' ];
}
return $encoding;
}
/**
* @return PHPUnit_Extensions_Database_DataSet_IDataSet
*/
public function getDataSet() {
return $this->createXMLDataSet( dirname( __FILE__ ) . '/DataSet.xml' );
}
/*
* @test search replace
*/
public function testSearchReplace() {
// search replace strings
$search = 'example.com';
$replace = 'example.org';
// runs search/replace
$srdb = new icit_srdb( array_merge( array(
'search' => $search,
'replace' => $replace,
'dry_run' => false
), $this->testdb ) );
// results from sample data
// no errors
$this->assertEmpty( $srdb->errors[ 'results' ], "Search replace script errors were found: \n" . implode( "\n", $srdb->errors[ 'results' ] ) );
$this->assertEmpty( $srdb->errors[ 'db' ], "Search replace script database errors were found: \n" . implode( "\n", $srdb->errors[ 'db' ] ) );
// update statements run
$updates = $srdb->report[ 'updates' ];
$this->assertEquals( 50, $updates, 'Wrong number of updates reported' );
// cells changed
$changes = $srdb->report[ 'change' ];
$this->assertEquals( 150, $changes, 'Wrong number of cells changed reported' );
// test the database is actually changed
$modified = self::$pdo->query( "SELECT url FROM `{$this->testdb[ 'table' ]}` LIMIT 1;" )->fetchColumn();
$this->assertRegExp( "/{$replace}/", $modified );
}
public function testSearchReplaceUnicode() {
// search replace strings
$search = 'perspiciatis';
$replace = '😸';
// runs search/replace
$srdb = new icit_srdb( array_merge( array(
'search' => $search,
'replace' => $replace,
'dry_run' => false
), $this->testdb ) );
// results from sample data
// no errors
$this->assertEmpty( $srdb->errors[ 'results' ], "Search replace script errors were found: \n" . implode( "\n", $srdb->errors[ 'results' ] ) );
$this->assertEmpty( $srdb->errors[ 'db' ], "Search replace script database errors were found: \n" . implode( "\n", $srdb->errors[ 'db' ] ) );
// update statements run
$updates = $srdb->report[ 'updates' ];
$this->assertEquals( 50, $updates, 'Wrong number of updates reported' );
// cells changed
$changes = $srdb->report[ 'change' ];
$this->assertEquals( 50, $changes, 'Wrong number of cells changed reported' );
// test the database is actually changed
$modified = self::$pdo->query( "SELECT content FROM `{$this->testdb[ 'table' ]}` LIMIT 1;" )->fetchColumn();
$this->assertRegExp( "/{$replace}/", $modified );
}
/*
* @test str_replace regex
*/
public function testRegexReplace() {
// search replace strings
$search = '#https?://([a-z0-9\.-]+)/?#';
$replace = 'https://$1/';
// class instance with regex enabled
$srdb = new icit_srdb( array_merge( array(
'search' => $search,
'replace' => $replace,
'regex' => true,
'dry_run' => false
), $this->testdb ) );
// direct method invocation
$subject = 'http://example.com/';
$result = 'https://example.com/';
$replaced = $srdb->str_replace( $search, $replace, $subject );
$this->assertEquals( $result, $replaced );
// results from sample data
// no errors
$this->assertEmpty( $srdb->errors[ 'results' ], "Search replace script errors were found: \n" . implode( "\n", $srdb->errors[ 'results' ] ) );
$this->assertEmpty( $srdb->errors[ 'db' ], "Search replace script database errors were found: \n" . implode( "\n", $srdb->errors[ 'db' ] ) );
// update statements run
$updates = $srdb->report[ 'updates' ];
$this->assertEquals( 50, $updates, 'Wrong number of updates reported' );
// cells changed
$changes = $srdb->report[ 'change' ];
$this->assertEquals( 150, $changes, 'Wrong number of changes reported' );
// test the database is actually changed
$modified = self::$pdo->query( "SELECT url FROM `{$this->testdb[ 'table' ]}` LIMIT 1;" )->fetchColumn();
$this->assertRegExp( "#{$result}#", $modified, 'Database not updated, modified result is ' . $modified );
}
/**
* @test str_replace serialised data
*/
public function testStrReplaceSerialised() {
// search replace strings
$search = 'serialised string';
$replace = 'longer serialised string';
// class instance with regex enabled
$srdb = new icit_srdb( array_merge( array(
'search' => $search,
'replace' => $replace,
'dry_run' => false
), $this->testdb ) );
// results from sample data
// no errors
$this->assertEmpty( $srdb->errors[ 'results' ], "Search replace script errors were found: \n" . implode( "\n", $srdb->errors[ 'results' ] ) );
$this->assertEmpty( $srdb->errors[ 'db' ], "Search replace script database errors were found: \n" . implode( "\n", $srdb->errors[ 'db' ] ) );
// update statements run
$updates = $srdb->report[ 'updates' ];
$this->assertEquals( 50, $updates, 'Wrong number of updates reported' );
// cells changed
$changes = $srdb->report[ 'change' ];
$this->assertEquals( 50, $changes, 'Wrong number of changes reported' );
// check unserialised values are what they should be
$modified = self::$pdo->query( "SELECT serialised FROM `{$this->testdb[ 'table' ]}` LIMIT 1;" )->fetchColumn();
$from = unserialize( $modified );
$this->assertEquals( $replace, $from[ 'string' ], 'Unserialised array value not updated' );
}
/*
* @test recursive unserialize replace
*/
public function testRecursiveUnserializeReplace() {
// search replace strings
$search = 'serialised string';
$replace = 'longer serialised string';
// class instance with regex enabled
$srdb = new icit_srdb( array_merge( array(
'search' => $search,
'replace' => $replace,
'dry_run' => false
), $this->testdb ) );
// results from sample data
// no errors
$this->assertEmpty( $srdb->errors[ 'results' ], "Search replace script errors were found: \n" . implode( "\n", $srdb->errors[ 'results' ] ) );
$this->assertEmpty( $srdb->errors[ 'db' ], "Search replace script database errors were found: \n" . implode( "\n", $srdb->errors[ 'db' ] ) );
// check unserialised values are what they should be
$modified = self::$pdo->query( "SELECT serialised FROM `{$this->testdb[ 'table' ]}` LIMIT 1;" )->fetchColumn();
$from = unserialize( $modified );
$this->assertEquals( $replace, $from[ 'nested' ][ 'string' ], 'Unserialised nested array value not updated' );
}
/*
* @test include columns
*/
public function testIncludeColumns() {
// search replace strings
$search = 'example.com';
$replace = 'example.org';
// class instance with regex enabled
$srdb = new icit_srdb( array_merge( array(
'search' => $search,
'replace' => $replace,
'dry_run' => false,
'include_cols' => array( 'url' )
), $this->testdb ) );
// results from sample data
// no errors
$this->assertEmpty( $srdb->errors[ 'results' ], "Search replace script errors were found: \n" . implode( "\n", $srdb->errors[ 'results' ] ) );
$this->assertEmpty( $srdb->errors[ 'db' ], "Search replace script database errors were found: \n" . implode( "\n", $srdb->errors[ 'db' ] ) );
// update statements run
$updates = $srdb->report[ 'updates' ];
$this->assertEquals( 50, $updates, 'Wrong number of updates reported' );
// cells changed
$changes = $srdb->report[ 'change' ];
$this->assertEquals( 50, $changes, 'Wrong number of changes reported' );
// check unserialised values are what they should be
$modified = self::$pdo->query( "SELECT content, url FROM `{$this->testdb[ 'table' ]}` LIMIT 1;" )->fetchAll();
$content = $modified[ 0 ][ 'content' ];
$url = $modified[ 0 ][ 'url' ];
$this->assertRegExp( "/$search/", $content, 'Content column was modified' );
$this->assertRegExp( "/$replace/", $url, 'URL column was not modified' );
}
/*
* @test exclude columns
*/
public function testExcludeColumns() {
// search replace strings
$search = 'example.com';
$replace = 'example.org';
// class instance with regex enabled
$srdb = new icit_srdb( array_merge( array(
'search' => $search,
'replace' => $replace,
'dry_run' => false,
'exclude_cols' => array( 'url' )
), $this->testdb ) );
// results from sample data
// no errors
$this->assertEmpty( $srdb->errors[ 'results' ], "Search replace script errors were found: \n" . implode( "\n", $srdb->errors[ 'results' ] ) );
$this->assertEmpty( $srdb->errors[ 'db' ], "Search replace script database errors were found: \n" . implode( "\n", $srdb->errors[ 'db' ] ) );
// update statements run
$updates = $srdb->report[ 'updates' ];
$this->assertEquals( 50, $updates, 'Wrong number of updates reported' );
// cells changed
$changes = $srdb->report[ 'change' ];
$this->assertEquals( 100, $changes, 'Wrong number of changes reported' );
// check unserialised values are what they should be
$modified = self::$pdo->query( "SELECT content, url FROM `{$this->testdb[ 'table' ]}` LIMIT 1;" )->fetchAll();
$content = $modified[ 0 ][ 'content' ];
$url = $modified[ 0 ][ 'url' ];
$this->assertRegExp( "/$replace/", $content, 'Content column was not modified' );
$this->assertRegExp( "/$search/", $url, 'URL column was modified' );
}
/**
* @test multibyte string replacement method
*/
public function testMultibyteStrReplace() {
$subject = 'föö ❤ ☀ ☆ ☂ ☻ ♞ ☯';
$result = 'föö ❤ ☻ ♞ ☯ ☻ ♞ ☯';
$replaced = icit_srdb::mb_str_replace( '☀ ☆ ☂', '☻ ♞ ☯', $subject );
$this->assertEquals( $result, $replaced );
}
}

93
srdb-tests/charset-test.php Executable file
View File

@ -0,0 +1,93 @@
<?php
// Connection details
$user = 'root';
$pass = '123';
$host = '127.0.0.1';
// Test data
$original = array(
'number' => 123,
'float' => 12.345,
'string' => 'serialised string',
'accented' => 'föó ßåŗ',
'unicode' => '❤ ☀ ☆ ☂ ☻ ♞ ☯',
'url' => 'http://example.com/'
);
$serialised = serialize( $original );
// Connect
$x = new PDO( "mysql:host={$host}", $user, $pass );
// Create our schema
$x->query( "CREATE DATABASE IF NOT EXISTS `encode` CHARACTER SET = 'utf8' COLLATE = 'utf8_general_ci';" );
$x->query( 'SET NAMES utf8;' );
// Create a table for each encoding type and stick the encoded array in it.
$charsets = $x->query( 'SELECT CHARACTER_SET_NAME as charset, COLLATION_NAME as collation FROM information_schema.COLLATION_CHARACTER_SET_APPLICABILITY;' );
if ( method_exists( $charsets, 'fetch' ) ) {
while( $collation = $charsets->fetch() ) {
$col = $collation[ 'collation' ];
$charset = $collation[ 'charset' ];
$tbl_name = $collation[ 'collation' ];
// Create the table for the collation
$x->query( "DROP TABLE IF EXISTS `encode`.`{$tbl_name}`" );
$x->query( "CREATE TABLE `encode`.`{$tbl_name}` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`number` decimal(10,0) NOT NULL,
`float` float NOT NULL,
`string` longtext COLLATE {$col} NOT NULL,
`accented` longtext COLLATE {$col} NOT NULL,
`unicode` longtext COLLATE {$col} NOT NULL,
`url` longtext COLLATE {$col} NOT NULL,
`serialised` longtext CHARACTER SET {$charset} NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET={$charset} COLLATE={$col};" );
// Set the name space to match to charset
switch( $charset ) {
// If I uncomment this utf-16 and utf-32 work, I don't know why though.
//case 'utf16':
//case 'utf32':
// $x->query( "SET NAMES utf8;" );
// break;
default:
$x->query( "SET NAMES {$charset};" );
}
// Insert our test data
if ( !$x->query( "INSERT INTO encode.`{$tbl_name}` ( number, float, string, accented, unicode, url, serialised )
VALUES (
{$original['number']},
{$original['float']},
'{$original['string']}',
'{$original['accented']}',
'{$original['unicode']}',
'{$original['url']}',
'{$serialised}'
);" ) )
echo "<pre style=\"color:blue\">Insert Failed: {$col}:{$charset}</pre>";
// Set names to match table's charset
$x->query( "SET NAMES {$charset};" );
// Reclaim what we just dumped into the db and compare
$q = $x->query( "SELECT serialised FROM encode.{$tbl_name} ORDER BY id DESC LIMIT 1;" );
if ( method_exists( $q, 'fetch' ) ) {
while( $var = $q->fetch( )[0] ) {
$unserialized = @unserialize( $var );
if ( !$unserialized || array_diff( $unserialized, $original ) ) {
echo "<pre style=\"color:red\">Failed: {$col}:{$charset}</pre>";
}
else {
echo "<pre style=\"color:green\">Success: {$col}:{$charset}</pre>";
}
}
}
}
}

45
srdb-tests/db.sql Executable file
View File

@ -0,0 +1,45 @@
CREATE DATABASE IF NOT EXISTS `srdbtest` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `srdbtest`;
-- MySQL dump 10.13 Distrib 5.5.16, for Win32 (x86)
--
-- Host: localhost Database: srdbtest
-- ------------------------------------------------------
-- Server version 5.5.24-log
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `posts`
--
DROP TABLE IF EXISTS `posts`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `posts` (
`id` int(11) NOT NULL,
`content` blob,
`url` varchar(1024) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
`serialised` varchar(2000) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2013-11-14 10:36:55

1217
srdb.class.php Executable file

File diff suppressed because it is too large Load Diff

238
srdb.cli.php Executable file
View File

@ -0,0 +1,238 @@
#!/usr/bin/php -q
<?php
/**
* To run this script, execute something like this:
* `./srdb.cli.php -h localhost -u root -n test -s "findMe" -r "replaceMe"`
* use the --dry-run flag to do a dry run without searching/replacing.
*/
// php 5.3 date timezone requirement, shouldn't affect anything
date_default_timezone_set( 'Europe/London' );
// include the srdb class
require_once( realpath( dirname( __FILE__ ) ) . '/srdb.class.php' );
$opts = array(
'h:' => 'host:',
'n:' => 'name:',
'u:' => 'user:',
'p:' => 'pass:',
'c:' => 'char:',
's:' => 'search:',
'r:' => 'replace:',
't:' => 'tables:',
'i:' => 'include-cols:',
'x:' => 'exclude-cols:',
'g' => 'regex',
'l:' => 'pagesize:',
'z' => 'dry-run',
'e:' => 'alter-engine:',
'a:' => 'alter-collation:',
'v:' => 'verbose:',
'port:',
'help'
);
$required = array(
'h:',
'n:',
'u:',
'p:'
);
function strip_colons( $string ) {
return str_replace( ':', '', $string );
}
// store arg values
$arg_count = $_SERVER[ 'argc' ];
$args_array = $_SERVER[ 'argv' ];
$short_opts = array_keys( $opts );
$short_opts_normal = array_map( 'strip_colons', $short_opts );
$long_opts = array_values( $opts );
$long_opts_normal = array_map( 'strip_colons', $long_opts );
// store array of options and values
$options = getopt( implode( '', $short_opts ), $long_opts );
if ( isset( $options[ 'help' ] ) ) {
echo "
#####################################################################
interconnect/it Safe Search & Replace tool
#####################################################################
This script allows you to search and replace strings in your database
safely without breaking serialised PHP.
Please report any bugs or fork and contribute to this script via
Github: https://github.com/interconnectit/search-replace-db
Argument values are strings unless otherwise specified.
ARGS
-h, --host
Required. The hostname of the database server.
-n, --name
Required. Database name.
-u, --user
Required. Database user.
-p, --pass
Required. Database user's password.
--port
Optional. Port on database server to connect to.
The default is 3306. (MySQL default port).
-s, --search
String to search for or `preg_replace()` style
regular expression.
-r, --replace
None empty string to replace search with or
`preg_replace()` style replacement.
-t, --tables
If set only runs the script on the specified table, comma
separate for multiple values.
-i, --include-cols
If set only runs the script on the specified columns, comma
separate for multiple values.
-x, --exclude-cols
If set excludes the specified columns, comma separate for
multiple values.
-g, --regex [no value]
Treats value for -s or --search as a regular expression and
-r or --replace as a regular expression replacement.
-l, --pagesize
How rows to fetch at a time from a table.
-z, --dry-run [no value]
Prevents any updates happening so you can preview the number
of changes to be made
-e, --alter-engine
Changes the database table to the specified database engine
eg. InnoDB or MyISAM. If specified search/replace arguments
are ignored. They will not be run simultaneously.
-a, --alter-collation
Changes the database table to the specified collation
eg. utf8_unicode_ci. If specified search/replace arguments
are ignored. They will not be run simultaneously.
-v, --verbose [true|false]
Defaults to true, can be set to false to run script silently.
--help
Displays this help message ;)
";
exit;
}
// missing field flag, show all missing instead of 1 at a time
$missing_arg = false;
// check required args are passed
foreach( $required as $key ) {
$short_opt = strip_colons( $key );
$long_opt = strip_colons( $opts[ $key ] );
if ( ! isset( $options[ $short_opt ] ) && ! isset( $options[ $long_opt ] ) ) {
fwrite( STDERR, "Error: Missing argument, -{$short_opt} or --{$long_opt} is required.\n" );
$missing_arg = true;
}
}
// bail if requirements not met
if ( $missing_arg ) {
fwrite( STDERR, "Please enter the missing arguments.\n" );
exit( 1 );
}
// new args array
$args = array(
'verbose' => true,
'dry_run' => false
);
// create $args array
foreach( $options as $key => $value ) {
// transpose keys
if ( ( $is_short = array_search( $key, $short_opts_normal ) ) !== false )
$key = $long_opts_normal[ $is_short ];
// boolean options as is, eg. a no value arg should be set true
if ( in_array( $key, $long_opts ) )
$value = true;
switch ( $key ) {
// boolean options.
case 'verbose':
$value = (boolean)filter_var( $value, FILTER_VALIDATE_BOOLEAN );
break;
}
// change to underscores
$key = str_replace( '-', '_', $key );
$args[ $key ] = $value;
}
// modify the log output
class icit_srdb_cli extends icit_srdb {
public function log( $type = '' ) {
$args = array_slice( func_get_args(), 1 );
$output = "";
switch( $type ) {
case 'error':
list( $error_type, $error ) = $args;
$output .= "$error_type: $error";
break;
case 'search_replace_table_start':
list( $table, $search, $replace ) = $args;
$output .= "{$table}: replacing {$search} with {$replace}";
break;
case 'search_replace_table_end':
list( $table, $report ) = $args;
$time = number_format( $report[ 'end' ] - $report[ 'start' ], 8 );
$output .= "{$table}: {$report['rows']} rows, {$report['change']} changes found, {$report['updates']} updates made in {$time} seconds";
break;
case 'search_replace_end':
list( $search, $replace, $report ) = $args;
$time = number_format( $report[ 'end' ] - $report[ 'start' ], 8 );
$dry_run_string = $this->dry_run ? "would have been" : "were";
$output .= "
Replacing {$search} with {$replace} on {$report['tables']} tables with {$report['rows']} rows
{$report['change']} changes {$dry_run_string} made
{$report['updates']} updates were actually made
It took {$time} seconds";
break;
case 'update_engine':
list( $table, $report, $engine ) = $args;
$output .= $table . ( $report[ 'converted' ][ $table ] ? ' has been' : 'has not been' ) . ' converted to ' . $engine;
break;
case 'update_collation':
list( $table, $report, $collation ) = $args;
$output .= $table . ( $report[ 'converted' ][ $table ] ? ' has been' : 'has not been' ) . ' converted to ' . $collation;
break;
}
if ( $this->verbose )
echo $output . "\n";
}
}
$report = new icit_srdb_cli( $args );
// Only print a separating newline if verbose mode is on to separate verbose output from result
if ($args[ 'verbose' ]) {
echo "\n";
}
if ( $report && ( ( isset( $args[ 'dry_run' ] ) && $args[ 'dry_run' ] ) || empty( $report->errors[ 'results' ] ) ) ) {
echo "And we're done!\n";
} else {
echo "Check the output for errors. You may need to ensure verbose output is on by using -v or --verbose.\n";
}