Normalized field definitions. */ private $fields; /** @param array $fields */ public function __construct( array $fields ) { $this->fields = FieldConfig::normalize( $fields ); } /** * @param array $in Raw form input. * @param int|null $now Unix time (injected in tests). */ public function validate( array $in, ?int $now = null ): Result { $now = $now ?? time(); $errors = []; $data = []; // Bot checks first: when they trip, other errors are irrelevant. if ( trim( (string) ( $in['website'] ?? '' ) ) !== '' ) { $errors['spam'] = __( 'Submission could not be verified.', 'admirly' ); } // started_at is always sent by the form; a missing or too-recent value means the form was not used. $started = (int) ( $in['started_at'] ?? 0 ); if ( $started <= 0 || ( $now - $started ) < self::MIN_SECONDS ) { $errors['spam'] = __( 'Submission could not be verified.', 'admirly' ); } // Name $name = sanitize_text_field( (string) ( $in['name'] ?? '' ) ); $len = mb_strlen( $name ); if ( $len < self::NAME_MIN || $len > self::NAME_MAX ) { $errors['name'] = sprintf( /* translators: 1: minimum length, 2: maximum length */ __( 'Your name must be %1$d–%2$d characters.', 'admirly' ), self::NAME_MIN, self::NAME_MAX ); } $data['name'] = $name; // Role: required only when roles are configured. $roles = FieldConfig::slugs( $this->fields['roles'] ); $role = self::slugOrEmpty( $in['role'] ?? '' ); if ( $roles === [] ) { $role = ''; } elseif ( ! in_array( $role, $roles, true ) ) { $errors['role'] = __( 'Please tell us who you are.', 'admirly' ); } $data['role'] = $role; // Sub-field: only when enabled and the role uses it (empty roles list = all roles). $subCfg = $this->fields['sub']; $applies = $subCfg['enabled'] && ( $subCfg['roles'] === [] || in_array( $role, $subCfg['roles'], true ) ); $sub = self::slugOrEmpty( $in['sub'] ?? '' ); if ( ! $applies ) { $sub = ''; } elseif ( $sub !== '' && ! in_array( $sub, FieldConfig::slugs( $subCfg['options'] ), true ) ) { $errors['sub'] = sprintf( /* translators: %s: sub-field label, e.g. grade */ __( 'Invalid %s.', 'admirly' ), mb_strtolower( $subCfg['label'] ) ); } elseif ( $sub === '' && $subCfg['required'] ) { $errors['sub'] = sprintf( /* translators: %s: sub-field label, e.g. grade */ __( 'Please choose your %s.', 'admirly' ), mb_strtolower( $subCfg['label'] ) ); } $data['sub'] = $sub; // Rating: integer 1–5 $ratingRaw = trim( (string) ( $in['rating'] ?? '' ) ); if ( ! preg_match( '/^[1-5]$/', $ratingRaw ) ) { $errors['rating'] = __( 'Please choose a rating from 1 to 5.', 'admirly' ); $data['rating'] = 0; } else { $data['rating'] = (int) $ratingRaw; } // Text $text = sanitize_textarea_field( (string) ( $in['text'] ?? '' ) ); $text = preg_replace( '/[ \t]+/', ' ', $text ) ?? $text; $tlen = mb_strlen( $text ); if ( $tlen < self::TEXT_MIN || $tlen > self::TEXT_MAX ) { $errors['text'] = sprintf( /* translators: 1: minimum length, 2: maximum length */ __( 'Your review must be %1$d–%2$d characters.', 'admirly' ), self::TEXT_MIN, self::TEXT_MAX ); } $data['text'] = $text; // Consent $consent = ! empty( $in['consent'] ); $data['consent'] = $consent; if ( ! $consent ) { $errors['consent'] = __( 'You must accept the privacy policy to continue.', 'admirly' ); } return new Result( $errors, $data ); } /** Normalizes a submitted option value to a slug; '' stays ''. @param mixed $value */ public static function slugOrEmpty( $value ): string { $v = trim( (string) $value ); return $v === '' ? '' : FieldConfig::slugify( $v ); } }