PageRenderTime 65ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/duplicator/files/installer/ajax.step2.php

https://bitbucket.org/nathancorbier/wastark.com
PHP | 498 lines | 389 code | 51 blank | 58 comment | 47 complexity | 8f38b8a9ab17c13829648b848f6781eb MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /** ******************************************************
  3. * CLASS::DUPDBTEXTSWAP
  4. * Walks every table in db that then walks every row and column replacing searches with replaces
  5. * large tables are split into 50k row blocks to save on memory. */
  6. class DupDBTextSwap {
  7. /**
  8. * LOG ERRORS
  9. */
  10. static public function log_errors($report) {
  11. if ( ! empty($report['errsql']) ) {
  12. DupUtil::log("====================================");
  13. DupUtil::log("DATA-REPLACE ERRORS (MySQL)");
  14. foreach( $report['errsql'] as $error ) {DupUtil::log($error);}
  15. DupUtil::log("");
  16. }
  17. if ( ! empty($report['errser']) ) {
  18. DupUtil::log("====================================");
  19. DupUtil::log("DATA-REPLACE ERRORS (Serialization):");
  20. foreach( $report['errser'] as $error ) {DupUtil::log($error);}
  21. DupUtil::log("");
  22. }
  23. if ( ! empty($report['errkey']) ) {
  24. DupUtil::log("====================================");
  25. DupUtil::log("DATA-REPLACE ERRORS (Key):");
  26. DupUtil::log('Use SQL: SELECT @row := @row + 1 as row, t.* FROM some_table t, (SELECT @row := 0) r');
  27. foreach( $report['errkey'] as $error ) {DupUtil::log($error);}
  28. DupUtil::log("");
  29. }
  30. }
  31. /**
  32. * LOG STATS
  33. */
  34. static public function log_stats($report) {
  35. if ( ! empty( $report ) && is_array( $report ) ) {
  36. $stats = sprintf("SEARCH1:\t'%s' \nREPLACE1:\t'%s' \n", $_POST['url_old'], $_POST['url_new']);
  37. $stats .= sprintf("SEARCH2:\t'%s' \nREPLACE2:\t'%s' \n", $_POST['path_old'], $_POST['path_new']);
  38. $stats .= sprintf("SCANNED:\tTables:%d | Rows:%d | Cells:%d \n", $report['scan_tables'], $report['scan_rows'], $report['scan_cells']);
  39. $stats .= sprintf("UPDATED:\tTables:%d | Rows:%d |Cells:%d \n", $report['updt_tables'], $report['updt_rows'], $report['updt_cells']);
  40. $stats .= sprintf("ERRORS:\t\t%d \nRUNTIME:\t%f sec", $report['err_all'], $report['time'] );
  41. DupUtil::log($stats);
  42. }
  43. }
  44. /**
  45. * LOAD
  46. * Begins the processing for replace logic
  47. * @param mysql $conn The db connection object
  48. * @param array $list Key value pair of 'search' and 'replace' arrays
  49. * @param array $tables The tables we want to look at.
  50. * @return array Collection of information gathered during the run.
  51. */
  52. static public function load($conn, $list=array(), $tables=array(), $cols=array()) {
  53. $exclude_cols = $cols;
  54. $report = array('scan_tables'=>0, 'scan_rows'=>0, 'scan_cells'=>0,
  55. 'updt_tables'=>0, 'updt_rows'=>0, 'updt_cells'=>0,
  56. 'errsql'=>array(), 'errser'=>array(), 'errkey'=>array(),
  57. 'errsql_sum'=>0, 'errser_sum'=>0, 'errkey_sum'=>0,
  58. 'time'=>'', 'err_all'=>0);
  59. $profile_start = DupUtil::get_microtime();
  60. if ( is_array( $tables ) && ! empty( $tables ) ) {
  61. foreach ($tables as $table) {
  62. $report['scan_tables']++;
  63. $columns = array( );
  64. // Get a list of columns in this table
  65. $fields = mysqli_query($conn, 'DESCRIBE ' . $table);
  66. while ($column = mysqli_fetch_array( $fields )) {
  67. $columns[ $column[ 'Field' ] ] = $column[ 'Key' ] == 'PRI' ? true : false;
  68. }
  69. // Count the number of rows we have in the table if large we'll split into blocks, This is a mod from Simon Wheatley
  70. $row_count = mysqli_query($conn, 'SELECT COUNT(*) FROM ' . $table);
  71. $rows_result = mysqli_fetch_array( $row_count );
  72. @mysqli_free_result($row_count);
  73. $row_count = $rows_result[ 0 ];
  74. if ( $row_count == 0 )
  75. continue;
  76. $page_size = 25000;
  77. $pages = ceil($row_count / $page_size);
  78. for ($page = 0; $page < $pages; $page++) {
  79. $current_row = 0;
  80. $start = $page * $page_size;
  81. $end = $start + $page_size;
  82. // Grab the content of the table
  83. $data = mysqli_query($conn, sprintf( 'SELECT * FROM %s LIMIT %d, %d', $table, $start, $end ) );
  84. if (! $data)
  85. $report['errsql'][] = mysqli_error($conn);
  86. //Loops every row
  87. while ($row = mysqli_fetch_array($data)) {
  88. $report['scan_rows']++;
  89. $current_row++;
  90. $upd_col = array();
  91. $upd_sql = array();
  92. $where_sql = array();
  93. $upd = false;
  94. $serial_err = 0;
  95. //Loops every cell
  96. foreach( $columns as $column => $primary_key ) {
  97. if (in_array( $column, $exclude_cols ) ) {
  98. continue;
  99. }
  100. $report['scan_cells']++;
  101. $edited_data = $data_to_fix = $row[$column];
  102. $base64coverted = false;
  103. //Only replacing string values
  104. if (! is_numeric($row[$column])) {
  105. //Base 64 detection
  106. if (base64_decode($row[$column], true)) {
  107. $decoded = base64_decode($row[$column], true);
  108. if(self::is_serialized($decoded)) {
  109. $edited_data = $decoded;
  110. $base64coverted = true;
  111. }
  112. }
  113. //Replace logic - level 1: simple check on basic serilized strings
  114. foreach($list as $item) {
  115. $edited_data = self::recursive_unserialize_replace($item['search'], $item['replace'], $edited_data);
  116. }
  117. //Replace logic - level 2: repair larger/complex serilized strings
  118. $serial_check = self::fix_serial_string($edited_data);
  119. if ($serial_check['fixed']) {
  120. $edited_data = $serial_check['data'];
  121. }
  122. elseif ($serial_check['tried'] && ! $serial_check['fixed']) {
  123. $serial_err++;
  124. }
  125. }
  126. //Change was made
  127. if ( $edited_data != $data_to_fix || $serial_err > 0) {
  128. $report['updt_cells']++;
  129. //Base 64 encode
  130. if ($base64coverted) {
  131. $edited_data = base64_encode($edited_data);
  132. }
  133. $upd_col[] = $column;
  134. $upd_sql[] = $column . ' = "' . mysqli_real_escape_string($conn, $edited_data) . '"';
  135. $upd = true;
  136. }
  137. if ($primary_key) {
  138. $where_sql[] = $column . ' = "' . mysqli_real_escape_string($conn, $data_to_fix) . '"';
  139. }
  140. }
  141. //PERFORM ROW UPDATE
  142. if ($upd && ! empty($where_sql)) {
  143. $sql = "UPDATE `{$table}` SET " . implode( ', ', $upd_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
  144. $result = mysqli_query($conn, $sql) or $report['errsql'][] = mysqli_error($conn);
  145. if ($result) {
  146. if ($serial_err > 0) {
  147. $report['errser'][] = "SELECT " . implode( ', ', $upd_col ) ." FROM `{$table}` WHERE " . implode(' AND ', array_filter( $where_sql )) . ';';
  148. }
  149. $report['updt_rows']++;
  150. }
  151. } elseif ($upd) {
  152. $report['errkey'][] = sprintf("Row [%s] on Table [%s] requires a manual update.", $current_row, $table);
  153. }
  154. DupUtil::fcgi_flush();
  155. }
  156. @mysqli_free_result($data);
  157. }
  158. if ($upd) {
  159. $report['updt_tables']++;
  160. }
  161. }
  162. }
  163. $profile_end = DupUtil::get_microtime();
  164. $report['time'] = DupUtil::elapsed_time($profile_end, $profile_start);
  165. $report['errsql_sum'] = empty($report['errsql']) ? 0 : count($report['errsql']);
  166. $report['errser_sum'] = empty($report['errser']) ? 0 : count($report['errser']);
  167. $report['errkey_sum'] = empty($report['errkey']) ? 0 : count($report['errkey']);
  168. $report['err_all'] = $report['errsql_sum'] + $report['errser_sum'] + $report['errkey_sum'];
  169. return $report;
  170. }
  171. /**
  172. * Take a serialised array and unserialise it replacing elements and
  173. * unserialising any subordinate arrays and performing the replace.
  174. * @param string $from String we're looking to replace.
  175. * @param string $to What we want it to be replaced with
  176. * @param array $data Used to pass any subordinate arrays back to in.
  177. * @param bool $serialised Does the array passed via $data need serialising.
  178. * @return array The original array with all elements replaced as needed.
  179. */
  180. static private function recursive_unserialize_replace($from = '', $to = '', $data = '', $serialised = false) {
  181. // some unseriliased data cannot be re-serialised eg. SimpleXMLElements
  182. try {
  183. if (is_string($data) && ($unserialized = @unserialize( $data )) !== false) {
  184. $data = self::recursive_unserialize_replace($from, $to, $unserialized, true);
  185. }
  186. elseif (is_array($data)) {
  187. $_tmp = array();
  188. foreach ($data as $key => $value) {
  189. $_tmp[$key] = self::recursive_unserialize_replace($from, $to, $value, false);
  190. }
  191. $data = $_tmp;
  192. unset($_tmp);
  193. }
  194. elseif (is_object($data)) {
  195. $dataClass = get_class($data);
  196. $_tmp = new $dataClass();
  197. foreach ($data as $key => $value) {
  198. $_tmp->$key = self::recursive_unserialize_replace($from, $to, $value, false);
  199. }
  200. $data = $_tmp;
  201. unset($_tmp);
  202. }
  203. else {
  204. if (is_string($data)) {
  205. $data = str_replace($from, $to, $data);
  206. }
  207. }
  208. if ($serialised)
  209. return serialize($data);
  210. } catch(Exception $error) {
  211. DupUtil::log("\nRECURSIVE UNSERIALIZE ERROR: With string\n". $error, 2);
  212. }
  213. return $data;
  214. }
  215. /**
  216. * IS_SERIALIZED
  217. * Test if a string in properly serialized */
  218. static public function is_serialized($data) {
  219. $test = @unserialize(($data));
  220. return ($test !== false || $test === 'b:0;') ? true : false;
  221. }
  222. /**
  223. * FIX_STRING
  224. * Fixes the string length of a string object that has been serialized but the length is broken
  225. * @param string $data The string ojbect to recalculate the size on.
  226. * @return
  227. */
  228. static private function fix_serial_string($data) {
  229. $result = array('data'=>$data, 'fixed'=>false, 'tried'=>false);
  230. if (preg_match("/s:[0-9]+:/", $data)) {
  231. if (! self::is_serialized($data) ) {
  232. $regex = '!(?<=^|;)s:(\d+)(?=:"(.*?)";(?:}|a:|s:|b:|d:|i:|o:|N;))!s';
  233. $serial_string = preg_match( '/^s:[0-9]+:"(.*$)/s', trim($data), $matches);
  234. //Nested serial string
  235. if ($serial_string) {
  236. $inner = preg_replace_callback($regex, 'DupDBTextSwap::fix_string_callback', rtrim($matches[1], '";'));
  237. $serialized_fixed = 's:' . strlen($inner) . ':"' . $inner . '";' ;
  238. } else {
  239. $serialized_fixed = preg_replace_callback($regex, 'DupDBTextSwap::fix_string_callback', $data);
  240. }
  241. if (self::is_serialized($serialized_fixed) ) {
  242. $result['data'] = $serialized_fixed;
  243. $result['fixed'] = true;
  244. }
  245. $result['tried'] = true;
  246. }
  247. }
  248. return $result;
  249. }
  250. static private function fix_string_callback($matches) {
  251. return 's:' . strlen(($matches[2]));
  252. }
  253. }
  254. //====================================================================================================
  255. //DATABASE UPDATES
  256. //====================================================================================================
  257. $ajax2_start = DupUtil::get_microtime();
  258. //MYSQL CONNECTION
  259. $dbh = @mysqli_connect($_POST['dbhost'], $_POST['dbuser'], $_POST['dbpass'], $_POST['dbname']);
  260. $charset_server = @mysqli_character_set_name($dbh);
  261. @mysqli_query($dbh, "SET wait_timeout = {$GLOBALS['DB_MAX_TIME']}");
  262. DupUtil::mysql_set_charset($dbh, $_POST['dbcharset'], $_POST['dbcollate']);
  263. //POST PARAMS
  264. $_POST['blogname'] = mysqli_real_escape_string($dbh, $_POST['blogname']);
  265. $_POST['postguid'] = isset($_POST['postguid']) && $_POST['postguid'] == 1 ? 1 : 0;
  266. $_POST['path_old'] = isset($_POST['path_old']) ? trim($_POST['path_old']) : null;
  267. $_POST['path_new'] = isset($_POST['path_new']) ? trim($_POST['path_new']) : null;
  268. $_POST['siteurl'] = isset($_POST['siteurl']) ? rtrim(trim($_POST['siteurl']), '/') : null;
  269. $_POST['tables'] = isset($_POST['tables']) && is_array($_POST['tables']) ? array_map('stripcslashes', $_POST['tables']) : array();
  270. $_POST['url_old'] = isset($_POST['url_old']) ? trim($_POST['url_old']) : null;
  271. $_POST['url_new'] = isset($_POST['url_new']) ? rtrim(trim($_POST['url_new']), '/') : null;
  272. //LOGGING
  273. $POST_LOG = $_POST;
  274. unset($POST_LOG['tables']);
  275. unset($POST_LOG['plugins']);
  276. unset($POST_LOG['dbpass']);
  277. ksort($POST_LOG);
  278. //GLOBAL DB-REPLACE
  279. DupUtil::log("\n\n\n{$GLOBALS['SEPERATOR1']}");
  280. DupUtil::log('DUPLICATOR INSTALL-LOG');
  281. DupUtil::log('STEP2 START @ ' . @date('h:i:s') );
  282. DupUtil::log('NOTICE: NOTICE: Do not post to public sites or forums');
  283. DupUtil::log("{$GLOBALS['SEPERATOR1']}");
  284. DupUtil::log("CHARSET SERVER:\t{$charset_server}");
  285. DupUtil::log("CHARSET CLIENT:\t" . @mysqli_character_set_name($dbh));
  286. DupUtil::log("--------------------------------------");
  287. DupUtil::log("POST DATA");
  288. DupUtil::log("--------------------------------------");
  289. DupUtil::log(print_r($POST_LOG, true));
  290. DupUtil::log("--------------------------------------");
  291. DupUtil::log("SCANNED TABLES");
  292. DupUtil::log("--------------------------------------");
  293. $msg = (isset($_POST['tables']) && count($_POST['tables'] > 0)) ? print_r($_POST['tables'], true) : 'No tables selected to update';
  294. DupUtil::log($msg);
  295. DupUtil::log("--------------------------------------");
  296. DupUtil::log("KEEP PLUGINS ACTIVE");
  297. DupUtil::log("--------------------------------------");
  298. $msg = (isset($_POST['plugins']) && count($_POST['plugins'] > 0)) ? print_r($_POST['plugins'], true) : 'No plugins selected for activation';
  299. DupUtil::log($msg);
  300. //UPDATE SETTINGS
  301. $serial_plugin_list = (isset($_POST['plugins']) && count($_POST['plugins'] > 0)) ? @serialize($_POST['plugins']) : '';
  302. mysqli_query($dbh, "UPDATE `{$GLOBALS['FW_TABLEPREFIX']}options` SET option_value = '{$_POST['blogname']}' WHERE option_name = 'blogname' ");
  303. mysqli_query($dbh, "UPDATE `{$GLOBALS['FW_TABLEPREFIX']}options` SET option_value = '{$_POST['url_new']}' WHERE option_name = 'home' ");
  304. mysqli_query($dbh, "UPDATE `{$GLOBALS['FW_TABLEPREFIX']}options` SET option_value = '{$_POST['siteurl']}' WHERE option_name = 'siteurl' ");
  305. mysqli_query($dbh, "UPDATE `{$GLOBALS['FW_TABLEPREFIX']}options` SET option_value = '{$serial_plugin_list}' WHERE option_name = 'active_plugins' ");
  306. DupUtil::log("--------------------------------------");
  307. DupUtil::log("GLOBAL DB-REPLACE");
  308. DupUtil::log("--------------------------------------");
  309. array_push($GLOBALS['REPLACE_LIST'],
  310. array('search' =>$_POST['url_old'], 'replace'=>$_POST['url_new']),
  311. array('search' =>$_POST['path_old'], 'replace'=>$_POST['path_new']),
  312. array('search' =>rtrim(DupUtil::unset_safe_path($_POST['path_old']), '\\'), 'replace'=>rtrim($_POST['path_new'], '/'))
  313. );
  314. @mysqli_autocommit($dbh, false);
  315. $report = DupDBTextSwap::load( $dbh, $GLOBALS['REPLACE_LIST'], $_POST['tables'], $GLOBALS['TABLES_SKIP_COLS']);
  316. @mysqli_commit($dbh);
  317. @mysqli_autocommit($dbh, true);
  318. //BUILD JSON RESPONSE
  319. $JSON = array();
  320. $JSON['step1'] = json_decode(urldecode($_POST['json']));
  321. $JSON['step2'] = $report;
  322. $JSON['step2']['warn_all'] = 0;
  323. $JSON['step2']['warnlist'] = array();
  324. DupDBTextSwap::log_stats($report);
  325. DupDBTextSwap::log_errors($report);
  326. //Reset the postguid data
  327. if ($_POST['postguid']) {
  328. mysqli_query($dbh, "UPDATE `{$GLOBALS['FW_TABLEPREFIX']}posts` SET guid = REPLACE(guid, '{$_POST['url_new']}', '{$_POST['url_old']}')");
  329. $update_guid = @mysqli_affected_rows($dbh) or 0;
  330. DupUtil::log("Reverted '{$update_guid}' post guid columns back to '{$_POST['url_old']}'");
  331. }
  332. //====================================================================================================
  333. //FINAL CLEANUP
  334. //====================================================================================================
  335. DupUtil::log("\n{$GLOBALS['SEPERATOR1']}");
  336. DupUtil::log('START FINAL CLEANUP: ' . @date('h:i:s') );
  337. DupUtil::log("{$GLOBALS['SEPERATOR1']}");
  338. $patterns = array("/'WP_HOME',\s*'.*?'/",
  339. "/'WP_SITEURL',\s*'.*?'/");
  340. $replace = array("'WP_HOME', " . '\''.$_POST['url_new'].'\'',
  341. "'WP_SITEURL', " . '\''.$_POST['url_new'].'\'');
  342. $config_file = @file_get_contents('wp-config.php', true);
  343. $config_file = preg_replace($patterns, $replace, $config_file);
  344. file_put_contents('wp-config.php', $config_file);
  345. //Create Snapshots directory
  346. if(!file_exists(DUPLICATOR_SSDIR_NAME)) {
  347. mkdir(DUPLICATOR_SSDIR_NAME, 0755);
  348. }
  349. $fp = fopen(DUPLICATOR_SSDIR_NAME . '/index.php', 'w');
  350. fclose($fp);
  351. //WEB CONFIG FILE
  352. $currdata = parse_url($_POST['url_old']);
  353. $newdata = parse_url($_POST['url_new']);
  354. $currpath = DupUtil::add_slash(isset($currdata['path']) ? $currdata['path'] : "");
  355. $newpath = DupUtil::add_slash(isset($newdata['path']) ? $newdata['path'] : "");
  356. if ($currpath != $newpath) {
  357. DupUtil::log("HTACCESS CHANGES:");
  358. @copy('.htaccess', '.htaccess.orig');
  359. @copy('web.config', 'web.config.orig');
  360. @unlink('.htaccess');
  361. @unlink('web.config');
  362. DupUtil::log("created backup of original .htaccess to htaccess.orig and web.config to web.config.orig");
  363. $tmp_htaccess = <<<HTACCESS
  364. # BEGIN WordPress
  365. <IfModule mod_rewrite.c>
  366. RewriteEngine On
  367. RewriteBase {$newpath}
  368. RewriteRule ^index\.php$ - [L]
  369. RewriteCond %{REQUEST_FILENAME} !-f
  370. RewriteCond %{REQUEST_FILENAME} !-d
  371. RewriteRule . {$newpath}index.php [L]
  372. </IfModule>
  373. # END WordPress
  374. HTACCESS;
  375. file_put_contents('.htaccess', $tmp_htaccess);
  376. DupUtil::log("created basic .htaccess file. If using IIS web.config this process will need to be done manually.");
  377. DupUtil::log("updated .htaccess file.");
  378. } else {
  379. DupUtil::log("web configuration file was not renamed because the paths did not change.");
  380. }
  381. //===============================
  382. //WARNING TESTS
  383. //===============================
  384. DupUtil::log("\n--------------------------------------");
  385. DupUtil::log("WARNINGS");
  386. DupUtil::log("--------------------------------------");
  387. $config_vars = array('WP_CONTENT_DIR', 'WP_CONTENT_URL', 'WPCACHEHOME', 'COOKIE_DOMAIN', 'WP_SITEURL', 'WP_HOME');
  388. $config_found = DupUtil::string_has_value($config_vars, $config_file);
  389. //Files
  390. if ($config_found) {
  391. $msg = 'WP-CONFIG WARNING: The wp-config.php has one or more of these values "' . implode(", ", $config_vars) . '" which may cause issues please validate these values by opening the file.';
  392. $JSON['step2']['warnlist'][] = $msg;
  393. DupUtil::log($msg);
  394. }
  395. //Database
  396. $result = @mysqli_query($dbh, "SELECT option_value FROM `{$GLOBALS['FW_TABLEPREFIX']}options` WHERE option_name IN ('upload_url_path','upload_path')");
  397. if ($result) {
  398. while ($row = mysqli_fetch_row($result)) {
  399. if (strlen($row[0])) {
  400. $msg = "MEDIA SETTINGS WARNING: The table '{$GLOBALS['FW_TABLEPREFIX']}options' has at least one the following values ['upload_url_path','upload_path'] set please validate settings. These settings can be changed in the wp-admin by going to Settings->Media area see 'Uploading Files'";
  401. $JSON['step2']['warnlist'][] = $msg;
  402. DupUtil::log($msg);
  403. break;
  404. }
  405. }
  406. }
  407. if (empty($JSON['step2']['warnlist'])) {
  408. DupUtil::log("No Warnings Found\n");
  409. }
  410. $JSON['step2']['warn_all'] = empty($JSON['step2']['warnlist']) ? 0 : count($JSON['step2']['warnlist']);
  411. mysqli_close($dbh);
  412. @unlink('database.sql');
  413. $ajax2_end = DupUtil::get_microtime();
  414. $ajax2_sum = DupUtil::elapsed_time($ajax2_end, $ajax2_start);
  415. DupUtil::log("{$GLOBALS['SEPERATOR1']}");
  416. DupUtil::log('STEP 2 COMPLETE @ ' . @date('h:i:s') . " - TOTAL RUNTIME: {$ajax2_sum}" );
  417. DupUtil::log("{$GLOBALS['SEPERATOR1']}");
  418. $JSON['step2']['pass'] = 1;
  419. die(json_encode($JSON));
  420. ?>