Skip to content

Closes #8769: Migrate StudioPress accelerator into Hostings Subscriber - #8771

Draft
remyperona wants to merge 2 commits into
developfrom
enhancement/8769-migrate-legacy-theme-compatibility
Draft

Closes #8769: Migrate StudioPress accelerator into Hostings Subscriber#8771
remyperona wants to merge 2 commits into
developfrom
enhancement/8769-migrate-legacy-theme-compatibility

Conversation

@remyperona

@remyperona remyperona commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

🤖 AI-generated — created by an automated pipeline. Review before acting on this.

Closes #8769

Description

Migrates the legacy inc/3rd-party/themes/studiopress.php procedural file into a container-registered Subscriber class at inc/ThirdParty/Hostings/StudioPress.php, resolving the issue where StudioPress/Genesis Accelerator compatibility was unconditionally required outside the DI container. The integration now lives under the Hostings namespace and is registered via the container, while preserving identical runtime detection behavior and cache-clearing functionality.

Type of change

  • New feature (non-breaking change which adds functionality).
  • Bug fix (non-breaking change which fixes an issue).
  • Enhancement (non-breaking change which improves an existing functionality).
  • Breaking change (fix or feature that would cause existing functionality to not work as before).
  • Sub-task of Migrate legacy theme compatibility into the ThirdParty service provider #8769
  • Chore
  • Release

Detailed scenario

What was tested

Automated coverage authored and run for the migrated StudioPress subscriber:

  • Unit (tests/Unit/inc/ThirdParty/Hostings/StudioPress/): getSubscribedEvents (asserts both hooks are returned unconditionally), clearCacheAfterAccelerator (capability guard, nonce handling, URL-purge vs theme-purge branches), cleanAcceleratorCache (runtime isset/is_a guard, cache_flush_theme() invoked only when the global is present).
  • Integration (tests/Integration/inc/ThirdParty/Hostings/StudioPress/): hooksAreRegistered (container resolves studiopress_accelerator to a StudioPress instance and both admin_init / rocket_after_clean_domain callbacks are actually attached after bootstrap), cleanAcceleratorCache.
  • Both hook callbacks were verified byte-for-byte equivalent to the deleted procedural inc/3rd-party/themes/studiopress.php; no behavioral change.

