*/ class Pizzapilot_Settings { /** * The ID of this plugin. * * @since 1.0.0 * @access private * @var string $plugin_name The ID of this plugin. */ private $plugin_name; /** * The version of this plugin. * * @since 1.0.0 * @access private * @var string $version The current version of this plugin. */ private $version; /** * Initialize the class and set its properties. * * @since 1.0.0 * @param string $plugin_name The name of this plugin. * @param string $version The version of this plugin. */ public function __construct( $plugin_name, $version ) { $this->plugin_name = $plugin_name; $this->version = $version; } /** * Register the PizzaPilot top-level admin menu and Settings submenu. * * Creates a top-level "PizzaPilot" menu with a dashicons-food icon. * The first submenu item is "Settings" which contains the tabbed * interface for General, Delivery, and Advanced configuration. * * @since 1.0.0 * @return void */ public function ppilot_admin_menu() { // Top-level menu. add_menu_page( __( 'PizzaPilot', 'pizzapilot' ), __( 'PizzaPilot', 'pizzapilot' ), 'manage_options', 'pizzapilot-settings', array( $this, 'settings_page' ), 'dashicons-food', 56 ); // "Settings" submenu (replaces the auto-generated first submenu item). add_submenu_page( 'pizzapilot-settings', __( 'PizzaPilot Settings', 'pizzapilot' ), __( 'Settings', 'pizzapilot' ), 'manage_options', 'pizzapilot-settings', array( $this, 'settings_page' ) ); } /** * Add contextual help tabs to the PizzaPilot settings page. * * Provides guidance for each settings tab (General, Delivery, Advanced) * via the WordPress Help dropdown at the top of the screen. * * @since 1.1.0 * @return void */ public function add_settings_help_tabs() { $screen = get_current_screen(); if ( ! $screen || 'toplevel_page_pizzapilot-settings' !== $screen->id ) { return; } $screen->add_help_tab( array( 'id' => 'pizzapilot-help-general', 'title' => __( 'General', 'pizzapilot' ), 'content' => '

' . __( 'The General tab lets you enable or disable PizzaPilot. When disabled, the time slot selector and delivery radius checker will not appear at checkout.', 'pizzapilot' ) . '

', ) ); $screen->add_help_tab( array( 'id' => 'pizzapilot-help-delivery', 'title' => __( 'Delivery', 'pizzapilot' ), 'content' => '

' . __( 'Configure your delivery settings:', 'pizzapilot' ) . '

' . '', ) ); $screen->add_help_tab( array( 'id' => 'pizzapilot-help-advanced', 'title' => __( 'Advanced', 'pizzapilot' ), 'content' => '

' . __( 'The Advanced tab shows features that can be unlocked with PizzaPilot Pro, including future date ordering, slot capacity limits, recurring slot templates, and interactive delivery maps.', 'pizzapilot' ) . '

', ) ); $screen->set_help_sidebar( '

' . __( 'For more information:', 'pizzapilot' ) . '

' . '

' . __( 'PizzaPilot Pro', 'pizzapilot' ) . '

' ); } /** * Render the PizzaPilot settings page with tabbed interface. * * Creates a tabbed interface for different setting categories: * - General: Basic plugin configuration * - Delivery: Delivery-related settings * - Advanced: Pro features and advanced options * * @since 1.0.0 * @return void */ public function settings_page() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- tab navigation only, no data processing. $active_tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : 'general'; ?>

'array', 'default' => array(), 'sanitize_callback' => array( $this, 'sanitize_general_settings' ), ) ); // Register Delivery Settings register_setting( 'pizzapilot_delivery_settings', 'pizzapilot_delivery_settings', array( 'type' => 'array', 'default' => array( 'radius_unit' => 'km', 'delivery_postcode' => '', 'delivery_radius' => 5, ), 'sanitize_callback' => array( $this, 'sanitize_delivery_settings' ), ) ); // Register Advanced Settings register_setting( 'pizzapilot_advanced_settings', 'pizzapilot_advanced_settings', array( 'type' => 'array', 'default' => array(), 'sanitize_callback' => array( $this, 'sanitize_advanced_settings' ), ) ); // General Settings Tab - Basic plugin configuration add_settings_section( 'pizzapilot_general', esc_html__( 'General Settings', 'pizzapilot' ), array( $this, 'section_callback' ), 'pizzapilot-settings-general' ); add_settings_field( 'pizzapilot_enabled', esc_html__( 'Enable PizzaPilot', 'pizzapilot' ), array( $this, 'enabled_callback' ), 'pizzapilot-settings-general', 'pizzapilot_general' ); // Delivery Settings Tab - Delivery configuration add_settings_section( 'pizzapilot_delivery', esc_html__( 'Delivery Settings', 'pizzapilot' ), array( $this, 'delivery_section_callback' ), 'pizzapilot-settings-delivery' ); add_settings_field( 'pizzapilot_delivery_postcode', esc_html__( 'Delivery Postcode', 'pizzapilot' ), array( $this, 'delivery_postcode_callback' ), 'pizzapilot-settings-delivery', 'pizzapilot_delivery' ); add_settings_field( 'pizzapilot_delivery_radius', esc_html__( 'Delivery Radius', 'pizzapilot' ), array( $this, 'delivery_radius_callback' ), 'pizzapilot-settings-delivery', 'pizzapilot_delivery' ); add_settings_field( 'pizzapilot_radius_unit', esc_html__( 'Radius Unit', 'pizzapilot' ), array( $this, 'radius_unit_callback' ), 'pizzapilot-settings-delivery', 'pizzapilot_delivery' ); /** * Callback to render the Delivery Start Time input field. * * Outputs a time input for the delivery start time setting on the Delivery tab. * Uses the value from settings or defaults to '09:30'. * * @since 1.0.0 * @return void */ if ( ! $pro_active ) { add_settings_field( 'pizzapilot_delivery_start_time', esc_html__( 'Delivery Start Time', 'pizzapilot' ), array( $this, 'delivery_start_time_callback' ), 'pizzapilot-settings-delivery', 'pizzapilot_delivery' ); } /** * Callback to render the Delivery End Time input field. * * Outputs a time input for the delivery end time setting on the Delivery tab. * Uses the value from settings or defaults to '17:30'. * * @since 1.0.0 * @return void */ if ( ! $pro_active ) { add_settings_field( 'pizzapilot_delivery_end_time', esc_html__( 'Delivery End Time', 'pizzapilot' ), array( $this, 'delivery_end_time_callback' ), 'pizzapilot-settings-delivery', 'pizzapilot_delivery' ); } /** * Allow Pro plugin to add its own delivery settings fields. * * @since 1.0.0 * @param bool $pro_active Whether the Pro plugin is active. * @param string $settings_page The settings page slug. * @param string $section_id The section ID. */ do_action( 'pizzapilot_register_pro_delivery_settings', $pro_active, 'pizzapilot-settings-delivery', 'pizzapilot_delivery' ); // Advanced Settings Tab - Pro features and advanced options add_settings_section( 'pizzapilot_advanced', esc_html__( 'Advanced Settings', 'pizzapilot' ), array( $this, 'advanced_section_callback' ), 'pizzapilot-settings-advanced' ); add_settings_field( 'pizzapilot_same_day_only', esc_html__( 'Same-Day Delivery Only', 'pizzapilot' ), array( $this, 'same_day_only_callback' ), 'pizzapilot-settings-advanced', 'pizzapilot_advanced' ); // Describe Pro features (text and upgrade link only) when Pro is not active. if ( ! $pro_active ) { add_settings_field( 'pizzapilot_slot_capacity_pro', esc_html__( 'Slot Capacity Limits', 'pizzapilot' ), array( $this, 'pro_slot_capacity_callback' ), 'pizzapilot-settings-advanced', 'pizzapilot_advanced' ); add_settings_field( 'pizzapilot_recurring_slots_pro', esc_html__( 'Auto-Generated Recurring Slots', 'pizzapilot' ), array( $this, 'pro_recurring_slots_callback' ), 'pizzapilot-settings-advanced', 'pizzapilot_advanced' ); add_settings_field( 'pizzapilot_delivery_maps_pro', esc_html__( 'Interactive Delivery Maps', 'pizzapilot' ), array( $this, 'pro_delivery_maps_callback' ), 'pizzapilot-settings-advanced', 'pizzapilot_advanced' ); } /** * Allow Pro plugin to add its own settings fields * * @param bool $pro_active Whether the Pro plugin is active * @param string $settings_page The settings page slug * @param string $section_id The section ID */ do_action( 'pizzapilot_register_pro_settings', $pro_active, 'pizzapilot-settings-advanced', 'pizzapilot_advanced' ); } /** * Sanitize general settings * * @param array $input The settings input array * @return array Sanitized settings */ public function sanitize_general_settings( $input ) { $sanitized = array(); // For checkboxes, we need to explicitly check if the key exists in the input // If it doesn't exist, it means the checkbox was unchecked $sanitized['enabled'] = isset( $input['enabled'] ) ? true : false; return $sanitized; } /** * Sanitize delivery settings * * @param array $input The settings input array * @return array Sanitized settings */ public function sanitize_delivery_settings( $input ) { $sanitized = array(); $sanitized['radius_unit'] = isset( $input['radius_unit'] ) && in_array( $input['radius_unit'], array( 'km', 'miles' ), true ) ? $input['radius_unit'] : 'km'; $sanitized['delivery_postcode'] = isset( $input['delivery_postcode'] ) ? sanitize_text_field( $input['delivery_postcode'] ) : ''; $sanitized['delivery_radius'] = isset( $input['delivery_radius'] ) ? absint( $input['delivery_radius'] ) : 5; // Sanitize delivery start time if ( isset( $input['delivery_start_time'] ) ) { // Validate time format (HH:mm) if ( preg_match( '/^([0-1][0-9]|2[0-3]):[0-5][0-9]$/', $input['delivery_start_time'] ) ) { $sanitized['delivery_start_time'] = $input['delivery_start_time']; } else { $sanitized['delivery_start_time'] = '10:00'; // Default if invalid } } else { $sanitized['delivery_start_time'] = '10:00'; } // Sanitize delivery end time if ( isset( $input['delivery_end_time'] ) ) { if ( preg_match( '/^([0-1][0-9]|2[0-3]):[0-5][0-9]$/', $input['delivery_end_time'] ) ) { $sanitized['delivery_end_time'] = $input['delivery_end_time']; } else { $sanitized['delivery_end_time'] = '17:30'; } } else { $sanitized['delivery_end_time'] = '17:30'; } /** * Allow Pro plugin to add its own delivery settings sanitization. * * @since 1.0.0 * @param array $sanitized The sanitized array so far. * @param array $input The raw input array. */ $sanitized = apply_filters( 'pizzapilot_sanitize_pro_delivery_settings', $sanitized, $input ); return $sanitized; } /** * Sanitize advanced settings * * @param array $input The settings input array * @return array Sanitized settings */ public function sanitize_advanced_settings( $input ) { $sanitized = array(); if ( Pizzapilot_Helpers::pizzapilot_is_pro_active( 'Pizzapilot_Pro' ) ) { // For checkboxes, we need to explicitly check if the key exists in the input // If it doesn't exist, it means the checkbox was unchecked $sanitized['same_day_only'] = isset( $input['same_day_only'] ) ? true : false; } else { // Without Pro no field is rendered, so nothing is ever submitted // for this key. Reading its absence as "unchecked" would store // false although the plugin only ever does same-day - and Pro // reads this key as its master switch, so the stored false would // silently switch Pro into multi-day mode on upgrade. Same-day is // the only mode free supports, so record it. $sanitized['same_day_only'] = true; } /** * Allow Pro plugin to add its own sanitization * * @param array $sanitized The sanitized array so far * @param array $input The raw input array */ $sanitized = apply_filters( 'pizzapilot_sanitize_pro_settings', $sanitized, $input ); return $sanitized; } /** * Get a specific setting value * * @param string $key The setting key * @param mixed $default Default value if setting doesn't exist * @return mixed The setting value */ public function get_setting( $key, $default_value = null ) { // Determine which option group the key belongs to. $general_keys = array( 'enabled' ); $delivery_keys = array( 'radius_unit', 'delivery_postcode', 'delivery_radius', 'delivery_start_time', 'delivery_end_time' ); $advanced_keys = array( 'same_day_only' ); /** * Allow Pro plugin to add its own setting keys * * @param array $advanced_keys Current advanced setting keys */ $advanced_keys = apply_filters( 'pizzapilot_advanced_setting_keys', $advanced_keys ); if ( in_array( $key, $general_keys, true ) ) { $settings = get_option( 'pizzapilot_general_settings', array() ); } elseif ( in_array( $key, $delivery_keys, true ) ) { $settings = get_option( 'pizzapilot_delivery_settings', array() ); } elseif ( in_array( $key, $advanced_keys, true ) ) { $settings = get_option( 'pizzapilot_advanced_settings', array() ); } else { return $default_value; } return isset( $settings[ $key ] ) ? $settings[ $key ] : $default_value; } /** * Section callback for the general settings section. * * Displays a description of the general settings section. * * @since 1.0.0 * @return void */ public function section_callback() { echo '

