Skip to content

Add priority-based notice bar with milestone and extrapolation messaging#1150

Merged
danieliser merged 10 commits intodevelopfrom
enhanced-notice-bar
Mar 25, 2026
Merged

Add priority-based notice bar with milestone and extrapolation messaging#1150
danieliser merged 10 commits intodevelopfrom
enhanced-notice-bar

Conversation

@danieliser
Copy link
Copy Markdown
Member

@danieliser danieliser commented Jan 11, 2026

Summary

Implements comprehensive upsell enhancement system with intelligent priority-based trigger selection inspired by the Review Request module pattern.

Priority System

4 Priority Groups (highest to lowest):

  1. Milestone Achievements (pri: 100) - Celebration messages for user success
  2. Conversion Extrapolation (pri: 80) - Data-driven "you could have X more" messaging
  3. Integration Detected (pri: 60) - Contextual plugin-based messages
  4. Generic Upgrade (pri: 40) - Fallback messages

New Milestone Triggers

  • 🎉 First form conversion: Celebration message with 300-500% increase stat
  • 📈 High engagement tiers: 1K, 5K, 10K+ popup views with analytics pitch
  • 90-day cooldown: Prevents message fatigue while allowing seasonal re-engagement
  • Natural progression: Celebration → extrapolation → integration → generic

New Extrapolation Messages

  • 💰 Ecommerce users: "You've tracked X form submissions. With Pro+ Exit Intent, you could capture ~Y more!"
    • Uses 33% extrapolation based on industry averages
    • Requires minimum 3 submissions for credibility
  • 🎓 LMS users: "You've tracked X form submissions. With Pro+ LMS targeting, you could capture ~Y more!"
    • Uses 40% extrapolation for education sector
    • Requires minimum 3 submissions
  • Accurate terminology: Uses "form submissions" not "conversions/enrollments" (free plugin doesn't track specific conversion types - that's what Pro is for!)

Milestone Cooldown System

  • 90-day cooldown period for milestone re-showing (seasonal re-engagement)
  • Tracks via user meta: _pum_last_milestone_trigger, _pum_last_milestone_at
  • Prevents showing same milestone repeatedly within cooldown
  • Allows different milestones to show as user progresses
  • Non-milestone messages (integration, generic) unaffected by cooldown

Technical Implementation

  • New methods:
    • get_notice_bar_triggers() - Priority-based trigger definition system
    • get_current_notice_bar_trigger() - Intelligent trigger selection with cooldown logic
    • get_form_conversion_count() - Helper with graceful fallback
    • rsort_by_priority() - Consistent priority sorting
  • Integration:
    • Connects to FormConversionTracking service (gracefully degrades if not available)
    • Uses existing popup view count for engagement milestones
    • All messages use generate_upgrade_url() for UTM tracking
  • Backward compatibility:
    • Deprecated old helper methods with @deprecated tags
    • Maintains existing integration detection logic
    • Fallback to generic messages ensures no user sees empty notice bar

Impact

  • Success-triggered messaging instead of frustration-based upsells
  • Data-driven value propositions with concrete numbers
  • Contextual relevance based on actual user behavior and installed plugins
  • Foundation for A/B testing (priority system makes message variants easy to test)
  • Milestone celebration creates positive emotional association with upgrade
  • Accurate terminology that doesn't over-promise on free plugin tracking
  • Quarterly re-engagement prevents message fatigue (90-day cooldown)

Testing Scenarios

  • 0 form submissions: Should skip milestone triggers, show integration or generic
  • 1 form submission: Should show celebration message
  • 1 form submission (repeat visit within 90 days): Should skip to next trigger (cooldown working)
  • 1 form submission (after 90 days): Should re-show celebration (cooldown expired)
  • 3+ form submissions + ecommerce: Should show extrapolation message
  • Popup view milestones: Test 1K, 5K, 10K+ view thresholds
  • Fallback behavior: Verify generic messages work when no triggers match
  • Pro vs Free users: Verify Pro users see Pro+ messages, Free users see Pro messages

