! in_array( $weekday, [ 'sun' ], true ), 'start' => '08:00', 'end' => 'sat' === $weekday ? '14:00' : '18:00', ]; } return [ 'business_name' => get_bloginfo( 'name' ), 'business_email' => get_option( 'admin_email' ), 'business_phone' => '', 'business_address' => '', 'currency_code' => 'USD', 'currency_symbol' => '$', 'currency_position' => 'before', 'price_decimals' => 2, 'decimal_separator' => '.', 'thousand_separator' => ',', 'working_hours' => $working_hours, 'slot_interval' => 60, 'buffer_minutes' => 30, 'max_bookings_per_slot' => 1, 'min_notice_hours' => 24, 'max_advance_days' => 60, 'blocked_dates' => [], 'tax_rate' => 0, 'tax_label' => __( 'Tax', 'cleannetic' ), 'default_status' => 'pending', 'form_title' => __( 'Book your cleaning', 'cleannetic' ), 'form_subtitle' => __( 'Instant pricing. No surprises. Takes less than 60 seconds.', 'cleannetic' ), 'accent_color' => '#0D9488', 'require_terms' => false, 'terms_text' => __( 'I agree to the terms of service and cancellation policy.', 'cleannetic' ), 'success_message' => __( 'Your cleaning is booked! We have emailed your confirmation and will see you soon.', 'cleannetic' ), 'cash_title' => __( 'Pay on site', 'cleannetic' ), 'cash_description' => __( 'Pay with cash or card when your cleaner arrives.', 'cleannetic' ), 'admin_notification_email' => get_option( 'admin_email' ), 'send_customer_email' => true, 'send_admin_email' => true, 'send_status_emails' => true, 'customer_email_subject' => __( 'Your cleaning is booked — {booking_ref}', 'cleannetic' ), 'customer_email_heading' => __( 'You are all set!', 'cleannetic' ), 'customer_email_intro' => __( 'Thanks for booking with {business_name}. Here are your appointment details.', 'cleannetic' ), 'admin_email_subject' => __( 'New booking {booking_ref} — {service_name}', 'cleannetic' ), 'delete_data_on_uninstall' => false, ]; } public static function all(): array { if ( null === self::$cache ) { $stored = get_option( self::OPTION_KEY, [] ); self::$cache = array_replace( self::defaults(), is_array( $stored ) ? $stored : [] ); } return self::$cache; } public static function get( string $key, $fallback = null ) { $settings = self::all(); return array_key_exists( $key, $settings ) ? $settings[ $key ] : $fallback; } /** * Merge a partial payload into stored settings and persist. */ public static function update( array $partial_settings ): array { $merged = array_replace( self::all(), $partial_settings ); $sanitized = self::sanitize( $merged ); update_option( self::OPTION_KEY, $sanitized ); self::$cache = null; return self::all(); } public static function sanitize( array $input ): array { $defaults = self::defaults(); $sanitized = []; $text_fields = [ 'business_name', 'business_phone', 'currency_code', 'currency_symbol', 'decimal_separator', 'thousand_separator', 'tax_label', 'form_title', 'form_subtitle', 'terms_text', 'cash_title', 'customer_email_subject', 'customer_email_heading', 'admin_email_subject' ]; foreach ( $text_fields as $field ) { $sanitized[ $field ] = sanitize_text_field( (string) ( $input[ $field ] ?? $defaults[ $field ] ) ); } $textarea_fields = [ 'business_address', 'success_message', 'cash_description', 'customer_email_intro' ]; foreach ( $textarea_fields as $field ) { $sanitized[ $field ] = sanitize_textarea_field( (string) ( $input[ $field ] ?? $defaults[ $field ] ) ); } foreach ( [ 'business_email', 'admin_notification_email' ] as $field ) { $email = sanitize_email( (string) ( $input[ $field ] ?? '' ) ); $sanitized[ $field ] = is_email( $email ) ? $email : $defaults[ $field ]; } $boolean_fields = [ 'require_terms', 'send_customer_email', 'send_admin_email', 'send_status_emails', 'delete_data_on_uninstall' ]; foreach ( $boolean_fields as $field ) { $sanitized[ $field ] = ! empty( $input[ $field ] ); } $sanitized['currency_code'] = strtoupper( substr( $sanitized['currency_code'], 0, 3 ) ); $sanitized['currency_position'] = in_array( $input['currency_position'] ?? '', [ 'before', 'after', 'before_space', 'after_space' ], true ) ? $input['currency_position'] : 'before'; $sanitized['price_decimals'] = min( 4, absint( $input['price_decimals'] ?? 2 ) ); $sanitized['slot_interval'] = in_array( absint( $input['slot_interval'] ?? 60 ), [ 15, 30, 45, 60, 90, 120 ], true ) ? absint( $input['slot_interval'] ) : 60; $sanitized['buffer_minutes'] = min( 240, absint( $input['buffer_minutes'] ?? 0 ) ); $sanitized['max_bookings_per_slot'] = max( 1, min( 50, absint( $input['max_bookings_per_slot'] ?? 1 ) ) ); $sanitized['min_notice_hours'] = min( 720, absint( $input['min_notice_hours'] ?? 0 ) ); $sanitized['max_advance_days'] = max( 1, min( 365, absint( $input['max_advance_days'] ?? 60 ) ) ); $sanitized['tax_rate'] = max( 0, min( 100, round( (float) ( $input['tax_rate'] ?? 0 ), 3 ) ) ); $sanitized['default_status'] = in_array( $input['default_status'] ?? '', [ 'pending', 'confirmed' ], true ) ? $input['default_status'] : 'pending'; $sanitized['accent_color'] = sanitize_hex_color( (string) ( $input['accent_color'] ?? '' ) ) ?: $defaults['accent_color']; $sanitized['working_hours'] = self::sanitize_working_hours( (array) ( $input['working_hours'] ?? [] ) ); $sanitized['blocked_dates'] = self::sanitize_dates( (array) ( $input['blocked_dates'] ?? [] ) ); return apply_filters( 'cleannetic_sanitize_settings', $sanitized, $input ); } private static function sanitize_working_hours( array $hours ): array { $sanitized = []; foreach ( self::WEEKDAYS as $weekday ) { $day = (array) ( $hours[ $weekday ] ?? [] ); $start = self::sanitize_time( (string) ( $day['start'] ?? '' ), '08:00' ); $end = self::sanitize_time( (string) ( $day['end'] ?? '' ), '18:00' ); $sanitized[ $weekday ] = [ 'enabled' => ! empty( $day['enabled'] ) && $end > $start, 'start' => $start, 'end' => $end, ]; } return $sanitized; } public static function sanitize_time( string $time, string $fallback ): string { return preg_match( '/^([01]\d|2[0-3]):[0-5]\d$/', $time ) ? $time : $fallback; } /** * @return string[] Unique, sorted Y-m-d dates. */ public static function sanitize_dates( array $dates ): array { $valid_dates = []; foreach ( $dates as $date ) { $date = sanitize_text_field( (string) $date ); $parsed = \DateTime::createFromFormat( 'Y-m-d', $date ); if ( $parsed && $parsed->format( 'Y-m-d' ) === $date ) { $valid_dates[] = $date; } } $valid_dates = array_values( array_unique( $valid_dates ) ); sort( $valid_dates ); return $valid_dates; } /** * Currencies offered in the settings dropdown (code => symbol). */ public static function currencies(): array { return apply_filters( 'cleannetic_currencies', [ 'USD' => '$', 'EUR' => '€', 'GBP' => '£', 'CAD' => 'C$', 'AUD' => 'A$', 'NZD' => 'NZ$', 'INR' => '₹', 'BDT' => '৳', 'AED' => 'د.إ', 'SAR' => '﷼', 'ZAR' => 'R', 'SGD' => 'S$', 'CHF' => 'CHF', 'SEK' => 'kr', 'NOK' => 'kr', 'DKK' => 'kr', 'PLN' => 'zł', 'BRL' => 'R$', 'MXN' => 'MX$', 'JPY' => '¥', ] ); } /** * Human-readable names for supported currencies. */ public static function currency_names(): array { return apply_filters( 'cleannetic_currency_names', [ 'USD' => __( 'US Dollar', 'cleannetic' ), 'EUR' => __( 'Euro', 'cleannetic' ), 'GBP' => __( 'British Pound', 'cleannetic' ), 'CAD' => __( 'Canadian Dollar', 'cleannetic' ), 'AUD' => __( 'Australian Dollar', 'cleannetic' ), 'NZD' => __( 'New Zealand Dollar', 'cleannetic' ), 'INR' => __( 'Indian Rupee', 'cleannetic' ), 'BDT' => __( 'Bangladeshi Taka', 'cleannetic' ), 'AED' => __( 'UAE Dirham', 'cleannetic' ), 'SAR' => __( 'Saudi Riyal', 'cleannetic' ), 'ZAR' => __( 'South African Rand', 'cleannetic' ), 'SGD' => __( 'Singapore Dollar', 'cleannetic' ), 'CHF' => __( 'Swiss Franc', 'cleannetic' ), 'SEK' => __( 'Swedish Krona', 'cleannetic' ), 'NOK' => __( 'Norwegian Krone', 'cleannetic' ), 'DKK' => __( 'Danish Krone', 'cleannetic' ), 'PLN' => __( 'Polish Zloty', 'cleannetic' ), 'BRL' => __( 'Brazilian Real', 'cleannetic' ), 'MXN' => __( 'Mexican Peso', 'cleannetic' ), 'JPY' => __( 'Japanese Yen', 'cleannetic' ), ] ); } /** * Weekday labels keyed by the stored weekday slug. */ public static function weekday_labels(): array { return [ 'mon' => __( 'Monday', 'cleannetic' ), 'tue' => __( 'Tuesday', 'cleannetic' ), 'wed' => __( 'Wednesday', 'cleannetic' ), 'thu' => __( 'Thursday', 'cleannetic' ), 'fri' => __( 'Friday', 'cleannetic' ), 'sat' => __( 'Saturday', 'cleannetic' ), 'sun' => __( 'Sunday', 'cleannetic' ), ]; } }