> */ private $fields = array(); /** * Adds a field. * * @param array $field Definition. * @return ParamSchema */ public function add( array $field ) { $field = wp_parse_args( $field, array( 'type' => 'text', 'label' => $field['key'], 'help' => '', 'required' => false, 'default' => null, ) ); $this->fields[] = $field; return $this; } /** * Field definitions. * * @return array> */ public function fields() { return $this->fields; } /** * REST form. * * @return array{fields: array>} */ public function to_array() { return array( 'fields' => $this->fields ); } /** * Validates and normalises params. Unknown keys are dropped. * * @param array $params Raw params. * @return ValidationResult */ public function validate( array $params ) { $result = new ValidationResult(); $clean = array(); foreach ( $this->fields as $field ) { $key = $field['key']; if ( ! empty( $field['show_if'] ) ) { $dep = $field['show_if']; $dep_value = isset( $clean[ $dep['key'] ] ) ? $clean[ $dep['key'] ] : ( isset( $params[ $dep['key'] ] ) ? $params[ $dep['key'] ] : null ); $wanted = (array) $dep['value']; if ( ! in_array( $dep_value, $wanted, true ) && ! in_array( (string) $dep_value, array_map( 'strval', $wanted ), true ) ) { continue; // Field not applicable; skip validation and omit. } } $present = array_key_exists( $key, $params ) && null !== $params[ $key ] && '' !== $params[ $key ]; if ( ! $present ) { if ( $field['required'] && null === $field['default'] ) { $result->add_error( $key, sprintf( /* translators: %s: field label */ __( '%s is required.', 'recast-subscriptions' ), $field['label'] ) ); } elseif ( null !== $field['default'] ) { $clean[ $key ] = $field['default']; } continue; } $value = $params[ $key ]; $error = null; $clean[ $key ] = $this->normalise( $field, $value, $error ); if ( null !== $error ) { $result->add_error( $key, $error ); unset( $clean[ $key ] ); } } $result->set_params( $clean ); return $result; } /** * Normalises one value per its type. * * @param array $field Field. * @param mixed $value Raw value. * @param string|null $error Error message (by reference). * @return mixed */ private function normalise( array $field, $value, &$error ) { $label = $field['label']; switch ( $field['type'] ) { case 'select': $allowed = wp_list_pluck( isset( $field['options'] ) ? $field['options'] : array(), 'value' ); if ( ! in_array( $value, $allowed, true ) && ! in_array( (string) $value, array_map( 'strval', $allowed ), true ) ) { /* translators: %s: field label */ $error = sprintf( __( '%s has an invalid choice.', 'recast-subscriptions' ), $label ); return null; } return is_numeric( $value ) && in_array( $value + 0, $allowed, true ) ? $value + 0 : (string) $value; case 'multiselect': $allowed = array_map( 'strval', wp_list_pluck( isset( $field['options'] ) ? $field['options'] : array(), 'value' ) ); $values = array_map( 'strval', (array) $value ); foreach ( $values as $v ) { if ( ! in_array( $v, $allowed, true ) ) { /* translators: %s: field label */ $error = sprintf( __( '%s has an invalid choice.', 'recast-subscriptions' ), $label ); return null; } } return array_values( array_unique( $values ) ); case 'number': case 'integer': if ( ! is_numeric( $value ) ) { /* translators: %s: field label */ $error = sprintf( __( '%s must be a number.', 'recast-subscriptions' ), $label ); return null; } $number = 'integer' === $field['type'] ? (int) $value : (float) $value; if ( isset( $field['min'] ) && $number < $field['min'] ) { /* translators: 1: field label, 2: minimum */ $error = sprintf( __( '%1$s must be at least %2$s.', 'recast-subscriptions' ), $label, $field['min'] ); return null; } if ( isset( $field['max'] ) && $number > $field['max'] ) { /* translators: 1: field label, 2: maximum */ $error = sprintf( __( '%1$s must be at most %2$s.', 'recast-subscriptions' ), $label, $field['max'] ); return null; } return $number; case 'bool': return in_array( $value, array( true, 1, '1', 'true', 'yes', 'on' ), true ); case 'date': $dt = is_string( $value ) ? \DateTimeImmutable::createFromFormat( '!Y-m-d', $value ) : false; if ( ! $dt || $dt->format( 'Y-m-d' ) !== $value ) { /* translators: %s: field label */ $error = sprintf( __( '%s must be a date (YYYY-MM-DD).', 'recast-subscriptions' ), $label ); return null; } return $value; case 'datetime': $dt = is_string( $value ) ? \DateTimeImmutable::createFromFormat( 'Y-m-d H:i', $value ) : false; if ( ! $dt || $dt->format( 'Y-m-d H:i' ) !== $value ) { /* translators: %s: field label */ $error = sprintf( __( '%s must be a date and time (YYYY-MM-DD HH:MM).', 'recast-subscriptions' ), $label ); return null; } return $value; case 'product': if ( ! is_numeric( $value ) || (int) $value <= 0 ) { /* translators: %s: field label */ $error = sprintf( __( '%s must be a product.', 'recast-subscriptions' ), $label ); return null; } return (int) $value; case 'product_ids': case 'variation_ids': case 'int_list': $ids = array(); foreach ( (array) $value as $id ) { if ( ! is_numeric( $id ) || (int) $id <= 0 ) { /* translators: %s: field label */ $error = sprintf( __( '%s must contain positive IDs.', 'recast-subscriptions' ), $label ); return null; } $ids[] = (int) $id; } $ids = array_values( array_unique( $ids ) ); sort( $ids ); return $ids; case 'text': default: if ( ! is_scalar( $value ) ) { /* translators: %s: field label */ $error = sprintf( __( '%s must be text.', 'recast-subscriptions' ), $label ); return null; } $text = sanitize_text_field( (string) $value ); if ( isset( $field['pattern'] ) && ! preg_match( $field['pattern'], $text ) ) { /* translators: %s: field label */ $error = sprintf( __( '%s has an invalid format.', 'recast-subscriptions' ), $label ); return null; } return $text; } } }