Files Changed

  • classes/Upsell.php - Complete refactor of message generation system (2 commits)

Commits

  1. b283d213 - feat: add priority-based notice bar with milestone and extrapolation messaging
  2. 68c51ed5 - fix: improve messaging accuracy and add milestone cooldown system

🤖 Ready for CodeRabbit review

Summary by CodeRabbit

  • Refactor

    • Reworked notice-bar/upgrade messaging into a priority-driven trigger system for deterministic, single-message selection.
  • Behavior Change

    • Notices now suppress when any enabled extension/add-on is present; fully licensed sites remain exempt. Milestone and engagement messages are prioritized and enforce cooldowns to avoid repeated prompts.
  • Bug Fix

    • Form-conversion and popup-view thresholds are retrieved with safer fallbacks to prevent missing-data errors.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Replaces randomized integration/tier upgrade messaging in classes/Upsell.php with a priority-driven trigger system: short-circuits for Pro Plus and any active extensions, computes matching notice-bar triggers via get_notice_bar_triggers() / get_current_notice_bar_trigger(), and returns that trigger’s message or ''.

Changes

Cohort / File(s) Summary
Upsell notice/triggers
classes/Upsell.php
Removes prior integration-message helpers and random selection. Adds priority-trigger framework: get_notice_bar_triggers(), get_current_notice_bar_trigger(), rsort_by_priority(), get_form_conversion_count(). Refactors generate_upgrade_message() to short-circuit for Pro Plus and active extensions and return the highest-priority trigger message or ''. Minor comment punctuation fix.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Upsell as Upsell
  participant License as LicenseService
  participant Integrations as IntegrationDetector
  participant Tracking as TrackingService

  Caller->>Upsell: generate_upgrade_message()
  Upsell->>License: is_pro_plus_with_valid_license?
  License-->>Upsell: yes/no
  alt pro_plus
    Upsell-->>Caller: "" 
  else not_pro_plus
    Upsell->>Integrations: detect_integrations()/active_extensions?
    Integrations-->>Upsell: active_extensions_count
    alt has_active_extensions
      Upsell-->>Caller: "" 
    else no_active_extensions
      Upsell->>Tracking: get_form_conversion_count()
      Tracking-->>Upsell: conversion_count (or 0)
      Upsell->>Upsell: get_notice_bar_triggers(conversion_count,...)
      Upsell->>Upsell: rsort_by_priority(groups & triggers)
      Upsell->>Upsell: get_current_notice_bar_trigger()
      alt trigger_found
        Upsell-->>Caller: selected_trigger.message
      else none
        Upsell-->>Caller: ""
      end
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: introducing a priority-based notice bar system with milestone and extrapolation messaging, which is the core feature implemented in the refactored Upsell.php file.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enhanced-notice-bar

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@danieliser
Copy link
Copy Markdown
Member Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 29, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@classes/Upsell.php`:
- Line 237: Remove the unused dead variable $pro_is_active from the method in
classes/Upsell.php: delete the assignment "$pro_is_active =
\PopupMaker\plugin()->is_pro_active();" (and any related unused references) so
the method no longer creates an unused local; if logic depended on that check,
instead reference \PopupMaker\plugin()->is_pro_active() inline where actually
needed or add a meaningful use, but if not required simply remove the
assignment.
🧹 Nitpick comments (1)
classes/Upsell.php (1)

488-500: Consider catching \Throwable for broader error coverage.

PHP 7+ can throw \Error in addition to \Exception. Using \Throwable would catch both. However, since the fallback is graceful (returns 0), this is a minor concern.

♻️ Optional improvement
 	private static function get_form_conversion_count() {
 		try {
 			$form_tracking = \PopupMaker\plugin( 'form_conversion_tracking' );
 			if ( $form_tracking && method_exists( $form_tracking, 'get_site_count' ) ) {
 				return $form_tracking->get_site_count();
 			}
-		} catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
+		} catch ( \Throwable $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
 			// Service not available, return 0.
 			unset( $e ); // Prevent unused variable warning.
 		}

 		return 0;
 	}

Comment thread classes/Upsell.php Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@classes/Upsell.php`:
- Around line 19-23: The file uses old array() syntax in the static hook
registrations (calls to add_filter/add_action) which triggers PHPCS
Generic.Arrays.DisallowLongArraySyntax; replace array( __CLASS__, 'addon_tabs' )
and similar occurrences (array( __CLASS__, 'popup_promotional_fields' ), array(
__CLASS__, 'theme_promotional_fields' ), array( __CLASS__, 'notice_bar_display'
)) with the short-array callable syntax [ __CLASS__, 'methodName' ] throughout
the Upsell class, or run vendor/bin/phpcbf classes/Upsell.php to auto-fix;
ensure all other array() usages in this file are converted to [] as well.
🧹 Nitpick comments (2)
classes/Upsell.php (2)

