Skip to content

fix(cron): only update geoip timestamp on successful download#185

Merged
parhumm merged 6 commits intodevelopmentfrom
fix/cron-geoip-retry
Mar 13, 2026
Merged

fix(cron): only update geoip timestamp on successful download#185
parhumm merged 6 commits intodevelopmentfrom
fix/cron-geoip-retry

Conversation

@parhumm
Copy link
Copy Markdown
Contributor

@parhumm parhumm commented Mar 13, 2026

Summary

  • Cron callback now checks updateDatabase() return value before updating slimstat_last_geoip_dl
  • Previously, a failed download (network error, filesystem issue) would still set the timestamp, suppressing retries until the next monthly window
  • Matches the existing AJAX handler behavior at admin/index.php:2088

Addresses review feedback from PR #183: #183 (comment)

Test plan

  • Existing E2E tests pass (issue-180 spec covers cron callback behavior)
  • When updateDatabase() returns false, slimstat_last_geoip_dl is NOT updated
  • When updateDatabase() returns true, timestamp is updated normally

Summary by CodeRabbit

  • Bug Fixes

    • GeoIP database update timestamps are now recorded only after a successful update.
  • Tests

    • Added end-to-end test ensuring failed GeoIP downloads do not change the recorded timestamp.
    • New E2E suite validating Pro MaxMindDetailsAddon behavior (whois AJAX, admin stability, provider variations).
    • Test helpers: nonce helper MU-plugin and install/uninstall utilities; cron shim tests now sanitize input and emit consistent JSON.

The cron callback updated slimstat_last_geoip_dl unconditionally after
updateDatabase(), while the AJAX path correctly checked the return value.
A transient download failure would suppress retries until the next monthly
window. Now checks $ok before updating, matching the AJAX handler behavior.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 13, 2026

Warning

Rate limit exceeded

@parhumm has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 11 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 458c7cfc-716d-4784-b0e2-1c6f74bea833

📥 Commits

Reviewing files that changed from the base of the PR and between fcf8e7f and e1f8f2e.

📒 Files selected for processing (1)
  • tests/e2e/helpers/nonce-helper-mu-plugin.php
📝 Walkthrough

Walkthrough

The GeoIP update now records the last-download timestamp only when updateDatabase() returns success; tests and test MU-plugins were added to verify that a failed download does not change the timestamp. New E2E helpers expose nonce creation for tests and add Pro MaxMindDetailsAddon test coverage.

Changes

Cohort / File(s) Summary
Core GeoIP change
wp-slimstat.php
Store updateDatabase() result in $ok and only update slimstat_last_geoip_dl when $ok is truthy (remove unconditional timestamp write on failure).
Cron frontend shim & geoip E2E spec
tests/e2e/helpers/cron-frontend-shim-mu-plugin.php, tests/e2e/issue-180-dbip-cron-tempnam.spec.ts
Add callback_download_fail branch that stubs HTTP to return WP_Error, switch JSON output to wp_json_encode, sanitize mode checks, snapshot slimstat_last_geoip_dl before/after, and add E2E test asserting timestamp is unchanged on failure.
Nonce helper MU-plugin & installer
tests/e2e/helpers/nonce-helper-mu-plugin.php, tests/e2e/helpers/setup.ts
Add MU-plugin exposing wp_ajax_test_create_nonce and install/uninstall helpers (installNonceHelper, uninstallNonceHelper) to manage the nonce helper in E2E setup.
Pro MaxMindDetailsAddon E2E tests
tests/e2e/pro-maxmind-details-addon.spec.ts
Add comprehensive Playwright tests for the Pro MaxMindDetailsAddon (whois AJAX, provider switching, addon enable/disable), using the new nonce helper and asserting no fatal errors and correct whois/geolocation behaviors.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐇 I hopped to fetch the GeoIP map,
A curl went click, then hit a gap.
If update wins, I stamp the day;
If it fails, I let time stay.
A rabbit nods — no false hooray! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title precisely describes the main production change: a conditional timestamp update gated on successful GeoIP database download, which is the core fix in wp-slimstat.php.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/cron-geoip-retry
📝 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.

Test 4 stubs HTTP with WP_Error so updateDatabase() returns false,
then asserts slimstat_last_geoip_dl remains null — proving failed
downloads don't suppress retry attempts.
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

🧹 Nitpick comments (1)
wp-slimstat.php (1)

1267-1269: Use a strict success check before persisting the timestamp.

