# woo-postnl/trunk/src/Frontend/Base.php

PostNL for WooCommerce, version trunk. 572 lines.

- Page: https://pluginprobe.com/plugins/woo-postnl/trunk/code/src/Frontend/Base.php
- Raw: https://pluginprobe.com/plugins/woo-postnl/trunk/raw/src/Frontend/Base.php
- Modified: 2026-06-24T06:23:00+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/woo-postnl/trunk/code/src/Frontend/Base.php#L10-L20`.

```php
<?php
/**
 * Class Frontend/Base file.
 *
 * @package PostNLWooCommerce\Frontend
 */

namespace PostNLWooCommerce\Frontend;

use PostNLWooCommerce\Shipping_Method\Settings;
use PostNLWooCommerce\Utils;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class Base
 *
 * @package PostNLWooCommerce\Frontend
 */
abstract class Base {
	/**
	 * Settings class instance.
	 *
	 * @var PostNLWooCommerce\Shipping_Method\Settings
	 */
	protected $settings;

	/**
	 * Template file name.
	 *
	 * @var string
	 */
	public $template_file;

	/**
	 * Prefix for meta box fields.
	 *
	 * @var prefix
	 */
	protected $prefix = POSTNL_SETTINGS_ID . '_';

	/**
	 * Primary field name.
	 *
	 * @var primary_field
	 */
	protected $primary_field;

	/**
	 * Prefix for meta box fields.
	 *
	 * @var meta_name
	 */
	protected $meta_name;

	/**
	 * Prefix for meta box fields.
	 *
	 * @var meta_name
	 */
	protected $letterbox_meta_name;

	/**
	 * Init and hook in the integration.
	 */
	public function __construct() {
		$this->settings            = Settings::get_instance();
		$this->meta_name           = '_' . $this->prefix . 'order_metadata';
		$this->letterbox_meta_name = '_' . $this->prefix . 'letterbox';
		$this->set_template_file();
		$this->set_primary_field_name();
		$this->init_hooks();
	}

	/**
	 * Need to set the primary field name;
	 */
	abstract public function set_primary_field_name();

	/**
	 * Need to set the template file name;
	 */
	abstract public function set_template_file();

	/**
	 * List of frontend fields.
	 */
	abstract public function get_fields();

	/**
	 * Check if this feature is enabled from the settings.
	 *
	 * @return bool
	 */
	abstract public function is_enabled();

	/**
	 * Collection of hooks when initiation.
	 */
	public function init_hooks() {

		add_filter( 'woocommerce_checkout_posted_data', array( $this, 'validate_posted_data' ) );
		add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'save_data' ), 10, 2 );
		add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'save_letterbox_data' ), 13, 2 );
		add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'save_letterbox_type' ), 14, 2 );
		add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'save_default_data' ), 15, 2 );
		add_filter( 'postnl_frontend_checkout_tab', array( $this, 'add_checkout_tab' ), 10, 2 );
		add_action( 'postnl_checkout_content', array( $this, 'display_content' ), 10, 2 );
		add_filter( 'woocommerce_email_order_meta_fields', array( $this, 'add_pickup_points_fields_to_email' ), 10, 3 );
	}

	/**
	 * Adding a tab in the frontend checkout.
	 *
	 * @param array $tabs List of displayed tabs.
	 * @param array $response Response from PostNL Checkout Rest API.
	 *
	 * @return array
	 */
	abstract public function add_checkout_tab( $tabs, $response );

	/**
	 * Adding a content in the frontend checkout.
	 *
	 * @param array $response Response from PostNL Checkout Rest API.
	 * @param array $post_data Post data on checkout page.
	 */
	abstract public function get_content_data( $response, $post_data );

	/**
	 * Add pickup points info to email templates.
	 *
	 * @param array     $fields Current fields.
	 * @param bool      $sent_to_admin If should sent to admin.
	 * @param \WC_Order $order Order instance.
	 *
	 * @return array
	 */
	public function add_pickup_points_fields_to_email( $fields, $sent_to_admin, $order ) {
		$data = $this->get_data( $order->get_id() );
		if ( ! empty( $data['frontend'] ) ) {
			$value                    = $this->generate_pickup_points_email_html( $data['frontend'] );
			$fields['Pickup address'] = array(
				'label' => __( 'Pick up at PostNL-point', 'postnl-for-woocommerce' ),
				'value' => $value,
			);
		}

		return $fields;
	}

	/**
	 * Generate the dropoff points html information.
	 *
	 * @param Array $infos Dropoff points informations.
	 */
	public function generate_pickup_points_email_html( $infos ) {
		$filtered_infos = Utils::get_filtered_pickup_points_infos( $infos );

		if ( empty( $filtered_infos ) ) {
			return '';
		}

		$value = "<div class='postnl-info-container pickup-points-info'>";

		foreach ( $filtered_infos as $info_idx => $info_val ) {
			switch ( $info_idx ) {
				case 'dropoff_points_date':
					$additional_text = esc_html__( 'Date:', 'postnl-for-woocommerce' );
					break;

				case 'dropoff_points_time':
					$additional_text = esc_html__( 'Time:', 'postnl-for-woocommerce' );
					break;

				default:
					$additional_text = '';
					break;
			}

			$value .= '
				<div>
					' . esc_html( $additional_text . ' ' . $info_val ) . '
				</div>
				';
		}

		$value .= ' </div> <br>';

		return $value;
	}
	/**
	 * Adding a content in the frontend checkout.
	 *
	 * @param array $response Response from PostNL Checkout Rest API.
	 * @param array $post_data Post data on checkout page.
	 */
	public function display_content( $response, $post_data ) {
		$template_args = array(
			'data' => $this->get_content_data( $response, $post_data ),
		);

		wc_get_template( $this->template_file, $template_args, '', POSTNL_WC_PLUGIN_DIR_PATH . '/templates/' );
	}

	/**
	 * Add value to the fields.
	 *
	 * @return array
	 */
	public function get_fields_with_value() {
		$post_data = array();

		if ( isset( $_REQUEST['post_data'] ) ) {
			parse_str( sanitize_text_field( wp_unslash( $_REQUEST['post_data'] ) ), $post_data );
		}

		$field_w_val = array_map(
			function ( $field ) use ( $post_data ) {
				$field['value'] = array_key_exists( $field['id'], $post_data ) ? $post_data[ $field['id'] ] : '';
				return $field;
			},
			$this->get_fields()
		);

		return $field_w_val;
	}

	/**
	 * Validate posted data.
	 *
	 * @param array $data Array of posted data.
	 */
	public function validate_posted_data( $data ) {
		$nonce_value    = wc_get_var( $_REQUEST['woocommerce-process-checkout-nonce'], wc_get_var( $_REQUEST['_wpnonce'], '' ) ); // phpcs:ignore
		$expiry_message = sprintf(
		/* translators: %s: shop cart url */
			__( 'Sorry, your session has expired. <a href="%s" class="wc-backward">Return to shop</a>', 'postnl-for-woocommerce' ),
			esc_url( wc_get_page_permalink( 'shop' ) )
		);

		if ( empty( $nonce_value ) || ! wp_verify_nonce( $nonce_value, 'woocommerce-process_checkout' ) ) {
			return $data;
		}

		if ( ! $this->check_selected_option( $_POST ) ) {
			return $data;
		}

		$data = $this->validate_fields( $data, $_POST );
		$data = $this->add_default_value_to_data( $data, $_POST );

		return $data;
	}

	/**
	 * Add default value to data.
	 *
	 * @param array $data Array of posted data.
	 * @param array $posted_data Array of global _POST data.
	 *
	 * @return array.
	 */
	public function add_default_value_to_data( $data, $posted_data ) {
		foreach ( $posted_data as $input_id => $input_val ) {
			if ( false === strpos( $input_id, 'postnl_default' ) ) {
				continue;
			}

			$data[ $input_id ] = $input_val;
		}
		return $data;
	}

	/**
	 * Validate delivery type fields.
	 *
	 * @param array $data Array of posted data.
	 * @param array $posted_data Array of global _POST data.
	 *
	 * @return array
	 */
	abstract public function validate_fields( $data, $posted_data );

	/**
	 * Check the selected options.
	 *
	 * @param array $posted_data Array of global _POST data.
	 *
	 * @return boolean
	 */
	public function check_selected_option( $posted_data ) {
		if ( empty( $posted_data['postnl_option'] ) ) {
			return false;
		}

		return ( $posted_data['postnl_option'] === $this->primary_field );
	}

	/**
	 * Get primary field value.
	 *
	 * @param array $post_data Array of global _POST data.
	 *
	 * @return mixed
	 */
	public function get_primary_field_value( $post_data ) {
		$fields = $this->get_fields();

		if ( empty( $post_data['postnl_option'] ) ) {
			return '';
		}

		if ( $this->primary_field !== $post_data['postnl_option'] ) {
			return '';
		}

		return ( ! empty( $post_data[ $fields['0']['id'] ] ) ) ? $post_data[ $fields['0']['id'] ] : '';
	}

	/**
	 * Get content data initiation.
	 *
	 * @param array $post_data Array of global _POST data.
	 *
	 * @return array
	 */
	public function get_init_content_data( $post_data ) {
		$fields = $this->get_fields();
		$value  = $this->get_primary_field_value( $post_data );

		return array(
			'field_name' => $fields['0']['id'],
			'value'      => $value,
		);
	}

	/**
	 * Get frontend data from Order object.
	 *
	 * @param int $order_id ID of the order.
	 *
	 * @return array.
	 */
	public function get_data( $order_id ) {
		$order = wc_get_order( $order_id );

		if ( ! is_a( $order, 'WC_Order' ) ) {
			return array();
		}

		$data = $order->get_meta( $this->meta_name );
		return ! empty( $data ) && is_array( $data ) ? $data : array();
	}

	/**
	 * Save default value to data if dropoff points is not picked.
	 *
	 * @param array $order_id ID of order post.
	 * @param array $posted_data Array of global _POST data.
	 *
	 * @return array.
	 */
	public function save_default_data( $order_id, $posted_data ) {
		$order = wc_get_order( $order_id );

		if ( ! is_a( $order, 'WC_Order' ) ) {
			return;
		}

		$data = $this->get_data( $order->get_id() );

		if ( ! empty( $data['frontend'] ) ) {
			return;
		}

		foreach ( $posted_data as $input_id => $input_val ) {
			if ( false === strpos( $input_id, 'postnl_default' ) || empty( $input_val ) ) {
				continue;
			}

			$replaced_id = str_replace( 'postnl_default', 'postnl_delivery_day', $input_id );
			$field_name  = Utils::remove_prefix_field( $this->prefix, $replaced_id );

			$data['frontend'][ $field_name ] = $input_val;
		}

		$order->update_meta_data( $this->meta_name, $data );
		$order->save();
	}

	/**
	 * Save frontend field value to meta.
	 *
	 * @param int   $order_id ID of the order.
	 * @param array $posted_data Posted values.
	 */
	public function save_data( $order_id, $posted_data ) {
		$order = wc_get_order( $order_id );

		if ( ! is_a( $order, 'WC_Order' ) ) {
			return;
		}

		$data = $this->get_data( $order->get_id() );

		foreach ( $this->get_fields() as $field ) {
			if ( array_key_exists( $field['id'], $posted_data ) && ! empty( $posted_data[ $field['id'] ] ) ) {
				$field_name                      = Utils::remove_prefix_field( $this->prefix, $field['id'] );
				$data['frontend'][ $field_name ] = sanitize_text_field( wp_unslash( $posted_data[ $field['id'] ] ) );
			}
		}

		$order->update_meta_data( $this->meta_name, $data );
		$order->save();
	}

	/**
	 * Check if order is eligible for the letterbox and save it as order meta.
	 *
	 * @param int   $order_id ID of the order.
	 * @param array $posted_data Posted values.
	 */
	public function save_letterbox_data( $order_id, $posted_data ) {
		$order = wc_get_order( $order_id );

		if ( ! is_a( $order, 'WC_Order' ) ) {
			return;
		}

		$eligible_for_letterbox = Utils::is_order_eligible_auto_letterbox( $order );

		if ( $eligible_for_letterbox ) {
			$order->update_meta_data( $this->letterbox_meta_name, 1 );
			$order->save();
		}
	}

	/**
	 * Save the letterbox type from the selected shipping method.
	 *
	 * @since 5.9.6
	 *
	 * @param int   $order_id    ID of the order.
	 * @param array $posted_data Posted values.
	 */
	public function save_letterbox_type( $order_id, $posted_data ) {
		$order = wc_get_order( $order_id );

		if ( ! is_a( $order, 'WC_Order' ) ) {
			return;
		}

		// Get the shipping methods from the order.
		$shipping_methods = $order->get_shipping_methods();

		if ( empty( $shipping_methods ) ) {
			return;
		}

		foreach ( $shipping_methods as $shipping_item ) {
			// Check if this is a PostNL shipping method.
			if ( ! in_array( $shipping_item->get_method_id(), $this->settings->get_supported_shipping_methods(), true ) ) {
				continue;
			}

			// Check if the rate has letterbox type in meta data.
			$meta_data = $shipping_item->get_meta_data();
			foreach ( $meta_data as $meta ) {
				if ( 'letterbox_type' === $meta->key && in_array( $meta->value, array( 'letterbox', 'letterbox_48' ), true ) ) {
					$order->update_meta_data( '_postnl_letterbox_type', $meta->value );
					// Persist the meta explicitly. Core saves the order BEFORE this
					// hook fires and passes only the order ID, so the in-memory change
					// above would otherwise be discarded. save_meta_data() writes just
					// the meta row and does not fire the full woocommerce_update_order
					// save cycle.
					$order->save_meta_data();
					return;
				}
			}

		}
	}

	/**
	 * Get evening fee data.
	 *
	 * @return array
	 */
	public static function evening_fee_data() {
		$settings    = Settings::get_instance();
		$evening_fee = $settings->get_evening_delivery_fee();

		return array(
			'fee_name'  => esc_html__( 'PostNL Evening Fee', 'postnl-for-woocommerce' ),
			'fee_price' => floatval( $evening_fee ),
			'condition' => array(
				'key'   => 'delivery_day_type',
				'value' => 'Evening',
			),
		);
	}

	/**
	 * Get morning fee data.
	 *
	 * @return array
	 */
	public static function morning_fee_data() {
		$settings    = Settings::get_instance();
		$morning_fee = $settings->get_morning_delivery_fee();

		return array(
			'fee_name'  => esc_html__( 'PostNL Morning Fee', 'postnl-for-woocommerce' ),
			'fee_price' => floatval( $morning_fee ),
			'condition' => array(
				'key'   => 'delivery_day_type',
				'value' => '08:00-12:00',
			),
		);
	}

	/**
	 * Get available nonstandard delivery time fees data.
	 *
	 * @return array
	 */
	public static function non_standard_fees_data() {
		return array(
			'08:00-12:00' => self::morning_fee_data(),
			'Evening'     => self::evening_fee_data(),
		);
	}

	/**
	 * Check whether the chosen PostNL shipping rate has cost = 0.
	 *
	 * Returns true when the currently selected shipping method is a supported
	 * PostNL method and its rate cost is zero — meaning the store's
	 * minimum_for_free_shipping threshold has been reached.
	 *
	 * @return bool
	 */
	protected function is_postnl_rate_free(): bool {
		if ( ! WC()->session ) {
			return false;
		}

		$chosen    = WC()->session->get( 'chosen_shipping_methods', array() );
		$packages  = WC()->shipping()->get_packages();
		$supported = $this->settings->get_supported_shipping_methods();

		foreach ( $packages as $i => $package ) {
			$method_key = $chosen[ $i ] ?? '';

			if ( ! isset( $package['rates'][ $method_key ] ) ) {
				continue;
			}

			$rate = $package['rates'][ $method_key ];

			if ( in_array( $rate->get_method_id(), $supported, true ) && 0.0 === (float) $rate->cost ) {
				return true;
			}
		}

		return false;
	}
}

```
