plugin install

This commit is contained in:
Tony Volpe
2024-06-18 17:29:05 -04:00
parent e1aaedd1ae
commit 41f50eacc4
5880 changed files with 1057631 additions and 39681 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,85 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
class Button extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'button';
/**
* Button input type.
*
* @since 2.5
*
* @var string
*/
public $input_type = 'button';
/**
* Button classes.
*
* @var string
*/
public $class = 'gform_settings_button button';
/**
* Initialize Button field.
*
* @since 2.5
*
* @param array $props Field properties.
* @param \Gravity_Forms\Gravity_Forms\Settings\Settings $settings Settings instance.
*/
public function __construct( $props, $settings ) {
parent::__construct( $props, $settings );
// Set default value.
if ( ! isset( $this->default_value ) ) {
$this->default_value = esc_html__( 'Submit', 'gravityforms' );
}
}
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
// Prepare markup.
$html = sprintf(
'<button type="%s" name="%s" value="save" %s>%s</button>',
$this->input_type,
$this->name,
implode( ' ', $this->get_attributes() ),
esc_attr( $this->default_value )
);
return $html;
}
}
Fields::register( 'button', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Button' );

View File

@@ -0,0 +1,174 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use GFCommon;
use Gravity_Forms\Gravity_Forms\Settings\Settings;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
// Load base class.
require_once 'class-radio.php';
class Card extends Radio {
/**
* Field type.
*
* @since 2.5.6
*
* @var string
*/
public $type = 'card';
/**
* Field choices.
*
* @since 2.5
*
* @var array
*/
public $choices = array();
/**
* Initialize field.
*
* @since 2.5
*
* @param array $props Field properties.
* @param Settings $settings Settings instance.
*/
public function __construct( $props, $settings ) {
// Prepare class.
$class = array( 'gform-settings-field__radio--visual' );
// Convert defined classes.
if ( rgar( $props, 'class' ) ) {
$props['class'] = explode( ' ', $props['class'] );
$class = array_merge( $class, $props['class'] );
unset( $props['class'] );
}
// Define class.
$this->class = implode( ' ', $class );
parent::__construct( $props, $settings );
}
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
// Get choices, selected value, classes.
$choices = $this->get_choices();
$selected_value = $this->get_value();
$has_icons = self::has_icons( $choices );
$has_tag = self::has_tag( $choices );
$icon_class = ' gform-settings-choice--visual';
// If no choices exist, return.
if ( $choices === false || empty( $choices ) ) {
return '';
}
// Display description.
$html = $this->get_description();
$html .= '<span class="' . esc_attr( $this->get_container_classes() ) . '">';
// Create inputs from choices.
foreach ( $choices as $i => $choice ) {
// Prepare choice attributes.
$choice['id'] = rgempty( 'id', $choice ) ? sprintf( '%s%s', $this->name, $i ) : $choice['id'];
$choice_value = rgar( $choice, 'value' ) ? $choice['value'] : $choice['label'];
$choice_tooltip = $this->settings->maybe_get_tooltip( $choice );
// Get input.
$choice_input = sprintf(
'<input type="radio" name="%s_%s" value="%s" %s %s />',
$this->settings->get_input_name_prefix(),
$this->name,
$choice_value,
checked( $selected_value, $choice_value, false ),
implode( ' ', self::get_choice_attributes( $choice, $this->get_attributes() ) )
);
// Get icon markup.
if ( $has_icons ) {
// Get the defined icon or use default.
$icon_markup = GFCommon::get_icon_markup( $choice, 'fa-cog' );
}
// Get tag markup.
if ( $has_tag ) {
$color = rgar( $choice, 'color' ) ? $choice['color'] : 'port';
$tag_markup = '<span class="gform-settings-card-tag gform-c-' . $color . '"><i class="gform-icon gform-icon--circle-star"></i>' . esc_html( $choice['tag'] ) . '</span>';
}
// Get description markup.
$description_markup = '<div class="gform-settings-card--description"><div class="gform-settings-card--description-content">';
if ( rgar( $choice, 'title' ) ) {
$description_markup .= '<strong>' . esc_html( $choice['title'] ) . '</strong>';
}
if ( rgar( $choice, 'description' ) ) {
$description_markup .= '<p>' . esc_html( $choice['description'] ) . '</p>';
}
$description_markup .= '</div></div>';
$html .= sprintf(
'<div id="gform-settings-radio-choice-%1$s" class="gform-settings-choice%2$s">
%3$s
<label for="%1$s">
<span>%4$s %5$s %6$s %7$s</span>
</label>
%8$s
</div>',
esc_attr( $choice['id'] ),
$icon_class,
$choice_input,
isset( $icon_markup ) ? $icon_markup : '',
esc_html( $choice['label'] ),
isset( $tag_markup ) ? $tag_markup : '',
$choice_tooltip,
$description_markup
);
}
// Wrap visual choices with container.
if ( ! empty( $icon_class ) ) {
$html = sprintf(
'<div class="gform-settings-choices--visual">%s</div>',
$html
);
}
// If field failed validation, add error icon.
$html .= $this->get_error_icon();
$html .= '</span>';
return $html;
}
}
Fields::register( 'card', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Card' );

View File

@@ -0,0 +1,205 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
// Load base classes.
require_once 'class-checkbox.php';
require_once 'class-select.php';
class Checkbox_And_Select extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'checkbox_and_select';
/**
* Child inputs.
*
* @since 2.5
*
* @var Base[]
*/
public $inputs = array();
/**
* Initialize Checkbox and Select field.
*
* @since 2.5
*
* @param array $props Field properties.
* @param \Gravity_Forms\Gravity_Forms\Settings\Settings $settings Settings instance.
*/
public function __construct( $props, $settings ) {
parent::__construct( $props, $settings );
// Prepare Checkbox field.
$checkbox_input = rgars( $props, 'checkbox' );
$checkbox_field = array(
'type' => 'checkbox',
'name' => rgar( $props, 'name' ) . 'Enable',
'label' => esc_html__( 'Enable', 'gravityforms' ),
'horizontal' => true,
'value' => '1',
'choices' => false,
'tooltip' => false,
);
$this->inputs['checkbox'] = wp_parse_args( $checkbox_input, $checkbox_field );
$this->inputs['checkbox'] = Fields::create( $this->inputs['checkbox'], $this->settings );
// Prepare Select field.
$select_input = rgars( $props, 'select' );
$select_field = array(
'name' => rgar( $props, 'name' ) . 'Value',
'type' => 'select',
'class' => '',
'tooltip' => false,
'disabled' => $this->inputs['checkbox']->get_value() ? false : true,
);
$select_field['class'] .= ' ' . $select_field['name'];
$this->inputs['select'] = wp_parse_args( $select_input, $select_field );
// Add on change event to Checkbox.
if ( empty( $this->inputs['checkbox']['choices'] ) ) {
$this->inputs['checkbox']['choices'] = array(
array(
'name' => $this->inputs['checkbox']['name'],
'label' => $this->inputs['checkbox']['label'],
'onchange' => sprintf(
"( function( $, elem ) {
$( elem ).parents( 'td' ).css( 'position', 'relative' );
if( $( elem ).prop( 'checked' ) ) {
$( '%1\$s' ).prop( 'disabled', false );
} else {
$( '%1\$s' ).prop( 'disabled', true );
}
} )( jQuery, this );",
"#{$this->inputs['select']['name']}Span select" ),
),
);
}
$this->inputs['select'] = Fields::create( $this->inputs['select'], $this->settings );
}
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
// Prepare markup.
// Display description.
$html = $this->get_description();
// Settings are more up-to-date at this point; set the disabled attr on the select based on checkbox state.
$this->inputs['select']->disabled = ! $this->inputs['checkbox']->get_value();
$html .= sprintf(
'<span class="%s">%s <span id="%s" class="gform-settings-input__target">%s %s</span></span>',
esc_attr( $this->get_container_classes() ),
$this->inputs['checkbox']->markup(),
$this->inputs['select']->name . 'Span',
$this->inputs['select']->markup(),
$this->settings->maybe_get_tooltip( $this->inputs['select'] )
);
$html .= $this->get_error_icon();
return $html;
}
/**
* Get the correctly-grouped values from $_POST for use in validation.
*
* @since 2.5
*
* @param array $values The $_POST values.
*
* @return array
*/
public function get_values_from_post( $values ) {
$return_values = array();
$cb_name = $this->inputs['checkbox']->name;
$select_name = $this->inputs['select']->name;
if ( isset( $values[ $cb_name ] ) ) {
$return_values['checkbox'] = $values[ $cb_name ];
}
if ( isset( $values[ $select_name ] ) ) {
$return_values['select'] = $values[ $select_name ];
}
return $return_values;
}
/**
* Filter out unneeded select values when the checkbox isn't checked.
*
* @since 2.5
*
* @param array $field_values Posted field values.
* @param array|bool|string $field_value Posted value for field.
*
* @return array
*/
public function save_field( $field_values, $field_value ) {
$field_values = parent::save_field( $field_values, $field_value );
$cb_value = isset( $field_values[ $this->inputs['checkbox']->name ] ) ? $field_values[ $this->inputs['checkbox']->name ] : 0;
// Checkbox is unchecked, remove the select value.
if ( $cb_value == 0 ) {
unset( $field_values[ $this->inputs['select']->name ] );
}
return $field_values;
}
// # VALIDATION METHODS --------------------------------------------------------------------------------------------
/**
* Validate posted field value.
*
* @since 2.5-beta-3
*
* @param array $values Posted field values.
*/
public function do_validation( $values ) {
$cb_value = isset( $values['checkbox'] ) ? $values['checkbox'] : null;
$select_value = isset( $values['select'] ) ? $values['select'] : null;
if ( ! isset( $cb_value[0] ) || $cb_value[0] != 1 ) {
return;
}
if ( isset( $cb_value[0] ) && $cb_value[0] == 1 ) {
$this->inputs['select']->required = true;
}
$this->inputs['checkbox']->handle_validation( $cb_value );
$this->inputs['select']->handle_validation( $select_value );
}
}
Fields::register( 'checkbox_and_select', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Checkbox_and_Select' );

View File

@@ -0,0 +1,388 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
class Checkbox extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'checkbox';
/**
* Field choices.
*
* @since 2.5
*
* @var array
*/
public $choices = array();
/**
* Data Format.
*
* Set this to "array" to return an array of values.
*
* @since 2.6
*
* @var string
*/
public $data_format = 'bool';
public $onclick;
protected $no_choices;
protected $horizontal;
protected $enabledLabel;
protected $onkeypress;
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
if ( $this->data_format === 'array' ) {
return $this->array_markup();
}
// Get choices, determine if choices have icon, display direction, default choice attributes.
$choices = $this->get_choices();
$have_icon = self::has_icons( $choices );
$horizontal_class = rgobj( $this, 'horizontal' ) || $have_icon ? ' gform-settings-choice--inline' : '';
$default_choice_attributes = array(
'onclick' => 'event.target.previousSibling.value = ( this.checked ? 1 : 0 ); event.target.previousSibling.dispatchEvent( new Event( "change" ) );',
'onkeypress' => 'event.target.previousSibling.value = ( this.checked ? 1 : 0 ); event.target.previousSibling.dispatchEvent( new Event( "change" ) );',
);
// If no choices exist, return.
if ( $choices === false || empty( $choices ) ) {
return '';
}
// Display description.
$html = $this->get_description();
$html .= '<span class="' . esc_attr( $this->get_container_classes() ) . '">';
// Display checkboxes.
foreach ( array_values( $choices ) as $i => $choice ) {
// Prepare choice ID, attributes, value and tooltip.
$choice['id'] = sanitize_title( $choice['name'] );
$choice_attributes = self::get_choice_attributes( $choice, $this->get_attributes(), $default_choice_attributes );
$value = $this->settings->get_value( $choice['name'], rgar( $choice, 'default_value' ) );
$tooltip = $this->settings->maybe_get_tooltip( $choice );
// Add icon to choice, if choices have icon and icon is missing.
if ( $have_icon && ! rgar( $choice, 'icon' ) ) {
$choice['icon'] = 'fag-cog';
}
// Open choice container.
$html .= sprintf(
'<div id="gform-settings-checkbox-choice-%s" class="gform-settings-choice%s%s">',
esc_attr( $choice['id'] ),
$horizontal_class,
rgar( $choice, 'icon' ) ? ' gform-settings-choice--visual' : ''
);
// Insert hidden input.
$html .= sprintf(
'<input type="hidden" name="%s_%s" value="%s" />',
esc_attr( $this->settings->get_input_name_prefix() ),
esc_attr( $choice['name'] ),
$value == '1' ? '1' : '0'
);
// Prepare choice markup.
if ( is_callable( array( $this, 'checkbox_input_' . $choice['name'] ) ) ) {
$html .= call_user_func( array(
$this,
'checkbox_input_' . $choice['name'],
), $choice, $choice_attributes, $value, $tooltip );
} else {
$html .= self::render_input( $choice, $choice_attributes, $value, $tooltip );
}
// Close container.
$html .= '</div>';
}
$html .= '</span>';
// Wrap visual choices with container.
if ( $have_icon ) {
$html = sprintf(
'<div class="gform-settings-choices--visual">%s</div>',
$html
);
}
// Display error.
$html .= $this->get_error_icon();
return $html;
}
/**
* Get the markup for array-style checkbox inputs.
*
* @since 2.6
*
* @return string
*/
public function array_markup() {
// Get choices, determine if choices have icon, display direction, default choice attributes.
$choices = $this->get_choices();
$have_icon = self::has_icons( $choices );
$horizontal_class = rgobj( $this, 'horizontal' ) || $have_icon ? ' gform-settings-choice--inline' : '';
$default_choice_attributes = '';
// If no choices exist, return.
if ( $choices === false || empty( $choices ) ) {
return '';
}
// Display description.
$html = $this->get_description();
$html .= '<span class="' . esc_attr( $this->get_container_classes() ) . '">';
// Display checkboxes.
foreach ( array_values( $choices ) as $i => $choice ) {
// Prepare choice ID, attributes, value and tooltip.
$choice['id'] = $choice['name'];
$value = $this->settings->get_value( $this->name );
$choice_attributes = self::get_choice_attributes( $choice, $this->get_attributes(), $default_choice_attributes );
$choice_attributes['name'] = 'name="_gform_setting_' . $this->name . '[]"';
$choice_attributes['value'] = 'value="' . $choice['name'] . '"';
$tooltip = $this->settings->maybe_get_tooltip( $choice );
// Add icon to choice, if choices have icon and icon is missing.
if ( $have_icon && ! rgar( $choice, 'icon' ) ) {
$choice['icon'] = 'fag-cog';
}
// Open choice container.
$html .= sprintf(
'<div id="gform-settings-checkbox-choice-%s" class="gform-settings-choice%s%s">',
esc_attr( $choice['id'] ),
$horizontal_class,
rgar( $choice, 'icon' ) ? ' gform-settings-choice--visual' : ''
);
// Prepare choice markup.
if ( is_callable( array( $this, 'checkbox_input_' . $choice['name'] ) ) ) {
$html .= call_user_func( array(
$this,
'checkbox_input_' . $choice['name'],
), $choice, $choice_attributes, $value, $tooltip );
} else {
$html .= self::render_input( $choice, $choice_attributes, $value, $tooltip, 'array' );
}
// Close container.
$html .= '</div>';
}
$html .= '</span>';
// Wrap visual choices with container.
if ( $have_icon ) {
$html = sprintf(
'<div class="gform-settings-choices--visual">%s</div>',
$html
);
}
// Display error.
$html .= $this->get_error_icon();
return $html;
}
/**
* Returns markup for an individual checkbox input and its label.
*
* @since 2.5
* @since 2.6 Added $data_as_array attribute
*
* @param array $choice Choice array with all configured properties.
* @param array $attributes Array containing all the attributes for the input tag.
* @param string $value Currently selection (1 if field has been checked. 0 or null otherwise).
* @param string $tooltip Tooltip for this checkbox item.
* @param string $data_as_array Whether the data should be formatted for array values.
*
* @return string
*/
public static function render_input( $choice, $attributes = array(), $value = null, $tooltip = '', $data_as_array = false ) {
// Initialize icon, label strings.
$icon_string = $label_string = '';
// Prepare the icon string.
if ( rgar( $choice, 'icon' ) ) {
// Get the icon.
$icon = rgar( $choice, 'icon' ) ? $choice['icon'] : 'fag-cog';
// Prepare string based on icon type.
if ( filter_var( $icon, FILTER_VALIDATE_URL ) ) {
$icon_string = sprintf( '<img src="%s" />', esc_attr( $icon ) );
} else {
$icon_string = sprintf( '<i class="fa %s"></i>', esc_attr( $icon ) );
}
}
// Prepare label markup.
if ( rgar( $choice, 'label' ) ) {
$label_string = sprintf(
'<label for="%s"><span>%s%s%s</span></label>',
esc_attr( $choice['id'] ),
$icon_string,
esc_html( $choice['label'] ),
$tooltip
);
}
if ( $data_as_array ) {
$checked = is_array( $value ) && in_array( $choice['name'], $value ) ? 'checked="checked"' : '';
} else {
$checked = checked( $value, '1', false );
}
// Prepare checkbox markup.
return sprintf(
'<input type="checkbox" %s %s />%s',
$checked,
implode( ' ', $attributes ),
$label_string
);
}
// # VALIDATION METHODS --------------------------------------------------------------------------------------------
/**
* Validate posted field value.
*
* @since 2.5
*
* @param array $value Posted field value.
*/
public function do_validation( $value ) {
if ( $this->data_format === 'array' ) {
return $this->do_array_validation( $value );
}
// Get choices.
$choices = $this->get_choices();
// If field does not have choices defined, exit.
if ( $choices === false || empty( $choices ) ) {
return;
}
$selected = 0;
// Loop through field choices, determine if any were selected.
foreach ( $choices as $choice ) {
// Get value for choice.
$value = $this->settings->get_value( $choice['name'], '' );
// If value is not in possible values array, set field error.
if ( ! in_array( $value, array( '1', '0' ) ) ) {
$this->set_error( esc_html__( 'Invalid selection.', 'gravityforms' ) );
return;
}
if ( $value === '1' ) {
$selected++;
}
}
// If field is required and no choices were selected, set field error.
if ( $this->required && $selected < 1 ) {
$this->set_error( rgobj( $this, 'error_message' ) );
}
}
/**
* Validate posted array field value.
*
* @since 2.6
*
* @param array $value Posted field value.
*/
public function do_array_validation( $value ) {
// Get choices.
$choices = $this->get_choices();
if ( empty( $value ) ) {
$value = $this->get_value( $this->name, array() );
}
// If field does not have choices defined, exit.
if ( $choices === false || empty( $choices ) ) {
return;
}
$selected = 0;
// get list of acceptable choices.
$whitelist = array();
foreach ( $choices as $choice ) {
$whitelist[] = $choice['name'];
}
foreach ( $value as $checked ) {
if ( ! in_array( $checked, $whitelist ) ) {
$this->set_error( esc_html__( 'Invalid selection.', 'gravityforms' ) );
return;
}
$selected++;
}
// If field is required and no choices were selected, set field error.
if ( $this->required && $selected < 1 ) {
$this->set_error( rgobj( $this, 'error_message' ) );
}
}
}
Fields::register( 'checkbox', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Checkbox' );

View File

@@ -0,0 +1,173 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use GFFormsModel;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
class Conditional_Logic extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'conditional_logic';
public $checkbox = array(
'label' => 'Enable Condition',
'hidden' => false,
);
public $object_type;
protected $checkbox_label;
protected $instructions;
/**
* Initialize Conditional Logic field.
*
* @since 2.5
*
* @param array $props Field properties.
* @param \Gravity_Forms\Gravity_Forms\Settings\Settings $settings Settings instance.
*/
public function __construct( $props, $settings ) {
parent::__construct( $props, $settings );
// Populate default instructions.
if ( $this->object_type === 'feed_condition' && ! rgobj( $this, 'instructions' ) ) {
$this->instructions = esc_html__( 'Process this feed if', 'gravityforms' );
}
// Translate Checkbox label.
if ( $this->checkbox['label'] === 'Enable Condition' ) {
$this->checkbox['label'] = esc_html__( 'Enable Condition', 'gravityforms' );
}
// Initialize Checkbox field.
$this->inputs['checkbox'] = Fields::create(
array(
'name' => sprintf( '%s_conditional_logic', esc_attr( $this->object_type ) ),
'type' => 'checkbox',
'hidden' => $this->checkbox['hidden'],
'choices' => array(
array(
'label' => $this->checkbox['label'],
'name' => sprintf( '%s_conditional_logic', esc_attr( $this->object_type ) ),
),
),
'onclick' => sprintf( 'ToggleConditionalLogic( false, "%s" );', esc_attr( $this->object_type ) ),
),
$settings
);
// Initialize Hidden field.
$this->inputs['hidden'] = Fields::create(
array(
'name' => sprintf( '%s_conditional_logic_object', esc_attr( $this->object_type ) ),
'type' => 'hidden',
'default_value' => wp_json_encode( $this->get_logic_object() ),
),
$settings
);
}
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
// Display description.
$html = $this->get_description();
$html .= '<span class="' . esc_attr( $this->get_container_classes() ) . '">';
$html .= $this->inputs['hidden']->markup();
$html .= $this->inputs['checkbox']->markup();
$html .= '</span>';
$html .= sprintf(
'<div id="%s_conditional_logic_container" class="gform-settings-field__conditional-logic">
<!-- content dynamically created from form_admin.js -->
</div>',
$this->object_type
);
// Handle feed condition.
if ( $this->object_type === 'feed_condition' ) {
// Prepare JS parameters.
$js_params = array(
'strings' => array( 'objectDescription' => esc_attr( $this->instructions ) ),
'logicObject' => $this->get_logic_object(),
);
// Initialize.
$html .= sprintf(
'<script type="text/javascript">var feedCondition = new FeedConditionObj(%s);</script>',
wp_json_encode( $js_params )
);
}
$html .= $this->get_error_icon();
return $html;
}
// # HELPER METHODS ------------------------------------------------------------------------------------------------
/**
* Returns the current conditional logic object.
*
* @since 2.5
*
* @return object
*/
private function get_logic_object() {
// Get logic object.
$object = $this->settings->get_value( sprintf( '%s_conditional_logic_object', $this->object_type ) );
/*
* Because we call this method during object instantiation, we might get back an encoded object when
* Conditional_Logic::markup is called later. To prevent encoding our empty objects as a string, we'll return a
* new generic PHP object here instead.
*/
if ( ! $object || $object === '{}' ) {
return new \stdClass();
}
// Handle feed condition.
if ( $this->object_type === 'feed_condition' && rgar( $object, 'actionType' ) ) {
$object = array( 'conditionalLogic' => $object );
}
// Trim values from existing object.
return GFFormsModel::trim_conditional_logic_values_from_element( $object, $this->settings->get_current_form() );
}
}
Fields::register( 'conditional_logic', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Conditional_Logic' );

View File

@@ -0,0 +1,206 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use GFCommon;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
// Load base class.
require_once 'class-select.php';
require_once 'class-text.php';
class Date_Time extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'date_time';
/**
* Child inputs.
*
* @since 2.5
*
* @var Base[]
*/
public $inputs = array();
/**
* Initialize Date Time field.
*
* @since 2.5
*
* @param array $props Field properties.
* @param \Gravity_Forms\Gravity_Forms\Settings\Settings $settings Settings instance.
*/
public function __construct( $props, $settings ) {
parent::__construct( $props, $settings );
// Prevent description from showing up on all sub-fields.
unset( $props['description'] );
// Prepare Date input.
$this->inputs['date'] = $props;
$this->inputs['date']['type'] = 'text';
$this->inputs['date']['name'] .= '[date]';
// Prepare hours as choices.
$hour_choices = array();
for ( $i = 1; $i <= 12; $i++ ) {
$hour_choices[] = array( 'label' => $i, 'value' => $i );
}
// Prepare hour drop down.
$this->inputs['hour'] = $props;
$this->inputs['hour']['type'] = 'select';
$this->inputs['hour']['name'] .= '[hour]';
$this->inputs['hour']['choices'] = $hour_choices;
// Prepare minutes as choices.
$minute_choices = array();
for ( $i = 0; $i < 60; $i++ ) {
$minute_choices[] = array( 'label' => sprintf( '%02d', $i ), 'value' => sprintf( '%d', $i ) );
}
// Prepare minute drop down.
$this->inputs['minute'] = $props;
$this->inputs['minute']['type'] = 'select';
$this->inputs['minute']['name'] .= '[minute]';
$this->inputs['minute']['choices'] = $minute_choices;
// Prepare AM/PM drop down.
$this->inputs['ampm'] = $props;
$this->inputs['ampm']['type'] = 'select';
$this->inputs['ampm']['name'] .= '[ampm]';
$this->inputs['ampm']['choices'] = array(
array( 'label' => 'AM', 'value' => 'am' ),
array( 'label' => 'PM', 'value' => 'pm' ),
);
/**
* Prepare input fields.
*
* @var array $input
*/
foreach ( $this->inputs as &$input ) {
$input = Fields::create( $input, $this->settings );
}
}
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
$html = $this->get_description();
$html .= '<span class="' . esc_attr( $this->get_container_classes() ) . '">';
// Display Date input, Time drop downs.
$html .= sprintf(
'%s %s<span class="gform-settings-input__separator">:</span>%s %s',
$this->inputs['date']->markup(),
$this->inputs['hour']->markup(),
$this->inputs['minute']->markup(),
$this->inputs['ampm']->markup()
);
// Insert jQuery Datepicker script.
$html .= sprintf(
"<script type='text/javascript'>
jQuery( function() {
jQuery( 'input[name=\"%s_%s\"]' ).datepicker(
{
showOn: 'both',
changeMonth: true,
changeYear: true,
buttonImage: '%s',
buttonText: '%s',
dateFormat: 'mm/dd/yy'
}
);
} )
</script>",
$this->settings->get_input_name_prefix(),
$this->inputs['date']->name,
GFCommon::get_image_url( 'datepicker/datepicker.svg' ),
esc_html__( 'Open Date Picker', 'gravityforms' )
);
$html .= '</span>';
// If field failed validation, add error icon.
$html .= $this->get_error_icon();
return $html;
}
// # VALIDATION METHODS --------------------------------------------------------------------------------------------
/**
* Validate posted field value.
*
* @since 2.5
*
* @param array $value Posted field value.
*/
public function do_validation( $value ) {
// If field is required and date is missing, set field error.
if ( $this->required && rgempty( 'date', $value ) ) {
$this->set_error( rgobj( $this, 'error_message' ) );
return;
}
// Test for valid date.
if ( wp_strip_all_tags( $value['date'] ) !== $value['date'] ) {
$this->inputs['date']->set_error( esc_html__( 'Date must not include HTML tags.', 'gravityforms' ) );
return;
}
// Test for valid hour.
if ( (int) $value['hour'] < 1 || (int) $value['hour'] > 12 ) {
$this->inputs['hour']->set_error( esc_html__( 'You must select a valid hour.', 'gravityforms' ) );
return;
}
// Test for valid minute.
if ( (int) $value['minute'] < 0 || (int) $value['minute'] > 59 ) {
$this->inputs['minute']->set_error( esc_html__( 'You must select a valid minute.', 'gravityforms' ) );
return;
}
// Test for valid AM/PM.
if ( ! in_array( rgar( $value, 'ampm' ), array( 'am', 'pm' ) ) ) {
$this->inputs['ampm']->set_error( esc_html__( 'You must select either am or pm.', 'gravityforms' ) );
return;
}
}
}
Fields::register( 'date_time', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Date_Time' );

View File

@@ -0,0 +1,53 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
// Load base class.
require_once 'class-generic-map.php';
class Dynamic_Field_Map extends Generic_Map {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'dynamic_field_map';
/**
* Initialize Dynamic Field Map field.
*
* @since 2.5
*
* @param array $props Field properties.
* @param \Gravity_Forms\Gravity_Forms\Settings\Settings $settings Settings instance.
*/
public function __construct( $props, $settings ) {
// Check for custom key support.
if ( isset( $props['disabled_custom'] ) && $props['disabled_custom'] === true ) {
$this->key_field['allow_custom'] = false;
unset( $props['disabled_custom'] );
} else if ( isset( $props['enable_custom_key'] ) && $props['enable_custom_key'] !== true ) {
$this->key_field['allow_custom'] = false;
unset( $props['enable_custom_key'] );
}
if ( isset( $props['allow_duplicates'] ) ) {
$this->key_field['allow_duplicates'] = $props['allow_duplicates'];
}
$this->value_field['allow_custom'] = false;
parent::__construct( $props, $settings );
}
}
Fields::register( 'dynamic_field_map', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Dynamic_Field_Map' );

View File

@@ -0,0 +1,143 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use GFCommon;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
// Load base class.
require_once 'class-generic-map.php';
class Field_Map extends Generic_Map {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'field_map';
/**
* Initialize Field Map field.
*
* @since 2.5
*
* @param array $props Field properties.
* @param \Gravity_Forms\Gravity_Forms\Settings\Settings $settings Settings instance.
*/
public function __construct( $props, $settings ) {
// Define key field header, disable custom keys.
$this->key_field['title'] = esc_html__( 'Field', 'gravityforms' );
$this->key_field['allow_custom'] = false;
$this->key_field['display_all'] = true;
// Define value field header, disable custom values.
$this->value_field['title'] = esc_html__( 'Form Field', 'gravityforms' );
$this->value_field['allow_custom'] = false;
// Prepare key field choices.
$this->key_field['choices'] = rgar( $props, 'field_map' ) ? array_values( $props['field_map'] ) : array();
foreach ( $this->key_field['choices'] as $i => $choice ) {
$field_types = isset( $choice['field_types'] ) ? $choice['field_types'] : rgar( $choice, 'field_type', false );
// Set required, excluded field types.
$this->key_field['choices'][ $i ]['required_types'] = $field_types ? $field_types : rgar( $choice, 'required_types', array() );
$this->key_field['choices'][ $i ]['excluded_types'] = rgar( $choice, 'exclude_field_types', false ) ? $choice['exclude_field_types'] : rgar( $choice, 'excluded_types', array() );
// Remove legacy properties.
unset( $this->key_field['choices'][ $i ]['field_type'], $this->key_field['choices'][ $i ]['exclude_field_types'] );
}
// Remove field map property.
unset( $props['field_map'] );
parent::__construct( $props, $settings );
}
// # HELPER METHODS ------------------------------------------------------------------------------------------------
/**
* Prepare field value for Field Map.
*
* @since 2.5
*
* @return array
*/
public function get_value() {
// Get existing value.
$existing_value = parent::get_value();
// If existing value is array, use it.
if ( is_array( $existing_value ) ) {
return $existing_value;
}
// Initialize return array.
$value = array();
// Loop through choices, get keys and values from main settings.
foreach ( $this->key_field['choices'] as $choice ) {
// Add choice to return array.
$value[] = array(
'key' => $choice['name'],
'custom_key' => '',
'value' => $this->settings->get_value( sprintf( '%s_%s', $this->name, $choice['name'] ) ),
'custom_value' => '',
);
}
return $value;
}
/**
* Modify field value for legacy setting.
*
* @since 2.5
*
* @param array $field_values
* @param array|bool|string $field_value
*
* @return array
*/
public function save_field( $field_values, $field_value ) {
// If field has a save callback, use parent method.
if ( is_callable( $this->save_callback ) ) {
return parent::save_field( $field_values, $field_value );
}
// Decode field value.
$field_value = GFCommon::maybe_decode_json( $field_value );
// Loop through map, save to field values.
foreach ( $field_value as $mapping ) {
$field_name = sprintf( '%s_%s', $this->name, $mapping['key'] );
$field_values[ $field_name ] = $mapping['value'];
}
// Remove main mapping.
unset( $field_values[ $this->name ] );
return $field_values;
}
}
Fields::register( 'field_map', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Field_Map' );

View File

@@ -0,0 +1,336 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use GF_Fields;
use GFCommon;
use GFFormsModel;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
// Load base class.
require_once 'class-select.php';
class Field_Select extends Select {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'field_select';
/**
* Drop Down arguments.
*
* @since 2.5
*
* @var array
*/
public $args = array();
protected $fields_callback;
/**
* Initialize Field Select field.
*
* @since 2.5
*
* @param array $props Field properties.
* @param \Gravity_Forms\Gravity_Forms\Settings\Settings $settings Settings instance.
*/
public function __construct( $props, $settings ) {
parent::__construct( $props, $settings );
// Force args to array.
if ( ! is_array( $this->args ) ) {
$this->args = (array) $this->args;
}
// Populate args with defaults.
$this->args = wp_parse_args(
$this->args,
array(
'append_choices' => array(),
'disable_first_choice' => false,
'input_types' => array(),
)
);
// Reset choices.
$this->choices = array();
// Add a "Select a Field" choice.
if ( ! $this->args['disable_first_choice'] ) {
// Prepare first choice label.
$first_choice_label = __( 'Select a Field', 'gravityforms' );
// If a singular input type is defined, set field type as first choice label.
if ( ( ! empty( $this->args['input_types'] ) && is_string( $this->args['input_types'] ) ) || ( is_array( $this->args['input_types'] ) && count( $this->args['input_types'] ) === 1 ) ) {
// Get input type and field object.
$input_type = is_array( $this->args['input_types'] ) ? $this->args['input_types'][0] : $this->args['input_types'];
$field = GF_Fields::get( $input_type );
// Prepare first choice label.
if ( $field ) {
$first_choice_label = sprintf( __( 'Select a %s Field', 'gravityforms' ), ucwords( $field->get_form_editor_field_title() ) );
}
}
// Add first choice label.
$this->choices[] = array(
'value' => '',
'label' => $first_choice_label,
);
}
// Add form fields, append choices as choices.
$this->choices = array_merge(
$this->choices,
$this->get_form_fields_as_choices( $this->settings->get_current_form() ),
$this->args['append_choices']
);
// Reset choices if only one exists and no choices label is set.
if ( ! $this->args['disable_first_choice'] && count( $this->choices ) === 1 && rgobj( $this, 'no_choices' ) ) {
$this->choices = array();
}
// Set default value.
$this->default_value = $this->get_default_choice();
}
// # HELPER METHODS ------------------------------------------------------------------------------------------------
/**
* Returns the field to be selected by default for Field Select fields based on matching labels.
*
* @since 2.5
*
* @return string|null
*/
public function get_default_choice() {
// If default value is defined and not an array, return.
if ( ! empty( $this->default_value ) && ! is_array( $this->default_value ) ) {
return $this->default_value;
}
// If auto population is disabled, return.
if ( rgobj( $this, 'auto_mapping' ) === false ) {
return null;
}
// Get field label.
$field_label = $this->label;
// Initialize array to store auto-population choices.
$default_value_choices = array( $field_label );
// Define global aliases to help with the common case mappings.
$global_aliases = array(
__( 'First Name', 'gravityforms' ) => array( __( 'Name (First)', 'gravityforms' ) ),
__( 'Last Name', 'gravityforms' ) => array( __( 'Name (Last)', 'gravityforms' ) ),
__( 'Address', 'gravityforms' ) => array( __( 'Address (Street Address)', 'gravityforms' ) ),
__( 'Address 2', 'gravityforms' ) => array( __( 'Address (Address Line 2)', 'gravityforms' ) ),
__( 'City', 'gravityforms' ) => array( __( 'Address (City)', 'gravityforms' ) ),
__( 'State', 'gravityforms' ) => array( __( 'Address (State / Province)', 'gravityforms' ) ),
__( 'Zip', 'gravityforms' ) => array( __( 'Address (Zip / Postal Code)', 'gravityforms' ) ),
__( 'Country', 'gravityforms' ) => array( __( 'Address (Country)', 'gravityforms' ) ),
);
// If one or more global aliases are defined for this particular field label, merge them into auto-population choices.
if ( isset( $global_aliases[ $field_label ] ) ) {
$default_value_choices = array_merge( $default_value_choices, $global_aliases[ $field_label ] );
}
// If field aliases are defined, merge them into auto-population choices.
if ( is_array( $this->default_value ) && rgar( $this->default_value, 'aliases' ) ) {
$default_value_choices = array_merge( $default_value_choices, $this->default_value['aliases'] );
}
// Convert all auto-population choices to lowercase.
foreach( $default_value_choices as $key => $value ) {
$default_value_choices[ $key ] = $value ? strtolower( $value ) : $value;
}
// Loop through fields.
foreach ( $this->choices as $choice ) {
// If choice value is empty, skip.
if ( rgblank( $choice['value'] ) ) {
continue;
}
// If lowercase field label matches a default value choice, set it to the default value.
if ( in_array( strtolower( $choice['label'] ), $default_value_choices ) ) {
return $choice['value'];
}
}
return null;
}
/**
* Prepares a form's fields as choices.
*
* @since 2.5
*
* @param array $form Form object.
*
* @return array
*/
public function get_form_fields_as_choices( $form ) {
// Parse arguments, add defaults.
$args = wp_parse_args(
$this->args,
array(
'field_types' => array(),
'input_types' => array(),
'callback' => false,
)
);
// Initialize choices array.
$choices = array();
$fields = $this->get_form_fields_to_process( $form, $args );
/**
* Loop through form fields, add as choices.
*
* @var \GF_Field $field
*/
foreach ( $fields as $field ) {
// Get inputs for field.
$inputs = $field->get_entry_inputs();
// Add choices based on field inputs.
if ( is_array( $inputs ) ) {
// Add full value for certain fields.
switch ( $field->get_input_type() ) {
case 'address':
case 'name':
$choices[] = array(
'value' => $field->id,
'label' => sprintf(
'%s (%s)',
strip_tags( GFCommon::get_label( $field ) ),
esc_html__( 'Full', 'gravityforms' )
),
);
break;
case 'checkbox':
$choices[] = array(
'value' => $field->id,
'label' => sprintf(
'%s (%s)',
strip_tags( GFCommon::get_label( $field ) ),
esc_html__( 'Selected', 'gravityforms' )
),
);
break;
}
// Loop through inputs, add as choices.
foreach ( $inputs as $input ) {
$choices[] = array(
'value' => $input['id'],
'label' => strip_tags( GFCommon::get_label( $field, $input['id'] ) ),
);
}
} elseif ( ! $field->displayOnly || $field->get_input_type() === 'password' ) {
$choices[] = array(
'value' => $field->id,
'label' => strip_tags( GFCommon::get_label( $field ) ),
);
}
}
return $choices;
}
/**
* Get the allowed form fields to process as choice values.
*
* @since 2.5.7
*
* @param array $form The form currently being configured.
* @param array $args A selection of field configuration arguments.
*
* @return array|false|mixed
*/
private function get_form_fields_to_process( $form, $args ) {
$fields = array();
$form_fields = array();
if ( GFCommon::form_has_fields( $form ) ) {
$form_fields = $form['fields'];
}
foreach ( $form_fields as $field ) {
// If field is not in the whitelisted field types, skip.
if ( ! empty( $args['field_types'] ) && is_array( $args['field_types'] ) && ! in_array( $field->type, $args['field_types'] ) ) {
continue;
}
// Determine if field is a matching input type.
$input_type = GFFormsModel::get_input_type( $field );
$allowed_input_type = empty( $args['input_types'] ) || ( is_array( $args['input_types'] ) && in_array( $input_type, $args['input_types'] ) );
// Apply callback to input type check.
if ( is_callable( $args['callback'] ) ) {
$allowed_input_type = call_user_func( $args['callback'], $allowed_input_type, $field, $form );
}
// If field's input type is not allowed, skip.
if ( ! $allowed_input_type ) {
continue;
}
// Test for field property value.
if ( rgar( $args, 'property' ) && ( ! isset( $field->{$args['property']} ) || $field->{$args['property']} != rgar( $args, 'property_value' ) ) ) {
continue;
}
$fields[] = $field;
}
// If the field as a valid fields_callback, pass $fields through it to allow modifications.
$fields_callback = rgobj( $this, 'fields_callback' );
if ( is_callable( $fields_callback ) ) {
$fields = call_user_func_array( $fields_callback, array( $fields, $this->settings->get_current_form() ) );
}
return $fields;
}
}
Fields::register( 'field_select', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Field_Select' );

View File

@@ -0,0 +1,758 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use GFCommon;
use GF_Fields;
use GFForms;
use GFFormsModel;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
class Generic_Map extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'map';
/**
* Number of mappings allowed.
*
* @since 2.5
*
* @var int
*/
public $limit = 0;
/**
* Required choices that were not selected on save.
*
* @since 2.5
*
* @var array
*/
public $invalid_choices = array();
/**
* Key field properties.
*
* @since 2.5
*
* @var array
*/
public $key_field = array(
'title' => '',
'choices' => array(),
'placeholder' => '',
'display_all' => false,
'allow_custom' => true,
'allow_duplicates' => false,
);
/**
* Value field properties.
*
* @since 2.5
*
* @var array
*/
public $value_field = array(
'title' => '',
'placeholder' => '',
'allow_custom' => true,
);
protected $excluded_field_types;
protected $required_field_types;
protected $merge_tags;
protected $field_map;
/**
* Initialize Generic Map field.
*
* @since 2.5
*
* @param array $props Field properties.
* @param \Gravity_Forms\Gravity_Forms\Settings\Settings $settings Settings instance.
*/
public function __construct( $props, $settings ) {
// Set Settings framework instance.
$this->settings = $settings;
// Prepare required and excluded field types.
$this->excluded_field_types = rgar( $props, 'exclude_field_types' );
$this->required_field_types = isset( $props['field_types'] ) ? $props['field_types'] : rgar( $props, 'field_type' );
// Merge Key Field properties.
if ( rgar( $props, 'key_field' ) ) {
if ( rgar( $props, 'disable_custom' ) ) {
$this->key_field['allow_custom'] = ! (bool) $props['disable_custom'];
unset( $props['disable_custom'] );
} else if ( rgar( $props, 'enable_custom_key' ) ) {
$this->key_field['allow_custom'] = (bool) $props['enable_custom_key'];
unset( $props['enable_custom_key'] );
} else if ( rgars( $props, 'key_field/custom_value' ) ) {
$this->key_field['allow_custom'] = (bool) $props['key_field']['custom_value'];
}
$this->key_field = array_merge( $this->key_field, $props['key_field'] );
}
// Merge value field properties.
if ( rgar( $props, 'value_field' ) ) {
if ( rgar( $props, 'enable_custom_value' ) ) {
$this->value_field['allow_custom'] = (bool) $props['enable_custom_value'];
unset( $props['enable_custom_value'] );
} else if ( rgars( $props, 'value_field/custom_value' ) ) {
$this->value_field['allow_custom'] = (bool) $props['value_field']['custom_value'];
}
$this->value_field = array_merge( $this->value_field, $props['value_field'] );
}
// Prepare key field choices.
if ( rgar( $props, 'field_map' ) ) {
$this->key_field['choices'] = array_values( $props['field_map'] );
} else if ( rgar( $props, 'key_choices' ) ) {
$this->key_field['choices'] = $props['key_choices'];
} else if ( rgars( $props, 'key_field/choices' ) ) {
$this->key_field['choices'] = $props['key_field']['choices'];
} else {
$this->key_field['choices'] = rgar( $this->key_field, 'choices', array() );
}
$passed_choices = rgar( $this->value_field, 'choices' );
if ( ! $passed_choices ) {
$passed_choices = rgar( $props, 'value_choices' );
}
if ( ! $passed_choices ) {
$passed_choices = 'form_fields';
}
$this->value_field['choices'] = array();
$this->value_field['choices']['default'] = $passed_choices == 'form_fields' ? $this->get_value_choices( $this->required_field_types, $this->excluded_field_types ) : $this->get_prepopulated_choice( 'default', $passed_choices );
// Assign the correct value field choices per key field choice.
foreach ( $this->key_field['choices'] as $choice ) {
// Choice doesn't have a name index; this likely means the choice is non-standard; bail and use default choices.
if ( ! rgar( $choice, 'name' ) ) {
continue;
}
$required_types = isset( $choice['field_types'] ) ? rgar( $choice, 'field_types', array() ) : rgar( $choice, 'required_types', array() );
$excluded_types = isset( $choice['exclude_field_types'] ) ? rgar( $choice, 'exclude_field_types', array() ) : rgar( $choice, 'excluded_types', array() );
// Convert to array if field type was configured as a string to require only one field type.
if ( ! is_array( $required_types ) ) {
$required_types = array( $required_types );
}
// Convert to array if excluded field type was configured as string to exclude only one field type.
if ( ! is_array( $excluded_types ) ) {
$excluded_types = array( $excluded_types );
}
$key = 'filtered_choices_' . implode( '-', $required_types ) . '_' . implode( '-', $excluded_types );
if ( ! isset( $this->value_field['choices'][ $key ] ) ) {
$this->value_field['choices'][ $key ] = $passed_choices == 'form_fields' ? $this->get_value_choices( $required_types, $excluded_types ) : $this->get_prepopulated_choice( 'default', $passed_choices );
}
$this->value_field['choice_keys'][ $choice['name'] ] = $key;
}
// Translate base strings.
if ( empty( $this->key_field['title'] ) ) {
$this->key_field['title'] = esc_html__( 'Key', 'gravityforms' );
}
if ( empty( $this->key_field['custom'] ) ) {
$this->key_field['custom'] = esc_html__( 'Custom Key', 'gravityforms' );
}
if ( empty( $this->value_field['title'] ) ) {
$this->value_field['title'] = esc_html__( 'Value', 'gravityforms' );
}
if ( empty( $this->value_field['custom'] ) ) {
$this->value_field['custom'] = esc_html__( 'Custom Value', 'gravityforms' );
}
unset( $props['key_field'], $props['value_field'] );
parent::__construct( $props, $settings );
}
/**
* Looks into the $passed_choices for the specified $key. If $key is specified and mapped to an array, return the contents of that array. Otherwise return the entired $passed_choices array.
*
* @since 2.5.16
*
* @param string $key They filtered key to be used when looking for the child choices.
* @param array $passed_choices The choices array that will be used to pre-populate the value field.
*
* @return array List of choices to be prepopulated
*/
private function get_prepopulated_choice( $key, $passed_choices ) {
$is_filtered_choice_defined = isset( $passed_choices[ $key ] ) && is_array( $passed_choices[ $key ] );
return $is_filtered_choice_defined ? $passed_choices[ $key ] : $passed_choices;
}
/**
* Register scripts to enqueue when displaying field.
*
* @since 2.5
*
* @return array
*/
public function scripts() {
$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min';
$dev_min = defined( 'GF_SCRIPT_DEBUG' ) && GF_SCRIPT_DEBUG ? '' : '.min';
return array(
array(
'handle' => 'gform_settings_field_map',
'src' => GFCommon::get_base_url() . "/assets/js/dist/field-map{$dev_min}.js",
'version' => defined( 'GF_SCRIPT_DEBUG' ) && GF_SCRIPT_DEBUG ? filemtime( GFCommon::get_base_path() . "/assets/js/dist/field-map{$dev_min}.js" ) : GFForms::$version,
'deps' => array( 'wp-element', 'wp-i18n' ),
),
);
}
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
* This contains the hidden input used to manage the state of the field and is also updated via react.
* This also contains the initializeFieldMap method which inits the react for the field and passes along
* the various props to then be used in the react app.
*
* @since 2.5
*
* @return string
*/
public function markup() {
// Get input, container names.
$input_name = esc_attr( sprintf( '%s_%s', $this->settings->get_input_name_prefix(), $this->name ) );
$container_name = $input_name . '_container';
// If no key field choices are provided, return.
if ( empty( $this->key_field['choices'] ) && $this->type !== 'generic_map' && ! $this->key_field['allow_custom'] ) {
return esc_html__( 'No mapping fields are available.', 'gravityforms' );
}
// Prepare value, key fields.
$value_field = $this->value_field;
$value_field['choices'] = rgar( $value_field, 'choices' ) ? $this->get_choices( $value_field['choices'] ) : array();
$key_field = $this->key_field;
$key_field['choices'] = rgar( $key_field, 'choices' ) ? $this->get_choices( $key_field['choices'] ) : array();
// Populate default choices only if the feed hasn't been saved yet.
if ( empty( $_GET['fid'] ) ) {
$key_field['choices'] = $this->populate_default_key_values( $key_field['choices'] );
}
// Prepare JS params.
$js_params = array(
'input' => $input_name,
'inputType' => $this->type,
'keyField' => $key_field,
'valueField' => $value_field,
'limit' => $this->limit,
'invalidChoices' => $this->invalid_choices,
'mergeTagSupport' => property_exists( $this, 'merge_tags' ) ? $this->merge_tags : false,
);
// Prepare markup.
// Display description.
$html = $this->get_description();
$html .= sprintf(
'<span class="%1$s"><input type="hidden" name="%2$s" id="%2$s" value=\'%3$s\' />
<div id="%4$s" class="gform-settings-field-map__container"></div>%5$s</span>
<script type="text/javascript">initializeFieldMap( \'%4$s\', %6$s );</script></span>',
esc_attr( $this->get_container_classes() ),
$input_name, // Input name
esc_attr( wp_json_encode( $this->get_value() ? $this->get_value() : array() ) ), // Input value
$container_name, // Container name
$this->get_error_icon(),
wp_json_encode( $js_params )// JS params
);
return $html;
}
/**
* Gets the classes to apply to the field container.
* Removes invalid class.
*
* @since 2.5
*
* @return string
*/
public function get_container_classes() {
$classes = parent::get_container_classes();
$classes = explode( ' ', $classes );
// Search for and remove invalid class.
$invalid_key = array_search( 'gform-settings-input__container--invalid', $classes );
if ( $invalid_key ) {
unset( $classes[ $invalid_key ] );
}
return implode( ' ', $classes );
}
/**
* Populate key field choices with their default mappings.
*
* @since 2.5
*
* @param array $choices Existing choices.
*
* @return array
*/
public function populate_default_key_values( $choices ) {
// If auto-mapping is disabled, return.
if ( rgobj( $this, 'auto_mapping' ) === false ) {
return $choices;
}
// Define global aliases to help with the common case mappings.
$global_aliases = array(
__( 'First Name', 'gravityforms' ) => array( __( 'Name (First)', 'gravityforms' ) ),
__( 'Last Name', 'gravityforms' ) => array( __( 'Name (Last)', 'gravityforms' ) ),
__( 'Address', 'gravityforms' ) => array( __( 'Address (Street Address)', 'gravityforms' ) ),
__( 'Address 2', 'gravityforms' ) => array( __( 'Address (Address Line 2)', 'gravityforms' ) ),
__( 'City', 'gravityforms' ) => array( __( 'Address (City)', 'gravityforms' ) ),
__( 'State', 'gravityforms' ) => array( __( 'Address (State / Province)', 'gravityforms' ) ),
__( 'Zip', 'gravityforms' ) => array( __( 'Address (Zip / Postal Code)', 'gravityforms' ) ),
__( 'Country', 'gravityforms' ) => array( __( 'Address (Country)', 'gravityforms' ) ),
);
// Loop through choices, set default values.
foreach ( $choices as &$choice ) {
// Initialize array to store auto-population choices.
$default_value_choices = array( $choice['label'] );
// If one or more global aliases are defined for this particular label, merge them into auto-population choices.
if ( isset( $global_aliases[ $choice['label'] ] ) ) {
$default_value_choices = array_merge( $default_value_choices, $global_aliases[ $choice['label'] ] );
}
// Convert all auto-population choices to lowercase.
$default_value_choices = array_map( 'strtolower', $default_value_choices );
// If choice has specific value choices after evaluating required and excluded types, get them.
if ( rgar( $choice, 'choices' ) && is_array( $choice['choices'] ) ) {
$value_choices = $choice['choices'];
} else {
$value_choices = $this->value_field['choices'];
}
// Each key field potentially has it's own array of potential choices based on required and excluded types, so we need to loop through those.
foreach ( $value_choices as $key_field_choices ) {
// Loop through each group for each key field and see if there are choices available.
foreach ( $key_field_choices as $group ) {
if ( ! rgar( $group, 'choices' ) || ! is_array( $group['choices'] ) ) {
continue;
}
foreach ( $group['choices'] as $value_choice ) {
if ( empty( $value_choice['value'] ) ) {
continue;
}
// If lowercase field label matches a default value choice, set it to the default value.
if ( in_array( strtolower( $value_choice['label'] ), $default_value_choices ) ) {
$choice['default_value'] = $value_choice['value'];
break 3;
}
}
}
}
}
return $choices;
}
/**
* Get choices for value field.
*
* @since 2.5
*
* @param array $required_types Required field types.
* @param array $excluded_types Excluded field types.
*
* @return array
*/
public function get_value_choices( $required_types = array(), $excluded_types = array() ) {
// Get form.
$form = $this->settings->get_current_form();
// No form found, set up a default array with empty values.
if ( ! is_array( $form ) ) {
$form = array(
'fields' => array(),
);
}
// Initialize choices array and input type.
$choices = array();
$input_type = '';
$form_id = isset( $form['id'] ) ? $form['id'] : 0;
// Force required, excluded types to arrays.
$required_types = is_array( $required_types ) ? $required_types : array();
$excluded_types = is_array( $excluded_types ) ? $excluded_types : array();
$form_field_choices = array();
/**
* Populate form fields.
*
* @var \GF_Field $field
*/
foreach ( $form['fields'] as $field ) {
// Get input type and available inputs.
$input_type = $field->get_input_type();
$inputs = $field->get_entry_inputs();
// If field type is excluded, skip.
if ( ! empty( $excluded_types ) && in_array( $input_type, $excluded_types ) ) {
continue;
}
// If field type is not whitelisted, skip.
if ( ! empty( $required_types ) && ! in_array( $input_type, $required_types ) ) {
continue;
}
// Handle fields with inputs.
if ( is_array( $inputs ) ) {
// Add full, selected choices.
switch ( $input_type ) {
case 'address':
$form_field_choices[] = array(
'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full Address', 'gravityforms' ) . ')' ),
'value' => $field->id,
'type' => 'address'
);
break;
case 'name':
$form_field_choices[] = array(
'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full Name', 'gravityforms' ) . ')' ),
'value' => $field->id,
'type' => 'name'
);
break;
case 'checkbox':
$form_field_choices[] = array(
'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html__( 'Selected', 'gravityforms' ) . ')' ),
'value' => $field->id,
'type' => 'checkbox'
);
break;
}
// Add inputs as choices.
foreach ( $inputs as $input ) {
$form_field_choices[] = array(
'label' => strip_tags( GFCommon::get_label( $field, $input['id'] ) ),
'value' => $input['id'],
'type' => $field['type'],
);
}
// Handle multi-column List fields.
} else if ( $input_type === 'list' && $field->enableColumns ) {
// Add choice for full List value.
$form_field_choices[] = array(
'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full', 'gravityforms' ) . ')' ),
'value' => $field->id,
'type' => 'list',
);
// Add choice for each column.
$col_index = 0;
foreach ( $field->choices as $column ) {
$form_field_choices[] = array(
'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html( rgar( $column, 'text' ) ) . ')' ),
'value' => $field->id . '.' . $col_index,
'type' => 'list',
);
$col_index++;
}
} else if ( ! $field->displayOnly ) {
$form_field_choices[] = array(
'label' => strip_tags( GFCommon::get_label( $field ) ),
'value' => $field->id,
'type' => $field['type'],
);
}
}
if ( count( $form_field_choices ) ) {
// Add first choice.
if ( count( $required_types ) === 0 || count( $required_types ) > 1 ) {
$choices[] = array(
'label' => esc_html__( 'Select a Field', 'gravityforms' ),
'value' => '',
'type' => $field['type'],
);
} else {
$choices[] = array(
'label' => sprintf(
esc_html__( 'Select a %s Field', 'gravityforms' ),
GF_Fields::get( $required_types[0] ) ? ucfirst( GF_Fields::get( $required_types[0] )->get_form_editor_field_title() ) : ''
),
'value' => '',
'type' => $field['type'],
);
}
// Add Form Fields choice.
$choices['fields'] = array(
'label' => esc_html__( 'Form Fields', 'gravityforms' ),
'choices' => $form_field_choices,
);
}
// If field type is not restricted, add default fields and entry meta.
if ( empty( $required_types ) ) {
// Add default fields.
$choices[] = array(
'label' => esc_html__( 'Entry Properties', 'gravityforms' ),
'choices' => array(
array(
'label' => esc_html__( 'Entry ID', 'gravityforms' ),
'value' => 'id',
),
array(
'label' => esc_html__( 'Entry Date', 'gravityforms' ),
'value' => 'date_created',
),
array(
'label' => esc_html__( 'User IP', 'gravityforms' ),
'value' => 'ip',
),
array(
'label' => esc_html__( 'Source Url', 'gravityforms' ),
'value' => 'source_url',
),
array(
'label' => esc_html__( 'Form Title', 'gravityforms' ),
'value' => 'form_title',
),
),
);
// Get Entry Meta.
$entry_meta = GFFormsModel::get_entry_meta( $this->settings->get_current_form_id() );
// If there is Entry Meta, add as choices.
if ( ! empty( $entry_meta ) ) {
$meta_choices = array();
foreach ( $entry_meta as $key => $meta ) {
$meta_choices[] = array(
'label' => rgar( $meta, 'label' ),
'value' => $key,
);
}
$choices[] = array(
'label' => esc_html__( 'Entry Meta', 'gravityforms' ),
'choices' => $meta_choices,
);
}
}
/**
* Filter the choices available in the field map drop down.
*
* @since 2.0.7.11
*
* @deprecated Deprecated since 2.5. Use gform_field_map_choices instead.
*
* @param array $fields The value and label properties for each choice.
* @param int $form_id The ID of the form currently being configured.
* @param null|array $field_type Null or the field types to be included in the drop down.
* @param null|array|string $exclude_field_types Null or the field type(s) to be excluded from the drop down.
*/
$choices = apply_filters( 'gform_addon_field_map_choices', $choices, $form_id, $input_type, $excluded_types );
/**
* Filter the choices available in the field map drop down.
*
* @since 2.5
*
* @param array $fields The value and label properties for each choice.
* @param int $form_id The ID of the form currently being configured.
* @param null|array $field_type Null or the field types to be included in the drop down.
* @param null|array|string $exclude_field_types Null or the field type(s) to be excluded from the drop down.
*/
$choices = apply_filters( 'gform_field_map_choices', $choices, $form_id, $input_type, $excluded_types );
return array_values( $choices );
}
// # VALIDATION METHODS --------------------------------------------------------------------------------------------
/**
* Validate posted field value.
*
* @since 2.5
*
* @param array|bool|string $value Posted field value.
*/
public function do_validation( $value ) {
// Get required choices.
$required_choices = $this->get_required_choices();
// If no choices are required, exit.
if ( empty( $required_choices ) ) {
return;
}
if ( ! is_array( $value ) ) {
return;
}
// Loop through the field map, check required choices.
foreach ( $value as $mapping ) {
// If this is not a required choice, skip.
if ( ! in_array( $mapping['key'], $required_choices ) ) {
continue;
}
// Get value.
$mapping_value = $mapping['value'] === 'gf_custom' ? $mapping['custom_value'] : $mapping['value'];
$mapping_value = trim( $mapping_value );
// If mapping value is empty, flag choice.
if ( empty( $mapping_value ) ) {
$this->set_error( rgobj( $this, 'error_message' ) );
$this->invalid_choices[] = $mapping['key'];
}
}
}
// # HELPER METHODS ------------------------------------------------------------------------------------------------
/**
* Get choices that are required to be filled out.
*
* @since 2.5
*
* @return array
*/
public function get_required_choices() {
// Get key field choices.
$key_choices = $this->get_choices( $this->key_field['choices'] );
if ( ! $key_choices ) {
return array();
}
// Merge sub-choices.
foreach ( $key_choices as &$key_choice ) {
if ( rgar( $key_choice, 'choices' ) ) {
$key_choices = array_merge( $key_choices, $key_choice['choices'] );
unset( $key_choice['choices'] );
}
}
// Get required choices.
$required_choices = array_filter(
$key_choices,
function( $choice ) { return rgar( $choice, 'required', false ); }
);
// Return only the name.
return array_map(
function( $choice ) {
return rgar( $choice, 'value' ) ? rgar( $choice, 'value' ) : rgar( $choice, 'name' );
},
$required_choices
);
}
/**
* Modify field value before saving.
*
* @since 2.5
*
* @param array|bool|string $value
*
* @return array|bool|string
*/
public function save( $value ) {
return is_array( $value ) ? $value : json_decode( $value, true );
}
}
Fields::register( 'generic_map', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Generic_Map' );

View File

@@ -0,0 +1,58 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
class Hidden extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'hidden';
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
// Get value.
$value = $this->get_value();
// Convert value to JSON string.
if ( is_array( $value ) ) {
$value = json_encode( $value );
}
// Prepare markup.
$html = sprintf(
'<input type="hidden" name="%1$s_%2$s" value=\'%3$s\' %4$s />',
esc_attr( $this->settings->get_input_name_prefix() ),
esc_attr( $this->name ),
esc_attr( $value ),
implode( ' ', $this->get_attributes() )
);
return $html;
}
}
Fields::register( 'hidden', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Hidden' );

View File

@@ -0,0 +1,46 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
class HTML extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'html';
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
$html = rgobj( $this, 'html' );
if ( is_callable( $html ) ) {
return call_user_func( $html );
}
// Prepare markup.
return $html;
}
}
Fields::register( 'html', '\Gravity_Forms\Gravity_Forms\Settings\Fields\HTML' );

