Skip to content

Instantly share code, notes, and snippets.

@codearachnid
Last active March 8, 2023 21:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save codearachnid/b7d993c30f56e97da707e01cda6c3245 to your computer and use it in GitHub Desktop.
Save codearachnid/b7d993c30f56e97da707e01cda6c3245 to your computer and use it in GitHub Desktop.
Gravity Forms: Add "other" option to checkboxes

This is a hacked in way of enabling "other" options to the Gravity Forms Checkbox Field https://docs.gravityforms.com/checkboxes/. Instead of creating a new field as a clone of checkbox or leveraging the built in class hooks to filter the processing I felt it would be better performance to directly modify the core GF logic in hopes they will adopt this in a future release. An example of this is seen like this !Screenshot

The status of these changes will affect the following areas

  • Changes were made against GF Version: 2.7.2
  • I have marked changed areas in these files with a comment Code4OtherChoice it's kludgy but worked for me to update the files as needed during update comparisons.
  • Validation works if the user has not changed the default value when selecting other
  • Validation works if no fields on form are selected
  • "Other" value saves successfully into entry
  • "Other" value exports succesfully as Checkbox "Other" Column
  • Works with "Select All" options
  • Issue in form editor if you select "Other" field does not automatically show until refresh after saving. Was not a deal breaker for me so I did not spend effort resolving.
  • Issue also exists when unchecking "Other" that the input field stays active
  • I did not integrate the markup cleanly as I cloned the functionality from radio button as much as possible, thus some optimization opportunities are available to make this better.
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GF_Field_Checkbox extends GF_Field {
/**
* @var string $type The field type.
*/
public $type = 'checkbox';
/**
* Indicates if this field supports state validation.
*
* @since 2.5.11
*
* @var bool
*/
protected $_supports_state_validation = true;
// # FORM EDITOR & FIELD MARKUP -------------------------------------------------------------------------------------
/**
* Returns the field title.
*
* @since Unknown
* @access public
*
* @return string
*/
public function get_form_editor_field_title() {
return esc_attr__( 'Checkboxes', 'gravityforms' );
}
/**
* Returns the field's form editor description.
*
* @since 2.5
*
* @return string
*/
public function get_form_editor_field_description() {
return esc_attr__( 'Allows users to select one or many checkboxes.', 'gravityforms' );
}
/**
* Returns the field's form editor icon.
*
* This could be an icon url or a gform-icon class.
*
* @since 2.5
*
* @return string
*/
public function get_form_editor_field_icon() {
return 'gform-icon--check-box';
}
/**
* The class names of the settings which should be available on the field in the form editor.
*
* @since Unknown
* @access public
*
* @return array
*/
public function get_form_editor_field_settings() {
return array(
'conditional_logic_field_setting',
'prepopulate_field_setting',
'error_message_setting',
'label_setting',
'label_placement_setting',
'admin_label_setting',
'choices_setting',
'rules_setting',
'visibility_setting',
'description_setting',
'css_class_setting',
'select_all_choices_setting',
'other_choice_setting', // Code4OtherChoice
);
}
// Code4OtherChoice
public function is_state_validation_supported() {
// TODO is there a better way to leverage the true input id.sub_id pattern?
$other_choice_id = $this->enableOtherChoice ? count( $this->choices )+1 : 0;
if ( $this->enableOtherChoice && rgpost( "is_submit_{$this->formId}" ) && rgpost( "input_{$this->id}_" .$other_choice_id ) == 'gf_other_choice' ) {
return false;
}
return parent::is_state_validation_supported();
}
// Code4OtherChoice
public function validate( $value, $form ) {
$other_choice_id = $this->enableOtherChoice ? count( $this->choices )+1 : 0;
$input_id = $this->id . '.' . $other_choice_id;
if ( $this->enableOtherChoice && rgpost( "input_{$this->id}_" . $other_choice_id ) == 'gf_other_choice' ) {
if ( empty( $value[ $input_id ] ) || strtolower( $value[ $input_id ] ) == strtolower( GFCommon::get_other_choice_value( $this ) ) ) {
$this->failed_validation = true;
$this->validation_message = empty( $this->errorMessage ) ? esc_html__( 'Please provide a value for "Other" option.', 'gravityforms' ) : $this->errorMessage;
}
}
}
// Code4OtherChoice
public function is_enableOtherChoice(){
return $this->enableOtherChoice;
}
/**
* Indicate if this field type can be used when configuring conditional logic rules.
*
* @since Unknown
* @access public
*
* @return bool
*/
public function is_conditional_logic_supported() {
return true;
}
/**
* Returns the HTML tag for the field container.
*
* @since 2.5
*
* @param array $form The current Form object.
*
* @return string
*/
public function get_field_container_tag( $form ) {
if ( GFCommon::is_legacy_markup_enabled( $form ) ) {
return parent::get_field_container_tag( $form );
}
return 'fieldset';
}
/**
* Returns the field inner markup.
*
* @since Unknown
* @since 2.5 Implement Select All directly.
*
* @param array $form The Form Object currently being processed.
* @param string|array $value The field value. From default/dynamic population, $_POST, or a resumed incomplete submission.
* @param null|array $entry Null or the Entry Object currently being edited.
*
* @return string
*/
public function get_field_input( $form, $value = '', $entry = null ) {
$form_id = absint( $form['id'] );
if ( GFCommon::is_legacy_markup_enabled( $form_id ) ) {
return $this->get_legacy_field_input( $form, $value, $entry );
}
$is_entry_detail = $this->is_entry_detail();
$is_form_editor = $this->is_form_editor();
$id = $this->id;
$field_id = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id";
$disabled_text = $is_form_editor ? 'disabled="disabled"' : '';
// Get checkbox choices markup.
$choices_markup = $this->get_checkbox_choices( $value, $disabled_text, $form_id );
if ( ! $this->enableSelectAll ) {
return sprintf(
"<div class='ginput_container ginput_container_checkbox'><div class='gfield_checkbox' id='%s'>%s</div></div>",
esc_attr( $field_id ),
$choices_markup
);
}
/**
* Modify the "Select All" checkbox label.
*
* @since 2.3
*
* @param string $select_label The "Select All" label.
* @param object $field The field currently being processed.
*/
$select_label = gf_apply_filters( array( 'gform_checkbox_select_all_label', $this->formId, $this->id ), esc_html__( 'Select All', 'gravityforms' ), $this );
$select_label = esc_html( $select_label );
/**
* Modify the "Deselect All" checkbox label.
*
* @since 2.3
*
* @param string $deselect_label The "Deselect All" label.
* @param object $field The field currently being processed.
*/
$deselect_label = gf_apply_filters( array( 'gform_checkbox_deselect_all_label', $this->formId, $this->id ), esc_html__( 'Deselect All', 'gravityforms' ), $this );
$deselect_label = esc_html( $deselect_label );
// Determine if all checkboxes are selected.
$all_selected = $this->get_selected_choices_count( $value, $entry ) === count( $this->choices );
// Prepare button markup.
$button_markup = sprintf(
'<button type="button" id="button_%1$d_select_all" onclick="gformToggleCheckboxes( this )" data-checked="%4$d" data-label-select="%2$s" data-label-deselect="%3$s"%6$s>%5$s</button>',
$this->id,
$select_label,
$deselect_label,
$all_selected ? 1 : 0,
$all_selected ? $deselect_label : $select_label,
$is_form_editor ? ' disabled="disabled"' : ''
);
return sprintf(
"<div class='ginput_container ginput_container_checkbox'><div class='gfield_checkbox' id='%s'>%s%s</div></div>",
esc_attr( $field_id ),
$choices_markup,
$button_markup
);
}
/**
* Returns the field inner markup.
*
* @since 2.5
*
* @param array $form The Form Object currently being processed.
* @param string|array $value The field value. From default/dynamic population, $_POST, or a resumed incomplete submission.
* @param null|array $entry Null or the Entry Object currently being edited.
*
* @return string
*/
public function get_legacy_field_input( $form, $value = '', $entry = null ) {
$form_id = absint( $form['id'] );
$is_entry_detail = $this->is_entry_detail();
$is_form_editor = $this->is_form_editor();
$id = $this->id;
$field_id = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id";
$disabled_text = $is_form_editor ? 'disabled="disabled"' : '';
$tag = GFCommon::is_legacy_markup_enabled( $form_id ) ? 'ul' : 'div';
return sprintf(
"<div class='ginput_container ginput_container_checkbox'><{$tag} class='gfield_checkbox' id='%s'>%s</{$tag}></div>",
esc_attr( $field_id ),
$this->get_checkbox_choices( $value, $disabled_text, $form_id )
);
}
/**
* Returns the number of selected choices.
* Used during field rendering to set the initial state of the (De)Select All toggle.
*
* @since 2.5
*
* @param string|array $value The field value. From default/dynamic population, $_POST, or a resumed incomplete submission.
* @param null|array $entry Null or the Entry Object currently being edited.
*
* @return int
*/
private function get_selected_choices_count( $value = '', $entry = null ) {
// Initialize selected, choice number counts.
$checkboxes_selected = 0;
$choice_number = 1;
foreach ( $this->choices as $choice ) {
// Hack to skip numbers ending in 0, so that 5.1 doesn't conflict with 5.10.
if ( $choice_number % 10 == 0 ) {
$choice_number ++;
}
// Prepare input ID.
$input_id = $this->id . '.' . $choice_number;
if ( ( $this->is_form_editor() || ( ! isset( $_GET['gf_token'] ) && empty( $_POST ) ) ) && rgar( $choice, 'isSelected' ) ) {
$checkboxes_selected++;
} else if ( is_array( $value ) && GFFormsModel::choice_value_match( $this, $choice, rgget( $input_id, $value ) ) ) {
$checkboxes_selected++;
} else if ( ! is_array( $value ) && GFFormsModel::choice_value_match( $this, $choice, $value ) ) {
$checkboxes_selected++;
}
$choice_number++;
}
return $checkboxes_selected;
}
// # SUBMISSION -----------------------------------------------------------------------------------------------------
/**
* Retrieve the field value on submission.
*
* @since Unknown
* @access public
*
* @param array $field_values The dynamic population parameter names with their corresponding values to be populated.
* @param bool|true $get_from_post_global_var Whether to get the value from the $_POST array as opposed to $field_values.
*
* @uses GFFormsModel::choice_value_match()
* @uses GFFormsModel::get_parameter_value()
*
* @return array|string
*/
public function get_value_submission( $field_values, $get_from_post_global_var = true ) {
// Get parameter values for field.
$parameter_values = GFFormsModel::get_parameter_value( $this->inputName, $field_values, $this );
// If parameter values exist but are not an array, convert to array.
if ( ! empty( $parameter_values ) && ! is_array( $parameter_values ) ) {
$parameter_values = explode( ',', $parameter_values );
}
// If no inputs are defined, return an empty string.
if ( ! is_array( $this->inputs ) ) {
return '';
}
// Set initial choice index.
$choice_index = 0;
// Initialize submission value array.
$value = array();
// Code4OtherChoice
if( $this->enableOtherChoice ){
$other_choice_id = count( $this->inputs )+1;
$this->inputs[] = [
"id" => $this->id . '.' . $other_choice_id,
"label" => $this->get_input_value_submission( 'input_' . $this->id . '_other', $this->inputName, $field_values, $get_from_post_global_var ),
"name" => '',
];
}
// Loop through field inputs.
foreach ( $this->inputs as $input ) {
if ( ! empty( $_POST[ 'is_submit_' . $this->formId ] ) && $get_from_post_global_var ) {
$input_value = rgpost( 'input_' . str_replace( '.', '_', strval( $input['id'] ) ) );
$value[ strval( $input['id'] ) ] = $input_value;
} else {
if ( is_array( $parameter_values ) ) {
foreach ( $parameter_values as $item ) {
$item = trim( $item );
if ( GFFormsModel::choice_value_match( $this, $this->choices[ $choice_index ], $item ) ) {
$value[ $input['id'] . '' ] = $item;
break;
}
}
}
}
// Increase choice index.
$choice_index ++;
}
// Code4OtherChoice
// reset the "other" choice value to be from the input field helps on validation but still need to reset in get_value_save_entry prior to save
if( $this->enableOtherChoice ){
if( rgpost( "input_{$this->id}_" . $other_choice_id ) == 'gf_other_choice' ){
// not sure what to do if this is not an array
if( is_array($value) ){
$value[ $this->id. '.' . $other_choice_id ] = $this->get_input_value_submission( 'input_' . $this->id . '_other', $this->inputName, $field_values, $get_from_post_global_var );
}
}
}
return $value;
}
// # ENTRY RELATED --------------------------------------------------------------------------------------------------
/**
* Format the entry value for display on the entries list page.
*
* Return a value that's safe to display on the page.
*
* @since Unknown
* @access public
*
* @param string|array $value The field value.
* @param array $entry The Entry Object currently being processed.
* @param string $field_id The field or input ID currently being processed.
* @param array $columns The properties for the columns being displayed on the entry list page.
* @param array $form The Form Object currently being processed.
*
* @uses GFCommon::implode_non_blank()
* @uses GFCommon::prepare_post_category_value()
* @uses GFCommon::selection_display()
* @uses GF_Field_Checkbox::is_checkbox_checked()
*
* @return string
*/
public function get_value_entry_list( $value, $entry, $field_id, $columns, $form ) {
// If this is the main checkbox field (not an input), display a comma separated list of all inputs.
if ( absint( $field_id ) == $field_id ) {
$lead_field_keys = array_keys( $entry );
$items = array();
foreach ( $lead_field_keys as $input_id ) {
if ( is_numeric( $input_id ) && absint( $input_id ) == $field_id ) {
$items[] = GFCommon::selection_display( rgar( $entry, $input_id ), null, $entry['currency'], false );
}
}
$value = GFCommon::implode_non_blank( ', ', $items );
// Special case for post category checkbox fields.
if ( $this->type == 'post_category' ) {
$value = GFCommon::prepare_post_category_value( $value, $this, 'entry_list' );
}
} else {
$value = '';
if ( ! rgblank( $this->is_checkbox_checked( $field_id, $columns[ $field_id ]['label'], $entry ) ) ) {
$value = "<i class='fa fa-check gf_valid'></i>";
}
}
return $value;
}
/**
* Format the entry value for display on the entry detail page and for the {all_fields} merge tag.
*
* Return a value that's safe to display for the context of the given $format.
*
* @since Unknown
* @access public
*
* @param string|array $value The field value.
* @param string $currency The entry currency code.
* @param bool|false $use_text When processing choice based fields should the choice text be returned instead of the value.
* @param string $format The format requested for the location the merge is being used. Possible values: html, text or url.
* @param string $media The location where the value will be displayed. Possible values: screen or email.
*
* @uses GFCommon::selection_display()
*
* @return string
*/
public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {
if ( is_array( $value ) ) {
$items = '';
foreach ( $value as $key => $item ) {
if ( ! rgblank( $item ) ) {
switch ( $format ) {
case 'text' :
$items .= GFCommon::selection_display( $item, $this, $currency, $use_text ) . ', ';
break;
default:
$items .= '<li>' . wp_kses_post( GFCommon::selection_display( $item, $this, $currency, $use_text ) ) . '</li>';
break;
}
}
}
if ( empty( $items ) ) {
return '';
} elseif ( $format == 'text' ) {
return substr( $items, 0, strlen( $items ) - 2 ); // Removing last comma.
} else {
return "<ul class='bulleted'>$items</ul>";
}
} else {
return $value;
}
}
/**
* Gets merge tag values.
*
* @since Unknown
* @access public
*
* @uses GFCommon::to_money()
* @uses GFCommon::format_post_category()
* @uses GFFormsModel::is_field_hidden()
* @uses GFFormsModel::get_choice_text()
* @uses GFCommon::format_variable_value()
* @uses GFCommon::implode_non_blank()
*
* @param array|string $value The value of the input.
* @param string $input_id The input ID to use.
* @param array $entry The Entry Object.
* @param array $form The Form Object
* @param string $modifier The modifier passed.
* @param array|string $raw_value The raw value of the input.
* @param bool $url_encode If the result should be URL encoded.
* @param bool $esc_html If the HTML should be escaped.
* @param string $format The format that the value should be.
* @param bool $nl2br If the nl2br function should be used.
*
* @uses GFCommon::format_post_category()
* @uses GFCommon::format_variable_value()
* @uses GFCommon::implode_non_blank()
* @uses GFCommon::to_money()
* @uses GFFormsModel::is_field_hidden()
*
* @return string The processed merge tag.
*/
public function get_value_merge_tag( $value, $input_id, $entry, $form, $modifier, $raw_value, $url_encode, $esc_html, $format, $nl2br ) {
// Check for passed modifiers.
$use_value = $modifier == 'value';
$use_price = in_array( $modifier, array( 'price', 'currency' ) );
$format_currency = $modifier == 'currency';
if ( is_array( $raw_value ) && (string) intval( $input_id ) != $input_id ) {
$items = array( $input_id => $value ); // Float input IDs. (i.e. 4.1 ). Used when targeting specific checkbox items.
} elseif ( is_array( $raw_value ) ) {
$items = $raw_value;
} else {
$items = array( $input_id => $raw_value );
}
$ary = array();
// Get the items available within the merge tags.
foreach ( $items as $input_id => $item ) {
// If the 'value' modifier was passed.
if ( $use_value ) {
list( $val, $price ) = rgexplode( '|', $item, 2 );
// If the 'price' or 'currency' modifiers were passed.
} elseif ( $use_price ) {
list( $name, $val ) = rgexplode( '|', $item, 2 );
if ( $format_currency ) {
$val = GFCommon::to_money( $val, rgar( $entry, 'currency' ) );
}
// If this is a post category checkbox.
} else if ( $this->type == 'post_category' ) {
$use_id = strtolower( $modifier ) == 'id';
$item_value = GFCommon::format_post_category( $item, $use_id );
$val = GFFormsModel::is_field_hidden( $form, $this, array(), $entry ) ? '' : $item_value;
// If no modifiers were passed.
} else {
$val = GFFormsModel::is_field_hidden( $form, $this, array(), $entry ) ? '' : RGFormsModel::get_choice_text( $this, $raw_value, $input_id );
}
$ary[] = GFCommon::format_variable_value( $val, $url_encode, $esc_html, $format );
}
return GFCommon::implode_non_blank( ', ', $ary );
}
/**
* Sanitize and format the value before it is saved to the Entry Object.
*
* @since Unknown
* @access public
*
* @param string $value The value to be saved.
* @param array $form The Form Object currently being processed.
* @param string $input_name The input name used when accessing the $_POST.
* @param int $lead_id The ID of the Entry currently being processed.
* @param array $lead The Entry Object currently being processed.
*
* @uses GF_Field_Checkbox::sanitize_entry_value()
*
* @return array|string The safe value.
*/
public function get_value_save_entry( $value, $form, $input_name, $lead_id, $lead ) {
// Code4OtherChoice
// fix the right field value from checkbox other
if ( $this->enableOtherChoice && $value == 'gf_other_choice' ) {
$value = rgpost( "input_{$this->id}_other" );
}
if ( rgblank( $value ) ) {
return '';
} elseif ( is_array( $value ) ) {
foreach ( $value as &$v ) {
if ( is_array( $v ) ) {
$v = '';
}
$v = $this->sanitize_entry_value( $v, $form['id'] );
}
return implode( ',', $value );
} else {
return $this->sanitize_entry_value( $value, $form['id'] );
}
}
/**
* Format the entry value before it is used in entry exports and by framework add-ons using GFAddOn::get_field_value().
*
* @since Unknown
* @access public
*
* @param array $entry The entry currently being processed.
* @param string $input_id The field or input ID.
* @param bool|false $use_text When processing choice based fields should the choice text be returned instead of the value.
* @param bool|false $is_csv Is the value going to be used in the .csv entries export?
*
* @uses GFCommon::get_label()
* @uses GFCommon::selection_display()
* @uses GF_Field_Checkbox::is_checkbox_checked()
*
* @return string
*/
public function get_value_export( $entry, $input_id = '', $use_text = false, $is_csv = false ) {
if ( empty( $input_id ) || absint( $input_id ) == $input_id ) {
$selected = array();
// TODO should I worry about this?
// Code4OtherChoice
// if( $this->enableOtherChoice ){
// $other_choice_id = count( $this->inputs )+1;
// $this->inputs[] = [
// "id" => $this->id . '.' . $other_choice_id,
// "label" => "Other",
// "name" => '',
// ];
// }
foreach ( $this->inputs as $input ) {
$index = (string) $input['id'];
if ( ! rgempty( $index, $entry ) ) {
$selected[] = GFCommon::selection_display( rgar( $entry, $index ), $this, rgar( $entry, 'currency' ), $use_text );
}
}
return implode( ', ', $selected );
} else if ( $is_csv ) {
$value = $this->is_checkbox_checked( $input_id, GFCommon::get_label( $this, $input_id ), $entry );
return empty( $value ) ? '' : $value;
} else {
return GFCommon::selection_display( rgar( $entry, $input_id ), $this, rgar( $entry, 'currency' ), $use_text );
}
}
// # INPUT ATTRIBUTE HELPERS ----------------------------------------------------------------------------------------
/**
* Get checkbox choice inputs for field.
*
* @since Unknown
* @access public
*
* @param string|array $value The field value. From default/dynamic population, $_POST, or a resumed incomplete submission.
* @param string $disabled_text The HTML disabled attribute.
* @param int $form_id The current form ID.
*
* @uses GFCommon::to_number()
* @uses GF_Field::get_conditional_logic_event()
* @uses GF_Field::get_tabindex()
* @uses GF_Field::is_entry_detail()
* @uses GF_Field::is_form_editor()
* @uses GFFormsModel::choice_value_match()
*
* @return string
*/
public function get_checkbox_choices( $value, $disabled_text, $form_id = 0 ) {
$choices = '';
$is_entry_detail = $this->is_entry_detail();
$is_form_editor = $this->is_form_editor();
if ( is_array( $this->choices ) ) {
$choice_number = 1;
$count = 1;
// Code4OtherChoice
$needs_other_choice = $this->enableOtherChoice;
$tag = GFCommon::is_legacy_markup_enabled( $form_id ) ? 'li' : 'div';
// Add Select All choice.
if ( $this->enableSelectAll && GFCommon::is_legacy_markup_enabled( $form_id ) ) {
/**
* Modify the "Select All" checkbox label.
*
* @since 2.3
*
* @param string $select_label The "Select All" label.
* @param object $field The field currently being processed.
*/
$select_label = gf_apply_filters( array( 'gform_checkbox_select_all_label', $this->formId, $this->id ), esc_html__( 'Select All', 'gravityforms' ), $this );
$select_label = esc_html( $select_label );
/**
* Modify the "Deselect All" checkbox label.
*
* @since 2.3
*
* @param string $deselect_label The "Deselect All" label.
* @param object $field The field currently being processed.
*/
$deselect_label = gf_apply_filters( array( 'gform_checkbox_deselect_all_label', $this->formId, $this->id ), esc_html__( 'Deselect All', 'gravityforms' ), $this );
$deselect_label = esc_html( $deselect_label );
// Get tabindex.
$tabindex = $this->get_tabindex();
// Prepare choice ID.
$id = 'choice_' . $this->id . '_select_all';
// Determine if all checkboxes are selected.
if ( $this->get_selected_choices_count( $value ) === count( $this->choices ) ) {
$checked = ' checked="checked"';
$toggle_label = $deselect_label;
} else {
$checked = '';
$toggle_label = $select_label;
}
// Prepare choice markup.
$choice_markup = "<{$tag} class='gchoice gchoice_select_all'>
<input class='gfield-choice-input' type='checkbox' id='{$id}' {$tabindex} {$disabled_text} onclick='gformToggleCheckboxes( this )' onkeypress='gformToggleCheckboxes( this )'{$checked} />
<label for='{$id}' id='label_" . $this->id . "_select_all' data-label-select='{$select_label}' data-label-deselect='{$deselect_label}'>{$toggle_label}</label>
</{$tag}>";
/**
* Override the default choice markup used when rendering radio button, checkbox and drop down type fields.
*
* @since 1.9.6
*
* @param string $choice_markup The string containing the choice markup to be filtered.
* @param array $choice An associative array containing the choice properties.
* @param object $field The field currently being processed.
* @param string $value The value to be selected if the field is being populated.
*/
$choices .= gf_apply_filters( array( 'gform_field_choice_markup_pre_render', $this->formId, $this->id ), $choice_markup, array(), $this, $value );
}
// Loop through field choices.
foreach ( $this->choices as $choice ) {
// Code4OtherChoice
if ( rgar( $choice, 'isOtherChoice' ) ) {
if ( ! $needs_other_choice ) {
continue;
}
$needs_other_choice = false;
}
// Get aria-describedby if this is the first choice
$aria_describedby = $choice_number === 1 ? $this->get_aria_describedby() : '';
// Hack to skip numbers ending in 0, so that 5.1 doesn't conflict with 5.10.
if ( $choice_number % 10 == 0 ) {
$choice_number ++;
}
// Prepare input ID.
$input_id = $this->id . '.' . $choice_number;
if ( $is_entry_detail || $is_form_editor || $form_id == 0 ) {
$id = $this->id . '_' . $choice_number ++;
} else {
$id = $form_id . '_' . $this->id . '_' . $choice_number ++;
}
if ( ( $is_form_editor || ( ! isset( $_GET['gf_token'] ) && empty( $_POST ) ) ) && rgar( $choice, 'isSelected' ) ) {
$checked = "checked='checked'";
} elseif ( is_array( $value ) && GFFormsModel::choice_value_match( $this, $choice, rgget( $input_id, $value ) ) ) {
$checked = "checked='checked'";
} elseif ( ! is_array( $value ) && GFFormsModel::choice_value_match( $this, $choice, $value ) && ! empty( $_POST[ 'is_submit_' . $form_id ] ) ) {
$checked = "checked='checked'";
} else {
$checked = '';
}
$tabindex = $this->get_tabindex();
$choice_value = $choice['value'];
if ( $this->enablePrice ) {
$price = rgempty( 'price', $choice ) ? 0 : GFCommon::to_number( rgar( $choice, 'price' ) );
$choice_value .= '|' . $price;
}
$choice_value = esc_attr( $choice_value );
$choice_markup = "<{$tag} class='gchoice gchoice_{$id}'>
<input class='gfield-choice-input' name='input_{$input_id}' type='checkbox' value='{$choice_value}' {$checked} id='choice_{$id}' {$tabindex} {$disabled_text} {$aria_describedby}/>
<label for='choice_{$id}' id='label_{$id}'>{$choice['text']}</label>
</{$tag}>";
/**
* Override the default choice markup used when rendering radio button, checkbox and drop down type fields.
*
* @since 1.9.6
*
* @param string $choice_markup The string containing the choice markup to be filtered.
* @param array $choice An associative array containing the choice properties.
* @param object $field The field currently being processed.
* @param string $value The value to be selected if the field is being populated.
*/
$choices .= gf_apply_filters( array( 'gform_field_choice_markup_pre_render', $this->formId, $this->id ), $choice_markup, $choice, $this, $value );
$is_admin = $is_entry_detail || $is_form_editor;
if ( $is_admin && rgget('view') != 'entry' && $count >= 5 ) {
break;
}
$count ++;
}
// Code4OtherChoice
if ( $needs_other_choice ) {
$other_choice = array(
'text' => GFCommon::get_other_choice_value( $this ),
'value' => 'gf_other_choice',
'isSelected' => false,
'isOtherChoice' => true,
);
$field_choices[] = $other_choice;
if ( ! $is_form_editor || ! $editor_limited ) {
$choices .= $this->get_choice_html( $other_choice, $choice_number, $value, $disabled_text, $is_admin );
$count ++;
}
}
$total = sizeof( $this->choices );
if ( $count < $total ) {
$choices .= "<{$tag} class='gchoice_total'>" . sprintf( esc_html__( '%d of %d items shown. Edit field to view all', 'gravityforms' ), $count, $total ) . "</{$tag}>";
}
}
/**
* Modify the checkbox items before they are added to the checkbox list.
*
* @since Unknown
*
* @param string $choices The string containing the choices to be filtered.
* @param object $field Ahe field currently being processed.
*/
return gf_apply_filters( array( 'gform_field_choices', $this->formId, $this->id ), $choices, $this );
}
/**
* Returns the choice HTML.
*
* @since 2.4.17
*
* @param array $choice The choice properties.
* @param int &$choice_id The choice number.
* @param string $value The current field value.
* @param string $disabled_text The disabled attribute or an empty string.
* @param bool $is_admin Indicates if this is the form editor or entry detail page.
*
* @return string
*/
// Code4OtherChoice
// TODO this probably should be improved to be reusable beyond "copying" from radio field
public function get_choice_html( $choice, &$choice_number, $value, $disabled_text, $is_admin ) {
$form_id = absint( $this->formId );
$tag = GFCommon::is_legacy_markup_enabled( $form_id ) ? 'li' : 'div';
$input_id = $this->id . '.' . $choice_number;
// if ( GFCommon::is_legacy_markup_enabled( $form_id ) ) {
// return $this->get_legacy_choice_html( $choice, $choice_id, $value, $disabled_text, $is_admin );
// }
if ( $is_admin || $form_id == 0 ) {
$id = $this->id . '_' . $choice_number ++;
} else {
$id = $form_id . '_' . $this->id . '_' . $choice_number ++;
}
$field_value = ! empty( $choice['value'] ) || $this->enableChoiceValue ? $choice['value'] : $choice['text'];
if ( $this->enablePrice ) {
$price = rgempty( 'price', $choice ) ? 0 : GFCommon::to_number( rgar( $choice, 'price' ) );
$field_value .= '|' . $price;
}
if ( rgblank( $value ) && rgget( 'view' ) != 'entry' ) {
$checked = rgar( $choice, 'isSelected' ) ? "checked='checked'" : '';
} else {
$checked = GFFormsModel::choice_value_match( $this, $choice, $value[ $input_id ] ) ? "checked='checked'" : '';
}
$aria_describedby = $this->add_aria_description( $checked, $choice_number ) ? $this->get_aria_describedby() : '';
$tabindex = $this->get_tabindex();
$label = sprintf( "<label for='choice_%s' id='label_%s'>%s</label>", $id, $id, $choice['text'] );
// Handle 'other' choice.
if ( $this->enableOtherChoice && rgar( $choice, 'isOtherChoice' ) ) {
$input_disabled_text = $disabled_text;
if ( $value == 'gf_other_choice' && rgpost( "input_{$this->id}_other" ) ) {
$other_value = rgpost( "input_{$this->id}_other" );
// TODO fix this checker maybe? feels less elegant if this method were to be reused
} elseif ( ! empty( $value ) && ! GFFormsModel::choices_value_match( $this, $this->choices, $value[$input_id] ) ) {
$other_value = rgpost( "input_{$this->id}_other" ); //$value["input_{$this->id}_other"];
$value = 'gf_other_choice';
$checked = "checked='checked'";
} else {
if ( ! $input_disabled_text ) {
$input_disabled_text = "disabled='disabled'";
}
$other_value = empty( $choice['text'] ) ? GFCommon::get_other_choice_value( $this ) : $choice['text'];
}
$label .= "<br /><input id='input_{$this->formId}_{$this->id}_other' name='input_{$this->id}_other' type='text' value='" . esc_attr( $other_value ) . "' aria-label='" . esc_attr__( 'Other Choice, please specify', 'gravityforms' ) . "' $tabindex $input_disabled_text />";
}
$choice_markup = sprintf( "
<{$tag} class='gchoice gchoice_{$id}'>
<input class='gfield-choice-input' name='input_%s' type='checkbox' value='%s' %s id='choice_%s' onchange='gformToggleRadioOther( this )' %s $tabindex %s />
%s
</{$tag}>",
$input_id, esc_attr( $field_value ), $checked, $id, $aria_describedby, $disabled_text, $label
);
return gf_apply_filters( array( 'gform_field_choice_markup_pre_render', $this->formId, $this->id ), $choice_markup, $choice, $this, $value );
}
/**
* Determine if we should add the aria description to a radio input.
*
* @since 2.5
*
* @param string $checked The checked attribute or a blank string.
* @param int $choice_id The choice number.
*
* @return string
*/
// Code4OtherChoice
public function add_aria_description( $checked, $choice_number ) {
// Determine if any choices are pre-selected.
foreach ( $this['choices'] as $choice ) {
$is_any_selected = rgar( $choice, 'isSelected' );
if ( $is_any_selected ) {
break;
}
}
// Return true if any choices are pre-selected, or if no choices are pre-selected and this is the first choice.
return ( ! $is_any_selected && $choice_number === 1 ) || $checked;
}
/**
* Determine if a specific checkbox is checked.
*
* @since Unknown
* @access public
*
* @param int $field_id Field ID.
* @param string $field_label Field label.
* @param array $entry Entry object.
*
* @return bool
*/
public function is_checkbox_checked( $field_id, $field_label, $entry ) {
$allowed_tags = wp_kses_allowed_html( 'post' );
$entry_field_keys = array_keys( $entry );
$other_choice_id = strtolower( $this->id . '.' . count( $this->inputs )+1 );
// Code4OtherChoice
// exit early if we can confirm a match
if( $this->enableOtherChoice && !empty($entry[ $field_id ]) && strtolower($field_id) == $other_choice_id){
return wp_kses( $entry[ $field_id ], $allowed_tags ); //$entry[ $input_id ];
}
// Looping through lead detail values trying to find an item identical to the column label. Mark with a tick if found.
foreach ( $entry_field_keys as $input_id ) {
// Mark as a tick if input label (from form meta) is equal to submitted value (from lead)
if ( is_numeric( $input_id ) && absint( $input_id ) == absint( $field_id ) ) {
$sanitized_value = wp_kses( $entry[ $input_id ], $allowed_tags );
$sanitized_label = wp_kses( $field_label, $allowed_tags );
if ( $sanitized_value == $sanitized_label ) {
return $entry[ $input_id ];
} else {
if ( $this->enableChoiceValue || $this->enablePrice ) {
foreach ( $this->choices as $choice ) {
if ( $choice['value'] == $entry[ $field_id ] ) {
return $choice['value'];
} else if ( $this->enablePrice ) {
$ary = explode( '|', $entry[ $field_id ] );
$val = count( $ary ) > 0 ? $ary[0] : '';
$price = count( $ary ) > 1 ? $ary[1] : '';
if ( $val == $choice['value'] ) {
return $choice['value'];
}
}
}
}
}
}
}
return false;
}
// # OTHER HELPERS --------------------------------------------------------------------------------------------------
/**
* Returns the input ID to be assigned to the field label for attribute.
*
* @since Unknown
* @access public
*
* @param array $form The Form Object currently being processed.
*
* @return string
*/
public function get_first_input_id( $form ) {
return '';
}
/**
* Retrieve the field default value.
*
* @since Unknown
* @access public
*
* @uses GFCommon::replace_variables_prepopulate()
* @uses GF_Field::is_form_editor()
*
* @return array|string
*/
public function get_value_default() {
return $this->is_form_editor() ? $this->defaultValue : GFCommon::replace_variables_prepopulate( $this->defaultValue );
}
/**
* Override and return the enabled other choice is set
*
* @return array|null
*/
//Code4OtherChoice
public function get_entry_inputs() {
$inputs = $this->inputs;
// Code4OtherChoice
if( $this->enableOtherChoice ){
$other_choice_id = count( $this->inputs )+1;
$inputs[] = [
"id" => $this->id . '.' . $other_choice_id,
"label" => 'Other',
"name" => '',
];
}
return $inputs;
}
// # SANITIZATION ---------------------------------------------------------------------------------------------------
/**
* If the field should allow html tags to be saved with the entry value. Default is false.
*
* @since Unknown
* @access public
*
* @return bool
*/
public function allow_html() {
return true;
}
/**
* Forces settings into expected values while saving the form object.
*
* No escaping should be done at this stage to prevent double escaping on output.
*
* Currently called only for forms created after version 1.9.6.10.
*
* @since Unknown
* @access public
*
*/
public function sanitize_settings() {
parent::sanitize_settings();
if ( 'option' === $this->type ) {
$this->productField = absint( $this->productField );
}
if ( 'post_category' === $this->type ) {
$this->displayAllCategories = (bool) $this->displayAllCategories;
}
}
/**
* Strip scripts and some HTML tags.
*
* @since Unknown
* @access public
*
* @param string $value The field value to be processed.
* @param int $form_id The ID of the form currently being processed.
*
* @uses GF_Field::get_allowable_tags()
*
* @return string
*/
public function sanitize_entry_value( $value, $form_id ) {
// If the value is an array, return an empty string.
if ( is_array( $value ) ) {
return '';
}
// Get allowable tags for field value.
$allowable_tags = $this->get_allowable_tags( $form_id );
// If allowable tags are defined, strip unallowed tags.
if ( $allowable_tags !== true ) {
$value = strip_tags( $value, $allowable_tags );
}
// Sanitize value.
$allowed_protocols = wp_allowed_protocols();
$value = wp_kses_no_null( $value, array( 'slash_zero' => 'keep' ) );
$value = wp_kses_hook( $value, 'post', $allowed_protocols );
$value = wp_kses_split( $value, 'post', $allowed_protocols );
return $value;
}
// # FIELD FILTER UI HELPERS ---------------------------------------------------------------------------------------
/**
* Returns the filter operators for the current field.
*
* @since 2.4
*
* @return array
*/
public function get_filter_operators() {
return array( 'is' );
}
}
GF_Fields::register( new GF_Field_Checkbox() );
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GFFormDisplay {
public static $submission = array();
public static $init_scripts = array();
public static $hooks_js_printed = false;
public static $sidebar_has_widget = false;
public static $submission_initiated_by = '';
const ON_PAGE_RENDER = 1;
const ON_CONDITIONAL_LOGIC = 2;
const SUBMISSION_INITIATED_BY_WEBFORM = 1;
const SUBMISSION_INITIATED_BY_API = 2;
const SUBMISSION_INITIATED_BY_API_VALIDATION = 3;
/**
* Starting point for the form submission process. Handles the following tasks: Form validation, save for later logic, entry creation, notification and confirmation.
*
* @since unknown
* @since 2.6.4 Added the $initiated_by param.
*
* @param int $form_id The form ID being submitted.
* @param int $initiated_by What process initiated the form submission. Possible options are self::SUBMISSION_INITIATED_BY_WEBFORM = 1 or self::SUBMISSION_INITIATED_BY_API = 2.
*/
public static function process_form( $form_id, $initiated_by = self::SUBMISSION_INITIATED_BY_API ) {
self::$submission_initiated_by = $initiated_by;
GFCommon::log_debug( "GFFormDisplay::process_form(): Starting to process form (#{$form_id}) submission." );
$form = GFAPI::get_form( $form_id );
/**
* Filter the form before GF begins to process the submission.
*
* @param array $form The Form Object
*/
$filtered_form = gf_apply_filters( array( 'gform_pre_process', $form['id'] ), $form );
if ( $filtered_form !== null ) {
$form = $filtered_form;
}
//reading form metadata
$form = self::maybe_add_review_page( $form );
if ( ! $form['is_active'] || $form['is_trash'] ) {
return;
}
/**
* Filter whether the user must be logged-in to submit the form.
*
* @since 2.4
*
* @param bool $require_login Whether or not user required to be logged-in to submit the form.
* @param array $form The current form object.
*/
$require_login = gf_apply_filters( array( 'gform_require_login', $form['id'] ), rgar( $form, 'requireLogin' ), $form );
if ( $require_login ) {
if ( ! is_user_logged_in() ) {
return;
}
// Bypass nonce check for requests coming from the REST API
$is_rest_request = defined( 'REST_REQUEST' ) && REST_REQUEST;
if ( ! $is_rest_request ) {
check_admin_referer( 'gform_submit_' . $form_id, '_gform_submit_nonce_' . $form_id );
}
}
$lead = array();
$field_values = RGForms::post( 'gform_field_values' );
$confirmation_message = '';
$source_page_number = self::get_source_page( $form_id );
$page_number = $source_page_number;
$target_page = self::get_target_page( $form, $page_number, $field_values );
GFCommon::log_debug( "GFFormDisplay::process_form(): Source page number: {$source_page_number}. Target page number: {$target_page}." );
// Set files that have been uploaded to temp folder
$files = GFFormsModel::set_uploaded_files( $form_id );
$saving_for_later = rgpost( 'gform_save' ) ? true : false;
$is_valid = true;
$failed_validation_page = $page_number;
//don't validate when going to previous page or saving for later
if ( ! $saving_for_later && ( empty( $target_page ) || $target_page >= $page_number ) ) {
$is_valid = self::validate( $form, $field_values, $page_number, $failed_validation_page );
}
$log_is_valid = $is_valid ? 'Yes' : 'No';
GFCommon::log_debug( "GFFormDisplay::process_form(): After validation. Is submission valid? {$log_is_valid}." );
// Upload files to temp folder when going to the next page or when submitting the form and it failed validation
if ( $target_page > $page_number || $target_page == 0 ) {
if ( ! empty( $_FILES ) && ! $saving_for_later ) {
// When saving, ignore files with single file upload fields as they have not been validated.
GFCommon::log_debug( 'GFFormDisplay::process_form(): Uploading files...' );
// Uploading files to temporary folder.
$files = self::upload_files( $form, $files );
RGFormsModel::$uploaded_files[ $form_id ] = $files;
}
}
// Load target page if it did not fail validation or if going to the previous page
if ( ! $saving_for_later && $is_valid ) {
$page_number = $target_page;
} else {
$page_number = $failed_validation_page;
}
$confirmation = '';
if ( ( $is_valid && $page_number == 0 ) || $saving_for_later ) {
//Make sure submit button isn't hidden by conditional logic
if ( GFFormsModel::is_submit_button_hidden( $form ) && ! $saving_for_later ) {
// Ignore submission.
return;
}
$ajax = isset( $_POST['gform_ajax'] );
//adds honeypot field if configured
if ( rgar( $form, 'enableHoneypot' ) ) {
$form['fields'][] = self::get_honeypot_field( $form );
}
$failed_honeypot = rgar( $form, 'enableHoneypot' ) && ! self::validate_honeypot( $form );
if ( $failed_honeypot ) {
GFCommon::log_debug( 'GFFormDisplay::process_form(): Failed Honeypot validation. Displaying confirmation and aborting.' );
//display confirmation but doesn't process the form when honeypot fails
$confirmation = self::handle_confirmation( $form, $lead, $ajax );
$is_valid = false;
} elseif ( ! $saving_for_later ) {
GFCommon::log_debug( 'GFFormDisplay::process_form(): Submission is valid. Moving forward.' );
$form = self::update_confirmation( $form );
//pre submission action
/**
* Fires before form submission is handled
*
* Typically used to modify values before the submission is processed.
*
* @param array $form The Form object
*/
gf_do_action( array( 'gform_pre_submission', $form['id'] ), $form );
//pre submission filter
$form = gf_apply_filters( array( 'gform_pre_submission_filter', $form_id ), $form );
//handle submission
$confirmation = self::handle_submission( $form, $lead, $ajax );
//after submission hook
if ( has_filter( 'gform_after_submission' ) || has_filter( "gform_after_submission_{$form['id']}" ) ) {
GFCommon::log_debug( __METHOD__ . '(): Executing functions hooked to gform_after_submission.' );
}
/**
* Fires after successful form submission
*
* Used to perform additional actions after submission
*
* @param array $lead The Entry object
* @param array $form The Form object
*/
gf_do_action( array( 'gform_after_submission', $form['id'] ), $lead, $form );
} elseif ( $saving_for_later ) {
GFCommon::log_debug( 'GFFormDisplay::process_form(): Saving for later.' );
$lead = GFFormsModel::get_current_lead();
$form = self::update_confirmation( $form, $lead, 'form_saved' );
$confirmation = rgar( $form['confirmation'], 'message' );
$nl2br = rgar( $form['confirmation'], 'disableAutoformat' ) ? false : true;
$confirmation = GFCommon::replace_variables( $confirmation, $form, $lead, false, true, $nl2br );
$form_unique_id = GFFormsModel::get_form_unique_id( $form_id );
$ip = rgars( $form, 'personalData/preventIP' ) ? '' : GFFormsModel::get_ip();
$source_url = GFFormsModel::get_current_page_url();
$source_url = esc_url_raw( $source_url );
$resume_token = rgpost( 'gform_resume_token' );
$resume_token = sanitize_key( $resume_token );
$resume_token = GFFormsModel::save_draft_submission( $form, $lead, $field_values, $page_number, $files, $form_unique_id, $ip, $source_url, $resume_token );
$notifications_to_send = GFCommon::get_notifications_to_send( 'form_saved', $form, $lead );
$log_notification_event = empty( $notifications_to_send ) ? 'No notifications to process' : 'Processing notifications';
GFCommon::log_debug( "GFFormDisplay::process_form(): {$log_notification_event} for form_saved event." );
foreach ( $notifications_to_send as $notification ) {
if ( isset( $notification['isActive'] ) && ! $notification['isActive'] ) {
GFCommon::log_debug( "GFFormDisplay::process_form(): Notification is inactive, not processing notification (#{$notification['id']} - {$notification['name']})." );
continue;
}
$notification['message'] = self::replace_save_variables( $notification['message'], $form, $resume_token );
GFCommon::send_notification( $notification, $form, $lead );
}
self::set_submission_if_null( $form_id, 'saved_for_later', true );
self::set_submission_if_null( $form_id, 'resume_token', $resume_token );
GFCommon::log_debug( 'GFFormDisplay::process_form(): Saved incomplete submission.' );
}
/**
* Allows the confirmation redirect header to be suppressed. Required by GFAPI::submit_form().
*
* @since 2.3
*
* @param bool $suppress_redirect
*/
$suppress_redirect = apply_filters( 'gform_suppress_confirmation_redirect', false );
if ( is_array( $confirmation ) && isset( $confirmation['redirect'] ) && ! $suppress_redirect ) {
header( "Location: {$confirmation["redirect"]}" );
/**
* Fires after submission, if the confirmation page includes a redirect
*
* Used to perform additional actions after submission
*
* @param array $lead The Entry object
* @param array $form The Form object
*/
gf_do_action( array( 'gform_post_submission', $form['id'] ), $lead, $form );
exit;
}
}
if ( ! isset( self::$submission[ $form_id ] ) ) {
self::$submission[ $form_id ] = array();
}
self::set_submission_if_null( $form_id, 'is_valid', $is_valid );
self::set_submission_if_null( $form_id, 'form', $form );
self::set_submission_if_null( $form_id, 'lead', $lead );
self::set_submission_if_null( $form_id, 'confirmation_message', $confirmation );
self::set_submission_if_null( $form_id, 'page_number', $page_number );
self::set_submission_if_null( $form_id, 'source_page_number', $source_page_number );
/**
* Fires after the form processing is completed. Form processing happens when submitting a page on a multi-page form (i.e. going to the "Next" or "Previous" page), or
* when submitting a single page form.
*
* @param array $form The Form Object
* @param int $page_number In a multi-page form, this variable contains the current page number.
* @param int $source_page_number In a multi-page form, this parameters contains the number of the page that the submission came from.
* For example, when clicking "Next" on page 1, this parameter will be set to 1. When clicking "Previous" on page 2, this parameter will be set to 2.
*/
gf_do_action( array( 'gform_post_process', $form['id'] ), $form, $page_number, $source_page_number );
}
/**
* Get form object and insert review page, if necessary.
*
* @since 2.1.1.25 Added $partial_entry parameter.
* @since 1.9.15
*
* @param array $form The current Form object.
* @param array $partial_entry The partial entry from the resumed incomplete submission. Defaults to an empty array.
*
* @return array The form object.
*/
public static function maybe_add_review_page( $form, $partial_entry = array() ) {
/* Setup default review page parameters. */
$review_page = array(
'content' => '',
'cssClass' => '',
'is_enabled' => false,
'nextButton' => array(
'type' => 'text',
'text' => __( 'Review Form', 'gravityforms' ),
'imageUrl' => '',
'imageAlt' => '',
),
'previousButton' => array(
'type' => 'text',
'text' => __( 'Previous', 'gravityforms' ),
'imageUrl' => '',
'imageAlt' => '',
),
);
if ( has_filter( 'gform_review_page' ) || has_filter( "gform_review_page_{$form['id']}" ) ) {
if ( empty( $partial_entry ) ) {
// Prepare partial entry for review page.
$partial_entry = GFFormsModel::get_current_lead();
}
/**
* GFFormsModel::create_lead() caches the field value and conditional logic visibility which can create
* issues when 3rd parties use hooks later in the process to modify the form. Let's flush the cache avoid
* any weirdness.
*/
GFCache::flush();
/**
* A filter for setting up the review page
*
* @param array $review_page The review page parameters
* @param array $form The current form object
* @param array|false $partial_entry The partial entry for the form or false on initial form display.
*/
$review_page = gf_apply_filters( array( 'gform_review_page', $form['id'] ), $review_page, $form, $partial_entry );
if ( ! rgempty( 'button_text', $review_page ) ) {
$review_page['nextButton']['text'] = $review_page['button_text'];
}
}
if ( rgar( $review_page, 'is_enabled' ) ) {
$form = self::insert_review_page( $form, $review_page );
}
return $form;
}
private static function set_submission_if_null( $form_id, $key, $val ) {
if ( ! isset( self::$submission[ $form_id ][ $key ] ) ) {
self::$submission[ $form_id ][ $key ] = $val;
}
}
private static function upload_files( $form, $files ) {
$form_upload_path = GFFormsModel::get_upload_path( $form['id'] );
GFCommon::log_debug( "GFFormDisplay::upload_files(): Upload path {$form_upload_path}" );
//Creating temp folder if it does not exist
$target_path = $form_upload_path . '/tmp/';
if ( ! is_dir( $target_path ) && wp_mkdir_p( $target_path ) ) {
GFCommon::recursive_add_index_file( $target_path );
}
foreach ( $form['fields'] as $field ) {
$input_name = "input_{$field->id}";
//skip fields that are not file upload fields or that don't have a file to be uploaded or that have failed validation
$input_type = RGFormsModel::get_input_type( $field );
if ( ! in_array( $input_type, array( 'fileupload', 'post_image' ) ) || $field->multipleFiles ) {
continue;
}
/*if ( $field->failed_validation || empty( $_FILES[ $input_name ]['name'] ) ) {
GFCommon::log_debug( "GFFormDisplay::upload_files(): Skipping field: {$field->label}({$field->id} - {$field->type})." );
continue;
}*/
if ( $field->failed_validation ) {
GFCommon::log_debug( "GFFormDisplay::upload_files(): Skipping field because it failed validation: {$field->label}({$field->id} - {$field->type})." );
continue;
}
if ( empty( $_FILES[ $input_name ]['name'] ) ) {
GFCommon::log_debug( "GFFormDisplay::upload_files(): Skipping field because a file could not be found: {$field->label}({$field->id} - {$field->type})." );
continue;
}
$file_name = $_FILES[ $input_name ]['name'];
if ( GFCommon::file_name_has_disallowed_extension( $file_name ) ) {
GFCommon::log_debug( __METHOD__ . "(): Illegal file extension: {$file_name}" );
continue;
}
$allowed_extensions = ! empty( $field->allowedExtensions ) ? GFCommon::clean_extensions( explode( ',', strtolower( $field->allowedExtensions ) ) ) : array();
if ( ! empty( $allowed_extensions ) ) {
if ( ! GFCommon::match_file_extension( $file_name, $allowed_extensions ) ) {
GFCommon::log_debug( __METHOD__ . "(): The uploaded file type is not allowed: {$file_name}" );
continue;
}
}
/**
* Allows the disabling of file upload whitelisting
*
* @param bool false Set to 'true' to disable whitelisting. Defaults to 'false'.
*/
$whitelisting_disabled = apply_filters( 'gform_file_upload_whitelisting_disabled', false );
if ( empty( $allowed_extensions ) && ! $whitelisting_disabled ) {
// Whitelist the file type
$valid_file_name = GFCommon::check_type_and_ext( $_FILES[ $input_name ], $file_name );
if ( is_wp_error( $valid_file_name ) ) {
GFCommon::log_debug( __METHOD__ . "(): The uploaded file type is not allowed: {$file_name}" );
continue;
}
}
$file_info = RGFormsModel::get_temp_filename( $form['id'], $input_name );
GFCommon::log_debug( 'GFFormDisplay::upload_files(): Temp file info: ' . print_r( $file_info, true ) );
if ( $file_info && move_uploaded_file( $_FILES[ $input_name ]['tmp_name'], $target_path . $file_info['temp_filename'] ) ) {
GFFormsModel::set_permissions( $target_path . $file_info['temp_filename'] );
$files[ $input_name ] = $file_info['uploaded_filename'];
GFCommon::log_debug( "GFFormDisplay::upload_files(): File uploaded successfully: {$file_info['uploaded_filename']}" );
} else {
GFCommon::log_error( "GFFormDisplay::upload_files(): File could not be uploaded: tmp_name: {$_FILES[ $input_name ]['tmp_name']} - target location: " . $target_path . $file_info['temp_filename'] );
}
}
return $files;
}
public static function get_state( $form, $field_values ) {
$fields = array();
foreach ( $form['fields'] as $field ) {
/* @var GF_Field $field */
if ( $field->is_state_validation_supported() ) {
$value = RGFormsModel::get_field_value( $field, $field_values, false );
$value = $field->get_value_default_if_empty( $value );
switch ( $field->get_input_type() ) {
case 'calculation' :
case 'singleproduct' :
case 'hiddenproduct' :
$price = ! is_array( $value ) || empty( $value[ $field->id . '.2' ] ) ? $field->basePrice : $value[ $field->id . '.2' ];
if ( empty( $price ) ) {
$price = 0;
}
$price = GFCommon::to_number( $price );
$product_name = ! is_array( $value ) || empty( $value[ $field->id . '.1' ] ) ? $field->label : $value[ $field->id . '.1' ];
$fields[ $field->id . '.1' ] = wp_hash( GFFormsModel::maybe_trim_input( $product_name, $form['id'], $field ) );
$fields[ $field->id . '.2' ] = wp_hash( GFFormsModel::maybe_trim_input( $price, $form['id'], $field ) );
break;
case 'singleshipping' :
$price = ! empty( $value ) ? $value : $field->basePrice;
$price = ! empty( $price ) ? GFCommon::to_number( $price ) : 0;
$fields[ $field->id ] = wp_hash( GFFormsModel::maybe_trim_input( $price, $form['id'], $field ) );
break;
case 'radio' :
case 'select' :
$fields[ $field->id ] = array();
foreach ( $field->choices as $choice ) {
$field_value = ! empty( $choice['value'] ) || $field->enableChoiceValue ? $choice['value'] : $choice['text'];
if ( $field->enablePrice ) {
$price = rgempty( 'price', $choice ) ? 0 : GFCommon::to_number( rgar( $choice, 'price' ) );
$field_value .= '|' . $price;
}
$fields[ $field->id ][] = wp_hash( GFFormsModel::maybe_trim_input( $field_value, $form['id'], $field ) );
}
break;
case 'checkbox' :
$index = 1;
foreach ( $field->choices as $choice ) {
$field_value = ! empty( $choice['value'] ) || $field->enableChoiceValue ? $choice['value'] : $choice['text'];
if ( $field->enablePrice ) {
$price = rgempty( 'price', $choice ) ? 0 : GFCommon::to_number( rgar( $choice, 'price' ) );
$field_value .= '|' . $price;
}
if ( $index % 10 == 0 ) { //hack to skip numbers ending in 0. so that 5.1 doesn't conflict with 5.10
$index ++;
}
$fields[ $field->id . '.' . $index ++ ] = wp_hash( GFFormsModel::maybe_trim_input( $field_value, $form['id'], $field ) );
}
break;
case 'consent':
$text = $field->checkboxLabel;
$description = GFFormsModel::get_latest_form_revisions_id( $form['id'] );
$fields[ $field->id . '.1' ] = wp_hash( 1 );
$fields[ $field->id . '.2' ] = wp_hash( GFFormsModel::maybe_trim_input( $text, $form['id'], $field ) );
$fields[ $field->id . '.3' ] = wp_hash( GFFormsModel::maybe_trim_input( $description, $form['id'], $field ) );
break;
}
}
}
$hash = json_encode( $fields );
$checksum = wp_hash( crc32( $hash ) );
return base64_encode( json_encode( array( $hash, $checksum ) ) );
}
/**
* Determine if form has any pages.
*
* @access private
*
* @param array $form The form object
*
* @return bool If form object has any pages
*/
private static function has_pages( $form ) {
return GFCommon::has_pages( $form );
}
private static function has_character_counter( $form ) {
if ( ! is_array( $form['fields'] ) ) {
return false;
}
foreach ( $form['fields'] as $field ) {
if ( $field->maxLength && ! $field->inputMask ) {
return true;
}
}
return false;
}
private static function has_placeholder( $form ) {
if ( ! is_array( $form['fields'] ) ) {
return false;
}
foreach ( $form['fields'] as $field ) {
if ( $field->placeholder != '' ) {
return true;
}
if ( is_array( $field->inputs ) ) {
foreach ( $field->inputs as $input ) {
if ( rgar( $input, 'placeholder' ) != '' ) {
return true;
}
}
}
}
return false;
}
private static function has_enhanced_dropdown( $form ) {
if ( ! is_array( $form['fields'] ) ) {
return false;
}
foreach ( $form['fields'] as $field ) {
if ( in_array( RGFormsModel::get_input_type( $field ), array( 'select', 'multiselect' ) ) && $field->enableEnhancedUI ) {
return true;
}
}
return false;
}
private static function has_password_strength( $form ) {
if ( ! is_array( $form['fields'] ) ) {
return false;
}
foreach ( $form['fields'] as $field ) {
if ( $field->type == 'password' && $field->passwordStrengthEnabled ) {
return true;
}
}
return false;
}
/**
* Determines if form has a Password field with the Password Visibility Toggle enabled.
*
* @since 2.4.15
*
* @param array $form Form object.
*
* @return bool
*/
private static function has_password_visibility( $form ) {
if ( ! is_array( $form['fields'] ) ) {
return false;
}
foreach ( $form['fields'] as $field ) {
if ( $field->type == 'password' && $field->passwordVisibilityEnabled ) {
return true;
}
}
return false;
}
private static function has_other_choice( $form ) {
if ( ! is_array( $form['fields'] ) ) {
return false;
}
foreach ( $form['fields'] as $field ) {
if ( $field->type == 'radio' && $field->enableOtherChoice ) {
return true;
}
// Code4OtherChoice
if ( $field->type == 'checkbox' && $field->enableOtherChoice ) {
return true;
}
}
return false;
}
public static function get_target_page( $form, $current_page, $field_values ) {
$page_number = RGForms::post( "gform_target_page_number_{$form['id']}" );
$page_number = ! is_numeric( $page_number ) ? 1 : $page_number;
// cast to an integer since page numbers can only be whole numbers
$page_number = absint( $page_number );
$direction = $page_number >= $current_page ? 1 : - 1;
//Finding next page that is not hidden by conditional logic
while ( RGFormsModel::is_page_hidden( $form, $page_number, $field_values ) ) {
$page_number += $direction;
}
//If all following pages are hidden, submit the form
if ( $page_number > self::get_max_page_number( $form ) ) {
$page_number = 0;
}
/**
* Modify the target page.
*
* @since 2.1.2.13
*
* @see https://docs.gravityforms.com/gform_target_page/
*
* @param int $page_number The target page number.
* @param array $form The current form object.
* @param int $current_page The page that was submitted.
* @param array $field_values Dynamic population values that were provided when loading the form.
*/
return gf_apply_filters( array( 'gform_target_page', $form['id'] ), $page_number, $form, $current_page, $field_values );
}
public static function get_source_page( $form_id ) {
$page_number = RGForms::post( "gform_source_page_number_{$form_id}" );
return ! is_numeric( $page_number ) ? 1 : $page_number;
}
public static function set_current_page( $form_id, $page_number ) {
self::$submission[ $form_id ]['page_number'] = $page_number;
}
public static function get_current_page( $form_id ) {
$page_number = isset( self::$submission[ $form_id ] ) ? intval( self::$submission[ $form_id ]['page_number'] ) : 1;
return $page_number;
}
private static function is_page_active( $form_id, $page_number ) {
return intval( self::get_current_page( $form_id ) ) == intval( $page_number );
}
/**
* Determine if the last page for the current form object is being submitted or rendered (depending on the provided $mode).
*
* @param array $form A Gravity Forms form object.
* @param string $mode Mode to check for: 'submit' or 'render'
*
* @return boolean
*/
public static function is_last_page( $form, $mode = 'submit' ) {
$page_number = self::get_source_page( $form['id'] );
$field_values = GFForms::post( 'gform_field_values' );
$target_page = self::get_target_page( $form, $page_number, $field_values );
if ( $mode == 'render' ) {
$is_valid = rgars( self::$submission, "{$form['id']}/is_valid" );
$is_last_page = $is_valid && $target_page == self::get_max_page_number( $form );
} else {
$is_last_page = (string) $target_page === '0';
}
return $is_last_page;
}
/**
* Returns the entry limit date range for the given period.
*
* @since unknown
* @since 2.4.15 Updated the day period to use the local time.
*
* @param string $period The eriod for the entry limit.
*
* @return array
*/
private static function get_limit_period_dates( $period ) {
if ( empty( $period ) ) {
return array( 'start_date' => null, 'end_date' => null );
}
switch ( $period ) {
case 'day':
return array(
'start_date' => current_time( 'Y-m-d' ),
'end_date' => current_time( 'Y-m-d 23:59:59' ),
);
break;
case 'week':
return array(
'start_date' => gmdate( 'Y-m-d', strtotime( 'Monday this week' ) ),
'end_date' => gmdate( 'Y-m-d 23:59:59', strtotime( 'Sunday this week' ) ),
);
break;
case 'month':
$month_start = gmdate( 'Y-m-1');
return array(
'start_date' => $month_start,
'end_date' => gmdate( 'Y-m-d H:i:s', strtotime( "{$month_start} +1 month -1 second" ) ),
);
break;
case 'year':
return array(
'start_date' => gmdate( 'Y-1-1' ),
'end_date' => gmdate( 'Y-12-31 23:59:59' ),
);
break;
}
}
public static function get_form( $form_id, $display_title = true, $display_description = true, $force_display = false, $field_values = null, $ajax = false, $tabindex = 0 ) {
/**
* Provides the ability to modify the options used to display the form
*
* @param array An array of Form Arguments when adding it to a page/post (Like the ID, Title, AJAX or not, etc)
*/
$form_args = apply_filters( 'gform_form_args', compact( 'form_id', 'display_title', 'display_description', 'force_display', 'field_values', 'ajax', 'tabindex' ) );
if ( empty( $form_args['form_id'] ) ) {
return self::get_form_not_found_html( $form_id, $ajax );
}
extract( $form_args );
//looking up form id by form name
if ( ! is_numeric( $form_id ) ) {
$form_title = $form_id;
$form_id = GFFormsModel::get_form_id( $form_title );
if ( $form_id === 0 ) {
return self::get_form_not_found_html( $form_title, $ajax );
}
}
$form = GFAPI::get_form( $form_id );
if ( ! $form ) {
return self::get_form_not_found_html( $form_id, $ajax );
}
$action = remove_query_arg( 'gf_token' );
if ( rgpost( 'gform_send_resume_link' ) == $form_id ) {
$save_email_confirmation = self::handle_save_email_confirmation( $form, $ajax );
if ( is_wp_error( $save_email_confirmation ) ) { // Failed email validation
$resume_token = rgpost( 'gform_resume_token' );
$resume_token = sanitize_key( $resume_token );
$incomplete_submission_info = GFFormsModel::get_draft_submission_values( $resume_token );
if ( $incomplete_submission_info['form_id'] == $form_id ) {
$submission_details_json = $incomplete_submission_info['submission'];
$submission_details = json_decode( $submission_details_json, true );
$partial_entry = $submission_details['partial_entry'];
$form = self::update_confirmation( $form, $partial_entry, 'form_saved' );
$confirmation_message = rgar( $form['confirmation'], 'message' );
$nl2br = rgar( $form['confirmation'], 'disableAutoformat' ) ? false : true;
$confirmation_message = GFCommon::replace_variables( $confirmation_message, $form, $partial_entry, false, true, $nl2br );
return self::handle_save_confirmation( $form, $resume_token, $confirmation_message, $ajax );
}
} else {
return $save_email_confirmation;
}
}
$is_postback = false;
$is_valid = true;
$confirmation_message = '';
//If form was submitted, read variables set during form submission procedure
$submission_info = isset( self::$submission[ $form_id ] ) ? self::$submission[ $form_id ] : false;
if ( rgar( $submission_info, 'saved_for_later' ) == true ) {
$resume_token = $submission_info['resume_token'];
$confirmation_message = rgar( $submission_info, 'confirmation_message' );
return self::handle_save_confirmation( $form, $resume_token, $confirmation_message, $ajax );
}
$partial_entry = $submitted_values = $review_page_done = false;
if ( isset( $_GET['gf_token'] ) ) {
$incomplete_submission_info = GFFormsModel::get_draft_submission_values( $_GET['gf_token'] );
if ( rgar( $incomplete_submission_info, 'form_id' ) == $form_id ) {
$submission_details_json = $incomplete_submission_info['submission'];
$submission_details = json_decode( $submission_details_json, true );
$partial_entry = $submission_details['partial_entry'];
$submitted_values = $submission_details['submitted_values'];
$field_values = $submission_details['field_values'];
GFFormsModel::$unique_ids[ $form_id ] = $submission_details['gform_unique_id'];
GFFormsModel::$uploaded_files[ $form_id ] = $submission_details['files'];
self::set_submission_if_null( $form_id, 'resuming_incomplete_submission', true );
self::set_submission_if_null( $form_id, 'form_id', $form_id );
$form = self::maybe_add_review_page( $form, $partial_entry );
$review_page_done = true;
$max_page_number = self::get_max_page_number( $form );
$page_number = $submission_details['page_number'];
if ( $page_number > 1 && $max_page_number > 0 && $page_number > $max_page_number ) {
$page_number = $max_page_number;
}
self::set_submission_if_null( $form_id, 'page_number', $page_number );
}
}
if ( ! $review_page_done && $form !== false ) {
$form = self::maybe_add_review_page( $form );
}
if ( ! is_array( $partial_entry ) ) {
/**
* A filter that allows disabling of the form view counter
*
* @param int $form_id The Form ID to filter when disabling the form view counter
* @param bool Default set to false (view counter enabled), can be set to true to disable the counter
*/
$view_counter_disabled = gf_apply_filters( array( 'gform_disable_view_counter', $form_id ), false );
if ( $submission_info ) {
$is_postback = true;
$is_valid = rgar( $submission_info, 'is_valid' ) || rgar( $submission_info, 'is_confirmation' );
$form = $submission_info['form'];
$lead = $submission_info['lead'];
$confirmation_message = rgget( 'confirmation_message', $submission_info );
if ( $is_valid && ! RGForms::get( 'is_confirmation', $submission_info ) ) {
if ( $submission_info['page_number'] == 0 ) {
/**
* Fired after form submission
*
* @param array $lead The Entry object
* @param array $form The Form object
*/
gf_do_action( array( 'gform_post_submission', $form['id'] ), $lead, $form );
} else {
/**
* Fired after the page changes on a multi-page form
*
* @param array $form The Form object
* @param int $submission_info['source_page_number'] The page that was submitted
* @param int $submission_info['page_number'] The page that the user is being sent to
*/
gf_do_action( array( 'gform_post_paging', $form['id'] ), $form, $submission_info['source_page_number'], $submission_info['page_number'] );
}
}
} elseif ( ! current_user_can( 'administrator' ) && ! $view_counter_disabled ) {
RGFormsModel::insert_form_view( $form_id );
}
}
if ( rgar( $form, 'enableHoneypot' ) ) {
$form['fields'][] = self::get_honeypot_field( $form );
}
//Fired right before the form rendering process. Allow users to manipulate the form object before it gets displayed in the front end
$form = gf_apply_filters( array( 'gform_pre_render', $form_id ), $form, $ajax, $field_values );
if ( empty( $form ) ) {
return self::get_form_not_found_html( $form_id, $ajax );
}
$has_pages = self::has_pages( $form );
//calling tab index filter
GFCommon::$tab_index = gf_apply_filters( array( 'gform_tabindex', $form_id ), $tabindex, $form );
//Don't display inactive forms
if ( ! $force_display && ! $is_postback ) {
$form_info = RGFormsModel::get_form( $form_id );
if ( empty( $form_info ) || ! $form_info->is_active ) {
return '';
}
// If form requires login, check if user is logged in
if ( rgar( $form, 'requireLogin' ) ) {
if ( ! is_user_logged_in() ) {
return empty( $form['requireLoginMessage'] ) ? '<p>' . esc_html__( 'Sorry. You must be logged in to view this form.', 'gravityforms' ) . '</p>' : '<p>' . GFCommon::gform_do_shortcode( $form['requireLoginMessage'] ) . '</p>';
}
}
}
// show the form regardless of the following validations when force display is set to true
if ( ! $force_display || $is_postback ) {
$form_schedule_validation = self::validate_form_schedule( $form );
// if form schedule validation fails AND this is not a postback, display the validation error
// if form schedule validation fails AND this is a postback, make sure is not a valid submission (enables display of confirmation message)
if ( $form_schedule_validation && ! $is_postback ) {
return $form_schedule_validation;
} elseif ( $form_schedule_validation && $is_postback && ! $is_valid ) {
return self::get_ajax_postback_html( $form_schedule_validation );
}
$entry_limit_validation = self::validate_entry_limit( $form );
// refer to form schedule condition notes above
if ( $entry_limit_validation && ! $is_postback ) {
return $entry_limit_validation;
} elseif ( $entry_limit_validation && $is_postback && ! $is_valid ) {
return self::get_ajax_postback_html( $entry_limit_validation );
}
}
$form_string = '';
//When called via a template, this will enqueue the proper scripts
//When called via a shortcode, this will be ignored (too late to enqueue), but the scripts will be enqueued via the enqueue_scripts event
self::enqueue_form_scripts( $form, $ajax );
$is_form_editor = GFCommon::is_form_editor();
$is_entry_detail = GFCommon::is_entry_detail();
$is_admin = $is_form_editor || $is_entry_detail;
if ( empty( $confirmation_message ) ) {
$wrapper_css_class = GFCommon::get_browser_class() . ' gform_wrapper';
if ( ! $is_valid ) {
$wrapper_css_class .= ' gform_validation_error';
}
$form_css_class = esc_attr( rgar( $form, 'cssClass' ) );
if ( GFCommon::is_legacy_markup_enabled( $form ) ) {
$form_css_class .= ' gform_legacy_markup';
} else {
$wrapper_css_class .= ' gravity-theme';
}
//Hiding entire form if conditional logic is on to prevent 'hidden' fields from blinking. Form will be set to visible in the conditional_logic.php after the rules have been applied.
$style = self::has_conditional_logic( $form ) ? "style='display:none'" : '';
// Split form CSS class by spaces and apply wrapper to each.
$custom_wrapper_css_class = '';
if ( ! empty( $form_css_class ) ) {
// Separate the CSS classes.
$form_css_classes = explode( ' ', $form_css_class );
$form_css_classes = array_filter( $form_css_classes );
// Append _wrapper to each class.
foreach ( $form_css_classes as &$wrapper_class ) {
$wrapper_class .= '_wrapper';
}
// Merge back into a string.
$custom_wrapper_css_class = ' ' . implode( ' ', $form_css_classes );
}
$form_string .= "
<div class='{$wrapper_css_class}{$custom_wrapper_css_class}' id='gform_wrapper_$form_id' " . $style . '>';
$anchor = self::get_anchor( $form, $ajax );
$form_string .= $anchor['tag'];
$action .= $anchor['id'];
$target = $ajax ? "target='gform_ajax_frame_{$form_id}'" : '';
$form_css_class = ! empty( $form['cssClass'] ) ? "class='{$form_css_class}'" : '';
if ( $is_postback && ! $is_valid ) {
$show_summary = rgar( $form, 'validationSummary', false );
// Generate validation heading message and errors list markup, append to form string.
$form_string .= self::get_validation_errors_markup( $form, $submitted_values, $show_summary );
}
$required_indicator_type = rgar( $form, 'requiredIndicator', 'text' );
$display_required_legend = GFCommon::has_required_field( $form ) && ! GFCommon::is_legacy_markup_enabled( $form ) && 'text' !== $required_indicator_type;
if ( ( $display_title || $display_description ) || $display_required_legend ) {
$gform_title_open = GFCommon::is_legacy_markup_enabled( $form ) ? '<h3 class="gform_title">' : '<h2 class="gform_title">';
$gform_title_close = GFCommon::is_legacy_markup_enabled( $form ) ? '</h3>' : '</h2>';
$form_string .= "
<div class='gform_heading'>";
if ( $display_title ) {
$form_string .= "
{$gform_title_open}" . $form['title'] . $gform_title_close;
}
if ( $display_description ) {
$form_string .= "
<span class='gform_description'>" . rgar( $form, 'description' ) . '</span>';
}
if ( $display_required_legend ) {
/**
* Modify the legend displayed at the bottom of the form header which explains how required fields are indicated.
*
* @since 2.5
*
* @param string $message The required indicator legend.
* @param array $form The current Form object.
*/
$required_legend = gf_apply_filters(
array( 'gform_required_legend', $form['id'] ),
/* Translators: the text or symbol that indicates a field is required */
sprintf( esc_html__( '"%s" indicates required fields', 'gravityforms' ), GFFormsModel::get_required_indicator( $form_id ) )
);
$form_string .= "
<p class='gform_required_legend'>{$required_legend}</p>";
}
$form_string .= '
</div>';
}
$novalidate = GFFormsModel::is_html5_enabled() ? 'novalidate' : '';
$action = esc_url( $action );
$form_string .= gf_apply_filters( array( 'gform_form_tag', $form_id ), "<form method='post' enctype='multipart/form-data' {$target} id='gform_{$form_id}' {$form_css_class} action='{$action}' $novalidate>", $form );
// If Save and Continue token was provided but expired/invalid, display error message.
if ( isset( $_GET['gf_token'] ) && ! is_array( $incomplete_submission_info ) ) {
/**
* Modify the error message displayed when an expired/invalid Save and Continue link is used.
*
* @since 2.4
*
* @param string $message Save & Continue expired/invalid link error message.
* @param array $form The current Form object.
*/
$savecontinue_expired_message = gf_apply_filters( array(
'gform_savecontinue_expired_message',
$form['id'],
), esc_html__( 'Save and Continue link used is expired or invalid.', 'gravityforms' ), $form );
// If message is not empty, add to form string.
if ( ! empty( $savecontinue_expired_message ) ) {
$form_string .= sprintf(
'<div class="validation_error gform_validation_error">%s</div>',
$savecontinue_expired_message
);
}
}
/* If the form was submitted, has multiple pages and is invalid, set the current page to the first page with an invalid field. */
if ( $has_pages && $is_postback && ! $is_valid ) {
self::set_current_page( $form_id, GFFormDisplay::get_first_page_with_error( $form ) );
}
$current_page = self::get_current_page( $form_id );
if ( $has_pages && ! $is_admin ) {
if ( $form['pagination']['type'] == 'percentage' ) {
$form_string .= self::get_progress_bar( $form, $current_page, $confirmation_message );
} else if ( $form['pagination']['type'] == 'steps' ) {
$form_string .= self::get_progress_steps( $form, $current_page );
}
}
$form_string .= "
<div class='gform_body gform-body'>";
//add first page if this form has any page fields
if ( $has_pages ) {
$style = self::is_page_active( $form_id, 1 ) ? '' : "style='display:none;'";
$class = ! empty( $form['firstPageCssClass'] ) ? " {$form['firstPageCssClass']}" : '';
$class = esc_attr( $class );
$form_string .= "<div id='gform_page_{$form_id}_1' class='gform_page{$class}' {$style}>
<div class='gform_page_fields'>";
}
$description_class = rgar( $form, 'descriptionPlacement' ) == 'above' ? 'description_above' : 'description_below';
$sublabel_class = rgar( $form, 'subLabelPlacement' ) == 'above' ? 'form_sublabel_above' : 'form_sublabel_below';
$tag = GFCommon::is_legacy_markup_enabled( $form ) ? 'ul' : 'div';
$form_string .= "<{$tag} id='gform_fields_{$form_id}' class='" . GFCommon::get_ul_classes( $form ) . "'>";
if ( is_array( $form['fields'] ) ) {
foreach ( $form['fields'] as $field ) {
/* @var GF_Field $field */
$field->conditionalLogicFields = self::get_conditional_logic_fields( $form, $field->id );
if ( is_array( $submitted_values ) ) {
$field_value = rgar( $submitted_values, $field->id );
if ( $field->type === 'consent'
&& ( $field_value[ $field->id . '.3' ] != GFFormsModel::get_latest_form_revisions_id( $form['id'] )
|| $field_value[ $field->id . '.2' ] != $field->checkboxLabel ) ) {
$field_value = GFFormsModel::get_field_value( $field, $field_values );
}
} else {
$field_value = GFFormsModel::get_field_value( $field, $field_values );
}
$form_string .= self::get_field( $field, $field_value, false, $form, $field_values );
if ( $field->layoutSpacerGridColumnSpan && ! GFCommon::is_legacy_markup_enabled( $form_id ) ) {
$form_string .= sprintf( '<div class="spacer gfield" style="grid-column: span %d;"></div>', $field->layoutSpacerGridColumnSpan );
}
}
}
$form_string .= "</{$tag}>";
if ( $has_pages ) {
$previous_button_alt = rgempty( 'imageAlt', $form['lastPageButton'] ) ? __( 'Previous Page', 'gravityforms' ) : $form['lastPageButton']['imageAlt'];
$previous_button = self::get_form_button( $form['id'], "gform_previous_button_{$form['id']}", $form['lastPageButton'], __( 'Previous', 'gravityforms' ), 'gform_previous_button', $previous_button_alt, self::get_current_page( $form_id ) - 1 );
/**
* Filter through the form previous button when paged
*
* @param int $form_id The Form ID to filter through
* @param string $previous_button The HTML rendered button (rendered with the form ID and the function get_form_button)
* @param array $form The Form object to filter through
*/
$previous_button = gf_apply_filters( array( 'gform_previous_button', $form_id ), $previous_button, $form );
$form_string .= '</div>' . self::gform_footer( $form, 'gform_page_footer ' . $form['labelPlacement'], $ajax, $field_values, $previous_button, $display_title, $display_description, $tabindex ) . '
</div>'; //closes gform_page
}
$form_string .= '</div>'; //closes gform_body
//suppress form footer for multi-page forms (footer will be included on the last page
$label_placement = rgar( $form, 'labelPlacement', 'before' );
if ( ! $has_pages ) {
$form_string .= self::gform_footer( $form, 'gform_footer ' . $label_placement, $ajax, $field_values, '', $display_title, $display_description, $tabindex );
}
$form_string .= '
</form>
</div>';
if ( $ajax && $is_postback ) {
global $wp_scripts;
$form_string = self::get_ajax_postback_html( $form_string );
}
if ( $ajax && ! $is_postback ) {
$spinner_url = gf_apply_filters( array( 'gform_ajax_spinner_url', $form_id ), GFCommon::get_base_url() . '/images/spinner.svg', $form );
$scroll_position = array( 'default' => '', 'confirmation' => '' );
if ( $anchor['scroll'] !== false ) {
$scroll_position['default'] = is_numeric( $anchor['scroll'] ) ? 'jQuery(document).scrollTop(' . intval( $anchor['scroll'] ) . ');' : "jQuery(document).scrollTop(jQuery('#gform_wrapper_{$form_id}').offset().top - mt);";
$scroll_position['confirmation'] = is_numeric( $anchor['scroll'] ) ? 'jQuery(document).scrollTop(' . intval( $anchor['scroll'] ) . ');' : "jQuery(document).scrollTop(jQuery('{$anchor['id']}').offset().top - mt);";
}
// Accessibility enhancements to properly handle the iframe title and content.
$iframe_content = esc_html__( 'This iframe contains the logic required to handle Ajax powered Gravity Forms.', 'gravityforms' );
$iframe_title = " title='{$iframe_content}'";
if ( defined( 'GF_DEBUG' ) && GF_DEBUG ) {
// In debug mode, display the iframe with the text content.
$iframe_style = 'display:block;width:600px;height:300px;border:1px solid #eee;';
} else {
// Hide the iframe and the content is not needed when not in debug mode.
$iframe_style = 'display:none;width:0px;height:0px;';
$iframe_content = '';
}
$form_scripts_body = 'gform.initializeOnLoaded( function() {' .
"gformInitSpinner( {$form_id}, '{$spinner_url}' );" .
"jQuery('#gform_ajax_frame_{$form_id}').on('load',function(){" .
"var contents = jQuery(this).contents().find('*').html();" .
"var is_postback = contents.indexOf('GF_AJAX_POSTBACK') >= 0;" .
'if(!is_postback){return;}' .
"var form_content = jQuery(this).contents().find('#gform_wrapper_{$form_id}');" .
"var is_confirmation = jQuery(this).contents().find('#gform_confirmation_wrapper_{$form_id}').length > 0;" .
"var is_redirect = contents.indexOf('gformRedirect(){') >= 0;" .
'var is_form = form_content.length > 0 && ! is_redirect && ! is_confirmation;' .
"var mt = parseInt(jQuery('html').css('margin-top'), 10) + parseInt(jQuery('body').css('margin-top'), 10) + 100;" .
'if(is_form){' .
"jQuery('#gform_wrapper_{$form_id}').html(form_content.html());" .
"if(form_content.hasClass('gform_validation_error')){jQuery('#gform_wrapper_{$form_id}').addClass('gform_validation_error');} else {jQuery('#gform_wrapper_{$form_id}').removeClass('gform_validation_error');}" .
"setTimeout( function() { /* delay the scroll by 50 milliseconds to fix a bug in chrome */ {$scroll_position['default']} }, 50 );" .
"if(window['gformInitDatepicker']) {gformInitDatepicker();}" .
"if(window['gformInitPriceFields']) {gformInitPriceFields();}" .
"var current_page = jQuery('#gform_source_page_number_{$form_id}').val();" .
"gformInitSpinner( {$form_id}, '{$spinner_url}' );" .
"jQuery(document).trigger('gform_page_loaded', [{$form_id}, current_page]);" .
"window['gf_submitting_{$form_id}'] = false;" .
'}' .
'else if(!is_redirect){' .
"var confirmation_content = jQuery(this).contents().find('.GF_AJAX_POSTBACK').html();" .
'if(!confirmation_content){' .
'confirmation_content = contents;' .
'}' .
'setTimeout(function(){' .
"jQuery('#gform_wrapper_{$form_id}').replaceWith(confirmation_content);" .
"{$scroll_position['confirmation']}" .
"jQuery(document).trigger('gform_confirmation_loaded', [{$form_id}]);" .
"window['gf_submitting_{$form_id}'] = false;" .
"wp.a11y.speak(jQuery('#gform_confirmation_message_{$form_id}').text());" .
'}, 50);' .
'}' .
'else{' .
"jQuery('#gform_{$form_id}').append(contents);" .
"if(window['gformRedirect']) {gformRedirect();}" .
'}' .
"jQuery(document).trigger('gform_post_render', [{$form_id}, current_page]);" .
'} );' .
'} );';
$form_scripts = GFCommon::get_inline_script_tag( $form_scripts_body );
$form_string .= "
<iframe style='{$iframe_style}' src='about:blank' name='gform_ajax_frame_{$form_id}' id='gform_ajax_frame_{$form_id}'" . $iframe_title . ">" . $iframe_content . "</iframe>
{$form_scripts}";
}
$is_first_load = ! $is_postback;
if ( ( ! $ajax || $is_first_load ) ) {
self::register_form_init_scripts( $form, $field_values, $ajax );
// We can't init in footer on AJAX calls, as those actions never get called.
$init_in_footer = ! ( defined('DOING_AJAX') && DOING_AJAX );
/**
* Allows init scripts to be outputted in either the header or footer.
*
* @since unknown
* @since 2.5.3 Defaults to ( ! DOING_AJAX )
*
* @param bool Whether to output init scripts in the footer. Defaults to ( ! DOING_AJAX ).
*/
if ( apply_filters( 'gform_init_scripts_footer', $init_in_footer ) ) {
$callback = array( new GF_Late_Static_Binding( array( 'form_id' => $form['id'] ) ), 'GFFormDisplay_footer_init_scripts' );
add_action( 'wp_footer', $callback, 999 );
add_action( 'admin_print_footer_scripts', $callback, 999 );
add_action( 'gform_preview_footer', $callback );
} else {
$form_string .= self::get_form_init_scripts( $form );
$init_script_body = "gform.initializeOnLoaded( function() { jQuery(document).trigger('gform_post_render', [{$form_id}, {$current_page}]) } );";
$form_string .= GFCommon::get_inline_script_tag( $init_script_body );
}
}
return gf_apply_filters( array( 'gform_get_form_filter', $form_id ), $form_string, $form );
} else {
$progress_confirmation = '';
//check admin setting for whether the progress bar should start at zero
$start_at_zero = rgars( $form, 'pagination/display_progressbar_on_confirmation' );
$start_at_zero = apply_filters( 'gform_progressbar_start_at_zero', $start_at_zero, $form );
//show progress bar on confirmation
if ( $start_at_zero && $has_pages && ! $is_admin && ( $form['confirmation']['type'] == 'message' && $form['pagination']['type'] == 'percentage' ) ) {
$progress_confirmation = self::get_progress_bar( $form, 0, $confirmation_message );
if ( $ajax ) {
$progress_confirmation = self::get_ajax_postback_html( $progress_confirmation );
}
} else {
//return regular confirmation message
if ( $ajax ) {
$progress_confirmation = self::get_ajax_postback_html( $confirmation_message );
} else {
$progress_confirmation = $confirmation_message;
}
}
/**
* Filters the form confirmation text.
*
* This filter allows the form confirmation text to be programmatically changed before it is rendered to the page.
*
* @since 2.5.15
*
* @param string $progress_confirmation Confirmation text to be filtered.
* @param array $form The current form object
*/
return gf_apply_filters( array( 'gform_get_form_confirmation_filter', $form_id ), $progress_confirmation, $form );
}
}
public static function footer_init_scripts( $form_id ) {
global $_init_forms;
$form = RGFormsModel::get_form_meta( $form_id );
$form_string = self::get_form_init_scripts( $form );
$current_page = self::get_current_page( $form_id );
$footer_script_body = "gform.initializeOnLoaded( function() { jQuery(document).trigger('gform_post_render', [{$form_id}, {$current_page}]) } );";
$form_string .= GFCommon::get_inline_script_tag( $footer_script_body );
/**
* A filter to allow modification of scripts that fire in the footer
*
* @param int $form_id The Form ID to filter through
* @param string $form_string Get the form scripts in a string
* @param array $form The Form object to filter through
* @param int $current_page The Current form page ID (If paging is enabled)
*/
$form_string = gf_apply_filters( array( 'gform_footer_init_scripts_filter', $form_id ), $form_string, $form, $current_page );
if ( ! isset( $_init_forms[ $form_id ] ) ) {
echo $form_string;
if ( ! is_array( $_init_forms ) ) {
$_init_forms = array();
}
$_init_forms[ $form_id ] = true;
}
}
public static function add_init_script( $form_id, $script_name, $location, $script ) {
$key = $script_name . '_' . $location;
if ( ! isset( self::$init_scripts[ $form_id ] ) ) {
self::$init_scripts[ $form_id ] = array();
}
//add script if it hasn't been added before
if ( ! array_key_exists( $key, self::$init_scripts[ $form_id ] ) ) {
self::$init_scripts[ $form_id ][ $key ] = array( 'location' => $location, 'script' => $script );
}
}
public static function get_form_button( $form_id, $button_input_id, $button, $default_text, $class, $alt, $target_page_number, $onclick = '' ) {
$is_form_editor = GFCommon::is_form_editor();
$tabindex = GFCommon::get_tabindex();
$input_type = ( rgar( $button, 'type' ) === 'link' ) ? 'button' : 'submit';
$do_submit = "jQuery(\"#gform_{$form_id}\").trigger(\"submit\",[true]);";
if( $is_form_editor ) {
$onclick = '';
} else {
if ( ! empty( $target_page_number ) ) {
$onclick = "onclick='jQuery(\"#gform_target_page_number_{$form_id}\").val(\"{$target_page_number}\"); {$onclick} {$do_submit} ' onkeypress='if( event.keyCode == 13 ){ jQuery(\"#gform_target_page_number_{$form_id}\").val(\"{$target_page_number}\"); {$onclick} {$do_submit} } '";
$input_type = 'button';
} else {
// prevent multiple form submissions when button is pressed multiple times
if ( GFFormsModel::is_html5_enabled() ) {
$set_submitting = "if( !jQuery(\"#gform_{$form_id}\")[0].checkValidity || jQuery(\"#gform_{$form_id}\")[0].checkValidity()){window[\"gf_submitting_{$form_id}\"]=true;}";
} else {
$set_submitting = "window[\"gf_submitting_{$form_id}\"]=true;";
}
$onclick_submit = $button['type'] == 'link' ? $do_submit : '';
$onclick = "onclick='if(window[\"gf_submitting_{$form_id}\"]){return false;} {$set_submitting} {$onclick} {$onclick_submit}' onkeypress='if( event.keyCode == 13 ){ if(window[\"gf_submitting_{$form_id}\"]){return false;} {$set_submitting} {$onclick} {$do_submit} }'";
}
}
if ( rgar( $button, 'type' ) == 'text' || rgar( $button, 'type' ) == 'link' || empty( $button['imageUrl'] ) ) {
$button_text = ! empty( $button['text'] ) ? $button['text'] : $default_text;
if ( rgar( $button, 'type' ) == 'link' ) {
if ( GFCommon::is_legacy_markup_enabled( $form_id ) ) {
$tag = 'a';
$target = 'href="javascript:void(0);"';
$icon = '';
} else {
$tag = 'button';
$class .= GFFormDisplay::get_submit_button_class( $button, $form_id );
$target = '';
$icon = '<svg aria-hidden="true" focusable="false" width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M0 8a4 4 0 004 4h3v3a1 1 0 102 0v-3h3a4 4 0 100-8 4 4 0 10-8 0 4 4 0 00-4 4zm9 4H7V7.414L5.707 8.707a1 1 0 01-1.414-1.414l3-3a1 1 0 011.414 0l3 3a1 1 0 01-1.414 1.414L9 7.414V12z" fill="#6B7280"/></svg>';
}
$button_input = "<{$tag} type='{$input_type}' {$target} id='{$button_input_id}_link' class='{$class}' {$tabindex} {$onclick}>{$icon} {$button_text}</{$tag}>";
} else {
$class .= GFFormDisplay::get_submit_button_class( $button, $form_id );
$button_input = "<input type='{$input_type}' id='{$button_input_id}' class='{$class}' value='" . esc_attr( $button_text ) . "' {$tabindex} {$onclick} />";
}
} else {
$imageUrl = esc_url( $button['imageUrl'] );
$class .= GFFormDisplay::get_submit_button_class( $button, $form_id );
$class .= ' gform_image_button';
$button_input = "<input type='image' src='{$imageUrl}' id='{$button_input_id}' class='{$class}' alt='{$alt}' {$tabindex} {$onclick} />";
}
return $button_input;
}
/**
* Get the CSS class for the submit button.
*
* @since 2.6
*
* @param array $button The button attributes.
* @param integer $form_id The ID of the form.
*
* @return string The CSS class(es) for this button.
*/
public static function get_submit_button_class( $button, $form_id ) {
$class = ( GFCommon::is_form_editor() ) ? '' : ' button';
$class .= rgar( $button, 'width' ) && 'full' == $button['width'] ? ' gform-button--width-full' : '';
// if the button is at the bottom, and if it has width, add a width class.
if ( rgar( $button, 'location' ) && 'bottom' == $button['location'] && rgar( $button, 'layoutGridColumnSpan' ) && 12 !== $button['layoutGridColumnSpan'] ) {
$form = GFAPI::get_form( $form_id );
$submit = new GF_Field_Submit();
$class .= ' ' . $submit->get_css_grid_class( $form );
}
return $class;
}
public static function gform_footer( $form, $class, $ajax, $field_values, $previous_button, $display_title, $display_description, $tabindex = 1 ) {
$form_id = absint( $form['id'] );
$footer = "
<div class='" . esc_attr( $class ) . "'>";
$button = rgar( $form, 'button', array( 'type' => 'link' ) );
if ( rgar( $form['button'], 'location' ) && 'inline' == $form['button']['location'] ) {
$button_input = '';
} else {
$button_input = self::get_form_button( $form['id'], "gform_submit_button_{$form['id']}", $button, __( 'Submit', 'gravityforms' ), 'gform_button', __( 'Submit', 'gravityforms' ), 0 );
$button_input = gf_apply_filters( array( 'gform_submit_button', $form_id ), $button_input, $form );
}
$save_button = rgars( $form, 'save/enabled' ) ? self::get_form_button( $form_id, "gform_save_{$form_id}_footer", $form['save']['button'], rgars( $form, 'save/button/text' ), 'gform_save_link', rgars( $form, 'save/button/text' ), 0, "jQuery(\"#gform_save_{$form_id}\").val(1);" ) : '';
/**
* Filters the save and continue link allowing the tag to be customized
*
* @since 2.0.7.7
*
* @param string $save_button The string containing the save and continue link markup.
* @param array $form The Form object associated with the link.
*/
$save_button = apply_filters( 'gform_savecontinue_link', $save_button, $form );
$save_button = apply_filters( "gform_savecontinue_link_{$form_id}", $save_button, $form );
$footer .= $previous_button . ' ' . $button_input . ' ' . $save_button;
$tabindex = (int) $tabindex;
if ( $ajax ) {
$footer .= "<input type='hidden' name='gform_ajax' value='" . esc_attr( "form_id={$form_id}&amp;title={$display_title}&amp;description={$display_description}&amp;tabindex={$tabindex}" ) . "' />";
}
$current_page = self::get_current_page( $form_id );
$next_page = $current_page + 1;
$next_page = $next_page > self::get_max_page_number( $form ) ? 0 : $next_page;
$field_values_str = is_array( $field_values ) ? http_build_query( $field_values ) : $field_values;
$files_input = '';
if ( GFCommon::has_multifile_fileupload_field( $form ) || ! empty( RGFormsModel::$uploaded_files[ $form_id ] ) ) {
$files = ! empty( RGFormsModel::$uploaded_files[ $form_id ] ) ? json_encode( RGFormsModel::$uploaded_files[ $form_id ], JSON_UNESCAPED_UNICODE ) : '';
$files_input = "<input type='hidden' name='gform_uploaded_files' id='gform_uploaded_files_{$form_id}' value='" . str_replace( "'", '&#039;', $files ) . "' />";
}
$save_inputs = '';
if ( rgars( $form, 'save/enabled' ) ) {
$resume_token = isset( $_POST['gform_resume_token'] ) ? $_POST['gform_resume_token'] : rgget( 'gf_token' );
$resume_token = sanitize_key( $resume_token );
$save_inputs = "<input type='hidden' class='gform_hidden' name='gform_save' id='gform_save_{$form_id}' value='' />
<input type='hidden' class='gform_hidden' name='gform_resume_token' id='gform_resume_token_{$form_id}' value='{$resume_token}' />";
}
if ( rgar( $form, 'requireLogin' ) ) {
$footer .= wp_nonce_field( 'gform_submit_' . $form_id, '_gform_submit_nonce_' . $form_id, true, false );
}
$unique_id = isset( self::$submission[ $form_id ] ) && rgar( self::$submission[ $form_id ], 'resuming_incomplete_submission' ) == true ? rgar( GFFormsModel::$unique_ids, $form_id ) : GFFormsModel::get_form_unique_id( $form_id );
$footer .= "
<input type='hidden' class='gform_hidden' name='is_submit_{$form_id}' value='1' />
<input type='hidden' class='gform_hidden' name='gform_submit' value='{$form_id}' />
{$save_inputs}
<input type='hidden' class='gform_hidden' name='gform_unique_id' value='" . esc_attr( $unique_id ) . "' />
<input type='hidden' class='gform_hidden' name='state_{$form_id}' value='" . self::get_state( $form, $field_values ) . "' />
<input type='hidden' class='gform_hidden' name='gform_target_page_number_{$form_id}' id='gform_target_page_number_{$form_id}' value='" . esc_attr( $next_page ) . "' />
<input type='hidden' class='gform_hidden' name='gform_source_page_number_{$form_id}' id='gform_source_page_number_{$form_id}' value='" . esc_attr( $current_page ) . "' />
<input type='hidden' name='gform_field_values' value='" . esc_attr( $field_values_str ) . "' />
{$files_input}
</div>";
return $footer;
}
public static function get_max_page_number( $form ) {
$page_number = 0;
foreach ( $form['fields'] as $field ) {
if ( $field->type == 'page' ) {
$page_number ++;
}
}
return $page_number == 0 ? 0 : $page_number + 1;
}
public static function get_first_page_with_error( $form ) {
$page = self::get_current_page( $form['id'] );
foreach ( $form['fields'] as $field ) {
if ( $field->failed_validation ) {
$page = $field->pageNumber;
break;
}
}
return $page;
}
/**
* Creates the honeypot field object for the given form.
*
* @since unknown
*
* @param array $form The form the honeypot field is to be created for.
*
* @return GF_Field
*/
private static function get_honeypot_field( $form ) {
$max_id = self::get_max_field_id( $form );
$labels = self::get_honeypot_labels();
$properties = array(
'type' => 'honeypot',
'label' => $labels[ rand( 0, 3 ) ],
'id' => $max_id + 1,
'cssClass' => 'gform_validation_container',
'description' => __( 'This field is for validation purposes and should be left unchanged.', 'gravityforms' ),
'formId' => absint( $form['id'] ),
);
return GF_Fields::create( $properties );
}
/**
* Get the maximum field ID for the current form.
*
* @since unknown
* @since 1.9.14 Updated to public access.
* @since 2.4.15 Updated to use GFFormsModel::get_next_field_id().
*
* @param array $form The current form object.
*
* @return int
*/
public static function get_max_field_id( $form ) {
if ( ! empty( $form['fields'] ) ) {
$max = GFFormsModel::get_next_field_id( $form['fields'] ) - 1;
} else {
$max = 0;
}
return $max;
}
private static function get_honeypot_labels() {
$honeypot_labels = array( 'Name', 'Email', 'Phone', 'Comments' );
/**
* Allow the honeypot field labels to be overridden.
*
* @since 2.0.7.16
*
* @param array $honeypot_labels The honeypot field labels.
*/
return apply_filters( 'gform_honeypot_labels_pre_render', $honeypot_labels );
}
/**
* Used to determine the required validation result.
*
* @param GF_Field $field
* @param int $form_id
*
* @return bool
*/
public static function is_empty( $field, $form_id = 0 ) {
if ( empty( $_POST[ 'is_submit_' . $field->formId ] ) ) {
return true;
}
return $field->is_value_submission_empty( $form_id );
}
private static function validate_honeypot( $form ) {
$honeypot_id = self::get_max_field_id( $form );
return rgempty( "input_{$honeypot_id}" );
}
public static function handle_submission( $form, &$lead, $ajax = false ){
$lead_id = gf_apply_filters( array( 'gform_entry_id_pre_save_lead', $form['id'] ), null, $form );
if ( ! empty( $lead_id ) ) {
if ( empty( $lead ) ) {
$lead = array();
}
$lead['id'] = $lead_id;
}
// Passwords are not saved to the database but should be available during the submission process.
GF_Field_Password::stash_passwords( $form );
//creating entry in DB
RGFormsModel::save_lead( $form, $lead );
$lead = GFFormsModel::set_entry_meta( $lead, $form );
$is_spam = GFCommon::is_spam_entry( $lead, $form );
if ( $is_spam ) {
// Marking entry as spam.
GFFormsModel::update_entry_property( $lead['id'], 'status', 'spam', false, true );
$lead['status'] = 'spam';
}
// Passwords are not saved to the database but should be available during the submission process.
$lead = GF_Field_Password::hydrate_passwords( $lead );
/**
* Fired after an entry is created
*
* @param array $lead The Entry object
* @param array $form The Form object
*/
do_action( 'gform_entry_created', $lead, $form );
$lead = gf_apply_filters( array( 'gform_entry_post_save', $form['id'] ), $lead, $form );
gf_feed_processor()->save()->dispatch();
RGFormsModel::set_current_lead( $lead );
if ( ! $is_spam ) {
GFCommon::create_post( $form, $lead );
//send notifications
GFCommon::send_form_submission_notifications( $form, $lead );
}
self::clean_up_files( $form );
// remove incomplete submission and purge expired
if ( rgars( $form, 'save/enabled' ) ) {
GFFormsModel::delete_draft_submission( rgpost( 'gform_resume_token' ) );
GFFormsModel::purge_expired_draft_submissions();
}
/**
* Fires during submission before the confirmation is processed.
*
* @since 2.3.3.10
*
* @param array $lead The entry array.
* @param array $form The Form array.
*/
do_action( 'gform_pre_handle_confirmation', $lead, $form );
/**
* Allows the entry to be modified before the confirmation is processed.
*
* @since 2.3.4.2
*
* @param array $lead The entry array.
* @param array $form The Form array.
*/
$lead = apply_filters( 'gform_entry_pre_handle_confirmation', $lead, $form );
//display confirmation message or redirect to confirmation page
return self::handle_confirmation( $form, $lead, $ajax );
}
public static function clean_up_files( $form ) {
$unique_form_id = rgpost( 'gform_unique_id' );
if ( ! ctype_alnum( $unique_form_id ) ) {
return false;
}
$target_path = RGFormsModel::get_upload_path( $form['id'] ) . '/tmp/';
$filename = $unique_form_id . '_input_*';
$files = GFCommon::glob( $filename, $target_path );
if ( is_array( $files ) ) {
array_map( 'unlink', $files );
}
// clean up files from abandoned submissions older than 48 hours (30 days if Save and Continue is enabled)
$files = GFCommon::glob( '*', $target_path );
if ( is_array( $files ) ) {
$seconds_in_day = 24 * 60 * 60;
$save_enabled = rgars( $form, 'save/enabled' );
$expiration_days = $save_enabled ? 30 : 2;
/**
* Filter lifetime in days of temporary files.
*
* @since 2.1.3.5
*
* @param int $expiration_days The number of days temporary files should remain in the uploads directory. Default is 2 or 30 if save and continue is enabled.
* @param array $form The form currently being processed.
*/
$expiration_days = apply_filters( 'gform_temp_file_expiration_days', $expiration_days, $form );
if ( $save_enabled ) {
/**
* Filter lifetime in days of an incomplete form submission
*
* @since 2.1.3.5
*
* @param int $expiration_days The number of days temporary files should remain in the uploads directory.
*/
$expiration_days = apply_filters( 'gform_incomplete_submissions_expiration_days', $expiration_days );
}
$lifespan = $expiration_days * $seconds_in_day;
foreach ( $files as $file ) {
if ( is_file( $file ) && time() - filemtime( $file ) >= $lifespan ) {
unlink( $file );
}
}
}
}
/**
* Handles the actions that occur when a confirmation occurs.
*
* @since 2.1.1.11 Refactored to use GFFormDisplay::get_confirmation_message().
* @since 2.5 Updated to use GFFormDisplay::get_confirmation_url().
*
* @param array $form The Form Object.
* @param array $entry The Entry Object.
* @param bool $ajax If AJAX is being used. Defaults to false.
* @param array $aux_data Additional data to use when building the confirmation message. Defaults to empty array.
*
* @return array The Confirmation Object.
*/
public static function handle_confirmation( $form, $entry, $ajax = false, $aux_data = array() ) {
$form = self::update_confirmation( $form, $entry );
GFCommon::log_debug( sprintf( '%s(): Preparing confirmation (#%s - %s).', __METHOD__, rgar( $form['confirmation'], 'id' ), rgar( $form['confirmation'], 'name' ) ) );
if ( rgar( $form['confirmation'], 'type' ) == 'message' ) {
$confirmation = self::get_confirmation_message( $form['confirmation'], $form, $entry, $aux_data );
} else {
$confirmation = array( 'redirect' => self::get_confirmation_url( $form['confirmation'], $form, $entry ) );
}
$form_id = absint( $form['id'] );
$filter = array( 'gform_confirmation', $form_id );
if ( gf_has_filters( $filter ) ) {
GFCommon::log_debug( __METHOD__ . '(): Executing functions hooked to gform_confirmation.' );
/**
* Allows the form confirmation to be overridden.
*
* @since unknown
*
* @param string|array $confirmation The confirmation message or an array when performing a redirect.
* @param array $form The form which was submitted.
* @param array $entry The entry created from the form submission.
* @param bool $ajax Indicates if ajax is enabled for the current form.
*/
$confirmation = gf_apply_filters( $filter, $confirmation, $form, $entry, $ajax );
}
if ( is_array( $confirmation ) && ! empty( $confirmation['redirect'] ) ) {
$suppress_redirect = false;
/**
* Allows the confirmation redirect header to be suppressed. Required by GFAPI::submit_form().
*
* @since 2.3
*
* @param bool $suppress_redirect Indicates if the redirect header should be suppressed.
*/
$suppress_redirect = apply_filters( 'gform_suppress_confirmation_redirect', $suppress_redirect );
if ( ( headers_sent() || $ajax ) && ! $suppress_redirect ) {
// Using client side redirect for AJAX forms or if headers have already been sent.
$confirmation = self::get_js_redirect_confirmation( $confirmation['redirect'], $ajax );
}
} elseif ( is_string( $confirmation ) && ! empty( $confirmation ) ) {
$confirmation = GFCommon::gform_do_shortcode( $confirmation );
} else {
$confirmation = null;
}
if ( empty( $confirmation ) ) {
GFCommon::log_debug( __METHOD__ . '(): Invalid confirmation; using default text instead.' );
$confirmation = self::get_confirmation_message( GFFormsModel::get_default_confirmation(), $form, $entry );
}
GFCommon::log_debug( __METHOD__ . '(): Sending confirmation => ' . print_r( $confirmation, true ) );
return $confirmation;
}
/**
* Returns the redirect URL for the current submission.
*
* @since 2.5
*
* @param array $confirmation The confirmation properties.
* @param array $form The form which was submitted.
* @param array $entry The entry created from the form submission.
*
* @return string
*/
public static function get_confirmation_url( $confirmation, $form, $entry ) {
if ( ! empty( $confirmation['pageId'] ) && $confirmation['type'] === 'page' ) {
$url = get_permalink( $confirmation['pageId'] );
if ( empty( $url ) ) {
GFCommon::log_debug( sprintf( '%s(): Selected page (%s) is invalid.', __METHOD__, $confirmation['pageId'] ) );
return '';
}
} else {
$url = rgar( $confirmation, 'url' );
if ( ! empty( $url ) ) {
$url = trim( GFCommon::replace_variables( $url, $form, $entry, false, false, true, 'text' ) );
}
if ( empty( $url ) ) {
GFCommon::log_debug( __METHOD__ . '(): URL is empty.' );
return '';
}
}
$url_info = parse_url( $url );
$query_string = rgar( $url_info, 'query' );
$dynamic_query = GFCommon::replace_variables( trim( $confirmation['queryString'] ), $form, $entry, true, false, false, 'text' );
$dynamic_query = str_replace( array( "\r", "\n" ), '', $dynamic_query );
$query_string .= rgempty( 'query', $url_info ) || empty( $dynamic_query ) ? $dynamic_query : '&' . $dynamic_query;
if ( ! empty( $url_info['fragment'] ) ) {
$query_string .= '#' . rgar( $url_info, 'fragment' );
}
$url = isset( $url_info['scheme'] ) ? $url_info['scheme'] : 'http';
$url .= '://' . rgar( $url_info, 'host' );
if ( ! empty( $url_info['port'] ) ) {
$url .= ':' . rgar( $url_info, 'port' );
}
$url .= rgar( $url_info, 'path' );
if ( ! empty( $query_string ) ) {
$url .= "?{$query_string}";
}
return $url;
}
/**
* Gets the confirmation message to be displayed.
*
* @since 2.1.1.11
* @access public
*
* @param array $confirmation The Confirmation Object.
* @param array $form The Form Object.
* @param array $entry The Entry Object.
* @param array $aux_data Additional data to be passed to GFCommon::replace_variables().
*
* @return string The confirmation message.
*/
public static function get_confirmation_message( $confirmation, $form, $entry, $aux_data = array() ) {
$ajax = isset( $_POST['gform_ajax'] );
$anchor = self::get_anchor( $form, $ajax );
$anchor = $anchor['tag'];
$nl2br = rgar( $confirmation, 'disableAutoformat' ) ? false : true;
$css_class = esc_attr( rgar( $form, 'cssClass' ) );
$message = GFCommon::replace_variables( $confirmation['message'], $form, $entry, false, true, $nl2br, 'html', $aux_data );
$message = self::maybe_sanitize_confirmation_message( $message );
$message = empty( $confirmation['message'] ) ? "{$anchor} " : "{$anchor}<div id='gform_confirmation_wrapper_{$form['id']}' class='gform_confirmation_wrapper {$css_class}'><div id='gform_confirmation_message_{$form['id']}' class='gform_confirmation_message_{$form['id']} gform_confirmation_message'>" . $message . '</div></div>';
return $message;
}
/**
* Sanitizes a confirmation message.
*
* @since 2.0.0
* @param $confirmation_message
*
* @return string
*/
private static function maybe_sanitize_confirmation_message( $confirmation_message ) {
return GFCommon::maybe_sanitize_confirmation_message( $confirmation_message );
}
private static function get_js_redirect_confirmation( $url, $ajax ) {
// JSON_HEX_TAG is available on PHP >= 5.3. It will prevent payloads such as <!--<script> from causing an error on redirection.
$url = defined( 'JSON_HEX_TAG' ) ? json_encode( $url, JSON_HEX_TAG ) : json_encode( $url );
$script_body = "function gformRedirect(){document.location.href={$url};}";
if ( ! $ajax ) {
$script_body .= 'gformRedirect();';
}
return GFCommon::get_inline_script_tag( $script_body );
}
public static function send_emails( $form, $lead ) {
_deprecated_function( 'send_emails', '1.7', 'GFCommon::send_form_submission_notifications' );
GFCommon::send_form_submission_notifications( $form, $lead );
}
/**
* Returns the context for the current submission.
*
* @since 2.6.4
*
* @return string
*/
public static function get_submission_context() {
switch ( self::$submission_initiated_by ) {
case self::SUBMISSION_INITIATED_BY_WEBFORM:
return 'form-submit';
case self::SUBMISSION_INITIATED_BY_API_VALIDATION:
return 'api-validate';
}
return 'api-submit';
}
/**
* Determines if the current form submission is valid.
*
* @since unknown
* @since 2.4.19 Updated to use GFFormDisplay::is_field_validation_supported().
*
* @param array $form The form being processed.
* @param array $field_values The dynamic population parameter names and values.
* @param int $page_number The current page number.
* @param int $failed_validation_page The page number which has failed validation.
*
* @return bool
*/
public static function validate( &$form, $field_values, $page_number = 0, &$failed_validation_page = 0 ) {
$form = gf_apply_filters( array( 'gform_pre_validation', $form['id'] ), $form );
// validate form schedule
if ( self::validate_form_schedule( $form ) ) {
return false;
}
// validate entry limit
if ( self::validate_entry_limit( $form ) ) {
return false;
}
// make sure database isn't being upgraded now and submissions are blocked
if ( gf_upgrade()->get_submissions_block() ) {
return false;
}
// Prevent tampering with the submitted form
if ( empty( $_POST[ 'is_submit_' . $form['id'] ] ) ) {
return false;
}
$context = self::get_submission_context();
$is_valid = true;
$is_last_page = self::get_target_page( $form, $page_number, $field_values ) == '0';
foreach ( $form['fields'] as &$field ) {
/* @var GF_Field $field */
if ( ! self::is_field_validation_supported( $field ) ) {
continue;
}
// If a page number is specified, only validates fields that are on current page
$field_in_other_page = $page_number > 0 && $field->pageNumber != $page_number;
// validate fields with 'no duplicate' functionality when they are present on pages before the current page.
$validate_duplicate_feature = $field->noDuplicates && $page_number > 0 && $field->pageNumber <= $page_number;
if ( $field_in_other_page && ! $is_last_page && ! $validate_duplicate_feature ) {
continue;
}
//ignore validation if field is hidden
if ( RGFormsModel::is_field_hidden( $form, $field, $field_values ) ) {
$field->is_field_hidden = true;
continue;
}
$value = RGFormsModel::get_field_value( $field );
$input_type = RGFormsModel::get_input_type( $field );
//display error message if field is marked as required and the submitted value is empty
if ( $field->isRequired && self::is_empty( $field, $form['id'] ) ) {
$field->set_required_error( $value );
} //display error if field does not allow duplicates and the submitted value already exists
else if ( $field->noDuplicates && RGFormsModel::is_duplicate( $form['id'], $field, $value ) ) {
$field->failed_validation = true;
//set page number so the failed field displays if on multi-page form
$failed_validation_page = $field->pageNumber;
switch ( $input_type ) {
case 'date' :
$default_message = __( 'This date has already been taken. Please select a new date.', 'gravityforms' );
break;
default:
$default_message = is_array( $value ) ? __( 'This field requires a unique entry and the values you entered have already been used.', 'gravityforms' ) :
sprintf( __( "This field requires a unique entry and '%s' has already been used", 'gravityforms' ), $value );
break;
}
$field->validation_message = gf_apply_filters( array( 'gform_duplicate_message', $form['id'] ), $default_message, $form, $field, $value );
} else {
if ( self::failed_state_validation( $form['id'], $field, $value ) ) {
$field->failed_validation = true;
$field->validation_message = in_array( $field->inputType, array( 'singleproduct', 'singleshipping', 'hiddenproduct', 'consent' ) ) ? __( 'Please enter a valid value.', 'gravityforms' ) : __( 'Invalid selection. Please select one of the available choices.', 'gravityforms' );
} else {
$field->validate( $value, $form );
}
}
/**
* Allows custom validation of the field value.
*
* @since Unknown
* @since 2.6.4 Added the $context param.
*
* @param array $result {
* An array containing the validation result properties.
*
* @type bool $is_valid The field validation result.
* @type array $message The field validation message.
* }
* @param mixed $value The field value currently being validated.
* @param array $form The form currently being validated.
* @param GF_Field $field The field currently being validated.
* @param string $context The context for the current submission. Possible values: form-submit, api-submit, api-validate.
*/
$result = gf_apply_filters( array( 'gform_field_validation', $form['id'], $field->id ), array(
'is_valid' => $field->failed_validation ? false : true,
'message' => $field->validation_message
), $value, $form, $field, $context );
$field->failed_validation = rgar( $result, 'is_valid' ) ? false : true;
$field->validation_message = rgar( $result, 'message' );
if ( $field->failed_validation ) {
$is_valid = false;
}
}
if ( $is_valid && $is_last_page && self::is_form_empty( $form ) ) {
foreach ( $form['fields'] as &$field ) {
if ( ! self::is_field_validation_supported( $field ) ) {
continue;
}
$field->failed_validation = true;
$field->validation_message = esc_html__( 'At least one field must be filled out', 'gravityforms' );
$is_valid = false;
unset( $field->is_field_hidden );
}
}
/**
* Allows custom validation of the form.
*
* @since Unknown
* @since 2.6.4 Added the $context param.
*
* @param array $validation_result {
* An array containing the validation properties.
*
* @type bool $is_valid The validation result.
* @type array $form The form currently being validated.
* @type int $failed_validation_page The number of the page that failed validation or the current page if the form is valid.
* }
* @param string $context The context for the current submission. Possible values: form-submit, api-submit, api-validate.
*/
$validation_result = gf_apply_filters( array( 'gform_validation', $form['id'] ), array( 'is_valid' => $is_valid, 'form' => $form, 'failed_validation_page' => $failed_validation_page ), $context );
$is_valid = $validation_result['is_valid'];
$form = $validation_result['form'];
$failed_validation_page = $validation_result['failed_validation_page'];
return $is_valid;
}
/**
* Determines if the supplied field is suitable for validation.
*
* @since 2.4.19
* @since 2.4.20 Added the second param.
*
* @param GF_Field $field The field being processed.
* @param bool $type_check_only Indicates if only the field type property should be evaluated.
*
* @return bool
*/
public static function is_field_validation_supported( $field, $type_check_only = false ) {
$is_valid_type = ! in_array( $field->type, array( 'html', 'page', 'section' ) );
if ( ! $is_valid_type || $type_check_only ) {
return $is_valid_type;
}
return ! ( $field->is_administrative() || $field->visibility === 'hidden' );
}
/**
* Determines if the current form submission is empty.
*
* @since unknown
* @since 2.4.19 Updated to use GFFormDisplay::is_field_validation_supported().
*
* @param array $form The form being processed.
*
* @return bool
*/
public static function is_form_empty( $form ) {
foreach ( $form['fields'] as $field ) {
if ( self::is_field_validation_supported( $field, true ) && ! $field->is_field_hidden && ! self::is_empty( $field, $form['id'] ) ) {
return false;
}
}
return true;
}
public static function failed_state_validation( $form_id, $field, $value ) {
global $_gf_state;
if ( ! $field->is_state_validation_supported() ) {
return false;
}
if ( ! isset( $_gf_state ) ) {
$state = json_decode( base64_decode( $_POST[ "state_{$form_id}" ] ), true );
if ( ! $state || sizeof( $state ) != 2 ) {
return true;
}
//making sure state wasn't tampered with by validating checksum
$checksum = wp_hash( crc32( $state[0] ) );
if ( $checksum !== $state[1] ) {
return true;
}
$_gf_state = json_decode( $state[0], true );
}
if ( ! is_array( $value ) ) {
$value = array( $field->id => $value );
}
foreach ( $value as $key => $input_value ) {
$state = isset( $_gf_state[ $key ] ) ? $_gf_state[ $key ] : false;
//converting price to a number for single product fields and single shipping fields
if ( ( in_array( $field->inputType, array( 'singleproduct', 'hiddenproduct' ) ) && $key == $field->id . '.2' ) || $field->inputType == 'singleshipping' ) {
$input_value = GFCommon::to_number( $input_value );
}
$sanitized_input_value = wp_kses( $input_value, wp_kses_allowed_html( 'post' ) );
$hash = wp_hash( $input_value );
$sanitized_hash = wp_hash( $sanitized_input_value );
$fails_hash = strlen( $input_value ) > 0 && $state !== false && ( ( is_array( $state ) && ! in_array( $hash, $state ) ) || ( ! is_array( $state ) && $hash != $state ) );
$fails_sanitized_hash = strlen( $sanitized_input_value ) > 0 && $state !== false && ( ( is_array( $state ) && ! in_array( $sanitized_hash, $state ) ) || ( ! is_array( $state ) && $sanitized_hash != $state ) );
if ( $fails_hash && $fails_sanitized_hash ) {
return true;
}
}
return false;
}
/**
* Enqueues scripts/styles for forms embedded via blocks and shortcodes.
*
* @since unknown
* @since 2.4.18 Added support for blocks and the gform_post_enqueue_scripts hook.
*/
public static function enqueue_scripts() {
global $wp_query;
if ( ! isset( $wp_query->posts ) || ! is_array( $wp_query->posts ) ) {
return;
}
foreach ( $wp_query->posts as $post ) {
if ( ! $post instanceof WP_Post ) {
continue;
}
$found_forms = $found_blocks = array();
self::parse_forms( $post->post_content, $found_forms, $found_blocks );
if ( ! empty( $found_forms ) ) {
foreach ( $found_forms as $form_id => $ajax ) {
$form = GFAPI::get_form( $form_id );
if ( $form && $form['is_active'] && ! $form['is_trash'] ) {
self::enqueue_form_scripts( $form, $ajax );
}
}
/**
* Allows custom actions to be performed when scripts/styles are enqueued.
*
* @since 2.4.18
*
* @param array $found_forms An array of found forms using the form ID as the key to the ajax status.
* @param array $found_blocks An array of found GF blocks.
* @param WP_Post $post The post which was processed.
*/
do_action( 'gform_post_enqueue_scripts', $found_forms, $found_blocks, $post );
}
}
}
/**
* Parses the supplied post content for forms embedded via blocks and shortcodes.
*
* @since 2.4.18
*
* @param string $post_content The post content to be parsed.
* @param array $found_forms An array of found forms using the form ID as the key to the ajax status.
* @param array $found_blocks An array of found GF blocks.
*/
public static function parse_forms( $post_content, &$found_forms, &$found_blocks ) {
if ( empty( $post_content ) ) {
return;
}
self::parse_forms_from_shortcode( $post_content, $found_forms );
if ( ! function_exists( 'has_blocks' ) || ! has_blocks( $post_content ) ) {
return;
}
self::parse_forms_from_blocks( parse_blocks( $post_content ), $found_forms, $found_blocks );
}
/**
* Finds forms embedded in the supplied blocks.
*
* @since 2.4.18
*
* @param array $blocks The blocks found in the post content.
* @param array $found_forms An array of found forms using the form ID as the key to the ajax status.
* @param array $found_blocks An array of found GF blocks.
*/
public static function parse_forms_from_blocks( $blocks, &$found_forms, &$found_blocks ) {
if ( ! method_exists( 'GF_Blocks', 'get_all_types' ) ) {
return;
}
$block_types = GF_Blocks::get_all_types();
foreach ( $blocks as $block ) {
if ( empty( $block['blockName'] ) ) {
continue;
}
if ( $block['blockName'] === 'core/block' ) {
self::parse_forms_from_reusable_block( $block, $found_forms, $found_blocks );
continue;
}
if ( ! empty( $block['innerBlocks'] ) ) {
self::parse_forms_from_blocks( $block['innerBlocks'], $found_forms, $found_blocks );
continue;
}
$supported_type = in_array( $block['blockName'], $block_types, true );
if ( ! $supported_type || ( $supported_type && empty( $block['attrs']['formId'] ) ) ) {
continue;
}
$found_blocks[] = $block;
// Get the form ID and AJAX attributes.
$form_id = (int) $block['attrs']['formId'];
$ajax = isset( $block['attrs']['ajax'] ) ? (bool) $block['attrs']['ajax'] : false;
if ( self::is_applicable_form( $form_id, $ajax, $found_forms ) ) {
$found_forms[ $form_id ] = $ajax;
}
}
}
/**
* Finds forms embedded in the supplied reusable block.
*
* @since 2.4.18
*
* @param array $block The block to be processed.
* @param array $found_forms An array of found forms using the form ID as the key to the ajax status.
* @param array $found_blocks An array of found GF blocks.
*/
public static function parse_forms_from_reusable_block( $block, &$found_forms, &$found_blocks ) {
if ( empty( $block['attrs']['ref'] ) ) {
return;
}
$reusable_block = get_post( $block['attrs']['ref'] );
if ( empty( $reusable_block ) || $reusable_block->post_type !== 'wp_block' ) {
return;
}
self::parse_forms( $reusable_block->post_content, $found_forms, $found_blocks );
}
/**
* Finds forms embedded in the supplied post content.
*
* @since 2.4.18
*
* @param string $post_content The post content to be processed.
* @param array $found_forms An array of found forms using the form ID as the key to the ajax status.
*/
public static function parse_forms_from_shortcode( $post_content, &$found_forms ) {
if ( empty( $post_content ) ) {
return;
}
if ( preg_match_all( '/\[gravityform[s]? +.*?((id=.+?)|(name=.+?))\]/is', $post_content, $matches, PREG_SET_ORDER ) ) {
foreach ( $matches as $match ) {
$attr = shortcode_parse_atts( $match[1] );
$form_id = rgar( $attr, 'id' );
if ( empty( $form_id ) && ! empty( $attr['name'] ) ) {
$form_id = $attr['name'];
}
if ( $form_id && ! is_numeric( $form_id ) ) {
$form_id = GFFormsModel::get_form_id( $form_id );
}
if ( empty( $form_id ) ) {
continue;
}
$form_id = (int) $form_id;
$ajax = isset( $attr['ajax'] ) && strtolower( substr( $attr['ajax'], 0, 4 ) ) == 'true';
if ( self::is_applicable_form( $form_id, $ajax, $found_forms ) ) {
$found_forms[ $form_id ] = $ajax;
}
}
}
}
/**
* Determines if the supplied form ID should be added to the found forms array.
*
* @since 2.4.18
*
* @param int $form_id The form ID to be checked.
* @param bool $ajax Indicates if Ajax is enabled for the found form.
* @param array $found_forms An array of found forms using the form ID as the key to the ajax status.
*
* @return bool
*/
public static function is_applicable_form( $form_id, $ajax, $found_forms ) {
return ! isset( $found_forms[ $form_id ] ) || ( isset( $found_forms[ $form_id ] ) && true === $ajax && false === $found_forms[ $form_id ] );
}
/**
* Returns forms embedded in the supplied post content.
*
* @since unknown
* @since 2.4.18 Updated to use GFFormDisplay::parse_forms_from_shortcode().
*
* @param string $post_content The post content to be processed.
* @param bool $ajax Indicates if Ajax is enabled for at least one of the forms.
*
* @return array
*/
public static function get_embedded_forms( $post_content, &$ajax ) {
$found_forms = $forms = array();
self::parse_forms_from_shortcode( $post_content, $found_forms );
if ( empty( $found_forms ) ) {
return $forms;
}
foreach ( $found_forms as $form_id => $is_ajax ) {
$forms[] = GFAPI::get_form( $form_id );
if ( ! $ajax && $is_ajax ) {
$ajax = true;
}
}
return $forms;
}
/**
* Get the various enqueueable assets for a given form.
*
* @since 2.5
*
* @param array $form An array representing the current Form object.
*
* @return GF_Asset[]
*/
public static function get_form_enqueue_assets( $form ) {
$assets = array();
if ( ! get_option( 'rg_gforms_disable_css' ) ) {
if ( GFCommon::is_legacy_markup_enabled( $form ) ) {
/**
* Allows users to disable legacy CSS files from being loaded on the Front End.
*
* @since 2.5-beta-rc-3
*
* @param boolean Whether to disable legacy css.
*/
$disable_legacy_css = apply_filters( 'gform_disable_form_legacy_css', false );
if ( ! $disable_legacy_css ) {
$assets[] = new GF_Style_Asset( 'gforms_reset_css' );
if ( self::has_datepicker_field( $form ) ) {
$assets[] = new GF_Style_Asset( 'gforms_datepicker_css' );
}
$assets[] = new GF_Style_Asset( 'gforms_formsmain_css' );
$assets[] = new GF_Style_Asset( 'gforms_ready_class_css' );
$assets[] = new GF_Style_Asset( 'gforms_browsers_css' );
if ( is_rtl() ) {
$assets[] = new GF_Style_Asset( 'gforms_rtl_css' );
}
}
} else {
$assets[] = new GF_Style_Asset( 'gform_basic' );
/**
* Allows users to disable the main theme.css file from being loaded on the Front End.
*
* @since 2.5-beta-3
*
* @param boolean Whether to disable the theme css.
*/
$disable_theme_css = apply_filters( 'gform_disable_form_theme_css', false );
if ( ! $disable_theme_css ) {
$assets[] = new GF_Style_Asset( 'gform_theme' );
}
}
if ( self::has_password_visibility( $form ) ) {
$assets[] = new GF_Style_Asset( 'dashicons' );
}
}
$assets[] = new GF_Script_Asset( 'wp-a11y' );
$gf_main = new GF_Script_Asset( 'gform_gravityforms' );
if ( self::has_checkbox_field( $form, true ) ) {
$gf_main->add_localize_data( 'gf_field_checkbox', array(
'strings' => array(
'selected' => wp_strip_all_tags( __( 'All choices are selected.', 'gravityforms' ) ),
'deselected' => wp_strip_all_tags( __( 'All choices are unselected.', 'gravityforms' ) ),
),
) );
}
if ( self::has_fileupload_field( $form ) ) {
$gf_main->add_localize_data( 'gf_legacy', array( 'is_legacy' => GFCommon::is_legacy_markup_enabled( $form ) ) );
GFCommon::localize_gform_gravityforms_multifile();
if ( ! GFCommon::is_legacy_markup_enabled( $form ) ) {
$assets[] = new GF_Style_Asset( 'dashicons' );
}
}
$gf_main->add_localize_data( 'gf_global', GFCommon::gf_global( false, true ) );
$assets[] = $gf_main;
$has_logic = false;
add_filter( 'gform_gf_legacy_multi', function( $data ) use ( $form ) {
$data[ $form['id'] ] = GFCommon::is_legacy_markup_enabled( $form );
return $data;
}, 10, 1 );
if ( self::has_conditional_logic( $form ) ) {
$has_logic = true;
}
if ( self::has_page_conditional_logic( $form ) ) {
$assets[] = new GF_Script_Asset( 'gform_page_conditional_logic' );
$has_logic = true;
}
// Conditional logic script is required for any type of conditional logic (page or field-level). Enqueue it if true.
if ( $has_logic ) {
$gf_conditional_logic = new GF_Script_Asset( 'gform_conditional_logic' );
$gf_conditional_logic->add_localize_data( 'gf_legacy', array( 'is_legacy' => GFCommon::is_legacy_markup_enabled( $form ) ) );
$assets[] = $gf_conditional_logic;
}
if ( self::has_datepicker_field( $form ) ) {
$assets[] = new GF_Script_Asset( 'gform_datepicker_init' );
}
if ( self::has_recaptcha_field( $form ) ) {
$language = self::get_recaptcha_language( $form );
$assets[] = new GF_Script_Asset( 'gform_recaptcha', esc_url( sprintf( 'https://www.google.com/recaptcha/api.js?hl=%s&render=explicit', $language ) ) );
}
if ( self::has_password_strength( $form ) ) {
$assets[] = new GF_Script_Asset( 'gforms_zxcvbn', includes_url( '/js/zxcvbn.min.js' ) );
$assets[] = new GF_Script_Asset( 'password-strength-meter' );
}
if ( GFCommon::has_multifile_fileupload_field( $form ) ) {
$assets[] = new GF_Script_Asset( 'plupload-all' );
}
if ( self::has_enhanced_dropdown( $form ) || self::has_pages( $form ) ) {
$assets[] = new GF_Script_Asset( 'gform_json' );
}
if ( self::has_character_counter( $form ) ) {
$assets[] = new GF_Script_Asset( 'gform_textarea_counter' );
}
if ( self::has_input_mask( $form ) ) {
$assets[] = new GF_Script_Asset( 'gform_masked_input' );
}
if ( self::has_enhanced_dropdown( $form ) ) {
if ( wp_script_is( 'chosen', 'registered' ) ) {
$assets[] = new GF_Script_Asset( 'chosen' );
} else {
$assets[] = new GF_Script_Asset( 'gform_chosen' );
}
}
if ( self::has_placeholder( $form ) ) {
$assets[] = new GF_Script_Asset( 'gform_placeholder' );
}
// enqueue jQuery every time form is displayed to allow 'gform_post_render' js hook
// to be available to users even when GF is not using it
$assets[] = new GF_Script_Asset( 'jquery' );
return $assets;
}
/**
* Enqueue the required scripts for this form.
*
* @param array $form An array representing the current Form object.
* @param false $ajax Whether this is being requested via AJAX.
*
* @return void
*/
public static function enqueue_form_scripts( $form, $ajax = false ) {
// adding pre enqueue scripts hook so that scripts can be added first if a need exists
/**
* Fires before any scripts are enqueued (form specific using the ID as well)
*
* @param array $form The Form Object
* @param bool $ajax Whether AJAX is on or off (True or False)
*/
gf_do_action( array( 'gform_pre_enqueue_scripts', $form['id'] ), $form, $ajax );
add_filter( 'script_loader_tag', array( 'GFFormDisplay', 'add_script_defer' ), 10, 2 );
$assets = self::get_form_enqueue_assets( $form );
foreach( $assets as $asset ) {
/**
* @var GF_Asset $asset
*/
$asset->enqueue_asset();
}
/**
* Fires after any scripts are enqueued (form specific using the ID as well)
*
* @param array $form The Form Object
* @param bool $ajax Whether AJAX is on or off (True or False)
*/
gf_do_action( array( 'gform_enqueue_scripts', $form['id'] ), $form, $ajax );
}
/**
* Add defer attribute to Gravity Forms scripts and any script dependent on a Gravity Forms script.
*
* @since 2.5
*
* @param string $tag The complete script markup that will be output.
* @param string $handle The handle of the current script.
*
* @return string
*/
public static function add_script_defer( $tag, $handle ) {
// If this is one of our scripts, let's defer it.
if( strpos( $handle, 'gform_' ) !== false && strpos( $tag, ' defer' ) === false ) {
$tag = str_replace( ' src', " defer='defer' src", $tag );
}
// Otherwise, let's hunt for scripts that have our scripts as dependencies of other scripts and defer those scripts too.
else {
global $wp_scripts;
$script = rgar( $wp_scripts->registered, $handle );
if ( $script && ! empty( $script->deps ) ) {
foreach( $script->deps as $dep_handle ) {
$tag = self::add_script_defer( $tag, $dep_handle );
}
}
}
return $tag;
}
private static $printed_scripts = array();
/**
* Print the required scripts for this form, since we're hooking in after enqueues have processed.
*
* @param array $form An array representing the current Form object.
* @param false $ajax Whether this is being requested via AJAX.
*
* @return void
*/
public static function print_form_scripts( $form, $ajax ) {
// adding pre print scripts hook so that scripts can be added first if a need exists
/**
* Fires before any scripts are printed (form specific using the ID as well)
*
* @since 2.5
*
* @param array $form The Form Object
* @param bool $ajax Whether AJAX is on or off (True or False)
*/
gf_do_action( array( 'gform_pre_print_scripts', $form['id'] ), $form, $ajax );
add_filter( 'script_loader_tag', array( 'GFFormDisplay', 'add_script_defer' ), 10, 2 );
$assets = self::get_form_enqueue_assets( $form );
foreach( $assets as $asset ) {
/**
* @var GF_Asset $asset
*/
$asset->print_asset();
}
/**
* Fires after any scripts are enqueued (form specific using the ID as well)
*
* @since 2.5
*
* @param array $form The Form Object
* @param bool $ajax Whether AJAX is on or off (True or False)
*/
gf_do_action( array( 'gform_print_scripts', $form['id'] ), $form, $ajax );
}
public static function has_conditional_logic( $form ) {
$has_conditional_logic = self::has_conditional_logic_legwork( $form );
/**
* A filter that runs through a form that has conditional logic
*
* @param bool $has_conditional_logic True or False if the user has conditional logic active in their current form settings
* @param array $form The Current form object
*/
return apply_filters( 'gform_has_conditional_logic', $has_conditional_logic, $form );
}
private static function has_conditional_logic_legwork( $form ) {
if ( empty( $form ) ) {
return false;
}
if ( isset( $form['button']['conditionalLogic'] ) ) {
return true;
}
if ( is_array( rgar( $form, 'fields' ) ) ) {
foreach ( rgar( $form, 'fields' ) as $field ) {
if ( isset( $field->fields ) && is_array( $field->fields ) && self::has_conditional_logic_legwork( array( 'fields' => $field->fields ) ) ) {
return true;
}
if ( ! empty( $field->conditionalLogic ) ) {
return true;
} else if ( isset( $field->nextButton ) && ! empty( $field->nextButton['conditionalLogic'] ) ) {
return true;
}
}
}
return false;
}
/**
* Determines if the form has page conditional logic.
*
* @since 2.5
*
* @param array $form Form object.
*
* @return bool
*/
public static function has_page_conditional_logic( $form ) {
if ( ! GFCommon::form_has_fields( $form ) ) {
return false;
}
foreach ( $form['fields'] as $field ) {
if ( $field->type === 'page' && ! empty( $field->conditionalLogic ) && is_array( $field->conditionalLogic ) ) {
return true;
}
}
return false;
}
/**
* Get init script and all necessary data for conditional logic.
*
* @todo: Replace much of the field value retrieval with a get_original_value() method in GF_Field class.
*
* @param $form
* @param array $field_values
*
* @return string
*/
private static function get_conditional_logic( $form, $field_values = array() ) {
$logics = '';
$dependents = '';
$fields_with_logic = array();
$default_values = array();
$field_dependents = array();
foreach ( $form['fields'] as $field ) {
/* @var GF_Field $field */
$field_deps = self::get_conditional_logic_fields( $form, $field->id );
$field_dependents[ $field->id ] = ! empty( $field_deps ) ? $field_deps : array();
//use section's logic if one exists
$section = RGFormsModel::get_section( $form, $field->id );
$section_logic = ! empty( $section ) ? $section->conditionalLogic : null;
$field_logic = $field->type != 'page' ? $field->conditionalLogic : null; //page break conditional logic will be handled during the next button click
$next_button_logic = ! empty( $field->nextButton ) && ! empty( $field->nextButton['conditionalLogic'] ) ? $field->nextButton['conditionalLogic'] : null;
if ( ! empty( $field_logic ) || ! empty( $next_button_logic ) ) {
$field_section_logic = array( 'field' => $field_logic, 'nextButton' => $next_button_logic, 'section' => $section_logic );
$logics .= $field->id . ': ' . GFCommon::json_encode( $field_section_logic ) . ',';
$fields_with_logic[] = $field->id;
$peers = $field->type == 'section' ? GFCommon::get_section_fields( $form, $field->id ) : array( $field );
$peer_ids = array();
foreach ( $peers as $peer ) {
$peer_ids[] = $peer->id;
}
$dependents .= $field->id . ': ' . GFCommon::json_encode( $peer_ids ) . ',';
}
//-- Saving default values so that they can be restored when toggling conditional logic ---
$field_val = '';
$input_type = $field->get_input_type();
$inputs = $field->get_entry_inputs();
//get parameter value if pre-populate is enabled
if ( $field->allowsPrepopulate ) {
if ( $input_type == 'checkbox' || $input_type == 'multiselect' ) {
$field_val = RGFormsModel::get_parameter_value( $field->inputName, $field_values, $field );
if ( ! is_array( $field_val ) ) {
$field_val = explode( ',', $field_val );
}
} elseif ( is_array( $inputs ) ) {
$field_val = array();
foreach ( $inputs as $input ) {
$field_val[ $input['id'] ] = RGFormsModel::get_parameter_value( rgar( $input, 'name' ), $field_values, $field );
}
} elseif ( $input_type == 'time' ) { // maintained for backwards compatibility. The Time field now has an inputs array.
$parameter_val = RGFormsModel::get_parameter_value( $field->inputName, $field_values, $field );
if ( ! empty( $parameter_val ) && ! is_array( $parameter_val ) && preg_match( '/^(\d*):(\d*) ?(.*)$/', $parameter_val, $matches ) ) {
$field_val = array();
$field_val[] = esc_attr( $matches[1] ); //hour
$field_val[] = esc_attr( $matches[2] ); //minute
$field_val[] = rgar( $matches, 3 ); //am or pm
}
} elseif ( $input_type == 'list' ) {
$parameter_val = RGFormsModel::get_parameter_value( $field->inputName, $field_values, $field );
$field_val = is_array( $parameter_val ) ? $parameter_val : explode( ',', str_replace( '|', ',', $parameter_val ) );
if ( is_array( rgar( $field_val, 0 ) ) ) {
$list_values = array();
foreach ( $field_val as $row ) {
$list_values = array_merge( $list_values, array_values( $row ) );
}
$field_val = $list_values;
}
} else {
$field_val = RGFormsModel::get_parameter_value( $field->inputName, $field_values, $field );
}
}
//use default value if pre-populated value is empty
$field_val = $field->get_value_default_if_empty( $field_val );
if ( is_array( $field->choices ) && $input_type != 'list' ) {
//radio buttons start at 0 and checkboxes start at 1
$choice_index = $input_type == 'radio' ? 0 : 1;
$is_pricing_field = GFCommon::is_pricing_field( $field->type );
foreach ( $field->choices as $choice ) {
if ( $input_type == 'checkbox' && ( $choice_index % 10 ) == 0 ){
$choice_index++;
}
$is_prepopulated = is_array( $field_val ) ? in_array( $choice['value'], $field_val ) : $choice['value'] == $field_val;
$is_choice_selected = rgar( $choice, 'isSelected' ) || $is_prepopulated;
if ( $is_choice_selected ) {
// Select
if ( $input_type == 'select' ) {
$price = GFCommon::to_number( rgar( $choice, 'price' ) ) == false ? 0 : GFCommon::to_number( rgar( $choice, 'price' ) );
$val = $is_pricing_field && $field->type != 'quantity' ? $choice['value'] . '|' . $price : $choice['value'];
$default_values[ $field->id ] = $val;
}
else {
if ( ! isset( $default_values[ $field->id ] ) ) {
$default_values[ $field->id ] = array();
}
// Multiselect
if ( $input_type == 'multiselect' ) {
$default_values[ $field->id ][] = $choice['value'];
}
// Checkboxes & Radio Buttons
else {
$default_values[ $field->id ][] = "choice_{$form['id']}_{$field->id}_{$choice_index}";
}
}
}
$choice_index ++;
}
} elseif ( ! rgblank( $field_val ) ) {
switch ( $input_type ) {
case 'date':
// for date fields; that are multi-input; and where the field value is a string
// (happens with prepop, default value will always be an array for multi-input date fields)
if ( is_array( $field->inputs ) && ( ! is_array( $field_val ) || ! isset( $field_val['m'] ) ) ) {
$format = empty( $field->dateFormat ) ? 'mdy' : esc_attr( $field->dateFormat );
$date_info = GFcommon::parse_date( $field_val, $format );
// converts date to array( 'm' => 1, 'd' => '13', 'y' => '1987' )
$field_val = $field->get_date_array_by_format( array( $date_info['month'], $date_info['day'], $date_info['year'] ) );
}
break;
case 'time':
if ( is_array( $field_val ) ) {
$ampm_key = key( array_slice( $field_val, - 1, 1, true ) );
$field_val[ $ampm_key ] = strtolower( $field_val[ $ampm_key ] );
}
break;
case 'address':
$state_input_id = sprintf( '%s.4', $field->id );
if ( isset( $field_val[ $state_input_id ] ) && ! $field_val[ $state_input_id ] ) {
$field_val[ $state_input_id ] = $field->defaultState;
}
$country_input_id = sprintf( '%s.6', $field->id );
if ( isset( $field_val[ $country_input_id ] ) && ! $field_val[ $country_input_id ] ) {
$field_val[ $country_input_id ] = $field->defaultCountry;
}
break;
}
$default_values[ $field->id ] = $field_val;
}
}
//adding form button conditional logic if enabled
if ( isset( $form['button']['conditionalLogic'] ) ) {
$logics .= '0: ' . GFCommon::json_encode( array( 'field' => $form['button']['conditionalLogic'], 'section' => null ) ) . ',';
$dependents .= '0: ' . GFCommon::json_encode( array( 0 ) ) . ',';
$fields_with_logic[] = 0;
}
if ( ! empty( $logics ) ) {
$logics = substr( $logics, 0, strlen( $logics ) - 1 );
} //removing last comma;
if ( ! empty( $dependents ) ) {
$dependents = substr( $dependents, 0, strlen( $dependents ) - 1 );
} //removing last comma;
$animation = rgar( $form, 'enableAnimation' ) ? '1' : '0';
global $wp_locale;
$number_format = $wp_locale->number_format['decimal_point'] == ',' ? 'decimal_comma' : 'decimal_dot';
$str = "if(window['jQuery']){" .
"if(!window['gf_form_conditional_logic'])" .
"window['gf_form_conditional_logic'] = new Array();" .
"window['gf_form_conditional_logic'][{$form['id']}] = { logic: { {$logics} }, dependents: { {$dependents} }, animation: {$animation}, defaults: " . json_encode( $default_values ) . ", fields: " . json_encode( $field_dependents ) . " }; " .
"if(!window['gf_number_format'])" .
"window['gf_number_format'] = '" . $number_format . "';" .
'jQuery(document).ready(function(){' .
"window['gformInitPriceFields']();" .
"gf_apply_rules({$form['id']}, " . json_encode( $fields_with_logic ) . ', true);' .
"jQuery('#gform_wrapper_{$form['id']}').show();" .
"jQuery(document).trigger('gform_post_conditional_logic', [{$form['id']}, null, true]);" .
'} );' .
'} ';
return $str;
}
/**
* Get conditional logic rules from page fields in a form.
*
* @since 2.5
*
* @param $form
*
* @return string
*/
private static function get_page_conditional_logic( $form ) {
$page_fields = array();
foreach ( $form['fields'] as $field ) {
if ( $field->type === 'page' ) {
$page_fields[] = array(
'fieldId' => $field->id,
'conditionalLogic' => $field->conditionalLogic,
'nextButton' => $field->nextButton,
);
}
}
$args = array(
'formId' => $form['id'],
'formButton' => $form['button'],
'pagination' => $form['pagination'],
'pages' => $page_fields,
);
return sprintf( '; new GFPageConditionalLogic( %s );', json_encode( $args ) );
}
/**
* Enqueue and retrieve all inline scripts that should be executed when the form is rendered.
* Use add_init_script() function to enqueue scripts.
*
* @param array $form
* @param array $field_values
* @param bool $is_ajax
*/
public static function register_form_init_scripts( $form, $field_values = array(), $is_ajax = false ) {
if ( rgars( $form, 'save/enabled' ) ) {
$save_script = "jQuery('#gform_save_{$form['id']}').val('');";
self::add_init_script( $form['id'], 'save', self::ON_PAGE_RENDER, $save_script );
}
// adding conditional logic script if conditional logic is configured for this form.
// get_conditional_logic also adds the chosen script for the enhanced dropdown option.
// if this form does not have conditional logic, add chosen script separately
if ( self::has_conditional_logic( $form ) ) {
self::add_init_script( $form['id'], 'number_formats', self::ON_PAGE_RENDER, self::get_number_formats_script( $form ) );
self::add_init_script( $form['id'], 'conditional_logic', self::ON_PAGE_RENDER, self::get_conditional_logic( $form, $field_values ) );
}
if ( self::has_page_conditional_logic( $form ) ) {
self::add_init_script( $form['id'], 'page_conditional_logic', self::ON_PAGE_RENDER, self::get_page_conditional_logic( $form ) );
}
//adding currency config if there are any product fields in the form
if ( self::has_price_field( $form ) ) {
self::add_init_script( $form['id'], 'number_formats', self::ON_PAGE_RENDER, self::get_number_formats_script( $form ) );
self::add_init_script( $form['id'], 'pricing', self::ON_PAGE_RENDER, self::get_pricing_init_script( $form ) );
}
if ( self::has_password_strength( $form ) ) {
$password_script = self::get_password_strength_init_script( $form );
self::add_init_script( $form['id'], 'password', self::ON_PAGE_RENDER, $password_script );
}
if ( self::has_enhanced_dropdown( $form ) ) {
$chosen_script = self::get_chosen_init_script( $form );
self::add_init_script( $form['id'], 'chosen', self::ON_PAGE_RENDER, $chosen_script );
self::add_init_script( $form['id'], 'chosen', self::ON_CONDITIONAL_LOGIC, $chosen_script );
}
if ( self::has_character_counter( $form ) ) {
self::add_init_script( $form['id'], 'character_counter', self::ON_PAGE_RENDER, self::get_counter_init_script( $form ) );
}
if ( self::has_input_mask( $form ) ) {
self::add_init_script( $form['id'], 'input_mask', self::ON_PAGE_RENDER, self::get_input_mask_init_script( $form ) );
}
if ( self::has_calculation_field( $form ) ) {
self::add_init_script( $form['id'], 'number_formats', self::ON_PAGE_RENDER, self::get_number_formats_script( $form ) );
self::add_init_script( $form['id'], 'calculation', self::ON_PAGE_RENDER, self::get_calculations_init_script( $form ) );
}
if ( self::has_currency_format_number_field( $form ) ) {
self::add_init_script( $form['id'], 'currency_format', self::ON_PAGE_RENDER, self::get_currency_format_init_script( $form ) );
}
if ( self::has_currency_copy_values_option( $form ) ) {
self::add_init_script( $form['id'], 'copy_values', self::ON_PAGE_RENDER, self::get_copy_values_init_script( $form ) );
}
if ( self::has_placeholder( $form ) ) {
self::add_init_script( $form['id'], 'placeholders', self::ON_PAGE_RENDER, self::get_placeholders_init_script( $form ) );
}
if ( isset( $form['fields'] ) && is_array( $form['fields'] ) ) {
foreach ( $form['fields'] as $field ) {
/* @var GF_Field $field */
if ( is_subclass_of( $field, 'GF_Field' ) ) {
$field->register_form_init_scripts( $form );
}
}
}
/**
* Fires when inline Gravity Forms scripts are enqueued
*
* Used to enqueue additional inline scripts
*
* @param array $form The Form object
* @param string $field_vale The current value of the selected field
* @param bool $is_ajax Returns true if using AJAX. Otherwise, false
*/
gf_do_action( array( 'gform_register_init_scripts', $form['id'] ), $form, $field_values, $is_ajax );
}
public static function get_form_init_scripts( $form ) {
$script_body = '';
if ( ! $form ) {
return $script_body;
}
/* rendering initialization scripts */
$init_scripts = rgar( self::$init_scripts, $form['id'] );
if ( ! empty( $init_scripts ) ) {
$script_body = isset( $gf_global_script ) ? $gf_global_script : '';
$script_body .=
"gform.initializeOnLoaded( function() { jQuery(document).on('gform_post_render', function(event, formId, currentPage){" .
"if(formId == {$form['id']}) {";
foreach ( $init_scripts as $init_script ) {
if ( $init_script['location'] == self::ON_PAGE_RENDER ) {
$script_body .= $init_script['script'];
}
}
$script_body .=
"} " . //keep the space. needed to prevent plugins from replacing }} with ]}
"} );" .
"jQuery(document).bind('gform_post_conditional_logic', function(event, formId, fields, isInit){";
foreach ( $init_scripts as $init_script ) {
if ( $init_script['location'] == self::ON_CONDITIONAL_LOGIC ) {
$script_body .= $init_script['script'];
}
}
$script_body .= '} ) } );';
}
return GFCommon::get_inline_script_tag( $script_body );
}
public static function get_chosen_init_script( $form ) {
$chosen_fields = array();
foreach ( $form['fields'] as $field ) {
$input_type = GFFormsModel::get_input_type( $field );
if ( $field->enableEnhancedUI && in_array( $input_type, array( 'select', 'multiselect' ) ) ) {
$chosen_fields[] = "#input_{$form['id']}_{$field->id}";
}
}
return "gformInitChosenFields('" . implode( ',', $chosen_fields ) . "','" . esc_attr( gf_apply_filters( array( 'gform_dropdown_no_results_text', $form['id'] ), __( 'No results matched', 'gravityforms' ), $form['id'] ) ) . "');";
}
public static function get_currency_format_init_script( $form ) {
$currency_fields = array();
foreach ( $form['fields'] as $field ) {
if ( $field->numberFormat == 'currency' ) {
$currency_fields[] = "#input_{$form['id']}_{$field->id}";
}
}
return "gformInitCurrencyFormatFields('" . implode( ',', $currency_fields ) . "');";
}
public static function get_copy_values_init_script( $form ) {
$script = "jQuery('.copy_values_activated').on('click', function(){
var inputId = this.id.replace('_copy_values_activated', '');
jQuery('#' + inputId).toggle(!this.checked);
});";
return $script;
}
public static function get_placeholders_init_script( $form ) {
$script = "if(typeof Placeholders != 'undefined'){
Placeholders.enable();
}";
return $script;
}
public static function get_counter_init_script( $form ) {
$script = '';
/** @var GF_Field $field */
foreach ( $form['fields'] as $field ) {
$max_length = absint( $field->maxLength );
$input_id = "input_{$form['id']}_{$field->id}";
if ( ! empty( $max_length ) && ! $field->is_administrative() ) {
$rte_enabled = $field instanceof GF_Field_Textarea && $field->is_rich_edit_enabled();
$truncate = $rte_enabled ? 'false' : 'true';
$tinymce_style = $rte_enabled ? ' ginput_counter_tinymce' : '';
$error_style = $rte_enabled ? ' ginput_counter_error' : '';
$field_script =
"if(!jQuery('#{$input_id}+.ginput_counter').length){jQuery('#{$input_id}').textareaCount(" .
" {'maxCharacterSize': {$max_length}," .
" 'originalStyle': 'ginput_counter{$tinymce_style}'," .
" 'truncate': {$truncate}," .
" 'errorStyle' : '{$error_style}'," .
" 'displayFormat' : '#input " . esc_js( __( 'of', 'gravityforms' ) ) . ' #max ' . esc_js( __( 'max characters', 'gravityforms' ) ) . "'" .
" });" . "jQuery('#{$input_id}').next('.ginput_counter').attr('aria-live','polite');}";
$script .= gf_apply_filters( array( 'gform_counter_script', $form['id'] ), $field_script, $form['id'], $input_id, $max_length, $field );
}
}
return $script;
}
public static function get_pricing_init_script( $form ) {
return "if(window[\"gformInitPriceFields\"]) jQuery(document).ready(function(){gformInitPriceFields();} );";
}
public static function get_password_strength_init_script( $form ) {
$field_script = "if(!window['gf_text']){window['gf_text'] = new Array();} window['gf_text']['password_blank'] = '" . esc_js( __( 'Strength indicator', 'gravityforms' ) ) . "'; window['gf_text']['password_mismatch'] = '" . esc_js( __( 'Mismatch', 'gravityforms' ) ) . "';window['gf_text']['password_unknown'] = '" . esc_js( __( 'Password strength unknown', 'gravityforms' ) ) . "';window['gf_text']['password_bad'] = '" . esc_js( __( 'Weak', 'gravityforms' ) ) . "'; window['gf_text']['password_short'] = '" . esc_js( __( 'Very weak', 'gravityforms' ) ) . "'; window['gf_text']['password_good'] = '" . esc_js( __( 'Medium', 'gravityforms' ) ) . "'; window['gf_text']['password_strong'] = '" . esc_js( __( 'Strong', 'gravityforms' ) ) . "';";
foreach ( $form['fields'] as $field ) {
if ( $field->type == 'password' && $field->passwordStrengthEnabled ) {
$field_id = "input_{$form['id']}_{$field->id}";
$field_script .= "gformShowPasswordStrength(\"$field_id\");";
}
}
return $field_script;
}
public static function get_input_mask_init_script( $form ) {
$script_str = '';
foreach ( $form['fields'] as $field ) {
if ( ! $field->inputMask || ! $field->inputMaskValue ) {
continue;
}
$mask = $field->inputMaskValue;
$script = "jQuery('#input_{$form['id']}_{$field->id}').mask('" . esc_js( $mask ) . "').bind('keypress', function(e){if(e.which == 13){jQuery(this).blur();} } );";
$script_str .= gf_apply_filters( array( 'gform_input_mask_script', $form['id'] ), $script, $form['id'], $field->id, $mask );
}
return $script_str;
}
public static function get_calculations_init_script( $form ) {
$formula_fields = array();
foreach ( $form['fields'] as $field ) {
if ( ! $field->enableCalculation || ! $field->calculationFormula ) {
continue;
}
$formula_fields[] = array( 'field_id' => $field->id, 'formula' => $field->calculationFormula, 'rounding' => $field->calculationRounding );
}
if ( empty( $formula_fields ) ) {
return '';
}
$script = 'if( typeof window.gf_global["gfcalc"] == "undefined" ) { window.gf_global["gfcalc"] = {}; } window.gf_global["gfcalc"][' . $form['id'] . '] = new GFCalc(' . $form['id'] . ', ' . GFCommon::json_encode( $formula_fields ) . ');';
return $script;
}
/**
* Generates a map of fields IDs and their corresponding number formats used by the GFCalc JS object for correctly
* converting field values to clean numbers.
*
* - Number fields have a 'numberFormat' setting (w/ UI).
* - Single-input product fields (i.e. 'singleproduct', 'calculation', 'price' and 'hiddenproduct') should default to
* the number format of the configured currency.
* - All other product fields will default to 'decimal_dot' for the number format.
* - All other fields will have no format (false) and inherit the format of the formula field when the formula is
* calculated.
*
* @param mixed $form
* @return string
*/
public static function get_number_formats_script( $form ) {
$number_formats = array();
$currency = RGCurrency::get_currency( GFCommon::get_currency() );
foreach ( $form['fields'] as $field ) {
// default format is false, fields with no format will inherit the format of the formula field when calculated
// price format is specified for product fields, value format is specified number fields; used in conditional
// logic to determine if field or rule value should be formatted
$price_format = false;
$value_format = false;
switch ( GFFormsModel::get_input_type( $field ) ) {
case 'number':
$value_format = $field->numberFormat ? $field->numberFormat : 'decimal_dot';
break;
case 'singleproduct':
case 'calculation':
case 'price':
case 'hiddenproduct':
case 'singleshipping':
$price_format = $currency['decimal_separator'] == ',' ? 'decimal_comma' : 'decimal_dot';
break;
default:
// we check above for all single-input product types, for all other products, assume decimal format
if ( in_array( $field->type, array( 'product', 'option', 'shipping' ) ) ) {
$price_format = 'decimal_dot';
}
}
$number_formats[ $field->id ] = array(
'price' => $price_format,
'value' => $value_format
);
}
return 'gf_global["number_formats"][' . $form['id'] . '] = ' . json_encode( $number_formats ) . ';';
}
private static function has_datepicker_field( $form ) {
if ( is_array( $form['fields'] ) ) {
foreach ( $form['fields'] as $field ) {
if ( isset( $field->fields ) && is_array( $field->fields ) ) {
return self::has_datepicker_field( array( 'fields' => $field->fields) );
}
if ( RGFormsModel::get_input_type( $field ) == 'date' && $field->dateType == 'datepicker' ) {
return true;
}
}
}
return false;
}
/**
* Determines if the supplied form has a Checkbox field.
*
* @since 2.3
* @access public
*
* @param array $form The current forms properties.
* @param bool $select_all_enabled Check if the "Select All" choices setting is enabled
*
* @return bool
*/
private static function has_checkbox_field( $form, $select_all_enabled = false ) {
if ( is_array( $form['fields'] ) ) {
foreach ( $form['fields'] as $field ) {
if ( $field->get_input_type() == 'checkbox' && ( ! $select_all_enabled || ( $select_all_enabled && $field->enableSelectAll ) ) ) {
return true;
}
}
}
return false;
}
/**
* Determines if the supplied form has a product field.
*
* @since 2.1.1.12 Updated to check the $field->type instead of the $field->inputType.
* @since Unknown
*
* @uses GFCommon::is_product_field()
*
* @param array $form The current forms properties.
*
* @return bool
*/
private static function has_price_field( $form ) {
if ( is_array( $form['fields'] ) ) {
foreach ( $form['fields'] as $field ) {
if ( GFCommon::is_product_field( $field->type ) ) {
return true;
}
}
}
return false;
}
private static function has_fileupload_field( $form ) {
if ( is_array( $form['fields'] ) ) {
foreach ( $form['fields'] as $field ) {
$input_type = RGFormsModel::get_input_type( $field );
if ( in_array( $input_type, array( 'fileupload', 'post_image' ) ) ) {
return true;
}
}
}
return false;
}
private static function has_currency_format_number_field( $form ) {
if ( is_array( $form['fields'] ) ) {
foreach ( $form['fields'] as $field ) {
$input_type = RGFormsModel::get_input_type( $field );
if ( $input_type == 'number' && $field->numberFormat == 'currency' ) {
return true;
}
}
}
return false;
}
private static function has_currency_copy_values_option( $form ) {
if ( is_array( $form['fields'] ) ) {
foreach ( $form['fields'] as $field ) {
if ( $field->enableCopyValuesOption == true ) {
return true;
}
}
}
return false;
}
/**
* Gets the language set for the recaptcha field on a form, defaults to english. Only one recaptcha field can be
* used per form.
*
* @since 2.5.6
*
* @param $form
*
* @return string
*/
private static function get_recaptcha_language( $form ) {
if ( is_array( $form['fields'] ) ) {
foreach ( $form['fields'] as $field ) {
if ( ( $field->type == 'captcha' || $field->inputType == 'captcha' ) && ! in_array( $field->captchaType, array( 'simple_captcha', 'math' ) ) ) {
return empty( $field->captchaLanguage ) ? 'en' : $field->captchaLanguage;
}
}
}
return 'en';
}
private static function has_recaptcha_field( $form ) {
if ( is_array( $form['fields'] ) ) {
foreach ( $form['fields'] as $field ) {
if ( ( $field->type == 'captcha' || $field->inputType == 'captcha' ) && ! in_array( $field->captchaType, array( 'simple_captcha', 'math' ) ) ) {
return true;
}
}
}
return false;
}
public static function has_input_mask( $form, $field = false ) {
if ( $field ) {
if ( self::has_field_input_mask( $field ) ) {
return true;
}
} else {
if ( ! is_array( $form['fields'] ) ) {
return false;
}
foreach ( $form['fields'] as $field ) {
if ( self::has_field_input_mask( $field ) ) {
return true;
}
}
}
return false;
}
/**
* Determines if the current field has an input mask.
*
* @param GF_Field $field The field to be checked.
*
* @return bool
*/
public static function has_field_input_mask( $field ) {
if ( $field->get_input_type() == 'phone' ) {
$phone_format = $field->get_phone_format();
if ( ! rgempty( 'mask', $phone_format ) ) {
return true;
}
}
if ( $field->inputMask && $field->inputMaskValue && ! $field->enablePasswordInput ) {
return true;
}
return false;
}
public static function has_calculation_field( $form ) {
if ( ! is_array( $form['fields'] ) ) {
return false;
}
foreach ( $form['fields'] as $field ) {
/* @var $field GF_Field */
if ( $field->has_calculation() ) {
return true;
}
}
return false;
}
/***
* Determines if this form will have support for JS merge tags
*
* @since 2.4
*
* @param array $form The current form object
*
* @return bool True if the form supports JS merge tags. False otherwise.
*/
public static function has_js_merge_tag( $form ){
/***
* Determines if javascript merge tags are supported. Defaults to false (not supported).
*
* @since 2.4
*
* @param bool $has_js_merge_tags Value to be filtered. Return true to add support for Javascript merge tags. Return false to disable it.
* @param array $form The current Form Object
*/
$has_js_merge_tags = gf_apply_filters( array( 'gform_has_js_merge_tag', $form['id'] ), false, $form );
return $has_js_merge_tags;
}
//Getting all fields that have a rule based on the specified field id
public static function get_conditional_logic_fields( $form, $fieldId ) {
$fields = array();
//adding submit button field if enabled
if ( isset( $form['button']['conditionalLogic'] ) ) {
$fields[] = 0;
}
foreach ( $form['fields'] as $field ) {
if ( $field->type != 'page' && ! empty( $field->conditionalLogic ) ) {
foreach ( $field->conditionalLogic['rules'] as $rule ) {
if ( intval( $rule['fieldId'] ) == $fieldId ) {
$fields[] = floatval( $field->id );
//if field is a section, add all fields in the section that have conditional logic (to support nesting)
if ( $field->type == 'section' ) {
$section_fields = GFCommon::get_section_fields( $form, $field->id );
foreach ( $section_fields as $section_field ) {
if ( ! empty( $section_field->conditionalLogic ) ) {
$fields[] = floatval( $section_field->id );
}
}
}
break;
}
}
}
//adding fields with next button logic
if ( ! empty( $field->nextButton['conditionalLogic'] ) ) {
foreach ( $field->nextButton['conditionalLogic']['rules'] as $rule ) {
if ( intval( $rule['fieldId'] ) == $fieldId && ! in_array( $fieldId, $fields ) ) {
$fields[] = floatval( $field->id );
break;
}
}
}
}
return $fields;
}
/**
* @param GF_Field $field
* @param string $value
* @param bool $force_frontend_label
* @param null $form
* @param null $field_values
*
* @return string|string[]|void
*/
public static function get_field( $field, $value = '', $force_frontend_label = false, $form = null, $field_values = null ) {
$is_form_editor = GFCommon::is_form_editor();
$is_entry_detail = GFCommon::is_entry_detail();
$is_admin = $is_form_editor || $is_entry_detail;
$custom_class = $is_admin ? esc_attr( $field->cssClass ) : esc_attr( self::convert_legacy_class( $form, $field->cssClass ) );
$form_id = (int) rgar( $form, 'id' );
if ( $field->type == 'page' ) {
if ( $is_entry_detail ) {
return; //ignore page breaks in the entry detail page
} else if ( ! $is_form_editor ) {
$previous_button_alt = rgempty( 'imageAlt', $field->previousButton ) ? __( 'Previous Page', 'gravityforms' ) : $field->previousButton['imageAlt'];
$previous_button = $field->pageNumber == 2 ? '' : self::get_form_button( $form_id, "gform_previous_button_{$form_id}_{$field->id}", $field->previousButton, __( 'Previous', 'gravityforms' ), 'gform_previous_button', $previous_button_alt, $field->pageNumber - 2 );
if ( ! empty( $previous_button ) ) {
$previous_button = gf_apply_filters( array( 'gform_previous_button', $form_id ), $previous_button, $form );
}
$next_button_alt = rgempty( 'imageAlt', $field->nextButton ) ? __( 'Next Page', 'gravityforms' ) : $field->nextButton['imageAlt'];
$next_button = self::get_form_button( $form_id, "gform_next_button_{$form_id}_{$field->id}", $field->nextButton, __( 'Next', 'gravityforms' ), 'gform_next_button', $next_button_alt, $field->pageNumber );
$next_button = gf_apply_filters( array( 'gform_next_button', $form_id ), $next_button, $form );
$save_button = rgars( $form, 'save/enabled' ) ? self::get_form_button( $form_id, "gform_save_{$form_id}_{$field->pageNumber}", $form['save']['button'], rgars( $form, 'save/button/text' ), 'gform_save_link', rgars( $form, 'save/button/text' ), 0, "jQuery(\"#gform_save_{$form_id}\").val(1);" ) : '';
/**
* Filters the save and continue link allowing the tag to be customized
*
* @since 2.0.7.7
*
* @param string $save_button The string containing the save and continue link markup.
* @param array $form The Form object associated with the link.
*/
$save_button = apply_filters( 'gform_savecontinue_link', $save_button, $form );
$save_button = apply_filters( "gform_savecontinue_link_{$form_id}", $save_button, $form );
$style = self::is_page_active( $form_id, $field->pageNumber ) ? '' : "style='display:none;'";
$custom_class = ! empty( $custom_class ) ? " {$custom_class}" : '';
$tag = GFCommon::is_legacy_markup_enabled( $form ) ? 'ul' : 'div';
$html = "</{$tag}>
</div>
<div class='gform_page_footer {$form['labelPlacement']}'>
{$previous_button} {$next_button} {$save_button}
</div>
</div>
<div id='gform_page_{$form['id']}_{$field->pageNumber}' class='gform_page{$custom_class}' {$style}>
<div class='gform_page_fields'>
<{$tag} id='gform_fields_{$form['id']}_{$field->pageNumber}' class='" . GFCommon::get_ul_classes( $form ) . "'>";
return $html;
}
}
if ( ! $is_admin && $field->visibility == 'administrative' ) {
if ( $field->allowsPrepopulate ) {
$field->inputType = 'adminonly_hidden';
} else {
return;
}
}
$id = $field->id;
$input_type = GFFormsModel::get_input_type( $field );
$error_class = $field->failed_validation ? 'gfield_error' : '';
$admin_only_class = $field->visibility == 'administrative' ? 'field_admin_only' : ''; // maintain for backwards compat
if ( $is_admin ) {
$visibility_class = 'gfield_visibility_visible';
} else {
$visibility_class = sprintf( 'gfield_visibility_%s', ( $field->visibility ? $field->visibility : 'visible' ) );
}
$selectable_class = $is_admin ? 'selectable' : '';
$hidden_class = in_array( $input_type, array( 'hidden', 'hiddenproduct' ) ) ? 'gform_hidden' : '';
$section_class = $field->type == 'section' ? 'gsection' : '';
$page_class = $field->type == 'page' ? 'gpage' : '';
$html_block_class = $field->type == 'html' ? 'gfield_html' : '';
$html_formatted_class = $field->type == 'html' && ! $field->disableMargins ? 'gfield_html_formatted' : '';
$html_no_follows_desc_class = $field->type == 'html' && ! $is_admin && ! self::prev_field_has_description( $form, $field->id ) ? 'gfield_no_follows_desc' : '';
$calculation_class = $input_type == 'calculation' || ( $input_type == 'number' && $field->has_calculation() ) ? 'gfield_calculation' : '';
$product_suffix = "_{$form_id}_" . $field->productField;
$option_class = $field->type == 'option' ? "gfield_price gfield_price{$product_suffix} gfield_option{$product_suffix}" : '';
$quantity_class = $field->type == 'quantity' ? "gfield_price gfield_price{$product_suffix} gfield_quantity gfield_quantity{$product_suffix}" : '';
$total_class = $field->type == 'total' ? "gfield_price gfield_price{$product_suffix} gfield_total gfield_total{$product_suffix}" : '';
$shipping_class = $field->type == 'shipping' ? "gfield_price gfield_shipping gfield_shipping_{$form_id}" : '';
$product_class = $field->type == 'product' ? "gfield_price gfield_price_{$form_id}_{$field->id} gfield_product_{$form_id}_{$field->id}" : '';
$hidden_product_class = $input_type == 'hiddenproduct' ? 'gfield_hidden_product' : '';
$donation_class = $field->type == 'donation' ? "gfield_price gfield_price_{$form_id}_{$field->id} gfield_donation_{$form_id}_{$field->id}" : '';
$required_class = $field->isRequired ? 'gfield_contains_required' : '';
$creditcard_warning_class = $input_type == 'creditcard' && ! GFCommon::is_ssl() ? 'gfield_creditcard_warning' : '';
$submit_width_class = $field->type == 'submit' && $field->submitWidth == 'full' ? 'width-full' : '';
$form_sublabel_setting = rgempty( 'subLabelPlacement', $form ) ? 'below' : $form['subLabelPlacement'];
$sublabel_setting = ! isset( $field->subLabelPlacement ) || empty( $field->subLabelPlacement ) ? $form_sublabel_setting : $field->subLabelPlacement;
$sublabel_class = "field_sublabel_{$sublabel_setting}";
$form_description_setting = rgempty( 'descriptionPlacement', $form ) ? 'below' : $form['descriptionPlacement'];
$description_setting = ! isset( $field->descriptionPlacement ) || empty( $field->descriptionPlacement ) ? $form_description_setting : $field->descriptionPlacement;
$description_class = "field_description_{$description_setting}";
$field_setting_label_placement = $field->labelPlacement;
$label_placement = empty( $field_setting_label_placement ) ? '' : $field_setting_label_placement;
$admin_hidden_class = ( $is_admin && $field->visibility == 'hidden' ) ? 'admin-hidden' : '';
$span_class = $field->get_css_grid_class( $form );
$css_class = "$selectable_class gfield $span_class $error_class $section_class $admin_only_class $custom_class $hidden_class $html_block_class $html_formatted_class $html_no_follows_desc_class $option_class $quantity_class $product_class $total_class $donation_class $shipping_class $page_class $required_class $hidden_product_class $creditcard_warning_class $submit_width_class $calculation_class $sublabel_class $description_class $label_placement $visibility_class $admin_hidden_class";
$css_class = preg_replace( '/\s+/', ' ', $css_class ); //removing extra spaces
/*
* This filter is applied twice because fields may either by using it to modify the collection of HTML classes
* by removing elements, or by providing their own custom HTML classes, as well. We want to capture any
* custom classes which are provided, but cannot guarantee that the $css_class string has been manipulated
* in a reliable way. As of 2.5, the $field_classes value is used by the Settings API to apply those classes
* to the settings sidebar panel while a field is active.
*/
$field_classes = gf_apply_filters( array( 'gform_field_css_class', $form_id ), '', $field, $form );
$css_class = gf_apply_filters( array( 'gform_field_css_class', $form_id ), trim( $css_class ), $field, $form );
$style = '';
$field_id = $is_admin || empty( $form ) ? "field_$id" : 'field_' . $form_id . "_$id";
$field_content = self::get_field_content( $field, $value, $force_frontend_label, $form_id, $form );
$css_class = esc_attr( $css_class );
$field_container = $field->get_field_container(
array(
'id' => $field_id,
'class' => $css_class,
'style' => $style,
'data-field-class' => trim( $field_classes ),
),
$form
);
/**
* Modify the markup used for the field container.
*
* @since 1.8.9
*
* @param string $field_container The field container markup. {FIELD_CONTENT} placeholder indicates where the markup for the field content should be located.
* @param GF_Field $field The Field currently being processed.
* @param array $form The Form currently being processed.
* @param string $css_class The CSS classes to be assigned to the container element.
* @param string $style Holds the conditional logic display style. Deprecated in 1.9.4.4.
* @param string $field_content The markup for the field content: label, description, inputs, etc.
*/
if ( rgar( $field, 'type' ) !== 'submit' ) {
$field_container = gf_apply_filters( array( 'gform_field_container', $form_id, $field->id ), $field_container, $field, $form, $css_class, $style, $field_content );
}
$field_markup = str_replace( '{FIELD_CONTENT}', $field_content, $field_container );
return $field_markup;
}
private static function prev_field_has_description( $form, $field_id ) {
if ( ! is_array( $form['fields'] ) ) {
return false;
}
$prev = null;
foreach ( $form['fields'] as $field ) {
if ( $field->id == $field_id ) {
return $prev != null && ! empty( $prev->description );
}
$prev = $field;
}
return false;
}
/**
* @param GF_Field $field
* @param string $value
* @param bool $force_frontend_label
* @param int $form_id
* @param null|array $form
*
* @return string
*/
public static function get_field_content( $field, $value = '', $force_frontend_label = false, $form_id = 0, $form = null ) {
$field_label = $field->get_field_label( $force_frontend_label, $value );
$admin_buttons = $field->get_admin_buttons();
$input_type = GFFormsModel::get_input_type( $field );
$is_form_editor = GFCommon::is_form_editor();
$is_entry_detail = GFCommon::is_entry_detail();
$is_admin = $is_form_editor || $is_entry_detail;
if ( $input_type == 'adminonly_hidden' ) {
$field_content = ! $is_admin ? '{FIELD}' : sprintf( "%s<label class='gfield_label' >%s</label>{FIELD}", $admin_buttons, esc_html( $field_label ) );
} else {
$field_content = $field->get_field_content( $value, $force_frontend_label, $form );
}
$value = $field->get_value_default_if_empty( $value );
$field_content = str_replace( '{FIELD}', GFCommon::get_field_input( $field, $value, 0, $form_id, $form ), $field_content );
$field_content = gf_apply_filters( array( 'gform_field_content', $form_id, $field->id ), $field_content, $field, $value, 0, $form_id );
return $field_content;
}
public static function get_progress_bar( $form, $page, $confirmation_message = '' ) {
$form_id = $form['id'];
$progress_complete = false;
$progress_bar = '';
$page_count = self::get_max_page_number( $form );
$current_page = $page;
$page_name = rgars( $form['pagination'], sprintf( 'pages/%d', $current_page - 1 ) );
$page_name = ! empty( $page_name ) ? ' - ' . $page_name : '';
$style = $form['pagination']['style'];
$color = $style == 'custom' ? " color:{$form['pagination']['color']};" : '';
$bgcolor = $style == 'custom' ? " background-color:{$form['pagination']['backgroundColor']};" : '';
if ( ! empty( $confirmation_message ) ) {
$progress_complete = true;
}
//check admin setting for whether the progress bar should start at zero
$start_at_zero = rgars( $form, 'pagination/display_progressbar_on_confirmation' );
//check for filter
$start_at_zero = apply_filters( 'gform_progressbar_start_at_zero', $start_at_zero, $form );
$progressbar_page_count = $start_at_zero ? $current_page - 1 : $current_page;
$percent = ! $progress_complete ? floor( ( ( $progressbar_page_count ) / $page_count ) * 100 ) . '%' : '100%';
$percent_number = ! $progress_complete ? floor( ( ( $progressbar_page_count ) / $page_count ) * 100 ) . '' : '100';
if ( $progress_complete ) {
$wrapper_css_class = GFCommon::get_browser_class() . ' gform_wrapper';
//add on surrounding wrapper class when confirmation page
$progress_bar = "<div class='{$wrapper_css_class}' id='gform_wrapper_$form_id' >";
$page_name = ! empty( $form['pagination']['progressbar_completion_text'] ) ? $form['pagination']['progressbar_completion_text'] : '';
}
$progress_bar_title_open = GFCommon::is_legacy_markup_enabled( $form ) ? '<h3 class="gf_progressbar_title">' : '<p class="gf_progressbar_title">';
$progress_bar_title_close = GFCommon::is_legacy_markup_enabled( $form ) ? '</h3>' : '</p>';
$progress_bar .= "
<div id='gf_progressbar_wrapper_{$form_id}' class='gf_progressbar_wrapper'>
{$progress_bar_title_open}";
$progress_bar .= ! $progress_complete ? esc_html__( 'Step', 'gravityforms' ) . " <span class='gf_step_current_page'>{$current_page}</span> " . esc_html__( 'of', 'gravityforms' ) . " <span class='gf_step_page_count'>{$page_count}</span>{$page_name}" : "{$page_name}";
$progress_bar .= "
{$progress_bar_title_close}
<div class='gf_progressbar gf_progressbar_{$style}' aria-hidden='true'>
<div class='gf_progressbar_percentage percentbar_{$style} percentbar_{$percent_number}' style='width:{$percent};{$color}{$bgcolor}'><span>{$percent}</span></div>
</div></div>";
//close div for surrounding wrapper class when confirmation page
$progress_bar .= $progress_complete ? $confirmation_message . '</div>' : '';
/**
* Filter the mulit-page progress bar markup.
*
* @since 2.0
*
* @param string $progress_bar Progress bar markup as an HTML string.
* @param array $form Current form object.
* @param string $confirmation_message The confirmation message to be displayed on the confirmation page.
*
* @see https://docs.gravityforms.com/gform_progress_bar/
*/
$progress_bar = apply_filters( 'gform_progress_bar', $progress_bar, $form, $confirmation_message );
$progress_bar = apply_filters( "gform_progress_bar_{$form_id}", $progress_bar, $form, $confirmation_message );
return $progress_bar;
}
public static function get_progress_steps( $form, $page ) {
$progress_steps = "<div id='gf_page_steps_{$form['id']}' class='gf_page_steps'>";
$pages = isset( $form['pagination']['pages'] ) ? $form['pagination']['pages'] : array();
for ( $i = 0, $count = sizeof( $pages ); $i < $count; $i ++ ) {
$step_number = $i + 1;
$active_class = $step_number == $page ? ' gf_step_active' : '';
$first_class = $i == 0 ? ' gf_step_first' : '';
$last_class = $i + 1 == $count ? ' gf_step_last' : '';
$complete_class = $step_number < $page ? ' gf_step_completed' : '';
$previous_class = $step_number + 1 == $page ? ' gf_step_previous' : '';
$next_class = $step_number - 1 == $page ? ' gf_step_next' : '';
$pending_class = $step_number > $page ? ' gf_step_pending' : '';
$classes = 'gf_step' . $active_class . $first_class . $last_class . $complete_class . $previous_class . $next_class . $pending_class;
$classes = GFCommon::trim_all( $classes );
$progress_steps .= "<div id='gf_step_{$form['id']}_{$step_number}' class='{$classes}'><span class='gf_step_number'>{$step_number}</span><span class='gf_step_label'>{$pages[ $i ]}</span></div>";
}
$progress_steps .= "</div>";
/**
* Filter the multi-page progress steps markup.
*
* @since 2.0-beta-3
*
* @param string $progress_steps HTML string containing the progress steps markup.
* @param array $form The current form object.
* @param int $page The current page number.
*
* @see https://docs.gravityforms.com/gform_progress_steps/
*/
$progress_steps = apply_filters( 'gform_progress_steps', $progress_steps, $form, $page );
$progress_steps = apply_filters( "gform_progress_steps_{$form['id']}", $progress_steps, $form, $page );
return $progress_steps;
}
/**
* Validates the form's entry limit settings. Returns the entry limit message if entry limit exceeded.
*
* @param array $form current GF form object
*
* @return string If entry limit exceeded returns entry limit setting.
*/
public static function validate_entry_limit( $form ) {
// If form has a limit of entries, check current entry count
if ( rgar( $form, 'limitEntries' ) ) {
$period = rgar( $form, 'limitEntriesPeriod' );
$range = self::get_limit_period_dates( $period );
$search_criteria = array(
'status' => 'active',
'start_date' => $range['start_date'],
'end_date' => $range['end_date'],
);
$entry_count = GFAPI::count_entries( $form['id'], $search_criteria );
if ( $entry_count >= $form['limitEntriesCount'] ) {
$error = empty( $form['limitEntriesMessage'] ) ? "<div class='gf_submission_limit_message'><p>" . esc_html__( 'Sorry. This form is no longer accepting new submissions.', 'gravityforms' ) . '</p></div>' : '<p>' . GFCommon::gform_do_shortcode( $form['limitEntriesMessage'] ) . '</p>';
self::set_submission_if_null( $form['id'], 'form_restriction_error', $error );
return $error;
}
}
}
public static function validate_form_schedule( $form ) {
//If form has a schedule, make sure it is within the configured start and end dates
if ( rgar( $form, 'scheduleForm' ) ) {
$local_time_start = sprintf( '%s %02d:%02d %s', $form['scheduleStart'], $form['scheduleStartHour'], $form['scheduleStartMinute'], $form['scheduleStartAmpm'] );
$local_time_end = sprintf( '%s %02d:%02d %s', $form['scheduleEnd'], $form['scheduleEndHour'], $form['scheduleEndMinute'], $form['scheduleEndAmpm'] );
$timestamp_start = strtotime( $local_time_start . ' +0000' );
$timestamp_end = strtotime( $local_time_end . ' +0000' );
$now = current_time( 'timestamp' );
if ( ! empty( $form['scheduleStart'] ) && $now < $timestamp_start ) {
$error = empty( $form['schedulePendingMessage'] ) ? '<p>' . esc_html__( 'This form is not yet available.', 'gravityforms' ) . '</p>' : '<p>' . GFCommon::gform_do_shortcode( $form['schedulePendingMessage'] ) . '</p>';
self::set_submission_if_null( $form['id'], 'form_restriction_error', $error );
return $error;
} elseif ( ! empty( $form['scheduleEnd'] ) && $now > $timestamp_end ) {
$error = empty( $form['scheduleMessage'] ) ? '<p>' . esc_html__( 'Sorry. This form is no longer available.', 'gravityforms' ) . '</p>' : '<p>' . GFCommon::gform_do_shortcode( $form['scheduleMessage'] ) . '</p>';
self::set_submission_if_null( $form['id'], 'form_restriction_error', $error );
return $error;
}
}
}
/**
* Populates the form confirmation property with the confirmation to be used for the current submission.
*
* @since unknown
*
* @param array $form The form being processed.
* @param null|array $entry Null, the entry being processed, or an empty array when the submission fails honeypot validation.
* @param string $event The confirmation event or an empty string.
*
* @return array
*/
public static function update_confirmation( $form, $entry = null, $event = '' ) {
if ( ( is_array( $entry ) && ( empty( $entry ) || rgar( $entry, 'status' ) === 'spam' ) ) || empty( $form['confirmations'] ) || ! is_array( $form['confirmations'] ) ) {
$form['confirmation'] = GFFormsModel::get_default_confirmation();
return $form;
}
if ( ! empty( $event ) ) {
$confirmations = wp_filter_object_list( $form['confirmations'], array( 'event' => $event ) );
} else {
$confirmations = $form['confirmations'];
}
// if there is only one confirmation, don't bother with the conditional logic, just return it
// this is here mostly to avoid the semi-costly GFFormsModel::create_lead() function unless we really need it
if ( count( $confirmations ) <= 1 ) {
$form['confirmation'] = reset( $confirmations );
return $form;
}
if ( is_null( $entry ) ) {
$entry = GFFormsModel::create_lead( $form );
}
foreach ( $confirmations as $confirmation ) {
if ( rgar( $confirmation, 'event' ) != $event ) {
continue;
}
if ( rgar( $confirmation, 'isDefault' ) ) {
continue;
}
if ( isset( $confirmation['isActive'] ) && ! $confirmation['isActive'] ) {
continue;
}
$logic = rgar( $confirmation, 'conditionalLogic' );
if ( GFCommon::evaluate_conditional_logic( $logic, $form, $entry ) ) {
$form['confirmation'] = $confirmation;
return $form;
}
}
$filtered_list = wp_filter_object_list( $form['confirmations'], array( 'isDefault' => true ) );
$form['confirmation'] = reset( $filtered_list );
return $form;
}
public static function process_send_resume_link() {
$form_id = rgpost( 'gform_send_resume_link' );
$form_id = absint( $form_id );
$email = rgpost( 'gform_resume_email' );
$resume_token = rgpost( 'gform_resume_token' );
$resume_token = sanitize_key( $resume_token );
if ( empty( $form_id ) || empty( $email ) || empty( $resume_token ) || ! GFCommon::is_valid_email( $email ) ) {
return;
}
$form = GFFormsModel::get_form_meta( $form_id );
if ( empty( $form ) ) {
return;
}
if ( rgar( $form, 'requireLogin' ) ) {
if ( ! is_user_logged_in() ) {
wp_die();
}
check_admin_referer( 'gform_send_resume_link', '_gform_send_resume_link_nonce' );
}
$draft_submission = GFFormsModel::get_draft_submission_values( $resume_token );
$submission = json_decode( $draft_submission['submission'], true );
$partial_entry = $submission['partial_entry'];
$notifications_to_send = GFCommon::get_notifications_to_send( 'form_save_email_requested', $form, $partial_entry );
$log_notification_event = empty( $notifications_to_send ) ? 'No notifications to process' : 'Processing notifications';
GFCommon::log_debug( "GFFormDisplay::process_send_resume_link(): {$log_notification_event} for form_save_email_requested event." );
foreach ( $notifications_to_send as $notification ) {
if ( isset( $notification['isActive'] ) && ! $notification['isActive'] ) {
GFCommon::log_debug( "GFFormDisplay::process_send_resume_link(): Notification is inactive, not processing notification (#{$notification['id']} - {$notification['name']})." );
continue;
}
if ( $notification['toType'] == 'hidden' ) {
$notification['to'] = $email;
}
$notification['message'] = self::replace_save_variables( $notification['message'], $form, $resume_token, $email );
GFCommon::send_notification( $notification, $form, $partial_entry );
}
GFFormsModel::add_email_to_draft_sumbmission( $resume_token, $email );
}
public static function replace_save_variables( $text, $form, $resume_token, $email = null ) {
$resume_token = sanitize_key( $resume_token );
$form_id = intval( $form['id'] );
/**
* Filters the 'Save and Continue' URL to be used with a partial entry submission.
*
* @since 1.9
*
* @param string $resume_url The URL to be used to resume the partial entry.
* @param array $form The Form Object.
* @param string $resume_token The token that is used within the URL.
* @param string $email The email address associated with the partial entry.
*/
$resume_url = apply_filters( 'gform_save_and_continue_resume_url', add_query_arg( array( 'gf_token' => $resume_token ), GFFormsModel::get_current_page_url() ), $form, $resume_token, $email );
$resume_url = esc_url( $resume_url );
$resume_link = "<a href=\"{$resume_url}\" class='resume_form_link'>{$resume_url}</a>";
$text = str_replace( '{save_link}', $resume_link, $text );
$text = str_replace( '{save_token}', $resume_token, $text );
$text = str_replace( '{save_url}', $resume_url, $text );
$email_esc = esc_attr( $email );
$text = str_replace( '{save_email}', $email_esc, $text );
$resume_submit_button_text = esc_html__( 'Send Link', 'gravityforms' );
$resume_email_validation_message = esc_html__( 'Please enter a valid email address.', 'gravityforms' );
$email_input_label = esc_html__( 'Email Address', 'gravityforms' );
// The {save_email_input} accepts shortcode-style options button_text and validation_message. E.g.,
// {save_email_input: button_text="Send the link to my email address" validation_message="The link couldn't be sent because the email address is not valid."}
preg_match_all( '/\{save_email_input:(.*?)\}/', $text, $matches, PREG_SET_ORDER );
if ( is_array( $matches ) && isset( $matches[0] ) && isset( $matches[0][1] ) ) {
$options_string = isset( $matches[0][1] ) ? $matches[0][1] : '';
$options = shortcode_parse_atts( $options_string );
if ( isset( $options['button_text'] ) ) {
$resume_submit_button_text = $options['button_text'];
}
if ( isset( $options['validation_message'] ) ) {
$resume_email_validation_message = $options['validation_message'];
}
if ( ! empty( $options['placeholder'] ) ) {
$email_input_placeholder = esc_attr( $options['placeholder'] );
}
$full_tag = $matches[0][0];
$text = str_replace( $full_tag, '{save_email_input}', $text );
}
$action = esc_url( remove_query_arg( 'gf_token' ) );
$ajax = isset( $_POST['gform_ajax'] );
$anchor = self::get_anchor( $form, $ajax );
$action .= $anchor['id'];
$html_input_type = RGFormsModel::is_html5_enabled() ? 'email' : 'text';
$resume_token = esc_attr( $resume_token );
if ( ! is_null( $email ) && ! GFCommon::is_valid_email( $email ) ) {
$validation_message = $resume_email_validation_message;
} else {
$validation_message = '';
}
$nonce_input = '';
if ( rgar( $form, 'requireLogin' ) ) {
$nonce_input = wp_nonce_field( 'gform_send_resume_link', '_gform_send_resume_link_nonce', true, false );
}
$target = $ajax ? "target='gform_ajax_frame_{$form_id}'" : '';
$ajax_fields = '';
if ( $ajax ) {
$ajax_fields = "<input type='hidden' name='gform_ajax' value='" . esc_attr( "form_id={$form_id}&amp;title=1&amp;description=1&amp;tabindex=1" ) . "' />";
$ajax_fields .= "<input type='hidden' name='gform_field_values' value='' />";
// Adding inputs required by GFFormDisplay::is_submit_form_id_valid() to verify the Ajax submission should be processed.
$ajax_fields .= "<input type='hidden' class='gform_hidden' name='is_submit_{$form_id}' value='1' />";
$ajax_fields .= "<input type='hidden' class='gform_hidden' name='gform_submit' value='{$form_id}' />";
}
$ajax_submit = $ajax ? "onclick='jQuery(\"#gform_{$form_id}\").trigger(\"submit\",[true]);'" : '';
if ( GFCommon::is_legacy_markup_enabled( $form_id ) ) {
$resume_form = "<div class='form_saved_message_emailform'>
<form action='{$action}' method='POST' id='gform_{$form_id}' {$target}>
{$ajax_fields}
<label for='gform_resume_email screen-reader-text' class='gform_resume_email_label gfield_label' aria-describedby='email-validation-error'>{$email_input_label}</label>
<input type='{$html_input_type}' name='gform_resume_email' value='{$email_esc}' id='gform_resume_email' placeholder='{$email_input_label}' aria-describedby='email-validation-error'/>
<input type='hidden' name='gform_resume_token' value='{$resume_token}' />
<input type='hidden' name='gform_send_resume_link' value='{$form_id}' />
<input type='hidden' class='gform_hidden' name='is_submit_{$form_id}' value='1' />
<input type='hidden' class='gform_hidden' name='gform_submit' value='{$form_id}' />
<input type='submit' name='gform_send_resume_link_button' id='gform_send_resume_link_button_{$form_id}' value='{$resume_submit_button_text}' {$ajax_submit}/>
<div class='gform_validation_message' id='email-validation-error' aria-live='assertive'>{$validation_message}</div>
{$nonce_input}
</form>
</div>";
} else {
$resume_form = "<div class='form_saved_message_emailform'>
<form action='{$action}' method='POST' id='gform_{$form_id}' {$target}>
<div class='gform-body gform_body'>
<div id='gform_fields_{$form_id}' class='gform_fields'>
{$ajax_fields}
<div class='gfield'>
<label for='gform_resume_email' class='gform_resume_email_label gfield_label' aria-describedby='email-validation-error'>{$email_input_label}</label>
<div class='ginput_container ginput_container_text'>
<input type='{$html_input_type}' name='gform_resume_email' class='medium' id='gform_resume_email' value='{$email_esc}' />
<div class='gform_validation_message' id='email-validation-error' aria-live='assertive'>{$validation_message}</div>
</div>
</div>
<input type='hidden' name='gform_resume_token' value='{$resume_token}' />
<input type='hidden' name='gform_send_resume_link' value='{$form_id}' />
<input type='hidden' class='gform_hidden' name='is_submit_{$form_id}' value='1' />
<input type='hidden' class='gform_hidden' name='gform_submit' value='{$form_id}' />
<input type='submit' name='gform_send_resume_link_button' id='gform_send_resume_link_button_{$form_id}' value='{$resume_submit_button_text}' {$ajax_submit}/>
{$nonce_input}
</div>
</div>
</form>
</div>";
}
$text = str_replace( '{save_email_input}', $resume_form, $text );
return $text;
}
public static function handle_save_email_confirmation( $form, $ajax ) {
$resume_email = $_POST['gform_resume_email'];
if ( ! GFCommon::is_valid_email( $resume_email ) ) {
GFCommon::log_debug( 'GFFormDisplay::handle_save_email_confirmation(): Invalid email address: ' . $resume_email );
return new WP_Error( 'invalid_email' );
}
$resume_token = $_POST['gform_resume_token'];
$submission_details = GFFormsModel::get_draft_submission_values( $resume_token );
$submission_json = $submission_details['submission'];
$submission = json_decode( $submission_json, true );
$entry = $submission['partial_entry'];
$form = self::update_confirmation( $form, $entry, 'form_save_email_sent' );
$confirmation_message = rgar( $form['confirmation'], 'message' );
$confirmation = '<div class="form_saved_message_sent" role="alert"><span>' . $confirmation_message . '</span></div>';
$nl2br = rgar( $form['confirmation'], 'disableAutoformat' ) ? false : true;
$save_email_confirmation = self::replace_save_variables( $confirmation, $form, $resume_token, $resume_email );
$save_email_confirmation = GFCommon::replace_variables( $save_email_confirmation, $form, $entry, false, true, $nl2br );
$save_email_confirmation = GFCommon::gform_do_shortcode( $save_email_confirmation );
$save_email_confirmation = self::maybe_sanitize_confirmation_message( $save_email_confirmation );
$anchor = self::get_anchor( $form, $ajax );
$save_email_confirmation = $anchor['tag'] . $save_email_confirmation;
if ( $ajax ) {
$save_email_confirmation = self::get_ajax_postback_html( $save_email_confirmation );
}
GFCommon::log_debug( 'GFFormDisplay::handle_save_email_confirmation(): Confirmation => ' . print_r( $save_email_confirmation, true ) );
return $save_email_confirmation;
}
public static function handle_save_confirmation( $form, $resume_token, $confirmation_message, $ajax ) {
$resume_email = isset( $_POST['gform_resume_email'] ) ? $_POST['gform_resume_email'] : null;
$confirmation_message = self::maybe_sanitize_confirmation_message( $confirmation_message );
$confirmation_message = self::replace_save_variables( $confirmation_message, $form, $resume_token, $resume_email );
$confirmation_message = GFCommon::gform_do_shortcode( $confirmation_message );
$confirmation_message = "<div class='form_saved_message'><span>" . $confirmation_message . '</span></div>';
$anchor = self::get_anchor( $form, $ajax );
$confirmation_message = $anchor['tag'] . $confirmation_message;
$form_id = absint( $form['id'] );
$wrapper_css_class = GFCommon::get_browser_class() . ' gform_wrapper';
$confirmation_message = "<div class='{$wrapper_css_class}' id='gform_wrapper_{$form_id}'>" . $confirmation_message . '</div>';
if ( $ajax ) {
$confirmation_message = self::get_ajax_postback_html( $confirmation_message );
}
GFCommon::log_debug( 'GFFormDisplay::handle_save_confirmation(): Confirmation => ' . print_r( $confirmation_message, true ) );
return $confirmation_message;
}
/**
* Insert review page into form.
*
* @since Unknown
* @access public
*
* @param array $form The current Form object
* @param array $review_page The review page
*
* @return array $form
*/
public static function insert_review_page( $form, $review_page ) {
/* Get field ID and page number for new fields. */
$new_field_id = self::get_max_field_id( $form ) + 1;
$page_number = self::get_max_page_number( $form );
$page_number = $page_number == 0 ? 2 : $page_number+1;
/* Create new Page field for review page. */
$review_page_break = new GF_Field_Page();
$review_page_break->id = $new_field_id;
$review_page_break->pageNumber = $page_number;
$review_page_break->nextButton = rgar( $review_page, 'nextButton' );
$review_page_break->cssClass = 'gform_review_page ' . rgar( $review_page, 'cssClass', '' );
/* Add review page break field to form. */
$form['fields'][] = $review_page_break;
/* Create new HTML field for review page. */
$review_page_field = new GF_Field_HTML();
$review_page_field->id = ++$new_field_id;
$review_page_field->pageNumber = $page_number;
$review_page_field->content = rgar( $review_page, 'content' );
/* Add review page field to form. */
$form['fields'][] = $review_page_field;
/* Configure the last page previous button */
$form['lastPageButton'] = rgar( $review_page, 'previousButton' );
return $form;
}
/**
* Get the anchor config for the current form.
*
* @since 2.2.2.1
*
* @param array $form The current Form object.
* @param bool $ajax Indicates if AJAX is enabled for the current form.
*
* @return array
*/
public static function get_anchor( $form, $ajax ) {
$form_id = absint( $form['id'] );
$anchor = $ajax || self::has_pages( $form ) ? true : false;
/**
* Allow the anchor to be enabled/disabled or set to a scroll distance.
*
* @since 1.9.17.12 Added the $form parameter.
* @since Unknown
*
* @param bool|int $anchor Is the form anchor enabled? True when ajax enabled or when the form has multiple pages.
* @param array $form The current Form object.
*/
$anchor = gf_apply_filters( array( 'gform_confirmation_anchor', $form_id ), $anchor, $form );
return array(
'scroll' => $anchor,
'tag' => $anchor !== false ? "<div id='gf_{$form_id}' class='gform_anchor' tabindex='-1'></div>" : '',
'id' => $anchor !== false ? "#gf_{$form_id}" : ''
);
}
/**
* Validates the posted input values from the form footer are for the same form, that it exists, is active, and not trashed.
*
* @since 2.4.18
*
* @param null|int $ajax_form_id Null or the form ID parsed from the gform_ajax input value.
*
* @return bool|int False or the ID of the form being processed.
*/
public static function is_submit_form_id_valid( $ajax_form_id = null ) {
if ( empty( $_POST['gform_submit'] ) ) {
return false;
}
$form_id = absint( $_POST['gform_submit'] );
if ( $form_id === 0 || rgpost( 'is_submit_' . $form_id ) !== '1' ) {
return false;
}
if ( $ajax_form_id !== null && absint( $ajax_form_id ) !== $form_id ) {
return false;
}
$form_info = GFFormsModel::get_form( $form_id );
if ( ! $form_info || ! $form_info->is_active || $form_info->is_trash ) {
return false;
}
return $form_id;
}
/**
* Returns the HTML for the ajax postback.
*
* @since 2.4.18
*
* @param string $body_content The content to be included in the body of the ajax postback.
*
* @return string
*/
public static function get_ajax_postback_html( $body_content ) {
$ajax_iframe_content = "<!DOCTYPE html><html><head><meta charset='UTF-8' /></head><body class='GF_AJAX_POSTBACK'>" . $body_content . '</body></html>';
/**
* Allows the content of the iframe for the ajax postback to be overridden.
*
* @since unknown
*
* @param string $ajax_iframe_content The HTML to be returned for the ajax postback.
*/
return apply_filters( 'gform_ajax_iframe_content', $ajax_iframe_content );
}
/**
* Returns the HTML to be be output when the requested form is not found.
*
* @since 2.5.7 Added the $ajax argument.
* @since 2.4.18
*
* @param int|string $form_id The ID or Title of the form requested for display.
* @param bool $ajax Whether to return the html as part of the ajax postback html or on its own.
*
* @return string
*/
public static function get_form_not_found_html( $form_id, $ajax = false ) {
$form_not_found_message = '<p class="gform_not_found">' . esc_html__( 'Oops! We could not locate your form.', 'gravityforms' ) . '</p>';
/**
* Allows the HTML that is displayed when the requested form is not found to be overridden.
*
* @since 2.2.6
*
* @param string $form_not_found_message The default form not found message.
* @param int|string $form_id The ID or Title of the form requested for display.
*/
$form_not_found_message = apply_filters( 'gform_form_not_found_message', $form_not_found_message, $form_id );
return $ajax ? self::get_ajax_postback_html( $form_not_found_message ) : $form_not_found_message;
}
/**
* Generates the markup for the validation errors list that goes on top of the form.
*
* @since 2.5
*
* @param array $form Current form being displayed.
* @param array $values Submitted values.
* @param bool $show_summary Whether to show a summary of validation errors or just show the validation message.
*
* @return string Validation errors markup.
*/
public static function get_validation_errors_markup( $form, $values, $show_summary = false ) {
$error_messages_list = '';
$hide_summary_class = $show_summary ? '' : ' hide_summary';
if ( gf_upgrade()->get_submissions_block() ) {
$validation_message_markup = "<h2 class='gf_submission_limit_message'>" . esc_html__( 'Your form was not submitted. Please try again in a few minutes.', 'gravityforms' ) . '</h2>';
} else {
$validation_message_markup = "<h2 class='gform_submission_error{$hide_summary_class}'><span class='gform-icon gform-icon--close'></span>" . esc_html__( 'There was a problem with your submission.', 'gravityforms' ) . ' ' . esc_html__( 'Please review the fields below.', 'gravityforms' ) . '</h2>';
// Generate validation errors summary if required.
if ( $show_summary ) {
$errors = self::get_validation_errors( $form, $values );
$error_messages_list = '<ol>';
foreach ( $errors as $error ) {
$separator = $error['field_label'] ? ': ' : '';
$error_messages_list .= '<li><a class="gform_validation_error_link" href="' . $error['field_selector'] . '">' . $error['field_label'] . $separator . $error['message'] . '</a></li>';
}
$error_messages_list .= '</ol>';
}
}
$validation_container_id = 'gform_' . $form['id'] . '_validation_container';
$validation_message_markup = gf_apply_filters( array( 'gform_validation_message', $form['id'] ), $validation_message_markup, $form );
// If validation message markup already has a list of errors after being filtered, remove our list.
if ( $show_summary && preg_match( '/<\s*ul[^>]*>(.*?)<\s*\/\s*ul>/', $validation_message_markup ) || preg_match( '/<\s*ol[^>]*>(.*?)<\s*\/\s*ol>/', $validation_message_markup ) ) {
$error_messages_list = '';
}
$wrapper_class = GFCommon::is_legacy_markup_enabled( $form ) ? 'gform_validation_errors validation_error' : 'gform_validation_errors';
$validation_errors_markup = sprintf(
'<div id="gf_form_focus" tabindex="-1" ></div><div class="%s" id="%s">%s%s</div>',
$wrapper_class,
$validation_container_id,
$validation_message_markup,
$error_messages_list
);
/**
* Filter validation errors markup.
*
* @since 2.5
*
* @param string $validation_errors_markup Validation errors markup.
* @param array $form The current form object.
*/
return gf_apply_filters( array( 'gform_form_validation_errors_markup', $form['id'] ), $validation_errors_markup, $form );
}
/**
* Gets a list of validation errors.
*
* @since 2.5
*
* @param array $form Current form being displayed.
* @param array $values Submitted values.
*
* @return array List of validation errors for each field, each item contains the error message and its corresponding field label and selector.
*/
public static function get_validation_errors( $form, $values ) {
$errors = array();
foreach ( $form['fields'] as $field ) {
/* @var GF_Field $field */
if ( ( $field->failed_validation && ! empty( $field->validation_message ) ) ) {
$errors[] = array(
'field_label' => $field->get_field_label( true, $values ),
'field_selector' => '#field_' . $form['id'] . '_' . $field->id,
'message' => $field->validation_message,
);
}
}
/**
* Filter validation errors array.
*
* @since 2.5
*
* @param array $errors List of validation errors.
* @param array $form The current form object.
*/
return gf_apply_filters( array( 'gform_form_validation_errors', $form['id'] ), $errors, $form );
}
/**
* Convert legacy ready class to the new equivalent.
*
* @since 2.5
*
* @param array $form The current form object.
* @param string $classes The class or classes to convert.
*
* @return string|void
*/
public static function convert_legacy_class( $form, $classes ) {
if ( GFCommon::is_legacy_markup_enabled( $form ) ) {
return $classes;
}
$upgraded_classes = array(
'gf_left_half' => 'gfield--width-half',
'gf_right_half' => 'gfield--width-half',
'gf_left_third' => 'gfield--width-third',
'gf_middle_third' => 'gfield--width-third',
'gf_right_third' => 'gfield--width-third',
'gf_first_quarter' => 'gfield--width-quarter',
'gf_second_quarter' => 'gfield--width-quarter',
'gf_third_quarter' => 'gfield--width-quarter',
'gf_fourth_quarter' => 'gfield--width-quarter',
);
$class_list = explode( ' ', $classes );
foreach ( $class_list as $class ) {
if ( array_key_exists( $class, $upgraded_classes ) ) {
$class_list[] = $upgraded_classes[ $class ];
}
}
$classes = implode( ' ', array_unique( $class_list ) );
return $classes;
}
}
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
use Gravity_Forms\Gravity_Forms\License;
use Gravity_Forms\Gravity_Forms\Query\Batch_Processing\GF_Entry_Meta_Batch_Processor;
use Gravity_Forms\Gravity_Forms\Query\Batch_Processing\GF_Batch_Operations_Service_Provider;
require_once GF_PLUGIN_DIR_PATH . 'includes/legacy/forms_model_legacy.php';
/**
* Class GFFormsModel
*
* Handles database calls and formatting of stored data regarding forms
*/
class GFFormsModel {
/**
* Stores the values containing and uploaded files for later access
*
* @since Unknwon
* @access public
*
* @var array Defaults to an empty array.
*/
public static $uploaded_files = array();
/**
* Stores unique form IDs found.
*
* @since Unknown
* @access public
*
* @var array Defaults to an empty array.
*/
public static $unique_ids = array();
/**
* Stores confirmations found.
*
* @since Unknown
* @access private
*
* @var array Defaults to an empty array.
*/
private static $_confirmations = array();
/**
* An in-memory cache for the form meta for the current blog.
*
* Use "{Blog ID}_{Form ID}" as the key.
*
* @since Unknown
* @access private
* @example $_current_forms['1_2']
*
* @var array $_current_forms
*/
private static $_current_forms = array();
/**
* An in-memory cache of form properties using "{Blog ID}_{Form ID}" as the key.
*
* @since 2.7
*
* @var array $_current_forms_props
*/
private static $_current_forms_props = array();
/**
* Handles batch operations for entry meta updates.
*
* @since 2.5.16
*
* @var GF_Entry_Meta_Batch_Processor
*/
private static $entry_meta_batch_processor;
/**
* The entry data for the current site.
*.
* @access private
*
* @var null Defaults to null.
*/
private static $_current_lead = null;
private static $_batch_field_updates = array();
private static $_batch_field_inserts = array();
private static $_batch_field_deletes = array();
/**
* Returns the current database version.
*
* @since 2.2
*
* @return string
*/
public static function get_database_version() {
static $db_version = array();
$blog_id = get_current_blog_id();
if ( empty( $db_version[ $blog_id ] ) ) {
$db_version[ $blog_id ] = get_option( 'gf_db_version' );
}
return $db_version[ $blog_id ];
}
/**
* Flushes the data stored within GFFormsModel::$_current_forms.
*
* @since Unknown
* @access public
*
* @uses GFFormsModel::$_current_forms
*
* @return void
*/
public static function flush_current_forms() {
self::$_current_forms = array();
self::$_current_forms_props = array();
self::flush_confirmations();
}
/**
* Flushes the data stored within GFFormsModel::$_current_lead.
*
* @since Unknown
* @access public
*
* @uses GFFormsModel::$_current_lead
*
* @return void
*/
public static function flush_current_lead() {
self::$_current_lead = null;
}
/**
* Removes the cached properties, meta, and confirmations for a specific form.
*
* @since 2.6
*
* @var string $key The cache key.
*
* @return void
*/
public static function flush_current_form( $key ) {
unset( self::$_current_forms[ $key ], self::$_current_forms_props[ $key ], self::$_confirmations[ $key ] );
}
/**
* Flushes the data stored within GFFormsModel::$_confirmations
*
* @since Unknown
* @access public
*
* @uses GFFormsModel::$_confirmations
*
* @return void
*/
public static function flush_confirmations() {
self::$_confirmations = array();
}
/**
* Gets the form table name, including the site's database prefix.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @return string The form table name.
*/
public static function get_form_table_name() {
global $wpdb;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return $wpdb->prefix . 'rg_form';
}
return $wpdb->prefix . 'gf_form';
}
/**
* Gets the form meta table, including the site's database prefix.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @return string The form meta table.
*/
public static function get_meta_table_name() {
global $wpdb;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return $wpdb->prefix . 'rg_form_meta';
}
return $wpdb->prefix . 'gf_form_meta';
}
/**
* Gets the form view table name, including the site's database prefix.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @return string The form view table name.
*/
public static function get_form_view_table_name() {
global $wpdb;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return $wpdb->prefix . 'rg_form_view';
}
return $wpdb->prefix . 'gf_form_view';
}
/**
* Gets the form revisions table name, including the site's database prefix.
*
* @since 2.4-dev
*
* @return string The form revisions table name.
*/
public static function get_form_revisions_table_name() {
global $wpdb;
return $wpdb->prefix . 'gf_form_revisions';
}
/**
* Gets the lead (entries) table name, including the site's database prefix.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @return string The lead (entry) table name.
*/
public static function get_lead_table_name() {
return GF_Forms_Model_Legacy::get_lead_table_name();
}
/**
* Gets the lead (entry) meta table name, including the site's database prefix.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @return string The lead (entry) meta table name.
*/
public static function get_lead_meta_table_name() {
return GF_Forms_Model_Legacy::get_lead_meta_table_name();
}
/**
* Gets the lead (entry) notes table name, including the site's database prefix.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @return string The lead (entry) notes table name.
*/
public static function get_lead_notes_table_name() {
return GF_Forms_Model_Legacy::get_lead_notes_table_name();
}
/**
* Gets the lead (entry) details table name, including the site's database prefix.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @return string The lead (entry) details table name.
*/
public static function get_lead_details_table_name() {
return GF_Forms_Model_Legacy::get_lead_details_table_name();
}
/**
* Gets the lead (entry) details long table name, including the site's database prefix.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @return string The lead (entry) details long table name.
*/
public static function get_lead_details_long_table_name() {
return GF_Forms_Model_Legacy::get_lead_details_long_table_name();
}
/**
* Gets the lead (entry) view table name, including the site's database prefix.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @return string The lead (entry) view table name.
*/
public static function get_lead_view_name() {
return GF_Forms_Model_Legacy::get_lead_view_name();
}
/**
* Gets the incomplete submissions table name, including the site's database prefix.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @return string he incomplete submissions table name.
*/
public static function get_incomplete_submissions_table_name() {
return GF_Forms_Model_Legacy::get_incomplete_submissions_table_name();
}
/**
* Gets the entry table name, including the site's database prefix
*
* @access public
* @static
* @global $wpdb
*
* @return string The entry table name
*/
public static function get_entry_table_name() {
global $wpdb;
return $wpdb->prefix . 'gf_entry';
}
/**
* Gets the entry meta table name, including the site's database prefix
*
* @access public
* @static
* @global $wpdb
*
* @return string The entry meta table name
*/
public static function get_entry_meta_table_name() {
global $wpdb;
return $wpdb->prefix . 'gf_entry_meta';
}
/**
* Gets the lead (entry) notes table name, including the site's database prefix
*
* @access public
* @static
* @global $wpdb
*
* @return string The lead (entry) notes table name
*/
public static function get_entry_notes_table_name() {
global $wpdb;
return $wpdb->prefix . 'gf_entry_notes';
}
/**
* Gets the draft submissions table name, including the site's database prefix
*
* @access public
* @static
* @global $wpdb
*
* @return string The draft submissions table name
*/
public static function get_draft_submissions_table_name() {
global $wpdb;
return $wpdb->prefix . 'gf_draft_submissions';
}
/**
* Gets the REST API Key table name, including the site's database prefix
*
* @access public
* @static
* @global $wpdb
*
* @return string The REST API Keys submissions table name
*/
public static function get_rest_api_keys_table_name() {
global $wpdb;
return $wpdb->prefix . 'gf_rest_api_keys';
}
/**
* Returns the name of the table where add-on feeds are stored, including the site's database prefix.
*
* @since 2.4.24
*
* @return string
*/
public static function get_addon_feed_table_name() {
global $wpdb;
return $wpdb->prefix . 'gf_addon_feed';
}
/**
* Gets all forms.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @uses GFFormsModel::get_form_table_name()
* @uses GFFormsModel::get_form_db_columns()
* @uses GFFormsModel::get_entry_count_per_form()
* @uses GFFormsModel::get_view_count_per_form()
*
* @param bool $is_active Optional. Defines if inactive forms should be displayed. Defaults to null.
* @param string $sort_column Optional. The column to be used for sorting the forms. Defaults to 'title'.
* @param string $sort_dir Optional. Defines the direction that sorting should occur. Defaults to 'ASC' (ascending). Use 'DESC' for descending.
* @param bool $is_trash Optional. Defines if forms within the trash should be displayed. Defaults to false.
*
* @return array $forms All forms found.
*/
public static function get_forms( $is_active = null, $sort_column = 'title', $sort_dir = 'ASC', $is_trash = false ) {
global $wpdb;
$form_table_name = esc_sql( self::get_form_table_name() );
$where_arr = array();
$where_arr[] = $wpdb->prepare( 'is_trash=%d', $is_trash );
if ( $is_active !== null ) {
$where_arr[] = $wpdb->prepare( 'is_active=%d', $is_active );
}
$where_clause = 'WHERE ' . join( ' AND ', $where_arr );
$sort_keyword = $sort_dir == 'ASC' ? 'ASC' : 'DESC';
$db_columns = self::get_form_db_columns();
if ( ! in_array( strtolower( $sort_column ), $db_columns ) ) {
$sort_column = 'title';
}
$sort_column = sanitize_sql_orderby( $sort_column );
$order_by = ! empty( $sort_column ) ? "ORDER BY $sort_column $sort_keyword" : '';
$sql = "SELECT f.id, f.title, f.date_created, f.is_active, 0 as entry_count, 0 view_count
FROM $form_table_name f
$where_clause
$order_by";
//Getting all forms
$forms = $wpdb->get_results( $sql );
//Getting entry count per form
$entry_count = self::get_entry_count_per_form();
//Getting view count per form
$view_count = self::get_view_count_per_form();
//Adding entry counts and to form array
foreach ( $forms as &$form ) {
foreach ( $view_count as $count ) {
if ( $count->form_id == $form->id ) {
$form->view_count = $count->view_count;
break;
}
}
foreach ( $entry_count as $count ) {
if ( $count->form_id == $form->id ) {
$form->entry_count = $count->entry_count;
break;
}
}
}
return $forms;
}
/**
* Searches form titles based on query.
*
* @access public
* @static
* @global $wpdb
* @see GFFormsModel::get_form_table_name
* @see GFFormsModel::get_form_db_columns
* @see GFFormsModel::get_entry_count_per_form
* @see GFFormsModel::get_view_count_per_form
*
* @param string $query Optional. The query to search.
* @param bool $is_active Optional. Defines if inactive forms should be displayed. Defaults to null.
* @param string $sort_column Optional. The column to be used for sorting the forms. Defaults to 'title'.
* @param string $sort_dir Optional. Defines the direction that sorting should occur. Defaults to 'ASC' (ascending). Use 'DESC' for descending.
* @param bool $is_trash Optional. Defines if forms within the trash should be displayed. Defaults to false.
*
* @return array $forms All forms found.
*/
public static function search_forms( $query = '', $is_active = null, $sort_column = 'title', $sort_dir = 'ASC', $is_trash = false ) {
global $wpdb;
$form_table_name = esc_sql( self::get_form_table_name() );
$where_arr = array();
$where_arr[] = $wpdb->prepare( 'is_trash=%d', $is_trash );
if ( $is_active !== null ) {
$where_arr[] = $wpdb->prepare( 'is_active=%d', $is_active );
}
if ( ! rgblank( $query ) ) {
$where_arr[] = $wpdb->prepare( 'title LIKE %s', '%' . $query . '%' );
}
$sort_keyword = $sort_dir == 'ASC' ? 'ASC' : 'DESC';
$db_columns = self::get_form_db_columns();
if ( ! in_array( strtolower( $sort_column ), $db_columns ) ) {
$sort_column = 'title';
}
$sort_column = sanitize_sql_orderby( $sort_column );
$where_clause = 'WHERE ' . join( ' AND ', $where_arr );
$order_by = ! empty( $sort_column ) ? "ORDER BY $sort_column $sort_keyword" : '';
// All of the pieces of this SQL statement have already gone through $wpdb->prepare, so we don't prepare it again here.
$sql = "SELECT f.id, f.title, f.date_created, f.is_active, 0 as entry_count, 0 view_count
FROM $form_table_name f
$where_clause
$order_by";
//Getting all forms
$forms = $wpdb->get_results( $sql );
//Getting entry count per form
$entry_count = self::get_entry_count_per_form();
//Getting view count per form
$view_count = self::get_view_count_per_form();
//Adding entry counts and to form array
foreach ( $forms as &$form ) {
foreach ( $view_count as $count ) {
if ( $count->form_id == $form->id ) {
$form->view_count = $count->view_count;
break;
}
}
foreach ( $entry_count as $count ) {
if ( $count->form_id == $form->id ) {
$form->entry_count = $count->entry_count;
break;
}
}
}
return $forms;
}
/**
* Gets the number of entries per form.
*
* First attempts to read from cache. If unavailable, gets the entry count, caches it, and returns it.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @uses GFFormsModel::get_lead_table_name()
* @uses GFCache::get()
* @uses GFCache::set()
*
* @return array $entry_count Array of forms, containing the form ID and the entry count
*/
public static function get_entry_count_per_form() {
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::get_entry_count_per_form();
}
global $wpdb;
$entry_table_name = esc_sql( self::get_entry_table_name() );
$entry_count = GFCache::get( 'get_entry_count_per_form' );
if ( empty( $entry_count ) ) {
//Getting entry count per form
$sql = $wpdb->prepare( "SELECT form_id, count(id) as entry_count FROM $entry_table_name l WHERE status=%s GROUP BY form_id", 'active' );
$entry_count = $wpdb->get_results( $sql );
GFCache::set( 'get_entry_count_per_form', $entry_count, true, 30 );
}
return $entry_count;
}
/**
* Gets the number of views per form
*
* Checks the cache first. If not there, gets the count from the database, stores it in the cache, and returns it.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @uses GFFormsModel::get_form_view_table_name()
* @uses GFCache::get()
* @uses GFCache::set()
*
* @return array $view_count Array of forms, containing the form ID and the view count
*/
public static function get_view_count_per_form() {
global $wpdb;
$view_table_name = esc_sql( self::get_form_view_table_name() );
$view_count = GFCache::get( 'get_view_count_per_form' );
if ( empty( $view_count ) ){
$sql = "SELECT form_id, sum(count) as view_count FROM $view_table_name GROUP BY form_id";
$view_count = $wpdb->get_results( $sql );
GFCache::set( 'get_view_count_per_form', $view_count, true, 30 );
}
return $view_count;
}
/**
* Returns the form database columns.
*
* @since Unknown
* @access public
*
* @return array The column IDs
*/
public static function get_form_db_columns() {
return array( 'id', 'title', 'date_created', 'is_active', 'is_trash' );
}
/**
* Gets the payment totals for a particular form ID.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @uses GFFormsModel::get_lead_table_name()
*
* @param int $form_id The form ID to get payment totals for.
*
* @return array $totals The payment totals found.
*/
public static function get_form_payment_totals( $form_id ) {
global $wpdb;
$entry_table_name = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_lead_table_name() : self::get_entry_table_name();
$sql = $wpdb->prepare(
" SELECT sum(payment_amount) revenue, count(l.id) orders
FROM $entry_table_name l
WHERE form_id=%d AND payment_amount IS NOT null", $form_id
);
$totals = $wpdb->get_row( $sql, ARRAY_A );
$active = $wpdb->get_var(
$wpdb->prepare(
" SELECT count(id) as active
FROM $entry_table_name
WHERE form_id=%d AND payment_status='Active'", $form_id
)
);
if ( empty( $active ) ) {
$active = 0;
}
$totals['active'] = $active;
return $totals;
}
/**
* Gets the total, unread, starred, spam, and trashed entry counts.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @uses GFFormsModel::get_lead_table_name()
* @uses GFFormsModel::get_lead_details_table_name()
*
* @param int $form_id The ID of the form to check.
*
* @return array $results[0] The form counts.
*/
public static function get_form_counts( $form_id ) {
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::get_form_counts( $form_id );
}
global $wpdb;
$cache_key = 'form_counts_' . $form_id;
$results = GFCache::get( $cache_key );
if ( ! empty( $results ) ) {
return $results;
}
$entry_table_name = self::get_entry_table_name();
$entry_detail_table_name = self::get_entry_meta_table_name();
$sql = $wpdb->prepare(
"SELECT
(SELECT count(DISTINCT(l.id)) FROM $entry_table_name l WHERE l.form_id=%d AND l.status='active') as total,
(SELECT count(DISTINCT(l.id)) FROM $entry_table_name l WHERE l.is_read=0 AND l.status='active' AND l.form_id=%d) as unread,
(SELECT count(DISTINCT(l.id)) FROM $entry_table_name l WHERE l.is_starred=1 AND l.status='active' AND l.form_id=%d) as starred,
(SELECT count(DISTINCT(l.id)) FROM $entry_table_name l WHERE l.status='spam' AND l.form_id=%d) as spam,
(SELECT count(DISTINCT(l.id)) FROM $entry_table_name l WHERE l.status='trash' AND l.form_id=%d) as trash",
$form_id, $form_id, $form_id, $form_id, $form_id
);
$wpdb->timer_start();
$results = $wpdb->get_results( $sql, ARRAY_A );
$time_total = $wpdb->timer_stop();
if ( $time_total > 1 ) {
GFCache::set( $cache_key, $results[0], true, 10 * MINUTE_IN_SECONDS );
}
return $results[0];
}
/**
* Gets the form summary for all forms.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @uses GFFormsModel::get_form_table_name()
* @uses GFFormsModel::get_lead_table_name()
*
* @return array $forms Contains the form summary for all forms.
*/
public static function get_form_summary() {
global $wpdb;
$form_table_name = esc_sql( self::get_form_table_name() );
$entry_table_name = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? esc_sql( self::get_lead_table_name() ) : esc_sql( self::get_entry_table_name() );
$sql = $wpdb->prepare( "SELECT l.form_id, count(l.id) as unread_count
FROM $entry_table_name l
WHERE is_read=%d AND status=%s
GROUP BY form_id",
0,
'active'
);
// Getting number of unread and total leads for all forms
$unread_results = $wpdb->get_results( $sql, ARRAY_A );
$sql = $wpdb->prepare( "SELECT l.form_id, max(l.date_created) as last_entry_date, count(l.id) as total_entries
FROM $entry_table_name l
WHERE status=%s
GROUP BY form_id",
'active'
);
$lead_date_results = $wpdb->get_results( $sql, ARRAY_A );
$sql = $wpdb->prepare( "SELECT id, title, is_trash, '' as last_entry_date, 0 as unread_count
FROM $form_table_name
WHERE is_active=%d
ORDER BY title",
1
);
$forms = $wpdb->get_results( $sql, ARRAY_A );
for ( $i = 0; $count = sizeof( $forms ), $i < $count; $i ++ ) {
if ( is_array( $unread_results ) ) {
foreach ( $unread_results as $unread_result ) {
if ( $unread_result['form_id'] == $forms[ $i ]['id'] ) {
$forms[ $i ]['unread_count'] = $unread_result['unread_count'];
break;
}
}
}
if ( is_array( $lead_date_results ) ) {
foreach ( $lead_date_results as $entry_date_result ) {
if ( $entry_date_result['form_id'] == $forms[ $i ]['id'] ) {
$forms[ $i ]['last_entry_date'] = $entry_date_result['last_entry_date'];
$forms[ $i ]['total_entries'] = $entry_date_result['total_entries'];
break;
}
}
}
}
/**
* Modifies the summary of all forms, includes unread and total entry counts.
*
* @since 2.4.16
*
* @param array $forms Form summary.
*/
$forms = apply_filters( 'gform_form_summary', $forms );
return $forms;
}
/**
* Gets the total, active, inactive, and trashed form counts.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @uses GFFormsModel::get_form_table_name()
*
* @return array The form counts.
*/
public static function get_form_count() {
global $wpdb;
$form_table_name = self::get_form_table_name();
if ( ! GFCommon::table_exists( $form_table_name ) ) {
return array(
'total' => 0,
'active' => 0,
'inactive' => 0,
'trash' => 0,
);
}
$results = $wpdb->get_results(
"
SELECT
(SELECT count(0) FROM $form_table_name WHERE is_trash = 0) as total,
(SELECT count(0) FROM $form_table_name WHERE is_active=1 AND is_trash = 0 ) as active,
(SELECT count(0) FROM $form_table_name WHERE is_active=0 AND is_trash = 0 ) as inactive,
(SELECT count(0) FROM $form_table_name WHERE is_trash=1) as trash
"
);
return array(
'total' => intval( $results[0]->total ),
'active' => intval( $results[0]->active ),
'inactive' => intval( $results[0]->inactive ),
'trash' => intval( $results[0]->trash ),
);
}
/**
* Gets the form ID based on the form title.
*
* @since Unknown
* @access public
*
* @uses GFFormsModel::get_forms()
*
* @param string $form_title The form title to search for.
*
* @return int The form ID. Returns 0 if not found.
*/
public static function get_form_id( $form_title ) {
$forms = self::get_forms();
foreach ( $forms as $form ) {
$sanitized_name = str_replace( '[', '', str_replace( ']', '', $form->title ) );
if ( $form->title == $form_title || $sanitized_name == $form_title ) {
return absint( $form->id );
}
}
return 0;
}
/**
* Returns the cache key for the specified form.
*
* @since 2.7
*
* @param int $form_id The form ID.
*
* @return string
*/
public static function get_form_cache_key( $form_id ) {
return get_current_blog_id() . '_' . $form_id;
}
/**
* Returns an object containing the properties of the specified form or false if the form doesn't exist.
*
* @since Unknown
* @since 2.7 Updated to cache the result.
*
* @param int $form_id The ID of the form to get.
* @param bool $allow_trash Optional. Set to true to allow trashed results. Defaults to false.
*
* @return bool|object
*/
public static function get_form( $form_id, $allow_trash = false ) {
$form_id = absint( $form_id );
if ( empty( $form_id ) ) {
return false;
}
$key = self::get_form_cache_key( $form_id );
$form_props = rgar( self::$_current_forms_props, $key );
if ( is_object( $form_props ) ) {
if ( ! $allow_trash && $form_props->is_trash ) {
return false;
}
return $form_props;
}
global $wpdb;
$table_name = self::get_form_table_name();
$trash_clause = $allow_trash ? '' : 'AND is_trash = 0';
$result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id=%d {$trash_clause}", $form_id ) );
if ( empty( $result ) ) {
return false;
}
self::$_current_forms_props[ $key ] = $result;
return $result;
}
/**
* Converts a serialized string or JSON for access in PHP.
*
* @since Unknown
* @access public
*
* @param string $string The string to convert.
*
* @return mixed
*/
public static function unserialize( $string ) {
if ( empty( $string ) ) {
return null;
} elseif ( is_serialized( $string ) ) {
$obj = @unserialize( $string );
} else {
$obj = json_decode( $string, true );
}
return $obj;
}
/**
* Gets the form meta based on the form ID.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @uses GFFormsModel::get_meta_table_name()
* @uses GFFormsModel::unserialize()
* @uses GFFormsModel::convert_field_objects()
* @uses GFFormsModel::load_notifications_from_legacy()
* @uses GFFormsModel::$_current_forms
*
* @param int $form_id The form ID.
*
* @return array|null $form Form object if found. Null if not found.
*/
public static function get_form_meta( $form_id ) {
global $wpdb;
$form_id = absint( $form_id );
if ( empty( $form_id ) ) {
return null;
}
$key = self::get_form_cache_key( $form_id );
// Return cached version if form meta has been previously retrieved for this form
if ( isset( self::$_current_forms[ $key ] ) ) {
return self::$_current_forms[ $key ];
}
$table_name = self::get_meta_table_name();
$form_row = $wpdb->get_row( $wpdb->prepare( "SELECT display_meta, notifications FROM {$table_name} WHERE form_id=%d", $form_id ), ARRAY_A );
// Loading main form object (supports serialized strings as well as JSON strings)
$form = self::unserialize( rgar( $form_row, 'display_meta' ) );
if ( ! $form ) {
return null;
}
// Ensure the fields property is in the correct format, an associative array will cause warnings and js errors in the form editor.
$form['fields'] = is_array( rgar( $form, 'fields' ) ) ? array_values( $form['fields'] ) : array();
// Loading notifications
$form['notifications'] = self::unserialize( $form_row['notifications'] );
// Creating field objects and copying some form variables down to fields for easier access
$form = self::convert_field_objects( $form );
// Loading confirmations from legacy structure into new structure
$form = self::load_confirmations( $form );
//only migrate legacy notification if there isn't any notification configured in new structure
if ( ! isset( $form['notifications'] ) ) {
$form = self::load_notifications_from_legacy( $form ); // Moving notification data from legacy structure into new 'notifications' array
}
// Load notifications to legacy structure to maintain backward compatibility with legacy hooks and functions
$form = self::load_notifications_to_legacy( $form );
// Ensure the next field ID is set correctly.
$form['nextFieldId'] = self::get_next_field_id( $form['fields'] );
/**
* Filters the Form object after the form meta is obtained
*
* @param array $form The Form object
*/
$form = gf_apply_filters( array( 'gform_form_post_get_meta', $form_id ), $form );
// Cached form meta for cheaper retrieval on subsequent requests
self::$_current_forms[ $key ] = $form;
return $form;
}
/**
* Recursively checks the highest ID for all the fields in the form and then returns the highest ID + 1.
*
* @since 2.4.6.12
*
* @param GF_Field[] $fields
* @param int $next_field_id
*
* @return int
*/
public static function get_next_field_id( $fields, $next_field_id = 1 ) {
foreach ( $fields as $field ) {
if ( is_array( $field->fields ) ) {
$next_field_id = self::get_next_field_id( $field->fields, $next_field_id );
}
if ( $field->id >= $next_field_id ) {
$next_field_id = $field->id + 1;
}
}
return (int) $next_field_id;
}
/**
* Converts all field objects in a form, based on field type.
*
* @since Unknown
* @access public
*
* @uses GF_Field_CreditCard::maybe_upgrade_inputs()
*
* @param array $form The Form object.
*
* @return array $form The Form object after the field objects are converted.
*/
public static function convert_field_objects( $form ) {
$page_number = 1;
$form['fields'] = is_array( rgar( $form, 'fields' ) ) ? array_values( $form['fields'] ) : array();
foreach ( $form['fields'] as &$field ) {
// convert adminOnly property to visibility
if ( ! isset( $field['visibility'] ) ) {
$field['visibility'] = isset( $field['adminOnly'] ) && $field['adminOnly'] ? 'administrative' : 'visible';
unset( $field['adminOnly'] );
}
$field = GF_Fields::create( $field );
if ( isset( $form['id'] ) ) {
$field->formId = $form['id'];
}
$field->pageNumber = $page_number;
if ( is_array( $field->fields ) ) {
self::convert_sub_field_objects( $field, $form['id'], $page_number );
}
if ( $field->type == 'page' ) {
$page_number ++;
$field->pageNumber = $page_number;
}
// Populate required cssClass property with empty string if not set to avoid JS errors when rendering.
if ( ! isset( $field->cssClass ) ) {
$field->cssClass = '';
}
$field->post_convert_field();
}
return $form;
}
/**
* Converts the sub fields to field objects.
*
* @since 2.4
*
* @param GF_Field $field The repeater field to be converted to objects.
* @param int $form_id The current form ID.
* @param int $page_number The page number the parent field is located on.
* @param int $nesting_level The level at which a repeater field is nested.
*/
private static function convert_sub_field_objects( &$field, $form_id, $page_number, $nesting_level = 0 ) {
$field->nestingLevel = $nesting_level;
foreach ( $field->fields as &$field ) {
$field = GF_Fields::create( $field );
$field->formId = $form_id;
$field->pageNumber = $page_number;
$field->post_convert_field();
if ( is_array( $field['fields'] ) ) {
$new_nesting_level = $nesting_level + 1;
self::convert_sub_field_objects( $field, $form_id, $page_number, $new_nesting_level );
}
}
}
/**
* Gets the form meta for multiple forms based on an array for form IDs.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @uses GFFormsModel::get_form_table_name()
* @uses GFFormsModel::get_meta_table_name()
* @uses GFFormsModel::unserialize()
* @uses GFFormsModel::convert_field_objects()
*
* @param array $ids Array of form IDs.
*
* @return array $results
*/
public static function get_form_meta_by_id( $ids ) {
global $wpdb;
$form_table_name = self::get_form_table_name();
$meta_table_name = self::get_meta_table_name();
if ( is_array( $ids ) ) {
$ids = implode( ',', array_map( 'intval', $ids ) );
} else {
$ids = intval( $ids );
}
$results = $wpdb->get_results(
" SELECT display_meta, confirmations, notifications FROM {$form_table_name} f
INNER JOIN {$meta_table_name} m ON f.id = m.form_id
WHERE id in({$ids})", ARRAY_A
);
foreach ( $results as &$result ) {
$form = self::unserialize( $result['display_meta'] );
$form['confirmations'] = self::unserialize( $result['confirmations'] );
$form['notifications'] = self::unserialize( $result['notifications'] );
// Creating field objects and copying some form variables down to fields for easier access
$form = self::convert_field_objects( $form );
$result = $form;
}
return $results;
}
/**
* Converts current notification structure to legacy.
*
* @since Unknown
* @access private
*
* @param array $form The Form object.
*
* @return array $form The Form object.
*/
private static function load_notifications_to_legacy( $form ) {
if ( ! is_array( rgar( $form, 'notifications' ) ) ) {
return $form;
}
foreach ( $form['notifications'] as $notification ) {
if ( ! in_array( rgar( $notification, 'type' ), array( 'user', 'admin' ) ) ) {
continue;
}
$legacy_notification = $notification;
if ( $notification['toType'] == 'field' ) {
$legacy_notification['toField'] = $notification['to'];
unset( $legacy_notification['to'] );
}
// Unsetting new properties
unset( $legacy_notification['toType'] );
unset( $legacy_notification['id'] );
unset( $legacy_notification['event'] );
unset( $legacy_notification['name'] );
if ( isset( $legacy_notification['type'] ) ) {
unset( $legacy_notification['type'] );
}
//saving into form object
$property = $notification['type'] == 'user' ? 'autoResponder' : 'notification';
$form[ $property ] = $legacy_notification;
}
return $form;
}
/**
* Loads notifications using from the legacy format.
*
* @since Unknown
* @access private
*
* @uses GFCommon::has_admin_notification()
* @uses GFFormsModel::convert_property_to_merge_tag()
* @uses GFCommon::has_user_notification()
* @uses GFFormsModel::save_form_notifications()
*
* @param array $form The Form object.
*
* @return array $form The Form object.
*/
private static function load_notifications_from_legacy( $form ) {
$form['notifications'] = array();
if ( GFCommon::has_admin_notification( $form ) ) {
$admin_notification = $form['notification'];
//if there is a fromField configured, move it to 'from' as a merge tag
$admin_notification = self::convert_property_to_merge_tag( $form, $admin_notification, 'from', 'fromField' );
//if there is a fromNameField configured, move it to 'fromName' as a merge tag
$admin_notification = self::convert_property_to_merge_tag( $form, $admin_notification, 'fromName', 'fromNameField' );
//if there is a replyToField configured, move it to 'replyTo' as a merge tag
$admin_notification = self::convert_property_to_merge_tag( $form, $admin_notification, 'replyTo', 'replyToField' );
//if routing is configured, set toType to routing, otherwise, set it to email
$admin_notification['toType'] = ! rgempty( 'routing', $admin_notification ) ? 'routing' : 'email';
$notification_id = uniqid();
//assigning this notification to the form_submission action
$admin_notification['event'] = 'form_submission';
$admin_notification['name'] = esc_html__( 'Admin Notification', 'gravityforms' );
$admin_notification['type'] = 'admin';
$admin_notification['id'] = $notification_id;
//copying admin notification as an item in the new notifications array
$form['notifications'][ $notification_id ] = $admin_notification;
}
if ( GFCommon::has_user_notification( $form ) ) {
$user_notification = $form['autoResponder'];
//if there is a toField configured, set toType to field, if not, set it toemail
$to_field = rgar( $user_notification, 'toField' );
if ( ! empty( $to_field ) ) {
$user_notification['toType'] = 'field';
$user_notification['to'] = $to_field;
} else {
$user_notification['toType'] = 'email';
}
$notification_id = uniqid();
//assigning this notification to the form_submission action
$user_notification['event'] = 'form_submission';
$user_notification['name'] = esc_html__( 'User Notification', 'gravityforms' );
$user_notification['type'] = 'user';
$user_notification['id'] = $notification_id;
//copying user notification as an item in the new notifications array
$form['notifications'][ $notification_id ] = $user_notification;
}
self::save_form_notifications( $form['id'], $form['notifications'] );
return $form;
}
/**
* Converts a form property to the merge tag format.
*
* @since Unknown
* @access private
*
* @uses GFFormsModel::get_field_merge_tag()
*
* @param array $form The Form object.
* @param array $array Array of properties to search through.
* @param string $target_property The property to move the value to.
* @param string $source_property The property to search for.
*
* @return array $array The array that was searched through.
*/
private static function convert_property_to_merge_tag( $form, $array, $target_property, $source_property ) {
$merge_tag = self::get_field_merge_tag( $form, rgar( $array, $source_property ) );
if ( $merge_tag ) {
$array[ $target_property ] = $merge_tag;
unset( $array[ $source_property ] );
}
return $array;
}
/**
* Gets a formatted merge tag for a field.
*
* @since Unknown
* @access private
*
* @uses GFFormsModel::get_field()
* @uses GFCommon::get_label()
*
* @param array $form The Form object.
* @param int $field_id The field ID.
*
* @return string|false The merge tag if found. False if not found.
*/
private static function get_field_merge_tag( $form, $field_id ) {
$field = self::get_field( $form, $field_id );
if ( ! $field ) {
return false;
}
return '{' . GFCommon::get_label( $field, $field_id ) . ':' . $field_id . '}';
}
/**
* Adds default form properties
*
* @deprecated 1.9
*/
public static function add_default_properties( $form ) {
_deprecated_function( 'GFFormsModel::add_default_properties', '1.9' );
if ( is_array( rgar( $form, 'fields' ) ) ) {
$all_fields = array(
'adminLabel' => '', 'allowsPrepopulate' => '', 'defaultValue' => '', 'description' => '', 'content' => '', 'cssClass' => '',
'errorMessage' => '', 'id' => '', 'inputName' => '', 'isRequired' => '', 'label' => '', 'noDuplicates' => '',
'size' => '', 'type' => '', 'postCustomFieldName' => '', 'displayAllCategories' => '', 'displayCaption' => '', 'displayDescription' => '',
'displayTitle' => '', 'displayAlt' => '', 'inputType' => '', 'rangeMin' => '', 'rangeMax' => '', 'calendarIconType' => '',
'calendarIconUrl' => '', 'dateType' => '', 'dateFormat' => '', 'phoneFormat' => '', 'addressType' => '', 'defaultCountry' => '', 'defaultProvince' => '',
'defaultState' => '', 'hideAddress2' => '', 'hideCountry' => '', 'hideState' => '', 'inputs' => '', 'nameFormat' => '', 'allowedExtensions' => '',
'captchaType' => '', 'pageNumber' => '', 'captchaTheme' => '', 'simpleCaptchaSize' => '', 'simpleCaptchaFontColor' => '', 'simpleCaptchaBackgroundColor' => '',
'failed_validation' => '', 'productField' => '', 'enablePasswordInput' => '', 'maxLength' => '', 'enablePrice' => '', 'basePrice' => '',
'visibility' => 'visible',
);
foreach ( $form['fields'] as &$field ) {
if ( is_array( $field ) ) {
$field = wp_parse_args( $field, $all_fields );
}
}
}
return $form;
}
/**
* Gets the column info for the entry listing page.
*
* @since Unknown
* @access public
* @global $wpdb
*
* @uses GFFormsModel::get_meta_table_name()
*
* @param int $form_id The ID of the form that entries are coming from.
*
* @return mixed
*/
public static function get_grid_column_meta( $form_id ) {
global $wpdb;
$table_name = self::get_meta_table_name();
return maybe_unserialize( $wpdb->get_var( $wpdb->prepare( "SELECT entries_grid_meta FROM $table_name WHERE form_id=%d", $form_id ) ) );
}
public static function update_grid_column_meta( $form_id, $columns ) {
global $wpdb;
$table_name = self::get_meta_table_name();
$meta = maybe_serialize( stripslashes_deep( $columns ) );
$wpdb->query( $wpdb->prepare( "UPDATE $table_name SET entries_grid_meta=%s WHERE form_id=%d", $meta, $form_id ) );
}
public static function get_lead_detail_id( $current_fields, $field_number, $item_index = '' ) {
if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::get_lead_detail_id( $current_fields, $field_number );
}
foreach ( $current_fields as $field ) {
if ( (string) $field->meta_key === (string) $field_number && $field->item_index == $item_index) {
return $field->id;
}
}
return 0;
}
/**
* Updates the form is_active property in the database.
*
* @since unknown
* @since 2.7 Updated to clear the form from the cache.
*
* @param int $form_id The ID of the form to be updated.
* @param int|bool $is_active Indicates if the form is being set as active.
*
* @return void
*/
public static function update_form_active( $form_id, $is_active ) {
global $wpdb;
$form_table = self::get_form_table_name();
$sql = $wpdb->prepare( "UPDATE $form_table SET is_active=%d WHERE id=%d", $is_active, $form_id );
$wpdb->query( $sql );
if ( $is_active ) {
/**
* Fires after an inactive form gets marked as active
*
* @since 1.9
*
* @param int $form_id The Form ID used to specify which form to activate
*/
do_action( 'gform_post_form_activated', $form_id );
} else {
/**
* Fires after an active form gets marked as inactive
*
* @since 1.9
*
* @param int $form_id The Form ID used to specify which form to activate
*/
do_action( 'gform_post_form_deactivated', $form_id );
}
self::flush_current_form( self::get_form_cache_key( $form_id ) );
}
public static function update_notification_active( $form_id, $notification_id, $is_active ) {
$form = GFFormsModel::get_form_meta( $form_id );
if ( ! isset( $form['notifications'][ $notification_id ] ) ) {
return new WP_Error( 'not_found', __( 'Notification not found', 'gravityforms' ) );
}
$form['notifications'][ $notification_id ]['isActive'] = (bool) $is_active;
if ( (bool) $is_active ) {
/**
* Fires before a notification is activated
*
* @param int $form['notifications'][ $notification_id ] The ID of the notification that was activated
* @param array $form The Form object
*/
do_action( 'gform_pre_notification_activated', $form['notifications'][ $notification_id ], $form );
} else {
/**
* Fires before a notification is deactivated
*
* @param int $form['notifications'][ $notification_id ] The ID of the notification that was deactivated
* @param array $form The Form object
*/
do_action( 'gform_pre_notification_deactivated', $form['notifications'][ $notification_id ], $form );
}
$result = GFFormsModel::update_form_meta( $form_id, $form['notifications'], 'notifications' );
return $result;
}
public static function update_confirmation_active( $form_id, $confirmation_id, $is_active ) {
$form = GFFormsModel::get_form_meta( $form_id );
if ( ! isset( $form['confirmations'][ $confirmation_id ] ) ) {
return new WP_Error( 'not_found', __( 'Confirmation not found', 'gravityforms' ) );
}
$form['confirmations'][ $confirmation_id ]['isActive'] = (bool) $is_active;
$result = GFFormsModel::update_form_meta( $form_id, $form['confirmations'], 'confirmations' );
return $result;
}
public static function update_forms_active( $forms, $is_active ) {
foreach ( $forms as $form_id ) {
self::update_form_active( $form_id, $is_active );
}
}
public static function update_leads_property( $leads, $property_name, $property_value ) {
self::update_entries_property( $leads, $property_name, $property_value );
}
public static function update_lead_property( $lead_id, $property_name, $property_value, $update_akismet = true, $disable_hook = false ) {
return self::update_entry_property( $lead_id, $property_name, $property_value, $update_akismet, $disable_hook );
}
public static function update_entries_property( $leads, $property_name, $property_value ) {
foreach ( $leads as $lead ) {
self::update_entry_property( $lead, $property_name, $property_value );
}
}
public static function update_entry_property( $lead_id, $property_name, $property_value, $update_akismet = true, $disable_hook = false ) {
global $wpdb, $current_user;
if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::update_lead_property( $lead_id, $property_name, $property_value, $update_akismet, $disable_hook );
}
$entry_table = self::get_entry_table_name();
$lead = self::get_entry( $lead_id );
//marking entry as 'spam' or 'not spam' with Akismet if the plugin is installed
if ( $update_akismet && GFCommon::akismet_enabled( $lead['form_id'] ) && $property_name == 'status' && in_array( $property_value, array( 'active', 'spam' ) ) ) {
$current_status = $lead['status'];
if ( $current_status == 'spam' && $property_value == 'active' ) {
$form = self::get_form_meta( $lead['form_id'] );
GFCommon::mark_akismet_spam( $form, $lead, false );
} else if ( $current_status == 'active' && $property_value == 'spam' ) {
$form = self::get_form_meta( $lead['form_id'] );
GFCommon::mark_akismet_spam( $form, $lead, true );
}
}
// If property is trash, log user login
if ( $property_name == 'status' && $property_value == 'trash' && ! empty( $current_user->user_login ) ) {
GFCommon::log_debug( __METHOD__ . "(): User ID {$current_user->ID} requested moving of entry #{$lead_id} to trash." );
}
//updating lead
$result = $wpdb->update( $entry_table, array( $property_name => $property_value ), array( 'id' => $lead_id ) );
if ( ! $disable_hook ) {
$previous_value = rgar( $lead, $property_name );
if ( $previous_value != $property_value ) {
// if property is status, prev value is spam and new value is active
if ( $property_name == 'status' && $previous_value == 'spam' && $property_value == 'active' && ! rgar( $lead, 'post_id' ) ) {
$lead[ $property_name ] = $property_value;
$lead['post_id'] = GFCommon::create_post( isset( $form ) ? $form : GFAPI::get_form( $lead['form_id'] ), $lead );
}
/**
* Fired after an entry property is updated
*
* @param string $property_name Used within the action string. Defines the property that fires the action.
*
* @param int $lead_id The Entry ID
* @param string $property_value The new value of the property that was updated
* @param string $previous_value The previous property value before the update
*/
do_action( "gform_update_{$property_name}", $lead_id, $property_value, $previous_value );
/**
* Fired after an entry property is updated.
*
* @param int $lead_id The Entry ID.
* @param string $property_name The property that was updated.
* @param string $property_value The new value of the property that was updated.
* @param string $previous_value The previous property value before the update.
*
* @since 2.3.3.9
*/
do_action( "gform_post_update_entry_property", $lead_id, $property_name, $property_value, $previous_value );
}
}
return $result;
}
private static function truncate( $str, $length ) {
if ( strlen( $str ) > $length ) {
$str = substr( $str, 0, $length );
}
return $str;
}
/**
*
* @param $leads
*/
public static function delete_leads( $leads ) {
self::delete_entries( $leads );
}
public static function delete_entries( $entries ) {
foreach ( $entries as $entry_id ) {
self::delete_entry( $entry_id );
}
}
public static function delete_forms( $forms ) {
foreach ( $forms as $form_id ) {
self::delete_form( $form_id );
}
}
public static function trash_forms( $form_ids ) {
foreach ( $form_ids as $form_id ) {
self::trash_form( $form_id );
}
}
public static function restore_forms( $form_ids ) {
foreach ( $form_ids as $form_id ) {
self::restore_form( $form_id );
}
}
public static function delete_leads_by_form( $form_id, $status = '' ) {
self::delete_entries_by_form( $form_id, $status );
}
public static function delete_entries_by_form( $form_id, $status = '' ) {
global $wpdb, $current_user;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
GF_Forms_Model_Legacy::delete_leads_by_form( $form_id, $status );
return;
}
$entry_table = self::get_entry_table_name();
$entry_notes_table = self::get_entry_notes_table_name();
$entry_meta_table = self::get_entry_meta_table_name();
GFCommon::log_debug( __METHOD__ . "(): Deleting entries for form #{$form_id}." );
/**
* Fires when you delete entries for a specific form
*
* @param int $form_id The form ID to specify from which form to delete entries
* @param string $status Allows you to set the form entries to a deleted status
*/
do_action( 'gform_delete_entries', $form_id, $status );
// Get status filter.
$status_filter = empty( $status ) ? '' : $wpdb->prepare( 'AND status=%s', $status );
// Get entry IDs.
$entry_ids = $wpdb->get_col( $wpdb->prepare( "SELECT id FROM $entry_table WHERE form_id=%d {$status_filter}", $form_id ) );
// If entries were found, loop through them and run action.
if ( ! empty( $entry_ids ) ) {
// Log user login for user requesting the deletion of entries
if ( ! empty( $current_user->user_login ) ) {
GFCommon::log_debug( __METHOD__ . "(): User ID {$current_user->ID} requested deletion of entries: " . json_encode( $entry_ids ) );
}
foreach ( $entry_ids as $entry_id ) {
/**
* Fires before an entry is deleted.
*
* @param int $entry_id Entry ID to be deleted.
*/
do_action( 'gform_delete_entry', $entry_id );
/**
* Fires before a lead is deleted
* @param $lead_id
* @deprecated Use gform_delete_entry instead
* @see gform_delete_entry
*/
do_action( 'gform_delete_lead', $entry_id );
}
}
// Deleting uploaded files
self::delete_files_by_form( $form_id, $status );
// Delete from entry notes
$sql = $wpdb->prepare(
" DELETE FROM $entry_notes_table
WHERE entry_id IN (
SELECT id FROM $entry_table WHERE form_id=%d {$status_filter}
)", $form_id
);
$wpdb->query( $sql );
// Delete from entry meta
$sql = $wpdb->prepare(
" DELETE FROM $entry_meta_table
WHERE entry_id IN (
SELECT id FROM $entry_table WHERE form_id=%d {$status_filter}
)", $form_id
);
$wpdb->query( $sql );
// Delete from entry
$sql = $wpdb->prepare( "DELETE FROM $entry_table WHERE form_id=%d {$status_filter}", $form_id );
$wpdb->query( $sql );
}
/**
* Delete the views for the specified form.
*
* @param int $form_id The form ID.
*/
public static function delete_views( $form_id ) {
if ( gf_upgrade()->get_submissions_block() ) {
return new WP_Error( 'submissions_blocked', __( 'Submissions are currently blocked due to an upgrade in progress', 'gravityforms' ) );
}
global $wpdb, $current_user;
// Log user login for user requesting deletion of views
if ( ! empty( $current_user->user_login ) ) {
GFCommon::log_debug( __METHOD__ . "(): User ID {$current_user->ID} requested deletion of views for form #{$form_id}." );
}
$form_view_table = self::get_form_view_table_name();
//Delete form view
$sql = $wpdb->prepare( "DELETE FROM $form_view_table WHERE form_id=%d", $form_id );
$wpdb->query( $sql );
/**
* Fires after form views are deleted
*
* @param int $form_id The ID of the form that views were deleted from
*/
do_action( 'gform_post_form_views_deleted', $form_id );
}
public static function delete_form( $form_id ) {
if ( gf_upgrade()->get_submissions_block() ) {
return new WP_Error( 'submissions_blocked', __( 'Submissions are currently blocked due to an upgrade in progress', 'gravityforms' ) );
}
global $wpdb, $current_user;
// Log user login for user requesting deletion of form
if ( ! empty( $current_user->user_login ) ) {
GFCommon::log_debug( __METHOD__ . "(): User ID {$current_user->ID} requested deletion of form #{$form_id}." );
}
/**
* Fires before a form is deleted
*
* @param int $form_id The ID of the form being deleted
*/
do_action( 'gform_before_delete_form', $form_id );
$form_meta_table = self::get_meta_table_name();
$form_table = self::get_form_table_name();
$form_revisions_table = self::get_form_revisions_table_name();
//Deleting form Entries
self::delete_leads_by_form( $form_id );
//Delete form meta
$sql = $wpdb->prepare( "DELETE FROM $form_meta_table WHERE form_id=%d", $form_id );
$wpdb->query( $sql );
//Delete form revisions
$sql = $wpdb->prepare( "DELETE FROM $form_revisions_table WHERE form_id=%d", $form_id );
$wpdb->query( $sql );
//Deleting form Views
self::delete_views( $form_id );
//Delete form
$sql = $wpdb->prepare( "DELETE FROM $form_table WHERE id=%d", $form_id );
$wpdb->query( $sql );
self::flush_current_form( self::get_form_cache_key( $form_id ) );
/**
* Fires after a form is deleted
*
* @param int $form_id The ID of the form that was deleted
*/
do_action( 'gform_after_delete_form', $form_id );
}
public static function trash_form( $form_id ) {
if ( gf_upgrade()->get_submissions_block() ) {
return new WP_Error( 'submissions_blocked', __( 'Submissions are currently blocked due to an upgrade in progress', 'gravityforms' ) );
}
global $wpdb, $current_user;
// Log user login for user moving the form to trash
if ( ! empty( $current_user->user_login ) ) {
GFCommon::log_debug( __METHOD__ . "(): User ID {$current_user->ID} requested moving of form #{$form_id} to trash." );
}
$form_table_name = self::get_form_table_name();
$sql = $wpdb->prepare( "UPDATE $form_table_name SET is_trash=1 WHERE id=%d", $form_id );
$result = $wpdb->query( $sql );
self::flush_current_form( self::get_form_cache_key( $form_id ) );
$success = $result == false;
/**
* Fires after a form is trashed
*
* @since 1.9
*
* @param int $form_id The ID of the form that was trashed
*/
do_action( 'gform_post_form_trashed', $form_id );
self::update_recent_forms( $form_id, true );
return $success;
}
public static function restore_form( $form_id ) {
if ( gf_upgrade()->get_submissions_block() ) {
return new WP_Error( 'submissions_blocked', __( 'Submissions are currently blocked due to an upgrade in progress', 'gravityforms' ) );
}
global $wpdb;
$form_table_name = self::get_form_table_name();
$sql = $wpdb->prepare( "UPDATE $form_table_name SET is_trash=0 WHERE id=%d", $form_id );
$result = $wpdb->query( $sql );
self::flush_current_form( self::get_form_cache_key( $form_id ) );
$success = $result == false;
/**
* Fires after a form is restored from trash
*
* @since 1.9
*
* @param int $form_id The ID of the form that was restored
*/
do_action( 'gform_post_form_restored', $form_id );
return $success;
}
/**
* Duplicate form.
*
* @access public
* @static
* @param int $form_id Form ID to duplicate.
*
* @return int|WP_Error
*/
public static function duplicate_form( $form_id ) {
if ( gf_upgrade()->get_submissions_block() ) {
return new WP_Error( 'submissions_blocked', __( 'Submissions are currently blocked due to an upgrade in progress', 'gravityforms' ) );
}
// Get form to be duplicated.
$form = self::get_form( $form_id );
// Set initial form title.
$new_title = self::maybe_increment_title( $form->title );
// Create new form.
$new_id = self::insert_form( $new_title );
// Copying form meta to new form.
$meta = self::get_form_meta( $form_id );
$meta['title'] = $new_title;
$meta['id'] = $new_id;
// Add notifications to new form.
self::update_form_meta( $new_id, $meta['notifications'], 'notifications' );
unset( $meta['notifications'] );
// Add confirmations to new form.
self::update_form_meta( $new_id, $meta['confirmations'], 'confirmations' );
unset( $meta['confirmations'] );
// Save form meta.
self::update_form_meta( $new_id, $meta );
// Set active state.
self::update_form_active( $new_id, $form->is_active );
// The gform_after_duplicate_form action is deprecated since version 1.9. Please use gform_post_form_duplicated instead
/**
* @deprecated
* @see gform_post_form_duplicated
*/
do_action( 'gform_after_duplicate_form', $form_id, $new_id );
/**
* Fires after a form is duplicated
*
* @param int $form_id The original form's ID
* @param int $new_id The ID of the new, duplicated form
*/
do_action( 'gform_post_form_duplicated', $form_id, $new_id );
return $new_id;
}
public static function is_unique_title( $title, $form_id=0 ) {
$forms = self::get_forms();
foreach ( $forms as $form ) {
if ( strtolower( $form->title ) === strtolower( $title ) && (int) $form->id !== (int) $form_id ) {
return false;
}
}
return true;
}
/**
* If a form title isn't unique, add a number to it to make it unique.
*
* @since 2.5
*
* @param string $title
* @param int|string $form_id
*
* @return string
*/
public static function maybe_increment_title( $title, $form_id = '' ) {
// Title is unique, so we don't need to do anything to it.
if ( self::is_unique_title( $title, $form_id ) ) {
return $title;
}
// Check for form count in form title.
preg_match_all( '/(\\(([0-9])*\\))$/mi', $title, $count_exists_in_title );
// If count does not exist, set count to 1.
if ( empty( $count_exists_in_title[0] ) ) {
// Set initial count.
$count = 1;
} else {
// Set existing count to current count plus one.
$count = (int) $count_exists_in_title[2][0] + 1;
// Remove existing count from title.
$title = preg_replace( '/(\\(([0-9])*\\))$/mi', null, $title );
}
// Trim title.
$title = trim( $title );
// Add copy count to form title.
$new_title = $title . " ($count)";
// If new form title is not unique, increment the count until a unique form title is created.
while ( ! self::is_unique_title( $new_title, $form_id ) ) {
$count++;
$new_title = $title . " ($count)";
}
return $new_title;
}
public static function ensure_tables_exist() {
global $wpdb;
$form_table_name = self::get_form_table_name();
$form_count = $wpdb->get_var( "SELECT count(0) FROM {$form_table_name}" );
if ( $wpdb->last_error ) {
GFCommon::log_debug( 'GFFormsModel::ensure_tables_exist(): Blog ' . get_current_blog_id() . ' - Form database table does not exist. Forcing database setup.' );
gf_upgrade()->upgrade_schema();
}
}
public static function insert_form( $form_title ) {
global $wpdb;
$form_table_name = self::get_form_table_name();
//creating new form
$wpdb->query( $wpdb->prepare( "INSERT INTO $form_table_name(title, date_created) VALUES(%s, utc_timestamp())", $form_title ) );
//returning newly created form id
return $wpdb->insert_id;
}
/**
* Update form meta.
*
* @since 2.4 Added the form revision creation functionality.
*
* @param int $form_id Form id.
* @param array $form_meta Form meta.
* @param string $meta_name Meta name.
*
* @return false|int Number of rows affected/selected or false on error.
*/
public static function update_form_meta( $form_id, $form_meta, $meta_name = 'display_meta' ) {
global $wpdb;
$form_meta = gf_apply_filters( array( 'gform_form_update_meta', $form_id ), $form_meta, $form_id, $meta_name );
$meta_table_name = self::get_meta_table_name();
$new_display_meta = $form_meta;
$form_meta = json_encode( $form_meta );
if ( $meta_name === 'display_meta' ) {
self::maybe_create_form_revision( $new_display_meta, $form_id );
}
if ( intval( $wpdb->get_var( $wpdb->prepare( "SELECT count(0) FROM $meta_table_name WHERE form_id=%d", $form_id ) ) ) > 0 ) {
$result = $wpdb->query( $wpdb->prepare( "UPDATE $meta_table_name SET $meta_name=%s WHERE form_id=%d", $form_meta, $form_id ) );
} else {
$result = $wpdb->query( $wpdb->prepare( "INSERT INTO $meta_table_name(form_id, $meta_name) VALUES(%d, %s)", $form_id, $form_meta ) );
}
self::flush_current_form( self::get_form_cache_key( $form_id ) );
/**
* Fires after form meta has been updated for any form
*
* @param mixed $form_meta The Form Meta object from the database
* @param int $form_id The ID of the form data was updated
* @param string $meta_name The name of the meta updated
*/
gf_do_action( array( 'gform_post_update_form_meta', $form_id ), $form_meta, $form_id, $meta_name );
return $result;
}
/**
* Create form revision if conditions met.
*
* @since 2.4
*
* @param array $new_display_meta Form meta.
* @param int $form_id Form ID.
*/
public static function maybe_create_form_revision( $new_display_meta, $form_id ) {
global $wpdb;
// Make sure the form isn't in the cache before calling get_form_meta().
self::flush_current_form( self::get_form_cache_key( $form_id ) );
$form = self::get_form_meta( $form_id );
// check if form has consent field.
if ( GFCommon::has_consent_field( $new_display_meta ) ) {
$revisions_table_name = self::get_form_revisions_table_name();
// create the first revision.
if ( intval( $wpdb->get_var( $wpdb->prepare( "SELECT count(0) FROM $revisions_table_name WHERE form_id=%d", $form_id ) ) ) === 0 ) {
$wpdb->query( $wpdb->prepare( "INSERT INTO $revisions_table_name(form_id, display_meta, date_created) VALUES(%d, %s, utc_timestamp())", $form_id, json_encode( $new_display_meta ) ) );
return;
}
$old_consent_fields = array();
foreach ( $form['fields'] as $field ) {
if ( $field->get_input_type() === 'consent' ) {
$old_consent_fields[ $field->id ] = $field->description;
}
}
// check if consent field description changed.
$create_revision = false;
foreach ( $new_display_meta['fields'] as $field ) {
if ( self::get_input_type( $field ) === 'consent' ) {
if ( $field['description'] !== rgar( $old_consent_fields, $field['id'] ) ) {
$create_revision = true;
break;
}
}
}
if ( $create_revision ) {
$wpdb->query( $wpdb->prepare( "INSERT INTO $revisions_table_name(form_id, display_meta, date_created) VALUES(%d, %s, utc_timestamp())", $form_id, json_encode( $new_display_meta ) ) );
}
}
}
/**
* Get the latest revision ID from form revisions.
*
* @since 2.4
*
* @param int $form_id Form ID.
*
* @return int Revision ID.
*/
public static function get_latest_form_revisions_id( $form_id ) {
global $wpdb;
$revisions_table_name = GFFormsModel::get_form_revisions_table_name();
$value = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $revisions_table_name WHERE form_id=%d ORDER BY date_created DESC, id DESC LIMIT 1", $form_id ) );
return $value;
}
public static function delete_files( $lead_id, $form = null ) {
$lead = self::get_lead( $lead_id );
if ( $form == null ) {
$form = self::get_form_meta( $lead['form_id'] );
}
$field_types = self::get_delete_file_field_types( $form );
$fields = self::get_fields_by_type( $form, $field_types );
if ( is_array( $fields ) ) {
foreach ( $fields as $field ) {
if ( $field->multipleFiles ) {
$value_json = self::get_lead_field_value( $lead, $field );
if ( ! empty( $value_json ) ) {
$files = json_decode( $value_json, true );
if ( false === empty( $files ) && is_array( $files ) ) {
foreach ( $files as $file ) {
self::delete_physical_file( $file, $lead_id );
}
}
}
} else {
$value = self::get_lead_field_value( $lead, $field );
self::delete_physical_file( $value, $lead_id );
}
}
}
}
public static function delete_files_by_form( $form_id, $status = '' ) {
global $wpdb;
$entry_table_name = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_lead_table_name() : self::get_entry_table_name();
$form = self::get_form_meta( $form_id );
$field_types = self::get_delete_file_field_types( $form );
$fields = self::get_fields_by_type( $form, $field_types );
if ( empty( $fields ) ) {
return;
}
$status_filter = empty( $status ) ? '' : $wpdb->prepare( 'AND status=%s', $status );
$results = $wpdb->get_results( $wpdb->prepare( "SELECT id FROM {$entry_table_name} WHERE form_id=%d {$status_filter}", $form_id ), ARRAY_A );
foreach ( $results as $result ) {
self::delete_files( $result['id'], $form );
}
}
/**
* Returns an array of field types for which can uploaded files can be deleted.
*
* @since 2.4.6.1
*
* @param array $form The current form.
*
* @return array
*/
public static function get_delete_file_field_types( $form ) {
$field_types = array( 'fileupload', 'post_image' );
/**
* Allows more files to be deleted
*
* @since 1.9.10
*
* @param array $field_types Field types which contain file uploads
* @param array $form The Form Object
*/
return gf_apply_filters( array( 'gform_field_types_delete_files', $form['id'] ), $field_types, $form );
}
/**
* Deletes the uploaded files for the specified form and field.
*
* Note: Does not delete the file URLs from the entries, that is done by GFFormsModel::delete_field_values().
*
* @since 2.4.6.1
*
* @since 2.5.16 changed the query to return entry ID as well.
*
* @param int $form_id The current form ID.
* @param int $field_id The ID of field being deleted.
*/
public static function delete_field_files( $form_id, $field_id ) {
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return;
}
global $wpdb;
$entry_meta_table_name = self::get_entry_meta_table_name();
$results = $wpdb->get_results( $wpdb->prepare( "SELECT entry_id, meta_value FROM {$entry_meta_table_name} WHERE form_id=%d AND meta_key=%s", $form_id, $field_id ), ARRAY_A );
if ( is_array( $results ) ) {
foreach ( $results as $result ) {
if ( ! is_array( $result ) || empty( rgar( $result, 'meta_value' ) ) ) {
continue;
}
if ( strpos( $result['meta_value'], '[' ) === 0 ) {
// Value from a multi-file enabled field.
$files = json_decode( $result['meta_value'], true );
if ( is_array( $files ) ) {
foreach ( $files as $file ) {
self::delete_physical_file( $file, $result['entry_id'] );
}
}
} else {
// Value from a single file or post image field.
self::delete_physical_file( $result['meta_value'], $result['entry_id'] );
}
}
}
}
public static function delete_file( $entry_id, $field_id, $file_index = 0 ) {
global $wpdb;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
GF_Forms_Model_Legacy::delete_file( $entry_id, $field_id, $file_index );
return;
}
if ( $entry_id == 0 || $field_id == 0 ) {
return;
}
$entry = self::get_lead( $entry_id );
$form_id = $entry['form_id'];
$form = self::get_form_meta( $form_id );
$field = self::get_field( $form, $field_id );
$multiple_files = $field->multipleFiles;
if ( $multiple_files ) {
$file_urls = json_decode( $entry[ $field_id ], true );
$file_url = $file_urls[ $file_index ];
unset( $file_urls[ $file_index ] );
$file_urls = array_values( $file_urls );
$field_value = empty( $file_urls ) ? '' : json_encode( $file_urls );
} else {
$file_url = $entry[ $field_id ];
$field_value = '';
}
self::delete_physical_file( $file_url, $entry_id );
// Update entry field value - simulate form submission.
$entry_meta_table_name = self::get_entry_meta_table_name();
$sql = $wpdb->prepare( "SELECT id FROM {$entry_meta_table_name} WHERE entry_id=%d AND meta_key = %s", $entry_id, $field_id );
$entry_meta_id = $wpdb->get_var( $sql );
self::update_entry_field_value( $form, $entry, $field, $entry_meta_id, $field_id, $field_value );
}
private static function delete_physical_file( $file_url, $entry_id ) {
$ary = explode( '|:|', $file_url );
$url = rgar( $ary, 0 );
if ( empty( $url ) ) {
return;
}
$file_path = self::get_physical_file_path( $url, $entry_id );
/**
* Allow the file path to be overridden so files stored outside the /wp-content/uploads/gravity_forms/ directory can be deleted.
*
* @since 2.2.3.1
*
* @param string $file_path The path of the file to be deleted.
* @param string $url The URL of the file to be deleted.
*/
$file_path = apply_filters( 'gform_file_path_pre_delete_file', $file_path, $url );
if ( file_exists( $file_path ) ) {
unlink( $file_path );
gform_delete_meta( $entry_id, GF_Field_FileUpload::get_file_upload_path_meta_key_hash( $url ) );
}
}
/**
* Gets the physical path of a file.
*
* @since unknown
*
* @since 2.5.16 Added optional $entry_id parameter.
*
* @param string $url
* @param integer|null $entry_id
*
* @return string
*/
public static function get_physical_file_path( $url, $entry_id = null ) {
$path_info = GF_Field_FileUpload::get_file_upload_path_info( $url, $entry_id );
// convert from url to physical path
if ( is_multisite() && get_site_option( 'ms_files_rewriting' ) ) {
$file_path = preg_replace( "|^(.*?)/files/gravity_forms/|", BLOGUPLOADDIR . 'gravity_forms/', $url );
} else {
$file_path = str_replace( trailingslashit( $path_info['url'] ), trailingslashit( $path_info['path'] ), $url );
}
return $file_path;
}
public static function delete_field( $form_or_id, $field_id, $save_form = true ) {
$form = is_numeric( $form_or_id ) ? self::get_form_meta( $form_or_id ) : $form_or_id;
if ( empty( $form['id'] ) || ! isset( $form['fields'] ) || ! is_array( $form['fields'] ) ) {
return null;
}
$form_id = $form['id'];
/**
* Fires before a field is deleted
*
* @param int $form_id The ID of the form that the field is being deleted from
* @param int $field_id The ID of the field being deleted
*/
do_action( 'gform_before_delete_field', $form_id, $field_id );
$field_type = '';
$count = sizeof( $form['fields'] );
for ( $i = $count - 1; $i >= 0; $i -- ) {
/** @var GF_Field $field */
$field = $form['fields'][ $i ];
// Deleting associated conditional logic rules.
if ( ! empty( $field->conditionalLogic ) ) {
$field->conditionalLogic = self::delete_field_from_conditional_logic( $field->conditionalLogic, $field_id );
}
if ( $field->type === 'page' && ! empty( $field->nextButton['conditionalLogic'] ) ) {
$field->nextButton['conditionalLogic'] = self::delete_field_from_conditional_logic( $field->nextButton['conditionalLogic'], $field_id );
}
// Deleting field from form meta.
if ( $field->id == $field_id ) {
$field_type = $field->type;
unset( $form['fields'][ $i ] );
}
}
// The field has already been removed from the form passed by GFFormDetail::save_form_info(), get the field from the db.
if ( empty( $field_type ) && $deleted_field = self::get_field( $form_id, $field_id ) ) {
$field_type = $deleted_field->type;
}
// Removing post content and title template if the field being deleted is a post content field or post title field.
if ( $field_type == 'post_content' ) {
$form['postContentTemplateEnabled'] = false;
$form['postContentTemplate'] = '';
} else if ( $field_type == 'post_title' ) {
$form['postTitleTemplateEnabled'] = false;
$form['postTitleTemplate'] = '';
}
if ( ! empty( $form['button']['conditionalLogic'] ) ) {
$form['button']['conditionalLogic'] = self::delete_field_from_conditional_logic( $form['button']['conditionalLogic'], $field_id );
}
// Notifications/confirmations are not present in the form passed by GFFormDetail::save_form_info() but they could be present in other scenarios.
$form = GFFormsModel::delete_field_from_confirmations( $form, $field_id );
$form = GFFormsModel::delete_field_from_notifications( $form, $field_id );
if ( in_array( $field_type, self::get_delete_file_field_types( $form ) ) ) {
self::delete_field_files( $form_id, $field_id );
}
$form['fields'] = array_values( $form['fields'] );
if ( $save_form ) {
self::update_form_meta( $form_id, $form );
}
//Delete from grid column meta
$columns = self::get_grid_column_meta( $form_id );
if ( is_array( $columns ) ) {
$count = sizeof( $columns );
for ( $i = $count - 1; $i >= 0; $i -- ) {
if ( intval( rgar( $columns, $i ) ) == intval( $field_id ) ) {
unset( $columns[ $i ] );
}
}
self::update_grid_column_meta( $form_id, $columns );
}
self::delete_field_values( $form_id, $field_id );
/**
* Fires after a field is deleted
*
* @param int $form_id The form ID where the form was deleted
* @param int $field_id The ID of the field that was deleted
*
*/
do_action( 'gform_after_delete_field', $form_id, $field_id );
return $form;
}
/**
* Deletes confirmation conditional logic rules based on the deleted field.
*
* @since 2.4.6.1
*
* @param array $form The form containing the confirmations to be processed.
* @param int $field_id The ID of the field being deleted.
*
* @return array
*/
public static function delete_field_from_confirmations( $form, $field_id ) {
if ( empty( $form['confirmations'] ) ) {
return $form;
}
$save = false;
foreach ( $form['confirmations'] as &$confirmation ) {
if ( ! empty( $confirmation['conditionalLogic'] ) ) {
$processed = self::delete_field_from_conditional_logic( $confirmation['conditionalLogic'], $field_id );
if ( $confirmation['conditionalLogic'] != $processed ) {
$confirmation['conditionalLogic'] = $processed;
$save = true;
}
}
}
if ( $save ) {
GFFormsModel::update_form_meta( $form['id'], $form['confirmations'], 'confirmations' );
}
return $form;
}
/**
* Deletes notification routing and conditional logic rules based on the deleted field.
*
* @since 2.4.6.1
*
* @param array $form The form containing the notifications to be processed.
* @param int $field_id The ID of the field being deleted.
*
* @return array
*/
public static function delete_field_from_notifications( $form, $field_id ) {
if ( empty( $form['notifications'] ) ) {
return $form;
}
$save = false;
foreach ( $form['notifications'] as &$notification ) {
if ( ! empty( $notification['routing'] ) ) {
$dirty = false;
foreach ( $notification['routing'] as $key => $rule ) {
if ( intval( rgar( $rule, 'fieldId' ) ) == $field_id ) {
unset( $notification['routing'][ $key ] );
$dirty = true;
}
}
if ( $dirty ) {
$notification['routing'] = empty( $notification['routing'] ) ? null : array_values( $notification['routing'] );
$save = true;
}
}
if ( ! empty( $notification['conditionalLogic'] ) ) {
$processed = self::delete_field_from_conditional_logic( $notification['conditionalLogic'], $field_id );
if ( $notification['conditionalLogic'] != $processed ) {
$notification['conditionalLogic'] = $processed;
$save = true;
}
}
}
if ( $save ) {
GFFormsModel::update_form_meta( $form['id'], $form['notifications'], 'notifications' );
}
return $form;
}
/**
* Deletes conditional logic rules based on the deleted field.
*
* If no rules remain following the deletion conditional logic is disabled.
*
* @since 2.4.6.1
*
* @param array $logic The conditional logic object to be processed.
* @param int $field_id The ID of the field being deleted.
*
* @return null|array
*/
public static function delete_field_from_conditional_logic( $logic, $field_id ) {
if ( empty( $logic['rules'] ) ) {
return null;
}
$dirty = false;
foreach ( $logic['rules'] as $key => $rule ) {
if ( intval( rgar( $rule, 'fieldId' ) ) == $field_id ) {
unset( $logic['rules'][ $key ] );
$dirty = true;
}
}
if ( $dirty ) {
if ( empty( $logic['rules'] ) ) {
$logic = null;
} else {
$logic['rules'] = array_values( $logic['rules'] );
}
}
return $logic;
}
public static function delete_field_values( $form_id, $field_id ) {
global $wpdb;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
GF_Forms_Model_Legacy::delete_field_values( $form_id, $field_id );
return;
}
$entry_table = self::get_entry_table_name();
$entry_meta_table = self::get_entry_meta_table_name();
// Delete from entry meta
$sql = $wpdb->prepare( "DELETE FROM $entry_meta_table WHERE form_id=%d AND meta_key = %s", $form_id, $field_id );
if ( is_numeric( $field_id ) ) {
$sql .= $wpdb->prepare( " OR form_id=%d AND meta_key LIKE %s", $form_id, sprintf( '%d.%%', $field_id ) );
}
$wpdb->query( $sql );
// Delete leads with no details
$sql = $wpdb->prepare(
" DELETE FROM $entry_table
WHERE form_id=%d
AND id NOT IN(
SELECT DISTINCT(entry_id) FROM $entry_meta_table WHERE form_id=%d
)", $form_id, $form_id
);
$wpdb->query( $sql );
}
/**
* Deletes a lead.
*
* @param $lead_id
*/
public static function delete_lead( $lead_id ) {
self::delete_entry( $lead_id );
}
public static function delete_entry( $entry_id ) {
global $wpdb, $current_user;
// Log if user requested deletion of entries
if ( ! empty( $current_user->user_login ) ) {
GFCommon::log_debug( __METHOD__ . "(): User ID {$current_user->ID} requested deletion of entry #{$entry_id}" );
}
if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
GF_Forms_Model_Legacy::delete_lead( $entry_id );
return;
}
GFCommon::log_debug( __METHOD__ . "(): Deleting entry #{$entry_id}." );
/**
* Fires before an entry is deleted.
*
* @param $entry_id
*/
do_action( 'gform_delete_entry', $entry_id );
/**
* Fires before a lead is deleted
* @param $lead_id
* @deprecated Use gform_delete_entry instead
* @see gform_delete_entry
*/
do_action( 'gform_delete_lead', $entry_id );
$entry_table = self::get_entry_table_name();
$entry_notes_table = self::get_entry_notes_table_name();
$entry_meta_table_name = self::get_entry_meta_table_name();
// Deleting uploaded files
self::delete_files( $entry_id );
// Delete from entry meta
$sql = $wpdb->prepare( "DELETE FROM $entry_meta_table_name WHERE entry_id=%d", $entry_id );
$wpdb->query( $sql );
// Delete from lead notes
$sql = $wpdb->prepare( "DELETE FROM $entry_notes_table WHERE entry_id=%d", $entry_id );
$wpdb->query( $sql );
// Delete from entry table
$sql = $wpdb->prepare( "DELETE FROM $entry_table WHERE id=%d", $entry_id );
$wpdb->query( $sql );
}
/**
* Adds a note.
*
* @since 2.4.18 Return statement added.
* @since unknown
*
* @param int $entry_id ID of the entry to add the note to.
* @param int $user_id ID of the user who created the note.
* @param string $user_name Name of the user who created the note.
* @param string $note Text of the note.
* @param string $note_type Note type.
* @param null $sub_type Note sub-type.
* @return int ID of the new note.
*/
public static function add_note( $entry_id, $user_id, $user_name, $note, $note_type = 'user', $sub_type = null ) {
global $wpdb;
if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
GF_Forms_Model_Legacy::add_note( $entry_id, $user_id, $user_name, $note, $note_type );
return;
}
$table_name = self::get_entry_notes_table_name();
$sql = $wpdb->prepare( "INSERT INTO $table_name(entry_id, user_id, user_name, value, note_type, sub_type, date_created) values(%d, %d, %s, %s, %s, %s, utc_timestamp())", $entry_id, $user_id, $user_name, $note, $note_type, $sub_type );
$wpdb->query( $sql );
/**
* Fires after a note has been added to an entry
*
* @since 2.4.13 Added sub_type parameter.
* @since Unknown
*
* @param int $wpdb->insert_id The row ID of this note in the database
* @param int $entry_id The ID of the entry that the note was added to
* @param int $user_id The ID of the current user adding the note
* @param string $user_name The user name of the current user
* @param string $note The content of the note being added
* @param string $note_type The type of note being added. Defaults to 'note'
* @param string $sub_type The sub-type of note being added.
*
*/
do_action( 'gform_post_note_added', $wpdb->insert_id, $entry_id, $user_id, $user_name, $note, $note_type, $sub_type );
return $wpdb->insert_id;
}
/**
* Updates a note.
*
* @since 2.4.18
*
* @param int $note_id ID of the note to update.
* @param int $entry_id ID of the entry to add the note to.
* @param int $user_id ID of the user who created the note.
* @param string $user_name Name of the user who created the note.
* @param string $date_created Date and time the note was created in SQL datetime format.
* @param string $note Text of the note.
* @param string $note_type Note type.
* @param null $sub_type Note sub-type.
* @return string
*/
public static function update_note( $note_id, $entry_id, $user_id, $user_name, $date_created, $note, $note_type = 'user', $sub_type = null ) {
global $wpdb;
$table_name = self::get_entry_notes_table_name();
$sql = $wpdb->prepare(
"
UPDATE $table_name
SET
entry_id = %d,
user_id = %d,
user_name = %s,
date_created = %s,
value = %s,
note_type = %s,
sub_type = %s
WHERE
id = %d
", $entry_id, $user_id, $user_name, $date_created, $note, $note_type, $sub_type, $note_id
);
$result = $wpdb->query( $sql );
return $result;
}
/**
* Deletes a note.
*
* @since 2.4.18 Return statement added.
* @since unknown
*
* @param $note_id int ID of the note to delete.
* @return bool|int Success or failure.
*/
public static function delete_note( $note_id ) {
global $wpdb;
if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
GF_Forms_Model_Legacy::delete_note( $note_id );
return;
}
$table_name = self::get_entry_notes_table_name();
$lead_id = $wpdb->get_var( $wpdb->prepare( "SELECT entry_id FROM $table_name WHERE id = %d", $note_id ) );
/**
* Fires before a note is deleted
*
* @param int $note_id The current note ID
* @param int $lead_id The current lead ID
*/
do_action( 'gform_pre_note_deleted', $note_id, $lead_id );
$sql = $wpdb->prepare( "DELETE FROM $table_name WHERE id=%d", $note_id );
$result = $wpdb->query( $sql );
return $result;
}
public static function delete_notes( $notes ) {
if ( ! is_array( $notes ) ) {
return;
}
foreach ( $notes as $note_id ) {
self::delete_note( $note_id );
}
}
/**
* Add note to entry containing the notification sending result
*
* @since 2.4.14
*
* @param integer $entry_id Id number for entry being processed.
* @param string|boolean $result The result returned by wp_mail().
* @param array $notification The notification properties.
* @param string $error_info Additional details for notifications with error.
* @param array $email Array containing email details.
* @param array $note_args Array containing text, type and subtype for the note.
*/
public static function add_notification_note( $entry_id, $result, $notification, $error_info = '', $email = array(), $note_args = array() ) {
// Skip if no entry id (e.g. Save and Continue notifications).
if ( empty( $entry_id ) ) {
return;
}
// If $note_args is empty, use default arguments for Gravity Forms core notifications.
if ( empty( $note_args ) ) {
if ( $result === true ) {
$note_args['type'] = 'notification';
$note_args['subtype'] = 'success';
$note_args['text'] = esc_html__( 'WordPress successfully passed the notification email to the sending server.', 'gravityforms' );
} elseif ( $result === false ) {
$note_args['type'] = 'notification';
$note_args['subtype'] = 'error';
$note_args['text'] = esc_html__( 'WordPress was unable to send the notification email.', 'gravityforms' );
// Add additional error message if any.
if ( ! empty( $error_info ) ) {
$note_args['text'] .= ' ' . $error_info;
}
}
}
/**
* Allow customization of the Sending Result Note.
*
* @param array $note_args Array containing text, type and subtype for the note.
* @param int $entry_id Id number for entry being processed.
* @param bool $result The result returned by wp_mail().
* @param array $notification The notification properties.
* @param string $error_info Additional details for notifications with error.
* @param array $email Array containing email details.
*/
$note_args = apply_filters( 'gform_notification_note', $note_args, $entry_id, $result, $notification, $error_info, $email );
if ( ! empty( $note_args['text'] ) ) {
// translators: Notification name followed by its ID. e.g. Admin Notification (ID: 5d4c0a2a37204).
self::add_note( $entry_id, 0, sprintf( esc_html__( '%1$s (ID: %2$s)', 'gravityforms' ), $notification['name'], $notification['id'] ), $note_args['text'], $note_args['type'], $note_args['subtype'] );
}
}
/**
* Gets the IP to be used within the entry.
*
* @since 2.2 Using $_SERVER['REMOTE_ADDR'].
*
* @return string The IP to be stored in the entry.
*/
public static function get_ip() {
$ip = rgar( $_SERVER, 'REMOTE_ADDR' );
/**
* Allows the IP address of the client to be modified.
*
* Use this filter if the server is behind a proxy.
*
* @since 2.2
* @example https://docs.gravityforms.com/gform_ip_address/
*
* @param string $ip The IP being used.
*/
$ip = apply_filters( 'gform_ip_address', $ip );
// HTTP_X_FORWARDED_FOR can return a comma separated list of IPs; use the first one
$ips = explode( ',', $ip );
return $ips[0];
}
public static function save_lead( $form, &$entry ) {
self::save_entry( $form, $entry );
}
/**
* Save Entry to database.
*
* @since 2.4.8.13 Updated created_by property to save as an empty value when undefined.
* @since Unknown
*
* @param array $form Form object.
* @param array $entry Entry object.
*/
public static function save_entry( $form, &$entry ) {
global $wpdb, $current_user;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
GF_Forms_Model_Legacy::save_lead( $form, $entry );
$entry = GFAPI::get_entry( $entry['id'] );
return;
}
GFCommon::timer_start( __METHOD__ );
GFCommon::log_debug( __METHOD__ . '(): Saving entry.' );
$is_form_editor = GFCommon::is_form_editor();
$is_entry_detail = GFCommon::is_entry_detail();
$is_admin = $is_form_editor || $is_entry_detail;
if ( $is_admin && ! GFCommon::current_user_can_any( 'gravityforms_edit_entries' ) ) {
wp_die( esc_html__( "You don't have adequate permission to edit entries.", 'gravityforms' ) );
}
$entry_meta_table = self::get_entry_meta_table_name();
$is_new_lead = empty( $entry );
// Log user login for user updating the entry
if ( ! $is_new_lead && ! empty( $entry['id'] ) && ! empty( $current_user->ID ) ) {
GFCommon::log_debug( __METHOD__ . "(): User ID {$current_user->ID} requested update of entry #{$entry['id']}." );
}
if ( ! $is_new_lead && ! self::entry_exists( rgar( $entry, 'id' ) ) ) {
// Force a new entry to be saved when an entry does not exist for the supplied id.
$entry = array();
$is_new_lead = true;
GFCommon::log_debug( __METHOD__ . '(): Existing entry not found. Saving new entry instead.' );
}
$die_message = esc_html__( 'An error prevented the entry for this form submission being saved. Please contact support.', 'gravityforms' );
$entry_table = GFFormsModel::get_entry_table_name();
$current_date = $wpdb->get_var( 'SELECT utc_timestamp()' );
if ( $is_new_lead ) {
// Saving the new entry.
$user_id = $current_user && $current_user->ID ? $current_user->ID : null;
$user_agent = self::truncate( rgar( $_SERVER, 'HTTP_USER_AGENT' ), 250 );
$user_agent = sanitize_text_field( $user_agent );
$source_url = self::truncate( self::get_current_page_url(), 200 );
/**
* Allow the currency code to be overridden.
*
* @param string $currency The three character ISO currency code to be stored in the entry. Default is value returned by GFCommon::get_currency()
* @param array $form The form currently being processed.
*
*/
$currency = gf_apply_filters( array( 'gform_currency_pre_save_entry', $form['id'] ), GFCommon::get_currency(), $form );
$ip = rgars( $form, 'personalData/preventIP' ) ? '' : self::get_ip();
$wpdb->insert(
$entry_table,
array(
'form_id' => $form['id'],
'ip' => $ip,
'source_url' => $source_url,
'date_created' => $current_date,
'date_updated' => $current_date,
'user_agent' => $user_agent,
'currency' => $currency,
'created_by' => $user_id,
),
array(
'form_id' => '%d',
'ip' => '%s',
'source_url' => '%s',
'date_created' => '%s',
'date_updated' => '%s',
'user_agent' => '%s',
'currency' => '%s',
'created_by' => '%s',
)
);
// Reading newly created lead id
$lead_id = $wpdb->insert_id;
if ( $lead_id == 0 ) {
GFCommon::log_error( __METHOD__ . '(): Unable to save entry. ' . $wpdb->last_error );
wp_die( $die_message );
}
$entry = array(
'id' => (string) $lead_id,
'status' => 'active',
'form_id' => (string) $form['id'],
'ip' => $ip,
'source_url' => $source_url,
'currency' => $currency,
'post_id' => null,
'date_created' => $current_date,
'date_updated' => $current_date,
'is_starred' => 0,
'is_read' => 0,
'user_agent' => $user_agent,
'payment_status' => null,
'payment_date' => null,
'payment_amount' => null,
'payment_method' => '',
'transaction_id' => null,
'is_fulfilled' => null,
'created_by' => (string) $user_id,
'transaction_type' => null,
);
GFCommon::log_debug( __METHOD__ . "(): Entry record created in the database. ID: {$lead_id}." );
} else {
GFCommon::log_debug( __METHOD__ . "(): Updating existing entry. ID: {$entry['id']}." );
// Ensures the entry being updated contains all the current properties and registered meta.
self::add_properties_to_entry( $entry );
self::add_meta_to_entry( $entry );
GFAPI::update_entry_property( $entry['id'], 'date_updated', $current_date );
$entry['date_updated'] = $current_date;
}
$current_fields = $wpdb->get_results( $wpdb->prepare( "SELECT id, meta_key, item_index FROM $entry_meta_table WHERE entry_id=%d", $entry['id'] ) );
$total_fields = array();
/* @var $calculation_fields GF_Field[] */
$calculation_fields = array();
$recalculate_total = false;
GFCommon::log_debug( __METHOD__ . '(): Saving entry fields.' );
GFFormsModel::begin_batch_field_operations();
foreach ( $form['fields'] as $field ) {
/* @var $field GF_Field */
// ignore the honeypot field
if ( $field->type == 'honeypot' ) {
continue;
}
//Ignore fields that are marked as display only
if ( $field->displayOnly ) {
continue;
}
// Ignore pricing fields in the entry detail
if ( $is_entry_detail && GFCommon::is_pricing_field( $field->type ) ) {
continue;
}
// Process total field after all fields have been saved
if ( $field->type == 'total' ) {
$total_fields[] = $field;
continue;
}
/**
* Specify whether to fetch values from the $_POST when evaluating a field's conditional logic. Defaults to true
* for new entries and false for existing entries.
*
* @since 2.3.1.11
*
* @param bool $read_value_from_post Should value be fetched from $_POST?
* @param array $form The current form object.
* @param array $entry The current entry object.
*/
$read_value_from_post = gf_apply_filters( array( 'gform_use_post_value_for_conditional_logic_save_entry', $form['id'] ), $is_new_lead || ! isset( $entry[ 'date_created' ] ), $form, $entry );
// Only save fields that are not hidden (except when updating an entry)
if ( $is_entry_detail || ! GFFormsModel::is_field_hidden( $form, $field, array(), $read_value_from_post ? null : $entry ) ) {
// process calculation fields after all fields have been saved (moved after the is hidden check)
if ( $field->has_calculation() ) {
$calculation_fields[] = $field;
continue;
}
if ( $field->type == 'post_category' ) {
$field = GFCommon::add_categories_as_choices( $field, '' );
}
$inputs = $field->get_entry_inputs();
if ( is_array( $inputs ) ) {
foreach ( $inputs as $input ) {
self::save_input( $form, $field, $entry, $current_fields, $input['id'] );
}
} else {
self::save_input( $form, $field, $entry, $current_fields, $field->id );
}
}
}
$results = GFFormsModel::commit_batch_field_operations();
if ( $is_new_lead && is_wp_error( $results['inserts'] ) ) {
/* @var WP_Error $error */
$error = $results['inserts'];
GFCommon::log_error( __METHOD__ . '(): Error while saving field values for new entry. ' . $error->get_error_message() );
self::add_note( $entry['id'], 0, 'Gravity Forms', sprintf( esc_html__( 'Error while saving field values: %s', 'gravityforms' ), $error->get_error_message() ), 'save_entry', 'error' );
wp_die( $die_message );
}
if ( ! empty( $calculation_fields ) ) {
GFFormsModel::begin_batch_field_operations();
foreach ( $calculation_fields as $calculation_field ) {
$inputs = $calculation_field->get_entry_inputs();
if ( is_array( $inputs ) ) {
foreach ( $inputs as $input ) {
self::save_input( $form, $calculation_field, $entry, $current_fields, $input['id'] );
}
} else {
self::save_input( $form, $calculation_field, $entry, $current_fields, $calculation_field->id );
}
}
$results = GFFormsModel::commit_batch_field_operations();
if ( $is_new_lead && is_wp_error( $results['inserts'] ) ) {
/* @var WP_Error $error */
$error = $results['inserts'];
GFCommon::log_error( __METHOD__ . '(): Error while saving calculation field values for new entry. ' . $error->get_error_message() );
self::add_note( $entry['id'], 0, 'Gravity Forms', sprintf( esc_html__( 'Error while saving calculation field values: %s', 'gravityforms' ), $error->get_error_message() ), 'save_entry', 'error' );
wp_die( $die_message );
}
self::refresh_product_cache( $form, $entry );
}
//saving total field as the last field of the form.
if ( ! empty( $total_fields ) ) {
GFFormsModel::begin_batch_field_operations();
foreach ( $total_fields as $total_field ) {
self::save_input( $form, $total_field, $entry, $current_fields, $total_field->id );
}
$results = GFFormsModel::commit_batch_field_operations();
if ( $is_new_lead && is_wp_error( $results['inserts'] ) ) {
/* @var WP_Error $error */
$error = $results['inserts'];
GFCommon::log_error( __METHOD__ . '(): Error while saving total field values for new entry. ' . $error->get_error_message() );
self::add_note( $entry['id'], 0, 'Gravity Forms', sprintf( esc_html__( 'Error while saving total field values: %s', 'gravityforms' ), $error->get_error_message() ), 'save_entry', 'error' );
wp_die( $die_message );
}
}
$processor = self::get_entry_meta_batch_processor();
$processor::begin_batch_entry_meta_operations();
foreach ( $form['fields'] as $field ) {
/* @var GF_Field $field */
if ( $field->displayOnly ) {
continue;
}
$inputs = $field->get_entry_inputs();
if ( is_array( $inputs ) ) {
foreach ( $inputs as $input ) {
$entry[ (string) $input['id'] ] = gf_apply_filters( array( 'gform_get_input_value', $form['id'], $field->id, $input['id'] ), rgar( $entry, (string) $input['id'] ), $entry, $field, $input['id'] );
}
} else {
$value = rgar( $entry, (string) $field->id );
if ( GFFormsModel::is_openssl_encrypted_field( $entry['id'], $field->id ) ) {
$value = GFCommon::openssl_decrypt( $value );
}
$entry[ (string) $field->id ] = gf_apply_filters( array( 'gform_get_input_value', $form['id'], $field->id ), $value, $entry, $field, '' );
}
self::save_extra_field_meta( $field, $form, $entry );
}
$processor::commit_batch_entry_meta_operations();
self::hydrate_repeaters( $entry, $form );
GFCommon::log_debug( __METHOD__ . '(): Finished saving entry fields.' );
GFCommon::log_debug( __METHOD__ . sprintf( '(): %s entry completed in %F seconds.', $is_new_lead ? 'Saving' : 'Updating', GFCommon::timer_end( __METHOD__ ) ) );
}
/**
* Gets the extra meta data a field wants to save to the entry and updates the entry meta with the retrieved data.
*
* This method depends on batch operations, so begin_batch_entry_meta_operations should be called before this method is called
* and commit_batch_entry_meta_operations should be called after.
*
* @since 2.5.16
*
* @param $field
* @param $form
* @param $entry
*/
public static function save_extra_field_meta( $field, $form, $entry ) {
$extra_meta = $field->get_extra_entry_metadata( $form, $entry );
foreach( $extra_meta as $key => $value ) {
$processor = self::get_entry_meta_batch_processor();
$processor::queue_batch_entry_meta_operation( $form, $entry, $key, $value );
}
}
/**
* Creates a new instance of the entry meta batch operations handler if required and returns it.
*
* @since 2.5.16
*
* @return GF_Entry_Meta_Batch_Processor
*/
public static function get_entry_meta_batch_processor() {
if ( is_null( self::$entry_meta_batch_processor ) ) {
$processor = GFForms::get_service_container()->get( GF_Batch_Operations_Service_Provider::ENTRY_META_BATCH_PROCESSOR );
self::$entry_meta_batch_processor = $processor;
}
return self::$entry_meta_batch_processor;
}
/**
* Populates the supplied entry with missing properties.
*
* @since 2.4.5.8
*
* @param array $entry The partial or complete entry currently being updated.
*/
public static function add_properties_to_entry( &$entry ) {
if ( empty( $entry['id'] ) ) {
return;
}
global $wpdb;
$entry_table = GFFormsModel::get_entry_table_name();
$sql = $wpdb->prepare( "SELECT * FROM $entry_table WHERE id=%d", $entry['id'] );
$properties = $wpdb->get_row( $sql, ARRAY_A );
foreach ( $properties as $key => $property ) {
if ( ! isset( $entry[ (string) $key ] ) ) {
// Add the missing entry property.
$entry[ (string) $key ] = $properties[ $key ];
}
}
}
/**
* Populates the supplied entry with missing meta.
*
* @since 2.4.5.8
*
* @param array $entry The partial or complete entry currently being updated.
*/
public static function add_meta_to_entry( &$entry ) {
if ( empty( $entry['id'] ) || empty( $entry['form_id'] ) ) {
return;
}
$meta_keys = array_keys( self::get_entry_meta( $entry['form_id'] ) );
foreach ( $meta_keys as $meta_key ) {
if ( ! isset( $entry[ $meta_key ] ) ) {
// Add the missing entry meta.
$entry[ $meta_key ] = gform_get_meta( $entry['id'], $meta_key );
}
}
}
/**
* Fill the repeater field with the entry data.
*
* @since Unknown.
* @since 2.5 Added the $apply_filters parameter.
*
* @param $entry
* @param $form
* @param bool $apply_filters Whether to apply the filter_input_value filter to the entry.
*/
public static function hydrate_repeaters( &$entry, $form, $apply_filters = false ) {
$fields = $form['fields'];
foreach ( $fields as $field ) {
if ( $field instanceof GF_Field_Repeater && isset( $field->fields ) && is_array( $field->fields ) ) {
/* @var GF_Field_Repeater $field */
$entry = $field->hydrate( $entry, $form );
}
}
}
public static function create_lead( $form ) {
GFCommon::timer_start( __METHOD__ );
$form_id = absint( rgar( $form, 'id' ) );
global $current_user;
$total_fields = array();
$calculation_fields = array();
$lead = array();
$lead['id'] = null;
$lead['post_id'] = null;
$lead['date_created'] = null;
$lead['date_updated'] = null;
$lead['form_id'] = rgar( $form, 'id' );
$lead['ip'] = rgars( $form, 'personalData/preventIP' ) ? '' : self::get_ip();
$source_url = self::truncate( self::get_current_page_url(), 200 );
$lead['source_url'] = esc_url_raw( $source_url );
$user_agent = self::truncate( rgar( $_SERVER, 'HTTP_USER_AGENT' ), 250 );
$lead['user_agent'] = sanitize_text_field( $user_agent );
$lead['created_by'] = $current_user && $current_user->ID ? $current_user->ID : 'NULL';
/**
* Allow the currency code to be overridden.
*
* @param string $currency The three character ISO currency code to be stored in the entry. Default is value returned by GFCommon::get_currency()
* @param array $form The form currently being processed.
*
*/
$lead['currency'] = gf_apply_filters( array( 'gform_currency_pre_save_entry', $form_id ), GFCommon::get_currency(), $form );
foreach ( $form['fields'] as $field ) {
/* @var $field GF_Field */
// ignore fields that are marked as display only
if ( $field->displayOnly ) {
continue;
}
// process total field after all fields have been saved
if ( $field->type == 'total' ) {
$total_fields[] = $field;
continue;
}
// process calculation fields after all fields have been saved
if ( $field->has_calculation() ) {
$calculation_fields[] = $field;
continue;
}
// only save fields that are not hidden
if ( ! RGFormsModel::is_field_hidden( $form, $field, array() ) ) {
if ( $field->type == 'post_category' ) {
$field = GFCommon::add_categories_as_choices( $field, '' );
}
$inputs = $field->get_entry_inputs();
if ( is_array( $inputs ) ) {
foreach ( $inputs as $input ) {
$lead[ (string) $input['id'] ] = self::get_prepared_input_value( $form, $field, $lead, $input['id'] );
}
} else {
$lead[ $field->id ] = self::get_prepared_input_value( $form, $field, $lead, $field->id );
}
}
}
if ( ! empty( $calculation_fields ) ) {
foreach ( $calculation_fields as $field ) {
/* @var $field GF_Field */
// only save fields that are not hidden
if ( RGFormsModel::is_field_hidden( $form, $field, array() ) ) {
continue;
}
$inputs = $field->get_entry_inputs();
if ( is_array( $inputs ) ) {
foreach ( $inputs as $input ) {
$lead[ (string) $input['id'] ] = self::get_prepared_input_value( $form, $field, $lead, $input['id'] );
}
} else {
$lead[ $field->id ] = self::get_prepared_input_value( $form, $field, $lead, $field->id );
}
}
self::refresh_product_cache( $form, $lead );
}
// saving total field as the last field of the form.
if ( ! empty( $total_fields ) ) {
foreach ( $total_fields as $total_field ) {
$lead[ $total_field->id ] = self::get_prepared_input_value( $form, $total_field, $lead, $total_field->id );
}
}
GFCommon::log_debug( __METHOD__ . sprintf( '(): Draft entry created for form (#%d) in %F seconds.', $form_id, GFCommon::timer_end( __METHOD__ ) ) );
return $lead;
}
public static function get_prepared_input_value( $form, $field, $lead, $input_id ) {
$input_name = 'input_' . str_replace( '.', '_', $input_id );
if ( $field->enableCopyValuesOption && rgpost( 'input_' . $field->id . '_copy_values_activated' ) ) {
$source_field_id = $field->copyValuesOptionField;
$source_input_name = str_replace( 'input_' . $field->id, 'input_' . $source_field_id, $input_name );
$value = rgpost( $source_input_name );
} else {
$value = rgpost( $input_name );
}
$value = self::maybe_trim_input( $value, $form['id'], $field );
$is_form_editor = GFCommon::is_form_editor();
$is_entry_detail = GFCommon::is_entry_detail();
$is_admin = $is_form_editor || $is_entry_detail;
if ( empty( $value ) && $field->is_administrative() && ! $is_admin ) {
$value = self::get_default_value( $field, $input_id );
}
switch ( self::get_input_type( $field ) ) {
case 'post_image':
$file_info = self::get_temp_filename( $form['id'], $input_name );
if ( ! empty( $file_info ) ) {
$file_path = self::get_file_upload_path( $form['id'], $file_info['uploaded_filename'] );
$url = $file_path['url'];
$image_alt = isset( $_POST[ "{$input_name}_2" ] ) ? strip_tags( $_POST[ "{$input_name}_2" ] ) : '';
$image_title = isset( $_POST[ "{$input_name}_1" ] ) ? strip_tags( $_POST[ "{$input_name}_1" ] ) : '';
$image_caption = isset( $_POST[ "{$input_name}_4" ] ) ? strip_tags( $_POST[ "{$input_name}_4" ] ) : '';
$image_description = isset( $_POST[ "{$input_name}_7" ] ) ? strip_tags( $_POST[ "{$input_name}_7" ] ) : '';
$value = ! empty( $url ) ? $url . '|:|' . $image_title . '|:|' . $image_caption . '|:|' . $image_description . '|:|' . $image_alt : '';
}
break;
case 'fileupload' :
if ( $field->multipleFiles ) {
if ( ! empty( $value ) ) {
$value = json_encode( $value );
}
} else {
$file_info = self::get_temp_filename( $form['id'], $input_name );
if ( ! empty( $file_info ) ) {
$file_path = self::get_file_upload_path( $form['id'], $file_info['uploaded_filename'] );
$value = $file_path['url'];
}
}
break;
default:
// processing values so that they are in the correct format for each input type
$value = self::prepare_value( $form, $field, $value, $input_name, rgar( $lead, 'id' ), $lead );
}
return gf_apply_filters( array( 'gform_save_field_value', $form['id'], $field->id ), $value, $lead, $field, $form, $input_id );
}
public static function refresh_product_cache( $form, $lead, $use_choice_text = false, $use_admin_label = false ) {
$cache_options = array(
array( false, false ),
array( false, true ),
array( true, false ),
array( true, true ),
);
foreach ( $cache_options as $cache_option ) {
list( $use_choice_text, $use_admin_label ) = $cache_option;
if ( gform_get_meta( rgar( $lead, 'id' ), "gform_product_info_{$use_choice_text}_{$use_admin_label}" ) ) {
gform_delete_meta( rgar( $lead, 'id' ), "gform_product_info_{$use_choice_text}_{$use_admin_label}" );
GFCommon::get_product_fields( $form, $lead, $use_choice_text, $use_admin_label );
}
}
}
/**
* Check whether a field is hidden via conditional logic.
*
* @param array $form Form object.
* @param GF_Field $field Field object.
* @param array $field_values Default field values for this form. Used when form has not yet been submitted. Pass an array if no default field values are available/required.
* @param array $lead Optional, default is null. If lead object is available, pass the lead.
*
* @return mixed
*/
public static function is_field_hidden( $form, $field, $field_values, $lead = null ) {
if ( empty( $field ) ) {
return false;
}
$cache_key = 'GFFormsModel::is_field_hidden_' . $form['id'] . '_' . $field->id;
if ( ! empty( $lead ) && isset( $lead['id'] ) ) {
// Make sure that we cache field visiblity on a per-entry basis
// https://github.com/gravityview/GravityView/issues/1307
$cache_key = $cache_key . '_' . $lead['id'];
}
$display = GFCache::get( $cache_key, $is_hit, false );
if ( $display !== false ) {
return $display;
}
$section = self::get_section( $form, $field->id );
$section_display = self::get_field_display( $form, $section, $field_values, $lead );
//if section is hidden, hide field no matter what. if section is visible, see if field is supposed to be visible
if ( $section_display == 'hide' ) {
$display = 'hide';
} else if ( self::is_page_hidden( $form, $field->pageNumber, $field_values, $lead ) ) {
$display = 'hide';
} else {
$display = self::get_field_display( $form, $field, $field_values, $lead );
return $display == 'hide';
}
GFCache::set( $cache_key, $display );
return $display == 'hide';
}
/***
* Determines if the submit button was supposed to be hidden by conditional logic. This function helps ensure that
* the form doesn't get submitted when the submit button is hidden by conditional logic.
*
* @param $form The Form object
*
* @return bool Returns true if the submit button is hidden by conditional logic, false otherwise.
*/
public static function is_submit_button_hidden( $form ) {
if( ! isset( $form['button']['conditionalLogic'] ) ){
return false;
}
$is_visible = self::evaluate_conditional_logic( $form, $form['button']['conditionalLogic'], array() );
return ! $is_visible;
}
public static function is_page_hidden( $form, $page_number, $field_values, $lead = null ) {
$page = self::get_page_by_number( $form, $page_number );
if ( ! $page ) {
return false;
}
$display = self::get_field_display( $form, $page, $field_values, $lead );
return $display == 'hide';
}
public static function get_page_by_number( $form, $page_number ) {
foreach ( $form['fields'] as $field ) {
if ( $field->type == 'page' && $field->pageNumber == $page_number ) {
return $field;
}
}
return null;
}
//gets the section that the specified field belongs to, or null if none
public static function get_section( $form, $field_id ) {
$current_section = null;
foreach ( $form['fields'] as $field ) {
if ( $field->type == 'section' ) {
$current_section = $field;
}
//stop section at a page break (sections don't go cross page)
if ( $field->type == 'page' ) {
$current_section = null;
}
if ( $field->id == $field_id ) {
return $current_section;
}
}
return null;
}
/**
* Determines if the field value matches the conditional logic rule value.
*
* @param mixed $field_value The field value to be checked.
* @param mixed $target_value The conditional logic rule value.
* @param string $operation The conditional logic rule operator.
* @param null|GF_Field $source_field The field the rule is based on.
* @param null|array $rule The conditional logic rule properties.
* @param null|array $form The current form.
*
* @return bool
*/
public static function is_value_match( $field_value, $target_value, $operation = 'is', $source_field = null, $rule = null, $form = null ) {
$is_match = false;
if ( $source_field && is_subclass_of( $source_field, 'GF_Field' ) ) {
if ( $source_field->type == 'post_category' ) {
$field_value = GFCommon::prepare_post_category_value( $field_value, $source_field, 'conditional_logic' );
} elseif ( $source_field instanceof GF_Field_MultiSelect && ! empty( $field_value ) && ! is_array( $field_value ) ) {
// Convert the comma-delimited string into an array.
$field_value = $source_field->to_array( $field_value );
} elseif ( $source_field->get_input_type() != 'checkbox' && is_array( $field_value ) && $source_field->id != $rule['fieldId'] && is_array( $source_field->get_entry_inputs() ) ) {
// Get the specific input value from the full field value.
$field_value = rgar( $field_value, $rule['fieldId'] );
}
}
$form_id = $source_field instanceof GF_Field ? $source_field->formId : 0;
$target_value = GFFormsModel::maybe_trim_input( $target_value, $form_id, $source_field );
if ( is_array( $field_value ) ) {
$field_value = array_values( $field_value ); // Returning array values, ignoring keys if array is associative.
$match_count = 0;
foreach ( $field_value as $val ) {
$val = GFFormsModel::maybe_trim_input( GFCommon::get_selection_value( $val ), $form_id, $source_field );
if ( self::matches_operation( $val, $target_value, $operation ) ) {
$match_count ++;
}
}
// If operation is Is Not, none of the values in the array can match the target value.
// Except when operation is Is Not Empty. In that case, one non-empty value is enough
$must_match_all = ( $operation == 'isnot' && ! rgblank( $target_value ) ) || ( $operation == 'is' && rgblank( $target_value ) );
$is_match = $must_match_all ? $match_count == count( $field_value ) : $match_count > 0;
} else if ( self::matches_operation( GFFormsModel::maybe_trim_input( GFCommon::get_selection_value( $field_value ), $form_id, $source_field ), $target_value, $operation ) ) {
$is_match = true;
}
return apply_filters( 'gform_is_value_match', $is_match, $field_value, $target_value, $operation, $source_field, $rule );
}
private static function try_convert_float( $text ) {
/*
global $wp_locale;
$number_format = $wp_locale->number_format['decimal_point'] == ',' ? 'decimal_comma' : 'decimal_dot';
if ( is_numeric( $text ) && $number_format == 'decimal_comma' ) {
return GFCommon::format_number( $text, 'decimal_comma' );
} else if ( GFCommon::is_numeric( $text, $number_format ) ) {
return GFCommon::clean_number( $text, $number_format );
}
*/
$number_format = 'decimal_dot';
if ( GFCommon::is_numeric( $text, 'currency' ) ) {
$number_format = 'currency';
}
if ( GFCommon::is_numeric( $text, $number_format ) ) {
return GFCommon::clean_number( $text, $number_format );
}
return 0;
}
public static function matches_operation( $val1, $val2, $operation ) {
$val1 = ! rgblank( $val1 ) ? strtolower( $val1 ) : '';
$val2 = ! rgblank( $val2 ) ? strtolower( $val2 ) : '';
switch ( $operation ) {
case 'is' :
return $val1 == $val2;
break;
case 'isnot' :
return $val1 != $val2;
break;
case 'greater_than':
case '>' :
$val1 = self::try_convert_float( $val1 );
$val2 = self::try_convert_float( $val2 );
return $val1 > $val2;
break;
case 'less_than':
case '<' :
$val1 = self::try_convert_float( $val1 );
$val2 = self::try_convert_float( $val2 );
return $val1 < $val2;
break;
case 'contains' :
return ! rgblank( $val2 ) && strpos( $val1, $val2 ) !== false;
break;
case 'starts_with' :
return ! rgblank( $val2 ) && strpos( $val1, $val2 ) === 0;
break;
case 'ends_with' :
// If target value is a 0 set $val2 to 0 rather than the empty string it currently is to prevent false positives.
if ( empty( $val2 ) ) {
$val2 = '0';
}
$start = strlen( $val1 ) - strlen( $val2 );
if ( $start < 0 ) {
return false;
}
$tail = substr( $val1, $start );
return $val2 == $tail;
break;
}
return false;
}
/**
* @param $form
* @param GF_Field $field
* @param $field_values
* @param null $lead
*
* @return string
*/
private static function get_field_display( $form, $field, $field_values, $lead = null ) {
if ( empty( $field ) ) {
return 'show';
}
$logic = $field->conditionalLogic;
//if this field does not have any conditional logic associated with it, it won't be hidden
if ( empty( $logic ) ) {
return 'show';
}
$is_visible = self::evaluate_conditional_logic( $form, $logic, $field_values, $lead );
return $is_visible ? 'show' : 'hide';
}
public static function get_custom_choices() {
$choices = get_option( 'gform_custom_choices' );
if ( ! $choices ) {
$choices = array();
}
return $choices;
}
public static function delete_custom_choice( $name ) {
$choices = self::get_custom_choices();
if ( array_key_exists( $name, $choices ) ) {
unset( $choices[ $name ] );
}
update_option( 'gform_custom_choices', $choices );
}
public static function save_custom_choice( $previous_name, $new_name, $choices ) {
$all_choices = self::get_custom_choices();
if ( array_key_exists( $previous_name, $all_choices ) ) {
unset( $all_choices[ $previous_name ] );
}
$all_choices[ $new_name ] = $choices;
update_option( 'gform_custom_choices', $all_choices );
}
/**
* Returns the value for a field.
*
* @param GF_Field $field
* @param array $field_values
* @param bool $get_from_post Whether to get the value from the $_POST array as opposed to $field_values
*
* @return array|mixed|string
*/
public static function get_field_value( &$field, $field_values = array(), $get_from_post = true ) {
if ( ! $field instanceof GF_Field ) {
$field = GF_Fields::create( $field );
}
if ( $field->type == 'post_category' ) {
$field = GFCommon::add_categories_as_choices( $field, '' );
}
$value = $field->get_value_submission( $field_values, $get_from_post );
if ( $field->get_input_type() == 'list' && $field->enableColumns && $get_from_post && rgpost( 'is_submit_' . $field->formId ) ) {
/** @var GF_Field_List $field */
$value = $field->create_list_array_recursive( $value );
}
return $value;
}
/**
* @deprecated 2.4
*
* @param int $expiration_days
*
* @return false|int
*/
public static function purge_expired_incomplete_submissions( $expiration_days = 30 ) {
_deprecated_function( 'GFFormsModel::purge_expired_incomplete_submissions', '2.4', 'GFFormsModel::purge_expired_draft_submissions' );
return self::purge_expired_draft_submissions( $expiration_days = 30 );
}
/**
* Purges expired draft submissions.
*
* @since 2.4
*
* @param int $expiration_days
*
* @return false|int
*/
public static function purge_expired_draft_submissions( $expiration_days = 30 ) {
global $wpdb;
/**
* Overrides the number of days until draft submissions are purged.
*
* @since 1.9
*
* @param int $expiration_days The number of days until expiration. Defaults to 30.
*/
$expiration_days = apply_filters( 'gform_incomplete_submissions_expiration_days', $expiration_days );
$expiration_date = gmdate( 'Y-m-d H:i:s', time() - ( $expiration_days * 24 * 60 * 60 ) );
$table = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_incomplete_submissions_table_name() : self::get_draft_submissions_table_name();
$query = array(
'delete' => 'DELETE',
'from' => sprintf( 'FROM %s', $table ),
'where' => $wpdb->prepare( 'WHERE date_created < %s', $expiration_date ),
);
/**
* Allows the query used to purge expired draft (save and continue) submissions to be overridden.
*
* @since 2.1.1.20
*
* @param array $query The delete, from, and where arguments to be used when the query is performed.
*/
$query = apply_filters( 'gform_purge_expired_incomplete_submissions_query', $query );
$result = $wpdb->query( implode( "\n", $query ) );
return $result;
}
/**
*
* @deprecated 2.4
*
* @param $token
*
* @return false|int
*/
public static function delete_incomplete_submission( $token ) {
_deprecated_function( 'GFFormsModel::delete_incomplete_submission', '2.4', 'GFFormsModel::delete_draft_submission' );
return self::delete_draft_submission( $token );
}
/**
* Deletes a draft submission.
*
* @since 2.4
*
* @param $token
*
* @return false|int
*/
public static function delete_draft_submission( $token ) {
global $wpdb;
$table = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_incomplete_submissions_table_name() : self::get_draft_submissions_table_name();
$result = $wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE uuid = %s", $token ) );
return $result;
}
/**
*
* @deprecated 2.4
*
* @param $form
* @param $entry
* @param $field_values
* @param $page_number
* @param $files
* @param $form_unique_id
* @param $ip
* @param $source_url
* @param string $resume_token
*
* @return bool|false|int|string
*/
public static function save_incomplete_submission( $form, $entry, $field_values, $page_number, $files, $form_unique_id, $ip, $source_url, $resume_token = '' ) {
_deprecated_function( 'GFFormsModel::save_incomplete_submission', '2.4', 'GFFormsModel::save_draft_submission' );
return self::save_draft_submission( $form, $entry, $field_values, $page_number, $files, $form_unique_id, $ip, $source_url, $resume_token );
}
/**
* Saves the draft submission.
*
* @since 2.4
*
* @param $form
* @param $entry
* @param $field_values
* @param $page_number
* @param $files
* @param $form_unique_id
* @param $ip
* @param $source_url
* @param string $resume_token
*
* @return bool|false|int|string
*/
public static function save_draft_submission( $form, $entry, $field_values, $page_number, $files, $form_unique_id, $ip, $source_url, $resume_token = '' ) {
if ( ! is_array( $form['fields'] ) ) {
return;
}
global $wpdb;
$table = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_incomplete_submissions_table_name() : self::get_draft_submissions_table_name();
$submitted_values = array();
foreach ( $form['fields'] as $field ) {
/* @var GF_Field $field */
if ( $field->type == 'creditcard' ) {
continue;
}
$submitted_values[ $field->id ] = RGFormsModel::get_field_value( $field, $field_values );
}
/**
* Allows the modification of submitted values before the draft submission is saved.
*
* @since 1.9
*
* @param array $submitted_values The submitted values
* @param array $form The Form object
*/
$submitted_values = apply_filters( 'gform_submission_values_pre_save', $submitted_values, $form );
$submission['submitted_values'] = $submitted_values;
$submission['partial_entry'] = $entry;
$submission['field_values'] = $field_values;
$submission['page_number'] = $page_number;
$submission['files'] = $files;
$submission['gform_unique_id'] = $form_unique_id;
// Issue a new token if no longer valid
if ( ! empty( $resume_token ) ) {
$sql = $wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE uuid = %s", $resume_token );
$count = $wpdb->get_var( $sql );
if ( $count != 1 ) {
$resume_token = false;
}
}
$is_new = empty( $resume_token );
if ( $is_new ) {
$resume_token = self::get_uuid();
}
$submission_json = json_encode( $submission );
$submission_json = self::filter_draft_submission_pre_save( $submission_json, $resume_token, $form );
if ( $is_new ) {
$result = $wpdb->insert(
$table,
array(
'uuid' => $resume_token,
'form_id' => $form['id'],
'date_created' => current_time( 'mysql', true ),
'submission' => $submission_json,
'ip' => $ip,
'source_url' => $source_url,
),
array(
'%s',
'%d',
'%s',
'%s',
'%s',
'%s',
)
);
} else {
$result = $wpdb->update(
$table,
array(
'form_id' => $form['id'],
'date_created' => current_time( 'mysql', true ),
'submission' => $submission_json,
'ip' => $ip,
'source_url' => $source_url,
),
array( 'uuid' => $resume_token ),
array(
'%d',
'%s',
'%s',
'%s',
'%s',
),
array( '%s' )
);
}
/**
* Fires after an draft submission is saved
*
* @since 1.9
*
* @param array $submission Contains the partially submitted entry, fields, values, and files.
* @param string $resume_token The unique resume token that was generated for this partial submission
* @param array $form The Form object
* @param array $entry The Entry object
*/
do_action( 'gform_incomplete_submission_post_save', $submission, $resume_token, $form, $entry );
return $result ? $resume_token : $result;
}
/**
* Filters the submission json string before saving.
*
* @since 2.4
*
* @param $submission_json
* @param $resume_token
* @param $form
*
* @return string
*/
private static function filter_draft_submission_pre_save( $submission_json, $resume_token, $form ) {
/**
* Allows the draft submission to be overridden before it is saved to the database.
*
* @since 2.3.3.1
*
* @param string $submission_json {
* JSON encoded associative array containing this incomplete submission.
*
* @type array $submitted_values The submitted values.
* @type array $partial_entry The draft entry created from the submitted values.
* @type null|array $field_values The dynamic population field values.
* @type int $page_number The forms current page number.
* @type array $files The uploaded file properties.
* @type string $gform_unique_id The unique id for this submission.
* }
* @param string $resume_token The unique token which can be used to resume this incomplete submission at a later date/time.
* @param array $form The form which this incomplete submission was created for.
*/
$submission_json = apply_filters( 'gform_incomplete_submission_pre_save', $submission_json, $resume_token, $form );
return $submission_json;
}
/**
* Updates a draft submission.
*
* @since 2.4
*
* @param string $resume_token The uuid of the draft submission to be updated.
* @param array $form
* @param string $date_created
* @param string $ip
* @param string $source_url
* @param string $submission_json
*
* @return bool|false|int|string
*/
public static function update_draft_submission( $resume_token, $form, $date_created, $ip, $source_url, $submission_json ) {
global $wpdb;
$form_id = $form['id'];
$submission_json = self::filter_draft_submission_pre_save( $submission_json, $resume_token, $form );
$table = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_incomplete_submissions_table_name() : self::get_draft_submissions_table_name();
$result = $wpdb->update(
$table,
array(
'form_id' => $form_id,
'date_created' => $date_created,
'submission' => $submission_json,
'ip' => $ip,
'source_url' => $source_url,
),
array( 'uuid' => $resume_token ),
array(
'%d',
'%s',
'%s',
'%s',
'%s',
),
array( '%s' )
);
return $result ? $resume_token : $result;
}
/**
* Returns a UUID. Uses openssl_random_pseudo_bytes() if available and falls back to mt_rand().
*
* source: http://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid
*
* @param string $s The separator e.g. '-'
*
* @return string
*/
public static function get_uuid( $s = '' ) {
if ( function_exists( 'openssl_random_pseudo_bytes' ) ) { // PHP 5 >= 5.3.0
$data = openssl_random_pseudo_bytes( 16 );
$data[6] = chr( ord( $data[6] ) & 0x0f | 0x40 ); // set version to 0100
$data[8] = chr( ord( $data[8] ) & 0x3f | 0x80 ); // set bits 6-7 to 10
return vsprintf( "%s%s{$s}%s{$s}%s{$s}%s{$s}%s%s%s", str_split( bin2hex( $data ), 4 ) );
} else {
return sprintf(
"%04x%04x{$s}%04x{$s}%04x{$s}%04x{$s}%04x%04x%04x",
// 32 bits for "time_low"
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
// 16 bits for 'time_mid'
mt_rand( 0, 0xffff ),
// 16 bits for 'time_hi_and_version',
// four most significant bits holds version number 4
mt_rand( 0, 0x0fff ) | 0x4000,
// 16 bits, 8 bits for 'clk_seq_hi_res',
// 8 bits for 'clk_seq_low',
// two most significant bits holds zero and one for variant DCE1.1
mt_rand( 0, 0x3fff ) | 0x8000,
// 48 bits for 'node'
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
);
}
}
/**
* @deprecated 2.4
*
* @param $resume_token
*
* @return array|null|object
*/
public static function get_incomplete_submission_values( $resume_token ) {
_deprecated_function( 'GFFormsModel::get_incomplete_submission_values', '2.4', 'GFFormsModel::get_draft_submission_values' );
return self::get_draft_submission_values( $resume_token );
}
/**
* Returns the values for the draft submission.
*
* @since 2.4
*
* @param $resume_token
*
* @return array|null|object
*/
public static function get_draft_submission_values( $resume_token ) {
global $wpdb;
self::purge_expired_draft_submissions();
$table = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_incomplete_submissions_table_name() : self::get_draft_submissions_table_name();
$sql = $wpdb->prepare( "SELECT date_created, form_id, submission, source_url FROM {$table} WHERE uuid = %s", $resume_token );
$row = $wpdb->get_row( $sql, ARRAY_A );
if ( ! empty( $row ) ) {
$form = self::get_form_meta( $row['form_id'] );
$row['submission'] = self::filter_draft_submission_post_get( $row['submission'], $resume_token, $form );
}
return $row;
}
/**
* Filters the draft submission after reading it from the database.
*
* @since 2.4
*
* @param $submission_json
* @param $resume_token
* @param $form
*
* @return string
*/
private static function filter_draft_submission_post_get( $submission_json, $resume_token, $form ) {
/**
* Allows the draft submission to be overridden after it is retrieved from the database but before it used to populate the form.
*
* @since 2.3.3.1
*
* @param string $submission_json {
* JSON encoded associative array containing the draft submission being resumed.
*
* @type array $submitted_values The submitted values.
* @type array $partial_entry The draft entry created from the submitted values.
* @type null|array $field_values The dynamic population field values.
* @type int $page_number The forms current page number.
* @type array $files The uploaded file properties.
* @type string $gform_unique_id The unique id for this submission.
* }
* @param string $resume_token The unique token which was used to resume this incomplete submission.
* @param array $form The form which this incomplete submission was created for.
*/
$submission_json = apply_filters( 'gform_incomplete_submission_post_get', $submission_json, $resume_token, $form );
return $submission_json;
}
/**
*
* @deprecated 2.4
*
* @param $token
* @param $email
*
* @return false|int
*/
public static function add_email_to_incomplete_sumbmission( $token, $email ) {
_deprecated_function( 'GFFormsModel::add_email_to_incomplete_sumbmission', '2.4', 'GFFormsModel::add_email_to_draft_sumbmission' );
return self::add_email_to_draft_sumbmission( $token, $email );
}
/**
* Adds the email address to the draft submission.
*
* @since 2.4
*
* @param $token
* @param $email
*
* @return false|int
*/
public static function add_email_to_draft_sumbmission( $token, $email ) {
global $wpdb;
self::purge_expired_draft_submissions();
$table = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_incomplete_submissions_table_name() : self::get_draft_submissions_table_name();
$sql = $wpdb->prepare( "UPDATE $table SET email = %s WHERE uuid = %s", $email, $token );
$result = $wpdb->query( $sql );
return $result;
}
public static function maybe_trim_input( $value, $form_id, $field ) {
if ( is_null( $value ) ) {
return $value;
}
$trim_value = apply_filters( 'gform_trim_input_value', true, $form_id, $field );
if ( $trim_value ) {
$value = is_array( $value ) ? GFCommon::trim_deep( $value ) : trim( $value );
}
return $value;
}
public static function get_parameter_value( $name, $field_values, $field ) {
$value = stripslashes_deep( rgget( $name ) );
if ( rgblank( $value ) ) {
$value = rgget( $name, $field_values );
}
// Converting list format
if ( ! empty( $value ) && RGFormsModel::get_input_type( $field ) == 'list' ) {
// Transforms this: col1|col2,col1b|col2b into this: col1,col2,col1b,col2b
$column_count = is_array( $field->choices ) ? count( $field->choices ) : 0;
$rows = is_array( $value ) ? $value : explode( ',', $value );
if ( ! empty( $rows ) ) {
$ary_rows = array();
foreach ( $rows as $row ) {
/**
* Allow modification of the delimiter used to parse List field URL parameters.
*
* @since 2.0.0
*
* @param string $delimiter Defaults to '|';
* @param array $field GF_Field object for the current field.
* @param string $name Name of the current dynamic population parameter.
* @param array $field_values Array of values provided for pre-population into the form.
*/
$delimiter = apply_filters( 'gform_list_field_parameter_delimiter', '|', $field, $name, $field_values );
$ary_rows = array_merge( $ary_rows, rgexplode( $delimiter, $row, $column_count ) );
}
$value = $ary_rows;
}
}
return gf_apply_filters( array( 'gform_field_value', $name ), $value, $field, $name );
}
public static function get_default_value( $field, $input_id ) {
if ( ! is_array( $field->choices ) ) {
// if entry is saved in separate inputs get requsted input's default value ($input_id = 2.1)
// some fields (like Date, Time) do not save their values in separate inputs and are correctly filtered out by this condition ($input_id = 2)
// other fields (like Email w/ Confirm-enabled) also do not save their values in separate inputs but *should be* processed as input-specific submissions ($input_id = 2)
if ( is_array( $field->get_entry_inputs() ) || ( $field->get_input_type() == 'email' && is_array( $field->inputs ) ) ) {
$input = RGFormsModel::get_input( $field, $input_id );
return rgar( $input, 'defaultValue' );
} else {
$value = $field->get_value_default();
if( ! IS_ADMIN ) {
if( is_array( $value ) ) {
foreach( $value as &$_value ) {
$_value = GFCommon::replace_variables_prepopulate( $_value );
}
} else {
$value = GFCommon::replace_variables_prepopulate( $value );
}
}
return $value;
}
} else if ( $field->type == 'checkbox' ) {
for ( $i = 0, $count = sizeof( $field->inputs ); $i < $count; $i ++ ) {
$input = $field->inputs[ $i ];
$choice = $field->choices[ $i ];
if ( $input['id'] == $input_id && rgar( $choice, 'isSelected' ) ) {
return $choice['value'];
}
}
return '';
} else {
foreach ( $field->choices as $choice ) {
if ( rgar( $choice, 'isSelected' ) || $field->type == 'post_category' ) {
return $choice['value'];
}
}
return '';
}
}
/**
* @param GF_Field $field
*
* @return string
*/
public static function get_input_type( $field ) {
// TODO: Deprecate
if ( ! $field instanceof GF_Field ) {
return empty( $field['inputType'] ) ? $field['type'] : $field['inputType'];
}
return $field->get_input_type();
}
private static function get_post_field_value( $field, $lead ) {
if ( is_array( $field->get_entry_inputs() ) ) {
$value = array();
foreach ( $field->inputs as $input ) {
$val = isset( $lead[ strval( $input['id'] ) ] ) ? $lead[ strval( $input['id'] ) ] : '';
if ( ! empty( $val ) ) {
// replace commas in individual values to prevent individual value from being split into multiple values (checkboxes, multiselects)
if ( $field->get_input_type() === 'checkbox' ) {
$val = str_replace( ',', '&#44;', $val );
}
$value[] = $val;
}
}
$value = implode( ',', $value );
} else {
$value = isset( $lead[ $field->id ] ) ? $lead[ $field->id ] : '';
if ( ! empty( $value ) && $field->get_input_type() === 'multiselect' ) {
$items = $field->to_array( $value );
foreach ( $items as &$item ) {
$item = str_replace( ',', '&#44;', $item );
}
$value = implode( ',', $items );
}
}
return $value;
}
private static function get_post_fields( $form, $lead ) {
$post_data = array();
$post_data['post_custom_fields'] = array();
$post_data['tags_input'] = array();
$categories = array();
$images = array();
foreach ( $form['fields'] as $field ) {
if ( self::is_field_hidden( $form, $field, array(), $lead ) ) {
continue;
}
if ( $field->type == 'post_category' ) {
$field = GFCommon::add_categories_as_choices( $field, '' );
}
$value = self::get_post_field_value( $field, $lead );
switch ( $field->type ) {
case 'post_title' :
case 'post_excerpt' :
case 'post_content' :
// Prevent shortcodes from being parsed.
$post_data[ $field->type ] = GFCommon::encode_shortcodes( $value );
break;
case 'post_tags' :
$tags = explode( ',', $value );
if ( is_array( $tags ) && sizeof( $tags ) > 0 ) {
$post_data['tags_input'] = array_merge( $post_data['tags_input'], $tags );
}
break;
case 'post_custom_field' :
$type = self::get_input_type( $field );
if ( 'fileupload' === $type && $field->multipleFiles ) {
$value = json_decode( $value, true );
}
$meta_name = $field->postCustomFieldName;
if ( ! isset( $post_data['post_custom_fields'][ $meta_name ] ) ) {
$post_data['post_custom_fields'][ $meta_name ] = $value;
} else if ( ! is_array( $post_data['post_custom_fields'][ $meta_name ] ) ) {
$post_data['post_custom_fields'][ $meta_name ] = array( $post_data['post_custom_fields'][ $meta_name ], $value );
} else {
$post_data['post_custom_fields'][ $meta_name ][] = $value;
}
break;
case 'post_category' :
foreach ( explode( ',', $value ) as $cat_string ) {
$cat_array = explode( ':', $cat_string );
// the category id is the last item in the array, access it using end() in case the category name includes colons.
array_push( $categories, end( $cat_array ) );
}
break;
case 'post_image' :
$ary = ! empty( $value ) ? explode( '|:|', $value ) : array();
$url = count( $ary ) > 0 ? $ary[0] : '';
$title = count( $ary ) > 1 ? $ary[1] : '';
$caption = count( $ary ) > 2 ? $ary[2] : '';
$description = count( $ary ) > 3 ? $ary[3] : '';
$alt = count( $ary ) > 4 ? $ary[4] : '';
array_push( $images, array( 'field_id' => $field->id, 'url' => $url, 'title' => $title, 'description' => $description, 'caption' => $caption, 'alt' => $alt ) );
break;
}
}
$post_data['post_status'] = rgar( $form, 'postStatus' );
$post_data['post_category'] = ! empty( $categories ) ? $categories : array( rgar( $form, 'postCategory' ) );
$post_data['images'] = $images;
//setting current user as author depending on settings
$post_data['post_author'] = $form['useCurrentUserAsAuthor'] && ! empty( $lead['created_by'] ) ? $lead['created_by'] : rgar( $form, 'postAuthor' );
return $post_data;
}
/**
* Retrieves the custom field names (meta keys) for the custom field select in the form editor.
*
* @since unknown
*
* @return array
*/
public static function get_custom_field_names() {
$form_id = absint( rgget( 'id' ) );
/**
* Allow the postmeta query which retrieves the custom field names (meta keys) to be disabled.
*
* @since 2.3.4.1
*
* @param bool $disable_query Indicates if the custom field names query should be disabled. Default is false.
*/
$disable_query = gf_apply_filters( array( 'gform_disable_custom_field_names_query', $form_id ), false );
if ( $disable_query ) {
return array();
}
global $wpdb;
$sql = "SELECT DISTINCT meta_key
FROM $wpdb->postmeta
WHERE meta_key NOT BETWEEN '_' AND '_z'
HAVING meta_key NOT LIKE %s
ORDER BY meta_key";
$keys = $wpdb->get_col( $wpdb->prepare( $sql, $wpdb->esc_like( '_' ) . '%' ) );
return $keys;
}
public static function get_input_masks() {
$masks = array(
'US Phone' => '(999) 999-9999',
'US Phone + Ext' => '(999) 999-9999? x99999',
'Date' => '99/99/9999',
'Tax ID' => '99-9999999',
'SSN' => '999-99-9999',
'Zip Code' => '99999',
'Full Zip Code' => '99999?-9999',
);
return apply_filters( 'gform_input_masks', $masks );
}
private static function get_default_post_title() {
global $wpdb;
$title = 'Untitled';
$count = 1;
$titles = $wpdb->get_col( "SELECT post_title FROM $wpdb->posts WHERE post_title like '%Untitled%'" );
$titles = array_values( $titles );
while ( in_array( $title, $titles ) ) {
$title = "Untitled_$count";
$count ++;
}
return $title;
}
public static function prepare_date( $date_format, $value ) {
$format = empty( $date_format ) ? 'mdy' : $date_format;
$date_info = GFCommon::parse_date( $value, $format );
if ( ! empty( $date_info ) && ! GFCommon::is_empty_array( $date_info ) ) {
$value = sprintf( '%s-%02d-%02d', $date_info['year'], $date_info['month'], $date_info['day'] );
} else {
$value = '';
}
return $value;
}
/**
* Prepare the value before saving it to the lead. For multi-input fields this will be called for each input.
*
* @param mixed $form
* @param GF_Field $field
* @param mixed $value
* @param mixed $input_name
* @param mixed $lead_id the current lead ID, used for fields that are processed after other fields have been saved (ie Total, Calculations)
* @param mixed $lead passed by the RGFormsModel::create_lead() method, lead ID is not available for leads created by this function
*
* @return mixed
*/
public static function prepare_value( $form, $field, $value, $input_name, $lead_id, $lead = array() ) {
$value = $field->get_value_save_entry( $value, $form, $input_name, $lead_id, $lead );
// special format for Post Category fields
if ( $field->type == 'post_category' ) {
$is_multiselect = $field->inputType === 'multiselect';
$full_values = array();
if ( ! is_array( $value ) ) {
$value = $is_multiselect ? $field->to_array( $value ) : explode( ',', $value );
}
foreach ( $value as $cat_id ) {
$cat = get_term( $cat_id, 'category' );
$full_values[] = ! is_wp_error( $cat ) && is_object( $cat ) ? $cat->name . ':' . $cat_id : '';
}
$value = $is_multiselect ? $field->to_string( $full_values ) : implode( ',', $full_values );
}
//do not save price fields with blank price
if ( $field->enablePrice ) {
$ary = explode( '|', $value );
$label = count( $ary ) > 0 ? $ary[0] : '';
$price = count( $ary ) > 1 ? $ary[1] : '';
$is_empty = ( strlen( trim( $price ) ) <= 0 );
if ( $is_empty ) {
$value = '';
}
}
return $value;
}
public static function is_checkbox_checked( $field_id, $field_label, $lead, $form ) {
//looping through lead detail values trying to find an item identical to the column label. Mark with a tick if found.
$lead_field_keys = array_keys( $lead );
foreach ( $lead_field_keys as $input_id ) {
//mark as a tick if input label (from form meta) is equal to submitted value (from lead)
if ( is_numeric( $input_id ) && absint( $input_id ) == absint( $field_id ) ) {
if ( $lead[ $input_id ] == $field_label ) {
return $lead[ $input_id ];
} else {
$field = RGFormsModel::get_field( $form, $field_id );
if ( $field->enableChoiceValue || $field->enablePrice ) {
foreach ( $field->choices as $choice ) {
if ( $choice['value'] == $lead[ $field_id ] ) {
return $choice['value'];
} else if ( $field->enablePrice ) {
$ary = explode( '|', $lead[ $field_id ] );
$val = count( $ary ) > 0 ? $ary[0] : '';
$price = count( $ary ) > 1 ? $ary[1] : '';
if ( $val == $choice['value'] ) {
return $choice['value'];
}
}
}
}
}
}
}
return false;
}
public static function get_fileupload_value( $form_id, $input_name ) {
_deprecated_function( 'GFFormsModel::get_fileupload_value', '1.9', 'GF_Field_Fileupload::get_fileupload_value' );
global $_gf_uploaded_files;
GFCommon::log_debug( 'GFFormsModel::get_fileupload_value(): Starting.' );
if ( empty( $_gf_uploaded_files ) ) {
GFCommon::log_debug( 'GFFormsModel::get_fileupload_value(): No files uploaded. Exiting.' );
$_gf_uploaded_files = array();
}
if ( ! isset( $_gf_uploaded_files[ $input_name ] ) ) {
//check if file has already been uploaded by previous step
$file_info = self::get_temp_filename( $form_id, $input_name );
$temp_filepath = self::get_upload_path( $form_id ) . '/tmp/' . $file_info['temp_filename'];
GFCommon::log_debug( 'GFFormsModel::get_fileupload_value(): Temp file path: ' . $temp_filepath );
if ( $file_info && file_exists( $temp_filepath ) ) {
GFCommon::log_debug( 'GFFormsModel::get_fileupload_value(): Moving temp file: ' . $temp_filepath );
$_gf_uploaded_files[ $input_name ] = self::move_temp_file( $form_id, $file_info );
} else if ( ! empty( $_FILES[ $input_name ]['name'] ) ) {
GFCommon::log_debug( 'GFFormsModel::get_fileupload_value(): Uploading file: ' . $_FILES[ $input_name ]['name'] );
$_gf_uploaded_files[ $input_name ] = self::upload_file( $form_id, $_FILES[ $input_name ] );
}
}
return rgget( $input_name, $_gf_uploaded_files );
}
public static function get_form_unique_id( $form_id ) {
$unique_id = '';
if ( rgpost( 'gform_submit' ) == $form_id ) {
$posted_uid = rgpost( 'gform_unique_id' );
if ( false === empty( $posted_uid ) && ctype_alnum( $posted_uid )) {
$unique_id = $posted_uid;
self::$unique_ids[ $form_id ] = $unique_id;
} elseif ( isset( self::$unique_ids[ $form_id ] ) ) {
$unique_id = self::$unique_ids[ $form_id ];
} else {
$unique_id = uniqid();
self::$unique_ids[ $form_id ] = $unique_id;
}
}
return $unique_id;
}
public static function get_temp_filename( $form_id, $input_name ) {
$uploaded_filename = ! empty( $_FILES[ $input_name ]['name'] ) && $_FILES[ $input_name ]['error'] === 0 ? $_FILES[ $input_name ]['name'] : '';
if ( empty( $uploaded_filename ) && isset( self::$uploaded_files[ $form_id ] ) ) {
$uploaded_filename = rgget( $input_name, self::$uploaded_files[ $form_id ] );
}
if ( empty( $uploaded_filename ) ) {
return false;
}
$ext_check = is_array( $uploaded_filename ) ? $uploaded_filename[0]['uploaded_filename'] : $uploaded_filename;
$form_unique_id = self::get_form_unique_id( $form_id );
$extension = pathinfo( $ext_check, PATHINFO_EXTENSION );
$temp_filename = "{$form_unique_id}_{$input_name}.{$extension}";
GFCommon::log_debug( __METHOD__ . '(): Uploaded filename is ' . $ext_check . ' and temporary filename is ' . $temp_filename );
return array( 'uploaded_filename' => $uploaded_filename, 'temp_filename' => $temp_filename );
}
public static function get_choice_text( $field, $value, $input_id = 0 ) {
if ( ! is_array( $field->choices ) ) {
return $value;
}
foreach ( $field->choices as $choice ) {
if ( is_array( $value ) && self::choice_value_match( $field, $choice, $value[ $input_id ] ) ) {
return $choice['text'];
} else if ( ! is_array( $value ) && self::choice_value_match( $field, $choice, $value ) ) {
return $choice['text'];
}
}
return is_array( $value ) ? '' : $value;
}
public static function choice_value_match( $field, $choice, $value ) {
$choice_value = GFFormsModel::maybe_trim_input( $choice['value'], $field->formId, $field );
$value = GFFormsModel::maybe_trim_input( $value, $field->formId, $field );
$allowed_html = wp_kses_allowed_html( 'post' );
$sanitized_value = wp_kses( $value, $allowed_html );
if ( $choice_value == $value || $choice_value == $sanitized_value ) {
return true;
} else if ( $field->enablePrice ) {
$ary = explode( '|', $value );
$val = count( $ary ) > 0 ? $ary[0] : '';
$sanitized_val = wp_kses( $val, $allowed_html );
$price = count( $ary ) > 1 ? $ary[1] : '';
if ( $choice['value'] == $val || $choice['value'] == $sanitized_val ) {
return true;
}
} // add support for prepopulating multiselects @alex
else if ( RGFormsModel::get_input_type( $field ) == 'multiselect' ) {
$values = $field->to_array( $value );
$sanitized_values = $field->to_array( $sanitized_value );
if ( in_array( $choice_value, $values ) || in_array( $choice_value, $sanitized_values ) ) {
return true;
}
}
return false;
}
public static function choices_value_match( $field, $choices, $value ) {
foreach ( $choices as $choice ) {
if ( self::choice_value_match( $field, $choice, $value ) ) {
return true;
}
}
return false;
}
public static function create_post( $form, &$lead ) {
GFCommon::timer_start( __METHOD__ );
GFCommon::log_debug( 'GFFormsModel::create_post(): Starting.' );
$has_post_field = false;
foreach ( $form['fields'] as $field ) {
$is_hidden = self::is_field_hidden( $form, $field, array(), $lead );
if ( ! $is_hidden && in_array( $field->type, array( 'post_category', 'post_title', 'post_content', 'post_excerpt', 'post_tags', 'post_custom_field', 'post_image' ) ) ) {
$has_post_field = true;
break;
}
}
//if this form does not have any post fields, don't create a post
if ( ! $has_post_field ) {
GFCommon::log_debug( "GFFormsModel::create_post(): Stopping. The form doesn't have any post fields." );
return $lead;
}
//processing post fields
GFCommon::log_debug( 'GFFormsModel::create_post(): Getting post fields.' );
$post_data = self::get_post_fields( $form, $lead );
//allowing users to change post fields before post gets created
$post_data = gf_apply_filters( array( 'gform_post_data', $form['id'] ), $post_data, $form, $lead );
//adding default title if none of the required post fields are in the form (will make sure wp_insert_post() inserts the post)
if ( empty( $post_data['post_title'] ) && empty( $post_data['post_content'] ) && empty( $post_data['post_excerpt'] ) ) {
$post_data['post_title'] = self::get_default_post_title();
}
// remove original post status and save it for later
$post_status = $post_data['post_status'];
// replace original post status with 'draft' so other plugins know this post is not fully populated yet
$post_data['post_status'] = 'draft';
// inserting post
GFCommon::log_debug( 'GFFormsModel::create_post(): Inserting post via wp_insert_post().' );
$post_id = wp_insert_post( $post_data, true );
GFCommon::log_debug( 'GFFormsModel::create_post(): Result from wp_insert_post(): ' . print_r( $post_id, 1 ) );
if ( is_wp_error( $post_id ) ) {
GFCommon::log_debug( __METHOD__ . '(): $post_data => ' . print_r( $post_data, 1 ) );
return false;
}
// Add the post id to the entry so it is available during merge tag replacement.
$lead['post_id'] = $post_id;
//adding form id and entry id hidden custom fields
add_post_meta( $post_id, '_gform-form-id', $form['id'] );
add_post_meta( $post_id, '_gform-entry-id', $lead['id'] );
$post_images = array();
if ( ! empty( $post_data['images'] ) ) {
// Creating post images.
GFCommon::log_debug( 'GFFormsModel::create_post(): Processing post images.' );
foreach ( $post_data['images'] as $image ) {
if ( empty( $image['url'] ) ) {
GFCommon::log_debug( __METHOD__ . '(): No image to process for field #' . $image['field_id'] );
continue;
}
$image_meta = array(
'post_excerpt' => $image['caption'],
'post_content' => $image['description'],
);
// Adding title only if it is not empty. It will default to the file name if it is not in the array.
if ( ! empty( $image['title'] ) ) {
$image_meta['post_title'] = $image['title'];
}
GFCommon::log_debug( sprintf( '%s(): Field #%s. URL: %s', __METHOD__, $image['field_id'], $image['url'] ) );
$media_id = self::media_handle_upload( $image['url'], $post_id, $image_meta );
if ( $media_id ) {
// Save media id for post body/title template variable replacement (below).
$post_images[ $image['field_id'] ] = $media_id;
$lead[ $image['field_id'] ] .= "|:|$media_id";
// Update the alt text.
update_post_meta( $media_id, '_wp_attachment_image_alt', $image['alt'] );
// Setting the featured image.
$field = RGFormsModel::get_field( $form, $image['field_id'] );
if ( $field->postFeaturedImage ) {
$result = set_post_thumbnail( $post_id, $media_id );
GFCommon::log_debug( __METHOD__ . '(): Setting the featured image. Result from set_post_thumbnail(): ' . var_export( $result, 1 ) );
}
}
}
}
//adding custom fields
GFCommon::log_debug( 'GFFormsModel::create_post(): Adding custom fields.' );
foreach ( $post_data['post_custom_fields'] as $meta_name => $meta_value ) {
if ( ! is_array( $meta_value ) ) {
$meta_value = array( $meta_value );
}
$meta_index = 0;
foreach ( $meta_value as $value ) {
GFCommon::log_debug( 'GFFormsModel::create_post(): Getting custom field: ' . $meta_name );
$custom_field = self::get_custom_field( $form, $meta_name, $meta_index );
//replacing template variables if template is enabled
if ( $custom_field && $custom_field->customFieldTemplateEnabled ) {
$value = self::process_post_template( $custom_field->customFieldTemplate, 'post_custom_field', $post_images, $post_data, $form, $lead );
}
switch ( RGFormsModel::get_input_type( $custom_field ) ) {
case 'list' :
$value = maybe_unserialize( $value );
if ( is_array( $value ) ) {
foreach ( $value as $item ) {
if ( is_array( $item ) ) {
$item = implode( '|', $item );
}
if ( ! rgblank( $item ) ) {
add_post_meta( $post_id, $meta_name, $item );
}
}
}
break;
case 'multiselect' :
case 'checkbox' :
$value = explode( ',', $value );
if ( is_array( $value ) ) {
foreach ( $value as $item ) {
if ( ! rgblank( $item ) ) {
// add post meta and replace HTML symbol in $item with real comma
add_post_meta( $post_id, $meta_name, str_replace( '&#44;', ',', $item ) );
}
}
}
break;
case 'date' :
$value = GFCommon::date_display( $value, rgar( $custom_field, 'dateFormat' ) );
if ( ! rgblank( $value ) ) {
add_post_meta( $post_id, $meta_name, $value );
}
break;
default :
if ( ! rgblank( $value ) ) {
add_post_meta( $post_id, $meta_name, $value );
}
break;
}
$meta_index ++;
}
}
$has_content_field = sizeof( self::get_fields_by_type( $form, array( 'post_content' ) ) ) > 0;
$has_title_field = sizeof( self::get_fields_by_type( $form, array( 'post_title' ) ) ) > 0;
$post = false;
//if a post field was configured with a content or title template, process template
if ( ( rgar( $form, 'postContentTemplateEnabled' ) && $has_content_field ) || ( rgar( $form, 'postTitleTemplateEnabled' ) && $has_title_field ) ) {
$post = get_post( $post_id );
if ( rgar( $form, 'postContentTemplateEnabled' ) && $has_content_field ) {
$post_content = self::process_post_template( $form['postContentTemplate'], 'post_content', $post_images, $post_data, $form, $lead );
//updating post content
$post->post_content = $post_content;
}
if ( rgar( $form, 'postTitleTemplateEnabled' ) && $has_title_field ) {
$post_title = self::process_post_template( $form['postTitleTemplate'], 'post_title', $post_images, $post_data, $form, $lead );
//updating post
$post->post_title = $post_title;
$post->post_name = $post_title;
}
}
// update post status back to original status (if not draft)
if ( $post_status != 'draft' ) {
$post = is_object( $post ) ? $post : get_post( $post_id );
$post->post_status = $post_status;
}
// if post has been modified since creation, save updates
if ( is_object( $post ) ) {
GFCommon::log_debug( 'GFFormsModel::create_post(): Updating post.' );
wp_update_post( $post );
}
//adding post format
if ( current_theme_supports( 'post-formats' ) && rgar( $form, 'postFormat' ) ) {
$formats = get_theme_support( 'post-formats' );
$post_format = rgar( $form, 'postFormat' );
if ( is_array( $formats ) ) {
$formats = $formats[0];
if ( in_array( $post_format, $formats ) ) {
set_post_format( $post_id, $post_format );
} else if ( '0' == $post_format ) {
set_post_format( $post_id, false );
}
}
}
// Update the post_id in the database for this entry.
GFCommon::log_debug( 'GFFormsModel::create_post(): Updating entry with post id.' );
self::update_lead_property( $lead['id'], 'post_id', $post_id );
$gform_after_create_post_args = array( 'gform_after_create_post', $form['id'] );
if ( gf_has_action( $gform_after_create_post_args ) ) {
GFCommon::log_debug( __METHOD__ . '(): Executing functions hooked to gform_after_create_post.' );
/**
* Fires after a post, from a form with post fields, is created
*
* @param int $form ['id'] The ID of the form where the new post was created
* @param int $post_id The new Post ID created after submission
* @param array $lead The Lead Object
* @param array $form The Form Object for the form used to create the post
*/
gf_do_action( $gform_after_create_post_args, $post_id, $lead, $form );
GFCommon::log_debug( __METHOD__ . '(): Completed gform_after_create_post.' );
}
GFCommon::log_debug( __METHOD__ . sprintf( '(): Post creation completed in %F seconds.', GFCommon::timer_end( __METHOD__ ) ) );
return $post_id;
}
/**
* Process any merge tags and shortcodes found in the template.
*
* @param string $template The template.
* @param string $field_type The field type currently being processed. Possible values: post_custom_field, post_content, or post_title.
* @param array $post_images The uploaded post images.
* @param array $post_data The post data prepared from the current entry.
* @param array $form The form currently being processed.
* @param array $entry The entry currently being processed.
*
* @return string
*/
public static function process_post_template( $template, $field_type, $post_images, $post_data, $form, $entry ) {
GFCommon::log_debug( __METHOD__ . "(): Processing {$field_type} template." );
//replacing post image variables
$template = GFCommon::replace_variables_post_image( $template, $post_images, $entry );
//replacing all other variables
$template = GFCommon::replace_variables( $template, $form, $entry, false, false, false );
if ( $field_type != 'post_content' ) {
$process_template_shortcodes = true;
/**
* Allow shortcode processing of custom field and post title templates to be disabled.
*
* @param boolean $process_template_shortcodes Should the shortcodes be processed? Default is true.
* @param string $field_type The field type currently being processed. Possible values: post_custom_field, post_content, or post_title.
* @param array $post_data The post data prepared from the current entry.
* @param array $form The form currently being processed.
* @param array $entry The entry currently being processed.
*
* @since 2.0.0.4
*/
$process_template_shortcodes = apply_filters( 'gform_process_template_shortcodes_pre_create_post', $process_template_shortcodes, $field_type, $post_data, $form, $entry );
$process_template_shortcodes = apply_filters( 'gform_process_template_shortcodes_pre_create_post_' . $form['id'], $process_template_shortcodes, $field_type, $post_data, $form, $entry );
if ( $process_template_shortcodes ) {
$template = do_shortcode( $template );
}
}
return $template;
}
private static function get_custom_field( $form, $meta_name, $meta_index ) {
$custom_fields = self::get_fields_by_type( $form, array( 'post_custom_field' ) );
$index = 0;
foreach ( $custom_fields as $field ) {
if ( $field->postCustomFieldName == $meta_name ) {
if ( $meta_index == $index ) {
return $field;
}
$index ++;
}
}
return false;
}
private static function copy_post_image( $url, $post_id ) {
$time = current_time( 'mysql' );
if ( $post = get_post( $post_id ) ) {
if ( substr( $post->post_date, 0, 4 ) > 0 ) {
$time = $post->post_date;
}
}
//making sure there is a valid upload folder
if ( ! ( ( $upload_dir = wp_upload_dir( $time ) ) && false === $upload_dir['error'] ) ) {
return false;
}
$form_id = get_post_meta( $post_id, '_gform-form-id', true );
/**
* Filter the media upload location.
*
* @param array $upload_dir The current upload directory’s path and url.
* @param int $form_id The ID of the form currently being processed.
* @param int $post_id The ID of the post created from the entry currently being processed.
*/
$upload_dir = gf_apply_filters( 'gform_media_upload_path', $form_id, $upload_dir, $form_id, $post_id );
if ( ! file_exists( $upload_dir['path'] ) ) {
if ( ! wp_mkdir_p( $upload_dir['path'] ) ) {
return false;
}
}
$name = wp_basename( $url );
$filename = wp_unique_filename( $upload_dir['path'], $name );
// the destination path
$new_file = $upload_dir['path'] . "/$filename";
// the source path
$upload_root_info = GF_Field_FileUpload::get_upload_root_info( $form_id );
$path = str_replace( $upload_root_info['url'], $upload_root_info['path'], $url );
// copy the file to the destination path
if ( ! copy( $path, $new_file ) ) {
return false;
}
// Set correct file permissions
$stat = stat( dirname( $new_file ) );
$perms = $stat['mode'] & 0000666;
@ chmod( $new_file, $perms );
// Compute the URL
$url = $upload_dir['url'] . "/$filename";
if ( is_multisite() ) {
delete_transient( 'dirsize_cache' );
}
$type = wp_check_filetype( $new_file );
return array( 'file' => $new_file, 'url' => $url, 'type' => $type['type'] );
}
public static function media_handle_upload( $url, $post_id, $post_data = array() ) {
// WordPress Administration API required for the media_handle_upload() function.
require_once( ABSPATH . 'wp-admin/includes/image.php' );
require_once( ABSPATH . 'wp-admin/includes/media.php' );
$name = wp_basename( $url );
$file = self::copy_post_image( $url, $post_id );
if ( ! $file ) {
GFCommon::log_debug( __METHOD__ . '(): Image could not be copied to the media directory.' );
return false;
}
$name_parts = pathinfo( $name );
$name = trim( substr( $name, 0, - ( 1 + strlen( $name_parts['extension'] ) ) ) );
$url = $file['url'];
$type = $file['type'];
$file = $file['file'];
$title = $name;
$content = '';
// use image exif/iptc data for title and caption defaults if possible
if ( $image_meta = @wp_read_image_metadata( $file ) ) {
if ( trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
$title = $image_meta['title'];
}
if ( trim( $image_meta['caption'] ) ) {
$content = $image_meta['caption'];
}
}
// Construct the attachment array
$attachment = array_merge(
array(
'post_mime_type' => $type,
'guid' => $url,
'post_parent' => $post_id,
'post_title' => $title,
'post_content' => $content,
), $post_data
);
// Save the data
$id = wp_insert_attachment( $attachment, $file, $post_id );
if ( ! is_wp_error( $id ) ) {
wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
}
GFCommon::log_debug( __METHOD__ . '(): Image copied to the media directory. Result from wp_insert_attachment(): ' . print_r( $id, 1 ) );
return $id;
}
public static function save_input( $form, $field, &$lead, $current_fields, $input_id ) {
if ( isset( $field->fields ) && is_array( $field->fields ) ) {
foreach( $field->fields as $sub_field ) {
$inputs = $sub_field->get_entry_inputs();
if ( is_array( $inputs ) ) {
foreach ( $inputs as $input ) {
self::save_input( $form, $sub_field, $lead, $current_fields, $input['id'] );
}
} else {
self::save_input( $form, $sub_field, $lead, $current_fields, $sub_field->id );
}
foreach ( $current_fields as $current_field ) {
if ( intval( $current_field->meta_key ) == $sub_field->id && ! isset( $current_field->update ) ) {
$current_field->delete = true;
$result = self::queue_batch_field_operation( $form, $lead, $sub_field, $current_field->id, $current_field->meta_key, '', $current_field->item_index );
GFCommon::log_debug( __METHOD__ . "(): Deleting: {$field->label}(#{$sub_field->id}{$current_field->item_index} - {$field->type}). Result: " . var_export( $result, 1 ) );
}
}
}
return;
}
$input_name = 'input_' . str_replace( '.', '_', $input_id );
if ( $field->enableCopyValuesOption && rgpost( 'input_' . $field->id . '_copy_values_activated' ) ) {
$source_field_id = $field->copyValuesOptionField;
$source_input_name = str_replace( 'input_' . $field->id, 'input_' . $source_field_id, $input_name );
$value = rgpost( $source_input_name );
} else {
$value = rgpost( $input_name );
}
$value = self::maybe_trim_input( $value, $form['id'], $field );
//ignore file upload when nothing was sent in the admin
//ignore post fields in the admin
$type = self::get_input_type( $field );
$multiple_files = $field->multipleFiles;
$uploaded_files = GFFormsModel::$uploaded_files;
$form_id = $form['id'];
if ( rgget( 'view' ) == 'entry' && $type == 'fileupload' && ( ( ! $multiple_files && empty( $_FILES[ $input_name ]['name'] ) ) || ( $multiple_files && ! isset( $uploaded_files[ $form_id ][ $input_name ] ) ) ) ) {
return;
} else if ( rgget( 'view' ) == 'entry' && in_array( $field->type, array( 'post_category', 'post_title', 'post_content', 'post_excerpt', 'post_tags', 'post_custom_field', 'post_image' ) ) ) {
return;
}
$is_form_editor = GFCommon::is_form_editor();
$is_entry_detail = GFCommon::is_entry_detail();
$is_admin = $is_form_editor || $is_entry_detail;
if ( empty( $value ) && $field->is_administrative() && ! $is_admin ) {
$value = self::get_default_value( $field, $input_id );
}
self::queue_save_input_value( $value, $form, $field, $lead, $current_fields, $input_id );
}
/**
* Queues the input value for saving.
*
* @since 2.4
*
* @param string|array $value
* @param array $form
* @param GF_Field $field
* @param array $lead
* @param array $current_fields
* @param string $input_id
* @param string $item_index
*/
public static function queue_save_input_value( $value, $form, $field, &$lead, $current_fields, $input_id, $item_index = '' ) {
$input_name = 'input_' . str_replace( '.', '_', $input_id );
if ( is_array( $value ) && ! ( $field->is_value_submission_array() && ! is_array( $value[0] ) ) ) {
foreach ( $value as $i => $v ) {
$new_item_index = $item_index . '_' . $i;
if ( is_array( $v ) && ! ( $field->is_value_submission_array() && ! is_array( $v[0] ) ) ) {
self::queue_save_input_value( $v, $form, $field, $lead, $current_fields, $input_id, $new_item_index );
continue;
}
//processing values so that they are in the correct format for each input type
$v = self::prepare_value( $form, $field, $v, $input_name, rgar( $lead, 'id' ) );
$lead_detail_id = self::get_lead_detail_id( $current_fields, $input_id, $new_item_index );
$result = self::queue_batch_field_operation( $form, $lead, $field, $lead_detail_id, $input_id, $v, $new_item_index );
GFCommon::log_debug( __METHOD__ . "(): Saving: {$field->label}(#{$input_id}{$item_index} - {$field->type}). Result: " . var_export( $result, 1 ) );
foreach ( $current_fields as $current_field ) {
if ( (string) $current_field->meta_key === (string) $input_id && $current_field->item_index == $new_item_index ) {
$current_field->update = true;
}
}
}
} else {
//processing values so that they are in the correct format for each input type
$value = self::prepare_value( $form, $field, $value, $input_name, rgar( $lead, 'id' ), $lead );
//ignore fields that have not changed
if ( $lead != null && isset( $lead[ $input_id ] ) && $value === rgget( (string) $input_id, $lead ) ) {
return;
}
$lead_detail_id = self::get_lead_detail_id( $current_fields, $input_id );
$result = self::queue_batch_field_operation( $form, $lead, $field, $lead_detail_id, $input_id, $value );
GFCommon::log_debug( __METHOD__ . "(): Queued field operation: {$field->label}(#{$input_id} - {$field->type})." );
}
}
/**
* Updates an existing field value in the database.
*
* @param array $form
* @param array $lead
* @param GF_Field $field
* @param int $lead_detail_id
* @param string $input_id
* @param string $value
*
* @return bool
*/
public static function update_lead_field_value( $form, $lead, $field, $lead_detail_id, $input_id, $value ) {
return self::update_entry_field_value( $form, $lead, $field, $lead_detail_id, $input_id, $value );
}
/**
* Updates an existing field value in the database.
*
* @since 2.3
*
* @param array $form
* @param array $entry
* @param GF_Field $field
* @param int $entry_meta_id
* @param string $input_id
* @param string $value
*
* @return bool
*/
public static function update_entry_field_value( $form, $entry, $field, $entry_meta_id, $input_id, $value, $item_index = '' ) {
global $wpdb;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::update_lead_field_value( $form, $entry, $field, $entry_meta_id, $input_id, $value );
}
/**
* Filter the value before it's saved to the database.
*
* @since 1.5.0
* @since 1.8.6 Added the $input_id parameter.
* @since 1.9.14 Added form and field specific versions.
*
* @param string|array $value The fields input value.
* @param array $entry The current entry object.
* @param GF_Field $field The current field object.
* @param array $form The current form object.
* @param string $input_id The ID of the input being saved or the field ID for single input field types.
*/
$value = apply_filters( 'gform_save_field_value', $value, $entry, $field, $form, $input_id );
$value = apply_filters( "gform_save_field_value_{$form['id']}", $value, $entry, $field, $form, $input_id );
if ( is_object( $field ) ) {
$value = apply_filters( "gform_save_field_value_{$form['id']}_{$field->id}", $value, $entry, $field, $form, $input_id );
}
if ( is_array( $value ) ) {
GFCommon::log_debug( __METHOD__ . '(): bailing. value is an array.' );
return false;
}
$entry_id = $entry['id'];
$form_id = $form['id'];
$entry_meta_table_name = self::get_entry_meta_table_name();
// Add emoji support.
if ( version_compare( get_bloginfo( 'version' ), '4.2', '>=' ) ) {
// Get charset for lead detail value column .
$charset = $wpdb->get_col_charset( $entry_meta_table_name, 'meta_value' );
// If entry detail value column is UTF-8, encode emoji.
if ( 'utf8' === $charset ) {
$value = wp_encode_emoji( $value );
}
}
if ( ! rgblank( $value ) ) {
if ( $entry_meta_id > 0 ) {
$result = $wpdb->update( $entry_meta_table_name, array( 'meta_value' => $value ), array( 'id' => $entry_meta_id ), array( '%s' ), array( '%d' ) );
if ( false === $result ) {
return false;
}
} else {
$result = $wpdb->insert( $entry_meta_table_name, array( 'entry_id' => $entry_id, 'form_id' => $form_id, 'meta_key' => $input_id, 'meta_value' => $value, 'item_index' => $item_index ), array( '%d', '%d', '%s', '%s', '%s' ) );
if ( false === $result ) {
return false;
}
}
} else {
// when the value is empty and no $entry_meta_id was set, check if it's a repeater field.
if ( empty( $entry_meta_id ) && $field instanceof GF_Field_Repeater && isset( $field->fields ) && is_array( $field->fields ) ) {
foreach ( $field->fields as $subfield ) {
self::update_entry_field_value( $form, $entry, $subfield, 0, $subfield->id, '' );
}
} else {
// Deleting details for this field
if ( is_array( $field->get_entry_inputs() ) ) {
$_input_id = ( false === strpos( $input_id, '.' ) ) ? sprintf( '%d.%%', $input_id ) : $input_id;
$sql = $wpdb->prepare( "DELETE FROM $entry_meta_table_name WHERE entry_id=%d AND meta_key LIKE %s ", $entry_id, $_input_id );
} else {
$sql = $wpdb->prepare( "DELETE FROM $entry_meta_table_name WHERE entry_id=%d AND meta_key = %s ", $entry_id, $input_id );
}
if ( $item_index ) {
$sql .= $wpdb->prepare( ' AND item_index=%s', $item_index );
}
$result = $wpdb->query( $sql );
if ( false === $result ) {
return false;
}
}
}
return true;
}
/**
* Returns the SQL to update or insert field values..
*
* @param array $form
* @param array $entry
* @param GF_Field $field
* @param int $entry_meta_id
* @param string $input_id
* @param string $value
* @param string $item_index
*
* @return bool
*/
public static function queue_batch_field_operation( $form, &$entry, $field, $entry_meta_id, $input_id, $value, $item_index = '' ) {
/**
* Filter the value before it's saved to the database.
*
* @since 1.5.0
* @since 1.8.6 Added the $input_id parameter.
* @since 1.9.14 Added form and field specific versions.
*
* @param string|array $value The fields input value.
* @param array $entry The current entry object.
* @param GF_Field $field The current field object.
* @param array $form The current form object.
* @param string $input_id The ID of the input being saved or the field ID for single input field types.
*/
$value = apply_filters( 'gform_save_field_value', $value, $entry, $field, $form, $input_id );
$value = apply_filters( "gform_save_field_value_{$form['id']}", $value, $entry, $field, $form, $input_id );
if ( is_object( $field ) ) {
$value = apply_filters( "gform_save_field_value_{$form['id']}_{$field->id}", $value, $entry, $field, $form, $input_id );
}
if ( is_array( $value ) ) {
GFCommon::log_debug( __METHOD__ . '(): bailing. value is an array.' );
return false;
}
$entry[ (string) $input_id . $item_index ] = $value;
$entry_id = $entry['id'];
$form_id = $form['id'];
if ( ! rgblank( $value ) ) {
if ( $entry_meta_id > 0 ) {
self::$_batch_field_updates[] = array( 'meta_value' => $value, 'id' => $entry_meta_id );
} else {
self::$_batch_field_inserts[] = array( 'entry_id' => $entry_id, 'form_id' => $form_id, 'meta_key' => $input_id, 'meta_value' => $value, 'item_index' => $item_index );
}
} elseif ( $entry_meta_id > 0 && ! in_array( $input_id, GFFormsModel::get_lead_db_columns() ) ) {
self::$_batch_field_deletes[] = $entry_meta_id;
}
return true;
}
/**
* Checks if any field updates, inserts, or deletions have been registered for batch processing.
*
* @since 2.4.17
*
* @return bool
*/
public static function has_batch_field_operations() {
return ! empty( self::$_batch_field_updates ) || ! empty( self::$_batch_field_inserts ) || ! empty( self::$_batch_field_deletes );
}
public static function flush_batch_field_operations() {
self::$_batch_field_updates = array();
self::$_batch_field_inserts = array();
self::$_batch_field_deletes = array();
}
public static function begin_batch_field_operations() {
self::flush_batch_field_operations();
}
/**
* Performs the update, inserts and deletes registered by queue_batch_field_operation()
*
* @return array An array of results.
*/
public static function commit_batch_field_operations() {
global $wpdb;
$meta_table = self::get_entry_meta_table_name();
$results = array(
'updates' => null,
'inserts' => null,
'deletes' => null,
);
// Updates
if ( ! empty( self::$_batch_field_updates ) ) {
$values = array();
foreach ( self::$_batch_field_updates as $update ) {
$values[] = $wpdb->prepare( '(%s,%s)', $update['id'], $update['meta_value'] );
}
$values_str = join( ',', $values );
$update_sql = "INSERT INTO {$meta_table} (id,meta_value)
VALUES {$values_str}
ON DUPLICATE KEY UPDATE meta_value=VALUES(meta_value);";
$result = $wpdb->query( $update_sql );
if ( $result === false ) {
$result = new WP_Error( 'update_error', $wpdb->last_error );
}
$results['updates'] = $result;
}
// Inserts
if ( ! empty( self::$_batch_field_inserts ) ) {
$values = array();
foreach ( self::$_batch_field_inserts as $insert ) {
$values[] = $wpdb->prepare( '(%d,%d,%s,%s,%s)', $insert['entry_id'], $insert['form_id'], $insert['meta_key'], $insert['meta_value'], $insert['item_index'] );
}
$values_str = join( ',', $values );
$insert_sql = "INSERT INTO {$meta_table} (entry_id, form_id, meta_key, meta_value, item_index) VALUES {$values_str};";
$result = $wpdb->query( $insert_sql );
if ( $result === false ) {
$result = new WP_Error( 'insert_error', $wpdb->last_error );
}
$results['inserts'] = $result;
}
// Deletes
if ( ! empty( self::$_batch_field_deletes ) ) {
$in_str_arr = array_fill( 0, count( self::$_batch_field_deletes ), '%d' );
$in_str = join( ',', $in_str_arr );
$ids = array_map( 'absint', self::$_batch_field_deletes );
$delete_sql = $wpdb->prepare( "DELETE FROM {$meta_table} WHERE id IN ( {$in_str} )", $ids);
$result = $wpdb->query( $delete_sql );
if ( $result === false ) {
$result = new WP_Error( 'delete_error', $wpdb->last_error );
}
$results['deletes'] = $result;
}
self::flush_batch_field_operations();
return $results;
}
private static function move_temp_file( $form_id, $tempfile_info ) {
_deprecated_function( 'move_temp_file', '1.9', 'GF_Field_Fileupload::move_temp_file' );
$target = self::get_file_upload_path( $form_id, $tempfile_info['uploaded_filename'] );
$source = self::get_upload_path( $form_id ) . '/tmp/' . $tempfile_info['temp_filename'];
if ( rename( $source, $target['path'] ) ) {
self::set_permissions( $target['path'] );
return $target['url'];
} else {
return 'FAILED (Temporary file could not be moved.)';
}
}
public static function set_permissions( $path ) {
$permission = apply_filters( 'gform_file_permission', 0644, $path );
if ( $permission ) {
@chmod( $path, $permission );
}
}
public static function upload_file( $form_id, $file ) {
_deprecated_function( 'upload_file', '1.9', 'GF_Field_Fileupload::upload_file' );
$target = self::get_file_upload_path( $form_id, $file['name'] );
if ( ! $target ) {
GFCommon::log_debug( 'GFFormsModel::upload_file(): FAILED (Upload folder could not be created.)' );
return 'FAILED (Upload folder could not be created.)';
}
if ( move_uploaded_file( $file['tmp_name'], $target['path'] ) ) {
GFCommon::log_debug( 'GFFormsModel::upload_file(): Setting permissions on ' . $target['path'] );
self::set_permissions( $target['path'] );
return $target['url'];
} else {
GFCommon::log_debug( 'GFFormsModel::upload_file(): FAILED (Temporary file could not be copied.)' );
return 'FAILED (Temporary file could not be copied.)';
}
}
public static function get_upload_root() {
$dir = wp_upload_dir();
if ( $dir['error'] ) {
return null;
}
return $dir['basedir'] . '/gravity_forms/';
}
public static function get_upload_url_root() {
$dir = wp_upload_dir();
if ( $dir['error'] ) {
return null;
}
return $dir['baseurl'] . '/gravity_forms/';
}
public static function get_upload_path( $form_id ) {
$form_id = absint( $form_id );
return self::get_upload_root() . $form_id . '-' . wp_hash( $form_id );
}
public static function get_upload_url( $form_id ) {
$form_id = absint( $form_id );
$dir = wp_upload_dir();
return $dir['baseurl'] . "/gravity_forms/$form_id" . '-' . wp_hash( $form_id );
}
public static function get_file_upload_path( $form_id, $file_name, $increment_found = true ) {
if ( version_compare( phpversion(), '7.4', '<' ) && get_magic_quotes_gpc() ) {
$file_name = stripslashes( $file_name );
}
$form_id = absint( $form_id );
//adding filter to upload root path and url
$upload_root_info = GF_Field_FileUpload::get_upload_root_info( $form_id );
$target_root = $upload_root_info['path'];
$target_root_url = $upload_root_info['url'];
$default_upload_root_info = GF_Field_FileUpload::get_default_upload_roots( $form_id );
$default_target_root = rgar( $default_upload_root_info, 'path' );
$y = rgar( $default_upload_root_info, 'y' );
$m = rgar( $default_upload_root_info, 'm' );
$target_root = trailingslashit( $target_root );
if ( ! is_dir( $target_root ) ) {
if ( ! wp_mkdir_p( $target_root ) ) {
return false;
}
// Adding index.html files to all subfolders.
if ( $default_target_root != $target_root && ! file_exists( $target_root . 'index.html' ) ) {
GFCommon::recursive_add_index_file( $target_root );
} elseif ( ! file_exists( self::get_upload_root() . '/index.html' ) ) {
GFCommon::recursive_add_index_file( self::get_upload_root() );
} elseif ( ! file_exists( self::get_upload_path( $form_id ) . '/index.html' ) ) {
GFCommon::recursive_add_index_file( self::get_upload_path( $form_id ) );
} elseif ( ! file_exists( self::get_upload_path( $form_id ) . "/$y/index.html" ) ) {
GFCommon::recursive_add_index_file( self::get_upload_path( $form_id ) . "/$y" );
} else {
GFCommon::recursive_add_index_file( self::get_upload_path( $form_id ) . "/$y/$m" );
}
}
//Add the original filename to our target path.
//Result is "uploads/filename.extension"
$extension = pathinfo( $file_name, PATHINFO_EXTENSION );
if ( ! empty( $extension ) ) {
$extension = '.' . $extension;
}
$file_name = wp_basename( $file_name, $extension );
$file_name = sanitize_file_name( $file_name );
$counter = 1;
$target_path = $target_root . $file_name . $extension;
while ( $increment_found && file_exists( $target_path ) ) {
$target_path = $target_root . $file_name . "$counter" . $extension;
$counter ++;
}
//Remove '.' from the end if file does not have a file extension
$target_path = trim( $target_path, '.' );
//creating url
$target_url = str_replace( $target_root, $target_root_url, $target_path );
return array( 'path' => $target_path, 'url' => $target_url );
}
public static function get_tables() {
return array(
self::get_form_view_table_name(),
self::get_meta_table_name(),
self::get_form_table_name(),
self::get_form_revisions_table_name(),
self::get_entry_table_name(),
self::get_entry_meta_table_name(),
self::get_entry_notes_table_name(),
self::get_draft_submissions_table_name(),
self::get_rest_api_keys_table_name(),
);
}
public static function drop_tables() {
global $wpdb;
remove_filter( 'query', array( 'GFForms', 'filter_query' ) );
foreach ( GF_Forms_Model_Legacy::get_legacy_tables() as $table ) {
$wpdb->query( "DROP TABLE IF EXISTS $table" );
}
foreach ( self::get_tables() as $table ) {
$wpdb->query( "DROP TABLE IF EXISTS $table" );
}
add_filter( 'query', array( 'GFForms', 'filter_query' ) );
}
/**
* Target for the wpmu_drop_tables filter. Adds all tables for Gravity Forms and the Add-On Framework to list
* of tables to drop when a site is deleted.
*
* @param $drop_tables
*
* @return array
*/
public static function mu_drop_tables( $drop_tables ) {
global $wpdb;
$addon_tables = array(
self::get_addon_feed_table_name(),
$wpdb->prefix . 'gf_addon_payment_callback',
$wpdb->prefix . 'gf_addon_payment_transaction',
);
$drop_tables = array_merge( $drop_tables, $addon_tables );
$core_tables = self::get_tables();
$drop_tables = array_merge( $drop_tables, $core_tables );
$legacy_tables = GF_Forms_Model_Legacy::get_legacy_tables();
$drop_tables = array_merge( $drop_tables, $legacy_tables );
// Prevent the legacy table query notice when they are dropped by wp_uninitialize_site().
remove_filter( 'query', array( 'GFForms', 'filter_query' ) );
return $drop_tables;
}
public static function insert_form_view( $form_id, $deprecated = null ) {
global $wpdb;
$table_name = self::get_form_view_table_name();
$sql = $wpdb->prepare(
" SELECT id FROM $table_name
WHERE form_id=%d
AND date_created BETWEEN DATE_SUB(utc_timestamp(), INTERVAL 1 DAY) AND utc_timestamp()", $form_id
);
$id = $wpdb->get_var( $sql, 0, 0 );
if ( empty( $id ) ) {
$wpdb->query( $wpdb->prepare( "INSERT INTO $table_name(form_id, date_created, ip) values(%d, utc_timestamp(), %s)", $form_id, '' ) );
} else {
$wpdb->query( $wpdb->prepare( "UPDATE $table_name SET count = count+1 WHERE id=%d", $id ) );
}
}
public static function is_duplicate( $form_id, $field, $value ) {
global $wpdb;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::is_duplicate( $form_id, $field, $value );
}
$entry_meta_table_name = self::get_entry_meta_table_name();
$entry_table_name = self::get_entry_table_name();
$sql_comparison = 'ld.meta_value = %s';
switch ( GFFormsModel::get_input_type( $field ) ) {
case 'time':
$value = sprintf( "%02d:%02d %s", $value[0], $value[1], $value[2] );
break;
case 'date':
$value = self::prepare_date( $field->dateFormat, $value );
break;
case 'number':
$value = GFCommon::clean_number( $value, $field->numberFormat );
break;
case 'phone':
$value = str_replace( array( ')', '(', '-', ' ' ), '', $value );
$sql_comparison = 'replace( replace( replace( replace( ld.meta_value, ")", "" ), "(", "" ), "-", "" ), " ", "" ) = %s';
break;
case 'email':
$value = is_array( $value ) ? rgar( $value, 0 ) : $value;
break;
}
$inner_sql_template = "SELECT %s as input, ld.entry_id
FROM {$entry_meta_table_name} ld
INNER JOIN {$entry_table_name} l ON l.id = ld.entry_id\n";
$inner_sql_template .= "WHERE l.form_id=%d AND ld.form_id=%d
AND ld.meta_key = %s
AND status='active' AND {$sql_comparison}";
$sql = "SELECT count(distinct input) as match_count FROM ( ";
$input_count = 1;
if ( is_array( $field->get_entry_inputs() ) ) {
$input_count = sizeof( $field->inputs );
$inner_sql = '';
foreach ( $field->inputs as $input ) {
$union = empty( $inner_sql ) ? '' : ' UNION ALL ';
$inner_sql .= $union . $wpdb->prepare( $inner_sql_template, $input['id'], $form_id, $form_id, $input['id'], $value[ $input['id'] ] );
}
} else {
$inner_sql = $wpdb->prepare( $inner_sql_template, $field->id, $form_id, $form_id, $field->id, $value );
}
$sql .= $inner_sql . "
) as count
GROUP BY entry_id
ORDER BY match_count DESC";
$count = gf_apply_filters( array( 'gform_is_duplicate', $form_id ), $wpdb->get_var( $sql ), $form_id, $field, $value );
return $count != null && $count >= $input_count;
}
public static function get_lead( $lead_id ) {
$entry = GFAPI::get_entry( $lead_id );
if ( is_wp_error( $entry ) ) {
$entry = false;
}
return $entry;
}
public static function get_entry( $entry_id ) {
return GFAPI::get_entry( $entry_id );
}
public static function get_lead_notes( $lead_id ) {
global $wpdb;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::get_lead_notes( $lead_id );
}
$notes_table = self::get_entry_notes_table_name();
return $wpdb->get_results(
$wpdb->prepare(
" SELECT n.id, n.user_id, n.date_created, n.value, n.note_type, n.sub_type, ifnull(u.display_name,n.user_name) as user_name, u.user_email
FROM $notes_table n
LEFT OUTER JOIN $wpdb->users u ON n.user_id = u.id
WHERE entry_id=%d ORDER BY id", $lead_id
)
);
}
/**
* Get a collection of notes.
*
* @since 2.4.18
*
* @param array $search_criteria {
* Array of search criteria.
*
* @type int $id Get the note with this ID.
* @type int $entry_id Get notes associated with this entry ID.
* @type int $user_id Get notes with this user ID.
* @type string $user_name Get notes with this user name.
* @type string $note_type Get notes with this note type.
* @type string $sub_type Get notes with this sub type.
* @type string $start_date Get notes on or after this date. Expects SQL datetime format.
* @type string $end_date Get notes on or before this date. Expects SQL datetime format.
* }
* @param null|array $sorting {
* Array of sort key and direction.
*
* @type string $key Key to sort on. Options: id, entry_id, user_id, user_name, note_type, sub_type, date.
* @type string $direction Sort direction. Options: ASC, DESC.
* }
* @return array|null|object
*/
public static function get_notes( $search_criteria = array(), $sorting = null ) {
global $wpdb;
$where = array();
if ( rgar( $search_criteria, 'id' ) ) {
$where[] = $wpdb->prepare( 'n.id = %d', $search_criteria['id'] );
}
if ( rgar( $search_criteria, 'entry_id' ) ) {
$where[] = $wpdb->prepare( 'entry_id = %d', $search_criteria['entry_id'] );
}
if ( rgars( $search_criteria, 'user_id' ) ) {
$where[] = $wpdb->prepare( 'user_id = %d', $search_criteria['user_id'] );
}
if ( isset( $search_criteria['user_name'] ) ) {
if ( '' !== $search_criteria['user_name'] ) {
$where[] = $wpdb->prepare('user_name = %s', $search_criteria['user_name']);
} else {
$where[] = "( user_name = '' OR user_name IS NULL )";
}
}
if ( rgar( $search_criteria, 'note_type' ) ) {
$where[] = $wpdb->prepare( 'note_type = %s', $search_criteria['note_type'] );
}
if ( isset( $search_criteria['sub_type'] ) ) {
if ( '' !== $search_criteria['sub_type'] ) {
$where[] = $wpdb->prepare('sub_type = %s', $search_criteria['sub_type']);
} else {
$where[] = "( sub_type = '' OR sub_type IS NULL )";
}
}
if ( rgar( $search_criteria, 'start_date' ) ) {
if ( ! is_numeric( $search_criteria['start_date'] ) || (int) $search_criteria['start_date'] != $search_criteria['start_date'] ) {
$search_criteria['start_date'] = strtotime( $search_criteria['start_date'] );
}
$valid_timestamp = gmdate( 'Y-m-d H:i:s', $search_criteria['start_date'] );
$where[] = $wpdb->prepare( 'timestampdiff(SECOND, %s, date_created) >= 0', $valid_timestamp );
}
if ( rgar( $search_criteria, 'end_date' ) ) {
if ( ! is_numeric( $search_criteria['end_date'] ) || (int) $search_criteria['end_date'] != $search_criteria['end_date'] ) {
$search_criteria['end_date'] = strtotime( $search_criteria['end_date'] );
}
$valid_timestamp = gmdate( 'Y-m-d H:i:s', $search_criteria['end_date'] );
// The user didn't specify and end time, so search until the end of the day.
if ( '00:00:00' == substr( $valid_timestamp, -8 ) ) {
$valid_timestamp = gmdate( 'Y-m-d', $search_criteria['end_date'] ) . ' 23:59:59';
}
$where[] = $wpdb->prepare( 'timestampdiff(SECOND, %s, date_created) <= 0', $valid_timestamp );
}
$where = 'WHERE ' . implode( ' AND ', $where );
if ( empty( $search_criteria ) ) {
$where = '';
}
if ( is_array( $sorting ) && ! empty( $sorting ) ) {
$sorting_options = array( 'entry_id', 'id', 'user_id', 'date_created', 'value', 'note_type', 'sub_type', 'user_name', 'user_email' );
if ( ! isset( $sorting['key'] ) || ! in_array( $sorting['key'], $sorting_options ) ) {
$sorting['key'] = 'n.id';
}
if ( 'id' == $sorting['key'] ) {
$sorting['key'] = 'n.id';
}
$direction_options = array( 'ASC', 'DESC' );
if ( ! isset( $sorting['direction'] ) || ! in_array( $sorting['direction'], $direction_options ) ) {
$sorting['direction'] = 'ASC';
}
$orderby = 'ORDER BY ' . $sorting['key'] . ' ' . $sorting['direction'];
} else {
$orderby = 'ORDER BY n.id ASC';
}
$notes_table = self::get_entry_notes_table_name();
return $wpdb->get_results(
" SELECT n.entry_id, n.id, n.user_id, n.date_created, n.value, n.note_type, n.sub_type, ifnull(u.display_name,n.user_name) as user_name, u.user_email
FROM $notes_table n
LEFT OUTER JOIN $wpdb->users u ON n.user_id = u.id
$where
$orderby"
);
}
public static function refresh_lead_field_value( $lead_id, $field_id ) {
if ( version_compare( GFForms::$version, '2.3-dev', '>=' ) ) {
_deprecated_function( 'GFFormsModel::refresh_lead_field_value', '2.3' );
}
$cache_key = 'GFFormsModel::get_lead_field_value_' . $lead_id . '_' . $field_id;
GFCache::delete( $cache_key );
}
/**
* @param $lead
* @param $field GF_Field
*
* @return array|bool|mixed|string|null
*/
public static function get_lead_field_value( $lead, $field ) {
if ( empty( $lead ) || ! is_array( $lead ) ) {
return null;
}
$field_id = $field instanceof GF_Field ? $field->id : rgar( $field, 'id' );
$value = array();
$inputs = $field instanceof GF_Field ? $field->get_entry_inputs() : rgar( $field, 'inputs' );
if ( is_array( $inputs ) ) {
// making sure values submitted are sent in the value even if
// there isn't an input associated with it
$lead_field_keys = array_keys( $lead );
natsort( $lead_field_keys );
foreach ( $lead_field_keys as $input_id ) {
if ( is_numeric( $input_id ) && absint( $input_id ) == absint( $field_id ) ) {
$val = $lead[ $input_id ];
$value[ $input_id ] = $val;
}
}
} else {
$value = rgget( $field_id, $lead );
}
// filtering lead value
$value = apply_filters( 'gform_get_field_value', $value, $lead, $field );
return $value;
}
/**
*
* @deprecated 2.0
* @param $lead
* @param $field_number
* @param $form
* @param bool $apply_filter
*
* @return mixed|null|string
*/
public static function get_field_value_long( $lead, $field_number, $form, $apply_filter = true ) {
_deprecated_function( 'get_field_value_long', '2.0', 'get_lead_field_value' );
global $wpdb;
$detail_table_name = self::get_lead_details_table_name();
$long_table_name = self::get_lead_details_long_table_name();
$sql = $wpdb->prepare(
" SELECT l.value FROM $detail_table_name d
INNER JOIN $long_table_name l ON l.lead_detail_id = d.id
WHERE lead_id=%d AND field_number BETWEEN %s AND %s", $lead['id'], doubleval( $field_number ) - 0.0001, doubleval( $field_number ) + 0.0001
);
$val = $wpdb->get_var( $sql );
//running aform_get_input_value when needed
if ( $apply_filter ) {
$field = RGFormsModel::get_field( $form, $field_number );
$input_id = (string) $field_number == (string) $field->id ? '' : $field_number;
$val = gf_apply_filters( array( 'gform_get_input_value', $field->formId, $field->id, $input_id ), $val, $lead, $field, $input_id );
}
return $val;
}
/**
* @param $meta_key
* @param $meta_value
*
* @return array
*/
public static function get_leads_by_meta( $meta_key, $meta_value ) {
return self::get_entries_by_meta( $meta_key, $meta_value );
}
/**
* Searches entries by entry meta
*
* @since 2.3
*
* @param $meta_key
* @param $meta_value
*
* @return array
*/
public static function get_entries_by_meta( $meta_key, $meta_value ) {
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::get_leads_by_meta( $meta_key, $meta_value );
}
$args = array(
'meta_key' => $meta_key,
'meta_value' => $meta_value,
'meta_compare' => '=',
);
$query = new GF_Query( $args );
return $query->entries;
}
/**
*
* @deprecated 2.3
*
* @param $form_id
* @param int $sort_field_number
* @param string $sort_direction
* @param string $search
* @param int $offset
* @param int $page_size
* @param null $star
* @param null $read
* @param bool $is_numeric_sort
* @param null $start_date
* @param null $end_date
* @param string $status
* @param bool $payment_status
*
* @return mixed
*/
public static function get_leads( $form_id, $sort_field_number = 0, $sort_direction = 'DESC', $search = '', $offset = 0, $page_size = 30, $star = null, $read = null, $is_numeric_sort = false, $start_date = null, $end_date = null, $status = 'active', $payment_status = false ) {
_deprecated_function( 'GFFormsModel::get_leads', '2.3', 'GFAPI::get_entries' );
$search_criteria = array(
'status' => $status,
);
if ( ! empty( $search ) ) {
$search_criteria['field_filters'][] = array( 'value' => $search );
}
if ( ! is_null( $star ) ) {
$search_criteria['field_filters'][] = array( 'is_starred' => $star );
}
if ( ! is_null( $read ) ) {
$search_criteria['field_filters'][] = array( 'is_read' => $read );
}
if ( $payment_status ) {
$search_criteria['field_filters'][] = array( 'payment_status' => $read );
}
$sorting = array(
'key' => $sort_field_number,
'direction' => $sort_direction
);
if ( $is_numeric_sort ) {
$sorting['is_numeric'] = true;
}
$paging = array(
'offset' => $offset,
'page_size' => $page_size,
);
if ( ! is_null( $start_date ) ) {
$search_criteria['start_date'] = $start_date;
}
if ( ! is_null( $end_date ) ) {
$search_criteria['end_date'] = $end_date;
}
return GFAPI::get_entries( $form_id, $search_criteria, $sorting, $paging );
}
/**
*
* @deprecated 2.3
*
* @param $args
*
* @return string
*/
public static function get_leads_where_sql( $args ) {
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::get_leads_where_sql( $args ) ;
}
return self::get_entries_where_sql( $args );
}
/**
* @deprecated 2.3
* @param $results
*
* @return array
*/
public static function build_lead_array( $results ) {
return GF_Forms_Model_Legacy::build_lead_array( $results );
}
/***
* Saves the Gravity Forms license key to the database and registers the site and license key with the Gravity Forms licensing server.
*
* @since 1.0
*
* @param string $new_key Gravity Forms license key to be saved.
*/
public static function save_key( $new_key ) {
$new_key = trim( $new_key );
$new_key_md5 = md5( $new_key );
$previous_key = get_option( 'rg_gforms_key' );
/**
* @var License\GF_License_API_Connector $license_connector
*/
$license_connector = GFForms::get_service_container()->get( License\GF_License_Service_Provider::LICENSE_API_CONNECTOR );
$license_connector->clear_cache_for_key( $new_key_md5 );
// Delete gform_version_info so GF will ping version.php to send site record update.
delete_option( 'gform_version_info' );
if ( empty( $new_key ) ) {
if ( is_multisite() && is_main_site() ) {
$sites = get_sites();
foreach ( $sites as $site ) {
delete_blog_option( $site->blog_id, 'rg_gforms_key' );
}
}
delete_option( 'rg_gforms_key' );
// Unlink the site with the license key on Gravity API.
$license_connector->update_site_registration( '' );
} elseif ( $previous_key != $new_key ) {
self::update_license_key( $new_key_md5 );
// Updating site registration with Gravity Server.
$result = $license_connector->update_site_registration( $new_key_md5, true );
// New key is invalid, revert to old key.
if ( ! $result->can_be_used() ) {
self::update_license_key( $previous_key );
}
} else {
// Updating site registration with Gravity Server.
$license_connector->update_site_registration( $new_key_md5, true );
}
}
/**
* Use GFAPI::count_entries() instead.
*
* @deprecated 2.3.0.1
*
*
* @param $form_id
* @param $search
* @param null $star
* @param null $read
* @param null $start_date
* @param null $end_date
* @param null $status
* @param null $payment_status
*
* @return null|string
*/
public static function get_lead_count( $form_id, $search, $star = null, $read = null, $start_date = null, $end_date = null, $status = null, $payment_status = null ) {
_deprecated_function( 'GFFormsModel::get_lead_count', '2.3.0.1', 'GFAPI::count_entries');
global $wpdb;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::get_lead_count( $form_id, $search, $star, $read, $start_date, $end_date, $status, $payment_status ) ;
}
if ( ! is_numeric( $form_id ) ) {
return '';
}
$entry_meta_table_name = self::get_entry_meta_table_name();
$entry_table_name = self::get_entry_table_name();
$where = self::get_entries_where_sql( compact( 'form_id', 'search', 'status', 'star', 'read', 'start_date', 'end_date', 'payment_status', 'is_default' ) );
$sql = "SELECT count(distinct l.id)
FROM $entry_table_name l
INNER JOIN $entry_meta_table_name ld ON l.id = ld.entry_id
$where";
return $wpdb->get_var( $sql );
}
/**
* Returns the WHERE clause for an entry search.
*
* This function is not used and is only included for backwards compatibility. Use GFAPI::count_entries() instead.
*
* @deprecated 2.3.0.1
*
* @since 2.3.0.1
*
* @param $args
*
* @return string
*/
public static function get_entries_where_sql( $args ) {
_doing_it_wrong( 'GFFormsModel::get_entries_where_sql', 'Use GFAPI::count_entries instead', '2.3.0.1');
global $wpdb;
extract(
wp_parse_args(
$args, array(
'form_id' => false,
'search' => '',
'status' => 'active',
'star' => null,
'read' => null,
'start_date' => null,
'end_date' => null,
'payment_status' => null,
'is_default' => true,
)
)
);
$where = array();
if ( $is_default ) {
$where[] = "l.form_id = $form_id";
}
if ( $search && $is_default ) {
$where[] = $wpdb->prepare( 'meta_value LIKE %s', "%$search%" );
} else if ( $search ) {
$where[] = $wpdb->prepare( 'd.meta_value LIKE %s', "%$search%" );
}
if ( $star !== null && $status == 'active' ) {
$where[] = $wpdb->prepare( "is_starred = %d AND status = 'active'", $star );
}
if ( $read !== null && $status == 'active' ) {
$where[] = $wpdb->prepare( "is_read = %d AND status = 'active'", $read );
}
if ( $payment_status ) {
$where[] = $wpdb->prepare( "payment_status = '%s'", $payment_status );
}
if ( $status !== null ) {
$where[] = $wpdb->prepare( 'status = %s', $status );
}
if ( ! empty( $start_date ) ) {
$where[] = "timestampdiff(SECOND, '$start_date', date_created) >= 0";
}
if ( ! empty( $end_date ) ) {
$where[] = "timestampdiff(SECOND, '$end_date', date_created) <= 0";
}
return 'WHERE ' . implode( ' AND ', $where );
}
/**
*
*
* @param $form_id
* @param $search
* @param null $star
* @param null $read
* @param null $start_date
* @param null $end_date
* @param null $status
* @param null $payment_status
*
* @return array|string
*/
public static function get_lead_ids( $form_id, $search, $star = null, $read = null, $start_date = null, $end_date = null, $status = null, $payment_status = null ) {
global $wpdb;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::get_lead_ids( $form_id, $search, $star, $read, $start_date, $end_date, $status, $payment_status ) ;
}
if ( ! is_numeric( $form_id ) ) {
return '';
}
$entry_meta_table_name = self::get_entry_meta_table_name();
$entry_table_name = self::get_entry_table_name();
$where = self::get_entries_where_sql( compact( 'form_id', 'search', 'status', 'star', 'read', 'start_date', 'end_date', 'payment_status', 'is_default' ) );
$sql = "SELECT distinct l.id
FROM $entry_table_name l
INNER JOIN $entry_meta_table_name ld ON l.id = ld.entry_id
$where";
$rows = $wpdb->get_results( $sql );
if ( empty( $rows ) ) {
return array();
}
$entry_ids = array();
foreach ( $rows as $row ) {
$entry_ids[] = $row->id;
}
return $entry_ids;
}
public static function get_grid_columns( $form_id, $input_label_only = false ) {
$form = self::get_form_meta( $form_id );
$field_ids = self::get_grid_column_meta( $form_id );
if ( ! is_array( $field_ids ) ) {
$field_ids = array();
foreach ( $form['fields'] as $field ) {
/* @var GF_Field $field */
//loading post category fields with choices and inputs
if ( $field->type == 'post_category' ) {
$field = GFCommon::add_categories_as_choices( $field, '' );
}
if ( $field->displayOnly || $field->get_input_type() == 'list' || $field->get_input_type() == 'repeater' ) {
continue;
}
$inputs = $field->get_entry_inputs();
if ( is_array( $inputs ) ) {
if ( $field->type == 'name' ) {
$field_ids[] = $field->id . '.3'; //adding first name
$field_ids[] = $field->id . '.6'; //adding last name
} else {
foreach ( $inputs as $input ) {
if ( rgar( $input, 'isHidden' ) ) {
continue;
}
$field_ids[] = $input['id']; //getting first input
break;
}
}
} else {
$field_ids[] = $field->id;
}
if ( count( $field_ids ) >= 5 ) {
break;
}
}
//adding default entry meta columns
$entry_metas = GFFormsModel::get_entry_meta( $form_id );
foreach ( $entry_metas as $key => $entry_meta ) {
if ( rgar( $entry_meta, 'is_default_column' ) ) {
$field_ids[] = $key;
}
}
}
$columns = array();
$entry_meta = self::get_entry_meta( $form_id );
foreach ( $field_ids as $field_id ) {
switch ( $field_id ) {
case 'id' :
$columns[ $field_id ] = array( 'label' => esc_html__( 'Entry Id', 'gravityforms' ), 'type' => 'id' );
break;
case 'ip' :
$columns[ $field_id ] = array( 'label' => esc_html__( 'User IP', 'gravityforms' ), 'type' => 'ip' );
break;
case 'date_created' :
$columns[ $field_id ] = array( 'label' => esc_html__( 'Entry Date', 'gravityforms' ), 'type' => 'date_created' );
break;
case 'source_url' :
$columns[ $field_id ] = array( 'label' => esc_html__( 'Source Url', 'gravityforms' ), 'type' => 'source_url' );
break;
case 'payment_status' :
$columns[ $field_id ] = array( 'label' => esc_html__( 'Payment Status', 'gravityforms' ), 'type' => 'payment_status' );
break;
case 'transaction_id' :
$columns[ $field_id ] = array( 'label' => esc_html__( 'Transaction Id', 'gravityforms' ), 'type' => 'transaction_id' );
break;
case 'payment_date' :
$columns[ $field_id ] = array( 'label' => esc_html__( 'Payment Date', 'gravityforms' ), 'type' => 'payment_date' );
break;
case 'payment_amount' :
$columns[ $field_id ] = array( 'label' => esc_html__( 'Payment Amount', 'gravityforms' ), 'type' => 'payment_amount' );
break;
case 'created_by' :
$columns[ $field_id ] = array( 'label' => esc_html__( 'User', 'gravityforms' ), 'type' => 'created_by' );
break;
case ( ( is_string( $field_id ) || is_int( $field_id ) ) && array_key_exists( $field_id, $entry_meta ) ) :
$columns[ $field_id ] = array( 'label' => $entry_meta[ $field_id ]['label'], 'type' => $field_id );
break;
default :
$field = self::get_field( $form, $field_id );
if ( $field ) {
if ( $field->type === 'consent' ) {
if ( false !== strpos( $field_id, '.1' ) ) {
$columns[ strval( $field_id ) ] = array( 'label' => self::get_label( $field, $field_id, false ),
'type' => $field->type,
'inputType' => $field->inputType
);
}
} else {
$input_label_only = apply_filters( 'gform_entry_list_column_input_label_only', $input_label_only, $form, $field );
$columns[ strval( $field_id ) ] = array( 'label' => self::get_label( $field, $field_id, $input_label_only ),
'type' => $field->type,
'inputType' => $field->inputType
);
}
}
}
}
return $columns;
}
/**
* @param GF_Field $field
* @param int $input_id
* @param bool $input_only
*
* @return string
*/
public static function get_label( $field, $input_id = 0, $input_only = false, $allow_admin_label = true ) {
if ( ! $field instanceof GF_Field ) {
$field = GF_Fields::create( $field );
}
$field_label = ( GFForms::get_page() ||
RG_CURRENT_PAGE == 'select_columns.php' ||
RG_CURRENT_PAGE == 'print-entry.php' ||
rgget( 'gf_page', $_GET ) == 'select_columns' ||
rgget( 'gf_page', $_GET ) == 'print-entry' ||
$field->get_context_property( 'use_admin_label' )
) && ! empty( $field->adminLabel ) && $allow_admin_label ? $field->adminLabel : $field->label;
$input = self::get_input( $field, $input_id );
// Code4OtherChoice
if (self::get_input_type( $field ) == 'checkbox' && $input == null && $field->is_enableOtherChoice() && strtolower($field_label) == 'checkbox' ){
return 'Checkbox "Other"';
} else if ( self::get_input_type( $field ) == 'checkbox' && $input != null ) {
return $input['label'];
} else if ( $input != null ) {
if ( self::get_input_type( $field ) === 'consent' &&
( RG_CURRENT_PAGE == 'select_columns.php' ||
RG_CURRENT_PAGE == 'print-entry.php' ||
rgget( 'gf_page', $_GET ) == 'select_columns' ||
rgget( 'gf_page', $_GET ) == 'print-entry' ||
GFForms::get_page() === 'entry_list'
) ) {
return $field_label;
}
$input_label = rgar( $input, 'customLabel', rgar( $input, 'label' ) );
return $input_only ? $input_label : $field_label . ' (' . $input_label . ')';
} else {
return $field_label;
}
}
/**
* Get the text that tells the user that the field is required.
*
* @since 2.5
*
* @param $form_id
*
* @return string HTML required indicator.
*/
public static function get_required_indicator( $form_id ) {
$meta = self::get_form_meta( $form_id );
$required_indicator = rgar( $meta, 'requiredIndicator' );
switch( $required_indicator ) {
case 'text':
$indicator = esc_html__( '(Required)', 'gravityforms' );
$indicator_class = 'gfield_required_text';
break;
case 'asterisk':
$indicator = '*';
$indicator_class = 'gfield_required_asterisk';
break;
case 'custom':
$indicator = rgar( $meta, 'customRequiredIndicator' ) ? $meta['customRequiredIndicator'] : esc_html__( '(Required)', 'gravityforms' );
$indicator_class = 'gfield_required_custom';
break;
default:
$legacy_markup = GFCommon::is_legacy_markup_enabled( $meta );
$indicator = $legacy_markup ? '*' : esc_html__( '(Required)', 'gravityforms' );
$indicator_class = $legacy_markup ? 'gfield_required_asterisk' : 'gfield_required_text';
break;
}
return '<span class="gfield_required ' . $indicator_class . '">' . $indicator . '</span>';
}
/**
* @param GF_Field $field
* @param $id
*
* @return null
*/
public static function get_input( $field, $id ) {
if ( is_array( $field->inputs ) ) {
foreach ( $field->inputs as $input ) {
if ( $input['id'] == $id ) {
return $input;
}
}
}
return null;
}
public static function has_input( $field, $input_id ) {
if ( ! is_array( $field->inputs ) ) {
return false;
} else {
foreach ( $field->inputs as $input ) {
if ( $input['id'] == $input_id ) {
return true;
}
}
return false;
}
}
public static function get_current_page_url( $force_ssl = false ) {
$pageURL = 'http';
if ( RGForms::get( 'HTTPS', $_SERVER ) == 'on' || $force_ssl ) {
$pageURL .= 's';
}
$pageURL .= '://';
$pageURL .= RGForms::get( 'HTTP_HOST', $_SERVER ) . rgget( 'REQUEST_URI', $_SERVER );
return $pageURL;
}
public static function get_submitted_fields( $form_id ) {
global $wpdb;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::get_submitted_fields( $form_id );
}
$entry_meta_table_name = self::get_entry_meta_table_name();
$field_list = '';
$fields = $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT meta_key FROM $entry_meta_table_name WHERE form_id=%d", $form_id ) );
foreach ( $fields as $field ) {
$field_list .= intval( $field->meta_key ) . ',';
}
if ( ! empty( $field_list ) ) {
$field_list = substr( $field_list, 0, strlen( $field_list ) - 1 );
}
return $field_list;
}
/**
* Returns the field object for the requested field or input ID from the supplied or specified form.
*
* @since 2.3 Updated to support being passed the form id or form object as the first parameter.
* @since unknown.
* @access public
*
* @param array|int $form_or_id The Form Object or ID.
* @param string|int $field_id The field or input ID.
*
* @return GF_Field|null
*/
public static function get_field( $form_or_id, $field_id ) {
$form = is_numeric( $form_or_id ) ? self::get_form_meta( $form_or_id ) : $form_or_id;
if ( ! isset( $form['fields'] ) || ! isset( $form['id'] ) || ! is_array( $form['fields'] ) ) {
return null;
}
if ( is_numeric( $field_id ) ) {
// Removing floating part of field (i.e 1.3 -> 1) to return field by input id.
$field_id = intval( $field_id );
}
global $_fields;
$key = $form['id'] . '_' . $field_id;
$return = null;
if (isset( $_fields[ $key ] ) ) {
return $_fields[ $key ];
}
$_fields[ $key ] = null;
foreach ( $form['fields'] as $field ) {
if ( $field->id == $field_id ) {
$_fields[ $key ] = $field;
$return = $field;
break;
} elseif ( is_array( $field->fields ) ) {
$sub_field = self::get_sub_field( $field, $field_id );
if ( $sub_field ) {
$_fields[ $key ] = $sub_field;
return $sub_field;
}
}
}
return $return;
}
/**
* Returns the field inside a repeater field with the specified ID.
*
* @since 2.4
*
* @param GF_Field_Repeater $repeater_field The repeater field.
* @param int $field_id The field ID.
*
* @return null|GF_Field
*/
public static function get_sub_field( $repeater_field, $field_id ) {
if ( is_array( $repeater_field->fields ) ) {
foreach ( $repeater_field->fields as $field ) {
if ( $field->id == $field_id ) {
return $field;
} elseif ( is_array( $field->fields ) ) {
$f = self::get_sub_field( $field, $field_id );
if ( $f ) {
return $f;
}
}
}
}
return null;
}
public static function is_html5_enabled() {
return get_option( 'rg_gforms_enable_html5', false );
}
/**
* Return the current lead being processed. Should only be called when a form has been submitted.
* If called before the "real" lead has been saved to the database, uses self::create_lead() to create
* a temporary lead to work with.
*
* @since Unknown
* @since 2.7.1 Added the optional $form arg.
*
* @param array $form The form being processed or an empty array to get the form based on the ID from the gform_submit input.
*
* @return false|array
*/
public static function get_current_lead( $form = array() ) {
$form_id = absint( rgpost( 'gform_submit' ) );
// If a GF submission is not in process, always return false.
if ( empty( $form_id ) ) {
return false;
}
if ( rgar( self::$_current_lead, 'form_id' ) != $form_id ) {
if ( absint( rgar( $form, 'id' ) ) !== $form_id ) {
$form = self::get_form_meta( $form_id );
}
self::$_current_lead = self::create_lead( $form );
}
return self::$_current_lead;
}
/**
* Set RGFormsModel::$lead for use in hooks where $lead is not explicitly passed.
*
* @param mixed $lead
*/
public static function set_current_lead( $lead ) {
GFCache::flush();
self::$_current_lead = $lead;
}
/**
* Converts the legacy confirmation from forms created prior to v1.7 to the current format or adds the default confirmation.
*
* @since 1.7
* @since 2.4.15 Fixed corrupt confirmation being created when form doesn't have one to convert.
*
* @param array $form The form being processed.
*
* @return array
*/
public static function convert_confirmation( $form ) {
$confirmation = rgar( $form, 'confirmation' );
if ( ! empty( $confirmation['type'] ) ) {
// Complete converting legacy confirmation by adding missing properties.
$confirmation['id'] = uniqid();
$confirmation['name'] = esc_html__( 'Default Confirmation', 'gravityforms' );
$confirmation['isDefault'] = true;
} else {
// Form does not have a valid legacy confirmation add the default confirmation instead.
$confirmation = self::get_default_confirmation();
}
$form['confirmations'] = array( $confirmation['id'] => $confirmation );
self::save_form_confirmations( $form['id'], $form['confirmations'] );
return $form;
}
/**
* Returns a default confirmation.
*
* @since 2.4.15
*
* @param string $event The confirmation event. form_saved, form_save_email_sent, or an empty string for the default form submission event.
*
* @return array
*/
public static function get_default_confirmation( $event = '' ) {
switch ( $event ) {
case 'form_saved':
return array(
'id' => uniqid( 'sc1' ),
'event' => 'form_saved',
'name' => __( 'Save and Continue Confirmation', 'gravityforms' ),
'isDefault' => true,
'type' => 'message',
'message' => sprintf(
'<h2>%s</h2><p role="alert">%s</p><p class="resume_form_link_wrapper">{save_link}</p><p>%s<br />%s</p>{save_email_input}',
__( 'Link to continue editing later', 'gravityforms' ),
__( 'Please use the following link to return and complete this form from any computer.', 'gravityforms' ),
__( 'Note: This link will expire after 30 days.', 'gravityforms' ),
__( 'Enter your email address if you would like to receive the link via email.', 'gravityforms' )
),
'url' => '',
'pageId' => '',
'queryString' => '',
);
case 'form_save_email_sent':
return array(
'id' => uniqid( 'sc2' ),
'event' => 'form_save_email_sent',
'name' => __( 'Save and Continue Email Sent Confirmation', 'gravityforms' ),
'isDefault' => true,
'type' => 'message',
'message' => sprintf(
'<h2 class="saved_message_success">%s</h2><p>%s <span class="saved_message_email">{save_email}</span></p>',
__( 'Success!', 'gravityforms' ),
__( 'The link was sent to the following email address:', 'gravityforms' )
),
'url' => '',
'pageId' => '',
'queryString' => '',
);
default:
return array(
'id' => uniqid(),
'name' => __( 'Default Confirmation', 'gravityforms' ),
'isDefault' => true,
'type' => 'message',
'message' => __( 'Thanks for contacting us! We will get in touch with you shortly.', 'gravityforms' ),
'url' => '',
'pageId' => '',
'queryString' => '',
);
}
}
public static function load_confirmations( $form ) {
$confirmations = self::get_form_confirmations( $form['id'] );
// If there are no confirmations convert the legacy confirmation or add the default.
if ( empty( $confirmations ) ) {
$form = self::convert_confirmation( $form );
} else {
$form['confirmations'] = $confirmations;
}
return $form;
}
public static function get_form_confirmations( $form_id ) {
global $wpdb;
$key = self::get_form_cache_key( $form_id );
if ( isset( self::$_confirmations[ $key ] ) ) {
return self::$_confirmations[ $key ];
}
$tablename = GFFormsModel::get_meta_table_name();
$sql = $wpdb->prepare( "SELECT confirmations FROM $tablename WHERE form_id = %d", $form_id );
$results = $wpdb->get_results( $sql, ARRAY_A );
$confirmations = rgars( $results, '0/confirmations', array() );
if ( ! empty( $confirmations ) ) {
$confirmations = self::remove_corrupt_confirmations( self::unserialize( $confirmations ) );
}
self::$_confirmations[ $key ] = $confirmations;
return self::$_confirmations[ $key ];
}
/**
* Delete a form confirmation by ID.
*
* @since 2.5
*
* @param string $confirmation_id The confirmation to be deleted.
* @param int|array $form_id The form ID or Form Object form the confirmation being deleted.
*
* @return false|int The result of the database operation.
*/
public static function delete_form_confirmation( $confirmation_id, $form_id ) {
// If no Form ID or object was provided, exit.
if ( ! $form_id ) {
return false;
}
// Get Form object if only ID was provided.
$form = ! is_array( $form_id ) ? self::get_form_meta( $form_id ) : $form_id;
/**
* Fires right before a confirmation is deleted.
*
* @since 1.9
*
* @param int $form ['confirmations'][$confirmation_id] The ID of the confirmation being deleted.
* @param array $form The Form object.
*/
do_action( 'gform_pre_confirmation_deleted', $form['confirmations'][ $confirmation_id ], $form );
unset( $form['confirmations'][ $confirmation_id ] );
// Clear form cache so next retrieval of form meta will reflect deleted confirmation.
self::flush_current_form( self::get_form_cache_key( $form_id ) );
return self::save_form_confirmations( $form['id'], $form['confirmations'] );
}
/**
* Remove corrupt confirmations created by old versions of GFFormsModel::convert_confirmation().
*
* @since 2.4.15
*
* @param mixed $confirmations The confirmations to be processed.
*
* @return array
*/
public static function remove_corrupt_confirmations( $confirmations ) {
if ( ! is_array( $confirmations ) ) {
return array();
}
foreach ( $confirmations as $id => $confirmation ) {
if ( is_array( $confirmation ) && ! empty( $confirmation['type'] ) ) {
continue;
}
unset( $confirmations[ $id ] );
}
return $confirmations;
}
public static function save_form_confirmations( $form_id, $confirmations ) {
return self::update_form_meta( $form_id, $confirmations, 'confirmations' );
}
public static function save_form_notifications( $form_id, $notifications ) {
return self::update_form_meta( $form_id, $notifications, 'notifications' );
}
/**
* Returns the ids of the specified forms.
*
* @since unknown
* @since 2.5 Added $sort_column and $sort_dir parameters.
* @since 2.5.8 Added support for passing null for the $active and $trash args.
*
* @param bool|null $active True if active forms are returned. False to get inactive forms. Null to ignore the is_active property. Defaults to true.
* @param bool|null $trash True if trashed forms are returned. False to exclude trash. Null to ignore the is_trash property. Defaults to false.
* @param string $sort_column The column to sort the results on.
* @param string $sort_dir The sort direction, ASC or DESC.
*
* @return array of form IDs.
*/
public static function get_form_ids( $active = true, $trash = false, $sort_column = 'id', $sort_dir = 'ASC' ) {
global $wpdb;
$sql = 'SELECT id FROM ' . self::get_form_table_name();
$where = array();
if ( null !== $active ) {
$where[] = $wpdb->prepare( 'is_active=%d', $active );
}
if ( null !== $trash ) {
$where[] = $wpdb->prepare( 'is_trash=%d', $trash );
}
if ( ! empty( $where ) ) {
$sql .= ' WHERE ' . join( ' AND ', $where );
}
if ( ! in_array( strtolower( $sort_column ), GFFormsModel::get_form_db_columns() ) ) {
$sort_column = 'id';
}
if ( ! empty( $sort_column ) ) {
$sql .= " ORDER BY $sort_column " . ( $sort_dir == 'ASC' ? 'ASC' : 'DESC' );
}
return $wpdb->get_col( $sql );
}
public static function get_entry_meta( $form_ids ) {
global $_entry_meta;
if ( $form_ids == 0 ) {
$form_ids = self::get_form_ids();
}
if ( ! is_array( $form_ids ) ) {
$form_ids = array( $form_ids );
}
$meta = array();
foreach ( $form_ids as $form_id ) {
if ( ! isset( $_entry_meta[ $form_id ] ) ) {
$_entry_meta = array();
$_entry_meta[ $form_id ] = apply_filters( 'gform_entry_meta', array(), $form_id );
}
$meta = array_merge( $meta, $_entry_meta[ $form_id ] );
}
return $meta;
}
public static function set_entry_meta( $lead, $form ) {
GFCommon::timer_start( __METHOD__ );
$entry_id = absint( rgar( $lead, 'id' ) );
$entry_meta = self::get_entry_meta( $form['id'] );
$keys = array_keys( $entry_meta );
foreach ( $keys as $key ) {
if ( isset( $entry_meta[ $key ]['update_entry_meta_callback'] ) ) {
$callback = $entry_meta[ $key ]['update_entry_meta_callback'];
$value = call_user_func_array( $callback, array( $key, $lead, $form ) );
gform_update_meta( $entry_id, $key, $value );
$lead[ $key ] = $value;
}
}
GFCommon::log_debug( __METHOD__ . sprintf( '(): Saving meta for entry (#%d) completed in %F seconds.', $entry_id, GFCommon::timer_end( __METHOD__ ) ) );
return $lead;
}
/**
*
* @param $form_id
* @param array $search_criteria
* @param null $sorting
* @param null $paging
*
* @return array
*/
public static function search_leads( $form_id, $search_criteria = array(), $sorting = null, $paging = null ) {
return GFAPI::get_entries( $form_id, $search_criteria, $sorting, $paging );
}
public static function search_lead_ids( $form_id, $search_criteria = array() ) {
return GFAPI::get_entry_ids( $form_id, $search_criteria );
}
/**
* Returns the gf_entry table field names.
*
* @since 2.3.2.13 Added date_updated.
* @since unknown
*
* @return array
*/
public static function get_lead_db_columns() {
return array(
'id',
'form_id',
'post_id',
'date_created',
'date_updated',
'is_starred',
'is_read',
'ip',
'source_url',
'user_agent',
'currency',
'payment_status',
'payment_date',
'payment_amount',
'payment_method',
'transaction_id',
'is_fulfilled',
'created_by',
'transaction_type',
'status',
);
}
/**
*
* @param $form_id
* @param array $search_criteria
*
* @return null|string
*/
public static function count_search_leads( $form_id, $search_criteria = array() ) {
return GFAPI::count_entries( $form_id, $search_criteria );
}
/**
* Returns the lead (entry) count for all forms.
*
* @param string $status
*
* @return null|string
*/
public static function get_lead_count_all_forms( $status = 'active' ) {
return self::get_entry_count_all_forms( $status );
}
/**
* Returns the entry count for all forms.
*
* @param string $status
*
* @return null|string
*/
public static function get_entry_count_all_forms( $status = 'active' ) {
global $wpdb;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::get_lead_count_all_forms( $status );
}
$entry_table_name = self::get_entry_table_name();
if ( ! GFCommon::table_exists( $entry_table_name ) ) {
return 0;
}
$sql = $wpdb->prepare( "SELECT count(id)
FROM $entry_table_name
WHERE status=%s", $status );
return $wpdb->get_var( $sql );
}
public static function get_entry_meta_counts() {
global $wpdb;
if ( version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::get_entry_meta_counts();
}
$meta_table_name = self::get_entry_meta_table_name();
$notes_table_name = self::get_entry_notes_table_name();
if ( ! GFCommon::table_exists( $meta_table_name ) ) {
return array(
'details' => 0,
'meta' => 0,
'notes' => 0,
);
}
$results = $wpdb->get_results(
"
SELECT
(SELECT count(0) FROM $meta_table_name) as meta,
(SELECT count(0) FROM $notes_table_name) as notes
"
);
return array(
'details' => intval( $results[0]->meta ),
'meta' => intval( $results[0]->meta ),
'notes' => intval( $results[0]->notes ),
);
}
/**
* @deprecated 2.2 Use gf_upgrade()->dbDelta() instead
*/
public static function dbDelta( $sql ) {
_deprecated_function( 'dbDelta', '2.2', 'gf_upgrade()->dbDelta()' );
gf_upgrade()->dbDelta( $sql );
}
public static function get_db_charset() {
global $wpdb;
if ( ! empty( $wpdb->charset ) ) {
$charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
}
if ( ! empty( $wpdb->collate ) ) {
$charset_collate .= " COLLATE $wpdb->collate";
}
return $charset_collate;
}
public static function is_valid_table( $table_name ){
global $wpdb;
$tables = array(
GFFormsModel::get_form_table_name(),
GFFormsModel::get_form_view_table_name(),
GFFormsModel::get_meta_table_name(),
GFFormsModel::get_lead_table_name(),
GFFormsModel::get_lead_notes_table_name(),
GFFormsModel::get_lead_details_table_name(),
GFFormsModel::get_lead_details_long_table_name(),
GFFormsModel::get_lead_meta_table_name(),
GFFormsModel::get_incomplete_submissions_table_name(),
self::get_addon_feed_table_name(),
"{$wpdb->prefix}gf_addon_payment_transaction",
"{$wpdb->prefix}gf_addon_payment_callback",
GFFormsModel::get_entry_table_name(),
GFFormsModel::get_entry_notes_table_name(),
GFFormsModel::get_entry_meta_table_name(),
GFFormsModel::get_draft_submissions_table_name(),
);
return in_array( $table_name, $tables );
}
public static function is_valid_index( $index_name ){
$indexes = array(
'id',
'form_id',
'status',
'lead_id',
'lead_user_key',
'lead_field_number',
'lead_detail_id',
'lead_detail_key',
'meta_key',
'form_id_meta_key',
'uuid',
'transaction_type',
'type_lead',
'slug_callback_id',
'addon_slug_callback_id',
'addon_form',
);
return in_array( $index_name, $indexes );
}
/**
* Trims values inside choice texts, choice values, input labels, field labels and field conditionalLogic
*
* @param array $form Form object.
* @param bool $form_updated Output parameter.
*
* @return array $form
*/
public static function trim_form_meta_values( $form, &$form_updated = false ) {
$form_id = $form['id'];
GFCommon::log_debug( 'GFFormsModel::trim_form_meta_values(): Starting.' );
if ( isset( $form['fields'] ) && is_array( $form['fields'] ) ) {
foreach ( $form['fields'] as &$field ) {
$trim_value = apply_filters( 'gform_trim_input_value', true, $form_id, $field );
if ( ! $trim_value ) {
continue;
}
if ( isset( $field->label ) && $field->label != trim( $field->label ) ) {
$field->label = trim( $field->label );
$form_updated = true;
}
if ( is_array( $field->choices ) ) {
foreach ( $field->choices as &$choice ) {
if ( isset( $choice['text'] ) && $choice['text'] != trim( $choice['text'] ) ) {
$choice['text'] = trim( $choice['text'] );
$form_updated = true;
}
if ( isset( $choice['value'] ) && $choice['value'] != trim( $choice['value'] ) ) {
$choice['value'] = trim( $choice['value'] );
$form_updated = true;
}
}
}
if ( is_array( $field->inputs ) ) {
foreach ( $field->inputs as &$input ) {
if ( isset( $input['label'] ) && $input['label'] != trim( $input['label'] ) ) {
$input['label'] = trim( $input['label'] );
$form_updated = true;
}
}
}
}
$form['fields'] = GFFormsModel::trim_conditional_logic_values( $form['fields'], $form, $form_updated );
}
if ( $form_updated ) {
GFCommon::log_debug( 'GFFormsModel::trim_form_meta_values(): Form values trimmed.' );
}
return $form;
}
/**
* Trims values from an array of elements e.g. notifications and confirmations
*
* @param array $meta_array Form object.
* @param array $form Form object.
* @param bool $updated Output parameter.
*
* @return array $meta_array
*/
public static function trim_conditional_logic_values( $meta_array, $form, &$updated = false ) {
GFCommon::log_debug( 'GFFormsModel::trim_conditional_logic_values(): Starting.' );
if ( is_array( $meta_array ) ) {
foreach ( $meta_array as &$meta ) {
$meta = self::trim_conditional_logic_values_from_element( $meta, $form, $updated );
}
}
if ( $updated ) {
GFCommon::log_debug( 'GFFormsModel::trim_conditional_logic_values(): Conditional logic values trimmed.' );
}
return $meta_array;
}
/**
* Trims values from elements e.g. fields, notifications and confirmations
*
* @param array $element Form object.
* @param array $form Form object.
* @param bool $updated Output parameter.
*
* @return array $element
*/
public static function trim_conditional_logic_values_from_element( $element, $form = array(), &$updated = false ) {
if ( $element instanceof GF_Field ) {
/* @var GF_Field $element */
if ( is_array( $element->conditionalLogic ) && isset( $element->conditionalLogic['rules'] ) && is_array( $element->conditionalLogic['rules'] ) ) {
foreach ( $element->conditionalLogic['rules'] as &$rule ) {
$value = (string) $rule['value'];
if ( $value !== trim( $value ) ) {
$field = isset( $form['fields'] ) ? GFFormsModel::get_field( $form, $rule['fieldId'] ) : array();
$trim_value = apply_filters( 'gform_trim_input_value', true, rgar( $form, 'id' ), $field );
if ( $trim_value ) {
$rule['value'] = trim( $rule['value'] );
$updated = true;
}
}
}
}
} else {
if ( isset( $element['conditionalLogic'] ) && is_array( $element['conditionalLogic'] ) && isset( $element['conditionalLogic']['rules'] ) && is_array( $element['conditionalLogic']['rules'] ) ) {
foreach ( $element['conditionalLogic']['rules'] as &$rule ) {
$value = (string) rgar( $rule, 'value' );
if ( $value !== trim( $value ) ) {
$field = isset( $form['fields'] ) ? GFFormsModel::get_field( $form, $rule['fieldId'] ) : array();
$trim_value = apply_filters( 'gform_trim_input_value', true, rgar( $form, 'id' ), $field );
if ( $trim_value ) {
$rule['value'] = trim( $rule['value'] );
$updated = true;
}
}
}
}
}
return $element;
}
/**
* Returns an array of field IDs that have been encrypted using GFCommon::encrypt()
*
* @deprecated
*
* @since unknown
*
* @param $entry_id
*
* @return array|bool|mixed
*/
public static function get_encrypted_fields( $entry_id ) {
_deprecated_function( 'GFCommon:get_encrypted_fields', '2.3', 'GFCommon:get_openssl_encrypted_fields' );
$encrypted_fields = gform_get_meta( $entry_id, '_encrypted_fields' );
if ( empty( $encrypted_fields ) ) {
$encrypted_fields = array();
}
return $encrypted_fields;
}
/**
* Stores the field IDs that have been encrypted using GFCommon::encrypt()
*
* @deprecated
*
* @since unknown
*
* @param $entry_id
* @param $field_ids
*
* @return bool
*/
public static function set_encrypted_fields( $entry_id, $field_ids ) {
_deprecated_function( 'GFCommon:set_encrypted_fields', '2.3', 'GFCommon:set_openssl_encrypted_fields' );
if ( ! is_array( $field_ids ) ) {
$field_ids = array( $field_ids );
}
$encrypted_fields = array_merge( self::get_encrypted_fields( $entry_id ), $field_ids );
gform_update_meta( $entry_id, '_encrypted_fields', $encrypted_fields );
return true;
}
/**
* Checks whether the given field was encrypted using GFCommon::encrpyt() and registered using GFCommon::set_encrypted_fields()
*
* @deprecated
*
* @since unknown
*
* @param $entry_id
* @param $field_id
*
* @return bool|mixed|void
*/
public static function is_encrypted_field( $entry_id, $field_id ) {
_deprecated_function( 'GFCommon:is_encrypted_field', '2.3', 'GFCommon:is_openssl_encrypted_field' );
/**
* Determines if an entry field is stored encrypted. Use this hook to change the default behavior of decrypting fields that have been encrypted or to completely disable the
* process if checking for encrypted fields.
*
* @param int $entry_id The current Entry ID
* @param int $field_id The current Field ID.
*/
$is_encrypted = apply_filters('gform_is_encrypted_field', '', $entry_id, $field_id );
if ( $is_encrypted !== '' ){
return $is_encrypted;
}
$encrypted_fields = self::get_encrypted_fields( $entry_id );
return in_array( $field_id, $encrypted_fields );
}
/**
* Returns an array of field IDs that have been encrypted using GFCommon::openssl_encrypt()
*
* @since 2.3
*
* @param $entry_id
*
* @return array|bool|mixed
*/
public static function get_openssl_encrypted_fields( $entry_id ) {
$encrypted_fields = gform_get_meta( $entry_id, '_openssl_encrypted_fields' );
if ( empty( $encrypted_fields ) ) {
$encrypted_fields = array();
}
return $encrypted_fields;
}
/**
* Returns the encrypted field IDs for each entry_id specified in the $entry_ids array.
*
* @since 2.6
*
* @param array $entry_ids An array of entry IDs.
*
* @return array Returns an array with entry_id as the key and an array of field IDs (that have been encrypted using GFCommon::openssl_encrypt()) as the value.
*/
public static function get_openssl_encrypted_fields_by_entries( $entry_ids ) {
global $wpdb;
$entry_ids = array_unique( $entry_ids );
$placeholders = implode( ',', array_fill( 0, count( $entry_ids ), '%d' ) );
$entry_meta_table = self::get_entry_meta_table_name();
$sql = sprintf( "SELECT entry_id, meta_value from $entry_meta_table WHERE meta_key = '_openssl_encrypted_fields' AND entry_id IN(%s)", $placeholders );
$results = $wpdb->get_results( $wpdb->prepare( $sql, $entry_ids ), ARRAY_A );
$encrypted_fields = array();
foreach ( $results as $row ) {
$encrypted_fields[ $row['entry_id'] ] = maybe_unserialize( $row['meta_value'] );
}
return $encrypted_fields;
}
/**
* Adds the field IDs that have been encrypted using GFCommon::encrypt(). Merges the new IDs with the existing IDs.
*
* @since 2.3
*
* @param $entry_id
* @param $field_ids
*
* @return bool
*/
public static function set_openssl_encrypted_fields( $entry_id, $field_ids ) {
if ( ! is_array( $field_ids ) ) {
$field_ids = array( $field_ids );
}
$encrypted_fields = array_merge( self::get_openssl_encrypted_fields( $entry_id ), $field_ids );
gform_update_meta( $entry_id, '_openssl_encrypted_fields', $encrypted_fields );
return true;
}
/**
* Checks whether the given field was encrypted using GFCommon::encrpyt() and registered using GFCommon::set_encrypted_fields()
*
* @since 2.3
*
* @param $entry_id
* @param $field_id
*
* @return bool|mixed|void
*/
public static function is_openssl_encrypted_field( $entry_id, $field_id ) {
/**
* Determines if an entry field is stored encrypted. Use this hook to change the default behavior of decrypting fields that have been encrypted or to completely disable the
* process if checking for encrypted fields.
*
* @param int $entry_id The current Entry ID
* @param int $field_id The current Field ID.
*/
$is_encrypted = apply_filters('gform_is_encrypted_field', '', $entry_id, $field_id );
if ( $is_encrypted !== '' ){
return $is_encrypted;
}
$encrypted_fields = self::get_openssl_encrypted_fields( $entry_id );
return in_array( $field_id, $encrypted_fields );
}
/**
* @deprecated 2.4.16
*
* @param $entry
* @param $form
*
* @return mixed
*/
public static function delete_password( $entry, $form ) {
$password_fields = self::get_fields_by_type( $form, array( 'password' ) );
if ( is_array( $password_fields ) ) {
foreach ( $password_fields as $password_field ) {
$entry[ $password_field->id ] = '';
}
}
GFAPI::update_entry( $entry );
return $entry;
}
public static function maybe_sanitize_form_settings( $form ) {
if ( isset( $form['version'] ) && version_compare( $form['version'], '1.9.6.10', '>=' ) ) {
$form = self::sanitize_settings( $form );
}
return $form;
}
public static function sanitize_settings( $form ) {
$form['version'] = GFForms::$version;
if ( apply_filters( 'gform_disable_form_settings_sanitization', false ) ) {
return $form;
}
// -- standard form settings --
$form['title'] = sanitize_text_field( rgar( $form, 'title' ) );
if ( isset( $form['description'] ) ) {
$form['description'] = self::maybe_wp_kses( $form['description'] );
}
if ( isset( $form['labelPlacement'] ) ) {
$form['labelPlacement'] = GFCommon::whitelist( $form['labelPlacement'], array( 'top_label', 'left_label', 'right_label' ) );
}
if ( isset( $form['descriptionPlacement'] ) ) {
$form['descriptionPlacement'] = GFCommon::whitelist( $form['descriptionPlacement'], array( 'below', 'above' ) );
}
if ( isset( $form['subLabelPlacement'] ) ) {
$form['subLabelPlacement'] = GFCommon::whitelist( $form['subLabelPlacement'], array( 'below', 'above' ) );
}
// -- advanced form settings --
if ( isset( $form['cssClass'] ) ) {
$form['cssClass'] = sanitize_text_field( $form['cssClass'] );
}
if ( isset( $form['enableHoneypot'] ) ) {
$form['enableHoneypot'] = (bool) $form['enableHoneypot'];
}
if ( isset( $form['honeypotAction'] ) ) {
$form['honeypotAction'] = GFCommon::whitelist( $form['honeypotAction'], array( 'abort', 'spam' ) );
}
if ( isset( $form['enableAnimation'] ) ) {
$form['enableAnimation'] = (bool) $form['enableAnimation'];
}
// form button settings
if ( isset( $form['button'] ) ) {
$form['button']['type'] = GFCommon::whitelist( $form['button']['type'], array( 'text', 'image' ) );
$form['button']['text'] = $form['button']['type'] == 'text' ? sanitize_text_field( $form['button']['text'] ) : '';
$form['button']['imageUrl'] = $form['button']['type'] == 'image' ? sanitize_text_field( $form['button']['imageUrl'] ) : '';
}
if ( isset( $form['button']['conditionalLogic'] ) ) {
$form['button']['conditionalLogic'] = self::sanitize_conditional_logic( $form['button']['conditionalLogic'] );
}
// Save and Continue settings
if ( isset( $form['save'] ) ) {
$form['save']['enabled'] = (bool) $form['save']['enabled'] ;
$form['save']['button']['type'] = 'link';
$form['save']['button']['text'] = sanitize_text_field( $form['save']['button']['text'] );
}
// limit entries settings
if ( isset( $form['limitEntries'] ) ) {
$form['limitEntries'] = (bool) $form['limitEntries'];
$form['limitEntriesCount'] = $form['limitEntries'] ? absint( $form['limitEntriesCount'] ) : '';
$form['limitEntriesPeriod'] = $form['limitEntries'] ? GFCommon::whitelist( $form['limitEntriesPeriod'], array( '', 'day', 'week', 'month', 'year' ) ) : '';
$form['limitEntriesMessage'] = $form['limitEntries'] ? self::maybe_wp_kses( $form['limitEntriesMessage'] ) : '';
}
// form scheduling settings
if ( isset( $form['scheduleForm'] ) ) {
$form['scheduleForm'] = (bool) $form['scheduleForm'];
$form['scheduleStart'] = $form['scheduleForm'] ? wp_strip_all_tags( $form['scheduleStart'] ) : '';
$form['scheduleStartHour'] = $form['scheduleForm'] ? GFCommon::int_range( $form['scheduleStartHour'], 1, 12 ) : '';
$form['scheduleStartMinute'] = $form['scheduleForm'] ? GFCommon::int_range( $form['scheduleStartMinute'], 1, 60 ) : '';
$form['scheduleStartAmpm'] = $form['scheduleForm'] ? GFCommon::whitelist( $form['scheduleStartAmpm'], array( 'am', 'pm' ) ) : '';
$form['scheduleEnd'] = $form['scheduleForm'] ? wp_strip_all_tags( $form['scheduleEnd'] ) : '';
$form['scheduleEndHour'] = $form['scheduleForm'] ? GFCommon::int_range( $form['scheduleEndHour'], 1, 12 ) : '';
$form['scheduleEndMinute'] = $form['scheduleForm'] ? GFCommon::int_range( $form['scheduleEndMinute'], 1, 60 ) : '';
$form['scheduleEndAmpm'] = $form['scheduleForm'] ? GFCommon::whitelist( $form['scheduleEndAmpm'], array( 'am', 'pm' ) ) : '';
$form['schedulePendingMessage'] = $form['scheduleForm'] ? self::maybe_wp_kses( $form['schedulePendingMessage'] ) : '';
$form['scheduleMessage'] = $form['scheduleForm'] ? self::maybe_wp_kses( $form['scheduleMessage'] ) : '';
}
// require login settings
if ( isset( $form['requireLogin'] ) ) {
$form['requireLogin'] = (bool) $form['requireLogin'];
$form['requireLoginMessage'] = $form['requireLogin'] ? self::maybe_wp_kses( $form['requireLoginMessage'] ) : '';
}
if ( isset( $form['fields'] ) ) {
foreach ( $form['fields'] as $field ) {
/* @var GF_Field $field */
$field->sanitize_settings();
}
}
return $form;
}
public static function sanitize_conditional_logic( $logic ) {
if ( ! is_array( $logic ) ) {
return $logic;
}
if ( apply_filters( 'gform_disable_form_settings_sanitization', false ) ) {
return $logic;
}
if ( isset( $logic['actionType'] ) && ! in_array( $logic['actionType'], array( 'show', 'hide' ) ) ) {
$logic['actionType'] = 'show';
}
if ( isset( $logic['logicType'] ) && ! in_array( $logic['logicType'], array( 'all', 'any' ) ) ) {
$logic['logicType'] = 'all';
}
if ( isset( $logic['rules'] ) && is_array( $logic['rules'] ) ) {
foreach ( $logic['rules'] as &$rule ) {
if ( isset( $rule['fieldId'] ) ) {
// Field ID could be meta key.
$rule['fieldId'] = wp_strip_all_tags( $rule['fieldId'] );
}
if ( isset( $rule['operator'] ) ) {
$is_valid_operator = self::is_valid_operator( $rule['operator'] );
$rule['operator'] = $is_valid_operator ? $rule['operator'] : 'is';
}
if ( isset( $rule['value'] ) ) {
// Strip scripts but don't encode.
$allowed_protocols = wp_allowed_protocols();
$rule['value'] = wp_kses_no_null( $rule['value'], array( 'slash_zero' => 'keep' ) );
$rule['value'] = wp_kses_hook( $rule['value'], 'post', $allowed_protocols );
$rule['value'] = wp_kses_split( $rule['value'], 'post', $allowed_protocols );
}
}
}
return $logic;
}
private static function maybe_wp_kses( $html, $allowed_html = 'post', $allowed_protocols = array() ) {
return GFCommon::maybe_wp_kses( $html, $allowed_html, $allowed_protocols );
}
/**
* Returns an array containing the form fields of the specified type or types.
*
* @since 1.9.9.10
* @param array $form
* @param array|string $types
* @param bool $use_input_type
*
* @return GF_Field[]
*/
public static function get_fields_by_type( $form, $types, $use_input_type = false ) {
$fields = array();
if ( ! is_array( rgar( $form, 'fields' ) ) ) {
return $fields;
}
if ( ! is_array( $types ) ) {
$types = array( $types );
}
foreach ( $form['fields'] as $field ) {
/* @var GF_Field $field */
$type = $use_input_type ? $field->get_input_type() : $field->type;
if ( in_array( $type, $types ) ) {
$fields[] = $field;
}
}
return $fields;
}
/**
* Checks whether the conditional logic operator passed in is valid.
*
* @since 2.0.7.20 Refactored and added filter gform_is_valid_conditional_logic_operator.
* @access public
*
* @param string $operator Conditional logic operator.
*
* @return bool true if a valid operator, false if not.
*/
public static function is_valid_operator( $operator ) {
$operators = array( 'is', 'isnot', '<>', 'not in', 'in', '>', '<', 'contains', 'starts_with', 'ends_with', 'like', '>=', '<=' );
$is_valid = in_array( strtolower( $operator ), $operators );
/**
* Filter which checks whether the operator is valid.
*
* Allows custom operators to be validated.
*
* @since 2.0.7.20
*
* @param bool $is_valid Whether the operator is valid or not.
* @param string $operator The conditional logic operator.
*/
return apply_filters( 'gform_is_valid_conditional_logic_operator', $is_valid, $operator );
}
/**
* Update the recent forms list for the current user when a form is edited or trashed.
*
* @since 2.0.7.14
*
* @param int $form_id The ID of the current form.
* @param bool $trashed Indicates if the form was trashed. Default is false, form was opened for editing.
*/
public static function update_recent_forms( $form_id, $trashed = false ) {
if ( ! get_option( 'gform_enable_toolbar_menu' ) ) {
return;
}
$current_user_id = get_current_user_id();
$recent_form_ids = self::get_recent_forms( $current_user_id );
$i = array_search( $form_id, $recent_form_ids );
if ( $i !== false ) {
unset( $recent_form_ids[ $i ] );
$recent_form_ids = array_values( $recent_form_ids );
}
if ( ! $trashed ) {
// Add the current form to the top of the list.
array_unshift( $recent_form_ids, $form_id );
$recent_form_ids = array_slice( $recent_form_ids, 0, 10 );
}
update_user_meta( $current_user_id, 'gform_recent_forms', $recent_form_ids );
}
/**
* Get the recent forms list for the current user.
*
* @since 2.2.1.14
*
* @param int $current_user_id The ID of the currently logged in user.
*
* @return array
*/
public static function get_recent_forms( $current_user_id = 0 ) {
if ( ! $current_user_id ) {
$current_user_id = get_current_user_id();
}
$recent_form_ids = get_user_meta( $current_user_id, 'gform_recent_forms', true );
if ( empty( $recent_form_ids ) || ! is_array( $recent_form_ids ) ) {
$all_form_ids = self::get_form_ids();
$all_form_ids = array_reverse( $all_form_ids );
$recent_form_ids = array_slice( $all_form_ids, 0, 10 );
if ( $recent_form_ids ) {
update_user_meta( $current_user_id, 'gform_recent_forms', $recent_form_ids );
}
}
return $recent_form_ids;
}
/**
* Evaluates the conditional logic based on the specified $logic variable. This method is used when evaluating field conditional logic.
* NOTE: There is a future refactoring opportunity to reduce code duplication by merging this method with GFCommon::evaluate_conditional_logic(), which currently handles non-field conditional logic (i.e. notification, confirmation and feed). A possible solution is for this method to call GFCommon::evaluate_conditional_logic().
*
* @param array $form The current Form object.
* @param array $logic The conditional logic configuration array with all the specified rules.
* @param array $field_values Default field values for this form. Used when form has not yet been submitted. Pass an array if no default field values are available/required.
* @param array $entry Optional, default is null. If entry object is available, pass the entry.
*
* @return bool Returns true if the conditional logic passes (i.e. field/button is supposed to be displayed), false otherwise.
*/
private static function evaluate_conditional_logic( $form, $logic, $field_values, $entry = null ) {
if ( empty( $logic ) ){
return true;
}
$match_count = 0;
foreach ( $logic['rules'] as $rule ) {
try {
/**
* Filter the conditional logic rule before it is evaluated.
*
* @since 2.4.22
*
* @param array $rule The conditional logic rule about to be evaluated.
* @param array $form The current form meta.
* @param array $logic All details required to evaluate an objects conditional logic.
* @param array $field_values The default field values for this form.
* @param array $entry The current entry object (if available).
*/
$rule = apply_filters( 'gform_rule_pre_evaluation', $rule, $form, $logic, $field_values, $entry );
} catch ( Error $e ) {
GFCommon::log_error( __METHOD__ . '(): Error from function hooked to gform_rule_pre_evaluation. ' . $e->getMessage() );
}
$source_field = RGFormsModel::get_field( $form, $rule['fieldId'] );
$source_value = empty( $entry ) ? self::get_field_value( $source_field, $field_values ) : self::get_lead_field_value( $entry, $source_field );
/**
* Filter the source value of a conditional logic rule before it is compared with the target value.
*
* @since 2.6.2
*
* @param int|string $source_value The value of the rule's configured field ID, entry meta, or custom property.
* @param array $rule The conditional logic rule that is being evaluated.
* @param array $form The current form meta.
* @param array $logic All details required to evaluate an objects conditional logic.
* @param array $entry The current entry object (if available).
*/
$source_value = apply_filters( 'gform_rule_source_value', $source_value, $rule, $form, $logic, $entry );
$is_value_match = self::is_value_match( $source_value, $rule['value'], $rule['operator'], $source_field, $rule, $form );
if ( $is_value_match ) {
$match_count ++;
}
}
$do_action = ( $logic['logicType'] == 'all' && $match_count == sizeof( $logic['rules'] ) ) || ( $logic['logicType'] == 'any' && $match_count > 0 );
$is_hidden = ( $do_action && $logic['actionType'] == 'hide' ) || ( ! $do_action && $logic['actionType'] == 'show' );
return ! $is_hidden;
}
/**
* Returns all the draft submissions.
*
* @since 2.4
*
* @return array|null|object The query result.
*/
public static function get_draft_submissions() {
global $wpdb;
self::purge_expired_draft_submissions();
$table = version_compare( self::get_database_version(), '2.3-dev-1', '<' ) ? self::get_incomplete_submissions_table_name() : self::get_draft_submissions_table_name();
$sql = "SELECT uuid, date_created, form_id, ip, submission, source_url FROM {$table}";
$rows = $wpdb->get_results( $sql, ARRAY_A );
foreach ( $rows as $row ) {
$form = self::get_form_meta( $row['form_id'] );
$row['submission'] = self::filter_draft_submission_post_get( $row['submission'], $row['uuid'], $form );
}
return $rows;
}
/**
* Sanitizes the names of the files that have been uploaded to the tmp directory and sent in
* $_POST['gform_uploaded_files'] and caches them in GFFormsModel::$uploaded_files.
*
* @since 2.4.3.5
*
* @param $form_id
*
* @return array
*/
public static function set_uploaded_files( $form_id ) {
$files = GFCommon::json_decode( stripslashes( GFForms::post( 'gform_uploaded_files' ) ) );
if ( ! is_array( $files ) ) {
$files = array();
}
foreach ( $files as &$upload_field ) {
if ( is_array( $upload_field ) ) {
if ( isset( $upload_field[0] ) && is_array( $upload_field[0] ) ) {
foreach ( $upload_field as &$upload ) {
if ( isset( $upload['temp_filename'] ) ) {
$upload['temp_filename'] = sanitize_file_name( wp_basename( $upload['temp_filename'] ) );
}
if ( isset( $upload['uploaded_filename'] ) ) {
$upload['uploaded_filename'] = sanitize_file_name( wp_basename( $upload['uploaded_filename'] ) );
}
}
}
} else {
$upload_field = wp_basename( $upload_field );
}
}
self::$uploaded_files[ $form_id ] = $files;
return $files;
}
/**
* Checks if an entry exists for the supplied ID.
*
* @since 2.4.6
* @since 2.4.24 Updated to use GFFormsModel::id_exists_in_table().
*
* @param int $entry_id The ID to be checked.
*
* @return bool
*/
public static function entry_exists( $entry_id ) {
return self::id_exists_in_table( $entry_id, GFFormsModel::get_entry_table_name() );
}
/**
* Checks if an id exists in the specified table.
*
* @since 2.4.24
*
* @param int $id The ID to be checked.
* @param string $table The table name, including the site's database prefix.
*
* @return bool
*/
public static function id_exists_in_table( $id, $table ) {
$id = intval( $id );
if ( $id <= 0 ) {
return false;
}
global $wpdb;
$sql = $wpdb->prepare( "SELECT count(id) FROM {$table} WHERE id = %d", $id );
$result = intval( $wpdb->get_var( $sql ) );
return $result > 0;
}
/**
* Returns an array of column names used by the gf_addon_feed table.
*
* @since 2.4.24
*
* @return string[]
*/
public static function get_addon_feed_db_columns() {
return array( 'id', 'form_id', 'is_active', 'feed_order', 'meta', 'addon_slug', 'event_type' );
}
/**
* Updates the specified feed with the given property value.
*
* @since 2.4.24
*
* @param int $feed_id The ID of the feed being updated.
* @param string $property_name The name of the property (column) being updated.
* @param mixed $property_value The new value of the specified property.
*
* @return bool|WP_Error
*/
public static function update_feed_property( $feed_id, $property_name, $property_value ) {
if ( $property_name === 'id' ) {
return new WP_Error( 'invalid_property', __( 'Updating the id property is not supported', 'gravityforms' ) );
}
if ( ! in_array( $property_name, self::get_addon_feed_db_columns() ) ) {
return new WP_Error( 'invalid_property', sprintf( __( '%s is not a valid feed property', 'gravityforms' ), $property_name ) );
}
if ( gf_upgrade()->get_submissions_block() ) {
return new WP_Error( 'submissions_blocked', __( 'Submissions are currently blocked due to an upgrade in progress', 'gravityforms' ) );
}
if ( $property_name === 'meta' ) {
if ( is_array( $property_value ) ) {
$property_value = json_encode( $property_value );
}
if ( empty( $property_value ) || ! is_string( $property_value ) || $property_value[0] !== '{' ) {
return new WP_Error( 'invalid_meta', __( 'Feed meta should be an associative array or JSON', 'gravityforms' ) );
}
}
global $wpdb;
$table = self::get_addon_feed_table_name();
$result = $wpdb->update( $table, array( $property_name => $property_value ), array( 'id' => $feed_id ) );
if ( $result === false ) {
return new WP_Error( 'error_updating', sprintf( __( 'There was an error while updating feed id %s', 'gravityforms' ), $feed_id ) );
}
if ( $result === 0 && ! self::id_exists_in_table( $feed_id, $table ) ) {
return new WP_Error( 'not_found', sprintf( __( 'Feed id %s not found', 'gravityforms' ), $feed_id ) );
}
return (bool) $result;
}
/**
* Updates the license key, If multisite, it updates the license key for all sites in the network.
*
* @since 2.7
*
* @param string $license The license key.
*
* @return void
*/
public static function update_license_key( $license ) {
if ( is_multisite() && is_main_site() ) {
$sites = get_sites();
foreach ( $sites as $site ) {
update_blog_option( $site->blog_id, 'rg_gforms_key', $license );
update_blog_option( $site->blog_id, 'gform_pending_installation', false );
}
}
update_option( 'rg_gforms_key', $license );
}
}
class RGFormsModel extends GFFormsModel {
}
/**
* In-memory cache of entry meta using "{blog_id}_{entry_id}_{meta_key}" as the key.
*
* @since 2.3 Prefixed cache key with the blog id.
* @since unknown
*
* @global array $_gform_lead_meta
*/
global $_gform_lead_meta;
$_gform_lead_meta = array();
// Functions to handle lead meta
function gform_get_meta( $entry_id, $meta_key ) {
if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::gform_get_meta( $entry_id, $meta_key );
}
global $wpdb, $_gform_lead_meta;
//get from cache if available
$cache_key = get_current_blog_id() . '_' . $entry_id . '_' . $meta_key;
if ( array_key_exists( $cache_key, $_gform_lead_meta ) ) {
return maybe_unserialize( $_gform_lead_meta[ $cache_key ] );
}
$table_name = GFFormsModel::get_entry_meta_table_name();
$results = $wpdb->get_results( $wpdb->prepare( "SELECT meta_value FROM {$table_name} WHERE entry_id=%d AND meta_key=%s", $entry_id, $meta_key ) );
$value = isset( $results[0] ) ? $results[0]->meta_value : null;
$meta_value = $value === null ? false : maybe_unserialize( $value );
$_gform_lead_meta[ $cache_key ] = $meta_value;
return $meta_value;
}
function gform_get_meta_values_for_entries( $entry_ids, $meta_keys ) {
global $wpdb;
if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
return GF_Forms_Model_Legacy::gform_get_meta_values_for_entries( $entry_ids, $meta_keys );
}
if ( empty( $meta_keys ) || empty( $entry_ids ) ) {
return array();
}
$table_name = RGFormsModel::get_entry_meta_table_name();
$meta_key_select_array = array();
foreach ( $meta_keys as $meta_key ) {
$meta_key_select_array[] = "max(case when meta_key = '$meta_key' then meta_value end) as `$meta_key`";
}
$entry_ids_str = join( ',', $entry_ids );
$meta_key_select = join( ',', $meta_key_select_array );
$sql_query = " SELECT
entry_id, $meta_key_select
FROM $table_name
WHERE entry_id IN ($entry_ids_str)
GROUP BY entry_id";
$results = $wpdb->get_results( $sql_query );
foreach ( $results as $result ) {
foreach ( $meta_keys as $meta_key ) {
$result->$meta_key = $result->$meta_key === null ? false : maybe_unserialize( $result->$meta_key );
}
}
$meta_value_array = $results;
return $meta_value_array;
}
/**
* Add or update metadata associated with an entry.
*
* Data will be serialized. Don't forget to sanitize user input.
*
* @since Unknown
* @since 2.5 Return the result of the query.
*
* @param int $entry_id The ID of the entry to be updated.
* @param string $meta_key The key for the meta data to be stored.
* @param mixed $meta_value The data to be stored for the entry.
* @param int|null $form_id The form ID of the entry (optional, saves extra query if passed when creating the metadata).
*
* @return int|false Returns the number of affected rows, or false if the operation fails.
*/
function gform_update_meta( $entry_id, $meta_key, $meta_value, $form_id = null ) {
global $wpdb, $_gform_lead_meta;
if ( gf_upgrade()->get_submissions_block() ) {
return;
}
if ( intval( $entry_id ) <= 0 ) {
return;
}
if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
GF_Forms_Model_Legacy::gform_update_meta( $entry_id, $meta_key, $meta_value, $form_id );
return;
}
$table_name = GFFormsModel::get_entry_meta_table_name();
if ( false === $meta_value ) {
$meta_value = '0';
}
$serialized_meta_value = maybe_serialize( $meta_value );
$meta_exists = gform_get_meta( $entry_id, $meta_key ) !== false;
if ( $meta_exists ) {
$result = $wpdb->update( $table_name, array( 'meta_value' => $serialized_meta_value ), array( 'entry_id' => $entry_id, 'meta_key' => $meta_key ), array( '%s' ), array( '%d', '%s' ) );
} else {
if ( empty( $form_id ) ) {
$entry_table_name = GFFormsModel::get_entry_table_name();
$form_id = $wpdb->get_var( $wpdb->prepare( "SELECT form_id from $entry_table_name WHERE id=%d", $entry_id ) );
} else {
$form_id = intval( $form_id );
}
$result = $wpdb->insert( $table_name, array( 'form_id' => $form_id, 'entry_id' => $entry_id, 'meta_key' => $meta_key, 'meta_value' => $serialized_meta_value ), array( '%d', '%d', '%s', '%s' ) );
}
if ( $result !== false ) {
//updates cache
$cache_key = get_current_blog_id() . '_' . $entry_id . '_' . $meta_key;
if ( array_key_exists( $cache_key, $_gform_lead_meta ) ) {
$_gform_lead_meta[ $cache_key ] = $meta_value;
}
}
return $result;
}
/**
* Add metadata associated with an entry.
*
* Data will be serialized; Don't forget to sanitize user input.
*
* @since Unknown
* @since 2.5 Return the result of the query.
*
* @param int $entry_id The ID of the entry where metadata is to be added.
* @param string $meta_key The key for the meta data to be stored.
* @param mixed $meta_value The data to be stored for the entry.
* @param int|null $form_id The form ID of the entry (optional, saves extra query if passed when creating the metadata).
*
* @return int|false Returns the number of affected rows, or false if the operation fails.
*/
function gform_add_meta( $entry_id, $meta_key, $meta_value, $form_id = null ) {
global $wpdb, $_gform_lead_meta;
if ( gf_upgrade()->get_submissions_block() ) {
return;
}
if ( intval( $entry_id ) <= 0 ) {
return;
}
if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
GF_Forms_Model_Legacy::gform_add_meta( $entry_id, $meta_key, $meta_value, $form_id );
return;
}
$table_name = RGFormsModel::get_entry_meta_table_name();
if ( false === $meta_value ) {
$meta_value = '0';
}
$serialized_meta_value = maybe_serialize( $meta_value );
if ( empty( $form_id ) ) {
$entry_table_name = GFFormsModel::get_entry_table_name();
$form_id = $wpdb->get_var( $wpdb->prepare( "SELECT form_id from $entry_table_name WHERE id=%d", $entry_id ) );
} else {
$form_id = intval( $form_id );
}
$result = $wpdb->insert( $table_name, array( 'form_id' => $form_id, 'entry_id' => $entry_id, 'meta_key' => $meta_key, 'meta_value' => $serialized_meta_value ), array( '%d', '%d', '%s', '%s' ) );
if ( $result !== false ) {
$cache_key = get_current_blog_id() . '_' . $entry_id . '_' . $meta_key;
$_gform_lead_meta[ $cache_key ] = $meta_value;
}
return $result;
}
/**
* Delete metadata associated with an entry.
*
* @since Unknown
* @since 2.5 Return the result of the query.
*
* @param int $entry_id The ID of the entry to be deleted.
* @param string $meta_key The key for the meta data to be deleted.
*
* @return int|bool Returns true or the number of rows affected, or false if the operation fails.
*/
function gform_delete_meta( $entry_id, $meta_key = '' ) {
global $wpdb, $_gform_lead_meta;
if ( gf_upgrade()->get_submissions_block() ) {
return;
}
if ( version_compare( GFFormsModel::get_database_version(), '2.3-dev-1', '<' ) ) {
GF_Forms_Model_Legacy::gform_delete_meta( $entry_id, $meta_key );
return;
}
$table_name = RGFormsModel::get_entry_meta_table_name();
$meta_filter = empty( $meta_key ) ? '' : $wpdb->prepare( 'AND meta_key=%s', $meta_key );
$result = $wpdb->query( $wpdb->prepare( "DELETE FROM {$table_name} WHERE entry_id=%d {$meta_filter}", $entry_id ) );
if ( $result !== false ) {
//clears cache.
$_gform_lead_meta = array();
}
return $result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment