<?php
/**
 * The Analytics Module
 *
 * @since      1.0.49
 * @package    RankMath
 * @subpackage RankMath\modules
 * @author     Rank Math <support@rankmath.com>
 */

namespace RankMath\Analytics;

use RankMath\KB;
use RankMath\Helper;
use RankMath\Helpers\Arr;
use RankMath\Helpers\Str;
use RankMath\Helpers\Param;
use RankMath\Google\Api;
use RankMath\Module\Base;
use RankMath\Admin\Page;
use RankMath\Google\Console;
use RankMath\Google\Authentication;
use RankMath\Analytics\Workflow\Jobs;
use RankMath\Analytics\Workflow\OAuth;
use RankMath\Analytics\Workflow\Workflow;

defined( 'ABSPATH' ) || exit;

/**
 * Analytics class.
 */
class Analytics extends Base {

	/**
	 * Module ID.
	 *
	 * @var string
	 */
	public $id = '';

	/**
	 * Module directory.
	 *
	 * @var string
	 */
	public $directory = '';

	/**
	 * Module help.
	 *
	 * @var array
	 */
	public $help = [];

	/**
	 * Module page.
	 *
	 * @var object
	 */
	public $page;

	/**
	 * The Constructor
	 */
	public function __construct() {
		if ( Helper::is_heartbeat() || ! Helper::has_cap( 'analytics' ) ) {
			return;
		}

		$directory = __DIR__;
		$this->config(
			[
				'id'        => 'analytics',
				'directory' => $directory,
				'help'      => [
					'title' => esc_html__( 'Analytics', 'seo-by-rank-math' ),
					'view'  => $directory . '/views/help.php',
				],
			]
		);
		parent::__construct();

		new AJAX();
		Api::get();
		Watcher::get();
		Stats::get();
		Jobs::get();
		Workflow::get();

		$this->action( 'admin_notices', 'render_notice' );
		$this->action( 'rank_math/admin/enqueue_scripts', 'enqueue' );
		$this->action( 'admin_enqueue_scripts', 'options_panel_messages' );
		$this->action( 'wp_helpers_notification_dismissed', 'analytic_first_fetch_dismiss' );

		if ( is_admin() ) {
			$this->filter( 'rank_math/settings/general', 'add_settings' );
			$this->action( 'admin_init', 'refresh_token_missing', 25 );
			$this->action( 'admin_init', 'cancel_fetch', 5 );
			$this->action( 'wp_helpers_notification_dismissed', 'notice_dismissible' );

			new OAuth();
		}
	}

	/**
	 * Cancel Fetching of Google.
	 */
	public function cancel_fetch() {
		$cancel = Param::get( 'cancel-fetch', false );
		if (
			empty( $cancel ) ||
			! Param::get( '_wpnonce' ) ||
			! wp_verify_nonce( Param::get( '_wpnonce' ), 'rank_math_cancel_fetch' ) ||
			! Helper::has_cap( 'analytics' )
		) {
			return;
		}

		Workflow::kill_workflows();
	}

	/**
	 * If refresh token missing add notice.
	 */
	public function refresh_token_missing() {
		// Bail if the user is not authenticated at all yet.
		if ( ! Helper::is_site_connected() || ! Authentication::is_authorized() ) {
			return;
		}

		$this->maybe_add_cron_notice();

		$tokens = Authentication::tokens();
		if ( ! empty( $tokens['refresh_token'] ) ) {
			Helper::remove_notification( 'reconnect' );
			return;
		}

		// Show admin notification.
		Helper::add_notification(
			sprintf(
				/* translators: Auth URL */
				'<i class="rm-icon rm-icon-rank-math"></i>' . __( 'It seems like the connection with your Google account & Rank Math needs to be made again. <a href="%s" class="rank-math-reconnect-google">Please click here.</a>', 'seo-by-rank-math' ),
				esc_url( Authentication::get_auth_url() )
			),
			[
				'type'    => 'error',
				'classes' => 'rank-math-error rank-math-notice',
				'id'      => 'reconnect',
			]
		);
	}

	/**
	 * Hide fetch notice.
	 *
	 * @param  string $notification_id Notification id.
	 */
	public function analytic_first_fetch_dismiss( $notification_id ) {
		if ( 'rank_math_analytics_first_fetch' !== $notification_id ) {
			return;
		}

		update_option( 'rank_math_analytics_first_fetch', 'hidden' );
	}

