AbstractOrderConfirmationBlock.php
<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* AbstractOrderConfirmationBlock class.
*/
abstract class AbstractOrderConfirmationBlock extends AbstractBlock {
/**
* Initialize this block type.
*
* - Hook into WP lifecycle.
* - Register the block with WordPress.
*/
protected function initialize() {
parent::initialize();
add_action( 'wp_loaded', array( $this, 'register_patterns' ) );
}
/**
* Get the content from a hook and return it.
*
* @param string $hook Hook name.
* @param array $args Array of args to pass to the hook.
* @return string
*/
protected function get_hook_content( $hook, $args ) {
ob_start();
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
do_action_ref_array( $hook, $args );
return ob_get_clean();
}
/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string | void Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
$order = $this->get_order();
$permission = $this->get_view_order_permissions( $order );
$block_content = $order ? $this->render_content( $order, $permission, $attributes, $content ) : $this->render_content_fallback();
$classname = $attributes['className'] ?? '';
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
if ( ! empty( $classes_and_styles['classes'] ) ) {
$classname .= ' ' . $classes_and_styles['classes'];
}
return $block_content ? sprintf(
'<div class="wc-block-%4$s %1$s" style="%2$s">%3$s</div>',
esc_attr( trim( $classname ) ),
esc_attr( $classes_and_styles['styles'] ),
$block_content,
esc_attr( $this->block_name )
) : '';
}
/**
* This renders the content of the block within the wrapper. The permission determines what data can be shown under
* the given context.
*
* @param \WC_Order $order Order object.
* @param string|false $permission If the current user can view the order details or not.
* @param array $attributes Block attributes.
* @param string $content Original block content.
* @return string
*/
abstract protected function render_content( $order, $permission = false, $attributes = [], $content = '' );
/**
* This is what gets rendered when the order does not exist. Renders nothing by default, but can be overridden by
* child classes.
*
* @return string
*/
protected function render_content_fallback() {
return '';
}
/**
* Get current order.
*
* @return \WC_Order|null
*/
protected function get_order() {
$order_id = absint( get_query_var( 'order-received' ) );
if ( $order_id ) {
return wc_get_order( $order_id );
}
return null;
}
/**
* View mode for order details based on the order, current user, and settings.
*
* @param \WC_Order|null $order Order object.
* @return string|false Returns "full" if the user can view all order details. False if they can view no details.
*/
protected function get_view_order_permissions( $order ) {
if ( ! $order || ! $this->has_valid_order_key( $order ) ) {
return false; // Always disallow access to invalid orders and those without a valid key.
}
// For customers with accounts, verify the order belongs to the current user or disallow access.
if ( $this->is_customer_order( $order ) ) {
/**
* Indicates if known (non-guest) shoppers need to be logged in before we let
* them access the order received page.
*
* @param bool $verify_known_shoppers If verification is required.
*
* @since 8.4.0
*/
$verify_known_shoppers = apply_filters( 'woocommerce_order_received_verify_known_shoppers', true );
// If verification for known shoppers is disabled, we can show the order details.
if ( ! $verify_known_shoppers ) {
return 'full';
}
return $this->is_current_customer_order( $order ) ? 'full' : false;
}
// Guest orders are displayed only within the grace period or after verification. If email verification is required, return false.
return $this->email_verification_required( $order ) ? false : 'full';
}
/**
* See if guest checkout is enabled.
*
* @return boolean
*/
protected function allow_guest_checkout() {
return 'yes' === get_option( 'woocommerce_enable_guest_checkout' );
}
/**
* Guest users without an active session can provide their email address to view order details. This however can only
* be permitted if the user also provided the correct order key, and guest checkout is actually enabled.
*
* @param \WC_Order $order Order object.
* @return boolean
*/
protected function email_verification_permitted( $order ) {
return $this->allow_guest_checkout() && $this->has_valid_order_key( $order ) && ! $this->is_customer_order( $order );
}
/**
* See if the order was created within the grace period for viewing details.
*
* @param \WC_Order $order Order object.
* @return boolean
*/
protected function is_within_grace_period( $order ) {
/**
* Controls the grace period within which we do not require any sort of email verification step before rendering
* the 'order received' or 'order pay' pages.
*
* @see \WC_Shortcode_Checkout::order_received()
* @since 11.4.0
* @param int $grace_period Time in seconds after an order is placed before email verification may be required.
* @param \WC_Order $order The order for which this grace period is being assessed.
* @param string $context Indicates the context in which we might verify the email address. Typically 'order-pay' or 'order-received'.
*/
$verification_grace_period = (int) apply_filters( 'woocommerce_order_email_verification_grace_period', 10 * MINUTE_IN_SECONDS, $order, 'order-received' );
$date_created = $order->get_date_created();
return is_a( $date_created, \WC_DateTime::class ) && time() - $date_created->getTimestamp() <= $verification_grace_period;
}
/**
* Returns true if the email has been verified (posted email matches given order email).
*
* @param \WC_Order $order Order object.
* @return boolean
*/
protected function is_email_verified( $order ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
if ( empty( $_POST ) || ! isset( $_POST['email'] ) || ! wp_verify_nonce( $_POST['check_submission'] ?? '', 'wc_verify_email' ) ) {
return false;
}
return $order->get_billing_email() && sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ) === $order->get_billing_email();
}
/**
* See if we need to verify the email address before showing the order details.
*
* @param \WC_Order $order Order object.
* @return boolean
*/
protected function email_verification_required( $order ) {
$session = wc()->session;
// Skip verification if the current user still has the order in their session.
if ( is_a( $session, \WC_Session::class ) && $order->get_id() === (int) $session->get( 'store_api_draft_order' ) ) {
return false;
}
// Skip verification if the order was created within the grace period.
if ( $this->is_within_grace_period( $order ) ) {
return false;
}
// If the user verified their email address, we can skip further verification.
if ( $this->is_email_verified( $order ) ) {
return false;
}
/**
* Provides an opportunity to override the (potential) requirement for shoppers to verify their email address
* before we show information such as the order summary, or order payment page.
*
* @see \WC_Shortcode_Checkout::order_received()
* @since 11.4.0
* @param bool $email_verification_required If email verification is required.
* @param WC_Order $order The relevant order.
* @param string $context The context under which we are performing this check.
*/
return (bool) apply_filters( 'woocommerce_order_email_verification_required', true, $order, 'order-received' );
}
/**
* See if the order key is valid.
*
* @param \WC_Order $order Order object.
* @return boolean
*/
protected function has_valid_order_key( $order ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
return ! empty( $_GET['key'] ) && $order->key_is_valid( wc_clean( wp_unslash( $_GET['key'] ) ) );
}
/**
* See if the current order came from a guest or a logged in customer.
*
* @param \WC_Order $order Order object.
* @return boolean
*/
protected function is_customer_order( $order ) {
return 0 < $order->get_user_id();
}
/**
* See if the current logged in user ID matches the given order customer ID.
*
* Returns false for logged-out customers.
*
* @param \WC_Order $order Order object.
* @return boolean
*/
protected function is_current_customer_order( $order ) {
return $this->is_customer_order( $order ) && $order->get_user_id() === get_current_user_id();
}
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*/
protected function get_block_type_script( $key = null ) {
return null;
}
/**
* Register block pattern for Order Confirmation to make it translatable.
*/
public function register_patterns() {
register_block_pattern(
'woocommerce/order-confirmation-totals-heading',
array(
'title' => '',
'inserter' => false,
'content' => '<!-- wp:heading {"level":3,"style":{"typography":{"fontSize":"24px"}}} --><h3 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Order details', 'woocommerce' ) . '</h3><!-- /wp:heading -->',
)
);
register_block_pattern(
'woocommerce/order-confirmation-downloads-heading',
array(
'title' => '',
'inserter' => false,
'content' => '<!-- wp:heading {"level":3,"style":{"typography":{"fontSize":"24px"}}} --><h3 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Downloads', 'woocommerce' ) . '</h3><!-- /wp:heading -->',
)
);
register_block_pattern(
'woocommerce/order-confirmation-shipping-heading',
array(
'title' => '',
'inserter' => false,
'content' => '<!-- wp:heading {"level":3,"style":{"typography":{"fontSize":"24px"}}} --><h3 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Shipping address', 'woocommerce' ) . '</h3><!-- /wp:heading -->',
)
);
register_block_pattern(
'woocommerce/order-confirmation-billing-heading',
array(
'title' => '',
'inserter' => false,
'content' => '<!-- wp:heading {"level":3,"style":{"typography":{"fontSize":"24px"}}} --><h3 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Billing address', 'woocommerce' ) . '</h3><!-- /wp:heading -->',
)
);
register_block_pattern(
'woocommerce/order-confirmation-additional-fields-heading',
array(
'title' => '',
'inserter' => false,
'content' => '<!-- wp:heading {"level":3,"style":{"typography":{"fontSize":"24px"}}} --><h3 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Additional information', 'woocommerce' ) . '</h3><!-- /wp:heading -->',
)
);
}
/**
* Render custom fields for the order.
*
* @param array $fields List of additional fields with values.
* @return string
*/
protected function render_additional_fields( $fields ) {
if ( empty( $fields ) ) {
return '';
}
return '<dl class="wc-block-components-additional-fields-list">' . implode( '', array_map( array( $this, 'render_additional_field' ), $fields ) ) . '</dl>';
}
/**
* Render custom field row.
*
* @param array $field An additional field and value.
* @return string
*/
protected function render_additional_field( $field ) {
return sprintf(
'<dt>%1$s</dt><dd>%2$s</dd>',
esc_html( $field['label'] ),
esc_html( $field['value'] )
);
}
}