|null, * error: Remote_API_Error|null, * } */ class Admin_Columns_Parsely_Stats { /** * Instance of Parsely class. * * @var Parsely */ private $parsely; /** * Instance of Parsely Analytics Posts API. * * @var Analytics_Posts_API */ private $analytics_api; /** * Internal Variable. * * @var WP_Screen|null */ private $current_screen; /** * Published times of visible posts. * * Analytics Endpoint don't support post_ids as param so to limit the API we will pass the * min/max publish time. * * @var string[] */ private $utc_published_times = array(); /** * Constructor. * * @param Parsely $parsely Instance of Parsely class. */ public function __construct( Parsely $parsely ) { $this->parsely = $parsely; } /** * Registers action and filter hook callbacks. * * @since 3.7.0 */ public function run(): void { if ( ! $this->should_add_hooks() ) { return; } add_action( 'current_screen', array( $this, 'set_current_screen' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_parsely_stats_styles' ) ); add_filter( 'manage_posts_columns', array( $this, 'add_parsely_stats_column_on_list_view' ) ); add_action( 'manage_posts_custom_column', array( $this, 'update_published_times_and_show_placeholder' ) ); add_filter( 'manage_pages_columns', array( $this, 'add_parsely_stats_column_on_list_view' ) ); add_action( 'manage_pages_custom_column', array( $this, 'update_published_times_and_show_placeholder' ) ); add_action( 'admin_footer', array( $this, 'enqueue_parsely_stats_script_with_data' ) ); } /** * Determines whether we should add hooks or not depending on plugin settings * and user capabilities. */ public function should_add_hooks(): bool { if ( ! $this->parsely->site_id_is_set() || ! $this->parsely->api_secret_is_set() ) { return false; } $this->analytics_api = new Analytics_Posts_API( $this->parsely ); // Don't add the column if the user is not allowed to make the API call. if ( ! $this->analytics_api->is_user_allowed_to_make_api_call() ) { return false; } return true; } /** * Sets current screen property. * * @since 3.7.0 */ public function set_current_screen(): void { $this->current_screen = get_current_screen(); } /** * Enqueues styles for Parse.ly Stats. * * @since 3.7.0 */ public function enqueue_parsely_stats_styles(): void { if ( ! $this->is_tracked_as_post_type() ) { return; } $admin_settings_asset = get_asset_info( 'build/admin-parsely-stats.asset.php' ); $built_assets_url = plugin_dir_url( PARSELY_FILE ) . 'build/'; wp_enqueue_style( 'admin-parsely-stats-styles', $built_assets_url . 'admin-parsely-stats.css', $admin_settings_asset['dependencies'], $admin_settings_asset['version'] ); } /** * Adds `Parse.ly Stats` column on admin columns. * * @since 3.7.0 * * @param array $columns Columns array which contain keys and labels. * * @return array */ public function add_parsely_stats_column_on_list_view( array $columns ): array { if ( $this->is_tracked_as_post_type() ) { $columns['parsely-stats'] = __( 'Parse.ly Stats (7d)', 'wp-parsely' ); } return $columns; } /** * Updates our published_times list with current post info and show placeholder. * * Note: We don't have `the_posts` hook for hierarchical post types like pages so we are following this approach: * * 1. Get post publish times and show a placeholder while displaying rows on Admin List. * 2. Make Parsely Analytics API call limited by publish dates inside `wp_footer` hook and pass the stats data to JS script. * 3. Show data on each Admin Row using JS. * * @since 3.7.0 * * @param string $column_name The name of the column to display. */ public function update_published_times_and_show_placeholder( string $column_name ): void { if ( 'parsely-stats' !== $column_name || ! $this->is_tracked_as_post_type() ) { return; } global $post; if ( 'publish' === $post->post_status && $this->parsely->api_secret_is_set() ) { array_push( $this->utc_published_times, $post->post_date_gmt ); } $stats_key = $this->get_unique_stats_key_of_current_post(); ?>
...
is_tracked_as_post_type() ) { return; } if ( $this->is_parsely_stats_column_hidden() ) { return; // Avoid calling the API if column is hidden. } $parsely_stats_response = $this->get_parsely_stats_response( $this->analytics_api ); if ( null === $parsely_stats_response ) { return; } $admin_settings_asset = get_asset_info( 'build/admin-parsely-stats.asset.php' ); $built_assets_url = plugin_dir_url( PARSELY_FILE ) . 'build/'; wp_enqueue_script( 'admin-parsely-stats-script', $built_assets_url . 'admin-parsely-stats.js', $admin_settings_asset['dependencies'], $admin_settings_asset['version'], true ); wp_add_inline_script( 'admin-parsely-stats-script', "window.wpParselyPostsStatsResponse = '" . wp_json_encode( $parsely_stats_response ) . "';", 'before' ); } /** * Return TRUE if Parse.ly Stats column is hidden. * * @return bool */ public function is_parsely_stats_column_hidden(): bool { if ( ! isset( $this->current_screen ) ) { return false; } return in_array( 'parsely-stats', get_hidden_columns( $this->current_screen ), true ); } /** * Calls Parse.ly Analytics API and get stats data. * * @since 3.7.0 * * @param Analytics_Posts_API $analytics_api Instance of Analytics_Posts_API. * * @return Parsely_Posts_Stats_Response|null */ public function get_parsely_stats_response( $analytics_api ) { if ( ! $this->is_tracked_as_post_type() ) { return null; } if ( ! $this->parsely->api_secret_is_set() ) { return array( 'data' => null, 'error' => array( 'code' => 403, 'message' => __( 'Forbidden.', 'wp-parsely' ), 'htmlMessage' => '

' . __( 'We are unable to retrieve data for Parse.ly Stats. Please contact support@parsely.com for help resolving this issue.', 'wp-parsely' ) . '

', ), ); } $date_params = $this->get_publish_date_params_for_analytics_api(); if ( is_null( $date_params ) ) { return null; } $response = $analytics_api->get_posts_analytics( array( 'period_start' => Analytics_Posts_API::ANALYTICS_API_DAYS_LIMIT . 'd', 'pub_date_start' => $date_params['pub_date_start'] ?? '', 'pub_date_end' => $date_params['pub_date_end'] ?? '', 'limit' => Analytics_Posts_API::MAX_RECORDS_LIMIT, 'sort' => 'avg_engaged', // Note: API sends different stats on different sort options. ) ); if ( is_wp_error( $response ) ) { return array( 'data' => null, 'error' => array( 'code' => (int) $response->get_error_code(), 'message' => $response->get_error_message(), 'htmlMessage' => ( '

' . esc_html__( 'Error while getting data for Parse.ly Stats.', 'wp-parsely' ) . '
' . esc_html__( 'Detail: ', 'wp-parsely' ) . esc_html( "({$response->get_error_code()}) {$response->get_error_message()}" ) . '

' ), ), ); } if ( null === $response ) { return array( 'data' => array(), 'error' => null, ); } /** * Variable. * * @var array */ $parsely_stats_map = array(); foreach ( $response as $post_analytics ) { $key = $this->get_unique_stats_key_from_analytics( $post_analytics ); if ( '' === $key || ! isset( $post_analytics['metrics'] ) ) { continue; } $metrics = $post_analytics['metrics']; $views = isset( $metrics['views'] ) ? $metrics['views'] : 0; $visitors = isset( $metrics['visitors'] ) ? $metrics['visitors'] : 0; $engaged_seconds = isset( $metrics['avg_engaged'] ) ? round( $metrics['avg_engaged'], 2 ) * 60 : 0; /** * Variable. * * @var Parsely_Post_Stats */ $stats = array( 'page_views' => get_formatted_number( $views ) . ' ' . _n( 'page view', 'page views', $views, 'wp-parsely' ), 'visitors' => get_formatted_number( $visitors ) . ' ' . _n( 'visitor', 'visitors', $visitors, 'wp-parsely' ), 'avg_time' => get_formatted_time( $engaged_seconds ) . ' ' . __( 'avg time', 'wp-parsely' ), ); $parsely_stats_map[ $key ] = $stats; } return array( 'data' => $parsely_stats_map, 'error' => null, ); } /** * Gets publish date params which we can used as params in analytics API. * * Uses published times list to get the min/max dates which we can use to limit the API. * * @since 3.7.0 * * @return Analytics_Post_API_Params|null */ private function get_publish_date_params_for_analytics_api() { $published_times = $this->utc_published_times; if ( count( $published_times ) === 0 ) { return null; } return array( 'pub_date_start' => ( new DateTime( min( $published_times ) ) )->format( DATE_UTC_FORMAT ), 'pub_date_end' => ( new DateTime( max( $published_times ) ) )->format( DATE_UTC_FORMAT ), ); } /** * Gets unique key which we can use for Parse.ly stats map. * * Needed because Parse.ly Analytics API doesn't have anything unique through which we can identify the post. * * @since 3.7.0 * * @param Analytics_Post $analytics_post Post analytics obj returned from Parse.ly API. * * @return string */ private function get_unique_stats_key_from_analytics( $analytics_post ): string { if ( ! isset( $analytics_post['url'] ) ) { return ''; } return (string) wp_parse_url( $analytics_post['url'], PHP_URL_PATH ); } /** * Gets unique key from currently set post which we can use to get data from Parse.ly stats map. * * @since 3.7.0 * * @return string */ private function get_unique_stats_key_of_current_post(): string { return (string) wp_parse_url( (string) get_permalink(), PHP_URL_PATH ); } /** * Returns TRUE if the current screen is the list screen and we are tracking it as `Post` in plugin settings. * * @since 3.7.0 * * @return bool */ private function is_tracked_as_post_type(): bool { if ( is_null( $this->current_screen ) ) { return false; } // Analytics API doesn't include Non-Post data, so we can only show stats on tracked post types. $track_post_types = $this->parsely->get_options()['track_post_types']; foreach ( $track_post_types as $track_post_type ) { if ( 'edit' === $this->current_screen->base && $track_post_type === $this->current_screen->post_type ) { return true; } } return false; } }