diff --git a/plugins/woocommerce/includes/class-wc-product-download.php b/plugins/woocommerce/includes/class-wc-product-download.php
index a0f47b516e7..a3176b7d5ca 100644
--- a/plugins/woocommerce/includes/class-wc-product-download.php
+++ b/plugins/woocommerce/includes/class-wc-product-download.php
@@ -201,7 +201,17 @@ class WC_Product_Download implements ArrayAccess {
return;
}
- $download_file = $this->get_file();
+ $download_file = $this->get_file();
+
+ /**
+ * Controls whether shortcodes should be resolved and validated using the Approved Download Directory feature.
+ *
+ * @param bool $should_validate
+ */
+ if ( apply_filters( 'woocommerce_product_downloads_approved_directory_validation_for_shortcodes', true ) && 'shortcode' === $this->get_type_of_file_path() ) {
+ $download_file = do_shortcode( $download_file );
+ }
+
$is_site_administrator = is_multisite() ? current_user_can( 'manage_sites' ) : current_user_can( 'manage_options' );
$valid_storage_directory = $download_directories->is_valid_path( $download_file );
@@ -216,7 +226,7 @@ class WC_Product_Download implements ArrayAccess {
/* translators: %1$s is the downloadable file path, %2$s is an opening link tag, %3%s is a closing link tag. */
__( 'The downloadable file %1$s cannot be used: it is not located in an approved directory. Please contact a site administrator and request their approval. %2$sLearn more.%3$s', 'woocommerce' ),
'' . $download_file . '
',
- '', // @todo update to working link (see https://github.com/Automattic/woocommerce/issues/181)
+ '',
''
)
);
@@ -229,7 +239,7 @@ class WC_Product_Download implements ArrayAccess {
/* translators: %1$s is the downloadable file path, %2$s is an opening link tag, %3%s is a closing link tag. */
__( 'The downloadable file %1$s cannot be used: it is not located in an approved directory. Please contact a site administrator for help. %2$sLearn more.%3$s', 'woocommerce' ),
'' . $download_file . '
',
- '', // @todo update to working link (see https://github.com/Automattic/woocommerce/issues/181)
+ '',
''
)
);
diff --git a/plugins/woocommerce/legacy/js/admin/settings.js b/plugins/woocommerce/legacy/js/admin/settings.js
index 702b0ae1a19..29276907af6 100644
--- a/plugins/woocommerce/legacy/js/admin/settings.js
+++ b/plugins/woocommerce/legacy/js/admin/settings.js
@@ -70,8 +70,14 @@
// Edit prompt
$( function() {
var changed = false;
+ let $check_column = $( '.wp-list-table .check-column' );
+
+ $( 'input, textarea, select, checkbox' ).on( 'change', function( event ) {
+ // Toggling WP List Table checkboxes should not trigger navigation warnings.
+ if ( $check_column.length && $check_column.has( event.target ) ) {
+ return;
+ }
- $( 'input, textarea, select, checkbox' ).on( 'change', function() {
if ( ! changed ) {
window.onbeforeunload = function() {
return params.i18n_nav_warning;
@@ -80,7 +86,7 @@
}
});
- $( '.submit :input' ).on( 'click', function() {
+ $( '.submit :input, input#search-submit' ).on( 'click', function() {
window.onbeforeunload = '';
});
});
diff --git a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/OptionRuleProcessor.php b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/OptionRuleProcessor.php
index 57ad552174a..3ef30a9b97e 100644
--- a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/OptionRuleProcessor.php
+++ b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/OptionRuleProcessor.php
@@ -42,7 +42,7 @@ class OptionRuleProcessor implements RuleProcessorInterface {
}
if ( isset( $rule->transformers ) && is_array( $rule->transformers ) ) {
- $option_value = TransformerService::apply( $option_value, $rule->transformers, $rule->default );
+ $option_value = TransformerService::apply( $option_value, $rule->transformers, $default );
}
return ComparisonOperation::compare(
diff --git a/plugins/woocommerce/src/Internal/ProductDownloads/ApprovedDirectories/Synchronize.php b/plugins/woocommerce/src/Internal/ProductDownloads/ApprovedDirectories/Synchronize.php
index 0db1da8bd5e..d44c0404991 100644
--- a/plugins/woocommerce/src/Internal/ProductDownloads/ApprovedDirectories/Synchronize.php
+++ b/plugins/woocommerce/src/Internal/ProductDownloads/ApprovedDirectories/Synchronize.php
@@ -219,7 +219,18 @@ class Synchronize {
$parent_url = _x( 'invalid URL', 'Approved product download URLs migration', 'woocommerce' );
try {
- $parent_url = ( new URL( $downloadable->get_file() ) )->get_parent_url();
+ $download_file = $downloadable->get_file();
+
+ /**
+ * Controls whether shortcodes should be resolved and validated using the Approved Download Directory feature.
+ *
+ * @param bool $should_validate
+ */
+ if ( apply_filters( 'woocommerce_product_downloads_approved_directory_validation_for_shortcodes', true ) && 'shortcode' === $downloadable->get_type_of_file_path() ) {
+ $download_file = do_shortcode( $download_file );
+ }
+
+ $parent_url = ( new URL( $download_file ) )->get_parent_url();
$this->register->add_approved_directory( $parent_url, false );
} catch ( Exception $e ) {
wc_get_logger()->log(
diff --git a/plugins/woocommerce/src/Internal/Utilities/URL.php b/plugins/woocommerce/src/Internal/Utilities/URL.php
index 340caa71c34..26b429e9f2a 100644
--- a/plugins/woocommerce/src/Internal/Utilities/URL.php
+++ b/plugins/woocommerce/src/Internal/Utilities/URL.php
@@ -300,8 +300,16 @@ class URL {
$parent_path = '/' . $parent_path;
}
- // Form the parent URL, then process it exactly as we would any other URL for consistency.
- $parent_url = $this->get_url( $this->get_path( $parent_path ) );
+ // Form the parent URL (ditching the query and fragment, if set).
+ $parent_url = $this->get_url(
+ array(
+ 'path' => $parent_path,
+ 'query' => null,
+ 'fragment' => null,
+ )
+ );
+
+ // We process the parent URL through a fresh instance of this class, for consistency.
return ( new self( $parent_url ) )->get_url();
}
@@ -310,27 +318,29 @@ class URL {
*
* Borrows from https://www.php.net/manual/en/function.parse-url.php#106731
*
- * @param string $path_override If provided this will be used as the URL path.
+ * @param array $component_overrides If provided, these will override values set in $this->components.
*
* @return string
*/
- public function get_url( string $path_override = null ): string {
- $scheme = null !== $this->components['scheme'] ? $this->components['scheme'] . '://' : '';
- $host = null !== $this->components['host'] ? $this->components['host'] : '';
- $port = null !== $this->components['port'] ? ':' . $this->components['port'] : '';
- $path = $path_override ?? $this->get_path();
+ public function get_url( array $component_overrides = array() ): string {
+ $components = array_merge( $this->components, $component_overrides );
+
+ $scheme = null !== $components['scheme'] ? $components['scheme'] . '://' : '';
+ $host = null !== $components['host'] ? $components['host'] : '';
+ $port = null !== $components['port'] ? ':' . $components['port'] : '';
+ $path = $this->get_path( $components['path'] );
// Special handling for hostless URLs (typically, filepaths) referencing the current working directory.
if ( '' === $host && ( '' === $path || '.' === $path ) ) {
$path = './';
}
- $user = null !== $this->components['user'] ? $this->components['user'] : '';
- $pass = null !== $this->components['pass'] ? ':' . $this->components['pass'] : '';
+ $user = null !== $components['user'] ? $components['user'] : '';
+ $pass = null !== $components['pass'] ? ':' . $components['pass'] : '';
$user_pass = ( ! empty( $user ) || ! empty( $pass ) ) ? $user . $pass . '@' : '';
- $query = null !== $this->components['query'] ? '?' . $this->components['query'] : '';
- $fragment = null !== $this->components['fragment'] ? '#' . $this->components['fragment'] : '';
+ $query = null !== $components['query'] ? '?' . $components['query'] : '';
+ $fragment = null !== $components['fragment'] ? '#' . $components['fragment'] : '';
return $scheme . $user_pass . $host . $port . $path . $query . $fragment;
}
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php b/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php
index 0cb0ad4dc15..ba3d9eb484e 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php
@@ -63,4 +63,48 @@ class WC_Product_Download_Test extends WC_Unit_Test_Case {
$this->expectExceptionMessage( 'cannot be used: it is not located in an approved directory' );
$download->check_is_valid();
}
+
+ /**
+ * Test handling of filepaths described via shortcodes in relation to the Approved Download Directory
+ * feature. This is to simulate scenarios such as encountered when using the S3 Downloads extension.
+ */
+ public function test_shortcode_resolution_for_approved_directory_rules() {
+ /** @var Download_Directories $download_directories */
+ $download_directories = wc_get_container()->get( Download_Directories::class );
+ $download_directories->set_mode( Download_Directories::MODE_ENABLED );
+ $dynamic_filepath = 'https://fast.reliable.external.fileserver.com/bucket-123/textbook.pdf';
+
+ // We select an admin user because we wish to automatically add Approved Directory rules.
+ $admin_user = wp_insert_user( array( 'user_login' => uniqid(), 'role' => 'administrator', 'user_pass' => 'x' ) );
+ wp_set_current_user( $admin_user );
+
+ add_shortcode( 'dynamic-download', function () {
+ return 'https://fast.reliable.external.fileserver.com/bucket-123/textbook.pdf';
+ } );
+
+ $this->assertFalse(
+ $download_directories->is_valid_path( $dynamic_filepath ),
+ 'Confirm the filepath returned by the test URL is not yet valid.'
+ );
+
+ $download = new WC_Product_Download();
+ $download->set_file( '[dynamic-download]' );
+
+ $this->assertNull(
+ $download->check_is_valid(),
+ 'The downloadable file successfully validates (if it did not, an exception would be thrown).'
+ );
+
+ $this->assertTrue(
+ $download_directories->is_valid_path( $dynamic_filepath ),
+ 'Confirm the filepath returned by the test URL is now considered valid.'
+ );
+
+ remove_shortcode( 'dynamic-download' );
+
+ // Now the shortcode is removed (perhaps the parent plugin has been removed/disabled) it will not resolve
+ // and so the filepath will not validate.
+ $this->expectException( 'Error' );
+ $download_directories->check_is_valid();
+ }
}