' . esc_html__( 'Configure the general settings for PizzaPilot.', 'pizzapilot' ) . '

'; } /** * Section callback for the delivery settings section. * * Displays a description of the delivery settings section. * * @since 1.0.0 * @return void */ public function delivery_section_callback() { echo '

' . esc_html__( 'Configure delivery-related settings.', 'pizzapilot' ) . '

'; } /** * Section callback for the advanced settings section. * * Displays a description of the advanced settings section. * * @since 1.0.0 * @return void */ public function advanced_section_callback() { echo '

' . esc_html__( 'Configure advanced settings and pro features.', 'pizzapilot' ) . '

'; } /** * Callback to render the Enable PizzaPilot checkbox. */ public function enabled_callback() { $option = $this->get_setting( 'enabled', true ); // Use checked() with the actual boolean value echo ''; echo ''; } /** * Callback to render the Delivery Postcode text input. */ public function delivery_postcode_callback() { $option = $this->get_setting( 'delivery_postcode', '' ); echo ''; echo ''; } /** * Callback to render the Delivery Radius text input. */ public function delivery_radius_callback() { $option = $this->get_setting( 'delivery_radius', 5 ); $units = $this->get_setting( 'radius_unit', 'km' ); echo ''; echo ''; } /** * Callback to render the Radius Unit select dropdown. */ public function radius_unit_callback() { $option = $this->get_setting( 'radius_unit', 'km' ); ?> get_setting( 'delivery_start_time', '09:30' ); echo ''; echo ''; if ( ! $pro_active ) { echo '

