id !== 'product') { return; } printf( '

%s

', 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('/]*\btype=)([^>]*\bsrc=)/', '