Initial Commit
This commit is contained in:
commit
74008c64e0
|
@ -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`.
|
|
@ -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" ]
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -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"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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>';
|
|
@ -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 );
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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";
|
||||
}
|
Loading…
Reference in New Issue