Skip to content

feat(api): add server-side tracking API for programmatic contexts (#171)#184

Merged
parhumm merged 7 commits intodevelopmentfrom
fix/171-server-side-tracking-api
Mar 13, 2026
Merged

feat(api): add server-side tracking API for programmatic contexts (#171)#184
parhumm merged 7 commits intodevelopmentfrom
fix/171-server-side-tracking-api

Conversation

@ParhamTehrani
Copy link
Copy Markdown
Contributor

@ParhamTehrani ParhamTehrani commented Mar 13, 2026

Add wp_slimstat::slimtrack() backward-compatible wrapper and wp_slimstat::slimtrack_server() for server-side tracking contexts (cron, CLI, redirect handlers) that bypasses CMP consent checks.

CMP consent is a browser-side concept. In server-side contexts, there is no browser session and CMP consent has no meaningful role.

The following settings remain enforced:

  • DNT (Do Not Track) headers
  • IP anonymization and hashing settings
  • Tracker cookie configuration
  • All exclusion rules

Closes #171

Related: #187 (pre-existing Browscap/Flysystem scoping bug discovered during E2E testing — unrelated to this PR)

Describe your changes

...

Submission Review Guidelines:

  • I have performed a self-review of my code
  • If it is a core feature, I have added thorough tests.
  • Will this be part of a product update? If yes, please write one phrase about this update.
  • I have reviewed my code for security best practices.
  • Following the above guidelines will result in quick merges and clear and detailed feedback when appropriate.
  • My code follows the style guidelines of this project
  • I have updated the change-log in CHANGELOG.md.

Type of change

  • Fix - Fixes an existing bug
  • Add - Adds functionality
  • Update - Update existing functionality
  • Dev - Development related task
  • Tweak - A minor adjustment to the codebase
  • Performance - Address performance issues
  • Enhancement - Improvement to existing functionality

Summary by CodeRabbit

  • New Features
    • Added a server-side programmatic tracking API and a backward-compatible wrapper for existing tracking calls.
  • Improvements
    • Consent checks now receive context about request source (browser vs server), enabling context-aware decisions.
    • Do-Not-Track is checked earlier and applied consistently across modes.
  • Bug Fixes
    • Consent and anonymous-mode logic refined for predictable behavior with external consent providers.
  • Tests
    • Added regression tests covering browser vs server consent filter behavior.

Add wp_slimstat::slimtrack() backward-compatible wrapper and
wp_slimstat::slimtrack_server() for server-side tracking contexts
(cron, CLI, redirect handlers) that bypasses CMP consent checks.

CMP consent is a browser-side concept. In server-side contexts,
there is no browser session and CMP consent has no meaningful role.

The following settings remain enforced:
- DNT (Do Not Track) headers
- IP anonymization and hashing settings
- Tracker cookie configuration
- All exclusion rules
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 13, 2026

📝 Walkthrough

Walkthrough

Adds a programmatic/server-side tracking mode, a new filter context builder, and reorders consent decision flow to evaluate DNT and programmatic bypass before CMP integrations; exposes server-side tracking API and a flag to control programmatic tracking context; adds tests for filter-context behavior.

Changes

Cohort / File(s) Summary
Consent logic
src/Utils/Consent.php
Adds public static function buildFilterContext(): array; injects context into slimstat_can_track filter calls; implements programmatic-tracking bypass, DNT-first checks, reordered decision branches, and updated server-side CMP handling in canTrack() and piiAllowed().
Server API / Entrypoints
wp-slimstat.php
Adds public static $is_programmatic_tracking flag, slimtrack() wrapper, and slimtrack_server() which toggles the programmatic flag, delegates to Tracker::slimtrack(), and restores the flag in a finally block.
Tests / Filtering harness
tests/consent-filter-context-test.php
Adds regression tests exercising buildFilterContext() and slimstat_can_track behavior for browser vs server contexts; introduces a minimal test harness and WordPress filter/function stubs to validate backward compatibility and context-aware filters.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client/Request
    participant SlimStat as SlimStat (wp-slimstat.php)
    participant Consent as Consent (src/Utils/Consent.php)
    participant CMP as CMP / External Filters

    rect rgba(120,200,100,0.5)
    Note over Client,SlimStat: Browser/client-side tracking
    Client->>SlimStat: track call
    SlimStat->>Consent: canTrack()
    Consent->>CMP: apply `slimstat_can_track` with context {source: 'browser', programmatic: false}
    CMP-->>Consent: consent decision
    Consent-->>SlimStat: allow/deny
    SlimStat-->>Client: result
    end

    rect rgba(100,150,220,0.5)
    Note over Client,SlimStat: Server-side / programmatic tracking
    Client->>SlimStat: slimtrack_server()
    SlimStat->>SlimStat: set is_programmatic_tracking = true
    SlimStat->>Consent: canTrack()
    Consent->>Consent: DNT & anonymous checks (respect DNT)
    Consent->>CMP: apply `slimstat_can_track` with context {source: 'server', programmatic: true}
    CMP-->>Consent: consent decision (may be bypassed for programmatic)
    Consent-->>SlimStat: allow/deny
    SlimStat->>SlimStat: restore is_programmatic_tracking = false
    SlimStat-->>Client: result
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A hop through filters, context in paw,

Server or browser — the rules we saw.
DNT first, banners take a bow,
Programmatic hops allowed somehow.
A carrot of tests — the rabbit says "wow!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main change: adding a server-side tracking API for programmatic contexts, which is the primary feature being introduced.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/171-server-side-tracking-api
📝 Coding Plan
  • Generate coding plan for human review comments

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.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Utils/Consent.php`:
- Around line 391-397: The early unconditional "return true" when
\wp_slimstat::$is_programmatic_tracking is set bypasses anonymous-mode PII
protections; change the branch so programmatic flows only bypass CMP checks but
still enforce anonymous/PII restrictions — e.g., within the block that checks
\wp_slimstat::$is_programmatic_tracking, return true only if anonymous mode is
not enabled (check the anonymous-mode flag or helper such as a
Consent::is_anonymous_mode() or \wp_slimstat::$anonymous_mode), otherwise skip
returning and allow the existing anonymous/PII handling to run; keep the
existing DNT handling intact.

In `@wp-slimstat.php`:
- Around line 203-209: In slimtrack_server (the block setting
self::$is_programmatic_tracking around \SlimStat\Tracker\Tracker::slimtrack()),
save the current value of self::$is_programmatic_tracking to a local variable
before setting it to true and then in the finally block restore that saved value
instead of unconditionally assigning false; this preserves nested/re-entrant
programmatic tracking state and ensures the outer call's flag is not clobbered
by inner calls.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 15e44025-f5e6-4302-8351-7a17becb085c

📥 Commits

Reviewing files that changed from the base of the PR and between ba1ff60 and 0bbcf0c.

📒 Files selected for processing (2)
  • src/Utils/Consent.php
  • wp-slimstat.php

- Restore prior flag state in slimtrack_server() for nested calls
- Keep anonymous mode PII restrictions intact in programmatic mode
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Utils/Consent.php`:
- Around line 175-181: Update the PHPDoc step lists in the Consent class to
match actual runtime order: for canTrack() and piiAllowed() reorder the numbered
steps so they reflect the code path (DNT header check first, then Anonymous
Tracking handling, then programmatic tracking flag that bypasses CMP, then
determine if config collects PII (cookies OR full IPs), then if PII collected
check CMP consent (server-side verifiable CMPs or conservative blocking), then
apply the 'slimstat_can_track' filter, and finally return the decision); adjust
both docblocks that describe the decision tree so the step numbers and
descriptions match the control flow implemented by the canTrack() and
piiAllowed() methods.
- Around line 233-246: The programmatic-mode branch currently returns
apply_filters('slimstat_can_track', $default) which allows a filter to re-enable
tracking even when $default is false due to DNT; change it so DNT wins: call
apply_filters('slimstat_can_track', $default) into a local $can_track, cast to
bool, but if $default is false (DNT set) always return false; otherwise return
$can_track. Reference wp_slimstat::$is_programmatic_tracking and
apply_filters('slimstat_can_track', $default).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 29bd3781-44e7-4975-a3d9-d62e0ce46195

📥 Commits

Reviewing files that changed from the base of the PR and between 0bbcf0c and 523ce9f.

📒 Files selected for processing (2)
  • src/Utils/Consent.php
  • wp-slimstat.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • wp-slimstat.php

Comment on lines +175 to +181
* 1. Check programmatic tracking flag (bypasses CMP consent checks)
* 2. Check DNT header (if enabled in settings)
* 3. Check Anonymous Tracking mode (allows tracking without consent)
* 4. Determine if configuration collects PII (cookies OR full IPs)
* 5. If collects PII: Check CMP consent (for server-side verifiable CMPs or conservative blocking)
* 6. Apply 'slimstat_can_track' filter for external override
* 7. Return final decision
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Decision-tree PHPDoc order does not match actual execution order.

Both method docs list programmatic checks before DNT/anonymous checks, but runtime flow does DNT first (and in piiAllowed(), anonymous-mode handling happens after the programmatic branch). Please align the step numbering with code to prevent maintenance mistakes.

Also applies to: 330-346

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

In `@src/Utils/Consent.php` around lines 175 - 181, Update the PHPDoc step lists
in the Consent class to match actual runtime order: for canTrack() and
piiAllowed() reorder the numbered steps so they reflect the code path (DNT
header check first, then Anonymous Tracking handling, then programmatic tracking
flag that bypasses CMP, then determine if config collects PII (cookies OR full
IPs), then if PII collected check CMP consent (server-side verifiable CMPs or
conservative blocking), then apply the 'slimstat_can_track' filter, and finally
return the decision); adjust both docblocks that describe the decision tree so
the step numbers and descriptions match the control flow implemented by the
canTrack() and piiAllowed() methods.

Comment on lines +233 to +246
// Programmatic tracking mode - bypass CMP consent checks
// Used by slimtrack_server() for server-side contexts (cron, CLI, redirect handlers)
// where no browser session exists and CMP consent has no meaningful role.
// DNT headers are still respected above.
if (\wp_slimstat::$is_programmatic_tracking) {
/**
* Filter: slimstat_can_track
*
* Allows third parties to override tracking decision in programmatic mode.
*
* @param bool $default Default decision (true in programmatic mode, respecting DNT)
*/
return (bool) apply_filters('slimstat_can_track', $default);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

DNT is not hard-enforced in programmatic mode because the filter can re-enable tracking.

When HTTP_DNT=1, $default is set to false, but Line 245 still passes that through apply_filters('slimstat_can_track', $default). A callback can flip it back to true, which contradicts the “DNT headers are still respected above” guarantee.

🔧 Proposed fix
 		// Respect Do Not Track if enabled in settings
 		$respectDnt = ('on' === ($settings['do_not_track'] ?? 'off'));
+		$dntBlocked = false;
 		if ($respectDnt) {
 			$dntHeader = isset($_SERVER['HTTP_DNT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_DNT'])) : '';
 			if ('1' === $dntHeader) {
-				$default = false;
+				$dntBlocked = true;
 			}
 		}
+
+		// DNT should be authoritative: do not allow overrides.
+		if ($dntBlocked) {
+			return false;
+		}

As per coding guidelines: "Ensure no regressions in GDPR compliance; new code must maintain or improve privacy posture."

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

In `@src/Utils/Consent.php` around lines 233 - 246, The programmatic-mode branch
currently returns apply_filters('slimstat_can_track', $default) which allows a
filter to re-enable tracking even when $default is false due to DNT; change it
so DNT wins: call apply_filters('slimstat_can_track', $default) into a local
$can_track, cast to bool, but if $default is false (DNT set) always return
false; otherwise return $can_track. Reference
wp_slimstat::$is_programmatic_tracking and apply_filters('slimstat_can_track',
$default).

*
* @param bool $default Default decision (true in programmatic mode, respecting DNT)
*/
return (bool) apply_filters('slimstat_can_track', $default);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One compatibility gap still looks open here: slimtrack_server() is described as bypassing CMP consent checks, but this branch still routes through apply_filters( slimstat_can_track, $default ). Consent.php documents that filter as the external override point for plugins, so any consent integration implemented there will continue to veto programmatic calls unless it is updated to inspect wp_slimstat::$is_programmatic_tracking.

That means #171 is only fully fixed for SlimStats built-in consent branches; filter-based integrations still have no explicit way to distinguish a normal browser hit from a server-side/programmatic one. The lowest-risk fix would be to pass a second context argument to the filter (for example [programmatic => \wp_slimstat::$is_programmatic_tracking, source => server]) and add a regression test that registers a deny-by-default slimstat_can_track callback, then proves slimtrack_server() can opt into the server-side path without changing normal browser behavior.

…tion

Add second $context argument to all slimstat_can_track filter calls,
allowing filter-based consent integrations to distinguish between
normal browser hits and server-side/programmatic calls from
slimtrack_server().

Context array contains:
- programmatic: bool flag indicating server-side call
- source: 'server' or 'browser' identifier

This closes the compatibility gap where filter-based integrations
would veto programmatic calls because they had no way to detect
the server-side context.

Adds regression test proving:
- Deny-by-default filters can allow programmatic tracking via context
- Normal browser behavior remains unchanged
- Backward compatible with single-arg filter callbacks

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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.

♻️ Duplicate comments (2)
src/Utils/Consent.php (2)

191-198: ⚠️ Potential issue | 🟡 Minor

Sync the decision-tree PHPDoc with the current control flow.

canTrack() still documents programmatic before DNT, and piiAllowed() still says "No CMP: allow" even though Lines 668-669 now deny PII in that case. The step lists are out of date in both order and outcome.

Also applies to: 362-382, 654-669

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

In `@src/Utils/Consent.php` around lines 191 - 198, Update the PHPDoc
decision-tree comments to match the actual control flow and outcomes: revise the
canTrack() doc to reflect that DNT is evaluated before/over programmatic flags
(or whatever the current code enforces), and update piiAllowed() wording to
state that absence of a CMP results in denying PII (not allowing it); ensure the
numbered steps and outcomes match the real sequence used in the functions
canTrack() and piiAllowed() and also correct the duplicated/outdated
decision-tree blocks present near the other comment blocks to avoid
contradictory descriptions.

247-276: ⚠️ Potential issue | 🟠 Major

Make the DNT decision terminal.

HTTP_DNT=1 only sets $default = false; the programmatic return here, the later Real Cookie Banner branch, and the final filter can still turn tracking back on. That means an opted-out request can still pass canTrack().

🔒 One possible fix
 		$respectDnt = ('on' === ($settings['do_not_track'] ?? 'off'));
 		if ($respectDnt) {
 			$dntHeader = isset($_SERVER['HTTP_DNT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_DNT'])) : '';
 			if ('1' === $dntHeader) {
-				$default = false;
+				return false;
 			}
 		}

As per coding guidelines: "Ensure no regressions in GDPR compliance; new code must maintain or improve privacy posture".

Also applies to: 341-346

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

In `@src/Utils/Consent.php` around lines 247 - 276, When HTTP_DNT is '1' we must
make the opt-out terminal: after the DNT header check in Consent::canTrack (or
the method containing $dntHeader and $default), immediately return false so
programmatic mode (\wp_slimstat::$is_programmatic_tracking), the Real Cookie
Banner branch, and the final apply_filters('slimstat_can_track', $default, ...)
cannot re-enable tracking; locate the block that reads $_SERVER['HTTP_DNT'],
sets $default = false, and change flow to short-circuit (return false) at that
point.
🧹 Nitpick comments (2)
tests/consent-filter-context-test.php (2)

16-118: Prefer a WP_UnitTestCase here over a local hook harness.

This file reimplements add_filter()/apply_filters() and uses a standalone assert/exit flow. For a regression about WordPress filter compatibility, that can drift from the real dispatcher and the repo's normal test bootstrap.

As per coding guidelines: "tests/**/*.php: Write unit tests using WP_UnitTestCase".

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

In `@tests/consent-filter-context-test.php` around lines 16 - 118, Replace this
custom test harness with a proper WP_UnitTestCase-based test: create a test
class extending WP_UnitTestCase (instead of top-level functions and custom
asserts), move reset_state() logic into setUp()/tearDown() to reset
wp_slimstat::$settings and the global $_filter_callbacks, remove the local
add_filter/apply_filters/remove_filter/sanitize_text_field/wp_unslash stubs so
the test uses WordPress's real dispatcher, and convert the procedural
asserts/exit flow to PHPUnit assertions (e.g. $this->assertTrue,
$this->assertFalse, $this->assertSame) while still requiring the Consent class
(use SlimStat\Utils\Consent) in the test file.

110-118: Add one GDPR-enabled scenario.

reset_state() pins 'gdpr_enabled' => 'off', so every assertion exits through src/Utils/Consent.php:210-223. None of these tests execute the new DNT/programmatic/CMP path, so the main slimtrack_server() flow is still untested.

As per coding guidelines: "tests/**/*.php: Include integration tests to confirm new features don't break existing reports and tracking".

Also applies to: 124-245

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

In `@tests/consent-filter-context-test.php` around lines 110 - 118, The tests
always force GDPR off in reset_state() which prevents exercising the
GDPR/DNT/programmatic/CMP branches and thus never hits the main
slimtrack_server() path; update the tests to include at least one GDPR-enabled
scenario by either changing reset_state() to accept a parameter (e.g.,
$gdpr_enabled) or adding a new helper (e.g., reset_state_gdpr_enabled) that sets
wp_slimstat::$settings['gdpr_enabled'] = 'on' and use that helper in the
relevant tests (around the blocks you're running 124-245) so Consent.php's
DNT/programmatic/CMP logic and the slimtrack_server() flow are executed. Ensure
references to global $_filter_callbacks and
wp_slimstat::$is_programmatic_tracking remain reset as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/Utils/Consent.php`:
- Around line 191-198: Update the PHPDoc decision-tree comments to match the
actual control flow and outcomes: revise the canTrack() doc to reflect that DNT
is evaluated before/over programmatic flags (or whatever the current code
enforces), and update piiAllowed() wording to state that absence of a CMP
results in denying PII (not allowing it); ensure the numbered steps and outcomes
match the real sequence used in the functions canTrack() and piiAllowed() and
also correct the duplicated/outdated decision-tree blocks present near the other
comment blocks to avoid contradictory descriptions.
- Around line 247-276: When HTTP_DNT is '1' we must make the opt-out terminal:
after the DNT header check in Consent::canTrack (or the method containing
$dntHeader and $default), immediately return false so programmatic mode
(\wp_slimstat::$is_programmatic_tracking), the Real Cookie Banner branch, and
the final apply_filters('slimstat_can_track', $default, ...) cannot re-enable
tracking; locate the block that reads $_SERVER['HTTP_DNT'], sets $default =
false, and change flow to short-circuit (return false) at that point.

---

Nitpick comments:
In `@tests/consent-filter-context-test.php`:
- Around line 16-118: Replace this custom test harness with a proper
WP_UnitTestCase-based test: create a test class extending WP_UnitTestCase
(instead of top-level functions and custom asserts), move reset_state() logic
into setUp()/tearDown() to reset wp_slimstat::$settings and the global
$_filter_callbacks, remove the local
add_filter/apply_filters/remove_filter/sanitize_text_field/wp_unslash stubs so
the test uses WordPress's real dispatcher, and convert the procedural
asserts/exit flow to PHPUnit assertions (e.g. $this->assertTrue,
$this->assertFalse, $this->assertSame) while still requiring the Consent class
(use SlimStat\Utils\Consent) in the test file.
- Around line 110-118: The tests always force GDPR off in reset_state() which
prevents exercising the GDPR/DNT/programmatic/CMP branches and thus never hits
the main slimtrack_server() path; update the tests to include at least one
GDPR-enabled scenario by either changing reset_state() to accept a parameter
(e.g., $gdpr_enabled) or adding a new helper (e.g., reset_state_gdpr_enabled)
that sets wp_slimstat::$settings['gdpr_enabled'] = 'on' and use that helper in
the relevant tests (around the blocks you're running 124-245) so Consent.php's
DNT/programmatic/CMP logic and the slimtrack_server() flow are executed. Ensure
references to global $_filter_callbacks and
wp_slimstat::$is_programmatic_tracking remain reset as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dd49a51b-45f5-41f5-9e6f-34e34185050a

📥 Commits

Reviewing files that changed from the base of the PR and between 523ce9f and e5b749f.

📒 Files selected for processing (2)
  • src/Utils/Consent.php
  • tests/consent-filter-context-test.php

parhumm added 3 commits March 13, 2026 12:35
…eated filter calls

Replaces 4 identical apply_filters('slimstat_can_track', ...) call sites
(each with ~8 lines of duplicated PHPDoc) with a single private helper.
Ensures the context parameter is never accidentally omitted at new call sites.
8 tests validating slimtrack_server() behavior:
- Programmatic tracking enters pipeline bypassing CMP consent
- Flag state restoration after calls (including error cases)
- Sequential calls don't leak state
- GDPR enabled/disabled modes
- Backward-compatible slimtrack() wrapper
- DNT header handling in programmatic context
@parhumm
Copy link
Copy Markdown
Contributor

parhumm commented Mar 13, 2026

QA Test Results — PR #184 (Issue #171)

Unit Tests: 14/14 passed

$ php tests/consent-filter-context-test.php
All 14 assertions passed in consent-filter-context-test.php

Covers: buildFilterContext(), 1-arg/2-arg slimstat_can_track filter backward compat, programmatic vs browser source discrimination.

E2E Tests: 8/8 passed

$ npx playwright test tests/e2e/pr184-server-side-tracking-api.spec.ts

  ✓ slimtrack_server() enters the tracking pipeline in programmatic mode (3.7s)
  ✓ regular slimtrack() also enters pipeline (GDPR enabled, AJAX context) (5.1s)
  ✓ $is_programmatic_tracking flag restored to false even after error (640ms)
  ✓ sequential slimtrack_server() calls maintain correct flag state (701ms)
  ✓ slimtrack_server() works with GDPR disabled (1.3s)
  ✓ wp_slimstat::slimtrack() wrapper is callable and delegates correctly (602ms)
  ✓ slimtrack_server() with DNT header — consent layer processes correctly (471ms)
  ✓ all server-tracking AJAX endpoints are registered (312ms)

  8 passed (21.6s)

Code Review (/simplify): Clean

Extracted applyCanTrackFilter() private helper to consolidate 4 repeated apply_filters('slimstat_can_track', ...) call sites with duplicated doc blocks. Net: +18 / -51 lines.

Blocking Issue Found

During E2E testing, a pre-existing Browscap/Flysystem namespace scoping bug was discovered that blocks the tracking pipeline from completing DB inserts. Filed as #187. This is unrelated to PR #184 — it affects all tracking calls on the current codebase.

Environment

Field Value
Plugin wp-slimstat v5.4.3
PHP 8.5.0
WordPress Local by Flywheel
Branch fix/171-server-side-tracking-api (merged with development)

Context-aware filter callbacks could override DNT=1 block in the
programmatic branch by returning true. Add $dntBlocked flag and
short-circuit return false before the filter when DNT is active.

Add regression tests (TEST 7-8) verifying DNT cannot be overridden
by permissive filters in programmatic mode.
@parhumm
Copy link
Copy Markdown
Contributor

parhumm commented Mar 13, 2026

P1 Fix: DNT bypass in programmatic tracking path

Commit: 77958b7

Issue: Context-aware slimstat_can_track filter callbacks could override a DNT=1 block in the programmatic branch. When DNT set $default = false, the value was passed through applyCanTrackFilter() where a callback checking $context['programmatic'] could return true, effectively ignoring the user's Do Not Track preference.

Fix: Added $dntBlocked flag in canTrack(). The programmatic branch now short-circuits return false before invoking the filter when DNT is active:

if (\wp_slimstat::$is_programmatic_tracking) {
    if ($dntBlocked) {
        return false;
    }
    return self::applyCanTrackFilter($default);
}

Tests: Added 2 regression assertions (TEST 7-8) verifying:

  • DNT=1 blocks programmatic tracking even when filter returns true
  • Programmatic tracking still works when DNT header is absent (control case)

All 16 unit tests pass.

@parhumm parhumm merged commit 4382551 into development Mar 13, 2026
1 check passed
@parhumm parhumm deleted the fix/171-server-side-tracking-api branch March 13, 2026 13:31
@parhumm parhumm mentioned this pull request Mar 14, 2026
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.

2 participants