Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Permalink solution for the checkout endpoint/template #9406

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 110 additions & 5 deletions src/BlockTemplatesController.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<?php
namespace Automattic\WooCommerce\Blocks;

use Automattic\WooCommerce\Admin\Overrides\Order;
use Automattic\WooCommerce\Blocks\Domain\Package;
use Automattic\WooCommerce\Blocks\Templates\CartTemplate;
use Automattic\WooCommerce\Blocks\Templates\CheckoutTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplateCompatibility;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
use Automattic\WooCommerce\Blocks\Templates\OrderReceivedTemplate;
use Automattic\WooCommerce\Blocks\Utils\SettingsUtils;
use \WP_Post;

/**
* BlockTypesController class.
Expand Down Expand Up @@ -74,6 +75,12 @@ protected function init() {
add_filter( 'taxonomy_template_hierarchy', array( $this, 'add_archive_product_to_eligible_for_fallback_templates' ), 10, 1 );
add_filter( 'post_type_archive_title', array( $this, 'update_product_archive_title' ), 10, 2 );

if ( wc_current_theme_is_fse_theme() ) {
add_filter( 'woocommerce_settings_pages', array( $this, 'template_permalink_settings' ) );
add_filter( 'pre_update_option', array( $this, 'update_template_permalink' ), 10, 2 );
add_action( 'woocommerce_admin_field_permalink', array( SettingsUtils::class, 'permalink_input_field' ) );
}

if ( $this->package->is_experimental_build() ) {
add_action( 'after_switch_theme', array( $this, 'check_should_use_blockified_product_grid_templates' ), 10, 2 );
}
Expand Down Expand Up @@ -566,18 +573,18 @@ public function render_block_template() {
}
} elseif (
is_cart() &&
! BlockTemplateUtils::theme_has_template( CartTemplate::SLUG ) && $this->block_template_is_available( CartTemplate::SLUG )
! BlockTemplateUtils::theme_has_template( CartTemplate::get_slug() ) && $this->block_template_is_available( CartTemplate::get_slug() )
) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
} elseif (
is_checkout() &&
! BlockTemplateUtils::theme_has_template( CheckoutTemplate::SLUG ) && $this->block_template_is_available( CheckoutTemplate::SLUG )
! BlockTemplateUtils::theme_has_template( CheckoutTemplate::get_slug() ) && $this->block_template_is_available( CheckoutTemplate::get_slug() )
) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
} elseif (
is_wc_endpoint_url( 'order-received' )
&& ! BlockTemplateUtils::theme_has_template( OrderReceivedTemplate::SLUG )
&& $this->block_template_is_available( OrderReceivedTemplate::SLUG )
&& ! BlockTemplateUtils::theme_has_template( OrderReceivedTemplate::get_slug() )
&& $this->block_template_is_available( OrderReceivedTemplate::get_slug() )
) {
add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
} else {
Expand Down Expand Up @@ -646,4 +653,102 @@ function_exists( 'is_shop' ) &&

return $post_type_name;
}

/**
* Replaces page settings in WooCommerce with text based permalinks which point to a template.
*
* @param array $settings Settings pages.
* @return array
*/
public function template_permalink_settings( $settings ) {
foreach ( $settings as $key => $setting ) {
if ( 'woocommerce_checkout_page_id' === $setting['id'] ) {
$checkout_page = CheckoutTemplate::get_placeholder_page();
$settings[ $key ] = [
'title' => __( 'Checkout page', 'woo-gutenberg-products-block' ),
'desc' => sprintf(
// translators: %1$s: opening anchor tag, %2$s: closing anchor tag.
__( 'The checkout template can be %1$s edited here%2$s.', 'woo-gutenberg-products-block' ),
'<a href="' . esc_url( admin_url( 'site-editor.php?postType=wp_template&postId=woocommerce%2Fwoocommerce%2F%2F' . CheckoutTemplate::get_slug() ) ) . '" target="_blank">',
'</a>'
),
'desc_tip' => __( 'This is the URL to the checkout page.', 'woo-gutenberg-products-block' ),
'id' => 'woocommerce_checkout_page_endpoint',
'type' => 'permalink',
'default' => $checkout_page ? $checkout_page->post_name : CheckoutTemplate::get_slug(),
'autoload' => false,
];
}
if ( 'woocommerce_cart_page_id' === $setting['id'] ) {
$cart_page = CartTemplate::get_placeholder_page();
$settings[ $key ] = [
'title' => __( 'Cart page', 'woo-gutenberg-products-block' ),
'desc' => sprintf(
// translators: %1$s: opening anchor tag, %2$s: closing anchor tag.
__( 'The cart template can be %1$s edited here%2$s.', 'woo-gutenberg-products-block' ),
'<a href="' . esc_url( admin_url( 'site-editor.php?postType=wp_template&postId=woocommerce%2Fwoocommerce%2F%2F' . CartTemplate::get_slug() ) ) . '" target="_blank">',
'</a>'
),
'desc_tip' => __( 'This is the URL to the cart page.', 'woo-gutenberg-products-block' ),
'id' => 'woocommerce_cart_page_endpoint',
'type' => 'permalink',
'default' => $cart_page ? $cart_page->post_name : CartTemplate::get_slug(),
'autoload' => false,
];
}
}

return $settings;
}

