*/ public function get_all(): array { $saved = get_option( self::OPTION_NAME, array() ); if ( ! is_array( $saved ) ) { $saved = array(); } return wp_parse_args( $saved, self::defaults() ); } /** * Save settings. * * @param array $settings Settings. */ public function save( array $settings ): bool { $current = $this->get_all(); $merged = wp_parse_args( $settings, $current ); $stored = get_option( self::OPTION_NAME, array() ); return update_option( self::OPTION_NAME, $merged, false ) || $stored === $merged; } /** * Save WooCommerce status automation settings. * * @param array $statuses Statuses by recipient type. */ public function save_wc_statuses( array $statuses ): bool { $current = $this->get_all(); $current['wc_statuses'] = array( 'admin' => array_values( array_unique( array_map( 'sanitize_key', $statuses['admin'] ?? array() ) ) ), 'customer' => array_values( array_unique( array_map( 'sanitize_key', $statuses['customer'] ?? array() ) ) ), ); return $this->save( $current ); } /** * Save active WooCommerce template scheme for a recipient/status pair. * * @param string $recipient_type Recipient type. * @param string $status Status key. * @param string $scheme Scheme. */ public function save_wc_template_scheme( string $recipient_type, string $status, string $scheme ): bool { if ( ! in_array( $recipient_type, array( 'admin', 'customer' ), true ) ) { return false; } if ( ! in_array( $scheme, array( 'sms', 'viber', 'viber_sms' ), true ) ) { return false; } $current = $this->get_all(); $map = isset( $current['wc_template_schemes'] ) && is_array( $current['wc_template_schemes'] ) ? $current['wc_template_schemes'] : array(); if ( ! isset( $map[ $recipient_type ] ) || ! is_array( $map[ $recipient_type ] ) ) { $map[ $recipient_type ] = array(); } $map[ $recipient_type ][ sanitize_key( $status ) ] = $scheme; $current['wc_template_schemes'] = $map; return $this->save( $current ); } /** * Get active WooCommerce template scheme for a recipient/status pair. * * @param string $recipient_type Recipient type. * @param string $status Status key. */ public function get_wc_template_scheme( string $recipient_type, string $status ): string { $settings = $this->get_all(); $map = isset( $settings['wc_template_schemes'] ) && is_array( $settings['wc_template_schemes'] ) ? $settings['wc_template_schemes'] : array(); if ( isset( $map[ $recipient_type ] ) && is_array( $map[ $recipient_type ] ) && isset( $map[ $recipient_type ][ $status ] ) && in_array( $map[ $recipient_type ][ $status ], array( 'sms', 'viber', 'viber_sms' ), true ) ) { return (string) $map[ $recipient_type ][ $status ]; } return 'sms'; } /** * Get enabled WooCommerce statuses by recipient type. * * @return array{admin:string[],customer:string[]} */ public function get_wc_statuses(): array { $settings = $this->get_all(); $statuses = isset( $settings['wc_statuses'] ) && is_array( $settings['wc_statuses'] ) ? $settings['wc_statuses'] : array(); return array( 'admin' => isset( $statuses['admin'] ) && is_array( $statuses['admin'] ) ? $statuses['admin'] : array(), 'customer' => isset( $statuses['customer'] ) && is_array( $statuses['customer'] ) ? $statuses['customer'] : array(), ); } /** * Sanitize settings input. * * @param array $input Raw input. * @param array $current Current settings. * @return array{settings: array, errors: string[]} */ public function sanitize( array $input, array $current ): array { $errors = array(); $settings = array(); $api_key = isset( $input['api_key'] ) ? sanitize_text_field( wp_unslash( (string) $input['api_key'] ) ) : ''; if ( '' !== $api_key && ! self::is_masked_api_key( $api_key ) ) { if ( ! preg_match( '/^[A-Za-z0-9._:-]{16,128}$/', $api_key ) ) { $errors[] = __( 'API key format is invalid.', 'inteltele' ); } else { $settings['api_key'] = $api_key; } } else { $settings['api_key'] = (string) $current['api_key']; } $settings['sms_sender'] = self::sanitize_sender( $input['sms_sender'] ?? $current['sms_sender'] ?? self::defaults()['sms_sender'], 'woocom', __( 'SMS sender must be 3-11 latin letters/digits or 3-15 digits.', 'inteltele' ), $errors, true ); $settings['viber_sender'] = self::sanitize_sender( $input['viber_sender'] ?? $current['viber_sender'] ?? self::defaults()['viber_sender'], 'woocomvib', __( 'Viber sender must contain 3 to 32 latin letters, digits, spaces, dots, underscores or hyphens.', 'inteltele' ), $errors, false ); $settings['balance'] = isset( $current['balance'] ) ? sanitize_text_field( (string) $current['balance'] ) : ''; $manager_phones = isset( $input['manager_phones'] ) && is_array( $input['manager_phones'] ) ? $input['manager_phones'] : array(); $phones = self::sanitize_phones( $manager_phones, $errors ); $settings['manager_phones'] = $phones; $settings['wc_statuses'] = isset( $current['wc_statuses'] ) && is_array( $current['wc_statuses'] ) ? $current['wc_statuses'] : self::defaults()['wc_statuses']; $settings['wc_template_schemes'] = isset( $current['wc_template_schemes'] ) && is_array( $current['wc_template_schemes'] ) ? $current['wc_template_schemes'] : self::defaults()['wc_template_schemes']; $settings['viber_ttl'] = self::sanitize_integer_range( $input['viber_ttl'] ?? self::defaults()['viber_ttl'], 600, 86400, self::defaults()['viber_ttl'], __( 'Viber TTL must be between 600 and 86400 seconds.', 'inteltele' ), $errors ); $settings['batch_size'] = self::sanitize_integer_range( $input['batch_size'] ?? self::defaults()['batch_size'], 1, 200, self::defaults()['batch_size'], __( 'Bulk campaign batch size must be between 1 and 200.', 'inteltele' ), $errors ); return array( 'settings' => $settings, 'errors' => $errors, ); } /** * Get masked API key. * * @param string $api_key API key. */ public static function mask_api_key( string $api_key ): string { $length = strlen( $api_key ); if ( 0 === $length ) { return ''; } if ( $length <= 8 ) { return str_repeat( '*', $length ); } return substr( $api_key, 0, 4 ) . str_repeat( '*', max( 4, $length - 8 ) ) . substr( $api_key, -4 ); } /** * Check if API key input is a mask. * * @param string $api_key API key input. */ private static function is_masked_api_key( string $api_key ): bool { return false !== strpos( $api_key, '*' ); } /** * Sanitize phone list. * * @param array $raw_phones Raw phone values. * @param string[] $errors Errors. * @return string[] */ private static function sanitize_phones( array $raw_phones, array &$errors ): array { $phones = array(); foreach ( $raw_phones as $raw_phone ) { $phone = preg_replace( '/\D+/', '', (string) wp_unslash( $raw_phone ) ); if ( '' === $phone ) { continue; } if ( strlen( $phone ) < 10 || strlen( $phone ) > 15 ) { $errors[] = sprintf( /* translators: %s: phone number. */ __( 'Phone number %s must contain 10 to 15 digits.', 'inteltele' ), $phone ); continue; } $phones[] = $phone; } $phones = array_values( array_unique( $phones ) ); if ( count( $phones ) > 15 ) { $errors[] = __( 'You can add up to 15 manager phone numbers.', 'inteltele' ); $phones = array_slice( $phones, 0, 15 ); } return $phones; } /** * Sanitize an integer setting with bounds. * * @param mixed $value Raw value. * @param int $min Minimum. * @param int $max Maximum. * @param int $default Default. * @param string $error_message Error message. * @param string[] $errors Errors. */ private static function sanitize_integer_range( $value, int $min, int $max, int $default, string $error_message, array &$errors ): int { $value = absint( $value ); if ( $value < $min || $value > $max ) { $errors[] = $error_message; return $default; } return $value; } /** * Sanitize sender names. * * @param mixed $value Raw value. * @param string $default Default sender. * @param string $error_message Error message. * @param string[] $errors Errors. * @param bool $sms_rules Whether to apply SMS sender rules. */ private static function sanitize_sender( $value, string $default, string $error_message, array &$errors, bool $sms_rules ): string { $sender = sanitize_text_field( wp_unslash( (string) $value ) ); $sender = trim( $sender ); if ( '' === $sender ) { $errors[] = $error_message; return $default; } if ( $sms_rules ) { if ( preg_match( '/^\d{3,15}$/', $sender ) || preg_match( '/^[A-Za-z0-9]{3,11}$/', $sender ) ) { return $sender; } $errors[] = $error_message; return $default; } if ( preg_match( '/^[A-Za-z0-9._ -]{3,32}$/', $sender ) ) { return $sender; } $errors[] = $error_message; return $default; } /** * Defaults. * * @return array */ private static function defaults(): array { return array( 'api_key' => '', 'balance' => '', 'sms_sender' => 'woocom', 'viber_sender' => 'woocomvib', 'manager_phones' => array(), 'wc_statuses' => array( 'admin' => array(), 'customer' => array(), ), 'wc_template_schemes' => array( 'admin' => array(), 'customer' => array(), ), 'viber_ttl' => 600, 'batch_size' => 20, ); } }