'4417', 'slug' => 'advanced-form-integration', 'type' => 'plugin', 'public_key' => 'pk_f94bb401ae01ff3a79f438df51715', 'is_premium' => false, 'premium_suffix' => 'Professional', 'has_addons' => false, 'has_paid_plans' => true, 'menu' => array( 'slug' => 'advanced-form-integration-settings', 'support' => false, 'parent' => array( 'slug' => 'advanced-form-integration', ), ), 'is_live' => true, 'is_org_compliant' => true, ) ); } return $adfoin_fs; } // Init Freemius. adfoin_fs(); // Signal that SDK was initiated. do_action( 'adfoin_fs_loaded' ); /** * Advanced Form Integration Main Class */ class Advanced_Form_Integration { /** * Plugin Version * * @var string */ public $version = '2.2.0'; /** * Initializes the Advanced_Form_Integration class * * Checks for an existing Advanced_Form_Integration instance * and if it doesn't find one, creates it. * * @since 1.0.0 * @return mixed | bool */ public static function init() { static $instance = false; if ( !$instance ) { $instance = new Advanced_Form_Integration(); } return $instance; } /** * Constructor for the Advanced_Form_Integration class * * Sets up all the appropriate hooks and actions * * @since 1.0 * @return void */ public function __construct() { register_activation_hook( __FILE__, [$this, 'activate'] ); register_deactivation_hook( __FILE__, [$this, 'deactivate'] ); add_action( 'wp_insert_site', array($this, 'new_site_added'), 10, 6 ); $this->init_plugin(); } /** * Initialize plugin * * @since 1.0.0 * @return void */ public function init_plugin() { /* Define constats */ $this->define_constants(); /* Include files */ $this->includes(); /* Instantiate classes */ $this->init_classes(); /* Initialize the action hooks */ $this->init_actions(); /* Initialize the filter hooks */ $this->init_filters(); } /** * Function activate * * This function creates the database tables for the plugin. * * @param bool $networkwide Whether to activate the plugin network-wide. */ public function activate( $networkwide ) { if ( function_exists( 'is_multisite' ) && is_multisite() ) { if ( $networkwide ) { global $wpdb; $blogids = $wpdb->get_col( "SELECT blog_id FROM {$wpdb->blogs}" ); foreach ( $blogids as $blog_id ) { switch_to_blog( $blog_id ); $this->create_table(); restore_current_blog(); } return; } } $this->create_table(); // Create default tables when plugin activates } /** * Function new_site_added * * This function creates the database tables for the plugin on a newly added site in a multisite network. * * @param object $site The newly added site object. */ public function new_site_added( $site ) { if ( is_plugin_active_for_network( plugin_basename( __FILE__ ) ) ) { switch_to_blog( $site->blog_id ); $this->create_table(); restore_current_blog(); } } /** * Function create_table * * This function creates the database tables for the plugin. * * @return void */ private function create_table() { global $wpdb; $collate = ''; if ( $wpdb->has_cap( 'collation' ) ) { if ( !empty( $wpdb->charset ) ) { $collate .= "DEFAULT CHARACTER SET {$wpdb->charset}"; } if ( !empty( $wpdb->collate ) ) { $collate .= " COLLATE {$wpdb->collate}"; } } $table_schema = array("CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}adfoin_integration` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n `title` text NOT NULL,\n `form_provider` varchar(255) NOT NULL,\n `form_id` varchar(255) NOT NULL,\n `form_name` varchar(255) DEFAULT NULL,\n `action_provider` varchar(255) NOT NULL,\n `task` varchar(255) NOT NULL,\n `data` longtext DEFAULT NULL,\n `extra_data` longtext DEFAULT NULL,\n `status` int(1) NOT NULL,\n `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n KEY `form_provider_form_id_status` (`form_provider`(50),`form_id`(50),`status`),\n KEY `status` (`status`),\n KEY `action_provider` (`action_provider`(50))\n ) {$collate};", "CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}adfoin_log` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n `response_code` int(3) DEFAULT NULL,\n `response_message` varchar(255) DEFAULT NULL,\n `integration_id` bigint(20) DEFAULT NULL,\n `request_data` longtext DEFAULT NULL,\n `response_data` longtext DEFAULT NULL,\n `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n KEY `integration_id_id` (`integration_id`,`id`),\n KEY `time` (`time`),\n KEY `response_code` (`response_code`)\n ) {$collate};"); require_once ABSPATH . 'wp-admin/includes/upgrade.php'; foreach ( $table_schema as $table ) { dbDelta( $table ); } } /** * One-time DB index migration. * * The original schema declared only `KEY id (id)` on adfoin_integration * and adfoin_log — no PRIMARY KEY, and none of the columns the plugin * actually filters by (form_provider, status, integration_id, time, * response_code) were indexed, so every read was a full table scan. * create_table() now ships the correct schema for fresh installs; this * brings already-activated sites up to the same shape on the first * wp-admin pageload after the update. * * Idempotent and version-stamped: it inspects the live indexes and only * adds what is missing, with each ALTER isolated (errors suppressed) so a * failure on one table or one index can't block the rest. * * @return void */ public function run_db_index_migration() { if ( (int) get_option( 'adfoin_db_indexes_v1', 0 ) >= 1 ) { return; } global $wpdb; $tables = array( $wpdb->prefix . 'adfoin_integration' => array( 'form_provider_form_id_status' => 'ADD INDEX `form_provider_form_id_status` (`form_provider`(50),`form_id`(50),`status`)', 'status' => 'ADD INDEX `status` (`status`)', 'action_provider' => 'ADD INDEX `action_provider` (`action_provider`(50))', ), $wpdb->prefix . 'adfoin_log' => array( 'integration_id_id' => 'ADD INDEX `integration_id_id` (`integration_id`,`id`)', 'time' => 'ADD INDEX `time` (`time`)', 'response_code' => 'ADD INDEX `response_code` (`response_code`)', ), ); // ALTER ... ADD INDEX on a duplicate name is a hard error; suppress // so the version stamp still gets written and the loop continues. $suppress = $wpdb->suppress_errors( true ); foreach ( $tables as $table => $indexes ) { // Skip a table that was never created (activation never ran). if ( !$wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) ) { continue; } $existing = $wpdb->get_col( "SHOW INDEX FROM `{$table}`", 2 ); // col 2 = Key_name $existing = ( is_array( $existing ) ? array_map( 'strtolower', $existing ) : array() ); // Promote `id` to a real PRIMARY KEY first — AUTO_INCREMENT // requires a key on the column at all times, so this must land // before the redundant secondary `KEY id` can be dropped. if ( !in_array( 'primary', $existing, true ) ) { $wpdb->query( "ALTER TABLE `{$table}` ADD PRIMARY KEY (`id`)" ); } foreach ( $indexes as $name => $add_clause ) { if ( !in_array( strtolower( $name ), $existing, true ) ) { $wpdb->query( "ALTER TABLE `{$table}` {$add_clause}" ); } } // Drop the now-redundant `KEY id (id)` once a PRIMARY KEY exists. $refreshed = $wpdb->get_col( "SHOW INDEX FROM `{$table}`", 2 ); $refreshed = ( is_array( $refreshed ) ? array_map( 'strtolower', $refreshed ) : array() ); if ( in_array( 'primary', $refreshed, true ) && in_array( 'id', $refreshed, true ) ) { $wpdb->query( "ALTER TABLE `{$table}` DROP INDEX `id`" ); } } $wpdb->suppress_errors( $suppress ); update_option( 'adfoin_db_indexes_v1', 1, 'no' ); } /** * Plugin deactivation function * * @since 1.0 * @return void */ public function deactivate() { } /** * Function define_constants * * This function defines the plugin's constants. * * @return void */ public function define_constants() { define( 'ADVANCED_FORM_INTEGRATION_VERSION', $this->version ); // Plugin Version define( 'ADVANCED_FORM_INTEGRATION_FILE', __FILE__ ); // Plugin Main Folder Path define( 'ADVANCED_FORM_INTEGRATION_PATH', dirname( ADVANCED_FORM_INTEGRATION_FILE ) ); // Parent Directory Path define( 'ADVANCED_FORM_INTEGRATION_INCLUDES', ADVANCED_FORM_INTEGRATION_PATH . '/includes' ); // Include Folder Path define( 'ADVANCED_FORM_INTEGRATION_URL', plugins_url( '', ADVANCED_FORM_INTEGRATION_FILE ) ); // URL Path define( 'ADVANCED_FORM_INTEGRATION_ASSETS', ADVANCED_FORM_INTEGRATION_URL . '/assets' ); // Asset Folder Path define( 'ADVANCED_FORM_INTEGRATION_VIEWS', ADVANCED_FORM_INTEGRATION_PATH . '/views' ); // View Folder Path define( 'ADVANCED_FORM_INTEGRATION_PLATFORMS', ADVANCED_FORM_INTEGRATION_PATH . '/platforms' ); // View Folder Path define( 'ADVANCED_FORM_INTEGRATION_TEMPLATES', ADVANCED_FORM_INTEGRATION_PATH . '/templates' ); // View Folder Path define( 'ADVANCED_FORM_INTEGRATION_PRO', ADVANCED_FORM_INTEGRATION_PATH . '/pro' ); // View Folder Path } /** * Include the required files * * @since 1.0 * @return void */ public function includes() { require_once ADVANCED_FORM_INTEGRATION_INCLUDES . '/class-adfoin-db.php'; require_once ADVANCED_FORM_INTEGRATION_INCLUDES . '/class-adfoin-admin-header.php'; require_once ADVANCED_FORM_INTEGRATION_INCLUDES . '/class-adfoin-admin-menu.php'; require_once ADVANCED_FORM_INTEGRATION_INCLUDES . '/class-adfoin-integration.php'; require_once ADVANCED_FORM_INTEGRATION_INCLUDES . '/class-adfoin-log.php'; require_once ADVANCED_FORM_INTEGRATION_INCLUDES . '/class-adfoin-submission.php'; // Review nag (admin_notices) and Import/Export handlers // (admin_post_* + admin_notices) are only meaningful in admin // context. Skip them on front-end pageloads so the bottom of the // bootstrap stays cheap. is_admin() is true for wp-admin pages, // admin-ajax.php, and admin-post.php — covering every entry point // these classes hook into. if ( is_admin() ) { require_once ADVANCED_FORM_INTEGRATION_INCLUDES . '/class-adfoin-review.php'; require_once ADVANCED_FORM_INTEGRATION_INCLUDES . '/class-adfoin-import-export.php'; } require_once ADVANCED_FORM_INTEGRATION_INCLUDES . '/class-oauth.php'; require_once ADVANCED_FORM_INTEGRATION_INCLUDES . '/class-adfoin-oauth-manager.php'; require_once ADVANCED_FORM_INTEGRATION_INCLUDES . '/class-adfoin-account-manager.php'; require_once ADVANCED_FORM_INTEGRATION_INCLUDES . '/functions-adfoin.php'; require_once ADVANCED_FORM_INTEGRATION_INCLUDES . '/api/credentials.php'; $trigger_keys = adfoin_get_trigger_keys(); foreach ( $trigger_keys as $trigger_key ) { if ( file_exists( ADVANCED_FORM_INTEGRATION_INCLUDES . '/triggers/' . $trigger_key . '/' . $trigger_key . '.php' ) ) { require_once ADVANCED_FORM_INTEGRATION_INCLUDES . '/triggers/' . $trigger_key . '/' . $trigger_key . '.php'; } } $platform_settings = adfoin_get_action_platform_settings(); foreach ( $platform_settings as $platform => $value ) { if ( true == $value ) { if ( file_exists( ADVANCED_FORM_INTEGRATION_PLATFORMS . "/{$platform}/{$platform}.php" ) ) { require_once ADVANCED_FORM_INTEGRATION_PLATFORMS . "/{$platform}/{$platform}.php"; } } } } /** * Instantiate classes * * @since 1.0 * @return void */ public function init_classes() { // Admin Menu Class new Advanced_Form_Integration_Admin_Menu(); // Submission Handler Class new Advanced_Form_Integration_Submission(); } /** * Initializes action hooks * * @since 1.0 * @return void */ public function init_actions() { add_action( 'init', array($this, 'localization_setup') ); add_action( 'admin_enqueue_scripts', array($this, 'register_scripts') ); add_action( 'wp_enqueue_scripts', array($this, 'register_public_scripts') ); add_action( 'plugins_loaded', array($this, 'load_action_scheduler') ); // One-time DB index migration — admin_init keeps the (possibly slow) // ALTER off front-end requests; it runs once then version-stamps out. add_action( 'admin_init', array($this, 'run_db_index_migration') ); } /** * Load Action Scheduler * * @since 1.0 * @return void */ public function load_action_scheduler() { require_once ADVANCED_FORM_INTEGRATION_PATH . '/library/action-scheduler/action-scheduler.php'; } /** * Initialize plugin for localization * * @since 1.0 * * @uses load_plugin_textdomain() * * @return void */ public function localization_setup() { load_plugin_textdomain( 'advanced-form-integration', false, dirname( plugin_basename( ADVANCED_FORM_INTEGRATION_FILE ) ) . '/languages/' ); } /** * Initializes action filters * * @since 1.0 * @return void */ public function init_filters() { } /** * Register Scripts * * @since 1.0 * @return mixed | void */ public function register_scripts( $hook ) { // Vue 3.5.x production build (minified, ~160 KB, no dev warnings). // The dev build (vue3.global.js) was used for the migration spike // and has been removed. vue.min.js (Vue 2) is kept on disk for // instant rollback — flip this source back to it if needed. // jQuery dependency dropped: Vue does not use jQuery. wp_register_script( 'adfoin-vuejs', ADVANCED_FORM_INTEGRATION_ASSETS . '/js/vue3.global.prod.js', array(), $this->version, 1 ); // Vue 2 -> 3 compatibility shim — must load right after Vue 3 and // before any plugin script (re-creates `new Vue()` / `Vue.component()`). wp_register_script( 'adfoin-vue3-shim', ADVANCED_FORM_INTEGRATION_ASSETS . '/js/vue3-compat-shim.js', array('adfoin-vuejs'), $this->version, 1 ); // Register shared Vue components (registered globally before the app boots) wp_register_script( 'adfoin-searchable-select', ADVANCED_FORM_INTEGRATION_ASSETS . '/js/components/searchable-select.js', array('adfoin-vue3-shim'), $this->version, 1 ); // Register core utilities (helpers + per-platform lazy loader) wp_register_script( 'adfoin-core', ADVANCED_FORM_INTEGRATION_ASSETS . '/js/core.js', array('adfoin-vue3-shim', 'adfoin-searchable-select'), $this->version, 1 ); // Register trigger components wp_register_script( 'adfoin-triggers', ADVANCED_FORM_INTEGRATION_ASSETS . '/js/triggers.js', array('adfoin-core'), $this->version, 1 ); // Register Vue app initialization (lazy-loads one platform component on demand) wp_register_script( 'adfoin-app', ADVANCED_FORM_INTEGRATION_ASSETS . '/js/app.js', array('adfoin-triggers'), $this->version, 1 ); // Backward-compat handle: assets/js/script.js is now an empty stub // kept so any third-party code that explicitly enqueues it still // resolves. All component code lives in platforms//-component.js // and is fetched on demand by adfoinComponentLoader.loadPlatform(). wp_register_script( 'adfoin-main-script', ADVANCED_FORM_INTEGRATION_ASSETS . '/js/script.js', array('adfoin-triggers'), $this->version, 1 ); // Stylesheet, localized payload, and the log-page code editor are // only useful on plugin admin pages. Skipping the rest avoids: // - the unused stylesheet hitting every admin pageload // - the scandir() inside adfoin_get_platform_scripts() // - shipping the localized adfoin object on screens that won't use it // wp_register_script() above remains universal so third-party code // that opts into adfoin-* handles still resolves them. if ( !$this->is_plugin_admin_page( $hook ) ) { return; } wp_enqueue_style( 'adfoin-main-style', ADVANCED_FORM_INTEGRATION_ASSETS . '/css/asset.css', array(), $this->version ); $platform_scripts = ( function_exists( 'adfoin_get_platform_scripts' ) ? adfoin_get_platform_scripts() : array() ); $localize_scripts = array( 'nonce' => wp_create_nonce( 'advanced-form-integration' ), 'delete_confirm' => __( 'Are you sure to delete the integration?', 'advanced-form-integration' ), 'list_url' => admin_url( 'admin.php?page=advanced-form-integration&status=1' ), 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'siteurl' => site_url(), 'assetsUrl' => ADVANCED_FORM_INTEGRATION_ASSETS, 'version' => $this->version, 'loadingText' => __( 'Loading...', 'advanced-form-integration' ), 'platformScripts' => $platform_scripts, ); // Localize to core script (loaded first) wp_localize_script( 'adfoin-core', 'adfoin', $localize_scripts ); // Also localize to legacy main script for backward compatibility wp_localize_script( 'adfoin-main-script', 'adfoin', $localize_scripts ); $this->add_log_code_editor(); } /** * Whether the current admin pageload is one of this plugin's screens. * * Hook names follow WordPress's add_menu_page / add_submenu_page * convention for this plugin: * - top-level: `toplevel_page_advanced-form-integration` * - submenus: `afi_page_advanced-form-integration[-suffix]` * (The `afi_` prefix is derived from the menu title "AFI".) * * @since 1.131.2 * @param string $hook Hook suffix passed to admin_enqueue_scripts. * @return bool */ private function is_plugin_admin_page( $hook ) { if ( empty( $hook ) || !is_string( $hook ) ) { return false; } if ( 'toplevel_page_advanced-form-integration' === $hook ) { return true; } return 0 === strpos( $hook, 'afi_page_advanced-form-integration' ); } /** * Function add_log_code_editor * * This function adds a code editor to the Advanced Form Integration log page. * * @return void */ public function add_log_code_editor() { if ( !function_exists( 'get_current_screen' ) ) { return; } $screen = get_current_screen(); if ( !$screen || 'afi_page_advanced-form-integration-log' !== $screen->id ) { return; } $settings = wp_enqueue_code_editor( array( 'type' => 'application/json', ) ); if ( false === $settings ) { return; } // wp.codeEditor.initialize() prepends "#" to a string id, so pass // the BARE id — "#adfoin-log-request-data" produced the invalid // "##adfoin-log-request-data" selector and threw in newer jQuery. // The existence guard matters because this inline script also runs // on the log *list* screen, which has no request-data textarea. wp_add_inline_script( 'code-editor', sprintf( 'jQuery( function() { if ( document.getElementById( "adfoin-log-request-data" ) ) { wp.codeEditor.initialize( "adfoin-log-request-data", %s ); } } );', wp_json_encode( $settings ) ) ); } /** * Register Public Script * * @since 1.53.0 * @return mixed | void */ public function register_public_scripts() { if ( 1 == get_option( 'adfoin_general_settings_utm' ) ) { wp_enqueue_script( 'js.cookie', ADVANCED_FORM_INTEGRATION_ASSETS . '/js/js.cookie.js', array('jquery'), $this->version, 1 ); wp_enqueue_script( 'afi-utm-grabber', ADVANCED_FORM_INTEGRATION_ASSETS . '/js/utm-grabber.js', array('jquery', 'js.cookie'), $this->version, 1 ); } } } $adfoin = Advanced_Form_Integration::init(); }