maybe_upgrade(); $this->capabilities()->register(); ( new OAuthReturn( fn(): OAuthFlow => $this->oauth() ) )->register(); ( new Routes( $this ) )->register(); $this->health()->register_site_health(); // The posts_column warm-up handler runs on its own wp-cron request, // so it is registered unconditionally rather than only in is_admin(). $posts_column = new Admin\PostsColumn( $this ); $posts_column->register_cron(); // So does the Clarity snapshot sync. The closure keeps boot() from // building ClaritySync (and its TokenStore) on every request. add_action( ClaritySync::CRON_HOOK, fn() => $this->clarity()->run_scheduled() ); // Saving a post (anywhere) forgets its cached Clarity page titles. add_action( 'post_updated', array( Insights::class, 'forget_post_titles' ), 10, 3 ); add_action( 'save_post', array( Insights::class, 'forget_post_titles' ), 10, 2 ); if ( is_admin() ) { ( new Admin\Menu( $this ) )->register(); $this->assets()->register(); $posts_column->register(); ( new Admin\EditorPanel( $this ) )->register(); ( new Admin\DashboardWidget( $this ) )->register(); ( new Admin\PrivacyPolicy( $this->settings() ) )->register(); } else { ( new TagRenderer( $this->settings() ) )->register(); } add_filter( 'wp_consent_api_registered_' . plugin_basename( TAGSMITH_FILE ), '__return_true' ); do_action( 'tagsmith_loaded', $this ); } /** * The typed settings store. */ public function settings(): Settings { if ( null === $this->settings ) { $this->settings = new Settings(); } return $this->settings; } /** * The report-viewing capability mapping. */ public function capabilities(): Capabilities { if ( null === $this->capabilities ) { $this->capabilities = new Capabilities( $this->settings() ); } return $this->capabilities; } /** * The encrypted-at-rest token store. */ public function tokens(): TokenStore { if ( null === $this->tokens ) { $this->tokens = new TokenStore(); } return $this->tokens; } /** * The Google connection state. */ public function connection(): Connection { if ( null === $this->connection ) { $this->connection = new Connection( $this->tokens() ); } return $this->connection; } /** * The HTTP client for Google and relay calls. */ public function http(): HttpClient { if ( null === $this->http ) { $this->http = new HttpClient(); } return $this->http; } /** * The Google OAuth flow (relay and BYO). */ public function oauth(): OAuthFlow { if ( null === $this->oauth ) { $this->oauth = new OAuthFlow( $this->settings(), $this->connection(), $this->http(), self::relay_url(), $this->tokens() ); } return $this->oauth; } /** * The Google Analytics Admin API client (accounts, properties, streams). */ public function admin_api(): AdminApi { if ( null === $this->admin_api ) { $this->admin_api = new AdminApi( $this->http(), $this->oauth() ); } return $this->admin_api; } /** * The Google Analytics Data API client (reports). */ public function data_api(): DataApi { if ( null === $this->data_api ) { $this->data_api = new DataApi( $this->http(), $this->oauth() ); } return $this->data_api; } /** * The Google Search Console API client. */ public function search_console(): SearchConsoleApi { if ( null === $this->search_console ) { $this->search_console = new SearchConsoleApi( $this->http(), $this->oauth() ); } return $this->search_console; } /** * The report definitions registry. */ public function registry(): ReportRegistry { if ( null === $this->registry ) { $this->registry = new ReportRegistry(); } return $this->registry; } /** * The report runner (cached Google reports). */ public function reports(): ReportRunner { if ( null === $this->reports ) { $this->reports = new ReportRunner( $this->settings(), $this->registry(), $this->data_api(), $this->search_console(), $this->connection() ); } return $this->reports; } /** * The diagnostics service (connection, tag, conflicts, environment, * consent), also registered with Site Health. Built with closures for * OAuthFlow/AdminApi/SearchConsoleApi so boot() never constructs * OAuthFlow eagerly (it needs TokenStore, which needs the auth salts). */ public function health(): HealthCheck { if ( null === $this->health ) { $this->health = new HealthCheck( $this->settings(), $this->connection(), fn(): OAuthFlow => $this->oauth(), fn(): AdminApi => $this->admin_api(), fn(): SearchConsoleApi => $this->search_console() ); } return $this->health; } /** * The admin asset enqueuer, also the single source of the * `window.tagsmithConfig` payload every Tagsmith JS entry * (dashboard, settings, editor, widget) reads. */ public function assets(): Assets { if ( null === $this->assets ) { $this->assets = new Assets( $this ); } return $this->assets; } /** * The local table of Clarity Data Export snapshots. */ public function clarity_store(): SnapshotStore { if ( null === $this->clarity_store ) { $this->clarity_store = new SnapshotStore(); } return $this->clarity_store; } /** * The Clarity Data Export sync: token (write-only), status, budget and * the twice-daily cron. */ public function clarity(): ClaritySync { if ( null === $this->clarity ) { $this->clarity = new ClaritySync( $this->tokens(), new ClarityApi(), $this->clarity_store(), $this->settings() ); } return $this->clarity; } /** * The URL of the Tagsmith relay service, used for the "relay" * connection mode. Overridable via the TAGSMITH_RELAY_URL constant, * then the tagsmith_relay_url filter. */ public static function relay_url(): string { return untrailingslashit( defined( 'TAGSMITH_RELAY_URL' ) ? TAGSMITH_RELAY_URL : apply_filters( 'tagsmith_relay_url', self::DEFAULT_RELAY_URL ) ); } /** * Whether the relay in use is the one run by the plugin's author. Copy * that says who runs the relay is only true then. */ public static function relay_is_default(): bool { return self::relay_url() === self::DEFAULT_RELAY_URL; } /** * The domain Google names on its "Google hasn't verified this app" * screen ("Go to (unsafe)"): the home page domain of the OAuth * app behind the relay. It is not the relay's own host, so it is kept * apart from relay_url(). The relay pins the same value * (UNVERIFIED_APP_DOMAIN in relay/src/confirm.ts). * * That domain is the author's only with the author's relay. With another * relay the filter starts from an empty string, and only a host name it * returns is used; anything else leaves no domain, and the Connect step * then says nothing about Google's warning. */ public static function oauth_app_domain(): string { /** * Filters the domain the Connect step names for Google's * unverified-app screen. With your own relay, return your OAuth app's * home domain here (a host name, without a scheme, a path or a * port). When nothing valid is returned, the note is not shown. * * @since 2.0.0 * * @param string $domain The OAuth app's home page domain: the author's with the default relay, otherwise an empty string. */ $domain = apply_filters( 'tagsmith_oauth_app_domain', self::relay_is_default() ? 'shivaatripathi.com' : '' ); if ( ! is_string( $domain ) ) { return ''; } $domain = strtolower( $domain ); return 1 === preg_match( '/^(?=.{1,253}$)([a-z0-9-]+\.)+[a-z]{2,}$/D', $domain ) ? $domain : ''; } }