205-206: Side effect in getter method: consider separating read from write.

get_current_notice_bar_trigger() updates user meta as a side effect when returning a milestone trigger. This could cause unexpected behavior if the method is called multiple times or in contexts where side effects aren't expected (e.g., testing, previews).

Consider separating the "get trigger" logic from "record that milestone was shown" into distinct methods, or document this behavior clearly in the method's docblock.


493-496: Minor: Simplify unused exception handling.

Instead of unset($e) to suppress the warning, you can avoid assigning the exception entirely:

♻️ Suggested simplification
-		} catch ( \Throwable $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
-			// Service not available, return 0.
-			unset( $e ); // Prevent unused variable warning.
-		}
+		} catch ( \Throwable $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Service not available, return 0.
+		}

Comment thread classes/Upsell.php Outdated
…messaging

Implements comprehensive upsell enhancement system with 4 priority groups:

**Priority System:**
- Milestone Achievements (pri: 100) - Celebration messages for success
- Conversion Extrapolation (pri: 80) - Data-driven "you could have X more"
- Integration Detected (pri: 60) - Contextual plugin-based messages
- Generic Upgrade (pri: 40) - Fallback messages

**New Milestone Triggers:**
- First form conversion celebration (🎉 with 300-500% increase stat)
- High engagement tiers (1K, 5K, 10K+ popup views with analytics pitch)

**New Extrapolation Messages:**
- Ecommerce: "You've tracked X conversions, capture ~Y more with Exit Intent"
- LMS: "You've tracked X enrollments, capture ~Y more with LMS targeting"
- Uses 33% extrapolation for ecommerce, 40% for LMS

**Integration:**
- Connects to FormConversionTracking service for milestone data
- Uses existing popup view count for engagement milestones
- Maintains backward compatibility with existing integration detection

**Technical Changes:**
- Added get_notice_bar_triggers() with Review Request pattern
- Added get_current_notice_bar_trigger() for priority selection
- Added get_form_conversion_count() helper with graceful fallback
- Added rsort_by_priority() for consistent sorting
- Deprecated get_integration_messages(), get_pro_integration_message(), get_free_upgrade_message()
- All messages use generate_upgrade_url() for UTM tracking

