/index.php
https://gitlab.com/Blueprint-Marketing/Search-Replace-DB · PHP · 1222 lines · 751 code · 271 blank · 200 comment · 77 complexity · 9357c81b3bb553d77290929601f88844 MD5 · raw file
- <?php
- /**
- *
- * Safe Search and Replace on Database with Serialized Data v3.0.0
- *
- * This script is to solve the problem of doing database search and replace when
- * some data is stored within PHP serialized arrays or objects.
- *
- * For more information, see
- * http://interconnectit.com/124/search-and-replace-for-wordpress-databases/
- *
- * To contribute go to
- * http://github.com/interconnectit/search-replace-db
- *
- * To use, load the script on your server and point your web browser to it.
- * In some situations, consider using the command line interface version.
- *
- * BIG WARNING! Take a backup first, and carefully test the results of this
- * code. If you don't, and you vape your data then you only have yourself to
- * blame. Seriously. And if your English is bad and you don't fully
- * understand the instructions then STOP. Right there. Yes. Before you do any
- * damage.
- *
- * USE OF THIS SCRIPT IS ENTIRELY AT YOUR OWN RISK. I/We accept no liability
- * from its use.
- *
- * First Written 2009-05-25 by David Coveney of Interconnect IT Ltd (UK)
- * http://www.davidcoveney.com or http://interconnectit.com
- * and released under the GPL v3
- * ie, do what ever you want with the code, and we take no responsibility for it
- * OK? If you don't wish to take responsibility, hire us at Interconnect IT Ltd
- * on +44 (0)151 331 5140 and we will do the work for you at our hourly rate,
- * minimum 1hr
- *
- * License: GPL v3
- * License URL: http://www.gnu.org/copyleft/gpl.html
- *
- *
- * Version 3.0.0:
- * * Major overhaul
- * * Multibyte string replacements
- * * UI completely redesigned
- * * Removed all links from script until 'delete' has been clicked to avoid
- * security risk from our access logs
- * * Search replace functionality moved to it's own separate class
- * * Replacements done table by table to avoid timeouts
- * * Convert tables to InnoDB
- * * Convert tables to utf8_unicode_ci
- * * Use PDO if available
- * * Preview/view changes
- * * Optionally use preg_replace()
- * * Scripts bootstraps WordPress/Drupal to avoid issues with unknown
- * serialised objects/classes
- * * Added marketing stuff to deleted screen (sorry but we're running a
- * business!)
- *
- * Version 2.2.0:
- * * Added remove script patch from David Anderson (wordshell.net)
- * * Added ability to replace strings with nothing
- * * Copy changes
- * * Added code to recursive_unserialize_replace to deal with objects not
- * just arrays. This was submitted by Tina Matter.
- * ToDo: Test object handling. Not sure how it will cope with object in the
- * db created with classes that don't exist in anything but the base PHP.
- *
- * Version 2.1.0:
- * - Changed to version 2.1.0
- * * Following change by Sergei Biryukov - merged in and tested by Dave Coveney
- * - Added Charset Support (tested with UTF-8, not tested on other charsets)
- * * Following changes implemented by James Whitehead with thanks to all the commenters and feedback given!
- * - Removed PHP warnings if you go to step 3+ without DB details.
- * - Added options to skip changing the guid column. If there are other
- * columns that need excluding you can add them to the $exclude_cols global
- * array. May choose to add another option to the table select page to let
- * you add to this array from the front end.
- * - Minor tweak to label styling.
- * - Added comments to each of the functions.
- * - Removed a dead param from icit_srdb_replacer
- * Version 2.0.0:
- * - returned to using unserialize function to check if string is
- * serialized or not
- * - marked is_serialized_string function as deprecated
- * - changed form order to improve usability and make use on multisites a
- * bit less scary
- * - changed to version 2, as really should have done when the UI was
- * introduced
- * - added a recursive array walker to deal with serialized strings being
- * stored in serialized strings. Yes, really.
- * - changes by James R Whitehead (kudos for recursive walker) and David
- * Coveney 2011-08-26
- * Version 1.0.2:
- * - typos corrected, button text tweak - David Coveney / Robert O'Rourke
- * Version 1.0.1
- * - styling and form added by James R Whitehead.
- *
- * Credits: moz667 at gmail dot com for his recursive_array_replace posted at
- * uk.php.net which saved me a little time - a perfect sample for me
- * and seems to work in all cases.
- *
- */
- // always good here
- header( 'HTTP/1.1 200 OK' );
- header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1.
- header('Pragma: no-cache'); // HTTP 1.0.
- header('Expires: 0'); // Proxies.
- require_once( 'srdb.class.php' );
- class icit_srdb_ui extends icit_srdb {
- /**
- * @var string Root path of the CMS
- */
- public $path;
- public $is_wordpress = false;
- public $is_drupal = false;
- public function __construct() {
- // php 5.4 date timezone requirement, shouldn't affect anything
- date_default_timezone_set( 'Europe/London' );
- // prevent fatals from hiding the UI
- register_shutdown_function( array( $this, 'fatal_handler' ) );
- // flag to bootstrap WP or Drupal
- $bootstrap = true; // isset( $_GET[ 'bootstrap' ] );
- // discover environment
- if ( $bootstrap && $this->is_wordpress() ) {
- // prevent warnings if the charset and collate aren't defined
- if ( !defined( 'DB_CHARSET') ) {
- define( 'DB_CHARSET', 'utf8' );
- }
- if ( !defined( 'DB_COLLATE') ) {
- define( 'DB_COLLATE', '' );
- }
- // populate db details
- $name = DB_NAME;
- $user = DB_USER;
- $pass = DB_PASSWORD;
- $host = DB_HOST;
- $charset = DB_CHARSET;
- $collate = DB_COLLATE;
- $this->response( $name, $user, $pass, $host, $charset, $collate );
- } elseif( $bootstrap && $this->is_drupal() ) {
- $database = Database::getConnection();
- $database_opts = $database->getConnectionOptions();
- // populate db details
- $name = $database_opts[ 'database' ];
- $user = $database_opts[ 'username' ];
- $pass = $database_opts[ 'password' ];
- $host = $database_opts[ 'host' ];
- $charset = 'utf8';
- $collate = '';
- $this->response( $name, $user, $pass, $host, $charset, $collate );
- } else {
- $this->response();
- }
- }
- public function response( $name = '', $user = '', $pass = '', $host = '127.0.0.1', $charset = 'utf8', $collate = '' ) {
- // always override with post data
- if ( isset( $_POST[ 'name' ] ) ) {
- $name = $_POST[ 'name' ]; // your database
- $user = $_POST[ 'user' ]; // your db userid
- $pass = $_POST[ 'pass' ]; // your db password
- $host = $_POST[ 'host' ]; // normally localhost, but not necessarily.
- $charset = 'utf8'; // isset( $_POST[ 'char' ] ) ? stripcslashes( $_POST[ 'char' ] ) : ''; // your db charset
- $collate = '';
- }
- // Search replace details
- $search = isset( $_POST[ 'search' ] ) ? $_POST[ 'search' ] : '';
- $replace = isset( $_POST[ 'replace' ] ) ? $_POST[ 'replace' ] : '';
- // regex options
- $regex = isset( $_POST[ 'regex' ] );
- $regex_i = isset( $_POST[ 'regex_i' ] );
- $regex_m = isset( $_POST[ 'regex_m' ] );
- $regex_s = isset( $_POST[ 'regex_s' ] );
- $regex_x = isset( $_POST[ 'regex_x' ] );
- // Tables to scanned
- $tables = isset( $_POST[ 'tables' ] ) && is_array( $_POST[ 'tables' ] ) ? $_POST[ 'tables' ] : array( );
- if ( isset( $_POST[ 'use_tables' ] ) && $_POST[ 'use_tables' ] == 'all' )
- $tables = array();
- // exclude / include columns
- $exclude_cols = isset( $_POST[ 'exclude_cols' ] ) ? $_POST[ 'exclude_cols' ] : array();
- $include_cols = isset( $_POST[ 'include_cols' ] ) ? $_POST[ 'include_cols' ] : array();
- foreach( array( 'exclude_cols', 'include_cols' ) as $maybe_string_arg ) {
- if ( is_string( $$maybe_string_arg ) )
- $$maybe_string_arg = array_filter( array_map( 'trim', explode( ',', $$maybe_string_arg ) ) );
- }
- // update class vars
- $vars = array(
- 'name', 'user', 'pass', 'host',
- 'charset', 'collate', 'tables',
- 'search', 'replace',
- 'exclude_cols', 'include_cols',
- 'regex', 'regex_i', 'regex_m', 'regex_s', 'regex_x'
- );
- foreach( $vars as $var ) {
- if ( isset( $$var ) )
- $this->set( $var, $$var );
- }
- // are doing something?
- $show = '';
- if ( isset( $_POST[ 'submit' ] ) ) {
- if ( is_array( $_POST[ 'submit' ] ) )
- $show = key( $_POST[ 'submit' ] );
- if ( is_string( $_POST[ 'submit' ] ) )
- $show = preg_replace( '/submit\[([a-z0-9]+)\]/', '$1', $_POST[ 'submit' ] );
- }
- // is it an AJAX call
- $ajax = isset( $_POST[ 'ajax' ] );
- // body callback
- $html = 'ui';
- switch( $show ) {
- // remove search replace
- case 'delete':
- // determine if it's the folder of compiled version
- if ( basename( __FILE__ ) == 'index.php' )
- $path = str_replace( basename( __FILE__ ), '', __FILE__ );
- else
- $path = __FILE__;
- if ( $this->delete_script( $path ) ) {
- if ( is_file( __FILE__ ) && file_exists( __FILE__ ) )
- $this->add_error( 'Could not delete the search replace script. You will have to delete it manually', 'delete' );
- else
- $this->add_error( 'Search/Replace has been successfully removed from your server', 'delete' );
- } else {
- $this->add_error( 'Could not delete the search replace script automatically. You will have to delete it manually, sorry!', 'delete' );
- }
- $html = 'deleted';
- break;
- case 'liverun':
- // bsy-web, 20130621: Check live run was explicitly clicked and only set false then
- $this->set( 'dry_run', false );
- case 'dryrun':
- // build regex string
- // non UI implements can just pass in complete regex string
- if ( $this->regex ) {
- $mods = '';
- if ( $this->regex_i ) $mods .= 'i';
- if ( $this->regex_s ) $mods .= 's';
- if ( $this->regex_m ) $mods .= 'm';
- if ( $this->regex_x ) $mods .= 'x';
- $this->search = '/' . $this->search . '/' . $mods;
- }
- // call search replace class
- $parent = parent::__construct( array(
- 'name' => $this->get( 'name' ),
- 'user' => $this->get( 'user' ),
- 'pass' => $this->get( 'pass' ),
- 'host' => $this->get( 'host' ),
- 'search' => $this->get( 'search' ),
- 'replace' => $this->get( 'replace' ),
- 'tables' => $this->get( 'tables' ),
- 'dry_run' => $this->get( 'dry_run' ),
- 'regex' => $this->get( 'regex' ),
- 'exclude_cols' => $this->get( 'exclude_cols' ),
- 'include_cols' => $this->get( 'include_cols' )
- ) );
- break;
- case 'innodb':
- // call search replace class to alter engine
- $parent = parent::__construct( array(
- 'name' => $this->get( 'name' ),
- 'user' => $this->get( 'user' ),
- 'pass' => $this->get( 'pass' ),
- 'host' => $this->get( 'host' ),
- 'tables' => $this->get( 'tables' ),
- 'alter_engine' => 'InnoDB',
- ) );
- break;
- case 'utf8':
- // call search replace class to alter engine
- $parent = parent::__construct( array(
- 'name' => $this->get( 'name' ),
- 'user' => $this->get( 'user' ),
- 'pass' => $this->get( 'pass' ),
- 'host' => $this->get( 'host' ),
- 'tables' => $this->get( 'tables' ),
- 'alter_collation' => 'utf8_unicode_ci',
- ) );
- break;
- case 'update':
- default:
- // get tables or error messages
- $this->db_setup();
- if ( $this->db_valid() ) {
- // get engines
- $this->set( 'engines', $this->get_engines() );
- // get tables
- $this->set( 'all_tables', $this->get_tables() );
- }
- break;
- }
- $info = array(
- 'table_select' => $this->table_select( false ),
- 'engines' => $this->get( 'engines' )
- );
- // set header again before output in case WP does it's thing
- header( 'HTTP/1.1 200 OK' );
- if ( ! $ajax ) {
- $this->html( $html );
- } else {
- // return json version of results
- header( 'Content-Type: application/json' );
- echo json_encode( array(
- 'errors' => $this->get( 'errors' ),
- 'report' => $this->get( 'report' ),
- 'info' => $info
- ) );
- exit;
- }
- }
- public function exceptions( $exception ) {
- $this->add_error( '<p class="exception">' . $exception->getMessage() . '</p>' );
- }
- public function errors( $no, $message, $file, $line ) {
- $this->add_error( '<p class="error">' . "<strong>{$no}:</strong> {$message} in {$file} on line {$line}" . '</p>', 'results' );
- }
- public function fatal_handler() {
- $error = error_get_last();
- if( $error !== NULL ) {
- $errno = $error["type"];
- $errfile = $error["file"];
- $errline = $error["line"];
- $errstr = $error["message"];
- if ( $errno == 1 ) {
- header( 'HTTP/1.1 200 OK' );
- $this->add_error( '<p class="error">Could not bootstrap environment.<br /> ' . "Fatal error in {$errfile}, line {$errline}. {$errstr}" . '</p>', 'environment' );
- $this->response();
- }
- }
- }
- /**
- * http://stackoverflow.com/questions/3349753/delete-directory-with-files-in-it
- *
- * @param string $path directory/file path
- *
- * @return void
- */
- public function delete_script( $path ) {
- return is_file( $path ) ?
- @unlink( $path ) :
- array_map( array( $this, __FUNCTION__ ), glob( $path . '/*' ) ) == @rmdir( $path );
- }
- /**
- * Attempts to detect a WordPress installation and bootstraps the environment with it
- *
- * @return bool Whether it is a WP install and we have database credentials
- */
- public function is_wordpress() {
- $path_mod = '';
- $depth = 0;
- $max_depth = 4;
- $bootstrap_file = 'wp-blog-header.php';
- while( ! file_exists( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" ) ) {
- $path_mod .= '/..';
- if ( $depth++ >= $max_depth )
- break;
- }
- if ( file_exists( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" ) ) {
- // store WP path
- $this->path = dirname( __FILE__ ) . $path_mod;
- // just in case we're white screening
- try {
- // need to make as many of the globals available as possible or things can break
- // (globals suck)
- global $wp, $wpdb, $wp_query, $wp_the_query, $wp_version,
- $wp_db_version, $tinymce_version, $manifest_version,
- $required_php_version, $required_mysql_version,
- $post, $posts, $wp_locale, $authordata, $more, $numpages,
- $currentday, $currentmonth, $page, $pages, $multipage,
- $wp_rewrite, $wp_filesystem, $blog_id, $request,
- $wp_styles, $wp_taxonomies, $wp_post_types, $wp_filter,
- $wp_object_cache, $query_string, $single, $post_type,
- $is_iphone, $is_chrome, $is_safari, $is_NS4, $is_opera,
- $is_macIE, $is_winIE, $is_gecko, $is_lynx, $is_IE,
- $is_apache, $is_iis7, $is_IIS;
- // prevent multisite redirect
- define( 'WP_INSTALLING', true );
- // prevent super/total cache
- define( 'DONOTCACHEDB', true );
- define( 'DONOTCACHEPAGE', true );
- define( 'DONOTCACHEOBJECT', true );
- define( 'DONOTCDN', true );
- define( 'DONOTMINIFY', true );
- // cancel batcache
- if ( function_exists( 'batcache_cancel' ) )
- batcache_cancel();
- // bootstrap WordPress
- require( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" );
- $this->set( 'path', ABSPATH );
- $this->set( 'is_wordpress', true );
- return true;
- } catch( Exception $error ) {
- // try and get database values using regex approach
- $db_details = $this->define_find( $this->path . '/wp-config.php' );
- if ( $db_details ) {
- define( 'DB_NAME', $db_details[ 'name' ] );
- define( 'DB_USER', $db_details[ 'user' ] );
- define( 'DB_PASSWORD', $db_details[ 'pass' ] );
- define( 'DB_HOST', $db_details[ 'host' ] );
- define( 'DB_CHARSET', $db_details[ 'char' ] );
- define( 'DB_COLLATE', $db_details[ 'coll' ] );
- // additional error message
- $this->add_error( 'WordPress detected but could not bootstrap environment. There might be a PHP error, possibly caused by changes to the database', 'db' );
- }
- if ( $db_details )
- return true;
- }
- }
- return false;
- }
- public function is_drupal() {
- $path_mod = '';
- $depth = 0;
- $max_depth = 4;
- $bootstrap_file = 'includes/bootstrap.inc';
- while( ! file_exists( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" ) ) {
- $path_mod .= '/..';
- if ( $depth++ >= $max_depth )
- break;
- }
- if ( file_exists( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" ) ) {
- try {
- // require the bootstrap include
- require_once( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" );
- // define drupal root
- if ( ! defined( 'DRUPAL_ROOT' ) )
- define( 'DRUPAL_ROOT', dirname( __FILE__ ) . $path_mod );
- // load drupal
- drupal_bootstrap( DRUPAL_BOOTSTRAP_FULL );
- // confirm environment
- $this->set( 'is_drupal', true );
- return true;
- } catch( Exception $error ) {
- $this->add_error( 'Drupal detected but could not bootstrap environment. There might be a PHP error, possibly caused by changes to the database', 'db' );
- }
- }
- return false;
- }
- /**
- * Search through the file name passed for a set of defines used to set up
- * WordPress db access.
- *
- * @param string $filename The file name we need to scan for the defines.
- *
- * @return array List of db connection details.
- */
- public function define_find( $filename = 'wp-config.php' ) {
- if ( $filename == 'wp-config.php' ) {
- $filename = dirname( __FILE__ ) . '/' . basename( $filename );
- // look up one directory if config file doesn't exist in current directory
- if ( ! file_exists( $filename ) )
- $filename = dirname( __FILE__ ) . '/../' . basename( $filename );
- }
- if ( file_exists( $filename ) && is_file( $filename ) && is_readable( $filename ) ) {
- $file = @fopen( $filename, 'r' );
- $file_content = fread( $file, filesize( $filename ) );
- @fclose( $file );
- }
- preg_match_all( '/define\s*?\(\s*?([\'"])(DB_NAME|DB_USER|DB_PASSWORD|DB_HOST|DB_CHARSET|DB_COLLATE)\1\s*?,\s*?([\'"])([^\3]*?)\3\s*?\)\s*?;/si', $file_content, $defines );
- if ( ( isset( $defines[ 2 ] ) && ! empty( $defines[ 2 ] ) ) && ( isset( $defines[ 4 ] ) && ! empty( $defines[ 4 ] ) ) ) {
- foreach( $defines[ 2 ] as $key => $define ) {
- switch( $define ) {
- case 'DB_NAME':
- $name = $defines[ 4 ][ $key ];
- break;
- case 'DB_USER':
- $user = $defines[ 4 ][ $key ];
- break;
- case 'DB_PASSWORD':
- $pass = $defines[ 4 ][ $key ];
- break;
- case 'DB_HOST':
- $host = $defines[ 4 ][ $key ];
- break;
- case 'DB_CHARSET':
- $char = $defines[ 4 ][ $key ];
- break;
- case 'DB_COLLATE':
- $coll = $defines[ 4 ][ $key ];
- break;
- }
- }
- }
- return array(
- 'host' => $host,
- 'name' => $name,
- 'user' => $user,
- 'pass' => $pass,
- 'char' => $char,
- 'coll' => $coll
- );
- }
- /**
- * Display the current url
- *
- */
- public function self_link() {
- return 'http://' . $_SERVER[ 'HTTP_HOST' ] . rtrim( $_SERVER[ 'REQUEST_URI' ], '/' );
- }
- /**
- * Simple html escaping
- *
- * @param string $string Thing that needs escaping
- * @param bool $echo Do we echo or return?
- *
- * @return string Escaped string.
- */
- public function esc_html_attr( $string = '', $echo = false ) {
- $output = htmlentities( $string, ENT_QUOTES, 'UTF-8' );
- if ( $echo )
- echo $output;
- else
- return $output;
- }
- public function checked( $value, $value2, $echo = true ) {
- $output = $value == $value2 ? ' checked="checked"' : '';
- if ( $echo )
- echo $output;
- return $output;
- }
- public function selected( $value, $value2, $echo = true ) {
- $output = $value == $value2 ? ' selected="selected"' : '';
- if ( $echo )
- echo $output;
- return $output;
- }
- public function get_errors( $type ) {
- if ( ! isset( $this->errors[ $type ] ) || ! count( $this->errors[ $type ] ) )
- return;
- echo '<div class="errors">';
- foreach( $this->errors[ $type ] as $error ) {
- if ( $error instanceof Exception )
- echo '<p class="exception">' . $error->getMessage() . '</p>';
- elseif ( is_string( $error ) )
- echo $error;
- }
- echo '</div>';
- }
- public function get_report( $table = null ) {
- $report = $this->get( 'report' );
- if ( empty( $report ) )
- return;
- $dry_run = $this->get( 'dry_run' );
- $search = $this->get( 'search' );
- $replace = $this->get( 'replace' );
- // Calc the time taken.
- $time = array_sum( explode( ' ', $report[ 'end' ] ) ) - array_sum( explode( ' ', $report[ 'start' ] ) );
- $srch_rplc_input_phrase = $dry_run ?
- 'searching for <strong>"' . $search . '"</strong> (to be replaced by <strong>"' . $replace . '"</strong>)' :
- 'replacing <strong>"' . $search . '"</strong> with <strong>"' . $replace . '"</strong>';
- echo '
- <div class="report">';
- echo '
- <h2>Report</h2>';
- echo '
- <p>';
- printf(
- 'In the process of %s we scanned <strong>%d</strong> tables with a total of
- <strong>%d</strong> rows, <strong>%d</strong> cells %s changed.
- <strong>%d</strong> db updates were actually performed.
- It all took <strong>%f</strong> seconds.',
- $srch_rplc_input_phrase,
- $report[ 'tables' ],
- $report[ 'rows' ],
- $report[ 'change' ],
- $dry_run ? 'would have been' : 'were',
- $report[ 'updates' ],
- $time
- );
- echo '
- </p>';
- echo '
- <table class="table-reports">
- <thead>
- <tr>
- <th>Table</th>
- <th>Rows</th>
- <th>Cells changed</th>
- <th>Updates</th>
- <th>Seconds</th>
- </tr>
- </thead>
- <tbody>';
- foreach( $report[ 'table_reports' ] as $table => $t_report ) {
- $t_time = array_sum( explode( ' ', $t_report[ 'end' ] ) ) - array_sum( explode( ' ', $t_report[ 'start' ] ) );
- echo '
- <tr>';
- printf( '
- <th>%s:</th>
- <td>%d</td>
- <td>%d</td>
- <td>%d</td>
- <td>%f</td>',
- $table,
- $t_report[ 'rows' ],
- $t_report[ 'change' ],
- $t_report[ 'updates' ],
- $t_time
- );
- echo '
- </tr>';
- }
- echo '
- </tbody>
- </table>';
- echo '
- </div>';
- }
- public function table_select( $echo = true ) {
- $table_select = '';
- if ( ! empty( $this->all_tables ) ) {
- $table_select .= '<select name="tables[]" multiple="multiple">';
- foreach( $this->all_tables as $table ) {
- $size = $table[ 'Data_length' ] / 1000;
- $size_unit = 'kb';
- if ( $size > 1000 ) {
- $size = $size / 1000;
- $size_unit = 'Mb';
- }
- if ( $size > 1000 ) {
- $size = $size / 1000;
- $size_unit = 'Gb';
- }
- $size = number_format( $size, 2 ) . $size_unit;
- $rows = $table[ 'Rows' ] > 1 ? 'rows' : 'row';
- $table_select .= sprintf( '<option value="%s" %s>%s</option>',
- $this->esc_html_attr( $table[ 0 ], false ),
- $this->selected( true, in_array( $table[ 0 ], $this->tables ), false ),
- "{$table[0]}: {$table['Engine']}, rows: {$table['Rows']}, size: {$size}, collation: {$table['Collation']}, character_set: {$table['Character_set']}"
- );
- }
- $table_select .= '</select>';
- }
- if ( $echo )
- echo $table_select;
- return $table_select;
- }
- public function ui() {
- // Warn if we're running in safe mode as we'll probably time out.
- if ( ini_get( 'safe_mode' ) ) {
- ?>
- <div class="special-errors">
- <h4>Warning</h4>
- <?php echo printf( '<p>Safe mode is on so you may run into problems if it takes longer than %s seconds to process your request.</p>', ini_get( 'max_execution_time' ) ); ?>
- </div>
- <?php
- }
- ?>
- <form action="" method="post">
- <!-- 1. search/replace -->
- <fieldset class="row row-search">
- <h1>search<span>/</span>replace</h1>
- <?php $this->get_errors( 'search' ); ?>
- <div class="fields fields-large">
- <label for="search"><span class="label-text">replace</span> <span class="hide-if-regex-off regex-left">/</span><input id="search" type="text" placeholder="search for…" value="<?php $this->esc_html_attr( $this->search, true ); ?>" name="search" /><span class="hide-if-regex-off regex-right">/</span></label>
- <label for="replace"><span class="label-text">with</span> <input id="replace" type="text" placeholder="replace with…" value="<?php $this->esc_html_attr( $this->replace, true ); ?>" name="replace" /></label>
- <label for="regex" class="field-advanced"><input id="regex" type="checkbox" name="regex" value="1" <?php $this->checked( true, $this->regex ); ?> /> use regex</label>
- </div>
- <div class="fields field-advanced hide-if-regex-off">
- <label for="regex_i" class="field field-advanced"><input type="checkbox" name="regex_i" id="regex_i" value="1" <?php $this->checked( true, $this->regex_i ); ?> /> <abbr title="case insensitive">i</abbr></abbr></label>
- <label for="regex_m" class="field field-advanced"><input type="checkbox" name="regex_m" id="regex_m" value="1" <?php $this->checked( true, $this->regex_m ); ?> /> <abbr title="multiline">m</abbr></label>
- <label for="regex_s" class="field field-advanced"><input type="checkbox" name="regex_s" id="regex_s" value="1" <?php $this->checked( true, $this->regex_s ); ?> /> <abbr title="dot also matches newlines">s</abbr></label>
- <label for="regex_x" class="field field-advanced"><input type="checkbox" name="regex_x" id="regex_x" value="1" <?php $this->checked( true, $this->regex_x ); ?> /> <abbr title="extended mode">x</abbr></label>
- </div>
- </fieldset>
- <!-- 2. db details -->
- <fieldset class="row row-db">
- <h1>db details</h1>
- <?php $this->get_errors( 'environment' ); ?>
- <?php $this->get_errors( 'db' ); ?>
- <div class="fields fields-small">
- <div class="field field-short">
- <label for="name">name</label>
- <input id="name" name="name" type="text" value="<?php $this->esc_html_attr( $this->name, true ); ?>" />
- </div>
- <div class="field field-short">
- <label for="user">user</label>
- <input id="user" name="user" type="text" value="<?php $this->esc_html_attr( $this->user, true ); ?>" />
- </div>
- <div class="field field-short">
- <label for="pass">pass</label>
- <input id="pass" name="pass" type="text" value="<?php $this->esc_html_attr( $this->pass, true ); ?>" />
- </div>
- <div class="field field-short">
- <label for="host">host</label>
- <input id="host" name="host" type="text" value="<?php $this->esc_html_attr( $this->host, true ); ?>" />
- </div>
- </div>
- </fieldset>
- <!-- 3. tables -->
- <fieldset class="row row-tables">
- <h1>tables</h1>
- <?php $this->get_errors( 'tables' ); ?>
- <div class="fields">
- <div class="field radio">
- <label for="all_tables">
- <input id="all_tables" name="use_tables" value="all" type="radio" <?php if ( ! $this->db_valid() ) echo 'disabled="disabled"'; ?> <?php $this->checked( true, empty( $this->tables ) ); ?> />
- all tables
- </label>
- </div>
- <div class="field radio">
- <label for="subset_tables">
- <input id="subset_tables" name="use_tables" value="subset" type="radio" <?php if ( ! $this->db_valid() ) echo 'disabled="disabled"'; ?> <?php $this->checked( false, empty( $this->tables ) ); ?> />
- select tables
- </label>
- </div>
- <div class="field table-select hide-if-js"><?php $this->table_select(); ?></div>
- </div>
- <div class="fields field-advanced">
- <div class="field field-advanced field-medium">
- <label for="exclude_cols">columns to exclude (optional, comma separated)</label>
- <input id="exclude_cols" type="text" name="exclude_cols" value="<?php $this->esc_html_attr( implode( ',', $this->get( 'exclude_cols' ) ) ) ?>" placeholder="eg. guid" />
- </div>
- <div class="field field-advanced field-medium">
- <label for="include_cols">columns to include only (optional, comma separated)</label>
- <input id="include_cols" type="text" name="include_cols" value="<?php $this->esc_html_attr( implode( ',', $this->get( 'include_cols' ) ) ) ?>" placeholder="eg. post_content, post_excerpt" />
- </div>
- </div>
- </fieldset>
- <!-- 4. results -->
- <fieldset class="row row-results">
- <h1>actions</h1>
- <?php $this->get_errors( 'results' ); ?>
- <div class="fields">
- <span class="submit-group">
- <input type="submit" name="submit[update]" value="update details" />
- <input type="submit" name="submit[dryrun]" value="dry run" <?php if ( ! $this->db_valid() ) echo 'disabled="disabled"'; ?> class="db-required" />
- <input type="submit" name="submit[liverun]" value="live run" <?php if ( ! $this->db_valid() ) echo 'disabled="disabled"'; ?> class="db-required" />
- <span class="separator">/</span>
- </span>
- <span class="submit-group">
- <?php if ( in_array( 'InnoDB', $this->get( 'engines' ) ) ) { ?>
- <input type="submit" name="submit[innodb]" value="convert to innodb" <?php if ( ! $this->db_valid() ) echo 'disabled="disabled"'; ?> class="db-required secondary field-advanced" />
- <?php } ?>
- <input type="submit" name="submit[utf8]" value="convert to utf8 unicode" <?php if ( ! $this->db_valid() ) echo 'disabled="disabled"'; ?> class="db-required secondary field-advanced" />
- </span>
- </div>
- <?php $this->get_report(); ?>
- </fieldset>
- <!-- 5. branding -->
- <section class="row row-delete">
- <h1>delete</h1>
- <div class="fields">
- <p>
- <input type="submit" name="submit[delete]" value="delete me" />
- Once you’re done click the <strong>delete me</strong> button to secure your server
- </p>
- </div>
- </section>
- </form>
- <section class="help">
- <h1 class="branding">interconnect/it</h1>
- <h2>Safe Search and Replace on Database with Serialized Data v3.0.0</h2>
- <p>This developer/sysadmin tool carries out search/replace functions on MySQL DBs and can handle serialised PHP Arrays and Objects.</p>
- <p><strong class="red">WARNINGS!</strong>
- Ensure data is backed up.
- We take no responsibility for any damage caused by this script or its misuse.
- DB Connection Settings are auto-filled when WordPress or Drupal is detected but can be confused by commented out settings so CHECK!
- There is NO UNDO!
- Be careful running this script on a production server.</p>
- <h3>Don't Forget to Remove Me!</h3>
- <p>Delete this utility from your
- server after use by clicking the 'delete me' button. It represents a major security threat to your database if
- maliciously used.</p>
- <p>If you have feedback or want to contribute to this script click the delete button to find out how.</p>
- <p><em>We don't put links on the search replace UI itself to avoid seeing URLs for the script in our access logs.</em></p>
- <h3>Again, use Of This Script Is Entirely At Your Own Risk</h3>
- <p>The easiest and safest way to use this script is to copy your site's files and DB to a new location.
- You then, if required, fix up your .htaccess and wp-config.php appropriately. Once
- done, run this script, select your tables (in most cases all of them) and then
- enter the search replace strings. You can press back in your browser to do
- this several times, as may be required in some cases.</p>
- </section>
- <?php
- }
- public function deleted() {
- // obligatory marketing!
- // seriously though it's good stuff
- ?>
- <!-- 1. branding -->
- <section class="row row-branding">
- <h1><a href="http://interconnectit.com/" target="_blank">interconnect<span>/</span><strong>it</strong></a></h1>
- <?php $this->get_errors( 'delete' ); ?>
- <div class="content">
- <p>Thanks for using our search/replace tool! We’d really appreciate it if you took a
- minute to join our mailing list and check out some of our other products.</p>
- </div>
- </section>
- <!-- 2. subscribe -->
- <section class="row row-subscribe">
- <h1>newsletter</h1>
- <form action="http://interconnectit.us2.list-manage.com/subscribe/post" method="POST" class="fields fields-small">
- <input type="hidden" name="u" value="08ec797202866aded7b2619b2">
- <input type="hidden" name="id" value="538abe0a97">
- <div id="mergeTable" class="mergeTable">
- <div class="mergeRow dojoDndItem mergeRow-email field field-short" id="mergeRow-0">
- <label for="MERGE0"><strong>email address</strong> <span class="asterisk">*</span></label>
- <input type="email" autocapitalize="off" autocorrect="off" name="MERGE0" id="MERGE0" size="25" value="">
- </div>
- <div class="mergeRow dojoDndItem mergeRow-text field field-short" id="mergeRow-1">
- <label for="MERGE1">first name</label>
- <input type="text" name="MERGE1" id="MERGE1" size="25" value="">
- </div>
- <div class="mergeRow dojoDndItem mergeRow-text field field-short" id="mergeRow-2">
- <label for="MERGE2">last name</label>
- <input type="text" name="MERGE2" id="MERGE2" size="25" value="">
- </div>
- <div class="submit_container field field-short">
- <br />
- <input type="submit" name="submit" value="subscribe">
- </div>
- </div>
- </form>
- </section>
- <!-- 3. contribute -->
- <section class="row row-contribute">
- <h1>contribute</h1>
- <div class="content">
- <p>Got suggestions? Found a bug? Want to contribute code? <a href="https://github.com/interconnectit/search-replace-db">Join us on Github!</a></p>
- </div>
- </section>
- <section class="row row-blog">
- <h1>blogs</h1>
- <div class="content">
- <p><a href="http://interconnectit.com/blog/" target="_blank">We couldn't load our blog feed for some reason so here's a link instead!</a></p>
- </div>
- </section>
- <!-- 5. products -->
- <section class="row row-products">
- <h1>products</h1>
- <div class="content">
- <p><a href="http://interconnectit.com/products/" target="_blank">We couldn't load our product feed for some reason so here's a link instead!</a></p>
- </div>
- </section>
- <?php
- }
- public function html( $body ) {
- // html classes
- $classes = array( 'no-js' );
- $classes[] = $this->regex ? 'regex-on' : 'regex-off';
- ?><!DOCTYPE html>
- <html class="<?php echo implode( ' ', $classes ); ?>">
- <head>
- <script>var h = document.getElementsByTagName('html')[0];h.className = h.className.replace('no-js', 'js');</script>
- <title>interconnect/it : search replace db</title>
- <?php $this->css(); ?>
- <?php $this->js(); ?>
- </head>
- <body>
- <?php $this->$body(); ?>
- </body>
- </html>
- <?php
- }
- public function css() {
- ?>
- <style type="text/css">
- * { margin: 0; padding: 0; }
- ::-webkit-input-placeholder { /* WebKit browsers */
- color: #999;
- }
- :-moz-placeholder { /* Mozilla Firefox 4 to 18 */
- color: #999;
- }
- ::-moz-placeholder { /* Mozilla Firefox 19+ */
- color: #999;
- }
- :-ms-input-placeholder { /* Internet Explorer 10+ */
- color: #999;
- }
- .js .hide-if-js {
- display: none;
- }
- .no-js .hide-if-nojs {
- display: none;
- }
- .regex-off .hide-if-regex-off {
- display: none;
- }
- .regex-on .hide-if-regex-on {
- display: none;
- }
- html {
- background: #fff;
- font-size: 10px;
- border-top: 20px solid #de1301;
- }
- body {
- font-family: 'Gill Sans MT', 'Gill Sans', Calibri, sans-serif;
- font-size: 1.6rem;
- }
- h2,
- h3 {
- text-transform: uppercase;
- font-weight: normal;
- margin: 2.0rem 0 1.0rem;
- }
- label {
- cursor: pointer;
- }
- /*.row {
- background-color: rgba( 210, 0, 0, 1 );
- padding: 20px 40px;
- border: 0;
- overflow: hidden;
- }
- .row + .row {
- background-color: rgba( 210, 0, 0, .8 );
- }
- .row + .row + .row {
- background-color: rgba( 210, 0, 0, .6 );
- }
- .row + .row + .row + .row {
- background-color: rgba( 210, 0, 0, .4 );
- }
- .row + .row + .row + .row + .row {
- background-color: rgba( 210, 0, 0, .2 );
- }*/
- .row {
- background-color: rgba( 210, 210, 210, 1 );
- padding: 20px 40px;
- border: 0;
- overflow: hidden;
- }
- .row + .row {
- background-color: rgba( 210, 210, 210, .8 );
- }
- .row + .row + .row {
- background-color: rgba( 210, 210, 210, .6 );
- }
- .row + .row + .row + .row {
- background-color: rgba( 210, 210, 210, .4 );
- }
- .row + .row + .row + .row + .row {
- background-color: rgba( 210, 210, 210, .2 );
- }
- .row h1 {
- display: block;
- font-size: 4.0rem;
- font-weight: normal;
- margin: 15px 0 20px;
- float: left;
- }
- .row h1,
- .branding {
- width: 260px;
- background:
- url(data:image/png;base64,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