diff --git a/functions.php b/functions.php
index 51ef884..0f3fa67 100644
--- a/functions.php
+++ b/functions.php
@@ -43,16 +43,42 @@ function wc_bootstrap_setup(): void {
add_action( 'after_setup_theme', 'wc_bootstrap_setup' );
/**
- * Register plugin template overrides.
+ * Initialize the WooCommerce-to-Twig template bridge.
*
- * Prepends the child theme's templates/ directory to the plugin's Twig loader,
- * so child theme templates take priority over plugin templates.
+ * Registers the WooCommerce Twig extension with the parent theme's TwigService
+ * and sets up template interception hooks so the child theme's Twig templates
+ * are rendered instead of WooCommerce's PHP templates.
+ *
+ * Runs at 'init' priority 20 to ensure WooCommerce is fully loaded (it
+ * initializes at 'init' priority 0).
*/
-function wc_bootstrap_register_template_override(): void {
+function wc_bootstrap_init_twig_bridge(): void {
+ // Guard: require parent TwigService and WooCommerce.
+ if ( ! class_exists( '\WPBootstrap\Twig\TwigService' ) || ! function_exists( 'WC' ) ) {
+ return;
+ }
+
+ $twig = \WPBootstrap\Twig\TwigService::getInstance();
+ $env = $twig->getEnvironment();
+
+ // Add child theme templates directory to the Twig loader so {% include %}
+ // directives in Twig templates can find other child theme templates.
+ $loader = $env->getLoader();
+ if ( $loader instanceof \Twig\Loader\FilesystemLoader ) {
+ $template_dir = WC_BOOTSTRAP_PATH . 'templates';
+ if ( is_dir( $template_dir ) ) {
+ $loader->prependPath( $template_dir );
+ }
+ }
+
+ // Register WooCommerce functions and filters as Twig extensions.
+ $env->addExtension( new \WcBootstrap\Twig\WooCommerceExtension() );
+
+ // Register template interception hooks.
$override = new \WcBootstrap\TemplateOverride();
$override->register();
}
-add_action( 'after_setup_theme', 'wc_bootstrap_register_template_override' );
+add_action( 'init', 'wc_bootstrap_init_twig_bridge', 20 );
/**
* Enqueue child theme styles.
diff --git a/inc/TemplateOverride.php b/inc/TemplateOverride.php
index 624a53a..20ab7ee 100644
--- a/inc/TemplateOverride.php
+++ b/inc/TemplateOverride.php
@@ -1,9 +1,12 @@
template_path = WC_BOOTSTRAP_PATH . 'templates';
+ $this->templatePath = WC_BOOTSTRAP_PATH . 'templates/';
}
/**
- * Register the template override with WordPress hooks.
+ * Register the template override hooks.
*
- * Must be called after the plugin's Template singleton is initialized
- * (plugin inits at 'init' priority 0).
+ * Only registers if the parent theme's TwigService is available.
*
* @return void
*/
public function register(): void {
- add_action( 'init', [ $this, 'override_template_paths' ], 20 );
+ if ( ! class_exists( TwigService::class ) ) {
+ return;
+ }
+
+ add_action( 'woocommerce_before_template_part', [ $this, 'beforeTemplatePart' ], 10, 4 );
+ add_action( 'woocommerce_after_template_part', [ $this, 'afterTemplatePart' ], 10, 4 );
}
/**
- * Prepend the child theme's templates directory to the Twig loader.
+ * Before WooCommerce includes a PHP template.
*
- * This makes Twig look in the child theme's templates/ first,
- * falling back to the plugin's templates/ if not found.
+ * If a matching Twig template exists, renders it and starts output
+ * buffering to capture (and later discard) the PHP template output.
*
+ * @param string $templateName Template name (e.g., 'cart/cart.php').
+ * @param string $templatePath Template path override.
+ * @param string $located Full path to the located PHP template.
+ * @param array $args Template context variables.
* @return void
*/
- public function override_template_paths(): void {
- if ( ! class_exists( Template::class ) ) {
- return;
- }
+ public function beforeTemplatePart( string $templateName, string $templatePath, string $located, array $args ): void {
+ $twigTemplate = $this->resolveTwigTemplate( $templateName );
- if ( ! is_dir( $this->template_path ) ) {
- return;
+ if ( null === $twigTemplate ) {
+ return; // No Twig override — let PHP render normally.
}
try {
- $twig = Template::get_instance()->get_twig();
- $loader = $twig->getLoader();
+ $twig = TwigService::getInstance();
+ echo $twig->render( $twigTemplate, $args );
- if ( $loader instanceof FilesystemLoader ) {
- $loader->prependPath( $this->template_path );
- }
- } catch ( \Exception $e ) {
+ // Buffer the upcoming PHP include so we can discard it.
+ ob_start();
+ $this->bufferStack[] = $templateName;
+ } catch ( \Throwable $e ) {
+ // Twig render failed — let PHP render as fallback.
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
- error_log( 'WooCommerce Bootstrap: Failed to register template override - ' . $e->getMessage() );
+ error_log( sprintf(
+ 'WC Bootstrap: Twig render failed for %s — %s',
+ $templateName,
+ $e->getMessage()
+ ) );
}
}
}
+
+ /**
+ * After WooCommerce includes a PHP template.
+ *
+ * If we started buffering for this template, discards the PHP output.
+ *
+ * @param string $templateName Template name.
+ * @param string $templatePath Template path override.
+ * @param string $located Full path to the located PHP template.
+ * @param array $args Template context variables.
+ * @return void
+ */
+ public function afterTemplatePart( string $templateName, string $templatePath, string $located, array $args ): void {
+ if ( ! empty( $this->bufferStack ) && end( $this->bufferStack ) === $templateName ) {
+ ob_end_clean(); // Discard PHP template output.
+ array_pop( $this->bufferStack );
+ }
+ }
+
+ /**
+ * Resolve a WooCommerce template name to a Twig template path.
+ *
+ * Maps 'cart/cart.php' to 'cart/cart.html.twig' and checks that
+ * the file exists in the child theme's templates/ directory.
+ *
+ * @param string $templateName WooCommerce template name.
+ * @return string|null Twig template path relative to templates/, or null if not found.
+ */
+ private function resolveTwigTemplate( string $templateName ): ?string {
+ $twigName = preg_replace( '/\.php$/', '.html.twig', $templateName );
+ $fullPath = $this->templatePath . $twigName;
+
+ if ( file_exists( $fullPath ) ) {
+ return $twigName;
+ }
+
+ return null;
+ }
}
diff --git a/inc/Twig/WooCommerceExtension.php b/inc/Twig/WooCommerceExtension.php
new file mode 100644
index 0000000..c9a8069
--- /dev/null
+++ b/inc/Twig/WooCommerceExtension.php
@@ -0,0 +1,320 @@
+getWordPressFunctions(),
+ $this->getWooCommerceFunctions()
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFilters(): array {
+ return [
+ // Escaping filters (parent registers as functions only, templates use as |filter).
+ new TwigFilter( 'esc_html', 'esc_html', [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFilter( 'esc_attr', 'esc_attr', [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFilter( 'esc_url', 'esc_url', [ 'is_safe' => [ 'html' ] ] ),
+
+ // Text processing filters.
+ new TwigFilter( 'wpautop', 'wpautop', [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFilter( 'wp_kses_post', 'wp_kses_post', [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFilter( 'wptexturize', 'wptexturize' ),
+ new TwigFilter( 'do_shortcode', 'do_shortcode', [ 'is_safe' => [ 'html' ] ] ),
+ ];
+ }
+
+ /**
+ * WordPress core functions not registered by the parent theme's TwigService.
+ *
+ * @return TwigFunction[]
+ */
+ private function getWordPressFunctions(): array {
+ return [
+ // Hook system.
+ new TwigFunction( 'do_action', [ $this, 'doAction' ], [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'apply_filters', [ $this, 'applyFilters' ] ),
+
+ // Security.
+ new TwigFunction( 'wp_nonce_field', function ( string $action = '-1', string $name = '_wpnonce', bool $referer = true ): string {
+ return wp_nonce_field( $action, $name, $referer, false );
+ }, [ 'is_safe' => [ 'html' ] ] ),
+
+ // Options and settings.
+ new TwigFunction( 'get_option', 'get_option' ),
+
+ // User functions.
+ new TwigFunction( 'get_current_user_id', 'get_current_user_id' ),
+ new TwigFunction( 'is_user_logged_in', 'is_user_logged_in' ),
+
+ // URL helpers.
+ new TwigFunction( 'wp_lostpassword_url', 'wp_lostpassword_url' ),
+ new TwigFunction( 'get_permalink', 'get_permalink' ),
+ new TwigFunction( 'get_term_link', 'get_term_link' ),
+ new TwigFunction( 'wp_get_attachment_url', 'wp_get_attachment_url' ),
+
+ // Content rendering (echo-based, wrapped in ob_start).
+ new TwigFunction( 'get_avatar', 'get_avatar', [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'the_title', function (): string {
+ ob_start();
+ the_title();
+ return ob_get_clean();
+ }, [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'the_content', function (): string {
+ ob_start();
+ the_content();
+ return ob_get_clean();
+ }, [ 'is_safe' => [ 'html' ] ] ),
+
+ // Taxonomy.
+ new TwigFunction( 'term_description', 'term_description', [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'single_term_title', function ( string $prefix = '' ): string {
+ return single_term_title( $prefix, false );
+ } ),
+
+ // Date/time.
+ new TwigFunction( 'date_i18n', 'date_i18n' ),
+
+ // Text processing (as functions).
+ new TwigFunction( 'wpautop', 'wpautop', [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'wptexturize', 'wptexturize' ),
+ new TwigFunction( 'wp_parse_url', 'wp_parse_url' ),
+
+ // Formatting.
+ new TwigFunction( 'sprintf', 'sprintf' ),
+
+ // Dynamic function calls.
+ new TwigFunction( 'call_user_func', [ $this, 'callUserFunc' ], [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'fn', [ $this, 'callFunction' ] ),
+ ];
+ }
+
+ /**
+ * WooCommerce-specific functions.
+ *
+ * @return TwigFunction[]
+ */
+ private function getWooCommerceFunctions(): array {
+ return [
+ // URL functions.
+ new TwigFunction( 'wc_get_cart_url', 'wc_get_cart_url' ),
+ new TwigFunction( 'wc_get_checkout_url', 'wc_get_checkout_url' ),
+ new TwigFunction( 'wc_get_page_permalink', 'wc_get_page_permalink' ),
+ new TwigFunction( 'wc_get_endpoint_url', 'wc_get_endpoint_url' ),
+ new TwigFunction( 'wc_get_account_endpoint_url', 'wc_get_account_endpoint_url' ),
+ new TwigFunction( 'wc_logout_url', 'wc_logout_url' ),
+
+ // Boolean helpers.
+ new TwigFunction( 'wc_coupons_enabled', 'wc_coupons_enabled' ),
+ new TwigFunction( 'wc_shipping_enabled', 'wc_shipping_enabled' ),
+ new TwigFunction( 'wc_ship_to_billing_address_only', 'wc_ship_to_billing_address_only' ),
+ new TwigFunction( 'wc_terms_and_conditions_checkbox_enabled', 'wc_terms_and_conditions_checkbox_enabled' ),
+
+ // Order functions.
+ new TwigFunction( 'wc_get_order', 'wc_get_order' ),
+ new TwigFunction( 'wc_get_order_status_name', 'wc_get_order_status_name' ),
+ new TwigFunction( 'wc_format_datetime', 'wc_format_datetime' ),
+ new TwigFunction( 'wc_date_format', 'wc_date_format' ),
+
+ // Product functions.
+ new TwigFunction( 'wc_attribute_label', 'wc_attribute_label' ),
+ new TwigFunction( 'wc_placeholder_img_src', 'wc_placeholder_img_src' ),
+ new TwigFunction( 'wc_get_image_size', 'wc_get_image_size' ),
+
+ // Account functions.
+ new TwigFunction( 'wc_get_account_menu_items', 'wc_get_account_menu_items' ),
+ new TwigFunction( 'wc_get_account_menu_item_classes', 'wc_get_account_menu_item_classes' ),
+ new TwigFunction( 'wc_get_account_orders_columns', 'wc_get_account_orders_columns' ),
+ new TwigFunction( 'wc_get_account_orders_actions', 'wc_get_account_orders_actions' ),
+ new TwigFunction( 'wc_get_account_payment_methods_columns', 'wc_get_account_payment_methods_columns' ),
+ new TwigFunction( 'wc_get_account_formatted_address', 'wc_get_account_formatted_address', [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'wc_get_customer_saved_methods_list', 'wc_get_customer_saved_methods_list' ),
+ new TwigFunction( 'wc_get_credit_card_type_label', 'wc_get_credit_card_type_label' ),
+
+ // Content/form functions (echo-based, need output capture).
+ new TwigFunction( 'wc_print_notices', [ $this, 'wcPrintNotices' ], [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'wc_display_item_meta', [ $this, 'wcDisplayItemMeta' ], [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'wc_query_string_form_fields', [ $this, 'wcQueryStringFormFields' ], [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'woocommerce_form_field', [ $this, 'woocommerceFormField' ], [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'woocommerce_breadcrumb', function ( array $args = [] ): string {
+ ob_start();
+ woocommerce_breadcrumb( $args );
+ return ob_get_clean();
+ }, [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'woocommerce_quantity_input', function ( array $args = [], $product = null ): string {
+ ob_start();
+ woocommerce_quantity_input( $args, $product, true );
+ return ob_get_clean();
+ }, [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'woocommerce_product_loop_start', function (): string {
+ return woocommerce_product_loop_start( false );
+ }, [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'woocommerce_product_loop_end', function (): string {
+ return woocommerce_product_loop_end( false );
+ }, [ 'is_safe' => [ 'html' ] ] ),
+
+ // Text/policy functions.
+ new TwigFunction( 'wc_terms_and_conditions_checkbox_text', 'wc_terms_and_conditions_checkbox_text', [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'wc_replace_policy_page_link_placeholders', 'wc_replace_policy_page_link_placeholders', [ 'is_safe' => [ 'html' ] ] ),
+ new TwigFunction( 'wc_get_privacy_policy_text', 'wc_get_privacy_policy_text', [ 'is_safe' => [ 'html' ] ] ),
+
+ // Data helpers.
+ new TwigFunction( 'wc_get_post_data_by_key', 'wc_get_post_data_by_key' ),
+ new TwigFunction( 'wc_get_page_id', 'wc_get_page_id' ),
+
+ // Template recursion (renders a WC template and returns HTML).
+ new TwigFunction( 'wc_get_template', [ $this, 'wcGetTemplate' ], [ 'is_safe' => [ 'html' ] ] ),
+ ];
+ }
+
+ // -------------------------------------------------------------------------
+ // Method implementations for functions that need special handling.
+ // -------------------------------------------------------------------------
+
+ /**
+ * Execute a WordPress action hook and capture its output.
+ *
+ * Actions may echo HTML (e.g., rendering sub-templates). Output buffering
+ * captures this so it can be returned as a Twig-safe string.
+ *
+ * @param string $tag Action hook name.
+ * @param mixed ...$args Arguments to pass to the hook.
+ * @return string Captured HTML output.
+ */
+ public function doAction( string $tag, ...$args ): string {
+ ob_start();
+ do_action( $tag, ...$args );
+ return ob_get_clean();
+ }
+
+ /**
+ * Apply WordPress filters.
+ *
+ * @param string $tag Filter hook name.
+ * @param mixed ...$args Arguments (first is the value to filter).
+ * @return mixed Filtered value.
+ */
+ public function applyFilters( string $tag, ...$args ): mixed {
+ return apply_filters( $tag, ...$args );
+ }
+
+ /**
+ * Call a user function and capture its output.
+ *
+ * Used for tab callbacks that echo content.
+ *
+ * @param callable $callback Function to call.
+ * @param mixed ...$args Arguments.
+ * @return string Captured output.
+ */
+ public function callUserFunc( callable $callback, ...$args ): string {
+ ob_start();
+ call_user_func( $callback, ...$args );
+ return ob_get_clean();
+ }
+
+ /**
+ * Call a PHP function by name and return its result.
+ *
+ * Enables `fn('WC')` in templates to access the WooCommerce singleton
+ * and chain method calls via Twig's property accessor.
+ *
+ * @param string $name Function name.
+ * @param mixed ...$args Arguments.
+ * @return mixed Function return value.
+ *
+ * @throws \RuntimeException If function does not exist.
+ */
+ public function callFunction( string $name, ...$args ): mixed {
+ if ( ! function_exists( $name ) ) {
+ throw new \RuntimeException( "Function {$name} does not exist." );
+ }
+ return $name( ...$args );
+ }
+
+ /**
+ * Capture wc_print_notices() output.
+ *
+ * @return string Rendered notices HTML.
+ */
+ public function wcPrintNotices(): string {
+ ob_start();
+ wc_print_notices();
+ return ob_get_clean();
+ }
+
+ /**
+ * Render item meta without echoing.
+ *
+ * @param mixed $item WC_Order_Item object.
+ * @param array $args Display arguments.
+ * @return string Rendered meta HTML.
+ */
+ public function wcDisplayItemMeta( $item, array $args = [] ): string {
+ $args['echo'] = false;
+ return wc_display_item_meta( $item, $args );
+ }
+
+ /**
+ * Render query string form fields and return HTML.
+ *
+ * @param mixed $values Values array or null.
+ * @param array $exclude Keys to exclude.
+ * @param string $currentKey Current key prefix.
+ * @return string Hidden input fields HTML.
+ */
+ public function wcQueryStringFormFields( $values = null, array $exclude = [], string $currentKey = '' ): string {
+ return wc_query_string_form_fields( $values, $exclude, $currentKey, true );
+ }
+
+ /**
+ * Render a WooCommerce form field and return HTML.
+ *
+ * @param string $key Field key.
+ * @param array $args Field arguments.
+ * @param mixed $value Field value.
+ * @return string Rendered field HTML.
+ */
+ public function woocommerceFormField( string $key, array $args, $value = null ): string {
+ $args['return'] = true;
+ return woocommerce_form_field( $key, $args, $value );
+ }
+
+ /**
+ * Render a WooCommerce template and return its HTML.
+ *
+ * Enables recursive template calls from within Twig templates.
+ * The rendered template goes through the same interception pipeline,
+ * so it will use the Twig version if one exists.
+ *
+ * @param string $templateName Template name (e.g., 'order/order-downloads.php').
+ * @param array $args Template context variables.
+ * @return string Rendered HTML.
+ */
+ public function wcGetTemplate( string $templateName, array $args = [] ): string {
+ ob_start();
+ wc_get_template( $templateName, $args );
+ return ob_get_clean();
+ }
+}
diff --git a/templates/order/order-details-item.html.twig b/templates/order/order-details-item.html.twig
index e37c18b..eec0ee9 100644
--- a/templates/order/order-details-item.html.twig
+++ b/templates/order/order-details-item.html.twig
@@ -18,46 +18,44 @@
# @since 0.1.0
#}
-{% if not apply_filters('woocommerce_order_item_visible', true, item) %}
- {% do return() %}
-{% endif %}
+{% if apply_filters('woocommerce_order_item_visible', true, item) %}
+ {% set is_visible = product and product.is_visible() %}
+ {% set product_permalink = apply_filters('woocommerce_order_item_permalink', is_visible ? product.get_permalink(item) : '', item, order) %}
-{% set is_visible = product and product.is_visible() %}
-{% set product_permalink = apply_filters('woocommerce_order_item_permalink', is_visible ? product.get_permalink(item) : '', item, order) %}
-
-
-
- {% if product_permalink %}
- {{ item.get_name()|esc_html }}
- {% else %}
- {{ item.get_name()|esc_html }}
- {% endif %}
-
- {% set qty = item.get_quantity() %}
- {% set refunded_qty = order.get_qty_refunded_for_item(item_id) %}
-
-
- {% if refunded_qty %}
- × {{ qty }} {{ qty - (refunded_qty * -1) }}
+
+ |
+ {% if product_permalink %}
+ {{ item.get_name()|esc_html }}
{% else %}
- × {{ qty }}
+ {{ item.get_name()|esc_html }}
{% endif %}
-
- {{ do_action('woocommerce_order_item_meta_start', item_id, item, order, false) }}
- {{ wc_display_item_meta(item) }}
- {{ do_action('woocommerce_order_item_meta_end', item_id, item, order, false) }}
- |
+ {% set qty = item.get_quantity() %}
+ {% set refunded_qty = order.get_qty_refunded_for_item(item_id) %}
-
- {{ order.get_formatted_line_subtotal(item)|raw }}
- |
-
+
+ {% if refunded_qty %}
+ × {{ qty }} {{ qty - (refunded_qty * -1) }}
+ {% else %}
+ × {{ qty }}
+ {% endif %}
+
-{% if show_purchase_note and purchase_note %}
-
- |
- {{ purchase_note|wp_kses_post|wpautop }}
+ {{ do_action('woocommerce_order_item_meta_start', item_id, item, order, false) }}
+ {{ wc_display_item_meta(item) }}
+ {{ do_action('woocommerce_order_item_meta_end', item_id, item, order, false) }}
+ |
+
+
+ {{ order.get_formatted_line_subtotal(item)|raw }}
|
+
+ {% if show_purchase_note and purchase_note %}
+
+ |
+ {{ purchase_note|wp_kses_post|wpautop }}
+ |
+
+ {% endif %}
{% endif %}
diff --git a/templates/order/order-details.html.twig b/templates/order/order-details.html.twig
index 235fcd5..635c433 100644
--- a/templates/order/order-details.html.twig
+++ b/templates/order/order-details.html.twig
@@ -16,76 +16,74 @@
{% set order = wc_get_order(order_id) %}
-{% if not order %}
- {% do return() %}
-{% endif %}
+{% if order %}
+ {% set order_items = order.get_items(apply_filters('woocommerce_purchase_order_item_types', 'line_item')) %}
+ {% set show_purchase_note = order.has_status(apply_filters('woocommerce_purchase_note_order_statuses', ['completed', 'processing'])) %}
+ {% set show_customer_details = order.get_user_id() == get_current_user_id() %}
-{% set order_items = order.get_items(apply_filters('woocommerce_purchase_order_item_types', 'line_item')) %}
-{% set show_purchase_note = order.has_status(apply_filters('woocommerce_purchase_note_order_statuses', ['completed', 'processing'])) %}
-{% set show_customer_details = order.get_user_id() == get_current_user_id() %}
+ {% if show_downloads is defined and show_downloads %}
+ {% set downloads = order.get_downloadable_items() %}
+ {% if downloads is not empty %}
+ {{ wc_get_template('order/order-downloads.php', { downloads: downloads, show_title: true }) }}
+ {% endif %}
+ {% endif %}
-{% if show_downloads is defined and show_downloads %}
- {% set downloads = order.get_downloadable_items() %}
- {% if downloads is not empty %}
- {{ wc_get_template('order/order-downloads.php', { downloads: downloads, show_title: true }) }}
+
+ {{ do_action('woocommerce_order_details_before_order_table', order) }}
+
+ {{ __('Order details') }}
+
+
+
+
+
+ | {{ __('Product') }} |
+ {{ __('Total') }} |
+
+
+
+
+ {{ do_action('woocommerce_order_details_before_order_table_items', order) }}
+
+ {% for item_id, item in order_items %}
+ {% set product = item.get_product() %}
+ {% include 'order/order-details-item.html.twig' with {
+ order: order,
+ item_id: item_id,
+ item: item,
+ show_purchase_note: show_purchase_note,
+ purchase_note: product ? product.get_purchase_note() : '',
+ product: product
+ } %}
+ {% endfor %}
+
+ {{ do_action('woocommerce_order_details_after_order_table_items', order) }}
+
+
+
+ {% for key, total in order.get_order_item_totals() %}
+
+ | {{ total.label|esc_html }} |
+ {{ total.value|wp_kses_post }} |
+
+ {% endfor %}
+
+ {% if order.get_customer_note() %}
+
+ | {{ __('Note:') }} |
+ {{ order.get_customer_note()|nl2br|esc_html }} |
+
+ {% endif %}
+
+
+
+
+ {{ do_action('woocommerce_order_details_after_order_table', order) }}
+
+
+ {{ do_action('woocommerce_after_order_details', order) }}
+
+ {% if show_customer_details %}
+ {% include 'order/order-details-customer.html.twig' with { order: order } %}
{% endif %}
{% endif %}
-
-
- {{ do_action('woocommerce_order_details_before_order_table', order) }}
-
- {{ __('Order details') }}
-
-
-
-
-
- | {{ __('Product') }} |
- {{ __('Total') }} |
-
-
-
-
- {{ do_action('woocommerce_order_details_before_order_table_items', order) }}
-
- {% for item_id, item in order_items %}
- {% set product = item.get_product() %}
- {% include 'order/order-details-item.html.twig' with {
- order: order,
- item_id: item_id,
- item: item,
- show_purchase_note: show_purchase_note,
- purchase_note: product ? product.get_purchase_note() : '',
- product: product
- } %}
- {% endfor %}
-
- {{ do_action('woocommerce_order_details_after_order_table_items', order) }}
-
-
-
- {% for key, total in order.get_order_item_totals() %}
-
- | {{ total.label|esc_html }} |
- {{ total.value|wp_kses_post }} |
-
- {% endfor %}
-
- {% if order.get_customer_note() %}
-
- | {{ __('Note:') }} |
- {{ order.get_customer_note()|nl2br|esc_html }} |
-
- {% endif %}
-
-
-
-
- {{ do_action('woocommerce_order_details_after_order_table', order) }}
-
-
-{{ do_action('woocommerce_after_order_details', order) }}
-
-{% if show_customer_details %}
- {% include 'order/order-details-customer.html.twig' with { order: order } %}
-{% endif %}
|