PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.1
WooCommerce v10.9.1
10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / src / Blocks / Utils / CartCheckoutUtils.php
woocommerce / src / Blocks / Utils Last commit date
BlockHooksTrait.php 4 weeks ago BlockTemplateUtils.php 2 months ago BlocksSharedState.php 3 months ago BlocksWpQuery.php 2 years ago CartCheckoutUtils.php 4 weeks ago MiniCartUtils.php 1 year ago ProductAvailabilityUtils.php 11 months ago ProductDataUtils.php 1 year ago ProductGalleryUtils.php 4 weeks ago StyleAttributesUtils.php 1 year ago Utils.php 2 years ago
CartCheckoutUtils.php
568 lines
1 <?php // phpcs:ignore Generic.PHP.RequireStrictTypes.MissingDeclaration
2 namespace Automattic\WooCommerce\Blocks\Utils;
3
4 use Automattic\Block_Scanner;
5
6 /**
7 * Class containing utility methods for dealing with the Cart and Checkout blocks.
8 */
9 class CartCheckoutUtils {
10 /**
11 * Caches if we're on the cart page.
12 *
13 * @var bool
14 */
15 private static $is_cart_page = null;
16
17 /**
18 * Caches if we're on the checkout page.
19 *
20 * @var bool
21 */
22 private static $is_checkout_page = null;
23
24 /**
25 * Returns true if the current page is a specific page type (cart or checkout).
26 *
27 * This is determined by looking at the global $post object and comparing it to the post ID defined in settings,
28 * or checking the page contents for a block or shortcode.
29 *
30 * This function cannot be used accurately before the `wp` action has been run.
31 *
32 * @param string $page_type The page type to check for.
33 * @return bool|null
34 */
35 private static function is_page_type( string $page_type ): ?bool {
36 if ( ! did_action( 'wp' ) ) {
37 return null;
38 }
39
40 $page_id = wc_get_page_id( $page_type );
41
42 if ( $page_id && is_page( $page_id ) ) {
43 return true;
44 }
45
46 // If the is_page check returned false, check the page contents for a cart block or shortcode.
47 global $post;
48
49 if ( null === $post ) {
50 return null;
51 }
52
53 if ( $post instanceof \WP_Post ) {
54 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 );
55 }
56
57 return false;
58 }
59
60 /**
61 * Returns true on the cart page.
62 *
63 * @return bool
64 */
65 public static function is_cart_page(): bool {
66 if ( null === self::$is_cart_page ) {
67 self::$is_cart_page = self::is_page_type( 'cart' );
68 }
69 return true === self::$is_cart_page;
70 }
71
72 /**
73 * Returns true on the checkout page.
74 *
75 * @return bool
76 */
77 public static function is_checkout_page(): bool {
78 if ( null === self::$is_checkout_page ) {
79 self::$is_checkout_page = self::is_page_type( 'checkout' );
80 }
81 return true === self::$is_checkout_page;
82 }
83
84 /**
85 * Returns true if shipping methods exist in the store. Excludes local pickup and only counts enabled shipping methods.
86 *
87 * @return bool true if shipping methods exist.
88 */
89 public static function shipping_methods_exist() {
90 // Local pickup is included with legacy shipping methods since they do not support shipping zones.
91 $local_pickup_count = count(
92 array_filter(
93 WC()->shipping()->get_shipping_methods(),
94 function ( $method ) {
95 return isset( $method->enabled ) && 'yes' === $method->enabled && ! $method->supports( 'shipping-zones' ) && $method->supports( 'local-pickup' );
96 }
97 )
98 );
99
100 $shipping_methods_count = wc_get_shipping_method_count( true, true ) - $local_pickup_count;
101 return $shipping_methods_count > 0;
102 }
103
104 /**
105 * Check if the post content contains a block with a specific attribute value.
106 *
107 * @param string $block_id The block ID to check for.
108 * @param string $attribute The attribute to check.
109 * @param string $value The value to check for.
110 * @param string $post_content The post content to check.
111 * @return boolean
112 */
113 public static function has_block_variation( $block_id, $attribute, $value, $post_content ) {
114 if ( ! $post_content ) {
115 return false;
116 }
117
118 $scanner = Block_Scanner::create( $post_content );
119 if ( ! $scanner ) {
120 return false;
121 }
122
123 while ( $scanner->next_delimiter() ) {
124 if ( ! $scanner->opens_block( $block_id ) ) {
125 continue;
126 }
127
128 $attrs = $scanner->allocate_and_return_parsed_attributes();
129
130 if ( isset( $attrs[ $attribute ] ) && $value === $attrs[ $attribute ] ) {
131 return true;
132 }
133
134 // `Cart` is default for `woocommerce/classic-shortcode` so it will be empty in the block attributes.
135 if ( 'woocommerce/classic-shortcode' === $block_id &&
136 'shortcode' === $attribute &&
137 'cart' === $value &&
138 ! isset( $attrs['shortcode'] ) ) {
139 return true;
140 }
141 }
142
143 return false;
144 }
145
146 /**
147 * Checks if the default cart page is using the Cart block.
148 *
149 * @return bool true if the WC cart page is using the Cart block.
150 */
151 public static function is_cart_block_default() {
152 if ( wp_is_block_theme() ) {
153 // Ignore the pages and check the templates.
154 $templates_from_db = BlockTemplateUtils::get_block_templates_from_db( array( 'cart' ), 'wp_template' );
155 foreach ( $templates_from_db as $template ) {
156 if ( has_block( 'woocommerce/cart', $template->content ) ) {
157 return true;
158 }
159 }
160 }
161 $cart_page_id = wc_get_page_id( 'cart' );
162 return $cart_page_id && has_block( 'woocommerce/cart', $cart_page_id );
163 }
164
165 /**
166 * Checks if the default checkout page is using the Checkout block.
167 *
168 * @return bool true if the WC checkout page is using the Checkout block.
169 */
170 public static function is_checkout_block_default() {
171 if ( wp_is_block_theme() ) {
172 // Ignore the pages and check the templates.
173 $templates_from_db = BlockTemplateUtils::get_block_templates_from_db( array( 'checkout' ), 'wp_template' );
174 foreach ( $templates_from_db as $template ) {
175 if ( has_block( 'woocommerce/checkout', $template->content ) ) {
176 return true;
177 }
178 }
179 }
180 $checkout_page_id = wc_get_page_id( 'checkout' );
181 return $checkout_page_id && has_block( 'woocommerce/checkout', $checkout_page_id );
182 }
183
184 /**
185 * Migrate checkout block field visibility attributes to settings when using the checkout block.
186 *
187 * This migration routine is called if the options (woocommerce_checkout_phone_field, woocommerce_checkout_company_field,
188 * woocommerce_checkout_address_2_field) are not set. They are not set by default; they were originally set by the
189 * customizer interface of the legacy shortcode based checkout.
190 *
191 * Once migration is initiated, the settings will be updated and will not trigger this routine again.
192 *
193 * Note: The block only stores non-default attributes. Not all attributes will be present.
194 *
195 * e.g. `{"showCompanyField":true,"requireCompanyField":true,"showApartmentField":false,"className":"wc-block-checkout"}`
196 *
197 * If the attributes are missing, we assume default values are needed.
198 */
199 protected static function migrate_checkout_block_field_visibility_attributes() {
200 // Before migrating attributes, migrate the "default" options checkout block uses into the settings.
201 update_option( 'woocommerce_checkout_phone_field', 'optional' );
202 update_option( 'woocommerce_checkout_company_field', 'hidden' );
203 update_option( 'woocommerce_checkout_address_2_field', 'optional' );
204
205 // Parse the block from the checkout page.
206 $checkout_blocks = \WC_Blocks_Utils::get_blocks_from_page( 'woocommerce/checkout', 'checkout' );
207
208 if ( empty( $checkout_blocks ) || ! isset( $checkout_blocks[0]['attrs'] ) ) {
209 return;
210 }
211
212 // Combine actual attributes with default values.
213 $block_attributes = wp_parse_args(
214 $checkout_blocks[0]['attrs'],
215 array(
216 'showPhoneField' => true,
217 'requirePhoneField' => false,
218 'showCompanyField' => false,
219 'requireCompanyField' => false,
220 'showApartmentField' => true,
221 'requireApartmentField' => false,
222 )
223 );
224
225 if ( $block_attributes['showPhoneField'] ) {
226 update_option( 'woocommerce_checkout_phone_field', $block_attributes['requirePhoneField'] ? 'required' : 'optional' );
227 } else {
228 update_option( 'woocommerce_checkout_phone_field', 'hidden' );
229 }
230
231 if ( $block_attributes['showCompanyField'] ) {
232 update_option( 'woocommerce_checkout_company_field', $block_attributes['requireCompanyField'] ? 'required' : 'optional' );
233 } else {
234 update_option( 'woocommerce_checkout_company_field', 'hidden' );
235 }
236
237 if ( $block_attributes['showApartmentField'] ) {
238 update_option( 'woocommerce_checkout_address_2_field', $block_attributes['requireApartmentField'] ? 'required' : 'optional' );
239 } else {
240 update_option( 'woocommerce_checkout_address_2_field', 'hidden' );
241 }
242 }
243
244 /**
245 * Get the default visibility for the address_2 field.
246 *
247 * @return string
248 */
249 public static function get_company_field_visibility() {
250 $option_value = get_option( 'woocommerce_checkout_company_field' );
251
252 if ( $option_value ) {
253 return $option_value;
254 }
255
256 if ( self::is_checkout_block_default() ) {
257 self::migrate_checkout_block_field_visibility_attributes();
258 return get_option( 'woocommerce_checkout_company_field', 'hidden' );
259 }
260
261 return 'optional';
262 }
263
264 /**
265 * Get the default visibility for the address_2 field.
266 *
267 * @return string
268 */
269 public static function get_address_2_field_visibility() {
270 $option_value = get_option( 'woocommerce_checkout_address_2_field' );
271
272 if ( $option_value ) {
273 return $option_value;
274 }
275
276 if ( self::is_checkout_block_default() ) {
277 self::migrate_checkout_block_field_visibility_attributes();
278 return get_option( 'woocommerce_checkout_address_2_field', 'optional' );
279 }
280
281 return 'optional';
282 }
283
284 /**
285 * Get the default visibility for the address_2 field.
286 *
287 * @return string
288 */
289 public static function get_phone_field_visibility() {
290 $option_value = get_option( 'woocommerce_checkout_phone_field' );
291
292 if ( $option_value ) {
293 return $option_value;
294 }
295
296 if ( self::is_checkout_block_default() ) {
297 self::migrate_checkout_block_field_visibility_attributes();
298 return get_option( 'woocommerce_checkout_phone_field', 'optional' );
299 }
300
301 return 'required';
302 }
303
304 /**
305 * Checks if the template overriding the page loads the page content or not.
306 * 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.
307 *
308 * @since 10.9.0
309 *
310 * @param string $block The block to check.
311 *
312 * @return bool true if the template has out of sync content.
313 */
314 public static function is_overridden_by_custom_template_content( string $block ): bool {
315
316 $block = str_replace( 'woocommerce/', '', $block );
317
318 if ( wp_is_block_theme() ) {
319 $templates_from_db = BlockTemplateUtils::get_block_templates_from_db( array( 'page-' . $block ) );
320 foreach ( $templates_from_db as $template ) {
321 if ( ! has_block( 'woocommerce/page-content-wrapper', $template->content ) ) {
322 // Return true if the template does not load the page content via the woocommerce/page-content-wrapper block.
323 return true;
324 }
325 }
326 }
327
328 return false;
329 }
330
331 /**
332 * Checks if the template overriding the page loads the page content or not.
333 * 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.
334 *
335 * @deprecated 10.9.0 Use is_overridden_by_custom_template_content() instead.
336 *
337 * @param string $block The block to check.
338 *
339 * @return bool true if the template has out of sync content.
340 */
341 public static function is_overriden_by_custom_template_content( string $block ): bool {
342 wc_deprecated_function( __METHOD__, '10.9.0', 'is_overridden_by_custom_template_content' );
343 return self::is_overridden_by_custom_template_content( $block );
344 }
345
346 /**
347 * Gets country codes, names, states, and locale information.
348 *
349 * @return array
350 */
351 public static function get_country_data() {
352 $billing_countries = WC()->countries->get_allowed_countries();
353 $shipping_countries = WC()->countries->get_shipping_countries();
354 $country_states = wc()->countries->get_states();
355 $all_countries = self::deep_sort_with_accents( array_unique( array_merge( $billing_countries, $shipping_countries ) ) );
356 $country_locales = array_map(
357 function ( $locale ) {
358 foreach ( $locale as $field => $field_data ) {
359 if ( isset( $field_data['priority'] ) ) {
360 $locale[ $field ]['index'] = $field_data['priority'];
361 unset( $locale[ $field ]['priority'] );
362 }
363 if ( isset( $field_data['class'] ) ) {
364 unset( $locale[ $field ]['class'] );
365 }
366 }
367 return $locale;
368 },
369 WC()->countries->get_country_locale()
370 );
371
372 $country_data = array();
373
374 foreach ( array_keys( $all_countries ) as $country_code ) {
375 $country_data[ $country_code ] = array(
376 'allowBilling' => isset( $billing_countries[ $country_code ] ),
377 'allowShipping' => isset( $shipping_countries[ $country_code ] ),
378 'states' => $country_states[ $country_code ] ?? array(),
379 'locale' => $country_locales[ $country_code ] ?? array(),
380 );
381 }
382
383 return $country_data;
384 }
385
386 /**
387 * Removes accents from an array of values, sorts by the values, then returns the original array values sorted.
388 *
389 * @param array $sort_array Array of values to sort.
390 * @return array Sorted array.
391 */
392 protected static function deep_sort_with_accents( $sort_array ) {
393 if ( ! is_array( $sort_array ) || empty( $sort_array ) ) {
394 return $sort_array;
395 }
396
397 $array_without_accents = array_map(
398 function ( $value ) {
399 return is_array( $value )
400 ? self::deep_sort_with_accents( $value )
401 : remove_accents( wc_strtolower( html_entity_decode( $value ) ) );
402 },
403 $sort_array
404 );
405
406 asort( $array_without_accents );
407 return array_replace( $array_without_accents, $sort_array );
408 }
409
410 /**
411 * Retrieves formatted shipping zones from WooCommerce.
412 *
413 * @return array An array of formatted shipping zones.
414 */
415 public static function get_shipping_zones() {
416 $shipping_zones = \WC_Shipping_Zones::get_zones();
417 $formatted_shipping_zones = array_reduce(
418 $shipping_zones,
419 function ( $acc, $zone ) {
420 $acc[] = array(
421 'id' => $zone['id'],
422 'title' => $zone['zone_name'],
423 'description' => $zone['formatted_zone_location'],
424 );
425 return $acc;
426 },
427 array()
428 );
429 $formatted_shipping_zones[] = array(
430 'id' => 0,
431 'title' => __( 'International', 'woocommerce' ),
432 'description' => __( 'Locations outside all other zones', 'woocommerce' ),
433 );
434 return $formatted_shipping_zones;
435 }
436
437 /**
438 * Recursively search the checkout block to find the express checkout block and
439 * get the button style attributes using the parse_blocks function.
440 *
441 * @param array $blocks Blocks to search.
442 * @param string $cart_or_checkout The block type to check.
443 *
444 * @return array Block attributes.
445 */
446 public static function find_express_checkout_attributes_in_parsed_blocks( $blocks, $cart_or_checkout ) {
447 $express_block_name = 'woocommerce/' . $cart_or_checkout . '-express-payment-block';
448 foreach ( $blocks as $block ) {
449 if ( ! empty( $block['blockName'] ) && $express_block_name === $block['blockName'] && ! empty( $block['attrs'] ) ) {
450 return $block['attrs'];
451 }
452
453 if ( ! empty( $block['innerBlocks'] ) ) {
454 $answer = self::find_express_checkout_attributes_in_parsed_blocks( $block['innerBlocks'], $cart_or_checkout );
455 if ( $answer ) {
456 return $answer;
457 }
458 }
459 }
460 }
461
462 /**
463 * Recursively search the checkout block to find the express checkout block and
464 * get the button style attributes
465 *
466 * @param string|array $post_content The post content.
467 * @param string $cart_or_checkout The block type to check.
468 *
469 * @return array|null Block attributes, if present and valid, otherwise `null`.
470 */
471 public static function find_express_checkout_attributes( $post_content, $cart_or_checkout ) {
472 if ( is_array( $post_content ) ) {
473 // If an array is passed, assume it's already been parsed with parse_blocks,
474 // use the old method, and show a deprecation warning.
475 wc_deprecated_argument(
476 'post_content',
477 '10.3.0',
478 'Passing parsed blocks as an array in $post_content is deprecated. Please pass the post content as a string.'
479 );
480 return self::find_express_checkout_attributes_in_parsed_blocks( $post_content, $cart_or_checkout );
481 }
482
483 $express_block_name = 'woocommerce/' . $cart_or_checkout . '-express-payment-block';
484
485 $scanner = Block_Scanner::create( $post_content );
486
487 while ( $scanner->next_delimiter() ) {
488 if ( $scanner->opens_block( $express_block_name ) ) {
489 return $scanner->allocate_and_return_parsed_attributes();
490 }
491 }
492
493 return null;
494 }
495
496 /**
497 * Given an array of blocks, find the express payment block and update its attributes.
498 *
499 * @param array $blocks Blocks to search.
500 * @param string $cart_or_checkout The block type to check.
501 * @param array $updated_attrs The new attributes to set.
502 */
503 public static function update_blocks_with_new_attrs( &$blocks, $cart_or_checkout, $updated_attrs ) {
504 $express_block_name = 'woocommerce/' . $cart_or_checkout . '-express-payment-block';
505 foreach ( $blocks as $key => &$block ) {
506 if ( ! empty( $block['blockName'] ) && $express_block_name === $block['blockName'] ) {
507 $blocks[ $key ]['attrs'] = $updated_attrs;
508 }
509
510 if ( ! empty( $block['innerBlocks'] ) ) {
511 self::update_blocks_with_new_attrs( $block['innerBlocks'], $cart_or_checkout, $updated_attrs );
512 }
513 }
514 }
515
516 /**
517 * Check if the cart page is defined.
518 *
519 * @return bool True if the cart page is defined, false otherwise.
520 */
521 public static function has_cart_page() {
522 return wc_get_page_permalink( 'cart', -1 ) !== -1;
523 }
524
525 /**
526 * Get product IDs from a user's persistent cart.
527 *
528 * This method retrieves product IDs stored in the user's persistent cart meta.
529 * It can be used for abandoned cart emails, cart-based product collections,
530 * and other scenarios where cart products need to be retrieved for a user.
531 *
532 * @param int|null $user_id The user ID. If not provided, will attempt to look up by email.
533 * @param string|null $user_email The user email. Used to lookup user if ID not provided.
534 * @return array<int> Array of product IDs from the user's cart, or empty array if none found.
535 */
536 public static function get_cart_product_ids_for_user( ?int $user_id, ?string $user_email ) {
537 if ( empty( $user_id ) && ! empty( $user_email ) ) {
538 $user = get_user_by( 'email', $user_email );
539 if ( $user ) {
540 $user_id = $user->ID;
541 }
542 }
543
544 if ( empty( $user_id ) ) {
545 return array();
546 }
547
548 $cart_meta = get_user_meta( $user_id, '_woocommerce_persistent_cart_' . get_current_blog_id(), true );
549
550 if ( empty( $cart_meta ) || ! is_array( $cart_meta ) || empty( $cart_meta['cart'] ) ) {
551 return array();
552 }
553
554 return array_values(
555 array_unique(
556 array_filter(
557 array_map(
558 function ( $cart_item ) {
559 return isset( $cart_item['product_id'] ) ? intval( $cart_item['product_id'] ) : 0;
560 },
561 $cart_meta['cart']
562 )
563 )
564 )
565 );
566 }
567 }
568