<?php

class_exists( 'GFForms' ) or die();

use Gravity_Forms\Gravity_Forms\Settings\Settings;

/**
 * Handles listing and editing Form Confirmations.
 *
 * @since 2.5
 *
 * Class GF_Confirmations
 */
class GF_Confirmation {

	use Redirects_On_Save;

	/**
	 * Regular expression for determining if string contains unsafe merge tags.
	 *
	 * @since 2.5
	 *
	 * @var string
	 */
	private static $unsafe_regex = '/(\S+)\s*=\s*["|\']({[^{]*?:(\d+(\.\d+)?)(:(.*?))?})["|\']/mi';

	/**
	 * Stores the current instance of the Settings renderer.
	 *
	 * @since 2.5
	 *
	 * @var false|Gravity_Forms\Gravity_Forms\Settings\Settings
	 */
	private static $_settings_renderer = false;

	/**
	 * Displays the Confirmations list or edit page.
	 *
	 * @since 2.5
	 */
	public static function render_page() {

		// Get form, confirmation IDs.
		$form_id         = absint( rgget( 'id' ) );
		$confirmation_id = rgpost( 'confirmation_id' ) ? rgpost( 'confirmation_id' ) : rgget( 'cid' );

		// Display edit or list page.
		if ( ! rgblank( $confirmation_id ) ) {
			self::confirmations_edit_page( $form_id, $confirmation_id );
		} else {
			self::confirmations_list_page( $form_id );
		}

	}





	// # LIST PAGE -----------------------------------------------------------------------------------------------------

	/**
	 * Displays the Confirmations listing page.
	 *
	 * @since  Unknown
	 * @access public
	 *
	 * @used-by GFFormSettings::confirmations_page()
	 * @uses    GFFormSettings::maybe_process_confirmation_list_action()
	 * @uses    GFFormSettings::page_header()
	 * @uses    GFFormsModel::get_form_meta()
	 * @uses    GFConfirmationTable::prepare_items()
	 * @uses    GFConfirmationTable::display()
	 * @uses    GFFormSettings::page_footer()
	 *
	 * @param int $form_id The form ID to display the confirmations for.
	 *
	 * @return void
	 */
	private static function confirmations_list_page( $form_id ) {

		// Process list actions.
		GFFormSettings::maybe_process_confirmation_list_action();

		// Get form object.
		$form = GFFormsModel::get_form_meta( $form_id );

		// Prepare list table.
		$confirmation_table = new GFConfirmationTable( $form );
		$confirmation_table->prepare_items();

		?>

		<div class="gform-settings-panel">
			<header class="gform-settings-panel__header">
				<h4 class="gform-settings-panel__title"><?php esc_html_e( 'Confirmations', 'gravityforms' ); ?></h4>
			</header>

			<div class="gform-settings-panel__content">

				<form id="confirmation_list_form" method="post">

					<?php
					$confirmation_table->display();
					wp_nonce_field( 'gform_confirmation_list_action', 'gform_confirmation_list_action' );
					?>

					<input id="action_argument" name="action_argument" type="hidden" />
					<input id="action" name="action" type="hidden" />

				</form>
			</div>

			<script type="text/javascript">
				var form = <?php echo json_encode( $form ); ?>;

				function ToggleActive( btn, confirmation_id ) {
					var is_active = jQuery( btn ).hasClass( 'gform-status--active' );

					jQuery.ajax(
						{
							url:      '<?php echo admin_url( 'admin-ajax.php' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>',
							method:   'POST',
							dataType: 'json',
							data: {
								action:                        'rg_update_confirmation_active',
								rg_update_confirmation_active: '<?php echo wp_create_nonce( 'rg_update_confirmation_active' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped  ?>',
								form_id:                       '<?php echo intval( $form_id ); ?>',
								confirmation_id:               confirmation_id,
								is_active:                     is_active ? 0 : 1,
							},
							success:  function() {
								if ( is_active ) {
									setToggleInactive();
								} else {
									setToggleActive();
								}
							},
							error:    function() {
								if ( ! is_active ) {
									setToggleInactive();
								} else {
									setToggleActive();
								}

								alert( '<?php echo esc_js( __( 'Ajax error while updating form', 'gravityforms' ) ); ?>' );
							}
						}
					);

					function setToggleInactive() {
						jQuery( btn ).removeClass( 'gform-status--active' ).addClass( 'gform-status--inactive' ).find( '.gform-status-indicator-status' ).html( <?php echo wp_json_encode( esc_attr__( 'Inactive', 'gravityforms' ) ); ?> );
					}

					function setToggleActive() {
						jQuery( btn ).removeClass( 'gform-status--inactive' ).addClass( 'gform-status--active' ).find( '.gform-status-indicator-status' ).html( <?php echo wp_json_encode( esc_attr__( 'Active', 'gravityforms' ) ); ?> );
					}

				}

			</script>

		</div>

		<?php
	}





