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
|
<?php
/**
* What gets loaded on the checkout page, and with what configuration.
*
* @package CepisWooCommerce
*/
declare(strict_types=1);
defined( 'ABSPATH' ) || exit;
/**
* Detects which of WooCommerce's two checkouts is on screen and loads the
* matching integration.
*
* They are not variations of one another. The classic checkout is a server
* rendered form, so the widget can own the fields directly. The Cart/Checkout
* Blocks are React, the fields are controlled inputs, and writing to them from
* outside is at best ignored on the next render — so there the plugin never
* touches the DOM and drives the `wc/store/cart` data store instead.
*/
final class Cepis_Woo_Assets {
/**
* Script handle for the vendored widget.
*/
private const WIDGET = 'cepis-autofill';
/**
* Hook the enqueue.
*/
public static function init(): void {
add_action( 'wp_enqueue_scripts', array( self::class, 'enqueue' ) );
}
/**
* Load the widget plus whichever integration this checkout needs.
*/
public static function enqueue(): void {
$checkout = self::checkout_type();
if ( '' === $checkout || ! Cepis_Woo_Options::enabled() ) {
return;
}
wp_register_script(
self::WIDGET,
CEPIS_WOO_URL . 'assets/js/cepis-autofill.min.js',
array(),
CEPIS_WOO_VERSION,
true
);
$handle = 'cepis-woocommerce-' . $checkout;
$dependencies = 'blocks' === $checkout
? array( self::WIDGET, 'wp-data', 'wc-blocks-data-store' )
: array( self::WIDGET, 'jquery' );
wp_enqueue_script(
$handle,
CEPIS_WOO_URL . 'assets/js/cepis-woocommerce-' . $checkout . '.js',
$dependencies,
CEPIS_WOO_VERSION,
true
);
wp_add_inline_script(
$handle,
'window.cepisWooCommerce = ' . wp_json_encode( self::config( $checkout ), JSON_HEX_TAG | JSON_HEX_AMP ) . ';',
'before'
);
}
/**
* Which checkout is being rendered.
*
* @return string `blocks`, `classic`, or an empty string when this is not a
* checkout the plugin should touch.
*/
private static function checkout_type(): string {
if ( ! function_exists( 'is_checkout' ) || ! is_checkout() ) {
return '';
}
if ( function_exists( 'is_order_received_page' ) && is_order_received_page() ) {
return '';
}
$post = get_post();
return ( $post instanceof WP_Post && has_block( 'woocommerce/checkout', $post ) ) ? 'blocks' : 'classic';
}
/**
* Everything the browser needs, in one object.
*
* @param string $checkout Either `blocks` or `classic`.
*
* @return array<string, mixed>
*/
private static function config( string $checkout ): array {
return array(
'baseUrl' => 'proxy' === Cepis_Woo_Options::mode()
? Cepis_Woo_Rest::base_url()
: Cepis_Woo_Options::base_url(),
'overwrite' => Cepis_Woo_Options::overwrite(),
'focusNumero' => Cepis_Woo_Options::focus_number(),
'debounceMs' => 250,
'timeoutMs' => 8000,
'messages' => Cepis_Woo_Messages::all(),
'fields' => 'blocks' === $checkout ? self::blocks_fields() : self::classic_fields(),
);
}
/**
* CSS selectors for the classic checkout, per address section.
*
* `billing_number` and `billing_neighborhood` are not WooCommerce fields —
* they come from the "Brazilian Market on WooCommerce" plugin, which is the
* de-facto way a Brazilian store gets a house-number and a neighbourhood
* box. A selector that matches nothing is skipped, so listing them costs
* nothing on a store that does not have them.
*
* There is deliberately no fallback onto `address_2` for the neighbourhood:
* in WooCommerce that field means "apartment, suite, unit", and putting a
* neighbourhood there produces a wrong address on the shipping label.
*
* @return array<string, array<string, string>>
*/
private static function classic_fields(): array {
$fields = array();
foreach ( array( 'billing', 'shipping' ) as $section ) {
$fields[ $section ] = array(
'cep' => '#' . $section . '_postcode',
'logradouro' => '#' . $section . '_address_1',
'bairro' => '#' . $section . '_neighborhood',
'localidade' => '#' . $section . '_city',
'uf' => '#' . $section . '_state',
'numero' => '#' . $section . '_number',
);
}
/**
* Filters the field selectors used on the classic checkout.
*
* Keys are the API's own field names; values are CSS selectors. A theme
* or one-page-checkout plugin that renames the fields hooks in here.
*
* @param array<string, array<string, string>> $fields Selectors per address section.
*/
return (array) apply_filters( 'cepis_woocommerce_classic_fields', $fields );
}
/**
* Store API address keys for the Cart/Checkout Blocks.
*
* The block checkout has no house-number and no neighbourhood field, and no
* plugin currently adds one: "Brazilian Market on WooCommerce" only extends
* the classic form. A store that registers its own with
* `woocommerce_register_additional_checkout_field()` can map it here, using
* the namespaced key the Store API exposes it under.
*
* @return array<string, string>
*/
private static function blocks_fields(): array {
$fields = array(
'logradouro' => 'address_1',
'localidade' => 'city',
'uf' => 'state',
);
/**
* Filters the Store API address keys written on the block checkout.
*
* Keys are the API's own field names; values are keys of the address
* object in the `wc/store/cart` data store.
*
* @param array<string, string> $fields Address keys per API field.
*/
return (array) apply_filters( 'cepis_woocommerce_blocks_fields', $fields );
}
}
|