Add generic function to determine if URL is a store page (#45299)

* Add is_store_page helper function

* Add changelog

* Update doc

* Update doc

* Revert unneed changes

* Fix test

* Fix lint

* Fix typo

* Add woocommerce_store_pages filter
This commit is contained in:
Chi-Hsuan Huang 2024-03-18 16:29:13 +08:00 committed by GitHub
parent 5e8bf7dbd6
commit 63a0d5e57c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 206 additions and 1 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add is_store_page helper function

View File

@ -122,4 +122,123 @@ class WCAdminHelper {
return $date > $month_ago;
}
/**
* 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:
*
* - 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(),
);
/**
* 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;
}
$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 ) {
$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;
}
}

View File

@ -12,7 +12,41 @@ use Automattic\WooCommerce\Admin\WCAdminHelper;
*
* @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();
// Ensure pages exist.
WC_Install::create_pages();
// 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,
)
);
}
/**
* 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.
@ -176,4 +210,52 @@ class WC_Admin_Tests_Admin_Helper extends WP_UnitTestCase {
update_option( 'fresh_site', '1' );
$this->assertTrue( WCAdminHelper::is_site_fresh() );
}
/**
* 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 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.
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.
);
}
/**
*
* Test is_store_page function with different URLs.
*
*/
public function test_is_store_page() {
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]' ) );
$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 );
}
}
}