	// # EDIT PAGE -----------------------------------------------------------------------------------------------------

	/**
	 * Displays the Confirmation Edit page.
	 *
	 * @since  2.5
	 * @access public
	 *
	 * @param int $form_id         The ID of the form confirmations are being edited for.
	 * @param int $confirmation_id The confirmation ID being edited.
	 */
	private static function confirmations_edit_page( $form_id, $confirmation_id ) {

		// Initialize settings.
		if ( ! self::get_settings_renderer() ) {
			self::initialize_settings_renderer();
		}

		// Render settings.
		self::get_settings_renderer()->render();

	}

	/**
	 * Get Confirmation settings fields.
	 *
	 * @since 2.5
	 *
	 * @param array $confirmation Confirmation being edited.
	 * @param array $form         The Form object.
	 *
	 * @return array
	 */
	private static function settings_fields( $confirmation, $form ) {

		// Build confirmation settings fields.
		$fields = array(
			array(
				'title'  => esc_html__( 'Confirmations', 'gravityforms' ),
				'fields' => array(
					array(
						'name'     => 'name',
						'label'    => esc_html__( 'Confirmation Name', 'gravityforms' ),
						'type'     => 'text',
						'required' => true,
						'hidden'   => rgar( $confirmation, 'isDefault' ),
					),
					array(
						'name' => 'event',
						'type' => 'hidden',
					),
					array(
						'name'          => 'type',
						'label'         => esc_html__( 'Confirmation Type', 'gravityforms' ),
						'type'          => 'radio',
						'horizontal'    => true,
						'default_value' => 'message',
						'choices'       => array(
							array(
								'label' => esc_html__( 'Text', 'gravityforms' ),
								'value' => 'message',
							),
							array(
								'label' => esc_html__( 'Page', 'gravityforms' ),
								'value' => 'page',
							),
							array(
								'label' => esc_html__( 'Redirect', 'gravityforms' ),
								'value' => 'redirect',
							),
						),
					),
					array(
						'name'       => 'message',
						'type'       => 'textarea',
						'label'      => esc_html__( 'Message', 'gravityforms' ),
						'tooltip'    => gform_tooltip( 'form_confirmation_message', null, true ),
						'use_editor' => true,
						'dependency' => array(
							'live'     => true,
							'operator' => 'ALL',
							'fields'   => array(
								array(
									'field'  => 'type',
									'values' => array( 'message' ),
								),
							),
						),
					),
					array(
						'name'       => 'disableAutoformat',
						'label'      => esc_html__( 'Auto-Formatting', 'gravityforms' ),
						'type'       => 'checkbox',
						'choices'    => array(
							array(
								'name'    => 'disableAutoformat',
								'label'   => esc_html__( 'Disable auto-formatting', 'gravityforms' ),
								'tooltip' => gform_tooltip( 'form_confirmation_autoformat', null, true ),
							),
						),
						'dependency' => array(
							'live'     => true,
							'operator' => 'ALL',
							'fields'   => array(
								array(
									'field'  => 'type',
									'values' => array( 'message' ),
								),
							),
						),
					),
					array(
						'name'       => 'page',
						'label'      => esc_html__( 'Page', 'gravityforms' ),
						'type'       => 'post_select',
						'required'   => true,
						'dependency' => array(
							'live'     => true,
							'operator' => 'ALL',
							'fields'   => array(
								array(
									'field'  => 'type',
									'values' => array( 'page' ),
								),
							),
						),
					),
					array(
						'name'                => 'url',
						'label'               => esc_html__( 'Redirect URL', 'gravityforms' ),
						'type'                => 'text',
						'required'            => true,
						'dependency'          => array(
							'live'     => true,
							'operator' => 'ALL',
							'fields'   => array(
								array(
									'field'  => 'type',
									'values' => array( 'redirect' ),
								),
							),
						),
						'validation_callback' => function( $field, $value ) {

							if ( ( empty( $value ) || ! GFCommon::is_valid_url( $value ) ) && ! GFCommon::has_merge_tag( $value ) ) {
								$field->set_error( __( 'You must specify a valid Redirect URL.', 'gravityforms' ) );
							}

						}
					),
					array(
						'name'        => 'queryString',
						'type'        => 'text',
						'label'       => esc_html__( 'Pass Field Data via Query String', 'gravityforms' ),
						'class'       => 'merge-tag-support mt-position-right mt-hide_all_fields mt-option-url',
						'tooltip'     => gform_tooltip( 'form_redirect_querystring', null, true ),
						'dependency'  => array(
							'live'     => true,
							'operator' => 'ALL',
							'fields'   => array(
								array(
									'field'  => 'type',
									'values' => array( 'page', 'redirect' ),
								),
							),
						),
						'description' => esc_html__( 'Sample: phone={Phone:1}&email={Email:2}', 'gravityforms' ),
					),
					array(
						'name'        => 'conditionalLogic',
						'label'       => esc_html__( 'Conditional Logic', 'gravityforms ' ),
						'type'        => 'conditional_logic',
						'object_type' => 'confirmation',
						'checkbox'    => array(
							'label'  => esc_html__( 'Enable conditional logic', 'gravityforms' ),
							'hidden' => false,
						),
						'dependency' => function( $settings ) use ( $confirmation ) {
							return ! rgar( $confirmation, 'isDefault' );
						}
					),
					array(
						'type'  => 'save',
						'value' => esc_html__( 'Save Confirmation', 'gravityforms' ),
					),
				),
			),
		);

		/**
		 * Filters the form settings before they are displayed.
		 *
		 * @since      Unknown
		 * @deprecated 2.5
		 *
		 * @param array $ui_settings  The Settings page markup.
		 * @param array $confirmation Contains the confirmation details.
		 * @param array $form         The Form Object.
		 */
		$legacy_settings = apply_filters( 'gform_confirmation_ui_settings', array(), $confirmation, $form );

		// If legacy settings exist, add to fields.
		if ( ! empty( $legacy_settings ) ) {

			// Prepare HTML.
			$html = '<table class="gforms_form_settings" cellspacing="0" cellpadding="0" width="100%">';
			foreach ( $legacy_settings as $legacy_field ) {
				$html .= $legacy_field;
			}
			$html .= '</table>';

			// Add section.
			$fields[] = array(
				'title'  => esc_html__( 'Legacy Settings', 'gravityforms' ),
				'class'  => 'gform-settings-panel--full',
				'fields' => array(
					array(
						'name' => 'legacy',
						'type' => 'html',
						'html' => $html,
					),
				),
			);

		}

		/**
		 * Filters the confirmation settings fields before they are displayed.
		 *
		 * @since 2.5
		 *
		 * @param array $fields       Form settings fields.
		 * @param array $confirmation Contains the Confirmation meta.
		 * @param array $form         Form Object.
		 */
		$fields = gf_apply_filters( array(
			'gform_confirmation_settings_fields',
			$form['id'],
		), $fields, $confirmation, $form );

		return $fields;

	}

