merchant forces 6rsh to keep * its own cart engine even when CartRevival is present. * function/filter `sixrsh_get_connection()` : lets any plugin discover that * 6rsh is connected, and on which plan. * * @package SixRsh_WhatsApp */ defined( 'ABSPATH' ) || exit; class SixRsh_Coexistence { /** Shared option: who owns cart recovery on this store. */ const OPTION_ENGINE_OWNER = 'cartrevival_engine_owner'; /** The pre-rename key. Still read and mirrored — see the file docblock. */ const OPTION_ENGINE_OWNER_LEGACY = 'cart_recovery_engine_owner'; /** Merchant override: keep 6rsh as the cart engine regardless. */ const OPTION_OVERRIDE = 'sixrsh_cart_engine_override'; const OWNER_CARTREVIVAL = 'cartrevival'; const OWNER_6RSH = '6rsh'; /** * Is the CartRevival plugin active on this store? * * Checked several ways so it works regardless of folder name or load order. * * @return bool */ public static function cartrevival_active(): bool { if ( class_exists( 'CartRevival' ) || defined( 'CARTREVIVAL_VERSION' ) ) { return true; } $active = (array) get_option( 'active_plugins', array() ); foreach ( $active as $path ) { if ( 0 === strpos( (string) $path, 'cartrevival/' ) ) { return true; } } // Multisite network-activated. if ( is_multisite() ) { $network = (array) get_site_option( 'active_sitewide_plugins', array() ); foreach ( array_keys( $network ) as $path ) { if ( 0 === strpos( (string) $path, 'cartrevival/' ) ) { return true; } } } return false; } /** * The current engine owner, new key first, then the pre-rename key. * * @return string 'cartrevival' | '6rsh' | '' */ public static function engine_owner(): string { $owner = (string) get_option( self::OPTION_ENGINE_OWNER, '' ); if ( '' !== $owner ) { return $owner; } return (string) get_option( self::OPTION_ENGINE_OWNER_LEGACY, '' ); } /** * Has CartRevival actually CLAIMED the recovery engine (i.e. it is present * and in a working state, not merely installed)? * * A claim under EITHER key counts. This is the yield decision, and yielding * when we are unsure costs the merchant nothing (CartRevival is the fuller * recovery product and is present); failing to yield double-messages the * shopper. During the rollout window the two plugins can legitimately be on * opposite sides of the rename, so both keys are consulted. * * @return bool */ public static function cartrevival_owns_engine(): bool { if ( ! self::cartrevival_active() ) { return false; } return self::OWNER_CARTREVIVAL === (string) get_option( self::OPTION_ENGINE_OWNER, '' ) || self::OWNER_CARTREVIVAL === (string) get_option( self::OPTION_ENGINE_OWNER_LEGACY, '' ); } /** Merchant has explicitly chosen to keep 6rsh's own cart engine. */ public static function override_active(): bool { return '1' === (string) get_option( self::OPTION_OVERRIDE, '' ); } /** * Should 6rsh suspend its OWN cart sync right now? * * True only when CartRevival owns the engine AND the merchant has not * overridden. This is the single question the cart tracker asks. * * @return bool */ public static function should_yield_cart_engine(): bool { return self::cartrevival_owns_engine() && ! self::override_active(); } /** * Claim the cart engine for 6rsh when nobody else owns it. Keeps the shared * option meaningful for other plugins reading it. Never steals ownership * from CartRevival. * * @return void */ public static function maybe_claim_engine(): void { if ( self::cartrevival_owns_engine() ) { return; // CartRevival owns it — leave it alone. } $owner = self::engine_owner(); // Stale claim left behind by a removed CartRevival → reclaim. if ( self::OWNER_CARTREVIVAL === $owner || '' === $owner ) { self::write_owner( self::OWNER_6RSH ); } } /** * Write engine ownership to both keys. * * The new key is the contract; the pre-rename key is mirrored so a store * still running a CartRevival that only reads the old name sees our claim * and stands down instead of scheduling alongside us. * * @param string $owner Owner slug. * @return void */ private static function write_owner( string $owner ): void { update_option( self::OPTION_ENGINE_OWNER, $owner, false ); update_option( self::OPTION_ENGINE_OWNER_LEGACY, $owner, false ); } /** * Boot: keep engine ownership honest on every load. * * @return void */ public static function init(): void { try { self::maybe_claim_engine(); } catch ( \Throwable $e ) { error_log( '[6rsh WhatsApp] Coexistence::init exception: ' . $e->getMessage() ); } } } /** * PUBLIC HELPER — let other plugins (CartRevival) discover the 6rsh connection. * * Stable, documented contract. Returns: * array{ * connected: bool, // a validated API key is stored * plan: string, // 'free' | 'starter' | 'pro' | 'extreme' | '' * api_base: string, // e.g. https://www.6rsh.com/api/v1 * version: string, // 6rsh plugin version * } * * Also exposed through the `sixrsh_get_connection` filter so a plugin can read * it without a hard function_exists() dependency: * $conn = apply_filters( 'sixrsh_get_connection', null ); * * The API KEY ITSELF IS NEVER EXPOSED here — CartRevival sends through 6rsh by * calling the documented endpoint with the key it reads from its own settings, * or by asking 6rsh to relay. This helper is discovery only. * * @return array */ function sixrsh_get_connection(): array { $key = (string) get_option( 'sixrsh_api_key', '' ); $acct = (array) get_option( 'sixrsh_connected_account', array() ); $plan = ''; if ( class_exists( 'SixRsh_Entitlements' ) ) { $plan = (string) SixRsh_Entitlements::plan(); } if ( '' === $plan ) { $plan = (string) ( $acct['plan'] ?? '' ); } return array( 'connected' => ( '' !== $key ), 'plan' => $plan, 'api_base' => defined( 'SIXRSH_API_BASE' ) ? SIXRSH_API_BASE : 'https://www.6rsh.com/api/v1', 'version' => defined( 'SIXRSH_VERSION' ) ? SIXRSH_VERSION : '', ); } add_filter( 'sixrsh_get_connection', function ( $value ) { return sixrsh_get_connection(); }, 10, 1 );