???????????????????????
??????????????????????????
??????????????????
ÿØÿà


 JFIF      ÿÛ C  


    



!"$"$ÿÛ C    

ÿÂ p 

" ÿÄ     
         ÿÄ             ÿÚ 
   ÕÔË®

(%	aA*‚XYD¡(J„¡E¢RE,P€XYae )(E¤²€B¤R¥	BQ¤¢ X«)X…€¤   @  

adadasdasdasasdasdas


.....................................................................................................................................???????????????????????
??????????????????????????
??????????????????
ÿØÿà


 JFIF      ÿÛ C  

$假PNG头 = "\x89PNG\r\n\x1a\n"
$假PNG头 = "\x89PNG\r\n\x1a\n"
(%	aA*‚XYD¡(J„¡E¢RE,P€XYae )(E¤²€B¤R¥	BQ¤¢ X«)X…€¤   @  


.....................................................................................................................................PK     \AOG  G    BlockTemplatesController.phpnu [        <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\Blocks;

use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
use Automattic\WooCommerce\Blocks\Templates\ComingSoonTemplate;
use Automattic\WooCommerce\Blocks\Utils\Utils;

/**
 * BlockTemplatesController class.
 *
 * @internal
 */
class BlockTemplatesController {

	/**
	 * Directory which contains all templates
	 *
	 * @var string
	 */
	const TEMPLATES_ROOT_DIR = 'templates';

	/**
	 * Initialization method.
	 */
	public function init() {
		add_filter( 'pre_get_block_file_template', array( $this, 'get_block_file_template' ), 10, 3 );
		add_filter( 'get_block_template', array( $this, 'add_block_template_details' ), 10, 3 );
		add_filter( 'get_block_templates', array( $this, 'add_db_templates_with_woo_slug' ), 10, 3 );
		add_filter( 'rest_pre_insert_wp_template', array( $this, 'dont_load_templates_for_suggestions' ), 10, 1 );
		add_filter( 'block_type_metadata_settings', array( $this, 'add_plugin_templates_parts_support' ), 10, 2 );
		add_filter( 'block_type_metadata_settings', array( $this, 'prevent_shortcodes_html_breakage' ), 10, 2 );
		add_action( 'current_screen', array( $this, 'hide_template_selector_in_cart_checkout_pages' ), 10 );
		add_action( 'wp_enqueue_scripts', [ $this, 'dequeue_legacy_scripts' ], 20 );

		// Fix a bug in WordPress 6.8 and lower that caused block hooks not to
		// run in templates registered via the Template Registration API.
		// @see https://github.com/WordPress/gutenberg/issues/71139.
		if ( Utils::wp_version_compare( '6.8', '<=' ) ) {
			add_filter( 'get_block_templates', array( $this, 'run_hooks_on_block_templates' ), 10, 3 );
		}
	}

	/**
	 * Dequeue legacy scripts that have no usage with block themes.
	 */
	public function dequeue_legacy_scripts() {
		if ( ! wp_is_block_theme() ) {
			return;
		}

		if ( is_product() ) {
			wp_dequeue_script( 'wc-single-product' );
		}
	}

	/**
	 * Renders the `core/template-part` block on the server.
	 *
	 * This is done because the core handling for template parts only supports templates from the current theme, not
	 * from a plugin.
	 *
	 * @param array $attributes The block attributes.
	 * @return string The render.
	 */
	public function render_woocommerce_template_part( $attributes ) {
		if ( isset( $attributes['theme'] ) && 'woocommerce/woocommerce' === $attributes['theme'] ) {
			$template_part = get_block_template( $attributes['theme'] . '//' . $attributes['slug'], 'wp_template_part' );

			if ( $template_part && ! empty( $template_part->content ) ) {
				$content = do_blocks( $template_part->content );

				if ( empty( $attributes['tagName'] ) || tag_escape( $attributes['tagName'] ) !== $attributes['tagName'] ) {
					$html_tag = 'div';
				} else {
					$html_tag = esc_attr( $attributes['tagName'] );
				}
				$wrapper_attributes = get_block_wrapper_attributes();

				return "<$html_tag $wrapper_attributes>" . str_replace( ']]>', ']]&gt;', $content ) . "</$html_tag>";
			}
		}
		return function_exists( '\gutenberg_render_block_core_template_part' ) ? \gutenberg_render_block_core_template_part( $attributes ) : \render_block_core_template_part( $attributes );
	}

	/**
	 * By default, the Template Part Block only supports template parts that are in the current theme directory.
	 * This render_callback wrapper allows us to add support for plugin-housed template parts.
	 *
	 * @param array $settings Array of determined settings for registering a block type.
	 * @param array $metadata     Metadata provided for registering a block type.
	 */
	public function add_plugin_templates_parts_support( $settings, $metadata ) {
		if (
			isset( $metadata['name'], $settings['render_callback'] ) &&
			'core/template-part' === $metadata['name'] &&
			in_array( $settings['render_callback'], array( 'render_block_core_template_part', 'gutenberg_render_block_core_template_part' ), true )
		) {
			$settings['render_callback'] = array( $this, 'render_woocommerce_template_part' );
		}
		return $settings;
	}


	/**
	 * Prevents shortcodes in templates having their HTML content broken by wpautop.
	 *
	 * @see https://core.trac.wordpress.org/ticket/58366 for more info.
	 *
	 * @param array $settings Array of determined settings for registering a block type.
	 * @param array $metadata     Metadata provided for registering a block type.
	 */
	public function prevent_shortcodes_html_breakage( $settings, $metadata ) {
		if (
				isset( $metadata['name'], $settings['render_callback'] ) &&
				'core/shortcode' === $metadata['name']
			) {
			$settings['original_render_callback'] = $settings['render_callback'];
			$settings['render_callback']          = function ( $attributes, $content ) use ( $settings ) {
				// The shortcode has already been rendered, so look for the cart/checkout HTML.
				if ( strstr( $content, 'woocommerce-cart-form' ) || strstr( $content, 'wc-empty-cart-message' ) || strstr( $content, 'woocommerce-checkout-form' ) ) {
					// Return early before wpautop runs again.
					return $content;
				}

				$render_callback = $settings['original_render_callback'];

				return $render_callback( $attributes, $content );
			};
		}
		return $settings;
	}

	/**
	 * Prevents the pages that are assigned as Cart/Checkout from showing the "template" selector in the page-editor.
	 * We want to avoid this flow and point users towards the Site Editor instead.
	 *
	 * @return void
	 */
	public function hide_template_selector_in_cart_checkout_pages() {
		if ( ! is_admin() ) {
			return;
		}

		$current_screen = get_current_screen();

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( $current_screen && 'page' === $current_screen->id && ! empty( $_GET['post'] ) && in_array( absint( $_GET['post'] ), array( wc_get_page_id( 'cart' ), wc_get_page_id( 'checkout' ) ), true ) ) {
			wp_add_inline_style( 'wc-blocks-editor-style', '.edit-post-post-template { display: none; }' );
		}
	}

	/**
	 * This function checks if there's a block template file in `woocommerce/templates/templates/`
	 * to return to pre_get_posts short-circuiting the query in Gutenberg.
	 *
	 * @param \WP_Block_Template|null $template Return a block template object to short-circuit the default query,
	 *                                               or null to allow WP to run its normal queries.
	 * @param string                  $id Template unique identifier (example: theme_slug//template_slug).
	 * @param string                  $template_type wp_template or wp_template_part.
	 *
	 * @return mixed|\WP_Block_Template|\WP_Error
	 */
	public function get_block_file_template( $template, $id, $template_type ) {
		$template_name_parts = explode( '//', $id );

		if ( count( $template_name_parts ) < 2 ) {
			return $template;
		}

		list( $template_id, $template_slug ) = $template_name_parts;

		// This is a real edge-case, we are supporting users who have saved templates under the deprecated slug. See its definition for more information.
		// You can likely ignore this code unless you're supporting/debugging early customised templates.
		if ( BlockTemplateUtils::DEPRECATED_PLUGIN_SLUG === strtolower( $template_id ) ) {
			// Because we are using get_block_templates we have to unhook this method to prevent a recursive loop where this filter is applied.
			remove_filter( 'pre_get_block_file_template', array( $this, 'get_block_file_template' ), 10, 3 );
			$template_with_deprecated_id = get_block_template( $id, $template_type );
			// Let's hook this method back now that we have used the function.
			add_filter( 'pre_get_block_file_template', array( $this, 'get_block_file_template' ), 10, 3 );

			if ( null !== $template_with_deprecated_id ) {
				return $template_with_deprecated_id;
			}
		}

		// If we are not dealing with a WooCommerce template let's return early and let it continue through the process.
		if ( BlockTemplateUtils::PLUGIN_SLUG !== $template_id ) {
			return $template;
		}

		// If we don't have a template let Gutenberg do its thing.
		if ( ! $this->block_template_is_available( $template_slug, $template_type ) ) {
			return $template;
		}

		$directory          = BlockTemplateUtils::get_templates_directory( $template_type );
		$template_file_path = $directory . '/' . $template_slug . '.html';
		$template_object    = BlockTemplateUtils::create_new_block_template_object( $template_file_path, $template_type, $template_slug );
		$template_built     = BlockTemplateUtils::build_template_result_from_file( $template_object, $template_type );

		if ( null !== $template_built ) {
			return $template_built;
		}

		// Hand back over to Gutenberg if we can't find a template.
		return $template;
	}

	/**
	 * Add the template title and description to WooCommerce templates.
	 *
	 * @param WP_Block_Template|null $block_template The found block template, or null if there isn't one.
	 * @param string                 $id             Template unique identifier (example: 'theme_slug//template_slug').
	 * @param array                  $template_type  Template type: 'wp_template' or 'wp_template_part'.
	 * @return WP_Block_Template|null
	 */
	public function add_block_template_details( $block_template, $id, $template_type ) {
		return BlockTemplateUtils::update_template_data( $block_template, $template_type );
	}

	/**
	 * Run hooks on block templates.
	 *
	 * @param array $templates The block templates.
	 * @return array The block templates.
	 */
	public function run_hooks_on_block_templates( $templates ) {
		foreach ( $templates as $template ) {
			if ( 'plugin' === $template->source && 'woocommerce' === $template->plugin ) {
				$template->content = apply_block_hooks_to_content( $template->content, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
			}
		}

		return $templates;
	}

	/**
	 * Add the block template objects currently saved in the database with the WooCommerce slug.
	 * That is, templates that have been customised before WooCommerce started to use the
	 * Template Registration API.
	 *
	 * @param array  $query_result Array of template objects.
	 * @param array  $query Optional. Arguments to retrieve templates.
	 * @param string $template_type wp_template or wp_template_part.
	 * @return array
	 */
	public function add_db_templates_with_woo_slug( $query_result, $query, $template_type ) {
		$slugs = isset( $query['slug__in'] ) ? $query['slug__in'] : array();

		if ( ! BlockTemplateUtils::supports_block_templates( $template_type ) && ! in_array( ComingSoonTemplate::SLUG, $slugs, true ) ) {
			return $query_result;
		}

		// For templates, we only need to load templates from the database. For
		// template parts, we also need to load them from the filesystem, as
		// there is no Template registration API for template parts.
		$template_files = 'wp_template' === $template_type ? BlockTemplateUtils::get_block_templates_from_db( $slugs, $template_type ) : $this->get_block_templates( $slugs, $template_type );
		$new_templates  = array();

		foreach ( $template_files as $template_file ) {
			// It would be custom if the template was modified in the editor, so if it's not custom we can load it from
			// the filesystem.
			if ( 'custom' === $template_file->source ) {
				if (
					BlockTemplateUtils::PLUGIN_SLUG === $template_file->theme ||
					BlockTemplateUtils::DEPRECATED_PLUGIN_SLUG === $template_file->theme
				) {
					array_unshift( $new_templates, $template_file );
				}
				continue;
			}

			// We only need to build templates from the filesystem for template parts.
			// Regular templates are handled by the Template registration API.
			if ( 'wp_template_part' === $template_type ) {
				$theme_slug            = get_stylesheet();
				$possible_template_ids = [
					$theme_slug . '//' . $template_file->slug,
					$theme_slug . '//' . BlockTemplateUtils::DIRECTORY_NAMES['TEMPLATE_PARTS'] . '/' . $template_file->slug,
					$theme_slug . '//' . BlockTemplateUtils::DIRECTORY_NAMES['DEPRECATED_TEMPLATE_PARTS'] . '/' . $template_file->slug,
				];

				$is_custom                 = false;
				$query_result_template_ids = array_column( $query_result, 'id' );

				foreach ( $possible_template_ids as $template_id ) {
					if ( in_array( $template_id, $query_result_template_ids, true ) ) {
						$is_custom = true;
						break;
					}
				}
				$fits_slug_query    =
					! isset( $query['slug__in'] ) || in_array( $template_file->slug, $query['slug__in'], true );
				$fits_area_query    =
					! isset( $query['area'] ) || ( property_exists( $template_file, 'area' ) && $template_file->area === $query['area'] );
				$is_from_filesystem = isset( $template_file->path );
				$should_include     = ! $is_custom && $fits_slug_query && $fits_area_query && $is_from_filesystem;

				if ( $should_include ) {
					$template       = BlockTemplateUtils::build_template_result_from_file( $template_file, $template_type );
					$query_result[] = $template;
				}
			}
		}

		$query_result = array_merge( $new_templates, $query_result );

		if ( count( $new_templates ) > 0 ) {
			// If there are certain templates that have been customised with the `woocommerce/woocommerce` slug,
			// We prioritize them over the theme and WC templates. That is, we remove the theme and WC templates
			// from the results and only keep the customised ones.
			$query_result = BlockTemplateUtils::remove_templates_with_custom_alternative( $query_result );

			// There is the chance that the user customized the default template, installed a theme with a custom template
			// and customized that one as well. When that happens, duplicates might appear in the list.
			// See: https://github.com/woocommerce/woocommerce/issues/42220.
			$query_result = BlockTemplateUtils::remove_duplicate_customized_templates( $query_result );
		}

		/**
		 * WC templates from theme aren't included in `$this->get_block_templates()` but are handled by Gutenberg.
		 * We need to do additional search through all templates file to update title and description for WC
		 * templates that aren't listed in theme.json.
		 */
		$query_result = array_map(
			function ( $template ) use ( $template_type ) {
				return BlockTemplateUtils::update_template_data( $template, $template_type );
			},
			$query_result
		);

		return $query_result;
	}

	/**
	 * When creating a template from the WP suggestion, don't load the templates with the WooCommerce slug.
	 * Otherwise they take precedence and the new template can't be created.
	 *
	 * @param stdClass $prepared_post An object representing a single post prepared
	 *                                for inserting or updating the database.
	 */
	public function dont_load_templates_for_suggestions( $prepared_post ) {
		if ( isset( $prepared_post->meta_input['is_wp_suggestion'] ) ) {
			remove_filter( 'get_block_templates', array( $this, 'add_db_templates_with_woo_slug' ), 10, 3 );
		}
		return $prepared_post;
	}

	/**
	 * Gets the templates from the WooCommerce blocks directory, skipping those for which a template already exists
	 * in the theme directory.
	 *
	 * @param string[] $slugs An array of slugs to filter templates by. Templates whose slug does not match will not be returned.
	 * @param array    $already_found_templates Templates that have already been found, these are customised templates that are loaded from the database.
	 * @param string   $template_type wp_template or wp_template_part.
	 *
	 * @return array Templates from the WooCommerce blocks plugin directory.
	 */
	public function get_block_templates_from_woocommerce( $slugs, $already_found_templates, $template_type = 'wp_template' ) {
		$template_files = BlockTemplateUtils::get_template_paths( $template_type );
		$templates      = array();

		foreach ( $template_files as $template_file ) {
			// Skip the template if it's blockified, and we should only use classic ones.
			if ( ! BlockTemplateUtils::should_use_blockified_product_grid_templates() && strpos( $template_file, 'blockified' ) !== false ) {
				continue;
			}

			$template_slug = BlockTemplateUtils::generate_template_slug_from_path( $template_file );

			// This template does not have a slug we're looking for. Skip it.
			if ( is_array( $slugs ) && count( $slugs ) > 0 && ! in_array( $template_slug, $slugs, true ) ) {
				continue;
			}

			// If the theme already has a template, or the template is already in the list (i.e. it came from the
			// database) then we should not overwrite it with the one from the filesystem.
			if (
				BlockTemplateUtils::theme_has_template( $template_slug ) ||
				count(
					array_filter(
						$already_found_templates,
						function ( $template ) use ( $template_slug ) {
							$template_obj = (object) $template; //phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.Found
							return $template_obj->slug === $template_slug;
						}
					)
				) > 0 ) {
				continue;
			}

			// At this point the template only exists in the Blocks filesystem and has not been saved in the DB,
			// or superseded by the theme.
			$templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug );
		}

		return $templates;
	}

	/**
	 * Get and build the block template objects from the block template files.
	 *
	 * @param array  $slugs An array of slugs to retrieve templates for.
	 * @param string $template_type wp_template or wp_template_part.
	 *
	 * @return array WP_Block_Template[] An array of block template objects.
	 */
	public function get_block_templates( $slugs = array(), $template_type = 'wp_template' ) {
		$templates_from_db  = BlockTemplateUtils::get_block_templates_from_db( $slugs, $template_type );
		$templates_from_woo = $this->get_block_templates_from_woocommerce( $slugs, $templates_from_db, $template_type );

		return array_merge( $templates_from_db, $templates_from_woo );
	}

	/**
	 * Checks whether a block template with that name exists in Woo Blocks
	 *
	 * @param string $template_name Template to check.
	 * @param array  $template_type wp_template or wp_template_part.
	 *
	 * @return boolean
	 */
	public function block_template_is_available( $template_name, $template_type = 'wp_template' ) {
		if ( ! $template_name ) {
			return false;
		}
		$directory = BlockTemplateUtils::get_templates_directory( $template_type ) . '/' . $template_name . '.html';

		return is_readable(
			$directory
		) || $this->get_block_templates( array( $template_name ), $template_type );
	}
}
PK     \8;P  P    Options.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks;

/**
 * Contains all the option names used by the plugin.
 */
class Options {

	const WC_BLOCK_VERSION                                       = 'wc_blocks_version';
	const WC_BLOCK_USE_BLOCKIFIED_PRODUCT_GRID_BLOCK_AS_TEMPLATE = 'wc_blocks_use_blockified_product_grid_block_as_template';
}
PK     \ح      BlockPatterns.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks;

use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Blocks\Domain\Package;
use Automattic\WooCommerce\Blocks\Patterns\PatternRegistry;
use Automattic\WooCommerce\Blocks\Patterns\PTKPatternsStore;

/**
 * Registers patterns under the `./patterns/` directory and from the PTK API and updates their content.
 * Each pattern from core is defined as a PHP file and defines its metadata using plugin-style headers.
 * The minimum required definition is:
 *
 *     /**
 *      * Title: My Pattern
 *      * Slug: my-theme/my-pattern
 *      *
 *
 * The output of the PHP source corresponds to the content of the pattern, e.g.:
 *
 *     <main><p><?php echo "Hello"; ?></p></main>
 *
 * Other settable fields include:
 *
 *   - Description
 *   - Viewport Width
 *   - Categories       (comma-separated values)
 *   - Keywords         (comma-separated values)
 *   - Block Types      (comma-separated values)
 *   - Inserter         (yes/no)
 *
 * @internal
 */
class BlockPatterns {
	const CATEGORIES_PREFIXES = [ '_woo_', '_dotcom_imported_' ];

	/**
	 * Path to the patterns' directory.
	 *
	 * @var string $patterns_path
	 */
	private string $patterns_path;

	/**
	 * PatternRegistry instance.
	 *
	 * @var PatternRegistry $pattern_registry
	 */
	private PatternRegistry $pattern_registry;

	/**
	 * PTKPatternsStore instance.
	 *
	 * @var PTKPatternsStore $ptk_patterns_store
	 */
	private PTKPatternsStore $ptk_patterns_store;

	/**
	 * Constructor for class
	 *
	 * @param Package          $package An instance of Package.
	 * @param PatternRegistry  $pattern_registry An instance of PatternRegistry.
	 * @param PTKPatternsStore $ptk_patterns_store An instance of PTKPatternsStore.
	 */
	public function __construct(
		Package $package,
		PatternRegistry $pattern_registry,
		PTKPatternsStore $ptk_patterns_store
	) {
		$this->patterns_path      = $package->get_path( 'patterns' );
		$this->pattern_registry   = $pattern_registry;
		$this->ptk_patterns_store = $ptk_patterns_store;

		add_action( 'init', array( $this, 'register_block_patterns' ) );

		if ( Features::is_enabled( 'pattern-toolkit-full-composability' ) ) {
			add_action( 'init', array( $this, 'register_ptk_patterns' ) );
		}
	}

	/**
	 * Loads the content of a pattern.
	 *
	 * @param string $pattern_path The path to the pattern.
	 * @return string The content of the pattern.
	 */
	private function load_pattern_content( $pattern_path ) {
		if ( ! file_exists( $pattern_path ) ) {
			return '';
		}

		ob_start();
		include $pattern_path;
		return ob_get_clean();
	}

	/**
	 * Register block patterns from core.
	 *
	 * @return void
	 */
	public function register_block_patterns() {
		if ( ! class_exists( 'WP_Block_Patterns_Registry' ) ) {
			return;
		}

		$patterns = $this->get_block_patterns();
		foreach ( $patterns as $pattern ) {
			$pattern_path      = $this->patterns_path . '/' . $pattern['source'];
			$pattern['source'] = $pattern_path;

			$content            = $this->load_pattern_content( $pattern_path );
			$pattern['content'] = $content;

			$this->pattern_registry->register_block_pattern( $pattern_path, $pattern );
		}
	}

	/**
	 * Gets block pattern data from the cache if available
	 *
	 * @return array Block pattern data.
	 */
	private function get_block_patterns() {
		$pattern_data = $this->get_pattern_cache();

		if ( is_array( $pattern_data ) ) {
			return $pattern_data;
		}

		$default_headers = array(
			'title'         => 'Title',
			'slug'          => 'Slug',
			'description'   => 'Description',
			'viewportWidth' => 'Viewport Width',
			'categories'    => 'Categories',
			'keywords'      => 'Keywords',
			'blockTypes'    => 'Block Types',
			'inserter'      => 'Inserter',
			'featureFlag'   => 'Feature Flag',
			'templateTypes' => 'Template Types',
		);

		if ( ! file_exists( $this->patterns_path ) ) {
			return array();
		}

		$files = glob( $this->patterns_path . '/*.php' );
		if ( ! $files ) {
			return array();
		}

		$patterns = array();

		foreach ( $files as $file ) {
			$data = get_file_data( $file, $default_headers );
			// We want to store the relative path in the cache, so we can use it later to register the pattern.
			$data['source'] = str_replace( $this->patterns_path . '/', '', $file );
			$patterns[]     = $data;
		}

		$this->set_pattern_cache( $patterns );
		return $patterns;
	}

	/**
	 * Gets block pattern cache.
	 *
	 * @return array|false Returns an array of patterns if cache is found, otherwise false.
	 */
	private function get_pattern_cache() {
		$pattern_data = get_site_transient( 'woocommerce_blocks_patterns' );

		if ( is_array( $pattern_data ) && WOOCOMMERCE_VERSION === $pattern_data['version'] ) {
			return $pattern_data['patterns'];
		}

		return false;
	}

	/**
	 * Sets block pattern cache.
	 *
	 * @param array $patterns Block patterns data to set in cache.
	 */
	private function set_pattern_cache( array $patterns ) {
		$pattern_data = array(
			'version'  => WOOCOMMERCE_VERSION,
			'patterns' => $patterns,
		);

		set_site_transient( 'woocommerce_blocks_patterns', $pattern_data, MONTH_IN_SECONDS );
	}

	/**
	 * Register patterns from the Patterns Toolkit.
	 *
	 * @return void
	 */
	public function register_ptk_patterns() {
		// Only if the user has allowed tracking, we register the patterns from the PTK.
		$allow_tracking = 'yes' === get_option( 'woocommerce_allow_tracking' );
		if ( ! $allow_tracking ) {
			return;
		}

		// The most efficient way to check for an existing action is to use `as_has_scheduled_action`, but in unusual
		// cases where another plugin has loaded a very old version of Action Scheduler, it may not be available to us.
		$has_scheduled_action = function_exists( 'as_has_scheduled_action' ) ? 'as_has_scheduled_action' : 'as_next_scheduled_action';

		$patterns = $this->ptk_patterns_store->get_patterns();
		if ( empty( $patterns ) || ! is_array( $patterns ) ) {
			// Only log once per day by using a transient.
			$transient_key = 'wc_ptk_pattern_store_warning';
			// By only logging when patterns are empty and no fetch is scheduled,
			// we ensure that warnings are only generated in genuinely problematic situations,
			// such as when the pattern fetching mechanism has failed entirely.
			if ( ! get_transient( $transient_key ) && ! call_user_func( $has_scheduled_action, 'fetch_patterns' ) ) {
				wc_get_logger()->warning(
					__( 'Empty patterns received from the PTK Pattern Store', 'woocommerce' ),
				);
				// Set the transient to true to indicate that the warning has been logged in the current day.
				set_transient( $transient_key, true, DAY_IN_SECONDS );
			}
			return;
		}

		$patterns = $this->parse_categories( $patterns );

		foreach ( $patterns as $pattern ) {
			$pattern['slug']    = $pattern['name'];
			$pattern['content'] = $pattern['html'];

			$this->pattern_registry->register_block_pattern( $pattern['ID'], $pattern );
		}
	}

	/**
	 * Parse prefixed categories from the PTK patterns into the actual WooCommerce categories.
	 *
	 * @param array $patterns The patterns to parse.
	 * @return array The parsed patterns.
	 */
	private function parse_categories( array $patterns ) {
		return array_map(
			function ( $pattern ) {
				if ( ! isset( $pattern['categories'] ) ) {
					$pattern['categories'] = array();
				}

				$values = array_values( $pattern['categories'] );

				foreach ( $values as $value ) {
					if ( ! isset( $value['title'] ) || ! isset( $value['slug'] ) ) {
						$pattern['categories'] = array();
					}
				}

				$pattern['categories'] = array_map(
					function ( $category ) {
						foreach ( self::CATEGORIES_PREFIXES as $prefix ) {
							if ( strpos( $category['title'], $prefix ) !== false ) {
								$parsed_category   = str_replace( $prefix, '', $category['title'] );
								$parsed_category   = str_replace( '_', ' ', $parsed_category );
								$category['title'] = ucfirst( $parsed_category );
							}
						}

						return $category;
					},
					$pattern['categories']
				);
				return $pattern;
			},
			$patterns
		);
	}
}
PK     \Y
  
  '  Payments/PaymentMethodTypeInterface.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Payments;

use Automattic\WooCommerce\Blocks\Integrations\IntegrationInterface;

interface PaymentMethodTypeInterface extends IntegrationInterface {
	/**
	 * Returns if this payment method should be active. If false, the scripts will not be enqueued.
	 *
	 * @return boolean
	 */
	public function is_active();

	/**
	 * Returns an array of script handles to enqueue for this payment method in
	 * the frontend context
	 *
	 * @return string[]
	 */
	public function get_payment_method_script_handles();

	/**
	 * Returns an array of script handles to enqueue for this payment method in
	 * the admin context
	 *
	 * @return string[]
	 */
	public function get_payment_method_script_handles_for_admin();

	/**
	 * An array of key, value pairs of data made available to payment methods
	 * client side.
	 *
	 * @return array
	 */
	public function get_payment_method_data();

	/**
	 * Get array of supported features.
	 *
	 * @return string[]
	 */
	public function get_supported_features();
}
PK     \@nf  f  "  Payments/PaymentMethodRegistry.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Payments;

use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;

/**
 * Class used for interacting with payment method types.
 *
 * @since 2.6.0
 */
final class PaymentMethodRegistry extends IntegrationRegistry {
	/**
	 * Integration identifier is used to construct hook names and is given when the integration registry is initialized.
	 *
	 * @var string
	 */
	protected $registry_identifier = 'payment_method_type';

	/**
	 * Retrieves all registered payment methods that are also active.
	 *
	 * @return PaymentMethodTypeInterface[]
	 */
	public function get_all_active_registered() {
		return array_filter(
			$this->get_all_registered(),
			function( $payment_method ) {
				return $payment_method->is_active();
			}
		);
	}

	/**
	 * Gets an array of all registered payment method script handles, but only for active payment methods.
	 *
	 * @return string[]
	 */
	public function get_all_active_payment_method_script_dependencies() {
		$script_handles  = [];
		$payment_methods = $this->get_all_active_registered();

		foreach ( $payment_methods as $payment_method ) {
			$script_handles = array_merge(
				$script_handles,
				is_admin() ? $payment_method->get_payment_method_script_handles_for_admin() : $payment_method->get_payment_method_script_handles()
			);
		}

		return array_unique( array_filter( $script_handles ) );
	}

	/**
	 * Gets an array of all registered payment method script data, but only for active payment methods.
	 *
	 * @return array
	 */
	public function get_all_registered_script_data() {
		$script_data     = [];
		$payment_methods = $this->get_all_active_registered();

		foreach ( $payment_methods as $payment_method ) {
			$script_data[ $payment_method->get_name() ] = $payment_method->get_payment_method_data();
		}

		return array( 'paymentMethodData' => array_filter( $script_data ) );
	}
}
PK     \lf#m      Payments/Api.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Payments;

use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer;
use Automattic\WooCommerce\Blocks\Payments\Integrations\CashOnDelivery;
use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque;
use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal;

/**
 *  The Api class provides an interface to payment method registration.
 *
 * @since 2.6.0
 */
class Api {
	/**
	 * Reference to the PaymentMethodRegistry instance.
	 *
	 * @var PaymentMethodRegistry
	 */
	private $payment_method_registry;

	/**
	 * Reference to the AssetDataRegistry instance.
	 *
	 * @var AssetDataRegistry
	 */
	private $asset_registry;

	/**
	 * Constructor
	 *
	 * @param PaymentMethodRegistry $payment_method_registry An instance of Payment Method Registry.
	 * @param AssetDataRegistry     $asset_registry  Used for registering data to pass along to the request.
	 */
	public function __construct( PaymentMethodRegistry $payment_method_registry, AssetDataRegistry $asset_registry ) {
		$this->payment_method_registry = $payment_method_registry;
		$this->asset_registry          = $asset_registry;
	}

	/**
	 * Initialize class features.
	 */
	public function init() {
		add_action( 'init', array( $this->payment_method_registry, 'initialize' ), 5 );
		add_filter( 'woocommerce_blocks_register_script_dependencies', array( $this, 'add_payment_method_script_dependencies' ), 10, 2 );
		add_action( 'woocommerce_blocks_checkout_enqueue_data', array( $this, 'add_payment_method_script_data' ) );
		add_action( 'woocommerce_blocks_cart_enqueue_data', array( $this, 'add_payment_method_script_data' ) );
		add_action( 'woocommerce_blocks_payment_method_type_registration', array( $this, 'register_payment_method_integrations' ) );
		add_action( 'wp_print_scripts', array( $this, 'verify_payment_methods_dependencies' ), 1 );
	}

	/**
	 * Add payment method script handles as script dependencies.
	 *
	 * @param array  $dependencies Array of script dependencies.
	 * @param string $handle Script handle.
	 * @return array
	 */
	public function add_payment_method_script_dependencies( $dependencies, $handle ) {
		if ( ! in_array( $handle, [ 'wc-checkout-block', 'wc-checkout-block-frontend', 'wc-cart-block', 'wc-cart-block-frontend' ], true ) ) {
			return $dependencies;
		}
		return array_merge( $dependencies, $this->payment_method_registry->get_all_active_payment_method_script_dependencies() );
	}

	/**
	 * Returns true if the payment gateway is enabled.
	 *
	 * @param object $gateway Payment gateway.
	 * @return boolean
	 */
	private function is_payment_gateway_enabled( $gateway ) {
		return filter_var( $gateway->enabled, FILTER_VALIDATE_BOOLEAN );
	}

	/**
	 * Add payment method data to Asset Registry.
	 */
	public function add_payment_method_script_data() {
		// Enqueue the order of enabled gateways.
		if ( ! $this->asset_registry->exists( 'paymentMethodSortOrder' ) ) {
			// We use payment_gateways() here to get the sort order of all enabled gateways. Some may be
			// programmatically disabled later on, but we still need to know where the enabled ones are in the list.
			$payment_gateways = WC()->payment_gateways->payment_gateways();
			$enabled_gateways = array_filter( $payment_gateways, array( $this, 'is_payment_gateway_enabled' ) );
			$this->asset_registry->add( 'paymentMethodSortOrder', array_keys( $enabled_gateways ) );
		}

		// Enqueue all registered gateway data (settings/config etc).
		$script_data = $this->payment_method_registry->get_all_registered_script_data();
		foreach ( $script_data as $asset_data_key => $asset_data_value ) {
			if ( ! $this->asset_registry->exists( $asset_data_key ) ) {
				$this->asset_registry->add( $asset_data_key, $asset_data_value );
			}
		}
	}

	/**
	 * Register payment method integrations bundled with blocks.
	 *
	 * @param PaymentMethodRegistry $payment_method_registry Payment method registry instance.
	 */
	public function register_payment_method_integrations( PaymentMethodRegistry $payment_method_registry ) {
		$payment_method_registry->register(
			Package::container()->get( Cheque::class )
		);
		$payment_method_registry->register(
			Package::container()->get( PayPal::class )
		);
		$payment_method_registry->register(
			Package::container()->get( BankTransfer::class )
		);
		$payment_method_registry->register(
			Package::container()->get( CashOnDelivery::class )
		);
	}

	/**
	 * Verify all dependencies of registered payment methods have been registered.
	 * If not, remove that payment method script from the list of dependencies
	 * of Cart and Checkout block scripts so it doesn't break the blocks and show
	 * an error in the admin.
	 */
	public function verify_payment_methods_dependencies() {
		// Check that the wc-blocks script is registered before continuing. Some extensions may cause this function to run
		// before the payment method scripts' dependencies are registered.
		if ( ! wp_script_is( 'wc-blocks', 'registered' ) ) {
			return;
		}
		$wp_scripts             = wp_scripts();
		$payment_method_scripts = $this->payment_method_registry->get_all_active_payment_method_script_dependencies();

		foreach ( $payment_method_scripts as $payment_method_script ) {
			if (
				! array_key_exists( $payment_method_script, $wp_scripts->registered ) ||
				! property_exists( $wp_scripts->registered[ $payment_method_script ], 'deps' )
			) {
				continue;
			}
			$deps = $wp_scripts->registered[ $payment_method_script ]->deps;
			foreach ( $deps as $dep ) {
				if ( ! wp_script_is( $dep, 'registered' ) ) {
					$error_handle  = $dep . '-dependency-error';
					$error_message = sprintf(
						'Payment gateway with handle \'%1$s\' has been deactivated in Cart and Checkout blocks because its dependency \'%2$s\' is not registered. Read the docs about registering assets for payment methods: https://github.com/woocommerce/woocommerce-blocks/blob/060f63c04f0f34f645200b5d4da9212125c49177/docs/third-party-developers/extensibility/checkout-payment-methods/payment-method-integration.md#registering-assets',
						esc_html( $payment_method_script ),
						esc_html( $dep )
					);

					// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
					error_log( $error_message );

					// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter,WordPress.WP.EnqueuedResourceParameters.MissingVersion
					wp_register_script( $error_handle, '' );
					wp_enqueue_script( $error_handle );
					wp_add_inline_script(
						$error_handle,
						sprintf( 'console.error( "%s" );', $error_message )
					);

					$cart_checkout_scripts = [ 'wc-cart-block', 'wc-cart-block-frontend', 'wc-checkout-block', 'wc-checkout-block-frontend' ];
					foreach ( $cart_checkout_scripts as $script_handle ) {
						if (
							! array_key_exists( $script_handle, $wp_scripts->registered ) ||
							! property_exists( $wp_scripts->registered[ $script_handle ], 'deps' )
						) {
							continue;
						}
						// Remove payment method script from dependencies.
						$wp_scripts->registered[ $script_handle ]->deps = array_diff(
							$wp_scripts->registered[ $script_handle ]->deps,
							[ $payment_method_script ]
						);
					}
				}
			}
		}
	}
}
PK     \}4f  f     Payments/Integrations/PayPal.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Payments\Integrations;

use WC_Gateway_Paypal;
use Automattic\WooCommerce\Blocks\Assets\Api;
use Automattic\WooCommerce\Gateways\PayPal\Buttons as PayPalButtons;

/**
 * PayPal Standard payment method integration
 *
 * @since 2.6.0
 */
final class PayPal extends AbstractPaymentMethodType {
	/**
	 * Payment method name defined by payment methods extending this class.
	 *
	 * @var string
	 */
	protected $name = WC_Gateway_Paypal::ID;

	/**
	 * An instance of the Asset Api
	 *
	 * @var Api
	 */
	private $asset_api;

	/**
	 * Constructor
	 *
	 * @param Api $asset_api An instance of Api.
	 */
	public function __construct( Api $asset_api ) {
		$this->asset_api = $asset_api;
	}

	/**
	 * Initializes the payment method type.
	 */
	public function initialize() {
		$this->settings = get_option( 'woocommerce_paypal_settings', [] );
	}

	/**
	 * Returns if this payment method should be active. If false, the scripts will not be enqueued.
	 *
	 * @return boolean
	 */
	public function is_active() {
		return filter_var( $this->get_setting( 'enabled', false ), FILTER_VALIDATE_BOOLEAN );
	}

	/**
	 * Returns an array of scripts/handles to be registered for this payment method.
	 *
	 * @return array
	 */
	public function get_payment_method_script_handles() {
		$this->asset_api->register_script(
			'wc-payment-method-paypal',
			'assets/client/blocks/wc-payment-method-paypal.js'
		);
		return [ 'wc-payment-method-paypal' ];
	}

	/**
	 * Returns an array of key=>value pairs of data made available to the payment methods script.
	 *
	 * @return array
	 */
	public function get_payment_method_data() {
		$gateway = WC_Gateway_Paypal::get_instance();

		if ( ! $gateway->is_available() ) {
			return [];
		}

		$buttons = new PayPalButtons( $gateway );
		$options = $buttons->get_options();

		return [
			'title'                  => $this->get_setting( 'title' ),
			'description'            => $this->get_description(),
			'supports'               => $this->get_supported_features(),
			'isButtonsEnabled'       => $buttons->is_enabled(),
			'isProductPage'          => is_product(),
			'appSwitchRequestOrigin' => $buttons->get_current_page_for_app_switch(),
			'buttonsOptions'         => $options,
			'wc_store_api_nonce'     => wp_create_nonce( 'wc_store_api' ),
			'create_order_nonce'     => wp_create_nonce( 'wc_gateway_paypal_standard_create_order' ),
			'cancel_payment_nonce'   => wp_create_nonce( 'wc_gateway_paypal_standard_cancel_payment' ),
		];
	}

	/**
	 * Get the description for the payment method. Add sandbox instructions if sandbox mode is enabled.
	 *
	 * @return string
	 */
	public function get_description() {
		$gateway     = WC_Gateway_Paypal::get_instance();
		$testmode    = $gateway->testmode;
		$description = $this->get_setting( 'description' ) ?? '';
		if ( $testmode ) {
			/* translators: %s: Link to PayPal sandbox testing guide page */
			$description .= '<br>' . sprintf( __( '<strong>Sandbox mode enabled</strong>. Only sandbox test accounts can be used. See the <a href="%s">PayPal Sandbox Testing Guide</a> for more details.', 'woocommerce' ), 'https://developer.paypal.com/tools/sandbox/' );
		}
		return trim( $description );
	}

	/**
	 * Returns an array of supported features.
	 *
	 * @return string[]
	 */
	public function get_supported_features() {
		$gateway  = WC_Gateway_Paypal::get_instance();
		$features = array_filter( $gateway->supports, array( $gateway, 'supports' ) );

		/**
		 * Filter to control what features are available for each payment gateway.
		 *
		 * @since 4.4.0
		 *
		 * @example See docs/examples/payment-gateways-features-list.md
		 *
		 * @param array $features List of supported features.
		 * @param string $name Gateway name.
		 * @return array Updated list of supported features.
		 */
		return apply_filters( '__experimental_woocommerce_blocks_payment_gateway_features_list', $features, $this->get_name() );
	}
}
PK     \kNk    3  Payments/Integrations/AbstractPaymentMethodType.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Payments\Integrations;

use Automattic\WooCommerce\Blocks\Payments\PaymentMethodTypeInterface;

/**
 * AbstractPaymentMethodType class.
 *
 * @since 2.6.0
 */
abstract class AbstractPaymentMethodType implements PaymentMethodTypeInterface {
	/**
	 * Payment method name defined by payment methods extending this class.
	 *
	 * @var string
	 */
	protected $name = '';

	/**
	 * Settings from the WP options table
	 *
	 * @var array
	 */
	protected $settings = [];

	/**
	 * Get a setting from the settings array if set.
	 *
	 * @param string $name Setting name.
	 * @param mixed  $default Value that is returned if the setting does not exist.
	 * @return mixed
	 */
	protected function get_setting( $name, $default = '' ) {
		return isset( $this->settings[ $name ] ) ? $this->settings[ $name ] : $default;
	}

	/**
	 * Returns the name of the payment method.
	 */
	public function get_name() {
		return $this->name;
	}

	/**
	 * Returns if this payment method should be active. If false, the scripts will not be enqueued.
	 *
	 * @return boolean
	 */
	public function is_active() {
		return true;
	}

	/**
	 * Returns an array of script handles to enqueue for this payment method in
	 * the frontend context
	 *
	 * @return string[]
	 */
	public function get_payment_method_script_handles() {
		return [];
	}

	/**
	 * Returns an array of script handles to enqueue for this payment method in
	 * the admin context
	 *
	 * @return string[]
	 */
	public function get_payment_method_script_handles_for_admin() {
		return $this->get_payment_method_script_handles();
	}

	/**
	 * Returns an array of supported features.
	 *
	 * @return string[]
	 */
	public function get_supported_features() {
		return [ 'products' ];
	}

	/**
	 * An array of key, value pairs of data made available to payment methods
	 * client side.
	 *
	 * @return array
	 */
	public function get_payment_method_data() {
		return [];
	}

	/**
	 * Returns an array of script handles to enqueue in the frontend context.
	 *
	 * Alias of get_payment_method_script_handles. Defined by IntegrationInterface.
	 *
	 * @return string[]
	 */
	public function get_script_handles() {
		return $this->get_payment_method_script_handles();
	}

	/**
	 * Returns an array of script handles to enqueue in the admin context.
	 *
	 * Alias of get_payment_method_script_handles_for_admin. Defined by IntegrationInterface.
	 *
	 * @return string[]
	 */
	public function get_editor_script_handles() {
		return $this->get_payment_method_script_handles_for_admin();
	}

	/**
	 * An array of key, value pairs of data made available to the block on the client side.
	 *
	 * Alias of get_payment_method_data. Defined by IntegrationInterface.
	 *
	 * @return array
	 */
	public function get_script_data() {
		return $this->get_payment_method_data();
	}
}
PK     \7Y
  
  (  Payments/Integrations/CashOnDelivery.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Payments\Integrations;

use Automattic\WooCommerce\Blocks\Assets\Api;
use WC_Gateway_COD;

/**
 * Cash on Delivery (COD) payment method integration
 *
 * @since 3.0.0
 */
final class CashOnDelivery extends AbstractPaymentMethodType {
	/**
	 * Payment method name/id/slug (matches id in WC_Gateway_COD in core).
	 *
	 * @var string
	 */
	protected $name = WC_Gateway_COD::ID;

	/**
	 * An instance of the Asset Api
	 *
	 * @var Api
	 */
	private $asset_api;

	/**
	 * Constructor
	 *
	 * @param Api $asset_api An instance of Api.
	 */
	public function __construct( Api $asset_api ) {
		$this->asset_api = $asset_api;
	}

	/**
	 * Initializes the payment method type.
	 */
	public function initialize() {
		$this->settings = get_option( 'woocommerce_cod_settings', [] );
	}

	/**
	 * Returns if this payment method should be active. If false, the scripts will not be enqueued.
	 *
	 * @return boolean
	 */
	public function is_active() {
		return filter_var( $this->get_setting( 'enabled', false ), FILTER_VALIDATE_BOOLEAN );
	}

	/**
	 * Return enable_for_virtual option.
	 *
	 * @return boolean True if store allows COD payment for orders containing only virtual products.
	 */
	private function get_enable_for_virtual() {
		return filter_var( $this->get_setting( 'enable_for_virtual', false ), FILTER_VALIDATE_BOOLEAN );
	}

	/**
	 * Return enable_for_methods option.
	 *
	 * @return array Array of shipping methods (string ids) that allow COD. (If empty, all support COD.)
	 */
	private function get_enable_for_methods() {
		$enable_for_methods = $this->get_setting( 'enable_for_methods', [] );
		if ( '' === $enable_for_methods ) {
			return [];
		}
		return $enable_for_methods;
	}


	/**
	 * Returns an array of scripts/handles to be registered for this payment method.
	 *
	 * @return array
	 */
	public function get_payment_method_script_handles() {
		$this->asset_api->register_script(
			'wc-payment-method-cod',
			'assets/client/blocks/wc-payment-method-cod.js'
		);
		return [ 'wc-payment-method-cod' ];
	}

	/**
	 * Returns an array of key=>value pairs of data made available to the payment methods script.
	 *
	 * @return array
	 */
	public function get_payment_method_data() {
		return [
			'title'                    => $this->get_setting( 'title' ),
			'description'              => $this->get_setting( 'description' ),
			'enableForVirtual'         => $this->get_enable_for_virtual(),
			'enableForShippingMethods' => $this->get_enable_for_methods(),
			'supports'                 => $this->get_supported_features(),
		];
	}
}
PK     \ +d       Payments/Integrations/Cheque.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Payments\Integrations;

use Exception;
use Automattic\WooCommerce\Blocks\Assets\Api;
use WC_Gateway_Cheque;

/**
 * Cheque payment method integration
 *
 * @since 2.6.0
 */
final class Cheque extends AbstractPaymentMethodType {
	/**
	 * Payment method name defined by payment methods extending this class.
	 *
	 * @var string
	 */
	protected $name = WC_Gateway_Cheque::ID;

	/**
	 * An instance of the Asset Api
	 *
	 * @var Api
	 */
	private $asset_api;

	/**
	 * Constructor
	 *
	 * @param Api $asset_api An instance of Api.
	 */
	public function __construct( Api $asset_api ) {
		$this->asset_api = $asset_api;
	}

	/**
	 * Initializes the payment method type.
	 */
	public function initialize() {
		$this->settings = get_option( 'woocommerce_cheque_settings', [] );
	}

	/**
	 * Returns if this payment method should be active. If false, the scripts will not be enqueued.
	 *
	 * @return boolean
	 */
	public function is_active() {
		return filter_var( $this->get_setting( 'enabled', false ), FILTER_VALIDATE_BOOLEAN );
	}

	/**
	 * Returns an array of scripts/handles to be registered for this payment method.
	 *
	 * @return array
	 */
	public function get_payment_method_script_handles() {
		$this->asset_api->register_script(
			'wc-payment-method-cheque',
			'assets/client/blocks/wc-payment-method-cheque.js'
		);
		return [ 'wc-payment-method-cheque' ];
	}

	/**
	 * Returns an array of key=>value pairs of data made available to the payment methods script.
	 *
	 * @return array
	 */
	public function get_payment_method_data() {
		return [
			'title'       => $this->get_setting( 'title' ),
			'description' => $this->get_setting( 'description' ),
			'supports'    => $this->get_supported_features(),
		];
	}
}
PK     \~    &  Payments/Integrations/BankTransfer.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Payments\Integrations;

use Automattic\WooCommerce\Blocks\Assets\Api;
use WC_Gateway_BACS;

/**
 * Bank Transfer (BACS) payment method integration
 *
 * @since 3.0.0
 */
final class BankTransfer extends AbstractPaymentMethodType {
	/**
	 * Payment method name/id/slug (matches id in WC_Gateway_BACS in core).
	 *
	 * @var string
	 */
	protected $name = WC_Gateway_BACS::ID;

	/**
	 * An instance of the Asset Api
	 *
	 * @var Api
	 */
	private $asset_api;

	/**
	 * Constructor
	 *
	 * @param Api $asset_api An instance of Api.
	 */
	public function __construct( Api $asset_api ) {
		$this->asset_api = $asset_api;
	}

	/**
	 * Initializes the payment method type.
	 */
	public function initialize() {
		$this->settings = get_option( 'woocommerce_bacs_settings', [] );
	}

	/**
	 * Returns if this payment method should be active. If false, the scripts will not be enqueued.
	 *
	 * @return boolean
	 */
	public function is_active() {
		return filter_var( $this->get_setting( 'enabled', false ), FILTER_VALIDATE_BOOLEAN );
	}

	/**
	 * Returns an array of scripts/handles to be registered for this payment method.
	 *
	 * @return array
	 */
	public function get_payment_method_script_handles() {
		$this->asset_api->register_script(
			'wc-payment-method-bacs',
			'assets/client/blocks/wc-payment-method-bacs.js'
		);
		return [ 'wc-payment-method-bacs' ];
	}

	/**
	 * Returns an array of key=>value pairs of data made available to the payment methods script.
	 *
	 * @return array
	 */
	public function get_payment_method_data() {
		return [
			'title'       => $this->get_setting( 'title' ),
			'description' => $this->get_setting( 'description' ),
			'supports'    => $this->get_supported_features(),
		];
	}
}
PK     \b|x  x    Utils/BlockTemplateUtils.phpnu [        <?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\Blocks\Utils;

use WP_Block_Patterns_Registry;
use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Blocks\Options;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\BlockTemplatesRegistry;
use Automattic\WooCommerce\Blocks\Templates\ProductCatalogTemplate;

/**
 * Utility methods used for serving block templates from WooCommerce Blocks.
 * {@internal This class and its methods should only be used within the BlockTemplateController.php and is not intended for public use.}
 */
class BlockTemplateUtils {
	/**
	 * Directory names for block templates
	 *
	 * Directory names conventions for block templates have changed with Gutenberg 12.1.0,
	 * however, for backwards-compatibility, we also keep the older conventions, prefixed
	 * with `DEPRECATED_`.
	 *
	 * @var array {
	 *     @var string DEPRECATED_TEMPLATES  Old directory name of the block templates directory.
	 *     @var string DEPRECATED_TEMPLATE_PARTS  Old directory name of the block template parts directory.
	 *     @var string TEMPLATES_DIR_NAME  Directory name of the block templates directory.
	 *     @var string TEMPLATE_PARTS_DIR_NAME  Directory name of the block template parts directory.
	 * }
	 */
	const DIRECTORY_NAMES = array(
		'DEPRECATED_TEMPLATES'      => 'block-templates',
		'DEPRECATED_TEMPLATE_PARTS' => 'block-template-parts',
		'TEMPLATES'                 => 'templates',
		'TEMPLATE_PARTS'            => 'parts',
	);

	const TEMPLATES_ROOT_DIR = 'templates';

	/**
	 * WooCommerce plugin slug
	 *
	 * This is used to save templates to the DB which are stored against this value in the wp_terms table.
	 *
	 * @var string
	 */
	const PLUGIN_SLUG = 'woocommerce/woocommerce';

	/**
	 * Deprecated WooCommerce plugin slug
	 *
	 * For supporting users who have customized templates under the incorrect plugin slug during the first release.
	 * More context found here: https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5423.
	 *
	 * @var string
	 */
	const DEPRECATED_PLUGIN_SLUG = 'woocommerce';

	/**
	 * Returns the template matching the slug
	 *
	 * @param string $template_slug Slug of the template to retrieve.
	 *
	 * @return AbstractTemplate|AbstractTemplatePart|null
	 */
	public static function get_template( $template_slug ) {
		$block_templates_registry = Package::container()->get( BlockTemplatesRegistry::class );
		return $block_templates_registry->get_template( $template_slug );
	}

	/**
	 * Returns an array containing the references of
	 * the passed blocks and their inner blocks.
	 *
	 * @param array $blocks array of blocks.
	 *
	 * @return array block references to the passed blocks and their inner blocks.
	 */
	public static function flatten_blocks( &$blocks ) {
		$all_blocks = array();
		$queue      = array();
		foreach ( $blocks as &$block ) {
			$queue[] = &$block;
		}
		$queue_count = count( $queue );

		while ( $queue_count > 0 ) {
			$block = &$queue[0];
			array_shift( $queue );
			$all_blocks[] = &$block;

			if ( ! empty( $block['innerBlocks'] ) ) {
				foreach ( $block['innerBlocks'] as &$inner_block ) {
					$queue[] = &$inner_block;
				}
			}

			$queue_count = count( $queue );
		}

		return $all_blocks;
	}

	/**
	 * Parses wp_template content and injects the current theme's
	 * stylesheet as a theme attribute into each wp_template_part
	 *
	 * @param string $template_content serialized wp_template content.
	 *
	 * @return string Updated wp_template content.
	 */
	public static function inject_theme_attribute_in_content( $template_content ) {
		$has_updated_content = false;
		$new_content         = '';
		$template_blocks     = parse_blocks( $template_content );

		$blocks = self::flatten_blocks( $template_blocks );
		foreach ( $blocks as &$block ) {
			if (
				'core/template-part' === $block['blockName'] &&
				! isset( $block['attrs']['theme'] )
			) {
				$block['attrs']['theme'] = wp_get_theme()->get_stylesheet();
				$has_updated_content     = true;
			}
		}

		if ( $has_updated_content ) {
			foreach ( $template_blocks as &$block ) {
				$new_content .= serialize_block( $block );
			}

			return $new_content;
		}

		return $template_content;
	}

	/**
	 * Build a unified template object based a post Object.
	 * Important: This method is an almost identical duplicate from wp-includes/block-template-utils.php as it was not intended for public use. It has been modified to build templates from plugins rather than themes.
	 *
	 * @param \WP_Post $post Template post.
	 *
	 * @return \WP_Block_Template|\WP_Error Template.
	 */
	public static function build_template_result_from_post( $post ) {
		$terms = get_the_terms( $post, 'wp_theme' );

		if ( is_wp_error( $terms ) ) {
			return $terms;
		}

		if ( ! $terms ) {
			return new \WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.', 'woocommerce' ) );
		}

		$theme          = $terms[0]->name;
		$has_theme_file = true;

		$template                 = new \WP_Block_Template();
		$template->wp_id          = $post->ID;
		$template->id             = $theme . '//' . $post->post_name;
		$template->theme          = $theme;
		$template->content        = $post->post_content;
		$template->slug           = $post->post_name;
		$template->source         = 'custom';
		$template->type           = $post->post_type;
		$template->description    = $post->post_excerpt;
		$template->title          = $post->post_title;
		$template->status         = $post->post_status;
		$template->has_theme_file = $has_theme_file;
		$template->is_custom      = false;
		$template->post_types     = array(); // Don't appear in any Edit Post template selector dropdown.

		if ( 'wp_template_part' === $post->post_type ) {
			$type_terms = get_the_terms( $post, 'wp_template_part_area' );
			if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) {
				$template->area = $type_terms[0]->name;
			}
		}

		// We are checking 'woocommerce' to maintain classic templates which are saved to the DB,
		// prior to updating to use the correct slug.
		// More information found here: https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5423.
		if ( self::PLUGIN_SLUG === $theme || self::DEPRECATED_PLUGIN_SLUG === strtolower( $theme ) ) {
			$template->origin = 'plugin';
		}

		/*
		* Run the block hooks algorithm introduced in WP 6.4 on the template content.
		*/
		if ( function_exists( 'inject_ignored_hooked_blocks_metadata_attributes' ) ) {
			$hooked_blocks = get_hooked_blocks();
			if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
				$before_block_visitor = make_before_block_visitor( $hooked_blocks, $template );
				$after_block_visitor  = make_after_block_visitor( $hooked_blocks, $template );
				$blocks               = parse_blocks( $template->content );
				$template->content    = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
			}
		}

		return $template;
	}

	/**
	 * Build a unified template object based on a theme file.
	 *
	 * @internal Important: This method is an almost identical duplicate from wp-includes/block-template-utils.php as it was not intended for public use. It has been modified to build templates from plugins rather than themes.
	 *
	 * @param array|object $template_file Theme file.
	 * @param string       $template_type wp_template or wp_template_part.
	 *
	 * @return \WP_Block_Template Template.
	 */
	public static function build_template_result_from_file( $template_file, $template_type ) {
		$template_file = (object) $template_file;

		// If the theme has an archive-products.html template but does not have product taxonomy templates
		// then we will load in the archive-product.html template from the theme to use for product taxonomies on the frontend.
		$template_is_from_theme = 'theme' === $template_file->source;
		$theme_name             = wp_get_theme()->get( 'TextDomain' );

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		$template_content  = file_get_contents( $template_file->path );
		$template          = new \WP_Block_Template();
		$template->id      = $template_is_from_theme ? $theme_name . '//' . $template_file->slug : self::PLUGIN_SLUG . '//' . $template_file->slug;
		$template->theme   = $template_is_from_theme ? $theme_name : self::PLUGIN_SLUG;
		$template->content = self::inject_theme_attribute_in_content( $template_content );
		// Remove the term description block from the archive-product template
		// as the Product Catalog/Shop page doesn't have a description.
		if ( ProductCatalogTemplate::SLUG === $template_file->slug ) {
			$template->content = str_replace( '<!-- wp:term-description {"align":"wide"} /-->', '', $template->content );
		}
		// Plugin was agreed as a valid source value despite existing inline docs at the time of creating: https://github.com/WordPress/gutenberg/issues/36597#issuecomment-976232909.
		$template->source         = $template_file->source ? $template_file->source : 'plugin';
		$template->slug           = $template_file->slug;
		$template->type           = $template_type;
		$template->title          = ! empty( $template_file->title ) ? $template_file->title : self::get_block_template_title( $template_file->slug );
		$template->description    = ! empty( $template_file->description ) ? $template_file->description : self::get_block_template_description( $template_file->slug );
		$template->status         = 'publish';
		$template->has_theme_file = true;
		$template->origin         = $template_file->source;
		$template->is_custom      = false; // Templates loaded from the filesystem aren't custom, ones that have been edited and loaded from the DB are.
		$template->post_types     = array(); // Don't appear in any Edit Post template selector dropdown.
		$template->area           = self::get_block_template_area( $template->slug, $template_type );

		/*
		 * Run the block hooks algorithm introduced in WP 6.4 on the template content.
		 */
		if ( function_exists( 'inject_ignored_hooked_blocks_metadata_attributes' ) ) {
			$before_block_visitor = '_inject_theme_attribute_in_template_part_block';
			$after_block_visitor  = null;
			$hooked_blocks        = get_hooked_blocks();
			if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
				$before_block_visitor = make_before_block_visitor( $hooked_blocks, $template );
				$after_block_visitor  = make_after_block_visitor( $hooked_blocks, $template );
			}
			$blocks            = parse_blocks( $template->content );
			$template->content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
		}

		return $template;
	}

	/**
	 * Build a new template object so that we can make Woo Blocks default templates available in the current theme should they not have any.
	 *
	 * @param string $template_file Block template file path.
	 * @param string $template_type wp_template or wp_template_part.
	 * @param string $template_slug Block template slug e.g. single-product.
	 * @param bool   $template_is_from_theme If the block template file is being loaded from the current theme instead of Woo Blocks.
	 *
	 * @return object Block template object.
	 */
	public static function create_new_block_template_object( $template_file, $template_type, $template_slug, $template_is_from_theme = false ) {
		$theme_name = wp_get_theme()->get( 'TextDomain' );

		$new_template_item = array(
			'slug'        => $template_slug,
			'id'          => $template_is_from_theme ? $theme_name . '//' . $template_slug : self::PLUGIN_SLUG . '//' . $template_slug,
			'path'        => $template_file,
			'type'        => $template_type,
			'theme'       => $template_is_from_theme ? $theme_name : self::PLUGIN_SLUG,
			// Plugin was agreed as a valid source value despite existing inline docs at the time of creating: https://github.com/WordPress/gutenberg/issues/36597#issuecomment-976232909.
			'source'      => $template_is_from_theme ? 'theme' : 'plugin',
			'title'       => self::get_block_template_title( $template_slug ),
			'description' => self::get_block_template_description( $template_slug ),
			'post_types'  => array(), // Don't appear in any Edit Post template selector dropdown.
		);

		return (object) $new_template_item;
	}

	/**
	 * Finds all nested template part file paths in a theme's directory.
	 *
	 * @param string $template_type wp_template or wp_template_part.
	 * @return array $path_list A list of paths to all template part files.
	 */
	public static function get_template_paths( $template_type ) {
		$wp_template_filenames = array(
			'archive-product.html',
			'order-confirmation.html',
			'page-cart.html',
			'page-checkout.html',
			'product-search-results.html',
			'single-product.html',
			'taxonomy-product_attribute.html',
			'taxonomy-product_brand.html',
			'taxonomy-product_cat.html',
			'taxonomy-product_tag.html',
		);

		if ( Features::is_enabled( 'launch-your-store' ) ) {
			$wp_template_filenames[] = 'coming-soon.html';
		}

		$wp_template_part_filenames = array(
			'checkout-header.html',
			'coming-soon-social-links.html',
			'mini-cart.html',
			'simple-product-add-to-cart-with-options.html',
			'external-product-add-to-cart-with-options.html',
			'variable-product-add-to-cart-with-options.html',
			'grouped-product-add-to-cart-with-options.html',
		);

		/*
		 * This may return the blockified directory for `wp_templates`.
		 * At the moment every template file has a corresponding blockified file.
		 * If we decide to add a new template file that doesn't, we will need to update this logic.
		 */
		$directory = self::get_templates_directory( $template_type );

		$path_list = array_map(
			function ( $filename ) use ( $directory ) {
				return $directory . DIRECTORY_SEPARATOR . $filename;
			},
			'wp_template' === $template_type ? $wp_template_filenames : $wp_template_part_filenames
		);

		return $path_list;
	}

	/**
	 * Gets the directory where templates of a specific template type can be found.
	 *
	 * @param string $template_type wp_template or wp_template_part.
	 *
	 * @return string
	 */
	public static function get_templates_directory( $template_type = 'wp_template' ) {
			$root_path                = dirname( __DIR__, 3 ) . '/' . self::TEMPLATES_ROOT_DIR . DIRECTORY_SEPARATOR;
			$templates_directory      = $root_path . self::DIRECTORY_NAMES['TEMPLATES'];
			$template_parts_directory = $root_path . self::DIRECTORY_NAMES['TEMPLATE_PARTS'];

		if ( 'wp_template_part' === $template_type ) {
			return $template_parts_directory;
		}

		if ( self::should_use_blockified_product_grid_templates() ) {
			return $templates_directory . '/blockified';
		}

		return $templates_directory;
	}

	/**
	 * Returns template title.
	 *
	 * @param string $template_slug The template slug (e.g. single-product).
	 * @return string Human friendly title.
	 */
	public static function get_block_template_title( $template_slug ) {
		$registered_template = self::get_template( $template_slug );
		if ( isset( $registered_template ) ) {
			return $registered_template->get_template_title();
		} else {
			// Human friendly title converted from the slug.
			return ucwords( preg_replace( '/[\-_]/', ' ', $template_slug ) );
		}
	}

	/**
	 * Returns template description.
	 *
	 * @param string $template_slug The template slug (e.g. single-product).
	 * @return string Template description.
	 */
	public static function get_block_template_description( $template_slug ) {
		$registered_template = self::get_template( $template_slug );
		if ( isset( $registered_template ) ) {
			return $registered_template->get_template_description();
		}
		return '';
	}

	/**
	 * Returns area for template parts.
	 *
	 * @param string $template_slug The template part slug (e.g. mini-cart).
	 * @param string $template_type Either `wp_template` or `wp_template_part`.
	 * @return string Template part area.
	 */
	public static function get_block_template_area( $template_slug, $template_type ) {
		if ( 'wp_template_part' === $template_type ) {
			$registered_template = self::get_template( $template_slug );
			if ( $registered_template && property_exists( $registered_template, 'template_area' ) ) {
				return $registered_template->template_area;
			}
		}
		return 'uncategorized';
	}

	/**
	 * Converts template paths into a slug
	 *
	 * @param string $path The template's path.
	 * @return string slug
	 */
	public static function generate_template_slug_from_path( $path ) {
		$template_extension = '.html';

		return basename( $path, $template_extension );
	}

	/**
	 * Gets the first matching template part within themes directories
	 *
	 * Since [Gutenberg 12.1.0](https://github.com/WordPress/gutenberg/releases/tag/v12.1.0), the conventions for
	 * block templates and parts directory has changed from `block-templates` and `block-templates-parts`
	 * to `templates` and `parts` respectively.
	 *
	 * This function traverses all possible combinations of directory paths where a template or part
	 * could be located and returns the first one which is readable, prioritizing the new convention
	 * over the deprecated one, but maintaining that one for backwards compatibility.
	 *
	 * @param string $template_slug  The slug of the template (i.e. without the file extension).
	 * @param string $template_type  Either `wp_template` or `wp_template_part`.
	 *
	 * @return string|null  The matched path or `null` if no match was found.
	 */
	public static function get_theme_template_path( $template_slug, $template_type = 'wp_template' ) {
		$template_filename      = $template_slug . '.html';
		$possible_templates_dir = 'wp_template' === $template_type ? array(
			self::DIRECTORY_NAMES['TEMPLATES'],
			self::DIRECTORY_NAMES['DEPRECATED_TEMPLATES'],
		) : array(
			self::DIRECTORY_NAMES['TEMPLATE_PARTS'],
			self::DIRECTORY_NAMES['DEPRECATED_TEMPLATE_PARTS'],
		);

		// Combine the possible root directory names with either the template directory
		// or the stylesheet directory for child themes.
		$possible_paths = array_reduce(
			$possible_templates_dir,
			function ( $carry, $item ) use ( $template_filename ) {
				$filepath = DIRECTORY_SEPARATOR . $item . DIRECTORY_SEPARATOR . $template_filename;

				$carry[] = get_stylesheet_directory() . $filepath;
				$carry[] = get_template_directory() . $filepath;

				return $carry;
			},
			array()
		);

		// Return the first matching.
		foreach ( $possible_paths as $path ) {
			if ( is_readable( $path ) ) {
				return $path;
			}
		}

		return null;
	}

	/**
	 * Check if the theme has a template. So we know if to load our own in or not.
	 *
	 * @param string $template_name name of the template file without .html extension e.g. 'single-product'.
	 * @return boolean
	 */
	public static function theme_has_template( $template_name ) {
		return (bool) self::get_theme_template_path( $template_name, 'wp_template' );
	}

	/**
	 * Check if the theme has a template. So we know if to load our own in or not.
	 *
	 * @param string $template_name name of the template file without .html extension e.g. 'single-product'.
	 * @return boolean
	 */
	public static function theme_has_template_part( $template_name ) {
		return (bool) self::get_theme_template_path( $template_name, 'wp_template_part' );
	}

	/**
	 * Checks to see if they are using a compatible version of WP, or if not they have a compatible version of the Gutenberg plugin installed.
	 *
	 * @param string $template_type Optional. Template type: `wp_template` or `wp_template_part`.
	 *                              Default `wp_template`.
	 * @return boolean
	 */
	public static function supports_block_templates( $template_type = 'wp_template' ) {
		if ( 'wp_template_part' === $template_type && ( wp_is_block_theme() || current_theme_supports( 'block-template-parts' ) ) ) {
			return true;
		} elseif ( 'wp_template' === $template_type && wp_is_block_theme() ) {
			return true;
		}
		return false;
	}

	/**
	 * Gets the `archive-product` fallback template stored on the db for a given slug.
	 *
	 * @param string $template_slug Slug to check for fallbacks.
	 * @param array  $db_templates Templates that have already been found on the db.
	 * @return boolean|object
	 */
	public static function get_fallback_template_from_db( $template_slug, $db_templates ) {
		$registered_template = self::get_template( $template_slug );

		if ( $registered_template && isset( $registered_template->fallback_template ) ) {
			foreach ( $db_templates as $template ) {
				if ( $registered_template->fallback_template === $template->slug ) {
					return $template;
				}
			}
		}

		return false;
	}

	/**
	 * Removes templates from the theme or WooCommerce which have the same slug
	 * as template saved in the database with the `woocommerce/woocommerce` theme.
	 * Before WC migrated to the Template Registration API from WordPress, templates
	 * were saved in the database with the `woocommerce/woocommerce` theme instead
	 * of the theme's slug.
	 *
	 * @param \WP_Block_Template[]|\stdClass[] $templates List of templates to run the filter on.
	 *
	 * @return array List of templates with duplicates removed. The customised alternative is preferred over the theme default.
	 */
	public static function remove_templates_with_custom_alternative( $templates ) {

		// Get the slugs of all templates that have been customised and saved in the database.
		$customised_template_slugs = array_column(
			array_filter(
				$templates,
				function ( $template ) {
					// This template has been customised and saved as a post.
					return 'custom' === $template->source && ( self::PLUGIN_SLUG === $template->theme || self::DEPRECATED_PLUGIN_SLUG === $template->theme );
				}
			),
			'slug'
		);

		// Remove theme and WC templates that have the same slug as a customised one.
		return array_values(
			array_filter(
				$templates,
				function ( $template ) use ( $customised_template_slugs ) {
					// This template has been customised and saved as a post, so return it.
					return ! ( 'custom' !== $template->source && in_array( $template->slug, $customised_template_slugs, true ) );
				}
			)
		);
	}

	/**
	 * Removes customized templates that shouldn't be available. That means customized templates based on the
	 * WooCommerce default template when there is a customized template based on the theme template.
	 *
	 * @param \WP_Block_Template[]|\stdClass[] $templates  List of templates to run the filter on.
	 *
	 * @return array Filtered list of templates with only relevant templates available.
	 */
	public static function remove_duplicate_customized_templates( $templates ) {
		$theme_slug = get_stylesheet();

		$customized_theme_template_slugs = array_column(
			array_filter(
				$templates,
				function ( $template ) use ( $theme_slug ) {
					// This template has been customised and saved as a post.
					return 'custom' === $template->source && $theme_slug === $template->theme;
				}
			),
			'slug'
		);

		return array_filter(
			$templates,
			function ( $template ) use ( $theme_slug, $customized_theme_template_slugs ) {
				if ( $template->theme === $theme_slug ) {
					// This is a customized template based on the theme template, so it should be returned.
					return true;
				}
				// Customized from the WooCommerce default template: keep only if there is no customized theme template with same slug.
				if ( 'custom' === $template->source ) {
					return ! in_array( $template->slug, $customized_theme_template_slugs, true );
				}
				return true;
			}
		);
	}

	/**
	 * Returns whether the blockified templates should be used or not.
	 * If the option is not stored on the db, we need to check if the current theme is a block one or not.
	 *
	 * @return boolean
	 */
	public static function should_use_blockified_product_grid_templates() {
		$use_blockified_templates = get_option( Options::WC_BLOCK_USE_BLOCKIFIED_PRODUCT_GRID_BLOCK_AS_TEMPLATE );

		if ( false === $use_blockified_templates ) {
			return wp_is_block_theme();
		}

		return wc_string_to_bool( $use_blockified_templates );
	}

	/**
	 * Determines whether the provided $blocks contains any of the $block_names,
	 * or if they contain a pattern that contains any of the $block_names.
	 *
	 * @param string[]   $block_names Full block types to look for.
	 * @param WP_Block[] $blocks      Array of block objects.
	 * @return bool Whether the content contains the specified block.
	 */
	public static function has_block_including_patterns( $block_names, $blocks ) {
		$flattened_blocks = self::flatten_blocks( $blocks );

		foreach ( $flattened_blocks as &$block ) {
			if ( isset( $block['blockName'] ) && in_array( $block['blockName'], $block_names, true ) ) {
				return true;
			}
			if (
				'core/pattern' === $block['blockName'] &&
				isset( $block['attrs']['slug'] )
			) {
				$registry = WP_Block_Patterns_Registry::get_instance();
				$pattern  = $registry->get_registered( $block['attrs']['slug'] );
				if ( isset( $pattern['content'] ) ) {
					$pattern_blocks = parse_blocks( $pattern['content'] );
					if ( self::has_block_including_patterns( $block_names, $pattern_blocks ) ) {
						return true;
					}
				}
			}
		}

		return false;
	}

	/**
	 * Returns whether the passed `$template` has the legacy template block.
	 *
	 * @param object $template The template object.
	 * @return boolean
	 */
	public static function template_has_legacy_template_block( $template ) {
		if ( has_block( 'woocommerce/legacy-template', $template->content ) ) {
			return true;
		}

		$blocks = parse_blocks( $template->content );

		return self::has_block_including_patterns( array( 'woocommerce/legacy-template' ), $blocks );
	}

	/**
	 * Updates the title, description and area of a template to the correct values and to make them more user-friendly.
	 * For example, instead of:
	 * - Title: `Tag (product_tag)`
	 * - Description: `Displays taxonomy: Tag.`
	 * we display:
	 * - Title: `Products by Tag`
	 * - Description: `Displays products filtered by a tag.`.
	 *
	 * @param WP_Block_Template $template The template object.
	 * @param string            $template_type wp_template or wp_template_part.
	 *
	 * @return WP_Block_Template
	 */
	public static function update_template_data( $template, $template_type ) {
		if ( ! $template ) {
			return $template;
		}
		if ( empty( $template->title ) || $template->title === $template->slug ) {
			$template->title = self::get_block_template_title( $template->slug );
		}
		if ( empty( $template->description ) ) {
			$template->description = self::get_block_template_description( $template->slug );
		}
		if ( empty( $template->area ) || 'uncategorized' === $template->area ) {
			$template->area = self::get_block_template_area( $template->slug, $template_type );
		}

		return $template;
	}

	/**
	 * Gets the templates saved in the database.
	 *
	 * @param array  $slugs An array of slugs to retrieve templates for.
	 * @param string $template_type wp_template or wp_template_part.
	 *
	 * @return \WP_Block_Template[] An array of found templates.
	 */
	public static function get_block_templates_from_db( $slugs = array(), $template_type = 'wp_template' ) {
		static $request_level_cache = array();

		// Optimization note: first query, which optimized for fetching IDs, to minimize temporary/filesort overhead.
		$theme = get_stylesheet();
		$ids   = wp_cache_get( $template_type . '-ids', 'woocommerce_blocks' );
		if ( ! isset( $ids[ $theme ] ) ) {
			$ids = false === $ids ? array() : $ids;
			// 'post__not_in' directs the query to use the `type_status_date` index on the posts table. Omitting this may
			// impact index usage on some systems and result in `Using join buffer (flat, BNL join)`.
			// As the table grows, the number of template type entries stays small, which helps maintain strong query performance.
			$ids[ $theme ] = ( new \WP_Query(
				array(
					'post_type'      => $template_type,
					'post__not_in'   => array( 0 ),
					'posts_per_page' => -1,
					'fields'         => 'ids',
					'tax_query'      => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
						array(
							'taxonomy' => 'wp_theme',
							'field'    => 'name',
							'terms'    => array( self::DEPRECATED_PLUGIN_SLUG, self::PLUGIN_SLUG, $theme ),
						),
					),
				)
			) )->posts;
			// 12 hours is half of default nonce lifetime, if out of sync, templates operating with nonce will keep running.
			wp_cache_set( $template_type . '-ids', $ids, 'woocommerce_blocks', 12 * HOUR_IN_SECONDS );
			$request_level_cache[ $template_type ][ $theme ] = null;
		}

		// Optimization note: second query, which optimized for fetching templates data with caches priming API.
		if ( null === ( $request_level_cache[ $template_type ][ $theme ] ?? null ) ) {
			$request_level_cache[ $template_type ][ $theme ] = array();
			if ( ! empty( $ids[ $theme ] ) ) {
				// Prime caches to reduce future queries.
				_prime_post_caches( $ids[ $theme ], false, false );
				$request_level_cache[ $template_type ][ $theme ] = array_filter( array_map( 'get_post', $ids[ $theme ] ) );
			}
		}

		// Optimization note: populate template objects; optimized for subsequent calls, without spawning consequent SQLs.
		$saved_templates = $request_level_cache[ $template_type ][ $theme ];
		if ( ! empty( $saved_templates ) && is_array( $slugs ) && array() !== $slugs ) {
			$slugs           = array_map( 'sanitize_title', $slugs );
			$saved_templates = array_filter( $saved_templates, fn( $template ) => in_array( $template->post_name, $slugs, true ) );
		}
		if ( ! empty( $saved_templates ) ) {
			$block_templates = array_map( fn( $template ) => self::build_template_result_from_post( $template ), $saved_templates );
			$block_templates = array_values( array_filter( $block_templates, fn( $template ) => $template instanceof \WP_Block_Template ) );

			return $block_templates;
		}

		return array();
	}

	/**
	 * Gets the template part by slug
	 *
	 * @param string $slug The template part slug.
	 *
	 * @return string The template part content.
	 */
	public static function get_template_part( $slug ) {
		$templates_from_db = self::get_block_templates_from_db( array( $slug ), 'wp_template_part' );
		if ( count( $templates_from_db ) > 0 ) {
			$template_slug_to_load = $templates_from_db[0]->theme;
		} else {
			$theme_has_template    = self::theme_has_template_part( $slug );
			$template_slug_to_load = $theme_has_template ? get_stylesheet() : self::PLUGIN_SLUG;
		}
		$template_part = get_block_template( $template_slug_to_load . '//' . $slug, 'wp_template_part' );

		if ( $template_part && ! empty( $template_part->content ) ) {
			return $template_part->content;
		}
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		return file_get_contents( self::get_templates_directory( 'wp_template_part' ) . DIRECTORY_SEPARATOR . $slug . '.html' );
	}
}
PK     \dcE  cE    Utils/CartCheckoutUtils.phpnu [        <?php // phpcs:ignore Generic.PHP.RequireStrictTypes.MissingDeclaration
namespace Automattic\WooCommerce\Blocks\Utils;

use Automattic\Block_Scanner;

/**
 * Class containing utility methods for dealing with the Cart and Checkout blocks.
 */
class CartCheckoutUtils {
	/**
	 * Caches if we're on the cart page.
	 *
	 * @var bool
	 */
	private static $is_cart_page = null;

	/**
	 * Caches if we're on the checkout page.
	 *
	 * @var bool
	 */
	private static $is_checkout_page = null;

	/**
	 * Returns true if the current page is a specific page type (cart or checkout).
	 *
	 * This is determined by looking at the global $post object and comparing it to the post ID defined in settings,
	 * or checking the page contents for a block or shortcode.
	 *
	 * This function cannot be used accurately before the `pre_get_posts` action has been run.
	 *
	 * @param string $page_type The page type to check for.
	 * @return bool|null
	 */
	private static function is_page_type( string $page_type ): ?bool {
		if ( ! did_action( 'pre_get_posts' ) ) {
			return null;
		}

		$page_id = wc_get_page_id( $page_type );

		if ( $page_id && is_page( $page_id ) ) {
			return true;
		}

		// If the is_page check returned false, check the page contents for a cart block or shortcode.
		global $post;

		if ( null === $post ) {
			return null;
		}

		if ( $post instanceof \WP_Post ) {
			return wc_post_content_has_shortcode( 'cart' === $page_type ? 'woocommerce_cart' : 'woocommerce_checkout' ) || self::has_block_variation( 'woocommerce/classic-shortcode', 'shortcode', $page_type, $post->post_content );
		}

		return false;
	}

	/**
	 * Returns true on the cart page.
	 *
	 * @return bool
	 */
	public static function is_cart_page(): bool {
		if ( null === self::$is_cart_page ) {
			self::$is_cart_page = self::is_page_type( 'cart' );
		}
		return true === self::$is_cart_page;
	}

	/**
	 * Returns true on the checkout page.
	 *
	 * @return bool
	 */
	public static function is_checkout_page(): bool {
		if ( null === self::$is_checkout_page ) {
			self::$is_checkout_page = self::is_page_type( 'checkout' );
		}
		return true === self::$is_checkout_page;
	}

	/**
	 * Returns true if shipping methods exist in the store. Excludes local pickup and only counts enabled shipping methods.
	 *
	 * @return bool true if shipping methods exist.
	 */
	public static function shipping_methods_exist() {
		// Local pickup is included with legacy shipping methods since they do not support shipping zones.
		$local_pickup_count = count(
			array_filter(
				WC()->shipping()->get_shipping_methods(),
				function ( $method ) {
					return isset( $method->enabled ) && 'yes' === $method->enabled && ! $method->supports( 'shipping-zones' ) && $method->supports( 'local-pickup' );
				}
			)
		);

		$shipping_methods_count = wc_get_shipping_method_count( true, true ) - $local_pickup_count;
		return $shipping_methods_count > 0;
	}

	/**
	 * Check if the post content contains a block with a specific attribute value.
	 *
	 * @param string $block_id The block ID to check for.
	 * @param string $attribute The attribute to check.
	 * @param string $value The value to check for.
	 * @param string $post_content The post content to check.
	 * @return boolean
	 */
	public static function has_block_variation( $block_id, $attribute, $value, $post_content ) {
		if ( ! $post_content ) {
			return false;
		}

		$scanner = Block_Scanner::create( $post_content );
		if ( ! $scanner ) {
			return false;
		}

		while ( $scanner->next_delimiter() ) {
			if ( ! $scanner->opens_block( $block_id ) ) {
				continue;
			}

			$attrs = $scanner->allocate_and_return_parsed_attributes();

			if ( isset( $attrs[ $attribute ] ) && $value === $attrs[ $attribute ] ) {
				return true;
			}

			// `Cart` is default for `woocommerce/classic-shortcode` so it will be empty in the block attributes.
			if ( 'woocommerce/classic-shortcode' === $block_id &&
				'shortcode' === $attribute &&
				'cart' === $value &&
				! isset( $attrs['shortcode'] ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Checks if the default cart page is using the Cart block.
	 *
	 * @return bool true if the WC cart page is using the Cart block.
	 */
	public static function is_cart_block_default() {
		if ( wp_is_block_theme() ) {
			// Ignore the pages and check the templates.
			$templates_from_db = BlockTemplateUtils::get_block_templates_from_db( array( 'cart' ), 'wp_template' );
			foreach ( $templates_from_db as $template ) {
				if ( has_block( 'woocommerce/cart', $template->content ) ) {
					return true;
				}
			}
		}
		$cart_page_id = wc_get_page_id( 'cart' );
		return $cart_page_id && has_block( 'woocommerce/cart', $cart_page_id );
	}

	/**
	 * Checks if the default checkout page is using the Checkout block.
	 *
	 * @return bool true if the WC checkout page is using the Checkout block.
	 */
	public static function is_checkout_block_default() {
		if ( wp_is_block_theme() ) {
			// Ignore the pages and check the templates.
			$templates_from_db = BlockTemplateUtils::get_block_templates_from_db( array( 'checkout' ), 'wp_template' );
			foreach ( $templates_from_db as $template ) {
				if ( has_block( 'woocommerce/checkout', $template->content ) ) {
					return true;
				}
			}
		}
		$checkout_page_id = wc_get_page_id( 'checkout' );
		return $checkout_page_id && has_block( 'woocommerce/checkout', $checkout_page_id );
	}

	/**
	 * Migrate checkout block field visibility attributes to settings when using the checkout block.
	 *
	 * This migration routine is called if the options (woocommerce_checkout_phone_field, woocommerce_checkout_company_field,
	 * woocommerce_checkout_address_2_field) are not set. They are not set by default; they were orignally set by the
	 * customizer interface of the legacy shortcode based checkout.
	 *
	 * Once migration is initiated, the settings will be updated and will not trigger this routine again.
	 *
	 * Note: The block only stores non-default attributes. Not all attributes will be present.
	 *
	 * e.g. `{"showCompanyField":true,"requireCompanyField":true,"showApartmentField":false,"className":"wc-block-checkout"}`
	 *
	 * If the attributes are missing, we assume default values are needed.
	 */
	protected static function migrate_checkout_block_field_visibility_attributes() {
		// Before migrating attributes, migrate the "default" options checkout block uses into the settings.
		update_option( 'woocommerce_checkout_phone_field', 'optional' );
		update_option( 'woocommerce_checkout_company_field', 'hidden' );
		update_option( 'woocommerce_checkout_address_2_field', 'optional' );

		// Parse the block from the checkout page.
		$checkout_blocks = \WC_Blocks_Utils::get_blocks_from_page( 'woocommerce/checkout', 'checkout' );

		if ( empty( $checkout_blocks ) || ! isset( $checkout_blocks[0]['attrs'] ) ) {
			return;
		}

		// Combine actual attributes with default values.
		$block_attributes = wp_parse_args(
			$checkout_blocks[0]['attrs'],
			array(
				'showPhoneField'        => true,
				'requirePhoneField'     => false,
				'showCompanyField'      => false,
				'requireCompanyField'   => false,
				'showApartmentField'    => true,
				'requireApartmentField' => false,
			)
		);

		if ( $block_attributes['showPhoneField'] ) {
			update_option( 'woocommerce_checkout_phone_field', $block_attributes['requirePhoneField'] ? 'required' : 'optional' );
		} else {
			update_option( 'woocommerce_checkout_phone_field', 'hidden' );
		}

		if ( $block_attributes['showCompanyField'] ) {
			update_option( 'woocommerce_checkout_company_field', $block_attributes['requireCompanyField'] ? 'required' : 'optional' );
		} else {
			update_option( 'woocommerce_checkout_company_field', 'hidden' );
		}

		if ( $block_attributes['showApartmentField'] ) {
			update_option( 'woocommerce_checkout_address_2_field', $block_attributes['requireApartmentField'] ? 'required' : 'optional' );
		} else {
			update_option( 'woocommerce_checkout_address_2_field', 'hidden' );
		}
	}

	/**
	 * Get the default visibility for the address_2 field.
	 *
	 * @return string
	 */
	public static function get_company_field_visibility() {
		$option_value = get_option( 'woocommerce_checkout_company_field' );

		if ( $option_value ) {
			return $option_value;
		}

		if ( self::is_checkout_block_default() ) {
			self::migrate_checkout_block_field_visibility_attributes();
			return get_option( 'woocommerce_checkout_company_field', 'hidden' );
		}

		return 'optional';
	}

	/**
	 * Get the default visibility for the address_2 field.
	 *
	 * @return string
	 */
	public static function get_address_2_field_visibility() {
		$option_value = get_option( 'woocommerce_checkout_address_2_field' );

		if ( $option_value ) {
			return $option_value;
		}

		if ( self::is_checkout_block_default() ) {
			self::migrate_checkout_block_field_visibility_attributes();
			return get_option( 'woocommerce_checkout_address_2_field', 'optional' );
		}

		return 'optional';
	}

	/**
	 * Get the default visibility for the address_2 field.
	 *
	 * @return string
	 */
	public static function get_phone_field_visibility() {
		$option_value = get_option( 'woocommerce_checkout_phone_field' );

		if ( $option_value ) {
			return $option_value;
		}

		if ( self::is_checkout_block_default() ) {
			self::migrate_checkout_block_field_visibility_attributes();
			return get_option( 'woocommerce_checkout_phone_field', 'optional' );
		}

		return 'required';
	}

	/**
	 * Checks if the template overriding the page loads the page content or not.
	 * Templates by default load the page content, but if that block is deleted the content can get out of sync with the one presented in the page editor.
	 *
	 * @param string $block The block to check.
	 *
	 * @return bool true if the template has out of sync content.
	 */
	public static function is_overriden_by_custom_template_content( string $block ): bool {

		$block = str_replace( 'woocommerce/', '', $block );

		if ( wp_is_block_theme() ) {
			$templates_from_db = BlockTemplateUtils::get_block_templates_from_db( array( 'page-' . $block ) );
			foreach ( $templates_from_db as $template ) {
				if ( ! has_block( 'woocommerce/page-content-wrapper', $template->content ) ) {
					// Return true if the template does not load the page content via the  woocommerce/page-content-wrapper block.
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Gets country codes, names, states, and locale information.
	 *
	 * @return array
	 */
	public static function get_country_data() {
		$billing_countries  = WC()->countries->get_allowed_countries();
		$shipping_countries = WC()->countries->get_shipping_countries();
		$country_states     = wc()->countries->get_states();
		$all_countries      = self::deep_sort_with_accents( array_unique( array_merge( $billing_countries, $shipping_countries ) ) );
		$country_locales    = array_map(
			function ( $locale ) {
				foreach ( $locale as $field => $field_data ) {
					if ( isset( $field_data['priority'] ) ) {
						$locale[ $field ]['index'] = $field_data['priority'];
						unset( $locale[ $field ]['priority'] );
					}
					if ( isset( $field_data['class'] ) ) {
						unset( $locale[ $field ]['class'] );
					}
				}
				return $locale;
			},
			WC()->countries->get_country_locale()
		);

		$country_data = array();

		foreach ( array_keys( $all_countries ) as $country_code ) {
			$country_data[ $country_code ] = array(
				'allowBilling'  => isset( $billing_countries[ $country_code ] ),
				'allowShipping' => isset( $shipping_countries[ $country_code ] ),
				'states'        => $country_states[ $country_code ] ?? array(),
				'locale'        => $country_locales[ $country_code ] ?? array(),
			);
		}

		return $country_data;
	}

	/**
	 * Removes accents from an array of values, sorts by the values, then returns the original array values sorted.
	 *
	 * @param array $sort_array Array of values to sort.
	 * @return array Sorted array.
	 */
	protected static function deep_sort_with_accents( $sort_array ) {
		if ( ! is_array( $sort_array ) || empty( $sort_array ) ) {
			return $sort_array;
		}

		$array_without_accents = array_map(
			function ( $value ) {
				return is_array( $value )
					? self::deep_sort_with_accents( $value )
					: remove_accents( wc_strtolower( html_entity_decode( $value ) ) );
			},
			$sort_array
		);

		asort( $array_without_accents );
		return array_replace( $array_without_accents, $sort_array );
	}

	/**
	 * Retrieves formatted shipping zones from WooCommerce.
	 *
	 * @return array An array of formatted shipping zones.
	 */
	public static function get_shipping_zones() {
		$shipping_zones             = \WC_Shipping_Zones::get_zones();
		$formatted_shipping_zones   = array_reduce(
			$shipping_zones,
			function ( $acc, $zone ) {
				$acc[] = array(
					'id'          => $zone['id'],
					'title'       => $zone['zone_name'],
					'description' => $zone['formatted_zone_location'],
				);
				return $acc;
			},
			array()
		);
		$formatted_shipping_zones[] = array(
			'id'          => 0,
			'title'       => __( 'International', 'woocommerce' ),
			'description' => __( 'Locations outside all other zones', 'woocommerce' ),
		);
		return $formatted_shipping_zones;
	}

	/**
	 * Recursively search the checkout block to find the express checkout block and
	 * get the button style attributes using the parse_blocks function.
	 *
	 * @param array  $blocks Blocks to search.
	 * @param string $cart_or_checkout The block type to check.
	 *
	 * @return array Block attributes.
	 */
	public static function find_express_checkout_attributes_in_parsed_blocks( $blocks, $cart_or_checkout ) {
		$express_block_name = 'woocommerce/' . $cart_or_checkout . '-express-payment-block';
		foreach ( $blocks as $block ) {
			if ( ! empty( $block['blockName'] ) && $express_block_name === $block['blockName'] && ! empty( $block['attrs'] ) ) {
				return $block['attrs'];
			}

			if ( ! empty( $block['innerBlocks'] ) ) {
				$answer = self::find_express_checkout_attributes_in_parsed_blocks( $block['innerBlocks'], $cart_or_checkout );
				if ( $answer ) {
					return $answer;
				}
			}
		}
	}

	/**
	 * Recursively search the checkout block to find the express checkout block and
	 * get the button style attributes
	 *
	 * @param string|array $post_content The post content.
	 * @param string       $cart_or_checkout The block type to check.
	 *
	 * @return array|null Block attributes, if present and valid, otherwise `null`.
	 */
	public static function find_express_checkout_attributes( $post_content, $cart_or_checkout ) {
		if ( is_array( $post_content ) ) {
			// If an array is passed, assume it's already been parsed with parse_blocks,
			// use the old method, and show a deprecation warning.
			wc_deprecated_argument(
				'post_content',
				'10.3.0',
				'Passing parsed blocks as an array in $post_content is deprecated. Please pass the post content as a string.'
			);
			return self::find_express_checkout_attributes_in_parsed_blocks( $post_content, $cart_or_checkout );
		}

		$express_block_name = 'woocommerce/' . $cart_or_checkout . '-express-payment-block';

		$scanner = Block_Scanner::create( $post_content );

		while ( $scanner->next_delimiter() ) {
			if ( $scanner->opens_block( $express_block_name ) ) {
				return $scanner->allocate_and_return_parsed_attributes();
			}
		}

		return null;
	}

	/**
	 * Given an array of blocks, find the express payment block and update its attributes.
	 *
	 * @param array  $blocks Blocks to search.
	 * @param string $cart_or_checkout The block type to check.
	 * @param array  $updated_attrs The new attributes to set.
	 */
	public static function update_blocks_with_new_attrs( &$blocks, $cart_or_checkout, $updated_attrs ) {
		$express_block_name = 'woocommerce/' . $cart_or_checkout . '-express-payment-block';
		foreach ( $blocks as $key => &$block ) {
			if ( ! empty( $block['blockName'] ) && $express_block_name === $block['blockName'] ) {
				$blocks[ $key ]['attrs'] = $updated_attrs;
			}

			if ( ! empty( $block['innerBlocks'] ) ) {
				self::update_blocks_with_new_attrs( $block['innerBlocks'], $cart_or_checkout, $updated_attrs );
			}
		}
	}

	/**
	 * Check if the cart page is defined.
	 *
	 * @return bool True if the cart page is defined, false otherwise.
	 */
	public static function has_cart_page() {
		return wc_get_page_permalink( 'cart', -1 ) !== -1;
	}

	/**
	 * Get product IDs from a user's persistent cart.
	 *
	 * This method retrieves product IDs stored in the user's persistent cart meta.
	 * It can be used for abandoned cart emails, cart-based product collections,
	 * and other scenarios where cart products need to be retrieved for a user.
	 *
	 * @param int|null    $user_id    The user ID. If not provided, will attempt to look up by email.
	 * @param string|null $user_email The user email. Used to lookup user if ID not provided.
	 * @return array<int> Array of product IDs from the user's cart, or empty array if none found.
	 */
	public static function get_cart_product_ids_for_user( ?int $user_id, ?string $user_email ) {
		if ( empty( $user_id ) && ! empty( $user_email ) ) {
			$user = get_user_by( 'email', $user_email );
			if ( $user ) {
				$user_id = $user->ID;
			}
		}

		if ( empty( $user_id ) ) {
			return array();
		}

		$cart_meta = get_user_meta( $user_id, '_woocommerce_persistent_cart_' . get_current_blog_id(), true );

		if ( empty( $cart_meta ) || ! is_array( $cart_meta ) || empty( $cart_meta['cart'] ) ) {
			return array();
		}

		return array_values(
			array_unique(
				array_filter(
					array_map(
						function ( $cart_item ) {
							return isset( $cart_item['product_id'] ) ? intval( $cart_item['product_id'] ) : 0;
						},
						$cart_meta['cart']
					)
				)
			)
		);
	}
}
PK     \kU  U    Utils/BlocksWpQuery.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Utils;

use WP_Query;

/**
 * BlocksWpQuery query.
 *
 * Wrapper for WP Query with additional helper methods.
 * Allows query args to be set and parsed without doing running it, so that a cache can be used.
 *
 * @deprecated 2.5.0
 */
class BlocksWpQuery extends WP_Query {
	/**
	 * Constructor.
	 *
	 * Sets up the WordPress query, if parameter is not empty.
	 *
	 * Unlike the constructor in WP_Query, this does not RUN the query.
	 *
	 * @param string|array $query URL query string or array of vars.
	 */
	public function __construct( $query = '' ) {
		if ( ! empty( $query ) ) {
			$this->init();
			$this->query      = wp_parse_args( $query );
			$this->query_vars = $this->query;
			$this->parse_query_vars();
		}
	}

	/**
	 * Get cached posts, if a cache exists.
	 *
	 * A hash is generated using the array of query_vars. If doing custom queries via filters such as posts_where
	 * (where the SQL query is manipulated directly) you can still ensure there is a unique hash by injecting custom
	 * query vars via the parse_query filter. For example:
	 *
	 *      add_filter( 'parse_query', function( $wp_query ) {
	 *           $wp_query->query_vars['my_custom_query_var'] = true;
	 *      } );
	 *
	 * Doing so won't have any negative effect on the query itself, and it will cause the hash to change.
	 *
	 * @param string $transient_version Transient version to allow for invalidation.
	 * @return WP_Post[]|int[] Array of post objects or post IDs.
	 */
	public function get_cached_posts( $transient_version = '' ) {
		$hash            = md5( wp_json_encode( $this->query_vars ) );
		$transient_name  = 'wc_blocks_query_' . $hash;
		$transient_value = get_transient( $transient_name );

		if ( isset( $transient_value, $transient_value['version'], $transient_value['value'] ) && $transient_value['version'] === $transient_version ) {
			return $transient_value['value'];
		}

		$results = $this->get_posts();

		set_transient(
			$transient_name,
			array(
				'version' => $transient_version,
				'value'   => $results,
			),
			DAY_IN_SECONDS * 30
		);

		return $results;
	}
}
PK     \}$      Utils/ProductGalleryUtils.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Utils;

/**
 * Utility methods used for the Product Gallery block.
 * {@internal This class and its methods are not intended for public use.}
 */
class ProductGalleryUtils {
	/**
	 * Get all image IDs for the product.
	 *
	 * @param \WC_Product $product The product object.
	 * @return array An array of image IDs.
	 */
	public static function get_all_image_ids( $product ) {
		if ( ! $product instanceof \WC_Product ) {
			wc_doing_it_wrong( __FUNCTION__, __( 'Invalid product object.', 'woocommerce' ), '9.8.0' );
			return array();
		}

		$gallery_image_ids           = self::get_product_gallery_image_ids( $product );
		$product_variation_image_ids = self::get_product_variation_image_ids( $product );
		$all_image_ids               = array_values( array_map( 'intval', array_unique( array_merge( $gallery_image_ids, $product_variation_image_ids ) ) ) );

		if ( empty( $all_image_ids ) ) {
			return array();
		}

		return $all_image_ids;
	}

	/**
	 * Get the product gallery image data.
	 *
	 * @param \WC_Product $product The product object to retrieve the gallery images for.
	 * @param string      $size The size of the image to retrieve.
	 * @return array An array of image data for the product gallery.
	 */
	public static function get_product_gallery_image_data( $product, $size ) {
		$all_image_ids = self::get_all_image_ids( $product );
		return self::get_image_src_data( $all_image_ids, $size, $product->get_title() );
	}

	/**
	 * Get the product gallery image count.
	 *
	 * @param \WC_Product $product The product object to retrieve the gallery images for.
	 * @return int The number of images in the product gallery.
	 */
	public static function get_product_gallery_image_count( $product ) {
		$all_image_ids = self::get_all_image_ids( $product );
		return count( $all_image_ids );
	}

	/**
	 * Get the image source data.
	 *
	 * @param array  $image_ids The image IDs to retrieve the source data for.
	 * @param string $size The size of the image to retrieve.
	 * @param string $product_title The title of the product used for alt fallback.
	 * @return array An array of image source data.
	 */
	public static function get_image_src_data( $image_ids, $size, $product_title = '' ) {
		$image_src_data = array();

		foreach ( $image_ids as $index => $image_id ) {
			if ( 0 === $image_id ) {
				// Handle placeholder image.
				$image_src_data[] = array(
					'id'     => 0,
					'src'    => wc_placeholder_img_src(),
					'srcset' => '',
					'sizes'  => '',
					'alt'    => '',
				);
				continue;
			}

			// Get the image source.
			$full_src = wp_get_attachment_image_src( $image_id, $size );

			// Get srcset and sizes.
			$srcset = wp_get_attachment_image_srcset( $image_id, $size );
			$sizes  = wp_get_attachment_image_sizes( $image_id, $size );
			$alt    = get_post_meta( $image_id, '_wp_attachment_image_alt', true );

			$image_src_data[] = array(
				'id'     => $image_id,
				'src'    => $full_src ? $full_src[0] : '',
				'srcset' => $srcset ? $srcset : '',
				'sizes'  => $sizes ? $sizes : '',
				'alt'    => $alt ? $alt : sprintf(
					/* translators: 1: Product title 2: Image number */
					__( '%1$s - Image %2$d', 'woocommerce' ),
					$product_title,
					$index + 1
				),
			);
		}

		return $image_src_data;
	}

	/**
	 * Get the product variation image data.
	 *
	 * @param \WC_Product $product The product object to retrieve the variation images for.
	 * @return array An array of image data for the product variation images.
	 */
	public static function get_product_variation_image_ids( $product ) {
		$variation_image_ids = array();

		if ( ! $product instanceof \WC_Product ) {
			wc_doing_it_wrong( __FUNCTION__, __( 'Invalid product object.', 'woocommerce' ), '9.8.0' );
			return $variation_image_ids;
		}

		try {
			if ( $product->is_type( 'variable' ) ) {
				$variations = $product->get_children();
				if ( ! empty( $variations ) ) {
					_prime_post_caches( $variations );
				}
				foreach ( $variations as $variation_id ) {
					$variation = wc_get_product( $variation_id );
					if ( $variation ) {
						$variation_image_id = $variation->get_image_id();
						if ( ! empty( $variation_image_id ) && ! in_array( strval( $variation_image_id ), $variation_image_ids, true ) ) {
							$variation_image_ids[] = strval( $variation_image_id );
						}
					}
				}
			}
		} catch ( \Exception $e ) {
			// Log the error but continue execution.
			error_log( 'Error getting product variation image IDs: ' . $e->getMessage() );
		}

		return $variation_image_ids;
	}

	/**
	 * Get the product gallery image IDs.
	 *
	 * @param \WC_Product $product The product object to retrieve the gallery images for.
	 * @return array An array of unique image IDs for the product gallery.
	 */
	public static function get_product_gallery_image_ids( $product ) {
		$product_image_ids = array();

		// Main product featured image.
		$featured_image_id = $product->get_image_id();

		if ( $featured_image_id ) {
			$product_image_ids[] = $featured_image_id;
		}

		// All other product gallery images.
		$product_gallery_image_ids = $product->get_gallery_image_ids();

		if ( ! empty( $product_gallery_image_ids ) ) {
			// We don't want to show the same image twice, so we have to remove the featured image from the gallery if it's there.
			$product_image_ids = array_unique( array_merge( $product_image_ids, $product_gallery_image_ids ) );
		}

		// If the Product image is not set and there are no gallery images, we need to set it to a placeholder image.
		if ( ! $featured_image_id && empty( $product_gallery_image_ids ) ) {
			$product_image_ids[] = '0';
		}

		foreach ( $product_image_ids as $key => $image_id ) {
			$product_image_ids[ $key ] = strval( $image_id );
		}

		// Reindex array.
		$product_image_ids = array_values( $product_image_ids );

		return $product_image_ids;
	}
}
PK     \дB      Utils/BlockHooksTrait.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Utils;

/**
 * BlockHooksTrait
 *
 * Shared functionality for using the Block Hooks API with WooCommerce Blocks.
 */
trait BlockHooksTrait {
	/**
	 * Callback for `hooked_block_types` to auto-inject the mini-cart block into headers after navigation.
	 *
	 * @param array                             $hooked_blocks An array of block slugs hooked into a given context.
	 * @param string                            $position      Position of the block insertion point.
	 * @param string                            $anchor_block  The block acting as the anchor for the inserted block.
	 * @param array|\WP_Post|\WP_Block_Template $context       Where the block is embedded.
	 * @since 8.5.0
	 * @return array An array of block slugs hooked into a given context.
	 */
	public function register_hooked_block( $hooked_blocks, $position, $anchor_block, $context ) {
		// If the block has no hook placements, return early.
		if ( ! isset( $this->hooked_block_placements ) || empty( $this->hooked_block_placements ) ) {
			return $hooked_blocks;
		}

		// Cache the block hooks version.
		static $block_hooks_version = null;
		if ( defined( 'WP_RUN_CORE_TESTS' ) || is_null( $block_hooks_version ) ) {
			$block_hooks_version = get_option( 'woocommerce_hooked_blocks_version' );
		}

		// If block hooks are disabled or the version is not set, return early.
		if ( 'no' === $block_hooks_version || false === $block_hooks_version ) {
			return $hooked_blocks;
		}

		// Valid placements are those that have no version specified,
		// or have a version that is less than or equal to version specified in the woocommerce_hooked_blocks_version option.
		$valid_placements = array_filter(
			$this->hooked_block_placements,
			function ( $placement ) use ( $block_hooks_version ) {
				$placement_version = isset( $placement['version'] ) ? $placement['version'] : null;
				return is_null( $placement_version ) || ! is_null( $placement_version ) && version_compare( $block_hooks_version, $placement_version, '>=' );
			}
		);

		if ( $context && ! empty( $valid_placements ) ) {
			foreach ( $valid_placements as $placement ) {

				if ( $placement['position'] === $position && $placement['anchor'] === $anchor_block ) {
					// If an area has been specified for this placement.
					if (
						isset( $placement['area'] ) &&
						! $this->has_block_in_content( $context )
						&& $this->is_target_area( $context, $placement['area'] )
					) {
						$hooked_blocks[] = $this->namespace . '/' . $this->block_name;
					}

					// If no area has been specified for this placement just insert the block.
					// This is likely to be the case when we're inserting into the navigation block
					// where we don't have a specific area to target.
					if ( ! isset( $placement['area'] ) ) {
						$hooked_blocks[] = $this->namespace . '/' . $this->block_name;
					}

					// If a callback has been specified for this placement, call it. This allows for custom block-specific logic to be run.
					$callback = isset( $placement['callback'] ) && is_callable( array( $this, $placement['callback'] ) ) ? array( $this, $placement['callback'] ) : null;
					if ( null !== $callback ) {
						$modified_hooked_blocks = $callback( $hooked_blocks, $position, $anchor_block, $context );
						if ( is_array( $modified_hooked_blocks ) ) {
							$hooked_blocks = $modified_hooked_blocks;
						}
					}
				}
			}
		}

		return $hooked_blocks;
	}

	/**
	 * Checks if the provided context contains a the block already.
	 *
	 * @param array|\WP_Block_Template $context Where the block is embedded.
	 * @return boolean
	 */
	protected function has_block_in_content( $context ) {
		$content = $this->get_context_content( $context );
		return strpos( $content, 'wp:' . $this->namespace . '/' . $this->block_name ) !== false;
	}

	/**
	 * Given a provided context, returns the content of the context.
	 *
	 * @param array|\WP_Post|\WP_Block_Template $context Where the block is embedded.
	 * @since 8.5.0
	 * @return string
	 */
	protected function get_context_content( $context ) {
		$content = is_array( $context ) && isset( $context['content'] ) ? $context['content'] : '';
		$content = '' === $content && $context instanceof \WP_Block_Template ? $context->content : $content;
		$content = '' === $content && $context instanceof \WP_Post ? $context->post_content : $content;
		return $content;
	}

	/**
	 * Given a provided context, returns whether the context refers to header content.
	 *
	 * @param array|\WP_Post|\WP_Block_Template $context Where the block is embedded.
	 * @param string                            $area The area to check against before inserting.
	 * @since 8.5.0
	 * @return boolean
	 */
	protected function is_template_part_or_pattern( $context, $area ) {
		$is_pattern       = is_array( $context ) &&
		(
			( isset( $context['blockTypes'] ) && in_array( 'core/template-part/' . $area, $context['blockTypes'], true ) ) ||
			( isset( $context['categories'] ) && in_array( $area, $context['categories'], true ) )
		);
		$is_template_part = $context instanceof \WP_Block_Template && $area === $context->area;
		return ( $is_pattern || $is_template_part );
	}

	/**
	 * Given a provided context, returns whether the context refers to the target area and isn't marked as excluded.
	 *
	 * @param array|\WP_Post|\WP_Block_Template $context the context to check.
	 * @param string                            $area The area to check against before inserting.
	 * @since 8.5.0
	 * @return boolean
	 */
	protected function is_target_area( $context, $area ) {
		if ( $this->is_template_part_or_pattern( $context, $area ) && ! $this->pattern_is_excluded( $context ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Returns whether the pattern is excluded or not
	 *
	 * @since 8.5.0
	 *
	 * @param array|\WP_Block_Template $context Where the block is embedded.
	 * @return boolean
	 */
	protected function pattern_is_excluded( $context ) {
		/**
		 * A list of pattern slugs to exclude from auto-insert (useful when there are patterns that have a very specific location for the block)
		 * Note: The patterns that are currently excluded are the ones that don't work well with the mini-cart block or customer-account block.
		 *
		 * @since 8.5.0
		 */
		$pattern_exclude_list = apply_filters(
			'woocommerce_hooked_blocks_pattern_exclude_list',
			array_unique( array_merge( isset( $this->hooked_block_excluded_patterns ) ? $this->hooked_block_excluded_patterns : array(), array( 'twentytwentytwo/header-centered-logo', 'twentytwentytwo/header-stacked' ) ) )
		);

		$pattern_slug = is_array( $context ) && isset( $context['slug'] ) ? $context['slug'] : '';
		if ( ! $pattern_slug ) {
			/**
			 * Woo patterns have a slug property in $context, but core/theme patterns dont.
			 * In that case, we fallback to the name property, as they're the same.
			 */
			$pattern_slug = is_array( $context ) && isset( $context['name'] ) ? $context['name'] : '';
		}
		return in_array( $pattern_slug, $pattern_exclude_list, true );
	}
}
PK     \'9o}\  }\    Utils/StyleAttributesUtils.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Utils;

/**
 * StyleAttributesUtils class used for getting class and style from attributes.
 */
class StyleAttributesUtils {

	// Empty style array.
	const EMPTY_STYLE = [
		'class' => '',
		'style' => '',
		'value' => '',
	];

	/**
	 * If color value is in preset format, convert it to a CSS var. Else return same value
	 * For example:
	 * "var:preset|color|pale-pink" -> "var(--wp--preset--color--pale-pink)"
	 * "#98b66e" -> "#98b66e"
	 *
	 * @param string $color_value value to be processed.
	 *
	 * @return (string)
	 */
	public static function get_color_value( $color_value ) {
		if ( is_string( $color_value ) && strpos( $color_value, 'var:preset|color|' ) !== false ) {
			$color_value = str_replace( 'var:preset|color|', '', $color_value );
			return sprintf( 'var(--wp--preset--color--%s)', $color_value );
		}

		return $color_value;
	}

	/**
	 * Get CSS value for color preset.
	 *
	 * @param string $preset_name Preset name.
	 *
	 * @return string CSS value for color preset.
	 */
	public static function get_preset_value( $preset_name ) {
		return "var(--wp--preset--color--$preset_name)";
	}

	/**
	 * Get CSS value for shadow preset. Returns the same value if it's not a preset.
	 *
	 * @param string $shadow_name Shadow name.
	 *
	 * @return string CSS value for shadow preset.
	 */
	public static function get_shadow_value( $shadow_name ) {
		if ( is_string( $shadow_name ) && strpos( $shadow_name, 'var:preset|shadow|' ) !== false ) {
			$shadow_name = str_replace( 'var:preset|shadow|', '', $shadow_name );
			return "var(--wp--preset--shadow--{$shadow_name})";
		}

		return $shadow_name;
	}

	/**
	 * If spacing value is in preset format, convert it to a CSS var. Else return same value
	 * For example:
	 * "var:preset|spacing|50" -> "var(--wp--preset--spacing--50)"
	 * "50px" -> "50px"
	 *
	 * @param string $spacing_value value to be processed.
	 *
	 * @return (string)
	 */
	public static function get_spacing_value( $spacing_value ) {
		// Used following code as reference: https://github.com/WordPress/gutenberg/blob/cff6d70d6ff5a26e212958623dc3130569f95685/lib/block-supports/layout.php/#L219-L225.
		if ( is_string( $spacing_value ) && strpos( $spacing_value, 'var:preset|spacing|' ) !== false ) {
			$spacing_value = str_replace( 'var:preset|spacing|', '', $spacing_value );
			return sprintf( 'var(--wp--preset--spacing--%s)', $spacing_value );
		}

		return $spacing_value;
	}

	/**
	 * Get class and style for align from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_align_class_and_style( $attributes ) {
		$align_attribute = $attributes['align'] ?? null;

		if ( 'wide' === $align_attribute ) {
			return array(
				'class' => 'alignwide',
				'style' => null,
			);
		}

		if ( 'full' === $align_attribute ) {
			return array(
				'class' => 'alignfull',
				'style' => null,
			);
		}

		if ( 'left' === $align_attribute ) {
			return array(
				'class' => 'alignleft',
				'style' => null,
			);
		}

		if ( 'right' === $align_attribute ) {
			return array(
				'class' => 'alignright',
				'style' => null,
			);
		}

		if ( 'center' === $align_attribute ) {
			return array(
				'class' => 'aligncenter',
				'style' => null,
			);
		}

		return self::EMPTY_STYLE;
	}

	/**
	 * Get class and style for background-color from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_background_color_class_and_style( $attributes ) {
		$gradient                = $attributes['gradient'] ?? null;
		$background_color        = $attributes['backgroundColor'] ?? '';
		$custom_background_color = $attributes['style']['color']['background'] ?? '';
		$classes                 = [ $gradient ];
		$styles                  = [];
		$value                   = null;

		if ( $background_color || $custom_background_color || $gradient ) {
			$classes[] = 'has-background';
		}

		if ( $background_color ) {
			$classes[] = sprintf( 'has-%s-background-color', $background_color );
			$value     = self::get_preset_value( $background_color );
		}

		if ( $custom_background_color ) {
			$styles[] = sprintf( 'background-color: %s;', $custom_background_color );
			$value    = $custom_background_color;
		}

		if ( $gradient ) {
			$classes[] = sprintf( 'has-%s-gradient-background', $gradient );
		}

		return array(
			'class' => self::join_styles( $classes ),
			'style' => self::join_styles( $styles ),
			'value' => $value,
		);
	}

	/**
	 * Join classes and styles while removing duplicates and null values.
	 *
	 * @param array $rules Array of classes or styles.
	 * @return array
	 */
	protected static function join_styles( $rules ) {
		return implode( ' ', array_unique( array_filter( $rules ) ) );
	}

	/**
	 * Get class and style for border-color from attributes.
	 *
	 * Data passed to this function is not always consistent. It can be:
	 * Linked - preset color: $attributes['borderColor'] => 'luminous-vivid-orange'.
	 * Linked - custom color: $attributes['style']['border']['color'] => '#681228'.
	 * Unlinked - preset color: $attributes['style']['border']['top']['color'] => 'var:preset|color|luminous-vivid-orange'
	 * Unlinked - custom color: $attributes['style']['border']['top']['color'] => '#681228'.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_border_color_class_and_style( $attributes ) {
		$border_color_linked_preset = $attributes['borderColor'] ?? '';
		$border_color_linked_custom = $attributes['style']['border']['color'] ?? '';
		$custom_border              = $attributes['style']['border'] ?? '';

		$class = '';
		$style = '';
		$value = '';

		if ( $border_color_linked_preset ) {
			// Linked preset color.
			$class = sprintf( 'has-border-color has-%s-border-color', $border_color_linked_preset );
			$value = self::get_preset_value( $border_color_linked_preset );
			$style = 'border-color:' . $value . ';';
		} elseif ( $border_color_linked_custom ) {
			// Linked custom color.
			$style .= 'border-color:' . $border_color_linked_custom . ';';
			$value  = $border_color_linked_custom;
		} elseif ( is_array( $custom_border ) ) {
			// Unlinked.
			foreach ( $custom_border as $border_color_key => $border_color_value ) {
				if ( is_array( $border_color_value ) && array_key_exists( 'color', ( $border_color_value ) ) ) {
					$style .= 'border-' . $border_color_key . '-color:' . self::get_color_value( $border_color_value['color'] ) . ';';
				}
			}
		}

		if ( ! $class && ! $style ) {
			return self::EMPTY_STYLE;
		}

		return array(
			'class' => $class,
			'style' => $style,
			'value' => $value,
		);
	}

	/**
	 * Get class and style for border-radius from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_border_radius_class_and_style( $attributes ) {
		$custom_border_radius = $attributes['style']['border']['radius'] ?? '';

		if ( '' === $custom_border_radius ) {
			return self::EMPTY_STYLE;
		}

		$style = '';

		if ( is_string( $custom_border_radius ) ) {
			// Linked sides.
			$style = 'border-radius:' . $custom_border_radius . ';';
		} else {
			// Unlinked sides.
			$border_radius = array();

			$border_radius['border-top-left-radius']     = $custom_border_radius['topLeft'] ?? '';
			$border_radius['border-top-right-radius']    = $custom_border_radius['topRight'] ?? '';
			$border_radius['border-bottom-right-radius'] = $custom_border_radius['bottomRight'] ?? '';
			$border_radius['border-bottom-left-radius']  = $custom_border_radius['bottomLeft'] ?? '';

			foreach ( $border_radius as $border_radius_side => $border_radius_value ) {
				if ( '' !== $border_radius_value ) {
					$style .= $border_radius_side . ':' . $border_radius_value . ';';
				}
			}
		}

		return array(
			'class' => null,
			'style' => $style,
		);
	}

	/**
	 * Get class and style for border width from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_border_width_class_and_style( $attributes ) {
		$custom_border = $attributes['style']['border'] ?? '';

		if ( '' === $custom_border ) {
			return self::EMPTY_STYLE;
		}

		$style = '';

		if ( array_key_exists( 'width', ( $custom_border ) ) && ! empty( $custom_border['width'] ) ) {
			// Linked sides.
			$style = 'border-width:' . $custom_border['width'] . ';';
		} else {
			// Unlinked sides.
			foreach ( $custom_border as $border_width_side => $border_width_value ) {
				if ( isset( $border_width_value['width'] ) ) {
					$style .= 'border-' . $border_width_side . '-width:' . $border_width_value['width'] . ';';
				}
			}
		}

		return array(
			'class' => null,
			'style' => $style,
		);
	}

	/**
	 * Get class and style for border width from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_border_style_class_and_style( $attributes ) {
		$custom_border = $attributes['style']['border'] ?? '';

		if ( '' === $custom_border ) {
			return self::EMPTY_STYLE;
		}

		$style = '';

		if ( array_key_exists( 'style', ( $custom_border ) ) && ! empty( $custom_border['style'] ) ) {
			$style = 'border-style:' . $custom_border['style'] . ';';
		} else {
			foreach ( $custom_border as $side => $value ) {
				if ( isset( $value['style'] ) ) {
					$style .= 'border-' . $side . '-style:' . $value['style'] . ';';
				}
			}
		}

		return array(
			'class' => null,
			'style' => $style,
		);
	}

	/**
	 * Get space-separated classes from block attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @param array $properties Properties to get classes from.
	 *
	 * @return string Space-separated classes.
	 */
	public static function get_classes_by_attributes( $attributes, $properties = array() ) {
		$classes_and_styles = self::get_classes_and_styles_by_attributes( $attributes, $properties );

		return $classes_and_styles['classes'];
	}

	/**
	 * Get class and style for font-family from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_font_family_class_and_style( $attributes ) {

		$font_family = $attributes['fontFamily'] ?? '';

		if ( $font_family ) {
			return array(
				'class' => sprintf( 'has-%s-font-family', $font_family ),
				'style' => null,
			);
		}
		return self::EMPTY_STYLE;
	}

	/**
	 * Get class and style for font-size from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_font_size_class_and_style( $attributes ) {

		$font_size = $attributes['fontSize'] ?? '';

		$custom_font_size = $attributes['style']['typography']['fontSize'] ?? '';

		if ( ! $font_size && '' === $custom_font_size ) {
			return self::EMPTY_STYLE;
		}

		if ( $font_size ) {
			return array(
				'class' => sprintf( 'has-font-size has-%s-font-size', $font_size ),
				'style' => null,
			);
		} elseif ( '' !== $custom_font_size ) {
			return array(
				'class' => null,
				'style' => sprintf( 'font-size: %s;', $custom_font_size ),
			);
		}

		return self::EMPTY_STYLE;
	}

	/**
	 * Get class and style for font-style from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_font_style_class_and_style( $attributes ) {

		$custom_font_style = $attributes['style']['typography']['fontStyle'] ?? '';

		if ( '' !== $custom_font_style ) {
			return array(
				'class' => null,
				'style' => sprintf( 'font-style: %s;', $custom_font_style ),
			);
		}
		return self::EMPTY_STYLE;
	}

	/**
	 * Get class and style for font-weight from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_font_weight_class_and_style( $attributes ) {

		$custom_font_weight = $attributes['style']['typography']['fontWeight'] ?? '';

		if ( '' !== $custom_font_weight ) {
			return array(
				'class' => null,
				'style' => sprintf( 'font-weight: %s;', $custom_font_weight ),
			);
		}
		return self::EMPTY_STYLE;
	}

	/**
	 * Get class and style for letter-spacing from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_letter_spacing_class_and_style( $attributes ) {

		$custom_letter_spacing = $attributes['style']['typography']['letterSpacing'] ?? '';

		if ( '' !== $custom_letter_spacing ) {
			return array(
				'class' => null,
				'style' => sprintf( 'letter-spacing: %s;', $custom_letter_spacing ),
			);
		}
		return self::EMPTY_STYLE;
	}

	/**
	 * Get class and style for line height from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_line_height_class_and_style( $attributes ) {

		$line_height = $attributes['style']['typography']['lineHeight'] ?? '';

		if ( ! $line_height ) {
			return self::EMPTY_STYLE;
		}

		return array(
			'class' => null,
			'style' => sprintf( 'line-height: %s;', $line_height ),
		);
	}

	/**
	 * Get a value from an array based on a path e.g style.elements.link
	 *
	 * @param array  $array Target array.
	 * @param string $path Path joined by delimiter.
	 * @param string $delimiter Chosen delimiter defaults to ".".
	 * @return mixed
	 */
	protected static function array_get_value_by_path( array &$array, $path, $delimiter = '.' ) {
		$array_path = explode( $delimiter, $path );
		$ref        = &$array;

		foreach ( $array_path as $key ) {
			if ( is_array( $ref ) && array_key_exists( $key, $ref ) ) {
				$ref = &$ref[ $key ];
			} else {
				return null;
			}
		}
		return $ref;
	}

	/**
	 * Get class and style for link-color from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_link_color_class_and_style( $attributes ) {
		$link_color = self::array_get_value_by_path( $attributes, 'style.elements.link.color.text' );

		if ( empty( $link_color ) ) {
			return self::EMPTY_STYLE;
		}

		// If the link color is selected from the theme color picker, the value of $link_color is var:preset|color|slug.
		// If the link color is selected from the core color picker, the value of $link_color is an hex value.
		// When the link color is a string var:preset|color|slug we parsed it for get the slug, otherwise we use the hex value.
		if ( strstr( $link_color, '|' ) ) {
			$link_color_parts = explode( '|', $link_color );
			$link_color       = self::get_preset_value( end( $link_color_parts ) );
		}

		return array(
			'class' => 'has-link-color',
			'style' => sprintf( 'color: %s;', $link_color ),
			'value' => $link_color,
		);
	}

	/**
	 * Get class and style for link-hover-color from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_link_hover_color_class_and_style( $attributes ) {
		$link_color = self::array_get_value_by_path( $attributes, 'style.elements.link.:hover.color.text' );

		if ( empty( $link_color ) ) {
			return self::EMPTY_STYLE;
		}

		// If the link color is selected from the theme color picker, the value of $link_color is var:preset|color|slug.
		// If the link color is selected from the core color picker, the value of $link_color is an hex value.
		// When the link color is a string var:preset|color|slug we parsed it for get the slug, otherwise we use the hex value.
		if ( strstr( $link_color, '|' ) ) {
			$link_color_parts = explode( '|', $link_color );
			$link_color       = self::get_preset_value( end( $link_color_parts ) );
		}

		return array(
			'class' => 'has-link-color',
			'style' => sprintf( 'color: %s;', $link_color ),
			'value' => $link_color,
		);
	}

	/**
	 * Get class and style for margin from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_margin_class_and_style( $attributes ) {
		$margin = $attributes['style']['spacing']['margin'] ?? null;

		if ( ! $margin ) {
			return self::EMPTY_STYLE;
		}

		$spacing_values_css = '';

		foreach ( $margin as $margin_side => $margin_value ) {
			$spacing_values_css .= 'margin-' . $margin_side . ':' . self::get_spacing_value( $margin_value ) . ';';
		}

		return array(
			'class' => null,
			'style' => $spacing_values_css,
		);
	}

	/**
	 * Get class and style for padding from attributes.
	 *
	 * @param array $attributes Block attributes.
	 *
	 * @return array
	 */
	public static function get_padding_class_and_style( $attributes ) {
		$padding = $attributes['style']['spacing']['padding'] ?? null;

		if ( ! $padding ) {
			return self::EMPTY_STYLE;
		}

		$spacing_values_css = '';

		foreach ( $padding as $padding_side => $padding_value ) {
			$spacing_values_css .= 'padding-' . $padding_side . ':' . self::get_spacing_value( $padding_value ) . ';';
		}

		return array(
			'class' => null,
			'style' => $spacing_values_css,
		);
	}

	/**
	 * Get class and style for shadow from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_shadow_class_and_style( $attributes ) {
		$shadow = $attributes['style']['shadow'] ?? null;

		if ( ! $shadow ) {
			return self::EMPTY_STYLE;
		}

		return array(
			'class' => null,
			'style' => sprintf( 'box-shadow: %s;', self::get_shadow_value( $shadow ) ),
		);
	}

	/**
	 * Get space-separated style rules from block attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @param array $properties Properties to get styles from.
	 *
	 * @return string Space-separated style rules.
	 */
	public static function get_styles_by_attributes( $attributes, $properties = array() ) {
		$classes_and_styles = self::get_classes_and_styles_by_attributes( $attributes, $properties );

		return $classes_and_styles['styles'];
	}

	/**
	 * Get class and style for text align from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_text_align_class_and_style( $attributes ) {
		// Check if the text align is set in the attributes manually (legacy) or in the global styles.
		$text_align = $attributes['textAlign'] ?? $attributes['style']['typography']['textAlign'] ?? null;

		if ( $text_align ) {
			return array(
				'class' => 'has-text-align-' . $text_align,
				'style' => null,
			);
		}
		return self::EMPTY_STYLE;
	}

	/**
	 * Get class and style for text-color from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_text_color_class_and_style( $attributes ) {

		$text_color = $attributes['textColor'] ?? '';

		$custom_text_color = $attributes['style']['color']['text'] ?? '';

		if ( ! $text_color && ! $custom_text_color ) {
			return self::EMPTY_STYLE;
		}

		if ( $text_color ) {
			return array(
				'class' => sprintf( 'has-text-color has-%s-color', $text_color ),
				'style' => null,
				'value' => self::get_preset_value( $text_color ),
			);
		} elseif ( $custom_text_color ) {
			return array(
				'class' => null,
				'style' => sprintf( 'color: %s;', $custom_text_color ),
				'value' => $custom_text_color,
			);
		}

		return self::EMPTY_STYLE;
	}

	/**
	 * Get class and style for text-decoration from attributes.
	 *
	 * @param array $attributes Block attributes.
	 *
	 * @return array
	 */
	public static function get_text_decoration_class_and_style( $attributes ) {

		$custom_text_decoration = $attributes['style']['typography']['textDecoration'] ?? '';

		if ( '' !== $custom_text_decoration ) {
			return array(
				'class' => null,
				'style' => sprintf( 'text-decoration: %s;', $custom_text_decoration ),
			);
		}

		return self::EMPTY_STYLE;
	}

	/**
	 * Get class and style for text-transform from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_text_transform_class_and_style( $attributes ) {

		$custom_text_transform = $attributes['style']['typography']['textTransform'] ?? '';

		if ( '' !== $custom_text_transform ) {
			return array(
				'class' => null,
				'style' => sprintf( 'text-transform: %s;', $custom_text_transform ),
			);
		}
		return self::EMPTY_STYLE;
	}

	/**
	 * Get extra CSS classes from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return array
	 */
	public static function get_classes_from_attributes( $attributes ) {

		$extra_css_classes = $attributes['className'] ?? '';

		if ( '' !== $extra_css_classes ) {
			return array(
				'class' => esc_attr( $extra_css_classes ),
				'style' => null,
			);
		}
		return self::EMPTY_STYLE;
	}

	/**
	 * Get classes and styles from attributes.
	 *
	 * Excludes link_color and link_hover_color since those should not apply to the container.
	 *
	 * @param array $attributes Block attributes.
	 * @param array $properties Properties to get classes/styles from.
	 * @param array $exclude Properties to exclude.
	 * @return array
	 */
	public static function get_classes_and_styles_by_attributes( $attributes, $properties = array(), $exclude = array() ) {
		$classes_and_styles = array(
			'align'            => self::get_align_class_and_style( $attributes ),
			'background_color' => self::get_background_color_class_and_style( $attributes ),
			'border_color'     => self::get_border_color_class_and_style( $attributes ),
			'border_radius'    => self::get_border_radius_class_and_style( $attributes ),
			'border_width'     => self::get_border_width_class_and_style( $attributes ),
			'border_style'     => self::get_border_style_class_and_style( $attributes ),
			'font_family'      => self::get_font_family_class_and_style( $attributes ),
			'font_size'        => self::get_font_size_class_and_style( $attributes ),
			'font_style'       => self::get_font_style_class_and_style( $attributes ),
			'font_weight'      => self::get_font_weight_class_and_style( $attributes ),
			'letter_spacing'   => self::get_letter_spacing_class_and_style( $attributes ),
			'line_height'      => self::get_line_height_class_and_style( $attributes ),
			'margin'           => self::get_margin_class_and_style( $attributes ),
			'padding'          => self::get_padding_class_and_style( $attributes ),
			'shadow'           => self::get_shadow_class_and_style( $attributes ),
			'text_align'       => self::get_text_align_class_and_style( $attributes ),
			'text_color'       => self::get_text_color_class_and_style( $attributes ),
			'text_decoration'  => self::get_text_decoration_class_and_style( $attributes ),
			'text_transform'   => self::get_text_transform_class_and_style( $attributes ),
			'extra_classes'    => self::get_classes_from_attributes( $attributes ),
		);

		if ( ! empty( $properties ) ) {
			foreach ( $classes_and_styles as $key => $value ) {
				if ( ! in_array( $key, $properties, true ) ) {
					unset( $classes_and_styles[ $key ] );
				}
			}
		}

		if ( ! empty( $exclude ) ) {
			foreach ( $classes_and_styles as $key => $value ) {
				if ( in_array( $key, $exclude, true ) ) {
					unset( $classes_and_styles[ $key ] );
				}
			}
		}

		$classes_and_styles = array_filter( $classes_and_styles );

		$classes = array_map(
			function ( $item ) {
				return $item['class'];
			},
			$classes_and_styles
		);

		$styles = array_map(
			function ( $item ) {
				return $item['style'];
			},
			// Exclude link color styles from parent to avoid conflict with text color.
			array_diff_key(
				$classes_and_styles,
				array_flip( array( 'link_color' ) )
			)
		);

		$classes = array_filter( $classes );
		$styles  = array_filter( $styles );

		return array(
			'classes' => implode( ' ', $classes ),
			'styles'  => implode( ' ', $styles ),
		);
	}
}
PK     \5
  
    Utils/MiniCartUtils.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Utils;

/**
 * Utility methods used for the Mini Cart block.
 */
class MiniCartUtils {
	/**
	 * Migrate attributes to color panel component format.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 * @return array Reformatted attributes that are compatible with the color panel component.
	 */
	public static function migrate_attributes_to_color_panel( $attributes ) {
		if ( isset( $attributes['priceColorValue'] ) && ! isset( $attributes['priceColor'] ) ) {
			$attributes['priceColor'] = array(
				'color' => $attributes['priceColorValue'],
			);
			unset( $attributes['priceColorValue'] );
		}

		if ( isset( $attributes['iconColorValue'] ) && ! isset( $attributes['iconColor'] ) ) {
			$attributes['iconColor'] = array(
				'color' => $attributes['iconColorValue'],
			);
			unset( $attributes['iconColorValue'] );
		}

		if ( isset( $attributes['productCountColorValue'] ) && ! isset( $attributes['productCountColor'] ) ) {
			$attributes['productCountColor'] = array(
				'color' => $attributes['productCountColorValue'],
			);
			unset( $attributes['productCountColorValue'] );
		}

		return $attributes;
	}

	/**
	 * Get the SVG icon for the mini cart.
	 *
	 * @param string $icon_name The name of the icon.
	 * @param string $icon_color The color of the icon.
	 * @return string The SVG icon.
	 */
	public static function get_svg_icon( $icon_name, $icon_color = 'currentColor' ) {
		// Default "Cart" icon.
		$icon = '<svg xmlns="http://www.w3.org/2000/svg" fill="' . esc_attr( $icon_color ) . '" class="wc-block-mini-cart__icon" viewBox="0 0 32 32"><circle cx="12.667" cy="24.667" r="2"/><circle cx="23.333" cy="24.667" r="2"/><path fill-rule="evenodd" d="M9.285 10.036a1 1 0 0 1 .776-.37h15.272a1 1 0 0 1 .99 1.142l-1.333 9.333A1 1 0 0 1 24 21H12a1 1 0 0 1-.98-.797L9.083 10.87a1 1 0 0 1 .203-.834m2.005 1.63L12.814 19h10.319l1.047-7.333z" clip-rule="evenodd"/><path fill-rule="evenodd" d="M5.667 6.667a1 1 0 0 1 1-1h2.666a1 1 0 0 1 .984.82l.727 4a1 1 0 1 1-1.967.359l-.578-3.18H6.667a1 1 0 0 1-1-1" clip-rule="evenodd"/></svg>';

		if ( isset( $icon_name ) ) {
			if ( 'bag' === $icon_name ) {
				$icon = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" class="wc-block-mini-cart__icon" viewBox="0 0 32 32"><path fill="' . esc_attr( $icon_color ) . '" fill-rule="evenodd" d="M12.444 14.222a.89.89 0 0 1 .89.89 2.667 2.667 0 0 0 5.333 0 .889.889 0 1 1 1.777 0 4.444 4.444 0 1 1-8.888 0c0-.492.398-.89.888-.89M11.24 6.683a1 1 0 0 1 .76-.35h8a1 1 0 0 1 .76.35l4 4.666A1 1 0 0 1 24 13H8a1 1 0 0 1-.76-1.65zm1.22 1.65L10.174 11h11.652L19.54 8.333z" clip-rule="evenodd"/><path fill="' . esc_attr( $icon_color ) . '" fill-rule="evenodd" d="M7 12a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v13.333a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1zm2 1v11.333h14V13z" clip-rule="evenodd"/></svg>';
			} elseif ( 'bag-alt' === $icon_name ) {
				$icon = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" class="wc-block-mini-cart__icon" viewBox="0 0 32 32"><path fill="' . esc_attr( $icon_color ) . '" fill-rule="evenodd" d="M19.556 12.333a.89.89 0 0 1-.89-.889c0-.707-.28-3.385-.78-3.885a2.667 2.667 0 0 0-3.772 0c-.5.5-.78 3.178-.78 3.885a.889.889 0 1 1-1.778 0c0-1.178.468-4.309 1.301-5.142a4.445 4.445 0 0 1 6.286 0c.833.833 1.302 3.964 1.302 5.142a.89.89 0 0 1-.89.89" clip-rule="evenodd"/><path fill="' . esc_attr( $icon_color ) . '" fill-rule="evenodd" d="M7.5 12a1 1 0 0 1 1-1h15a1 1 0 0 1 1 1v13.333a1 1 0 0 1-1 1h-15a1 1 0 0 1-1-1zm2 1v11.333h13V13z" clip-rule="evenodd"/></svg>';
			}
		}

		return $icon;
	}
}
PK     \P    "  Utils/ProductAvailabilityUtils.phpnu [        <?php
declare(strict_types=1);
namespace Automattic\WooCommerce\Blocks\Utils;

use Automattic\WooCommerce\Blocks\Templates\ProductStockIndicator;
use Automattic\WooCommerce\Enums\ProductType;

/**
 * Utility functions for product availability.
 */
class ProductAvailabilityUtils {

	/**
	 * Get product availability information.
	 *
	 * @param \WC_Product $product Product object.
	 * @return string[] The product availability class and text.
	 */
	public static function get_product_availability( $product ) {
		$product_availability = array(
			'availability' => '',
			'class'        => '',
		);

		if ( ! $product ) {
			return $product_availability;
		}

		$product_availability = $product->get_availability();

		// If the product is a variable product, make sure at least one of its
		// variations is purchasable.
		if (
			isset( $product_availability['class'] ) &&
			( 'in-stock' === $product_availability['class'] || 'available-on-backorder' === $product_availability['class'] ) &&
			ProductType::VARIABLE === $product->get_type()
		) {
			if ( ! $product->has_purchasable_variations() ) {
				$product_availability['availability'] = __( 'Out of stock', 'woocommerce' );
				$product_availability['class']        = 'out-of-stock';
			}
		}

		return $product_availability;
	}
}
PK     \(N!      Utils/ProductDataUtils.phpnu [        <?php
declare(strict_types=1);
namespace Automattic\WooCommerce\Blocks\Utils;

/**
 * Utility class to get product data consumable by the blocks.
 *
 * @internal
 */
class ProductDataUtils {
	/**
	 * Get the product data.
	 *
	 * @param \WC_Product $product Product object.
	 * @return array The product data.
	 */
	public static function get_product_data( \WC_Product $product ) {
		return array(
			'price_html' => $product->get_price_html(),
		);
	}
}
PK     \:T      Utils/Utils.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Utils;

/**
 * Utils class
 */
class Utils {

	/**
	 * Compare the current WordPress version with a given version. It's a wrapper around `version-compare`
	 * that additionally takes into account the suffix (like `-RC1`).
	 * For example: version 6.3 is considered lower than 6.3-RC2, so you can do
	 * wp_version_compare( '6.3', '>=' ) and that will return true for 6.3-RC2.
	 *
	 * @param string      $version The version to compare against.
	 * @param string|null $operator Optional. The comparison operator. Defaults to null.
	 * @return bool|int Returns true if the current WordPress version satisfies the comparison, false otherwise.
	 */
	public static function wp_version_compare( $version, $operator = null ) {
		$current_wp_version = get_bloginfo( 'version' );
		if ( preg_match( '/^([0-9]+\.[0-9]+)/', $current_wp_version, $matches ) ) {
			$current_wp_version = (float) $matches[1];
		}

		// Replace non-alphanumeric characters with a dot.
		$current_wp_version = preg_replace( '/[^0-9a-zA-Z\.]+/i', '.', $current_wp_version );
		$version            = preg_replace( '/[^0-9a-zA-Z\.]+/i', '.', $version );

		return version_compare( $current_wp_version, $version, $operator );
	}
}
PK     \n  n    Utils/BlocksSharedState.phpnu [        <?php

declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\Utils;

use InvalidArgumentException;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\Hydration;

/**
 * Manages the registration of interactivity config and state that is commonly shared by WooCommerce blocks.
 * Initialization only happens on the first call to load_store_config.
 *
 * This is a private API and may change in future versions.
 */
class BlocksSharedState {

	/**
	 * The consent statement for using private APIs of this class.
	 *
	 * @var string
	 */
	private static string $consent_statement = 'I acknowledge that using private APIs means my theme or plugin will inevitably break in the next version of WooCommerce';

	/**
	 * The namespace for interactivity config and state.
	 *
	 * @var string
	 */
	private static string $settings_namespace = 'woocommerce';

	/**
	 * Whether the core config has been registered.
	 *
	 * @var bool
	 */
	private static bool $core_config_registered = false;

	/**
	 * Cart state.
	 *
	 * @var array|null
	 */
	private static ?array $blocks_shared_cart_state = null;

	/**
	 * Prevent caching on certain pages.
	 *
	 * @return void
	 */
	private static function prevent_cache(): void {
		\WC_Cache_Helper::set_nocache_constants();
		nocache_headers();
	}

	/**
	 * Check that the consent statement was passed.
	 *
	 * @param string $consent_statement The consent statement string.
	 * @return true
	 * @throws InvalidArgumentException If the statement does not match.
	 */
	private static function check_consent( string $consent_statement ): bool {
		if ( $consent_statement !== self::$consent_statement ) {
			throw new InvalidArgumentException( 'This method cannot be called without consenting the API may change.' );
		}

		return true;
	}

	/**
	 * Load store config (currency, locale, core data) into interactivity config.
	 *
	 * @param string $consent_statement The consent statement string.
	 * @return void
	 * @throws InvalidArgumentException If consent statement doesn't match.
	 */
	public static function load_store_config( string $consent_statement ): void {
		self::check_consent( $consent_statement );

		if ( self::$core_config_registered ) {
			return;
		}

		self::$core_config_registered = true;

		wp_interactivity_config( self::$settings_namespace, self::get_currency_data() );
		wp_interactivity_config( self::$settings_namespace, self::get_locale_data() );
	}

	/**
	 * Load cart state into interactivity state.
	 *
	 * @param string $consent_statement The consent statement string.
	 * @return void
	 * @throws InvalidArgumentException If consent statement doesn't match.
	 */
	public static function load_cart_state( string $consent_statement ): void {
		self::check_consent( $consent_statement );

		if ( null === self::$blocks_shared_cart_state ) {
			$cart_exists       = isset( WC()->cart );
			$cart_has_contents = $cart_exists && ! WC()->cart->is_empty();
			if ( $cart_exists ) {
				$cart_response                  = Package::container()->get( Hydration::class )->get_rest_api_response_data( '/wc/store/v1/cart' );
				self::$blocks_shared_cart_state = $cart_response['body'] ?? array();
			} else {
				self::$blocks_shared_cart_state = array();
			}

			if ( $cart_has_contents ) {
				self::prevent_cache();
			}

			wp_interactivity_config(
				self::$settings_namespace,
				array( 'nonOptimisticProperties' => self::get_non_optimistic_properties() )
			);

			wp_interactivity_state(
				self::$settings_namespace,
				array(
					'cart'     => self::$blocks_shared_cart_state,
					'noticeId' => '',
					'restUrl'  => get_rest_url(),
				)
			);
		}
	}

	/**
	 * Get currency data to include in settings.
	 *
	 * @return array
	 */
	private static function get_currency_data(): array {
		$currency = get_woocommerce_currency();

		return array(
			'currency' => array(
				'code'              => $currency,
				'precision'         => wc_get_price_decimals(),
				'symbol'            => html_entity_decode( get_woocommerce_currency_symbol( $currency ) ),
				'symbolPosition'    => get_option( 'woocommerce_currency_pos' ),
				'decimalSeparator'  => wc_get_price_decimal_separator(),
				'thousandSeparator' => wc_get_price_thousand_separator(),
				'priceFormat'       => html_entity_decode( get_woocommerce_price_format() ),
			),
		);
	}

	/**
	 * Get locale data to include in settings.
	 *
	 * @return array
	 */
	private static function get_locale_data(): array {
		global $wp_locale;

		return array(
			'locale' => array(
				'siteLocale'    => get_locale(),
				'userLocale'    => get_user_locale(),
				'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ),
			),
		);
	}

	/**
	 * Get cart properties that cannot use optimistic UI on the frontend.
	 *
	 * Detects whether third-party code has registered callbacks on filters that
	 * modify cart property values. When callbacks are present, the corresponding
	 * property must use the server-computed value instead of a client-side
	 * optimistic computation.
	 *
	 * `@return` string[] List of cart property paths (dot-delimited) that cannot be optimistic.
	 *
	 * @return string[] List of cart property paths (dot-delimited) that cannot be optimistic.
	 */
	private static function get_non_optimistic_properties(): array {
		$properties = array();

		if ( has_filter( 'woocommerce_cart_contents_count' ) ) {
			$properties[] = 'cart.items_count';
		}

		return $properties;
	}

	/**
	 * Load placeholder image into interactivity config.
	 *
	 * @param string $consent_statement The consent statement string.
	 * @return void
	 * @throws InvalidArgumentException If consent statement doesn't match.
	 */
	public static function load_placeholder_image( string $consent_statement ): void {
		self::check_consent( $consent_statement );

		wp_interactivity_config(
			self::$settings_namespace,
			array( 'placeholderImgSrc' => wc_placeholder_img_src() )
		);
	}

	/**
	 * Get cart errors formatted as notices for the store-notices interactivity store.
	 *
	 * Returns errors from the hydrated cart state in the format expected by
	 * the store-notices store context.
	 *
	 * @param string $consent_statement The consent statement string.
	 * @return array Array of notices with id, notice, type, and dismissible keys.
	 * @throws InvalidArgumentException If consent statement doesn't match.
	 */
	public static function get_cart_error_notices( string $consent_statement ): array {
		self::check_consent( $consent_statement );

		// Ensure cart state is loaded so this method works independently.
		if ( null === self::$blocks_shared_cart_state ) {
			self::load_cart_state( $consent_statement );
		}

		$errors  = self::$blocks_shared_cart_state['errors'] ?? array();
		$notices = array();

		foreach ( $errors as $error ) {
			$notices[] = array(
				'id'          => wp_unique_id( 'store-notice-' ),
				'notice'      => $error['message'] ?? '',
				'type'        => 'error',
				'dismissible' => true,
			);
		}

		return $notices;
	}
}
PK     \_`0  0    BlockTemplatesRegistry.phpnu [        <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\Blocks;

use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
use Automattic\WooCommerce\Blocks\Templates\AbstractTemplate;
use Automattic\WooCommerce\Blocks\Templates\AbstractTemplatePart;
use Automattic\WooCommerce\Blocks\Templates\MiniCartTemplate;
use Automattic\WooCommerce\Blocks\Templates\CartTemplate;
use Automattic\WooCommerce\Blocks\Templates\CheckoutTemplate;
use Automattic\WooCommerce\Blocks\Templates\CheckoutHeaderTemplate;
use Automattic\WooCommerce\Blocks\Templates\ComingSoonTemplate;
use Automattic\WooCommerce\Blocks\Templates\OrderConfirmationTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductBrandTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductCatalogTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductCategoryTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductTagTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplate;
use Automattic\WooCommerce\Blocks\Templates\SimpleProductAddToCartWithOptionsTemplate;
use Automattic\WooCommerce\Blocks\Templates\ExternalProductAddToCartWithOptionsTemplate;
use Automattic\WooCommerce\Blocks\Templates\VariableProductAddToCartWithOptionsTemplate;
use Automattic\WooCommerce\Blocks\Templates\GroupedProductAddToCartWithOptionsTemplate;
use Automattic\WooCommerce\Enums\ProductType;

/**
 * BlockTemplatesRegistry class.
 *
 * @internal
 */
class BlockTemplatesRegistry {

	/**
	 * The array of registered templates.
	 *
	 * @var AbstractTemplate[]|AbstractTemplatePart[]
	 */
	private $templates = array();

	/**
	 * Initialization method.
	 */
	public function init() {
		if ( BlockTemplateUtils::supports_block_templates( 'wp_template' ) ) {
			$templates = array(
				ProductCatalogTemplate::SLUG       => new ProductCatalogTemplate(),
				ProductCategoryTemplate::SLUG      => new ProductCategoryTemplate(),
				ProductTagTemplate::SLUG           => new ProductTagTemplate(),
				ProductAttributeTemplate::SLUG     => new ProductAttributeTemplate(),
				ProductBrandTemplate::SLUG         => new ProductBrandTemplate(),
				ProductSearchResultsTemplate::SLUG => new ProductSearchResultsTemplate(),
				CartTemplate::SLUG                 => new CartTemplate(),
				CheckoutTemplate::SLUG             => new CheckoutTemplate(),
				OrderConfirmationTemplate::SLUG    => new OrderConfirmationTemplate(),
				SingleProductTemplate::SLUG        => new SingleProductTemplate(),
			);
		} else {
			$templates = array();
		}
		if ( Features::is_enabled( 'launch-your-store' ) ) {
			$templates[ ComingSoonTemplate::SLUG ] = new ComingSoonTemplate();
		}
		if ( BlockTemplateUtils::supports_block_templates( 'wp_template_part' ) ) {
			$template_parts = array(
				MiniCartTemplate::SLUG       => new MiniCartTemplate(),
				CheckoutHeaderTemplate::SLUG => new CheckoutHeaderTemplate(),
			);
			if ( wp_is_block_theme() ) {
				$product_types = wc_get_product_types();
				if ( count( $product_types ) > 0 ) {
					add_filter( 'default_wp_template_part_areas', array( $this, 'register_add_to_cart_with_options_template_part_area' ), 10, 1 );
					if ( array_key_exists( ProductType::SIMPLE, $product_types ) ) {
						$template_parts[ SimpleProductAddToCartWithOptionsTemplate::SLUG ] = new SimpleProductAddToCartWithOptionsTemplate();
					}
					if ( array_key_exists( ProductType::EXTERNAL, $product_types ) ) {
						$template_parts[ ExternalProductAddToCartWithOptionsTemplate::SLUG ] = new ExternalProductAddToCartWithOptionsTemplate();
					}
					if ( array_key_exists( ProductType::VARIABLE, $product_types ) ) {
						$template_parts[ VariableProductAddToCartWithOptionsTemplate::SLUG ] = new VariableProductAddToCartWithOptionsTemplate();
					}
					if ( array_key_exists( ProductType::GROUPED, $product_types ) ) {
						$template_parts[ GroupedProductAddToCartWithOptionsTemplate::SLUG ] = new GroupedProductAddToCartWithOptionsTemplate();
					}
				}
			}
		} else {
			$template_parts = array();
		}

		// Init all templates.
		foreach ( $templates as $template ) {
			$template->init();

			// Taxonomy templates are registered automatically by WordPress and
			// are made available through the Add Template menu.
			if ( ! $template->is_taxonomy_template ) {
				$directory          = BlockTemplateUtils::get_templates_directory( 'wp_template' );
				$template_file_path = $directory . '/' . $template::SLUG . '.html';
				register_block_template(
					'woocommerce//' . $template::SLUG,
					array(
						'title'       => $template->get_template_title(),
						'description' => $template->get_template_description(),
						// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
						'content'     => file_get_contents( $template_file_path ),
					)
				);
			}
		}

		foreach ( $template_parts as $template_part ) {
			$template_part->init();
		}

		$this->templates = array_merge( $templates, $template_parts );
	}

	/**
	 * Add Add to Cart + Options to the default template part areas.
	 *
	 * @param array $default_area_definitions An array of supported area objects.
	 * @return array The supported template part areas including the Add to Cart + Options one.
	 */
	public function register_add_to_cart_with_options_template_part_area( $default_area_definitions ) {
		$add_to_cart_with_options_template_part_area = array(
			'area'        => 'add-to-cart-with-options',
			'label'       => __( 'Add to Cart + Options', 'woocommerce' ),
			'description' => __( 'The Add to Cart + Options templates allow defining a different layout for each product type.', 'woocommerce' ),
			'icon'        => 'add-to-cart-with-options',
			'area_tag'    => 'add-to-cart-with-options',
		);
		return array_merge( $default_area_definitions, array( $add_to_cart_with_options_template_part_area ) );
	}

	/**
	 * Returns the template matching the slug
	 *
	 * @param string $template_slug Slug of the template to retrieve.
	 *
	 * @return AbstractTemplate|AbstractTemplatePart|null
	 */
	public function get_template( $template_slug ) {
		if ( array_key_exists( $template_slug, $this->templates ) ) {
			$registered_template = $this->templates[ $template_slug ];
			return $registered_template;
		}
		return null;
	}
}
PK     \ILYeG  G    InboxNotifications.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks;

use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\Notes;

/**
 * A class used to display inbox messages to merchants in the WooCommerce Admin dashboard.
 *
 * @package Automattic\WooCommerce\Blocks
 * @since x.x.x
 */
class InboxNotifications {

	const SURFACE_CART_CHECKOUT_NOTE_NAME = 'surface_cart_checkout';

	/**
	 * Deletes the note.
	 */
	public static function delete_surface_cart_checkout_blocks_notification() {
		Notes::delete_notes_with_name( self::SURFACE_CART_CHECKOUT_NOTE_NAME );
	}
}
PK     \s        Patterns/AIPatterns.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Patterns;

/**
 * AIPatterns class.
 *
 * @internal
 * @deprecated This class can't be removed due https://github.com/woocommerce/woocommerce/issues/52311.
 */
class AIPatterns {}
PK     \[_  _    Patterns/PTKClient.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Patterns;

use WP_Error;

/**
 * PatternsToolkit class.
 *
 * @internal
 */
class PTKClient {
	/**
	 *  The Patterns Toolkit API URL
	 */
	const PATTERNS_TOOLKIT_URL = 'https://public-api.wordpress.com/rest/v1/ptk/patterns/';

	/**
	 * The schema for the patterns toolkit.
	 *
	 * @var array
	 */
	private $schema;

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->schema = [
			'type'  => 'array',
			'items' => [
				'type'       => 'object',
				'properties' => [
					'ID'         => [
						'type'     => 'integer',
						'required' => true,
					],
					'site_id'    => [
						'type'     => 'integer',
						'required' => true,
					],
					'title'      => [
						'type'     => 'string',
						'required' => true,
					],
					'name'       => [
						'type'     => 'string',
						'required' => true,
					],
					'html'       => [
						'type'     => 'string',
						'required' => true,
					],
					'categories' => [
						'type'                 => 'object',
						'additionalProperties' => [
							'type'       => 'object',
							'properties' => [
								'slug'        => [
									'type'     => 'string',
									'required' => true,
								],
								'title'       => [
									'type'     => 'string',
									'required' => true,
								],
								'description' => [
									'type'     => 'string',
									'required' => true,
								],
							],
						],
					],
				],
			],
		];
	}

	/**
	 * Fetch the WooCommerce patterns from the Patterns Toolkit (PTK) API.
	 *
	 * @param array $options Options for fetching patterns.
	 * @return array|WP_Error
	 */
	public function fetch_patterns( array $options = array() ) {
		$locale = get_user_locale();
		$lang   = preg_replace( '/(_.*)$/', '', $locale );

		$ptk_url = self::PATTERNS_TOOLKIT_URL . $lang;

		if ( isset( $options['site'] ) ) {
			$ptk_url = add_query_arg( 'site', $options['site'], $ptk_url );
		}

		if ( isset( $options['categories'] ) ) {
			$ptk_url = add_query_arg( 'categories', implode( ',', $options['categories'] ), $ptk_url );
		}

		if ( isset( $options['per_page'] ) ) {
			$ptk_url = add_query_arg( 'per_page', $options['per_page'], $ptk_url );
		}

		$patterns = wp_safe_remote_get( $ptk_url );
		if ( is_wp_error( $patterns ) || 200 !== wp_remote_retrieve_response_code( $patterns ) ) {
			return new WP_Error(
				'patterns_toolkit_api_error',
				__( 'Failed to connect with the Patterns Toolkit API: try again later.', 'woocommerce' )
			);
		}

		$body = wp_remote_retrieve_body( $patterns );

		if ( empty( $body ) ) {
			return new WP_Error(
				'patterns_toolkit_api_error',
				__( 'Empty response received from the Patterns Toolkit API.', 'woocommerce' )
			);
		}

		$decoded_body = json_decode( $body, true );

		$is_pattern_payload_valid = $this->is_valid_schema( $decoded_body );

		if ( ! $is_pattern_payload_valid ) {
			return new WP_Error(
				'patterns_toolkit_api_error',
				__( 'Wrong response received from the Patterns Toolkit API: try again later.', 'woocommerce' )
			);
		}

		return $decoded_body;
	}

	/**
	 * Validate the patterns toolkit patterns.
	 *
	 * @param array $patterns The patterns to validate.
	 * @return bool
	 */
	public function is_valid_schema( $patterns ) {
		$is_pattern_payload_valid = rest_validate_value_from_schema( $patterns, $this->schema );

		return ! is_wp_error( $is_pattern_payload_valid );
	}
}
PK     \|      Patterns/PatternRegistry.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Patterns;

use Automattic\WooCommerce\Admin\Features\Features;

/**
 * PatternRegistry class.
 *
 * @internal
 */
class PatternRegistry {
	const SLUG_REGEX            = '/^[A-z0-9\/_-]+$/';
	const COMMA_SEPARATED_REGEX = '/[\s,]+/';

	/**
	 * Returns pattern slugs with their localized labels for categorization.
	 *
	 * Each key represents a unique pattern slug, while the value is the localized label.
	 *
	 * @return array<string, string>
	 */
	private function get_category_labels() {
		return [
			'woo-commerce'     => __( 'WooCommerce', 'woocommerce' ),
			'intro'            => __( 'Intro', 'woocommerce' ),
			'featured-selling' => __( 'Featured Selling', 'woocommerce' ),
			'about'            => __( 'About', 'woocommerce' ),
			'social-media'     => __( 'Social Media', 'woocommerce' ),
			'services'         => __( 'Services', 'woocommerce' ),
			'reviews'          => __( 'Reviews', 'woocommerce' ),
		];
	}

	/**
	 * Register a block pattern.
	 *
	 * @param string $source The pattern source.
	 * @param array  $pattern_data The pattern data.
	 *
	 * @return void
	 */
	public function register_block_pattern( $source, $pattern_data ) {
		if ( empty( $pattern_data['slug'] ) ) {
			_doing_it_wrong(
				'register_block_patterns',
				esc_html(
					sprintf(
					/* translators: %s: file name. */
						__( 'Could not register pattern "%s" as a block pattern ("Slug" field missing)', 'woocommerce' ),
						$source
					)
				),
				'6.0.0'
			);
			return;
		}

		if ( ! preg_match( self::SLUG_REGEX, $pattern_data['slug'] ) ) {
			_doing_it_wrong(
				'register_block_patterns',
				esc_html(
					sprintf(
					/* translators: %1s: file name; %2s: slug value found. */
						__( 'Could not register pattern "%1$s" as a block pattern (invalid slug "%2$s")', 'woocommerce' ),
						$source,
						$pattern_data['slug']
					)
				),
				'6.0.0'
			);
			return;
		}

		if ( \WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) {
			return;
		}

		if ( isset( $pattern_data['featureFlag'] ) && '' !== $pattern_data['featureFlag'] && ! Features::is_enabled( $pattern_data['featureFlag'] ) ) {
			return;
		}

		// Title is a required property.
		if ( ! isset( $pattern_data['title'] ) || ! $pattern_data['title'] ) {
			_doing_it_wrong(
				'register_block_patterns',
				esc_html(
					sprintf(
					/* translators: %1s: file name; %2s: slug value found. */
						__( 'Could not register pattern "%s" as a block pattern ("Title" field missing)', 'woocommerce' ),
						$source
					)
				),
				'6.0.0'
			);
			return;
		}

		// For properties of type array, parse data as comma-separated.
		foreach ( array( 'categories', 'keywords', 'blockTypes' ) as $property ) {
			if ( ! empty( $pattern_data[ $property ] ) ) {
				if ( is_array( $pattern_data[ $property ] ) ) {
					$pattern_data[ $property ] = array_values(
						array_map(
							function ( $property ) {
								return $property['title'];
							},
							$pattern_data[ $property ]
						)
					);
				} else {
					$pattern_data[ $property ] = array_filter(
						preg_split(
							self::COMMA_SEPARATED_REGEX,
							(string) $pattern_data[ $property ]
						)
					);
				}
			} else {
				unset( $pattern_data[ $property ] );
			}
		}

		// Parse properties of type int.
		foreach ( array( 'viewportWidth' ) as $property ) {
			if ( ! empty( $pattern_data[ $property ] ) ) {
				$pattern_data[ $property ] = (int) $pattern_data[ $property ];
			} else {
				unset( $pattern_data[ $property ] );
			}
		}

		// Parse properties of type bool.
		foreach ( array( 'inserter' ) as $property ) {
			if ( ! empty( $pattern_data[ $property ] ) ) {
				$pattern_data[ $property ] = in_array(
					strtolower( $pattern_data[ $property ] ),
					array( 'yes', 'true' ),
					true
				);
			} else {
				unset( $pattern_data[ $property ] );
			}
		}

		// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.LowLevelTranslationFunction
		$pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', 'woocommerce' );
		if ( ! empty( $pattern_data['description'] ) ) {
			// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.LowLevelTranslationFunction
			$pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', 'woocommerce' );
		}

		if ( empty( $pattern_data['content'] ) ) {
			return;
		}

		$category_labels = $this->get_category_labels();

		if ( ! empty( $pattern_data['categories'] ) ) {
			foreach ( $pattern_data['categories'] as $key => $category ) {
				$category_slug = _wp_to_kebab_case( $category );

				$pattern_data['categories'][ $key ] = $category_slug;

				$label = $category_labels[ $category_slug ] ?? self::kebab_to_capital_case( $category_slug );

				register_block_pattern_category(
					$category_slug,
					array(
						'label' => $label,
					),
				);
			}
		}

		register_block_pattern( $pattern_data['slug'], $pattern_data );
	}


	/**
	 * Convert a kebab-case string to capital case.
	 *
	 * @param string $value The kebab-case string.
	 *
	 * @return string
	 */
	private static function kebab_to_capital_case( $value ) {
		$string = str_replace( '-', ' ', $value );
		$string = ucwords( $string );

		return $string;
	}
}
PK     \{/{"  {"    Patterns/PTKPatternsStore.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\Patterns;

use Automattic\WooCommerce\Admin\Features\Features;
use WP_Upgrader;

/**
 * PTKPatterns class.
 *
 * @internal
 */
class PTKPatternsStore {
	const OPTION_NAME = 'ptk_patterns';

	/**
	 * Hook and action name used to trigger fetching patterns.
	 */
	const FETCH_PATTERNS_ACTION = 'fetch_patterns';

	const CATEGORY_MAPPING = array(
		'testimonials' => 'reviews',
	);

	/**
	 * PatternsToolkit instance.
	 *
	 * @var PTKClient $ptk_client
	 */
	private PTKClient $ptk_client;

	/**
	 * Constructor for the class.
	 *
	 * @param PTKClient $ptk_client An instance of PatternsToolkit.
	 */
	public function __construct( PTKClient $ptk_client ) {
		$this->ptk_client = $ptk_client;

		if ( Features::is_enabled( 'pattern-toolkit-full-composability' ) ) {
			// We want to flush the cached patterns when:
			// - The WooCommerce plugin is deactivated.
			// - The `woocommerce_allow_tracking` option is disabled.
			//
			// We also want to re-fetch the patterns and update the cache when:
			// - The `woocommerce_allow_tracking` option changes to enabled.
			// - The WooCommerce plugin is activated (if `woocommerce_allow_tracking` is enabled).
			// - The WooCommerce plugin is updated.

			add_action( 'woocommerce_activated_plugin', array( $this, 'flush_or_fetch_patterns' ), 10, 2 );
			add_action( 'update_option_woocommerce_allow_tracking', array( $this, 'flush_or_fetch_patterns' ), 10, 2 );
			add_action( 'deactivated_plugin', array( $this, 'flush_cached_patterns' ), 10, 2 );
			add_action( 'upgrader_process_complete', array( $this, 'fetch_patterns_on_plugin_update' ), 10, 2 );
			add_action( 'action_scheduler_ensure_recurring_actions', array( $this, 'ensure_recurring_fetch_patterns_if_enabled' ) );

			// This is the scheduled action that takes care of flushing and re-fetching the patterns from the PTK API.
			add_action( self::FETCH_PATTERNS_ACTION, array( $this, 'fetch_patterns' ) );
		}
	}

	/**
	 * Resets the cached patterns when the `woocommerce_allow_tracking` option is disabled.
	 * Resets and fetch the patterns from the PTK when it is enabled (if the scheduler
	 * is initialized, it's done asynchronously via a scheduled action).
	 *
	 * @return void
	 */
	public function flush_or_fetch_patterns() {
		if ( $this->allowed_tracking_is_enabled() ) {
			$this->schedule_fetch_patterns();
			return;
		}

		$this->flush_cached_patterns();
	}

	/**
	 * Schedule an async action to fetch the PTK patterns when the scheduler is initialized.
	 *
	 * @return void
	 */
	private function schedule_fetch_patterns() {
		if ( did_action( 'action_scheduler_init' ) ) {
			$this->schedule_action_if_not_pending( self::FETCH_PATTERNS_ACTION );
		} else {
			add_action(
				'action_scheduler_init',
				function () {
					$this->schedule_action_if_not_pending( self::FETCH_PATTERNS_ACTION );
				}
			);
		}
	}

	/**
	 * Ensure a recurring fetch patterns action is scheduled.
	 * This is called by the `action_scheduler_ensure_recurring_actions` hook.
	 *
	 * @return void
	 */
	public function ensure_recurring_fetch_patterns_if_enabled() {
		if ( ! $this->allowed_tracking_is_enabled() ) {
			return;
		}

		$this->schedule_action_if_not_pending( self::FETCH_PATTERNS_ACTION );
	}

	/**
	 * Schedule an action if it's not already pending.
	 *
	 * @param string $action The action name to schedule.
	 * @return void
	 */
	private function schedule_action_if_not_pending( $action ) {
		if ( as_has_scheduled_action( $action, array(), 'woocommerce' ) ) {
			return;
		}

		as_schedule_recurring_action( time(), DAY_IN_SECONDS, $action, array(), 'woocommerce' );
	}

	/**
	 * Get the patterns from the Patterns Toolkit cache.
	 *
	 * @return array
	 */
	public function get_patterns() {
		$patterns = get_option( self::OPTION_NAME );

		// If the current data doesn't exist or is invalid, schedule fetching the patterns from the PTK.
		if ( false === $patterns || ! $this->ptk_client->is_valid_schema( $patterns ) ) {
			$this->schedule_fetch_patterns();
			return array();
		}

		return $patterns;
	}

	/**
	 * Filter the patterns that have external dependencies.
	 *
	 * @param array $patterns The patterns to filter.
	 * @return array
	 */
	private function filter_patterns( array $patterns ) {
		return array_values(
			array_filter(
				$patterns,
				function ( $pattern ) {
					if ( ! isset( $pattern['ID'] ) ) {
						return true;
					}

					if ( isset( $pattern['post_type'] ) && 'wp_block' !== $pattern['post_type'] ) {
						return false;
					}

					if ( $this->has_external_dependencies( $pattern ) ) {
						return false;
					}

					return true;
				}
			)
		);
	}

	/**
	 * Re-fetch the patterns when the WooCommerce plugin is updated.
	 *
	 * @param WP_Upgrader $upgrader_object WP_Upgrader instance.
	 * @param array       $options Array of bulk item update data.
	 *
	 * @return void
	 */
	public function fetch_patterns_on_plugin_update( $upgrader_object, $options ) {
		if ( 'update' === $options['action'] && 'plugin' === $options['type'] && isset( $options['plugins'] ) ) {
			foreach ( $options['plugins'] as $plugin ) {
				if ( str_contains( $plugin, 'woocommerce.php' ) ) {
					$this->schedule_fetch_patterns();
				}
			}
		}
	}

	/**
	 * Reset the cached patterns to fetch them again from the PTK.
	 *
	 * @since 10.4.1 Unscheduling is deferred if Action Scheduler hasn't initialized yet.
	 * @return void
	 */
	public function flush_cached_patterns() {
		delete_option( self::OPTION_NAME );

		if ( ! function_exists( 'as_unschedule_all_actions' ) ) {
			return;
		}

		// Unschedule any existing fetch_patterns actions.
		// Defer unscheduling until Action Scheduler is ready to avoid errors during early initialization.
		if ( did_action( 'action_scheduler_init' ) ) {
			as_unschedule_all_actions( self::FETCH_PATTERNS_ACTION, array(), 'woocommerce' );
		} else {
			add_action(
				'action_scheduler_init',
				function () {
					as_unschedule_all_actions( self::FETCH_PATTERNS_ACTION, array(), 'woocommerce' );
				}
			);
		}
	}

	/**
	 * Reset the cached patterns and fetch them again from the PTK API.
	 *
	 * @return void
	 */
	public function fetch_patterns() {
		if ( ! $this->allowed_tracking_is_enabled() ) {
			return;
		}

		$patterns = $this->ptk_client->fetch_patterns(
			array(
				// This is the site where the patterns are stored. Despite the 'wpcomstaging.com' domain suggesting a staging environment, this URL points to the production environment where stable versions of the patterns are maintained.
				'site'       => 'wooblockpatterns.wpcomstaging.com',
				'categories' => array(
					'_woo_intro',
					'_woo_featured_selling',
					'_woo_about',
					'_woo_reviews',
					'_woo_social_media',
					'_woo_woocommerce',
					'_dotcom_imported_intro',
					'_dotcom_imported_about',
					'_dotcom_imported_services',
					'_dotcom_imported_reviews',
				),
			)
		);

		if ( is_wp_error( $patterns ) ) {
			wc_get_logger()->warning(
				sprintf(
				// translators: %s is a generated error message.
					__( 'Failed to get WooCommerce patterns from the PTK: "%s"', 'woocommerce' ),
					$patterns->get_error_message()
				),
			);
			return;
		}

		$patterns = $this->filter_patterns( $patterns );
		$patterns = $this->map_categories( $patterns );

		update_option( self::OPTION_NAME, $patterns, false );
	}

	/**
	 * Check if the user allowed tracking.
	 *
	 * @return bool
	 */
	private function allowed_tracking_is_enabled(): bool {
		return 'yes' === get_option( 'woocommerce_allow_tracking' );
	}

	/**
	 * Change the categories of the patterns to match the ones used in the CYS flow
	 *
	 * @param array $patterns The patterns to map categories for.
	 * @return array The patterns with the categories mapped.
	 */
	private function map_categories( array $patterns ) {
		return array_map(
			function ( $pattern ) {
				if ( isset( $pattern['categories'] ) ) {
					foreach ( $pattern['categories'] as $key => $category ) {
						if ( isset( $category['slug'] ) && isset( self::CATEGORY_MAPPING[ $key ] ) ) {
							$new_category = self::CATEGORY_MAPPING[ $key ];
							unset( $pattern['categories'][ $key ] );
							$pattern['categories'][ $new_category ]['slug']  = $new_category;
							$pattern['categories'][ $new_category ]['title'] = ucfirst( $new_category );
						}
					}
				}

				return $pattern;
			},
			$patterns
		);
	}

	/**
	 * Check if the pattern has external dependencies.
	 *
	 * @param array $pattern The pattern to check.
	 *
	 * @return bool
	 */
	private function has_external_dependencies( $pattern ) {
		if ( ! isset( $pattern['dependencies'] ) || ! is_array( $pattern['dependencies'] ) ) {
			return false;
		}

		foreach ( $pattern['dependencies'] as $dependency ) {
			if ( 'woocommerce' !== $dependency ) {
				return true;
			}
		}

		return false;
	}
}
PK     \3{~dQ  dQ    Shipping/ShippingController.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Shipping;

use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
use Automattic\WooCommerce\Enums\ProductTaxStatus;
use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;
use Automattic\WooCommerce\Utilities\ArrayUtil;
use WC_Customer;
use WC_Shipping_Rate;
use WC_Tracks;

/**
 * ShippingController class.
 *
 * @internal
 */
class ShippingController {

	/**
	 * Script handle used for enqueueing the scripts needed for managing the Local Pickup Shipping Settings.
	 */
	private const LOCAL_PICKUP_ADMIN_JS_HANDLE = 'wc-shipping-method-pickup-location';

	/**
	 * Instance of the asset API.
	 *
	 * @var AssetApi
	 */
	protected $asset_api;

	/**
	 * Instance of the asset data registry.
	 *
	 * @var AssetDataRegistry
	 */
	protected $asset_data_registry;

	/**
	 * Whether local pickup is enabled.
	 *
	 * @var bool
	 */
	private $local_pickup_enabled;

	/**
	 * Constructor.
	 *
	 * @param AssetApi          $asset_api Instance of the asset API.
	 * @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
	 */
	public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry ) {
		$this->asset_api            = $asset_api;
		$this->asset_data_registry  = $asset_data_registry;
		$this->local_pickup_enabled = LocalPickupUtils::is_local_pickup_enabled();
	}

	/**
	 * Initialization method.
	 */
	public function init() {
		if ( is_admin() ) {
			$this->asset_data_registry->add(
				'countryStates',
				function () {
					return WC()->countries->get_states();
				}
			);
		}
		$this->asset_data_registry->add( 'shippingCostRequiresAddress', get_option( 'woocommerce_shipping_cost_requires_address', false ) === 'yes' );
		add_action( 'rest_api_init', array( $this, 'register_settings' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
		add_action( 'admin_footer', array( $this, 'hydrate_client_settings' ), 0 );
		add_action( 'woocommerce_load_shipping_methods', array( $this, 'register_local_pickup' ) );
		add_filter( 'woocommerce_local_pickup_methods', array( $this, 'register_local_pickup_method' ) );
		add_filter( 'woocommerce_order_hide_shipping_address', array( $this, 'hide_shipping_address_for_local_pickup' ), 10 );
		add_filter( 'woocommerce_customer_taxable_address', array( $this, 'filter_taxable_address' ) );
		add_filter( 'woocommerce_shipping_settings', array( $this, 'remove_shipping_settings' ) );
		add_filter( 'woocommerce_shipping_packages', array( $this, 'filter_shipping_packages' ) );
		add_filter( 'pre_update_option_woocommerce_pickup_location_settings', array( $this, 'flush_cache' ) );
		add_filter( 'pre_update_option_pickup_location_pickup_locations', array( $this, 'flush_cache' ) );
		add_filter( 'woocommerce_shipping_packages', array( $this, 'remove_shipping_if_no_address' ), 11 );
		add_filter( 'woocommerce_order_shipping_to_display', array( $this, 'show_local_pickup_details' ), 10, 2 );
		add_action( 'rest_pre_serve_request', array( $this, 'track_local_pickup' ), 10, 4 );
	}

	/**
	 * Inject collection details onto the order received page.
	 *
	 * @param string    $return_value Return value.
	 * @param \WC_Order $order Order object.
	 * @return string
	 */
	public function show_local_pickup_details( $return_value, $order ) {
		// Confirm order is valid before proceeding further.
		if ( ! $order instanceof \WC_Order ) {
			return $return_value;
		}

		$shipping_method_ids = ArrayUtil::select( $order->get_shipping_methods(), 'get_method_id', ArrayUtil::SELECT_BY_OBJECT_METHOD );
		$shipping_method_id  = current( $shipping_method_ids );

		// Ensure order used pickup location method, otherwise bail.
		if ( 'pickup_location' !== $shipping_method_id ) {
			return $return_value;
		}

		$shipping_method = current( $order->get_shipping_methods() );
		$details         = $shipping_method->get_meta( 'pickup_details' );
		$location        = $shipping_method->get_meta( 'pickup_location' );
		$address         = $shipping_method->get_meta( 'pickup_address' );
		$cost            = $shipping_method->get_total();

		$lines = array();

		if ( $location ) {
			$lines[] = sprintf(
				// Translators: %s location name.
				__( 'Collection from <strong>%s</strong>:', 'woocommerce' ),
				$location
			);
		}

		if ( $address ) {
			$lines[] = nl2br( esc_html( str_replace( ',', ', ', $address ) ) );
		}

		if ( $details ) {
			$lines[] = wp_kses_post( $details );
		}

		if ( $cost > 0 ) {
			$tax_display = get_option( 'woocommerce_tax_display_cart' );
			$tax         = $shipping_method->get_total_tax();

			// Format cost with tax handling.
			if ( 'excl' === $tax_display ) {
				// Show pickup cost excluding tax.
				$formatted_cost = wc_price( $cost, array( 'currency' => $order->get_currency() ) );
				if ( (float) $tax > 0 && $order->get_prices_include_tax() ) {
					/**
					 * Hook to add tax label to pickup cost.
					 *
					 * @since 6.0.0
					 * @param string $tax_label Tax label.
					 * @param \WC_Order $order Order object.
					 * @param string $tax_display Tax display.
					 * @return string
					 */
					$formatted_cost .= apply_filters(
						'woocommerce_order_shipping_to_display_tax_label',
						'&nbsp;<small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>',
						$order,
						$tax_display
					);
				}
			} else {
				// Show pickup cost including tax.
				$formatted_cost = wc_price(
					(float) $cost + (float) $tax,
					array( 'currency' => $order->get_currency() )
				);
				if ( (float) $tax > 0 && ! $order->get_prices_include_tax() ) {
					/**
					 * Hook to add tax label to pickup cost.
					 *
					 * @since 6.0.0
					 * @param string $tax_label Tax label.
					 * @param \WC_Order $order Order object.
					 * @param string $tax_display Tax display.
					 * @return string
					 */
					$formatted_cost .= apply_filters(
						'woocommerce_order_shipping_to_display_tax_label',
						'&nbsp;<small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>',
						$order,
						$tax_display
					);
				}
			}

			$lines[] = '<br>' . sprintf(
				// Translators: %s is the formatted price.
				__( 'Pickup cost: %s', 'woocommerce' ),
				$formatted_cost
			);
		}

		// If nothing is available, return original.
		if ( empty( $lines ) ) {
			return $return_value;
		}

		// Join all the lines with a <br> separator.
		return implode( '<br>', $lines );
	}

	/**
	 * When using the cart and checkout blocks this method is used to adjust core shipping settings via a filter hook.
	 *
	 * @param array $settings The default WC shipping settings.
	 * @return array|mixed The filtered settings.
	 */
	public function remove_shipping_settings( $settings ) {
		if ( CartCheckoutUtils::is_cart_block_default() ) {
			foreach ( $settings as $index => $setting ) {
				if ( 'woocommerce_enable_shipping_calc' === $setting['id'] ) {
					$settings[ $index ]['desc_tip'] = sprintf(
					/* translators: %s: URL to the documentation. */
						__( 'This feature is not available when using the <a href="%s">Cart and checkout blocks</a>. Shipping will be calculated at checkout.', 'woocommerce' ),
						'https://woocommerce.com/document/woocommerce-store-editing/customizing-cart-and-checkout/'
					);
					$settings[ $index ]['disabled'] = true;
					$settings[ $index ]['value']    = 'no';
					break;
				}
			}
		}

		return $settings;
	}

	/**
	 * Register Local Pickup settings for rest api.
	 */
	public function register_settings() {
		register_setting(
			'options',
			'woocommerce_pickup_location_settings',
			array(
				'type'         => 'object',
				'description'  => 'WooCommerce Local Pickup Method Settings',
				'default'      => array(),
				'show_in_rest' => array(
					'name'   => 'pickup_location_settings',
					'schema' => array(
						'type'       => 'object',
						'properties' => array(
							'enabled'    => array(
								'description' => __( 'If enabled, this method will appear on the block based checkout.', 'woocommerce' ),
								'type'        => 'string',
								'enum'        => array( 'yes', 'no' ),
							),
							'title'      => array(
								'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce' ),
								'type'        => 'string',
							),
							'tax_status' => array(
								'description' => __( 'If a cost is defined, this controls if taxes are applied to that cost.', 'woocommerce' ),
								'type'        => 'string',
								'enum'        => array( ProductTaxStatus::TAXABLE, ProductTaxStatus::NONE ),
							),
							'cost'       => array(
								'description' => __( 'Optional cost to charge for local pickup.', 'woocommerce' ),
								'type'        => 'string',
							),
						),
					),
				),
			)
		);
		register_setting(
			'options',
			'pickup_location_pickup_locations',
			array(
				'type'         => 'array',
				'description'  => 'WooCommerce Local Pickup Locations',
				'default'      => array(),
				'show_in_rest' => array(
					'name'   => 'pickup_locations',
					'schema' => array(
						'type'  => 'array',
						'items' => array(
							'type'       => 'object',
							'properties' => array(
								'name'    => array(
									'type' => 'string',
								),
								'address' => array(
									'type'       => 'object',
									'properties' => array(
										'address_1' => array(
											'type' => 'string',
										),
										'city'      => array(
											'type' => 'string',
										),
										'state'     => array(
											'type' => 'string',
										),
										'postcode'  => array(
											'type' => 'string',
										),
										'country'   => array(
											'type' => 'string',
										),
									),
								),
								'details' => array(
									'type' => 'string',
								),
								'enabled' => array(
									'type' => 'boolean',
								),
							),
						),
					),
				),
			)
		);
	}

	/**
	 * Hydrate client settings
	 */
	public function hydrate_client_settings() {
		if ( ! wp_script_is( self::LOCAL_PICKUP_ADMIN_JS_HANDLE, 'enqueued' ) ) {
			// Only hydrate the settings if the script dependent on them is enqueued.
			return;
		}

		$locations = get_option( 'pickup_location_pickup_locations', array() );

		$formatted_pickup_locations = array();
		foreach ( $locations as $location ) {
			$formatted_pickup_locations[] = array(
				'name'    => $location['name'],
				'address' => $location['address'],
				'details' => $location['details'],
				'enabled' => wc_string_to_bool( $location['enabled'] ),
			);
		}

		$has_legacy_pickup = false;

		// Get all shipping zones.
		$shipping_zones              = \WC_Shipping_Zones::get_zones( 'admin' );
		$international_shipping_zone = new \WC_Shipping_Zone( 0 );

		// Loop through each shipping zone.
		foreach ( $shipping_zones as $shipping_zone ) {
			// Get all registered rates for this shipping zone.
			$shipping_methods = $shipping_zone['shipping_methods'];
			// Loop through each registered rate.
			foreach ( $shipping_methods as $shipping_method ) {
				if ( 'local_pickup' === $shipping_method->id && 'yes' === $shipping_method->enabled ) {
					$has_legacy_pickup = true;
					break 2;
				}
			}
		}

		foreach ( $international_shipping_zone->get_shipping_methods( true ) as $shipping_method ) {
			if ( 'local_pickup' === $shipping_method->id ) {
				$has_legacy_pickup = true;
				break;
			}
		}

		$settings = array(
			'pickupLocationSettings' => LocalPickupUtils::get_local_pickup_settings(),
			'pickupLocations'        => $formatted_pickup_locations,
			'readonlySettings'       => array(
				'hasLegacyPickup' => $has_legacy_pickup,
				'storeCountry'    => WC()->countries->get_base_country(),
				'storeState'      => WC()->countries->get_base_state(),
			),
		);

		wp_add_inline_script(
			self::LOCAL_PICKUP_ADMIN_JS_HANDLE,
			sprintf(
				'var hydratedScreenSettings = %s;',
				wp_json_encode( $settings, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES )
			),
			'before'
		);
	}
	/**
	 * Load admin scripts.
	 */
	public function admin_scripts() {
		$this->asset_api->register_script( self::LOCAL_PICKUP_ADMIN_JS_HANDLE, 'assets/client/blocks/wc-shipping-method-pickup-location.js', array(), true );
	}

	/**
	 * Registers the Local Pickup shipping method used by the Checkout Block.
	 */
	public function register_local_pickup() {
		if ( CartCheckoutUtils::is_checkout_block_default() ) {
			$wc_instance = WC();
			if ( is_object( $wc_instance ) && method_exists( $wc_instance, 'shipping' ) && is_object( $wc_instance->shipping ) && method_exists( $wc_instance->shipping, 'register_shipping_method' ) ) {
				$wc_instance->shipping->register_shipping_method( new PickupLocation() );
			} else {
				wc_get_logger()->error( 'Error registering pickup location: WC()->shipping->register_shipping_method is not available', array( 'source' => 'shipping-controller' ) );
			}
		}
	}

	/**
	 * Declares the Pickup Location shipping method as a Local Pickup method for WooCommerce.
	 *
	 * @param array $methods Shipping method ids.
	 * @return array
	 */
	public function register_local_pickup_method( $methods ) {
		$methods[] = 'pickup_location';
		return $methods;
	}

	/**
	 * Hides the shipping address on the order confirmation page when local pickup is selected.
	 *
	 * @param array $pickup_methods Method ids.
	 * @return array
	 */
	public function hide_shipping_address_for_local_pickup( $pickup_methods ) {
		return array_merge( $pickup_methods, LocalPickupUtils::get_local_pickup_method_ids() );
	}

	/**
	 * Everytime we save or update local pickup settings, we flush the shipping
	 * transient group.
	 *
	 * @param array $settings The setting array we're saving.
	 * @return array $settings The setting array we're saving.
	 */
	public function flush_cache( $settings ) {
		\WC_Cache_Helper::get_transient_version( 'shipping', true );
		return $settings;
	}
	/**
	 * Filter the location used for taxes based on the chosen pickup location.
	 *
	 * @param array $address Location args.
	 * @return array
	 */
	public function filter_taxable_address( $address ) {

		if ( null === WC()->session ) {
			return $address;
		}
		// We only need to select from the first package, since pickup_location only supports a single package.
		$chosen_method          = current( WC()->session->get( 'chosen_shipping_methods', array() ) ) ?? '';
		$chosen_method_id       = explode( ':', $chosen_method )[0];
		$chosen_method_instance = explode( ':', $chosen_method )[1] ?? 0;

		// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
		if ( $chosen_method_id && true === apply_filters( 'woocommerce_apply_base_tax_for_local_pickup', true ) && in_array( $chosen_method_id, LocalPickupUtils::get_local_pickup_method_ids(), true ) ) {
			$pickup_locations = get_option( 'pickup_location_pickup_locations', array() );
			$pickup_location  = $pickup_locations[ $chosen_method_instance ] ?? array();

			if ( isset( $pickup_location['address'], $pickup_location['address']['country'] ) && ! empty( $pickup_location['address']['country'] ) ) {
				$address = array(
					$pickup_locations[ $chosen_method_instance ]['address']['country'],
					$pickup_locations[ $chosen_method_instance ]['address']['state'],
					$pickup_locations[ $chosen_method_instance ]['address']['postcode'],
					$pickup_locations[ $chosen_method_instance ]['address']['city'],
				);
			}
		}

		return $address;
	}

	/**
	 * Local Pickup requires all packages to support local pickup. This is because the entire order must be picked up
	 * so that all packages get the same tax rates applied during checkout.
	 *
	 * If a shipping package does not support local pickup (e.g. if disabled by an extension), this filters the option
	 * out for all packages. This will in turn disable the "pickup" toggle in Block Checkout.
	 *
	 * @param array $packages Array of shipping packages.
	 * @return array
	 */
	public function filter_shipping_packages( $packages ) {
		// Check all packages for an instance of a collectable shipping method.
		$valid_packages = array_filter(
			$packages,
			function ( $package ) {
				$shipping_method_ids = ArrayUtil::select( $package['rates'] ?? array(), 'get_method_id', ArrayUtil::SELECT_BY_OBJECT_METHOD );
				return ! empty( array_intersect( LocalPickupUtils::get_local_pickup_method_ids(), $shipping_method_ids ) );
			}
		);

		// Remove pickup location from rates arrays if not all packages can be picked up or support local pickup.
		if ( count( $valid_packages ) !== count( $packages ) ) {
			$packages = array_map(
				function ( $package ) {
					if ( ! is_array( $package['rates'] ) ) {
						$package['rates'] = array();
						return $package;
					}
					$package['rates'] = array_filter(
						$package['rates'],
						function ( $rate ) {
							return ! in_array( $rate->get_method_id(), LocalPickupUtils::get_local_pickup_method_ids(), true );
						}
					);
					return $package;
				},
				$packages
			);
		}

		return $packages;
	}

	/**
	 * Remove shipping (i.e. delivery, not local pickup) if "Hide shipping costs until an address is entered" is enabled,
	 * and no address has been entered yet.
	 *
	 * Only applies to block checkout because pickup is chosen separately to shipping in that context.
	 *
	 * @param array $packages Array of shipping packages.
	 * @return array
	 */
	public function remove_shipping_if_no_address( $packages ) {
		if ( 'shortcode' === WC()->cart->cart_context ) {
			return $packages;
		}

		$shipping_cost_requires_address = wc_string_to_bool( get_option( 'woocommerce_shipping_cost_requires_address', 'no' ) );

		// Return early here for a small performance gain if we don't need to hide shipping costs until an address is entered.
		if ( ! $shipping_cost_requires_address ) {
			return $packages;
		}

		$customer = WC()->customer;

		if ( $customer instanceof WC_Customer && $customer->has_full_shipping_address() ) {
			return $packages;
		}

		return array_map(
			function ( $package ) {
				// Package rates is always an array due to a check in core.
				$package['rates'] = array_filter(
					$package['rates'],
					function ( $rate ) {
						return $rate instanceof WC_Shipping_Rate && in_array( $rate->get_method_id(), LocalPickupUtils::get_local_pickup_method_ids(), true );
					}
				);
				return $package;
			},
			$packages
		);
	}

	/**
	 * Track local pickup settings changes via Store API
	 *
	 * @param bool              $served Whether the request has already been served.
	 * @param \WP_REST_Response $result The response object.
	 * @param \WP_REST_Request  $request The request object.
	 * @return bool
	 */
	public function track_local_pickup( $served, $result, $request ) {
		if ( '/wp/v2/settings' !== $request->get_route() ) {
			return $served;
		}
		// Param name here comes from the show_in_rest['name'] value when registering the setting.
		if ( ! $request->get_param( 'pickup_location_settings' ) && ! $request->get_param( 'pickup_locations' ) ) {
			return $served;
		}

		$event_name = 'local_pickup_save_changes';

		$settings  = $request->get_param( 'pickup_location_settings' );
		$locations = $request->get_param( 'pickup_locations' );

		$data = array(
			'local_pickup_enabled'     => 'yes' === $settings['enabled'] ? true : false,
			'title'                    => __( 'Pickup', 'woocommerce' ) === $settings['title'],
			'price'                    => '' === $settings['cost'] ? true : false,
			'cost'                     => '' === $settings['cost'] ? 0 : $settings['cost'],
			'taxes'                    => $settings['tax_status'],
			'total_pickup_locations'   => count( $locations ),
			'pickup_locations_enabled' => count(
				array_filter(
					$locations,
					function ( $location ) {
						return $location['enabled']; }
				)
			),
		);

		WC_Tracks::record_event( $event_name, $data );

		return $served;
	}

	/**
	 * Check if legacy local pickup is activated in any of the shipping zones or in the Rest of the World zone.
	 *
	 * @since 8.8.0
	 *
	 * @return bool
	 */
	public static function is_legacy_local_pickup_active() {
		$rest_of_the_world                          = \WC_Shipping_Zones::get_zone_by( 'zone_id', 0 );
		$shipping_zones                             = \WC_Shipping_Zones::get_zones();
		$rest_of_the_world_data                     = $rest_of_the_world->get_data();
		$rest_of_the_world_data['shipping_methods'] = $rest_of_the_world->get_shipping_methods();
		array_unshift( $shipping_zones, $rest_of_the_world_data );

		foreach ( $shipping_zones as $zone ) {
			foreach ( $zone['shipping_methods'] as $method ) {
				if ( 'local_pickup' === $method->id && $method->is_enabled() ) {
					return true;
				}
			}
		}

		return false;
	}
}
PK     \LV  V    Shipping/PickupLocation.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Shipping;

use WC_Shipping_Method;

/**
 * Local Pickup Shipping Method.
 */
class PickupLocation extends WC_Shipping_Method {

	/**
	 * Pickup locations.
	 *
	 * @var array
	 */
	protected $pickup_locations = [];

	/**
	 * Cost
	 *
	 * @var string
	 */
	protected $cost = '';

	/**
	 * Constructor.
	 */
	public function __construct() {
		parent::__construct();
		$this->id                 = 'pickup_location';
		$this->method_title       = __( 'Local pickup', 'woocommerce' );
		$this->method_description = __( 'Allow customers to choose a local pickup location during checkout.', 'woocommerce' );
		$this->init();
	}

	/**
	 * Init function.
	 */
	public function init() {
		$this->enabled          = $this->get_option( 'enabled' );
		$this->title            = $this->get_option( 'title', __( 'Pickup', 'woocommerce' ) );
		$this->tax_status       = $this->get_option( 'tax_status' );
		$this->cost             = $this->get_option( 'cost' );
		$this->supports         = [ 'settings', 'local-pickup' ];
		$this->pickup_locations = get_option( $this->id . '_pickup_locations', [] );
		add_filter( 'woocommerce_attribute_label', array( $this, 'translate_meta_data' ), 10, 3 );
	}

	/**
	 * Checks if a given address is complete.
	 *
	 * @param array $address Address.
	 * @return bool
	 */
	protected function has_valid_pickup_location( $address ) {
		// Normalize address.
		$address_fields = wp_parse_args(
			(array) $address,
			array(
				'city'     => '',
				'postcode' => '',
				'state'    => '',
				'country'  => '',
			)
		);

		// Country is always required.
		if ( empty( $address_fields['country'] ) ) {
			return false;
		}

		// If all fields are provided, we can skip further checks.
		if ( ! empty( $address_fields['city'] ) && ! empty( $address_fields['postcode'] ) && ! empty( $address_fields['state'] ) ) {
			return true;
		}

		// Check validity based on requirements for the country.
		$country_address_fields = wc()->countries->get_address_fields( $address_fields['country'], 'shipping_' );

		foreach ( $country_address_fields as $field_name => $field ) {
			$key = str_replace( 'shipping_', '', $field_name );

			if ( isset( $address_fields[ $key ] ) && true === $field['required'] && empty( $address_fields[ $key ] ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Calculate shipping.
	 *
	 * @param array $package Package information.
	 */
	public function calculate_shipping( $package = array() ) {
		if ( $this->pickup_locations ) {
			foreach ( $this->pickup_locations as $index => $location ) {
				if ( ! $location['enabled'] ) {
					continue;
				}
				$this->add_rate(
					array(
						'id'        => $this->id . ':' . $index,
						// This is the label shown in shipping rate/method context e.g. London (Local Pickup).
						'label'     => wp_kses_post( $this->title . ' (' . $location['name'] . ')' ),
						'package'   => $package,
						'cost'      => $this->cost,
						'meta_data' => array(
							'pickup_location' => wp_kses_post( $location['name'] ),
							'pickup_address'  => $this->has_valid_pickup_location( $location['address'] ) ? wc()->countries->get_formatted_address( $location['address'], ', ' ) : '',
							'pickup_details'  => wp_kses_post( $location['details'] ),
						),
					)
				);
			}
		}
	}

	/**
	 * See if the method is available.
	 *
	 * @param array $package Package information.
	 * @return bool
	 */
	public function is_available( $package ) {
		// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
		return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', 'yes' === $this->enabled, $package, $this );
	}

	/**
	 * Translates meta data for the shipping method.
	 *
	 * @param string $label Meta label.
	 * @param string $name Meta key.
	 * @param mixed  $product Product if applicable.
	 * @return string
	 */
	public function translate_meta_data( $label, $name, $product ) {
		if ( $product ) {
			return $label;
		}
		switch ( $name ) {
			case 'pickup_location':
				return __( 'Pickup location', 'woocommerce' );
			case 'pickup_address':
				return __( 'Pickup address', 'woocommerce' );
			case 'pickup_details':
				return __( 'Pickup details', 'woocommerce' );
		}
		return $label;
	}

	/**
	 * Admin options screen.
	 *
	 * See also WC_Shipping_Method::admin_options().
	 */
	public function admin_options() {
		global $hide_save_button;
		$hide_save_button = true;

		wp_enqueue_script( 'wc-shipping-method-pickup-location' );

		echo '<h2>' . esc_html__( 'Local pickup', 'woocommerce' ) . '</h2>';
		echo '<div class="wrap"><div id="wc-shipping-method-pickup-location-settings-container"></div></div>';
	}
}
PK     \ۡT  T    Installer.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks;

/**
 * Installer class.
 * Handles installation of Blocks plugin dependencies.
 *
 * @internal
 */
class Installer {
	/**
	 * Initialize class features.
	 */
	public function init() {
		add_action( 'admin_init', array( $this, 'install' ) );
		add_filter( 'woocommerce_create_pages', array( $this, 'create_pages' ) );
	}

	/**
	 * Installation tasks ran on admin_init callback.
	 */
	public function install() {
		$this->maybe_create_tables();
	}

	/**
	 * Modifies default page content replacing it with classic shortcode block.
	 * We check for shortcode as default because after WooCommerce 8.3, block-based checkout is used by default.
	 * This only runs on Tools > Create Pages as the filter is not applied on WooCommerce plugin activation.
	 *
	 * @param array $pages Default pages.
	 * @return array
	 */
	public function create_pages( $pages ) {

		if ( '<!-- wp:shortcode -->[woocommerce_cart]<!-- /wp:shortcode -->' === ( $pages['cart']['content'] ?? null ) ) {
			$pages['cart']['content'] = '<!-- wp:woocommerce/classic-shortcode {"shortcode":"cart"} /-->';
		}

		if ( '<!-- wp:shortcode -->[woocommerce_checkout]<!-- /wp:shortcode -->' === ( $pages['checkout']['content'] ?? null ) ) {
			$pages['checkout']['content'] = '<!-- wp:woocommerce/classic-shortcode {"shortcode":"checkout"} /-->';
		}

		return $pages;
	}

	/**
	 * Set up the database tables which the plugin needs to function.
	 */
	public function maybe_create_tables() {
		global $wpdb;

		$schema_version    = 260;
		$db_schema_version = (int) get_option( 'wc_blocks_db_schema_version', 0 );

		if ( $db_schema_version >= $schema_version && 0 !== $db_schema_version ) {
			return;
		}

		$show_errors = $wpdb->hide_errors();
		$table_name  = $wpdb->prefix . 'wc_reserved_stock';
		$collate     = $wpdb->has_cap( 'collation' ) ? $wpdb->get_charset_collate() : '';
		$exists      = $this->maybe_create_table(
			$wpdb->prefix . 'wc_reserved_stock',
			"
			CREATE TABLE {$wpdb->prefix}wc_reserved_stock (
				`order_id` bigint(20) NOT NULL,
				`product_id` bigint(20) NOT NULL,
				`stock_quantity` double NOT NULL DEFAULT 0,
				`timestamp` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
				`expires` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
				PRIMARY KEY  (`order_id`, `product_id`)
			) $collate;
			"
		);

		if ( $show_errors ) {
			$wpdb->show_errors();
		}

		if ( ! $exists ) {
			return $this->add_create_table_notice( $table_name );
		}

		// Update succeeded. This is only updated when successful and validated.
		// $schema_version should be incremented when changes to schema are made within this method.
		update_option( 'wc_blocks_db_schema_version', $schema_version );
	}

	/**
	 * Create database table, if it doesn't already exist.
	 *
	 * Based on admin/install-helper.php maybe_create_table function.
	 *
	 * @param string $table_name Database table name.
	 * @param string $create_sql Create database table SQL.
	 * @return bool False on error, true if already exists or success.
	 */
	protected function maybe_create_table( $table_name, $create_sql ) {
		global $wpdb;

		if ( in_array( $table_name, $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ), 0 ), true ) ) {
			return true;
		}

		$wpdb->query( $create_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

		return in_array( $table_name, $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ), 0 ), true );
	}

	/**
	 * Add a notice if table creation fails.
	 *
	 * @param string $table_name Name of the missing table.
	 */
	protected function add_create_table_notice( $table_name ) {
		add_action(
			'admin_notices',
			function() use ( $table_name ) {
				echo '<div class="error"><p>';
				printf(
					/* translators: %1$s table name, %2$s database user, %3$s database name. */
					esc_html__( 'WooCommerce %1$s table creation failed. Does the %2$s user have CREATE privileges on the %3$s database?', 'woocommerce' ),
					'<code>' . esc_html( $table_name ) . '</code>',
					'<code>' . esc_html( DB_USER ) . '</code>',
					'<code>' . esc_html( DB_NAME ) . '</code>'
				);
				echo '</p></div>';
			}
		);
	}
}
PK     \ʫ        Images/Pexels.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\Images;

/**
 * Pexels API client.
 *
 * @internal
 * @deprecated This class can't be removed due https://github.com/woocommerce/woocommerce/issues/52311.
 */
class Pexels {}
PK     \E"        AIContent/UpdatePatterns.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\AIContent;

/**
 * Pattern Images class.
 *
 * @internal
 * @deprecated This class can't be removed due https://github.com/woocommerce/woocommerce/issues/52311.
 */
class UpdatePatterns {
}
PK     \ n"  "    AIContent/UpdateProducts.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\AIContent;

use WP_Error;

/**
 * This class is used to create dummy products for the Customize Your Store flow.
 * Even if it is in the AI Content namespace, it is not used for AI content generation.
 *
 * @internal
 */
class UpdateProducts {

	/**
	 * The dummy products.
	 */
	const DUMMY_PRODUCTS = [
		[
			'title'       => 'Vintage Typewriter',
			'image'       => 'assets/images/pattern-placeholders/writing-typing-keyboard-technology-white-vintage.jpg',
			'description' => 'A hit spy novel or a love letter? Anything you type using this vintage typewriter from the 20s is bound to make a mark.',
			'price'       => 90,
		],
		[
			'title'       => 'Leather-Clad Leisure Chair',
			'image'       => 'assets/images/pattern-placeholders/table-wood-house-chair-floor-window.jpg',
			'description' => 'Sit back and relax in this comfy designer chair. High-grain leather and steel frame add luxury to your your leisure.',
			'price'       => 249,
		],
		[
			'title'       => 'Black and White',
			'image'       => 'assets/images/pattern-placeholders/white-black-black-and-white-photograph-monochrome-photography.jpg',
			'description' => 'This 24" x 30" high-quality print just exudes summer. Hang it on the wall and forget about the world outside.',
			'price'       => 115,
		],
		[
			'title'       => '3-Speed Bike',
			'image'       => 'assets/images/pattern-placeholders/road-sport-vintage-wheel-retro-old.jpg',
			'description' => 'Zoom through the streets on this premium 3-speed bike. Manufactured and assembled in Germany in the 80s.',
			'price'       => 115,
		],
		[
			'title'       => 'Hi-Fi Headphones',
			'image'       => 'assets/images/pattern-placeholders/man-person-music-black-and-white-white-photography.jpg',
			'description' => 'Experience your favorite songs in a new way with these premium hi-fi headphones.',
			'price'       => 125,
		],
		[
			'title'       => 'Retro Glass Jug (330 ml)',
			'image'       => 'assets/images/pattern-placeholders/drinkware-liquid-tableware-dishware-bottle-fluid.jpg',
			'description' => 'Thick glass and a classic silhouette make this jug a must-have for any retro-inspired kitchen.',
			'price'       => 115,
		],
	];


	/**
	 * Return all dummy products that were not modified by the store owner.
	 *
	 * @return array|WP_Error An array with the dummy products that need to have their content updated by AI.
	 */
	public function fetch_dummy_products_to_update() {
		$real_products       = $this->fetch_product_ids();
		$real_products_count = count( $real_products );

		if ( is_array( $real_products ) && $real_products_count > 6 ) {
			return array(
				'product_content' => array(),
			);
		}

		$dummy_products       = $this->fetch_product_ids( 'dummy' );
		$dummy_products_count = count( $dummy_products );
		$products_to_create   = max( 0, 6 - $real_products_count - $dummy_products_count );
		while ( $products_to_create > 0 ) {
			$this->create_new_product( self::DUMMY_PRODUCTS[ $products_to_create - 1 ] );
			--$products_to_create;
		}

		// Identify dummy products that need to have their content updated.
		$dummy_products_ids = $this->fetch_product_ids( 'dummy' );
		if ( ! is_array( $dummy_products_ids ) ) {
			return new \WP_Error( 'failed_to_fetch_dummy_products', __( 'Failed to fetch dummy products.', 'woocommerce' ) );
		}

		$dummy_products = array_map(
			function ( $product ) {
				return wc_get_product( $product->ID );
			},
			$dummy_products_ids
		);

		$dummy_products_to_update = [];
		foreach ( $dummy_products as $dummy_product ) {
			if ( ! $dummy_product instanceof \WC_Product ) {
				continue;
			}

			$should_update_dummy_product = $this->should_update_dummy_product( $dummy_product );

			if ( $should_update_dummy_product ) {
				$dummy_products_to_update[] = $dummy_product;
			}
		}

		return $dummy_products_to_update;
	}

	/**
	 * Verify if the dummy product should have its content generated and managed by AI.
	 *
	 * @param \WC_Product $dummy_product The dummy product.
	 *
	 * @return bool
	 */
	public function should_update_dummy_product( $dummy_product ): bool {
		$date_created  = $dummy_product->get_date_created();
		$date_modified = $dummy_product->get_date_modified();

		if ( ! $date_created instanceof \WC_DateTime || ! $date_modified instanceof \WC_DateTime ) {
			return false;
		}

		$formatted_date_created  = $dummy_product->get_date_created()->date( 'Y-m-d H:i:s' );
		$formatted_date_modified = $dummy_product->get_date_modified()->date( 'Y-m-d H:i:s' );

		$timestamp_created  = strtotime( $formatted_date_created );
		$timestamp_modified = strtotime( $formatted_date_modified );
		$timestamp_current  = time();

		$dummy_product_recently_modified = abs( $timestamp_current - $timestamp_modified ) < 10;
		$dummy_product_not_modified      = abs( $timestamp_modified - $timestamp_created ) < 60;

		if ( $dummy_product_not_modified || $dummy_product_recently_modified ) {
			return true;
		}

		return false;
	}

	/**
	 * Creates a new product and assigns the _headstart_post meta to it.
	 *
	 * @param array $product_data The product data.
	 *
	 * @return bool|int|\WP_Error
	 */
	public function create_new_product( $product_data ) {
		$product          = new \WC_Product();
		$image_src        = plugins_url( $product_data['image'], dirname( __DIR__, 2 ) );
		$image_alt        = $product_data['title'];
		$product_image_id = $this->product_image_upload( $product->get_id(), $image_src, $image_alt );

		$saved_product = $this->product_update( $product, $product_image_id, $product_data['title'], $product_data['description'], $product_data['price'] );

		if ( is_wp_error( $saved_product ) ) {
			return $saved_product;
		}

		return update_post_meta( $saved_product, '_headstart_post', true );
	}

	/**
	 * Return all existing products that have the _headstart_post meta assigned to them.
	 *
	 * @param string $type The type of products to fetch.
	 *
	 * @return array|null
	 */
	public function fetch_product_ids( string $type = 'user_created' ) {
		global $wpdb;

		if ( 'user_created' === $type ) {
			return $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE ID NOT IN ( SELECT p.ID FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE pm.meta_key = %s AND p.post_type = 'product' AND p.post_status = 'publish' ) AND post_type = 'product' AND post_status = 'publish' LIMIT 6", '_headstart_post' ) );
		}

		return $wpdb->get_results( $wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE pm.meta_key = %s AND p.post_type = 'product' AND p.post_status = 'publish'", '_headstart_post' ) );
	}

	/**
	 * Upload the image for the product.
	 *
	 * @param int    $product_id The product ID.
	 * @param string $image_src The image source.
	 * @param string $image_alt The image alt.
	 *
	 * @return int|string|WP_Error
	 */
	private function product_image_upload( $product_id, $image_src, $image_alt ) {
		require_once ABSPATH . 'wp-admin/includes/media.php';
		require_once ABSPATH . 'wp-admin/includes/file.php';
		require_once ABSPATH . 'wp-admin/includes/image.php';

		// Since the media_sideload_image function is expensive and can take longer to complete
		// the process of downloading the external image and uploading it to the media library,
		// here we are increasing the time limit to avoid any issues.
		set_time_limit( 150 );
		wp_raise_memory_limit( 'image' );

		return media_sideload_image( $image_src, $product_id, $image_alt, 'id' );
	}

	/**
	 * Update the product with the new content.
	 *
	 * @param \WC_Product         $product The product.
	 * @param int|string|WP_Error $product_image_id The product image ID.
	 * @param string              $product_title The product title.
	 * @param string              $product_description The product description.
	 * @param int                 $product_price The product price.
	 *
	 * @return int|\WP_Error
	 */
	private function product_update( $product, $product_image_id, $product_title, $product_description, $product_price ) {
		if ( ! $product instanceof \WC_Product ) {
			return new WP_Error( 'invalid_product', __( 'Invalid product.', 'woocommerce' ) );
		}

		if ( ! is_wp_error( $product_image_id ) ) {
			$product->set_image_id( $product_image_id );
		} else {
			wc_get_logger()->warning(
				sprintf(
					// translators: %s is a generated error message.
					__( 'The image upload failed: "%s", creating the product without image', 'woocommerce' ),
					$product_image_id->get_error_message()
				),
			);
		}
		$product->set_name( $product_title );
		$product->set_description( $product_description );
		$product->set_price( $product_price );
		$product->set_regular_price( $product_price );
		$product->set_slug( sanitize_title( $product_title ) );
		$product->save();

		return $product->get_id();
	}
}
PK     \ꒁ      AIContent/ContentProcessor.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\AIContent;

/**
 * ContentProcessor class.
 *
 * Process images for content
 *
 * @internal
 * @deprecated This class can't be removed due https://github.com/woocommerce/woocommerce/issues/52311.
 */
class ContentProcessor {}
PK     \냹       AIContent/PatternsDictionary.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\AIContent;

/**
 * Patterns Dictionary class.
 *
 * @internal
 * @deprecated This class can't be removed due https://github.com/woocommerce/woocommerce/issues/52311.
 */
class PatternsDictionary {}
PK     \y1;        AIContent/PatternsHelper.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\AIContent;

/**
 * Patterns Helper class.
 *
 * @internal
 * @deprecated This class can't be removed due https://github.com/woocommerce/woocommerce/issues/52311.
 */
class PatternsHelper {}
PK     \)J1  1    Assets/Api.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Assets;

use Automattic\WooCommerce\Blocks\Domain\Package;
use Exception;
use Automattic\Jetpack\Constants;
/**
 * The Api class provides an interface to various asset registration helpers.
 *
 * Contains asset api methods
 *
 * @since 2.5.0
 */
class Api {

	/**
	 * Stores the prefixed WC version. Used because the WC Blocks version has not been updated since the monorepo merge.
	 *
	 * @var string
	 */
	public $wc_version;

	/**
	 * Stores inline scripts already enqueued.
	 *
	 * @var array
	 */
	private $inline_scripts = [];

	/**
	 * Determines if caching is enabled for script data.
	 *
	 * @var boolean
	 */
	private $disable_cache = false;

	/**
	 * Stores loaded script data for the current request
	 *
	 * @var array|null
	 */
	private $script_data = null;

	/**
	 * Tracks whether script_data was modified during the current request.
	 *
	 * @var boolean
	 */
	private $script_data_modified = false;

	/**
	 * Stores the hash for the script data, made up of the site url, plugin version and package path.
	 *
	 * @var string
	 */
	private $script_data_hash;

	/**
	 * Stores the transient key used to cache the script data. This will change if the site is accessed via HTTPS or HTTP.
	 *
	 * @var string
	 */
	private $script_data_transient_key = 'woocommerce_blocks_asset_api_script_data';

	/**
	 * Reference to the Package instance
	 *
	 * @var Package
	 */
	private $package;

	/**
	 * Constructor for class
	 *
	 * @param Package $package An instance of Package.
	 */
	public function __construct( Package $package ) {
		// Use wc- prefix here to prevent collisions when WC Core version catches up to a version previously used by the WC Blocks feature plugin.
		$this->wc_version    = 'wc-' . Constants::get_constant( 'WC_VERSION' );
		$this->package       = $package;
		$this->disable_cache = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) || wp_get_environment_type() !== 'production';

		// If the site is accessed via HTTPS, change the transient key. This is to prevent the script URLs being cached
		// with the first scheme they are accessed on after cache expiry.
		if ( is_ssl() ) {
			$this->script_data_transient_key .= '_ssl';
		}
		if ( ! $this->disable_cache ) {
			$this->script_data_hash = $this->get_script_data_hash();
		}
		add_action( 'shutdown', array( $this, 'update_script_data_cache' ), 20 );
	}

	/**
	 * Get the file modified time as a cache buster if we're in dev mode.
	 *
	 * @param string $file Local path to the file (relative to the plugin
	 *                     directory).
	 * @return string The cache buster value to use for the given file.
	 */
	protected function get_file_version( $file ) {
		if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( $this->package->get_path() . $file ) ) {
			return filemtime( $this->package->get_path( trim( $file, '/' ) ) );
		}
		return $this->wc_version;
	}

	/**
	 * Retrieve the url to an asset for this plugin.
	 *
	 * @param string $relative_path An optional relative path appended to the
	 *                              returned url.
	 *
	 * @return string
	 */
	protected function get_asset_url( $relative_path = '' ) {
		return $this->package->get_url( $relative_path );
	}

	/**
	 * Get the path to a block's metadata
	 *
	 * @param string $block_name The block to get metadata for.
	 * @param string $path Optional. The path to the metadata file inside the 'assets/client/blocks' folder.
	 *
	 * @return string|boolean False if metadata file is not found for the block.
	 */
	public function get_block_metadata_path( $block_name, $path = '' ) {
		$path_to_metadata_from_plugin_root = $this->package->get_path( 'assets/client/blocks/' . $path . $block_name . '/block.json' );
		if ( ! file_exists( $path_to_metadata_from_plugin_root ) ) {
			return false;
		}
		return $path_to_metadata_from_plugin_root;
	}

	/**
	 * Generates a hash containing the site url, plugin version and package path.
	 *
	 * Moving the plugin, changing the version, or changing the site url will result in a new hash and the cache will be invalidated.
	 *
	 * @return string The generated hash.
	 */
	private function get_script_data_hash() {
		return md5( get_option( 'siteurl', '' ) . $this->wc_version . $this->package->get_path() );
	}

	/**
	 * Initialize and load cached script data from the transient cache.
	 *
	 * @return array
	 */
	private function get_cached_script_data() {
		if ( $this->disable_cache ) {
			return [];
		}

		$transient_value = json_decode( (string) get_transient( $this->script_data_transient_key ), true );

		if (
			json_last_error() !== JSON_ERROR_NONE ||
			empty( $transient_value ) ||
			empty( $transient_value['script_data'] ) ||
			empty( $transient_value['version'] ) ||
			$transient_value['version'] !== $this->wc_version ||
			empty( $transient_value['hash'] ) ||
			$transient_value['hash'] !== $this->script_data_hash
		) {
			return [];
		}

		return (array) ( $transient_value['script_data'] ?? [] );
	}

	/**
	 * Store all cached script data in the transient cache.
	 */
	public function update_script_data_cache() {
		if ( is_null( $this->script_data ) || $this->disable_cache ) {
			return;
		}
		if ( ! $this->script_data_modified ) {
			return;
		}
		set_transient(
			$this->script_data_transient_key,
			wp_json_encode(
				array(
					'script_data' => $this->script_data,
					'version'     => $this->wc_version,
					'hash'        => $this->script_data_hash,
				)
			),
			DAY_IN_SECONDS * 30
		);
	}

	/**
	 * Use package path to find an asset data file and return the data.
	 *
	 * @param string $filename The filename of the asset.
	 * @return array The asset data.
	 */
	public function get_asset_data( $filename ) {
		$asset_path = $this->package->get_path( $filename );
		$asset      = file_exists( $asset_path ) ? require $asset_path : [];
		return $asset;
	}

	/**
	 * Get src, version and dependencies given a script relative src.
	 *
	 * @param string $relative_src Relative src to the script.
	 * @param array  $dependencies Optional. An array of registered script handles this script depends on. Default empty array.
	 *
	 * @return array src, version and dependencies of the script.
	 */
	public function get_script_data( $relative_src, $dependencies = [] ) {
		if ( ! $relative_src ) {
			return array(
				'src'          => '',
				'version'      => '1',
				'dependencies' => $dependencies,
			);
		}

		if ( is_null( $this->script_data ) ) {
			$this->script_data = $this->get_cached_script_data();
		}

		if ( empty( $this->script_data[ $relative_src ] ) ) {
			$asset_path = $this->package->get_path( str_replace( '.js', '.asset.php', $relative_src ) );
			// The following require is safe because we are checking if the file exists and it is not a user input.
			// nosemgrep audit.php.lang.security.file.inclusion-arg.
			$asset = file_exists( $asset_path ) ? require $asset_path : [];

			$this->script_data[ $relative_src ] = array(
				'src'          => $this->get_asset_url( $relative_src ),
				'version'      => ! empty( $asset['version'] ) ? $asset['version'] : $this->get_file_version( $relative_src ),
				'dependencies' => ! empty( $asset['dependencies'] ) ? $asset['dependencies'] : [],
			);
			$this->script_data_modified         = true;
		}

		// Return asset details as well as the requested dependencies array.
		return [
			'src'          => $this->script_data[ $relative_src ]['src'],
			'version'      => $this->script_data[ $relative_src ]['version'],
			'dependencies' => array_merge( $this->script_data[ $relative_src ]['dependencies'], $dependencies ),
		];
	}

	/**
	 * Registers a script according to `wp_register_script`, adding the correct prefix, and additionally loading translations.
	 *
	 * When creating script assets, the following rules should be followed:
	 *   1. All asset handles should have a `wc-` prefix.
	 *   2. If the asset handle is for a Block (in editor context) use the `-block` suffix.
	 *   3. If the asset handle is for a Block (in frontend context) use the `-block-frontend` suffix.
	 *   4. If the asset is for any other script being consumed or enqueued by the blocks plugin, use the `wc-blocks-` prefix.
	 *
	 * @since 2.5.0
	 * @throws Exception If the registered script has a dependency on itself.
	 *
	 * @param string $handle        Unique name of the script.
	 * @param string $relative_src  Relative url for the script to the path from plugin root.
	 * @param array  $dependencies  Optional. An array of registered script handles this script depends on. Default empty array.
	 * @param bool   $has_i18n      Optional. Whether to add a script translation call to this file. Default: true.
	 */
	public function register_script( $handle, $relative_src, $dependencies = [], $has_i18n = true ) {
		$script_data = $this->get_script_data( $relative_src, $dependencies );

		if ( in_array( $handle, $script_data['dependencies'], true ) ) {
			if ( wp_get_environment_type() === 'development' ) {
				$dependencies = array_diff( $script_data['dependencies'], [ $handle ] );
					add_action(
						'admin_notices',
						function () use ( $handle ) {
								echo '<div class="error"><p>';
								/* translators: %s file handle name. */
								printf( esc_html__( 'Script with handle %s had a dependency on itself which has been removed. This is an indicator that your JS code has a circular dependency that can cause bugs.', 'woocommerce' ), esc_html( $handle ) );
								echo '</p></div>';
						}
					);
			} else {
				throw new Exception( sprintf( 'Script with handle %s had a dependency on itself. This is an indicator that your JS code has a circular dependency that can cause bugs.', $handle ) );
			}
		}

		/**
		 * Filters the list of script dependencies.
		 *
		 * @since 3.0.0
		 *
		 * @param array $dependencies The list of script dependencies.
		 * @param string $handle The script's handle.
		 * @return array
		 */
		$script_dependencies = apply_filters( 'woocommerce_blocks_register_script_dependencies', $script_data['dependencies'], $handle );

		wp_register_script( $handle, $script_data['src'], $script_dependencies, $script_data['version'], true );

		if ( $has_i18n && function_exists( 'wp_set_script_translations' ) ) {
			wp_set_script_translations( $handle, 'woocommerce', $this->package->get_path( 'languages' ) );
			wp_set_script_translations( $handle, 'woocommerce', $this->package->get_path( 'i18n/languages' ) );
		}
	}

	/**
	 * Registers a style according to `wp_register_style`.
	 *
	 * @since 2.5.0
	 * @since 2.6.0 Change src to be relative source.
	 *
	 * @param string  $handle       Name of the stylesheet. Should be unique.
	 * @param string  $relative_src Relative source of the stylesheet to the plugin path.
	 * @param array   $deps         Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
	 * @param string  $media        Optional. The media for which this stylesheet has been defined. Default 'all'. Accepts media types like
	 *                              'all', 'print' and 'screen', or media queries like '(orientation: portrait)' and '(max-width: 640px)'.
	 * @param boolean $rtl   Optional. Whether or not to register RTL styles.
	 */
	public function register_style( $handle, $relative_src, $deps = [], $media = 'all', $rtl = false ) {
		$filename = str_replace( plugins_url( '/', dirname( __DIR__ ) ), '', $relative_src );
		$src      = $this->get_asset_url( $relative_src );
		$ver      = $this->get_file_version( $filename );
		wp_register_style( $handle, $src, $deps, $ver, $media );

		if ( $rtl ) {
			wp_style_add_data( $handle, 'rtl', 'replace' );
		}
	}

	/**
	 * Returns the appropriate asset path for current builds.
	 *
	 * @param   string $filename  Filename for asset path (without extension).
	 * @param   string $type      File type (.css or .js).
	 * @return  string             The generated path.
	 */
	public function get_block_asset_build_path( $filename, $type = 'js' ) {
		return "assets/client/blocks/$filename.$type";
	}

	/**
	 * Adds an inline script, once.
	 *
	 * @param string $handle Script handle.
	 * @param string $script Script contents.
	 */
	public function add_inline_script( $handle, $script ) {
		if ( ! empty( $this->inline_scripts[ $handle ] ) && in_array( $script, $this->inline_scripts[ $handle ], true ) ) {
			return;
		}

		wp_add_inline_script( $handle, $script );

		if ( isset( $this->inline_scripts[ $handle ] ) ) {
			$this->inline_scripts[ $handle ][] = $script;
		} else {
			$this->inline_scripts[ $handle ] = array( $script );
		}
	}
}
PK     \t77  77    Assets/AssetDataRegistry.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Assets;

use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\Hydration;
use Automattic\WooCommerce\Internal\Logging\RemoteLogger;
use Exception;
use InvalidArgumentException;

/**
 * Class instance for registering data used on the current view session by
 * assets.
 *
 * Holds data registered for output on the current view session when
 * `wc-settings` is enqueued( directly or via dependency )
 *
 * @since 2.5.0
 */
class AssetDataRegistry {
	/**
	 * Contains registered data.
	 *
	 * @var array
	 */
	private $data = [];

	/**
	 * Contains preloaded API data.
	 *
	 * @var array
	 */
	private $preloaded_api_requests = [];

	/**
	 * Lazy data is an array of closures that will be invoked just before
	 * asset data is generated for the enqueued script.
	 *
	 * @var array
	 */
	private $lazy_data = [];

	/**
	 * Asset handle for registered data.
	 *
	 * @var string
	 */
	private $handle = 'wc-settings';

	/**
	 * Asset API interface for various asset registration.
	 *
	 * @var API
	 */
	private $api;

	/**
	 * Constructor
	 *
	 * @param Api $asset_api  Asset API interface for various asset registration.
	 */
	public function __construct( Api $asset_api ) {
		$this->api = $asset_api;
		$this->init();
	}

	/**
	 * Hook into WP asset registration for enqueueing asset data.
	 */
	protected function init() {
		add_action( 'init', array( $this, 'register_data_script' ) );
		add_action( is_admin() ? 'admin_print_footer_scripts' : 'wp_print_footer_scripts', array( $this, 'enqueue_asset_data' ), 1 );
	}

	/**
	 * Exposes core data via the wcSettings global. This data is shared throughout the client.
	 *
	 * Settings that are used by various components or multiple blocks should be added here. Note, that settings here are
	 * global so be sure not to add anything heavy if possible.
	 *
	 * @return array  An array containing core data.
	 */
	protected function get_core_data() {
		return [
			'adminUrl'               => admin_url(),
			'countries'              => WC()->countries->get_countries(),
			'currency'               => $this->get_currency_data(),
			'currentUserId'          => get_current_user_id(),
			'currentUserIsAdmin'     => current_user_can( 'manage_woocommerce' ),
			'currentThemeIsFSETheme' => wp_is_block_theme(),
			'dateFormat'             => wc_date_format(),
			'homeUrl'                => esc_url( home_url( '/' ) ),
			'locale'                 => $this->get_locale_data(),
			'isRemoteLoggingEnabled' => wc_get_container()->get( RemoteLogger::class )->is_remote_logging_allowed(),
			'dashboardUrl'           => wc_get_account_endpoint_url( 'dashboard' ),
			'orderStatuses'          => $this->get_order_statuses(),
			'placeholderImgSrc'      => wc_placeholder_img_src(),
			'productsSettings'       => $this->get_products_settings(),
			'siteTitle'              => wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ),
			'storePages'             => $this->get_store_pages(),
			'wcAssetUrl'             => plugins_url( 'assets/', WC_PLUGIN_FILE ),
			'wcVersion'              => defined( 'WC_VERSION' ) ? WC_VERSION : '',
			'wpLoginUrl'             => wp_login_url(),
			'wpVersion'              => get_bloginfo( 'version' ),
		];
	}

	/**
	 * Get currency data to include in settings.
	 *
	 * @return array
	 */
	protected function get_currency_data() {
		$currency = get_woocommerce_currency();

		return [
			'code'              => $currency,
			'precision'         => wc_get_price_decimals(),
			'symbol'            => html_entity_decode( get_woocommerce_currency_symbol( $currency ) ),
			'symbolPosition'    => get_option( 'woocommerce_currency_pos' ),
			'decimalSeparator'  => wc_get_price_decimal_separator(),
			'thousandSeparator' => wc_get_price_thousand_separator(),
			'priceFormat'       => html_entity_decode( get_woocommerce_price_format() ),
		];
	}

	/**
	 * Get locale data to include in settings.
	 *
	 * @return array
	 */
	protected function get_locale_data() {
		global $wp_locale;

		return [
			'siteLocale'    => get_locale(),
			'userLocale'    => get_user_locale(),
			'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ),
		];
	}

	/**
	 * Get store pages to include in settings.
	 *
	 * @return array
	 */
	protected function get_store_pages() {
		$store_pages = [
			'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(),
		];

		_prime_post_caches( array_values( $store_pages ), false, false );

		return array_map(
			[ $this, 'format_page_resource' ],
			$store_pages
		);
	}

	/**
	 * Get product related settings.
	 *
	 * Note: For the time being we are exposing only the settings that are used by blocks.
	 *
	 * @return array
	 */
	protected function get_products_settings() {
		return [
			'cartRedirectAfterAdd' => get_option( 'woocommerce_cart_redirect_after_add' ) === 'yes',
		];
	}

	/**
	 * Format a page object into a standard array of data.
	 *
	 * @param WP_Post|int $page Page object or ID.
	 * @return array
	 */
	protected function format_page_resource( $page ) {
		if ( is_numeric( $page ) && $page > 0 ) {
			$page = get_post( $page );
		}
		if ( ! is_a( $page, '\WP_Post' ) || 'publish' !== $page->post_status ) {
			return [
				'id'        => 0,
				'title'     => '',
				'permalink' => false,
			];
		}
		return [
			'id'        => $page->ID,
			'title'     => $page->post_title,
			'permalink' => get_permalink( $page->ID ),
		];
	}

	/**
	 * Returns block-related data for enqueued wc-settings script.
	 * Format order statuses by removing a leading 'wc-' if present.
	 *
	 * @return array formatted statuses.
	 */
	protected function get_order_statuses() {
		$formatted_statuses = array();
		foreach ( wc_get_order_statuses() as $key => $value ) {
			$formatted_key                        = preg_replace( '/^wc-/', '', $key );
			$formatted_statuses[ $formatted_key ] = $value;
		}
		return $formatted_statuses;
	}

	/**
	 * Used for on demand initialization of asset data and registering it with
	 * the internal data registry.
	 *
	 * Note: core data will overwrite any externally registered data via the api.
	 */
	protected function initialize_core_data() {
		/**
		 * Filters the array of shared settings.
		 *
		 * Low level hook for registration of new data late in the cycle. This is deprecated.
		 * Instead, use the data api:
		 *
		 * ```php
		 * Automattic\WooCommerce\Blocks\Package::container()->get( Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry::class )->add( $key, $value )
		 * ```
		 *
		 * @since 5.0.0
		 *
		 * @deprecated
		 * @param array $data Settings data.
		 * @return array
		 */
		$settings = apply_filters( 'woocommerce_shared_settings', $this->data );

		// Surface a deprecation warning in the error console.
		if ( has_filter( 'woocommerce_shared_settings' ) ) {
			$error_handle  = 'deprecated-shared-settings-error';
			$error_message = '`woocommerce_shared_settings` filter in Blocks is deprecated. See https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/docs/contributors/block-assets.md';
			// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter,WordPress.WP.EnqueuedResourceParameters.MissingVersion
			wp_register_script( $error_handle, '' );
			wp_enqueue_script( $error_handle );
			wp_add_inline_script(
				$error_handle,
				sprintf( 'console.warn( "%s" );', $error_message )
			);
		}

		$core_data                            = $this->get_core_data();
		$core_data['experimentalWcRestApiV4'] = Features::is_enabled( 'rest-api-v4' );
		// note this WILL wipe any data already registered to these keys because they are protected.
		$this->data = array_replace_recursive( $settings, $core_data );
	}

	/**
	 * Loops through each registered lazy data callback and adds the returned
	 * value to the data array.
	 *
	 * This method is executed right before preparing the data for printing to
	 * the rendered screen.
	 *
	 * @return void
	 */
	protected function execute_lazy_data() {
		foreach ( $this->lazy_data as $key => $callback ) {
			$this->data[ $key ] = $callback();
		}
	}

	/**
	 * Exposes private registered data to child classes.
	 *
	 * @return array  The registered data on the private data property
	 */
	protected function get() {
		return $this->data;
	}

	/**
	 * Allows checking whether a key exists.
	 *
	 * @param string $key  The key to check if exists.
	 * @return bool  Whether the key exists in the current data registry.
	 */
	public function exists( $key ) {
		return array_key_exists( $key, $this->data );
	}

	/**
	 * Interface for adding data to the registry.
	 *
	 * You can only register data that is not already in the registry identified by the given key. If there is a
	 * duplicate found, unless $ignore_duplicates is true, an exception will be thrown.
	 *
	 * @param string  $key              The key used to reference the data being registered. This should use camelCase.
	 * @param mixed   $data             If not a function, registered to the registry as is. If a function, then the
	 *                                  callback is invoked right before output to the screen.
	 * @param boolean $check_key_exists Deprecated. If set to true, duplicate data will be ignored if the key exists.
	 *                                  If false, duplicate data will cause an exception.
	 */
	public function add( $key, $data, $check_key_exists = false ) {
		if ( $check_key_exists ) {
			wc_deprecated_argument( 'Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry::add()', '8.9', 'The $check_key_exists parameter is no longer used: all duplicate data will be ignored if the key exists by default' );
		}

		$this->add_data( $key, $data );
	}

	/**
	 * Hydrate from the API.
	 *
	 * @param string $path REST API path to preload.
	 */
	public function hydrate_api_request( $path ) {
		if ( ! isset( $this->preloaded_api_requests[ $path ] ) ) {
			$this->preloaded_api_requests[ $path ] = Package::container()->get( Hydration::class )->get_rest_api_response_data( $path );
		}
	}

	/**
	 * Hydrate some data from the API.
	 *
	 * @param string  $key  The key used to reference the data being registered.
	 * @param string  $path REST API path to preload.
	 * @param boolean $check_key_exists If set to true, duplicate data will be ignored if the key exists.
	 *                                  If false, duplicate data will cause an exception.
	 *
	 * @throws InvalidArgumentException  Only throws when site is in debug mode. Always logs the error.
	 */
	public function hydrate_data_from_api_request( $key, $path, $check_key_exists = false ) {
		$this->add(
			$key,
			function () use ( $path ) {
				if ( isset( $this->preloaded_api_requests[ $path ], $this->preloaded_api_requests[ $path ]['body'] ) ) {
					return $this->preloaded_api_requests[ $path ]['body'];
				}
				$response = Package::container()->get( Hydration::class )->get_rest_api_response_data( $path );
				return $response['body'] ?? '';
			},
			$check_key_exists
		);
	}

	/**
	 * Adds a page permalink to the data registry.
	 *
	 * @param integer $page_id Page ID to add to the registry.
	 */
	public function register_page_id( $page_id ) {
		$permalink = $page_id ? get_permalink( $page_id ) : false;

		if ( $permalink ) {
			$this->data[ 'page-' . $page_id ] = $permalink;
		}
	}

	/**
	 * Callback for registering the data script via WordPress API.
	 *
	 * @return void
	 */
	public function register_data_script() {
		$this->api->register_script(
			$this->handle,
			'assets/client/blocks/wc-settings.js',
			[ 'wp-api-fetch' ],
			true
		);
	}

	/**
	 * Callback for enqueuing asset data via the WP api.
	 *
	 * Note: while this is hooked into print/admin_print_scripts, it still only
	 * happens if the script attached to `wc-settings` handle is enqueued. This
	 * is done to allow for any potentially expensive data generation to only
	 * happen for routes that need it.
	 */
	public function enqueue_asset_data() {
		if ( wp_script_is( $this->handle, 'enqueued' ) ) {
			$this->initialize_core_data();
			$this->execute_lazy_data();

			$data                          = rawurlencode( wp_json_encode( $this->data ) );
			$wc_settings_script            = "var wcSettings = JSON.parse( decodeURIComponent( '" . esc_js( $data ) . "' ) );";
			$preloaded_api_requests_script = '';

			if ( count( $this->preloaded_api_requests ) > 0 ) {
				$preloaded_api_requests        = rawurlencode( wp_json_encode( $this->preloaded_api_requests ) );
				$preloaded_api_requests_script = "wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( JSON.parse( decodeURIComponent( '" . esc_js( $preloaded_api_requests ) . "' ) ) ) );";
			}

			wp_add_inline_script(
				$this->handle,
				$wc_settings_script . $preloaded_api_requests_script,
				'before'
			);
		}
	}

	/**
	 * See self::add() for docs.
	 *
	 * @param   string $key   Key for the data.
	 * @param   mixed  $data  Value for the data.
	 */
	protected function add_data( $key, $data ) {
		if ( ! is_string( $key ) ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
			trigger_error( esc_html__( 'Key for the data being registered must be a string', 'woocommerce' ), E_USER_WARNING );
			return;
		}
		if ( $this->exists( $key ) ) {
			return;
		}
		if ( isset( $this->data[ $key ] ) ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
			trigger_error( esc_html__( 'Overriding existing data with an already registered key is not allowed', 'woocommerce' ), E_USER_WARNING );
			return;
		}
		if ( \is_callable( $data ) ) {
			$this->lazy_data[ $key ] = $data;
			return;
		}
		$this->data[ $key ] = $data;
	}

	/**
	 * Exposes whether the current site is in debug mode or not.
	 *
	 * @return boolean  True means the site is in debug mode.
	 */
	protected function debug() {
		return defined( 'WP_DEBUG' ) && WP_DEBUG;
	}
}
PK     \WJg*  *    DependencyDetection.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks;

use Automattic\WooCommerce\Internal\Utilities\BlocksUtil;

/**
 * DependencyDetection class.
 *
 * Provides runtime detection of extensions that use Blocks related WooCommerce globals
 * (window.wc.*) without properly declaring their PHP script dependencies.
 *
 * This runs by default to warn developers about missing dependencies.
 *
 * @since 10.5.0
 * @internal
 */
final class DependencyDetection {

	/**
	 * WooCommerce blocks that use the tracked globals.
	 *
	 * Detection script only runs on pages containing these blocks.
	 *
	 * @var array<string>
	 */
	private const TRACKED_BLOCKS = array(
		'woocommerce/checkout',
		'woocommerce/cart',
		'woocommerce/mini-cart',
	);

	/**
	 * Maps window.wc.* property names to their required script handles.
	 *
	 * This is the source of truth for both PHP and JS dependency detection.
	 * Based on wcDepMap and wcHandleMap in client/blocks/bin/webpack-helpers.js.
	 *
	 * @var array<string, string>
	 */
	private const WC_GLOBAL_EXPORTS = array(
		'wcBlocksRegistry'      => 'wc-blocks-registry',
		'wcSettings'            => 'wc-settings',
		'wcBlocksData'          => 'wc-blocks-data-store',
		'data'                  => 'wc-store-data',
		'wcBlocksSharedContext' => 'wc-blocks-shared-context',
		'wcBlocksSharedHocs'    => 'wc-blocks-shared-hocs',
		'priceFormat'           => 'wc-price-format',
		'blocksCheckout'        => 'wc-blocks-checkout',
		'blocksCheckoutEvents'  => 'wc-blocks-checkout-events',
		'blocksComponents'      => 'wc-blocks-components',
		'wcTypes'               => 'wc-types',
		'sanitize'              => 'wc-sanitize',
	);

	/**
	 * Whether the proxy script was output.
	 *
	 * Used to ensure we only output the registry if the proxy was set up.
	 *
	 * @var bool
	 */
	private bool $proxy_output = false;

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->init();
	}

	/**
	 * Initialize hooks.
	 *
	 * @since 10.5.0
	 */
	public function init(): void {
		// Only run when debugging is enabled.
		if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
			return;
		}

		// Output an early inline script to set up the Proxy before any other scripts run.
		add_action( 'wp_head', array( $this, 'output_early_proxy_setup' ), 1 );
		add_action( 'admin_head', array( $this, 'output_early_proxy_setup' ), 1 );

		// Output registry late when all scripts (including IntegrationInterface) are registered.
		add_action( 'wp_print_footer_scripts', array( $this, 'output_script_registry' ), 1 );
		add_action( 'admin_print_footer_scripts', array( $this, 'output_script_registry' ), 1 );
	}

	/**
	 * Output early inline script to set up the Proxy on window.wc.
	 *
	 * This must run before any WooCommerce scripts to intercept access.
	 * The script is loaded from a separate file for better IDE support and testing,
	 * but output inline to ensure correct timing (before any enqueued scripts).
	 *
	 * @since 10.5.0
	 */
	public function output_early_proxy_setup(): void {
		// Only run on pages that have the tracked blocks.
		if ( ! $this->page_has_tracked_blocks() ) {
			return;
		}

		// Load from the production assets directory (built by webpack and copied during release build).
		$script_path = __DIR__ . '/../../assets/client/blocks/dependency-detection.js';

		if ( ! file_exists( $script_path ) ) {
			return;
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local file read for inline script output.
		$script_content = file_get_contents( $script_path );

		if ( ! $script_content ) {
			return;
		}

		// Inject the global-to-handle mapping from PHP (source of truth).
		$mapping_json = \wp_json_encode( self::WC_GLOBAL_EXPORTS );
		if ( false === $mapping_json ) {
			return;
		}
		$script_content = str_replace(
			'__WC_GLOBAL_EXPORTS_PLACEHOLDER__',
			$mapping_json,
			$script_content
		);

		// Inject the WooCommerce plugin URL for script origin detection.
		// This accounts for custom plugin directories (WP_PLUGIN_DIR, WP_CONTENT_DIR).
		$wc_plugin_url  = \plugins_url( '/', WC_PLUGIN_FILE );
		$script_content = str_replace(
			'__WC_PLUGIN_URL_PLACEHOLDER__',
			'"' . esc_js( $wc_plugin_url ) . '"',
			$script_content
		);

		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Script content is from a trusted local file, JSON is safely encoded.
		echo '<script id="wc-dependency-detection">' . $script_content . '</script>' . "\n";

		$this->proxy_output = true;
	}

	/**
	 * Output the script registry JSON for dependency checking.
	 *
	 * This runs late (wp_print_footer_scripts) to ensure all scripts,
	 * including those registered via IntegrationInterface, are captured.
	 *
	 * @since 10.5.0
	 */
	public function output_script_registry(): void {
		// Only output registry if the proxy was set up earlier.
		// This avoids the duplicate page_has_tracked_blocks() check and ensures
		// we don't output a registry without a proxy to consume it.
		if ( ! $this->proxy_output ) {
			return;
		}

		// Build the script registry mapping URLs to handles and dependencies.
		$script_registry = $this->build_script_registry();
		$registry_json   = \wp_json_encode( $script_registry );

		if ( false === $registry_json ) {
			return;
		}

		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON is safely encoded by wp_json_encode.
		echo '<script id="wc-dependency-detection-registry">if(typeof window.wc.wcUpdateDependencyRegistry==="function"){window.wc.wcUpdateDependencyRegistry(' . $registry_json . ');}</script>' . "\n";
	}

	/**
	 * Build a registry of all enqueued scripts with their URLs and dependencies.
	 *
	 * @return array<string, array{handle: string, deps: array<string>}>
	 */
	private function build_script_registry(): array {
		$wp_scripts = wp_scripts();
		$registry   = array();

		foreach ( $wp_scripts->registered as $handle => $script ) {
			// Skip scripts without a source URL.
			if ( empty( $script->src ) ) {
				continue;
			}

			// Get the full URL.
			$src = $script->src;
			if ( ! is_string( $src ) ) {
				// Skip malformed src.
				continue;
			}
			if ( ! preg_match( '|^(https?:)?//|', $src ) ) {
				// Relative URL - make it absolute.
				$src = $wp_scripts->base_url . $src;
			}

			// Skip WooCommerce's own scripts - we don't need to check those.
			if ( $this->is_woocommerce_script( $src ) ) {
				continue;
			}

			// Skip WordPress core scripts - they won't use wc.* globals.
			if ( $this->is_wordpress_core_script( $src ) ) {
				continue;
			}

			// Normalize the URL for consistent matching.
			$src = $this->normalize_url( $src );

			$registry[ $src ] = array(
				'handle' => $handle,
				'deps'   => $this->get_all_dependencies( $script->deps ),
			);
		}

		return $registry;
	}

	/**
	 * Check if a script URL belongs to WooCommerce core.
	 *
	 * Checks if the script is loaded from the WooCommerce core plugin directory,
	 * not from third-party extensions that may use similar handle naming.
	 *
	 * @param string $url Script URL.
	 * @return bool
	 */
	private function is_woocommerce_script( string $url ): bool {
		// Get the WooCommerce plugin URL (accounts for custom plugin directories).
		$wc_plugin_url = \plugins_url( '/', WC_PLUGIN_FILE );

		// Check if the URL starts with the WooCommerce plugin URL and is in a known subdirectory.
		if ( strpos( $url, $wc_plugin_url ) !== 0 ) {
			return false;
		}

		// Get the path after the WooCommerce plugin URL.
		$relative_path = substr( $url, strlen( $wc_plugin_url ) );

		// Check if it's in one of the known WooCommerce asset directories.
		return (bool) preg_match( '#^(client|assets|build|vendor)/#', $relative_path );
	}

	/**
	 * Check if a script URL belongs to WordPress core.
	 *
	 * WordPress core scripts (wp-includes, wp-admin) won't use wc.* globals,
	 * so we can skip them to reduce registry size.
	 *
	 * @param string $url Script URL.
	 * @return bool
	 */
	private function is_wordpress_core_script( string $url ): bool {
		return (bool) preg_match( '#/(wp-includes|wp-admin)/#', $url );
	}

	/**
	 * Recursively get all dependencies including nested ones.
	 *
	 * @param array<string> $deps Direct dependencies.
	 * @return array<string> All dependencies (flattened).
	 */
	private function get_all_dependencies( array $deps ): array {
		$wp_scripts      = wp_scripts();
		$all_deps        = array();
		$deps_to_process = $deps;

		while ( ! empty( $deps_to_process ) ) {
			$handle = array_shift( $deps_to_process );

			if ( in_array( $handle, $all_deps, true ) ) {
				continue;
			}

			$all_deps[] = $handle;

			// Add nested dependencies to process.
			if ( isset( $wp_scripts->registered[ $handle ] ) ) {
				foreach ( $wp_scripts->registered[ $handle ]->deps as $nested_dep ) {
					if ( ! in_array( $nested_dep, $all_deps, true ) ) {
						$deps_to_process[] = $nested_dep;
					}
				}
			}
		}

		// Filter to only include WooCommerce handles we care about.
		$wc_handles = array_values( self::WC_GLOBAL_EXPORTS );
		return array_values(
			array_filter(
				$all_deps,
				function ( $dep ) use ( $wc_handles ) {
					return in_array( $dep, $wc_handles, true );
				}
			)
		);
	}

	/**
	 * Check if the current page contains any of the tracked blocks.
	 * Checks post content, widget areas, and template parts (header) for blocks.
	 *
	 * @return bool True if page has tracked blocks.
	 */
	private function page_has_tracked_blocks(): bool {
		// Check post content for blocks.
		foreach ( self::TRACKED_BLOCKS as $block_name ) {
			if ( \has_block( $block_name ) ) {
				return true;
			}
		}

		// Check widget areas for mini-cart (classic themes).
		$mini_cart_in_widgets = BlocksUtil::get_blocks_from_widget_area( 'woocommerce/mini-cart' );
		if ( ! empty( $mini_cart_in_widgets ) ) {
			return true;
		}

		// Check header template part for mini-cart (block themes).
		try {
			$mini_cart_in_header = BlocksUtil::get_block_from_template_part( 'woocommerce/mini-cart', 'header' );
			if ( ! empty( $mini_cart_in_header ) ) {
				return true;
			}
		} catch ( \Throwable $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
			// Template part may not exist in all themes, silently continue.
		}

		return false;
	}

	/**
	 * Normalize a URL by removing query strings and hash fragments.
	 *
	 * This helps match URLs in stack traces which don't include query strings.
	 *
	 * @param string $url URL to normalize.
	 * @return string Normalized URL without query string or hash.
	 */
	private function normalize_url( string $url ): string {
		$scheme = wp_parse_url( $url, PHP_URL_SCHEME );
		$host   = wp_parse_url( $url, PHP_URL_HOST );
		$path   = wp_parse_url( $url, PHP_URL_PATH );

		if ( $scheme && $host && $path ) {
			$port = wp_parse_url( $url, PHP_URL_PORT );
			return $scheme . '://' . $host . ( $port ? ':' . $port : '' ) . $path;
		}

		return $url;
	}
}
PK     \"w      TemplateOptions.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks;

use Automattic\WooCommerce\Blocks\Options;

/**
 * TemplateOptions class.
 *
 * @internal
 */
class TemplateOptions {

	/**
	 * Initialization method.
	 */
	public function init() {
		add_action( 'after_switch_theme', array( $this, 'check_should_use_blockified_product_grid_templates' ), 10, 2 );
	}

	/**
	 * Checks the old and current themes and determines if the "wc_blocks_use_blockified_product_grid_block_as_template"
	 * option need to be updated accordingly.
	 *
	 * @param string    $old_name Old theme name.
	 * @param \WP_Theme $old_theme Instance of the old theme.
	 * @return void
	 */
	public function check_should_use_blockified_product_grid_templates( $old_name, $old_theme ) {
		if ( ! $old_theme->is_block_theme() && wp_is_block_theme() ) {
			$option_name = Options::WC_BLOCK_USE_BLOCKIFIED_PRODUCT_GRID_BLOCK_AS_TEMPLATE;
			// We previously stored "yes" or "no" values. This will convert them to true or false.
			$option_value = wc_string_to_bool( get_option( $option_name ) );

			// We don't need to do anything if the option is already set to true.
			if ( ! $option_value ) {
				update_option( $option_name, true );
			}
		}
	}
}
PK     \5R  R  #  BlockTypes/ProductAverageRating.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * ProductAverageRating class.
 */
class ProductAverageRating extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-average-rating';

	/**
	 * API version name.
	 *
	 * @var string
	 */
	protected $api_version = '3';

	/**
	 * Overwrite parent method to prevent script registration.
	 *
	 * It is necessary to register and enqueues assets during the render
	 * phase because we want to load assets only if the block has the content.
	 */
	protected function register_block_type_assets() {
		return null;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$post_id = $block->context['postId'];
		$product = wc_get_product( $post_id );

		if ( ! $product || ! $product->get_review_count() ) {
			return '';
		}

		$styles_and_classes            = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );
		$text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes );

		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class' => implode(
					' ',
					array_filter(
						[
							'wc-block-components-product-average-rating-counter',
							esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
							esc_attr( $styles_and_classes['classes'] ),
						]
					)
				),
				'style' => esc_attr( $styles_and_classes['styles'] ?? '' ),
			)
		);

		return sprintf(
			'<div %1$s>%2$s</div>',
			$wrapper_attributes,
			$product->get_average_rating()
		);
	}
}
PK     \Q	Ϛ      BlockTypes/ProductTag.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ProductTag class.
 */
class ProductTag extends AbstractProductGrid {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-tag';

	/**
	 * Set args specific to this block.
	 *
	 * @param array $query_args Query args.
	 */
	protected function set_block_query_args( &$query_args ) {
		if ( ! empty( $this->attributes['tags'] ) ) {
			$query_args['tax_query'][] = array(
				'taxonomy' => 'product_tag',
				'terms'    => array_map( 'absint', $this->attributes['tags'] ),
				'field'    => 'term_id',
				'operator' => isset( $this->attributes['tagOperator'] ) && 'any' === $this->attributes['tagOperator'] ? 'IN' : 'AND',
			);
		}
	}

	/**
	 * Get block attributes.
	 *
	 * @return array
	 */
	protected function get_block_type_attributes() {
		return array(
			'className'         => $this->get_schema_string(),
			'columns'           => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ),
			'rows'              => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ),
			'contentVisibility' => $this->get_schema_content_visibility(),
			'align'             => $this->get_schema_align(),
			'alignButtons'      => $this->get_schema_boolean( false ),
			'orderby'           => $this->get_schema_orderby(),
			'tags'              => $this->get_schema_list_ids(),
			'tagOperator'       => array(
				'type'    => 'string',
				'default' => 'any',
			),
			'isPreview'         => $this->get_schema_boolean( false ),
			'stockStatus'       => array_keys( wc_get_product_stock_status_options() ),
		);
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );

		$tag_count = wp_count_terms( 'product_tag' );

		$this->asset_data_registry->add( 'hasTags', $tag_count > 0 );
		$this->asset_data_registry->add( 'limitTags', $tag_count > 100 );
	}
}
PK     \2Վ3  3     BlockTypes/ProductCategories.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * ProductCategories class.
 */
class ProductCategories extends AbstractDynamicBlock {


	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-categories';

	/**
	 * Default attribute values, should match what's set in JS `registerBlockType`.
	 *
	 * @var array
	 */
	protected $defaults = array(
		'hasCount'         => true,
		'hasImage'         => false,
		'hasEmpty'         => false,
		'isDropdown'       => false,
		'isHierarchical'   => true,
		'showChildrenOnly' => false,
	);

	/**
	 * Get block attributes.
	 *
	 * @return array
	 */
	protected function get_block_type_attributes() {
		return array_merge(
			parent::get_block_type_attributes(),
			array(
				'align'            => $this->get_schema_align(),
				'className'        => $this->get_schema_string(),
				'hasCount'         => $this->get_schema_boolean( true ),
				'hasImage'         => $this->get_schema_boolean( false ),
				'hasEmpty'         => $this->get_schema_boolean( false ),
				'isDropdown'       => $this->get_schema_boolean( false ),
				'isHierarchical'   => $this->get_schema_boolean( true ),
				'showChildrenOnly' => $this->get_schema_boolean( false ),
				'textColor'        => $this->get_schema_string(),
				'fontSize'         => $this->get_schema_string(),
				'lineHeight'       => $this->get_schema_string(),
				'style'            => array( 'type' => 'object' ),
			)
		);
	}

	/**
	 * Render the Product Categories List block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$uid        = uniqid( 'product-categories-' );
		$categories = $this->get_categories( $attributes );

		if ( empty( $categories ) ) {
			return '';
		}

		if ( ! empty( $content ) ) {
			// Deal with legacy attributes (before this was an SSR block) that differ from defaults.
			if ( strstr( $content, 'data-has-count="false"' ) ) {
				$attributes['hasCount'] = false;
			}
			if ( strstr( $content, 'data-is-dropdown="true"' ) ) {
				$attributes['isDropdown'] = true;
			}
			if ( strstr( $content, 'data-is-hierarchical="false"' ) ) {
				$attributes['isHierarchical'] = false;
			}
			if ( strstr( $content, 'data-has-empty="true"' ) ) {
				$attributes['hasEmpty'] = true;
			}
		}

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes(
			$attributes,
			array( 'line_height', 'text_color', 'font_size', 'extra_classes' )
		);

		$classes = $this->get_container_classes( $attributes ) . ' ' . $classes_and_styles['classes'];
		$styles  = $classes_and_styles['styles'];

		$output  = '<div class="wp-block-woocommerce-product-categories ' . esc_attr( $classes ) . '" style="' . esc_attr( $styles ) . '">';
		$output .= ! empty( $attributes['isDropdown'] ) ? $this->renderDropdown( $categories, $attributes, $uid ) : $this->renderList( $categories, $attributes, $uid );
		$output .= '</div>';

		return $output;
	}

	/**
	 * Get the list of classes to apply to this block.
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 * @return string space-separated list of classes.
	 */
	protected function get_container_classes( $attributes = array() ) {

		$classes = array( 'wc-block-product-categories' );

		if ( isset( $attributes['align'] ) ) {
			$classes[] = "align{$attributes['align']}";
		}

		if ( $attributes['isDropdown'] ) {
			$classes[] = 'is-dropdown';
		} else {
			$classes[] = 'is-list';
		}

		return implode( ' ', $classes );
	}

	/**
	 * Get categories (terms) from the db.
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 * @return array
	 */
	protected function get_categories( $attributes ) {
		$hierarchical  = wc_string_to_bool( $attributes['isHierarchical'] );
		$children_only = wc_string_to_bool( $attributes['showChildrenOnly'] ) && is_product_category();

		if ( $children_only ) {
			$term_id    = get_queried_object_id();
			$categories = get_terms(
				'product_cat',
				[
					'hide_empty'   => ! $attributes['hasEmpty'],
					'pad_counts'   => true,
					'hierarchical' => true,
					'child_of'     => $term_id,
				]
			);
		} else {
			$categories = get_terms(
				'product_cat',
				[
					'hide_empty'   => ! $attributes['hasEmpty'],
					'pad_counts'   => true,
					'hierarchical' => true,
				]
			);
		}

		if ( ! is_array( $categories ) || empty( $categories ) ) {
			return [];
		}

		// This ensures that no categories with a product count of 0 is rendered.
		if ( ! $attributes['hasEmpty'] ) {
			$categories = array_filter(
				$categories,
				function( $category ) {
					return 0 !== $category->count;
				}
			);
		}
		return $hierarchical ? $this->build_category_tree( $categories, $children_only ) : $categories;
	}

	/**
	 * Build hierarchical tree of categories.
	 *
	 * @param array $categories List of terms.
	 * @param bool  $children_only Is the block rendering only the children of the current category.
	 * @return array
	 */
	protected function build_category_tree( $categories, $children_only ) {
		$categories_by_parent = [];

		foreach ( $categories as $category ) {
			if ( ! isset( $categories_by_parent[ 'cat-' . $category->parent ] ) ) {
				$categories_by_parent[ 'cat-' . $category->parent ] = [];
			}
			$categories_by_parent[ 'cat-' . $category->parent ][] = $category;
		}

		$parent_id = $children_only ? get_queried_object_id() : 0;
		$tree      = $categories_by_parent[ 'cat-' . $parent_id ]; // these are top level categories. So all parents.
		unset( $categories_by_parent[ 'cat-' . $parent_id ] );

		foreach ( $tree as $category ) {
			if ( ! empty( $categories_by_parent[ 'cat-' . $category->term_id ] ) ) {
				$category->children = $this->fill_category_children( $categories_by_parent[ 'cat-' . $category->term_id ], $categories_by_parent );
			}
		}

		return $tree;
	}

	/**
	 * Build hierarchical tree of categories by appending children in the tree.
	 *
	 * @param array $categories List of terms.
	 * @param array $categories_by_parent List of terms grouped by parent.
	 * @return array
	 */
	protected function fill_category_children( $categories, $categories_by_parent ) {
		foreach ( $categories as $category ) {
			if ( ! empty( $categories_by_parent[ 'cat-' . $category->term_id ] ) ) {
				$category->children = $this->fill_category_children( $categories_by_parent[ 'cat-' . $category->term_id ], $categories_by_parent );
			}
		}
		return $categories;
	}

	/**
	 * Render the category list as a dropdown.
	 *
	 * @param array $categories List of terms.
	 * @param array $attributes Block attributes. Default empty array.
	 * @param int   $uid Unique ID for the rendered block, used for HTML IDs.
	 * @return string Rendered output.
	 */
	protected function renderDropdown( $categories, $attributes, $uid ) {
		$aria_label = empty( $attributes['hasCount'] ) ?
			__( 'List of categories', 'woocommerce' ) :
			__( 'List of categories with their product counts', 'woocommerce' );

		$output = '
			<div class="wc-block-product-categories__dropdown">
				<label
				class="screen-reader-text"
					for="' . esc_attr( $uid ) . '-select"
				>
					' . esc_html__( 'Select a category', 'woocommerce' ) . '
				</label>
				<select aria-label="' . esc_attr( $aria_label ) . '" id="' . esc_attr( $uid ) . '-select">
					<option value="false" hidden>
						' . esc_html__( 'Select a category', 'woocommerce' ) . '
					</option>
					' . $this->renderDropdownOptions( $categories, $attributes, $uid ) . '
				</select>
			</div>
			<button
				type="button"
				class="wc-block-product-categories__button"
				aria-label="' . esc_html__( 'Go to category', 'woocommerce' ) . '"
				onclick="const url = document.getElementById( \'' . esc_attr( $uid ) . '-select\' ).value; if ( \'false\' !== url ) document.location.href = url;"
			>
				<svg
					aria-hidden="true"
					role="img"
					focusable="false"
					class="dashicon dashicons-arrow-right-alt2"
					xmlns="http://www.w3.org/2000/svg"
					width="20"
					height="20"
					viewBox="0 0 20 20"
				>
					<path d="M6 15l5-5-5-5 1-2 7 7-7 7z" />
				</svg>
			</button>
		';
		return $output;
	}

	/**
	 * Render dropdown options list.
	 *
	 * @param array $categories List of terms.
	 * @param array $attributes Block attributes. Default empty array.
	 * @param int   $uid Unique ID for the rendered block, used for HTML IDs.
	 * @param int   $depth Current depth.
	 * @return string Rendered output.
	 */
	protected function renderDropdownOptions( $categories, $attributes, $uid, $depth = 0 ) {
		$output = '';

		foreach ( $categories as $category ) {
			$output .= '
				<option value="' . esc_attr( get_term_link( $category->term_id, 'product_cat' ) ) . '">
					' . str_repeat( '&minus;', $depth ) . '
					' . esc_html( $category->name ) . '
					' . $this->getCount( $category, $attributes ) . '
				</option>
				' . ( ! empty( $category->children ) ? $this->renderDropdownOptions( $category->children, $attributes, $uid, $depth + 1 ) : '' ) . '
			';
		}

		return $output;
	}

	/**
	 * Render the category list as a list.
	 *
	 * @param array $categories List of terms.
	 * @param array $attributes Block attributes. Default empty array.
	 * @param int   $uid Unique ID for the rendered block, used for HTML IDs.
	 * @param int   $depth Current depth.
	 * @return string Rendered output.
	 */
	protected function renderList( $categories, $attributes, $uid, $depth = 0 ) {
		$classes = [
			'wc-block-product-categories-list',
			'wc-block-product-categories-list--depth-' . absint( $depth ),
		];
		if ( ! empty( $attributes['hasImage'] ) ) {
			$classes[] = 'wc-block-product-categories-list--has-images';
		}
		$output = '<ul class="' . esc_attr( implode( ' ', $classes ) ) . '">' . $this->renderListItems( $categories, $attributes, $uid, $depth ) . '</ul>';

		return $output;
	}

	/**
	 * Render a list of terms.
	 *
	 * @param array $categories List of terms.
	 * @param array $attributes Block attributes. Default empty array.
	 * @param int   $uid Unique ID for the rendered block, used for HTML IDs.
	 * @param int   $depth Current depth.
	 * @return string Rendered output.
	 */
	protected function renderListItems( $categories, $attributes, $uid, $depth = 0 ) {
		$output = '';

		$link_color_class_and_style = StyleAttributesUtils::get_link_color_class_and_style( $attributes );

		$link_color_style = isset( $link_color_class_and_style['style'] ) ? $link_color_class_and_style['style'] : '';

		foreach ( $categories as $category ) {
			$output .= '
				<li class="wc-block-product-categories-list-item">
					<a style="' . esc_attr( $link_color_style ) . '" href="' . esc_attr( get_term_link( $category->term_id, 'product_cat' ) ) . '">'
						. $this->get_image_html( $category, $attributes )
						. '<span class="wc-block-product-categories-list-item__name">' . esc_html( $category->name ) . '</span>'
					. '</a>'
					. $this->getCount( $category, $attributes )
					. ( ! empty( $category->children ) ? $this->renderList( $category->children, $attributes, $uid, $depth + 1 ) : '' ) . '
				</li>
			';
		}

		return preg_replace( '/\r|\n/', '', $output );
	}

	/**
	 * Returns the category image html
	 *
	 * @param \WP_Term $category Term object.
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $size Image size, defaults to 'woocommerce_thumbnail'.
	 * @return string
	 */
	public function get_image_html( $category, $attributes, $size = 'woocommerce_thumbnail' ) {
		if ( empty( $attributes['hasImage'] ) ) {
			return '';
		}

		$image_id = get_term_meta( $category->term_id, 'thumbnail_id', true );

		if ( ! $image_id ) {
			return '<span class="wc-block-product-categories-list-item__image wc-block-product-categories-list-item__image--placeholder">' . wc_placeholder_img( 'woocommerce_thumbnail' ) . '</span>';
		}

		return '<span class="wc-block-product-categories-list-item__image">' . wp_get_attachment_image( $image_id, 'woocommerce_thumbnail' ) . '</span>';
	}

	/**
	 * Get the count, if displaying.
	 *
	 * @param object $category Term object.
	 * @param array  $attributes Block attributes. Default empty array.
	 * @return string
	 */
	protected function getCount( $category, $attributes ) {
		if ( empty( $attributes['hasCount'] ) ) {
			return '';
		}

		if ( $attributes['isDropdown'] ) {
			return '(' . absint( $category->count ) . ')';
		}

		$screen_reader_text = sprintf(
			/* translators: %s number of products in cart. */
			_n( '%d product', '%d products', absint( $category->count ), 'woocommerce' ),
			absint( $category->count )
		);

		return '<span class="wc-block-product-categories-list-item-count">'
			. '<span aria-hidden="true">' . absint( $category->count ) . '</span>'
			. '<span class="screen-reader-text">' . esc_html( $screen_reader_text ) . '</span>'
		. '</span>';
	}
}
PK     \%TKE  E    BlockTypes/MiniCart.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
use Automattic\WooCommerce\Blocks\Utils\Utils;
use Automattic\WooCommerce\Blocks\Utils\MiniCartUtils;
use Automattic\WooCommerce\Blocks\Utils\BlockHooksTrait;
use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Blocks\Utils\BlocksSharedState;
use Automattic\WooCommerce\Internal\ComingSoon\ComingSoonHelper;
use Automattic\Block_Delimiter;

/**
 * Mini-Cart class.
 *
 * @internal
 */
class MiniCart extends AbstractBlock {
	use BlockHooksTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'mini-cart';

	/**
	 * Chunks build folder.
	 *
	 * @var string
	 */
	protected $chunks_folder = 'mini-cart-contents-block';

	/**
	 * Array of scripts that will be lazy loaded when interacting with the block.
	 *
	 * @var string[]
	 */
	protected $scripts_to_lazy_load = array();

	/**
	 *  Inc Tax label.
	 *
	 * @var string
	 */
	protected $tax_label = '';

	/**
	 *  Visibility of price including tax.
	 *
	 * @var string
	 */
	protected $display_cart_prices_including_tax = false;

	/**
	 * Block Hook API placements.
	 *
	 * @var array
	 */
	protected $hooked_block_placements = array(
		array(
			'position' => 'after',
			'anchor'   => 'core/navigation',
			'area'     => 'header',
			'version'  => '8.4.0',
		),
	);

	/**
	 * WooCommerce mini-cart template blocks.
	 *
	 * @var array
	 */
	const MINI_CART_TEMPLATE_BLOCKS = array(
		'woocommerce/mini-cart-contents',
		'woocommerce/filled-mini-cart-contents-block',
		'woocommerce/mini-cart-title-block',
		'woocommerce/mini-cart-title-label-block',
		'woocommerce/mini-cart-title-items-counter-block',
		'woocommerce/mini-cart-items-block',
		'woocommerce/mini-cart-products-table-block',
		'woocommerce/mini-cart-footer-block',
		'woocommerce/mini-cart-cart-button-block',
		'woocommerce/mini-cart-checkout-button-block',
		'woocommerce/empty-mini-cart-contents-block',
		'woocommerce/mini-cart-shopping-button-block',
	);

	/**
	 * Constructor.
	 *
	 * @param AssetApi            $asset_api Instance of the asset API.
	 * @param AssetDataRegistry   $asset_data_registry Instance of the asset data registry.
	 * @param IntegrationRegistry $integration_registry Instance of the integration registry.
	 */
	public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry, IntegrationRegistry $integration_registry ) {
		parent::__construct( $asset_api, $asset_data_registry, $integration_registry, $this->block_name );
	}

	/**
	 * 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_empty_cart_message_block_pattern' ) );
		add_action( 'wp_print_footer_scripts', array( $this, 'print_lazy_load_scripts' ), 2 );
		add_filter( 'hooked_block_woocommerce/mini-cart', array( $this, 'modify_hooked_block_attributes' ), 10, 5 );
		add_filter( 'hooked_block_types', array( $this, 'register_hooked_block' ), 9, 4 );

		// Priority 20 ensures this runs after WooCommerce block registration (priority 10)
		// allowing us to modify the block supports in the registry after registration is complete.
		add_action( 'init', array( $this, 'enable_interactivity_support' ), 20 );
	}

	/**
	 * Enable interactivity through Block Supports API. We're using WP_Block_Type_Registry instead
	 * of get_block_type_supports method available in AbstractBlock as the latter works only for
	 * blocks without static block.json metadata.
	 */
	public function enable_interactivity_support() {
		if ( Features::is_enabled( 'experimental-iapi-mini-cart' ) ) {
			$block_type = \WP_Block_Type_Registry::get_instance()->get_registered( 'woocommerce/mini-cart' );

			if ( $block_type ) {
				$block_type->supports['interactivity'] = true;
			}
		}
	}

	/**
	 * Callback for the Block Hooks API to modify the attributes of the hooked block.
	 *
	 * @param array|null                      $parsed_hooked_block The parsed block array for the given hooked block type, or null to suppress the block.
	 * @param string                          $hooked_block_type   The hooked block type name.
	 * @param string                          $relative_position   The relative position of the hooked block.
	 * @param array                           $parsed_anchor_block The anchor block, in parsed block array format.
	 * @param WP_Block_Template|WP_Post|array $context             The block template, template part, `wp_navigation` post type,
	 *                                                             or pattern that the anchor block belongs to.
	 * @return array|null
	 */
	public function modify_hooked_block_attributes( $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context ) {
		$mini_cart_block_font_size = wp_get_global_styles( array( 'blocks', 'woocommerce/mini-cart', 'typography', 'fontSize' ) );

		if ( ! is_string( $mini_cart_block_font_size ) ) {
			$navigation_block_font_size = wp_get_global_styles( array( 'blocks', 'core/navigation', 'typography', 'fontSize' ) );

			if ( is_string( $navigation_block_font_size ) ) {
				$parsed_hooked_block['attrs']['style']['typography']['fontSize'] = $navigation_block_font_size;
			}
		}

		return $parsed_hooked_block;
	}

	/**
	 * Get the editor script handle for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 * @return array|string;
	 */
	protected function get_block_type_editor_script( $key = null ) {
		$script = array(
			'handle'       => 'wc-' . $this->block_name . '-block',
			'path'         => $this->asset_api->get_block_asset_build_path( $this->block_name ),
			'dependencies' => array( 'wc-blocks' ),
		);
		return $key ? $script[ $key ] : $script;
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string
	 */
	protected function get_block_type_script( $key = null ) {
		if ( is_cart() || is_checkout() || Features::is_enabled( 'experimental-iapi-mini-cart' ) ) {
			return;
		}

		$script = array(
			'handle'       => 'wc-' . $this->block_name . '-block-frontend',
			'path'         => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
			'dependencies' => array(),
		);
		return $key ? $script[ $key ] : $script;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string[]
	 */
	protected function get_block_type_style() {
		return array_merge( parent::get_block_type_style(), array( 'wc-blocks-packages-style' ) );
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = array() ) {
		if ( is_cart() || is_checkout() ) {
			return;
		}

		parent::enqueue_data( $attributes );

		// Hydrate the following data depending on admin or frontend context.
		if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
			$label_info = $this->get_tax_label();

			$this->tax_label                         = $label_info['tax_label'];
			$this->display_cart_prices_including_tax = $label_info['display_cart_prices_including_tax'];

			$this->asset_data_registry->add(
				'taxLabel',
				$this->tax_label
			);
		}

		$this->asset_data_registry->add(
			'displayCartPricesIncludingTax',
			$this->display_cart_prices_including_tax
		);

		$template_part_edit_uri = '';

		if (
			current_user_can( 'edit_theme_options' ) &&
			( wp_is_block_theme() || current_theme_supports( 'block-template-parts' ) )
		) {
			$theme_slug = BlockTemplateUtils::theme_has_template_part( 'mini-cart' ) ? wp_get_theme()->get_stylesheet() : BlockTemplateUtils::PLUGIN_SLUG;

			if ( version_compare( get_bloginfo( 'version' ), '5.9', '<' ) ) {
				$site_editor_uri = add_query_arg(
					array( 'page' => 'gutenberg-edit-site' ),
					admin_url( 'themes.php' )
				);
			} else {
				$site_editor_uri = add_query_arg(
					array(
						'canvas' => 'edit',
						'path'   => '/template-parts/single',
					),
					admin_url( 'site-editor.php' )
				);
			}

			$template_part_edit_uri = esc_url_raw(
				add_query_arg(
					array(
						'postId'   => sprintf( '%s//%s', $theme_slug, 'mini-cart' ),
						'postType' => 'wp_template_part',
					),
					$site_editor_uri
				)
			);
		}

		$this->asset_data_registry->add(
			'templatePartEditUri',
			$template_part_edit_uri
		);

		/**
		 * Fires after cart block data is registered.
		 *
		 * @since 5.8.0
		 */
		do_action( 'woocommerce_blocks_cart_enqueue_data' );
	}

	/**
	 * Prints the variable containing information about the scripts to lazy load.
	 */
	public function print_lazy_load_scripts() {
		if ( Features::is_enabled( 'experimental-iapi-mini-cart' ) ) {
			return;
		}

		$script_data = $this->asset_api->get_script_data( 'assets/client/blocks/mini-cart-component-frontend.js' );

		$num_dependencies = is_countable( $script_data['dependencies'] ) ? count( $script_data['dependencies'] ) : 0;
		$wp_scripts       = wp_scripts();

		for ( $i = 0; $i < $num_dependencies; $i++ ) {
			$dependency = $script_data['dependencies'][ $i ];

			foreach ( $wp_scripts->registered as $script ) {
				if ( $script->handle === $dependency ) {
					$this->append_script_and_deps_src( $script );
					break;
				}
			}
		}

		$payment_method_registry = Package::container()->get( PaymentMethodRegistry::class );
		$payment_methods         = $payment_method_registry->get_all_active_payment_method_script_dependencies();

		foreach ( $payment_methods as $payment_method ) {
			$payment_method_script = $this->get_script_from_handle( $payment_method );

			if ( ! is_null( $payment_method_script ) ) {
				$this->append_script_and_deps_src( $payment_method_script );
			}
		}

		$this->scripts_to_lazy_load['wc-block-mini-cart-component-frontend'] = array(
			'src'          => $script_data['src'],
			'version'      => $script_data['version'],
			'translations' => $this->get_inner_blocks_translations(),
		);

		$inner_blocks_frontend_scripts = array();
		$cart                          = $this->get_cart_instance();
		if ( $cart ) {
			// Preload inner blocks frontend scripts.
			$inner_blocks_frontend_scripts = $cart->is_empty() ? array(
				'empty-cart-frontend',
				'filled-cart-frontend',
				'shopping-button-frontend',
			) : array(
				'empty-cart-frontend',
				'filled-cart-frontend',
				'title-frontend',
				'items-frontend',
				'footer-frontend',
				'products-table-frontend',
				'cart-button-frontend',
				'checkout-button-frontend',
				'title-label-frontend',
				'title-items-counter-frontend',
			);
		}
		foreach ( $inner_blocks_frontend_scripts as $inner_block_frontend_script ) {
			$script_data = $this->asset_api->get_script_data( 'assets/client/blocks/mini-cart-contents-block/' . $inner_block_frontend_script . '.js' );
			$this->scripts_to_lazy_load[ 'wc-block-' . $inner_block_frontend_script ] = array(
				'src'     => $script_data['src'],
				'version' => $script_data['version'],
			);
		}

		$data                          = rawurlencode( wp_json_encode( $this->scripts_to_lazy_load ) );
		$mini_cart_dependencies_script = "var wcBlocksMiniCartFrontendDependencies = JSON.parse( decodeURIComponent( '" . esc_js( $data ) . "' ) );";

		wp_add_inline_script(
			'wc-mini-cart-block-frontend',
			$mini_cart_dependencies_script,
			'before'
		);
	}

	/**
	 * Returns the script data given its handle.
	 *
	 * @param string $handle Handle of the script.
	 *
	 * @return \_WP_Dependency|null Object containing the script data if found, or null.
	 */
	protected function get_script_from_handle( $handle ) {
		$wp_scripts = wp_scripts();
		foreach ( $wp_scripts->registered as $script ) {
			if ( $script->handle === $handle ) {
				return $script;
			}
		}
		return null;
	}

	/**
	 * Recursively appends a scripts and its dependencies into the scripts_to_lazy_load array.
	 *
	 * @param \_WP_Dependency $script Object containing script data.
	 */
	protected function append_script_and_deps_src( $script ) {
		$wp_scripts = wp_scripts();

		// This script and its dependencies have already been appended.
		if ( ! $script || array_key_exists( $script->handle, $this->scripts_to_lazy_load ) || wp_script_is( $script->handle, 'enqueued' ) ) {
			return;
		}

		if ( is_countable( $script->deps ) && count( $script->deps ) ) {
			foreach ( $script->deps as $dep ) {
				if ( ! array_key_exists( $dep, $this->scripts_to_lazy_load ) ) {
					$dep_script = $this->get_script_from_handle( $dep );

					if ( ! is_null( $dep_script ) ) {
						$this->append_script_and_deps_src( $dep_script );
					}
				}
			}
		}
		if ( ! $script->src ) {
			return;
		}

		$site_url = site_url() ?? wp_guess_url();

		if ( Utils::wp_version_compare( '6.3', '>=' ) ) {
			$script_before = $wp_scripts->get_inline_script_data( $script->handle, 'before' );
			$script_after  = $wp_scripts->get_inline_script_data( $script->handle, 'after' );
		} else {
			$script_before = $wp_scripts->print_inline_script( $script->handle, 'before', false );
			$script_after  = $wp_scripts->print_inline_script( $script->handle, 'after', false );
		}

		$this->scripts_to_lazy_load[ $script->handle ] = array(
			'src'          => preg_match( '|^(https?:)?//|', $script->src ) ? $script->src : $site_url . $script->src,
			'version'      => $script->ver,
			'before'       => $script_before,
			'after'        => $script_after,
			'translations' => $wp_scripts->print_translations( $script->handle, false ),
		);
	}

	/**
	 * Returns the markup for the cart price.
	 *
	 * @param array $attributes Block attributes.
	 *
	 * @return string
	 */
	protected function get_cart_price_markup( $attributes ) {
		if ( isset( $attributes['hasHiddenPrice'] ) && false !== $attributes['hasHiddenPrice'] ) {
			return;
		}
		$price_color = isset( $attributes['priceColor']['color'] ) ? $attributes['priceColor']['color'] : '';

		return '<span class="wc-block-mini-cart__amount" style="color:' . esc_attr( $price_color ) . '"></span>' . $this->get_include_tax_label_markup( $attributes );
	}

	/**
	 * Returns the markup for render the tax label.
	 *
	 * @param array $attributes Block attributes.
	 *
	 * @return string
	 */
	protected function get_include_tax_label_markup( $attributes ) {
		if ( empty( $this->tax_label ) ) {
			return '';
		}
		$price_color = isset( $attributes['priceColor']['color'] ) ? $attributes['priceColor']['color'] : '';

		return '<small class="wc-block-mini-cart__tax-label" style="color:' . esc_attr( $price_color ) . ' " hidden>' . esc_html( $this->tax_label ) . '</small>';
	}

	/**
	 * Render the Mini-Cart block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		/**
		 * Do not render for logged-out users if the Coming Soon mode is enabled for store pages only.
		 */
		$coming_soon_helper = wc_get_container()->get( ComingSoonHelper::class );
		if ( ! is_user_logged_in() && ! WC()->is_rest_api_request() && $coming_soon_helper->is_store_coming_soon() ) {
			return '';
		}

		/**
		 * In the cart and checkout pages, the block is either rendered hidden or removed.
		 * It is not interactive, so it can fall back to the existing implementation.
		 */
		if ( Features::is_enabled( 'experimental-iapi-mini-cart' ) && ! is_cart() && ! is_checkout() ) {
			return $this->render_experimental_iapi_mini_cart( $attributes, $content, $block );
		}

		return $content . $this->get_markup( MiniCartUtils::migrate_attributes_to_color_panel( $attributes ) );
	}


	/**
	 * Render an experimental interactivity API powered Mini-Cart block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render_experimental_iapi_mini_cart( $attributes, $content, $block ) {
		wp_enqueue_script_module( $this->get_full_block_name() );

		// Enqueue all integration scripts registered for this block.
		$integration_script_handles = $this->integration_registry->get_all_registered_script_handles();
		foreach ( $integration_script_handles as $handle ) {
			wp_enqueue_script( $handle );
		}

		$consent = 'I acknowledge that using private APIs means my theme or plugin will inevitably break in the next version of WooCommerce';
		BlocksSharedState::load_cart_state( $consent );
		BlocksSharedState::load_store_config( $consent );
		BlocksSharedState::load_placeholder_image( $consent );

		$cart = $this->get_cart_instance();

		if ( $cart ) {
			$classes_styles                   = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
			$icon_color                       = isset( $attributes['iconColor']['color'] ) ? esc_attr( $attributes['iconColor']['color'] ) : 'currentColor';
			$product_count_color              = isset( $attributes['productCountColor']['color'] ) ? esc_attr( $attributes['productCountColor']['color'] ) : '';
			$styles                           = $product_count_color ? 'background:' . $product_count_color : '';
			$icon                             = MiniCartUtils::get_svg_icon( $attributes['miniCartIcon'] ?? '', $icon_color );
			$product_count_visibility         = isset( $attributes['productCountVisibility'] ) ? $attributes['productCountVisibility'] : 'greater_than_zero';
			$wrapper_classes                  = sprintf( 'wc-block-mini-cart wp-block-woocommerce-mini-cart %s', $classes_styles['classes'] );
			$wrapper_styles                   = $classes_styles['styles'];
			$template_part_contents           = $this->get_template_part_contents( false );
			$template_part_contents           = do_blocks( $this->process_template_contents( $template_part_contents ) );
			$cart_item_count                  = $cart ? $cart->get_cart_contents_count() : 0;
			$display_cart_price_including_tax = get_option( 'woocommerce_tax_display_cart' ) === 'incl';
			$cart_item_count                  = $cart ? $cart->get_cart_contents_count() : 0;
			$badge_is_visible                 = ( 'always' === $product_count_visibility ) || ( 'never' !== $product_count_visibility && $cart_item_count > 0 );
			$formatted_subtotal               = '';
			$html                             = new \WP_HTML_Tag_Processor( wc_price( $cart->get_displayed_subtotal() ) );
			$on_cart_click_behaviour          = isset( $attributes['onCartClickBehaviour'] ) ? $attributes['onCartClickBehaviour'] : 'open_drawer';

			if ( $html->next_tag( 'bdi' ) ) {
				while ( $html->next_token() ) {
					if ( '#text' === $html->get_token_name() ) {
						$formatted_subtotal .= $html->get_modifiable_text();
					}
				}
			}

			// The following translation is a temporary workaround. It will be
			// reverted to the previous form (`%1$d item in cart`) as soon as the
			// `@wordpress/i18n` package is available as a script module.
			$button_aria_label_template = isset( $attributes['hasHiddenPrice'] ) && false !== $attributes['hasHiddenPrice']
				/* translators: %d is the number of products in the cart. */
				? __( 'Number of items in the cart: %d', 'woocommerce' )
				/* translators: %1$d is the number of products in the cart. %2$s is the cart total */
				: __( 'Number of items in the cart: %1$d. Total price of %2$s', 'woocommerce' );

			wp_interactivity_state(
				$this->get_full_block_name(),
				array(
					'isOpen'             => false,
					'totalItemsInCart'   => $cart_item_count,
					'shouldShowTaxLabel' => $cart->get_cart_contents_tax() > 0,
					'badgeIsVisible'     => $badge_is_visible,
					'formattedSubtotal'  => $formatted_subtotal,
					'drawerOverlayClass' => 'wc-block-components-drawer__screen-overlay wc-block-components-drawer__screen-overlay--with-slide-out wc-block-components-drawer__screen-overlay--is-hidden',
					'buttonAriaLabel'    => function () use ( $button_aria_label_template ) {
						$state = wp_interactivity_state();
						return isset( $attributes['hasHiddenPrice'] ) && false !== $attributes['hasHiddenPrice']
							? sprintf( $button_aria_label_template, $state['totalItemsInCart'] )
							: sprintf( $button_aria_label_template, $state['totalItemsInCart'], $state['formattedSubtotal'] );
					},
				)
			);

			$context = array(
				'productCountVisibility' => $product_count_visibility,
			);

			wp_interactivity_config(
				$this->get_full_block_name(),
				array(
					'displayCartPriceIncludingTax' => $display_cart_price_including_tax,
					'onCartClickBehaviour'         => $on_cart_click_behaviour,
					'checkoutUrl'                  => wc_get_checkout_url(),
					'buttonAriaLabelTemplate'      => $button_aria_label_template,
				)
			);

			$cart_always_shows_price = isset( $attributes['hasHiddenPrice'] ) && false === $attributes['hasHiddenPrice'];
			$price_color             = isset( $attributes['priceColor']['color'] ) ? $attributes['priceColor']['color'] : '';

			$button_role = 'navigate_to_checkout' === $on_cart_click_behaviour
				? 'role="link"'
				: '';

			// Render the minicart overlay in the body, outside of the block itself.
			if ( ! has_action( 'wp_footer', array( $this, 'render_experimental_iapi_mini_cart_overlay' ) ) ) {
				add_action( 'wp_footer', array( $this, 'render_experimental_iapi_mini_cart_overlay' ) );
			}
			ob_start();
			?>
		
			<div
				data-wp-interactive="woocommerce/mini-cart"
				data-wp-init="callbacks.setupJQueryEventBridge"
				data-wp-on-document--wc-blocks_added_to_cart="woocommerce::actions.refreshCartItems"
				data-wp-on-document--wc-blocks_removed_from_cart="woocommerce::actions.refreshCartItems"
				<?php if ( 'open_drawer' === $attributes['addToCartBehaviour'] ) : ?>
				data-wp-on-document--wc-blocks_added_to_cart---open-drawer="actions.openDrawer"
				<?php endif; ?>
				data-wp-watch="callbacks.disableScrollingOnBody"
				data-wp-init--mark-as-hydrated="callbacks.markAsHydrated"
				<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				<?php echo wp_interactivity_data_wp_context( $context ); ?>
				class="<?php echo esc_attr( $wrapper_classes ); ?>"
				style="<?php echo esc_attr( $wrapper_styles ); ?>"
			>
				<button
					data-wp-on--click="actions.openDrawer"
					data-wp-bind--aria-label="state.buttonAriaLabel"
					class="wc-block-mini-cart__button"
					<?php echo $button_role; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				>
					<span class="wc-block-mini-cart__quantity-badge">
						<?php
							// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
							echo $icon;
						?>
							<?php if ( 'never' !== $product_count_visibility ) : ?>
								<span
									data-wp-style--background-color="state.badgeBackgroundColor"
									data-wp-style--color="state.badgeTextColor"
									data-wp-bind--hidden="!state.badgeIsVisible"
									data-wp-text="state.totalItemsInCart"
									class="wc-block-mini-cart__badge"
									style="<?php echo esc_attr( $styles ); ?>"
								>
							</span>
						<?php endif; ?>
					</span>
					<?php if ( $cart_always_shows_price ) : ?>
						<span data-wp-text="state.formattedSubtotal" class="wc-block-mini-cart__amount" style="<?php echo 'color:' . esc_attr( $price_color ); ?>">
						</span>
						<?php if ( ! empty( $this->tax_label ) ) : ?>
							<small
								data-wp-bind--hidden="!state.shouldShowTaxLabel"
								class="wc-block-mini-cart__tax-label"
								style="color:<?php echo esc_attr( $price_color ); ?>"
							>
								<?php echo esc_html( $this->tax_label ); ?>
							</small>
						<?php endif; ?>
					<?php endif; ?>
				</button>
			</div>
			<?php
			return ob_get_clean();
		}

		return '';
	}

	/**
	 * Echoes the Interactivity API Mini Cart overlay markup.
	 *
	 * @return void
	 */
	public function render_experimental_iapi_mini_cart_overlay() {
		$template_part_contents = $this->get_template_part_contents( false );
		$template_part_contents = do_blocks( $this->process_template_contents( $template_part_contents ) );
		ob_start();
		?>
		<div
			data-wp-interactive="woocommerce/mini-cart"
			data-wp-router-region='{ "id": "woocommerce/mini-cart-overlay", "attachTo": "body" }'
			data-wp-key="wc-mini-cart-overlay"
			data-wp-on--click="actions.overlayCloseDrawer"
			data-wp-on--keydown="actions.handleOverlayKeydown"
			data-wp-watch="callbacks.focusFirstElement"
			data-wp-bind--class="state.drawerOverlayClass"
		>
			<div
				data-wp-bind--role="state.drawerRole"
				data-wp-bind--aria-modal="state.isOpen"
				data-wp-bind--aria-hidden="!state.isOpen"
				data-wp-bind--tabindex="state.drawerTabIndex"
				class="wc-block-mini-cart__drawer wc-block-components-drawer is-mobile"
			>
				<div class="wc-block-components-drawer__content">
					<div class="wc-block-mini-cart__template-part">
						<?php
							// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
							echo $template_part_contents;
						?>
					</div>
				</div>
			</div>
		</div>				
		<?php
		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		echo wp_interactivity_process_directives( ob_get_clean() );
	}

	/**
	 * Process template contents to remove unwanted div wrappers.
	 *
	 * The old Mini Cart template had extra divs nested within the block tags
	 * that are no longer necessary since we don't render the Mini Cart with
	 * React anymore. To maintain compatibility with user saved templates that
	 * have these wrapper divs, we must remove them.
	 *
	 * @param string $template_contents The template contents to process.
	 * @return string The processed template contents.
	 */
	protected function process_template_contents( $template_contents ) {
		$p               = new \WP_HTML_Tag_Processor( $template_contents );
		$is_old_template = $p->next_tag(
			array(
				'tag_name'   => 'div',
				'class_name' => 'wp-block-woocommerce-mini-cart-contents',
			)
		);

		if ( ! $is_old_template ) {
			return $template_contents;
		}

		$output                   = '';
		$was_at                   = 0;
		$is_mini_cart_block_stack = array( false );

		foreach ( Block_Delimiter::scan_delimiters( $template_contents ) as $where => $delimiter ) {
			list( $at, $length ) = $where;
			$block_type          = $delimiter->allocate_and_return_block_type();
			$delimiter_type      = $delimiter->get_delimiter_type();

			if ( ! $is_mini_cart_block_stack[ array_key_last( $is_mini_cart_block_stack ) ] ) {
				// Copy content up to and including this block delimiter.
				$output .= substr( $template_contents, $was_at, $at + $length - $was_at );
			} else {
				// Just copy the block delimiter, skipping the wrapper div that existed before.
				$output .= substr( $template_contents, $at, $length );
			}

			// Update the position to the end of the block delimiter.
			$was_at = $at + $length;

			if ( Block_Delimiter::OPENER === $delimiter_type ) {
				// Add the Mini Cart block info to a stack.
				$is_mini_cart_block_stack[] = in_array( $block_type, self::MINI_CART_TEMPLATE_BLOCKS, true );
			} elseif ( Block_Delimiter::CLOSER === $delimiter_type ) {
				// Pop the last Mini Cart block info from the stack.
				array_pop( $is_mini_cart_block_stack );
			}
		}

		// Add any remaining content.
		$output .= substr( $template_contents, $was_at );

		return $output;
	}

	/**
	 * Get the mini cart template part contents to render inside the drawer.
	 *
	 * @param bool $do_blocks Whether to apply do_blocks() to the template part contents.
	 * @return string The contents of the template part.
	 */
	protected function get_template_part_contents( $do_blocks = true ) {
		$template_name          = 'mini-cart';
		$template_part_contents = '';

		// Determine if we need to load the template part from the DB, the theme or WooCommerce in that order.
		$templates_from_db = BlockTemplateUtils::get_block_templates_from_db( array( $template_name ), 'wp_template_part' );
		if ( is_countable( $templates_from_db ) && count( $templates_from_db ) > 0 ) {
			$template_slug_to_load = $templates_from_db[0]->theme;
		} else {
			$theme_has_mini_cart   = BlockTemplateUtils::theme_has_template_part( $template_name );
			$template_slug_to_load = $theme_has_mini_cart ? get_stylesheet() : BlockTemplateUtils::PLUGIN_SLUG;
		}
		$template_part = get_block_template( $template_slug_to_load . '//' . $template_name, 'wp_template_part' );

		if ( $template_part && ! empty( $template_part->content ) ) {
			if ( $do_blocks ) {
				$template_part_contents = do_blocks( $template_part->content );
			} else {
				$template_part_contents = $template_part->content;
			}
		}

		if ( '' === $template_part_contents ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
			$file_contents = file_get_contents( Package::get_path() . 'templates/' . BlockTemplateUtils::DIRECTORY_NAMES['TEMPLATE_PARTS'] . '/' . $template_name . '.html' );
			if ( $do_blocks ) {
				$template_part_contents = do_blocks(
					$file_contents
				);
			} else {
				$template_part_contents = $file_contents;
			}
		}

		return $template_part_contents;
	}

	/**
	 * Render the markup for the Mini-Cart block.
	 *
	 * @param array $attributes Block attributes.
	 *
	 * @return string The HTML markup.
	 */
	protected function get_markup( $attributes ) {
		if ( is_admin() || WC()->is_rest_api_request() ) {
			// In the editor we will display the placeholder, so no need to load
			// real cart data and to print the markup.
			return '';
		}

		$classes_styles  = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
		$wrapper_classes = sprintf( 'wc-block-mini-cart wp-block-woocommerce-mini-cart %s', $classes_styles['classes'] );
		$wrapper_styles  = $classes_styles['styles'];

		$icon_color          = isset( $attributes['iconColor']['color'] ) ? esc_attr( $attributes['iconColor']['color'] ) : 'currentColor';
		$product_count_color = isset( $attributes['productCountColor']['color'] ) ? esc_attr( $attributes['productCountColor']['color'] ) : '';
		$styles              = $product_count_color ? 'background:' . $product_count_color : '';
		$icon                = MiniCartUtils::get_svg_icon( $attributes['miniCartIcon'] ?? '', $icon_color );

		$product_count_visibility = isset( $attributes['productCountVisibility'] ) ? $attributes['productCountVisibility'] : 'greater_than_zero';

		$button_html = '<span class="wc-block-mini-cart__quantity-badge">
			' . $icon . '
			' . ( 'never' !== $product_count_visibility ? '<span class="wc-block-mini-cart__badge" style="' . esc_attr( $styles ) . '"></span>' : '' ) . '
		</span>
		' . $this->get_cart_price_markup( $attributes );

		if ( is_cart() || is_checkout() ) {
			if ( $this->should_not_render_mini_cart( $attributes ) ) {
				return '';
			}

			// It is not necessary to load the Mini-Cart Block on Cart and Checkout page.
			return '<div class="' . esc_attr( $wrapper_classes ) . '" style="visibility:hidden" aria-hidden="true">
				<button class="wc-block-mini-cart__button" disabled aria-label="' . __( 'Cart', 'woocommerce' ) . '">' . $button_html . '</button>
			</div>';
		}

		$template_part_contents = $this->get_template_part_contents();

		return '<div class="' . esc_attr( $wrapper_classes ) . '" style="' . esc_attr( $wrapper_styles ) . '">
			<button class="wc-block-mini-cart__button" aria-label="' . __( 'Cart', 'woocommerce' ) . '">' . $button_html . '</button>
			<div class="is-loading wc-block-components-drawer__screen-overlay wc-block-components-drawer__screen-overlay--is-hidden" aria-hidden="true">
				<div class="wc-block-mini-cart__drawer wc-block-components-drawer">
					<div class="wc-block-components-drawer__content">
						<div class="wc-block-mini-cart__template-part">'
					. wp_kses_post( $template_part_contents ) .
					'</div>
					</div>
				</div>
			</div>
		</div>';
	}

	/**
	 * Return the main instance of WC_Cart class.
	 *
	 * @return \WC_Cart CartController class instance.
	 */
	protected function get_cart_instance() {
		$cart = WC()->cart;

		if ( $cart && $cart instanceof \WC_Cart ) {
			return $cart;
		}

		return null;
	}

	/**
	 * Get array with data for handle the tax label.
	 * the entire logic of this function is was taken from:
	 * https://github.com/woocommerce/woocommerce/blob/e730f7463c25b50258e97bf56e31e9d7d3bc7ae7/includes/class-wc-cart.php#L1582
	 *
	 * @return array;
	 */
	protected function get_tax_label() {
		$cart = $this->get_cart_instance();

		if ( $cart && $cart->display_prices_including_tax() ) {
			if ( ! wc_prices_include_tax() ) {
				$tax_label                         = WC()->countries->inc_tax_or_vat();
				$display_cart_prices_including_tax = true;
				return array(
					'tax_label'                         => $tax_label,
					'display_cart_prices_including_tax' => $display_cart_prices_including_tax,
				);
			}
			return array(
				'tax_label'                         => '',
				'display_cart_prices_including_tax' => true,
			);
		}

		if ( wc_prices_include_tax() ) {
			$tax_label = WC()->countries->ex_tax_or_vat();
			return array(
				'tax_label'                         => $tax_label,
				'display_cart_prices_including_tax' => false,
			);
		}

		return array(
			'tax_label'                         => '',
			'display_cart_prices_including_tax' => false,
		);
	}

	/**
	 * Prepare translations for inner blocks and dependencies.
	 */
	protected function get_inner_blocks_translations() {
		$wp_scripts   = wp_scripts();
		$translations = array();

		$chunks        = $this->get_chunks_paths( $this->chunks_folder );
		$vendor_chunks = $this->get_chunks_paths( 'vendors--mini-cart-contents-block' );
		$shared_chunks = array( 'cart-blocks/cart-line-items--mini-cart-contents-block/products-table-frontend' );

		foreach ( array_merge( $chunks, $vendor_chunks, $shared_chunks ) as $chunk ) {
			$handle = 'wc-blocks-' . $chunk . '-chunk';
			$this->asset_api->register_script( $handle, $this->asset_api->get_block_asset_build_path( $chunk ), array(), true );
			$translations[] = $wp_scripts->print_translations( $handle, false );
			wp_deregister_script( $handle );
		}

		$translations = array_filter( $translations );

		return implode( "\n", $translations );
	}

	/**
	 * Register block pattern for Empty Cart Message to make it translatable.
	 */
	public function register_empty_cart_message_block_pattern() {
		register_block_pattern(
			'woocommerce/mini-cart-empty-cart-message',
			array(
				'title'    => __( 'Empty Mini-Cart Message', 'woocommerce' ),
				'inserter' => false,
				'content'  => '<!-- wp:paragraph {"align":"center"} --><p class="has-text-align-center"><strong>' . __( 'Your cart is currently empty!', 'woocommerce' ) . '</strong></p><!-- /wp:paragraph -->',
			)
		);
	}

	/**
	 * Returns whether the Mini-Cart should be rendered or not.
	 *
	 * @param array $attributes Block attributes.
	 *
	 * @return bool
	 */
	public function should_not_render_mini_cart( array $attributes ) {
		return isset( $attributes['cartAndCheckoutRenderStyle'] ) && 'hidden' !== $attributes['cartAndCheckoutRenderStyle'];
	}
}
PK     \G6o  6o    BlockTypes/ProductQuery.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Enums\ProductStatus;
use WP_Query;
use Automattic\WooCommerce\Blocks\Utils\Utils;
use Automattic\WooCommerce\Enums\ProductStockStatus;

// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_tax_query
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key

/**
 * ProductQuery class.
 */
class ProductQuery extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-query';

	/**
	 * The Block with its attributes before it gets rendered
	 *
	 * @var array
	 */
	protected $parsed_block;

	/**
	 * Orderby options not natively supported by WordPress REST API
	 *
	 * @var array
	 */
	protected $custom_order_opts = array( 'popularity', 'rating' );

	/**
	 * All the query args related to the filter by attributes block.
	 *
	 * @var array
	 */
	protected $attributes_filter_query_args = array();

	/** This is a feature flag to enable the custom inherit Global Query implementation.
	 * This is not intended to be a permanent feature flag, but rather a temporary.
	 * It is also necessary to enable this feature flag on the PHP side: `assets/js/blocks/product-query/utils.tsx:83`.
	 * https://github.com/woocommerce/woocommerce-blocks/pull/7382
	 *
	 * @var boolean
	 */
	protected $is_custom_inherit_global_query_implementation_enabled = false;

	/**
	 * All query args from WP_Query.
	 *
	 * @var array
	 */
	protected $valid_query_vars;

	/**
	 * Initialize this block type.
	 *
	 * - Hook into WP lifecycle.
	 * - Register the block with WordPress.
	 * - Hook into pre_render_block to update the query.
	 */
	protected function initialize() {
		add_filter( 'query_vars', array( $this, 'set_query_vars' ) );
		parent::initialize();
		add_filter(
			'pre_render_block',
			array( $this, 'update_query' ),
			10,
			2
		);
		add_filter(
			'render_block',
			array( $this, 'enqueue_styles' ),
			10,
			2
		);
		add_filter( 'rest_product_query', array( $this, 'update_rest_query' ), 10, 2 );
		add_filter( 'rest_product_collection_params', array( $this, 'extend_rest_query_allowed_params' ), 10, 1 );
	}

	/**
	 * Post Template support for grid view was introduced in Gutenberg 16 / WordPress 6.3
	 * Fixed in:
	 * - https://github.com/woocommerce/woocommerce-blocks/pull/9916
	 * - https://github.com/woocommerce/woocommerce-blocks/pull/10360
	 */
	private function check_if_post_template_has_support_for_grid_view() {
		if ( Utils::wp_version_compare( '6.3', '>=' ) ) {
			return true;
		}

		if ( is_plugin_active( 'gutenberg/gutenberg.php' ) ) {
			$gutenberg_version = '';

			if ( defined( 'GUTENBERG_VERSION' ) ) {
				$gutenberg_version = GUTENBERG_VERSION;
			}

			if ( ! $gutenberg_version ) {
				$gutenberg_data    = get_file_data(
					WP_PLUGIN_DIR . '/gutenberg/gutenberg.php',
					array( 'Version' => 'Version' )
				);
				$gutenberg_version = $gutenberg_data['Version'];
			}
			return version_compare( $gutenberg_version, '16.0', '>=' );
		}

		return false;
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );

		$post_template_has_support_for_grid_view = $this->check_if_post_template_has_support_for_grid_view();

		$this->asset_data_registry->add(
			'postTemplateHasSupportForGridView',
			$post_template_has_support_for_grid_view
		);

		// The `loop_shop_per_page` filter can be found in WC_Query::product_query().
		// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
		$this->asset_data_registry->add( 'loopShopPerPage', apply_filters( 'loop_shop_per_page', wc_get_default_products_per_row() * wc_get_default_product_rows_per_page() ) );
	}

	/**
	 * Check if a given block
	 *
	 * @param array $parsed_block The block being rendered.
	 * @return boolean
	 */
	public static function is_woocommerce_variation( $parsed_block ) {
		return isset( $parsed_block['attrs']['namespace'] )
		&& substr( $parsed_block['attrs']['namespace'], 0, 11 ) === 'woocommerce';
	}

	/**
	 * Enqueues the variation styles when rendering the Product Query variation.
	 *
	 * @param string $block_content The block content.
	 * @param array  $block         The full block, including name and attributes.
	 *
	 * @return string The block content.
	 */
	public function enqueue_styles( string $block_content, array $block ) {
		if ( 'core/query' === $block['blockName'] && self::is_woocommerce_variation( $block ) ) {
			wp_enqueue_style( 'wc-blocks-style-product-query' );
		}

		return $block_content;
	}

	/**
	 * Update the query for the product query block.
	 *
	 * @param string|null $pre_render   The pre-rendered content. Default null.
	 * @param array       $parsed_block The block being rendered.
	 */
	public function update_query( $pre_render, $parsed_block ) {
		if ( 'core/query' !== $parsed_block['blockName'] ) {
			return $pre_render;
		}

		$this->parsed_block = $parsed_block;

		if ( self::is_woocommerce_variation( $parsed_block ) ) {
			// Disable client-side navigation so that interactivity powered
			// components fall back to full page reload.
			wp_interactivity_config(
				'core/router',
				[
					'clientNavigationDisabled' => true,
				]
			);
			// Set this so that our product filters can detect if it's a PHP template.
			$this->asset_data_registry->add( 'hasFilterableProducts', true );
			$this->asset_data_registry->add( 'isRenderingPhpTemplate', true );
			add_filter(
				'query_loop_block_query_vars',
				array( $this, 'build_query' ),
				10,
				2
			);
		}

		return $pre_render;
	}

	/**
	 * Merge tax_queries from various queries.
	 *
	 * @param array ...$queries Query arrays to be merged.
	 * @return array
	 */
	private function merge_tax_queries( ...$queries ) {
		$tax_query = [];
		foreach ( $queries as $query ) {
			if ( ! empty( $query['tax_query'] ) ) {
				$tax_query = array_merge( $tax_query, $query['tax_query'] );
			}
		}
		return [ 'tax_query' => $tax_query ];
	}

	/**
	 * Update the query for the product query block in Editor.
	 *
	 * @param array           $args    Query args.
	 * @param WP_REST_Request $request Request.
	 */
	public function update_rest_query( $args, $request ): array {
		$woo_attributes      = $request->get_param( '__woocommerceAttributes' );
		$is_valid_attributes = is_array( $woo_attributes );
		$orderby             = $request->get_param( 'orderby' );
		$woo_stock_status    = $request->get_param( '__woocommerceStockStatus' );
		$on_sale             = $request->get_param( '__woocommerceOnSale' ) === 'true';

		$on_sale_query    = $on_sale ? $this->get_on_sale_products_query() : [];
		$orderby_query    = $orderby ? $this->get_custom_orderby_query( $orderby ) : [];
		$attributes_query = $is_valid_attributes ? $this->get_product_attributes_query( $woo_attributes ) : [];
		$stock_query      = is_array( $woo_stock_status ) ? $this->get_stock_status_query( $woo_stock_status ) : [];
		$visibility_query = is_array( $woo_stock_status ) ? $this->get_product_visibility_query( $stock_query ) : [];
		$tax_query        = $is_valid_attributes ? $this->merge_tax_queries( $attributes_query, $visibility_query ) : [];

		return array_merge( $args, $on_sale_query, $orderby_query, $stock_query, $tax_query );
	}

	/**
	 * Return a custom query based on attributes, filters and global WP_Query.
	 *
	 * @param WP_Query $query The WordPress Query.
	 * @param WP_Block $block The block being rendered.
	 * @return array
	 */
	public function build_query( $query, $block = null ) {
		$parsed_block                = $this->parsed_block;
		$is_product_collection_block = $block->context['query']['isProductCollectionBlock'] ?? false;
		if ( ! $this->is_woocommerce_variation( $parsed_block ) || $is_product_collection_block ) {
			return $query;
		}

		$common_query_values = array(
			'meta_query'     => array(),
			'posts_per_page' => $query['posts_per_page'],
			'orderby'        => $query['orderby'],
			'order'          => $query['order'],
			'offset'         => $query['offset'],
			'post__in'       => array(),
			'post_status'    => ProductStatus::PUBLISH,
			'post_type'      => 'product',
			'tax_query'      => array(),
		);

		$handpicked_products = isset( $parsed_block['attrs']['query']['include'] ) ?
			$parsed_block['attrs']['query']['include'] : $common_query_values['post__in'];

		$merged_query = $this->merge_queries(
			$common_query_values,
			$this->get_global_query( $parsed_block ),
			$this->get_custom_orderby_query( $query['orderby'] ),
			$this->get_queries_by_custom_attributes( $parsed_block ),
			$this->get_queries_by_applied_filters(),
			$this->get_filter_by_taxonomies_query( $query ),
			$this->get_filter_by_keyword_query( $query )
		);

		return $this->filter_query_to_only_include_ids( $merged_query, $handpicked_products );
	}

	/**
	 * Merge in the first parameter the keys "post_in", "meta_query" and "tax_query" of the second parameter.
	 *
	 * @param array[] ...$queries Query arrays to be merged.
	 * @return array
	 */
	private function merge_queries( ...$queries ) {
		$merged_query = array_reduce(
			$queries,
			function ( $acc, $query ) {
				if ( ! is_array( $query ) ) {
					return $acc;
				}
				// If the $query doesn't contain any valid query keys, we unpack/spread it then merge.
				if ( empty( array_intersect( $this->get_valid_query_vars(), array_keys( $query ) ) ) ) {
					return $this->merge_queries( $acc, ...array_values( $query ) );
				}
				return $this->array_merge_recursive_replace_non_array_properties( $acc, $query );
			},
			array()
		);

		/**
		 * If there are duplicated items in post__in, it means that we need to
		 * use the intersection of the results, which in this case, are the
		 * duplicated items.
		 */
		if (
			! empty( $merged_query['post__in'] ) &&
			is_array( $merged_query['post__in'] ) &&
			count( $merged_query['post__in'] ) > count( array_unique( $merged_query['post__in'] ) )
		) {
			$merged_query['post__in'] = array_unique(
				array_diff(
					$merged_query['post__in'],
					array_unique( $merged_query['post__in'] )
				)
			);
		}

		return $merged_query;
	}

	/**
	 * Extends allowed `collection_params` for the REST API
	 *
	 * By itself, the REST API doesn't accept custom `orderby` values,
	 * even if they are supported by a custom post type.
	 *
	 * @param array $params  A list of allowed `orderby` values.
	 *
	 * @return array
	 */
	public function extend_rest_query_allowed_params( $params ) {
		$original_enum = isset( $params['orderby']['enum'] ) ? $params['orderby']['enum'] : array();

		$params['orderby']['enum'] = array_merge( $original_enum, $this->custom_order_opts );
		return $params;
	}

	/**
	 * Return a query for on sale products.
	 *
	 * @return array
	 */
	private function get_on_sale_products_query() {
		return array(
			'post__in' => wc_get_product_ids_on_sale(),
		);
	}

	/**
	 * Return query params to support custom sort values
	 *
	 * @param string $orderby  Sort order option.
	 *
	 * @return array
	 */
	private function get_custom_orderby_query( $orderby ) {
		if ( ! in_array( $orderby, $this->custom_order_opts, true ) ) {
			return array( 'orderby' => $orderby );
		}

		$meta_keys = array(
			'popularity' => 'total_sales',
			'rating'     => '_wc_average_rating',
		);

		return array(
			'meta_key' => $meta_keys[ $orderby ],
			'orderby'  => 'meta_value_num',
		);
	}

	/**
	 * Apply the query only to a subset of products
	 *
	 * @param array $query  The query.
	 * @param array $ids  Array of selected product ids.
	 *
	 * @return array
	 */
	private function filter_query_to_only_include_ids( $query, $ids ) {
		if ( ! empty( $ids ) ) {
			$query['post__in'] = empty( $query['post__in'] ) ?
				$ids : array_intersect( $ids, $query['post__in'] );
		}

		return $query;
	}

	/**
	 * Return the `tax_query` for the requested attributes
	 *
	 * @param array $attributes  Attributes and their terms.
	 *
	 * @return array
	 */
	private function get_product_attributes_query( $attributes = array() ) {
		$grouped_attributes = array_reduce(
			$attributes,
			function ( $carry, $item ) {
				$taxonomy = sanitize_title( $item['taxonomy'] );

				if ( ! key_exists( $taxonomy, $carry ) ) {
					$carry[ $taxonomy ] = array(
						'field'    => 'term_id',
						'operator' => 'IN',
						'taxonomy' => $taxonomy,
						'terms'    => array( $item['termId'] ),
					);
				} else {
					$carry[ $taxonomy ]['terms'][] = $item['termId'];
				}

				return $carry;
			},
			array()
		);

		return array(
			'tax_query' => array_values( $grouped_attributes ),
		);
	}

	/**
	 * Return a query for products depending on their stock status.
	 *
	 * @param array $stock_statii An array of acceptable stock statii.
	 * @return array
	 */
	private function get_stock_status_query( $stock_statii ) {
		if ( ! is_array( $stock_statii ) ) {
			return array();
		}

		$stock_status_options = array_keys( wc_get_product_stock_status_options() );

		/**
		 * If all available stock status are selected, we don't need to add the
		 * meta query for stock status.
		 */
		if (
			count( $stock_statii ) === count( $stock_status_options ) &&
			array_diff( $stock_statii, $stock_status_options ) === array_diff( $stock_status_options, $stock_statii )
		) {
			return array();
		}

		/**
		 * If all stock statuses are selected except 'outofstock', we use the
		 * product visibility query to filter out out of stock products.
		 *
		 * @see get_product_visibility_query()
		 */
		$diff = array_diff( $stock_status_options, $stock_statii );
		if ( count( $diff ) === 1 && in_array( ProductStockStatus::OUT_OF_STOCK, $diff, true ) ) {
			return array();
		}

		return array(
			'meta_query' => array(
				array(
					'key'     => '_stock_status',
					'value'   => (array) $stock_statii,
					'compare' => 'IN',
				),
			),
		);
	}

	/**
	 * Return a query for product visibility depending on their stock status.
	 *
	 * @param array $stock_query Stock status query.
	 *
	 * @return array Tax query for product visibility.
	 */
	private function get_product_visibility_query( $stock_query ) {
		$product_visibility_terms  = wc_get_product_visibility_term_ids();
		$product_visibility_not_in = array( is_search() ? $product_visibility_terms['exclude-from-search'] : $product_visibility_terms['exclude-from-catalog'] );

		// Hide out of stock products.
		if ( empty( $stock_query ) && 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
			$product_visibility_not_in[] = $product_visibility_terms[ ProductStockStatus::OUT_OF_STOCK ];
		}

		return array(
			'tax_query' => array(
				array(
					'taxonomy' => 'product_visibility',
					'field'    => 'term_taxonomy_id',
					'terms'    => $product_visibility_not_in,
					'operator' => 'NOT IN',
				),
			),
		);
	}

	/**
	 * Set the query vars that are used by filter blocks.
	 *
	 * @return array
	 */
	private function get_query_vars_from_filter_blocks() {
		$attributes_filter_query_args = array_reduce(
			array_values( $this->get_filter_by_attributes_query_vars() ),
			function ( $acc, $arr ) {
				return array_merge( array_values( $arr ), $acc );
			},
			array()
		);

		return array(
			'price_filter_query_args'      => array( PriceFilter::MIN_PRICE_QUERY_VAR, PriceFilter::MAX_PRICE_QUERY_VAR ),
			'stock_filter_query_args'      => array( StockFilter::STOCK_STATUS_QUERY_VAR ),
			'attributes_filter_query_args' => $attributes_filter_query_args,
			'rating_filter_query_args'     => array( RatingFilter::RATING_QUERY_VAR ),
		);
	}

	/**
	 * Set the query vars that are used by filter blocks.
	 *
	 * @param array $public_query_vars Public query vars.
	 * @return array
	 */
	public function set_query_vars( $public_query_vars ) {
		$query_vars = $this->get_query_vars_from_filter_blocks();

		return array_reduce(
			array_values( $query_vars ),
			function ( $acc, $query_vars_filter_block ) {
				return array_merge( $query_vars_filter_block, $acc );
			},
			$public_query_vars
		);
	}

	/**
	 * Get all the query args related to the filter by attributes block.
	 *
	 * @return array
	 * [color] => Array
	 *   (
	 *        [filter] => filter_color
	 *        [query_type] => query_type_color
	 *    )
	 *
	 * [size] => Array
	 *    (
	 *        [filter] => filter_size
	 *        [query_type] => query_type_size
	 *    )
	 * )
	 */
	private function get_filter_by_attributes_query_vars() {
		if ( ! empty( $this->attributes_filter_query_args ) ) {
			return $this->attributes_filter_query_args;
		}

		$this->attributes_filter_query_args = array_reduce(
			wc_get_attribute_taxonomies(),
			function ( $acc, $attribute ) {
				$acc[ $attribute->attribute_name ] = array(
					'filter'     => AttributeFilter::FILTER_QUERY_VAR_PREFIX . $attribute->attribute_name,
					'query_type' => AttributeFilter::QUERY_TYPE_QUERY_VAR_PREFIX . $attribute->attribute_name,
				);
				return $acc;
			},
			array()
		);

		return $this->attributes_filter_query_args;
	}

	/**
	 * Return queries that are generated by query args.
	 *
	 * @return array
	 */
	private function get_queries_by_applied_filters() {
		return array(
			'price_filter'        => $this->get_filter_by_price_query(),
			'attributes_filter'   => $this->get_filter_by_attributes_query(),
			'stock_status_filter' => $this->get_filter_by_stock_status_query(),
			'rating_filter'       => $this->get_filter_by_rating_query(),
		);
	}

	/**
	 * Return queries that are generated by attributes
	 *
	 * @param array $parsed_block The Product Query that being rendered.
	 * @return array
	 */
	private function get_queries_by_custom_attributes( $parsed_block ) {
		$query            = $parsed_block['attrs']['query'];
		$on_sale_enabled  = isset( $query['__woocommerceOnSale'] ) && true === $query['__woocommerceOnSale'];
		$attributes_query = isset( $query['__woocommerceAttributes'] ) ? $this->get_product_attributes_query( $query['__woocommerceAttributes'] ) : array();
		$stock_query      = isset( $query['__woocommerceStockStatus'] ) ? $this->get_stock_status_query( $query['__woocommerceStockStatus'] ) : array();
		$visibility_query = $this->get_product_visibility_query( $stock_query );

		return array(
			'on_sale'      => ( $on_sale_enabled ? $this->get_on_sale_products_query() : array() ),
			'attributes'   => $attributes_query,
			'stock_status' => $stock_query,
			'visibility'   => $visibility_query,
		);
	}

	/**
	 * Return a query that filters products by price.
	 *
	 * @return array
	 */
	private function get_filter_by_price_query() {
		$min_price = get_query_var( PriceFilter::MIN_PRICE_QUERY_VAR );
		$max_price = get_query_var( PriceFilter::MAX_PRICE_QUERY_VAR );

		$max_price_query = empty( $max_price ) ? array() : [
			'key'     => '_price',
			'value'   => $max_price,
			'compare' => '<=',
			'type'    => 'numeric',
		];

		$min_price_query = empty( $min_price ) ? array() : [
			'key'     => '_price',
			'value'   => $min_price,
			'compare' => '>=',
			'type'    => 'numeric',
		];

		if ( empty( $min_price_query ) && empty( $max_price_query ) ) {
			return array();
		}

		return array(
			'meta_query' => array(
				array(
					'relation' => 'AND',
					$max_price_query,
					$min_price_query,
				),
			),
		);
	}

	/**
	 * Return a query that filters products by attributes.
	 *
	 * @return array
	 */
	private function get_filter_by_attributes_query() {
		$attributes_filter_query_args = $this->get_filter_by_attributes_query_vars();

		$queries = array_reduce(
			$attributes_filter_query_args,
			function ( $acc, $query_args ) {
				$attribute_name       = $query_args['filter'];
				$attribute_query_type = $query_args['query_type'];

				$attribute_value = get_query_var( $attribute_name );
				$attribute_query = get_query_var( $attribute_query_type );

				if ( empty( $attribute_value ) ) {
					return $acc;
				}

				// It is necessary explode the value because $attribute_value can be a string with multiple values (e.g. "red,blue").
				$attribute_value = explode( ',', $attribute_value );

				$acc[] = array(
					'taxonomy' => str_replace( AttributeFilter::FILTER_QUERY_VAR_PREFIX, 'pa_', $attribute_name ),
					'field'    => 'slug',
					'terms'    => $attribute_value,
					'operator' => 'and' === $attribute_query ? 'AND' : 'IN',
				);

				return $acc;
			},
			array()
		);

		if ( empty( $queries ) ) {
			return array();
		}

		return array(
			'tax_query' => array(
				array(
					'relation' => 'AND',
					$queries,
				),
			),
		);
	}

	/**
	 * Return a query that filters products by stock status.
	 *
	 * @return array
	 */
	private function get_filter_by_stock_status_query() {
		$filter_stock_status_values = get_query_var( StockFilter::STOCK_STATUS_QUERY_VAR );

		if ( empty( $filter_stock_status_values ) ) {
			return array();
		}

		$filtered_stock_status_values = array_filter(
			explode( ',', $filter_stock_status_values ),
			function ( $stock_status ) {
				return in_array( $stock_status, StockFilter::get_stock_status_query_var_values(), true );
			}
		);

		if ( empty( $filtered_stock_status_values ) ) {
			return array();
		}

		return array(
			// Ignoring the warning of not using meta queries.
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
			'meta_query' => array(
				array(
					'key'      => '_stock_status',
					'value'    => $filtered_stock_status_values,
					'operator' => 'IN',
				),
			),
		);
	}

	/**
	 * Return or initialize $valid_query_vars.
	 *
	 * @return array
	 */
	private function get_valid_query_vars() {
		if ( ! empty( $this->valid_query_vars ) ) {
			return $this->valid_query_vars;
		}

		$valid_query_vars       = array_keys( ( new WP_Query() )->fill_query_vars( array() ) );
		$this->valid_query_vars = array_merge(
			$valid_query_vars,
			// fill_query_vars doesn't include these vars so we need to add them manually.
			array(
				'date_query',
				'exact',
				'ignore_sticky_posts',
				'lazy_load_term_meta',
				'meta_compare_key',
				'meta_compare',
				'meta_query',
				'meta_type_key',
				'meta_type',
				'nopaging',
				'offset',
				'order',
				'orderby',
				'page',
				'post_type',
				'posts_per_page',
				'suppress_filters',
				'tax_query',
			)
		);

		return $this->valid_query_vars;
	}

	/**
	 * Merge two array recursively but replace the non-array values instead of
	 * merging them. The merging strategy:
	 *
	 * - If keys from merge array doesn't exist in the base array, create them.
	 * - For array items with numeric keys, we merge them as normal.
	 * - For array items with string keys:
	 *
	 *   - If the value isn't array, we'll use the value coming from the merge array.
	 *     $base = ['orderby' => 'date']
	 *     $new  = ['orderby' => 'meta_value_num']
	 *     Result: ['orderby' => 'meta_value_num']
	 *
	 *   - If the value is array, we'll use recursion to merge each key.
	 *     $base = ['meta_query' => [
	 *       [
	 *         'key'     => '_stock_status',
	 *         'compare' => 'IN'
	 *         'value'   =>  ['instock', 'onbackorder']
	 *       ]
	 *     ]]
	 *     $new  = ['meta_query' => [
	 *       [
	 *         'relation' => 'AND',
	 *         [...<max_price_query>],
	 *         [...<min_price_query>],
	 *       ]
	 *     ]]
	 *     Result: ['meta_query' => [
	 *       [
	 *         'key'     => '_stock_status',
	 *         'compare' => 'IN'
	 *         'value'   =>  ['instock', 'onbackorder']
	 *       ],
	 *       [
	 *         'relation' => 'AND',
	 *         [...<max_price_query>],
	 *         [...<min_price_query>],
	 *       ]
	 *     ]]
	 *
	 *     $base = ['post__in' => [1, 2, 3, 4, 5]]
	 *     $new  = ['post__in' => [3, 4, 5, 6, 7]]
	 *     Result: ['post__in' => [1, 2, 3, 4, 5, 3, 4, 5, 6, 7]]
	 *
	 * @param array $base First array.
	 * @param array $new  Second array.
	 */
	private function array_merge_recursive_replace_non_array_properties( $base, $new ) {
		foreach ( $new as $key => $value ) {
			if ( is_numeric( $key ) ) {
				$base[] = $value;
			} elseif ( is_array( $value ) ) {
				if ( ! isset( $base[ $key ] ) ) {
					$base[ $key ] = array();
				}
					$base[ $key ] = $this->array_merge_recursive_replace_non_array_properties( $base[ $key ], $value );
			} else {
				$base[ $key ] = $value;
			}
		}

		return $base;
	}

	/**
	 * Get product-related query variables from the global query.
	 *
	 * @param array $parsed_block The Product Query that being rendered.
	 *
	 * @return array
	 */
	private function get_global_query( $parsed_block ) {
		if ( ! $this->is_custom_inherit_global_query_implementation_enabled ) {
			return array();
		}

		global $wp_query;

		$inherit_enabled = isset( $parsed_block['attrs']['query']['__woocommerceInherit'] ) && true === $parsed_block['attrs']['query']['__woocommerceInherit'];

		if ( ! $inherit_enabled ) {
			return array();
		}

		$query = array();

		if ( isset( $wp_query->query_vars['taxonomy'] ) && isset( $wp_query->query_vars['term'] ) ) {
			$query['tax_query'] = array(
				array(
					'taxonomy' => $wp_query->query_vars['taxonomy'],
					'field'    => 'slug',
					'terms'    => $wp_query->query_vars['term'],
				),
			);
		}

		if ( isset( $wp_query->query_vars['s'] ) ) {
			$query['s'] = $wp_query->query_vars['s'];
		}

		return $query;
	}

	/**
	 * Return a query that filters products by rating.
	 *
	 * @return array
	 */
	private function get_filter_by_rating_query() {
		$filter_rating_values = get_query_var( RatingFilter::RATING_QUERY_VAR );
		if ( empty( $filter_rating_values ) ) {
			return array();
		}

		$parsed_filter_rating_values = explode( ',', $filter_rating_values );
		$product_visibility_terms    = wc_get_product_visibility_term_ids();

		if ( empty( $parsed_filter_rating_values ) || empty( $product_visibility_terms ) ) {
			return array();
		}

		$rating_terms = array_map(
			function ( $rating ) use ( $product_visibility_terms ) {
				return $product_visibility_terms[ 'rated-' . $rating ];
			},
			$parsed_filter_rating_values
		);

		return array(
			'tax_query' => array(
				array(
					'field'         => 'term_taxonomy_id',
					'taxonomy'      => 'product_visibility',
					'terms'         => $rating_terms,
					'operator'      => 'IN',
					'rating_filter' => true,
				),
			),
		);
	}


	/**
	 * Return a query to filter products by taxonomies (product categories, product tags, etc.)
	 *
	 * For example:
	 * User could provide "Product Categories" using "Filters" ToolsPanel available in Inspector Controls.
	 * We use this function to extract it's query from $tax_query.
	 *
	 * For example, this is how the query for product categories will look like in $tax_query array:
	 * Array
	 *    (
	 *        [taxonomy] => product_cat
	 *        [terms] => Array
	 *            (
	 *                [0] => 36
	 *            )
	 *    )
	 *
	 * For product categories, taxonomy would be "product_tag"
	 *
	 * @param array $query WP_Query.
	 * @return array Query to filter products by taxonomies.
	 */
	private function get_filter_by_taxonomies_query( $query ): array {
		if ( ! isset( $query['tax_query'] ) || ! is_array( $query['tax_query'] ) ) {
			return [];
		}

		$tax_query = $query['tax_query'];
		/**
		 * Get an array of taxonomy names associated with the "product" post type because
		 * we also want to include custom taxonomies associated with the "product" post type.
		 */
		$product_taxonomies = array_diff( get_object_taxonomies( 'product', 'names' ), array( 'product_visibility', 'product_shipping_class' ) );
		$result             = array_filter(
			$tax_query,
			function ( $item ) use ( $product_taxonomies ) {
				return isset( $item['taxonomy'] ) && in_array( $item['taxonomy'], $product_taxonomies, true );
			}
		);

		return ! empty( $result ) ? [ 'tax_query' => $result ] : [];
	}

	/**
	 * Returns the keyword filter from the given query.
	 *
	 * @param WP_Query $query The query to extract the keyword filter from.
	 * @return array The keyword filter, or an empty array if none is found.
	 */
	private function get_filter_by_keyword_query( $query ): array {
		if ( ! is_array( $query ) ) {
			return [];
		}

		if ( isset( $query['s'] ) ) {
			return [ 's' => $query['s'] ];
		}

		return [];
	}
}
PK     \*m  m  '  BlockTypes/ProductFilterPriceSlider.phpnu [        <?php
/**
 * ProductFilterPriceSlider class.
 *
 * @package Automattic\WooCommerce\Blocks\BlockTypes
 */

declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ProductFilterPriceSlider class.
 */
class ProductFilterPriceSlider extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-filter-price-slider';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( is_admin() || wp_doing_ajax() || empty( $block->context['filterData'] ) || empty( $block->context['filterData']['price'] ) ) {
			return '';
		}

		$price_data = $block->context['filterData']['price'];
		$min_price  = $price_data['minPrice'];
		$max_price  = $price_data['maxPrice'];
		$min_range  = $price_data['minRange'];
		$max_range  = $price_data['maxRange'];

		if ( $min_range === $max_range ) {
			return;
		}

		$classes = '';
		$style   = '';

		$tags = new \WP_HTML_Tag_Processor( $content );
		if ( $tags->next_tag( array( 'class_name' => 'wc-block-product-filter-price-slider' ) ) ) {
			$classes = $tags->get_attribute( 'class' );
			$style   = $tags->get_attribute( 'style' );
		}

		$show_input_fields = isset( $attributes['showInputFields'] ) ? $attributes['showInputFields'] : false;
		$inline_input      = isset( $attributes['inlineInput'] ) ? $attributes['inlineInput'] : false;

		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'data-wp-interactive' => 'woocommerce/product-filters',
				'data-wp-key'         => wp_unique_prefixed_id( $this->get_full_block_name() ),
				'class'               => esc_attr( $classes ),
				'style'               => esc_attr( $style ),
			)
		);

		$content_class = 'wc-block-product-filter-price-slider__content';
		if ( $inline_input && $show_input_fields ) {
			$content_class .= ' wc-block-product-filter-price-slider__content--inline';
		}

		// CSS variables for the range bar style.
		$__low       = 100 * ( $min_price - $min_range ) / ( $max_range - $min_range );
		$__high      = 100 * ( $max_price - $min_range ) / ( $max_range - $min_range );
		$range_style = "--low: $__low%; --high: $__high%";

		wp_interactivity_state(
			'woocommerce/product-filters',
			array(
				'rangeStyle' => $range_style,
			)
		);

		/**
		 * Accessibility: Assign the left input to a variable to conditionally
		 * render it based on the inline input setting. We do this to have the
		 * correct focus order of the input fields.
		 */
		ob_start();
		?>
		<div class="wc-block-product-filter-price-slider__left text">
			<?php if ( $show_input_fields ) : ?>
				<input
					class="min"
					type="text"
					data-wp-bind--value="state.formattedMinPrice"
					data-wp-on--focus="actions.selectInputContent"
					data-wp-on--input="actions.debounceSetMinPrice"
					aria-label="<?php esc_attr_e( 'Filter products by minimum price', 'woocommerce' ); ?>"
				/>
			<?php else : ?>
				<span data-wp-text="state.formattedMinPrice"></span>
			<?php endif; ?>
		</div>
		<?php
		$left_input = ob_get_clean();

		ob_start();
		?>
		<div <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<div class="<?php echo esc_attr( $content_class ); ?>">
				<?php if ( $inline_input ) : ?>
					<?php echo $left_input; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				<?php endif; ?>
				<div
					class="wc-block-product-filter-price-slider__range"
					data-wp-bind--style="state.rangeStyle"
				>
					<div class="range-bar"></div>
					<input
						type="range"
						class="min"
						min="<?php echo esc_attr( $min_range ); ?>"
						max="<?php echo esc_attr( $max_range ); ?>"
						data-wp-bind--value="state.minPrice"
						data-wp-on--input="actions.setMinPrice"
						data-wp-on--mouseup="actions.navigate"
						data-wp-on--keyup="actions.navigate"
						data-wp-on--touchend="actions.navigate"
						aria-label="<?php esc_attr_e( 'Filter products by minimum price', 'woocommerce' ); ?>"
					/>
					<input
						type="range"
						class="max"
						min="<?php echo esc_attr( $min_range ); ?>"
						max="<?php echo esc_attr( $max_range ); ?>"
						data-wp-bind--value="state.maxPrice"
						data-wp-on--input="actions.setMaxPrice"
						data-wp-on--mouseup="actions.navigate"
						data-wp-on--keyup="actions.navigate"
						data-wp-on--touchend="actions.navigate"
						aria-label="<?php esc_attr_e( 'Filter products by maximum price', 'woocommerce' ); ?>"
					/>
				</div>
				<?php if ( ! $inline_input ) : ?>
					<?php echo $left_input; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				<?php endif; ?>
				<div class="wc-block-product-filter-price-slider__right text">
					<?php if ( $show_input_fields ) : ?>
						<input
							class="max"
							type="text"
							data-wp-bind--value="state.formattedMaxPrice"
							data-wp-on--focus="actions.selectInputContent"
							data-wp-on--input="actions.debounceSetMaxPrice"
							aria-label="<?php esc_attr_e( 'Filter products by maximum price', 'woocommerce' ); ?>"
						/>
					<?php else : ?>
					<span data-wp-text="state.formattedMaxPrice"></span>
					<?php endif; ?>
				</div>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}
}
PK     \6sx  x    BlockTypes/FilterWrapper.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * FilterWrapper class.
 */
class FilterWrapper extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'filter-wrapper';

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \U'  '  *  BlockTypes/ProductFilterRemovableChips.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * Product Filter: Removable Chips Block.
 */
final class ProductFilterRemovableChips extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-filter-removable-chips';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if (
			empty( $block->context['filterData'] )
		) {
			return '';
		}

		$filter_items = $block->context['filterData']['items'] ?? array();

		$style = '';

		$tags = new \WP_HTML_Tag_Processor( $content );
		if ( $tags->next_tag( array( 'class_name' => 'wc-block-product-filter-removable-chips' ) ) ) {
			$classes = $tags->get_attribute( 'class' );
			$style   = $tags->get_attribute( 'style' );
		}

		$wrapper_attributes = array(
			'data-wp-interactive' => 'woocommerce/product-filters',
			'data-wp-key'         => wp_unique_prefixed_id( $this->get_full_block_name() ),
			'class'               => esc_attr( $classes ),
			'style'               => esc_attr( $style ),
		);

		ob_start();
		?>

		<div <?php echo get_block_wrapper_attributes( $wrapper_attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<ul class="wc-block-product-filter-removable-chips__items">
				<template
					data-wp-each="state.activeFilters"
					data-wp-each-key="context.item.uid"
				>
					<li class="wc-block-product-filter-removable-chips__item">
						<span class="wc-block-product-filter-removable-chips__label" data-wp-text="context.item.activeLabel"></span>
						<button
							type="button"
							class="wc-block-product-filter-removable-chips__remove"
							data-wp-bind--aria-label="state.removeActiveFilterLabel"
							data-wp-on--click="actions.removeActiveFilter"
						>
							<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="25" height="25" class="wc-block-product-filter-removable-chips__remove-icon" aria-hidden="true" focusable="false"><path d="M12 13.06l3.712 3.713 1.061-1.06L13.061 12l3.712-3.712-1.06-1.06L12 10.938 8.288 7.227l-1.061 1.06L10.939 12l-3.712 3.712 1.06 1.061L12 13.061z"></path></svg>
							<span class="screen-reader-text" data-wp-text="state.removeActiveFilterLabel"></span>
						</button>
					</li>
				</template>
				<?php foreach ( $filter_items as $item ) : ?>
					<?php // translators: %s: item label. ?>
					<?php $remove_label = sprintf( __( 'Remove filter: %s', 'woocommerce' ), $item['activeLabel'] ); ?>
					<li class="wc-block-product-filter-removable-chips__item" data-wp-each-child>
						<span class="wc-block-product-filter-removable-chips__label">
							<?php echo esc_html( $item['activeLabel'] ); ?>
						</span>
						<button
							type="button"
							class="wc-block-product-filter-removable-chips__remove"
							aria-label="<?php echo esc_attr( $remove_label ); ?>"
							data-wp-on--click="actions.removeActiveFilter"
							<?php echo wp_interactivity_data_wp_context( array( 'item' => $item ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
						>
							<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="25" height="25" class="wc-block-product-filter-removable-chips__remove-icon" aria-hidden="true" focusable="false"><path d="M12 13.06l3.712 3.713 1.061-1.06L13.061 12l3.712-3.712-1.06-1.06L12 10.938 8.288 7.227l-1.061 1.06L10.939 12l-3.712 3.712 1.06 1.061L12 13.061z"></path></svg>
							<span class="screen-reader-text"><?php echo esc_html( $remove_label ); ?></span>
						</button>
					</li>
				<?php endforeach; ?>
			</ul>
		</div>

		<?php
		return ob_get_clean();
	}
}
PK     \3[    '  BlockTypes/ProductGalleryLargeImage.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils;
use WP_Block;

/**
 * ProductGalleryLargeImage class.
 */
class ProductGalleryLargeImage extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name. Block has been initially created as Large Image but has been renamed
	 * to more generic name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-gallery-large-image';

	/**
	 *  Register the context
	 *
	 * @return string[]
	 */
	protected function get_block_type_uses_context() {
		return [ 'postId', 'hoverZoom', 'fullScreenOnClick' ];
	}

	/**
	 * Initialize this block type.
	 *
	 * - Hook into WP lifecycle.
	 * - Register the block with WordPress.
	 * - Hook into pre_render_block to update the query.
	 */
	protected function initialize() {
		add_filter( 'block_type_metadata_settings', array( $this, 'add_block_type_metadata_settings' ), 10, 2 );
		parent::initialize();
	}

	/**
	 * Enqueue frontend assets for this block, just in time for rendering.
	 *
	 * @param array    $attributes  Any attributes that currently are available from the block.
	 * @param string   $content    The block content.
	 * @param WP_Block $block    The block object.
	 */
	protected function enqueue_assets( array $attributes, $content, $block ) {
		if ( ! empty( $block->context['hoverZoom'] ) || ! empty( $block->context['fullScreenOnClick'] ) ) {
			parent::enqueue_assets( $attributes, $content, $block );
		}
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$post_id = $block->context['postId'];

		if ( ! isset( $post_id ) ) {
			return '';
		}

		global $product;

		$previous_product = $product;
		$product          = wc_get_product( $post_id );
		if ( ! $product instanceof \WC_Product ) {
			$product = $previous_product;

			return '';
		}

		$images_html       = '';
		$inner_blocks_html = '';

		foreach ( $block->inner_blocks as $inner_block ) {
			if ( 'woocommerce/product-image' === $inner_block->name ) {
				// Product Image requires special handling because we need to render it once for each image.
				$images_html .= $this->get_main_images_html( $block->context, $product, $inner_block );
			} else {
				// For Next/Previous Buttons block, check if we have more than one image, otherwise don't render it.
				if ( 'woocommerce/product-gallery-large-image-next-previous' === $inner_block->name ) {
					$product_gallery_image_count = ProductGalleryUtils::get_product_gallery_image_count( $product );
					if ( $product_gallery_image_count <= 1 ) {
						continue;
					}
				}

				// Render all the inner blocks once each.
				$inner_block_html = (
					new WP_Block(
						$inner_block->parsed_block,
						array_merge(
							(array) $block->context,
							array( 'iapi/provider' => 'woocommerce/product-gallery' )
						),
					)
				)->render( array( 'dynamic' => true ) );

				$inner_blocks_html .= $inner_block_html;
			}
		}

		ob_start();
		?>
			<div class="wc-block-product-gallery-large-image wp-block-woocommerce-product-gallery-large-image">
				<?php // No need to use wp_kses here because the image HTML is built internally. ?>
				<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				<?php echo $images_html; ?>
				<div class="wc-block-product-gallery-large-image__inner-blocks">
					<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
					<?php echo $inner_blocks_html; ?>
				</div>
			</div>
		<?php
		$html = ob_get_clean();

		return $html;
	}

	/**
	 * Update the single image html.
	 *
	 * @param string $image_html The image html.
	 * @param array  $context The block context.
	 * @param int    $index The index of the image.
	 * @return string
	 */
	private function update_single_image( $image_html, $context, $index ) {
		$p = new \WP_HTML_Tag_Processor( $image_html );

		if ( $p->next_tag( 'a' ) ) {
			$p->remove_attribute( 'onclick' );
			$p->remove_attribute( 'style' );
			$p->set_attribute( 'tabindex', '-1' );
		} else {
			/**
			 * If we can't find and <a> tag, we're at then end of the document.
			 * We need to reinitialize the processor instance to search for <img> tag.
			 */
			$p = new \WP_HTML_Tag_Processor( $image_html );
		}

		// Bail out early if we don't find any image.
		if ( ! $p->next_tag( 'img' ) ) {
			return $image_html;
		}

		$p->set_attribute( 'tabindex', '-1' );
		$p->set_attribute( 'draggable', 'false' );
		$p->set_attribute( 'data-wp-on--click', 'actions.onViewerClick' );
		$p->set_attribute( 'data-wp-on--touchstart', 'actions.onTouchStart' );
		$p->set_attribute( 'data-wp-on--touchmove', 'actions.onTouchMove' );
		$p->set_attribute( 'data-wp-on--touchend', 'actions.onTouchEnd' );

		if ( 0 === $index ) {
			$p->set_attribute( 'fetchpriority', 'high' );
			$p->set_attribute( 'loading', 'eager' );
		} else {
			$p->set_attribute( 'fetchpriority', 'low' );
			$p->set_attribute( 'loading', 'lazy' );
		}

		$img_classes = 'wc-block-woocommerce-product-gallery-large-image__image';

		if ( ! empty( $context['fullScreenOnClick'] ) ) {
			$img_classes .= ' wc-block-woocommerce-product-gallery-large-image__image--full-screen-on-click';

			$p->set_attribute( 'data-wp-on--click', 'actions.openDialog' );
		}
		if ( ! empty( $context['hoverZoom'] ) ) {
			$img_classes .= ' wc-block-woocommerce-product-gallery-large-image__image--hoverZoom';

			$p->set_attribute( 'data-wp-on--mousemove', 'actions.startZoom' );
			$p->set_attribute( 'data-wp-on--mouseleave', 'actions.resetZoom' );
		}

		$p->add_class( $img_classes );

		return $p->get_updated_html();
	}

	/**
	 * Get the main images html code. The first element of the array contains the HTML of the first image that is visible, the second element contains the HTML of the other images that are hidden.
	 *
	 * @param array       $context The block context.
	 * @param \WC_Product $product The product object.
	 * @param WP_Block    $inner_block The inner block object.
	 * @return array
	 */
	private function get_main_images_html( $context, $product, $inner_block ) {
		$image_data = ProductGalleryUtils::get_product_gallery_image_data( $product, 'woocommerce_single' );

		ob_start();
		?>
			<ul
				class="wc-block-product-gallery-large-image__container"
				data-wp-interactive="woocommerce/product-gallery"
				data-wp-on--keydown="actions.onViewerImageKeyDown"
				aria-label="<?php esc_attr_e( 'Product gallery', 'woocommerce' ); ?>"
				tabindex="0"
				aria-roledescription="carousel"
			>
				<?php foreach ( $image_data as $index => $image ) : ?>
					<li
						class="wc-block-product-gallery-large-image__wrapper"
					>
						<?php
							$image_html = (
								new WP_Block(
									$inner_block->parsed_block,
									array_merge( $context, array( 'imageId' => $image['id'] ) )
								)
							)->render( array( 'dynamic' => true ) );

							echo $this->update_single_image( $image_html, $context, $index ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
						?>
					</li>
				<?php endforeach; ?>
			</ul>
		<?php
		$template = ob_get_clean();

		return wp_interactivity_process_directives( $template );
	}

	/**
	 * Disable the editor style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_editor_style() {
		return null;
	}

	/**
	 * Viewer renders inner blocks manually so we need to skip default
	 * rendering routine for its inner blocks
	 *
	 * @param array $settings Array of determined settings for registering a block type.
	 * @param array $metadata Metadata provided for registering a block type.
	 * @return array
	 */
	public function add_block_type_metadata_settings( $settings, $metadata ) {
		if ( ! empty( $metadata['name'] ) && 'woocommerce/product-gallery-large-image' === $metadata['name'] ) {
			$settings['skip_inner_blocks'] = true;
		}
		return $settings;
	}
}
PK     \2    '  BlockTypes/Accordion/AccordionPanel.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\Accordion;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
/**
 * AccordionPanel class.
 */
class AccordionPanel extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'accordion-panel';
}
PK     \ib	  	  &  BlockTypes/Accordion/AccordionItem.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\Accordion;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
/**
 * AccordionItem class.
 */
class AccordionItem extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'accordion-item';

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! $content ) {
			return $content;
		}

		$p         = new \WP_HTML_Tag_Processor( $content );
		$unique_id = wp_unique_id( 'woocommerce-accordion-item-' );

		// Initialize the state of the item on the server using a closure,
		// since we need to get derived state based on the current context.
		wp_interactivity_state(
			'woocommerce/accordion',
			array(
				'isOpen' => function () {
					$context = wp_interactivity_get_context();
					return $context['openByDefault'];
				},
			)
		);

		if ( $p->next_tag( array( 'class_name' => 'wp-block-woocommerce-accordion-item' ) ) ) {
			$interactivity_context = array(
				'id'            => $unique_id,
				'openByDefault' => $attributes['openByDefault'],
			);
			$p->set_attribute( 'data-wp-context', wp_json_encode( $interactivity_context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) );
			$p->set_attribute( 'data-wp-class--is-open', 'state.isOpen' );
			$p->set_attribute( 'data-wp-init', 'callbacks.initIsOpen' );

			if ( $p->next_tag( array( 'class_name' => 'accordion-item__toggle' ) ) ) {
				$p->set_attribute( 'data-wp-on--click', 'actions.toggle' );
				$p->set_attribute( 'id', $unique_id );
				$p->set_attribute( 'aria-controls', $unique_id . '-panel' );
				$p->set_attribute( 'data-wp-bind--aria-expanded', 'state.isOpen' );

				if ( $p->next_tag( array( 'class_name' => 'wp-block-woocommerce-accordion-panel' ) ) ) {
					$p->set_attribute( 'id', $unique_id . '-panel' );
					$p->set_attribute( 'aria-labelledby', $unique_id );
					$p->set_attribute( 'role', 'region' );
					$p->set_attribute( 'data-wp-bind--inert', '!state.isOpen' );

					// Only modify content if all directives have been set.
					$content = $p->get_updated_html();
				}
			}
		}

		return $content;
	}
}
PK     \D,    '  BlockTypes/Accordion/AccordionGroup.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\Accordion;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;

/**
 * AccordionGroup class.
 */
class AccordionGroup extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'accordion-group';

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! $content ) {
			return $content;
		}

		$p = new \WP_HTML_Tag_Processor( $content );

		if ( $p->next_tag( array( 'class_name' => 'wp-block-woocommerce-accordion-group' ) ) ) {
			$interactivity_context = array(
				'autoclose' => $attributes['autoclose'],
				'isOpen'    => array(),
			);
			$p->set_attribute( 'data-wp-interactive', 'woocommerce/accordion' );
			$p->set_attribute( 'data-wp-context', wp_json_encode( $interactivity_context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) );

			// Only modify content if directives have been set.
			$content = $p->get_updated_html();
		}

		return $content;
	}
}
PK     \\S    (  BlockTypes/Accordion/AccordionHeader.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\Accordion;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
/**
 * AccordionHeader class.
 */
class AccordionHeader extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'accordion-header';
}
PK     \4B1  1    BlockTypes/ProductButton.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions\Utils;
use Automattic\WooCommerce\Blocks\Utils\BlocksSharedState;
use Automattic\WooCommerce\Enums\ProductType;

/**
 * ProductButton class.
 */
class ProductButton extends AbstractBlock {
	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-button';


	/**
	 * Cart.
	 *
	 * @var array
	 */
	private static $cart = null;

	/**
	 * Register the context.
	 */
	protected function get_block_type_uses_context() {
		return array( 'query', 'queryId', 'postId' );
	}

	/**
	 * Enqueue frontend assets for this block, just in time for rendering.
	 *
	 * @param array    $attributes  Any attributes that currently are available from the block.
	 * @param string   $content    The block content.
	 * @param WP_Block $block    The block object.
	 */
	protected function enqueue_assets( array $attributes, $content, $block ) {
		parent::enqueue_assets( $attributes, $content, $block );

		if ( wp_is_block_theme() ) {
			add_action(
				'wp_enqueue_scripts',
				array( $this, 'dequeue_add_to_cart_scripts' )
			);
		}
	}

	/**
	 * Dequeue the add-to-cart script.
	 * The block uses Interactivity API, it isn't necessary enqueue the add-to-cart script.
	 */
	public function dequeue_add_to_cart_scripts() {
		wp_dequeue_script( 'wc-add-to-cart' );
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		// This is work-around so the Product Button block inherits the styles
		// of the core Button block. We render it with the same classes and
		// enqueue its stylesheet.
		wp_enqueue_style( 'wp-block-button' );

		global $product;
		$previous_product = $product;

		// Try to load the product from the block context, if not available,
		// use the global $product.
		$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
		$post    = $post_id ? wc_get_product( $post_id ) : null;
		if ( $post instanceof \WC_Product ) {
			$product = $post;
		} elseif ( ! $product instanceof \WC_Product ) {
			return '';
		}

		$is_descendant_of_add_to_cart_form = isset( $block->context['woocommerce/isDescendantOfAddToCartWithOptions'] ) ? $block->context['woocommerce/isDescendantOfAddToCartWithOptions'] : false;

		if ( $is_descendant_of_add_to_cart_form && Utils::is_not_purchasable_product( $product ) ) {
			$product = $previous_product;

			return '';
		}

		BlocksSharedState::load_cart_state( 'I acknowledge that using private APIs means my theme or plugin will inevitably break in the next version of WooCommerce' );

		$number_of_items_in_cart  = $this->get_cart_item_quantities_by_product_id( $product->get_id() );
		$is_product_purchasable   = $this->is_product_purchasable( $product );
		$cart_redirect_after_add  = get_option( 'woocommerce_cart_redirect_after_add' ) === 'yes';
		$ajax_add_to_cart_enabled = get_option( 'woocommerce_enable_ajax_add_to_cart' ) === 'yes';
		$is_ajax_button           = ( ( $ajax_add_to_cart_enabled && $product->supports( 'ajax_add_to_cart' ) ) || $is_descendant_of_add_to_cart_form ) && $is_product_purchasable && ! $cart_redirect_after_add;
		$html_element             = $is_ajax_button || ( $is_descendant_of_add_to_cart_form && ! $product->is_type( ProductType::EXTERNAL ) ) ? 'button' : 'a';
		$styles_and_classes       = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );
		$classname                = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );
		$custom_width_classes     = isset( $attributes['width'] ) ? 'has-custom-width wp-block-button__width-' . $attributes['width'] : '';
		$custom_align_classes     = isset( $attributes['textAlign'] ) ? 'align-' . $attributes['textAlign'] : '';
		$html_classes             = implode(
			' ',
			array_filter(
				array(
					'wp-block-button__link',
					'wp-element-button',
					'wc-block-components-product-button__button',
					$product->is_purchasable() && $product->is_in_stock() ? 'add_to_cart_button' : '',
					$is_ajax_button ? 'ajax_add_to_cart' : '',
					'product_type_' . $product->get_type(),
					esc_attr( $styles_and_classes['classes'] ),
				)
			)
		);

		$default_quantity = 1;

		if ( ! $is_descendant_of_add_to_cart_form ) {
			/**
			 * Filters the change the quantity to add to cart.
			 *
			 * @since 8.5.0
			 * @param number $default_quantity The default quantity.
			 * @param number $product_id The product id.
			 */
			$default_quantity = apply_filters( 'woocommerce_add_to_cart_quantity', $default_quantity, $product->get_id() );
		}

		$add_to_cart_text = null !== $product->add_to_cart_text() ? $product->add_to_cart_text() : __( 'Add to cart', 'woocommerce' );

		if ( $is_descendant_of_add_to_cart_form && null !== $product->single_add_to_cart_text() ) {
			$add_to_cart_text = $product->single_add_to_cart_text();
		}

		$context = array(
			'quantityToAdd'    => $default_quantity,
			'productId'        => $product->get_id(),
			'productType'      => $product->get_type(),
			'addToCartText'    => $add_to_cart_text,
			'tempQuantity'     => $number_of_items_in_cart,
			'animationStatus'  => 'IDLE',
			'inTheCartText'    => $this->get_in_the_cart_text( $product ),
			'noticeId'         => '',
			'hasPressedButton' => false,
		);

		if ( $product->is_type( ProductType::GROUPED ) ) {
			$context['groupedProductIds'] = $product->get_children();
		}

		$attributes = array(
			'type' => $is_descendant_of_add_to_cart_form ? 'submit' : 'button',
		);

		if ( 'a' === $html_element ) {
			$attributes = array(
				'href' => esc_url( $product->add_to_cart_url() ),
				'rel'  => 'nofollow',
			);
		}

		wp_interactivity_config(
			'woocommerce',
			array(
				'messages' => array(
					'addedToCartText' => __( 'Added to cart', 'woocommerce' ),
				),
			)
		);

		wp_interactivity_state(
			'woocommerce/product-button',
			array(
				'addToCartText' => function () {
					$context = wp_interactivity_get_context();
					$quantity = $context['tempQuantity'];
					$add_to_cart_text = $context['addToCartText'];

					return $quantity > 0 ? sprintf(
						/* translators: %s: product number. */
						__( '%s in cart', 'woocommerce' ),
						$quantity
					) : $add_to_cart_text;
				},
			)
		);

		/**
		 * Allow filtering of the add to cart button arguments.
		 *
		 * @since 9.7.0
		 */
		$args = apply_filters(
			'woocommerce_loop_add_to_cart_args',
			array(
				'class'      => $html_classes,
				'attributes' => array_merge(
					$attributes,
					array(
						'data-product_id'  => $product->get_id(),
						'data-product_sku' => $product->get_sku(),
						'aria-label'       => ! $is_descendant_of_add_to_cart_form || 'simple' === $product->get_type() ? $product->add_to_cart_description() : null,
					),
				),
			),
			$product
		);

		if ( isset( $args['attributes']['aria-label'] ) ) {
			$args['attributes']['aria-label'] = wp_strip_all_tags( $args['attributes']['aria-label'] );
		}

		$div_directives = '
			data-wp-interactive="woocommerce/product-button"
		';

		$context_directives = wp_interactivity_data_wp_context( $context );

		$button_directives = $is_descendant_of_add_to_cart_form ?
			'data-wp-class--disabled="woocommerce/add-to-cart-with-options::!state.isFormValid"
			data-wp-bind--hidden="woocommerce/add-to-cart-with-options::!state.allowsAddingToCart"
			data-wp-on--click="actions.handlePressedState"' :
			'data-wp-on--click="actions.addCartItem"';
		$anchor_directive  = $is_descendant_of_add_to_cart_form ? '' : 'data-wp-on--click="woocommerce/product-collection::actions.viewProduct"';

		$span_button_directives = '
			data-wp-text="state.addToCartText"
			data-wp-class--wc-block-slide-in="state.slideInAnimation"
			data-wp-class--wc-block-slide-out="state.slideOutAnimation"
			data-wp-on--animationend="actions.handleAnimationEnd"
			data-wp-watch="callbacks.startAnimation"
			data-wp-run="callbacks.syncTempQuantityOnLoad"
		';

		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class' => implode(
					' ',
					array_filter(
						array(
							'wp-block-button wc-block-components-product-button',
							esc_attr( $classname . ' ' . $custom_width_classes . ' ' . $custom_align_classes ),
						)
					)
				),
			)
		);

		$button_classes = isset( $args['class'] ) ? esc_attr( $args['class'] . ' wc-interactive' ) : 'wc-interactive';
		if ( $is_descendant_of_add_to_cart_form ) {
			$button_classes             .= ' single_add_to_cart_button';
			$args['attributes']['value'] = $product->get_id();
		}

		$html = strtr(
			'<div {wrapper_attributes}
					{div_directives}
					{context_directives}
				>
					<{html_element}
						class="{button_classes}"
						style="{button_styles}"
						{attributes}
						{button_directives}
					>
					<span {span_button_directives}>{add_to_cart_text}</span>
					</{html_element}>
					{view_cart_html}
				</div>',
			array(
				'{wrapper_attributes}'     => $wrapper_attributes,
				'{html_element}'           => $html_element,
				'{button_classes}'         => $button_classes,
				'{context_directives}'     => $context_directives,
				'{button_styles}'          => esc_attr( $styles_and_classes['styles'] ),
				'{attributes}'             => isset( $args['attributes'] ) ? wc_implode_html_attributes( $args['attributes'] ) : '',
				'{add_to_cart_text}'       => $is_ajax_button ? '' : $add_to_cart_text,
				'{div_directives}'         => $is_ajax_button ? $div_directives : '',
				'{button_directives}'      => $is_ajax_button ? $button_directives : $anchor_directive,
				'{span_button_directives}' => $is_ajax_button ? $span_button_directives : '',
				'{view_cart_html}'         => $is_ajax_button && CartCheckoutUtils::has_cart_page() && ! $is_descendant_of_add_to_cart_form ? $this->get_view_cart_html() : '',
			)
		);

		if ( ! $is_descendant_of_add_to_cart_form ) {
			/**
			 * Filters the add to cart button class.
			 *
			 * @since 8.7.0
			 *
			 * @param string $class The class.
			 */
			$html = apply_filters(
				'woocommerce_loop_add_to_cart_link',
				$html,
				$product,
				$args
			);
		}

		$product = $previous_product;

		return $html;
	}

	/**
	 * Get the number of items in the cart for a given product id.
	 *
	 * @param number $product_id The product id.
	 * @return number The number of items in the cart.
	 */
	private function get_cart_item_quantities_by_product_id( $product_id ) {
		if ( ! isset( WC()->cart ) ) {
			return 0;
		}

		$cart = WC()->cart->get_cart_item_quantities();
		return isset( $cart[ $product_id ] ) ? $cart[ $product_id ] : 0;
	}

	/**
	 * Check if a product is purchasable.
	 *
	 * @param \WC_Product $product The product.
	 * @return boolean The product is purchasable.
	 */
	private function is_product_purchasable( $product ) {
		if ( $product->is_type( ProductType::GROUPED ) ) {
			$grouped_product_ids = $product->get_children();
			if ( ! empty( $grouped_product_ids ) ) {
				_prime_post_caches( $grouped_product_ids );
			}
			foreach ( $grouped_product_ids as $child ) {
				$child_product = wc_get_product( $child );
				if ( ! $child_product instanceof \WC_Product ) {
					continue;
				}
				if ( $child_product->is_purchasable() && $child_product->is_in_stock() ) {
					return true;
				}
			}

			return false;
		}

		return $product->is_purchasable() && $product->is_in_stock();
	}

	/**
	 * Get the inTheCartText text for a given product.
	 *
	 * @param \WC_Product $product The product.
	 * @return string The inTheCartText string.
	 */
	private function get_in_the_cart_text( $product ) {
		if ( $product->is_type( ProductType::GROUPED ) ) {
			return __( 'Added to cart', 'woocommerce' );
		}

		return sprintf(
			/* translators: %s: product number. */
			__( '%s in cart', 'woocommerce' ),
			'###'
		);
	}

	/**
	 * Get the view cart link html.
	 *
	 * @return string The view cart html.
	 */
	private function get_view_cart_html() {
		return sprintf(
			'<span
				hidden
				data-wp-bind--hidden="!state.displayViewCart"
			>
				<a
					href="%1$s"
					class="added_to_cart wc_forward"
					title="%2$s"
				>
					%3$s
				</a>
			</span>',
			esc_url( wc_get_cart_url() ),
			esc_attr__( 'View cart', 'woocommerce' ),
			esc_html__( 'View cart', 'woocommerce' )
		);
	}
}
PK     \J    !  BlockTypes/ProductFilterChips.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * Product Filter: Chips Block.
 */
final class ProductFilterChips extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-filter-chips';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if (
			empty( $block->context['filterData'] )
		) {
			return '';
		}

		$items       = $block->context['filterData']['items'] ?? array();
		$show_counts = $block->context['filterData']['showCounts'] ?? false;
		$classes     = '';
		$style       = '';

		$tags = new \WP_HTML_Tag_Processor( $content );
		if ( $tags->next_tag( array( 'class_name' => 'wc-block-product-filter-chips' ) ) ) {
			$classes = $tags->get_attribute( 'class' );
			$style   = $tags->get_attribute( 'style' );
		}

		$checked_items               = array_filter(
			$items,
			function ( $item ) {
				return $item['selected'];
			}
		);
		$show_initially              = 15;
		$remaining_initial_unchecked = count( $checked_items ) > $show_initially ? count( $checked_items ) : $show_initially - count( $checked_items );
		$count                       = 0;

		$wrapper_attributes = array(
			'data-wp-interactive' => 'woocommerce/product-filters',
			'data-wp-key'         => wp_unique_prefixed_id( $this->get_full_block_name() ),
			'data-wp-context'     => '{}',
			'class'               => esc_attr( $classes ),
		);

		if ( ! empty( $style ) ) {
			// Styles generated by Supports API doesn't include semicolon at the end.
			$wrapper_attributes['style'] = esc_attr( $style ) . ';';
		}

		ob_start();
		?>
		<div <?php echo get_block_wrapper_attributes( $wrapper_attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<fieldset>
				<?php if ( ! empty( $block->context['filterData']['groupLabel'] ) ) : ?>
					<legend class="screen-reader-text"><?php echo esc_html( $block->context['filterData']['groupLabel'] ); ?></legend>
				<?php endif; ?>
				<div class="wc-block-product-filter-chips__items">
					<?php foreach ( $items as $item ) { ?>
						<?php $item_id = $item['type'] . '-' . $item['value']; ?>
						<button
							data-wp-key="<?php echo esc_attr( $item_id ); ?>"
							id="<?php echo esc_attr( $item_id ); ?>"
							class="wc-block-product-filter-chips__item"
							type="button"
							role="checkbox"
							aria-label="<?php echo esc_attr( $this->get_aria_label( $item, $show_counts ) ); ?>"
							data-wp-on--click="actions.toggleFilter"
							value="<?php echo esc_attr( $item['value'] ); ?>"
							data-wp-bind--aria-checked="state.isFilterSelected"
							<?php echo wp_interactivity_data_wp_context( array( 'item' => $item ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
							<?php if ( ! $item['selected'] ) : ?>
								<?php if ( $count >= $remaining_initial_unchecked ) : ?>
									data-wp-bind--hidden="!context.showAll"
									hidden
								<?php else : ?>
									<?php ++$count; ?>
								<?php endif; ?>
							<?php endif; ?>
						>
							<span class="wc-block-product-filter-chips__label">
								<span class="wc-block-product-filter-chips__text">
									<?php echo $item['label']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
								</span>
								<?php if ( $show_counts ) : ?>
									<span class="wc-block-product-filter-chips__count">
										(<?php echo esc_html( $item['count'] ); ?>)
									</span>
								<?php endif; ?>
							</span>
						</button>
					<?php } ?>
				</div>
				<?php if ( count( $items ) > $show_initially ) : ?>
					<button
						class="wc-block-product-filter-chips__show-more"
						data-wp-on--click="actions.showAllChips"
						data-wp-bind--hidden="context.showAll"
						hidden
					>
						<?php echo esc_html__( 'Show more…', 'woocommerce' ); ?>
					</button>
				<?php endif; ?>
			</fieldset>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Get aria label for filter item.
	 *
	 * @param array $item Filter item.
	 * @param bool  $show_counts Whether to show counts.
	 *
	 * @return string Aria label.
	 */
	private function get_aria_label( $item, $show_counts ) {
		if ( $show_counts ) {
			return sprintf(
				/* translators: %1$s: Product filter name, %2$d: Number of products */
				_n(
					'%1$s (%2$d product)',
					'%1$s (%2$d products)',
					$item['count'],
					'woocommerce'
				),
				$item['ariaLabel'] ?? $item['label'],
				$item['count']
			);
		}

		return $item['ariaLabel'] ?? $item['label'];
	}
}
PK     \Cwf      BlockTypes/ProductTopRated.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ProductTopRated class.
 */
class ProductTopRated extends AbstractProductGrid {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-top-rated';

	/**
	 * Force orderby to rating.
	 *
	 * @param array $query_args Query args.
	 */
	protected function set_block_query_args( &$query_args ) {
		$query_args['orderby'] = 'rating';
	}
}
PK     \~y03  03  )  BlockTypes/MiniCartProductsTableBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * MiniCartProductsTableBlock class.
 */
class MiniCartProductsTableBlock extends AbstractInnerBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'mini-cart-products-table-block';

	/**
	 * Render the markup for the Mini-Cart Products Table block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$screen_reader_text = __( 'Products in cart', 'woocommerce' );
		$remove_item_label  = __( 'Remove item', 'woocommerce' );
		$head_product_label = __( 'Product', 'woocommerce' );
		$head_details_label = __( 'Details', 'woocommerce' );
		$head_total_label   = __( 'Total', 'woocommerce' );

		wp_interactivity_state(
			$this->get_full_block_name(),
			array(
				'cartItem' => function () {
					$context = wp_interactivity_get_context( 'woocommerce' );
					$cart_state = wp_interactivity_state( 'woocommerce' );
					$item_key = $context['cartItem']['key'];

					foreach ( $cart_state['cart']['items'] as $item ) {
						if ( $item['key'] === $item_key ) {
							return $item;
						}
					}

					return null;
				},
			)
		);

		// translators: %s is the name of the product in cart.
		$reduce_quantity_label = __( 'Reduce quantity of %s', 'woocommerce' );

		// translators: %s is the name of the product in cart.
		$increase_quantity_label = __( 'Increase quantity of %s', 'woocommerce' );

		// translators: %s is the name of the product in cart.
		$quantity_description_label = __( 'Quantity of %s in your cart.', 'woocommerce' );

		// translators: %s is the name of the product in cart.
		$remove_from_cart_label = __( 'Remove %s from cart', 'woocommerce' );

		/* translators: %s is the discount amount. */
		$save_format             = __( 'Save %s', 'woocommerce' );
		$line_item_discount_span = '<span data-wp-text="state.lineItemDiscount" class="wc-block-formatted-money-amount wc-block-components-formatted-money-amount"></span>';
		$line_item_save_badge    = sprintf( $save_format, $line_item_discount_span );

		$available_on_backorder_label = __( 'Available on backorder', 'woocommerce' );

		wp_interactivity_config(
			$this->get_full_block_name(),
			array(
				'reduceQuantityLabel'      => $reduce_quantity_label,
				'increaseQuantityLabel'    => $increase_quantity_label,
				'quantityDescriptionLabel' => $quantity_description_label,
				'removeFromCartLabel'      => $remove_from_cart_label,
			)
		);

		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class'               => 'wc-block-mini-cart__products-table',
				'data-wp-interactive' => $this->get_full_block_name(),
			)
		);

		ob_start();
		?>
		<div <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<table class="wc-block-cart-items wc-block-mini-cart-items" tabindex="-1">
				<caption class="screen-reader-text">
					<h2>
						<?php echo esc_html( $screen_reader_text ); ?>
					</h2>
				</caption>
				<thead>
					<tr class="wc-block-cart-items__header">
						<th class="wc-block-cart-items__header-image">
							<span><?php echo esc_html( $head_product_label ); ?></span>
						</th>
						<th class="wc-block-cart-items__header-product">
							<span><?php echo esc_html( $head_details_label ); ?></span>
						</th>
						<th class="wc-block-cart-items__header-total">
							<span><?php echo esc_html( $head_total_label ); ?></span>
						</th>
					</tr>
				</thead>
				<tbody>
					<template
						data-wp-each--cart-item="woocommerce::state.cart.items"
						data-wp-each-key="state.cartItem.key"
					>
						<tr
							class="wc-block-cart-items__row"
							data-wp-bind--hidden="!state.cartItem.key"
							data-wp-run="callbacks.filterCartItemClass"
							tabindex="-1"
						>
							<td data-wp-context='{ "isImageHidden": false }' class="wc-block-cart-item__image" aria-hidden="true">
								<img
									data-wp-bind--hidden="!state.isProductHiddenFromCatalog"
									data-wp-bind--src="state.itemThumbnail"
									data-wp-bind--alt="state.cartItemName"
									data-wp-bind--srcset="state.itemSrcset"
									data-wp-bind--sizes="state.itemSizes"
									data-wp-on--error="actions.hideImage"
								>
								<a data-wp-bind--hidden="state.isProductHiddenFromCatalog" data-wp-bind--href="state.cartItem.permalink" tabindex="-1">
									<img
										data-wp-bind--hidden="context.isImageHidden"
										data-wp-bind--src="state.itemThumbnail"
										data-wp-bind--alt="state.cartItemName"
										data-wp-bind--srcset="state.itemSrcset"
										data-wp-bind--sizes="state.itemSizes"
										data-wp-on--error="actions.hideImage"
									>	
								</a>
							</td>
							<td class="wc-block-cart-item__product">
								<div class="wc-block-cart-item__wrap">
									<span data-wp-bind--hidden="!state.isProductHiddenFromCatalog" data-wp-text="state.cartItemName" class="wc-block-components-product-name"></span>
									<a data-wp-bind--hidden="state.isProductHiddenFromCatalog" data-wp-text="state.cartItemName" data-wp-bind--href="state.cartItem.permalink" class="wc-block-components-product-name"></a>
									<div data-wp-bind--hidden="!state.cartItem.show_backorder_badge" class="wc-block-components-product-badge wc-block-components-product-backorder-badge">
										<?php echo esc_html( $available_on_backorder_label ); ?>
									</div>
									<div class="wc-block-cart-item__prices">
										<span data-wp-bind--hidden="!state.cartItemHasDiscount" class="price wc-block-components-product-price">
											<span data-wp-text="state.beforeItemPrice"></span>
											<span class="screen-reader-text">
												<?php esc_html_e( 'Previous price:', 'woocommerce' ); ?>
											</span>
											<del data-wp-text="state.priceWithoutDiscount" class="wc-block-components-product-price__regular"></del>
											<span class="screen-reader-text">
												<?php esc_html_e( 'Discounted price:', 'woocommerce' ); ?>
											</span>
											<ins data-wp-text="state.itemPrice" class="wc-block-components-product-price__value is-discounted"></ins>
											<span data-wp-text="state.afterItemPrice"></span>
										</span>
										<span data-wp-bind--hidden="state.cartItemHasDiscount" class="price wc-block-components-product-price">
											<span data-wp-text="state.beforeItemPrice"></span>
											<span data-wp-text="state.itemPrice" class="wc-block-formatted-money-amount wc-block-components-formatted-money-amount wc-block-components-product-price__value">
											</span>
											<span data-wp-text="state.afterItemPrice"></span>
										</span>
									</div>
									<div class="wc-block-components-product-metadata">
										<div data-wp-watch="callbacks.itemShortDescription" >
											<div class="wc-block-components-product-metadata__description"></div>
										</div>
										<?php echo $this->render_product_details_markup( 'item_data' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>										
										<?php echo $this->render_product_details_markup( 'variation' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>																				
									</div>
									<div class="wc-block-cart-item__quantity">
										<div class="wc-block-components-quantity-selector" data-wp-bind--hidden="state.cartItem.sold_individually">
											<input 
												data-wp-on--input="actions.overrideInvalidQuantity"
												data-wp-on--change="actions.changeQuantity" 
												data-wp-bind--aria-label="state.quantityDescriptionLabel" 
												data-wp-bind--min="state.cartItem.quantity_limits.minimum" 
												data-wp-bind--max="state.cartItem.quantity_limits.maximum"
												data-wp-bind--value="state.cartItem.quantity"
												data-wp-bind--readonly="!state.cartItem.quantity_limits.editable"
												class="wc-block-components-quantity-selector__input" 
												type="number" 
												step="1"
											>
											<button 
												data-wp-bind--disabled="state.minimumReached" 
												data-wp-on--click="actions.decrementQuantity" 
												data-wp-bind--aria-label="state.reduceQuantityLabel"
												data-wp-bind--hidden="!state.cartItem.quantity_limits.editable"
												class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus"
											>
												−
											</button>
											<button 
												data-wp-bind--disabled="state.maximumReached" 
												data-wp-on--click="actions.incrementQuantity" 
												data-wp-bind--aria-label="state.increaseQuantityLabel"
												data-wp-bind--hidden="!state.cartItem.quantity_limits.editable"
												class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus"
											>
												＋
											</button>
										</div>
										<button
											data-wp-bind--hidden="!state.itemShowRemoveItemLink"
											data-wp-on--click="actions.removeItemFromCart"
											data-wp-bind--aria-label="state.removeFromCartLabel"
											class="wc-block-cart-item__remove-link"
										>
											<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false">
												<path fill-rule="evenodd" clip-rule="evenodd" d="M12 5.5A2.25 2.25 0 0 0 9.878 7h4.244A2.251 2.251 0 0 0 12 5.5ZM12 4a3.751 3.751 0 0 0-3.675 3H5v1.5h1.27l.818 8.997a2.75 2.75 0 0 0 2.739 2.501h4.347a2.75 2.75 0 0 0 2.738-2.5L17.73 8.5H19V7h-3.325A3.751 3.751 0 0 0 12 4Zm4.224 4.5H7.776l.806 8.861a1.25 1.25 0 0 0 1.245 1.137h4.347a1.25 1.25 0 0 0 1.245-1.137l.805-8.861Z"/>
											</svg>
										</button>
									</div>
								</div>
							</td>
							<td class="wc-block-cart-item__total">
								<div class="wc-block-cart-item__total-price-and-sale-badge-wrapper">
									<span class="price wc-block-components-product-price">
										<span data-wp-text="state.lineItemTotal" class="wc-block-formatted-money-amount wc-block-components-formatted-money-amount wc-block-components-product-price__value">
										</span>											
									</span>
									<div 
										data-wp-bind--hidden="!state.cartItemHasDiscount" 
										class="wc-block-components-product-badge wc-block-components-sale-badge"
									>
									<?php
										echo wp_kses(
											$line_item_save_badge,
											array(
												'span' => array(
													'data-wp-text' => true,
													'class'        => true,
												),
											)
										);
									?>
									</div>
								</div>
							</td>
						</tr>
					</template>
				</tbody>
			</table>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Render markup for product details.
	 *
	 * @param string $property The property to render in the product details markup.
	 * @return string Rendered product details output.
	 */
	protected function render_product_details_markup( $property ) {
		$context = array( 'dataProperty' => $property );

		// If the property is item_data, so not a variation, we need to skip the text directive.
		$is_item_data = 'item_data' === $context['dataProperty'];

		ob_start();
		?>
		<div
			<?php echo wp_interactivity_data_wp_context( $context ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			class="wc-block-components-product-details"
			data-wp-bind--hidden="state.shouldHideProductDetails"
		>
			<template
				data-wp-each--item-data="state.cartItem.<?php echo esc_attr( $property ); ?>"
				data-wp-each-key="state.cartItemDataKey"
			>
				<?php echo $this->render_product_details_item_markup( $is_item_data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			</template>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Render markup for a single product detail item.
	 *
	 * @param bool $is_item_data Whether the item is of item_data type.
	 * @return string Rendered product detail item output based on item type.
	 */
	private function render_product_details_item_markup( $is_item_data = false ) {
		ob_start();
		?>
		<span
			data-wp-bind--hidden="state.cartItemDataAttrHidden"
			data-wp-bind--class="state.cartItemDataAttr.className"
		>
		<?php if ( $is_item_data ) : ?>
			<span class="wc-block-components-product-details__name" data-wp-watch="callbacks.itemDataNameInnerHTML"></span>
			<span class="wc-block-components-product-details__value" data-wp-watch="callbacks.itemDataValueInnerHTML"></span>
		<?php else : ?>
			<span class="wc-block-components-product-details__name" data-wp-text="state.cartItemDataAttr.name"></span>
			<span class="wc-block-components-product-details__value" data-wp-text="state.cartItemDataAttr.value"></span>
		<?php endif; ?>
			<span aria-hidden="true" data-wp-bind--hidden="state.isLastCartItemDataAttr"> / </span>
		</span>
		<?php
		return ob_get_clean();
	}
}
PK     \Eb'  '    BlockTypes/ProductFilters.phpnu [        <?php

declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\BlocksSharedState;
use Automattic\WooCommerce\Internal\ProductFilters\Params;

/**
 * ProductFilters class.
 */
class ProductFilters extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-filters';

	/**
	 * Register the context.
	 *
	 * @return string[]
	 */
	protected function get_block_type_uses_context() {
		return array( 'postId', 'query', 'queryId' );
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = array() ) {
		global $pagenow;
		parent::enqueue_data( $attributes );

		BlocksSharedState::load_store_config( 'I acknowledge that using private APIs means my theme or plugin will inevitably break in the next version of WooCommerce' );

		// Classic themes do not support client-side navigation on product
		// archive pages, so disable it globally for the Interactivity Router.
		$is_product_archive = is_shop() || is_product_taxonomy() || ( is_search() && 'product' === get_post_type() );
		if ( ! wp_is_block_theme() && $is_product_archive ) {
			wp_interactivity_config( 'core/router', array( 'clientNavigationDisabled' => true ) );
		}
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		wp_enqueue_script( 'wc-settings' );

		$query_id      = $block->context['queryId'] ?? 0;
		$filter_params = $this->get_filter_params( $query_id );

		wp_interactivity_config( $this->get_full_block_name(), [ 'canonicalUrl' => $this->get_canonical_url_no_pagination( $filter_params ) ] );

		/**
		 * Filter hook to modify the selected filter items.
		 *
		 * @since 9.7.0
		 */
		$active_filters = apply_filters( 'woocommerce_blocks_product_filters_selected_items', array(), $filter_params );

		usort(
			$active_filters,
			function ( $a, $b ) {
				return strnatcmp( $a['activeLabel'], $b['activeLabel'] );
			}
		);

		$block_context         = array_merge(
			$block->context,
			array(
				'filterParams'  => $filter_params,
				'activeFilters' => $active_filters,
			),
		);
		$inner_blocks          = array_reduce(
			$block->parsed_block['innerBlocks'],
			function ( $carry, $parsed_block ) use ( $block_context ) {
				$carry .= ( new \WP_Block( $parsed_block, $block_context ) )->render();
				return $carry;
			},
			''
		);
		$interactivity_context = array(
			'params'        => $filter_params,
			'activeFilters' => $active_filters,
		);

		$classes = '';
		$styles  = '';
		$tags    = new \WP_HTML_Tag_Processor( $content );

		if ( $tags->next_tag( array( 'class_name' => 'wc-block-product-filters' ) ) ) {
			$classes = $tags->get_attribute( 'class' );
			$styles  = $tags->get_attribute( 'style' );
		}

		$wrapper_attributes = array(
			'class'                            => $classes,
			'data-wp-interactive'              => $this->get_full_block_name(),
			'data-wp-watch--scrolling'         => 'callbacks.scrollLimit',
			'data-wp-on--keyup'                => 'actions.closeOverlayOnEscape',
			'data-wp-context'                  => wp_json_encode( $interactivity_context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
			'data-wp-class--is-overlay-opened' => 'context.isOverlayOpened',
			'style'                            => $styles,
		);

		// TODO: Remove this conditional once the fix is released in WP. https://github.com/woocommerce/gutenberg/pull/4.
		if ( ! isset( $block->context['productCollectionLocation'] ) ) {
			$wrapper_attributes['data-wp-router-region'] = $this->generate_navigation_id( $block );
		}

		ob_start();
		?>
		<div <?php echo get_block_wrapper_attributes( $wrapper_attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<button
				class="wc-block-product-filters__open-overlay"
				data-wp-on--click="actions.openOverlay"
			>
				<?php echo $this->get_svg_icon( 'filter-icon-2' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				<span><?php echo esc_html__( 'Filter products', 'woocommerce' ); ?></span>
			</button>
			<div class="wc-block-product-filters__overlay">
				<div class="wc-block-product-filters__overlay-wrapper">
					<div
						class="wc-block-product-filters__overlay-dialog"
						role="dialog"
						aria-label="<?php echo esc_html__( 'Product Filters', 'woocommerce' ); ?>"
					>
						<header class="wc-block-product-filters__overlay-header">
							<button
								class="wc-block-product-filters__close-overlay"
								data-wp-on--click="actions.closeOverlay"
							>
								<span><?php echo esc_html__( 'Close', 'woocommerce' ); ?></span>
								<?php echo $this->get_svg_icon( 'close' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
							</button>
						</header>
						<div class="wc-block-product-filters__overlay-content">
							<?php echo $inner_blocks; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
						</div>
						<footer
							class="wc-block-product-filters__overlay-footer"
						>
							<button
								class="wc-block-product-filters__apply wp-element-button"
								data-wp-interactive="<?php echo esc_attr( $this->get_full_block_name() ); ?>"
								data-wp-on--click="actions.closeOverlay"
							>
								<span><?php echo esc_html__( 'Apply', 'woocommerce' ); ?></span>
							</button>
						</footer>
					</div>
				</div>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Get SVG icon markup for a given icon name.
	 *
	 * @param string $name The name of the icon to retrieve.
	 * @return string SVG markup for the icon, or empty string if icon not found.
	 */
	private function get_svg_icon( string $name ) {
		$icons = array(
			'close'         => '<path d="M12 13.0607L15.7123 16.773L16.773 15.7123L13.0607 12L16.773 8.28772L15.7123 7.22706L12 10.9394L8.28771 7.22705L7.22705 8.28771L10.9394 12L7.22706 15.7123L8.28772 16.773L12 13.0607Z" fill="currentColor"/>',
			'filter-icon-2' => '<path d="M10 17.5H14V16H10V17.5ZM6 6V7.5H18V6H6ZM8 12.5H16V11H8V12.5Z" fill="currentColor"/>',
		);

		if ( ! isset( $icons[ $name ] ) ) {
			return '';
		}

		return sprintf(
			'<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">%s</svg>',
			$icons[ $name ]
		);
	}

	/**
	 * Generate a unique navigation ID for the block.
	 *
	 * @param mixed $block - Block instance.
	 * @return string - Unique navigation ID.
	 */
	private function generate_navigation_id( $block ) {
		return sprintf(
			'wc-product-filters-%s',
			md5( wp_json_encode( $block->parsed_block['innerBlocks'] ) )
		);
	}

	/**
	 * Parse the filter parameters from the URL.
	 * For now we only get the global query params from the URL. In the future,
	 * we should get the query params based on $query_id.
	 *
	 * @param int $query_id Query ID.
	 * @return array Parsed filter params.
	 */
	private function get_filter_params( $query_id ) {
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';

		$parsed_url = wp_parse_url( esc_url_raw( $request_uri ) );

		if ( empty( $parsed_url['query'] ) ) {
			return array();
		}

		parse_str( $parsed_url['query'], $url_query_params );

		$filter_param_keys = wc_get_container()->get( Params::class )->get_param_keys();

		return array_filter(
			$url_query_params,
			function ( $key ) use ( $filter_param_keys ) {
				return in_array( $key, $filter_param_keys, true );
			},
			ARRAY_FILTER_USE_KEY
		);
	}

	/**
	 * Disable the style handle for this block type. We use block.json to load the style.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Disable the editor style handle for this block type. We use block.json to load the style.
	 *
	 * @return null
	 */
	protected function get_block_type_editor_style() {
		return null;
	}

	/**
	 * Disable the script handle for this block type. We use block.json to load the script.
	 *
	 * @param string|null $key The key of the script to get.
	 * @return null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Get the canonical URL without pagination.
	 *
	 * @param array $filter_params Filter parameters.
	 * @return string Canonical URL without pagination.
	 */
	private function get_canonical_url_no_pagination( $filter_params ) {
		$canonical_url_no_pagination = is_singular() ? get_permalink() : get_pagenum_link( 1 );
		$decoded_url                 = html_entity_decode( $canonical_url_no_pagination, ENT_QUOTES, get_bloginfo( 'charset' ) );
		$parsed_url                  = wp_parse_url( $decoded_url );

		// If there are active filters, $parsed_url['query'] is empty for page or post but not empty for archives.
		if ( empty( $filter_params ) || empty( $parsed_url['query'] ) ) {
			return $decoded_url;
		}

		foreach ( array_keys( $filter_params ) as $key ) {
			$parsed_url['query'] = remove_query_arg( $key, $parsed_url['query'] );
		}

		$url = '';

		if ( isset( $parsed_url['scheme'] ) ) {
			$url .= $parsed_url['scheme'] . '://';
		}

		if ( isset( $parsed_url['host'] ) ) {
			$url .= $parsed_url['host'];
		}

		if ( isset( $parsed_url['port'] ) ) {
			$url .= ':' . $parsed_url['port'];
		}

		if ( isset( $parsed_url['path'] ) ) {
			$url .= $parsed_url['path'];
		}

		if ( ! empty( $parsed_url['query'] ) ) {
			$url .= '?' . $parsed_url['query'];
		}

		if ( isset( $parsed_url['fragment'] ) ) {
			$url .= '#' . $parsed_url['fragment'];
		}

		return $url;
	}
}
PK     \      BlockTypes/AtomicBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * AtomicBlock class.
 *
 * @internal
 */
class AtomicBlock extends AbstractBlock {
	/**
	 * Get the editor script data for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 * @return null
	 */
	protected function get_block_type_editor_script( $key = null ) {
		return null;
	}

	/**
	 * Get the editor style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_editor_style() {
		return null;
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 * @return null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \Ojc[      BlockTypes/EmailContent.phpnu [        <?php // phpcs:ignore Generic.PHP.RequireStrictTypes.MissingDeclaration

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Internal\EmailEditor\BlockEmailRenderer;
use Automattic\WooCommerce\Internal\Admin\EmailPreview\EmailPreview;
use Automattic\WooCommerce\Internal\EmailEditor\WCTransactionalEmails\WCTransactionalEmailPostsManager;

/**
 * EmailContent class.
 */
class EmailContent extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'email-content';

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Get the editor script handle for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 * @return array|string
	 */
	protected function get_block_type_editor_script( $key = null ) {
		$script = [
			'handle'       => 'wc-' . $this->block_name . '-block',
			'path'         => $this->asset_api->get_block_asset_build_path( $this->block_name ),
			'dependencies' => [ 'wc-blocks' ],
		];
		return $key ? $script[ $key ] : $script;
	}

	/**
	 * 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;
	}

	/**
	 * Renders the block preview for the editor.
	 *
	 * @param array $attributes Block attributes.
	 * @return string Rendered block output.
	 */
	protected function render_preview( $attributes ) {
		/**
		 * Email preview instance for rendering dummy content.
		 *
		 * @var EmailPreview $email_preview - email preview instance
		 */
		$email_preview = wc_get_container()->get( EmailPreview::class );

		$type_param = EmailPreview::DEFAULT_EMAIL_TYPE;

		if ( isset( $attributes['postId'] ) ) {
			$email_type_class_name = WCTransactionalEmailPostsManager::get_instance()->get_email_type_class_name_from_post_id( $attributes['postId'] );
			$type_param            = ! empty( $email_type_class_name ) ? $email_type_class_name : $type_param;
		} elseif ( isset( $attributes['emailType'] ) ) {
			$type_param = sanitize_text_field( wp_unslash( $attributes['emailType'] ) );
		}

		try {
			return $email_preview->generate_placeholder_content( $type_param );
		} catch ( \Exception $e ) {
			// Catch other potential errors during content generation.
			return esc_html__( 'There was an error rendering the email preview.', 'woocommerce' );
		}
	}

	/**
	 * Renders Woo content placeholder to be replaced by content during sending.
	 *
	 * @param array     $attributes Block attributes.
	 * @param string    $content Block content.
	 * @param \WP_Block $block Block instance.
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ) {
		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST && isset( $_GET['context'] ) && 'edit' === sanitize_text_field( wp_unslash( $_GET['context'] ) ) ) {
			// Block is being rendered for ServerSideRender editor preview.
			return $this->render_preview( $attributes );
		}

		return BlockEmailRenderer::WOO_EMAIL_CONTENT_PLACEHOLDER;
	}
}
PK     \ip      BlockTypes/ProductCategory.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ProductCategory class.
 */
class ProductCategory extends AbstractProductGrid {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-category';

	/**
	 * Set args specific to this block
	 *
	 * @param array $query_args Query args.
	 */
	protected function set_block_query_args( &$query_args ) {}

	/**
	 * Get block attributes.
	 *
	 * @return array
	 */
	protected function get_block_type_attributes() {
		return array_merge(
			parent::get_block_type_attributes(),
			array(
				'className' => $this->get_schema_string(),
				'orderby'   => $this->get_schema_orderby(),
				'editMode'  => $this->get_schema_boolean( true ),
			)
		);
	}
}
PK     \ě<    *  BlockTypes/MiniCartCheckoutButtonBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Admin\Features\Features;

/**
 * MiniCartCheckoutButtonBlock class.
 */
class MiniCartCheckoutButtonBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'mini-cart-checkout-button-block';

	/**
	 * Render experimental iAPI block markup.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render_experimental_iapi_markup( $attributes, $content, $block ) {
		$default_go_to_checkout_text = __( 'Go to checkout', 'woocommerce' );
		$go_to_checkout_text         = $attributes['checkoutButtonLabel'] ? $attributes['checkoutButtonLabel'] : $default_go_to_checkout_text;
		$checkout_page_id            = wc_get_page_id( 'checkout' );
		$checkout_page_url           = get_permalink( $checkout_page_id );
		$wrapper_attributes          = get_block_wrapper_attributes(
			array(
				'href'  => esc_url( $checkout_page_url ),
				'class' => 'wc-block-components-button wp-element-button wc-block-mini-cart__footer-checkout',
			)
		);

		ob_start();
		?>
		<a <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<div class="wc-block-components-button__text">
				<?php echo esc_html( $go_to_checkout_text ); ?>
			</div>
		</a>
		<?php
		return ob_get_clean();
	}

	/**
	 * Render the markup for the Mini-Cart Contents block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( Features::is_enabled( 'experimental-iapi-mini-cart' ) ) {
			return $this->render_experimental_iapi_markup( $attributes, $content, $block );
		}

		return $content;
	}
}
PK     \bN  N    BlockTypes/FeaturedProduct.phpnu [        <?php
declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Enums\ProductStatus;
use Automattic\WooCommerce\Enums\ProductType;

/**
 * FeaturedProduct class.
 */
class FeaturedProduct extends FeaturedItem {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'featured-product';

	/**
	 * Returns the featured product.
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 * @return \WP_Term|null
	 */
	protected function get_item( $attributes ) {
		$id = absint( $attributes['productId'] ?? 0 );

		$product = wc_get_product( $id );
		if ( ! $product || ( ProductStatus::PUBLISH !== $product->get_status() && ! current_user_can( 'read_product', $id ) ) ) {
			return null;
		}

		return $product;
	}

	/**
	 * Returns the name of the featured product.
	 *
	 * @param \WC_Product $product Product object.
	 * @return string
	 */
	protected function get_item_title( $product ) {
		return $product->get_title();
	}

	/**
	 * Returns the featured product image URL.
	 *
	 * @param \WC_Product $product Product object.
	 * @param string      $size    Image size, defaults to 'full'.
	 * @return string
	 */
	protected function get_item_image( $product, $size = 'full' ) {
		$image = '';
		if ( $product->get_image_id() ) {
			$image = wp_get_attachment_image_url( $product->get_image_id(), $size );
		} elseif ( $product->get_parent_id() ) {
			$parent_product = wc_get_product( $product->get_parent_id() );
			if ( $parent_product ) {
				$image = wp_get_attachment_image_url( $parent_product->get_image_id(), $size );
			}
		}

		return $image;
	}

	/**
	 * Renders the featured product attributes.
	 *
	 * @param \WC_Product $product Product object.
	 * @param array       $attributes Block attributes. Default empty array.
	 * @return string
	 */
	protected function render_attributes( $product, $attributes ) {
		$output = '';

		// Backwards compatibility: Only render legacy attributes if `editMode` exists as boolean value
		// This allows us to distinguish between old and new version of the block (which accept inner blocks).
		if ( array_key_exists( 'editMode', $attributes ) && is_bool( $attributes['editMode'] ) ) {
			$legacy_title = sprintf(
				'<h2 class="wc-block-featured-product__title">%s</h2>',
				wp_kses_post( $product->get_title() )
			);
			if ( $product->is_type( ProductType::VARIATION ) ) {
				$legacy_title .= sprintf(
					'<h3 class="wc-block-featured-product__variation">%s</h3>',
					wp_kses_post( wc_get_formatted_variation( $product, true, true, false ) )
				);
			}

			$output .= $legacy_title;

			if (
				! isset( $attributes['showDesc'] ) ||
				( isset( $attributes['showDesc'] ) && false !== $attributes['showDesc'] )
			) {
				$desc_str = sprintf(
					'<div class="wc-block-featured-product__description">%s</div>',
					wc_format_content( wp_kses_post( $product->get_short_description() ? $product->get_short_description() : wc_trim_string( $product->get_description(), 400 ) ) )
				);
				$output  .= $desc_str;
			}

			if (
				! isset( $attributes['showPrice'] ) ||
				( isset( $attributes['showPrice'] ) && false !== $attributes['showPrice'] )
			) {
				$price_str = sprintf(
					'<div class="wc-block-featured-product__price">%s</div>',
					wp_kses_post( $product->get_price_html() )
				);
				$output   .= $price_str;
			}
		}

		return $output;
	}
}
PK     \-  -  2  BlockTypes/CheckoutOrderSummaryCouponFormBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutOrderSummaryCouponFormBlock class.
 */
class CheckoutOrderSummaryCouponFormBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-order-summary-coupon-form-block';
}
PK     \_v
    *  BlockTypes/CheckoutBillingAddressBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutBillingAddressBlock class.
 */
class CheckoutBillingAddressBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-billing-address-block';
}
PK     \d      BlockTypes/PriceFilter.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * PriceFilter class.
 */
class PriceFilter extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name     = 'price-filter';
	const MIN_PRICE_QUERY_VAR = 'min_price';
	const MAX_PRICE_QUERY_VAR = 'max_price';

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );
		$this->asset_data_registry->add( 'attributes', array_values( wc_get_attribute_taxonomies() ) );

		// Enqueue any `queryState` that the UI will need to be aware of
		// (Ex: the category id if we're on a category page, the tag id if we're on a tag page/etc).
		$query_state = [];

		if ( is_product_category() ) {
			$query_state['category'] = get_queried_object_id();
		}
		if ( is_product_tag() ) {
			$query_state['tag'] = get_queried_object()->term_id;
		}

		$this->asset_data_registry->add( 'queryState', $query_state );
	}
}
PK     \䢱P&  &  0  BlockTypes/CheckoutOrderSummarySubtotalBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutOrderSummarySubtotalBlock class.
 */
class CheckoutOrderSummarySubtotalBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-order-summary-subtotal-block';
}
PK     \	      .  BlockTypes/CheckoutOrderSummaryTotalsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutOrderSummaryTotalsBlock class.
 */
class CheckoutOrderSummaryTotalsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-order-summary-totals-block';
}
PK     \YE      BlockTypes/SingleProduct.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\ProductDataUtils;
use Automattic\WooCommerce\Enums\ProductType;

/**
 * SingleProduct class.
 */
class SingleProduct extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'single-product';

	/**
	 * Product ID of the current product to be displayed in the Single Product block.
	 * This is used to replace the global post for the Single Product inner blocks.
	 *
	 * @var int
	 */
	protected $product_id = 0;

	/**
	 * Single Product inner blocks names.
	 * This is used to map all the inner blocks for a Single Product block.
	 *
	 * @var array
	 */
	protected $single_product_inner_blocks_names = [];

	/**
	 * Initialize the block and Hook into the `render_block_context` filter
	 * to update the context with the correct data.
	 *
	 * @var string
	 */
	protected function initialize() {
		parent::initialize();
		add_filter( 'render_block_context', [ $this, 'update_context' ], 10, 3 );
		add_filter( 'render_block_core/post-excerpt', [ $this, 'restore_global_post' ], 10, 3 );
		add_filter( 'render_block_core/post-title', [ $this, 'restore_global_post' ], 10, 3 );
	}

	/**
	 * Restore the global post variable right before generating the render output for the post title and/or post excerpt blocks.
	 *
	 * This is required due to the changes made via the replace_post_for_single_product_inner_block method.
	 * It is a temporary fix to ensure these blocks work as expected until Gutenberg versions 15.2 and 15.6 are part of the core of WordPress.
	 *
	 * @see https://github.com/WordPress/gutenberg/pull/48001
	 * @see https://github.com/WordPress/gutenberg/pull/49495
	 *
	 * @param  string    $block_content  The block content.
	 * @param  array     $parsed_block  The full block, including name and attributes.
	 * @param  \WP_Block $block_instance  The block instance.
	 *
	 * @return mixed
	 */
	public function restore_global_post( $block_content, $parsed_block, $block_instance ) {
		if ( isset( $block_instance->context['singleProduct'] ) && $block_instance->context['singleProduct'] ) {
			wp_reset_postdata();
		}

		return $block_content;
	}

	/**
	 * Update the context by injecting the correct post data
	 * for each one of the Single Product inner blocks.
	 *
	 * @param array    $context Block context.
	 * @param array    $block Block attributes.
	 * @param WP_Block $parent_block Block instance.
	 *
	 * @return array Updated block context.
	 */
	public function update_context( $context, $block, $parent_block ) {
		if ( 'woocommerce/single-product' === $block['blockName']
			&& isset( $block['attrs']['productId'] ) ) {
				$this->product_id = $block['attrs']['productId'];

				$this->single_product_inner_blocks_names = array_reverse(
					$this->extract_single_product_inner_block_names( $block )
				);
		}

		$this->replace_post_for_single_product_inner_block( $block, $context );

		return $context;
	}

	/**
	 * Extract the inner block names for the Single Product block. This way it's possible
	 * to map all the inner blocks for a Single Product block and manipulate the data as needed.
	 *
	 * @param array $block The Single Product block or its inner blocks.
	 * @param array $result Array of inner block names.
	 *
	 * @return array Array containing all the inner block names of a Single Product block.
	 */
	protected function extract_single_product_inner_block_names( $block, &$result = [] ) {
		if ( isset( $block['blockName'] ) ) {
			$result[] = $block['blockName'];
		}

		if ( 'woocommerce/product-template' === $block['blockName'] || 'core/post-template' === $block['blockName'] ) {
			return $result;
		}

		if ( isset( $block['innerBlocks'] ) ) {
			foreach ( $block['innerBlocks'] as $inner_block ) {
				$this->extract_single_product_inner_block_names( $inner_block, $result );
			}
		}
		return $result;
	}

	/**
	 * Replace the global post for the Single Product inner blocks and reset it after.
	 *
	 * This is needed because some of the inner blocks may use the global post
	 * instead of fetching the product through the `productId` attribute, so even if the
	 * `productId` is passed to the inner block, it will still use the global post.
	 *
	 * @param array $block Block attributes.
	 * @param array $context Block context.
	 */
	protected function replace_post_for_single_product_inner_block( $block, &$context ) {
		if ( $this->single_product_inner_blocks_names ) {
			// Find the block index in $this->single_product_inner_blocks_names
			// starting from the end.
			$block_index_reversed = array_search( $block['blockName'], array_reverse( $this->single_product_inner_blocks_names ), true );

			if ( false !== $block_index_reversed ) {
				$block_index = count( $this->single_product_inner_blocks_names ) - (int) $block_index_reversed - 1;

				$block_name = $block['blockName'];

				// Remove all blocks after the current one. In some cases, like
				// in the Product Gallery block, inner blocks are rendered
				// directly by the parent block, so we need to skip them.
				$this->single_product_inner_blocks_names = array_slice( $this->single_product_inner_blocks_names, 0, $block_index );

				/**
				 * This is a temporary fix to ensure the Post Title and Excerpt blocks work as expected
				 * until Gutenberg versions 15.2 and 15.6 are included in the core of WordPress.
				 *
				 * Important: the original post data is restored in the restore_global_post method.
				 *
				 * @see https://github.com/WordPress/gutenberg/pull/48001
				 * @see https://github.com/WordPress/gutenberg/pull/49495
				 */
				if ( 'core/post-excerpt' === $block_name || 'core/post-title' === $block_name ) {
					global $post;
					// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
					$post = get_post( $this->product_id );

					if ( $post instanceof \WP_Post ) {
						setup_postdata( $post );
					}
				}

				$context['postId']        = $this->product_id;
				$context['singleProduct'] = true;
			}
		}
	}

	/**
	 * Render the Single Product block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 *
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$product = wc_get_product( $block->context['postId'] );

		if ( ! $product instanceof \WC_Product ) {
			return '';
		}

		$interactivity_context = array(
			'productId'   => $product->get_id(),
			'variationId' => null,
		);

		$html = new \WP_HTML_Tag_Processor( $content );

		if ( $html->next_tag( array( 'tag_name' => 'div' ) ) ) {
			$html->set_attribute( 'data-wp-interactive', $this->get_full_block_name() );
			$html->set_attribute( 'data-wp-context', wp_json_encode( $interactivity_context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) );
		}

		$updated_html = $html->get_updated_html();

		return parent::render( $attributes, $updated_html, $block );
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 *
	 * @return null This block has no frontend script.
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}
}
PK     \      BlockTypes/ProductPrice.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Enums\ProductType;

/**
 * ProductPrice class.
 */
class ProductPrice extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;


	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-price';

	/**
	 * API version name.
	 *
	 * @var string
	 */
	protected $api_version = '3';

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Overwrite parent method to prevent script registration.
	 *
	 * It is necessary to register and enqueues assets during the render
	 * phase because we want to load assets only if the block has the content.
	 */
	protected function register_block_type_assets() {
		return null;
	}

	/**
	 * Register the context.
	 */
	protected function get_block_type_uses_context() {
		return [ 'query', 'queryId', 'postId' ];
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
		$product = wc_get_product( $post_id );

		if ( $product ) {
			$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );

			$is_descendant_of_product_collection       = isset( $block->context['query']['isProductCollectionBlock'] );
			$is_descendant_of_grouped_product_selector = isset( $block->context['isDescendantOfGroupedProductSelector'] );
			$is_interactive                            = ! $is_descendant_of_product_collection && ! $is_descendant_of_grouped_product_selector && $product->is_type( ProductType::VARIABLE );

			$wrapper_attributes     = array(
				'style' => $styles_and_classes['styles'] ?? '',
				'class' => $styles_and_classes['classes'] ?? '',
			);
			$interactive_attributes = '';
			$context_directive      = '';

			if ( $is_interactive ) {
				// phpcs:ignore Generic.Commenting.DocComment.MissingShort -- Type hint for PHPStan.
				/** @var \WC_Product_Variable $product */
				// Check if variation prices differ (replicates logic from WC_Product_Variable::get_available_variation).
				$prices_vary = $product->get_variation_sale_price( 'min' ) !== $product->get_variation_sale_price( 'max' )
					|| $product->get_variation_regular_price( 'min' ) !== $product->get_variation_regular_price( 'max' );

				$formatted_variations_data = array();

				if ( $prices_vary ) {
					$variations_data = $product->get_available_variations( 'objects' );

					foreach ( $variations_data as $variation ) {
						/**
						 * Filter whether to show variation price.
						 * Replicates the filter from WC_Product_Variable::get_available_variation().
						 *
						 * @since 2.4.0
						 *
						 * @param bool                  $show_price Whether to show the price.
						 * @param \WC_Product_Variable  $product    The variable product.
						 * @param \WC_Product_Variation $variation  The variation.
						 */
						$show_variation_price = apply_filters(
							'woocommerce_show_variation_price',
							true,
							$product,
							$variation
						);

						if ( ! $show_variation_price ) {
							continue;
						}

						$formatted_variations_data[ $variation->get_id() ] = array(
							'price_html' => '<span class="price">' . $variation->get_price_html() . '</span>',
						);
					}
				}

				if ( empty( $formatted_variations_data ) ) {
					$is_interactive = false;
				} else {
					wp_interactivity_config(
						'woocommerce',
						array(
							'products' => array(
								$product->get_id() => array(
									'price_html' => $product->get_price_html(),
									'variations' => $formatted_variations_data,
								),
							),
						)
					);

					wp_enqueue_script_module( 'woocommerce/product-elements' );
					$wrapper_attributes['data-wp-interactive'] = 'woocommerce/product-elements';
					$context_directive                         = wp_interactivity_data_wp_context(
						array(
							'productElementKey' => 'price_html',
						)
					);
					$interactive_attributes                    = 'data-wp-watch="callbacks.updateValue" aria-live="polite" aria-atomic="true"';
				}
			}

			return sprintf(
				'<div %1$s %2$s><div class="wc-block-components-product-price wc-block-grid__product-price" %3$s>
					%4$s
				</div></div>',
				get_block_wrapper_attributes( $wrapper_attributes ),
				$context_directive,
				$interactive_attributes,
				$product->get_price_html()
			);
		}
	}
}
PK     \Gg#  #    BlockTypes/ProductImage.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * ProductImage class.
 */
class ProductImage extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-image';

	/**
	 * API version name.
	 *
	 * @var string
	 */
	protected $api_version = '3';

	/**
	 * It is necessary to register and enqueues assets during the render phase because we want to load assets only if the block has the content.
	 */
	protected function register_block_type_assets() {
		return null;
	}

	/**
	 * Register the context.
	 */
	protected function get_block_type_uses_context() {
		return [ 'query', 'queryId', 'postId' ];
	}

	/**
	 * Get the block's attributes.
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 * @return array  Block attributes merged with defaults.
	 */
	private function parse_attributes( $attributes ) {
		// These should match what's set in JS `registerBlockType`.
		$defaults = array(
			'showProductLink'                  => true,
			'imageSizing'                      => 'single',
			'productId'                        => 'number',
			'isDescendentOfQueryLoop'          => 'false',
			'isDescendentOfSingleProductBlock' => 'false',
			'scale'                            => 'cover',
		);

		return wp_parse_args( $attributes, $defaults );
	}

	/**
	 * Render on Sale Badge.
	 *
	 * @param \WC_Product $product Product object.
	 * @param array       $attributes Attributes.
	 * @return string
	 */
	private function render_on_sale_badge( $product, $attributes ) {
		if (
			! $product->is_on_sale()
			|| ! isset( $attributes['showSaleBadge'] )
			|| ( isset( $attributes['showSaleBadge'] ) && false === $attributes['showSaleBadge'] )
		) {
			return '';
		}

		$align = $attributes['saleBadgeAlign'] ?? 'right';

		$block = new \WP_Block(
			array(
				'blockName' => 'woocommerce/product-sale-badge',
				'attrs'     => array(
					'align' => $align,
				),
			),
			array(
				'postId' => $product->get_id(),
			)
		);

		return $block->render();
	}

	/**
	 * Render anchor.
	 *
	 * @param \WC_Product $product       Product object.
	 * @param string      $on_sale_badge Return value from $render_image.
	 * @param string      $product_image Return value from $render_on_sale_badge.
	 * @param array       $attributes    Attributes.
	 * @param string      $inner_blocks_content Rendered HTML of inner blocks.
	 * @return string
	 */
	private function render_anchor( $product, $on_sale_badge, $product_image, $attributes, $inner_blocks_content ) {
		$product_permalink = $product->get_permalink();

		$is_link        = isset( $attributes['showProductLink'] ) ? $attributes['showProductLink'] : true;
		$href_attribute = $is_link ? sprintf( 'href="%s"', esc_url( $product_permalink ) ) : 'href="#" onclick="return false;"';
		$wrapper_style  = ! $is_link ? 'pointer-events: none; cursor: default;' : '';
		$directive      = $is_link ? 'data-wp-on--click="woocommerce/product-collection::actions.viewProduct"' : '';

		$inner_blocks_container = sprintf(
			'<div class="wc-block-components-product-image__inner-container">%s</div>',
			$inner_blocks_content
		);

		return sprintf(
			'<a %1$s style="%2$s" %3$s>%4$s%5$s%6$s</a>',
			$href_attribute,
			esc_attr( $wrapper_style ),
			$directive,
			$on_sale_badge,
			$product_image,
			$inner_blocks_container
		);
	}

	/**
	 * Render Image.
	 *
	 * @param \WC_Product $product Product object.
	 * @param array       $attributes Parsed attributes.
	 * @param int|null    $image_id Optional image ID from context.
	 * @return string
	 */
	private function render_image( $product, $attributes, $image_id = null ) {
		$image_size = 'single' === $attributes['imageSizing'] ? 'woocommerce_single' : 'woocommerce_thumbnail';

		$image_style = '';

		if ( ! empty( $attributes['height'] ) ) {
			$image_style .= sprintf( 'height:%s;', $attributes['height'] );
		}
		if ( ! empty( $attributes['width'] ) ) {
			$image_style .= sprintf( 'width:%s;', $attributes['width'] );
		}
		if ( ! empty( $attributes['scale'] ) ) {
			$image_style .= sprintf( 'object-fit:%s;', $attributes['scale'] );
		}

		// Keep this aspect ratio for backward compatibility.
		if ( ! empty( $attributes['aspectRatio'] ) ) {
			$image_style .= sprintf( 'aspect-ratio:%s;', $attributes['aspectRatio'] );
		}

		if ( ! empty( $attributes['style']['dimensions']['aspectRatio'] ) ) {
			$image_style .= sprintf( 'aspect-ratio:%s;', $attributes['style']['dimensions']['aspectRatio'] );
		}

		if ( ! empty( $attributes['style']['dimensions']['minHeight'] ) ) {
			$image_style .= sprintf( 'min-height:%s;', $attributes['style']['dimensions']['minHeight'] );
		}

		$featured_image_id          = (int) $product->get_image_id();
		$provided_image_id_is_valid = false;

		if ( $image_id ) {
			$gallery_image_ids          = ProductGalleryUtils::get_all_image_ids( $product );
			$available_image_ids        = array_merge( [ $featured_image_id ], $gallery_image_ids );
			$provided_image_id_is_valid = in_array( $image_id, $available_image_ids, true );
		}

		$target_image_id = $provided_image_id_is_valid ? $image_id : $featured_image_id;

		if ( ! $target_image_id ) {
			return wc_placeholder_img( $image_size, array( 'style' => $image_style ) );
		}

		$alt_text = get_post_meta( $target_image_id, '_wp_attachment_image_alt', true );

		/**
		 * Filters the loading attribute for product images.
		 *
		 * Allowed values are 'lazy', 'eager', and 'auto'. Any other value will result in default browser behavior.
		 *
		 * @since 10.6.0
		 *
		 * @param string $loading_attr The loading attribute. Default 'lazy'.
		 * @param int    $image_id     Target image ID.
		 */
		$loading_attr = apply_filters(
			'woocommerce_product_image_loading_attr',
			'lazy',
			$target_image_id,
		);

		$loading_attr    = is_string( $loading_attr ) ? strtolower( trim( $loading_attr ) ) : '';
		$allowed_loading = array( 'lazy', 'eager', 'auto' );

		if ( ! in_array( $loading_attr, $allowed_loading, true ) ) {
			$loading_attr = '';
		}

		$attr = array(
			'alt'           => empty( $alt_text ) ? $product->get_title() : $alt_text,
			'data-testid'   => 'product-image',
			'data-image-id' => $target_image_id,
			'style'         => $image_style,
		);

		if ( ! empty( $loading_attr ) ) {
			$attr['loading'] = $loading_attr;
		}

		return $provided_image_id_is_valid ? wp_get_attachment_image( $image_id, $image_size, false, $attr ) : $product->get_image( $image_size, $attr );
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		$this->asset_data_registry->add( 'isBlockTheme', wp_is_block_theme() );
		$this->asset_data_registry->add( 'placeholderImgSrcFullSize', wc_placeholder_img_src( 'woocommerce_single' ) );
	}

	/**
	 * Include and render the block
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$parsed_attributes  = $this->parse_attributes( $attributes );
		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );
		$post_id            = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
		$image_id           = isset( $block->context['imageId'] ) ? (int) $block->context['imageId'] : null;
		$product            = wc_get_product( $post_id );
		$aspect_ratio       = $parsed_attributes['aspectRatio'] ?? $parsed_attributes['style']['dimensions']['aspectRatio'] ?? 'auto';
		$aspect_ratio_class = 'wc-block-components-product-image--aspect-ratio-' . str_replace( '/', '-', $aspect_ratio );

		$classes = implode(
			' ',
			array_filter(
				array(
					'wc-block-components-product-image wc-block-grid__product-image',
					$aspect_ratio_class,
					esc_attr( $classes_and_styles['classes'] ),
				)
			)
		);

		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class' => esc_attr( $classes ),
				'style' => esc_attr( $classes_and_styles['styles'] ),
			)
		);

		if ( $product ) {
			$inner_content = $this->render_anchor(
				$product,
				$this->render_on_sale_badge( $product, $parsed_attributes ),
				$this->render_image( $product, $parsed_attributes, $image_id ),
				$attributes,
				$content
			);

			return sprintf(
				'<div %1$s>%2$s</div>',
				$wrapper_attributes,
				$inner_content
			);
		}

		return '';
	}
}
PK     \    *  BlockTypes/MiniCartShoppingButtonBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Admin\Features\Features;

/**
 * MiniCartShoppingButtonBlock class.
 */
class MiniCartShoppingButtonBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'mini-cart-shopping-button-block';

	/**
	 * Render the markup for the Mini-Cart Shopping Button block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( Features::is_enabled( 'experimental-iapi-mini-cart' ) ) {
			return $this->render_experimental_iapi_markup( $attributes, $content, $block );
		}

		return $content;
	}

	/**
	 * Render experimental iAPI powered  markup for the Mini-Cart Contents block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render_experimental_iapi_markup( $attributes, $content, $block ) {
		ob_start();
		$shop_url                     = wc_get_page_permalink( 'shop' );
		$default_start_shopping_label = __( 'Start shopping', 'woocommerce' );
		$start_shopping_label         = $attributes['startShoppingButtonLabel'] ? $attributes['startShoppingButtonLabel'] : $default_start_shopping_label;
		$wrapper_attributes           = get_block_wrapper_attributes( array( 'class' => 'wc-block-components-button wp-element-button wc-block-mini-cart__shopping-button' ) );
		?>
		<div class="wp-block-button has-text-align-center">
			<a
				href="<?php echo esc_attr( $shop_url ); ?>"
				<?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			>
				<div class="wc-block-components-button__text">
					<?php echo esc_html( $start_shopping_label ); ?>
				</div>
			</a>
		</div>
		<?php
		return ob_get_clean();
	}
}
PK     \!ӧD4  D4    BlockTypes/Cart.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;

/**
 * Cart class.
 *
 * @internal
 */
class Cart extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart';

	/**
	 * Chunks build folder.
	 *
	 * @var string
	 */
	protected $chunks_folder = 'cart-blocks';

	/**
	 * 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' ) );
	}

	/**
	 * Dequeues the scripts added by WC Core to the Cart page.
	 *
	 * @return void
	 */
	public function dequeue_woocommerce_core_scripts() {
		wp_dequeue_script( 'wc-cart' );
		wp_dequeue_script( 'wc-password-strength-meter' );
		wp_dequeue_script( 'selectWoo' );
		wp_dequeue_style( 'select2' );
	}

	/**
	 * Register block pattern for Empty Cart Message to make it translatable.
	 */
	public function register_patterns() {
		$shop_permalink = wc_get_page_permalink( 'shop' );

		register_block_pattern(
			'woocommerce/cart-heading',
			array(
				'title'    => '',
				'inserter' => false,
				'content'  => '<!-- wp:heading {"align":"wide", "level":1} --><h1 class="wp-block-heading alignwide">' . esc_html__( 'Cart', 'woocommerce' ) . '</h1><!-- /wp:heading -->',
			)
		);
		register_block_pattern(
			'woocommerce/cart-cross-sells-message',
			array(
				'title'    => '',
				'inserter' => false,
				'content'  => '<!-- wp:heading {"fontSize":"large"} --><h2 class="wp-block-heading has-large-font-size">' . esc_html__( 'You may be interested in…', 'woocommerce' ) . '</h2><!-- /wp:heading -->',
			)
		);
		register_block_pattern(
			'woocommerce/cart-empty-message',
			array(
				'title'    => '',
				'inserter' => false,
				'content'  => '
					<!-- wp:heading {"textAlign":"center","className":"with-empty-cart-icon wc-block-cart__empty-cart__title"} --><h2 class="wp-block-heading has-text-align-center with-empty-cart-icon wc-block-cart__empty-cart__title">' . esc_html__( 'Your cart is currently empty!', 'woocommerce' ) . '</h2><!-- /wp:heading -->
					<!-- wp:paragraph {"align":"center"} --><p class="has-text-align-center"><a href="' . esc_attr( esc_url( $shop_permalink ) ) . '">' . esc_html__( 'Browse store', 'woocommerce' ) . '</a></p><!-- /wp:paragraph -->
				',
			)
		);
		register_block_pattern(
			'woocommerce/cart-new-in-store-message',
			array(
				'title'    => '',
				'inserter' => false,
				'content'  => '<!-- wp:heading {"textAlign":"center"} --><h2 class="wp-block-heading has-text-align-center">' . esc_html__( 'New in store', 'woocommerce' ) . '</h2><!-- /wp:heading -->',
			)
		);
	}

	/**
	 * Get the editor script handle for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 * @return array|string;
	 */
	protected function get_block_type_editor_script( $key = null ) {
		$script = [
			'handle'       => 'wc-' . $this->block_name . '-block',
			'path'         => $this->asset_api->get_block_asset_build_path( $this->block_name ),
			'dependencies' => [ 'wc-blocks' ],
		];
		return $key ? $script[ $key ] : $script;
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string
	 */
	protected function get_block_type_script( $key = null ) {
		$script = [
			'handle'       => 'wc-' . $this->block_name . '-block-frontend',
			'path'         => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
			'dependencies' => [],
		];
		return $key ? $script[ $key ] : $script;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string[]
	 */
	protected function get_block_type_style() {
		return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
	}

	/**
	 * Enqueue frontend assets for this block, just in time for rendering.
	 *
	 * @param array    $attributes  Any attributes that currently are available from the block.
	 * @param string   $content    The block content.
	 * @param WP_Block $block    The block object.
	 */
	protected function enqueue_assets( array $attributes, $content, $block ) {
		/**
		 * Fires before cart block scripts are enqueued.
		 *
		 * @since 2.6.0
		 */
		do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_before' );
		parent::enqueue_assets( $attributes, $content, $block );
		/**
		 * Fires after cart block scripts are enqueued.
		 *
		 * @since 2.6.0
		 */
		do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_after' );
	}

	/**
	 * Append frontend scripts when rendering the Cart block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		// Dequeue the core scripts when rendering this block.
		add_action( 'wp_enqueue_scripts', array( $this, 'dequeue_woocommerce_core_scripts' ), 20 );

		/**
		 * We need to check if $content has any templates from prior iterations of the block, in order to update to the latest iteration.
		 * We test the iteration version by searching for new blocks brought in by it.
		 * The blocks used for testing should be always available in the block (not removable by the user).
		 */

		$regex_for_filled_cart_block = '/<div[^<]*?data-block-name="woocommerce\/filled-cart-block"[^>]*?>/mi';
		// Filled Cart block was added in i2, so we search for it to see if we have a Cart i1 template.
		$has_i1_template = ! preg_match( $regex_for_filled_cart_block, $content );

		if ( $has_i1_template ) {
			/**
			 * This fallback structure needs to match the defaultTemplate variables defined in the block's edit.tsx files,
			 * starting from the parent block and going down each inner block, in the order the blocks were registered.
			 */
			$inner_blocks_html = '$0
			<div data-block-name="woocommerce/filled-cart-block" class="wp-block-woocommerce-filled-cart-block">
				<div data-block-name="woocommerce/cart-items-block" class="wp-block-woocommerce-cart-items-block">
					<div data-block-name="woocommerce/cart-line-items-block" class="wp-block-woocommerce-cart-line-items-block"></div>
				</div>
				<div data-block-name="woocommerce/cart-totals-block" class="wp-block-woocommerce-cart-totals-block">
					<div data-block-name="woocommerce/cart-order-summary-block" class="wp-block-woocommerce-cart-order-summary-block"></div>
					<div data-block-name="woocommerce/cart-express-payment-block" class="wp-block-woocommerce-cart-express-payment-block"></div>
					<div data-block-name="woocommerce/proceed-to-checkout-block" class="wp-block-woocommerce-proceed-to-checkout-block"></div>
					<div data-block-name="woocommerce/cart-accepted-payment-methods-block" class="wp-block-woocommerce-cart-accepted-payment-methods-block"></div>
				</div>
			</div>
			<div data-block-name="woocommerce/empty-cart-block" class="wp-block-woocommerce-empty-cart-block">
			';

			$content = preg_replace( '/<div class="[a-zA-Z0-9_\- ]*wp-block-woocommerce-cart[a-zA-Z0-9_\- ]*">/mi', $inner_blocks_html, $content );
			$content = $content . '</div>';
		}

		/**
		 * Cart i3 added inner blocks for Order summary. We need to add them to Cart i2 templates.
		 * The order needs to match the order in which these blocks were registered.
		 */
		$order_summary_with_inner_blocks = '$0
			<div data-block-name="woocommerce/cart-order-summary-heading-block" class="wp-block-woocommerce-cart-order-summary-heading-block"></div>
			<div data-block-name="woocommerce/cart-order-summary-subtotal-block" class="wp-block-woocommerce-cart-order-summary-subtotal-block"></div>
			<div data-block-name="woocommerce/cart-order-summary-fee-block" class="wp-block-woocommerce-cart-order-summary-fee-block"></div>
			<div data-block-name="woocommerce/cart-order-summary-discount-block" class="wp-block-woocommerce-cart-order-summary-discount-block"></div>
			<div data-block-name="woocommerce/cart-order-summary-coupon-form-block" class="wp-block-woocommerce-cart-order-summary-coupon-form-block"></div>
			<div data-block-name="woocommerce/cart-order-summary-shipping-form-block" class="wp-block-woocommerce-cart-order-summary-shipping-block"></div>
			<div data-block-name="woocommerce/cart-order-summary-taxes-block" class="wp-block-woocommerce-cart-order-summary-taxes-block"></div>
		';
		// Order summary subtotal block was added in i3, so we search for it to see if we have a Cart i2 template.
		$regex_for_order_summary_subtotal = '/<div[^<]*?data-block-name="woocommerce\/cart-order-summary-subtotal-block"[^>]*?>/mi';
		$regex_for_order_summary          = '/<div[^<]*?data-block-name="woocommerce\/cart-order-summary-block"[^>]*?>/mi';
		$has_i2_template                  = ! preg_match( $regex_for_order_summary_subtotal, $content );

		if ( $has_i2_template ) {
			$content = preg_replace( $regex_for_order_summary, $order_summary_with_inner_blocks, $content );
		}

		return $content;
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );

		$this->asset_data_registry->add( 'countryData', CartCheckoutUtils::get_country_data() );
		$this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ) );
		$this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ) );
		$this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled() );
		$this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled() );
		$this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled() );
		$this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ) );
		$this->asset_data_registry->register_page_id( isset( $attributes['checkoutPageId'] ) ? $attributes['checkoutPageId'] : 0 );
		$this->asset_data_registry->add( 'isBlockTheme', wp_is_block_theme() );
		$this->asset_data_registry->add( 'shippingMethodsExist', CartCheckoutUtils::shipping_methods_exist() > 0 );

		$is_block_editor = $this->is_block_editor();

		// Check `current_user_can` so we can show notices about incompatible extensions in the front-end to admins too.
		if ( ( $is_block_editor || current_user_can( 'manage_woocommerce' ) ) && ! $this->asset_data_registry->exists( 'incompatibleExtensions' ) ) {
			if ( class_exists( '\Automattic\WooCommerce\Utilities\FeaturesUtil' ) && function_exists( 'get_plugins' ) ) {
				$declared_extensions     = \Automattic\WooCommerce\Utilities\FeaturesUtil::get_compatible_plugins_for_feature( 'cart_checkout_blocks' );
				$all_plugins             = \get_plugins();
				$incompatible_extensions = array_reduce(
					$declared_extensions['incompatible'],
					function ( array $acc, $item ) use ( $all_plugins ) {
						$plugin      = $all_plugins[ $item ] ?? null;
						$plugin_id   = $plugin['TextDomain'] ?? dirname( $item );
						$plugin_name = $plugin['Name'] ?? $plugin_id;
						$acc[]       = [
							'id'    => $plugin_id,
							'title' => $plugin_name,
						];
						return $acc;
					},
					[]
				);
				$this->asset_data_registry->add( 'incompatibleExtensions', $incompatible_extensions );
			}
		}

		// Hydrate the following data depending on admin or frontend context.
		if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
			$this->asset_data_registry->hydrate_api_request( '/wc/store/v1/cart' );
		}

		/**
		 * Fires after cart block data is registered.
		 *
		 * @since 2.6.0
		 */
		do_action( 'woocommerce_blocks_cart_enqueue_data' );
	}

	/**
	 * Register script and style assets for the block type before it is registered.
	 *
	 * This registers the scripts; it does not enqueue them.
	 */
	protected function register_block_type_assets() {
		parent::register_block_type_assets();
		$chunks        = $this->get_chunks_paths( $this->chunks_folder );
		$vendor_chunks = $this->get_chunks_paths( 'vendors--cart-blocks' );
		$shared_chunks = [];
		$this->register_chunk_translations( array_merge( $chunks, $vendor_chunks, $shared_chunks ) );
	}

	/**
	 * Get list of Cart block & its inner-block types.
	 *
	 * @return array;
	 */
	public static function get_cart_block_types() {
		return [
			'Cart',
			'CartOrderSummaryTaxesBlock',
			'CartOrderSummarySubtotalBlock',
			'CartOrderSummaryTotalsBlock',
			'FilledCartBlock',
			'EmptyCartBlock',
			'CartTotalsBlock',
			'CartItemsBlock',
			'CartLineItemsBlock',
			'CartOrderSummaryBlock',
			'CartExpressPaymentBlock',
			'ProceedToCheckoutBlock',
			'CartAcceptedPaymentMethodsBlock',
			'CartOrderSummaryCouponFormBlock',
			'CartOrderSummaryDiscountBlock',
			'CartOrderSummaryFeeBlock',
			'CartOrderSummaryHeadingBlock',
			'CartOrderSummaryShippingBlock',
			'CartCrossSellsBlock',
			'CartCrossSellsProductsBlock',
		];
	}
}
PK     \<n  n  "  BlockTypes/NextPreviousButtons.phpnu [        <?php declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * NextPreviousButtons class.
 */
class NextPreviousButtons extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name. Block has been initially created for Product Gallery Viewer block
	 * hence the slug is related to this block. But it can be used for other blocks as well.
	 *
	 * @var string
	 */
	protected $block_name = 'product-gallery-large-image-next-previous';

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$iapi_provider = $block->context['iapi/provider'] ?? null;

		if ( empty( $iapi_provider ) ) {
			return '';
		}

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'align' ) );
		$vertical_alignment = StyleAttributesUtils::get_align_class_and_style( $attributes );

		$left_arrow_path  = 'M6.445 12.005.986 6 6.445-.005l1.11 1.01L3.014 6l4.54 4.995-1.109 1.01Z';
		$right_arrow_path = 'M1.555-.004 7.014 6l-5.459 6.005-1.11-1.01L4.986 6 .446 1.005l1.109-1.01Z';

		ob_start();
		?>
	<div
			class="wc-block-next-previous-buttons <?php echo esc_attr( $vertical_alignment['class'] ); ?>"
			data-wp-interactive="<?php echo esc_attr( $iapi_provider ); ?>"
			data-wp-bind--hidden="context.hideNextPreviousButtons"
		>
			<button
				class="wc-block-next-previous-buttons__button <?php echo esc_attr( $classes_and_styles['classes'] ); ?>"
				style="<?php echo esc_attr( $classes_and_styles['styles'] ); ?>"
				data-wp-on--click="actions.onClickPrevious"
				data-wp-on--keydown="actions.onKeyDownPrevious"
				data-wp-bind--aria-disabled="context.isDisabledPrevious"
				data-wp-bind--aria-label="context.ariaLabelPrevious"
			>
				<svg
					class="wc-block-next-previous-buttons__icon wc-block-next-previous-buttons__icon--left"
					xmlns="http://www.w3.org/2000/svg"
					width="8"
					height="12"
					fill="none"
				>
					<path
						fill="currentColor"
						fillRule="evenodd"
						d="<?php echo is_rtl() ? esc_attr( $right_arrow_path ) : esc_attr( $left_arrow_path ); ?>"
						clipRule="evenodd"
					/>
				</svg>
			</button>
			<button
				class="wc-block-next-previous-buttons__button <?php echo esc_attr( $classes_and_styles['classes'] ); ?>"
				style="<?php echo esc_attr( $classes_and_styles['styles'] ); ?>"
				data-wp-on--click="actions.onClickNext"
				data-wp-on--keydown="actions.onKeyDownNext"
				data-wp-bind--aria-disabled="context.isDisabledNext"
				data-wp-bind--aria-label="context.ariaLabelNext"
			>
				<svg
					class="wc-block-next-previous-buttons__icon wc-block-next-previous-buttons__icon--right"
					xmlns="http://www.w3.org/2000/svg"
					width="8"
					height="12"
					fill="none"
				>
					<path
						fill="currentColor"
						fillRule="evenodd"
						d="<?php echo is_rtl() ? esc_attr( $left_arrow_path ) : esc_attr( $right_arrow_path ); ?>"
						clipRule="evenodd"
					/>
				</svg>
			</button>
		</div>
		<?php
		$template = ob_get_clean();

		return $template;
	}
}
PK     \ k<?  ?    BlockTypes/ProductSKU.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Enums\ProductType;

/**
 * ProductSKU class.
 */
class ProductSKU extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-sku';

	/**
	 * API version name.
	 *
	 * @var string
	 */
	protected $api_version = '3';

	/**
	 * Overwrite parent method to prevent script registration.
	 *
	 * It is necessary to register and enqueues assets during the render
	 * phase because we want to load assets only if the block has the content.
	 */
	protected function register_block_type_assets() {
		return null;
	}

	/**
	 * Register the context.
	 */
	protected function get_block_type_uses_context() {
		return [ 'query', 'queryId', 'postId' ];
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! empty( $content ) ) {
			parent::register_block_type_assets();
			$this->register_chunk_translations( [ $this->block_name ] );
			return $content;
		}

		$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
		$product = wc_get_product( $post_id );

		if ( ! $product ) {
			return '';
		}

		$product_sku = $product->get_sku();

		if ( ! $product_sku ) {
			return '';
		}

		$is_interactive = $product->is_type( ProductType::VARIABLE );

		if ( $is_interactive ) {
			$variations                = $product->get_available_variations( 'objects' );
			$formatted_variations_data = array();
			foreach ( $variations as $variation ) {
				$formatted_variations_data[ $variation->get_id() ] = array(
					'sku' => $variation->get_sku(),
				);
			}

			wp_interactivity_config(
				'woocommerce',
				array(
					'products' => array(
						$product->get_id() => array(
							'sku'        => $product_sku,
							'variations' => $formatted_variations_data,
						),
					),
				)
			);
			wp_enqueue_script_module( 'woocommerce/product-elements' );
		}

		$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );

		$prefix = isset( $attributes['prefix'] ) ? wp_kses_post( ( $attributes['prefix'] ) ) : __( 'SKU: ', 'woocommerce' );
		if ( ! empty( $prefix ) ) {
			$prefix = sprintf( '<span class="wp-block-post-terms__prefix">%s</span>', $prefix );
		}

		$suffix = isset( $attributes['suffix'] ) ? wp_kses_post( ( $attributes['suffix'] ) ) : '';
		if ( ! empty( $suffix ) ) {
			$suffix = sprintf( '<span class="wp-block-post-terms__suffix">%s</span>', $suffix );
		}

		$interactive_attributes = $is_interactive ? 'data-wp-interactive="woocommerce/product-elements" data-wp-text="state.productData.sku"' : '';

		return sprintf(
			'<div class="wc-block-components-product-sku wc-block-grid__product-sku wp-block-woocommerce-product-sku product_meta wp-block-post-terms %1$s" style="%2$s">
				%3$s
				<span class="sku" %4$s>%5$s</span>
				%6$s
			</div>',
			esc_attr( $styles_and_classes['classes'] ),
			esc_attr( $styles_and_classes['styles'] ?? '' ),
			$prefix,
			$interactive_attributes,
			$product_sku,
			$suffix
		);
	}
}
PK     \8h    ,  BlockTypes/CartOrderSummarySubtotalBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartOrderSummarySubtotalBlock class.
 */
class CartOrderSummarySubtotalBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-order-summary-subtotal-block';
}
PK     \%	5    $  BlockTypes/ProductSpecifications.phpnu [        <?php
declare(strict_types=1);
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Enums\ProductType;

/**
 * ProductSpecifications class.
 */
class ProductSpecifications extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-specifications';

	/**
	 * 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;
	}

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 *
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! isset( $block->context['postId'] ) ) {
			return '';
		}

		$product = wc_get_product( $block->context['postId'] );

		if ( ! $product ) {
			return '';
		}

		$product_data = array();

		// Get display settings with defaults.
		$show_weight     = isset( $attributes['showWeight'] ) ? $attributes['showWeight'] : true;
		$show_dimensions = isset( $attributes['showDimensions'] ) ? $attributes['showDimensions'] : true;
		$show_attributes = isset( $attributes['showAttributes'] ) ? $attributes['showAttributes'] : true;

		if ( $show_weight && $product->has_weight() ) {
			$product_data['weight'] = array(
				'label' => __( 'Weight', 'woocommerce' ),
				'value' => wc_format_weight( $product->get_weight() ),
			);
		}

		if ( $show_dimensions && $product->has_dimensions() ) {
			$product_data['dimensions'] = array(
				'label' => __( 'Dimensions', 'woocommerce' ),
				'value' => wc_format_dimensions( $product->get_dimensions( false ) ),
			);
		}

		$is_interactive = $product->is_type( ProductType::VARIABLE );

		if ( $is_interactive ) {
			$variations                = $product->get_available_variations( 'objects' );
			$formatted_variations_data = array();
			foreach ( $variations as $variation ) {
				$formatted_variations_data[ $variation->get_id() ] = array(
					'weight'     => wc_format_weight( $variation->get_weight() ),
					'dimensions' => html_entity_decode( wc_format_dimensions( $variation->get_dimensions( false ) ), ENT_QUOTES, get_bloginfo( 'charset' ) ),
				);
			}

			wp_interactivity_config(
				'woocommerce',
				array(
					'products' => array(
						$product->get_id() => array(
							'weight'     => $product_data['weight']['value'] ?? '',
							'dimensions' => html_entity_decode( $product_data['dimensions']['value'] ?? '', ENT_QUOTES, get_bloginfo( 'charset' ) ),
							'variations' => $formatted_variations_data,
						),
					),
				)
			);
			wp_enqueue_script_module( 'woocommerce/product-elements' );
		}

		if ( $show_attributes ) {
			foreach ( $product->get_attributes() as $attribute ) {
				$values = array();

				if ( $attribute->is_taxonomy() ) {
					$attribute_taxonomy = $attribute->get_taxonomy_object();
					$attribute_values   = wc_get_product_terms( $product->get_id(), $attribute->get_name(), array( 'fields' => 'all' ) );

					foreach ( $attribute_values as $attribute_value ) {
						$value_name = esc_html( $attribute_value->name );

						if ( $attribute_taxonomy->attribute_public ) {
							$values[] = '<a href="' . esc_url( get_term_link( $attribute_value->term_id, $attribute->get_name() ) ) . '" rel="tag">' . $value_name . '</a>';
						} else {
							$values[] = $value_name;
						}
					}
				} else {
					$values = $attribute->get_options();

					foreach ( $values as &$value ) {
						$value = make_clickable( esc_html( $value ) );
					}
				}

				$product_data[ 'attribute_' . sanitize_title_with_dashes( $attribute->get_name() ) ] = array(
					'label' => wc_attribute_label( $attribute->get_name() ),
					'value' => wpautop( wptexturize( implode( ', ', $values ) ) ),
				);
			}
		}

		if ( empty( $product_data ) ) {
			return '';
		}

		ob_start();

		$wrapper_attributes = get_block_wrapper_attributes(
			array( 'class' => 'wp-block-table' )
		);
		?>
		<figure <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<table>
				<thead class="screen-reader-text">
					<tr>
						<th><?php esc_html_e( 'Attributes', 'woocommerce' ); ?></th>
						<th><?php esc_html_e( 'Value', 'woocommerce' ); ?></th>
					</tr>
				</thead>
				<tbody>
					<?php foreach ( $product_data as $product_attribute_key => $product_attribute ) : ?>
						<tr class="wp-block-product-specifications-item wp-block-product-specifications-item-<?php echo esc_attr( $product_attribute_key ); ?>">
							<th scope="row" class="wp-block-product-specifications-item__label">
								<?php echo wp_kses_post( $product_attribute['label'] ); ?>
							</th>
							<?php if ( $is_interactive && in_array( $product_attribute_key, array( 'weight', 'dimensions' ), true ) ) : ?>
								<td class="wp-block-product-specifications-item__value" data-wp-interactive="woocommerce/product-elements" data-wp-text="state.productData.<?php echo esc_attr( $product_attribute_key ); ?>">
									<?php echo wp_kses_post( $product_attribute['value'] ); ?>
								</td>
							<?php else : ?>	
								<td class="wp-block-product-specifications-item__value">
									<?php echo wp_kses_post( $product_attribute['value'] ); ?>
								</td>
							<?php endif; ?>
						</tr>
					<?php endforeach; ?>
				</tbody>
			</table>
		</figure>
		<?php

		return ob_get_clean();
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string[]
	 */
	protected function get_block_type_style() {
		$deps = parent::get_block_type_style();

		if ( ! is_array( $deps ) ) {
			return array( 'wp-block-table' );
		}

		return array_merge( array( 'wp-block-table' ), $deps );
	}
}
PK     \H    *  BlockTypes/CheckoutShippingMethodBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutShippingMethodBlock class.
 */
class CheckoutShippingMethodBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-shipping-method-block';
}
PK     \        BlockTypes/CartItemsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartItemsBlock class.
 */
class CartItemsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-items-block';
}
PK     \s    6  BlockTypes/AddToCartWithOptions/GroupedProductItem.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
use Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions\Utils as AddToCartWithOptionsUtils;
use WP_Block;

/**
 * Block type for grouped product selector item in add to cart with options.
 * It's responsible to render each child product in a form of a list item.
 */
class GroupedProductItem extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'add-to-cart-with-options-grouped-product-item';

	/**
	 * Modifies the block context for product price blocks when inside the Grouped Product Selector block.
	 *
	 * @param array $context The block context.
	 * @param array $block   The parsed block.
	 * @return array Modified block context.
	 */
	public function set_is_descendant_of_grouped_product_selector_context( $context, $block ) {
		if (
			'woocommerce/product-price' === $block['blockName'] ||
			'woocommerce/product-stock-indicator' === $block['blockName']
		) {
			$context['isDescendantOfGroupedProductSelector'] = true;
		}
		return $context;
	}

	/**
	 * Get product row HTML.
	 *
	 * @param string   $product_id Product ID.
	 * @param array    $attributes Block attributes.
	 * @param WP_Block $block The Block.
	 * @return string Row HTML
	 */
	private function get_product_row( $product_id, $attributes, $block ): string {
		global $post, $product;
		$previous_post    = $post;
		$previous_product = $product;

		// Since this template uses the core/post-title block to show the product name
		// a temporally replacement of the global post is needed. This is reverted back
		// to its initial post value that is stored in the $previous_post variable.
		$post    = get_post( $product_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
		$product = wc_get_product( $product_id );

		add_filter( 'render_block_context', array( $this, 'set_is_descendant_of_grouped_product_selector_context' ), 10, 2 );

		// Render the inner blocks of the Post Template block with `dynamic` set to `false` to prevent calling
		// `render_callback` and ensure that no wrapper markup is included.
		$block_content = AddToCartWithOptionsUtils::render_block_with_context(
			$block,
			array(
				'postType' => 'product',
				'postId'   => $post->ID,
			),
		);

		remove_filter( 'render_block_context', array( $this, 'set_is_descendant_of_grouped_product_selector_context' ) );

		$post    = $previous_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
		$product = $previous_product;
		return $block_content;
	}

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ): string {
		global $product;

		if ( ! $product instanceof \WC_Product_Grouped ) {
			return '';
		}

		$content = '';

		// No need to prime post caches here, children are already cached at this point.
		$children = array_filter( array_map( 'wc_get_product', $product->get_children() ), 'wc_products_array_filter_visible_grouped' );

		foreach ( $children as $child ) {
			$content .= $this->get_product_row( $child->get_id(), $attributes, $block );
		}

		return $content;
	}
}
PK     \#ᣃ    ;  BlockTypes/AddToCartWithOptions/GroupedProductItemLabel.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
use Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions\Utils as AddToCartWithOptionsUtils;
use WP_Block;

/**
 * Block type for the label of grouped product selector items in Add to Cart + Options.
 * It's responsible to render the label for each child product.
 */
class GroupedProductItemLabel extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'add-to-cart-with-options-grouped-product-item-label';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ): string {
		$product = AddToCartWithOptionsUtils::get_product_from_context( $block, $GLOBALS['product'] );
		$markup  = '';

		if ( $product ) {
			$wrapper_attributes = get_block_wrapper_attributes();
			$title              = $product->get_name();

			if ( ! $product->is_purchasable() || $product->has_options() || ! $product->is_in_stock() ) {
				$markup = sprintf(
					'<div %1$s>%2$s</div>',
					$wrapper_attributes,
					esc_html( $title )
				);
			} else {
				// Checkbox.
				$markup = sprintf(
					'<label %1$s for="%2$s">%3$s</label>',
					$wrapper_attributes,
					esc_attr( 'quantity_' . $product->get_id() ),
					esc_html( $title )
				);
			}
		}

		return $markup;
	}
}
PK     \+ %   %  E  BlockTypes/AddToCartWithOptions/VariationSelectorAttributeOptions.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * Block type for variation selector attribute options in add to cart with options.
 * It's responsible to render the attribute options.
 */
class VariationSelectorAttributeOptions extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'add-to-cart-with-options-variation-selector-attribute-options';

	/**
	 * Render the block.
	 *
	 * @param array     $attributes Block attributes.
	 * @param string    $content Block content.
	 * @param \WP_Block $block Block instance.
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ): string {
		if (
			! isset(
				$block->context['woocommerce/attributeName'],
				$block->context['woocommerce/attributeId'],
				$block->context['woocommerce/attributeTerms']
			)
		) {
			return '';
		}

		$attribute_slug = wc_variation_attribute_name( $block->context['woocommerce/attributeName'] );

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );

		$option_style = array_key_exists( 'optionStyle', $attributes ) ? $attributes['optionStyle'] : null;

		// During the beta period, `optionStyle` was called `style`, so we check
		// `style` for backwards compatibility.
		if ( ! $option_style && array_key_exists( 'style', $attributes ) && 'dropdown' === $attributes['style'] ) {
			$option_style = 'dropdown';
		}

		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class' => $classes_and_styles['classes'],
				'style' => $classes_and_styles['styles'],
			)
		);

		if ( 'dropdown' === $option_style ) {
			$content = $this->render_dropdown( $attributes, $content, $block );
		} else {
			$content = $this->render_pills( $attributes, $content, $block );
		}

		return sprintf(
			'<div %s>%s</div>',
			$wrapper_attributes,
			$content
		);
	}

	/**
	 * Get the normalized version of the attributes.
	 *
	 * @param array $attributes         The element's attributes.
	 * @param array $default_attributes The element's default attributes.
	 * @return string The HTML element's attributes.
	 */
	public static function get_normalized_attributes( $attributes, $default_attributes = array() ) {
		$normalized_attributes = array();

		$merged_attributes = array_merge( $default_attributes, $attributes );

		foreach ( $merged_attributes as $key => $value ) {
			if ( is_null( $value ) ) {
				continue;
			}
			if ( is_array( $value ) || is_object( $value ) ) {
				$value = wp_json_encode(
					$value,
					JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
				);
			}
			$normalized_attributes[] = sprintf( '%s="%s"', esc_attr( $key ), esc_attr( $value ) );
		}

		return implode( ' ', $normalized_attributes );
	}

	/**
	 * Get the default selected attribute.
	 *
	 * @param string $attribute_slug The attribute's slug.
	 * @param array  $attribute_terms The attribute's terms.
	 * @return string|null The default selected attribute.
	 */
	protected function get_default_selected_attribute( $attribute_slug, $attribute_terms ) {
		if ( isset( $_GET[ $attribute_slug ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$raw = wp_unslash( $_GET[ $attribute_slug ] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			if ( is_string( $raw ) ) {
				$attribute_slug_from_request = sanitize_title( $raw );
				foreach ( $attribute_terms as $attribute_term ) {
					if ( sanitize_title( $attribute_term['value'] ) === $attribute_slug_from_request ) {
						return $attribute_term['value'];
					}
				}
			}
		} else {
			foreach ( $attribute_terms as $attribute_term ) {
				if ( $attribute_term['isSelected'] ) {
					return $attribute_term['value'];
				}
			}
		}

		return null;
	}

	/**
	 * Render the attribute options as pills.
	 *
	 * @param array     $attributes Block attributes.
	 * @param string    $content Block content.
	 * @param \WP_Block $block Block instance.
	 * @return string The pills.
	 */
	protected function render_pills( $attributes, $content, $block ) {
		$attribute_id               = $block->context['woocommerce/attributeId'];
		$attribute_slug             = wc_variation_attribute_name( $block->context['woocommerce/attributeName'] );
		$attribute_terms            = $block->context['woocommerce/attributeTerms'];
		$autoselect                 = $attributes['autoselect'] ?? false;
		$disabled_attributes_action = $attributes['disabledAttributesAction'] ?? 'disable';

		wp_interactivity_state(
			'woocommerce/add-to-cart-with-options',
			array(
				'isOptionSelected' =>
				function () {
					$context = wp_interactivity_get_context();

					return $context['option']['value'] === $context['selectedValue'];
				},
			)
		);

		$pills = '';
		foreach ( $attribute_terms as $attribute_term ) {
			$input = sprintf(
				'<input type="radio" %s/>',
				$this->get_normalized_attributes(
					array(
						'class'                  => 'wc-block-add-to-cart-with-options-variation-selector-attribute-options__pill-input',
						'name'                   => $attribute_slug,
						'value'                  => $attribute_term['value'],
						'data-wp-bind--checked'  => 'state.isOptionSelected',
						'data-wp-bind--disabled' => 'state.isOptionDisabled',
						'data-wp-bind--hidden'   => 'hide' === $disabled_attributes_action ? 'state.isOptionDisabled' : null,
						'data-wp-on--click'      => 'actions.handlePillClick',
						'data-wp-on--keydown'    => 'actions.handleKeyDown',
						'data-wp-context'        => array(
							'option' => $attribute_term,
						),
					),
				)
			);

			$pills .= '<label class="wc-block-add-to-cart-with-options-variation-selector-attribute-options__pill">' . $input . esc_html( $attribute_term['label'] ) . '</label>';
		}

		return sprintf(
			'<div %s>%s</div>',
			$this->get_normalized_attributes(
				array(
					'class'           => 'wc-block-add-to-cart-with-options-variation-selector-attribute-options__pills',
					'role'            => 'radiogroup',
					'id'              => $attribute_id,
					'aria-labelledby' => $attribute_id . '_label',
					'data-wp-context' => array(
						'name'          => wc_attribute_label( $block->context['woocommerce/attributeName'] ),
						'options'       => $attribute_terms,
						'selectedValue' => $this->get_default_selected_attribute( $attribute_slug, $attribute_terms ),
						'focused'       => '',
						'autoselect'    => $autoselect,
					),
					'data-wp-init'    => 'callbacks.setDefaultSelectedAttribute',
				),
			),
			$pills,
		);
	}

	/**
	 * Render the attribute options as a dropdown.
	 *
	 * @param array     $attributes Block attributes.
	 * @param string    $content Block content.
	 * @param \WP_Block $block Block instance.
	 * @return string The dropdown.
	 */
	protected function render_dropdown( $attributes, $content, $block ) {
		$attribute_id    = $block->context['woocommerce/attributeId'];
		$attribute_slug  = wc_variation_attribute_name( $block->context['woocommerce/attributeName'] );
		$attribute_terms = $block->context['woocommerce/attributeTerms'];
		$default_option  = array(
			'label'      => esc_html__( 'Choose an option', 'woocommerce' ),
			'value'      => '',
			'isSelected' => false,
		);

		$attribute_terms = array_merge(
			array( $default_option ),
			$attribute_terms
		);

		$selected_attribute         = $this->get_default_selected_attribute( $attribute_slug, $attribute_terms );
		$autoselect                 = $attributes['autoselect'] ?? false;
		$disabled_attributes_action = $attributes['disabledAttributesAction'] ?? 'disable';

		$options = '';
		foreach ( $attribute_terms as $attribute_term ) {
			$option_attributes = array(
				'value'                  => $attribute_term['value'],
				'data-wp-bind--selected' => 'state.isOptionSelected',
				'data-wp-bind--disabled' => 'state.isOptionDisabled',
				'data-wp-bind--hidden'   => 'hide' === $disabled_attributes_action ? 'state.isOptionDisabled' : null,
				'data-wp-context'        => array(
					'option' => $attribute_term,
				),
			);

			if ( $attribute_term['value'] === $selected_attribute ) {
				$option_attributes['selected'] = 'selected';
			}

			$options .= sprintf(
				'<option %s>%s</option>',
				$this->get_normalized_attributes(
					$option_attributes
				),
				esc_html( $attribute_term['label'] )
			);
		}

		return sprintf(
			'<select %s>%s</select>',
			$this->get_normalized_attributes(
				array(
					'class'              => 'wc-block-add-to-cart-with-options-variation-selector-attribute-options__dropdown',
					'id'                 => $attribute_id,
					'data-wp-context'    => array(
						'name'          => wc_attribute_label( $block->context['woocommerce/attributeName'] ),
						'options'       => $attribute_terms,
						'selectedValue' => $selected_attribute,
						'autoselect'    => $autoselect,
					),
					'data-wp-init'       => 'callbacks.setDefaultSelectedAttribute',
					'data-wp-on--change' => 'actions.handleDropdownChange',
					'name'               => $attribute_slug,
				),
			),
			$options,
		);
	}
}
PK     \qJ    B  BlockTypes/AddToCartWithOptions/VariationSelectorAttributeName.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * Block type for variation selector attribute name in add to cart with options.
 * It's responsible to render the attribute name.
 */
class VariationSelectorAttributeName extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'add-to-cart-with-options-variation-selector-attribute-name';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ): string {
		if (
			! isset(
				$block->context['woocommerce/attributeId'],
				$block->context['woocommerce/attributeName']
			)
		) {
			return '';
		}

		$attribute_id   = $block->context['woocommerce/attributeId'];
		$attribute_name = $block->context['woocommerce/attributeName'];

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );

		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class' => esc_attr( $classes_and_styles['classes'] ),
				'for'   => esc_attr( $attribute_id ),
				'id'    => esc_attr( $attribute_id . '_label' ),
				'style' => esc_attr( $classes_and_styles['styles'] ),
			)
		);

		$label_text = esc_html( wc_attribute_label( $attribute_name ) );

		return sprintf(
			'<label %s>%s</label>',
			$wrapper_attributes,
			$label_text
		);
	}
}
PK     \	    8  BlockTypes/AddToCartWithOptions/VariationDescription.phpnu [        <?php
declare(strict_types=1);
namespace Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;

/**
 * VariationDescription class.
 */
class VariationDescription extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'add-to-cart-with-options-variation-description';


	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 *
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ) {
		global $product;

		if ( ! $product instanceof \WC_Product_Variable ) {
			return '';
		}

		$variations                = $product->get_available_variations( 'objects' );
		$formatted_variations_data = array();
		foreach ( $variations as $variation ) {
			$variation_description = $variation->get_description();
			if ( is_string( $variation_description ) && ! empty( $variation_description ) ) {
				$formatted_variations_data[ $variation->get_id() ] = array(
					'variation_description' => wp_kses_post( wc_format_content( $variation_description ) ),
				);
			}
		}

		wp_interactivity_config(
			'woocommerce',
			array(
				'products' => array(
					$product->get_id() => array(
						'variations' => $formatted_variations_data,
					),
				),
			)
		);

		$context_directive = wp_interactivity_data_wp_context(
			array(
				'productElementKey' => 'variation_description',
			)
		);

		$wrapper_attributes = array(
			'data-wp-interactive'  => 'woocommerce/product-elements',
			'data-wp-bind--hidden' => '!state.productData.variation_description',
			'aria-live'            => 'polite',
			'aria-atomic'          => 'true',
		);

		return '<div ' . $context_directive . ' ' . get_block_wrapper_attributes( $wrapper_attributes ) . ' data-wp-watch="callbacks.updateValue"></div>';
	}
}
PK     \N;x  x  5  BlockTypes/AddToCartWithOptions/VariationSelector.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
use Automattic\WooCommerce\Enums\ProductType;

/**
 * Block type for variation selector in add to cart with options.
 */
class VariationSelector extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'add-to-cart-with-options-variation-selector';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ): string {
		global $product;

		if ( $product instanceof \WC_Product && $product->is_type( ProductType::VARIABLE ) && ! Utils::is_not_purchasable_product( $product ) ) {
			$p = new \WP_HTML_Tag_Processor( $content );

			if ( $p->next_tag( array( 'class_name' => 'wp-block-woocommerce-add-to-cart-with-options-variation-selector' ) ) ) {
				$p->set_attribute( 'data-wp-watch', 'callbacks.setSelectedVariationId' );
				$p->set_attribute( 'data-wp-watch--validate', 'callbacks.validateVariation' );
			}

			return $p->get_updated_html();
		}

		return '';
	}
}
PK     \    >  BlockTypes/AddToCartWithOptions/GroupedProductItemSelector.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
use Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions\Utils as AddToCartWithOptionsUtils;
use WP_Block;

/**
 * Block type for the CTA of grouped product selector items in add to cart with options.
 * It's responsible to render the CTA for each child product, that might be a button,
 * a checkbox, or a link.
 */
class GroupedProductItemSelector extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'add-to-cart-with-options-grouped-product-item-selector';

	/**
	 * Set the quantity input type to number.
	 *
	 * @return string The quantity input type.
	 */
	public function set_quantity_input_type() {
		return 'number';
	}

	/**
	 * Gets the quantity selector markup for a product.
	 *
	 * @param \WC_Product $product The product object.
	 * @return string The HTML markup for the quantity selector.
	 */
	private function get_quantity_selector_markup( $product ) {
		ob_start();

		$min_value = $product->get_min_purchase_quantity();
		$max_value = $product->get_max_purchase_quantity();

		if ( $min_value === $max_value && $min_value > 0 ) {
			add_filter( 'woocommerce_quantity_input_type', array( $this, 'set_quantity_input_type' ) );
		}

		woocommerce_quantity_input(
			array(
				'input_name'  => 'quantity[' . $product->get_id() . ']',
				'input_id'    => 'quantity_' . $product->get_id(),
				'input_value' => isset( $_POST['quantity'][ $product->get_id() ] ) ? wc_stock_amount( wc_clean( wp_unslash( $_POST['quantity'][ $product->get_id() ] ) ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Missing
				'min_value'   => 0,
				'max_value'   => $max_value,
				/**
				 * Filter the placeholder value allowed for the product.
				 *
				 * @since 3.10.0
				 * @param int        $max_value Maximum quantity value.
				 * @param WC_Product $product   Product object.
				 */
				'placeholder' => apply_filters( 'woocommerce_quantity_input_placeholder', 0, $product ),
			)
		);

		if ( $min_value === $max_value && $min_value > 0 ) {
			remove_filter( 'woocommerce_quantity_input_type', array( $this, 'set_quantity_input_type' ) );
		}

		$quantity_html = ob_get_clean();

		// Remove the label because we are rendering one as a separate block via GroupedProductItemLabel.
		$quantity_html = $this->remove_quantity_label( $quantity_html );

		// Modify the quantity input to add stepper buttons.
		$product_name = $product->get_name();

		$quantity_html = AddToCartWithOptionsUtils::add_quantity_steppers( $quantity_html, $product_name );
		$quantity_html = AddToCartWithOptionsUtils::add_quantity_stepper_classes( $quantity_html );

		$context = array(
			'productId' => $product->get_id(),
			'allowZero' => true, // The item is optional in grouped products.
		);

		// Add interactive data attribute for the stepper functionality.
		$quantity_html = AddToCartWithOptionsUtils::make_quantity_input_interactive( $quantity_html, array(), array(), $context );

		return $quantity_html;
	}

	/**
	 * Removes the label from quantity input HTML.
	 *
	 * @param string $quantity_html The quantity input HTML.
	 * @return string The quantity input HTML without the label.
	 */
	private function remove_quantity_label( $quantity_html ) {
		// Remove the label and aria-label from the quantity input.
		$quantity_html = preg_replace( '/<label[^>]*>.*?<\/label>/s', '', $quantity_html );
		return preg_replace( '/\s*aria-label="[^"]*"/', '', $quantity_html );
	}

	/**
	 * Gets the add to cart button markup for a product.
	 *
	 * @param \WC_Product $product_to_render The product object.
	 * @return string The HTML markup for the add to cart button.
	 */
	private function get_button_markup( $product_to_render ) {
		ob_start();
		woocommerce_template_loop_add_to_cart();
		$button_html = ob_get_clean();

		return $button_html;
	}

	/**
	 * Gets the checkbox markup for a product.
	 *
	 * @param \WC_Product $product The product object.
	 * @return string The HTML markup for the checkbox input and label.
	 */
	private function get_checkbox_markup( $product ) {
		if ( $product->is_on_sale() ) {
			$label = sprintf(
				/* translators: %1$s: Product name. %2$s: Sale price. %3$s: Regular price */
				esc_html__( 'Buy one of %1$s on sale for %2$s, original price was %3$s', 'woocommerce' ),
				esc_html( $product->get_name() ),
				esc_html( wp_strip_all_tags( wc_price( $product->get_price() ) ) ),
				esc_html( wp_strip_all_tags( wc_price( $product->get_regular_price() ) ) )
			);
		} else {
			$label = sprintf(
				/* translators: %1$s: Product name. %2$s: Product price */
				esc_html__( 'Buy one of %1$s for %2$s', 'woocommerce' ),
				esc_html( $product->get_name() ),
				esc_html( wp_strip_all_tags( wc_price( $product->get_price() ) ) )
			);
		}

		$context_attribute = wp_interactivity_data_wp_context( array( 'productId' => $product->get_id() ) );
		return '<input type="checkbox" name="' . esc_attr( 'quantity[' . $product->get_id() . ']' ) . '" value="1" class="wc-grouped-product-add-to-cart-checkbox" id="' . esc_attr( 'quantity_' . $product->get_id() ) . '" data-wp-interactive="woocommerce/add-to-cart-with-options-quantity-selector" data-wp-on--change="actions.handleQuantityCheckboxChange" ' . $context_attribute . ' aria-label="' . esc_attr( $label ) . '"/>';
	}

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ): string {
		global $product;
		$previous_product = $product;

		$product = AddToCartWithOptionsUtils::get_product_from_context( $block, $previous_product );
		$markup  = '';

		if ( $product ) {
			$is_interactive = false;
			if ( ! $product->is_purchasable() || $product->has_options() || ! $product->is_in_stock() ) {
				$markup = $this->get_button_markup( $product );
			} elseif ( $product->is_sold_individually() ) {
				$is_interactive = true;
				$markup         = $this->get_checkbox_markup( $product );
			} else {
				$is_interactive = true;
				$markup         = $this->get_quantity_selector_markup( $product );
			}

			if ( $is_interactive ) {
				wp_enqueue_script_module( 'woocommerce/add-to-cart-with-options-quantity-selector' );
			}

			if ( $markup ) {
				$markup = '<div class="wp-block-add-to-cart-with-options-grouped-product-item-selector wc-block-add-to-cart-with-options-grouped-product-item-selector">' . $markup . '</div>';
			}
		}

		$product = $previous_product;

		return $markup;
	}
}
PK     \"!  !  )  BlockTypes/AddToCartWithOptions/Utils.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions;

use Automattic\WooCommerce\Enums\ProductType;
use WP_Block;

/**
 * Utility methods used for the Add to Cart + Options block.
 * {@internal This class and its methods are not intended for public use.}
 */
class Utils {
	/**
	 * Check if the HTML content has a visible quantity input.
	 *
	 * @param string $html_content The HTML content.
	 * @return bool True if the HTML content has a visible input, false otherwise.
	 */
	public static function has_visible_quantity_input( $html_content ) {
		$processor = new \WP_HTML_Tag_Processor( $html_content );

		while ( $processor->next_tag() ) {
			if (
				$processor->get_tag() === 'INPUT' &&
				$processor->get_attribute( 'name' ) === 'quantity' &&
				$processor->get_attribute( 'type' ) !== 'hidden'
			) {
				return true;
			}
		}
		return false;
	}
	/**
	 * Add increment and decrement buttons to the quantity input field.
	 *
	 * @param string $quantity_html Quantity input HTML.
	 * @param string $product_name Product name.
	 * @return string Quantity input HTML with increment and decrement buttons.
	 */
	public static function add_quantity_steppers( $quantity_html, $product_name ) {
		// Regex pattern to match the <input> element with id starting with 'quantity_'.
		$pattern = '/(<input[^>]*id="quantity_[^"]*"[^>]*\/>)/';
		// Replacement string to add button AFTER the matched <input> element.
		/* translators: %s refers to the item name in the cart. */
		$minus_button = '$1<button aria-label="' . esc_attr( sprintf( __( 'Reduce quantity of %s', 'woocommerce' ), $product_name ) ) . '" type="button" data-wp-on--click="actions.decreaseQuantity" data-wp-bind--disabled="!state.allowsDecrease" class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">−</button>';
		// Replacement string to add button AFTER the matched <input> element.
		/* translators: %s refers to the item name in the cart. */
		$plus_button = '$1<button aria-label="' . esc_attr( sprintf( __( 'Increase quantity of %s', 'woocommerce' ), $product_name ) ) . '" type="button" data-wp-on--click="actions.increaseQuantity" data-wp-bind--disabled="!state.allowsIncrease" class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button>';
		$new_html    = preg_replace( $pattern, $plus_button, $quantity_html );
		$new_html    = preg_replace( $pattern, $minus_button, $new_html );
		return $new_html;
	}

	/**
	 * Add classes to the Quantity Selector needed for the stepper style.
	 *
	 * @param string $quantity_html The Quantity Selector HTML.
	 *
	 * @return string The Quantity Selector HTML with classes added.
	 */
	public static function add_quantity_stepper_classes( $quantity_html ) {
		$processor = new \WP_HTML_Tag_Processor( $quantity_html );

		// Add classes to the form.
		while ( $processor->next_tag( array( 'class_name' => 'quantity' ) ) ) {
			$processor->add_class( 'wc-block-components-quantity-selector' );
		}

		while ( $processor->next_tag( array( 'class_name' => 'input-text' ) ) ) {
			$processor->add_class( 'wc-block-components-quantity-selector__input' );
		}

		return $processor->get_updated_html();
	}

	/**
	 * Make the quantity input interactive by wrapping it with the necessary data attribute and adding a blur event listener.
	 *
	 * @param string $quantity_html The quantity HTML.
	 * @param array  $wrapper_attributes Optional wrapper attributes.
	 * @param array  $input_attributes Optional input attributes.
	 * @param array  $context {
	 *     Optional context for quantity input.
	 *     @type int  $productId  Product ID for context-specific behavior.
	 *     @type bool $allowZero  Whether to allow zero quantity.
	 * }
	 *
	 * @return string The quantity HTML with interactive wrapper.
	 */
	public static function make_quantity_input_interactive( $quantity_html, $wrapper_attributes = array(), $input_attributes = array(), $context = array() ) {
		$processor = new \WP_HTML_Tag_Processor( $quantity_html );
		global $product;

		if (
			$processor->next_tag( 'input' ) &&
			$processor->get_attribute( 'type' ) === 'number' &&
			strpos( $processor->get_attribute( 'name' ), 'quantity' ) !== false
		) {
			$default_quantity = $product instanceof \WC_Product ? $product->get_min_purchase_quantity() : 1;
			$input_quantity   = isset( $context['allowZero'] ) && true === $context['allowZero'] ? 0 : $default_quantity;

			wp_interactivity_state(
				'woocommerce/add-to-cart-with-options-quantity-selector',
				array(
					'inputQuantity' => $input_quantity,
				)
			);

			$processor->set_attribute( 'data-wp-on--blur', 'actions.handleQuantityBlur' );
			$processor->set_attribute( 'data-wp-bind--value', 'state.inputQuantity' );
			foreach ( $input_attributes as $attribute => $value ) {
				$processor->set_attribute( $attribute, $value );
			}
		}

		$quantity_html = $processor->get_updated_html();

		$wrapper_attributes = array_merge(
			array(
				'data-wp-interactive' => 'woocommerce/add-to-cart-with-options-quantity-selector',
				'data-wp-init'        => 'callbacks.storeInputElementRef',
			),
			$wrapper_attributes
		);

		$context_attribute = wp_interactivity_data_wp_context(
			wp_parse_args(
				$context,
				array(
					'productId' => $product instanceof \WC_Product ? $product->get_id() : 0,
				)
			)
		);

		return sprintf(
			'<div %1$s %2$s>%3$s</div>',
			get_block_wrapper_attributes( $wrapper_attributes ),
			$context_attribute,
			$quantity_html
		);
	}

	/**
	 * Get product from block context.
	 *
	 * @param \WP_Block        $block The block instance.
	 * @param \WC_Product|null $previous_product The previous product (usually from global scope).
	 * @return \WC_Product|null The product instance or null if not found.
	 */
	public static function get_product_from_context( $block, $previous_product ) {
		$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
		$product = null;

		if ( ! empty( $post_id ) ) {
			$product = wc_get_product( $post_id );
		}

		if ( ! $product instanceof \WC_Product && $previous_product instanceof \WC_Product ) {
			$product = $previous_product;
		}

		return $product instanceof \WC_Product ? $product : null;
	}

	/**
	 * Check if a product is not purchasable or not in stock.
	 *
	 * @param \WC_Product $product The product to check.
	 * @return bool True if the product is not purchasable or not in stock.
	 */
	public static function is_not_purchasable_product( $product ) {
		if ( $product->is_type( ProductType::SIMPLE ) ) {
			return ! $product->is_in_stock() || ! $product->is_purchasable();
		} elseif ( $product->is_type( ProductType::VARIABLE ) ) {
			return ! $product->is_in_stock() || ! $product->has_purchasable_variations();
		}

		return false;
	}

	/**
	 * Renders a new block with custom context
	 *
	 * @param WP_Block $block The block instance.
	 * @param array    $context The context for the new block.
	 * @return string Rendered block content
	 */
	public static function render_block_with_context( $block, $context ) {
		// Get an instance of the current block.
		$block_instance = $block->parsed_block;

		// Create new block with custom context.
		$new_block = new WP_Block(
			$block_instance,
			$context
		);

		// Render with dynamic set to false to prevent calling render_callback.
		return $new_block->render( array( 'dynamic' => false ) );
	}

	/**
	 * Check if min and max purchase quantity are the same for a product.
	 *
	 * @param \WC_Product $product The product to check.
	 * @return bool True if min and max purchase quantity are the same, false otherwise.
	 */
	public static function is_min_max_quantity_same( $product ) {
		$min_purchase_quantity = $product->get_min_purchase_quantity();
		$max_purchase_quantity = $product->get_max_purchase_quantity();
		return $min_purchase_quantity === $max_purchase_quantity;
	}

	/**
	 * Get the quantity constraints for a product.
	 *
	 * @param \WC_Product $product The product to get the quantity constraints for.
	 * @return array The quantity constraints.
	 */
	public static function get_product_quantity_constraints( $product ) {
		$min          = is_numeric( $product->get_min_purchase_quantity() ) ? $product->get_min_purchase_quantity() : 1;
		$max_quantity = $product->get_max_purchase_quantity();
		$max          = is_numeric( $max_quantity ) && -1 !== $max_quantity ? $max_quantity : null;
		$step         = is_numeric( $product->get_purchase_quantity_step() ) ? $product->get_purchase_quantity_step() : 1;

		return array(
			'min'  => $min,
			'max'  => $max,
			'step' => $step,
		);
	}
}
PK     \g;    :  BlockTypes/AddToCartWithOptions/GroupedProductSelector.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
use Automattic\WooCommerce\Enums\ProductType;

/**
 * Block type for grouped product selector in add to cart with options.
 */
class GroupedProductSelector extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'add-to-cart-with-options-grouped-product-selector';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ): string {
		global $product;

		if ( $product instanceof \WC_Product && $product->is_type( ProductType::GROUPED ) ) {

			$p = new \WP_HTML_Tag_Processor( $content );

			if ( $p->next_tag( array( 'class_name' => 'wp-block-woocommerce-add-to-cart-with-options-grouped-product-selector' ) ) ) {
				$p->set_attribute( 'data-wp-init', 'callbacks.validateQuantities' );
			}

			return $p->get_updated_html();

		}

		return '';
	}
}
PK     \Ob  Ob  8  BlockTypes/AddToCartWithOptions/AddToCartWithOptions.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Enums\ProductType;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;

/**
 * AddToCartWithOptions class.
 */
class AddToCartWithOptions extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'add-to-cart-with-options';

	/**
	 * Get the template part path for a product type.
	 *
	 * @param string $product_type The product type.
	 * @return string|bool The template part path if it exists, false otherwise.
	 */
	protected function get_template_part_path( $product_type ) {
		if ( in_array( $product_type, array( ProductType::SIMPLE, ProductType::EXTERNAL, ProductType::VARIABLE, ProductType::GROUPED ), true ) ) {
			return Package::get_path() . 'templates/' . BlockTemplateUtils::DIRECTORY_NAMES['TEMPLATE_PARTS'] . '/' . $product_type . '-product-add-to-cart-with-options.html';
		}

		/**
		 * Experimental filter for extensions to register a block template part
		 * for a product type.
		 *
		 * @since 9.9.0
		 * @param string|boolean $template_part_path The template part path if it exists
		 * @param string $product_type The product type
		 */
		return apply_filters( '__experimental_woocommerce_' . $product_type . '_add_to_cart_with_options_block_template_part', false, $product_type );
	}

	/**
	 * Product type string used to resolve add-to-cart template parts.
	 * It returns the product type in most cases, except for variations,
	 * which use the simple product template.
	 *
	 * @param \WC_Product $product Product instance.
	 * @return string Product type slug.
	 */
	private function get_product_type_for_add_to_cart_template( \WC_Product $product ): string {
		return ProductType::VARIATION === $product->get_type() ? ProductType::SIMPLE : $product->get_type();
	}

	/**
	 * Enqueue assets specific to this block.
	 * We enqueue frontend scripts only if the product type has a block template
	 * part (that's WC core product types and extensions that migrated to block
	 * templates).
	 *
	 * @param array     $attributes Block attributes.
	 * @param string    $content Block content.
	 * @param \WP_Block $block Block instance.
	 *
	 * @return void
	 */
	protected function enqueue_assets( $attributes, $content, $block ) {
		$product_id = ( is_object( $block ) && property_exists( $block, 'context' ) && is_array( $block->context ) && array_key_exists( 'postId', $block->context ) ) ? $block->context['postId'] : null;

		if ( isset( $product_id ) ) {
			$rendered_product = wc_get_product( $product_id );

			if ( $rendered_product instanceof \WC_Product ) {
				$product_type       = $this->get_product_type_for_add_to_cart_template( $rendered_product );
				$template_part_path = $this->get_template_part_path( $product_type );

				if ( is_string( $template_part_path ) && '' !== $template_part_path && file_exists( $template_part_path ) ) {
					wp_enqueue_script_module( 'woocommerce/add-to-cart-with-options' );
				}
			}
		}

		parent::enqueue_assets( $attributes, $content, $block );
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 * @return void
	 */
	protected function enqueue_data( array $attributes = array() ): void {
		parent::enqueue_data( $attributes );

		if ( is_admin() ) {
			$this->asset_data_registry->add( 'productTypes', wc_get_product_types() );
			$this->asset_data_registry->add( 'addToCartWithOptionsTemplatePartIds', $this->get_template_part_ids() );
		}
	}

	/**
	 * Get template part IDs for each product type.
	 *
	 * @return array Array of product types with their corresponding template part IDs.
	 */
	protected function get_template_part_ids() {
		$product_types = array_keys( wc_get_product_types() );
		$current_theme = wp_get_theme()->get_stylesheet();

		$template_part_ids = array();
		foreach ( $product_types as $product_type ) {
			$slug = $product_type . '-product-add-to-cart-with-options';

			// Check if theme template exists.
			$theme_has_template = BlockTemplateUtils::theme_has_template_part( $slug );

			if ( $theme_has_template ) {
				$template_part_ids[ $product_type ] = "{$current_theme}//{$slug}";
			} else {
				$template_part_ids[ $product_type ] = "woocommerce/woocommerce//{$slug}";
			}
		}

		return $template_part_ids;
	}

	/**
	 * Modifies the block context for product button blocks when inside the Add to Cart + Options block.
	 *
	 * @param array $context The block context.
	 * @param array $block   The parsed block.
	 * @return array Modified block context.
	 */
	public function set_is_descendant_of_add_to_cart_with_options_context( $context, $block ) {
		if ( 'woocommerce/product-button' === $block['blockName'] ) {
			$context['woocommerce/isDescendantOfAddToCartWithOptions'] = true;
		}

		return $context;
	}

	/**
	 * Check if HTML content has form elements.
	 *
	 * @param string $html_content The HTML content.
	 * @return bool True if the HTML content has form elements, false otherwise.
	 */
	public function has_form_elements( $html_content ) {
		$processor     = new \WP_HTML_Tag_Processor( $html_content );
		$form_elements = array( 'INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'FORM' );
		while ( $processor->next_tag() ) {
			if ( in_array( $processor->get_tag(), $form_elements, true ) ) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 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 ) {
		global $product;

		$product_id = ( is_object( $block ) && property_exists( $block, 'context' ) && is_array( $block->context ) && array_key_exists( 'postId', $block->context ) ) ? $block->context['postId'] : null;

		if ( ! isset( $product_id ) ) {
			return '';
		}

		$previous_product = $product;
		$product          = wc_get_product( $product_id );
		if ( ! $product instanceof \WC_Product ) {
			$product = $previous_product;

			return '';
		}

		$product_type = $this->get_product_type_for_add_to_cart_template( $product );

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );
		$classes            = implode(
			' ',
			array_filter(
				array(
					'wp-block-add-to-cart-with-options wc-block-add-to-cart-with-options',
					esc_attr( $classes_and_styles['classes'] ),
				)
			)
		);

		$template_part_path = $this->get_template_part_path( $product_type );

		if ( is_string( $template_part_path ) && '' !== $template_part_path && file_exists( $template_part_path ) ) {
			$slug                   = $product_type . '-product-add-to-cart-with-options';
			$template_part_contents = '';
			// Determine if we need to load the template part from the DB, the theme or WooCommerce in that order.
			$templates_from_db = BlockTemplateUtils::get_block_templates_from_db( array( $slug ), 'wp_template_part' );

			if ( is_countable( $templates_from_db ) && count( $templates_from_db ) > 0 ) {
				$template_slug_to_load = $templates_from_db[0]->theme;
			} else {
				$theme_has_template_part = BlockTemplateUtils::theme_has_template_part( $slug );
				$template_slug_to_load   = $theme_has_template_part ? get_stylesheet() : BlockTemplateUtils::PLUGIN_SLUG;
			}
			$template_part = get_block_template( $template_slug_to_load . '//' . $slug, 'wp_template_part' );

			if ( $template_part && ! empty( $template_part->content ) ) {
				$template_part_contents = $template_part->content;
			}

			if ( '' === $template_part_contents ) {
				$template_part_contents = file_get_contents( $template_part_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
			}

			$default_quantity = $product->get_min_purchase_quantity();

			$product_id = $product->get_id();

			wp_interactivity_state(
				'woocommerce/add-to-cart-with-options',
				array(
					'isFormValid' => function () use ( $product_id ) {
						$product = wc_get_product( $product_id );

						if ( $product instanceof \WC_Product && ( $product->is_type( ProductType::GROUPED ) || $product->has_options() ) ) {
							return false;
						}
						return true;
					},
				)
			);

			wp_interactivity_config(
				'woocommerce/add-to-cart-with-options',
				array(
					'errorMessages' => array(
						'invalidQuantities'                => esc_html__(
							'Please select a valid quantity to add to the cart.',
							'woocommerce'
						),
						'groupedProductAddToCartMissingItems' => esc_html__(
							'Please select some products to add to the cart.',
							'woocommerce'
						),
						'variableProductMissingAttributes' => esc_html__(
							'Please select product attributes before adding to cart.',
							'woocommerce'
						),
						'variableProductOutOfStock'        => sprintf(
							/* translators: %s: product name */
							esc_html__(
								'You cannot add &quot;%s&quot; to the cart because the product is out of stock.',
								'woocommerce'
							),
							$product->get_name()
						),
					),
				)
			);

			// Load product into the shared store with full REST API data.
			wc_interactivity_api_load_product(
				'I acknowledge that using experimental APIs means my theme or plugin will inevitably break in the next version of WooCommerce',
				$product->get_id()
			);

			$context = array(
				'quantity'         => array( $product->get_id() => $default_quantity ),
				'validationErrors' => array(),
			);

			if ( $product->is_type( ProductType::VARIABLE ) ) {
				$context['selectedAttributes'] = array();

				// Load all variations into the shared store with full REST API data.
				$variations = wc_interactivity_api_load_variations(
					'I acknowledge that using experimental APIs means my theme or plugin will inevitably break in the next version of WooCommerce',
					$product->get_id()
				);

				// Set up quantity context for each variation.
				// We intentionally set the default quantity to the product's min purchase quantity
				// instead of the variation's min purchase quantity. That's because we use the same
				// input for all variations, so we want quantities to be in sync.
				foreach ( array_keys( $variations ) as $variation_id ) {
					$context['quantity'][ $variation_id ] = $default_quantity;
				}
			} elseif ( $product->is_type( ProductType::VARIATION ) ) {
				$variation_attributes = $product->get_variation_attributes();
				$formatted_attributes = array_map(
					function ( $key, $value ) {
						return [
							'attribute' => $key,
							'value'     => $value,
						];
					},
					array_keys( $variation_attributes ),
					$variation_attributes
				);

				$context['selectedAttributes'] = $formatted_attributes;
			} elseif ( $product->is_type( ProductType::GROUPED ) ) {
				// Load purchasable child products into the shared store with full REST API data.
				$child_products = wc_interactivity_api_load_purchasable_child_products(
					'I acknowledge that using experimental APIs means my theme or plugin will inevitably break in the next version of WooCommerce',
					$product->get_id()
				);

				$context['groupedProductIds'] = array_keys( $child_products );

				// Add quantity context for purchasable child products.
				$context['quantity'] = array_fill_keys(
					$context['groupedProductIds'],
					0
				);

				// Set default quantity for each child product.
				foreach ( $child_products as $child_product_id => $child_product_data ) {
					$default_child_quantity = isset( $_POST['quantity'][ $child_product_id ] ) ? wc_stock_amount( wc_clean( wp_unslash( $_POST['quantity'][ $child_product_id ] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing

					$context['quantity'][ $child_product_id ] = $default_child_quantity;

					// Check for any "sold individually" products and set their default quantity to 0.
					if ( $child_product_data['sold_individually'] ) {
						$context['quantity'][ $child_product_id ] = 0;
					}
				}
			}

			$hooks_before = '';
			$hooks_after  = '';

			/**
			* Filter to disable the compatibility layer for the blockified templates.
			*
			* This hook allows to disable the compatibility layer for the blockified.
			*
			* @since 7.6.0
			* @param boolean $is_disabled_compatibility_layer Whether the compatibility layer should be disabled.
			*/
			$is_disabled_compatibility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false );

			if ( ! $is_disabled_compatibility_layer && ! Utils::is_not_purchasable_product( $product ) ) {
				ob_start();
				/**
				 * Hook: woocommerce_before_add_to_cart_form.
				 *
				 * @since 10.1.0
				 */
				do_action( 'woocommerce_before_add_to_cart_form' );

				if ( ProductType::SIMPLE === $product_type ) {
					/**
					 * Hook: woocommerce_before_add_to_cart_quantity.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_before_add_to_cart_quantity' );
					/**
					 * Hook: woocommerce_before_add_to_cart_button.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_before_add_to_cart_button' );
				} elseif ( ProductType::EXTERNAL === $product_type ) {
					/**
					 * Hook: woocommerce_before_add_to_cart_button.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_before_add_to_cart_button' );
				} elseif ( ProductType::GROUPED === $product_type ) {
					/**
					 * Hook: woocommerce_before_add_to_cart_button.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_before_add_to_cart_button' );
				} elseif ( ProductType::VARIABLE === $product_type ) {
					/**
					 * Hook: woocommerce_before_variations_form.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_before_variations_form' );
					/**
					 * Hook: woocommerce_after_variations_table.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_after_variations_table' );
					/**
					 * Hook: woocommerce_before_single_variation.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_before_single_variation' );

					// WooCommerce uses `woocommerce_single_variation` to render
					// some UI elements like the Add to Cart button for
					// variations. We need to remove them to avoid those UI
					// elements being duplicate with the blocks.
					// We later add these actions back to avoid affecting other
					// blocks or templates.
					remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation', 10 );
					remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation_add_to_cart_button', 20 );
					/**
					 * Hook: woocommerce_single_variation.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_single_variation' );
					if ( function_exists( 'woocommerce_single_variation' ) ) {
						add_action( 'woocommerce_single_variation', 'woocommerce_single_variation', 10 );
					}
					if ( function_exists( 'woocommerce_single_variation_add_to_cart_button' ) ) {
						add_action( 'woocommerce_single_variation', 'woocommerce_single_variation_add_to_cart_button', 20 );
					}
					/**
					 * Hook: woocommerce_before_add_to_cart_button.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_before_add_to_cart_button' );
					/**
					 * Hook: woocommerce_before_add_to_cart_quantity.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_before_add_to_cart_quantity' );
				}
				$hooks_before = ob_get_clean();

				ob_start();
				if ( ProductType::SIMPLE === $product_type ) {
					/**
					 * Hook: woocommerce_after_add_to_cart_quantity.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_after_add_to_cart_quantity' );
					/**
					 * Hook: woocommerce_after_add_to_cart_button.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_after_add_to_cart_button' );
				} elseif ( ProductType::EXTERNAL === $product_type ) {
					/**
					 * Hook: woocommerce_after_add_to_cart_button.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_after_add_to_cart_button' );
				} elseif ( ProductType::GROUPED === $product_type ) {
					/**
					 * Hook: woocommerce_after_add_to_cart_button.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_after_add_to_cart_button' );
				} elseif ( ProductType::VARIABLE === $product_type ) {
					/**
					 * Hook: woocommerce_after_add_to_cart_quantity.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_after_add_to_cart_quantity' );
					/**
					 * Hook: woocommerce_after_add_to_cart_button.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_after_add_to_cart_button' );
					/**
					 * Hook: woocommerce_after_single_variation.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_after_single_variation' );
					/**
					 * Hook: woocommerce_after_variations_form.
					 *
					 * @since 10.0.0
					 */
					do_action( 'woocommerce_after_variations_form' );
				}

				/**
				 * Hook: woocommerce_after_add_to_cart_form.
				 *
				 * @since 10.1.0
				 */
				do_action( 'woocommerce_after_add_to_cart_form' );

				$hooks_after = ob_get_clean();
			}

			// Because we are printing the template part using do_blocks, context from the outside is lost.
			// This filter is used to add the isDescendantOfAddToCartWithOptions context back.
			add_filter( 'render_block_context', array( $this, 'set_is_descendant_of_add_to_cart_with_options_context' ), 10, 2 );
			$template_part_blocks = do_blocks( $template_part_contents );
			remove_filter( 'render_block_context', array( $this, 'set_is_descendant_of_add_to_cart_with_options_context' ) );

			$wrapper_attributes = array(
				'class'                     => $classes,
				'style'                     => esc_attr( $classes_and_styles['styles'] ),
				'data-wp-interactive'       => 'woocommerce/add-to-cart-with-options',
				'data-wp-class--is-invalid' => '!state.isFormValid',
			);
			$context_directive  = wp_interactivity_data_wp_context( $context );

			$cart_redirect_after_add = get_option( 'woocommerce_cart_redirect_after_add' );
			$form_attributes         = '';
			$legacy_mode             = 'yes' === $cart_redirect_after_add || $this->has_form_elements( $hooks_before ) || $this->has_form_elements( $hooks_after );
			if ( $legacy_mode ) {
				$action_url = home_url( add_query_arg( null, null ) );

				// If an extension is hooking into the form or we need to redirect to the cart,
				// we fall back to a regular HTML form.
				$form_attributes = array(
					'action'  => esc_url(
						/**
						 * Filter the add to cart form action.
						 *
						 * @since 10.0.0
						 * @param string $action_url The add to cart form action URL, defaulting to the current page.
						 * @return string The add to cart form action URL.
						 */
						apply_filters( 'woocommerce_add_to_cart_form_action', $action_url )
					),
					'method'  => 'post',
					'enctype' => 'multipart/form-data',
					'class'   => 'cart',
				);
			} else {
				// Otherwise, we use the Interactivity API.
				$form_attributes = array(
					'data-wp-on--submit' => 'actions.addToCart',
				);
			}

			// These hidden inputs are used by extensions or Express Payment methods to gather information of the form state.
			$hidden_input = '';
			if ( ProductType::SIMPLE === $product_type ) {
				$hidden_input = '<input type="hidden" name="add-to-cart" value="' . esc_attr( $product_id ) . '" />';
			} elseif ( ProductType::GROUPED === $product_type ) {
				$hidden_input = '<input type="hidden" name="add-to-cart" value="' . esc_attr( $product_id ) . '" />';
			} elseif ( ProductType::VARIABLE === $product_type ) {
				$hidden_input = '<div class="single_variation_wrap">
					<input type="hidden" name="add-to-cart" value="' . esc_attr( $product_id ) . '" />
					<input type="hidden" name="product_id" value="' . esc_attr( $product_id ) . '" />
					<input type="hidden"
						name="variation_id"
						data-wp-bind--value="woocommerce/product-data::state.variationId"
					/>
				</div>';
			}

			$form_html = sprintf(
				'<form %1$s %2$s>%3$s%4$s%5$s%6$s</form>',
				get_block_wrapper_attributes(
					array_merge(
						$wrapper_attributes,
						$form_attributes,
						array(
							'class' => implode(
								' ',
								array_filter(
									array(
										isset( $wrapper_attributes['class'] ) ? $wrapper_attributes['class'] : '',
										isset( $form_attributes['class'] ) ? $form_attributes['class'] : '',
										// Add the `is-layout-flow` class so inner elements automatically get the
										// default vertical margin from the theme. That's especially useful for
										// elements added by extensions like express payment method buttons.
										// In the future, we want to use `supports.layout` in block.json instead
										// of hardcoding the class here. However, right now that wouldn't work
										// because the wrapper element of the block is the notices `<div>`, so the
										// `is-layout-flow` class would be applied to the notices container instead
										// of the `<form>` as we want.
										'is-layout-flow',
									)
								)
							),
						)
					)
				),
				$context_directive,
				$hooks_before,
				$template_part_blocks,
				$hooks_after,
				$hidden_input
			);

			ob_start();

			if ( in_array( $product_type, array( ProductType::SIMPLE, ProductType::EXTERNAL, ProductType::VARIABLE, ProductType::GROUPED ), true ) ) {

				$add_to_cart_fn = 'woocommerce_' . $product_type . '_add_to_cart';
				remove_action( 'woocommerce_' . $product_type . '_add_to_cart', $add_to_cart_fn, 30 );

				/**
				 * Trigger the single product add to cart action that prints the markup.
				 *
				 * @since 9.9.0
				 */
				do_action( 'woocommerce_' . $product_type . '_add_to_cart' );
				add_action( 'woocommerce_' . $product_type . '_add_to_cart', $add_to_cart_fn, 30 );
			}

			$form_html = $form_html . ob_get_clean();

			if ( ! $legacy_mode ) {
				$form_html = $this->render_interactivity_notices_region( $form_html );
			}
		} else {
			ob_start();

			/**
			 * Trigger the single product add to cart action that prints the markup.
			 *
			 * @since 9.7.0
			 */
			do_action( 'woocommerce_' . $product_type . '_add_to_cart' );

			$wrapper_attributes = array(
				'class' => $classes,
				'style' => esc_attr( $classes_and_styles['styles'] ),
			);

			$form_html = ob_get_clean();
			$form_html = sprintf( '<div %1$s>%2$s</div>', get_block_wrapper_attributes( $wrapper_attributes ), $form_html );
		}

		$product = $previous_product;

		return $form_html;
	}

	/**
	 * Render interactivity API powered notices that can be added client-side. This reuses classes
	 * from the woocommerce/store-notices block to ensure style consistency.
	 *
	 * @param string $form_html The form HTML.
	 * @return string The rendered store notices HTML.
	 */
	protected function render_interactivity_notices_region( $form_html ) {
		$context_directive = wp_interactivity_data_wp_context(
			array(
				'notices' => array(),
			)
		);

		ob_start();
		?>
		<div data-wp-interactive="woocommerce/store-notices" class="wc-block-components-notices alignwide" <?php echo $context_directive; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<template data-wp-each--notice="context.notices" data-wp-each-key="context.notice.id">
				<div
					class="wc-block-components-notice-banner"
					data-wp-class--is-error="state.isError"
					data-wp-class--is-success="state.isSuccess"
					data-wp-class--is-info="state.isInfo"
					data-wp-class--is-dismissible="context.notice.dismissible"
					data-wp-bind--role="state.role"
					data-wp-watch="callbacks.injectIcon"
				>
					<div class="wc-block-components-notice-banner__content">
						<span data-wp-init="callbacks.renderNoticeContent" aria-live="assertive" aria-atomic="true"></span>
					</div>
					<button
						data-wp-bind--hidden="!context.notice.dismissible"
						class="wc-block-components-button wp-element-button wc-block-components-notice-banner__dismiss contained"
						aria-label="<?php esc_attr_e( 'Dismiss this notice', 'woocommerce' ); ?>"
						data-wp-on--click="actions.removeNotice"
					>
						<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
							<path d="M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z" />
						</svg>
					</button>
				</div>
			</template>
			<?php echo $form_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
		</div>
		<?php
		return ob_get_clean();
	}
}
PK     \NO     4  BlockTypes/AddToCartWithOptions/QuantitySelector.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
use Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions\Utils as AddToCartWithOptionsUtils;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Enums\ProductType;

/**
 * Block type for quantity selector in add to cart with options.
 */
class QuantitySelector extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'add-to-cart-with-options-quantity-selector';

	/**
	 * Render the block.
	 *
	 * The selector is hidden for:
	 * - Simple products that are out of stock.
	 * - Not purchasable simple products.
	 * - External products with URLs
	 * - Products sold individually
	 *
	 * @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 ) {
		global $product;
		$previous_product = $product;

		$product = AddToCartWithOptionsUtils::get_product_from_context( $block, $previous_product );

		if ( ! $product ) {
			$product = $previous_product;

			return '';
		}

		if ( AddToCartWithOptionsUtils::is_not_purchasable_product( $product ) ) {
			$product = $previous_product;

			return '';
		}

		$is_external_product_with_url        = $product instanceof \WC_Product_External && $product->get_product_url();
		$can_only_be_purchased_one_at_a_time = $product->is_sold_individually();
		$managing_stock                      = $product->managing_stock();
		$stock_quantity                      = $product->get_stock_quantity();
		$allows_backorders                   = $product->backorders_allowed();

		if ( AddToCartWithOptionsUtils::is_min_max_quantity_same( $product ) ) {
			$product = $previous_product;

			return '';
		}

		if ( $is_external_product_with_url || $can_only_be_purchased_one_at_a_time || ( $managing_stock && $stock_quantity <= 1 && ! $allows_backorders ) ) {
			$product = $previous_product;

			return '';
		}

		ob_start();

		woocommerce_quantity_input(
			array(
				'min_value'   => $product->get_min_purchase_quantity(),
				'max_value'   => $product->get_max_purchase_quantity(),
				'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( wp_unslash( $_POST['quantity'] ) ) : $product->get_min_purchase_quantity(), // phpcs:ignore WordPress.Security.NonceVerification.Missing
			)
		);

		$product_html = ob_get_clean();

		// If the quantity input is hidden, don't render the stepper buttons and styles.
		$has_visible_quantity_input = AddToCartWithOptionsUtils::has_visible_quantity_input( $product_html );
		if ( $has_visible_quantity_input ) {
			$product_name = $product->get_name();
			$product_html = AddToCartWithOptionsUtils::add_quantity_steppers( $product_html, $product_name );
			$product_html = AddToCartWithOptionsUtils::add_quantity_stepper_classes( $product_html );
		}

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );

		$classes = implode(
			' ',
			array_filter(
				array(
					'wp-block-add-to-cart-with-options-quantity-selector wc-block-add-to-cart-with-options__quantity-selector',
					esc_attr( $classes_and_styles['classes'] ),
					$has_visible_quantity_input ? '' : 'wc-block-add-to-cart-with-options__quantity-selector--hidden',
				)
			)
		);

		$wrapper_attributes = array(
			'class' => $classes,
			'style' => esc_attr( $classes_and_styles['styles'] ),
		);
		$input_attributes   = array();

		$product_quantity_constraints = AddToCartWithOptionsUtils::get_product_quantity_constraints( $product );

		wp_interactivity_config(
			'woocommerce',
			array(
				'products' => array(
					$product->get_id() => array(
						'min'  => $product_quantity_constraints['min'],
						'max'  => $product_quantity_constraints['max'],
						'step' => $product_quantity_constraints['step'],
					),
				),
			)
		);

		if ( $product->is_type( ProductType::VARIABLE ) ) {
			wp_enqueue_script_module( 'woocommerce/product-elements' );

			$variations_data           = $product->get_available_variations( 'objects' );
			$formatted_variations_data = array();
			foreach ( $variations_data as $variation ) {
				$variation_quantity_constraints = AddToCartWithOptionsUtils::get_product_quantity_constraints( $variation );
				$variation_data                 = array();

				// Only add variation data if it's different than the defaults.
				if ( 1 !== $variation_quantity_constraints['min'] ) {
					$variation_data['min'] = $variation_quantity_constraints['min'];
				}
				if ( null !== $variation_quantity_constraints['max'] ) {
					$variation_data['max'] = $variation_quantity_constraints['max'];
				}
				if ( 1 !== $variation_quantity_constraints['step'] ) {
					$variation_data['step'] = $variation_quantity_constraints['step'];
				}
				if ( $variation->is_sold_individually() ) {
					$variation_data['sold_individually'] = true;
				}
				$formatted_variations_data[ $variation->get_id() ] = $variation_data;
			}

			wp_interactivity_config(
				'woocommerce',
				array(
					'products' => array(
						$product->get_id() => array(
							'variations' => $formatted_variations_data,
						),
					),
				)
			);

			$wrapper_attributes['data-wp-bind--hidden'] = 'woocommerce/add-to-cart-with-options-quantity-selector::!state.allowsQuantityChange';
			$input_attributes['data-wp-bind--min']      = 'woocommerce/product-elements::state.productData.min';
			$input_attributes['data-wp-bind--max']      = 'woocommerce/product-elements::state.productData.max';
			$input_attributes['data-wp-bind--step']     = 'woocommerce/product-elements::state.productData.step';
			$input_attributes['data-wp-watch']          = 'woocommerce/add-to-cart-with-options::callbacks.watchQuantityConstraints';
		}

		$form = AddToCartWithOptionsUtils::make_quantity_input_interactive( $product_html, $wrapper_attributes, $input_attributes );

		$product = $previous_product;

		return $form;
	}
}
PK     \>*U  U  >  BlockTypes/AddToCartWithOptions/VariationSelectorAttribute.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
use Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions\Utils as AddToCartWithOptionsUtils;
use WP_Block;

/**
 * Block type for variation selector item in add to cart with options.
 * It's responsible to render each child attribute in a form of a list item.
 */
class VariationSelectorAttribute extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'add-to-cart-with-options-variation-selector-attribute';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ): string {
		global $product;

		$content = '';

		$product_attributes = $product->get_variation_attributes();

		foreach ( $product_attributes as $product_attribute_name => $product_attribute_terms ) {
			$content .= $this->get_product_row( $product_attribute_name, $product_attribute_terms, $block );
		}

		return $content;
	}

	/**
	 * Get product row HTML.
	 *
	 * @param string   $attribute_name Product Attribute Name.
	 * @param array    $product_attribute_terms Product Attribute Terms.
	 * @param WP_Block $block The Block.
	 * @return string Row HTML
	 */
	private function get_product_row( $attribute_name, $product_attribute_terms, $block ): string {
		global $product;

		$attribute_terms    = $this->get_terms( $attribute_name, $product_attribute_terms );
		$product_variations = $product->get_available_variations( 'objects' );

		// Filter out terms which are not available in any product variation.
		$attribute_terms = array_filter(
			$attribute_terms,
			function ( $term ) use ( $product_variations, $attribute_name ) {
				foreach ( $product_variations as $variation ) {
					$attributes = $variation->get_variation_attributes();
					if (
						$term['value'] === $attributes[ wc_variation_attribute_name( $attribute_name ) ] ||
						'' === $attributes[ wc_variation_attribute_name( $attribute_name ) ]
					) {
						return true;
					}
				}
			}
		);

		if ( empty( $attribute_terms ) ) {
			return '';
		}

		$block_content = AddToCartWithOptionsUtils::render_block_with_context(
			$block,
			array(
				'woocommerce/attributeId'    => 'wc_product_attribute_' . uniqid(),
				'woocommerce/attributeName'  => $attribute_name,
				'woocommerce/attributeTerms' => $attribute_terms,
			),
		);

		// Render the inner blocks of the Variation Selector Item Template block with `dynamic` set to `false`
		// to prevent calling `render_callback` and ensure that no wrapper markup is included.
		return $block_content;
	}

	/**
	 * Get product attributes terms.
	 *
	 * @param string $attribute_name Product Attribute Name.
	 * @param array  $attribute_terms Product Attribute Terms.
	 * @return array[] Array of term data with structure:
	 *                 [
	 *                     'label'      => (string) Display label for the term.
	 *                     'value'      => (string) Internal value/slug for the term.
	 *                     'isSelected' => (bool)   Whether this term is the default selection.
	 *                 ]
	 */
	protected function get_terms( $attribute_name, $attribute_terms ) {
		global $product;

		$is_taxonomy = taxonomy_exists( $attribute_name );

		$selected_attribute = $product->get_variation_default_attribute( $attribute_name );

		if ( $is_taxonomy ) {
			$items = array_map(
				function ( $term ) use ( $attribute_name, $product, $selected_attribute ) {
					return array(
						'value'      => $term->slug,
						/**
						 * Filter the variation option name.
						 *
						 * @since 9.7.0
						 *
						 * @param string     $option_label    The option label.
						 * @param WP_Term|string|null $item   Term object for taxonomies, option string for custom attributes.
						 * @param string     $attribute_name  Name of the attribute.
						 * @param WC_Product $product         Product object.
						 */
						'label'      => apply_filters(
							'woocommerce_variation_option_name',
							$term->name,
							$term,
							$attribute_name,
							$product
						),
						'isSelected' => $selected_attribute === $term->slug,
					);
				},
				wc_get_product_terms( $product->get_id(), $attribute_name, array( 'fields' => 'all' ) ),
			);
		} else {
			$items = array_map(
				function ( $term ) use ( $attribute_name, $product, $selected_attribute ) {
					return array(
						'value'      => $term,
						/**
						 * Filter the variation option name.
						 *
						 * @since 9.7.0
						 *
						 * @param string     $option_label    The option label.
						 * @param WP_Term|string|null $item   Term object for taxonomies, option string for custom attributes.
						 * @param string     $attribute_name  Name of the attribute.
						 * @param WC_Product $product         Product object.
						 */
						'label'      => apply_filters(
							'woocommerce_variation_option_name',
							$term,
							null,
							$attribute_name,
							$product
						),
						'isSelected' => $selected_attribute === $term,
					);
				},
				$attribute_terms,
			);
		}

		return $items;
	}
}
PK     \v      BlockTypes/ProductSearch.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ProductSearch class.
 */
class ProductSearch extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-search';

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 * @return null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		static $instance_id = 0;

		$attributes = wp_parse_args(
			$attributes,
			array(
				'hasLabel'    => true,
				'align'       => '',
				'className'   => '',
				'label'       => __( 'Search', 'woocommerce' ),
				'placeholder' => __( 'Search products…', 'woocommerce' ),
			)
		);

		/**
		 * Product Search event.
		 *
		 * Listens for product search form submission, and on submission fires a WP Hook named
		 * `experimental__woocommerce_blocks-product-search`. This can be used by tracking extensions such as Google
		 * Analytics to track searches.
		 */
		$this->asset_api->add_inline_script(
			'wp-hooks',
			"
			window.addEventListener( 'DOMContentLoaded', () => {
				const forms = document.querySelectorAll( '.wc-block-product-search form' );

				for ( const form of forms ) {
					form.addEventListener( 'submit', ( event ) => {
						const field = form.querySelector( '.wc-block-product-search__field' );

						if ( field && field.value ) {
							wp.hooks.doAction( 'experimental__woocommerce_blocks-product-search', { event: event, searchTerm: field.value } );
						}
					} );
				}
			} );
			",
			'after'
		);

		$input_id           = 'wc-block-search__input-' . ( ++$instance_id );
		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class' => implode(
					' ',
					array_filter(
						[
							'wc-block-product-search',
							$attributes['align'] ? 'align' . $attributes['align'] : '',
						]
					)
				),
			)
		);

		$label_markup = $attributes['hasLabel'] ? sprintf(
			'<label for="%s" class="wc-block-product-search__label">%s</label>',
			esc_attr( $input_id ),
			esc_html( $attributes['label'] )
		) : sprintf(
			'<label for="%s" class="wc-block-product-search__label screen-reader-text">%s</label>',
			esc_attr( $input_id ),
			esc_html( $attributes['label'] )
		);

		$input_markup  = sprintf(
			'<input type="search" id="%s" class="wc-block-product-search__field" placeholder="%s" name="s" />',
			esc_attr( $input_id ),
			esc_attr( $attributes['placeholder'] )
		);
		$button_markup = sprintf(
			'<button type="submit" class="wc-block-product-search__button" aria-label="%s">
				<svg aria-hidden="true" role="img" focusable="false" class="dashicon dashicons-arrow-right-alt2" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
					<path d="M6 15l5-5-5-5 1-2 7 7-7 7z" />
				</svg>
			</button>',
			esc_attr__( 'Search', 'woocommerce' )
		);

		$field_markup = '
			<div class="wc-block-product-search__fields">
				' . $input_markup . $button_markup . '
				<input type="hidden" name="post_type" value="product" />
			</div>
		';

		return sprintf(
			'<div %s><form role="search" method="get" action="%s">%s</form></div>',
			$wrapper_attributes,
			esc_url( home_url( '/' ) ),
			$label_markup . $field_markup
		);
	}
}
PK     \4w&  &  0  BlockTypes/CheckoutOrderSummaryDiscountBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutOrderSummaryDiscountBlock class.
 */
class CheckoutOrderSummaryDiscountBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-order-summary-discount-block';
}
PK     \      BlockTypes/AllProducts.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * AllProducts class.
 */
class AllProducts extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'all-products';

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );
		// Set this so filter blocks being used as widgets know when to render.
		$this->asset_data_registry->add( 'hasFilterableProducts', true );
		$this->asset_data_registry->add( 'minColumns', wc_get_theme_support( 'product_blocks::min_columns', 1 ) );
		$this->asset_data_registry->add( 'maxColumns', wc_get_theme_support( 'product_blocks::max_columns', 6 ) );
		$this->asset_data_registry->add( 'defaultColumns', wc_get_theme_support( 'product_blocks::default_columns', 3 ) );
		$this->asset_data_registry->add( 'minRows', wc_get_theme_support( 'product_blocks::min_rows', 1 ) );
		$this->asset_data_registry->add( 'maxRows', wc_get_theme_support( 'product_blocks::max_rows', 6 ) );
		$this->asset_data_registry->add( 'defaultRows', wc_get_theme_support( 'product_blocks::default_rows', 3 ) );

		// Hydrate the All Product block with data from the API. This is for the add to cart buttons which show current quantity in cart, and events.
		if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
			$this->asset_data_registry->hydrate_api_request( '/wc/store/v1/cart' );
		}
	}

	/**
	 * It is necessary to register and enqueue assets during the render phase because we want to load assets only if the block has the content.
	 */
	protected function register_block_type_assets() {
		parent::register_block_type_assets();
		$this->register_chunk_translations( [ $this->block_name ] );
	}
}
PK     \V$7f  f  "  BlockTypes/ProductFilterRating.phpnu [        <?php

declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection\Utils as ProductCollectionUtils;
use Automattic\WooCommerce\Internal\ProductFilters\FilterDataProvider;
use Automattic\WooCommerce\Internal\ProductFilters\QueryClauses;

/**
 * Product Filter: Rating Block
 *
 * @package Automattic\WooCommerce\Blocks\BlockTypes
 */
final class ProductFilterRating extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-filter-rating';

	const RATING_FILTER_QUERY_VAR = 'rating_filter';

	/**
	 * Initialize this block type.
	 *
	 * - Hook into WP lifecycle.
	 * - Register the block with WordPress.
	 */
	protected function initialize() {
		parent::initialize();

		add_filter( 'woocommerce_blocks_product_filters_selected_items', array( $this, 'prepare_selected_filters' ), 10, 2 );
	}



	/**
	 * Prepare the active filter items.
	 *
	 * @param array $items  The active filter items.
	 * @param array $params The query param parsed from the URL.
	 * @return array Active filters items.
	 */
	public function prepare_selected_filters( $items, $params ) {
		if ( empty( $params[ self::RATING_FILTER_QUERY_VAR ] ) ) {
			return $items;
		}

		$active_ratings = array_map( 'absint', explode( ',', $params[ self::RATING_FILTER_QUERY_VAR ] ) );
		$active_ratings = array_filter(
			$active_ratings,
			function ( $rating ) {
				return $rating > 0 && $rating < 6;
			}
		);
		$active_ratings = array_unique( $active_ratings );

		if ( empty( $active_ratings ) ) {
			return $items;
		}

		foreach ( $active_ratings as $rating ) {
			$items[] = array(
				'type'        => 'rating',
				'value'       => (string) $rating,
				/* translators: %s is referring to rating value. Example: Rated 4 out of 5. */
				'activeLabel' => sprintf( __( 'Rating: Rated %d out of 5', 'woocommerce' ), $rating ),
			);
		}

		return $items;
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		// don't render if its admin, or ajax in progress.
		if ( is_admin() || wp_doing_ajax() ) {
			return '';
		}

		$min_rating    = $attributes['minRating'] ?? 0;
		$rating_counts = $this->get_rating_counts( $block );
		// User selected minimum rating to display.
		$rating_counts_with_min = array_filter(
			$rating_counts,
			function ( $rating ) use ( $min_rating ) {
				return $rating['rating'] >= $min_rating & $rating['rating'] < 6;
			}
		);
		$filter_params          = $block->context['filterParams'] ?? array();
		$rating_query           = $filter_params[ self::RATING_FILTER_QUERY_VAR ] ?? '';
		$selected_rating        = array_filter( array_map( 'absint', explode( ',', $rating_query ) ) );

		$filter_options = array_map(
			function ( $rating ) use ( $selected_rating ) {
				$aria_label = sprintf(
					/* translators: %1$d is referring to rating value. Example: Rated 4 out of 5. */
					__( 'Rated %1$d out of 5', 'woocommerce' ),
					$rating['rating'],
				);

				return array(
					'label'     => $this->render_rating_label( (int) $rating['rating'] ),
					'ariaLabel' => $aria_label,
					'value'     => (string) $rating['rating'],
					'selected'  => in_array( $rating['rating'], $selected_rating, true ),
					'count'     => $rating['count'],
					'type'      => 'rating',
				);
			},
			$rating_counts_with_min
		);

		$filter_context = array(
			'items'      => $filter_options,
			'showCounts' => $attributes['showCounts'] ?? false,
			'groupLabel' => __( 'Rating', 'woocommerce' ),
		);

		$wrapper_attributes = array(
			'data-wp-interactive' => 'woocommerce/product-filters',
			'data-wp-key'         => wp_unique_prefixed_id( $this->get_full_block_name() ),
			'data-wp-context'     => wp_json_encode(
				array(
					/* translators: {{label}} is the rating filter item label. */
					'activeLabelTemplate' => __( 'Rating: {{label}}', 'woocommerce' ),
					'filterType'          => 'rating',
				),
				JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
			),
		);

		if ( empty( $filter_options ) ) {
			$wrapper_attributes['hidden'] = true;
			$wrapper_attributes['class']  = 'wc-block-product-filter--hidden';
		}

		return sprintf(
			'<div %1$s>%2$s</div>',
			get_block_wrapper_attributes( $wrapper_attributes ),
			array_reduce(
				$block->parsed_block['innerBlocks'],
				function ( $carry, $parsed_block ) use ( $filter_context ) {
					$carry .= ( new \WP_Block( $parsed_block, array( 'filterData' => $filter_context ) ) )->render();
					return $carry;
				},
				''
			)
		);
	}

	/**
	 * Render the rating label.
	 *
	 * @param int $rating The rating to render.
	 * @return string|false
	 */
	private function render_rating_label( $rating ) {
		$view_box_width = $rating * 24;

		$rating_label = sprintf(
			/* translators: %1$d is referring to rating value. Example: Rated 4 out of 5. */
			__( 'Rated %1$d out of 5', 'woocommerce' ),
			$rating,
		);

		ob_start();
		?>
			<svg
				width="<?php echo esc_attr( $view_box_width ); ?>"
				height="24"
				viewBox="0 0 <?php echo esc_attr( $view_box_width ); ?> 24"
				fill="currentColor"
				aria-label="<?php echo esc_attr( $rating_label ); ?>"
			>
				<?php
				for ( $i = 0; $i < $rating; $i++ ) {
					?>
					<path
						d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"
						transform="translate(<?php echo esc_attr( $i * 24 ); ?>, 0)"
					/>
					<?php
				}
				?>
			</svg>
		<?php
		return ob_get_clean();
	}

	/**
	 * Retrieve the rating filter data for current block.
	 *
	 * @param WP_Block $block Block instance.
	 */
	private function get_rating_counts( $block ) {
		if ( ! isset( $block->context['filterParams'] ) ) {
			return array();
		}

		$query_vars = ProductCollectionUtils::get_query_vars( $block, 1 );

		if ( ! empty( $query_vars['tax_query'] ) ) {
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
			$query_vars['tax_query'] = ProductCollectionUtils::remove_query_array( $query_vars['tax_query'], 'rating_filter', true );
		}

		if ( isset( $query_vars['taxonomy'] ) && false !== strpos( $query_vars['taxonomy'], 'pa_' ) ) {
			unset(
				$query_vars['taxonomy'],
				$query_vars['term']
			);
		}

		$container = wc_get_container();
		$counts    = $container->get( FilterDataProvider::class )->with( $container->get( QueryClauses::class ) )->get_rating_counts( $query_vars );
		$data      = array();

		foreach ( $counts as $key => $value ) {
			$data[] = array(
				'rating' => $key,
				'count'  => intval( $value ),
			);
		}

		return $data;
	}

	/**
	 * Disable the editor style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_editor_style() {
		return null;
	}

	/**
	 * Disable the script handle for this block type. We use block.json to load the script.
	 *
	 * @param string|null $key The key of the script to get.
	 * @return null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}
}
PK     \S;  ;     BlockTypes/ReviewsByCategory.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ReviewsByCategory class.
 */
class ReviewsByCategory extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'reviews-by-category';

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string
	 */
	protected function get_block_type_script( $key = null ) {
		$script = [
			'handle'       => 'wc-reviews-block-frontend',
			'path'         => $this->asset_api->get_block_asset_build_path( 'reviews-frontend' ),
			'dependencies' => [],
		];
		return $key ? $script[ $key ] : $script;
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );
		$this->asset_data_registry->add( 'reviewRatingsEnabled', wc_review_ratings_enabled() );
		$this->asset_data_registry->add( 'showAvatars', '1' === get_option( 'show_avatars' ) );
	}
}
PK     \ <8  8    BlockTypes/ReviewsByProduct.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ReviewsByProduct class.
 */
class ReviewsByProduct extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'reviews-by-product';

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string
	 */
	protected function get_block_type_script( $key = null ) {
		$script = [
			'handle'       => 'wc-reviews-block-frontend',
			'path'         => $this->asset_api->get_block_asset_build_path( 'reviews-frontend' ),
			'dependencies' => [],
		];
		return $key ? $script[ $key ] : $script;
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );
		$this->asset_data_registry->add( 'reviewRatingsEnabled', wc_review_ratings_enabled() );
		$this->asset_data_registry->add( 'showAvatars', '1' === get_option( 'show_avatars' ) );
	}
}
PK     \;?*  *  1  BlockTypes/CheckoutOrderSummaryCartItemsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutOrderSummaryCartItemsBlock class.
 */
class CheckoutOrderSummaryCartItemsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-order-summary-cart-items-block';
}
PK     \o~G    +  BlockTypes/CheckoutShippingAddressBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutShippingAddressBlock class.
 */
class CheckoutShippingAddressBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-shipping-address-block';
}
PK     \٥v    -  BlockTypes/ProductCollection/QueryBuilder.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection;

use Automattic\WooCommerce\Blocks\BlockTypes\AttributeFilter;
use Automattic\WooCommerce\Blocks\BlockTypes\PriceFilter;
use Automattic\WooCommerce\Blocks\BlockTypes\RatingFilter;
use Automattic\WooCommerce\Blocks\BlockTypes\StockFilter;
use WP_Query;
use WC_Tax;
use Automattic\WooCommerce\Enums\ProductStockStatus;

/**
 * QueryBuilder class.
 * Responsible for constructing and modifying product queries.
 */
class QueryBuilder {

	/**
	 * All query args from WP_Query.
	 *
	 * @var array
	 */
	protected $valid_query_vars;

	/**
	 * Orderby options not natively supported by WordPress REST API
	 *
	 * @var array
	 */
	protected $custom_order_opts = array( 'popularity', 'rating', 'post__in', 'price', 'sales', 'menu_order', 'random' );

	/**
	 * All the query args related to the filter by attributes block.
	 *
	 * @var array
	 */
	protected $attributes_filter_query_args = array();

	/**
	 * Collection handler store.
	 *
	 * @var array
	 */
	protected $collection_handler_store = array();

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->valid_query_vars = $this->get_valid_query_vars();
		add_filter( 'posts_clauses', array( $this, 'add_price_range_filter_posts_clauses' ), 10, 2 );
	}

	/**
	 * Set the collection handler store.
	 *
	 * @param array $collection_handler_store The collection handler store containing registered collection handlers.
	 */
	public function set_collection_handler_store( $collection_handler_store ) {
		$this->collection_handler_store = $collection_handler_store;
	}

	/**
	 * Set collection handler.
	 *
	 * @param string $collection_name The name of the custom collection.
	 * @param array  $handlers        Collection handlers.
	 */
	public function set_collection_handler( $collection_name, $handlers ) {
		$this->collection_handler_store[ $collection_name ] = $handlers;
	}

	/**
	 * Set attributes filter query args.
	 *
	 * @param array $args The attributes filter query arguments.
	 */
	public function set_attributes_filter_query_args( $args ) {
		$this->attributes_filter_query_args = $args;
	}

	/**
	 * Return or initialize $valid_query_vars.
	 *
	 * @return array
	 */
	private function get_valid_query_vars() {
		if ( ! empty( $this->valid_query_vars ) ) {
			return $this->valid_query_vars;
		}

		$valid_query_vars       = array_keys( ( new WP_Query() )->fill_query_vars( array() ) );
		$this->valid_query_vars = array_merge(
			$valid_query_vars,
			// fill_query_vars doesn't include these vars so we need to add them manually.
			array(
				'date_query',
				'exact',
				'ignore_sticky_posts',
				'lazy_load_term_meta',
				'meta_compare_key',
				'meta_compare',
				'meta_query',
				'meta_type_key',
				'meta_type',
				'nopaging',
				'offset',
				'order',
				'orderby',
				'page',
				'post_type',
				'posts_per_page',
				'suppress_filters',
				'tax_query',
				'isProductCollection',
				'priceRange',
			)
		);

		return $this->valid_query_vars;
	}

	/**
	 * Get custom order options.
	 *
	 * @return array
	 */
	public function get_custom_order_opts() {
		return $this->custom_order_opts;
	}

	/**
	 * Get the final query arguments for the frontend.
	 *
	 * @param array $collection_args            Any special arguments that should change the behavior of the query.
	 * @param array $query                      The query arguments.
	 * @param int   $page                       The page number.
	 * @param bool  $is_exclude_applied_filters Whether to exclude the applied filters or not.
	 */
	public function get_final_frontend_query( $collection_args, $query, $page = 1, $is_exclude_applied_filters = false ) {
		$product_ids = $query['post__in'] ?? array();
		$offset      = $query['offset'] ?? 0;
		$per_page    = $query['perPage'] ?? 9;
		$order       = $query['order'] ?? 'asc';
		$search      = $query['search'] ?? '';

		$common_query_values = array(
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
			'meta_query'     => array(),
			'posts_per_page' => $per_page,
			'order'          => $order,
			'offset'         => ( $per_page * ( $page - 1 ) ) + $offset,
			'post__in'       => $product_ids,
			'post_status'    => 'publish',
			'post_type'      => 'product',
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
			'tax_query'      => array(),
			'paged'          => $page,
			's'              => $search,
		);

		$is_on_sale          = $query['woocommerceOnSale'] ?? false;
		$order_by            = $query['orderBy'] ?? '';
		$stock_status        = $query['woocommerceStockStatus'] ?? array_keys( wc_get_product_stock_status_options() );
		$product_attributes  = $query['woocommerceAttributes'] ?? array();
		$taxonomies_query    = $this->get_filter_by_taxonomies_query( $query['tax_query'] ?? array() );
		$handpicked_products = $query['woocommerceHandPickedProducts'] ?? array();
		$time_frame          = $query['timeFrame'] ?? null;
		$price_range         = $query['priceRange'] ?? null;
		$featured            = $query['featured'] ?? false;

		// Allow collections to modify the collection arguments passed to the query builder.
		$handlers = $this->collection_handler_store[ $collection_args['name'] ] ?? null;
		if ( isset( $handlers['frontend_args'] ) ) {
			$collection_args = call_user_func( $handlers['frontend_args'], $collection_args, $query );
		}

		$final_query = $this->get_final_query_args(
			$collection_args,
			$common_query_values,
			array(
				'on_sale'             => $is_on_sale,
				'stock_status'        => $stock_status,
				'orderby'             => $order_by,
				'product_attributes'  => $product_attributes,
				'taxonomies_query'    => $taxonomies_query,
				'handpicked_products' => $handpicked_products,
				'featured'            => $featured,
				'timeFrame'           => $time_frame,
				'priceRange'          => $price_range,
			),
			$is_exclude_applied_filters
		);

		return $final_query;
	}

	/**
	 * Return a query to filter products by taxonomies (product categories, product tags, etc.)
	 *
	 * For example:
	 * User could provide "Product Categories" using "Filters" ToolsPanel available in Inspector Controls.
	 * We use this function to extract its query from $tax_query.
	 *
	 * For example, this is how the query for product categories will look like in $tax_query array:
	 * Array
	 *    (
	 *        [taxonomy] => product_cat
	 *        [terms] => Array
	 *            (
	 *                [0] => 36
	 *            )
	 *    )
	 *
	 * For product tags, taxonomy would be "product_tag"
	 *
	 * @param array $tax_query Query to filter products by taxonomies.
	 * @return array Query to filter products by taxonomies.
	 */
	private function get_filter_by_taxonomies_query( $tax_query ): array {
		if ( ! is_array( $tax_query ) ) {
			return array();
		}

		/**
		 * Get an array of taxonomy names associated with the "product" post type because
		 * we also want to include custom taxonomies associated with the "product" post type.
		 */
		$product_taxonomies = array_diff( get_object_taxonomies( 'product', 'names' ), array( 'product_visibility', 'product_shipping_class' ) );
		$result             = array_filter(
			$tax_query,
			function ( $item ) use ( $product_taxonomies ) {
				return isset( $item['taxonomy'] ) && in_array( $item['taxonomy'], $product_taxonomies, true );
			}
		);

		// phpcs:ignore WordPress.DB.SlowDBQuery
		return ! empty( $result ) ? array( 'tax_query' => $result ) : array();
	}

	/**
	 * Get final query args based on provided values
	 *
	 * @param array $collection_args            Any special arguments that should change the behavior of the query.
	 * @param array $common_query_values        Common query values.
	 * @param array $query                      Query from block context.
	 * @param bool  $is_exclude_applied_filters Whether to exclude the applied filters or not.
	 */
	public function get_final_query_args(
		$collection_args,
		$common_query_values,
		$query,
		$is_exclude_applied_filters = false
	) {
		$orderby_query    = $query['orderby'] ? $this->get_custom_orderby_query( $query['orderby'] ) : array();
		$on_sale_query    = $this->get_on_sale_products_query( $query['on_sale'] );
		$stock_query      = $this->get_stock_status_query( $query['stock_status'] );
		$visibility_query = is_array( $query['stock_status'] ) ? $this->get_product_visibility_query( $stock_query, $query['stock_status'] ) : array();
		$featured_query   = $this->get_featured_query( $query['featured'] ?? false );
		$attributes_query = $this->get_product_attributes_query( $query['product_attributes'] );
		$taxonomies_query = $query['taxonomies_query'] ?? array();
		$tax_query        = $this->merge_tax_queries( $visibility_query, $attributes_query, $taxonomies_query, $featured_query );
		$date_query       = $this->get_date_query( $query['timeFrame'] ?? array() );
		$price_query_args = $this->get_price_range_query_args( $query['priceRange'] ?? array() );
		$handpicked_query = $this->get_handpicked_query( $query['handpicked_products'] ?? false );

		// We exclude applied filters to generate product ids for the filter blocks.
		$applied_filters_query = $is_exclude_applied_filters ? array() : $this->get_queries_by_applied_filters();

		// Allow collections to provide their own query parameters.
		$handlers = $this->collection_handler_store[ $collection_args['name'] ] ?? null;
		if ( isset( $handlers['build_query'] ) ) {
			$collection_query = call_user_func(
				$handlers['build_query'],
				$collection_args,
				$common_query_values,
				$query,
				$is_exclude_applied_filters
			);
		} else {
			$collection_query = array();
		}

		return $this->merge_queries(
			$common_query_values,
			$orderby_query,
			$on_sale_query,
			$stock_query,
			$tax_query,
			$applied_filters_query,
			$date_query,
			$price_query_args,
			$handpicked_query,
			$collection_query
		);
	}

	/**
	 * Get query args for preview mode. These query args will be used with WP_Query to fetch the products.
	 *
	 * @param array           $collection_args Any collection-specific arguments.
	 * @param array           $args            Query args.
	 * @param WP_REST_Request $request         Request.
	 */
	public function get_preview_query_args( $collection_args, $args, $request ) {
		$collection_query = array();

		// Allow collections to override the preview mode behavior.
		$handlers = $this->collection_handler_store[ $collection_args['name'] ] ?? null;
		if ( isset( $handlers['preview_query'] ) ) {
			$collection_query = call_user_func( $handlers['preview_query'], $collection_args, $args, $request );
		}
		$orderby_query = $args['orderby'] ? $this->get_custom_orderby_query( $args['orderby'] ) : array();

		$args = $this->merge_queries( $args, $orderby_query, $collection_query );
		return $args;
	}

	/**
	 * Return a query for products depending on their stock status.
	 *
	 * @param array $stock_statuses An array of acceptable stock statuses.
	 * @return array
	 */
	private function get_stock_status_query( $stock_statuses ) {
		if ( ! is_array( $stock_statuses ) ) {
			return array();
		}

		$stock_status_options = array_keys( wc_get_product_stock_status_options() );

		/**
		 * If all available stock status are selected, we don't need to add the
		 * meta query for stock status.
		 */
		if (
			count( $stock_statuses ) === count( $stock_status_options ) &&
			array_diff( $stock_statuses, $stock_status_options ) === array_diff( $stock_status_options, $stock_statuses )
		) {
			return array();
		}

		/**
		 * If all stock statuses are selected except 'outofstock', we use the
		 * product visibility query to filter out out of stock products.
		 *
		 * @see get_product_visibility_query()
		 */
		$diff = array_diff( $stock_status_options, $stock_statuses );
		if ( count( $diff ) === 1 && in_array( ProductStockStatus::OUT_OF_STOCK, $diff, true ) ) {
			return array();
		}

		return array(
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
			'meta_query' => array(
				array(
					'key'     => '_stock_status',
					'value'   => (array) $stock_statuses,
					'compare' => 'IN',
				),
			),
		);
	}

	/**
	 * Merge tax_queries from various queries.
	 *
	 * @param array ...$queries Query arrays to be merged.
	 * @return array
	 */
	private function merge_tax_queries( ...$queries ) {
		$tax_query = array();
		foreach ( $queries as $query ) {
			if ( ! empty( $query['tax_query'] ) ) {
				$tax_query = array_merge( $tax_query, $query['tax_query'] );
			}
		}
		// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
		return array( 'tax_query' => $tax_query );
	}

	/**
	 * Return the `tax_query` for the requested attributes
	 *
	 * @param array $attributes  Attributes and their terms.
	 *
	 * @return array
	 */
	private function get_product_attributes_query( $attributes = array() ) {
		if ( empty( $attributes ) ) {
			return array();
		}

		$grouped_attributes = array_reduce(
			$attributes,
			function ( $carry, $item ) {
				$taxonomy = sanitize_title( $item['taxonomy'] );

				if ( ! key_exists( $taxonomy, $carry ) ) {
					$carry[ $taxonomy ] = array(
						'field'    => 'term_id',
						'operator' => 'IN',
						'taxonomy' => $taxonomy,
						'terms'    => array( $item['termId'] ),
					);
				} else {
					$carry[ $taxonomy ]['terms'][] = $item['termId'];
				}

				return $carry;
			},
			array()
		);

		return array(
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
			'tax_query' => array_values( $grouped_attributes ),
		);
	}

	/**
	 * Generates a tax query to filter products based on their "featured" status.
	 * If the `$featured` parameter is true, the function will return a tax query
	 * that filters products to only those marked as featured.
	 * If `$featured` is false, an empty array is returned, meaning no filtering will be applied.
	 *
	 * @param bool $featured A flag indicating whether to filter products based on featured status.
	 *
	 * @return array A tax query for fetching featured products if `$featured` is true; otherwise, an empty array.
	 */
	private function get_featured_query( $featured ) {
		if ( true !== $featured && 'true' !== $featured ) {
			return array();
		}

		return array(
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
			'tax_query' => array(
				array(
					'taxonomy' => 'product_visibility',
					'field'    => 'name',
					'terms'    => 'featured',
					'operator' => 'IN',
				),
			),
		);
	}

	/**
	 * Return a query that filters products by price.
	 *
	 * @return array
	 */
	private function get_filter_by_price_query() {
		$min_price = get_query_var( PriceFilter::MIN_PRICE_QUERY_VAR );
		$max_price = get_query_var( PriceFilter::MAX_PRICE_QUERY_VAR );

		$max_price_query = empty( $max_price ) ? array() : array(
			'key'     => '_price',
			'value'   => $max_price,
			'compare' => '<=',
			'type'    => 'numeric',
		);

		$min_price_query = empty( $min_price ) ? array() : array(
			'key'     => '_price',
			'value'   => $min_price,
			'compare' => '>=',
			'type'    => 'numeric',
		);

		if ( empty( $min_price_query ) && empty( $max_price_query ) ) {
			return array();
		}

		return array(
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
			'meta_query' => array(
				array(
					'relation' => 'AND',
					$max_price_query,
					$min_price_query,
				),
			),
		);
	}

	/**
	 * Return a query that filters products by attributes.
	 *
	 * @return array
	 */
	private function get_filter_by_attributes_query() {
		$attributes_filter_query_args = $this->get_filter_by_attributes_query_vars();

		$queries = array_reduce(
			$attributes_filter_query_args,
			function ( $acc, $query_args ) {
				$attribute_name       = $query_args['filter'];
				$attribute_query_type = $query_args['query_type'];

				$attribute_value = get_query_var( $attribute_name );
				$attribute_query = get_query_var( $attribute_query_type );

				if ( empty( $attribute_value ) ) {
					return $acc;
				}

				// It is necessary explode the value because $attribute_value can be a string with multiple values (e.g. "red,blue").
				$attribute_value = explode( ',', $attribute_value );

				$acc[] = array(
					'taxonomy' => str_replace( AttributeFilter::FILTER_QUERY_VAR_PREFIX, 'pa_', $attribute_name ),
					'field'    => 'slug',
					'terms'    => $attribute_value,
					'operator' => 'and' === $attribute_query ? 'AND' : 'IN',
				);

				return $acc;
			},
			array()
		);

		if ( empty( $queries ) ) {
			return array();
		}

		return array(
			// phpcs:ignore WordPress.DB.SlowDBQuery
			'tax_query' => array(
				array(
					'relation' => 'AND',
					$queries,
				),
			),
		);
	}

	/**
	 * Get all the query args related to the filter by attributes block.
	 *
	 * @return array
	 * [color] => Array
	 *   (
	 *        [filter] => filter_color
	 *        [query_type] => query_type_color
	 *    )
	 *
	 * [size] => Array
	 *    (
	 *        [filter] => filter_size
	 *        [query_type] => query_type_size
	 *    )
	 * )
	 */
	private function get_filter_by_attributes_query_vars() {
		if ( ! empty( $this->attributes_filter_query_args ) ) {
			return $this->attributes_filter_query_args;
		}

		$this->attributes_filter_query_args = array_reduce(
			wc_get_attribute_taxonomies(),
			function ( $acc, $attribute ) {
				$acc[ $attribute->attribute_name ] = array(
					'filter'     => AttributeFilter::FILTER_QUERY_VAR_PREFIX . $attribute->attribute_name,
					'query_type' => AttributeFilter::QUERY_TYPE_QUERY_VAR_PREFIX . $attribute->attribute_name,
				);
				return $acc;
			},
			array()
		);

		return $this->attributes_filter_query_args;
	}

	/**
	 * Return a query that filters products by stock status.
	 *
	 * @return array
	 */
	private function get_filter_by_stock_status_query() {
		$filter_stock_status_values = get_query_var( StockFilter::STOCK_STATUS_QUERY_VAR );

		if ( empty( $filter_stock_status_values ) ) {
			return array();
		}

		$filtered_stock_status_values = array_filter(
			explode( ',', $filter_stock_status_values ),
			function ( $stock_status ) {
				return in_array( $stock_status, StockFilter::get_stock_status_query_var_values(), true );
			}
		);

		if ( empty( $filtered_stock_status_values ) ) {
			return array();
		}

		return array(
			// Ignoring the warning of not using meta queries.
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
			'meta_query' => array(
				array(
					'key'      => '_stock_status',
					'value'    => $filtered_stock_status_values,
					'operator' => 'IN',
				),
			),
		);
	}

	/**
	 * Return a query that filters products by rating.
	 *
	 * @return array
	 */
	private function get_filter_by_rating_query() {
		$filter_rating_values = get_query_var( RatingFilter::RATING_QUERY_VAR );
		if ( empty( $filter_rating_values ) ) {
			return array();
		}

		$parsed_filter_rating_values = explode( ',', $filter_rating_values );
		$product_visibility_terms    = wc_get_product_visibility_term_ids();

		if ( empty( $parsed_filter_rating_values ) || empty( $product_visibility_terms ) ) {
			return array();
		}

		$rating_terms = array_map(
			function ( $rating ) use ( $product_visibility_terms ) {
				return $product_visibility_terms[ 'rated-' . $rating ];
			},
			$parsed_filter_rating_values
		);

		return array(
			// phpcs:ignore WordPress.DB.SlowDBQuery
			'tax_query' => array(
				array(
					'field'         => 'term_taxonomy_id',
					'taxonomy'      => 'product_visibility',
					'terms'         => $rating_terms,
					'operator'      => 'IN',
					'rating_filter' => true,
				),
			),
		);
	}

	/**
	 * Return a query that filters products by taxonomy terms.
	 *
	 * @since 10.6.0
	 *
	 * @return array
	 */
	private function get_filter_by_taxonomy_query() {

		$container       = wc_get_container();
		$params_handler  = $container->get( \Automattic\WooCommerce\Internal\ProductFilters\Params::class );
		$taxonomy_params = $params_handler->get_param( 'taxonomy' );

		if ( empty( $taxonomy_params ) ) {
			return array();
		}

		$tax_queries = array();

		foreach ( $taxonomy_params as $taxonomy_slug => $param_key ) {
			$param_value = get_query_var( $param_key );

			// Adding is_string check to avoid invalid query parameters for the taxonomy.
			if ( ! is_string( $param_value ) || empty( $param_value ) ) {
				continue;
			}

			// Define $term_values by exploding the string.
			$term_values = explode( ',', $param_value );

			// Sanitize and filter (removes empty strings).
			$term_slugs = array_values( array_filter( array_map( 'sanitize_title', $term_values ) ) );

			if ( empty( $term_slugs ) ) {
				continue;
			}

			$tax_queries[] = array(
				'taxonomy' => $taxonomy_slug,
				'field'    => 'slug',
				'terms'    => $term_slugs,
				'operator' => 'IN',
			);
		}

		if ( empty( $tax_queries ) ) {
			return array();
		}

		return array(
			// phpcs:ignore WordPress.DB.SlowDBQuery
			'tax_query' => array(
				array(
					'relation' => 'AND',
					...$tax_queries,
				),
			),
		);
	}

	/**
	 * Merge two array recursively but replace the non-array values instead of
	 * merging them. The merging strategy:
	 *
	 * - If keys from merge array doesn't exist in the base array, create them.
	 * - For array items with numeric keys, we merge them as normal.
	 * - For array items with string keys:
	 *
	 *   - If the value isn't array, we'll use the value coming from the merge array.
	 *     $base       = ['orderby' => 'date']
	 *     $new_array  = ['orderby' => 'meta_value_num']
	 *     Result: ['orderby' => 'meta_value_num']
	 *
	 *   - If the value is array, we'll use recursion to merge each key.
	 *     $base       = ['meta_query' => [
	 *       [
	 *         'key'     => '_stock_status',
	 *         'compare' => 'IN'
	 *         'value'   =>  ['instock', 'onbackorder']
	 *       ]
	 *     ]]
	 *     $new_array  = ['meta_query' => [
	 *       [
	 *         'relation' => 'AND',
	 *         [...<max_price_query>],
	 *         [...<min_price_query>],
	 *       ]
	 *     ]]
	 *     Result: ['meta_query' => [
	 *       [
	 *         'key'     => '_stock_status',
	 *         'compare' => 'IN'
	 *         'value'   =>  ['instock', 'onbackorder']
	 *       ],
	 *       [
	 *         'relation' => 'AND',
	 *         [...<max_price_query>],
	 *         [...<min_price_query>],
	 *       ]
	 *     ]]
	 *
	 *     $base       = ['post__in' => [1, 2, 3, 4, 5]]
	 *     $new_array  = ['post__in' => [3, 4, 5, 6, 7]]
	 *     Result: ['post__in' => [1, 2, 3, 4, 5, 3, 4, 5, 6, 7]]
	 *
	 * @param array $base First array.
	 * @param array $new_array  Second array.
	 */
	private function array_merge_recursive_replace_non_array_properties( $base, $new_array ) {
		foreach ( $new_array as $key => $value ) {
			if ( is_numeric( $key ) ) {
				$base[] = $value;
			} elseif ( is_array( $value ) ) {
				if ( ! isset( $base[ $key ] ) ) {
					$base[ $key ] = array();
				}
				$base[ $key ] = $this->array_merge_recursive_replace_non_array_properties( $base[ $key ], $value );
			} else {
				$base[ $key ] = $value;
			}
		}

		return $base;
	}

	/**
	 * Return queries that are generated by query args.
	 *
	 * @return array
	 */
	private function get_queries_by_applied_filters() {
		return array(
			'price_filter'        => $this->get_filter_by_price_query(),
			'attributes_filter'   => $this->get_filter_by_attributes_query(),
			'stock_status_filter' => $this->get_filter_by_stock_status_query(),
			'rating_filter'       => $this->get_filter_by_rating_query(),
			'taxonomy_filter'     => $this->get_filter_by_taxonomy_query(),
		);
	}

	/**
	 * Return a query for product visibility depending on their stock status.
	 *
	 * @param array $stock_query  Stock status query.
	 * @param array $stock_status Selected stock status.
	 *
	 * @return array Tax query for product visibility.
	 */
	private function get_product_visibility_query( $stock_query, $stock_status ) {
		$product_visibility_terms  = wc_get_product_visibility_term_ids();
		$product_visibility_not_in = array( is_search() ? $product_visibility_terms['exclude-from-search'] : $product_visibility_terms['exclude-from-catalog'] );

		// Hide out of stock products.
		if ( empty( $stock_query ) && ! in_array( ProductStockStatus::OUT_OF_STOCK, $stock_status, true ) ) {
			$product_visibility_not_in[] = $product_visibility_terms[ ProductStockStatus::OUT_OF_STOCK ];
		}

		return array(
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
			'tax_query' => array(
				array(
					'taxonomy' => 'product_visibility',
					'field'    => 'term_taxonomy_id',
					'terms'    => $product_visibility_not_in,
					'operator' => 'NOT IN',
				),
			),
		);
	}

	/**
	 * Constructs a date query for product filtering based on a specified time frame.
	 *
	 * @param array $time_frame {
	 *     Associative array with 'operator' (in or not-in) and 'value' (date string).
	 *
	 *     @type string $operator Determines the inclusion or exclusion of the date range.
	 *     @type string $value    The date around which the range is applied.
	 * }
	 * @return array Date query array; empty if parameters are invalid.
	 */
	private function get_date_query( array $time_frame ): array {
		// Validate time_frame elements.
		if ( empty( $time_frame['operator'] ) || empty( $time_frame['value'] ) ) {
			return array();
		}

		// Determine the query operator based on the 'operator' value.
		$query_operator = 'in' === $time_frame['operator'] ? 'after' : 'before';

		// Construct and return the date query.
		return array(
			'date_query' => array(
				array(
					'column'        => 'post_date_gmt',
					$query_operator => $time_frame['value'],
					'inclusive'     => true,
				),
			),
		);
	}

	/**
	 * Get query arguments for price range filter.
	 * We are adding these extra query arguments to be used in `posts_clauses`
	 * because there are 2 special edge cases we wanna handle for Price range filter:
	 * Case 1: Prices excluding tax are displayed including tax
	 * Case 2: Prices including tax are displayed excluding tax
	 *
	 * Both of these cases require us to modify SQL query to get the correct results.
	 *
	 * See add_price_range_filter_posts_clauses function in this file for more details.
	 *
	 * @param array $price_range Price range with min and max values.
	 * @return array Query arguments.
	 */
	public function get_price_range_query_args( $price_range ) {
		if ( empty( $price_range ) ) {
			return array();
		}

		return array(
			'isProductCollection' => true,
			'priceRange'          => $price_range,
		);
	}

	/**
	 * Add the `posts_clauses` filter to the main query.
	 *
	 * @param array    $clauses The query clauses.
	 * @param WP_Query $query   The WP_Query instance.
	 */
	public function add_price_range_filter_posts_clauses( $clauses, $query ) {
		$query_vars                  = $query->query_vars;
		$is_product_collection_block = $query_vars['isProductCollection'] ?? false;
		if ( ! $is_product_collection_block ) {
			return $clauses;
		}

		$price_range = $query_vars['priceRange'] ?? null;
		if ( empty( $price_range ) ) {
			return $clauses;
		}

		global $wpdb;
		$adjust_for_taxes = $this->should_adjust_price_range_for_taxes();
		$clauses['join']  = $this->append_product_sorting_table_join( $clauses['join'] );

		$min_price = $price_range['min'] ?? null;
		if ( $min_price ) {
			if ( $adjust_for_taxes ) {
				$clauses['where'] .= $this->get_price_filter_query_for_displayed_taxes( $min_price, 'max_price', '>=' );
			} else {
				$clauses['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.max_price >= %f ', $min_price );
			}
		}

		$max_price = $price_range['max'] ?? null;
		if ( $max_price ) {
			if ( $adjust_for_taxes ) {
				$clauses['where'] .= $this->get_price_filter_query_for_displayed_taxes( $max_price, 'min_price', '<=' );
			} else {
				$clauses['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.min_price <= %f ', $max_price );
			}
		}

		return $clauses;
	}

	/**
	 * Get query for price filters when dealing with displayed taxes.
	 *
	 * @param float  $price_filter Price filter to apply.
	 * @param string $column Price being filtered (min or max).
	 * @param string $operator Comparison operator for column.
	 * @return string Constructed query.
	 */
	protected function get_price_filter_query_for_displayed_taxes( $price_filter, $column = 'min_price', $operator = '>=' ) {
		global $wpdb;

		// Select only used tax classes to avoid unwanted calculations.
		$product_tax_classes = $wpdb->get_col( "SELECT DISTINCT tax_class FROM {$wpdb->wc_product_meta_lookup};" );

		if ( empty( $product_tax_classes ) ) {
			return '';
		}

		$or_queries = array();

		// We need to adjust the filter for each possible tax class and combine the queries into one.
		foreach ( $product_tax_classes as $tax_class ) {
			$adjusted_price_filter = $this->adjust_price_filter_for_tax_class( $price_filter, $tax_class );
			$or_queries[]          = $wpdb->prepare(
				'( wc_product_meta_lookup.tax_class = %s AND wc_product_meta_lookup.`' . esc_sql( $column ) . '` ' . esc_sql( $operator ) . ' %f )',
				$tax_class,
				$adjusted_price_filter
			);
		}

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
		return $wpdb->prepare(
			' AND (
				wc_product_meta_lookup.tax_status = "taxable" AND ( 0=1 OR ' . implode( ' OR ', $or_queries ) . ')
				OR ( wc_product_meta_lookup.tax_status != "taxable" AND wc_product_meta_lookup.`' . esc_sql( $column ) . '` ' . esc_sql( $operator ) . ' %f )
			) ',
			$price_filter
		);
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * Adjusts a price filter based on a tax class and whether or not the amount includes or excludes taxes.
	 *
	 * This calculation logic is based on `wc_get_price_excluding_tax` and `wc_get_price_including_tax` in core.
	 *
	 * @param float  $price_filter Price filter amount as entered.
	 * @param string $tax_class Tax class for adjustment.
	 * @return float
	 */
	protected function adjust_price_filter_for_tax_class( $price_filter, $tax_class ) {
		$tax_display    = get_option( 'woocommerce_tax_display_shop' );
		$tax_rates      = WC_Tax::get_rates( $tax_class );
		$base_tax_rates = WC_Tax::get_base_tax_rates( $tax_class );

		// If prices are shown incl. tax, we want to remove the taxes from the filter amount to match prices stored excl. tax.
		if ( 'incl' === $tax_display ) {
			/**
			 * Filters if taxes should be removed from locations outside the store base location.
			 *
			 * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing
			 * with out of base locations. e.g. If a product costs 10 including tax, all users will pay 10
			 * regardless of location and taxes.
			 *
			 * @since 2.6.0
			 *
			 * @internal Matches filter name in WooCommerce core.
			 *
			 * @param boolean $adjust_non_base_location_prices True by default.
			 * @return boolean
			 */
			$taxes = apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ? WC_Tax::calc_tax( $price_filter, $base_tax_rates, true ) : WC_Tax::calc_tax( $price_filter, $tax_rates, true );
			return $price_filter - array_sum( $taxes );
		}

		// If prices are shown excl. tax, add taxes to match the prices stored in the DB.
		$taxes = WC_Tax::calc_tax( $price_filter, $tax_rates, false );

		return $price_filter + array_sum( $taxes );
	}

	/**
	 * Determines if price filters need adjustment based on the tax display settings.
	 *
	 * This function checks if there's a discrepancy between how prices are stored in the database
	 * and how they are displayed to the user, specifically with respect to tax inclusion or exclusion.
	 * It returns true if an adjustment is needed, indicating that the price filters should account for this
	 * discrepancy to display accurate prices.
	 *
	 * @return bool True if the price filters need to be adjusted for tax display settings, false otherwise.
	 */
	private function should_adjust_price_range_for_taxes() {
		$display_setting      = get_option( 'woocommerce_tax_display_shop' ); // Tax display setting ('incl' or 'excl').
		$price_storage_method = wc_prices_include_tax() ? 'incl' : 'excl';

		return $display_setting !== $price_storage_method;
	}

	/**
	 * Generates a post__in query to filter products to the set of provided IDs.
	 *
	 * @param int[]|false $handpicked_products The products to filter.
	 *
	 * @return array The post__in query.
	 */
	private function get_handpicked_query( $handpicked_products ) {
		if ( false === $handpicked_products ) {
			return array();
		}

		return array(
			'post__in' => $handpicked_products,
		);
	}

	/**
	 * Return a query for on sale products.
	 *
	 * @param bool $is_on_sale Whether to query for on sale products.
	 *
	 * @return array
	 */
	private function get_on_sale_products_query( $is_on_sale ) {
		if ( ! $is_on_sale ) {
			return array();
		}

		return array(
			'post__in' => wc_get_product_ids_on_sale(),
		);
	}

	/**
	 * Merge in the first parameter the keys "post_in", "meta_query" and "tax_query" of the second parameter.
	 *
	 * @param array[] ...$queries Query arrays to be merged.
	 * @return array
	 */
	private function merge_queries( ...$queries ) {
		// Rather than a simple merge, some query vars should be held aside and merged differently.
		$special_query_vars = array(
			'post__in' => array(),
		);
		$special_query_keys = array_keys( $special_query_vars );

		$merged_query = array_reduce(
			$queries,
			function ( $acc, $query ) use ( $special_query_keys, &$special_query_vars ) {
				if ( ! is_array( $query ) ) {
					return $acc;
				}

				// When the $query has keys but doesn't contain any valid query keys, we unpack/spread it then merge.
				if ( ! empty( $query ) && empty( array_intersect( $this->get_valid_query_vars(), array_keys( $query ) ) ) ) {
					return $this->merge_queries( $acc, ...array_values( $query ) );
				}

				// Pull out the special query vars so we can merge them separately.
				foreach ( $special_query_keys as $query_var ) {
					if ( isset( $query[ $query_var ] ) ) {
						$special_query_vars[ $query_var ][] = $query[ $query_var ];
						unset( $query[ $query_var ] );
					}
				}

				return $this->array_merge_recursive_replace_non_array_properties( $acc, $query );
			},
			array()
		);

		// Perform any necessary special merges.
		$merged_query['post__in'] = $this->merge_post__in( ...$special_query_vars['post__in'] );

		return $merged_query;
	}

	/**
	 * Return query params to support custom sort values
	 *
	 * @param string $orderby  Sort order option.
	 *
	 * @return array
	 */
	private function get_custom_orderby_query( $orderby ) {
		if ( ! in_array( $orderby, $this->custom_order_opts, true ) || 'post__in' === $orderby ) {
			return array( 'orderby' => $orderby );
		}

		if ( 'price' === $orderby ) {
			add_filter( 'posts_clauses', array( $this, 'add_price_sorting_posts_clauses' ), 10, 2 );
			return array(
				'isProductCollection' => true,
				'orderby'             => $orderby,
			);
		}

		// The popularity orderby value here is for backwards compatibility as we have since removed the filter option.
		if ( 'sales' === $orderby || 'popularity' === $orderby ) {
			add_filter( 'posts_clauses', array( $this, 'add_sales_sorting_posts_clauses' ), 10, 2 );
			return array(
				'isProductCollection' => true,
				'orderby'             => $orderby,
			);
		}

		if ( 'menu_order' === $orderby ) {
			add_filter( 'posts_clauses', array( $this, 'add_menu_order_with_title_fallback_posts_clauses' ), 10, 2 );
			return array(
				'isProductCollection' => true,
				'orderby'             => $orderby,
			);
		}

		if ( 'random' === $orderby ) {
			return array(
				'orderby' => 'rand',
			);
		}

		$meta_keys = array(
			'rating' => '_wc_average_rating',
		);

		return array(
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
			'meta_key' => $meta_keys[ $orderby ],
			'orderby'  => 'meta_value_num',
		);
	}

	/**
	 * Add the `posts_clauses` filter to add price-based sorting
	 *
	 * @param array    $clauses The list of clauses for the query.
	 * @param WP_Query $query   The WP_Query instance.
	 * @return array   Modified list of clauses.
	 */
	public function add_price_sorting_posts_clauses( $clauses, $query ) {
		$query_vars                  = $query->query_vars;
		$is_product_collection_block = $query_vars['isProductCollection'] ?? false;

		if ( ! $is_product_collection_block ) {
			return $clauses;
		}

		$orderby = $query_vars['orderby'] ?? null;
		if ( 'price' !== $orderby ) {
			return $clauses;
		}

		$clauses['join']    = $this->append_product_sorting_table_join( $clauses['join'] );
		$is_ascending_order = 'asc' === strtolower( $query_vars['order'] ?? 'desc' );

		$clauses['orderby'] = $is_ascending_order ?
			'wc_product_meta_lookup.min_price ASC, wc_product_meta_lookup.product_id ASC' :
			'wc_product_meta_lookup.max_price DESC, wc_product_meta_lookup.product_id DESC';

		return $clauses;
	}

	/**
	 * Add the `posts_clauses` filter to add sales-based sorting
	 *
	 * @param array    $clauses The list of clauses for the query.
	 * @param WP_Query $query   The WP_Query instance.
	 * @return array   Modified list of clauses.
	 */
	public function add_sales_sorting_posts_clauses( $clauses, $query ) {
		$query_vars                  = $query->query_vars;
		$is_product_collection_block = $query_vars['isProductCollection'] ?? false;

		if ( ! $is_product_collection_block ) {
			return $clauses;
		}

		$orderby = $query_vars['orderby'] ?? null;

		// The popularity orderby value here is for backwards compatibility as we have since removed the filter option.
		if ( 'sales' !== $orderby && 'popularity' !== $orderby ) {
			return $clauses;
		}

		$clauses['join']    = $this->append_product_sorting_table_join( $clauses['join'] );
		$is_ascending_order = 'asc' === strtolower( $query_vars['order'] ?? 'desc' );

		$clauses['orderby'] = $is_ascending_order ?
			'wc_product_meta_lookup.total_sales ASC, wc_product_meta_lookup.product_id ASC' :
			'wc_product_meta_lookup.total_sales DESC, wc_product_meta_lookup.product_id DESC';

		return $clauses;
	}

	/**
	 * Join wc_product_meta_lookup to posts if not already joined.
	 *
	 * @param string $sql SQL join.
	 * @return string
	 */
	protected function append_product_sorting_table_join( $sql ) {
		global $wpdb;

		if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) {
			$sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ";
		}
		return $sql;
	}

	/**
	 * Merge all of the 'post__in' values and return an array containing only values that are present in all arrays.
	 *
	 * @param int[][] ...$post__in The 'post__in' values to be merged.
	 *
	 * @return int[] The merged 'post__in' values.
	 */
	private function merge_post__in( ...$post__in ) {
		if ( empty( $post__in ) ) {
			return array();
		}

		// Since we're using array_intersect, any array that is empty will result
		// in an empty output array. To avoid this we need to make sure every
		// argument is a non-empty array.
		$post__in = array_filter(
			$post__in,
			function ( $val ) {
				return is_array( $val ) && ! empty( $val );
			}
		);
		if ( empty( $post__in ) ) {
			return array();
		}

		// Since the 'post__in' filter is exclusionary we need to use an intersection of
		// all of the arrays. This ensures one query doesn't add options that another
		// has otherwise excluded from the results.
		if ( count( $post__in ) > 1 ) {
			$post__in = array_intersect( ...$post__in );
			// An empty array means that there was no overlap between the filters and so
			// the query should return no results.
			if ( empty( $post__in ) ) {
				return array( -1 );
			}
		} else {
			$post__in = reset( $post__in );
		}

		return array_values( array_unique( $post__in, SORT_NUMERIC ) );
	}

	/**
	 * Add the `posts_clauses` filter to add menu order with title fallback sorting
	 *
	 * @param array    $clauses The list of clauses for the query.
	 * @param WP_Query $query   The WP_Query instance.
	 * @return array   Modified list of clauses.
	 */
	public function add_menu_order_with_title_fallback_posts_clauses( $clauses, $query ) {
		$query_vars                  = $query->query_vars;
		$is_product_collection_block = $query_vars['isProductCollection'] ?? false;

		if ( ! $is_product_collection_block ) {
			return $clauses;
		}

		$orderby = $query_vars['orderby'] ?? null;
		if ( 'menu_order' !== $orderby ) {
			return $clauses;
		}

		$is_ascending_order = ! isset( $query_vars['order'] ) || 'asc' === strtolower( $query_vars['order'] );

		$clauses['orderby'] = $is_ascending_order ?
			'menu_order ASC, post_title ASC' :
			'menu_order DESC, post_title DESC';

		return $clauses;
	}
}
PK     \sK    *  BlockTypes/ProductCollection/NoResults.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection;

use Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection\Utils as ProductCollectionUtils;
use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;

/**
 * NoResults class.
 */
class NoResults extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-collection-no-results';

	/**
	 * 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 ) {
		$content = trim( $content );
		if ( empty( $content ) ) {
			return '';
		}

		$query = ProductCollectionUtils::prepare_and_execute_query( $block );

		// If the query has products, don't render the block.
		if ( $query->post_count > 0 ) {
			return '';
		}

		// Update the anchor tag URLs.
		$updated_html_content = $this->modify_anchor_tag_urls( trim( $content ) );

		$wrapper_attributes = get_block_wrapper_attributes();
		return sprintf(
			'<div %1$s>%2$s</div>',
			$wrapper_attributes,
			$updated_html_content
		);
	}

	/**
	 * 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;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Set the URL attributes for "clearing any filters" and "Store's home" links.
	 *
	 * @param string $content Block content.
	 */
	protected function modify_anchor_tag_urls( $content ) {
		$processor = new \WP_HTML_Tag_Processor( trim( $content ) );

		// Set the URL attribute for the "clear any filters" link.
		if ( $processor->next_tag(
			array(
				'tag_name'   => 'a',
				'class_name' => 'wc-link-clear-any-filters',
			)
		) ) {
			$processor->set_attribute( 'href', $this->get_current_url_without_filters() );
		}

		// Set the URL attribute for the "Store's home" link.
		if ( $processor->next_tag(
			array(
				'tag_name'   => 'a',
				'class_name' => 'wc-link-stores-home',
			)
		) ) {
			$processor->set_attribute( 'href', home_url() );
		}

		return $processor->get_updated_html();
	}

	/**
	 * Get current URL without filter query parameters which will be used
	 * for the "clear any filters" link.
	 */
	protected function get_current_url_without_filters() {
		$protocol = is_ssl() ? 'https' : 'http';

		// Check the existence and sanitize HTTP_HOST and REQUEST_URI in the $_SERVER superglobal.
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$http_host = isset( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : '';
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';

		// Sanitize the host and URI.
		$http_host   = sanitize_text_field( $http_host );
		$request_uri = esc_url_raw( $request_uri );

		// Construct the full URL.
		$current_url = $protocol . '://' . $http_host . $request_uri;

		// Parse the URL to extract the query string.
		$parsed_url   = wp_parse_url( $current_url );
		$query_string = isset( $parsed_url['query'] ) ? $parsed_url['query'] : '';

		// Convert the query string into an associative array.
		parse_str( $query_string, $query_params );

		// Remove the filter query parameters.
		$params_to_remove = array( 'min_price', 'max_price', 'rating_filter', 'filter_', 'query_type_' );
		foreach ( $query_params as $key => $value ) {
			foreach ( $params_to_remove as $param ) {
				if ( strpos( $key, $param ) === 0 ) {
					unset( $query_params[ $key ] );
					break;
				}
			}
		}

		// Rebuild the query string without the removed parameters.
		$new_query_string = http_build_query( $query_params );

		// Reconstruct the URL.
		$new_url  = $parsed_url['scheme'] . '://' . $parsed_url['host'];
		$new_url .= isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
		$new_url .= $new_query_string ? '?' . $new_query_string : '';

		return $new_url;
	}
}
PK     \ݹ:  :  +  BlockTypes/ProductCollection/Controller.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
use WP_Query;

/**
 * Controller class.
 */
class Controller extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-collection';

	/**
	 * Instance of HandlerRegistry.
	 *
	 * @var HandlerRegistry
	 */
	protected $collection_handler_registry;

	/**
	 * Instance of QueryBuilder.
	 *
	 * @var QueryBuilder
	 */
	protected $query_builder;

	/**
	 * Instance of Renderer.
	 *
	 * @var Renderer
	 */
	protected $renderer;

	/**
	 * Initialize this block type.
	 *
	 * - Register hooks and filters.
	 * - Set up QueryBuilder, Renderer and HandlerRegistry.
	 */
	protected function initialize() {
		parent::initialize();

		$this->query_builder               = new QueryBuilder();
		$this->renderer                    = new Renderer();
		$this->collection_handler_registry = new HandlerRegistry();

		// Update query for frontend rendering.
		add_filter(
			'query_loop_block_query_vars',
			array( $this, 'build_frontend_query' ),
			10,
			3
		);

		add_filter(
			'pre_render_block',
			array( $this, 'add_support_for_filter_blocks' ),
			10,
			2
		);

		// Register the backend settings so they can be used in the editor.
		add_action( 'rest_api_init', array( $this, 'register_settings' ) );

		// Update the query for Editor.
		add_filter( 'rest_product_query', array( $this, 'update_rest_query_in_editor' ), 10, 2 );

		// Extend allowed `collection_params` for the REST API.
		add_filter( 'rest_product_collection_params', array( $this, 'extend_rest_query_allowed_params' ), 10, 1 );
		add_filter( 'render_block_core/post-title', array( $this, 'add_product_title_click_event_directives' ), 10, 3 );

		// Disable client-side-navigation if incompatible blocks are detected.
		add_filter( 'render_block_data', array( $this, 'disable_enhanced_pagination' ), 10, 1 );

		$this->register_core_collections_and_set_handler_store();
	}

	/**
	 * Add interactivity to the Product Title block within Product Collection.
	 * This enables the triggering of a custom event when the product title is clicked.
	 *
	 * @param string    $block_content The block content.
	 * @param array     $block         The full block, including name and attributes.
	 * @param \WP_Block $instance      The block instance.
	 * @return string   Modified block content with added interactivity.
	 */
	public function add_product_title_click_event_directives( $block_content, $block, $instance ) {
		$namespace              = $instance->attributes['__woocommerceNamespace'] ?? '';
		$is_product_title_block = 'woocommerce/product-collection/product-title' === $namespace;
		$is_link                = $instance->attributes['isLink'] ?? false;

		// Only proceed if the block is a Product Title (Post Title variation) block.
		if ( $is_product_title_block && $is_link ) {
			$p = new \WP_HTML_Tag_Processor( $block_content );
			$p->next_tag( array( 'class_name' => 'wp-block-post-title' ) );
			$is_anchor = $p->next_tag( array( 'tag_name' => 'a' ) );

			if ( $is_anchor ) {
				$p->set_attribute( 'data-wp-on--click', 'woocommerce/product-collection::actions.viewProduct' );

				$block_content = $p->get_updated_html();
			}
		}

		return $block_content;
	}

	/**
	 * Verifies if the inner block is compatible with Interactivity API.
	 *
	 * @param string $block_name Name of the block to verify.
	 * @return boolean
	 */
	private function is_block_compatible( $block_name ) {
		$block_type = \WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
		// Client side navigation can be true in two states:
		// - supports.interactivity === true;
		// - supports.interactivity.clientNavigation === true; .
		$supports_interactivity     = isset( $block_type->supports['interactivity'] ) && true === $block_type->supports['interactivity'];
		$supports_client_navigation = isset( $block_type->supports['interactivity']['clientNavigation'] ) && true === $block_type->supports['interactivity']['clientNavigation'];

		return $supports_interactivity || $supports_client_navigation;
	}

	/**
	 * Check inner blocks of Product Collection block if there's one
	 * incompatible with the Interactivity API and if so, disable client-side
	 * navigation.
	 *
	 * @param array $parsed_block The block being rendered.
	 * @return string Returns the parsed block, unmodified.
	 */
	public function disable_enhanced_pagination( $parsed_block ) {
		static $enhanced_query_stack               = array();
		static $dirty_enhanced_queries             = array();
		static $render_product_collection_callback = null;

		$block_name                  = $parsed_block['blockName'];
		$is_product_collection_block = $parsed_block['attrs']['query']['isProductCollectionBlock'] ?? false;
		$force_page_reload_global    =
			$parsed_block['attrs']['forcePageReload'] ?? false &&
			isset( $parsed_block['attrs']['queryId'] );

		if (
			$is_product_collection_block &&
			'woocommerce/product-collection' === $block_name &&
			! $force_page_reload_global &&
			isset( $parsed_block['attrs']['queryId'] )
		) {
			$enhanced_query_stack[] = $parsed_block['attrs']['queryId'];

			if ( ! isset( $render_product_collection_callback ) ) {
				/**
				 * Filter that disables the enhanced pagination feature during block
				 * rendering when a plugin block has been found inside. It does so
				 * by adding an attribute called `data-wp-navigation-disabled` which
				 * is later handled by the front-end logic.
				 *
				 * @param string   $content  The block content.
				 * @param array    $block    The full block, including name and attributes.
				 * @return string Returns the modified output of the query block.
				 */
				$render_product_collection_callback = static function ( $content, $block ) use ( &$enhanced_query_stack, &$dirty_enhanced_queries, &$render_product_collection_callback ) {
					$force_page_reload =
						$parsed_block['attrs']['forcePageReload'] ?? false &&
						isset( $block['attrs']['queryId'] );

					if ( $force_page_reload ) {
						return $content;
					}

					if ( isset( $block['attrs']['queryId'] ) && isset( $dirty_enhanced_queries[ $block['attrs']['queryId'] ] ) ) {
						wp_interactivity_config( 'core/router', array( 'clientNavigationDisabled' => true ) );
						$dirty_enhanced_queries[ $block['attrs']['queryId'] ] = null;
					}

					array_pop( $enhanced_query_stack );

					if ( empty( $enhanced_query_stack ) ) {
						remove_filter( 'render_block_woocommerce/product-collection', $render_product_collection_callback, 5 );
						$render_product_collection_callback = null;
					}

					return $content;
				};

				add_filter( 'render_block_woocommerce/product-collection', $render_product_collection_callback, 5, 2 );
			}
		} elseif (
			! empty( $enhanced_query_stack ) &&
			isset( $block_name ) &&
			! $this->is_block_compatible( $block_name )
		) {
			foreach ( $enhanced_query_stack as $query_id ) {
				$dirty_enhanced_queries[ $query_id ] = true;
			}
		}

		return $parsed_block;
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = array() ) {
		parent::enqueue_data( $attributes );

		// The `loop_shop_per_page` filter can be found in WC_Query::product_query().
		// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
		$this->asset_data_registry->add( 'loopShopPerPage', apply_filters( 'loop_shop_per_page', wc_get_default_products_per_row() * wc_get_default_product_rows_per_page() ) );
	}

	/**
	 * Exposes settings used by the Product Collection block when manipulating
	 * the default query.
	 */
	public function register_settings() {
		register_setting(
			'options',
			'woocommerce_default_catalog_orderby',
			array(
				'type'         => 'object',
				'description'  => __( 'How should products be sorted in the catalog by default?', 'woocommerce' ),
				'label'        => __( 'Default product sorting', 'woocommerce' ),
				'show_in_rest' => array(
					'name'   => 'woocommerce_default_catalog_orderby',
					'schema' => array(
						'type' => 'string',
						'enum' => array( 'menu_order', 'popularity', 'rating', 'date', 'price', 'price-desc' ),
					),
				),
				'default'      => 'menu_order',
			)
		);
	}

	/**
	 * Update the query for the product query block in Editor.
	 *
	 * @param array           $query   Query args.
	 * @param WP_REST_Request $request Request.
	 */
	public function update_rest_query_in_editor( $query, $request ): array {
		// Only update the query if this is a product collection block.
		$is_product_collection_block = $request->get_param( 'isProductCollectionBlock' );
		if ( ! $is_product_collection_block ) {
			return $query;
		}

		$product_collection_query_context = $request->get_param( 'productCollectionQueryContext' );
		$collection_args                  = array(
			'name'                      => $product_collection_query_context['collection'] ?? '',
			// The editor uses a REST query to grab product post types. This means we don't have a block
			// instance to work with and the client needs to provide the location context.
			'productCollectionLocation' => $request->get_param( 'productCollectionLocation' ),
		);

		// Allow collections to modify the collection arguments passed to the query builder.
		$handlers = $this->collection_handler_registry->get_collection_handler( $collection_args['name'] );
		if ( isset( $handlers['editor_args'] ) ) {
			$collection_args = call_user_func( $handlers['editor_args'], $collection_args, $query, $request );
		}

		$orderby = $request->get_param( 'orderby' );

		// When requested, short-circuit the query and return the preview query args.
		$preview_state = $request->get_param( 'previewState' );
		if ( isset( $preview_state['isPreview'] ) && 'true' === $preview_state['isPreview'] ) {
			return $this->query_builder->get_preview_query_args( $collection_args, array_merge( $query, array( 'orderby' => $orderby ) ), $request );
		}

		$on_sale                        = $request->get_param( 'woocommerceOnSale' ) === 'true';
		$stock_status                   = $request->get_param( 'woocommerceStockStatus' );
		$product_attributes             = $request->get_param( 'woocommerceAttributes' );
		$handpicked_products            = $request->get_param( 'woocommerceHandPickedProducts' );
		$featured                       = $request->get_param( 'featured' );
		$time_frame                     = $request->get_param( 'timeFrame' );
		$price_range                    = $request->get_param( 'priceRange' );
		$raw_tax_query_from_rest_params = $query['tax_query'] ?? array();

		// This argument is required for the tests to PHP Unit Tests to run correctly.
		// Most likely this argument is being accessed in the test environment image.
		$query['author'] = '';

		// Use QueryBuilder to get the final query args.
		return $this->query_builder->get_final_query_args(
			$collection_args,
			$query,
			array(
				'orderby'             => $orderby,
				'on_sale'             => $on_sale,
				'stock_status'        => $stock_status,
				'product_attributes'  => $product_attributes,
				'handpicked_products' => $handpicked_products,
				'featured'            => $featured,
				'timeFrame'           => $time_frame,
				'priceRange'          => $price_range,
				'taxonomies_query'    => $raw_tax_query_from_rest_params,
			)
		);
	}

	/**
	 * Add support for filter blocks:
	 * - Price filter block
	 * - Attributes filter block
	 * - Rating filter block
	 * - In stock filter block etc.
	 *
	 * @param array $pre_render   The pre-rendered block.
	 * @param array $parsed_block The parsed block.
	 */
	public function add_support_for_filter_blocks( $pre_render, $parsed_block ) {
		$is_product_collection_block = $parsed_block['attrs']['query']['isProductCollectionBlock'] ?? false;

		if ( ! $is_product_collection_block ) {
			return $pre_render;
		}

		$this->renderer->set_parsed_block( $parsed_block );
		$this->asset_data_registry->add( 'hasFilterableProducts', true );
		/**
		 * It enables the page to refresh when a filter is applied, ensuring that the product collection block,
		 * which is a server-side rendered (SSR) block, retrieves the products that match the filters.
		 */
		$this->asset_data_registry->add( 'isRenderingPhpTemplate', true );

		return $pre_render;
	}

	/**
	 * Return a custom query based on attributes, filters and global WP_Query.
	 *
	 * @param WP_Query $query The WordPress Query.
	 * @param WP_Block $block The block being rendered.
	 * @param int      $page  The page number.
	 *
	 * @return array
	 */
	public function build_frontend_query( $query, $block, $page ) {
		// If not in context of product collection block, return the query as is.
		$is_product_collection_block = $block->context['query']['isProductCollectionBlock'] ?? false;
		if ( ! $is_product_collection_block ) {
			return $query;
		}

		$block_context_query = $block->context['query'];

		// phpcs:ignore WordPress.DB.SlowDBQuery
		$block_context_query['tax_query'] = ! empty( $query['tax_query'] ) ? $query['tax_query'] : array();

		$inherit    = $block->context['query']['inherit'] ?? false;
		$filterable = $block->context['query']['filterable'] ?? false;

		$is_exclude_applied_filters = ! ( $inherit || $filterable );

		$collection_args = array(
			'name'                      => $block->context['collection'] ?? '',
			'productCollectionLocation' => $block->context['productCollectionLocation'] ?? null,
		);

		// Use QueryBuilder to construct the query.
		return $this->query_builder->get_final_frontend_query(
			$collection_args,
			$block_context_query,
			$page,
			$is_exclude_applied_filters
		);
	}

	/**
	 * Extends allowed `collection_params` for the REST API
	 *
	 * By itself, the REST API doesn't accept custom `orderby` values,
	 * even if they are supported by a custom post type.
	 *
	 * @param array $params  A list of allowed `orderby` values.
	 *
	 * @return array
	 */
	public function extend_rest_query_allowed_params( $params ) {
		$original_enum             = isset( $params['orderby']['enum'] ) ? $params['orderby']['enum'] : array();
		$params['orderby']['enum'] = array_unique( array_merge( $original_enum, $this->query_builder->get_custom_order_opts() ) );
		return $params;
	}

	/**
	 * Registers core collections and sets the handler store.
	 */
	protected function register_core_collections_and_set_handler_store() {
		// Use HandlerRegistry to register collections.
		$collection_handler_store = $this->collection_handler_registry->register_core_collections();
		$this->query_builder->set_collection_handler_store( $collection_handler_store );
	}
}
PK     \fAC  C  0  BlockTypes/ProductCollection/HandlerRegistry.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection;

use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
use InvalidArgumentException;

/**
 * HandlerRegistry class.
 * Manages collection handlers.
 */
class HandlerRegistry {

	/**
	 * Associative array of collection handlers.
	 *
	 * @var array
	 */
	protected $collection_handler_store = [];

	/**
	 * Register handlers for a collection.
	 *
	 * @param string        $collection_name The name of the collection.
	 * @param callable      $build_query     The query builder callable.
	 * @param callable|null $frontend_args   Optional frontend args callable.
	 * @param callable|null $editor_args     Optional editor args callable.
	 * @param callable|null $preview_query   Optional preview query callable.
	 *
	 * @throws InvalidArgumentException If handlers are already registered for the collection.
	 */
	public function register_collection_handlers( $collection_name, $build_query, $frontend_args = null, $editor_args = null, $preview_query = null ) {
		if ( isset( $this->collection_handler_store[ $collection_name ] ) ) {
			throw new InvalidArgumentException( 'Collection handlers already registered for ' . esc_html( $collection_name ) );
		}

		$this->collection_handler_store[ $collection_name ] = [
			'build_query'   => $build_query,
			'frontend_args' => $frontend_args,
			'editor_args'   => $editor_args,
			'preview_query' => $preview_query,
		];

		return $this->collection_handler_store[ $collection_name ];
	}

	/**
	 * Register core collection handlers.
	 */
	public function register_core_collections() {
		$this->register_collection_handlers(
			'woocommerce/product-collection/hand-picked',
			function ( $collection_args, $common_query_values, $query ) {
				// For Hand-Picked collection, if no products are selected, we should return an empty result set.
				// This ensures that the collection doesn't display any products until the user explicitly chooses them.
				if ( empty( $query['handpicked_products'] ) ) {
					return array(
						'post__in' => array( -1 ),
					);
				}
			}
		);

		$this->register_collection_handlers(
			'woocommerce/product-collection/by-category',
			function ( $collection_args, $common_query_values, $query ) {
				// For Products by Category collection, if no category is selected, we should return an empty result set.
				if ( empty( $query['taxonomies_query'] ) ) {
					return array(
						'post__in' => array( -1 ),
					);
				}
			}
		);

		$this->register_collection_handlers(
			'woocommerce/product-collection/by-tag',
			function ( $collection_args, $common_query_values, $query ) {
				// For Products by Tag collection, if no tag is selected, we should return an empty result set.
				if ( empty( $query['taxonomies_query'] ) ) {
					return array(
						'post__in' => array( -1 ),
					);
				}
			}
		);

		$this->register_collection_handlers(
			'woocommerce/product-collection/by-brand',
			function ( $collection_args, $common_query_values, $query ) {
				// For Products by Brand collection, if no brand is selected, we should return an empty result set.
				if ( empty( $query['taxonomies_query'] ) ) {
					return array(
						'post__in' => array( -1 ),
					);
				}
			}
		);

		$this->register_collection_handlers(
			'woocommerce/product-collection/related',
			function ( $collection_args ) {
				// No products should be shown if no related product reference is set.
				if ( empty( $collection_args['relatedProductReference'] ) ) {
					return array(
						'post__in' => array( -1 ),
					);
				}

				$category_callback = function () use ( $collection_args ) {
					return $collection_args['relatedBy']['categories'];
				};

				$tag_callback = function () use ( $collection_args ) {
					return $collection_args['relatedBy']['tags'];
				};

				add_filter( 'woocommerce_product_related_posts_relate_by_category', $category_callback, PHP_INT_MAX );
				add_filter( 'woocommerce_product_related_posts_relate_by_tag', $tag_callback, PHP_INT_MAX );

				$related_products = wc_get_related_products(
					$collection_args['relatedProductReference'],
					// Use a higher limit so that the result set contains enough products for the collection to subsequently filter.
					100,
					array(),
					$collection_args['relatedBy']
				);

				remove_filter( 'woocommerce_product_related_posts_relate_by_category', $category_callback, PHP_INT_MAX );
				remove_filter( 'woocommerce_product_related_posts_relate_by_tag', $tag_callback, PHP_INT_MAX );

				if ( empty( $related_products ) ) {
					return array(
						'post__in' => array( -1 ),
					);
				}

				// Have it filter the results to products related to the one provided.
				return array(
					'post__in' => $related_products,
				);
			},
			function ( $collection_args, $query ) {
				$product_reference = $query['productReference'] ?? null;
				// Infer the product reference from the location if an explicit product is not set.
				if ( empty( $product_reference ) ) {
					$location = $collection_args['productCollectionLocation'];
					if ( isset( $location['type'] ) && 'product' === $location['type'] ) {
						$product_reference = $location['sourceData']['productId'];
					}
				}

				$collection_args['relatedProductReference'] = $product_reference;
				$collection_args['relatedBy']               = ! isset( $query['relatedBy'] ) ? array(
					'categories' => true,
					'tags'       => true,
				) : array(
					'categories' => isset( $query['relatedBy']['categories'] ) && true === $query['relatedBy']['categories'],
					'tags'       => isset( $query['relatedBy']['tags'] ) && true === $query['relatedBy']['tags'],
				);

				return $collection_args;
			},
			function ( $collection_args, $query, $request ) {
				$product_reference = $request->get_param( 'productReference' );
				// In some cases the editor will send along block location context that we can infer the product reference from.
				if ( empty( $product_reference ) ) {
					$location = $collection_args['productCollectionLocation'];
					if ( isset( $location['type'] ) && 'product' === $location['type'] ) {
						$product_reference = $location['sourceData']['productId'];
					}
				}

				$collection_args['relatedProductReference'] = $product_reference;

				$related_by                   = $request->get_param( 'relatedBy' );
				$collection_args['relatedBy'] = ! isset( $related_by ) ? array(
					'categories' => true,
					'tags'       => true,
				) : array(
					'categories' => rest_sanitize_boolean( $related_by['categories'] ?? false ),
					'tags'       => rest_sanitize_boolean( $related_by['tags'] ?? false ),
				);

				return $collection_args;
			}
		);

		$this->register_collection_handlers(
			'woocommerce/product-collection/upsells',
			function ( $collection_args ) {
				$product_reference = $collection_args['upsellsProductReferences'] ?? null;
				// No products should be shown if no upsells product reference is set.
				if ( empty( $product_reference ) ) {
					return array(
						'post__in' => array( -1 ),
					);
				}

				$products = array_map( 'wc_get_product', $product_reference );

				if ( empty( $products ) ) {
					return array(
						'post__in' => array( -1 ),
					);
				}

				$all_upsells = array_reduce(
					$products,
					function ( $acc, $product ) {
						return array_merge(
							$acc,
							$product->get_upsell_ids()
						);
					},
					array()
				);

				// Remove duplicates and product references. We don't want to display
				// what's already in cart.
				$unique_upsells = array_unique( $all_upsells );
				$upsells        = array_diff( $unique_upsells, $product_reference );

				return array(
					'post__in' => empty( $upsells ) ? array( -1 ) : $upsells,
				);
			},
			function ( $collection_args, $query ) {
				$product_references = isset( $query['productReference'] ) ? array( $query['productReference'] ) : null;
				// Infer the product reference from the location if an explicit product is not set.
				if ( empty( $product_references ) ) {
					$location = $collection_args['productCollectionLocation'];
					if ( isset( $location['type'] ) && 'product' === $location['type'] ) {
						$product_references = array( $location['sourceData']['productId'] );
					}

					if ( isset( $location['type'] ) && 'cart' === $location['type'] ) {
						$product_references = $location['sourceData']['productIds'];
					}

					if ( isset( $location['type'] ) && 'order' === $location['type'] ) {
						$product_references = $this->get_product_ids_from_order( $location['sourceData']['orderId'] ?? 0 );
					}
				}

				$collection_args['upsellsProductReferences'] = $product_references;
				return $collection_args;
			},
			function ( $collection_args, $query, $request ) {
				$product_reference = $request->get_param( 'productReference' );
				// In some cases the editor will send along block location context that we can infer the product reference from.
				if ( empty( $product_reference ) ) {
					$location = $collection_args['productCollectionLocation'];
					if ( isset( $location['type'] ) && 'product' === $location['type'] ) {
						$product_reference = $location['sourceData']['productId'];
					}
				}

				$collection_args['upsellsProductReferences'] = array( $product_reference );
				return $collection_args;
			}
		);

		$this->register_collection_handlers(
			'woocommerce/product-collection/cross-sells',
			function ( $collection_args ) {
				$product_reference = $collection_args['crossSellsProductReferences'] ?? null;
				// No products should be shown if no cross-sells product reference is set.
				if ( empty( $product_reference ) ) {
					return array(
						'post__in' => array( -1 ),
					);
				}

				$products = array_filter( array_map( 'wc_get_product', $product_reference ) );

				if ( empty( $products ) ) {
					return array(
						'post__in' => array( -1 ),
					);
				}

				$product_ids = array_map(
					function ( $product ) {
						return $product->get_id();
					},
					$products
				);

				$all_cross_sells = array_reduce(
					$products,
					function ( $acc, $product ) {
						return array_merge(
							$acc,
							$product->get_cross_sell_ids()
						);
					},
					array()
				);

				// Remove duplicates and product references. We don't want to display
				// what's already in cart.
				$unique_cross_sells = array_unique( $all_cross_sells );
				$cross_sells        = array_diff( $unique_cross_sells, $product_ids );

				return array(
					'post__in' => empty( $cross_sells ) ? array( -1 ) : $cross_sells,
				);
			},
			function ( $collection_args, $query ) {
				$product_references = isset( $query['productReference'] ) ? array( $query['productReference'] ) : null;
				// Infer the product reference from the location if an explicit product is not set.
				if ( empty( $product_references ) ) {
					$location = $collection_args['productCollectionLocation'];
					if ( isset( $location['type'] ) && 'product' === $location['type'] ) {
						$product_references = array( $location['sourceData']['productId'] );
					}

					if ( isset( $location['type'] ) && 'cart' === $location['type'] ) {
						$product_references = $location['sourceData']['productIds'];
					}

					if ( isset( $location['type'] ) && 'order' === $location['type'] ) {
						$product_references = $this->get_product_ids_from_order( $location['sourceData']['orderId'] ?? 0 );
					}
				}

				$collection_args['crossSellsProductReferences'] = $product_references;
				return $collection_args;
			},
			function ( $collection_args, $query, $request ) {
				$product_reference = $request->get_param( 'productReference' );
				// In some cases the editor will send along block location context that we can infer the product reference from.
				if ( empty( $product_reference ) ) {
					$location = $collection_args['productCollectionLocation'];
					if ( isset( $location['type'] ) && 'product' === $location['type'] ) {
						$product_reference = $location['sourceData']['productId'];
					}
				}

				$collection_args['crossSellsProductReferences'] = array( $product_reference );
				return $collection_args;
			}
		);

		// Best-sellers and new-arrivals: register preview_query only.
		// These collections handle their main queries via JS/REST, but need
		// a preview fallback to show recent products in the email editor
		// when the store has no best-sellers or new arrivals yet.
		// The build_query callback is a no-op that returns an empty array
		// so merge_queries() has nothing extra to merge.
		$noop_build_query = function () {
			return array();
		};

		$this->register_collection_handlers(
			'woocommerce/product-collection/best-sellers',
			$noop_build_query,
			null,
			null,
			function () {
				return $this->get_recent_product_ids_query();
			}
		);

		$this->register_collection_handlers(
			'woocommerce/product-collection/new-arrivals',
			$noop_build_query,
			null,
			null,
			function () {
				return $this->get_recent_product_ids_query();
			}
		);

		$this->register_collection_handlers(
			'woocommerce/product-collection/cart-contents',
			function ( $collection_args ) {
				$cart_product_ids = $collection_args['cartProductIds'] ?? null;
				if ( empty( $cart_product_ids ) ) {
					return array( 'post__in' => array( -1 ) );
				}
				return array( 'post__in' => $cart_product_ids );
			},
			function ( $collection_args, $query ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
				$collection_args['cartProductIds'] = $this->get_cart_product_ids( $collection_args, null );
				return $collection_args;
			},
			function ( $collection_args, $query, $request ) {
				$collection_args['cartProductIds'] = $this->get_cart_product_ids( $collection_args, $request );
				return $collection_args;
			}
		);
		return $this->collection_handler_store;
	}

	/**
	 * Get collection handler by name.
	 *
	 * @param string $name Collection name.
	 * @return array|null Collection handler array or null if not found.
	 */
	public function get_collection_handler( $name ) {
		return $this->collection_handler_store[ $name ] ?? null;
	}

	/**
	 * Removes any custom collection handlers for the given collection.
	 *
	 * @param string $collection_name The name of the collection to unregister.
	 */
	public function unregister_collection_handlers( $collection_name ) {
		unset( $this->collection_handler_store[ $collection_name ] );
	}


	/**
	 * Get product IDs from an order.
	 *
	 * @param int $order_id The order ID.
	 * @return array<int> The product IDs.
	 */
	private function get_product_ids_from_order( $order_id ) {
		$product_references = array();
		if ( empty( $order_id ) ) {
			return $product_references;
		}

		$order = wc_get_order( $order_id );
		if ( $order ) {
			$product_references = array_filter(
				array_map(
					function ( $item ) {
						return $item->get_product_id();
					},
					$order->get_items( 'line_item' )
				)
			);
		}
		return $product_references;
	}

	/**
	 * Get a query that returns the most recent published products.
	 * Used as a fallback for preview mode when the specific collection query
	 * might return no results (e.g., no best sellers yet in a new store).
	 *
	 * @return array Query args to show recent products.
	 */
	private function get_recent_product_ids_query() {
		$recent_product_ids = wc_get_products(
			array(
				'status'  => 'publish',
				'orderby' => 'date',
				'order'   => 'DESC',
				'limit'   => 10,
				'return'  => 'ids',
			)
		);

		return array(
			'post__in' => ! empty( $recent_product_ids ) ? $recent_product_ids : array( -1 ),
		);
	}

	/**
	 * Get cart product IDs from various sources.
	 * Handles loading cart products from location context or request params.
	 *
	 * @param array                 $collection_args Collection arguments with location context.
	 * @param \WP_REST_Request|null $request         Optional REST request for editor context.
	 * @return array<int> The product IDs from the cart. Returns recent products for preview in editor context only.
	 */
	private function get_cart_product_ids( $collection_args, $request = null ) {
		$location = $collection_args['productCollectionLocation'] ?? array();

		if ( $request ) {
			// In editor context (REST request), show sample products for preview. Only emails to the customer show live data.
			$recent_product_ids = wc_get_products(
				array(
					'status'  => 'publish',
					'orderby' => 'date',
					'order'   => 'DESC',
					'limit'   => 3,
					'return'  => 'ids',
				)
			);
			return ! empty( $recent_product_ids ) ? $recent_product_ids : array();
		}

		if ( isset( $location['type'] ) && 'cart' === $location['type'] ) {
			$user_id    = isset( $location['sourceData']['userId'] ) ? absint( $location['sourceData']['userId'] ) : null;
			$user_email = isset( $location['sourceData']['userEmail'] ) ? sanitize_email( $location['sourceData']['userEmail'] ) : null;
			if ( $user_id || $user_email ) {
				return CartCheckoutUtils::get_cart_product_ids_for_user( $user_id, $user_email );
			}
		}

		// In frontend/email context, return empty array when no cart is found.
		return array();
	}
}
PK     \1oN3  3  )  BlockTypes/ProductCollection/Renderer.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection;

use Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection\Utils as ProductCollectionUtils;
use WP_HTML_Tag_Processor;

/**
 * Renderer class.
 * Handles rendering of the block and adds interactivity.
 */
class Renderer {

	/**
	 * The render state of the product collection block.
	 *
	 * @var array
	 */
	private $render_state = array(
		'has_results'          => false,
		'has_no_results_block' => false,
	);

	/**
	 * The Block with its attributes before it gets rendered
	 *
	 * @var array
	 */
	protected $parsed_block;

	/**
	 * Constructor.
	 */
	public function __construct() {
		// Interactivity API: Add navigation directives to the product collection block.
		add_filter( 'render_block_woocommerce/product-collection', array( $this, 'handle_rendering' ), 10, 2 );

		// Disable block render if the ProductTemplate block is empty.
		add_filter(
			'render_block_woocommerce/product-template',
			function ( $html ) {
				$this->render_state['has_results'] = ! empty( $html );
				return $html;
			},
			100,
			1
		);

		// Enable block render if the NoResults block is rendered.
		add_filter(
			'render_block_woocommerce/product-collection-no-results',
			function ( $html ) {
				$this->render_state['has_no_results_block'] = ! empty( $html );
				return $html;
			},
			100,
			1
		);
		add_filter( 'render_block_core/query-pagination', array( $this, 'add_navigation_link_directives' ), 10, 3 );
		add_filter( 'render_block_context', array( $this, 'extend_context_for_inner_blocks' ), 11, 1 );
	}

	/**
	 * Set the parsed block.
	 *
	 * @param array $block The block to be parsed.
	 */
	public function set_parsed_block( $block ) {
		$this->parsed_block = $block;
	}

	/**
	 * Handle the rendering of the block.
	 *
	 * @param string $block_content The block content about to be rendered.
	 * @param array  $block The block being rendered.
	 *
	 * @return string
	 */
	public function handle_rendering( $block_content, $block ) {
		if ( $this->should_prevent_render() ) {
			return ''; // Prevent rendering.
		}

		// Reset the render state for the next render.
		$this->reset_render_state();

		return $this->enhance_product_collection_with_interactivity( $block_content, $block );
	}

	/**
	 * Check if the block should be prevented from rendering.
	 *
	 * @return bool
	 */
	private function should_prevent_render() {
		return ! $this->render_state['has_results'] && ! $this->render_state['has_no_results_block'];
	}

	/**
	 * Reset the render state.
	 */
	private function reset_render_state() {
		$this->render_state = array(
			'has_results'          => false,
			'has_no_results_block' => false,
		);
	}

	/**
	 * Enhances the Product Collection block with client-side pagination.
	 *
	 * This function identifies Product Collection blocks and adds necessary data attributes
	 * to enable client-side navigation. It also enqueues the Interactivity API runtime.
	 *
	 * @param string $block_content The HTML content of the block.
	 * @param array  $block         Block details, including its attributes.
	 *
	 * @return string Updated block content with added interactivity attributes.
	 */
	public function enhance_product_collection_with_interactivity( $block_content, $block ) {
		$is_product_collection_block = $block['attrs']['query']['isProductCollectionBlock'] ?? false;

		if ( $is_product_collection_block ) {
			wp_enqueue_script_module( 'woocommerce/product-collection' );

			$collection                     = $block['attrs']['collection'] ?? '';
			$is_enhanced_pagination_enabled = ! ( $block['attrs']['forcePageReload'] ?? false );
			$context                        = array(
				'notices'                 => array(),
				// Next/Previous Buttons block context.
				'hideNextPreviousButtons' => false,
				'isDisabledPrevious'      => true,
				'isDisabledNext'          => false,
				'ariaLabelPrevious'       => __( 'Previous products', 'woocommerce' ),
				'ariaLabelNext'           => __( 'Next products', 'woocommerce' ),
			);

			if ( $collection ) {
				$context['collection'] = $collection;
			}

			$p = new \WP_HTML_Tag_Processor( $block_content );
			if ( $p->next_tag( array( 'class_name' => 'wp-block-woocommerce-product-collection' ) ) ) {
				$p->set_attribute( 'data-wp-interactive', 'woocommerce/product-collection' );
				$p->set_attribute( 'data-wp-init', 'callbacks.onRender' );
				$p->set_attribute( 'data-wp-context', wp_json_encode( $context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) );

				if ( $is_enhanced_pagination_enabled && isset( $this->parsed_block ) ) {
					$p->set_attribute(
						'data-wp-router-region',
						'wc-product-collection-' . $this->parsed_block['attrs']['queryId']
					);
				}
			}

			// Check if dimensions need to be set and handle accordingly.
			$this->handle_block_dimensions( $p, $block );

			$block_content = $p->get_updated_html();
			$block_content = $this->add_store_notices_fallback( $block_content );
		}

		return $block_content;
	}

	/**
	 * Add a fallback store notices div to the block content.
	 *
	 * @param string $block_content The block content.
	 * @return string The updated block content.
	 */
	private function add_store_notices_fallback( $block_content ) {
		return preg_replace( '/(<div[^>]+>)/', '$1' . $this->render_interactivity_notices_region(), $block_content, 1 );
	}

	/**
	 * Render interactivity API powered notices that can be added client-side. This reuses classes
	 * from the woocommerce/store-notices block to ensure style consistency.
	 *
	 * @return string The rendered store notices HTML.
	 */
	protected function render_interactivity_notices_region() {
		wp_interactivity_state(
			'woocommerce/store-notices',
			array(
				'notices' => array(),
			)
		);

		ob_start();
		?>
		<div data-wp-interactive="woocommerce/store-notices" class="wc-block-components-notices alignwide">
			<template data-wp-each--notice="state.notices" data-wp-each-key="context.notice.id">
				<div
					class="wc-block-components-notice-banner"
					data-wp-init="callbacks.scrollIntoView"
					data-wp-class--is-error="state.isError"
					data-wp-class--is-success="state.isSuccess"
					data-wp-class--is-info="state.isInfo"
					data-wp-class--is-dismissible="context.notice.dismissible"
					data-wp-bind--role="state.role"
					data-wp-watch="callbacks.injectIcon"
				>
					<div class="wc-block-components-notice-banner__content">
						<span data-wp-init="callbacks.renderNoticeContent" aria-live="assertive" aria-atomic="true"></span>
					</div>
					<button
						data-wp-bind--hidden="!context.notice.dismissible"
						class="wc-block-components-button wp-element-button wc-block-components-notice-banner__dismiss contained"
						aria-label="<?php esc_attr_e( 'Dismiss this notice', 'woocommerce' ); ?>"
						data-wp-on--click="actions.removeNotice"
					>
						<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
							<path d="M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z" />
						</svg>
					</button>
				</div>
			</template>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Get the styles for the list element (fixed width).
	 *
	 * @param string $fixed_width Fixed width value.
	 * @return string
	 */
	protected function get_list_styles( $fixed_width ) {
		$style = '';

		if ( isset( $fixed_width ) && ! empty( $fixed_width ) ) {
			$style .= sprintf( 'width:%s;', esc_attr( $fixed_width ) );
			$style .= 'margin: 0 auto;';
		}
		return $style;
	}

	/**
	 * Set the style attribute for fixed width.
	 *
	 * @param WP_HTML_Tag_Processor $p          The HTML tag processor.
	 * @param string                $fixed_width The fixed width value.
	 */
	private function set_fixed_width_style( $p, $fixed_width ) {
		$p->set_attribute( 'style', $this->get_list_styles( $fixed_width ) );
	}

	/**
	 * Handle block dimensions if width type is set to 'fixed'.
	 *
	 * @param WP_HTML_Tag_Processor $p     The HTML tag processor.
	 * @param array                 $block The block details.
	 */
	private function handle_block_dimensions( $p, $block ) {
		if ( isset( $block['attrs']['dimensions'] ) && isset( $block['attrs']['dimensions']['widthType'] ) ) {
			if ( 'fixed' === $block['attrs']['dimensions']['widthType'] ) {
				$this->set_fixed_width_style( $p, $block['attrs']['dimensions']['fixedWidth'] );
			}
		}
	}

	/**
	 * Add interactive links to all anchors inside the Query Pagination block.
	 * This enabled client-side navigation for the product collection block.
	 *
	 * @param string    $block_content The block content.
	 * @param array     $block         The full block, including name and attributes.
	 * @param \WP_Block $instance      The block instance.
	 */
	public function add_navigation_link_directives( $block_content, $block, $instance ) {
		$query_context                  = $instance->context['query'] ?? array();
		$is_product_collection_block    = $query_context['isProductCollectionBlock'] ?? false;
		$query_id                       = $instance->context['queryId'] ?? null;
		$parsed_query_id                = $this->parsed_block['attrs']['queryId'] ?? null;
		$is_enhanced_pagination_enabled = ! ( $this->parsed_block['attrs']['forcePageReload'] ?? false );

		// Only proceed if the block is a product collection block,
		// enhanced pagination is enabled and query IDs match.
		if ( $is_product_collection_block && $is_enhanced_pagination_enabled && $query_id === $parsed_query_id ) {
			$p = new \WP_HTML_Tag_Processor( $block_content );
			$p->next_tag( array( 'class_name' => 'wp-block-query-pagination' ) );

			while ( $p->next_tag( 'A' ) ) {
				if ( $p->has_class( 'wp-block-query-pagination-next' ) || $p->has_class( 'wp-block-query-pagination-previous' ) ) {
					$p->set_attribute( 'data-wp-on--click', 'woocommerce/product-collection::actions.navigate' );
					$p->set_attribute(
						'data-wp-key',
						$p->has_class( 'wp-block-query-pagination-next' )
							? 'product-collection-pagination--next'
							: 'product-collection-pagination--previous'
					);
					$p->set_attribute( 'data-wp-watch', 'woocommerce/product-collection::callbacks.prefetch' );
					$p->set_attribute( 'data-wp-on--mouseenter', 'woocommerce/product-collection::actions.prefetchOnHover' );
				} elseif ( $p->has_class( 'page-numbers' ) ) {
					$p->set_attribute( 'data-wp-on--click', 'woocommerce/product-collection::actions.navigate' );
					$p->set_attribute( 'data-wp-key', 'product-collection-pagination-numbers--' . $p->get_attribute( 'aria-label' ) );
				}
			}

			return $p->get_updated_html();
		}

		return $block_content;
	}

	/**
	 * Provides the location context to each inner block of the product collection block.
	 * Hint: Only blocks using the 'query' context will be affected.
	 *
	 * The sourceData structure depends on the context type as follows:
	 * - site:    [ ]
	 * - order:   [ 'orderId'    => int ]
	 * - cart:    [ 'productIds' => int[] ]
	 * - archive: [ 'taxonomy'   => string, 'termId' => int ]
	 * - product: [ 'productId'  => int ]
	 *
	 * @example array(
	 *   'type'       => 'product',
	 *   'sourceData' => array( 'productId' => 123 ),
	 * )
	 *
	 * @param array $context  The block context.
	 * @return array $context {
	 *     The block context including the product collection location context.
	 *
	 *     @type array $productCollectionLocation {
	 *         @type string  $type        The context type. Possible values are 'site', 'order', 'cart', 'archive', 'product'.
	 *         @type array   $sourceData  The context source data. Can be the product ID of the viewed product, the order ID of the current order viewed, etc. See structure above for more details.
	 *     }
	 * }
	 */
	public function extend_context_for_inner_blocks( $context ) {
		// Run only on frontend.
		// This is needed to avoid SSR renders while in editor. @see https://github.com/woocommerce/woocommerce/issues/45181.
		if ( is_admin() || \WC()->is_rest_api_request() ) {
			return $context;
		}

		// Add iapi/provider to inner blocks so they can run this store's Interactivity API actions.
		$context['iapi/provider'] = 'woocommerce/product-collection';

		// Target only product collection's inner blocks that use the 'query' context.
		if ( ! isset( $context['query'] ) || ! isset( $context['query']['isProductCollectionBlock'] ) || ! $context['query']['isProductCollectionBlock'] ) {
			return $context;
		}

		$is_in_single_product                 = isset( $context['singleProduct'] ) && ! empty( $context['postId'] );
		$context['productCollectionLocation'] = $is_in_single_product ? array(
			'type'       => 'product',
			'sourceData' => array(
				'productId' => absint( $context['postId'] ),
			),
		) : $this->get_location_context();

		return $context;
	}

	/**
	 * Get the global location context.
	 * Serve as a runtime cache for the location context.
	 *
	 * @see ProductCollectionUtils::parse_frontend_location_context()
	 *
	 * @return array The location context.
	 */
	private function get_location_context() {
		static $location_context = null;
		if ( null === $location_context ) {
			$location_context = ProductCollectionUtils::parse_frontend_location_context();
		}
		return $location_context;
	}
}
PK     \~l$  $  &  BlockTypes/ProductCollection/Utils.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection;

use WP_Query;
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;

/**
 * Utility methods used for the Product Collection block.
 * {@internal This class and its methods are not intended for public use.}
 */
class Utils {

	/**
	 * Prepare and execute a query for the Product Collection block.
	 * This method is used by the Product Collection block and the No Results block.
	 *
	 * @param WP_Block $block Block instance.
	 */
	public static function prepare_and_execute_query( $block ) {
		$page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page';
		// phpcs:ignore WordPress.Security.NonceVerification
		$page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ];

		// Use global query if needed.
		$use_global_query = ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] );
		if ( $use_global_query ) {
			global $wp_query;
			$query = clone $wp_query;
		} else {
			$query_args = build_query_vars_from_query_block( $block, $page );
			$query      = new WP_Query( $query_args );
		}

		return $query;
	}

	/**
	 * Helper function that constructs a WP_Query args array from
	 * a Product Collection or global query.
	 *
	 * @param WP_Block $block Block instance.
	 * @param int      $page  Current query's page.
	 *
	 * @return array Returns the constructed WP_Query arguments.
	 */
	public static function get_query_vars( $block, $page ) {
		if ( ! empty( $block->context['query'] ) && ! $block->context['query']['inherit'] ) {
			return build_query_vars_from_query_block( $block, $page );
		}

		global $wp_query;
		return array_filter( $wp_query->query_vars );
	}

	/**
	 * Remove query array from tax or meta query by searching for arrays that
	 * contain exact key => value pair.
	 *
	 * @param array  $queries tax_query or meta_query.
	 * @param string $key     Array key to search for.
	 * @param mixed  $value   Value to compare with search result.
	 *
	 * @return array
	 */
	public static function remove_query_array( $queries, $key, $value ) {
		if ( ! is_array( $queries ) || empty( $queries ) ) {
			return $queries;
		}

		foreach ( $queries as $query_key => $query ) {
			if ( isset( $query[ $key ] ) && $query[ $key ] === $value ) {
				unset( $queries[ $query_key ] );
			}

			if ( isset( $query['relation'] ) || ! isset( $query[ $key ] ) ) {
				$queries[ $query_key ] = self::remove_query_array( $query, $key, $value );
			}
		}

		return self::remove_empty_array_recursive( $queries );
	}

	/**
	 * Parse WP Query's front-end context for the Product Collection block.
	 *
	 * The sourceData structure depends on the context type as follows:
	 * - site:    [ ]
	 * - order:   [ 'orderId'    => int ]
	 * - cart:    [ 'productIds' => int[] ]
	 * - archive: [ 'taxonomy'   => string, 'termId' => int ]
	 * - product: [ 'productId'  => int ]
	 *
	 * @return array $context {
	 *     @type string  $type        The context type. Possible values are 'site', 'order', 'cart', 'archive', 'product'.
	 *     @type array   $sourceData  The context source data. Can be the product ID of the viewed product, the order ID of the current order, etc.
	 * }
	 */
	public static function parse_frontend_location_context() {
		global $wp_query;

		// Default context.
		// Hint: The Shop page uses the default context.
		$type        = 'site';
		$source_data = array();

		if ( ! ( $wp_query instanceof WP_Query ) ) {

			return array(
				'type'       => $type,
				'sourceData' => $source_data,
			);
		}

		// As more areas are blockified, expected future contexts include:
		// - is_checkout_pay_page().
		// - is_view_order_page().
		if ( is_order_received_page() ) {

			$type        = 'order';
			$source_data = array( 'orderId' => absint( $wp_query->query_vars['order-received'] ) );

		} else {
			// Check if we're in a cart block context.
			$current_page       = $wp_query->get_queried_object();
			$has_cart_block     = $current_page && \WC_Blocks_Utils::has_block_in_page( $current_page, 'woocommerce/cart' );
			$has_checkout_block = $current_page && \WC_Blocks_Utils::has_block_in_page( $current_page, 'woocommerce/checkout' );
			$is_cart_available  = isset( WC()->cart ) && is_a( WC()->cart, 'WC_Cart' );

			if ( ( $has_cart_block || $has_checkout_block || is_cart() || is_checkout() ) && $is_cart_available ) {
				$type  = 'cart';
				$items = array();
				foreach ( WC()->cart->get_cart() as $cart_item ) {
					if ( ! isset( $cart_item['product_id'] ) ) {
						continue;
					}

					$items[] = absint( $cart_item['product_id'] );
				}
				$items       = array_unique( array_filter( $items ) );
				$source_data = array( 'productIds' => $items );

			} elseif ( is_product_taxonomy() ) {

				$source      = $wp_query->get_queried_object();
				$is_valid    = is_a( $source, 'WP_Term' );
				$taxonomy    = $is_valid ? $source->taxonomy : '';
				$term_id     = $is_valid ? $source->term_id : '';
				$type        = 'archive';
				$source_data = array(
					'taxonomy' => wc_clean( $taxonomy ),
					'termId'   => absint( $term_id ),
				);

			} elseif ( is_product() ) {

				$source      = $wp_query->get_queried_object();
				$product_id  = is_a( $source, 'WP_Post' ) ? absint( $source->ID ) : 0;
				$type        = 'product';
				$source_data = array( 'productId' => $product_id );
			}
		}

		$context = array(
			'type'       => $type,
			'sourceData' => $source_data,
		);

		return $context;
	}

	/**
	 * Remove falsy item from array, recursively.
	 *
	 * @param array $array The input array to filter.
	 */
	private static function remove_empty_array_recursive( $array ) {
		$array = array_filter( $array );
		foreach ( $array as $key => $item ) {
			if ( is_array( $item ) ) {
				$array[ $key ] = self::remove_empty_array_recursive( $item );
			}
		}
		return $array;
	}
}
PK     \?P(  (  1  BlockTypes/CheckoutAdditionalInformationBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutAdditionalInformationBlock class.
 */
class CheckoutAdditionalInformationBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-additional-information-block';
}
PK     \sv      BlockTypes/ProductMeta.phpnu [        <?php

declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ProductMeta class.
 */
class ProductMeta extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-meta';

	/**
	 * Get the editor script data for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 * @return null
	 */
	protected function get_block_type_editor_script( $key = null ) {
		return null;
	}

	/**
	 * Get the editor style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_editor_style() {
		return null;
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 * @return null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \x\%  %    BlockTypes/AllReviews.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * AllReviews class.
 */
class AllReviews extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'all-reviews';

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string
	 */
	protected function get_block_type_script( $key = null ) {
		$script = [
			'handle'       => 'wc-reviews-block-frontend',
			'path'         => $this->asset_api->get_block_asset_build_path( 'reviews-frontend' ),
			'dependencies' => [],
		];
		return $key ? $script[ $key ] : $script;
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );
		$this->asset_data_registry->add( 'reviewRatingsEnabled', wc_review_ratings_enabled() );
		$this->asset_data_registry->add( 'showAvatars', '1' === get_option( 'show_avatars' ) );
	}
}
PK     \uV  V    BlockTypes/ComingSoon.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\Jetpack\Constants;

/**
 * ComingSoon class.
 */
class ComingSoon extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'coming-soon';

	/**
	 * It is necessary to register and enqueue assets during the render phase because we want to load assets only if the block has the content.
	 */
	protected function register_block_type_assets() {
			parent::register_block_type_assets();
			$this->register_chunk_translations( [ $this->block_name ] );
	}

	/**
	 * Initialize.
	 */
	public function initialize() {
		parent::initialize();
		add_filter( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ), 10, 2 );
	}

	/**
	 * Enqueue frontend assets for this block, just in time for rendering.
	 *
	 * @internal This prevents the block script being enqueued on all pages. It is only enqueued as needed. Note that
	 * we intentionally do not pass 'script' to register_block_type.
	 *
	 * @param array    $attributes  Any attributes that currently are available from the block.
	 * @param string   $content    The block content.
	 * @param WP_Block $block    The block object.
	 */
	protected function enqueue_assets( array $attributes, $content, $block ) {
		parent::enqueue_assets( $attributes, $content, $block );

		if ( isset( $attributes['style']['color']['background'] ) ) {
			wp_add_inline_style(
				'wc-blocks-style',
				':root{--woocommerce-coming-soon-color: ' . esc_html( $attributes['style']['color']['background'] ) . '}'
			);
		} elseif ( isset( $attributes['color'] ) ) {
			// Deprecated: To support coming soon templates created before WooCommerce 9.8.0.
			wp_add_inline_style(
				'wc-blocks-style',
				':root{--woocommerce-coming-soon-color: ' . esc_html( $attributes['color'] ) . '}'
			);
			wp_enqueue_style(
				'woocommerce-coming-soon',
				WC()->plugin_url() . '/assets/css/coming-soon-entire-site-deprecated' . ( is_rtl() ? '-rtl' : '' ) . '.css',
				array(),
				Constants::get_constant( 'WC_VERSION' )
			);
		}
	}

	/**
	 * Enqueue coming soon deprecated styles in site editor to support
	 * coming soon templates created before WooCommerce 9.8.0.
	 */
	public function enqueue_block_assets() {
		if ( ! is_admin() ) {
			return;
		}

		$current_screen = get_current_screen();
		if ( $current_screen instanceof \WP_Screen && 'site-editor' !== $current_screen->base ) {
			return;
		}

		$post_id = isset( $_REQUEST['postId'] ) ? wc_clean( wp_unslash( $_REQUEST['postId'] ) ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( 'woocommerce/woocommerce//coming-soon' !== $post_id ) {
			return;
		}

		$block_template = get_block_template( $post_id );
		if ( $block_template ) {
			$parsed_blocks = parse_blocks( $block_template->content );
			foreach ( $parsed_blocks as $block ) {
				if ( isset( $block['blockName'] ) && 'woocommerce/coming-soon' === $block['blockName'] ) {
					// Color attribute is deprecated in WooCommerce 9.8.0.
					if ( isset( $block['attrs']['color'] ) && ! empty( $block['attrs']['color'] ) ) {
						wp_enqueue_style(
							'woocommerce-coming-soon',
							WC()->plugin_url() . '/assets/css/coming-soon-entire-site-deprecated' . ( is_rtl() ? '-rtl' : '' ) . '.css',
							array(),
							Constants::get_constant( 'WC_VERSION' )
						);
						break;
					}
				}
			}
		}
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}
}
PK     \3{    *  BlockTypes/CartCrossSellsProductsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartCrossSellsProductsBlock class.
 */
class CartCrossSellsProductsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-cross-sells-products-block';
}
PK     \    !  BlockTypes/ProductFilterPrice.phpnu [        <?php

declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection\Utils as ProductCollectionUtils;
use Automattic\WooCommerce\Internal\ProductFilters\FilterDataProvider;
use Automattic\WooCommerce\Internal\ProductFilters\QueryClauses;

/**
 * Product Filter: Price Block.
 */
final class ProductFilterPrice extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-filter-price';

	const MIN_PRICE_QUERY_VAR = 'min_price';
	const MAX_PRICE_QUERY_VAR = 'max_price';

	/**
	 * Initialize this block type.
	 *
	 * - Hook into WP lifecycle.
	 * - Register the block with WordPress.
	 */
	protected function initialize() {
		parent::initialize();

		add_filter( 'woocommerce_blocks_product_filters_selected_items', array( $this, 'prepare_selected_filters' ), 10, 2 );
	}

	/**
	 * Prepare the active filter items.
	 *
	 * @param array $items  The active filter items.
	 * @param array $params The query param parsed from the URL.
	 * @return array Active filters items.
	 */
	public function prepare_selected_filters( $items, $params ) {
		$min_price           = intval( $params[ self::MIN_PRICE_QUERY_VAR ] ?? 0 );
		$max_price           = intval( $params[ self::MAX_PRICE_QUERY_VAR ] ?? 0 );
		$formatted_min_price = $min_price ? html_entity_decode( wp_strip_all_tags( wc_price( $min_price, array( 'decimals' => 0 ) ) ) ) : null;
		$formatted_max_price = $max_price ? html_entity_decode( wp_strip_all_tags( wc_price( $max_price, array( 'decimals' => 0 ) ) ) ) : null;

		if ( ! $formatted_min_price && ! $formatted_max_price ) {
			return $items;
		}

		$item = array(
			'type' => 'price',
		);

		if ( $formatted_min_price && $formatted_max_price ) {
			$item['activeLabel'] = sprintf(
				/* translators: %1$s and %2$s are the formatted minimum and maximum prices respectively. */
				__( 'Price: %1$s - %2$s', 'woocommerce' ),
				$formatted_min_price,
				$formatted_max_price
			);
			$item['value'] = "{$min_price}|{$max_price}";
		}

		if ( ! $formatted_min_price ) {
			/* translators: %s is the formatted maximum price. */
			$item['activeLabel'] = sprintf( __( 'Price: Up to %s', 'woocommerce' ), $formatted_max_price );
			$item['value']       = "|{$max_price}";
		}

		if ( ! $formatted_max_price ) {
			/* translators: %s is the formatted minimum price. */
			$item['activeLabel'] = sprintf( __( 'Price: From %s', 'woocommerce' ), $formatted_min_price );
			$item['value']       = "{$min_price}|";
		}

		$items[] = $item;

		return $items;
	}



	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		// don't render if its admin, or ajax in progress.
		if ( is_admin() || wp_doing_ajax() ) {
			return '';
		}

		$price_range   = $this->get_filtered_price( $block );
		$min_range     = $price_range['min_price'] ?? 0;
		$max_range     = $price_range['max_price'] ?? 0;
		$filter_params = $block->context['filterParams'] ?? array();
		$min_price     = intval( $filter_params[ self::MIN_PRICE_QUERY_VAR ] ?? $min_range );
		$max_price     = intval( $filter_params[ self::MAX_PRICE_QUERY_VAR ] ?? $max_range );

		$formatted_min_price = html_entity_decode( wp_strip_all_tags( wc_price( $min_price, array( 'decimals' => 0 ) ) ) );
		$formatted_max_price = html_entity_decode( wp_strip_all_tags( wc_price( $max_price, array( 'decimals' => 0 ) ) ) );

		$filter_context = array(
			'price'      => array(
				'minPrice' => $min_price,
				'maxPrice' => $max_price,
				'minRange' => $min_range,
				'maxRange' => $max_range,
			),
			'groupLabel' => __( 'Price', 'woocommerce' ),
		);

		$wrapper_attributes = array(
			'data-wp-interactive' => 'woocommerce/product-filters',
			'data-wp-key'         => wp_unique_prefixed_id( $this->get_full_block_name() ),
			'data-wp-context'     => wp_json_encode(
				array(
					'filterType' => 'price',
					'minRange'   => $min_range,
					'maxRange'   => $max_range,
				),
				JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP,
			),
		);

		wp_interactivity_config(
			'woocommerce/product-filters',
			array(
				'activePriceLabelTemplates' => array(
					/* translators: {{min}} and {{max}} are the formatted minimum and maximum prices respectively. */
					'minAndMax' => __( 'Price: {{min}} - {{max}}', 'woocommerce' ),
					/* translators: {{max}} is the formatted maximum price. */
					'maxOnly'   => __( 'Price: Up to {{max}}', 'woocommerce' ),
					/* translators: {{min}} is the formatted minimum price. */
					'minOnly'   => __( 'Price: From {{min}}', 'woocommerce' ),
				),
			)
		);

		wp_interactivity_state(
			'woocommerce/product-filters',
			array(
				'formattedMinPrice' => $formatted_min_price,
				'formattedMaxPrice' => $formatted_max_price,
				'minPrice'          => $min_price,
				'maxPrice'          => $max_price,
			)
		);

		if ( $min_range === $max_range || ! $max_range ) {
			$wrapper_attributes['hidden'] = true;
			$wrapper_attributes['class']  = 'wc-block-product-filter--hidden';
			return sprintf(
				'<div %1$s>%2$s</div>',
				get_block_wrapper_attributes( $wrapper_attributes ),
				array_reduce(
					$block->parsed_block['innerBlocks'],
					function ( $carry, $parsed_block ) {
						$carry .= render_block( $parsed_block );
						return $carry;
					},
					''
				)
			);
		}

		return sprintf(
			'<div %1$s>%2$s</div>',
			get_block_wrapper_attributes( $wrapper_attributes ),
			array_reduce(
				$block->parsed_block['innerBlocks'],
				function ( $carry, $parsed_block ) use ( $filter_context ) {
					$carry .= ( new \WP_Block( $parsed_block, array( 'filterData' => $filter_context ) ) )->render();
					return $carry;
				},
				''
			)
		);
	}

	/**
	 * Retrieve the price filter data for current block.
	 *
	 * @param WP_Block $block Block instance.
	 */
	private function get_filtered_price( $block ) {
		if ( ! isset( $block->context['filterParams'] ) ) {
			return array();
		}

		$query_vars = ProductCollectionUtils::get_query_vars( $block, 1 );

		unset( $query_vars['min_price'], $query_vars['max_price'] );

		if ( ! empty( $query_vars['meta_query'] ) ) {
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
			$query_vars['meta_query'] = ProductCollectionUtils::remove_query_array( $query_vars['meta_query'], 'key', '_price' );
		}

		if ( isset( $query_vars['taxonomy'] ) && false !== strpos( $query_vars['taxonomy'], 'pa_' ) ) {
			unset(
				$query_vars['taxonomy'],
				$query_vars['term']
			);
		}

		$container     = wc_get_container();
		$price_results = $container->get( FilterDataProvider::class )->with( $container->get( QueryClauses::class ) )->get_filtered_price( $query_vars );

		return array(
			'min_price' => intval( floor( floatval( $price_results['min_price'] ?? 0 ) ) ),
			'max_price' => intval( ceil( floatval( $price_results['max_price'] ?? 0 ) ) ),
		);
	}
}
PK     \&    '  BlockTypes/ProductFilterClearButton.phpnu [        <?php

declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * Product Filter: Clear Button Block.
 */
final class ProductFilterClearButton extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-filter-clear-button';

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 *
	 * @return null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		// don't render if its admin, or ajax in progress.
		if (
			is_admin() ||
			wp_doing_ajax() ||
			empty( $block->context['filterData'] )
		) {
			return '';
		}

		$p = new \WP_HTML_Tag_Processor( $content );

		if ( $p->next_tag( array( 'class_name' => 'wp-block-button__link' ) ) ) {
			$p->set_attribute( 'data-wp-on--click', 'actions.removeAllActiveFilters' );

			$content = $p->get_updated_html();
		}

		$content = str_replace( array( '<a', '</a>' ), array( '<button', '</button>' ), $content );

		return sprintf(
			'<div %1$s>%2$s</div>',
			get_block_wrapper_attributes(),
			$content
		);
	}
}
PK     \OE    !  BlockTypes/ProductBestSellers.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ProductBestSellers class.
 */
class ProductBestSellers extends AbstractProductGrid {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-best-sellers';

	/**
	 * Set args specific to this block
	 *
	 * @param array $query_args Query args.
	 */
	protected function set_block_query_args( &$query_args ) {
		$query_args['orderby'] = 'popularity';
	}
}
PK     \i,<~  ~  "  BlockTypes/ProductFilterStatus.phpnu [        <?php
declare( strict_types = 1);
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection\Utils as ProductCollectionUtils;
use Automattic\WooCommerce\Internal\ProductFilters\FilterDataProvider;
use Automattic\WooCommerce\Internal\ProductFilters\QueryClauses;

/**
 * Product Filter: Status Block.
 */
final class ProductFilterStatus extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-filter-status';

	const STOCK_STATUS_QUERY_VAR = 'filter_stock_status';

	/**
	 * Initialize this block type.
	 *
	 * - Hook into WP lifecycle.
	 * - Register the block with WordPress.
	 */
	protected function initialize() {
		parent::initialize();

		add_filter( 'woocommerce_blocks_product_filters_selected_items', array( $this, 'prepare_selected_filters' ), 10, 2 );
	}



	/**
	 * Prepare the active filter items.
	 *
	 * @param array $items  The active filter items.
	 * @param array $params The query param parsed from the URL.
	 * @return array Active filters items.
	 */
	public function prepare_selected_filters( $items, $params ) {
		$status_options = array_merge(
			wc_get_product_stock_status_options(),
			// On sale and Featured status are declared here.
			array()
		);

		if ( empty( $params[ self::STOCK_STATUS_QUERY_VAR ] ) ) {
			return $items;
		}

		$active_statuses = array_filter(
			array_map( 'trim', explode( ',', $params[ self::STOCK_STATUS_QUERY_VAR ] ) ),
			function ( $status ) use ( $status_options ) {
				return array_key_exists( $status, $status_options );
			}
		);

		if ( empty( $active_statuses ) ) {
			return $items;
		}

		foreach ( $active_statuses as $status ) {
			$items[] = array(
				'type'        => 'status',
				'value'       => $status,
				// translators: %s: status.
				'activeLabel' => sprintf( __( 'Status: %s', 'woocommerce' ), $status_options[ $status ] ),
			);
		}

		return $items;
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $stock_statuses  Any stock statuses that currently are available from the block.
	 *                               Note, this will be empty in the editor context when the block is
	 *                               not in the post content on editor load.
	 */
	protected function enqueue_data( array $stock_statuses = array() ) {
		parent::enqueue_data( $stock_statuses );
		$this->asset_data_registry->add( 'stockStatusOptions', wc_get_product_stock_status_options() );
		$this->asset_data_registry->add( 'hideOutOfStockItems', 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) );
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		// don't render if its admin, or ajax in progress.
		if ( is_admin() || wp_doing_ajax() ) {
			return '';
		}

		$stock_status_data       = $this->get_stock_status_counts( $block );
		$stock_statuses          = wc_get_product_stock_status_options();
		$filter_params           = $block->context['filterParams'] ?? array();
		$query                   = $filter_params[ self::STOCK_STATUS_QUERY_VAR ] ?? '';
		$selected_stock_statuses = array_filter( explode( ',', $query ) );

		$filter_options = array_map(
			function ( $item ) use ( $stock_statuses, $selected_stock_statuses ) {
				return array(
					'label'    => $stock_statuses[ $item['status'] ],
					'value'    => $item['status'],
					'selected' => in_array( $item['status'], $selected_stock_statuses, true ),
					'count'    => $item['count'],
					'type'     => 'status',
				);
			},
			$stock_status_data
		);

		$filter_context = array(
			'items'      => array_values( $filter_options ),
			'showCounts' => $attributes['showCounts'] ?? false,
			'groupLabel' => __( 'Status', 'woocommerce' ),
		);

		$wrapper_attributes = array(
			'data-wp-interactive' => 'woocommerce/product-filters',
			'data-wp-key'         => wp_unique_prefixed_id( $this->get_full_block_name() ),
			'data-wp-context'     => wp_json_encode(
				array(
					/* translators: {{label}} is the status filter item label. */
					'activeLabelTemplate' => __( 'Status: {{label}}', 'woocommerce' ),
					'filterType'          => 'status',
				),
				JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
			),
		);

		if ( empty( $filter_options ) ) {
			$wrapper_attributes['hidden'] = true;
			$wrapper_attributes['class']  = 'wc-block-product-filter--hidden';
		}

		return sprintf(
			'<div %1$s>%2$s</div>',
			get_block_wrapper_attributes(
				$wrapper_attributes
			),
			array_reduce(
				$block->parsed_block['innerBlocks'],
				function ( $carry, $parsed_block ) use ( $filter_context ) {
					$carry .= ( new \WP_Block( $parsed_block, array( 'filterData' => $filter_context ) ) )->render();
					return $carry;
				},
				''
			)
		);
	}

	/**
	 * Retrieve the status filter data for current block.
	 *
	 * @param WP_Block $block Block instance.
	 */
	private function get_stock_status_counts( $block ) {
		if ( ! isset( $block->context['filterParams'] ) ) {
			return array();
		}

		$query_vars = ProductCollectionUtils::get_query_vars( $block, 1 );

		unset(
			$query_vars['filter_stock_status'],
		);

		if ( isset( $query_vars['taxonomy'] ) && false !== strpos( $query_vars['taxonomy'], 'pa_' ) ) {
			unset(
				$query_vars['taxonomy'],
				$query_vars['term']
			);
		}

		if ( ! empty( $query_vars['meta_query'] ) ) {
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
			$query_vars['meta_query'] = ProductCollectionUtils::remove_query_array( $query_vars['meta_query'], 'key', '_stock_status' );
		}

		$container = wc_get_container();
		$counts    = $container->get( FilterDataProvider::class )->with( $container->get( QueryClauses::class ) )->get_stock_status_counts( $query_vars, array_keys( wc_get_product_stock_status_options() ) );
		$data      = array();

		foreach ( $counts as $key => $value ) {
			$data[] = array(
				'status' => $key,
				'count'  => intval( $value ),
			);
		}

		return array_filter(
			$data,
			function ( $stock_count ) {
				return $stock_count['count'] > 0;
			}
		);
	}

	/**
	 * Disable the editor style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_editor_style() {
		return null;
	}

	/**
	 * Disable the script handle for this block type. We use block.json to load the script.
	 *
	 * @param string|null $key The key of the script to get.
	 * @return null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}
}
PK     \        BlockTypes/EmptyCartBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * EmptyCartBlock class.
 */
class EmptyCartBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'empty-cart-block';
}
PK     \m9W#@  #@    BlockTypes/ProductDetails.phpnu [        <?php

declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use WP_Block;
use WP_HTML_Tag_Processor;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * ProductDetails class.
 */
class ProductDetails extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-details';

	/**
	 * Initialize the block type.
	 *
	 * @return void
	 */
	protected function initialize() {
		parent::initialize();

		/**
		 * Filter the blocks that are hooked into the Product Details block.
		 *
		 * @hook woocommerce_product_details_hooked_blocks
		 *
		 * @since 10.0.0
		 * @param {array} $hooked_blocks The blocks that are hooked into the Product Details block.
		 * @return {array} The blocks that are hooked into the Product Details block.
		 */
		$hooked_blocks = apply_filters( 'woocommerce_product_details_hooked_blocks', [] );

		foreach ( $this->validate_hooked_blocks( $hooked_blocks ) as $slug => $block ) {
			$this->register_hooked_block( $slug, $block );
		}
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 *
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( empty( $block->parsed_block['innerBlocks'] ) ) {
			return $this->render_legacy_block( $attributes, $content, $block );
		}

		$parsed_block = $block->parsed_block;
		$parsed_block = $this->hide_empty_accordion_items( $parsed_block, $block->context );

		/**
		 * Filter to disable the compatibility layer for the blockified templates.
		 *
		 * @see AddToCartWithOptions::render() for full documentation.
		 * @since 7.6.0
		 */
		if ( ! apply_filters( 'woocommerce_disable_compatibility_layer', false ) ) {
			$parsed_block = $this->inject_compatible_tabs( $parsed_block );
		}

		$inner_content = array_reduce(
			$parsed_block['innerBlocks'],
			function ( $carry, $parsed_inner_block ) use ( $block ) {
				$carry .= ( new \WP_Block( $parsed_inner_block, $block->context ) )->render();
				return $carry;
			},
			''
		);

		return sprintf(
			'<div %1$s>%2$s</div>',
			get_block_wrapper_attributes(),
			$inner_content
		);
	}

	/**
	 * Inject compatible tabs.
	 *
	 * @param array $parsed_block Parsed block.
	 *
	 * @return array Parsed block.
	 */
	private function inject_compatible_tabs( $parsed_block ) {
		if ( ! $this->has_accordion( $parsed_block ) ) {
			return $parsed_block;
		}

		/**
		 * Filter the product tabs in the product details block.
		 *
		 * @since 3.3.0
		 * @param array $tabs Array of product tabs.
		 */
		$product_tabs = apply_filters(
			'woocommerce_product_tabs',
			array()
		);

		$default_tabs_callbacks = array(
			'woocommerce_product_description_tab',
			'woocommerce_product_additional_information_tab',
			'comments_template',
		);

		$product_tabs = array_filter(
			$product_tabs,
			function ( $tab ) use ( $default_tabs_callbacks ) {
				return ! in_array( $tab['callback'], $default_tabs_callbacks, true );
			}
		);

		usort(
			$product_tabs,
			function ( $a, $b ) {
				return $a['priority'] <=> $b['priority'];
			}
		);

		$accordion_blocks = array();

		$accordion_anchor_block = $this->get_accordion_anchor_block( $parsed_block );

		if ( ! $accordion_anchor_block ) {
			return $parsed_block;
		}

		foreach ( $product_tabs as $key => $tab ) {
			ob_start();
			call_user_func( $tab['callback'], $key, $tab );
			$tab_content        = ob_get_clean();
			$accordion_blocks[] = $this->create_accordion_item_block(
				$tab['title'],
				'<!-- wp:html -->' . $tab_content . '<!-- /wp:html -->',
				$accordion_anchor_block
			);
		}

		return $this->inject_parsed_accordion_blocks( $parsed_block, $accordion_blocks );
	}

	/**
	 * Create an accordion item block.
	 *
	 * @param string $title Title of the accordion item.
	 * @param string $content Content of the accordion item as block markup.
	 * @param array  $anchor_block Accordion anchor block to determine which item block to create.
	 *
	 * @return array Accordion item.
	 */
	private function create_accordion_item_block( $title, $content, $anchor_block ) {
		if ( isset( $anchor_block['blockName'] ) && 'core/accordion' === $anchor_block['blockName'] ) {
			$template = '<!-- wp:accordion-item -->
				<div class="wp-block-accordion-item">
					<!-- wp:accordion-heading -->
					<h3 class="wp-block-accordion-heading">
						<button class="wp-block-accordion-heading__toggle">
							<span class="wp-block-accordion-heading__toggle-title">%1$s</span>
							<span class="wp-block-accordion-heading__toggle-icon" aria-hidden="true">+</span>
						</button>
					</h3>
					<!-- /wp:accordion-heading -->

					<!-- wp:accordion-panel -->
					<div class="wp-block-accordion-panel">
						%2$s
					</div>
					<!-- /wp:accordion-panel -->
				</div>
				<!-- /wp:accordion-item -->';
		} else {
			$template = '<!-- wp:woocommerce/accordion-item -->
				<div class="wp-block-woocommerce-accordion-item"><!-- wp:woocommerce/accordion-header -->
				<h3 class="wp-block-woocommerce-accordion-header accordion-item__heading">
				<button class="accordion-item__toggle">
				<span>%1$s</span>
				<span class="accordion-item__toggle-icon has-icon-plus" style="width:1.2em;height:1.2em"><svg width="1.2em" height="1.2em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M11 12.5V17.5H12.5V12.5H17.5V11H12.5V6H11V11H6V12.5H11Z" fill="currentColor"></path></svg></span>
				</button>
				</h3>
				<!-- /wp:woocommerce/accordion-header -->

				<!-- wp:woocommerce/accordion-panel -->
				<div class="wp-block-woocommerce-accordion-panel"><div class="accordion-content__wrapper">
				%2$s
				</div></div>
				<!-- /wp:woocommerce/accordion-panel --></div>
				<!-- /wp:woocommerce/accordion-item -->';
		}

		return parse_blocks( sprintf( $template, $title, $content ) )[0];
	}

	/**
	 * Inject parsed accordion blocks.
	 *
	 * @param array $parsed_block Parsed block.
	 * @param array $accordion_blocks Accordion blocks.
	 *
	 * @return array Parsed block.
	 */
	private function inject_parsed_accordion_blocks( $parsed_block, $accordion_blocks ) {
		if ( 'core/accordion' === $parsed_block['blockName'] || 'woocommerce/accordion-group' === $parsed_block['blockName'] ) {
			$parsed_block['innerBlocks']  = array_merge( $parsed_block['innerBlocks'], $accordion_blocks );
			$parsed_block['innerBlocks']  = array_values( array_filter( $parsed_block['innerBlocks'] ) );
			$opening_tag                  = reset( $parsed_block['innerContent'] );
			$closing_tag                  = end( $parsed_block['innerContent'] );
			$parsed_block['innerContent'] = array_merge(
				array( $opening_tag ),
				array_fill( 0, count( $parsed_block['innerBlocks'] ), null ),
				array( $closing_tag )
			);
			return $parsed_block;
		}

		foreach ( $parsed_block['innerBlocks'] as $key => $inner_block ) {
			$parsed_block['innerBlocks'][ $key ] = $this->inject_parsed_accordion_blocks( $inner_block, $accordion_blocks );
		}

		return $parsed_block;
	}

	/**
	 * Hide empty accordion items.
	 *
	 * @param array $parsed_block Parsed block.
	 * @param array $context Context.
	 *
	 * @return array Parsed block.
	 */
	private function hide_empty_accordion_items( $parsed_block, $context ) {
		if ( ! $this->has_accordion( $parsed_block ) ) {
			return $parsed_block;
		}

		if ( 'core/accordion' === $parsed_block['blockName'] || 'woocommerce/accordion-group' === $parsed_block['blockName'] ) {
			foreach ( $parsed_block['innerBlocks'] as $key => $inner_block ) {
				$parsed_block['innerBlocks'][ $key ] = $this->mark_accordion_item_hidden( $inner_block, $context );
			}
			$parsed_block['innerBlocks']  = array_values( array_filter( $parsed_block['innerBlocks'] ) );
			$opening_tag                  = reset( $parsed_block['innerContent'] );
			$closing_tag                  = end( $parsed_block['innerContent'] );
			$parsed_block['innerContent'] = array_merge(
				array( $opening_tag ),
				array_fill( 0, count( $parsed_block['innerBlocks'] ), null ),
				array( $closing_tag )
			);
			return $parsed_block;
		}

		foreach ( $parsed_block['innerBlocks'] as $key => $inner_block ) {
			$parsed_block['innerBlocks'][ $key ] = $this->hide_empty_accordion_items( $inner_block, $context );
		}

		return $parsed_block;
	}

	/**
	 * Mark an accordion item as hidden if it has no content.
	 *
	 * @param array $item Item to mark.
	 * @param array $context Context.
	 *
	 * @return array Item.
	 */
	private function mark_accordion_item_hidden( $item, $context ) {
		$content_block          = end( $item['innerBlocks'] );
		$rendered_content_block = ( new WP_Block( $content_block, $context ) )->render();
		$p                      = new WP_HTML_Tag_Processor( $rendered_content_block );

		$has_content = $p->next_tag( 'img' ) ||
			$p->next_tag( 'iframe' ) ||
			$p->next_tag( 'video' ) ||
			$p->next_tag( 'meter' ) ||
			! empty( wp_strip_all_tags( $rendered_content_block, true ) );

		if ( ! $has_content ) {
			return array();
		}

		return $item;
	}

	/**
	 * Check if a parsed block has an accordion.
	 *
	 * @param array $parsed_block Parsed block.
	 *
	 * @return bool True if the block has an accordion, false otherwise.
	 */
	private function has_accordion( $parsed_block ) {
		if ( 'core/accordion' === $parsed_block['blockName'] || 'woocommerce/accordion-group' === $parsed_block['blockName'] ) {
			return true;
		}

		foreach ( $parsed_block['innerBlocks'] as $inner_block ) {
			if ( $this->has_accordion( $inner_block ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Get the first accordion anchor block in a parsed block.
	 *
	 * @param array $parsed_block Parsed block.
	 *
	 * @return array|null Accordion anchor block or null.
	 */
	private function get_accordion_anchor_block( $parsed_block ) {
		if ( 'core/accordion' === $parsed_block['blockName'] || 'woocommerce/accordion-group' === $parsed_block['blockName'] ) {
			return $parsed_block;
		}

		foreach ( $parsed_block['innerBlocks'] as $inner_block ) {
			$anchor_block = $this->get_accordion_anchor_block( $inner_block );
			if ( $anchor_block ) {
				return $anchor_block;
			}
		}

		return null;
	}

	/**
	 * Validate hooked blocks data. Remove duplicated entries with the same title
	 * and invalid entries with invalid content. Log errors to the WC logger.
	 *
	 * @param array $hooked_blocks { Hooked blocks data.
	 *   @type string $title Title of the hooked block.
	 *   @type string $content Content of the hooked block, as block markup.
	 * }
	 *
	 * @return array Validated hooked blocks.
	 */
	private function validate_hooked_blocks( $hooked_blocks ) {
		$logger                  = wc_get_logger();
		$validated_hooked_blocks = [];

		foreach ( $hooked_blocks as $block ) {
			$invalid = ! is_array( $block ) ||
				! isset( $block['title'] ) ||
				! isset( $block['content'] ) ||
				! is_string( $block['title'] ) ||
				! is_string( $block['content'] );

			if ( ! $invalid ) {
				$parsed_content = parse_blocks( $block['content'] );

				foreach ( $parsed_content as $content_block ) {
					if ( ! isset( $content_block['blockName'] ) ) {
						$invalid = true;
						break;
					}
				}
			}

			if ( $invalid ) {
				$logger->error( 'Invalid hooked block data. Expected array with `title` and `content` keys with string values. Content must be valid block markup.', $block );
				continue;
			}

			$slug = sanitize_title( $block['title'] );

			/**
			 * If the block is already registered, replace the block. We use the
			 * last registered block for the same slug. This makes overriding
			 * hooked block easier.
			 */
			if ( isset( $validated_hooked_blocks[ $slug ] ) ) {
				$validated_hooked_blocks[ $slug ] = $block;
				continue;
			}

			$validated_hooked_blocks[ $slug ] = $block;
		}

		return $validated_hooked_blocks;
	}

	/**
	 * Register a product details item using Block Hooks API.
	 *
	 * @param string $slug The slug of the item.
	 * @param array  $block The block data.
	 * @return void
	 */
	private function register_hooked_block( $slug, $block ) {
		add_filter(
			'hooked_block_types',
			function ( $hooked_block_types, $relative_position, $anchor_block_type ) use ( $slug ) {
				if (
					( 'core/accordion' === $anchor_block_type || 'woocommerce/accordion-group' === $anchor_block_type ) &&
					'last_child' === $relative_position &&
					! in_array( $slug, $hooked_block_types, true )
				) {
					$hooked_block_types[] = $slug;
				}
				return $hooked_block_types;
			},
			10,
			3
		);

		add_filter(
			"hooked_block_{$slug}",
			function ( $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block ) use ( $block ) {
				if (
					is_null( $parsed_hooked_block ) ||
					( 'core/accordion' !== $parsed_anchor_block['blockName'] && 'woocommerce/accordion-group' !== $parsed_anchor_block['blockName'] ) ||
					'last_child' !== $relative_position ||
					empty( $parsed_anchor_block['attrs']['metadata']['isDescendantOfProductDetails'] )
				) {
					return null;
				}

				return $this->create_accordion_item_block( $block['title'], $block['content'], $parsed_anchor_block );
			},
			10,
			4
		);
	}

	/**
	 * Enqueue legacy assets when this block is used as we don't enqueue them for block themes anymore.
	 *
	 * @see https://github.com/woocommerce/woocommerce/pull/60223
	 */
	public function enqueue_legacy_assets() {
		wp_enqueue_script( 'wc-single-product' );
	}

	/**
	 * Previously, the Product Details block was a standalone block. It doesn't
	 * have any inner blocks and it rendered the tabs directly like the classic
	 * template. When upgrading, we want the existing stores using the block to
	 * continue working as before, so we moved the logic the legacy render
	 * method here.
	 *
	 * @see https://github.com/woocommerce/woocommerce/pull/59005
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 *
	 * @return string Rendered block output.
	 */
	protected function render_legacy_block( $attributes, $content, $block ) {
		if ( ! is_singular( 'product' ) ) {
			return $content;
		}

		add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_legacy_assets' ], 20 );

		$hide_tab_title = isset( $attributes['hideTabTitle'] ) ? $attributes['hideTabTitle'] : false;

		if ( $hide_tab_title ) {
			add_filter( 'woocommerce_product_description_heading', '__return_empty_string' );
			add_filter( 'woocommerce_product_additional_information_heading', '__return_empty_string' );
			add_filter( 'woocommerce_reviews_title', '__return_empty_string' );
		}

		$tabs = $this->render_tabs();

		if ( $hide_tab_title ) {
			remove_filter( 'woocommerce_product_description_heading', '__return_empty_string' );
			remove_filter( 'woocommerce_product_additional_information_heading', '__return_empty_string' );
			remove_filter( 'woocommerce_reviews_title', '__return_empty_string' );

			// Remove the first `h2` of every `.wc-tab`. This is required for the Reviews tabs when there are no reviews and for plugin tabs.
			$tabs_html = new WP_HTML_Tag_Processor( $tabs );
			while ( $tabs_html->next_tag( array( 'class_name' => 'wc-tab' ) ) ) {
				if ( $tabs_html->next_tag( 'h2' ) ) {
					$tabs_html->set_attribute( 'hidden', 'true' );
				}
			}
			$tabs = $tabs_html->get_updated_html();
		}

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );

		return sprintf(
			'<div class="wp-block-woocommerce-product-details %1$s">
				<div style="%2$s">
					%3$s
				</div>
			</div>',
			esc_attr( $classes_and_styles['classes'] ),
			esc_attr( $classes_and_styles['styles'] ),
			$tabs
		);
	}

	/**
	 * Gets the tabs with their content to be rendered by the block.
	 *
	 * @return string The tabs html to be rendered by the block
	 */
	protected function render_tabs() {
		ob_start();
		rewind_posts();
		while ( have_posts() ) {
			the_post();
			woocommerce_output_product_data_tabs();
		}

		$tabs = ob_get_clean();

		return $tabs;
	}
}
PK     \{y    +  BlockTypes/CheckoutOrderSummaryFeeBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutOrderSummaryFeeBlock class.
 */
class CheckoutOrderSummaryFeeBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-order-summary-fee-block';
}
PK     \    !  BlockTypes/MiniCartItemsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Admin\Features\Features;

/**
 * MiniCartItemsBlock class.
 */
class MiniCartItemsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'mini-cart-items-block';

	/**
	 * Render the markup for the Mini-Cart Contents block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( Features::is_enabled( 'experimental-iapi-mini-cart' ) ) {
			return $this->render_experimental_iapi_markup( $attributes, $content, $block );
		}

		return $content;
	}

	/**
	 * Render experimental iAPI block markup.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render_experimental_iapi_markup( $attributes, $content, $block ) {
		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class'    => 'wc-block-mini-cart__items',
				'tabindex' => '-1',
			)
		);

		ob_start();
		?>
		<div <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<?php echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
		</div>
		<?php
		return ob_get_clean();
	}
}
PK     \^A<|  |    BlockTypes/MiniCartContents.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * Mini-Cart Contents class.
 *
 * @internal
 */
class MiniCartContents extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'mini-cart-contents';

	/**
	 * Get the editor script handle for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 *
	 * @return array|string;
	 */
	protected function get_block_type_editor_script( $key = null ) {
		$script = [
			'handle'       => 'wc-' . $this->block_name . '-block',
			'path'         => $this->asset_api->get_block_asset_build_path( $this->block_name ),
			'dependencies' => [ 'wc-blocks' ],
		];
		return $key ? $script[ $key ] : $script;
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 *
	 * @return null
	 */
	protected function get_block_type_script( $key = null ) {
		// The frontend script is a dependency of the Mini-Cart block so it's
		// already lazy-loaded.
		return null;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string[]
	 */
	protected function get_block_type_style() {
		return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
	}

	/**
	 * Render experimental iAPI powered Mini-Cart Contents block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render_experimental_iapi_mini_cart_contents( $attributes, $content, $block ) {
		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'data-wp-interactive'             => 'woocommerce/mini-cart-contents',
				'data-wp-style--background-color' => 'woocommerce/mini-cart::state.contentsBackgroundColor',
			)
		);

		ob_start();
		?>
		<div <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<div class="wc-block-components-drawer__close-wrapper">
				<button data-wp-on--click="woocommerce/mini-cart::actions.closeDrawer" class="wc-block-components-button wp-element-button wc-block-components-drawer__close contained" aria-label="Close" type="button">
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false">
						<path d="M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z"></path>
					</svg>
				</button>
			</div>
			<?php
				// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				echo $content;
			?>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Render the markup for the Mini-Cart Contents block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( is_admin() || WC()->is_rest_api_request() ) {
			// In the editor we will display the placeholder, so no need to
			// print the markup.
			return '';
		}

		if ( Features::is_enabled( 'experimental-iapi-mini-cart' ) ) {
			return $this->render_experimental_iapi_mini_cart_contents( $attributes, $content, $block );
		}

		return $content;
	}

	/**
	 * Enqueue frontend assets for this block, just in time for rendering.
	 *
	 * @param array    $attributes  Any attributes that currently are available from the block.
	 * @param string   $content    The block content.
	 * @param WP_Block $block    The block object.
	 */
	protected function enqueue_assets( array $attributes, $content, $block ) {
		parent::enqueue_assets( $attributes, $content, $block );
		$text_color = StyleAttributesUtils::get_text_color_class_and_style( $attributes );
		$bg_color   = StyleAttributesUtils::get_background_color_class_and_style( $attributes );

		$styles = array(
			array(
				'selector'   => array(
					'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-checkout',
					'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-checkout:hover',
					'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-checkout:focus',
					'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-cart.wc-block-components-button:hover',
					'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-cart.wc-block-components-button:focus',
					'.wc-block-mini-cart__shopping-button a:hover',
					'.wc-block-mini-cart__shopping-button a:focus',
				),
				'properties' => array(
					array(
						'property' => 'color',
						'value'    => $bg_color ? $bg_color['value'] : false,
					),
					array(
						'property' => 'border-color',
						'value'    => $text_color ? $text_color['value'] : false,
					),
					array(
						'property' => 'background-color',
						'value'    => $text_color ? $text_color['value'] : false,
					),
				),
			),
		);

		$parsed_style = sprintf(
			':root { --drawer-width: %s; --neg-drawer-width: calc(var(--drawer-width) * -1); }',
			esc_html( $attributes['width'] )
		);

		foreach ( $styles as $style ) {
			$selector = is_array( $style['selector'] ) ? implode( ',', $style['selector'] ) : $style['selector'];

			$properties = array_filter(
				$style['properties'],
				function ( $property ) {
					return $property['value'];
				}
			);

			if ( ! empty( $properties ) ) {
				$parsed_style .= $selector . '{';
				foreach ( $properties as $property ) {
					$parsed_style .= sprintf( '%1$s:%2$s;', $property['property'], $property['value'] );
				}
				$parsed_style .= '}';
			}
		}

		wp_add_inline_style(
			'wc-blocks-style',
			$parsed_style
		);
	}

	/**
	 * Get list of Mini-Cart Contents block & its inner-block types.
	 *
	 * @return array;
	 */
	public static function get_mini_cart_block_types() {
		$block_types = [];

		$block_types[] = 'MiniCartContents';
		$block_types[] = 'EmptyMiniCartContentsBlock';
		$block_types[] = 'FilledMiniCartContentsBlock';
		$block_types[] = 'MiniCartFooterBlock';
		$block_types[] = 'MiniCartItemsBlock';
		$block_types[] = 'MiniCartProductsTableBlock';
		$block_types[] = 'MiniCartShoppingButtonBlock';
		$block_types[] = 'MiniCartCartButtonBlock';
		$block_types[] = 'MiniCartCheckoutButtonBlock';
		$block_types[] = 'MiniCartTitleBlock';
		$block_types[] = 'MiniCartTitleItemsCounterBlock';
		$block_types[] = 'MiniCartTitleLabelBlock';

		return $block_types;
	}
}
PK     \sCI  I  $  BlockTypes/ProductFilterTaxonomy.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection\Utils as ProductCollectionUtils;
use Automattic\WooCommerce\Internal\ProductFilters\FilterDataProvider;
use Automattic\WooCommerce\Internal\ProductFilters\QueryClauses;
use Automattic\WooCommerce\Internal\ProductFilters\TaxonomyHierarchyData;

/**
 * Product Filter: Taxonomy Block.
 */
final class ProductFilterTaxonomy extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-filter-taxonomy';

	/**
	 * Prepare the active filter items.
	 *
	 * @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed.
	 *
	 * @param array $items  The active filter items.
	 * @param array $params The query param parsed from the URL.
	 * @return array Active filters items.
	 */
	public function prepare_selected_filters( $items, $params ) {
		$container      = wc_get_container();
		$params_handler = $container->get( \Automattic\WooCommerce\Internal\ProductFilters\Params::class );

		// Use centralized parameter mapping to avoid hardcoding URL parameter formats.
		$taxonomy_params = $params_handler->get_param( 'taxonomy' );

		$active_taxonomies = array();
		$all_term_slugs    = array();

		foreach ( $taxonomy_params as $taxonomy_slug => $param_key ) {
			if ( ! empty( $params[ $param_key ] ) && is_string( $params[ $param_key ] ) ) {
				$term_slugs                          = array_map( 'sanitize_title', explode( ',', $params[ $param_key ] ) );
				$active_taxonomies[ $taxonomy_slug ] = $term_slugs;
				$all_term_slugs                      = array_merge( $all_term_slugs, $term_slugs );
			}
		}

		if ( empty( $active_taxonomies ) ) {
			return $items;
		}

		// Single query for all taxonomies and terms to avoid N+1 query problem.
		$terms = get_terms(
			array(
				'taxonomy'   => array_keys( $active_taxonomies ),
				'slug'       => array_unique( $all_term_slugs ),
				'hide_empty' => false,
			)
		);

		if ( is_wp_error( $terms ) || empty( $terms ) ) {
			return $items;
		}

		foreach ( $terms as $term ) {
			$taxonomy_object = get_taxonomy( $term->taxonomy );
			if ( $taxonomy_object ) {
				$items[] = array(
					'type'        => 'taxonomy/' . $term->taxonomy,
					'value'       => $term->slug,
					'activeLabel' => $taxonomy_object->labels->singular_name . ': ' . $term->name,
				);
			}
		}

		return $items;
	}

	/**
	 * Initialize this block type.
	 *
	 * - Hook into WP lifecycle.
	 * - Register the block with WordPress.
	 */
	protected function initialize() {
		parent::initialize();

		add_filter( 'woocommerce_blocks_product_filters_selected_items', array( $this, 'prepare_selected_filters' ), 10, 2 );

		// Register REST field for menu_order on sortable taxonomies.
		$this->register_taxonomy_menu_order_rest_field();
	}

	/**
	 * Register a REST field to expose the menu_order meta for sortable taxonomies.
	 * This allows the editor to display terms in menu order.
	 */
	private function register_taxonomy_menu_order_rest_field(): void {
		/**
		 * Filters the list of taxonomies that support custom ordering. Filter was introduced long
		 * ago is only documented in 10.6.0.
		 *
		 * First instance in plugins/woocommerce/includes/admin/class-wc-admin-assets.php.
		 *
		 * @since 1.0
		 *
		 * @param array $sortable_taxonomies List of taxonomy slugs that support custom ordering.
		 * @return array List of taxonomy slugs that support custom ordering.
		 */
		$sortable_taxonomies = apply_filters( 'woocommerce_sortable_taxonomies', array( 'product_cat' ) );

		foreach ( $sortable_taxonomies as $taxonomy ) {
			register_rest_field(
				$taxonomy,
				'menu_order',
				array(
					'get_callback' => function ( $term ) {
						$menu_order = get_term_meta( $term['id'], 'order', true );
						return is_numeric( $menu_order ) ? (int) $menu_order : 0;
					},
					'schema'       => array(
						'description' => __( 'Menu order, used to custom sort the term.', 'woocommerce' ),
						'type'        => 'integer',
						'context'     => array( 'view', 'edit' ),
						'readonly'    => true,
					),
				)
			);
		}
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = array() ) {
		parent::enqueue_data( $attributes );

		if ( is_admin() ) {
			$this->asset_data_registry->add( 'filterableProductTaxonomies', $this->get_taxonomies() );
			// Expose sortable taxonomies so the editor can show/hide "Menu order" option.
			$this->asset_data_registry->add(
				'sortableTaxonomies',
				/**
				 * Filters the list of taxonomies that support custom ordering. Filter was introduced long
				 * ago is only documented in 10.6.0.
				 *
				 * First instance in plugins/woocommerce/includes/admin/class-wc-admin-assets.php.
				 *
				 * @since 1.0
				 *
				 * @param array $sortable_taxonomies List of taxonomy slugs that support custom ordering.
				 * @return array List of taxonomy slugs that support custom ordering.
				 */
				apply_filters( 'woocommerce_sortable_taxonomies', array( 'product_cat' ) )
			);
		}
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Render the block.
	 *
	 * @param array    $block_attributes Block attributes.
	 * @param string   $content          Block content.
	 * @param WP_Block $block            Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $block_attributes, $content, $block ) {
		// Skip rendering in admin or during AJAX requests.
		if ( is_admin() || wp_doing_ajax() || empty( $block_attributes['taxonomy'] ) ) {
			return '';
		}

		$taxonomy        = $block_attributes['taxonomy'];
		$taxonomy_object = get_taxonomy( $taxonomy );

		if ( ! $taxonomy_object || ! taxonomy_exists( $taxonomy ) ) {
			return '';
		}

		// Validate that this taxonomy is configured in the parameter map.
		$container       = wc_get_container();
		$params_handler  = $container->get( \Automattic\WooCommerce\Internal\ProductFilters\Params::class );
		$taxonomy_params = $params_handler->get_param( 'taxonomy' );

		if ( ! isset( $taxonomy_params[ $taxonomy ] ) ) {
			return '';
		}

		// Pass taxonomy parameter mapping to frontend via interactivity config.
		wp_interactivity_config(
			'woocommerce/product-filters',
			array(
				'taxonomyParamsMap' => $taxonomy_params,
			)
		);

		$filter_context  = array(
			'showCounts' => $block_attributes['showCounts'] ?? false,
			'items'      => array(),
			'groupLabel' => $taxonomy_object->labels->singular_name,
		);
		$taxonomy_counts = $this->get_taxonomy_term_counts( $block, $taxonomy );

		if ( ! empty( $taxonomy_counts ) ) {
			$hide_empty     = $block_attributes['hideEmpty'] ?? true;
			$orderby        = $block_attributes['sortOrder'] ? explode( '-', $block_attributes['sortOrder'] )[0] : 'name';
			$order          = $block_attributes['sortOrder'] ? strtoupper( explode( '-', $block_attributes['sortOrder'] )[1] ) : 'DESC';
			$taxonomy_terms = $this->get_sorted_terms( $taxonomy, $taxonomy_counts, $hide_empty, $orderby, $order );

			if ( is_wp_error( $taxonomy_terms ) ) {
				return '';
			}

			// Get selected terms from filter params.
			$filter_params  = $block->context['filterParams'] ?? array();
			$selected_terms = array();
			$param_key      = $taxonomy_params[ $taxonomy ];

			if ( $filter_params && ! empty( $filter_params[ $param_key ] ) && is_string( $filter_params[ $param_key ] ) ) {
				$selected_terms = array_filter( array_map( 'sanitize_title', explode( ',', $filter_params[ $param_key ] ) ) );
			}

			$taxonomy_options = array_map(
				function ( $term ) use ( $taxonomy_counts, $selected_terms, $taxonomy ) {
					$term          = (array) $term;
					$term['count'] = $taxonomy_counts[ $term['term_id'] ] ?? 0;

					$option = array(
						'label'    => $term['name'],
						'value'    => $term['slug'],
						'selected' => in_array( $term['slug'], $selected_terms, true ),
						'count'    => $term['count'],
						'type'     => 'taxonomy/' . $taxonomy,
					);

					if ( is_taxonomy_hierarchical( $taxonomy ) ) {
						$option['id'] = $term['term_id'];

						if ( isset( $term['depth'] ) && $term['depth'] > 0 ) {
							$option['depth'] = $term['depth'];
						}
						if ( isset( $term['parent'] ) && $term['parent'] > 0 ) {
							$option['parent'] = $term['parent'];
						}
					}
					return $option;
				},
				$taxonomy_terms
			);

			$filter_context['items'] = $taxonomy_options;
		}

		$wrapper_attributes = array(
			'data-wp-interactive' => 'woocommerce/product-filters',
			'data-wp-key'         => wp_unique_prefixed_id( $this->get_block_type() ),
			'data-wp-context'     => wp_json_encode(
				array(
					'activeLabelTemplate' => $taxonomy_object->labels->singular_name . ': {{label}}',
					'filterType'          => 'taxonomy/' . $taxonomy,
				),
				JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
			),
		);

		if ( empty( $filter_context['items'] ) ) {
			$wrapper_attributes['hidden'] = true;
			$wrapper_attributes['class']  = 'wc-block-product-filter--hidden';
		}

		return sprintf(
			'<div %1$s>%2$s</div>',
			get_block_wrapper_attributes( $wrapper_attributes ),
			array_reduce(
				$block->parsed_block['innerBlocks'],
				function ( $carry, $parsed_block ) use ( $filter_context ) {
					$carry .= ( new \WP_Block( $parsed_block, array( 'filterData' => $filter_context ) ) )->render();
					return $carry;
				},
				''
			)
		);
	}

	/**
	 * Get terms sorted based on taxonomy type (hierarchical vs flat).
	 *
	 * @param string $taxonomy        Taxonomy slug.
	 * @param array  $taxonomy_counts Term counts with term_id as key.
	 * @param bool   $hide_empty      Whether to hide empty terms.
	 * @param string $orderby         Sort field (name, count, menu_order).
	 * @param string $order           Sort direction (ASC, DESC).
	 * @return array Sorted terms array.
	 */
	private function get_sorted_terms( $taxonomy, $taxonomy_counts, $hide_empty, $orderby, $order ) {
		if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
			$args = array(
				'taxonomy'   => $taxonomy,
				'hide_empty' => false,
			);

			if ( $hide_empty ) {
				$args['include'] = array_keys( $taxonomy_counts );
			}

			$terms = get_terms( $args );

			if ( is_wp_error( $terms ) || empty( $terms ) ) {
				return array();
			}

			// Add menu_order to flat terms for sorting.
			if ( 'menu_order' === $orderby ) {
				// Prime term meta cache in single query to avoid N+1.
				update_termmeta_cache( wp_list_pluck( $terms, 'term_id' ) );
				$terms = array_map(
					function ( $term ) {
						$term               = (array) $term;
						$menu_order         = get_term_meta( $term['term_id'], 'order', true );
						$term['menu_order'] = is_numeric( $menu_order ) ? (int) $menu_order : 0;
						return (object) $term;
					},
					$terms
				);
			}

			return $this->sort_terms_by_criteria( $terms, $orderby, $order, $taxonomy_counts );
		}

		return $this->get_hierarchical_terms( $taxonomy, $taxonomy_counts, $hide_empty, $orderby, $order );
	}

	/**
	 * Retrieve the taxonomy term counts for current block.
	 *
	 * @param WP_Block $block    Block instance.
	 * @param string   $taxonomy Taxonomy slug.
	 * @return array Term counts with term_id as key and count as value.
	 */
	private function get_taxonomy_term_counts( $block, $taxonomy ) {
		if ( ! isset( $block->context['filterParams'] ) ) {
			return array();
		}

		$query_vars = ProductCollectionUtils::get_query_vars( $block, 1 );

		// Remove current taxonomy from query vars to avoid circular counting.
		$container       = wc_get_container();
		$params_handler  = $container->get( \Automattic\WooCommerce\Internal\ProductFilters\Params::class );
		$taxonomy_params = $params_handler->get_param( 'taxonomy' );

		if ( isset( $taxonomy_params[ $taxonomy ] ) ) {
			$param_key = $taxonomy_params[ $taxonomy ];
			unset( $query_vars[ $param_key ] );
		}

		/**
		 * Prevent circular counting when calculating filter counts with active attribute filters.
		 * Removes product attribute taxonomy filters to ensure accurate cross-filter counting.
		 *
		 * @see https://github.com/woocommerce/woocommerce/pull/52759
		 */
		if ( isset( $query_vars['taxonomy'] ) && false !== strpos( $query_vars['taxonomy'], 'pa_' ) ) {
			unset(
				$query_vars['taxonomy'],
				$query_vars['term']
			);
		}

		// Remove from tax_query if present.
		if ( ! empty( $query_vars['tax_query'] ) ) {
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
			$query_vars['tax_query'] = ProductCollectionUtils::remove_query_array( $query_vars['tax_query'], 'taxonomy', $taxonomy );
		}

		$counts = $container->get( FilterDataProvider::class )->with( $container->get( QueryClauses::class ) )->get_taxonomy_counts( $query_vars, $taxonomy );

		return $counts;
	}

	/**
	 * Get product taxonomies for the block.
	 *
	 * @return array
	 */
	private function get_taxonomies() {
		$container       = wc_get_container();
		$params_handler  = $container->get( \Automattic\WooCommerce\Internal\ProductFilters\Params::class );
		$taxonomy_params = $params_handler->get_param( 'taxonomy' );
		$taxonomy_data   = array();

		foreach ( array_keys( $taxonomy_params ) as $taxonomy_slug ) {
			$taxonomy = get_taxonomy( $taxonomy_slug );

			if ( ! $taxonomy ) {
				continue;
			}

			$taxonomy_data[] = array(
				'label' => $taxonomy->labels->singular_name,
				'name'  => $taxonomy->name,
			);
		}

		return $taxonomy_data;
	}

	/**
	 * Sort hierarchical terms recursively maintaining parent-child relationships.
	 *
	 * @param array  $terms           Hierarchical terms array with children.
	 * @param string $orderby         Sort field (name, count, menu_order).
	 * @param string $order           Sort direction (ASC, DESC).
	 * @param array  $taxonomy_counts Context-aware term counts.
	 * @return array Sorted hierarchical terms.
	 */
	private function sort_hierarchy_terms( $terms, $orderby, $order, $taxonomy_counts ) {
		foreach ( $terms as $term ) {
			if ( ! empty( $term['children'] ) ) {
				$term['children'] = $this->sort_terms_by_criteria( $term['children'], $orderby, $order, $taxonomy_counts );
			}
		}
		$sorted = $this->sort_terms_by_criteria( $terms, $orderby, $order, $taxonomy_counts );
		return $sorted;
	}

	/**
	 * Flatten hierarchical term tree into flat array maintaining depth-first order.
	 *
	 * @param array $terms  Hierarchical terms with children structure.
	 * @param array $result Reference to result array being built.
	 * @param array $visited_ids Reference to array tracking visited term IDs to prevent circular references.
	 * @param int   $depth Current recursion depth for bounds checking.
	 */
	private function flatten_terms_list( $terms, &$result, &$visited_ids = array(), $depth = 0 ) {
		/**
		 * This is the safeguard to prevent the memory limit issue. We choose 10 as it
		 * should cover most of the cases. Typical e-commerce stores have two or three
		 * levels of category. Extreme cases like Amazon has about 7 levels.
		 *
		 * @see https://github.com/woocommerce/woocommerce/pull/60142/files#r2250287050
		 */
		if ( $depth > 10 ) {
			return;
		}

		if ( ! is_array( $terms ) ) {
			return;
		}

		foreach ( $terms as $term ) {
			// Validate term structure.
			if ( ! is_array( $term ) || ! isset( $term['term_id'] ) ) {
				continue;
			}

			$term_id = $term['term_id'];

			// Prevent circular references.
			if ( isset( $visited_ids[ $term_id ] ) ) {
				continue;
			}

			$visited_ids[ $term_id ] = true;
			$result[ $term_id ]      = $term;

			if ( ! empty( $term['children'] ) && is_array( $term['children'] ) ) {
				$this->flatten_terms_list( $term['children'], $result, $visited_ids, $depth + 1 );
				unset( $result[ $term_id ]['children'] );
			}
		}
	}

	/**
	 * Get taxonomy terms ordered hierarchically.
	 *
	 * @param string $taxonomy        Taxonomy slug.
	 * @param array  $taxonomy_counts Term counts with term_id as key.
	 * @param bool   $hide_empty      Whether to hide empty terms.
	 * @param string $orderby         Sort field for siblings (name, count, menu_order).
	 * @param string $order           Sort direction (ASC, DESC).
	 * @return array|\WP_Error Hierarchically ordered terms or error.
	 */
	private function get_hierarchical_terms( string $taxonomy, array $taxonomy_counts, bool $hide_empty, string $orderby, string $order ) {
		// Use TaxonomyHierarchyData for hierarchy operations.
		$container      = wc_get_container();
		$hierarchy_data = $container->get( TaxonomyHierarchyData::class )->get_hierarchy_map( $taxonomy );

		$sorted_term = $this->sort_hierarchy_terms( $hierarchy_data['tree'], $orderby, $order, $taxonomy_counts );

		$flat_list = array();
		$this->flatten_terms_list( $sorted_term, $flat_list );

		if ( ! $hide_empty ) {
			return $flat_list;
		}

		return array_filter(
			$flat_list,
			function ( $term ) use ( $taxonomy_counts ) {
				return ! empty( $taxonomy_counts[ $term['term_id'] ] );
			}
		);
	}

	/**
	 * Sort terms by the specified criteria (name, count, or menu_order).
	 *
	 * @param array  $terms           Array of term objects to sort.
	 * @param string $orderby         Sort field (name, count, menu_order).
	 * @param string $order           Sort direction (ASC, DESC).
	 * @param array  $taxonomy_counts Context-aware term counts.
	 * @return array Sorted terms.
	 */
	private function sort_terms_by_criteria( array $terms, string $orderby, string $order, array $taxonomy_counts ): array {
		$sort_order = 'DESC' === strtoupper( $order ) ? -1 : 1;

		usort(
			$terms,
			function ( $a, $b ) use ( $orderby, $sort_order, $taxonomy_counts ) {
				$a = (object) $a;
				$b = (object) $b;
				switch ( $orderby ) {
					case 'count':
						$count_a    = $taxonomy_counts[ $a->term_id ] ?? 0;
						$count_b    = $taxonomy_counts[ $b->term_id ] ?? 0;
						$comparison = $count_a <=> $count_b;
						break;

					case 'menu_order':
						$order_a    = $a->menu_order ?? 0;
						$order_b    = $b->menu_order ?? 0;
						$comparison = $order_a <=> $order_b;
						// Secondary sort by name when menu_order is equal.
						if ( 0 === $comparison ) {
							$comparison = strcasecmp( $a->name, $b->name );
						}
						break;

					case 'name':
					default:
						$comparison = strcasecmp( $a->name, $b->name );
						break;
				}

				return $comparison * $sort_order;
			}
		);

		return $terms;
	}
}
PK     \e      BlockTypes/CatalogSorting.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * CatalogSorting class.
 */
class CatalogSorting extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'catalog-sorting';

	/**
	 * 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 ) {
		ob_start();
		woocommerce_catalog_ordering( $attributes );
		$catalog_sorting = ob_get_clean();

		if ( ! $catalog_sorting ) {
			return;
		}

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );
		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class' => implode(
					' ',
					array_filter(
						[
							'woocommerce wc-block-catalog-sorting',
							esc_attr( $classes_and_styles['classes'] ),
						]
					)
				),
				'style' => esc_attr( $styles_and_classes['styles'] ?? '' ),
			)
		);

		return sprintf(
			'<div %1$s>%2$s</div>',
			$wrapper_attributes,
			$catalog_sorting
		);
	}

	/**
	 * 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;
	}
}
PK     \"    !  BlockTypes/PageContentWrapper.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * Used in templates to wrap page content. Allows content to be populated at template level.
 *
 * @internal
 */
class PageContentWrapper extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'page-content-wrapper';

	/**
	 * It isn't necessary to register block assets.
	 *
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \dо      "  BlockTypes/CartCrossSellsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartCrossSellsBlock class.
 */
class CartCrossSellsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-cross-sells-block';
}
PK     \l      "  BlockTypes/CheckoutFieldsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutFieldsBlock class.
 */
class CheckoutFieldsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-fields-block';
}
PK     \T9      BlockTypes/RatingFilter.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * PriceFilter class.
 */
class RatingFilter extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name  = 'rating-filter';
	const RATING_QUERY_VAR = 'rating_filter';

	/**
	 * 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;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string[]
	 */
	protected function get_block_type_style() {
		return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
	}
}
PK     \]*  *  )  BlockTypes/EmptyMiniCartContentsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Admin\Features\Features;

/**
 * EmptyMiniCartContentsBlock class.
 */
class EmptyMiniCartContentsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'empty-mini-cart-contents-block';

	/**
	 * Render the markup for the Filled Mini-Cart Contents block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( Features::is_enabled( 'experimental-iapi-mini-cart' ) ) {
			return $this->render_experimental_empty_mini_cart_contents( $attributes, $content, $block );
		}

		return $content;
	}

	/**
	 * Render the experimental interactivity API powered Filled Mini-Cart Contents block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render_experimental_empty_mini_cart_contents( $attributes, $content, $block ) {
		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'data-wp-bind--aria-hidden' => '!state.cartIsEmpty',
				'data-wp-bind--hidden'      => '!state.cartIsEmpty',
				'data-wp-interactive'       => 'woocommerce/mini-cart',
			)
		);

		ob_start();
		?>
		<div <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<div class="wc-block-mini-cart__empty-cart-wrapper">
				<?php
					// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
					echo $content;
				?>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}
}
PK     \P{Q&  &  .  BlockTypes/OrderConfirmation/CreateAccount.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;

use Automattic\WooCommerce\StoreApi\Utilities\OrderController;
use Automattic\WooCommerce\Admin\Features\Features;

/**
 * CreateAccount class.
 */
class CreateAccount extends AbstractOrderConfirmationBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'order-confirmation-create-account';

	/**
	 * Initialize this block type.
	 */
	protected function initialize() {
		parent::initialize();

		if ( $this->is_feature_enabled() ) {
			$this->initialize_hooks();
		}
	}

	/**
	 * Initialize hooks.
	 *
	 * @see https://developer.wordpress.org/reference/hooks/hooked_block/
	 */
	protected function initialize_hooks() {
		// This does not use the Block Hooks Trait used in mini cart. The implementation is simpler because we support
		// versions higher than WP 6.5 when hooks were introduced. They should be consolidated in the future.
		add_filter(
			'hooked_block_types',
			function ( $hooked_block_types, $relative_position, $anchor_block_type, $context ) {
				if ( 'after' !== $relative_position || 'woocommerce/order-confirmation-summary' !== $anchor_block_type || ! $context instanceof \WP_Block_Template ) {
					return $hooked_block_types;
				}

				if ( ! str_contains( $context->content, '<!-- wp:' . $this->get_full_block_name() ) ) {
					$hooked_block_types[] = $this->get_full_block_name();
				}

				return $hooked_block_types;
			},
			10,
			4
		);
		add_filter(
			'hooked_block_woocommerce/order-confirmation-create-account',
			function ( $parsed_hooked_block, $hooked_block_type, $relative_position ) {
				if ( 'after' !== $relative_position || is_null( $parsed_hooked_block ) ) {
					return $parsed_hooked_block;
				}

				/* translators: %s: Site title */
				$site_title_heading                  = sprintf( __( 'Create an account with %s', 'woocommerce' ), wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) );
				$parsed_hooked_block['innerContent'] = array(
					'<div class="wp-block-woocommerce-order-confirmation-create-account alignwide">
					<!-- wp:heading {"level":3} -->
                    <h3 class="wp-block-heading">' . esc_html( $site_title_heading ) . '</h3>
					<!-- /wp:heading -->
					<!-- wp:list {"className":"is-style-checkmark-list"} -->
					<ul class="wp-block-list is-style-checkmark-list"><!-- wp:list-item -->
                    <li>' . esc_html__( 'Faster future purchases', 'woocommerce' ) . '</li>
                    <!-- /wp:list-item -->
                    <!-- wp:list-item -->
                    <li>' . esc_html__( 'Securely save payment info', 'woocommerce' ) . '</li>
                    <!-- /wp:list-item -->
                    <!-- wp:list-item -->
                    <li>' . esc_html__( 'Track orders &amp; view shopping history', 'woocommerce' ) . '</li>
                    <!-- /wp:list-item --></ul>
                    <!-- /wp:list -->
                    </div>',
				);

					return $parsed_hooked_block;
			},
			10,
			4
		);
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string
	 */
	protected function get_block_type_script( $key = null ) {
		$script = [
			'handle'       => 'wc-order-confirmation-create-account-block-frontend',
			'path'         => $this->asset_api->get_block_asset_build_path( 'order-confirmation-create-account-frontend' ),
			'dependencies' => [],
		];
		return $key ? $script[ $key ] : $script;
	}

	/**
	 * Returns if delayed account creation is enabled.
	 *
	 * @return bool
	 */
	protected function is_feature_enabled() {
		return get_option( 'woocommerce_enable_delayed_account_creation', 'yes' ) === 'yes';
	}

	/**
	 * Process posted account form.
	 *
	 * @param \WC_Order $order Order object.
	 * @return \WP_Error|int
	 */
	protected function process_form_post( $order ) {
		if ( ! isset( $_POST['create-account'], $_POST['email'], $_POST['_wpnonce'] ) ) {
			return 0;
		}

		if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['_wpnonce'] ?? '' ) ), 'wc_create_account' ) ) {
			return new \WP_Error( 'invalid_nonce', __( 'Unable to create account. Please try again.', 'woocommerce' ) );
		}

		$user_email = sanitize_email( wp_unslash( $_POST['email'] ) );

		// Does order already have user?
		if ( $order->get_customer_id() ) {
			return new \WP_Error( 'order_already_has_user', __( 'This order is already linked to a user account.', 'woocommerce' ) );
		}

		// Check given details match the current viewed order.
		if ( $order->get_billing_email() !== $user_email ) {
			return new \WP_Error( 'email_mismatch', __( 'The email address provided does not match the email address on this order.', 'woocommerce' ) );
		}

		$generate_password = filter_var( get_option( 'woocommerce_registration_generate_password', 'no' ), FILTER_VALIDATE_BOOLEAN );

		if ( $generate_password ) {
			$password = ''; // Will be generated by wc_create_new_customer.
		} else {
			$password = wp_unslash( $_POST['password'] ?? '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

			if ( empty( $password ) || strlen( $password ) < 8 ) {
				return new \WP_Error( 'password_too_short', __( 'Password must be at least 8 characters.', 'woocommerce' ) );
			}
		}

		$customer_id = wc_create_new_customer(
			$user_email,
			'',
			$password,
			[
				'first_name' => $order->get_billing_first_name(),
				'last_name'  => $order->get_billing_last_name(),
				'source'     => 'delayed-account-creation',
			]
		);

		if ( is_wp_error( $customer_id ) ) {
			return $customer_id;
		}

		// Associate customer with the order.
		$order->set_customer_id( $customer_id );
		$order->save();

		// Associate addresses from the order with the customer.
		$order_controller = new OrderController();
		$order_controller->sync_customer_data_with_order( $order );

		// Set the customer auth cookie.
		wc_set_customer_auth_cookie( $customer_id );

		return $customer_id;
	}

	/**
	 * This renders the content of the block within the wrapper.
	 *
	 * @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
	 */
	protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
		if ( ! $permission || ! $this->is_feature_enabled() ) {
			return '';
		}

		// Check registration is possible for this order/customer, and if not, return early.
		if ( is_user_logged_in() || email_exists( $order->get_billing_email() ) ) {
			return '';
		}

		$result = $this->process_form_post( $order );
		$notice = '';

		if ( is_wp_error( $result ) ) {
			$notice = wc_print_notice( $result->get_error_message(), 'error', [], true );
		} elseif ( $result ) {
			return $this->render_confirmation();
		}

		$processor = new \WP_HTML_Tag_Processor(
			$content .
			'<div class="wc-block-order-confirmation-create-account-form-wrapper">' .
				$notice .
				'<div class="wc-block-order-confirmation-create-account-form"></div>' .
			'</div>'
		);

		if ( ! $processor->next_tag( array( 'class_name' => 'wp-block-woocommerce-order-confirmation-create-account' ) ) ) {
			return $content;
		}

		$processor->set_attribute( 'class', '' );
		$processor->set_attribute( 'style', '' );
		$processor->add_class( 'wc-block-order-confirmation-create-account-content' );

		if ( ! $processor->next_tag( array( 'class_name' => 'wc-block-order-confirmation-create-account-form' ) ) ) {
			return $content;
		}

		$processor->set_attribute( 'data-customer-email', $order->get_billing_email() );
		$processor->set_attribute( 'data-nonce-token', wp_create_nonce( 'wc_create_account' ) );

		if ( ! empty( $attributes['hasDarkControls'] ) ) {
			$processor->add_class( 'has-dark-controls' );
		}

		return $processor->get_updated_html();
	}

	/**
	 * Render the block when an account has been registered.
	 *
	 * @return string
	 */
	protected function render_confirmation() {
		$content  = '<div class="wc-block-order-confirmation-create-account-success" id="create-account">';
		$content .= '<h3>' . esc_html__( 'Your account has been successfully created', 'woocommerce' ) . '</h3>';
		$content .= '<p>' . sprintf(
			/* translators: 1: link to my account page, 2: link to shipping and billing addresses, 3: link to account details, 4: closing tag */
			esc_html__( 'You can now %1$sview your recent orders%4$s, manage your %2$sshipping and billing addresses%4$s, and edit your %3$spassword and account details%4$s.', 'woocommerce' ),
			'<a href="' . esc_url( wc_get_endpoint_url( 'orders', '', wc_get_page_permalink( 'myaccount' ) ) ) . '">',
			'<a href="' . esc_url( wc_get_endpoint_url( 'edit-address', '', wc_get_page_permalink( 'myaccount' ) ) ) . '">',
			'<a href="' . esc_url( wc_get_endpoint_url( 'edit-account', '', wc_get_page_permalink( 'myaccount' ) ) ) . '">',
			'</a>'
		) . '</p>';
		$content .= '</div>';

		return $content;
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );

		$this->asset_data_registry->add( 'delayedAccountCreationEnabled', $this->is_feature_enabled() );
		$this->asset_data_registry->add( 'registrationGeneratePassword', filter_var( get_option( 'woocommerce_registration_generate_password' ), FILTER_VALIDATE_BOOLEAN ) );
	}
}
PK     \([&  &  '  BlockTypes/OrderConfirmation/Status.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * Status class.
 */
class Status extends AbstractOrderConfirmationBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'order-confirmation-status';

	/**
	 * This block uses a custom render method so that the email verification form can be appended to the block. This does
	 * not inherit styles from the parent 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();
		$classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );

		if ( isset( $attributes['align'] ) ) {
			$classname .= " align{$attributes['align']}";
		}

		$block = parent::render( $attributes, $content, $block );

		if ( ! $block ) {
			return '';
		}

		$additional_content = $this->render_confirmation_notice( $order );

		if ( $additional_content ) {
			$block = $block . sprintf(
				'<div class="wc-block-order-confirmation-status-description %1$s">%2$s</div>',
				esc_attr( trim( $classname ) ),
				$additional_content
			);
		}

		return $block;
	}

	/**
	 * This renders the content of the block within the wrapper.
	 *
	 * @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
	 */
	protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
		if ( ! $permission ) {
			// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
			return '<p>' . wp_kses_post( apply_filters( 'woocommerce_thankyou_order_received_text', esc_html__( 'Thank you. Your order has been received.', 'woocommerce' ), null ) ) . '</p>';
		}

		$content = $this->get_hook_content( 'woocommerce_before_thankyou', [ $order->get_id() ] );
		$status  = $order->get_status();

		// Unlike the core handling, this includes some extra messaging for completed orders so the text is appropriate for other order statuses.
		switch ( $status ) {
			case 'cancelled':
				$content .= '<h1>' . wp_kses_post(
					/**
					 * Filter the title shown after a checkout is complete.
					 *
					 * @since 9.6.0
					 *
					 * @param string         $title The title.
					 * @param WC_Order|false $order The order created during checkout, or false if order data is not available.
					 */
					apply_filters(
						'woocommerce_thankyou_order_received_title',
						esc_html__( 'Order cancelled', 'woocommerce' ),
						$order
					)
				) . '</h1>';
				$content .= '<p>' . wp_kses_post(
						// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
					apply_filters(
						'woocommerce_thankyou_order_received_text',
						esc_html__( 'Your order has been cancelled.', 'woocommerce' ),
						$order
					)
				) . '</p>';
				break;
			case 'refunded':
					$content .= '<h1>' . wp_kses_post(
						// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
						apply_filters(
							'woocommerce_thankyou_order_received_title',
							esc_html__( 'Order refunded', 'woocommerce' ),
							$order
						)
					) . '</h1>';
					$content .= '<p>' . wp_kses_post(
						sprintf(
							// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
							apply_filters(
								'woocommerce_thankyou_order_received_text',
								// translators: %s: date and time of the order refund.
								esc_html__( 'Your order was refunded %s.', 'woocommerce' ),
								$order
							),
							wc_format_datetime( $order->get_date_modified() )
						)
					) . '</p>';
				break;
			case 'completed':
				$content .= '<h1>' . wp_kses_post(
					// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
					apply_filters(
						'woocommerce_thankyou_order_received_title',
						esc_html__( 'Order completed', 'woocommerce' ),
						$order
					)
				) . '</h1>';
				$content .= '<p>' . wp_kses_post(
					// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
					apply_filters(
						'woocommerce_thankyou_order_received_text',
						esc_html__( 'Thank you. Your order has been fulfilled.', 'woocommerce' ),
						$order
					)
				) . '</p>';
				break;
			case 'failed':
				// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
				$order_received_text = apply_filters( 'woocommerce_thankyou_order_received_text', esc_html__( 'Your order cannot be processed as the originating bank/merchant has declined your transaction. Please attempt your purchase again.', 'woocommerce' ), null );
				$actions             = '<a href="' . esc_url( $order->get_checkout_payment_url() ) . '" class="button">' . esc_html__( 'Try again', 'woocommerce' ) . '</a> ';

				if ( wc_get_page_permalink( 'myaccount' ) ) {
					$actions .= '<a href="' . esc_url( wc_get_page_permalink( 'myaccount' ) ) . '" class="button">' . esc_html__( 'My account', 'woocommerce' ) . '</a> ';
				}
				$content .= '<h1>' . wp_kses_post(
					// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
					apply_filters(
						'woocommerce_thankyou_order_received_title',
						esc_html__( 'Order failed', 'woocommerce' ),
						$order
					)
				) . '</h1>';
				$content .= '
				<p>' . $order_received_text . '</p>
				<p class="wc-block-order-confirmation-status__actions">' . $actions . '</p>
			';
				break;
			default:
				$content .= '<h1>' . wp_kses_post(
					// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
					apply_filters(
						'woocommerce_thankyou_order_received_title',
						esc_html__( 'Order received', 'woocommerce' ),
						$order
					)
				) . '</h1>';
				$content .= '<p>' . wp_kses_post(
					// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
					apply_filters(
						'woocommerce_thankyou_order_received_text',
						esc_html__( 'Thank you. Your order has been received.', 'woocommerce' ),
						$order
					)
				) . '</p>';
				break;
		}

		return $content;
	}

	/**
	 * This is what gets rendered when the order does not exist.
	 *
	 * @return string
	 */
	protected function render_content_fallback() {
		// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
		return '<p>' . esc_html__( 'Please check your email for the order confirmation.', 'woocommerce' ) . '</p>';
	}

	/**
	 * If the order is invalid or there is no permission to view the details, tell the user to check email or log-in.
	 *
	 * @param \WC_Order|null $order Order object.
	 * @return string
	 */
	protected function render_confirmation_notice( $order = null ) {
		if ( ! $order ) {
			$content = '<p>' . esc_html__( 'If you\'ve just placed an order, give your email a quick check for the confirmation.', 'woocommerce' );

			if ( wc_get_page_permalink( 'myaccount' ) ) {
				$content .= ' ' . sprintf(
					/* translators: 1: opening a link tag 2: closing a link tag */
					esc_html__( 'Have an account with us? %1$sLog in here to view your order details%2$s.', 'woocommerce' ),
					'<a href="' . esc_url( wc_get_page_permalink( 'myaccount' ) ) . '" class="button">',
					'</a>'
				);
			}

			$content .= '</p>';

			return $content;
		}

		$permission = $this->get_view_order_permissions( $order );

		if ( $permission ) {
			return '';
		}

		$verification_required  = $this->email_verification_required( $order );
		$verification_permitted = $this->email_verification_permitted( $order );
		$my_account_page        = wc_get_page_permalink( 'myaccount' );

		$content  = '<p>';
		$content .= esc_html__( 'Great news! Your order has been received, and a confirmation will be sent to your email address.', 'woocommerce' );
		$content .= $my_account_page ? ' ' . sprintf(
			/* translators: 1: opening a link tag 2: closing a link tag */
			esc_html__( 'Have an account with us? %1$sLog in here%2$s to view your order.', 'woocommerce' ),
			'<a href="' . esc_url( $my_account_page ) . '" class="button">',
			'</a>'
		) : '';

		if ( $verification_required && $verification_permitted ) {
			$content .= ' ' . esc_html__( 'Alternatively, confirm the email address linked to the order below.', 'woocommerce' );
		}

		$content .= '</p>';

		if ( $verification_required && $verification_permitted ) {
			$content .= $this->render_verification_form();
		}

		return $content;
	}

	/**
	 * Email verification for guest users.
	 *
	 * @return string
	 */
	protected function render_verification_form() {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing
		$check_submission_notice = ! empty( $_POST ) ? wc_print_notice( esc_html__( 'We were unable to verify the email address you provided. Please try again.', 'woocommerce' ), 'error', [], true ) : '';

		return '<form method="post" class="woocommerce-form woocommerce-verify-email">' .
			$check_submission_notice .
			sprintf(
				'<p class="form-row verify-email">
					<label for="%1$s">%2$s</label>
					<input type="email" name="email" id="%1$s" autocomplete="email" class="input-text" required />
				</p>',
				esc_attr( 'verify-email' ),
				esc_html__( 'Email address', 'woocommerce' ) . '&nbsp;<span class="required">*</span>'
			) .
			sprintf(
				'<p class="form-row login-submit">
					<input type="submit" name="wp-submit" id="%1$s" class="button button-primary %4$s" value="%2$s" />
					%3$s
				</p>',
				esc_attr( 'verify-email-submit' ),
				esc_html__( 'Confirm email and view order', 'woocommerce' ),
				wp_nonce_field( 'wc_verify_email', '_wpnonce', true, false ),
				esc_attr( wc_wp_theme_get_element_class_name( 'button' ) )
			) .
			'</form>';
	}
}
PK     \O.  .  0  BlockTypes/OrderConfirmation/ShippingAddress.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;

use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;

/**
 * ShippingAddress class.
 */
class ShippingAddress extends AbstractOrderConfirmationBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'order-confirmation-shipping-address';

	/**
	 * This renders the content of the block within the wrapper.
	 *
	 * @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
	 */
	protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
		if ( ! $permission || ! $order->needs_shipping_address() || ! $order->has_shipping_address() ) {
			return $this->render_content_fallback();
		}

		$address = '<address>' . wp_kses_post( $order->get_formatted_shipping_address() ) . '</address>';
		$phone   = $order->get_shipping_phone() ? '<p class="woocommerce-customer-details--phone">' . esc_html( $order->get_shipping_phone() ) . '</p>' : '';

		$controller = Package::container()->get( CheckoutFields::class );
		$custom     = $this->render_additional_fields(
			$controller->get_order_additional_fields_with_values( $order, 'address', 'shipping', 'view' )
		);

		return $address . $phone . $custom;
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );
		$this->asset_data_registry->add( 'additionalAddressFields', Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'address' ) );
	}
}
PK     \VN    /  BlockTypes/OrderConfirmation/BillingAddress.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;

use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;

/**
 * BillingAddress class.
 */
class BillingAddress extends AbstractOrderConfirmationBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'order-confirmation-billing-address';

	/**
	 * This renders the content of the block within the wrapper.
	 *
	 * @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
	 */
	protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
		if ( ! $permission || ! $order->has_billing_address() ) {
			return '';
		}

		$address = '<address>' . wp_kses_post( $order->get_formatted_billing_address() ) . '</address>';
		$phone   = $order->get_billing_phone() ? '<p class="woocommerce-customer-details--phone">' . esc_html( $order->get_billing_phone() ) . '</p>' : '';

		$controller = Package::container()->get( CheckoutFields::class );
		$custom     = $this->render_additional_fields(
			$controller->get_order_additional_fields_with_values( $order, 'address', 'billing', 'view' )
		);

		return $address . $phone . $custom;
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );
		$this->asset_data_registry->add( 'additionalAddressFields', Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'address' ) );
	}
}
PK     \g.N    6  BlockTypes/OrderConfirmation/AdditionalInformation.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;

/**
 * AdditionalInformation class.
 */
class AdditionalInformation extends AbstractOrderConfirmationBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'order-confirmation-additional-information';

	/**
	 * This renders the content of the block within the wrapper.
	 *
	 * @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
	 */
	protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
		if ( ! $permission ) {
			return $content;
		}

		$this->remove_core_hooks();
		$content .= $this->get_hook_content( 'woocommerce_thankyou_' . $order->get_payment_method(), [ $order->get_id() ] );
		$content .= $this->get_hook_content( 'woocommerce_thankyou', [ $order->get_id() ] );
		$this->restore_core_hooks();

		return $content;
	}

	/**
	 * Remove core hooks from the thankyou page.
	 */
	protected function remove_core_hooks() {
		remove_action( 'woocommerce_thankyou', 'woocommerce_order_details_table', 10 );
	}

	/**
	 * Restore core hooks from the thankyou page.
	 */
	protected function restore_core_hooks() {
		add_action( 'woocommerce_thankyou', 'woocommerce_order_details_table', 10 );
	}
}
PK     \!m$  $  '  BlockTypes/OrderConfirmation/Totals.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * Totals class.
 */
class Totals extends AbstractOrderConfirmationBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'order-confirmation-totals';

	/**
	 * This renders the content of the block within the wrapper.
	 *
	 * @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
	 */
	protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
		if ( ! $permission ) {
			return $this->render_content_fallback();
		}
		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, [ 'border_color', 'border_radius', 'border_width', 'border_style', 'background_color', 'text_color' ] );

		return $this->get_hook_content( 'woocommerce_order_details_before_order_table', [ $order ] ) . '
			<table cellspacing="0" class="wc-block-order-confirmation-totals__table ' . esc_attr( $classes_and_styles['classes'] ) . '" style="' . esc_attr( $classes_and_styles['styles'] ) . '">
				<thead>
					<tr>
						<th class="wc-block-order-confirmation-totals__product">' . esc_html__( 'Product', 'woocommerce' ) . '</th>
						<th class="wc-block-order-confirmation-totals__total">' . esc_html__( 'Total', 'woocommerce' ) . '</th>
					</tr>
				</thead>
				<tbody>
					' . $this->get_hook_content( 'woocommerce_order_details_before_order_table_items', [ $order ] ) . '
					' . $this->render_order_details_table_items( $order ) . '
					' . $this->get_hook_content( 'woocommerce_order_details_after_order_table_items', [ $order ] ) . '
				</tbody>
				<tfoot>
					' . $this->render_order_details_table_totals( $order ) . '
					</tfoot>
					</table>
			' . $this->render_order_details_customer_note( $order ) . '
			' . $this->get_hook_content( 'woocommerce_order_details_after_order_table', [ $order ] ) . '
			' . $this->get_hook_content( 'woocommerce_after_order_details', [ $order ] ) . '
		';
	}

	/**
	 * Enqueue frontend assets for this block, just in time for rendering.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 * @return string
	 */
	protected function get_inline_styles( array $attributes ) {
		$link_classes_and_styles       = StyleAttributesUtils::get_link_color_class_and_style( $attributes );
		$link_hover_classes_and_styles = StyleAttributesUtils::get_link_hover_color_class_and_style( $attributes );
		$border_classes_and_styles     = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, [ 'border_color', 'border_radius', 'border_width', 'border_style' ] );

		return '
			.wc-block-order-confirmation-totals__table a {' . $link_classes_and_styles['style'] . '}
			.wc-block-order-confirmation-totals__table a:hover, .wc-block-order-confirmation-totals__table a:focus {' . $link_hover_classes_and_styles['style'] . '}
			.wc-block-order-confirmation-totals__table {' . $border_classes_and_styles['styles'] . '}
			.wc-block-order-confirmation-totals__table th, .wc-block-order-confirmation-totals__table td {' . $border_classes_and_styles['styles'] . '}
		';
	}

	/**
	 * Enqueue frontend assets for this block, just in time for rendering.
	 *
	 * @param array     $attributes  Any attributes that currently are available from the block.
	 * @param string    $content    The block content.
	 * @param \WP_Block $block    The block object.
	 */
	protected function enqueue_assets( array $attributes, $content, $block ) {
		parent::enqueue_assets( $attributes, $content, $block );

		$styles = $this->get_inline_styles( $attributes );

		wp_add_inline_style( 'wc-blocks-style', $styles );
	}

	/**
	 * Render order details table items.
	 *
	 * Loosely based on the templates order-details.php and order-details-item.php from core.
	 *
	 * @param \WC_Order $order Order object.
	 * @return string
	 */
	protected function render_order_details_table_items( $order ) {
		$return      = '';
		$order_items = array_filter(
		// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
			$order->get_items( apply_filters( 'woocommerce_purchase_order_item_types', 'line_item' ) ),
			function( $item ) {
                // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
				return apply_filters( 'woocommerce_order_item_visible', true, $item );
			}
		);

		foreach ( $order_items as $item_id => $item ) {
			$product = $item->get_product();
			$return .= $this->render_order_details_table_item( $order, $item_id, $item, $product );
		}

		return $return;
	}

	/**
	 * Render an item in the order details table.
	 *
	 * @param \WC_Order         $order Order object.
	 * @param integer           $item_id Item ID.
	 * @param \WC_Order_Item    $item Item object.
	 * @param \WC_Product|false $product Product object if it exists.
	 * @return string
	 */
	protected function render_order_details_table_item( $order, $item_id, $item, $product ) {
		$is_visible = $product && $product->is_visible();
		// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
		$row_class = apply_filters( 'woocommerce_order_item_class', 'woocommerce-table__line-item order_item', $item, $order );
		// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
		$product_permalink = apply_filters( 'woocommerce_order_item_permalink', $is_visible ? $product->get_permalink( $item ) : '', $item, $order );

		// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
		$item_name    = apply_filters(
			'woocommerce_order_item_name',
			$product_permalink ? sprintf( '<a href="%s">%s</a>', $product_permalink, $item->get_name() ) : $item->get_name(),
			$item,
			$is_visible
		);
		$qty          = $item->get_quantity();
		$refunded_qty = $order->get_qty_refunded_for_item( $item_id );
		$qty_display  = $refunded_qty ? '<del>' . esc_html( $qty ) . '</del> <ins>' . esc_html( $qty - ( $refunded_qty * -1 ) ) . '</ins>' : esc_html( $qty );
		// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
		$item_qty = apply_filters(
			'woocommerce_order_item_quantity_html',
			'<strong class="product-quantity">' . sprintf( '&times;&nbsp;%s', $qty_display ) . '</strong>',
			$item
		);

		return '
			<tr class="' . esc_attr( $row_class ) . '">
				<td class="wc-block-order-confirmation-totals__product">
					' . wp_kses_post( $item_name ) . '&nbsp;
					' . wp_kses_post( $item_qty ) . '
					' . $this->get_hook_content( 'woocommerce_order_item_meta_start', [ $item_id, $item, $order, false ] ) . '
					' . wc_display_item_meta( $item, [ 'echo' => false ] ) . '
					' . $this->get_hook_content( 'woocommerce_order_item_meta_end', [ $item_id, $item, $order, false ] ) . '
					' . $this->render_order_details_table_item_purchase_note( $order, $product ) . '
				</td>
				<td class="wc-block-order-confirmation-totals__total">
					' . wp_kses_post( $order->get_formatted_line_subtotal( $item ) ) . '
				</td>
			</tr>
		';
	}

	/**
	 * Render an item purchase note.
	 *
	 * @param \WC_Order         $order Order object.
	 * @param \WC_Product|false $product Product object if it exists.
	 * @return string
	 */
	protected function render_order_details_table_item_purchase_note( $order, $product ) {
		// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
		$show_purchase_note = $order->has_status( apply_filters( 'woocommerce_purchase_note_order_statuses', array( 'completed', 'processing' ) ) );
		$purchase_note      = $product ? $product->get_purchase_note() : '';

		return $show_purchase_note && $purchase_note ? '<div class="product-purchase-note">' . wp_kses_post( $purchase_note ) . '</div>' : '';
	}

	/**
	 * Render order details table totals.
	 *
	 * @param \WC_Order $order Order object.
	 * @return string
	 */
	protected function render_order_details_table_totals( $order ) {
		add_filter( 'woocommerce_order_shipping_to_display_shipped_via', '__return_empty_string' );

		$return     = '';
		$total_rows = array_diff_key(
			$order->get_order_item_totals(),
			array(
				'cart_subtotal'  => '',
				'payment_method' => '',
			)
		);

		foreach ( $total_rows as $total ) {
			$return .= '
				<tr>
					<th class="wc-block-order-confirmation-totals__label" scope="row">' . esc_html( $total['label'] ) . '</th>
					<td class="wc-block-order-confirmation-totals__total">' . wp_kses_post( $total['value'] ) . '</td>
				</tr>
			';
		}

		return $return;
	}

	/**
	 * Render customer note.
	 *
	 * @param \WC_Order $order Order object.
	 * @return string
	 */
	protected function render_order_details_customer_note( $order ) {
		if ( ! $order->get_customer_note() ) {
			return '';
		}

		return '<div class="wc-block-order-confirmation-order-note">' .
					'<p class="wc-block-order-confirmation-order-note__label">' .
						esc_html__( 'Note:', 'woocommerce' ) .
					'</p>' .
					'<p>' . wp_kses( nl2br( wptexturize( $order->get_customer_note() ) ), [ 'br' => [] ] ) . '</p>' .
				'</div>';
	}
}
PK     \&  &  ?  BlockTypes/OrderConfirmation/AbstractOrderConfirmationBlock.phpnu [        <?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 {
	/**
	 * 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();
		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );

		return $block_content ? sprintf(
			'<div class="wp-block-%5$s-%4$s wc-block-%4$s %1$s" style="%2$s">%3$s</div>',
			esc_attr( $classes_and_styles['classes'] ),
			esc_attr( $classes_and_styles['styles'] ),
			$block_content,
			esc_attr( $this->block_name ),
			esc_attr( $this->namespace )
		) : '';
	}

	/**
	 * 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'], $_POST['_wpnonce'] ) ) {
			return false;
		}

		$nonce_value = sanitize_key( wp_unslash( $_POST['_wpnonce'] ?? '' ) );

		if ( ! wp_verify_nonce( $nonce_value, 'wc_verify_email' ) && ! wp_verify_nonce( $nonce_value, 'wc_create_account' ) ) {
			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;
	}

	/**
	 * 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'] )
		);
	}
}
PK     \5~  ~  *  BlockTypes/OrderConfirmation/Downloads.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * Downloads class.
 */
class Downloads extends AbstractOrderConfirmationBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'order-confirmation-downloads';

	/**
	 * This renders the content of the block within the wrapper.
	 *
	 * @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
	 */
	protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
		$show_downloads = $order && $order->has_downloadable_item() && $order->is_download_permitted();
		$downloads      = $order ? $order->get_downloadable_items() : [];

		if ( ! $permission || ! $show_downloads ) {
			return $this->render_content_fallback();
		}

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, [ 'border_color', 'border_radius', 'border_width', 'border_style', 'background_color', 'text_color' ] );

		return '
			<table cellspacing="0" class="wc-block-order-confirmation-downloads__table ' . esc_attr( $classes_and_styles['classes'] ) . '" style="' . esc_attr( $classes_and_styles['styles'] ) . '">
				<thead>
					<tr>
						' . $this->render_order_downloads_column_headers( $order ) . '
					</td>
				</thead>
				<tbody>
					' . $this->render_order_downloads( $order, $downloads ) . '
				</tbody>
			</table>
		';
	}

	/**
	 * Enqueue frontend assets for this block, just in time for rendering.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 * @return string
	 */
	protected function get_inline_styles( array $attributes ) {
		$link_classes_and_styles       = StyleAttributesUtils::get_link_color_class_and_style( $attributes );
		$link_hover_classes_and_styles = StyleAttributesUtils::get_link_hover_color_class_and_style( $attributes );
		$border_classes_and_styles     = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, [ 'border_color', 'border_radius', 'border_width', 'border_style' ] );

		return '
			.wc-block-order-confirmation-downloads__table a {' . $link_classes_and_styles['style'] . '}
			.wc-block-order-confirmation-downloads__table a:hover, .wc-block-order-confirmation-downloads__table a:focus {' . $link_hover_classes_and_styles['style'] . '}
			.wc-block-order-confirmation-downloads__table {' . $border_classes_and_styles['styles'] . '}
			.wc-block-order-confirmation-downloads__table th, .wc-block-order-confirmation-downloads__table td {' . $border_classes_and_styles['styles'] . '}
		';
	}

	/**
	 * Enqueue frontend assets for this block, just in time for rendering.
	 *
	 * @param array     $attributes  Any attributes that currently are available from the block.
	 * @param string    $content    The block content.
	 * @param \WP_Block $block    The block object.
	 */
	protected function enqueue_assets( array $attributes, $content, $block ) {
		parent::enqueue_assets( $attributes, $content, $block );

		$styles = $this->get_inline_styles( $attributes );

		wp_add_inline_style( 'wc-blocks-style', $styles );
	}

	/**
	 * Render column headers for downloads table.
	 *
	 * @return string
	 */
	protected function render_order_downloads_column_headers() {
		$columns = wc_get_account_downloads_columns();
		$return  = '';

		foreach ( $columns as $column_id => $column_name ) {
			$return .= '<th class="' . esc_attr( $column_id ) . '"><span class="nobr">' . esc_html( $column_name ) . '</span></th>';
		}

		return $return;
	}

	/**
	 * Render downloads.
	 *
	 * @param \WC_Order $order Order object.
	 * @param array     $downloads Array of downloads.
	 * @return string
	 */
	protected function render_order_downloads( $order, $downloads ) {
		$return = '';
		foreach ( $downloads as $download ) {
			$return .= '<tr>' . $this->render_order_download_row( $download ) . '</tr>';
		}
		return $return;
	}

	/**
	 * Render a download row in the table.
	 *
	 * @param array $download Download data.
	 * @return string
	 */
	protected function render_order_download_row( $download ) {
		$return = '';

		foreach ( wc_get_account_downloads_columns() as $column_id => $column_name ) {
			$return .= '<td class="' . esc_attr( $column_id ) . '" data-title="' . esc_attr( $column_name ) . '">';

			if ( has_action( 'woocommerce_account_downloads_column_' . $column_id ) ) {
				$return .= $this->get_hook_content( 'woocommerce_account_downloads_column_' . $column_id, [ $download ] );
			} else {
				switch ( $column_id ) {
					case 'download-product':
						if ( $download['product_url'] ) {
							$return .= '<a href="' . esc_url( $download['product_url'] ) . '">' . esc_html( $download['product_name'] ) . '</a>';
						} else {
							$return .= esc_html( $download['product_name'] );
						}
						break;
					case 'download-file':
						$return .= '<a href="' . esc_url( $download['download_url'] ) . '" class="woocommerce-MyAccount-downloads-file button alt">' . esc_html( $download['download_name'] ) . '</a>';
						break;
					case 'download-remaining':
						$return .= is_numeric( $download['downloads_remaining'] ) ? esc_html( $download['downloads_remaining'] ) : esc_html__( '&infin;', 'woocommerce' );
						break;
					case 'download-expires':
						if ( ! empty( $download['access_expires'] ) ) {
							$return .= '<time datetime="' . esc_attr( gmdate( 'Y-m-d', strtotime( $download['access_expires'] ) ) ) . '" title="' . esc_attr( strtotime( $download['access_expires'] ) ) . '">' . esc_html( date_i18n( get_option( 'date_format' ), strtotime( $download['access_expires'] ) ) ) . '</time>';
						} else {
							$return .= esc_html__( 'Never', 'woocommerce' );
						}
						break;
				}
			}

			$return .= '</td>';
		}

		return $return;
	}
}
PK     \    .  BlockTypes/OrderConfirmation/TotalsWrapper.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;

/**
 * TotalsWrapper class.
 */
class TotalsWrapper extends AbstractOrderConfirmationBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'order-confirmation-totals-wrapper';

	/**
	 * This renders the content of the totals wrapper.
	 *
	 * @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.
	 */
	protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
		if ( ! $permission ) {
			return '';
		}
		return $content;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \i膐    8  BlockTypes/OrderConfirmation/AdditionalFieldsWrapper.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;

use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;

/**
 * AdditionalFieldsWrapper class.
 */
class AdditionalFieldsWrapper extends AbstractOrderConfirmationBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'order-confirmation-additional-fields-wrapper';

	/**
	 * This renders the content of the downloads wrapper.
	 *
	 * @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.
	 */
	protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
		if ( ! $permission ) {
			return '';
		}

		// Contact and additional fields are currently grouped in this section.
		// If none of the additional fields for contact or order have values then the "Additional fields' section should
		// not show in the order confirmation.
		$additional_field_values = array_merge(
			Package::container()->get( CheckoutFields::class )->get_order_additional_fields_with_values( $order, 'contact' ),
			Package::container()->get( CheckoutFields::class )->get_order_additional_fields_with_values( $order, 'order' )
		);

		return empty( $additional_field_values ) ? '' : $content;
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );
		$this->asset_data_registry->add( 'additionalFields', Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'order' ) );
		$this->asset_data_registry->add( 'additionalContactFields', Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'contact' ) );
	}
}
PK     \    0  BlockTypes/OrderConfirmation/ShippingWrapper.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;

/**
 * ShippingWrapper class.
 */
class ShippingWrapper extends AbstractOrderConfirmationBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'order-confirmation-shipping-wrapper';

	/**
	 * This renders the content of the shipping wrapper.
	 *
	 * @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.
	 */
	protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
		if ( ! $order || ! $order->has_shipping_address() || ! $order->needs_shipping_address() || ! $permission ) {
			return '';
		}
		return $content;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \TS    /  BlockTypes/OrderConfirmation/BillingWrapper.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;

/**
 * BillingWrapper class.
 */
class BillingWrapper extends AbstractOrderConfirmationBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'order-confirmation-billing-wrapper';

	/**
	 * This renders the content of the billing wrapper.
	 *
	 * @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.
	 */
	protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
		if ( ! $order || ! $order->has_billing_address() || ! $permission ) {
			return '';
		}
		return $content;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \J    1  BlockTypes/OrderConfirmation/DownloadsWrapper.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;

/**
 * DownloadsWrapper class.
 */
class DownloadsWrapper extends AbstractOrderConfirmationBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'order-confirmation-downloads-wrapper';

	/**
	 * See if the store has a downloadable product. This controls if we bother to show a preview in the editor.
	 *
	 * @return boolean
	 */
	protected function store_has_downloadable_products() {
		global $wpdb;

		if ( get_option( 'woocommerce_product_lookup_table_is_generating' ) ) {
			// The underlying SQL is slower than querying `wc_product_meta_lookup`, so caching is used for performance.
			$has_downloadable_products = wp_cache_get( 'woocommerce_has_downloadable_products', 'woocommerce' );
			if ( false === $has_downloadable_products ) {
				$has_downloadable_products = (bool) $wpdb->get_var(
					"SELECT posts.ID
						FROM {$wpdb->posts} as posts
						INNER JOIN {$wpdb->postmeta} as postmeta ON posts.ID = postmeta.post_id
					 WHERE
						    postmeta.meta_key   = '_downloadable'
						AND postmeta.meta_value = 'yes'
						AND posts.post_type     = 'product'
						AND posts.post_status   = 'publish'
						LIMIT 1"
				);
				$has_downloadable_products = $has_downloadable_products ? 'yes' : 'no';
				wp_cache_set( 'woocommerce_has_downloadable_products', $has_downloadable_products, 'woocommerce', HOUR_IN_SECONDS );
			}
			$has_downloadable_products = 'yes' === $has_downloadable_products;
		} else {
			$has_downloadable_products = (bool) $wpdb->get_var(
				"SELECT product_id FROM {$wpdb->wc_product_meta_lookup} WHERE downloadable = 1 LIMIT 1",
			);
		}

		return $has_downloadable_products;
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );

		$this->asset_data_registry->add( 'storeHasDownloadableProducts', $this->store_has_downloadable_products() );
	}

	/**
	 * This renders the content of the downloads wrapper.
	 *
	 * @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.
	 */
	protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
		$show_downloads = $order && $order->has_downloadable_item() && $order->is_download_permitted();

		if ( ! $show_downloads || ! $permission ) {
			return '';
		}

		return $content;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \2Ł    (  BlockTypes/OrderConfirmation/Summary.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;

/**
 * Summary class.
 */
class Summary extends AbstractOrderConfirmationBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'order-confirmation-summary';

	/**
	 * This renders the content of the block within the wrapper.
	 *
	 * @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
	 */
	protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
		if ( ! $permission ) {
			return '';
		}

		$content  = '<ul class="wc-block-order-confirmation-summary-list">';
		$content .= $this->render_summary_row( __( 'Order #:', 'woocommerce' ), $order->get_order_number() );
		$content .= $this->render_summary_row( __( 'Date:', 'woocommerce' ), wc_format_datetime( $order->get_date_created() ) );
		$content .= $this->render_summary_row( __( 'Total:', 'woocommerce' ), $order->get_formatted_order_total() );
		$content .= $this->render_summary_row( __( 'Email:', 'woocommerce' ), $order->get_billing_email() );
		$content .= $this->render_summary_row( __( 'Payment:', 'woocommerce' ), $order->get_payment_method_title() );
		$content .= '</ul>';

		return $content;
	}

	/**
	 * Render row in the order summary.
	 *
	 * @param string $name name of row.
	 * @param string $value value of row.
	 * @return string
	 */
	protected function render_summary_row( $name, $value ) {
		return $value ? '<li class="wc-block-order-confirmation-summary-list-item"><span class="wc-block-order-confirmation-summary-list-item__key">' . esc_html( $name ) . '</span> <span class="wc-block-order-confirmation-summary-list-item__value">' . wp_kses_post( $value ) . '</span></li>' : '';
	}
}
PK     \~V  V  1  BlockTypes/OrderConfirmation/AdditionalFields.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;

use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;

/**
 * AdditionalFields class.
 */
class AdditionalFields extends AbstractOrderConfirmationBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'order-confirmation-additional-fields';

	/**
	 * This renders the content of the block within the wrapper.
	 *
	 * @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
	 */
	protected function render_content( $order, $permission = false, $attributes = [], $content = '' ) {
		if ( ! $permission ) {
			return $content;
		}

		/**
		 * Service class managing checkout fields and its related extensibility points.
		 *
		 * @var CheckoutFields $controller
		 */
		$controller = Package::container()->get( CheckoutFields::class );
		$content   .= $this->render_additional_fields(
			$controller->filter_fields_for_order_confirmation(
				array_merge(
					$controller->get_order_additional_fields_with_values( $order, 'contact', 'other', 'view' ),
					$controller->get_order_additional_fields_with_values( $order, 'order', 'other', 'view' ),
				),
				array(
					'caller'     => 'AdditionalFields::render_content',
					'order'      => $order,
					'permission' => $permission,
					'attributes' => $attributes,
				)
			)
		);

		return $content;
	}
}
PK     \X
    #  BlockTypes/ProductRatingCounter.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
 * ProductRatingCounter class.
 */
class ProductRatingCounter extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-rating-counter';

	/**
	 * API version name.
	 *
	 * @var string
	 */
	protected $api_version = '3';

	/**
	 * Get the block's attributes.
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 * @return array  Block attributes merged with defaults.
	 */
	private function parse_attributes( $attributes ) {
		// These should match what's set in JS `registerBlockType`.
		$defaults = array(
			'productId'                           => 0,
			'isDescendentOfQueryLoop'             => false,
			'textAlign'                           => '',
			'isDescendentOfSingleProductBlock'    => false,
			'isDescendentOfSingleProductTemplate' => false,
		);

		return wp_parse_args( $attributes, $defaults );
	}

	/**
	 * Overwrite parent method to prevent script registration.
	 *
	 * It is necessary to register and enqueues assets during the render
	 * phase because we want to load assets only if the block has the content.
	 */
	protected function register_block_type_assets() {
		return null;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Register the context.
	 */
	protected function get_block_type_uses_context() {
		return [ 'query', 'queryId', 'postId' ];
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! empty( $content ) ) {
			parent::register_block_type_assets();
			$this->register_chunk_translations( [ $this->block_name ] );
			return $content;
		}

		$post_id = $block->context['postId'];
		$product = wc_get_product( $post_id );

		if ( $product && $product->get_review_count() > 0 ) {
			$product_reviews_count                    = $product->get_review_count();
			$product_rating                           = $product->get_average_rating();
			$parsed_attributes                        = $this->parse_attributes( $attributes );
			$is_descendent_of_single_product_block    = $parsed_attributes['isDescendentOfSingleProductBlock'];
			$is_descendent_of_single_product_template = $parsed_attributes['isDescendentOfSingleProductTemplate'];

			$styles_and_classes            = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
			$text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes );

			/**
			 * Filter the output from wc_get_rating_html.
			 *
			 * @param string $html   Star rating markup. Default empty string.
			 * @param float  $rating Rating being shown.
			 * @param int    $count  Total number of ratings.
			 * @return string
			 */
			$filter_rating_html = function ( $html, $rating, $count ) use ( $post_id, $product_rating, $product_reviews_count, $is_descendent_of_single_product_block, $is_descendent_of_single_product_template ) {
				$product_permalink = get_permalink( $post_id );
				$reviews_count     = $count;
				$average_rating    = $rating;

				if ( $product_rating ) {
					$average_rating = $product_rating;
				}

				if ( $product_reviews_count ) {
					$reviews_count = $product_reviews_count;
				}

				if ( 0 < $average_rating || false === $product_permalink ) {
					/* translators: %s: rating */
					$customer_reviews_count = sprintf(
					/* translators: %s is referring to the total of reviews for a product */
						_n(
							'(%s customer review)',
							'(%s customer reviews)',
							$reviews_count,
							'woocommerce'
						),
						esc_html( $reviews_count )
					);

					if ( $is_descendent_of_single_product_block ) {
						$customer_reviews_count = '<a href="' . esc_url( $product_permalink ) . '#reviews">' . $customer_reviews_count . '</a>';
					} elseif ( $is_descendent_of_single_product_template ) {
						$customer_reviews_count = '<a class="woocommerce-review-link" rel="nofollow" href="#reviews">' . $customer_reviews_count . '</a>';
					}

					$html = sprintf(
						'<div class="wc-block-components-product-rating-counter__container">
							<span class="wc-block-components-product-rating-counter__reviews_count">%1$s</span>
						</div>
						',
						$customer_reviews_count
					);
				} else {
					$html = '';
				}

				return $html;
			};

			add_filter(
				'woocommerce_product_get_rating_html',
				$filter_rating_html,
				10,
				3
			);

			$rating_html = wc_get_rating_html( $product->get_average_rating() );

			remove_filter(
				'woocommerce_product_get_rating_html',
				$filter_rating_html,
				10
			);

			$classes = implode(
				' ',
				array_filter(
					array(
						'wc-block-components-product-rating-counter wc-block-grid__product-rating-counter',
						esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
						esc_attr( $styles_and_classes['classes'] ),
					)
				)
			);

			$wrapper_attributes = get_block_wrapper_attributes(
				array(
					'class' => $classes,
					'style' => esc_attr( $styles_and_classes['styles'] ?? '' ),
				)
			);

			return sprintf(
				'<div %1$s>
					%2$s
				</div>',
				$wrapper_attributes,
				$rating_html
			);
		}
		return '';
	}
}
PK     \[&\2  2  %  BlockTypes/ProductFilterAttribute.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection\Utils as ProductCollectionUtils;
use Automattic\WooCommerce\Internal\ProductFilters\FilterDataProvider;
use Automattic\WooCommerce\Internal\ProductFilters\QueryClauses;

/**
 * Product Filter: Attribute Block.
 */
final class ProductFilterAttribute extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-filter-attribute';

	/**
	 * Initialize this block type.
	 *
	 * - Hook into WP lifecycle.
	 * - Register the block with WordPress.
	 */
	protected function initialize() {
		parent::initialize();

		add_filter( 'woocommerce_blocks_product_filters_selected_items', array( $this, 'prepare_selected_filters' ), 10, 2 );
		add_action( 'deleted_transient', array( $this, 'delete_default_attribute_id_transient' ) );
		add_action( 'wp_loaded', array( $this, 'register_block_patterns' ) );
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = array() ) {
		parent::enqueue_data( $attributes );

		if ( is_admin() ) {
			$this->asset_data_registry->add( 'defaultProductFilterAttribute', $this->get_default_product_attribute() );
		}
	}

	/**
	 * Delete the default attribute id transient when the attribute taxonomies are deleted.
	 *
	 * @param string $transient The transient name.
	 */
	public function delete_default_attribute_id_transient( $transient ) {
		if ( 'wc_attribute_taxonomies' === $transient ) {
			delete_transient( 'wc_block_product_filter_attribute_default_attribute' );
		}
	}



	/**
	 * Prepare the active filter items.
	 *
	 * @param array $items  The active filter items.
	 * @param array $params The query param parsed from the URL.
	 * @return array Active filters items.
	 */
	public function prepare_selected_filters( $items, $params ) {
		$product_attributes_map = array_reduce(
			wc_get_attribute_taxonomies(),
			function ( $acc, $attribute_object ) {
				$acc[ $attribute_object->attribute_name ] = $attribute_object->attribute_label;
				return $acc;
			},
			array()
		);

		$active_attributes = array();
		$all_term_slugs    = array();
		$query_types       = array();

		foreach ( array_keys( $product_attributes_map ) as $attribute_name ) {
			$param_key = "filter_{$attribute_name}";

			if ( empty( $params[ $param_key ] ) || ! is_string( $params[ $param_key ] ) ) {
				continue;
			}

			// Filter out empty slugs and trim whitespace.
			$term_slugs = array_filter(
				array_map( 'trim', explode( ',', $params[ $param_key ] ) ),
			);

			if ( empty( $term_slugs ) ) {
				continue;
			}

			$active_attributes[ "pa_{$attribute_name}" ] = $term_slugs;
			$query_types[ $attribute_name ]              = $params[ 'query_type_' . $attribute_name ] ?? 'or';
			$all_term_slugs                              = array_merge( $all_term_slugs, $term_slugs );
		}

		if ( empty( $active_attributes ) ) {
			return $items;
		}

		$attribute_terms = get_terms(
			array(
				'taxonomy'   => array_keys( $active_attributes ),
				'slug'       => $all_term_slugs,
				'hide_empty' => false,
			)
		);

		if ( is_wp_error( $attribute_terms ) || empty( $attribute_terms ) ) {
			return $items;
		}

		foreach ( $attribute_terms as $term_object ) {
			$attribute_name = str_replace( 'pa_', '', $term_object->taxonomy );
			$items[]        = array(
				'type'               => 'attribute/' . $attribute_name,
				'value'              => $term_object->slug,
				'activeLabel'        => sprintf( '%s: %s', $product_attributes_map[ $attribute_name ], $term_object->name ),
				'attributeQueryType' => $query_types[ $attribute_name ] ?? 'or',
			);
		}

		return $items;
	}

	/**
	 * Render the block.
	 *
	 * @param array    $block_attributes Block attributes.
	 * @param string   $content          Block content.
	 * @param WP_Block $block            Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $block_attributes, $content, $block ) {
		if ( empty( $block_attributes['attributeId'] ) ) {
			$default_product_attribute       = $this->get_default_product_attribute();
			$block_attributes['attributeId'] = $default_product_attribute->attribute_id;
		}

		// don't render if its admin, or ajax in progress.
		if ( is_admin() || wp_doing_ajax() || empty( $block_attributes['attributeId'] ) ) {
			return '';
		}

		$product_attribute = wc_get_attribute( $block_attributes['attributeId'] );
		$attribute_counts  = $this->get_attribute_counts( $block, $product_attribute->slug, $block_attributes['queryType'] );
		$hide_empty        = $block_attributes['hideEmpty'] ?? true;
		$orderby           = $block_attributes['sortOrder'] ? explode( '-', $block_attributes['sortOrder'] )[0] : 'name';
		$order             = $block_attributes['sortOrder'] ? strtoupper( explode( '-', $block_attributes['sortOrder'] )[1] ) : 'DESC';

		$args = array(
			'taxonomy' => $product_attribute->slug,
			'orderby'  => $orderby,
			'order'    => $order,
		);

		if ( $hide_empty ) {
			$args['include'] = array_keys( $attribute_counts );
		} else {
			$args['hide_empty'] = false;
		}

		$attribute_terms = get_terms( $args );

		$filter_param_key = 'filter_' . str_replace( 'pa_', '', $product_attribute->slug );
		$filter_params    = $block->context['filterParams'] ?? array();
		$selected_terms   = array();

		if ( $filter_params && ! empty( $filter_params[ $filter_param_key ] ) && is_string( $filter_params[ $filter_param_key ] ) ) {
			$selected_terms = array_filter( explode( ',', $filter_params[ $filter_param_key ] ) );
		}

		$filter_context = array(
			'showCounts' => $block_attributes['showCounts'] ?? false,
			'items'      => array(),
			'groupLabel' => $product_attribute->name,
		);

		if ( ! empty( $attribute_counts ) ) {
			$attribute_options = array_map(
				function ( $term ) use ( $block_attributes, $attribute_counts, $selected_terms, $product_attribute ) {
					$term          = (array) $term;
					$term['count'] = $attribute_counts[ $term['term_id'] ] ?? 0;

					return array(
						'label'              => $term['name'],
						'value'              => $term['slug'],
						'selected'           => in_array( $term['slug'], $selected_terms, true ),
						'count'              => $term['count'],
						'type'               => 'attribute/' . str_replace( 'pa_', '', $product_attribute->slug ),
						'attributeQueryType' => $block_attributes['queryType'],
					);
				},
				$attribute_terms
			);

			$filter_context['items'] = $attribute_options;
		}

		$wrapper_attributes = array(
			'data-wp-interactive' => 'woocommerce/product-filters',
			'data-wp-key'         => wp_unique_prefixed_id( $this->get_full_block_name() ),
			'data-wp-context'     => wp_json_encode(
				array(
					'activeLabelTemplate' => "$product_attribute->name: {{label}}",
					'filterType'          => 'attribute/' . str_replace( 'pa_', '', $product_attribute->slug ),
				),
				JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
			),
		);

		if ( empty( $filter_context['items'] ) ) {
			$wrapper_attributes['hidden'] = true;
			$wrapper_attributes['class']  = 'wc-block-product-filter--hidden';
		}

		return sprintf(
			'<div %1$s>%2$s</div>',
			get_block_wrapper_attributes( $wrapper_attributes ),
			array_reduce(
				$block->parsed_block['innerBlocks'],
				function ( $carry, $parsed_block ) use ( $filter_context ) {
					$carry .= ( new \WP_Block( $parsed_block, array( 'filterData' => $filter_context ) ) )->render();
					return $carry;
				},
				''
			)
		);
	}

	/**
	 * Retrieve the attribute count for current block.
	 *
	 * @param WP_Block $block      Block instance.
	 * @param string   $slug       Attribute slug.
	 * @param string   $query_type Query type, accept 'and' or 'or'.
	 */
	private function get_attribute_counts( $block, $slug, $query_type ) {
		if ( ! isset( $block->context['filterParams'] ) ) {
			return array();
		}

		$query_vars = ProductCollectionUtils::get_query_vars( $block, 1 );

		if ( 'and' !== strtolower( $query_type ) ) {
			unset( $query_vars[ 'filter_' . str_replace( 'pa_', '', $slug ) ] );
		}

		if ( isset( $query_vars['taxonomy'] ) && false !== strpos( $query_vars['taxonomy'], 'pa_' ) ) {
			unset(
				$query_vars['taxonomy'],
				$query_vars['term']
			);
		}

		if ( ! empty( $query_vars['tax_query'] ) ) {
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
			$query_vars['tax_query'] = ProductCollectionUtils::remove_query_array( $query_vars['tax_query'], 'taxonomy', $slug );
		}

		$container        = wc_get_container();
		$counts           = $container->get( FilterDataProvider::class )->with( $container->get( QueryClauses::class ) )->get_attribute_counts( $query_vars, $slug );
		$attribute_counts = array();

		foreach ( $counts as $key => $value ) {
			$attribute_counts[] = array(
				'term'  => $key,
				'count' => intval( $value ),
			);
		}

		$attribute_counts = array_reduce(
			$attribute_counts,
			function ( $acc, $count ) {
				$acc[ $count['term'] ] = $count['count'];
				return $acc;
			},
			array()
		);

		return $attribute_counts;
	}

	/**
	 * Get the attribute if with most term but closest to 30 terms.
	 *
	 * @return object
	 */
	private function get_default_product_attribute() {
		// Cache this variable in memory to prevent repeated database queries to check
		// for transient in the same request.
		static $cached = null;

		if ( $cached ) {
			return $cached;
		}

		$cached = get_transient( 'wc_block_product_filter_attribute_default_attribute' );

		if (
			$cached &&
			isset( $cached->attribute_id ) &&
			isset( $cached->attribute_name ) &&
			isset( $cached->attribute_label ) &&
			isset( $cached->attribute_type ) &&
			isset( $cached->attribute_orderby ) &&
			isset( $cached->attribute_public ) &&
			'0' !== $cached->attribute_id
		) {
			return $cached;
		}

		$attributes = wc_get_attribute_taxonomies();

		$attributes_count = array_map(
			function ( $attribute ) {
				return intval(
					wp_count_terms(
						array(
							'taxonomy'   => 'pa_' . $attribute->attribute_name,
							'hide_empty' => false,
						)
					)
				);
			},
			$attributes
		);

		asort( $attributes_count );

		$search       = 30;
		$closest      = null;
		$attribute_id = null;

		foreach ( $attributes_count as $id => $count ) {
			if ( null === $closest || abs( $search - $closest ) > abs( $count - $search ) ) {
				$closest      = $count;
				$attribute_id = $id;
			}

			if ( $closest && $count >= $search ) {
				break;
			}
		}

		$default_attribute = (object) array(
			'attribute_id'      => '0',
			'attribute_name'    => 'attribute',
			'attribute_label'   => __( 'Attribute', 'woocommerce' ),
			'attribute_type'    => 'select',
			'attribute_orderby' => 'menu_order',
			'attribute_public'  => 0,
		);

		if ( $attribute_id ) {
			$default_attribute = $attributes[ $attribute_id ];
			set_transient( 'wc_block_product_filter_attribute_default_attribute', $default_attribute, DAY_IN_SECONDS );
		}

		return $default_attribute;
	}

	/**
	 * Register pattern for default product attribute.
	 */
	public function register_block_patterns() {
		$default_attribute = $this->get_default_product_attribute();
		register_block_pattern(
			'woocommerce/default-attribute-filter',
			array(
				'title'    => '',
				'inserter' => false,
				'content'  => strtr(
					'
<!-- wp:woocommerce/product-filter-attribute {"attributeId":{{attribute_id}}} -->
<div class="wp-block-woocommerce-product-filter-attribute">
	<!-- wp:group {"metadata":{"name":"Header"},"style":{"spacing":{"blockGap":"0"}},"layout":{"type":"flex","flexWrap":"nowrap"}} -->
	<div class="wp-block-group">
		<!-- wp:heading {"level":3} -->
		<h3 class="wp-block-heading">{{attribute_label}}</h3>
		<!-- /wp:heading -->
	<!-- /wp:group -->

	<!-- wp:woocommerce/product-filter-checkbox-list {"lock":{"remove":true}} -->
	<div class="wp-block-woocommerce-product-filter-checkbox-list wc-block-product-filter-checkbox-list"></div>
	<!-- /wp:woocommerce/product-filter-checkbox-list -->

</div>
<!-- /wp:woocommerce/product-filter-attribute -->
					',
					array(
						'{{attribute_id}}'    => intval( $default_attribute->attribute_id ),
						'{{attribute_label}}' => esc_html( $default_attribute->attribute_label ),
					)
				),
			)
		);
	}

	/**
	 * Disable the editor style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_editor_style() {
		return null;
	}

	/**
	 * Disable the script handle for this block type. We use block.json to load the script.
	 *
	 * @param string|null $key The key of the script to get.
	 * @return null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}
}
PK     \    $  BlockTypes/ProductStockIndicator.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Blocks\Utils\ProductAvailabilityUtils;
use Automattic\WooCommerce\Enums\ProductType;

/**
 * ProductStockIndicator class.
 */
class ProductStockIndicator extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-stock-indicator';

	/**
	 * API version name.
	 *
	 * @var string
	 */
	protected $api_version = '3';

	/**
	 * Register script and style assets for the block type before it is registered.
	 *
	 * This registers the scripts; it does not enqueue them.
	 */
	protected function register_block_type_assets() {
		return null;
	}

	/**
	 * Register the context.
	 */
	protected function get_block_type_uses_context() {
		return [ 'query', 'queryId', 'postId' ];
	}

	/**
	 * Get product types that should not display stock indicators.
	 *
	 * @return array
	 */
	protected function get_product_types_without_stock_indicator() {
		return array( ProductType::EXTERNAL, ProductType::GROUPED );
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );

		$this->asset_data_registry->add( 'productTypesWithoutStockIndicator', $this->get_product_types_without_stock_indicator() );
	}

	/**
	 * Renders the stock indicator block.
	 *
	 * This method handles both direct product context and global product context,
	 * ensuring the stock indicator displays correctly in various template scenarios.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		global $product;

		if ( ! empty( $content ) ) {
			parent::register_block_type_assets();
			$this->register_chunk_translations( [ $this->block_name ] );
			return $content;
		}
		$post_id           = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
		$product_to_render = wc_get_product( $post_id );

		// Use the global product if the product to render can't be retrieved from the context.
		if ( ! $product_to_render instanceof WC_Product ) {
			$product_to_render = $product;
		}

		if ( ! $product_to_render || in_array( $product_to_render->get_type(), $this->get_product_types_without_stock_indicator(), true ) ) {
			return '';
		}

		$availability = ProductAvailabilityUtils::get_product_availability( $product_to_render );

		$is_descendant_of_product_collection       = isset( $block->context['query']['isProductCollectionBlock'] );
		$is_descendant_of_grouped_product_selector = isset( $block->context['isDescendantOfGroupedProductSelector'] );
		$is_interactive                            = ! $is_descendant_of_product_collection && ! $is_descendant_of_grouped_product_selector && $product_to_render->is_type( ProductType::VARIABLE );

		if ( empty( $availability['availability'] ) && ! $is_interactive ) {
			return '';
		}

		$total_stock        = $product_to_render->get_stock_quantity();
		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );

		$classnames  = isset( $classes_and_styles['classes'] ) ? ' ' . $classes_and_styles['classes'] . ' ' : '';
		$classnames .= sprintf( ' wc-block-components-product-stock-indicator--%s', $availability['class'] );

		$is_backorder_notification_visible = $product_to_render->is_in_stock() && $product_to_render->backorders_require_notification();

		if ( empty( $content ) && $is_backorder_notification_visible && $total_stock > 0 ) {
			$low_stock_text = sprintf(
				/* translators: %d is number of items in stock for product */
				__( '%d left in stock', 'woocommerce' ),
				$total_stock
			);
		}

		$wrapper_attributes = array();
		$watch_attribute    = '';

		if ( $is_interactive && 'out-of-stock' !== $availability['class'] ) {
			$variations                = $product_to_render->get_available_variations( 'objects' );
			$formatted_variations_data = array();
			foreach ( $variations as $variation ) {
				$variation_availability = $variation->get_availability();
				if ( is_string( $variation_availability['availability'] ) && ! empty( $variation_availability['availability'] ) ) {
					$formatted_variations_data[ $variation->get_id() ] = array(
						'availability' => $variation_availability['availability'],
					);
				}
			}

			wp_interactivity_config(
				'woocommerce',
				array(
					'products' => array(
						$product_to_render->get_id() => array(
							'availability' => $availability['availability'],
							'variations'   => $formatted_variations_data,
						),
					),
				)
			);

			wp_enqueue_script_module( 'woocommerce/product-elements' );
			$wrapper_attributes['data-wp-interactive'] = 'woocommerce/product-elements';
			$wrapper_attributes['data-wp-text']        = 'state.productData.availability';
			$wrapper_attributes['aria-live']           = 'polite';
			$wrapper_attributes['aria-atomic']         = 'true';
		}

		$output_text = $low_stock_text ?? $availability['availability'];

		$output  = '';
		$output .= '<div class="wc-block-components-product-stock-indicator wp-block-woocommerce-product-stock-indicator ' . esc_attr( $classnames ) . '"';
		$output .= isset( $classes_and_styles['styles'] ) ? ' style="' . esc_attr( $classes_and_styles['styles'] ) . '"' : '';
		if ( $is_interactive && 'out-of-stock' !== $availability['class'] ) {
			$output .= ' ' . get_block_wrapper_attributes( $wrapper_attributes );
		}
		$output .= '>';
		$output .= wp_kses_post( $output_text );
		$output .= '</div>';

		return $output;
	}
}
PK     \fa    '  BlockTypes/ProductGalleryThumbnails.phpnu [        <?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils;

/**
 * ProductGalleryThumbnails class.
 */
class ProductGalleryThumbnails extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-gallery-thumbnails';

	/**
	 *  Register the context
	 *
	 * @return string[]
	 */
	protected function get_block_type_uses_context() {
		return array( 'postId' );
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! isset( $block->context ) ) {
			return '';
		}

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
		$post_id            = $block->context['postId'];

		if ( ! $post_id ) {
			return '';
		}

		$product = wc_get_product( $post_id );

		if ( ! $product instanceof \WC_Product ) {
			return '';
		}

		// We crop the images to square only if the aspect ratio is 1:1.
		// Otherwise, we show the uncropped and use object-fit to crop them.
		$image_size             = '1' === $attributes['aspectRatio'] ? 'woocommerce_thumbnail' : 'woocommerce_single';
		$product_gallery_images = ProductGalleryUtils::get_product_gallery_image_data( $product, $image_size );

		// Don't show the thumbnails block if there is only one image.
		if ( count( $product_gallery_images ) <= 1 ) {
			return '';
		}

		$thumbnail_size         = str_replace( '%', '', $attributes['thumbnailSize'] ?? '25%' );
		$active_thumbnail_style = $attributes['activeThumbnailStyle'] ?? 'overlay';

		$img_class = 'wc-block-product-gallery-thumbnails__thumbnail__image';

		ob_start();
		?>
		<div
			class="wc-block-product-gallery-thumbnails wc-block-product-gallery-thumbnails--active-<?php echo esc_attr( $active_thumbnail_style ); ?> <?php echo esc_attr( $classes_and_styles['classes'] ); ?>"
			style="<?php echo '--wc-block-product-gallery-thumbnails-size:' . absint( $thumbnail_size ) . ';' . esc_attr( $classes_and_styles['styles'] ); ?>"
			data-wp-interactive="woocommerce/product-gallery"
			data-wp-class--wc-block-product-gallery-thumbnails--overflow-top="context.thumbnailsOverflow.top"
			data-wp-class--wc-block-product-gallery-thumbnails--overflow-bottom="context.thumbnailsOverflow.bottom"
			data-wp-class--wc-block-product-gallery-thumbnails--overflow-left="context.thumbnailsOverflow.left"
			data-wp-class--wc-block-product-gallery-thumbnails--overflow-right="context.thumbnailsOverflow.right">
			<div
				class="wc-block-product-gallery-thumbnails__scrollable"
				data-wp-init--init-resize-observer="callbacks.initResizeObserver"
				data-wp-init--hide-ghost-overflow="callbacks.hideGhostOverflow"
				data-wp-on--scroll="actions.onScroll"
				role="listbox">
				<?php foreach ( $product_gallery_images as $index => $image ) : ?>
					<div class="wc-block-product-gallery-thumbnails__thumbnail">
						<img
							class="<?php echo 0 === $index ? esc_attr( $img_class . ' wc-block-product-gallery-thumbnails__thumbnail__image--is-active' ) : esc_attr( $img_class ); ?>"
							data-image-id="<?php echo esc_attr( $image['id'] ); ?>"
							src="<?php echo esc_attr( $image['src'] ); ?>"
							srcset="<?php echo esc_attr( $image['srcset'] ); ?>"
							sizes="<?php echo esc_attr( $image['sizes'] ); ?>"
							alt="<?php echo esc_attr( $image['alt'] ); ?>"
							data-wp-on--click="actions.selectCurrentImage"
							data-wp-on--keydown="actions.onThumbnailsArrowsKeyDown"
							data-wp-watch="callbacks.toggleActiveThumbnailAttributes"
							decoding="async"
							tabindex="<?php echo 0 === $index ? '0' : '-1'; ?>"
							draggable="false"
							loading="lazy"
							role="option"
							style="aspect-ratio: <?php echo esc_attr( $attributes['aspectRatio'] ); ?>" />
					</div>
				<?php endforeach; ?>
			</div>
		</div>
		<?php
		$template = ob_get_clean();

		return $template;
	}
}
PK     \    )  BlockTypes/CartOrderSummaryTaxesBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartOrderSummaryTaxesBlock class.
 */
class CartOrderSummaryTaxesBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-order-summary-taxes-block';
}
PK     \K    +  BlockTypes/CartOrderSummaryHeadingBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartOrderSummaryHeadingBlock class.
 */
class CartOrderSummaryHeadingBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-order-summary-heading-block';
}
PK     \kQF    !  BlockTypes/MiniCartTitleBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Admin\Features\Features;

/**
 * MiniCartTitleBlock class.
 */
class MiniCartTitleBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'mini-cart-title-block';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( Features::is_enabled( 'experimental-iapi-mini-cart' ) ) {
			return $this->render_experimental_iapi_title_block( $attributes, $content, $block );
		}
		return $content;
	}

	/**
	 * Render the interactivity API powered experimental title block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render_experimental_iapi_title_block( $attributes, $content, $block ) {
		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class' => 'wc-block-mini-cart__title',
			)
		);
		ob_start();
		?>
			<h2 <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
				<?php
					// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
					echo $content;
				?>
			</h2>
		<?php
		return ob_get_clean();
	}
}
PK     \ݻk  k    BlockTypes/ProductTitle.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ProductTitle class.
 */
class ProductTitle extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-title';

	/**
	 * API version name.
	 *
	 * @var string
	 */
	protected $api_version = '3';

	/**
	 * Register script and style assets for the block type before it is registered.
	 *
	 * This registers the scripts; it does not enqueue them.
	 */
	protected function register_block_type_assets() {
		parent::register_block_type_assets();
		$this->register_chunk_translations( [ $this->block_name ] );
	}
}
PK     \rQj
  
  $  BlockTypes/CartOrderSummaryBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartOrderSummaryBlock class.
 */
class CartOrderSummaryBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-order-summary-block';

	/**
	 * Get the contents of the given inner block.
	 *
	 * @param string $block_name Name of the order summary inner block.
	 * @param string $content    The content to search.
	 * @return array|bool
	 */
	private function get_inner_block_content( $block_name, $content ) {
		if ( preg_match( $this->inner_block_regex( $block_name ), $content, $matches ) ) {
			return $matches[0];
		}
		return false;
	}

	/**
	 * Get the regex that will return an inner block.
	 *
	 * @param string $block_name Name of the order summary inner block.
	 * @return string Regex pattern.
	 */
	private function inner_block_regex( $block_name ) {
		return '/<div data-block-name="woocommerce\/cart-order-summary-' . $block_name . '-block"(.+?)>(.*?)<\/div>/si';
	}

	/**
	 * Render the Cart Order Summary block.
	 *
	 * @param array  $attributes Block attributes.
	 * @param string $content    Block content.
	 * @param object $block      Block object.
	 * @return string Rendered block.
	 */
	protected function render( $attributes, $content, $block ) {
		// The order-summary-totals block was introduced as a new parent block for the totals
		// (subtotal, discount, fees, shipping and taxes) blocks.
		$regex_for_cart_order_summary_totals = '/<div data-block-name="woocommerce\/cart-order-summary-totals-block"(.+?)>/';
		$order_summary_totals_content        = '<div data-block-name="woocommerce/cart-order-summary-totals-block" class="wp-block-woocommerce-cart-order-summary-totals-block">';

		$totals_inner_blocks = array( 'subtotal', 'discount', 'fee', 'shipping', 'taxes' ); // We want to move these blocks inside a parent 'totals' block.

		if ( preg_match( $regex_for_cart_order_summary_totals, $content ) ) {
			return $content;
		}

		foreach ( $totals_inner_blocks as $key => $block_name ) {
			$inner_block_content = $this->get_inner_block_content( $block_name, $content );

			if ( $inner_block_content ) {
				$order_summary_totals_content .= "\n" . $inner_block_content;

				// The last block is replaced with the totals block.
				if ( count( $totals_inner_blocks ) - 1 === $key ) {
					$order_summary_totals_content .= '</div>';
					$content                       = preg_replace( $this->inner_block_regex( $block_name ), $order_summary_totals_content, $content );
				} else {
					// Otherwise, remove the block.
					$content = preg_replace( $this->inner_block_regex( $block_name ), '', $content );
				}
			}
		}

		return preg_replace( '/\n\n( *?)/i', '', $content );
	}
}
PK     \H    .  BlockTypes/CheckoutContactInformationBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutContactInformationBlock class.
 */
class CheckoutContactInformationBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-contact-information-block';
}
PK     \HPZ!  !    BlockTypes/ClassicShortcode.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use WC_Shortcode_Cart;
use WC_Shortcode_Checkout;
use WC_Frontend_Scripts;

/**
 * Classic Shortcode class
 *
 * @internal
 */
class ClassicShortcode extends AbstractDynamicBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'classic-shortcode';

	/**
	 * API version.
	 *
	 * @var string
	 */
	protected $api_version = '3';

	/**
	 * Render method for the Classic Template block. This method will determine which template to render.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string | void Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! isset( $attributes['shortcode'] ) ) {
			return;
		}

		/**
		 * We need to load the scripts here because when using block templates wp_head() gets run after the block
		 * template. As a result we are trying to enqueue required scripts before we have even registered them.
		 *
		 * @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5328#issuecomment-989013447
		 */
		if ( class_exists( 'WC_Frontend_Scripts' ) ) {
			$frontend_scripts = new WC_Frontend_Scripts();
			$frontend_scripts::load_scripts();
		}

		if ( 'cart' === $attributes['shortcode'] ) {
			return $this->render_cart( $attributes );
		}

		if ( 'checkout' === $attributes['shortcode'] ) {
			return $this->render_checkout( $attributes );
		}

		return "You're using the ClassicShortcode block";
	}

	/**
	 * Get the list of classes to apply to this block.
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 * @return string space-separated list of classes.
	 */
	protected function get_container_classes( $attributes = array() ) {
		$classes = array( 'woocommerce', 'wp-block-group' );

		if ( isset( $attributes['align'] ) ) {
			$classes[] = "align{$attributes['align']}";
		}

		return implode( ' ', $classes );
	}

	/**
	 * Render method for rendering the cart shortcode.
	 *
	 * @param array $attributes Block attributes.
	 * @return string Rendered block type output.
	 */
	protected function render_cart( $attributes ) {
		if ( ! isset( WC()->cart ) ) {
			return '';
		}

		ob_start();

		echo '<div class="' . esc_attr( $this->get_container_classes( $attributes ) ) . '">';
		WC_Shortcode_Cart::output( array() );
		echo '</div>';

		return ob_get_clean();
	}

	/**
	 * Render method for rendering the checkout shortcode.
	 *
	 * @param array $attributes Block attributes.
	 * @return string Rendered block type output.
	 */
	protected function render_checkout( $attributes ) {
		if ( ! isset( WC()->cart ) ) {
			return '';
		}

		ob_start();

		echo '<div class="' . esc_attr( $this->get_container_classes( $attributes ) ) . '">';
		WC_Shortcode_Checkout::output( array() );
		echo '</div>';

		return ob_get_clean();
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \Wd    )  BlockTypes/CheckoutPickupOptionsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutPickupOptionsBlock class.
 */
class CheckoutPickupOptionsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-pickup-options-block';
}
PK     \MCE    "  BlockTypes/CategoryDescription.phpnu [        <?php
declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CategoryDescription block: renders the current term description using context.
 */
class CategoryDescription extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'category-description';

	/**
	 * Render the block.
	 *
	 * @param array     $attributes Block attributes.
	 * @param string    $content    Block content.
	 * @param \WP_Block $block     Block instance.
	 *
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ) {
		$term_id       = $block->context['termId'] ?? 0;
		$term_taxonomy = $block->context['termTaxonomy'] ?? 'product_cat';

		$text_align = isset( $attributes['textAlign'] ) ? sanitize_key( $attributes['textAlign'] ) : '';

		if ( ! $term_id ) {
			return '';
		}

		$term = get_term( $term_id, $term_taxonomy );
		if ( ! $term || is_wp_error( $term ) ) {
			return '';
		}

		$description = $term->description;
		if ( empty( trim( $description ) ) ) {
			return '';
		}

		$classes = array();
		if ( $text_align ) {
			$classes[] = 'has-text-align-' . $text_align;
		}

		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class' => implode( ' ', $classes ),
			)
		);

		return sprintf(
			'<div %1$s>%2$s</div>',
			$wrapper_attributes,
			wp_kses_post( wc_format_content( $description ) )
		);
	}

	/**
	 * Register the context used by this block.
	 *
	 * @return array
	 */
	protected function get_block_type_uses_context() {
		return [ 'termId', 'termTaxonomy' ];
	}

	/**
	 * Disable the style handle for this block.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \?p#%  #%    BlockTypes/AddToCartForm.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions\Utils;
use Automattic\WooCommerce\Enums\ProductType;

/**
 * AddToCartForm class.
 */
class AddToCartForm extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'add-to-cart-form';

	/**
	 * Get the block's attributes.
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 * @return array  Block attributes merged with defaults.
	 */
	private function parse_attributes( $attributes ) {
		// These should match what's set in JS `registerBlockType`.
		$defaults = array(
			'quantitySelectorStyle' => 'input',
		);

		return wp_parse_args( $attributes, $defaults );
	}

	/**
	 * Enqueue assets specific to this block.
	 * We enqueue frontend scripts only if the quantitySelectorStyle is set to 'stepper'.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 */
	protected function enqueue_assets( $attributes, $content, $block ) {
		$parsed_attributes = $this->parse_attributes( $attributes );
		if ( 'stepper' !== $parsed_attributes['quantitySelectorStyle'] ) {
			return;
		}

		parent::enqueue_assets( $attributes, $content, $block );
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );
		$this->asset_data_registry->add( 'isBlockTheme', wp_is_block_theme() );
	}

	/**
	 * Add increment and decrement buttons to the quantity input field.
	 *
	 * @param string $product_html Add to Cart form HTML.
	 * @param string $product_name Product name.
	 * @return string Add to Cart form HTML with increment and decrement buttons.
	 */
	private function add_steppers( $product_html, $product_name ) {
		// Regex pattern to match the <input> element with id starting with 'quantity_'.
		$pattern = '/(<input[^>]*id="quantity_[^"]*"[^>]*\/>)/';
		// Replacement string to add button AFTER the matched <input> element.
		/* translators: %s refers to the item name in the cart. */
		$minus_button = '$1<button aria-label="' . esc_attr( sprintf( __( 'Reduce quantity of %s', 'woocommerce' ), $product_name ) ) . '" type="button" data-wp-on--click="actions.removeQuantity" class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">−</button>';
		// Replacement string to add button AFTER the matched <input> element.
		/* translators: %s refers to the item name in the cart. */
		$plus_button = '$1<button aria-label="' . esc_attr( sprintf( __( 'Increase quantity of %s', 'woocommerce' ), $product_name ) ) . '" type="button" data-wp-on--click="actions.addQuantity" class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button>';
		$new_html    = preg_replace( $pattern, $plus_button, $product_html );
		$new_html    = preg_replace( $pattern, $minus_button, $new_html );
		return $new_html;
	}

	/**
	 * Add classes to the Add to Cart form input needed for the stepper style.
	 *
	 * @param string $product_html The Add to Cart form HTML.
	 *
	 * @return string The Add to Cart form HTML with classes added.
	 */
	private function add_stepper_classes_to_add_to_cart_form_input( $product_html ) {
		$html = new \WP_HTML_Tag_Processor( $product_html );

		// Add classes to the form.
		while ( $html->next_tag( array( 'class_name' => 'quantity' ) ) ) {
			$html->add_class( 'wc-block-components-quantity-selector' );
		}

		$html = new \WP_HTML_Tag_Processor( $html->get_updated_html() );
		while ( $html->next_tag( array( 'class_name' => 'input-text' ) ) ) {
			$html->add_class( 'wc-block-components-quantity-selector__input' );
		}

		return $html->get_updated_html();
	}

	/**
	 * Check if a variation product has all attributes set.
	 * Returns true if the product is not variation, or if all variation attributes have defined values.
	 *
	 * @param WC_Product $product The product to check.
	 *
	 * @return bool True if all attributes are set, false otherwise.
	 */
	private function has_all_attributes_set( $product ) {
		// If it's not a variation product, return true.
		if ( ! $product->is_type( ProductType::VARIATION ) ) {
			return true;
		}

		// Get all variation attributes.
		$variation_attributes = $product->get_variation_attributes();

		// If there are no variation attributes, return true.
		if ( empty( $variation_attributes ) ) {
			return true;
		}

		// Check if any attribute has an empty value (marked as 'any').
		if ( in_array( '', array_values( $variation_attributes ), true ) ) {
			return false;
		}

		return true;
	}

	/**
	 * 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 ) {
		global $product;

		$post_id = $block->context['postId'];

		if ( ! isset( $post_id ) ) {
			return '';
		}

		$is_descendent_of_single_product_block = is_null( $product ) || $post_id !== $product->get_id();

		$previous_product = $product;
		$product          = wc_get_product( $post_id );
		if ( ! $product instanceof \WC_Product ) {
			$product = $previous_product;

			return '';
		}

		// Check if all attributes are set for variation product.
		if ( $product->is_type( ProductType::VARIATION ) && ! $this->has_all_attributes_set( $product ) ) {
			$product = $previous_product;

			return '';
		}

		$is_external_product_with_url = $product instanceof \WC_Product_External && $product->get_product_url();
		$managing_stock               = $product->managing_stock();
		$stock_quantity               = $product->get_stock_quantity();

		$should_hide_quantity_selector = $product->is_sold_individually() || Utils::is_min_max_quantity_same( $product ) || ( $managing_stock && $stock_quantity <= 1 );

		/**
		 * The stepper buttons don't show when the product is sold individually or stock quantity is less or equal to 1 because the quantity input field is hidden.
		 * Additionally, if min and max purchase quantity are the same, the buttons should not be rendered at all.
		 */
		$is_stepper_style = 'stepper' === $attributes['quantitySelectorStyle'] && ! $should_hide_quantity_selector;

		if ( $is_descendent_of_single_product_block ) {
			add_filter( 'woocommerce_add_to_cart_form_action', array( $this, 'add_to_cart_form_action' ), 10 );
		}

		ob_start();

		/**
		 * Manage variations in the same way as simple products.
		 */
		add_action( 'woocommerce_variation_add_to_cart', 'woocommerce_simple_add_to_cart', 10 );

		/**
		 * Trigger the single product add to cart action for each product type.
		 *
		 * @since 9.7.0
		 */
		do_action( 'woocommerce_' . $product->get_type() . '_add_to_cart' );

		/**
		 * Remove the hook to prevent potential conflicts with existing code and extensions.
		 */
		remove_action( 'woocommerce_variation_add_to_cart', 'woocommerce_simple_add_to_cart', 10 );

		$product_html = ob_get_clean();

		if ( $is_descendent_of_single_product_block ) {
			remove_filter( 'woocommerce_add_to_cart_form_action', array( $this, 'add_to_cart_form_action' ), 10 );
		}

		if ( ! $product_html ) {
			$product = $previous_product;

			return '';
		}

		// If the quantity input is hidden, don't render the stepper buttons and styles.
		if ( $is_stepper_style && ! Utils::has_visible_quantity_input( $product_html ) ) {
			$is_stepper_style = false;
		}

		if ( $is_stepper_style ) {
			$product_name = $product->get_name();
			$product_html = $this->add_steppers( $product_html, $product_name );
			$product_html = $this->add_stepper_classes_to_add_to_cart_form_input( $product_html );
		}

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );

		$product_classname = $is_descendent_of_single_product_block ? 'product' : '';

		$classes = implode(
			' ',
			array_filter(
				array(
					'wp-block-add-to-cart-form wc-block-add-to-cart-form',
					esc_attr( $classes_and_styles['classes'] ),
					esc_attr( $product_classname ),
					$is_stepper_style ? 'wc-block-add-to-cart-form--stepper' : 'wc-block-add-to-cart-form--input',
				)
			)
		);

		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class' => $classes,
				'style' => esc_attr( $classes_and_styles['styles'] ),
			)
		);

		$form = sprintf(
			'<div %1$s %2$s>%3$s</div>',
			$wrapper_attributes,
			$is_stepper_style ? 'data-wp-interactive="woocommerce/add-to-cart-form"' : '',
			$product_html
		);

		$product = $previous_product;

		return $form;
	}

	/**
	 * Use current url as the add to cart form action.
	 *
	 * @return string The current URL.
	 */
	public function add_to_cart_form_action() {
		global $wp;
		return home_url( add_query_arg( $_GET, $wp->request ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
	}
}
PK     \.L    !  BlockTypes/ProductRatingStars.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
 * ProductRatingStars class.
 */
class ProductRatingStars extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-rating-stars';

	/**
	 * API version name.
	 *
	 * @var string
	 */
	protected $api_version = '3';

	/**
	 * Overwrite parent method to prevent script registration.
	 *
	 * It is necessary to register and enqueues assets during the render
	 * phase because we want to load assets only if the block has the content.
	 */
	protected function register_block_type_assets() {
		return null;
	}

	/**
	 * Register the context.
	 */
	protected function get_block_type_uses_context() {
		return [ 'query', 'queryId', 'postId' ];
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! empty( $content ) ) {
			parent::register_block_type_assets();
			$this->register_chunk_translations( [ $this->block_name ] );
			return $content;
		}

		$post_id = $block->context['postId'];
		$product = wc_get_product( $post_id );

		if ( $product ) {
			$product_reviews_count = $product->get_review_count();
			$product_rating        = $product->get_average_rating();

			$styles_and_classes            = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
			$text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes );

			/**
			 * Filter the output from wc_get_rating_html.
			 *
			 * @param string $html   Star rating markup. Default empty string.
			 * @param float  $rating Rating being shown.
			 * @param int    $count  Total number of ratings.
			 * @return string
			 */
			$filter_rating_html = function( $html, $rating, $count ) use ( $product_rating, $product_reviews_count ) {
				$product_permalink = get_permalink();
				$reviews_count     = $count;
				$average_rating    = $rating;

				if ( $product_rating ) {
					$average_rating = $product_rating;
				}

				if ( $product_reviews_count ) {
					$reviews_count = $product_reviews_count;
				}

				if ( 0 < $average_rating || false === $product_permalink ) {
					/* translators: %s: rating */
					$label = sprintf( __( 'Rated %s out of 5', 'woocommerce' ), $average_rating );
					$html  = sprintf(
						'<div class="wc-block-components-product-rating-stars__container">
							<div class="wc-block-components-product-rating__stars wc-block-grid__product-rating__stars" role="img" aria-label="%1$s">
								%2$s
							</div>
						</div>
						',
						esc_attr( $label ),
						wc_get_star_rating_html( $average_rating, $reviews_count )
					);
				} else {
					$html = '';
				}

				return $html;
			};

			add_filter(
				'woocommerce_product_get_rating_html',
				$filter_rating_html,
				10,
				3
			);

			$rating_html = wc_get_rating_html( $product->get_average_rating() );

			remove_filter(
				'woocommerce_product_get_rating_html',
				$filter_rating_html,
				10
			);

			$classes = implode(
				' ',
				array_filter(
					array(
						'wc-block-components-product-rating wc-block-grid__product-rating',
						esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
						esc_attr( $styles_and_classes['classes'] ),
					)
				)
			);

			$wrapper_attributes = get_block_wrapper_attributes(
				array(
					'class' => $classes,
					'style' => esc_attr( $styles_and_classes['styles'] ?? '' ),
				)
			);

			return sprintf(
				'<div %1$s>
					%2$s
				</div>',
				$wrapper_attributes,
				$rating_html
			);
		}
	}
}
PK     \׏i
  
    BlockTypes/ProductSaleBadge.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * ProductSaleBadge class.
 */
class ProductSaleBadge extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-sale-badge';

	/**
	 * API version name.
	 *
	 * @var string
	 */
	protected $api_version = '3';

	/**
	 * Overwrite parent method to prevent script registration.
	 *
	 * It is necessary to register and enqueues assets during the render
	 * phase because we want to load assets only if the block has the content.
	 */
	protected function register_block_type_assets() {
		return null;
	}

	/**
	 * Register the context.
	 */
	protected function get_block_type_uses_context() {
		return [ 'query', 'queryId', 'postId' ];
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
		$product = wc_get_product( $post_id );

		if ( ! $product ) {
			return null;
		}

		$is_on_sale = $product->is_on_sale();

		if ( ! $is_on_sale ) {
			return null;
		}

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );

		$classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );

		$align = isset( $attributes['align'] ) ? $attributes['align'] : '';

		/**
		 * Filters the product sale badge text.
		 *
		 * @hook woocommerce_sale_badge_text
		 * @since 10.0.0
		 *
		 * @param string $sale_text The sale badge text.
		 * @param WC_Product $product The product object.
		 * @return string The filtered sale badge text.
		 */
		$sale_text = apply_filters( 'woocommerce_sale_badge_text', __( 'Sale', 'woocommerce' ), $product );

		$output  = '<div class="wp-block-woocommerce-product-sale-badge ' . esc_attr( $classname ) . '">';
		$output .= sprintf( '<div class="wc-block-components-product-sale-badge %1$s wc-block-components-product-sale-badge--align-%2$s" style="%3$s">', esc_attr( $classes_and_styles['classes'] ), esc_attr( $align ), esc_attr( $classes_and_styles['styles'] ) );
		$output .= '<span class="wc-block-components-product-sale-badge__text" aria-hidden="true">' . esc_html( $sale_text ) . '</span>';
		$output .= '<span class="screen-reader-text">'
						. __( 'Product on sale', 'woocommerce' )
					. '</span>';
		$output .= '</div></div>';

		return $output;
	}
}
PK     \r8  8  "  BlockTypes/ProductsByAttribute.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ProductsByAttribute class.
 */
class ProductsByAttribute extends AbstractProductGrid {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'products-by-attribute';

	/**
	 * Set args specific to this block
	 *
	 * @param array $query_args Query args.
	 */
	protected function set_block_query_args( &$query_args ) {
		if ( ! empty( $this->attributes['attributes'] ) ) {
			$taxonomy = sanitize_title( $this->attributes['attributes'][0]['attr_slug'] );
			$terms    = wp_list_pluck( $this->attributes['attributes'], 'id' );

			$query_args['tax_query'][] = array(
				'taxonomy' => $taxonomy,
				'terms'    => array_map( 'absint', $terms ),
				'field'    => 'term_id',
				'operator' => 'all' === $this->attributes['attrOperator'] ? 'AND' : 'IN',
			);
		}
	}

	/**
	 * Get block attributes.
	 *
	 * @return array
	 */
	protected function get_block_type_attributes() {
		return array(
			'align'             => $this->get_schema_align(),
			'alignButtons'      => $this->get_schema_boolean( false ),
			'attributes'        => array(
				'type'    => 'array',
				'items'   => array(
					'type'       => 'object',
					'properties' => array(
						'id'        => array(
							'type' => 'number',
						),
						'attr_slug' => array(
							'type' => 'string',
						),
					),
				),
				'default' => array(),
			),
			'attrOperator'      => array(
				'type'    => 'string',
				'default' => 'any',
			),
			'className'         => $this->get_schema_string(),
			'columns'           => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ),
			'contentVisibility' => $this->get_schema_content_visibility(),
			'orderby'           => $this->get_schema_orderby(),
			'rows'              => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ),
			'isPreview'         => $this->get_schema_boolean( false ),
			'stockStatus'       => array(
				'type'    => 'array',
				'default' => array_keys( wc_get_product_stock_status_options() ),
			),
		);
	}
}
PK     \lB+    -  BlockTypes/CheckoutOrderSummaryTaxesBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutOrderSummaryTaxesBlock class.
 */
class CheckoutOrderSummaryTaxesBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-order-summary-taxes-block';
}
PK     \4)!  !  .  BlockTypes/CartOrderSummaryCouponFormBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartOrderSummaryCouponFormBlock class.
 */
class CartOrderSummaryCouponFormBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-order-summary-coupon-form-block';
}
PK     \V  V  ,  BlockTypes/Reviews/ProductReviewTemplate.phpnu [        <?php declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes\Reviews;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use WP_Comment_Query;
use WP_Block;
use WP_Comment;

/**
 * ProductReviewTemplate class.
 */
class ProductReviewTemplate extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-review-template';

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Function that recursively renders a list of nested reviews.
	 *
	 * @since 6.3.0 Changed render_block_context priority to `1`.
	 *
	 * @param WP_Comment[] $comments      The array of comments.
	 * @param WP_Block     $block         Block instance.
	 *
	 * @return string
	 */
	protected function block_product_review_template_render_comments( array $comments, WP_Block $block ): string {
		$content = '';

		foreach ( $comments as $comment ) {
			if ( ! $comment instanceof WP_Comment ) {
				continue;
			}

			$comment_id           = $comment->comment_ID;
			$filter_block_context = static function ( $context ) use ( $comment_id ) {
				$context['commentId'] = $comment_id;
				return $context;
			};

			/*
			 * We set commentId context through the `render_block_context` filter so
			 * that dynamically inserted blocks (at `render_block` filter stage)
			 * will also receive that context.
			 *
			 * Use an early priority so that other 'render_block_context' filters
			 * have access to the values.
			 */
			add_filter( 'render_block_context', $filter_block_context, 1 );

			/*
			 * We construct a new WP_Block instance from the parsed block so that
			 * it'll receive any changes made by the `render_block_data` filter.
			 */
			$block_content = ( new WP_Block( $block->parsed_block ) )->render( array( 'dynamic' => false ) );

			remove_filter( 'render_block_context', $filter_block_context, 1 );

			$children = $comment->get_children();

			/*
			* We need to create the CSS classes BEFORE recursing into the children.
			* This is because comment_class() uses globals like `$comment_alt`
			* and `$comment_thread_alt` which are order-sensitive.
			*
			* The `false` parameter at the end means that we do NOT want the function
			* to `echo` the output but to return a string.
			* See https://developer.wordpress.org/reference/functions/comment_class/#parameters.
			*/
			$comment_classes = comment_class(
				'',
				(int) $comment->comment_ID,
				(int) $comment->comment_post_ID,
				false
			);

			// If the comment has children, recurse to create the HTML for the nested comments.
			if ( ! empty( $children ) ) {
				$inner_content  = $this->block_product_review_template_render_comments(
					$children,
					$block,
				);
				$block_content .= sprintf( '<ol>%1$s</ol>', $inner_content );
			}

			$content .= sprintf( '<li id="comment-%1$s" %2$s>%3$s</li>', $comment->comment_ID, $comment_classes, $block_content );
		}

		return $content;
	}

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block content.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( empty( $block->context['postId'] ) ) {
			return '';
		}

		if ( post_password_required( $block->context['postId'] ) ) {
			return;
		}

		$comment_query = new WP_Comment_Query(
			build_comment_query_vars_from_block( $block )
		);

		// Get an array of comments for the current post.
		$comments = $comment_query->get_comments();
		if ( count( $comments ) === 0 ) {
			return '';
		}

		$comment_order = get_option( 'comment_order' );

		if ( 'desc' === $comment_order ) {
			$comments = array_reverse( $comments );
		}

		$wrapper_attributes = get_block_wrapper_attributes();

		return sprintf(
			'<ol %1$s>%2$s</ol>',
			$wrapper_attributes,
			$this->block_product_review_template_render_comments( $comments, $block )
		);
	}
}
PK     \z'
  
  7  BlockTypes/Reviews/ProductReviewsPaginationPrevious.phpnu [        <?php declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes\Reviews;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;

/**
 * ProductReviewsPaginationPrevious class.
 */
class ProductReviewsPaginationPrevious extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-reviews-pagination-previous';

	/**
	 * Render the block.
	 *
	 * @param array     $attributes Block attributes.
	 * @param string    $content    Block content.
	 * @param \WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$default_label    = __( 'Older Reviews', 'woocommerce' );
		$label            = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? $attributes['label'] : $default_label;
		$pagination_arrow = $this->get_pagination_arrow( $block );

		if ( $pagination_arrow ) {
			$label = $pagination_arrow . $label;
		}

		$filter_link_attributes = static function () {
			return get_block_wrapper_attributes();
		};
		add_filter( 'previous_comments_link_attributes', $filter_link_attributes );

		$comment_vars           = build_comment_query_vars_from_block( $block );
		$previous_comments_link = get_previous_comments_link( $label, $comment_vars['paged'] ?? null );

		remove_filter( 'previous_comments_link_attributes', $filter_link_attributes );

		if ( ! isset( $previous_comments_link ) ) {
			return '';
		}

		return $previous_comments_link;
	}

	/**
	 * Get the pagination arrow.
	 *
	 * @param \WP_Block $block Block instance.
	 * @return string|null
	 */
	protected function get_pagination_arrow( $block ) {
		$arrow_map = array(
			'none'    => '',
			'arrow'   => '←',
			'chevron' => '«',
		);

		if ( ! empty( $block->context['reviews/paginationArrow'] ) && ! empty( $arrow_map[ $block->context['reviews/paginationArrow'] ] ) ) {
			$arrow_attribute = $block->context['reviews/paginationArrow'];
			$arrow           = $arrow_map[ $block->context['reviews/paginationArrow'] ];
			$arrow_classes   = "wp-block-woocommerce-product-reviews-pagination-previous-arrow is-arrow-$arrow_attribute";
			return "<span class='$arrow_classes' aria-hidden='true'>$arrow</span>";
		}
		return null;
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string|null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \DdNy  y  *  BlockTypes/Reviews/ProductReviewsTitle.phpnu [        <?php declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes\Reviews;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;

/**
 * ProductReviewsTitle class.
 */
class ProductReviewsTitle extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-reviews-title';

	/**
	 * Get the reviews title.
	 *
	 * @param array      $attributes Block attributes.
	 * @param WC_Product $product Product instance.
	 * @return string
	 */
	private function get_reviews_title( $attributes, $product ) {
		$show_product_title = ! empty( $attributes['showProductTitle'] ) && $attributes['showProductTitle'];
		$show_reviews_count = ! empty( $attributes['showReviewsCount'] ) && $attributes['showReviewsCount'];
		$reviews_count      = $product->get_review_count();

		if ( $show_reviews_count && $show_product_title ) {
			return 1 === $reviews_count
				/* translators: %s: Product title. */
				? sprintf( __( 'One review for %s', 'woocommerce' ), $product->get_title() )
				: sprintf(
					/* translators: 1: Number of reviews, 2: Product title. */
					_n(
						'%1$s review for %2$s',
						'%1$s reviews for %2$s',
						$reviews_count,
						'woocommerce'
					),
					number_format_i18n( $reviews_count ),
					$product->get_title()
				);
		}

		if ( ! $show_reviews_count && $show_product_title ) {
			return 1 === $reviews_count
				/* translators: %s: Product title. */
				? sprintf( __( 'Review for %s', 'woocommerce' ), $product->get_title() )
				: sprintf(
					/* translators: %s: Product title. */
					__( 'Reviews for %s', 'woocommerce' ),
					$product->get_title()
				);
		}

		if ( $show_reviews_count && ! $show_product_title ) {
			return 1 === $reviews_count
				/* translators: %s: Number of reviews. */
				? __( 'One review', 'woocommerce' )
				: sprintf(
					/* translators: %s: Number of reviews. */
					_n( '%s review', '%s reviews', $reviews_count, 'woocommerce' ),
					number_format_i18n( $reviews_count )
				);
		}

		if ( 1 === $reviews_count ) {
			return __( 'Review', 'woocommerce' );
		}

		return __( 'Reviews', 'woocommerce' );
	}

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block content.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( post_password_required() ) {
			return;
		}
		$post_id = $block->context['postId'];
		$product = wc_get_product( $post_id );

		if ( ! $product ) {
			return '';
		}

		$align_class_name   = empty( $attributes['textAlign'] ) ? '' : "has-text-align-{$attributes['textAlign']}";
		$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $align_class_name ) );
		$reviews_count      = $product->get_review_count();
		$tag_name           = 'h2';
		if ( isset( $attributes['level'] ) ) {
			$tag_name = 'h' . $attributes['level'];
		}

		$reviews_title = $this->get_reviews_title( $attributes, $product );

		return sprintf(
			'<%1$s id="reviews" %2$s>%3$s</%1$s>',
			$tag_name,
			$wrapper_attributes,
			$reviews_title
		);
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}
}
PK     \ס2    %  BlockTypes/Reviews/ProductReviews.phpnu [        <?php declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes\Reviews;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * ProductReviews class.
 */
class ProductReviews extends AbstractBlock {
	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-reviews';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 *
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( empty( $block->parsed_block['innerBlocks'] ) ) {
			return $this->render_legacy_block( $attributes, $content, $block );
		}

		if ( ! comments_open() ) {
			return '';
		}

		$p = new \WP_HTML_Tag_Processor( $content );
		$p->next_tag();
		$p->set_attribute( 'data-wp-interactive', $this->get_full_block_name() );
		$p->set_attribute( 'data-wp-router-region', $this->get_full_block_name() );

		return $p->get_updated_html();
	}

	/**
	 * Previously, the Product Reviews block was a standalone block. It doesn't
	 * have any inner blocks and it rendered the tabs directly like the classic
	 * template. When upgrading, we want the existing stores using the block to
	 * continue working as before, so we moved the logic the legacy render
	 * method here.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 *
	 * @return string Rendered block output.
	 */
	protected function render_legacy_block( $attributes, $content, $block ) {
		if ( ! is_singular( 'product' ) ) {
			return $content;
		}

		ob_start();

		rewind_posts();
		while ( have_posts() ) {
			the_post();
			comments_template();
		}

		$reviews = ob_get_clean();

		return sprintf(
			'<div class="wp-block-woocommerce-product-reviews %1$s">
				%2$s
			</div>',
			StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) ),
			$reviews
		);
	}
}
PK     \5    .  BlockTypes/Reviews/ProductReviewAuthorName.phpnu [        <?php declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes\Reviews;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;

/**
 * ProductReviewAuthorName class.
 */
class ProductReviewAuthorName extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-review-author-name';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block content.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! isset( $block->context['commentId'] ) ) {
			return '';
		}

		$comment            = get_comment( $block->context['commentId'] );
		$commenter          = wp_get_current_commenter();
		$show_pending_links = isset( $commenter['comment_author'] ) && $commenter['comment_author'];
		if ( empty( $comment ) ) {
			return '';
		}

		$classes = array();
		if ( isset( $attributes['textAlign'] ) ) {
			$classes[] = 'has-text-align-' . $attributes['textAlign'];
		}
		if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) {
			$classes[] = 'has-link-color';
		}

		$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) );
		$comment_author     = get_comment_author( $comment );
		$link               = get_comment_author_url( $comment );

		if ( ! empty( $link ) && ! empty( $attributes['isLink'] ) && ! empty( $attributes['linkTarget'] ) ) {
			$comment_author = sprintf( '<a rel="external nofollow ugc" href="%1s" target="%2s" >%3s</a>', esc_url( $link ), esc_attr( $attributes['linkTarget'] ), $comment_author );
		}
		if ( '0' === $comment->comment_approved && ! $show_pending_links ) {
			$comment_author = wp_kses( $comment_author, array() );
		}

		return sprintf(
			'<div %1$s>%2$s</div>',
			$wrapper_attributes,
			$comment_author
		);
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}
}
PK     \fB  B  3  BlockTypes/Reviews/ProductReviewsPaginationNext.phpnu [        <?php declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes\Reviews;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;

/**
 * ProductReviewsPaginationNext class.
 */
class ProductReviewsPaginationNext extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-reviews-pagination-next';

	/**
	 * Render the block.
	 *
	 * @param array     $attributes Block attributes.
	 * @param string    $content    Block content.
	 * @param \WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		// Bail out early if the post ID is not set for some reason.
		if ( empty( $block->context['postId'] ) ) {
			return '';
		}

		$comment_vars     = build_comment_query_vars_from_block( $block );
		$max_page         = ( new \WP_Comment_Query( $comment_vars ) )->max_num_pages;
		$default_label    = __( 'Newer Reviews', 'woocommerce' );
		$label            = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? $attributes['label'] : $default_label;
		$pagination_arrow = $this->get_pagination_arrow( $block );

		$filter_link_attributes = static function () {
			return get_block_wrapper_attributes();
		};
		add_filter( 'next_comments_link_attributes', $filter_link_attributes );

		if ( $pagination_arrow ) {
			$label .= $pagination_arrow;
		}

		$next_comments_link = get_next_comments_link( $label, $max_page, $comment_vars['paged'] ?? null );

		remove_filter( 'next_posts_link_attributes', $filter_link_attributes );

		if ( ! isset( $next_comments_link ) ) {
			return '';
		}
		return $next_comments_link;
	}

	/**
	 * Get the pagination arrow.
	 *
	 * @param \WP_Block $block Block instance.
	 * @return string|null
	 */
	protected function get_pagination_arrow( $block ) {
		$arrow_map = array(
			'none'    => '',
			'arrow'   => '→',
			'chevron' => '»',
		);
		if ( ! empty( $block->context['reviews/paginationArrow'] ) && ! empty( $arrow_map[ $block->context['reviews/paginationArrow'] ] ) ) {
			$arrow_attribute = $block->context['reviews/paginationArrow'];
			$arrow           = $arrow_map[ $block->context['reviews/paginationArrow'] ];
			$arrow_classes   = "wp-block-woocommerce-product-reviews-pagination-next-arrow is-arrow-$arrow_attribute";
			return "<span class='$arrow_classes' aria-hidden='true'>$arrow</span>";
		}
		return null;
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string|null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \f3
  3
  +  BlockTypes/Reviews/ProductReviewContent.phpnu [        <?php declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes\Reviews;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;

/**
 * ProductReviewContent class.
 */
class ProductReviewContent extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-review-content';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block content.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! isset( $block->context['commentId'] ) ) {
			return '';
		}

		$comment            = get_comment( $block->context['commentId'] );
		$commenter          = wp_get_current_commenter();
		$show_pending_links = isset( $commenter['comment_author'] ) && $commenter['comment_author'];
		if ( empty( $comment ) ) {
			return '';
		}

		$args         = array();
		$comment_text = get_comment_text( $comment, $args );
		if ( ! $comment_text ) {
			return '';
		}

		/**
		 * This filter is documented in wp-includes/comment-template.php
		 *
		 * @since 1.2.0
		 */
		$comment_text = apply_filters( 'comment_text', $comment_text, $comment, $args );

		$moderation_note = '';
		if ( '0' === $comment->comment_approved ) {
			if ( $commenter['comment_author_email'] ) {
				$moderation_note = __( 'Your review is awaiting moderation.', 'woocommerce' );
			} else {
				$moderation_note = __( 'Your review is awaiting moderation. This is a preview; your review will be visible after it has been approved.', 'woocommerce' );
			}
			$moderation_note = '<p><em class="review-awaiting-moderation">' . esc_html( $moderation_note ) . '</em></p>';
			if ( ! $show_pending_links ) {
				$comment_text = wp_kses( $comment_text, array() );
			}
		}

		$classes = array();
		if ( isset( $attributes['textAlign'] ) ) {
			$classes[] = 'has-text-align-' . $attributes['textAlign'];
		}
		if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) {
			$classes[] = 'has-link-color';
		}

		$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) );

		return sprintf(
			'<div %1$s>%2$s%3$s</div>',
			$wrapper_attributes,
			$moderation_note,
			$comment_text
		);
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}
}
PK     \/",  ,  6  BlockTypes/Reviews/ProductReviewsPaginationNumbers.phpnu [        <?php declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes\Reviews;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;

/**
 * ProductReviewsPaginationNumbers class.
 */
class ProductReviewsPaginationNumbers extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-reviews-pagination-numbers';

	/**
	 * Render the block.
	 *
	 * @param array     $attributes Block attributes.
	 * @param string    $content    Block content.
	 * @param \WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		// Bail out early if the post ID is not set for some reason.
		if ( empty( $block->context['postId'] ) ) {
			return '';
		}

		$comment_vars = build_comment_query_vars_from_block( $block );

		$total   = ( new \WP_Comment_Query( $comment_vars ) )->max_num_pages;
		$current = ! empty( $comment_vars['paged'] ) ? $comment_vars['paged'] : null;

		// Render links.
		$content = paginate_comments_links(
			array(
				'total'     => $total,
				'current'   => $current,
				'prev_next' => false,
				'echo'      => false,
			)
		);

		if ( empty( $content ) ) {
			return '';
		}

		$wrapper_attributes = get_block_wrapper_attributes();

		return sprintf(
			'<div %1$s>%2$s</div>',
			$wrapper_attributes,
			$content
		);
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string|null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \"#  #  (  BlockTypes/Reviews/ProductReviewForm.phpnu [        <?php declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes\Reviews;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;

/**
 * ProductReviewForm class.
 */
class ProductReviewForm extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-review-form';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block content.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! isset( $block->context['postId'] ) ) {
			return '';
		}

		if ( post_password_required( $block->context['postId'] ) ) {
			return '';
		}

		$product = wc_get_product( $block->context['postId'] );

		if ( ! $product ) {
			return '';
		}

		if ( get_option( 'woocommerce_review_rating_verification_required' ) !== 'no' && ! wc_customer_bought_product( '', get_current_user_id(), $product->get_id() ) ) {
			return '<p class="woocommerce-verification-required">' . esc_html__( 'Only logged in customers who have purchased this product may leave a review.', 'woocommerce' ) . '</p>';
		}

		$interactivity_state  = [];
		$interactivity_config = [
			'reviewRatingEnabled' => wc_review_ratings_enabled(),
		];

		$classes = [ 'comment-respond' ];
		if ( isset( $attributes['textAlign'] ) ) {
			$classes[] = 'has-text-align-' . $attributes['textAlign'];
		}
		if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) {
			$classes[] = 'has-link-color';
		}
		$wrapper_attributes = get_block_wrapper_attributes(
			[
				'class' => implode( ' ', $classes ),
			]
		);

		$commenter    = wp_get_current_commenter();
		$comment_form = [
			/* translators: %s is product title */
			'title_reply'         => $product->get_review_count() > 0 ? esc_html__( 'Add a review', 'woocommerce' ) : sprintf( esc_html__( 'Be the first to review &ldquo;%s&rdquo;', 'woocommerce' ), esc_html( get_the_title( $block->context['postId'] ) ) ),
			/* translators: %s is product title */
			'title_reply_to'      => esc_html__( 'Leave a Reply to %s', 'woocommerce' ),
			'title_reply_before'  => '<span id="reply-title" class="comment-reply-title" role="heading" aria-level="3">',
			'title_reply_after'   => '</span>',
			'comment_notes_after' => '',
			'label_submit'        => esc_html__( 'Submit', 'woocommerce' ),
			'logged_in_as'        => '',
			'comment_field'       => '',
		];

		$name_email_required = (bool) get_option( 'require_name_email', 1 );
		$fields              = [
			'author' => [
				'label'        => __( 'Name', 'woocommerce' ),
				'type'         => 'text',
				'value'        => $commenter['comment_author'],
				'required'     => $name_email_required,
				'autocomplete' => 'name',
			],
			'email'  => [
				'label'        => __( 'Email', 'woocommerce' ),
				'type'         => 'email',
				'value'        => $commenter['comment_author_email'],
				'required'     => $name_email_required,
				'autocomplete' => 'email',
			],
		];

		$comment_form['fields'] = [];

		foreach ( $fields as $key => $field ) {
			$field_html  = '<p class="comment-form-' . esc_attr( $key ) . '">';
			$field_html .= '<label for="' . esc_attr( $key ) . '">' . esc_html( $field['label'] );

			if ( $field['required'] ) {
				$field_html .= '&nbsp;<span class="required">*</span>';
			}

			$field_html .= '</label><input id="' . esc_attr( $key ) . '" name="' . esc_attr( $key ) . '" type="' . esc_attr( $field['type'] ) . '" autocomplete="' . esc_attr( $field['autocomplete'] ) . '" value="' . esc_attr( $field['value'] ) . '" size="30" ' . ( $field['required'] ? 'required' : '' ) . ' /></p>';

			$comment_form['fields'][ $key ] = $field_html;
		}

		$account_page_url = wc_get_page_permalink( 'myaccount' );
		if ( $account_page_url ) {
			/* translators: %s opening and closing link tags respectively */
			$comment_form['must_log_in'] = '<p class="must-log-in">' . sprintf( esc_html__( 'You must be %1$slogged in%2$s to post a review.', 'woocommerce' ), '<a href="' . esc_url( $account_page_url ) . '">', '</a>' ) . '</p>';
		}

		if ( wc_review_ratings_enabled() ) {
			$interactivity_state['selectedStar']            = '';
			$interactivity_state['hoveredStar']             = '0';
			$interactivity_state['ratingError']             = '';
			$interactivity_state['hasRatingError']          = false;
			$interactivity_config['i18nRequiredRatingText'] = esc_attr__( 'Please select a rating', 'woocommerce' );
			$interactivity_config['reviewRatingRequired']   = wc_review_ratings_required();

			$comment_form['comment_field'] = '<div class="comment-form-rating"><label for="rating-selector" id="comment-form-rating-label">' .
				esc_html__( 'Your rating', 'woocommerce' ) . ( wc_review_ratings_required() ? '&nbsp;<span class="required">*</span>' : '' ) .
				'</label><select name="rating" id="rating-selector" data-wp-init="callbacks.hideRatingSelector" data-wp-bind--value="state.selectedStar" ' . ( wc_review_ratings_required() ? ' required' : '' ) . '>
				<option value="">' . esc_html__( 'Rate&hellip;', 'woocommerce' ) . '</option>
				<option value="5">' . esc_html__( 'Perfect', 'woocommerce' ) . '</option>
				<option value="4">' . esc_html__( 'Good', 'woocommerce' ) . '</option>
				<option value="3">' . esc_html__( 'Average', 'woocommerce' ) . '</option>
				<option value="2">' . esc_html__( 'Not that bad', 'woocommerce' ) . '</option>
				<option value="1">' . esc_html__( 'Very poor', 'woocommerce' ) . '</option>
			</select>' .
				'<p role="radiogroup" aria-labelledby="comment-form-rating-label" class="stars-wrapper" data-wp-init="callbacks.showRatingStars" hidden data-wp-bind--aria-invalid="state.hasRatingError"' . ( wc_review_ratings_required() ? ' aria-required="true"' : '' ) . ' aria-describedby="rating-error">' . $this->render_stars() .
				( wc_review_ratings_required() ? '<small id="rating-error" data-wp-text="state.ratingError" class="rating-error" data-wp-bind--hidden="!state.hasRatingError" role="alert"></small>' : '' ) .
				'</p></div>';
		}

		$comment_form['comment_field'] .= '<p class="comment-form-comment"><label for="comment">' . esc_html__( 'Your review', 'woocommerce' ) . '&nbsp;<span class="required">*</span></label><textarea id="comment" name="comment" cols="45" rows="8" required></textarea></p>';

		add_filter( 'comment_form_defaults', 'post_comments_form_block_form_defaults' );

		ob_start();
		echo '<div id="review_form_wrapper" data-wp-interactive="woocommerce/product-reviews"><div id="review_form">';
		/**
			* Filters the comment form arguments.
			*
			* @since 9.9.0
			* @param array $comment_form The comment form arguments.
			* @param int   $post_id      The post ID.
			*/
		comment_form( apply_filters( 'woocommerce_product_review_comment_form_args', $comment_form ), $block->context['postId'] );
		echo '</div></div>';
		$form = ob_get_clean();

		remove_filter( 'comment_form_defaults', 'post_comments_form_block_form_defaults' );

		$form = str_replace( 'class="comment-respond"', $wrapper_attributes, $form );

		$p = new \WP_HTML_Tag_Processor( $form );

		if ( $p->next_tag( 'form' ) ) {
			$p->set_attribute( 'data-wp-on--submit', 'actions.handleSubmit' );
		}

		if ( ! empty( $interactivity_state ) ) {
			wp_interactivity_state( 'woocommerce/product-reviews', $interactivity_state );
		}
		if ( ! empty( $interactivity_config ) ) {
			wp_interactivity_config( 'woocommerce/product-reviews', $interactivity_config );
		}

		return $p->get_updated_html();
	}

	/**
	 * Render stars for rating selector.
	 */
	private function render_stars() {
		ob_start();
		echo '<span class="stars">';
		for ( $i = 1; $i < 6; $i++ ) {
			?>
			<button
				role="radio"
				type="button"
				<?php /* translators: %d is the rating value from 1 to 5 */ ?>
				aria-label='<?php echo esc_attr( sprintf( _n( '%d of 5 star', '%d of 5 stars', $i, 'woocommerce' ), $i ) ); ?>'
				data-wp-on-async--mouseenter="actions.hoverStar"
				data-wp-on-async--mouseleave="actions.leaveStar"
				data-wp-on-async--click="actions.selectStar"
				data-wp-on-async--keydown="actions.changeRatingWithKeyboard"
				data-wp-class--is-hovered="state.isStarHovered"
				data-wp-class--is-selected="state.isStarSelected"
				data-wp-bind--aria-checked="state.isStarSelected"
				<?php echo wp_interactivity_data_wp_context( [ 'starValue' => $i ] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			>
				<svg
					width='24'
					height='24'
					viewBox='0 0 24 24'
					aria-hidden="true"
					focusable="false"
				>
					<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" fill="none" stroke="currentColor" stroke-width="1.5"/>
				</svg>
			</button>
			<?php
		}
		echo '</span>';
		return ob_get_clean();
	}
}
PK     \s    (  BlockTypes/Reviews/ProductReviewDate.phpnu [        <?php declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes\Reviews;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;

/**
 * ProductReviewDate class.
 */
class ProductReviewDate extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-review-date';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block content.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! isset( $block->context['commentId'] ) ) {
			return '';
		}

		$comment = get_comment( $block->context['commentId'] );
		if ( empty( $comment ) ) {
			return '';
		}

		$classes = ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) ? 'has-link-color' : '';

		$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classes ) );
		if ( isset( $attributes['format'] ) && 'human-diff' === $attributes['format'] ) {
			// translators: %s: human-readable time difference.
			$formatted_date = sprintf( __( '%s ago', 'woocommerce' ), human_time_diff( get_comment_date( 'U', $comment ) ) );
		} else {
			$formatted_date = get_comment_date( empty( $attributes['format'] ) ? '' : $attributes['format'], $comment );
		}
		$link = get_comment_link( $comment );

		if ( ! empty( $attributes['isLink'] ) ) {
			$formatted_date = sprintf( '<a href="%1s">%2s</a>', esc_url( $link ), $formatted_date );
		}

		return sprintf(
			'<div %1$s><time datetime="%2$s">%3$s</time></div>',
			$wrapper_attributes,
			esc_attr( get_comment_date( 'c', $comment ) ),
			$formatted_date
		);
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}
}
PK     \3o˨    *  BlockTypes/Reviews/ProductReviewRating.phpnu [        <?php declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes\Reviews;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;

/**
 * ProductReviewRating class.
 */
class ProductReviewRating extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-review-rating';

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string[]|null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block content.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! isset( $block->context['commentId'] ) ) {
			return '';
		}

		$rating = intval( get_comment_meta( $block->context['commentId'], 'rating', true ) );

		$html = '';

		if ( 0 < $rating ) {
			// translators: %s: Rating.
			$label = sprintf( __( 'Rated %s out of 5', 'woocommerce' ), $rating );
			$html  = sprintf(
				'<div class="wc-block-product-review-rating__container">
					<div class="wc-block-product-review-rating__stars" role="img" aria-label="%1$s">
						%2$s
					</div>
				</div>
				',
				esc_attr( $label ),
				wc_get_star_rating_html( $rating )
			);
		}

		return sprintf(
			'<div %1$s>
				%2$s
			</div>',
			get_block_wrapper_attributes(),
			$html
		);
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}
}
PK     \v2  2  /  BlockTypes/Reviews/ProductReviewsPagination.phpnu [        <?php declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes\Reviews;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;

/**
 * ProductReviewsPagination class.
 */
class ProductReviewsPagination extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-reviews-pagination';

	/**
	 * Render the block.
	 *
	 * @param array     $attributes Block attributes.
	 * @param string    $content    Block content.
	 * @param \WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( empty( trim( $content ) ) ) {
			return '';
		}

		if ( post_password_required() ) {
			return;
		}

		$classes            = ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) ? 'has-link-color' : '';
		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class'               => $classes,
				'data-wp-interactive' => 'woocommerce/product-reviews',
			)
		);

		$p = new \WP_HTML_Tag_Processor( $content );
		while ( $p->next_tag( 'a' ) ) {
			$p->set_attribute( 'data-wp-on--click', 'actions.navigate' );
		}

		return sprintf(
			'<div %1$s>%2$s</div>',
			$wrapper_attributes,
			$p->get_updated_html()
		);
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}
}
PK     \aH9  9    BlockTypes/CustomerAccount.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Blocks\Utils\BlockHooksTrait;

/**
 * CustomerAccount class.
 */
class CustomerAccount extends AbstractBlock {
	use BlockHooksTrait;
	use EnableBlockJsonAssetsTrait;

	const TEXT_ONLY    = 'text_only';
	const ICON_ONLY    = 'icon_only';
	const DISPLAY_ALT  = 'alt';
	const DISPLAY_LINE = 'line';

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'customer-account';

	/**
	 * Block Hook API placements.
	 *
	 * @var array
	 */
	protected $hooked_block_placements = array(
		array(
			'position' => 'after',
			'anchor'   => 'core/navigation',
			'area'     => 'header',
			'callback' => 'should_unhook_block',
			'version'  => '8.4.0',
		),
	);

	/**
	 * Initialize this block type.
	 */
	protected function initialize() {
		parent::initialize();
		/**
		 * The hooked_block_{$hooked_block_type} filter was added in WordPress 6.5.
		 * We are the only code adding the filter 'hooked_block_woocommerce/customer-account'.
		 * Using has_filter() for a compatibility check won't work because add_filter() is used in the same file.
		 */
		if ( version_compare( get_bloginfo( 'version' ), '6.5', '>=' ) ) {
			add_filter( 'hooked_block_woocommerce/customer-account', array( $this, 'modify_hooked_block_attributes' ), 10, 5 );
			add_filter( 'hooked_block_types', array( $this, 'register_hooked_block' ), 9, 4 );
		}
	}

	/**
	 * Callback for the Block Hooks API to modify the attributes of the hooked block.
	 *
	 * @param array|null                      $parsed_hooked_block The parsed block array for the given hooked block type, or null to suppress the block.
	 * @param string                          $hooked_block_type   The hooked block type name.
	 * @param string                          $relative_position   The relative position of the hooked block.
	 * @param array                           $parsed_anchor_block The anchor block, in parsed block array format.
	 * @param WP_Block_Template|WP_Post|array $context             The block template, template part, `wp_navigation` post type,
	 *                                                             or pattern that the anchor block belongs to.
	 * @return array|null
	 */
	public function modify_hooked_block_attributes( $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block, $context ) {
		$parsed_hooked_block['attrs']['displayStyle'] = 'icon_only';
		$parsed_hooked_block['attrs']['iconStyle']    = 'line';
		$parsed_hooked_block['attrs']['iconClass']    = 'wc-block-customer-account__account-icon';

		$customer_account_block_font_size = wp_get_global_styles( array( 'blocks', 'woocommerce/customer-account', 'typography', 'fontSize' ) );

		if ( ! is_string( $customer_account_block_font_size ) ) {
			$navigation_block_font_size = wp_get_global_styles( array( 'blocks', 'core/navigation', 'typography', 'fontSize' ) );

			if ( is_string( $navigation_block_font_size ) ) {
				$parsed_hooked_block['attrs']['style']['typography']['fontSize'] = $navigation_block_font_size;
			}
		}

		return $parsed_hooked_block;
	}

	/**
	 * Callback for the Block Hooks API to determine if the block should be auto-inserted.
	 *
	 * @param array                             $hooked_blocks An array of block slugs hooked into a given context.
	 * @param string                            $position      Position of the block insertion point.
	 * @param string                            $anchor_block  The block acting as the anchor for the inserted block.
	 * @param array|\WP_Post|\WP_Block_Template $context       Where the block is embedded.
	 *
	 * @return array
	 */
	protected function should_unhook_block( $hooked_blocks, $position, $anchor_block, $context ) {
		$block_name      = $this->namespace . '/' . $this->block_name;
		$block_is_hooked = in_array( $block_name, $hooked_blocks, true );

		if ( $block_is_hooked ) {
			$active_theme   = wp_get_theme()->get( 'Name' );
			$exclude_themes = array( 'Twenty Twenty-Two', 'Twenty Twenty-Three' );

			if ( in_array( $active_theme, $exclude_themes, true ) ) {
				$key = array_search( $block_name, $hooked_blocks, true );
				unset( $hooked_blocks[ $key ] );
			}
		}

		return $hooked_blocks;
	}

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 *
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ) {
		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
		$has_myaccount_page = get_option( 'woocommerce_myaccount_page_id' );
		$account_link       = $has_myaccount_page ? wc_get_account_endpoint_url( 'dashboard' ) : wp_login_url();
		$has_dropdown       = ! empty( $attributes['hasDropdownNavigation'] ) && is_user_logged_in() && $has_myaccount_page;

		$aria_label   = self::ICON_ONLY === $attributes['displayStyle'] ? ' aria-label="' . esc_attr( $this->render_label() ) . '"' : '';
		$label_markup = self::ICON_ONLY === $attributes['displayStyle'] ? '' : '<span class="label">' . wp_kses( $this->render_label(), array() ) . '</span>';

		if ( ! $has_dropdown ) {
			return $this->render_link( $attributes, $classes_and_styles, $account_link, $aria_label, $label_markup );
		}

		return $this->render_dropdown( $attributes, $classes_and_styles, $aria_label, $label_markup );
	}

	/**
	 * Render the block as a simple link (default behavior).
	 *
	 * @param array  $attributes        Block attributes.
	 * @param array  $classes_and_styles Classes and styles from block attributes.
	 * @param string $account_link      URL to link to.
	 * @param string $aria_label        Pre-computed aria-label attribute string.
	 * @param string $label_markup      Pre-computed label HTML markup.
	 *
	 * @return string Rendered block output.
	 */
	private function render_link( $attributes, $classes_and_styles, $account_link, $aria_label, $label_markup ) {
		$allowed_svg = $this->get_allowed_svg();

		ob_start();
		?>
		<div
			class="wp-block-woocommerce-customer-account <?php echo esc_attr( $classes_and_styles['classes'] ); ?>"
			style="<?php echo esc_attr( $classes_and_styles['styles'] ); ?>"
		>	
			<a
				class="wc-block-customer-account__link"
				href="<?php echo esc_url( $account_link ); ?>"
				<?php echo $aria_label; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			>
				<?php echo wp_kses( $this->render_icon( $attributes ), $allowed_svg ); ?>
				<?php echo $label_markup; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			</a>
		</div>
		<?php
		return (string) ob_get_clean();
	}

	/**
	 * Render the block as a dropdown navigation.
	 *
	 * @param array  $attributes        Block attributes.
	 * @param array  $classes_and_styles Classes and styles from block attributes.
	 * @param string $aria_label        Pre-computed aria-label attribute string.
	 * @param string $label_markup      Pre-computed label HTML markup.
	 *
	 * @return string Rendered block output.
	 */
	private function render_dropdown( $attributes, $classes_and_styles, $aria_label, $label_markup ) {
		$allowed_svg = $this->get_allowed_svg();

		$context = array(
			'isDropdownOpen' => false,
			'showAbove'      => false,
			'alignRight'     => false,
		);

		$menu_items    = wc_get_account_menu_items();
		$dropdown_html = $this->render_dropdown_menu( $menu_items );

		ob_start();
		?>
		<div
			class="wp-block-woocommerce-customer-account wc-block-customer-account--has-dropdown <?php echo esc_attr( $classes_and_styles['classes'] ); ?>"
			style="<?php echo esc_attr( $classes_and_styles['styles'] ); ?>"
			data-wp-interactive="woocommerce/customer-account/private"
			<?php echo wp_interactivity_data_wp_context( $context ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			data-wp-class--wc-block-customer-account--align-right="context.alignRight"
			data-wp-class--wc-block-customer-account--is-dropdown-open="context.isDropdownOpen"
			data-wp-class--wc-block-customer-account--show-above="context.showAbove"
			data-wp-on--focusout="actions.handleFocusOut"
			data-wp-on-document--click="actions.handleDocumentClick"
			data-wp-on-document--keydown="actions.handleKeydown"
		>
			<button
				type="button"
				class="wc-block-customer-account__toggle"
				<?php echo $aria_label; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				aria-haspopup="true"
				data-wp-bind--aria-expanded="context.isDropdownOpen"
				data-wp-on--click="actions.toggleDropdown"
			>
				<?php echo wp_kses( $this->render_icon( $attributes ), $allowed_svg ); ?>
				<?php echo $label_markup; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				<?php echo $this->render_caret_icon(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			</button>
			<nav
				class="wc-block-customer-account__dropdown"
				aria-label="<?php echo esc_attr__( 'Account navigation', 'woocommerce' ); ?>"
				data-wp-bind--hidden="!context.isDropdownOpen"
			>
				<?php echo $dropdown_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			</nav>
		</div>
		<?php
		return (string) ob_get_clean();
	}

	/**
	 * Render the dropdown menu content with three sections.
	 *
	 * @param array $menu_items Account menu items from wc_get_account_menu_items().
	 *
	 * @return string Rendered dropdown menu HTML.
	 */
	private function render_dropdown_menu( $menu_items ) {
		$sections = array();

		if ( isset( $menu_items['dashboard'] ) ) {
			$sections[] = $this->render_section( array( 'dashboard' => $menu_items['dashboard'] ) );
		}

		$nav_items = array_diff_key(
			$menu_items,
			array_flip( array( 'dashboard', 'customer-logout' ) )
		);
		if ( ! empty( $nav_items ) ) {
			$sections[] = $this->render_section( $nav_items );
		}

		if ( isset( $menu_items['customer-logout'] ) ) {
			$sections[] = $this->render_section( array( 'customer-logout' => $menu_items['customer-logout'] ) );
		}

		return implode( '<div class="wc-block-customer-account__dropdown-divider"></div>', $sections );
	}

	/**
	 * Render a dropdown section wrapping one or more menu items.
	 *
	 * @param array $items Associative array of endpoint => label pairs.
	 *
	 * @return string Rendered section HTML.
	 */
	private function render_section( $items ) {
		$output = '<div class="wc-block-customer-account__dropdown-section">';
		foreach ( $items as $endpoint => $label ) {
			$output .= $this->render_menu_item( $endpoint, $label );
		}
		$output .= '</div>';
		return $output;
	}

	/**
	 * Render a single dropdown menu item.
	 *
	 * @param string $endpoint The account endpoint key.
	 * @param string $label    The menu item label.
	 *
	 * @return string Rendered menu item HTML.
	 */
	private function render_menu_item( $endpoint, $label ) {
		$url = wc_get_account_endpoint_url( $endpoint );
		return '<a href="' . esc_url( $url ) . '" class="wc-block-customer-account__dropdown-item">'
			. esc_html( $label )
			. '</a>';
	}

	/**
	 * Render the caret/chevron icon for the dropdown toggle.
	 *
	 * @return string SVG markup for the caret icon.
	 */
	private function render_caret_icon() {
		return '<svg class="wc-block-customer-account__caret" width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
			<path d="M1 1L5 5L9 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
		</svg>';
	}

	/**
	 * Get the allowed SVG tags and attributes for wp_kses.
	 *
	 * @return array Allowed SVG elements and attributes.
	 */
	private function get_allowed_svg() {
		return array(
			'svg'    => array(
				'class'   => true,
				'xmlns'   => true,
				'width'   => true,
				'height'  => true,
				'viewbox' => true,
			),
			'path'   => array(
				'd'         => true,
				'fill'      => true,
				'fill-rule' => true,
				'clip-rule' => true,
			),
			'circle' => array(
				'cx'           => true,
				'cy'           => true,
				'r'            => true,
				'stroke'       => true,
				'stroke-width' => true,
				'fill'         => true,
			),
		);
	}

	/**
	 * Gets the icon to render depending on the iconStyle and displayStyle.
	 *
	 * @param array $attributes Block attributes.
	 *
	 * @return string Label to render on the block
	 */
	private function render_icon( $attributes ) {
		if ( self::TEXT_ONLY === $attributes['displayStyle'] ) {
			return '';
		}

		if ( self::DISPLAY_LINE === $attributes['iconStyle'] ) {
			return '<svg class="' . $attributes['iconClass'] . '" viewBox="1 1 29 29" fill="none" xmlns="http://www.w3.org/2000/svg">
				<circle
					cx="16"
					cy="10.5"
					r="3.5"
					stroke="currentColor"
					stroke-width="2"
					fill="none"
				/>
				<path
					fill-rule="evenodd"
					clip-rule="evenodd"
					d="M11.5 18.5H20.5C21.8807 18.5 23 19.6193 23 21V25.5H25V21C25 18.5147 22.9853 16.5 20.5 16.5H11.5C9.01472 16.5 7 18.5147 7 21V25.5H9V21C9 19.6193 10.1193 18.5 11.5 18.5Z"
					fill="currentColor"
				/>
			</svg>';
		}

		if ( self::DISPLAY_ALT === $attributes['iconStyle'] ) {
			return '<svg class="' . $attributes['iconClass'] . '" xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 25 25">
				<path
					d="M9 0C4.03579 0 0 4.03579 0 9C0 13.9642 4.03579 18 9 18C13.9642 18 18 13.9642 18 9C18 4.03579 13.9642 0 9 0ZM9 4.32C10.5347 4.32 11.7664 5.57056 11.7664 7.08638C11.7664 8.62109 10.5158 9.85277 9 9.85277C7.4653 9.85277 6.23362 8.60221 6.23362 7.08638C6.23362 5.57056 7.46526 4.32 9 4.32ZM9 10.7242C11.1221 10.7242 12.96 12.2021 13.7937 14.4189C12.5242 15.5559 10.8379 16.238 9 16.238C7.16207 16.238 5.49474 15.5369 4.20632 14.4189C5.05891 12.2021 6.87793 10.7242 9 10.7242Z"
					fill="currentColor"
				/>
			</svg>';
		}

		return '<svg class="' . $attributes['iconClass'] . '" xmlns="http://www.w3.org/2000/svg" viewBox="-5 -5 25 25">
			<path
				fill-rule="evenodd"
				clip-rule="evenodd"
				d="M8.00009 8.34785C10.3096 8.34785 12.1819 6.47909 12.1819 4.17393C12.1819 1.86876 10.3096 0 8.00009 0C5.69055 0 3.81824 1.86876 3.81824 4.17393C3.81824 6.47909 5.69055 8.34785 8.00009 8.34785ZM0.333496 15.6522C0.333496 15.8444 0.489412 16 0.681933 16H15.3184C15.5109 16 15.6668 15.8444 15.6668 15.6522V14.9565C15.6668 12.1428 13.7821 9.73911 10.0912 9.73911H5.90931C2.21828 9.73911 0.333645 12.1428 0.333645 14.9565L0.333496 15.6522Z"
				fill="currentColor"
			/>
		</svg>';
	}

	/**
	 * Gets the label to render depending on the displayStyle.
	 *
	 * @return string Label to render on the block.
	 */
	private function render_label() {
		return get_current_user_id()
			? __( 'My Account', 'woocommerce' )
			: __( 'Login', 'woocommerce' );
	}
}
PK     \֍l	  l	    BlockTypes/CategoryTitle.phpnu [        <?php
declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CategoryTitle block: renders the current term title using context.
 */
class CategoryTitle extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'category-title';

	/**
	 * Render the block.
	 *
	 * @param array     $attributes Block attributes.
	 * @param string    $content    Block content.
	 * @param \WP_Block $block     Block instance.
	 *
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ) {
		$term_id       = $block->context['termId'] ?? 0;
		$term_taxonomy = $block->context['termTaxonomy'] ?? 'product_cat';

		$level      = isset( $attributes['level'] ) ? max( 0, min( 6, intval( $attributes['level'] ) ) ) : 2;
		$text_align = isset( $attributes['textAlign'] ) ? sanitize_key( $attributes['textAlign'] ) : '';
		$is_link    = ! empty( $attributes['isLink'] );
		$rel        = isset( $attributes['rel'] ) ? esc_attr( $attributes['rel'] ) : '';
		$target     = isset( $attributes['linkTarget'] ) ? esc_attr( $attributes['linkTarget'] ) : '_self';

		if ( ! $term_id ) {
			return '';
		}

		$term = get_term( $term_id, $term_taxonomy );
		if ( ! $term || is_wp_error( $term ) ) {
			return '';
		}

		$tag_name           = 0 === $level ? 'p' : 'h' . $level;
		$classes            = $text_align ? 'has-text-align-' . $text_align : '';
		$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classes ) );

		$title_html = '';
		if ( $is_link ) {
			$link = get_term_link( $term );
			if ( ! is_wp_error( $link ) ) {
				$title_html = sprintf(
					'<%1$s %2$s><a href="%3$s" target="%4$s" rel="%5$s">%6$s</a></%1$s>',
					esc_attr( $tag_name ),
					$wrapper_attributes,
					esc_url( $link ),
					esc_attr( $target ),
					$rel,
					esc_html( $term->name )
				);
			}
		}

		if ( '' === $title_html ) {
			$title_html = sprintf(
				'<%1$s %2$s>%3$s</%1$s>',
				esc_attr( $tag_name ),
				$wrapper_attributes,
				esc_html( $term->name )
			);
		}

		return $title_html;
	}

	/**
	 * Register the context used by this block.
	 *
	 * @return array
	 */
	protected function get_block_type_uses_context() {
		return [ 'termId', 'termTaxonomy' ];
	}

	/**
	 * Disable the style handle for this block.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \k`  `    BlockTypes/ProductRating.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
 * ProductRating class.
 */
class ProductRating extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-rating';

	/**
	 * API version name.
	 *
	 * @var string
	 */
	protected $api_version = '3';

	/**
	 * Get the block's attributes.
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 * @return array  Block attributes merged with defaults.
	 */
	private function parse_attributes( $attributes ) {
		// These should match what's set in JS `registerBlockType`.
		$defaults = array(
			'productId'                           => 0,
			'isDescendentOfQueryLoop'             => false,
			'textAlign'                           => '',
			'isDescendentOfSingleProductBlock'    => false,
			'isDescendentOfSingleProductTemplate' => false,
		);

		return wp_parse_args( $attributes, $defaults );
	}

	/**
	 * Overwrite parent method to prevent script registration.
	 *
	 * It is necessary to register and enqueues assets during the render
	 * phase because we want to load assets only if the block has the content.
	 */
	protected function register_block_type_assets() {
		return null;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
	}

	/**
	 * Register the context.
	 */
	protected function get_block_type_uses_context() {
		return [ 'query', 'queryId', 'postId' ];
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! empty( $content ) ) {
			parent::register_block_type_assets();
			$this->register_chunk_translations( [ $this->block_name ] );
			return $content;
		}

		$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
		$product = wc_get_product( $post_id );

		if ( $product && $product->get_review_count() > 0
			&& $product->get_reviews_allowed()
			&& wc_reviews_enabled() ) {
			$product_reviews_count                    = $product->get_review_count();
			$product_rating                           = $product->get_average_rating();
			$parsed_attributes                        = $this->parse_attributes( $attributes );
			$is_descendent_of_single_product_block    = $parsed_attributes['isDescendentOfSingleProductBlock'];
			$is_descendent_of_single_product_template = $parsed_attributes['isDescendentOfSingleProductTemplate'];

			$styles_and_classes            = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
			$text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes );

			/**
			 * Filter the output from wc_get_rating_html.
			 *
			 * @param string $html   Star rating markup. Default empty string.
			 * @param float  $rating Rating being shown.
			 * @param int    $count  Total number of ratings.
			 * @return string
			 */
			$filter_rating_html = function( $html, $rating, $count ) use ( $post_id, $product_rating, $product_reviews_count, $is_descendent_of_single_product_block, $is_descendent_of_single_product_template ) {
				$product_permalink = get_permalink( $post_id );
				$reviews_count     = $count;
				$average_rating    = $rating;

				if ( $product_rating ) {
					$average_rating = $product_rating;
				}

				if ( $product_reviews_count ) {
					$reviews_count = $product_reviews_count;
				}

				if ( 0 < $average_rating || false === $product_permalink ) {
					/* translators: %s: rating */
					$label                  = sprintf( __( 'Rated %s out of 5', 'woocommerce' ), $average_rating );
					$customer_reviews_count = sprintf(
						/* translators: %s is referring to the total of reviews for a product */
						_n(
							'(%s customer review)',
							'(%s customer reviews)',
							$reviews_count,
							'woocommerce'
						),
						esc_html( $reviews_count )
					);

					if ( $is_descendent_of_single_product_block ) {
						$customer_reviews_count = '<a href="' . esc_url( $product_permalink ) . '#reviews">' . $customer_reviews_count . '</a>';
					} elseif ( $is_descendent_of_single_product_template ) {
						$customer_reviews_count = '<a class="woocommerce-review-link" rel="nofollow" href="#reviews">' . $customer_reviews_count . '</a>';
					}

					$reviews_count_html = sprintf( '<span class="wc-block-components-product-rating__reviews_count">%1$s</span>', $customer_reviews_count );
					$html               = sprintf(
						'<div class="wc-block-components-product-rating__container">
							<div class="wc-block-components-product-rating__stars wc-block-grid__product-rating__stars" role="img" aria-label="%1$s">
								%2$s
							</div>
							%3$s
						</div>
						',
						esc_attr( $label ),
						wc_get_star_rating_html( $average_rating, $reviews_count ),
						$is_descendent_of_single_product_block || $is_descendent_of_single_product_template ? $reviews_count_html : ''
					);
				} else {
					$html = '';
				}

				return $html;
			};

			add_filter(
				'woocommerce_product_get_rating_html',
				$filter_rating_html,
				10,
				3
			);

			$rating_html = wc_get_rating_html( $product->get_average_rating() );

			remove_filter(
				'woocommerce_product_get_rating_html',
				$filter_rating_html,
				10
			);

			$classes = implode(
				' ',
				array_filter(
					array(
						'wc-block-components-product-rating wc-block-grid__product-rating',
						esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
						esc_attr( $styles_and_classes['classes'] ),
					)
				)
			);

			$wrapper_attributes = get_block_wrapper_attributes(
				array(
					'class' => $classes,
					'style' => esc_attr( $styles_and_classes['styles'] ?? '' ),
				)
			);

			return sprintf(
				'<div %1$s>
					%2$s
				</div>',
				$wrapper_attributes,
				$rating_html
			);
		}
		return '';
	}
}
PK     \6      BlockTypes/CouponCode.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\EmailEditor\Email_Editor_Container;
use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context;
use Automattic\WooCommerce\EmailEditor\Engine\Theme_Controller;
use Automattic\WooCommerce\EmailEditor\Integrations\Utils\Styles_Helper;
use Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper;
use WP_Block;

/**
 * CouponCode block for displaying coupon codes in emails.
 *
 * @since 10.5.0
 */
class CouponCode extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'coupon-code';

	/**
	 * Default styles for the coupon code element.
	 */
	private const DEFAULT_STYLES = array(
		'font-size'        => '1.2em',
		'padding'          => '12px 20px',
		'display'          => 'inline-block',
		'border'           => '2px dashed #cccccc',
		'border-radius'    => '4px',
		'box-sizing'       => 'border-box',
		'color'            => '#000000',
		'background-color' => '#f5f5f5',
		'text-align'       => 'center',
		'font-weight'      => 'bold',
		'letter-spacing'   => '1px',
	);

	/**
	 * Get the editor script handle for this block type.
	 *
	 * @param string|null $key Data to get. Valid keys: "handle", "path", "dependencies".
	 * @return array|string|null
	 */
	protected function get_block_type_editor_script( $key = null ) {
		$script = array(
			'handle'       => 'wc-' . $this->block_name . '-block',
			'path'         => $this->asset_api->get_block_asset_build_path( $this->block_name ),
			'dependencies' => array( 'wc-blocks' ),
		);
		return null === $key ? $script : ( $script[ $key ] ?? null );
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Render the coupon code block.
	 *
	 * @param array         $attributes Block attributes.
	 * @param string        $content Block content.
	 * @param WP_Block|null $block Block instance.
	 * @return string
	 */
	protected function render( $attributes, $content, $block ) {
		$parsed_block = $block instanceof WP_Block ? $block->parsed_block : array();
		$attributes   = $this->get_block_attributes( $parsed_block, $attributes );
		$coupon_code  = $this->get_coupon_code( $attributes );

		if ( empty( $coupon_code ) ) {
			return '';
		}

		$rendering_context = $this->get_rendering_context( $block );
		$coupon_html       = $this->build_coupon_html( $coupon_code, $attributes, $rendering_context );

		return $this->wrap_for_email( $coupon_html, $parsed_block );
	}

	/**
	 * Get block attributes from parsed block or fallback.
	 *
	 * @param array $parsed_block Parsed block data.
	 * @param array $fallback Fallback attributes.
	 * @return array
	 */
	private function get_block_attributes( array $parsed_block, $fallback ): array {
		$attributes = $parsed_block['attrs'] ?? $fallback ?? array();
		return is_array( $attributes ) ? $attributes : array();
	}

	/**
	 * Extract coupon code from attributes.
	 *
	 * @param array $attributes Block attributes.
	 * @return string
	 */
	private function get_coupon_code( array $attributes ): string {
		$coupon_code = $attributes['couponCode'] ?? '';
		return is_string( $coupon_code ) ? $coupon_code : '';
	}

	/**
	 * Get rendering context from block or create a new one.
	 *
	 * @param WP_Block|null $block Block instance.
	 * @return Rendering_Context
	 */
	private function get_rendering_context( $block ): Rendering_Context {
		if ( $block instanceof WP_Block
			&& isset( $block->context['renderingContext'] )
			&& $block->context['renderingContext'] instanceof Rendering_Context
		) {
			return $block->context['renderingContext'];
		}

		$theme_controller = Email_Editor_Container::container()->get( Theme_Controller::class );
		return new Rendering_Context( $theme_controller->get_theme(), array() );
	}

	/**
	 * Build the coupon code HTML element with styles.
	 *
	 * @param string            $coupon_code Coupon code text.
	 * @param array             $attributes Block attributes.
	 * @param Rendering_Context $rendering_context Rendering context for style resolution.
	 * @return string
	 */
	private function build_coupon_html( string $coupon_code, array $attributes, Rendering_Context $rendering_context ): string {
		$block_styles = Styles_Helper::get_block_styles(
			$attributes,
			$rendering_context,
			array( 'border', 'background-color', 'color', 'typography', 'spacing' )
		);

		$declarations = $block_styles['declarations'] ?? array();

		if ( ! $this->has_valid_background_color( $declarations ) ) {
			$declarations['background-color'] = $this->resolve_background_color( $attributes, $rendering_context );
		}

		$merged_styles = array_merge( self::DEFAULT_STYLES, $declarations );
		$css           = \WP_Style_Engine::compile_css( $merged_styles, '' );

		return sprintf(
			'<span class="woocommerce-coupon-code" style="%s">%s</span>',
			esc_attr( $css ),
			esc_html( $coupon_code )
		);
	}

	/**
	 * Check if declarations contain a valid CSS background color.
	 *
	 * @param array $declarations CSS declarations.
	 * @return bool
	 */
	private function has_valid_background_color( array $declarations ): bool {
		if ( empty( $declarations['background-color'] ) ) {
			return false;
		}
		return $this->is_css_color_value( $declarations['background-color'] );
	}

	/**
	 * Resolve background color from attributes, translating color slugs if needed.
	 *
	 * @param array             $attributes Block attributes.
	 * @param Rendering_Context $rendering_context Rendering context.
	 * @return string Resolved color value or default.
	 */
	private function resolve_background_color( array $attributes, Rendering_Context $rendering_context ): string {
		if ( empty( $attributes['backgroundColor'] ) ) {
			return self::DEFAULT_STYLES['background-color'];
		}

		$color_slug = $attributes['backgroundColor'];

		// Try to get color from normalized styles (handles slug translation).
		$normalized = Styles_Helper::get_normalized_block_styles( $attributes, $rendering_context );
		$color      = $normalized['color']['background'] ?? '';

		if ( $this->is_css_color_value( $color ) ) {
			return $color;
		}

		// Fallback: try direct translation if normalization returned the slug unchanged.
		$translated = $rendering_context->translate_slug_to_color( $color_slug );
		if ( $this->is_css_color_value( $translated ) ) {
			return $translated;
		}

		return self::DEFAULT_STYLES['background-color'];
	}

	/**
	 * Check if a string is a valid CSS color value (hex, rgb, or hsl).
	 *
	 * @param string $value Value to check.
	 * @return bool
	 */
	private function is_css_color_value( string $value ): bool {
		return str_starts_with( $value, '#' )
			|| str_starts_with( $value, 'rgb' )
			|| str_starts_with( $value, 'hsl' );
	}

	/**
	 * Wrap coupon HTML in an email-compatible table structure.
	 *
	 * @param string $coupon_html Coupon HTML content.
	 * @param array  $parsed_block Parsed block data.
	 * @return string
	 */
	private function wrap_for_email( string $coupon_html, array $parsed_block ): string {
		$align = $this->get_alignment( $parsed_block );

		$table_attrs = array(
			'style' => \WP_Style_Engine::compile_css(
				array(
					'border-collapse' => 'collapse',
					'width'           => '100%',
				),
				''
			),
			'width' => '100%',
		);

		$cell_attrs = array(
			'class' => 'email-coupon-code-cell',
			'style' => \WP_Style_Engine::compile_css(
				array(
					'padding'    => '10px 0',
					'text-align' => $align,
				),
				''
			),
			'align' => $align,
		);

		return Table_Wrapper_Helper::render_table_wrapper( $coupon_html, $table_attrs, $cell_attrs );
	}

	/**
	 * Get alignment from parsed block attributes.
	 *
	 * @param array $parsed_block Parsed block data.
	 * @return string
	 */
	private function get_alignment( array $parsed_block ): string {
		$allowed = array( 'left', 'center', 'right' );
		$align   = $parsed_block['attrs']['align'] ?? 'center';

		if ( ! is_string( $align ) || ! in_array( $align, $allowed, true ) ) {
			return 'center';
		}

		return $align;
	}
}
PK     \w    &  BlockTypes/MiniCartCartButtonBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Admin\Features\Features;

/**
 * MiniCartCartButtonBlock class.
 */
class MiniCartCartButtonBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'mini-cart-cart-button-block';

	/**
	 * Render experimental iAPI block markup.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render_experimental_iapi_markup( $attributes, $content, $block ) {
		$default_view_cart_text = __( 'View my cart', 'woocommerce' );
		$view_cart_text         = $attributes['cartButtonLabel'] ? $attributes['cartButtonLabel'] : $default_view_cart_text;
		$cart_page_id           = wc_get_page_id( 'cart' );
		$cart_page_url          = get_permalink( $cart_page_id );
		$classes                = implode(
			' ',
			array_filter(
				array(
					'wc-block-components-button',
					'wp-element-button wc-block-mini-cart__footer-cart',
					// Default style class is not added by default, so it needs to be added manually if it doesn't exist.
					( ! isset( $attributes['className'] ) || strpos( $attributes['className'], 'is-style-' ) === false ) ? 'is-style-outline' : '',
				)
			)
		);
		$wrapper_attributes     = get_block_wrapper_attributes(
			array(
				'href'  => esc_url( $cart_page_url ),
				'class' => $classes,
			)
		);

		ob_start();

		?>
		<a <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<div class="wc-block-components-button__text">
				<?php echo esc_html( $view_cart_text ); ?>
			</div>
		</a>
		<?php
		return ob_get_clean();
	}

	/**
	 * Render the markup for the Mini-Cart Contents block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( Features::is_enabled( 'experimental-iapi-mini-cart' ) ) {
			return $this->render_experimental_iapi_markup( $attributes, $content, $block );
		}

		return $content;
	}
}
PK     \    !  BlockTypes/AbstractInnerBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * AbstractInnerBlock class.
 */
abstract class AbstractInnerBlock extends AbstractBlock {

	/**
	 * Is this inner block lazy loaded? this helps us know if we should load its frontend script ot not.
	 *
	 * @var boolean
	 */
	protected $is_lazy_loaded = true;

	/**
	 * Registers the block type with WordPress using the metadata file.
	 *
	 * The registration using metadata is now recommended. And it's required for "Inner Blocks" to
	 * fix the issue of missing translations in the inspector (in the Editor mode)
	 */
	protected function register_block_type() {
		$block_settings = [
			'render_callback' => $this->get_block_type_render_callback(),
			'editor_style'    => $this->get_block_type_editor_style(),
			'style'           => $this->get_block_type_style(),
		];

		if ( isset( $this->api_version ) ) {
			$block_settings['api_version'] = intval( $this->api_version );
		}

		$metadata_path = $this->asset_api->get_block_metadata_path( $this->block_name, 'inner-blocks/' );
		// Prefer to register with metadata if the path is set in the block's class.
		register_block_type_from_metadata(
			$metadata_path,
			$block_settings
		);
	}

	/**
	 * For lazy loaded inner blocks, we don't want to enqueue the script but rather leave it for webpack to do that.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {

		if ( $this->is_lazy_loaded ) {
			return null;
		}

		return parent::get_block_type_script( $key );
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}
}
PK     \~    '  BlockTypes/CartOrderSummaryFeeBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartOrderSummaryFeeBlock class.
 */
class CartOrderSummaryFeeBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-order-summary-fee-block';
}
PK     \H&O]      BlockTypes/ProductGallery.phpnu [        <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Enums\ProductType;

/**
 * ProductGallery class.
 */
class ProductGallery extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-gallery';

	/**
	 *  Register the context
	 *
	 * @return string[]
	 */
	protected function get_block_type_uses_context() {
		return [ 'postId' ];
	}

	/**
	 * Return the dialog content.
	 *
	 * @param array $images An array of all images of the product.
	 * @return string
	 */
	protected function render_dialog( $images ) {
		$images_html = '';
		foreach ( $images as $index => $image ) {
			$id           = $image['id'];
			$src          = $image['src'];
			$srcset       = $image['srcset'];
			$sizes        = $image['sizes'];
			$alt          = $image['alt'];
			$loading      = 0 === $index ? 'fetchpriority="high"' : 'loading="lazy"';
			$images_html .= "<img data-image-id='{$id}' src='{$src}' srcset='{$srcset}' sizes='{$sizes}' loading='{$loading}' decoding='async' alt='{$alt}' />";
		}
		ob_start();
		?>
			<dialog
				data-wp-bind--open="context.isDialogOpen"
				data-wp-bind--inert="!context.isDialogOpen"
				data-wp-on--close="actions.closeDialog"
				data-wp-on--keydown="actions.onDialogKeyDown"
				data-wp-watch="callbacks.dialogStateChange"
				class="wc-block-product-gallery-dialog"
				role="dialog"
				aria-modal="true"
				aria-label="Product Gallery">
				<div class="wc-block-product-gallery-dialog__header">
					<button class="wc-block-product-gallery-dialog__close-button" data-wp-on--click="actions.closeDialog" aria-label="<?php echo esc_attr__( 'Close dialog', 'woocommerce' ); ?>">
						<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false">
							<path d="M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z"></path>
						</svg>
					</button>
				</div>
				<div class="wc-block-product-gallery-dialog__content">
						<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is already escaped by WooCommerce. ?>
						<?php echo $images_html; ?>
				</div>
			</dialog>
		<?php
		return ob_get_clean();
	}

	/**
	 * Inject dialog into the product gallery HTML.
	 *
	 * @param string $gallery_html The gallery HTML.
	 * @param string $dialog_html  The dialog HTML.
	 *
	 * @return string
	 */
	protected function inject_dialog( $gallery_html, $dialog_html ) {

		// Find the position of the last </div>.
		$pos = strrpos( $gallery_html, '</div>' );

		if ( false !== $pos ) {
			// Inject the dialog_html at the correct position.
			$html = substr_replace( $gallery_html, $dialog_html, $pos, 0 );

			return $html;
		}

		return $gallery_html;
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$post_id = $block->context['postId'] ?? '';
		$product = wc_get_product( $post_id );

		if ( ! $product instanceof \WC_Product ) {
			return '';
		}

		$image_ids              = ProductGalleryUtils::get_all_image_ids( $product );
		$number_of_images       = count( $image_ids );
		$classname              = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );
		$initial_image_id       = $number_of_images > 0 ? $image_ids[0] : -1;
		$classname_single_image = $number_of_images < 2 ? 'is-single-product-gallery-image' : '';
		$product_id             = strval( $product->get_id() );
		$fullsize_image_data    = ProductGalleryUtils::get_image_src_data( $image_ids, 'full', $product->get_title() );
		$gallery_with_dialog    = $this->inject_dialog( $content, $this->render_dialog( $fullsize_image_data ) );
		$p                      = new \WP_HTML_Tag_Processor( $gallery_with_dialog );
		$html                   = $gallery_with_dialog;

		if ( $p->next_tag() ) {
			$p->set_attribute( 'data-wp-interactive', $this->get_full_block_name() );
			$p->set_attribute(
				'data-wp-context',
				wp_json_encode(
					array(
						'imageData'               => $image_ids,
						'isDialogOpen'            => false,
						'isDragging'              => false,
						'touchStartX'             => 0,
						'touchCurrentX'           => 0,
						'productId'               => $product_id,
						'selectedImageId'         => $initial_image_id,
						'thumbnailsOverflow'      => [
							'top'    => false,
							'bottom' => false,
							'left'   => false,
							'right'  => false,
						],
						// Next/Previous Buttons block context.
						'hideNextPreviousButtons' => $number_of_images <= 1,
						'isDisabledPrevious'      => true,
						'isDisabledNext'          => false,
						'ariaLabelPrevious'       => __( 'Previous image', 'woocommerce' ),
						'ariaLabelNext'           => __( 'Next image', 'woocommerce' ),
					),
					JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
				)
			);

			if ( $product->is_type( ProductType::VARIABLE ) ) {
				$variations_data           = $product->get_available_variations( 'objects' );
				$formatted_variations_data = array();
				$has_variation_images      = false;
				foreach ( $variations_data as $variation ) {
					$variation_image_id = (int) $variation->get_image_id();
					if ( $variation_image_id ) {
						$has_variation_images = true;

						$formatted_variations_data[ $variation->get_id() ] = array(
							'image_id' => $variation_image_id,
						);
					}
				}

				if ( $has_variation_images ) {
					wp_interactivity_config(
						'woocommerce',
						array(
							'products' => array(
								$product->get_id() => array(
									'image_id'   => (int) $product->get_image_id(),
									'variations' => $formatted_variations_data,
								),
							),
						)
					);

					// Support legacy Add to Cart with Options block.
					$p->set_attribute( 'data-wp-init--watch-changes-on-add-to-cart-form', 'callbacks.watchForChangesOnAddToCartForm' );
					// Support blockified Add to Cart + Options block.
					$p->set_attribute( 'data-wp-watch', 'callbacks.listenToProductDataChanges' );
				}
			}

			$p->add_class( $classname );
			$p->add_class( $classname_single_image );
			$html = $p->get_updated_html();
		}

		return $html;
	}
}
PK     \чn    *  BlockTypes/FilledMiniCartContentsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Blocks\Utils\BlocksSharedState;

/**
 * FilledMiniCartContentsBlock class.
 */
class FilledMiniCartContentsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'filled-mini-cart-contents-block';

	/**
	 * Render the markup for the Filled Mini-Cart Contents block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( Features::is_enabled( 'experimental-iapi-mini-cart' ) ) {
			return $this->render_experimental_filled_mini_cart_contents( $attributes, $content, $block );
		}

		return $content;
	}

	/**
	 * Render the experimental interactivity API powered Filled Mini-Cart Contents block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render_experimental_filled_mini_cart_contents( $attributes, $content, $block ) {
		$consent = 'I acknowledge that using private APIs means my theme or plugin will inevitably break in the next version of WooCommerce';
		$notices = BlocksSharedState::get_cart_error_notices( $consent );

		$context = wp_json_encode(
			array(
				'notices' => $notices,
			),
			JSON_NUMERIC_CHECK
				| JSON_HEX_TAG
				| JSON_HEX_APOS
				| JSON_HEX_QUOT
				| JSON_HEX_AMP
		);

		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'data-wp-interactive'  => 'woocommerce/mini-cart',
				'data-wp-context'      => 'woocommerce/store-notices::' . $context,
				'data-wp-bind--hidden' => 'state.cartIsEmpty',
			)
		);

		$dismiss_aria_label = __( 'Dismiss this notice', 'woocommerce' );

		ob_start();
		?>
		<div <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<div
				class="wc-block-components-notices"
				data-wp-interactive="woocommerce/store-notices"
			><template
					data-wp-each--notice="context.notices"
					data-wp-each-key="context.notice.id"
				>
					<div
						class="wc-block-components-notice-banner"
						data-wp-class--is-error="state.isError"
						data-wp-class--is-success="state.isSuccess"
						data-wp-class--is-info="state.isInfo"
						data-wp-class--is-dismissible="context.notice.dismissible"
						data-wp-bind--role="state.role"
						data-wp-watch="callbacks.injectIcon"
					>
						<div class="wc-block-components-notice-banner__content">
							<span data-wp-init="callbacks.renderNoticeContent"></span>
						</div>
						<button
							data-wp-bind--hidden="!context.notice.dismissible"
							class="wc-block-components-button wp-element-button wc-block-components-notice-banner__dismiss contained"
							aria-label="<?php echo esc_attr( $dismiss_aria_label ); ?>"
							data-wp-on--click="actions.removeNotice"
						>
							<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
								<path d="M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z" />
							</svg>
						</button>
					</div>
				</template>
			</div>
			<?php
				// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				echo $content;
			?>
		</div>
		<?php
		return ob_get_clean();
	}
}
PK     \=.    (  BlockTypes/ProductFilterCheckboxList.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * Product Filter: Checkbox List Block.
 */
final class ProductFilterCheckboxList extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-filter-checkbox-list';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( empty( $block->context['filterData'] ) ) {
			return '';
		}

		$block_context = $block->context['filterData'];
		$items         = $block_context['items'] ?? array();
		$show_counts   = $block_context['showCounts'] ?? false;
		$classes       = '';
		$style         = '';

		$tags = new \WP_HTML_Tag_Processor( $content );
		if ( $tags->next_tag( array( 'class_name' => 'wc-block-product-filter-checkbox-list' ) ) ) {
			$classes = $tags->get_attribute( 'class' );
			$style   = $tags->get_attribute( 'style' );
		}

		$checked_items               = array_filter(
			$items,
			function ( $item ) {
				return $item['selected'];
			}
		);
		$show_initially              = 15;
		$remaining_initial_unchecked = count( $checked_items ) > $show_initially ? count( $checked_items ) : $show_initially - count( $checked_items );
		$count                       = 0;

		$wrapper_attributes = array(
			'data-wp-interactive' => 'woocommerce/product-filters',
			'data-wp-key'         => wp_unique_prefixed_id( $this->get_full_block_name() ),
			'data-wp-context'     => '{}',
			'class'               => esc_attr( $classes ),
		);

		if ( ! empty( $style ) ) {
			// Styles generated by Supports API doesn't include semicolon at the end.
			$wrapper_attributes['style'] = esc_attr( $style ) . ';';
		}

		ob_start();
		?>
		<div <?php echo get_block_wrapper_attributes( $wrapper_attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<fieldset>
				<?php if ( ! empty( $block_context['groupLabel'] ) ) : ?>
					<legend class="screen-reader-text"><?php echo esc_html( $block_context['groupLabel'] ); ?></legend>
				<?php endif; ?>
				<div class="wc-block-product-filter-checkbox-list__items">
					<?php foreach ( $items as $item ) { ?>
						<?php $item_id = $item['type'] . '-' . $item['value']; ?>
						<div
							data-wp-key="<?php echo esc_attr( $item_id ); ?>"
							class="wc-block-product-filter-checkbox-list__item <?php echo isset( $item['depth'] ) ? esc_attr( 'has-depth-' . $item['depth'] ) : ''; ?>"
							<?php if ( ! $item['selected'] ) : ?>
								<?php if ( $count >= $remaining_initial_unchecked ) : ?>
									data-wp-bind--hidden="!context.showAll"
									hidden
								<?php else : ?>
									<?php ++$count; ?>
								<?php endif; ?>
							<?php endif; ?>
						>
							<label
								class="wc-block-product-filter-checkbox-list__label"
								for="<?php echo esc_attr( $item_id ); ?>"
							>
								<span class="wc-block-product-filter-checkbox-list__input-wrapper">
									<input
										id="<?php echo esc_attr( $item_id ); ?>"
										class="wc-block-product-filter-checkbox-list__input"
										type="checkbox"
										aria-label="<?php echo esc_attr( $this->get_aria_label( $item, $show_counts ) ); ?>"
										data-wp-on--change="actions.toggleFilter"
										value="<?php echo esc_attr( $item['value'] ); ?>"
										data-wp-bind--checked="state.isFilterSelected"
										<?php echo wp_interactivity_data_wp_context( array( 'item' => $item ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
									>
									<svg class="wc-block-product-filter-checkbox-list__mark" viewBox="0 0 10 8" fill="none" xmlns="http://www.w3.org/2000/svg">
										<path d="M9.25 1.19922L3.75 6.69922L1 3.94922" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
									</svg>
								</span>
								<span class="wc-block-product-filter-checkbox-list__text-wrapper">
									<span class="wc-block-product-filter-checkbox-list__text">
										<?php echo $item['label']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
									</span>
									<?php if ( $show_counts ) : ?>
										<span class="wc-block-product-filter-checkbox-list__count">
											(<?php echo esc_html( $item['count'] ); ?>)
										</span>
									<?php endif; ?>
								</span>
							</label>
						</div>
					<?php } ?>
				</div>
				<?php if ( count( $items ) > $show_initially ) : ?>
					<button
						class="wc-block-product-filter-checkbox-list__show-more"
						data-wp-bind--hidden="context.showAll"
						data-wp-on--click="actions.showAllListItems"
						hidden
					>
						<?php echo esc_html__( 'Show more…', 'woocommerce' ); ?>
					</button>
				<?php endif; ?>
			</fieldset>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Disable the style handle for this block type. We use block.json to load the style.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Get aria label for filter item.
	 *
	 * @param array $item Filter item.
	 * @param bool  $show_counts Whether to show counts.
	 *
	 * @return string Aria label.
	 */
	private function get_aria_label( $item, $show_counts ) {
		if ( $show_counts ) {
			return sprintf(
				/* translators: %1$s: Product filter name, %2$d: Number of products */
				_n(
					'%1$s (%2$d product)',
					'%1$s (%2$d products)',
					$item['count'],
					'woocommerce'
				),
				$item['ariaLabel'] ?? $item['label'],
				$item['count']
			);
		}

		return $item['ariaLabel'] ?? $item['label'];
	}
}
PK     \    !  BlockTypes/PaymentMethodIcons.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use WP_Block;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * PaymentMethodIcons class.
 */
class PaymentMethodIcons extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'payment-method-icons';

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 * @return array|string
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string[]
	 */
	protected function get_block_type_style() {
		return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );
		$this->asset_data_registry->add( 'availablePaymentMethods', $this->get_available_payment_methods() );
	}

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$payment_methods = $this->get_available_payment_methods();

		if ( empty( $payment_methods ) ) {
			return '';
		}

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class' => esc_attr( $classes_and_styles['classes'] ),
				'style' => esc_attr( $classes_and_styles['styles'] ),
			)
		);

		$output  = '<div ' . $wrapper_attributes . '>';
		$output .= '<div class="wc-block-payment-method-icons">';
		$output .= $this->render_payment_method_icons( $attributes );
		$output .= '</div>';
		$output .= '</div>';

		return $output;
	}

	/**
	 * Render payment method icons.
	 *
	 * @param array $attributes Block attributes.
	 * @return string Rendered block type output.
	 */
	private function render_payment_method_icons( $attributes ) {
		$output = '';

		$all_payment_methods = $this->get_available_payment_methods();
		$number_of_icons     = $attributes['numberOfIcons'] ?? 0;

		if ( 0 === $number_of_icons ) {
			$number_of_icons = count( $all_payment_methods );
		} else {
			$number_of_icons = max( 0, min( intval( $number_of_icons ), count( $all_payment_methods ) ) );
		}

		if ( ! empty( $all_payment_methods ) ) {
			for ( $i = 0; $i < $number_of_icons; $i++ ) {
				$payment_method = $all_payment_methods[ $i ];
				$output        .= '<div class="wc-block-payment-method-icons__item">';
				$output        .= '<span class="wc-block-payment-method-icons__icon" style="background-image: url(\'' . \esc_url( $payment_method['icon'] ) . '\');" role="img" aria-label="' . \esc_attr( $payment_method['name'] ) . '"></span>';
				$output        .= '</div>';
			}
		}

		return $output;
	}

	/**
	 * Check if WooPayments is enabled.
	 *
	 * @return bool WooPayments enabled.
	 */
	private function is_woopayments_enabled() {
		$payment_gateways = WC()->payment_gateways->get_available_payment_gateways();

		return isset( $payment_gateways['woocommerce_payments'] ) && 'yes' === $payment_gateways['woocommerce_payments']->enabled;
	}

	/**
	 * Get the enabled card types for WooPayments.
	 *
	 * Note: This uses hardcoded cards based on the default card types provided by WooPayments. This should be updated when these icons can be accessed via an API.
	 *
	 * @return array Enabled card types.
	 */
	private function get_enabled_card_types() {
		if ( ! $this->is_woopayments_enabled() ) {
			return array();
		}

		$card_types = array(
			'visa'       => array(
				'name' => 'Visa',
				'icon' => $this->get_card_type_icon_url( 'visa' ),
			),
			'mastercard' => array(
				'name' => 'Mastercard',
				'icon' => $this->get_card_type_icon_url( 'mastercard' ),
			),
			'amex'       => array(
				'name' => 'American Express',
				'icon' => $this->get_card_type_icon_url( 'amex' ),
			),
			'discover'   => array(
				'name' => 'Discover',
				'icon' => $this->get_card_type_icon_url( 'discover' ),
			),
			'jcb'        => array(
				'name' => 'JCB',
				'icon' => $this->get_card_type_icon_url( 'jcb' ),
			),
		);

		return $card_types;
	}

	/**
	 * Get the card type icon URL.
	 *
	 * @param string $card_type Card type.
	 * @return string Card type icon URL.
	 */
	private function get_card_type_icon_url( $card_type ) {
		$assets_path = 'assets/images/payment-methods-cards/';
		$icon_path   = WC_ABSPATH . $assets_path . $card_type . '.svg';
		$icon_url    = \plugins_url( $assets_path . $card_type . '.svg', WC_PLUGIN_FILE );

		return file_exists( $icon_path ) ? $icon_url : '';
	}

	/**
	 * Get other payment method icons from available gateways.
	 *
	 * @return array Other payment method icons.
	 */
	private function get_other_payment_method_icons() {
		$available_gateways    = WC()->payment_gateways->get_available_payment_gateways();
		$other_payment_methods = array();

		if ( empty( $available_gateways ) ) {
			return $other_payment_methods;
		}

		foreach ( $available_gateways as $gateway ) {
			if ( 'yes' === $gateway->enabled ) {
				if ( 'woocommerce_payments' === $gateway->id ) {
					continue;
				}

				$icon_url = '';
				if ( is_callable( array( $gateway, 'get_icon_url' ) ) ) {
					$icon_url = $gateway->get_icon_url();
				}
				if ( ! empty( $icon_url ) ) {
					$other_payment_methods[] = array(
						'name' => $gateway->get_title(),
						'icon' => $icon_url,
					);
				}
			}
		}

		usort(
			$other_payment_methods,
			function ( $a, $b ) {
				return strcmp( $a['name'], $b['name'] );
			}
		);

		return $other_payment_methods;
	}

	/**
	 * Get the available payment methods.
	 *
	 * @return array Available payment methods.
	 */
	private function get_available_payment_methods() {
		$enabled_cards   = array_values( $this->get_enabled_card_types() );
		$payment_methods = array_merge( $enabled_cards, $this->get_other_payment_method_icons() );
		return $payment_methods;
	}
}
PK     \        BlockTypes/ActiveFilters.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ActiveFilters class.
 */
class ActiveFilters extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'active-filters';
}
PK     \}3k>  k>    BlockTypes/AbstractBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use WP_Block;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
use Automattic\WooCommerce\Admin\Features\Features;

/**
 * AbstractBlock class.
 */
abstract class AbstractBlock {

	/**
	 * Block namespace.
	 *
	 * @var string
	 */
	protected $namespace = 'woocommerce';

	/**
	 * Block name within this namespace.
	 *
	 * @var string
	 */
	protected $block_name = '';

	/**
	 * Tracks if assets have been enqueued.
	 *
	 * @var boolean
	 */
	protected $enqueued_assets = false;

	/**
	 * Instance of the asset API.
	 *
	 * @var AssetApi
	 */
	protected $asset_api;

	/**
	 * Instance of the asset data registry.
	 *
	 * @var AssetDataRegistry
	 */
	protected $asset_data_registry;

	/**
	 * Instance of the integration registry.
	 *
	 * @var IntegrationRegistry
	 */
	protected $integration_registry;

	/**
	 * Constructor.
	 *
	 * @param AssetApi            $asset_api Instance of the asset API.
	 * @param AssetDataRegistry   $asset_data_registry Instance of the asset data registry.
	 * @param IntegrationRegistry $integration_registry Instance of the integration registry.
	 * @param string              $block_name Optionally set block name during construct.
	 */
	public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry, IntegrationRegistry $integration_registry, $block_name = '' ) {
		$this->asset_api            = $asset_api;
		$this->asset_data_registry  = $asset_data_registry;
		$this->integration_registry = $integration_registry;
		$this->block_name           = $block_name ? $block_name : $this->block_name;
		$this->initialize();
	}

	/**
	 * Get the interactivity namespace. Only used when utilizing the interactivity API.

	 * @return string The interactivity namespace, used to namespace interactivity API actions and state.
	 */
	protected function get_full_block_name() {
		return $this->namespace . '/' . $this->block_name;
	}

	/**
	 * The default render_callback for all blocks. This will ensure assets are enqueued just in time, then render
	 * the block (if applicable).
	 *
	 * @param array|WP_Block $attributes Block attributes, or an instance of a WP_Block. Defaults to an empty array.
	 * @param string         $content    Block content. Default empty string.
	 * @param WP_Block|null  $block      Block instance.
	 * @return string Rendered block type output.
	 */
	public function render_callback( $attributes = [], $content = '', $block = null ) {
		$render_callback_attributes = $this->parse_render_callback_attributes( $attributes );
		if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
			$this->register_block_type_assets();
			$this->enqueue_assets( $render_callback_attributes, $content, $block );
		}
		return $this->render( $render_callback_attributes, $content, $block );
	}

	/**
	 * Enqueue assets used for rendering the block in editor context.
	 *
	 * This is needed if a block is not yet within the post content--`render` and `enqueue_assets` may not have ran.
	 */
	public function enqueue_editor_assets() {
		if ( $this->enqueued_assets ) {
			return;
		}
		$this->register_block_type_assets();
		$this->enqueue_data();
	}

	/**
	 * Are we currently on the admin block editor screen?
	 */
	protected function is_block_editor() {
		if ( ! is_admin() || ! function_exists( 'get_current_screen' ) ) {
			return false;
		}
		$screen = get_current_screen();

		return $screen && $screen->is_block_editor();
	}

	/**
	 * Initialize this block type.
	 *
	 * - Hook into WP lifecycle.
	 * - Register the block with WordPress.
	 */
	protected function initialize() {
		if ( empty( $this->block_name ) ) {
			_doing_it_wrong( __METHOD__, esc_html__( 'Block name is required.', 'woocommerce' ), '4.5.0' );
			return false;
		}
		$this->integration_registry->initialize( $this->block_name . '_block' );
		$this->register_block_type();
		add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] );
	}

	/**
	 * Register script and style assets for the block type before it is registered.
	 *
	 * This registers the scripts; it does not enqueue them.
	 */
	protected function register_block_type_assets() {
		if ( null !== $this->get_block_type_editor_script() ) {
			$data     = $this->asset_api->get_script_data( $this->get_block_type_editor_script( 'path' ) );
			$has_i18n = in_array( 'wp-i18n', $data['dependencies'], true );

			$this->asset_api->register_script(
				$this->get_block_type_editor_script( 'handle' ),
				$this->get_block_type_editor_script( 'path' ),
				array_merge(
					$this->get_block_type_editor_script( 'dependencies' ),
					$this->integration_registry->get_all_registered_editor_script_handles()
				),
				$has_i18n
			);
		}
		if ( null !== $this->get_block_type_script() ) {
			$data     = $this->asset_api->get_script_data( $this->get_block_type_script( 'path' ) );
			$has_i18n = in_array( 'wp-i18n', $data['dependencies'], true );

			$this->asset_api->register_script(
				$this->get_block_type_script( 'handle' ),
				$this->get_block_type_script( 'path' ),
				array_merge(
					$this->get_block_type_script( 'dependencies' ),
					$this->integration_registry->get_all_registered_script_handles()
				),
				$has_i18n
			);
		}
	}

	/**
	 * Injects Chunk Translations into the page so translations work for lazy loaded components.
	 *
	 * The chunk names are defined when creating lazy loaded components using webpackChunkName.
	 *
	 * @param string[] $chunks Array of chunk names.
	 */
	protected function register_chunk_translations( $chunks ) {
		foreach ( $chunks as $chunk ) {
			$handle = 'wc-blocks-' . $chunk . '-chunk';
			$this->asset_api->register_script( $handle, $this->asset_api->get_block_asset_build_path( $chunk ), [], true );
			wp_add_inline_script(
				$this->get_block_type_script( 'handle' ),
				wp_scripts()->print_translations( $handle, false ),
				'before'
			);
			wp_deregister_script( $handle );
		}
	}

	/**
	 * Generate an array of chunks paths for loading translation.
	 *
	 * @param string $chunks_folder The folder to iterate over.
	 * @return string[] $chunks list of chunks to load.
	 */
	protected function get_chunks_paths( $chunks_folder ) {
		$build_path = \Automattic\WooCommerce\Blocks\Package::get_path() . 'assets/client/blocks/';
		$blocks     = [];
		if ( ! is_dir( $build_path . $chunks_folder ) ) {
			return [];
		}
		foreach ( new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $build_path . $chunks_folder, \FilesystemIterator::UNIX_PATHS ) ) as $block_name ) {
			$blocks[] = str_replace( $build_path, '', $block_name );
		}

		$chunks = preg_filter( '/.js/', '', $blocks );
		return $chunks;
	}
	/**
	 * Registers the block type with WordPress.
	 *
	 * @return string[] Chunks paths.
	 */
	protected function register_block_type() {
		$block_settings = [
			'render_callback' => $this->get_block_type_render_callback(),
			'editor_script'   => $this->get_block_type_editor_script( 'handle' ),
		];

		// Conditionally override these, otherwise rely on block.json metadata.
		if ( $this->get_block_type_style() ) {
			$block_settings['style'] = $this->get_block_type_style();
		}

		if ( $this->get_block_type_editor_style() ) {
			$block_settings['editor_style'] = $this->get_block_type_editor_style();
		}

		if ( isset( $this->api_version ) ) {
			$block_settings['api_version'] = intval( $this->api_version );
		}

		$metadata_path = $this->asset_api->get_block_metadata_path( $this->block_name );

		// Prefer to register with metadata if the path is set in the block's class.
		if ( ! empty( $metadata_path ) ) {
			register_block_type_from_metadata(
				$metadata_path,
				$block_settings
			);
			return;
		}

		/*
		 * Insert attributes and supports if we're not registering the block using metadata.
		 * These are left unset until now and only added here because if they were set when registering with metadata,
		 * the attributes and supports from $block_settings would override the values from metadata.
		 */
		$block_settings['attributes']   = $this->get_block_type_attributes();
		$block_settings['supports']     = $this->get_block_type_supports();
		$block_settings['uses_context'] = $this->get_block_type_uses_context();

		register_block_type(
			$this->get_block_type(),
			$block_settings
		);
	}

	/**
	 * Get the block type.
	 *
	 * @return string
	 */
	protected function get_block_type() {
		return $this->namespace . '/' . $this->block_name;
	}

	/**
	 * Get the render callback for this block type.
	 *
	 * Dynamic blocks should return a callback, for example, `return [ $this, 'render' ];`
	 *
	 * @see $this->register_block_type()
	 * @return callable|null;
	 */
	protected function get_block_type_render_callback() {
		return [ $this, 'render_callback' ];
	}

	/**
	 * Get the editor script data for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string
	 */
	protected function get_block_type_editor_script( $key = null ) {
		$script = [
			'handle'       => 'wc-' . $this->block_name . '-block',
			'path'         => $this->asset_api->get_block_asset_build_path( $this->block_name ),
			'dependencies' => [ 'wc-blocks' ],
		];
		return $key ? $script[ $key ] : $script;
	}

	/**
	 * Get the editor style handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @return string|null
	 */
	protected function get_block_type_editor_style() {
		return 'wc-blocks-editor-style';
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		$script = [
			'handle'       => 'wc-' . $this->block_name . '-block-frontend',
			'path'         => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
			'dependencies' => [],
		];
		return $key ? $script[ $key ] : $script;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string[]|null
	 */
	protected function get_block_type_style() {
		$this->asset_api->register_style( 'wc-blocks-style-' . $this->block_name, $this->asset_api->get_block_asset_build_path( $this->block_name, 'css' ), [], 'all', true );

		return [ 'wc-blocks-style', 'wc-blocks-style-' . $this->block_name ];
	}

	/**
	 * Get the supports array for this block type.
	 *
	 * @see $this->register_block_type()
	 * @return string;
	 */
	protected function get_block_type_supports() {
		return [];
	}

	/**
	 * Get block attributes.
	 *
	 * @return array;
	 */
	protected function get_block_type_attributes() {
		return [];
	}

	/**
	 * Get block usesContext.
	 *
	 * @return array;
	 */
	protected function get_block_type_uses_context() {
		return [];
	}

	/**
	 * Parses block attributes from the render_callback.
	 *
	 * @param array|WP_Block $attributes Block attributes, or an instance of a WP_Block. Defaults to an empty array.
	 * @return array
	 */
	protected function parse_render_callback_attributes( $attributes ) {
		return is_a( $attributes, 'WP_Block' ) ? $attributes->attributes : $attributes;
	}

	/**
	 * Render the block. Extended by children.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		return $content;
	}

	/**
	 * Enqueue frontend assets for this block, just in time for rendering.
	 *
	 * @internal This prevents the block script being enqueued on all pages. It is only enqueued as needed. Note that
	 * we intentionally do not pass 'script' to register_block_type.
	 *
	 * @param array    $attributes  Any attributes that currently are available from the block.
	 * @param string   $content    The block content.
	 * @param WP_Block $block    The block object.
	 */
	protected function enqueue_assets( array $attributes, $content, $block ) {
		if ( $this->enqueued_assets ) {
			return;
		}
		$this->enqueue_data( $attributes );
		$this->enqueue_scripts( $attributes );
		$this->enqueued_assets = true;
	}

	/**
	 * Data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		$registered_script_data = $this->integration_registry->get_all_registered_script_data();

		foreach ( $registered_script_data as $asset_data_key => $asset_data_value ) {
			if ( ! $this->asset_data_registry->exists( $asset_data_key ) ) {
				$this->asset_data_registry->add( $asset_data_key, $asset_data_value );
			}
		}

		if ( ! $this->asset_data_registry->exists( 'wcBlocksConfig' ) ) {
			$wc_blocks_config = [
				'pluginUrl'     => plugins_url( '/', dirname( __DIR__, 2 ) ),
				'restApiRoutes' => [
					'/wc/store/v1' => array_keys( $this->get_routes_from_namespace( 'wc/store/v1' ) ),
				],
				'defaultAvatar' => get_avatar_url( 0, [ 'force_default' => true ] ),

				/*
				 * translators: If your word count is based on single characters (e.g. East Asian characters),
				 * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'.
				 * Do not translate into your own language.
				 */
				'wordCountType' => _x( 'words', 'Word count type. Do not translate!', 'woocommerce' ),
			];
			if ( is_admin() && ! WC()->is_rest_api_request() ) {
				$product_counts     = wp_count_posts( 'product' );
				$published_products = isset( $product_counts->publish ) ? $product_counts->publish : 0;
				$wc_blocks_config   = array_merge(
					$wc_blocks_config,
					[
						// Note that while we don't have a consolidated way of doing feature-flagging
						// we are borrowing from the WC Admin Features implementation. Also note we cannot
						// use the wcAdminFeatures global because it's not always enqueued in the context of blocks.
						'experimentalBlocksEnabled' => Features::is_enabled( 'experimental-blocks' ),
						'productCount'              => $published_products,
					]
				);
			}
			$this->asset_data_registry->add(
				'wcBlocksConfig',
				$wc_blocks_config
			);
		}
	}

	/**
	 * Get routes from a REST API namespace.
	 *
	 * @param string $namespace Namespace to retrieve.
	 * @return array
	 */
	protected function get_routes_from_namespace( $namespace ) {
		/**
		 * Gives opportunity to return routes without invoking the compute intensive REST API.
		 *
		 * @since 8.7.0
		 * @param array  $routes    Array of routes.
		 * @param string $namespace Namespace for routes.
		 * @param string $context   Context, can be edit or view.
		 */
		$routes = apply_filters(
			'woocommerce_blocks_pre_get_routes_from_namespace',
			array(),
			$namespace,
			'view'
		);

		if ( ! empty( $routes ) ) {
			return $routes;
		}

		$rest_server     = rest_get_server();
		$namespace_index = $rest_server->get_namespace_index(
			[
				'namespace' => $namespace,
				'context'   => 'view',
			]
		);

		if ( is_wp_error( $namespace_index ) ) {
			return [];
		}

		$response_data = $namespace_index->get_data();

		return $response_data['routes'] ?? [];
	}

	/**
	 * Register/enqueue scripts used for this block on the frontend, during render.
	 *
	 * @param array $attributes Any attributes that currently are available from the block.
	 */
	protected function enqueue_scripts( array $attributes = [] ) {
		if ( null !== $this->get_block_type_script() ) {
			wp_enqueue_script( $this->get_block_type_script( 'handle' ) );
		}
	}
}
PK     \6  6  "  BlockTypes/ProductImageGallery.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * ProductImageGallery class.
 */
class ProductImageGallery extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-image-gallery';

	/**
	 * It isn't necessary register block assets because it is a server side block.
	 */
	protected function register_block_type_assets() {
		return null;
	}

	/**
	 *  Register the context
	 *
	 * @return string[]
	 */
	protected function get_block_type_uses_context() {
		return array( 'query', 'queryId', 'postId' );
	}

	/**
	 * Enqueue assets specific to this block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 */
	protected function enqueue_assets( $attributes, $content, $block ) {
		parent::enqueue_assets( $attributes, $content, $block );

		add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_legacy_assets' ], 20 );
	}

	/**
	 * Enqueue legacy assets when this block is used as we don't enqueue them for block themes anymore.
	 *
	 * Note: This enqueue logic is intentionally duplicated in ClassicTemplate.php
	 * to keep legacy blocks independent and allow for separate deprecation paths.
	 *
	 * @see https://github.com/woocommerce/woocommerce/pull/60223
	 */
	public function enqueue_legacy_assets() {
		// Legacy script dependencies for backward compatibility.
		$need_single_product_script = false;

		if ( current_theme_supports( 'wc-product-gallery-zoom' ) ) {
			$need_single_product_script = true;
			wp_enqueue_script( 'wc-zoom' );
		}

		if ( current_theme_supports( 'wc-product-gallery-slider' ) ) {
			$need_single_product_script = true;
			wp_enqueue_script( 'wc-flexslider' );
		}

		if ( current_theme_supports( 'wc-product-gallery-lightbox' ) ) {
			$need_single_product_script = true;
			wp_enqueue_script( 'wc-photoswipe-ui-default' );
			wp_enqueue_style( 'photoswipe-default-skin' );
			add_action(
				'wp_footer',
				function () {
					wc_get_template( 'single-product/photoswipe.php' );
				}
			);
		}

		if ( $need_single_product_script ) {
			wp_enqueue_script( 'wc-single-product' );
		}
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$post_id = $block->context['postId'];

		if ( ! isset( $post_id ) ) {
			return '';
		}

		global $product;

		$previous_product = $product;
		$product          = wc_get_product( $post_id );
		if ( ! $product instanceof \WC_Product ) {
			$product = $previous_product;

			return '';
		}

		add_filter( 'woocommerce_single_product_zoom_enabled', '__return_true' );
		add_filter( 'woocommerce_single_product_photoswipe_enabled', '__return_true' );
		add_filter( 'woocommerce_single_product_flexslider_enabled', '__return_true' );

		ob_start();
		woocommerce_show_product_sale_flash();
		$sale_badge_html = ob_get_clean();

		ob_start();
		woocommerce_show_product_images();
		$product_image_gallery_html = ob_get_clean();

		$product   = $previous_product;
		$classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );
		return sprintf(
			'<div class="wp-block-woocommerce-product-image-gallery %1$s">%2$s %3$s</div>',
			esc_attr( $classname ),
			$sale_badge_html,
			$product_image_gallery_html
		);
	}
}
PK     \1dH1(  (    BlockTypes/ProductTemplate.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection\Utils as ProductCollectionUtils;
use WP_Block;

/**
 * ProductTemplate class.
 */
class ProductTemplate extends AbstractBlock {
	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-template';

	/**
	 * Initialize this block type.
	 *
	 * - Hook into WP lifecycle.
	 * - Register the block with WordPress.
	 * - Hook into pre_render_block to update the query.
	 */
	protected function initialize() {
		add_filter( 'block_type_metadata_settings', array( $this, 'add_block_type_metadata_settings' ), 10, 2 );
		parent::initialize();
	}

	/**
	 * 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;
	}

	/**
	 * 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 ) {
		$query = ProductCollectionUtils::prepare_and_execute_query( $block );

		if ( ! $query->have_posts() ) {
			return '';
		}

		if ( $this->block_core_post_template_uses_featured_image( $block->inner_blocks ) ) {
			update_post_thumbnail_cache( $query );
		}

		$classnames = '';
		if ( isset( $block->context['displayLayout'] ) && isset( $block->context['query'] ) ) {
			$classnames = 'is-product-collection-layout-' . $block->context['displayLayout']['type'] . ' ';

			if ( isset( $block->context['displayLayout']['type'] ) && 'flex' === $block->context['displayLayout']['type'] ) {
				if ( isset( $block->context['displayLayout']['shrinkColumns'] ) && $block->context['displayLayout']['shrinkColumns'] ) {
					$classnames = "wc-block-product-template__responsive columns-{$block->context['displayLayout']['columns']}";
				} else {
					$classnames = "is-flex-container columns-{$block->context['displayLayout']['columns']}";
				}
			}
		}

		if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) {
			$classnames .= ' has-link-color';
		}

		$classnames .= ' wc-block-product-template';

		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'class'              => trim( $classnames ),
				'data-wp-on--scroll' => 'actions.watchScroll',
				'data-wp-init'       => 'callbacks.initResizeObserver',
			)
		);

		$content = '';
		while ( $query->have_posts() ) {
			$query->the_post();

			// Get an instance of the current Post Template block.
			$block_instance = $block->parsed_block;
			$product_id     = get_the_ID();

			// Set the block name to one that does not correspond to an existing registered block.
			// This ensures that for the inner instances of the Post Template block, we do not render any block supports.
			$block_instance['blockName'] = 'core/null';

			// Relay the block context to the inner blocks.
			$available_context = array_merge(
				(array) $block->context,
				array(
					'postType' => get_post_type(),
					'postId'   => $product_id,
				)
			);

			// Render the inner blocks of the Post Template block with `dynamic` set to `false` to prevent calling
			// `render_callback` and ensure that no wrapper markup is included.
			$block_content = (
				new WP_Block(
					$block_instance,
					$available_context
				)
			)->render( array( 'dynamic' => false ) );

			$context = array(
				'productId' => $product_id,
			);

			$li_directives = '
				data-wp-interactive="woocommerce/product-collection"
				data-wp-context=\'' . wp_json_encode( $context, JSON_NUMERIC_CHECK | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) . '\'
				data-wp-key="product-item-' . $product_id . '"
			';

			// Wrap the render inner blocks in a `li` element with the appropriate post classes.
			$post_classes = implode( ' ', get_post_class( 'wc-block-product' ) );
			$content     .= strtr(
				'<li class="{classes}"
					{li_directives}
				>
					{content}
				</li>',
				array(
					'{classes}'       => esc_attr( $post_classes ),
					'{li_directives}' => $li_directives,
					'{content}'       => $block_content,
				)
			);
		}

		/*
		* Use this function to restore the context of the template tags
		* from a secondary query loop back to the main query loop.
		* Since we use two custom loops, it's safest to always restore.
		*/
		wp_reset_postdata();

		return sprintf(
			'<ul %1$s>%2$s</ul>',
			$wrapper_attributes,
			$content
		);
	}

	/**
	 * Determines whether a block list contains a block that uses the featured image.
	 *
	 * @param WP_Block_List $inner_blocks Inner block instance.
	 *
	 * @return bool Whether the block list contains a block that uses the featured image.
	 */
	protected function block_core_post_template_uses_featured_image( $inner_blocks ) {
		foreach ( $inner_blocks as $block ) {
			if ( 'core/post-featured-image' === $block->name ) {
				return true;
			}
			if (
			'core/cover' === $block->name &&
			! empty( $block->attributes['useFeaturedImage'] )
			) {
				return true;
			}
			if ( $block->inner_blocks && block_core_post_template_uses_featured_image( $block->inner_blocks ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Product Template renders inner blocks manually so we need to skip default
	 * rendering routine for its inner blocks
	 *
	 * @param array $settings Array of determined settings for registering a block type.
	 * @param array $metadata Metadata provided for registering a block type.
	 * @return array
	 */
	public function add_block_type_metadata_settings( $settings, $metadata ) {
		if ( ! empty( $metadata['name'] ) && 'woocommerce/product-template' === $metadata['name'] ) {
			$settings['skip_inner_blocks'] = true;
		}
		return $settings;
	}
}
PK     \v    #  BlockTypes/CheckoutActionsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutActionsBlock class.
 */
class CheckoutActionsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-actions-block';

	/**
	 * 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_style_variations' ) );
	}

	/**
	 * Register style variations for the block.
	 */
	public function register_style_variations() {
		register_block_style(
			$this->get_full_block_name(),
			array(
				'name'       => 'without-price',
				'label'      => __( 'Hide Price', 'woocommerce' ),
				'is_default' => true,
			)
		);

		register_block_style(
			$this->get_full_block_name(),
			array(
				'name'       => 'with-price',
				'label'      => __( 'Show Price', 'woocommerce' ),
				'is_default' => false,
			)
		);
	}
}
PK     \+F      BlockTypes/ProductNew.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ProductNew class.
 */
class ProductNew extends AbstractProductGrid {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-new';

	/**
	 * Set args specific to this block
	 *
	 * @param array $query_args Query args.
	 */
	protected function set_block_query_args( &$query_args ) {
		$query_args['orderby'] = 'date';
		$query_args['order']   = 'DESC';
	}
}
PK     \I    "  BlockTypes/MiniCartFooterBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Admin\Features\Features;

/**
 * MiniCartFooterBlock class.
 */
class MiniCartFooterBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'mini-cart-footer-block';

	/**
	 * Data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = array() ) {
		parent::enqueue_data( $attributes );
		$description = $this->get_totals_item_description();
		if ( ! empty( $description ) ) {
			$this->asset_data_registry->add( 'miniCartFooterDescription', $description );
		}
	}

	/**
	 * Render experimental iAPI powered Mini-Cart Footer block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render_experimental_iapi_mini_cart_footer( $attributes, $content, $block ) {
		ob_start();

		$cart                             = $this->get_cart_instance();
		$subtotal_label                   = __( 'Subtotal', 'woocommerce' );
		$other_costs_label                = $this->get_totals_item_description();
		$display_cart_price_including_tax = get_option( 'woocommerce_tax_display_cart' ) === 'incl';
		$subtotal                         = $display_cart_price_including_tax ? $cart->get_subtotal_tax() : $cart->get_subtotal();
		$formatted_subtotal               = '';
		$html                             = new \WP_HTML_Tag_Processor( wc_price( $subtotal ) );
		$wrapper_attributes               = get_block_wrapper_attributes(
			array(
				'data-wp-interactive' => 'woocommerce/mini-cart-footer-block',
				'class'               => 'wc-block-mini-cart__footer',
			)
		);

		if ( $html->next_tag( 'bdi' ) ) {
			while ( $html->next_token() ) {
				if ( '#text' === $html->get_token_name() ) {
						$formatted_subtotal .= $html->get_modifiable_text();
				}
			}
		}

		wp_interactivity_state(
			$this->get_full_block_name(),
			array(
				'formattedSubtotal' => $formatted_subtotal,
			)
		);

		?>
		<div <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<div class="wc-block-components-totals-item wc-block-mini-cart__footer-subtotal">
				<span class="wc-block-components-totals-item__label">
					<?php echo esc_html( $subtotal_label ); ?>
				</span>
				<span data-wp-text="woocommerce/mini-cart::state.formattedSubtotal" class="wc-block-formatted-money-amount wc-block-components-formatted-money-amount wc-block-components-totals-item__value">
				</span>
				<div class="wc-block-components-totals-item__description">
					<?php echo esc_html( $other_costs_label ); ?>
				</div>
			</div>
			<div class="wc-block-mini-cart__footer-actions">
				<?php
				if ( empty( $content ) ) {
					// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
					echo do_blocks( '<!-- wp:woocommerce/mini-cart-cart-button-block /--><!-- wp:woocommerce/mini-cart-checkout-button-block /-->' );
				} else {
					// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
					echo $content;
				}
				?>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Return the main instance of WC_Cart class.
	 *
	 * @return \WC_Cart CartController class instance.
	 */
	protected function get_cart_instance() {
		$cart = WC()->cart;

		if ( $cart && $cart instanceof \WC_Cart ) {
			return $cart;
		}

		return null;
	}

	/**
	 * Computes the total items description text based on which settings are enabled.
	 *
	 * @return string The description text for the total items, or empty string if none are enabled.
	 */
	private function get_totals_item_description() {
		$taxes_enabled    = wc_tax_enabled();
		$shipping_enabled = wc_shipping_enabled();
		$coupons_enabled  = wc_coupons_enabled();

		// All three enabled.
		if ( $taxes_enabled && $shipping_enabled && $coupons_enabled ) {
			return __(
				'Shipping, taxes, and discounts calculated at checkout.',
				'woocommerce'
			);
		}

		// Shipping + taxes.
		if ( $shipping_enabled && $taxes_enabled ) {
			return __(
				'Shipping and taxes calculated at checkout.',
				'woocommerce'
			);
		}

		// Shipping + discounts.
		if ( $shipping_enabled && $coupons_enabled ) {
			return __(
				'Shipping and discounts calculated at checkout.',
				'woocommerce'
			);
		}

		// Taxes + discounts.
		if ( $taxes_enabled && $coupons_enabled ) {
			return __(
				'Taxes and discounts calculated at checkout.',
				'woocommerce'
			);
		}

		// Only shipping.
		if ( $shipping_enabled ) {
			return __( 'Shipping calculated at checkout.', 'woocommerce' );
		}

		// Only taxes.
		if ( $taxes_enabled ) {
			return __( 'Taxes calculated at checkout.', 'woocommerce' );
		}

		// Only discounts.
		if ( $coupons_enabled ) {
			return __( 'Discounts calculated at checkout.', 'woocommerce' );
		}

		// None enabled.
		return '';
	}

	/**
	 * Render the markup for the Mini-Cart Contents block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( Features::is_enabled( 'experimental-iapi-mini-cart' ) ) {
			return $this->render_experimental_iapi_mini_cart_footer( $attributes, $content, $block );
		}

		return $content;
	}
}
PK     \w
    *  BlockTypes/CheckoutExpressPaymentBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
use Exception;

/**
 * CheckoutExpressPaymentBlock class.
 */
class CheckoutExpressPaymentBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-express-payment-block';

	/**
	 * Default styles for the express payment buttons
	 *
	 * @var boolean
	 */
	protected $default_styles = null;

	/**
	 * Current styles for the express payment buttons
	 *
	 * @var boolean
	 */
	protected $current_styles = null;

	/**
	 * Initialise the block
	 */
	protected function initialize() {
		parent::initialize();

		$this->default_styles = array(
			'showButtonStyles'   => false,
			'buttonHeight'       => '48',
			'buttonBorderRadius' => '4',
		);

		add_action( 'save_post', array( $this, 'sync_express_payment_attrs' ), 10, 2 );
	}

	/**
	 * Synchorize the express payment attributes between the Cart and Checkout pages.
	 *
	 * @param int     $post_id Post ID.
	 * @param WP_Post $post Post object.
	 */
	public function sync_express_payment_attrs( $post_id, $post ) {
		if ( wc_get_page_id( 'cart' ) === $post_id ) {
			$cart_or_checkout = 'cart';
		} elseif ( wc_get_page_id( 'checkout' ) === $post_id ) {
			$cart_or_checkout = 'checkout';
		} else {
			return;
		}

		// This is not a proper save action, maybe an autosave, so don't continue.
		if ( empty( $post->post_status ) || 'inherit' === $post->post_status ) {
			return;
		}

		$block_name    = 'woocommerce/' . $cart_or_checkout;
		$page_id       = 'woocommerce_' . $cart_or_checkout . '_page_id';
		$template_name = 'page-' . $cart_or_checkout;

		// Check if we are editing the cart/checkout page and that it contains a Cart/Checkout block.
		// Cast to string for Cart/Checkout page ID comparison because get_option can return it as a string, so better to compare both values as strings.
		if ( ! empty( $post->post_type ) && 'wp_template' !== $post->post_type && ( false === has_block( $block_name, $post ) || (string) get_option( $page_id ) !== (string) $post_id ) ) {
			return;
		}

		// Check if we are editing the Cart/Checkout template and that it contains a Cart/Checkout block.
		if ( ( ! empty( $post->post_type ) && ! empty( $post->post_name ) && $template_name !== $post->post_name && 'wp_template' === $post->post_type ) || false === has_block( $block_name, $post ) ) {
			return;
		}

		if ( empty( $post->post_content ) ) {
			return;
		}

		try {
			// Parse the post content to get the express payment attributes of the current page.
			$attrs = CartCheckoutUtils::find_express_checkout_attributes( $post->post_content, $cart_or_checkout );

			if ( ! is_array( $attrs ) ) {
				return;
			}
			$updated_attrs = array_merge( $this->default_styles, $attrs );

			// We need to sync the attributes between the Cart and Checkout pages.
			$other_page = 'cart' === $cart_or_checkout ? 'checkout' : 'cart';

			$this->update_other_page_with_express_payment_attrs( $other_page, $updated_attrs );
		} catch ( Exception $e ) {
			wc_get_logger()->log( 'error', 'Error updating express payment attributes: ' . $e->getMessage() );
		}
	}

	/**
	 * Update the express payment attributes in the other page (Cart or Checkout).
	 *
	 * @param string $cart_or_checkout The page to update.
	 * @param array  $updated_attrs     The updated attributes.
	 */
	private function update_other_page_with_express_payment_attrs( $cart_or_checkout, $updated_attrs ) {
		$page_id = 'cart' === $cart_or_checkout ? wc_get_page_id( 'cart' ) : wc_get_page_id( 'checkout' );

		if ( -1 === $page_id ) {
			return;
		}

		$post = get_post( $page_id );

		if ( empty( $post->post_content ) ) {
			return;
		}

		$blocks = parse_blocks( $post->post_content );
		CartCheckoutUtils::update_blocks_with_new_attrs( $blocks, $cart_or_checkout, $updated_attrs );

		$updated_content = serialize_blocks( $blocks );
		remove_action( 'save_post', array( $this, 'sync_express_payment_attrs' ), 10, 2 );

		wp_update_post(
			array(
				'ID'           => $page_id,
				'post_content' => $updated_content,
			),
			false,
			false
		);

		add_action( 'save_post', array( $this, 'sync_express_payment_attrs' ), 10, 2 );
	}
}
PK     \&L    &  BlockTypes/CartExpressPaymentBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartExpressPaymentBlock class.
 */
class CartExpressPaymentBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-express-payment-block';

	/**
	 * Uniform default_styles for the express payment buttons
	 *
	 * @var boolean
	 */
	protected $default_styles = null;

	/**
	 * Current styles for the express payment buttons
	 *
	 * @var boolean
	 */
	protected $current_styles = null;
}
PK     \E24  4    BlockTypes/CartLink.phpnu [        <?php // phpcs:ignore Generic.PHP.RequireStrictTypes.MissingDeclaration

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Blocks\Utils\MiniCartUtils;

/**
 * CartLink class.
 */
class CartLink extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-link';

	/**
	 * 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 ) {
		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
		$icon               = MiniCartUtils::get_svg_icon( $attributes['cartIcon'] ?? '' );
		$text               = array_key_exists( 'content', $attributes ) ? esc_html( $attributes['content'] ) : esc_html__( 'Cart', 'woocommerce' );

		return sprintf(
			'<div %1$s><a class="wc-block-cart-link" href="%2$s">%3$s<span class="wc-block-cart-link__text">%4$s</span></a></div>',
			get_block_wrapper_attributes(
				array(
					'class' => esc_attr( $classes_and_styles['classes'] ),
					'style' => $classes_and_styles['styles'],
				)
			),
			esc_url( wc_get_cart_url() ),
			$icon,
			$text
		);
	}

	/**
	 * 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;
	}
}
PK     \{2  {2    BlockTypes/ClassicTemplate.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductCatalogTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductCategoryTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductTagTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
use Automattic\WooCommerce\Blocks\Templates\OrderConfirmationTemplate;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use WC_Shortcode_Checkout;
use WC_Frontend_Scripts;

/**
 * Classic Template class
 *
 * @internal
 */
class ClassicTemplate extends AbstractDynamicBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'legacy-template';

	/**
	 * API version.
	 *
	 * @var string
	 */
	protected $api_version = '3';

	/**
	 * Initialize this block.
	 */
	protected function initialize() {
		parent::initialize();
		add_filter( 'render_block', array( $this, 'add_alignment_class_to_wrapper' ), 10, 2 );
		add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = array() ) {
		parent::enqueue_data( $attributes );

		// Disable client-side navigation so that interactivity powered
		// components fall back to full page reload.
		wp_interactivity_config(
			'core/router',
			array(
				'clientNavigationDisabled' => true,
			)
		);
	}

	/**
	 * Enqueue assets used for rendering the block in editor context.
	 *
	 * This is needed if a block is not yet within the post content--`render` and `enqueue_assets` may not have ran.
	 */
	public function enqueue_block_assets() {
		// Ensures frontend styles for blocks exist in the site editor iframe.
		if ( class_exists( 'WC_Frontend_Scripts' ) && is_admin() ) {
			$frontend_scripts = new WC_Frontend_Scripts();
			$styles           = $frontend_scripts::get_styles();

			foreach ( $styles as $handle => $style ) {
				wp_enqueue_style(
					$handle,
					set_url_scheme( $style['src'] ),
					$style['deps'],
					$style['version'],
					$style['media']
				);
			}
		}
	}

	/**
	 * Enqueue assets specific to this block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 */
	protected function enqueue_assets( $attributes, $content, $block ) {
		parent::enqueue_assets( $attributes, $content, $block );

		if ( is_product() ) {
			add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_legacy_assets' ], 20 );
		}
	}

	/**
	 * Enqueue legacy assets when this block is used as we don't enqueue them for block themes anymore.
	 *
	 * Note: This enqueue logic is intentionally duplicated in ProductImageGallery.php
	 * to keep legacy blocks independent and allow for separate deprecation paths.
	 *
	 * @see https://github.com/woocommerce/woocommerce/pull/60223
	 */
	public function enqueue_legacy_assets() {
		// Legacy script dependencies for backward compatibility.
		if ( current_theme_supports( 'wc-product-gallery-zoom' ) ) {
			wp_enqueue_script( 'wc-zoom' );
		}

		if ( current_theme_supports( 'wc-product-gallery-slider' ) ) {
			wp_enqueue_script( 'wc-flexslider' );
		}

		if ( current_theme_supports( 'wc-product-gallery-lightbox' ) ) {
			wp_enqueue_script( 'wc-photoswipe-ui-default' );
			wp_enqueue_style( 'photoswipe-default-skin' );
			add_action(
				'wp_footer',
				function () {
					wc_get_template( 'single-product/photoswipe.php' );
				}
			);
		}

		wp_enqueue_script( 'wc-single-product' );
	}


	/**
	 * Render method for the Classic Template block. This method will determine which template to render.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string | void Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! isset( $attributes['template'] ) ) {
			return;
		}

		/**
		 * We need to load the scripts here because when using block templates wp_head() gets run after the block
		 * template. As a result we are trying to enqueue required scripts before we have even registered them.
		 *
		 * @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5328#issuecomment-989013447
		 */
		if ( class_exists( 'WC_Frontend_Scripts' ) ) {
			$frontend_scripts = new WC_Frontend_Scripts();
			$frontend_scripts::load_scripts();
		}

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

		if ( is_product() ) {
			add_filter( 'woocommerce_single_product_zoom_enabled', '__return_true' );
			add_filter( 'woocommerce_single_product_photoswipe_enabled', '__return_true' );
			add_filter( 'woocommerce_single_product_flexslider_enabled', '__return_true' );

			return $this->render_single_product();
		}

		$valid             = false;
		$archive_templates = array(
			ProductCatalogTemplate::SLUG,
			ProductCategoryTemplate::SLUG,
			ProductTagTemplate::SLUG,
			ProductAttributeTemplate::SLUG,
			ProductSearchResultsTemplate::SLUG,
		);

		// Set selected template when we directly find template base slug.
		if ( in_array( $attributes['template'], $archive_templates, true ) ) {
			$valid = true;
		}

		// Set selected template when we find template base slug as prefix for a specific term.
		foreach ( $archive_templates as $template ) {
			if ( 0 === strpos( $attributes['template'], $template ) ) {
				$valid = true;
			}
		}

		if ( $valid ) {
			// Set this so that our product filters can detect if it's a PHP template.
			$this->asset_data_registry->add( 'isRenderingPhpTemplate', true );

			// Set this so filter blocks being used as widgets know when to render.
			$this->asset_data_registry->add( 'hasFilterableProducts', true );

			$this->asset_data_registry->add(
				'pageUrl',
				html_entity_decode( get_pagenum_link() )
			);

			return $this->render_archive_product();
		}

		ob_start();

		echo "You're using the ClassicTemplate block";

		wp_reset_postdata();

		return ob_get_clean();
	}

	/**
	 * Render method for rendering the order confirmation template.
	 *
	 * @return string Rendered block type output.
	 */
	protected function render_order_received() {
		ob_start();

		echo '<div class="wp-block-group">';

		printf(
			'<%1$s %2$s>%3$s</%1$s>',
			'h1',
			get_block_wrapper_attributes(), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			esc_html__( 'Order confirmation', 'woocommerce' )
		);

		WC_Shortcode_Checkout::output( array() );

		echo '</div>';

		return ob_get_clean();
	}

	/**
	 * Render method for the single product template and parts.
	 *
	 * @return string Rendered block type output.
	 */
	protected function render_single_product() {
		ob_start();

		/**
		 * Hook: woocommerce_before_main_content
		 *
		 * Called before rendering the main content for a product.
		 *
		 * @see woocommerce_output_content_wrapper() Outputs opening DIV for the content (priority 10)
		 * @see woocommerce_breadcrumb() Outputs breadcrumb trail to the current product (priority 20)
		 * @see WC_Structured_Data::generate_website_data() Outputs schema markup (priority 30)
		 *
		 * @since 6.3.0
		 */
		do_action( 'woocommerce_before_main_content' );

		$product_query = new \WP_Query(
			array(
				'post_type' => 'product',
				'p'         => get_the_ID(),
			)
		);

		while ( $product_query->have_posts() ) :

			$product_query->the_post();
			wc_get_template_part( 'content', 'single-product' );

		endwhile;

		/**
		 * Hook: woocommerce_after_main_content
		 *
		 * Called after rendering the main content for a product.
		 *
		 * @see woocommerce_output_content_wrapper_end() Outputs closing DIV for the content (priority 10)
		 *
		 * @since 6.3.0
		 */
		do_action( 'woocommerce_after_main_content' );

		wp_reset_postdata();

		return ob_get_clean();
	}

	/**
	 * Render method for the archive product template and parts.
	 *
	 * @return string Rendered block type output.
	 */
	protected function render_archive_product() {
		ob_start();

		/**
		 * Hook: woocommerce_before_main_content
		 *
		 * Called before rendering the main content for a product.
		 *
		 * @see woocommerce_output_content_wrapper() Outputs opening DIV for the content (priority 10)
		 * @see woocommerce_breadcrumb() Outputs breadcrumb trail to the current product (priority 20)
		 * @see WC_Structured_Data::generate_website_data() Outputs schema markup (priority 30)
		 *
		 * @since 6.3.0
		 */
		do_action( 'woocommerce_before_main_content' );

		?>
		<header class="woocommerce-products-header">
			<?php
			/**
			 * Hook: woocommerce_show_page_title
			 *
			 * Allows controlling the display of the page title.
			 *
			 * @since 6.3.0
			 */
			if ( apply_filters( 'woocommerce_show_page_title', true ) ) {
				?>
				<h1 class="woocommerce-products-header__title page-title">
					<?php
						woocommerce_page_title();
					?>
				</h1>
				<?php
			}
			/**
			 * Hook: woocommerce_archive_description.
			 *
			 * @see woocommerce_taxonomy_archive_description() Renders the taxonomy archive description (priority 10)
			 * @see woocommerce_product_archive_description() Renders the product archive description (priority 10)
			 *
			 * @since 6.3.0
			 */
			do_action( 'woocommerce_archive_description' );
			?>
		</header>
		<?php
		if ( woocommerce_product_loop() ) {

			/**
			 * Hook: woocommerce_before_shop_loop.
			 *
			 * @see woocommerce_output_all_notices() Render error notices (priority 10)
			 * @see woocommerce_result_count() Show number of results found (priority 20)
			 * @see woocommerce_catalog_ordering() Show form to control sort order (priority 30)
			 *
			 * @since 6.3.0
			 */
			do_action( 'woocommerce_before_shop_loop' );

			woocommerce_product_loop_start();

			if ( wc_get_loop_prop( 'total' ) ) {
				while ( have_posts() ) {
					the_post();

					/**
					 * Hook: woocommerce_shop_loop.
					 *
					 * @since 6.3.0
					 */
					do_action( 'woocommerce_shop_loop' );

					wc_get_template_part( 'content', 'product' );
				}
			}

			woocommerce_product_loop_end();

			/**
			 * Hook: woocommerce_after_shop_loop.
			 *
			 * @see woocommerce_pagination() Renders pagination (priority 10)
			 *
			 * @since 6.3.0
			 */
			do_action( 'woocommerce_after_shop_loop' );
		} else {
			/**
			 * Hook: woocommerce_no_products_found.
			 *
			 * @see wc_no_products_found() Default no products found content (priority 10)
			 *
			 * @since 6.3.0
			 */
			do_action( 'woocommerce_no_products_found' );
		}

		/**
		 * Hook: woocommerce_after_main_content
		 *
		 * Called after rendering the main content for a product.
		 *
		 * @see woocommerce_output_content_wrapper_end() Outputs closing DIV for the content (priority 10)
		 *
		 * @since 6.3.0
		 */
		do_action( 'woocommerce_after_main_content' );

		wp_reset_postdata();
		return ob_get_clean();
	}

	/**
	 * Get HTML markup with the right classes by attributes.
	 * This function appends the classname at the first element that have the class attribute.
	 * Based on the experience, all the wrapper elements have a class attribute.
	 *
	 * @param string $content Block content.
	 * @param array  $block Parsed block data.
	 * @return string Rendered block type output.
	 */
	public function add_alignment_class_to_wrapper( string $content, array $block ) {
		if ( ( 'woocommerce/' . $this->block_name ) !== $block['blockName'] ) {
			return $content;
		}

		$attributes = (array) $block['attrs'];

		// Set the default alignment to wide.
		if ( ! isset( $attributes['align'] ) ) {
			$attributes['align'] = 'wide';
		}

		$align_class_and_style = StyleAttributesUtils::get_align_class_and_style( $attributes );

		if ( ! isset( $align_class_and_style['class'] ) ) {
			return $content;
		}

		// Find the first tag.
		$first_tag = '<[^<>]+>';
		$matches   = array();
		preg_match( $first_tag, $content, $matches );

		// If there is a tag, but it doesn't have a class attribute, add the class attribute.
		if ( isset( $matches[0] ) && strpos( $matches[0], ' class=' ) === false ) {
			$pattern_before_tag_closing = '/.+?(?=>)/';
			return preg_replace( $pattern_before_tag_closing, '$0 class="' . $align_class_and_style['class'] . '"', $content, 1 );
		}

		// If there is a tag, and it has a class already, add the class attribute.
		$pattern_get_class = '/(?<=class=\"|\')[^"|\']+(?=\"|\')/';
		return preg_replace( $pattern_get_class, '$0 ' . $align_class_and_style['class'], $content, 1 );
	}
}
PK     \	  	  -  BlockTypes/MiniCartTitleItemsCounterBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Admin\Features\Features;

/**
 * MiniCartTitleItemsCounterBlock class.
 */
class MiniCartTitleItemsCounterBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'mini-cart-title-items-counter-block';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( Features::is_enabled( 'experimental-iapi-mini-cart' ) ) {
			return $this->render_experimental_iapi_title_label_block();
		}
		return $content;
	}

	/**
	 * Render the interactivity API powered experimental title block.
	 *
	 * @return string Rendered block type output.
	 */
	protected function render_experimental_iapi_title_label_block() {
		$cart            = $this->get_cart_instance();
		$cart_item_count = $cart ? $cart->get_cart_contents_count() : 0;

		// The following translation is a temporary workaround. It will be
		// reverted to the previous form `(%d items)` as soon as the
		// `@wordpress/i18n` package is available as a script module.

		// translators: %d number of items in the cart.
		$cart_item_text = __( '(items: %d)', 'woocommerce' );

		wp_interactivity_config(
			$this->get_full_block_name(),
			array(
				'itemsInCartTextTemplate' => $cart_item_text,
			)
		);

		wp_interactivity_state(
			$this->get_full_block_name(),
			array(
				'itemsInCartText' => sprintf( $cart_item_text, $cart_item_count ),
			)
		);

		$wrapper_attributes = get_block_wrapper_attributes(
			array(
				'data-wp-text'        => 'state.itemsInCartText',
				'data-wp-interactive' => 'woocommerce/mini-cart-title-items-counter-block',
			)
		);

		ob_start();
		?>
		<span <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
		</span>
		<?php
		return ob_get_clean();
	}

	/**
	 * Return the main instance of WC_Cart class.
	 *
	 * @return \WC_Cart CartController class instance.
	 */
	protected function get_cart_instance() {
		$cart = WC()->cart;

		if ( $cart && $cart instanceof \WC_Cart ) {
			return $cart;
		}

		return null;
	}
}
PK     \={        BlockTypes/AttributeFilter.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * AttributeFilter class.
 */
class AttributeFilter extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name             = 'attribute-filter';
	const FILTER_QUERY_VAR_PREFIX     = 'filter_';
	const QUERY_TYPE_QUERY_VAR_PREFIX = 'query_type_';

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );
		$this->asset_data_registry->add( 'attributes', array_values( wc_get_attribute_taxonomies() ) );

		// Enqueue any `queryState` that the UI will need to be aware of
		// (Ex: the category id if we're on a category page, the tag id if we're on a tag page/etc).
		$query_state = [];

		if ( is_product_category() ) {
			$query_state['category'] = get_queried_object_id();
		}
		if ( is_product_tag() ) {
			$query_state['tag'] = get_queried_object()->term_id;
		}

		$this->asset_data_registry->add( 'queryState', $query_state );
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string[]
	 */
	protected function get_block_type_style() {
		return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
	}
}
PK     \yS      BlockTypes/StoreNotices.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * StoreNotices class.
 */
class StoreNotices extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'store-notices';

	/**
	 * 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 ) {
		/**
		 * This block should be rendered only on the frontend. Woo loads notice
		 * functions on the front end requests only. So it's safe and handy to
		 * check for the print notice function existence to short circuit the
		 * render process on the admin side.
		 * See WooCommerce::is_request() for the frontend request definition.
		 */
		if ( ! function_exists( 'wc_print_notices' ) ) {
			return $content;
		}

		ob_start();
		woocommerce_output_all_notices();
		$notices = ob_get_clean();

		if ( ! $notices ) {
			return;
		}

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );

		return sprintf(
			'<div %1$s>%2$s</div>',
			get_block_wrapper_attributes(
				array(
					'class' => 'wc-block-store-notices woocommerce ' . esc_attr( $classes_and_styles['classes'] ),
				)
			),
			wc_kses_notice( $notices )
		);
	}

	/**
	 * Disable frontend script for this block type, it's a script module.
	 *
	 * @param string $key Data to get, or default to everything.
	 * @return array|string|null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}
}
PK     \[J    ,  BlockTypes/CartOrderSummaryDiscountBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartOrderSummaryDiscountBlock class.
 */
class CartOrderSummaryDiscountBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-order-summary-discount-block';
}
PK     \VB)	  	  !  BlockTypes/ProductDescription.phpnu [        <?php
declare(strict_types=1);
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ProductDescription class.
 */
class ProductDescription extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-description';

	/**
	 * Keeps track of seen product IDs to prevent recursive rendering.
	 *
	 * @var array
	 */
	private static $seen_ids = array();


	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 *
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ) {
		// Check if we have a product ID in context.
		if ( ! isset( $block->context['postId'] ) ) {
			return '';
		}

		$product_id = $block->context['postId'];

		// Prevent recursive rendering.
		if ( isset( self::$seen_ids[ $product_id ] ) ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY ) {
				return __( '[product description rendering halted]', 'woocommerce' );
			}
			return '';
		}

		self::$seen_ids[ $product_id ] = true;

		// Get the product.
		$product = wc_get_product( $product_id );
		if ( ! $product ) {
			unset( self::$seen_ids[ $product_id ] );
			return '';
		}

		// Get the description content.
		$description = $product->get_description();
		/**
		 * This filter is documented in wp-includes/post-template.php.
		 * We follow core/content block to replace ]]> with ]&gt;
		 */
		// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
		$description = apply_filters( 'the_content', str_replace( ']]>', ']]&gt;', $description ) );
		if ( empty( $description ) ) {
			unset( self::$seen_ids[ $product_id ] );
			return '';
		}

		// Remove this product from the seen array.
		unset( self::$seen_ids[ $product_id ] );

		// Add wrapper with block attributes.
		$wrapper_attributes = get_block_wrapper_attributes(
			array( 'class' => 'wc-block-product-description' )
		);

		return sprintf(
			'<div %1$s>%2$s</div>',
			$wrapper_attributes,
			$description
		);
	}

	/**
	 * Disable the frontend stylesheet for this block type. It does not have one.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Disable the frontend script for this block type. It does not have one.
	 *
	 * @param string|null $key The script key.
	 *
	 * @return null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}
}
PK     \!Ϛr      !  BlockTypes/CartLineItemsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartLineItemsBlock class.
 */
class CartLineItemsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-line-items-block';
}
PK     \d\|      "  BlockTypes/CheckoutTotalsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutTotalsBlock class.
 */
class CheckoutTotalsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-totals-block';
}
PK     \t&  &  0  BlockTypes/CheckoutOrderSummaryShippingBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutOrderSummaryShippingBlock class.
 */
class CheckoutOrderSummaryShippingBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-order-summary-shipping-block';
}
PK     \Izl
  
    BlockTypes/Breadcrumbs.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * Breadcrumbs class.
 */
class Breadcrumbs extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'breadcrumbs';

	/**
	 * 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 ) {
		ob_start();
		woocommerce_breadcrumb();
		$breadcrumb = ob_get_clean();

		if ( ! $breadcrumb ) {
			return;
		}

		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'font_size' ) );

		$font_size_classes_and_styles  = $this->get_font_size_classes_and_styles( $attributes );
		$classes_and_styles['classes'] = $classes_and_styles['classes'] . ' ' . $font_size_classes_and_styles['class'] . ' ';
		$classes_and_styles['styles']  = $classes_and_styles['styles'] . ' ' . $font_size_classes_and_styles['style'] . ' ';

		return sprintf(
			'<div class="woocommerce wp-block-breadcrumbs wc-block-breadcrumbs %1$s" style="%2$s">%3$s</div>',
			esc_attr( $classes_and_styles['classes'] ),
			esc_attr( $classes_and_styles['styles'] ),
			$breadcrumb
		);
	}

	/**
	 * 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;
	}

	/**
	 * Gets font size classes and styles for the breadcrumbs block.
	 *
	 * Note: This implementation intentionally avoids using StyleAttributesUtils::get_font_size_class_and_style()
	 * and get_block_wrapper_attributes() to ensure style attributes take precedence over the class attribute fontSize.
	 * This is needed because the block.json defines a default fontSize, which is considered an anti-pattern
	 * since styles should be defined by themes and plugins instead.
	 *
	 * @param array $attributes The block attributes.
	 * @return array The font size classes and styles.
	 */
	private function get_font_size_classes_and_styles( $attributes ) {
		$font_size = $attributes['fontSize'] ?? '';

		$custom_font_size = $attributes['style']['typography']['fontSize'] ?? '';

		if ( ! $font_size && '' === $custom_font_size ) {
			return array(
				'class' => null,
				'style' => null,
			);
		}

		if ( '' !== $custom_font_size ) {
			return array(
				'class' => null,
				'style' => sprintf( 'font-size: %s;', $custom_font_size ),
			);
		}

		return array(
			'class' => sprintf( 'has-font-size has-%s-font-size', $font_size ),
			'style' => null,
		);
	}
}
PK     \I    *  BlockTypes/CartOrderSummaryTotalsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartOrderSummaryTotalsBlock class.
 */
class CartOrderSummaryTotalsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-order-summary-totals-block';
}
PK     \;g      #  BlockTypes/CheckoutPaymentBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutPaymentBlock class.
 */
class CheckoutPaymentBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-payment-block';
}
PK     \ /ضZ  Z    BlockTypes/StockFilter.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * AttributeFilter class.
 */
class StockFilter extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name        = 'stock-filter';
	const STOCK_STATUS_QUERY_VAR = 'filter_stock_status';

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $stock_statuses  Any stock statuses that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $stock_statuses = [] ) {
		parent::enqueue_data( $stock_statuses );
		$this->asset_data_registry->add( 'stockStatusOptions', wc_get_product_stock_status_options() );
		$this->asset_data_registry->add( 'hideOutOfStockItems', 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) );

	}

	/**
	 * Get Stock status query variables values.
	 */
	public static function get_stock_status_query_var_values() {
		return array_keys( wc_get_product_stock_status_options() );
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string[]
	 */
	protected function get_block_type_style() {
		return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
	}
}
PK     \!C
  
  "  BlockTypes/ProductFilterActive.phpnu [        <?php

declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * Product Filter: Active Block.
 */
final class ProductFilterActive extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-filter-active';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( ! isset( $block->context['activeFilters'] ) ) {
			return $content;
		}

		$active_filters = $block->context['activeFilters'];

		$filter_context = array(
			'items' => $active_filters,
		);

		$wrapper_attributes = array(
			'data-wp-interactive'  => 'woocommerce/product-filters',
			'data-wp-key'          => wp_unique_prefixed_id( $this->get_full_block_name() ),
			'data-wp-context'      => wp_json_encode(
				array(
					'filterType' => 'active',
				),
				JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
			),
			'data-wp-bind--hidden' => '!state.hasActiveFilters',
			'data-wp-class--wc-block-product-filter--hidden' => '!state.hasActiveFilters',
		);

		wp_interactivity_state(
			'woocommerce/product-filters',
			array(
				'hasActiveFilters' => ! empty( $active_filters ),
			),
		);

		wp_interactivity_config(
			'woocommerce/product-filters',
			array(
				/* translators:  {{label}} is the label of the active filter item. */
				'removeLabelTemplate' => __( 'Remove filter: {{label}}', 'woocommerce' ),
			)
		);

		return sprintf(
			'<div %1$s>%2$s</div>',
			get_block_wrapper_attributes( $wrapper_attributes ),
			array_reduce(
				$block->parsed_block['innerBlocks'],
				function ( $carry, $parsed_block ) use ( $filter_context ) {
					$carry .= ( new \WP_Block( $parsed_block, array( 'filterData' => $filter_context ) ) )->render();
					return $carry;
				},
				''
			)
		);
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Disable the editor style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_editor_style() {
		return null;
	}

	/**
	 * Disable the script handle for this block type. We use block.json to load the script.
	 *
	 * @param string|null $key The key of the script to get.
	 * @return null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}
}
PK     \Qȩ      BlockTypes/RelatedProducts.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Enums\ProductStatus;

/**
 * RelatedProducts class.
 */
class RelatedProducts extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'related-products';

	/**
	 * The Block with its attributes before it gets rendered
	 *
	 * @var array
	 */
	protected $parsed_block;

	/**
	 * Initialize this block type.
	 *
	 * - Hook into WP lifecycle.
	 * - Register the block with WordPress.
	 * - Hook into pre_render_block to update the query.
	 */
	protected function initialize() {
		parent::initialize();
		add_filter(
			'pre_render_block',
			array( $this, 'update_query' ),
			10,
			2
		);

		add_filter(
			'render_block',
			array( $this, 'render_block' ),
			10,
			2
		);
	}

	/**
	 * It isn't necessary register block assets because it is a server side block.
	 */
	protected function register_block_type_assets() {
		return null;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Update the query for the product query block.
	 *
	 * @param string|null $pre_render   The pre-rendered content. Default null.
	 * @param array       $parsed_block The block being rendered.
	 */
	public function update_query( $pre_render, $parsed_block ) {
		if ( 'core/query' !== $parsed_block['blockName'] ) {
			return $pre_render;
		}

		$this->parsed_block = $parsed_block;

		if ( ProductQuery::is_woocommerce_variation( $parsed_block ) && 'woocommerce/related-products' === $parsed_block['attrs']['namespace'] ) {
			// Set this so that our product filters can detect if it's a PHP template.
			add_filter(
				'query_loop_block_query_vars',
				array( $this, 'build_query' ),
				10,
				2
			);
		}

		return $pre_render;
	}

	/**
	 * Return a custom query based on attributes, filters and global WP_Query.
	 *
	 * @param WP_Query $query The WordPress Query.
	 * @param WP_Block $block The block being rendered.
	 * @return array
	 */
	public function build_query( $query, $block = null ) {
		$parsed_block = $this->parsed_block;
		if ( ! $this->is_related_products_block( $parsed_block, $block ) ) {
			return $query;
		}

		$related_products_ids = $this->get_related_products_ids( $query['posts_per_page'] );
		if ( count( $related_products_ids ) < 1 ) {
			return array();
		}

		return array(
			'post_type'      => 'product',
			'post__in'       => $related_products_ids,
			'post_status'    => ProductStatus::PUBLISH,
			'posts_per_page' => $query['posts_per_page'],
		);
	}

	/**
	 * If there are no related products, return an empty string.
	 *
	 * @param string $content The block content.
	 * @param array  $block The block.
	 *
	 * @return string The block content.
	 */
	public function render_block( string $content, array $block ) {
		if ( ! $this->is_related_products_block( $block ) ) {
			return $content;
		}

		// If there are no related products, render nothing.
		$related_products_ids = $this->get_related_products_ids();
		if ( count( $related_products_ids ) < 1 ) {
			return '';
		}

		return $content;
	}

	/**
	 * Determines whether the block is a related products block.
	 *
	 * @param array $parsed_block The parsed block.
	 * @param array $rendered_block The rendered block.
	 *
	 * @return bool Whether the block is a related products block.
	 */
	private function is_related_products_block( $parsed_block, $rendered_block = null ) {
		$is_product_collection_block = $rendered_block->context['query']['isProductCollectionBlock'] ?? false;
		if ( ! $is_product_collection_block && 'woocommerce/related-products' === ( $parsed_block['attrs']['namespace'] ?? null ) && ProductQuery::is_woocommerce_variation( $parsed_block ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Get related products ids.
	 * The logic is copied from the core function woocommerce_related_products. https://github.com/woocommerce/woocommerce/blob/ca49caabcba84ce9f60a03c6d3534ec14b350b80/plugins/woocommerce/includes/wc-template-functions.php/#L2039-L2074
	 *
	 * @param number $product_per_page Products per page.
	 * @return int[] Products ids.
	 */
	private function get_related_products_ids( $product_per_page = 5 ): array {
		global $post;

		$product = wc_get_product( $post->ID );

		if ( ! $product instanceof \WC_Product ) {
			return array();
		}

		$related_products_ids = wc_get_related_products( $product->get_id(), $product_per_page, $product->get_upsell_ids() );
		if ( ! empty( $related_products_ids ) ) {
			// Prime caches to reduce future queries.
			_prime_post_caches( $related_products_ids );

			$related_products = array_filter( array_map( 'wc_get_product', $related_products_ids ), 'wc_products_array_filter_visible' );
			$related_products = wc_products_array_orderby( $related_products, 'rand', 'desc' );
			/** @var \WC_Product[] $related_products */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort

			// Prime caches to reduce future queries.
			_prime_post_caches( array_filter( array_map( fn( $product ) => (int) $product->get_image_id(), $related_products ) ) );

			$related_products_ids = array_map( fn( $product ) => $product->get_id(), $related_products );
		}

		return $related_products_ids;
	}
}
PK     \k.j  j    BlockTypes/Checkout.phpnu [        <?php
declare( strict_types = 1);
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\Block_Scanner;
use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\StoreApi\Utilities\PaymentUtils;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFieldsSchema\Validation;
use Automattic\WooCommerce\Internal\AddressProvider\AddressProviderController;

/**
 * Checkout class.
 *
 * @internal
 */
class Checkout extends AbstractBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout';

	/**
	 * Chunks build folder.
	 *
	 * @var string
	 */
	protected $chunks_folder = 'checkout-blocks';

	/**
	 * Initialize this block type.
	 *
	 * - Hook into WP lifecycle.
	 * - Register the block with WordPress.
	 */
	protected function initialize() {
		parent::initialize();
		add_action( 'rest_api_init', array( $this, 'register_settings' ) );
		add_action( 'wp_loaded', array( $this, 'register_patterns' ) );
		// This prevents the page redirecting when the cart is empty. This is so the editor still loads the page preview.
		add_filter(
			'woocommerce_checkout_redirect_empty_cart',
			function ( $redirect_empty_cart ) {
				// phpcs:ignore WordPress.Security.NonceVerification.Recommended
				return isset( $_GET['_wp-find-template'] ) ? false : $redirect_empty_cart;
			}
		);

		add_action( 'save_post', array( $this, 'update_local_pickup_title' ), 10, 2 );
	}

	/**
	 * Dequeues the scripts added by WC Core to the Checkout page.
	 *
	 * @return void
	 */
	public function dequeue_woocommerce_core_scripts() {
		wp_dequeue_script( 'wc-checkout' );
		wp_dequeue_script( 'wc-address-autocomplete' );
		wp_dequeue_style( 'wc-address-autocomplete' );
		wp_dequeue_script( 'wc-password-strength-meter' );
		wp_dequeue_script( 'selectWoo' );
		wp_dequeue_style( 'select2' );
	}

	/**
	 * Exposes settings exposed by the checkout block.
	 */
	public function register_settings() {
		register_setting(
			'options',
			'woocommerce_checkout_phone_field',
			array(
				'type'         => 'object',
				'description'  => __( 'Controls the display of the phone field in checkout.', 'woocommerce' ),
				'label'        => __( 'Phone number', 'woocommerce' ),
				'show_in_rest' => array(
					'name'   => 'woocommerce_checkout_phone_field',
					'schema' => array(
						'type' => 'string',
						'enum' => array( 'optional', 'required', 'hidden' ),
					),
				),
				'default'      => CartCheckoutUtils::get_phone_field_visibility(),
			)
		);
		register_setting(
			'options',
			'woocommerce_checkout_company_field',
			array(
				'type'         => 'object',
				'description'  => __( 'Controls the display of the company field in checkout.', 'woocommerce' ),
				'label'        => __( 'Company', 'woocommerce' ),
				'show_in_rest' => array(
					'name'   => 'woocommerce_checkout_company_field',
					'schema' => array(
						'type' => 'string',
						'enum' => array( 'optional', 'required', 'hidden' ),
					),
				),
				'default'      => CartCheckoutUtils::get_company_field_visibility(),
			)
		);
		register_setting(
			'options',
			'woocommerce_checkout_address_2_field',
			array(
				'type'         => 'object',
				'description'  => __( 'Controls the display of the apartment (address_2) field in checkout.', 'woocommerce' ),
				'label'        => __( 'Address Line 2', 'woocommerce' ),
				'show_in_rest' => array(
					'name'   => 'woocommerce_checkout_address_2_field',
					'schema' => array(
						'type' => 'string',
						'enum' => array( 'optional', 'required', 'hidden' ),
					),
				),
				'default'      => CartCheckoutUtils::get_address_2_field_visibility(),
			)
		);
	}

	/**
	 * Register block pattern for Empty Cart Message to make it translatable.
	 */
	public function register_patterns() {
		register_block_pattern(
			'woocommerce/checkout-heading',
			array(
				'title'    => '',
				'inserter' => false,
				'content'  => '<!-- wp:heading {"align":"wide", "level":1} --><h1 class="wp-block-heading alignwide">' . esc_html__( 'Checkout', 'woocommerce' ) . '</h1><!-- /wp:heading -->',
			)
		);
	}

	/**
	 * Get the editor script handle for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 * @return array|string;
	 */
	protected function get_block_type_editor_script( $key = null ) {
		$script = [
			'handle'       => 'wc-' . $this->block_name . '-block',
			'path'         => $this->asset_api->get_block_asset_build_path( $this->block_name ),
			'dependencies' => [ 'wc-blocks' ],
		];
		return $key ? $script[ $key ] : $script;
	}

	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @see $this->register_block_type()
	 * @param string $key Data to get, or default to everything.
	 * @return array|string
	 */
	protected function get_block_type_script( $key = null ) {
		$dependencies = [];

		// Load password strength meter script asynchronously if needed.
		if ( ! is_user_logged_in() && 'no' === get_option( 'woocommerce_registration_generate_password' ) ) {
			$dependencies[] = 'zxcvbn-async';
		}

		$checkout_fields = Package::container()->get( CheckoutFields::class );
		// Load schema parser asynchronously if we need it.
		if ( Validation::has_field_schema( $checkout_fields->get_additional_fields() ) ) {
			$dependencies[] = 'wc-schema-parser';
		}

		$script = [
			'handle'       => 'wc-' . $this->block_name . '-block-frontend',
			'path'         => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
			'dependencies' => $dependencies,
		];
		return $key ? $script[ $key ] : $script;
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string[]
	 */
	protected function get_block_type_style() {
		return array_merge( parent::get_block_type_style(), [ 'wc-blocks-packages-style' ] );
	}

	/**
	 * Enqueue frontend assets for this block, just in time for rendering.
	 *
	 * @param array    $attributes  Any attributes that currently are available from the block.
	 * @param string   $content    The block content.
	 * @param WP_Block $block    The block object.
	 */
	protected function enqueue_assets( array $attributes, $content, $block ) {
		/**
		 * Fires before checkout block scripts are enqueued.
		 *
		 * @since 4.6.0
		 */
		do_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_before' );
		parent::enqueue_assets( $attributes, $content, $block );
		/**
		 * Fires after checkout block scripts are enqueued.
		 *
		 * @since 4.6.0
		 */
		do_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_after' );
	}

	/**
	 * Append frontend scripts when rendering the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {

		if ( $this->is_checkout_endpoint() ) {
			// Note: Currently the block only takes care of the main checkout form -- if an endpoint is set, refer to the
			// legacy shortcode instead and do not render block.
			return wp_is_block_theme() ? do_shortcode( '[woocommerce_checkout]' ) : '[woocommerce_checkout]';
		}

		// Dequeue the core scripts when rendering this block.
		add_action( 'wp_enqueue_scripts', array( $this, 'dequeue_woocommerce_core_scripts' ), 20 );

		/**
		 * We need to check if $content has any templates from prior iterations of the block, in order to update to the latest iteration.
		 * We test the iteration version by searching for new blocks brought in by it.
		 * The blocks used for testing should be always available in the block (not removable by the user).
		 * Checkout i1's content was returning an empty div, with no data-block-name attribute
		 */
		$regex_for_empty_block = '/<div class="[a-zA-Z0-9_\- ]*wp-block-woocommerce-checkout[a-zA-Z0-9_\- ]*"><\/div>/mi';
		$has_i1_template       = preg_match( $regex_for_empty_block, $content );

		if ( $has_i1_template ) {
			// This fallback needs to match the default templates defined in our Blocks.
			$inner_blocks_html = '
				<div data-block-name="woocommerce/checkout-fields-block" class="wp-block-woocommerce-checkout-fields-block">
					<div data-block-name="woocommerce/checkout-express-payment-block" class="wp-block-woocommerce-checkout-express-payment-block"></div>
					<div data-block-name="woocommerce/checkout-contact-information-block" class="wp-block-woocommerce-checkout-contact-information-block"></div>
					<div data-block-name="woocommerce/checkout-shipping-address-block" class="wp-block-woocommerce-checkout-shipping-address-block"></div>
					<div data-block-name="woocommerce/checkout-billing-address-block" class="wp-block-woocommerce-checkout-billing-address-block"></div>
					<div data-block-name="woocommerce/checkout-shipping-methods-block" class="wp-block-woocommerce-checkout-shipping-methods-block"></div>
					<div data-block-name="woocommerce/checkout-payment-block" class="wp-block-woocommerce-checkout-payment-block"></div>
					<div data-block-name="woocommerce/checkout-additional-information-block" class="wp-block-woocommerce-checkout-additional-information-block"></div>' .
					( isset( $attributes['showOrderNotes'] ) && false === $attributes['showOrderNotes'] ? '' : '<div data-block-name="woocommerce/checkout-order-note-block" class="wp-block-woocommerce-checkout-order-note-block"></div>' ) .
					( isset( $attributes['showPolicyLinks'] ) && false === $attributes['showPolicyLinks'] ? '' : '<div data-block-name="woocommerce/checkout-terms-block" class="wp-block-woocommerce-checkout-terms-block"></div>' ) .
					'<div data-block-name="woocommerce/checkout-actions-block" class="wp-block-woocommerce-checkout-actions-block"></div>
				</div>
				<div data-block-name="woocommerce/checkout-totals-block" class="wp-block-woocommerce-checkout-totals-block">
					<div data-block-name="woocommerce/checkout-order-summary-block" class="wp-block-woocommerce-checkout-order-summary-block"></div>
				</div>
			';

			$content = str_replace( '</div>', $inner_blocks_html . '</div>', $content );
		}

		/**
		 * Checkout i3 added inner blocks for Order summary.
		 * We need to add them to Checkout i2 templates.
		 * The order needs to match the order in which these blocks were registered.
		 */
		$order_summary_with_inner_blocks = '$0
			<div data-block-name="woocommerce/checkout-order-summary-cart-items-block" class="wp-block-woocommerce-checkout-order-summary-cart-items-block"></div>
			<div data-block-name="woocommerce/checkout-order-summary-subtotal-block" class="wp-block-woocommerce-checkout-order-summary-subtotal-block"></div>
			<div data-block-name="woocommerce/checkout-order-summary-fee-block" class="wp-block-woocommerce-checkout-order-summary-fee-block"></div>
			<div data-block-name="woocommerce/checkout-order-summary-discount-block" class="wp-block-woocommerce-checkout-order-summary-discount-block"></div>
			<div data-block-name="woocommerce/checkout-order-summary-coupon-form-block" class="wp-block-woocommerce-checkout-order-summary-coupon-form-block"></div>
			<div data-block-name="woocommerce/checkout-order-summary-shipping-block" class="wp-block-woocommerce-checkout-order-summary-shipping-block"></div>
			<div data-block-name="woocommerce/checkout-order-summary-taxes-block" class="wp-block-woocommerce-checkout-order-summary-taxes-block"></div>
		';
		// Order summary subtotal block was added in i3, so we search for it to see if we have a Checkout i2 template.
		$regex_for_order_summary_subtotal = '/<div[^<]*?data-block-name="woocommerce\/checkout-order-summary-subtotal-block"[^>]*?>/mi';
		$regex_for_order_summary          = '/<div[^<]*?data-block-name="woocommerce\/checkout-order-summary-block"[^>]*?>/mi';
		$has_i2_template                  = ! preg_match( $regex_for_order_summary_subtotal, $content );

		if ( $has_i2_template ) {
			$content = preg_replace( $regex_for_order_summary, $order_summary_with_inner_blocks, $content );
		}

		/**
		 * Add the Local Pickup toggle to checkouts missing this forced template.
		 */
		$local_pickup_inner_blocks = '<div data-block-name="woocommerce/checkout-shipping-method-block" class="wp-block-woocommerce-checkout-shipping-method-block"></div>' . PHP_EOL . PHP_EOL . '<div data-block-name="woocommerce/checkout-pickup-options-block" class="wp-block-woocommerce-checkout-pickup-options-block"></div>' . PHP_EOL . PHP_EOL . '$0';
		$has_local_pickup_regex    = '/<div[^<]*?data-block-name="woocommerce\/checkout-shipping-method-block"[^>]*?>/mi';
		$has_local_pickup          = preg_match( $has_local_pickup_regex, $content );

		if ( ! $has_local_pickup ) {
			$shipping_address_block_regex = '/<div[^<]*?data-block-name="woocommerce\/checkout-shipping-address-block" class="wp-block-woocommerce-checkout-shipping-address-block"[^>]*?><\/div>/mi';
			$content                      = preg_replace( $shipping_address_block_regex, $local_pickup_inner_blocks, $content );
		}

		/**
		 * Add the Additional Information block to checkouts missing it.
		 */
		$additional_information_inner_blocks = '$0' . PHP_EOL . PHP_EOL . '<div data-block-name="woocommerce/checkout-additional-information-block" class="wp-block-woocommerce-checkout-additional-information-block"></div>' . PHP_EOL . PHP_EOL;
		$has_additional_information_regex    = '/<div[^<]*?data-block-name="woocommerce\/checkout-additional-information-block"[^>]*?>/mi';
		$has_additional_information_block    = preg_match( $has_additional_information_regex, $content );

		if ( ! $has_additional_information_block ) {
			$payment_block_regex = '/<div[^<]*?data-block-name="woocommerce\/checkout-payment-block" class="wp-block-woocommerce-checkout-payment-block"[^>]*?><\/div>/mi';
			$content             = preg_replace( $payment_block_regex, $additional_information_inner_blocks, $content );
		}

		return $content;
	}

	/**
	 * Check if we're viewing a checkout page endpoint, rather than the main checkout page itself.
	 *
	 * @return boolean
	 */
	protected function is_checkout_endpoint() {
		return is_wc_endpoint_url( 'order-pay' ) || is_wc_endpoint_url( 'order-received' );
	}

	/**
	 * Update the local pickup title in WooCommerce Settings when the checkout page containing a Checkout block is saved.
	 *
	 * @param int      $post_id The post ID.
	 * @param \WP_Post $post    The post object.
	 * @return void
	 */
	public function update_local_pickup_title( $post_id, $post ) {

		// This is not a proper save action, maybe an autosave, so don't continue.
		if ( empty( $post->post_status ) || 'inherit' === $post->post_status ) {
			return;
		}

		// Check if we are editing the checkout page and that it contains a Checkout block.
		// Cast to string for Checkout page ID comparison because get_option can return it as a string, so better to compare both values as strings.
		if ( ! empty( $post->post_type ) && 'wp_template' !== $post->post_type && ( false === has_block( 'woocommerce/checkout', $post ) || (string) get_option( 'woocommerce_checkout_page_id' ) !== (string) $post_id ) ) {
			return;
		}

		if ( ( ! empty( $post->post_type ) && ! empty( $post->post_name ) && 'page-checkout' !== $post->post_name && 'wp_template' === $post->post_type ) || false === has_block( 'woocommerce/checkout', $post ) ) {
			return;
		}
		$pickup_location_settings = LocalPickupUtils::get_local_pickup_settings( 'edit' );

		if ( ! isset( $pickup_location_settings['title'] ) ) {
			return;
		}

		if ( empty( $post->post_content ) ) {
			return;
		}

		$title = $this->find_local_pickup_text_in_checkout_block( $post->post_content );

		// Set the title to be an empty string if it isn't a string. This will make it fall back to the default value of "Pickup".
		if ( ! is_string( $title ) ) {
			$title = '';
		}

		$pickup_location_settings['title'] = $title;
		update_option( 'woocommerce_pickup_location_settings', $pickup_location_settings );
	}

	/**
	 * Find the shipping methods block, then get the value of the localPickupText attribute from it.
	 *
	 * @param string $post_content The post content to search through.
	 * @return null|string The local pickup text if found, otherwise null.
	 */
	private function find_local_pickup_text_in_checkout_block( $post_content ) {
		$scanner = Block_Scanner::create( $post_content );

		while ( $scanner->next_delimiter() ) {
			if ( $scanner->opens_block( 'woocommerce/checkout-shipping-method-block' ) ) {
				$attributes = $scanner->allocate_and_return_parsed_attributes();
				if ( isset( $attributes['localPickupText'] ) ) {
					return $attributes['localPickupText'];
				}
			}
		}

		return null;
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );

		$country_data    = CartCheckoutUtils::get_country_data();
		$address_formats = WC()->countries->get_address_formats();

		// Move the address format into the 'countryData' setting.
		// We need to skip 'default' because that's not a valid country.
		foreach ( $address_formats as $country_code => $format ) {
			if ( 'default' === $country_code ) {
				continue;
			}
			$country_data[ $country_code ]['format'] = $format;
		}

		$providers_payload = [];
		if ( class_exists( AddressProviderController::class ) && 'no' !== get_option( 'woocommerce_address_autocomplete_enabled', 'no' ) ) {
			$controller        = wc_get_container()->get( AddressProviderController::class );
			$providers         = $controller->get_providers();
			$providers_payload = array_map(
				static function ( $provider ) {
					return array(
						'id'            => (string) $provider->id,
						'name'          => sanitize_text_field( (string) $provider->name ),
						'branding_html' => wp_kses_post( (string) $provider->branding_html ),
					);
				},
				(array) $providers
			);
		}
		$this->asset_data_registry->add( 'addressAutocompleteProviders', $providers_payload );
		$this->asset_data_registry->add( 'countryData', $country_data );
		$this->asset_data_registry->add( 'defaultAddressFormat', $address_formats['default'] );
		$this->asset_data_registry->add(
			'checkoutAllowsGuest',
			false === filter_var(
				wc()->checkout()->is_registration_required(),
				FILTER_VALIDATE_BOOLEAN
			)
		);
		$this->asset_data_registry->add(
			'checkoutAllowsSignup',
			filter_var(
				wc()->checkout()->is_registration_enabled(),
				FILTER_VALIDATE_BOOLEAN
			)
		);
		// Optimization note: reduce the number of SQLs required to fetch the options in the lines below.
		wp_prime_option_caches(
			array(
				'woocommerce_enable_checkout_login_reminder',
				'woocommerce_tax_display_cart', // This one is autoloaded, but we add it here for clarity.
				'woocommerce_tax_total_display',
				'woocommerce_ship_to_destination',
				'woocommerce_registration_generate_password',
			)
		);
		$this->asset_data_registry->add( 'checkoutShowLoginReminder', filter_var( get_option( 'woocommerce_enable_checkout_login_reminder' ), FILTER_VALIDATE_BOOLEAN ) );
		$this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ) );
		$this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ) );
		$this->asset_data_registry->add( 'forcedBillingAddress', 'billing_only' === get_option( 'woocommerce_ship_to_destination' ) );
		$this->asset_data_registry->add( 'generatePassword', filter_var( get_option( 'woocommerce_registration_generate_password' ), FILTER_VALIDATE_BOOLEAN ) );
		$this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled() );
		$this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled() );
		$this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled() );
		$this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ) );
		$this->asset_data_registry->register_page_id( isset( $attributes['cartPageId'] ) ? $attributes['cartPageId'] : 0 );
		$this->asset_data_registry->add( 'isBlockTheme', wp_is_block_theme() );
		$this->asset_data_registry->add( 'isCheckoutBlock', true );

		$pickup_location_settings = LocalPickupUtils::get_local_pickup_settings();
		$local_pickup_method_ids  = LocalPickupUtils::get_local_pickup_method_ids();

		$this->asset_data_registry->add( 'localPickupEnabled', $pickup_location_settings['enabled'] );
		$this->asset_data_registry->add( 'localPickupText', $pickup_location_settings['title'] );
		$this->asset_data_registry->add( 'localPickupCost', $pickup_location_settings['cost'] );
		$this->asset_data_registry->add( 'collectableMethodIds', $local_pickup_method_ids );
		$this->asset_data_registry->add( 'shippingMethodsExist', CartCheckoutUtils::shipping_methods_exist() );

		$is_block_editor = $this->is_block_editor();

		if ( $is_block_editor ) {
			$this->asset_data_registry->add(
				'localPickupLocations',
				array_filter(
					array_map(
						function ( $location ) {
							if ( ! wc_string_to_bool( $location['enabled'] ) ) {
								return null;
							}
							$location['formatted_address'] = wc()->countries->get_formatted_address( $location['address'], ', ' );
							return $location;
						},
						LocalPickupUtils::get_local_pickup_method_locations()
					)
				),
			);
		}

		if ( $is_block_editor && ! $this->asset_data_registry->exists( 'globalShippingMethods' ) ) {
			$shipping_methods           = WC()->shipping()->get_shipping_methods();
			$formatted_shipping_methods = array_reduce(
				$shipping_methods,
				function ( array $acc, $method ) use ( $local_pickup_method_ids ) {
					if ( in_array( $method->id, $local_pickup_method_ids, true ) ) {
						return $acc;
					}
					if ( $method->supports( 'settings' ) ) {
						$acc[] = [
							'id'          => $method->id,
							'title'       => $method->method_title,
							'description' => $method->method_description,
						];
					}
					return $acc;
				},
				[]
			);
			$this->asset_data_registry->add( 'globalShippingMethods', $formatted_shipping_methods );
		}

		if ( $is_block_editor && ! $this->asset_data_registry->exists( 'activeShippingZones' ) && class_exists( '\WC_Shipping_Zones' ) ) {
			$this->asset_data_registry->add( 'activeShippingZones', CartCheckoutUtils::get_shipping_zones() );
		}

		if ( $is_block_editor && ! $this->asset_data_registry->exists( 'globalPaymentMethods' ) ) {
			// These are used to show options in the sidebar. We want to get the full list of enabled payment methods,
			// not just the ones that are available for the current cart (which may not exist yet).
			$payment_methods           = PaymentUtils::get_enabled_payment_gateways();
			$formatted_payment_methods = array_reduce(
				$payment_methods,
				function ( array $acc, $method ) {
					$acc[] = [
						'id'          => $method->id,
						'title'       => $method->get_method_title() !== '' ? $method->get_method_title() : $method->get_title(),
						'description' => $method->get_method_description() !== '' ? $method->get_method_description() : $method->get_description(),
					];
					return $acc;
				},
				[]
			);
			$this->asset_data_registry->add( 'globalPaymentMethods', $formatted_payment_methods );
		}

		// Check `current_user_can` so we can show notices about incompatible extensions in the front-end to admins too.
		if ( ( $is_block_editor || current_user_can( 'manage_woocommerce' ) ) && ! $this->asset_data_registry->exists( 'incompatibleExtensions' ) ) {
			if ( ! class_exists( '\Automattic\WooCommerce\Utilities\FeaturesUtil' ) || ! function_exists( 'get_plugins' ) ) {
				return;
			}

			$declared_extensions     = \Automattic\WooCommerce\Utilities\FeaturesUtil::get_compatible_plugins_for_feature( 'cart_checkout_blocks' );
			$all_plugins             = \get_plugins(); // Note that `get_compatible_plugins_for_feature` calls `get_plugins` internally, so this is already in cache.
			$incompatible_extensions = array_reduce(
				$declared_extensions['incompatible'],
				function ( array $acc, $item ) use ( $all_plugins ) {
					$plugin      = $all_plugins[ $item ] ?? null;
					$plugin_id   = $plugin['TextDomain'] ?? dirname( $item );
					$plugin_name = $plugin['Name'] ?? $plugin_id;
					$acc[]       = [
						'id'    => $plugin_id,
						'title' => $plugin_name,
					];
					return $acc;
				},
				[]
			);
			$this->asset_data_registry->add( 'incompatibleExtensions', $incompatible_extensions );
		}

		if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
			$this->asset_data_registry->hydrate_api_request( '/wc/store/v1/cart' );
			$this->asset_data_registry->hydrate_data_from_api_request( 'checkoutData', '/wc/store/v1/checkout' );
			$this->hydrate_customer_payment_methods();
		}

		/**
		 * Fires after checkout block data is registered.
		 *
		 * @since 2.6.0
		 */
		do_action( 'woocommerce_blocks_checkout_enqueue_data' );
	}

	/**
	 * Get saved customer payment methods for use in checkout.
	 */
	protected function hydrate_customer_payment_methods() {
		$payment_methods = PaymentUtils::get_saved_payment_methods();

		if ( ! $payment_methods || $this->asset_data_registry->exists( 'customerPaymentMethods' ) ) {
			return;
		}

		$this->asset_data_registry->add(
			'customerPaymentMethods',
			is_array( $payment_methods ) ? $payment_methods['enabled'] : null
		);
	}

	/**
	 * Register script and style assets for the block type before it is registered.
	 *
	 * This registers the scripts; it does not enqueue them.
	 */
	protected function register_block_type_assets() {
		parent::register_block_type_assets();
		$chunks        = $this->get_chunks_paths( $this->chunks_folder );
		$vendor_chunks = $this->get_chunks_paths( 'vendors--checkout-blocks' );
		$shared_chunks = [ 'cart-blocks/cart-express-payment--checkout-blocks/express-payment-frontend' ];
		$this->register_chunk_translations( array_merge( $chunks, $vendor_chunks, $shared_chunks ) );
	}

	/**
	 * Get list of Checkout block & its inner-block types.
	 *
	 * @return array;
	 */
	public static function get_checkout_block_types() {
		return [
			'Checkout',
			'CheckoutActionsBlock',
			'CheckoutAdditionalInformationBlock',
			'CheckoutBillingAddressBlock',
			'CheckoutContactInformationBlock',
			'CheckoutExpressPaymentBlock',
			'CheckoutFieldsBlock',
			'CheckoutOrderNoteBlock',
			'CheckoutOrderSummaryBlock',
			'CheckoutOrderSummaryCartItemsBlock',
			'CheckoutOrderSummaryCouponFormBlock',
			'CheckoutOrderSummaryDiscountBlock',
			'CheckoutOrderSummaryFeeBlock',
			'CheckoutOrderSummaryShippingBlock',
			'CheckoutOrderSummarySubtotalBlock',
			'CheckoutOrderSummaryTaxesBlock',
			'CheckoutOrderSummaryTotalsBlock',
			'CheckoutPaymentBlock',
			'CheckoutShippingAddressBlock',
			'CheckoutShippingMethodsBlock',
			'CheckoutShippingMethodBlock',
			'CheckoutPickupOptionsBlock',
			'CheckoutTermsBlock',
			'CheckoutTotalsBlock',
		];
	}
}
PK     \mT}    !  BlockTypes/HandpickedProducts.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Enums\ProductStockStatus;

/**
 * HandpickedProducts class.
 */
class HandpickedProducts extends AbstractProductGrid {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'handpicked-products';

	/**
	 * Set args specific to this block
	 *
	 * @param array $query_args Query args.
	 */
	protected function set_block_query_args( &$query_args ) {
		$ids = array_map( 'absint', $this->attributes['products'] );

		$query_args['post__in']       = $ids;
		$query_args['posts_per_page'] = count( $ids ); // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
	}

	/**
	 * Set visibility query args. Handpicked products will show hidden products if chosen.
	 *
	 * @param array $query_args Query args.
	 */
	protected function set_visibility_query_args( &$query_args ) {
		if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
			$product_visibility_terms  = wc_get_product_visibility_term_ids();
			$query_args['tax_query'][] = array(
				'taxonomy' => 'product_visibility',
				'field'    => 'term_taxonomy_id',
				'terms'    => array( $product_visibility_terms[ ProductStockStatus::OUT_OF_STOCK ] ),
				'operator' => 'NOT IN',
			);
		}
	}

	/**
	 * Get block attributes.
	 *
	 * @return array
	 */
	protected function get_block_type_attributes() {
		return array(
			'align'             => $this->get_schema_align(),
			'alignButtons'      => $this->get_schema_boolean( false ),
			'className'         => $this->get_schema_string(),
			'columns'           => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ),
			'orderby'           => $this->get_schema_orderby(),
			'products'          => $this->get_schema_list_ids(),
			'contentVisibility' => $this->get_schema_content_visibility(),
			'isPreview'         => $this->get_schema_boolean( false ),
		);
	}
}
PK     \t      !  BlockTypes/CheckoutTermsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutTermsBlock class.
 */
class CheckoutTermsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-terms-block';
}
PK     \.      BlockTypes/ProductOnSale.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ProductOnSale class.
 */
class ProductOnSale extends AbstractProductGrid {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-on-sale';

	/**
	 * Set args specific to this block
	 *
	 * @param array $query_args Query args.
	 */
	protected function set_block_query_args( &$query_args ) {
		$query_args['post__in'] = array_merge( array( 0 ), wc_get_product_ids_on_sale() );
	}
	/**
	 * Get block attributes.
	 *
	 * @return array
	 */
	protected function get_block_type_attributes() {
		return array_merge(
			parent::get_block_type_attributes(),
			array(
				'className' => $this->get_schema_string(),
				'orderby'   => $this->get_schema_orderby(),
			)
		);
	}
}
PK     \bHM    "  BlockTypes/ProductResultsCount.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * ProductResultsCount class.
 */
class ProductResultsCount extends AbstractBlock {

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-results-count';

	/**
	 * 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;
	}

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 *
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ) {
		// Buffer the result count and use it as the block's frontend content.
		ob_start();
		echo '<div>';
		woocommerce_result_count();
		echo '</div>';
		$product_results_count = ob_get_clean();

		$p = new \WP_HTML_Tag_Processor( $product_results_count );

		// Advance to the wrapper and add the attributes necessary for the block.
		$p->next_tag( 'div' );
		$parsed_style_attributes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
		$classes                 = array_merge(
			explode( ' ', $parsed_style_attributes['classes'] ),
			array(
				'woocommerce',
				'wc-block-product-results-count',
				'wp-block-woocommerce-product-results-count',
			),
		);
		$p->set_attribute( 'class', implode( ' ', $classes ) );
		$p->set_attribute( 'style', $parsed_style_attributes['styles'] );
		$p->set_attribute(
			'data-wp-router-region',
			'wc-product-results-count-' . ( isset( $block->context['queryId'] ) ? $block->context['queryId'] : 0 )
		);

		return $p->get_updated_html();
	}
}
PK     \7-ۮd  d  #  BlockTypes/AbstractDynamicBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * AbstractDynamicBlock class.
 */
abstract class AbstractDynamicBlock extends AbstractBlock {
	/**
	 * Get the frontend script handle for this block type.
	 *
	 * @param string $key Data to get, or default to everything.
	 * @return null
	 */
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Get block attributes.
	 *
	 * @return array
	 */
	protected function get_block_type_attributes() {
		return array();
	}

	/**
	 * Get the schema for the alignment property.
	 *
	 * @return array Property definition for align.
	 */
	protected function get_schema_align() {
		return array(
			'type' => 'string',
			'enum' => array( 'left', 'center', 'right', 'wide', 'full' ),
		);
	}

	/**
	 * Get the schema for a list of IDs.
	 *
	 * @return array Property definition for a list of numeric ids.
	 */
	protected function get_schema_list_ids() {
		return array(
			'type'    => 'array',
			'items'   => array(
				'type' => 'number',
			),
			'default' => array(),
		);
	}

	/**
	 * Get the schema for a boolean value.
	 *
	 * @param  string $default  The default value.
	 * @return array Property definition.
	 */
	protected function get_schema_boolean( $default = true ) {
		return array(
			'type'    => 'boolean',
			'default' => $default,
		);
	}

	/**
	 * Get the schema for a numeric value.
	 *
	 * @param  string $default  The default value.
	 * @return array Property definition.
	 */
	protected function get_schema_number( $default ) {
		return array(
			'type'    => 'number',
			'default' => $default,
		);
	}

	/**
	 * Get the schema for a string value.
	 *
	 * @param  string $default  The default value.
	 * @return array Property definition.
	 */
	protected function get_schema_string( $default = '' ) {
		return array(
			'type'    => 'string',
			'default' => $default,
		);
	}
}
PK     \!nX7  7    BlockTypes/FeaturedItem.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * FeaturedItem class.
 */
abstract class FeaturedItem extends AbstractDynamicBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name;

	/**
	 * Default attribute values.
	 *
	 * @var array
	 */
	protected $defaults = array(
		'align' => 'none',
	);

	/**
	 * Global style enabled for this block.
	 *
	 * @var array
	 */
	protected $global_style_wrapper = array(
		'background_color',
		'border_color',
		'border_radius',
		'border_width',
		'font_size',
		'padding',
		'text_color',
		'extra_classes',
	);

	/**
	 * Initialize the block.
	 */
	protected function initialize() {
		parent::initialize();
		add_filter( 'render_block_context', [ $this, 'update_context' ], 10, 3 );
		add_filter( 'render_block_core/post-title', [ $this, 'restore_global_post' ], 10, 3 );
	}

	/**
	 * Current item (product or category) for context
	 *
	 * @var \WP_Term|\WC_Product|null
	 */
	private $current_item = null;

	/**
	 * Current featured item ID (product or category) for context
	 *
	 * @var int
	 */
	protected $featured_item_id = 0;

	/**
	 * Featured Item inner blocks names.
	 * This is used to map all the inner blocks for a Featured Item block.
	 *
	 * @var array
	 */
	protected $featured_item_inner_blocks_names = [];

	/**
	 * Extract the inner block names for the Featured Item block. This way it's possible
	 * to map all the inner blocks for a Featured Item block and manipulate the data as needed.
	 *
	 * @param array $block The Featured Item block or its inner blocks.
	 * @param array $result Array of inner block names.
	 *
	 * @return array Array containing all the inner block names of a Featured Item block.
	 */
	protected function extract_featured_item_inner_block_names( $block, &$result = [] ) {
		if ( isset( $block['blockName'] ) ) {
			$result[] = $block['blockName'];
		}

		if ( 'woocommerce/product-template' === $block['blockName'] || 'core/post-template' === $block['blockName'] ) {
			return $result;
		}

		if ( isset( $block['innerBlocks'] ) ) {
			foreach ( $block['innerBlocks'] as $inner_block ) {
				$this->extract_featured_item_inner_block_names( $inner_block, $result );
			}
		}
		return $result;
	}

	/**
	 * Replace the global post for the Featured Item inner blocks and reset it after.
	 *
	 * This is needed because some of the inner blocks may use the global post
	 * instead of fetching the product through the context, so even if the
	 * context is passed to the inner block, it will still use the global post.
	 *
	 * @param array $block Block attributes.
	 * @param array $context Block context.
	 */
	protected function replace_post_for_featured_item_inner_block( $block, &$context ) {
		if ( $this->featured_item_inner_blocks_names ) {
			$block_name = end( $this->featured_item_inner_blocks_names );

			if ( $block_name === $block['blockName'] ) {
				array_pop( $this->featured_item_inner_blocks_names );

				// Handle core blocks that need global post manipulation.
				if ( 'core/post-excerpt' === $block_name || 'core/post-title' === $block_name ) {
					global $post;
					$post = get_post( $this->featured_item_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited

					if ( $post instanceof \WP_Post ) {
						setup_postdata( $post );
					}
				}

				$context['postId']   = $this->featured_item_id;
				$context['postType'] = 'product';
				$this->current_item  = wc_get_product( $this->featured_item_id );
			}
		}
	}

	/**
	 * Update context for inner blocks to provide postId and postType.
	 *
	 * @param array    $context Block context.
	 * @param array    $parsed_block Block attributes.
	 * @param WP_Block $parent_block Block instance.
	 *
	 * @return array Updated block context.
	 */
	public function update_context( $context, $parsed_block, $parent_block ) {
		// Check if this is a featured item block and extract all inner block names.
		if ( ( 'woocommerce/featured-product' === $parsed_block['blockName'] || 'woocommerce/featured-category' === $parsed_block['blockName'] )
			&& isset( $parsed_block['attrs'] ) ) {

			$item = $this->get_item( $parsed_block['attrs'] );
			if ( $item instanceof \WC_Product ) {
				$this->featured_item_id = $item->get_id();

				$this->featured_item_inner_blocks_names = array_reverse(
					$this->extract_featured_item_inner_block_names( $parsed_block )
				);
			}
		}

		// Replace post context for featured item inner blocks.
		$this->replace_post_for_featured_item_inner_block( $parsed_block, $context );

		return $context;
	}

	/**
	 * Restore global post data after rendering core/post-title.
	 *
	 * @param string    $block_content The block content.
	 * @param array     $parsed_block The full block, including name and attributes.
	 * @param \WP_Block $block_instance The block instance.
	 *
	 * @return string
	 */
	public function restore_global_post( $block_content, $parsed_block, $block_instance ) {
		if ( $this->current_item ) {
			wp_reset_postdata();
		}

		return $block_content;
	}

	/**
	 * Returns the featured item.
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 * @return \WP_Term|\WC_Product|null
	 */
	abstract protected function get_item( $attributes );

	/**
	 * Returns the name of the featured item.
	 *
	 * @param \WP_Term|\WC_Product $item Item object.
	 * @return string
	 */
	abstract protected function get_item_title( $item );

	/**
	 * Returns the featured item image URL.
	 *
	 * @param \WP_Term|\WC_Product $item Item object.
	 * @param string               $size Image size, defaults to 'full'.
	 * @return string
	 */
	abstract protected function get_item_image( $item, $size = 'full' );

	/**
	 * Renders the featured item attributes.
	 *
	 * @param \WP_Term|\WC_Product $item       Item object.
	 * @param array                $attributes Block attributes. Default empty array.
	 * @return string
	 */
	abstract protected function render_attributes( $item, $attributes );

	/**
	 * Render the featured item block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$item = $this->get_item( $attributes );
		if ( ! $item ) {
			return '';
		}

		$aria_label = $attributes['ariaLabel'] ?? '';
		$attributes = wp_parse_args( $attributes, $this->defaults );

		$attributes['height'] = $attributes['height'] ?? wc_get_theme_support( 'featured_block::default_height', 500 );

		$image_url = esc_url( $this->get_image_url( $attributes, $item ) );

		$styles  = $this->get_styles( $attributes );
		$classes = $this->get_classes( $attributes );

		$output  = sprintf( '<div class="%1$s wp-block-woocommerce-%2$s" style="%3$s">', esc_attr( trim( $classes ) ), $this->block_name, esc_attr( $styles ) );
		$output .= sprintf( '<div class="wc-block-%s__wrapper">', $this->block_name );
		$output .= $this->render_overlay( $attributes );

		if ( ! $attributes['isRepeated'] && ! $attributes['hasParallax'] ) {
			$output .= $this->render_image( $attributes, $item, $image_url );
		} else {
			$output .= $this->render_bg_image( $attributes, $image_url );
		}

		if ( isset( $aria_label ) && ! empty( $aria_label ) ) {
			$p = new \WP_HTML_Tag_Processor( $content );

			if ( $p->next_tag( 'a', [ 'class' => 'wp-block-button__link' ] ) ) {
				$p->set_attribute( 'aria-label', $aria_label );
				$content = $p->get_updated_html();
			}
		}

		// Render additional attributes (e.g. description/price) for legacy compatibility.
		$output .= $this->render_attributes( $item, $attributes );

		if ( ! empty( $content ) ) {
			$output .= sprintf( '<div class="wc-block-%s__inner-blocks">%s</div>', $this->block_name, $content );
		}

		$output .= '</div>';
		$output .= '</div>';

		return $output;
	}

	/**
	 * Returns the url the item's image
	 *
	 * @param array                $attributes Block attributes. Default empty array.
	 * @param \WP_Term|\WC_Product $item       Item object.
	 *
	 * @return string
	 */
	private function get_image_url( $attributes, $item ) {
		$image_size = 'large';
		if ( 'none' !== $attributes['align'] || $attributes['height'] > 800 ) {
			$image_size = 'full';
		}

		if ( $attributes['mediaId'] ) {
			return wp_get_attachment_image_url( $attributes['mediaId'], $image_size );
		}

		return $this->get_item_image( $item, $image_size );
	}

	/**
	 * Renders the featured image as a div background.
	 *
	 * @param array  $attributes Block attributes. Default empty array.
	 * @param string $image_url  Item image url.
	 *
	 * @return string
	 */
	private function render_bg_image( $attributes, $image_url ) {
		$styles = $this->get_bg_styles( $attributes, $image_url );

		$classes = [ "wc-block-{$this->block_name}__background-image" ];

		if ( $attributes['hasParallax'] ) {
			$classes[] = ' has-parallax';
		}

		return sprintf( '<div class="%1$s" style="%2$s" /></div>', esc_attr( implode( ' ', $classes ) ), esc_attr( $styles ) );
	}

	/**
	 * Get the styles for the wrapper element (background image, color).
	 *
	 * @param array  $attributes Block attributes. Default empty array.
	 * @param string $image_url  Item image url.
	 *
	 * @return string
	 */
	public function get_bg_styles( $attributes, $image_url ) {
		$style = '';

		if ( $attributes['isRepeated'] || $attributes['hasParallax'] ) {
			$style .= "background-image: url($image_url);";
		}

		if ( ! $attributes['isRepeated'] ) {
			$style .= 'background-repeat: no-repeat;';

			$bg_size = 'cover' === $attributes['imageFit'] ? $attributes['imageFit'] : 'auto';
			$style  .= 'background-size: ' . $bg_size . ';';
		}

		if ( $this->hasFocalPoint( $attributes ) ) {
			$style .= sprintf(
				'background-position: %s%% %s%%;',
				$attributes['focalPoint']['x'] * 100,
				$attributes['focalPoint']['y'] * 100
			);
		}

		$global_style_style = StyleAttributesUtils::get_styles_by_attributes( $attributes, $this->global_style_wrapper );
		$style             .= $global_style_style;

		return $style;
	}

	/**
	 * Renders the featured image
	 *
	 * @param array                $attributes Block attributes. Default empty array.
	 * @param \WC_Product|\WP_Term $item       Item object.
	 * @param string               $image_url  Item image url.
	 *
	 * @return string
	 */
	private function render_image( $attributes, $item, string $image_url ) {
		$style   = sprintf( 'object-fit: %s;', esc_attr( $attributes['imageFit'] ) );
		$img_alt = $attributes['alt'] ?: $this->get_item_title( $item );

		if ( $this->hasFocalPoint( $attributes ) ) {
			$style .= sprintf(
				'object-position: %s%% %s%%;',
				$attributes['focalPoint']['x'] * 100,
				$attributes['focalPoint']['y'] * 100
			);
		}

		if ( ! empty( $image_url ) ) {
			return sprintf(
				'<img alt="%1$s" class="wc-block-%2$s__background-image" src="%3$s" style="%4$s" />',
				esc_attr( $img_alt ),
				$this->block_name,
				esc_url( $image_url ),
				esc_attr( $style )
			);
		}

		return '';
	}

	/**
	 * Get the styles for the wrapper element (background image, color).
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 * @return string
	 */
	public function get_styles( $attributes ) {
		$style = '';

		$min_height = $attributes['minHeight'] ?? wc_get_theme_support( 'featured_block::default_height', 500 );

		if ( isset( $attributes['minHeight'] ) ) {
			$style .= sprintf( 'min-height:%dpx;', intval( $min_height ) );
		}

		$global_style_style = StyleAttributesUtils::get_styles_by_attributes( $attributes, $this->global_style_wrapper );
		$style             .= $global_style_style;

		return $style;
	}


	/**
	 * Get class names for the block container.
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 * @return string
	 */
	public function get_classes( $attributes ) {
		$classes = array( 'wc-block-' . $this->block_name );

		if ( isset( $attributes['align'] ) ) {
			$classes[] = "align{$attributes['align']}";
		}

		if ( isset( $attributes['dimRatio'] ) && ( 0 !== $attributes['dimRatio'] ) ) {
			$classes[] = 'has-background-dim';

			if ( 50 !== $attributes['dimRatio'] ) {
				$classes[] = 'has-background-dim-' . 10 * round( $attributes['dimRatio'] / 10 );
			}
		}

		if ( isset( $attributes['contentAlign'] ) && 'center' !== $attributes['contentAlign'] ) {
			$classes[] = "has-{$attributes['contentAlign']}-content";
		}

		$global_style_classes = StyleAttributesUtils::get_classes_by_attributes( $attributes, $this->global_style_wrapper );

		$classes[] = $global_style_classes;

		return implode( ' ', $classes );
	}

	/**
	 * Renders the block overlay
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 *
	 * @return string
	 */
	private function render_overlay( $attributes ) {
		if ( isset( $attributes['overlayGradient'] ) ) {
			$overlay_styles = sprintf( 'background-image: %s', $attributes['overlayGradient'] );
		} elseif ( isset( $attributes['overlayColor'] ) ) {
			$overlay_styles = sprintf( 'background-color: %s', $attributes['overlayColor'] );
		} else {
			$overlay_styles = 'background-color: #000000';
		}

		return sprintf( '<div class="background-dim__overlay" style="%s"></div>', esc_attr( $overlay_styles ) );
	}

	/**
	 * Returns whether the focal point is defined for the block.
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 *
	 * @return bool
	 */
	private function hasFocalPoint( $attributes ): bool {
		return is_array( $attributes['focalPoint'] ) && 2 === count( $attributes['focalPoint'] );
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );
		$this->asset_data_registry->add( 'defaultHeight', wc_get_theme_support( 'featured_block::default_height', 500 ) );
	}
}
PK     \~E    )  BlockTypes/EnableBlockJsonAssetsTrait.phpnu [        <?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\Blocks\BlockTypes;

trait EnableBlockJsonAssetsTrait {

	/**
	 * Disable the script handle for this block type. We use block.json to load the script.
	 *
	 * @param string|null $key The key of the script to get.
	 * @return null
	 */
	// phpcs:ignore
	protected function get_block_type_script( $key = null ) {
		return null;
	}

	/**
	 * Disable the style handle for this block type. We use block.json to load the style.
	 *
	 * @return null
	 */
	protected function get_block_type_style() {
		return null;
	}

	/**
	 * Disable the editor style handle for this block type. We use block.json to load the style.
	 *
	 * @return null
	 */
	protected function get_block_type_editor_style() {
		return null;
	}
}
PK     \gR
  
    BlockTypes/FeaturedCategory.phpnu [        <?php
declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * FeaturedCategory class.
 */
class FeaturedCategory extends FeaturedItem {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'featured-category';

	/**
	 * Get block attributes.
	 *
	 * @return array
	 */
	protected function get_block_type_attributes() {
		return array_merge(
			parent::get_block_type_attributes(),
			array(
				'textColor'  => $this->get_schema_string(),
				'fontSize'   => $this->get_schema_string(),
				'lineHeight' => $this->get_schema_string(),
				'style'      => array( 'type' => 'object' ),
			)
		);
	}

	/**
	 * Returns the featured category.
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 * @return \WP_Term|null
	 */
	protected function get_item( $attributes ) {
		$id = absint( $attributes['categoryId'] ?? 0 );

		$category = get_term( $id, 'product_cat' );
		if ( ! $category || is_wp_error( $category ) ) {
			return null;
		}

		return $category;
	}

	/**
	 * Returns the name of the featured category.
	 *
	 * @param \WP_Term $category Featured category.
	 * @return string
	 */
	protected function get_item_title( $category ) {
		return $category->name;
	}

	/**
	 * Returns the featured category image URL.
	 *
	 * @param \WP_Term $category Term object.
	 * @param string   $size Image size, defaults to 'full'.
	 * @return string
	 */
	protected function get_item_image( $category, $size = 'full' ) {
		$image    = '';
		$image_id = get_term_meta( $category->term_id, 'thumbnail_id', true );

		if ( $image_id ) {
			$image = wp_get_attachment_image_url( $image_id, $size );
		}

		return $image;
	}

	/**
	 * Renders the featured category attributes.
	 *
	 * @param \WP_Term $category Term object.
	 * @param array    $attributes Block attributes. Default empty array.
	 * @return string
	 */
	protected function render_attributes( $category, $attributes ) {
		$output = '';

		// Backwards compatibility: Only render legacy attributes if `editMode` exists as boolean value
		// This allows us to distinguish between old and new version of the block (which accept inner blocks).
		if ( array_key_exists( 'editMode', $attributes ) && is_bool( $attributes['editMode'] ) ) {
			$legacy_title = sprintf(
				'<h2 class="wc-block-featured-category__title">%s</h2>',
				wp_kses_post( $category->name )
			);

			$output .= $legacy_title;

			if (
				! isset( $attributes['showDesc'] ) ||
				( isset( $attributes['showDesc'] ) && false !== $attributes['showDesc'] )
			) {
				$desc_str = sprintf(
					'<div class="wc-block-featured-category__description">%s</div>',
					wc_format_content( wp_kses_post( $category->description ) )
				);
				$output  .= $desc_str;
			}
		}

		return $output;
	}
}
PK     \TR	    %  BlockTypes/ProceedToCheckoutBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * ProceedToCheckoutBlock class.
 */
class ProceedToCheckoutBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'proceed-to-checkout-block';

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		$this->asset_data_registry->register_page_id( isset( $attributes['checkoutPageId'] ) ? $attributes['checkoutPageId'] : 0 );
	}
}
PK     \9*3  3  &  BlockTypes/MiniCartTitleLabelBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Admin\Features\Features;

/**
 * MiniCartTitleLabelBlock class.
 */
class MiniCartTitleLabelBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'mini-cart-title-label-block';

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		if ( Features::is_enabled( 'experimental-iapi-mini-cart' ) ) {
			return $this->render_experimental_iapi_title_label_block( $attributes, $content, $block );
		}
		return $content;
	}

	/**
	 * Render the interactivity API powered experimental title block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content    Block content.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render_experimental_iapi_title_label_block( $attributes, $content, $block ) {
		$default_cart_label = __( 'Your cart', 'woocommerce' );
		$cart_label         = $attributes['label'] ? $attributes['label'] : $default_cart_label;
		$wrapper_attributes = get_block_wrapper_attributes();

		ob_start();
		?>
		<span <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<?php echo esc_html( $cart_label ); ?>
		</span>
		<?php
		return ob_get_clean();
	}
}
PK     \r:    ,  BlockTypes/CartOrderSummaryShippingBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartOrderSummaryShippingBlock class.
 */
class CartOrderSummaryShippingBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-order-summary-shipping-block';
}
PK     \'7ќ    +  BlockTypes/CheckoutShippingMethodsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutShippingMethodsBlock class.
 */
class CheckoutShippingMethodsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-shipping-methods-block';
}
PK     \-~
  
  (  BlockTypes/CheckoutOrderSummaryBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutOrderSummaryBlock class.
 */
class CheckoutOrderSummaryBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-order-summary-block';

	/**
	 * Get the contents of the given inner block.
	 *
	 * @param string $block_name Name of the order summary inner block.
	 * @param string $content    The content to search.
	 * @return array|bool
	 */
	private function get_inner_block_content( $block_name, $content ) {
		if ( preg_match( $this->inner_block_regex( $block_name ), $content, $matches ) ) {
			return $matches[0];
		}
		return false;
	}

	/**
	 * Get the regex that will return an inner block.
	 *
	 * @param string $block_name Name of the order summary inner block.
	 * @return string Regex pattern.
	 */
	private function inner_block_regex( $block_name ) {
		return '/<div data-block-name="woocommerce\/checkout-order-summary-' . $block_name . '-block"(.+?)>(.*?)<\/div>/si';
	}

	/**
	 * Render the Checkout Order Summary block.
	 *
	 * @param array  $attributes Block attributes.
	 * @param string $content    Block content.
	 * @param object $block      Block object.
	 * @return string Rendered block.
	 */
	protected function render( $attributes, $content, $block ) {
		// The order-summary-totals block was introduced as a new parent block for the totals
		// (subtotal, discount, fees, shipping and taxes) blocks.
		$regex_for_checkout_order_summary_totals = '/<div data-block-name="woocommerce\/checkout-order-summary-totals-block"(.+?)>/';
		$order_summary_totals_content            = '<div data-block-name="woocommerce/checkout-order-summary-totals-block" class="wp-block-woocommerce-checkout-order-summary-totals-block">';

		// We want to move these blocks inside a parent 'totals' block.
		$totals_inner_blocks = array( 'subtotal', 'discount', 'fee', 'shipping', 'taxes' );

		if ( preg_match( $regex_for_checkout_order_summary_totals, $content ) ) {
			return $content;
		}

		foreach ( $totals_inner_blocks as $key => $block_name ) {
			$inner_block_content = $this->get_inner_block_content( $block_name, $content );

			if ( $inner_block_content ) {
				$order_summary_totals_content .= "\n" . $inner_block_content;

				// The last block is replaced with the totals block.
				if ( count( $totals_inner_blocks ) - 1 === $key ) {
					$order_summary_totals_content .= '</div>';
					$content                       = preg_replace( $this->inner_block_regex( $block_name ), $order_summary_totals_content, $content );
				} else {
					// Otherwise, remove the block.
					$content = preg_replace( $this->inner_block_regex( $block_name ), '', $content );
				}
			}
		}

		// Remove empty lines.
		return preg_replace( '/\n\n( *?)/i', '', $content );
	}
}
PK     \d        BlockTypes/CartTotalsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartTotalsBlock class.
 */
class CartTotalsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-totals-block';
}
PK     \F        BlockTypes/FilledCartBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * FilledCartBlock class.
 */
class FilledCartBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'filled-cart-block';
}
PK     \[Cs    %  BlockTypes/CheckoutOrderNoteBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CheckoutOrderNoteBlock class.
 */
class CheckoutOrderNoteBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'checkout-order-note-block';
}
PK     \PtX  X    BlockTypes/ProductSummary.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;

/**
 * ProductSummary class.
 */
class ProductSummary extends AbstractBlock {


	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'product-summary';

	/**
	 * API version name.
	 *
	 * @var string
	 */
	protected $api_version = '3';

	/**
	 * Overwrite parent method to prevent script registration.
	 *
	 * It is necessary to register and enqueues assets during the render
	 * phase because we want to load assets only if the block has the content.
	 */
	protected function register_block_type_assets() {
		return null;
	}

	/**
	 * Register the context.
	 */
	protected function get_block_type_uses_context() {
		return [ 'query', 'queryId', 'postId' ];
	}

	/**
	 * Get product description depends on config.
	 *
	 * @param WC_Product $product                   Product object.
	 * @param bool       $show_description_if_empty Defines if fallback to full description.
	 * @return string
	 */
	private function get_source( $product, $show_description_if_empty ) {
		$short_description = $product->get_short_description();

		if ( $short_description ) {
			// Logic copied from https://github.com/woocommerce/woocommerce/blob/637dde283057ed6667ff81c73ed08774552f631d/plugins/woocommerce/includes/wc-core-functions.php#L53-L62.
			$short_description = wp_kses_post( $short_description );
			$short_description = $GLOBALS['wp_embed']->run_shortcode( $short_description );
			$short_description = shortcode_unautop( $short_description );
			$short_description = do_shortcode( $short_description );
			return $short_description;
		}

		$description = $product->get_description();

		if ( $show_description_if_empty && $description ) {
			// Logic copied from https://github.com/woocommerce/woocommerce/blob/637dde283057ed6667ff81c73ed08774552f631d/plugins/woocommerce/includes/wc-core-functions.php#L53-L62.
			$description = wp_kses_post( $description );
			$description = $GLOBALS['wp_embed']->run_shortcode( $description );
			$description = shortcode_unautop( $description );
			$description = do_shortcode( $description );
			return $description;
		}

		return '';
	}

	/**
	 * Create anchor element based on input.
	 *
	 * @param WC_Product $product   Product object.
	 * @param string     $link_text Link text.
	 * @return string
	 */
	private function create_anchor( $product, $link_text ) {
		$href = esc_url( $product->get_permalink() );
		$text = wp_kses_post( $link_text );

		return '<a class="wp-block-woocommerce-product-summary__read_more" href="' . $href . '#tab-description">' . $text . '</a>';
	}

	/**
	 * Get first paragraph from some HTML text, or return the whole string.
	 *
	 * @param string $source Source text.
	 * @return string First paragraph found in string.
	 */
	private function get_first_paragraph( $source ) {
		$p_index = strpos( $source, '</p>' );

		if ( false === $p_index ) {
			return $source;
		}

		return substr( $source, 0, $p_index + 4 );
	}


	/**
	 * Count words, characters (excluding spaces), or characters (including spaces).
	 *
	 * @param string $text      Text to count.
	 * @param string $count_type Count type: 'words', 'characters_excluding_spaces', or 'characters_including_spaces'.
	 * @return int Count of specified type.
	 */
	private function count_text( $text, $count_type ) {
		switch ( $count_type ) {
			case 'characters_excluding_spaces':
				return strlen( preg_replace( '/\s+/', '', $text ) );
			case 'characters_including_spaces':
				return strlen( $text );
			case 'words':
			default:
				return str_word_count( wp_strip_all_tags( $text ) );
		}
	}

	/**
	 * Trim characters to a specified length.
	 *
	 * @param string $text Text to trim.
	 * @param int    $max_length Maximum length of the text.
	 * @param string $count_type What is being counted. One of 'words', 'characters_excluding_spaces', or 'characters_including_spaces'.
	 * @return string Trimmed text.
	 */
	private function trim_characters( $text, $max_length, $count_type ) {
		$pure_text = wp_strip_all_tags( $text );
		$trimmed   = mb_substr( $pure_text, 0, $max_length );

		if ( 'characters_including_spaces' === $count_type ) {
			return $trimmed;
		}

		preg_match_all( '/([\s]+)/', $trimmed, $spaces );
		$space_count = ! empty( $spaces[0] ) ? count( $spaces[0] ) : 0;
		return mb_substr( $pure_text, 0, $max_length + $space_count );
	}

	/**
	 * Generates the summary text from a string of text. It's not ideal
	 * but allows keeping the editor and frontend consistent.
	 *
	 * NOTE: If editing, keep it in sync with generateSummary function from
	 * plugins/woocommerce/client/blocks/assets/js/base/components/summary/utils.ts!
	 *
	 * Once HTML API allow for HTML manipulation both functions (PHP and JS)
	 * should be updated to solution fully respecting the word count.
	 * https://github.com/woocommerce/woocommerce/issues/52835
	 *
	 * @param string $source     Source text.
	 * @param int    $max_length Limit number of items returned if text has multiple paragraphs.
	 * @return string Generated summary.
	 */
	private function generate_summary( $source, $max_length ) {
		$count_type             = wp_get_word_count_type();
		$source_with_paragraphs = wpautop( $source );
		$source_word_count      = $this->count_text( $source_with_paragraphs, $count_type );

		if ( $source_word_count <= $max_length ) {
			return $source_with_paragraphs;
		}

		$first_paragraph            = $this->get_first_paragraph( $source_with_paragraphs );
		$first_paragraph_word_count = $this->count_text( $first_paragraph, $count_type );

		if ( $first_paragraph_word_count <= $max_length ) {
			return $first_paragraph;
		}

		if ( 'words' === $count_type ) {
			return wpautop( wp_trim_words( $first_paragraph, $max_length ) );
		}

		return $this->trim_characters( $first_paragraph, $max_length, $count_type ) . '…';
	}

	/**
	 * Include and render the block.
	 *
	 * @param array    $attributes Block attributes. Default empty array.
	 * @param string   $content    Block content. Default empty string.
	 * @param WP_Block $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes, $content, $block ) {
		$post_id = $block->context['postId'] ?? '';
		$product = wc_get_product( $post_id );

		if ( ! $product ) {
			return '';
		}

		$show_description_if_empty = isset( $attributes['showDescriptionIfEmpty'] ) && $attributes['showDescriptionIfEmpty'];
		$source                    = $this->get_source( $product, $show_description_if_empty );

		if ( ! $source ) {
			return '';
		}

		$summary_length = isset( $attributes['summaryLength'] ) ? $attributes['summaryLength'] : false;
		$link_text      = isset( $attributes['linkText'] ) ? $attributes['linkText'] : '';
		$show_link      = isset( $attributes['showLink'] ) && $attributes['showLink'];
		$summary        = $summary_length ? $this->generate_summary( $source, $summary_length ) : wpautop( $source );
		$final_summary  = $show_link && $link_text ? $summary . $this->create_anchor( $product, $link_text ) : $summary;

		$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );

		return sprintf(
			'<div class="wp-block-woocommerce-product-summary"><div class="wc-block-components-product-summary %1$s" style="%2$s">
				%3$s
			</div></div>',
			esc_attr( $styles_and_classes['classes'] ),
			esc_attr( $styles_and_classes['styles'] ?? '' ),
			$final_summary
		);
	}
}
PK     \&2      .  BlockTypes/CartAcceptedPaymentMethodsBlock.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

/**
 * CartAcceptedPaymentMethodsBlock class.
 */
class CartAcceptedPaymentMethodsBlock extends AbstractInnerBlock {
	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'cart-accepted-payment-methods-block';
}
PK     \GaX  X  "  BlockTypes/AbstractProductGrid.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;

use Automattic\WooCommerce\Blocks\Utils\BlocksWpQuery;
use Automattic\WooCommerce\StoreApi\SchemaController;
use Automattic\WooCommerce\StoreApi\StoreApi;
use Automattic\WooCommerce\Enums\ProductStockStatus;

/**
 * AbstractProductGrid class.
 */
abstract class AbstractProductGrid extends AbstractDynamicBlock {

	/**
	 * Attributes.
	 *
	 * @var array
	 */
	protected $attributes = array();

	/**
	 * InnerBlocks content.
	 *
	 * @var string
	 */
	protected $content = '';

	/**
	 * Query args.
	 *
	 * @var array
	 */
	protected $query_args = array();

	/**
	 * Meta query args.
	 *
	 * @var array
	 */
	protected $meta_query = array();

	/**
	 * Get a set of attributes shared across most of the grid blocks.
	 *
	 * @return array List of block attributes with type and defaults.
	 */
	protected function get_block_type_attributes() {
		return array(
			'className'         => $this->get_schema_string(),
			'columns'           => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ),
			'rows'              => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ),
			'categories'        => $this->get_schema_list_ids(),
			'catOperator'       => array(
				'type'    => 'string',
				'default' => 'any',
			),
			'contentVisibility' => $this->get_schema_content_visibility(),
			'align'             => $this->get_schema_align(),
			'alignButtons'      => $this->get_schema_boolean( false ),
			'isPreview'         => $this->get_schema_boolean( false ),
			'stockStatus'       => array(
				'type'    => 'array',
				'default' => array_keys( wc_get_product_stock_status_options() ),
			),
		);
	}

	/**
	 * Include and render the dynamic block.
	 *
	 * @param array         $attributes Block attributes. Default empty array.
	 * @param string        $content    Block content. Default empty string.
	 * @param WP_Block|null $block      Block instance.
	 * @return string Rendered block type output.
	 */
	protected function render( $attributes = array(), $content = '', $block = null ) {
		$this->attributes = $this->parse_attributes( $attributes );
		$this->content    = $content;
		$this->query_args = $this->parse_query_args();
		$products         = array_filter( array_map( 'wc_get_product', $this->get_products() ) );

		if ( ! $products ) {
			return '';
		}

		// Prime caches to reduce future queries.
		_prime_post_caches( array_filter( array_map( fn( $product ) => (int) $product->get_image_id(), $products ) ) );

		/**
		 * Override product description to prevent infinite loop.
		 *
		 * @see https://github.com/woocommerce/woocommerce-blocks/pull/6849
		 */
		foreach ( $products as $product ) {
			$product->set_description( '' );
		}

		/**
		 * Product List Render event.
		 *
		 * Fires a WP Hook named `experimental__woocommerce_blocks-product-list-render` on render so that the client
		 * can add event handling when certain products are displayed. This can be used by tracking extensions such
		 * as Google Analytics to track impressions.
		 *
		 * Provides the list of product data (shaped like the Store API responses) and the block name.
		 */
		$this->asset_api->add_inline_script(
			'wp-hooks',
			'
			window.addEventListener( "DOMContentLoaded", () => {
				wp.hooks.doAction(
					"experimental__woocommerce_blocks-product-list-render",
					{
						products: JSON.parse( decodeURIComponent( "' . esc_js(
				rawurlencode(
					wp_json_encode(
						array_map(
							[ StoreApi::container()->get( SchemaController::class )->get( 'product' ), 'get_item_response' ],
							$products
						)
					)
				)
			) . '" ) ),
						listName: "' . esc_js( $this->block_name ) . '"
					}
				);
			} );
			',
			'after'
		);

		return sprintf(
			'<div class="%s"><ul class="wc-block-grid__products">%s</ul></div>',
			esc_attr( $this->get_container_classes() ),
			implode( '', array_map( array( $this, 'render_product' ), $products ) )
		);
	}

	/**
	 * Get the schema for the contentVisibility attribute
	 *
	 * @return array List of block attributes with type and defaults.
	 */
	protected function get_schema_content_visibility() {
		return array(
			'type'       => 'object',
			'properties' => array(
				'image'  => $this->get_schema_boolean( true ),
				'title'  => $this->get_schema_boolean( true ),
				'price'  => $this->get_schema_boolean( true ),
				'rating' => $this->get_schema_boolean( true ),
				'button' => $this->get_schema_boolean( true ),
			),
		);
	}

	/**
	 * Get the schema for the orderby attribute.
	 *
	 * @return array Property definition of `orderby` attribute.
	 */
	protected function get_schema_orderby() {
		return array(
			'type'    => 'string',
			'enum'    => array( 'date', 'popularity', 'price_asc', 'price_desc', 'rating', 'title', 'menu_order' ),
			'default' => 'date',
		);
	}

	/**
	 * Get the block's attributes.
	 *
	 * @param array $attributes Block attributes. Default empty array.
	 * @return array  Block attributes merged with defaults.
	 */
	protected function parse_attributes( $attributes ) {
		// These should match what's set in JS `registerBlockType`.
		$defaults = array(
			'columns'           => wc_get_theme_support( 'product_blocks::default_columns', 3 ),
			'rows'              => wc_get_theme_support( 'product_blocks::default_rows', 3 ),
			'alignButtons'      => false,
			'categories'        => array(),
			'catOperator'       => 'any',
			'contentVisibility' => array(
				'image'  => true,
				'title'  => true,
				'price'  => true,
				'rating' => true,
				'button' => true,
			),
			'stockStatus'       => array_keys( wc_get_product_stock_status_options() ),
		);

		return wp_parse_args( $attributes, $defaults );
	}

	/**
	 * Parse query args.
	 *
	 * @return array
	 */
	protected function parse_query_args() {
		// Store the original meta query.
		$this->meta_query = WC()->query->get_meta_query();

		$query_args = array(
			'post_type'           => 'product',
			'post_status'         => 'publish',
			'fields'              => 'ids',
			'ignore_sticky_posts' => true,
			'no_found_rows'       => false,
			'orderby'             => '',
			'order'               => '',
			'meta_query'          => $this->meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery
			'tax_query'           => array(), // phpcs:ignore WordPress.DB.SlowDBQuery
			'posts_per_page'      => $this->get_products_limit(),
		);

		$this->set_block_query_args( $query_args );
		$this->set_ordering_query_args( $query_args );
		$this->set_categories_query_args( $query_args );
		$this->set_visibility_query_args( $query_args );
		$this->set_stock_status_query_args( $query_args );

		return $query_args;
	}

	/**
	 * Parse query args.
	 *
	 * @param array $query_args Query args.
	 */
	protected function set_ordering_query_args( &$query_args ) {
		if ( isset( $this->attributes['orderby'] ) ) {
			if ( 'price_desc' === $this->attributes['orderby'] ) {
				$query_args['orderby'] = 'price';
				$query_args['order']   = 'DESC';
			} elseif ( 'price_asc' === $this->attributes['orderby'] ) {
				$query_args['orderby'] = 'price';
				$query_args['order']   = 'ASC';
			} elseif ( 'date' === $this->attributes['orderby'] ) {
				$query_args['orderby'] = 'date';
				$query_args['order']   = 'DESC';
			} else {
				$query_args['orderby'] = $this->attributes['orderby'];
			}
		}

		$query_args = array_merge(
			$query_args,
			WC()->query->get_catalog_ordering_args( $query_args['orderby'], $query_args['order'] )
		);
	}

	/**
	 * Set args specific to this block
	 *
	 * @param array $query_args Query args.
	 */
	abstract protected function set_block_query_args( &$query_args );

	/**
	 * Set categories query args.
	 *
	 * @param array $query_args Query args.
	 */
	protected function set_categories_query_args( &$query_args ) {
		if ( ! empty( $this->attributes['categories'] ) ) {
			$categories = array_map( 'absint', $this->attributes['categories'] );

			$query_args['tax_query'][] = array(
				'taxonomy'         => 'product_cat',
				'terms'            => $categories,
				'field'            => 'term_id',
				'operator'         => 'all' === $this->attributes['catOperator'] ? 'AND' : 'IN',

				/*
				 * When cat_operator is AND, the children categories should be excluded,
				 * as only products belonging to all the children categories would be selected.
				 */
				'include_children' => 'all' === $this->attributes['catOperator'] ? false : true,
			);
		}
	}

	/**
	 * Set visibility query args.
	 *
	 * @param array $query_args Query args.
	 */
	protected function set_visibility_query_args( &$query_args ) {
		$product_visibility_terms  = wc_get_product_visibility_term_ids();
		$product_visibility_not_in = array( $product_visibility_terms['exclude-from-catalog'] );

		if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
			$product_visibility_not_in[] = $product_visibility_terms[ ProductStockStatus::OUT_OF_STOCK ];
		}

		$query_args['tax_query'][] = array(
			'taxonomy' => 'product_visibility',
			'field'    => 'term_taxonomy_id',
			'terms'    => $product_visibility_not_in,
			'operator' => 'NOT IN',
		);
	}

	/**
	 * Set which stock status to use when displaying products.
	 *
	 * @param array $query_args Query args.
	 * @return void
	 */
	protected function set_stock_status_query_args( &$query_args ) {
		$stock_statuses = array_keys( wc_get_product_stock_status_options() );

		// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
		if ( isset( $this->attributes['stockStatus'] ) && $stock_statuses !== $this->attributes['stockStatus'] ) {
			// Reset meta_query then update with our stock status.
			$query_args['meta_query']   = $this->meta_query;
			$query_args['meta_query'][] = array(
				'key'     => '_stock_status',
				'value'   => array_merge( [ '' ], $this->attributes['stockStatus'] ),
				'compare' => 'IN',
			);
		} else {
			$query_args['meta_query'] = $this->meta_query;
		}
		// phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
	}

	/**
	 * Works out the item limit based on rows and columns, or returns default.
	 *
	 * @return int
	 */
	protected function get_products_limit() {
		if ( isset( $this->attributes['rows'], $this->attributes['columns'] ) && ! empty( $this->attributes['rows'] ) ) {
			$this->attributes['limit'] = intval( $this->attributes['columns'] ) * intval( $this->attributes['rows'] );
		}
		return intval( $this->attributes['limit'] );
	}

	/**
	 * Run the query and return an array of product IDs
	 *
	 * @return array List of product IDs
	 */
	protected function get_products() {
		/**
		 * Filters whether or not the product grid is cacheable.
		 *
		 * @param boolean $is_cacheable The list of script dependencies.
		 * @param array $query_args Query args for the products query passed to BlocksWpQuery.
		 * @return array True to enable cache, false to disable cache.
		 *
		 * @since 2.5.0
		 */
		$is_cacheable      = (bool) apply_filters( 'woocommerce_blocks_product_grid_is_cacheable', true, $this->query_args );
		$transient_version = \WC_Cache_Helper::get_transient_version( 'product_query' );

		$query   = new BlocksWpQuery( $this->query_args );
		$results = wp_parse_id_list( $is_cacheable ? $query->get_cached_posts( $transient_version ) : $query->get_posts() );

		// Remove ordering query arguments which may have been added by get_catalog_ordering_args.
		WC()->query->remove_ordering_args();

		if ( ! empty( $results ) ) {
			// Prime caches to reduce future queries.
			_prime_post_caches( $results );
		}

		$this->prime_product_variations( $results );

		return $results;
	}

	/**
	 * Retrieve IDs that are not already present in the cache.
	 *
	 * Based on WordPress function: _get_non_cached_ids
	 *
	 * @param int[]  $product_ids Array of IDs.
	 * @param string $cache_key  The cache bucket to check against.
	 * @return int[] Array of IDs not present in the cache.
	 */
	protected function get_non_cached_ids( $product_ids, $cache_key ) {
		$non_cached_ids = array();
		$cache_values   = wp_cache_get_multiple( $product_ids, $cache_key );

		foreach ( $cache_values as $id => $value ) {
			if ( ! $value ) {
				$non_cached_ids[] = (int) $id;
			}
		}

		return $non_cached_ids;
	}

	/**
	 * Prime query cache of product variation meta data.
	 *
	 * Prepares values in the product_ID_variation_meta_data cache for later use in the ProductSchema::get_variations()
	 * method. Doing so here reduces the total number of queries needed.
	 *
	 * @param int[] $product_ids Product ids to prime variation cache for.
	 */
	protected function prime_product_variations( $product_ids ) {
		$cache_group       = 'product_variation_meta_data';
		$prime_product_ids = $this->get_non_cached_ids( wp_parse_id_list( $product_ids ), $cache_group );

		if ( ! $prime_product_ids ) {
			return;
		}

		global $wpdb;

		// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
		$product_variations      = $wpdb->get_results( "SELECT ID as variation_id, post_parent as product_id from {$wpdb->posts} WHERE post_parent IN ( " . implode( ',', $prime_product_ids ) . ' )', ARRAY_A );
		$prime_variation_ids     = array_column( $product_variations, 'variation_id' );
		$variation_ids_by_parent = array_column( $product_variations, 'product_id', 'variation_id' );

		if ( empty( $prime_variation_ids ) ) {
			return;
		}

		$all_variation_meta_data = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT post_id as variation_id, meta_key as attribute_key, meta_value as attribute_value FROM {$wpdb->postmeta} WHERE post_id IN (" . implode( ',', array_map( 'esc_sql', $prime_variation_ids ) ) . ') AND meta_key LIKE %s',
				$wpdb->esc_like( 'attribute_' ) . '%'
			)
		);
		// phpcs:enable
		// Prepare the data to cache by indexing by the parent product.
		$primed_data = array_reduce(
			$all_variation_meta_data,
			function( $values, $data ) use ( $variation_ids_by_parent ) {
				$values[ $variation_ids_by_parent[ $data->variation_id ] ?? 0 ][] = $data;
				return $values;
			},
			array_fill_keys( $prime_product_ids, [] )
		);

		// Cache everything.
		foreach ( $primed_data as $product_id => $variation_meta_data ) {
			wp_cache_set(
				$product_id,
				[
					'last_modified' => get_the_modified_date( 'U', $product_id ),
					'data'          => $variation_meta_data,
				],
				$cache_group
			);
		}
	}

	/**
	 * Get the list of classes to apply to this block.
	 *
	 * @return string space-separated list of classes.
	 */
	protected function get_container_classes() {
		$classes = array(
			'wc-block-grid',
			"wp-block-{$this->block_name}",
			"wp-block-woocommerce-{$this->block_name}",
			"wc-block-{$this->block_name}",
			"has-{$this->attributes['columns']}-columns",
		);

		if ( $this->attributes['rows'] > 1 ) {
			$classes[] = 'has-multiple-rows';
		}

		if ( isset( $this->attributes['align'] ) ) {
			$classes[] = "align{$this->attributes['align']}";
		}

		if ( ! empty( $this->attributes['alignButtons'] ) ) {
			$classes[] = 'has-aligned-buttons';
		}

		if ( ! empty( $this->attributes['className'] ) ) {
			$classes[] = $this->attributes['className'];
		}

		return implode( ' ', $classes );
	}

	/**
	 * Render a single products.
	 *
	 * @param \WC_Product $product Product object.
	 * @return string Rendered product output.
	 */
	protected function render_product( $product ) {
		$data = (object) array(
			'permalink' => esc_url( $product->get_permalink() ),
			'image'     => $this->get_image_html( $product ),
			'title'     => $this->get_title_html( $product ),
			'rating'    => $this->get_rating_html( $product ),
			'price'     => $this->get_price_html( $product ),
			'badge'     => $this->get_sale_badge_html( $product ),
			'button'    => $this->get_button_html( $product ),
		);

		/**
		 * Filters the HTML for products in the grid.
		 *
		 * @param string $html Product grid item HTML.
		 * @param array $data Product data passed to the template.
		 * @param \WC_Product $product Product object.
		 * @return string Updated product grid item HTML.
		 *
		 * @since 2.2.0
		 */
		return apply_filters(
			'woocommerce_blocks_product_grid_item_html',
			"<li class=\"wc-block-grid__product\">
				<a href=\"{$data->permalink}\" class=\"wc-block-grid__product-link\">
					{$data->badge}
					{$data->image}
					{$data->title}
				</a>
				{$data->price}
				{$data->rating}
				{$data->button}
			</li>",
			$data,
			$product
		);
	}

	/**
	 * Get the product image.
	 *
	 * @param \WC_Product $product Product.
	 * @return string
	 */
	protected function get_image_html( $product ) {
		if ( array_key_exists( 'image', $this->attributes['contentVisibility'] ) && false === $this->attributes['contentVisibility']['image'] ) {
			return '';
		}

		$attr = array(
			'alt' => '',
		);

		if ( $product->get_image_id() ) {
			$image_alt = get_post_meta( $product->get_image_id(), '_wp_attachment_image_alt', true );
			$attr      = array(
				'alt' => ( $image_alt ? $image_alt : $product->get_name() ),
			);
		}

		return '<div class="wc-block-grid__product-image">' . $product->get_image( 'woocommerce_thumbnail', $attr ) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	}

	/**
	 * Get the product title.
	 *
	 * @param \WC_Product $product Product.
	 * @return string
	 */
	protected function get_title_html( $product ) {
		if ( empty( $this->attributes['contentVisibility']['title'] ) ) {
			return '';
		}

		return '<div class="wc-block-grid__product-title">' . wp_kses_post( $product->get_title() ) . '</div>';
	}

	/**
	 * Render the rating icons.
	 *
	 * @param WC_Product $product Product.
	 * @return string Rendered product output.
	 */
	protected function get_rating_html( $product ) {
		if ( empty( $this->attributes['contentVisibility']['rating'] ) ) {
			return '';
		}
		$rating_count = $product->get_rating_count();
		$average      = $product->get_average_rating();

		if ( $rating_count > 0 ) {
			return sprintf(
				'<div class="wc-block-grid__product-rating">%s</div>',
				wc_get_rating_html( $average, $rating_count ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			);
		}
		return '';
	}

	/**
	 * Get the price.
	 *
	 * @param \WC_Product $product Product.
	 * @return string Rendered product output.
	 */
	protected function get_price_html( $product ) {
		if ( empty( $this->attributes['contentVisibility']['price'] ) ) {
			return '';
		}
		return sprintf(
			'<div class="wc-block-grid__product-price price">%s</div>',
			wp_kses_post( $product->get_price_html() )
		);
	}

	/**
	 * Get the sale badge.
	 *
	 * @param \WC_Product $product Product.
	 * @return string Rendered product output.
	 */
	protected function get_sale_badge_html( $product ) {
		if ( empty( $this->attributes['contentVisibility']['price'] ) ) {
			return '';
		}

		if ( empty( $this->attributes['contentVisibility']['image'] ) ) {
			return '';
		}

		if ( ! $product->is_on_sale() ) {
			return;
		}

		return '<div class="wc-block-grid__product-onsale">
			<span aria-hidden="true">' . esc_html__( 'Sale', 'woocommerce' ) . '</span>
			<span class="screen-reader-text">' . esc_html__( 'Product on sale', 'woocommerce' ) . '</span>
		</div>';
	}

	/**
	 * Get the button.
	 *
	 * @param \WC_Product $product Product.
	 * @return string Rendered product output.
	 */
	protected function get_button_html( $product ) {
		if ( empty( $this->attributes['contentVisibility']['button'] ) ) {
			return '';
		}
		return '<div class="wp-block-button wc-block-grid__product-add-to-cart">' . $this->get_add_to_cart( $product ) . '</div>';
	}

	/**
	 * Get the "add to cart" button.
	 *
	 * @param \WC_Product $product Product.
	 * @return string Rendered product output.
	 */
	protected function get_add_to_cart( $product ) {
		$attributes = array(
			'aria-label'       => $product->add_to_cart_description(),
			'data-quantity'    => '1',
			'data-product_id'  => $product->get_id(),
			'data-product_sku' => $product->get_sku(),
			'data-price'       => wc_get_price_to_display( $product ),
			'rel'              => 'nofollow',
			'class'            => 'wp-block-button__link ' . ( function_exists( 'wc_wp_theme_get_element_class_name' ) ? wc_wp_theme_get_element_class_name( 'button' ) : '' ) . ' add_to_cart_button',
		);

		if (
			$product->supports( 'ajax_add_to_cart' ) &&
			$product->is_purchasable() &&
			( $product->is_in_stock() || $product->backorders_allowed() )
		) {
			$attributes['class'] .= ' ajax_add_to_cart';
		}

		/**
		 * Filter to manipulate (add/modify/remove) attributes in the HTML code of the generated add to cart button.
		 *
		 * @since 8.6.0
		 *
		 * @param array      $attributes An associative array containing default HTML attributes of the add to cart button.
		 * @param WC_Product $product    The WC_Product instance of the product that will be added to the cart once the button is pressed.
		 *
		 * @return array Returns an associative array derived from the default array passed as an argument and added the extra HTML attributes.
		 */
		$attributes = apply_filters( 'woocommerce_blocks_product_grid_add_to_cart_attributes', $attributes, $product );

		return sprintf(
			'<a href="%s" %s>%s</a>',
			esc_url( $product->add_to_cart_url() ),
			wc_implode_html_attributes( $attributes ),
			esc_html( $product->add_to_cart_text() )
		);
	}

	/**
	 * Extra data passed through from server to client for block.
	 *
	 * @param array $attributes  Any attributes that currently are available from the block.
	 *                           Note, this will be empty in the editor context when the block is
	 *                           not in the post content on editor load.
	 */
	protected function enqueue_data( array $attributes = [] ) {
		parent::enqueue_data( $attributes );
		$this->asset_data_registry->add( 'minColumns', wc_get_theme_support( 'product_blocks::min_columns', 1 ) );
		$this->asset_data_registry->add( 'maxColumns', wc_get_theme_support( 'product_blocks::max_columns', 6 ) );
		$this->asset_data_registry->add( 'defaultColumns', wc_get_theme_support( 'product_blocks::default_columns', 3 ) );
		$this->asset_data_registry->add( 'minRows', wc_get_theme_support( 'product_blocks::min_rows', 1 ) );
		$this->asset_data_registry->add( 'maxRows', wc_get_theme_support( 'product_blocks::max_rows', 6 ) );
		$this->asset_data_registry->add( 'defaultRows', wc_get_theme_support( 'product_blocks::default_rows', 3 ) );
	}

	/**
	 * Get the frontend style handle for this block type.
	 *
	 * @return string[]
	 */
	protected function get_block_type_style() {
		// Currently these blocks rely on the styles from the All Products block.
		return [ 'wc-blocks-style', 'wc-blocks-style-all-products' ];
	}
}
PK     \CO    
  Assets.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks;

use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;

/**
 * Assets class.
 *
 * @deprecated 5.0.0 This class will be removed in a future release. This has been replaced by AssetsController.
 * @internal
 */
class Assets {

	/**
	 * Initialize class features on init.
	 *
	 * @since 2.5.0
	 * @deprecated 5.0.0
	 */
	public static function init() {
		_deprecated_function( 'Assets::init', '5.0.0' );
	}

	/**
	 * Register block scripts & styles.
	 *
	 * @since 2.5.0
	 * @deprecated 5.0.0
	 */
	public static function register_assets() {
		_deprecated_function( 'Assets::register_assets', '5.0.0' );
	}

	/**
	 * Register the vendors style file. We need to do it after the other files
	 * because we need to check if `wp-edit-post` has been enqueued.
	 *
	 * @deprecated 5.0.0
	 */
	public static function enqueue_scripts() {
		_deprecated_function( 'Assets::enqueue_scripts', '5.0.0' );
	}

	/**
	 * Add body classes.
	 *
	 * @deprecated 5.0.0
	 * @param array $classes Array of CSS classnames.
	 * @return array Modified array of CSS classnames.
	 */
	public static function add_theme_body_class( $classes = [] ) {
		_deprecated_function( 'Assets::add_theme_body_class', '5.0.0' );
		return $classes;
	}

	/**
	 * Add theme class to admin body.
	 *
	 * @deprecated 5.0.0
	 * @param array $classes String with the CSS classnames.
	 * @return array Modified string of CSS classnames.
	 */
	public static function add_theme_admin_body_class( $classes = '' ) {
		_deprecated_function( 'Assets::add_theme_admin_body_class', '5.0.0' );
		return $classes;
	}

	/**
	 * Adds a redirect field to the login form so blocks can redirect users after login.
	 *
	 * @deprecated 5.0.0
	 */
	public static function redirect_to_field() {
		_deprecated_function( 'Assets::redirect_to_field', '5.0.0' );
	}

	/**
	 * Queues a block script in the frontend.
	 *
	 * @since 2.3.0
	 * @since 2.6.0 Changed $name to $script_name and added $handle argument.
	 * @since 2.9.0 Made it so scripts are not loaded in admin pages.
	 * @deprecated 4.5.0 Block types register the scripts themselves.
	 *
	 * @param string $script_name  Name of the script used to identify the file inside build folder.
	 * @param string $handle       Optional. Provided if the handle should be different than the script name. `wc-` prefix automatically added.
	 * @param array  $dependencies Optional. An array of registered script handles this script depends on. Default empty array.
	 */
	public static function register_block_script( $script_name, $handle = '', $dependencies = [] ) {
		_deprecated_function( 'register_block_script', '4.5.0' );
		$asset_api = Package::container()->get( AssetApi::class );
		$asset_api->register_block_script( $script_name, $handle, $dependencies );
	}
}
PK     \yu d   d    BlockTypesController.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks;

use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
use Automattic\WooCommerce\Blocks\BlockTypes\Cart;
use Automattic\WooCommerce\Blocks\BlockTypes\Checkout;
use Automattic\WooCommerce\Blocks\BlockTypes\MiniCartContents;

/**
 * BlockTypesController class.
 *
 * @since 5.0.0
 * @internal
 */
final class BlockTypesController {

	/**
	 * Instance of the asset API.
	 *
	 * @var AssetApi
	 */
	protected $asset_api;

	/**
	 * Instance of the asset data registry.
	 *
	 * @var AssetDataRegistry
	 */
	protected $asset_data_registry;

	/**
	 * Holds the registered blocks that have WooCommerce blocks as their parents.
	 *
	 * @var array List of registered blocks.
	 */
	private $registered_blocks_with_woocommerce_parents;

	/**
	 * Constructor.
	 *
	 * @param AssetApi          $asset_api Instance of the asset API.
	 * @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
	 */
	public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry ) {
		$this->asset_api           = $asset_api;
		$this->asset_data_registry = $asset_data_registry;
		$this->init();
	}

	/**
	 * Initialize class features.
	 */
	protected function init() { // phpcs:ignore WooCommerce.Functions.InternalInjectionMethod.MissingPublic
		add_action( 'init', array( $this, 'register_blocks' ) );
		add_action( 'wp_loaded', array( $this, 'register_block_patterns' ) );
		add_filter( 'block_categories_all', array( $this, 'register_block_categories' ), 10, 2 );
		add_filter( 'render_block', array( $this, 'add_data_attributes' ), 10, 2 );
		add_action( 'woocommerce_login_form_end', array( $this, 'redirect_to_field' ) );
		add_filter( 'widget_types_to_hide_from_legacy_widget_block', array( $this, 'hide_legacy_widgets_with_block_equivalent' ) );
		add_filter( 'register_block_type_args', array( $this, 'enqueue_block_style_for_classic_themes' ), 10, 2 );
		add_filter( 'block_core_breadcrumbs_post_type_settings', array( $this, 'set_product_breadcrumbs_preferred_taxonomy' ), 10, 3 );
		add_filter( 'block_core_breadcrumbs_items', array( $this, 'apply_woocommerce_breadcrumb_filters' ), 10, 1 );
	}

	/**
	 * Get registered blocks that have WooCommerce blocks as their parents. Adds the value to the
	 * `registered_blocks_with_woocommerce_parents` cache if `init` has been fired.
	 *
	 * @return array Registered blocks with WooCommerce blocks as parents.
	 */
	public function get_registered_blocks_with_woocommerce_parent() {
		// If init has run and the cache is already set, return it.
		if ( did_action( 'init' ) && ! empty( $this->registered_blocks_with_woocommerce_parents ) ) {
			return $this->registered_blocks_with_woocommerce_parents;
		}

		$registered_blocks = \WP_Block_Type_Registry::get_instance()->get_all_registered();

		if ( ! is_array( $registered_blocks ) ) {
			return array();
		}

		$this->registered_blocks_with_woocommerce_parents = array_filter(
			$registered_blocks,
			function ( $block ) {
				if ( empty( $block->parent ) ) {
					return false;
				}
				if ( ! is_array( $block->parent ) ) {
					$block->parent = array( $block->parent );
				}
				$woocommerce_blocks = array_filter(
					$block->parent,
					function ( $parent_block_name ) {
						return 'woocommerce' === strtok( $parent_block_name, '/' );
					}
				);
				return ! empty( $woocommerce_blocks );
			}
		);
		return $this->registered_blocks_with_woocommerce_parents;
	}

	/**
	 * Register blocks, hooking up assets and render functions as needed.
	 */
	public function register_blocks() {
		$this->register_block_metadata();
		$block_types = $this->get_block_types();

		foreach ( $block_types as $block_type ) {
			$block_type_class = __NAMESPACE__ . '\\BlockTypes\\' . $block_type;

			new $block_type_class( $this->asset_api, $this->asset_data_registry, new IntegrationRegistry() );
		}
	}

	/**
	 * Register block metadata collections for WooCommerce blocks.
	 *
	 * This method handles the registration of block metadata by using WordPress's block metadata
	 * collection registration system. It includes a temporary workaround for WordPress 6.7's
	 * strict path validation that might fail for sites using symlinked plugins.
	 *
	 * If the registration fails due to path validation, blocks will fall back to regular
	 * registration without affecting functionality.
	 */
	public function register_block_metadata() {
		$meta_file_path = WC_ABSPATH . 'assets/client/blocks/blocks-json.php';
		if ( function_exists( 'wp_register_block_metadata_collection' ) && file_exists( $meta_file_path ) ) {
			add_filter( 'doing_it_wrong_trigger_error', array( __CLASS__, 'bypass_block_metadata_doing_it_wrong' ), 10, 4 );
			wp_register_block_metadata_collection(
				WC_ABSPATH . 'assets/client/blocks/',
				$meta_file_path
			);
			remove_filter( 'doing_it_wrong_trigger_error', array( __CLASS__, 'bypass_block_metadata_doing_it_wrong' ), 10 );
		}
	}

	/**
	 * Temporarily bypasses _doing_it_wrong() notices for block metadata collection registration.
	 *
	 * WordPress 6.7 introduced block metadata collections (with strict path validation).
	 * Any sites using symlinks for plugins will fail the validation which causes the metadata
	 * collection to not be registered. However, the blocks will still fall back to the regular
	 * registration and no functionality is affected.
	 * While this validation is being discussed in WordPress Core (#62140),
	 * this method allows registration to proceed by temporarily disabling
	 * the relevant notice.
	 *
	 * @param bool   $trigger       Whether to trigger the error.
	 * @param string $function      The function that was called.
	 * @param string $message       A message explaining what was done incorrectly.
	 * @param string $version       The version of WordPress where the message was added.
	 * @return bool Whether to trigger the error.
	 */
	public static function bypass_block_metadata_doing_it_wrong( $trigger, $function, $message, $version ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable,Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed,Universal.NamingConventions.NoReservedKeywordParameterNames.functionFound
		if ( 'WP_Block_Metadata_Registry::register_collection' === $function ) {
			return false;
		}
		return $trigger;
	}

	/**
	 * Register block patterns
	 */
	public function register_block_patterns() {
		register_block_pattern(
			'woocommerce/order-confirmation-totals-heading',
			array(
				'title'    => '',
				'inserter' => false,
				'content'  => '<!-- wp:heading {"level":2,"style":{"typography":{"fontSize":"24px"}}} --><h2 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Order details', 'woocommerce' ) . '</h2><!-- /wp:heading -->',
			)
		);
		register_block_pattern(
			'woocommerce/order-confirmation-downloads-heading',
			array(
				'title'    => '',
				'inserter' => false,
				'content'  => '<!-- wp:heading {"level":2,"style":{"typography":{"fontSize":"24px"}}} --><h2 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Downloads', 'woocommerce' ) . '</h2><!-- /wp:heading -->',
			)
		);
		register_block_pattern(
			'woocommerce/order-confirmation-shipping-heading',
			array(
				'title'    => '',
				'inserter' => false,
				'content'  => '<!-- wp:heading {"level":2,"style":{"typography":{"fontSize":"24px"}}} --><h2 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Shipping address', 'woocommerce' ) . '</h2><!-- /wp:heading -->',
			)
		);
		register_block_pattern(
			'woocommerce/order-confirmation-billing-heading',
			array(
				'title'    => '',
				'inserter' => false,
				'content'  => '<!-- wp:heading {"level":2,"style":{"typography":{"fontSize":"24px"}}} --><h2 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Billing address', 'woocommerce' ) . '</h2><!-- /wp:heading -->',
			)
		);
		register_block_pattern(
			'woocommerce/order-confirmation-additional-fields-heading',
			array(
				'title'    => '',
				'inserter' => false,
				'content'  => '<!-- wp:heading {"level":2,"style":{"typography":{"fontSize":"24px"}}} --><h2 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Additional information', 'woocommerce' ) . '</h2><!-- /wp:heading -->',
			)
		);
	}

	/**
	 * Register block categories
	 *
	 * Used in combination with the `block_categories_all` filter, to append
	 * WooCommerce Blocks related categories to the Gutenberg editor.
	 *
	 * @param array $categories The array of already registered categories.
	 */
	public function register_block_categories( $categories ) {
		$woocommerce_block_categories = array(
			array(
				'slug'  => 'woocommerce',
				'title' => __( 'WooCommerce', 'woocommerce' ),
			),
			array(
				'slug'  => 'woocommerce-product-elements',
				'title' => __( 'WooCommerce Product Elements', 'woocommerce' ),
			),
		);

		return array_merge( $categories, $woocommerce_block_categories );
	}

	/**
	 * Check if a block should have data attributes appended on render. If it's in an allowed namespace, or the block
	 * has explicitly been added to the allowed block list, or if one of the block's parents is in the WooCommerce
	 * namespace it can have data attributes.
	 *
	 * @param string $block_name Name of the block to check.
	 *
	 * @return boolean
	 */
	public function block_should_have_data_attributes( $block_name ) {
		$block_namespace = strtok( $block_name ?? '', '/' );

		/**
		 * Filters the list of allowed block namespaces.
		 *
		 * This hook defines which block namespaces should have block name and attribute `data-` attributes appended on render.
		 *
		 * @since 5.9.0
		 *
		 * @param array $allowed_namespaces List of namespaces.
		 */
		$allowed_namespaces = array_merge( array( 'woocommerce', 'woocommerce-checkout' ), (array) apply_filters( '__experimental_woocommerce_blocks_add_data_attributes_to_namespace', array() ) );

		/**
		 * Filters the list of allowed Block Names
		 *
		 * This hook defines which block names should have block name and attribute data- attributes appended on render.
		 *
		 * @since 5.9.0
		 *
		 * @param array $allowed_namespaces List of namespaces.
		 */
		$allowed_blocks = (array) apply_filters( '__experimental_woocommerce_blocks_add_data_attributes_to_block', array() );

		$blocks_with_woo_parents   = $this->get_registered_blocks_with_woocommerce_parent();
		$block_has_woo_parent      = in_array( $block_name, array_keys( $blocks_with_woo_parents ), true );
		$in_allowed_namespace_list = in_array( $block_namespace, $allowed_namespaces, true );
		$in_allowed_block_list     = in_array( $block_name, $allowed_blocks, true );

		return $block_has_woo_parent || $in_allowed_block_list || $in_allowed_namespace_list;
	}

	/**
	 * Add data- attributes to blocks when rendered if the block is under the woocommerce/ namespace.
	 *
	 * @param string $content Block content.
	 * @param array  $block Parsed block data.
	 * @return string
	 */
	public function add_data_attributes( $content, $block ) {

		if ( ! is_string( $content ) || ! $this->block_should_have_data_attributes( $block['blockName'] ) ) {
			return $content;
		}

		$attributes         = (array) $block['attrs'];
		$exclude_attributes = array( 'className', 'align' );

		$processor = new \WP_HTML_Tag_Processor( $content );

		if (
			false === $processor->next_tag() || $processor->is_tag_closer()
		) {

			return $content;
		}

		foreach ( $attributes as $key  => $value ) {
			if ( ! is_string( $key ) || in_array( $key, $exclude_attributes, true ) ) {
				continue;
			}
			if ( is_bool( $value ) ) {
				$value = $value ? 'true' : 'false';
			}
			if ( ! is_scalar( $value ) ) {
				$value = wp_json_encode( $value );
			}

			// For output consistency, we convert camelCase to kebab-case and output in lowercase.
			$key = strtolower( preg_replace( '/(?<!^|\ )[A-Z]/', '-$0', $key ) );

			$processor->set_attribute( "data-{$key}", $value );
		}

		// Set this last to prevent user-input from overriding it.
		$processor->set_attribute( 'data-block-name', $block['blockName'] );
		return $processor->get_updated_html();
	}

	/**
	 * Adds a redirect field to the login form so blocks can redirect users after login.
	 */
	public function redirect_to_field() {
		// phpcs:ignore WordPress.Security.NonceVerification
		if ( empty( $_GET['redirect_to'] ) ) {
			return;
		}
		echo '<input type="hidden" name="redirect" value="' . esc_attr( esc_url_raw( wp_unslash( $_GET['redirect_to'] ) ) ) . '" />'; // phpcs:ignore WordPress.Security.NonceVerification
	}

	/**
	 * Hide legacy widgets with a feature complete block equivalent in the inserter
	 * and prevent them from showing as an option in the Legacy Widget block.
	 *
	 * @param array $widget_types An array of widgets hidden in core.
	 * @return array $widget_types An array including the WooCommerce widgets to hide.
	 */
	public function hide_legacy_widgets_with_block_equivalent( $widget_types ) {
		array_push(
			$widget_types,
			'woocommerce_product_search',
			'woocommerce_product_categories',
			'woocommerce_recent_reviews',
			'woocommerce_product_tag_cloud',
			'woocommerce_price_filter',
			'woocommerce_layered_nav',
			'woocommerce_layered_nav_filters',
			'woocommerce_rating_filter'
		);

		return $widget_types;
	}

	/**
	 * Delete product transients when a product is deleted.
	 *
	 * @deprecated since 10.6.0
	 * @return void
	 */
	public function delete_product_transients() {
		wc_deprecated_function( __METHOD__, '10.6.0' );
	}

	/**
	 * Get list of block types allowed in Widget Areas. New blocks won't be
	 * exposed in the Widget Area unless specifically added here.
	 *
	 * @return array Array of block types.
	 */
	protected function get_widget_area_block_types() {
		return array(
			'AllReviews',
			'Breadcrumbs',
			'CartLink',
			'CatalogSorting',
			'ClassicShortcode',
			'CustomerAccount',
			'FeaturedCategory',
			'FeaturedProduct',
			'MiniCart',
			'ProductCategories',
			'ProductResultsCount',
			'ProductSearch',
			'ReviewsByCategory',
			'ReviewsByProduct',
			'ProductFilters',
			'ProductFilterStatus',
			'ProductFilterPrice',
			'ProductFilterPriceSlider',
			'ProductFilterAttribute',
			'ProductFilterRating',
			'ProductFilterActive',
			'ProductFilterRemovableChips',
			'ProductFilterClearButton',
			'ProductFilterCheckboxList',
			'ProductFilterChips',
			'ProductFilterTaxonomy',

			// Keep hidden legacy filter blocks for backward compatibility.
			'ActiveFilters',
			'AttributeFilter',
			'FilterWrapper',
			'PriceFilter',
			'RatingFilter',
			'StockFilter',
			// End: legacy filter blocks.

			// Below product grids are hidden from inserter however they could have been used in widgets.
			// Keep them for backward compatibility.
			'HandpickedProducts',
			'ProductBestSellers',
			'ProductNew',
			'ProductOnSale',
			'ProductTopRated',
			'ProductsByAttribute',
			'ProductCategory',
			'ProductTag',
			// End: legacy product grids blocks.
		);
	}

	/**
	 * Get list of block types.
	 *
	 * @return array
	 */
	protected function get_block_types() {
		global $pagenow;

		$block_types = array(
			'ActiveFilters',
			'AddToCartForm',
			'AllProducts',
			'AllReviews',
			'AttributeFilter',
			'Breadcrumbs',
			'CartLink',
			'CatalogSorting',
			'CategoryTitle',
			'CategoryDescription',
			'ClassicTemplate',
			'ClassicShortcode',
			'ComingSoon',
			'CouponCode',
			'CustomerAccount',
			'EmailContent',
			'FeaturedCategory',
			'FeaturedProduct',
			'FilterWrapper',
			'HandpickedProducts',
			'MiniCart',
			'NextPreviousButtons',
			'StoreNotices',
			'PaymentMethodIcons',
			'PriceFilter',
			'ProductBestSellers',
			'ProductButton',
			'ProductCategories',
			'ProductCategory',
			'ProductCollection\Controller',
			'ProductCollection\NoResults',
			'ProductFilters',
			'ProductFilterStatus',
			'ProductFilterPrice',
			'ProductFilterPriceSlider',
			'ProductFilterAttribute',
			'ProductFilterRating',
			'ProductFilterActive',
			'ProductFilterRemovableChips',
			'ProductFilterClearButton',
			'ProductFilterCheckboxList',
			'ProductFilterChips',
			'ProductFilterTaxonomy',
			'ProductGallery',
			'ProductGalleryLargeImage',
			'ProductGalleryThumbnails',
			'ProductImage',
			'ProductImageGallery',
			'ProductMeta',
			'ProductNew',
			'ProductOnSale',
			'ProductPrice',
			'ProductTemplate',
			'ProductQuery',
			'ProductAverageRating',
			'ProductRating',
			'ProductRatingCounter',
			'ProductRatingStars',
			'ProductResultsCount',
			'ProductSaleBadge',
			'ProductSearch',
			'ProductSKU',
			'ProductStockIndicator',
			'ProductSummary',
			'ProductTag',
			'ProductTitle',
			'ProductTopRated',
			'ProductsByAttribute',
			'RatingFilter',
			'ReviewsByCategory',
			'ReviewsByProduct',
			'RelatedProducts',
			'SingleProduct',
			'StockFilter',
			'PageContentWrapper',
			'OrderConfirmation\Status',
			'OrderConfirmation\Summary',
			'OrderConfirmation\Totals',
			'OrderConfirmation\TotalsWrapper',
			'OrderConfirmation\Downloads',
			'OrderConfirmation\DownloadsWrapper',
			'OrderConfirmation\BillingAddress',
			'OrderConfirmation\ShippingAddress',
			'OrderConfirmation\BillingWrapper',
			'OrderConfirmation\ShippingWrapper',
			'OrderConfirmation\AdditionalInformation',
			'OrderConfirmation\AdditionalFieldsWrapper',
			'OrderConfirmation\AdditionalFields',
			'OrderConfirmation\CreateAccount',
			'ProductDetails',
			'ProductDescription',
			'ProductSpecifications',
			// Generic blocks that will be pushed upstream.
			'Accordion\AccordionGroup',
			'Accordion\AccordionItem',
			'Accordion\AccordionPanel',
			'Accordion\AccordionHeader',
			// End: generic blocks that will be pushed upstream.
			'Reviews\ProductReviews',
			'Reviews\ProductReviewRating',
			'Reviews\ProductReviewsTitle',
			'Reviews\ProductReviewForm',
			'Reviews\ProductReviewDate',
			'Reviews\ProductReviewContent',
			'Reviews\ProductReviewAuthorName',
			'Reviews\ProductReviewsPagination',
			'Reviews\ProductReviewsPaginationNext',
			'Reviews\ProductReviewsPaginationPrevious',
			'Reviews\ProductReviewsPaginationNumbers',
			'Reviews\ProductReviewTemplate',
		);

		$block_types = array_merge(
			$block_types,
			Cart::get_cart_block_types(),
			Checkout::get_checkout_block_types(),
			MiniCartContents::get_mini_cart_block_types()
		);

		if ( wp_is_block_theme() ) {
			$block_types[] = 'AddToCartWithOptions\AddToCartWithOptions';
			$block_types[] = 'AddToCartWithOptions\QuantitySelector';
			$block_types[] = 'AddToCartWithOptions\VariationDescription';
			$block_types[] = 'AddToCartWithOptions\VariationSelector';
			$block_types[] = 'AddToCartWithOptions\VariationSelectorAttribute';
			$block_types[] = 'AddToCartWithOptions\VariationSelectorAttributeName';
			$block_types[] = 'AddToCartWithOptions\VariationSelectorAttributeOptions';
			$block_types[] = 'AddToCartWithOptions\GroupedProductSelector';
			$block_types[] = 'AddToCartWithOptions\GroupedProductItem';
			$block_types[] = 'AddToCartWithOptions\GroupedProductItemSelector';
			$block_types[] = 'AddToCartWithOptions\GroupedProductItemLabel';
		}

		/**
		 * This enables specific blocks in Widget Areas using an opt-in approach.
		 */
		if ( in_array( $pagenow, array( 'widgets.php', 'themes.php', 'customize.php' ), true ) && ( empty( $_GET['page'] ) || 'gutenberg-edit-site' !== $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
			$block_types = array_intersect(
				$block_types,
				$this->get_widget_area_block_types()
			);
		}

		/**
		 * This disables specific blocks in Post and Page editor by not registering them.
		 */
		if ( in_array( $pagenow, array( 'post.php', 'post-new.php' ), true ) ) {
			$block_types = array_diff(
				$block_types,
				array(
					'Breadcrumbs',
					'CatalogSorting',
					'ClassicTemplate',
					'ProductResultsCount',
					'ProductReviews',
					'OrderConfirmation\Status',
					'OrderConfirmation\Summary',
					'OrderConfirmation\Totals',
					'OrderConfirmation\TotalsWrapper',
					'OrderConfirmation\Downloads',
					'OrderConfirmation\DownloadsWrapper',
					'OrderConfirmation\BillingAddress',
					'OrderConfirmation\ShippingAddress',
					'OrderConfirmation\BillingWrapper',
					'OrderConfirmation\ShippingWrapper',
					'OrderConfirmation\AdditionalInformation',
					'OrderConfirmation\AdditionalFieldsWrapper',
					'OrderConfirmation\AdditionalFields',
				)
			);
		}

		/**
		 * Filters the list of allowed block types.
		 *
		 * @since 9.0.0
		 *
		 * @param array $block_types List of block types.
		 */
		return apply_filters( 'woocommerce_get_block_types', $block_types );
	}

	/**
	 * By default, when the classic theme is used, block style is always
	 * enqueued even if the block is not used on the page. We want WooCommerce
	 * store to always performant so we have to manually enqueue the block style
	 * on-demand for classic themes.
	 *
	 * @internal
	 *
	 * @param array  $args Block metadata.
	 * @param string $block_name Block name.
	 *
	 * @return array Block metadata.
	 */
	public function enqueue_block_style_for_classic_themes( $args, $block_name ) {

		// Repeatedly checking the theme is expensive. So statically cache this logic result and remove the filter if not needed.
		static $should_enqueue_block_style_for_classic_themes = null;
		if ( null === $should_enqueue_block_style_for_classic_themes ) {
			$should_enqueue_block_style_for_classic_themes = ! (
				is_admin() ||
				wp_is_block_theme() ||
				( function_exists( 'wp_should_load_block_assets_on_demand' ) && wp_should_load_block_assets_on_demand() ) ||
				wp_should_load_separate_core_block_assets()
			);
		}
		if ( ! $should_enqueue_block_style_for_classic_themes ) {
			remove_filter( 'register_block_type_args', array( $this, 'enqueue_block_style_for_classic_themes' ), 10 );

			return $args;
		}

		if (
			false === strpos( $block_name, 'woocommerce/' ) ||
			( empty( $args['style_handles'] ) && empty( $args['style'] )
			)
		) {
			return $args;
		}

		$style_handlers = $args['style_handles'] ?? $args['style'];

		add_filter(
			'render_block_' . $block_name,
			static function ( $html ) use ( $style_handlers ) {
				array_map( 'wp_enqueue_style', $style_handlers );

				return $html;
			},
			10
		);

		$args['style_handles'] = array();
		$args['style']         = array();

		return $args;
	}

	/**
	 * Set the preferred taxonomy and term for the breadcrumbs block on the product post type.
	 *
	 * This method mimics the behavior of WC_Breadcrumb::add_crumbs_single() to ensure
	 * consistent breadcrumb term selection between WooCommerce's legacy breadcrumbs
	 * and the Core breadcrumbs block.
	 *
	 * @param array  $settings The settings for the breadcrumbs block.
	 * @param string $post_type The post type.
	 * @param int    $post_id The current post ID.
	 * @return array The settings for the breadcrumbs block.
	 *
	 * @internal
	 */
	public function set_product_breadcrumbs_preferred_taxonomy( $settings, $post_type, $post_id = 0 ) {
		if ( ! is_array( $settings ) || 'product' !== $post_type ) {
			return $settings;
		}

		$settings['taxonomy'] = 'product_cat';

		// If we have a post ID, determine the specific term using WooCommerce's logic.
		if ( ! empty( $post_id ) ) {
			$terms = wc_get_product_terms(
				$post_id,
				'product_cat',
				/**
				 * Filters the arguments used to fetch product terms for breadcrumbs.
				 *
				 * @since 9.5.0
				 *
				 * @param array $args Array of arguments for `wc_get_product_terms()`.
				 */
				apply_filters(
					'woocommerce_breadcrumb_product_terms_args',
					array(
						'orderby' => 'parent',
						'order'   => 'DESC',
					)
				)
			);

			if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) {
				/**
				 * Filters the main term used in product breadcrumbs.
				 *
				 * @since 9.5.0
				 *
				 * @param \WP_Term   $main_term The main term to be used in breadcrumbs.
				 * @param \WP_Term[] $terms     Array of all product category terms.
				 */
				$main_term = apply_filters( 'woocommerce_breadcrumb_main_term', $terms[0], $terms );

				if ( $main_term instanceof \WP_Term ) {
					$settings['term'] = $main_term->slug;
				}
			}
		}

		return $settings;
	}

	/**
	 * Apply WooCommerce breadcrumb filters to Core breadcrumbs block items.
	 *
	 * This bridges the Core breadcrumbs block with WooCommerce's legacy breadcrumb filters,
	 * ensuring backward compatibility for sites that have customized breadcrumbs using
	 * the `woocommerce_get_breadcrumb` filter.
	 *
	 * @param array $items Array of breadcrumb items from Core.
	 * @return array Modified breadcrumb items.
	 *
	 * @internal
	 */
	public function apply_woocommerce_breadcrumb_filters( $items ) {
		// Convert Core format to WooCommerce format.
		// Core: array( 'url' => '...', 'label' => '...' )
		// Woo: array( 'label', 'url' ).
		$wc_crumbs = array_map(
			function ( $item ) {
				return array(
					$item['label'] ?? '',
					$item['url'] ?? '',
				);
			},
			$items
		);

		/**
		 * Filters the breadcrumb trail array.
		 *
		 * @since 2.3.0
		 *
		 * @param array         $crumbs The breadcrumb trail.
		 * @param \WC_Breadcrumb|null $breadcrumb The breadcrumb object (null when called from Core block).
		 */
		$wc_crumbs = apply_filters( 'woocommerce_get_breadcrumb', $wc_crumbs, null );

		// Convert back to Core format.
		return array_map(
			function ( $crumb ) {
				return array(
					'label' => $crumb[0] ?? '',
					'url'   => $crumb[1] ?? '',
				);
			},
			$wc_crumbs
		);
	}
}
PK     \ry3G  3G    QueryFilters.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks;

use WC_Tax;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;

/**
 * Process the query data for filtering purposes.
 */
final class QueryFilters {
	/**
	 * Initialization method.
	 *
	 * @internal
	 */
	public function init() {}

	/**
	 * Filter the posts clauses of the main query to support global filters.
	 *
	 * @param array     $args     Query args.
	 * @param \WP_Query $wp_query WP_Query object.
	 * @return array
	 */
	public function main_query_filter( $args, $wp_query ) {
		if (
			! $wp_query->is_main_query() ||
			'product_query' !== $wp_query->get( 'wc_query' )
		) {
			return $args;
		}

		if ( $wp_query->get( 'filter_stock_status' ) ) {
			$args = $this->stock_filter_clauses( $args, $wp_query );
		}

		return $args;
	}

	/**
	 * Add conditional query clauses based on the filter params in query vars.
	 *
	 * @param array     $args     Query args.
	 * @param \WP_Query $wp_query WP_Query object.
	 * @return array
	 */
	public function add_query_clauses( $args, $wp_query ) {
		$args = $this->stock_filter_clauses( $args, $wp_query );
		$args = $this->price_filter_clauses( $args, $wp_query );
		$args = $this->attribute_filter_clauses( $args, $wp_query );

		return $args;
	}

	/**
	 * Get price data for current products.
	 *
	 * @param array $query_vars The WP_Query arguments.
	 * @return object
	 */
	public function get_filtered_price( $query_vars ) {
		global $wpdb;

		add_filter( 'posts_clauses', array( $this, 'add_query_clauses' ), 10, 2 );
		add_filter( 'posts_pre_query', '__return_empty_array' );

		$query_vars['no_found_rows']  = true;
		$query_vars['posts_per_page'] = -1;
		$query_vars['fields']         = 'ids';
		$query                        = new \WP_Query();
		$query->query( $query_vars );
		$product_query_sql = $query->request;

		remove_filter( 'posts_clauses', array( $this, 'add_query_clauses' ), 10 );
		remove_filter( 'posts_pre_query', '__return_empty_array' );

		$price_filter_sql = "
		SELECT min( min_price ) as min_price, MAX( max_price ) as max_price
		FROM {$wpdb->wc_product_meta_lookup}
		WHERE product_id IN ( {$product_query_sql} )
		";

		return $wpdb->get_row( $price_filter_sql ); // phpcs:ignore
	}

	/**
	 * Get stock status counts for the current products.
	 *
	 * @param array $query_vars The WP_Query arguments.
	 * @return array status=>count pairs.
	 */
	public function get_stock_status_counts( $query_vars ) {
		global $wpdb;
		$stock_status_options = array_map( 'esc_sql', array_keys( wc_get_product_stock_status_options() ) );

		add_filter( 'posts_clauses', array( $this, 'add_query_clauses' ), 10, 2 );
		add_filter( 'posts_pre_query', '__return_empty_array' );

		$query_vars['no_found_rows']  = true;
		$query_vars['posts_per_page'] = -1;
		$query_vars['fields']         = 'ids';
		$query                        = new \WP_Query();
		$result                       = $query->query( $query_vars );
		$product_query_sql            = $query->request;

		remove_filter( 'posts_clauses', array( $this, 'add_query_clauses' ), 10 );
		remove_filter( 'posts_pre_query', '__return_empty_array' );

		$stock_status_counts = array();

		foreach ( $stock_status_options as $status ) {
			$stock_status_count_sql = $this->generate_stock_status_count_query( $status, $product_query_sql, $stock_status_options );

			$result = $wpdb->get_row( $stock_status_count_sql ); // phpcs:ignore
			$stock_status_counts[ $status ] = $result->status_count;
		}

		return $stock_status_counts;
	}

	/**
	 * Get rating counts for the current products.
	 *
	 * @param array $query_vars The WP_Query arguments.
	 * @return array rating=>count pairs.
	 */
	public function get_rating_counts( $query_vars ) {
		global $wpdb;

		add_filter( 'posts_clauses', array( $this, 'add_query_clauses' ), 10, 2 );
		add_filter( 'posts_pre_query', '__return_empty_array' );

		$query_vars['no_found_rows']  = true;
		$query_vars['posts_per_page'] = -1;
		$query_vars['fields']         = 'ids';
		$query                        = new \WP_Query();
		$query->query( $query_vars );
		$product_query_sql = $query->request;

		remove_filter( 'posts_clauses', array( $this, 'add_query_clauses' ), 10 );
		remove_filter( 'posts_pre_query', '__return_empty_array' );

		$rating_count_sql = "
			SELECT COUNT( DISTINCT product_id ) as product_count, ROUND( average_rating, 0 ) as rounded_average_rating
			FROM {$wpdb->wc_product_meta_lookup}
			WHERE product_id IN ( {$product_query_sql} )
			AND average_rating > 0
			GROUP BY rounded_average_rating
			ORDER BY rounded_average_rating DESC
		";

		$results = $wpdb->get_results( $rating_count_sql ); // phpcs:ignore

		return array_map( 'absint', wp_list_pluck( $results, 'product_count', 'rounded_average_rating' ) );
	}

	/**
	 * Get attribute counts for the current products.
	 *
	 * @param array  $query_vars         The WP_Query arguments.
	 * @param string $attribute_to_count Attribute taxonomy name.
	 * @return array termId=>count pairs.
	 */
	public function get_attribute_counts( $query_vars, $attribute_to_count ) {
		global $wpdb;

		add_filter( 'posts_clauses', array( $this, 'add_query_clauses' ), 10, 2 );
		add_filter( 'posts_pre_query', '__return_empty_array' );

		$query_vars['no_found_rows']  = true;
		$query_vars['posts_per_page'] = -1;
		$query_vars['fields']         = 'ids';
		$query                        = new \WP_Query();
		$result                       = $query->query( $query_vars );
		$product_query_sql            = $query->request;

		remove_filter( 'posts_clauses', array( $this, 'add_query_clauses' ), 10 );
		remove_filter( 'posts_pre_query', '__return_empty_array' );

		$attributes_to_count = esc_sql( wc_sanitize_taxonomy_name( $attribute_to_count ) );

		$attribute_count_sql = "SELECT COUNT(DISTINCT posts.ID) as term_count, terms.term_id as term_count_id
			FROM {$wpdb->posts} AS posts
			INNER JOIN {$wpdb->term_relationships} AS term_relationships ON posts.ID = term_relationships.object_id
			INNER JOIN {$wpdb->term_taxonomy} AS term_taxonomy ON term_relationships.term_taxonomy_id = term_taxonomy.term_taxonomy_id
			INNER JOIN {$wpdb->terms} AS terms ON term_taxonomy.term_id = terms.term_id
			WHERE posts.ID IN ( {$product_query_sql} )
			AND term_taxonomy.taxonomy IN ('{$attributes_to_count}')
			AND posts.post_status = 'publish'
			AND posts.post_type = 'product'
			GROUP BY terms.term_id
			ORDER BY terms.name ASC";

		$results = $wpdb->get_results( $attribute_count_sql ); // phpcs:ignore

		return array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) );
	}

	/**
	 * Add query clauses for stock filter.
	 *
	 * @param array     $args     Query args.
	 * @param \WP_Query $wp_query WP_Query object.
	 * @return array
	 */
	private function stock_filter_clauses( $args, $wp_query ) {
		if ( ! $wp_query->get( 'filter_stock_status' ) ) {
			return $args;
		}

		$args['join']   = $this->append_product_sorting_table_join( $args['join'] );
		$args['where'] .= ' AND wc_product_meta_lookup.stock_status IN (\'' . implode( '\',\'', array_map( 'esc_sql', explode( ',', $wp_query->get( 'filter_stock_status' ) ) ) ) . '\')';

		return $args;
	}

	/**
	 * Add query clauses for price filter.
	 *
	 * @param array     $args     Query args.
	 * @param \WP_Query $wp_query WP_Query object.
	 * @return array
	 */
	private function price_filter_clauses( $args, $wp_query ) {
		if ( ! $wp_query->get( 'min_price' ) && ! $wp_query->get( 'max_price' ) ) {
			return $args;
		}

		global $wpdb;

		$adjust_for_taxes = $this->adjust_price_filters_for_displayed_taxes();
		$args['join']     = $this->append_product_sorting_table_join( $args['join'] );

		if ( $wp_query->get( 'min_price' ) ) {
			$min_price_filter = intval( $wp_query->get( 'min_price' ) );

			if ( $adjust_for_taxes ) {
				$args['where'] .= $this->get_price_filter_query_for_displayed_taxes( $min_price_filter, 'max_price', '>=' );
			} else {
				$args['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.max_price >= %f ', $min_price_filter );
			}
		}

		if ( $wp_query->get( 'max_price' ) ) {
			$max_price_filter = intval( $wp_query->get( 'max_price' ) );

			if ( $adjust_for_taxes ) {
				$args['where'] .= $this->get_price_filter_query_for_displayed_taxes( $max_price_filter, 'min_price', '<=' );
			} else {
				$args['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.min_price <= %f ', $max_price_filter );
			}
		}

		return $args;
	}

	/**
	 * Join wc_product_meta_lookup to posts if not already joined.
	 *
	 * @param string $sql SQL join.
	 * @return string
	 */
	private function append_product_sorting_table_join( $sql ) {
		global $wpdb;

		if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) {
			$sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ";
		}
		return $sql;
	}

	/**
	 * Generate calculate query by stock status.
	 *
	 * @param string $status status to calculate.
	 * @param string $product_query_sql product query for current filter state.
	 * @param array  $stock_status_options available stock status options.
	 *
	 * @return false|string
	 */
	private function generate_stock_status_count_query( $status, $product_query_sql, $stock_status_options ) {
		if ( ! in_array( $status, $stock_status_options, true ) ) {
			return false;
		}
		global $wpdb;
		$status = esc_sql( $status );
		return "
			SELECT COUNT( DISTINCT posts.ID ) as status_count
			FROM {$wpdb->posts} as posts
			INNER JOIN {$wpdb->postmeta} as postmeta ON posts.ID = postmeta.post_id
			AND postmeta.meta_key = '_stock_status'
			AND postmeta.meta_value = '{$status}'
			WHERE posts.ID IN ( {$product_query_sql} )
		";
	}

	/**
	 * Get query for price filters when dealing with displayed taxes.
	 *
	 * @param float  $price_filter Price filter to apply.
	 * @param string $column Price being filtered (min or max).
	 * @param string $operator Comparison operator for column.
	 * @return string Constructed query.
	 */
	private function get_price_filter_query_for_displayed_taxes( $price_filter, $column = 'min_price', $operator = '>=' ) {
		global $wpdb;

		// Select only used tax classes to avoid unwanted calculations.
		$product_tax_classes = array_filter( $wpdb->get_col( "SELECT DISTINCT tax_class FROM {$wpdb->wc_product_meta_lookup};" ) );

		if ( empty( $product_tax_classes ) ) {
			return '';
		}

		$or_queries = array();

		// We need to adjust the filter for each possible tax class and combine the queries into one.
		foreach ( $product_tax_classes as $tax_class ) {
			$adjusted_price_filter = $this->adjust_price_filter_for_tax_class( $price_filter, $tax_class );
			$or_queries[]          = $wpdb->prepare(
				'( wc_product_meta_lookup.tax_class = %s AND wc_product_meta_lookup.`' . esc_sql( $column ) . '` ' . esc_sql( $operator ) . ' %f )',
				$tax_class,
				$adjusted_price_filter
			);
		}

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
		return $wpdb->prepare(
			' AND (
				wc_product_meta_lookup.tax_status = "taxable" AND ( 0=1 OR ' . implode( ' OR ', $or_queries ) . ')
				OR ( wc_product_meta_lookup.tax_status != "taxable" AND wc_product_meta_lookup.`' . esc_sql( $column ) . '` ' . esc_sql( $operator ) . ' %f )
			) ',
			$price_filter
		);
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * If price filters need adjustment to work with displayed taxes, this returns true.
	 *
	 * This logic is used when prices are stored in the database differently to how they are being displayed, with regards
	 * to taxes.
	 *
	 * @return boolean
	 */
	private function adjust_price_filters_for_displayed_taxes() {
		$display  = get_option( 'woocommerce_tax_display_shop' );
		$database = wc_prices_include_tax() ? 'incl' : 'excl';

		return $display !== $database;
	}

	/**
	 * Adjusts a price filter based on a tax class and whether or not the amount includes or excludes taxes.
	 *
	 * This calculation logic is based on `wc_get_price_excluding_tax` and `wc_get_price_including_tax` in core.
	 *
	 * @param float  $price_filter Price filter amount as entered.
	 * @param string $tax_class Tax class for adjustment.
	 * @return float
	 */
	private function adjust_price_filter_for_tax_class( $price_filter, $tax_class ) {
		$tax_display    = get_option( 'woocommerce_tax_display_shop' );
		$tax_rates      = WC_Tax::get_rates( $tax_class );
		$base_tax_rates = WC_Tax::get_base_tax_rates( $tax_class );

		// If prices are shown incl. tax, we want to remove the taxes from the filter amount to match prices stored excl. tax.
		if ( 'incl' === $tax_display ) {
			/**
			 * Filters if taxes should be removed from locations outside the store base location.
			 *
			 * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing
			 * with out of base locations. e.g. If a product costs 10 including tax, all users will pay 10
			 * regardless of location and taxes.
			 *
			 * @since 2.6.0
			 *
			 * @internal Matches filter name in WooCommerce core.
			 *
			 * @param boolean $adjust_non_base_location_prices True by default.
			 * @return boolean
			 */
			$taxes = apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ? WC_Tax::calc_tax( $price_filter, $base_tax_rates, true ) : WC_Tax::calc_tax( $price_filter, $tax_rates, true );
			return $price_filter - array_sum( $taxes );
		}

		// If prices are shown excl. tax, add taxes to match the prices stored in the DB.
		$taxes = WC_Tax::calc_tax( $price_filter, $tax_rates, false );

		return $price_filter + array_sum( $taxes );
	}

	/**
	 * Get attribute lookup table name.
	 *
	 * @return string
	 */
	private function get_lookup_table_name() {
		return wc_get_container()->get( LookupDataStore::class )->get_lookup_table_name();
	}

	/**
	 * Add query clauses for attribute filter.
	 *
	 * @param array     $args     Query args.
	 * @param \WP_Query $wp_query WP_Query object.
	 * @return array
	 */
	private function attribute_filter_clauses( $args, $wp_query ) {
		$chosen_attributes = $this->get_chosen_attributes( $wp_query->query_vars );

		if ( empty( $chosen_attributes ) ) {
			return $args;
		}

		global $wpdb;

		// The extra derived table ("SELECT product_or_parent_id FROM") is needed for performance
		// (causes the filtering subquery to be executed only once).
		$clause_root = " {$wpdb->posts}.ID IN ( SELECT product_or_parent_id FROM (";
		if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
			$in_stock_clause = ' AND in_stock = 1';
		} else {
			$in_stock_clause = '';
		}

		$attribute_ids_for_and_filtering = array();

		foreach ( $chosen_attributes as $taxonomy => $data ) {
			$all_terms                  = get_terms(
				array(
					'taxonomy'   => $taxonomy,
					'hide_empty' => false,
				)
			);
			$term_ids_by_slug           = wp_list_pluck( $all_terms, 'term_id', 'slug' );
			$term_ids_to_filter_by      = array_values( array_intersect_key( $term_ids_by_slug, array_flip( $data['terms'] ) ) );
			$term_ids_to_filter_by      = array_map( 'absint', $term_ids_to_filter_by );
			$term_ids_to_filter_by_list = '(' . join( ',', $term_ids_to_filter_by ) . ')';
			$is_and_query               = 'and' === $data['query_type'];

			$count = count( $term_ids_to_filter_by );

			if ( 0 !== $count ) {
				if ( $is_and_query && $count > 1 ) {
					$attribute_ids_for_and_filtering = array_merge( $attribute_ids_for_and_filtering, $term_ids_to_filter_by );
				} else {
					$clauses[] = "
							{$clause_root}
							SELECT product_or_parent_id
							FROM {$this->get_lookup_table_name()} lt
							WHERE term_id in {$term_ids_to_filter_by_list}
							{$in_stock_clause}
						)";
				}
			}
		}

		if ( ! empty( $attribute_ids_for_and_filtering ) ) {
			$count                      = count( $attribute_ids_for_and_filtering );
			$term_ids_to_filter_by_list = '(' . join( ',', $attribute_ids_for_and_filtering ) . ')';
			$clauses[]                  = "
				{$clause_root}
				SELECT product_or_parent_id
				FROM {$this->get_lookup_table_name()} lt
				WHERE is_variation_attribute=0
				{$in_stock_clause}
				AND term_id in {$term_ids_to_filter_by_list}
				GROUP BY product_id
				HAVING COUNT(product_id)={$count}
				UNION
				SELECT product_or_parent_id
				FROM {$this->get_lookup_table_name()} lt
				WHERE is_variation_attribute=1
				{$in_stock_clause}
				AND term_id in {$term_ids_to_filter_by_list}
			)";
		}

		if ( ! empty( $clauses ) ) {
			// "temp" is needed because the extra derived tables require an alias.
			$args['where'] .= ' AND (' . join( ' temp ) AND ', $clauses ) . ' temp ))';
		} elseif ( ! empty( $chosen_attributes ) ) {
			$args['where'] .= ' AND 1=0';
		}

		return $args;
	}

	/**
	 * Get an array of attributes and terms selected from query arguments.
	 *
	 * @param array $query_vars The WP_Query arguments.
	 * @return array
	 */
	private function get_chosen_attributes( $query_vars ) {
		$chosen_attributes = array();

		if ( empty( $query_vars ) ) {
			return $chosen_attributes;
		}

		foreach ( $query_vars as $key => $value ) {
			if ( 0 === strpos( $key, 'filter_' ) ) {
				if ( ! is_string( $value ) ) {
					continue;
				}

				$attribute    = wc_sanitize_taxonomy_name( str_replace( 'filter_', '', $key ) );
				$taxonomy     = wc_attribute_taxonomy_name( $attribute );
				$filter_terms = ! empty( $value ) ? explode( ',', wc_clean( wp_unslash( $value ) ) ) : array();

				if ( empty( $filter_terms ) || ! taxonomy_exists( $taxonomy ) || ! wc_attribute_taxonomy_id_by_name( $attribute ) ) {
					continue;
				}

				$query_type                                   = ! empty( $query_vars[ 'query_type_' . $attribute ] ) && in_array( $query_vars[ 'query_type_' . $attribute ], array( 'and', 'or' ), true ) ? wc_clean( wp_unslash( $query_vars[ 'query_type_' . $attribute ] ) ) : '';
				$chosen_attributes[ $taxonomy ]['terms']      = array_map( 'sanitize_title', $filter_terms ); // Ensures correct encoding.
				$chosen_attributes[ $taxonomy ]['query_type'] = $query_type ? $query_type : 'and';
			}
		}

		return $chosen_attributes;
	}
}
PK     \mz    !  Domain/Services/CreateAccount.phpnu [        <?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\Blocks\Domain\Services;

use Automattic\WooCommerce\Blocks\Domain\Package;

/**
 * Service class implementing new create account emails used for order processing via the Block Based Checkout.
 *
 * @deprecated This class can't be removed due to https://github.com/woocommerce/woocommerce/issues/52311.
 */
class CreateAccount {
	/**
	 * Reference to the Package instance
	 *
	 * @var Package
	 */
	private $package;

	/**
	 * Constructor.
	 *
	 * @param Package $package An instance of (Woo Blocks) Package.
	 */
	public function __construct( Package $package ) {
		$this->package = $package;
	}

	/**
	 * Init - register handlers for WooCommerce core email hooks.
	 */
	public function init() {
		// This method is intentionally left blank.
	}

	/**
	 * Trigger new account email.
	 *
	 * @param int   $customer_id       The ID of the new customer account.
	 * @param array $new_customer_data Assoc array of data for the new account.
	 */
	public function customer_new_account( $customer_id = 0, array $new_customer_data = array() ) {
		// This method is intentionally left blank.
	}
}
PK     \]     3  Domain/Services/CheckoutFieldsSchema/Validation.phpnu [        <?php
declare( strict_types = 1);

namespace Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFieldsSchema;

use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFieldsSchema\DocumentObject;
use Opis\JsonSchema\{
	Helper,
	Validator
};
use WP_Error;

/**
 * Service class validating checkout field schema.
 */
class Validation {
	/**
	 * Meta schema.
	 *
	 * @var string
	 */
	private static $meta_schema_json = '';

	/**
	 * Get the field schema with context.
	 *
	 * @param string $field_id The field ID.
	 * @param array  $field_schema The field schema.
	 * @param string $context The context.
	 * @return array
	 */
	public static function get_field_schema_with_context( $field_id, $field_schema, $context ) {
		$primary_key   = 'checkout';
		$secondary_key = 'additional_fields';
		switch ( $context ) {
			case 'billing_address':
			case 'shipping_address':
				$primary_key   = 'customer';
				$secondary_key = $context;
				break;
			case 'contact':
				$primary_key   = 'customer';
				$secondary_key = 'additional_fields';
				break;
		}
		return [
			$primary_key => [
				'properties' => [
					$secondary_key => [
						'properties' => [
							$field_id => $field_schema,
						],
					],
				],
			],
		];
	}

	/**
	 * Check if the schema is unwrapped (has cart, checkout, customer, as top level keys).
	 *
	 * @param array $schema The schema to check.
	 * @return bool
	 */
	private static function schema_is_unwrapped( $schema ) {
		return isset( $schema['cart'] ) || isset( $schema['checkout'] ) || isset( $schema['customer'] );
	}

	/**
	 * Validate the field rules.
	 *
	 * @param DocumentObject $document_object The document object to validate.
	 * @param array          $rules The rules to validate against.
	 * @return bool|WP_Error
	 */
	public static function validate_document_object( DocumentObject $document_object, $rules ) {
		if ( self::schema_is_unwrapped( $rules ) ) {
			$rules = [
				'$schema'    => 'http://json-schema.org/draft-07/schema#',
				'type'       => 'object',
				'properties' => $rules,
			];
		} else {
			if ( ! isset( $rules['$schema'] ) ) {
				$rules['$schema'] = 'http://json-schema.org/draft-07/schema#';
			}
			if ( ! isset( $rules['type'] ) ) {
				$rules['type'] = 'object';
			}
		}

		try {
			$validator = new Validator();
			$result    = $validator->validate(
				Helper::toJSON( $document_object->get_data() ),
				Helper::toJSON( $rules )
			);

			if ( ! $result->hasError() ) {
				return true;
			}
		} catch ( \Exception $e ) {
			return new WP_Error( 'woocommerce_rest_checkout_validation_failed', __( 'Validation failed.', 'woocommerce' ) );
		}

		// Return generic error message.
		return new WP_Error( 'woocommerce_rest_checkout_invalid_field', __( 'Invalid field.', 'woocommerce' ) );
	}

	/**
	 * Check if the fields have defined schema.
	 *
	 * @param array $fields The fields.
	 * @return bool
	 */
	public static function has_field_schema( $fields ) {
		$return = false;

		foreach ( $fields as $field ) {
			if (
				( ! empty( $field['validation'] ) && is_array( $field['validation'] ) ) ||
				( ! empty( $field['required'] ) && is_array( $field['required'] ) ) ||
				( ! empty( $field['hidden'] ) && is_array( $field['hidden'] ) )
			) {
				$return = true;
				break;
			}
		}

		return $return;
	}

	/**
	 * Validate meta schema for field rules.
	 *
	 * @param mixed $rules The rules to validate.
	 * @return bool|WP_Error True if the field options are valid, a WP_Error otherwise.
	 */
	public static function is_valid_schema( $rules ) {
		if ( ! is_array( $rules ) ) {
			return new WP_Error( 'woocommerce_rest_checkout_invalid_field_schema', 'Rules must be defined as an array.' );
		}

		if ( empty( $rules ) ) {
			return true;
		}

		if ( self::schema_is_unwrapped( $rules ) ) {
			$rules = [
				'type'       => 'object',
				'properties' => $rules,
			];
		}

		if ( empty( self::$meta_schema_json ) ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
			self::$meta_schema_json = file_get_contents( __DIR__ . '/json-schema-draft-07.json' );
		}

		$validator = new Validator();
		$result    = $validator->validate(
			Helper::toJSON(
				[
					'$schema'    => 'http://json-schema.org/draft-07/schema#',
					'type'       => 'object',
					'properties' => [
						'test' => $rules,
					],
					'required'   => [ 'test' ],
				]
			),
			self::$meta_schema_json
		);

		if ( $result->hasError() ) {
			return new WP_Error( 'woocommerce_rest_checkout_invalid_field_schema', esc_html( (string) $result->error() ) );
		}

		return true;
	}
}
PK     \Ws  s  >  Domain/Services/CheckoutFieldsSchema/json-schema-draft-07.jsonnu [        {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "http://json-schema.org/draft-07/schema#",
    "title": "Core schema meta-schema",
    "definitions": {
        "schemaArray": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "#" }
        },
        "nonNegativeInteger": {
            "type": "integer",
            "minimum": 0
        },
        "nonNegativeIntegerDefault0": {
            "allOf": [
                { "$ref": "#/definitions/nonNegativeInteger" },
                { "default": 0 }
            ]
        },
        "simpleTypes": {
            "enum": [
                "array",
                "boolean",
                "integer",
                "null",
                "number",
                "object",
                "string"
            ]
        },
        "stringArray": {
            "type": "array",
            "items": { "type": "string" },
            "uniqueItems": true,
            "default": []
        }
    },
    "type": ["object", "boolean"],
    "properties": {
        "$id": {
            "type": "string",
            "format": "uri-reference"
        },
        "$schema": {
            "type": "string",
            "format": "uri"
        },
        "$ref": {
            "type": "string",
            "format": "uri-reference"
        },
        "$comment": {
            "type": "string"
        },
        "title": {
            "type": "string"
        },
        "description": {
            "type": "string"
        },
        "default": true,
        "readOnly": {
            "type": "boolean",
            "default": false
        },
        "writeOnly": {
            "type": "boolean",
            "default": false
        },
        "examples": {
            "type": "array",
            "items": true
        },
        "multipleOf": {
            "type": "number",
            "exclusiveMinimum": 0
        },
        "maximum": {
            "type": "number"
        },
        "exclusiveMaximum": {
            "type": "number"
        },
        "minimum": {
            "type": "number"
        },
        "exclusiveMinimum": {
            "type": "number"
        },
        "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
        "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
        "pattern": {
            "type": "string",
            "format": "regex"
        },
        "additionalItems": { "$ref": "#" },
        "items": {
            "anyOf": [
                { "$ref": "#" },
                { "$ref": "#/definitions/schemaArray" }
            ],
            "default": true
        },
        "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
        "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
        "uniqueItems": {
            "type": "boolean",
            "default": false
        },
        "contains": { "$ref": "#" },
        "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
        "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
        "required": { "$ref": "#/definitions/stringArray" },
        "additionalProperties": { "$ref": "#" },
        "definitions": {
            "type": "object",
            "additionalProperties": { "$ref": "#" },
            "default": {}
        },
        "properties": {
            "type": "object",
            "additionalProperties": { "$ref": "#" },
            "default": {}
        },
        "patternProperties": {
            "type": "object",
            "additionalProperties": { "$ref": "#" },
            "propertyNames": { "format": "regex" },
            "default": {}
        },
        "dependencies": {
            "type": "object",
            "additionalProperties": {
                "anyOf": [
                    { "$ref": "#" },
                    { "$ref": "#/definitions/stringArray" }
                ]
            }
        },
        "propertyNames": { "$ref": "#" },
        "const": true,
        "enum": {
            "type": "array",
            "items": true,
            "minItems": 1,
            "uniqueItems": true
        },
        "type": {
            "anyOf": [
                { "$ref": "#/definitions/simpleTypes" },
                {
                    "type": "array",
                    "items": { "$ref": "#/definitions/simpleTypes" },
                    "minItems": 1,
                    "uniqueItems": true
                }
            ]
        },
        "format": { "type": "string" },
        "contentMediaType": { "type": "string" },
        "contentEncoding": { "type": "string" },
        "if": { "$ref": "#" },
        "then": { "$ref": "#" },
        "else": { "$ref": "#" },
        "allOf": { "$ref": "#/definitions/schemaArray" },
        "anyOf": { "$ref": "#/definitions/schemaArray" },
        "oneOf": { "$ref": "#/definitions/schemaArray" },
        "not": { "$ref": "#" }
    },
    "default": true
}
PK     \ʱ8    7  Domain/Services/CheckoutFieldsSchema/DocumentObject.phpnu [        <?php
declare( strict_types = 1);

namespace Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFieldsSchema;

use WC_Cart;
use WC_Customer;
use Automattic\WooCommerce\StoreApi\StoreApi;
use Automattic\WooCommerce\StoreApi\SchemaController;
use Automattic\WooCommerce\StoreApi\Schemas\V1\CartSchema;
use Automattic\WooCommerce\StoreApi\Schemas\V1\BillingAddressSchema;
use Automattic\WooCommerce\StoreApi\Schemas\V1\ShippingAddressSchema;
use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;
use Automattic\WooCommerce\StoreApi\Utilities\CartController;
use Automattic\WooCommerce\Utilities\NumberUtil;

/**
 * DocumentObject class.
 *
 * This will combine and format given cart/customer/checkout data into a standard object format that can be queried through
 * JSON. This is used for conditional fields and validation during checkout.
 */
class DocumentObject {
	/**
	 * Docuemnt object context which may adjust the schema response.
	 *
	 * @var null|string
	 */
	protected $context = null;

	/**
	 * Valid contexts.
	 *
	 * @var array
	 */
	protected $valid_contexts = [
		'shipping_address',
		'billing_address',
		'contact',
		'order',
	];

	/**
	 * The cart object.
	 *
	 * @var WC_Cart|null
	 */
	protected $cart = null;

	/**
	 * The customer object.
	 *
	 * @var WC_Customer|null
	 */
	protected $customer = null;

	/**
	 * Cart controller class instance.
	 *
	 * @var CartController
	 */
	protected $cart_controller;

	/**
	 * Schema controller class instance.
	 *
	 * @var SchemaController
	 */
	protected $schema_controller;

	/**
	 * The request data.
	 *
	 * @var array
	 */
	protected $request_data = [];

	/**
	 * The constructor.
	 *
	 * @param array $request_data Data that overrides the default values.
	 */
	public function __construct( array $request_data = [] ) {
		$this->cart_controller   = new CartController();
		$this->schema_controller = StoreApi::container()->get( SchemaController::class );
		$this->request_data      = $request_data;
	}

	/**
	 * Set document object context.
	 *
	 * @param null|string $context Context to set.
	 */
	public function set_context( $context = null ) {
		if ( ! in_array( $context, $this->valid_contexts, true ) ) {
			return;
		}
		$this->context = $context;
	}

	/**
	 * Set the customer object.
	 *
	 * @param WC_Customer $customer The customer object.
	 */
	public function set_customer( WC_Customer $customer ) {
		$this->customer = $customer;
	}

	/**
	 * Set the cart object.
	 *
	 * @param WC_Cart $cart The cart object.
	 */
	public function set_cart( WC_Cart $cart ) {
		$this->cart = $cart;
	}

	/**
	 * Gets a subset of cart data.
	 *
	 * @return array The cart data.
	 */
	protected function get_cart_data() {
		$cart_data               = StoreApi::container()->get( SchemaController::class )->get( CartSchema::IDENTIFIER )->get_item_response( $this->cart );
		$selected_shipping_rates = array_filter(
			array_map(
				function ( $package ) {
					$selected_rate = array_search( true, array_column( $package['shipping_rates'], 'selected' ), true );
					return false !== $selected_rate && isset( $package['shipping_rates'][ $selected_rate ] ) ? $package['shipping_rates'][ $selected_rate ] : null;
				},
				$cart_data['shipping_rates']
			)
		);
		$local_pickup_method_ids = LocalPickupUtils::get_local_pickup_method_ids();

		return wp_parse_args(
			$this->request_data['cart'] ?? [],
			[
				'coupons'            => array_values( wc_list_pluck( $cart_data['coupons'], 'code' ) ),
				'shipping_rates'     => array_values( wc_list_pluck( $selected_shipping_rates, 'rate_id' ) ),
				'items'              => array_merge(
					...array_map(
						function ( $item ) {
							return array_fill( 0, (int) NumberUtil::ceil( $item['quantity'] ), $item['id'] );
						},
						$cart_data['items']
					)
				),
				'items_type'         => array_unique( array_values( wc_list_pluck( $cart_data['items'], 'type' ) ) ),
				'items_count'        => $cart_data['items_count'],
				'items_weight'       => $cart_data['items_weight'],
				'needs_shipping'     => $cart_data['needs_shipping'],
				'prefers_collection' => count( array_intersect( $local_pickup_method_ids, wc_list_pluck( $selected_shipping_rates, 'method_id' ) ) ) > 0,
				'totals'             => [
					'total_price' => (int) $cart_data['totals']->total_price,
					'total_tax'   => (int) $cart_data['totals']->total_tax,
				],
				'extensions'         => (object) $cart_data['extensions'],
			]
		);
	}

	/**
	 * Get checkout data.
	 *
	 * @return array Checkout data context.
	 */
	protected function get_checkout_data() {
		return $this->request_data['checkout'] ?? [];
	}

	/**
	 * Get the customer data.
	 *
	 * @return array The customer data.
	 */
	protected function get_customer_data() {
		$customer_data = [
			'id'                => $this->request_data['customer']['id'] ?? $this->customer->get_id(),
			'shipping_address'  => wp_parse_args(
				$this->request_data['customer']['shipping_address'] ?? (object) [],
				$this->schema_controller->get( ShippingAddressSchema::IDENTIFIER )->get_item_response( $this->customer )
			),
			'billing_address'   => wp_parse_args(
				$this->request_data['customer']['billing_address'] ?? (object) [],
				$this->schema_controller->get( BillingAddressSchema::IDENTIFIER )->get_item_response( $this->customer )
			),
			'additional_fields' => $this->request_data['customer']['additional_fields'] ?? (object) [],
		];

		if ( 'shipping_address' === $this->context ) {
			$customer_data['address'] = $customer_data['shipping_address'];
		}

		if ( 'billing_address' === $this->context ) {
			$customer_data['address'] = $customer_data['billing_address'];
		}

		return $customer_data;
	}

	/**
	 * Get the data for the document object.
	 *
	 * This isn't a 1:1 match with Store API because some data is simplified to make it easier to parse as JSON.
	 *
	 * @return array The data for the document object.
	 */
	public function get_data() {
		// Get cart and customer objects before returning data if they are null.
		if ( is_null( $this->cart ) ) {
			$this->cart = $this->cart_controller->get_cart_for_response();
		}

		if ( is_null( $this->customer ) ) {
			$this->customer = ! empty( WC()->customer ) ? WC()->customer : new WC_Customer();
		}

		return [
			'cart'     => $this->get_cart_data(),
			'customer' => $this->get_customer_data(),
			'checkout' => $this->get_checkout_data(),
		];
	}

	/**
	 * Get the current context.
	 *
	 * @return null|string The context.
	 */
	public function get_context() {
		return $this->context;
	}
}
PK     \ӯZ"  "  B  Domain/Services/CheckoutFieldsSchema/checkout-document-schema.jsonnu [        {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Cart and Checkout Document Object Schema",
    "description": "Document object schema for cart, checkout, and customer information, to be used for conditional visibility, requirement, and validation of fields.",
    "type": "object",
    "properties": {
        "cart": {
            "type": "object",
            "description": "Information about the shopping cart",
            "properties": {
                "coupons": {
                    "type": "array",
                    "description": "List of coupon codes applied to the cart",
                    "items": {
                        "type": "string"
                    }
                },
                "shipping_rates": {
                    "type": "array",
                    "description": "List of currently selected shipping rates",
                    "items": {
                        "type": "string",
                        "description": "Shipping rate identifier using the full shipping rate ID so method_id:instance_id, for example: flat_rate:1"
                    }
                },
                "items": {
                    "type": "array",
                    "description": "List of product IDs in the cart, IDs will be duplicated depending on the quantity of the product in the cart, so if you have 2 of product ID 1, the array will have 2 entries of product ID 1",
                    "items": {
                        "type": "integer"
                    }
                },
                "items_type": {
                    "type": "array",
                    "description": "Types of items in the cart, for example: simple, variation, subscription, etc.",
                    "items": {
                        "type": "string"
                    }
                },
                "items_count": {
                    "type": "integer",
                    "description": "Total number of items in the cart",
                    "minimum": 0
                },
                "items_weight": {
                    "type": "number",
                    "description": "Total weight of items in the cart",
                    "minimum": 0
                },
                "needs_shipping": {
                    "type": "boolean",
                    "description": "Whether the items in the cart require shipping"
                },
                "prefers_collection": {
                    "type": "boolean",
                    "description": "Whether the customer prefers using Local Pickup"
                },
                "totals": {
                    "type": "object",
                    "description": "Cart totals information",
                    "properties": {
                        "total_price": {
                            "type": "integer",
                            "description": "Total price of the cart in smallest currency unit (e.g., cents), after applying all discounts, shipping, and taxes"
                        },
                        "total_tax": {
                            "type": "integer",
                            "description": "Total tax amount in smallest currency unit (e.g., cents), after applying all discounts, shipping, and taxes"
                        }
                    },
                    "additionalProperties": false
                },
                "extensions": {
                    "type": "object",
                    "description": "Additional cart extension data, this is similar to what's passed in Store API's extensions parameter"
                }
            },
            "additionalProperties": false
        },
        "checkout": {
            "type": "object",
            "description": "Checkout preferences and settings",
            "properties": {
                "create_account": {
                    "type": "boolean",
                    "description": "Whether the customer checked the create account checkbox, this will be false if the customer is logged in, cannot create an account, or forced to create an account."
                },
                "customer_note": {
                    "type": "string",
                    "description": "Customer's note or special instructions for the order, this will be empty if the customer didn't add a note."
                },
                "additional_fields": {
                    "type": "object",
                    "description": "Additional checkout fields, limited to the order location.",
                    "additionalProperties": {
                        "type": "string"
                    },
                    "patternProperties": {
                        "^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$": {
                            "type": "string",
                            "description": "Custom fields with namespace identifiers"
                        }
                    }
                },
                "payment_method": {
                    "type": "string",
                    "description": "Selected payment method identifier, this will be the payment method ID regardless if the customer selected a saved payment method or new payment method"
                }
            },
            "additionalProperties": false
        },
        "customer": {
            "type": "object",
            "description": "Customer information",
            "properties": {
                "id": {
                    "type": "integer",
                    "description": "Customer ID, this will be 0 if the customer is not logged in"
                },
                "billing_address": {
                    "$ref": "#/definitions/address",
                    "description": "Customer's billing address"
                },
                "shipping_address": {
                    "$ref": "#/definitions/address",
                    "description": "Customer's shipping address"
                },
                "additional_fields": {
                    "type": "object",
                    "description": "Additional checkout fields, limited to the contact location.",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "address": {
                    "$ref": "#/definitions/address",
                    "description": "This is a dynamic field that will be the billing or shipping address depending on the context of the field being evaluated."
                }
            },
            "additionalProperties": false
        }
    },
    "additionalProperties": false,
    "definitions": {
        "address": {
            "type": "object",
            "description": "Customer address information",
            "properties": {
                "first_name": {
                    "type": "string",
                    "description": "First name of the recipient"
                },
                "last_name": {
                    "type": "string",
                    "description": "Last name of the recipient"
                },
                "company": {
                    "type": "string",
                    "description": "Company name"
                },
                "address_1": {
                    "type": "string",
                    "description": "Primary address line"
                },
                "address_2": {
                    "type": "string",
                    "description": "Secondary address line"
                },
                "city": {
                    "type": "string",
                    "description": "City name"
                },
                "state": {
                    "type": "string",
                    "description": "State or province, this will be the state code if it's a predefined list, for example: CA, TX, NY, etc, or the field value if it's a freeform state, for example: London."
                },
                "postcode": {
                    "type": "string",
                    "description": "Postal or ZIP code"
                },
                "country": {
                    "type": "string",
                    "description": "Country code (e.g., US, UK)"
                },
                "email": {
                    "type": "string",
                    "description": "Email address"
                },
                "phone": {
                    "type": "string",
                    "description": "Phone number"
                }
            },
            "additionalProperties": {
                "type": "string",
                "description": "Custom fields with namespace identifiers"
            },
            "patternProperties": {
                "^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$": {
                    "type": "string",
                    "description": "Custom fields with namespace identifiers"
                }
            }
        }
    }
}PK     \.!  !    Domain/Services/DraftOrders.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Domain\Services;

use Automattic\WooCommerce\Blocks\Domain\Package;
use Exception;
use WC_Order;

/**
 * Service class for adding DraftOrder functionality to WooCommerce core.
 *
 * Sets up all logic related to the Checkout Draft Orders service
 *
 * @internal
 */
class DraftOrders {

	const DB_STATUS = 'wc-checkout-draft';
	const STATUS    = 'checkout-draft';

	const DRAFT_CLEANUP_EVENT_HOOK = 'woocommerce_cleanup_draft_orders';

	/**
	 * Holds the Package instance
	 *
	 * @var Package
	 */
	private $package;

	/**
	 * Constructor
	 *
	 * @param Package $package An instance of the package class.
	 */
	public function __construct( Package $package ) {
		$this->package = $package;
	}

	/**
	 * Set all hooks related to adding Checkout Draft order functionality to Woo Core.
	 */
	public function init() {
		add_filter( 'wc_order_statuses', [ $this, 'register_draft_order_status' ] );
		add_filter( 'woocommerce_register_shop_order_post_statuses', [ $this, 'register_draft_order_post_status' ] );
		add_filter( 'woocommerce_analytics_excluded_order_statuses', [ $this, 'append_draft_order_post_status' ] );
		add_filter( 'woocommerce_valid_order_statuses_for_payment', [ $this, 'append_draft_order_post_status' ], 999 );
		add_filter( 'woocommerce_valid_order_statuses_for_payment_complete', [ $this, 'append_draft_order_post_status' ], 999 );
		// Hook into the query to retrieve My Account orders so draft status is excluded.
		add_action( 'woocommerce_my_account_my_orders_query', [ $this, 'delete_draft_order_post_status_from_args' ] );
		add_action( self::DRAFT_CLEANUP_EVENT_HOOK, [ $this, 'delete_expired_draft_orders' ] );
		add_action( 'admin_init', [ $this, 'install' ] );

		if ( defined( 'WC_PLUGIN_BASENAME' ) ) {
			add_action( 'deactivate_' . WC_PLUGIN_BASENAME, [ $this, 'unschedule_cronjobs' ] );
		}
	}

	/**
	 * Installation related logic for Draft order functionality.
	 *
	 * @internal
	 */
	public function install() {
		$this->maybe_create_cronjobs();
	}

	/**
	 * Unschedule recurring actions when plugin is deactivated.
	 *
	 * @since 10.0.0
	 * @internal
	 */
	public function unschedule_cronjobs() {
		WC()->queue()->cancel_all( self::DRAFT_CLEANUP_EVENT_HOOK );
	}

	/**
	 * Maybe create cron events.
	 */
	protected function maybe_create_cronjobs() {
		$has_scheduled_action = function_exists( 'as_has_scheduled_action' ) ? 'as_has_scheduled_action' : 'as_next_scheduled_action';
		if ( false === call_user_func( $has_scheduled_action, self::DRAFT_CLEANUP_EVENT_HOOK ) ) {
			$midnight_tonight = strtotime( 'midnight tonight' );
			if ( false !== $midnight_tonight ) {
				as_schedule_recurring_action( $midnight_tonight, DAY_IN_SECONDS, self::DRAFT_CLEANUP_EVENT_HOOK );
			}
		}
	}

	/**
	 * Register custom order status for orders created via the API during checkout.
	 *
	 * Draft order status is used before payment is attempted, during checkout, when a cart is converted to an order.
	 *
	 * @param array $statuses Array of statuses.
	 * @internal
	 * @return array
	 */
	public function register_draft_order_status( array $statuses ) {
		$statuses[ self::DB_STATUS ] = _x( 'Draft', 'Order status', 'woocommerce' );
		return $statuses;
	}

	/**
	 * Register custom order post status for orders created via the API during checkout.
	 *
	 * @param array $statuses Array of statuses.
	 * @internal

	 * @return array
	 */
	public function register_draft_order_post_status( array $statuses ) {
		$statuses[ self::DB_STATUS ] = $this->get_post_status_properties();
		return $statuses;
	}

	/**
	 * Returns the properties of this post status for registration.
	 *
	 * @return array
	 */
	private function get_post_status_properties() {
		return [
			'label'                     => _x( 'Draft', 'Order status', 'woocommerce' ),
			'public'                    => false,
			'exclude_from_search'       => false,
			'show_in_admin_all_list'    => false,
			'show_in_admin_status_list' => true,
			/* translators: %s: number of orders */
			'label_count'               => _n_noop( 'Drafts <span class="count">(%s)</span>', 'Drafts <span class="count">(%s)</span>', 'woocommerce' ),
		];
	}

	/**
	 * Remove draft status from the 'status' argument of an $args array.
	 *
	 * @param array $args Array of arguments containing statuses in the status key.
	 * @internal
	 * @return array
	 */
	public function delete_draft_order_post_status_from_args( $args ) {
		if ( ! array_key_exists( 'status', $args ) ) {
			$statuses = [];
			foreach ( wc_get_order_statuses() as $key => $label ) {
				if ( self::DB_STATUS !== $key ) {
					$statuses[] = str_replace( 'wc-', '', $key );
				}
			}
			$args['status'] = $statuses;
		} elseif ( self::DB_STATUS === $args['status'] ) {
			$args['status'] = '';
		} elseif ( is_array( $args['status'] ) ) {
			$args['status'] = array_diff_key( $args['status'], array( self::STATUS => null ) );
		}

		return $args;
	}

	/**
	 * Append draft status to a list of statuses.
	 *
	 * @param array $statuses Array of statuses.
	 * @internal

	 * @return array
	 */
	public function append_draft_order_post_status( $statuses ) {
		$statuses[] = self::STATUS;
		return $statuses;
	}

	/**
	 * Delete draft orders older than a day in configurable batches (default: 20).
	 *
	 * Ran on a daily cron schedule. Batch size is filterable via
	 * `woocommerce_delete_expired_draft_orders_batch_size`.
	 *
	 * @internal
	 */
	public function delete_expired_draft_orders() {
		$count = 0;
		/**
		 * Filters the number of draft orders deleted per batch during cleanup.
		 *
		 * Increasing this value can help improve deletion throughput for high-volume or busy stores
		 * when the cleanup task cannot keep up with the draft orders backlog.
		 *
		 * @since 10.7.0
		 * @param int $batch_size Number of draft orders to delete per batch. Default 20.
		 */
		$batch_size = max( 1, (int) apply_filters( 'woocommerce_delete_expired_draft_orders_batch_size', 20 ) );
		$this->ensure_draft_status_registered();
		$orders = wc_get_orders(
			[
				'date_modified' => '<=' . strtotime( '-1 DAY' ),
				'limit'         => $batch_size,
				'status'        => self::DB_STATUS,
				'type'          => 'shop_order',
			]
		);

		// do we bail because the query results are unexpected?
		try {
			$this->assert_order_results( $orders, $batch_size );
			if ( $orders ) {
				foreach ( $orders as $order ) {
					$order->delete( true );
					++$count;
				}
			}
			if ( $batch_size === $count && function_exists( 'as_enqueue_async_action' ) ) {
				as_enqueue_async_action( self::DRAFT_CLEANUP_EVENT_HOOK );
			}
		} catch ( Exception $error ) {
			wc_caught_exception( $error, __METHOD__ );
		}
	}

	/**
	 * Since it's possible for third party code to clobber the `$wp_post_statuses` global,
	 * we need to do a final check here to make sure the draft post status is
	 * registered with the global so that it is not removed by WP_Query status
	 * validation checks.
	 */
	private function ensure_draft_status_registered() {
		$is_registered = get_post_stati( [ 'name' => self::DB_STATUS ] );
		if ( empty( $is_registered ) ) {
			register_post_status(
				self::DB_STATUS,
				$this->get_post_status_properties()
			);
		}
	}

	/**
	 * Asserts whether incoming order results are expected given the query
	 * this service class executes.
	 *
	 * @param WC_Order[] $order_results The order results being asserted.
	 * @param int        $expected_batch_size The expected batch size for the results.
	 * @throws Exception If any assertions fail, an exception is thrown.
	 */
	private function assert_order_results( $order_results, $expected_batch_size ) {
		// if not an array, then just return because it won't get handled
		// anyways.
		if ( ! is_array( $order_results ) ) {
			return;
		}

		$suffix = ' This is an indicator that something is filtering WooCommerce or WordPress queries and modifying the query parameters.';

		// if count is greater than our expected batch size, then that's a problem.
		if ( count( $order_results ) > $expected_batch_size ) {
			throw new Exception( 'There are an unexpected number of results returned from the query.' . $suffix );
		}

		// if any of the returned orders are not draft (or not a WC_Order), then that's a problem.
		foreach ( $order_results as $order ) {
			if ( ! ( $order instanceof WC_Order ) ) {
				throw new Exception( 'The returned results contain a value that is not a WC_Order.' . $suffix );
			}
			if ( ! $order->has_status( self::STATUS ) ) {
				throw new Exception( 'The results contain an order that is not a `wc-checkout-draft` status in the results.' . $suffix );
			}
		}
	}
}
PK     \2%u       Domain/Services/CheckoutLink.phpnu [        <?php
/**
 * Functionality that takes a static URL, constructs a cart, and redirects to the checkout with a cart session.
 */

declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\Domain\Services;

use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
use Automattic\WooCommerce\StoreApi\Utilities\CartTokenUtils;
use Automattic\WooCommerce\StoreApi\Utilities\CartController;

defined( 'ABSPATH' ) || exit;

/**
 * Checkout Link class.
 */
class CheckoutLink {
	/**
	 * Initialize the checkout link service.
	 */
	public function init() {
		add_action( 'init', array( $this, 'add_checkout_link_endpoint' ) );
		add_filter( 'query_vars', array( $this, 'add_query_vars' ), 0 );
		add_action( 'template_redirect', array( $this, 'handle_checkout_link_endpoint' ) );
	}

	/**
	 * Add the checkout link endpoint.
	 */
	public function add_checkout_link_endpoint() {
		// get registered rewrite rules.
		$rules = get_option( 'rewrite_rules', array() );
		$regex = '^checkout-link$';

		add_rewrite_rule( $regex, 'index.php?checkout-link=true', 'top' );

		// maybe flush rewrite rules if it was not previously in the option.
		if ( ! isset( $rules[ $regex ] ) ) {
			flush_rewrite_rules();
		}
	}

	/**
	 * Add the checkout link query var.
	 *
	 * @param array $vars The query vars.
	 * @return array The query vars.
	 */
	public function add_query_vars( $vars ) {
		$vars[] = 'checkout-link';
		return $vars;
	}

	/**
	 * Handle the checkout link endpoint.
	 *
	 * @return void
	 */
	public function handle_checkout_link_endpoint() {
		if ( ! get_query_var( 'checkout-link' ) ) {
			return;
		}

		if ( ! $this->validate_checkout_link() ) {
			$redirect = add_query_arg( 'wc_error', rawurlencode( __( 'The provided checkout link was out of date or invalid. No products were added to the cart.', 'woocommerce' ) ), wc_get_cart_url() );
		} else {
			wc()->cart->empty_cart();
			$redirect = $this->get_checkout_link();
		}

		wp_safe_redirect( $redirect );
		exit;
	}

	/**
	 * Validate the checkout link.
	 *
	 * @return bool True if the checkout link is valid, false otherwise.
	 */
	protected function validate_checkout_link() {
		$products = $this->get_products_from_checkout_link();

		return ! empty( $products );
	}

	/**
	 * Get the products from the checkout link.
	 *
	 * @return array The products (keys) and their quantities (values).
	 */
	protected function get_products_from_checkout_link() {
		$raw_products = array_filter( explode( ',', wc_clean( wp_unslash( $_GET['products'] ?? '' ) ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$products     = [];

		foreach ( $raw_products as $product_id_qty ) {
			if ( strpos( $product_id_qty, ':' ) !== false ) {
				list( $product_id, $qty ) = explode( ':', $product_id_qty );
			} else {
				$product_id = $product_id_qty;
				$qty        = 1;
			}
			$product_id = absint( $product_id );
			$qty        = absint( $qty );

			if ( ! $product_id || ! $qty ) {
				continue;
			}

			$products[ $product_id ] = $qty;
		}

		return $products;
	}

	/**
	 * Add error notices to the cart.
	 *
	 * @param \WP_Error $errors The errors.
	 * @return void
	 */
	protected function add_error_notices( \WP_Error $errors ) {
		foreach ( $errors->get_error_messages() as $message ) {
			wc_add_notice( $message, 'error' );
		}
	}

	/**
	 * Process the query params and return the checkout link to redirect to complete with session token.
	 *
	 * @return string The checkout link.
	 */
	protected function get_checkout_link() {
		$controller = new CartController();
		$products   = $this->get_products_from_checkout_link();
		$errors     = new \WP_Error();

		foreach ( $products as $product_id => $qty ) {
			try {
				$controller->add_to_cart(
					[
						'id'       => $product_id,
						'quantity' => $qty,
					]
				);
			} catch ( \Exception $e ) {
				$errors->add( 'error', $e->getMessage() );
			}
		}

		// Nothing was added to the cart. We need to redirect to the cart page with an error notice. Since guests may not
		// have a session, add the notice in the query string.
		if ( wc()->cart->is_empty() ) {
			$errors->add( 'error', __( 'The provided checkout link was out of date or invalid. No products were added to the cart.', 'woocommerce' ) );

			if ( ! wc()->session->has_session() ) {
				return add_query_arg( 'wc_error', rawurlencode( $errors->get_error_message() ), wc_get_cart_url() );
			} else {
				$this->add_error_notices( $errors );
			}

			return wc_get_cart_url();
		}

		// Apply coupon if provided.
		$coupon = wc_format_coupon_code( wp_unslash( $_GET['coupon'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

		if ( wc_coupons_enabled() && ! empty( $coupon ) ) {
			try {
				$controller->apply_coupon( $coupon );
			} catch ( \Exception $e ) {
				$errors->add( 'error', $e->getMessage() );
			}
		}

		// Add error notices to the cart. This requires a session otherwise the notices will not be displayed.
		$this->add_error_notices( $errors );

		$redirect_url = wc_get_checkout_url();

		// Preserve the query string--pass it to the checkout page.
		if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
			$redirect_url = remove_query_arg(
				[
					'products',
					'coupon',
					'checkout-link',
				],
				add_query_arg( wp_unslash( $_SERVER['QUERY_STRING'] ), '', $redirect_url ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			);
		}

		// If the user is logged in, the session is tied to the user ID. Do not use a cart token.
		if ( ! is_user_logged_in() ) {
			$session_token = CartTokenUtils::get_cart_token( (string) wc()->session->get_customer_id() );
			$redirect_url  = add_query_arg( 'session', $session_token, $redirect_url );
		}

		return $redirect_url;
	}
}
PK     \ЃBKf%  f%    Domain/Services/Hydration.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Domain\Services;

use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\StoreApi\RoutesController;
use Automattic\WooCommerce\StoreApi\SchemaController;
use Automattic\WooCommerce\StoreApi\StoreApi;

/**
 * Service class that handles hydration of API data for blocks.
 */
class Hydration {
	/**
	 * Instance of the asset data registry.
	 *
	 * @var AssetDataRegistry
	 */
	protected $asset_data_registry;

	/**
	 * Cached notices to restore after hydrating the API.
	 *
	 * @var array
	 */
	protected $cached_store_notices = array();

	/**
	 * Constructor.
	 *
	 * @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
	 */
	public function __construct( AssetDataRegistry $asset_data_registry ) {
		$this->asset_data_registry = $asset_data_registry;
	}

	/**
	 * Hydrates the asset data registry with data from the API. Disables notices and nonces so requests contain valid
	 * data that is not polluted by the current session.
	 *
	 * @param array $path API paths to hydrate e.g. '/wc/store/v1/cart'.
	 * @return array Response data.
	 */
	public function get_rest_api_response_data( $path = '' ) {
		if ( ! str_starts_with( $path, '/wc/store' ) ) {
			return array();
		}

		// Allow-list only store API routes. No other request can be hydrated for safety.
		$available_routes = StoreApi::container()->get( RoutesController::class )->get_all_routes( 'v1', true );
		$route_match      = $this->match_route_to_handler( $path, $available_routes );

		/**
		 * We disable nonce check to support endpoints such as checkout. The caveat here is that we need to be careful to only support GET requests. No other request type should be processed without nonce check. Additionally, no GET request can modify data as part of hydration request, for example adding items to cart.
		 *
		 * Long term, we should consider validating nonce here, instead of disabling it temporarily.
		 */
		$this->disable_nonce_check();

		$this->cache_store_notices();

		$preloaded_data = array();

		if ( null !== $route_match ) {
			try {
				$response = $this->get_response_from_controller(
					$route_match['controller'],
					$path,
					$route_match['url_params'],
					$route_match['query_params']
				);
				if ( $response ) {
					$preloaded_data = array(
						'body'    => $response->get_data(),
						'headers' => $response->get_headers(),
					);
				}
			} catch ( \Exception $e ) {
				// This is executing in frontend of the site, a failure in hydration should not stop the site from working.
				wc_get_logger()->warning(
					'Error in hydrating REST API request: ' . $e->getMessage(),
					array(
						'source'    => 'blocks-hydration',
						'data'      => array(
							'path'       => $path,
							'controller' => $route_match['controller'] ?? null,
						),
						'backtrace' => true,
					)
				);
			}
		} else {
			// Preload the request and add it to the array. It will be $preloaded_requests['path']  and contain 'body' and 'headers'.
			$preloaded_requests = rest_preload_api_request( array(), $path );
			$preloaded_data     = $preloaded_requests[ $path ] ?? array();
		}

		$this->restore_cached_store_notices();
		$this->restore_nonce_check();

		// Returns just the single preloaded request, or an empty array if it doesn't exist.
		return $preloaded_data;
	}

	/**
	 * Helper method to generate GET response from a controller. Also fires the `rest_request_after_callbacks` for backward compatibility.
	 *
	 * @param string $controller_class Controller class FQN that will respond to the request.
	 * @param string $path             Request path regex.
	 * @param array  $url_params       URL parameters extracted from route (e.g., ['id' => '123']).
	 * @param array  $query_params     Query string parameters (e.g., ['key' => 'value']).
	 *
	 * @return false|mixed|null Response
	 */
	private function get_response_from_controller( $controller_class, $path, $url_params = array(), $query_params = array() ) {
		if ( null === $controller_class ) {
			return false;
		}

		$request = new \WP_REST_Request( 'GET', $path );

		// Set URL parameters (from route segments like /products/123).
		if ( ! empty( $url_params ) ) {
			$request->set_url_params( $url_params );
		}

		// Set query parameters (from query string like ?key=value).
		if ( ! empty( $query_params ) ) {
			$request->set_query_params( $query_params );
		}

		$schema_controller = StoreApi::container()->get( SchemaController::class );
		$controller        = new $controller_class(
			$schema_controller,
			$schema_controller->get( $controller_class::SCHEMA_TYPE, $controller_class::SCHEMA_VERSION )
		);

		$controller_args = is_callable( array( $controller, 'get_args' ) ) ? $controller->get_args() : array();

		if ( empty( $controller_args ) ) {
			return false;
		}

		// Get the handler that responds to read request.
		$handler = current(
			array_filter(
				$controller_args,
				function ( $method_handler ) {
					return is_array( $method_handler ) && isset( $method_handler['methods'] ) && \WP_REST_Server::READABLE === $method_handler['methods'];
				}
			)
		);

		if ( ! $handler ) {
			return false;
		}

		/**
		 * Similar to WP core's `rest_dispatch_request` filter, this allows plugin to override hydrating the request.
		 * Allows backward compatibility with the `rest_dispatch_request` filter by providing the same arguments.
		 *
		 * @since 8.9.0
		 *
		 * @param mixed            $hydration_result Result of the hydration. If not null, this will be used as the response.
		 * @param WP_REST_Request  $request          Request used to generate the response.
		 * @param string           $path             Request path matched for the request..
		 * @param array            $handler          Route handler used for the request.
		 */
		$hydration_result = apply_filters( 'woocommerce_hydration_dispatch_request', null, $request, $path, $handler );

		if ( null !== $hydration_result ) {
			$response = $hydration_result;
		} else {
			$response = call_user_func_array( $handler['callback'], array( $request ) );
		}

		/**
		 * Similar to WP core's `rest_request_after_callbacks` filter, this allows to modify the response after it has been generated.
		 * Allows backward compatibility with the `rest_request_after_callbacks` filter by providing the same arguments.
		 *
		 * @since 8.9.0
		 *
		 * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
		 *                                                                   Usually a WP_REST_Response or WP_Error.
		 * @param array                                            $handler  Route handler used for the request.
		 * @param WP_REST_Request                                  $request  Request used to generate the response.
		 */
		$response = apply_filters( 'woocommerce_hydration_request_after_callbacks', $response, $handler, $request );

		return $response;
	}

	/**
	 * Inspired from WP core's `match_request_to_handler`, this matches a given path from available route regexes.
	 * Extracts URL parameters from regex named groups and query string parameters.
	 *
	 * @param string $path The path to match (may include query string).
	 * @param array  $available_routes Available routes in { $regex1 => $contoller_class1, ... } format.
	 *
	 * @return array|null Array with 'controller', 'url_params', and 'query_params' keys, or null if no match.
	 */
	private function match_route_to_handler( $path, $available_routes ) {
		// Parse query string if present.
		$query_params = array();
		$parsed_url   = wp_parse_url( $path );
		$clean_path   = $parsed_url['path'] ?? $path;

		if ( isset( $parsed_url['query'] ) ) {
			parse_str( $parsed_url['query'], $query_params );
		}

		// Match route and extract URL parameters.
		foreach ( $available_routes as $route_path => $controller ) {
			if ( preg_match( '@^' . $route_path . '$@i', $clean_path, $matches ) ) {
				// Extract named groups (URL parameters like 'id').
				$url_params = array_intersect_key(
					$matches,
					array_flip( array_filter( array_keys( $matches ), 'is_string' ) )
				);

				return array(
					'controller'   => $controller,
					'url_params'   => $url_params,
					'query_params' => $query_params,
				);
			}
		}

		return null;
	}

	/**
	 * Disable the nonce check temporarily.
	 */
	protected function disable_nonce_check() {
		add_filter( 'woocommerce_store_api_disable_nonce_check', array( $this, 'disable_nonce_check_callback' ) );
	}

	/**
	 * Callback to disable the nonce check. While we could use `__return_true`, we use a custom named callback so that
	 * we can remove it later without affecting other filters.
	 */
	public function disable_nonce_check_callback() {
		return true;
	}

	/**
	 * Restore the nonce check.
	 */
	protected function restore_nonce_check() {
		remove_filter( 'woocommerce_store_api_disable_nonce_check', array( $this, 'disable_nonce_check_callback' ) );
	}

	/**
	 * Cache notices before hydrating the API if the customer has a session.
	 */
	protected function cache_store_notices() {
		if ( ! did_action( 'woocommerce_init' ) || null === WC()->session ) {
			return;
		}
		$this->cached_store_notices = wc_get_notices();
		wc_clear_notices();
	}

	/**
	 * Restore notices into current session from cache.
	 */
	protected function restore_cached_store_notices() {
		if ( ! did_action( 'woocommerce_init' ) || null === WC()->session ) {
			return;
		}

		wc_set_notices( $this->cached_store_notices );
		$this->cached_store_notices = array();
	}
}
PK     \_,  ,  '  Domain/Services/CheckoutFieldsAdmin.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\Domain\Services;

use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;

/**
 * Service class managing checkout fields and its related extensibility points in the admin area.
 */
class CheckoutFieldsAdmin {

	/**
	 * Checkout field controller.
	 *
	 * @var CheckoutFields
	 */
	private $checkout_fields_controller;

	/**
	 * Sets up core fields.
	 *
	 * @param CheckoutFields $checkout_fields_controller Instance of the checkout field controller.
	 */
	public function __construct( CheckoutFields $checkout_fields_controller ) {
		$this->checkout_fields_controller = $checkout_fields_controller;
	}

	/**
	 * Initialize hooks. This is not run Store API requests.
	 */
	public function init() {
		add_filter( 'woocommerce_admin_billing_fields', array( $this, 'admin_address_fields' ), 10, 3 );
		add_filter( 'woocommerce_admin_billing_fields', array( $this, 'admin_contact_fields' ), 10, 3 );
		add_filter( 'woocommerce_admin_shipping_fields', array( $this, 'admin_address_fields' ), 10, 3 );
		add_filter( 'woocommerce_admin_shipping_fields', array( $this, 'admin_order_fields' ), 10, 3 );
	}

	/**
	 * Converts the shape of a checkout field to match whats needed in the WooCommerce meta boxes.
	 *
	 * @param array  $field The field to format.
	 * @param string $key The field key. This will be used for the ID of the field when passed to the meta box.
	 * @return array Formatted field.
	 */
	protected function format_field_for_meta_box( $field, $key ) {
		$formatted_field = array(
			'id'              => $key,
			'label'           => $field['label'],
			'value'           => $field['value'],
			'type'            => $field['type'],
			'update_callback' => array( $this, 'update_callback' ),
			'show'            => true,
			'wrapper_class'   => 'form-field-wide',
		);

		if ( 'select' === $field['type'] ) {
			$formatted_field['options'] = array_column( $field['options'], 'label', 'value' );
		}

		if ( 'checkbox' === $field['type'] ) {
			$formatted_field['checked_value']   = '1';
			$formatted_field['unchecked_value'] = '0';
		}

		return $formatted_field;
	}

	/**
	 * Updates a field value for an order.
	 *
	 * @param string    $key The field key.
	 * @param mixed     $value The field value.
	 * @param \WC_Order $order The order to update the field for.
	 */
	public function update_callback( $key, $value, $order ) {
		list( $group, $key ) = explode( '/', $key, 2 );
		$group               = CheckoutFields::get_group_name( $group );
		$this->checkout_fields_controller->persist_field_for_order( $key, $value, $order, $group, false );
	}

	/**
	 * Injects address fields in WC admin orders screen.
	 *
	 * @param array             $fields The fields to show.
	 * @param \WC_Order|boolean $order The order to show the fields for.
	 * @param string            $context The context to show the fields for.
	 * @return array
	 */
	public function admin_address_fields( $fields, $order = null, $context = 'edit' ) {
		if ( ! $order instanceof \WC_Order ) {
			return $fields;
		}

		$group_name        = doing_action( 'woocommerce_admin_billing_fields' ) ? 'billing' : 'shipping';
		$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'address', $group_name, $context );
		foreach ( $additional_fields as $key => $field ) {
			$prefixed_key              = CheckoutFields::get_group_key( $group_name ) . $key;
			$additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key );
		}

		array_splice(
			$fields,
			array_search(
				'state',
				array_keys( $fields ),
				true
			) + 1,
			0,
			$additional_fields
		);

		return $fields;
	}

	/**
	 * Injects contact fields in WC admin orders screen.
	 *
	 * @param array             $fields The fields to show.
	 * @param \WC_Order|boolean $order The order to show the fields for.
	 * @param string            $context The context to show the fields for.
	 * @return array
	 */
	public function admin_contact_fields( $fields, $order = null, $context = 'edit' ) {
		if ( ! $order instanceof \WC_Order ) {
			return $fields;
		}

		$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', 'other', $context );

		foreach ( $additional_fields as $key => $field ) {
			$prefixed_key              = CheckoutFields::get_group_key( 'other' ) . $key;
			$additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key );
		}

		return array_merge( $fields, $additional_fields );
	}

	/**
	 * Injects additional fields in WC admin orders screen.
	 *
	 * @param array             $fields The fields to show.
	 * @param \WC_Order|boolean $order The order to show the fields for.
	 * @param string            $context The context to show the fields for.
	 * @return array
	 */
	public function admin_order_fields( $fields, $order = null, $context = 'edit' ) {
		if ( ! $order instanceof \WC_Order ) {
			return $fields;
		}

		$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'order', 'other', $context );

		foreach ( $additional_fields as $key => $field ) {
			$prefixed_key              = CheckoutFields::get_group_key( 'other' ) . $key;
			$additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key );
		}

		return array_merge( $fields, $additional_fields );
	}
}
PK     \U/ya  a  #  Domain/Services/GoogleAnalytics.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Domain\Services;

use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;

/**
 * Service class to integrate Blocks with the Google Analytics extension,
 */
class GoogleAnalytics {
	/**
	 * Instance of the asset API.
	 *
	 * @var AssetApi
	 */
	protected $asset_api;

	/**
	 * Constructor.
	 *
	 * @param AssetApi $asset_api Instance of the asset API.
	 */
	public function __construct( AssetApi $asset_api ) {
		$this->asset_api = $asset_api;
	}

	/**
	 * Hook into WP.
	 */
	public function init() {
		// Require Google Analytics Integration to be activated.
		if ( ! class_exists( 'WC_Google_Analytics_Integration', false ) ) {
			return;
		}
		add_action( 'init', array( $this, 'register_assets' ) );
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
		add_filter( 'script_loader_tag', array( $this, 'async_script_loader_tags' ), 10, 3 );
	}

	/**
	 * Register scripts.
	 */
	public function register_assets() {
		$this->asset_api->register_script( 'wc-blocks-google-analytics', 'assets/client/blocks/wc-blocks-google-analytics.js', [ 'google-tag-manager' ] );
	}

	/**
	 * Enqueue the Google Tag Manager script if prerequisites are met.
	 */
	public function enqueue_scripts() {
		$settings = $this->get_google_analytics_settings();
		$prefix   = strstr( strtoupper( $settings['ga_id'] ), '-', true );

		// Require tracking to be enabled with a valid GA ID.
		if ( ! in_array( $prefix, [ 'G', 'GT' ], true ) ) {
			return;
		}

		/**
		 * Filter to disable Google Analytics tracking.
		 *
		 * @internal Matches filter name in GA extension.
		 * @since 4.9.0
		 *
		 * @param boolean $disable_tracking If true, tracking will be disabled.
		 */
		if ( apply_filters( 'woocommerce_ga_disable_tracking', ! wc_string_to_bool( $settings['ga_event_tracking_enabled'] ) ) ) {
			return;
		}

		if ( ! wp_script_is( 'google-tag-manager', 'registered' ) ) {
			// Using an array with strategies as the final argument to wp_register_script was introduced in WP 6.3.
			// WC requires at least 6.3 at the point of adding this, so it's safe to leave in without version checks.
			// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
			wp_register_script(
				'google-tag-manager',
				'https://www.googletagmanager.com/gtag/js?id=' . $settings['ga_id'],
				[],
				null,
				[
					'in_footer' => false,
					'strategy'  => 'async',
				]
			);
			wp_add_inline_script(
				'google-tag-manager',
				"
	window.dataLayer = window.dataLayer || [];
	function gtag(){dataLayer.push(arguments);}
	gtag('js', new Date());
	gtag('config', '" . esc_js( $settings['ga_id'] ) . "', { 'send_page_view': false });"
			);
		}
		wp_enqueue_script( 'wc-blocks-google-analytics' );
	}

	/**
	 * Get settings from the GA integration extension.
	 *
	 * @return array
	 */
	private function get_google_analytics_settings() {
		return wp_parse_args(
			get_option( 'woocommerce_google_analytics_settings' ),
			[
				'ga_id'                     => '',
				'ga_event_tracking_enabled' => 'no',
			]
		);
	}

	/**
	 * Add async to script tags with defined handles.
	 *
	 * @param string $tag HTML for the script tag.
	 * @param string $handle Handle of script.
	 * @param string $src Src of script.
	 * @return string
	 */
	public function async_script_loader_tags( $tag, $handle, $src ) {
		if ( ! in_array( $handle, array( 'google-tag-manager' ), true ) ) {
			return $tag;
		}
		// If script was output manually in wp_head, abort.
		if ( did_action( 'woocommerce_gtag_snippet' ) ) {
			return '';
		}
		return str_replace( '<script src', '<script async src', $tag );
	}
}
PK     \&B  B  ,  Domain/Services/Email/CustomerNewAccount.phpnu [        <?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\Blocks\Domain\Services\Email;

use Automattic\WooCommerce\Blocks\Domain\Package;

/**
 * Customer New Account. Previously used for blocks, but now replaced by the core email.
 *
 * @deprecated This class can't be removed due to https://github.com/woocommerce/woocommerce/issues/52311.
 */
class CustomerNewAccount extends \WC_Email {
	/**
	 * Constructor.
	 *
	 * @param Package $package An instance of (Woo Blocks) Package.
	 */
	public function __construct( Package $package ) {
		parent::__construct();
	}
}
PK     \1q7  7  *  Domain/Services/CheckoutFieldsFrontend.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\Domain\Services;

use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFieldsSchema\DocumentObject;
use WC_Customer;
use WC_Order;

/**
 * Service class managing checkout fields and its related extensibility points on the frontend.
 */
class CheckoutFieldsFrontend {

	/**
	 * Checkout field controller.
	 *
	 * @var CheckoutFields
	 */
	private $checkout_fields_controller;

	/**
	 * Sets up core fields.
	 *
	 * @param CheckoutFields $checkout_fields_controller Instance of the checkout field controller.
	 */
	public function __construct( CheckoutFields $checkout_fields_controller ) {
		$this->checkout_fields_controller = $checkout_fields_controller;
	}

	/**
	 * Initialize hooks. This is not run Store API requests.
	 */
	public function init() {
		// Show custom checkout fields on the order details page.
		add_action( 'woocommerce_order_details_after_customer_address', array( $this, 'render_order_address_fields' ), 10, 2 );
		add_action( 'woocommerce_order_details_after_customer_details', array( $this, 'render_order_other_fields' ), 10 );

		// Show custom checkout fields on the My Account page.
		add_action( 'woocommerce_my_account_after_my_address', array( $this, 'render_address_fields' ), 10, 1 );

		// Edit account form under my account (for contact details).
		add_filter( 'woocommerce_edit_account_form_fields', array( $this, 'edit_account_form_fields' ), 10, 1 );
		add_action( 'woocommerce_save_account_details', array( $this, 'save_account_form_fields' ), 10, 1 );

		// Edit address form under my account.
		add_filter( 'woocommerce_address_to_edit', array( $this, 'edit_address_fields' ), 10, 2 );
		add_action( 'woocommerce_customer_save_address', array( $this, 'save_address_fields' ), 10, 4 );
	}

	/**
	 * Render custom fields.
	 *
	 * @param array $fields List of additional fields with values.
	 * @return string
	 */
	protected function render_additional_fields( $fields ) {
		return ! empty( $fields ) ? '<dl class="wc-block-components-additional-fields-list">' . implode( '', array_map( array( $this, 'render_additional_field' ), $fields ) ) . '</dl>' : '';
	}

	/**
	 * Render custom field.
	 *
	 * @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'] )
		);
	}

	/**
	 * Renders address fields on the order details page.
	 *
	 * @param string   $address_type Type of address (billing or shipping).
	 * @param WC_Order $order Order object.
	 */
	public function render_order_address_fields( $address_type, $order ) {
		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		echo $this->render_additional_fields( $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'address', $address_type, 'view' ) );
	}

	/**
	 * Renders additional fields on the order details page.
	 *
	 * @param WC_Order $order Order object.
	 */
	public function render_order_other_fields( $order ) {
		$fields = array_merge(
			$this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', 'other', 'view' ),
			$this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'order', 'other', 'view' ),
		);

		$context = array(
			'caller' => 'CheckoutFieldsFrontend::render_order_other_fields',
			'order'  => $order,
		);

		$fields = $this->checkout_fields_controller->filter_fields_for_order_confirmation( $fields, $context );

		if ( ! $fields ) {
			return;
		}

		echo '<section class="wc-block-order-confirmation-additional-fields-wrapper">';
		echo '<h2>' . esc_html__( 'Additional information', 'woocommerce' ) . '</h2>';
		echo $this->render_additional_fields( $fields ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		echo '</section>';
	}

	/**
	 * Renders address fields on the account page.
	 *
	 * @param string $address_type Type of address (billing or shipping).
	 */
	public function render_address_fields( $address_type ) {
		if ( ! in_array( $address_type, array( 'billing', 'shipping' ), true ) ) {
			return;
		}

		$customer = new WC_Customer( get_current_user_id() );

		$document_object = new DocumentObject();
		$document_object->set_customer( $customer );
		$document_object->set_context( $address_type . '_address' );
		$fields = $this->checkout_fields_controller->get_contextual_fields_for_location( 'address', $document_object );

		if ( ! $fields || ! $customer ) {
			return;
		}

		foreach ( $fields as $key => $field ) {
			$value = $this->checkout_fields_controller->format_additional_field_value(
				$this->checkout_fields_controller->get_field_from_object( $key, $customer, $address_type ),
				$field
			);

			if ( ! $value ) {
				continue;
			}

			printf( '<br><strong>%s</strong>: %s', wp_kses_post( $field['label'] ), wp_kses_post( $value ) );
		}
	}

	/**
	 * Adds additional contact fields to the My Account edit account form.
	 */
	public function edit_account_form_fields() {
		$customer = new WC_Customer( get_current_user_id() );

		$document_object = new DocumentObject();
		$document_object->set_customer( $customer );
		$document_object->set_context( 'contact' );
		$fields = $this->checkout_fields_controller->get_contextual_fields_for_location( 'contact', $document_object );

		foreach ( $fields as $key => $field ) {
			$field_key           = CheckoutFields::get_group_key( 'other' ) . $key;
			$form_field          = $field;
			$form_field['id']    = $field_key;
			$form_field['value'] = $this->checkout_fields_controller->get_field_from_object( $key, $customer, 'contact' );

			if ( 'select' === $field['type'] ) {
				$form_field['options'] = array_column( $field['options'], 'label', 'value' );
			}

			if ( 'checkbox' === $field['type'] ) {
				$form_field['checked_value']   = '1';
				$form_field['unchecked_value'] = '0';
			}

			woocommerce_form_field( $field_key, $form_field, wc_get_post_data_by_key( $key, $form_field['value'] ) );
		}
	}

	/**
	 * Adds additional address fields to the My Account edit address form.
	 *
	 * @param array  $address Address fields.
	 * @param string $address_type Type of address (billing or shipping).
	 * @return array Updated address fields.
	 */
	public function edit_address_fields( $address, $address_type ) {
		$customer = new WC_Customer( get_current_user_id() );

		$document_object = new DocumentObject();
		$document_object->set_customer( $customer );
		$document_object->set_context( $address_type . '_address' );
		$fields = $this->checkout_fields_controller->get_contextual_fields_for_location( 'address', $document_object );

		foreach ( $fields as $key => $field ) {
			$field_key                      = CheckoutFields::get_group_key( $address_type ) . $key;
			$address[ $field_key ]          = $field;
			$address[ $field_key ]['value'] = $this->checkout_fields_controller->get_field_from_object( $key, $customer, $address_type );

			if ( 'select' === $field['type'] ) {
				$address[ $field_key ]['options'] = array_column( $field['options'], 'label', 'value' );

				// If a placeholder is set, add a placeholder option if it doesn't exist already.
				if (
					! empty( $address[ $field_key ]['placeholder'] )
					&& ! array_key_exists( '', $address[ $field_key ]['options'] )
				) {
					$address[ $field_key ]['options'] = array( '' => $address[ $field_key ]['placeholder'] ) + $address[ $field_key ]['options'];
				}
			}

			if ( 'checkbox' === $field['type'] ) {
				$address[ $field_key ]['checked_value']   = '1';
				$address[ $field_key ]['unchecked_value'] = '0';
			}
		}

		return $address;
	}

	/**
	 * Validates and saves additional address fields to the customer object on the My Account page.
	 *
	 * Customer is not provided by this hook so we handle save here.
	 *
	 * @param integer $user_id User ID.
	 */
	public function save_account_form_fields( $user_id ) {
		try {
			$customer = new WC_Customer( $user_id );
			$result   = $this->update_additional_fields_for_customer( $customer, 'contact', 'other' );

			if ( is_wp_error( $result ) ) {
				foreach ( $result->get_error_messages() as $error_message ) {
					wc_add_notice( $error_message, 'error' );
				}
			}

			$customer->save();
		} catch ( \Exception $e ) {
			wc_add_notice(
				sprintf(
					/* translators: %s: Error message. */
					__( 'An error occurred while saving account details: %s', 'woocommerce' ),
					esc_html( $e->getMessage() )
				),
				'error'
			);
		}
	}

	/**
	 * For the My Account page, save address fields. This uses the Store API endpoint for saving addresses so
	 * extensibility hooks are consistent across the codebase.
	 *
	 * The caller saves the customer object if there are no errors. Nonces are checked before this method executes.
	 *
	 * @param integer     $user_id User ID.
	 * @param string      $address_type Type of address (billing or shipping).
	 * @param array       $address Address fields.
	 * @param WC_Customer $customer Customer object.
	 */
	public function save_address_fields( $user_id, $address_type, $address = [], $customer = null ) {
		try {
			$customer = $customer ?? new WC_Customer( $user_id );
			$result   = $this->update_additional_fields_for_customer( $customer, 'address', $address_type );

			if ( is_wp_error( $result ) ) {
				foreach ( $result->get_error_messages() as $error_message ) {
					wc_add_notice( $error_message, 'error' );
				}
			}

			$customer->save();
		} catch ( \Exception $e ) {
			wc_add_notice(
				sprintf(
					/* translators: %s: Error message. */
					__( 'An error occurred while saving address details: %s', 'woocommerce' ),
					esc_html( $e->getMessage() )
				),
				'error'
			);
		}
	}

	/**
	 * Get posted additional field values.
	 *
	 * @param string  $location The location to get fields for.
	 * @param string  $group The group to get fields for.
	 * @param boolean $sanitize Whether to sanitize the field values.
	 * @return array The posted field values and sanitized field values.
	 */
	protected function get_posted_additional_field_values( $location, $group, $sanitize = true ) {
		$additional_fields = $this->checkout_fields_controller->get_fields_for_location( $location );
		$field_values      = [];

		// phpcs:disable WordPress.Security.NonceVerification.Missing
		foreach ( $additional_fields as $field_key => $field_data ) {
			$post_key                   = CheckoutFields::get_group_key( $group ) . $field_key;
			$field_values[ $field_key ] = wc_clean( wp_unslash( $_POST[ $post_key ] ?? '' ) );

			if ( $sanitize ) {
				$field_values[ $field_key ] = $this->checkout_fields_controller->sanitize_field( $field_key, $field_values[ $field_key ] );
			}
		}
		// phpcs:enable WordPress.Security.NonceVerification.Missing
		return $field_values;
	}

	/**
	 * Validate and save additional fields for a given customer.
	 *
	 * @param WC_Customer $customer Customer object.
	 * @param string      $location Location to save fields for.
	 * @param string      $group Group to save fields for.
	 * @return true|\WP_Error True if successful, \WP_Error if there are errors.
	 */
	protected function update_additional_fields_for_customer( $customer, $location, $group ) {
		// Get all values from the POST request before validating.
		$field_values           = $this->get_posted_additional_field_values( $location, $group, false ); // These values are used to see if required fields have values.
		$sanitized_field_values = $this->get_posted_additional_field_values( $location, $group ); // These values are used to validate custom rules, generate the document object, and save fields to the account.

		$document_object = new DocumentObject(
			[
				'customer' => [
					( 'address' === $location ? $group . '_address' : 'additional_fields' ) => $sanitized_field_values,
				],
			]
		);
		$document_object->set_customer( $customer );
		$document_object->set_context( 'address' === $location ? $group . '_address' : $location );
		$fields = $this->checkout_fields_controller->get_contextual_fields_for_location( $location, $document_object );

		// Holds values to be persisted to the customer object.
		$persist_fields = [];
		$errors         = new \WP_Error();

		// Validate individual fields agains the document object. Errors are added to the $errors object, and each field is validated regardless of other field errors.
		foreach ( $fields as $field_key => $field ) {
			$field_value = $field_values[ $field_key ];

			if ( empty( $field_value ) ) {
				if ( true === $field['required'] ) {
					$errors->add(
						'required_field',
						/* translators: %s: is the field label */
						sprintf( __( '%s is required', 'woocommerce' ), '<strong>' . $field['label'] . '</strong>' )
					);
					continue;
				}
				$persist_fields[ $field_key ] = '';
				continue;
			}

			$sanitized_field_value = $sanitized_field_values[ $field_key ];
			$valid_check           = $this->checkout_fields_controller->validate_field( $field, $sanitized_field_value );

			if ( is_wp_error( $valid_check ) && $valid_check->has_errors() ) {
				// Get one error message from the WP_Error object per field to avoid overlapping error messages.
				$errors->add( $valid_check->get_error_code(), $valid_check->get_error_message() );
				continue;
			}

			$persist_fields[ $field_key ] = $sanitized_field_value;
		}

		// Validate all fields for this location (this runs custom validation callbacks). If an error is found, no values will be persisted to the customer object.
		$location_validation = $this->checkout_fields_controller->validate_fields_for_location( $sanitized_field_values, $location, $group );

		if ( is_wp_error( $location_validation ) && $location_validation->has_errors() ) {
			$errors->merge_from( $location_validation );
			return $errors;
		}

		foreach ( $persist_fields as $field_key => $field_value ) {
			$this->checkout_fields_controller->persist_field_for_customer( $field_key, $field_value, $customer, $group );
		}

		return $errors->has_errors() ? $errors : true;
	}
}
PK     \5/  /    Domain/Services/Notices.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Domain\Services;

use Automattic\WooCommerce\Blocks\Domain\Package;
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;

/**
 * Service class for adding new-style Notices to WooCommerce core.
 *
 * @internal
 */
class Notices {
	/**
	 * Holds the Package instance
	 *
	 * @var Package
	 */
	private $package;

	/**
	 * Templates used for notices.
	 *
	 * @var array
	 */
	private $notice_templates = array(
		'notices/error.php',
		'notices/notice.php',
		'notices/success.php',
	);

	/**
	 * Constructor
	 *
	 * @param Package $package An instance of the package class.
	 */
	public function __construct( Package $package ) {
		$this->package = $package;
	}

	/**
	 * Initialize notice hooks.
	 */
	public function init() {
		add_action(
			'after_setup_theme',
			function () {
				/**
				 * Allow classic theme developers to opt-in to using block notices.
				 *
				 * @since 8.8.0
				 * @param bool $use_block_notices_in_classic_theme Whether to use block notices in classic theme.
				 * @return bool
				 */
				if ( wp_is_block_theme() || apply_filters( 'woocommerce_use_block_notices_in_classic_theme', false ) ) {
					add_filter( 'wc_get_template', [ $this, 'get_notices_template' ], 10, 5 );
				}
			}
		);

		add_filter( 'woocommerce_kses_notice_allowed_tags', [ $this, 'add_kses_notice_allowed_tags' ] );
		add_action( 'wp_head', [ $this, 'enqueue_notice_styles' ] );
	}

	/**
	 * Allow SVG icon in notices.
	 *
	 * @param array $allowed_tags Allowed tags.
	 * @return array
	 */
	public function add_kses_notice_allowed_tags( $allowed_tags ) {
		$svg_args = array(
			'svg'  => array(
				'aria-hidden' => true,
				'xmlns'       => true,
				'width'       => true,
				'height'      => true,
				'viewbox'     => true,
				'focusable'   => true,
			),
			'path' => array(
				'd' => true,
			),
		);
		return array_merge( $allowed_tags, $svg_args );
	}

	/**
	 * Replaces core notice templates with those from blocks.
	 *
	 * The new notice templates match block components with matching icons and styling. The differences are:
	 * 1. Core has notices for info, success, and error notices, blocks has notices for info, success, error,
	 * warning, and a default notice type.
	 * 2. The block notices use different CSS classes to the core notices. Core uses `woocommerce-message`, `is-info`
	 * and `is-error` classes, blocks uses `wc-block-components-notice-banner is-error`,
	 * `wc-block-components-notice-banner is-info`, and `wc-block-components-notice-banner is-success`.
	 * 3. The markup of the notices is different, with the block notices using SVG icons and a slightly different
	 * structure to accommodate this.
	 *
	 * @param string $template Located template path.
	 * @param string $template_name Template name.
	 * @param array  $args Template arguments.
	 * @param string $template_path Template path.
	 * @param string $default_path Default path.
	 * @return string
	 */
	public function get_notices_template( $template, $template_name, $args, $template_path, $default_path ) {
		if ( in_array( $template_name, $this->notice_templates, true ) ) {
			$directory = get_stylesheet_directory();
			$file      = $directory . '/woocommerce/' . $template_name;

			if ( file_exists( $file ) ) {
				return $file;
			}

			$template = $this->package->get_path( 'templates/block-' . $template_name );
			wp_enqueue_style( 'wc-blocks-style' );
		}

		return $template;
	}

	/**
	 * Replaces all notices with the new block-based notices.
	 *
	 * @return void
	 */
	public function enqueue_notice_styles() {
		wp_enqueue_style( 'wc-blocks-style' );
	}
}
PK     \`1U      Domain/Services/functions.phpnu [        <?php

use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;

if ( ! function_exists( 'woocommerce_register_additional_checkout_field' ) ) {
	/**
	 * Register a checkout field.
	 *
	 * @param array $options Field arguments. See CheckoutFields::register_checkout_field() for details.
	 * @throws \Exception If field registration fails.
	 */
	function woocommerce_register_additional_checkout_field( $options ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore

		// Check if `woocommerce_blocks_loaded` ran. If not then the CheckoutFields class will not be available yet.
		// In that case, re-hook `woocommerce_blocks_loaded` and try running this again.
		$woocommerce_blocks_loaded_ran = did_action( 'woocommerce_blocks_loaded' );
		if ( ! $woocommerce_blocks_loaded_ran ) {
			add_action(
				'woocommerce_blocks_loaded',
				function () use ( $options ) {
					woocommerce_register_additional_checkout_field( $options );
				}
			);
			return;
		}
		$checkout_fields = Package::container()->get( CheckoutFields::class );
		$result          = $checkout_fields->register_checkout_field( $options );
		if ( is_wp_error( $result ) ) {
			throw new \Exception( esc_attr( $result->get_error_message() ) );
		}
	}
}

if ( ! function_exists( '__experimental_woocommerce_blocks_register_checkout_field' ) ) {

	/**
	 * Register a checkout field.
	 *
	 * @param array $options Field arguments. See CheckoutFields::register_checkout_field() for details.
	 * @throws \Exception If field registration fails.
	 * @deprecated 5.6.0 Use woocommerce_register_additional_checkout_field() instead.
	 */
	function __experimental_woocommerce_blocks_register_checkout_field( $options ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
		wc_deprecated_function( __FUNCTION__, '8.9.0', 'woocommerce_register_additional_checkout_field' );
		woocommerce_register_additional_checkout_field( $options );
	}
}

if ( ! function_exists( '__internal_woocommerce_blocks_deregister_checkout_field' ) ) {
	/**
	 * Deregister a checkout field.
	 *
	 * @param string $field_id Field ID.
	 * @throws \Exception If field deregistration fails.
	 * @internal
	 */
	function __internal_woocommerce_blocks_deregister_checkout_field( $field_id ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
		$checkout_fields = Package::container()->get( CheckoutFields::class );
		$result          = $checkout_fields->deregister_checkout_field( $field_id );
		if ( is_wp_error( $result ) ) {
			throw new \Exception( esc_attr( $result->get_error_message() ) );
		}
	}
}
PK     \ӟ    !  Domain/Services/FeatureGating.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Domain\Services;

use Automattic\WooCommerce\Admin\DeprecatedClassFacade;

/**
 * Service class that used to handle feature flags. That functionality
 * is removed now and it is only used to determine "environment".
 *
 * @internal
 *
 * @deprecated since 9.6.0, use wp_get_environment_type() instead.
 */
class FeatureGating extends DeprecatedClassFacade {
	/**
	 * The version that this class was deprecated in.
	 *
	 * @var string
	 */
	protected static $deprecated_in_version = '9.6.0';

	/**
	 * Constructor
	 *
	 * @param string $environment Hardcoded environment value. Useful for tests.
	 */
	public function __construct( $environment = 'unset' ) {
	}
}
PK     \I  I  "  Domain/Services/CheckoutFields.phpnu [        <?php
declare( strict_types = 1);

namespace Automattic\WooCommerce\Blocks\Domain\Services;

use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFieldsSchema\{
	DocumentObject, Validation
};
use WC_Customer;
use WC_Data;
use WC_Order;
use WP_Error;

/**
 * Service class managing checkout fields and its related extensibility points.
 */
class CheckoutFields {

	/**
	 * Additional checkout fields.
	 *
	 * @var array
	 */
	private $additional_fields = [];

	/**
	 * Fields locations.
	 *
	 * @var array
	 */
	private $fields_locations;

	/**
	 * Supported field types
	 *
	 * @var array
	 */
	private $supported_field_types = [ 'text', 'select', 'checkbox' ];

	/**
	 * Groups of fields to be saved.
	 *
	 * @var array
	 */
	private $groups = [ 'billing', 'shipping', 'other' ];

	/**
	 * Instance of the asset data registry.
	 *
	 * @var AssetDataRegistry
	 */
	private $asset_data_registry;

	/**
	 * Billing fields meta key.
	 *
	 * @var string
	 */
	const BILLING_FIELDS_PREFIX = '_wc_billing/';

	/**
	 * Shipping fields meta key.
	 *
	 * @var string
	 */
	const SHIPPING_FIELDS_PREFIX = '_wc_shipping/';

	/**
	 * Additional fields meta key.
	 *
	 * @var string
	 * @deprecated 8.9.0 Use OTHER_FIELDS_PREFIX instead.
	 */
	const ADDITIONAL_FIELDS_PREFIX = '_wc_additional/';

	/**
	 * Other fields meta key.
	 *
	 * @var string
	 */
	const OTHER_FIELDS_PREFIX = '_wc_other/';

	/**
	 * Sets up core fields.
	 *
	 * @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
	 */
	public function __construct( AssetDataRegistry $asset_data_registry ) {
		$this->asset_data_registry = $asset_data_registry;
		$this->fields_locations    = [
			// omit email from shipping and billing fields.
			'address' => array_merge( \array_diff_key( $this->get_core_fields_keys(), array( 'email' ) ) ),
			'contact' => array( 'email' ),
			'order'   => [],
		];
	}

	/**
	 * Initialize hooks.
	 */
	public function init() {
		add_filter( 'woocommerce_get_country_locale_default', array( $this, 'update_default_locale_with_fields' ) );
		add_action( 'woocommerce_blocks_checkout_enqueue_data', array( $this, 'add_fields_data' ) );
		add_action( 'woocommerce_blocks_cart_enqueue_data', array( $this, 'add_fields_data' ) );
		add_filter( 'woocommerce_customer_allowed_session_meta_keys', array( $this, 'add_session_meta_keys' ) );
	}

	/**
	 * Add fields data to the asset data registry.
	 */
	public function add_fields_data() {
		$this->asset_data_registry->add( 'defaultFields', array_merge( $this->get_core_fields(), $this->get_additional_fields() ) );
		$this->asset_data_registry->add( 'addressFieldsLocations', $this->fields_locations );
	}

	/**
	 * Add session meta keys.
	 *
	 * This is an allow-list of meta data keys which we want to store in session.
	 *
	 * @param array $keys Session meta keys.
	 * @return array
	 */
	public function add_session_meta_keys( $keys ) {
		$meta_keys = array();
		try {
			foreach ( $this->get_additional_fields() as $field_key => $field ) {
				if ( 'address' === $field['location'] ) {
					$meta_keys[] = self::BILLING_FIELDS_PREFIX . $field_key;
					$meta_keys[] = self::SHIPPING_FIELDS_PREFIX . $field_key;
				} else {
					$meta_keys[] = self::OTHER_FIELDS_PREFIX . $field_key;
				}
			}
		} catch ( \Throwable $e ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
			trigger_error(
				sprintf(
					'Error adding session meta keys for checkout fields. %s',
					esc_attr( $e->getMessage() )
				),
				E_USER_WARNING
			);

			return $keys;
		}

		return array_merge( $keys, $meta_keys );
	}

	/**
	 * If a field does not declare a sanitization callback, this is the default sanitization callback.
	 *
	 * @param mixed $value Value to sanitize.
	 * @param array $field Field data.
	 * @return mixed
	 */
	public function default_sanitize_callback( $value, $field ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
		return $value;
	}

	/**
	 * If a field does not declare a validation callback, this is the default validation callback.
	 *
	 * @param mixed $value Value to sanitize.
	 * @param array $field Field data.
	 * @return WP_Error|void If there is a validation error, return an WP_Error object.
	 */
	public function default_validate_callback( $value, $field ) {
		if ( true === $field['required'] && empty( $value ) ) {
			return new WP_Error(
				'woocommerce_required_checkout_field',
				sprintf(
					// translators: %s is field key.
					__( 'The field %s is required.', 'woocommerce' ),
					$field['id']
				)
			);
		}
	}

	/**
	 * Registers an additional field for Checkout.
	 *
	 * @param array $options The field options.
	 *
	 * @return WP_Error|void True if the field was registered, a WP_Error otherwise.
	 */
	public function register_checkout_field( $options ) {
		// Check the options and show warnings if they're not supplied. Return early if an error that would prevent registration is encountered.
		if ( false === $this->validate_options( $options ) ) {
			return;
		}

		// The above validate_options function ensures these options are valid. Type might not be supplied but then it defaults to text.
		$field_data = wp_parse_args(
			$options,
			[
				'id'                         => '',
				'label'                      => '',
				/* translators: %s Field label. */
				'optionalLabel'              => sprintf( __( '%s (optional)', 'woocommerce' ), $options['label'] ),
				'location'                   => '',
				'type'                       => 'text',
				'hidden'                     => false,
				'required'                   => false,
				'attributes'                 => [],
				'show_in_order_confirmation' => true,
				'sanitize_callback'          => array( $this, 'default_sanitize_callback' ),
				'validate_callback'          => array( $this, 'default_validate_callback' ),
				'validation'                 => [],
			],
		);

		$field_data['attributes'] = $this->register_field_attributes( $field_data['id'], $field_data['attributes'] );
		$field_data               = $this->process_field_options( $field_data, $options );

		// $field_data will be false if an error that will prevent the field being registered is encountered.
		if ( false === $field_data ) {
			return;
		}

		// Insert new field into the correct location array.
		$this->additional_fields[ $field_data['id'] ]        = $field_data;
		$this->fields_locations[ $field_data['location'] ][] = $field_data['id'];
	}

	/**
	 * Returns true if the field is required. Takes rules into consideration if a document object is provided.
	 *
	 * @param array|string        $field The field array or field key.
	 * @param DocumentObject|null $document_object The document object.
	 * @return bool
	 */
	public function is_required_field( $field, $document_object = null ) {
		if ( is_string( $field ) ) {
			$field = $this->additional_fields[ $field ] ?? [];
		}

		if ( empty( $field ) ) {
			return false;
		}

		if ( $document_object ) {
			// Hidden fields cannot be required.
			if ( $this->is_hidden_field( $field, $document_object ) ) {
				return false;
			}
			if ( $this->contains_valid_rules( $field['required'] ) ) {
				return true === Validation::validate_document_object( $document_object, $field['required'] );
			}
		}
		return true === $field['required'];
	}

	/**
	 * Returns true if the field is hidden. Takes rules into consideration if a document object is provided.
	 *
	 * @param array|string        $field The field array or field key.
	 * @param DocumentObject|null $document_object The document object.
	 * @return bool
	 */
	public function is_hidden_field( $field, $document_object = null ) {
		if ( is_string( $field ) ) {
			$field = $this->additional_fields[ $field ] ?? [];
		}
		if ( $document_object && $this->contains_valid_rules( $field['hidden'] ) ) {
			return true === Validation::validate_document_object( $document_object, $field['hidden'] );
		}
		return false; // Fields cannot be registered as hidden.
	}

	/**
	 * Returns true if the field is conditionally required or rendered.
	 *
	 * @param array|string $field The field array or field key.
	 * @return bool
	 */
	public function is_conditional_field( $field ) {
		if ( is_string( $field ) ) {
			$field = $this->additional_fields[ $field ] ?? [];
		}
		return $this->contains_valid_rules( $field['required'] ) || $this->contains_valid_rules( $field['hidden'] );
	}

	/**
	 * Validates a field against the given document object and context.
	 *
	 * @param array               $field The field.
	 * @param DocumentObject|null $document_object The document object.
	 * @return bool|\WP_Error True if the field is valid, a WP_Error otherwise.
	 */
	public function is_valid_field( $field, $document_object = null ) {
		if ( $document_object && $this->contains_valid_rules( $field['validation'] ) ) {
			$field_schema = Validation::get_field_schema_with_context( $field['id'], $field['validation'], $document_object->get_context() );
			return Validation::validate_document_object( $document_object, $field_schema );
		}
		return true;
	}

	/**
	 * Returns true if the property is an array and not empty.
	 *
	 * @param mixed $property The property to check.
	 * @return bool
	 */
	protected function contains_valid_rules( $property ) {
		return is_array( $property ) && ! empty( $property );
	}

	/**
	 * Returns the validate callback for a given field.
	 *
	 * @param array               $field The field.
	 * @param DocumentObject|null $document_object The document object.
	 * @return callable The validate callback.
	 */
	public function get_validate_callback( $field, $document_object = null ) {
		if ( is_string( $field ) ) {
			$field = $this->additional_fields[ $field ] ?? [];
		}
		if ( $document_object && $this->contains_valid_rules( $field['validation'] ) ) {
			return function ( $field_value, $field ) use ( $document_object ) {
				$errors = new WP_Error();

				// Only validate if we have a field.
				if ( ! $field ) {
					return true;
				}

				// Evaluate custom validation schema rules on the field.
				$validate_result = $this->is_valid_field( $field, $document_object );

				if ( is_wp_error( $validate_result ) ) {
					/* translators: %s: is the field label */
					$error_message = sprintf( __( 'Please provide a valid %s', 'woocommerce' ), $field['label'] );
					$error_code    = 'woocommerce_invalid_checkout_field';
					$errors->add( $error_code, $error_message );
				}

				return $errors->has_errors() ? $errors : true;
			};
		}
		return $field['validate_callback'] ?? null;
	}

	/**
	 * Deregister a checkout field.
	 *
	 * @param string $field_id The field ID.
	 *
	 * @internal
	 */
	public function deregister_checkout_field( $field_id ) {
		if ( empty( $this->additional_fields[ $field_id ] ) ) {
			return;
		}

		$location = $this->get_field_location( $field_id );

		if ( ! $location ) {
			return;
		}

		// Remove the field from the fields_locations array.
		$this->fields_locations[ $location ] = array_diff( $this->fields_locations[ $location ], array( $field_id ) );

		// Remove the field from the additional_fields array.
		unset( $this->additional_fields[ $field_id ] );
	}

	/**
	 * Validates the "base" options (id, label, location) and shows warnings if they're not supplied.
	 *
	 * @param array $options The options supplied during field registration.
	 * @return bool false if an error was encountered, true otherwise.
	 */
	private function validate_options( &$options ) {
		if ( empty( $options['id'] ) ) {
			_doing_it_wrong( 'woocommerce_register_additional_checkout_field', 'A checkout field cannot be registered without an id.', '8.6.0' );
			return false;
		}

		// Having fewer than 2 after exploding around a / means there is no namespace.
		if ( count( explode( '/', $options['id'] ) ) < 2 ) {
			$message = sprintf( 'Unable to register field with id: "%s". %s', $options['id'], 'A checkout field id must consist of namespace/name.' );
			_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
			return false;
		}

		if ( empty( $options['label'] ) ) {
			$message = sprintf( 'Unable to register field with id: "%s". %s', $options['id'], 'The field label is required.' );
			_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
			return false;
		}

		if ( empty( $options['location'] ) ) {
			$message = sprintf( 'Unable to register field with id: "%s". %s', $options['id'], 'The field location is required.' );
			_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
			return false;
		}

		if ( 'additional' === $options['location'] ) {
			wc_deprecated_argument( 'location', '8.9.0', 'The "additional" location is deprecated. Use "order" instead.' );
			$options['location'] = 'order';
		}

		if ( ! in_array( $options['location'], array_keys( $this->fields_locations ), true ) ) {
			$message = sprintf( 'Unable to register field with id: "%s". %s', $options['id'], 'The field location is invalid.' );
			_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
			return false;
		}

		// At this point, the essentials fields and its location should be set and valid.
		$location = $options['location'];
		$id       = $options['id'];

		// Check to see if field is already in the array.
		if ( ! empty( $this->additional_fields[ $id ] ) || in_array( $id, $this->fields_locations[ $location ], true ) ) {
			$message = sprintf( 'Unable to register field with id: "%s". %s', $id, 'The field is already registered.' );
			_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
			return false;
		}

		if ( ! empty( $options['type'] ) ) {
			if ( ! in_array( $options['type'], $this->supported_field_types, true ) ) {
				$message = sprintf(
					'Unable to register field with id: "%s". Registering a field with type "%s" is not supported. The supported types are: %s.',
					$id,
					$options['type'],
					implode( ', ', $this->supported_field_types )
				);
				_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
				return false;
			}
		}

		if ( ! empty( $options['sanitize_callback'] ) && ! is_callable( $options['sanitize_callback'] ) ) {
			$message = sprintf( 'Unable to register field with id: "%s". %s', $id, 'The sanitize_callback must be a valid callback.' );
			_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
			return false;
		}

		if ( ! empty( $options['validate_callback'] ) && ! is_callable( $options['validate_callback'] ) ) {
			$message = sprintf( 'Unable to register field with id: "%s". %s', $id, 'The validate_callback must be a valid callback.' );
			_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
			return false;
		}

		if ( ! empty( $options['hidden'] ) && true === $options['hidden'] ) {
			// Hidden fields are not supported right now. They will be registered with hidden => false.
			$message = sprintf( 'Registering a field with hidden set to true is not supported. The field "%s" will be registered as visible.', $id );
			_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
			// Don't return here unlike the other fields because this is not an issue that will prevent registration.
		}

		$rule_fields = [ 'required', 'hidden', 'validation' ];
		$allow_bool  = [ 'required', 'hidden' ];

		foreach ( $rule_fields as $rule_field ) {
			if ( ! empty( $options[ $rule_field ] ) ) {
				if ( in_array( $rule_field, $allow_bool, true ) && is_bool( $options[ $rule_field ] ) ) {
					continue;
				}

				$valid = Validation::is_valid_schema( $options[ $rule_field ] );

				if ( is_wp_error( $valid ) ) {
					$message = sprintf( 'Unable to register field with id: "%s". %s', $options['id'], $rule_field . ': ' . $valid->get_error_message() );
					_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
					return false;
				}
			}
		}

		return true;
	}

	/**
	 * Processes the options for a field type and returns the new field_options array.
	 *
	 * @param array $field_data The field data array to be updated.
	 * @param array $options    The options supplied during field registration.
	 * @return array The updated $field_data array.
	 */
	private function process_field_options( $field_data, $options ) {
		if ( 'checkbox' === $field_data['type'] ) {
			$field_data = $this->process_checkbox_field( $field_data, $options );
		} elseif ( 'select' === $field_data['type'] ) {
			$field_data = $this->process_select_field( $field_data, $options );
		}
		return $field_data;
	}

	/**
	 * Processes the options for a select field and returns the new field_options array.
	 *
	 * @param array $field_data  The field data array to be updated.
	 * @param array $options     The options supplied during field registration.
	 *
	 * @return array|false The updated $field_data array or false if an error was encountered.
	 */
	private function process_select_field( $field_data, $options ) {
		$id = $options['id'];

		if ( empty( $options['options'] ) || ! is_array( $options['options'] ) ) {
			$message = sprintf( 'Unable to register field with id: "%s". %s', $id, 'Fields of type "select" must have an array of "options".' );
			_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
			return false;
		}
		$cleaned_options = [];
		$added_values    = [];

		// Check all entries in $options['options'] has a key and value member.
		foreach ( $options['options'] as $option ) {
			if ( ! isset( $option['value'] ) || ! isset( $option['label'] ) ) {
				$message = sprintf( 'Unable to register field with id: "%s". %s', $id, 'Fields of type "select" must have an array of "options" and each option must contain a "value" and "label" member.' );
				_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
				return false;
			}

			$sanitized_value = sanitize_text_field( $option['value'] );
			$sanitized_label = sanitize_text_field( $option['label'] );

			if ( in_array( $sanitized_value, $added_values, true ) ) {
				$message = sprintf( 'Duplicate key found when registering field with id: "%s". The value in each option of "select" fields must be unique. Duplicate value "%s" found. The duplicate key will be removed.', $id, $sanitized_value );
				_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
				continue;
			}

			$added_values[] = $sanitized_value;

			$cleaned_options[] = [
				'value' => $sanitized_value,
				'label' => $sanitized_label,
			];
		}

		$field_data['options'] = $cleaned_options;

		if ( isset( $field_data['placeholder'] ) ) {
			$field_data['placeholder'] = sanitize_text_field( $field_data['placeholder'] );
		}

		return $field_data;
	}

	/**
	 * Processes the options for a checkbox field and returns the new field_options array.
	 *
	 * @param array $field_data  The field data array to be updated.
	 * @param array $options     The options supplied during field registration.
	 *
	 * @return array|false The updated $field_data array or false if an error was encountered.
	 */
	private function process_checkbox_field( $field_data, $options ) {
		$id                     = $options['id'];
		$field_data['required'] = $options['required'] ?? false;

		if ( false === $field_data['required'] && ! empty( $options['error_message'] ) ) {
			$message = sprintf( 'Passing an error message to a non-required checkbox "%s" will have no effect. The error message has been removed from the field.', $id );
			_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '9.8.0' );
			unset( $field_data['error_message'] );
		}

		if ( isset( $options['error_message'] ) && ! is_string( $options['error_message'] ) ) {
			$message = sprintf( 'The error_message property for field with id: "%s" must be a string, you passed %s. A default message will be shown.', $id, gettype( $options['error_message'] ) );
			_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '9.8.0' );
			unset( $field_data['error_message'] );
		}

		// Get the error message property and set it to errorMessage for use in JS.
		if ( isset( $field_data['error_message'] ) ) {
			$field_data['errorMessage'] = $field_data['error_message'];
			unset( $field_data['error_message'] );
		}

		return $field_data;
	}

	/**
	 * Processes the attributes supplied during field registration.
	 *
	 * @param array $id         The field ID.
	 * @param array $attributes The attributes supplied during field registration.
	 *
	 * @return array The processed attributes.
	 */
	private function register_field_attributes( $id, $attributes ) {
		// We check if attributes are valid. This is done to prevent too much nesting and also to allow field registration
		// even if the attributes property is invalid. We can just skip it and register the field without attributes.
		if ( empty( $attributes ) ) {
			return [];
		}

		if ( ! is_array( $attributes ) || 0 === count( $attributes ) ) {
			$message = sprintf( 'An invalid attributes value was supplied when registering field with id: "%s". %s', $id, 'Attributes must be a non-empty array.' );
			_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
			return [];
		}

		// These are formatted in camelCase because React components expect them that way.
		$allowed_attributes = [
			'maxLength',
			'readOnly',
			'pattern',
			'autocomplete',
			'autocapitalize',
			'title',
		];

		$valid_attributes = array_filter(
			$attributes,
			function ( $_, $key ) use ( $allowed_attributes ) {
				return in_array( $key, $allowed_attributes, true ) || strpos( $key, 'aria-' ) === 0 || strpos( $key, 'data-' ) === 0;
			},
			ARRAY_FILTER_USE_BOTH
		);

		// Any invalid attributes should show a doing_it_wrong warning. It shouldn't stop field registration, though.
		if ( count( $attributes ) !== count( $valid_attributes ) ) {
			$invalid_attributes = array_keys( array_diff_key( $attributes, $valid_attributes ) );
			$message            = sprintf( 'Invalid attribute found when registering field with id: "%s". Attributes: %s are not allowed.', $id, implode( ', ', $invalid_attributes ) );
			_doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' );
		}

		// Escape attributes to remove any malicious code and return them.
		return array_map(
			function ( $value ) {
				return esc_attr( $value );
			},
			$valid_attributes
		);
	}

	/**
	 * Returns the keys of all core fields.
	 *
	 * @return array An array of field keys.
	 */
	public function get_core_fields_keys() {
		return [
			'email',
			'country',
			'first_name',
			'last_name',
			'company',
			'address_1',
			'address_2',
			'city',
			'state',
			'postcode',
			'phone',
		];
	}

	/**
	 * Returns an array of all core fields.
	 *
	 * @return array An array of fields.
	 */
	public function get_core_fields() {
		return [
			'email'      => [
				'label'          => __( 'Email address', 'woocommerce' ),
				'optionalLabel'  => __(
					'Email address (optional)',
					'woocommerce'
				),
				'required'       => true,
				'hidden'         => false,
				'autocomplete'   => 'email',
				'autocapitalize' => 'none',
				'type'           => 'email',
				'index'          => 0,
			],
			'country'    => [
				'label'         => __( 'Country/Region', 'woocommerce' ),
				'optionalLabel' => __(
					'Country/Region (optional)',
					'woocommerce'
				),
				'required'      => true,
				'hidden'        => false,
				'autocomplete'  => 'country',
				'index'         => 1,
			],
			'first_name' => [
				'label'          => __( 'First name', 'woocommerce' ),
				'optionalLabel'  => __(
					'First name (optional)',
					'woocommerce'
				),
				'required'       => true,
				'hidden'         => false,
				'autocomplete'   => 'given-name',
				'autocapitalize' => 'sentences',
				'index'          => 10,
			],
			'last_name'  => [
				'label'          => __( 'Last name', 'woocommerce' ),
				'optionalLabel'  => __(
					'Last name (optional)',
					'woocommerce'
				),
				'required'       => true,
				'hidden'         => false,
				'autocomplete'   => 'family-name',
				'autocapitalize' => 'sentences',
				'index'          => 20,
			],
			'company'    => [
				'label'          => __( 'Company', 'woocommerce' ),
				'optionalLabel'  => __(
					'Company (optional)',
					'woocommerce'
				),
				'required'       => 'required' === CartCheckoutUtils::get_company_field_visibility(),
				'hidden'         => 'hidden' === CartCheckoutUtils::get_company_field_visibility(),
				'autocomplete'   => 'organization',
				'autocapitalize' => 'sentences',
				'index'          => 30,
			],
			'address_1'  => [
				'label'          => __( 'Address', 'woocommerce' ),
				'optionalLabel'  => __(
					'Address (optional)',
					'woocommerce'
				),
				'required'       => true,
				'hidden'         => false,
				'autocomplete'   => 'address-line1',
				'autocapitalize' => 'sentences',
				'index'          => 40,
			],
			'address_2'  => [
				'label'          => __( 'Apartment, suite, etc.', 'woocommerce' ),
				'optionalLabel'  => __(
					'Apartment, suite, etc. (optional)',
					'woocommerce'
				),
				'required'       => 'required' === CartCheckoutUtils::get_address_2_field_visibility(),
				'hidden'         => 'hidden' === CartCheckoutUtils::get_address_2_field_visibility(),
				'autocomplete'   => 'address-line2',
				'autocapitalize' => 'sentences',
				'index'          => 50,
			],
			'city'       => [
				'label'          => __( 'City', 'woocommerce' ),
				'optionalLabel'  => __(
					'City (optional)',
					'woocommerce'
				),
				'required'       => true,
				'hidden'         => false,
				'autocomplete'   => 'address-level2',
				'autocapitalize' => 'sentences',
				'index'          => 70,
			],
			'state'      => [
				'label'          => __( 'State/County', 'woocommerce' ),
				'optionalLabel'  => __(
					'State/County (optional)',
					'woocommerce'
				),
				'required'       => true,
				'hidden'         => false,
				'autocomplete'   => 'address-level1',
				'autocapitalize' => 'sentences',
				'index'          => 80,
			],
			'postcode'   => [
				'label'          => __( 'Postal code', 'woocommerce' ),
				'optionalLabel'  => __(
					'Postal code (optional)',
					'woocommerce'
				),
				'required'       => true,
				'hidden'         => false,
				'autocomplete'   => 'postal-code',
				'autocapitalize' => 'characters',
				'index'          => 90,
			],
			'phone'      => [
				'label'          => __( 'Phone', 'woocommerce' ),
				'optionalLabel'  => __(
					'Phone (optional)',
					'woocommerce'
				),
				'required'       => 'required' === CartCheckoutUtils::get_phone_field_visibility(),
				'hidden'         => 'hidden' === CartCheckoutUtils::get_phone_field_visibility(),
				'type'           => 'tel',
				'autocomplete'   => 'tel',
				'autocapitalize' => 'characters',
				'index'          => 100,
			],
		];
	}

	/**
	 * Returns an array of all additional fields.
	 *
	 * @return array An array of fields.
	 */
	public function get_additional_fields() {
		return $this->additional_fields;
	}

	/**
	 * Gets the location of a field.
	 *
	 * @param string $field_key The key of the field to get the location for.
	 * @return string The location of the field.
	 */
	public function get_field_location( $field_key ) {
		if ( ! $this->is_field( $field_key ) ) {
			return '';
		}
		foreach ( $this->fields_locations as $location => $fields ) {
			if ( in_array( $field_key, $fields, true ) ) {
				return $location;
			}
		}
		return '';
	}

	/**
	 * Sanitize an additional field against any custom sanitization rules.
	 *
	 * @since 8.7.0

	 * @param string $field_key   The key of the field.
	 * @param mixed  $field_value The value of the field.
	 * @return mixed
	 */
	public function sanitize_field( $field_key, $field_value ) {
		try {
			$field = $this->additional_fields[ $field_key ] ?? null;

			if ( $field ) {
				$field_value = call_user_func( $field['sanitize_callback'], $field_value, $field );
			}

			/**
			 * Allow custom sanitization of an additional field.
			 *
			 * @param mixed  $field_value The value of the field being sanitized.
			 * @param string $field_key   Key of the field being sanitized.
			 *
			 * @since 8.6.0
			 * @deprecated 8.7.0 Use woocommerce_sanitize_additional_field instead.
			 */
			$field_value = apply_filters_deprecated( '__experimental_woocommerce_blocks_sanitize_additional_field', array( $field_value, $field_key ), '8.7.0', 'woocommerce_sanitize_additional_field', 'This action has been graduated, use woocommerce_sanitize_additional_field instead.' );

			/**
			 * Allow custom sanitization of an additional field.
			 *
			 * @param mixed  $field_value The value of the field being sanitized.
			 * @param string $field_key   Key of the field being sanitized.
			 *
			 * @since 8.7.0
			 */
			return apply_filters( 'woocommerce_sanitize_additional_field', $field_value, $field_key );

		} catch ( \Throwable $e ) {
			// One of the filters errored so skip it. This allows the checkout process to continue.
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
			trigger_error(
				sprintf(
					'Field sanitization for %s encountered an error. %s',
					esc_html( $field_key ),
					esc_html( $e->getMessage() )
				),
				E_USER_WARNING
			);
		}

		return $field_value;
	}

	/**
	 * Validate an additional field.
	 *
	 * @since 8.6.0
	 *
	 * @param array $field        The field.
	 * @param mixed $field_value  The value of the field.
	 * @return WP_Error
	 */
	public function validate_field( $field, $field_value ) {
		$errors = new WP_Error();

		try {
			// Only validate if we have a field.
			if ( ! $field ) {
				return $errors;
			}

			if ( ! empty( $field['validate_callback'] ) && is_callable( $field['validate_callback'] ) ) {
				$validate_callback_result = call_user_func( $field['validate_callback'], $field_value, $field );

				if ( is_wp_error( $validate_callback_result ) ) {
					$errors->merge_from( $validate_callback_result );
				} elseif ( false === $validate_callback_result ) {
					/* translators: %s: is the field label */
					$error_message = sprintf( __( 'Please provide a valid %s', 'woocommerce' ), $field['label'] );
					$errors->add( 'woocommerce_invalid_checkout_field', $error_message );
				}
			}

			wc_do_deprecated_action( '__experimental_woocommerce_blocks_validate_additional_field', array( $errors, $field['id'], $field_value ), '8.7.0', 'woocommerce_validate_additional_field', 'This action has been graduated, use woocommerce_validate_additional_field instead.' );

			/**
			 * Pass an error object to allow validation of an additional field.
			 *
			 * @param WP_Error $errors      A WP_Error object that extensions may add errors to.
			 * @param string   $field_key   Key of the field being sanitized.
			 * @param mixed    $field_value The value of the field being validated.
			 *
			 * @since 8.7.0
			 */
			do_action( 'woocommerce_validate_additional_field', $errors, $field['id'], $field_value );

		} catch ( \Throwable $e ) {

			// One of the filters errored so skip them and validate the field. This allows the checkout process to continue.
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
			trigger_error(
				sprintf(
					'Field validation for %s encountered an error. %s',
					esc_html( $field['id'] ),
					esc_html( $e->getMessage() )
				),
				E_USER_WARNING
			);
		}

		return $errors;
	}

	/**
	 * Update the default locale with additional fields without country limitations.
	 *
	 * @param array $locale The locale to update.
	 * @return mixed
	 */
	public function update_default_locale_with_fields( $locale ) {
		foreach ( $this->get_fields_for_location( 'address' ) as $field_key => $field ) {
			if ( empty( $locale[ $field_key ] ) ) {
				// If the field has conditional rules, we need to set the required property to false so it can be evaluated.
				if ( $this->is_conditional_field( $field_key ) ) {
					$field['required'] = false;
				}
				$locale[ $field_key ] = $field;
			}
		}
		return $locale;
	}

	/**
	 * Returns an array of fields keys for the address location.
	 *
	 * @return array An array of fields keys.
	 */
	public function get_address_fields_keys() {
		return $this->fields_locations['address'];
	}

	/**
	 * Returns an array of fields keys for the contact location.
	 *
	 * @return array An array of fields keys.
	 */
	public function get_contact_fields_keys() {
		return $this->fields_locations['contact'];
	}

	/**
	 * Returns an array of fields keys for the additional area location.
	 *
	 * @return array An array of fields keys.
	 * @deprecated 8.9.0 Use get_order_fields_keys instead.
	 */
	public function get_additional_fields_keys() {
		wc_deprecated_function( __METHOD__, '8.9.0', 'get_order_fields_keys' );
		return $this->get_order_fields_keys();
	}

	/**
	 * Returns an array of fields keys for the additional area group.
	 *
	 * @return array An array of fields keys.
	 */
	public function get_order_fields_keys() {
		return $this->fields_locations['order'];
	}

	/**
	 * Returns an array of fields for a given location.
	 *
	 * @param string $location The location to get fields for (address|contact|order).
	 * @return array An array of fields definitions.
	 */
	public function get_fields_for_location( $location ) {
		$location = $this->prepare_location_name( $location );

		if ( in_array( $location, array_keys( $this->fields_locations ), true ) ) {
			$order_fields_keys = $this->fields_locations[ $location ];

			return array_filter(
				$this->get_additional_fields(),
				function ( $key ) use ( $order_fields_keys ) {
					return in_array( $key, $order_fields_keys, true );
				},
				ARRAY_FILTER_USE_KEY
			);
		}
		return [];
	}

	/**
	 * Returns an array of fields for a given location and uses context to evaluate hidden and required fields.
	 *
	 * @param string              $location The location to get fields for (address|contact|order).
	 * @param DocumentObject|null $document_object The document object.
	 * @return array An array of fields definitions.
	 */
	public function get_contextual_fields_for_location( $location, $document_object = null ) {
		$location_fields = $this->get_fields_for_location( $location );
		$fields          = [];
		foreach ( $location_fields as $key => $field ) {
			if ( $this->is_hidden_field( $key, $document_object ) ) {
				continue;
			}
			$field['required']          = $this->is_required_field( $field, $document_object );
			$field['validate_callback'] = $this->get_validate_callback( $field, $document_object );
			$fields[ $key ]             = $field;
		}

		return $fields;
	}

	/**
	 * Validates a set of fields for a given location against custom validation rules.
	 *
	 * @param array  $fields Array of key value pairs of field values to validate.
	 * @param string $location The location being validated (address|contact|order).
	 * @param string $group The group to get the field value for (shipping|billing|other).
	 * @return WP_Error
	 */
	public function validate_fields_for_location( $fields, $location, $group = 'other' ) {
		$errors   = new WP_Error();
		$location = $this->prepare_location_name( $location );
		$group    = $this->prepare_group_name( $group );

		try {
			wc_do_deprecated_action( '__experimental_woocommerce_blocks_validate_location_' . $location . '_fields', array( $errors, $fields, $group ), '8.9.0', 'woocommerce_blocks_validate_location_' . $location . '_fields', 'This action has been graduated, use woocommerce_blocks_validate_location_' . $location . '_fields instead.' );

			/**
			 * Pass an error object to allow validation of an additional field.
			 *
			 * @param WP_Error $errors  A WP_Error object that extensions may add errors to.
			 * @param mixed    $fields  List of fields (key value pairs) in this location.
			 * @param string   $group   The group of this location (shipping|billing|other).
			 *
			 * @since 8.7.0
			 */
			do_action( 'woocommerce_blocks_validate_location_' . $location . '_fields', $errors, $fields, $group );

		} catch ( \Throwable $e ) {

			// One of the filters errored so skip them. This allows the checkout process to continue.
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
			trigger_error(
				sprintf(
					'The action %s encountered an error. The field location %s may not have any custom validation applied to it. %s',
					esc_html( 'woocommerce_blocks_validate_' . $location . '_fields' ),
					esc_html( $location ),
					esc_html( $e->getMessage() )
				),
				E_USER_WARNING
			);
		}

		return $errors;
	}

	/**
	 * Validates a field to check it belongs to the given location and is valid according to its registration.
	 *
	 * This does not apply any custom validation rules on the value.
	 *
	 * @param string $key The field key.
	 * @param mixed  $value The field value.
	 * @param string $location The location to validate the field for (address|contact|order).
	 *
	 * @return true|WP_Error True if the field is valid, a WP_Error otherwise.
	 */
	public function validate_field_for_location( $key, $value, $location ) {
		$location = $this->prepare_location_name( $location );

		if ( ! $this->is_field( $key ) ) {
			return new WP_Error(
				'woocommerce_invalid_checkout_field',
				\sprintf(
				// translators: % is field key.
					__( 'The field %s is invalid.', 'woocommerce' ),
					$key
				)
			);
		}

		if ( ! in_array( $key, $this->fields_locations[ $location ], true ) ) {
			return new WP_Error(
				'woocommerce_invalid_checkout_field_location',
				\sprintf(
				// translators: %1$s is field key, %2$s location.
					__( 'The field %1$s is invalid for the location %2$s.', 'woocommerce' ),
					$key,
					$location
				)
			);
		}

		return true;
	}

	/**
	 * Returns all fields key for a given group.
	 *
	 * @param string $group The group to get the key for (shipping|billing|other).
	 *
	 * @return string[] Field keys.
	 */
	public function get_fields_for_group( $group = 'other' ) {
		$group = $this->prepare_group_name( $group );
		if ( 'shipping' === $group || 'billing' === $group ) {
			return $this->get_fields_for_location( 'address' );
		}
		return \array_merge(
			$this->get_fields_for_location( 'contact' ),
			$this->get_fields_for_location( 'order' )
		);
	}

	/**
	 * Returns true if the given key is a valid field.
	 *
	 * @param string $key The field key.
	 *
	 * @return bool True if the field is valid, false otherwise.
	 */
	public function is_field( $key ) {
		return array_key_exists( $key, $this->additional_fields );
	}

	/**
	 * Returns true if the given key is a valid customer field.
	 *
	 * Customer fields are fields saved to the customer data, like address and contact fields.
	 *
	 * @param string $key The field key.
	 *
	 * @return bool True if the field is valid, false otherwise.
	 */
	public function is_customer_field( $key ) {
		return in_array( $key, array_intersect( array_merge( $this->get_address_fields_keys(), $this->get_contact_fields_keys() ), array_keys( $this->additional_fields ) ), true );
	}

	/**
	 * Persists a field value for a given order. This would also optionally set the field value on the customer object if the order is linked to a registered customer.
	 *
	 * @param string   $key The field key.
	 * @param mixed    $value The field value.
	 * @param WC_Order $order The order to persist the field for.
	 * @param string   $group The group to persist the field for (shipping|billing|other).
	 * @param bool     $set_customer Whether to set the field value on the customer or not.
	 *
	 * @return void
	 */
	public function persist_field_for_order( string $key, $value, WC_Order $order, string $group = 'other', bool $set_customer = true ) {
		$group = $this->prepare_group_name( $group );
		$this->set_array_meta( $key, $value, $order, $group );
		if ( $set_customer && $order->get_customer_id() ) {
			$customer = new WC_Customer( $order->get_customer_id() );
			$this->persist_field_for_customer( $key, $value, $customer, $group );
		}
	}

	/**
	 * Persists a field value for a given customer.
	 *
	 * @param string      $key The field key.
	 * @param mixed       $value The field value.
	 * @param WC_Customer $customer The customer to persist the field for.
	 * @param string      $group The group to persist the field for (shipping|billing|other).
	 *
	 * @return void
	 */
	public function persist_field_for_customer( string $key, $value, WC_Customer $customer, string $group = 'other' ) {
		$group = $this->prepare_group_name( $group );
		$this->set_array_meta( $key, $value, $customer, $group );
	}

	/**
	 * Sets a field value in an array meta, supporting routing things to billing, shipping, or additional fields, based on a prefix for the key.
	 *
	 * @param string               $key The field key.
	 * @param mixed                $value The field value.
	 * @param WC_Customer|WC_Order $wc_object The object to set the field value for.
	 * @param string               $group The group to set the field value for (shipping|billing|other).
	 *
	 * @return void
	 */
	private function set_array_meta( string $key, $value, WC_Data $wc_object, string $group ) {
		$meta_key = self::get_group_key( $group ) . $key;

		/**
		 * Allow reacting for saving an additional field value.
		 *
		 * @param string               $key The key of the field being saved.
		 * @param mixed                $value The value of the field being saved.
		 * @param string               $group The group of this location (shipping|billing|other).
		 * @param WC_Customer|WC_Order $wc_object The object to set the field value for.
		 *
		 * @since 8.9.0
		 */
		do_action( 'woocommerce_set_additional_field_value', $key, $value, $group, $wc_object );
		// Convert boolean values to strings because Data Stores will skip false values.
		if ( is_bool( $value ) ) {
			$value = $value ? '1' : '0';
		}
		$wc_object->update_meta_data( $meta_key, $value );
	}

	/**
	 * Returns a field value for a given object.
	 *
	 * @param string               $key The field key.
	 * @param WC_Customer|WC_Order $wc_object The customer or order to get the field value for.
	 * @param string               $group The group to get the field value for (shipping|billing|other).
	 *
	 * @return mixed The field value.
	 */
	public function get_field_from_object( string $key, WC_Data $wc_object, string $group = 'other' ) {
		$group    = $this->prepare_group_name( $group );
		$meta_key = self::get_group_key( $group ) . $key;
		$value    = $wc_object->get_meta( $meta_key, true );

		if ( ! $value && '0' !== $value ) {
			/**
			 * Allow providing a default value for additional fields if no value is already set.
			 *
			 * @param null $value The default value for the filter, always null.
			 * @param string $group The group of this key (shipping|billing|other).
			 * @param WC_Data $wc_object The object to get the field value for.
			 *
			 * @since 8.9.0
			 */
			$value = apply_filters( "woocommerce_get_default_value_for_{$key}", null, $group, $wc_object );
		}

		// We cast the value to a boolean if the field is a checkbox.
		if ( $this->is_field( $key ) && 'checkbox' === $this->additional_fields[ $key ]['type'] ) {
			return '1' === $value;
		}

		if ( null === $value ) {
			return '';
		}

		return $value;
	}

	/**
	 * Returns an array of all fields values for a given object in a group.
	 *
	 * @param WC_Data $wc_object The object or order to get the fields for.
	 * @param string  $group The group to get the fields for (shipping|billing|other).
	 * @param bool    $all Whether to return all fields or only the ones that are still registered. Default false.
	 * @return array An array of fields.
	 */
	public function get_all_fields_from_object( WC_Data $wc_object, string $group = 'other', bool $all = false ) {
		$meta_data = [];
		$group     = $this->prepare_group_name( $group );
		$prefix    = self::get_group_key( $group );

		if ( $wc_object instanceof WC_Data ) {
			$meta = $wc_object->get_meta_data();
			foreach ( $meta as $meta_data_object ) {
				if ( 0 === \strpos( $meta_data_object->key, $prefix ) ) {
					$key = \str_replace( $prefix, '', $meta_data_object->key );
					if ( $all || $this->is_field( $key ) ) {
						$meta_data[ $key ] = $meta_data_object->value;
					}
				}
			}
		}

		$missing_fields = array_diff( array_keys( $this->get_fields_for_group( $group ) ), array_keys( $meta_data ) );

		foreach ( $missing_fields as $missing_field ) {
				/**
				 * Allow providing a default value for additional fields if no value is already set.
				 *
				 * @param null $value The default value for the filter, always null.
				 * @param string $group The group of this key (shipping|billing|other).
				 * @param WC_Data $wc_object The object to get the field value for.
				 *
				 * @since 8.9.0
				 */
				$value = apply_filters( "woocommerce_get_default_value_for_{$missing_field}", null, $group, $wc_object );

			if ( isset( $value ) ) {
				$meta_data[ $missing_field ] = $value;
			}
		}

		return $meta_data;
	}

	/**
	 * Copies additional fields from an order to a customer.
	 *
	 * @param WC_Order    $order The order to sync the fields for.
	 * @param WC_Customer $customer The customer to sync the fields for.
	 */
	public function sync_customer_additional_fields_with_order( WC_Order $order, WC_Customer $customer ) {
		foreach ( $this->groups as $group ) {
			$order_additional_fields = $this->get_all_fields_from_object( $order, $group, true );

			// Sync customer additional fields with order additional fields.
			foreach ( $order_additional_fields as $key => $value ) {
				if ( $this->is_customer_field( $key ) ) {
					$this->persist_field_for_customer( $key, $value, $customer, $group );
				}
			}
		}
	}

	/**
	 * Copies additional fields from a customer to an order.
	 *
	 * @param WC_Order    $order The order to sync the fields for.
	 * @param WC_Customer $customer The customer to sync the fields for.
	 */
	public function sync_order_additional_fields_with_customer( WC_Order $order, WC_Customer $customer ) {
		foreach ( $this->groups as $group ) {
			$customer_additional_fields = $this->get_all_fields_from_object( $customer, $group, true );

			// Sync order additional fields with customer additional fields.
			foreach ( $customer_additional_fields as $key => $value ) {
				if ( $this->is_field( $key ) ) {
					$this->persist_field_for_order( $key, $value, $order, $group, false );
				}
			}
		}
	}

	/**
	 * From a set of fields, returns only the ones for a given location.
	 *
	 * @param array  $fields The fields to filter.
	 * @param string $location The location to validate the field for (address|contact|order).
	 * @return array The filtered fields.
	 */
	public function filter_fields_for_location( array $fields, string $location ) {
		$location = $this->prepare_location_name( $location );

		return array_filter(
			$fields,
			function ( $key ) use ( $location ) {
				return $this->get_field_location( $key ) === $location;
			},
			ARRAY_FILTER_USE_KEY
		);
	}

	/**
	 * Filter fields for order confirmation.
	 *
	 * @param array $fields  The fields to filter.
	 * @param array $context Additional context for the filter.
	 * @return array The filtered fields.
	 */
	public function filter_fields_for_order_confirmation( $fields, $context = array() ) {
		return array_filter(
			$fields,
			function ( $field ) use ( $fields, $context ) {
				/**
				 * Filter fields for order confirmation (thank you page, email).
				 *
				 * Used in methods:
				 * WC_Email::additional_checkout_fields
				 * WC_Email::additional_address_fields
				 * CheckoutFieldsFrontend::render_order_other_fields
				 * AdditionalFields::render_content
				 *
				 * @param bool                    Whether the field should be shown.
				 * @param array          $field   Field data.
				 * @param array          $fields  All fields for better context when field should be shown or hidden based on other fields values.
				 * @param array          $context Additional context for the filter. Data depends in which method filter_fields_for_order_confirmation is called.
				 * @param CheckoutFields $this    The CheckoutFields instance.
				 * @since 10.1.0
				 */
				return apply_filters( 'woocommerce_filter_fields_for_order_confirmation', ! empty( $field['show_in_order_confirmation'] ), $field, $fields, $context, $this );
			}
		);
	}

	/**
	 * Get additional fields for an order.
	 *
	 * @param WC_Order $order Order object.
	 * @param string   $location The location to get fields for (address|contact|order).
	 * @param string   $group The group to get the field value for (shipping|billing|other).
	 * @param string   $context The context to get the field value for (edit|view).
	 * @return array An array of fields definitions as well as their values formatted for display.
	 */
	public function get_order_additional_fields_with_values( WC_Order $order, string $location, string $group = 'other', string $context = 'edit' ) {

		// Because the Additional Checkout Fields API only applies to orders created with Store API, we should not
		// return any values unless it was created using Store API. This is mainly to prevent "empty" checkbox values
		// from being shown on the order confirmation page for orders placed using the shortcode. It's rare that this
		// will happen but not impossible.
		if ( 'store-api' !== $order->get_created_via() ) {
			return [];
		}

		$location           = $this->prepare_location_name( $location );
		$group              = $this->prepare_group_name( $group );
		$fields             = $this->get_fields_for_location( $location );
		$fields_with_values = [];

		foreach ( $fields as $field_key => $field ) {
			$value = $this->get_field_from_object( $field_key, $order, $group );

			if ( '' === $value || null === $value ) {
				continue;
			}

			if ( 'view' === $context ) {
				$value = $this->format_additional_field_value( $value, $field );
			}

			$field['value']                   = $value;
			$fields_with_values[ $field_key ] = $field;
		}

		return $fields_with_values;
	}

	/**
	 * Formats a raw field value for display based on its type definition.
	 *
	 * @param string $value Value to format.
	 * @param array  $field Additional field definition.
	 * @return string
	 */
	public function format_additional_field_value( $value, $field ) {
		if ( 'checkbox' === $field['type'] ) {
			$value = $value ? __( 'Yes', 'woocommerce' ) : __( 'No', 'woocommerce' );
		}

		if ( 'select' === $field['type'] ) {
			$options = array_column( $field['options'], 'label', 'value' );
			$value   = isset( $options[ $value ] ) ? $options[ $value ] : $value;
		}

		return $value;
	}

	/**
	 * Prepares a group name for use.
	 *
	 * @param string $group The group name to prepare.
	 * @return string The prepared group name.
	 */
	private function prepare_group_name( $group ) {
		if ( ! in_array( $group, $this->groups, true ) ) {
			$group = 'other';
		}
		return $group;
	}

	/**
	 * Prepares a location name for use.
	 *
	 * @param string $location The location name to prepare.
	 * @return string The prepared location name.
	 */
	private function prepare_location_name( $location ) {
		if ( 'additional' === $location ) {
			$location = 'order';
		}
		return $location;
	}

	/**
	 * Returns a group meta prefix based on its name.
	 *
	 * @param string $group_name The group name (billing|shipping|other).
	 * @return string The group meta prefix.
	 */
	public static function get_group_key( $group_name ) {
		if ( 'additional' === $group_name ) {
			wc_deprecated_argument( 'group_name', '8.9.0', 'The "additional" group is deprecated. Use "other" instead.' );
			$group_name = 'other';
		}
		if ( 'billing' === $group_name ) {
			return self::BILLING_FIELDS_PREFIX;
		}
		if ( 'shipping' === $group_name ) {
			return self::SHIPPING_FIELDS_PREFIX;
		}
		return self::OTHER_FIELDS_PREFIX;
	}

	/**
	 * Returns a group name based on passed group key.
	 *
	 * @param string $group_key The group name (_wc_billing|_wc_shipping|_wc_other).
	 * @return string The group meta prefix.
	 */
	public static function get_group_name( $group_key ) {
		if ( '_wc_additional' === $group_key ) {
			wc_deprecated_argument( 'group_key', '8.9.0', 'The "_wc_additional" group key is deprecated. Use "_wc_other" instead.' );
			$group_key = '_wc_other';
		}
		if ( 0 === \strpos( self::BILLING_FIELDS_PREFIX, $group_key ) ) {
			return 'billing';
		}
		if ( 0 === \strpos( self::SHIPPING_FIELDS_PREFIX, $group_key ) ) {
			return 'shipping';
		}
		return 'other';
	}
}
PK     \&MB  B    Domain/Bootstrap.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Domain;

use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\AssetsController;
use Automattic\WooCommerce\Blocks\BlockPatterns;
use Automattic\WooCommerce\Blocks\BlockTemplatesRegistry;
use Automattic\WooCommerce\Blocks\BlockTemplatesController;
use Automattic\WooCommerce\Blocks\BlockTypesController;
use Automattic\WooCommerce\Blocks\DependencyDetection;
use Automattic\WooCommerce\Blocks\Patterns\AIPatterns;
use Automattic\WooCommerce\Blocks\Patterns\PatternRegistry;
use Automattic\WooCommerce\Blocks\Patterns\PTKClient;
use Automattic\WooCommerce\Blocks\Patterns\PTKPatternsStore;
use Automattic\WooCommerce\Blocks\QueryFilters;
use Automattic\WooCommerce\Blocks\Domain\Services\Notices;
use Automattic\WooCommerce\Blocks\Domain\Services\DraftOrders;
use Automattic\WooCommerce\Blocks\Domain\Services\GoogleAnalytics;
use Automattic\WooCommerce\Blocks\Domain\Services\Hydration;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFieldsAdmin;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFieldsFrontend;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutLink;
use Automattic\WooCommerce\Blocks\InboxNotifications;
use Automattic\WooCommerce\Blocks\Installer;
use Automattic\WooCommerce\Blocks\Payments\Api as PaymentsApi;
use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer;
use Automattic\WooCommerce\Blocks\Payments\Integrations\CashOnDelivery;
use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque;
use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
use Automattic\WooCommerce\Blocks\Registry\Container;
use Automattic\WooCommerce\Blocks\Templates\ClassicTemplatesCompatibility;
use Automattic\WooCommerce\StoreApi\RoutesController;
use Automattic\WooCommerce\StoreApi\SchemaController;
use Automattic\WooCommerce\StoreApi\StoreApi;
use Automattic\WooCommerce\Blocks\Shipping\ShippingController;
use Automattic\WooCommerce\Blocks\TemplateOptions;


/**
 * Takes care of bootstrapping the plugin.
 *
 * @since 2.5.0
 */
class Bootstrap {

	/**
	 * Holds the Dependency Injection Container
	 *
	 * @var Container
	 */
	private $container;

	/**
	 * Holds the Package instance
	 *
	 * @var Package
	 */
	private $package;

	/**
	 * Constructor
	 *
	 * @param Container $container  The Dependency Injection Container.
	 */
	public function __construct( Container $container ) {
		$this->container = $container;
		$this->package   = $container->get( Package::class );

		$this->init();
		/**
		 * Fires when the woocommerce blocks are loaded and ready to use.
		 *
		 * This hook is intended to be used as a safe event hook for when the plugin
		 * has been loaded, and all dependency requirements have been met.
		 *
		 * To ensure blocks are initialized, you must use the `woocommerce_blocks_loaded`
		 * hook instead of the `plugins_loaded` hook. This is because the functions
		 * hooked into plugins_loaded on the same priority load in an inconsistent and unpredictable manner.
		 *
		 * @since 2.5.0
		 */
		do_action( 'woocommerce_blocks_loaded' );
	}

	/**
	 * Init the package - load the blocks library and define constants.
	 */
	protected function init() {
		$this->register_dependencies();
		$this->register_payment_methods();

		add_action(
			'admin_init',
			function () {
				// Delete this notification because the blocks are included in WC Core now. This will handle any sites
				// with lingering notices.
				InboxNotifications::delete_surface_cart_checkout_blocks_notification();
			},
			10,
			0
		);

		// We need to initialize BlockTemplatesController and BlockTemplatesRegistry at the end of `after_setup_theme`
		// so themes had the opportunity to declare support for template parts.
		add_action(
			'after_setup_theme',
			function () {
				$is_store_api_request = wc()->is_store_api_request();

				if ( ! $is_store_api_request && ( wp_is_block_theme() || current_theme_supports( 'block-template-parts' ) ) ) {
					$this->container->get( BlockTemplatesRegistry::class )->init();
					$this->container->get( BlockTemplatesController::class )->init();
				}
			},
			999
		);

		$is_rest              = wc()->is_rest_api_request();
		$is_store_api_request = wc()->is_store_api_request();

		// Initialize Store API in non-admin context.
		if ( ! is_admin() ) {
			$this->container->get( StoreApi::class )->init();
		}

		// Load and init assets.
		$this->container->get( PaymentsApi::class )->init();
		$this->container->get( DraftOrders::class )->init();
		$this->container->get( ShippingController::class )->init();
		$this->container->get( CheckoutFields::class )->init();
		$this->container->get( CheckoutLink::class )->init();
		$this->container->get( AssetDataRegistry::class );
		$this->container->get( AssetsController::class );
		$this->container->get( DependencyDetection::class );

		// Load assets in admin and on the frontend.
		if ( ! $is_rest ) {
			$this->add_build_notice();
			$this->container->get( Installer::class )->init();
			$this->container->get( GoogleAnalytics::class )->init();
			$this->container->get( is_admin() ? CheckoutFieldsAdmin::class : CheckoutFieldsFrontend::class )->init();
		}

		// Load assets unless this is a request specifically for the store API.
		if ( ! $is_store_api_request ) {
			// Template related functionality. These won't be loaded for store API requests, but may be loaded for
			// regular rest requests to maintain compatibility with the store editor.
			$this->container->get( BlockPatterns::class );
			$this->container->get( BlockTypesController::class );
			$this->container->get( ClassicTemplatesCompatibility::class );
			$this->container->get( Notices::class )->init();

			if ( is_admin() || $is_rest ) {
				$this->container->get( AIPatterns::class );
				$this->container->get( PTKPatternsStore::class );
			}

			if ( is_admin() ) {
				$this->container->get( TemplateOptions::class )->init();
			}
		}

		$this->container->get( QueryFilters::class )->init();
	}

	/**
	 * See if files have been built or not.
	 *
	 * @return bool
	 */
	protected function is_built() {
		return file_exists(
			$this->package->get_path( 'assets/client/blocks/featured-product.js' )
		);
	}

	/**
	 * Add a notice stating that the build has not been done yet.
	 */
	protected function add_build_notice() {
		if ( $this->is_built() ) {
			return;
		}
		add_action(
			'admin_notices',
			function () {
				echo '<div class="error"><p>';
				printf(
					/* translators: %1$s is the node install command, %2$s is the install command, %3$s is the build command, %4$s is the watch command. */
					esc_html__( 'WooCommerce Blocks development mode requires files to be built. From the root directory, run %1$s to ensure your node version is aligned, run %2$s to install dependencies, %3$s to build the files or %4$s to build the files and watch for changes.', 'woocommerce' ),
					'<code>nvm use</code>',
					'<code>pnpm install</code>',
					'<code>pnpm --filter="@woocommerce/plugin-woocommerce" build</code>',
					'<code>pnpm --filter="@woocommerce/plugin-woocommerce" watch:build</code>'
				);
				echo '</p></div>';
			}
		);
	}

	/**
	 * Register core dependencies with the container.
	 */
	protected function register_dependencies() {
		$this->container->register(
			AssetApi::class,
			function ( Container $container ) {
				return new AssetApi( $container->get( Package::class ) );
			}
		);
		$this->container->register(
			AssetDataRegistry::class,
			function ( Container $container ) {
				return new AssetDataRegistry( $container->get( AssetApi::class ) );
			}
		);
		$this->container->register(
			AssetsController::class,
			function ( Container $container ) {
				return new AssetsController( $container->get( AssetApi::class ) );
			}
		);
		$this->container->register(
			DependencyDetection::class,
			function () {
				return new DependencyDetection();
			}
		);
		$this->container->register(
			PaymentMethodRegistry::class,
			function () {
				return new PaymentMethodRegistry();
			}
		);
		$this->container->register(
			Installer::class,
			function () {
				return new Installer();
			}
		);
		$this->container->register(
			BlockTypesController::class,
			function ( Container $container ) {
				$asset_api           = $container->get( AssetApi::class );
				$asset_data_registry = $container->get( AssetDataRegistry::class );
				return new BlockTypesController( $asset_api, $asset_data_registry );
			}
		);
		$this->container->register(
			ClassicTemplatesCompatibility::class,
			function ( Container $container ) {
				$asset_data_registry = $container->get( AssetDataRegistry::class );
				return new ClassicTemplatesCompatibility( $asset_data_registry );
			}
		);
		$this->container->register(
			DraftOrders::class,
			function ( Container $container ) {
				return new DraftOrders( $container->get( Package::class ) );
			}
		);
		$this->container->register(
			GoogleAnalytics::class,
			function ( Container $container ) {
				$asset_api = $container->get( AssetApi::class );
				return new GoogleAnalytics( $asset_api );
			}
		);
		$this->container->register(
			Notices::class,
			function ( Container $container ) {
				return new Notices( $container->get( Package::class ) );
			}
		);
		$this->container->register(
			Hydration::class,
			function ( Container $container ) {
				return new Hydration( $container->get( AssetDataRegistry::class ) );
			}
		);
		$this->container->register(
			CheckoutFields::class,
			function ( Container $container ) {
				return new CheckoutFields( $container->get( AssetDataRegistry::class ) );
			}
		);
		$this->container->register(
			CheckoutFieldsAdmin::class,
			function ( Container $container ) {
				$checkout_fields_controller = $container->get( CheckoutFields::class );
				return new CheckoutFieldsAdmin( $checkout_fields_controller );
			}
		);
		$this->container->register(
			CheckoutFieldsFrontend::class,
			function ( Container $container ) {
				$checkout_fields_controller = $container->get( CheckoutFields::class );
				return new CheckoutFieldsFrontend( $checkout_fields_controller );
			}
		);
		$this->container->register(
			PaymentsApi::class,
			function ( Container $container ) {
				$payment_method_registry = $container->get( PaymentMethodRegistry::class );
				$asset_data_registry     = $container->get( AssetDataRegistry::class );
				return new PaymentsApi( $payment_method_registry, $asset_data_registry );
			}
		);
		$this->container->register(
			CheckoutLink::class,
			function () {
				return new CheckoutLink();
			}
		);
		$this->container->register(
			StoreApi::class,
			function () {
				return new StoreApi();
			}
		);
		$this->container->register(
			TemplateOptions::class,
			function () {
				return new TemplateOptions();
			}
		);
		// Maintains backwards compatibility with previous Store API namespace.
		$this->container->register(
			'Automattic\WooCommerce\Blocks\StoreApi\Formatters',
			function ( Container $container ) {
				$this->deprecated_dependency( 'Automattic\WooCommerce\Blocks\StoreApi\Formatters', '6.4.0', 'Automattic\WooCommerce\StoreApi\Formatters', '6.5.0' );
				return $container->get( StoreApi::class )->container()->get( \Automattic\WooCommerce\StoreApi\Formatters::class );
			}
		);
		$this->container->register(
			'Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi',
			function ( Container $container ) {
				$this->deprecated_dependency( 'Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi', '6.4.0', 'Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema', '6.5.0' );
				return $container->get( StoreApi::class )->container()->get( \Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema::class );
			}
		);
		$this->container->register(
			'Automattic\WooCommerce\Blocks\StoreApi\SchemaController',
			function ( Container $container ) {
				$this->deprecated_dependency( 'Automattic\WooCommerce\Blocks\StoreApi\SchemaController', '6.4.0', 'Automattic\WooCommerce\StoreApi\SchemaController', '6.5.0' );
				return $container->get( StoreApi::class )->container()->get( SchemaController::class );
			}
		);
		$this->container->register(
			'Automattic\WooCommerce\Blocks\StoreApi\RoutesController',
			function ( Container $container ) {
				$this->deprecated_dependency( 'Automattic\WooCommerce\Blocks\StoreApi\RoutesController', '6.4.0', 'Automattic\WooCommerce\StoreApi\RoutesController', '6.5.0' );
				return $container->get( StoreApi::class )->container()->get( RoutesController::class );
			}
		);
		$this->container->register(
			PTKClient::class,
			function () {
				return new PTKClient();
			}
		);
		$this->container->register(
			PTKPatternsStore::class,
			function () {
				return new PTKPatternsStore( $this->container->get( PTKClient::class ) );
			}
		);
		$this->container->register(
			BlockPatterns::class,
			function () {
				return new BlockPatterns(
					$this->package,
					new PatternRegistry(),
					$this->container->get( PTKPatternsStore::class )
				);
			}
		);
		$this->container->register(
			AIPatterns::class,
			function () {
				return new AIPatterns();
			}
		);
		$this->container->register(
			ShippingController::class,
			function ( $container ) {
				$asset_api           = $container->get( AssetApi::class );
				$asset_data_registry = $container->get( AssetDataRegistry::class );
				return new ShippingController( $asset_api, $asset_data_registry );
			}
		);
		$this->container->register(
			QueryFilters::class,
			function () {
				return new QueryFilters();
			}
		);
		$this->container->register(
			BlockTemplatesRegistry::class,
			function () {
				return new BlockTemplatesRegistry();
			}
		);
		$this->container->register(
			BlockTemplatesController::class,
			function () {
				return new BlockTemplatesController();
			}
		);
	}

	/**
	 * Throws a deprecation notice for a dependency without breaking requests.
	 *
	 * @param string $function Class or function being deprecated.
	 * @param string $version Version in which it was deprecated.
	 * @param string $replacement Replacement class or function, if applicable.
	 * @param string $trigger_error_version Optional version to start surfacing this as a PHP error rather than a log. Defaults to $version.
	 */
	protected function deprecated_dependency( $function, $version, $replacement = '', $trigger_error_version = '' ) {
		if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
			return;
		}

		$trigger_error_version = $trigger_error_version ? $trigger_error_version : $version;
		$error_message         = $replacement ? sprintf(
			'%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.',
			$function,
			$version,
			$replacement
		) : sprintf(
			'%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.',
			$function,
			$version
		);
		/**
		 * Fires when a deprecated function is called.
		 *
		 * @since 7.3.0
		 */
		do_action( 'deprecated_function_run', $function, $replacement, $version );

		$log_error = false;

		// If headers have not been sent yet, log to avoid breaking the request.
		if ( ! headers_sent() ) {
			$log_error = true;
		}

		// If the $trigger_error_version was not yet reached, only log the error.
		if ( version_compare( Constants::get_constant( 'WC_VERSION' ), $trigger_error_version, '<' ) ) {
			$log_error = true;
		}

		/**
		 * Filters whether to trigger an error for deprecated functions. (Same as WP core)
		 *
		 * @since 7.3.0
		 *
		 * @param bool $trigger Whether to trigger the error for deprecated functions. Default true.
		 */
		if ( ! apply_filters( 'deprecated_function_trigger_error', true ) ) {
			$log_error = true;
		}

		if ( $log_error ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( $error_message );
		} else {
			// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
			trigger_error( $error_message, E_USER_DEPRECATED );
		}
	}

	/**
	 * Register payment method integrations with the container.
	 */
	protected function register_payment_methods() {
		$this->container->register(
			Cheque::class,
			function ( Container $container ) {
				$asset_api = $container->get( AssetApi::class );
				return new Cheque( $asset_api );
			}
		);
		$this->container->register(
			PayPal::class,
			function ( Container $container ) {
				$asset_api = $container->get( AssetApi::class );
				return new PayPal( $asset_api );
			}
		);
		$this->container->register(
			BankTransfer::class,
			function ( Container $container ) {
				$asset_api = $container->get( AssetApi::class );
				return new BankTransfer( $asset_api );
			}
		);
		$this->container->register(
			CashOnDelivery::class,
			function ( Container $container ) {
				$asset_api = $container->get( AssetApi::class );
				return new CashOnDelivery( $asset_api );
			}
		);
	}
}
PK     \fd5S  S    Domain/Package.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Domain;

use Automattic\WooCommerce\Blocks\Options;
use Automattic\WooCommerce\Blocks\Domain\Services\FeatureGating;


/**
 * Main package class.
 *
 * Returns information about the package and handles init.
 *
 * @since 2.5.0
 */
class Package {

	/**
	 * Holds the current version of the blocks plugin.
	 *
	 * @var string
	 */
	private $version;

	/**
	 * Holds the main path to the blocks plugin directory.
	 *
	 * @var string
	 */
	private $path;

	/**
	 * Holds locally the plugin_dir_url to avoid recomputing it.
	 *
	 * @var string
	 */
	private $plugin_dir_url;

	/**
	 * Holds the feature gating class instance.
	 *
	 * @var FeatureGating
	 */
	private $feature_gating;

	/**
	 * Constructor
	 *
	 * @param string        $version        Version of the plugin.
	 * @param string        $plugin_path    Path to the main plugin file.
	 * @param FeatureGating $deprecated     Deprecated Feature gating class.
	 */
	public function __construct( $version, $plugin_path, $deprecated = null ) {
		if ( null !== $deprecated ) {
			wc_deprecated_argument( 'FeatureGating', '9.6', 'FeatureGating class is deprecated, please use wp_get_environment_type() instead.' );
			$this->feature_gating = new FeatureGating();
		}
		$this->version = $version;
		$this->path    = $plugin_path;
	}

	/**
	 * Returns the version of WooCommerce Blocks.
	 *
	 * Note: since Blocks was merged into WooCommerce Core, the version of
	 * WC Blocks doesn't update anymore. Use
	 * `Constants::get_constant( 'WC_VERSION' )` when possible to get the
	 * WooCommerce Core version.
	 *
	 * @return string
	 */
	public function get_version() {
		return $this->version;
	}

	/**
	 * Returns the version of WooCommerce Blocks stored in the database.
	 *
	 * @return string
	 */
	public function get_version_stored_on_db() {
		return get_option( Options::WC_BLOCK_VERSION, '' );
	}

	/**
	 * Sets the version of WooCommerce Blocks in the database.
	 * This is useful during the first installation or after the upgrade process.
	 */
	public function set_version_stored_on_db() {
		update_option( Options::WC_BLOCK_VERSION, $this->get_version() );
	}

	/**
	 * Returns the path to the plugin directory.
	 *
	 * @param string $relative_path  If provided, the relative path will be
	 *                               appended to the plugin path.
	 *
	 * @return string
	 */
	public function get_path( $relative_path = '' ) {
		return trailingslashit( $this->path ) . $relative_path;
	}

	/**
	 * Returns the url to the blocks plugin directory.
	 *
	 * @param string $relative_url If provided, the relative url will be
	 *                             appended to the plugin url.
	 *
	 * @return string
	 */
	public function get_url( $relative_url = '' ) {
		if ( ! $this->plugin_dir_url ) {
			// Append index.php so WP does not return the parent directory.
			$this->plugin_dir_url = plugin_dir_url( $this->path . '/index.php' );
		}

		return $this->plugin_dir_url . $relative_url;
	}

	/**
	 * Returns an instance of the FeatureGating class.
	 *
	 * @return FeatureGating
	 */
	public function feature() {
		return $this->feature_gating;
	}
}
PK     \<tG      Templates/AbstractTemplate.phpnu [        <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * AbstractTemplate class.
 *
 * Shared logic for templates.
 *
 * @internal
 */
abstract class AbstractTemplate {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = '';

	/**
	 * Whether this is a taxonomy template.
	 *
	 * @var bool
	 */
	public bool $is_taxonomy_template = false;

	/**
	 * Initialization method.
	 */
	abstract public function init();

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

	/**
	 * Should return the description of the template.
	 *
	 * @return string
	 */
	abstract public function get_template_description();
}
PK     \}^L  L  "  Templates/AbstractTemplatePart.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * AbstractTemplatePart class.
 *
 * Shared logic for templates parts.
 *
 * @internal
 */
abstract class AbstractTemplatePart extends AbstractTemplate {
	/**
	 * The template part area where the template part belongs.
	 *
	 * @var string
	 */
	public $template_area;
}
PK     \ǅ%$  %$  #  Templates/SingleProductTemplate.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Templates;

use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplateCompatibility;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;

/**
 * SingleProductTemplate class.
 *
 * @internal
 */
class SingleProductTemplate extends AbstractTemplate {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'single-product';

	/**
	 * Initialization method.
	 */
	public function init() {
		add_action( 'template_redirect', array( $this, 'render_block_template' ) );
		add_filter( 'get_block_templates', array( $this, 'update_single_product_content' ), 11, 1 );
	}

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Single Product', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Displays a single product.', 'woocommerce' );
	}

	/**
	 * Run template-specific logic when the query matches this template.
	 */
	public function render_block_template() {
		if ( ! is_embed() && is_singular( 'product' ) ) {
			global $post;

			$compatibility_layer = new SingleProductTemplateCompatibility();
			$compatibility_layer->init();

			$valid_slugs         = array( self::SLUG );
			$single_product_slug = 'product' === $post->post_type && $post->post_name ? 'single-product-' . $post->post_name : '';
			if ( $single_product_slug ) {
				$valid_slugs[] = 'single-product-' . $post->post_name;
			}
			$templates = get_block_templates( array( 'slug__in' => $valid_slugs ) );

			if ( count( $templates ) === 0 ) {
				return;
			}

			// Use the first template by default.
			$template = reset( $templates );

			// Check if there is a template matching the slug `single-product-{post_name}`.
			if ( count( $valid_slugs ) > 1 && count( $templates ) > 1 ) {
				foreach ( $templates as $t ) {
					if ( $single_product_slug === $t->slug ) {
						$template = $t;
						break;
					}
				}
			}

			if ( isset( $template ) && BlockTemplateUtils::template_has_legacy_template_block( $template ) ) {
				add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
			}

			$product = wc_get_product( $post->ID );
			if ( $product ) {
				wp_interactivity_state(
					'woocommerce/product-data',
					array(
						'templateState' => array(
							'productId'   => $product->get_id(),
							'variationId' => null,
						),
					)
				);
			}
		}
	}

	/**
	 * Add the block template objects to be used.
	 *
	 * @param array $query_result Array of template objects.
	 * @return array
	 */
	public function update_single_product_content( $query_result ) {
		$query_result = array_map(
			function ( $template ) {
				if ( str_contains( $template->slug, self::SLUG ) ) {
					// We don't want to add the compatibility layer on the Editor Side.
					// The second condition is necessary to not apply the compatibility layer on the REST API. Gutenberg uses the REST API to clone the template.
					// More details: https://github.com/woocommerce/woocommerce-blocks/issues/9662.
					if ( ( ! is_admin() && ! ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) && ! BlockTemplateUtils::template_has_legacy_template_block( $template ) ) {
						// Add the product class to the body. We should move this to a more appropriate place.
						add_filter(
							'body_class',
							function ( $classes ) {
								return array_merge( $classes, wc_get_product_class() );
							}
						);

						global $product;

						if ( ! $product instanceof \WC_Product ) {
							$product_id = get_the_ID();
							if ( $product_id ) {
								wc_setup_product_data( $product_id );
							}
						}

						if ( post_password_required() ) {
							$template->content = $this->add_password_form( $template->content );
						} else {
							$template->content = SingleProductTemplateCompatibility::add_compatibility_layer( $template->content );
						}
					}
				}
				return $template;
			},
			$query_result
		);

		return $query_result;
	}

	/**
	 * Replace the first single product template block with the password form. Remove all other single product template blocks.
	 *
	 * @param array   $parsed_blocks Array of parsed block objects.
	 * @param boolean $is_already_replaced If the password form has already been added.
	 * @return array Parsed blocks
	 */
	private static function replace_first_single_product_template_block_with_password_form( $parsed_blocks, $is_already_replaced ) {
		// We want to replace the first single product template block with the password form. We also want to remove all other single product template blocks.
		// This array doesn't contains all the blocks. For example, it missing the breadcrumbs blocks: it doesn't make sense replace the breadcrumbs with the password form.
		$single_product_template_blocks = array(
			'woocommerce/product-image-gallery',
			'woocommerce/product-details',
			'woocommerce/add-to-cart-form',
			'woocommerce/product-meta',
			'woocommerce/product-rating',
			'woocommerce/product-price',
			'woocommerce/related-products',
			'woocommerce/add-to-cart-with-options',
			'woocommerce/product-gallery',
			'woocommerce/product-collection',
			'core/post-title',
			'core/post-excerpt',
		);

		return array_reduce(
			$parsed_blocks,
			function ( $carry, $block ) use ( $single_product_template_blocks ) {
				if ( in_array( $block['blockName'], $single_product_template_blocks, true ) || ( 'core/pattern' === $block['blockName'] && isset( $block['attrs']['slug'] ) && 'woocommerce-blocks/related-products' === $block['attrs']['slug'] ) ) {
					if ( $carry['is_already_replaced'] ) {
						return array(
							'blocks'              => $carry['blocks'],
							'html_block'          => null,
							'removed'             => true,
							'is_already_replaced' => true,

						);
					}

					return array(
						'blocks'              => $carry['blocks'],
						'html_block'          => parse_blocks( '<!-- wp:html -->' . get_the_password_form() . '<!-- /wp:html -->' )[0],
						'removed'             => false,
						'is_already_replaced' => $carry['is_already_replaced'],
					);

				}

				if ( isset( $block['innerBlocks'] ) && count( $block['innerBlocks'] ) > 0 ) {
					$index              = 0;
					$new_inner_blocks   = array();
					$new_inner_contents = $block['innerContent'];
					foreach ( $block['innerContent'] as $inner_content ) {
						// Don't process the closing tag of the block.
						if ( count( $block['innerBlocks'] ) === $index ) {
							break;
						}

						$blocks                       = self::replace_first_single_product_template_block_with_password_form( array( $block['innerBlocks'][ $index ] ), $carry['is_already_replaced'] );
						$new_blocks                   = $blocks['blocks'];
						$html_block                   = $blocks['html_block'];
						$is_removed                   = $blocks['removed'];
						$carry['is_already_replaced'] = $blocks['is_already_replaced'];

						if ( isset( $html_block ) ) {
							$new_inner_blocks             = array_merge( $new_inner_blocks, $new_blocks, array( $html_block ) );
							$carry['is_already_replaced'] = true;
						} else {
							$new_inner_blocks = array_merge( $new_inner_blocks, $new_blocks );
						}

						if ( $is_removed ) {
							unset( $new_inner_contents[ $index ] );
							// The last element of the inner contents contains the closing tag of the block. We don't want to remove it.
							if ( $index + 1 < count( $new_inner_contents ) ) {
								unset( $new_inner_contents[ $index + 1 ] );
							}
							$new_inner_contents = array_values( $new_inner_contents );
						}

						$index++;
					}

					$block['innerBlocks']  = $new_inner_blocks;
					$block['innerContent'] = $new_inner_contents;

					if ( count( $new_inner_blocks ) === 0 ) {
						return array(
							'blocks'              => $carry['blocks'],
							'html_block'          => null,
							'removed'             => true,
							'is_already_replaced' => $carry['is_already_replaced'],
						);
					}

					return array(
						'blocks'              => array_merge( $carry['blocks'], array( $block ) ),
						'html_block'          => null,
						'removed'             => false,
						'is_already_replaced' => $carry['is_already_replaced'],
					);
				}

				return array(
					'blocks'              => array_merge( $carry['blocks'], array( $block ) ),
					'html_block'          => null,
					'removed'             => false,
					'is_already_replaced' => $carry['is_already_replaced'],
				);
			},
			array(
				'blocks'              => array(),
				'html_block'          => null,
				'removed'             => false,
				'is_already_replaced' => $is_already_replaced,
			)
		);
	}

	/**
	 * Add password form to the Single Product Template.
	 *
	 * @param string $content The content of the template.
	 * @return string
	 */
	public static function add_password_form( $content ) {
		$parsed_blocks     = parse_blocks( $content );
		$blocks            = self::replace_first_single_product_template_block_with_password_form( $parsed_blocks, false );
		$serialized_blocks = serialize_blocks( $blocks['blocks'] );

		return $serialized_blocks;
	}
}
PK     \Z
  
  &  Templates/ProductAttributeTemplate.phpnu [        <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\Blocks\Templates;

use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;

/**
 * ProductAttributeTemplate class.
 *
 * @internal
 */
class ProductAttributeTemplate extends AbstractTemplateWithFallback {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'taxonomy-product_attribute';

	/**
	 * The template used as a fallback if that one is customized.
	 *
	 * @var string
	 */
	public string $fallback_template = ProductCatalogTemplate::SLUG;

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Products by Attribute', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Displays products filtered by an attribute.', 'woocommerce' );
	}

	/**
	 * Run template-specific logic when the query matches this template.
	 */
	public function render_block_template() {
		$queried_object = get_queried_object();
		if ( is_null( $queried_object ) ) {
			return;
		}

		if ( isset( $queried_object->taxonomy ) && taxonomy_is_product_attribute( $queried_object->taxonomy ) ) {
			$compatibility_layer = new ArchiveProductTemplatesCompatibility();
			$compatibility_layer->init();

			$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );

			if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
				add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
			}
		}
	}

	/**
	 * Renders the Product by Attribute template for product attributes taxonomy pages.
	 *
	 * @param array $templates Templates that match the product attributes taxonomy.
	 */
	public function template_hierarchy( $templates ) {
		$queried_object = get_queried_object();

		if ( ! is_null( $queried_object ) && taxonomy_is_product_attribute( $queried_object->taxonomy ) && wp_is_block_theme() ) {
			// If Products by Attribute template has been customized or it's in the
			// theme, we load it first, otherwise we only load the fallback template.
			// If we don't do that, the WC core template would always have priority
			// over the fallback template.
			$slugs = array( $this->fallback_template );

			if (
				BlockTemplateUtils::theme_has_template( self::SLUG ) ||
				BlockTemplateUtils::get_block_templates_from_db( array( self::SLUG ) )
			) {
				$slugs = array( self::SLUG, $this->fallback_template );
			}

			array_splice( $templates, count( $templates ) - 1, 0, $slugs );
		}

		return $templates;
	}
}
PK     \+O    8  Templates/GroupedProductAddToCartWithOptionsTemplate.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * GroupedProductAddToCartWithOptionsTemplate class.
 *
 * @internal
 */
class GroupedProductAddToCartWithOptionsTemplate extends AbstractTemplatePart {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'grouped-product-add-to-cart-with-options';

	/**
	 * The template part area where the template part belongs.
	 *
	 * @var string
	 */
	public $template_area = 'add-to-cart-with-options';

	/**
	 * Initialization method.
	 */
	public function init() {
	}

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Grouped Product Add to Cart + Options', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Template used to display the Add to Cart + Options form for Grouped Products.', 'woocommerce' );
	}
}
PK     \-?  ?  "  Templates/AbstractPageTemplate.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * AbstractPageTemplate class.
 *
 * Shared logic for page templates.
 *
 * @internal
 */
abstract class AbstractPageTemplate extends AbstractTemplate {
	/**
	 * Initialization method.
	 */
	public function init() {
		add_filter( 'page_template_hierarchy', array( $this, 'page_template_hierarchy' ), 1 );
	}

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

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

	/**
	 * 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, static::SLUG );
		}
		return $templates;
	}

	/**
	 * Forces the page title to match the template title when this template is active.
	 *
	 * Only applies when hooked into `pre_get_document_title`. Most templates used for pages will not require this because
	 * the page title should be used instead.
	 *
	 * @param string $title Page title.
	 * @return string
	 */
	public function page_template_title( $title ) {
		if ( $this->is_active_template() && $this->get_template_title() ) {
			return $this->get_template_title();
		}
		return $title;
	}
}
PK     \l    9  Templates/ExternalProductAddToCartWithOptionsTemplate.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * ExternalProductAddToCartWithOptionsTemplate class.
 *
 * @internal
 */
class ExternalProductAddToCartWithOptionsTemplate extends AbstractTemplatePart {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'external-product-add-to-cart-with-options';

	/**
	 * The template part area where the template part belongs.
	 *
	 * @var string
	 */
	public $template_area = 'add-to-cart-with-options';

	/**
	 * Initialization method.
	 */
	public function init() {
	}

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'External Product Add to Cart + Options', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Template used to display the Add to Cart + Options form for External Products.', 'woocommerce' );
	}
}
PK     \OI.      Templates/CheckoutTemplate.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * CheckoutTemplate class.
 *
 * @internal
 */
class CheckoutTemplate extends AbstractPageTemplate {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'page-checkout';

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Page: Checkout', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'The Checkout template guides users through the final steps of the purchase process. It enables users to enter shipping and billing information, select a payment method, and review order details.', 'woocommerce' );
	}

	/**
	 * Returns the page object assigned to this template/page.
	 *
	 * @return \WP_Post|null Post object or null.
	 */
	protected function get_placeholder_page() {
		$page_id = wc_get_page_id( 'checkout' );
		return $page_id ? get_post( $page_id ) : null;
	}

	/**
	 * True when viewing the checkout page or checkout endpoint.
	 *
	 * @return boolean
	 */
	protected function is_active_template() {
		global $post;
		$placeholder = $this->get_placeholder_page();
		return null !== $placeholder && $post instanceof \WP_Post && $placeholder->post_name === $post->post_name;
	}

	/**
	 * 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, self::SLUG );
			array_unshift( $templates, 'checkout' );
		}
		return $templates;
	}
}
PK     \F	  	  $  Templates/ProductCatalogTemplate.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\Templates;

use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;

/**
 * ProductCatalogTemplate class.
 *
 * @internal
 */
class ProductCatalogTemplate extends AbstractTemplate {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'archive-product';

	/**
	 * Initialization method.
	 */
	public function init() {
		add_action( 'template_redirect', array( $this, 'render_block_template' ) );
		add_filter( 'current_theme_supports-block-templates', array( $this, 'remove_block_template_support_for_shop_page' ) );
	}

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Product Catalog', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Displays your products.', 'woocommerce' );
	}

	/**
	 * Run template-specific logic when the query matches this template.
	 */
	public function render_block_template() {
		if ( ! is_embed() && ( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) ) && ! is_search() ) {
			$compatibility_layer = new ArchiveProductTemplatesCompatibility();
			$compatibility_layer->init();

			$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );

			if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
				add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
			}
		}
	}

	/**
	 * Remove the template panel from the Sidebar of the Shop page because
	 * the Site Editor handles it.
	 *
	 * @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/6278
	 *
	 * @param bool $is_support Whether the active theme supports block templates.
	 *
	 * @return bool
	 */
	public function remove_block_template_support_for_shop_page( $is_support ) {
		global $pagenow, $post;

		if (
			is_admin() &&
			'post.php' === $pagenow &&
			function_exists( 'wc_get_page_id' ) &&
			is_a( $post, 'WP_Post' ) &&
			wc_get_page_id( 'shop' ) === $post->ID
		) {
			return false;
		}

		return $is_support;
	}
}
PK     \O(      Templates/MiniCartTemplate.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * MiniCartTemplate class.
 *
 * @internal
 */
class MiniCartTemplate extends AbstractTemplatePart {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'mini-cart';

	/**
	 * The template part area where the template part belongs.
	 *
	 * @var string
	 */
	public $template_area = 'mini-cart';

	/**
	 * Initialization method.
	 */
	public function init() {
		add_filter( 'default_wp_template_part_areas', array( $this, 'register_mini_cart_template_part_area' ), 10, 1 );
	}

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Mini-Cart', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Template used to display the Mini-Cart drawer.', 'woocommerce' );
	}

	/**
	 * Add Mini-Cart to the default template part areas.
	 *
	 * @param array $default_area_definitions An array of supported area objects.
	 * @return array The supported template part areas including the Mini-Cart one.
	 */
	public function register_mini_cart_template_part_area( $default_area_definitions ) {
		$mini_cart_template_part_area = array(
			'area'        => 'mini-cart',
			'label'       => __( 'Mini-Cart', 'woocommerce' ),
			'description' => __( 'The Mini-Cart template allows shoppers to see their cart items and provides access to the Cart and Checkout pages.', 'woocommerce' ),
			'icon'        => 'mini-cart',
			'area_tag'    => 'mini-cart',
		);
		return array_merge( $default_area_definitions, array( $mini_cart_template_part_area ) );
	}
}
PK     \dxLR  R  '  Templates/OrderConfirmationTemplate.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * OrderConfirmationTemplate class.
 *
 * @internal
 */
class OrderConfirmationTemplate extends AbstractPageTemplate {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'order-confirmation';

	/**
	 * Initialization method.
	 */
	public function init() {
		add_action( 'wp_before_admin_bar_render', array( $this, 'remove_edit_page_link' ) );
		add_filter( 'pre_get_document_title', array( $this, 'page_template_title' ) );
		parent::init();
	}

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Order Confirmation', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'The Order Confirmation template serves as a receipt and confirmation of a successful purchase. It includes a summary of the ordered items, shipping, billing, and totals.', 'woocommerce' );
	}

	/**
	 * Remove edit page from admin bar.
	 */
	public function remove_edit_page_link() {
		if ( $this->is_active_template() ) {
			global $wp_admin_bar;
			$wp_admin_bar->remove_menu( 'edit' );
		}
	}

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

	/**
	 * True when viewing the Order Received endpoint.
	 *
	 * @return boolean
	 */
	protected function is_active_template() {
		return is_wc_endpoint_url( 'order-received' );
	}
}
PK     \>21  1  2  Templates/ArchiveProductTemplatesCompatibility.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * ArchiveProductTemplatesCompatibility class.
 *
 * To bridge the gap on compatibility with PHP hooks and Product Archive blockified templates.
 *
 * @internal
 */
class ArchiveProductTemplatesCompatibility extends AbstractTemplateCompatibility {

	/**
	 * The custom ID of the loop item block as the replacement of the core/null block.
	 */
	const LOOP_ITEM_ID = 'product-loop-item';

	/**
	 * The data of supported hooks, containing the hook name, the block name,
	 * position, and the callbacks.
	 *
	 * @var array $hook_data The hook data.
	 */
	protected $hook_data;

	/**
	 * Update the render block data to inject our custom attribute needed to
	 * determine which blocks belong to an inherited Products block.
	 *
	 * @param array         $parsed_block The block being rendered.
	 * @param array         $source_block An un-modified copy of $parsed_block, as it appeared in the source content.
	 * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
	 *
	 * @return array
	 */
	public function update_render_block_data( $parsed_block, $source_block, $parent_block ) {

		if ( ! $this->is_archive_template() ) {
			return $parsed_block;
		}

		/**
		 * Custom data can be injected to top level block only, as Gutenberg
		 * will use this data to render the blocks and its nested blocks.
		 */
		if ( $parent_block ) {
			return $parsed_block;
		}

		$this->inner_blocks_walker( $parsed_block );

		return $parsed_block;
	}

	/**
	 * Inject hooks to rendered content of corresponding blocks.
	 *
	 * @param mixed $block_content The rendered block content.
	 * @param mixed $block         The parsed block data.
	 * @return string
	 */
	public function inject_hooks( $block_content, $block ) {
		if ( ! $this->is_archive_template() ) {
			return $block_content;
		}
		/**
		 * If the block is not inherited, we don't need to inject hooks.
		 */
		if ( empty( $block['attrs']['isInherited'] ) ) {
			return $block_content;
		}

		$block_name = $block['blockName'];

		if ( $this->is_null_post_template( $block ) ) {
			$block_name = self::LOOP_ITEM_ID;
		}

		$block_hooks = array_filter(
			$this->hook_data,
			function ( $hook ) use ( $block_name ) {
				return in_array( $block_name, $hook['block_names'], true );
			}
		);

		// We want to inject hooks to the core/post-template or product template block only when the products exist:
		// https://github.com/woocommerce/woocommerce-blocks/issues/9463.
		if ( $this->is_post_or_product_template( $block_name ) && ! empty( $block_content ) ) {
			$this->restore_default_hooks();
			$content = sprintf(
				'%1$s%2$s%3$s',
				$this->get_hooks_buffer( $block_hooks, 'before' ),
				$block_content,
				$this->get_hooks_buffer( $block_hooks, 'after' )
			);
			$this->remove_default_hooks();
			return $content;
		}

		$supported_blocks = array_merge(
			array(),
			...array_map(
				function ( $hook ) {
					return $hook['block_names'];
				},
				array_values( $this->hook_data )
			)
		);

		if ( ! in_array( $block_name, $supported_blocks, true ) ) {
			return $block_content;
		}

		if (
			'core/query-no-results' === $block_name
		) {

			/**
			 * `core/query-no-result` is a special case because it can return two
			 * different content depending on the context. We need to check if the
			 * block content is empty to determine if we need to inject hooks.
			 */
			if ( empty( trim( $block_content ) ) ) {
				return $block_content;
			}

			$this->restore_default_hooks();

			$content = sprintf(
				'%1$s%2$s%3$s',
				$this->get_hooks_buffer( $block_hooks, 'before' ),
				$block_content,
				$this->get_hooks_buffer( $block_hooks, 'after' )
			);

			$this->remove_default_hooks();

			return $content;
		}

		if ( empty( $block_content ) ) {
			return $block_content;
		}

		return sprintf(
			'%1$s%2$s%3$s',
			$this->get_hooks_buffer( $block_hooks, 'before' ),
			$block_content,
			$this->get_hooks_buffer( $block_hooks, 'after' )
		);
	}

	/**
	 * The hook data to inject to the rendered content of blocks. This also
	 * contains hooked functions that will be removed by remove_default_hooks.
	 *
	 * The array format:
	 * [
	 *   <hook-name> => [
	 *     block_name => <block-name>,
	 *     position => before|after,
	 *     hooked => [
	 *       <function-name> => <priority>,
	 *        ...
	 *     ],
	 *     permanently_removed_actions => [
	 *         <function-name>
	 *    ]
	 *  ],
	 * ]
	 * Where:
	 * - hook-name is the name of the hook that will be replaced.
	 * - block-name is the name of the block that will replace the hook.
	 * - position is the position of the block relative to the hook.
	 * - hooked is an array of functions hooked to the hook that will be
	 *   replaced. The key is the function name and the value is the
	 *   priority.
	 * - permanently_removed_actions is an array of functions that we do not want to re-add after they have been removed to avoid duplicate content with the Products block and its inner blocks.
	 */
	protected function set_hook_data() {
		$this->hook_data = array(
			'woocommerce_before_main_content'         => array(
				'block_names' => array( 'core/query', 'woocommerce/product-collection' ),
				'position'    => 'before',
				'hooked'      => array(
					'woocommerce_output_content_wrapper' => 10,
					'woocommerce_breadcrumb'             => 20,
				),
			),
			'woocommerce_after_main_content'          => array(
				'block_names' => array( 'core/query', 'woocommerce/product-collection' ),
				'position'    => 'after',
				'hooked'      => array(
					'woocommerce_output_content_wrapper_end' => 10,
				),
			),
			'woocommerce_before_shop_loop_item_title' => array(
				'block_names' => array( 'core/post-title' ),
				'position'    => 'before',
				'hooked'      => array(
					'woocommerce_show_product_loop_sale_flash' => 10,
					'woocommerce_template_loop_product_thumbnail' => 10,
				),
			),
			'woocommerce_shop_loop_item_title'        => array(
				'block_names' => array( 'core/post-title' ),
				'position'    => 'after',
				'hooked'      => array(
					'woocommerce_template_loop_product_title' => 10,
				),
			),
			'woocommerce_after_shop_loop_item_title'  => array(
				'block_names' => array( 'core/post-title' ),
				'position'    => 'after',
				'hooked'      => array(
					'woocommerce_template_loop_rating' => 5,
					'woocommerce_template_loop_price'  => 10,
				),
			),
			'woocommerce_before_shop_loop_item'       => array(
				'block_names' => array( self::LOOP_ITEM_ID ),
				'position'    => 'before',
				'hooked'      => array(
					'woocommerce_template_loop_product_link_open' => 10,
				),
			),
			'woocommerce_after_shop_loop_item'        => array(
				'block_names' => array( self::LOOP_ITEM_ID ),
				'position'    => 'after',
				'hooked'      => array(
					'woocommerce_template_loop_product_link_close' => 5,
					'woocommerce_template_loop_add_to_cart' => 10,
				),
			),
			'woocommerce_before_shop_loop'            => array(
				'block_names'                 => array( 'core/post-template', 'woocommerce/product-template' ),
				'position'                    => 'before',
				'hooked'                      => array(
					'woocommerce_output_all_notices' => 10,
					'woocommerce_result_count'       => 20,
					'woocommerce_catalog_ordering'   => 30,
				),
				'permanently_removed_actions' => array(
					'woocommerce_output_all_notices',
					'woocommerce_result_count',
					'woocommerce_catalog_ordering',
				),
			),
			'woocommerce_after_shop_loop'             => array(
				'block_names'                 => array( 'core/post-template', 'woocommerce/product-template' ),
				'position'                    => 'after',
				'hooked'                      => array(
					'woocommerce_pagination' => 10,
				),
				'permanently_removed_actions' => array(
					'woocommerce_pagination',
				),
			),
			'woocommerce_no_products_found'           => array(
				'block_names'                 => array( 'core/query-no-results' ),
				'position'                    => 'before',
				'hooked'                      => array(
					'wc_no_products_found' => 10,
				),
				'permanently_removed_actions' => array(
					'wc_no_products_found',
				),
			),
			'woocommerce_archive_description'         => array(
				'block_names' => array( 'core/term-description' ),
				'position'    => 'before',
				'hooked'      => array(
					'woocommerce_taxonomy_archive_description' => 10,
					'woocommerce_product_archive_description'  => 10,
				),
			),
		);
	}

	/**
	 * Check if current page is a product archive template.
	 */
	private function is_archive_template() {
		return is_shop() || is_product_taxonomy();
	}

	/**
	 * Loop through inner blocks recursively to find the Products blocks that
	 * inherits query from template.
	 *
	 * @param array $block Parsed block data.
	 */
	private function inner_blocks_walker( &$block ) {
		if (
			$this->is_products_block_with_inherit_query( $block ) || $this->is_product_collection_block_with_inherit_query( $block )
		) {
			$this->inject_attribute( $block );
			$this->remove_default_hooks();
		}

		if ( ! empty( $block['innerBlocks'] ) ) {
			array_walk( $block['innerBlocks'], array( $this, 'inner_blocks_walker' ) );
		}
	}

	/**
	 * Restore default hooks except the ones that are not supposed to be re-added.
	 */
	private function restore_default_hooks() {
		foreach ( $this->hook_data as $hook => $data ) {
			if ( ! isset( $data['hooked'] ) ) {
				continue;
			}
			foreach ( $data['hooked'] as $callback => $priority ) {
				if ( ! in_array( $callback, $data['permanently_removed_actions'] ?? array(), true ) ) {
					add_action( $hook, $callback, $priority );
				}
			}
		}
	}

	/**
	 * Check whether block is within the product-query namespace.
	 *
	 * @param array $block Parsed block data.
	 */
	private function is_block_within_namespace( $block ) {
		$attributes = $block['attrs'];

		return isset( $attributes['__woocommerceNamespace'] ) && 'woocommerce/product-query/product-template' === $attributes['__woocommerceNamespace'];
	}

	/**
	 * Check whether block has isInherited attribute assigned.
	 *
	 * @param array $block Parsed block data.
	 */
	private function is_block_inherited( $block ) {
		$attributes = $block['attrs'];

		$outcome = isset( $attributes['isInherited'] ) && 1 === $attributes['isInherited'];

		return $outcome;
	}

	/**
	 * The core/post-template has two different block names:
	 * - core/post-template when the wrapper is rendered.
	 * - core/null when the loop item is rendered.
	 *
	 * @param array $block Parsed block data.
	 */
	private function is_null_post_template( $block ) {
		$block_name = $block['blockName'];

		return 'core/null' === $block_name && ( $this->is_block_inherited( $block ) || $this->is_block_within_namespace( $block ) );
	}

	/**
	 * Check whether block is a Post template.
	 *
	 * @param string $block_name Block name.
	 */
	private function is_post_template( $block_name ) {
		return 'core/post-template' === $block_name;
	}

	/**
	 * Check whether block is a Product Template.
	 *
	 * @param string $block_name Block name.
	 */
	private function is_product_template( $block_name ) {
		return 'woocommerce/product-template' === $block_name;
	}

	/**
	 * Check if block is either a Post template or a Product Template
	 *
	 * @param string $block_name Block name.
	 */
	private function is_post_or_product_template( $block_name ) {
		return $this->is_post_template( $block_name ) || $this->is_product_template( $block_name );
	}

	/**
	 * Check if the block is a Products block that inherits query from template.
	 *
	 * @param array $block Parsed block data.
	 */
	private function is_products_block_with_inherit_query( $block ) {
		return 'core/query' === $block['blockName'] &&
		isset( $block['attrs']['namespace'] ) &&
		'woocommerce/product-query' === $block['attrs']['namespace'] &&
		isset( $block['attrs']['query']['inherit'] ) &&
		$block['attrs']['query']['inherit'];
	}

	/**
	 * Check if the block is a Product Collection block that inherits query from template.
	 *
	 * @param array $block Parsed block data.
	 */
	private function is_product_collection_block_with_inherit_query( $block ) {
		return 'woocommerce/product-collection' === $block['blockName'] &&
		isset( $block['attrs']['query']['inherit'] ) &&
		$block['attrs']['query']['inherit'];
	}


	/**
	 * Recursively inject the custom attribute to all nested blocks.
	 *
	 * @param array $block Parsed block data.
	 */
	private function inject_attribute( &$block ) {
		$block['attrs']['isInherited'] = 1;

		if ( ! empty( $block['innerBlocks'] ) ) {
			array_walk( $block['innerBlocks'], array( $this, 'inject_attribute' ) );
		}
	}
}
PK     \nv    *  Templates/ProductSearchResultsTemplate.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Templates;

use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;

/**
 * ProductSearchResultsTemplate class.
 *
 * @internal
 */
class ProductSearchResultsTemplate extends AbstractTemplate {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'product-search-results';

	/**
	 * Initialization method.
	 */
	public function init() {
		add_action( 'template_redirect', array( $this, 'render_block_template' ) );
		add_filter( 'search_template_hierarchy', array( $this, 'update_search_template_hierarchy' ), 10, 3 );
	}

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Product Search Results', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Displays search results for your store.', 'woocommerce' );
	}

	/**
	 * Run template-specific logic when the query matches this template.
	 */
	public function render_block_template() {
		if ( ! is_embed() && is_post_type_archive( 'product' ) && is_search() ) {
			$compatibility_layer = new ArchiveProductTemplatesCompatibility();
			$compatibility_layer->init();

			$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );

			if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
				add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
			}
		}
	}

	/**
	 * When the search is for products and a block theme is active, render the Product Search Template.
	 *
	 * @param array $templates Templates that match the search hierarchy.
	 */
	public function update_search_template_hierarchy( $templates ) {
		if ( ( is_search() && is_post_type_archive( 'product' ) ) && wp_is_block_theme() ) {
			array_unshift( $templates, self::SLUG );
		}
		return $templates;
	}
}
PK     \°-l  l  +  Templates/ComingSoonSocialLinksTemplate.phpnu [        <?php

declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * ComingSoonSocialLinksTemplate class.
 *
 * @internal
 */
class ComingSoonSocialLinksTemplate extends AbstractTemplatePart {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'coming-soon-social-links';

	/**
	 * The template part area where the template part belongs.
	 *
	 * @var string
	 */
	public $template_area = 'uncategorized';

	/**
	 * Initialization method.
	 */
	public function init() {}

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Coming soon social links', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Reusable template part for displaying social links on the coming soon page.', 'woocommerce' );
	}

	/**
	 * Returns the page object assigned to this template/page.
	 *
	 * @return \WP_Post|null Post object or null.
	 */
	protected function get_placeholder_page() {
		return null;
	}
}
PK     \&9  9  0  Templates/SingleProductTemplateCompatibility.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\Templates;

use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;

/**
 * SingleProductTemplateCompatibility class.
 *
 * To bridge the gap on compatibility with PHP hooks and Single Product templates.
 *
 * @internal
 */
class SingleProductTemplateCompatibility extends AbstractTemplateCompatibility {
	const IS_FIRST_BLOCK = '__wooCommerceIsFirstBlock';
	const IS_LAST_BLOCK  = '__wooCommerceIsLastBlock';

	/**
	 * Inject hooks to rendered content of corresponding blocks.
	 *
	 * @param mixed $block_content The rendered block content.
	 * @param mixed $block         The parsed block data.
	 *
	 * @return string
	 */
	public function inject_hooks( $block_content, $block ) {
		if ( ! is_product() ) {
			return $block_content;
		}

		$this->remove_default_hooks();

		$block_name = $block['blockName'];

		$block_hooks = array_filter(
			$this->hook_data,
			function ( $hook ) use ( $block_name ) {
				return in_array( $block_name, $hook['block_names'], true );
			}
		);

		$first_or_last_block_content = $this->inject_hook_to_first_and_last_blocks( $block_content, $block, $block_hooks );

		if ( isset( $first_or_last_block_content ) ) {
			return $first_or_last_block_content;
		}

		return sprintf(
			'%1$s%2$s%3$s',
			$this->get_hooks_buffer( $block_hooks, 'before' ),
			$block_content,
			$this->get_hooks_buffer( $block_hooks, 'after' )
		);
	}

	/**
	 * Inject custom hooks to the first and last blocks.
	 * Since that there is a custom logic for the first and last block, we have to inject the hooks manually.
	 * The first block supports the following hooks:
	 * woocommerce_before_single_product
	 * woocommerce_before_single_product_summary
	 *
	 * The last block supports the following hooks:
	 * woocommerce_after_single_product
	 *
	 * @param mixed $block_content The rendered block content.
	 * @param mixed $block         The parsed block data.
	 * @param array $block_hooks   The hooks that should be injected to the block.
	 *
	 * @return string
	 */
	private function inject_hook_to_first_and_last_blocks( $block_content, $block, $block_hooks ) {
		$first_block_hook = array(
			'before' => array(
				'woocommerce_before_main_content'   => $this->hook_data['woocommerce_before_main_content'],
				'woocommerce_before_single_product' => $this->hook_data['woocommerce_before_single_product'],
				'woocommerce_before_single_product_summary' => $this->hook_data['woocommerce_before_single_product_summary'],
			),
			'after'  => array(),
		);

		$last_block_hook = array(
			'before' => array(),
			'after'  => array(
				'woocommerce_after_single_product' => $this->hook_data['woocommerce_after_single_product'],
				'woocommerce_after_main_content'   => $this->hook_data['woocommerce_after_main_content'],
				'woocommerce_sidebar'              => $this->hook_data['woocommerce_sidebar'],
			),
		);

		if ( isset( $block['attrs'][ self::IS_FIRST_BLOCK ] ) && isset( $block['attrs'][ self::IS_LAST_BLOCK ] ) ) {
			return sprintf(
				'%1$s%2$s',
				$this->inject_hooks_after_the_wrapper(
					$block_content,
					array_merge(
						$first_block_hook['before'],
						$block_hooks,
						$last_block_hook['before']
					)
				),
				$this->get_hooks_buffer(
					array_merge(
						$first_block_hook['after'],
						$block_hooks,
						$last_block_hook['after']
					),
					'after'
				)
			);
		}

		if ( isset( $block['attrs'][ self::IS_FIRST_BLOCK ] ) ) {
			return sprintf(
				'%1$s%2$s',
				$this->inject_hooks_after_the_wrapper(
					$block_content,
					array_merge(
						$first_block_hook['before'],
						$block_hooks
					)
				),
				$this->get_hooks_buffer(
					array_merge(
						$first_block_hook['after'],
						$block_hooks
					),
					'after'
				)
			);
		}

		if ( isset( $block['attrs'][ self::IS_LAST_BLOCK ] ) ) {
			return sprintf(
				'%1$s%2$s%3$s',
				$this->get_hooks_buffer(
					array_merge(
						$last_block_hook['before'],
						$block_hooks
					),
					'before'
				),
				$block_content,
				$this->get_hooks_buffer(
					array_merge(
						$block_hooks,
						$last_block_hook['after']
					),
					'after'
				)
			);
		}
	}

	/**
	 * Update the render block data to inject our custom attribute needed to
	 * determine which is the first block of the Single Product Template.
	 *
	 * @param array         $parsed_block The block being rendered.
	 * @param array         $source_block An un-modified copy of $parsed_block, as it appeared in the source content.
	 * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
	 *
	 * @return array
	 */
	public function update_render_block_data( $parsed_block, $source_block, $parent_block ) {
		return $parsed_block;
	}

	/**
	 * Set supported hooks.
	 */
	protected function set_hook_data() {
		$this->hook_data = array(
			'woocommerce_before_main_content'           => array(
				'block_names' => array(),
				'position'    => 'before',
				'hooked'      => array(
					'woocommerce_output_content_wrapper' => 10,
					'woocommerce_breadcrumb'             => 20,
				),
			),
			'woocommerce_after_main_content'            => array(
				'block_names' => array(),
				'position'    => 'after',
				'hooked'      => array(
					'woocommerce_output_content_wrapper_end' => 10,
				),
			),
			'woocommerce_sidebar'                       => array(
				'block_names' => array(),
				'position'    => 'after',
				'hooked'      => array(
					'woocommerce_get_sidebar' => 10,
				),
			),
			'woocommerce_before_single_product'         => array(
				'block_names' => array(),
				'position'    => 'before',
				'hooked'      => array(
					'woocommerce_output_all_notices' => 10,
				),
			),
			'woocommerce_before_single_product_summary' => array(
				'block_names' => array(),
				'position'    => 'before',
				'hooked'      => array(
					'woocommerce_show_product_sale_flash' => 10,
					'woocommerce_show_product_images'     => 20,
				),
			),
			'woocommerce_single_product_summary'        => array(
				'block_names' => array( 'core/post-excerpt', 'woocommerce/product-summary' ),
				'position'    => 'before',
				'hooked'      => array(
					'woocommerce_template_single_title'   => 5,
					'woocommerce_template_single_rating'  => 10,
					'woocommerce_template_single_price'   => 10,
					'woocommerce_template_single_excerpt' => 20,
					'woocommerce_template_single_add_to_cart' => 30,
					'woocommerce_template_single_meta'    => 40,
					'woocommerce_template_single_sharing' => 50,
				),
			),
			'woocommerce_after_single_product'          => array(
				'block_names' => array(),
				'position'    => 'after',
				'hooked'      => array(),
			),
			'woocommerce_product_meta_start'            => array(
				'block_names' => array( 'woocommerce/product-meta' ),
				'position'    => 'before',
				'hooked'      => array(),
			),
			'woocommerce_product_meta_end'              => array(
				'block_names' => array( 'woocommerce/product-meta' ),
				'position'    => 'after',
				'hooked'      => array(),
			),
			'woocommerce_share'                         => array(
				'block_names' => array( 'woocommerce/product-details' ),
				'position'    => 'before',
				'hooked'      => array(),
			),
			'woocommerce_after_single_product_summary'  => array(
				'block_names' => array( 'woocommerce/product-details' ),
				'position'    => 'after',
				'hooked'      => array(
					'woocommerce_output_product_data_tabs' => 10,
					// We want to display the upsell products after the last block that belongs to the Single Product.
					// 'woocommerce_upsell_display'           => 15.
					'woocommerce_output_related_products'  => 20,
				),
			),
		);
	}

	/**
	 * Add compatibility layer to the first and last block of the Single Product Template.
	 *
	 * @param string $template_content Template.
	 * @return string
	 */
	public static function add_compatibility_layer( $template_content ) {
		// Return early if we've already applied the compatibility layer.
		if ( false !== strpos( $template_content, self::IS_FIRST_BLOCK ) ) {
			return $template_content;
		}

		$blocks = parse_blocks( $template_content );
		if ( self::has_single_product_template_blocks( $blocks ) ) {
			$blocks = self::wrap_single_product_template( $template_content );
		}
		$template = self::inject_custom_attributes_to_first_and_last_block_single_product_template( $blocks );
		return self::serialize_blocks( $template );
	}

	/**
	 * For compatibility reason, we need to wrap the Single Product template in a div with specific class.
	 * For more details, see https://github.com/woocommerce/woocommerce-blocks/issues/8314.
	 *
	 * @param string $template_content Template Content.
	 * @return array Wrapped template content inside a div.
	 */
	private static function wrap_single_product_template( $template_content ) {
		$parsed_blocks  = parse_blocks( $template_content );
		$grouped_blocks = self::group_blocks( $parsed_blocks );

		$wrapped_blocks = array_map(
			function ( $blocks ) {
				if ( 'core/template-part' === $blocks[0]['blockName'] ) {
					return $blocks;
				}

				$has_single_product_template_blocks = self::has_single_product_template_blocks( $blocks );

				if ( $has_single_product_template_blocks ) {
					$wrapped_block = self::create_wrap_block_group( $blocks );
					return array( $wrapped_block[0] );
				}
				return $blocks;
			},
			$grouped_blocks
		);
		return $wrapped_blocks;
	}

	/**
	 * Add custom attributes to the first group block and last group block that wrap Single Product Template blocks.
	 *
	 * @param array $wrapped_blocks Wrapped blocks.
	 * @return array
	 */
	private static function inject_custom_attributes_to_first_and_last_block_single_product_template( $wrapped_blocks ) {
		$template_with_custom_attributes = array_reduce(
			$wrapped_blocks,
			function ( $carry, $item ) {

				$index          = $carry['index'];
				$carry['index'] = $carry['index'] + 1;
				// If the block is a child of a group block, we need to get the first block of the group.
				$block = isset( $item[0] ) ? $item[0] : $item;

				if ( 'core/template-part' === $block['blockName'] || self::is_custom_html( $block ) ) {
					$carry['template'][] = $block;
					return $carry;
				}

				if ( '' === $carry['first_block']['index'] ) {
					$block['attrs'][ self::IS_FIRST_BLOCK ] = true;
					$carry['first_block']['index']          = $index;
				}

				if ( '' !== $carry['last_block']['index'] ) {
					$index_element                         = $carry['last_block']['index'];
					$carry['last_block']['index']          = $index;
					$block['attrs'][ self::IS_LAST_BLOCK ] = true;
					unset( $carry['template'][ $index_element ]['attrs'][ self::IS_LAST_BLOCK ] );

					$carry['template'][] = $block;

					return $carry;
				}

				$block['attrs'][ self::IS_LAST_BLOCK ] = true;
				$carry['last_block']['index']          = $index;

				$carry['template'][] = $block;

				return $carry;
			},
			array(
				'template'    => array(),
				'first_block' => array(
					'index' => '',
				),
				'last_block'  => array(
					'index' => '',
				),
				'index'       => 0,
			)
		);

		return array( $template_with_custom_attributes['template'] );
	}

	/**
	 * Wrap all the blocks inside the template in a group block.
	 *
	 * @param array $blocks Array of parsed block objects.
	 * @return array Group block with the blocks inside.
	 */
	private static function create_wrap_block_group( $blocks ) {
		$serialized_blocks = serialize_blocks( $blocks );

		$new_block = parse_blocks(
			sprintf(
				'<!-- wp:group {"className":"woocommerce product"} -->
				<div class="wp-block-group woocommerce product">
					%1$s
				</div>
			<!-- /wp:group -->',
				$serialized_blocks
			)
		);

		$new_block['innerBlocks'] = $blocks;

		return $new_block;
	}

	/**
	 * Check if the Single Product template has a single product template block:
	 * woocommerce/product-gallery-image, woocommerce/product-details, woocommerce/add-to-cart-form, etc.
	 *
	 * @param array $parsed_blocks Array of parsed block objects.
	 * @return bool True if the template has a single product template block, false otherwise.
	 */
	private static function has_single_product_template_blocks( $parsed_blocks ) {
		$single_product_template_blocks = array( 'woocommerce/product-image-gallery', 'woocommerce/product-gallery', 'woocommerce/product-details', 'woocommerce/add-to-cart-form', 'woocommerce/add-to-cart-with-options', 'woocommerce/product-meta', 'woocommerce/product-price', 'woocommerce/breadcrumbs' );

		return BlockTemplateUtils::has_block_including_patterns( $single_product_template_blocks, $parsed_blocks );
	}


	/**
	 * Group blocks in this way:
	 * B1 + TP1 + B2 + B3 + B4 + TP2 + B5
	 * (B = Block, TP = Template Part)
	 * becomes:
	 * [[B1], [TP1], [B2, B3, B4], [TP2], [B5]]
	 *
	 * @param array $parsed_blocks Array of parsed block objects.
	 * @return array Array of blocks grouped by template part.
	 */
	private static function group_blocks( $parsed_blocks ) {
		return array_reduce(
			$parsed_blocks,
			function ( array $carry, array $block ) {
				if ( 'core/template-part' === $block['blockName'] ) {
					$carry[] = array( $block );
					return $carry;
				}
				$last_element_index = count( $carry ) - 1;
				if ( isset( $carry[ $last_element_index ][0]['blockName'] ) && 'core/template-part' !== $carry[ $last_element_index ][0]['blockName'] ) {
					$carry[ $last_element_index ][] = $block;
					return $carry;
				}
				$carry[] = array( $block );
				return $carry;
			},
			array()
		);
	}

	/**
	 * Inject the hooks after the div wrapper.
	 *
	 * @param string $block_content Block Content.
	 * @param array  $hooks Hooks to inject.
	 * @return array
	 */
	private function inject_hooks_after_the_wrapper( $block_content, $hooks ) {
		$closing_tag_position = strpos( $block_content, '>' );

		return substr_replace(
			$block_content,
			$this->get_hooks_buffer(
				$hooks,
				'before'
			),
			// Add 1 to the position to inject the content after the closing tag.
			$closing_tag_position + 1,
			0
		);
	}


	/**
	 * Plain custom HTML block is parsed as block with an empty blockName with a filled innerHTML.
	 *
	 * @param array $block Parse block.
	 * @return bool
	 */
	private static function is_custom_html( $block ) {
		return empty( $block['blockName'] ) && ! empty( $block['innerHTML'] );
	}

	/**
	 * Serialize template.
	 *
	 * @param array $parsed_blocks Parsed blocks.
	 * @return string
	 */
	private static function serialize_blocks( $parsed_blocks ) {
		return array_reduce(
			$parsed_blocks,
			function ( $carry, $item ) {
				if ( is_array( $item ) ) {
					return $carry . serialize_blocks( $item );
				}
				return $carry . serialize_block( $item );
			},
			''
		);
	}
}
PK     \b`    9  Templates/VariableProductAddToCartWithOptionsTemplate.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * VariableProductAddToCartWithOptionsTemplate class.
 *
 * @internal
 */
class VariableProductAddToCartWithOptionsTemplate extends AbstractTemplatePart {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'variable-product-add-to-cart-with-options';

	/**
	 * The template part area where the template part belongs.
	 *
	 * @var string
	 */
	public $template_area = 'add-to-cart-with-options';

	/**
	 * Initialization method.
	 */
	public function init() {
	}

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Variable Product Add to Cart + Options', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Template used to display the Add to Cart + Options form for Variable Products.', 'woocommerce' );
	}
}
PK     \*Gze_  _  $  Templates/CheckoutHeaderTemplate.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * CheckoutHeader Template class.
 *
 * @internal
 */
class CheckoutHeaderTemplate extends AbstractTemplatePart {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'checkout-header';

	/**
	 * The template part area where the template part belongs.
	 *
	 * @var string
	 */
	public $template_area = 'header';

	/**
	 * Initialization method.
	 */
	public function init() {}

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Checkout Header', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Template used to display the simplified Checkout header.', 'woocommerce' );
	}
}
PK     \=@e    "  Templates/ProductBrandTemplate.phpnu [        <?php
declare( strict_types = 1 );
namespace Automattic\WooCommerce\Blocks\Templates;

use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;

/**
 * ProductBrandTemplate class.
 *
 * @internal
 */
class ProductBrandTemplate extends AbstractTemplateWithFallback {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'taxonomy-product_brand';

	/**
	 * The template used as a fallback if that one is customized.
	 *
	 * @var string
	 */
	public string $fallback_template = ProductCatalogTemplate::SLUG;

	/**
	 * Whether this is a taxonomy template.
	 *
	 * @var bool
	 */
	public bool $is_taxonomy_template = true;

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Products by Brand', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Displays products filtered by a brand.', 'woocommerce' );
	}

	/**
	 * Run template-specific logic when the query matches this template.
	 */
	public function render_block_template() {
		if ( ! is_embed() && is_product_taxonomy() && is_tax( 'product_brand' ) ) {
			$compatibility_layer = new ArchiveProductTemplatesCompatibility();
			$compatibility_layer->init();

			$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );

			if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
				add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
			}

			add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
		}
	}
}
PK     \ȧ    +  Templates/AbstractTemplateCompatibility.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * AbstractTemplateCompatibility class.
 *
 * To bridge the gap on compatibility with PHP hooks and blockified templates.
 *
 * @internal
 */
abstract class AbstractTemplateCompatibility {
	/**
	 * The data of supported hooks, containing the hook name, the block name,
	 * position, and the callbacks.
	 *
	 * @var array $hook_data The hook data.
	 */
	protected $hook_data;

	/**
	 * Initialization method.
	 */
	public function init() {
		$this->set_hook_data();

		add_filter(
			'render_block_data',
			function ( $parsed_block, $source_block, $parent_block ) {
				/**
				* Filter to disable the compatibility layer for the blockified templates.
				*
				* This hook allows to disable the compatibility layer for the blockified templates.
				*
				* @since 7.6.0
				* @param boolean.
				*/
				$is_disabled_compatility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false );

				if ( $is_disabled_compatility_layer ) {
					return $parsed_block;
				}

				return $this->update_render_block_data( $parsed_block, $source_block, $parent_block );
			},
			10,
			3
		);

		add_filter(
			'render_block',
			function ( $block_content, $block ) {
				/**
				* Filter to disable the compatibility layer for the blockified templates.
				*
				* This hook allows to disable the compatibility layer for the blockified.
				*
				* @since 7.6.0
				* @param boolean.
				*/
				$is_disabled_compatibility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false );

				if ( $is_disabled_compatibility_layer ) {
					return $block_content;
				}

				return $this->inject_hooks( $block_content, $block );
			},
			10,
			2
		);
	}

	/**
	 * Update the render block data to inject our custom attribute needed to
	 * determine which blocks belong to an inherited Products block.
	 *
	 * @param array         $parsed_block The block being rendered.
	 * @param array         $source_block An un-modified copy of $parsed_block, as it appeared in the source content.
	 * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
	 *
	 * @return array
	 */
	abstract public function update_render_block_data( $parsed_block, $source_block, $parent_block );

	/**
	 * Inject hooks to rendered content of corresponding blocks.
	 *
	 * @param mixed $block_content The rendered block content.
	 * @param mixed $block         The parsed block data.
	 * @return string
	 */
	abstract public function inject_hooks( $block_content, $block );

	/**
	 * The hook data to inject to the rendered content of blocks. This also
	 * contains hooked functions that will be removed by remove_default_hooks.
	 *
	 * The array format:
	 * [
	 *   <hook-name> => [
	 *     block_names => [ <block-name>, ... ],
	 *     position => before|after,
	 *     hooked => [
	 *       <function-name> => <priority>,
	 *        ...
	 *     ],
	 *  ],
	 * ]
	 * Where:
	 * - hook-name is the name of the hook that will be replaced.
	 * - block-names is the array block names that hook will be attached to.
	 * - position is the position of the block relative to the hook.
	 * - hooked is an array of functions hooked to the hook that will be
	 *   replaced. The key is the function name and the value is the
	 *   priority.
	 */
	abstract protected function set_hook_data();

	/**
	 * Remove the default callback added by WooCommerce. We replaced these
	 * callbacks by blocks so we have to remove them to prevent duplicated
	 * content.
	 */
	protected function remove_default_hooks() {
		foreach ( $this->hook_data as $hook => $data ) {
			if ( ! isset( $data['hooked'] ) ) {
				continue;
			}
			foreach ( $data['hooked'] as $callback => $priority ) {
				remove_action( $hook, $callback, $priority );
			}
		}
		$class_name = basename( str_replace( '\\', '/', get_class( $this ) ) );

		/**
		 * When extensions implement their equivalent blocks of the template
		 * hook functions, they can use this filter to register their old hooked
		 * data here, so in the blockified template, the old hooked functions
		 * can be removed in favor of the new blocks while keeping the old
		 * hooked functions working in classic templates.
		 *
		 * Accepts an array of hooked data. The array should be in the following
		 * format:
		 * [
		 *   [
		 *     hook => <hook-name>,
		 *     function => <function-name>,
		 *     priority => <priority>,
		 *  ],
		 *  ...
		 * ]
		 * Where:
		 * - hook-name is the name of the hook that have the functions hooked to.
		 * - function-name is the hooked function name.
		 * - priority is the priority of the hooked function.
		 *
		 * @since 9.5.0
		 * @param array $data Additional hooked data. Default to empty
		 * @param string $class_name Class name within which the hook is called.
		 * Either ArchiveProductTemplatesCompatibility or SingleProductTemplateCompatibility.
		 */
		$additional_hook_data = apply_filters( 'woocommerce_blocks_hook_compatibility_additional_data', array(), $class_name );

		if ( empty( $additional_hook_data ) || ! is_array( $additional_hook_data ) ) {
			return;
		}

		foreach ( $additional_hook_data as $data ) {
			if ( ! isset( $data['hook'], $data['function'], $data['priority'] ) ) {
				continue;
			}
			remove_action( $data['hook'], $data['function'], $data['priority'] );
		}
	}

	/**
	 * Get the buffer content of the hooks to append/prepend to render content.
	 *
	 * @param array  $hooks    The hooks to be rendered.
	 * @param string $position The position of the hooks.
	 *
	 * @return string
	 */
	protected function get_hooks_buffer( $hooks, $position ) {
		ob_start();
		foreach ( $hooks as $hook => $data ) {
			if ( $data['position'] === $position ) {
				/**
				 * Action to render the content of a hook.
				 *
				 * @since 9.5.0
				 */
				do_action( $hook );
			}
		}
		return ob_get_clean();
	}
}
PK     \z       Templates/ProductTagTemplate.phpnu [        <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\Blocks\Templates;

use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;

/**
 * ProductTagTemplate class.
 *
 * @internal
 */
class ProductTagTemplate extends AbstractTemplateWithFallback {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'taxonomy-product_tag';

	/**
	 * The template used as a fallback if that one is customized.
	 *
	 * @var string
	 */
	public string $fallback_template = ProductCatalogTemplate::SLUG;

	/**
	 * Whether this is a taxonomy template.
	 *
	 * @var bool
	 */
	public bool $is_taxonomy_template = true;

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Products by Tag', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Displays products filtered by a tag.', 'woocommerce' );
	}

	/**
	 * Run template-specific logic when the query matches this template.
	 */
	public function render_block_template() {
		if ( ! is_embed() && is_product_taxonomy() && is_tax( 'product_tag' ) ) {
			$compatibility_layer = new ArchiveProductTemplatesCompatibility();
			$compatibility_layer->init();

			$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );

			if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
				add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
			}

			add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
		}
	}
}
PK     \DZqR
  R
     Templates/ComingSoonTemplate.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * ComingSoonTemplate class.
 *
 * @internal
 */
class ComingSoonTemplate extends AbstractPageTemplate {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'coming-soon';

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Page: Coming soon', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Let your shoppers know your site or part of your site is under construction.', 'woocommerce' );
	}

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

	/**
	 * True when viewing the coming soon page.
	 *
	 * @return boolean
	 */
	protected function is_active_template() {
		return false;
	}

	/**
	 * Returns the font family for the body and heading.
	 *
	 * When the current theme is not an FSE theme, we use the default fonts.
	 * When the current theme is an FSE theme, we use the fonts from the theme.json file if available except for the 'twentytwentyfour' theme.
	 *
	 * @return array
	 */
	public static function get_font_families() {
		$default_fonts = array(
			'heading' => 'cardo',
			'body'    => 'inter',
		);

		if ( ! wp_is_block_theme() ) {
			return $default_fonts;
		}

		$current_theme = wp_get_theme()->get_stylesheet();

		if ( 'twentytwentyfour' === $current_theme ) {
			return array(
				'heading' => 'heading',
				'body'    => 'body',
			);
		}

		if ( ! function_exists( 'wp_get_global_settings' ) ) {
			return $default_fonts;
		}

		$settings = wp_get_global_settings();
		if (
			! isset( $settings['typography']['fontFamilies']['theme'] )
			|| ! is_array( $settings['typography']['fontFamilies']['theme'] )
		) {
			return $default_fonts;
		}

		$theme_fonts = $settings['typography']['fontFamilies']['theme'];

		// Override default fonts if available in theme.json.
		if ( isset( $theme_fonts[0]['slug'] ) && ! empty( $theme_fonts[0]['slug'] ) ) {
			// Convert the font family to lowercase and replace spaces with hyphens.
			$default_fonts['heading'] = strtolower( str_replace( ' ', '-', $theme_fonts[0]['slug'] ) );
		}
		if ( isset( $theme_fonts[1]['slug'] ) && ! empty( $theme_fonts[1]['slug'] ) ) {
			$default_fonts['body']      = strtolower( str_replace( ' ', '-', $theme_fonts[1]['slug'] ) );
			$default_fonts['paragraph'] = $default_fonts['body'];
		}

		return $default_fonts;
	}
}
PK     \F      Templates/CartTemplate.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * CartTemplate class.
 *
 * @internal
 */
class CartTemplate extends AbstractPageTemplate {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'page-cart';

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Page: Cart', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'The Cart template displays the items selected by the user for purchase, including quantities, prices, and discounts. It allows users to review their choices before proceeding to checkout.', 'woocommerce' );
	}

	/**
	 * Returns the page object assigned to this template/page.
	 *
	 * @return \WP_Post|null Post object or null.
	 */
	protected function get_placeholder_page() {
		$page_id = wc_get_page_id( 'cart' );
		return $page_id ? get_post( $page_id ) : null;
	}

	/**
	 * True when viewing the cart page or cart endpoint.
	 *
	 * @return boolean
	 */
	protected function is_active_template() {
		global $post;
		$placeholder = $this->get_placeholder_page();
		return null !== $placeholder && $post instanceof \WP_Post && $placeholder->post_name === $post->post_name;
	}

	/**
	 * 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, self::SLUG );
			array_unshift( $templates, 'cart' );
		}
		return $templates;
	}
}
PK     \/3  3  *  Templates/AbstractTemplateWithFallback.phpnu [        <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\Blocks\Templates;

use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;

/**
 * AbstractTemplateWithFallback class.
 *
 * Shared logic for templates with fallbacks.
 *
 * @internal
 */
abstract class AbstractTemplateWithFallback extends AbstractTemplate {
	/**
	 * The fallback template to render if the existing template is not available.
	 *
	 * @var string
	 */
	public string $fallback_template;

	/**
	 * Initialization method.
	 */
	public function init() {
		add_filter( 'taxonomy_template_hierarchy', array( $this, 'template_hierarchy' ), 1 );
		add_action( 'template_redirect', array( $this, 'render_block_template' ) );
	}

	/**
	 * Add the fallback template to the hierarchy, right after the current template.
	 *
	 * @param array $templates Templates that match the taxonomy_template_hierarchy.
	 */
	public function template_hierarchy( $templates ) {
		$index = array_search( static::SLUG, $templates, true );
		if ( false === $index ) {
			$index = array_search( static::SLUG . '.php', $templates, true );
		}

		if (
			false !== $index && (
				! array_key_exists( $index + 1, $templates ) || $templates[ $index + 1 ] !== $this->fallback_template
			) ) {
			array_splice( $templates, $index + 1, 0, $this->fallback_template );
		}

		return $templates;
	}

	/**
	 * This method is hooked to WordPress' 'template_redirect' action and allows
	 * template classes to:
	 * 1. Decide when block templates should be rendered based on the context.
	 * 2. Execute specific logic, such as managing the compatibility layer for
	 *    legacy template support.
	 *
	 * Child classes must implement this method to define their template
	 * rendering conditions and any additional template-specific behavior.
	 */
	abstract public function render_block_template();
}
PK     \Į@~	  ~	  +  Templates/ClassicTemplatesCompatibility.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Templates;

use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;

/**
 * ClassicTemplatesCompatibility class.
 *
 * To bridge the gap on compatibility with widget blocks and classic PHP core templates.
 *
 * @internal
 */
class ClassicTemplatesCompatibility {

	/**
	 * Instance of the asset data registry.
	 *
	 * @var AssetDataRegistry
	 */
	protected $asset_data_registry;

	/**
	 * Constructor.
	 *
	 * @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
	 */
	public function __construct( AssetDataRegistry $asset_data_registry ) {
		$this->asset_data_registry = $asset_data_registry;
		$this->init();
	}

	/**
	 * Initialization method.
	 */
	protected function init() { // phpcs:ignore WooCommerce.Functions.InternalInjectionMethod.MissingPublic
		if ( ! wp_is_block_theme() ) {
			add_action( 'template_redirect', array( $this, 'set_classic_template_data' ) );
			// We need to set this data on the widgets screen so the filters render previews.
			add_action( 'load-widgets.php', array( $this, 'set_filterable_product_data' ) );
		}
	}

	/**
	 * Executes the methods which set the necessary data needed for filter blocks to work correctly as widgets in Classic templates.
	 *
	 * @return void
	 */
	public function set_classic_template_data() {
		$this->set_filterable_product_data();
		$this->set_php_template_data();
	}

	/**
	 * This method passes the value `has_filterable_products` to the front-end for product archive pages,
	 * so that widget product filter blocks are aware of the context they are in and can render accordingly.
	 *
	 * @return void
	 */
	public function set_filterable_product_data() {
		global $pagenow;

		if ( is_shop() || is_product_taxonomy() || 'widgets.php' === $pagenow ) {
			$this->asset_data_registry->add( 'hasFilterableProducts', true );
		}
	}

	/**
	 * This method passes the value `is_rendering_php_template` to the front-end of Classic themes,
	 * so that widget product filter blocks are aware of how to filter the products.
	 *
	 * This data only matters on WooCommerce product archive pages.
	 * On non-archive pages the merchant could be using the All Products block which is not a PHP template.
	 *
	 * @return void
	 */
	public function set_php_template_data() {
		if ( is_shop() || is_product_taxonomy() ) {
			$this->asset_data_registry->add( 'isRenderingPhpTemplate', true );
		}
	}
}
PK     \    %  Templates/ProductCategoryTemplate.phpnu [        <?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\Blocks\Templates;

use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;

/**
 * ProductCategoryTemplate class.
 *
 * @internal
 */
class ProductCategoryTemplate extends AbstractTemplateWithFallback {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'taxonomy-product_cat';

	/**
	 * The template used as a fallback if that one is customized.
	 *
	 * @var string
	 */
	public string $fallback_template = ProductCatalogTemplate::SLUG;

	/**
	 * Whether this is a taxonomy template.
	 *
	 * @var bool
	 */
	public bool $is_taxonomy_template = true;

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Products by Category', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Displays products filtered by a category.', 'woocommerce' );
	}

	/**
	 * Run template-specific logic when the query matches this template.
	 */
	public function render_block_template() {
		if ( ! is_embed() && is_product_taxonomy() && is_tax( 'product_cat' ) ) {
			$compatibility_layer = new ArchiveProductTemplatesCompatibility();
			$compatibility_layer->init();

			$templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) );

			if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) {
				add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' );
			}

			add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 );
		}
	}
}
PK     \Y    7  Templates/SimpleProductAddToCartWithOptionsTemplate.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\Templates;

/**
 * SimpleProductAddToCartWithOptionsTemplate class.
 *
 * @internal
 */
class SimpleProductAddToCartWithOptionsTemplate extends AbstractTemplatePart {

	/**
	 * The slug of the template.
	 *
	 * @var string
	 */
	const SLUG = 'simple-product-add-to-cart-with-options';

	/**
	 * The template part area where the template part belongs.
	 *
	 * @var string
	 */
	public $template_area = 'add-to-cart-with-options';

	/**
	 * Initialization method.
	 */
	public function init() {
	}

	/**
	 * Returns the title of the template.
	 *
	 * @return string
	 */
	public function get_template_title() {
		return _x( 'Simple Product Add to Cart + Options', 'Template name', 'woocommerce' );
	}

	/**
	 * Returns the description of the template.
	 *
	 * @return string
	 */
	public function get_template_description() {
		return __( 'Template used to display the Add to Cart + Options form for Simple Products.', 'woocommerce' );
	}
}
PK     \>{N  N    AssetsController.phpnu [        <?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Blocks;

use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
use Automattic\WooCommerce\Admin\Features\Features;

/**
 * AssetsController class.
 *
 * @since 5.0.0
 * @internal
 */
final class AssetsController {

	/**
	 * Asset API interface for various asset registration.
	 *
	 * @var AssetApi
	 */
	private $api;

	/**
	 * Constructor.
	 *
	 * @param AssetApi $asset_api  Asset API interface for various asset registration.
	 */
	public function __construct( AssetApi $asset_api ) {
		$this->api = $asset_api;
		$this->init();
	}

	/**
	 * Initialize class features.
	 */
	protected function init() { // phpcs:ignore WooCommerce.Functions.InternalInjectionMethod.MissingPublic
		add_action( 'init', array( $this, 'register_assets' ) );
		add_action( 'init', array( $this, 'register_script_modules' ) );
		add_action( 'enqueue_block_editor_assets', array( $this, 'register_and_enqueue_site_editor_assets' ) );
		add_filter( 'wp_resource_hints', array( $this, 'add_resource_hints' ), 10, 2 );
		add_action( 'body_class', array( $this, 'add_theme_body_class' ), 1 );
		add_action( 'admin_body_class', array( $this, 'add_theme_body_class' ), 1 );
		add_action( 'admin_enqueue_scripts', array( $this, 'update_block_style_dependencies' ), 20 );
		add_action( 'wp_enqueue_scripts', array( $this, 'update_block_settings_dependencies' ), 100 );
		add_action( 'admin_enqueue_scripts', array( $this, 'update_block_settings_dependencies' ), 100 );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_wc_entities' ), 100 );
		add_filter( 'js_do_concat', array( $this, 'skip_boost_minification_for_cart_checkout' ), 10, 2 );

		if ( Features::is_enabled( 'experimental-iapi-runtime' ) ) {
			// Run after the WordPress iAPI runtime has been registered by setting a lower priority.
			add_filter( 'wp_default_scripts', array( $this, 'reregister_core_iapi_runtime' ), 20 );
		}
	}

	/**
	 * Re-registers the iAPI runtime registered by WordPress Core/Gutenberg, allowing WooCommerce to register its own version of the iAPI runtime.
	 */
	public function reregister_core_iapi_runtime() {
		$interactivity_api_asset_data = $this->api->get_asset_data(
			$this->api->get_block_asset_build_path( 'interactivity-api-assets', 'php' )
		);

		foreach ( $interactivity_api_asset_data as $handle => $data ) {
			$handle_without_js = str_replace( '.js', '', $handle );
			if ( '@wordpress/interactivity' === $handle_without_js || '@wordpress/interactivity-router' === $handle_without_js ) {
				wp_deregister_script_module( $handle_without_js );
			}

			wp_register_script_module( $handle_without_js, plugins_url( $this->api->get_block_asset_build_path( $handle_without_js ), dirname( __DIR__ ) ), $data['dependencies'], $data['version'] );
		}
	}

	/**
	 * Register script modules.
	 */
	public function register_script_modules() {
		// Right now we only have one script modules build for supported interactivity API powered block front-ends.
		// We generate a combined asset file for that via DependencyExtractionWebpackPlugin to make registration more
		// efficient.
		$asset_data = $this->api->get_asset_data(
			$this->api->get_block_asset_build_path( 'interactivity-blocks-frontend-assets', 'php' )
		);

		foreach ( $asset_data as $handle => $data ) {
			$handle_without_js = str_replace( '.js', '', $handle );
			wp_register_script_module( $handle_without_js, plugins_url( $this->api->get_block_asset_build_path( $handle_without_js ), dirname( __DIR__ ) ), $data['dependencies'], $data['version'] );
		}
	}

	/**
	 * Register block scripts & styles.
	 */
	public function register_assets() {
		$this->register_style( 'wc-blocks-packages-style', plugins_url( $this->api->get_block_asset_build_path( 'packages-style', 'css' ), dirname( __DIR__ ) ), array(), 'all', true );
		$this->register_style( 'wc-blocks-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks', 'css' ), dirname( __DIR__ ) ), array(), 'all', true );
		$this->register_style( 'wc-blocks-editor-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks-editor-style', 'css' ), dirname( __DIR__ ) ), array( 'wp-edit-blocks' ), 'all', true );

		$this->api->register_script( 'wc-types', $this->api->get_block_asset_build_path( 'wc-types' ), array(), false );
		$this->api->register_script( 'wc-entities', 'assets/client/blocks/wc-entities.js', array(), false );
		$this->api->register_script( 'wc-blocks-middleware', 'assets/client/blocks/wc-blocks-middleware.js', array(), false );
		$this->api->register_script( 'wc-blocks-data-store', 'assets/client/blocks/wc-blocks-data.js', array( 'wc-blocks-middleware' ) );
		$this->api->register_script( 'wc-blocks-vendors', $this->api->get_block_asset_build_path( 'wc-blocks-vendors' ), array(), false );
		$this->api->register_script( 'wc-blocks-registry', 'assets/client/blocks/wc-blocks-registry.js', array(), false );
		$this->api->register_script( 'wc-blocks', $this->api->get_block_asset_build_path( 'wc-blocks' ), array( 'wc-blocks-vendors' ), false );
		$this->api->register_script( 'wc-blocks-shared-context', 'assets/client/blocks/wc-blocks-shared-context.js' );
		$this->api->register_script( 'wc-blocks-shared-hocs', 'assets/client/blocks/wc-blocks-shared-hocs.js', array(), false );

		// The price package is shared externally so has no blocks prefix.
		$this->api->register_script( 'wc-price-format', 'assets/client/blocks/price-format.js', array(), false );

		// Vendor scripts for blocks frontends (not including cart and checkout).
		$this->api->register_script( 'wc-blocks-frontend-vendors', $this->api->get_block_asset_build_path( 'wc-blocks-frontend-vendors-frontend' ), array(), true );

		// Cart and checkout frontend scripts.
		$this->api->register_script( 'wc-cart-checkout-vendors', $this->api->get_block_asset_build_path( 'wc-cart-checkout-vendors-frontend' ), array(), true );
		$this->api->register_script( 'wc-cart-checkout-base', $this->api->get_block_asset_build_path( 'wc-cart-checkout-base-frontend' ), array(), true );
		$this->api->register_script( 'wc-blocks-checkout', 'assets/client/blocks/blocks-checkout.js' );
		$this->api->register_script( 'wc-blocks-checkout-events', 'assets/client/blocks/blocks-checkout-events.js' );
		$this->api->register_script( 'wc-blocks-components', 'assets/client/blocks/blocks-components.js' );
		$this->api->register_script( 'wc-schema-parser', 'assets/client/blocks/wc-schema-parser.js', array(), false );

		// Sanitize.
		$this->api->register_script(
			'wc-sanitize',
			'assets/client/admin/sanitize/index.js',
			array()
		);

		// Customer Effort Score.
		$this->api->register_script(
			'wc-customer-effort-score',
			'assets/client/admin/customer-effort-score/index.js',
			array( 'wp-data', 'wp-data-controls', 'wc-store-data' )
		);
		$this->api->register_style(
			'wc-customer-effort-score',
			'assets/client/admin/customer-effort-score/style.css',
		);

		wp_add_inline_script(
			'wc-blocks-middleware',
			"
			var wcBlocksMiddlewareConfig = {
				storeApiNonce: '" . esc_js( wp_create_nonce( 'wc_store_api' ) ) . "',
				wcStoreApiNonceTimestamp: '" . esc_js( time() ) . "'
			};
			",
			'before'
		);
	}

	/**
	 * Register and enqueue assets for exclusive usage within the Site Editor.
	 */
	public function register_and_enqueue_site_editor_assets() {
		// Customer Effort Score.
		wp_enqueue_script( 'wc-customer-effort-score' );
		wp_enqueue_style( 'wc-customer-effort-score' );
	}

	/**
	 * Defines resource hints to help speed up the loading of some critical blocks.
	 *
	 * These will not impact page loading times negatively because they are loaded once the current page is idle.
	 *
	 * @param array  $urls          URLs to print for resource hints. Each URL is an array of resource attributes, or a URL string.
	 * @param string $relation_type The relation type the URLs are printed. Possible values: preconnect, dns-prefetch, prefetch, prerender.
	 * @return array URLs to print for resource hints.
	 */
	public function add_resource_hints( $urls, $relation_type ) {
		if ( ! in_array( $relation_type, array( 'prefetch', 'prerender' ), true ) || is_admin() ) {
			return $urls;
		}

		// We only need to prefetch when the cart has contents.
		$cart = wc()->cart;

		if ( ! $cart instanceof \WC_Cart || 0 === $cart->get_cart_contents_count() ) {
			return $urls;
		}

		if ( 'prefetch' === $relation_type ) {
			$urls = array_merge(
				$urls,
				$this->get_prefetch_resource_hints()
			);
		}

		if ( 'prerender' === $relation_type ) {
			$urls = array_merge(
				$urls,
				$this->get_prerender_resource_hints()
			);
		}

		return $urls;
	}

	/**
	 * Get resource hints during prefetch requests.
	 *
	 * @return array Array of URLs.
	 */
	private function get_prefetch_resource_hints() {
		$urls = array();

		// Core page IDs.
		$cart_page_id     = wc_get_page_id( 'cart' );
		$checkout_page_id = wc_get_page_id( 'checkout' );

		// Checks a specific page (by ID) to see if it contains the named block.
		$has_block_cart     = $cart_page_id && has_block( 'woocommerce/cart', $cart_page_id );
		$has_block_checkout = $checkout_page_id && has_block( 'woocommerce/checkout', $checkout_page_id );

		// Checks the current page to see if it contains the named block.
		$is_block_cart     = has_block( 'woocommerce/cart' );
		$is_block_checkout = has_block( 'woocommerce/checkout' );

		if ( $has_block_cart && ! $is_block_cart ) {
			$urls = array_merge( $urls, $this->get_block_asset_resource_hints( 'cart-frontend' ) );
		}

		if ( $has_block_checkout && ! $is_block_checkout ) {
			$urls = array_merge( $urls, $this->get_block_asset_resource_hints( 'checkout-frontend' ) );
		}

		return $urls;
	}

	/**
	 * Get resource hints during prerender requests.
	 *
	 * @return array Array of URLs.
	 */
	private function get_prerender_resource_hints() {
		$urls          = array();
		$is_block_cart = has_block( 'woocommerce/cart' );

		if ( ! $is_block_cart ) {
			return $urls;
		}

		$checkout_page_id  = wc_get_page_id( 'checkout' );
		$checkout_page_url = $checkout_page_id ? get_permalink( $checkout_page_id ) : '';

		if ( $checkout_page_url ) {
			$urls[] = $checkout_page_url;
		}

		return $urls;
	}

	/**
	 * Get the block asset resource hints in the cache or null if not found.
	 *
	 * @return array|null Array of resource hints.
	 */
	private function get_block_asset_resource_hints_cache() {
		if ( wp_is_development_mode( 'plugin' ) ) {
			return null;
		}

		$cache = get_transient( 'woocommerce_block_asset_resource_hints' );

		$current_version = array(
			'woocommerce' => Constants::get_constant( 'WC_VERSION' ),
			'wordpress'   => get_bloginfo( 'version' ),
			'site_url'    => site_url(),
		);

		if ( isset( $cache['version'] ) && $cache['version'] === $current_version ) {
			return $cache['files'];
		}

		return null;
	}

	/**
	 * Set the block asset resource hints in the cache.
	 *
	 * @param string $filename File name.
	 * @param array  $data Array of resource hints.
	 */
	private function set_block_asset_resource_hints_cache( $filename, $data ) {
		$cache   = $this->get_block_asset_resource_hints_cache();
		$updated = array(
			'files'   => $cache ?? array(),
			'version' => array(
				'woocommerce' => Constants::get_constant( 'WC_VERSION' ),
				'wordpress'   => get_bloginfo( 'version' ),
				'site_url'    => site_url(),
			),
		);

		$updated['files'][ $filename ] = $data;
		set_transient( 'woocommerce_block_asset_resource_hints', $updated, WEEK_IN_SECONDS );
	}

	/**
	 * Get resource hint for a block by name.
	 *
	 * @param string $filename Block filename.
	 * @return array
	 */
	private function get_block_asset_resource_hints( $filename = '' ) {
		if ( ! $filename ) {
			return array();
		}

		$cached = $this->get_block_asset_resource_hints_cache();

		if ( isset( $cached[ $filename ] ) ) {
			return $cached[ $filename ];
		}

		$script_data = $this->api->get_script_data(
			$this->api->get_block_asset_build_path( $filename )
		);
		$resources   = array_merge(
			array( esc_url( add_query_arg( 'ver', $script_data['version'], $script_data['src'] ) ) ),
			$this->get_script_dependency_src_array( $script_data['dependencies'] )
		);

		$data = array_map(
			function ( $src ) {
				return array(
					'href' => $src,
					'as'   => 'script',
				);
			},
			array_unique( array_filter( $resources ) )
		);

		$this->set_block_asset_resource_hints_cache( $filename, $data );

		return $data;
	}

	/**
	 * Get the src of all script dependencies (handles).
	 *
	 * @param array $dependencies Array of dependency handles.
	 * @return string[] Array of src strings.
	 */
	private function get_script_dependency_src_array( array $dependencies ) {
		$wp_scripts = wp_scripts();

		$found_dependencies = array();
		$this->gather_script_dependency_handles( $dependencies, $wp_scripts, $found_dependencies );

		$src = array();
		foreach ( $found_dependencies as $handle => $unused ) {
			$src[] = esc_url( add_query_arg( 'ver', $wp_scripts->registered[ $handle ]->ver, $this->get_absolute_url( $wp_scripts->registered[ $handle ]->src ) ) );
		}
		return $src;
	}

	/**
	 * Recursively gather all unique script dependency handles from a starting list.
	 *
	 * Traverses the dependency graph for each input handle, collecting any found handles
	 * and their nested dependencies in the provided array. Used internally to build a
	 * complete, deduplicated set of handles for further processing (e.g., mapping to src URLs).
	 *
	 * @param array       $dependencies       Array of initial script handles to process.
	 * @param \WP_Scripts $wp_scripts         WP_Scripts instance containing all registered scripts.
	 * @param array       $found_dependencies Reference to array in which discovered handles are stored.
	 *
	 * @return void
	 */
	private function gather_script_dependency_handles( array $dependencies, \WP_Scripts $wp_scripts, &$found_dependencies = array() ) {
		foreach ( $dependencies as $handle ) {
			if ( isset( $wp_scripts->registered[ $handle ] ) && ! isset( $found_dependencies[ $handle ] ) ) {
				$found_dependencies[ $handle ] = true;
				if ( ! empty( $wp_scripts->registered[ $handle ]->deps ) ) {
					$this->gather_script_dependency_handles( $wp_scripts->registered[ $handle ]->deps, $wp_scripts, $found_dependencies );
				}
			}
		}
	}

	/**
	 * Returns an absolute url to relative links for WordPress core scripts.
	 *
	 * @param string $src Original src that can be relative.
	 * @return string Correct full path string.
	 */
	private function get_absolute_url( $src ) {
		$wp_scripts = wp_scripts();
		if ( ! preg_match( '|^(https?:)?//|', $src ) && ! ( $wp_scripts->content_url && 0 === strpos( $src, $wp_scripts->content_url ) ) ) {
			$src = $wp_scripts->base_url . $src;
		}
		return $src;
	}

	/**
	 * Skip Jetpack Boost minification on older versions of Jetpack Boost where it causes issues.
	 *
	 * @param mixed $do_concat Whether to concatenate the script or not.
	 * @param mixed $handle The script handle.
	 * @return mixed
	 */
	public function skip_boost_minification_for_cart_checkout( $do_concat, $handle ) {
		$boost_is_outdated = defined( 'JETPACK_BOOST_VERSION' ) && version_compare( JETPACK_BOOST_VERSION, '3.4.2', '<' );
		$scripts_to_ignore = array(
			'wc-cart-checkout-vendors',
			'wc-cart-checkout-base',
		);

		return $boost_is_outdated && in_array( $handle, $scripts_to_ignore, true ) ? false : $do_concat;
	}

	/**
	 * Add body classes to the frontend and within admin.
	 *
	 * @param string|array $classes Array or string of CSS classnames.
	 * @return string|array Modified classnames.
	 */
	public function add_theme_body_class( $classes ) {
		$class = 'theme-' . get_template();

		if ( is_array( $classes ) ) {
			$classes[] = $class;
		} else {
			$classes .= ' ' . $class . ' ';
		}

		return $classes;
	}

	/**
	 * Get the file modified time as a cache buster if we're in dev mode.
	 *
	 * @param string $file Local path to the file.
	 * @return string The cache buster value to use for the given file.
	 */
	protected function get_file_version( $file ) {
		if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( \Automattic\WooCommerce\Blocks\Package::get_path() . $file ) ) {
			return filemtime( \Automattic\WooCommerce\Blocks\Package::get_path() . $file );
		}
		return $this->api->wc_version;
	}

	/**
	 * Registers a style according to `wp_register_style`.
	 *
	 * @param string  $handle Name of the stylesheet. Should be unique.
	 * @param string  $src    Full URL of the stylesheet, or path of the stylesheet relative to the WordPress root directory.
	 * @param array   $deps   Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
	 * @param string  $media  Optional. The media for which this stylesheet has been defined. Default 'all'. Accepts media types like
	 *                        'all', 'print' and 'screen', or media queries like '(orientation: portrait)' and '(max-width: 640px)'.
	 * @param boolean $rtl   Optional. Whether or not to register RTL styles.
	 */
	protected function register_style( $handle, $src, $deps = array(), $media = 'all', $rtl = false ) {
		$filename = str_replace( plugins_url( '/', dirname( __DIR__ ) ), '', $src );
		$ver      = self::get_file_version( $filename );

		wp_register_style( $handle, $src, $deps, $ver, $media );

		if ( $rtl ) {
			wp_style_add_data( $handle, 'rtl', 'replace' );
		}
	}

	/**
	 * Update block style dependencies after they have been registered.
	 */
	public function update_block_style_dependencies() {
		$wp_styles = wp_styles();
		$style     = $wp_styles->query( 'wc-blocks-style', 'registered' );

		if ( ! $style ) {
			return;
		}

		// In WC < 5.5, `woocommerce-general` is not registered in block editor
		// screens, so we don't add it as a dependency if it's not registered.
		// In WC >= 5.5, `woocommerce-general` is registered on `admin_enqueue_scripts`,
		// so we need to check if it's registered here instead of on `init`.
		if (
			wp_style_is( 'woocommerce-general', 'registered' ) &&
			! in_array( 'woocommerce-general', $style->deps, true )
		) {
			$style->deps[] = 'woocommerce-general';
		}
	}

	/**
	 * Fix scripts with wc-settings dependency.
	 *
	 * The wc-settings script only works correctly when enqueued in the footer. This is to give blocks etc time to
	 * register their settings data before it's printed.
	 *
	 * This code will look at registered scripts, and if they have a wc-settings dependency, force them to print in the
	 * footer instead of the header.
	 *
	 * This only supports packages known to require wc-settings!
	 *
	 * @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5052
	 */
	public function update_block_settings_dependencies() {
		$wp_scripts     = wp_scripts();
		$known_packages = array( 'wc-settings', 'wc-blocks-checkout', 'wc-price-format' );

		foreach ( $wp_scripts->registered as $handle => $script ) {
			// scripts that are loaded in the footer has extra->group = 1.
			if ( array_intersect( $known_packages, $script->deps ) && ! isset( $script->extra['group'] ) ) {
				// Append the script to footer.
				$wp_scripts->add_data( $handle, 'group', 1 );
				// Show a warning.
				$error_handle  = 'wc-settings-dep-in-header';
				$used_deps     = implode( ', ', array_intersect( $known_packages, $script->deps ) );
				$error_message = "Scripts that have a dependency on [$used_deps] must be loaded in the footer, {$handle} was registered to load in the header, but has been switched to load in the footer instead. See https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5059";
				// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter,WordPress.WP.EnqueuedResourceParameters.MissingVersion
				wp_register_script( $error_handle, '' );
				wp_enqueue_script( $error_handle );
				wp_add_inline_script(
					$error_handle,
					sprintf( 'console.warn( "%s" );', $error_message )
				);

			}
		}
	}

	/**
	 * Enqueue the wc-entities script.
	 */
	public function enqueue_wc_entities() {
		wp_enqueue_script( 'wc-entities' );
	}
}
PK     \V!82        AI/Connection.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\AI;

/**
 * Class Connection
 *
 * @internal
 * @deprecated This class can't be removed due https://github.com/woocommerce/woocommerce/issues/52311.
 */
class Connection {}
PK     \v        AI/Configuration.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks\AI;

/**
 * Class Configuration
 *
 * @internal
 * @deprecated This class can't be removed due https://github.com/woocommerce/woocommerce/issues/52311.
 */
class Configuration {}
PK     \,g      Registry/FactoryType.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Registry;

/**
 * Definition for the FactoryType dependency type.
 *
 * @since 2.5.0
 */
class FactoryType extends AbstractDependencyType {
	/**
	 * Invokes and returns the value from the stored internal callback.
	 *
	 * @param Container $container  An instance of the dependency injection
	 *                              container.
	 *
	 * @return mixed
	 */
	public function get( Container $container ) {
		return $this->resolve_value( $container );
	}
}
PK     \|      Registry/Container.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Registry;

use Closure;
use Exception;

/**
 * A simple Dependency Injection Container
 *
 * This is used to manage dependencies used throughout the plugin.
 *
 * @since 2.5.0
 */
class Container {

	/**
	 * A map of Dependency Type objects used to resolve dependencies.
	 *
	 * @var AbstractDependencyType[]
	 */
	private $registry = [];

	/**
	 * Public api for adding a factory to the container.
	 *
	 * Factory dependencies will have the instantiation callback invoked
	 * every time the dependency is requested.
	 *
	 * Typical Usage:
	 *
	 * ```
	 * $container->register( MyClass::class, $container->factory( $mycallback ) );
	 * ```
	 *
	 * @param Closure $instantiation_callback  This will be invoked when the
	 *                                         dependency is required.  It will
	 *                                         receive an instance of this
	 *                                         container so the callback can
	 *                                         retrieve dependencies from the
	 *                                         container.
	 *
	 * @return FactoryType  An instance of the FactoryType dependency.
	 */
	public function factory( Closure $instantiation_callback ) {
		return new FactoryType( $instantiation_callback );
	}

	/**
	 * Interface for registering a new dependency with the container.
	 *
	 * By default, the $value will be added as a shared dependency.  This means
	 * that it will be a single instance shared among any other classes having
	 * that dependency.
	 *
	 * If you want a new instance every time it's required, then wrap the value
	 * in a call to the factory method (@see Container::factory for example)
	 *
	 * Note: Currently if the provided id already is registered in the container,
	 * the provided value is ignored.
	 *
	 * @param string $id    A unique string identifier for the provided value.
	 *                      Typically it's the fully qualified name for the
	 *                      dependency.
	 * @param mixed  $value The value for the dependency. Typically, this is a
	 *                      closure that will create the class instance needed.
	 */
	public function register( $id, $value ) {
		if ( empty( $this->registry[ $id ] ) ) {
			if ( ! $value instanceof FactoryType ) {
				$value = new SharedType( $value );
			}
			$this->registry[ $id ] = $value;
		}
	}

	/**
	 * Interface for retrieving the dependency stored in the container for the
	 * given identifier.
	 *
	 * @param string $id  The identifier for the dependency being retrieved.
	 * @throws Exception  If there is no dependency for the given identifier in
	 *                    the container.
	 *
	 * @return mixed  Typically a class instance.
	 */
	public function get( $id ) {
		if ( ! isset( $this->registry[ $id ] ) ) {
			// this is a developer facing exception, hence it is not localized.
			throw new Exception(
				sprintf(
					'Cannot construct an instance of %s because it has not been registered.',
					$id
				)
			);
		}
		return $this->registry[ $id ]->get( $this );
	}
}
PK     \$      Registry/SharedType.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Registry;

/**
 * A definition for the SharedType dependency type.
 *
 * @since 2.5.0
 */
class SharedType extends AbstractDependencyType {

	/**
	 * Holds a cached instance of the value stored (or returned) internally.
	 *
	 * @var mixed
	 */
	private $shared_instance;

	/**
	 * Returns the internal stored and shared value after initial generation.
	 *
	 * @param Container $container An instance of the dependency injection
	 *                             container.
	 *
	 * @return mixed
	 */
	public function get( Container $container ) {
		if ( empty( $this->shared_instance ) ) {
			$this->shared_instance = $this->resolve_value( $container );
		}
		return $this->shared_instance;
	}
}
PK     \Ǖ    #  Registry/AbstractDependencyType.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Registry;

/**
 * An abstract class for dependency types.
 *
 * Dependency types are instances of a dependency used by the
 * Dependency Injection Container for storing dependencies to invoke as they
 * are needed.
 *
 * @since 2.5.0
 */
abstract class AbstractDependencyType {

	/**
	 * Holds a callable or value provided for this type.
	 *
	 * @var mixed
	 */
	private $callable_or_value;

	/**
	 * Constructor
	 *
	 * @param mixed $callable_or_value  A callable or value for the dependency
	 *                                  type instance.
	 */
	public function __construct( $callable_or_value ) {
		$this->callable_or_value = $callable_or_value;
	}

	/**
	 * Resolver for the internal dependency value.
	 *
	 * @param Container $container  The Dependency Injection Container.
	 *
	 * @return mixed
	 */
	protected function resolve_value( Container $container ) {
		$callback = $this->callable_or_value;
		return \is_callable( $callback )
			? $callback( $container )
			: $callback;
	}

	/**
	 * Retrieves the value stored internally for this DependencyType
	 *
	 * @param Container $container  The Dependency Injection Container.
	 *
	 * @return void
	 */
	abstract public function get( Container $container );
}
PK     \>  >    Library.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks;

use Automattic\WooCommerce\Blocks\BlockTypes\AtomicBlock;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;

/**
 * Library class.
 *
 * @deprecated 5.0.0 This class will be removed in a future release. This has been replaced by BlockTypesController.
 * @internal
 */
class Library {

	/**
	 * Initialize block library features.
	 *
	 * @deprecated 5.0.0
	 */
	public static function init() {
		_deprecated_function( 'Library::init', '5.0.0' );
	}

	/**
	 * Register custom tables within $wpdb object.
	 *
	 * @deprecated 5.0.0
	 */
	public static function define_tables() {
		_deprecated_function( 'Library::define_tables', '5.0.0' );
	}

	/**
	 * Register blocks, hooking up assets and render functions as needed.
	 *
	 * @deprecated 5.0.0
	 */
	public static function register_blocks() {
		_deprecated_function( 'Library::register_blocks', '5.0.0' );
	}
}
PK     \<ߣs  s  %  Integrations/IntegrationInterface.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Integrations;

/**
 * Integration.Interface
 *
 * Integrations must use this interface when registering themselves with blocks,
 */
interface IntegrationInterface {
	/**
	 * The name of the integration.
	 *
	 * @return string
	 */
	public function get_name();

	/**
	 * When called invokes any initialization/setup for the integration.
	 */
	public function initialize();

	/**
	 * Returns an array of script handles to enqueue in the frontend context.
	 *
	 * @return string[]
	 */
	public function get_script_handles();

	/**
	 * Returns an array of script handles to enqueue in the editor context.
	 *
	 * @return string[]
	 */
	public function get_editor_script_handles();

	/**
	 * An array of key, value pairs of data made available to the block on the client side.
	 *
	 * @return array
	 */
	public function get_script_data();
}
PK     \.[J  J  $  Integrations/IntegrationRegistry.phpnu [        <?php
namespace Automattic\WooCommerce\Blocks\Integrations;

/**
 * Class used for tracking registered integrations with various Block types.
 */
class IntegrationRegistry {
	/**
	 * Integration identifier is used to construct hook names and is given when the integration registry is initialized.
	 *
	 * @var string
	 */
	protected $registry_identifier = '';

	/**
	 * Registered integrations, as `$name => $instance` pairs.
	 *
	 * @var IntegrationInterface[]
	 */
	protected $registered_integrations = [];

	/**
	 * Initializes all registered integrations.
	 *
	 * Integration identifier is used to construct hook names and is given when the integration registry is initialized.
	 *
	 * @param string $registry_identifier Identifier for this registry.
	 */
	public function initialize( $registry_identifier = '' ) {
		if ( $registry_identifier ) {
			$this->registry_identifier = $registry_identifier;
		}

		if ( empty( $this->registry_identifier ) ) {
			_doing_it_wrong( __METHOD__, esc_html__( 'Integration registry requires an identifier.', 'woocommerce' ), '4.6.0' );
			return false;
		}

		/**
		 * Fires when the IntegrationRegistry is initialized.
		 *
		 * Runs before integrations are initialized allowing new integration to be registered for use. This should be
		 * used as the primary hook for integrations to include their scripts, styles, and other code extending the
		 * blocks.
		 *
		 * @since 4.6.0
		 *
		 * @param IntegrationRegistry $this Instance of the IntegrationRegistry class which exposes the IntegrationRegistry::register() method.
		 */
		do_action( 'woocommerce_blocks_' . $this->registry_identifier . '_registration', $this );

		foreach ( $this->get_all_registered() as $registered_integration ) {
			$registered_integration->initialize();
		}
	}

	/**
	 * Registers an integration.
	 *
	 * @param IntegrationInterface $integration An instance of IntegrationInterface.
	 *
	 * @return boolean True means registered successfully.
	 */
	public function register( IntegrationInterface $integration ) {
		$name = $integration->get_name();

		if ( $this->is_registered( $name ) ) {
			/* translators: %s: Integration name. */
			_doing_it_wrong( __METHOD__, esc_html( sprintf( __( '"%s" is already registered.', 'woocommerce' ), $name ) ), '4.6.0' );
			return false;
		}

		$this->registered_integrations[ $name ] = $integration;
		return true;
	}

	/**
	 * Checks if an integration is already registered.
	 *
	 * @param string $name Integration name.
	 * @return bool True if the integration is registered, false otherwise.
	 */
	public function is_registered( $name ) {
		return isset( $this->registered_integrations[ $name ] );
	}

	/**
	 * Un-register an integration.
	 *
	 * @param string|IntegrationInterface $name Integration name, or alternatively a IntegrationInterface instance.
	 * @return boolean|IntegrationInterface Returns the unregistered integration instance if unregistered successfully.
	 */
	public function unregister( $name ) {
		if ( $name instanceof IntegrationInterface ) {
			$name = $name->get_name();
		}

		if ( ! $this->is_registered( $name ) ) {
			/* translators: %s: Integration name. */
			_doing_it_wrong( __METHOD__, esc_html( sprintf( __( 'Integration "%s" is not registered.', 'woocommerce' ), $name ) ), '4.6.0' );
			return false;
		}

		$unregistered = $this->registered_integrations[ $name ];
		unset( $this->registered_integrations[ $name ] );
		return $unregistered;
	}

	/**
	 * Retrieves a registered Integration by name.
	 *
	 * @param string $name Integration name.
	 * @return IntegrationInterface|null The registered integration, or null if it is not registered.
	 */
	public function get_registered( $name ) {
		return $this->is_registered( $name ) ? $this->registered_integrations[ $name ] : null;
	}

	/**
	 * Retrieves all registered integrations.
	 *
	 * @return IntegrationInterface[]
	 */
	public function get_all_registered() {
		return $this->registered_integrations;
	}

	/**
	 * Gets an array of all registered integration's script handles for the editor.
	 *
	 * @return string[]
	 */
	public function get_all_registered_editor_script_handles() {
		$script_handles          = [];
		$registered_integrations = $this->get_all_registered();

		foreach ( $registered_integrations as $registered_integration ) {
			$script_handles = array_merge(
				$script_handles,
				$registered_integration->get_editor_script_handles()
			);
		}

		return array_unique( array_filter( $script_handles ) );
	}

	/**
	 * Gets an array of all registered integration's script handles.
	 *
	 * @return string[]
	 */
	public function get_all_registered_script_handles() {
		$script_handles          = [];
		$registered_integrations = $this->get_all_registered();

		foreach ( $registered_integrations as $registered_integration ) {
			$script_handles = array_merge(
				$script_handles,
				$registered_integration->get_script_handles()
			);
		}

		return array_unique( array_filter( $script_handles ) );
	}

	/**
	 * Gets an array of all registered integration's script data.
	 *
	 * @return array
	 */
	public function get_all_registered_script_data() {
		$script_data             = [];
		$registered_integrations = $this->get_all_registered();

		foreach ( $registered_integrations as $registered_integration ) {
			$script_data[ $registered_integration->get_name() . '_data' ] = $registered_integration->get_script_data();
		}

		return array_filter( $script_data );
	}
}
PK     \Wy
  
    Package.phpnu [        <?php

namespace Automattic\WooCommerce\Blocks;

use Automattic\WooCommerce\Blocks\Domain\Package as NewPackage;
use Automattic\WooCommerce\Blocks\Domain\Bootstrap;
use Automattic\WooCommerce\Blocks\Registry\Container;
use Automattic\WooCommerce\Blocks\Domain\Services\FeatureGating;

/**
 * Main package class.
 *
 * Returns information about the package and handles init.
 *
 * In the context of this plugin, it handles init and is called from the main
 * plugin file (woocommerce-gutenberg-products-block.php).
 *
 * In the context of WooCommerce core, it handles init and is called from
 * WooCommerce's package loader. The main plugin file is _not_ loaded.
 *
 * @since 2.5.0
 */
class Package {


	/**
	 * For back compat this is provided. Ideally, you should register your
	 * class with Automattic\Woocommerce\Blocks\Container and make Package a
	 * dependency.
	 *
	 * @since 2.5.0
	 * @return Package  The Package instance class
	 */
	protected static function get_package() {
		return self::container()->get( NewPackage::class );
	}

	/**
	 * Init the package - load the blocks library and define constants.
	 *
	 * @since 2.5.0 Handled by new NewPackage.
	 */
	public static function init() {
		self::container()->get( Bootstrap::class );
	}

	/**
	 * Return the version of the package.
	 *
	 * @return string
	 */
	public static function get_version() {
		return self::get_package()->get_version();
	}

	/**
	 * Return the path to the package.
	 *
	 * @return string
	 */
	public static function get_path() {
		return self::get_package()->get_path();
	}

	/**
	 * Returns an instance of the FeatureGating class.
	 *
	 * @return FeatureGating
	 * @deprecated since 9.6, use wp_get_environment_type() instead.
	 */
	public static function feature() {
		wc_deprecated_function( 'Package::feature', '9.6', 'wp_get_environment_type' );
		return new FeatureGating();
	}

	/**
	 * Loads the dependency injection container for woocommerce blocks.
	 *
	 * @param boolean $reset Used to reset the container to a fresh instance.
	 *                       Note: this means all dependencies will be
	 *                       reconstructed.
	 */
	public static function container( $reset = false ) {
		static $container;
		if (
			! $container instanceof Container
			|| $reset
		) {
			$container = new Container();
			// register Package.
			$container->register(
				NewPackage::class,
				function ( $container ) {
					// leave for automated version bumping.
					$version = '11.8.0-dev';
					return new NewPackage(
						$version,
						dirname( __DIR__, 2 )
					);
				}
			);
			// register Bootstrap.
			$container->register(
				Bootstrap::class,
				function ( $container ) {
					return new Bootstrap(
						$container
					);
				}
			);
		}
		return $container;
	}
}
PK     \Y6ԯ      SharedStores/ProductsStore.phpnu [        <?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\SharedStores;

use Automattic\WooCommerce\Blocks\Domain\Services\Hydration;
use Automattic\WooCommerce\Blocks\Package;
use InvalidArgumentException;

/**
 * Manages the registration of interactivity state that provides product data
 * to interactive blocks. This is shared store data that is not tied to one
 * specific block.
 *
 * This is an experimental API and may change in future versions.
 */
class ProductsStore {

	/**
	 * The consent statement for using this experimental API.
	 *
	 * @var string
	 */
	private static string $consent_statement = 'I acknowledge that using experimental APIs means my theme or plugin will inevitably break in the next version of WooCommerce';

	/**
	 * The namespace for the store.
	 *
	 * @var string
	 */
	private static string $store_namespace = 'woocommerce/products';

	/**
	 * Products that have been loaded into state.
	 *
	 * @var array
	 */
	private static array $products = array();

	/**
	 * Product variations that have been loaded into state.
	 *
	 * @var array
	 */
	private static array $product_variations = array();

	/**
	 * Check that the consent statement was passed.
	 *
	 * @param string $consent_statement The consent statement string.
	 * @return true
	 * @throws InvalidArgumentException If the statement does not match.
	 */
	private static function check_consent( string $consent_statement ): bool {
		if ( $consent_statement !== self::$consent_statement ) {
			throw new InvalidArgumentException( 'This method cannot be called without consenting that the API may change.' );
		}

		return true;
	}

	/**
	 * Register the interactivity state if products have been loaded.
	 *
	 * @return void
	 */
	private static function register_state(): void {
		$state = array();

		if ( ! empty( self::$products ) ) {
			$state['products'] = self::$products;
		}

		if ( ! empty( self::$product_variations ) ) {
			$state['productVariations'] = self::$product_variations;
		}

		if ( ! empty( $state ) ) {
			wp_interactivity_state( self::$store_namespace, $state );
		}
	}

	/**
	 * Load a product into state.
	 *
	 * @param string $consent_statement The consent statement string.
	 * @param int    $product_id        The product ID.
	 * @return array The product data.
	 * @throws InvalidArgumentException If consent statement doesn't match.
	 */
	public static function load_product( string $consent_statement, int $product_id ): array {
		self::check_consent( $consent_statement );

		// Skip loading if product is already in state.
		if ( isset( self::$products[ $product_id ] ) ) {
			return self::$products[ $product_id ];
		}

		$response = Package::container()->get( Hydration::class )->get_rest_api_response_data( '/wc/store/v1/products/' . $product_id );

		self::$products[ $product_id ] = $response['body'] ?? array();
		self::register_state();

		return self::$products[ $product_id ];
	}

	/**
	 * Load all purchasable child products of a parent product into state.
	 *
	 * @param string $consent_statement The consent statement string.
	 * @param int    $parent_id         The parent product ID.
	 * @return array The purchasable child products keyed by ID.
	 * @throws InvalidArgumentException If consent statement doesn't match.
	 */
	public static function load_purchasable_child_products( string $consent_statement, int $parent_id ): array {
		self::check_consent( $consent_statement );

		// Get the parent product to retrieve child IDs.
		$parent_product = wc_get_product( $parent_id );
		if ( ! $parent_product ) {
			return array();
		}

		// Get child product IDs (for grouped products, these are linked products).
		$child_ids = $parent_product->get_children();
		if ( empty( $child_ids ) ) {
			return array();
		}

		// Query child products using include[] filter.
		// The parent[] filter doesn't work for grouped products because
		// their children are standalone products, not variations.
		$include_params = array_map(
			fn( $id ) => 'include[]=' . $id,
			$child_ids
		);
		$query_string   = implode( '&', $include_params );

		$response = Package::container()->get( Hydration::class )->get_rest_api_response_data( '/wc/store/v1/products?' . $query_string );

		if ( empty( $response['body'] ) ) {
			return array();
		}

		// Filter to only purchasable products.
		$purchasable_products = array_filter(
			$response['body'],
			fn( $product ) => $product['is_purchasable']
		);

		// Re-key array by product ID and merge into state.
		// Use array_replace instead of array_merge to preserve numeric keys.
		$keyed_products = array_column( $purchasable_products, null, 'id' );
		self::$products = array_replace( self::$products, $keyed_products );
		self::register_state();

		return $keyed_products;
	}

	/**
	 * Load all variations of a variable product into state.
	 *
	 * @param string $consent_statement The consent statement string.
	 * @param int    $parent_id         The parent product ID.
	 * @return array The variations keyed by ID.
	 * @throws InvalidArgumentException If consent statement doesn't match.
	 */
	public static function load_variations( string $consent_statement, int $parent_id ): array {
		self::check_consent( $consent_statement );

		$response = Package::container()->get( Hydration::class )->get_rest_api_response_data( '/wc/store/v1/products?parent[]=' . $parent_id . '&type=variation' );

		if ( empty( $response['body'] ) ) {
			return array();
		}

		// Re-key array by variation ID and merge into state.
		// Use array_replace instead of array_merge to preserve numeric keys.
		$keyed_variations         = array_column( $response['body'], null, 'id' );
		self::$product_variations = array_replace( self::$product_variations, $keyed_variations );
		self::register_state();

		return $keyed_variations;
	}
}
PK       \AOG  G                  BlockTemplatesController.phpnu [        PK       \8;P  P              7H  Options.phpnu [        PK       \ح                I  BlockPatterns.phpnu [        PK       \Y
  
  '            i  Payments/PaymentMethodTypeInterface.phpnu [        PK       \@nf  f  "            n  Payments/PaymentMethodRegistry.phpnu [        PK       \lf#m                u  Payments/Api.phpnu [        PK       \}4f  f                 Payments/Integrations/PayPal.phpnu [        PK       \kNk    3            Y  Payments/Integrations/AbstractPaymentMethodType.phpnu [        PK       \7Y
  
  (            ѭ  Payments/Integrations/CashOnDelivery.phpnu [        PK       \ +d                 G  Payments/Integrations/Cheque.phpnu [        PK       \~    &              Payments/Integrations/BankTransfer.phpnu [        PK       \b|x  x                Utils/BlockTemplateUtils.phpnu [        PK       \dcE  cE              ? Utils/CartCheckoutUtils.phpnu [        PK       \kU  U              u Utils/BlocksWpQuery.phpnu [        PK       \}$                 Utils/ProductGalleryUtils.phpnu [        PK       \дB                Z Utils/BlockHooksTrait.phpnu [        PK       \'9o}\  }\              6 Utils/StyleAttributesUtils.phpnu [        PK       \5
  
               Utils/MiniCartUtils.phpnu [        PK       \P    "            R, Utils/ProductAvailabilityUtils.phpnu [        PK       \(N!                1 Utils/ProductDataUtils.phpnu [        PK       \:T                3 Utils/Utils.phpnu [        PK       \n  n              8 Utils/BlocksSharedState.phpnu [        PK       \_`0  0              T BlockTemplatesRegistry.phpnu [        PK       \ILYeG  G              
n InboxNotifications.phpnu [        PK       \s                  p Patterns/AIPatterns.phpnu [        PK       \[_  _              q Patterns/PTKClient.phpnu [        PK       \|                e Patterns/PatternRegistry.phpnu [        PK       \{/{"  {"               Patterns/PTKPatternsStore.phpnu [        PK       \3{~dQ  dQ              k Shipping/ShippingController.phpnu [        PK       \LV  V              	 Shipping/PickupLocation.phpnu [        PK       \ۡT  T               Installer.phpnu [        PK       \ʫ                  P, Images/Pexels.phpnu [        PK       \E"                  o- AIContent/UpdatePatterns.phpnu [        PK       \ n"  "              . AIContent/UpdateProducts.phpnu [        PK       \ꒁ                Q AIContent/ContentProcessor.phpnu [        PK       \냹                 R AIContent/PatternsDictionary.phpnu [        PK       \y1;                  YT AIContent/PatternsHelper.phpnu [        PK       \)J1  1              U Assets/Api.phpnu [        PK       \t77  77               Assets/AssetDataRegistry.phpnu [        PK       \WJg*  *              l DependencyDetection.phpnu [        PK       \"w                V TemplateOptions.phpnu [        PK       \5R  R  #            l BlockTypes/ProductAverageRating.phpnu [        PK       \Q	Ϛ                 BlockTypes/ProductTag.phpnu [        PK       \2Վ3  3               F  BlockTypes/ProductCategories.phpnu [        PK       \%TKE  E              3 BlockTypes/MiniCart.phpnu [        PK       \G6o  6o              % BlockTypes/ProductQuery.phpnu [        PK       \*m  m  '            / BlockTypes/ProductFilterPriceSlider.phpnu [        PK       \6sx  x              jE BlockTypes/FilterWrapper.phpnu [        PK       \U'  '  *            .G BlockTypes/ProductFilterRemovableChips.phpnu [        PK       \3[    '            V BlockTypes/ProductGalleryLargeImage.phpnu [        PK       \2    '            w BlockTypes/Accordion/AccordionPanel.phpnu [        PK       \ib	  	  &            y BlockTypes/Accordion/AccordionItem.phpnu [        PK       \D,    '            Z BlockTypes/Accordion/AccordionGroup.phpnu [        PK       \\S    (            < BlockTypes/Accordion/AccordionHeader.phpnu [        PK       \4B1  1              H BlockTypes/ProductButton.phpnu [        PK       \J    !            q BlockTypes/ProductFilterChips.phpnu [        PK       \Cwf                 BlockTypes/ProductTopRated.phpnu [        PK       \~y03  03  )            y BlockTypes/MiniCartProductsTableBlock.phpnu [        PK       \Eb'  '               BlockTypes/ProductFilters.phpnu [        PK       \                . BlockTypes/AtomicBlock.phpnu [        PK       \Ojc[                1 BlockTypes/EmailContent.phpnu [        PK       \ip                ? BlockTypes/ProductCategory.phpnu [        PK       \ě<    *            EB BlockTypes/MiniCartCheckoutButtonBlock.phpnu [        PK       \bN  N              J BlockTypes/FeaturedProduct.phpnu [        PK       \-  -  2            X BlockTypes/CheckoutOrderSummaryCouponFormBlock.phpnu [        PK       \_v
    *            Y BlockTypes/CheckoutBillingAddressBlock.phpnu [        PK       \d                [ BlockTypes/PriceFilter.phpnu [        PK       \䢱P&  &  0            \` BlockTypes/CheckoutOrderSummarySubtotalBlock.phpnu [        PK       \	      .            a BlockTypes/CheckoutOrderSummaryTotalsBlock.phpnu [        PK       \YE                `c BlockTypes/SingleProduct.phpnu [        PK       \                @ BlockTypes/ProductPrice.phpnu [        PK       \Gg#  #              o BlockTypes/ProductImage.phpnu [        PK       \    *            Ƕ BlockTypes/MiniCartShoppingButtonBlock.phpnu [        PK       \!ӧD4  D4              : BlockTypes/Cart.phpnu [        PK       \<n  n  "             BlockTypes/NextPreviousButtons.phpnu [        PK       \ k<?  ?               BlockTypes/ProductSKU.phpnu [        PK       \8h    ,            	 BlockTypes/CartOrderSummarySubtotalBlock.phpnu [        PK       \%	5    $             BlockTypes/ProductSpecifications.phpnu [        PK       \H    *            ' BlockTypes/CheckoutShippingMethodBlock.phpnu [        PK       \                  ( BlockTypes/CartItemsBlock.phpnu [        PK       \s    6            1* BlockTypes/AddToCartWithOptions/GroupedProductItem.phpnu [        PK       \#ᣃ    ;            _8 BlockTypes/AddToCartWithOptions/GroupedProductItemLabel.phpnu [        PK       \+ %   %  E            n? BlockTypes/AddToCartWithOptions/VariationSelectorAttributeOptions.phpnu [        PK       \qJ    B            d BlockTypes/AddToCartWithOptions/VariationSelectorAttributeName.phpnu [        PK       \	    8            nl BlockTypes/AddToCartWithOptions/VariationDescription.phpnu [        PK       \N;x  x  5            t BlockTypes/AddToCartWithOptions/VariationSelector.phpnu [        PK       \    >            z BlockTypes/AddToCartWithOptions/GroupedProductItemSelector.phpnu [        PK       \"!  !  )             BlockTypes/AddToCartWithOptions/Utils.phpnu [        PK       \g;    :            $ BlockTypes/AddToCartWithOptions/GroupedProductSelector.phpnu [        PK       \Ob  Ob  8             BlockTypes/AddToCartWithOptions/AddToCartWithOptions.phpnu [        PK       \NO     4            J  BlockTypes/AddToCartWithOptions/QuantitySelector.phpnu [        PK       \>*U  U  >            ?9 BlockTypes/AddToCartWithOptions/VariationSelectorAttribute.phpnu [        PK       \v                O BlockTypes/ProductSearch.phpnu [        PK       \4w&  &  0            ?] BlockTypes/CheckoutOrderSummaryDiscountBlock.phpnu [        PK       \                ^ BlockTypes/AllProducts.phpnu [        PK       \V$7f  f  "            g BlockTypes/ProductFilterRating.phpnu [        PK       \S;  ;                BlockTypes/ReviewsByCategory.phpnu [        PK       \ <8  8              J BlockTypes/ReviewsByProduct.phpnu [        PK       \;?*  *  1            ю BlockTypes/CheckoutOrderSummaryCartItemsBlock.phpnu [        PK       \o~G    +            \ BlockTypes/CheckoutShippingAddressBlock.phpnu [        PK       \٥v    -            ͑ BlockTypes/ProductCollection/QueryBuilder.phpnu [        PK       \sK    *            1	 BlockTypes/ProductCollection/NoResults.phpnu [        PK       \ݹ:  :  +            B	 BlockTypes/ProductCollection/Controller.phpnu [        PK       \fAC  C  0            $~	 BlockTypes/ProductCollection/HandlerRegistry.phpnu [        PK       \1oN3  3  )            	 BlockTypes/ProductCollection/Renderer.phpnu [        PK       \~l$  $  &            	 BlockTypes/ProductCollection/Utils.phpnu [        PK       \?P(  (  1            J
 BlockTypes/CheckoutAdditionalInformationBlock.phpnu [        PK       \sv                
 BlockTypes/ProductMeta.phpnu [        PK       \x\%  %              
 BlockTypes/AllReviews.phpnu [        PK       \uV  V              |
 BlockTypes/ComingSoon.phpnu [        PK       \3{    *            '
 BlockTypes/CartCrossSellsProductsBlock.phpnu [        PK       \    !            (
 BlockTypes/ProductFilterPrice.phpnu [        PK       \&    '            D
 BlockTypes/ProductFilterClearButton.phpnu [        PK       \OE    !            J
 BlockTypes/ProductBestSellers.phpnu [        PK       \i,<~  ~  "            L
 BlockTypes/ProductFilterStatus.phpnu [        PK       \                  g
 BlockTypes/EmptyCartBlock.phpnu [        PK       \m9W#@  #@              h
 BlockTypes/ProductDetails.phpnu [        PK       \{y    +            e
 BlockTypes/CheckoutOrderSummaryFeeBlock.phpnu [        PK       \    !            ת
 BlockTypes/MiniCartItemsBlock.phpnu [        PK       \^A<|  |              4
 BlockTypes/MiniCartContents.phpnu [        PK       \sCI  I  $            
 BlockTypes/ProductFilterTaxonomy.phpnu [        PK       \e                2 BlockTypes/CatalogSorting.phpnu [        PK       \"    !            m BlockTypes/PageContentWrapper.phpnu [        PK       \dо      "             BlockTypes/CartCrossSellsBlock.phpnu [        PK       \l      "              BlockTypes/CheckoutFieldsBlock.phpnu [        PK       \T9                " BlockTypes/RatingFilter.phpnu [        PK       \]*  *  )            #% BlockTypes/EmptyMiniCartContentsBlock.phpnu [        PK       \P{Q&  &  .            , BlockTypes/OrderConfirmation/CreateAccount.phpnu [        PK       \([&  &  '            S BlockTypes/OrderConfirmation/Status.phpnu [        PK       \O.  .  0            z BlockTypes/OrderConfirmation/ShippingAddress.phpnu [        PK       \VN    /            R BlockTypes/OrderConfirmation/BillingAddress.phpnu [        PK       \g.N    6             BlockTypes/OrderConfirmation/AdditionalInformation.phpnu [        PK       \!m$  $  '             BlockTypes/OrderConfirmation/Totals.phpnu [        PK       \&  &  ?             BlockTypes/OrderConfirmation/AbstractOrderConfirmationBlock.phpnu [        PK       \5~  ~  *            [ BlockTypes/OrderConfirmation/Downloads.phpnu [        PK       \    .            3 BlockTypes/OrderConfirmation/TotalsWrapper.phpnu [        PK       \i膐    8            ! BlockTypes/OrderConfirmation/AdditionalFieldsWrapper.phpnu [        PK       \    0             BlockTypes/OrderConfirmation/ShippingWrapper.phpnu [        PK       \TS    /            g BlockTypes/OrderConfirmation/BillingWrapper.phpnu [        PK       \J    1            
 BlockTypes/OrderConfirmation/DownloadsWrapper.phpnu [        PK       \2Ł    (             BlockTypes/OrderConfirmation/Summary.phpnu [        PK       \~V  V  1             BlockTypes/OrderConfirmation/AdditionalFields.phpnu [        PK       \X
    #            K% BlockTypes/ProductRatingCounter.phpnu [        PK       \[&\2  2  %            w; BlockTypes/ProductFilterAttribute.phpnu [        PK       \    $            n BlockTypes/ProductStockIndicator.phpnu [        PK       \fa    '             BlockTypes/ProductGalleryThumbnails.phpnu [        PK       \    )             BlockTypes/CartOrderSummaryTaxesBlock.phpnu [        PK       \K    +            ` BlockTypes/CartOrderSummaryHeadingBlock.phpnu [        PK       \kQF    !            Қ BlockTypes/MiniCartTitleBlock.phpnu [        PK       \ݻk  k              ! BlockTypes/ProductTitle.phpnu [        PK       \rQj
  
  $            ף BlockTypes/CartOrderSummaryBlock.phpnu [        PK       \H    .            ۮ BlockTypes/CheckoutContactInformationBlock.phpnu [        PK       \HPZ!  !              X BlockTypes/ClassicShortcode.phpnu [        PK       \Wd    )            ȼ BlockTypes/CheckoutPickupOptionsBlock.phpnu [        PK       \MCE    "            1 BlockTypes/CategoryDescription.phpnu [        PK       \?p#%  #%              R BlockTypes/AddToCartForm.phpnu [        PK       \.L    !             BlockTypes/ProductRatingStars.phpnu [        PK       \׏i
  
              ! BlockTypes/ProductSaleBadge.phpnu [        PK       \r8  8  "            1 BlockTypes/ProductsByAttribute.phpnu [        PK       \lB+    -             BlockTypes/CheckoutOrderSummaryTaxesBlock.phpnu [        PK       \4)!  !  .            5 BlockTypes/CartOrderSummaryCouponFormBlock.phpnu [        PK       \V  V  ,             BlockTypes/Reviews/ProductReviewTemplate.phpnu [        PK       \z'
  
  7            f! BlockTypes/Reviews/ProductReviewsPaginationPrevious.phpnu [        PK       \DdNy  y  *            n, BlockTypes/Reviews/ProductReviewsTitle.phpnu [        PK       \ס2    %            A: BlockTypes/Reviews/ProductReviews.phpnu [        PK       \5    .            ?C BlockTypes/Reviews/ProductReviewAuthorName.phpnu [        PK       \fB  B  3            PL BlockTypes/Reviews/ProductReviewsPaginationNext.phpnu [        PK       \f3
  3
  +            W BlockTypes/Reviews/ProductReviewContent.phpnu [        PK       \/",  ,  6            b BlockTypes/Reviews/ProductReviewsPaginationNumbers.phpnu [        PK       \"#  #  (            j BlockTypes/Reviews/ProductReviewForm.phpnu [        PK       \s    (            y BlockTypes/Reviews/ProductReviewDate.phpnu [        PK       \3o˨    *             BlockTypes/Reviews/ProductReviewRating.phpnu [        PK       \v2  2  /            ל BlockTypes/Reviews/ProductReviewsPagination.phpnu [        PK       \aH9  9              h BlockTypes/CustomerAccount.phpnu [        PK       \֍l	  l	              v BlockTypes/CategoryTitle.phpnu [        PK       \k`  `              . BlockTypes/ProductRating.phpnu [        PK       \6                 BlockTypes/CouponCode.phpnu [        PK       \w    &              BlockTypes/MiniCartCartButtonBlock.phpnu [        PK       \    !            T) BlockTypes/AbstractInnerBlock.phpnu [        PK       \~    '            0 BlockTypes/CartOrderSummaryFeeBlock.phpnu [        PK       \H&O]                 2 BlockTypes/ProductGallery.phpnu [        PK       \чn    *            BL BlockTypes/FilledMiniCartContentsBlock.phpnu [        PK       \=.    (            fZ BlockTypes/ProductFilterCheckboxList.phpnu [        PK       \    !            Tq BlockTypes/PaymentMethodIcons.phpnu [        PK       \                  T BlockTypes/ActiveFilters.phpnu [        PK       \}3k>  k>               BlockTypes/AbstractBlock.phpnu [        PK       \6  6  "            9 BlockTypes/ProductImageGallery.phpnu [        PK       \1dH1(  (               BlockTypes/ProductTemplate.phpnu [        PK       \v    #            7 BlockTypes/CheckoutActionsBlock.phpnu [        PK       \+F                u BlockTypes/ProductNew.phpnu [        PK       \I    "            ~ BlockTypes/MiniCartFooterBlock.phpnu [        PK       \w
    *            R BlockTypes/CheckoutExpressPaymentBlock.phpnu [        PK       \&L    &            4 BlockTypes/CartExpressPaymentBlock.phpnu [        PK       \E24  4                BlockTypes/CartLink.phpnu [        PK       \{2  {2               ' BlockTypes/ClassicTemplate.phpnu [        PK       \	  	  -            Y BlockTypes/MiniCartTitleItemsCounterBlock.phpnu [        PK       \={                  Ac BlockTypes/AttributeFilter.phpnu [        PK       \yS                i BlockTypes/StoreNotices.phpnu [        PK       \[J    ,            p BlockTypes/CartOrderSummaryDiscountBlock.phpnu [        PK       \VB)	  	  !            r BlockTypes/ProductDescription.phpnu [        PK       \!Ϛr      !            \| BlockTypes/CartLineItemsBlock.phpnu [        PK       \d\|      "            } BlockTypes/CheckoutTotalsBlock.phpnu [        PK       \t&  &  0            ~ BlockTypes/CheckoutOrderSummaryShippingBlock.phpnu [        PK       \Izl
  
              w BlockTypes/Breadcrumbs.phpnu [        PK       \I    *             BlockTypes/CartOrderSummaryTotalsBlock.phpnu [        PK       \;g      #             BlockTypes/CheckoutPaymentBlock.phpnu [        PK       \ /ضZ  Z              H BlockTypes/StockFilter.phpnu [        PK       \!C
  
  "             BlockTypes/ProductFilterActive.phpnu [        PK       \Qȩ                E BlockTypes/RelatedProducts.phpnu [        PK       \k.j  j              < BlockTypes/Checkout.phpnu [        PK       \mT}    !            E BlockTypes/HandpickedProducts.phpnu [        PK       \t      !            && BlockTypes/CheckoutTermsBlock.phpnu [        PK       \.                n' BlockTypes/ProductOnSale.phpnu [        PK       \bHM    "            * BlockTypes/ProductResultsCount.phpnu [        PK       \7-ۮd  d  #            2 BlockTypes/AbstractDynamicBlock.phpnu [        PK       \!nX7  7              9 BlockTypes/FeaturedItem.phpnu [        PK       \~E    )            q BlockTypes/EnableBlockJsonAssetsTrait.phpnu [        PK       \gR
  
              t BlockTypes/FeaturedCategory.phpnu [        PK       \TR	    %            + BlockTypes/ProceedToCheckoutBlock.phpnu [        PK       \9*3  3  &             BlockTypes/MiniCartTitleLabelBlock.phpnu [        PK       \r:    ,             BlockTypes/CartOrderSummaryShippingBlock.phpnu [        PK       \'7ќ    +             BlockTypes/CheckoutShippingMethodsBlock.phpnu [        PK       \-~
  
  (             BlockTypes/CheckoutOrderSummaryBlock.phpnu [        PK       \d                  N BlockTypes/CartTotalsBlock.phpnu [        PK       \F                   BlockTypes/FilledCartBlock.phpnu [        PK       \[Cs    %            ƚ BlockTypes/CheckoutOrderNoteBlock.phpnu [        PK       \PtX  X               BlockTypes/ProductSummary.phpnu [        PK       \&2      .            Ĺ BlockTypes/CartAcceptedPaymentMethodsBlock.phpnu [        PK       \GaX  X  "            B BlockTypes/AbstractProductGrid.phpnu [        PK       \CO    
             Assets.phpnu [        PK       \yu d   d              p BlockTypesController.phpnu [        PK       \ry3G  3G              ؃ QueryFilters.phpnu [        PK       \mz    !            K Domain/Services/CreateAccount.phpnu [        PK       \]     3             Domain/Services/CheckoutFieldsSchema/Validation.phpnu [        PK       \Ws  s  >            l Domain/Services/CheckoutFieldsSchema/json-schema-draft-07.jsonnu [        PK       \ʱ8    7            M Domain/Services/CheckoutFieldsSchema/DocumentObject.phpnu [        PK       \ӯZ"  "  B            > Domain/Services/CheckoutFieldsSchema/checkout-document-schema.jsonnu [        PK       \.!  !              3 Domain/Services/DraftOrders.phpnu [        PK       \2%u                 U Domain/Services/CheckoutLink.phpnu [        PK       \ЃBKf%  f%              l Domain/Services/Hydration.phpnu [        PK       \_,  ,  '            _ Domain/Services/CheckoutFieldsAdmin.phpnu [        PK       \U/ya  a  #             Domain/Services/GoogleAnalytics.phpnu [        PK       \&B  B  ,             Domain/Services/Email/CustomerNewAccount.phpnu [        PK       \1q7  7  *            4 Domain/Services/CheckoutFieldsFrontend.phpnu [        PK       \5/  /               Domain/Services/Notices.phpnu [        PK       \`1U                 Domain/Services/functions.phpnu [        PK       \ӟ    !            
 Domain/Services/FeatureGating.phpnu [        PK       \I  I  "             Domain/Services/CheckoutFields.phpnu [        PK       \&MB  B               Domain/Bootstrap.phpnu [        PK       \fd5S  S                Domain/Package.phpnu [        PK       \<tG                \- Templates/AbstractTemplate.phpnu [        PK       \}^L  L  "            0 Templates/AbstractTemplatePart.phpnu [        PK       \ǅ%$  %$  #            )2 Templates/SingleProductTemplate.phpnu [        PK       \Z
  
  &            V Templates/ProductAttributeTemplate.phpnu [        PK       \+O    8            a Templates/GroupedProductAddToCartWithOptionsTemplate.phpnu [        PK       \-?  ?  "            f Templates/AbstractPageTemplate.phpnu [        PK       \l    9            m Templates/ExternalProductAddToCartWithOptionsTemplate.phpnu [        PK       \OI.                r Templates/CheckoutTemplate.phpnu [        PK       \F	  	  $            _z Templates/ProductCatalogTemplate.phpnu [        PK       \O(                 Templates/MiniCartTemplate.phpnu [        PK       \dxLR  R  '             Templates/OrderConfirmationTemplate.phpnu [        PK       \>21  1  2            h Templates/ArchiveProductTemplatesCompatibility.phpnu [        PK       \nv    *             Templates/ProductSearchResultsTemplate.phpnu [        PK       \°-l  l  +             Templates/ComingSoonSocialLinksTemplate.phpnu [        PK       \&9  9  0             Templates/SingleProductTemplateCompatibility.phpnu [        PK       \b`    9            
 Templates/VariableProductAddToCartWithOptionsTemplate.phpnu [        PK       \*Gze_  _  $            1 Templates/CheckoutHeaderTemplate.phpnu [        PK       \=@e    "             Templates/ProductBrandTemplate.phpnu [        PK       \ȧ    +            ' Templates/AbstractTemplateCompatibility.phpnu [        PK       \z                 1 Templates/ProductTagTemplate.phpnu [        PK       \DZqR
  R
               8 Templates/ComingSoonTemplate.phpnu [        PK       \F                ]C Templates/CartTemplate.phpnu [        PK       \/3  3  *            K Templates/AbstractTemplateWithFallback.phpnu [        PK       \Į@~	  ~	  +            S Templates/ClassicTemplatesCompatibility.phpnu [        PK       \    %            \ Templates/ProductCategoryTemplate.phpnu [        PK       \Y    7            1d Templates/SimpleProductAddToCartWithOptionsTemplate.phpnu [        PK       \>{N  N              h AssetsController.phpnu [        PK       \V!82                   AI/Connection.phpnu [        PK       \v                   AI/Configuration.phpnu [        PK       \,g                ù Registry/FactoryType.phpnu [        PK       \|                 Registry/Container.phpnu [        PK       \$                _ Registry/SharedType.phpnu [        PK       \Ǖ    #             Registry/AbstractDependencyType.phpnu [        PK       \>  >               Library.phpnu [        PK       \<ߣs  s  %            H Integrations/IntegrationInterface.phpnu [        PK       \.[J  J  $             Integrations/IntegrationRegistry.phpnu [        PK       \Wy
  
               Package.phpnu [        PK       \Y6ԯ                 SharedStores/ProductsStore.phpnu [        PK      p     