	/**
	 * Get Confirmation object for Confirmation edit page.
	 *    Handles duplication.
	 *
	 * @param string $confirmation_id Confirmation ID.
	 * @param array  $form            Form object.
	 *
	 * @return array
	 */
	private static function get_confirmation( $confirmation_id, $form ) {

		// Get ID of confirmation to duplicate, determine if we are duplicating confirmation.
		$duplicated_cid = sanitize_key( rgget( 'duplicatedcid' ) );
		$is_duplicate   = empty( $_POST ) && ! empty( $duplicated_cid ); // phpcs:ignore WordPress.Security.NonceVerification.Missing

		// Get confirmation object.
		$confirmation = rgar( $form['confirmations'], $is_duplicate ? $duplicated_cid : $confirmation_id, array() );

		// If confirmation is not being duplicated, return.
		if ( ! $is_duplicate ) {
			return $confirmation;
		}

		// Reset confirmation ID, default status, conditional logic.
		$confirmation['id']        = null;
		$confirmation['isDefault'] = false;

		// Check for confirmation count in confirmation name.
		preg_match_all( '/(\\(([0-9])*\\))$/mi', $confirmation['name'], $count_exists_in_name );

		// If count does not exist, set count to 1.
		if ( empty( $count_exists_in_name[0] ) ) {

			// Set initial count.
			$count = 1;

		} else {

			// Set existing count to current count plus one.
			$count = (int) $count_exists_in_name[2][0] + 1;

			// Remove existing count from title.
			$confirmation['name'] = preg_replace( '/(\\(([0-9])*\\))$/mi', '', $confirmation['name'] );

		}

		// Trim confirmation name, add copy count.
		$confirmation['name'] = trim( $confirmation['name'] );
		$new_name             = $confirmation['name'] . " ($count)";

		// If new confirmation name is not unique, increment the count until a unique confirmation name is created.
		while ( ! self::is_unique_name( $new_name, rgar( $form, 'confirmations', array() ) ) ) {
			$count++;
			$new_name = $confirmation['name'] . " ($count)";
		}

		// Set confirmation name.
		$confirmation['name'] = $new_name;

		return $confirmation;

	}