QA: PASS (report: #8771 (comment)). QA booted the wp-env environment and verified all four acceptance criteria live: the container resolves studiopress_accelerator to a StudioPress instance; both admin_init and rocket_after_clean_domain hooks are attached at priority 10; a full cache clear ran with no warnings/fatals; and the new suites passed in-container (unit --group StudioPress 10 tests/41 assertions, integration --group StudioPress 3 tests/6 assertions). The real host-purge branch (SP_Accel_Nginx_Proxy_Cache_Purge::cache_flush_theme()) is guarded and not exercisable off a StudioPress host — covered by Mockery-based tests. All CI checks green (PHPUnit matrix PHP 7.4–8.5, PHPCS, PHPStan, task-check).

How to test

  1. Manual Smoke Test (required): On a site WITHOUT SP_Accel_Nginx_Proxy_Cache_Purge defined (the common case):

    • Verify the StudioPress subscriber hooks are still registered: wp eval 'var_dump( has_action( "admin_init" ), has_action( "rocket_after_clean_domain" ) );' should confirm both hooks attached.
    • Trigger a full cache clear via the admin dashboard "Clear cache" button, or programmatically with wp eval 'rocket_clean_domain();'.
    • Confirm cache clear completes without fatal errors or warnings referencing sp_accel_nginx_proxy_cache_purge.
  2. Automated Unit Test Coverage: 10 new unit tests in tests/Unit/inc/ThirdParty/Hostings/StudioPress/ cover:

    • clear_cache_after_accelerator() with: (a) capability denied, (b) valid sp-accel-purge-url nonce + cache-purge-url present, (c) valid sp-accel-purge-theme nonce, (d) invalid/missing nonce, (e) $GLOBALS['sp_accel_nginx_proxy_cache_purge'] unset.
    • clean_accelerator_cache() with the global set and absent.
  3. Automated Integration Test Coverage: 3 new integration tests in tests/Integration/inc/ThirdParty/Hostings/StudioPress/ verify:

    • End-to-end hook wiring via the real Subscriber_Interface and event manager.
    • Subscriber is registered and has_action() reports both hooks present regardless of HostResolver::get_host_service()'s return value (locks in the "always-on" contract).

Affected Features & Quality Assurance Scope

  • StudioPress/Genesis Accelerator nginx cache-purge integration.
  • WP Rocket cache-clearing workflows: admin dashboard "Clear cache" button, WP-CLI wp rocket clean, and programmatic rocket_clean_domain() calls.
  • ThirdParty DI container registration and event subscriber wiring.
  • Existing Kinsta/WPEngine/GoDaddy Hostings subscribers remain unaffected.

Technical description

Documentation

Why always-loaded registration (not gated by HostResolver):

SP_Accel_Nginx_Proxy_Cache_Purge can be defined by a Genesis/StudioPress child theme's functions.php, which WordPress loads after the plugins_loaded action. HostResolver::get_host_service() runs inside Hostings\ServiceProvider::register(), which is hooked to plugins_loaded. A resolver-time class_exists() check would return false on sites where the integration exists but is defined theme-side, silently dropping both hooks on exactly the sites this integration targets — a regression from the current always-loaded require.

Detection is therefore performed at hook-fire time inside each callback (admin_init and rocket_after_clean_domain), using the runtime isset()/is_a() guard that was already present in the legacy code. This guard is evaluated after the theme has loaded, so it correctly detects theme-defined instances.

Implementation pattern:

  • StudioPress class implements Subscriber_Interface and is registered as a shared container binding ('studiopress_accelerator' => StudioPress::class) unconditionally in Hostings\ServiceProvider::register().
  • get_subscribed_events() returns both hooks unconditionally (no resolver-time gate).
  • Each callback method preserves the original isset()/is_a() guard verbatim; mirrors the pattern used by Kinsta.php (defensive re-check even though the subscriber is conditionally registered there; here the guard is the entire detection mechanism).
  • StudioPress is added to the $common_subscribers array in Plugin::init_common_subscribers() as an unconditional entry, matching the pattern used by 'jetpack', 'mobile_subscriber', and 'wordfence_subscriber'.
  • HostResolver.php and HostSubscriberFactory.php are left unchanged; no new detection branch or case added.

Behavioral equivalence:

Both hooks (admin_init for user-initiated purge confirmation, rocket_after_clean_domain for automatic cache coordination) are ported verbatim from the legacy procedural file:

  • clear_cache_after_accelerator() → previously rocket_clear_cache_after_studiopress_accelerator()
  • clean_accelerator_cache() → previously rocket_clean_studiopress_accelerator()

All conditionals, nonce action strings, sanitization, and function calls are unchanged.

New dependencies

None.

Risks

None identified.

The new code path is a 1:1 refactoring of existing, production-proven logic. The only structural change is moving from a procedural require to a container-registered Subscriber, which is the standard DI pattern used throughout the codebase. Runtime behavior is identical (detection and cache-clearing happen at the same hooks with the same guards).

No new permissions, capabilities, or API calls introduced. Multisite network activation and plugin activation/deactivation handling are unaffected (the legacy code never hooked those events either).

Follow-up tickets

None. Two nice-to-have items surfaced during review (a wiring-key regression unit test, and a doc comment on the always-on integration test's coverage limitation) were reviewed and discarded — the existing integration test already asserts the end-to-end wiring at runtime.

Mandatory Checklist

Code validation

  • I validated all the Acceptance Criteria. If possible, provide screenshots or videos.
  • I triggered all changed lines of code at least once without new errors/warnings/notices.
  • I implemented built-in tests to cover the new/changed code.

Code style

  • I wrote a self-explanatory code about what it does.
  • I protected entry points against unexpected inputs.
  • I did not introduce unnecessary complexity.
  • Output messages (errors, notices, logs) are explicit enough for users to understand the issue and are actionnable.

Unticked items justification

Output-messages item is N/A: this refactoring introduces no user-facing output messages — it relocates existing hook callbacks 1:1 with no new notices, errors, or logs. Acceptance criteria were validated by the QA pass (see "What was tested"); the new/changed lines are exercised by the unit + integration suites.

Additional Checks

  • In the case of complex code, I wrote comments to explain it.
  • When possible, I prepared ways to observe the implemented system (logs, data, etc.).
  • I added error handling logic when using functions that could throw errors (HTTP/API request, filesystem, etc.)

…Hostings Subscriber

Ports the raw-require'd inc/3rd-party/themes/studiopress.php into
inc/ThirdParty/Hostings/StudioPress.php as a container-registered
Subscriber, resolving #8769. Both hooks (admin_init, rocket_after_clean_domain)
are registered unconditionally since the runtime detection global
(SP_Accel_Nginx_Proxy_Cache_Purge) can be theme-defined and therefore isn't
reliably present when HostResolver runs at plugins_loaded; the existing
isset()/is_a() guard is preserved verbatim inside each callback instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@remyperona remyperona self-assigned this Aug 26, 2026
@codacy-production

codacy-production Bot commented Aug 26, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 12 complexity

Metric Results
Complexity 12

View in Codacy

🟢 Coverage 85.00% diff coverage

Metric Results
Coverage variation Report missing for 308c4d71
Diff coverage 85.00% diff coverage (50.00%)

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (308c4d7) Report Missing Report Missing Report Missing
Head commit (49f2481) 47680 22474 47.14%

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#8771) 20 17 85.00%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

1 Codacy didn't receive coverage data for the commit, or there was an error processing the received data. Check your integration for errors and validate that your coverage setup is correct.

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@remyperona

Copy link
Copy Markdown
Contributor Author

Note

Generated by the AI delivery pipeline (qa-engineer · Claude Opus 4.8).

QA: ✅ PASS

Acceptance Criterion Method Result
Container-registered subscriber under ThirdParty namespace, legacy require removed API/Analysis
Detection at hook-fire time; always-loaded decision documented in code Analysis
No behavioral change: cache clearing on StudioPress/Genesis Accelerator still works API/Analysis ✅ (host-specific purge path guarded — see note)
Existing tests pass; new unit + integration coverage added API (local phpunit)

Evidence

  1. Container registrationdocker exec ... wp eval confirms apply_filters('rocket_container','')->has('studiopress_accelerator') returns true, resolving to WP_Rocket\ThirdParty\Hostings\StudioPress. inc/ThirdParty/Hostings/ServiceProvider.php:56 calls addShared('studiopress_accelerator', StudioPress::class) unconditionally, ahead of and independent from the HostResolver::get_host_service() gate that applies only to the actual hosting subscriber (lines 58-63). inc/3rd-party/3rd-party.php no longer requires themes/studiopress.php (deleted, confirmed via git diff 308c4d793).

  2. No resolver-time gateinc/ThirdParty/Hostings/StudioPress.php:5-16 docblock documents the rationale (child-theme functions.php loads after plugins_loaded, so a resolver-time class_exists() would miss theme-defined instances). Grep for class_exists/get_host_service in StudioPress.php returns no hits — confirms no resolver-time gate. inc/Plugin.php:426 adds 'studiopress_accelerator' to the unconditional $common_subscribers array (same tier as jetpack, optimole_subscriber), above the HostResolver::get_host_service() conditional block.

  3. Hook wiring + cache clearwp eval confirms both has_action('admin_init', [$sub,'clear_cache_after_accelerator']) and has_action('rocket_after_clean_domain', [$sub,'clean_accelerator_cache']) return 10 (registered). Running rocket_clean_domain() via wp eval --debug completes with no PHP warnings/notices/fatals and no debug.log entries. Note: the PR's suggested wp rocket clean --confirm command does not exist in this codebase (wp help rocket only lists abilities-catalog); I used rocket_clean_domain() directly, which fires the same rocket_after_clean_domain hook.
    Guard note (CANNOT_VERIFY in this environment, as expected): the actual StudioPress-host purge call ($GLOBALS['sp_accel_nginx_proxy_cache_purge']->cache_flush_theme()) is gated by isset($GLOBALS['sp_accel_nginx_proxy_cache_purge']) && is_a(..., 'SP_Accel_Nginx_Proxy_Cache_Purge') at inc/ThirdParty/Hostings/StudioPress.php:46,72. Neither the global nor the SP_Accel_Nginx_Proxy_Cache_Purge class exist on this local/non-StudioPress environment, so the purge branch is a verified no-op here, not a positive confirmation of the real hosting integration. This path is covered by the unit/integration tests via Mockery mocks of the class instead.

  4. Test coverage — ran the new suites directly inside the wp-env cli container (not CI, but a real local execution):

    • vendor/bin/phpunit --configuration tests/Unit/phpunit.xml.dist --group StudioPress10 tests, 37 assertions, OK.
    • vendor/bin/phpunit --configuration tests/Integration/phpunit.xml.dist --group StudioPress3 tests, 6 assertions, OK.
      Read all new test/fixture files — coverage matches what the PR describes (capability denied, valid sp-accel-purge-url nonce with URL, valid sp-accel-purge-theme nonce, invalid nonce, missing global, valid-nonce-but-empty-url edge case, and an integration test locking in the "always registered regardless of HostResolver" contract).

Smoke tests

  • Plugin deactivate/reactivate cycle: clean, no fatals/warnings.
  • /wp-admin/options-general.php?page=wprocket and /wp-admin/ load with HTTP 200 (authenticated) and no debug.log output.

Note on scope vs. originating issue: issue #8769 suggested inc/ThirdParty/Themes/StudioPress.php registered via the Themes service provider / ThemeResolver. The PR instead places the class under inc/ThirdParty/Hostings/ and registers it in the Hostings ServiceProvider, with a documented rationale (mirrors the Kinsta.php pattern; StudioPress/Genesis Accelerator is a hosting-infra integration, not a theme-detection one). This satisfies the literal acceptance criteria ("container-registered subscriber under the ThirdParty namespace") and is a reasonable interpretation, but it is a location deviation from the issue text worth a reviewer's explicit sign-off.

Comment thread tests/Unit/inc/ThirdParty/Hostings/StudioPress/clearCacheAfterAccelerator.php Outdated
Comment thread tests/Unit/inc/ThirdParty/Hostings/StudioPress/cleanAcceleratorCache.php Outdated
Comment thread tests/Integration/inc/ThirdParty/Hostings/StudioPress/cleanAcceleratorCache.php Outdated
@remyperona

remyperona commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Note

Generated by the AI delivery pipeline (lead-reviewer · Claude Opus 4.8).

Review: ✅ PASS

Re-reviewed after commit 49f248169. All 5 MEDIUM blockers from the prior pass are resolved:

  • tests/Fixtures/classes/SP_Accel_Nginx_Proxy_Cache_Purge.php added (class_exists()-guarded stub, mirrors the existing NinukisCaching.php convention in the same directory) — resolves the class.notFound PHPStan errors.
  • All 3 addToAssertionCount(1) calls replaced with real assertions (assertArrayNotHasKey('sp_accel_nginx_proxy_cache_purge', $GLOBALS) for the "global absent" no-op paths; explicit Functions\expect(...)->never() on all five downstream calls for the capability-denied early-return path) — resolves the method.internal PHPStan errors, and the capability-denied assertion is now strictly stronger than before.

Verified: lint / PHPStan and lint / PHP CodeSniffer CI checks now pass. Zero diff in inc/ between fd118a801 and 49f248169 — production logic (StudioPress.php, ServiceProvider.php, Plugin.php) is unchanged from the already-approved implementation. No new issues introduced.

Adds a tests/Fixtures/classes stub for SP_Accel_Nginx_Proxy_Cache_Purge
(mirroring the NinukisCaching pattern) so PHPStan can resolve the
Mockery::mock() calls, and replaces addToAssertionCount() with real
assertions (assertArrayNotHasKey / Mockery ->never() expectations) in
the three no-op test branches, since addToAssertionCount() is not
baseline-eligible for new code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@remyperona
remyperona marked this pull request as ready for review August 26, 2026 20:25
@remyperona
remyperona marked this pull request as draft August 26, 2026 20:39
@remyperona

Copy link
Copy Markdown
Contributor Author

Important

On hold — do not merge yet. Pending confirmation of whether StudioPress Accelerator is still a supported integration.

The migration itself is complete, green (all CI passing), QA-passed, and lead-review-approved — it strictly preserves current behavior. However, before merging we want to resolve an open question raised during review:

Is StudioPress Accelerator still in use, or fully deprecated since the WP Engine acquisition?

Signals that it may be dead:

  • The integration logic has had no purposeful, product-driven change since ~2017–2018 — every commit since is an incidental repo-wide sweep (coding standards, capability/sanitization pass, a WPML fix), not StudioPress-Accelerator work.
  • No public documentation could be found for the SP_Accel_Nginx_Proxy_Cache_Purge class, the sp_accel_nginx_proxy_cache_purge global, or the sp-accel-purge-url / sp-accel-purge-theme nonce actions.

What unblocks this PR:

  • If telemetry shows ~0 installs with SP_Accel_Nginx_Proxy_Cache_Purge present (or WP Engine confirms Accelerator is retired) → the integration is dead and the right move is to delete it entirely (drop the require + file, no new subscriber), not migrate it. This PR would be repurposed or closed in favor of a removal.
  • If it's still supported → merge this PR as-is (safe, behavior-preserving, and it unblocks the resolver optimization in Implement a plugin resolver service to optimize loading of 3rd party compatibility subscribers #6418).

Marking as draft until that decision is made.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate legacy theme compatibility into the ThirdParty service provider

1 participant