/index.php
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…" 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…" 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’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’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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