cart->empty_cart(); } add_action("wp_footer", "show_modal"); render_dialog_html(); } } add_action("wp_ajax_unset_my_session", "handle_unset_session_fetch"); add_action("wp_ajax_nopriv_unset_my_session", "handle_unset_session_fetch"); add_action("wp_footer", "modify_checkout_with_js"); add_action("wp_enqueue_scripts", "load_assets_reset_postcode_on_checkout"); } function modal_styles() { wp_enqueue_style( "prijs-per-postcode", plugins_url("/assets/postcode_modal.css", __FILE__), [], // Voeg een lege dependencies array toe (verplicht) filemtime(plugin_dir_path(__FILE__) . "assets/postcode_modal.css"), // Voeg versie toe ); } function show_modal() { echo <<<'HTML' HTML; } function send_postcode_data() { $URL = get_rest_url(null, "postcode-modal/v1/submit"); $nonce = wp_create_nonce("wp_rest"); echo << const postcodeModal = document.querySelector("#postcode_modal"); const modalForm = document.querySelector("#postcode_modal_form"); const sndBtn = document.querySelector("#postcode_modal_submit"); const sndBtnLdr = sndBtn.querySelector(".btn_loader"); const sndBtnTxt = sndBtn.querySelector(".btn_text"); const errorMsg = document.querySelector("#error_message_modal_postcode"); // 1. Validatie bij blur (client-side checks) modalForm.querySelectorAll("input").forEach(input => { input.addEventListener("blur", ev => { errorMsg.innerHTML = ""; if (!ev.target.validity.valid && ev.target.value !== "") { errorMsg.innerHTML = ev.target.title; } }); }); // 2. Submit Handler modalForm.addEventListener('submit', async (e) => { e.preventDefault(); errorMsg.innerHTML = ""; // UI: Laadstatus sndBtn.disabled = true; sndBtnLdr.style.opacity = "1"; sndBtnTxt.style.opacity = "0"; const formData = new FormData(e.target); const payload = Object.fromEntries(formData.entries()); try { const resp = await fetch('{$URL}', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': '{$nonce}' }, body: JSON.stringify(payload) }); const data = await resp.json(); sndBtn.disabled = false; sndBtnLdr.style.opacity = "0"; sndBtnTxt.style.opacity = "1"; if (data.status === "error") { const errorCode = data.code; // Komt nu correct door vanuit PHP const apiMessage = data.message; let userErrorMessage = "Gegevens zijn niet correct."; // Specifieke foutafhandeling if (errorCode === "HUISNUMMER_NOT_FOUND" || apiMessage.includes("not found")) { userErrorMessage = "Adres is niet gevonden."; } else if (errorCode === "HUISNUMMER_AMBIGUOUS") { const suggestions = data.suggestions || []; if (suggestions.length > 0) { userErrorMessage = `Meerdere opties: \${suggestions.join(", ")}. Voeg een toevoeging toe.`; } else { userErrorMessage = "Huisnummertoevoeging is verplicht."; } } else if (errorCode === "CURL_ERROR" || errorCode === "JSON_ERROR") { userErrorMessage = "Serverfout. Probeer het later opnieuw."; } errorMsg.innerHTML = userErrorMessage; } if (data.status === "success") { // Succes: Modal sluiten en eventueel reload of update postcodeModal.close(); // Optioneel: location.reload(); of update de pagina content } } catch (err) { sndBtn.disabled = false; sndBtnLdr.style.opacity = "0"; sndBtnTxt.style.opacity = "1"; errorMsg.innerHTML = "Er ging iets mis bij het verbinden."; } }); HTML; } function render_dialog_html() { echo <<<'HTML'

Vul je postcode en huisnummer in.

Onze prijzen zijn afhankelijk van de regio. Vul daarom de postcode en het huisnummer in om de exacte prijzen te bekijken.

HTML; } function handle_postcode_modal($data) { $params = $data->get_params(); $nonce = $data->get_header("X-WP-Nonce"); // 1. Security if (!wp_verify_nonce($nonce, "wp_rest")) { echo json_encode(["status" => "error", "message" => "nononce"]); exit(); } // 2. Basis Validatie if (!verify_postcode($params["postcode"])) { echo json_encode(["status" => "error", "message" => "postcode"]); exit(); } if (!verify_huisnummer($params["huisnummer"])) { echo json_encode(["status" => "error", "message" => "huisnummer"]); exit(); } // 3. API Call (Ruwe data) $result = getStraatnaam($params["postcode"], $params["huisnummer"]); // 4. Validatie & Logic $hasError = isset($result["error"]); $errorCode = null; $errorMessage = null; $candidates = []; $straatnaam = null; $woonplaats = null; if ($hasError) { $errorCode = $result["error"]["code"] ?? "UNKNOWN_ERROR"; $errorMessage = $result["error"]["message"] ?? "Onbekende fout"; $candidates = $result["error"]["details"]["candidates"] ?? []; // SPECIAL CASE: HUISNUMMER_AMBIGUOUS if ($errorCode === "HUISNUMMER_AMBIGUOUS" && !empty($candidates)) { $plainMatch = null; // Zoek naar candidate ZONDER toevoeging foreach ($candidates as $candidate) { if ( empty($candidate["huisletter"]) && empty($candidate["huisnummertoevoeging"]) ) { $plainMatch = $candidate; break; } } // Als we een 'platte' match hebben (bijv. 15), dan is het SUCCES if ($plainMatch) { $hasError = false; // Negeer de error $straatnaam = $plainMatch["straat"]; $woonplaats = $plainMatch["woonplaats"]; } } } else { // Normaal succes (geen error in response) if (!empty($result["results"])) { $straatnaam = $result["results"][0]["straat"]; $woonplaats = $result["results"][0]["woonplaats"]; } else { // Edge case: geen error veld, maar ook geen results $hasError = true; $errorCode = "NO_RESULTS"; $errorMessage = "Adres niet gevonden"; } } // 5. Response sturen if ($hasError) { return new WP_REST_Response([ "status" => "error", "code" => $errorCode, "message" => $errorMessage, "suggestions" => $result["error"]["details"]["suggestions"] ?? [], "apirequest" => "openpostcode.nl", ], 400); } set_pppr_cookie( postcode_in_range($params["postcode"]), trim($straatnaam), trim($params["huisnummer"]), trim($params["postcode"]), trim($woonplaats) ); return new WP_REST_Response([ "status" => "success", "message" => "all good", ], 200); } function verify_postcode($postcode) { if (!preg_match('/^[0-9]{4}\s?[A-Za-z]{2}$/', $postcode) === 1) { return false; } return true; } function verify_huisnummer($huisnummer) { if (!preg_match('/^[0-9]{4}\s?[A-Za-z]{2}$/', $huisnummer) === 1) { return false; } return true; } function getStraatnaam($postcode, $huisnummer) { $url = "https://openpostcode.nl/api/v2/address?postcode=" . urlencode($postcode) . "&huisnummer=" . urlencode($huisnummer); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_USERAGENT, "PHP/OpenPostcodeClient"); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if (curl_error($ch)) { curl_close($ch); return [ "error" => ["code" => "CURL_ERROR", "message" => curl_error($ch)], ]; } curl_close($ch); // Decodeer als associatieve array (true) $data = json_decode($response, true); // Als JSON decode faalt if (json_last_error() !== JSON_ERROR_NONE) { return [ "error" => [ "code" => "JSON_ERROR", "message" => "Invalid JSON response", ], ]; } // Give me back RUWE data (inclusive meta, error, results, etc.) return $data; } function postcode_in_range($postcode) { $vals = get_option("local_postcodes_values", ""); $rows = preg_split("/\R/", $vals, -1, PREG_SPLIT_NO_EMPTY); $pc_arr = []; foreach ($rows as $row) { $row = trim($row); $postcode_range = explode("-", $row); $pc_arr[] = [(int) $postcode_range[0], (int) $postcode_range[1]]; } $cleanPostcode = strtoupper(preg_replace("/\s+/", "", $postcode)); if (!preg_match('/^\d{4}[A-Z]{2}$/', $cleanPostcode)) { return false; } $numberPart = (int) substr($cleanPostcode, 0, 4); foreach ($pc_arr as $pc_to_check) { if ($numberPart >= $pc_to_check[0] && $numberPart <= $pc_to_check[1]) { return true; } } return false; } function modify_checkout_with_js() { if ( ! is_checkout() || ( is_wc_endpoint_url() && ! is_wc_endpoint_url( 'order-received' ) ) ) { return; } $data = get_PPPR_data(); if ( empty( $data ) ) { return; } $city = esc_js( $data['city'] ); $postcode = esc_js( preg_replace( '/(\d+)([A-Z]+)/', '$1 $2', strtoupper( $data['postcode']))); $address = esc_js( $data['street'] . ' ' . strtoupper( $data['house_number'])); // Enqueue script properly to handle dependencies and nonces wp_enqueue_script( 'jquery' ); wp_localize_script( 'jquery', 'ajax_object', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'unset_my_session_nonce' ) ) ); echo << document.addEventListener('DOMContentLoaded', function() { // Safari Fix: Handle back/forward cache (bfcache) window.addEventListener('pageshow', function(event) { if (event.persisted || (typeof wp !== 'undefined' && wp.data)) { initPPPRFields(); } }); function initPPPRFields() { // Wait for React/WC Blocks to mount setTimeout(() => { const addressVal = '{$address}'; const cityVal = '{$city}'; const postcodeVal = '{$postcode}'; // Map WC Blocks field selectors const fields = [ { id: 'shipping-address_1', val: addressVal }, { id: 'shipping-city', val: cityVal }, { id: 'shipping-postcode', val: postcodeVal } ]; fields.forEach(field => { // 1. Try Redux Dispatch (Standard) if (typeof wp !== 'undefined' && wp.data && wp.data.dispatch) { try { wp.data.dispatch('wc/store/cart').setShippingAddress({ [field.id.replace('shipping-', '')]: field.val }); } catch(e) { console.warn('Redux update skipped', e); } } // 2. Force DOM Update (Safari Fallback) // Select both legacy and Blocks inputs const inputs = document.querySelectorAll('#' + field.id + ', input[name="' + field.id.replace('shipping-', '') + '"]'); inputs.forEach(input => { if (input.value !== field.val) { input.value = field.val; // CRITICAL FOR SAFARI: Manually trigger events so React notices ['input', 'change'].forEach(evtType => { input.dispatchEvent(new Event(evtType, { bubbles: true, cancelable: true })); }); } // 3. Apply Visual Lock input.setAttribute('data-pppr-locked', 'true'); input.readOnly = true; }); }); // Inject Reset Button once if (!document.querySelector('.postcode-reset')) { const cityContainer = document.querySelector('.wc-block-components-address-form__city'); if (cityContainer) { const div = document.createElement('div'); div.className = 'postcode-reset'; div.style.display = 'block'; div.style.width = '100%'; div.style.marginTop = '10px'; div.innerHTML = ` Reset postcode. `; cityContainer.parentNode.insertBefore(div, cityContainer.nextSibling); // Event Listeners (using delegation for safety) div.addEventListener('click', function(e) { e.preventDefault(); if (e.target.classList.contains('reset-postcode-show-comfirm')) { e.target.style.display = 'none'; div.querySelector('.bevestiging').style.display = 'block'; } else if (e.target.classList.contains('decline')) { div.querySelector('.bevestiging').style.display = 'none'; div.querySelector('.reset-postcode-show-comfirm').style.display = 'block'; } else if (e.target.classList.contains('accept')) { fetch(ajax_object.ajax_url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'unset_my_session', nonce: ajax_object.nonce }) }) .then(res => res.json()) .then(res => { if (res.success) location.reload(); else alert('Failed to reset: ' + (res.data || 'Unknown error')); }) .catch(err => console.error('AJAX error:', err)); } }); } } // Add CSS for locked state dynamically if (!document.getElementById('pppr-styles')) { const style = document.createElement('style'); style.id = 'pppr-styles'; style.textContent = ` input[data-pppr-locked="true"] { background-color: #f9f9f9 !important; cursor: not-allowed !important; pointer-events: none !important; opacity: 0.7; } `; document.head.appendChild(style); } }, 600); // Delay for WC Blocks hydration } // Initialize on load initPPPRFields(); // Re-apply if checkout updates (e.g. shipping method change) document.body.addEventListener('updated_checkout', function() { setTimeout(initPPPRFields, 400); }); }); HTML; } function load_assets_reset_postcode_on_checkout() { if (is_checkout() && !is_wc_endpoint_url()) { wp_enqueue_style( "reset-postcode-style", plugin_dir_url(__FILE__) . "assets/reset-postcode.css", [], "1.0.0", ); wp_enqueue_script( "reset-postcode-script", plugin_dir_url(__FILE__) . "assets/reset-postcode.js", [], "1.0.0", true, ); // Pass PHP variables to JavaScript wp_localize_script("reset-postcode-script", "ajax_object", [ "ajax_url" => admin_url("admin-ajax.php"), "nonce" => wp_create_nonce("reset_postcode_nonce"), // Creates a secure token ]); } } function register_modal_api() { register_rest_route("postcode-modal/v1", "submit", [ "methods" => "POST", "callback" => "handle_postcode_modal", "permission_callback" => "__return_true", ]); } function handle_unset_session_fetch() { // Verify the nonce for security if (!wp_verify_nonce($_POST["nonce"], "reset_postcode_nonce")) { wp_die("Security check failed."); } unset_pppr_cookie(); // Send a JSON response wp_send_json_success(); }