File "class-receipt-layouts.php"

Full Path: /home/romayxjt/public_html/wp-content/plugins/orderable/inc/modules/receipt-layouts/class-receipt-layouts.php
File size: 47.56 KB
MIME-type: text/x-php
Charset: utf-8

<?php
/**
 * Module: Receipt Layouts.
 *
 * @package Orderable/Classes
 */

defined( 'ABSPATH' ) || exit;

use Automattic\WooCommerce\Internal\ReceiptRendering\ReceiptRenderingEngine;
use Automattic\WooCommerce\Internal\TransientFiles\TransientFilesEngine;
use Automattic\WooCommerce\Utilities\OrderUtil;

/**
 * Checkout module class.
 */
class Orderable_Receipt_Layouts {
	/**
	 * Post type key.
	 *
	 * @var string
	 */
	protected static $post_type_key = 'orderable_receipt';

	/**
	 * Init.
	 */
	public static function run() {
		add_filter( 'wpsf_register_settings_orderable', [ __CLASS__, 'add_settings' ] );

		if ( self::should_disable_module() ) {
			return;
		}

		add_action( 'init', [ __CLASS__, 'register_blocks' ] );
		add_action( 'init', [ __CLASS__, 'register_post_type' ] );
		add_action( 'admin_init', [ __CLASS__, 'should_create_default_receipt_layouts' ] );
		add_action( 'enqueue_block_editor_assets', [ __CLASS__, 'dequeue_assets_from_block_editor' ], 45 );
		add_action( 'enqueue_block_editor_assets', [ __CLASS__, 'enqueue_block_editor_assets' ], 50 );
		add_action( 'current_screen', [ __CLASS__, 'register_block_patterns' ], 15 );
		add_action( 'admin_action_duplicate_' . self::$post_type_key, [ __CLASS__, 'handle_duplicate_action' ], 10 );
		add_action( 'current_screen', [ __CLASS__, 'update_theme_support' ] );
		add_action( 'admin_print_footer_scripts-woocommerce_page_wc-orders', [ __CLASS__, 'add_print_order_buttons_to_hpos_edit_order_page' ] );
		add_action( 'admin_print_footer_scripts-post.php', [ __CLASS__, 'add_print_order_buttons_to_edit_order_page' ] );
		add_action( 'admin_enqueue_scripts', [ __CLASS__, 'enqueue_assets_to_orders_page' ] );
		add_action( 'admin_print_styles', [ __CLASS__, 'output_custom_block_editor_style' ], 50 );

		add_filter( 'block_categories_all', [ __CLASS__, 'add_orderable_block_category' ], 10, 2 );
		add_filter( 'allowed_block_types_all', [ __CLASS__, 'get_allowed_blocks' ], 10, 2 );
		add_filter( 'rest_dispatch_request', [ __CLASS__, 'maybe_apply_receipt_layout' ], 10, 3 );
		add_filter( 'should_load_remote_block_patterns', [ __CLASS__, 'skip_remote_block_patterns' ], 15 );
		add_filter( 'post_row_actions', [ __CLASS__, 'add_duplicate_row_action_link' ], 10, 2 );
		add_filter( 'woocommerce_admin_order_preview_get_order_details', [ __CLASS__, 'add_print_order_button_to_preview_order_details' ] );
	}

	/**
	 * Whether the Receipt Layouts module should be disabled.
	 *
	 * @return boolean
	 */
	protected static function should_disable_module() {
		switch ( true ) {
			case Orderable_Helpers::woocommerce_version_compare( '8.7', '<' ):
				$disable = true;
				break;

			case self::is_bizprint_activated():
				$enable_with_bizprint = Orderable_Settings::get_setting( 'printing_compatibility_enable_with_bizprint' );
				$disable              = ! $enable_with_bizprint;
				break;

			default:
				$disable = false;
				break;
		}

		/**
		 * Filter whether the Receipt Layouts module should be disabled.
		 *
		 * @since 1.18.0
		 * @hook orderable_disable_receipt_layouts_module
		 * @param  bool $disable The value to disable the Receipt Layouts module.
		 * @return bool
		 */
		return apply_filters( 'orderable_disable_receipt_layouts_module', $disable );
	}

	/**
	 * Check if BizPrint Print Manager for WooCommerce is activated
	 *
	 * @see https://wordpress.org/plugins/print-google-cloud-print-gcp-woocommerce/
	 * @return boolean
	 */
	protected static function is_bizprint_activated() {
		return class_exists( 'Zprint\Setup' );
	}

	/**
	 * Get the layout ID defined in Printing settings.
	 *
	 * @return int|null
	 */
	protected static function get_layout_id() {
		$layout_id = absint( Orderable_Settings::get_setting( 'printing_printing_settings_default_printing_layout' ) );

		if ( self::layout_exists( $layout_id ) ) {
			return $layout_id;
		}

		$receipt_layout = self::get_last_receipt_layout();

		if ( ! $receipt_layout ) {
			return null;
		}

		return $receipt_layout->ID;

	}

	/**
	 * Check if layout exists
	 *
	 * @param int $id The layout ID.
	 * @return bool
	 */
	protected static function layout_exists( $id ) {
		if ( empty( $id ) ) {
			return false;
		}

		$receipt_layout = self::get_receipt_layout( $id );

		return ! empty( $receipt_layout );
	}

	/**
	 * Get the rendered receipt layout.
	 *
	 * @param int $layout_id The receipt layout ID.
	 * @return string|false
	 */
	protected static function get_layout( $layout_id ) {
		$orderable_layout = get_post( $layout_id );

		if ( empty( $orderable_layout ) ) {
			return false;
		}

		// phpcs:ignore WooCommerce.Commenting.CommentHooks
		$rendered_content = apply_filters( 'the_content', $orderable_layout->post_content );

		if ( empty( $rendered_content ) || ! is_string( $rendered_content ) ) {
			return false;
		}

		return trim( $rendered_content );
	}