/**
* Syncs entered permalink with the pages and returns the correct value.
*
* @param string $value Value of the option.
* @param string $option Name of the option.
* @return string
*/
public function update_template_permalink( $value, $option ) {
if ( 'woocommerce_checkout_page_endpoint' === $option ) {
return $this->sync_endpoint_with_page( CheckoutTemplate::get_placeholder_page(), 'checkout', $value );
}
if ( 'woocommerce_cart_page_endpoint' === $option ) {
return $this->sync_endpoint_with_page( CartTemplate::get_placeholder_page(), 'cart', $value );
}
return $value;
}

/**
* Syncs the provided permalink with the actual WP page.
*
* @param WP_Post|null $page The page object, or null if it does not exist.
* @param string $page_slug The identifier for the page e.g. cart, checkout.
* @param string $permalink The new permalink to use.
* @return string THe actual permalink assigned to the page. May differ from $permalink if it was already taken.
*/
protected function sync_endpoint_with_page( $page, $page_slug, $permalink ) {
if ( ! $page ) {
$updated_page_id = wc_create_page(
esc_sql( $permalink ),
'woocommerce_' . $page_slug . '_page_id',
$page_slug,
'',
'',
'publish'
);
} else {
$updated_page_id = wp_update_post(
[
'ID' => $page->ID,
'post_name' => esc_sql( $permalink ),
]
);
}

// Get post again in case slug was updated with a suffix.
if ( $updated_page_id && ! is_wp_error( $updated_page_id ) ) {
return get_post( $updated_page_id )->post_name;
}
return $permalink;
}
}
2 changes: 1 addition & 1 deletion src/BlockTypes/ClassicTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ protected function render( $attributes, $content, $block ) {
$frontend_scripts::load_scripts();
}

if ( OrderReceivedTemplate::SLUG === $attributes['template'] ) {
if ( OrderReceivedTemplate::get_slug() === $attributes['template'] ) {
return $this->render_order_received();
}

Expand Down
138 changes: 138 additions & 0 deletions src/Templates/AbstractPageTemplate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php
namespace Automattic\WooCommerce\Blocks\Templates;

/**
* AbstractPageTemplate class.
*
* Shared logic for page templates.
*
* @internal
*/
abstract class AbstractPageTemplate {
/**
* Page Template functionality is only initialized when using a block theme.
*/
public function __construct() {
if ( wc_current_theme_is_fse_theme() ) {
$this->init();
}
}

/**
* Initialization method.
*/
protected function init() {
add_filter( 'page_template_hierarchy', array( $this, 'page_template_hierarchy' ), 1 );
add_filter( 'frontpage_template_hierarchy', array( $this, 'page_template_hierarchy' ), 1 );
add_filter( 'woocommerce_blocks_template_content', array( $this, 'page_template_content' ), 10, 2 );
add_action( 'current_screen', array( $this, 'page_template_editor_redirect' ) );
add_filter( 'pre_get_document_title', array( $this, 'page_template_title' ) );
}

/**
* Returns the template slug.
*
* @return string
*/
abstract public static function get_slug();

/**
* Returns the page object assigned to this template/page.
*
* @return \WP_Post|null Post object or null.
*/
abstract public static function get_placeholder_page();

/**
* Should return the title of the page.
*
* @return string
*/
abstract public static function get_template_title();

/**
* Should return true on pages/endpoints/routes where the template should be shown.
*
* @return boolean
*/
abstract protected function is_active_template();

/**
* Returns the URL to edit the template.
*
* @return string
*/
protected function get_edit_template_url() {
return admin_url( 'site-editor.php?postType=wp_template&postId=woocommerce%2Fwoocommerce%2F%2F' . $this->get_slug() );
}

/**
* Get the default content for a template.
*
* Overridden by child class to include their own logic.
*
* @param string $template_content The original content of the template.
* @return string
*/
protected function get_default_template_content( $template_content ) {
return $template_content;
}

/**
* When the page should be displaying the template, add it to the hierarchy.
*
* This places the template name e.g. `cart`, at the beginning of the template hierarchy array. The hook priority
* is 1 to ensure it runs first; other consumers e.g. extensions, could therefore inject their own template instead
* of this one when using the default priority of 10.
*
* @param array $templates Templates that match the pages_template_hierarchy.
*/
public function page_template_hierarchy( $templates ) {
if ( $this->is_active_template() ) {
array_unshift( $templates, $this->get_slug() );
}
return $templates;
}

/**
* Returns the default template content.
*
* @param string $template_content The content of the template.
* @param object $template_file The template file object.
* @return string
*/
public function page_template_content( $template_content, $template_file ) {
if ( $this->get_slug() !== $template_file->slug ) {
return $template_content;
}
return $this->get_default_template_content( $template_content );
}

/**
* Redirect the edit page screen to the template editor.
*
* @param \WP_Screen $current_screen Current screen information.
*/
public function page_template_editor_redirect( \WP_Screen $current_screen ) {
$page = $this->get_placeholder_page();
$edit_page_id = 'page' === $current_screen->id && ! empty( $_GET['post'] ) ? absint( $_GET['post'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

if ( $page && $edit_page_id === $page->id ) {
wp_safe_redirect( $this->get_edit_template_url() );
exit;
}
}

/**
* Filter the page title when the template is active.
*
* @param string $title Page title.
* @return string
*/
public function page_template_title( $title ) {
if ( $this->is_active_template() ) {
return $this->get_template_title();
}
return $title;
}
}
Loading