diff --git a/3rd-party/3rd-party.php b/3rd-party/3rd-party.php
index 9d5462747315..cdf08f273e27 100644
--- a/3rd-party/3rd-party.php
+++ b/3rd-party/3rd-party.php
@@ -10,6 +10,7 @@
require_once( JETPACK__PLUGIN_DIR . '3rd-party/bitly.php' );
require_once( JETPACK__PLUGIN_DIR . '3rd-party/bbpress.php' );
require_once( JETPACK__PLUGIN_DIR . '3rd-party/woocommerce.php' );
+require_once( JETPACK__PLUGIN_DIR . '3rd-party/domain-mapping.php' );
// We can't load this conditionally since polldaddy add the call in class constuctor.
require_once( JETPACK__PLUGIN_DIR . '3rd-party/polldaddy.php' );
diff --git a/3rd-party/domain-mapping.php b/3rd-party/domain-mapping.php
new file mode 100644
index 000000000000..6079ac325908
--- /dev/null
+++ b/3rd-party/domain-mapping.php
@@ -0,0 +1,113 @@
+function_exists( 'domain_mapping_siteurl' ) ) {
+ return false;
+ }
+
+ add_filter( 'jetpack_sync_home_url', 'domain_mapping_siteurl' );
+ add_filter( 'jetpack_sync_site_url', 'domain_mapping_siteurl' );
+
+ return true;
+ }
+
+ /**
+ * This method will test for a class and method known to be used in WPMU Dev's domain mapping plugin. If the
+ * method exists, then we'll hook the swap_to_mapped_url() to our Jetpack sync filters for home_url and site_url.
+ *
+ * @return bool
+ */
+ function hook_wpmu_dev_domain_mapping() {
+ if ( ! $this->class_exists( 'domain_map' ) || ! $this->method_exists( 'domain_map', 'utils' ) ) {
+ return false;
+ }
+
+ $utils = $this->get_domain_mapping_utils_instance();
+ add_filter( 'jetpack_sync_home_url', array( $utils, 'swap_to_mapped_url' ) );
+ add_filter( 'jetpack_sync_site_url', array( $utils, 'swap_to_mapped_url' ) );
+
+ return true;
+ }
+
+ /*
+ * Utility Methods
+ *
+ * These methods are very minimal, and in most cases, simply pass on arguments. Why create them you ask?
+ * So that we can test.
+ */
+
+ public function method_exists( $class, $method ) {
+ return method_exists( $class, $method );
+ }
+
+ public function class_exists( $class ) {
+ return class_exists( $class );
+ }
+
+ public function function_exists( $function ) {
+ return function_exists( $function );
+ }
+
+ public function get_domain_mapping_utils_instance() {
+ return domain_map::utils();
+ }
+}
+
+Jetpack_3rd_Party_Domain_Mapping::init();
diff --git a/class.jetpack-xmlrpc-server.php b/class.jetpack-xmlrpc-server.php
index 61f1be7c3556..54e311b8da21 100644
--- a/class.jetpack-xmlrpc-server.php
+++ b/class.jetpack-xmlrpc-server.php
@@ -340,9 +340,10 @@ function sync_object( $args ) {
* @return array
*/
function validate_urls_for_idc_mitigation() {
+ require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-functions.php';
return array(
- 'home' => get_home_url(),
- 'siteurl' => get_site_url(),
+ 'home' => Jetpack_Sync_Functions::home_url(),
+ 'siteurl' => Jetpack_Sync_Functions::site_url(),
);
}
diff --git a/class.jetpack.php b/class.jetpack.php
index 2544348a5ae1..6a7a497b2d64 100644
--- a/class.jetpack.php
+++ b/class.jetpack.php
@@ -5797,9 +5797,10 @@ public static function normalize_url_protocol_agnostic( $url ) {
* @return array Array of the local urls, wpcom urls, and error code
*/
public static function get_sync_error_idc_option( $response = array() ) {
+ require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-functions.php';
$local_options = array(
- 'home' => get_home_url(),
- 'siteurl' => get_site_url(),
+ 'home' => Jetpack_Sync_Functions::home_url(),
+ 'siteurl' => Jetpack_Sync_Functions::site_url(),
);
$options = array_merge( $local_options, $response );
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index c25dbf8962a4..2f27db1b85b2 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -80,6 +80,9 @@
tests/php/test_class.jetpack-jitm.php
+
+ tests/php/3rd-party
+
diff --git a/sync/class.jetpack-sync-actions.php b/sync/class.jetpack-sync-actions.php
index 05a507783448..a01d819abdd0 100644
--- a/sync/class.jetpack-sync-actions.php
+++ b/sync/class.jetpack-sync-actions.php
@@ -113,6 +113,7 @@ static function set_is_importing_true() {
}
static function send_data( $data, $codec_name, $sent_timestamp, $queue_id, $checkout_duration, $preprocess_duration ) {
+ require_once dirname( __FILE__ ) . '/class.jetpack-sync-functions.php';
Jetpack::load_xml_rpc_client();
$query_args = array(
@@ -120,8 +121,8 @@ static function send_data( $data, $codec_name, $sent_timestamp, $queue_id, $chec
'codec' => $codec_name, // send the name of the codec used to encode the data
'timestamp' => $sent_timestamp, // send current server time so we can compensate for clock differences
'queue' => $queue_id, // sync or full_sync
- 'home' => get_home_url(), // Send home url option to check for Identity Crisis server-side
- 'siteurl' => get_site_url(), // Send siteurl option to check for Identity Crisis server-side
+ 'home' => Jetpack_Sync_Functions::home_url(), // Send home url option to check for Identity Crisis server-side
+ 'siteurl' => Jetpack_Sync_Functions::site_url(), // Send siteurl option to check for Identity Crisis server-side
'cd' => sprintf( '%.4f', $checkout_duration), // Time spent retrieving queue items from the DB
'pd' => sprintf( '%.4f', $preprocess_duration), // Time spent converting queue items into data to send
);
diff --git a/sync/class.jetpack-sync-functions.php b/sync/class.jetpack-sync-functions.php
index 596af3573976..f8d76def4e75 100644
--- a/sync/class.jetpack-sync-functions.php
+++ b/sync/class.jetpack-sync-functions.php
@@ -144,18 +144,53 @@ public static function file_system_write_access() {
return false;
}
+ /**
+ * Helper function that is used when getting home or siteurl values. Decides
+ * whether to get the raw or filtered value.
+ *
+ * @return string
+ */
+ public static function get_raw_or_filtered_url( $url_type ) {
+ if (
+ ! Jetpack_Constants::is_defined( 'JETPACK_SYNC_USE_RAW_URL' ) ||
+ Jetpack_Constants::get_constant( 'JETPACK_SYNC_USE_RAW_URL' )
+ ) {
+ $url = self::get_raw_url( $url_type );
+ } else {
+ $url_function = ( 'home' == $url_type )
+ ? 'home_url'
+ : 'site_url';
+ $url = self::normalize_www_in_url( $url_type, $url_function );
+ $url = self::get_protocol_normalized_url( $url_function, $url );
+ }
+
+ return $url;
+ }
+
public static function home_url() {
- return self::get_protocol_normalized_url(
- 'home_url',
- self::normalize_www_in_url( 'home', 'home_url' )
- );
+ $url = self::get_raw_or_filtered_url( 'home' );
+
+ /**
+ * Allows overriding of the home_url value that is synced back to WordPress.com.
+ *
+ * @since 5.2
+ *
+ * @param string $home_url
+ */
+ return esc_url_raw( apply_filters( 'jetpack_sync_home_url', $url ) );
}
public static function site_url() {
- return self::get_protocol_normalized_url(
- 'site_url',
- self::normalize_www_in_url( 'siteurl', 'site_url' )
- );
+ $url = self::get_raw_or_filtered_url( 'siteurl' );
+
+ /**
+ * Allows overriding of the site_url value that is synced back to WordPress.com.
+ *
+ * @since 5.2
+ *
+ * @param string $site_url
+ */
+ return esc_url_raw( apply_filters( 'jetpack_sync_site_url', $url ) );
}
public static function main_network_site_url() {
@@ -184,6 +219,23 @@ public static function get_protocol_normalized_url( $callable, $new_value ) {
return set_url_scheme( $new_value, $forced_scheme );
}
+ public static function get_raw_url( $option_name ) {
+ $value = null;
+ $constant = ( 'home' == $option_name )
+ ? 'WP_HOME'
+ : 'WP_SITEURL';
+
+ if ( Jetpack_Constants::is_defined( $constant ) ) {
+ $value = Jetpack_Constants::get_constant( $constant );
+ } else {
+ // Let's get the option from the database so that we can bypass filters. This will help
+ // ensure that we get more uniform values.
+ $value = Jetpack_Options::get_raw_option( $option_name );
+ }
+
+ return $value;
+ }
+
public static function normalize_www_in_url( $option, $url_function ) {
$url = wp_parse_url( call_user_func( $url_function ) );
$option_url = wp_parse_url( get_option( $option ) );
diff --git a/sync/class.jetpack-sync-module-callables.php b/sync/class.jetpack-sync-module-callables.php
index b056521332b0..917345b3f61d 100644
--- a/sync/class.jetpack-sync-module-callables.php
+++ b/sync/class.jetpack-sync-module-callables.php
@@ -145,7 +145,7 @@ public function maybe_sync_callables() {
return;
}
- $callable_checksums = (array) get_option( self::CALLABLES_CHECKSUM_OPTION_NAME, array() );
+ $callable_checksums = (array) Jetpack_Options::get_raw_option( self::CALLABLES_CHECKSUM_OPTION_NAME, array() );
// only send the callables that have changed
foreach ( $callables as $name => $value ) {
@@ -166,7 +166,7 @@ public function maybe_sync_callables() {
$callable_checksums[ $name ] = $checksum;
}
}
- update_option( self::CALLABLES_CHECKSUM_OPTION_NAME, $callable_checksums );
+ Jetpack_Options::update_raw_option( self::CALLABLES_CHECKSUM_OPTION_NAME, $callable_checksums );
}
public function expand_callables( $args ) {
diff --git a/tests/php/3rd-party/test_class.jetpack-domain-mapping.php b/tests/php/3rd-party/test_class.jetpack-domain-mapping.php
new file mode 100644
index 000000000000..3e53b86331ba
--- /dev/null
+++ b/tests/php/3rd-party/test_class.jetpack-domain-mapping.php
@@ -0,0 +1,152 @@
+get_jetpack_sync_filters() as $filter ) {
+ remove_all_filters( $filter );
+ }
+ }
+
+ function test_domain_mapping_should_not_try_to_hook_when_sunrise_disable() {
+ $stub = $this->getMockBuilder( 'MockDomainMapping' )
+ ->setMethods( array( 'hook_wordpress_mu_domain_mapping', 'hook_wpmu_dev_domain_mapping' ) )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ // Both of these methods should not be called
+ $stub->expects( $this->exactly( 0 ) )
+ ->method( 'hook_wordpress_mu_domain_mapping' )
+ ->will( $this->returnValue( false ) );
+
+ $stub->expects( $this->exactly( 0 ) )
+ ->method( 'hook_wpmu_dev_domain_mapping' )
+ ->will( $this->returnValue( false ) );
+
+ $stub->attempt_to_hook_domain_mapping_plugins();
+ }
+
+ function test_domain_mapping_should_stop_search_after_hooking_once() {
+ Jetpack_Constants::set_constant( 'SUNRISE', true );
+
+ $stub = $this->getMockBuilder( 'MockDomainMapping' )
+ ->setMethods( array( 'hook_wordpress_mu_domain_mapping', 'hook_wpmu_dev_domain_mapping' ) )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ // The first method in the array should be the only one called.
+ $stub->expects( $this->exactly( 1 ) )
+ ->method( 'hook_wordpress_mu_domain_mapping' )
+ ->will( $this->returnValue( true ) );
+
+ $stub->expects( $this->exactly( 0 ) )
+ ->method( 'hook_wpmu_dev_domain_mapping' )
+ ->will( $this->returnValue( false ) );
+
+ $stub->attempt_to_hook_domain_mapping_plugins();
+ }
+
+ function test_domain_mapping_mu_domain_mapping_not_hooked_when_function_not_exists() {
+ Jetpack_Constants::set_constant( 'SUNRISE_LOADED', true );
+
+ $stub = $this->getMockBuilder( 'MockDomainMapping' )
+ ->setMethods( array( 'function_exists' ) )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $stub->expects( $this->once() )
+ ->method( 'function_exists' )
+ ->will( $this->returnValue( false ) );
+
+ $this->assertFalse( $stub->hook_wordpress_mu_domain_mapping() );
+
+ foreach ( $this->get_jetpack_sync_filters() as $filter ) {
+ $this->assertFalse( $this->filter_has_hook( $filter ) );
+ }
+ }
+
+ function test_domain_mapping_mu_domain_mapping_hooked_when_function_exists() {
+ Jetpack_Constants::set_constant( 'SUNRISE_LOADED', true );
+
+ $stub = $this->getMockBuilder( 'MockDomainMapping' )
+ ->setMethods( array( 'function_exists' ) )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $stub->expects( $this->once() )
+ ->method( 'function_exists' )
+ ->will( $this->returnValue( true ) );
+
+ $this->assertTrue( $stub->hook_wordpress_mu_domain_mapping() );
+
+ foreach ( $this->get_jetpack_sync_filters() as $filter ) {
+ $this->assertTrue( $this->filter_has_hook( $filter ) );
+ }
+ }
+
+ function test_domain_mapping_wpmu_dev_domain_mapping_not_hooked_when_functions_not_exist() {
+ $stub = $this->getMockBuilder( 'MockDomainMapping' )
+ ->setMethods( array( 'class_exists', 'method_exists' ) )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $stub->expects( $this->once() )
+ ->method( 'class_exists' )
+ ->will( $this->returnValue( false ) );
+
+ $stub->expects( $this->exactly( 0 ) )
+ ->method( 'method_exists' )
+ ->will( $this->returnValue( false ) );
+
+ $this->assertFalse( $stub->hook_wpmu_dev_domain_mapping() );
+
+ foreach ( $this->get_jetpack_sync_filters() as $filter ) {
+ $this->assertFalse( $this->filter_has_hook( $filter ) );
+ }
+ }
+
+ function test_domain_mapping_wpmu_dev_domain_mapping_hooked_when_functions_exist() {
+ $stub = $this->getMockBuilder( 'MockDomainMapping' )
+ ->setMethods( array( 'class_exists', 'method_exists', 'get_domain_mapping_utils_instance' ) )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $stub->expects( $this->once() )
+ ->method( 'class_exists' )
+ ->will( $this->returnValue( true ) );
+
+ $stub->expects( $this->once() )
+ ->method( 'method_exists' )
+ ->will( $this->returnValue( true ) );
+
+ $stub->expects( $this->once() )
+ ->method( 'get_domain_mapping_utils_instance' )
+ ->will( $this->returnValue( new stdClass() ) );
+
+ $this->assertTrue( $stub->hook_wpmu_dev_domain_mapping() );
+
+ foreach ( $this->get_jetpack_sync_filters() as $filter ) {
+ $this->assertTrue( $this->filter_has_hook( $filter ) );
+ }
+ }
+
+ function filter_has_hook( $hook ) {
+ global $wp_filter;
+ return isset( $wp_filter[ $hook ] ) && ! empty( $wp_filter[ $hook ] );
+ }
+
+ function get_jetpack_sync_filters() {
+ return array(
+ 'jetpack_sync_home_url',
+ 'jetpack_sync_site_url',
+ );
+ }
+}
diff --git a/tests/php/sync/test_class.jetpack-sync-callables.php b/tests/php/sync/test_class.jetpack-sync-callables.php
index 95694bdc9f76..55e38362a531 100644
--- a/tests/php/sync/test_class.jetpack-sync-callables.php
+++ b/tests/php/sync/test_class.jetpack-sync-callables.php
@@ -204,9 +204,8 @@ function test_sync_jetpack_sync_unlock_sync_callable_action_allows_syncing_siteu
$this->server_replica_storage->reset();
- // We set the filters here to simulate how setting the WP_HOME and WP_SITEURL constant works.
- add_filter( 'option_home', array( $this, 'return_https_site_com_blog' ) );
- add_filter( 'option_siteurl', array( $this, 'return_https_site_com_blog' ) );
+ update_option( 'home', $this->return_https_site_com_blog() );
+ update_option( 'siteurl', $this->return_https_site_com_blog() );
/**
* Used to signal that the callables await transient should be cleared. Clearing the await transient is useful
@@ -228,8 +227,9 @@ function test_sync_jetpack_sync_unlock_sync_callable_action_allows_syncing_siteu
// Cleanup
unset( $_SERVER['HTTPS'] );
- remove_filter( 'option_home', array( $this, 'return_https_site_com_blog' ) );
- remove_filter( 'option_siteurl', array( $this, 'return_https_site_com_blog' ) );
+
+ update_option( 'home', $original_home_option );
+ update_option( 'siteurl', $original_siteurl_option );
}
function test_home_site_urls_synced_while_migrate_for_idc_set() {
@@ -270,25 +270,6 @@ function test_home_site_urls_synced_while_migrate_for_idc_set() {
Jetpack_Options::delete_option( 'migrate_for_idc' );
}
- function test_scheme_switching_does_not_cause_sync() {
- $this->setSyncClientDefaults();
- delete_transient( Jetpack_Sync_Module_Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
- delete_option( Jetpack_Sync_Module_Callables::CALLABLES_CHECKSUM_OPTION_NAME );
- $_SERVER['HTTPS'] = 'off';
- $home_url = home_url();
- $this->sender->do_sync();
-
- $this->assertEquals( $home_url, $this->server_replica_storage->get_callable( 'home_url' ) );
-
- // this sets is_ssl() to return true.
- $_SERVER['HTTPS'] = 'on';
- delete_transient( Jetpack_Sync_Module_Callables::CALLABLES_AWAIT_TRANSIENT_NAME );
- $this->sender->do_sync();
-
- unset( $_SERVER['HTTPS'] );
- $this->assertEquals( $home_url, $this->server_replica_storage->get_callable( 'home_url' ) );
- }
-
function return_example_com() {
return 'http://example.com';
}
@@ -517,7 +498,60 @@ function test_sanitize_sync_taxonomies_method() {
$sanitized = Jetpack_Sync_Functions::sanitize_taxonomy( (object) array( 'rest_controller_class' => 'WP_REST_Terms_Controller' ) );
$this->assertEquals( $sanitized->rest_controller_class, 'WP_REST_Terms_Controller' );
+}
+ function test_get_raw_url_by_option_bypasses_filters() {
+ add_filter( 'option_home', array( $this, '__return_filtered_url' ) );
+ $this->assertTrue( 'http://filteredurl.com' !== Jetpack_Sync_Functions::get_raw_url( 'home' ) );
+ remove_filter( 'option_home', array( $this, '__return_filtered_url' ) );
+ }
+
+ function test_get_raw_url_by_constant_bypasses_filters() {
+ Jetpack_Constants::set_constant( 'WP_HOME', 'http://constanturl.com' );
+ Jetpack_Constants::set_constant( 'WP_SITEURL', 'http://constanturl.com' );
+ add_filter( 'option_home', array( $this, '__return_filtered_url' ) );
+ add_filter( 'option_siteurl', array( $this, '__return_filtered_url' ) );
+
+ $this->assertEquals( 'http://constanturl.com', Jetpack_Sync_Functions::get_raw_url( 'home' ) );
+ $this->assertEquals( 'http://constanturl.com', Jetpack_Sync_Functions::get_raw_url( 'siteurl' ) );
+
+ remove_filter( 'option_home', array( $this, '__return_filtered_url' ) );
+ remove_filter( 'option_siteurl', array( $this, '__return_filtered_url' ) );
+ Jetpack_Constants::clear_constants();
+ }
+
+ function test_get_raw_url_returns_with_http_if_is_ssl() {
+ $home_option = get_option( 'home' );
+
+ // Test without https first
+ $this->assertEquals( $home_option, Jetpack_Sync_Functions::get_raw_url( 'home' ) );
+
+ // Now, with https
+ $_SERVER['HTTPS'] = 'on';
+ $this->assertEquals(
+ set_url_scheme( $home_option, 'http' ),
+ Jetpack_Sync_Functions::get_raw_url( 'home' )
+ );
+ unset( $_SERVER['HTTPS'] );
+ }
+
+ function test_user_can_stop_raw_urls() {
+ add_filter( 'option_home', array( $this, '__return_filtered_url' ) );
+ add_filter( 'option_siteurl', array( $this, '__return_filtered_url' ) );
+
+ // Test with constant first
+ $this->assertTrue( 'http://filteredurl.com' !== Jetpack_Sync_Functions::home_url() );
+
+ // Now, without, which should return the filtered URL
+ Jetpack_Constants::set_constant( 'JETPACK_SYNC_USE_RAW_URL', false );
+ $this->assertEquals( $this->__return_filtered_url(), Jetpack_Sync_Functions::home_url() );
+ Jetpack_Constants::clear_constants();
+
+ remove_filter( 'option_home', array( $this, '__return_filtered_url' ) );
+ remove_filter( 'option_siteurl', array( $this, '__return_filtered_url' ) );
+ }
+ function __return_filtered_url() {
+ return 'http://filteredurl.com';
}
function add_www_subdomain_to_siteurl( $url ) {
diff --git a/tests/php/test_class.jetpack.php b/tests/php/test_class.jetpack.php
index bdf5590d69c9..bf364c15d732 100644
--- a/tests/php/test_class.jetpack.php
+++ b/tests/php/test_class.jetpack.php
@@ -323,14 +323,6 @@ function test_idc_optin_default() {
}
}
- function test_idc_optin_false_when_sunrise() {
- Jetpack_Constants::set_constant( 'SUNRISE', true );
-
- $this->assertFalse( Jetpack::sync_idc_optin() );
-
- Jetpack_Constants::clear_constants();
- }
-
function test_idc_optin_filter_overrides_development_version() {
add_filter( 'jetpack_development_version', '__return_true' );
add_filter( 'jetpack_sync_idc_optin', '__return_false' );