	/**
	 * Apply Receipt Layout
	 *
	 * @param WC_Order $order The order.
	 * @param string   $layout The layout content.      $layout_id The receipt layout ID.
	 * @return string
	 */
	protected static function apply_layout( $order, $layout ) {
		$font_size = self::get_default_font_size();
		$max_width = self::get_default_max_width();

		ob_start();
		$css = include __DIR__ . '/templates/order-receipt-css.php';
		$css = ob_get_contents();
		ob_end_clean();

		/**
		 * Filter the CSS to be used in the receipt layout template.
		 *
		 * @since 1.16.0
		 * @hook orderable_receipt_layouts_template_css
		 * @param  string                         $css   The CSS used in the receipt layout template.
		 * @param  WC_Order|WC_Order_Refund|false $order The order.
		 * @return string New value
		 */
		$data['css']     = apply_filters( 'orderable_receipt_layouts_template_css', $css, $order );
		$data['content'] = $layout;

		ob_start();
		?>
		<html>
			<head>
				<meta
					http-equiv="Content-Type"
					content="<?php bloginfo( 'html_type' ); ?>; charset=<?php echo esc_attr( get_option( 'blog_charset' ) ); ?>"
				/>
				<style>
					<?php echo $data['css']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				</style>

				<script>
					window.print();
				</script>
			</head>

			<body>
				<?php
					/**
					 * Fires before receipt layout content.
					 *
					 * @since 1.16.0
					 * @hook orderable_receipt_layouts_before_template_content
					 * @param  WC_Order|WC_Order_Refund|false $order The order.
					 */
					do_action( 'orderable_receipt_layouts_before_template_content', $order );

					echo $data['content']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

					/**
					 * Fires after receipt layout content.
					 *
					 * @since 1.16.0
					 * @hook orderable_receipt_layouts_before_template_content
					 * @param  WC_Order|WC_Order_Refund|false $order The order.
					 */
					do_action( 'orderable_receipt_layouts_after_template_content', $order );
				?>
			</body>
		</html>
		<?php

		return ob_get_clean();
	}

	/**
	 * Register blocks to be used in the Receipt Layouts custom post type.
	 *
	 * @return void
	 */
	public static function register_blocks() {
		$args = [
			'supports' => [
				'html'       => false,
				'align'      => false,
				'spacing'    => [
					'padding' => true,
					'margin'  => true,
				],
				'color'      => [
					'text' => true,
				],
				'typography' => [
					'textAlign' => true,
				],
			],
		];

		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/customer-billing-details/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/customer-name/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/customer-shipping-details/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/divider/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/order-date-time/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/order-line-items/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/order-location/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/order-meta-fields/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/order-notes/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/order-number/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/order-payment-method/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/order-service-date-time/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/order-service-type/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/order-table/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/order-total-items/build', $args );
		register_block_type( ORDERABLE_MODULES_PATH . 'receipt-layouts/blocks/order-totals/build', $args );
	}

	/**
	 * Get the order to be used in the receipt layout based on the
	 * `orderable_layout_id` parameter passed in the GET request.
	 *
	 * @return WC_Order|WC_Order_Refund|null
	 */
	public static function get_order() {
		switch ( true ) {
			case wp_is_rest_endpoint():
				$order_id = self::get_order_id_from_rest_endpoint();
				break;

			case wp_doing_ajax():
				$order_id = self::get_order_id_from_ajax();
				break;

			default:
				$order_id = self::get_order_id_from_edit_page();
				break;
		}

		if ( ! $order_id ) {
			return null;
		}

		return wc_get_order( $order_id );
	}

	/**
	 * Get the order ID from REST endpoint request.
	 *
	 * @return int|null
	 */
	protected static function get_order_id_from_rest_endpoint() {
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
		if ( 'POST' !== ( $_SERVER['REQUEST_METHOD'] ?? false ) ) {
			return null;
		}

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
		parse_str( $_SERVER['QUERY_STRING'] ?? '', $request_query_string );

		if ( empty( $request_query_string['orderable_layout_id'] ) ) {
			return null;
		}

		/**
		 * Try to catch a pattern like `/wc/v3/orders/1005/receipt`
		 * to make sure we are intercepting the correct endpoint
		 */
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
		if ( 1 !== preg_match( '#.*/wc/v3/orders/(?P<id>[\d]+)/receipt\W.*#', $_SERVER['REQUEST_URI'] ?? '', $matches ) ) {
			return null;
		}

		$order_id = $matches['id'] ?? null;

		if ( empty( $order_id ) ) {
			return null;
		}

		return absint( $order_id );
	}

	/**
	 * Get the order ID from REST endpoint request.
	 *
	 * @return int|null
	 */
	protected static function get_order_id_from_ajax() {
		$allowed_actions = [
			'woocommerce_get_order_details',
		];

		// phpcs:ignore WordPress.Security.NonceVerification
		if ( empty( $_GET['action'] ) || empty( $_GET['order_id'] ) ) {
			return null;
		}

		// phpcs:ignore WordPress.Security.NonceVerification
		$action = sanitize_text_field( wp_unslash( $_GET['action'] ) );

		if ( ! in_array( $action, $allowed_actions, true ) ) {
			return null;
		}

		// phpcs:ignore WordPress.Security.NonceVerification
		$order_id = sanitize_text_field( wp_unslash( $_GET['order_id'] ) );

		if ( empty( $order_id ) ) {
			return null;
		}

		return absint( $order_id );
	}

	/**
	 * Get the order ID from edit Order page.
	 *
	 * @return int|null
	 */
	protected static function get_order_id_from_edit_page() {
		if ( ! function_exists( 'get_current_screen' ) ) {
			return;
		}

		$screen = get_current_screen();

		if ( empty( $screen ) ) {
			return null;
		}

		if ( OrderUtil::custom_orders_table_usage_is_enabled() && 'woocommerce_page_wc-orders' !== $screen->id ) {
			return null;
		}

		if ( ! OrderUtil::custom_orders_table_usage_is_enabled() && 'shop_order' !== $screen->post_type ) {
			return null;
		}

		// phpcs:ignore WordPress.Security.NonceVerification
		if ( empty( $_GET['action'] ) || ( empty( $_GET['id'] ) && empty( $_GET['post'] ) ) ) {
			return null;
		}

		// phpcs:ignore WordPress.Security.NonceVerification
		$action = sanitize_text_field( wp_unslash( $_GET['action'] ) );

		if ( 'edit' !== $action ) {
			return null;
		}

		// phpcs:ignore WordPress.Security.NonceVerification
		$order_id = absint( wp_unslash( $_GET['id'] ?? $_GET['post'] ?? 0 ) );

		return $order_id;
	}

