<?php
/**
 * The Forminator_Integration class.
 *
 * @package Forminator
 */

/**
 * Class Forminator_Integration
 * Extend this class to create new forminator addon / integrations
 * Any change(s) to this file is subject to:
 * - Properly Written DocBlock! (what is this, why is that, how to be like those, etc, as long as you want!)
 * - Properly Written Changelog!
 * - Properly Written Sample Usage on @see Forminator_Integration_Simple
 *
 * @since 1.1
 */
abstract class Forminator_Integration implements Forminator_Integration_Interface {
	/**
	 * Multi Id
	 *
	 * @var mixed
	 */
	public $multi_id;

	/**
	 * Slug will be used as identifier throughout forminator
	 * make sure its unique, else it won't be loaded
	 * or will carelessly override other addon with same slug
	 *
	 * @since 1.1
	 * @var string
	 */
	protected $_slug;

	/**
	 * Version number of the Add-On
	 * It will save on the wp options
	 * And if user updated the addon, it will try to call @see Forminator_Integration::version_changed()
	 *
	 * @since 1.1
	 * @var string
	 */
	protected $_version;

	/**
	 * Minimum version of Forminator, that the addon will work correctly
	 *
	 * @since 1.1
	 * @var string
	 */
	protected $_min_forminator_version;

	/**
	 * URL info to of the Integration website / doc / info
	 *
	 * @since  1.1
	 * @var string
	 */
	protected $_url = 'http://wpmudev.com';

	/**
	 * Title of the addon will be used on add on list and add on setting
	 *
	 * @since  1.1
	 * @var string
	 */
	protected $_title;

	/**
	 * Short version of the addon title, will be used at small places for the addon to be displayed
	 * its optional, when its omitted it will use $_title
	 * make sure its less then 10 chars to displayed correctly, we will auto truncate it if its more than 10 chars
	 *
	 * @since  1.1
	 * @var string
	 */
	protected $_short_title;

	/**
	 * Integration Brief Desription, of what it does
	 *
	 * @since  1.1
	 * @var string
	 */
	protected $_description = '';

	/**
	 * Integration promotion description
	 *
	 * @since  1.7.1
	 * @var string
	 */
	protected $_promotion = '';

	/**
	 * Integration documentation link
	 *
	 * @since  1.7.1
	 * @var string
	 */
	protected $_documentation = '';

	/**
	 * Flag that an addon can be activated, that auto set by abstract
	 *
	 * @since  1.1
	 * @var bool
	 */
	private $is_activable = null;

	/**
	 * Semaphore non redundant hooks for admin side
	 *
	 * @since  1.1
	 * @var bool
	 */
	private $_is_admin_hooked = false;

	/**
	 * Semaphore non redundant hooks for global hooks
	 *
	 * @since  1.1
	 * @var bool
	 */
	private $_is_global_hooked = false;

	/**
	 * Add-on order position
	 *
	 * @since 1.7.1
	 * @var int
	 */
	protected $_position = 1;


	/*********************************** Errors Messages ********************************/
	/**
	 * These error message can be set on the start of addon as default, or dynamically set on each related process
	 *
	 * @example $_activation_error_message can be dynamically set on activate() to display custom error messages when activatation failed
	 *          Default is empty, which will be replaced by forminator default messages
	 */
	/**
	 * Error Message on activation
	 *
	 * @since  1.1
	 * @var string
	 */
	protected $_activation_error_message = '';

	/**
	 * Error Message on deactivation
	 *
	 * @since  1.1
	 * @var string
	 */
	protected $_deactivation_error_message = '';

	/**
	 * Error Message on update general settings
	 *
	 * @since  1.1
	 * @var string
	 */
	protected $_update_settings_error_message = '';
	/*********************************** END Errors Messages ********************************/

	/**
	 * Form Hooks Instances with `module_id` as key
	 *
	 * @since  1.1
	 * @var Forminator_Integration_Hooks[]|array
	 */
	protected $addon_hooks_instances = array();

	/**
	 * Id of multiple provider accounts
	 *
	 * @var string
	 */
	public $multi_global_id;

	/**
	 * Global Id for new integrations
	 *
	 * @var string
	 */
	public $global_id_for_new_integrations;

	/**
	 * Support multiple accounts
	 *
	 * @var bool
	 */
	public $is_multi_global = false;

	/**
	 * Wizard steps
	 *
	 * @var array
	 */
	private $steps = array();

	/**
	 * Nonce option name
	 *
	 * @var string
	 */
	const NONCE_OPTION_NAME = 'forminator_custom_nonce';

	const DOMAIN       = 'https://wpmudev.com';
	const REDIRECT_URI = 'https://wpmudev.com/api/forminator/v1/provider';


	/**
	 * Get addon instance
	 *
	 * @return self
	 */
	public static function get_instance() {
		if ( is_null( static::$instance ) ) {
			static::$instance = new static();
		}

		return static::$instance;
	}

	/**
	 * Get this addon slug
	 *
	 * @see    Forminator_Integration::$_slug
	 *
	 * its behave like `IDENTIFIER`, used for :
	 * - easly calling this instance with @see forminator_get_addon(`slug`)
	 * - avoid collision, registered as FIFO of @see do_action()
	 *
	 * Shouldn't be implemented / overridden on addons
	 *
	 * @since  1.1
	 * @return string
	 */
	final public function get_slug() {
		return $this->_slug;
	}

	/**
	 * Get this addon version
	 *
	 * @since  1.1
	 * @return string
	 */
	final public function get_version() {
		return $this->_version;
	}

	/**
	 * Get this addon requirement of installed forminator version
	 *
	 * @since  1.1
	 * @return string
	 */
	final public function get_min_forminator_version() {
		return $this->_min_forminator_version;
	}

	/**
	 * Get external url of addon website / info / doc
	 *
	 * Can be overridden to offer dynamic external url display
	 *
	 * @since  1.1
	 * @return string
	 */
	public function get_url() {
		return $this->_url;
	}

	/**
	 * Get redirect URL
	 *
	 * @param string $provider Provider.
	 * @param string $action Action.
	 * @param array  $params Params.
	 * @return string
	 */
	public static function redirect_uri( $provider, $action, $params ) {
		$params = wp_parse_args(
			$params,
			array(
				'action'   => $action,
				'provider' => $provider,
			)
		);

		return add_query_arg( $params, self::REDIRECT_URI );
	}

	/**
	 * Validates request callback from WPMU DEV
	 *
	 * @param string $provider Provider.
	 * @return bool
	 */
	public static function validate_callback_request( $provider ) {
		$wpnonce        = filter_input( INPUT_GET, 'wpnonce', FILTER_SANITIZE_SPECIAL_CHARS );
		$domain         = filter_input( INPUT_GET, 'domain', FILTER_VALIDATE_URL );
		$provider_input = filter_input( INPUT_GET, 'provider', FILTER_SANITIZE_SPECIAL_CHARS );

		return ! empty( $wpnonce ) && self::verify_nonce( $wpnonce )
			&& self::DOMAIN === $domain && $provider === $provider_input;
	}

	/**
	 * Helper function to validate nonce value.
	 *
	 * @param string $nonce Nonce.
	 *
	 * @return bool
	 */
	private static function verify_nonce( $nonce ) {
		return self::get_nonce_value() === $nonce;
	}

	/**
	 * Helper function to generate unique none changeable nonce.
	 *
	 * @return string The unique nonce value.
	 */
	public static function get_nonce_value() {
		$nonce = get_option( self::NONCE_OPTION_NAME );

		if ( empty( $nonce ) ) {
			/**
			 * Generate the nonce value only once to avoid error response
			 * when retrieving access token.
			 */
			$nonce = wp_generate_password( 40, false, false );

			update_option( self::NONCE_OPTION_NAME, $nonce );
		}

		return $nonce;
	}

	/**
	 * Get external title of addon
	 *
	 * @since  1.1
	 * @return string
	 */
	final public function get_title() {
		return $this->_title;
	}


	/**
	 * Get short title for small width placeholder
	 *
	 * @since  1.1
	 * @return string
	 */
	final public function get_short_title() {
		if ( empty( $this->_short_title ) ) {
			$this->_short_title = $this->_title;
		}

		return substr( $this->_short_title, 0, self::SHORT_TITLE_MAX_LENGTH );
	}

	/**
	 * Get Image
	 *
	 * @since  1.1
	 * @return string
	 */
	public function get_image() {
		return $this->assets_path() . 'image.png';
	}

	/**
	 * Get Retina image
	 *
	 * @since  1.1
	 * @return string
	 */
	public function get_image_x2() {
		return $this->assets_path() . 'image@2x.png';
	}

	/**
	 * Get banner image
	 *
	 * @since  1.7.1
	 * @return string
	 */
	public function get_banner() {
		return $this->assets_path() . 'banner.png';
	}

	/**
	 * Get retina banner image
	 *
	 * @since  1.7.1
	 * @return string
	 */
	public function get_banner_x2() {
		return $this->assets_path() . 'banner@2x.png';
	}

	/**
	 * Get icon
	 *
	 * @since  1.1
	 * @return string
	 */
	public function get_icon() {
		return $this->assets_path() . 'icon.png';
	}

	/**
	 * Get Retina icon
	 *
	 * @since  1.1
	 * @return string
	 */
	public function get_icon_x2() {
		return $this->assets_path() . 'icon@2x.png';
	}

	/**
	 * Get path to assets folder
	 *
	 * @return string
	 */
	public function assets_path(): string {
		return $this->addon_path() . 'assets/';
	}
	/**
	 * Get path to assets folder
	 *
	 * @return string
	 */
	public function addon_path(): string {
		return trailingslashit( forminator_plugin_url() . 'addons/pro/' . $this->get_slug() );
	}

	/**
	 * Get Description
	 *
	 * @since  1.1
	 * @return string
	 */
	public function get_description() {
		return $this->_description;
	}

	/**
	 * Get promotion
	 *
	 * @since  1.7.1
	 * @return string
	 */
	public function get_promotion() {
		return __( $this->_promotion, 'forminator' ); // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
	}

	/**
	 * Get documentation link
	 *
	 * @since  1.7.1
	 * @return string
	 */
	public function get_documentation() {
		return $this->_documentation;
	}

	/**
	 * Get add-on position
	 *
	 * @since 1.7.1
	 * @return int
	 */
	public function get_position() {
		return $this->_position;
	}

