' . esc_html__( 'This site measures its own traffic with Saddle Analytics. The data stays in this site’s database and is not shared with anyone.', 'saddle-analytics' ) . '

' . '

' . esc_html__( 'We record the pages you view, the site that referred you, any campaign tags in the link, the links to other sites you click, your country (when our host provides it), and your browser, operating system and device type. We do not store your IP address.', 'saddle-analytics' ) . '

' . '

' . ( $cookie ? esc_html__( 'We set two first-party cookies: _saddle_analytics_v holds a random id for up to one year so a returning visit is recognised, and _saddle_analytics_s holds a random session id for 30 minutes.', 'saddle-analytics' ) : esc_html__( 'We do not use cookies for this. A visit is recognised for one day by a hash of your IP address and browser, made with a secret that changes daily and is then deleted, so it cannot be traced back to you.', 'saddle-analytics' ) ) . '

' . '

' . esc_html__( 'If your browser sends Do Not Track or Global Privacy Control, we do not record your visit at all.', 'saddle-analytics' ) . '

' . '

' . sprintf( /* translators: %d: number of days page views are kept. */ esc_html__( 'Page views are deleted after %d days. A short summary of each visitor (first and last source, landing page, country, browser, operating system, device and totals) is kept until the site owner deletes the analytics data.', 'saddle-analytics' ), (int) Saddle_Analytics_Cron::retention_days() ) . '

'; wp_add_privacy_policy_content( 'Saddle Analytics', wp_kses_post( $text ) ); } /** The at-a-glance widget on wp-admin's Dashboard: plain WordPress markup, filled by build/widget.js. */ public function dashboard_widget(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } wp_add_dashboard_widget( 'saddle_analytics_dashboard', __( 'Saddle Analytics', 'saddle-analytics' ), array( $this, 'render_widget' ) ); } public function render_widget(): void { echo '
'; } /** With Saddle: nothing, Core adds Saddle → Analytics. Without it: the Install Saddle page. */ public function menu(): void { if ( self::inside_saddle() ) { return; } add_menu_page( __( 'Saddle Analytics', 'saddle-analytics' ), __( 'Saddle Analytics', 'saddle-analytics' ), 'manage_options', self::SLUG, array( $this, 'render_install_saddle' ), $this->menu_icon(), 58 ); } /** * Plain PHP, no bundle: says what still works and offers the one action that * opens the screens. Only reached without Saddle_Modules, so a defined * SADDLE_VERSION means Saddle is active but too old to host the page. */ public function render_install_saddle(): void { $installed = file_exists( WP_PLUGIN_DIR . '/saddle/saddle.php' ); // Saddle runs but has no page registry: a release before 1.5. Probe, never compare versions. $outdated = defined( 'SADDLE_VERSION' ); if ( $outdated && current_user_can( 'update_plugins' ) ) { $url = self_admin_url( 'update-core.php' ); $label = __( 'Update Saddle', 'saddle-analytics' ); } elseif ( $outdated ) { $url = self_admin_url( 'plugins.php' ); $label = __( 'See plugins', 'saddle-analytics' ); } elseif ( $installed && current_user_can( 'activate_plugins' ) ) { $url = wp_nonce_url( self_admin_url( 'plugins.php?action=activate&plugin=saddle%2Fsaddle.php' ), 'activate-plugin_saddle/saddle.php' ); $label = __( 'Activate Saddle', 'saddle-analytics' ); } elseif ( ! $installed && current_user_can( 'install_plugins' ) ) { $url = wp_nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=saddle' ), 'install-plugin_saddle' ); $label = __( 'Install Saddle', 'saddle-analytics' ); } else { $url = self_admin_url( 'plugin-install.php?tab=search&type=term&s=saddle' ); $label = __( 'Find Saddle', 'saddle-analytics' ); } echo '

' . esc_html__( 'Saddle Analytics', 'saddle-analytics' ) . '

'; echo '

' . ( $outdated ? esc_html__( 'Visits are still counted. The reports need Saddle 1.5 or newer.', 'saddle-analytics' ) : esc_html__( 'Visits are still counted. The reports open inside Saddle.', 'saddle-analytics' ) ) . '

'; echo '

' . esc_html( $label ) . '