	/**
	 * Get the Receipt block wrapper attributes.
	 *
	 * By default, the class `'wp-block-orderable-receipt-layouts` is added
	 * to all Receipt blocks.
	 *
	 * @return string
	 */
	public static function get_receipt_block_wrapper_attributes() {
		return get_block_wrapper_attributes( [ 'class' => 'wp-block-orderable-receipt-layouts' ] );
	}

	/**
	 * Get allowed blocks for `orderable_receipt` post type.
	 *
	 * @param bool|string[]           $allowed_block_types  Array of block type slugs, or boolean to enable/disable all.
	 * @param WP_Block_Editor_Context $block_editor_context The current block editor context.
	 * @return bool|string[]
	 */
	public static function get_allowed_blocks( $allowed_block_types, $block_editor_context ) {
		$receipt_layouts_blocks = [
			'orderable/customer-billing-details',
			'orderable/customer-name',
			'orderable/customer-shipping-details',
			'orderable/divider',
			'orderable/order-date-time',
			'orderable/order-line-items',
			'orderable/order-location',
			'orderable/order-meta-fields',
			'orderable/order-notes',
			'orderable/order-number',
			'orderable/order-payment-method',
			'orderable/order-service-date-time',
			'orderable/order-service-type',
			'orderable/order-table',
			'orderable/order-total-items',
			'orderable/order-totals',
		];

		if ( self::$post_type_key === ( $block_editor_context->post->post_type ?? false ) ) {
			$additional_blocks = [
				'core/paragraph',
				'core/spacer',
				'core/columns',
				'core/column',
				'core/heading',
				'core/table',
				'core/image',
			];

			return array_merge( $receipt_layouts_blocks, $additional_blocks );
		}

		if ( empty( $allowed_block_types ) ) {
			return $allowed_block_types;
		}

		if ( is_array( $allowed_block_types ) ) {
			// Remove the Receipt Layouts blocks
			return array_values(
				array_diff(
					$allowed_block_types,
					$receipt_layouts_blocks
				)
			);
		}

		$registered_blocks = WP_Block_Type_Registry::get_instance()->get_all_registered();
		$all_block_types   = array_keys( $registered_blocks );

		// Remove the Receipt Layouts blocks
		return array_values(
			array_diff(
				$all_block_types,
				$receipt_layouts_blocks
			)
		);
	}

	/**
	 * Register Receipt Layouts post type.
	 *
	 * @return void
	 */
	public static function register_post_type() {
		$labels = Orderable_Helpers::prepare_post_type_labels(
			array(
				'plural'   => __( 'Receipt Layouts', 'orderable' ),
				'singular' => __( 'Receipt Layout', 'orderable' ),
			)
		);

		$args = [
			'labels'              => $labels,
			'supports'            => [ 'title', 'editor' ],
			'hierarchical'        => false,
			'public'              => false,
			'show_ui'             => true,
			'show_in_menu'        => 'orderable',
			'menu_position'       => 55,
			'show_in_admin_bar'   => true,
			'show_in_nav_menus'   => false,
			'can_export'          => true,
			'has_archive'         => false,
			'exclude_from_search' => true,
			'publicly_queryable'  => false,
			'show_in_rest'        => true,
			'capability_type'     => 'post',
		];

		register_post_type( self::$post_type_key, $args );
	}

	/**
	 * Check whether the current page is the block editor
	 *
	 * @param WP_Screee|null $current_screen The current screen.
	 * @return boolean
	 */
	protected static function is_orderable_receipt_block_editor_page( $current_screen = null ) {
		$current_screen = is_a( $current_screen, 'WP_Screen' ) ? $current_screen : get_current_screen();

		if ( ! $current_screen ) {
			return false;
		}

		if ( ! $current_screen->is_block_editor() ) {
			return false;
		}

		if ( self::$post_type_key !== $current_screen->post_type ) {
			return false;
		}

		return true;
	}

	/**
	 * Dequeue assets from block editor.
	 *
	 * Since some 3rd-party assets can conflict
	 * with Orderable Layout styles, this function tries
	 * to remove them and keep only the core assets.
	 *
	 * @return void
	 */
	public static function dequeue_assets_from_block_editor() {
		if ( ! self::is_orderable_receipt_block_editor_page() ) {
			return;
		}

		global $wp_styles, $wp_scripts;

		remove_editor_styles();

		foreach ( $wp_styles->queue as $handle ) {
			$allowed_handles = [
				'admin-bar',
				'media-views',
				'imgareaselect',
				'buttons',
				'editor-buttons',
				'wp-edit-post',
				'wp-block-directory',
				'wp-format-library',
			];

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

			wp_dequeue_style( $handle );
		}

		foreach ( $wp_scripts->queue as $handle ) {
			$allowed_handles = [
				'common',
				'admin-bar',
				'heartbeat',
				'wp-edit-post',
				'media-editor',
				'media-audiovideo',
				'mce-view',
				'image-edit',
				'editor',
				'quicktags',
				'wplink',
				'jquery-ui-autocomplete',
				'media-upload',
				'wp-block-styles',
				'wp-block-directory',
				'wp-format-library',
			];

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

			wp_dequeue_script( $handle );
		}
	}

	/**
	 * Enqueue block editor assets for `orderable_receipt` post type.
	 *
	 * @return void
	 */
	public static function enqueue_block_editor_assets() {
		if ( ! self::is_orderable_receipt_block_editor_page() ) {
			return;
		}

		$suffix     = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
		$suffix_css = ( is_rtl() ? '-rtl' : '' ) . $suffix;

		wp_enqueue_style(
			'orderable-receipt-admin',
			ORDERABLE_URL . 'inc/modules/receipt-layouts/assets/admin/css/block-editor/receipt-layouts-block-editor' . $suffix_css . '.css',
			[],
			ORDERABLE_VERSION
		);

		wp_enqueue_script(
			'orderable-receipt-admin',
			ORDERABLE_URL . 'inc/modules/receipt-layouts/assets/admin/js/block-editor/main' . $suffix . '.js',
			[ 'wp-hooks' ],
			ORDERABLE_VERSION,
			true
		);

		$welcome_guide_script_id = 'orderable-receipt-welcome-guide';
		$script_asset_path       = __DIR__ . '/assets/admin/js/block-editor/welcome-guide/index.asset.php';
		$script_asset            = file_exists( $script_asset_path )
			? require $script_asset_path
			: [
				'dependencies' => [],
				'version'      => ORDERABLE_VERSION,
			];

		wp_enqueue_script(
			$welcome_guide_script_id,
			ORDERABLE_URL . 'inc/modules/receipt-layouts/assets/admin/js/block-editor/welcome-guide/index.js',
			$script_asset['dependencies'],
			$script_asset['version'],
			true
		);

		wp_localize_script(
			$welcome_guide_script_id,
			'orderableReceiptWelcomeGuide',
			[
				'shouldShowWelcomeGuide' => self::should_show_welcome_guide(),
			]
		);

		$preview_sidebar_script_id = 'orderable-receipt-preview-sidebar';
		$script_asset_path         = __DIR__ . '/assets/admin/js/block-editor/preview-sidebar/index.asset.php';
		$script_asset              = file_exists( $script_asset_path )
			? require $script_asset_path
			: [
				'dependencies' => [],
				'version'      => ORDERABLE_VERSION,
			];

		wp_enqueue_script(
			$preview_sidebar_script_id,
			ORDERABLE_URL . 'inc/modules/receipt-layouts/assets/admin/js/block-editor/preview-sidebar/index.js',
			$script_asset['dependencies'],
			$script_asset['version'],
			true
		);
	}