	/**
	 * WP options name that holds settings of addon
	 *
	 * @since  1.1
	 * @return string
	 */
	final public function get_settings_options_name() {
		$addon_slug            = $this->get_slug();
		$addon                 = $this;
		$settings_options_name = 'forminator_addon_' . $this->get_slug() . '_settings';

		/**
		 * Filter wp options name for saving addon settings
		 *
		 * @since 1.1
		 *
		 * @param string                    $settings_options_name
		 * @param Forminator_Integration $addon Integration instance.
		 */
		$settings_options_name = apply_filters( 'forminator_addon_' . $addon_slug . '_settings_options_name', $settings_options_name, $addon );

		return $settings_options_name;
	}

	/**
	 * WP options name that holds current version of addon
	 *
	 * @since  1.1
	 * @return string
	 */
	final public function get_version_options_name() {
		$addon_slug           = $this->get_slug();
		$addon                = $this;
		$version_options_name = 'forminator_addon_' . $this->get_slug() . '_version';

		/**
		 * Filter wp options name for saving addon version
		 *
		 * @since 1.1
		 *
		 * @param string                    $version_options_name
		 * @param Forminator_Integration $addon Integration instance.
		 */
		$version_options_name = apply_filters( 'forminator_addon_' . $addon_slug . '_version_options_name', $version_options_name, $addon );

		return $version_options_name;
	}

	/**
	 * Get multi global ids with identifiers.
	 *
	 * @return array
	 */
	final public function get_multi_global_ids() {
		$all_settings     = $this->get_all_settings_values();
		$multi_global_ids = array();
		foreach ( $all_settings as $key => $settings ) {
			$multi_global_ids[ $key ] = ! empty( $settings['identifier'] ) ? $settings['identifier'] : '';
		}

		return $multi_global_ids;
	}

	/**
	 * Transform addon instance into array
	 *
	 * @since  1.1
	 * @return array
	 */
	final public function to_array() {
		$to_array = array(
			'slug'                   => $this->get_slug(),
			'is_pro'                 => $this->is_pro(),
			'icon'                   => $this->get_icon(),
			'icon_x2'                => $this->get_icon_x2(),
			'image'                  => $this->get_image(),
			'image_x2'               => $this->get_image_x2(),
			'banner'                 => $this->get_banner(),
			'banner_x2'              => $this->get_banner_x2(),
			'short_title'            => $this->get_short_title(),
			'title'                  => $this->get_title(),
			'url'                    => $this->get_url(),
			'description'            => $this->get_description(),
			'promotion'              => $this->get_promotion(),
			'documentation'          => $this->get_documentation(),
			'version'                => $this->get_version(),
			'min_forminator_version' => $this->get_min_forminator_version(),
			'setting_options_name'   => $this->get_settings_options_name(),
			'version_option_name'    => $this->get_version_options_name(),
			'is_activable'           => $this->is_activable(),
			'is_settings_available'  => $this->is_settings_available(),
			'is_connected'           => $this->is_connected(),
			'is_multi_global'        => $this->is_multi_global,
			'new_global_id'          => $this->global_id_for_new_integrations,
			'position'               => $this->get_position(),
		);

		$addon_slug = $this->get_slug();
		$addon      = $this;

		/**
		 * Filter array of addon properties
		 *
		 * @since 1.1
		 *
		 * @param array                     $to_array array of addonn properties.
		 * @param int                       $form_id  Form ID.
		 * @param Forminator_Integration $addon    Integration Instance.
		 */
		$to_array = apply_filters( 'forminator_addon_' . $addon_slug . '_to_array', $to_array, $addon );

		return $to_array;
	}

	/**
	 * Transform addon instance into array with form relation
	 *
	 * @since  1.1
	 * @since  1.2 generate new multi_id to allow reference on wizard
	 *
	 * @param int $form_id Form Id.
	 *
	 * @return array
	 */
	final public function to_array_with_form( $form_id ) {
		$to_array                               = $this->to_array();
		$is_allow_multi_on_form                 = $this->is_allow_multi_on_form();
		$to_array['is_form_connected']          = $this->is_module_connected( $form_id );
		$to_array['is_form_settings_available'] = $this->is_form_settings_available( $form_id );
		$to_array['is_allow_multi_on_form']     = $is_allow_multi_on_form;

		$to_array['multi_id'] = $this->generate_form_settings_multi_id( $form_id );

		// handle multiple form setting.
		if ( $is_allow_multi_on_form ) {
			$to_array['multi_ids'] = $this->get_form_settings_multi_ids( $form_id );
		}

		$to_array_with_form = $to_array;
		$addon_slug         = $this->get_slug();
		$addon              = $this;

		/**
		 * Filter array of addon properties
		 *
		 * Including relation with form_id
		 *
		 * @since 1.1
		 *
		 * @param array                     $to_array_with_form array of addonn properties.
		 * @param int                       $form_id            Form ID.
		 * @param Forminator_Integration $addon              Integration instance.
		 */
		$to_array_with_form = apply_filters( 'forminator_addon_' . $addon_slug . '_to_array_with_form', $to_array_with_form, $form_id, $addon );

		return $to_array_with_form;
	}


