From 185b3b84516c429e9f7fae0aaec5826414f0256a Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 5 Mar 2024 11:02:43 +0800 Subject: [PATCH 1/9] Add is_store_page helper function --- .../woocommerce/src/Admin/WCAdminHelper.php | 112 +++++++++++++++++- .../woocommerce-admin/wc-admin-helper.php | 66 ++++++++++- 2 files changed, 176 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/src/Admin/WCAdminHelper.php b/plugins/woocommerce/src/Admin/WCAdminHelper.php index 4e4ad1796c4b..52b2d04f803d 100644 --- a/plugins/woocommerce/src/Admin/WCAdminHelper.php +++ b/plugins/woocommerce/src/Admin/WCAdminHelper.php @@ -65,7 +65,6 @@ public static function get_wcadmin_active_for_in_seconds() { */ public static function is_wc_admin_active_for( $seconds ) { $wc_admin_active_for = self::get_wcadmin_active_for_in_seconds(); - return ( $wc_admin_active_for >= $seconds ); } @@ -122,4 +121,115 @@ public static function is_site_fresh() { return $date > $month_ago; } + + /** + * Test if a URL is a store page. + * + * Store pages are defined as: + * + * - My Account + * - Shop + * - Cart + * - Checkout + * - Privacy Policy + * - Terms and Conditions + * + * Additionally, the following autogenerated pages should be included: + * - Product pages + * - Product Category pages + * - Product Tag pages + * + * @param string $url URL to check. If not provided, the current URL will be used. + * @return bool Whether or not the URL is a store page. + */ + public static function is_store_page( $url = '' ) { + $url = $url ? $url : esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) ); + + if ( ! $url ) { + return false; + } + $normalized_path = self::get_normalized_url_path( $url ); + + // WC store pages. + $store_pages = array( + 'myaccount' => wc_get_page_id( 'myaccount' ), + 'shop' => wc_get_page_id( 'shop' ), + 'cart' => wc_get_page_id( 'cart' ), + 'checkout' => wc_get_page_id( 'checkout' ), + 'privacy' => wc_privacy_policy_page_id(), + 'terms' => wc_terms_and_conditions_page_id(), + ); + + foreach ( $store_pages as $page => $page_id ) { + if ( 0 >= $page_id ) { + continue; + } + + $permalink = get_permalink( $page_id ); + if ( ! $permalink ) { + continue; + } + + if ( 0 === strpos( $normalized_path, self::get_normalized_url_path( $permalink ) ) ) { + return true; + } + } + + // Check product, category and tag pages. + $permalink_structure = wc_get_permalink_structure(); + $permalink_keys = array( + 'category_base', + 'tag_base', + 'product_base', + ); + + foreach ( $permalink_keys as $key ) { + if ( ! isset( $permalink_structure[ $key ] ) || ! is_string( $permalink_structure[ $key ] ) ) { + continue; + } + + // Check if the URL path starts with the matching base. + if ( 0 === strpos( $normalized_path, trim( $permalink_structure[ $key ], '/' ) ) ) { + return true; + } + + // If the permalink structure contains placeholders, we need to check if the URL matches the structure using regex. + if ( strpos( $permalink_structure[ $key ], '%' ) !== false ) { + global $wp_rewrite; + $rules = $wp_rewrite->generate_rewrite_rule( $permalink_structure[ $key ] ); + + if ( is_array( $rules ) && ! empty( $rules ) ) { + // rule key is the regex pattern. + $rule = array_keys( $rules )[0]; + $rule = '#^' . str_replace( '?$', '', $rule ) . '#'; + + if ( preg_match( $rule, $normalized_path ) ) { + return true; + } + } + } + } + + return false; + } + + /** + * Get normalized URL path. + * 1. Only keep the path and query string (if any). + * 2. Remove wp home path from the URL path if WP is installed in a subdirectory. + * 3. Remove leading and trailing slashes. + * + * For example: + * + * - https://example.com/wordpress/shop/uncategorized/test/?add-to-cart=123 => shop/uncategorized/test/?add-to-cart=123 + * + * @param string $url URL to normalize. + */ + private static function get_normalized_url_path( $url ) { + $quey = wp_parse_url( $url, PHP_URL_QUERY ); + $path = wp_parse_url( $url, PHP_URL_PATH ) . ( $quey ? '?' . $quey : '' ); + $home_path = wp_parse_url( site_url(), PHP_URL_PATH ); + $normalized_path = trim( substr( $path, strlen( $home_path ) ), '/' ); + return $normalized_path; + } } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php index 03d23a3ac326..f7f70ff6ca46 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php @@ -12,7 +12,25 @@ * * @package WooCommerce\Admin\Tests\WCAdminHelper */ -class WC_Admin_Tests_Admin_Helper extends WP_UnitTestCase { +class WC_Admin_Tests_Admin_Helper extends WC_Unit_Test_Case { + /** + * Set up before class. + */ + public static function setUpBeforeClass(): void { + parent::setUpBeforeClass(); + + // Set up permalinks. + update_option( + 'woocommerce_permalinks', + array( + 'product_base' => '/shop/%product_cat%', + 'category_base' => 'product-category', + 'tag_base' => 'product-tag', + 'attribute_base' => 'test', + 'use_verbose_page_rules' => true, + ) + ); + } /** * Test get_wcadmin_active_for_in_seconds_with with invalid timestamp option. @@ -176,4 +194,50 @@ public function test_is_fresh_site_fresh_site_option_must_be_1() { update_option( 'fresh_site', '1' ); $this->assertTrue( WCAdminHelper::is_site_fresh() ); } + + /** + * Data provider for test_is_store_page . + * @return array + */ + public function store_page_data_provider() { + // Ensure pages exist. + WC_Install::create_pages(); + + return array( + array( get_permalink( wc_get_page_id( 'cart' ) ), true ), // Test case 1: URL matches cart page. + array( get_permalink( wc_get_page_id( 'myaccount' ) ) . '/orders/', true ), // Test case 2: URL matches my account > orders page. + array( 'https://example.com/product-category/sample-category/', true ), // Test case 3: URL matches product category page. + array( 'https://example.com/product-tag/sample-tag/', true ), // Test case 4: URL matches product tag page. + array( 'https://example.com/shop/uncategorized/test/', true ), // Test case 5: URL matches product page. + array( '/shop/t-shirt/test/', true ), // Test case 6: URL path matches product page. + array( 'https://example.com/about-us/', false ), // Test case 7: URL does not match any store page. + ); + } + + /** + * @dataProvider store_page_data_provider + * + * Test is_store_page function with different URLs. + * + * @param string $url URL to test. + * @param bool $expected_result Expected result. + */ + public function test_is_store_page( $url, $expected_result ) { + global $wp_rewrite; + + $wp_rewrite = $this->getMockBuilder( 'WP_Rewrite' )->getMock(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + + $permalink_structure = array( + 'category_base' => 'product-category', + 'tag_base' => 'product-tag', + 'product_base' => 'product', + ); + + $wp_rewrite->expects( $this->any() ) + ->method( 'generate_rewrite_rule' ) + ->willReturn( array( 'shop/(.+?)/?$' => 'index.php?product_cat=$matches[1]&year=$matches[2]' ) ); + + $result = WCAdminHelper::is_store_page( $url ); + $this->assertEquals( $expected_result, $result ); + } } From 8d473bcbade87917614587d353fefbc47ea77a0d Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 5 Mar 2024 11:03:07 +0800 Subject: [PATCH 2/9] Add changelog --- plugins/woocommerce/changelog/add-is-store-page | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/add-is-store-page diff --git a/plugins/woocommerce/changelog/add-is-store-page b/plugins/woocommerce/changelog/add-is-store-page new file mode 100644 index 000000000000..4794367bf70b --- /dev/null +++ b/plugins/woocommerce/changelog/add-is-store-page @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add is_store_page helper function From 18657a94d291a97016f4a33ba751a0580cc5025c Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 5 Mar 2024 11:15:00 +0800 Subject: [PATCH 3/9] Update doc --- plugins/woocommerce/src/Admin/WCAdminHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Admin/WCAdminHelper.php b/plugins/woocommerce/src/Admin/WCAdminHelper.php index 52b2d04f803d..4cbf99dc2bad 100644 --- a/plugins/woocommerce/src/Admin/WCAdminHelper.php +++ b/plugins/woocommerce/src/Admin/WCAdminHelper.php @@ -123,7 +123,7 @@ public static function is_site_fresh() { } /** - * Test if a URL is a store page. + * Test if a URL is a store page. This function only checks URL paths and does not consider the domain. * * Store pages are defined as: * From 263ea48e8393af88dcaf76fa26a988458932f566 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 5 Mar 2024 11:18:57 +0800 Subject: [PATCH 4/9] Update doc --- plugins/woocommerce/src/Admin/WCAdminHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/src/Admin/WCAdminHelper.php b/plugins/woocommerce/src/Admin/WCAdminHelper.php index 4cbf99dc2bad..46aa7fe9c276 100644 --- a/plugins/woocommerce/src/Admin/WCAdminHelper.php +++ b/plugins/woocommerce/src/Admin/WCAdminHelper.php @@ -123,7 +123,7 @@ public static function is_site_fresh() { } /** - * Test if a URL is a store page. This function only checks URL paths and does not consider the domain. + * Test if a URL is a store page. This function ignores the domain and protocol of the URL and only checks the path and query string. * * Store pages are defined as: * From f2a3e114d9a1fed6295a3c8ef3ed72418b972a3f Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 5 Mar 2024 11:36:05 +0800 Subject: [PATCH 5/9] Revert unneed changes --- plugins/woocommerce/src/Admin/WCAdminHelper.php | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/woocommerce/src/Admin/WCAdminHelper.php b/plugins/woocommerce/src/Admin/WCAdminHelper.php index 46aa7fe9c276..bdadf42e77f2 100644 --- a/plugins/woocommerce/src/Admin/WCAdminHelper.php +++ b/plugins/woocommerce/src/Admin/WCAdminHelper.php @@ -65,6 +65,7 @@ public static function get_wcadmin_active_for_in_seconds() { */ public static function is_wc_admin_active_for( $seconds ) { $wc_admin_active_for = self::get_wcadmin_active_for_in_seconds(); + return ( $wc_admin_active_for >= $seconds ); } From 490f5b52aa70d39bf581f32be49f63081ac71b56 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 5 Mar 2024 12:24:45 +0800 Subject: [PATCH 6/9] Fix test --- .../woocommerce-admin/wc-admin-helper.php | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php index f7f70ff6ca46..1f684db124f7 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php @@ -19,6 +19,9 @@ class WC_Admin_Tests_Admin_Helper extends WC_Unit_Test_Case { public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); + // Ensure pages exist. + WC_Install::create_pages(); + // Set up permalinks. update_option( 'woocommerce_permalinks', @@ -32,6 +35,19 @@ public static function setUpBeforeClass(): void { ); } + /** + * Tear down after class. + */ + public static function tearDownAfterClass(): void { + // Delete pages. + wp_delete_post( get_option( 'woocommerce_shop_page_id' ), true ); + wp_delete_post( get_option( 'woocommerce_cart_page_id' ), true ); + wp_delete_post( get_option( 'woocommerce_checkout_page_id' ), true ); + wp_delete_post( get_option( 'woocommerce_myaccount_page_id' ), true ); + wp_delete_post( wc_privacy_policy_page_id(), true ); + wp_delete_post( wc_terms_and_conditions_page_id(), true ); + } + /** * Test get_wcadmin_active_for_in_seconds_with with invalid timestamp option. */ @@ -196,13 +212,13 @@ public function test_is_fresh_site_fresh_site_option_must_be_1() { } /** - * Data provider for test_is_store_page . - * @return array + * Get store page test data. This data is used to test is_store_page function. + * + * We don't use the data provider in this test because data provider are executed before setUpBeforeClass and cause other tests to fail since we need to create pages to generate the test data. + * + * @return array[] list of store page test data. */ - public function store_page_data_provider() { - // Ensure pages exist. - WC_Install::create_pages(); - + public function get_store_page_test_data() { return array( array( get_permalink( wc_get_page_id( 'cart' ) ), true ), // Test case 1: URL matches cart page. array( get_permalink( wc_get_page_id( 'myaccount' ) ) . '/orders/', true ), // Test case 2: URL matches my account > orders page. @@ -215,14 +231,13 @@ public function store_page_data_provider() { } /** - * @dataProvider store_page_data_provider * * Test is_store_page function with different URLs. * * @param string $url URL to test. * @param bool $expected_result Expected result. */ - public function test_is_store_page( $url, $expected_result ) { + public function test_is_store_page() { global $wp_rewrite; $wp_rewrite = $this->getMockBuilder( 'WP_Rewrite' )->getMock(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited @@ -237,7 +252,12 @@ public function test_is_store_page( $url, $expected_result ) { ->method( 'generate_rewrite_rule' ) ->willReturn( array( 'shop/(.+?)/?$' => 'index.php?product_cat=$matches[1]&year=$matches[2]' ) ); - $result = WCAdminHelper::is_store_page( $url ); - $this->assertEquals( $expected_result, $result ); + $test_data = $this->get_store_page_test_data(); + + foreach ( $test_data as $data ) { + list( $url, $expected_result ) = $data; + $result = WCAdminHelper::is_store_page( $url ); + $this->assertEquals( $expected_result, $result ); + } } } From 7bf2c0944306d7f5fd977c8fa7cf4f35d7e5f37a Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 5 Mar 2024 13:23:36 +0800 Subject: [PATCH 7/9] Fix lint --- .../legacy/unit-tests/woocommerce-admin/wc-admin-helper.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php index 1f684db124f7..f132325adbd2 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php @@ -234,8 +234,6 @@ public function get_store_page_test_data() { * * Test is_store_page function with different URLs. * - * @param string $url URL to test. - * @param bool $expected_result Expected result. */ public function test_is_store_page() { global $wp_rewrite; From d84d9f20d955c19bfc6f5409993837cb43160d2f Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Mon, 18 Mar 2024 14:06:39 +0800 Subject: [PATCH 8/9] Fix typo --- plugins/woocommerce/src/Admin/WCAdminHelper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/src/Admin/WCAdminHelper.php b/plugins/woocommerce/src/Admin/WCAdminHelper.php index bdadf42e77f2..52aafb5bd73d 100644 --- a/plugins/woocommerce/src/Admin/WCAdminHelper.php +++ b/plugins/woocommerce/src/Admin/WCAdminHelper.php @@ -227,8 +227,8 @@ public static function is_store_page( $url = '' ) { * @param string $url URL to normalize. */ private static function get_normalized_url_path( $url ) { - $quey = wp_parse_url( $url, PHP_URL_QUERY ); - $path = wp_parse_url( $url, PHP_URL_PATH ) . ( $quey ? '?' . $quey : '' ); + $query = wp_parse_url( $url, PHP_URL_QUERY ); + $path = wp_parse_url( $url, PHP_URL_PATH ) . ( $query ? '?' . $query : '' ); $home_path = wp_parse_url( site_url(), PHP_URL_PATH ); $normalized_path = trim( substr( $path, strlen( $home_path ) ), '/' ); return $normalized_path; From 095c96345e70061a16afe801489bb10c56698247 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Mon, 18 Mar 2024 14:12:08 +0800 Subject: [PATCH 9/9] Add woocommerce_store_pages filter --- plugins/woocommerce/src/Admin/WCAdminHelper.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/woocommerce/src/Admin/WCAdminHelper.php b/plugins/woocommerce/src/Admin/WCAdminHelper.php index 52aafb5bd73d..4295b69e80d6 100644 --- a/plugins/woocommerce/src/Admin/WCAdminHelper.php +++ b/plugins/woocommerce/src/Admin/WCAdminHelper.php @@ -161,6 +161,14 @@ public static function is_store_page( $url = '' ) { 'terms' => wc_terms_and_conditions_page_id(), ); + /** + * Filter the store pages array to check if a URL is a store page. + * + * @since 8.8.0 + * @param array $store_pages The store pages array. The keys are the page slugs and the values are the page IDs. + */ + $store_pages = apply_filters( 'woocommerce_store_pages', $store_pages ); + foreach ( $store_pages as $page => $page_id ) { if ( 0 >= $page_id ) { continue;