This repository has been archived by the owner on Feb 23, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 219
/
MiniCart.php
530 lines (458 loc) · 18.6 KB
/
MiniCart.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\StoreApi\Utilities\CartController;
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;
/**
* Mini Cart class.
*
* @internal
*/
class MiniCart extends AbstractBlock {
/**
* 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;
/**
* 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' ) );
}
/**
* 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 ) {
if ( is_cart() || is_checkout() ) {
return;
}
$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;
}
/**
* 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 = [] ) {
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,
''
);
$cart_payload = $this->get_cart_payload();
$this->asset_data_registry->add(
'cartTotals',
isset( $cart_payload['totals'] ) ? $cart_payload['totals'] : null,
null
);
$this->asset_data_registry->add(
'cartItemsCount',
isset( $cart_payload['items_count'] ) ? $cart_payload['items_count'] : null,
null
);
}
$script_data = $this->asset_api->get_script_data( 'build/mini-cart-component-frontend.js' );
$num_dependencies = count( $script_data['dependencies'] );
$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(),
);
$this->asset_data_registry->add(
'mini_cart_block_frontend_dependencies',
$this->scripts_to_lazy_load,
true
);
$this->asset_data_registry->add(
'displayCartPricesIncludingTax',
$this->display_cart_prices_including_tax,
true
);
$template_part_edit_uri = '';
if (
current_user_can( 'edit_theme_options' ) &&
wc_current_theme_is_fse_theme()
) {
$theme_slug = BlockTemplateUtils::theme_has_template_part( 'mini-cart' ) ? wp_get_theme()->get_stylesheet() : BlockTemplateUtils::PLUGIN_SLUG;
$site_editor_uri = admin_url( 'site-editor.php' );
if ( version_compare( get_bloginfo( 'version' ), '5.9', '<' ) ) {
$site_editor_uri = add_query_arg(
array( 'page' => 'gutenberg-edit-site' ),
admin_url( 'themes.php' )
);
}
$template_part_edit_uri = 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.
*/
do_action( 'woocommerce_blocks_cart_enqueue_data' );
}
/**
* 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 ) ) {
return;
}
if ( 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();
$this->scripts_to_lazy_load[ $script->handle ] = array(
'src' => preg_match( '|^(https?:)?//|', $script->src ) ? $script->src : $site_url . $script->src,
'version' => $script->ver,
'before' => $wp_scripts->print_inline_script( $script->handle, 'before', false ),
'after' => $wp_scripts->print_inline_script( $script->handle, 'after', false ),
'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;
}
$cart_controller = $this->get_cart_controller();
$cart = $cart_controller->get_cart_instance();
$cart_contents_total = $cart->get_subtotal();
return '<span class="wc-block-mini-cart__amount">' . esc_html( wp_strip_all_tags( wc_price( $cart_contents_total ) ) ) . '</span>
' . $this->get_include_tax_label_markup();
}
/**
* Returns the markup for render the tax label.
*
* @return string
*/
protected function get_include_tax_label_markup() {
$cart_controller = $this->get_cart_controller();
$cart = $cart_controller->get_cart_instance();
$cart_contents_total = $cart->get_subtotal();
return ( ! empty( $this->tax_label ) && 0 !== $cart_contents_total ) ? ( "<small class='wc-block-mini-cart__tax-label'>" . esc_html( $this->tax_label ) . '</small>' ) : '';
}
/**
* Append frontend scripts when rendering 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 ) {
return $content . $this->get_markup( $attributes );
}
/**
* 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 '';
}
$cart_controller = $this->get_cart_controller();
$cart = $cart_controller->get_cart_instance();
$cart_contents_count = $cart->get_cart_contents_count();
$cart_contents = $cart->get_cart();
$cart_contents_total = $cart->get_subtotal();
if ( $cart->display_prices_including_tax() ) {
$cart_contents_total += $cart->get_subtotal_tax();
}
$classes_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array( 'text_color', 'background_color', 'font_size', 'font_weight', 'font_family' ) );
$wrapper_classes = sprintf( 'wc-block-mini-cart wp-block-woocommerce-mini-cart %s', $classes_styles['classes'] );
if ( ! empty( $attributes['className'] ) ) {
$wrapper_classes .= ' ' . $attributes['className'];
}
$wrapper_styles = $classes_styles['styles'];
$aria_label = sprintf(
/* translators: %1$d is the number of products in the cart. %2$s is the cart total */
_n(
'%1$d item in cart, total price of %2$s',
'%1$d items in cart, total price of %2$s',
$cart_contents_count,
'woo-gutenberg-products-block'
),
$cart_contents_count,
wp_strip_all_tags( wc_price( $cart_contents_total ) )
);
$icon = '<svg class="wc-block-mini-cart__icon" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.84614 18.2769C7.89712 18.2769 7.93845 18.2356 7.93845 18.1846C7.93845 18.1336 7.89712 18.0923 7.84614 18.0923C7.79516 18.0923 7.75384 18.1336 7.75384 18.1846C7.75384 18.2356 7.79516 18.2769 7.84614 18.2769ZM6.03076 18.1846C6.03076 17.182 6.84353 16.3692 7.84614 16.3692C8.84875 16.3692 9.66152 17.182 9.66152 18.1846C9.66152 19.1872 8.84875 20 7.84614 20C6.84353 20 6.03076 19.1872 6.03076 18.1846Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.3231 18.2769C17.3741 18.2769 17.4154 18.2356 17.4154 18.1846C17.4154 18.1336 17.3741 18.0923 17.3231 18.0923C17.2721 18.0923 17.2308 18.1336 17.2308 18.1846C17.2308 18.2356 17.2721 18.2769 17.3231 18.2769ZM15.5077 18.1846C15.5077 17.182 16.3205 16.3692 17.3231 16.3692C18.3257 16.3692 19.1385 17.182 19.1385 18.1846C19.1385 19.1872 18.3257 20 17.3231 20C16.3205 20 15.5077 19.1872 15.5077 18.1846Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0631 9.53835L19.4662 12.6685L19.4648 12.6757L19.4648 12.6757C19.3424 13.2919 19.0072 13.8454 18.5178 14.2394C18.031 14.6312 17.4226 14.8404 16.798 14.8308H8.44017C7.81556 14.8404 7.20714 14.6312 6.72038 14.2394C6.2312 13.8456 5.89605 13.2924 5.77352 12.6765L5.77335 12.6757L4.33477 5.48814C4.3286 5.46282 4.32345 5.43711 4.31934 5.41104L3.61815 1.90768H0.953842C0.42705 1.90768 0 1.48063 0 0.953842C0 0.42705 0.42705 0 0.953842 0H4.4C4.85462 0 5.24607 0.320858 5.33529 0.766644L6.04403 4.30769H12.785C13.0114 4.99157 13.3319 5.63258 13.7312 6.21538H6.42585L7.64421 12.3026L7.64449 12.304C7.67966 12.4811 7.77599 12.6402 7.91662 12.7534C8.05725 12.8666 8.23322 12.9267 8.41372 12.9233L8.432 12.9231H16.8062L16.8244 12.9233C17.0049 12.9267 17.1809 12.8666 17.3215 12.7534C17.4614 12.6408 17.5575 12.4828 17.5931 12.3068L17.5937 12.304L18.1649 9.30867C18.762 9.45873 19.387 9.53842 20.0307 9.53842C20.0415 9.53842 20.0523 9.5384 20.0631 9.53835Z" fill="currentColor"/>
</svg>';
$button_html = $this->get_cart_price_markup( $attributes ) . '
<span class="wc-block-mini-cart__quantity-badge">
' . $icon . '
<span class="wc-block-mini-cart__badge">' . $cart_contents_count . '</span>
</span>';
if ( is_cart() || is_checkout() ) {
// It is not necessary to load the Mini Cart Block on Cart and Checkout page.
return '<div class="' . $wrapper_classes . '" style="visibility:hidden" aria-hidden="true">
<button class="wc-block-mini-cart__button" aria-label="' . esc_attr( $aria_label ) . '" disabled>' . $button_html . '</button>
</div>';
}
$template_part_contents = '';
// Determine if we need to load the template part from the theme, or WooCommerce in that order.
$theme_has_mini_cart = BlockTemplateUtils::theme_has_template_part( 'mini-cart' );
$template_slug_to_load = $theme_has_mini_cart ? get_stylesheet() : BlockTemplateUtils::PLUGIN_SLUG;
$template_part = BlockTemplateUtils::get_block_template( $template_slug_to_load . '//mini-cart', 'wp_template_part' );
if ( $template_part && ! empty( $template_part->content ) ) {
$template_part_contents = do_blocks( $template_part->content );
}
if ( '' === $template_part_contents ) {
$template_part_contents = do_blocks(
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
file_get_contents( Package::get_path() . 'templates/' . BlockTemplateUtils::DIRECTORY_NAMES['TEMPLATE_PARTS'] . '/mini-cart.html' )
);
}
return '<div class="' . $wrapper_classes . '" style="' . $wrapper_styles . '">
<button class="wc-block-mini-cart__button" aria-label="' . esc_attr( $aria_label ) . '">' . $button_html . '</button>
<div class="wc-block-mini-cart__drawer is-loading is-mobile wc-block-components-drawer__screen-overlay wc-block-components-drawer__screen-overlay--is-hidden" aria-hidden="true">
<div class="components-modal__frame wc-block-components-drawer">
<div class="components-modal__content">
<div class="components-modal__header">
<div class="components-modal__header-heading-container"></div>
</div>
<div class="wc-block-mini-cart__template-part">'
. wp_kses_post( $template_part_contents ) .
'</div>
</div>
</div>
</div>
</div>';
}
/**
* Return an instace of the CartController class.
*
* @return CartController CartController class instance.
*/
protected function get_cart_controller() {
return new CartController();
}
/**
* 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 = WC()->cart;
if ( $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,
);
}
/**
* Get Cart Payload.
*
* @return object;
*/
protected function get_cart_payload() {
$notices = wc_get_notices(); // Backup the notices because StoreAPI will remove them.
$payload = WC()->api->get_endpoint_data( '/wc/store/cart' );
if ( ! empty( $notices ) ) {
wc_set_notices( $notices ); // Restore the notices.
}
return $payload;
}
/**
* 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 = [ '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 ), [], true );
$translations[] = $wp_scripts->print_translations( $handle, false );
wp_deregister_script( $handle );
}
$translations = array_filter( $translations );
return implode( '', $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', 'woo-gutenberg-products-block' ),
'inserter' => false,
'content' => '<!-- wp:paragraph {"align":"center"} --><p class="has-text-align-center"><strong>' . __( 'Your cart is currently empty!', 'woo-gutenberg-products-block' ) . '</strong></p><!-- /wp:paragraph -->',
)
);
}
}