Edit Entry

Edit the entry that was passed through via GP Easy Passthrough rather than creating a new entry.

Read the Walkthrough

Code

Filename: gpep-edit-entry.php

<?php
/**
 * Gravity Perks // Easy Passthrough // Edit Entry
 * https://gravitywiz.com/edit-gravity-forms-entries-on-the-front-end/
 *
 * Edit the entry that was passed through via GP Easy Passthrough rather than creating a new entry.
 *
 * Plugin Name:  GP Easy Passthrough — Edit Entry
 * Plugin URI:   https://gravitywiz.com/edit-gravity-forms-entries-on-the-front-end/
 * Description:  Edit the entry that was passed through via GP Easy Passthrough rather than creating a new entry.
 * Author:       Gravity Wiz
 * Version:      1.4.3
 * Author URI:   https://gravitywiz.com/
 */
class GPEP_Edit_Entry {

	private $form_id;
	private $delete_partial;
	private $passed_through_entries;
	private $refresh_token;
	private $process_feeds;

	public function __construct( $options ) {

		if ( ! function_exists( 'rgar' ) ) {
			return;
		}

		$this->form_id        = rgar( $options, 'form_id' );
		$this->delete_partial = rgar( $options, 'delete_partial', true );
		$this->refresh_token  = rgar( $options, 'refresh_token', false );
		$this->process_feeds  = rgar( $options, 'process_feeds', false );

		add_filter( "gpep_form_{$this->form_id}", array( $this, 'capture_passed_through_entry_ids' ), 10, 3 );
		add_filter( "gform_entry_id_pre_save_lead_{$this->form_id}", array( $this, 'update_entry_id' ), 10, 2 );
		add_filter( "gform_entry_post_save_{$this->form_id}", array( $this, 'delete_values_for_conditionally_hidden_fields' ), 10, 2 );

		// Enable edit view in GP Inventory.
		add_filter( "gpi_is_edit_view_{$this->form_id}", '__return_true' );

		// Bypass limit submissions on validation
		add_filter( 'gform_validation', array( $this, 'bypass_limit_submission_validation' ) );

		add_filter( "gpi_query_{$this->form_id}", array( $this, 'exclude_edit_entry_from_inventory' ), 10, 2 );

		// If we need to reprocess any feeds on 'edit'.
		add_filter( 'gform_entry_post_save', array( $this, 'process_feeds' ), 10, 2 );
	}

	public function capture_passed_through_entry_ids( $form, $values, $passed_through_entries ) {

		// Save a runtime cache for use when releasing inventory reserved by the entry being edited.
		$this->passed_through_entries = $passed_through_entries;

		if ( empty( $passed_through_entries ) ) {
			return $form;
		}

		// Add hidden input to capture entry IDs passed through via GPEP.

		add_filter( "gform_form_tag_{$form['id']}", function( $form_tag, $form ) use ( $passed_through_entries ) {
			$entry_ids = implode( ',', wp_list_pluck( $passed_through_entries, 'entry_id' ) );
			$hash      = wp_hash( $entry_ids );
			$value     = sprintf( '%s|%s', $entry_ids, $hash );
			$input     = sprintf( '<input type="hidden" name="%s" value="%s">', $this->get_passed_through_entries_input_name( $form['id'] ), $value );
			$form_tag .= $input;
			return $form_tag;
		}, 10, 2 );

		add_filter( "gpls_rule_groups_{$this->form_id}", function( $rule_groups, $form_id ) use ( $passed_through_entries ) {
			// Bypass GPLS if we're updating an entry.
			if ( ! empty( $passed_through_entries ) ) {
				$rule_groups = array();
			}

			return $rule_groups;
		}, 10, 2 );

		return $form;
	}

	public function bypass_limit_submission_validation( $validation_result ) {
		$edit_entry_id = $this->get_edit_entry_id( rgars( $validation_result, 'form/id' ) );

		if ( ! $edit_entry_id ) {
			return $validation_result;
		}

		add_filter( "gpls_rule_groups_{$this->form_id}", function( $rule_groups, $form_id ) use ( $edit_entry_id ) {
			return array();
		}, 10, 2 );

		return $validation_result;
	}

	public function update_entry_id( $entry_id, $form ) {

		$update_entry_id = $this->get_edit_entry_id( $form['id'] );
		if ( $update_entry_id ) {
			// Purge product info cache
			$this->purge_product_cache( $form, GFAPI::get_entry( $update_entry_id ) );

			if ( $this->delete_partial
				&& is_callable( array( 'GF_Partial_Entries', 'get_instance' ) )
				&& $entry_id !== null
				&& ! empty( GF_Partial_Entries::get_instance()->get_active_feeds( $form['id'] ) )
			) {
				GFAPI::delete_entry( $entry_id );
			}
			if ( $this->refresh_token ) {
				gform_delete_meta( $update_entry_id, 'fg_easypassthrough_token' );
				gp_easy_passthrough()->get_entry_token( $update_entry_id );
				// Remove entry from the session and prevent Easy Passthrough from resaving it.
				$session = gp_easy_passthrough()->session_manager();
				$session[ gp_easy_passthrough()->get_slug() . '_' . $form['id'] ] = null;
				remove_action( 'gform_after_submission', array( gp_easy_passthrough(), 'store_entry_id' ) );
			}
			return $update_entry_id;
		}

		return $entry_id;
	}

