diff --git a/files/acl/acl.php b/files/acl/acl.php index c61de59136..1e3012020f 100644 --- a/files/acl/acl.php +++ b/files/acl/acl.php @@ -72,25 +72,32 @@ function get_option_as_bool_if_exists( $option_name ) { /** * Check if the path is allowed for the current context. * - * @param string $file_path Path to the file, minus the `/wp-content/uploads/` bit. It's the second portion returned by `Pre_Wp_Utils\prepare_request()` + * @param string $file_path Path to the file, minus the `/wp-content/uploads/` bit. + * This is the second portion returned by `Pre_Wp_Utils\prepare_request()`. + * @return bool True if the file path is valid for the current site, false otherwise. */ function is_valid_path_for_site( $file_path ) { - if ( ! is_multisite() ) { - return true; - } - - // If main site, don't allow access to /sites/ subdirectories. - if ( is_main_network() && is_main_site() ) { - if ( 0 === strpos( $file_path, 'sites/' ) ) { - return false; + $is_valid = true; + + if ( is_multisite() ) { + // If main site, don't allow access to `/sites/` subdirectories. + if ( is_main_network() && is_main_site() ) { + $is_valid = ! str_starts_with( $file_path, 'sites/' ); + } else { + // Check if the file path matches the current site ID's directory. + $base_path = sprintf( 'sites/%d', get_current_blog_id() ); + $is_valid = str_starts_with( $file_path, $base_path ); } - - return true; } - $base_path = sprintf( 'sites/%d', get_current_blog_id() ); - - return 0 === strpos( $file_path, $base_path ); + /** + * Filter the result of the path validation for the current site. + * Allows to override the logic used to determine if a file path is valid for the current site. + * + * @param bool $is_valid Whether the file path is valid for the current site. + * @param string $file_path Path to the file, minus the `/wp-content/uploads/` bit. + */ + return apply_filters( 'vip_files_acl_is_valid_path_for_site', $is_valid, $file_path ); } /** diff --git a/tests/files/acl/test-acl.php b/tests/files/acl/test-acl.php index e2948cc90c..f043476a1f 100644 --- a/tests/files/acl/test-acl.php +++ b/tests/files/acl/test-acl.php @@ -366,4 +366,59 @@ public function test__is_valid_path_for_site__multisite_subsite_cannot_access_an $this->assertEquals( $expected_is_allowed, $actual_is_allowed ); } + + public function test__is_valid_path_for_site__multisite_subsite_can_access_other_subsite_with_filter() { + if ( ! is_multisite() ) { + $this->markTestSkipped(); + } + + $expected_is_allowed = true; + + add_filter( 'vip_files_acl_is_valid_path_for_site', '__return_true' ); + + // Create two subsites + $first_subsite_id = $this->factory()->blog->create(); + $second_subsite_id = $this->factory()->blog->create(); + + // Get file path from second + $file_path = sprintf( 'sites/%d/2021/01/parakeets.gif', $second_subsite_id ); + + // Restore first subsite + switch_to_blog( $first_subsite_id ); + + $actual_is_allowed = is_valid_path_for_site( $file_path ); + + $this->assertEquals( $expected_is_allowed, $actual_is_allowed ); + } + + public function test__is_valid_path_for_site__multisite_subsite_can_access_other_paths_with_filter() { + if ( ! is_multisite() ) { + $this->markTestSkipped(); + } + + $expected_is_allowed = true; + + add_filter( + 'vip_files_acl_is_valid_path_for_site', + function ( $is_valid, $file_path ) { + // Allow access to any file path starting with 'custom-uploads/'. + if ( str_starts_with( $file_path, 'custom-uploads/' ) ) { + return true; + } + + // Return the original validation result for other paths. + return $is_valid; + }, + 10, + 2 + ); + + $this->factory()->blog->create(); + + $file_path = 'custom-uploads/parakeets.gif'; + + $actual_is_allowed = is_valid_path_for_site( $file_path ); + + $this->assertEquals( $expected_is_allowed, $actual_is_allowed ); + } }