value. */ private static $values = array(); /** @var array Original entry of an edit in progress, per form id. */ private static $original = array(); /** * Registers the Gravity Forms hooks. */ public static function hooks() { add_filter( 'gform_pre_render', array( __CLASS__, 'pre_render' ), 20, 3 ); add_filter( 'gform_field_value', array( __CLASS__, 'field_value' ), 20, 3 ); add_filter( 'gform_form_tag', array( __CLASS__, 'form_tag' ), 20, 2 ); add_filter( 'gform_pre_validation', array( __CLASS__, 'pre_validation' ), 20 ); add_filter( 'gform_validation', array( __CLASS__, 'validation' ), 5 ); add_filter( 'gform_entry_id_pre_save_lead', array( __CLASS__, 'entry_id' ), 20, 2 ); add_filter( 'gform_save_field_value', array( __CLASS__, 'keep_value' ), 20, 5 ); add_filter( 'gform_disable_notification', array( __CLASS__, 'disable_notification' ), 20, 4 ); add_filter( 'gform_confirmation', array( __CLASS__, 'confirmation' ), 20, 4 ); add_action( 'gform_after_submission', array( __CLASS__, 'after_submission' ), 20, 2 ); } /** * Edit/duplicate context of a form for the current request, or null. * * Read from the posted hidden inputs on submission, otherwise from the link. * * @param int $form_id Form id. * @return array|null */ public static function context( $form_id ) { $form_id = (int) $form_id; if ( isset( self::$context[ $form_id ] ) ) { return self::$context[ $form_id ] ? self::$context[ $form_id ] : null; } self::$context[ $form_id ] = false; // phpcs:disable WordPress.Security.NonceVerification -- the nonce is the "estg_nonce" value checked below. $source = isset( $_POST['estg_action'] ) ? $_POST : $_GET; if ( ! isset( $source['estg_action'], $source['estg_entry'], $source['estg_nonce'] ) ) { return null; } $action = sanitize_key( wp_unslash( $source['estg_action'] ) ); $entry_id = absint( wp_unslash( $source['estg_entry'] ) ); $nonce = sanitize_text_field( wp_unslash( $source['estg_nonce'] ) ); $return = isset( $source['estg_return'] ) ? (string) wp_unslash( $source['estg_return'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- validated by safe_return(). // phpcs:enable if ( ! in_array( $action, array( 'edit', 'duplicate' ), true ) || ! $entry_id ) { return null; } if ( ! wp_verify_nonce( $nonce, Urls::nonce_action( $action, $entry_id ) ) ) { return null; } $entry = \GFAPI::get_entry( $entry_id ); if ( is_wp_error( $entry ) || (int) $entry['form_id'] !== $form_id ) { return null; } $form = \GFAPI::get_form( $form_id ); if ( ! $form || ! Access::can( $action, $entry, Settings::get( $form ) ) ) { return null; } self::$context[ $form_id ] = array( 'action' => $action, 'entry' => $entry, 'nonce' => $nonce, 'return' => Urls::safe_return( $return, '' ), ); return self::$context[ $form_id ]; } /** * The current request carries edit arguments for this form (valid or not). * * @param int $form_id Form id. * @return bool */ private static function is_attempt( $form_id ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- presence check only. return isset( $_POST['estg_action'], $_POST['estg_entry'] ) && isset( $_POST['gform_submit'] ) && absint( $_POST['gform_submit'] ) === (int) $form_id; } /** * Fills the form with the entry values. * * @param array $form Form object. * @param bool $ajax Ajax form. * @param array $field_values Field values passed to gravity_form(). * @return array */ public static function pre_render( $form, $ajax = false, $field_values = array() ) { if ( ! is_array( $form ) || empty( $form['id'] ) ) { return $form; } $context = self::context( $form['id'] ); if ( ! $context ) { return $form; } $entry = $context['entry']; $values = array(); foreach ( $form['fields'] as $field ) { if ( in_array( $field->type, array( 'html', 'section', 'page', 'captcha', 'password', 'fileupload', 'signature' ), true ) ) { if ( 'edit' === $context['action'] && in_array( $field->type, array( 'fileupload', 'signature' ), true ) ) { self::describe_current_file( $field, $entry ); } continue; } $inputs = $field->get_entry_inputs(); if ( is_array( $inputs ) && ! in_array( $field->type, array( 'checkbox' ), true ) && 'checkbox' !== $field->get_input_type() ) { // Multi-input fields (name, address, product...): one value per input. foreach ( $field->inputs as $i => $input ) { $value = rgar( $entry, (string) $input['id'] ); if ( '' === (string) $value ) { continue; } $name = self::input_name( $field, $i ); $values[ $name ] = $value; } $field->allowsPrepopulate = true; continue; } $value = self::single_value( $field, $entry ); if ( null === $value || '' === $value || array() === $value ) { continue; } $name = self::field_name( $field ); $values[ $name ] = $value; $field->allowsPrepopulate = true; } /** * Filters the values used to fill the form (name => value). * * @param array $values Values. * @param array $entry Entry. * @param array $form Form object. * @param string $action edit | duplicate. */ self::$values[ (int) $form['id'] ] = (array) apply_filters( 'estg_prefill_values', $values, $entry, $form, $context['action'] ); return $form; } /** * Value of a single-input field as Gravity Forms expects it for dynamic population. * * @param \GF_Field $field Field. * @param array $entry Entry. * @return mixed */ private static function single_value( $field, $entry ) { $type = $field->get_input_type(); if ( 'checkbox' === $type ) { $selected = array(); foreach ( (array) $field->inputs as $input ) { $v = rgar( $entry, (string) $input['id'] ); if ( '' !== (string) $v ) { $selected[] = $v; } } return $selected; } $value = rgar( $entry, (string) $field->id ); if ( 'multiselect' === $type && method_exists( $field, 'to_array' ) ) { return $field->to_array( $value ); } if ( 'list' === $type ) { $rows = maybe_unserialize( $value ); return is_array( $rows ) ? $rows : ''; } if ( 'date' === $type && 'datepicker' === rgobj( $field, 'dateType' ) && '' !== (string) $value ) { $display = \GFCommon::date_display( $value, 'ymd_dash', rgobj( $field, 'dateFormat' ) ? rgobj( $field, 'dateFormat' ) : 'mdy' ); return $display ? $display : $value; } if ( in_array( $type, array( 'select', 'radio' ), true ) && ! empty( $field->enablePrice ) && false !== strpos( (string) $value, '|' ) ) { // Product options are stored as "value|price". $value = substr( $value, 0, strpos( $value, '|' ) ); } return $value; } /** * Population name of a field (keeps the one set in the form if any). * * @param \GF_Field $field Field. * @return string */ private static function field_name( $field ) { if ( '' === trim( (string) rgobj( $field, 'inputName' ) ) ) { $field->inputName = 'estg_' . $field->id; } return $field->inputName; } /** * Population name of one input of a field. * * @param \GF_Field $field Field. * @param int $i Input index. * @return string */ private static function input_name( $field, $i ) { $inputs = $field->inputs; if ( '' === trim( (string) rgar( $inputs[ $i ], 'name' ) ) ) { $inputs[ $i ]['name'] = 'estg_' . str_replace( '.', '_', (string) $inputs[ $i ]['id'] ); $field->inputs = $inputs; } return $field->inputs[ $i ]['name']; } /** * Shows the files already attached under an upload field. * * @param \GF_Field $field Field. * @param array $entry Entry. */ private static function describe_current_file( $field, $entry ) { $value = rgar( $entry, (string) $field->id ); if ( '' === (string) $value ) { return; } $files = json_decode( $value, true ); $files = is_array( $files ) ? $files : array( $value ); $names = array(); foreach ( $files as $url ) { $names[] = esc_html( wp_basename( (string) strtok( (string) $url, '?' ) ) ); } $note = '' . esc_html__( 'Current file (kept if you add nothing):', 'entrystage' ) . ' ' . implode( ', ', $names ) . ''; $field->description = trim( rgobj( $field, 'description' ) . ' ' . $note ); } /** * Gives Gravity Forms the entry values. * * @param mixed $value Current value. * @param \GF_Field $field Field. * @param string $name Population name. * @return mixed */ public static function field_value( $value, $field, $name ) { if ( ! is_object( $field ) || empty( $field->formId ) ) { return $value; } $form_id = (int) $field->formId; if ( isset( self::$values[ $form_id ] ) && array_key_exists( (string) $name, self::$values[ $form_id ] ) ) { return self::$values[ $form_id ][ (string) $name ]; } return $value; } /** * Adds the hidden inputs and a banner to the form. * * @param string $tag Form tag. * @param array $form Form object. * @return string */ public static function form_tag( $tag, $form ) { $context = self::context( rgar( $form, 'id' ) ); if ( ! $context ) { return $tag; } $entry_id = (int) $context['entry']['id']; $tag .= ''; $tag .= ''; $tag .= ''; $tag .= ''; $text = 'edit' === $context['action'] /* translators: %d: entry id */ ? sprintf( __( 'You are editing entry #%d.', 'entrystage' ), $entry_id ) /* translators: %d: entry id */ : sprintf( __( 'New entry pre-filled from entry #%d.', 'entrystage' ), $entry_id ); $banner = '