diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index 50afb3fdb..117833160 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -26,6 +26,7 @@ use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Settings\Endpoint\StylingRestEndpoint; use WooCommerce\PayPalCommerce\Settings\Data\StylingSettings; +use WooCommerce\PayPalCommerce\Settings\Service\DataSanitizer; return array( 'settings.url' => static function ( ContainerInterface $container ) : string { @@ -76,7 +77,10 @@ return array( return new CommonRestEndpoint( $container->get( 'settings.data.general' ) ); }, 'settings.rest.styling' => static function ( ContainerInterface $container ) : StylingRestEndpoint { - return new StylingRestEndpoint( $container->get( 'settings.data.styling' ) ); + return new StylingRestEndpoint( + $container->get( 'settings.data.styling' ), + $container->get( 'settings.service.sanitizer' ) + ); }, 'settings.rest.refresh_feature_status' => static function ( ContainerInterface $container ) : RefreshFeatureStatusEndpoint { return new RefreshFeatureStatusEndpoint( @@ -195,6 +199,9 @@ return array( $container->get( 'woocommerce.logger.woocommerce' ), ); }, + 'settings.service.sanitizer' => static function ( ContainerInterface $container ) : DataSanitizer { + return new DataSanitizer(); + }, 'settings.ajax.switch_ui' => static function ( ContainerInterface $container ) : SwitchSettingsUiEndpoint { return new SwitchSettingsUiEndpoint( $container->get( 'woocommerce.logger.woocommerce' ), diff --git a/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php index 8ad08f631..450549e43 100644 --- a/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php @@ -14,6 +14,7 @@ use WP_REST_Response; use WP_REST_Request; use WooCommerce\PayPalCommerce\Settings\Data\StylingSettings; use WooCommerce\PayPalCommerce\Settings\DTO\LocationStylingDTO; +use WooCommerce\PayPalCommerce\Settings\Service\DataSanitizer; /** * REST controller for the "Styling" settings tab. @@ -36,6 +37,13 @@ class StylingRestEndpoint extends RestEndpoint { */ protected StylingSettings $settings; + /** + * Data sanitizer service. + * + * @var DataSanitizer + */ + protected DataSanitizer $sanitizer; + /** * Field mapping for request to profile transformation. * @@ -62,16 +70,33 @@ class StylingRestEndpoint extends RestEndpoint { /** * Constructor. * - * @param StylingSettings $settings The settings instance. + * @param StylingSettings $settings The settings instance. + * @param DataSanitizer $sanitizer Data sanitizer service. */ - public function __construct( StylingSettings $settings ) { - $this->settings = $settings; + public function __construct( StylingSettings $settings, DataSanitizer $sanitizer ) { + $this->settings = $settings; + $this->sanitizer = $sanitizer; - $this->field_map['cart']['sanitize'] = array( $this, 'to_location' ); - $this->field_map['classic_checkout']['sanitize'] = array( $this, 'to_location' ); - $this->field_map['express_checkout']['sanitize'] = array( $this, 'to_location' ); - $this->field_map['mini_cart']['sanitize'] = array( $this, 'to_location' ); - $this->field_map['product']['sanitize'] = array( $this, 'to_location' ); + $this->field_map['cart']['sanitize'] = array( + $this->sanitizer, + 'sanitize_location_style', + ); + $this->field_map['classic_checkout']['sanitize'] = array( + $this->sanitizer, + 'sanitize_location_style', + ); + $this->field_map['express_checkout']['sanitize'] = array( + $this->sanitizer, + 'sanitize_location_style', + ); + $this->field_map['mini_cart']['sanitize'] = array( + $this->sanitizer, + 'sanitize_location_style', + ); + $this->field_map['product']['sanitize'] = array( + $this->sanitizer, + 'sanitize_location_style', + ); } /** @@ -142,37 +167,4 @@ class StylingRestEndpoint extends RestEndpoint { return $this->get_details(); } - - /** - * Converts the plain location-style input to a structured DTO. - * - * @param array $data Raw data received from the request. - * @param string $key The field name. - * - * @return LocationStylingDTO - */ - protected function to_location( array $data, string $key ) : LocationStylingDTO { - $is_enabled = ! isset( $data['enabled'] ) || $data['enabled']; - $methods = array(); - $shape = sanitize_text_field( $data['shape'] ?? 'rect' ); - $label = sanitize_text_field( $data['label'] ?? 'pay' ); - $color = sanitize_text_field( $data['color'] ?? 'gold' ); - $layout = sanitize_text_field( $data['layout'] ?? 'vertical' ); - $tagline = isset( $data['tagline'] ) && $data['tagline']; - - if ( isset( $data['methods'] ) && is_array( $data['methods'] ) ) { - $methods = array_map( 'sanitize_text_field', $data['methods'] ); - } - - return new LocationStylingDTO( - $key, - $is_enabled, - $methods, - $shape, - $label, - $color, - $layout, - $tagline, - ); - } } diff --git a/modules/ppcp-settings/src/Service/DataSanitizer.php b/modules/ppcp-settings/src/Service/DataSanitizer.php new file mode 100644 index 000000000..d7dccbbce --- /dev/null +++ b/modules/ppcp-settings/src/Service/DataSanitizer.php @@ -0,0 +1,89 @@ +sanitize_bool( $data['enabled'] ?? true ); + $shape = $this->sanitize_text( $data['shape'] ?? 'rect' ); + $label = $this->sanitize_text( $data['label'] ?? 'pay' ); + $color = $this->sanitize_text( $data['color'] ?? 'gold' ); + $layout = $this->sanitize_text( $data['layout'] ?? 'vertical' ); + $tagline = $this->sanitize_bool( $data['tagline'] ?? false ); + $methods = $this->sanitize_array( + $data['methods'] ?? array(), + array( $this, 'sanitize_text' ) + ); + + return new LocationStylingDTO( + $location, + $is_enabled, + $methods, + $shape, + $label, + $color, + $layout, + $tagline + ); + } + + /** + * Helper. Ensures the value is a string. + * + * @param mixed $value Value to sanitize. + * @param string $default Default value. + * @return string Sanitized string. + */ + protected function sanitize_text( $value, string $default = '' ) : string { + return sanitize_text_field( $value ?? $default ); + } + + /** + * Helper. Ensures the value is a boolean. + * + * @param mixed $value Value to sanitize. + * @return bool Sanitized boolean. + */ + protected function sanitize_bool( $value ) : bool { + return filter_var( $value, FILTER_VALIDATE_BOOLEAN ); + } + + /** + * Helper. Ensures the value is an array and all items are sanitized. + * + * @param null|array $array Value to sanitize. + * @param callable $sanitize_callback Callback to sanitize each item in the array. + * @return array Array with sanitized items. + */ + protected function sanitize_array( ?array $array, callable $sanitize_callback ) : array { + if ( ! is_array( $array ) ) { + return array(); + } + + return array_map( $sanitize_callback, $array ); + } +}