Skip to content

Instantly share code, notes, and snippets.

@RadGH
Last active February 17, 2025 02:53
Show Gist options
  • Select an option

  • Save RadGH/2e419f7b67974f969e55fa3556f652fc to your computer and use it in GitHub Desktop.

Select an option

Save RadGH/2e419f7b67974f969e55fa3556f652fc to your computer and use it in GitHub Desktop.
Edit an existing gravityforms entry on the frontend
<?php
if ( !defined( 'ABSPATH' ) ) exit;
// How to use:
// 1. Create your own class that extends this one, such as: class Amazing_Quiz extends Editable_GF_Form {...}
// 2. Set the $form_id in your class (Amazing_Quiz). *Note: This code is designed for a single form per class but could easily be adapted to support any form.
// 3. Edit an entry by visiting your form normally but with ?entry_id=123 in the URL. If your user account created the entry, you can edit it.
// Requirements:
// 1. All fields must have a unique name set for "allow to be pre-populated".
// * This is a dumb restriction by GF as without a name, there is no filter to set the value.
// * File inputs do not need a name though (they don't support one anyway).
// 3. This is only for the front-end. I don't know why GF doesn't let you edit entries on the backend.
abstract class Evaluation_Form_GF_Editable {
// Form ID that will allow editing entries
public $form_id = null;
public $initialized = false;
public $sub_inputs = array();
public function __construct() {;
// Add other hooks on every page load -- for things like saving data
add_action( 'after_setup_theme', array( $this, 'add_hooks_on_every_load' ) );
// Add certain hooks only when the form is going to be displayed (based on shortcode usage)
add_filter( 'gform_form_args', array( $this, 'add_hooks_on_display' ), 20 );
}
/**
* Add hooks so that entry can save. This can happen on submit or ajax, the shortcode will not be processed so it needs a separate hook.
*/
public function add_hooks_on_every_load() {
// Only for this form
if ( $this->form_id === null ) return;
// Allow editing previous insurance entry, if reinstatement needed and admin reinstatement tracking allows for it
add_filter( "gform_entry_id_pre_save_lead_{$this->form_id}", array($this, 'change_saved_entry_id'), 50, 2 );
}
/**
* Add certain hooks only when the form is going to be displayed (based on shortcode usage)
*
* @param $args
* @return array
*/
public function add_hooks_on_display( $args ) {
// Only for this form
if ( $this->form_id !== (int) $args['form_id'] ) return $args;
// Add saved files to edited entry when the form started to be rendered
add_filter( "gform_pre_render_{$this->form_id}", array($this, 'prepare_previously_uploaded_files'), 20, 3 );
// Fill in the value of all fields that have a name. The name must match the "autofill parameter name" in the field's settings.
$form = GFAPI::get_form( $this->form_id );
$ignored_field_types = array('html', 'section', 'page');
$compound_field_types = array('checkbox');
foreach( $form['fields'] as $field ) {
if ( !($field instanceof GF_Field) ) continue;
$name = rgobj( $field, 'inputName' );
$type = rgobj( $field, 'type' );
$sub_inputs = rgobj($field, 'inputs');
// Always ignore certain field types
if ( !$type ) continue;
if ( in_array($type, $ignored_field_types) ) continue;
// Compound inputs have just one name, but have multiple inputs with their own values.
if ( in_array($type, $compound_field_types) ) {
add_filter( "gform_field_value_{$name}", array( $this, 'fill_field_value_compound' ), 20, 3 );
continue;
}
// Standard inputs like text and selects have just one input name for the field with a single value.
// And some like the Date field have one name with multiple inputs, but still just a single value.
if ( $name ) {
add_filter( "gform_field_value_{$name}", array( $this, 'fill_field_value_text' ), 20, 3 );
continue;
}
// Advanced inputs like Name and Address have multiple sub inputs, each with their own name and values
if ( $sub_inputs ) {
foreach( $sub_inputs as $sub_input ) {
if ( !is_array($sub_input) ) continue;
// Get the name, if any
$name = rgar($sub_input, 'name' );
if ( !$name ) continue;
// Store the sub input, particularly so we can get the ID later
$this->sub_inputs[ $name ] = $sub_input;
// Use a special filter for sub inputs
add_filter( "gform_field_value_{$name}", array( $this, 'fill_sub_input_value_text' ), 20, 3 );
}
continue;
}
}
return $args;
}
/**
* Get the entry id that is being edited
*
* @return bool|int|mixed
*/
public function get_edited_entry_id() {
$evaluation_id = (int) rgar( $_GET, 'evaluation_id' );
if ( !$evaluation_id ) return false;
// Cache the entry_id for this evaluation for the session.
static $cached_entry_ids = null;
if ( $cached_entry_ids === null ) $cached_entry_ids = array();
// Return from cache if set
if ( isset($cached_entry_ids[$evaluation_id]) ) {
return $cached_entry_ids[$evaluation_id];
}
// Get entry id from evaluation
$entry_id = (int) get_field( 'entry_id', $evaluation_id );
if ( !$entry_id ) $entry_id = false;
// Cache the entry id for next occurrence
$cached_entry_ids[$evaluation_id] = $entry_id;
return $entry_id;
}
/**
* Return true if the given user is able to make edits to the entry.
*
* @param int $user_id
* @param array|int $entry
*
* @return bool
*/
public function can_user_edit_entry( $user_id, $entry ) {
if ( is_numeric($entry) ) $entry = GFAPI::get_entry( $entry );
if ( !$entry || is_wp_error( $entry ) ) return false;
// Must be an entry belonging to this renewal form
if ( $entry['form_id'] != $this->form_id ) return false;
// Check if user is owner of the entry
if ( (int)$entry['created_by'] != (int)$user_id ) return false;
// Check if status of entry is no longer "active". Only active entries can be edited
if ( $entry['status'] !== 'active' ) return false;
return true;
}
/**
* Set up "uploaded_files" using values from the previous entry. Allows you to keep your previous upload, or remove it and start over.
*
* @param $form
* @param $ajax
* @param $field_values
*
* @return mixed
*/
public function prepare_previously_uploaded_files( $form, $ajax, $field_values ) {
$user_id = get_current_user_id();
// Get the edited entry
$entry_id = $this->get_edited_entry_id();
if ( !$entry_id ) return $form;
if ( !$this->can_user_edit_entry( $user_id, $entry_id ) ) return $form;
// Check if any field is a file upload. If not, we can ignore this function
do {
foreach( $form['fields'] as $field ) {
if ( $field instanceof GF_Field_FileUpload ) {
// File upload found. Abort the do{} loop and proceed with the function
break 2;
}
}
// No file uploads found
return $form;
} while(false);
// Get the entry object
$entry = GFAPI::get_entry( $entry_id );
if ( !isset( GFFormsModel::$uploaded_files[ $form['id'] ] ) ) {
GFFormsModel::$uploaded_files[ $form['id'] ] = array();
}
// Loop through each file upload and put the basename as an uploaded file
foreach( $form['fields'] as $field ) {
if ( !$field instanceof GF_Field_FileUpload ) continue;
$value = rgar( $entry, $field->id );
GFFormsModel::$uploaded_files[ $form['id'] ]["input_{$field->id}"] = basename( $value );
}
return $form;
}
/**
* Allow editing previous form entry
*
* @param $entry_id
* @param $form
*
* @return mixed
*/
public function change_saved_entry_id( $entry_id, $form ) {
if ( $entry_id !== null ) return $entry_id;
$user_id = get_current_user_id();
// Get the entry being edited
$existing_entry_id = $this->get_edited_entry_id();
if ( !$existing_entry_id ) return $entry_id;
// Use the edited entry if the user is permitted to edit it
if ( $this->can_user_edit_entry( $user_id, $existing_entry_id ) ) {
return $existing_entry_id;
}
return $entry_id;
}
/**
* Fill text fields with the value of an existing entry
*
* @param null $value
* @param GF_Field|null $field
* @param null $name
*
* @return string
*/
public function fill_field_value_text( $value = null, GF_Field $field = null, $name = null ) {
if ( $field->formId != $this->form_id ) return $value;
$existing_entry_id = $this->get_edited_entry_id();
if ( $existing_entry_id ) {
$value = gform_get_meta( $existing_entry_id, $field['id'] );
}
return $value;
}
/**
* Fill text fields with the value of an existing entry
*
* @param null $value
* @param GF_Field|null $field
* @param null $name
*
* @return array
*/
public function fill_field_value_compound( $value = null, GF_Field $field = null, $name = null ) {
if ( $field->formId != $this->form_id ) return $value;
$sub_inputs = rgobj($field, 'inputs');
if ( !$sub_inputs ) return $value;
$existing_entry_id = $this->get_edited_entry_id();
$value = array();
if ( $existing_entry_id ) {
foreach( $sub_inputs as $sub_input ) {
if ( !is_array($sub_input) ) continue;
$sub_input_id = $sub_input['id'];
$v = gform_get_meta( $existing_entry_id, $sub_input_id );
if ( $v ) $value[] = $v;
}
}
return $value;
}
/**
* Same as fill_field_value_text, but for sub inputs which are recorded in $this->sub_inputs
*
* @param null $value
* @param GF_Field|null $field
* @param null $name
*
* @return string
*/
public function fill_sub_input_value_text( $value = null, GF_Field $field = null, $name = null ) {
if ( $field->formId != $this->form_id ) return $value;
// Name must be defined in $this->sub_inputs so we know what sub field this hook relates to.
if ( !isset($this->sub_inputs[$name]) ) return $value;
$id = rgar( $this->sub_inputs[$name], 'id' );
if ( !$id ) return $value;
$existing_entry_id = $this->get_edited_entry_id();
if ( $existing_entry_id ) {
$value = gform_get_meta( $existing_entry_id, $id );
}
return $value;
}
}
@RadGH
Copy link
Copy Markdown
Author