	/**
	 * Whether should show the Welcome Guide.
	 *
	 * @return boolean
	 */
	protected static function should_show_welcome_guide() {
		$has_seen_welcome_guide_key = 'orderable_receipt_has_seen_welcome_guide';
		$has_seen_welcome_guide     = get_user_option( $has_seen_welcome_guide_key );

		if ( $has_seen_welcome_guide ) {
			// phpcs:ignore WooCommerce.Commenting.CommentHooks
			return apply_filters( 'orderable_receipt_should_show_welcome_guide', false );
		}

		update_user_option( get_current_user_id(), $has_seen_welcome_guide_key, true );

		/**
		 * Filter whether should show the Welcome Guide.
		 *
		 * @since 1.19.0
		 * @hook orderable_receipt_should_show_welcome_guide
		 * @param  bool $should_show Whether should show the Welcome Guide.
		 */
		return apply_filters( 'orderable_receipt_should_show_welcome_guide', true );
	}

	/**
	 * Intercept the receipt geneartion and maybe apply the Receipt layout.
	 *
	 * @param mixed           $dispatch_result Dispatch result, will be used if not empty.
	 * @param WP_REST_Request $request         Request used to generate the response.
	 * @param string          $route           Route matched for the request.
	 * @return mixed|array
	 */
	public static function maybe_apply_receipt_layout( $dispatch_result, WP_REST_Request $request, $route ) {
		if ( '/wc/v3/orders/(?P<id>[\d]+)/receipt' !== $route ) {
			return $dispatch_result;
		}

		$orderable_layout_id = $request->get_param( 'orderable_layout_id' );

		if ( ! $orderable_layout_id ) {
			return $dispatch_result;
		}

		$order_id = $request->get_param( 'id' );

		$order = wc_get_order( $order_id );

		if ( ! $order ) {
			return $dispatch_result;
		}

		if ( ! $request->get_param( 'force_new' ) ) {
			$existing_receipt_filename = wc_get_container()->get( ReceiptRenderingEngine::class )->get_existing_receipt( $order );

			if ( ! is_null( $existing_receipt_filename ) ) {
				return $existing_receipt_filename;
			}
		}

		$expiration_date =
			$request->get_param( 'expiration_date' ) ??
			gmdate( 'Y-m-d', strtotime( "+{$request->get_param('expiration_days')} days" ) );

		$layout = self::get_layout( $orderable_layout_id );

		if ( ! $layout ) {
			return null;
		}

		$rendered_template = self::apply_layout( $order, $layout );

		if ( empty( $rendered_template ) ) {
			return $dispatch_result;
		}

		$file_name = wc_get_container()->get( TransientFilesEngine::class )->create_transient_file( $rendered_template, $expiration_date );

		$order->update_meta_data( ReceiptRenderingEngine::RECEIPT_FILE_NAME_META_KEY, $file_name );
		$order->save_meta_data();

		if ( is_null( $file_name ) ) {
			return new WP_Error( 'woocommerce_rest_not_found', __( 'Order not found', 'woocommerce' ), [ 'status' => 404 ] );
		}

		$expiration_date = TransientFilesEngine::get_expiration_date( $file_name );
		$public_url      = wc_get_container()->get( TransientFilesEngine::class )->get_public_url( $file_name );

		return [
			'receipt_url'     => $public_url,
			'expiration_date' => $expiration_date,
		];
	}

	/**
	 * Get rendered receipt layout public URL
	 *
	 * @param int|WC_Order $order The order.
	 * @param int          $layout_id The layout ID.
	 * @return string|null
	 */
	public static function get_public_url( $order, $layout_id ) {
		if ( ! is_a( $order, 'WC_Order' ) ) {
			$order = wc_get_order( $order );
		}

		if ( ! $order ) {
			return null;
		}

		$layout = self::get_layout( $layout_id );

		if ( ! $layout ) {
			return null;
		}

		$rendered_layout = self::apply_layout( $order, $layout );

		if ( ! $rendered_layout ) {
			return null;
		}

		$expiration = new DateTime( 'now', wp_timezone() );
		$expiration->modify( '+3 hours' );

		$file_name = wc_get_container()->get( TransientFilesEngine::class )->create_transient_file( $rendered_layout, $expiration->getTimestamp() );

		$order->update_meta_data( ReceiptRenderingEngine::RECEIPT_FILE_NAME_META_KEY, $file_name );
		$order->save_meta_data();

		if ( is_null( $file_name ) ) {
			return null;
		}

		return wc_get_container()->get( TransientFilesEngine::class )->get_public_url( $file_name );
	}

	/**
	 * Add `Orderable` block category
	 *
	 * @param array[]                 $block_categories     Array of categories for block types.
	 * @param WP_Block_Editor_Context $block_editor_context The current block editor context.
	 * @return array[]
	 */
	public static function add_orderable_block_category( $block_categories, $block_editor_context ) {
		if ( self::$post_type_key !== ( $block_editor_context->post->post_type ?? false ) ) {
			return $block_categories;
		}

		if ( ! empty( $block_categories['orderable'] ) ) {
			return $block_categories;
		}

		$block_categories[] = [
			'slug'  => 'orderable',
			'title' => __( 'Orderable', 'orderable' ),
		];

		return $block_categories;
	}

