plugin updates

This commit is contained in:
Tony Volpe
2024-11-20 22:40:39 -05:00
parent 0238f0c4ca
commit 3362947c6e
434 changed files with 13405 additions and 9202 deletions

View File

@@ -79,7 +79,7 @@ class GF_Field_Calculation extends GF_Field {
if ( $is_entry_detail || $is_form_editor ) {
$style = $this->disableQuantity ? "style='display:none;'" : '';
$quantity_field = " <span class='ginput_quantity_label gform-field-label gform-field-label--type-sub-large' {$style}>{$product_quantity_sub_label}</span> <input type='number' name='input_{$id}.3' value='{$quantity}' id='ginput_quantity_{$form_id}_{$this->id}' class='ginput_quantity' size='10' min='0' {$disabled_text} />";
$quantity_field = " <span class='ginput_quantity_label gform-field-label' {$style}>{$product_quantity_sub_label}</span> <input type='number' name='input_{$id}.3' value='{$quantity}' id='ginput_quantity_{$form_id}_{$this->id}' class='ginput_quantity' size='10' min='0' {$disabled_text} />";
} elseif ( ! $this->disableQuantity ) {
$tabindex = $this->get_tabindex();
$describedby_extra_id = array();
@@ -107,7 +107,7 @@ class GF_Field_Calculation extends GF_Field {
<span class='gform-field-label gform-field-label--type-sub-large ginput_product_price_label'>" . gf_apply_filters( array( 'gform_product_price', $form_id, $this->id ), esc_html__( 'Price', 'gravityforms' ), $form_id ) . ":</span>
<span class='gform-field-label gform-field-label--type-sub-large ginput_product_price' id='{$field_id}'>" . esc_html( GFCommon::to_money( $price, $currency ) ) . "</span>
$wrapper_close
<input type='hidden' name='input_{$id}.2' id='ginput_base_price_{$form_id}_{$this->id}' class='gform_hidden' value='" . esc_attr( $price ) . "'/>
<input type='hidden' name='input_{$id}.2' id='ginput_base_price_{$form_id}_{$this->id}' class='gform_hidden ginput_calculated_price' value='" . esc_attr( $price ) . "'/>
{$quantity_field}
</div>";
}

View File

@@ -41,6 +41,9 @@ class GF_Field_CAPTCHA extends GF_Field {
* @param $data
*/
public function __construct( $data = array() ) {
add_filter( 'gform_ajax_submission_result', array( 'GF_Field_CAPTCHA', 'set_recaptcha_response' ) );
add_filter( 'gform_ajax_validation_result', array( 'GF_Field_CAPTCHA', 'set_recaptcha_response' ) );
parent::__construct( $data );
if ( ! has_filter( 'gform_pre_render', array( __CLASS__, 'maybe_remove_recaptcha_v2' ) ) ) {
@@ -48,7 +51,33 @@ class GF_Field_CAPTCHA extends GF_Field {
}
}
public function get_form_editor_field_title() {
/**
* Add recaptcha response to the AJAX request result.
*
* @since 2.9.0
*
* @param array $result The result of the AJAX validation and submission request.
*
* @return mixed
*/
public static function set_recaptcha_response( $result ) {
$form = $result['form'];
// Adding recaptcha response to the result.
$recaptcha_field = \GFFormsModel::get_fields_by_type( $form, array( 'captcha' ) );
if ( empty( $recaptcha_field ) ) {
return $result;
}
$recaptcha_field = $recaptcha_field[0];
$recaptcha_response = $recaptcha_field->get_encoded_recaptcha_response( $form, $recaptcha_field->get_posted_recaptcha_response() );
if ( $recaptcha_field->verify_decoded_response( $form, $recaptcha_response ) ) {
$result['recaptcha_response'] = $recaptcha_response;
}
return $result;
}
public function get_form_editor_field_title() {
return esc_attr__( 'CAPTCHA', 'gravityforms' );
}
@@ -111,18 +140,29 @@ class GF_Field_CAPTCHA extends GF_Field {
if ( ( ! empty( $this->get_site_key() ) && ! empty( $this->get_secret_key() ) ) ) {
if ( is_plugin_active( 'gravityformsconversationalforms/conversationalforms.php' ) ) {
return array(
'type' => 'notice',
'content' => esc_html__( 'The reCAPTCHA v2 field is not supported in Conversational Forms and will be removed, but will continue to work as expected in other contexts.', 'gravityforms' )
'type' => 'notice',
'content' => sprintf(
'<div class="gform-typography--weight-regular">%s</div>',
__( 'The reCAPTCHA v2 field is not supported in Conversational Forms and will be removed, but will continue to work as expected in other contexts.', 'gravityforms' )
),
'icon_helper_text' => __( 'This field is not supported in Conversational Forms', 'gravityforms' ),
);
} else {
return '';
}
}
// If the reCAPTCHA keys are not configured, we need to display a warning.
// Translators: 1. Opening <a> tag with link to the Forms > Settings > reCAPTCHA page. 2. closing <a> tag.
return sprintf( __( 'To use reCAPTCHA v2 you must configure the site and secret keys on the %1$sreCAPTCHA Settings%2$s page.', 'gravityforms' ), "<a href='?page=gf_settings&subview=recaptcha' target='_blank'>", '</a>' );
return array(
'type' => 'notice',
'content' => sprintf(
'%s<div class="gform-spacing gform-spacing--top-1">%s</div>',
__( 'Configuration Required', 'gravityforms' ),
// Translators: 1. Opening <a> tag with link to the Forms > Settings > reCAPTCHA page. 2. closing <a> tag.
sprintf( __( 'To use the reCAPTCHA field, please configure your %1$sreCAPTCHA settings%2$s.', 'gravityforms' ), '<a href="?page=gf_settings&subview=recaptcha" target="_blank">', '</a>' )
),
'icon_helper_text' => __( 'This field requires additional configuration', 'gravityforms' ),
);
}
/**
@@ -242,6 +282,7 @@ class GF_Field_CAPTCHA extends GF_Field {
return true;
}
}
$response = $this->get_posted_recaptcha_response();
if ( ! ( $this->verify_decoded_response( $form, $response ) || $this->verify_recaptcha_response( $response ) ) ) {
@@ -265,7 +306,7 @@ class GF_Field_CAPTCHA extends GF_Field {
*
* @return bool
*/
private function verify_decoded_response( $form, $response ) {
public function verify_decoded_response( $form, $response ) {
$decoded_response = $this->get_decoded_recaptcha_response( $response );
// No decoded object.
@@ -273,11 +314,6 @@ class GF_Field_CAPTCHA extends GF_Field {
return false;
}
// Not a time that we need to verify the decoded object.
if ( ! GFFormDisplay::is_last_page( $form ) || $this->is_on_last_page( $form ) ) {
return false;
}
return (
$decoded_response->success === true
&& ! empty( $decoded_response->token )
@@ -306,7 +342,7 @@ class GF_Field_CAPTCHA extends GF_Field {
*/
public function get_site_key() {
if ( ! $this->site_key ) {
$this->site_key = get_option( 'rg_gforms_captcha_public_key', '' );
$this->site_key = get_option( 'rg_gforms_captcha_public_key', '' );
}
return $this->site_key;
@@ -341,7 +377,7 @@ class GF_Field_CAPTCHA extends GF_Field {
*
* @return string
*/
private function get_posted_recaptcha_response() {
public function get_posted_recaptcha_response() {
return sanitize_text_field( rgpost( 'g-recaptcha-response' ) );
}
@@ -425,11 +461,28 @@ class GF_Field_CAPTCHA extends GF_Field {
$type = get_option( 'rg_gforms_captcha_type' );
if ( $is_entry_detail || $is_form_editor ){
//for admin, show a thumbnail depending on chosen theme
if ( empty( $this->site_key ) || empty( $this->secret_key ) ) {
return "<div class='ginput_container'><div class='captcha_message'>" . __( 'To use the reCAPTCHA field you must do the following:', 'gravityforms' ) . "</div><div class='captcha_message'>1 - <a href='https://www.google.com/recaptcha/admin' target='_blank'>" . sprintf( __( 'Sign up%s for an API key pair for your site.', 'gravityforms' ), '</a>' ) . "</div><div class='captcha_message'>2 - " . sprintf( __( 'Enter your reCAPTCHA site and secret keys in the %sreCAPTCHA Settings%s.', 'gravityforms' ), "<a href='?page=gf_settings&subview=recaptcha' target='_blank'>", '</a>' ) . '</div></div>';
}
return '<div class="ginput_container ginput_container_addon_message ginput_container_addon_message_captcha">
<div class="gform-alert gform-alert--info gform-alert--theme-cosmos gform-spacing gform-spacing--bottom-0 gform-theme__disable">
<span
class="gform-icon gform-icon--information-simple gform-icon--preset-active gform-icon-preset--status-info gform-alert__icon"
aria-hidden="true"
></span>
<div class="gform-alert__message-wrap">
<div class="gform-alert__message">
'. __( 'Configuration Required', 'gravityforms' ) .'
<div class="gform-spacing gform-spacing--top-1">'. sprintf(
'%s %s%s%s.',
__( 'To use the reCAPTCHA field, please configure your', 'gravityforms' ),
'<a href="?page=gf_settings&subview=recaptcha" target="_blank">',
__( 'reCAPTCHA settings', 'gravityforms' ),
'</a>'
) .'</div>
</div>
</div>
</div>
</div>';
}
$type_suffix = $type == 'invisible' ? 'invisible_' : '';
$alt = esc_attr__( 'An example of reCAPTCHA', 'gravityforms' );
@@ -491,7 +544,7 @@ class GF_Field_CAPTCHA extends GF_Field {
*
* @return string
*/
private function get_encoded_recaptcha_response( $form, $response ) {
public function get_encoded_recaptcha_response( $form, $response ) {
if ( ! $this->response ) {
return $response;
}

View File

@@ -4,6 +4,8 @@ if ( ! class_exists( 'GFForms' ) ) {
die();
}
require_once( plugin_dir_path( __FILE__ ) . 'field-decorator-choice/class-gf-field-decorator-choice-checkbox-markup.php' );
class GF_Field_Checkbox extends GF_Field {
/**
@@ -120,6 +122,12 @@ class GF_Field_Checkbox extends GF_Field {
}
public function get_default_properties() {
return array(
'selectAllText' => esc_html__( 'Select All', 'gravityforms' ),
);
}
/**
* Returns the field inner markup.
*
@@ -135,6 +143,11 @@ class GF_Field_Checkbox extends GF_Field {
*/
public function get_field_input( $form, $value = '', $entry = null ) {
if ( $this->type == 'image_choice' ) {
$this->image_markup = new GF_Field_Decorator_Choice_Checkbox_Markup( $this );
return $this->image_markup->get_field_input( $form, $value, $entry );
}
$form_id = absint( $form['id'] );
if ( GFCommon::is_legacy_markup_enabled( $form ) ) {
@@ -151,14 +164,35 @@ class GF_Field_Checkbox extends GF_Field {
// Get checkbox choices markup.
$choices_markup = $this->get_checkbox_choices( $value, $disabled_text, $form_id );
if ( ! $this->enableSelectAll ) {
// Get button markup.
$button_markup = $this->get_button_markup( $value, $entry );
$select_all_enabled_class = $this->enableSelectAll ? 'gfield_choice--select_all_enabled' : '';
$limit_message = $this->get_limit_message();
if ( 'multi_choice' == $this->type || ! $this->enableSelectAll ) {
return sprintf(
"<div class='ginput_container ginput_container_checkbox'><div class='gfield_checkbox' id='%s'>%s</div></div>",
"<div class='ginput_container ginput_container_checkbox'>%s<div class='gfield_checkbox %s' id='%s'>%s</div></div>",
$limit_message,
$select_all_enabled_class,
esc_attr( $field_id ),
$choices_markup
);
}
return sprintf(
"<div class='ginput_container ginput_container_checkbox'>%s<div class='gfield_checkbox %s' id='%s'>%s%s</div></div>",
$limit_message,
$select_all_enabled_class,
esc_attr( $field_id ),
$choices_markup,
$button_markup
);
}
public function get_button_markup( $value, $entry ) {
/**
* Modify the "Select All" checkbox label.
*
@@ -192,16 +226,139 @@ class GF_Field_Checkbox extends GF_Field {
$deselect_label,
$all_selected ? 1 : 0,
$all_selected ? $deselect_label : $select_label,
$is_form_editor ? ' disabled="disabled"' : ''
$this->is_form_editor() ? ' disabled="disabled"' : ''
);
return sprintf(
"<div class='ginput_container ginput_container_checkbox'><div class='gfield_checkbox' id='%s'>%s%s</div></div>",
esc_attr( $field_id ),
$choices_markup,
$button_markup
);
return $button_markup;
}
/**
* Get the message that describes the choice limit.
*
* @since 2.9.0
*
* @return string
*/
public function get_limit_message() {
$text = $this->get_limit_message_text();
if ( ! $text ) {
return '';
}
$form_id = $this->formId;
$form = GFAPI::get_form( $form_id );
// If the validation message is the same as the limit message, and they both display above the field, don't display the limit message.
if ( $this->failed_validation && $this->validation_message === $text && $this->is_validation_above( $form ) ) {
return;
}
$id = $this->id;
return "<span class='gfield_choice_limit_message gfield_description' id='gfield_choice_limit_message_{$form_id}_{$id}'>{$text}</span>";
}
/**
* Get the text of the choice limit message, or return false if there is no limit.
*
* @since 2.9.0
*
* @return false|string
*/
public function get_limit_message_text() {
if ( $this->choiceLimit === 'exactly' && $this->choiceLimitNumber ) {
$message = sprintf(
esc_attr(
_n(
'Select exactly %s choice.',
'Select exactly %s choices.',
$this->choiceLimitNumber,
'gravityforms'
)
),
"<strong>$this->choiceLimitNumber</strong>"
);
/**
* Modify the message displayed when a checkbox is limited to an exact number of entries.
*
* @since 2.9.0
*
* @param string $message The message to filter.
* @param int $number The number of choices that must be selected.
* @param object $field The field currently being processed.
*/
return gf_apply_filters( array( 'gform_checkbox_limit_exact_message', $this->formId, $this->id ), $message, $this->choiceLimitNumber, $this );
}
if ( $this->choiceLimit === 'range' ) {
$min = $this->choiceLimitMin;
$max = $this->choiceLimitMax;
if ( ! $min && $max ) {
$message = sprintf(
esc_attr(
_n(
'Select up to %s choice.',
'Select up to %s choices.',
$max,
'gravityforms'
)
),
"<strong>$max</strong>"
);
/**
* Modify the message displayed when a checkbox is limited to a maximum number of choices.
*
* @since 2.9.0
*
* @param string $message The message to filter.
* @param int $max The maximum number of choices that must be selected.
* @param object $field The field currently being processed.
*/
return gf_apply_filters( array( 'gform_checkbox_limit_max_message', $this->formId, $this->id ), $message, $max, $this );
}
if ( ! $max && $min ) {
$message = sprintf(
esc_attr(
_n(
'Select at least %s choice.',
'Select at least %s choices.',
$min,
'gravityforms'
)
),
"<strong>$min</strong>"
);
/**
* Modify the message displayed when a checkbox is limited to a minimum number of choices.
*
* @since 2.9.0
*
* @param string $message The message to filter.
* @param int $min The minimum number of choices that must be selected.
* @param object $field The field currently being processed.
*/
return gf_apply_filters( array( 'gform_checkbox_limit_min_message', $this->formId, $this->id ), $message, $min, $this );
}
if( $min && $max ) {
$message = sprintf( esc_html__( 'Select between %s and %s choices.', 'gravityforms' ), "<strong>$min</strong>", "<strong>$max</strong>" );
/**
* Modify the message displayed when a checkbox is limited to a maximum number of entries.
*
* @since 2.9.0
*
* @param string $message The message to filter.
* @param int $min The minimum number of choices that must be selected.
* @param int $max The maximum number of choices that must be selected.
* @param object $field The field currently being processed.
*/
return gf_apply_filters( array( 'gform_checkbox_limit_range_message', $this->formId, $this->id ), $message, $min, $max, $this );
}
}
return false;
}
/**
@@ -259,7 +416,11 @@ class GF_Field_Checkbox extends GF_Field {
}
// Prepare input ID.
$input_id = $this->id . '.' . $choice_number;
if ( rgar( $choice, 'key' ) ) {
$input_id = $this->get_input_id_from_choice_key( $choice['key'] );
} else {
$input_id = $this->id . '.' . $choice_number;
}
if ( ( $this->is_form_editor() || ( ! isset( $_GET['gf_token'] ) && empty( $_POST ) ) ) && rgar( $choice, 'isSelected' ) ) {
$checkboxes_selected++;
@@ -273,7 +434,6 @@ class GF_Field_Checkbox extends GF_Field {
}
return $checkboxes_selected;
}
@@ -336,7 +496,17 @@ class GF_Field_Checkbox extends GF_Field {
$item = trim( $item );
if ( GFFormsModel::choice_value_match( $this, $this->choices[ $choice_index ], $item ) ) {
if ( rgar( $input, 'key' ) ) {
$choice_id = $this->get_choice_id_from_input_key( $input['key'] );
if ( '' == $choice_id ) {
continue;
}
$choice = $this->choices[ $choice_id ];
} else {
$choice = $this->choices[ $choice_index ];
}
if ( GFFormsModel::choice_value_match( $this, $choice, $item ) ) {
$value[ $input['id'] . '' ] = $item;
break;
}
@@ -356,7 +526,27 @@ class GF_Field_Checkbox extends GF_Field {
}
public function validate( $value, $form ) {
if ( $this->choiceLimit == 'exactly' ) {
$selected_choices_count = $this->get_selected_choices_count( $value );
if ( 0 === $selected_choices_count ) {
return;
}
if ( $selected_choices_count != $this->choiceLimitNumber ) {
$this->failed_validation = true;
$this->validation_message = $this->get_limit_message_text();
}
} elseif ( $this->choiceLimit == 'range' ) {
$selected_choices_count = $this->get_selected_choices_count( $value );
if ( 0 === $selected_choices_count ) {
return;
}
if ( ( $this->choiceLimitMin && $selected_choices_count < $this->choiceLimitMin ) || ( $this->choiceLimitMax && $selected_choices_count > $this->choiceLimitMax ) ) {
$this->failed_validation = true;
$this->validation_message = $this->get_limit_message_text();
}
}
}
@@ -403,7 +593,6 @@ class GF_Field_Checkbox extends GF_Field {
if ( $this->type == 'post_category' ) {
$value = GFCommon::prepare_post_category_value( $value, $this, 'entry_list' );
}
} else {
$value = '';
@@ -437,7 +626,6 @@ class GF_Field_Checkbox extends GF_Field {
* @return string
*/
public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {
if ( is_array( $value ) ) {
$items = '';
@@ -511,6 +699,7 @@ class GF_Field_Checkbox extends GF_Field {
$use_value = in_array( 'value', $modifiers );
$format_currency = in_array( 'currency', $modifiers );
$use_price = $format_currency || in_array( 'price', $modifiers );
$image_url = in_array( 'img_url', $modifiers );
if ( is_array( $raw_value ) && (string) intval( $input_id ) != $input_id ) {
$items = array( $input_id => $value ); // Float input IDs. (i.e. 4.1 ). Used when targeting specific checkbox items.
@@ -524,38 +713,40 @@ class GF_Field_Checkbox extends GF_Field {
// Get the items available within the merge tags.
foreach ( $items as $input_id => $item ) {
switch (true) {
// If the 'value' modifier was passed.
case $use_value:
list( $val, $price ) = rgexplode( '|', $item, 2 );
break;
// If the 'value' modifier was passed.
if ( $use_value ) {
// If the 'price' or 'currency' modifiers were passed.
case $use_price:
list( $name, $val ) = rgexplode( '|', $item, 2 );
if ( $format_currency ) {
$val = GFCommon::to_money( $val, rgar( $entry, 'currency' ) );
}
break;
list( $val, $price ) = rgexplode( '|', $item, 2 );
// If the 'image_url' modifier was passed.
case $image_url:
$image_choice = new GF_Field_Image_Choice( $this );
$val = $image_choice->get_merge_tag_img_url( $raw_value, $input_id, $entry, $form, $this );
break;
// If the 'price' or 'currency' modifiers were passed.
} elseif ( $use_price ) {
list( $name, $val ) = rgexplode( '|', $item, 2 );
if ( $format_currency ) {
$val = GFCommon::to_money( $val, rgar( $entry, 'currency' ) );
}
// If this is a post category checkbox.
} else if ( $this->type == 'post_category' ) {
$use_id = strtolower( $modifier ) == 'id';
$item_value = GFCommon::format_post_category( $item, $use_id );
$val = GFFormsModel::is_field_hidden( $form, $this, array(), $entry ) ? '' : $item_value;
// If no modifiers were passed.
} else {
$val = GFFormsModel::is_field_hidden( $form, $this, array(), $entry ) ? '' : RGFormsModel::get_choice_text( $this, $raw_value, $input_id );
// If this is a post category checkbox.
case $this->type == 'post_category':
$use_id = strtolower( $modifier ) == 'id';
$item_value = GFCommon::format_post_category( $item, $use_id );
$val = GFFormsModel::is_field_hidden( $form, $this, array(), $entry ) ? '' : $item_value;
break;
// If no modifiers were passed.
default:
$val = GFFormsModel::is_field_hidden( $form, $this, array(), $entry ) ? '' : RGFormsModel::get_choice_text( $this, $raw_value, $input_id );
break;
}
$ary[] = GFCommon::format_variable_value( $val, $url_encode, $esc_html, $format );
}
return GFCommon::implode_non_blank( ', ', $ary );
@@ -606,6 +797,29 @@ class GF_Field_Checkbox extends GF_Field {
}
/**
* Return the entry inputs in the order they are configured in the form editor.
*
* @since 2.9
*
* @return array|null
*/
public function get_entry_inputs() {
if ( $this->has_persistent_choices() && is_array( $this->inputs ) ) {
$inputs_by_key = array_column( $this->inputs, null, 'key' );
$sorted_inputs = array();
foreach ( $this->choices as $choice ) {
if ( isset( $inputs_by_key[ $choice['key'] ] ) ) {
$sorted_inputs[] = $inputs_by_key[ $choice['key'] ];
}
}
return $sorted_inputs;
} else {
return $this->inputs;
}
}
/**
* Format the entry value before it is used in entry exports and by framework add-ons using GFAddOn::get_field_value().
*
@@ -690,11 +904,23 @@ class GF_Field_Checkbox extends GF_Field {
$choice_number = 1;
$count = 1;
/**
* A filter that allows for the setting of the maximum number of choices shown in
* the form editor for choice based fields (radio, checkbox, image, and multiple choice).
*
* @since 2.9
*
* @param int $max_choices_visible_count The default number of choices visible is 5.
* @param object $field The current field object.
*/
$max_choices_count = gf_apply_filters( array( 'gform_field_choices_max_count_visible', $form_id ), 5, $this );
$legacy_markup = GFCommon::is_legacy_markup_enabled( $form_id );
$tag = $legacy_markup ? 'li' : 'div';
// Add Select All choice.
// Add Select All choice for legacy markup.
if ( $this->enableSelectAll && $legacy_markup ) {
/**
@@ -754,11 +980,22 @@ class GF_Field_Checkbox extends GF_Field {
}
$need_aria_describedby = true;
// Add select all choice for the Multiple Choice field.
if ( $this->type == 'multi_choice' && $this->enableSelectAll ) {
$this->choice_field = new GF_Field_Multiple_Choice( $this );
$selected_choices_count = $this->get_selected_choices_count( $value );
$tabindex = $this->get_tabindex();
$need_aria_describedby = false; // We're going to add the aria-describedby attribute to the "Select All" choice, so we don't need it later on the first choice.
$choices .= $this->choice_field->get_choice_field_select_all_markup( $value, $tabindex, $selected_choices_count );
}
// Loop through field choices.
foreach ( $this->choices as $choice ) {
// Get aria-describedby if this is the first choice
$aria_describedby = $choice_number === 1 ? $this->get_aria_describedby() : '';
$aria_describedby = ( $choice_number === 1 && $need_aria_describedby ) ? $this->get_choice_aria_describedby( $form_id ) : '';
// Hack to skip numbers ending in 0, so that 5.1 doesn't conflict with 5.10.
if ( $choice_number % 10 == 0 ) {
@@ -766,7 +1003,13 @@ class GF_Field_Checkbox extends GF_Field {
}
// Prepare input ID.
$input_id = $this->id . '.' . $choice_number;
$input_id = '';
if ( $this->has_persistent_choices() ) {
$input_id = $this->get_input_id_from_choice_key( $choice['key'] );
} else {
// Regular checkboxes field generates input IDs on the fly.
$input_id = $this->id . '.' . $choice_number;
}
if ( $is_entry_detail || $is_form_editor || $form_id == 0 ) {
$id = $this->id . '_' . $choice_number ++;
@@ -774,16 +1017,7 @@ class GF_Field_Checkbox extends GF_Field {
$id = $form_id . '_' . $this->id . '_' . $choice_number ++;
}
if ( ( $is_form_editor || ( ! isset( $_GET['gf_token'] ) && empty( $_POST ) ) ) && rgar( $choice, 'isSelected' ) ) {
$checked = "checked='checked'";
} elseif ( is_array( $value ) && GFFormsModel::choice_value_match( $this, $choice, rgget( $input_id, $value ) ) ) {
$checked = "checked='checked'";
} elseif ( ! is_array( $value ) && GFFormsModel::choice_value_match( $this, $choice, $value ) && ! empty( $_POST[ 'is_submit_' . $form_id ] ) ) {
$checked = "checked='checked'";
} else {
$checked = '';
}
$checked = $this->get_checked_attribute( $choice, $value, $input_id, $form_id );
$tabindex = $this->get_tabindex();
$choice_value = $choice['value'];
@@ -812,7 +1046,7 @@ class GF_Field_Checkbox extends GF_Field {
$is_admin = $is_entry_detail || $is_form_editor;
if ( $is_admin && rgget('view') != 'entry' && $count >= 5 ) {
if ( $is_admin && rgget('view') != 'entry' && $count >= $max_choices_count ) {
break;
}
@@ -823,7 +1057,7 @@ class GF_Field_Checkbox extends GF_Field {
$total = sizeof( $this->choices );
if ( $count < $total ) {
$choices .= "<{$tag} class='gchoice_total'>" . sprintf( esc_html__( '%d of %d items shown. Edit field to view all', 'gravityforms' ), $count, $total ) . "</{$tag}>";
$choices .= "<{$tag} class='gchoice_total'><span>" . sprintf( esc_html__( '%d of %d items shown. Edit choices to view all.', 'gravityforms' ), $count, $total ) . "</span></{$tag}>";
}
}
@@ -834,12 +1068,53 @@ class GF_Field_Checkbox extends GF_Field {
* @since Unknown
*
* @param string $choices The string containing the choices to be filtered.
* @param object $field Ahe field currently being processed.
* @param object $field The field currently being processed.
*/
return gf_apply_filters( array( 'gform_field_choices', $this->formId, $this->id ), $choices, $this );
}
public function get_checked_attribute( $choice, $value, $input_id, $form_id ) {
$is_form_editor = $this->is_form_editor();
if ( ( $is_form_editor || ( ! isset( $_GET['gf_token'] ) && empty( $_POST ) ) ) && rgar( $choice, 'isSelected' ) ) {
$checked = "checked='checked'";
} elseif ( is_array( $value ) && GFFormsModel::choice_value_match( $this, $choice, rgget( $input_id, $value ) ) ) {
$checked = "checked='checked'";
} elseif ( ! is_array( $value ) && GFFormsModel::choice_value_match( $this, $choice, $value ) && ! empty( $_POST[ 'is_submit_' . $form_id ] ) ) {
$checked = "checked='checked'";
} else {
$checked = '';
}
return $checked;
}
/**
* Get the aria-describedby attribute for the first choice.
*
* @since 2.9.0
* @access public
*
* @param int $form_id The current form ID.
* @param array $describedby Additional describedby attribute value.
*
* @return string
*/
public function get_choice_aria_describedby( $form_id, $describedby = array() ) {
$limit_describedby = array();
if ( is_array( $describedby ) ) {
$limit_describedby = array_merge( $limit_describedby, $describedby );
}
if ( $this->get_limit_message_text() ) {
$limit_describedby[] = 'gfield_choice_limit_message_' . $form_id . '_' . $this->id;
}
return $this->get_aria_describedby( $limit_describedby );
}
/**
* Determine if a specific checkbox is checked.
*

View File

@@ -278,7 +278,7 @@ class GF_Field_Date extends GF_Field {
$field_position = substr( $format, 0, 3 );
if ( $is_form_editor ) {
$datepicker_display = in_array( $this->dateType, array( 'datefield', 'datedropdown' ) ) ? 'none' : 'block';
$datepicker_display = in_array( $this->dateType, array( 'datefield', 'datedropdown' ) ) ? 'none' : 'flex';
$datefield_display = $this->dateType == 'datefield' ? 'inline' : 'none';
$dropdown_display = $this->dateType == 'datedropdown' ? 'inline' : 'none';
$icon_display = $this->calendarIconType == 'calendar' ? 'inline' : 'none';
@@ -328,7 +328,7 @@ class GF_Field_Date extends GF_Field {
$day_dropdown = "<div class='gfield_date_dropdown_day ginput_date_dropdown ginput_container ginput_container_date gform-grid-col' id='gfield_dropdown_date_day' style='display:$dropdown_display'>" . $this->get_day_dropdown( '', "{$field_id}_2", rgar( $date_info, 'day' ), '', $disabled_text, $day_placeholder_value ) . '</div>';
$year_dropdown = "<div class='gfield_date_dropdown_year ginput_date_dropdown ginput_container ginput_container_date gform-grid-col' id='gfield_dropdown_date_year' style='display:$dropdown_display'>" . $this->get_year_dropdown( '', "{$field_id}_3", rgar( $date_info, 'year' ), '', $disabled_text, $year_placeholder_value, $form ) . '</div>';
$field_string = "<div class='ginput_container ginput_container_date gform-grid-col' id='gfield_input_datepicker' style='display:$datepicker_display'><input name='ginput_datepicker' type='text' {$date_picker_placeholder} {$disabled_text} value='{$picker_value}'/><img src='" . GFCommon::get_base_url() . "/images/datepicker/datepicker.svg' id='gfield_input_datepicker_icon' style='display:$icon_display'/></div>";
$field_string = "<div class='ginput_container ginput_container_date' id='gfield_input_datepicker' style='display:$datepicker_display'><input name='ginput_datepicker' type='text' {$date_picker_placeholder} {$disabled_text} value='{$picker_value}'/><img src='" . GFCommon::get_base_url() . "/images/datepicker/datepicker.svg' id='gfield_input_datepicker_icon' style='display:$icon_display'/></div>";
switch ( $field_position ) {
case 'dmy' :
@@ -1015,7 +1015,7 @@ class GF_Field_Date extends GF_Field {
* @return string
*/
public function get_field_placeholder_attribute() {
if ( $this->dateType === 'datepicker' && empty( $this->placeholder ) ) {
if ( empty( $this->placeholder ) ) {
$format = $this->is_form_editor() ? wp_strip_all_tags( $this->get_date_format() ) : $this->get_date_format();
return sprintf( "placeholder='%s'", esc_attr( $format ) );
@@ -1086,7 +1086,7 @@ class GF_Field_Date extends GF_Field {
public function get_filter_settings() {
$filter_settings = parent::get_filter_settings();
$filter_settings['placeholder'] = esc_html__( 'yyyy-mm-dd', 'gravityforms' );
$filter_settings['cssClass'] = 'datepicker ymd_dash';
$filter_settings['cssClass'] = 'datepicker gform-datepicker ymd_dash';
return $filter_settings;
}

View File

@@ -253,10 +253,6 @@ class GF_Field_FileUpload extends GF_Field {
$multiple_files = $this->multipleFiles;
$file_list_id = 'gform_preview_' . $form_id . '_' . $id;
$is_entry_detail = $this->is_entry_detail();
$is_form_editor = $this->is_form_editor();
$is_admin = $is_entry_detail || $is_form_editor;
// Generate upload rules messages ( allowed extensions, max no. of files, max file size ).
$upload_rules_messages = array();
// Extensions.
@@ -346,7 +342,7 @@ class GF_Field_FileUpload extends GF_Field {
$upload = "<div id='{$container_id}' data-settings='{$plupload_init_json}' class='gform_fileupload_multifile'>
<div id='{$drag_drop_id}' class='gform_drop_area gform-theme-field-control'>
<span class='gform_drop_instructions'>{$drop_files_here_text} </span>
<button type='button' id='{$browse_button_id}' class='button gform_button_select_files gform-theme-button gform-theme-button--control' {$describedby} {$tabindex} >{$select_files_text}</button>
<button type='button' id='{$browse_button_id}' class='button gform_button_select_files gform-theme-button gform-theme-button--control' {$describedby} {$tabindex} {$disabled_text}>{$select_files_text}</button>
</div>
</div>";

View File

@@ -101,7 +101,7 @@ class GF_Field_HiddenProduct extends GF_Field {
$field_type = $is_entry_detail || $is_form_editor ? 'text' : 'hidden';
return $quantity_field . $product_name_field . "<input name='input_{$id}.2' id='ginput_base_price_{$form_id}_{$this->id}' type='{$field_type}' value='{$price}' class='gform_hidden ginput_amount' {$disabled_text}/>";
return "<div class='ginput_container ginput_container_product_price_hidden'>" . $quantity_field . $product_name_field . "<input name='input_{$id}.2' id='ginput_base_price_{$form_id}_{$this->id}' type='{$field_type}' value='{$price}' class='gform_hidden ginput_amount' {$disabled_text}/></div>";
}
public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {

View File

@@ -0,0 +1,193 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
require_once( plugin_dir_path( __FILE__ ) . 'class-gf-field-multiple-choice.php' );
class GF_Field_Image_Choice extends GF_Field_Multiple_Choice {
public $type = 'image_choice';
public $checkbox_choice;
public function get_form_editor_field_title() {
return esc_attr__( 'Image Choice', 'gravityforms' );
}
/**
* Returns the field's form editor description.
*
* @since 2.5
*
* @return string
*/
public function get_form_editor_field_description() {
return esc_attr__( 'Allow users to choose from a list of images.', 'gravityforms' );
}
/**
* Returns the field's form editor icon.
*
* This could be an icon url or a gform-icon class.
*
* @since 2.5
*
* @return string
*/
public function get_form_editor_field_icon() {
return 'gform-icon--image_choice';
}
function get_form_editor_field_settings() {
return array(
'conditional_logic_field_setting',
'prepopulate_field_setting',
'error_message_setting',
'label_setting',
'label_placement_setting',
'admin_label_setting',
'choices_setting',
'rules_setting',
'visibility_setting',
'description_setting',
'css_class_setting',
'enable_multiple_selections_setting',
'choice_min_max_setting',
'image_choice_ui_show_label_setting'
);
}
/**
* Get the choice labels visibility setting default for the image choice field.
*
* @since 2.9.0
*
* @param object $field The field object.
*
* @return string
*/
public static function get_image_choice_label_visibility_setting_default( $form_id ) {
/**
* A filter that allows for the managing of the default image choice labels visibility setting.
* Default is show. Options are 'show' and 'hide'.
*
* @since 2.9
*
* @param string $label_visibility_default The default image choice labels visibility.
*
* @return string
*/
return gf_apply_filters( array( 'gform_image_choice_label_visibility_default', $form_id ), 'show' );
}
/**
* Get the choice labels visibility setting for the given image choice field.
*
* @since 2.9.0
*
* @param object $field The field object.
*
* @return string
*/
public static function get_image_choice_label_visibility_setting( $field ) {
return $field->imageChoiceLabelVisibility;
}
/**
* Get the choice inputs visibility setting for the given image choice field.
*
* @since 2.9.0
*
* @param object $field The field object.
*
* @return string
*/
public static function get_image_choice_input_visibility_setting( $field ) {
/**
* A filter that allows for the managing of image choice inputs visibility.
* Default is show. Options are 'show' and 'hide'.
* If image choice labels are hidden, the inputs will also be hidden.
*
* @since 2.9
*
* @param string $input_visibility The image choice inputs visibility.
* @param object $field The current field object.
*/
return gf_apply_filters( array( 'gform_image_choice_input_visibility', $field->formId ), 'show', $field );
}
public function get_form_editor_inline_script_on_page_render() {
// Set the field settings default values
$js = sprintf( '
function SetDefaultValues_%1$s( field ) {
if ( ! field.imageChoiceLabelVisibility ) {
field.imageChoiceLabelVisibility = "%2$s";
}
};',
$this->type,
self::get_image_choice_label_visibility_setting_default( rgget( 'id' ) )
) . PHP_EOL;
// Initialize the field settings values
$js .= 'jQuery( document ).on( "gform_load_field_settings", function( event, field, form ) {
if ( field.type !== "image_choice" ) {
return;
}
document.getElementById("image_choice_ui_show_label").value = field.imageChoiceLabelVisibility;
if ( field.type === "image_choice" && field.imageChoiceLabelVisibility === "hide" ) {
SetFieldAccessibilityWarning( "image_choice_ui_show_label_setting", "above" );
}
} );' . PHP_EOL;
// Update the field settings values on change of the setting
$js .= 'function SetFieldImageChoiceLabelVisibility( visibility ) {
SetFieldProperty( "imageChoiceLabelVisibility", visibility );
if ( visibility === "hide" ) {
SetFieldAccessibilityWarning( "image_choice_ui_show_label_setting", "above" );
} else {
ResetFieldAccessibilityWarning( "image_choice_ui_show_label_setting" );
}
RefreshSelectedFieldPreview();
}' . PHP_EOL;
return $js;
}
/**
* Returns the image URL for a choice to display in a merge tage with the "img_url" modifier.
*
* @since 2.9.0
*
* @param string $value The value of the merge tag.
* @param string $input_id The ID of the input.
* @param array $entry The entry currently being processed.
* @param array $form The form currently being processed.
* @param object $field The field currently being processed.
*
* @return string|array
*/
public function get_merge_tag_img_url( $value, $input_id, $entry, $form, $field ) {
if ( ! is_array( $field->choices ) ) {
return $value;
}
foreach ( $field->choices as $choice ) {
if ( is_array( $value ) && GFFormsModel::choice_value_match( $field, $choice, $value[ $input_id ] ) ) {
return rgar( $choice, 'file_url' ) ? $choice['file_url'] : '';
} else if ( ! is_array( $value ) && GFFormsModel::choice_value_match( $field, $choice, $value ) ) {
return rgar( $choice, 'file_url' ) ? $choice['file_url'] : '';
}
}
return '';
}
}
GF_Fields::register( new GF_Field_Image_Choice() );

View File

@@ -0,0 +1,148 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_Field_Multiple_Choice extends GF_Field {
public $type = 'multi_choice';
public function get_form_editor_field_title() {
return esc_attr__( 'Multiple Choice', 'gravityforms' );
}
/**
* Returns the field's form editor description.
*
* @return string
*/
public function get_form_editor_field_description() {
return esc_attr__( 'Allow users to choose from a list of options.', 'gravityforms' );
}
/**
* Returns the field's form editor icon.
*
* This could be an icon url or a gform-icon class.
*
* @return string
*/
public function get_form_editor_field_icon() {
return 'gform-icon--choice';
}
function get_form_editor_field_settings() {
return array(
'conditional_logic_field_setting',
'prepopulate_field_setting',
'error_message_setting',
'label_setting',
'label_placement_setting',
'admin_label_setting',
'choices_setting',
'rules_setting',
'visibility_setting',
'description_setting',
'css_class_setting',
'select_all_text_setting',
'choice_min_max_setting',
'horizontal_vertical_setting',
);
}
/**
* Generate the "select all" choice markup for the choice field.
*
* @since 2.9.0
*
* @param $value
* @param $tabindex
* @param $selected_choices_count
* @return string The select all choice markup.
*/
public function get_choice_field_select_all_markup( $value, $tabindex, $selected_choices_count ) {
if ( isset( $this->choiceLimit ) && $this->choiceLimit !== 'unlimited' && $this->choiceLimit !== 'range' ) {
return '';
}
$select_label = isset( $this->selectAllText ) ? esc_html( $this->selectAllText ) : esc_html__( 'Select All', 'gravityforms' );
$disabled_text = $this->is_form_editor() ? 'disabled="disabled"' : '';
// Prepare choice ID.
$id = 'choice_' . $this->id . '_select_all';
// Determine if all checkboxes are selected.
if ( $selected_choices_count === count( $this->choices ) ) {
$checked = ' checked="checked"';
} else {
$checked = '';
}
$checkbox = new GF_Field_Checkbox( $this );
$aria_describedby = $checkbox->get_choice_aria_describedby( $this->formId );
// Prepare choice markup.
$choice_markup = "<div class='gchoice gchoice_select_all'>
<input class='gfield-choice-input gfield_choice_all_toggle' type='checkbox' id='{$id}' {$tabindex} {$aria_describedby} onclick='gformToggleCheckboxes( this )' onkeypress='gformToggleCheckboxes( this )'{$checked} {$disabled_text} />
<label for='{$id}' id='label_" . $this->id . "_select_all' class='gform-field-label gform-field-label--type-inline' data-label-select='{$select_label}''>{$select_label}</label>
</div>";
/**
* Override the default choice markup used when rendering radio button, checkbox and drop down type fields.
*
* @since 1.9.6
*
* @param string $choice_markup The string containing the choice markup to be filtered.
* @param array $choice An associative array containing the choice properties.
* @param object $field The field currently being processed.
* @param string $value The value to be selected if the field is being populated.
*/
$select_all = gf_apply_filters( array( 'gform_field_choice_markup_pre_render', $this->formId, $this->id ), $choice_markup, array(), $this, $value );
return $select_all;
}
/**
* Get the choice alignment for the given field.
*
* @since 2.9.0
*
* @param object $field The field object.
* @return string
*/
public static function get_field_choice_alignment( $field ) {
return rgempty( 'choiceAlignment', $field ) ? self::get_default_choice_alignment( $field ) : $field->choiceAlignment;
}
/**
* Get the default choice alignment for the multi_choice field.
*
* @since 2.9.0
*
* @param object $field The field object.
* @return string
*/
public static function get_default_choice_alignment( $field) {
/*
* Filter the default choice alignment. Default is vertical. Options are 'vertical' and 'horizontal'.
*
* @since 2.9.0
*
* @param string $default_choice_alignment The default choice alignment.
* @param object $field The field.
*
* @return string
*/
return gf_apply_filters( array( 'gform_default_choice_alignment', $field->formId ), 'vertical', $field );
}
public function get_form_editor_inline_script_on_page_render() {
$alignment = self::get_default_choice_alignment( $this );
return "gform.addAction( 'gform_post_load_field_settings', function( [ field, form ] ) { if( '" . $alignment . "' == 'horizontal' ) { jQuery('#choice_alignment_horizontal').prop('checked', true); } else { jQuery('#choice_alignment_vertical').prop('checked', true); } } );";
}
}
GF_Fields::register( new GF_Field_Multiple_Choice() );

View File

@@ -82,15 +82,14 @@ class GF_Field_Post_Tags extends GF_Field {
$aria_describedby = $this->get_aria_describedby();
// Use the WordPress built-in class "howto" in the form editor.
$text_hint_class = $is_form_editor ? 'howto' : 'gfield_post_tags_hint gfield_description';
$text_hint = '<p class="' . $text_hint_class . '" id="' . $field_id . '_desc">' . gf_apply_filters( array(
$text_hint = '<p class="gfield_post_tags_hint gfield_description" id="' . $field_id . '_desc">' . gf_apply_filters( array(
'gform_post_tags_hint',
$form_id,
$this->id,
), esc_html__( 'Separate tags with commas', 'gravityforms' ), $form_id ) . '</p>';
return "<div class='ginput_container ginput_container_post_tags'>
<input name='input_{$id}' id='{$field_id}' type='text' value='{$value}' class='{$class}' {$tabindex} {$placeholder_attribute} {$required_attribute} {$invalid_attribute} {$aria_describedby} {$disabled_text}/> {$text_hint}
<input name='input_{$id}' id='{$field_id}' type='text' value='{$value}' class='{$class}' {$tabindex} {$placeholder_attribute} {$required_attribute} {$invalid_attribute} {$aria_describedby} {$disabled_text}/>{$text_hint}
</div>";
}

View File

@@ -4,6 +4,7 @@ if ( ! class_exists( 'GFForms' ) ) {
die();
}
require_once( plugin_dir_path( __FILE__ ) . 'field-decorator-choice/class-gf-field-decorator-choice-radio-markup.php' );
class GF_Field_Radio extends GF_Field {
@@ -117,6 +118,11 @@ class GF_Field_Radio extends GF_Field {
public function get_field_input( $form, $value = '', $entry = null ) {
if ( $this->type == 'image_choice' ) {
$this->image_markup = new GF_Field_Decorator_Choice_Radio_Markup( $this );
return $this->image_markup->get_field_input( $form, $value, $entry );
}
$form_id = $form['id'];
$is_entry_detail = $this->is_entry_detail();
$is_form_editor = $this->is_form_editor();
@@ -145,6 +151,17 @@ class GF_Field_Radio extends GF_Field {
$choice_id = 0;
$count = 1;
/**
* A filter that allows for the setting of the maximum number of choices shown in
* the form editor for choice based fields (radio, checkbox, image, and multiple choice).
*
* @since 2.9
*
* @param int $max_choices_visible_count The default number of choices visible is 5.
* @param object $field The current field object.
*/
$max_choices_count = gf_apply_filters( array( 'gform_field_choices_max_count_visible', $form_id ), 5, $this );
$tag = GFCommon::is_legacy_markup_enabled( $form_id ) ? 'li' : 'div';
foreach ( $field_choices as $choice ) {
@@ -157,7 +174,7 @@ class GF_Field_Radio extends GF_Field {
$choices .= $this->get_choice_html( $choice, $choice_id, $value, $disabled_text, $is_admin );
if ( $is_form_editor && $count >= 5 ) {
if ( $is_form_editor && $count >= $max_choices_count ) {
$editor_limited = true;
break;
}
@@ -182,7 +199,7 @@ class GF_Field_Radio extends GF_Field {
$total = sizeof( $field_choices );
if ( $is_form_editor && ( $count < $total ) ) {
$choices .= "<{$tag} class='gchoice_total'>" . sprintf( esc_html__( '%d of %d items shown. Edit field to view all', 'gravityforms' ), $count, $total ) . "</{$tag}>";
$choices .= "<{$tag} class='gchoice_total'><span>" . sprintf( esc_html__( '%d of %d items shown. Edit choices to view all.', 'gravityforms' ), $count, $total ) . "</span></{$tag}>";
}
}
@@ -191,8 +208,8 @@ class GF_Field_Radio extends GF_Field {
*
* @since unknown
*
* @param string $choices The choices HTML.
* @param GF_Field_Radio $field The current field object.
* @param string $choices The choices HTML.
* @param object $field The current field object.
*/
return gf_apply_filters( array( 'gform_field_choices', $this->formId ), $choices, $this );
}
@@ -449,6 +466,7 @@ class GF_Field_Radio extends GF_Field {
$use_value = in_array( 'value', $modifiers );
$format_currency = ! $use_value && in_array( 'currency', $modifiers );
$use_price = $format_currency || ( ! $use_value && in_array( 'price', $modifiers ) );
$image_url = in_array( 'img_url', $modifiers );
if ( is_array( $raw_value ) && (string) intval( $input_id ) != $input_id ) {
$items = array( $input_id => $value ); // Float input Ids. (i.e. 4.1 ). Used when targeting specific checkbox items.
@@ -461,20 +479,32 @@ class GF_Field_Radio extends GF_Field {
$ary = array();
foreach ( $items as $input_id => $item ) {
if ( $use_value ) {
list( $val, $price ) = rgexplode( '|', $item, 2 );
} elseif ( $use_price ) {
list( $name, $val ) = rgexplode( '|', $item, 2 );
if ( $format_currency ) {
$val = GFCommon::to_money( $val, rgar( $entry, 'currency' ) );
}
} elseif ( $this->type == 'post_category' ) {
$use_id = strtolower( $modifier ) == 'id';
$item_value = GFCommon::format_post_category( $item, $use_id );
switch (true) {
case $use_value:
list( $val, $price ) = rgexplode( '|', $item, 2 );
break;
$val = RGFormsModel::is_field_hidden( $form, $this, array(), $entry ) ? '' : $item_value;
} else {
$val = RGFormsModel::is_field_hidden( $form, $this, array(), $entry ) ? '' : RGFormsModel::get_choice_text( $this, $raw_value, $input_id );
case $use_price:
list( $name, $val ) = rgexplode( '|', $item, 2 );
if ( $format_currency ) {
$val = GFCommon::to_money( $val, rgar( $entry, 'currency' ) );
}
break;
case $image_url:
$image_choice = new GF_Field_Image_Choice( $this );
$val = $image_choice->get_merge_tag_img_url( $raw_value, $input_id, $entry, $form, $this );
break;
case $this->type == 'post_category':
$use_id = strtolower( $modifier ) == 'id';
$item_value = GFCommon::format_post_category( $item, $use_id );
$val = RGFormsModel::is_field_hidden( $form, $this, array(), $entry ) ? '' : $item_value;
break;
default:
$val = RGFormsModel::is_field_hidden( $form, $this, array(), $entry ) ? '' : RGFormsModel::get_choice_text( $this, $raw_value, $input_id );
break;
}
$ary[] = GFCommon::format_variable_value( $val, $url_encode, $esc_html, $format );
@@ -551,6 +581,17 @@ class GF_Field_Radio extends GF_Field {
return $operators;
}
/**
* Override to return null instead of the array of inputs in case this is a choice field.
*
* @since 2.9
*
* @return array|null
*/
public function get_entry_inputs() {
return null;
}
}
GF_Fields::register( new GF_Field_Radio() );

View File

@@ -191,7 +191,19 @@ class GF_Field_Repeater extends GF_Field {
$prefix = $sub_field->id . '_';
}
$field_items = $this->flatten( $values, $prefix, $sub_field->is_value_submission_array() );
if ( $sub_field instanceof GF_Field_List ) {
// List field expects an array. If we flatten the List Field values, we end up adding extra repeater items instead of populating the list.
if ( is_array( $values ) && is_array( $values[0] ) ) {
$field_items = array();
foreach ( $values as $key => $value ) {
$field_items[ $prefix . $key ] = $value;
}
} else {
$field_items = array( $prefix . '0' => $values );
}
} else {
$field_items = $this->flatten( is_array( $values ) ? $values : array( $values ), $prefix, $sub_field->is_value_submission_array() );
}
}
$items = array_merge( $items, $field_items );
}
@@ -692,6 +704,8 @@ class GF_Field_Repeater extends GF_Field {
$repeater_fields = array();
$is_new_entry = empty( $_POST[ 'is_submit_' . $this->formId ] ) && ! array_key_exists( 'id', $entry );
foreach ( $repeater_field->fields as $field ) {
if ( is_array( $field->fields ) ) {
$repeater_fields[] = $field;
@@ -706,7 +720,7 @@ class GF_Field_Repeater extends GF_Field {
$input_id = $input['id'];
$key = $input_id . $index . '_' . $i;
$key = $input_id . $index . ( $is_new_entry ? '' : '_' . $i );
$value = isset( $entry[ $key ] ) ? $entry[ $key ] : '';

View File

@@ -154,13 +154,15 @@ class GF_Field_Submit extends GF_Field {
$form_id = absint( $form['id'] );
$is_form_editor = $this->is_form_editor();
$class = esc_attr( 'gform-button gform-button--white ' );
$class_theme = $is_form_editor ? esc_attr( 'gform-theme-button gform-theme-button--secondary ' ) : '';
$class = sprintf( '%s%s', esc_attr( 'gform-button gform-button--white ' ), $class_theme );
$default_text = __( 'Submit', 'gravityforms' );
$button = rgar( $form, 'button', array( 'type' => 'link' ) );
$inline = rgar( $form['button'], 'location', 'bottom' );
// If we're in the editor or the button is inline, display the button. Otherwise, the button will be added to the footer in form_display.php.
// If we're in the editor or the button is inline, display the button.
// Otherwise, the button will be added to the footer in form_display.php.
if ( $is_form_editor || 'inline' == $inline ) {
$submit = GFFormDisplay::get_form_button( $form_id, "gform_submit_button_{$form_id}", $button, $default_text, $class, $default_text, 0 );
return gf_apply_filters( array( 'gform_submit_button', $form_id ), $submit, $form );

View File

@@ -119,15 +119,14 @@ class GF_Field_Text extends GF_Field {
// For Post Tags, Use the WordPress built-in class "howto" in the form editor.
$text_hint = '';
if ( $this->type === 'post_tags' ) {
$text_hint_class = $is_form_editor ? 'howto' : 'gfield_post_tags_hint gfield_description';
$text_hint = '<p class="' . $text_hint_class . '" id="' . $field_id . '_desc">' . gf_apply_filters( array(
$text_hint = '<p class="gfield_post_tags_hint gfield_description" id="' . $field_id . '_desc">' . gf_apply_filters( array(
'gform_post_tags_hint',
$form_id,
$this->id,
), esc_html__( 'Separate tags with commas', 'gravityforms' ), $form_id ) . '</p>';
}
$input = "<input name='input_{$id}' id='{$field_id}' type='{$html_input_type}' value='{$value}' class='{$class}' {$max_length} {$aria_describedby} {$tabindex} {$placeholder_attribute} {$required_attribute} {$invalid_attribute} {$disabled_text} {$autocomplete} /> {$text_hint}";
$input = "<input name='input_{$id}' id='{$field_id}' type='{$html_input_type}' value='{$value}' class='{$class}' {$max_length} {$aria_describedby} {$tabindex} {$placeholder_attribute} {$required_attribute} {$invalid_attribute} {$disabled_text} {$autocomplete} />{$text_hint}";
return sprintf( "<div class='ginput_container ginput_container_text'>%s</div>", $input );
}

View File

@@ -164,7 +164,7 @@ class GF_Field_Textarea extends GF_Field {
$display = $this->useRichTextEditor ? 'block' : 'none';
$input_style = $this->useRichTextEditor ? 'style="display:none;"' : '';
$size = $this->size ? $this->size : 'medium';
$input = "<div id='{$field_id}_rte_preview' class='gform-rte-preview {$size}' style='display:{$display}'>
$input = "<div id='{$field_id}_rte_preview' class='gform-rte-preview gform-theme__disable {$size}' style='display:{$display}'>
<ul class='rte_preview_header'>
<li class='icon'><svg width='24' height='24' viewBox='0 0 24 24' version='1.1' xmlns='http://www.w3.org/2000/svg'><path d='M7.761 19c-.253 0-.44-.06-.56-.18-.12-.12-.18-.307-.18-.56l-.02-12.52c0-.267.08-.457.2-.57.12-.113.307-.17.56-.17h5.019c1.56 0 2.723.317 3.49.95.767.633 1.15 1.497 1.15 2.59 0 .667-.21 1.283-.63 1.85-.42.567-.937.963-1.55 1.19l.02.08c.387.067.793.247 1.22.54.427.293.787.677 1.08 1.15.293.473.44 1.01.44 1.61 0 1.32-.427 2.323-1.28 3.01-.853.687-2.12 1.03-3.8 1.03H7.761zm4.969-8.26c.687 0 1.237-.17 1.65-.51.414-.34.621-.77.621-1.29 0-1.147-.757-1.72-2.271-1.72H9.28v3.52h3.45zm.59 6.02c.725 0 1.283-.17 1.674-.51.39-.34.586-.823.586-1.45 0-.587-.223-1.037-.67-1.35-.446-.313-1.088-.47-1.925-.47H9.28v3.78h4.04z' fill='#555d66' stroke='none' stroke-width='1' fill-rule='evenodd'/></svg></li>
<li class='icon'><svg width='24' height='24' viewBox='0 0 24 24' ersion='1.1' xmlns='http://www.w3.org/2000/svg'><path d='M16.746 4.723l-.176.84h-.263c-.638 0-1.097.12-1.377.36-.28.242-.47.6-.567 1.075l-2.07 9.805c-.059.28-.088.465-.088.556 0 .534.482.801 1.445.801h.254l-.175.84h-5.82l.155-.84h.264c1.107 0 1.761-.478 1.963-1.435l2.08-9.805c.052-.247.078-.433.078-.557 0-.534-.482-.8-1.445-.8h-.254l.176-.84h5.82z' fill='#555d66' stroke='none' stroke-width='1' fill-rule='evenodd'/></svg></li>

View File

@@ -62,6 +62,17 @@ class GF_Field extends stdClass implements ArrayAccess {
}
}
/**
* Whether the choice field has entries that persist after changing the field type.
*
* @since 2.9
*
* @return boolean
*/
public function has_persistent_choices() {
return in_array( $this->type, array( 'multi_choice', 'image_choice' ) );
}
/**
* Fires the deprecation notice only once per page. Not fired during AJAX requests.
*
@@ -330,6 +341,26 @@ class GF_Field extends stdClass implements ArrayAccess {
return 'gform-icon--cog';
}
/**
* Returns the form editor icon for the field type.
*
* Sometimes the field type and the input type are not the same, but we want the field type icon.
*
* @since 2.9.0
*
* @return string
*/
public function get_form_editor_field_type_icon() {
if ( $this->type !== $this->inputType ) {
$field_class = GF_Fields::get( $this->type );
if ( $field_class ) {
return $field_class->get_form_editor_field_icon();
}
}
return $this->get_form_editor_field_icon();
}
/**
* Returns the field's form editor description.
*
@@ -550,8 +581,7 @@ class GF_Field extends stdClass implements ArrayAccess {
$disable_ajax_reload = $this->disable_ajax_reload();
$ajax_reload_id = $disable_ajax_reload === 'skip' || $disable_ajax_reload === 'true' || $disable_ajax_reload === true ? 'true' : esc_attr( rgar( $atts, 'id' ) );
$is_form_editor = $this->is_form_editor();
$target_input_id = esc_attr( rgar( $atts, 'id' ) );
$target_input_id = esc_attr( rgar( $atts, 'id' ) );
// Get the field sidebar messages, this could be an array of messages or a warning message string.
$field_sidebar_messages = GFCommon::is_form_editor() ? $this->get_field_sidebar_messages() : '';
@@ -565,7 +595,7 @@ class GF_Field extends stdClass implements ArrayAccess {
}
if ( ! empty( $sidebar_message_content ) ) {
$atts['class'] .= ' gfield_' . ( $sidebar_message_type === 'error' ? 'warning' : $sidebar_message_type );
$atts['class'] .= ' gfield-has-sidebar-message gfield-has-sidebar-message--type-' . ( $sidebar_message_type === 'error' ? 'warning' : $sidebar_message_type );
if ( $sidebar_message_type === 'error' ) {
$atts['aria-invalid'] = 'true';
}
@@ -583,7 +613,7 @@ class GF_Field extends stdClass implements ArrayAccess {
rgar( $atts, 'data-field-position' ) ? ' data-field-position="' . esc_attr( $atts['data-field-position'] ) . '"' : '',
$ajax_reload_id,
rgar( $atts, 'aria-invalid' ) ? ' aria-invalid="true"' : '',
empty( $sidebar_message_content ) ? '' : '<span class="field-' . $sidebar_message_type . '-message-content hidden">' . \GFCommon::maybe_wp_kses( $sidebar_message_content ) . '</span>'
empty( $sidebar_message_content ) ? '' : '<span class="field-sidebar-message-content field-sidebar-message-content--type-' . $sidebar_message_type . ' hidden">' . \GFCommon::maybe_wp_kses( $sidebar_message_content ) . '</span>'
);
}
@@ -652,7 +682,16 @@ class GF_Field extends stdClass implements ArrayAccess {
} else {
$label = wp_strip_all_tags( $this->get_field_label( true, '' ) );
}
return sprintf( '%1$s - %2$s, %3$s.', esc_attr( $label ), esc_attr( $this->type ), esc_attr( $action ) );
// Sometimes the field editor label is different from the field type, so make sure we're using the correct field type.
$field_class = GF_Fields::get( $this->type );
if ( is_object( $field_class ) ) {
$field_title = $field_class->get_form_editor_field_title();
} else {
$field_title = $this->type;
}
return sprintf( '%1$s - %2$s, %3$s.', esc_attr( $label ), esc_attr( $field_title ), esc_attr( $action ) );
}
// # SUBMISSION -----------------------------------------------------------------------------------------------------
@@ -668,6 +707,50 @@ class GF_Field extends stdClass implements ArrayAccess {
return false;
}
/**
* Returns the input ID given the choice key for Multiple Choice and Image Choice fields.
*
* @since 2.9
*
* @param string $key The choice key.
*
* @return string
*/
public function get_input_id_from_choice_key( $key ) {
$input_id = '';
if ( is_array( $this->inputs ) ) {
foreach ( $this->inputs as $input ) {
if ( rgar( $input, 'key' ) == $key ) {
$input_id = rgar( $input, 'id' );
break;
}
}
}
return $input_id;
}
/**
* Returns the choice ID given the input key for Multiple Choice and Image Choice fields.
*
* @since 2.9
*
* @param string $key The choice key.
*
* @return string
*/
public function get_choice_id_from_input_key( $key ) {
$choice_id = '';
if ( is_array( $this->choices ) ) {
foreach ( $this->choices as $index => $choice ) {
if ( rgar( $choice, 'key' ) == $key ) {
$choice_id = $index;
break;
}
}
}
return $choice_id;
}
/**
* Used to determine the required validation result.
*
@@ -679,7 +762,8 @@ class GF_Field extends stdClass implements ArrayAccess {
$copy_values_option_activated = $this->enableCopyValuesOption && rgpost( 'input_' . $this->id . '_copy_values_activated' );
if ( is_array( $this->inputs ) ) {
// GF_Field_Radio can now have inputs if it's a Choice or Image Choice field
if ( is_array( $this->inputs ) && ! ( $this instanceof GF_Field_Radio ) ) {
foreach ( $this->inputs as $input ) {
if ( $copy_values_option_activated ) {
@@ -1662,7 +1746,7 @@ class GF_Field extends stdClass implements ArrayAccess {
$drag_handle = '';
}
$field_icon = '<span class="gfield-field-action gfield-icon" title="' . $this->get_form_editor_field_title() . '">' . GFCommon::get_icon_markup( array( 'icon' => $this->get_form_editor_field_icon() ) ) . '</span>';
$field_icon = '<span class="gfield-field-action gfield-icon" title="' . $this->get_form_editor_field_title() . '">' . GFCommon::get_icon_markup( array( 'icon' => $this->get_form_editor_field_type_icon() ) ) . '</span>';
$field_id = '<span class="gfield-compact-icon--id">' . sprintf( esc_html__( 'ID: %s', 'gravityforms' ), $this->id ) . '</span>';
@@ -1671,21 +1755,30 @@ class GF_Field extends stdClass implements ArrayAccess {
$field_sidebar_messages = $this->get_field_sidebar_messages();
$sidebar_message = is_array( rgar( $field_sidebar_messages, '0' ) ) ? array_shift( $field_sidebar_messages ) : $field_sidebar_messages;
$compact_view_warning_icon = '';
$compact_view_sidebar_message_icon = '';
if ( ! empty( $sidebar_message ) ) {
$compact_view_warning_icon = rgar( $sidebar_message, 'type' ) === 'warning' || ! is_array( $sidebar_message ) ? '<span class="gform-icon gform-icon--exclamation-simple gform-icon--preset-active gform-icon-preset--status-error gform-compact-view-warning-icon" ></span>' : '';
$sidebar_message_types = array(
'warning' => array( 'gform-icon--exclamation-simple', 'gform-icon-preset--status-error' ),
'error' => array( 'gform-icon--exclamation-simple', 'gform-icon-preset--status-error' ),
'info' => array( 'gform-icon--information-simple', 'gform-icon-preset--status-info' ),
'notice' => array( 'gform-icon--information-simple', 'gform-icon-preset--status-info' ),
'success' => array( 'gform-icon--checkmark-simple', 'gform-icon-preset--status-correct' ),
);
$compact_view_sidebar_message_icon_type = is_array( $field_sidebar_messages ) ? rgar( $sidebar_message, 'type' ) : 'warning';
$compact_view_sidebar_message_icon_helper_text = is_array( $field_sidebar_messages ) ? rgar( $sidebar_message, 'icon_helper_text' ) : __( 'This field has an issue', 'gravityforms' );
$compact_view_sidebar_message_icon = sprintf( '<span class="gfield-sidebar-message-icon gform-icon gform-icon--preset-active %1$s" title="%2$s" aria-label="%2$s"></span>', implode( ' ', $sidebar_message_types[ $compact_view_sidebar_message_icon_type ] ), esc_attr( $compact_view_sidebar_message_icon_helper_text ) );
}
$admin_buttons = "
<div class='gfield-admin-icons'>
<div class='gfield-admin-icons gform-theme__disable'>
{$drag_handle}
{$duplicate_field_link}
{$edit_field_link}
{$delete_field_link}
{$compact_view_warning_icon}
{$compact_view_sidebar_message_icon}
{$field_icon}
</div>
<div class='gfield-compact-icons'>
<div class='gfield-compact-icons gform-theme__disable'>
{$field_id}
{$conditional}
</div>";
@@ -1713,7 +1806,7 @@ class GF_Field extends stdClass implements ArrayAccess {
*/
public function get_hidden_admin_markup() {
return "<div class='admin-hidden-markup'><i class='gform-icon gform-icon--hidden'></i><span>Hidden</span></div>";
return '<div class="admin-hidden-markup"><i class="gform-icon gform-icon--hidden" aria-hidden="true" title="'. esc_attr( __( 'This field is hidden when viewing the form', 'gravityforms' ) ) .'"></i><span>'. esc_attr( __( 'This field is hidden when viewing the form', 'gravityforms' ) ) .'</span></div>';
}

View File

@@ -0,0 +1,159 @@
<?php
require_once( plugin_dir_path( __FILE__ ) . 'class-gf-field-decorator-choice.php' );
class GF_Field_Decorator_Choice_Checkbox_Markup extends ChoiceDecorator {
public function get_field_input( $form, $value = '', $entry = null ) {
$form_id = absint( $form['id'] );
$is_entry_detail = $this->field->is_entry_detail();
$is_form_editor = $this->field->is_form_editor();
$id = $this->field->id;
$field_id = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id";
$disabled_text = $is_form_editor ? 'disabled="disabled"' : '';
$limit_message = $this->get_limit_message();
$image_style_classes = $this->get_field_classes( $form_id, $this->field );
// Get checkbox choices markup.
$choices_markup = $this->get_checkbox_choices( $value, $disabled_text, $form, $field_id );
return sprintf(
"<div class='ginput_container ginput_container_checkbox ginput_container_image_choice %s'>%s%s</div>",
$image_style_classes,
$limit_message,
$choices_markup
);
}
public function get_checkbox_choices( $value, $disabled_text, $form, $field_id ) {
$form_id = absint( $form['id'] );
if ( GFCommon::is_legacy_markup_enabled( $form_id ) ) {
return '';
}
$choices = '';
$is_entry_detail = $this->field->is_entry_detail();
$is_form_editor = $this->field->is_form_editor();
if ( is_array( $this->field->choices ) ) {
$choice_number = 1;
$count = 1;
/**
* A filter that allows for the setting of the maximum number of choices shown in
* the form editor for choice based fields (radio, checkbox, image, and multiple choice).
*
* @since 2.9
*
* @param int $max_choices_visible_count The default number of choices visible is 8.
* @param object $field The current field object.
*/
$max_choices_count = gf_apply_filters( array( 'gform_field_choices_max_count_visible', $this->field->formId ), 8, $this->field );
$choices .= sprintf( '<div class="gfield_checkbox" id="%s">', esc_attr( $field_id ) );
foreach ( $this->field->choices as $choice ) {
// Hack to skip numbers ending in 0, so that 5.1 doesn't conflict with 5.10.
if ( $choice_number % 10 == 0 ) {
$choice_number ++;
}
// Prepare input ID.
if ( $is_entry_detail || $is_form_editor || $form_id == 0 ) {
$id = $this->field->id . '_' . $choice_number;
} else {
$id = $form_id . '_' . $this->field->id . '_' . $choice_number;
}
// Handling of input/image aria-describedby
$image = $this->get_image_markup( $choice, $id, $choice_number, $form );
$image_aria_describedby = 'gchoice_image_' . $id;
$aria_describedby = '';
if ( $choice_number === 1 ) {
$image_aria_describedby = $image_aria_describedby ? array( $image_aria_describedby ) : array();
$aria_describedby = $this->field->get_choice_aria_describedby( $form_id, $image_aria_describedby );
} elseif ( $image_aria_describedby ) {
$aria_describedby = sprintf( 'aria-describedby="%s"', $image_aria_describedby );
}
$choice_number ++;
// Prepare choice attributes.
$input_id = $this->get_input_id_from_choice_key( $choice['key'] );
$checked = $this->field->get_checked_attribute( $choice, $value, $input_id, $form_id );
$tabindex = $this->field->get_tabindex();
$choice_value = $choice['value'];
if ( $this->field->enablePrice ) {
$price = rgempty( 'price', $choice ) ? 0 : GFCommon::to_number( rgar( $choice, 'price' ) );
$choice_value .= '|' . $price;
}
$choice_value = esc_attr( $choice_value );
$choice_markup = "<div class='gchoice gchoice_{$id}'>
<span class='gfield-image-choice-wrapper-outer'>
{$image}
<span class='gfield-image-choice-wrapper-inner'>
<input class='gfield-choice-input' name='input_{$input_id}' type='checkbox' value='{$choice_value}' {$checked} id='choice_{$id}' {$tabindex} {$disabled_text} {$aria_describedby}/>
<label for='choice_{$id}' id='label_{$id}' class='gform-field-label gform-field-label--type-inline'>
{$choice['text']}
</label>
</span>
</span>
</div>";
/**
* Override the default choice markup used when rendering radio button, checkbox and drop down type fields.
*
* @since 1.9.6
*
* @param string $choice_markup The string containing the choice markup to be filtered.
* @param array $choice An associative array containing the choice properties.
* @param object $field The field currently being processed.
* @param string $value The value to be selected if the field is being populated.
*/
$choices .= gf_apply_filters( array(
'gform_field_choice_markup_pre_render',
$this->field->formId,
$this->field->id
), $choice_markup, $choice, $this->field, $value );
$is_admin = $is_entry_detail || $is_form_editor;
if ( $is_admin && rgget( 'view' ) != 'entry' && $count >= $max_choices_count ) {
break;
}
$count ++;
}
$choices .= '</div>';
$total = sizeof( $this->field->choices );
if ( $count < $total ) {
$choices .= "<div class='gchoice_total'><span>"
. sprintf( esc_html__( '%d of %d items shown. Edit choices to view all.', 'gravityforms' ), $count, $total ) .
"</span></div>";
}
}
/**
* Modify the checkbox items before they are added to the checkbox list.
*
* @since Unknown
*
* @param string $choices The string containing the choices to be filtered.
* @param object $field The field currently being processed.
*/
return gf_apply_filters( array( 'gform_field_choices', $this->field->formId ), $choices, $this->field );
}
}

View File

@@ -0,0 +1,192 @@
<?php
require_once( plugin_dir_path( __FILE__ ) . 'class-gf-field-decorator-choice.php' );
class GF_Field_Decorator_Choice_Radio_Markup extends ChoiceDecorator {
public function get_field_input( $form, $value = '', $entry = null ) {
$form_id = $form['id'];
$is_entry_detail = $this->field->is_entry_detail();
$is_form_editor = $this->field->is_form_editor();
$id = $this->field->id;
$field_id = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id";
$disabled_text = $is_form_editor ? 'disabled="disabled"' : '';
$image_style_classes = $this->get_field_classes( $form_id, $this->field );
return sprintf( "<div class='ginput_container ginput_container_radio ginput_container_image_choice {$image_style_classes}'>%s</div>", $this->get_radio_choices( $value, $disabled_text, $form, $field_id ) );
}
public function get_radio_choices( $value, $disabled_text, $form, $field_id ) {
$choices = '';
if ( is_array( $this->field->choices ) ) {
$is_entry_detail = $this->field->is_entry_detail();
$is_form_editor = $this->field->is_form_editor();
$is_admin = $is_entry_detail || $is_form_editor;
$field_choices = $this->field->choices;
$needs_other_choice = $this->field->enableOtherChoice;
$editor_limited = false;
$choice_id = 0;
$count = 1;
/**
* A filter that allows for the setting of the maximum number of choices shown in
* the form editor for choice based fields (radio, checkbox, image, and multiple choice).
*
* @since 2.9
*
* @param int $max_choices_visible_count The default number of choices visible is 8.
* @param object $field The current field object.
*/
$max_choices_count = gf_apply_filters( array( 'gform_field_choices_max_count_visible', $this->field->formId ), 8, $this->field );
$choices .= sprintf( '<div class="gfield_radio" id="%s">', esc_attr( $field_id ) );
foreach ( $field_choices as $choice ) {
if ( rgar( $choice, 'isOtherChoice' ) ) {
if ( ! $needs_other_choice ) {
continue;
}
$needs_other_choice = false;
}
$choices .= $this->get_choice_html( $choice, $choice_id, $value, $disabled_text, $is_admin, $form );
if ( $is_form_editor && $count >= $max_choices_count ) {
$editor_limited = true;
break;
}
$count ++;
}
if ( $needs_other_choice ) {
$other_choice = array(
'text' => GFCommon::get_other_choice_value( $this ),
'value' => 'gf_other_choice',
'isSelected' => false,
'isOtherChoice' => true,
);
$field_choices[] = $other_choice;
if ( ! $is_form_editor || ! $editor_limited ) {
$choices .= $this->get_choice_html( $other_choice, $choice_id, $value, $disabled_text, $is_admin );
$count ++;
}
}
$choices .= '</div>';
$total = sizeof( $field_choices );
if ( $is_form_editor && ( $count < $total ) ) {
$choices .= "<div class='gchoice_total'><span>" . sprintf( esc_html__( '%d of %d items shown. Edit choices to view all.', 'gravityforms' ), $count, $total ) . "</span></div>";
}
}
/**
* Allows the HTML for multiple choices to be overridden.
*
* @since unknown
*
* @param string $choices The choices HTML.
* @param object $field The current field object.
*/
return gf_apply_filters( array( 'gform_field_choices', $this->field->formId ), $choices, $this->field );
}
public function get_choice_html( $choice, &$choice_id, $value, $disabled_text, $is_admin, $form = null ) {
$form_id = absint( $this->field->formId );
if ( GFCommon::is_legacy_markup_enabled( $form_id ) ) {
return '';
}
if ( $is_admin || $form_id == 0 ) {
$id = $this->field->id . '_' . $choice_id ++;
} else {
$id = $form_id . '_' . $this->field->id . '_' . $choice_id ++;
}
$field_value = ! empty( $choice['value'] ) || $this->field->enableChoiceValue ? $choice['value'] : $choice['text'];
if ( $this->field->enablePrice ) {
$price = rgempty( 'price', $choice ) ? 0 : GFCommon::to_number( rgar( $choice, 'price' ) );
$field_value .= '|' . $price;
}
if ( rgblank( $value ) && rgget( 'view' ) != 'entry' ) {
$checked = rgar( $choice, 'isSelected' ) ? "checked='checked'" : '';
} else {
$checked = GFFormsModel::choice_value_match( $this->field, $choice, $value ) ? "checked='checked'" : '';
}
$tabindex = $this->field->get_tabindex();
$image = $this->get_image_markup( $choice, $id, $choice_id, $form );
$image_aria_describedby = 'gchoice_image_' . $id;
// Handle 'other' choice.
$other = '';
if ( $this->field->enableOtherChoice && rgar( $choice, 'isOtherChoice' ) ) {
$input_disabled_text = $disabled_text;
if ( $value == 'gf_other_choice' && rgpost( "input_{$this->field->id}_other" ) ) {
$other_value = rgpost( "input_{$this->field->id}_other" );
} elseif ( ! empty( $value ) && ! GFFormsModel::choices_value_match( $this->field, $this->field->choices, $value ) ) {
$other_value = $value;
$value = 'gf_other_choice';
$checked = "checked='checked'";
} else {
if ( ! $input_disabled_text ) {
$input_disabled_text = "disabled='disabled'";
}
$other_value = empty( $choice['text'] ) ? GFCommon::get_other_choice_value( $this->field ) : $choice['text'];
}
$other = "<br /><input id='input_{$this->field->formId}_{$this->field->id}_other' class='gchoice_other_control' name='input_{$this->field->id}_other' type='text' value='" . esc_attr( $other_value ) . "' aria-label='" . esc_attr__( 'Other Choice, please specify', 'gravityforms' ) . "' $tabindex $input_disabled_text />";
}
// Handling of input/image aria-describedby
$aria_describedby = '';
if ( $this->add_aria_description( $checked, $choice_id ) ) {
$image_aria_describedby = $image_aria_describedby ? array( $image_aria_describedby ) : array();
$aria_describedby = $this->get_aria_describedby( $image_aria_describedby );
} else if ( $image_aria_describedby ) {
$aria_describedby = sprintf( 'aria-describedby="%s"', $image_aria_describedby );
}
$choice_value = esc_attr( $field_value );
$choice_markup = "<div class='gchoice gchoice_{$id}'>
<span class='gfield-image-choice-wrapper-outer'>
{$image}
<span class='gfield-image-choice-wrapper-inner'>
<input class='gfield-choice-input' name='input_{$this->field->id}' type='radio' value='{$choice_value}' {$checked} id='choice_{$id}' onchange='gformToggleRadioOther( this )' {$tabindex} {$disabled_text} {$aria_describedby}/>
<label for='choice_{$id}' id='label_{$id}' class='gform-field-label gform-field-label--type-inline'>
{$choice['text']}
</label>
</span>
{$other}
</span>
</div>";
/**
* Allows the HTML for a specific choice to be overridden.
*
* @since 1.9.6
* @since 1.9.12 Added the field specific version.
* @since 2.4.17 Moved from GF_Field_Radio::get_radio_choices().
*
* @param string $choice_markup The choice HTML.
* @param array $choice The choice properties.
* @param GF_Field_Radio $field The current field object.
* @param string $value The current field value.
*/
return gf_apply_filters( array( 'gform_field_choice_markup_pre_render', $this->field->formId, $this->field->id ), $choice_markup, $choice, $this->field, $value );
}
}

View File

@@ -0,0 +1,93 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class ChoiceDecorator {
/**
* @var GF_Field
*/
protected $field;
public function __construct( $field ) {
$this->field = $field;
}
public function __call( $name, $args ) {
return call_user_func_array( array( $this->field, $name ), $args );
}
/**
* Get the style classes for the image choice field.
*
* @since 2.9
*
* @param $form_id
* @param $field_id
*
* @return string
*/
public function get_field_classes( $form_id, $field ) {
// Choice label visibility class
$choice_label_visibility = GF_Field_Image_Choice::get_image_choice_label_visibility_setting( $field );
$label_visibility_class = "ginput_container_image_choice--label-{$choice_label_visibility}";
// Choice input visibility class
$choice_input_visibility = GF_Field_Image_Choice::get_image_choice_input_visibility_setting( $field );
$input_visibility = ( $choice_input_visibility === 'show' ) && ( $choice_label_visibility === 'show' ) ? 'show' : 'hide';
$input_visibility_class = "ginput_container_image_choice--input-{$input_visibility}";
return $label_visibility_class . ' ' . $input_visibility_class;
}
/**
* Get the image markup for a choice field.
*
* @since 2.9
*
* @param $choice
* @param $choice_id
* @param $choice_number
* @param $form
*
* @return string
*/
public function get_image_markup( $choice, $choice_id, $choice_number, $form ) {
$image_aria_describedby = 'gchoice_image_' . $choice_id;
if ( ! empty( $choice['attachment_id'] ) ) {
$image_alt = get_post_meta( $choice['attachment_id'], '_wp_attachment_image_alt', true );
$image_alt = ! empty( $image_alt ) ? $image_alt : sprintf( '%s %d', __( 'Image for choice number', 'gravityforms' ), $choice_number );
$image_size = isset( $form['styles'] ) && rgar( $form['styles'], 'inputImageChoiceSize' ) ? $form['styles']['inputImageChoiceSize'] : 'md';
$image = wp_get_attachment_image(
$choice['attachment_id'],
'gform-image-choice-' . $image_size,
false,
array(
'class' => 'gfield-choice-image',
'alt' => $image_alt,
'id' => $image_aria_describedby,
'loading' => 'false',
)
);
} else {
$image = sprintf(
'<span class="gfield-choice-image-no-image" id="%s"><span>%s</span></span>',
$image_aria_describedby,
sprintf(
'%s %d %s',
__( 'Choice number', 'gravityforms' ),
$choice_number,
__( 'does not have an image', 'gravityforms' )
)
);
}
return sprintf( '<div class="gfield-choice-image-wrapper">%s</div>', $image );
}
}
GFCommon::glob_require_once( '/includes/fields/field-decorator-choice/class-gf-field-decorator-choice-*.php' );