PageRenderTime 66ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-content/plugins/wp-sync-db-master/class/wpsdb.php

https://gitlab.com/jessehall/hudson_alpha
PHP | 2521 lines | 2102 code | 312 blank | 107 comment | 407 complexity | 83433841bedc6559499bb5ec4eab577c MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. class WPSDB extends WPSDB_Base {
  3. protected $fp;
  4. protected $absolute_root_file_path;
  5. protected $form_defaults;
  6. protected $accepted_fields;
  7. protected $default_profile;
  8. protected $maximum_chunk_size;
  9. protected $current_chunk = '';
  10. protected $connection_details;
  11. protected $remote_url;
  12. protected $remote_key;
  13. protected $form_data;
  14. protected $max_insert_string_len;
  15. protected $row_tracker;
  16. protected $rows_per_segment = 100;
  17. protected $create_alter_table_query;
  18. protected $alter_table_name;
  19. protected $session_salt;
  20. protected $primary_keys;
  21. function __construct( $plugin_file_path ) {
  22. parent::__construct( $plugin_file_path );
  23. $this->plugin_slug = 'wp-sync-db';
  24. $this->plugin_version = $GLOBALS['wpsdb_meta']['wp-sync-db']['version'];
  25. $this->max_insert_string_len = 50000; // 50000 is the default as defined by phphmyadmin
  26. $default_settings = array(
  27. 'key' => $this->generate_key(),
  28. 'allow_pull' => false,
  29. 'allow_push' => false,
  30. 'profiles' => array(),
  31. 'verify_ssl' => false,
  32. 'blacklist_plugins' => array(),
  33. );
  34. if( empty( $this->settings['max_request'] ) ) {
  35. $this->settings['max_request'] = min( 1024 * 1024, $this->get_bottleneck( 'max' ) );
  36. update_option( 'wpsdb_settings', $this->settings );
  37. }
  38. // if no settings exist then this is a fresh install, set up some default settings
  39. if ( empty( $this->settings ) ) {
  40. $this->settings = $default_settings;
  41. update_option( 'wpsdb_settings', $this->settings );
  42. }
  43. // When we add a new setting, an existing customer's db won't have this
  44. // new setting, so we need to add it. Otherwise, they'll see
  45. // array index errors in debug mode
  46. else {
  47. $update_settings = false;
  48. foreach ( $default_settings as $key => $value ) {
  49. if ( !isset( $this->settings[$key] ) ) {
  50. $this->settings[$key] = $value;
  51. $update_settings = true;
  52. }
  53. }
  54. if ( $update_settings ) {
  55. update_option( 'wpsdb_settings', $this->settings );
  56. }
  57. }
  58. add_filter( 'plugin_action_links_' . $this->plugin_basename, array( $this, 'plugin_action_links' ) );
  59. add_filter( 'network_admin_plugin_action_links_' . $this->plugin_basename, array( $this, 'plugin_action_links' ) );
  60. // internal AJAX handlers
  61. add_action( 'wp_ajax_wpsdb_verify_connection_to_remote_site', array( $this, 'ajax_verify_connection_to_remote_site' ) );
  62. add_action( 'wp_ajax_wpsdb_reset_api_key', array( $this, 'ajax_reset_api_key' ) );
  63. add_action( 'wp_ajax_wpsdb_delete_migration_profile', array( $this, 'ajax_delete_migration_profile' ) );
  64. add_action( 'wp_ajax_wpsdb_save_setting', array( $this, 'ajax_save_setting' ) );
  65. add_action( 'wp_ajax_wpsdb_save_profile', array( $this, 'ajax_save_profile' ) );
  66. add_action( 'wp_ajax_wpsdb_initiate_migration', array( $this, 'ajax_initiate_migration' ) );
  67. add_action( 'wp_ajax_wpsdb_migrate_table', array( $this, 'ajax_migrate_table' ) );
  68. add_action( 'wp_ajax_wpsdb_finalize_migration', array( $this, 'ajax_finalize_migration' ) );
  69. add_action( 'wp_ajax_wpsdb_clear_log', array( $this, 'ajax_clear_log' ) );
  70. add_action( 'wp_ajax_wpsdb_get_log', array( $this, 'ajax_get_log' ) );
  71. add_action( 'wp_ajax_wpsdb_fire_migration_complete', array( $this, 'fire_migration_complete' ) );
  72. add_action( 'wp_ajax_wpsdb_update_max_request_size', array( $this, 'ajax_update_max_request_size' ) );
  73. add_action( 'wp_ajax_wpsdb_plugin_compatibility', array( $this, 'ajax_plugin_compatibility' ) );
  74. add_action( 'wp_ajax_wpsdb_blacklist_plugins', array( $this, 'ajax_blacklist_plugins' ) );
  75. add_action( 'wp_ajax_wpsdb_cancel_migration', array( $this, 'ajax_cancel_migration' ) );
  76. // external AJAX handlers
  77. add_action( 'wp_ajax_nopriv_wpsdb_verify_connection_to_remote_site', array( $this, 'respond_to_verify_connection_to_remote_site' ) );
  78. add_action( 'wp_ajax_nopriv_wpsdb_remote_initiate_migration', array( $this, 'respond_to_remote_initiate_migration' ) );
  79. add_action( 'wp_ajax_nopriv_wpsdb_process_chunk', array( $this, 'ajax_process_chunk' ) );
  80. add_action( 'wp_ajax_nopriv_wpsdb_process_pull_request', array( $this, 'respond_to_process_pull_request' ) );
  81. add_action( 'wp_ajax_nopriv_wpsdb_fire_migration_complete', array( $this, 'fire_migration_complete' ) );
  82. add_action( 'wp_ajax_nopriv_wpsdb_backup_remote_table', array( $this, 'respond_to_backup_remote_table' ) );
  83. add_action( 'wp_ajax_nopriv_wpsdb_remote_finalize_migration', array( $this, 'respond_to_remote_finalize_migration' ) );
  84. add_action( 'wp_ajax_nopriv_wpsdb_process_push_migration_cancellation', array( $this, 'respond_to_process_push_migration_cancellation' ) );
  85. // Clear update transients when the user clicks the "Check Again" button from the update screen
  86. add_action( 'current_screen', array( $this, 'check_again_clear_transients' ) );
  87. $absolute_path = rtrim( ABSPATH, '\\/' );
  88. $site_url = rtrim( site_url( '', 'http' ), '\\/' );
  89. $home_url = rtrim( home_url( '', 'http' ), '\\/' );
  90. if ( $site_url != $home_url ) {
  91. $difference = str_replace( $home_url, '', $site_url );
  92. if( strpos( $absolute_path, $difference ) !== false ) {
  93. $absolute_path = rtrim( substr( $absolute_path, 0, -strlen( $difference ) ), '\\/' );
  94. }
  95. }
  96. $this->absolute_root_file_path = $absolute_path;
  97. $this->accepted_fields = array(
  98. 'action',
  99. 'save_computer',
  100. 'gzip_file',
  101. 'connection_info',
  102. 'replace_old',
  103. 'replace_new',
  104. 'table_migrate_option',
  105. 'select_tables',
  106. 'replace_guids',
  107. 'exclude_spam',
  108. 'save_migration_profile',
  109. 'save_migration_profile_option',
  110. 'create_new_profile',
  111. 'create_backup',
  112. 'remove_backup',
  113. 'keep_active_plugins',
  114. 'select_post_types',
  115. 'backup_option',
  116. 'select_backup',
  117. 'exclude_transients',
  118. 'exclude_post_types'
  119. );
  120. $this->default_profile = array(
  121. 'action' => 'savefile',
  122. 'save_computer' => '1',
  123. 'gzip_file' => '1',
  124. 'table_migrate_option' => 'migrate_only_with_prefix',
  125. 'replace_guids' => '1',
  126. 'default_profile' => true,
  127. 'name' => '',
  128. 'select_tables' => array(),
  129. 'select_post_types' => array(),
  130. 'backup_option' => 'backup_only_with_prefix',
  131. 'exclude_transients' => '1',
  132. );
  133. $this->checkbox_options = array(
  134. 'save_computer' => '0',
  135. 'gzip_file' => '0',
  136. 'replace_guids' => '0',
  137. 'exclude_spam' => '0',
  138. 'keep_active_plugins' => '0',
  139. 'create_backup' => '0',
  140. 'exclude_post_types' =>'0'
  141. );
  142. if ( is_multisite() ) {
  143. add_action( 'network_admin_menu', array( $this, 'network_admin_menu' ) );
  144. }
  145. else {
  146. add_action( 'admin_menu', array( $this, 'admin_menu' ) );
  147. }
  148. add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
  149. // this is how many DB rows are processed at a time, allow devs to change this value
  150. $this->rows_per_segment = apply_filters( 'wpsdb_rows_per_segment', $this->rows_per_segment );
  151. if ( is_multisite() ) {
  152. add_action( 'network_admin_menu', array( $this, 'network_admin_menu' ) );
  153. $this->plugin_base = 'settings.php?page=wp-sync-db';
  154. }
  155. else {
  156. add_action( 'admin_menu', array( $this, 'admin_menu' ) );
  157. $this->plugin_base = 'tools.php?page=wp-sync-db';
  158. }
  159. }
  160. function ajax_blacklist_plugins() {
  161. $this->settings['blacklist_plugins'] = $_POST['blacklist_plugins'];
  162. update_option( 'wpsdb_settings', $this->settings );
  163. exit;
  164. }
  165. function ajax_plugin_compatibility() {
  166. $mu_dir = ( defined( 'WPMU_PLUGIN_DIR' ) && defined( 'WPMU_PLUGIN_URL' ) ) ? WPMU_PLUGIN_DIR : trailingslashit( WP_CONTENT_DIR ) . 'mu-plugins';
  167. $source = trailingslashit( $this->plugin_dir_path ) . 'compatibility/wp-sync-db-compatibility.php';
  168. $dest = trailingslashit( $mu_dir ) . 'wp-sync-db-compatibility.php';
  169. if ( '1' === trim( $_POST['install'] ) ) { // install MU plugin
  170. if ( !wp_mkdir_p( $mu_dir ) ) {
  171. _e( sprintf( 'The following directory could not be created: %s', $mu_dir ), 'wp-sync-db' );
  172. exit;
  173. }
  174. if ( !copy( $source, $dest ) ) {
  175. _e( sprintf( 'Could not copy the compatibility plugin from %1$s to %2$s', $source, $destination ), 'wp-sync-db' );
  176. exit;
  177. }
  178. } else { // uninstall MU plugin
  179. if ( file_exists( $dest ) && !unlink( $dest ) ) {
  180. _e( sprintf( 'Could not remove the compatibility plugin from %s', $dest ), 'wp-sync-db' );
  181. exit;
  182. }
  183. }
  184. exit;
  185. }
  186. function check_again_clear_transients( $current_screen ) {
  187. if( ! isset( $current_screen->id ) || strpos( $current_screen->id, 'update-core' ) === false || ! isset( $_GET['force-check'] ) ) return;
  188. delete_site_transient( 'wpsdb_upgrade_data' );
  189. delete_site_transient( 'update_plugins' );
  190. }
  191. function get_alter_table_name() {
  192. if ( ! is_null( $this->alter_table_name ) ) {
  193. return $this->alter_table_name;
  194. }
  195. global $wpdb;
  196. $this->alter_table_name = apply_filters( 'wpsdb_alter_table_name', $wpdb->prefix . 'wpsdb_alter_statements' );
  197. return $this->alter_table_name;
  198. }
  199. function get_create_alter_table_query() {
  200. if ( ! is_null( $this->create_alter_table_query ) ) {
  201. return $this->create_alter_table_query;
  202. }
  203. $alter_table_name = $this->get_alter_table_name();
  204. $this->create_alter_table_query = sprintf( "DROP TABLE IF EXISTS `%s`;\n", $alter_table_name );
  205. $this->create_alter_table_query .= sprintf( "CREATE TABLE `%s` ( `query` longtext NOT NULL );\n", $alter_table_name );
  206. $this->create_alter_table_query = apply_filters( 'wpsdb_create_alter_table_query', $this->create_alter_table_query );
  207. return $this->create_alter_table_query;
  208. }
  209. function get_short_uploads_dir() {
  210. $short_path = str_replace( $this->absolute_root_file_path, '', $this->get_upload_info( 'path' ) );
  211. return trailingslashit( substr( str_replace( '\\', '/', $short_path ), 1 ) );
  212. }
  213. function get_upload_info( $type = 'path' ) {
  214. // Let developers define their own path to for export files
  215. // Note: We require a very specific data set here, it should be similiar to the following
  216. // array(
  217. // 'path' => '/path/to/custom/uploads/directory', <- note missing end trailing slash
  218. // 'url' => 'http://yourwebsite.com/custom/uploads/directory' <- note missing end trailing slash
  219. // );
  220. $upload_info = apply_filters( 'wpsdb_upload_info', array() );
  221. if ( !empty( $upload_info ) ) {
  222. return $upload_info[$type];
  223. }
  224. $upload_dir = wp_upload_dir();
  225. $upload_info['path'] = $upload_dir['basedir'];
  226. $upload_info['url'] = $upload_dir['baseurl'];
  227. $upload_dir_name = apply_filters( 'wpsdb_upload_dir_name', 'wp-sync-db' );
  228. if( ! file_exists( $upload_dir['basedir'] . DS . $upload_dir_name ) ) {
  229. $url = wp_nonce_url( $this->plugin_base, 'wp-sync-db-nonce' );
  230. if( false === @mkdir( $upload_dir['basedir'] . DS . $upload_dir_name, 0755 ) ) {
  231. return $upload_info[$type];
  232. }
  233. $filename = $upload_dir['basedir'] . DS . $upload_dir_name . DS . 'index.php';
  234. if( false === @file_put_contents( $filename, "<?php\r\n// Silence is golden\r\n?>" ) ) {
  235. return $upload_info[$type];
  236. }
  237. }
  238. $upload_info['path'] .= DS . $upload_dir_name;
  239. $upload_info['url'] .= '/' . $upload_dir_name;
  240. return $upload_info[$type];
  241. }
  242. function ajax_update_max_request_size() {
  243. $this->check_ajax_referer( 'update-max-request-size' );
  244. $this->settings['max_request'] = (int) $_POST['max_request_size'] * 1024;
  245. update_option( 'wpsdb_settings', $this->settings );
  246. $result = $this->end_ajax();
  247. return $result;
  248. }
  249. function is_json( $string, $strict = false ) {
  250. $json = @json_decode( $string, true );
  251. if( $strict == true && ! is_array( $json ) ) return false;
  252. return ! ( $json == NULL || $json == false );
  253. }
  254. function get_sql_dump_info( $migration_type, $info_type ) {
  255. if( empty( $this->session_salt ) ) {
  256. $this->session_salt = strtolower( wp_generate_password( 5, false, false ) );
  257. }
  258. $datetime = date('YmdHis');
  259. $ds = ( $info_type == 'path' ? DS : '/' );
  260. return sprintf( '%s%s%s-%s-%s-%s.sql', $this->get_upload_info( $info_type ), $ds, sanitize_title_with_dashes( DB_NAME ), $migration_type, $datetime, $this->session_salt );
  261. }
  262. function parse_migration_form_data( $data ) {
  263. parse_str( $data, $form_data );
  264. $this->accepted_fields = apply_filters( 'wpsdb_accepted_profile_fields', $this->accepted_fields );
  265. $form_data = array_intersect_key( $form_data, array_flip( $this->accepted_fields ) );
  266. unset( $form_data['replace_old'][0] );
  267. unset( $form_data['replace_new'][0] );
  268. return $form_data;
  269. }
  270. function plugin_action_links( $links ) {
  271. $link = sprintf( '<a href="%s">%s</a>', network_admin_url( $this->plugin_base ), __( 'Settings', 'wp-sync-db' ) );
  272. array_unshift( $links, $link );
  273. return $links;
  274. }
  275. function ajax_clear_log() {
  276. $this->check_ajax_referer( 'clear-log' );
  277. delete_option( 'wpsdb_error_log' );
  278. $result = $this->end_ajax();
  279. return $result;
  280. }
  281. function ajax_get_log() {
  282. $this->check_ajax_referer( 'get-log' );
  283. ob_start();
  284. $this->output_diagnostic_info();
  285. $this->output_log_file();
  286. $return = ob_get_clean();
  287. $result = $this->end_ajax( $return );
  288. return $result;
  289. }
  290. function output_log_file() {
  291. $log = get_option( 'wpsdb_error_log' );
  292. if( $log ) {
  293. echo $log;
  294. }
  295. }
  296. function output_diagnostic_info() {
  297. global $table_prefix;
  298. global $wpdb;
  299. echo 'site_url(): ';
  300. echo site_url();
  301. echo "\r\n";
  302. echo 'home_url(): ';
  303. echo home_url();
  304. echo "\r\n";
  305. echo 'Table Prefix: ';
  306. echo $table_prefix;
  307. echo "\r\n";
  308. echo 'WordPress: ';
  309. if ( is_multisite() ) echo 'WPMU'; else echo 'WP'; echo bloginfo('version');
  310. echo "\r\n";
  311. echo 'Web Server: ';
  312. echo $_SERVER['SERVER_SOFTWARE'];
  313. echo "\r\n";
  314. echo 'PHP: ';
  315. if ( function_exists( 'phpversion' ) ) echo esc_html( phpversion() );
  316. echo "\r\n";
  317. echo 'MySQL: ';
  318. echo esc_html( empty( $wpdb->use_mysqli ) ? mysql_get_server_info() : mysqli_get_server_info( $wpdb->dbh ) );
  319. echo "\r\n";
  320. _e( 'ext/mysqli', 'wp-app-store' ); echo ': ';
  321. echo empty( $wpdb->use_mysqli ) ? 'no' : 'yes';
  322. echo "\r\n";
  323. _e( 'WP Memory Limit', 'wp-app-store' ); echo ': ';
  324. echo WP_MEMORY_LIMIT;
  325. echo "\r\n";
  326. echo 'WPSDB Bottleneck: ';
  327. echo size_format( $this->get_bottleneck() );
  328. echo "\r\n";
  329. if ( function_exists( 'ini_get' ) && $suhosin_limit = ini_get( 'suhosin.post.max_value_length' ) ) {
  330. echo 'Suhosin Post Max Value Length: ';
  331. echo is_numeric( $suhosin_limit ) ? size_format( $suhosin_limit ) : $suhosin_limit;
  332. echo "\r\n";
  333. }
  334. if ( function_exists( 'ini_get' ) && $suhosin_limit = ini_get( 'suhosin.request.max_value_length' ) ) {
  335. echo 'Suhosin Request Max Value Length: ';
  336. echo is_numeric( $suhosin_limit ) ? size_format( $suhosin_limit ) : $suhosin_limit;
  337. echo "\r\n";
  338. }
  339. echo 'Debug Mode: ';
  340. if ( defined('WP_DEBUG') && WP_DEBUG ) { echo 'Yes'; } else { echo 'No'; }
  341. echo "\r\n";
  342. echo 'WP Max Upload Size: ';
  343. echo size_format( wp_max_upload_size() );
  344. echo "\r\n";
  345. echo 'PHP Post Max Size: ';
  346. echo size_format( $this->get_post_max_size() );
  347. echo "\r\n";
  348. echo 'PHP Time Limit: ';
  349. if ( function_exists( 'ini_get' ) ) echo ini_get('max_execution_time');
  350. echo "\r\n";
  351. echo 'PHP Error Log: ';
  352. if ( function_exists( 'ini_get' ) ) echo ini_get('error_log');
  353. echo "\r\n";
  354. echo 'fsockopen: ';
  355. if ( function_exists( 'fsockopen' ) ) {
  356. echo 'Enabled';
  357. } else {
  358. echo 'Disabled';
  359. }
  360. echo "\r\n";
  361. echo 'OpenSSL: ';
  362. if ( $this->open_ssl_enabled() ) {
  363. echo OPENSSL_VERSION_TEXT;
  364. } else {
  365. echo 'Disabled';
  366. }
  367. echo "\r\n";
  368. echo 'cURL: ';
  369. if ( function_exists( 'curl_init' ) ) {
  370. echo 'Enabled';
  371. } else {
  372. echo 'Disabled';
  373. }
  374. echo "\r\n";
  375. echo "\r\n";
  376. echo "Active Plugins:\r\n";
  377. $active_plugins = (array) get_option( 'active_plugins', array() );
  378. if ( is_multisite() ) {
  379. $network_active_plugins = wp_get_active_network_plugins();
  380. $active_plugins = array_map( array( $this, 'remove_wp_plugin_dir' ), $network_active_plugins );
  381. }
  382. foreach ( $active_plugins as $plugin ) {
  383. $plugin_data = @get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
  384. if ( empty( $plugin_data['Name'] ) ) continue;
  385. printf( "%s (v%s) by %s\r\n", $plugin_data['Name'], $plugin_data['Version'], $plugin_data['AuthorName'] );
  386. }
  387. echo "\r\n";
  388. }
  389. function remove_wp_plugin_dir( $name ) {
  390. $plugin = str_replace( WP_PLUGIN_DIR, '', $name );
  391. return substr( $plugin, 1 );
  392. }
  393. function fire_migration_complete() {
  394. $filtered_post = $this->filter_post_elements( $_POST, array( 'action', 'url' ) );
  395. if ( ! $this->verify_signature( $filtered_post, $this->settings['key'] ) ) {
  396. $error_msg = $this->invalid_content_verification_error . ' (#138)';
  397. $this->log_error( $error_msg, $filtered_post );
  398. $result = $this->end_ajax( $error_msg );
  399. return $result;
  400. }
  401. do_action( 'wpsdb_migration_complete', 'pull', $_POST['url'] );
  402. $result = $this->end_ajax();
  403. return $result;
  404. }
  405. function get_alter_queries() {
  406. global $wpdb;
  407. $alter_table_name = $this->get_alter_table_name();
  408. $sql = '';
  409. $alter_queries = $wpdb->get_results( "SELECT * FROM `{$alter_table_name}`", ARRAY_A );
  410. if( ! empty( $alter_queries ) ) {
  411. foreach( $alter_queries as $alter_query ) {
  412. $sql .= $alter_query['query'];
  413. }
  414. }
  415. return $sql;
  416. }
  417. // After table migration, delete old tables and rename new tables removing the temporarily prefix
  418. function ajax_finalize_migration() {
  419. $this->check_ajax_referer( 'finalize-migration' );
  420. global $wpdb;
  421. $return = '';
  422. if ( $_POST['intent'] == 'pull' ) {
  423. $return = $this->finalize_migration();
  424. }
  425. else {
  426. do_action( 'wpsdb_migration_complete', 'push', $_POST['url'] );
  427. $data = $_POST;
  428. if ( isset( $data['nonce'] ) ) {
  429. unset( $data['nonce'] );
  430. }
  431. $data['action'] = 'wpsdb_remote_finalize_migration';
  432. $data['intent'] = 'pull';
  433. $data['prefix'] = $wpdb->prefix;
  434. $data['type'] = 'push';
  435. $data['location'] = home_url();
  436. $data['temp_prefix'] = $this->temp_prefix;
  437. $data['sig'] = $this->create_signature( $data, $data['key'] );
  438. $ajax_url = trailingslashit( $_POST['url'] ) . 'wp-admin/admin-ajax.php';
  439. $response = $this->remote_post( $ajax_url, $data, __FUNCTION__ );
  440. ob_start();
  441. echo $response;
  442. $this->display_errors();
  443. $return = ob_get_clean();
  444. }
  445. $result = $this->end_ajax( $return );
  446. return $result;
  447. }
  448. function respond_to_remote_finalize_migration() {
  449. $filtered_post = $this->filter_post_elements( $_POST, array( 'action', 'intent', 'url', 'key', 'form_data', 'prefix', 'type', 'location', 'tables', 'temp_prefix' ) );
  450. if ( ! $this->verify_signature( $filtered_post, $this->settings['key'] ) ) {
  451. $error_msg = $this->invalid_content_verification_error . ' (#123)';
  452. $this->log_error( $error_msg, $filtered_post );
  453. $result = $this->end_ajax( $error_msg );
  454. return $result;
  455. }
  456. $return = $this->finalize_migration();
  457. $result = $this->end_ajax( $return );
  458. return $result;
  459. }
  460. function finalize_migration() {
  461. global $wpdb;
  462. $tables = explode( ',', $_POST['tables'] );
  463. $temp_tables = array();
  464. foreach( $tables as $table ) {
  465. $temp_prefix = stripslashes( $_POST['temp_prefix'] );
  466. $temp_tables[] = $temp_prefix . $table;
  467. }
  468. $sql = "SET FOREIGN_KEY_CHECKS=0;\n";
  469. $preserved_options = array( 'wpsdb_settings', 'wpsdb_error_log' );
  470. $this->form_data = $this->parse_migration_form_data( $_POST['form_data'] );
  471. if( isset( $this->form_data['keep_active_plugins'] ) ) {
  472. $preserved_options[] = 'active_plugins';
  473. }
  474. $preserved_options = apply_filters( 'wpsdb_preserved_options', $preserved_options );
  475. foreach ( $temp_tables as $table ) {
  476. $sql .= 'DROP TABLE IF EXISTS ' . $this->backquote( substr( $table, strlen( $temp_prefix ) ) ) . ';';
  477. $sql .= "\n";
  478. $sql .= 'RENAME TABLE ' . $this->backquote( $table ) . ' TO ' . $this->backquote( substr( $table, strlen( $temp_prefix ) ) ) . ';';
  479. $sql .= "\n";
  480. }
  481. $preserved_options_data = $wpdb->get_results( sprintf( "SELECT * FROM %soptions WHERE `option_name` IN ('%s')", $wpdb->prefix, implode( "','", $preserved_options ) ), ARRAY_A );
  482. foreach( $preserved_options_data as $option ) {
  483. $sql .= $wpdb->prepare( "DELETE FROM `{$_POST['prefix']}options` WHERE `option_name` = %s;\n", $option['option_name'] );
  484. $sql .= $wpdb->prepare( "INSERT INTO `{$_POST['prefix']}options` ( `option_id`, `option_name`, `option_value`, `autoload` ) VALUES ( NULL , %s, %s, %s );\n", $option['option_name'], $option['option_value'], $option['autoload'] );
  485. }
  486. $alter_table_name = $this->get_alter_table_name();
  487. $sql .= $this->get_alter_queries();
  488. $sql .= "DROP TABLE IF EXISTS " . $this->backquote( $alter_table_name ) . ";\n";
  489. $process_chunk_result = $this->process_chunk( $sql );
  490. if( true !== $process_chunk_result ) {
  491. $result = $this->end_ajax( $process_chunk_result );
  492. return $result;
  493. }
  494. $type = ( isset( $_POST['type'] ) ? 'push' : 'pull' );
  495. $location = ( isset( $_POST['location'] ) ? $_POST['location'] : $_POST['url'] );
  496. if( ! isset( $_POST['location'] ) ) {
  497. $data = array();
  498. $data['action'] = 'wpsdb_fire_migration_complete';
  499. $data['url'] = home_url();
  500. $data['sig'] = $this->create_signature( $data, $_POST['key'] );
  501. $ajax_url = trailingslashit( $_POST['url'] ) . 'wp-admin/admin-ajax.php';
  502. $response = $this->remote_post( $ajax_url, $data, __FUNCTION__ );
  503. ob_start();
  504. echo $response;
  505. $this->display_errors();
  506. $maybe_errors = trim( ob_get_clean() );
  507. if( false === empty( $maybe_errors ) ) {
  508. $result = $this->end_ajax( $maybe_errors );
  509. return $result;
  510. }
  511. }
  512. // flush rewrite rules to prevent 404s and other oddities
  513. flush_rewrite_rules( true ); // true = hard refresh, recreates the .htaccess file
  514. do_action( 'wpsdb_migration_complete', $type, $location );
  515. }
  516. function ajax_process_chunk() {
  517. $filtered_post = $this->filter_post_elements( $_POST, array( 'action', 'table', 'chunk_gzipped' ) );
  518. $gzip = ( isset( $_POST['chunk_gzipped'] ) && $_POST['chunk_gzipped'] );
  519. $tmp_file_name = 'chunk.txt';
  520. if( $gzip ) {
  521. $tmp_file_name .= '.gz';
  522. }
  523. $tmp_file_path = wp_tempnam( $tmp_file_name );
  524. if ( !isset( $_FILES['chunk']['tmp_name'] ) || !move_uploaded_file( $_FILES['chunk']['tmp_name'], $tmp_file_path ) ) {
  525. $result = $this->end_ajax( __( 'Could not upload the SQL to the server. (#135)', 'wp-sync-db' ) );
  526. return $result;
  527. }
  528. if ( false === ( $chunk = file_get_contents( $tmp_file_path ) ) ) {
  529. $result = $this->end_ajax( __( 'Could not read the SQL file we uploaded to the server. (#136)', 'wp-sync-db' ) );
  530. return $result;
  531. }
  532. @unlink( $tmp_file_path );
  533. $filtered_post['chunk'] = $chunk;
  534. if ( !$this->verify_signature( $filtered_post, $this->settings['key'] ) ) {
  535. $error_msg = $this->invalid_content_verification_error . ' (#130)';
  536. $this->log_error( $error_msg, $filtered_post );
  537. $result = $this->end_ajax( $error_msg );
  538. return $result;
  539. }
  540. if ( $this->settings['allow_push'] != true ) {
  541. $result = $this->end_ajax( __( 'The connection succeeded but the remote site is configured to reject push connections. You can change this in the "settings" tab on the remote site. (#133)', 'wp-sync-db' ) );
  542. return $result;
  543. }
  544. if( $gzip ) {
  545. $filtered_post['chunk'] = gzuncompress( $filtered_post['chunk'] );
  546. }
  547. $process_chunk_result = $this->process_chunk( $filtered_post['chunk'] );
  548. $result = $this->end_ajax( $process_chunk_result );
  549. return $result;
  550. }
  551. function process_chunk( $chunk ) {
  552. // prepare db
  553. global $wpdb;
  554. $this->set_time_limit();
  555. $queries = array_filter( explode( ";\n", $chunk ) );
  556. array_unshift( $queries, "SET sql_mode='NO_AUTO_VALUE_ON_ZERO';" );
  557. ob_start();
  558. $wpdb->show_errors();
  559. if( empty( $wpdb->charset ) ) {
  560. $charset = ( defined( 'DB_CHARSET' ) ? DB_CHARSET : 'utf8' );
  561. $wpdb->charset = $charset;
  562. $wpdb->set_charset( $wpdb->dbh, $wpdb->charset );
  563. }
  564. foreach( $queries as $query ) {
  565. if( false === $wpdb->query( $query ) ) {
  566. $return = ob_get_clean();
  567. $result = $this->end_ajax( $return );
  568. return $result;
  569. }
  570. }
  571. return true;
  572. }
  573. function ajax_migrate_table() {
  574. $this->check_ajax_referer( 'migrate-table' );
  575. global $wpdb;
  576. $this->form_data = $this->parse_migration_form_data( $_POST['form_data'] );
  577. $result = '';
  578. // checks if we're performing a backup, if so, continue with the backup and exit immediately after
  579. if ( $_POST['stage'] == 'backup' && $_POST['intent'] != 'savefile' ) {
  580. // if performing a push we need to backup the REMOTE machine's DB
  581. if ( $_POST['intent'] == 'push' ) {
  582. $data = $_POST;
  583. if ( isset( $data['nonce'] ) ) {
  584. unset( $data['nonce'] );
  585. }
  586. $data['action'] = 'wpsdb_backup_remote_table';
  587. $data['intent'] = 'pull';
  588. $ajax_url = trailingslashit( $_POST['url'] ) . 'wp-admin/admin-ajax.php';
  589. $data['primary_keys'] = stripslashes( $data['primary_keys'] );
  590. $data['sig'] = $this->create_signature( $data, $data['key'] );
  591. $response = $this->remote_post( $ajax_url, $data, __FUNCTION__ );
  592. ob_start();
  593. $this->display_errors();
  594. $return = ob_get_clean();
  595. $return .= $response;
  596. }
  597. else {
  598. $return = $this->handle_table_backup();
  599. }
  600. $result = $this->end_ajax( $return );
  601. return $result;
  602. }
  603. // Pull and push need to be handled differently for obvious reasons, trigger different code depending on the migration intent (push or pull)
  604. if ( $_POST['intent'] == 'push' || $_POST['intent'] == 'savefile' ) {
  605. $this->maximum_chunk_size = $this->get_bottleneck();
  606. if ( isset( $_POST['bottleneck'] ) ) {
  607. $this->maximum_chunk_size = (int) $_POST['bottleneck'];
  608. }
  609. if ( $_POST['intent'] == 'push' ) {
  610. $this->remote_key = $_POST['key'];
  611. $this->remote_url = $_POST['url'];
  612. }
  613. $sql_dump_file_name = $this->get_upload_info( 'path' ) . DS;
  614. $sql_dump_file_name .= $this->format_dump_name( $_POST['dump_filename'] );
  615. if ( $_POST['intent'] == 'savefile' ) {
  616. $this->fp = $this->open( $sql_dump_file_name );
  617. }
  618. $result = $this->export_table( $_POST['table'] );
  619. if ( $_POST['intent'] == 'savefile' ) {
  620. $this->close( $this->fp );
  621. }
  622. ob_start();
  623. $this->display_errors();
  624. $maybe_errors = trim( ob_get_clean() );
  625. if( false === empty( $maybe_errors ) ) {
  626. $result = $this->end_ajax( $maybe_errors );
  627. return $result;
  628. }
  629. return $result;
  630. }
  631. else {
  632. $data = $_POST;
  633. if ( isset( $data['nonce'] ) ) {
  634. unset( $data['nonce'] );
  635. }
  636. $data['action'] = 'wpsdb_process_pull_request';
  637. $data['pull_limit'] = $this->get_sensible_pull_limit();
  638. if( is_multisite() ) {
  639. $data['path_current_site'] = $this->get_path_current_site();
  640. $data['domain_current_site'] = $this->get_domain_current_site();
  641. }
  642. $data['prefix'] = $wpdb->prefix;
  643. if ( isset( $data['sig'] ) ) {
  644. unset( $data['sig'] );
  645. }
  646. $ajax_url = trailingslashit( $data['url'] ) . 'wp-admin/admin-ajax.php';
  647. $data['primary_keys'] = stripslashes( $data['primary_keys'] );
  648. $data['sig'] = $this->create_signature( $data, $data['key'] );
  649. $response = $this->remote_post( $ajax_url, $data, __FUNCTION__ );
  650. ob_start();
  651. $this->display_errors();
  652. $maybe_errors = trim( ob_get_clean() );
  653. if( false === empty( $maybe_errors ) ) {
  654. $result = $this->end_ajax( $maybe_errors );
  655. return $result;
  656. }
  657. if( strpos( $response, ';' ) === false ) {
  658. $result = $this->end_ajax( $response );
  659. return $result;
  660. }
  661. // returned data is just a big string like this query;query;query;33
  662. // need to split this up into a chunk and row_tracker
  663. $row_information = trim( substr( strrchr( $response, "\n" ), 1 ) );
  664. $row_information = explode( ',', $row_information );
  665. $chunk = substr( $response, 0, strrpos( $response, ";\n" ) + 1 );
  666. if ( ! empty( $chunk ) ) {
  667. $process_chunk_result = $this->process_chunk( $chunk );
  668. if( true !== $process_chunk_result ) {
  669. $result = $this->end_ajax( $process_chunk_result );
  670. return $result;
  671. }
  672. }
  673. $result = $this->end_ajax( json_encode(
  674. array(
  675. 'current_row' => $row_information[0],
  676. 'primary_keys' => $row_information[1]
  677. )
  678. ) );
  679. }
  680. return $result;
  681. }
  682. function respond_to_backup_remote_table() {
  683. $filtered_post = $this->filter_post_elements( $_POST, array( 'action', 'intent', 'url', 'key', 'table', 'form_data', 'stage', 'bottleneck', 'prefix', 'current_row', 'dump_filename', 'last_table', 'gzip', 'primary_keys', 'path_current_site', 'domain_current_site' ) );
  684. $filtered_post['primary_keys'] = stripslashes( $filtered_post['primary_keys'] );
  685. if ( ! $this->verify_signature( $filtered_post, $this->settings['key'] ) ) {
  686. $error_msg = $this->invalid_content_verification_error . ' (#137)';
  687. $this->log_error( $error_msg, $filtered_post );
  688. $result = $this->end_ajax( $error_msg );
  689. return $result;
  690. }
  691. $this->form_data = $this->parse_migration_form_data( $_POST['form_data'] );
  692. $result = $this->handle_table_backup();
  693. return $result;
  694. }
  695. function handle_table_backup() {
  696. if ( isset( $this->form_data['gzip_file'] ) ) {
  697. unset( $this->form_data['gzip_file'] );
  698. }
  699. $this->maximum_chunk_size = $this->get_bottleneck();
  700. $sql_dump_file_name = $this->get_upload_info( 'path' ) . DS;
  701. $sql_dump_file_name .= $this->format_dump_name( $_POST['dump_filename'] );
  702. $file_created = file_exists( $sql_dump_file_name );
  703. $this->fp = $this->open( $sql_dump_file_name );
  704. if ( $file_created == false ) {
  705. $this->db_backup_header();
  706. }
  707. $result = $this->export_table( $_POST['table'] );
  708. if( isset( $this->fp ) ) {
  709. $this->close( $this->fp );
  710. }
  711. ob_start();
  712. $this->display_errors();
  713. $maybe_errors = trim( ob_get_clean() );
  714. if( false === empty( $maybe_errors ) ) {
  715. $result = $this->end_ajax( $maybe_errors );
  716. return $result;
  717. }
  718. return $result;
  719. }
  720. function respond_to_process_pull_request() {
  721. $filtered_post = $this->filter_post_elements( $_POST, array( 'action', 'intent', 'url', 'key', 'table', 'form_data', 'stage', 'bottleneck', 'prefix', 'current_row', 'dump_filename', 'pull_limit', 'last_table', 'gzip', 'primary_keys', 'path_current_site', 'domain_current_site' ) );
  722. // verification will fail unless we strip slashes on primary_keys and form_data
  723. $filtered_post['primary_keys'] = stripslashes( $filtered_post['primary_keys'] );
  724. $filtered_post['form_data'] = stripslashes( $filtered_post['form_data'] );
  725. if( isset( $filtered_post['path_current_site'] ) ) {
  726. $filtered_post['path_current_site'] = stripslashes( $filtered_post['path_current_site'] );
  727. }
  728. if ( ! $this->verify_signature( $filtered_post, $this->settings['key'] ) ) {
  729. $error_msg = $this->invalid_content_verification_error . ' (#124)';
  730. $this->log_error( $error_msg, $filtered_post );
  731. $result = $this->end_ajax( $error_msg );
  732. return $result;
  733. }
  734. if ( $this->settings['allow_pull'] != true ) {
  735. $result = $this->end_ajax( __( 'The connection succeeded but the remote site is configured to reject pull connections. You can change this in the "settings" tab on the remote site. (#132)', 'wp-sync-db' ) );
  736. return $result;
  737. }
  738. $this->maximum_chunk_size = $_POST['pull_limit'];
  739. $this->export_table( $_POST['table'] );
  740. ob_start();
  741. $this->display_errors();
  742. $return = ob_get_clean();
  743. $result = $this->end_ajax( $return );
  744. return $result;
  745. }
  746. // Occurs right before the first table is migrated / backed up during the migration process
  747. // Does a quick check to make sure the verification string is valid and also opens / creates files for writing to (if required)
  748. function ajax_initiate_migration() {
  749. $this->check_ajax_referer( 'initiate-migration' );
  750. $this->form_data = $this->parse_migration_form_data( $_POST['form_data'] );
  751. if ( $_POST['intent'] == 'savefile' ) {
  752. $return = array(
  753. 'code' => 200,
  754. 'message' => 'OK',
  755. 'body' => json_encode( array( 'error' => 0 ) ),
  756. );
  757. $return['dump_filename'] = basename( $this->get_sql_dump_info( 'migrate', 'path' ) );
  758. $return['dump_url'] = $this->get_sql_dump_info( 'migrate', 'url' );
  759. $dump_filename_no_extension = substr( $return['dump_filename'], 0, -4 );
  760. $create_alter_table_query = $this->get_create_alter_table_query();
  761. // sets up our table to store 'ALTER' queries
  762. $process_chunk_result = $this->process_chunk( $create_alter_table_query );
  763. if( true !== $process_chunk_result ) {
  764. $result = $this->end_ajax( $process_chunk_result );
  765. return $result;
  766. }
  767. if ( $this->gzip() && isset( $this->form_data['gzip_file'] ) ) {
  768. $return['dump_filename'] .= '.gz';
  769. $return['dump_url'] .= '.gz';
  770. }
  771. $this->fp = $this->open( $this->get_upload_info( 'path' ) . DS . $return['dump_filename'] );
  772. $this->db_backup_header();
  773. $this->close( $this->fp );
  774. $return['dump_filename'] = $dump_filename_no_extension;
  775. }
  776. else { // does one last check that our verification string is valid
  777. $data = array(
  778. 'action' => 'wpsdb_remote_initiate_migration',
  779. 'intent' => $_POST['intent'],
  780. 'form_data' => $_POST['form_data'],
  781. );
  782. $data['sig'] = $this->create_signature( $data, $_POST['key'] );
  783. $ajax_url = trailingslashit( $_POST['url'] ) . 'wp-admin/admin-ajax.php';
  784. $response = $this->remote_post( $ajax_url, $data, __FUNCTION__ );
  785. if ( false === $response ) {
  786. $return = array( 'wpsdb_error' => 1, 'body' => $this->error );
  787. $result = $this->end_ajax( json_encode( $return ) );
  788. return $result;
  789. }
  790. $return = @unserialize( trim( $response ) );
  791. if ( false === $return ) {
  792. $error_msg = __( 'Failed attempting to unserialize the response from the remote server. Please contact support.', 'wp-sync-db' );
  793. $return = array( 'wpsdb_error' => 1, 'body' => $error_msg );
  794. $this->log_error( $error_msg, $response );
  795. $result = $this->end_ajax( json_encode( $return ) );
  796. return $result;
  797. }
  798. if ( isset( $return['error'] ) && $return['error'] == 1 ) {
  799. $return = array( 'wpsdb_error' => 1, 'body' => $return['message'] );
  800. $result = $this->end_ajax( json_encode( $return ) );
  801. return $result;
  802. }
  803. if( $_POST['intent'] == 'pull' ) {
  804. // sets up our table to store 'ALTER' queries
  805. $create_alter_table_query = $this->get_create_alter_table_query();
  806. $process_chunk_result = $this->process_chunk( $create_alter_table_query );
  807. if( true !== $process_chunk_result ) {
  808. $result = $this->end_ajax( $process_chunk_result );
  809. return $result;
  810. }
  811. }
  812. if( ! empty( $this->form_data['create_backup'] ) && $_POST['intent'] == 'pull' ) {
  813. $return['dump_filename'] = basename( $this->get_sql_dump_info( 'backup', 'path' ) );
  814. $return['dump_filename'] = substr( $return['dump_filename'], 0, -4 );
  815. $return['dump_url'] = $this->get_sql_dump_info( 'backup', 'url' );
  816. }
  817. }
  818. $return['dump_filename'] = ( empty( $return['dump_filename'] ) ) ? '' : $return['dump_filename'];
  819. $return['dump_url'] = ( empty( $return['dump_url'] ) ) ? '' : $return['dump_url'];
  820. $result = $this->end_ajax( json_encode( $return ) );
  821. return $result;
  822. }
  823. // End point for the above remote_post call, ensures that the verification string is valid before continuing with the migration
  824. function respond_to_remote_initiate_migration() {
  825. $return = array();
  826. $filtered_post = $this->filter_post_elements( $_POST, array( 'action', 'intent', 'form_data' ) );
  827. if ( $this->verify_signature( $filtered_post, $this->settings['key'] ) ) {
  828. if ( isset( $this->settings['allow_' . $_POST['intent']] ) && ( true === $this->settings['allow_' . $_POST['intent']] || 1 === $this->settings['allow_' . $_POST['intent']] ) ) {
  829. $return['error'] = 0;
  830. }
  831. else {
  832. $return['error'] = 1;
  833. if( $_POST['intent'] == 'pull' ) {
  834. $intent = __( 'pull', 'wp-sync-db' );
  835. }
  836. else {
  837. $intent = __( 'push', 'wp-sync-db' );
  838. }
  839. $return['message'] = sprintf( __( 'The connection succeeded but the remote site is configured to reject %s connections. You can change this in the "settings" tab on the remote site. (#110)', 'wp-sync-db'), $intent );
  840. }
  841. }
  842. else {
  843. $return['error'] = 1;
  844. $error_msg = $this->invalid_content_verification_error . ' (#111)';
  845. $this->log_error( $error_msg, $filtered_post );
  846. $return['message'] = $error_msg;
  847. }
  848. $this->form_data = $this->parse_migration_form_data( $_POST['form_data'] );
  849. if( ! empty( $this->form_data['create_backup'] ) && $_POST['intent'] == 'push' ) {
  850. $return['dump_filename'] = basename( $this->get_sql_dump_info( 'backup', 'path' ) );
  851. $return['dump_filename'] = substr( $return['dump_filename'], 0, -4 );
  852. $return['dump_url'] = $this->get_sql_dump_info( 'backup', 'url' );
  853. }
  854. if( $_POST['intent'] == 'push' ) {
  855. // sets up our table to store 'ALTER' queries
  856. $create_alter_table_query = $this->get_create_alter_table_query();
  857. $process_chunk_result = $this->process_chunk( $create_alter_table_query );
  858. if( true !== $process_chunk_result ) {
  859. $result = $this->end_ajax( $process_chunk_result );
  860. return $result;
  861. }
  862. }
  863. $result = $this->end_ajax( serialize( $return ) );
  864. return $result;
  865. }
  866. function ajax_save_profile() {
  867. $this->check_ajax_referer( 'save-profile' );
  868. $profile = $this->parse_migration_form_data( $_POST['profile'] );
  869. $profile = wp_parse_args( $profile, $this->checkbox_options );
  870. if ( isset( $profile['save_migration_profile_option'] ) && $profile['save_migration_profile_option'] == 'new' ) {
  871. $profile['name'] = $profile['create_new_profile'];
  872. $this->settings['profiles'][] = $profile;
  873. }
  874. else {
  875. $key = $profile['save_migration_profile_option'];
  876. $name = $this->settings['profiles'][$key]['name'];
  877. $this->settings['profiles'][$key] = $profile;
  878. $this->settings['profiles'][$key]['name'] = $name;
  879. }
  880. update_option( 'wpsdb_settings', $this->settings );
  881. end( $this->settings['profiles'] );
  882. $key = key( $this->settings['profiles'] );
  883. $result = $this->end_ajax( $key );
  884. return $result;
  885. }
  886. function ajax_save_setting() {
  887. $this->check_ajax_referer( 'save-setting' );
  888. $this->settings[$_POST['setting']] = ( $_POST['checked'] == 'false' ? false : true );
  889. update_option( 'wpsdb_settings', $this->settings );
  890. $result = $this->end_ajax();
  891. return $result;
  892. }
  893. function ajax_delete_migration_profile() {
  894. $this->check_ajax_referer( 'delete-migration-profile' );
  895. $key = absint( $_POST['profile_id'] );
  896. --$key;
  897. $return = '';
  898. if ( isset( $this->settings['profiles'][$key] ) ) {
  899. unset( $this->settings['profiles'][$key] );
  900. update_option( 'wpsdb_settings', $this->settings );
  901. }
  902. else {
  903. $return = '-1';
  904. }
  905. $result = $this->end_ajax( $return );
  906. return $result;
  907. }
  908. function ajax_reset_api_key() {
  909. $this->check_ajax_referer( 'reset-api-key' );
  910. $this->settings['key'] = $this->generate_key();
  911. update_option( 'wpsdb_settings', $this->settings );
  912. $result = $this->end_ajax( sprintf( "%s\n%s", site_url( '', 'https' ), $this->settings['key'] ) );
  913. return $result;
  914. }
  915. // AJAX endpoint for when the user pastes into the connection info box (or when they click "connect")
  916. // Responsible for contacting the remote website and retrieving info and testing the verification string
  917. function ajax_verify_connection_to_remote_site() {
  918. $this->check_ajax_referer( 'verify-connection-to-remote-site' );
  919. $data = array(
  920. 'action' => 'wpsdb_verify_connection_to_remote_site',
  921. 'intent' => $_POST['intent']
  922. );
  923. $data['sig'] = $this->create_signature( $data, $_POST['key'] );
  924. $ajax_url = trailingslashit( $_POST['url'] ) . 'wp-admin/admin-ajax.php';
  925. $timeout = apply_filters( 'wpsdb_prepare_remote_connection_timeout', 10 );
  926. $response = $this->remote_post( $ajax_url, $data, __FUNCTION__, compact( 'timeout' ), true );
  927. $url_bits = parse_url( $this->attempting_to_connect_to );
  928. $return = $response;
  929. $alt_action = '';
  930. if ( false === $response ) {
  931. $return = array( 'wpsdb_error' => 1, 'body' => $this->error );
  932. $result = $this->end_ajax( json_encode( $return ) );
  933. return $result;
  934. }
  935. $response = unserialize( trim( $response ) );
  936. if ( false === $response ) {
  937. $error_msg = __( 'Failed attempting to unserialize the response from the remote server. Please contact support.', 'wp-sync-db' );
  938. $return = array( 'wpsdb_error' => 1, 'body' => $error_msg );
  939. $this->log_error( $error_msg );
  940. $result = $this->end_ajax( json_encode( $return ) );
  941. return $result;
  942. }
  943. if ( isset( $response['error'] ) && $response['error'] == 1 ) {
  944. $return = array( 'wpsdb_error' => 1, 'body' => $response['message'] );
  945. $this->log_error( $response['message'], $response );
  946. $result = $this->end_ajax( json_encode( $return ) );
  947. return $result;
  948. }
  949. if ( isset( $_POST['convert_post_type_selection'] ) && '1' == $_POST['convert_post_type_selection'] ) {
  950. $profile = (int) $_POST['profile'];
  951. unset( $this->settings['profiles'][$profile]['post_type_migrate_option'] );
  952. $this->settings['profiles'][$profile]['exclude_post_types'] = '1';
  953. $this->settings['profiles'][$profile]['select_post_types'] = array_values( array_diff( $response['post_types'], $this->settings['profiles'][$profile]['select_post_types'] ) );
  954. $response['select_post_types'] = $this->settings['profiles'][$profile]['select_post_types'];
  955. update_option( 'wpsdb_settings', $this->settings );
  956. }
  957. $response['scheme'] = $url_bits['scheme'];
  958. $return = json_encode( $response );
  959. $result = $this->end_ajax( $return );
  960. return $result;
  961. }
  962. // End point for the above remote_post call, returns table information, absolute file path, table prefix, etc
  963. function respond_to_verify_connection_to_remote_site() {
  964. global $wpdb;
  965. $return = array();
  966. $filtered_post = $this->filter_post_elements( $_POST, array( 'action', 'intent' ) );
  967. if ( !$this->verify_signature( $filtered_post, $this->settings['key'] ) ) {
  968. $return['error'] = 1;
  969. $return['message'] = $this->invalid_content_verification_error . ' (#120) <a href="#" class="try-again js-action-link">' . __( 'Try again?', 'wp-sync-db' ) . '</a>';
  970. $this->log_error( $this->invalid_content_verification_error . ' (#120)', $filtered_post );
  971. $result = $this->end_ajax( serialize( $return ) );
  972. return $result;
  973. }
  974. if ( !isset( $this->settings['allow_' . $_POST['intent']] ) || $this->settings['allow_' . $_POST['intent']] != true ) {
  975. $return['error'] = 1;
  976. if( $_POST['intent'] == 'pull' ) {
  977. $intent = __( 'pull', 'wp-sync-db' );
  978. }
  979. else {
  980. $intent = __( 'push', 'wp-sync-db' );
  981. }
  982. $return['message'] = sprintf( __( 'The connection succeeded but the remote site is configured to reject %s connections. You can change this in the "settings" tab on the remote site. (#122) <a href="#" class="try-again js-action-link">Try again?</a>', 'wp-sync-db' ), $intent );
  983. $result = $this->end_ajax( serialize( $return ) );
  984. return $result;
  985. }
  986. $return['tables'] = $this->get_tables();
  987. $return['prefixed_tables'] = $this->get_tables( 'prefix' );
  988. $return['table_sizes'] = $this->get_table_sizes();
  989. $return['table_rows'] = $this->get_table_row_count();
  990. $return['table_sizes_hr'] = array_map( array( $this, 'format_table_sizes' ), $this->get_table_sizes() );
  991. $return['path'] = $this->absolute_root_file_path;
  992. $return['url'] = home_url();
  993. $return['prefix'] = $wpdb->prefix;
  994. $return['bottleneck'] = $this->get_bottleneck();
  995. $return['error'] = 0;
  996. $return['plugin_version'] = $this->plugin_version;
  997. $return['domain'] = $this->get_domain_current_site();
  998. $return['path_current_site'] = $this->get_path_current_site();
  999. $return['uploads_dir'] = $this->get_short_uploads_dir();
  1000. $return['gzip'] = ( $this->gzip() ? '1' : '0' );
  1001. $return['post_types'] = $this->get_post_types();
  1002. $return['write_permissions'] = ( is_writeable( $this->get_upload_info( 'path' ) ) ? '1' : '0' );
  1003. $return['upload_dir_long'] = $this->get_upload_info( 'path' );
  1004. $return['temp_prefix'] = $this->temp_prefix;
  1005. $return = apply_filters( 'wpsdb_establish_remote_connection_data', $return );
  1006. $result = $this->end_ajax( serialize( $return ) );
  1007. return $result;
  1008. }
  1009. function format_table_sizes( $size ) {
  1010. $size *= 1024;
  1011. return size_format( $size );
  1012. }
  1013. // Generates our secret key
  1014. function generate_key() {
  1015. $keyset = 'abcdefghijklmnopqrstuvqxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/';
  1016. $key = '';
  1017. for ( $i = 0; $i < 32; $i++ ) {
  1018. $key .= substr( $keyset, rand( 0, strlen( $keyset ) -1 ), 1 );
  1019. }
  1020. return $key;
  1021. }
  1022. function get_post_types() {
  1023. global $wpdb;
  1024. if( is_multisite() ) {
  1025. $tables = $this->get_tables();
  1026. $sql = "SELECT `post_type` FROM `{$wpdb->prefix}posts` ";
  1027. foreach( $tables as $table ) {
  1028. if( 0 == preg_match( '/' . $wpdb->prefix . '[0-9]+_posts/', $table ) ) continue;
  1029. $blog_id = str_replace( array( $wpdb->prefix, '_posts' ), array( '', '' ), $table );
  1030. $sql .= "UNION SELECT `post_type` FROM `{$wpdb->prefix}" . $blog_id . "_posts` ";
  1031. }
  1032. $sql .= ";";
  1033. $post_types = $wpdb->get_results( $sql, ARRAY_A );
  1034. }
  1035. else {
  1036. $post_types = $wpdb->get_results(
  1037. "SELECT DISTINCT `post_type`
  1038. FROM `{$wpdb->prefix}posts`
  1039. WHERE 1;", ARRAY_A
  1040. );
  1041. }
  1042. $return = array( 'revision' );
  1043. foreach( $post_types as $post_type ) {
  1044. $return[] = $post_type['post_type'];
  1045. }
  1046. return apply_filters( 'wpsdb_post_types', array_unique( $return ) );
  1047. }
  1048. // Retrieves the specified profile, if -1, returns the default profile
  1049. function get_profile( $profile_id ) {
  1050. --$profile_id;
  1051. if ( $profile_id == '-1' || ! isset( $this->settings['profiles'][$profile_id] ) ) {
  1052. return $this->default_profile;
  1053. }
  1054. return $this->settings['profiles'][$profile_id];
  1055. }
  1056. function get_table_row_count() {
  1057. global $wpdb;
  1058. $results = $wpdb->get_results( $wpdb->prepare(
  1059. 'SELECT table_name, TABLE_ROWS FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = %s', DB_NAME
  1060. ), ARRAY_A
  1061. );
  1062. $return = array();
  1063. foreach( $results as $results ) {
  1064. $return[$results['table_name']] = ( $results['TABLE_ROWS'] == 0 ? 1 : $results['TABLE_ROWS'] );
  1065. }
  1066. return $return;
  1067. }
  1068. function get_table_sizes( $scope = 'regular' ) {
  1069. global $wpdb;
  1070. $prefix = ( $scope == 'temp' ? $this->temp_prefix : $wpdb->prefix );
  1071. $results = $wpdb->get_results( $wpdb->prepare(
  1072. 'SELECT TABLE_NAME AS "table",
  1073. ROUND((data_length + index_length)/1024,0) AS "size"
  1074. FROM information_schema.TABLES
  1075. WHERE information_schema.TABLES.table_schema="%s"
  1076. AND information_schema.TABLES.table_type="%s"', DB_NAME, "BASE TABLE"
  1077. ), ARRAY_A
  1078. );
  1079. $return = array();
  1080. foreach ( $results as $result ) {
  1081. $return[$result['table']] = $result['size'];
  1082. }
  1083. return apply_filters( 'wpsdb_table_sizes', $return, $scope );
  1084. }
  1085. function get_post_max_size() {
  1086. $val = trim( ini_get( 'post_max_size' ) );
  1087. $last = strtolower( $val[ strlen( $val ) - 1 ] );
  1088. switch ( $last ) {
  1089. case 'g':
  1090. $val *= 1024;
  1091. case 'm':
  1092. $val *= 1024;
  1093. case 'k':
  1094. $val *= 1024;
  1095. }
  1096. return $val;
  1097. }
  1098. function get_sensible_pull_limit() {
  1099. return apply_filters( 'wpsdb_sensible_pull_limit', min( 26214400, $this->settings['max_request'] ) );
  1100. }
  1101. function get_bottleneck( $type = 'regular' ) {
  1102. $suhosin_limit = false;
  1103. $suhosin_request_limit = false;
  1104. $suhosin_post_limit = false;
  1105. if ( function_exists( 'ini_get' ) ) {
  1106. $suhosin_request_limit = $this->return_bytes( ini_get( 'suhosin.request.max_value_length' ) );
  1107. $suhosin_post_limit = $this->return_bytes( ini_get( 'suhosin.post.max_value_length' ) );
  1108. }
  1109. if ( $suhosin_request_limit && $suhosin_post_limit ) {
  1110. $suhosin_limit = min( $suhosin_request_limit, $suhosin_post_limit );
  1111. }
  1112. // we have to account for HTTP headers and other bloating, here we minus 1kb for bloat
  1113. $post_max_upper_size = apply_filters( 'wpsdb_post_max_upper_size', 26214400 );
  1114. $calculated_bottleneck = min( ( $this->get_post_max_size() - 1024 ), $post_max_upper_size );
  1115. if ( $suhosin_limit ) {
  1116. $calculated_bottleneck = min( $calculated_bottleneck, $suhosin_limit - 1024 );
  1117. }
  1118. if( $type != 'max' ) {
  1119. $calculated_bottleneck = min( $calculated_bottleneck, $this->settings['max_request'] );
  1120. }
  1121. return apply_filters( 'wpsdb_bottleneck', $calculated_bottleneck );
  1122. }
  1123. function format_dump_name( $dump_name ) {
  1124. $extension = '.sql';
  1125. $dump_name = sanitize_file_name( $dump_name );
  1126. if ( $this->gzip() && isset( $this->form_data['gzip_file'] ) ) {
  1127. $extension .= '.gz';
  1128. }
  1129. return $dump_name . $extension;
  1130. }
  1131. function options_page() {
  1132. ?>
  1133. <div class="wrap wpsdb">
  1134. <div id="icon-tools" class="icon32"><br /></div><h2>Migrate DB</h2>
  1135. <h2 class="nav-tab-wrapper"><a href="#" class="nav-tab nav-tab-active js-action-link migrate" data-div-name="migrate-tab"><?php _e( 'Migrate', 'wp-sync-db' ); ?></a><a href="#" class="nav-tab js-action-link settings" data-div-name="settings-tab"><?php _e( 'Settings', 'wp-sync-db' ); ?></a><a href="#" class="nav-tab js-action-link help" data-div-name="help-tab"><?php _e( 'Help', 'wp-sync-db' ); ?></a></h2>
  1136. <?php do_action( 'wpsdb_notices' ); ?>
  1137. <?php
  1138. $hide_warning = apply_filters( 'wpsdb_hide_outdated_addons_warning', false );
  1139. foreach( $this->addons as $addon_basename => $addon ) {
  1140. if( false == $this->is_addon_outdated( $addon_basename ) || false == is_plugin_active( $addon_basename ) ) continue;
  1141. $update_url = wp_nonce_url( network_admin_url( 'update.php?action=upgrade-plugin&plugin=' . urlencode( $addon_basename ) ), 'upgrade-plugin_' . $addon_basename );
  1142. $addon_slug = current( explode( '/', $addon_basename ) );
  1143. if ( isset( $GLOBALS['wpsdb_meta'][$addon_slug]['version'] ) ) {
  1144. $version = ' (' . $GLOBALS['wpsdb_meta'][$addon_slug]['version'] . ')';
  1145. }
  1146. else {
  1147. $version = '';
  1148. }
  1149. ?>
  1150. <div class="updated warning inline-message">
  1151. <strong>Update Required</strong> &mdash;
  1152. <?php printf( __( 'The version of the %1$s addon you have installed%2$s is out-of-date and will not work with this version WP Sync DB. <a href="%3$s">Update Now</a>', 'wp-sync-db' ), $addon['name'], $version, $update_url ); ?>
  1153. </div>
  1154. <?php
  1155. }
  1156. $hide_warning = apply_filters( 'wpsdb_hide_safe_mode_warning', false );
  1157. if ( function_exists( 'ini_get' ) && ini_get( 'safe_mode' ) && !$hide_warning ) { ?>
  1158. <div class="updated warning inline-message">
  1159. <?php
  1160. _e( "<strong>PHP Safe Mode Enabled</strong> &mdash; We do not officially support running this plugin in safe mode because <code>set_time_limit()</code> has no effect. Therefore we can't extend the run time of the script and ensure it doesn't time out before the migration completes. We haven't disabled the plugin however, so you're free to cross your fingers and hope for the best. However, if you have trouble, we can't help you until you turn off safe mode.", 'wp-sync-db' );
  1161. if ( function_exists( 'ini_get' ) ) {
  1162. printf( __( 'Your current PHP run time limit is set to %s seconds.', 'wp-sync-db' ), ini_get( 'max_execution_time' ) );
  1163. } ?>
  1164. </div>
  1165. <?php
  1166. }
  1167. ?>
  1168. <div class="updated warning ie-warning inline-message" style="display: none;">
  1169. <?php _e( "<strong>Internet Explorer Not Supported</strong> &mdash; Less than 2% of our customers use IE, so we've decided not to spend time supporting it. We ask that you use Firefox or a Webkit-based browser like Chrome or Safari instead. If this is a problem for you, please let us know.", 'wp-sync-db' ); ?>
  1170. </div>
  1171. <?php
  1172. $hide_warning = apply_filters( 'wpsdb_hide_set_time_limit_warning', false );
  1173. if ( false == $this->set_time_limit_available() && !$hide_warning && !$safe_mode ) {
  1174. ?>
  1175. <div class="updated warning inline-message">
  1176. <?php
  1177. _e( "<strong>PHP Function Disabled</strong> &mdash; The <code>set_time_limit()</code> function is currently disabled on your server. We use this function to ensure that the migration doesn't time out. We haven't disabled the plugin however, so you're free to cross your fingers and hope for the best. You may want to contact your web host to enable this function.", 'wp-sync-db' );
  1178. if ( function_exists( 'ini_get' ) ) {
  1179. printf( __( 'Your current PHP run time limit is set to %s seconds.', 'wp-sync-db' ), ini_get( 'max_execution_time' ) );
  1180. } ?>
  1181. </div>
  1182. <?php
  1183. }
  1184. ?>
  1185. <div id="wpsdb-main">
  1186. <?php
  1187. // select profile if more than > 1 profile saved
  1188. if ( ! empty( $this->settings['profiles'] ) && ! isset( $_GET['wpsdb-profile'] ) ) {
  1189. $this->template( 'profile' );
  1190. }
  1191. else {
  1192. $this->template( 'migrate' );
  1193. }
  1194. $this->template( 'settings' );
  1195. $this->template( 'help' );
  1196. ?>
  1197. </div> <!-- end #wpsdb-main -->
  1198. </div> <!-- end .wrap -->
  1199. <?php
  1200. }
  1201. function apply_replaces( $subject, $is_serialized = false ) {
  1202. $search = $this->form_data['replace_old'];
  1203. $replace = $this->form_data['replace_new'];
  1204. $new = str_ireplace( $search, $replace, $subject, $count );
  1205. /*
  1206. * Automatically replace URLs for subdomain based multisite installations
  1207. * e.g. //site1.example.com -> //site1.example.local for site with domain example.com
  1208. * NB: only handles the current network site, does not work for additional networks / mapped domains
  1209. */
  1210. $subdomain_replace_enabled = apply_filters( 'wpsdb_subdomain_replace', true ); // allow developers to turn off this functionality
  1211. if ( $subdomain_replace_enabled && is_multisite() && defined( 'SUBDOMAIN_INSTALL' ) && SUBDOMAIN_INSTALL & !empty( $_POST['domain_current_site'] ) ) {
  1212. $pattern = '|//(.*?)\\.' . preg_quote( $this->get_domain_current_site(), '|' ) . '|';
  1213. $replacement = '//$1.' . trim( $_POST['domain_current_site'] );
  1214. $new = preg_replace( $pattern, $replacement, $new );
  1215. }
  1216. return $new;
  1217. }
  1218. function process_sql_constraint( $create_query, $table, &$alter_table_query ) {
  1219. if( preg_match( '@CONSTRAINT|FOREIGN[\s]+KEY@', $create_query ) ) {
  1220. $sql_constraints_query = '';
  1221. $nl_nix = "\n";
  1222. $nl_win = "\r\n";
  1223. $nl_mac = "\r";
  1224. if( strpos( $create_query, $nl_win ) !== false ) {
  1225. $crlf = $nl_win;
  1226. }
  1227. elseif( strpos( $create_query, $nl_mac ) !== false ) {
  1228. $crlf = $nl_mac;
  1229. }
  1230. elseif( strpos( $create_query, $nl_nix ) !== false ) {
  1231. $crlf = $nl_nix;
  1232. }
  1233. // Split the query into lines, so we can easily handle it.
  1234. // We know lines are separated by $crlf (done few lines above).
  1235. $sql_lines = explode( $crlf, $create_query );
  1236. $sql_count = count( $sql_lines );
  1237. // lets find first line with constraints
  1238. for( $i = 0; $i < $sql_count; $i++ ) {
  1239. if (preg_match(
  1240. '@^[\s]*(CONSTRAINT|FOREIGN[\s]+KEY)@',
  1241. $sql_lines[$i]
  1242. )) {
  1243. break;
  1244. }
  1245. }
  1246. // If we really found a constraint
  1247. if( $i != $sql_count ) {
  1248. // remove, from the end of create statement
  1249. $sql_lines[$i - 1] = preg_replace(
  1250. '@,$@',
  1251. '',
  1252. $sql_lines[$i - 1]
  1253. );
  1254. // let's do the work
  1255. $sql_constraints_query .= 'ALTER TABLE '
  1256. . $this->backquote( $table )
  1257. . $crlf;
  1258. $first = true;
  1259. for( $j = $i; $j < $sql_count; $j++ ) {
  1260. if( preg_match(
  1261. '@CONSTRAINT|FOREIGN[\s]+KEY@',
  1262. $sql_lines[$j]
  1263. )) {
  1264. if( strpos( $sql_lines[$j], 'CONSTRAINT' ) === false ) {
  1265. $tmp_str = preg_replace(
  1266. '/(FOREIGN[\s]+KEY)/',
  1267. 'ADD \1',
  1268. $sql_lines[$j]
  1269. );
  1270. $sql_constraints_query .= $tmp_str;
  1271. }
  1272. else {
  1273. $tmp_str = preg_replace(
  1274. '/(CONSTRAINT)/',
  1275. 'ADD \1',
  1276. $sql_lines[$j]
  1277. );
  1278. $sql_constraints_query .= $tmp_str;
  1279. preg_match(
  1280. '/(CONSTRAINT)([\s])([\S]*)([\s])/',
  1281. $sql_lines[$j],
  1282. $matches
  1283. );
  1284. }
  1285. $first = false;
  1286. }
  1287. else {
  1288. break;
  1289. }
  1290. }
  1291. $sql_constraints_query .= ";\n";
  1292. $create_query = implode(
  1293. $crlf,
  1294. array_slice($sql_lines, 0, $i)
  1295. )
  1296. . $crlf
  1297. . implode(
  1298. $crlf,
  1299. array_slice( $sql_lines, $j, $sql_count - 1 )
  1300. );
  1301. unset( $sql_lines );
  1302. $alter_table_query = $sql_constraints_query;
  1303. return $create_query;
  1304. }
  1305. }
  1306. return $create_query;
  1307. }
  1308. /**
  1309. * Taken partially from phpMyAdmin and partially from
  1310. * Alain Wolf, Zurich - Switzerland
  1311. * Website: http://restkultur.ch/personal/wolf/scripts/db_backup/
  1312. * Modified by Scott Merrill (http://www.skippy.net/)
  1313. * to use the WordPress $wpdb object
  1314. *
  1315. * @param string $table
  1316. * @return void
  1317. */
  1318. function export_table( $table ) {
  1319. global $wpdb;
  1320. $this->set_time_limit();
  1321. if ( empty( $this->form_data ) ) {
  1322. $this->form_data = $this->parse_migration_form_data( $_POST['form_data'] );
  1323. }
  1324. $temp_prefix = $this->temp_prefix;
  1325. $remote_prefix = ( isset( $_POST['prefix'] ) ? $_POST['prefix'] : $wpdb->prefix );
  1326. $table_structure = $wpdb->get_results( "DESCRIBE " . $this->backquote( $table ) );
  1327. if ( ! $table_structure ) {
  1328. $this->error = __( 'Failed to retrieve table structure, please ensure your database is online. (#125)', 'wp-sync-db' );
  1329. return false;
  1330. }
  1331. $current_row = -1;
  1332. if ( ! empty( $_POST['current_row'] ) ) {
  1333. $temp_current_row = trim( $_POST['current_row'] );
  1334. if ( ! empty( $temp_current_row ) ) {
  1335. $current_row = (int) $temp_current_row;
  1336. }
  1337. }
  1338. if ( $current_row == -1 ) {
  1339. // Add SQL statement to drop existing table
  1340. if ( $this->form_data['action'] == 'savefile' || $_POST['stage'] == 'backup' ) {
  1341. $this->stow( "\n\n" );
  1342. $this->stow( "#\n" );
  1343. $this->stow( "# " . sprintf( __( 'Delete any existing table %s', 'wp-sync-db' ), $this->backquote( $table ) ) . "\n" );
  1344. $this->stow( "#\n" );
  1345. $this->stow( "\n" );
  1346. $this->stow( "DROP TABLE IF EXISTS " . $this->backquote( $table ) . ";\n" );
  1347. }
  1348. else {
  1349. $this->stow( "DROP TABLE IF EXISTS " . $this->backquote( $temp_prefix . $table ) . ";\n" );
  1350. }
  1351. // Table structure
  1352. // Comment in SQL-file
  1353. if ( $this->form_data['action'] == 'savefile' || $_POST['stage'] == 'backup' ) {
  1354. $this->stow( "\n\n" );
  1355. $this->stow( "#\n" );
  1356. $this->stow( "# " . sprintf( __( 'Table structure of table %s', 'wp-sync-db' ), $this->backquote( $table ) ) . "\n" );
  1357. $this->stow( "#\n" );
  1358. $this->stow( "\n" );
  1359. }
  1360. $create_table = $wpdb->get_results( "SHOW CREATE TABLE " . $this->backquote( $table ), ARRAY_N );
  1361. if ( false === $create_table ) {
  1362. $this->error = __( 'Failed to generate the create table query, please ensure your database is online. (#126)', 'wp-sync-db' );
  1363. return false;
  1364. }
  1365. if ( $this->form_data['action'] != 'savefile' && $_POST['stage'] != 'backup' ) {
  1366. $create_table[0][1] = str_replace( 'CREATE TABLE `', 'CREATE TABLE `' . $temp_prefix, $create_table[0][1] );
  1367. }
  1368. $create_table[0][1] = str_replace( 'TYPE=', 'ENGINE=', $create_table[0][1] );
  1369. $alter_table_query = '';
  1370. $create_table[0][1] = $this->process_sql_constraint( $create_table[0][1], $table, $alter_table_query );
  1371. $create_table[0][1] = apply_filters( 'wpsdb_create_table_query', $create_table[0][1], $table );
  1372. $this->stow( $create_table[0][1] . ";\n" );
  1373. if( ! empty( $alter_table_query ) ) {
  1374. $alter_table_name = $this->get_alter_table_name();
  1375. $insert = sprintf( "INSERT INTO %s ( `query` ) VALUES ( '%s' );\n", $this->backquote( $alter_table_name ), esc_sql( $alter_table_query ) );
  1376. if ( $this->form_data['action'] == 'savefile' || $_POST['stage'] == 'backup' ) {
  1377. $process_chunk_result = $this->process_chunk( $insert );
  1378. if( true !== $process_chunk_result ) {
  1379. $result = $this->end_ajax( $process_chunk_result );
  1380. return $result;
  1381. }
  1382. }
  1383. else {
  1384. $this->stow( $insert );
  1385. }
  1386. }
  1387. // Comment in SQL-file
  1388. if ( $this->form_data['action'] == 'savefile' || $_POST['stage'] == 'backup' ) {
  1389. $this->stow( "\n\n" );
  1390. $this->stow( "#\n" );
  1391. $this->stow( '# ' . sprintf( __( 'Data contents of table %s', 'wp-sync-db' ), $this->backquote( $table ) ) . "\n" );
  1392. $this->stow( "#\n" );
  1393. }
  1394. }
  1395. // $defs = mysql defaults, looks up the default for that paricular column, used later on to prevent empty inserts values for that column
  1396. // $ints = holds a list of the possible integar types so as to not wrap them in quotation marks later in the insert statements
  1397. $defs = array();
  1398. $ints = array();
  1399. foreach ( $table_structure as $struct ) {
  1400. if ( ( 0 === strpos( $struct->Type, 'tinyint' ) ) ||
  1401. ( 0 === strpos( strtolower( $struct->Type ), 'smallint' ) ) ||
  1402. ( 0 === strpos( strtolower( $struct->Type ), 'mediumint' ) ) ||
  1403. ( 0 === strpos( strtolower( $struct->Type ), 'int' ) ) ||
  1404. ( 0 === strpos( strtolower( $struct->Type ), 'bigint' ) ) ) {
  1405. $defs[strtolower( $struct->Field )] = ( null === $struct->Default ) ? 'NULL' : $struct->Default;
  1406. $ints[strtolower( $struct->Field )] = "1";
  1407. }
  1408. }
  1409. // Batch by $row_inc
  1410. $row_inc = $this->rows_per_segment;
  1411. $row_start = 0;
  1412. if ( $current_row != -1 ) {
  1413. $row_start = $current_row;
  1414. }
  1415. $this->row_tracker = $row_start;
  1416. // \x08\\x09, not required
  1417. $search = array( "\x00", "\x0a", "\x0d", "\x1a" );
  1418. $replace = array( '\0', '\n', '\r', '\Z' );
  1419. $query_size = 0;
  1420. $table_name = $table;
  1421. if ( $this->form_data['action'] != 'savefile' && $_POST['stage'] != 'backup' ) {
  1422. $table_name = $temp_prefix . $table;
  1423. }
  1424. $this->primary_keys = array();
  1425. $use_primary_keys = true;
  1426. foreach( $table_structure as $col ){
  1427. $field_set[] = $this->backquote( $col->Field );
  1428. if( $col->Key == 'PRI' && true == $use_primary_keys ) {
  1429. if( false === strpos( $col->Type, 'int' ) ) {
  1430. $use_primary_keys = false;
  1431. $this->primary_keys = array();
  1432. continue;
  1433. }
  1434. $this->primary_keys[$col->Field] = 0;
  1435. }
  1436. }
  1437. $first_select = true;
  1438. if( ! empty( $_POST['primary_keys'] ) ) {
  1439. $_POST['primary_keys'] = trim( $_POST['primary_keys'] );
  1440. if( ! empty( $_POST['primary_keys'] ) && is_serialized( $_POST['primary_keys'] ) ) {
  1441. $this->primary_keys = unserialize( stripslashes( $_POST['primary_keys'] ) );
  1442. $first_select = false;
  1443. }
  1444. }
  1445. $fields = implode( ', ', $field_set );
  1446. $insert_buffer = $insert_query_template = "INSERT INTO " . $this->backquote( $table_name ) . " ( " . $fields . ") VALUES\n";
  1447. do {
  1448. $join = array();
  1449. $where = 'WHERE 1=1';
  1450. $order_by = '';
  1451. // We need ORDER BY here because with LIMIT, sometimes it will return
  1452. // the same results from the previous query and we'll have duplicate insert statements
  1453. if ( 'backup' != $_POST['stage'] && isset( $this->form_data['exclude_spam'] ) ) {
  1454. if ( $this->table_is( 'comments', $table ) ) {
  1455. $where .= ' AND comment_approved != "spam"';
  1456. }
  1457. elseif ( $this->table_is( 'commentmeta', $table ) ) {
  1458. extract( $this->get_ms_compat_table_names( array( 'commentmeta', 'comments' ), $table ) );
  1459. $join[] = sprintf( 'INNER JOIN %1$s ON %1$s.comment_ID = %2$s.comment_id', $this->backquote( $comments_table ), $this->backquote( $commentmeta_table ) );
  1460. $where .= sprintf( ' AND %1$s.comment_approved != \'spam\'', $this->backquote( $comments_table ) );
  1461. }
  1462. }
  1463. if ( 'backup' != $_POST['stage'] && isset( $this->form_data['exclude_post_types'] ) && ! empty( $this->form_data['select_post_types'] ) ) {
  1464. $post_types = '\'' . implode( '\', \'', $this->form_data['select_post_types'] ) . '\'';
  1465. if( $this->table_is( 'posts', $table ) ) {
  1466. $where .= ' AND `post_type` NOT IN ( ' . $post_types . ' )';
  1467. }
  1468. elseif( $this->table_is( 'postmeta', $table ) ) {
  1469. extract( $this->get_ms_compat_table_names( array( 'postmeta', 'posts' ), $table ) );
  1470. $join[] = sprintf( 'INNER JOIN %1$s ON %1$s.ID = %2$s.post_id', $this->backquote( $posts_table ), $this->backquote( $postmeta_table ) );
  1471. $where .= sprintf( ' AND %1$s.post_type NOT IN ( ' . $post_types . ' )', $this->backquote( $posts_table ) );
  1472. }
  1473. elseif ( $this->table_is( 'comments', $table ) ) {
  1474. extract( $this->get_ms_compat_table_names( array( 'comments', 'posts' ), $table ) );
  1475. $join[] = sprintf( 'INNER JOIN %1$s ON %1$s.ID = %2$s.comment_post_ID', $this->backquote( $posts_table ), $this->backquote( $comments_table ) );
  1476. $where .= sprintf( ' AND %1$s.post_type NOT IN ( ' . $post_types . ' )', $this->backquote( $posts_table ) );
  1477. }
  1478. elseif( $this->table_is( 'commentmeta', $table ) ) {
  1479. extract( $this->get_ms_compat_table_names( array( 'commentmeta', 'posts', 'comments' ), $table ) );
  1480. $join[] = sprintf( 'INNER JOIN %1$s ON %1$s.comment_ID = %2$s.comment_id', $this->backquote( $comments_table ), $this->backquote( $commentmeta_table ) );
  1481. $join[] = sprintf( 'INNER JOIN %2$s ON %2$s.ID = %1$s.comment_post_ID', $this->backquote( $comments_table ), $this->backquote( $posts_table ) );
  1482. $where .= sprintf( ' AND %1$s.post_type NOT IN ( ' . $post_types . ' )', $this->backquote( $posts_table ) );
  1483. }
  1484. }
  1485. if ( 'backup' != $_POST['stage'] && true === apply_filters( 'wpsdb_exclude_transients', true ) && isset( $this->form_data['exclude_transients'] ) && '1' === $this->form_data['exclude_transients'] && ( $this->table_is( 'options', $table ) || ( isset( $wpdb->sitemeta ) && $wpdb->sitemeta == $table ) ) ) {
  1486. $col_name = 'option_name';
  1487. if( isset( $wpdb->sitemeta ) && $wpdb->sitemeta == $table ) {
  1488. $col_name = 'meta_key';
  1489. }
  1490. $where .= " AND `{$col_name}` NOT LIKE '\_transient\_%' AND `{$col_name}` NOT LIKE '\_site\_transient\_%'";
  1491. }
  1492. $limit = "LIMIT {$row_start}, {$row_inc}";
  1493. if( ! empty( $this->primary_keys ) ) {
  1494. $primary_keys_keys = array_keys( $this->primary_keys );
  1495. $primary_keys_keys = array_map( array( $this, 'backquote' ), $primary_keys_keys );
  1496. $order_by = 'ORDER BY ' . implode( ',', $primary_keys_keys );
  1497. $limit = "LIMIT $row_inc";
  1498. if( false == $first_select ) {
  1499. $where .= ' AND ';
  1500. $temp_primary_keys = $this->primary_keys;
  1501. $primary_key_count = count( $temp_primary_keys );
  1502. // build a list of clauses, iteratively reducing the number of fields compared in the compound key
  1503. // e.g. (a = 1 AND b = 2 AND c > 3) OR (a = 1 AND b > 2) OR (a > 1)
  1504. $clauses = array();
  1505. for( $j = 0; $j < $primary_key_count; $j++ ) {
  1506. // build a subclause for each field in the compound index
  1507. $subclauses = array();
  1508. $i = 0;
  1509. foreach( $temp_primary_keys as $primary_key => $value ) {
  1510. // only the last field in the key should be different in this subclause
  1511. $operator = ( count( $temp_primary_keys ) - 1 == $i ? '>' : '=' );
  1512. $subclauses[] = sprintf( '%s %s %s', $this->backquote( $primary_key ), $operator, $wpdb->prepare( '%s', $value ) );
  1513. ++$i;
  1514. }
  1515. // remove last field from array to reduce fields in next clause
  1516. array_pop( $temp_primary_keys );
  1517. // join subclauses into a single clause
  1518. // NB: AND needs to be wrapped in () as it has higher precedence than OR
  1519. $clauses[] = '( ' . implode( ' AND ', $subclauses ) . ' )';
  1520. }
  1521. // join clauses into a single clause
  1522. // NB: OR needs to be wrapped in () as it has lower precedence than AND
  1523. $where .= '( ' . implode( ' OR ', $clauses ) . ' )';
  1524. }
  1525. $first_select = false;
  1526. }
  1527. $join = implode( ' ', array_unique( $join ) );
  1528. $join = apply_filters( 'wpsdb_rows_join', $join, $table );
  1529. $where = apply_filters( 'wpsdb_rows_where', $where, $table );
  1530. $order_by = apply_filters( 'wpsdb_rows_order_by', $order_by, $table );
  1531. $limit = apply_filters( 'wpsdb_rows_limit', $limit, $table );
  1532. $sql = "SELECT " . $this->backquote( $table ) . ".* FROM " . $this->backquote( $table ) . " $join $where $order_by $limit";
  1533. $sql = apply_filters( 'wpsdb_rows_sql', $sql, $table );
  1534. $table_data = $wpdb->get_results( $sql );
  1535. if ( $table_data ) {
  1536. foreach ( $table_data as $row ) {
  1537. $values = array();
  1538. foreach ( $row as $key => $value ) {
  1539. if ( isset( $ints[strtolower( $key )] ) && $ints[strtolower( $key )] ) {
  1540. // make sure there are no blank spots in the insert syntax,
  1541. // yet try to avoid quotation marks around integers
  1542. $value = ( null === $value || '' === $value ) ? $defs[strtolower( $key )] : $value;
  1543. $values[] = ( '' === $value ) ? "''" : $value;
  1544. } else {
  1545. if ( null === $value ) {
  1546. $values[] = 'NULL';
  1547. }
  1548. else {
  1549. if( is_multisite() && 'path' == $key && $_POST['stage'] != 'backup' && ( $wpdb->site == $table || $wpdb->blogs == $table ) ) {
  1550. $old_path_current_site = $this->get_path_current_site();
  1551. if( ! empty( $_POST['path_current_site'] ) ) {
  1552. $new_path_current_site = stripslashes( $_POST['path_current_site'] );
  1553. }
  1554. else {
  1555. $new_path_current_site = $this->get_path_from_url( $this->form_data['replace_new'][1] );
  1556. }
  1557. if( $old_path_current_site != $new_path_current_site ) {
  1558. $pos = strpos( $value, $old_path_current_site );
  1559. $value = substr_replace( $value, $new_path_current_site, $pos, strlen( $old_path_current_site ) );
  1560. }
  1561. }
  1562. if( is_multisite() && 'domain' == $key && $_POST['stage'] != 'backup' && ( $wpdb->site == $table || $wpdb->blogs == $table ) ) {
  1563. if( ! empty( $_POST['domain_current_site'] ) ) {
  1564. $main_domain_replace = $_POST['domain_current_site'];
  1565. }
  1566. else {
  1567. $url = parse_url( $this->form_data['replace_new'][1] );
  1568. $main_domain_replace = $url['host'];
  1569. }
  1570. $main_domain_find = sprintf( "/%s/", $this->get_domain_current_site() );
  1571. $domain_replaces[$main_domain_find] = $main_domain_replace;
  1572. $domain_replaces = apply_filters( 'wpsdb_domain_replaces', $domain_replaces );
  1573. $value = preg_replace( array_keys( $domain_replaces ), array_values( $domain_replaces ), $value );
  1574. }
  1575. if ( 'guid' != $key || ( isset( $this->form_data['replace_guids'] ) && $this->table_is( 'posts', $table ) ) ) {
  1576. if ( $_POST['stage'] != 'backup' ) {
  1577. $value = $this->recursive_unserialize_replace( $value );
  1578. }
  1579. }
  1580. $values[] = "'" . str_replace( $search, $replace, $this->sql_addslashes( $value ) ) . "'";
  1581. }
  1582. }
  1583. }
  1584. $insert_line = '(' . implode( ', ', $values ) . '),';
  1585. $insert_line .= "\n";
  1586. if ( ( strlen( $this->current_chunk ) + strlen( $insert_line ) + strlen( $insert_buffer ) + 10 ) > $this->maximum_chunk_size ) {
  1587. if( $insert_buffer == $insert_query_template ) {
  1588. $insert_buffer .= $insert_line;
  1589. ++$this->row_tracker;
  1590. if( ! empty( $this->primary_keys ) ) {
  1591. foreach( $this->primary_keys as $primary_key => $value ) {
  1592. $this->primary_keys[$primary_key] = $row->$primary_key;
  1593. }
  1594. }
  1595. }
  1596. $insert_buffer = rtrim( $insert_buffer, "\n," );
  1597. $insert_buffer .= " ;\n";
  1598. $this->stow( $insert_buffer );
  1599. $insert_buffer = $insert_query_template;
  1600. $query_size = 0;
  1601. return $this->transfer_chunk();
  1602. }
  1603. if ( ( $query_size + strlen( $insert_line ) ) > $this->max_insert_string_len && $insert_buffer != $insert_query_template ) {
  1604. $insert_buffer = rtrim( $insert_buffer, "\n," );
  1605. $insert_buffer .= " ;\n";
  1606. $this->stow( $insert_buffer );
  1607. $insert_buffer = $insert_query_template;
  1608. $query_size = 0;
  1609. }
  1610. $insert_buffer .= $insert_line;
  1611. $query_size += strlen( $insert_line );
  1612. ++$this->row_tracker;
  1613. if( ! empty( $this->primary_keys ) ) {
  1614. foreach( $this->primary_keys as $primary_key => $value ) {
  1615. $this->primary_keys[$primary_key] = $row->$primary_key;
  1616. }
  1617. }
  1618. }
  1619. $row_start += $row_inc;
  1620. if ( $insert_buffer != $insert_query_template ) {
  1621. $insert_buffer = rtrim( $insert_buffer, "\n," );
  1622. $insert_buffer .= " ;\n";
  1623. $this->stow( $insert_buffer );
  1624. $insert_buffer = $insert_query_template;
  1625. $query_size = 0;
  1626. }
  1627. }
  1628. } while ( count( $table_data ) > 0 );
  1629. // Create footer/closing comment in SQL-file
  1630. if ( $this->form_data['action'] == 'savefile' || $_POST['stage'] == 'backup' ) {
  1631. $this->stow( "\n" );
  1632. $this->stow( "#\n" );
  1633. $this->stow( "# " . sprintf( __( 'End of data contents of table %s', 'wp-sync-db' ), $this->backquote( $table ) ) . "\n" );
  1634. $this->stow( "# --------------------------------------------------------\n" );
  1635. $this->stow( "\n" );
  1636. if( $_POST['last_table'] == '1' ) {
  1637. $this->stow( "#\n" );
  1638. $this->stow( "# Add constraints back in\n" );
  1639. $this->stow( "#\n\n" );
  1640. $this->stow( $this->get_alter_queries() );
  1641. $alter_table_name = $this->get_alter_table_name();
  1642. if ( $this->form_data['action'] == 'savefile' ) {
  1643. $wpdb->query( "DROP TABLE IF EXISTS " . $this->backquote( $alter_table_name ) . ";" );
  1644. }
  1645. }
  1646. }
  1647. $this->row_tracker = -1;
  1648. return $this->transfer_chunk();
  1649. } // end backup_table()
  1650. function table_is( $desired_table, $given_table ) {
  1651. global $wpdb;
  1652. return ( $wpdb->{$desired_table} == $given_table || preg_match( '/' . $wpdb->prefix . '[0-9]+_' . $desired_table . '/', $given_table ) );
  1653. }
  1654. /**
  1655. * return multisite-compatible names for requested tables, based on queried table name
  1656. *
  1657. * @param array $tables list of table names required
  1658. * @param string $queried_table name of table from which to derive the blog ID
  1659. *
  1660. * @return array list of table names altered for multisite compatibility
  1661. */
  1662. function get_ms_compat_table_names( $tables, $queried_table ) {
  1663. global $wpdb;
  1664. // default table prefix
  1665. $prefix = $wpdb->prefix;
  1666. // if multisite, extract blog ID from queried table name and add to prefix
  1667. // won't match for primary blog because it uses standard table names, i.e. blog_id will never be 1
  1668. if ( is_multisite() && preg_match( '/^' . preg_quote( $wpdb->prefix, '/' ) . '([0-9]+)_/', $queried_table, $matches ) ) {
  1669. $blog_id = $matches[1];
  1670. $prefix .= $blog_id . '_';
  1671. }
  1672. // build table names
  1673. $ms_compat_table_names = array();
  1674. foreach( $tables as $table ) {
  1675. $ms_compat_table_names[$table . '_table'] = $prefix . $table;
  1676. }
  1677. return $ms_compat_table_names;
  1678. }
  1679. /**
  1680. * Take a serialized array and unserialize it replacing elements as needed and
  1681. * unserialising any subordinate arrays and performing the replace on those too.
  1682. *
  1683. * Mostly from https://github.com/interconnectit/Search-Replace-DB
  1684. *
  1685. * @param array $data Used to pass any subordinate arrays back to in.
  1686. * @param bool $serialized Does the array passed via $data need serialising.
  1687. * @param bool $parent_serialized Passes whether the original data passed in was serialized
  1688. *
  1689. * @return array The original array with all elements replaced as needed.
  1690. */
  1691. function recursive_unserialize_replace( $data, $serialized = false, $parent_serialized = false ) {
  1692. $is_json = false;
  1693. // some unseriliased data cannot be re-serialized eg. SimpleXMLElements
  1694. try {
  1695. if ( is_string( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
  1696. // PHP currently has a bug that doesn't allow you to clone the DateInterval / DatePeriod classes.
  1697. // We skip them here as they probably won't need data to be replaced anyway
  1698. if( is_object( $unserialized ) ) {
  1699. if( $unserialized instanceof DateInterval || $unserialized instanceof DatePeriod ) return $data;
  1700. }
  1701. $data = $this->recursive_unserialize_replace( $unserialized, true, true );
  1702. }
  1703. elseif ( is_array( $data ) ) {
  1704. $_tmp = array( );
  1705. foreach ( $data as $key => $value ) {
  1706. $_tmp[ $key ] = $this->recursive_unserialize_replace( $value, false, $parent_serialized );
  1707. }
  1708. $data = $_tmp;
  1709. unset( $_tmp );
  1710. }
  1711. // Submitted by Tina Matter
  1712. elseif ( is_object( $data ) ) {
  1713. $_tmp = clone $data;
  1714. foreach ( $data as $key => $value ) {
  1715. $_tmp->$key = $this->recursive_unserialize_replace( $value, false, $parent_serialized );
  1716. }
  1717. $data = $_tmp;
  1718. unset( $_tmp );
  1719. }
  1720. elseif ( $this->is_json( $data, true ) ) {
  1721. $_tmp = array( );
  1722. $data = json_decode( $data, true );
  1723. foreach ( $data as $key => $value ) {
  1724. $_tmp[ $key ] = $this->recursive_unserialize_replace( $value, false, $parent_serialized );
  1725. }
  1726. $data = $_tmp;
  1727. unset( $_tmp );
  1728. $is_json = true;
  1729. }
  1730. elseif ( is_string( $data ) ) {
  1731. $data = $this->apply_replaces( $data, $parent_serialized );
  1732. }
  1733. if ( $serialized )
  1734. return serialize( $data );
  1735. if ( $is_json )
  1736. return json_encode( $data );
  1737. } catch( Exception $error ) {
  1738. }
  1739. return $data;
  1740. }
  1741. function db_backup_header() {
  1742. $charset = ( defined( 'DB_CHARSET' ) ? DB_CHARSET : 'utf8' );
  1743. $this->stow( "# " . __( 'WordPress MySQL database migration', 'wp-sync-db' ) . "\n", false );
  1744. $this->stow( "#\n", false );
  1745. $this->stow( "# " . sprintf( __( 'Generated: %s', 'wp-sync-db' ), date( "l j. F Y H:i T" ) ) . "\n", false );
  1746. $this->stow( "# " . sprintf( __( 'Hostname: %s', 'wp-sync-db' ), DB_HOST ) . "\n", false );
  1747. $this->stow( "# " . sprintf( __( 'Database: %s', 'wp-sync-db' ), $this->backquote( DB_NAME ) ) . "\n", false );
  1748. $this->stow( "# --------------------------------------------------------\n\n", false );
  1749. $this->stow( "/*!40101 SET NAMES $charset */;\n\n", false );
  1750. $this->stow( "SET sql_mode='NO_AUTO_VALUE_ON_ZERO';\n\n", false );
  1751. }
  1752. function gzip() {
  1753. return function_exists( 'gzopen' );
  1754. }
  1755. function open( $filename = '', $mode = 'a' ) {
  1756. if ( '' == $filename ) return false;
  1757. if ( $this->gzip() && isset( $this->form_data['gzip_file'] ) )
  1758. $fp = gzopen( $filename, $mode );
  1759. else
  1760. $fp = fopen( $filename, $mode );
  1761. return $fp;
  1762. }
  1763. function close( $fp ) {
  1764. if ( $this->gzip() && isset( $this->form_data['gzip_file'] ) ) gzclose( $fp );
  1765. else fclose( $fp );
  1766. unset( $this->fp );
  1767. }
  1768. function stow( $query_line, $replace = true ) {
  1769. $this->current_chunk .= $query_line;
  1770. if ( $this->form_data['action'] == 'savefile' || $_POST['stage'] == 'backup' ) {
  1771. if ( $this->gzip() && isset( $this->form_data['gzip_file'] ) ) {
  1772. if ( ! @gzwrite( $this->fp, $query_line ) ) {
  1773. $this->error = __( 'Failed to write the gzipped SQL data to the file. (#127)', 'wp-sync-db' );
  1774. return false;
  1775. }
  1776. }
  1777. else {
  1778. if ( false === @fwrite( $this->fp, $query_line ) ) {
  1779. $this->error = __( 'Failed to write the SQL data to the file. (#128)', 'wp-sync-db' );
  1780. return false;
  1781. }
  1782. }
  1783. }
  1784. else if ( $_POST['intent'] == 'pull' ) {
  1785. echo $query_line;
  1786. }
  1787. }
  1788. // Called in the $this->stow function once our chunk buffer is full, will transfer the SQL to the remote server for importing
  1789. function transfer_chunk() {
  1790. if( $_POST['intent'] == 'savefile' || $_POST['stage'] == 'backup' ) {
  1791. $this->close( $this->fp );
  1792. $result = $this->end_ajax( json_encode(
  1793. array(
  1794. 'current_row' => $this->row_tracker,
  1795. 'primary_keys' => serialize( $this->primary_keys )
  1796. )
  1797. ) );
  1798. return $result;
  1799. }
  1800. if ( $_POST['intent'] == 'pull' ) {
  1801. $result = $this->end_ajax( $this->row_tracker . ',' . serialize( $this->primary_keys ) );
  1802. return $result;
  1803. }
  1804. $chunk_gzipped = '0';
  1805. if( isset( $_POST['gzip'] ) && $_POST['gzip'] == '1' && $this->gzip() ) {
  1806. $this->current_chunk = gzcompress( $this->current_chunk );
  1807. $chunk_gzipped = '1';
  1808. }
  1809. $data = array(
  1810. 'action' => 'wpsdb_process_chunk',
  1811. 'table' => $_POST['table'],
  1812. 'chunk_gzipped' => $chunk_gzipped,
  1813. 'chunk' => $this->current_chunk // NEEDS TO BE the last element in this array because of adding it back into the array in ajax_process_chunk()
  1814. );
  1815. $data['sig'] = $this->create_signature( $data, $_POST['key'] );
  1816. $ajax_url = trailingslashit( $this->remote_url ) . 'wp-admin/admin-ajax.php';
  1817. $response = $this->remote_post( $ajax_url, $data, __FUNCTION__ );
  1818. ob_start();
  1819. $this->display_errors();
  1820. $response = ob_get_clean();
  1821. $response .= trim( $response );
  1822. if( ! empty( $response ) ) {
  1823. $result = $this->end_ajax( $response );
  1824. return $result;
  1825. }
  1826. $result = $this->end_ajax( json_encode(
  1827. array(
  1828. 'current_row' => $this->row_tracker,
  1829. 'primary_keys' => serialize( $this->primary_keys )
  1830. )
  1831. ) );
  1832. return $result;
  1833. }
  1834. /**
  1835. * Add backquotes to tables and db-names in
  1836. * SQL queries. Taken from phpMyAdmin.
  1837. */
  1838. function backquote( $a_name ) {
  1839. if ( !empty( $a_name ) && $a_name != '*' ) {
  1840. if ( is_array( $a_name ) ) {
  1841. $result = array();
  1842. reset( $a_name );
  1843. while ( list( $key, $val ) = each( $a_name ) )
  1844. $result[$key] = '`' . $val . '`';
  1845. return $result;
  1846. } else {
  1847. return '`' . $a_name . '`';
  1848. }
  1849. } else {
  1850. return $a_name;
  1851. }
  1852. }
  1853. /**
  1854. * Better addslashes for SQL queries.
  1855. * Taken from phpMyAdmin.
  1856. */
  1857. function sql_addslashes( $a_string = '', $is_like = false ) {
  1858. if ( $is_like ) $a_string = str_replace( '\\', '\\\\\\\\', $a_string );
  1859. else $a_string = str_replace( '\\', '\\\\', $a_string );
  1860. return str_replace( '\'', '\\\'', $a_string );
  1861. }
  1862. function network_admin_menu() {
  1863. $hook_suffix = add_submenu_page( 'settings.php', 'Migrate DB', 'Migrate DB', 'manage_network_options', 'wp-sync-db', array( $this, 'options_page' ) );
  1864. $this->after_admin_menu( $hook_suffix );
  1865. }
  1866. function admin_menu() {
  1867. $hook_suffix = add_management_page( 'Migrate DB', 'Migrate DB', 'export', 'wp-sync-db', array( $this, 'options_page' ) );
  1868. $this->after_admin_menu( $hook_suffix );
  1869. }
  1870. function after_admin_menu( $hook_suffix ) {
  1871. add_action( 'admin_head-' . $hook_suffix, array( $this, 'admin_head_connection_info' ) );
  1872. add_action( 'load-' . $hook_suffix , array( $this, 'load_assets' ) );
  1873. }
  1874. function admin_body_class( $classes ) {
  1875. if ( !$classes ) {
  1876. $classes = array();
  1877. }
  1878. else {
  1879. $classes = explode( ' ', $classes );
  1880. }
  1881. // Recommended way to target WP 3.8+
  1882. // http://make.wordpress.org/ui/2013/11/19/targeting-the-new-dashboard-design-in-a-post-mp6-world/
  1883. if ( version_compare( $GLOBALS['wp_version'], '3.8-alpha', '>' ) ) {
  1884. if ( !in_array( 'mp6', $classes ) ) {
  1885. $classes[] = 'mp6';
  1886. }
  1887. }
  1888. return implode( ' ', $classes );
  1889. }
  1890. function load_assets() {
  1891. if ( ! empty( $_GET['download'] ) ) {
  1892. $this->download_file();
  1893. }
  1894. $plugins_url = trailingslashit( plugins_url() ) . trailingslashit( $this->plugin_folder_name );
  1895. $version = defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? time() : $this->plugin_version;
  1896. $src = $plugins_url . 'asset/css/styles.css';
  1897. wp_enqueue_style( 'wp-sync-db-styles', $src, array(), $version );
  1898. $src = $plugins_url . 'asset/js/common.js';
  1899. wp_enqueue_script( 'wp-sync-db-common', $src, NULL, $version, true );
  1900. $src = $plugins_url . 'asset/js/hook.js';
  1901. wp_enqueue_script( 'wp-sync-db-hook', $src, NULL, $version, true );
  1902. do_action( 'wpsdb_load_assets' );
  1903. $src = $plugins_url . 'asset/js/script.js';
  1904. wp_enqueue_script( 'wp-sync-db-script', $src, array( 'jquery' ), $version, true );
  1905. wp_localize_script( 'wp-sync-db-script', 'wpsdb_i10n', array(
  1906. 'max_request_size_problem' => __( "A problem occurred when trying to change the maximum request size, please try again.", 'wp-sync-db' ),
  1907. 'establishing_remote_connection' => __( "Establishing connection to remote server, please wait", 'wp-sync-db' ),
  1908. 'connection_local_server_problem' => __( "A problem occurred when attempting to connect to the local server, please check the details and try again.", 'wp-sync-db' ),
  1909. 'clear_log_problem' => __( "An error occurred when trying to clear the debug log. Please contact support. (#132)", 'wp-sync-db' ),
  1910. 'update_log_problem' => __( "An error occurred when trying to update the debug log. Please contact support. (#133)", 'wp-sync-db' ),
  1911. 'migrate_db_save' => __( "Migrate DB & Save", 'wp-sync-db' ),
  1912. 'migrate_db' => __( "Migrate DB", 'wp-sync-db' ),
  1913. 'please_select_one_table' => __( "Please select at least one table to migrate.", 'wp-sync-db' ),
  1914. 'enter_name_for_profile' => __( "Please enter a name for your migration profile.", 'wp-sync-db' ),
  1915. 'save_profile_problem' => __( "An error occurred when attempting to save the migration profile. Please see the Help tab for details on how to request support. (#118)", 'wp-sync-db' ),
  1916. 'exporting_complete' => __( "Exporting complete", 'wp-sync-db' ),
  1917. 'exporting_please_wait' => __( "Exporting, please wait...", 'wp-sync-db' ),
  1918. 'please_wait' => __( "please wait...", 'wp-sync-db' ),
  1919. 'complete' => __( "complete", 'wp-sync-db' ),
  1920. 'migration_failed' => __( "Migration failed", 'wp-sync-db' ),
  1921. 'backing_up' => __( "Backing up", 'wp-sync-db' ),
  1922. 'migrating' => __( "Migrating", 'wp-sync-db' ),
  1923. 'status' => __( "Status", 'wp-sync-db' ),
  1924. 'response' => __( "Response", 'wp-sync-db' ),
  1925. 'table_process_problem' => __( "A problem occurred when attempting to process the following table (#113)", 'wp-sync-db' ),
  1926. 'table_process_problem_empty_response' => __( "A problem occurred when processing the following table. We were expecting a response in JSON format but instead received an empty response.", 'wp-sync-db' ),
  1927. 'completed_with_some_errors' => __( "Migration completed with some errors", 'wp-sync-db' ),
  1928. 'completed_dump_located_at' => __( "Migration complete, your backup is located at:", 'wp-sync-db' ),
  1929. 'finalize_tables_problem' => __( "A problem occurred when finalizing the backup. (#132)", 'wp-sync-db' ),
  1930. 'saved' => __( "Saved", 'wp-sync-db' ),
  1931. 'reset_api_key' => __( "Any sites setup to use the current API key will no longer be able to connect. You will need to update those sites with the newly generated API key. Do you wish to continue?", 'wp-sync-db' ),
  1932. 'reset_api_key_problem' => __( "An error occurred when trying to generate the API key. Please see the Help tab for details on how to request support. (#105)", 'wp-sync-db' ),
  1933. 'remove_profile' => __( "You are removing the following migration profile. This cannot be undone. Do you wish to continue?", 'wp-sync-db' ),
  1934. 'remove_profile_problem' => __( "An error occurred when trying to delete the profile. Please see the Help tab for details on how to request support. (#106)", 'wp-sync-db' ),
  1935. 'remove_profile_not_found' => __( "The selected migration profile could not be deleted because it was not found.\nPlease refresh this page to see an accurate list of the currently available migration profiles.", 'wp-sync-db' ),
  1936. 'change_connection_info' => __( "If you change the connection details, you will lose any replaces and table selections you have made below. Do you wish to continue?", 'wp-sync-db' ),
  1937. 'enter_connection_info' => __( "Please enter the connection information above to continue.", 'wp-sync-db' ),
  1938. 'save_settings_problem' => __( "An error occurred when trying to save the settings. Please try again. If the problem persists, please see the Help tab for details on how to request support. (#108)", 'wp-sync-db' ),
  1939. 'connection_info_missing' => __( "The connection information appears to be missing, please enter it to continue.", 'wp-sync-db' ),
  1940. 'connection_info_incorrect' => __( "The connection information appears to be incorrect, it should consist of two lines. The first being the remote server's URL and the second being the secret key.", 'wp-sync-db' ),
  1941. 'connection_info_url_invalid' => __( "The URL on the first line appears to be invalid, please check it and try again.", 'wp-sync-db' ),
  1942. 'connection_info_key_invalid' => __( "The secret key on the second line appears to be invalid. It should be a 32 character string that consists of letters, numbers and special characters only.", 'wp-sync-db' ),
  1943. 'connection_info_local_url' => __( "It appears you've entered the URL for this website, you need to provide the URL of the remote website instead.", 'wp-sync-db' ),
  1944. 'connection_info_local_key' => __( "It appears you've entered the secret key for this website, you need to provide the secret key for the remote website instead.", 'wp-sync-db' ),
  1945. 'time_elapsed' => __( "Time Elapsed:", 'wp-sync-db' ),
  1946. 'pause' => __( "Pause", 'wp-sync-db' ),
  1947. 'migration_paused' => __( "Migration Paused", 'wp-sync-db' ),
  1948. 'resume' => __( "Resume", 'wp-sync-db' ),
  1949. 'completing_current_request' => __( "Completing current request", 'wp-sync-db' ),
  1950. 'cancelling_migration' => __( "Cancelling migration", 'wp-sync-db' ),
  1951. 'paused' => __( "Paused", 'wp-sync-db' ),
  1952. 'removing_local_sql' => __( "Removing the local MySQL export file", 'wp-sync-db' ),
  1953. 'removing_local_backup' => __( "Removing the local backup MySQL export file", 'wp-sync-db' ),
  1954. 'removing_local_temp_tables' => __( "Removing the local temporary tables", 'wp-sync-db' ),
  1955. 'removing_remote_sql' => __( "Removing the remote backup MySQL export file", 'wp-sync-db' ),
  1956. 'removing_remote_temp_tables' => __( "Removing the remote temporary tables", 'wp-sync-db' ),
  1957. 'migration_cancellation_failed' => __( "Migration cancellation failed", 'wp-sync-db' ),
  1958. 'manually_remove_temp_files' => __( "A problem occurred while cancelling the migration, you may have to manually delete some temporary files / tables.", 'wp-sync-db' ),
  1959. 'migration_cancelled' => __( "Migration cancelled", 'wp-sync-db' ),
  1960. ) );
  1961. wp_enqueue_script('jquery');
  1962. wp_enqueue_script('jquery-ui-core');
  1963. wp_enqueue_script('jquery-ui-slider');
  1964. wp_enqueue_script('jquery-ui-sortable');
  1965. }
  1966. function download_file() {
  1967. // dont need to check for user permissions as our 'add_management_page' already takes care of this
  1968. $this->set_time_limit();
  1969. $dump_name = $this->format_dump_name( $_GET['download'] );
  1970. if( isset( $_GET['gzip'] ) ) {
  1971. $dump_name .= '.gz';
  1972. }
  1973. $diskfile = $this->get_upload_info( 'path' ) . DS . $dump_name;
  1974. $filename = basename( $diskfile );
  1975. $last_dash = strrpos( $filename, '-' );
  1976. $salt = substr( $filename, $last_dash, 6 );
  1977. $filename_no_salt = str_replace( $salt, '', $filename );
  1978. if ( file_exists( $diskfile ) ) {
  1979. header( 'Content-Description: File Transfer' );
  1980. header( 'Content-Type: application/octet-stream' );
  1981. header( 'Content-Length: ' . filesize( $diskfile ) );
  1982. header( 'Content-Disposition: attachment; filename=' . $filename_no_salt );
  1983. $success = readfile( $diskfile );
  1984. unlink( $diskfile );
  1985. exit;
  1986. }
  1987. else {
  1988. wp_die( __( 'Could not find the file to download:', 'wp-sync-db' ) . '<br />' . $diskfile );
  1989. }
  1990. }
  1991. function admin_head_connection_info() {
  1992. global $table_prefix;
  1993. $nonces = array(
  1994. 'update_max_request_size' => wp_create_nonce( 'update-max-request-size' ),
  1995. 'verify_connection_to_remote_site' => wp_create_nonce( 'verify-connection-to-remote-site' ),
  1996. 'clear_log' => wp_create_nonce( 'clear-log' ),
  1997. 'get_log' => wp_create_nonce( 'get-log' ),
  1998. 'save_profile' => wp_create_nonce( 'save-profile' ),
  1999. 'initiate_migration' => wp_create_nonce( 'initiate-migration' ),
  2000. 'migrate_table' => wp_create_nonce( 'migrate-table' ),
  2001. 'finalize_migration' => wp_create_nonce( 'finalize-migration' ),
  2002. 'reset_api_key' => wp_create_nonce( 'reset-api-key' ),
  2003. 'delete_migration_profile' => wp_create_nonce( 'delete-migration-profile' ),
  2004. 'save_setting' => wp_create_nonce( 'save-setting' ),
  2005. );
  2006. $nonces = apply_filters( 'wpsdb_nonces', $nonces );
  2007. ?>
  2008. <script type='text/javascript'>
  2009. var wpsdb_connection_info = <?php echo json_encode( array( site_url( '', 'https' ), $this->settings['key'] ) ); ?>;
  2010. var wpsdb_this_url = '<?php echo addslashes( home_url() ) ?>';
  2011. var wpsdb_this_path = '<?php echo addslashes( $this->absolute_root_file_path ); ?>';
  2012. var wpsdb_this_domain = '<?php echo $this->get_domain_current_site(); ?>';
  2013. var wpsdb_this_tables = <?php echo json_encode( $this->get_tables() ); ?>;
  2014. var wpsdb_this_prefixed_tables = <?php echo json_encode( $this->get_tables( 'prefix' ) ); ?>;
  2015. var wpsdb_this_table_sizes = <?php echo json_encode( $this->get_table_sizes() ); ?>;
  2016. var wpsdb_this_table_rows = <?php echo json_encode( $this->get_table_row_count() ); ?>;
  2017. var wpsdb_this_upload_url = '<?php echo addslashes( trailingslashit( $this->get_upload_info( 'url' ) ) ); ?>';
  2018. var wpsdb_this_upload_dir_long = '<?php echo addslashes( trailingslashit( $this->get_upload_info( 'path' ) ) ); ?>';
  2019. var wpsdb_this_website_name = '<?php echo sanitize_title_with_dashes( DB_NAME ); ?>';
  2020. var wpsdb_this_download_url = '<?php echo network_admin_url( $this->plugin_base . '&download=' ); ?>';
  2021. var wpsdb_this_prefix = '<?php echo $table_prefix; ?>';
  2022. var wpsdb_is_multisite = <?php echo ( is_multisite() ? 'true' : 'false' ); ?>;
  2023. var wpsdb_openssl_available = <?php echo ( $this->open_ssl_enabled() ? 'true' : 'false' ); ?>;
  2024. var wpsdb_plugin_version = '<?php echo $this->plugin_version; ?>';
  2025. var wpsdb_max_request = '<?php echo $this->settings['max_request'] ?>';
  2026. var wpsdb_bottleneck = '<?php echo $this->get_bottleneck( 'max' ); ?>';
  2027. var wpsdb_this_uploads_dir = '<?php echo addslashes( $this->get_short_uploads_dir() ); ?>';
  2028. var wpsdb_write_permission = <?php echo ( is_writeable( $this->get_upload_info( 'path' ) ) ? 'true' : 'false' ); ?>;
  2029. var wpsdb_nonces = <?php echo json_encode( $nonces ); ?>;
  2030. var wpsdb_profile = '<?php echo ( isset( $_GET['wpsdb-profile'] ) ? $_GET['wpsdb-profile'] : '-1' ); ?>';
  2031. <?php do_action( 'wpsdb_js_variables' ); ?>
  2032. </script>
  2033. <?php
  2034. }
  2035. function maybe_update_profile( $profile, $profile_id ) {
  2036. $profile_changed = false;
  2037. if ( isset( $profile['exclude_revisions'] ) ) {
  2038. unset( $profile['exclude_revisions'] );
  2039. $profile['select_post_types'] = array( 'revision' );
  2040. $profile_changed = true;
  2041. }
  2042. if ( isset( $profile['post_type_migrate_option'] ) && 'migrate_select_post_types' == $profile['post_type_migrate_option'] && 'pull' != $profile['action'] ) {
  2043. unset( $profile['post_type_migrate_option'] );
  2044. $profile['exclude_post_types'] = '1';
  2045. $all_post_types = $this->get_post_types();
  2046. $profile['select_post_types'] = array_diff( $all_post_types, $profile['select_post_types'] );
  2047. $profile_changed = true;
  2048. }
  2049. if ( $profile_changed ) {
  2050. $this->settings['profiles'][$profile_id] = $profile;
  2051. update_option( 'wpsdb_settings', $this->settings );
  2052. }
  2053. return $profile;
  2054. }
  2055. function get_path_from_url( $url ) {
  2056. $parts = parse_url( $url );
  2057. return ( ! empty( $parts['path'] ) ) ? trailingslashit( $parts['path'] ) : '/';
  2058. }
  2059. function get_path_current_site() {
  2060. if( ! is_multisite() ) return '';
  2061. $current_site = get_current_site();
  2062. return $current_site->path;
  2063. }
  2064. function get_domain_current_site() {
  2065. if( ! is_multisite() ) return '';
  2066. $current_site = get_current_site();
  2067. return $current_site->domain;
  2068. }
  2069. function return_bytes($val) {
  2070. if( is_numeric( $val ) ) return $val;
  2071. if( empty( $val ) ) return false;
  2072. $val = trim($val);
  2073. $last = strtolower($val[strlen($val)-1]);
  2074. switch($last) {
  2075. // The 'G' modifier is available since PHP 5.1.0
  2076. case 'g':
  2077. $val *= 1024;
  2078. case 'm':
  2079. $val *= 1024;
  2080. case 'k':
  2081. $val *= 1024;
  2082. break;
  2083. default :
  2084. $val = false;
  2085. break;
  2086. }
  2087. return $val;
  2088. }
  2089. function maybe_checked( $option ) {
  2090. echo ( isset( $option ) && $option == '1' ) ? ' checked="checked"' : '';
  2091. }
  2092. function ajax_cancel_migration() {
  2093. $this->form_data = $this->parse_migration_form_data( $_POST['form_data'] );
  2094. switch( $_POST['intent'] ) {
  2095. case 'savefile' :
  2096. $this->delete_export_file( $_POST['dump_filename'], false );
  2097. break;
  2098. case 'push' :
  2099. $data = $_POST;
  2100. $data['action'] = 'wpsdb_process_push_migration_cancellation';
  2101. $data['temp_prefix'] = $this->temp_prefix;
  2102. $ajax_url = trailingslashit( $data['url'] ) . 'wp-admin/admin-ajax.php';
  2103. $data['sig'] = $this->create_signature( $data, $data['key'] );
  2104. $response = $this->remote_post( $ajax_url, $data, __FUNCTION__ );
  2105. $this->display_errors();
  2106. echo trim( $response );
  2107. break;
  2108. case 'pull' :
  2109. if( $_POST['stage'] == 'backup' ) {
  2110. $this->delete_export_file( $_POST['dump_filename'], true );
  2111. }
  2112. else {
  2113. $this->delete_temporary_tables( $_POST['temp_prefix'] );
  2114. }
  2115. break;
  2116. default:
  2117. break;
  2118. }
  2119. exit;
  2120. }
  2121. function respond_to_process_push_migration_cancellation() {
  2122. $filtered_post = $this->filter_post_elements( $_POST, array( 'action', 'intent', 'url', 'key', 'form_data', 'dump_filename', 'temp_prefix', 'stage' ) );
  2123. if ( ! $this->verify_signature( $filtered_post, $this->settings['key'] ) ) {
  2124. echo $this->invalid_content_verification_error;
  2125. exit;
  2126. }
  2127. $this->form_data = $this->parse_migration_form_data( $filtered_post['form_data'] );
  2128. if( $filtered_post['stage'] == 'backup' ) {
  2129. $this->delete_export_file( $filtered_post['dump_filename'], true );
  2130. }
  2131. else {
  2132. $this->delete_temporary_tables( $filtered_post['temp_prefix'] );
  2133. }
  2134. exit;
  2135. }
  2136. function delete_export_file( $filename, $is_backup ) {
  2137. $dump_file = $this->format_dump_name( $filename );
  2138. if( true == $is_backup ) {
  2139. $dump_file = preg_replace( '/.gz$/', '', $dump_file );
  2140. }
  2141. $dump_file = $this->get_upload_info( 'path' ) . DS . $dump_file;
  2142. if( empty( $dump_file ) || false == file_exists( $dump_file ) ) {
  2143. _e( 'MySQL export file not found.', 'wp-sync-db' );
  2144. exit;
  2145. }
  2146. if( false === @unlink( $dump_file ) ) {
  2147. e( 'Could not delete the MySQL export file.', 'wp-sync-db' );
  2148. exit;
  2149. }
  2150. }
  2151. function delete_temporary_tables( $prefix ) {
  2152. $tables = $this->get_tables();
  2153. $delete_queries = '';
  2154. foreach( $tables as $table ) {
  2155. if( 0 !== strpos( $table, $prefix ) ) continue;
  2156. $delete_queries .= sprintf( "DROP TABLE %s;\n", $this->backquote( $table ) );
  2157. }
  2158. $this->process_chunk( $delete_queries );
  2159. }
  2160. function empty_current_chunk() {
  2161. $this->current_chunk = '';
  2162. }
  2163. }