'equashop', 'slot' => 'pro', 'locale' => get_user_locale(),
* ) );
*
* The socle talks to the EquaMarket content server over HTTPS only. It owns the
* cache (WP transients on the host site), the timeouts, the degradation and a
* same-origin authenticated proxy so the browser never talks cross-origin. It
* requires NOTHING of EquaMarket on the host site.
*
* Host integration points (all filters, sensible defaults):
* equasuite_cc_content_base (string) — server base URL (default production marketing.equadev.fr).
* equasuite_cc_optin (bool, $product_id) — has the host authorised server calls?
* equasuite_cc_capability (string) — capability gating the local /consume proxy.
* equasuite_cc_fallback_text (string, $product_id) — the host's locally-translated
* (gettext) showcase fallback.
*
* Collision-safe: single shared class, all state scoped by product_id, namespaced
* handles/transients/routes (equasuite_cc*). @see equasuite-content-client.php (loader).
*
* @package EquaSuite\ContentClient
*/
defined( 'ABSPATH' ) || exit;
class EquaSuite_Content_Client {
/** REST namespace of the local (same-origin) proxy on the host site. */
const REST_NS = 'equasuite-cc/v1';
/** Cache durations (seconds) — the frozen EquaSuite content-cache policy. */
const BANNER_CONTENT_TTL = 28800; // 8h.
const BANNER_EARLY_REFRESH = 14400; // 4h (TTL/2) — banner refetches from mid-life.
const BANNER_EMPTY_TTL = 3600; // 1h.
const BANNER_TIMEOUT = 2.5; // seconds, off the render path.
const PAGE_CONTENT_TTL = 3600; // 1h.
const PAGE_EMPTY_TTL = 3600; // 1h.
const PAGE_TIMEOUT = 10; // seconds, on the render path (clock-covered).
/** @var string Absolute dir of the booted copy. */
private static $dir = '';
/** @var string Public URL of the booted copy (for assets). */
private static $url = '';
/** @var string Booted copy version. */
private static $version = '0.0.0';
/** @var bool */
private static $booted = false;
/** @var bool Localised config emitted once. */
private static $localized = false;
/**
* Boots the socle (called once, for the highest-versioned embedded copy).
*
* @param string $dir Absolute directory of the winning copy.
* @param string $version Its version.
* @return void
*/
public static function boot( $dir, $version ) {
if ( self::$booted ) {
return;
}
self::$booted = true;
self::$dir = wp_normalize_path( $dir );
self::$version = (string) $version;
self::$url = self::dir_to_url( self::$dir );
add_action( 'rest_api_init', array( __CLASS__, 'register_routes' ) );
}
/* ====================================================================== */
/* Host configuration (filterable, self-contained defaults). */
/* ====================================================================== */
/**
* Content server base URL (production default; filterable for testing).
*
* @return string
*/
public static function remote_base() {
return untrailingslashit( (string) apply_filters( 'equasuite_cc_content_base', 'https://marketing.equadev.fr/wp-json/equamarket/v1' ) );
}
/**
* Has the host authorised contacting the content server for this product?
*
* @param string $product_id Host product id.
* @return bool
*/
public static function is_opted_in( $product_id ) {
return (bool) apply_filters( 'equasuite_cc_optin', false, $product_id );
}
/**
* Capability gating the local /consume proxy.
*
* @return string
*/
public static function capability() {
return (string) apply_filters( 'equasuite_cc_capability', 'manage_options' );
}
/**
* The host's locally-translated dignified showcase fallback text.
*
* @param string $product_id Host product id.
* @return string
*/
public static function fallback_text( $product_id ) {
return (string) apply_filters(
'equasuite_cc_fallback_text',
'This EquaSuite presentation is currently unavailable. Please try again later.',
$product_id
);
}
/* ====================================================================== */
/* Assets — enqueue triggerable by the host on ITS hooks. */
/* ====================================================================== */
/**
* Registers + enqueues the socle CSS/JS and localises the shared config.
* Idempotent; call from the host's own admin_enqueue_scripts for its screen.
*
* @return void
*/
public static function enqueue() {
if ( ! self::$booted ) {
return;
}
wp_enqueue_style( 'equasuite-cc-client', self::$url . '/assets/equasuite-content-client.css', array(), self::$version );
wp_enqueue_script( 'equasuite-cc-client', self::$url . '/assets/equasuite-content-client.js', array(), self::$version, true );
if ( ! self::$localized ) {
self::$localized = true;
wp_localize_script(
'equasuite-cc-client',
'EQUASUITE_CC',
array(
'rest' => esc_url_raw( rest_url( self::REST_NS . '/consume' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
// Client abort budget = server remote-fetch timeout + headroom for
// the local proxy round-trip, so a cold-cache fetch that succeeds
// server-side is not aborted before its response reaches the browser.
'bannerMs' => (int) round( self::BANNER_TIMEOUT * 1000 ) + 2000,
'pageMs' => (int) ( self::PAGE_TIMEOUT * 1000 ) + 3000,
)
);
}
}
/* ====================================================================== */
/* Public mount API. */
/* ====================================================================== */
/**
* Banner container with an inline first paint from cache (no network). The
* host echoes the returned string at its chosen insertion point.
*
* @param array $args product_id (required), slot (required), locale (optional).
* @return string
*/
public static function render_banner( array $args ) {
self::enqueue(); // defensive; best practice is to enqueue on the host hook.
$product = self::norm_product( isset( $args['product_id'] ) ? $args['product_id'] : '' );
$slot = sanitize_key( isset( $args['slot'] ) ? $args['slot'] : '' );
$locale = self::norm_locale( isset( $args['locale'] ) ? $args['locale'] : get_user_locale() );
$optin = self::is_opted_in( $product );
// Only paint cached server content while opted in — if the host revoked the
// opt-in, the banner shows nothing (consistent with the showcase, which
// shows its local fallback). No stale server content survives an opt-out.
$seed = $optin ? self::servable_from_cache( 'banner', $product, $slot, $locale ) : array( 'status' => 'empty' );
$inline = ( 'content' === $seed['status'] )
? '
' . wp_kses_post( $seed['html'] ) . '
'
: '';
return sprintf(
'%s
',
'' === $inline ? '' : ' is-shown',
esc_attr( $product ),
esc_attr( $slot ),
esc_attr( $locale ),
$optin ? '1' : '0',
$inline
);
}
/**
* Showcase container. JS fetches on the render path (clock) then shows content
* or the host's dignified fallback. `