	/**
	 * Delete values that exist for the entry in the database for fields that are now conditionally hidden.
	 *
	 * If we find any instance where a conditionally hidden field has a value, we'll update the DB with the passed entry,
	 * which was just submitted and will not contain conditionally hidden values.
	 *
	 * Note: There's a good case for us to simply call GFAPI::update_entry() with the passed entry without all the other
	 * fancy logic to that only makes the call if it identifies a conditionally hidden field with a DB value. A thought
	 * for future us.
	 *
	 * @param $entry
	 * @param $form
	 *
	 * @return mixed
	 */
	public function delete_values_for_conditionally_hidden_fields( $entry, $form ) {

		// We'll only update the entry if we identify a field value that needs to be deleted.
		$has_change = false;

		// The passed entry does not reflect what is actually in the database.
		$db_entry = null;

		/**
		 * @var \GF_Field $field
		 */
		foreach ( $form['fields'] as $field ) {

			if ( ! GFFormsModel::is_field_hidden( $form, $field, array(), $entry ) ) {
				continue;
			}

			if ( ! $db_entry ) {
				$db_entry = GFAPI::get_entry( $entry['id'] );
			}

			$inputs = $field->get_entry_inputs();
			if ( ! $inputs ) {
				$inputs = array(
					array(
						'id' => $field->id,
					),
				);
			}

			foreach ( $inputs as $input ) {
				if ( ! empty( $db_entry[ $input['id'] ] ) ) {
					$has_change = true;
					break 2;
				}
			}
		}

		if ( $has_change ) {
			GFAPI::update_entry( $entry );
		}

		return $entry;
	}

	public function get_passed_through_entries_input_name( $form_id ) {
		return "gpepee_passed_through_entries_{$form_id}";
	}

	public function get_passed_through_entry_ids( $form_id ) {

		$entry_ids = array();

		if ( ! empty( $_POST ) ) {

			$posted_value = rgpost( $this->get_passed_through_entries_input_name( $form_id ) );
			if ( empty( $posted_value ) ) {
				return $entry_ids;
			}

			list( $entry_ids, $hash ) = explode( '|', $posted_value );
			if ( $hash !== wp_hash( $entry_ids ) ) {
				return $entry_ids;
			}

			$entry_ids = explode( ',', $entry_ids );

		} elseif ( ! empty( $this->passed_through_entries ) ) {

			$entry_ids = wp_list_pluck( $this->passed_through_entries, 'entry_id' );

		}

		return $entry_ids;
	}

	public function get_edit_entry_id( $form_id ) {

		$entry_ids = $this->get_passed_through_entry_ids( $form_id );
		$entry_id  = array_shift( $entry_ids );

		/**
		 * Filter the ID that will be used to fetch assign the entry to be edited.
		 *
		 * @since 1.3
		 *
		 * @param int|bool $edit_entry_id The ID of the entry to be edited.
		 * @param int      $form_id       The ID of the form that was submitted.
		 */
		return gf_apply_filters( array( 'gpepee_edit_entry_id', $form_id ), $entry_id, $form_id );
	}

	/**
	 * Exclude the entry being edited in GravityView from inventory counts.
	 *
	 * Without this, you can't reselect choices that the current entry has consumed.
	 */
	public function exclude_edit_entry_from_inventory( $query, $field ) {
		global $wpdb;

		$entry_ids = $this->get_passed_through_entry_ids( $field->formId );

		// @todo Update to work with multiple passed through entries.
		$current_entry_id = array_pop( $entry_ids );
		if ( ! $current_entry_id ) {
			return $query;
		}

		$query['where'] .= $wpdb->prepare( "\nAND em.entry_id != %d", $current_entry_id );

		return $query;
	}

	public static function purge_product_cache( $form, $entry ) {

		$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( $entry, 'id' ), "gform_product_info_{$use_choice_text}_{$use_admin_label}" ) ) {
				gform_delete_meta( rgar( $entry, 'id' ), "gform_product_info_{$use_choice_text}_{$use_admin_label}" );
			}
		}

	}

	public function process_feeds( $entry, $form ) {
		if ( ! $this->process_feeds ) {
			return $entry;
		}

		/**
		 * Disable asynchronous feed process on edit otherwise async feeds will not be re-ran due to a check in
		 * class-gf-feed-processor.php that checks `gform_get_meta( $entry_id, 'processed_feeds' )` and there isn't
		 * a way to bypass it.
		 */
		$filter_priority = rand( 100000, 999999 );
		add_filter( 'gform_is_feed_asynchronous', '__return_false', $filter_priority );

		foreach ( GFAddOn::get_registered_addons( true ) as $addon ) {
			if ( method_exists( $addon, 'maybe_process_feed' ) && ( $this->process_feeds === true || strpos( $this->process_feeds, $addon->get_slug() ) !== false ) ) {
				$addon->maybe_process_feed( $entry, $form );
			}
		}

		remove_filter( 'gform_is_feed_asynchronous', '__return_false', $filter_priority );
		return $entry;
	}

}

// Configurations
new GPEP_Edit_Entry( array(
	'form_id'        => 123,   // Set this to the form ID.
	'delete_partial' => false, // Set this to false if you wish to preserve partial entries after an edit is submitted.
	'refresh_token'  => false,  // Set this to true to generate a fresh Easy Passthrough token after updating an entry.
	'process_feeds'  => false,  // Set this to true to process all feed addons on Edit Entry, or provide a comma separated list of addon slugs like 'gravityformsuserregistration', etc.
) );

Leave a Reply

Your email address will not be published. Required fields are marked *

  • Trouble installing this snippet? See our troubleshooting tips.
  • Need to include code? Create a gist and link to it in your comment.
  • Reporting a bug? Provide a URL where this issue can be recreated.

By commenting, I understand that I may receive emails related to Gravity Wiz and can unsubscribe at any time.