Skip to main content

Staging Mode Internals — StagingManager

Reference for the Benecaster\Staging\StagingManager class, the feed cache TTL override, and the is_staging field in the daily validation payload. Intended for add-on developers who need to be staging-aware or want to interact with the staging active set.


StagingManager Class

Location: src/Staging/StagingManager.php

The entry point for all staging mode detection and subscriber set management. Available via the service container.

$staging = benecaster()->container()->make( \Benecaster\Staging\StagingManager::class );

is_staging(): bool

Returns true if the current site is operating in staging mode.

Detection is performed once per request and cached in a static property. Subsequent calls within the same request return the cached result without re-running the URL checks.

Two conditions activate staging mode:

  1. The site URL (get_site_url()) matches a known staging domain pattern — see URL patterns below.
  2. define('BENECASTER_STAGING', true) is present in wp-config.php.

Either condition is sufficient. The constant takes precedence over URL detection and allows forcing staging mode on a domain that would not otherwise be detected.

if ( $staging->is_staging() ) {
    // Running on a staging environment.
}

get_staging_cap(): int|null

Returns the staging subscriber cap for this license.

Reads the staging_cap field from the benecaster_license_cache WordPress option, which is populated by the daily validation response from the license server.

  • Returns int (e.g. 25) — Free, Starter, and Growth plans have a 25-subscriber staging cap.
  • Returns null — Pro, Multi-Show, and Studio plans have no staging cap. An operator-set null override also returns null.

When null, add_to_set() will never return a cap error and rebuild_default_set() includes all subscribers.


get_active_set(): array

Returns the current staging active set as an array of WordPress user IDs.

Reads from the benecaster_staging_tokens WordPress option. Returns an empty array when staging is not active or the set has not yet been built.

$active_user_ids = $staging->get_active_set();
// → [17, 42, 88, ...]  or  []

Use this to check whether a specific user has an active feed token on this staging site:

$is_active = in_array( $user_id, $staging->get_active_set(), true );

add_to_set(int $user_id): bool|WP_Error

Adds a user to the staging active set.

Parameters:

Parameter Type Description
$user_id int WordPress user ID to add.

Returns:

  • true — user was added successfully.
  • WP_Error — staging cap is set and the set is already at capacity (cap !== null && count >= cap). The caller is responsible for presenting a swap UI before retrying.

This method does not auto-swap. If the set is at cap, the caller must first call remove_from_set() to free a slot, then call add_to_set() again.

After a successful add, add_to_set() fires do_action('benecaster_clear_feed_cache', $show_id, $tier_slug) once per distinct (show, tier) pair derived from the user’s active tokens. This prevents stale cache entries from serving the new staging-active subscriber an empty feed on their first poll.

$result = $staging->add_to_set( $user_id );
if ( is_wp_error( $result ) ) {
    // At cap — present the swap modal.
} else {
    // Successfully added.
}

remove_from_set(int $user_id): void

Removes a user from the staging active set.

Silently succeeds if the user is not currently in the set — safe to call without checking first.

$staging->remove_from_set( $user_id );
// Always void — no return value to check.

rebuild_default_set(): void

Regenerates the staging active set using the default selection algorithm.

Algorithm: For each configured membership tier, selects at least 2 subscribers. Remaining cap slots are filled by join date (oldest subscribers first) until the cap is reached. When get_staging_cap() returns null, all subscribers are included.

Called automatically on first staging detection (when the set is empty) and when the admin triggers “Reset to defaults.” Overwrites the current set entirely — any manual additions are discarded.

$staging->rebuild_default_set();
// New set is saved to benecaster_staging_tokens option.

Feed Cache TTL in Staging

FeedCache::get_ttl(int $show_id) is staging-aware:

public function get_ttl( int $show_id ): int {
    if ( $this->staging_manager->is_staging() ) {
        return defined( 'BENECASTER_STAGING_CACHE_TTL' )
            ? (int) BENECASTER_STAGING_CACHE_TTL
            : FeedCache::STAGING_CACHE_TTL_DEFAULT; // 45
    }
    return $this->get_production_ttl( $show_id );
}

Constants:

Constant Default Notes
FeedCache::STAGING_CACHE_TTL_DEFAULT 45 Default TTL in seconds when staging is active.
BENECASTER_STAGING_CACHE_TTL (not defined) Set in wp-config.php to override the default staging TTL. Value is in seconds.

To extend the staging cache TTL (for testing caching behavior itself):

// In wp-config.php on your staging site:
define( 'BENECASTER_STAGING_CACHE_TTL', 300 ); // 5 minutes

To effectively disable the staging cache TTL reduction (not recommended):

define( 'BENECASTER_STAGING_CACHE_TTL', PHP_INT_MAX );

is_staging In the Validation Payload

When StagingManager::is_staging() returns true, the daily POST /validate request body sent to the Benecaster license server includes:

{
  "is_staging": true,
  ...
}

The is_staging field is omitted entirely in production. Absence of the field is the canonical signal that a ping comes from a production site — the license server does not treat absence as false; it treats it as “production.”

Two additional staging-specific adjustments apply to the validation payload:

  • subscriber_count is capped: min(actual_paying_count, staging_cap). This prevents a staging site with a full production subscriber database from triggering a tier upgrade or overage counter.
  • When staging_cap is null (Pro, Multi-Show, Studio), the actual count is sent uncapped — consistent with these plans already having no subscriber ceiling in production.

The license server excludes staging pings from tier upgrade logic, benchmark aggregation, and the estimated-reach preview for announcements. See License REST API for the full validation payload reference.


URL Patterns

The following URL patterns trigger automatic staging detection. Matching is case-insensitive and checks only the host portion of get_site_url().

Pattern Example match
staging.* subdomain staging.mypodcast.com
*.local TLD mypodcast.local
localhost http://localhost
*.test TLD mypodcast.test
*.wpengine.com mypodcast.staging.wpengine.com
*.kinsta.cloud mypodcast.kinsta.cloud
*.flywheelstaging.com mypodcast.flywheelstaging.com
*.ngrok.io / *.ngrok.app abc123.ngrok.io

Domains not matching these patterns require define('BENECASTER_STAGING', true) in wp-config.php.


WordPress Option

The staging active set is stored in the benecaster_staging_tokens WordPress option as a serialized array of user IDs. Read it via $staging->get_active_set() rather than calling get_option() directly — the method handles empty-state edge cases and is the stable API surface.