RadGH commented Jun 25, 2020

Updated so you do not need to manually type in all the field names and IDs. Hooray!

@RadGH
Copy link
Copy Markdown
Author

RadGH commented Jul 14, 2020

Updated:

  • Added support for Checkbox fields which has one name and multiple values (array). Add others to the "$compound_field_types" array.
  • Added support for "compound" fields like the Name and Address field, with multiple sub-inputs that each have a name and value.
  • Continued support for "date" field which, although it acts like a compound field, only returns a single value.
  • Fixed hooks running every page load, which would get the form data even if the form was never used on a page.

PS: Gravity forms is ridiculously inconsistent

@RadGH
Copy link
Copy Markdown
Author

RadGH commented May 16, 2022

Updated:

  1. Major change: You no longer need to manually enable auto populate on every field. Fields are made to prepopulate automatically based on "add_form_prepopulate_names".

  2. A working example is provided, see QUICK TEST INSTRUCTIONS and import this form: https://gist.github.com/RadGH/afb032d642515aedbb068f6b5990b668

  3. Files handled better. If you do not remove or replace a file, it will now be preserved

  4. The class "GF_Form_12" is an example you should copy and customize. The class "Editable_GF_Form" you generally shouldn't need to touch.

@Antonio78
Copy link
Copy Markdown

Hi, how can I prevent confirmation emails from being sent when the form is in edit mode?
I have tried adding a remove filter, and also changing the email sending filter but to no avail..

@ashish200025
Copy link
Copy Markdown

HI RadGH

Is there way we can show the uploaded file when file is multi File upload input Field?

Thanks

@LoreGre
Copy link
Copy Markdown

LoreGre commented Nov 22, 2024

Hi,
please someone find solutions for multi files upload issue?

@LoreGre
Copy link
Copy Markdown

LoreGre commented Nov 22, 2024

I edit row 425 like this. But the filename not work. Please let me know if someone solve it. regards

`$value = rgar( $entry, $field->id );

	if( !str_contains($value, '[') ){
		GFFormsModel::$uploaded_files[ $form['id'] ]["input_{$field->id}"] = basename($value);
	} else {
		$values = json_decode($value);
		$urls = array();
		foreach($values as $url){
			$urls[] = basename($url);
		}
		GFFormsModel::$uploaded_files[ $form['id'] ]["input_{$field->id}"] = $urls;
	}`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment