Preview: Endpoint.php
Size: 27.62 KB
/home/nshryvcy/radiantskinclinics.org/wp-content/plugins/woocommerce/src/Internal/OrderReviews/Endpoint.php
<?php
/**
* Endpoint class file.
*/
declare( strict_types = 1 );
namespace Automattic\WooCommerce\Internal\OrderReviews;
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Enums\OrderStatus;
use WC_Order;
use WP_Post;
/**
* Routes `/review-order/{id}/?key={order_key}` to the WooCommerce-managed
* Review Order page and renders the read-only landing page through the
* `[woocommerce_review_order]` shortcode.
*
* The page is intentionally hosted outside the checkout/my-account family:
*
* - It is not a checkout sub-mode like order-pay or order-received; the
* customer is reviewing past purchases, not transacting.
* - It is not a my-account endpoint because the order key is the auth, so
* guest customers must be able to reach it without logging in.
*
* The route uses the same wp_posts-backed page pattern as the checkout
* page so the active theme owns the page chrome (header, footer, sidebar)
* on both classic and block themes; the shortcode only renders the form
* body inside `the_content`. Any failed gating check renders the theme's
* 404 template so a leaked or stale link cannot disclose order existence.
*
* The container auto-calls `init()` after instantiation, which is where
* the WordPress hooks are registered. Resolution is driven by the
* `OrderReviews` wrapper that lists this class as an `init()` argument.
*
* @internal Just for internal use.
*
* @since 10.8.0
*/
class Endpoint {
/**
* Query var that the rewrite rule sets to the order id.
*/
public const QUERY_VAR = 'review-order';
/**
* `wc_get_page_id()` key for the WC-managed Review Order page.
*/
public const PAGE_KEY = 'review_order';
/**
* Shortcode tag that renders the page body inside the WC page content.
*/
public const SHORTCODE = 'woocommerce_review_order';
/**
* Wire the endpoint into WordPress.
*
* Auto-called by the WC dependency container after instantiation. The
* title-suppression filters are deliberately NOT registered here; they
* land inside `gate_request()` once the request is confirmed to be an
* authorised review-order render, so they never run on unrelated pages.
*
* @internal
*/
final public function init(): void {
// Seed the host page before `add_rewrite_rule` runs on init:10.
add_action( 'init', array( $this, 'maybe_create_host_page' ), 4 );
add_action( 'init', array( $this, 'add_rewrite_rule' ) );
add_filter( 'query_vars', array( $this, 'add_query_var' ), 0 );
add_action( 'template_redirect', array( $this, 'gate_request' ) );
add_action( 'wp_loaded', array( $this, 'maybe_flush_pending_rewrite' ) );
add_action( 'transition_post_status', array( $this, 'skip_auto_menu_for_self' ), 9, 3 );
add_filter( 'get_pages', array( $this, 'exclude_self_from_page_list' ) );
add_filter( 'display_post_states', array( $this, 'add_post_state_label' ), 10, 2 );
// Inject our entry into every `WC_Install::create_pages()` invocation so
// Status → Tools "Create default pages" and any other repair caller see it too.
add_filter( 'woocommerce_create_pages', array( $this, 'inject_review_order_page' ) );
add_shortcode( self::SHORTCODE, array( $this, 'render_shortcode' ) );
}
/**
* Create or adopt the Review Order host page on every feature-on init.
*
* Idempotent and self-healing: re-aligns the stored option with whichever
* row WP's permalink routing would resolve `/review-order/` to, so the
* page id `gate_request()` checks always matches the page that
* `add_rewrite_rule()` points at. Leftover duplicates from prior
* activation/disable cycles no longer cause asset enqueueing to silently
* skip.
*
* @since 10.8.0
*
* @internal
*/
public function maybe_create_host_page(): void {
// Fast path: the stored option already points at a published page
// that still embeds our shortcode. `get_post()` is served from the
// posts cache so this short-circuit costs ~nothing per request and
// avoids the slug `wp_posts` lookup the reconciliation path runs.
$option_id = (int) wc_get_page_id( self::PAGE_KEY );
$option_page = $option_id > 0 ? get_post( $option_id ) : null;
if ( $option_page instanceof WP_Post
&& 'page' === $option_page->post_type
&& 'publish' === $option_page->post_status
&& false !== strpos( (string) $option_page->post_content, '[' . self::SHORTCODE . ']' ) ) {
return;
}
// Reconcile: adopt the slug-routed page when it also embeds our
// shortcode. The combined signal avoids hijacking a merchant page
// that happens to share either the slug or the shortcode alone.
$canonical = $this->find_canonical_host_page();
if ( $canonical instanceof WP_Post ) {
$needs_save = false;
if ( $option_id !== (int) $canonical->ID ) {
update_option( 'woocommerce_review_order_page_id', (int) $canonical->ID );
$needs_save = true;
}
if ( 'publish' !== $canonical->post_status ) {
wp_update_post(
array(
'ID' => (int) $canonical->ID,
'post_status' => 'publish',
)
);
$needs_save = true;
}
if ( $needs_save ) {
update_option( 'woocommerce_review_order_flush_rewrite_pending', 'yes' );
}
return;
}
// No slug-canonical page. If the merchant renamed the host page away
// from our default slug but the stored option still resolves to a
// non-trashed page, respect it and only republish a draft we own.
if ( $option_page instanceof WP_Post && 'page' === $option_page->post_type && 'trash' !== $option_page->post_status ) {
if ( 'publish' !== $option_page->post_status ) {
wp_update_post(
array(
'ID' => (int) $option_page->ID,
'post_status' => 'publish',
)
);
update_option( 'woocommerce_review_order_flush_rewrite_pending', 'yes' );
}
return;
}
// No managed page anywhere. The permanent `woocommerce_create_pages`
// filter (registered in `init()`) makes the call inject our entry.
\WC_Install::create_pages();
// Defer the rewrite flush to wp_loaded; rewrite_rule fires later on init.
update_option( 'woocommerce_review_order_flush_rewrite_pending', 'yes' );
}
/**
* Append the Review Order page to any caller of
* `WC_Install::create_pages()` — keeps Status → Tools' "Create default
* pages" repair path and any third-party callers seeded with our page
* whenever the feature is on, without having to call create_pages()
* with a one-off filter in `maybe_create_host_page()`.
*
* @since 10.8.0
*
* @internal Public only because WP filter callbacks need to be callable from outside.
*
* @param array<string,array<string,string>>|mixed $pages Existing page definitions.
* @return array<string,array<string,string>>|mixed
*/
public function inject_review_order_page( $pages ) {
if ( ! is_array( $pages ) ) {
return $pages;
}
$pages[ self::PAGE_KEY ] = array(
'name' => _x( 'review-order', 'Page slug', 'woocommerce' ),
'title' => _x( 'Review your order', 'Page title', 'woocommerce' ),
'content' => '<!-- wp:shortcode -->[' . self::SHORTCODE . ']<!-- /wp:shortcode -->',
);
return $pages;
}
/**
* Return the slug-routed page if it also embeds our shortcode, so we only
* adopt rows that are unambiguously WC-owned (matching slug alone or the
* shortcode alone would hijack merchant-authored pages).
*
* @since 10.8.0
*
* @return WP_Post|null
*/
private function find_canonical_host_page(): ?WP_Post {
$page = get_page_by_path( _x( 'review-order', 'Page slug', 'woocommerce' ), OBJECT, 'page' );
if ( ! $page instanceof WP_Post || 'trash' === $page->post_status ) {
return null;
}
if ( false === strpos( (string) $page->post_content, '[' . self::SHORTCODE . ']' ) ) {
return null;
}
return $page;
}
/**
* Label the Review Order page in the admin Pages list ("— Review Order
* Page"), mirroring how `WC_Admin_Post_Types` labels Shop / Cart /
* Checkout / My account so editors can spot it at a glance.
*
* @since 10.8.0
*
* @internal Public only because WP filter callbacks need to be callable from outside.
*
* @param array<string,string>|mixed $post_states Existing post-state labels keyed by id.
* @param \WP_Post|mixed $post Current post being listed.
* @return array<string,string>|mixed
*/
public function add_post_state_label( $post_states, $post ) {
if ( ! is_array( $post_states ) || ! $post instanceof \WP_Post ) {
return $post_states;
}
$page_id = (int) wc_get_page_id( self::PAGE_KEY );
if ( $page_id > 0 && $page_id === (int) $post->ID ) {
$post_states['wc_page_for_review_order'] = __( 'Review Order Page', 'woocommerce' );
}
return $post_states;
}
/**
* Hide the Review Order page from `get_pages()` results.
*
* Block themes' `core/page-list` block (and any classic theme using
* `wp_list_pages()`) calls `get_pages()` to populate its list. Without
* this filter the tokenised landing page would appear in the site
* navigation alongside Cart / Checkout / My account, which is wrong:
* the page is reachable only through the per-order email link.
*
* @param \WP_Post[]|mixed $pages Page objects returned by get_pages().
* @return \WP_Post[]|mixed
*/
public function exclude_self_from_page_list( $pages ) {
if ( ! is_array( $pages ) || empty( $pages ) ) {
return $pages;
}
$page_id = (int) wc_get_page_id( self::PAGE_KEY );
if ( $page_id <= 0 ) {
return $pages;
}
return array_values(
array_filter(
$pages,
static function ( $page ) use ( $page_id ) {
return ! ( $page instanceof \WP_Post ) || (int) $page->ID !== $page_id;
}
)
);
}
/**
* Suppress the theme-rendered page title for classic themes on the
* Review Order page.
*
* The page body (`templates/order/customer-review-order.php` and the
* empty-state template) already prints its own `<h1>`, so the chrome
* heading would duplicate the text both visually and for screen readers.
*
* `gate_request()` registers this filter only after the request passes
* the auth check, so on any unrelated render it isn't even on the hook.
* Two in-method guards narrow the scope to the page title slot of the
* Review Order render itself:
*
* - The post id must match the Review Order page id, so within the same
* render a nav menu item or "recent posts" widget pointing at another
* post stays intact.
* - `in_the_loop() && is_main_query()` keeps the filter scoped to the
* actual page title slot. WP's `wp_get_document_title()` reads the
* post title outside the loop, so the `<title>` tag stays meaningful.
*
* @since 10.8.0
*
* @param string|mixed $title Title being rendered.
* @param int|mixed $post_id Post id the title belongs to.
* @return string|mixed
*/
public function maybe_hide_page_title( $title, $post_id = 0 ) {
$page_id = (int) wc_get_page_id( self::PAGE_KEY );
if ( (int) $post_id !== $page_id ) {
return $title;
}
if ( ! in_the_loop() || ! is_main_query() ) {
return $title;
}
return '';
}
/**
* Suppress the `core/post-title` block on block themes when it is bound
* to the Review Order page itself.
*
* Block themes render the page title through `core/post-title` rather
* than `the_title`, so the classic-theme filter above doesn't catch it.
* Two guards keep the suppression narrow (registration is gated by
* `gate_request()` so the filter isn't even on the hook for unrelated
* renders):
*
* - The hook is `render_block_core/post-title` so unrelated block types
* (headings, paragraphs, navigation, etc.) never reach this method.
* - The block's resolved `context['postId']` must match the Review Order
* page id, so a `core/post-title` rendered inside a Query Loop, a
* related-posts template part, or a footer "recent posts" panel for a
* different post on the same render is untouched.
*
* @since 10.8.0
*
* @param string|mixed $block_content Block markup.
* @param array<string,mixed> $block Parsed block (unused but kept for filter signature).
* @param \WP_Block|mixed|null $instance Rendering instance carrying context.
* @return string|mixed
*/
public function maybe_hide_post_title_block( $block_content, $block, $instance = null ) {
unset( $block );
if ( ! $instance instanceof \WP_Block ) {
return $block_content;
}
$page_id = (int) wc_get_page_id( self::PAGE_KEY );
$block_postid = isset( $instance->context['postId'] ) ? (int) $instance->context['postId'] : 0;
if ( $block_postid !== $page_id ) {
return $block_content;
}
return '';
}
/**
* Keep the Review Order page out of nav menus that have "Auto add new
* top-level pages" enabled.
*
* The page is reachable only through the tokenised URL the email sends
* out; nobody navigates to it from a menu, so it should never appear
* there. WP's `_wp_auto_add_pages_to_menu()` runs on
* `transition_post_status` at priority 10. Detach it just before that
* for our specific page, then restore it on priority 11 so other
* transitions are unaffected.
*
* Compares by slug rather than by stored option id so it also fires on
* the very first install — before `woocommerce_review_order_page_id`
* is written.
*
* @param string $new_status New post status.
* @param string $old_status Old post status.
* @param \WP_Post $post Post object.
*/
public function skip_auto_menu_for_self( $new_status, $old_status, $post ): void {
unset( $new_status, $old_status );
if ( ! $post instanceof \WP_Post || 'page' !== $post->post_type ) {
return;
}
// Identify the page by stored option id (post-install) or by the
// shortcode in its content (during install, before the option
// exists). Don't compare $post->post_name to 'review-order' alone:
// WP appends -2/-3/... if the slug already exists.
$stored_id = (int) get_option( 'woocommerce_review_order_page_id' );
$is_by_id = $stored_id > 0 && $stored_id === (int) $post->ID;
$is_by_slug = '' === $post->post_name
? false
: ( 'review-order' === $post->post_name || 0 === strpos( $post->post_name, 'review-order-' ) );
$is_by_body = false !== strpos( (string) $post->post_content, '[' . self::SHORTCODE . ']' );
if ( ! $is_by_id && ! $is_by_slug && ! $is_by_body ) {
return;
}
remove_action( 'transition_post_status', '_wp_auto_add_pages_to_menu', 10 );
add_action(
'transition_post_status',
static function () {
add_action( 'transition_post_status', '_wp_auto_add_pages_to_menu', 10, 3 );
},
11
);
}
/**
* Flush rewrite rules once after the Review Order page is seeded or
* republished.
*
* `maybe_create_host_page()` runs on `init` priority 4 and queues the
* flush by setting `woocommerce_review_order_flush_rewrite_pending`;
* `add_rewrite_rule()` doesn't fire until `init` priority 10, so the
* flush has to happen later. `wp_loaded` runs after every `init`
* callback, which is the earliest safe moment.
*/
public function maybe_flush_pending_rewrite(): void {
if ( 'yes' !== get_option( 'woocommerce_review_order_flush_rewrite_pending' ) ) {
return;
}
flush_rewrite_rules( false );
delete_option( 'woocommerce_review_order_flush_rewrite_pending' );
}
/**
* Register the rewrite rule for the review-order endpoint.
*
* Maps `/<page-slug>/{id}/` to the WC-managed Review Order page so the
* active theme renders its standard page chrome around the shortcode.
*/
public function add_rewrite_rule(): void {
$page_id = (int) wc_get_page_id( self::PAGE_KEY );
if ( $page_id <= 0 ) {
return;
}
$page = get_post( $page_id );
if ( ! $page instanceof WP_Post || 'publish' !== $page->post_status ) {
return;
}
// Use the full page-permalink path so hierarchical pages
// (Review Order page moved under a parent) keep working.
$permalink = get_permalink( $page_id );
if ( ! is_string( $permalink ) || '' === $permalink ) {
return;
}
$path = trim( (string) wp_make_link_relative( $permalink ), '/' );
if ( '' === $path ) {
return;
}
add_rewrite_rule(
'^' . preg_quote( $path, '/' ) . '/([0-9]+)/?$',
'index.php?page_id=' . $page_id . '&' . self::QUERY_VAR . '=$matches[1]',
'top'
);
}
/**
* Allow the query var through `WP::parse_request()`.
*
* @param string[] $vars Query vars.
* @return string[]
*/
public function add_query_var( array $vars ): array {
$vars[] = self::QUERY_VAR;
return $vars;
}
/**
* Run the gating checks before the page template renders.
*
* Auth failures fall through to a 404 here rather than inside the
* shortcode so the response status is set before any output begins.
* On success the request continues into normal page rendering and the
* shortcode echoes the body inside `the_content`.
*/
public function gate_request(): void {
global $wp;
// Only act when the request resolves to the WC-managed Review Order
// page. A leftover review-order query var on some other page (manual
// URL tampering, third-party plugin) shouldn't trigger our auth
// path or 404 an unrelated page.
$page_id = (int) wc_get_page_id( self::PAGE_KEY );
if ( $page_id <= 0 || ! is_page( $page_id ) ) {
return;
}
// Use isset() rather than empty() so the literal "0" doesn't slip
// through to normal WP routing; the auth check 404s on order_id 0.
if ( ! isset( $wp->query_vars[ self::QUERY_VAR ] ) ) {
// Visiting the host page directly (no order id in the URL) is a
// dead end — the shortcode renders nothing and the customer
// sees a chrome-only page. Send them to the home page instead.
wp_safe_redirect( home_url( '/' ) );
exit;
}
$order_id = absint( $wp->query_vars[ self::QUERY_VAR ] );
$order_key = $this->read_order_key();
$order = $order_id ? wc_get_order( $order_id ) : false;
if ( ! $this->is_authorised( $order, $order_key ) ) {
$this->render_404();
exit;
}
// Register the page-title suppression filters now that the request
// is fully authorised. Doing this here instead of `init()` keeps the
// filters out of every unrelated page render and removes the need
// for a per-instance "is this an authorised render" boolean.
add_filter( 'the_title', array( $this, 'maybe_hide_page_title' ), 10, 2 );
// Block-specific filter so only `core/post-title` is touched —
// `render_block` would fire for every block on the page. The third
// arg is the `WP_Block` instance carrying `context['postId']`, used
// to scope to the host page.
add_filter( 'render_block_core/post-title', array( $this, 'maybe_hide_post_title_block' ), 10, 3 );
if ( $order instanceof WC_Order ) {
$this->maybe_mark_no_actionable_rows( $order );
}
// template_redirect fires after wp_enqueue_scripts but before
// wp_head, so styles registered here are still output in <head>.
$this->enqueue_assets();
}
/**
* Render the Review Order page body for the WC-managed page.
*
* Called by `the_content` on the page that hosts `[woocommerce_review_order]`.
* Returns an empty string when the request did not arrive through the
* tokenised rewrite, so a logged-in admin previewing the page directly
* sees nothing rather than a partial form.
*
* @return string
*/
public function render_shortcode(): string {
global $wp;
if ( ! isset( $wp->query_vars[ self::QUERY_VAR ] ) ) {
return '';
}
$order_id = absint( $wp->query_vars[ self::QUERY_VAR ] );
$order = $order_id ? wc_get_order( $order_id ) : false;
if ( ! $order instanceof WC_Order ) {
// gate_request() will already have 404'd; this is defensive.
return '';
}
ob_start();
wc_get_template( 'order/customer-review-order.php', array( 'order' => $order ) );
return (string) ob_get_clean();
}
/**
* Render the Review Order body directly. Public so unit tests can drive
* the rendering path without staging a global request and the rewrite.
*
* @internal
*
* @param int $order_id Order id parsed from the URL.
*/
public function render( int $order_id ): void {
$order_key = $this->read_order_key();
$order = $order_id ? wc_get_order( $order_id ) : false;
if ( ! $this->is_authorised( $order, $order_key ) ) {
$this->render_404();
return;
}
if ( $order instanceof WC_Order ) {
$this->maybe_mark_no_actionable_rows( $order );
}
wc_get_template( 'order/customer-review-order.php', array( 'order' => $order ) );
}
/**
* Stamp the completed-at meta when the Review Order page would render the
* empty-state, so back-button visits and direct revisits also record
* completion. The persistent write lives here, in the controller, so the
* page template stays read-only.
*
* Scope differs from `SubmissionHandler::maybe_mark_order_complete()`:
* that one counts the customer's reviews per product across all of their
* history, while this one walks the per-item decisions ItemEligibility
* produces (order-scoped, mirroring exactly what the page renders).
*
* @param WC_Order $order Order being reviewed.
*/
private function maybe_mark_no_actionable_rows( WC_Order $order ): void {
$completed_meta_key = SubmissionHandler::COMPLETED_META_KEY;
if ( $order->get_meta( $completed_meta_key ) ) {
return;
}
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment -- documented on customer-review-order.php template.
$items = (array) apply_filters( 'woocommerce_review_order_eligible_items', $order->get_items(), $order );
ItemEligibility::preload_for_items( $items, $order );
foreach ( $items as $item ) {
if ( ! $item instanceof \WC_Order_Item_Product ) {
continue;
}
$decision = ItemEligibility::decide( $item, $order );
// Skip rows are intentionally treated as "done": an order whose
// items all have reviews disabled renders the empty-state, so we
// stamp completion to match what the customer sees on the page.
if ( ItemEligibility::STATUS_SKIP === $decision['status'] ) {
continue;
}
// Any non-skip row without a review tied to this order means the
// customer still has something to submit — order isn't complete.
if ( ! ( $decision['comment'] instanceof \WP_Comment ) ) {
return;
}
}
$order->update_meta_data( $completed_meta_key, (string) time() );
try {
$order->save();
} catch ( \Exception $e ) {
wc_get_logger()->warning(
sprintf(
/* translators: 1: order ID, 2: error message */
__( 'Could not stamp Review Order completion meta on order %1$d: %2$s.', 'woocommerce' ),
$order->get_id(),
$e->getMessage()
),
array( 'source' => 'order-reviews' )
);
}
}
/**
* Build the public, tokenised URL for an order's review-order page.
*
* @param WC_Order $order Order to build the URL for.
* @return string
*/
public static function get_url( WC_Order $order ): string {
$page_id = (int) wc_get_page_id( self::PAGE_KEY );
$permalink = (string) ( $page_id > 0 ? get_permalink( $page_id ) : '' );
if ( '' === $permalink ) {
$url = '';
} elseif ( false === strpos( $permalink, '?' ) ) {
// Pretty permalinks: append the order id as a path segment.
$url = trailingslashit( $permalink ) . (string) $order->get_id() . '/';
$url = add_query_arg( 'key', $order->get_order_key(), $url );
} else {
// Plain permalinks: page permalink is /?page_id=NNN, so add the
// order id as a query var rather than munging the path.
$url = add_query_arg(
array(
self::QUERY_VAR => (string) $order->get_id(),
'key' => $order->get_order_key(),
),
$permalink
);
}
/**
* Filter the Review Order URL that the review-request email links to.
*
* @since 10.8.0
*
* @param string $url The review-order URL.
* @param WC_Order $order The order object.
*/
return (string) apply_filters( 'woocommerce_review_order_url', $url, $order );
}
/**
* Read the order key from the request, sanitised.
*
* @return string
*/
private function read_order_key(): string {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only landing page; the order key is the auth.
$raw = ( isset( $_GET['key'] ) && is_string( $_GET['key'] ) ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : '';
return is_string( $raw ) ? $raw : '';
}
/**
* Decide whether the request is allowed to render the page.
*
* @param mixed $order The candidate order. Anything other than a `WC_Order` fails.
* @param string $order_key The order key supplied via query arg.
* @return bool
*/
private function is_authorised( $order, string $order_key ): bool {
if ( ! $order instanceof WC_Order ) {
return false;
}
if ( '' === $order_key || ! hash_equals( $order->get_order_key(), $order_key ) ) {
return false;
}
/**
* Filter the order statuses that are eligible to access the Review Order page.
*
* The scheduler unschedules pending sends on refund/cancel/trash/delete, but
* emails already in the customer's inbox can still be clicked. The route-level
* check blocks those late clicks for orders that have moved out of the
* eligible set.
*
* @since 10.8.0
*
* @param string[] $eligible_statuses Status slugs without the `wc-` prefix.
* @param WC_Order $order The order being reviewed.
*/
$eligible_statuses = (array) apply_filters(
'woocommerce_review_order_eligible_statuses',
array( OrderStatus::COMPLETED ),
$order
);
if ( ! in_array( $order->get_status(), $eligible_statuses, true ) ) {
return false;
}
// Logged-in customer must own the order. Guests with the order key still pass.
if ( $order->get_customer_id() && is_user_logged_in() && get_current_user_id() !== $order->get_customer_id() ) {
return false;
}
return true;
}
/**
* Enqueue the JS and CSS that progressively enhance the page.
*
* Both files live under `client/legacy/` and are built into
* `assets/{js|css}/` by the classic-assets pipeline.
*/
private function enqueue_assets(): void {
$plugin_url = untrailingslashit( plugins_url( '', WC_PLUGIN_FILE ) );
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
$version = Constants::get_constant( 'WC_VERSION' );
$asset_url = static function ( string $path ) use ( $plugin_url ): string {
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment -- documented in includes/class-wc-frontend-scripts.php.
return (string) apply_filters( 'woocommerce_get_asset_url', $plugin_url . $path, $path );
};
wp_enqueue_style( 'wc-order-review', $asset_url( '/assets/css/order-review.css' ), array(), $version );
// Tell WP to swap to the *-rtl.css variant on RTL sites.
wp_style_add_data( 'wc-order-review', 'rtl', 'replace' );
wp_enqueue_script(
'wc-order-review',
$asset_url( '/assets/js/frontend/order-review' . $suffix . '.js' ),
array(),
$version,
array(
'strategy' => 'defer',
'in_footer' => true,
)
);
wp_localize_script(
'wc-order-review',
'wcOrderReview',
array(
'i18n' => array(
'ok' => __( 'Thanks, your review is live.', 'woocommerce' ),
'pending_moderation' => __( 'Thanks, your review is pending approval.', 'woocommerce' ),
'error' => __( 'Something went wrong, please try again.', 'woocommerce' ),
'rating_required' => __( 'Please rate this product before submitting your review.', 'woocommerce' ),
),
)
);
}
/**
* Mark the current request as a 404 and load the theme's 404 template.
*
* Fails closed on every gating check so a stale or tampered link cannot
* disclose order existence.
*/
private function render_404(): void {
global $wp_query;
$wp_query->set_404();
status_header( 404 );
nocache_headers();
$template = get_query_template( '404' );
if ( ! empty( $template ) && file_exists( $template ) ) {
include $template;
return;
}
// Fallback when the active theme has no 404 template: emit a minimal
// page so the response body isn't empty.
printf(
'<!doctype html><html><head><meta charset="utf-8"><title>%1$s</title></head><body><h1>%1$s</h1></body></html>',
esc_html__( 'Page not found', 'woocommerce' )
);
}
}
Directory Contents
Dirs: 0 × Files: 6