	/**
	 * Admin init.
	 */
	public function render_notice() {
		$this->remove_action( 'admin_notices', 'render_notice' );
		if ( 'fetching' === get_option( 'rank_math_analytics_first_fetch' ) ) {
			$actions = as_get_scheduled_actions(
				[
					'order'  => 'DESC',
					'hook'   => 'rank_math/analytics/clear_cache',
					'status' => \ActionScheduler_Store::STATUS_PENDING,
				]
			);

			if ( empty( $actions ) ) {
				update_option( 'rank_math_analytics_first_fetch', 'hidden' );
				return;
			}

			$action         = current( $actions );
			$schedule       = $action->get_schedule();
			$next_timestamp = $schedule->get_date()->getTimestamp();

			// Calculate extra time needed for the inspections.
			$objects_count   = DB::objects()->selectCount( 'id' )->getVar();
			$daily_api_limit = \RankMath\Analytics\Workflow\Inspections::API_LIMIT;
			$time_gap        = \RankMath\Analytics\Workflow\Inspections::REQUEST_GAP_SECONDS;
			$extra_time      = $objects_count * $time_gap;
			if ( $objects_count > $daily_api_limit ) {
				$extra_time += DAY_IN_SECONDS * floor( $objects_count / $daily_api_limit );
			}

			// phpcs:disable
			$notification   = new \RankMath\Admin\Notifications\Notification(
				/* translators: delete counter */
				sprintf(
					'<svg style="vertical-align: middle; margin-right: 5px" viewBox="0 0 462.03 462.03" xmlns="http://www.w3.org/2000/svg" width="20"><g><path d="m462 234.84-76.17 3.43 13.43 21-127 81.18-126-52.93-146.26 60.97 10.14 24.34 136.1-56.71 128.57 54 138.69-88.61 13.43 21z"></path><path d="m54.1 312.78 92.18-38.41 4.49 1.89v-54.58h-96.67zm210.9-223.57v235.05l7.26 3 89.43-57.05v-181zm-105.44 190.79 96.67 40.62v-165.19h-96.67z"></path></g></svg>' .
					esc_html__( 'Rank Math is importing latest data from connected Google Services, %1$s remaining.', 'seo-by-rank-math' ) .
					'&nbsp;<a href="%2$s">' . esc_html__( 'Cancel Fetch', 'seo-by-rank-math' ) . '</a>',
					$this->human_interval( $next_timestamp - gmdate( 'U' ) + $extra_time ),
					esc_url( wp_nonce_url( add_query_arg( 'cancel-fetch', 1 ), 'rank_math_cancel_fetch' ) )
				),
				[
					'type'    => 'info',
					'id'      => 'rank_math_analytics_first_fetch',
					'classes' => 'rank-math-notice',
				]
			);

			echo $notification;
		}
	}

	/**
	 * Store a value in the options table when CRON notice is dismissed to prevent the site from showing it again.
	 *
	 * @param string $notification_id Notification id.
	 */
	public function notice_dismissible( $notification_id ) {
		if ( 'analytics_cron_notice' === $notification_id ) {
			update_option( 'rank_math_analytics_cron_notice_dismissed', true, false );
		}
	}

	/**
	 * Add Notice on Analytics page when CRON is not working on the site.
	 */
	private function maybe_add_cron_notice() {
		if ( ! $this->page->is_current_page() || get_option( 'rank_math_analytics_cron_notice_dismissed' ) ) {
			return;
		}

		if ( Helper::is_cron_enabled() ) {
			Helper::remove_notification( 'analytics_cron_notice' );
			return;
		}

		$message = sprintf(
			/* translators: constant value */
			esc_html__( 'Loopback requests to %s are blocked. This may prevent scheduled tasks from running. Please check your server configuration.', 'seo-by-rank-math' ),
			'<code>wp-cron.php</code>'
		);
		if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
			$message = sprintf(
				/* translators: constant value */
				esc_html__( 'WordPress\'s internal cron system is disabled via %s. Please ensure a real cron job is set up, otherwise scheduled features like Analytics may not work correctly.', 'seo-by-rank-math' ),
				'<code>DISABLE_WP_CRON</code>'
			);
		}

