stats = $stats; $this->settings = $settings; $this->countries = $countries; $this->view = $view; } /** * Register hooks. * * @return void */ public function boot(): void { add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) ); add_action( 'admin_post_' . self::REFRESH_ACTION, array( $this, 'refresh' ) ); } /** * Title for the browser tab and the page heading. * * @return string */ public function page_title(): string { return __( 'Site statistics', 'adpix-analytics' ); } /** * Label in the sidebar. * * @return string */ public function menu_title(): string { return __( 'Site statistics', 'adpix-analytics' ); } /** * Capability required to open it. * * @return string */ public function capability(): string { return self::CAPABILITY; } /** * Menu slug. * * @return string */ public function slug(): string { return self::PAGE; } /** * Record the hook suffix WordPress gave this screen. * * @param string $hook Hook suffix. * @return void */ public function set_hook( string $hook ): void { $this->hook = $hook; } /** * Throw away the cached reports and come back. * * @return void */ public function refresh(): void { if ( ! current_user_can( self::CAPABILITY ) ) { wp_die( esc_html__( 'You do not have permission to do that.', 'adpix-analytics' ), '', array( 'response' => 403 ) ); } check_admin_referer( self::REFRESH_ACTION ); $this->stats->flush(); $range = isset( $_REQUEST[ self::RANGE_ARG ] ) ? StatsService::range( sanitize_key( wp_unslash( $_REQUEST[ self::RANGE_ARG ] ) ) ) : StatsService::DEFAULT_RANGE; wp_safe_redirect( $this->url( $range ) ); exit; } /** * Load the assets, on this screen only. * * @param string $hook Current admin page hook. * @return void */ public function enqueue( $hook ): void { if ( '' === $this->hook || $this->hook !== $hook ) { return; } wp_enqueue_style( 'adpix-analytics-admin', ADPIX_ANALYTICS_URL . 'assets/css/admin.css', array( 'dashicons' ), ADPIX_ANALYTICS_VERSION ); wp_enqueue_script( 'adpix-analytics-stats', ADPIX_ANALYTICS_URL . 'assets/js/stats.js', array(), ADPIX_ANALYTICS_VERSION, true ); } /** * Render the screen. * * @return void */ public function render(): void { if ( ! current_user_can( self::CAPABILITY ) ) { wp_die( esc_html__( 'You do not have permission to view AdPix Analytics statistics.', 'adpix-analytics' ) ); } $settings = $this->settings->load(); $range = $this->requested_range(); $data = array( 'range' => $range, 'ranges' => $this->range_links( $range ), 'can_report' => $settings->can_report(), // Which of the three the setup card should tick off. A site that already sends measurements // has the property ID, and should be told it only needs the other two. 'checklist' => array( 'property_id' => '' !== $settings->str( 'property_id' ), 'api_token' => '' !== $settings->str( 'api_token' ), ), 'settings_url' => admin_url( 'admin.php?page=' ) . SettingsController::PAGE, 'refresh_action' => self::REFRESH_ACTION, 'refresh_url' => admin_url( 'admin-post.php' ), 'range_arg' => self::RANGE_ARG, 'countries' => $this->countries, 'logo_url' => ADPIX_ANALYTICS_URL . 'assets/img/logo.svg', ); // Only ask the API for anything once the screen knows it has somewhere to ask. Six failed // round trips would otherwise stand between an unconfigured site and the sentence explaining // what it is missing. if ( $settings->can_report() ) { $data += array( 'overview' => $this->stats->overview( $range ), 'timeseries' => $this->stats->timeseries( $range ), 'geo' => $this->stats->breakdown( 'geo', $range, array( 'sessions', 'totalUsers' ) ), 'channels' => $this->stats->breakdown( 'channels', $range, array( 'sessions', 'totalUsers' ) ), 'pages' => $this->stats->breakdown( 'top_pages', $range, array( 'pageviews', 'sessions' ) ), 'devices' => $this->stats->breakdown( 'devices', $range, array( 'sessions' ) ), ); // "Not found" means the token is valid but this property is outside it. Ask which properties // it does reach, so the screen can say whether the ID or the token is the thing to change. if ( ! $data['overview']->ok() && false !== strpos( $data['overview']->error(), 'NOT_FOUND' ) ) { $data['diagnosis'] = $this->stats->accessible_properties(); $data['property_id'] = $settings->str( 'property_id' ); } } $this->view->render( 'stats-page', $data ); } /** * The window the reader asked for. * * @return string */ private function requested_range(): string { // Read-only, changes nothing, and is whitelisted against a fixed list before it is used, so a // nonce here would only be ceremony — and would break the plain links the tabs are made of. // phpcs:ignore WordPress.Security.NonceVerification.Recommended $raw = isset( $_GET[ self::RANGE_ARG ] ) ? sanitize_key( wp_unslash( $_GET[ self::RANGE_ARG ] ) ) : ''; return StatsService::range( $raw ); } /** * The window switcher: every offered range with its label, link and current state. * * @param string $current The window in view. * @return array> */ private function range_links( string $current ): array { $links = array(); foreach ( StatsService::RANGES as $range ) { $links[] = array( 'key' => $range, 'label' => StatsService::range_label( $range ), 'url' => $this->url( $range ), 'current' => $range === $current, ); } return $links; } /** * This screen's address, for one window. * * @param string $range Range key. * @return string */ private function url( string $range ): string { return add_query_arg( array( 'page' => self::PAGE, self::RANGE_ARG => $range, ), admin_url( 'admin.php' ) ); } }