PageRenderTime 46ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/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. * Safe Search and Replace on Database with Serialized Data v3.0.0
  5. *
  6. * This script is to solve the problem of doing database search and replace when
  7. * some data is stored within PHP serialized arrays or objects.
  8. *
  9. * For more information, see
  10. * http://interconnectit.com/124/search-and-replace-for-wordpress-databases/
  11. *
  12. * To contribute go to
  13. * http://github.com/interconnectit/search-replace-db
  14. *
  15. * To use, load the script on your server and point your web browser to it.
  16. * In some situations, consider using the command line interface version.
  17. *
  18. * BIG WARNING! Take a backup first, and carefully test the results of this
  19. * code. If you don't, and you vape your data then you only have yourself to
  20. * blame. Seriously. And if your English is bad and you don't fully
  21. * understand the instructions then STOP. Right there. Yes. Before you do any
  22. * damage.
  23. *
  24. * USE OF THIS SCRIPT IS ENTIRELY AT YOUR OWN RISK. I/We accept no liability
  25. * from its use.
  26. *
  27. * First Written 2009-05-25 by David Coveney of Interconnect IT Ltd (UK)
  28. * http://www.davidcoveney.com or http://interconnectit.com
  29. * and released under the GPL v3
  30. * ie, do what ever you want with the code, and we take no responsibility for it
  31. * OK? If you don't wish to take responsibility, hire us at Interconnect IT Ltd
  32. * on +44 (0)151 331 5140 and we will do the work for you at our hourly rate,
  33. * minimum 1hr
  34. *
  35. * License: GPL v3
  36. * License URL: http://www.gnu.org/copyleft/gpl.html
  37. *
  38. *
  39. * Version 3.0.0:
  40. * * Major overhaul
  41. * * Multibyte string replacements
  42. * * UI completely redesigned
  43. * * Removed all links from script until 'delete' has been clicked to avoid
  44. * security risk from our access logs
  45. * * Search replace functionality moved to it's own separate class
  46. * * Replacements done table by table to avoid timeouts
  47. * * Convert tables to InnoDB
  48. * * Convert tables to utf8_unicode_ci
  49. * * Use PDO if available
  50. * * Preview/view changes
  51. * * Optionally use preg_replace()
  52. * * Scripts bootstraps WordPress/Drupal to avoid issues with unknown
  53. * serialised objects/classes
  54. * * Added marketing stuff to deleted screen (sorry but we're running a
  55. * business!)
  56. *
  57. * Version 2.2.0:
  58. * * Added remove script patch from David Anderson (wordshell.net)
  59. * * Added ability to replace strings with nothing
  60. * * Copy changes
  61. * * Added code to recursive_unserialize_replace to deal with objects not
  62. * just arrays. This was submitted by Tina Matter.
  63. * ToDo: Test object handling. Not sure how it will cope with object in the
  64. * db created with classes that don't exist in anything but the base PHP.
  65. *
  66. * Version 2.1.0:
  67. * - Changed to version 2.1.0
  68. * * Following change by Sergei Biryukov - merged in and tested by Dave Coveney
  69. * - Added Charset Support (tested with UTF-8, not tested on other charsets)
  70. * * Following changes implemented by James Whitehead with thanks to all the commenters and feedback given!
  71. * - Removed PHP warnings if you go to step 3+ without DB details.
  72. * - Added options to skip changing the guid column. If there are other
  73. * columns that need excluding you can add them to the $exclude_cols global
  74. * array. May choose to add another option to the table select page to let
  75. * you add to this array from the front end.
  76. * - Minor tweak to label styling.
  77. * - Added comments to each of the functions.
  78. * - Removed a dead param from icit_srdb_replacer
  79. * Version 2.0.0:
  80. * - returned to using unserialize function to check if string is
  81. * serialized or not
  82. * - marked is_serialized_string function as deprecated
  83. * - changed form order to improve usability and make use on multisites a
  84. * bit less scary
  85. * - changed to version 2, as really should have done when the UI was
  86. * introduced
  87. * - added a recursive array walker to deal with serialized strings being
  88. * stored in serialized strings. Yes, really.
  89. * - changes by James R Whitehead (kudos for recursive walker) and David
  90. * Coveney 2011-08-26
  91. * Version 1.0.2:
  92. * - typos corrected, button text tweak - David Coveney / Robert O'Rourke
  93. * Version 1.0.1
  94. * - styling and form added by James R Whitehead.
  95. *
  96. * Credits: moz667 at gmail dot com for his recursive_array_replace posted at
  97. * uk.php.net which saved me a little time - a perfect sample for me
  98. * and seems to work in all cases.
  99. *
  100. */
  101. // always good here
  102. header( 'HTTP/1.1 200 OK' );
  103. header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1.
  104. header('Pragma: no-cache'); // HTTP 1.0.
  105. header('Expires: 0'); // Proxies.
  106. require_once( 'srdb.class.php' );
  107. class icit_srdb_ui extends icit_srdb {
  108. /**
  109. * @var string Root path of the CMS
  110. */
  111. public $path;
  112. public $is_wordpress = false;
  113. public $is_drupal = false;
  114. public function __construct() {
  115. // php 5.4 date timezone requirement, shouldn't affect anything
  116. date_default_timezone_set( 'Europe/London' );
  117. // prevent fatals from hiding the UI
  118. register_shutdown_function( array( $this, 'fatal_handler' ) );
  119. // flag to bootstrap WP or Drupal
  120. $bootstrap = true; // isset( $_GET[ 'bootstrap' ] );
  121. // discover environment
  122. if ( $bootstrap && $this->is_wordpress() ) {
  123. // prevent warnings if the charset and collate aren't defined
  124. if ( !defined( 'DB_CHARSET') ) {
  125. define( 'DB_CHARSET', 'utf8' );
  126. }
  127. if ( !defined( 'DB_COLLATE') ) {
  128. define( 'DB_COLLATE', '' );
  129. }
  130. // populate db details
  131. $name = DB_NAME;
  132. $user = DB_USER;
  133. $pass = DB_PASSWORD;
  134. $host = DB_HOST;
  135. $charset = DB_CHARSET;
  136. $collate = DB_COLLATE;
  137. $this->response( $name, $user, $pass, $host, $charset, $collate );
  138. } elseif( $bootstrap && $this->is_drupal() ) {
  139. $database = Database::getConnection();
  140. $database_opts = $database->getConnectionOptions();
  141. // populate db details
  142. $name = $database_opts[ 'database' ];
  143. $user = $database_opts[ 'username' ];
  144. $pass = $database_opts[ 'password' ];
  145. $host = $database_opts[ 'host' ];
  146. $charset = 'utf8';
  147. $collate = '';
  148. $this->response( $name, $user, $pass, $host, $charset, $collate );
  149. } else {
  150. $this->response();
  151. }
  152. }
  153. public function response( $name = '', $user = '', $pass = '', $host = '127.0.0.1', $charset = 'utf8', $collate = '' ) {
  154. // always override with post data
  155. if ( isset( $_POST[ 'name' ] ) ) {
  156. $name = $_POST[ 'name' ]; // your database
  157. $user = $_POST[ 'user' ]; // your db userid
  158. $pass = $_POST[ 'pass' ]; // your db password
  159. $host = $_POST[ 'host' ]; // normally localhost, but not necessarily.
  160. $charset = 'utf8'; // isset( $_POST[ 'char' ] ) ? stripcslashes( $_POST[ 'char' ] ) : ''; // your db charset
  161. $collate = '';
  162. }
  163. // Search replace details
  164. $search = isset( $_POST[ 'search' ] ) ? $_POST[ 'search' ] : '';
  165. $replace = isset( $_POST[ 'replace' ] ) ? $_POST[ 'replace' ] : '';
  166. // regex options
  167. $regex = isset( $_POST[ 'regex' ] );
  168. $regex_i = isset( $_POST[ 'regex_i' ] );
  169. $regex_m = isset( $_POST[ 'regex_m' ] );
  170. $regex_s = isset( $_POST[ 'regex_s' ] );
  171. $regex_x = isset( $_POST[ 'regex_x' ] );
  172. // Tables to scanned
  173. $tables = isset( $_POST[ 'tables' ] ) && is_array( $_POST[ 'tables' ] ) ? $_POST[ 'tables' ] : array( );
  174. if ( isset( $_POST[ 'use_tables' ] ) && $_POST[ 'use_tables' ] == 'all' )
  175. $tables = array();
  176. // exclude / include columns
  177. $exclude_cols = isset( $_POST[ 'exclude_cols' ] ) ? $_POST[ 'exclude_cols' ] : array();
  178. $include_cols = isset( $_POST[ 'include_cols' ] ) ? $_POST[ 'include_cols' ] : array();
  179. foreach( array( 'exclude_cols', 'include_cols' ) as $maybe_string_arg ) {
  180. if ( is_string( $$maybe_string_arg ) )
  181. $$maybe_string_arg = array_filter( array_map( 'trim', explode( ',', $$maybe_string_arg ) ) );
  182. }
  183. // update class vars
  184. $vars = array(
  185. 'name', 'user', 'pass', 'host',
  186. 'charset', 'collate', 'tables',
  187. 'search', 'replace',
  188. 'exclude_cols', 'include_cols',
  189. 'regex', 'regex_i', 'regex_m', 'regex_s', 'regex_x'
  190. );
  191. foreach( $vars as $var ) {
  192. if ( isset( $$var ) )
  193. $this->set( $var, $$var );
  194. }
  195. // are doing something?
  196. $show = '';
  197. if ( isset( $_POST[ 'submit' ] ) ) {
  198. if ( is_array( $_POST[ 'submit' ] ) )
  199. $show = key( $_POST[ 'submit' ] );
  200. if ( is_string( $_POST[ 'submit' ] ) )
  201. $show = preg_replace( '/submit\[([a-z0-9]+)\]/', '$1', $_POST[ 'submit' ] );
  202. }
  203. // is it an AJAX call
  204. $ajax = isset( $_POST[ 'ajax' ] );
  205. // body callback
  206. $html = 'ui';
  207. switch( $show ) {
  208. // remove search replace
  209. case 'delete':
  210. // determine if it's the folder of compiled version
  211. if ( basename( __FILE__ ) == 'index.php' )
  212. $path = str_replace( basename( __FILE__ ), '', __FILE__ );
  213. else
  214. $path = __FILE__;
  215. if ( $this->delete_script( $path ) ) {
  216. if ( is_file( __FILE__ ) && file_exists( __FILE__ ) )
  217. $this->add_error( 'Could not delete the search replace script. You will have to delete it manually', 'delete' );
  218. else
  219. $this->add_error( 'Search/Replace has been successfully removed from your server', 'delete' );
  220. } else {
  221. $this->add_error( 'Could not delete the search replace script automatically. You will have to delete it manually, sorry!', 'delete' );
  222. }
  223. $html = 'deleted';
  224. break;
  225. case 'liverun':
  226. // bsy-web, 20130621: Check live run was explicitly clicked and only set false then
  227. $this->set( 'dry_run', false );
  228. case 'dryrun':
  229. // build regex string
  230. // non UI implements can just pass in complete regex string
  231. if ( $this->regex ) {
  232. $mods = '';
  233. if ( $this->regex_i ) $mods .= 'i';
  234. if ( $this->regex_s ) $mods .= 's';
  235. if ( $this->regex_m ) $mods .= 'm';
  236. if ( $this->regex_x ) $mods .= 'x';
  237. $this->search = '/' . $this->search . '/' . $mods;
  238. }
  239. // call search replace class
  240. $parent = parent::__construct( array(
  241. 'name' => $this->get( 'name' ),
  242. 'user' => $this->get( 'user' ),
  243. 'pass' => $this->get( 'pass' ),
  244. 'host' => $this->get( 'host' ),
  245. 'search' => $this->get( 'search' ),
  246. 'replace' => $this->get( 'replace' ),
  247. 'tables' => $this->get( 'tables' ),
  248. 'dry_run' => $this->get( 'dry_run' ),
  249. 'regex' => $this->get( 'regex' ),
  250. 'exclude_cols' => $this->get( 'exclude_cols' ),
  251. 'include_cols' => $this->get( 'include_cols' )
  252. ) );
  253. break;
  254. case 'innodb':
  255. // call search replace class to alter engine
  256. $parent = parent::__construct( array(
  257. 'name' => $this->get( 'name' ),
  258. 'user' => $this->get( 'user' ),
  259. 'pass' => $this->get( 'pass' ),
  260. 'host' => $this->get( 'host' ),
  261. 'tables' => $this->get( 'tables' ),
  262. 'alter_engine' => 'InnoDB',
  263. ) );
  264. break;
  265. case 'utf8':
  266. // call search replace class to alter engine
  267. $parent = parent::__construct( array(
  268. 'name' => $this->get( 'name' ),
  269. 'user' => $this->get( 'user' ),
  270. 'pass' => $this->get( 'pass' ),
  271. 'host' => $this->get( 'host' ),
  272. 'tables' => $this->get( 'tables' ),
  273. 'alter_collation' => 'utf8_unicode_ci',
  274. ) );
  275. break;
  276. case 'update':
  277. default:
  278. // get tables or error messages
  279. $this->db_setup();
  280. if ( $this->db_valid() ) {
  281. // get engines
  282. $this->set( 'engines', $this->get_engines() );
  283. // get tables
  284. $this->set( 'all_tables', $this->get_tables() );
  285. }
  286. break;
  287. }
  288. $info = array(
  289. 'table_select' => $this->table_select( false ),
  290. 'engines' => $this->get( 'engines' )
  291. );
  292. // set header again before output in case WP does it's thing
  293. header( 'HTTP/1.1 200 OK' );
  294. if ( ! $ajax ) {
  295. $this->html( $html );
  296. } else {
  297. // return json version of results
  298. header( 'Content-Type: application/json' );
  299. echo json_encode( array(
  300. 'errors' => $this->get( 'errors' ),
  301. 'report' => $this->get( 'report' ),
  302. 'info' => $info
  303. ) );
  304. exit;
  305. }
  306. }
  307. public function exceptions( $exception ) {
  308. $this->add_error( '<p class="exception">' . $exception->getMessage() . '</p>' );
  309. }
  310. public function errors( $no, $message, $file, $line ) {
  311. $this->add_error( '<p class="error">' . "<strong>{$no}:</strong> {$message} in {$file} on line {$line}" . '</p>', 'results' );
  312. }
  313. public function fatal_handler() {
  314. $error = error_get_last();
  315. if( $error !== NULL ) {
  316. $errno = $error["type"];
  317. $errfile = $error["file"];
  318. $errline = $error["line"];
  319. $errstr = $error["message"];
  320. if ( $errno == 1 ) {
  321. header( 'HTTP/1.1 200 OK' );
  322. $this->add_error( '<p class="error">Could not bootstrap environment.<br /> ' . "Fatal error in {$errfile}, line {$errline}. {$errstr}" . '</p>', 'environment' );
  323. $this->response();
  324. }
  325. }
  326. }
  327. /**
  328. * http://stackoverflow.com/questions/3349753/delete-directory-with-files-in-it
  329. *
  330. * @param string $path directory/file path
  331. *
  332. * @return void
  333. */
  334. public function delete_script( $path ) {
  335. return is_file( $path ) ?
  336. @unlink( $path ) :
  337. array_map( array( $this, __FUNCTION__ ), glob( $path . '/*' ) ) == @rmdir( $path );
  338. }
  339. /**
  340. * Attempts to detect a WordPress installation and bootstraps the environment with it
  341. *
  342. * @return bool Whether it is a WP install and we have database credentials
  343. */
  344. public function is_wordpress() {
  345. $path_mod = '';
  346. $depth = 0;
  347. $max_depth = 4;
  348. $bootstrap_file = 'wp-blog-header.php';
  349. while( ! file_exists( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" ) ) {
  350. $path_mod .= '/..';
  351. if ( $depth++ >= $max_depth )
  352. break;
  353. }
  354. if ( file_exists( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" ) ) {
  355. // store WP path
  356. $this->path = dirname( __FILE__ ) . $path_mod;
  357. // just in case we're white screening
  358. try {
  359. // need to make as many of the globals available as possible or things can break
  360. // (globals suck)
  361. global $wp, $wpdb, $wp_query, $wp_the_query, $wp_version,
  362. $wp_db_version, $tinymce_version, $manifest_version,
  363. $required_php_version, $required_mysql_version,
  364. $post, $posts, $wp_locale, $authordata, $more, $numpages,
  365. $currentday, $currentmonth, $page, $pages, $multipage,
  366. $wp_rewrite, $wp_filesystem, $blog_id, $request,
  367. $wp_styles, $wp_taxonomies, $wp_post_types, $wp_filter,
  368. $wp_object_cache, $query_string, $single, $post_type,
  369. $is_iphone, $is_chrome, $is_safari, $is_NS4, $is_opera,
  370. $is_macIE, $is_winIE, $is_gecko, $is_lynx, $is_IE,
  371. $is_apache, $is_iis7, $is_IIS;
  372. // prevent multisite redirect
  373. define( 'WP_INSTALLING', true );
  374. // prevent super/total cache
  375. define( 'DONOTCACHEDB', true );
  376. define( 'DONOTCACHEPAGE', true );
  377. define( 'DONOTCACHEOBJECT', true );
  378. define( 'DONOTCDN', true );
  379. define( 'DONOTMINIFY', true );
  380. // cancel batcache
  381. if ( function_exists( 'batcache_cancel' ) )
  382. batcache_cancel();
  383. // bootstrap WordPress
  384. require( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" );
  385. $this->set( 'path', ABSPATH );
  386. $this->set( 'is_wordpress', true );
  387. return true;
  388. } catch( Exception $error ) {
  389. // try and get database values using regex approach
  390. $db_details = $this->define_find( $this->path . '/wp-config.php' );
  391. if ( $db_details ) {
  392. define( 'DB_NAME', $db_details[ 'name' ] );
  393. define( 'DB_USER', $db_details[ 'user' ] );
  394. define( 'DB_PASSWORD', $db_details[ 'pass' ] );
  395. define( 'DB_HOST', $db_details[ 'host' ] );
  396. define( 'DB_CHARSET', $db_details[ 'char' ] );
  397. define( 'DB_COLLATE', $db_details[ 'coll' ] );
  398. // additional error message
  399. $this->add_error( 'WordPress detected but could not bootstrap environment. There might be a PHP error, possibly caused by changes to the database', 'db' );
  400. }
  401. if ( $db_details )
  402. return true;
  403. }
  404. }
  405. return false;
  406. }
  407. public function is_drupal() {
  408. $path_mod = '';
  409. $depth = 0;
  410. $max_depth = 4;
  411. $bootstrap_file = 'includes/bootstrap.inc';
  412. while( ! file_exists( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" ) ) {
  413. $path_mod .= '/..';
  414. if ( $depth++ >= $max_depth )
  415. break;
  416. }
  417. if ( file_exists( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" ) ) {
  418. try {
  419. // require the bootstrap include
  420. require_once( dirname( __FILE__ ) . "{$path_mod}/{$bootstrap_file}" );
  421. // define drupal root
  422. if ( ! defined( 'DRUPAL_ROOT' ) )
  423. define( 'DRUPAL_ROOT', dirname( __FILE__ ) . $path_mod );
  424. // load drupal
  425. drupal_bootstrap( DRUPAL_BOOTSTRAP_FULL );
  426. // confirm environment
  427. $this->set( 'is_drupal', true );
  428. return true;
  429. } catch( Exception $error ) {
  430. $this->add_error( 'Drupal detected but could not bootstrap environment. There might be a PHP error, possibly caused by changes to the database', 'db' );
  431. }
  432. }
  433. return false;
  434. }
  435. /**
  436. * Search through the file name passed for a set of defines used to set up
  437. * WordPress db access.
  438. *
  439. * @param string $filename The file name we need to scan for the defines.
  440. *
  441. * @return array List of db connection details.
  442. */
  443. public function define_find( $filename = 'wp-config.php' ) {
  444. if ( $filename == 'wp-config.php' ) {
  445. $filename = dirname( __FILE__ ) . '/' . basename( $filename );
  446. // look up one directory if config file doesn't exist in current directory
  447. if ( ! file_exists( $filename ) )
  448. $filename = dirname( __FILE__ ) . '/../' . basename( $filename );
  449. }
  450. if ( file_exists( $filename ) && is_file( $filename ) && is_readable( $filename ) ) {
  451. $file = @fopen( $filename, 'r' );
  452. $file_content = fread( $file, filesize( $filename ) );
  453. @fclose( $file );
  454. }
  455. 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 );
  456. if ( ( isset( $defines[ 2 ] ) && ! empty( $defines[ 2 ] ) ) && ( isset( $defines[ 4 ] ) && ! empty( $defines[ 4 ] ) ) ) {
  457. foreach( $defines[ 2 ] as $key => $define ) {
  458. switch( $define ) {
  459. case 'DB_NAME':
  460. $name = $defines[ 4 ][ $key ];
  461. break;
  462. case 'DB_USER':
  463. $user = $defines[ 4 ][ $key ];
  464. break;
  465. case 'DB_PASSWORD':
  466. $pass = $defines[ 4 ][ $key ];
  467. break;
  468. case 'DB_HOST':
  469. $host = $defines[ 4 ][ $key ];
  470. break;
  471. case 'DB_CHARSET':
  472. $char = $defines[ 4 ][ $key ];
  473. break;
  474. case 'DB_COLLATE':
  475. $coll = $defines[ 4 ][ $key ];
  476. break;
  477. }
  478. }
  479. }
  480. return array(
  481. 'host' => $host,
  482. 'name' => $name,
  483. 'user' => $user,
  484. 'pass' => $pass,
  485. 'char' => $char,
  486. 'coll' => $coll
  487. );
  488. }
  489. /**
  490. * Display the current url
  491. *
  492. */
  493. public function self_link() {
  494. return 'http://' . $_SERVER[ 'HTTP_HOST' ] . rtrim( $_SERVER[ 'REQUEST_URI' ], '/' );
  495. }
  496. /**
  497. * Simple html escaping
  498. *
  499. * @param string $string Thing that needs escaping
  500. * @param bool $echo Do we echo or return?
  501. *
  502. * @return string Escaped string.
  503. */
  504. public function esc_html_attr( $string = '', $echo = false ) {
  505. $output = htmlentities( $string, ENT_QUOTES, 'UTF-8' );
  506. if ( $echo )
  507. echo $output;
  508. else
  509. return $output;
  510. }
  511. public function checked( $value, $value2, $echo = true ) {
  512. $output = $value == $value2 ? ' checked="checked"' : '';
  513. if ( $echo )
  514. echo $output;
  515. return $output;
  516. }
  517. public function selected( $value, $value2, $echo = true ) {
  518. $output = $value == $value2 ? ' selected="selected"' : '';
  519. if ( $echo )
  520. echo $output;
  521. return $output;
  522. }
  523. public function get_errors( $type ) {
  524. if ( ! isset( $this->errors[ $type ] ) || ! count( $this->errors[ $type ] ) )
  525. return;
  526. echo '<div class="errors">';
  527. foreach( $this->errors[ $type ] as $error ) {
  528. if ( $error instanceof Exception )
  529. echo '<p class="exception">' . $error->getMessage() . '</p>';
  530. elseif ( is_string( $error ) )
  531. echo $error;
  532. }
  533. echo '</div>';
  534. }
  535. public function get_report( $table = null ) {
  536. $report = $this->get( 'report' );
  537. if ( empty( $report ) )
  538. return;
  539. $dry_run = $this->get( 'dry_run' );
  540. $search = $this->get( 'search' );
  541. $replace = $this->get( 'replace' );
  542. // Calc the time taken.
  543. $time = array_sum( explode( ' ', $report[ 'end' ] ) ) - array_sum( explode( ' ', $report[ 'start' ] ) );
  544. $srch_rplc_input_phrase = $dry_run ?
  545. 'searching for <strong>"' . $search . '"</strong> (to be replaced by <strong>"' . $replace . '"</strong>)' :
  546. 'replacing <strong>"' . $search . '"</strong> with <strong>"' . $replace . '"</strong>';
  547. echo '
  548. <div class="report">';
  549. echo '
  550. <h2>Report</h2>';
  551. echo '
  552. <p>';
  553. printf(
  554. 'In the process of %s we scanned <strong>%d</strong> tables with a total of
  555. <strong>%d</strong> rows, <strong>%d</strong> cells %s changed.
  556. <strong>%d</strong> db updates were actually performed.
  557. It all took <strong>%f</strong> seconds.',
  558. $srch_rplc_input_phrase,
  559. $report[ 'tables' ],
  560. $report[ 'rows' ],
  561. $report[ 'change' ],
  562. $dry_run ? 'would have been' : 'were',
  563. $report[ 'updates' ],
  564. $time
  565. );
  566. echo '
  567. </p>';
  568. echo '
  569. <table class="table-reports">
  570. <thead>
  571. <tr>
  572. <th>Table</th>
  573. <th>Rows</th>
  574. <th>Cells changed</th>
  575. <th>Updates</th>
  576. <th>Seconds</th>
  577. </tr>
  578. </thead>
  579. <tbody>';
  580. foreach( $report[ 'table_reports' ] as $table => $t_report ) {
  581. $t_time = array_sum( explode( ' ', $t_report[ 'end' ] ) ) - array_sum( explode( ' ', $t_report[ 'start' ] ) );
  582. echo '
  583. <tr>';
  584. printf( '
  585. <th>%s:</th>
  586. <td>%d</td>
  587. <td>%d</td>
  588. <td>%d</td>
  589. <td>%f</td>',
  590. $table,
  591. $t_report[ 'rows' ],
  592. $t_report[ 'change' ],
  593. $t_report[ 'updates' ],
  594. $t_time
  595. );
  596. echo '
  597. </tr>';
  598. }
  599. echo '
  600. </tbody>
  601. </table>';
  602. echo '
  603. </div>';
  604. }
  605. public function table_select( $echo = true ) {
  606. $table_select = '';
  607. if ( ! empty( $this->all_tables ) ) {
  608. $table_select .= '<select name="tables[]" multiple="multiple">';
  609. foreach( $this->all_tables as $table ) {
  610. $size = $table[ 'Data_length' ] / 1000;
  611. $size_unit = 'kb';
  612. if ( $size > 1000 ) {
  613. $size = $size / 1000;
  614. $size_unit = 'Mb';
  615. }
  616. if ( $size > 1000 ) {
  617. $size = $size / 1000;
  618. $size_unit = 'Gb';
  619. }
  620. $size = number_format( $size, 2 ) . $size_unit;
  621. $rows = $table[ 'Rows' ] > 1 ? 'rows' : 'row';
  622. $table_select .= sprintf( '<option value="%s" %s>%s</option>',
  623. $this->esc_html_attr( $table[ 0 ], false ),
  624. $this->selected( true, in_array( $table[ 0 ], $this->tables ), false ),
  625. "{$table[0]}: {$table['Engine']}, rows: {$table['Rows']}, size: {$size}, collation: {$table['Collation']}, character_set: {$table['Character_set']}"
  626. );
  627. }
  628. $table_select .= '</select>';
  629. }
  630. if ( $echo )
  631. echo $table_select;
  632. return $table_select;
  633. }
  634. public function ui() {
  635. // Warn if we're running in safe mode as we'll probably time out.
  636. if ( ini_get( 'safe_mode' ) ) {
  637. ?>
  638. <div class="special-errors">
  639. <h4>Warning</h4>
  640. <?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' ) ); ?>
  641. </div>
  642. <?php
  643. }
  644. ?>
  645. <form action="" method="post">
  646. <!-- 1. search/replace -->
  647. <fieldset class="row row-search">
  648. <h1>search<span>/</span>replace</h1>
  649. <?php $this->get_errors( 'search' ); ?>
  650. <div class="fields fields-large">
  651. <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>
  652. <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>
  653. <label for="regex" class="field-advanced"><input id="regex" type="checkbox" name="regex" value="1" <?php $this->checked( true, $this->regex ); ?> /> use regex</label>
  654. </div>
  655. <div class="fields field-advanced hide-if-regex-off">
  656. <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>
  657. <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>
  658. <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>
  659. <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>
  660. </div>
  661. </fieldset>
  662. <!-- 2. db details -->
  663. <fieldset class="row row-db">
  664. <h1>db details</h1>
  665. <?php $this->get_errors( 'environment' ); ?>
  666. <?php $this->get_errors( 'db' ); ?>
  667. <div class="fields fields-small">
  668. <div class="field field-short">
  669. <label for="name">name</label>
  670. <input id="name" name="name" type="text" value="<?php $this->esc_html_attr( $this->name, true ); ?>" />
  671. </div>
  672. <div class="field field-short">
  673. <label for="user">user</label>
  674. <input id="user" name="user" type="text" value="<?php $this->esc_html_attr( $this->user, true ); ?>" />
  675. </div>
  676. <div class="field field-short">
  677. <label for="pass">pass</label>
  678. <input id="pass" name="pass" type="text" value="<?php $this->esc_html_attr( $this->pass, true ); ?>" />
  679. </div>
  680. <div class="field field-short">
  681. <label for="host">host</label>
  682. <input id="host" name="host" type="text" value="<?php $this->esc_html_attr( $this->host, true ); ?>" />
  683. </div>
  684. </div>
  685. </fieldset>
  686. <!-- 3. tables -->
  687. <fieldset class="row row-tables">
  688. <h1>tables</h1>
  689. <?php $this->get_errors( 'tables' ); ?>
  690. <div class="fields">
  691. <div class="field radio">
  692. <label for="all_tables">
  693. <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 ) ); ?> />
  694. all tables
  695. </label>
  696. </div>
  697. <div class="field radio">
  698. <label for="subset_tables">
  699. <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 ) ); ?> />
  700. select tables
  701. </label>
  702. </div>
  703. <div class="field table-select hide-if-js"><?php $this->table_select(); ?></div>
  704. </div>
  705. <div class="fields field-advanced">
  706. <div class="field field-advanced field-medium">
  707. <label for="exclude_cols">columns to exclude (optional, comma separated)</label>
  708. <input id="exclude_cols" type="text" name="exclude_cols" value="<?php $this->esc_html_attr( implode( ',', $this->get( 'exclude_cols' ) ) ) ?>" placeholder="eg. guid" />
  709. </div>
  710. <div class="field field-advanced field-medium">
  711. <label for="include_cols">columns to include only (optional, comma separated)</label>
  712. <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" />
  713. </div>
  714. </div>
  715. </fieldset>
  716. <!-- 4. results -->
  717. <fieldset class="row row-results">
  718. <h1>actions</h1>
  719. <?php $this->get_errors( 'results' ); ?>
  720. <div class="fields">
  721. <span class="submit-group">
  722. <input type="submit" name="submit[update]" value="update details" />
  723. <input type="submit" name="submit[dryrun]" value="dry run" <?php if ( ! $this->db_valid() ) echo 'disabled="disabled"'; ?> class="db-required" />
  724. <input type="submit" name="submit[liverun]" value="live run" <?php if ( ! $this->db_valid() ) echo 'disabled="disabled"'; ?> class="db-required" />
  725. <span class="separator">/</span>
  726. </span>
  727. <span class="submit-group">
  728. <?php if ( in_array( 'InnoDB', $this->get( 'engines' ) ) ) { ?>
  729. <input type="submit" name="submit[innodb]" value="convert to innodb" <?php if ( ! $this->db_valid() ) echo 'disabled="disabled"'; ?> class="db-required secondary field-advanced" />
  730. <?php } ?>
  731. <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" />
  732. </span>
  733. </div>
  734. <?php $this->get_report(); ?>
  735. </fieldset>
  736. <!-- 5. branding -->
  737. <section class="row row-delete">
  738. <h1>delete</h1>
  739. <div class="fields">
  740. <p>
  741. <input type="submit" name="submit[delete]" value="delete me" />
  742. Once you&rsquo;re done click the <strong>delete me</strong> button to secure your server
  743. </p>
  744. </div>
  745. </section>
  746. </form>
  747. <section class="help">
  748. <h1 class="branding">interconnect/it</h1>
  749. <h2>Safe Search and Replace on Database with Serialized Data v3.0.0</h2>
  750. <p>This developer/sysadmin tool carries out search/replace functions on MySQL DBs and can handle serialised PHP Arrays and Objects.</p>
  751. <p><strong class="red">WARNINGS!</strong>
  752. Ensure data is backed up.
  753. We take no responsibility for any damage caused by this script or its misuse.
  754. DB Connection Settings are auto-filled when WordPress or Drupal is detected but can be confused by commented out settings so CHECK!
  755. There is NO UNDO!
  756. Be careful running this script on a production server.</p>
  757. <h3>Don't Forget to Remove Me!</h3>
  758. <p>Delete this utility from your
  759. server after use by clicking the 'delete me' button. It represents a major security threat to your database if
  760. maliciously used.</p>
  761. <p>If you have feedback or want to contribute to this script click the delete button to find out how.</p>
  762. <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>
  763. <h3>Again, use Of This Script Is Entirely At Your Own Risk</h3>
  764. <p>The easiest and safest way to use this script is to copy your site's files and DB to a new location.
  765. You then, if required, fix up your .htaccess and wp-config.php appropriately. Once
  766. done, run this script, select your tables (in most cases all of them) and then
  767. enter the search replace strings. You can press back in your browser to do
  768. this several times, as may be required in some cases.</p>
  769. </section>
  770. <?php
  771. }
  772. public function deleted() {
  773. // obligatory marketing!
  774. // seriously though it's good stuff
  775. ?>
  776. <!-- 1. branding -->
  777. <section class="row row-branding">
  778. <h1><a href="http://interconnectit.com/" target="_blank">interconnect<span>/</span><strong>it</strong></a></h1>
  779. <?php $this->get_errors( 'delete' ); ?>
  780. <div class="content">
  781. <p>Thanks for using our search/replace tool! We&rsquo;d really appreciate it if you took a
  782. minute to join our mailing list and check out some of our other products.</p>
  783. </div>
  784. </section>
  785. <!-- 2. subscribe -->
  786. <section class="row row-subscribe">
  787. <h1>newsletter</h1>
  788. <form action="http://interconnectit.us2.list-manage.com/subscribe/post" method="POST" class="fields fields-small">
  789. <input type="hidden" name="u" value="08ec797202866aded7b2619b2">
  790. <input type="hidden" name="id" value="538abe0a97">
  791. <div id="mergeTable" class="mergeTable">
  792. <div class="mergeRow dojoDndItem mergeRow-email field field-short" id="mergeRow-0">
  793. <label for="MERGE0"><strong>email address</strong> <span class="asterisk">*</span></label>
  794. <input type="email" autocapitalize="off" autocorrect="off" name="MERGE0" id="MERGE0" size="25" value="">
  795. </div>
  796. <div class="mergeRow dojoDndItem mergeRow-text field field-short" id="mergeRow-1">
  797. <label for="MERGE1">first name</label>
  798. <input type="text" name="MERGE1" id="MERGE1" size="25" value="">
  799. </div>
  800. <div class="mergeRow dojoDndItem mergeRow-text field field-short" id="mergeRow-2">
  801. <label for="MERGE2">last name</label>
  802. <input type="text" name="MERGE2" id="MERGE2" size="25" value="">
  803. </div>
  804. <div class="submit_container field field-short">
  805. <br />
  806. <input type="submit" name="submit" value="subscribe">
  807. </div>
  808. </div>
  809. </form>
  810. </section>
  811. <!-- 3. contribute -->
  812. <section class="row row-contribute">
  813. <h1>contribute</h1>
  814. <div class="content">
  815. <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>
  816. </div>
  817. </section>
  818. <section class="row row-blog">
  819. <h1>blogs</h1>
  820. <div class="content">
  821. <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>
  822. </div>
  823. </section>
  824. <!-- 5. products -->
  825. <section class="row row-products">
  826. <h1>products</h1>
  827. <div class="content">
  828. <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>
  829. </div>
  830. </section>
  831. <?php
  832. }
  833. public function html( $body ) {
  834. // html classes
  835. $classes = array( 'no-js' );
  836. $classes[] = $this->regex ? 'regex-on' : 'regex-off';
  837. ?><!DOCTYPE html>
  838. <html class="<?php echo implode( ' ', $classes ); ?>">
  839. <head>
  840. <script>var h = document.getElementsByTagName('html')[0];h.className = h.className.replace('no-js', 'js');</script>
  841. <title>interconnect/it : search replace db</title>
  842. <?php $this->css(); ?>
  843. <?php $this->js(); ?>
  844. </head>
  845. <body>
  846. <?php $this->$body(); ?>
  847. </body>
  848. </html>
  849. <?php
  850. }
  851. public function css() {
  852. ?>
  853. <style type="text/css">
  854. * { margin: 0; padding: 0; }
  855. ::-webkit-input-placeholder { /* WebKit browsers */
  856. color: #999;
  857. }
  858. :-moz-placeholder { /* Mozilla Firefox 4 to 18 */
  859. color: #999;
  860. }
  861. ::-moz-placeholder { /* Mozilla Firefox 19+ */
  862. color: #999;
  863. }
  864. :-ms-input-placeholder { /* Internet Explorer 10+ */
  865. color: #999;
  866. }
  867. .js .hide-if-js {
  868. display: none;
  869. }
  870. .no-js .hide-if-nojs {
  871. display: none;
  872. }
  873. .regex-off .hide-if-regex-off {
  874. display: none;
  875. }
  876. .regex-on .hide-if-regex-on {
  877. display: none;
  878. }
  879. html {
  880. background: #fff;
  881. font-size: 10px;
  882. border-top: 20px solid #de1301;
  883. }
  884. body {
  885. font-family: 'Gill Sans MT', 'Gill Sans', Calibri, sans-serif;
  886. font-size: 1.6rem;
  887. }
  888. h2,
  889. h3 {
  890. text-transform: uppercase;
  891. font-weight: normal;
  892. margin: 2.0rem 0 1.0rem;
  893. }
  894. label {
  895. cursor: pointer;
  896. }
  897. /*.row {
  898. background-color: rgba( 210, 0, 0, 1 );
  899. padding: 20px 40px;
  900. border: 0;
  901. overflow: hidden;
  902. }
  903. .row + .row {
  904. background-color: rgba( 210, 0, 0, .8 );
  905. }
  906. .row + .row + .row {
  907. background-color: rgba( 210, 0, 0, .6 );
  908. }
  909. .row + .row + .row + .row {
  910. background-color: rgba( 210, 0, 0, .4 );
  911. }
  912. .row + .row + .row + .row + .row {
  913. background-color: rgba( 210, 0, 0, .2 );
  914. }*/
  915. .row {
  916. background-color: rgba( 210, 210, 210, 1 );
  917. padding: 20px 40px;
  918. border: 0;
  919. overflow: hidden;
  920. }
  921. .row + .row {
  922. background-color: rgba( 210, 210, 210, .8 );
  923. }
  924. .row + .row + .row {
  925. background-color: rgba( 210, 210, 210, .6 );
  926. }
  927. .row + .row + .row + .row {
  928. background-color: rgba( 210, 210, 210, .4 );
  929. }
  930. .row + .row + .row + .row + .row {
  931. background-color: rgba( 210, 210, 210, .2 );
  932. }
  933. .row h1 {
  934. display: block;
  935. font-size: 4.0rem;
  936. font-weight: normal;
  937. margin: 15px 0 20px;
  938. float: left;
  939. }
  940. .row h1,
  941. .branding {
  942. width: 260px;
  943. background:
  944. url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAH0CAYAAACHEBA3AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAAWdEVYdENyZWF0aW9uIFRpbWUAMTAvMDkvMTMvbciPAAAgAElEQVR4nO2d7XHbSLO2b791/kunNgDx6QTEE4GwEZgbgeAIlhuB6QgsR2AogqUjWCiCJROYBQPYeqQI/P7oHmEIAiAAgh8j3VeVC6YAzAy+bvQMero/gBBC9vDz589zNwEA8D/nbsBbQ0QmACYACudccax63C8fpgAeACzl358Px6rnUgjOKwDAOZefqy3kfPy/czfgDZIC+MuWx67nDkBx5HouhRR6Xv0/8g6hYMVLYsv8jG0g5KRQsCLE/fJhAuAWwA/59+fzeVtDyOmgYMXJzJbLs7aCkBNDwYqT1JYULPKuoGBFhvvlwzW0O7hmd5C8NyhY8eG7g9k5G0HIOaBgxQfHr8i7pdVx1Jz1EqjD3jOA3Dm36lq4iExt/2v708rK6NWVEZEEwNTKKQCs+rSjobwEekzLNgdPEbm2bafBn1fWhsb9WuoEBpxL4LU7+BHaHexcd9CGGfQ4Cuhx770Odg2nKJ02C2jbe9d/Kir3LTDwfAfljXUPRHcuL41awbILnkEdE6vrNgAWzrmsqVB7MB4A3DSsfwQwb3tgrA0LqEVxVbP+ycqovQlFZAHgM4AvzrlFUGaG7eP6KiKv2wT7X9sx3Le0cQ0gbXsQTKgy1JyLfcdQQ2LLvKGuBfSYPb8653J7UDLo2Jfne91xV9r9UNmnc9tr2lLlBfYCg4rn4BdQUKf3/t+5b239Bnq98o7lTay8jy3bPAF4cM41WryHnktSstMltIu0gl70JwCfAPwK4DcAj1Ar57vdkDuIyAOAP6EP6AbANwBf7N8j9Ea9B7CyG6yujBTAP1CxyqAP3gfn3AcA/2fl3AHIm8qoKXNqxzUN2vRkqz+LyLKybYFSrH4Ex+CPYwO9Ab312HQcf9m5eAz2/2b79zoGDBi/suuZo/5h+WzXq7rPwtpd+4AZvu1p17ZUuLIyPgP4W0Rya+sgrB1/o0GsjBsAf3Vpc3APNoqVcQdg3lLOAsc/l++GOgsrg95Mfzjnqjfz0iyPRV1hIjIH8DtUlBY1+3vLJYPeCEsRmdZYWhOoSKTVdfYWSkXk2eqaY880mKDOvFqm3SQPts5vm0PPwQ/om69oKHeG5qkxE2vbk9VZ3W4uIhlUFJcI5sm1MAOwkX9/9nkTL6HHsrG2TrFtsW5dI7uGVcvIW0Oo7H8F4EFEDuqiG3fQl1gysKy08jtsc1XEHkSksUts98T3mlX+HNaVWVfOuc7lm2VLsOxhvQOwqRMbALCLvPNGsbfjV/vZeNPZ/jMRWUHfOg/YvdmWTV2VgAeoYM32bAeoWBXOuZ1tnXNZ5SbxD/ijc67aruq+bQPf9wCenHNJy/6pdRduRCRt62a7Xz74rnGfwfYF9BxvvXzsQVpAuzJF8PcJymsI6MM1r7bL9vfbXUGvRbKnLX+gfFABtUwT6PXz3eUrmMV5wLjOE/RlmQftnUDPm7dyrqD3XN0LdVLz99oXlwlbWteII5/Ld0u1S+i7JsWAsha2/NLxDeFF777aFeiyf3Dz7IxvVZhBrZe0pawV8DrWcAd9kzaa+R15QTcxXdhy37Z+fR/BukONpWy/k5qXQvj7xbbJqoXa/p/CeuzctbFyzuXBv6Vzbu6cm0C7y54rDHfZ+OacS6pjVHavLCrbNrV3ge176tE5N6sTUOdcZi+kunslrG/sc/luqQrWq6lq1lYf/HhP1mVju6nW9jPpWVcfblHTtWwgteWi75fMGh46luHP+WTPdgmAF/n3Zx/BarOUt14Kdr3DDwxZ24vDHr5N8Kcu4txUVopyPBHQh3bSs5i5c67xJVNjDTfd3+FxbPZZ2Vb2xZzLt85Wl9A592xfLPwgYO2bpUrwRtgAmPS42QqooHQddB7CY48xgSFWzEE451YiArQMylrsKz9w34esx7bVa9Bl3yW0W163f18W2A4bM0NNl62Jkb4yJti2rhYDizr3uXyz1A26z1B+VVrZ17PFHuHyJ/gGw2IV7b1AJoJTlH4sk45lF102srfiFYD1CNbV2KS2zHvu12f7pPL7wYS0jUnw/72D0G2Y+8ULSsFI0EOw2rCvsGmHTav3YT6wyqTy+6Tn8i2zI1hmZSUofZDuoeNMbf4m3rx+xLDxh6avNdfQG22OcmA2/MoyJv5mvTSxAs7j3X6Oh2YV1Nt3SGILe8Gl9q/WH7CGrTpHdOikAI1EreOoWRip+ZB4t4E76NhC02d6QL/E5WM0rOJ8+gQ1z7c+RYvIZQSaPiIW++oGp4999eWEdY1Gg/Poxv72tXan4xPlubxEWqfmmCjNoT5DKVQ0vL/MJBCPHO1ezb0wsfoTak19avvcPyKXaFkBp7OuqsefnWHKyOSQnWv8p7Z6BSLSS7DsHi8GNOUSzuWbpPPkZxONKfTLnvcX8RS2HOvrhi97x2/lWASDtpdmvqe2zI9cT7WbnRy5vi2sCxd23fKe+0+xLVZ/mItDH6Gv1pn0aUPAWc/lW6ZXtAbv9Gk/k+DvBWyqyqE+JN6REup0mR1S1gDW1ob0xPXWEoRCHjTZuVdd2pV/Cf60OGZ9NaSV33nP/RfB/5+a3Dn2UBWaRd1G+7iAc/lm6R1eJjBtqwOZC1v2vlEqPl+JLfO+5YzAq2V3hrrrSGyZnai+8Nrd2NShvYjItG5OYlfMOgrP+WbAWGg456/vvgBeX8ih60jnc1DDWc7lW6e3YAUTddfh380aWkOtrKxHeXNs36y+/9/6lWiAY+teAge+XsdwRLw1m5+ovgdsWwb3Nik5qdtYRGbm9vI39rumTBrKSFDO3fQc+sKorasji8rvxnMgItcikopIXlPOMc/lu6U6lzCBmuZZ3RsumEQM1L/1U+jNd+99X1rCjyQoB/HDryhL6NecVERqPc6t7GXlb9fQAda0rr4ezKA3zb2V2Tb5eQLg+hiTVYPYV30nOw+vU11aUugHD88dNMJB6E5yjfboA3X4CB9LlC+lWU05jz3HnTzroKx7EXkIplxN0GL5i8i1v8+cc4WIfML2eFjdOZigxV3iyOfy3VL3ldD7XW2g4lPY371PlHeu3LkBzGs7Qel4+rdozKjwBryGdnX8RXpCcDPZDfMN6vWbm2j5rzyJteEeOiH1xv6eQt/Kk05H3YIdw28oI0p8FJEf2B3f8A/bl5p1Y3AO3ys455b2wD5g2+rx4WCayDsUf4PSm7uOvRPOW1hgWxz8vfeM+nbfmWXk3SAWfoVNiAd2IzbsOwdbHPlcvku2uoRmVf0KFZEbqDB8tn+/Q0/0N7R89bC32gTlWMBtUIYv5xb6RvxkX3KeK2XMrZ5bAH+KyE/zufoLeoP9ZpEX/Pyz71BhnfQ6+uZjWFo9/hg+Vo7hs7Vtg+OIFXD68atXrGucYHt+XxOP0Hhliz3bbfas++0Q69iuWdXf6RZlXDd/X4fcwaKR1pSXAfgPuk2H+tHSrgzjn8t3y4emFbIbZrZAz3CuUh9atnM5lTasUAlJa13DGUaKWNnQhqZjOGrcIvfLhzmAa/n35+JYdXShMiXK8wyLvtCy3wKV6KfQ85Zg+54a9Txae2dQS/4ZldDIUoaJBjreN3YPTLH9oi7Q01F66Lm8BH7+vAwf7UbBIuQQ6gTr0h9K0sylCBaz5hBCooGCRQiJBgoWISQaKFiEkGigYBFCooGCRQiJBgoWISQaWgP4EXIAGbY9yE8yH5K8beg4SgjZCx1HCSGkJ6N1CX2cn7cy/cLmfU2g88WKszaGEAJgXAvrLwzLSXippNDjSc/bDEKIh11CQkg0ULAIIdEQlWBZDO3i3O0ghJyHqAQLGi64a9pxQsgbIzbBurQkp4SQE0JPd3I2LsUZkcRDbBYWIeQd09nCMsfQJPjTTlKIHuVMoUkCChw5mcPYddYkEsitvJ38icdsUyVBx06yhQ77+6QKPiHtysrodRyEnJK9cwktmcAc23nVQnxewT8BwDm3U6Y9XAtoNpO6cp6gCUvD7CY5+uWA26p3SJ2V/RfQJApfnHMLE5gHNCe9/AagNvHrWG0KyshQf2421oasZf8Z9DiaPl48WhuOLlzsEpK+tKX5ukaZEBXQPILVhKgzVG78GuFIoXkDX6AP2tJP37G3/Bya//AFQBJk602xm2fQZ2Gp5p9DmMttaJ2Vdi+svi92rL/DBAGWpszO0czK83kKZw3ljdGmCdQSuoKKWwa1znw7vBB+qcttJyIPKBOZbrCdhXkS7N94HGNCwSJ9qRUsexBXUDFaQ9+4ecO2CcqU83WCtYB2fdKmt3bwILVm/rVkqrVW3Nh1BoL1An2I97UtgwrOBsC0Wu9Ibcqh5/mPuszbdt0WAJ6rgiUicwBf7XgWLftn0MSxtccxJhQs0pcmwVpCb9ofaHnAKvvUiomITPe9qc1y+AfAi3PuumW7roJ1cJ2VvHqf2rpZwT7+vO0IzqFtMjH5L4CNc26yry0N5QLA/3VoxwpqMR6SOn4vFCzSl52vhGYx+TdsJ7Fqo0u3Ihi4bxonO2edj13EypjbcmYCM2ab/CB/UbNuHwtbfunYzfPHcW9iR8hFUOfWkNqydQD5HVF03dAE5wdUcJKR2+GFZloVww7c2zLrsrF1/9f2M+lZFyFHo86tYWbLZc06sp8caqFOMeI5dM49i8gTdAwrF5FZF5cSH6cMajFPelhMBbRbON2zHSEnY0uw7M19BWB9TOuq4ss0we7XwJjrfLWEjtCmGcovtysbM1vsES7fjhsMi1dGwSIXQ9XC8jfn6GJlYphCx0e8K8QLjpic4Bx1BtR22w5pk1lZCdSP6t7/M8vrwTlXZ9H5djyiY5ewAocFyMVwkrmEFWfFJ+gg8DK04vwXwJjrrLDzoI/RJts2DRx6U2g38c6EK22wuIq3Er6avF+qguXf8pOxKrCH9E+oFdHJPSDGOgO8lbplMY3dJhOlOYC5OaUuoMK1EpFJIII5SvcMQqJm6yuh3eQvAG5G/JztHRTnJxSOc9Tp8R8tql28o7XJyptCv+xdBXUB5VfOGQiJnDq3Bj8Okh5auI233AB4OpVwnKPOSt1+jl9+yjbZy8aLUhL8vYB+IbwNvhgSEiV1grWw5Web43YIiS3zA8u59Do93rJ5rHxlTWyZH7PyYOyqOrF5Ycud6Tj7GODzRcjR2BEsu+m/2c/lgV1D/9C23vQjPxQnr9NizWdQd4MXlJ7iJ21T8IJZh383q24NtbKyHuXNsXsshJyNpgB+C+gNfgMdxE2bChCRiU3KreO1e9n0MNpDVh2gvm54sDa2fmc8JhDWsevcJzIJ1HLy3uSzGh+2g9skIomIZE3dumDiMlDvvpBCxfReRFZt1rPVlUMnSxNyMfQJL7Ox30Ww2QylVXEF1E5+foBGIFhDnRyX9vcE+hDdQ6ezfLRdPkHf6pOaCcALlBEUwu7NFMBHX/cYdVYmP9cd+zW2w+u8QF0Kar3bD22TbecdP6vt8b5d3um3VoxMpHKUcxXrQgYlKK/5E+oFeBQ4+Zn0pWsAvxTtAd8WsGgADQH8/MNa5fXhle2AfY1RIoIwLlWenHPJWHWaxTaDHntT0D6gjG+1d+7lCG1KEITyqaFLEMFrlI6nTayhjqhZyzYHQ8EifdkrWB7ZDukL6Ns97xoiWbZD+q5QCa9sb/8Z1JGy1es72La1HWPVaeX4sjzPVl7e1tZjtKlSBtDzWlgZ3poKrbHe5RwCBYv0pbNgETI2FCzSF2bNIYREAwWLEBINFCxCSDRQsAgh0UDBIoREAwWLEBINFCxCSDRQsAgh0UDBIoREAwWLEBINJ0lCsY9grl4xxjw2myc3BfDcMdMxISQCLsXCSqGhU9KRyptaeb0jbBJCLpdLESxCCNkLBYsQEg2NgmXheYsTtoUQQlppG3RfojnKKDkxjB1FSHuXsCkMLyGEnAWOYRFCooGCRQiJhtEdRyvJKgpokoVBzpvmUDpFmSght/JGTztlyR8SlEk2VtCEDJ3rqiSHeLb96bhKyEj8DwBUUkttISI7o701uQcn0PRTM5Q578L1TwDmXR9eE70H7KbX+mzr96az6oolZX1AwwcGEXmEtr0tddYEmupr5xyKyMbamh3aVkLeO97CyqDWS4hPIvqlrQDLCv0dZX6+pU99ZVbLHJoDLxeRpEMKL5+7bwNNJpo75wqbbjOz8n4HMBOR2SEWTCVP4Ab6ZdQL08TquweQNNVlYrWCCvUT9BwUKBOtzgB8F5GJc24xtK2EEBOsure/iHy2dYs9ZUzQkPjUHvBURJ6hwjBH+/SbOfTBf3TObW1nZWcAsiCZ6lJEpkMsLRHxwvcCtYB2pvEE6d8/ttSVWZv/qCljaWUs+raPELLLGGNYyw6i5i2Z2Z7trgB82td9cs6lJgQfrey0U0sNs4q+2s9Gq8/EaSYiK2j3dKsua8MdgE2d4AVlzPu0jxBSz8FfCbt0yYIIDDvjWxUee4z1eBGYmXD0YWHLLx27lL6uexM7j/8YUPSsnxAygEtzayi6bmgi+AMqgknPeu5tmXWsKwewtp9hXV7spgNEkxDSk4uIh3UAObRbOIUOmO/FvkACOsg+qVhMbRTQbqG3quCce7YvoHfQjwqzMeJ5EULqOYpgVfynJvbvGLxaOD328dveQGNm9aVa1wwqnLcAViKyhA7iFwPKJoS0MJpgWZcohY73eJ+mF5Sickz6dMf8to/o2CWsUP0S+hz4jd37f2Z5PTjnOll+hJD9jCJYFefLJ+ig9jJ0AahzQB2RIQ6khfcXOxQ7zlREFihdN+4A3JlwpbS4CDmcgwfdTaz+hFoun5xziXMuO8b0mRp896yPFZcfoR0A9EOAc27unLuGOr1uoMK14qA8IYczxldC7380P8P0E+/X1Uewisq+R8HOxRT6dfEKjC9PyMEcJFg2dnMD4OnUYmV1+7l7edf9rGu2AXAbfDE8Ct7x1H4etS5C3gOHWliJLfMDyxmCt1geB3Q/F5UyOtO3axeMXTF6KyEHcqhgeaFofYjHHL+xWPMZ1I3gBQOmvZg1uIZaWVmPuud967MJ4EDpeEoIGUibYG2A10H1LQJnS//JPm0SJXtgV5W/XTcIxT7hS6DWnPdUnx0wuJ9CBe9eRFaBsNTWayF4vtb8PWvqWgaTp4FhLhSEkIA2t4YMGmImszAsninUu/yDhX35Bp3YnIvIwvsd2UOcQsXlB6xLZOFo5qh3Jv3dBDLH9jQdH6ol9O9KD3FLcM6tAgG8BfC3iKyx7TF/De32+rhcT9jtRnq/q02l3d4v7QrAumlyNCGkOx/aVgZhXKo8OeeSYLswrlTIGur1vawECdwKR2MW2wz6gFeD9oX4mFutwftMiP6qtrNh22uUTp9NrKFOoFlDXQs0J+0YJdggs+YQskewgNcune8WFrCAejXbTVCGB15BQxkXwXpfzrItQoKV48vyPFt5+b72DsWEK8H21JsCDcdbs/8E5fH32rcLFCxCOggWuQwoWIRcXngZQghphIJFCIkGChYhJBooWISQaKBgEUKigV8JCb9AkmighUUIiQYKFiEkGihYhJBooGARQqIh9ryEr9hcwCmA547ZnAkhkfGWLKwpNEIDw7gQ8kZ5S4JFCHnjULAIIdFw8Y6jNja1cs5Nzt2WtwodR0ksxGBhLcGMM4QQxCFYTaGHCSHvjBgEixBCAFCwCCERsddx1LLCTKFpqwroAHhvx8yGJA87ySpOjSXHSFDmRCwwQvIIO2+J/Xy2Mnudt0pii0FlEPKWqP1KaA/KAprl5qpmkycA8y4PT480Wqlt13nMyjn32v4+qb1s+xR6jE0D+q8pyvaUs4Dmb/zinFtYO7KGcjudNzv/GerPxcbalbWV0Qd+JSSxsGNh2YP8HWUOwKVPr2XWyBwqPrmIJHtSdk2hyUW96P3AdhboCcpEpT5Lcl4p5rMtv3Q6oj2YgOYo8x+u7bfPGxi26U8ReXTOpR3LTlGeu0dsJ1WdQQWo9byZWK2g5+wJek6KoIwZgO8iMnHOLbq0i5C3Ql2XcIJKolOPPWSpiDxDE6fOoZbRDoEwXFl586ZulmV7LuryDorIZ6t7sf9w2qmI1dratFOnbZvCLEMRQQfRmkDPxxP03BWV8hYoLc0l6jNfAypQVwD+qMkWvbRjWOxpCyFvkp0uoYhMO3ZZ/gHw4py7btgmh1oUnS2UhnJ+Atvdv4btEuzpEgaZrNcAkn3ZmCsW4m913cOgSwgAP5xzs+o2le0LaHfxU7VbZ2L0XwCbUzrKsktIYmHnK2GXcanAeqgb3/LicQcdb5kPbt2IWJvuod21WZfU8d6itJ/7JlW/oMHarODLSWrW+Q8SRYdyCHl3HMutIbXlooswnIjUlg99vgCaVfUE4Ma6iU08dDzW3JbTmnX+ZTE1a4sQEnAswfLdotYvbCfGtykbsK/fp7W714XAgr2tWfcMFccr6OD85ND6CHlLdArgZw/O1P5N0Dxg7MdhrgCsL8W6qrSpGFDEEvr1bzJis5qYofwwsBKRJdRSLU5QNyEXTaOFJSLXIjK3QeJ/APwJHY+a7CnTd3UuQqyMg9oUCO+OVTQ2VlcCdYu4go67/SMiuX1NJeTdUitY9mCsAHyFDgB/AvC/zrlr51zSxTGTDMc592xfVv8D4Bt0QP8O6hfGriJ5t9Q5js6g1tQLaj69d+CSLCuPHzcaNJAdDICvx2lON6wbOAcwDzzz76BdxcmldLkJORV1Fpb/7D4fMv0jGFS+mLAw9mC/ALgdaJ34rlgxVpv6YtdiChXNKzB2PXmHbAmW+SrdQJ0vswPKXVt56QFljI3/YpkO2Nfvc9avnia8XjyTMzaFkLNQtbASW+YHlvtqpR1Yzphktpz38XEKnWDHnHA8lOBrIaOwkndHVbD8mEjrA73vgbcHewPtgmVDGzcmNmfQf3nLu4iWTc3xVtVFdMGsTcCJx9MIuQSqgvXabWp6oO2BWVX+dl0jTL7rci8iy7axIxGZBA9ilY1ts/NJf8B41Bz6oN/Coia0tClFOY/wsWYi8uiISCIiWVO77Jpk9jOr24aQt8zWV0LnXCEi36CRGHIReY0HZQ9RCvUL+gHrktiDveOf5Zxbichv0AfrI4CPIlINLwOosN1Cw8fUzWPMoJOLMxEJRWNq5XbO/OOce7bjyK3Ov0SkKbyM73IdNHl7APdQkd9Yuwr7+zX0/HsH2Iuw+Ag5JTtuDc65uYgAKlp/2v89a1jUgiAaw3eogCU1ZS3NclpAH8SP9q/KBvViBQuKN7H9P1dWPzUeWQM2cD0N3ARuUe8Q2imA35g453IR+RWl+0Jd0MNvYHgZ8k5ptE4q4XlXqIQyNiGaQQP87Y3w0BAiuUDHkMtBfX6/g8MYB+Um2A6RPCgM9JhUzj8w4jFXYXgZEgsXn0iVHB8KFokFZs0hhEQDBYsQEg0ULEJINFCwCCHRQMEihEQDBYsQEg0ULEJINFCwCCHRQMEihEQDBYsQEg2d0nx1wYdEsbhTR8HmI04BPJ97rh8h5PSMJlgA/rLlMecnTq2eJzBEMCHvDnYJCSHRQMEihEQDBWsPFv65OHc7CCHjjmG9VZaIIEMNY1qR9wAtrP1cTEJYQt47FCxCSDRQsAgh0dB5DMscQ5PgTzuJKXqUVU38sIImWHhu3Gkgp6zrkuom5C2yV7BEZAHNO3jVsP4JHbMiWzLUBzQMYovII4D5GA/0kLqC1GV12++Majvnap1kT3mchLwnGgXLpsHkKHP2rVFmhgbUaphBH/C9A9OWBPV3+7mxssLkpTNoHr5ERGaHTL05oK4MeswhPhfilyPXTQjZQ5OFcA3tvtxAhWreNEfQuooLmGjVWR0iMgfwFcALNDnpjkUWpGH/CH3Qp1ULxOr6C8CTcy5paM8odQXb/mw6rmPX3Qe6NZD3QNOgewYVqx8AkrYJzc65vEk8gNeEoF/tZ9KUYt059+ycm0EF8gYdu5nnquuS6ibkvbAjWGbF+Ld/OsLbf2HLLx27P3Nb3psIXGpdl1Q3Ie+COgsrteVipEHhe1tmXTY2a25tP5MLruuS6ibkXVA36D6z5bJmXS98jCyotTbpYUkU0MH+6SXWdUl1E/Ke2BIsGxC+ArAeybryD+INynhZQ/a/tLouqW5C3g1VC8s/OGP5B3mHyUd07CpV6NOOU9Z1SXUT8m44VbSG4pihk89Y1yXVTcibpzro7r9uTUYqPx+pnEur65LqJuTdsCVYNm71AuBmpE/thS1nbRuNxCnruqS6CXk31Lk1+K+D6aGF28ToDYDb4EvaUThlXZdUNyHviTrBWtjys0UbOBRf3hDP9ev9W52trkuqm5B3wY5gmbXwzX4uD+0aOucyqIPkrYhkXfezeXnzvRueqa5LqpuQ90LTXMIFyrluKxFJmwoQkYmFZWkjhY6N3YvIqs1yE5HEyvvatM2J69rYtjvjUzViPnbdhJCAxggENeFlNva7CDab2foXWLyslhhRU9vfx9WqC1eTBPU9AZhV4lUl2BOtYay6grIW0BAzL9ju7k0BfKwe75h194HRGsh7oEvIlAXUcmjKHPMItcj+AdrDsJgIPqCcd1fHGsCDdbGq+yfoIFhj1FUpK2sop7YdY9ZNyCVyrhdk57TyJhZTlF7dBTTcb9G3UnugfXmeweWdoi6znHy3sNP+pzxOQk7JxQsWIYR4ziVYzJpDCIkGChYhJBooWISQaKBgEUKigYJFCIkGChYhJBooWISQaKBgEUKigYJFCIkGChYhJBp2klDY/LcpgOeOGYwJede4Xz5MoZPdl/Lvz94BHEl36iysKTQiAk88Id1IAdxhO/QSOQKnSvNFyFsmsWUe/tECX04AZIzOMQ4cwyLkANwvHybQYIw/5N+fYbDJBYDv0OCPK8btH4cdC8sSgZ487Ixd0JVzbnLqusn75sB7z8dIW1b+ngT/v4IOteQDyicBl2RhLdEc1ZSQY3LIvZcGZYTkwf9fUCYpJgdwSYJ1d+4GkHfLoHvP/fLhGgRtI5IAACAASURBVNodXIfdQQBwzi0AfALwBcC0S8z+tmQvROGgOyHD8d3BrG5ln3j9Jlbfm8oiyiVZWITERtP4VS8CsSJ7GM3CsiQVif18hiZaOEm/3RJEJCgTZKys/sGps4LjeQawbPss3ZBsYgUdyG3cr6ac6nEUGCFhxdjXZszyxrx2Y12HLlh38CO0Ozi4bIpVP3a+Bu5LpRXk6fvinFvY9hnqBy2fAMyrN7MlEO08btCS63AGdXBtS0E2b7r5q8dif5tAj6favtdtgv27pvNK2x5ou2kXLcexBrBwzrW+yce4Nscsr1L2QdeuUlbn62DbHXzvuV8+zAD8CeCb/PtzJ3N3cO4ay+khVr/a1/uL4VxJKA6ysIIT/gK9wQpbdQ01l+8A5CKSVG7kDLufeP3F/dKx7gcAv9vPDdQs9zf3xOq/B5CIyKzLgxQkQQWAb1ZeYsfxWUSmzrlZZVufMPUHtr8ETVAmTK31walJVru23+Fx+DL+FJFH51y67zis7BTDrs3Ryxvz2g24DhkOvPeM1vGrfdCyGsYhgjUBMIe+WdOqyW1vGP/WW9r2AOoHI0Xks61b7KtYRObQG/4FannsTCMyMcigZvvSxKbxbR1sn9vxhE6AqR1LHmybQx+SH1BLoGgod4aaKRsVsVpbGXlDGb7+exFBB9GaYOC1OXZ5Y167Ideh7hz3ufcCZgA28u/P3l1hitVwDhGsewA/vMVRxW6w1LoRNyKSjpHl2LpsX+1n49vc6p+JyAoqCg8ofWbqyKA39M7xOOcyEVkFdS2hD8lei6elG/eAUqySNjH19UMfznsRWe7pHo59bUYp7wjXbozr0BvrDl5h+GD7CsCv9n9/H3h+3d2cPlyeQ74SvqBdADz+DZocUFfIwpZfOo6X+PGFe3tg6phBrYK0qRBflz2Ud9CuzM7YRResjHvoOZx1Gaex+n37dqySCmNfm7HKW9jy4Gs3xnU4AC/cgwTLObdyzuVm7T1X1uU1/3p/gHirHCJYDx1PZG7LadtGPfADq1mXje2mWNvPpGGzW1S6gS2ktlwccCP5Mh76fL0yK+EJZsW0bDr2tRmrvDGvXWrLQ67DUBIAL/Lvz9GsNtKNozuOOudWIgJsm72DsLcqoG/VSYvFVKWw+psepMcen+UPertWysgG7JtBLYvZwP1fGfPa7CvvCNdujOvQG4t9dQP98EBOTGye7v6mvYG6Xgzdv0rRZWcb5L0CsB76Vq+U0aneCkvogO1kSP1nZLRrN8Z1OIDUlvmJ6yWIT7C8e8AjhlkXh97c/qE5pJyDynDOPY9pFZ2QMa/dGNdhKGex7IgSm2B5aj9PkyiI9tpZ7KsbVGJfkdMR21zC/Mz1j3GT+rGy69atGggCwa1bN7w88hHLOpdY0Lo6M7EJVmHLWn+gYxMMzA8OhWNjLi8AbnsMPIf4Yy+GtuFMFLY8+NqNcR0GktoyP3G9xIhKsGyQegN92JMzNWMNHBy7yL+hh5Th94nqLX+EazfGdehMEAr5oMnO5DCiEixjYct9zpM7jBRX29d7iLNi5svo06bQWXKMWQNnYGHLMa7dGNehD4kts2NWMtDqfjdEJ1j2oK6hb+qs6342h+3gm9vq95ZC5/orZeTQr2VX0AnDe0XLJvl6q6r3A38JjHntxrgOPfFd2XzkcqvlbV1bEbkWkdSu/7vnkgRrA7xOUt2i5q2TQseB7kVk1XYxRSSxcDZfm7YZgG/jvYgs296KIjJpaN8c9vDCohy0lJGinOT7WDdhOCJSjHftxrgOwJ57L4h9NWiy8x7yyu+PIlKISG5zKf8L9bsbo3cQPZfk1pBBw3xkFn7EM4XeLK/xhMyjOkEZ7eBvEVlje1znGmVYEUCntIzyoFv9v6GMKPBRRKphTQB9oG6hYUtWlTKeK8fwlx1Djt3wMj5mVOfwMpfKmNdujOtgZGi/9z7Z79HHDZ1zuYg8YjuW1w2YkKWWixEsCxA3gV64z5XVTzXbr2z7B9vnFvXOlGvoXLhs5PYu7Y29sPo/2r8qGzTMtrcvhlMpA/i1HcPeAH6xMOa1G+k67Lv3Evt/YzsOwTmXisgzyhhhVZ4Q31fho3Dy/IP7sJsv/HS/N0Sw1IfG7bTvGLTUH4ak6VLOFLshknuVERtjXrtDr0PTved++TAHcC3//lz0aU9fTDQTlNOuXttwzHqHcK6IoxcnWISQy+dcgnVJg+6EENIKBYsQEg0ULEJINFCwCCHRQMEihEQDBYsQEg0ULEJINFCwCCHRQMEihEQDBYsQEg0XM/n5LWFz2qYAnt/yPEBCTg0trOMwhebeizluFSEXBwWLEBIN7BKSd8+5Ig+Q/tDCeiNY7O/iWNsTcglQsN4OS/QLq9t3e0LODgXr7dA3qeipk5AScjAULEJINFCwCCHR0PkroaVmSuznMzQ4/iCnyJpkCysr77lxpzNTSRBw0PE3lB/NOTlGW4P76xnA8hITL5Dzs5OEQkQW0FRHXyz9UQJNb1Q3QPsEYN71wbVElQ8NZQGaDXlevfEtmeYdgP90vZEtv9zMOTfpsr3tU1jb/te3wYQqQ/2YzwaafiurlJNAHUefnHPJnjp7n5PgfHTlqc/2zrna5CRDr1+w/wLBvWV/m6D+/L5uc2zo1hAPrRaW5cv7Ds3U+4gyN9o1NB3SHSxr8T7RMgHxedc20K9UYcLQGTQvXCIis0p5mdXVJ918CuBKRKYdUzwl0AfxsSJWK2jG5SdrR4Hy+GcAvovIZMjDdcA5ybCbMdjn0/tSU1XRc/sx29pW5jRo1zcrL4Fe68927XayMZP3S5tgTaDi8AQgrVo29rb0iTCXKHOp7SAic+jN/gK1SHamrNj8uwyaBHNpN+szADjnMqsvRQfBMkvgyn7Obb99+HKz4G+ZlfNHTZuX1uZFh7Lr2jj4nNQlFhWRz4AmBe1Yf+ftD71+DWX67XPo/RVakCn03sq7HAt5P7Q