'; } /** * The brand mark as a menu glyph: the logo circle with three ascending * bars knocked out. Black fill so wp-admin recolors it per admin color scheme. */ private function menu_icon(): string { $svg = '' . '' . ''; return 'data:image/svg+xml;base64,' . base64_encode( $svg ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- standard WP menu icon data URI. } /** What both bundles read: the REST base, a nonce, and a few URLs. */ private function config(): array { return array( 'restUrl' => esc_url_raw( rest_url( 'saddle-analytics/v1/' ) ), // Base for the ?rest_route= fallback when a host WAF blocks a // pretty REST path (20i StackProtect blocks */settings). 'homeUrl' => esc_url_raw( trailingslashit( home_url() ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), // Country flags are static files, not bundled components (Phase 20). 'flagsUrl' => esc_url_raw( SADDLE_ANALYTICS_URL . 'assets/flags/' ), 'openUrl' => esc_url_raw( self::page_url() ), ); } /** * Register the screens bundle as the handle the descriptor names. Core * enqueues it on its own page, after its own bundle, because the handle * depends on `saddle-admin`. Nothing prints anywhere else. */ public function register_screens(): void { $asset_file = SADDLE_ANALYTICS_DIR . 'build/index.asset.php'; if ( ! self::inside_saddle() || ! file_exists( $asset_file ) || ! current_user_can( 'manage_options' ) ) { return; } $asset = require $asset_file; wp_register_script( Saddle_Analytics_Module::SCRIPT, SADDLE_ANALYTICS_URL . 'build/index.js', array_merge( $asset['dependencies'], array( 'saddle-admin' ) ), $asset['version'], true ); wp_set_script_translations( Saddle_Analytics_Module::SCRIPT, 'saddle-analytics' ); // Layout rules only: Core's stylesheets own every colour and size. if ( file_exists( SADDLE_ANALYTICS_DIR . 'build/index.css' ) ) { wp_register_style( Saddle_Analytics_Module::SCRIPT, SADDLE_ANALYTICS_URL . 'build/index.css', array( 'saddle-admin' ), $asset['version'] ); wp_style_add_data( Saddle_Analytics_Module::SCRIPT, 'rtl', 'replace' ); } wp_add_inline_script( Saddle_Analytics_Module::SCRIPT, 'window.saddleAnalyticsAdmin=' . wp_json_encode( $this->config() ) . ';', 'before' ); } /** Core fires this on its pages after the module's script was queued; the layout sheet follows it. */ public function enqueue_layout(): void { if ( wp_script_is( Saddle_Analytics_Module::SCRIPT, 'enqueued' ) && wp_style_is( Saddle_Analytics_Module::SCRIPT, 'registered' ) ) { wp_enqueue_style( Saddle_Analytics_Module::SCRIPT ); } } /** The Dashboard-widget bundle (build/widget.*), enqueued only on index.php. */ public function widget_assets( string $hook_suffix ): void { $asset_file = SADDLE_ANALYTICS_DIR . 'build/widget.asset.php'; if ( 'index.php' !== $hook_suffix || ! current_user_can( 'manage_options' ) || ! file_exists( $asset_file ) ) { return; } // Core's chart ink for the bars, when Core can hand it over; the widget CSS has a fallback. if ( method_exists( 'Saddle_Modules', 'enqueue_palette' ) ) { Saddle_Modules::enqueue_palette(); } $asset = require $asset_file; wp_enqueue_script( 'saddle-analytics-widget', SADDLE_ANALYTICS_URL . 'build/widget.js', $asset['dependencies'], $asset['version'], true ); wp_set_script_translations( 'saddle-analytics-widget', 'saddle-analytics' ); if ( file_exists( SADDLE_ANALYTICS_DIR . 'build/widget.css' ) ) { wp_enqueue_style( 'saddle-analytics-widget', SADDLE_ANALYTICS_URL . 'build/widget.css', array(), $asset['version'] ); wp_style_add_data( 'saddle-analytics-widget', 'rtl', 'replace' ); } wp_add_inline_script( 'saddle-analytics-widget', 'window.saddleAnalyticsAdmin=' . wp_json_encode( $this->config() ) . ';', 'before' ); } }