	// # SETTINGS RENDERER ---------------------------------------------------------------------------------------------

	/**
	 * Initialize Plugin Settings fields renderer.
	 *
	 * @since 2.5
	 */
	public static function initialize_settings_renderer() {

		if ( ! class_exists( 'GFFormSettings' ) ) {
			require_once( GFCommon::get_base_path() . '/form_settings.php' );
		}

		// Get form, confirmation IDs.
		$form_id         = absint( rgget( 'id' ) );
		$confirmation_id = rgpost( 'confirmation_id' ) ? rgpost( 'confirmation_id' ) : rgget( 'cid' );

		$form = GFCommon::gform_admin_pre_render( GFFormsModel::get_form_meta( $form_id ) );

		// Get confirmation object.
		$confirmation = self::get_confirmation( $confirmation_id, $form );

		// Get initial values.
		$initial_values = array();
		foreach ( $confirmation as $key => $value ) {
			$initial_values[ $key ] = $value;
		}
		$initial_values['page']                                  = rgar( $confirmation, 'pageId' );
		$initial_values['type']                                  = rgar( $confirmation, 'type' ) ? rgar( $confirmation, 'type' ) : 'message';
		$initial_values['confirmation_conditional_logic']        = ! rgar( $confirmation, 'isDefault' );
		$initial_values['confirmation_conditional_logic_object'] = htmlentities( json_encode( rgget( 'conditionalLogic', $confirmation ) ) );

		// Add warning if confirmation message is unsafe.
		if ( ! empty( $confirmation['message'] ) && self::confirmation_looks_unsafe( $confirmation['message'] ) ) {
			$dismissible_message = esc_html__( 'Your confirmation message appears to contain a merge tag as the value for an HTML attribute. Depending on the attribute and field type, this might be a security risk. %sFurther details%s', 'gravityforms' );
			$dismissible_message = sprintf(
				$dismissible_message,
				'<a href="https://docs.gravityforms.com/security-warning-merge-tags-html-attribute-values/" target="_blank">',
				'<span class="screen-reader-text">' . esc_html__( '(opens in a new tab)', 'gravityforms' ) . '</span>&nbsp;<span class="gform-icon gform-icon--external-link" aria-hidden="true"></span></a>'
			);
			GFCommon::add_dismissible_message( $dismissible_message, 'confirmation_unsafe_' . $form_id );
		}

		// Get fields.
		$sections = self::settings_fields( $confirmation, $form );

		// Initialize new settings renderer.
		$renderer = new Settings(
			array(
				'fields'         => $sections,
				'header'         => array(
					'icon'  => 'fa fa-cogs',
					'title' => esc_html__( 'Confirmation Settings', 'gravityforms' ),
				),
				'initial_values' => $initial_values,
				'save_callback'  => function( $values ) use ( &$confirmation, &$form, &$confirmation_id ) {

					// Determine if new confirmation.
					$is_new_confirmation = ! $confirmation;

					// Set confirmation ID.
					if ( $is_new_confirmation ) {
						$confirmation_id = $confirmation['id'] = uniqid();
					}

					// Save values to the confirmation object in advance so non-custom values will be rewritten when we apply values below.
					$confirmation = GFFormSettings::save_changed_form_settings_fields( $confirmation, $values );

					// Apply values.
					$confirmation['name']              = rgar( $values, 'name' );
					$confirmation['event']             = rgar( $values, 'event' );
					$confirmation['type']              = GFCommon::whitelist( rgar( $values, 'type' ), array(
						'message',
						'page',
						'redirect',
					) );
					$confirmation['message']           = self::maybe_wp_kses( rgar( $values, 'message' ) );
					$confirmation['disableAutoformat'] = (bool) rgar( $values, 'disableAutoformat' );
					$confirmation['pageId']            = rgar( $values, 'page' );
					$confirmation['url']               = rgar( $values, 'url' );
					$confirmation['queryString']       = rgar( $values, 'queryString' );

					$confirmation['conditionalLogic'] = rgar( $confirmation, 'isDefault' ) ? array() : rgar( $values, 'confirmation_conditional_logic_object' );
					$confirmation['conditionalLogic'] = GFFormsModel::sanitize_conditional_logic( $confirmation['conditionalLogic'] );

					/**
					 * Filters the confirmation before it is saved.
					 *
					 * @since Unknown
					 *
					 * @param array $confirmation        The confirmation details.
					 * @param array $form                The Form Object.
					 * @param bool  $is_new_confirmation True if this is a new confirmation. False if editing existing.
					 */
					$confirmation = gf_apply_filters( array(
						'gform_pre_confirmation_save',
						$form['id']
					), $confirmation, $form, $is_new_confirmation );

					$confirmation = GFFormsModel::trim_conditional_logic_values_from_element( $confirmation, $form );

					// Save confirmation.
					$form['confirmations'][ $confirmation['id'] ] = $confirmation;
					$result                                       = GFFormsModel::save_form_confirmations( $form['id'], $form['confirmations'] );

					self::$_saved_item_id = $confirmation_id;
				},
				'before_fields'  => function() use ( &$confirmation, $confirmation_id, $form ) {

					$entry_meta = GFFormsModel::get_entry_meta( $form['id'] );
					/**
					 * Filters the entry meta used within confirmations.
					 *
					 * @since Unknown
					 *
					 * @param array $entry_meta      The Entry Object.
					 * @param array $form            The Form Object.
					 * @param int   $confirmation_id The ID of the confirmation being edited.
					 */
					$entry_meta = apply_filters( 'gform_entry_meta_conditional_logic_confirmations', $entry_meta, $form, $confirmation_id );

					?>

					<script type="text/javascript">

						var confirmation = <?php echo $confirmation ? json_encode( $confirmation ) : 'new ConfirmationObj()' ?>;
						var form = <?php echo json_encode( $form ); ?>;
						var entry_meta = <?php echo GFCommon::json_encode( $entry_meta ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>;

						gform.addFilter( 'gform_merge_tags', 'MaybeAddSaveMergeTags' );

						function MaybeAddSaveMergeTags( mergeTags, elementId, hideAllFields, excludeFieldTypes, isPrepop, option ) {
							var event = confirmation.event;
							if ( event === 'form_saved' || event === 'form_save_email_sent' ) {
								mergeTags[ 'other' ].tags.push( {
									tag: '{save_link}',
									label: <?php echo json_encode( __( 'Save &amp; Continue Link', 'gravityforms' ) ) ?> } );
								mergeTags[ 'other' ].tags.push( {
									tag: '{save_token}',
									label: <?php echo json_encode( __( 'Save &amp; Continue Token', 'gravityforms' ) ) ?> } );
							}

							if ( event === 'form_saved' ) {
								mergeTags[ 'other' ].tags.push( {
									tag: '{save_email_input}',
									label: <?php echo json_encode( __( 'Save &amp; Continue Email Input', 'gravityforms' ) ) ?> } );
							}

							return mergeTags;
						}

						jQuery( function() {
							if ( confirmation.event === 'form_saved' || confirmation.event === 'form_save_email_sent'  ) {
								jQuery( '#type1, #type2' ).attr( 'disabled', true );
							}
						} );

						<?php if ( ! rgar( $confirmation, 'isDefault' ) ) : ?>
						jQuery( function() {
							ToggleConditionalLogic( true, 'confirmation' );
						} );
						<?php endif; ?>

						<?php GFFormSettings::output_field_scripts() ?>

					</script>
					<?php

				},
				'after_fields'   => function() use ( &$confirmation_id ) {
					printf( '<input type="hidden" id="confirmation_id" name="confirmation_id" value="%s" />', esc_attr( $confirmation_id ) );
				}
			)
		);

		self::set_settings_renderer( $renderer );

		if ( self::is_save_redirect( 'cid' ) ) {
			self::get_settings_renderer()->set_save_message_after_redirect();
		}

		// Process save callback.
		if ( self::get_settings_renderer()->is_save_postback() ) {
			self::get_settings_renderer()->process_postback();
			self::redirect_after_valid_save( 'cid' );
		}

	}

	/**
	 * Gets the current instance of Settings handling settings rendering.
	 *
	 * @since 2.5
	 *
	 * @return false|Gravity_Forms\Gravity_Forms\Settings\Settings
	 */
	private static function get_settings_renderer() {

		return self::$_settings_renderer;

	}

	/**
	 * Sets the current instance of Settings handling settings rendering.
	 *
	 * @since 2.5
	 *
	 * @param \Gravity_Forms\Gravity_Forms\Settings\Settings $renderer Settings renderer.
	 *
	 * @return bool|WP_Error
	 */
	private static function set_settings_renderer( $renderer ) {

		// Ensure renderer is an instance of Settings
		if ( ! is_a( $renderer, 'Gravity_Forms\Gravity_Forms\Settings\Settings' ) ) {
			return new WP_Error( 'Renderer must be an instance of Gravity_Forms\Gravity_Forms\Settings\Settings.' );
		}

		self::$_settings_renderer = $renderer;

		return true;

	}






	// # VALIDATION METHODS --------------------------------------------------------------------------------------------

	/**
	 * Checks if a confirmation name is unique.
	 *
	 * @since  2.5
	 *
	 * @param string $name          The confirmation name to check for.
	 * @param array  $confirmations The confirmations to check through.
	 *
	 * @return bool True if unique. False otherwise.
	 */
	private static function is_unique_name( $name, $confirmations ) {

		foreach ( $confirmations as $confirmation ) {
			if ( strtolower( rgar( $confirmation, 'name' ) ) == strtolower( $name ) ) {
				return false;
			}
		}

		return true;

	}






	// # HELPER METHODS ------------------------------------------------------------------------------------------------

	/**
	 * Checks the text for merge tags as attribute values.
	 *
	 * @since  Unknown
	 *
	 * @param string $text The confirmation text to check.
	 *
	 * @return bool
	 */
	private static function confirmation_looks_unsafe( $text ) {

		$unsafe = false;
		preg_match_all( self::$unsafe_regex, $text, $matches, PREG_SET_ORDER );
		if ( is_array( $matches ) && count( $matches ) > 0 ) {
			foreach ( $matches as $match ) {
				if ( strtolower( $match[1] ) !== 'merge_tag' ) {
					$unsafe = true;
				}
			}
		}

		return $unsafe;

	}

	/**
	 * Alias for GFCommon::maybe_wp_kses().
	 *
	 * @since  2.5
	 *
	 * @param string $html              The HTML markup to sanitize.
	 * @param string $allowed_html      The allowed HTML content. Defaults to 'post'.
	 * @param array  $allowed_protocols Allowed protocols. Defaults to empty array.
	 *
	 * @return string The sanitized HTML markup.
	 */
	private static function maybe_wp_kses( $html, $allowed_html = 'post', $allowed_protocols = array() ) {

		if ( ! current_user_can( 'unfiltered_html' ) ) {
			$html = self::remove_unsafe_merge_tags( $html );
		}

		return GFCommon::maybe_wp_kses( $html, $allowed_html, $allowed_protocols );

	}

	/**
	 * Removes merge tags used as HTML attributes.
	 *
	 * @since  2.5
	 *
	 * @param string $text The confirmation text to check.
	 *
	 * @return bool
	 */
	private static function remove_unsafe_merge_tags( $text ) {

		preg_match_all( self::$unsafe_regex, $text, $matches, PREG_SET_ORDER );

		if ( is_array( $matches ) && count( $matches ) > 0 ) {
			foreach ( $matches as $match ) {
				// Ignore conditional shortcodes.
				if ( strtolower( $match[1] ) !== 'merge_tag' ) {
					// Remove the merge tag.
					$text = str_replace( $match[0], $match[1] . '=""', $text );
				}
			}
		}

		return $text;

	}

	/**
	 * Output the duplicate conditional logic confirmation notice.
	 *
	 * @since  2.6
	 */
	public static function output_duplicate_confirmation_notice() {
		echo '<div class="gform-alert gform-alert--notice" data-js="gform-alert">
				<span class="gform-alert__icon gform-icon gform-icon--circle-notice" aria-hidden="true"></span>
  				<div class="gform-alert__message-wrap">
    				<p class="gform-alert__message">In order to avoid conflicts with other confirmations on this form, please ensure these conditional logic rules are unique.</p>
  				</div>
			</div>';
	}

}

// Include WP_List_Table.
require_once( ABSPATH . '/wp-admin/includes/class-wp-list-table.php' );

/**
 * Class GFConfirmationTable
 *    Handles the creation of a list table for displaying the confirmations listing.
 *
 * @since Unknown
 *
 * @param array $form The form to display the confirmation listing for.
 */
class GFConfirmationTable extends WP_List_Table {