	/**
	 * Skip remote block patterns for `orderable_receipt` custom post type.
	 *
	 * @param bool $should_load_remote Wehther should load remote block patterns.
	 * @return bool
	 */
	public static function skip_remote_block_patterns( $should_load_remote ) {
		$http_referer = wp_get_referer();

		if ( ! $http_referer ) {
			return $should_load_remote;
		}

		$query = wp_parse_url( $http_referer, PHP_URL_QUERY );

		if ( ! $query ) {
			return $should_load_remote;
		}

		wp_parse_str( $query, $result );

		$post_id   = absint( $result['post'] ?? 0 );
		$action    = $result['action'] ?? '';
		$post_type = $result['post_type'] ?? '';

		if ( ( empty( $post_id ) || 'edit' !== $action ) && ( self::$post_type_key !== $post_type ) ) {
			return $should_load_remote;
		}

		if ( ! empty( $post_id ) && self::$post_type_key !== get_post_type( $post_id ) ) {
			return $should_load_remote;
		}

		return false;
	}

	protected static function get_block_patterns() {
		$block_patterns = [];

		$pattern_files = glob( ORDERABLE_MODULES_PATH . '/receipt-layouts/patterns/*.php' );

		if ( ! $pattern_files ) {
			return $block_patterns;
		}

		$default_pattern_properties = [
			'postTypes'  => [ self::$post_type_key ],
			'categories' => [ 'orderable/receipt-layouts' ],
		];

		foreach ( $pattern_files as $pattern_file ) {
			$pattern_data = get_file_data(
				$pattern_file,
				[
					'title'       => 'Title',
					'description' => 'Description',
					'slug'        => 'Slug',
					'categories'  => 'Categories',
				]
			);

			if ( empty( $pattern_data['title'] ) || empty( $pattern_data['slug'] ) ) {
				continue;
			}

			$pattern_properties = wp_parse_args(
				array_filter( $pattern_data ),
				$default_pattern_properties
			);

			if ( ! file_exists( $pattern_file ) ) {
				continue;
			}

			ob_start();

			include $pattern_file;
			$pattern_properties['content'] = ob_get_clean();

			$block_patterns[] = $pattern_properties;
		}

		return $block_patterns;
	}

	/**
	 * Register `orderable_receipt` block patterns.
	 *
	 * @return void
	 */
	public static function register_block_patterns() {
		$screen = get_current_screen();

		if ( self::$post_type_key !== $screen->post_type || ! $screen->is_block_editor() ) {
			return;
		}

		$registered_patterns          = WP_Block_Patterns_Registry::get_instance()->get_all_registered() ?? [];
		$registered_category_patterns = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered() ?? [];

		foreach ( $registered_patterns as $pattern_data ) {
			unregister_block_pattern( $pattern_data['name'] );
		}

		foreach ( $registered_category_patterns as $category_pattern_data ) {
			unregister_block_pattern_category( $category_pattern_data['name'] );
		}

		register_block_pattern_category(
			'orderable/receipt-layouts',
			[
				'label' => __( 'Orderable Receipt Layouts', 'orderable' ),
			]
		);

		foreach ( self::get_block_patterns() as $block_pattern ) {
			register_block_pattern( $block_pattern['slug'], $block_pattern );
		}
	}

	/**
	 * Add `Duplicate` action
	 *
	 * @param string[] $actions An array of row action links.
	 * @param WP_Post  $post    The post object
	 * @return string[]
	 */
	public static function add_duplicate_row_action_link( $actions, $post ) {
		if ( self::$post_type_key !== $post->post_type ) {
			return $actions;
		}

		$post_type_object = get_post_type_object( self::$post_type_key );

		if ( empty( $post_type_object ) ) {
			return $actions;
		}

		$url_to_duplicate = wp_nonce_url(
			admin_url(
				sprintf(
					'edit.php?post_type=%1$s&action=duplicate_%1$s&amp;post=%2$d',
					self::$post_type_key,
					$post->ID
				)
			),
			'orderable_duplicate_' . self::$post_type_key . '_' . $post->ID
		);

		$actions['duplicate'] = sprintf(
			'<a href="%s" aria-label="%s" rel="permalink">%s</a>',
			$url_to_duplicate,
			// translators: %s - singular name of the Receipt Layout type.
			sprintf( __( 'Make a duplicate from this %s' ), $post_type_object->labels->singular_name ),
			__( 'Duplicate', 'iconic-wsb' )
		);

		return $actions;
	}

	/**
	 * Handle the action to duplicate the receipt layout.
	 *
	 * @return void
	 */
	public static function handle_duplicate_action() {
		$post_type_object = get_post_type_object( self::$post_type_key );

		if ( empty( $post_type_object ) ) {
			wp_die( esc_html__( "It's not possible to duplicate this receipt layout", 'orderable' ) );
		}

		if ( empty( $_REQUEST['post'] ) ) {
			wp_die(
				sprintf(
					// translators: %s - Receipt Layout type.
					esc_html__( 'No %s to duplicate has been supplied!', 'orderable' ),
					esc_html( $post_type_object->labels->singular_name )
				)
			);
		}

		$receipt_layout_id = absint( $_REQUEST['post'] );

		check_admin_referer( 'orderable_duplicate_' . self::$post_type_key . '_' . $receipt_layout_id );

		$receipt_layout = self::get_receipt_layout( $receipt_layout_id, [ 'post_status' => 'any' ] );

		if ( ! $receipt_layout ) {
			wp_die(
				sprintf(
					/* translators: %1$s: Receipt Layout type; %2$d: Receipt Layout ID*/
					esc_html__( '%1$s creation failed, could not find original post: %2$d', 'orderable' ),
					esc_html( $post_type_object->labels->singular_name ),
					esc_html( $receipt_layout_id )
				)
			);
		}

		$duplicated_post_args = [
			/* translators: %s contains the name of the original post. */
			'post_title'   => sprintf( esc_html__( '%s (Copy)', 'orderable' ), get_the_title( $receipt_layout_id ) ),
			'post_content' => get_the_content( null, false, $receipt_layout_id ),
			'post_type'    => self::$post_type_key,
		];

		$duplicated_post_id = wp_insert_post( $duplicated_post_args );

		// it can hold WP_Error @phpstan-ignore function.impossibleType
		if ( empty( $duplicated_post_id ) || is_wp_error( $duplicated_post_id ) ) {
			wp_die(
				sprintf(
					// translators: %s - Receipt Layout type.
					esc_html__( '%s creation failed', 'orderable' ),
					esc_html( $post_type_object->labels->singular_name )
				)
			);
		}

		wp_safe_redirect( admin_url( 'post.php?action=edit&post=' . $duplicated_post_id ) );
		exit;
	}

