File: /wordpress/plugins/wp-cloud-client/beta/src/Settings/SettingsRepository.php
<?php
declare(strict_types=1);
namespace VPlugins\WPCloudClient\Settings;
class SettingsRepository {
private const OPTION_KEY = 'wpcc_settings';
/**
* Retrieve the configured site ID (9-char WP Cloud / Atomic site ID).
*
* Resolution order:
* 1. Atomic_Persistent_Data::ATOMIC_SITE_ID — platform-managed.
* 2. ATOMIC_SITE_ID constant — direct platform constant.
* 3. wpcc_site_id filter — last-resort hook.
*
* @return string|null Site ID or null if not resolvable.
*/
public function getSiteId(): ?string {
if ( class_exists( 'Atomic_Persistent_Data' ) ) {
$persistent = new \Atomic_Persistent_Data();
$v = trim( (string) ( $persistent->ATOMIC_SITE_ID ?? '' ) );
if ( '' !== $v ) {
return $v;
}
}
if ( \defined( 'ATOMIC_SITE_ID' ) && is_scalar( \ATOMIC_SITE_ID ) ) {
$v = (string) \ATOMIC_SITE_ID;
return '' !== $v ? $v : null;
}
try {
$value = (string) apply_filters( 'wpcc_site_id', '' );
} catch ( \Throwable $e ) {
error_log( '[WP Cloud Client] wpcc_site_id filter threw: ' . $e->getMessage() );
return null;
}
return '' !== $value ? $value : null;
}
/**
* Retrieve the Vendasta site ID (64-char VStore PK) for GA4 tracking.
*
* @return string|null Vendasta site ID or null if not set.
*/
public function getVendastaSiteId(): ?string {
if ( class_exists( 'Atomic_Persistent_Data' ) ) {
$persistent = new \Atomic_Persistent_Data();
$v = trim( (string) ( $persistent->WSP_SITE_ID ?? '' ) );
if ( '' !== $v ) {
return $v;
}
}
return null;
}
/**
* Retrieve the configured API key.
*
* Resolution order:
* 1. WSP_API_KEY constant — explicit override (wp-config.php or mu-plugin).
* 2. Atomic_Persistent_Data — platform-managed, not in DB; survives staging→prod pushes.
* 3. wpcc_api_key filter — last-resort hook.
*
* @return string|null API key or null if not set.
*/
public function getApiKey(): ?string {
if ( \defined( 'WSP_API_KEY' ) && is_scalar( \WSP_API_KEY ) ) {
$v = (string) \WSP_API_KEY;
return '' !== $v ? $v : null;
}
if ( class_exists( 'Atomic_Persistent_Data' ) ) {
$persistent = new \Atomic_Persistent_Data();
$v = trim( (string) ( $persistent->WSP_API_KEY ?? '' ) );
if ( '' !== $v ) {
return $v;
}
}
try {
$value = (string) apply_filters( 'wpcc_api_key', '' );
} catch ( \Throwable $e ) {
error_log( '[WP Cloud Client] wpcc_api_key filter threw: ' . $e->getMessage() );
return null;
}
return '' !== $value ? $value : null;
}
/**
* Retrieve the configured environment.
*
* Reads WSP_SITE_ENVIRONMENT from Atomic_Persistent_Data (PROD or DEMO).
* Defaults to 'PROD' if the value is absent.
*
* @return string Environment value ('PROD' or 'DEMO').
*/
public function getEnvironment(): string {
if ( class_exists( 'Atomic_Persistent_Data' ) ) {
$persistent = new \Atomic_Persistent_Data();
$value = trim( (string) ( $persistent->WSP_SITE_ENVIRONMENT ?? '' ) );
if ( '' !== $value ) {
return $value;
}
}
return 'PROD';
}
/**
* Check whether this site is a multisite installation.
*
* Reads WSP_SITE_TYPE from Atomic_Persistent_Data (SINGLE or MULTISITE).
* Defaults to false (single site) if not set.
*
* @return bool True if the site type is MULTISITE.
*/
public function isMultisite(): bool {
if ( class_exists( 'Atomic_Persistent_Data' ) ) {
$persistent = new \Atomic_Persistent_Data();
$value = strtoupper( trim( (string) ( $persistent->WSP_SITE_TYPE ?? '' ) ) );
return 'MULTISITE' === $value;
}
return false;
}
/**
* Check whether this install is safe to treat as a network for the
* purposes of reading/writing the primary site's option table.
*
* Those WordPress core functions (`get_blog_option()`, `update_blog_option()`,
* `get_main_site_id()`) are only defined when is_multisite() is true — ms-blog.php is
* conditionally loaded. WSP_SITE_TYPE is pushed by a separate, out-of-band
* platform API call, independent of the actual wp-config.php MULTISITE
* conversion, so it can briefly (or persistently, if misconfigured) say
* MULTISITE before/without the site actually being one. Calling those
* functions in that window would be a fatal "Call to undefined function"
* on every call site, including the wp_mail() interceptor. This requires
* both signals to agree before any of them are called.
*
* @return bool True only when WordPress core is multisite AND the
* platform flag also says so.
*/
public function isRealMultisite(): bool {
return is_multisite() && $this->isMultisite();
}
/**
* Check whether this site is a staging site.
*
* Reads WSP_STAGING from Atomic_Persistent_Data (TRUE or FALSE).
* Defaults to false if not set.
*
* @return bool True if staging is enabled.
*/
public function isStaging(): bool {
if ( class_exists( 'Atomic_Persistent_Data' ) ) {
$persistent = new \Atomic_Persistent_Data();
$value = strtoupper( trim( (string) ( $persistent->WSP_STAGING ?? '' ) ) );
return 'TRUE' === $value;
}
return false;
}
/**
* Retrieve the custom GA4 tracking ID.
*
* On multisite, this is a single network-wide value stored on the primary
* site, rather than a per-subsite value — every site in the network uses
* the same custom tracking ID (or falls back to the same platform default
* tracking ID, which is already identical across all sites regardless).
*
* @return string|null Tracking ID or null if not set.
*/
public function getGa4TrackingId(): ?string {
if ( $this->isRealMultisite() ) {
$value = trim( (string) ( get_blog_option( get_main_site_id(), self::OPTION_KEY, [] )['ga4_tracking_id'] ?? '' ) );
return '' !== $value ? $value : null;
}
return $this->get( 'ga4_tracking_id' );
}
/**
* Set the custom GA4 tracking ID.
*
* On multisite, always writes to the primary site's own settings,
* regardless of which site's admin the form was submitted from; see
* {@see self::getGa4TrackingId()}.
*
* @param string $value Tracking ID to store.
* @return bool True if the option was updated, false on failure.
*/
public function setGa4TrackingId( string $value ): bool {
if ( $this->isRealMultisite() ) {
$mainSiteId = get_main_site_id();
$current = get_blog_option( $mainSiteId, self::OPTION_KEY, [] );
$current = is_array( $current ) ? $current : [];
return (bool) update_blog_option( $mainSiteId, self::OPTION_KEY, array_merge( $current, [ 'ga4_tracking_id' => $value ] ) );
}
return $this->update( [ 'ga4_tracking_id' => $value ] );
}
/**
* Check whether the platform's default Google Analytics 4 tracking is enabled.
*
* Set via the Advanced Tools toggle (wp-cloud-manager's SetDefaultGoogleAnalytics
* RPC, sync_ga_settings action). When true, GoogleAnalytics4::injectScript()
* outputs the platform's default GA4 tracking script; when false, no script
* is injected at all. Defaults to true — tracking runs out of the box on
* every site without any action from wp-cloud-manager; the manager only
* needs to call this to turn it off for a specific site.
*
* On multisite, this is a single network-wide value stored on the primary
* site — see {@see self::getGa4TrackingId()} for why.
*
* @return bool True when the platform's default GA4 tracking should run.
*/
public function isGaEnabled(): bool {
if ( $this->isRealMultisite() ) {
$value = get_blog_option( get_main_site_id(), self::OPTION_KEY, [] )['ga_enabled'] ?? null;
return null === $value || filter_var( $value, FILTER_VALIDATE_BOOLEAN );
}
$value = $this->get( 'ga_enabled' );
if ( null === $value ) {
return true;
}
return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
}
/**
* Set whether the platform's default Google Analytics 4 tracking is enabled.
*
* On multisite, always writes to the primary site's own settings,
* regardless of which site's admin the form was submitted from; see
* {@see self::setGa4TrackingId()}.
*
* @param bool $enabled True to inject the platform's default GA4 tracking script.
* @return bool True if the option was updated, false on failure.
*/
public function setGaEnabled( bool $enabled ): bool {
if ( $this->isRealMultisite() ) {
$mainSiteId = get_main_site_id();
$current = get_blog_option( $mainSiteId, self::OPTION_KEY, [] );
$current = is_array( $current ) ? $current : [];
return (bool) update_blog_option( $mainSiteId, self::OPTION_KEY, array_merge( $current, [ 'ga_enabled' => $enabled ? '1' : '0' ] ) );
}
return $this->update( [ 'ga_enabled' => $enabled ? '1' : '0' ] );
}
/**
* One-time migration: partner_ga_enabled meant "the partner has their own
* separate GA integration, so suppress ours" — product decided to invert
* this to a plain "is our GA4 script enabled" toggle (ga_enabled). Moves
* any existing stored value to the new key, inverted, and removes the old
* key so a stale value can never be read again.
*
* @return void
*/
public function migrateGaToggle(): void {
if ( $this->isRealMultisite() ) {
$mainSiteId = get_main_site_id();
$current = get_blog_option( $mainSiteId, self::OPTION_KEY, [] );
$current = is_array( $current ) ? $current : [];
if ( ! array_key_exists( 'partner_ga_enabled', $current ) ) {
return;
}
$oldValue = filter_var( $current['partner_ga_enabled'], FILTER_VALIDATE_BOOLEAN );
unset( $current['partner_ga_enabled'] );
$current['ga_enabled'] = $oldValue ? '0' : '1';
update_blog_option( $mainSiteId, self::OPTION_KEY, $current );
return;
}
$current = $this->all();
if ( ! array_key_exists( 'partner_ga_enabled', $current ) ) {
return;
}
$oldValue = filter_var( $current['partner_ga_enabled'], FILTER_VALIDATE_BOOLEAN );
unset( $current['partner_ga_enabled'] );
$current['ga_enabled'] = $oldValue ? '0' : '1';
update_option( self::OPTION_KEY, $current );
}
/**
* Check whether debug mode is enabled.
*
* @return bool True if debug mode is on.
*/
public function isDebugMode(): bool {
return (bool) $this->get( 'debug_mode' );
}
/**
* Check whether the wp_mail interceptor (Email History logging) has been disabled.
*
* On multisite, this is a single network-wide value stored on the primary
* site — see {@see self::getGa4TrackingId()} for why. Supersedes the
* WSP-3560 per-site behavior: Email History is now a network administrator
* setting like GA4/business profile, not an independent per-subsite toggle.
*
* @return bool True if the mail interceptor is disabled.
*/
public function isMailInterceptorDisabled(): bool {
if ( $this->isRealMultisite() ) {
$value = get_blog_option( get_main_site_id(), self::OPTION_KEY, [] )['disable_mail_interceptor'] ?? '';
return (bool) $value;
}
return (bool) $this->get( 'disable_mail_interceptor' );
}
/**
* Set whether the wp_mail interceptor (Email History logging) is disabled.
*
* On multisite, always writes to the primary site's own settings,
* regardless of which site's admin the form was submitted from; see
* {@see self::setGa4TrackingId()}.
*
* @param bool $disabled True to disable mail interception, false to keep it enabled.
* @return bool True if the option was updated, false on failure.
*/
public function setMailInterceptorDisabled( bool $disabled ): bool {
if ( $this->isRealMultisite() ) {
$mainSiteId = get_main_site_id();
$current = get_blog_option( $mainSiteId, self::OPTION_KEY, [] );
$current = is_array( $current ) ? $current : [];
return (bool) update_blog_option( $mainSiteId, self::OPTION_KEY, array_merge( $current, [ 'disable_mail_interceptor' => $disabled ? '1' : '' ] ) );
}
return $this->update( [ 'disable_mail_interceptor' => $disabled ? '1' : '' ] );
}
/**
* Check whether the wp-login.php page is exposed.
*
* Defaults to false (login hidden) so direct access to wp-login.php is
* redirected away unless the manager has explicitly exposed it via
* set_login_visibility.
*
* On multisite, this is a single network-wide value stored on the primary
* site — see {@see self::getGa4TrackingId()} for why.
*
* @return bool True if the login page is exposed, false if hidden.
*/
public function isLoginExposed(): bool {
if ( $this->isRealMultisite() ) {
$value = get_blog_option( get_main_site_id(), self::OPTION_KEY, [] )['expose_login'] ?? '';
return (bool) $value;
}
return (bool) $this->get( 'expose_login' );
}
/**
* Set whether the wp-login.php page is exposed.
*
* On multisite, always writes to the primary site's own settings,
* regardless of which site's admin the form was submitted from; see
* {@see self::setGa4TrackingId()}.
*
* @param bool $exposed True to expose the login page, false to hide it.
* @return bool True if the option was updated, false on failure.
*/
public function setLoginExposed( bool $exposed ): bool {
if ( $this->isRealMultisite() ) {
$mainSiteId = get_main_site_id();
$current = get_blog_option( $mainSiteId, self::OPTION_KEY, [] );
$current = is_array( $current ) ? $current : [];
return (bool) update_blog_option( $mainSiteId, self::OPTION_KEY, array_merge( $current, [ 'expose_login' => $exposed ? '1' : '' ] ) );
}
return $this->update( [ 'expose_login' => $exposed ? '1' : '' ] );
}
/**
* Retrieve the redirect URL for hidden login page access attempts.
*
* On multisite, this is a single network-wide value stored on the primary
* site — see {@see self::getGa4TrackingId()} for why.
*
* @return string Redirect URL, or empty string if not set.
*/
public function getLoginRedirectUrl(): string {
if ( $this->isRealMultisite() ) {
return (string) ( get_blog_option( get_main_site_id(), self::OPTION_KEY, [] )['login_redirect_url'] ?? '' );
}
return (string) $this->get( 'login_redirect_url' );
}
/**
* Set the redirect URL for hidden login page access attempts.
*
* On multisite, always writes to the primary site's own settings,
* regardless of which site's admin the form was submitted from; see
* {@see self::setGa4TrackingId()}.
*
* @param string $url URL to redirect unauthenticated visitors to.
* @return bool True if the option was updated, false on failure.
*/
public function setLoginRedirectUrl( string $url ): bool {
if ( $this->isRealMultisite() ) {
$mainSiteId = get_main_site_id();
$current = get_blog_option( $mainSiteId, self::OPTION_KEY, [] );
$current = is_array( $current ) ? $current : [];
return (bool) update_blog_option( $mainSiteId, self::OPTION_KEY, array_merge( $current, [ 'login_redirect_url' => $url ] ) );
}
return $this->update( [ 'login_redirect_url' => $url ] );
}
/**
* Retrieve the WP Cloud Manager base URL.
*
* Checks the WPCC_MANAGER_URL constant first, then falls back to the
* wpcc_manager_url filter so the value can be supplied by the hosting
* environment without touching the database.
*
* @return string Manager base URL, or empty string if not configured.
*/
public function getManagerUrl(): string {
if ( \defined( 'WPCC_MANAGER_URL' ) ) {
return (string) \WPCC_MANAGER_URL;
}
return (string) apply_filters( 'wpcc_manager_url', '' );
}
/**
* Check whether all required credentials are configured.
*
* @return bool True if site ID and API key are set.
*/
public function isConfigured(): bool {
return $this->getSiteId() !== null
&& $this->getApiKey() !== null;
}
/**
* Update settings by merging with existing values.
*
* @param array $values Key-value pairs to update.
* @return bool True if the option was updated, false on failure.
*/
public function update( array $values ): bool {
$current = $this->all();
return (bool) update_option( self::OPTION_KEY, array_merge( $current, $values ) );
}
/**
* Remove specific keys from stored settings.
*
* Used for migrations to clean up stale keys from previous versions.
*
* @param array $keys Keys to remove.
*/
public function purge( array $keys ): void {
$current = $this->all();
foreach ( $keys as $key ) {
unset( $current[ $key ] );
}
update_option( self::OPTION_KEY, $current );
}
/**
* Retrieve all settings as an array.
*
* @return array All stored settings.
*/
public function all(): array {
return (array) get_option( self::OPTION_KEY, [] );
}
/**
* Retrieve a single setting value by key.
*
* @param string $key Setting key to retrieve.
* @return string|null Setting value or null if empty.
*/
private function get( string $key ): ?string {
$settings = $this->all();
$value = isset( $settings[ $key ] ) ? trim( (string) $settings[ $key ] ) : null;
return null !== $value && '' !== $value ? $value : null;
}
}