	/**
	 * @since  Unknown
	 * @access public
	 *
	 * @var array The Form Object to get confirmations from.
	 */
	public $form;

	/**
	 * GFConfirmationTable constructor.
	 *
	 * @since  Unknown
	 *
	 * @param array $form The Form Object to display the confirmation listing for.
	 */
	public function __construct( $form ) {

		$this->form = $form;

		$this->_column_headers = array(
			array(
				'cb'      => '',
				'name'    => __( 'Name', 'gravityforms' ),
				'type'    => __( 'Type', 'gravityforms' ),
				'content' => __( 'Content', 'gravityforms' ),
			),
			array(),
			array( 'name' => array( 'name', false ) ),
			'name',
		);

		parent::__construct();
	}

	/**
	 * Prepares the confirmation items.
	 *
	 * @since  Unknown
	 */
	public function prepare_items() {

		$this->items = $this->form['confirmations'];

		switch ( rgget( 'orderby' ) ) {

			case 'name':

				// Sort confirmations alphabetically.
				usort( $this->items, array( $this, 'sort_confirmations' ) );

				// Reverse sort.
				if ( 'desc' === rgget( 'order' ) ) {
					$this->items = array_reverse( $this->items );
				}

				break;

			default:
				break;

		}

	}

	/**
	 * Sort confirmations alphabetically.
	 *
	 * @since  2.4
	 *
	 * @param array $a First confirmation to compare.
	 * @param array $b Second confirmation to compare.
	 *
	 * @return int
	 */
	public function sort_confirmations( $a = array(), $b = array() ) {

		return strcasecmp( $a['name'], $b['name'] );

	}