'; echo ''; echo esc_html__( 'Pro: Custom time slots', 'pizzapilot' ); echo ''; echo esc_html__( 'With PizzaPilot Pro, create individual time slots with custom durations, day-specific schedules, and auto-recurring patterns.', 'pizzapilot' ); echo ' '; echo wp_kses_post( $upgrade_message ); echo '

'; } } /** * Callback to render the Delivery End Time input field. * * Outputs a time input for the delivery end time setting on the Delivery tab. * Uses the value from settings or defaults to '17:30'. * * @since 1.0.0 * @return void */ public function delivery_end_time_callback() { $pro_active = Pizzapilot_Helpers::pizzapilot_is_pro_active( 'Pizzapilot_Pro' ); $upgrade_message = Pizzapilot_Helpers::pizzapilot_pro_upgrade_message(); $option = $this->get_setting( 'delivery_end_time', '17:30' ); echo ''; echo ''; if ( ! $pro_active ) { echo '

'; echo ''; echo esc_html__( 'Pro: Custom time slots', 'pizzapilot' ); echo ''; echo esc_html__( 'With PizzaPilot Pro, create individual time slots with custom durations, day-specific schedules, and auto-recurring patterns.', 'pizzapilot' ); echo ' '; echo wp_kses_post( $upgrade_message ); echo '

