field; } /** * Returns the template rendering this field type. */ public function template(): string { $templates = array( FieldTypes::TEXT => 'input', FieldTypes::EMAIL => 'input', FieldTypes::URL => 'input', FieldTypes::PHONE => 'input', FieldTypes::NUMBER => 'input', FieldTypes::DATE => 'input', FieldTypes::HIDDEN => 'hidden', FieldTypes::TEXTAREA => 'textarea', FieldTypes::SELECT => 'select', FieldTypes::CHECKBOX => 'checkbox', FieldTypes::RADIO => 'radio', FieldTypes::FILE => 'file', ); $template = $templates[ $this->field->type() ] ?? 'input'; /** * Filters the template used to render a field type. * * @since 1.0.0 * * @param string $template Template name inside frontend/fields. * @param Field $field Field being rendered. */ $template = (string) apply_filters( 'formpilot_field_template', $template, $this->field ); return 'frontend/fields/' . $template; } /** * Returns the HTML input type of the control. */ public function input_type(): string { $types = array( FieldTypes::EMAIL => 'email', FieldTypes::URL => 'url', FieldTypes::PHONE => 'tel', FieldTypes::NUMBER => 'number', FieldTypes::DATE => 'date', FieldTypes::FILE => 'file', FieldTypes::HIDDEN => 'hidden', ); return $types[ $this->field->type() ] ?? 'text'; } /** * Returns the input name. * * @param bool $multiple Optional. Whether the control submits several values. Default false. */ public function name( bool $multiple = false ): string { $name = self::NAME_PREFIX . '[' . $this->field->field_key() . ']'; return $multiple ? $name . '[]' : $name; } /** * Returns the control id. */ public function id(): string { return $this->form_uid . '-' . $this->field->field_key(); } /** * Returns the id of a single choice control. * * @param int $position Zero based option position. */ public function option_id( int $position ): string { return $this->id() . '-' . $position; } /** * Returns the id of the help text. * * @return string The id attribute, or an empty string without a description. */ public function description_id(): string { return '' === $this->field->description() ? '' : $this->id() . '-description'; } /** * Returns the field key. * * Printed on the wrapper so the browser can index a field by the same key * the server uses. */ public function key(): string { return $this->field->field_key(); } /** * Returns the conditional logic as JSON for the browser. * * The same rule the server will re-evaluate on submit. Sending it is a * convenience for the visitor, never a decision: the server ignores whatever * the browser concluded. * * @return string The JSON payload, or an empty string when no rule applies. */ public function logic(): string { $rule = $this->field->logic(); if ( ! $rule->is_active() ) { return ''; } return (string) wp_json_encode( array( 'action' => $rule->action(), 'rules' => $rule->conditions()->to_array(), ) ); } /** * Returns the wrapper classes. */ public function wrapper_class(): string { $classes = array( 'formpilot-field', 'formpilot-field--' . $this->field->type(), ); if ( $this->field->is_required() ) { $classes[] = 'formpilot-field--required'; } $custom = (string) $this->field->setting( 'css_class', '' ); if ( '' !== $custom ) { foreach ( preg_split( '/\s+/', $custom ) ?: array() as $class ) { $classes[] = (string) $class; } } return implode( ' ', array_filter( array_map( 'sanitize_html_class', $classes ) ) ); } /** * Determines whether the field must be filled in. * * @return bool True when required. */ public function is_required(): bool { return $this->field->is_required(); } /** * Determines whether the control accepts several values. */ public function is_multiple(): bool { if ( FieldTypes::CHECKBOX === $this->field->type() ) { return true; } return FieldTypes::SELECT === $this->field->type() && (bool) $this->field->setting( 'multiple', false ); } /** * Builds the attributes of the main control. * * The values are not escaped; the `frontend/fields/attributes` template * prints them and escapes every name and value. * * @param array $extra Optional. Additional attributes. Default empty. * * @return array Attribute values keyed by name. */ public function attributes( array $extra = array() ): array { $attributes = array( 'id' => $this->id(), 'name' => $this->name( $this->is_multiple() ), 'class' => 'formpilot-input formpilot-input--' . $this->field->type(), ); if ( '' !== $this->field->placeholder() ) { $attributes['placeholder'] = $this->field->placeholder(); } if ( $this->is_required() ) { $attributes['required'] = true; $attributes['aria-required'] = 'true'; } if ( '' !== $this->description_id() ) { $attributes['aria-describedby'] = $this->description_id(); } foreach ( array( 'min', 'max', 'step' ) as $setting ) { $value = $this->field->setting( $setting, '' ); if ( '' !== $value && is_numeric( $value ) ) { $attributes[ $setting ] = (string) $value; } } /* * A hint, not a control: `LengthRule` is what actually enforces this, and * it runs whatever the browser did. Emitting it means the visitor finds out * while typing instead of after a round trip. */ $max_length = (int) $this->field->setting( 'max_length', 0 ); if ( $max_length > 0 ) { $attributes['maxlength'] = (string) $max_length; } $min_length = (int) $this->field->setting( 'min_length', 0 ); if ( $min_length > 0 ) { $attributes['minlength'] = (string) $min_length; } foreach ( array( 'min_date' => 'min', 'max_date' => 'max' ) as $setting => $attribute ) { $value = (string) $this->field->setting( $setting, '' ); if ( FieldTypes::DATE === $this->field->type() && '' !== $value ) { $attributes[ $attribute ] = $value; } } if ( FieldTypes::FILE === $this->field->type() ) { $accept = $this->accept(); if ( '' !== $accept ) { $attributes['accept'] = $accept; } $max_size = (int) $this->field->setting( 'max_size', 0 ); if ( $max_size > 0 ) { $attributes['data-max-size'] = (string) $max_size; } } if ( $this->is_multiple() && FieldTypes::SELECT === $this->field->type() ) { $attributes['multiple'] = true; } $attributes = array_merge( $attributes, $extra ); /** * Filters the attributes of a rendered control. * * @since 1.0.0 * * @param array $attributes Attribute values keyed by name. * @param Field $field Field being rendered. */ $attributes = (array) apply_filters( 'formpilot_field_attributes', $attributes, $this->field ); return Escape::attribute_map( $attributes ); } /** * Returns the accept attribute of an upload field. */ private function accept(): string { $extensions = (array) $this->field->setting( 'allowed_types', array() ); $extensions = array_filter( array_map( static fn( $extension ): string => '.' . ltrim( strtolower( (string) $extension ), '.' ), $extensions ), static fn( string $extension ): bool => '.' !== $extension ); return implode( ',', $extensions ); } }