568 lines
20 KiB
PHP
568 lines
20 KiB
PHP
<?php
|
|
|
|
function init_postcode_handlers($uri)
|
|
{
|
|
if (strpos($uri, "/winkel/") !== false) {
|
|
add_action("wp_enqueue_scripts", "modal_styles");
|
|
add_action("wp_footer", "send_postcode_data");
|
|
if (!verify_pppr_cookie_string()) {
|
|
if (!is_admin()) {
|
|
WC()->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'
|
|
<script id="postcode_modal_open">
|
|
const postcodeModal = document.querySelector("#postcode_modal");
|
|
postcodeModal.showModal();
|
|
</script>
|
|
HTML;
|
|
}
|
|
|
|
function send_postcode_data()
|
|
{
|
|
$URL = get_rest_url(null, "postcode-modal/v1/submit");
|
|
$nonce = wp_create_nonce("wp_rest");
|
|
|
|
echo <<<HTML
|
|
<script type="module">
|
|
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.";
|
|
}
|
|
});
|
|
</script>
|
|
HTML;
|
|
}
|
|
|
|
function render_dialog_html()
|
|
{
|
|
echo <<<'HTML'
|
|
<dialog id="postcode_modal" class="postcode_modal" closedby="none">
|
|
<h2>Vul je postcode en huisnummer in.</h2>
|
|
<p>Onze prijzen zijn afhankelijk van de regio. Vul daarom de postcode en het huisnummer in om de exacte prijzen te bekijken.</p>
|
|
<form id="postcode_modal_form" method="post" action="" novalidate>
|
|
<div class="form_fields">
|
|
<!-- Dutch Postcode Field -->
|
|
<input
|
|
type="text"
|
|
name="postcode"
|
|
title="Voer een geldige postcode in (bijv. 1234AB of 1234 AB)."
|
|
pattern="[1-9][0-9]{3} ?(?!sa|sd|ss)[a-zA-Z]{2}"
|
|
placeholder="5010 AA"
|
|
size="8"
|
|
required
|
|
autocomplete="off"
|
|
/>
|
|
|
|
<!-- House Number Field -->
|
|
<input
|
|
type="text"
|
|
name="huisnummer"
|
|
title="Voer een geldig huisnummer in (bijv. 1, 1A, 1-A)."
|
|
pattern="\d+([\- ]?[a-zA-Z]+)?"
|
|
placeholder="10"
|
|
size="3"
|
|
required
|
|
autocomplete="off"
|
|
/>
|
|
<button id="postcode_modal_submit" type="submit"><span class="btn_text">OK</span>
|
|
<div class="btn_loader">
|
|
<svg viewBox="0 0 128 30" xmlns="http://www.w3.org/2000/svg">
|
|
<circle style="--i:0" cx="14" cy="15" r="14" />
|
|
<circle style="--i:1" cx="64" cy="15" r="14" />
|
|
<circle style="--i:2" cx="114" cy="15" r="14" />
|
|
</svg>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
<div class="error_message_modal_postcode" id="error_message_modal_postcode" aria-live="polite"></div>
|
|
</dialog>
|
|
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 <<<HTML
|
|
<script type="text/javascript" id="set_pppr_fields">
|
|
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 = `
|
|
<a href="#" class="reset-postcode-show-comfirm" style="font-size:12px; text-decoration:underline; display:block; margin-top:5px;">Reset postcode.</a>
|
|
<span class="bevestiging" style="display:none; font-size:12px; margin-top:5px;">
|
|
Deze handeling leegt ook de winkelwagen. Weet je het zeker?
|
|
<a href="#" class="accept" style="color:red; font-weight:bold;"> JA </a>/
|
|
<a href="#" class="decline"> nee </a>
|
|
</span>`;
|
|
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);
|
|
});
|
|
});
|
|
</script>
|
|
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();
|
|
}
|