'; } } /** * Callback to render the Same-Day Delivery Only setting. * * With Pro active this is a checkbox. Without Pro it is a plain-text * explanation, since same-day is the only mode the free plugin supports. */ public function same_day_only_callback() { $pro_active = Pizzapilot_Helpers::pizzapilot_is_pro_active( 'Pizzapilot_Pro' ); $upgrade_message = Pizzapilot_Helpers::pizzapilot_pro_upgrade_message(); if ( $pro_active ) { $option = $this->get_setting( 'same_day_only', true ); // Use checked() with the actual boolean value echo ''; echo ''; echo '

' . esc_html__( 'Note: Changing this setting will require saving and refreshing the page to show/hide slot countdown options.', 'pizzapilot' ) . '

'; } else { // No form control here: free only supports same-day ordering, and // sanitize_advanced_settings() records that without needing a value. echo '

'; echo esc_html__( 'Orders are taken for today only. Future-date ordering is available in PizzaPilot Pro.', 'pizzapilot' ); echo ' ' . wp_kses_post( $upgrade_message ); echo '

'; } } /** * Render a plain-text Pro feature description. * * Displays a PRO badge, a short description and an upgrade link. * Text only: no form controls are rendered for Pro features. * * @since 1.1.0 * @param string $label The feature label text. * @param string $description The feature description text. * @return void */ private function render_pro_feature_preview( $label, $description ) { $upgrade_url = admin_url( 'admin.php?page=pizzapilot-upgrade' ); echo ''; echo esc_html__( 'PRO', 'pizzapilot' ); echo ' '; echo ''; echo esc_html( $label ); echo ''; echo '

