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 = `