	/**
	 * Displays the list table.
	 *
	 * @since  Unknown
	 */
	public function display() {

		$singular = rgar( $this->_args, 'singular' );

		$this->display_tablenav( 'top' );
		?>
		<table class="wp-list-table <?php echo esc_attr( implode( ' ', $this->get_table_classes() ) ); ?>" cellspacing="0">
			<thead>
			<tr>
				<?php $this->print_column_headers(); ?>
			</tr>
			</thead>

			<tfoot>
			<tr>
				<?php $this->print_column_headers( false ); ?>
			</tr>
			</tfoot>

			<tbody id="the-list"<?php if ( $singular ) {
				echo " class='list:$singular'"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			} ?>>

			<?php $this->display_rows_or_placeholder(); ?>

			</tbody>
		</table>

		<?php

	}

	/**
	 * Displays a single list table row.
	 *
	 * @since  Unknown
	 *
	 * @param array $item The row item.
	 */
	public function single_row( $item ) {

		static $row_class = '';
		$row_class = ( $row_class == '' ? ' class="alternate"' : '' );

		printf(
			'<tr id="confirmation-%s" %s>',
			esc_attr( $item['id'] ),
			$row_class // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		);
		$this->single_row_columns( $item );
		echo '</tr>';

	}

	/**
	 * Gets the list table column headers.
	 *
	 * @since  Unknown
	 *
	 * @return string The primary column header.
	 */
	public function get_columns() {

		return $this->_column_headers[0];

	}

	/**
	 * Gets the column content.
	 *
	 * @since  Unknown
	 *
	 * @param array $item The column item to process.
	 *
	 * @return string
	 */
	public function column_content( $item ) {

		return self::get_column_content( $item );

	}

	/**
	 * Sets the default column data.
	 *
	 * @since  Unknown
	 *
	 * @param array  $item   The column item.
	 * @param string $column The column name.
	 */
	public function column_default( $item, $column ) {

		echo rgar( $item, $column ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

	}

	/**
	 * Sets the column type.
	 *
	 * @since  Unknown
	 *
	 * @param object $item The column item.
	 *
	 * @return string The column type.
	 */
	public function column_type( $item ) {

		return self::get_column_type( $item );

	}

	/**
	 * Handles the activation/deactivation button on confirmation list table items.
	 *
	 * @since  Unknown
	 *
	 * @param array $item The list table item.
	 */
	public function column_cb( $item ) {

		if ( isset( $item['isDefault'] ) && $item['isDefault'] ) {
			return;
		}

		$active = rgar( $item, 'isActive' ) !== false;

		if ( $active ) {
			$class = 'gform-status--active';
			$text  = esc_html__( 'Active', 'gravityforms' );
		} else {
			$class = 'gform-status--inactive';
			$text  = esc_html__( 'Inactive', 'gravityforms' );
		}
		?>
		<button
			type="button"
			class="gform-status-indicator gform-status-indicator--size-sm gform-status-indicator--theme-cosmos <?php echo esc_attr( $class ); ?>"
			onclick="ToggleActive( this, '<?php echo esc_js( $item['id'] ); ?>' );"
			onkeypress="ToggleActive( this, '<?php echo esc_js( $item['id'] ); ?>' );"
		>
			<span class="gform-status-indicator-status gform-typography--weight-medium gform-typography--size-text-xs">
				<?php echo esc_html( $text ); ?>
			</span>
		</button>
		<?php

	}

	/**
	 * Displays the available confirmation list item actions.
	 *
	 * @since  Unknown
	 *
	 * @param array $item The list table column item.
	 */
	public function column_name( $item ) {

		$edit_url        = add_query_arg( array( 'cid' => $item['id'] ) );
		$duplicate_url   = add_query_arg( array( 'cid' => 0, 'duplicatedcid' => $item['id'] ) );
		$confirm_message = esc_js(
			__( 'WARNING: You are about to delete this confirmation.', 'gravityforms' ) .
			' ' .
			__( "'Cancel' to stop, 'OK' to delete.", 'gravityforms' )
		);

		$actions       = apply_filters(
			'gform_confirmation_actions', array(
				'edit'      => '<a href="' . esc_url( $edit_url ) . '">' . __( 'Edit', 'gravityforms' ) . '</a>',
				'duplicate' => '<a href="' . esc_url( $duplicate_url ) . '">' . __( 'Duplicate', 'gravityforms' ) . '</a>',
				'delete'    => '<a class="submitdelete" onclick="javascript: if(confirm(\'' . $confirm_message . '\')){ DeleteConfirmation(\'' . esc_js( $item['id'] ) . '\'); }" onkeypress="javascript: if(confirm(\'' . $confirm_message . '\')){ DeleteConfirmation(\'' . esc_js( $item['id'] ) . '\'); }" style="cursor:pointer;">' . esc_html__( 'Delete', 'gravityforms' ) . '</a>',
			)
		);

		if ( isset( $item['isDefault'] ) && $item['isDefault'] ) {
			unset( $actions['delete'] );
		}

		$aria_label = sprintf(
			/* translators: %s: Confirmation name */
			__( '%s (Edit)', 'gravityforms' ),
			$item['name']
		);
		?>

		<a href="<?php echo esc_url( $edit_url ); ?>" aria-label="<?php echo esc_attr( $aria_label ); ?>"><strong><?php echo esc_html( rgar( $item, 'name' ) ); ?></strong></a>
		<div class="row-actions">

			<?php

			if ( is_array( $actions ) && ! empty( $actions ) ) {
				$keys     = array_keys( $actions );
				$last_key = array_pop( $keys );
				foreach ( $actions as $key => $html ) {
					$divider = $key == $last_key ? '' : ' | ';
					?>
					<span class="<?php echo esc_attr( $key ); ?>">
                        <?php echo $html . $divider; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
                    </span>
					<?php

				}
			}
			?>

		</div>

		<?php

	}

	/**
	 * Displays the confirmations list item column content.
	 *
	 * @since  Unknown
	 *
	 * @param array $item The list item.
	 *
	 * @return string The HTML markup for the column content.
	 */
	public static function get_column_content( $item ) {

		switch ( rgar( $item, 'type' ) ) {

			case 'message':
				return '<a class="limit-text">' . wp_strip_all_tags( $item['message'] ) . '</a>';

			case 'page':

				$page = get_post( $item['pageId'] );
				if ( empty( $page ) ) {
					return __( '<em>This page does not exist.</em>', 'gravityforms' );
				}

				return '<a href="' . get_permalink( $item['pageId'] ) . '">' . esc_html( $page->post_title ) . '</a>';

			case 'redirect':
				$url_pieces    = parse_url( $item['url'] );
				$url_connector = rgar( $url_pieces, 'query' ) ? '&' : '?';
				$url           = rgar( $item, 'queryString' ) ? "{$item['url']}{$url_connector}{$item['queryString']}" : $item['url'];
				$url           = esc_url( $url );

				return '<a class="limit-text">' . $url . '</a>';
		}

		return '';

	}

	/**
	 * Gets the column type.
	 *
	 * @since  Unknown
	 *
	 * @param array $item The column item.
	 *
	 * @return string The column item type. If none found, empty string. Escaped.
	 */
	public static function get_column_type( $item ) {

		switch ( rgar( $item, 'type' ) ) {
			case 'message':
				return __( 'Text', 'gravityforms' );

			case 'page':
				return __( 'Page', 'gravityforms' );

			case 'redirect':
				return __( 'Redirect', 'gravityforms' );
		}

		return '';

	}

	/**
	 * Extra controls to be displayed between bulk actions and pagination
	 *
	 * @since 2.5
	 *
	 * @param string $which
	 */
	protected function extra_tablenav( $which ) {

		if ( $which !== 'top' ) {
			return;
		}

		printf(
			'<div class="alignright"><a href="%s" class="button">%s</a></div>',
			esc_url( add_query_arg( array( 'cid' => 0 ) ) ),
			esc_html__( 'Add New', 'gravityforms' )
		);

	}

}