View File

@@ -0,0 +1,618 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use GFAPI;
use GFCommon;
use GFFormsModel;
use GFNotification;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
class Notification_Routing extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'notification_routing';
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
// Get notification ID, current form.
$notification_id = rgempty( 'gform_notification_id' ) ? rgget( 'nid' ) : rgpost( 'gform_notification_id' );
$form = $this->settings->get_current_form();
$form = gf_apply_filters( array( 'gform_form_notification_page', $form['id'] ), $form, $notification_id );
// Get routing fields.
$routing_fields = self::get_routing_fields( $form, '0' );
// If form does not have routing fields, exit.
if ( empty( $routing_fields ) ) {
$html = sprintf(
'<div id="gform_notification_to_routing_rules"><div class="gold_notice"><p>%s</p></div></div>',
esc_html__( 'To use notification routing, your form must have a field supported by conditional logic.', 'gravityforms' )
);
return $html;
}
// Get routing.
$routing = $this->get_value();
$routing = empty( $routing ) ? array( array() ) : array_values( $routing );
// Add hidden input with routing.
$html = sprintf(
'<input type="hidden" name="%1$s_%2$s" id="%2$s" value=\'%3$s\' />',
esc_attr( $this->settings->get_input_name_prefix() ),
esc_attr( $this->name ),
esc_attr( json_encode( $routing ) )
);
// Open routing container.
$html .= '<div id="gform_notification_to_routing_rules">';
// Get routing operators.
$operators = array(
'is' => esc_html__( 'is', 'gravityforms' ),
'isnot' => esc_html__( 'is not', 'gravityforms' ),
'>' => esc_html__( 'greater than', 'gravityforms' ),
'<' => esc_html__( 'less than', 'gravityforms' ),
'contains' => esc_html__( 'contains', 'gravityforms' ),
'starts_with' => esc_html__( 'starts with', 'gravityforms' ),
'ends_with' => esc_html__( 'ends with', 'gravityforms' ),
);
// Set has invalid rule flag.
$has_invalid_rule = false;
// Loop through routing, display inputs.
foreach ( $routing as $i => $route ) {
// Determine if rule is valid.
if ( ! GFNotification::is_valid_notification_email( rgar( $route, 'email' ) ) && $this->settings->is_dependency_met( rgobj( $this, 'dependency' ) ) ) {
$valid_rule = false;
$has_invalid_rule = true;
} else {
$valid_rule = true;
}
// Prepare email input.
$email_input = sprintf(
'<input type="text" id="routing_email_%d" value="%s" class="gfield_routing_email" />',
$i,
esc_attr( rgar( $route, 'email' ) )
);
// Prepare routing field input.
$field_input = sprintf(
'<select id="routing_field_id_%1$d" class="gfield_routing_select" onchange="jQuery( \'#routing_value_%1$d\' ).replaceWith( GetRoutingValues( %1$d, jQuery( this ).val() ) ); SetRouting( %1$d );">%2$s</select>',
$i,
self::get_routing_fields( $form, rgar( $route, 'fieldId' ) )
);
// Get operators for route.
$operator_options = '';
foreach ( $operators as $key => $value ) {
$operator_options .= sprintf(
'<option value="%1$s"%3$s>%2$s</option>',
$key,
$value,
selected( rgar( $route, 'operator' ), $key, false )
);
}
// Prepare operator input.
$operator_input = sprintf( '<select id="routing_operator_%1$d" onchange="SetRouting(%1$d);" class="gform_routing_operator">%2$s</select>', $i, $operator_options );
// Prepare add button.
$add_button = sprintf(
'<button class="gform-settings-field__notification-routing-button gform-settings-field__notification-routing-button--add gform-st-icon gform-st-icon--circle-plus" onclick="SetRouting(%2$d); InsertRouting(%3$d);">
<span class="screen-reader-text">%1$s</span>
</button>',
esc_attr__( 'Add Another Rule', 'gravityforms' ),
$i,
$i + 1
);
// Prepare delete button.
if ( count( $routing ) > 1 ) {
$delete_button = sprintf(
'<img src="%3$s/images/remove.png" id="routing_delete_%1$d" title="%2%s" class="delete_field_choice" style="cursor:pointer;" onclick="DeleteRouting(%1$d);" onkeypress="DeleteRouting(%1$d);" />',
$i,
esc_attr__( 'Remove This Rule', 'gravityforms' ),
GFCommon::get_base_url()
);
$delete_button = sprintf(
'<button class="gform-settings-field__notification-routing-button gform-settings-field__notification-routing-button--delete gform-st-icon gform-st-icon--circle-minus" onclick="DeleteRouting(%2$d);">
<span class="screen-reader-text">%1$s</span>
</button>',
esc_attr__( 'Remove This Rule', 'gravityforms' ),
$i
);
} else {
$delete_button = '';
}
// Display input.
$html .= sprintf(
'<div%s>%s%s%s%s%s%s%s%s</div>',
$valid_rule ? '' : ' class="gform-settings-field__notification-routing-route--invalid"',
esc_html__( 'Send to', 'gravityforms' ),
$email_input,
esc_html__( 'if', 'gravityforms' ),
$field_input,
$operator_input,
self::get_field_values( $i, $form, rgar( $route, 'fieldId' ), rgar( $route, 'value' ) ),
$add_button,
$delete_button
);
}
// Close routing container.
$html .= '</div>';
// Display validation error.
if ( $has_invalid_rule ) {
$html .= sprintf(
'<span class="gform-settings-validation__error">%s</span>',
esc_html__( 'Please enter a valid email address for all highlighted routing rules above.', 'gravityforms' )
);
}
ob_start();
?>
<script type="text/javascript">
jQuery( document ).ready( function () {
jQuery( document ).on( 'input propertychange', '.gfield_routing_email', function () {
SetRoutingEmail( jQuery( this ) );
} );
jQuery( document ).on( 'change', '.gfield_routing_value_dropdown', function () {
SetRoutingValueDropDown( jQuery( this ) );
} );
} );
function SetRoutingEmail( element ) {
// Parsing ID to get routing Index
var index = element.attr( 'id' ).replace( 'routing_email_', '' );
SetRouting( index );
}
function SetRoutingValueDropDown(element) {
// Parsing ID to get routing Index
var index = element.attr("id").replace("routing_value_", '');
SetRouting(index);
}
function CreateRouting( routings ) {
var str = '';
for ( var i = 0; i < routings.length; i ++ ) {
var isSelected = routings[ i ].operator == 'is' ? "selected='selected'" : '';
var isNotSelected = routings[ i ].operator == 'isnot' ? "selected='selected'" : '';
var greaterThanSelected = routings[ i ].operator == '>' ? "selected='selected'" : '';
var lessThanSelected = routings[ i ].operator == '<' ? "selected='selected'" : '';
var containsSelected = routings[ i ].operator == 'contains' ? "selected='selected'" : '';
var startsWithSelected = routings[ i ].operator == 'starts_with' ? "selected='selected'" : '';
var endsWithSelected = routings[ i ].operator == 'ends_with' ? "selected='selected'" : '';
var email = routings[ i ][ "email" ] ? routings[ i ][ "email" ] : '';
str += "<div>" + <?php echo json_encode( esc_html__( 'Send to', 'gravityforms' ) ); ?> + " <input type='text' id='routing_email_" + i + "' value='" + email + "' class='gfield_routing_email' />";
str += " " + <?php echo json_encode( esc_html__( 'if', 'gravityforms' ) ); ?> + " " + GetRoutingFields( i, routings[ i ].fieldId ) + "&nbsp;";
str += "<select id='routing_operator_" + i + "' onchange='SetRouting(" + i + ");' class='gform_routing_operator'>";
str += "<option value='is' " + isSelected + ">" + <?php echo json_encode( esc_html__( 'is', 'gravityforms' ) ); ?> + "</option>";
str += "<option value='isnot' " + isNotSelected + ">" + <?php echo json_encode( esc_html__( 'is not', 'gravityforms' ) ); ?> + "</option>";
str += "<option value='>' " + greaterThanSelected + ">" + <?php echo json_encode( esc_html__( 'greater than', 'gravityforms' ) ); ?> + "</option>";
str += "<option value='<' " + lessThanSelected + ">" + <?php echo json_encode( esc_html__( 'less than', 'gravityforms' ) ); ?> + "</option>";
str += "<option value='contains' " + containsSelected + ">" + <?php echo json_encode( esc_html__( 'contains', 'gravityforms' ) ); ?> + "</option>";
str += "<option value='starts_with' " + startsWithSelected + ">" + <?php echo json_encode( esc_html__( 'starts with', 'gravityforms' ) ); ?> + "</option>";
str += "<option value='ends_with' " + endsWithSelected + ">" + <?php echo json_encode( esc_html__( 'ends with', 'gravityforms' ) ); ?> + "</option>";
str += "</select>&nbsp;";
str += GetRoutingValues( i, routings[ i ].fieldId, routings[ i ].value ) + "&nbsp;";
str += "<button class='gform-settings-field__notification-routing-button gform-settings-field__notification-routing-button--add gform-st-icon gform-st-icon--circle-plus' onclick='InsertRouting(" + ( i + 1 ) + ");'>";
str += "<span class='screen-reader-text'><?php esc_attr_e( 'Add Another Rule', 'gravityforms' ); ?></span>";
str += "</button>";
if ( routings.length > 1 ) {
str += "<button class='gform-settings-field__notification-routing-button gform-settings-field__notification-routing-button--delete gform-st-icon gform-st-icon--circle-minus' onclick='DeleteRouting(" + ( i ) + ");'>";
str += "<span class='screen-reader-text'><?php esc_attr_e( 'Remove This Rule', 'gravityforms' ); ?></span>";
str += "</button>";
}
str += "</div>";
}
jQuery( "#gform_notification_to_routing_rules" ).html( str );
jQuery( '#routing' ).val( JSON.stringify( current_notification.routing ) );
}
function GetRoutingValues( index, fieldId, selectedValue ) {
var str = GetFieldValues( index, fieldId, selectedValue, 16 );
return str;
}
function GetRoutingFields( index, selectedItem ) {
var str = "<select id='routing_field_id_" + index + "' class='gfield_routing_select' onchange='jQuery(\"#routing_value_" + index + "\").replaceWith(GetRoutingValues(" + index + ", jQuery(this).val())); SetRouting(" + index + "); '>";
str += GetSelectableFields( selectedItem, 16 );
str += "</select>";
return str;
}
//---------------------- generic ---------------
function GetSelectableFields(selectedFieldId, labelMaxCharacters) {
var str = "";
var inputType;
for (var i = 0; i < form.fields.length; i++) {
inputType = form.fields[i].inputType ? form.fields[i].inputType : form.fields[i].type;
// See if this field type can be used for conditionals.
if (IsNotificationConditionalLogicField(form.fields[i])) {
var selected = form.fields[i].id == selectedFieldId ? "selected='selected'" : "";
str += "<option value='" + form.fields[i].id + "' " + selected + ">" + GetLabel(form.fields[i]) + "</option>";
}
}
return str;
}
function IsNotificationConditionalLogicField( field ) {
// This function is a duplicate of IsConditionalLogicField from form_editor.js
inputType = field.inputType ? field.inputType : field.type;
var supported_fields = <?php echo json_encode( GFNotification::get_routing_field_types() ); ?>;
var index = jQuery.inArray( inputType, supported_fields );
return index >= 0;
}
function GetFirstSelectableField() {
var inputType;
for (var i = 0; i < form.fields.length; i++) {
inputType = form.fields[i].inputType ? form.fields[i].inputType : form.fields[i].type;
if (IsNotificationConditionalLogicField(form.fields[i])) {
return form.fields[i].id;
}
}
return 0;
}
function TruncateMiddle(text, maxCharacters) {
if (!text)
return "";
if (text.length <= maxCharacters)
return text;
var middle = parseInt(maxCharacters / 2);
return text.substr(0, middle) + "..." + text.substr(text.length - middle, middle);
}
function GetFieldValues(index, fieldId, selectedValue, labelMaxCharacters) {
if (!fieldId)
fieldId = GetFirstSelectableField();
if (!fieldId)
return "";
var str = '';
var field = GetFieldById(fieldId);
var isAnySelected = false;
if (!field)
return "";
if (field["type"] == 'post_category' && field["displayAllCategories"]) {
var dropdown_id = 'routing_value_' + index;
var dropdown = jQuery('#' + dropdown_id + ".gfield_category_dropdown");
// Don't load category drop down if it already exists (to avoid unecessary ajax requests).
if (dropdown.length > 0) {
var options = dropdown.html();
options = options.replace("value=\"" + selectedValue + "\"", "value=\"" + selectedValue + "\" selected=\"selected\"");
str = "<select id='" + dropdown_id + "' class='gfield_routing_select gfield_category_dropdown gfield_routing_value_dropdown'>" + options + "</select>";
}
else {
// Loading categories via AJAX.
jQuery.post(ajaxurl, { action: "gf_get_notification_post_categories",
ruleIndex : index,
selectedValue : selectedValue},
function (dropdown_string) {
if (dropdown_string) {
jQuery('#gfield_ajax_placeholder_' + index).replaceWith(dropdown_string.trim());
}
}
);
// Will be replaced by real drop down during the ajax callback.
str = "<select id='gfield_ajax_placeholder_" + index + "' class='gfield_routing_select'><option>" + <?php json_encode( esc_html__( 'Loading...', 'gravityforms' ) ); ?> + "</option></select>";
}
}
else if (field.choices) {
// Create a drop down for fields that have choices (i.e. drop down, radio, checkboxes, etc...).
str = "<select class='gfield_routing_select gfield_routing_value_dropdown' id='routing_value_" + index + "'>";
if (field.placeholder) {
str += "<option value=''>" + field.placeholder + "</option>";
}
for (var i = 0; i < field.choices.length; i++) {
var choiceValue = field.choices[i].value ? field.choices[i].value : field.choices[i].text;
var isSelected = choiceValue == selectedValue;
var selected = isSelected ? "selected='selected'" : '';
if (isSelected)
isAnySelected = true;
str += "<option value='" + choiceValue.replace(/'/g, "&#039;") + "' " + selected + ">" + field.choices[i].text + "</option>";
}
if (!isAnySelected && selectedValue) {
str += "<option value='" + selectedValue.replace(/'/g, "&#039;") + "' selected='selected'>" + selectedValue + "</option>";
}
str += "</select>";
}
else {
selectedValue = selectedValue ? selectedValue.replace(/'/g, "&#039;") : "";
// Create a text field for fields that don't have choices (i.e text, textarea, number, email, etc...).
str = "<input type='text' placeholder='" + <?php echo json_encode( esc_html__( 'Enter value', 'gravityforms' ) ); ?> +"' class='gfield_routing_select' id='routing_value_" + index + "' value='" + selectedValue.replace(/'/g, "&#039;") + "' onchange='SetRouting(" + index + ");' onkeyup='SetRouting(" + index + ");'>";
}
return str;
}
//---------------------------------------------------------------------------------
function InsertRouting(index) {
var routings = current_notification.routing;
routings.splice(index, 0, new ConditionalRule());
CreateRouting(routings);
SetRouting(index);
jQuery( '#routing' ).val( JSON.stringify( current_notification.routing ) );
}
/**
* Set the route array and the hidden field that holds the route JSON.
*
* @since unknown
* @since 2.5 Updated to keep the hidden field and the routing array in sync.
*
* @param {int} ruleIndex The index of the rule being edited.
*/
function SetRouting( ruleIndex ) {
// Get the current value of the hidden field and set it to a new conditional rule if it is an empty array.
$currentHiddenValue = JSON.parse( jQuery( '#routing' ).val() );
if ( $currentHiddenValue[0].length === 0 ) {
$currentHiddenValue[0] = new ConditionalRule();
jQuery( '#routing' ).val( JSON.stringify( $currentHiddenValue ) );
};
// Set the routing array for the current notification based on the hidden field value, so it populates even if validation fails.
current_notification.routing = $currentHiddenValue;
// If the current routing index doesn't exist after failed validation, create a blank conditional rule so we can update the values.
if ( !current_notification.routing[ruleIndex] ) {
current_notification.routing.splice( current_notification.routing.length, 0, new ConditionalRule() );
}
current_notification.routing[ruleIndex]["email"] = jQuery( "#routing_email_" + ruleIndex ).val();
current_notification.routing[ruleIndex]["fieldId"] = jQuery( "#routing_field_id_" + ruleIndex ).val();
current_notification.routing[ruleIndex]["operator"] = jQuery( "#routing_operator_" + ruleIndex ).val();
current_notification.routing[ruleIndex]["value"] = jQuery( "#routing_value_" + ruleIndex ).val();
jQuery( '#routing' ).val( JSON.stringify( current_notification.routing ) );
}
function DeleteRouting(ruleIndex) {
// Set the routing array for the current notification based on the hidden field value, so we delete the correct index.
current_notification.routing = JSON.parse( jQuery( '#routing' ).val() );
current_notification.routing.splice(ruleIndex, 1);
CreateRouting(current_notification.routing);
jQuery( '#routing' ).val( JSON.stringify( current_notification.routing ) );
}
</script>
<?php
$html .= ob_get_contents();
ob_end_clean();
return $html;
}
// # VALIDATION METHODS --------------------------------------------------------------------------------------------
/**
* Validate posted field value.
*
* @since 2.5
*
* @param array $value Posted field value.
*/
public function do_validation( $value ) {
// If no routes are defined, set field error.
if ( empty( $value ) || ! is_array( $value ) ) {
$this->set_error( rgobj( $this, 'error_message' ) );
}
// Validate routes.
foreach ( $value as $route ) {
if ( ! GFNotification::is_valid_notification_email( rgar( $route, 'email' ) ) ) {
$this->set_error( rgobj( $this, 'error_message' ) );
}
}
}
// # HELPER METHODS ------------------------------------------------------------------------------------------------
/**
* Gets all fields that can be used for notification routing and builds dropdowns.
*
* @since 2.5
*
* @param array $form The Form Object to search through.
* @param int $selected_field_id The currently selected field ID.
*
* @return string
*/
private static function get_routing_fields( $form, $selected_field_id ) {
$str = '';
foreach ( $form['fields'] as $field ) {
$field_label = GFFormsModel::get_label( $field );
if ( in_array( $field->get_input_type(), GFNotification::get_routing_field_types() ) ) {
$selected = $field->id == $selected_field_id ? "selected='selected'" : '';
$str .= "<option value='" . $field->id . "' " . $selected . '>' . $field_label . '</option>';
}
}
return $str;
}
/**
* Gets field values to be used with routing
*
* @since Unknown
*
* @param int $i The routing rule ID.
* @param array $form The Form Object.
* @param int $field_id The field ID.
* @param string $selected_value The field value of the selected item.
*
* @return string
*/
private static function get_field_values( $i, $form, $field_id, $selected_value ) {
if ( empty( $field_id ) ) {
$field_id = self::get_first_routing_field( $form );
}
if ( empty( $field_id ) ) {
return null;
}
$field = GFAPI::get_field( $form, $field_id );
if ( ! $field ) {
return null;
}
if ( $field->type == 'post_category' && $field->displayAllCategories == true ) {
return wp_dropdown_categories(
array(
'class' => 'gfield_routing_select gfield_category_dropdown gfield_routing_value_dropdown',
'orderby' => 'name',
'id' => 'routing_value_' . $i,
'selected' => $selected_value,
'hierarchical' => true,
'hide_empty' => 0,
'echo' => false,
)
);
} else if ( $field->choices ) {
$is_any_selected = false;
$html = sprintf( '<select id="routing_value_%d" class="gfield_routing_select gfield_routing_value_dropdown">', $i );
$html .= $field->placeholder ? sprintf( '<option value="">%s</option>', esc_html( $field->placeholder ) ) : null;
foreach ( $field->choices as $choice ) {
if ( $choice['value'] == $selected_value ) {
$is_any_selected = true;
}
$html .= sprintf(
'<option value="%s"%s>%s</option>',
esc_html( $choice['value'] ),
selected( $selected_value, $choice['value'], false ),
esc_html( rgar( $choice, 'text' ) )
);
}
// Adding current selected field value to the list
if ( ! $is_any_selected && ! empty( $selected_value ) ) {
$html .= sprintf(
'<option value="%1$s" selected="selected">%1$s</option>',
esc_html( $selected_value )
);
}
$html .= '</select>';
return $html;
} else {
return sprintf(
'<input type="text" placeholder="%1$s" class="gfield_routing_select" id="routing_value_%3$d" value="%2$s" onchange="SetRouting( %3$d );" onkeyup="SetRouting( %3$d );" />',
esc_html__( 'Enter value', 'gravityforms' ),
esc_attr( $selected_value ),
$i
);
}
}
/**
* Gets the first field that can be used for notification routing.
*
* @since Unknown
*
* @param array $form The Form Object to search through.
*
* @return int
*/
private static function get_first_routing_field( $form ) {
foreach ( $form['fields'] as $field ) {
if ( in_array( $field->get_input_type(), GFNotification::get_routing_field_types() ) ) {
return $field->id;
}
}
return 0;
}
}
Fields::register( 'notification_routing', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Notification_Routing' );

View File

@@ -0,0 +1,164 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
class Post_Select extends Select {
/**
* Field type.
*
* @since 2.6.2
*
* @var string
*/
public $type = 'post_select';
/**
* Post type.
*
* @since 2.6.2
*
* @var string
*/
public $post_type = 'page';
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.6.2
*
* @return string
*/
public function markup() {
// Display description.
$html = $this->get_description();
$html .= '<span class="' . esc_attr( $this->get_container_classes() ) . '">';
// Get post type details.
$post_type = get_post_type_object( $this->post_type );
if ( ! $post_type ) {
$html .= esc_html( sprintf( __( 'The requested post type %s does not exist.', 'gravityforms' ), $this->post_type ) );
} else {
$post_singular = $post_type->labels->singular_name;
$post_plural = $post_type->labels->name;
$html = sprintf(
'<article class="gform-dropdown" data-js="gform-settings-field-select" data-post-type="%1$s">
<span class="gform-visually-hidden" id="gform-%2$s-label">
%3$s
</span>
<button
type="button"
aria-expanded="false"
aria-haspopup="listbox"
aria-labelledby="gform-%2$s-label gform-%2$s-control"
class="gform-dropdown__control %6$s"
data-js="gform-dropdown-control"
id="gform-%2$s-control"
>
<span
class="gform-dropdown__control-text"
data-js="gform-dropdown-control-text"
>
%3$s
</span>
<span class="gform-spinner gform-dropdown__spinner"></span>
<span class="gform-icon gform-icon--chevron gform-dropdown__chevron"></span>
</button>
<div
aria-labelledby="gform-%2$s-label"
class="gform-dropdown__container"
role="listbox"
data-js="gform-dropdown-container"
tabindex="-1"
>
<div class="gform-dropdown__search">
<label for="gform-settings-field__%2$s-search" class="gform-visually-hidden">
%4$s
</label>
<input
id="gform-settings-field__%2$s-search"
type="text"
class="gform-input gform-dropdown__search-input"
placeholder="%4$s"
data-js="gform-dropdown-search"
/>
<span class="gform-icon gform-icon--search gform-dropdown__search-icon"></span>
</div>
<div class="gform-dropdown__list-container">
<ul class="gform-dropdown__list" data-js="gform-dropdown-list"></ul>
</div>
</div>
<input type="hidden" data-js="gf-post-select-input" name="_gform_setting_%2$s" id="%2$s" value="%5$s"/>
</article>',
$this->post_type,
esc_attr( $this->name ), // field name, used in HTML attributes
esc_html( $this->get_dropdown_label( $post_singular ) ), // form switcher label
esc_html( $this->get_search_label( $post_plural ) ), // label for search field
esc_attr( $this->get_value() ),
empty( $this->get_value() ) ? 'gform-dropdown__control--placeholder' : ''
);
}
// If field failed validation, add error icon.
$html .= $this->get_error_icon();
$html .= '</span>';
return $html;
}
/**
* Get the label for the dropdown.
*
* @since 2.6.2
*
* @param string $singular Post type name (singular)
*
* @return string
*/
public function get_dropdown_label( $singular ) {
if ( empty( $this->get_value() ) ) {
// Translators: singular post type name (e.g. 'post').
return sprintf( __( 'Select a %s', 'gravityforms' ), $singular );
}
$post_id = $this->get_value();
return get_the_title( $post_id );
}
/**
* Get the label for the search field.
*
* @since 2.6.2
*
* @param string $plural Post type name (plural)
*
* @return string
*/
public function get_search_label( $plural ) {
// Translators: plural post type name (e.g. 'post's).
return sprintf( __( 'Search all %s', 'gravityforms' ), $plural );
}
}
Fields::register( 'post_select', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Post_Select' );

View File

@@ -0,0 +1,210 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use GFCommon;
use Gravity_Forms\Gravity_Forms\Settings\Settings;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
class Radio extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'radio';
/**
* Field choices.
*
* @since 2.5
*
* @var array
*/
public $choices = array();
/**
* Whether the input should be an image select.
*
* @var bool
*/
public $image_select = false;
/**
* Whether the inputs should be horizontal.
*
* @since 2.5
*
* @var bool
*/
public $horizontal;
/**
* Initialize field.
*
* @since 2.5
*
* @param array $props Field properties.
* @param Settings $settings Settings instance.
*/
public function __construct( $props, $settings ) {
// Prepare class.
$class = self::has_icons( rgar( $props, 'choices', array() ) ) ? array( 'gform-settings-field__radio--visual' ) : array();
// Convert defined classes.
if ( rgar( $props, 'class' ) ) {
$props['class'] = explode( ' ', $props['class'] );
$class = array_merge( $class, $props['class'] );
unset( $props['class'] );
}
// Define class.
$this->class = implode( ' ', $class );
parent::__construct( $props, $settings );
}
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
// Get choices, selected value, classes.
$choices = $this->get_choices();
$selected_value = $this->get_value();
$horizontal_class = rgobj( $this, 'horizontal' ) || self::has_icons( $choices ) ? ' gform-settings-choice--inline' : '';
$icon_class = self::has_icons( $choices ) ? ' gform-settings-choice--visual' : '';
if ( $this->image_select ) {
$icon_class = ' gform-settings-choice--image-select';
}
// If no choices exist, return.
if ( $choices === false || empty( $choices ) ) {
return '';
}
// Display description.
$html = $this->get_description();
$html .= '<span class="' . esc_attr( $this->get_container_classes() ) . '">';
// Create inputs from choices.
foreach ( $choices as $i => $choice ) {
// Prepare choice attributes.
$choice['id'] = rgempty( 'id', $choice ) ? sprintf( '%s%s', $this->name, $i ) : $choice['id'];
$choice_value = isset( $choice['value'] ) ? $choice['value'] : $choice['label'];
$choice_tooltip = $this->settings->maybe_get_tooltip( $choice );
// Get input.
$choice_input = sprintf(
'<input type="radio" name="%s_%s" value="%s" %s %s />',
$this->settings->get_input_name_prefix(),
$this->name,
$choice_value,
checked( $selected_value, $choice_value, false ),
implode( ' ', self::get_choice_attributes( $choice, $this->get_attributes() ) )
);
// Get icon markup.
if ( self::has_icons( $choices ) ) {
// Get the defined icon or use default.
$icon_markup = GFCommon::get_icon_markup( $choice, 'fa-cog' );
}
$html .= sprintf(
'<div id="gform-settings-radio-choice-%1$s" class="gform-settings-choice%2$s%3$s">
%4$s
<label for="%1$s">
<span>%5$s <span class="gform-settings-choice-label">%6$s</span> %7$s</span>
</label>
</div>',
esc_attr( $choice['id'] ),
$icon_class,
$horizontal_class,
$choice_input,
isset( $icon_markup ) ? $icon_markup : '',
esc_html( $choice['label'] ),
$choice_tooltip
);
}
// Wrap visual choices with container.
if ( ! empty( $icon_class ) ) {
$html = sprintf(
'<div class="gform-settings-choices--visual">%s</div>',
$html
);
}
// If field failed validation, add error icon.
$html .= $this->get_error_icon();
$html .= '</span>';
return $html;
}
// # VALIDATION METHODS --------------------------------------------------------------------------------------------
/**
* Validate posted field value.
*
* @since 2.5
*
* @param string $value Posted field value.
*/
public function do_validation( $value ) {
// If field is required and value is missing, set field error.
if ( $this->required && rgblank( $value ) ) {
$this->set_error( rgobj( $this, 'error_message' ) );
}
// Get choices.
$choices = $this->get_choices();
// If no selection was made or no choices exist, exit.
if ( rgblank( $value ) || $choices === false || empty( $choices ) ) {
return;
}
// Loop through choices, determine if valid.
foreach ( $choices as $choice ) {
if ( self::is_choice_valid( $choice, $value ) ) {
return; // Choice is valid
}
}
$this->set_error( esc_html__( 'Invalid selection.', 'gravityforms' ) );
}
}
Fields::register( 'radio', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Radio' );

View File

@@ -0,0 +1,228 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use GFCommon;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
// Load base class.
require_once 'class-select.php';
require_once 'class-text.php';
class Select_Custom extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'select_custom';
/**
* Child inputs.
*
* @since 2.5
*
* @var Base[]
*/
public $inputs = array();
/**
* Initialize Field Select field.
*
* @since 2.5
*
* @param array $props Field properties.
* @param \Gravity_Forms\Gravity_Forms\Settings\Settings $settings Settings instance.
*/
public function __construct( $props, $settings ) {
parent::__construct( $props, $settings );
// Prevent description from showing up on all sub-fields.
unset( $props['description'] );
// Prepare Custom input.
$this->inputs['custom'] = $props;
$this->inputs['custom']['type'] = 'text';
$this->inputs['custom']['name'] .= '_custom';
// Prepare Select input.
$this->inputs['select'] = $props;
$this->inputs['select']['type'] = 'select';
$this->inputs['select']['onchange'] = '';
// Set custom option flag.
$has_custom_option = false;
// Loop through choices, search for custom option.
foreach ( $this->inputs['select']['choices'] as $choice ) {
// If choice is a custom option, set flag and stop loop.
if ( 'gf_custom' === rgar( $choice, 'name' ) || 'gf_custom' === rgar( $choice, 'value' ) ) {
$has_custom_option = true;
break;
}
// Check sub-choices, if present.
if ( rgar( $choice, 'choices' ) ) {
foreach ( $choice['choices'] as $subchoice ) {
if ( 'gf_custom' === rgar( $subchoice, 'name' ) || 'gf_custom' === rgar( $subchoice, 'value' ) ) {
$has_custom_option = true;
break;
}
}
}
}
// If no custom option exists, add it.
if ( ! $has_custom_option ) {
// Prepare label.
if ( rgar( $this->inputs['select'], 'label' ) ) {
$custom_label = sprintf(
'%s %s',
esc_html__( 'Add Custom', 'gravityforms' ),
esc_html( $this->inputs['select']['label'] )
);
} else {
$custom_label = esc_html__( 'Add Custom Value', 'gravityforms' );
}
// Add custom option.
$this->inputs['select']['choices'][] = array(
'label' => $custom_label,
'value' => 'gf_custom',
);
}
/**
* Prepare input fields.
*
* @var array $input
*/
foreach ( $this->inputs as &$input ) {
$input = Fields::create( $input, $this->settings );
}
}
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
// Get selected option, initialize custom input display.
$selected_option = $this->inputs['select']->get_value();
$custom_input_display = '';
// If selected option is the custom option or the only option is the custom option, hide select field.
if ( $selected_option === 'gf_custom' || ( 1 === count( $this->inputs['select']->choices ) && $this->inputs['select']->choices[0]['value'] === 'gf_custom' ) ) {
$this->inputs['select']->style = 'display:none;';
} else {
$custom_input_display = 'style="display:none;"';
}
// Prepare markup.
$html = $this->get_description();
$html .= sprintf(
'<span class="%s">%s<div class="gform-settings-select-custom__custom" %s>%s%s</div>%s</span>',
esc_attr( $this->get_container_classes() ),
$this->inputs['select']->markup(),
$custom_input_display,
count( $this->inputs['select']->choices ) > 1 ? '<button type="button" class="gform-settings-select-custom__reset"><span class="screen-reader-text">' . esc_html__( 'Reset', 'gravityforms' ) . '</span></button>' : '',
$this->inputs['custom']->markup(),
$this->get_error_icon()
);
return $html;
}
// # VALIDATION METHODS --------------------------------------------------------------------------------------------
/**
* Validate posted field value.
*
* @since 2.5
*
* @param array $value Posted field values.
*/
public function do_validation( $value ) {
// If field has no choices, exit.
if ( empty( $this->inputs['select']->choices ) ) {
return;
}
// Get values for both inputs.
$select_value = $value;
$custom_value = rgar( $this->settings->get_posted_values(), $this->inputs['custom']->name );
// If field is required and no choice was selected, set field error.
if ( $this->required && rgblank( $select_value ) ) {
$this->inputs['select']->set_error();
return;
}
// If field is required and no custom value was submitted, set field error.
if ( $this->required && $select_value === 'gf_custom' && rgblank( $custom_value ) ) {
$this->inputs['select']->set_error();
return;
}
// If a custom choice was not selected, validate selected choice.
if ( $select_value === 'gf_custom' ) {
// Loop through field choices, determine if valid.
foreach ( $this->inputs['select']->choices as $choice ) {
// If choice has nested choices, loop through and determine if valid.
if ( isset( $choice['choices'] ) ) {
foreach ( $choice['choices'] as $optgroup_choice ) {
if ( self::is_choice_valid( $optgroup_choice, $select_value ) ) {
return;
}
}
} else {
if ( self::is_choice_valid( $choice, $select_value ) ) {
return;
}
}
}
$this->inputs['select']->set_error( esc_html__( 'Invalid selection.', 'gravityforms' ) );
}
}
}
Fields::register( 'select_custom', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Select_Custom' );

View File

@@ -0,0 +1,326 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
class Select extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'select';
/**
* Field choices.
*
* @since 2.5
*
* @var array
*/
public $choices = array();
/**
* Message to display when no choices exist.
*
* @since 2.5
*
* @var string
*/
public $no_choices;
/**
* Enable enhanced UI with Select2.
*
* @since 2.5
*
* @var bool
*/
public $enhanced_ui = false;
/**
* Field content after select.
*
* @since 2.5
*
* @var string
*/
public $after_select;
public $disabled;
/**
* Register scripts to enqueue when displaying field.
*
* @since 2.5
*
* @return array
*/
public function scripts() {
if ( ! $this->enhanced_ui ) {
return array();
}
return array(
array(
'handle' => 'gform_selectwoo',
),
);
}
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
// Display description.
$html = $this->get_description();
$html .= '<span class="' . esc_attr( $this->get_container_classes() ) . '">';
// Get choices.
$choices = $this->get_choices();
// If no choices were provided and there is a no choices message, display it.
if ( ( $choices === false || empty( $choices ) ) && ! empty( $this->no_choices ) ) {
$html .= $this->no_choices;
} else {
$attributes = $this->get_attributes();
$select_input = sprintf(
'<select name="%s_%s" %s %s>%s</select>%s',
esc_attr( $this->settings->get_input_name_prefix() ),
esc_attr( $this->name ),
$this->get_describer() ? sprintf( 'aria-describedby="%s"', $this->get_describer() ) : '',
implode( ' ', $attributes ),
self::get_options( $choices, $this->get_value() ),
rgobj( $this, 'after_select' )
);
// Display enhanced select UI.
if ( $this->enhanced_ui ) {
$input_id = preg_replace( "/id='(.*)'/m", '${1}', $attributes['id'] );
// Wrap select input.
$html .= sprintf( '<span class="gform-settings-field__select--enhanced">%s</span>', $select_input );
$html .= '<script type="text/javascript">
jQuery( document ).ready( function () {
jQuery( "#' . esc_attr( $input_id ) . '" ).select2( {
minimumResultsForSearch: Infinity,
dropdownCssClass: "gform-settings-field__select-enhanced-container",
dropdownParent: jQuery( "#' . esc_attr( $input_id ) . '" ).parent(),
} );
} );
</script>';
} else {
$html .= $select_input;
}
}
// If field failed validation, add error icon.
$html .= $this->get_error_icon();
$html .= '</span>';
return $html;
}
/**
* Prepares an array of choices as an HTML string of options.
*
* @since 2.5
*
* @param array $choices Array of drop down choices.
* @param string $selected Currently selected value.
*
* @return string
*/
public static function get_options( $choices, $selected ) {
// Return blank if $choices is not an array.
if ( ! is_array( $choices ) ) {
return '';
}
$html = '';
// Loop through choices, prepare HTML.
foreach ( $choices as $choice ) {
// If choice has choices, render as option group.
if ( isset( $choice['choices'] ) ) {
$html .= sprintf(
'<optgroup label="%s">%s</optgroup>',
esc_attr( $choice['label'] ),
self::get_options( $choice['choices'], $selected )
);
} else {
// Set value to label if not defined.
$choice['value'] = isset( $choice['value'] ) ? $choice['value'] : $choice['label'];
// Prepare selected attribute.
if ( is_array( $selected ) ) {
$selected_attr = in_array( $choice['value'], $selected ) ? 'selected="selected"' : '';
} else {
$selected_attr = selected( $selected, $choice['value'], false );
}
$html .= sprintf(
'<option value="%s" %s>%s</option>',
esc_attr( $choice['value'] ),
$selected_attr,
esc_attr( $choice['label'] )
);
}
}
return $html;
}
// # VALIDATION METHODS --------------------------------------------------------------------------------------------
/**
* Validate posted field value.
*
* @since 2.5
*
* @param string|array $value Posted field value.
*/
public function do_validation( $value ) {
// If field is not multiselect, but is required with no selection, set field error.
if ( ! $this->support_multiple() && $this->required && rgblank( $value ) ) {
$this->set_error( rgobj( $this, 'error_message' ) );
return;
}
// Get choices.
$choices = $this->get_choices();
// If no selection was made or no choices exist, exit.
if ( ( rgblank( $value ) && ! $this->required ) || $choices === false || empty( $choices ) ) {
return;
}
if ( $this->support_multiple() ) {
$selected = 0;
// Loop through field choices, determine if valid.
foreach ( $choices as $choice ) {
// If choice has nested choices, loop through and determine if valid.
if ( isset( $choice['choices'] ) ) {
foreach ( $choice['choices'] as $optgroup_choice ) {
if ( self::is_choice_valid( $optgroup_choice, $value ) ) {
$selected++;
}
}
} else {
if ( self::is_choice_valid( $choice, $value ) ) {
$selected++;
}
}
}
// If field is required and no choices were selected, set field error.
if ( $this->required && $selected == 0 ) {
$this->set_error( rgobj( $this, 'error_message' ) );
return;
}
// If field is not required and the number of valid selected choices does not match posted value, set field error.
if ( ! $this->required && $selected !== count( $value ) ) {
$this->set_error( esc_html__( 'Invalid selection.', 'gravityforms' ) );
}
} else {
// Loop through field choices, determine if valid.
foreach ( $choices as $choice ) {
// If choice has nested choices, loop through and determine if valid.
if ( isset( $choice['choices'] ) ) {
foreach ( $choice['choices'] as $optgroup_choice ) {
if ( self::is_choice_valid( $optgroup_choice, $value ) ) {
return;
}
}
} else {
if ( self::is_choice_valid( $choice, $value ) ) {
return; // Choice is valid
}
}
}
// If invalid selection was made, set field error.
$this->set_error( esc_html__( 'Invalid selection.', 'gravityforms' ) );
}
}
/**
* Determine if a select field supports multiple choices.
*
* @since 2.5
*
* @return bool true if the field supports multiple choices, false if not.
*/
public function support_multiple() {
$multiple = rgobj( $this, 'multiple' );
if ( $multiple === 'multiple' || $multiple === true ) {
return true;
}
return false;
}
}
Fields::register( 'select', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Select' );

View File

@@ -0,0 +1,108 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
// Load base class.
require_once 'class-select.php';
require_once 'class-text.php';
class Text_And_Select extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'text_and_select';
/**
* Child inputs.
*
* @since 2.5
*
* @var Base[]
*/
public $inputs = array();
/**
* Initialize Field Select field.
*
* @since 2.5
*
* @param array $props Field properties.
* @param \Gravity_Forms\Gravity_Forms\Settings\Settings $settings Settings instance.
*/
public function __construct( $props, $settings ) {
parent::__construct( $props, $settings );
// Prevent description from showing up on all sub-fields.
unset( $props['description'] );
// Prepare Text field.
if ( isset( $this->inputs['text'] ) ) {
// Set field type.
if ( rgars( $this->inputs, 'text/type' ) && $this->inputs['text']['type'] !== 'text' ) {
$this->inputs['text']['input_type'] = $this->inputs['text']['type'];
$this->inputs['text']['type'] = 'text';
} else if ( ! rgars( $this->inputs, 'text/type' ) ) {
$this->inputs['text']['type'] = 'text';
}
}
// Prepare Select field.
if ( isset( $this->inputs['select'] ) ) {
// Set field type.
$this->inputs['select']['type'] = 'select';
}
// Prepare input fields.
foreach ( $this->inputs as &$input ) {
$input = Fields::create( $input, $this->settings );
}
}
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
// Prepare markup.
$html = $this->get_description();
$html .= sprintf(
'<span class="%s">%s %s %s</span>',
esc_attr( $this->get_container_classes() ),
isset( $this->inputs['text'] ) ? $this->inputs['text']->markup() : '',
isset( $this->inputs['select'] ) ? $this->inputs['select']->markup() : '',
$this->get_error_icon()
);
return $html;
}
}
Fields::register( 'text_and_select', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Text_And_Select' );

View File

@@ -0,0 +1,132 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
class Text extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'text';
/**
* Input type.
*
* @since 2.5
*
* @var string
*/
public $input_type = 'text';
protected $step;
protected $append;
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
// Get value.
$value = $this->get_value();
// Prepare after_input.
// Dynamic after_input content should use a callable to render.
if ( isset( $this->after_input ) && is_callable( $this->after_input ) ) {
$this->after_input = call_user_func( $this->after_input, $value, $this );
}
// Prepare markup.
// Display description.
$html = $this->get_description();
$html .= sprintf(
'<span class="%s"><input type="%s" name="%s_%s" value="%s" %s %s />%s %s</span>',
esc_attr( $this->get_container_classes() ),
esc_attr( $this->input_type ),
esc_attr( $this->settings->get_input_name_prefix() ),
esc_attr( $this->name ),
$value ? esc_attr( htmlspecialchars( $value, ENT_QUOTES ) ) : '',
$this->get_describer() ? sprintf( 'aria-describedby="%s"', $this->get_describer() ) : '',
implode( ' ', $this->get_attributes() ),
isset( $this->append ) ? sprintf( '<span class="gform-settings-field__text-append">%s</span>', esc_html( $this->append ) ) : '',
$this->get_error_icon()
);
// Insert after input markup.
$html .= isset( $this->after_input ) ? $this->after_input : '';
return $html;
}
// # VALIDATION METHODS --------------------------------------------------------------------------------------------
/**
* Validate posted field value.
*
* @since 2.5
*
* @param string $value Posted field value.
*/
public function do_validation( $value ) {
// If field is required and value is missing, set field error.
if ( $this->required && rgblank( $value ) ) {
$this->set_error( rgobj( $this, 'error_message' ) );
}
// Sanitize posted value.
$sanitized_value = sanitize_text_field( $value );
// If posted and sanitized values do not match, add field error.
if ( $value !== $sanitized_value ) {
// Prepare correction script.
$double_encoded_safe_value = htmlspecialchars( htmlspecialchars( $sanitized_value, ENT_QUOTES ), ENT_QUOTES );
$script = sprintf(
'jQuery("input[name=\"%s_%s\"]").val(jQuery(this).data("safe"));',
$this->settings->get_input_name_prefix(),
$this->name
);
// Prepare message.
$message = sprintf(
"%s <a href='javascript:void(0);' onclick='%s' data-safe='%s'>%s</a>",
esc_html__( 'The text you have entered is not valid. For security reasons, some characters are not allowed. ', 'gravityforms' ),
htmlspecialchars( $script, ENT_QUOTES ),
$double_encoded_safe_value,
esc_html__( 'Fix it', 'gravityforms' )
);
// Set field error.
$this->set_error( $message );
}
}
}
Fields::register( 'text', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Text' );

View File

@@ -0,0 +1,270 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
use GFCommon;
defined( 'ABSPATH' ) || die();
class Textarea extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'textarea';
/**
* Allow HTML in field value.
*
* @since 2.5
*
* @var bool
*/
public $allow_html = false;
/**
* Initialize as Rich Text Editor.
*
* @since 2.5
*
* @var bool
*/
public $use_editor = false;
/**
* Number of rows.
*
* @since 2.5
*
* @var int
*/
public $rows;
/**
* Editor height.
*
* @since 2.5
*
* @var int
*/
public $editor_height;
/**
* Initialize Textarea field.
*
* @since 2.5
*
* @param array $props Field properties.
* @param \Gravity_Forms\Gravity_Forms\Settings\Settings $settings Settings instance.
*/
public function __construct( $props, $settings ) {
parent::__construct( $props, $settings );
// Set default row count.
if ( ! isset( $this->rows ) ) {
$this->rows = 4;
}
// Set default editor height.
if ( ! isset( $this->editor_height ) ) {
$this->editor_height = 200;
}
}
private function get_editor_id() {
return esc_attr( $this->settings->get_input_name_prefix() ) . '_' . esc_attr( $this->name );
}
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
// Get value.
$value = $this->get_value();
if ($value === null) {
$value = '';
}
// Initialize rich text editor.
if ( $this->use_editor ) {
// Create editor container.
$html = sprintf(
'<span class="mt-gaddon-editor mt-%s_%s"></span>',
esc_attr( $this->settings->get_input_name_prefix() ),
esc_attr( $this->name )
);
// Display description.
$html .= $this->get_description();
$html .= '<span class="' . esc_attr( $this->get_container_classes() ) . '">';
// Insert editor.
ob_start();
wp_editor(
$value,
$this->get_editor_id(),
array(
'autop' => false,
'editor_class' => $this->get_editor_class(),
'editor_height' => $this->editor_height,
)
);
$html .= ob_get_contents();
ob_end_clean();
// If field failed validation, add error icon.
$html .= $this->get_error_icon();
$html .= '</span>';
} else {
$escaped_value = $value !== null ? esc_textarea( $value ) : '';
// Prepare markup.
// Display description.
$html = $this->get_description();
$html .= sprintf(
'<span class="%s"><textarea name="%s_%s" %s %s>%s</textarea>%s</span>',
esc_attr( $this->get_container_classes() ),
esc_attr( $this->settings->get_input_name_prefix() ),
esc_attr( $this->name ),
$this->get_describer() ? sprintf( 'aria-describedby="%s"', $this->get_describer() ) : '',
implode( ' ', $this->get_attributes() ),
$escaped_value,
// If field failed validation, add error icon.
$this->get_error_icon()
);
}
return $html;
}
/**
* Get the CSS classes for the rich text editor.
*
* @since 2.6
*
* @return string
*/
public function get_editor_class() {
$editor_class = ! is_null( $this->class ) ? $this->class : 'merge-tag-support mt-wp_editor mt-manual_position mt-position-right';
// If a rich text editor has custom classes and merge tag support, make sure it includes the 'mt-manual_position' class to prevent layout problems.
$classes = explode( ' ', $editor_class );
if ( in_array( 'merge-tag-support', $classes ) && ! in_array( 'mt-manual_position', $classes ) ) {
$editor_class .= ' mt-manual_position';
}
return $editor_class;
}
// # VALIDATION METHODS --------------------------------------------------------------------------------------------
/**
* Validate posted field value.
*
* @since 2.5
*
* @param string $value Posted field value.
*/
public function do_validation( $value ) {
// If field is required and value is missing, set field error.
if ( $this->required && rgblank( $value ) ) {
$this->set_error( rgobj( $this, 'error_message' ) );
}
$sanitized_value = $this->get_sanitized_value( $value );
// If posted and sanitized values match, we're done here.
if ( $value === $sanitized_value ) {
return;
}
// Failed validation. Prepare field error.
$message = sprintf(
"%s <a href='javascript:void(0);' onclick='%s' data-safe='%s'>%s</a>",
esc_html__( 'The text you have entered is not valid. For security reasons, some characters are not allowed. ', 'gravityforms' ),
$this->get_validation_correction_script(),
htmlspecialchars( $sanitized_value, ENT_QUOTES ),
esc_html__( 'Fix it', 'gravityforms' )
);
// Set field error.
$this->set_error( $message );
}
/**
* Gets the sanitized value of the user input.
*
* Textarea fields must explicitly opt in to allowing HTML, either by indicating the editor type or by passing the
* allow_html setting. In those cases, we run the content through wp_kses based on user permissions (users with
* the unfiltered_html capability can enter in raw html).
*
* By default, HTML is not allowed, so we simply sanitize the field, even if the user has permission to include
* unfiltered html.
*
* @since 2.5.2
*
* @param string $value The input value to sanitized.
*
* @return string
*/
private function get_sanitized_value( $value ) {
add_filter( 'safe_style_css', array( $this, 'disable_style_attr_parsing' ), 10, 1 );
$sanitized = ( $this->use_editor || $this->allow_html ) ? GFCommon::maybe_wp_kses( $value ) : sanitize_textarea_field( $value );
remove_filter( 'safe_style_css', array( $this, 'disable_style_attr_parsing' ), 10 );
return $sanitized;
}
public function disable_style_attr_parsing( $allowed ) {
return array();
}
/**
* Get the correction script for the field.
*
* @since 2.5.2
*
* @return string
*/
protected function get_validation_correction_script() {
$script = sprintf(
'jQuery("textarea[name=\"%s_%s\"]").val(jQuery(this).data("safe"));',
$this->settings->get_input_name_prefix(),
$this->name
);
return htmlspecialchars( $script, ENT_QUOTES );
}
}
Fields::register( 'textarea', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Textarea' );

View File

@@ -0,0 +1,89 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Settings\Fields;
use Gravity_Forms\Gravity_Forms\Settings\Fields;
defined( 'ABSPATH' ) || die();
class Toggle extends Base {
/**
* Field type.
*
* @since 2.5
*
* @var string
*/
public $type = 'toggle';
/**
* Message to display for screen readers.
*
* @since 2.5
*
* @var string
*/
public $toggle_label;
protected $disabled;
// # RENDER METHODS ------------------------------------------------------------------------------------------------
/**
* Render field.
*
* @since 2.5
*
* @return string
*/
public function markup() {
// Determine if toggle is selected.
$enabled = $this->get_value();
// Prepare input ID.
$input_id = sprintf(
'%s_%s',
esc_attr( $this->settings->get_input_name_prefix() ),
esc_attr( $this->name )
);
// Display description.
$html = $this->get_description();
$html .= '<span class="' . esc_attr( $this->get_container_classes() ) . '">';
// Insert checkbox.
$html .= sprintf(
'<input type="checkbox" name="%1$s" id="%1$s" value="1" %2$s %3$s %4$s />',
$input_id,
checked( '1', $enabled, false ),
$this->get_describer() ? sprintf( 'aria-describedby="%s"', $this->get_describer() ) : '',
implode( ' ', $this->get_attributes() )
);
// Insert toggle UI.
$html .= sprintf( '<label class="gform-field__toggle-container" for="%s">', $input_id );
$html .= $this->toggle_label ? sprintf( '<span class="screen-reader-text">%s</span>', rgar( $this, 'label' ) ? esc_html( $this->toggle_label ) : '' ) : '';
$html .= '<span class="gform-field__toggle-switch"></span>';
$html .= '</label>';
// Insert after input markup.
$html .= rgobj( $this, 'after_input' );
// If field failed validation, add error icon.
$html .= $this->get_error_icon();
$html .= '</span>';
return $html;
}
}
Fields::register( 'toggle', '\Gravity_Forms\Gravity_Forms\Settings\Fields\Toggle' );