Line 1269 currently treats any truthy return as success. Tighten this to strict boolean success (true ===) so future provider contract drift cannot incorrectly advance slimstat_last_geoip_dl.

🔧 Small hardening diff
-				$ok = $geographicProvider->updateDatabase();
+				$ok = ( true === $geographicProvider->updateDatabase() );

-				if ($ok) {
+				if ( true === $ok ) {
 					update_option('slimstat_last_geoip_dl', time());
 				}

As per coding guidelines, "Use Yoda Conditions: if ( 'value' === $var )".

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

In `@wp-slimstat.php` around lines 1267 - 1269, The code currently treats any
truthy return from $geographicProvider->updateDatabase() as success before
persisting slimstat_last_geoip_dl; change the conditional to a strict boolean
comparison using Yoda style (true === $geographicProvider->updateDatabase()) so
only an explicit true advances slimstat_last_geoip_dl, ensuring provider
contract drift or non-boolean truthy values won't update the timestamp.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wp-slimstat.php`:
- Around line 1267-1271: The GeoIP update path
($geographicProvider->updateDatabase()) currently only updates
slimstat_last_geoip_dl on success, leaving the timestamp stale on failure and
causing repeated blocking attempts in the cron-disabled fallback; change the
failure handling so a retry/backoff is recorded: when updateDatabase() fails,
set a retry timestamp (either update_option('slimstat_last_geoip_dl', time() +
BACKOFF_SECONDS) or a dedicated option like 'slimstat_geoip_retry_after') and
implement exponential/backoff logic (e.g., increase BACKOFF_SECONDS on
consecutive failures using a failure count option) so admin/index.php’s fallback
will skip attempts until the retry timestamp has passed instead of retrying on
every page load.

---

Nitpick comments:
In `@wp-slimstat.php`:
- Around line 1267-1269: The code currently treats any truthy return from
$geographicProvider->updateDatabase() as success before persisting
slimstat_last_geoip_dl; change the conditional to a strict boolean comparison
using Yoda style (true === $geographicProvider->updateDatabase()) so only an
explicit true advances slimstat_last_geoip_dl, ensuring provider contract drift
or non-boolean truthy values won't update the timestamp.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3a7995a1-e1ca-4400-8337-1e2d72b3b9dd

📥 Commits

Reviewing files that changed from the base of the PR and between 624e278 and e945a08.

📒 Files selected for processing (1)
  • wp-slimstat.php

Comment on lines +1267 to +1271
$ok = $geographicProvider->updateDatabase();

// Set the last update time
update_option('slimstat_last_geoip_dl', time());
if ($ok) {
update_option('slimstat_last_geoip_dl', time());
}
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

Add retry backoff to avoid repeated blocking update attempts when cron is disabled.

With this change, failed updates leave slimstat_last_geoip_dl stale. In the cron-disabled fallback path (admin/index.php Line 300–315), that keeps update-needed true and can retrigger GeoIP downloads on every eligible admin page load. Given provider timeout can be long, this can repeatedly block admin requests.

💡 Proposed mitigation (rate-limit failed attempts)
-		$last_update = get_option('slimstat_last_geoip_dl', 0);
-		if ($last_update < $this_update) {
+		$last_update  = (int) get_option('slimstat_last_geoip_dl', 0);
+		$last_attempt = (int) get_option('slimstat_last_geoip_attempt', 0);
+		$retry_after  = 12 * HOUR_IN_SECONDS;
+		if ( $last_update < $this_update && ( $current_time - $last_attempt ) >= $retry_after ) {
+			update_option('slimstat_last_geoip_attempt', $current_time);

 			// Determine which geolocation provider to use
 			$provider = self::resolve_geolocation_provider();
 			if (false === $provider) {
 				return;
 			}

 			try {
 				$geographicProvider = new \SlimStat\Services\Geolocation\GeolocationService($provider, []);
-				$ok = $geographicProvider->updateDatabase();
+				$ok = ( true === $geographicProvider->updateDatabase() );

-				if ($ok) {
-					update_option('slimstat_last_geoip_dl', time());
+				if ( true === $ok ) {
+					update_option('slimstat_last_geoip_dl', $current_time);
 				}

As per coding guidelines, "Implement graceful fallbacks when external services fail; do not break the site if a remote request fails" and "Use WP-Cron or batching for heavy jobs like IP lookups and aggregations; avoid blocking requests".

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

In `@wp-slimstat.php` around lines 1267 - 1271, The GeoIP update path
($geographicProvider->updateDatabase()) currently only updates
slimstat_last_geoip_dl on success, leaving the timestamp stale on failure and
causing repeated blocking attempts in the cron-disabled fallback; change the
failure handling so a retry/backoff is recorded: when updateDatabase() fails,
set a retry timestamp (either update_option('slimstat_last_geoip_dl', time() +
BACKOFF_SECONDS) or a dedicated option like 'slimstat_geoip_retry_after') and
implement exponential/backoff logic (e.g., increase BACKOFF_SECONDS on
consecutive failures using a failure count option) so admin/index.php’s fallback
will skip attempts until the retry timestamp has passed instead of retrying on
every page load.

@parhumm
Copy link
Copy Markdown
Contributor Author

parhumm commented Mar 13, 2026

E2E Test Results

4/4 passed (7.0s)

Test Description Result
Test 1 DbIpProvider.updateDatabase() includes file.php in non-admin context PASS
Test 2 Cron callback catches \Throwable from provider PASS
Test 3 Admin AJAX handler catches \Throwable from provider PASS
Test 4 Failed download does not update geoip timestamp PASS

Test 4 is new — stubs HTTP with WP_Error so updateDatabase() returns false, then asserts slimstat_last_geoip_dl remains null. Confirms that failed downloads no longer suppress retry attempts.

@parhumm
Copy link
Copy Markdown
Contributor Author

parhumm commented Mar 13, 2026

PR Review: fix(cron): only update geoip timestamp on successful download

Verdict: APPROVE

Production Change (wp-slimstat.php)

Single-line behavioral fix at L1267: captures updateDatabase() return value and gates update_option() on success. Clean, minimal, correct.

Security: No new superglobal access, no DB queries, no user input handling. N/A.
Performance: No impact — same code path, one extra boolean check.
Backward Compatibility: No API changes, no hook modifications.

Test Changes

  • cron-frontend-shim-mu-plugin.php: New callback_download_fail mode stubs HTTP with WP_Error so updateDatabase() returns false. All superglobal access is gated behind SLIMSTAT_E2E_TESTING constant — safe.
  • issue-180-dbip-cron-tempnam.spec.ts: Test 4 verifies timestamp stays null after failed download. Double assertion (PHP-side ts_changed + DB-side getGeoipTimestamp()).

Checklist

Category Status
Security Pass — no new attack surface
Performance Pass — negligible overhead
Standards Pass — follows existing patterns
Backward Compat Pass — no public API changes
Test Coverage Pass — dedicated regression test added

No findings above confidence threshold. Clean PR.

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

🧹 Nitpick comments (2)
tests/e2e/helpers/cron-frontend-shim-mu-plugin.php (1)

81-86: Align this new branch with PHP style guidelines (Yoda + tabs).

Use Yoda condition format and tab indentation in this changed block.

Suggested change
-    } elseif ($mode === 'callback_download_fail') {
+	} elseif ( 'callback_download_fail' === $mode ) {
As per coding guidelines, `**/*.php`: "Use tabs (not spaces) for indentation" and "Use Yoda Conditions: `if ( 'value' === $var )`".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/helpers/cron-frontend-shim-mu-plugin.php` around lines 81 - 86,
Change the branch to use a Yoda condition and tabs for indentation: replace
"elseif ($mode === 'callback_download_fail')" with "elseif (
'callback_download_fail' === $mode )" and convert the indentation in that block
(including the add_filter('pre_http_request', function () { ... }, 1); closure
and its inner lines) from spaces to tabs so the block uses tab indentation
consistently.
tests/e2e/issue-180-dbip-cron-tempnam.spec.ts (1)

83-88: Assert shim success before timestamp assertions.

Add an explicit success check so failures in the shim path are surfaced immediately.

Suggested change
     const body = await res.json();
+    expect(body.success, 'Shim callback should complete successfully').toBe(true);

     // The download failed (HTTP stubbed with WP_Error → updateDatabase returns false).
     // The timestamp must NOT be updated — otherwise retries are suppressed until next month.
     expect(body.ts_changed, 'Timestamp should not change on failed download').toBe(false);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/issue-180-dbip-cron-tempnam.spec.ts` around lines 83 - 88, Before
asserting the timestamp, add an explicit assertion that the shim succeeded by
checking the response body success flag (e.g. assert expect(body.success, 'Shim
should succeed').toBe(true) or the appropriate success field on body)
immediately after const body = await res.json(); so shim failures are surfaced
before the ts_changed assertion; keep the new expect placed before
expect(body.ts_changed...).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/e2e/helpers/cron-frontend-shim-mu-plugin.php`:
- Around line 91-104: The current try/catch around
\wp_slimstat::wp_slimstat_update_geoip_database() swallows any \Throwable and
still returns success: true, hiding failures; change the handler so that if a
\Throwable is caught you either rethrow or set the response to indicate failure
(e.g. success: false) and include/log the exception details (message/trace)
before returning the JSON; update the catch block for
wp_slimstat_update_geoip_database to capture $e, record/process the error (via
error_log or returning it in the JSON) and ensure the echo json_encode response
uses the captured error to set 'success' => false and include an 'error' field
instead of always reporting success.

---

Nitpick comments:
In `@tests/e2e/helpers/cron-frontend-shim-mu-plugin.php`:
- Around line 81-86: Change the branch to use a Yoda condition and tabs for
indentation: replace "elseif ($mode === 'callback_download_fail')" with "elseif
( 'callback_download_fail' === $mode )" and convert the indentation in that
block (including the add_filter('pre_http_request', function () { ... }, 1);
closure and its inner lines) from spaces to tabs so the block uses tab
indentation consistently.

In `@tests/e2e/issue-180-dbip-cron-tempnam.spec.ts`:
- Around line 83-88: Before asserting the timestamp, add an explicit assertion
that the shim succeeded by checking the response body success flag (e.g. assert
expect(body.success, 'Shim should succeed').toBe(true) or the appropriate
success field on body) immediately after const body = await res.json(); so shim
failures are surfaced before the ts_changed assertion; keep the new expect
placed before expect(body.ts_changed...).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 82d5f0f4-4561-4d80-9158-eaf9a824d074

📥 Commits

Reviewing files that changed from the base of the PR and between e945a08 and f6df713.

📒 Files selected for processing (2)
  • tests/e2e/helpers/cron-frontend-shim-mu-plugin.php
  • tests/e2e/issue-180-dbip-cron-tempnam.spec.ts

parhumm added 3 commits March 13, 2026 10:33
- Shim callback_download_fail catch block now returns success:false with
  error details when \Throwable is caught instead of silently succeeding
- Use Yoda conditions throughout cron-frontend-shim-mu-plugin.php
- Add expect(body.success).toBe(true) guard in Test 4 before timestamp
  assertions to surface shim failures early
Add E2E tests for Pro issue #182 (Advanced Whois AJAX migration) with
supporting nonce-helper MU-plugin and setup.ts helper functions.
Replace json_encode() with wp_json_encode() (6 instances) and sanitize
direct $_GET comparison on line 24 for WPCS consistency.
@parhumm
Copy link
Copy Markdown
Contributor Author

parhumm commented Mar 13, 2026

PR Review: APPROVE

Reviewer: Automated WP PR Review
Files reviewed: 3 PHP + 2 TypeScript (+270 / -5 lines)

Production Code (wp-slimstat.php)

The core fix is minimal and correct: updateDatabase() return value is now checked before calling update_option('slimstat_last_geoip_dl', time()). This prevents failed downloads from suppressing retries until the next month.

Security: No issues — no new user input handling, no new DB queries, no new output.
Performance: No issues — no new queries, no autoload concerns.
Compatibility: No issues — updateDatabase() already returned bool, this just uses the return value.

Test Code

  • cron-frontend-shim-mu-plugin.php: Properly guarded by SLIMSTAT_E2E_TESTING constant. New callback_download_fail mode correctly stubs HTTP, snapshots timestamps, and surfaces caught errors.
  • nonce-helper-mu-plugin.php: Clean — current_user_can('manage_options') check, proper sanitization.
  • issue-180-dbip-cron-tempnam.spec.ts: New Test 4 validates the fix with three-layer assertion (shim success, timestamp unchanged, DB verification).
  • pro-maxmind-details-addon.spec.ts: Comprehensive coverage for Pro addon migration ([Bug] Pro MaxMindDetailsAddon references removed GeoIP class — fatal on Advanced Whois AJAX #182).

Verdict

No CRITICAL or WARNING findings. Two INFO items (wp_json_encode, $_GET sanitization consistency) were already fixed in fcf8e7fc.

APPROVE — Ship it.

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 `@tests/e2e/helpers/nonce-helper-mu-plugin.php`:
- Around line 1-21: Wrap the registration of the WP AJAX endpoint so it only
runs when the E2E harness constant is set: check for the SLIMSTAT_E2E_TESTING
constant before calling add_action('wp_ajax_test_create_nonce', ...). Modify the
top-level registration in this MU-plugin (the anonymous callback that uses
current_user_can('manage_options'), sanitize_text_field($_POST['nonce_action']),
wp_create_nonce and wp_send_json_success/wp_send_json_error) to be a no-op
unless SLIMSTAT_E2E_TESTING is defined and truthy, mirroring the guard used in
cron-frontend-shim-mu-plugin.php.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 18d78b85-5c28-4ab8-b512-e2807c8d6ec2

📥 Commits

Reviewing files that changed from the base of the PR and between f6df713 and fcf8e7f.

📒 Files selected for processing (5)
  • tests/e2e/helpers/cron-frontend-shim-mu-plugin.php
  • tests/e2e/helpers/nonce-helper-mu-plugin.php
  • tests/e2e/helpers/setup.ts
  • tests/e2e/issue-180-dbip-cron-tempnam.spec.ts
  • tests/e2e/pro-maxmind-details-addon.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/e2e/issue-180-dbip-cron-tempnam.spec.ts

Comment on lines +67 to +84
test('admin pages load without PHP fatal errors', async ({ page }) => {
await setSlimstatOption(page, 'addon_maxmind_enable', 'on');

const adminPages = [
'/wp-admin/',
'/wp-admin/admin.php?page=slimview1',
'/wp-admin/admin.php?page=slimconfig',
];

for (const adminPage of adminPages) {
const response = await page.goto(adminPage);
expect(response?.status(), `${adminPage} should not 500`).toBeLessThan(500);

const body = await page.content();
expect(body).not.toContain('Fatal error');
expect(body).not.toContain("Class 'SlimStat\\Services\\GeoIP' not found");
}
});
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

Assert authenticated admin context before treating these pages as covered.

Tests 1, 5, and 6 will also pass on wp-login.php: the login page typically returns a non-500 response, contains neither fatal string, and does not inject the whois URL. That means these checks can go green without ever rendering the addon UI. Please add an explicit admin-only assertion before the page checks so the suite fails fast when auth/setup is broken.

Suggested change
   test('admin pages load without PHP fatal errors', async ({ page }) => {
     await setSlimstatOption(page, 'addon_maxmind_enable', 'on');
+    await page.goto('/wp-admin/');
+    await expect(page).toHaveTitle(/Dashboard/);

     const adminPages = [
       '/wp-admin/',
       '/wp-admin/admin.php?page=slimview1',
       '/wp-admin/admin.php?page=slimconfig',
@@
   test('admin pages stable when switching providers with addon enabled', async ({ page }) => {
     await setSlimstatOption(page, 'addon_maxmind_enable', 'on');
+    await page.goto('/wp-admin/');
+    await expect(page).toHaveTitle(/Dashboard/);

     for (const provider of ['dbip', 'maxmind', 'cloudflare', 'disable']) {
       await setSlimstatOption(page, 'geolocation_provider', provider);
@@
   test('addon disabled does not inject whois URL', async ({ page }) => {
     await setSlimstatOption(page, 'addon_maxmind_enable', 'off');
     await setSlimstatOption(page, 'geolocation_provider', 'dbip');
+    await page.goto('/wp-admin/');
+    await expect(page).toHaveTitle(/Dashboard/);

     await page.goto('/wp-admin/admin.php?page=slimview1');
     await page.waitForLoadState('domcontentloaded');

Also applies to: 155-170, 174-183

Mirror the safety guard from cron-frontend-shim-mu-plugin.php so the
test AJAX endpoint is a no-op on non-test environments.
@parhumm parhumm merged commit 4dcac5a into development Mar 13, 2026
1 check passed
@parhumm parhumm deleted the fix/cron-geoip-retry branch March 13, 2026 09:58
parhumm added a commit that referenced this pull request Mar 14, 2026
…218 fix

- Added PR #218: Fix 500 errors on REST/admin-ajax tracking endpoints
- Added PR links for consent fixes (#172, #178), CSS (#175), email layout (#177),
  GeoIP timestamp (#185), admin performance (#189), visitor counting (#178)
@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.

1 participant