PageRenderTime 336ms CodeModel.GetById 24ms app.highlight 278ms RepoModel.GetById 1ms app.codeStats 1ms

/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
   1<?php
   2
   3/**
   4 *
   5 * Safe Search and Replace on Database with Serialized Data v3.0.0
   6 *
   7 * This script is to solve the problem of doing database search and replace when
   8 * some data is stored within PHP serialized arrays or objects.
   9 *
  10 * For more information, see
  11 * http://interconnectit.com/124/search-and-replace-for-wordpress-databases/
  12 *
  13 * To contribute go to
  14 * http://github.com/interconnectit/search-replace-db
  15 *
  16 * To use, load the script on your server and point your web browser to it.
  17 * In some situations, consider using the command line interface version.
  18 *
  19 * BIG WARNING!  Take a backup first, and carefully test the results of this
  20 * code. If you don't, and you vape your data then you only have yourself to
  21 * blame. Seriously.  And if your English is bad and you don't fully
  22 * understand the instructions then STOP. Right there. Yes. Before you do any
  23 * damage.
  24 *
  25 * USE OF THIS SCRIPT IS ENTIRELY AT YOUR OWN RISK. I/We accept no liability
  26 * from its use.
  27 *
  28 * First Written 2009-05-25 by David Coveney of Interconnect IT Ltd (UK)
  29 * http://www.davidcoveney.com or http://interconnectit.com
  30 * and released under the GPL v3
  31 * ie, do what ever you want with the code, and we take no responsibility for it
  32 * OK? If you don't wish to take responsibility, hire us at Interconnect IT Ltd
  33 * on +44 (0)151 331 5140 and we will do the work for you at our hourly rate,
  34 * minimum 1hr
  35 *
  36 * License: GPL v3
  37 * License URL: http://www.gnu.org/copyleft/gpl.html
  38 *
  39 *
  40 * Version 3.0.0:
  41 * 		* Major overhaul
  42 * 		* Multibyte string replacements
  43 * 		* UI completely redesigned
  44 * 		* Removed all links from script until 'delete' has been clicked to avoid
  45 * 		  security risk from our access logs
  46 * 		* Search replace functionality moved to it's own separate class
  47 * 		* Replacements done table by table to avoid timeouts
  48 * 		* Convert tables to InnoDB
  49 * 		* Convert tables to utf8_unicode_ci
  50 * 		* Use PDO if available
  51 * 		* Preview/view changes
  52 * 		* Optionally use preg_replace()
  53 * 		* Scripts bootstraps WordPress/Drupal to avoid issues with unknown
  54 * 		  serialised objects/classes
  55 * 		* Added marketing stuff to deleted screen (sorry but we're running a
  56 * 		  business!)
  57 *
  58 * Version 2.2.0:
  59 * 		* Added remove script patch from David Anderson (wordshell.net)
  60 * 		* Added ability to replace strings with nothing
  61 *		* Copy changes
  62 * 		* Added code to recursive_unserialize_replace to deal with objects not
  63 * 		just arrays. This was submitted by Tina Matter.
  64 * 		ToDo: Test object handling. Not sure how it will cope with object in the
  65 * 		db created with classes that don't exist in anything but the base PHP.
  66 *
  67 * Version 2.1.0:
  68 *              - Changed to version 2.1.0
  69 *		* Following change by Sergei Biryukov - merged in and tested by Dave Coveney
  70 *              - Added Charset Support (tested with UTF-8, not tested on other charsets)
  71 *		* Following changes implemented by James Whitehead with thanks to all the commenters and feedback given!
  72 * 		- Removed PHP warnings if you go to step 3+ without DB details.
  73 * 		- Added options to skip changing the guid column. If there are other
  74 * 		columns that need excluding you can add them to the $exclude_cols global
  75 * 		array. May choose to add another option to the table select page to let
  76 * 		you add to this array from the front end.
  77 * 		- Minor tweak to label styling.
  78 * 		- Added comments to each of the functions.
  79 * 		- Removed a dead param from icit_srdb_replacer
  80 * Version 2.0.0:
  81 * 		- returned to using unserialize function to check if string is
  82 * 		serialized or not
  83 * 		- marked is_serialized_string function as deprecated
  84 * 		- changed form order to improve usability and make use on multisites a
  85 * 		bit less scary
  86 * 		- changed to version 2, as really should have done when the UI was
  87 * 		introduced
  88 * 		- added a recursive array walker to deal with serialized strings being
  89 * 		stored in serialized strings. Yes, really.
  90 * 		- changes by James R Whitehead (kudos for recursive walker) and David
  91 * 		Coveney 2011-08-26
  92 *  Version 1.0.2:
  93 *  	- typos corrected, button text tweak - David Coveney / Robert O'Rourke
  94 *  Version 1.0.1
  95 *  	- styling and form added by James R Whitehead.
  96 *
  97 *  Credits:  moz667 at gmail dot com for his recursive_array_replace posted at
  98 *            uk.php.net which saved me a little time - a perfect sample for me
  99 *            and seems to work in all cases.
 100 *
 101 */
 102
 103// always good here
 104header( 'HTTP/1.1 200 OK' );
 105header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1.
 106header('Pragma: no-cache'); // HTTP 1.0.
 107header('Expires: 0'); // Proxies.
 108
 109require_once( 'srdb.class.php' );
 110
 111class icit_srdb_ui extends icit_srdb {
 112
 113	/**
 114	 * @var string Root path of the CMS
 115	 */
 116	public $path;
 117
 118	public $is_wordpress = false;
 119	public $is_drupal = false;
 120
 121	public function __construct() {
 122
 123		// php 5.4 date timezone requirement, shouldn't affect anything
 124		date_default_timezone_set( 'Europe/London' );
 125
 126		// prevent fatals from hiding the UI
 127		register_shutdown_function( array( $this, 'fatal_handler' ) );
 128
 129		// flag to bootstrap WP or Drupal
 130		$bootstrap = true; // isset( $_GET[ 'bootstrap' ] );
 131
 132		// discover environment
 133		if ( $bootstrap && $this->is_wordpress() ) {
 134
 135			// prevent warnings if the charset and collate aren't defined
 136			if ( !defined( 'DB_CHARSET') ) {
 137				define( 'DB_CHARSET', 'utf8' );
 138			}
 139			if ( !defined( 'DB_COLLATE') ) {
 140				define( 'DB_COLLATE', '' );
 141			}
 142
 143			// populate db details
 144			$name 		= DB_NAME;
 145			$user 		= DB_USER;
 146			$pass 		= DB_PASSWORD;
 147			$host 		= DB_HOST;
 148			$charset 	= DB_CHARSET;
 149			$collate 	= DB_COLLATE;
 150
 151			$this->response( $name, $user, $pass, $host, $charset, $collate );
 152
 153		} elseif( $bootstrap && $this->is_drupal() ) {
 154
 155			$database = Database::getConnection();
 156			$database_opts = $database->getConnectionOptions();
 157
 158			// populate db details
 159			$name 		= $database_opts[ 'database' ];
 160			$user 		= $database_opts[ 'username' ];
 161			$pass 		= $database_opts[ 'password' ];
 162			$host 		= $database_opts[ 'host' ];
 163			$charset 	= 'utf8';
 164			$collate 	= '';
 165
 166		    $this->response( $name, $user, $pass, $host, $charset, $collate );
 167
 168		} else {
 169
 170			$this->response();
 171
 172		}
 173
 174	}
 175
 176
 177	public function response( $name = '', $user = '', $pass = '', $host = '127.0.0.1', $charset = 'utf8', $collate = '' ) {
 178
 179		// always override with post data
 180		if ( isset( $_POST[ 'name' ] ) ) {
 181			$name = $_POST[ 'name' ];	// your database
 182			$user = $_POST[ 'user' ];	// your db userid
 183			$pass = $_POST[ 'pass' ];	// your db password
 184			$host = $_POST[ 'host' ]; // normally localhost, but not necessarily.
 185			$charset = 'utf8'; // isset( $_POST[ 'char' ] ) ? stripcslashes( $_POST[ 'char' ] ) : '';	// your db charset
 186			$collate = '';
 187		}
 188
 189		// Search replace details
 190		$search = isset( $_POST[ 'search' ] ) ? $_POST[ 'search' ] : '';
 191		$replace = isset( $_POST[ 'replace' ] ) ? $_POST[ 'replace' ] : '';
 192
 193		// regex options
 194		$regex 	 = isset( $_POST[ 'regex' ] );
 195		$regex_i = isset( $_POST[ 'regex_i' ] );
 196		$regex_m = isset( $_POST[ 'regex_m' ] );
 197		$regex_s = isset( $_POST[ 'regex_s' ] );
 198		$regex_x = isset( $_POST[ 'regex_x' ] );
 199
 200		// Tables to scanned
 201		$tables = isset( $_POST[ 'tables' ] ) && is_array( $_POST[ 'tables' ] ) ? $_POST[ 'tables' ] : array( );
 202		if ( isset( $_POST[ 'use_tables' ] ) && $_POST[ 'use_tables' ] == 'all' )
 203			$tables = array();
 204
 205		// exclude / include columns
 206		$exclude_cols = isset( $_POST[ 'exclude_cols' ] ) ? $_POST[ 'exclude_cols' ] : array();
 207		$include_cols = isset( $_POST[ 'include_cols' ] ) ? $_POST[ 'include_cols' ] : array();
 208
 209		foreach( array( 'exclude_cols', 'include_cols' ) as $maybe_string_arg ) {
 210			if ( is_string( $$maybe_string_arg ) )
 211				$$maybe_string_arg = array_filter( array_map( 'trim', explode( ',', $$maybe_string_arg ) ) );
 212		}
 213
 214		// update class vars
 215		$vars = array(
 216			'name', 'user', 'pass', 'host',
 217			'charset', 'collate', 'tables',
 218			'search', 'replace',
 219			'exclude_cols', 'include_cols',
 220			'regex', 'regex_i', 'regex_m', 'regex_s', 'regex_x'
 221		);
 222
 223		foreach( $vars as $var ) {
 224			if ( isset( $$var ) )
 225				$this->set( $var, $$var );
 226		}
 227
 228		// are doing something?
 229		$show = '';
 230		if ( isset( $_POST[ 'submit' ] ) ) {
 231			if ( is_array( $_POST[ 'submit' ] ) )
 232				$show = key( $_POST[ 'submit' ] );
 233			if ( is_string( $_POST[ 'submit' ] ) )
 234				$show = preg_replace( '/submit\[([a-z0-9]+)\]/', '$1', $_POST[ 'submit' ] );
 235		}
 236
 237		// is it an AJAX call
 238		$ajax = isset( $_POST[ 'ajax' ] );
 239
 240		// body callback
 241		$html = 'ui';
 242
 243		switch( $show ) {
 244
 245			// remove search replace
 246			case 'delete':
 247
 248				// determine if it's the folder of compiled version
 249				if ( basename( __FILE__ ) == 'index.php' )
 250					$path = str_replace( basename( __FILE__ ), '', __FILE__ );
 251				else
 252					$path = __FILE__;
 253
 254				if ( $this->delete_script( $path ) ) {
 255					if ( is_file( __FILE__ ) && file_exists( __FILE__ ) )
 256						$this->add_error( 'Could not delete the search replace script. You will have to delete it manually', 'delete' );
 257					else
 258						$this->add_error( 'Search/Replace has been successfully removed from your server', 'delete' );
 259				} else {
 260					$this->add_error( 'Could not delete the search replace script automatically. You will have to delete it manually, sorry!', 'delete' );
 261				}
 262
 263				$html = 'deleted';
 264
 265				break;
 266
 267			case 'liverun':
 268
 269				// bsy-web, 20130621: Check live run was explicitly clicked and only set false then
 270				$this->set( 'dry_run', false );
 271
 272			case 'dryrun':
 273
 274				// build regex string
 275				// non UI implements can just pass in complete regex string
 276				if ( $this->regex ) {
 277					$mods = '';
 278					if ( $this->regex_i ) $mods .= 'i';
 279					if ( $this->regex_s ) $mods .= 's';
 280					if ( $this->regex_m ) $mods .= 'm';
 281					if ( $this->regex_x ) $mods .= 'x';
 282					$this->search = '/' . $this->search . '/' . $mods;
 283				}
 284
 285				// call search replace class
 286				$parent = parent::__construct( array(
 287					'name' => $this->get( 'name' ),
 288					'user' => $this->get( 'user' ),
 289					'pass' => $this->get( 'pass' ),
 290					'host' => $this->get( 'host' ),
 291					'search' => $this->get( 'search' ),
 292					'replace' => $this->get( 'replace' ),
 293					'tables' => $this->get( 'tables' ),
 294					'dry_run' => $this->get( 'dry_run' ),
 295					'regex' => $this->get( 'regex' ),
 296					'exclude_cols' => $this->get( 'exclude_cols' ),
 297					'include_cols' => $this->get( 'include_cols' )
 298				) );
 299
 300				break;
 301
 302			case 'innodb':
 303
 304				// call search replace class to alter engine
 305				$parent = parent::__construct( array(
 306					'name' => $this->get( 'name' ),
 307					'user' => $this->get( 'user' ),
 308					'pass' => $this->get( 'pass' ),
 309					'host' => $this->get( 'host' ),
 310					'tables' => $this->get( 'tables' ),
 311					'alter_engine' => 'InnoDB',
 312				) );
 313
 314				break;
 315
 316			case 'utf8':
 317
 318				// call search replace class to alter engine
 319				$parent = parent::__construct( array(
 320					'name' => $this->get( 'name' ),
 321					'user' => $this->get( 'user' ),
 322					'pass' => $this->get( 'pass' ),
 323					'host' => $this->get( 'host' ),
 324					'tables' => $this->get( 'tables' ),
 325					'alter_collation' => 'utf8_unicode_ci',
 326				) );
 327
 328				break;
 329
 330			case 'update':
 331			default:
 332
 333				// get tables or error messages
 334				$this->db_setup();
 335
 336				if ( $this->db_valid() ) {
 337
 338					// get engines
 339					$this->set( 'engines', $this->get_engines() );
 340
 341					// get tables
 342					$this->set( 'all_tables', $this->get_tables() );
 343
 344				}
 345
 346				break;
 347		}
 348
 349		$info = array(
 350			'table_select' => $this->table_select( false ),
 351			'engines' => $this->get( 'engines' )
 352		);
 353
 354		// set header again before output in case WP does it's thing
 355		header( 'HTTP/1.1 200 OK' );
 356
 357		if ( ! $ajax ) {
 358			$this->html( $html );
 359		} else {
 360
 361			// return json version of results
 362			header( 'Content-Type: application/json' );
 363
 364			echo json_encode( array(
 365				'errors' => $this->get( 'errors' ),
 366				'report' => $this->get( 'report' ),
 367				'info' 	 => $info
 368			) );
 369
 370			exit;
 371
 372		}
 373
 374	}
 375
 376
 377	public function exceptions( $exception ) {
 378		$this->add_error( '<p class="exception">' . $exception->getMessage() . '</p>' );
 379	}
 380
 381	public function errors( $no, $message, $file, $line ) {
 382		$this->add_error( '<p class="error">' . "<strong>{$no}:</strong> {$message} in {$file} on line {$line}" . '</p>', 'results' );
 383	}
 384
 385	public function fatal_handler() {
 386	    $error = error_get_last();
 387
 388		if( $error !== NULL ) {
 389			$errno   = $error["type"];
 390			$errfile = $error["file"];
 391			$errline = $error["line"];
 392			$errstr  = $error["message"];
 393
 394			if ( $errno == 1 ) {
 395				header( 'HTTP/1.1 200 OK' );
 396				$this->add_error( '<p class="error">Could not bootstrap environment.<br /> ' . "Fatal error in {$errfile}, line {$errline}. {$errstr}" . '</p>', 'environment' );
 397				$this->response();
 398			}
 399		}
 400	}
 401
 402
 403	/**
 404	 * http://stackoverflow.com/questions/3349753/delete-directory-with-files-in-it
 405	 *
 406	 * @param string $path directory/file path
 407	 *
 408	 * @return void
 409	 */
 410	public function delete_script( $path ) {
 411		return is_file( $path ) ?
 412				@unlink( $path ) :
 413				array_map( array( $this, __FUNCTION__ ), glob( $path . '/*' ) ) == @rmdir( $path );
 414	}
 415
 416
 417	/**
 418	 * Attempts to detect a WordPress installation and bootstraps the environment with it
 419	 *
 420	 * @return bool    Whether it is a WP install and we have database credentials
 421	 */
 422	public function is_wordpress() {
 423
 424		$path_mod = '';
 425		$depth = 0;
 426		$max_depth = 4;
 427		$bootstrap_file = 'wp-blog-header.php';
 428
 429		while( ! file_exists( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" ) ) {
 430			$path_mod .= '/..';
 431			if ( $depth++ >= $max_depth )
 432				break;
 433		}
 434
 435		if ( file_exists( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" ) ) {
 436
 437			// store WP path
 438			$this->path = dirname( __FILE__ ) . $path_mod;
 439
 440			// just in case we're white screening
 441			try {
 442				// need to make as many of the globals available as possible or things can break
 443				// (globals suck)
 444				global $wp, $wpdb, $wp_query, $wp_the_query, $wp_version,
 445					$wp_db_version, $tinymce_version, $manifest_version,
 446					$required_php_version, $required_mysql_version,
 447					$post, $posts, $wp_locale, $authordata, $more, $numpages,
 448					$currentday, $currentmonth, $page, $pages, $multipage,
 449					$wp_rewrite, $wp_filesystem, $blog_id, $request,
 450					$wp_styles, $wp_taxonomies, $wp_post_types, $wp_filter,
 451					$wp_object_cache, $query_string, $single, $post_type,
 452					$is_iphone, $is_chrome, $is_safari, $is_NS4, $is_opera,
 453					$is_macIE, $is_winIE, $is_gecko, $is_lynx, $is_IE,
 454					$is_apache, $is_iis7, $is_IIS;
 455
 456				// prevent multisite redirect
 457				define( 'WP_INSTALLING', true );
 458
 459				// prevent super/total cache
 460				define( 'DONOTCACHEDB', true );
 461				define( 'DONOTCACHEPAGE', true );
 462				define( 'DONOTCACHEOBJECT', true );
 463				define( 'DONOTCDN', true );
 464				define( 'DONOTMINIFY', true );
 465
 466				// cancel batcache
 467				if ( function_exists( 'batcache_cancel' ) )
 468					batcache_cancel();
 469
 470				// bootstrap WordPress
 471				require( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" );
 472
 473				$this->set( 'path', ABSPATH );
 474
 475				$this->set( 'is_wordpress', true );
 476
 477				return true;
 478
 479			} catch( Exception $error ) {
 480
 481				// try and get database values using regex approach
 482				$db_details = $this->define_find( $this->path . '/wp-config.php' );
 483
 484				if ( $db_details ) {
 485
 486					define( 'DB_NAME', $db_details[ 'name' ] );
 487					define( 'DB_USER', $db_details[ 'user' ] );
 488					define( 'DB_PASSWORD', $db_details[ 'pass' ] );
 489					define( 'DB_HOST', $db_details[ 'host' ] );
 490					define( 'DB_CHARSET', $db_details[ 'char' ] );
 491					define( 'DB_COLLATE', $db_details[ 'coll' ] );
 492
 493					// additional error message
 494					$this->add_error( 'WordPress detected but could not bootstrap environment. There might be a PHP error, possibly caused by changes to the database', 'db' );
 495
 496				}
 497
 498				if ( $db_details )
 499					return true;
 500
 501			}
 502
 503		}
 504
 505		return false;
 506	}
 507
 508
 509	public function is_drupal() {
 510
 511		$path_mod = '';
 512		$depth = 0;
 513		$max_depth = 4;
 514		$bootstrap_file = 'includes/bootstrap.inc';
 515
 516		while( ! file_exists( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" ) ) {
 517			$path_mod .= '/..';
 518			if ( $depth++ >= $max_depth )
 519				break;
 520		}
 521
 522		if ( file_exists( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" ) ) {
 523
 524			try {
 525				// require the bootstrap include
 526				require_once( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" );
 527
 528				// define drupal root
 529				if ( ! defined( 'DRUPAL_ROOT' ) )
 530					define( 'DRUPAL_ROOT', dirname( __FILE__ ) . $path_mod );
 531
 532				// load drupal
 533				drupal_bootstrap( DRUPAL_BOOTSTRAP_FULL );
 534
 535				// confirm environment
 536				$this->set( 'is_drupal', true );
 537
 538				return true;
 539
 540			} catch( Exception $error ) {
 541
 542				$this->add_error( 'Drupal detected but could not bootstrap environment. There might be a PHP error, possibly caused by changes to the database', 'db' );
 543
 544			}
 545
 546		}
 547
 548		return false;
 549	}
 550
 551
 552	/**
 553	 * Search through the file name passed for a set of defines used to set up
 554	 * WordPress db access.
 555	 *
 556	 * @param string $filename The file name we need to scan for the defines.
 557	 *
 558	 * @return array    List of db connection details.
 559	 */
 560	public function define_find( $filename = 'wp-config.php' ) {
 561
 562		if ( $filename == 'wp-config.php' ) {
 563			$filename = dirname( __FILE__ ) . '/' . basename( $filename );
 564
 565			// look up one directory if config file doesn't exist in current directory
 566			if ( ! file_exists( $filename ) )
 567				$filename = dirname( __FILE__ ) . '/../' . basename( $filename );
 568		}
 569
 570		if ( file_exists( $filename ) && is_file( $filename ) && is_readable( $filename ) ) {
 571			$file = @fopen( $filename, 'r' );
 572			$file_content = fread( $file, filesize( $filename ) );
 573			@fclose( $file );
 574		}
 575
 576		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 );
 577
 578		if ( ( isset( $defines[ 2 ] ) && ! empty( $defines[ 2 ] ) ) && ( isset( $defines[ 4 ] ) && ! empty( $defines[ 4 ] ) ) ) {
 579			foreach( $defines[ 2 ] as $key => $define ) {
 580
 581				switch( $define ) {
 582					case 'DB_NAME':
 583						$name = $defines[ 4 ][ $key ];
 584						break;
 585					case 'DB_USER':
 586						$user = $defines[ 4 ][ $key ];
 587						break;
 588					case 'DB_PASSWORD':
 589						$pass = $defines[ 4 ][ $key ];
 590						break;
 591					case 'DB_HOST':
 592						$host = $defines[ 4 ][ $key ];
 593						break;
 594					case 'DB_CHARSET':
 595						$char = $defines[ 4 ][ $key ];
 596						break;
 597					case 'DB_COLLATE':
 598						$coll = $defines[ 4 ][ $key ];
 599						break;
 600				}
 601			}
 602		}
 603
 604		return array(
 605			'host' => $host,
 606			'name' => $name,
 607			'user' => $user,
 608			'pass' => $pass,
 609			'char' => $char,
 610			'coll' => $coll
 611		);
 612	}
 613
 614
 615	/**
 616	 * Display the current url
 617	 *
 618	 */
 619	public function self_link() {
 620		return 'http://' . $_SERVER[ 'HTTP_HOST' ] . rtrim( $_SERVER[ 'REQUEST_URI' ], '/' );
 621	}
 622
 623
 624	/**
 625	* Simple html escaping
 626	*
 627	* @param string $string Thing that needs escaping
 628	* @param bool $echo   Do we echo or return?
 629	*
 630	* @return string    Escaped string.
 631	*/
 632	public function esc_html_attr( $string = '', $echo = false ) {
 633		$output = htmlentities( $string, ENT_QUOTES, 'UTF-8' );
 634		if ( $echo )
 635			echo $output;
 636		else
 637			return $output;
 638	}
 639
 640	public function checked( $value, $value2, $echo = true ) {
 641		$output = $value == $value2 ? ' checked="checked"' : '';
 642		if ( $echo )
 643			echo $output;
 644		return $output;
 645	}
 646
 647	public function selected( $value, $value2, $echo = true ) {
 648		$output = $value == $value2 ? ' selected="selected"' : '';
 649		if ( $echo )
 650			echo $output;
 651		return $output;
 652	}
 653
 654
 655	public function get_errors( $type ) {
 656		if ( ! isset( $this->errors[ $type ] ) || ! count( $this->errors[ $type ] ) )
 657			return;
 658
 659		echo '<div class="errors">';
 660		foreach( $this->errors[ $type ] as $error ) {
 661			if ( $error instanceof Exception )
 662				echo '<p class="exception">' . $error->getMessage() . '</p>';
 663			elseif ( is_string( $error ) )
 664				echo $error;
 665		}
 666		echo '</div>';
 667	}
 668
 669
 670	public function get_report( $table = null ) {
 671
 672		$report = $this->get( 'report' );
 673
 674		if ( empty( $report ) )
 675			return;
 676
 677		$dry_run = $this->get( 'dry_run' );
 678		$search = $this->get( 'search' );
 679		$replace = $this->get( 'replace' );
 680
 681		// Calc the time taken.
 682		$time = array_sum( explode( ' ', $report[ 'end' ] ) ) - array_sum( explode( ' ', $report[ 'start' ] ) );
 683
 684		$srch_rplc_input_phrase = $dry_run ?
 685			'searching for <strong>"' . $search . '"</strong> (to be replaced by <strong>"' . $replace . '"</strong>)' :
 686			'replacing <strong>"' . $search . '"</strong> with <strong>"' . $replace . '"</strong>';
 687
 688		echo '
 689		<div class="report">';
 690
 691		echo '
 692			<h2>Report</h2>';
 693
 694		echo '
 695			<p>';
 696		printf(
 697			'In the process of %s we scanned <strong>%d</strong> tables with a total of
 698			<strong>%d</strong> rows, <strong>%d</strong> cells %s changed.
 699			<strong>%d</strong> db updates were actually performed.
 700			It all took <strong>%f</strong> seconds.',
 701			$srch_rplc_input_phrase,
 702			$report[ 'tables' ],
 703			$report[ 'rows' ],
 704			$report[ 'change' ],
 705			$dry_run ? 'would have been' : 'were',
 706			$report[ 'updates' ],
 707			$time
 708		);
 709		echo '
 710			</p>';
 711
 712		echo '
 713			<table class="table-reports">
 714				<thead>
 715					<tr>
 716						<th>Table</th>
 717						<th>Rows</th>
 718						<th>Cells changed</th>
 719						<th>Updates</th>
 720						<th>Seconds</th>
 721					</tr>
 722				</thead>
 723				<tbody>';
 724		foreach( $report[ 'table_reports' ] as $table => $t_report ) {
 725
 726			$t_time = array_sum( explode( ' ', $t_report[ 'end' ] ) ) - array_sum( explode( ' ', $t_report[ 'start' ] ) );
 727
 728			echo '
 729					<tr>';
 730			printf( '
 731						<th>%s:</th>
 732						<td>%d</td>
 733						<td>%d</td>
 734						<td>%d</td>
 735						<td>%f</td>',
 736				$table,
 737				$t_report[ 'rows' ],
 738				$t_report[ 'change' ],
 739				$t_report[ 'updates' ],
 740				$t_time
 741			);
 742			echo '
 743					</tr>';
 744
 745		}
 746		echo '
 747				</tbody>
 748			</table>';
 749
 750		echo '
 751		</div>';
 752
 753	}
 754
 755
 756	public function table_select( $echo = true ) {
 757
 758		$table_select = '';
 759
 760		if ( ! empty( $this->all_tables ) ) {
 761			$table_select .= '<select name="tables[]" multiple="multiple">';
 762			foreach( $this->all_tables as $table ) {
 763				$size = $table[ 'Data_length' ] / 1000;
 764				$size_unit = 'kb';
 765				if ( $size > 1000 ) {
 766					$size = $size / 1000;
 767					$size_unit = 'Mb';
 768				}
 769				if ( $size > 1000 ) {
 770					$size = $size / 1000;
 771					$size_unit = 'Gb';
 772				}
 773				$size = number_format( $size, 2 ) . $size_unit;
 774				$rows = $table[ 'Rows' ] > 1 ? 'rows' : 'row';
 775
 776				$table_select .= sprintf( '<option value="%s" %s>%s</option>',
 777							 $this->esc_html_attr( $table[ 0 ], false ),
 778							 $this->selected( true, in_array( $table[ 0 ], $this->tables ), false ),
 779							 "{$table[0]}: {$table['Engine']}, rows: {$table['Rows']}, size: {$size}, collation: {$table['Collation']}, character_set: {$table['Character_set']}"
 780						);
 781			}
 782			$table_select .= '</select>';
 783		}
 784
 785		if ( $echo )
 786			echo $table_select;
 787		return $table_select;
 788	}
 789
 790
 791	public function ui() {
 792
 793		// Warn if we're running in safe mode as we'll probably time out.
 794		if ( ini_get( 'safe_mode' ) ) {
 795			?>
 796			<div class="special-errors">
 797				<h4>Warning</h4>
 798				<?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' ) ); ?>
 799			</div>
 800			<?php
 801		}
 802
 803		?>
 804		<form action="" method="post">
 805
 806			<!-- 1. search/replace -->
 807			<fieldset class="row row-search">
 808
 809				<h1>search<span>/</span>replace</h1>
 810
 811				<?php $this->get_errors( 'search' ); ?>
 812
 813				<div class="fields fields-large">
 814					<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&hellip;" value="<?php $this->esc_html_attr( $this->search, true ); ?>" name="search" /><span class="hide-if-regex-off regex-right">/</span></label>
 815					<label for="replace"><span class="label-text">with</span> <input id="replace" type="text" placeholder="replace with&hellip;" value="<?php $this->esc_html_attr( $this->replace, true ); ?>" name="replace" /></label>
 816					<label for="regex" class="field-advanced"><input id="regex" type="checkbox" name="regex" value="1" <?php $this->checked( true, $this->regex ); ?> /> use regex</label>
 817				</div>
 818
 819				<div class="fields field-advanced hide-if-regex-off">
 820					<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>
 821					<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>
 822					<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>
 823					<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>
 824				</div>
 825
 826			</fieldset>
 827
 828			<!-- 2. db details -->
 829			<fieldset class="row row-db">
 830
 831				<h1>db details</h1>
 832
 833				<?php $this->get_errors( 'environment' ); ?>
 834
 835				<?php $this->get_errors( 'db' ); ?>
 836
 837				<div class="fields fields-small">
 838
 839					<div class="field field-short">
 840						<label for="name">name</label>
 841						<input id="name" name="name" type="text" value="<?php $this->esc_html_attr( $this->name, true ); ?>" />
 842					</div>
 843
 844					<div class="field field-short">
 845						<label for="user">user</label>
 846						<input id="user" name="user" type="text" value="<?php $this->esc_html_attr( $this->user, true ); ?>" />
 847					</div>
 848
 849					<div class="field field-short">
 850						<label for="pass">pass</label>
 851						<input id="pass" name="pass" type="text" value="<?php $this->esc_html_attr( $this->pass, true ); ?>" />
 852					</div>
 853
 854					<div class="field field-short">
 855						<label for="host">host</label>
 856						<input id="host" name="host" type="text" value="<?php $this->esc_html_attr( $this->host, true ); ?>" />
 857					</div>
 858
 859				</div>
 860
 861			</fieldset>
 862
 863			<!-- 3. tables -->
 864			<fieldset class="row row-tables">
 865
 866				<h1>tables</h1>
 867
 868				<?php $this->get_errors( 'tables' ); ?>
 869
 870				<div class="fields">
 871
 872					<div class="field radio">
 873						<label for="all_tables">
 874							<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 ) ); ?> />
 875							all tables
 876						</label>
 877					</div>
 878
 879					<div class="field radio">
 880						<label for="subset_tables">
 881							<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 ) ); ?> />
 882							select tables
 883						</label>
 884					</div>
 885
 886					<div class="field table-select hide-if-js"><?php $this->table_select(); ?></div>
 887
 888				</div>
 889
 890				<div class="fields field-advanced">
 891
 892					<div class="field field-advanced field-medium">
 893						<label for="exclude_cols">columns to exclude (optional, comma separated)</label>
 894						<input id="exclude_cols" type="text" name="exclude_cols" value="<?php $this->esc_html_attr( implode( ',', $this->get( 'exclude_cols' ) ) ) ?>" placeholder="eg. guid" />
 895					</div>
 896					<div class="field field-advanced field-medium">
 897						<label for="include_cols">columns to include only (optional, comma separated)</label>
 898						<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" />
 899					</div>
 900
 901				</div>
 902
 903			</fieldset>
 904
 905			<!-- 4. results -->
 906			<fieldset class="row row-results">
 907
 908				<h1>actions</h1>
 909
 910				<?php $this->get_errors( 'results' ); ?>
 911
 912				<div class="fields">
 913
 914					<span class="submit-group">
 915						<input type="submit" name="submit[update]" value="update details" />
 916
 917						<input type="submit" name="submit[dryrun]" value="dry run" <?php if ( ! $this->db_valid() ) echo 'disabled="disabled"'; ?> class="db-required" />
 918
 919						<input type="submit" name="submit[liverun]" value="live run" <?php if ( ! $this->db_valid() ) echo 'disabled="disabled"'; ?> class="db-required" />
 920
 921						<span class="separator">/</span>
 922					</span>
 923
 924					<span class="submit-group">
 925						<?php if ( in_array( 'InnoDB', $this->get( 'engines' ) ) ) { ?>
 926						<input type="submit" name="submit[innodb]" value="convert to innodb" <?php if ( ! $this->db_valid() ) echo 'disabled="disabled"'; ?> class="db-required secondary field-advanced" />
 927						<?php } ?>
 928
 929						<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" />
 930
 931					</span>
 932
 933				</div>
 934
 935				<?php $this->get_report(); ?>
 936
 937			</fieldset>
 938
 939
 940			<!-- 5. branding -->
 941			<section class="row row-delete">
 942
 943				<h1>delete</h1>
 944
 945				<div class="fields">
 946					<p>
 947						<input type="submit" name="submit[delete]" value="delete me" />
 948						Once you&rsquo;re done click the <strong>delete me</strong> button to secure your server
 949					</p>
 950				</div>
 951
 952			</section>
 953
 954		</form>
 955
 956		<section class="help">
 957
 958			<h1 class="branding">interconnect/it</h1>
 959
 960			<h2>Safe Search and Replace on Database with Serialized Data v3.0.0</h2>
 961
 962			<p>This developer/sysadmin tool carries out search/replace functions on MySQL DBs and can handle serialised PHP Arrays and Objects.</p>
 963
 964			<p><strong class="red">WARNINGS!</strong>
 965			Ensure data is backed up.
 966			We take no responsibility for any damage caused by this script or its misuse.
 967			DB Connection Settings are auto-filled when WordPress or Drupal is detected but can be confused by commented out settings so CHECK!
 968			There is NO UNDO!
 969			Be careful running this script on a production server.</p>
 970
 971			<h3>Don't Forget to Remove Me!</h3>
 972
 973			<p>Delete this utility from your
 974			server after use by clicking the 'delete me' button. It represents a major security threat to your database if
 975			maliciously used.</p>
 976
 977			<p>If you have feedback or want to contribute to this script click the delete button to find out how.</p>
 978
 979			<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>
 980
 981			<h3>Again, use Of This Script Is Entirely At Your Own Risk</h3>
 982
 983			<p>The easiest and safest way to use this script is to copy your site's files and DB to a new location.
 984			You then, if required, fix up your .htaccess and wp-config.php appropriately.  Once
 985			done, run this script, select your tables (in most cases all of them) and then
 986			enter the search replace strings.  You can press back in your browser to do
 987			this several times, as may be required in some cases.</p>
 988
 989		</section>
 990
 991		<?php
 992	}
 993
 994	public function deleted() {
 995
 996		// obligatory marketing!
 997		// seriously though it's good stuff
 998		?>
 999
1000		<!-- 1. branding -->
1001		<section class="row row-branding">
1002
1003			<h1><a href="http://interconnectit.com/" target="_blank">interconnect<span>/</span><strong>it</strong></a></h1>
1004
1005			<?php $this->get_errors( 'delete' ); ?>
1006
1007			<div class="content">
1008				<p>Thanks for using our search/replace tool! We&rsquo;d really appreciate it if you took a
1009					minute to join our mailing list and check out some of our other products.</p>
1010			</div>
1011
1012		</section>
1013
1014		<!-- 2. subscribe -->
1015		<section class="row row-subscribe">
1016
1017			<h1>newsletter</h1>
1018
1019			<form action="http://interconnectit.us2.list-manage.com/subscribe/post" method="POST" class="fields fields-small">
1020				<input type="hidden" name="u" value="08ec797202866aded7b2619b2">
1021				<input type="hidden" name="id" value="538abe0a97">
1022
1023				<div id="mergeTable" class="mergeTable">
1024
1025					<div class="mergeRow dojoDndItem mergeRow-email field field-short" id="mergeRow-0">
1026						<label for="MERGE0"><strong>email address</strong> <span class="asterisk">*</span></label>
1027						<input type="email" autocapitalize="off" autocorrect="off" name="MERGE0" id="MERGE0" size="25" value="">
1028					</div>
1029
1030					<div class="mergeRow dojoDndItem mergeRow-text field field-short" id="mergeRow-1">
1031						<label for="MERGE1">first name</label>
1032						<input type="text" name="MERGE1" id="MERGE1" size="25" value="">
1033					</div>
1034
1035					<div class="mergeRow dojoDndItem mergeRow-text field field-short" id="mergeRow-2">
1036						<label for="MERGE2">last name</label>
1037						<input type="text" name="MERGE2" id="MERGE2" size="25" value="">
1038					</div>
1039
1040					<div class="submit_container field field-short">
1041						<br />
1042						<input type="submit" name="submit" value="subscribe">
1043					</div>
1044
1045				</div>
1046			</form>
1047
1048		</section>
1049
1050		<!-- 3. contribute -->
1051		<section class="row row-contribute">
1052
1053			<h1>contribute</h1>
1054
1055			<div class="content">
1056
1057				<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>
1058
1059			</div>
1060
1061		</section>
1062
1063		<section class="row row-blog">
1064
1065			<h1>blogs</h1>
1066
1067			<div class="content">
1068				<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>
1069			</div>
1070
1071		</section>
1072
1073		<!-- 5. products -->
1074		<section class="row row-products">
1075
1076			<h1>products</h1>
1077
1078			<div class="content">
1079				<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>
1080			</div>
1081
1082		</section>
1083
1084
1085
1086		<?php
1087
1088	}
1089
1090	public function html( $body ) {
1091
1092		// html classes
1093		$classes = array( 'no-js' );
1094		$classes[] = $this->regex ? 'regex-on' : 'regex-off';
1095
1096		?><!DOCTYPE html>
1097<html class="<?php echo implode( ' ', $classes ); ?>">
1098	<head>
1099		<script>var h = document.getElementsByTagName('html')[0];h.className = h.className.replace('no-js', 'js');</script>
1100
1101		<title>interconnect/it : search replace db</title>
1102
1103		<?php $this->css(); ?>
1104		<?php $this->js(); ?>
1105
1106	</head>
1107	<body>
1108
1109		<?php $this->$body(); ?>
1110
1111
1112	</body>
1113</html>
1114		<?php
1115	}
1116
1117
1118
1119	public function css() {
1120		?>
1121		<style type="text/css">
1122* { margin: 0; padding: 0; }
1123
1124::-webkit-input-placeholder { /* WebKit browsers */
1125    color:    #999;
1126}
1127:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
1128    color:    #999;
1129}
1130::-moz-placeholder { /* Mozilla Firefox 19+ */
1131    color:    #999;
1132}
1133:-ms-input-placeholder { /* Internet Explorer 10+ */
1134    color:    #999;
1135}
1136
1137.js .hide-if-js {
1138	display: none;
1139}
1140.no-js .hide-if-nojs {
1141	display: none;
1142}
1143
1144.regex-off .hide-if-regex-off {
1145	display: none;
1146}
1147.regex-on .hide-if-regex-on {
1148	display: none;
1149}
1150
1151html {
1152	background: #fff;
1153	font-size: 10px;
1154	border-top: 20px solid #de1301;
1155}
1156
1157body {
1158	font-family: 'Gill Sans MT', 'Gill Sans', Calibri, sans-serif;
1159	font-size: 1.6rem;
1160}
1161
1162h2,
1163h3 {
1164	text-transform: uppercase;
1165	font-weight: normal;
1166	margin: 2.0rem 0 1.0rem;
1167}
1168
1169label {
1170	cursor: pointer;
1171}
1172
1173/*.row {
1174	background-color: rgba( 210, 0, 0, 1 );
1175	padding: 20px 40px;
1176	border: 0;
1177	overflow: hidden;
1178}
1179.row + .row {
1180	background-color: rgba( 210, 0, 0, .8 );
1181}
1182.row + .row + .row {
1183	background-color: rgba( 210, 0, 0, .6 );
1184}
1185.row + .row + .row + .row {
1186	background-color: rgba( 210, 0, 0, .4 );
1187}
1188.row + .row + .row + .row + .row {
1189	background-color: rgba( 210, 0, 0, .2 );
1190}*/
1191
1192.row {
1193	background-color: rgba( 210, 210, 210, 1 );
1194	padding: 20px 40px;
1195	border: 0;
1196	overflow: hidden;
1197}
1198.row + .row {
1199	background-color: rgba( 210, 210, 210, .8 );
1200}
1201.row + .row + .row {
1202	background-color: rgba( 210, 210, 210, .6 );
1203}
1204.row + .row + .row + .row {
1205	background-color: rgba( 210, 210, 210, .4 );
1206}
1207.row + .row + .row + .row + .row {
1208	background-color: rgba( 210, 210, 210, .2 );
1209}
1210
1211.row h1 {
1212	display: block;
1213	font-size: 4.0rem;
1214	font-weight: normal;
1215	margin: 15px 0 20px;
1216	float: left;
1217}
1218.row h1,
1219.branding {
1220	width: 260px;
1221	background:
1222		url(data:image/png;base64,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