'; echo esc_html( $description ) . ' '; echo ''; echo esc_html__( 'Upgrade to Pro', 'pizzapilot' ); echo ''; echo '

'; } /** * Callback to render the Slot Capacity Pro feature description. * * @since 1.1.0 * @return void */ public function pro_slot_capacity_callback() { $this->render_pro_feature_preview( __( 'Set maximum orders or pizza quantities per time slot.', 'pizzapilot' ), __( 'Slots auto-lock when capacity is reached. Separate limits for delivery vs collection.', 'pizzapilot' ) ); } /** * Callback to render the Recurring Slots Pro feature description. * * @since 1.1.0 * @return void */ public function pro_recurring_slots_callback() { $this->render_pro_feature_preview( __( 'Define slot templates that auto-generate on a schedule.', 'pizzapilot' ), __( 'Create recurring patterns like "Every Friday 5-8pm" with day-specific variations.', 'pizzapilot' ) ); } /** * Callback to render the Delivery Maps Pro feature description. * * @since 1.1.0 * @return void */ public function pro_delivery_maps_callback() { $this->render_pro_feature_preview( __( 'Interactive Mapbox maps on checkout and admin pages.', 'pizzapilot' ), __( 'Customers pin their location on a map. Supports What3Words for precise delivery.', 'pizzapilot' ) ); } /** * Get available delivery time slots between start and end times * * Generates an array of 30-minute time slots for today between the configured * delivery start and end times. Returns unix timestamps as keys and formatted * date/time strings as values. Only returns time slots that haven't elapsed * with a 20-minute buffer (slots are removed 20 minutes after they start). * * @since 1.0.0 * @return array Array of time slots with timestamp keys and formatted values */ private function get_delivery_time_slots() { $start_time = $this->get_setting( 'delivery_start_time', '09:30' ); $end_time = $this->get_setting( 'delivery_end_time', '17:30' ); $slots = array(); // Get WordPress timezone $timezone = wp_timezone(); // Create DateTime objects for today with the configured times $today = new DateTime( 'today', $timezone ); $start = DateTime::createFromFormat( 'H:i', $start_time, $timezone ); $end = DateTime::createFromFormat( 'H:i', $end_time, $timezone ); if ( false === $start || false === $end ) { return $slots; // Return empty array if invalid times } // Set the date to today for start and end times $start->setDate( $today->format( 'Y' ), $today->format( 'm' ), $today->format( 'd' ) ); $end->setDate( $today->format( 'Y' ), $today->format( 'm' ), $today->format( 'd' ) ); // Get current time in the same timezone $now = new DateTime( 'now', $timezone ); // Add 30 minutes until we reach or exceed end time $current = clone $start; while ( $current < $end ) { $slot_end = clone $current; $slot_end->modify( '+30 minutes' ); // Don't create a slot if it would go beyond the end time if ( $slot_end > $end ) { break; } // Calculate the cutoff time (20 minutes into the slot, or 10 minutes before slot ends) $slot_cutoff = clone $current; $slot_cutoff->modify( '+20 minutes' ); // Only include slots where current time hasn't passed the 20-minute cutoff if ( $now < $slot_cutoff ) { // Create unix timestamp key $timestamp = $current->getTimestamp(); // Create formatted value: 'Tuesday 24th Jun from 9:30am to 10:30am' $day_name = $current->format( 'l' ); // Tuesday $day_number = $current->format( 'jS' ); // 24th $month = $current->format( 'M' ); // Jun $start_time_formatted = $current->format( 'g:ia' ); // 9:30am $end_time_formatted = $slot_end->format( 'g:ia' ); // 10:00am $value = sprintf( '%s %s %s from %s to %s', $day_name, $day_number, $month, $start_time_formatted, $end_time_formatted ); $slots[ $timestamp ] = $value; } $current->modify( '+30 minutes' ); } return $slots; } /** * Get formatted delivery time slots for display * * Returns an array of time slots with both 24-hour and 12-hour formats * suitable for display in dropdowns or other UI elements. * * @since 1.0.0 * @return array Array of arrays with 'value' (24h) and 'label' (12h) format times */ public function get_formatted_delivery_slots() { $slots = $this->get_delivery_time_slots(); // die early if no slots are found if ( empty( $slots ) ) { return array(); } return apply_filters( 'pizzapilot_time_slots', $slots ); } }