-
-
Save phillipwilhelm/d30bf4143b473ebcd32a7bf6e9f3e014 to your computer and use it in GitHub Desktop.
Edit an existing gravityforms entry on the frontend
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
Plugin Name: GF Editable by Radley | |
Description: Example classes to make a particular gravity form editable on the front-end. | |
Author: Radley Sustaire | |
Author URI: https://radleysustaire.com/ | |
Version: 1.0.0 | |
*/ | |
// QUICK TEST INSTRUCTIONS: | |
// 1. Import this example form in Gravity Forms (download as json): | |
// https://gist.github.com/RadGH/afb032d642515aedbb068f6b5990b668 | |
// | |
// 2. Modify "GF_Form_12" below to be your own form ID after import. | |
// * If you change the class, make sure to replace "new GF_Form_12()" below too. | |
// | |
// 3. Submit an entry on the form. | |
// 4. Go back to the form and add ?edited_entry_id=100 (where 100 is your entry id) | |
// 5. Field values should populate, allowing to edit the same entry. | |
// RECOMMENDED AFTER TESTING: | |
// | |
// 1. Put the "abstract" class Editable_GF_Form into a separate file. | |
// 2. Create a copy of GF_Form_12 for any of your forms. | |
// 3. Include the abstract class, and your custom classes, then instantiate your custom classes like: new GF_Form_12() | |
// Extending "Editable_GF_Form" gives the ability to edit entries. You only need to supply $form_id and $query_arg | |
/** | |
* @class GF_Form_12 | |
* @version 1.0 | |
*/ | |
class GF_Form_12 extends Editable_GF_Form { | |
// Each form should have its own class. | |
public $form_id = 12; | |
public $query_arg = 'edited_entry_id'; | |
public function __construct() { | |
// Add essential hooks from the parent Editable_GF_Form class | |
parent::__construct(); | |
// Register actions or filters for the form here. | |
// EXAMPLE: Add a key when the form gets saved | |
// add_filter( 'gform_entry_post_save', array($this, 'fill_random_key'), 30, 2 ); | |
} | |
// EXAMPLE: The function to add a key when the form gets saved | |
/* | |
public function fill_random_key( $entry, $form ) { | |
if ( $this->form_id != $form['id'] ) return $entry; | |
// Some form of key | |
$key = uniqid(); | |
// Save to the database | |
gform_update_meta( $entry['id'], 'secret_key', $key ); | |
// Add to current entry too | |
$entry['secret_key'] = $key; | |
return $entry; | |
} | |
*/ | |
} | |
// Remember to instantiate your class, or else it won't do anything. | |
new GF_Form_12(); | |
/** | |
* This class is used to make a gravity form entry editable. | |
* Do not edit below, create your own object that extends this class. | |
* An example is above "GF_Form_12" | |
*/ | |
abstract class Editable_GF_Form { | |
/** | |
* Form ID that will allow editing entries. | |
* | |
* @var int | |
*/ | |
public $form_id = null; | |
// Internal variables | |
protected $uploads = array(); | |
protected $sub_inputs = array(); | |
public function __construct() { | |
if ( $this->form_id === null ) { | |
_doing_it_wrong(__FUNCTION__, '$this->form_id must be an integer', '1.0'); | |
exit; | |
} | |
// When editing an entry, change the entry ID to the edited entry instead of creating a new entry | |
add_filter( "gform_entry_id_pre_save_lead_{$this->form_id}", array($this, 'change_saved_entry_id'), 50, 2 ); | |
// Prepare the form to be editable on a very early hook | |
add_filter( 'gform_form_args', array( $this, 'prepare_editable_form' ), 20 ); | |
// Insert field values for most fields | |
add_filter( "gform_field_value", array($this, 'load_regular_field_value'), 20, 3 ); | |
// Keeps file uploads unless the user actually deletes or replaces them | |
add_filter( "gform_pre_process", array( $this, 'pre_restore_existing_uploads' ), 20, 1 ); | |
} | |
/** | |
* Get the entry id that is being edited from the url ?entry_id=100 | |
* | |
* @return int|false | |
*/ | |
public function get_edited_entry_id() { | |
$entry_id = (int) rgar( $_GET, $this->query_arg ); | |
if ( !$entry_id ) $entry_id = false; | |
if ( $this->can_user_edit_entry( get_current_user_id(), $entry_id ) ) { | |
return $entry_id; | |
}else{ | |
return false; | |
} | |
} | |
/** | |
* Keeps file uploads unless the user actually deletes or replaces them. | |
* Gravity forms seems to do this, but fails at it. | |
* To make this work we capture values before (here) and restore them after the entry is saved. | |
* | |
* @param $form | |
* | |
* @return mixed | |
*/ | |
public function pre_restore_existing_uploads( $form ) { | |
if ( $form['id'] != $this->form_id ) return $form; | |
$entry_id = $this->get_edited_entry_id(); | |
// Get uploads from $_POST, served as JSON string, which has file upload info | |
$uploads = rgpost( 'gform_uploaded_files' ); | |
if ( !$uploads ) return $form; | |
// Decode the json | |
$uploads = json_decode( $uploads ); | |
// Each file upload field will have the previous filename, or NULL if removing that file. | |
if ( $uploads ) foreach( $uploads as $input_name => $file ) { | |
// If file was removed by user, or replaced with new file | |
if ( ! $file ) continue; | |
// File should be kept. | |
$field_id = (int) str_replace('input_', '', $input_name ); | |
$url = gform_get_meta( $entry_id, $field_id ); | |
$this->uploads[ $input_name ] = array( | |
'entry_id' => $entry_id, | |
'input_name' => $input_name, | |
'field_id' => $field_id, | |
'url' => $url | |
); | |
} | |
add_filter( "gform_after_submission_{$form['id']}", array( $this, 'restore_existing_uploads' ), 10, 2 ); | |
return $form; | |
} | |
/** | |
* Restore files preserved by pre_restore_existing_uploads() after the entry has been saved | |
* | |
* @param $entry | |
* @param $form | |
* | |
* @return mixed | |
*/ | |
public function restore_existing_uploads( $entry, $form ) { | |
if ( $form['id'] != $this->form_id ) return $entry; | |
if ( $this->uploads ) foreach( $this->uploads as $u ) { | |
if ( $entry['id'] != $u['entry_id'] ) continue; | |
// Get the field ID and URL of the file that should be preserved | |
$field_id = $u['field_id']; // 13 | |
$url = $u['url']; // https://example.com/.../icon-zm3.png | |
// Restore the URL to the entry | |
$entry[ $field_id ] = $url; | |
// Update the entry | |
GFAPI::update_entry( $entry ); | |
} | |
return $entry; | |
} | |
/** | |
* Make all fields on the form editable (allowPrepopulate) and add names if not already given. | |
* | |
* @param $form | |
* | |
* @return mixed | |
*/ | |
public function add_form_prepopulate_names( $form ) { | |
// Modify every field and add a name if needed. | |
// Names added manually work too, by enabling "Allow field to be populated dynamically" on the field. | |
if ( $form['fields'] ) foreach( $form['fields'] as &$field ) { | |
if ( !($field instanceof GF_Field) ) continue; | |
// Don't affect display fields (like html) | |
if ( $field->displayOnly ) continue; | |
// Enable pre-populate | |
$field->allowsPrepopulate = true; | |
// Get regular field name | |
$inputName = $field->inputName; | |
if ( $inputName ) continue; | |
// Check if we need a regular field name, or if we need sub fields with names. | |
if ( empty( $field->inputs ) ) { | |
// Regular field | |
// 1st and 2nd are form ID ($form['id']), and field ID ($form->fields[0]->id) | |
// field_12_4_value | |
$field->inputName = "field_{$field['formId']}_{$field['id']}_value"; | |
}else{ | |
// Sub fields | |
// Loop through sub fields and add names to any that are missing | |
foreach( $field->inputs as $key => &$input ) { | |
if ( empty($input['name']) ) { | |
// 3rd number is the sub field ID: ($form->fields[0]->inputs[0]->id) | |
// input name="field_12_4_1_value" | |
$input['name'] = "sub_field_" . $field['formId'] . '_' . $field['id'] . '_'. $key .'_value'; | |
} | |
} | |
} | |
} | |
return $form; | |
} | |
/** | |
* Fill the value on our form using the existing entry's data | |
* | |
* @param $value | |
* @param GF_Field|null $field | |
* @param $name | |
* | |
* @return array | |
*/ | |
public function load_regular_field_value( $value = null, GF_Field $field = null, $name = null ) { | |
if ( ! ($field instanceof GF_Field) ) return $value; | |
if ( $field->formId != $this->form_id ) return $value; | |
$existing_entry_id = $this->get_edited_entry_id(); | |
if ( !$existing_entry_id ) return $value; | |
$existing_entry = GFAPI::get_entry( $existing_entry_id ); | |
if ( !$existing_entry ) return $value; | |
$value = GFFormsModel::get_lead_field_value( $existing_entry, $field ); | |
// If no sub fields just return value | |
if ( empty($field->inputs) ) return $value; | |
// Checkboxes work with the given value | |
if ( $field->type == 'checkbox' ) return $value; | |
// Names do not seem to work. | |
return $value; | |
} | |
/** | |
* Add certain hooks only when the form is going to be displayed (based on shortcode usage) | |
* | |
* $args = array(7) { | |
* "form_id" => "12" | |
* "display_title" => true | |
* "display_description" => true | |
* "force_display" => false | |
* "field_values" => array() (empty) | |
* "ajax" => false | |
* "tabindex" => "0" | |
* } | |
* | |
* @param $args | |
* @return array | |
*/ | |
public function prepare_editable_form( $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 ); | |
$form = $this->add_form_prepopulate_names( $form ); | |
foreach( $form['fields'] as $field ) { | |
if ( !($field instanceof GF_Field) ) continue; | |
$sub_inputs = rgobj($field, 'inputs'); | |
// Advanced inputs like Name and Address have multiple sub inputs, each with their own name and values | |
// They are tricky to fill because Gravity Forms doesn't give you the key with this filter. | |
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 field assigned to the name (first_name) so we can get field data 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' ), 20, 3 ); | |
} | |
continue; | |
} | |
} | |
return $args; | |
} | |
/** | |
* 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 form | |
if ( $entry['form_id'] != $this->form_id ) return false; | |
// The user ID must match the owner of the entry | |
if ( (int) $entry['created_by'] != (int) $user_id ) 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. | |
* | |
* If file is kept, form submits: | |
* gform_uploaded_files: {"input_13":"icon-aa3.png"} | |
* | |
* If file is removed, form submits: | |
* gform_uploaded_files: {"input_13":null} | |
* | |
* | |
* | |
* @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; | |
// Check permissions | |
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; | |
} | |
/** | |
* Make Gravity Forms edit an existing entry ($entry_id = int), instead of creating a new one ($entry_id = null). | |
* | |
* @param $entry_id null|int | |
* @param $form array | |
* | |
* @return null|int | |
*/ | |
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; | |
// Check access, if user can edit then return the previous entry ID, instead of creating a new one (null). | |
if ( $this->can_user_edit_entry( $user_id, $existing_entry_id ) ) { | |
// Edit previous entry | |
return $existing_entry_id; | |
}else{ | |
// Create a new entry | |
return null; | |
} | |
} | |
/** | |
* Get text used for sub inputs | |
* | |
* @param null $value | |
* @param GF_Field|null $field | |
* @param null $name | |
* | |
* @return string | |
*/ | |
public function fill_sub_input_value( $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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment