Base class representing an element type.
Description
Source
File: src/db-objects/elements/element-types/element-type.php
abstract class Element_Type { /** * The element type slug. Must match the slug when registering the element type. * * @since 1.0.0 * @var string */ protected $slug = ''; /** * The element type title. * * @since 1.0.0 * @var string */ protected $title = ''; /** * The element type description. * * @since 1.0.0 * @var string */ protected $description = ''; /** * The element type icon CSS class. * * @since 1.0.0 * @var string */ protected $icon_css_class = ''; /** * The element type icon SVG ID. * * @since 1.0.0 * @var string */ protected $icon_svg_id = ''; /** * The element type icon URL. * * @since 1.0.0 * @var string */ protected $icon_url = ''; /** * The element type settings sections. * * @since 1.0.0 * @var array */ protected $settings_sections = array(); /** * The element type settings fields. * * @since 1.0.0 * @var array */ protected $settings_fields = array(); /** * The element type manager instance. * * @since 1.0.0 * @var Element_Type_Manager */ protected $manager; /** * Constructor. * * @since 1.0.0 * * @param Element_Type_Manager $manager The element type manager instance. */ public function __construct( $manager ) { $this->manager = $manager; $this->settings_sections = array( 'content' => array( 'title' => _x( 'Content', 'element type section', 'torro-forms' ), ), 'settings' => array( 'title' => _x( 'Settings', 'element type section', 'torro-forms' ), ), ); $this->settings_fields = array( 'label' => array( 'section' => 'content', 'type' => 'text', 'label' => __( 'Label', 'torro-forms' ), 'description' => __( 'Enter the form field label.', 'torro-forms' ), 'input_classes' => array( 'regular-text' ), 'is_label' => true, ), ); $this->bootstrap(); $this->sanitize_settings_sections(); $this->sanitize_settings_fields(); } /** * Returns the element type slug. * * @since 1.0.0 * * @return string Element type slug. */ public function get_slug() { return $this->slug; } /** * Returns the element type title. * * @since 1.0.0 * * @return string Element type title. */ public function get_title() { return $this->title; } /** * Returns the element type description. * * @since 1.0.0 * * @return string Element type description. */ public function get_description() { return $this->description; } /** * Returns the element type icon CSS class. * * @since 1.0.0 * * @return string Element type icon CSS class. */ public function get_icon_css_class() { return $this->icon_css_class; } /** * Returns the element type icon SVG ID. * * @since 1.0.0 * * @return string Element type icon SVG ID. */ public function get_icon_svg_id() { return $this->icon_svg_id; } /** * Returns the element type icon URL. * * @since 1.0.0 * * @return string Element type icon URL. */ public function get_icon_url() { return $this->icon_url; } /** * Returns the element type settings sections. * * @since 1.0.0 * * @return array Element type settings sections. */ public function get_settings_sections() { return $this->settings_sections; } /** * Returns the element type settings fields. * * @since 1.0.0 * * @return array Element type settings fields. */ public function get_settings_fields() { return $this->settings_fields; } /** * Returns the available settings. * * @since 1.0.0 * * @param Element $element Element to get settings for. * @return array Associative array of `$setting_name => $setting_value` pairs. */ public function get_settings( $element ) { $settings = array(); $element_settings = $element->get_element_settings(); foreach ( $element_settings as $element_setting ) { $settings[ $element_setting->name ] = $element_setting->value; } return $settings; } /** * Returns the current values for the element fields, optionally for a specific submission. * * @since 1.0.0 * * @param Element $element The element object to get values for. * @param Submission|null $submission Optional. Submission to get the values from, if available. Default null. * @return array Associative array of `$field => $value` pairs, with the main element field having the key '_main'. */ public function get_values( $element, $submission = null ) { $values = array(); if ( $submission ) { $all_values = $submission->get_element_values_data(); if ( isset( $all_values[ $element->id ] ) ) { $values = $all_values[ $element->id ]; } } if ( has_filter( "{$this->manager->get_prefix()}allow_get_params" ) && isset( $_GET[ 'torro_input_value_' . $element->id ] ) && ( is_array( $_GET[ 'torro_input_value_' . $element->id ] ) || empty( $values['_main'] ) ) ) { $container = $element->get_container(); if ( $container ) { $form = $container->get_form(); if ( $form ) { /** * Filters whether to allow GET parameters to pre-populate form element values. * * @since 1.0.0 * * @param bool $allow_get_paramss Whether to allow GET parameters. Default false. * @param int $element_id Element ID for which GET parameters are being checked. * @param int $form_id Form ID the element is part of. */ $allow_get_params = apply_filters( "{$this->manager->get_prefix()}allow_get_params", false, $element->id, $form->id ); if ( $allow_get_params ) { $choices = is_a( $this, Choice_Element_Type_Interface::class ) ? $this->get_choices( $element ) : array(); $get_params = wp_unslash( $_GET[ 'torro_input_value_' . $element->id ] ); if ( is_array( $get_params ) ) { foreach ( $get_params as $field => $value ) { if ( empty( $values[ $field ] ) ) { if ( ! empty( $choices[ $field ] ) ) { if ( isset( $choices[ $field ][ $value ] ) ) { $values[ $field ] = $choices[ $field ][ $value ]; } elseif ( in_array( $value, $choices[ $field ], true ) ) { $values[ $field ] = $value; } continue; } $values[ $field ] = $value; } } } elseif ( empty( $values['_main'] ) ) { if ( ! empty( $choices['_main'] ) ) { if ( isset( $choices['_main'][ $get_params ] ) ) { $values['_main'] = $choices['_main'][ $get_params ]; } elseif ( in_array( $get_params, $choices['_main'], true ) ) { $values[ $field ] = $get_params; } } else { $values['_main'] = $get_params; } } } } } } return $values; } /** * Formats values for an export. * * @since 1.0.0 * * @param array $values Associative array of `$field => $value` pairs, with the main element field having the key '_main'. * @param Element $element Element the values belong to. * @param string $export_format Export format identifier. May be 'xls', 'csv', 'json', 'xml' or 'html'. * @return array Associative array of `$column_slug => $column_value` pairs. The number of items and the column slugs * must match those returned from the get_export_columns() method. */ public function format_values_for_export( $values, $element, $export_format ) { if ( is_a( $this, Choice_Element_Type_Interface::class ) && ! $this->use_single_export_column_for_choices( $element ) ) { $value = isset( $values['_main'] ) ? (array) $values['_main'] : array(); $yes_no = $this->get_export_column_choices_yes_no( $element ); $columns = array(); foreach ( $element->get_element_choices() as $element_choice ) { $choice_slug = sanitize_title( $element_choice->value ); $columns[ 'element_' . $element->id . '__main_' . $choice_slug ] = in_array( $element_choice->value, $value ) ? $yes_no[0] : $yes_no[1]; } return $columns; } $value = isset( $values['_main'] ) ? $values['_main'] : ''; if ( is_array( $value ) ) { $value = implode( ', ', $value ); } return array( 'element_' . $element->id . '__main' => $this->escape_single_value_for_export( $value, $export_format ), ); } /** * Gets the columns required for an export. * * @since 1.0.0 * * @param Element $element Element to export columns for. * @return array Associative array of `$column_slug => $column_label` pairs. */ public function get_export_columns( $element ) { if ( is_a( $this, Choice_Element_Type_Interface::class ) && ! $this->use_single_export_column_for_choices( $element ) ) { $columns = array(); foreach ( $element->get_element_choices() as $element_choice ) { $choice_slug = sanitize_title( $element_choice->value ); $columns[ 'element_' . $element->id . '__main_' . $choice_slug ] = $element->label . ' - ' . $element_choice->value; } return $columns; } return array( 'element_' . $element->id . '__main' => $element->label, ); } /** * Filters the array representation of a given element of this type. * * @since 1.0.0 * * @param array $data Element data to filter. * @param Element $element The element object to get the data for. * @param Submission|null $submission Optional. Submission to get the values from, if available. Default null. * @return array Array including all information for the element type. */ public function filter_json( $data, $element, $submission = null ) { $data['template_suffix'] = $this->slug; $settings = $this->get_settings( $element ); $values = $this->get_values( $element, $submission ); $data['value'] = ! empty( $values['_main'] ) ? $values['_main'] : ''; $placeholder = ! empty( $settings['placeholder'] ) ? $settings['placeholder'] : ''; /** * Filters the placeholder for an element field. * * @since 1.0.0 * * @param string $placeholder Original placeholder. * @param int $element_id Element ID. */ $placeholder = apply_filters( "{$this->manager->get_prefix()}input_placeholder", $placeholder, $element->id ); if ( ! empty( $placeholder ) ) { $data['input_attrs']['placeholder'] = $placeholder; } if ( ! empty( $settings['description'] ) ) { $data['description'] = $settings['description']; $data['input_attrs']['aria-describedby'] = $data['description_attrs']['id']; } if ( ! empty( $settings['required'] ) && 'no' !== $settings['required'] ) { $required_indicator = '<span class="screen-reader-text">' . __( '(required)', 'torro-forms' ) . '</span><span class="torro-required-indicator" aria-hidden="true">*</span>'; /** * Filters the required indicator for an element that must be filled. * * @since 1.0.0 * * @param string $required_indicator Indicator HTML string. Default is a screen-reader-only * '(required)' text and an asterisk for visual appearance. */ $data['label_required'] = apply_filters( "{$this->manager->get_prefix()}required_indicator", $required_indicator ); $data['input_attrs']['aria-required'] = 'true'; $data['input_attrs']['required'] = true; } if ( ! empty( $settings['css_classes'] ) ) { if ( ! empty( $data['input_attrs']['class'] ) ) { $data['input_attrs']['class'] .= ' '; } else { $data['input_attrs']['class'] = ''; } $data['input_attrs']['class'] .= $settings['css_classes']; } if ( $submission && $submission->has_errors( $element->id ) ) { $data['errors'] = $submission->get_errors( $element->id ); $data['input_attrs']['aria-invalid'] = 'true'; } $choices = array(); if ( is_a( $this, Choice_Element_Type_Interface::class ) ) { $choices = $this->get_choices( $element ); $data['choices'] = ! empty( $choices['_main'] ) ? $choices['_main'] : array(); } if ( is_a( $this, Multi_Field_Element_Type_Interface::class ) ) { $data['additional_fields'] = $this->additional_fields_to_json( $element, $submission, $choices, $settings, $values ); } return $data; } /** * Validates a field value for an element. * * @since 1.0.0 * * @param mixed $value The value to validate. It is already unslashed when it arrives here. * @param Element $element Element to validate the field value for. * @param Submission $submission Submission the value belongs to. * @return mixed|array|WP_Error Validated value, or error object on failure. If an array is returned, * the individual values will be stored in the database separately. The * array may also contain error objects for cases where errors occurred. */ public abstract function validate_field( $value, $element, $submission ); /** * Gets the fields arguments for an element of this type when editing submission values in the admin. * * @since 1.0.0 * * @param Element $element Element to get fields arguments for. * @return array An associative array of `$field_slug => $field_args` pairs. */ public function get_edit_submission_fields_args( $element ) { $settings = $this->get_settings( $element ); $slug = $this->get_edit_submission_field_slug( $element->id ); $args = array( 'type' => 'text', 'label' => $element->label, ); if ( ! empty( $settings['placeholder'] ) ) { $args['placeholder'] = $settings['placeholder']; } if ( ! empty( $settings['description'] ) ) { $args['description'] = $settings['description']; } if ( ! empty( $settings['required'] ) && 'no' !== $settings['required'] ) { $args['required'] = true; } if ( ! empty( $settings['css_classes'] ) ) { $args['input_classes'] = explode( ' ', $settings['css_classes'] ); } if ( is_a( $this, Choice_Element_Type_Interface::class ) ) { $choices = $this->get_choices( $element ); $args['choices'] = ! empty( $choices['_main'] ) ? array_combine( $choices['_main'], $choices['_main'] ) : array(); } return array( $slug => $args, ); } /** * Bootstraps the element type by setting properties. * * @since 1.0.0 */ protected abstract function bootstrap(); /** * Gets the two strings indicating 'Yes' and 'No' in an export column. * * By default, these are simply localized 'Yes' and 'No'. * * @since 1.0.0 * * @param Element $element Element for which to use the strings. * @return array Array with two elements where the first value is the 'Yes' string and the second is the 'No' string. */ protected function get_export_column_choices_yes_no( $element ) { $yes_no = array( __( 'Yes', 'torro-forms' ), __( 'No', 'torro-forms' ), ); /** * Filters the two strings to use for choice export columns indicating whether the choice was included in the submission or not. * * By default, the strings are a localized 'Yes' and 'No'. * * @since 1.0.0 * * @param array $yes_no Array with two elements where the first value is the 'Yes' string and the second value * is the 'No' string. * @param Element_Type $element_type Current element type. * @param Element $element Current element. */ return apply_filters( "{$this->manager->get_prefix()}export_column_choices_yes_no", $yes_no, $this, $element ); } /** * Checks whether a single export column should be used for all choices. * * By default, each choice has its own column. * * @since 1.0.0 * * @param Element $element Element for which to check this flag. * @return bool True if a single column should be used, false otherwise. */ protected function use_single_export_column_for_choices( $element ) { /** * Filters whether to only render a single column for all choices when exporting submissions. * * If this filter returns true, there will only be one column for all choices. In case of an element * where multiple choices are seletable, those values will be concatenated. * * By default, each choice has its own column. * * @since 1.0.0 * * @param bool $single_column Whether to only render a single column for all choices. * @param Element_Type $element_type Current element type. * @param Element $element Current element. */ return apply_filters( "{$this->manager->get_prefix()}use_single_export_column_for_choices", false, $this, $element ); } /** * Escapes a single value for a specific export format. * * @since 1.0.0 * * @param mixed $value Value to escape. * @param string $export_format Export format identifier. May be 'xls', 'csv', 'json', 'xml' or 'html'. * @return mixed Escaped value, usually a string. */ protected function escape_single_value_for_export( $value, $export_format ) { switch ( $export_format ) { case 'xls': case 'csv': if ( is_array( $value ) && is_string( $value[ key( $value ) ] ) ) { $value = implode( ', ', $value ); } if ( is_string( $value ) ) { if ( 'csv' === $export_format ) { // Replace CSV delimiter. $value = str_replace( ';', ',', $value ); } // Add paragraphs if there are linebreaks. if ( false !== strpos( $value, "\n" ) ) { $value = wpautop( $value ); } } break; case 'json': break; case 'xml': case 'html': if ( is_array( $value ) && is_string( $value[ key( $value ) ] ) ) { $value = implode( ', ', $value ); } $value = esc_html( $value ); } return $value; } /** * Creates a new error object. * * This method should be used to create the result to return in case * submission value validation errors occur. * * @since 1.0.0 * * @param string $code Error code. * @param string $message Error message. * @param mixed $validated_value Optional. Validated value to store in the database, * regardless of it being invalid. * @return WP_Error Error object to return. */ protected function create_error( $code, $message, $validated_value = null ) { $data = ''; if ( null !== $validated_value ) { $data = array( 'validated_value' => $validated_value ); } return new WP_Error( $code, $message, $data ); } /** * Gets the slug for a submission value edit field. * * @since 1.0.0 * * @param int $element_id Element ID the submission value is for. * @param string $element_field Element field the submission value is for. * @return string Edit field slug. */ protected function get_edit_submission_field_slug( $element_id, $element_field = '' ) { $element_field = ! empty( $element_field ) ? $element_field : '_main'; return 'element_' . $element_id . '_' . $element_field . '_value'; } /** * Adds a settings field for specifying the element placeholder. * * @since 1.0.0 * * @param string $section Optional. Settings section the settings field should be part of. Default 'settings'. */ protected function add_placeholder_settings_field( $section = 'settings' ) { $this->settings_fields['placeholder'] = array( 'section' => $section, 'type' => 'text', 'label' => __( 'Placeholder', 'torro-forms' ), 'description' => __( 'Placeholder text will be shown until data is being entered.', 'torro-forms' ), 'input_classes' => array( 'regular-text' ), ); } /** * Adds a settings field for specifying the element description. * * @since 1.0.0 * * @param string $section Optional. Settings section the settings field should be part of. Default 'settings'. */ protected function add_description_settings_field( $section = 'settings' ) { $this->settings_fields['description'] = array( 'section' => $section, 'type' => 'textarea', 'label' => __( 'Description', 'torro-forms' ), 'description' => __( 'The description will be shown below the element.', 'torro-forms' ), 'input_classes' => array( 'widefat' ), ); } /** * Adds a settings field for specifying whether the element is required to be filled in. * * @since 1.0.0 * * @param string $section Optional. Settings section the settings field should be part of. Default 'settings'. */ protected function add_required_settings_field( $section = 'settings' ) { $this->settings_fields['required'] = array( 'section' => $section, 'type' => 'radio', 'label' => __( 'Required?', 'torro-forms' ), 'choices' => array( 'yes' => __( 'Yes', 'torro-forms' ), 'no' => __( 'No', 'torro-forms' ), ), 'description' => __( 'Whether the user must input something.', 'torro-forms' ), 'default' => 'no', ); } /** * Adds a settings field for specifying additional CSS classes for the input. * * @since 1.0.0 * * @param string $section Optional. Settings section the settings field should be part of. Default 'settings'. */ protected function add_css_classes_settings_field( $section = 'settings' ) { $this->settings_fields['css_classes'] = array( 'section' => $section, 'type' => 'text', 'label' => __( 'CSS Classes', 'torro-forms' ), 'description' => __( 'Additional CSS Classes separated by whitespaces.', 'torro-forms' ), 'input_classes' => array( 'regular-text' ), ); } /** * Sanitizes the settings sections. * * @since 1.0.0 */ protected final function sanitize_settings_sections() { $defaults = array( 'title' => '', ); foreach ( $this->settings_sections as $slug => $section ) { $this->settings_sections[ $slug ] = array_merge( $defaults, $section ); } } /** * Sanitizes the settings fields. * * @since 1.0.0 */ protected final function sanitize_settings_fields() { $defaults = array( 'section' => '', 'type' => 'text', 'label' => '', 'description' => '', 'is_label' => false, 'is_choices' => false, ); $invalid_fields = array(); $valid_sections = array(); foreach ( $this->settings_fields as $slug => $field ) { if ( empty( $field['section'] ) || ! isset( $this->settings_sections[ $field['section'] ] ) ) { /* translators: %s: field section slug */ $this->manager->error_handler()->doing_it_wrong( get_class( $this ) . '::bootstrap()', sprintf( __( 'Invalid element type field section %s.', 'torro-forms' ), esc_html( $field['section'] ) ), '1.0.0' ); $invalid_fields[ $slug ] = true; continue; } if ( empty( $field['type'] ) || ! Field_Manager::is_field_type_registered( $field['type'] ) ) { /* translators: %s: field type slug */ $this->manager->error_handler()->doing_it_wrong( get_class( $this ) . '::bootstrap()', sprintf( __( 'Invalid element type field type %s.', 'torro-forms' ), esc_html( $field['type'] ) ), '1.0.0' ); $invalid_fields[ $slug ] = true; continue; } if ( in_array( $field['type'], array( 'multiselect', 'multibox', 'group' ), true ) || empty( $field['is_choices'] ) && 'torrochoices' === $field['type'] ) { /* translators: %s: field type slug */ $this->manager->error_handler()->doing_it_wrong( get_class( $this ) . '::bootstrap()', sprintf( __( 'Disallowed element type field type %s.', 'torro-forms' ), esc_html( $field['type'] ) ), '1.0.0' ); $invalid_fields[ $slug ] = true; continue; } if ( ! empty( $field['repeatable'] ) && empty( $field['is_choices'] ) ) { /* translators: %s: field type slug */ $this->manager->error_handler()->doing_it_wrong( get_class( $this ) . '::bootstrap()', __( 'Disallowed repeatable element type field.', 'torro-forms' ), '1.0.0' ); $invalid_fields[ $slug ] = true; continue; } $valid_sections[ $field['section'] ] = true; $this->settings_fields[ $slug ] = array_merge( $defaults, $field ); } $this->settings_fields = array_diff_key( $this->settings_fields, $invalid_fields ); $this->settings_sections = array_intersect_key( $this->settings_sections, $valid_sections ); } }
Changelog
Version | Description |
---|---|
1.0.0 | Introduced. |
Methods
- bootstrap — Bootstraps the element type by setting properties.
- filter_json — Filters the array representation of a given element of this type.
- format_values_for_export — Formats values for an export.
- get_edit_submission_fields_args — Gets the fields arguments for an element of this type when editing submission values in the admin.
- get_file_id — Gets the ID under which to store the upload in $_FILES.
- get_file_mime_types — Gets the available file mime types.
- get_media_types — Gets the available media types.
- get_type_dropdown_options — Gets the available dropdown options to specify the file type.
- validate_field — Validates a field value for an element.