#!/usr/bin/env -S php -q . */ /** * 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:', 'Required. The hostname of the database server.', ], [ 'n:', 'name:', 'Required. Database name.', ], [ 'u:', 'user:', 'Required. Database user.', ], [ 'p:', 'pass:', 'Database user\'s password.', ], [ 'P:', '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.', ], [ 'w:', 'exclude-tables:', 'If set excluded the specified tables, comma separate for multuple 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', 'Treats value for -s or --search as a regular expression and -r or --replace as a regular expression replacement.', '[no value]' ], [ 'l:', 'page-size:', 'How rows to fetch at a time from a table.', ], [ 'z', 'dry-run', 'Prevents any updates happening so you can preview the number of changes to be made', '[no value]' ], [ '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:', 'Defaults to true, can be set to false to run script silently.', '[true|false]' ], [ '', 'debug:', 'Defaults to false, prints more verbose errors.', '[true|false]' ], [ '', 'ssl-key:', 'Define the path to the SSL KEY file.', ], [ '', 'ssl-cert:', 'Define the path to the SSL certificate file.', ], [ '', 'ssl-ca:', 'Define the path to the certificate authority file.', ], [ '', 'ssl-ca-dir:', 'Define the path to a directory that contains trusted SSL CA certificates in PEM format.', ], [ '', 'ssl-cipher:', 'Define the cipher to use for SSL.', ], [ '', 'ssl-check:', 'Check the SSL certificate, default to True.', '[true|false]' ], [ '', 'allow-old-php', 'Suppress the check for PHP version, use it at your own risk!' ], [ '', 'help', 'Displays this help message ;)', ], ); $required = array( 'h' => 'host', 'n' => 'name', 'u' => 'user' ); function strip_colons( $string ) { return str_replace( ':', '', $string ); } // store arg values $arg_count = $_SERVER['argc']; $args_array = $_SERVER['argv']; $short_opts = array_filter( array_column( $opts, 0 ) ); $short_opts_normal = array_map( 'strip_colons', $short_opts ); $long_opts = array_filter( array_column( $opts, 1 ) ); $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 "; foreach ( $opts as $argument ) { echo ' '; if ( $argument[0] ) { echo '-' . strip_colons( $argument[0] ) . ', '; } if ( $argument[1] ) { echo '--' . strip_colons( $argument[1] ) . ' '; } if ( isset( $argument[3] ) ) { echo $argument[3]; } echo "\n"; if ( $argument[2] ) { echo ' ' . wordwrap( $argument[2], 65, "\n " ); } echo "\n\n"; } echo "\nSearch-Replace-DB Copyright © 2020 Interconnect IT Limited This program comes with ABSOLUTELY NO WARRANTY; This is free software, and you are welcome to redistribute it under certain conditions; see README for details. "; exit; } // missing field flag, show all missing instead of 1 at a time $missing_arg = false; if ( ! extension_loaded( "mbstring" ) ) { fwrite( STDERR, "This script requires mbstring. Please install mbstring and try again.\n" ); exit ( 1 ); } // check required args are passed foreach ( $required as $short_opt => $long_opt ) { 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, 'ssl_check' => true, 'dry_run' => false, 'debug' => false, 'allow_old_php' => false ); if ( isset( $options['allow-old-php'] ) ) { $args['allow_old_php'] = true; } // 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 ]; } if ( in_array( $key, [ 'search', 'replace' ] ) && is_array( $jsonVal = @json_decode( $value, true ) ) ) { $args[ $key ] = $jsonVal; continue; } // 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 'debug': case 'ssl-check': case 'verbose': $value = (boolean) filter_var( $value, FILTER_VALIDATE_BOOLEAN ); break; } // change to underscores $key = str_replace( '-', '_', $key ); $args[ $key ] = $value; } if ( $args['allow_old_php'] === false ) { if ( version_compare( PHP_VERSION, '7.3' ) < 0 ) { fwrite( STDERR, "This script has been tested using PHP7.3 +, whereas your version is: " . PHP_VERSION . ". Although this script may work with older versions you do so at your own risk. Please update php and try again. \n" ); exit( 1 ); } } // 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; if ( is_array( $search ) ) { $search = implode( ' or ', $search ); } if ( is_array( $replace ) ) { $replace = implode( ' or ', $replace ); } $output .= "{$table}: replacing {$search} with {$replace}"; break; case 'search_replace_table_end': list( $table, $report ) = $args; $time = number_format( floatval( $report['end'] ) - floatval( $report['start'] ), 8 ); if ( $time < 0 ) { $time = $time * - 1; } $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; if ( is_array( $search ) ) { $search = implode( ' or ', $search ); } if ( is_array( $replace ) ) { $replace = implode( ' or ', $replace ); } $time = number_format( floatval( $report['end'] ) - floatval( $report['start'] ), 8 ); if ( $time < 0 ) { $time = $time * - 1; } $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"; }