id !== 'product') {
return;
}
printf(
'
',
esc_html__('Ahime Joinery Configurator: the price catalogue shipped with the plugin cannot be read. Reinstall the plugin.', 'ahime-joinery-configurator')
);
});
}
add_action('plugins_loaded', static function (): void {
// WordPress refuses to activate this plugin without WooCommerce (Requires Plugins header).
if (!class_exists('WooCommerce')) {
return;
}
$catalogue = ahjc_catalogue();
if ($catalogue === null) {
return;
}
$structural = new AHJC_Structural($catalogue);
$pricing = new AHJC_Pricing($catalogue, $structural);
$validate = new AHJC_Validate($catalogue, $structural);
(new AHJC_Panier($catalogue, $pricing, $validate))->brancher();
});
/**
* The configurator, placed by a shortcode: `[ahjc_configurator famille="fenetre" produit="123"]`.
*
* `famille` is the product family the configurator opens on, `produit` the ID of the WooCommerce product
* that carries the cart line. Everything else (catalogue, language, cart nonce) reaches the web component as
* JSON in `window.__AHJC_CONTEXT__`, never as HTML attributes.
*
* @param array|string $attributs
*/
function ahjc_shortcode($attributs = []): string
{
$a = shortcode_atts(
['famille' => 'fenetre', 'produit' => '0'],
is_array($attributs) ? $attributs : [],
'ahjc_configurator'
);
$catalogue = ahjc_catalogue();
if ($catalogue === null || !function_exists('wc_get_cart_url')) {
return '';
}
$familles = array_keys((array) $catalogue->get('familles'));
$famille = sanitize_key($a['famille']);
if (!in_array($famille, $familles, true)) {
$famille = 'fenetre';
}
$produit = absint($a['produit']);
$jeton = wp_create_nonce(AHJC_Panier::JETON);
wp_enqueue_script('ahjc-widget', AHJC_URL . 'assets/ahjc-widget.js', [], AHJC_VERSION, true);
wp_add_inline_script(
'ahjc-widget',
'window.__AHJC_CONTEXT__ = ' . wp_json_encode([
'plateforme' => 'wordpress',
// The same catalogue the server prices with: the browser never shows a price the cart recalculates
// differently.
'pricing' => $catalogue->donnees(),
'famillesActives' => [$famille],
'langue' => substr(get_locale(), 0, 2),
// The widget only shows "Add to cart" when it has a nonce.
'nonce' => $jeton,
'urlPanier' => wc_get_cart_url(),
]) . ';',
'before'
);
// ADDING TO THE CART. The widget posts nothing itself: it emits `ahjc:panier` (configuration, quantity)
// and `ahjc:configuration` (current price). This bridge turns them into the standard WooCommerce
// add-to-cart form. The server filters the configuration, validates it and recalculates the price: the
// price announced by the browser is only compared, never trusted.
wp_add_inline_script(
'ahjc-widget',
'(function(){var prix=null;'
. 'document.addEventListener("ahjc:configuration",function(e){if(e.detail){prix=e.detail.totalCents;}});'
. 'document.addEventListener("ahjc:panier",function(e){if(!e.detail||!e.detail.config){return;}'
. 'var f=document.createElement("form");f.method="post";f.action=' . wp_json_encode(wc_get_cart_url()) . ';'
. 'var champs={"add-to-cart":' . $produit . ',"quantity":e.detail.quantite||1,'
. '"ahjc_configuration":JSON.stringify(e.detail.config),'
. '"ahjc_nonce":' . wp_json_encode($jeton) . '};'
. 'if(prix!==null){champs.ahjc_prix_annonce_cents=prix;}'
. 'Object.keys(champs).forEach(function(k){var i=document.createElement("input");i.type="hidden";i.name=k;i.value=String(champs[k]);f.appendChild(i);});'
. 'document.body.appendChild(f);f.submit();});})();',
'before'
);
return sprintf(
'',
esc_attr($famille),
$produit
);
}
add_shortcode('ahjc_configurator', 'ahjc_shortcode');
/**
* The widget is an ES module (lazy chunks): only the tag that carries `src` becomes `type="module"`. The
* context printed "before" stays a classic script, so it runs first.
*/
add_filter('script_loader_tag', static function (string $balise, string $poignee): string {
if ($poignee !== 'ahjc-widget') {
return $balise;
}
return (string) preg_replace('/