		// Show admin notification.
		Helper::add_notification(
			$message,
			[
				'type'   => 'warning',
				'id'     => 'analytics_cron_notice',
				'screen' => 'rank-math_page_rank-math-analytics',
			]
		);
	}

	/**
	 * Convert an interval of seconds into a two part human friendly string.
	 *
	 * The WordPress human_time_diff() function only calculates the time difference to one degree, meaning
	 * even if an action is 1 day and 11 hours away, it will display "1 day". This function goes one step
	 * further to display two degrees of accuracy.
	 *
	 * Inspired by the Crontrol::interval() function by Edward Dale: https://wordpress.org/plugins/wp-crontrol/
	 *
	 * @param int $interval A interval in seconds.
	 * @param int $periods_to_include Depth of time periods to include, e.g. for an interval of 70, and $periods_to_include of 2, both minutes and seconds would be included. With a value of 1, only minutes would be included.
	 * @return string A human friendly string representation of the interval.
	 */
	private function human_interval( $interval, $periods_to_include = 2 ) {
		$time_periods = [
			[
				'seconds' => YEAR_IN_SECONDS,
				/* translators: %s: amount of time */
				'names'   => _n_noop( '%s year', '%s years', 'seo-by-rank-math' ),
			],
			[
				'seconds' => MONTH_IN_SECONDS,
				/* translators: %s: amount of time */
				'names'   => _n_noop( '%s month', '%s months', 'seo-by-rank-math' ),
			],
			[
				'seconds' => WEEK_IN_SECONDS,
				/* translators: %s: amount of time */
				'names'   => _n_noop( '%s week', '%s weeks', 'seo-by-rank-math' ),
			],
			[
				'seconds' => DAY_IN_SECONDS,
				/* translators: %s: amount of time */
				'names'   => _n_noop( '%s day', '%s days', 'seo-by-rank-math' ),
			],
			[
				'seconds' => HOUR_IN_SECONDS,
				/* translators: %s: amount of time */
				'names'   => _n_noop( '%s hour', '%s hours', 'seo-by-rank-math' ),
			],
			[
				'seconds' => MINUTE_IN_SECONDS,
				/* translators: %s: amount of time */
				'names'   => _n_noop( '%s minute', '%s minutes', 'seo-by-rank-math' ),
			],
			[
				'seconds' => 1,
				/* translators: %s: amount of time */
				'names'   => _n_noop( '%s second', '%s seconds', 'seo-by-rank-math' ),
			],
		];

		if ( $interval <= 0 ) {
			return __( 'Now!', 'seo-by-rank-math' );
		}

		$output = '';

		$time_period_count = count( $time_periods );
		for ( $time_period_index = 0, $periods_included = 0, $seconds_remaining = $interval; $time_period_index < $time_period_count && $seconds_remaining > 0 && $periods_included < $periods_to_include; $time_period_index++ ) {

			$periods_in_interval = floor( $seconds_remaining / $time_periods[ $time_period_index ]['seconds'] );

			if ( $periods_in_interval > 0 ) {
				if ( ! empty( $output ) ) {
					$output .= ' ';
				}
				$output            .= sprintf( translate_nooped_plural( $time_periods[ $time_period_index ]['names'], $periods_in_interval, 'seo-by-rank-math' ), $periods_in_interval );
				$seconds_remaining -= $periods_in_interval * $time_periods[ $time_period_index ]['seconds'];
				++$periods_included;
			}
		}

		return $output;
	}

	/**
	 * Add l18n for the Settings.
	 *
	 * @return void
	 */
	public function options_panel_messages() {
		$screen = get_current_screen();

		if ( 'rank-math_page_rank-math-options-general' !== $screen->id ) {
			return;
		}

		Helper::add_json( 'confirmAction', esc_html__( 'Are you sure you want to do this?', 'seo-by-rank-math' ) );
		Helper::add_json( 'confirmClearImportedData', esc_html__( 'You are about to delete all the previously imported data.', 'seo-by-rank-math' ) );
		Helper::add_json( 'confirmClear90DaysCache', esc_html__( 'You are about to delete your 90 days cache.', 'seo-by-rank-math' ) );
		Helper::add_json( 'confirmDisconnect', esc_html__( 'Are you sure you want to disconnect Google services from your site?', 'seo-by-rank-math' ) );
		Helper::add_json( 'feedbackCacheDeleted', esc_html__( 'Cache deleted.', 'seo-by-rank-math' ) );
	}

	/**
	 * Enqueue scripts for the metabox.
	 */
	public function enqueue() {
		$screen = get_current_screen();

		if ( 'rank-math_page_rank-math-analytics' !== $screen->id ) {
			return;
		}

		$uri = untrailingslashit( plugin_dir_url( __FILE__ ) );

		wp_enqueue_style(
			'rank-math-analytics',
			$uri . '/assets/css/stats.css',
			[],
			rank_math()->version
		);

		wp_register_script(
			'rank-math-analytics',
			$uri . '/assets/js/stats.js',
			[
				'lodash',
				'wp-components',
				'wp-element',
				'wp-i18n',
				'wp-date',
				'wp-api-fetch',
				'wp-html-entities',
				'rank-math-components',
			],
			rank_math()->version,
			true
		);

		wp_set_script_translations( 'rank-math-analytics', 'seo-by-rank-math', plugin_dir_path( __FILE__ ) . 'languages/' );

		$this->action( 'admin_footer', 'dequeue_cmb2' );

		$preference = apply_filters(
			'rank_math/analytics/user_preference',
			[
				'topPosts'        => [
					'seo_score'       => false,
					'schemas_in_use'  => false,
					'impressions'     => true,
					'pageviews'       => true,
					'clicks'          => false,
					'position'        => true,
					'positionHistory' => true,
				],
				'siteAnalytics'   => [
					'seo_score'       => true,
					'schemas_in_use'  => true,
					'impressions'     => false,
					'pageviews'       => true,
					'links'           => true,
					'clicks'          => false,
					'position'        => false,
					'positionHistory' => false,
				],
				'performance'     => [
					'seo_score'       => true,
					'schemas_in_use'  => true,
					'impressions'     => true,
					'pageviews'       => true,
					'ctr'             => false,
					'clicks'          => true,
					'position'        => true,
					'positionHistory' => true,
				],
				'keywords'        => [
					'impressions'     => true,
					'ctr'             => false,
					'clicks'          => true,
					'position'        => true,
					'positionHistory' => true,
				],
				'topKeywords'     => [
					'impressions'     => true,
					'ctr'             => true,
					'clicks'          => true,
					'position'        => true,
					'positionHistory' => true,
				],
				'trackKeywords'   => [
					'impressions'     => true,
					'ctr'             => false,
					'clicks'          => true,
					'position'        => true,
					'positionHistory' => true,
				],
				'rankingKeywords' => [
					'impressions'     => true,
					'ctr'             => false,
					'clicks'          => true,
					'position'        => true,
					'positionHistory' => true,
				],
				'indexing'        => [
					'index_verdict'      => true,
					'indexing_state'     => true,
					'rich_results_items' => true,
					'page_fetch_state'   => false,
				],
			]
		);

		$user_id = get_current_user_id();
		if ( metadata_exists( 'user', $user_id, 'rank_math_analytics_table_columns' ) ) {
			$preference = wp_parse_args(
				get_user_meta( $user_id, 'rank_math_analytics_table_columns', true ),
				$preference
			);
		}

		Helper::add_json( 'userColumnPreference', $preference );

		// Last Updated.
		$updated = get_option( 'rank_math_analytics_last_updated', false );
		$updated = $updated ? date_i18n( get_option( 'date_format' ), $updated ) : '';
		Helper::add_json( 'lastUpdated', $updated );

		Helper::add_json( 'singleImage', rank_math()->plugin_url() . 'includes/modules/analytics/assets/img/single-post-report.jpg' );

		// Index Status tab.
		$enable_index_status = Helper::can_add_index_status();
		Helper::add_json( 'enableIndexStatus', $enable_index_status );
		Helper::add_json( 'viewedIndexStatus', get_option( 'rank_math_viewed_index_status', false ) );

		if ( $enable_index_status ) {
			update_option( 'rank_math_viewed_index_status', true );
		}

		Helper::add_json( 'isRtl', is_rtl() );
	}

	/**
	 * Dequeue cmb2.
	 */
	public function dequeue_cmb2() {
		wp_dequeue_script( 'cmb2-scripts' );
	}

	/**
	 * Register admin page.
	 */
	public function register_admin_page() {
		$dot_color = '#ed5e5e';
		if ( Console::is_console_connected() ) {
			$dot_color = '#11ac84';
		}

		$this->page = new Page(
			'rank-math-analytics',
			esc_html__( 'Analytics', 'seo-by-rank-math' ) . '<span class="rm-menu-new update-plugins" style="background: ' . $dot_color . '; margin-left: 5px;min-width: 10px;height: 10px;margin-top: 5px;"><span class="plugin-count"></span></span>',
			[
				'position'   => 5,
				'parent'     => 'rank-math',
				'capability' => 'rank_math_analytics',
				'render'     => $this->directory . '/views/dashboard.php',
				'classes'    => [ 'rank-math-page' ],
				'assets'     => [
					'styles'  => [
						'rank-math-common'    => '',
						'rank-math-analytics' => '',
					],
					'scripts' => [
						'rank-math-analytics' => '',
					],
				],
			]
		);
	}

	/**
	 * Add module settings into general optional panel.
	 *
	 * @param array $tabs Array of option panel tabs.
	 *
	 * @return array
	 */
	public function add_settings( $tabs ) {
		$db_info = \RankMath\Analytics\DB::info();
		$next_fetch = '';
		$actions = as_get_scheduled_actions(
			[
				'order'  => 'DESC',
				'hook'   => 'rank_math/analytics/data_fetch',
				'status' => \ActionScheduler_Store::STATUS_PENDING,
			]
		);
		if ( Authentication::is_authorized() && ! empty( $actions ) ) {
			$action    = current( $actions );
			$schedule  = $action->get_schedule();
			$next_date = $schedule->get_date();
			if ( $next_date ) {
				$next_fetch = sprintf(
					__( 'Next update on %s (in %s)', 'seo-by-rank-math' ),
					date_i18n( 'd M, Y H:m:i', $next_date->getTimestamp() ),
					human_time_diff( $next_date->getTimestamp() )
				);
			}
		}

		Arr::insert(
			$tabs,
			[
				'analytics' => [
					'icon'  => 'rm-icon rm-icon-search-console',
					'title' => esc_html__( 'Analytics', 'seo-by-rank-math' ),
					/* translators: Link to kb article */
					'desc'  => sprintf( esc_html__( 'See your Google Search Console, Analytics and AdSense data without leaving your WP dashboard. %s.', 'seo-by-rank-math' ), '<a href="' . KB::get( 'analytics-settings', 'Options Panel Analytics Tab' ) . '" target="_blank">' . esc_html__( 'Learn more', 'seo-by-rank-math' ) . '</a>' ),
					'file'  => $this->directory . '/views/options.php',
					'json'  => apply_filters(
						'rank_math/analytics/options/data',
						[
							'analytics' => \RankMath\Wizard\Search_Console::get_localized_data(),
							'isSettingsPage' => true,
							'homeUrl' => home_url(),
							'fields' => [
								'console_caching_control' => [
									'description' => $this->get_description( 'console_caching_control' ),
								],
							],
							'dbInfo' => [
								'days'    => $db_info['days'] ?? 0,
								'rows'    => Str::human_number( $db_info['rows'] ?? 0 ),
								'size'    => size_format( $db_info['size'] ?? 0 ),
							],
							'isFetching' => 'fetching' === get_option( 'rank_math_analytics_first_fetch' ),
							'nextFetch'  => $next_fetch,
							'isAuthorized' => Authentication::is_authorized(),
						]
					),
				],
			],
			9
		);

		return $tabs;
	}

	/**
	 * Get the description for a field.
	 *
	 * @param string $field_id The field ID.
	 *
	 * @return string
	 */
	public function get_description( $field_id ) {
		if ( ! $field_id ) {
			return '';
		}

		$description = '';

		switch ( $field_id ) {
			case 'console_caching_control':
				// Translators: placeholder is a link to rankmath.com, with "free version" as the anchor text.
				$description = sprintf( __( 'Enter the number of days to keep Analytics data in your database. The maximum allowed days are 90 in the %s. Though, 2x data will be stored in the DB for calculating the difference properly.', 'seo-by-rank-math' ), '<a href="' . KB::get( 'pro', 'Analytics DB Option' ) . '" target="_blank" rel="noopener noreferrer">' . __( 'free version', 'seo-by-rank-math' ) . '</a>' );
				$description = apply_filters_deprecated( 'rank_math/analytics/options/cahce_control/description', [ $description ], '1.0.61.1', 'rank_math/analytics/options/cache_control/description' );
				$description = apply_filters( 'rank_math/analytics/options/cache_control/description', $description );
				break;
			default:
				$description = apply_filters( 'rank_math/analytics/options/' . $field_id . '/description', '' );
				break;
		}

		return $description;
	}

}