	/**
	 * Check if Plugin Is Pro
	 *
	 * @see    forminator_get_pro_addon_list()
	 * @since  1.1
	 * @return bool
	 */
	final public function is_pro() {
		if ( in_array( $this->_slug, array_keys( forminator_get_pro_addon_list() ), true ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Get activable status
	 *
	 * @since  1.1
	 * @return bool
	 */
	final public function is_activable() {
		if ( is_null( $this->is_activable ) ) {
			$this->is_activable = $this->check_is_activable();
		}

		return $this->is_activable;
	}

	/**
	 * Actually check requirement of an addon that can be activated
	 * Override this method if you have another logic for checking activable_plugins
	 *
	 * @since  1.1
	 * @return bool
	 */
	public function check_is_activable() {
		// Check supported forminator version.
		if ( empty( $this->_min_forminator_version ) ) {
			forminator_addon_maybe_log( __METHOD__, $this->get_slug(), 'empty _min_forminator_version' );

			return false;
		}

		$is_forminator_version_supported = version_compare( FORMINATOR_VERSION, $this->_min_forminator_version, '>=' );
		if ( ! $is_forminator_version_supported ) {
			forminator_addon_maybe_log( __METHOD__, $this->get_slug(), $this->_min_forminator_version, FORMINATOR_VERSION, 'Forminator Version not supported' );

			// un-strict version compare of forminator, override if needed.
			return true;
		}

		return true;
	}

	/**
	 * Override or implement this method to add action when user deactivate addon
	 *
	 * @example DROP table
	 * return true when succes
	 * return false on failure, forminator will stop deactivate process
	 *
	 * @since   1.1
	 * @return bool
	 */
	public function deactivate() {
		return true;
	}


	/**
	 * Override or implement this method to add action when user activate addon
	 *
	 * @example CREATE table
	 * return true when succes
	 * return false on failure, forminator will stop activation process
	 *
	 * @since   1.1
	 * @return bool
	 */
	public function activate() {
		return true;
	}

	/**
	 * Override or implement this method to add action when version of addon changed
	 *
	 * @example CREATE table
	 * return true when succes
	 * return false on failure, forminator will stop activation process
	 *
	 * @since   1.1
	 *
	 * @param string $old_version Old version.
	 * @param string $new_version New version.
	 *
	 * @return bool
	 */
	public function version_changed( $old_version, $new_version ) {
		return true;
	}

	/**
	 * Check if addon version has changed
	 *
	 * @since   1.1
	 * @return bool
	 */
	final public function is_version_changed() {
		$installed_version = $this->get_installed_version();
		// new installed.
		if ( false === $installed_version ) {
			return false;
		}
		$version_is_changed = version_compare( $this->_version, $installed_version, '!=' );
		if ( $version_is_changed ) {
			return true;
		}

		return false;
	}

	/**
	 * Get currently installed addon version
	 * retrieved from wp options
	 *
	 * @since 1.1
	 * @return string|bool
	 */
	final public function get_installed_version() {
		return get_option( $this->get_version_options_name(), false );
	}

	/**
	 * Get error message on activation
	 *
	 * @since 1.1
	 * @return string
	 */
	public function get_activation_error_message() {
		if ( ! empty( $this->_activation_error_message ) ) {
			return $this->_activation_error_message;
		}

		/* translators: integration title */
		return sprintf( esc_html__( 'Sorry but we failed to activate %s Integration, don\'t hesitate to contact us', 'forminator' ), $this->get_title() );
	}

	/**
	 * Get error message on deactivation
	 *
	 * @since 1.1
	 * @return string
	 */
	public function get_deactivation_error_message() {
		if ( ! empty( $this->_deactivation_error_message ) ) {
			return $this->_deactivation_error_message;
		}

		/* translators: integration title */
		return sprintf( esc_html__( 'Sorry but we failed to deactivate %s Integration, please try again', 'forminator' ), $this->get_title() );
	}

	/**
	 * Get error message on deactivation
	 *
	 * @since 1.1
	 * @return string
	 */
	public function get_update_settings_error_message() {
		if ( ! empty( $this->_update_settings_error_message ) ) {
			return $this->_update_settings_error_message;
		}

		return esc_html__( 'Sorry, we failed to update settings, please check your form and try again', 'forminator' );
	}


	/**
	 * Override this function to set wizardable settings
	 * Default its and empty array which is indicating that Integration doesnt have settings
	 *
	 * Its multi array, with numerical key, start with `0`
	 * Every step on wizard, will consist at least
	 * - `callback` : when application requesting wizard, Forminator will do `call_user_func` on this value, with these arguments
	 *      - `$submitted_data` : array of submitted data POST-ed by user
	 *      - `$form_id` : current form_id when called on `Form Settings` or 0 when called on Global Settings
	 * - `is_completed` : when application requesting wizard, will check if `Previous Step` `is_completed` by doing `call_user_func` on its value
	 *      this function should return `true` or `false`
	 *
	 * @since 1.1
	 * @return array
	 */
	public function settings_wizards() {
		// What this function return should looks like.
		$steps = array(
			// First Step / step `0`.
			array(
				/**
				 * Value of `callback` will be passed as first argument of `call_user_func`
				 * it does not have to be passed `$this` as reference such as `array( $this, 'sample_setting_first_step' )`,
				 * But its encouraged to passed `$this` because you will be benefited with $this class instance, in case you need to call private function or variable inside it
				 * you can make the value to be `some_function_name` as long `some_function_name` as long it will globally callable which will be checked with `is_callable`
				 * and should be able to accept 2 arguments $submitted_data, $form_id
				 *
				 * This callback should return an array @see Forminator_Integration::sample_setting_first_step()
				 *
				 * @see Forminator_Integration::sample_setting_first_step()
				 */
				'callback'     => array( $this, 'sample_setting_first_step' ),
				/**
				 * Before Forminator call the `calback`, Forminator will attempt to run `is_completed` from the previous step
				 * In this case, `is_completed` will be called when Forminator trying to display Settings Wizard for Second Step / step `1`
				 * Like `callback` its value will be passed as first argument of `call_user_func`
				 * and no arguments passed to this function when its called
				 *
				 * @see Forminator_Integration::sample_setting_first_step_is_completed()
				 */
				'is_completed' => array( $this, 'sample_setting_first_step_is_completed' ),
			),
		);

		return array();
	}

	/**
	 * Get Global Setting Wizard
	 * This function will process @see Forminator_Integration::settings_wizards()
	 * Please keep in mind this function will only be called when @see Forminator_Integration::is_settings_available() return `true`
	 * Which is doing check on @see Forminator_Integration::settings_wizards() requirements is passed
	 *
	 * @since 1.1
	 *
	 * @param array $submitted_data Submitted data.
	 * @param int   $form_id Form Id.
	 * @param int   $current_step Current step.
	 * @param int   $step Step.
	 *
	 * @return array|mixed
	 */
	final public function get_settings_wizard( $submitted_data, $form_id = 0, $current_step = 0, $step = 0 ) {

		$steps = $this->settings_wizards();
		if ( ! is_array( $steps ) ) {
			/* translators: integration title */
			return $this->get_empty_wizard( sprintf( esc_html__( 'No Settings available for %1$s', 'forminator' ), $this->get_title() ) );
		}
		$total_steps = count( $steps );
		if ( $total_steps < 1 ) {
			/* translators: integration title */
			return $this->get_empty_wizard( sprintf( esc_html__( 'No Settings available for %1$s', 'forminator' ), $this->get_title() ) );
		}

		if ( ! isset( $steps[ $step ] ) ) {
			// go to last step.
			$step = $total_steps - 1;

			return $this->get_settings_wizard( $submitted_data, $form_id, $current_step, $step );
		}

		if ( $step > 0 ) {
			if ( $current_step > 0 ) {
				// check previous step is complete.
				$prev_step              = $current_step - 1;
				$prev_step_is_completed = true;
				// only call `is_completed` when its defined.
				if ( isset( $steps[ $prev_step ]['is_completed'] ) && is_callable( $steps[ $prev_step ]['is_completed'] ) ) {
					$prev_step_is_completed = call_user_func( $steps[ $prev_step ]['is_completed'], $submitted_data );
				}
				if ( ! $prev_step_is_completed ) {
					--$step;

					return $this->get_settings_wizard( $submitted_data, $form_id, $current_step, $step );
				}
			}

			// only validation when it moves forward.
			if ( $step > $current_step ) {
				$current_step_result = $this->get_settings_wizard( $submitted_data, $form_id, $current_step, $current_step );
				if ( isset( $current_step_result['has_errors'] ) && true === $current_step_result['has_errors'] ) {
					return $current_step_result;
				} else {
					// set empty submitted data for next step.
					$submitted_data = array();
				}
			}
		}

		return $this->get_wizard( $steps, $submitted_data, $form_id, $step );
	}

	/**
	 * Get steps.
	 *
	 * @param int    $form_id Form id.
	 * @param string $module_type Module type.
	 * @return array
	 */
	final public function get_steps( $form_id, $module_type = 'form' ) {
		if ( ! empty( $this->steps[ $form_id ] ) ) {
			$steps = $this->steps[ $form_id ];
		} else {
			$settings_steps = array();
			if ( ! $this->is_connected() ) {
				$settings_steps = $this->settings_wizards();
			}
			$get_module_settings_steps = 'get_' . $module_type . '_settings_steps';

			$form_settings_steps = $this->$get_module_settings_steps( $form_id );

			$steps = array_merge( $settings_steps, $form_settings_steps );
		}

		return $steps;
	}

	/**
	 * Get Form Setting Wizard
	 * This function will process @see Forminator_Integration::settings_wizards()
	 * Please keep in mind this function will only be called when @see Forminator_Integration::is_settings_available() return `true`
	 * Which is doing check on @see Forminator_Integration::settings_wizards() requirements is passed
	 *
	 * @since 1.1
	 *
	 * @param array $submitted_data Submitted data.
	 * @param int   $form_id Form Id.
	 * @param int   $current_step Current step.
	 * @param int   $step Step.
	 *
	 * @return array|mixed
	 */
	final public function get_form_settings_wizard( $submitted_data, $form_id, $current_step = 0, $step = 0 ) {
		$steps = $this->get_steps( $form_id );
		if ( ! is_array( $steps ) ) {
			/* translators: integration title */
			return $this->get_empty_wizard( sprintf( esc_html__( 'No Form Settings available for %1$s', 'forminator' ), $this->get_title() ) );
		}
		$total_steps = count( $steps );
		if ( $total_steps < 1 ) {
			/* translators: integration title */
			return $this->get_empty_wizard( sprintf( esc_html__( 'No Form Settings available for %1$s', 'forminator' ), $this->get_title() ) );
		}

		if ( ! isset( $steps[ $step ] ) ) {
			// go to last step.
			$step = $total_steps - 1;

			return $this->get_form_settings_wizard( $submitted_data, $form_id, $current_step, $step );
		}

		if ( $step > 0 ) {
			if ( $current_step > 0 ) {
				// check previous step is complete.
				$prev_step              = $current_step - 1;
				$prev_step_is_completed = true;
				// only call `is_completed` when its defined.
				if ( isset( $steps[ $prev_step ]['is_completed'] ) && is_callable( $steps[ $prev_step ]['is_completed'] ) ) {
					$prev_step_is_completed = call_user_func( $steps[ $prev_step ]['is_completed'], $submitted_data );
				}
				if ( ! $prev_step_is_completed ) {
					--$step;

					return $this->get_form_settings_wizard( $submitted_data, $form_id, $current_step, $step );
				}
			}

			// only validation when it moves forward.
			if ( $step > $current_step ) {
				$current_step_result = $this->get_form_settings_wizard( $submitted_data, $form_id, $current_step, $current_step );
				if ( isset( $current_step_result['has_errors'] ) && true === $current_step_result['has_errors'] ) {
					return $current_step_result;
				} else {
					// set empty submitted data for next step, except preserved as reference.
					$preserved_keys = array(
						'multi_id',
					);
					foreach ( $submitted_data as $key => $value ) {
						if ( ! in_array( $key, $preserved_keys, true ) ) {
							unset( $submitted_data[ $key ] );
						}
					}
					// Reset steps cache - uses when wizard steps are conditional.
					unset( $this->steps[ $form_id ] );
					$steps = $this->get_steps( $form_id );
				}
			}
		}

		$form_settings_wizard = $this->get_wizard( $steps, $submitted_data, $form_id, $step );

		$addon_slug             = $this->get_slug();
		$addon                  = $this;
		$form_settings_instance = $this->get_addon_settings( $form_id, 'form' );

		/**
		 * Filter form settings wizard returned to client
		 *
		 * @since 1.1
		 *
		 * @param array                                        $form_settings_wizard
		 * @param array                                        $submitted_data         $_POST from client.
		 * @param int                                          $form_id                Form ID requested for.
		 * @param int                                          $current_step           Current Step displayed to user, start from 0.
		 * @param int                                          $step                   Step requested by client, start from 0.
		 * @param Forminator_Integration                    $addon                  Integration Instance.
		 * @param Forminator_Integration_Form_Settings|null $form_settings_instance Integration Form settings instancce, or null if unavailable.
		 */
		$form_settings_wizard = apply_filters(
			'forminator_addon_' . $addon_slug . '_form_settings_wizard',
			$form_settings_wizard,
			$submitted_data,
			$form_id,
			$current_step,
			$step,
			$addon,
			$form_settings_instance
		);

		return $form_settings_wizard;
	}

	/**
	 * Get form settings wizard steps
	 *
	 * @since 1.1
	 *
	 * @param int $form_id Form Id.
	 *
	 * @return array
	 */
	private function get_form_settings_steps( $form_id ) {
		$addon_slug             = $this->get_slug();
		$addon                  = $this;
		$form_settings_steps    = array();
		$form_settings_instance = $this->get_addon_settings( $form_id, 'form' );
		if ( ! is_null( $form_settings_instance ) && $form_settings_instance instanceof Forminator_Integration_Form_Settings ) {
			$form_settings_steps = $form_settings_instance->module_settings_wizards();
		}

		/**
		 * Filter form settings step that will be used for building wizard
		 *
		 * More detail : @param array $form_settings_steps
		 *
		 * @param int                                     $form_id current form id.
		 * @param Forminator_Integration_Form_Settings $addon   Integration instance.
		 * @param Forminator_Integration_Form_Settings|null Form settings of addon if available, or null otherwise
		 *@see Forminator_Integration_Form_Settings::module_settings_wizards()
		 *
		 * @since 1.1
		 */
		$form_settings_steps = apply_filters( 'forminator_addon_' . $addon_slug . '_form_settings_steps', $form_settings_steps, $form_id, $addon, $form_settings_instance );

		return $form_settings_steps;
	}

	/**
	 * Get settings multi id
	 *
	 * @since 1.1
	 *
	 * @param int $form_id Form Id.
	 *
	 * @return array
	 */
	private function get_form_settings_multi_ids( $form_id ) {
		$addon_slug             = $this->get_slug();
		$addon                  = $this;
		$multi_ids              = array();
		$form_settings_instance = $this->get_addon_settings( $form_id, 'form' );
		if ( $this->is_allow_multi_on_form() && ! is_null( $form_settings_instance ) && $form_settings_instance instanceof Forminator_Integration_Form_Settings ) {
			$multi_ids = $form_settings_instance->get_multi_ids();
		}

		/**
		 * Filter multi id of addon form settings
		 *
		 * @since 1.1
		 *
		 * @param array                                   $multi_ids
		 * @param Forminator_Integration_Form_Settings $addon                  Integration Instance.
		 * @param Forminator_Integration_Form_Settings $form_settings_instance Integration Form Settings Instance.
		 */
		$multi_ids = apply_filters( 'forminator_addon_' . $addon_slug . '_form_settings_multi_ids', $multi_ids, $addon, $form_settings_instance );

		return $multi_ids;
	}

	/**
	 * Get the requested wizard
	 *
	 * @since 1.1
	 * @since 1.2 Refactor setup default values, rename `hasBack` to `has_back`
	 *
	 * @param array $steps Steps.
	 * @param array $submitted_data Submitted data.
	 * @param int   $module_id Module id.
	 * @param int   $step Step.
	 *
	 * @return array|mixed
	 */
	private function get_wizard( $steps, $submitted_data, $module_id, $step = 0 ) {
		$total_steps = count( $steps );

		// validate callback, when it's empty or not callable, mark as no wizard.
		if ( ! isset( $steps[ $step ]['callback'] ) || ! is_callable( $steps[ $step ]['callback'] ) ) {
			/* translators: integration title */
			return $this->get_empty_wizard( sprintf( esc_html__( 'No Settings available for %1$s', 'forminator' ), $this->get_title() ) );
		}

		$wizard = call_user_func( $steps[ $step ]['callback'], $submitted_data, $module_id );
		// a wizard to be able to processed by our application need to has at least `html` which will be rendered or `redirect` which will be the url for redirect user to go to.
		if ( ! isset( $wizard['html'] ) && ! isset( $wizard['redirect'] ) ) {
			/* translators: integration title */
			return $this->get_empty_wizard( sprintf( esc_html__( 'No Settings available for %1$s', 'forminator' ), $this->get_title() ) );
		}
		$wizard['forminator_addon_current_step']  = $step;
		$wizard['forminator_addon_count_step']    = $total_steps;
		$wizard['forminator_addon_has_next_step'] = ( ( $step + 1 ) >= $total_steps ? false : true );
		$wizard['forminator_addon_has_prev_step'] = ( $step > 0 ? true : false );

		$wizard_default_values = array(
			'has_errors'   => false,
			'is_close'     => false,
			'notification' => array(),
			'size'         => 'small',
			'has_back'     => false,
			'is_poll'      => false,
		);

		foreach ( $wizard_default_values as $key => $wizard_default_value ) {
			if ( ! isset( $wizard[ $key ] ) ) {
				$wizard[ $key ] = $wizard_default_value;
			}
		}

		$addon_slug = $this->get_slug();
		$addon      = $this;

		/**
		 * Filter returned setting wizard to client
		 *
		 * @since 1.1
		 *
		 * @param array                     $wizard         current wizard.
		 * @param Forminator_Integration $addon          current addon instance.
		 * @param array                     $steps          defined settings / form settings steps.
		 * @param array                     $submitted_data $_POST.
		 * @param int                       $module_id      current form_id.
		 * @param int                       $step           requested step.
		 */
		$wizard = apply_filters( 'forminator_addon_' . $addon_slug . '_wizard', $wizard, $addon, $steps, $submitted_data, $module_id, $step );

		return $wizard;
	}

	/**
	 * Get Empty wizard markup
	 *
	 * @since   1.1
	 *
	 * @param string $notice Message.
	 *
	 * @return array
	 */
	protected function get_empty_wizard( $notice ) {
		$empty_wizard_html = Forminator_Admin::get_red_notice( esc_html( $notice ) );

		/**
		 * Filter html markup for empty wizard
		 *
		 * @since 1.1
		 *
		 * @param string $empty_wizard_html
		 * @param string $notice notice or message to be displayed on empty wizard.
		 */
		$empty_wizard_html = apply_filters( 'forminator_addon_empty_wizard_html', $empty_wizard_html, $notice );

		return array(
			'html'    => $empty_wizard_html,
			'buttons' => array(
				'close' => array(
					'action' => 'close',
					'data'   => array(),
					'markup' => self::get_button_markup( esc_html__( 'Close', 'forminator' ), 'sui-button-ghost forminator-addon-close' ),
				),
			),
		);
	}


	/**
	 * Override this function if addon need to do something with addon setting values
	 *
	 * @example transform, load from other storage ?
	 * called when rendering settings form
	 *
	 * @since   1.1
	 *
	 * @param array $values Settings.
	 *
	 * @return mixed
	 */
	public function before_get_settings_values( $values ) {
		return $values;
	}

	/**
	 * Get settings value
	 * its already hooked with
	 *
	 * @see     before_get_settings_values
	 *
	 * @since   1.1
	 * @return array
	 */
	final public function get_settings_values() {
		$all_values = $this->get_all_settings_values();

		if ( is_null( $this->multi_global_id ) ) {
			$values = $all_values ? reset( $all_values ) : array();
		} elseif ( isset( $all_values[ $this->multi_global_id ] ) ) {
			$values = $all_values[ $this->multi_global_id ];
		} else {
			$values = array();
		}

		$addon_slug = $this->get_slug();

		/**
		 * Filter retrieved saved addon's settings values from db
		 *
		 * @since 1.1
		 *
		 * @param mixed $values
		 */
		$values = apply_filters( 'forminator_addon_' . $addon_slug . '_get_settings_values', $values );

		return $values;
	}

	/**
	 * Get settings value for all accouns
	 *
	 * @return array
	 */
	final public function get_all_settings_values() {
		$all_values = get_option( $this->get_settings_options_name(), array() );

		if ( $all_values && is_array( $all_values ) && ! is_array( reset( $all_values ) ) ) {
			// Backward compatibility before having multiple aprovider accounts.
			$this->multi_global_id = uniqid( '', true );
			$all_values            = array( $this->multi_global_id => $all_values );
			update_option( $this->get_settings_options_name(), $all_values );
			// Update modules integration options.
			$types = array( 'form', 'poll', 'quiz' );
			global $wpdb;
			foreach ( $types as $type ) {
				$meta_key = 'forminator_addon_' . $this->get_slug() . '_' . $type . '_settings';
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				$results = $wpdb->get_results( $wpdb->prepare( "SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key = %s", $meta_key ), ARRAY_A );
				$results = wp_list_pluck( $results, 'meta_value', 'post_id' );
				foreach ( $results as $id => $value ) {
					update_post_meta( $id, $meta_key . '_' . $this->multi_global_id, maybe_unserialize( $value ) );
					delete_post_meta( $id, $meta_key );
				}
			}
			if ( 'hubspot' === $this->get_slug() ) {
				$option_name  = 'forminator-hubspot-token';
				$option_value = get_option( $option_name );
				if ( $option_value ) {
					update_option( $option_name . $this->multi_global_id, $option_value );
					delete_option( $option_name );
				}
			}
		}

		return $all_values;
	}

	/**
	 * Override this function if addon need to do something with addon setting values
	 *
	 * @example transform, save to other storage ?
	 * Called before settings values saved to db
	 *
	 * @since   1.1
	 *
	 * @param array $values Settings.
	 *
	 * @return mixed
	 */
	public function before_save_settings_values( $values ) {
		return $values;
	}

	/**
	 * Save settings value
	 * its already hooked with
	 *
	 * @see     before_save_settings_values
	 *
	 * @since   1.1
	 *
	 * @param array $values Settings.
	 */
	final public function save_settings_values( $values ) {
		$addon_slug = $this->get_slug();
		$all_values = $this->get_all_settings_values();

		/**
		 * Filter settings values of addon to be saved
		 *
		 * `$addon_slug` is current slug of addon that will on save.
		 * Example : `malchimp`, `webhook`, `etc`
		 *
		 * @since 1.1
		 *
		 * @param mixed $values
		 */
		$values = apply_filters( 'forminator_addon_' . $addon_slug . '_save_settings_values', $values );

		if ( empty( $this->multi_global_id ) ) {
			$this->multi_global_id = uniqid( '', true );
		}

		if ( $this->is_multi_global ) {
			$all_values[ $this->multi_global_id ] = forminator_sanitize_array_field( $values );
		} else {
			$all_values = array(
				$this->multi_global_id => forminator_sanitize_array_field( $values ),
			);
		}

		update_option( $this->get_settings_options_name(), $all_values );
	}

	/**
	 * Auto Attach Default Admin hooks for addon
	 *
	 * @since   1.1
	 * @return bool
	 */
	final public function admin_hookable() {
		if ( $this->_is_admin_hooked ) {
			return true;
		}
		$default_filters = array(
			'forminator_addon_' . $this->get_slug() . '_save_settings_values' => array( array( $this, 'before_save_settings_values' ), 1 ),
		);

		if ( $this->is_connected() ) {
			$default_filters[ 'forminator_addon_' . $this->get_slug() . '_save_form_settings_values' ] = array( array( $this, 'before_save_form_settings_values' ), 2 );
			$default_filters[ 'forminator_addon_' . $this->get_slug() . '_save_poll_settings_values' ] = array( array( $this, 'before_save_poll_settings_values' ), 2 );
			$default_filters[ 'forminator_addon_' . $this->get_slug() . '_save_quiz_settings_values' ] = array( array( $this, 'before_save_quiz_settings_values' ), 2 );
		}

		foreach ( $default_filters as $filter => $default_filter ) {
			$function_to_add = $default_filter[0];
			if ( is_callable( $function_to_add ) ) {
				$accepted_args = $default_filter[1];
				add_filter( $filter, $function_to_add, 10, $accepted_args );
			}
		}
		$this->_is_admin_hooked = true;

		return true;
	}

	/**
	 * Maintain hooks all pages for addons
	 *
	 * @since   1.1
	 * @return bool
	 */
	final public function global_hookable() {
		if ( $this->_is_global_hooked ) {
			return true;
		}

		$default_filters = array(
			'forminator_addon_' . $this->get_slug() . '_get_settings_values' => array( array( $this, 'before_get_settings_values' ), 1 ),
		);

		if ( $this->is_connected() ) {
			$default_filters[ 'forminator_addon_' . $this->get_slug() . '_get_form_settings_values' ] = array( array( $this, 'before_get_form_settings_values' ), 2 );
			$default_filters[ 'forminator_addon_' . $this->get_slug() . '_get_poll_settings_values' ] = array( array( $this, 'before_get_poll_settings_values' ), 2 );
			$default_filters[ 'forminator_addon_' . $this->get_slug() . '_get_quiz_settings_values' ] = array( array( $this, 'before_get_quiz_settings_values' ), 2 );
		}

		foreach ( $default_filters as $filter => $default_filter ) {
			$function_to_add = $default_filter[0];
			if ( is_callable( $function_to_add ) ) {
				$accepted_args = $default_filter[1];
				add_filter( $filter, $function_to_add, 10, $accepted_args );
			}
		}
		$this->_is_global_hooked = true;

		return true;
	}

	/**
	 * Override this function if you need to apply some conditional logic on it
	 * By Default this function will only check @see Forminator_Integration::settings_wizards() as valid multi array
	 *
	 * @since 1.1
	 * @return bool
	 */
	public function is_settings_available() {
		$steps = $this->settings_wizards();
		if ( ! is_array( $steps ) ) {
			return false;
		}

		if ( count( $steps ) < 1 ) {
			return false;
		}

		return true;
	}

	/**
	 * Override this function if you need to apply some conditional logic on it
	 * By Default this function will check
	 *
	 * @param int $form_id Form Id.
	 *
	 * @return bool
	 * @since   1.1
	 *
	 * @see     Forminator_Integration::settings_wizards()
	 * @see     Forminator_Integration_Form_Settings::module_settings_wizards()
	 * as valid multi array
	 */
	public function is_form_settings_available( $form_id ) {
		$steps = $this->get_steps( $form_id );
		if ( ! is_array( $steps ) ) {
			return false;
		}

		if ( count( $steps ) < 1 ) {
			return false;
		}

		return true;
	}

	/**
	 * Flag for check if and addon connected (global settings such as api key complete)
	 *
	 * Please apply necessary WordPress hook on the inheritance class
	 *
	 * @since   1.1
	 * @return boolean
	 */
	public function is_connected() {
		$is_connected = $this->is_active() && $this->is_authorized();

		/**
		 * Filter connected status
		 *
		 * @param bool $is_connected
		 */
		$is_connected = apply_filters( 'forminator_addon_' . $this->get_slug() . '_is_connected', $is_connected );

		return $is_connected;
	}

	/**
	 * Authorized Callback
	 *
	 * @return bool
	 */
	public function is_authorized() {
		return false; }

	/**
	 * Flag for check if and addon connected to a form(form settings such as list id completed)
	 *
	 * Please apply necessary WordPress hook on the inheritance class
	 *
	 * @since   1.1
	 * @param int    $module_id Form ID.
	 * @param string $module_slug Module type.
	 * @param bool   $check_lead Check is lead connected or not.
	 * @return boolean
	 * @throws Forminator_Integration_Exception When there is an Interaction error.
	 */
	public function is_module_connected( $module_id, $module_slug = 'form', $check_lead = false ) {
		try {
			$addon_settings = null;
			if ( ! $this->is_connected() ) {
				throw new Forminator_Integration_Exception( esc_html__( ' Integration is not connected', 'forminator' ) );
			}

			$addon_settings = $this->get_addon_settings( $module_id, $module_slug );
			if ( ! $addon_settings instanceof Forminator_Integration_Settings ) {
				throw new Forminator_Integration_Exception( esc_html__( 'Invalid Module Integration Settings', 'forminator' ) );
			}

			if ( $check_lead ) {
				$is_connected = $addon_settings->has_lead();
			} else {
				// Mark as active when there is at least one active connection.
				if ( false === $addon_settings->find_one_active_connection() ) {
					throw new Forminator_Integration_Exception( esc_html__( 'No active Integration connection found in this module', 'forminator' ) );
				}

				$is_connected = true;
			}
		} catch ( Forminator_Integration_Exception $e ) {
			$is_connected = false;
			forminator_addon_maybe_log( __METHOD__, '[' . $this->get_title() . ']' . $e->getMessage() );
		}

		/**
		 * Filter addon connected status with the form
		 *
		 * @param bool                                    $is_connected
		 * @param int                                     $module_id Current Module ID.
		 * @param Forminator_Integration_Form_Settings $addon_settings Instance of form settings, or null when unavailable.
		 */
		$is_connected = apply_filters( 'forminator_addon_' . $this->get_slug() . '_is_' . $module_slug . ( $check_lead ? '_lead' : '' ) . '_connected', $is_connected, $module_id, $addon_settings );

		return $is_connected;
	}

	/**
	 * Check if this addon on active
	 *
	 * @since   1.1
	 * @return bool
	 */
	final public function is_active() {
		return forminator_addon_is_active( $this->get_slug() );
	}

	/**
	 * Flag show full log on entries
	 *
	 * @return bool
	 */
	public function is_show_full_log() {
		$slug  = $this->get_slug();
		$const = 'FORMINATOR_ADDON_' . strtoupper( $slug ) . '_SHOW_FULL_LOG';
		$glob  = 'FORMINATOR_ADDON_SHOW_FULL_LOG';

		$show_full_log = ( defined( $const ) && constant( $const ) ) || ( defined( $glob ) && constant( $glob ) );

		/**
		 * Filter Flag show full log on entries
		 *
		 * @params bool $show_full_log
		 */
		$show_full_log = apply_filters( 'forminator_addon_' . $slug . '_show_full_log', $show_full_log );

		return $show_full_log;
	}

	/**
	 * Get ClassName of addon Module Settings
	 *
	 * @see   Forminator_Integration_Settings
	 *
	 * @param string $module_type Module type.
	 *
	 * @since 1.1
	 * @return null|string
	 */
	final public function get_settings_class_name( $module_type ) {
		$addon_slug          = $this->get_slug();
		$settings_class_name = 'Forminator_' . ucfirst( $addon_slug ) . '_' . ucfirst( $module_type ) . '_Settings';

		/**
		 * Filter class name of the addon module settings
		 *
		 * Module settings class name is a string
		 * it will be validated by `class_exists` and must be instanceof @see Forminator_Integration_Settings
		 *
		 * @since 1.1
		 *
		 * @param string $settings_class_name
		 */
		$settings_class_name = apply_filters( 'forminator_addon_' . $addon_slug . '_' . $module_type . '_settings_class_name', $settings_class_name );

		return $settings_class_name;
	}

	/**
	 * Get Form Settings Instance
	 *
	 * @since   1.1
	 *
	 * @param int    $module_id Module type.
	 * @param string $module_type Moodule type.
	 *
	 * @return Forminator_Integration_Form_Settings | null
	 * @throws Forminator_Integration_Exception When there is an Integration error.
	 */
	final public function get_addon_settings( $module_id, $module_type ) {
		$class_name = $this->get_settings_class_name( $module_type );
		if ( empty( $class_name ) || ! class_exists( $class_name ) ) {
			return null;
		}

		try {
			$settings_instance = new $class_name( $this, $module_id );
			if ( ! $settings_instance instanceof Forminator_Integration_Settings ) {
				throw new Forminator_Integration_Exception( $class_name . ' is not instanceof Forminator_Integration_Settings' );
			}
			forminator_maybe_attach_addon_hook( $this );
			return $settings_instance;
		} catch ( Exception $e ) {
			forminator_addon_maybe_log( $this->get_slug(), 'Failed to instantiate its _addon_settings_instances', $e->getMessage(), $e->getTrace() );
			return null;
		}
	}

	/**
	 * Executor of before get form settings values, to be correctly mapped with form_setting instance for form_id
	 *
	 * @since 1.1
	 *
	 * @param string $values Settings.
	 * @param int    $form_id Form Id.
	 *
	 * @return mixed
	 */
	final public function before_get_form_settings_values( $values, $form_id ) {
		$form_settings = $this->get_addon_settings( $form_id, 'form' );
		if ( $form_settings instanceof Forminator_Integration_Form_Settings ) {
			if ( is_callable( array( $form_settings, 'before_get_form_settings_values' ) ) ) {
				return $form_settings->before_get_form_settings_values( $values );
			}
		}

		return $values;
	}

	/**
	 * Executor of before save form settings values, to be correctly mapped with form_setting instance for form_id
	 *
	 * @since 1.1
	 *
	 * @param string $values Settings.
	 * @param int    $form_id Form Id.
	 *
	 * @return mixed
	 */
	final public function before_save_form_settings_values( $values, $form_id ) {
		$form_settings = $this->get_addon_settings( $form_id, 'form' );
		if ( $form_settings instanceof Forminator_Integration_Form_Settings ) {
			if ( is_callable( array( $form_settings, 'before_save_form_settings_values' ) ) ) {
				return $form_settings->before_save_form_settings_values( $values );
			}
		}

		return $values;
	}


	/**
	 * Get Hooks object of Integrations
	 *
	 * @param int $module_id Module ID.
	 * @param int $module_type Module type.
	 *
	 * @return Forminator_Integration_Hooks|null
	 * @since 1.1
	 */
	final public function get_addon_hooks( $module_id, $module_type ) {
		if ( ! isset( $this->addon_hooks_instances[ $module_id ] ) || ! $this->addon_hooks_instances[ $module_id ] instanceof Forminator_Integration_Hooks ) {
			$addon_slug = $this->get_slug();
			$classname  = 'Forminator_' . ucfirst( $addon_slug ) . '_' . ucfirst( $module_type ) . '_Hooks';

			if ( ! class_exists( $classname ) ) {
				return null;
			}

			try {
				$this->addon_hooks_instances[ $module_id ] = new $classname( $this, $module_id );
			} catch ( Exception $e ) {
				forminator_addon_maybe_log( $this->get_slug(), 'Failed to instantiate its addon_hooks_instance', $e->getMessage() );

				return null;
			}
		}

		return $this->addon_hooks_instances[ $module_id ];
	}

	/**
	 * SAMPLE of callback wizard
	 *
	 * @example {
	 * 'html' : '', => will contains title, description, and form it self
	 * 'has_errors' : true/false => true when it has error, such as invalid input
	 * buttons [
	 *      submit [
	 *          action: forminator_load_mailchimp_settings
	 *          data: {
	 *              step: 2
	 *          },
	 *          markup: '<a></a>'
	 *      ],
	 *      disconnect [
	 *          action: forminator_disconnect_mailchimp,
	 *          data: [],
	 *          markup: '<a></a>'
	 *      ]
	 * }
	 * 'redirect': '',
	 * 'is_close' : true if wizard should be closed
	 * ]
	 *
	 * @param array $submitted_data Submitted data.
	 * @param int   $form_id Form Id.
	 *
	 * @since   1.1
	 * @return array
	 */
	private function sample_setting_first_step( $submitted_data, $form_id ) {
		// TODO: break `html` into `parts` to make easier for addon to extend.
		return array(
			'html'       => '<p>Hello im from first step settings</p>',
			'has_errors' => false,
		);
	}

	/**
	 * SAMPLE of is_completed wizard step
	 *
	 * @since   1.1
	 * @return bool
	 */
	private function sample_setting_first_step_is_completed() {
		// check something.
		return true; // when check is passed.
	}

	/**
	 * Override this function if you wanna make an addon allow multiple instance on 1 form
	 *
	 * @since 1.1
	 * @return bool
	 */
	public function is_allow_multi_on_form() {
		return false;
	}

	/**
	 * Return button markup
	 *
	 * @since 1.1
	 *
	 * @param string $label Text label.
	 * @param string $classes Class names.
	 * @param string $tooltip Content for Tooltip.
	 *
	 * @return string
	 */
	public static function get_button_markup( $label, $classes = '', $tooltip = '' ) {
		$markup = '<button type="button" class="sui-button ';
		if ( ! empty( $classes ) ) {
			$markup .= $classes;
		}
		$markup .= '"';
		if ( ! empty( $tooltip ) ) {
			$markup .= 'data-tooltip="' . $tooltip . '"';
		}
		$markup .= '>';
		$markup .= '<span class="sui-loading-text">' . $label . '</span>';
		$markup .= '<i class="sui-icon-loader sui-loading" aria-hidden="true"></i>';
		$markup .= '</button>';

		/**
		 * Filter Integration button markup for setting
		 *
		 * Its possible @see Forminator_Integration::get_button_markup() overridden.
		 * Thus this filter wont be called
		 *
		 * @since 1.1
		 *
		 * @param string $markup  Current markup.
		 * @param string $label   Button label.
		 * @param string $classes Additional classes for `<button>`.
		 * @param string $tooltip
		 */
		$markup = apply_filters( 'forminator_addon_setting_button_markup', $markup, $label, $classes, $tooltip );

		return $markup;
	}

	/**
	 * Return link markup
	 *
	 * @since 1.13
	 *
	 * @param string $url URL.
	 * @param string $label Text for label.
	 * @param string $target Target attribute.
	 * @param string $classes Class names.
	 * @param string $tooltip Content for tooltip.
	 *
	 * @return string
	 */
	public static function get_link_markup( $url, $label, $target = '_blank', $classes = '', $tooltip = '' ) {
		$markup = '<a href="' . $url . '" target="' . $target . '" class="sui-button ';
		if ( ! empty( $classes ) ) {
			$markup .= $classes;
		}
		$markup .= '"';
		if ( ! empty( $tooltip ) ) {
			$markup .= 'data-tooltip="' . $tooltip . '"';
		}
		$markup .= '>';
		$markup .= '<span class="sui-loading-text">' . $label . '</span>';
		$markup .= '<i class="sui-icon-loader sui-loading" aria-hidden="true"></i>';
		$markup .= '</a>';

		/**
		 * Filter Integration link markup for setting
		 *
		 * Its possible @see Forminator_Integration::get_link_markup() overridden.
		 * Thus this filter wont be called
		 *
		 * @since 1.1
		 *
		 * @param string $markup  Current markup.
		 * @param string $url     Link URL.
		 * @param string $label   Button label.
		 * @param string $target  Link target.
		 * @param string $classes Additional classes for `<button>`.
		 * @param string $tooltip
		 */
		$markup = apply_filters( 'forminator_addon_setting_link_markup', $markup, $url, $label, $target, $classes, $tooltip );

		return $markup;
	}

	/**
	 * Get Template as string
	 *
	 * @since 1.2
	 *
	 * @param string $template Template path.
	 * @param array  $params Template variables.
	 *
	 * @return string
	 */
	public static function get_template( $template, $params ) {
		/* @noinspection PhpUnusedLocalVariableInspection */
		$template_vars = $params;
		ob_start();
		/* @noinspection PhpIncludeInspection */
		include $template;
		$html = ob_get_clean();

		/**
		 * Filter Html String from template
		 *
		 * @since 1.2
		 *
		 * @param string $html
		 * @param string $template template file path.
		 * @param array  $params   template variables.
		 */
		$html = apply_filters( 'forminator_addon_get_template', $html, $template, $params );

		return $html;
	}

	/**
	 * Register section(s) on integrations page
	 *
	 * @see   wp-admin/admin.php?page=forminator-integrations
	 *
	 * @since 1.2
	 *
	 * @return array
	 */
	public function register_integration_sections() {
		// callback must be public method on this class.
		return array();
	}

	/**
	 * Get Callback of section on integration page
	 *
	 * When section not provided, it will return all callbacks
	 *
	 * @since 1.2
	 *
	 * @param string $section Section.
	 *
	 * @return array|null
	 */
	public function get_integration_section_callback( $section = null ) {
		$addon_slug           = $this->_slug;
		$integration_sections = $this->register_integration_sections();

		$callback = null;

		if ( is_null( $section ) ) {
			$callback = $integration_sections;
		} elseif ( isset( $integration_sections[ $section ] ) ) {
			$callback = $integration_sections[ $section ];
		}

		/**
		 * Filter Integration section callback
		 *
		 * @since 1.2
		 *
		 * @param array|null  $callback
		 * @param string|null $section              requested section.
		 * @param array       $integration_sections registered sections.
		 */
		$callback = apply_filters( 'forminator_addon_' . $addon_slug . '_integration_section_callback', $callback, $section, $integration_sections );

		return $callback;
	}

	/**
	 * Generate multi_id
	 *
	 * @since 1.2
	 *
	 * @param int $form_id Form id.
	 *
	 * @return array
	 */
	private function generate_form_settings_multi_id( $form_id ) {
		$addon_slug             = $this->get_slug();
		$addon                  = $this;
		$multi_id               = 0;
		$form_settings_instance = $this->get_addon_settings( $form_id, 'form' );
		if ( $this->is_allow_multi_on_form() && ! is_null( $form_settings_instance ) && $form_settings_instance instanceof Forminator_Integration_Form_Settings ) {
			$multi_id = $form_settings_instance->generate_multi_id();
		}

		/**
		 * Filter new generated multi id of addon form setting
		 *
		 * @since 1.2
		 *
		 * @param string                                  $multi_id
		 * @param Forminator_Integration_Form_Settings $addon                  Integration Instance.
		 * @param Forminator_Integration_Form_Settings $form_settings_instance Integration Form Settings Instance.
		 */
		$multi_ids = apply_filters( 'forminator_addon_' . $addon_slug . '_form_settings_multi_id', $multi_id, $addon, $form_settings_instance );

		return $multi_ids;
	}

	/**
	 * Executor of before get form settings values, to be correctly mapped with poll_setting instance for poll_id
	 *
	 * @since 1.6.1
	 *
	 * @param array $values Settings.
	 * @param int   $poll_id Poll Id.
	 *
	 * @return mixed
	 */
	final public function before_get_poll_settings_values( $values, $poll_id ) {
		$poll_settings = $this->get_addon_settings( $poll_id, 'poll' );
		if ( $poll_settings instanceof Forminator_Integration_Poll_Settings ) {
			if ( is_callable( array( $poll_settings, 'before_get_poll_settings_values' ) ) ) {
				return $poll_settings->before_get_poll_settings_values( $values );
			}
		}

		return $values;
	}

	/**
	 * Executor of before save form settings values, to be correctly mapped with poll_setting instance for poll_id
	 *
	 * @since 1.6.1
	 *
	 * @param array $values Settings.
	 * @param int   $poll_id Poll Id.
	 *
	 * @return mixed
	 */
	final public function before_save_poll_settings_values( $values, $poll_id ) {
		$poll_settings = $this->get_addon_settings( $poll_id, 'poll' );
		if ( $poll_settings instanceof Forminator_Integration_Poll_Settings ) {
			if ( is_callable( array( $poll_settings, 'before_save_poll_settings_values' ) ) ) {
				return $poll_settings->before_save_poll_settings_values( $values );
			}
		}

		return $values;
	}

	/**
	 * Get Poll Setting Wizard
	 * This function will process @see Forminator_Integration::settings_wizards()
	 * Please keep in mind this function will only be called when @see Forminator_Integration::is_settings_available() return `true`
	 * Which is doing check on @see Forminator_Integration::settings_wizards() requirements is passed
	 *
	 * @since 1.6.1
	 *
	 * @param array $submitted_data Submitted data.
	 * @param int   $poll_id Poll Id.
	 * @param int   $current_step Current step.
	 * @param int   $step Step.
	 *
	 * @return array|mixed
	 */
	final public function get_poll_settings_wizard( $submitted_data, $poll_id, $current_step = 0, $step = 0 ) {

		$settings_steps = array();
		if ( ! $this->is_connected() ) {
			$settings_steps = $this->settings_wizards();
		}

		$poll_settings_steps = $this->get_poll_settings_steps( $poll_id );

		$steps = array_merge( $settings_steps, $poll_settings_steps );

		if ( ! is_array( $steps ) ) {
			/* translators: integration title */
			return $this->get_empty_wizard( sprintf( esc_html__( 'No Poll Settings available for %1$s', 'forminator' ), $this->get_title() ) );
		}
		$total_steps = count( $steps );
		if ( $total_steps < 1 ) {
			/* translators: integration title */
			return $this->get_empty_wizard( sprintf( esc_html__( 'No Poll Settings available for %1$s', 'forminator' ), $this->get_title() ) );
		}

		if ( ! isset( $steps[ $step ] ) ) {
			// go to last step.
			$step = $total_steps - 1;

			return $this->get_poll_settings_wizard( $submitted_data, $poll_id, $current_step, $step );
		}

		if ( $step > 0 ) {
			if ( $current_step > 0 ) {
				// check previous step is complete.
				$prev_step              = $current_step - 1;
				$prev_step_is_completed = true;
				// only call `is_completed` when its defined.
				if ( isset( $steps[ $prev_step ]['is_completed'] ) && is_callable( $steps[ $prev_step ]['is_completed'] ) ) {
					$prev_step_is_completed = call_user_func( $steps[ $prev_step ]['is_completed'], $submitted_data );
				}
				if ( ! $prev_step_is_completed ) {
					--$step;

					return $this->get_poll_settings_wizard( $submitted_data, $poll_id, $current_step, $step );
				}
			}

			// only validation when it moves forward.
			if ( $step > $current_step ) {
				$current_step_result = $this->get_poll_settings_wizard( $submitted_data, $poll_id, $current_step, $current_step );
				if ( isset( $current_step_result['has_errors'] ) && true === $current_step_result['has_errors'] ) {
					return $current_step_result;
				} else {
					// set empty submitted data for next step, except preserved as reference.
					$preserved_keys = array(
						'multi_id',
					);
					foreach ( $submitted_data as $key => $value ) {
						if ( ! in_array( $key, $preserved_keys, true ) ) {
							unset( $submitted_data[ $key ] );
						}
					}
				}
			}
		}

		$poll_settings_wizard = $this->get_wizard( $steps, $submitted_data, $poll_id, $step );

		$addon_slug             = $this->get_slug();
		$addon                  = $this;
		$poll_settings_instance = $this->get_addon_settings( $poll_id, 'poll' );

		/**
		 * Filter poll settings wizard returned to client
		 *
		 * @since 1.6.1
		 *
		 * @param array                                        $poll_settings_wizard
		 * @param array                                        $submitted_data         $_POST from client.
		 * @param int                                          $poll_id                poll ID requested for.
		 * @param int                                          $current_step           Current Step displayed to user, start from 0.
		 * @param int                                          $step                   Step requested by client, start from 0.
		 * @param Forminator_Integration                    $addon                  Integration Instance.
		 * @param Forminator_Integration_Poll_Settings|null $poll_settings_instance Integration Form settings instance, or null if unavailable.
		 */
		$poll_settings_wizard = apply_filters(
			'forminator_addon_' . $addon_slug . '_poll_settings_wizard',
			$poll_settings_wizard,
			$submitted_data,
			$poll_id,
			$current_step,
			$step,
			$addon,
			$poll_settings_instance
		);

		return $poll_settings_wizard;
	}

	/**
	 * Get poll settings wizard steps
	 *
	 * @since 1.6.1
	 *
	 * @param int $poll_id Poll Id.
	 *
	 * @return array
	 */
	private function get_poll_settings_steps( $poll_id ) {
		$addon_slug             = $this->get_slug();
		$addon                  = $this;
		$poll_settings_steps    = array();
		$poll_settings_instance = $this->get_addon_settings( $poll_id, 'poll' );
		if ( ! is_null( $poll_settings_instance ) && $poll_settings_instance instanceof Forminator_Integration_Poll_Settings ) {
			$poll_settings_steps = $poll_settings_instance->module_settings_wizards();
		}

		/**
		 * Filter form settings step that will be used for building wizard
		 *
		 * More detail : @param array $poll_settings_steps
		 *
		 * @param int                                          $poll_id                current form id.
		 * @param Forminator_Integration                    $addon                  Integration instance.
		 * @param Forminator_Integration_Poll_Settings|null $poll_settings_instance Form settings of addon if available, or null otherwise.
		 *@see Forminator_Integration_Poll_Settings::module_settings_wizards()
		 *
		 * @since 1.6.1
		 */
		$poll_settings_steps = apply_filters( 'forminator_addon_' . $addon_slug . '_poll_settings_steps', $poll_settings_steps, $poll_id, $addon, $poll_settings_instance );

		return $poll_settings_steps;
	}

	/**
	 * Get poll settings multi id
	 *
	 * @since 1.6.1
	 *
	 * @param int $poll_id Poll Id.
	 *
	 * @return array
	 */
	private function get_poll_settings_multi_ids( $poll_id ) {
		$addon_slug             = $this->get_slug();
		$addon                  = $this;
		$multi_ids              = array();
		$poll_settings_instance = $this->get_addon_settings( $poll_id, 'poll' );
		if ( $this->is_allow_multi_on_poll() && ! is_null( $poll_settings_instance ) && $poll_settings_instance instanceof Forminator_Integration_Poll_Settings ) {
			$multi_ids = $poll_settings_instance->get_multi_ids();
		}

		/**
		 * Filter multi id of addon poll settings
		 *
		 * @since 1.6.1
		 *
		 * @param array                                   $multi_ids
		 * @param Forminator_Integration               $addon                  Integration Instance.
		 * @param Forminator_Integration_Poll_Settings $poll_settings_instance Integration Form Settings Instance.
		 */
		$multi_ids = apply_filters( 'forminator_addon_' . $addon_slug . '_poll_settings_multi_ids', $multi_ids, $addon, $poll_settings_instance );

		return $multi_ids;
	}

	/**
	 * Override this function if you wanna make an addon allow multiple instance on 1 poll
	 *
	 * @since 1.6.1
	 * @return bool
	 */
	public function is_allow_multi_on_poll() {
		return false;
	}

	/**
	 * Transform addon instance into array with form relation
	 *
	 * @since  1.1
	 * @since  1.2 generate new multi_id to allow reference on wizard
	 *
	 * @param int $poll_id Poll Id.
	 *
	 * @return array
	 */
	final public function to_array_with_poll( $poll_id ) {
		$to_array                               = $this->to_array();
		$is_allow_multi_on_poll                 = $this->is_allow_multi_on_poll();
		$to_array['is_poll_connected']          = $this->is_module_connected( $poll_id, 'poll' );
		$to_array['is_poll_settings_available'] = $this->is_poll_settings_available( $poll_id );
		$to_array['is_allow_multi_on_poll']     = $is_allow_multi_on_poll;

		$to_array['multi_id'] = $this->generate_poll_settings_multi_id( $poll_id );

		// handle multiple form setting.
		if ( $is_allow_multi_on_poll ) {
			$to_array['multi_ids'] = $this->get_poll_settings_multi_ids( $poll_id );
		}

		$to_array_with_poll = $to_array;
		$addon_slug         = $this->get_slug();
		$addon              = $this;

		/**
		 * Filter array of addon properties
		 *
		 * Including relation with form_id
		 *
		 * @since 1.6.1
		 *
		 * @param array                     $to_array_with_poll array of addon properties.
		 * @param int                       $poll_id            Poll ID.
		 * @param Forminator_Integration $addon              Integration instance.
		 */
		$to_array_with_poll = apply_filters( 'forminator_addon_' . $addon_slug . '_to_array_with_poll', $to_array_with_poll, $poll_id, $addon );

		return $to_array_with_poll;
	}

	/**
	 * Override this function if you need to apply some conditional logic on it
	 * By Default this function will check
	 *
	 * @param int $poll_id Poll Id.
	 *
	 * @return bool
	 * @since   1.6.1
	 *
	 * @see     Forminator_Integration::settings_wizards()
	 * @see     Forminator_Integration_Poll_Settings::module_settings_wizards()
	 * as valid multi array
	 */
	public function is_poll_settings_available( $poll_id ) {
		$steps      = array();
		$poll_steps = $this->get_poll_settings_steps( $poll_id );

		$steps = array_merge( $steps, $poll_steps );
		if ( ! is_array( $steps ) ) {
			return false;
		}

		if ( count( $steps ) < 1 ) {
			return false;
		}

		return true;
	}

	/**
	 * Generate multi_id
	 *
	 * @since 1.6.1
	 *
	 * @param int $poll_id Poll Id.
	 *
	 * @return array
	 */
	private function generate_poll_settings_multi_id( $poll_id ) {
		$addon_slug             = $this->get_slug();
		$addon                  = $this;
		$multi_id               = 0;
		$poll_settings_instance = $this->get_addon_settings( $poll_id, 'poll' );
		if ( $this->is_allow_multi_on_poll() && ! is_null( $poll_settings_instance ) && $poll_settings_instance instanceof Forminator_Integration_Poll_Settings ) {
			$multi_id = $poll_settings_instance->generate_multi_id();
		}

		/**
		 * Filter new generated multi id of addon form setting
		 *
		 * @since 1.6.1
		 *
		 * @param string                                  $multi_id
		 * @param Forminator_Integration               $addon                  Integration Instance.
		 * @param Forminator_Integration_Poll_Settings $poll_settings_instance Integration Poll Settings Instance.
		 */
		$multi_ids = apply_filters( 'forminator_addon_' . $addon_slug . '_poll_settings_multi_id', $multi_id, $addon, $poll_settings_instance );

		return $multi_ids;
	}

	/**
	 * Executor of before get form settings values, to be correctly mapped with quiz_setting instance for quiz_id
	 *
	 * @since 1.6.2
	 *
	 * @param array $values Settings.
	 * @param int   $quiz_id Quiz Id.
	 *
	 * @return mixed
	 */
	final public function before_get_quiz_settings_values( $values, $quiz_id ) {
		$quiz_settings = $this->get_addon_settings( $quiz_id, 'quiz' );
		if ( $quiz_settings instanceof Forminator_Integration_Quiz_Settings ) {
			if ( is_callable( array( $quiz_settings, 'before_get_quiz_settings_values' ) ) ) {
				return $quiz_settings->before_get_quiz_settings_values( $values );
			}
		}

		return $values;
	}

	/**
	 * Executor of before save form settings values, to be correctly mapped with quiz_setting instance for quiz_id
	 *
	 * @since 1.6.2
	 *
	 * @param array $values Settings.
	 * @param int   $quiz_id Quiz Id.
	 *
	 * @return mixed
	 */
	final public function before_save_quiz_settings_values( $values, $quiz_id ) {
		$quiz_settings = $this->get_addon_settings( $quiz_id, 'quiz' );
		if ( $quiz_settings instanceof Forminator_Integration_Quiz_Settings ) {
			if ( is_callable( array( $quiz_settings, 'before_save_quiz_settings_values' ) ) ) {
				return $quiz_settings->before_save_quiz_settings_values( $values );
			}
		}

		return $values;
	}

	/**
	 * Get Quiz Setting Wizard
	 * This function will process @see Forminator_Integration::settings_wizards()
	 * Please keep in mind this function will only be called when @see Forminator_Integration::is_settings_available() return `true`
	 * Which is doing check on @see Forminator_Integration::settings_wizards() requirements is passed
	 *
	 * @since 1.6.2
	 *
	 * @param array $submitted_data Submitted data.
	 * @param int   $quiz_id Quiz Id.
	 * @param int   $current_step Current step.
	 * @param int   $step Step.
	 *
	 * @return array|mixed
	 */
	final public function get_quiz_settings_wizard( $submitted_data, $quiz_id, $current_step = 0, $step = 0 ) {
		$steps = $this->get_steps( $quiz_id, 'quiz' );

		if ( ! is_array( $steps ) ) {
			/* translators: integration title */
			return $this->get_empty_wizard( sprintf( esc_html__( 'No Quiz Settings available for %1$s', 'forminator' ), $this->get_title() ) );
		}
		$total_steps = count( $steps );
		if ( $total_steps < 1 ) {
			/* translators: integration title */
			return $this->get_empty_wizard( sprintf( esc_html__( 'No Quiz Settings available for %1$s', 'forminator' ), $this->get_title() ) );
		}

		if ( ! isset( $steps[ $step ] ) ) {
			// go to last step.
			$step = $total_steps - 1;

			return $this->get_quiz_settings_wizard( $submitted_data, $quiz_id, $current_step, $step );
		}

		if ( $step > 0 ) {
			if ( $current_step > 0 ) {
				// check previous step is complete.
				$prev_step              = $current_step - 1;
				$prev_step_is_completed = true;
				// only call `is_completed` when its defined.
				if ( isset( $steps[ $prev_step ]['is_completed'] ) && is_callable( $steps[ $prev_step ]['is_completed'] ) ) {
					$prev_step_is_completed = call_user_func( $steps[ $prev_step ]['is_completed'], $submitted_data );
				}
				if ( ! $prev_step_is_completed ) {
					--$step;

					return $this->get_quiz_settings_wizard( $submitted_data, $quiz_id, $current_step, $step );
				}
			}

			// only validation when it moves forward.
			if ( $step > $current_step ) {
				$current_step_result = $this->get_quiz_settings_wizard( $submitted_data, $quiz_id, $current_step, $current_step );
				if ( isset( $current_step_result['has_errors'] ) && true === $current_step_result['has_errors'] ) {
					return $current_step_result;
				} else {
					// set empty submitted data for next step, except preserved as reference.
					$preserved_keys = array(
						'multi_id',
					);
					foreach ( $submitted_data as $key => $value ) {
						if ( ! in_array( $key, $preserved_keys, true ) ) {
							unset( $submitted_data[ $key ] );
						}
					}
					// Reset steps cache - uses when wizard steps are conditional.
					unset( $this->steps[ $quiz_id ] );
					$steps = $this->get_steps( $quiz_id, 'quiz' );
				}
			}
		}

		$quiz_settings_wizard = $this->get_wizard( $steps, $submitted_data, $quiz_id, $step );

		$addon_slug             = $this->get_slug();
		$addon                  = $this;
		$quiz_settings_instance = $this->get_addon_settings( $quiz_id, 'quiz' );

		/**
		 * Filter quiz settings wizard returned to client
		 *
		 * @since 1.6.2
		 *
		 * @param array                                        $quiz_settings_wizard
		 * @param array                                        $submitted_data         $_POST from client.
		 * @param int                                          $quiz_id                quiz ID requested for.
		 * @param int                                          $current_step           Current Step displayed to user, start from 0.
		 * @param int                                          $step                   Step requested by client, start from 0.
		 * @param Forminator_Integration                    $addon                  Integration Instance.
		 * @param Forminator_Integration_Quiz_Settings|null $quiz_settings_instance Integration Form settings instance, or null if unavailable.
		 */
		$quiz_settings_wizard = apply_filters(
			'forminator_addon_' . $addon_slug . '_quiz_settings_wizard',
			$quiz_settings_wizard,
			$submitted_data,
			$quiz_id,
			$current_step,
			$step,
			$addon,
			$quiz_settings_instance
		);

		return $quiz_settings_wizard;
	}

	/**
	 * Get quiz settings wizard steps
	 *
	 * @since 1.6.2
	 *
	 * @param int $quiz_id Quiz Id.
	 *
	 * @return array
	 */
	private function get_quiz_settings_steps( $quiz_id ) {
		$addon_slug             = $this->get_slug();
		$addon                  = $this;
		$quiz_settings_steps    = array();
		$quiz_settings_instance = $this->get_addon_settings( $quiz_id, 'quiz' );
		if ( ! is_null( $quiz_settings_instance ) && $quiz_settings_instance instanceof Forminator_Integration_Quiz_Settings ) {
			$quiz_settings_steps = $quiz_settings_instance->module_settings_wizards();
		}

		/**
		 * Filter form settings step that will be used for building wizard
		 *
		 * More detail : @param array $quiz_settings_steps
		 *
		 * @param int                                          $quiz_id                current quiz id.
		 * @param Forminator_Integration                    $addon                  Integration instance.
		 * @param Forminator_Integration_Quiz_Settings|null $quiz_settings_instance Quiz settings of addon if available, or null otherwise.
		 *@see Forminator_Integration_Quiz_Settings::module_settings_wizards()
		 *
		 * @since 1.6.2
		 */
		$quiz_settings_steps = apply_filters( 'forminator_addon_' . $addon_slug . '_quiz_settings_steps', $quiz_settings_steps, $quiz_id, $addon, $quiz_settings_instance );

		return $quiz_settings_steps;
	}

	/**
	 * Get quiz settings multi id
	 *
	 * @since 1.6.2
	 *
	 * @param int $quiz_id Quiz Id.
	 *
	 * @return array
	 */
	private function get_quiz_settings_multi_ids( $quiz_id ) {
		$addon_slug             = $this->get_slug();
		$addon                  = $this;
		$multi_ids              = array();
		$quiz_settings_instance = $this->get_addon_settings( $quiz_id, 'quiz' );
		if ( $this->is_allow_multi_on_quiz() && ! is_null( $quiz_settings_instance ) && $quiz_settings_instance instanceof Forminator_Integration_Quiz_Settings ) {
			$multi_ids = $quiz_settings_instance->get_multi_ids();
		}

		/**
		 * Filter multi id of addon quiz settings
		 *
		 * @since 1.6.2
		 *
		 * @param array                                   $multi_ids
		 * @param Forminator_Integration               $addon                  Integration Instance.
		 * @param Forminator_Integration_Quiz_Settings $quiz_settings_instance Integration Quiz Settings Instance.
		 */
		$multi_ids = apply_filters( 'forminator_addon_' . $addon_slug . '_quiz_settings_multi_ids', $multi_ids, $addon, $quiz_settings_instance );

		return $multi_ids;
	}

	/**
	 * Override this function if you wanna make an addon allow multiple instance on 1 quiz
	 *
	 * @since 1.6.2
	 * @return bool
	 */
	public function is_allow_multi_on_quiz() {
		return false;
	}

	/**
	 * Transform addon instance into array with form relation
	 *
	 * @since  1.6.2
	 *
	 * @param int $quiz_id Quiz Id.
	 *
	 * @return array
	 */
	final public function to_array_with_quiz( $quiz_id ) {
		$to_array                               = $this->to_array();
		$is_allow_multi_on_quiz                 = $this->is_allow_multi_on_quiz();
		$to_array['is_quiz_connected']          = $this->is_module_connected( $quiz_id, 'quiz' );
		$to_array['is_quiz_settings_available'] = $this->is_quiz_settings_available( $quiz_id );
		$to_array['is_allow_multi_on_quiz']     = $is_allow_multi_on_quiz;

		$to_array['multi_id'] = $this->generate_quiz_settings_multi_id( $quiz_id );

		// handle multiple form setting.
		if ( $is_allow_multi_on_quiz ) {
			$to_array['multi_ids'] = $this->get_quiz_settings_multi_ids( $quiz_id );
		}

		$to_array_with_quiz = $to_array;
		$addon_slug         = $this->get_slug();
		$addon              = $this;

		/**
		 * Filter array of addon properties
		 *
		 * Including relation with form_id
		 *
		 * @since 1.6.2
		 *
		 * @param array                     $to_array_with_quiz array of addon properties.
		 * @param int                       $quiz_id            Quiz ID.
		 * @param Forminator_Integration $addon              Integration instance.
		 */
		$to_array_with_quiz = apply_filters( 'forminator_addon_' . $addon_slug . '_to_array_with_quiz', $to_array_with_quiz, $quiz_id, $addon );

		return $to_array_with_quiz;
	}

	/**
	 * Override this function if you need to apply some conditional logic on it
	 * By Default this function will check
	 *
	 * @param int $quiz_id Quiz Id.
	 *
	 * @return bool
	 * @since   1.6.2
	 *
	 * @see     Forminator_Integration::settings_wizards()
	 * @see     Forminator_Integration_Quiz_Settings::module_settings_wizards()
	 * as valid multi array
	 */
	public function is_quiz_settings_available( $quiz_id ) {
		$steps      = array();
		$quiz_steps = $this->get_quiz_settings_steps( $quiz_id );

		$steps = array_merge( $steps, $quiz_steps );
		if ( ! is_array( $steps ) ) {
			return false;
		}

		if ( count( $steps ) < 1 ) {
			return false;
		}

		return true;
	}

	/**
	 * Generate multi_id
	 *
	 * @since 1.6.2
	 *
	 * @param int $quiz_id Quiz Id.
	 *
	 * @return array
	 */
	private function generate_quiz_settings_multi_id( $quiz_id ) {
		$addon_slug             = $this->get_slug();
		$addon                  = $this;
		$multi_id               = 0;
		$quiz_settings_instance = $this->get_addon_settings( $quiz_id, 'quiz' );
		if ( $this->is_allow_multi_on_quiz() && ! is_null( $quiz_settings_instance ) && $quiz_settings_instance instanceof Forminator_Integration_Quiz_Settings ) {
			$multi_id = $quiz_settings_instance->generate_multi_id();
		}

		/**
		 * Filter new generated multi id of addon form setting
		 *
		 * @since 1.6.2
		 *
		 * @param string                                  $multi_id
		 * @param Forminator_Integration               $addon                  Integration Instance.
		 * @param Forminator_Integration_Quiz_Settings $quiz_settings_instance Integration Quiz Settings Instance.
		 */
		$multi_ids = apply_filters( 'forminator_addon_' . $addon_slug . '_quiz_settings_multi_id', $multi_id, $addon, $quiz_settings_instance );

		return $multi_ids;
	}

	/**
	 * Connection failed
	 *
	 * @return string
	 */
	final public function connection_failed() {

		/* translators: integration title */
		return sprintf( esc_html__( 'We couldn\'t connect to your %s account. Please resolve the errors below and try again.', 'forminator' ), $this->get_title() );
	}

	/**
	 * Get success authorize content
	 *
	 * @return string
	 */
	protected function success_authorize() {
		ob_start();
		?>
		<div class="forminator-integration-popup__header">
			<h3 id="forminator-integration-popup__title" class="sui-box-title sui-lg" style="overflow: initial; white-space: normal; text-overflow: initial;">
				<?php
				/* translators: 1: Add-on name */
				printf( esc_html__( '%1$s Connected', 'forminator' ), esc_html( $this->get_title() ) );
				?>
			</h3>
		</div>

		<p id="forminator-integration-popup__description" class="sui-description" style="text-align: center;">
			<?php
				printf(
					/* translators: 1: Title */
					esc_html__( 'Awesome! You are connected to %1$s. You can now go to your forms and activate %1$s integration to collect data.', 'forminator' ),
					esc_html( $this->get_title() )
				);
			?>
		</p>

		<div class="forminator-integration-popup__footer-temp">
			<button class="sui-button forminator-addon-close forminator-integration-popup__close">
				<?php esc_html_e( 'Close', 'forminator' ); ?>
			</button>
		</div>
		<?php
		return ob_get_clean();
	}
}