	/**
	 * Update the theme support.
	 *
	 * @param WP_Screen $current_screen Current WP_Screen object.
	 * @return void
	 */
	public static function update_theme_support( $current_screen ) {
		if ( ! self::is_orderable_receipt_block_editor_page( $current_screen ) ) {
			return;
		}

		add_theme_support( 'custom-spacing' );

		add_theme_support(
			'editor-color-palette',
			[
				[
					'name'  => __( 'Default', 'orderable' ),
					'slug'  => 'default',
					'color' => '#111111',
				],
			]
		);
	}

	/**
	 * Get the default `Print Order` label.
	 *
	 * @return string
	 */
	protected static function get_button_label() {
		return __( 'Print Order', 'orderable' );
	}

	/**
	 * Add Print Order button to preview order details.
	 *
	 * @param array $order_details The order details.
	 * @return array
	 */
	public static function add_print_order_button_to_preview_order_details( $order_details ) {
		if ( empty( $order_details['data']['id'] ) ) {
			return $order_details;
		}

		$layout_id = self::get_layout_id();

		if ( ! $layout_id ) {
			return $order_details;
		}

		$order_id = $order_details['data']['id'];

		$url = self::get_public_url( $order_id, $layout_id );

		ob_start();
		?>
		<div class="orderable-receipt-layouts__wrapper-print-order-button" style="float:left;margin-right: 15px">
			<?php self::output_print_order_button( $url, self::get_button_label(), $order_id ); ?>
		</div>
		<?php

		$print_order_button = ob_get_clean();

		$order_details['actions_html'] = $print_order_button . $order_details['actions_html'];
		return $order_details;

	}

	/**
	 * Output the Print Order button.
	 *
	 * @param string $url      The URL.
	 * @param string $label    The Label.
	 * @param int    $order_id The order ID.
	 * @return void
	 */
	protected static function output_print_order_button( $url, $label, $order_id ) {
		$classes = [ 'button-primary', 'orderable-receipt-layouts__print-order-button' ];
		$classes = join( ' ', $classes );

		$receipt_layouts = self::get_receipt_layouts( [ 'posts_per_page' => 20 ] );

		if ( ! $receipt_layouts ) {
			return;
		}
		?>
		<span style="position: relative;">
			<a
				href="<?php echo esc_url( $url ); ?>"
				target="_blank"
				rel="noopener noreferrer"
				role="button"
				class="<?php echo esc_attr( $classes ); ?>"
			>
				<?php echo esc_html( $label ); ?>
			</a>
			<?php self::output_list_receipt_layouts( $receipt_layouts, $order_id, $url ); ?>
		</span>
		<?php
	}

	/**
	 * Output the list of receipt layouts.
	 *
	 * @param array $receipt_layouts The receipt layouts.
	 * @param int   $order_id        The order ID.
	 * @return void
	 */
	protected static function output_list_receipt_layouts( $receipt_layouts, $order_id, $default_receipt_layout_url ) {
		if ( ! $receipt_layouts || count( $receipt_layouts ) < 2 ) {
			return;
		}

		$default_receipt_layout_id = self::get_layout_id();

		foreach ( $receipt_layouts as $key => $receipt_layout ) {
			if ( $default_receipt_layout_id !== $receipt_layout->ID ) {
				continue;
			}

			$default_receipt_layout = $receipt_layout;
			unset( $receipt_layouts[ $key ] );
			break;
		}

		array_unshift( $receipt_layouts, $default_receipt_layout );

		?>
		<button class="button-primary orderable-receipt-layouts__receipt-layout-options-button">
			<span class="dashicons dashicons-arrow-down orderable-receipt-layouts__receipt-layout-options-icon-button"></span>
		</button>
		<ul class="orderable-receipt-layouts__receipt-layout-options-list">
			<?php foreach ( $receipt_layouts as $receipt_layout ) : ?>
				<li class="orderable-receipt-layouts__receipt-layout-options-item-list">
					<?php if ( $receipt_layout->ID === $default_receipt_layout_id ) : ?>
						<a
							href="<?php echo esc_url( $default_receipt_layout_url ); ?>"
							target="_blank"
							rel="noopener noreferrer"
							role="button"
							class="orderable-receipt-layouts__receipt-layout-option orderable-receipt-layouts__receipt-layout-option-print-link"
						>
							<?php echo sprintf( '%s (default)', esc_html( $receipt_layout->post_title ) ); ?>
						</a>
					<?php endif; ?>

					<?php if ( $receipt_layout->ID !== $default_receipt_layout_id ) : ?>
						<button
							class="orderable-receipt-layouts__receipt-layout-option orderable-receipt-layouts__receipt-layout-option-print-button"
							data-order-id="<?php echo esc_attr( $order_id ); ?>"
							data-receipt-layout-id="<?php echo esc_attr( $receipt_layout->ID ); ?>"
						>
							<?php echo esc_html( $receipt_layout->post_title ); ?>
						</button>
						<span class="orderable-receipt-layouts__receipt-layout-option-loading spinner"></span>
					<?php endif; ?>
				</li>
			<?php endforeach; ?>
		</ul>
		<?php
	}