**Impact:**
- Success-triggered messaging (not frustration-based)
- Data-driven value propositions with concrete numbers
- Foundation for future A/B testing and message optimization
**Messaging Accuracy:**
- Changed "conversions" → "form submissions" (free plugin doesn't track specific conversion types)
- Changed "enrollments" → "form submissions" for LMS (specific tracking is Pro feature)
- Updated translator comments to reflect accurate terminology

**Milestone Cooldown:**
- Added 90-day cooldown for milestone achievement messages (seasonal re-engagement)
- Tracks last shown milestone via user meta: _pum_last_milestone_trigger, _pum_last_milestone_at
- Prevents showing same milestone repeatedly within cooldown period
- Allows different milestones to show (e.g., first form → high engagement)
- Non-milestone messages (integration detected, generic upgrade) unaffected

**Impact:**
- More accurate messaging that doesn't over-promise on free plugin tracking
- Quarterly milestone re-engagement prevents message fatigue
- Natural progression: celebration → extrapolation → generic
@danieliser danieliser force-pushed the enhanced-notice-bar branch from 0f210dd to a8b7c8f Compare March 24, 2026 20:59
@danieliser
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
classes/Upsell.php (2)

180-207: Consider defensive validation for user ID.

get_current_user_id() returns 0 for non-logged-in users. While admin context typically ensures authentication, adding a guard improves robustness and prevents silent failures in edge cases (e.g., REST API calls, cron contexts).

Additionally, the update_user_meta() calls on lines 205-206 are a side effect during trigger selection—this is intentional for tracking, but worth documenting in the method's docblock since the method name suggests a pure getter.

🛡️ Proposed defensive check
 private static function get_current_notice_bar_trigger() {
 	$triggers           = self::get_notice_bar_triggers();
+	$user_id            = get_current_user_id();
+
+	// Skip milestone tracking for non-logged-in users.
+	if ( 0 === $user_id ) {
+		// Fall through to non-milestone triggers or return first matching.
+		foreach ( $triggers as $group_key => $group ) {
+			if ( 'milestone_achievements' === $group_key ) {
+				continue;
+			}
+			foreach ( $group['triggers'] as $trigger ) {
+				if ( ! in_array( false, $trigger['conditions'], true ) ) {
+					return $trigger;
+				}
+			}
+		}
+		return false;
+	}
+
-	$last_milestone_key = get_user_meta( get_current_user_id(), '_pum_last_milestone_trigger', true );
-	$last_milestone_at  = get_user_meta( get_current_user_id(), '_pum_last_milestone_at', true );
+	$last_milestone_key = get_user_meta( $user_id, '_pum_last_milestone_trigger', true );
+	$last_milestone_at  = get_user_meta( $user_id, '_pum_last_milestone_at', true );

And update the update_user_meta() calls to use $user_id.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@classes/Upsell.php` around lines 180 - 207, The
get_current_notice_bar_trigger() function uses get_current_user_id() without
validating it and performs update_user_meta() side-effects with that implicit
ID; add a defensive guard that captures get_current_user_id() into a $user_id
variable, verify $user_id is a positive int (return early if not), and replace
direct get_current_user_id() calls in update_user_meta() (and the
get_user_meta() reads) with $user_id; also update the method docblock to note
that this getter mutates user meta for milestone tracking.

257-270: Strict equality may miss milestone if conversions batch-process.

The condition 1 === $form_count only triggers when exactly 1 conversion exists. If conversions are batch-imported or the count jumps from 0 to 2+, users miss this celebration milestone.

Consider using $form_count >= 1 combined with a "has shown first conversion" flag in user meta, or accept this as intentional behavior for organically acquired first conversions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@classes/Upsell.php` around lines 257 - 270, The current condition for the
'first_form_conversion' milestone uses a strict equality (1 === $form_count)
which misses cases where the count jumps from 0 to >1; change the condition to
check for $form_count >= 1 and add a persistent "shown" flag check/update in
user meta so the message only displays once (e.g., check and set a meta key like
popup_maker_first_form_milestone_shown via get_user_meta/update_user_meta or
your platform's equivalents inside the same logic that builds
'first_form_conversion'); update the 'conditions' entry for
'first_form_conversion' to include both ($form_count >= 1) and the inverse of
the shown-flag, and mark the flag as true when the notice is shown.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@classes/Upsell.php`:
- Around line 180-207: The get_current_notice_bar_trigger() function uses
get_current_user_id() without validating it and performs update_user_meta()
side-effects with that implicit ID; add a defensive guard that captures
get_current_user_id() into a $user_id variable, verify $user_id is a positive
int (return early if not), and replace direct get_current_user_id() calls in
update_user_meta() (and the get_user_meta() reads) with $user_id; also update
the method docblock to note that this getter mutates user meta for milestone
tracking.
- Around line 257-270: The current condition for the 'first_form_conversion'
milestone uses a strict equality (1 === $form_count) which misses cases where
the count jumps from 0 to >1; change the condition to check for $form_count >= 1
and add a persistent "shown" flag check/update in user meta so the message only
displays once (e.g., check and set a meta key like
popup_maker_first_form_milestone_shown via get_user_meta/update_user_meta or
your platform's equivalents inside the same logic that builds
'first_form_conversion'); update the 'conditions' entry for
'first_form_conversion' to include both ($form_count >= 1) and the inverse of
the shown-flag, and mark the flag as true when the notice is shown.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 05b314d1-64e9-4487-a748-7d4974030ca5

📥 Commits

Reviewing files that changed from the base of the PR and between 0f210dd and ae4e003.

📒 Files selected for processing (1)
  • classes/Upsell.php

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
classes/Upsell.php (2)

249-251: Consider extracting the hardcoded date to a constant.

The date '2026-04-01' determines new vs existing install behavior. Extracting this to a class constant would improve maintainability and make the feature ship date self-documenting.

+    /**
+     * Date when form conversion tracking shipped.
+     */
+    const FORM_TRACKING_SHIP_DATE = '2026-04-01';

Then use self::FORM_TRACKING_SHIP_DATE at line 250.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@classes/Upsell.php` around lines 249 - 251, The comparison uses a hardcoded
date string ('2026-04-01') in Upsell that should be extracted to a class
constant; add a class constant (e.g. const FORM_TRACKING_SHIP_DATE =
'2026-04-01';) to the Upsell class and replace the literal in the is_new_install
calculation (currently using strtotime('2026-04-01')) with
self::FORM_TRACKING_SHIP_DATE so the ship date is centralized and
self-documenting.

228-232: Static cache may persist stale data within long-running requests.

The static $triggers cache is never invalidated. In typical page loads this is fine, but if this method is called before and after a form conversion happens (e.g., during an AJAX request that processes a form), the cached value won't reflect the new $form_count.

Given this runs only in admin page loads (not AJAX handlers), this is likely acceptable. Consider adding a comment noting the cache assumption.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@classes/Upsell.php` around lines 228 - 232, The static cache variable
$triggers in the Upsell class is never invalidated which can persist stale data
across long-running requests; update the method that declares static $triggers
to either invalidate the cache when relevant or (preferred) add a clear comment
above the static $triggers declaration documenting the assumption that this
method runs only during normal admin page loads (not AJAX/form-processing flows)
so the static cache is safe, and mention that if the method is ever used in
async/form-processing contexts the cache must be cleared or removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@classes/Upsell.php`:
- Around line 249-251: The comparison uses a hardcoded date string
('2026-04-01') in Upsell that should be extracted to a class constant; add a
class constant (e.g. const FORM_TRACKING_SHIP_DATE = '2026-04-01';) to the
Upsell class and replace the literal in the is_new_install calculation
(currently using strtotime('2026-04-01')) with self::FORM_TRACKING_SHIP_DATE so
the ship date is centralized and self-documenting.
- Around line 228-232: The static cache variable $triggers in the Upsell class
is never invalidated which can persist stale data across long-running requests;
update the method that declares static $triggers to either invalidate the cache
when relevant or (preferred) add a clear comment above the static $triggers
declaration documenting the assumption that this method runs only during normal
admin page loads (not AJAX/form-processing flows) so the static cache is safe,
and mention that if the method is ever used in async/form-processing contexts
the cache must be cleared or removed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d0e6c4c3-cb7a-421e-b070-5d12fa73b0da

📥 Commits

Reviewing files that changed from the base of the PR and between ae4e003 and 74929e0.

📒 Files selected for processing (1)
  • classes/Upsell.php

- Remove conversion extrapolation group (mixed form data with
  ecommerce/LMS features incorrectly)
- Add variant messages for ecommerce (carts, revenue, upsell),
  LMS (enrollment, targeting), and CRM (tagging, automation)
- Randomize across all matching triggers instead of always showing
  the first match
- Engagement milestones already exclusive via conditions (1K/5K/10K
  ranges don't overlap)
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
classes/Upsell.php (1)

477-485: Consider logging caught exceptions in debug mode for observability.

Graceful fallback to 0 is good, but swallowing all \Throwable makes troubleshooting hard when service wiring breaks.

🛠️ Suggested lightweight debug logging
 	} catch ( \Throwable $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
-		// Service not available, graceful fallback.
-		unset( $e );
+		// Service not available, graceful fallback.
+		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
+			error_log( 'Popup Maker: form_conversion_tracking unavailable in PUM_Upsell::get_form_conversion_count().' );
+		}
 	}
As per coding guidelines "Use WordPress debug logging features for error handling".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@classes/Upsell.php` around lines 477 - 485, The catch block currently
swallows all \Throwable for the \PopupMaker\plugin('form_conversion_tracking')
call and get_site_count usage; update the catch for (\Throwable $e) to log the
exception details when WP debug logging is enabled (e.g. check
defined('WP_DEBUG') && WP_DEBUG and/or WP_DEBUG_LOG) and emit a concise message
including context (mention Upsell/get_site_count, $e->getMessage(),
$e->getFile(), $e->getLine()) so failures are recorded while still returning the
graceful fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@classes/Upsell.php`:
- Around line 172-175: Update the stale docblock on the Upsell class method that
returns the highest-priority notice bar trigger (e.g.,
getHighestPriorityNoticeBarTrigger) to remove any mention of milestone
cooldowns; the implementation no longer enforces a 90-day cooldown, so the
docblock should state that the method returns the highest-priority matching
trigger and describe any current conditions it checks, without referencing
cooldown behavior.
- Around line 226-229: The hardcoded cutoff date makes the new-install check in
Upsell.php unreachable now; replace the literal '2026-04-01' used in computing
$is_new_install with a configurable or dynamic cutoff (e.g., use an option like
get_option('pum_reviews_new_install_cutoff') or a relative calculation such as
strtotime('-30 days')) so installs can be evaluated correctly; update the logic
around $installed_on and $is_new_install to read the cutoff from that option or
compute it at runtime and ensure the check remains strtotime($installed_on) >
strtotime($cutoff) (keep variable names $installed_on and $is_new_install so
callers remain stable).

---

Nitpick comments:
In `@classes/Upsell.php`:
- Around line 477-485: The catch block currently swallows all \Throwable for the
\PopupMaker\plugin('form_conversion_tracking') call and get_site_count usage;
update the catch for (\Throwable $e) to log the exception details when WP debug
logging is enabled (e.g. check defined('WP_DEBUG') && WP_DEBUG and/or
WP_DEBUG_LOG) and emit a concise message including context (mention
Upsell/get_site_count, $e->getMessage(), $e->getFile(), $e->getLine()) so
failures are recorded while still returning the graceful fallback.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 21c918c4-e2e8-48bd-b075-c3d28b2344c3

📥 Commits

Reviewing files that changed from the base of the PR and between 74929e0 and 1304660.

📒 Files selected for processing (1)
  • classes/Upsell.php

Comment thread classes/Upsell.php Outdated
Comment thread classes/Upsell.php Outdated
@danieliser
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 25, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
classes/Upsell.php (2)

470-478: Priority sorting is computed but not utilized in selection.

The groups and triggers are sorted by priority (highest first), but get_current_notice_bar_trigger() collects all matching triggers into a flat array and randomly selects one. This sorting computation has no effect on the final selection.

Either remove the sorting to avoid unnecessary computation, or modify the selection logic to respect priority.

♻️ Option A: Remove unused sorting
-		// Sort groups by priority (highest first).
-		uasort( $triggers, [ __CLASS__, 'rsort_by_priority' ] );
-
-		// Sort triggers within each group by priority (highest first).
-		foreach ( $triggers as $group_key => $group ) {
-			if ( ! empty( $group['triggers'] ) ) {
-				uasort( $triggers[ $group_key ]['triggers'], [ __CLASS__, 'rsort_by_priority' ] );
-			}
-		}
-
 		return $triggers;
♻️ Option B: Respect priority in selection (in get_current_notice_bar_trigger)
 		if ( empty( $matching ) ) {
 			return false;
 		}

-		// Randomize so users see different messages across page loads.
-		return $matching[ array_rand( $matching ) ];
+		// Return first match (already sorted by priority, highest first).
+		return reset( $matching );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@classes/Upsell.php` around lines 470 - 478, The code currently sorts groups
and triggers using Upsell::rsort_by_priority but then flattens matching triggers
in get_current_notice_bar_trigger and picks one at random, so the priority
sorting is unused; fix by updating get_current_notice_bar_trigger to honor
priority (e.g., weight selection toward higher-priority entries or pick the
highest-priority matching trigger first) instead of uniformly randomizing, or
remove the uasort calls around $triggers if you opt to drop priority entirely;
locate the uasort usages and the get_current_notice_bar_trigger method to
implement the chosen approach so rsort_by_priority and the $triggers sort are
either relied upon in selection or eliminated.

201-209: Docblock describes priority-based selection, but implementation uses randomization.

The docblock states triggers are "sorted by group priority" and "sorted by trigger priority," implying priority-based selection. However, get_current_notice_bar_trigger() collects all matching triggers and randomly selects one via array_rand(), making the priority sorting unused for selection purposes.

Consider either:

  1. Updating the docblock to reflect the randomization behavior, or
  2. Changing get_current_notice_bar_trigger() to select the first match (respecting priority order)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@classes/Upsell.php` around lines 201 - 209, The docblock for the notice-bar
trigger API claims triggers are selected by priority, but
Upsell::get_current_notice_bar_trigger() actually gathers all matching triggers
and uses array_rand() to pick one at random; update behavior to be consistent:
either (A) change get_current_notice_bar_trigger() to return the first matching
trigger respecting the sorted group/trigger order (remove array_rand() and
return the top-priority match after the existing sort), or (B) change the
docblock on Upsell::get_current_notice_bar_trigger() (and the return type
description if necessary) to explicitly state that a random matching trigger is
chosen and priority sorting is not used for selection — pick one of these fixes
and apply it consistently in the Upsell class.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@classes/Upsell.php`:
- Around line 470-478: The code currently sorts groups and triggers using
Upsell::rsort_by_priority but then flattens matching triggers in
get_current_notice_bar_trigger and picks one at random, so the priority sorting
is unused; fix by updating get_current_notice_bar_trigger to honor priority
(e.g., weight selection toward higher-priority entries or pick the
highest-priority matching trigger first) instead of uniformly randomizing, or
remove the uasort calls around $triggers if you opt to drop priority entirely;
locate the uasort usages and the get_current_notice_bar_trigger method to
implement the chosen approach so rsort_by_priority and the $triggers sort are
either relied upon in selection or eliminated.
- Around line 201-209: The docblock for the notice-bar trigger API claims
triggers are selected by priority, but Upsell::get_current_notice_bar_trigger()
actually gathers all matching triggers and uses array_rand() to pick one at
random; update behavior to be consistent: either (A) change
get_current_notice_bar_trigger() to return the first matching trigger respecting
the sorted group/trigger order (remove array_rand() and return the top-priority
match after the existing sort), or (B) change the docblock on
Upsell::get_current_notice_bar_trigger() (and the return type description if
necessary) to explicitly state that a random matching trigger is chosen and
priority sorting is not used for selection — pick one of these fixes and apply
it consistently in the Upsell class.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 80702742-9d45-490f-985b-a3fed0a5e669

📥 Commits

Reviewing files that changed from the base of the PR and between 74929e0 and 8f6dd6f.

📒 Files selected for processing (1)
  • classes/Upsell.php

- Fix docblock to describe randomized selection (not cooldown)
- Use pum_installed_on option with 2026-03-25 cutoff for new vs
  existing install detection
- Remove unused priority sorting (randomized selection doesn't need it)
- Remove orphaned rsort_by_priority method
@danieliser danieliser merged commit 6137504 into develop Mar 25, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant