-
-
Save RadGH/2e419f7b67974f969e55fa3556f652fc to your computer and use it in GitHub Desktop.
| <?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 Editable_GF_Form { | |
| // 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 int|false | |
| */ | |
| public function get_edited_entry_id() { | |
| $entry_id = (int) rgar( $_GET, 'entry_id' ); | |
| if ( !$entry_id ) $entry_id = false; | |
| 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; | |
| } | |
| } |
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
Updated:
-
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".
-
A working example is provided, see QUICK TEST INSTRUCTIONS and import this form: https://gist.github.com/RadGH/afb032d642515aedbb068f6b5990b668
-
Files handled better. If you do not remove or replace a file, it will now be preserved
-
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.
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..
HI RadGH
Is there way we can show the uploaded file when file is multi File upload input Field?
Thanks
Hi,
please someone find solutions for multi files upload issue?
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;
}`
Updated so you do not need to manually type in all the field names and IDs. Hooray!