Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/handl-utm-grabber/client/build/index.asset.php
+++ b/handl-utm-grabber/client/build/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-dom-ready', 'wp-element'), 'version' => '2fe5a17549977c9219ca');
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-dom-ready', 'wp-element'), 'version' => '0babbdf4047a125b9211');
--- a/handl-utm-grabber/external/ninja.php
+++ b/handl-utm-grabber/external/ninja.php
@@ -20,12 +20,14 @@
$my_merge_tags = array();
$fields = array('utm_source','utm_medium','utm_term', 'utm_content', 'utm_campaign', 'gclid', 'handl_original_ref', 'handl_landing_page', 'handl_ip', 'handl_ref', 'handl_url');
foreach ($fields as $field){
- $cookie_field = isset($_COOKIE[$field]) ? $_COOKIE[$field] : '';
$my_merge_tags[$field] = array(
'id' => $field,
'tag' => '{handl:'.$field.'}', // The tag to be used.
'label' => __( $field, 'handl_utm_grabber' ), // Translatable label for tag selection.
- 'callback' => function() use ($cookie_field) {return urldecode($cookie_field);} // Class method for processing the tag. See below.
+ // Read at tag use.
+ 'callback' => function() use ($field) {
+ return isset($_COOKIE[$field]) ? esc_html($_COOKIE[$field]) : '';
+ }
);
}
--- a/handl-utm-grabber/handl-utm-grabber.php
+++ b/handl-utm-grabber/handl-utm-grabber.php
@@ -5,14 +5,18 @@
* Plugin Name: HandL UTM Grabber
* Description: The easiest way to capture UTMs on your (optin) forms.
* Author: Haktan Suren
- * Version: 2.9.2
+ * Version: 2.9.3
* Author URI: https://www.utmgrabber.com/
- */
+*/
use HandlUtmrabberFreeAdminHandl_React_Pages_Manager;
use HandlUtmrabberFreeAdminHandl_Promos_Manager;
+use HandlUtmrabberFreeIntegrationsHandl_Integrations_Manager;
+use HandlUtmrabberFreeOnboardingHandl_Onboarding_Manager;
define( 'HANDL_UTM_V3_LINK', 'https://utmgrabber.com' );
+$handl_free_header = get_file_data( __FILE__, array( 'Version' => 'Version' ) );
+define( 'HANDL_UTM_GRABBER_FREE_VERSION', isset( $handl_free_header['Version'] ) ? $handl_free_header['Version'] : '' );
define( 'PREMIUM_FEATURES', ['Organic Traffic (Google, Bing etc.)', 'Google Ads (ValueTrack Params e.g. keyword)' , 'Facebook Ads (fbclid)', 'Traffic Source (Paid, Organic, Referrer, Direct)','First/Last attribution', 'Microsoft Ads (msclkid)', 'Affiliate Marketing']);
require_once "external/zapier.php";
@@ -416,7 +420,11 @@
'utm_medium',
'utm_campaign',
'utm_term',
- 'utm_content'
+ 'utm_content',
+ 'handl_landing_page',
+ 'handl_original_ref',
+ 'handl_ip',
+ 'gclid',
);
}
@@ -669,290 +677,6 @@
add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), 'handl_utm_grabber_action_links' );
-function get_test_handl_version(){
- return array(
- 'label' => 'Upgrade your plugin to the premium version.',
- 'status' => 'recommended',
- 'badge' => array(
- 'color' => 'blue',
- 'label' => 'UTM'
- ),
- 'description' => 'To get the full benefit from your tracking experience. We highly recommend updating the plugin to the latest and premium version.',
- 'actions' => '<a href="'.handl_v3_generate_links('handl_version','','health_check').'" target="_blank"><b>Click here</b> <span aria-hidden="true" class="dashicons dashicons-external"></span></a> to learn more',
- 'test' => 'handl_version',
- );
-}
-function get_test_handl_free_audit(){
- return array(
- 'label' => 'Scan your site to make sure your campaign tracking works as expected',
- 'status' => 'recommended',
- 'badge' => array(
- 'color' => 'blue',
- 'label' => 'UTM'
- ),
- 'description' => 'Your site might get benefit from our marketing review',
- 'actions' => '<a href="https://handldigital.com/free-utm-audit/?utm_campaign=UTMAudit&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"><b>Take completely FREE audit </b> <span aria-hidden="true" class="dashicons dashicons-external"></span></a> to improve your tracking and boost up your revenue.',
- 'test' => 'handl_free_audit',
- );
-}
-function handl_utm_site_status_filters($tests){
-
- $tests['direct']['handl_version'] = array(
- 'test' => 'get_test_handl_version',
- );
- $tests['direct']['handl_free_audit'] = array(
- 'test' => 'get_test_handl_free_audit'
- );
- $tests['direct']['handl_caching_enabled'] = array(
- 'test' => 'get_test_handl_caching_enabled'
- );
- if (is_plugin_active('contact-form-7/wp-contact-form-7.php')) {
- $tests['direct']['handl_cf7_shortcodes_used'] = array(
- 'test' => 'get_test_handl_cf7_shortcodes_used'
- );
- }
- if (is_plugin_active('gravityforms/gravityforms.php')) {
- $tests['direct']['handl_gf_shortcodes_used'] = array(
- 'test' => 'get_test_handl_gf_shortcodes_used'
- );
- }
- if (is_plugin_active('ninja-forms/ninja-forms.php')) {
- $tests['direct']['handl_nf_shortcodes_used'] = array(
- 'test' => 'get_test_handl_nf_shortcodes_used'
- );
- }
- return $tests;
-}
-add_filter( 'site_status_tests', 'handl_utm_site_status_filters' );
-
-function get_test_handl_caching_enabled() {
- $cache_exist = false;
-
- $recommendation='';
- if ( function_exists( 'is_wpe' ) || function_exists( 'is_wpe_snapshot' ) ){
- $cache_exist = true;
- $recommendation .= "<li>- You are using WP Engine as your server provider: WP Engine uses server caching and it is known that it stripes query arguments and cookies.</li>";
- }
-
- if ( is_plugin_active('wp-rocket/wp-rocket.php') ){
- $cache_exist = true;
- $recommendation .= "<li>- You are using WP Rocket. WP Rocket does caching and it is known that it stripes query arguments and cookies.</li>";
- }
-
- if (isset($_ENV['PANTHEON_ENVIRONMENT'])) {
- $recommendation .= "<li>- You are using Pantheon as your server provider: Pantheon uses server caching and it is known that it stripes query arguments and cookies.</li>";
- }
-
- $positive = "<p>We could not find and caching plugin installed. Hence you should be good collecting UTMs no problem.</p>";
- $negative = "<p>You might be occasionally missing UTMs or COOKIE parameters due to caching. Here is the list of things we could find that may adversely impacting the data collection.</p>
- <ul>$recommendation</ul>
- ";
-
- $positive_action = 'If you are having trouble collecting UTMs, or if you think you are missing some UTMs, <a href="https://wordpress.org/support/plugin/handl-utm-grabber/" target="_blank"> create a support ticket here <span aria-hidden="true" class="dashicons dashicons-external"></span></a>, we'd be happy to take a look at it for you';
- $negative_action = $positive_action;
-
- return array(
- 'label' => 'You might be missing some UTMs due to server caching',
- 'status' => $cache_exist ? 'recommended' : 'good',
- 'badge' => array(
- 'color' => $cache_exist ? 'red' : 'blue',
- 'label' => 'UTM'
- ),
- 'description' => $cache_exist ? $negative : $positive,
- 'actions' => $cache_exist ? $negative_action : $positive_action,
- 'test' => 'handl_caching_enabled',
- );
-}
-
-function get_test_handl_cf7_shortcodes_used() {
- $posts = WPCF7_ContactForm::find( array(
- 'post_status' => 'any',
- 'posts_per_page' => - 1,
- ) );
-
- $utm_variables = handl_utm_variables();
- $cf7_forms = array();
- $cf7_forms_id2_name = array();
- $cf7_forms_feedback = array();
- foreach ( $posts as $post ) {
- $formID = $post->id();
- $cf7_forms_id2_name[$formID] = $post->title();
- $cf7_forms[$formID] = true;
- /** @var WPCF7_ContactForm $post */
- $props = $post->get_properties();
-
- foreach ($utm_variables as $variable){
- if ( !preg_match("/".preg_quote("[$variable",'/')."/", $props['form'] ) ){
- $cf7_forms[$formID] = false;
- $cf7_forms_feedback[$formID][] = $variable;
- //break;
- }
- }
- }
-
- $all_forms_good = array_filter($cf7_forms, function ($element) {
- return ($element !== true);
- });
-
- $recommendation = '';
- if (sizeof($cf7_forms_feedback) > 0){
- foreach ($cf7_forms_feedback as $id=>$fb){
- $recommendation .= "<p><b>$cf7_forms_id2_name[$id]</b>: ".implode(",",$fb)."</p>";
- }
- }
-
- $positive = "<p>All of your Contact Form 7 set up properly. You are good to go!</p>";
- $negative = "<p>Your Contact Form 7 forms are not capturing all the UTMs recommended. See the list of forms below having problems and resolve to make sure you do not miss any data</p>
- $recommendation
- ";
-
- $positive_action = 'You want to up your game? <a href="https://docs.utmgrabber.com/books/101-getting-started-for-handl-utm-grabber-v3/page/native-wp-shortcodes?utm_campaign=utm_proper_cf7&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"> Click here to get the list of things you can track more <span aria-hidden="true" class="dashicons dashicons-external"></span></a>';
- $negative_action = '<a href="https://docs.utmgrabber.com/books/contact-form-7-integration/page/contact-form-7-utm-tracking?utm_campaign=utm_proper_cf7&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"> Click here to learn the best practice of collecting UTM parameters in Contact Form 7 <span aria-hidden="true" class="dashicons dashicons-external"></span></a>';
-
- return array(
- 'label' => 'Are your capturing/tracking UTMs properly in your Contact Form 7?',
- 'status' => sizeof($all_forms_good) > 0 ? 'recommended' : 'good',
- 'badge' => array(
- 'color' => sizeof($all_forms_good) > 0 ? 'red' : 'blue',
- 'label' => 'UTM'
- ),
- 'description' => sizeof($all_forms_good) > 0 ? $negative : $positive,
- 'actions' => sizeof($all_forms_good) > 0 ? $negative_action : $positive_action,
- 'test' => 'handl_cf7_shortcodes_used',
- );
-}
-
-function get_test_handl_gf_shortcodes_used() {
- $posts = GFAPI::get_forms();
-
- $utm_variables = handl_utm_variables();
- $gf_forms = array();
- $gf_forms_id2_name = array();
- $gf_forms_feedback = array();
- foreach ( $posts as $post ) {
- $formID = $post['id'];
- $gf_forms_id2_name[$formID] = $post['title'];
- $gf_forms[$formID] = true;
-
- $fields = $post['fields'];
-
- foreach ($utm_variables as $variable){
- $check = false;
- foreach ($fields as $field){
- if ($field['inputName'] != ''){
- if ( $variable == $field['inputName'] ){
- $check = true;
- }
- }
- }
- if (!$check){
- $gf_forms[$formID] = false;
- $gf_forms_feedback[$formID][] = $variable;
- }
- }
- }
-
- $all_forms_good = array_filter($gf_forms, function ($element) {
- return ($element !== true);
- });
-
- $recommendation = '';
- if (sizeof($gf_forms_feedback) > 0){
- foreach ($gf_forms_feedback as $id=>$fb){
- $recommendation .= "<p><b>$gf_forms_id2_name[$id]</b>: ".implode(",",$fb)."</p>";
- }
- }
-
- $positive = "<p>All of your Gravity forms set up properly. You are good to go!</p>";
- $negative = "<p>Your Gravity forms are not capturing all the UTMs recommended. See the list of forms below having problems and resolve to make sure you do not miss any data</p>
- $recommendation
- ";
-
- $positive_action = 'You want to up your game? <a href="https://docs.utmgrabber.com/books/101-getting-started-for-handl-utm-grabber-v3/page/native-wp-shortcodes?utm_campaign=utm_proper_gf&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"> Click here to get the list of things you can track more <span aria-hidden="true" class="dashicons dashicons-external"></span></a>';
- $negative_action = '<a href="https://docs.utmgrabber.com/books/gravity-forms-integration/page/gravity-forms-integration?utm_campaign=utm_proper_gf&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"> Click here to learn the best practice of collecting UTM parameters in Gravity Forms <span aria-hidden="true" class="dashicons dashicons-external"></span></a>';
-
- return array(
- 'label' => 'Are your capturing/tracking UTMs properly in your Gravity Form?',
- 'status' => sizeof($all_forms_good) > 0 ? 'recommended' : 'good',
- 'badge' => array(
- 'color' => sizeof($all_forms_good) > 0 ? 'red' : 'blue',
- 'label' => 'UTM'
- ),
- 'description' => sizeof($all_forms_good) > 0 ? $negative : $positive,
- 'actions' => sizeof($all_forms_good) > 0 ? $negative_action : $positive_action,
- 'test' => 'handl_gf_shortcodes_used',
- );
-}
-
-function get_test_handl_nf_shortcodes_used() {
- $posts = Ninja_Forms()->form()->get_forms();
- /** @var NF_Database_Models_Form $post */
- $utm_variables = handl_utm_variables();
- $nf_forms = array();
- $nf_forms_id2_name = array();
- $nf_forms_feedback = array();
- foreach ( $posts as $post ) {
- $form = $post->get_settings();
- $formID = $post->get_id();
- $nf_forms_id2_name[$formID] = $form['title'];
- $nf_forms[$formID] = true;
-
- $fields = $form['formContentData'];
-
- foreach ($utm_variables as $variable){
- $check = false;
- foreach ($fields as $field){
- // Add type check to ensure $field is a string
- if (is_string($field) && $field != ''){
- if ( preg_match("/".preg_quote($variable,'/')."/", $field) ){
- $check = true;
- }
- } elseif (is_array($field) && isset($field['value']) && is_string($field['value'])) {
- // If field is an array, check the 'value' property if it exists
- if ( preg_match("/".preg_quote($variable,'/')."/", $field['value']) ){
- $check = true;
- }
- }
- }
- if (!$check){
- $nf_forms[$formID] = false;
- $nf_forms_feedback[$formID][] = $variable;
- }
- }
- }
-
- $all_forms_good = array_filter($nf_forms, function ($element) {
- return ($element !== true);
- });
-
- $recommendation = '';
- if (sizeof($nf_forms_feedback) > 0){
- foreach ($nf_forms_feedback as $id=>$fb){
- $recommendation .= "<p><b>$nf_forms_id2_name[$id]</b>: ".implode(",",$fb)."</p>";
- }
- }
-
- $positive = "<p>All of your Ninja Forms set up properly. You are good to go!</p>";
- $negative = "<p>Your Ninja forms are not capturing all the UTMs recommended. See the list of forms below having problems and resolve to make sure you do not miss any data</p>
- $recommendation
- ";
-
- $positive_action = 'You want to up your game? <a href="https://docs.utmgrabber.com/books/101-getting-started-for-handl-utm-grabber-v3/page/native-wp-shortcodes?utm_campaign=utm_proper_nf&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"> Click here to get the list of things you can track more <span aria-hidden="true" class="dashicons dashicons-external"></span></a>';
- $negative_action = '<a href="https://docs.utmgrabber.com/books/ninja-forms-integration/page/ninja-forms-integration?utm_campaign=utm_proper_nf&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"> Click here to learn the best practice of collecting UTM parameters in Ninja Forms <span aria-hidden="true" class="dashicons dashicons-external"></span></a>';
-
- return array(
- 'label' => 'Are your capturing/tracking UTMs properly in your Ninja Form?',
- 'status' => sizeof($all_forms_good) > 0 ? 'recommended' : 'good',
- 'badge' => array(
- 'color' => sizeof($all_forms_good) > 0 ? 'red' : 'blue',
- 'label' => 'UTM'
- ),
- 'description' => sizeof($all_forms_good) > 0 ? $negative : $positive,
- 'actions' => sizeof($all_forms_good) > 0 ? $negative_action : $positive_action,
- 'test' => 'handl_nf_shortcodes_used',
- );
-}
-
function handl_v3_generate_links($utm_campaign = '', $utm_source = 'WordPress_FREE', $utm_medium = ''){
$utm_source = $utm_source != '' ? $utm_source : 'WordPress_FREE';
return add_query_arg(array(
@@ -1051,10 +775,48 @@
}
require_once "includes/admin/handl-options.php";
+require_once "includes/onboarding/class-onboarding-manager.php";
+
+$handl_integrations_manager = null;
if ( is_admin() ) {
require_once "includes/integrations/class-integrations-manager.php";
- new HandlUtmrabberFreeIntegrationsHandl_Integrations_Manager();
+ $handl_integrations_manager = new Handl_Integrations_Manager();
+
+ require_once "includes/onboarding/class-setup-notice.php";
+ ( new HandlUtmrabberFreeOnboardingHandl_Setup_Notice( $handl_integrations_manager ) )->register();
+}
+
+require_once "includes/health/class-site-health-manager.php";
+( new HandlUtmrabberFreeHealthHandl_Site_Health_Manager() )->register();
+
+$handl_onboarding_manager = new Handl_Onboarding_Manager( $handl_integrations_manager );
+$handl_onboarding_manager->register_test_listeners();
+
+if ( is_admin() ) {
+ $handl_onboarding_manager->register_admin_hooks();
+}
+
+function handl_utm_grabber_activate() {
+ if ( ! get_option( 'handl_onboarding_completed', false ) ) {
+ update_option( 'handl_onboarding_redirect', true );
+ }
+}
+register_activation_hook( __FILE__, 'handl_utm_grabber_activate' );
+
+function handl_utm_grabber_maybe_redirect() {
+ if ( ! get_option( 'handl_onboarding_redirect', false ) ) {
+ return;
+ }
+ delete_option( 'handl_onboarding_redirect' );
+
+ if ( isset( $_GET['activate-multi'] ) || wp_doing_ajax() || ! current_user_can( 'manage_options' ) ) {
+ return;
+ }
+
+ wp_safe_redirect( admin_url( 'admin.php?page=handl-onboarding' ) );
+ exit;
}
+add_action( 'admin_init', 'handl_utm_grabber_maybe_redirect' );
--- a/handl-utm-grabber/includes/admin/promos.php
+++ b/handl-utm-grabber/includes/admin/promos.php
@@ -5,13 +5,13 @@
class Handl_Promos_Manager
{
- private const PROMOS_ENDPOINT = 'https://api.utmgrabber.com/http/plugin-promos';
- private const TRANSIENT_KEY = 'handl_plugin_promos';
- private const INSTALL_DATE_OPTION = 'handl_utm_grabber_install_date';
- private const DISMISSALS_USER_META_KEY = 'handl_promo_dismissals';
- private const CACHE_DURATION = 6 * HOUR_IN_SECONDS;
+ const PROMOS_ENDPOINT = 'https://api.utmgrabber.com/http/plugin-promos';
+ const TRANSIENT_KEY = 'handl_plugin_promos';
+ const INSTALL_DATE_OPTION = 'handl_utm_grabber_install_date';
+ const DISMISSALS_USER_META_KEY = 'handl_promo_dismissals';
+ const CACHE_DURATION = 6 * HOUR_IN_SECONDS;
- private const VALID_LOCATIONS = [
+ const VALID_LOCATIONS = [
'plugin_banner',
'admin_notice',
'dashboard_widget',
@@ -34,14 +34,14 @@
add_filter( 'handl_promo_menu_badge', [ $this, 'get_sidebar_badge_html' ] );
}
- public function maybe_set_install_date(): void
+ public function maybe_set_install_date()
{
if ( ! get_option( self::INSTALL_DATE_OPTION ) ) {
update_option( self::INSTALL_DATE_OPTION, current_time( 'mysql' ) );
}
}
- public function get_install_date(): string
+ public function get_install_date()
{
$install_date = get_option( self::INSTALL_DATE_OPTION );
if ( ! $install_date ) {
@@ -51,7 +51,7 @@
return $install_date;
}
- public function get_days_since_install(): int
+ public function get_days_since_install()
{
$install_date = $this->get_install_date();
$install_timestamp = strtotime( $install_date );
@@ -63,7 +63,7 @@
// Dismissal Management
// =========================================================================
- public function get_user_dismissals(): array
+ public function get_user_dismissals()
{
$user_id = get_current_user_id();
if ( ! $user_id ) {
@@ -73,7 +73,7 @@
return is_array( $dismissals ) ? $dismissals : [];
}
- public function dismiss_promo( string $campaign_key ): bool
+ public function dismiss_promo( $campaign_key )
{
$user_id = get_current_user_id();
if ( ! $user_id ) {
@@ -84,7 +84,7 @@
return update_user_meta( $user_id, self::DISMISSALS_USER_META_KEY, $dismissals );
}
- public function is_promo_dismissed( string $campaign_key, ?array $promo = null ): bool
+ public function is_promo_dismissed( $campaign_key, $promo = null )
{
$dismissals = $this->get_user_dismissals();
if ( ! isset( $dismissals[ $campaign_key ] ) ) {
@@ -104,7 +104,7 @@
return true;
}
- public function clear_user_dismissals(): bool
+ public function clear_user_dismissals()
{
$user_id = get_current_user_id();
if ( ! $user_id ) {
@@ -117,7 +117,7 @@
// AJAX Endpoints
// =========================================================================
- public function ajax_get_promos(): void
+ public function ajax_get_promos()
{
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 );
@@ -132,7 +132,7 @@
] );
}
- public function ajax_dismiss_promo(): void
+ public function ajax_dismiss_promo()
{
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 );
@@ -157,7 +157,7 @@
// Promo Fetching & Filtering
// =========================================================================
- private function get_promos(): array
+ private function get_promos()
{
$cached = get_transient( self::TRANSIENT_KEY );
if ( $cached !== false ) {
@@ -169,7 +169,7 @@
return $promos;
}
- private function fetch_remote_promos(): array
+ private function fetch_remote_promos()
{
$response = wp_remote_get( self::PROMOS_ENDPOINT, [
'timeout' => 10,
@@ -191,7 +191,7 @@
return is_array( $data ) ? $data : [];
}
- private function filter_promos( array $promos, int $install_days, ?string $location = null ): array
+ private function filter_promos( array $promos, $install_days, $location = null )
{
$now = current_time( 'timestamp' );
@@ -229,7 +229,7 @@
}
if ( $location !== null ) {
- $locations = $promo['display_locations'] ?? [];
+ $locations = isset( $promo['display_locations'] ) ? $promo['display_locations'] : [];
if ( ! in_array( $location, $locations, true ) ) {
return false;
}
@@ -241,7 +241,7 @@
return array_values( $filtered );
}
- public function get_active_promo_for_location( string $location ): ?array
+ public function get_active_promo_for_location( $location )
{
if ( ! in_array( $location, self::VALID_LOCATIONS, true ) ) {
return null;
@@ -256,15 +256,15 @@
}
usort( $filtered, function( $a, $b ) {
- $date_a = strtotime( $a['date_end'] ?? '9999-12-31' );
- $date_b = strtotime( $b['date_end'] ?? '9999-12-31' );
+ $date_a = strtotime( isset( $a['date_end'] ) ? $a['date_end'] : '9999-12-31' );
+ $date_b = strtotime( isset( $b['date_end'] ) ? $b['date_end'] : '9999-12-31' );
return $date_a - $date_b;
} );
return $filtered[0];
}
- public function has_active_promo_for_location( string $location ): bool
+ public function has_active_promo_for_location( $location )
{
return $this->get_active_promo_for_location( $location ) !== null;
}
@@ -273,7 +273,7 @@
// URL Generation
// =========================================================================
- public function get_promo_settings_url( string $campaign_key ): string
+ public function get_promo_settings_url( $campaign_key )
{
return admin_url( 'admin.php?page=handl-utm-grabber.php#/handl-options?promo=' . urlencode( $campaign_key ) );
}
@@ -282,7 +282,7 @@
// Admin Notice
// =========================================================================
- public function render_admin_notice(): void
+ public function render_admin_notice()
{
if ( ! current_user_can( 'manage_options' ) ) {
return;
@@ -309,7 +309,7 @@
// Dashboard Widget
// =========================================================================
- public function register_dashboard_widget(): void
+ public function register_dashboard_widget()
{
if ( ! current_user_can( 'manage_options' ) ) {
return;
@@ -327,7 +327,7 @@
);
}
- public function render_dashboard_widget_content(): void
+ public function render_dashboard_widget_content()
{
$promo = $this->get_active_promo_for_location( 'dashboard_widget' );
if ( ! $promo ) {
@@ -350,7 +350,7 @@
// Admin Bar
// =========================================================================
- public function render_admin_bar_item( WP_Admin_Bar $admin_bar ): void
+ public function render_admin_bar_item( WP_Admin_Bar $admin_bar )
{
if ( ! current_user_can( 'manage_options' ) ) {
return;
@@ -384,7 +384,7 @@
// Sidebar Badge
// =========================================================================
- public function get_sidebar_badge_html(): string
+ public function get_sidebar_badge_html()
{
$promo = $this->get_active_promo_for_location( 'sidebar_badge' );
if ( ! $promo ) {
@@ -398,7 +398,7 @@
// Admin Styles & Scripts
// =========================================================================
- public function render_admin_styles(): void
+ public function render_admin_styles()
{
?>
<style>
@@ -454,7 +454,7 @@
<?php
}
- public function render_dismiss_script(): void
+ public function render_dismiss_script()
{
?>
<script>
@@ -507,7 +507,7 @@
<?php
}
- public static function clear_cache(): bool
+ public static function clear_cache()
{
return delete_transient( self::TRANSIENT_KEY );
}
--- a/handl-utm-grabber/includes/admin/react-admin.php
+++ b/handl-utm-grabber/includes/admin/react-admin.php
@@ -16,6 +16,7 @@
// Register admin page
add_action('admin_menu', [$this, 'add_react_menu_pages'],1);
+ add_action('admin_menu', [$this, 'reorder_setup_submenu'], 999);
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
}
@@ -54,6 +55,35 @@
[ $this, 'render_analytics_page' ]
);
+ add_submenu_page(
+ 'handl-utm-grabber.php',
+ 'UTM Grabber Setup',
+ 'Setup',
+ 'manage_options',
+ 'handl-onboarding',
+ [ $this, 'render_onboarding_page' ]
+ );
+ }
+
+ /**
+ * Move the "Setup" submenu to the top, above the default UTM item.
+ */
+ public function reorder_setup_submenu()
+ {
+ global $submenu;
+ $parent = 'handl-utm-grabber.php';
+ if ( empty( $submenu[ $parent ] ) ) {
+ return;
+ }
+ foreach ( $submenu[ $parent ] as $i => $item ) {
+ if ( isset( $item[2] ) && $item[2] === 'handl-onboarding' ) {
+ $setup = $item;
+ unset( $submenu[ $parent ][ $i ] );
+ array_unshift( $submenu[ $parent ], $setup );
+ $submenu[ $parent ] = array_values( $submenu[ $parent ] );
+ break;
+ }
+ }
}
/**
@@ -66,6 +96,9 @@
public function render_analytics_page() {
$this->render_react_container( 'handl_analytics' );
}
+ public function render_onboarding_page() {
+ $this->render_react_container( 'handl_onboarding' );
+ }
private function render_react_container($container_id)
{
printf(
@@ -85,6 +118,8 @@
$allowed_hooks = [
'toplevel_page_handl-utm-grabber',
'utm_page_handl_analytics',
+ 'utm_page_handl-onboarding',
+ 'admin_page_handl-onboarding',
];
if (! in_array($hook_suffix, $allowed_hooks, true)) {
return;
@@ -98,6 +133,13 @@
$script_asset = require $script_asset_path;
+ wp_enqueue_style(
+ 'handl-react-fonts',
+ 'https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600;9..144,700;9..144,800&family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap',
+ [],
+ null
+ );
+
wp_enqueue_script(
'handl-react-main-script',
$this->plugin_url . '../../client/build/index.js',
@@ -129,9 +171,14 @@
),
$script_asset['version']
);
+ $current_user = wp_get_current_user();
$wp_api_props = apply_filters( 'handl_react_admin_localize', [
- 'ajax_url' => admin_url('admin-ajax.php'),
- 'nonce' => array(),
+ 'ajax_url' => admin_url('admin-ajax.php'),
+ 'nonce' => array(),
+ 'current_user' => array(
+ 'email' => $current_user ? $current_user->user_email : '',
+ 'first_name' => $current_user ? ( get_user_meta( $current_user->ID, 'first_name', true ) ?: '' ) : '',
+ ),
] );
wp_localize_script('handl-react-main-script', 'wpAPIProps', $wp_api_props);
// wp_localize_script('handl-react-main-script', 'appProps', [
--- a/handl-utm-grabber/includes/health/class-site-health-manager.php
+++ b/handl-utm-grabber/includes/health/class-site-health-manager.php
@@ -0,0 +1,263 @@
+<?php
+namespace HandlUtmrabberFreeHealth;
+
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+require_once dirname( __DIR__ ) . '/integrations/class-integration.php';
+require_once dirname( __DIR__ ) . '/integrations/gravity-forms/class-gravity-forms-integration.php';
+require_once dirname( __DIR__ ) . '/integrations/contact-form-7/class-contact-form-7-integration.php';
+require_once dirname( __DIR__ ) . '/integrations/ninja-forms/class-ninja-forms-integration.php';
+require_once dirname( __DIR__ ) . '/integrations/elementor/class-elementor-integration.php';
+
+use HandlUtmrabberFreeIntegrationsGravity_Forms_Integration;
+use HandlUtmrabberFreeIntegrationsContact_Form_7_Integration;
+use HandlUtmrabberFreeIntegrationsNinja_Forms_Integration;
+use HandlUtmrabberFreeIntegrationsElementor_Integration;
+
+// Registers Site Health tests; owns form integration instances for safe `direct` tests during cron, without relying on the admin-only integrations manager.
+class Handl_Site_Health_Manager {
+
+ /** @var array<string, HandlUtmrabberFreeIntegrationsHandl_Integration>|null */
+ private $integrations = null;
+
+ public function register() {
+ add_filter( 'site_status_tests', array( $this, 'add_tests' ) );
+ }
+
+ /** @return array<string, HandlUtmrabberFreeIntegrationsHandl_Integration> */
+ private function integrations() {
+ if ( $this->integrations === null ) {
+ $this->integrations = array(
+ 'contact-form-7' => new Contact_Form_7_Integration(),
+ 'gravity-forms' => new Gravity_Forms_Integration(),
+ 'ninja-forms' => new Ninja_Forms_Integration(),
+ 'elementor' => new Elementor_Integration(),
+ );
+ }
+ return $this->integrations;
+ }
+
+ public function add_tests( $tests ) {
+ $tests['direct']['handl_version'] = array(
+ 'test' => array( $this, 'test_version' ),
+ );
+ $tests['direct']['handl_free_audit'] = array(
+ 'test' => array( $this, 'test_free_audit' ),
+ );
+ $tests['direct']['handl_caching_enabled'] = array(
+ 'test' => array( $this, 'test_caching' ),
+ );
+
+ $config = $this->form_tests_config();
+
+ foreach ( $this->integrations() as $integration ) {
+ $slug = $integration->get_slug();
+ if ( ! isset( $config[ $slug ] ) || ! $integration->is_active() ) {
+ continue;
+ }
+ $copy = $config[ $slug ];
+ $tests['direct'][ $copy['test'] ] = array(
+ 'test' => function () use ( $integration, $copy ) {
+ return $this->form_test( $integration, $copy );
+ },
+ );
+ }
+
+ return $tests;
+ }
+
+ public function test_version() {
+ return array(
+ 'label' => 'Upgrade your plugin to the premium version.',
+ 'status' => 'recommended',
+ 'badge' => array(
+ 'color' => 'blue',
+ 'label' => 'UTM',
+ ),
+ 'description' => 'To get the full benefit from your tracking experience. We highly recommend updating the plugin to the latest and premium version.',
+ 'actions' => '<a href="' . handl_v3_generate_links( 'handl_version', '', 'health_check' ) . '" target="_blank"><b>Click here</b> <span aria-hidden="true" class="dashicons dashicons-external"></span></a> to learn more',
+ 'test' => 'handl_version',
+ );
+ }
+
+ public function test_free_audit() {
+ return array(
+ 'label' => 'Scan your site to make sure your campaign tracking works as expected',
+ 'status' => 'recommended',
+ 'badge' => array(
+ 'color' => 'blue',
+ 'label' => 'UTM',
+ ),
+ 'description' => 'Your site might get benefit from our marketing review',
+ 'actions' => '<a href="https://handldigital.com/free-utm-audit/?utm_campaign=UTMAudit&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"><b>Take completely FREE audit </b> <span aria-hidden="true" class="dashicons dashicons-external"></span></a> to improve your tracking and boost up your revenue.',
+ 'test' => 'handl_free_audit',
+ );
+ }
+
+ public function test_caching() {
+ if ( ! function_exists( 'is_plugin_active' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
+ }
+
+ $cache_exist = false;
+ $recommendation = '';
+
+ if ( function_exists( 'is_wpe' ) || function_exists( 'is_wpe_snapshot' ) ) {
+ $cache_exist = true;
+ $recommendation .= "<li>- You are using WP Engine as your server provider: WP Engine uses server caching and it is known that it stripes query arguments and cookies.</li>";
+ }
+
+ if ( is_plugin_active( 'wp-rocket/wp-rocket.php' ) ) {
+ $cache_exist = true;
+ $recommendation .= "<li>- You are using WP Rocket. WP Rocket does caching and it is known that it stripes query arguments and cookies.</li>";
+ }
+
+ if ( isset( $_ENV['PANTHEON_ENVIRONMENT'] ) ) {
+ $recommendation .= "<li>- You are using Pantheon as your server provider: Pantheon uses server caching and it is known that it stripes query arguments and cookies.</li>";
+ }
+
+ $positive = "<p>We could not find and caching plugin installed. Hence you should be good collecting UTMs no problem.</p>";
+ $negative = "<p>You might be occasionally missing UTMs or COOKIE parameters due to caching. Here is the list of things we could find that may adversely impacting the data collection.</p>
+ <ul>$recommendation</ul>
+ ";
+
+ $positive_action = 'If you are having trouble collecting UTMs, or if you think you are missing some UTMs, <a href="https://wordpress.org/support/plugin/handl-utm-grabber/" target="_blank"> create a support ticket here <span aria-hidden="true" class="dashicons dashicons-external"></span></a>, we'd be happy to take a look at it for you';
+ $negative_action = $positive_action;
+
+ return array(
+ 'label' => 'You might be missing some UTMs due to server caching',
+ 'status' => $cache_exist ? 'recommended' : 'good',
+ 'badge' => array(
+ 'color' => $cache_exist ? 'red' : 'blue',
+ 'label' => 'UTM',
+ ),
+ 'description' => $cache_exist ? $negative : $positive,
+ 'actions' => $cache_exist ? $negative_action : $positive_action,
+ 'test' => 'handl_caching_enabled',
+ );
+ }
+
+ /**
+ * Per-integration Site Health copy/docs links, keyed by integration slug.
+ *
+ * @return array<string, array>
+ */
+ private function form_tests_config() {
+ return array(
+ 'contact-form-7' => array(
+ 'label' => 'Are your capturing/tracking UTMs properly in your Contact Form 7?',
+ 'positive' => "<p>All of your Contact Form 7 set up properly. You are good to go!</p>",
+ 'negative_intro' => "<p>Your Contact Form 7 forms are not capturing all the UTMs recommended. See the list of forms below having problems and resolve to make sure you do not miss any data</p>",
+ 'positive_action' => 'You want to up your game? <a href="https://docs.utmgrabber.com/books/101-getting-started-for-handl-utm-grabber-v3/page/native-wp-shortcodes?utm_campaign=utm_proper_cf7&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"> Click here to get the list of things you can track more <span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
+ 'negative_action' => '<a href="https://docs.utmgrabber.com/books/contact-form-7-integration/page/contact-form-7-utm-tracking?utm_campaign=utm_proper_cf7&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"> Click here to learn the best practice of collecting UTM parameters in Contact Form 7 <span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
+ 'test' => 'handl_cf7_shortcodes_used',
+ ),
+ 'gravity-forms' => array(
+ 'label' => 'Are your capturing/tracking UTMs properly in your Gravity Form?',
+ 'positive' => "<p>All of your Gravity forms set up properly. You are good to go!</p>",
+ 'negative_intro' => "<p>Your Gravity forms are not capturing all the UTMs recommended. See the list of forms below having problems and resolve to make sure you do not miss any data</p>",
+ 'positive_action' => 'You want to up your game? <a href="https://docs.utmgrabber.com/books/101-getting-started-for-handl-utm-grabber-v3/page/native-wp-shortcodes?utm_campaign=utm_proper_gf&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"> Click here to get the list of things you can track more <span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
+ 'negative_action' => '<a href="https://docs.utmgrabber.com/books/gravity-forms-integration/page/gravity-forms-integration?utm_campaign=utm_proper_gf&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"> Click here to learn the best practice of collecting UTM parameters in Gravity Forms <span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
+ 'test' => 'handl_gf_shortcodes_used',
+ ),
+ 'ninja-forms' => array(
+ 'label' => 'Are your capturing/tracking UTMs properly in your Ninja Form?',
+ 'positive' => "<p>All of your Ninja Forms set up properly. You are good to go!</p>",
+ 'negative_intro' => "<p>Your Ninja forms are not capturing all the UTMs recommended. See the list of forms below having problems and resolve to make sure you do not miss any data</p>",
+ 'positive_action' => 'You want to up your game? <a href="https://docs.utmgrabber.com/books/101-getting-started-for-handl-utm-grabber-v3/page/native-wp-shortcodes?utm_campaign=utm_proper_nf&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"> Click here to get the list of things you can track more <span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
+ 'negative_action' => '<a href="https://docs.utmgrabber.com/books/ninja-forms-integration/page/ninja-forms-integration?utm_campaign=utm_proper_nf&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"> Click here to learn the best practice of collecting UTM parameters in Ninja Forms <span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
+ 'test' => 'handl_nf_shortcodes_used',
+ ),
+ 'elementor' => array(
+ 'label' => 'Are your capturing/tracking UTMs properly in your Elementor forms?',
+ 'positive' => "<p>All of your Elementor forms set up properly. You are good to go!</p>",
+ 'negative_intro' => "<p>Your Elementor forms are not capturing all the UTMs recommended. See the list of forms below having problems and resolve to make sure you do not miss any data</p>",
+ 'positive_action' => 'You want to up your game? <a href="https://docs.utmgrabber.com/books/101-getting-started-for-handl-utm-grabber-v3/page/native-wp-shortcodes?utm_campaign=utm_proper_elementor&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"> Click here to get the list of things you can track more <span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
+ 'negative_action' => '<a href="https://docs.utmgrabber.com/books/elementor-integration/page/native-elementor-form-support?utm_campaign=utm_proper_elementor&utm_source=WordPress_FREE&utm_medium=health_check" target="_blank"> Click here to learn the best practice of collecting UTM parameters in Elementor <span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
+ 'test' => 'handl_elementor_shortcodes_used',
+ ),
+ );
+ }
+
+ /**
+ * Build a Site Health result for a form integration: flags any form that
+ * is missing one or more tracked params, using get_form_status().
+ *
+ * @param HandlUtmrabberFreeIntegrationsHandl_Integration $integration
+ * @param array $copy
+ * @return array
+ */
+ private function form_test( $integration, $copy ) {
+ $problem_forms = array();
+
+ foreach ( $integration->get_forms() as $form ) {
+ $status = $integration->get_form_status( $form['id'] );
+ if ( $status['status'] !== 'complete' && ! empty( $status['missing'] ) ) {
+ $problem_forms[] = array(
+ 'title' => isset( $form['title'] ) ? (string) $form['title'] : '',
+ 'missing' => $status['missing'],
+ 'integrated' => $status['integrated'],
+ );
+ }
+ }
+
+ $bad = ! empty( $problem_forms );
+
+ if ( ! $bad ) {
+ return array(
+ 'label' => $copy['label'],
+ 'status' => 'good',
+ 'badge' => array(
+ 'color' => 'blue',
+ 'label' => 'UTM',
+ ),
+ 'description' => $copy['positive'],
+ 'actions' => $copy['positive_action'],
+ 'test' => $copy['test'],
+ );
+ }
+
+ $items = '';
+ foreach ( $problem_forms as $pf ) {
+ $captured = empty( $pf['integrated'] ) ? 'none yet' : $this->param_chips( $pf['integrated'] );
+ $items .= '<li><strong>' . esc_html( $pf['title'] ) . '</strong><br>'
+ . '<span class="dashicons dashicons-warning" aria-hidden="true"></span> Missing: ' . $this->param_chips( $pf['missing'], true ) . '<br>'
+ . '<span class="dashicons dashicons-yes" aria-hidden="true"></span> Captured: ' . $captured . '</li>';
+ }
+
+ $description = $copy['negative_intro']
+ . '<ul>' . $items . '</ul>'
+ . '<p>Use the one click setup to add the missing fields automatically, or follow the guide below to do it manually.</p>';
+
+ $setup_url = admin_url( 'admin.php?page=handl-onboarding#/connect' );
+ $setup_btn = '<a class="button button-primary" href="' . esc_url( $setup_url ) . '">One Click Setup</a>';
+
+ return array(
+ 'label' => $copy['label'],
+ 'status' => 'recommended',
+ 'badge' => array(
+ 'color' => 'red',
+ 'label' => 'UTM',
+ ),
+ 'description' => $description,
+ 'actions' => '<span style="display:inline-flex;align-items:center;gap:12px;flex-wrap:wrap;">' . $setup_btn . $copy['negative_action'] . '</span>',
+ 'test' => $copy['test'],
+ );
+ }
+
+ /**
+ * Render param keys as inline <code> chips.
+ *
+ * @param string[] $params
+ * @param bool $danger Tint the chips red (for missing params).
+ * @return string
+ */
+ private function param_chips( $params, $danger = false ) {
+ $style = $danger ? ' style="background:#fcf0f1;color:#d63638;padding:1px 6px;border-radius:3px;"' : '';
+ $chips = array();
+ foreach ( $params as $param ) {
+ $chips[] = '<code' . $style . '>' . esc_html( $param ) . '</code>';
+ }
+ return implode( ' ', $chips );
+ }
+}
--- a/handl-utm-grabber/includes/integrations/class-integration.php
+++ b/handl-utm-grabber/includes/integrations/class-integration.php
@@ -28,6 +28,45 @@
return handl_lite_tracking_params();
}
+ /**
+ * Per-form status. @param string|int $form_id
+ * @return array{form_id:string,status:string,integrated:string[],missing:string[]} status: complete|partial|none|not_found.
+ */
+ public function get_form_status( $form_id ) {
+ $present = $this->detect_integrated_params( $form_id );
+
+ if ( $present === null ) {
+ return array(
+ 'form_id' => (string) $form_id,
+ 'status' => 'not_found',
+ 'integrated' => array(),
+ 'missing' => array(),
+ );
+ }
+
+ $tracked = array_map( 'strval', $this->get_tracked_params() );
+ $integrated = array_values( array_intersect( $tracked, $present ) );
+ $missing = array_values( array_diff( $tracked, $integrated ) );
+
+ if ( empty( $integrated ) ) {
+ $status = 'none';
+ } elseif ( empty( $missing ) ) {
+ $status = 'complete';
+ } else {
+ $status = 'partial';
+ }
+
+ return array(
+ 'form_id' => (string) $form_id,
+ 'status' => $status,
+ 'integrated' => $integrated,
+ 'missing' => $missing,
+ );
+ }
+
+ /** Tracked params present on the form (current + legacy formats). @return string[]|null null if form not loadable. */
+ abstract protected function detect_integrated_params( $form_id );
+
public function to_array() {
return array(
'slug' => $this->get_slug(),
--- a/handl-utm-grabber/includes/integrations/class-integrations-manager.php
+++ b/handl-utm-grabber/includes/integrations/class-integrations-manager.php
@@ -38,6 +38,80 @@
$this->integrations[ $integration->get_slug() ] = $integration;
}
+ /** @return array<string, Handl_Integration> */
+ public function get_integrations() {
+ return $this->integrations;
+ }
+
+ /** @return Handl_Integration|null */
+ public function get_integration( $slug ) {
+ return isset( $this->integrations[ $slug ] ) ? $this->integrations[ $slug ] : null;
+ }
+
+ /**
+ * Roll up per-form status for one integration. @param string $slug
+ * @return array{slug:string,active:bool,status:string,total_forms:int,integrated_forms:int,forms:array} status: complete (all forms complete)|partial|none.
+ */
+ public function get_integration_status( $slug ) {
+ $integration = $this->get_integration( $slug );
+
+ if ( ! $integration || ! $integration->is_active() ) {
+ return array(
+ 'slug' => $integration ? $integration->get_slug() : (string) $slug,
+ 'active' => false,
+ 'status' => 'none',
+ 'total_forms' => 0,
+ 'integrated_forms' => 0,
+ 'forms' => array(),
+ );
+ }
+
+ $forms = array_values( $integration->get_forms() );
+ $form_statuses = array();
+ $integrated_forms = 0;
+ $complete_forms = 0;
+
+ foreach ( $forms as $form ) {
+ $status = $integration->get_form_status( $form['id'] );
+ $status['title'] = isset( $form['title'] ) ? (string) $form['title'] : '';
+ $form_statuses[] = $status;
+
+ if ( $status['status'] === 'complete' ) {
+ $complete_forms++;
+ $integrated_forms++;
+ } elseif ( $status['status'] === 'partial' ) {
+ $integrated_forms++;
+ }
+ }
+
+ $total = count( $form_statuses );
+ if ( $integrated_forms === 0 ) {
+ $overall = 'none';
+ } elseif ( $total > 0 && $complete_forms === $total ) {
+ $overall = 'complete';
+ } else {
+ $overall = 'partial';
+ }
+
+ return array(
+ 'slug' => $integration->get_slug(),
+ 'active' => true,
+ 'status' => $overall,
+ 'total_forms' => $total,
+ 'integrated_forms' => $integrated_forms,
+ 'forms' => $form_statuses,
+ );
+ }
+
+ /** @return array<string, array> Status roll-up keyed by integration slug. */
+ public function get_all_statuses() {
+ $out = array();
+ foreach ( $this->integrations as $slug => $integration ) {
+ $out[ $slug ] = $this->get_integration_status( $slug );
+ }
+ return $out;
+ }
+
/**
* @param array $data
* @return array
@@ -59,12 +133,12 @@
*/
private function authorize_and_resolve() {
if ( ! current_user_can( 'manage_options' ) ) {
- wp_send_json_error( 'Unauthorized access. Admin privileges required.' );
+ wp_send_json_error( array( 'message' => 'Unauthorized access. Admin privileges required.' ) );
return null;
}
if ( ! check_ajax_referer( self::NONCE_ACTION, 'nonce', false ) ) {
- wp_send_json_error( 'Invalid nonce.' );
+ wp_send_json_error( array( 'message' => 'Invalid nonce.' ) );
return null;
}
@@ -74,7 +148,7 @@
}
if ( ! isset( $this->integrations[ $slug ] ) ) {
- wp_send_json_error( 'Unknown integration.' );
+ wp_send_json_error( array( 'message' => 'Unknown integration.' ) );
return null;
}
@@ -83,12 +157,12 @@
public function ajax_list() {
if ( ! current_user_can( 'manage_options' ) ) {
- wp_send_json_error( 'Unauthorized access. Admin privileges required.' );
+ wp_send_json_error( array( 'message' => 'Unauthorized access. Admin privileges required.' ) );
return;
}
if ( ! check_ajax_referer( self::NONCE_ACTION, 'nonce', false ) ) {
- wp_send_json_error( 'Invalid nonce.' );
+ wp_send_json_error( array( 'message' => 'Invalid nonce.' ) );
return;
}
@@ -107,7 +181,7 @@
}
if ( ! $integration->is_active() ) {
- wp_send_json_error( 'Integration is not active on this site.' );
+ wp_send_json_error( array( 'message' => 'Integration is not active on this site.' ) );
return;
}
@@ -121,13 +195,13 @@
}
if ( ! $integration->is_active() ) {
- wp_send_json_error( 'Integration is not active on this site.' );
+ wp_send_json_error( array( 'message' => 'Integration is not active on this site.' ) );
return;
}
$action = isset( $_POST['action_type'] ) ? sanitize_key( wp_unslash( $_POST['action_type'] ) ) : 'add';
if ( ! in_array( $action, array( 'add', 'remove' ), true ) ) {
- wp_send_json_error( 'Invalid action.' );
+ wp_send_json_error( array( 'message' => 'Invalid action.' ) );
return;
}
@@ -135,7 +209,7 @@
$fields = $this->decode_array_param( 'fields' );
if ( empty( $form_ids ) ) {
- wp_send_json_error( 'No forms selected.' );
+ wp_send_json_error( array( 'message' => 'No forms selected.' ) );
return;
}
@@ -143,7 +217,7 @@
$allowed = $integration->get_tracked_params();
$fields = array_values( array_intersect( $fields, $allowed ) );
if ( empty( $fields ) ) {
- wp_send_json_error( 'No valid fields selected.' );
+ wp_send_json_error( array( 'message' => 'No valid fields selected.' ) );
return;
}
}
@@ -161,6 +235,7 @@
* @param string $key
* @return array
*/
+ /** Decode a JSON assoc-array POST param (e.g. CF7's `also_add_to_email`). @return array */
private function decode_assoc_param( $key ) {
if ( ! isset( $_POST[ $key ] ) ) {
return array();
--- a/handl-utm-grabber/includes/integrations/contact-form-7/class-contact-form-7-integration.php
+++ b/handl-utm-grabber/includes/integrations/contact-form-7/class-contact-form-7-integration.php
@@ -148,6 +148,31 @@
return $results;
}
+ protected function detect_integrated_params( $form_id ) {
+ if ( ! $this->is_active() ) {
+ return null;
+ }
+
+ $cf = WPCF7_ContactForm::get_instance( (int) $form_id );
+ if ( ! $cf ) {
+ return null;
+ }
+
+ $props = $cf->get_properties();
+ $form_text = isset( $props['form'] ) ? (string) $props['form'] : '';
+
+ $present = array();
+ foreach ( $this->get_tracked_params() as $param ) {
+ // Matches marker-block + legacy stray `[hidden {param}_cf7 ...]` tags.
+ $pattern = '/[hiddens+' . preg_quote( $param . '_cf7', '/' ) . '[^]]*]/';
+ if ( preg_match( $pattern, $form_text ) === 1 ) {
+ $present[] = (string) $param;
+ }
+ }
+
+ return $present;
+ }
+
/**
* Remove all content between (and including) the provided markers, along with any following single newline to prevent leftover blank lines.
*
--- a/handl-utm-grabber/includes/integrations/elementor/class-elementor-integration.php
+++ b/handl-utm-grabber/includes/integrations/elementor/class-elementor-integration.php
@@ -199,6 +199,65 @@
return $results;
}
+ protected function detect_integrated_params( $form_id ) {
+ if ( ! $this->is_active() ) {
+ return null;
+ }
+
+ $parts = explode( ':', (string) $form_id, 2 );
+ if ( count( $parts ) !== 2 ) {
+ return null;
+ }
+ $post_id = (int) $parts[0];
+ $widget_id = (string) $parts[1];
+ if ( $post_id <= 0 || $widget_id === '' ) {
+ return null;
+ }
+
+ $raw = get_post_meta( $post_id, '_elementor_data', true );
+ $tree = is_string( $raw ) ? json_decode( $raw, true ) : null;
+ if ( ! is_array( $tree ) ) {
+ return null;
+ }
+
+ $widget = &$this->find_form_widget( $tree, $widget_id );
+ if ( $widget === null ) {
+ unset( $widget );
+ return null;
+ }
+
+ $fields = ( isset( $widget['settings']['form_fields'] ) && is_array( $widget['settings']['form_fields'] ) )
+ ? $widget['settings']['form_fields']
+ : array();
+ unset( $widget );
+
+ $tracked = array_map( 'strval', $this->get_tracked_params() );
+ $present = array();
+
+ foreach ( $fields as $field ) {
+ $type = isset( $field['field_type'] ) ? (string) $field['field_type'] : '';
+ $cid = isset( $field['custom_id'] ) ? (string) $field['custom_id'] : '';
+ $label = isset( $field['field_label'] ) ? (string) $field['field_label'] : '';
+
+ foreach ( $tracked as $param ) {
+ if ( in_array( $param, $present, true ) ) {
+ continue;
+ }
+ // Primary: hidden field with custom_id === param.
+ if ( $type === 'hidden' && $cid === $param ) {
+ $present[] = $param;
+ continue;
+ }
+ // Legacy: HandL-labelled field referencing the param.
+ if ( strpos( $label, 'HandL' ) !== false && strpos( $label, $param ) !== false ) {
+ $present[] = $param;
+ }
+ }
+ }
+
+ return array_values( array_unique( $present ) );
+ }
+
/**
* Recursively walk an Elementor tree, collecting every node with
* `widgetType === 'form'`.
@@ -326,9 +385,9 @@
array_filter(
$widget['settings']['form_fields'],
function ( $field ) use ( $tracked ) {
- $type = (string) ( $field['field_type'] ?? '' );
- $cid = (string) ( $field['custom_id'] ?? '' );
- $dyn = (string) ( $field['__dynamic__']['field_value'] ?? '' );
+ $type = isset( $field['field_type'] ) ? (string) $field['field_type'] : '';
+ $cid = isset( $field['custom_id'] ) ? (string) $field['custom_id'] : '';
+ $dyn = isset( $field['__dynamic__']['field_value'] ) ? (string) $field['__dynamic__']['field_value'] : '';
// (a) Our custom_id-flow fields.
if ( $type === 'hidden' && in_array( $cid, $tracked, true ) ) {
--- a/handl-utm-grabber/includes/integrations/gravity-forms/class-gravity-forms-integration.php
+++ b/handl-utm-grabber/includes/integrations/gravity-forms/class-gravity-forms-integration.php
@@ -91,6 +91,42 @@
return $results;
}
+ protected function detect_integrated_params( $form_id ) {
+ if ( ! $this->is_active() ) {
+ return null;
+ }
+
+ $form = GFAPI::get_form( (int) $form_id );
+ if ( ! $form || ! isset( $form['fields'] ) || ! is_array( $form['fields'] ) ) {
+ return null;
+ }
+
+ $tracked = array_map( 'strval', $this->get_tracked_params() );
+ $present = array();
+
+ foreach ( $form['fields'] as $field ) {
+ $input_name = isset( $field->inputName ) ? (string) $field->inputName : ( isset( $field['inputName'] ) ? (string) $field['inputName'] : '' );
+ $label = isset( $field->label ) ? (string) $field->label : ( isset( $field['label'] ) ? (string) $field['label'] : '' );
+
+ foreach ( $tracked as $param ) {
+ if ( in_array( $param, $present, true ) ) {
+ continue;
+ }
+ // Primary: inputName === param.
+ if ( $input_name === $param ) {
+ $present[] = $param;
+ continue;
+ }
+ // Legacy
+ if ( strpos( $label, 'HandL' ) !== false && strpos( $label, $param ) !== false ) {
+ $present[] = $param;
+ }
+ }
+ }
+
+ return array_values( array_unique( $present ) );
+ }
+
/**
* Append HandL hidden fields to a form for any param not already present.
*
--- a/handl-utm-grabber/includes/integrations/ninja-forms/class-ninja-forms-integration.php
+++ b/handl-utm-grabber/includes/integrations/ninja-forms/class-ninja-forms-integration.php
@@ -87,6 +87,37 @@
return $results;
}
+ protected function detect_integrated_params( $form_id ) {
+ if ( ! $this->is_active() ) {
+ return null;
+ }
+
+ $fields = Ninja_Forms()->form( (int) $form_id )->get_fields();
+ if ( ! is_array( $fields ) && ! is_object( $fields ) ) {
+ return null;
+ }
+
+ $tracked = array_map( 'strval', $this->get_tracked_params() );
+ $present = array();
+
+ foreach ( $fields as $field ) {
+ $key = (string) $field->get_setting( 'key' );
+ $default = (string) $field->get_setting( 'default' );
+
+ foreach ( $tracked as $param ) {
+ if ( in_array( $param, $present, true ) ) {
+ continue;
+ }
+ // Primary: key === param. Legacy: default is `{handl:param}`.
+ if ( $key === $param || $default === '{handl:' . $param . '}' ) {
+ $present[] = $param;
+ }
+ }
+ }
+
+ return array_values( array_unique( $present ) );
+ }
+
/**
* Insert one hidden field per requested param, skipping any that already
* exist on the form
--- a/handl-utm-grabber/includes/onboarding/class-integration-onboarding.php
+++ b/handl-utm-grabber/includes/onboarding/class-integration-onboarding.php
@@ -0,0 +1,25 @@
+<?php
+namespace HandlUtmrabberFreeOnboarding;
+
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+/** Onboarding contract per form-plugin: locate host pages + listen for a tagged submission. */
+abstract class Handl_Integration_Onboarding {
+
+ /** Must match the matching Handl_Integration slug. */
+ abstract public function get_slug();
+
+ /**
+ * @param string|int $form_id
+ * @return string[] URLs of published pages that embed this form.
+ */
+ abstract public function find_host_pages( $form_id );
+
+ /** @param callable $on_match function( string $form_id, array $posted ): void */
+ abstract public function register_test_listener( callable $on_match );
+
+ /** Whether the live-test flow is wired (vs just listing forms). */
+ public function supports_live_test() {
+ return true;
+ }
+}
--- a/handl-utm-grabber/includes/onboarding/class-onboarding-manager.php
+++ b/handl-utm-grabber/includes/onboarding/class-onboarding-manager.php
@@ -0,0 +1,463 @@
+<?php
+namespace HandlUtmrabberFreeOnboarding;
+
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+use HandlUtmrabberFreeIntegrationsHandl_Integrations_Manager;
+
+require_once __DIR__ . '/class-integration-onboarding.php';
+require_once __DIR__ . '/integrations/class-gravity-forms-onboarding.php';
+require_once __DIR__ . '/integrations/class-contact-form-7-onboarding.php';
+require_once __DIR__ . '/integrations/class-ninja-forms-onboarding.php';
+require_once __DIR__ . '/integrations/class-elementor-onboarding.php';
+
+class Handl_Onboarding_Manager {
+
+ const NONCE_ACTION = 'handl_onboarding_nonce';
+ const ACTIVE_TRANSIENT = 'handl_onboarding_active';
+ const COMPLETED_OPTION = 'handl_onboarding_completed';
+ const TOKEN_PREFIX = 'first-test-';
+ const SIGNUP_ENDPOINT = 'https://api.utmgrabber.com/http/users/silent-register'