	/**
	 * Add Print Order buttons to the edit order page (HPOS)
	 */
	public static function add_print_order_buttons_to_hpos_edit_order_page() {
		if ( empty( $_GET['action'] ) || empty( $_GET['id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification
		$action = sanitize_text_field( wp_unslash( $_GET['action'] ) );

		if ( 'edit' !== $action ) {
			return;
		}

		$order_id = absint( wp_unslash( $_GET['id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
		self::output_print_order_buttons_to_edit_order_page( $order_id );
	}

	/**
	 * Output the script to render the print order butons to edit order page.
	 *
	 * @param int $order_id The order ID.
	 */
	protected static function output_print_order_buttons_to_edit_order_page( $order_id ) {
		$layout_id = self::get_layout_id();

		if ( ! $layout_id ) {
			return;
		}

		$url = self::get_public_url( $order_id, $layout_id );

		ob_start();

		self::output_print_order_button( $url, self::get_button_label(), $order_id );

		$print_order_button = trim( ob_get_clean() );

		?>
			<script>
				jQuery( document ).ready( function() {
					const $add_new_button = jQuery( '.page-title-action' ).first();

					if ( $add_new_button.length ) {
						$add_new_button.before( `<span style="position:relative; top: -3px"><?php echo $print_order_button; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>` );
					}

					const $add_items = jQuery('.add-items');

					if ($add_items.length) {
						$add_items.prepend(`<div style="float:left; margin-right:.25em"><?php echo $print_order_button; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>`);
					}

				} );
			</script>
		<?php
	}

	/**
	 * Add print order buttons to edit order page (classic).
	 */
	public static function add_print_order_buttons_to_edit_order_page() {
		if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
			return;
		}

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

		$screen = get_current_screen();

		if ( empty( $screen ) ) {
			return;
		}

		if ( 'shop_order' !== $screen->id ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification
		if ( empty( $_GET['action'] ) || empty( $_GET['post'] ) ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification
		$action = sanitize_text_field( wp_unslash( $_GET['action'] ) );

		if ( 'edit' !== $action ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification
		$order_id = absint( wp_unslash( $_GET['post'] ) );
		self::output_print_order_buttons_to_edit_order_page( $order_id );
	}

	/**
	 * Add Printing settings.
	 *
	 * @param array $settings The Orderable settings.
	 * @return array
	 */
	public static function add_settings( $settings ) {
		$settings['tabs'][] = [
			'id'       => 'printing',
			'title'    => __( 'Printing', 'orderable' ),
			'priority' => 30,
		];

		$settings['sections'][] = [
			'tab_id'              => 'printing',
			'section_id'          => 'printing_settings',
			'section_title'       => __( 'Printing Settings', 'orderable' ),
			'section_description' => '',
			'section_order'       => 0,
			'fields'              => [
				[
					'id'       => 'default_printing_layout',
					'title'    => __( 'Default layout', 'orderable' ),
					'subtitle' => __( 'Select the default receipt layout when printing an order.', 'orderable' ),
					'type'     => 'select',
					'choices'  => self::get_receipt_layouts_options(),
				],
				[
					'id'       => 'default_font_size',
					'title'    => __( 'Default font size', 'orderable' ),
					'subtitle' => __( 'Set the default font size for text across the entire layout.', 'orderable' ),
					'type'     => 'custom',
					'output'   => self::output_default_font_size_field(),
				],
				[
					'id'       => 'max_preview_width',
					'title'    => __( 'Maximum preview width', 'orderable' ),
					'subtitle' => __( 'Enforces a max width in the editor and in the generated preview.', 'orderable' ),
					'type'     => 'custom',
					'output'   => self::output_maximum_preview_width_field(),
				],
			],
		];

		if ( self::is_bizprint_activated() ) {
			$settings['sections'][] = [
				'tab_id'              => 'printing',
				'section_id'          => 'compatibility',
				'section_title'       => __( 'Compatibility', 'orderable' ),
				'section_description' => '',
				'section_order'       => 0,
				'fields'              => [
					[
						'id'       => 'enable_with_bizprint',
						'title'    => __( 'Enable with BizPrint', 'orderable' ),
						'subtitle' => __( 'By default, Receipt Layouts is disabled when BizPrint is enabled. Check this field if you want to run both together.', 'orderable' ),
						'type'     => 'checkbox',
					],

				],
			];
		}

		return $settings;
	}

	/**
	 * Get receipt layout by ID.
	 *
	 * @param int   $id The receipt layout ID.
	 * @param array $query_args The WP_Query args.
	 * @return WP_Post|null
	 */
	protected static function get_receipt_layout( $id, $query_args = [] ) {
		$default_query_args = [
			'p'              => $id,
			'posts_per_page' => 1,
		];

		$query_args = wp_parse_args( $query_args, $default_query_args );

		$receipt_layout = self::get_receipt_layouts( $query_args );

		return $receipt_layout[0] ?? null;
	}

	/**
	 * Get last receipt layout created.
	 *
	 * @return WP_Post|int|null
	 */
	protected static function get_last_receipt_layout() {
		$receipt_layouts = self::get_receipt_layouts(
			[
				'posts_per_page' => 1,
				'order'          => 'DESC',
				'orderby'        => 'modified date',
			]
		);

		return $receipt_layouts[0] ?? null;
	}

	/**
	 * Get receipt layouts.
	 *
	 * @param array $query_args The WP_Query args.
	 * @return WP_Post[]|int[]|null
	 */
	protected static function get_receipt_layouts( $query_args = [] ) {
		$default_query_args = [
			'fields'                 => 'all',
			'post_type'              => self::$post_type_key,
			'posts_per_page'         => 200,
			'post_status'            => 'publish',
			'no_found_rows'          => true,
			'update_post_meta_cache' => false,
			'update_post_term_cache' => false,
		];

		$query_args = wp_parse_args( $query_args, $default_query_args );

		/**
		 * Filter the query args to retrieve the receipt layouts.
		 *
		 * @since 1.18.0
		 * @hook orderable_get_receipt_layouts_query_args
		 * @param  array $query_args The WP query args to retrieve the receipt layouts.
		 */
		$query_args = apply_filters( 'orderable_get_receipt_layouts_query_args', $query_args );

		$query = new WP_Query( $query_args );

		if ( empty( $query->posts ) ) {
			return null;
		}

		return $query->posts;
	}

	/**
	 * Get the receipt layout options.
	 *
	 * @return array
	 */
	protected static function get_receipt_layouts_options() {
		$options = [ __( 'No receipt layouts created', 'orderable' ) ];

		$receipt_layouts = self::get_receipt_layouts();

		if ( ! $receipt_layouts ) {
			return $options;
		}

		$options = [ __( 'Select...', 'orderable' ) ];

		foreach ( $receipt_layouts as $layout ) {
			$options[ $layout->ID ] = $layout->post_title;
		}

		return $options;
	}

	/**
	 * Enqueue assets to orders page.
	 *
	 * @param string $hook_suffix The current admin page.
	 * @return void
	 */
	public static function enqueue_assets_to_orders_page( $hook_suffix ) {
		$current_screen = get_current_screen();

		if ( 'woocommerce_page_wc-orders' !== $hook_suffix && 'edit-shop_order' !== $current_screen->id && 'shop_order' !== $current_screen->id ) {
			return;
		}

		if ( ! self::get_layout_id() ) {
			return;
		}

		$asset_id = 'orderable-receipt-orders-page';

		$assets_path = ORDERABLE_URL . 'inc/modules/receipt-layouts/assets/admin/';
		$suffix      = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
		$suffix_css  = ( is_rtl() ? '-rtl' : '' ) . $suffix;

		wp_enqueue_style( $asset_id, $assets_path . 'css/orders-page/receipt-layouts-orders-page' . $suffix_css . '.css', [], ORDERABLE_VERSION );
		wp_enqueue_script( $asset_id, $assets_path . 'js/orders-page/main' . $suffix . '.js', [ 'jquery' ], ORDERABLE_VERSION, true );

		wp_localize_script(
			$asset_id,
			'orderableReceiptLayouts',
			[
				'receiptLayoutId' => self::get_layout_id(),
			]
		);
	}

	/**
	 * Get the option key.
	 *
	 * @return string
	 */
	protected static function get_has_created_default_receipt_layouts_key() {
		return '_orderable_has_created_default_receipt_layouts';
	}

	/**
	 * Check whether there is at least one receipt layout created.
	 *
	 * @return boolean
	 */
	protected static function has_receipt_layouts() {
		$receipt_layouts = self::get_receipt_layouts(
			[
				'post_per_page' => 1,
				'fields'        => 'ids',
				'post_status'   => 'any',
			]
		);

		return ! empty( $receipt_layouts );
	}

	/**
	 * Check if it's necessary to create the default receipt layouts.
	 *
	 * @return void
	 */
	public static function should_create_default_receipt_layouts() {
		$option_key = self::get_has_created_default_receipt_layouts_key();

		$has_created_default_receipt_layouts = get_option( $option_key, false );

		if ( ! empty( $has_created_default_receipt_layouts ) ) {
			return;
		}

		if ( self::has_receipt_layouts() ) {
			update_option( $option_key, true, false );
			return;
		}

		foreach ( self::get_block_patterns() as $block_pattern ) {
			wp_insert_post(
				[
					'post_title'   => esc_html( $block_pattern['title'] ),
					'post_content' => wp_kses_post( $block_pattern['content'] ),
					'post_type'    => self::$post_type_key,
					'post_status'  => 'publish',
				]
			);
		}

		if ( self::has_receipt_layouts() ) {
			update_option( $option_key, true, false );
			return;
		}
	}

	/**
	 * Output the select field to show the unit options.
	 *
	 * @param string $setting_name  The settings name.
	 * @param string $default_value The default value to be selected.
	 * @return void
	 */
	protected static function unit_options_select_field( $setting_name, $default_value ) {
		$option         = Orderable_Settings::get_setting( $setting_name );
		$selected_value = $option['unit'] ?? $default_value;

		$unit_options = [
			'pt' => __( 'Point', 'orderable' ),
			'px' => __( 'Pixel', 'orderable' ),
			'cm' => __( 'Centimeters', 'orderable' ),
			'in' => __( 'Inches', 'orderable' ),
			'mm' => __( 'Millimeters', 'orderable' ),
		];

		?>
		<select name='orderable_settings[<?php echo esc_attr( $setting_name ); ?>][unit]'>
			<?php foreach ( $unit_options as $value => $label ) : ?>
				<option
					value="<?php echo esc_attr( $value ); ?>"
					<?php selected( $selected_value, $value ); ?>
				>
					<?php echo esc_html( $label ); ?>
				</option>
			<?php endforeach; ?>
		</select>
		<?php
	}

	/**
	 * The default font size field.
	 *
	 * @return string
	 */
	protected static function output_default_font_size_field() {
		$default_font_size = Orderable_Settings::get_setting( 'printing_printing_settings_default_font_size' );
		$font_size         = $default_font_size['value'] ?? '12';

		ob_start();
		?>
		<div style="display: flex;">
			<input 
				type="number" 
				name='orderable_settings[printing_printing_settings_default_font_size][value]' 
				value="<?php echo esc_attr( $font_size ); ?>"
				min="0"
			/>
			<?php self::unit_options_select_field( 'printing_printing_settings_default_font_size', 'pt' ); ?>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * The Maximum preview width field.
	 *
	 * @return string
	 */
	protected static function output_maximum_preview_width_field() {
		$default_maximum_preview_width = Orderable_Settings::get_setting( 'printing_printing_settings_maximum_preview_width_field' );
		$width                         = $default_maximum_preview_width['value'] ?? '768';

		ob_start();
		?>
		<div style="display: flex;">
			<input 
				type="number" 
				name='orderable_settings[printing_printing_settings_maximum_preview_width_field][value]' 
				value="<?php echo esc_attr( $width ); ?>"
				min="0"
			/>
			<?php self::unit_options_select_field( 'printing_printing_settings_maximum_preview_width_field', 'px' ); ?>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Get the default font size setting.
	 *
	 * @return string
	 */
	protected static function get_default_font_size() {
		$font_size  = Orderable_Settings::get_setting( 'printing_printing_settings_default_font_size' );
		$font_value = $font_size['value'] ?? 12;
		$font_unit  = $font_size['unit'] ?? 'pt';

		return $font_value . $font_unit;
	}

	/**
	 * Get the default max width setting.
	 *
	 * @return string
	 */
	protected static function get_default_max_width() {
		$max_width       = Orderable_Settings::get_setting( 'printing_printing_settings_maximum_preview_width_field' );
		$max_width_value = $max_width['value'] ?? 768;
		$max_width_unit  = $max_width['unit'] ?? 'px';

		return $max_width_value . $max_width_unit;
	}

	/**
	 * Output the custom styles to the block editor defined on the Printing settings page.
	 *
	 * @return void
	 */
	public static function output_custom_block_editor_style() {
		if ( ! self::is_orderable_receipt_block_editor_page() ) {
			return;
		}

		$font_size = self::get_default_font_size();
		$max_width = self::get_default_max_width();

		?>
			<style>
				html .post-type-orderable_receipt :where(.wp-block):not(h1, h2, h3, h4, h5, h6) {
					font-size: <?php echo esc_html( $font_size ); ?> !important;
				}

				html .post-type-orderable_receipt .wp-block-post-content,
				html .post-type-orderable_receipt .editor-styles-wrapper .wp-block {
					max-width: <?php echo esc_html( $max_width ); ?> !important;
				}
			</style>
		<?php
	}
}