<?php
/**
 * Analytics Email Reports.
 *
 * @since      1.0.68
 * @package    RankMath
 * @subpackage RankMath\modules
 * @author     Rank Math <support@rankmath.com>
 */

namespace RankMath\Analytics;

use RankMath\KB;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Google\Console;
use RankMath\Admin\Admin_Helper;

use RankMath\Helpers\Param;

defined( 'ABSPATH' ) || exit;

/**
 * Email_Reports class.
 */
class Email_Reports {

	use Hooker;

	/**
	 * Email content variables.
	 *
	 * @var array
	 */
	private $variables = [];

	/**
	 * Path to the views directory.
	 *
	 * @var array
	 */
	private $views_path = '';

	/**
	 * URL to the assets directory.
	 *
	 * @var array
	 */
	private $assets_url = '';

	/**
	 * Charts Account.
	 *
	 * @var string
	 */
	private $charts_account = 'rankmath';

	/**
	 * Charts Key.
	 *
	 * @var string
	 */
	private $charts_key = '10042B42-9193-428A-ABA7-5753F3370F84';

	/**
	 * Graph data.
	 *
	 * @var array
	 */
	private $graph_data = [];

	/**
	 * Debug mode.
	 *
	 * @var boolean
	 */
	private $debug = false;

	/**
	 * The constructor.
	 */
	public function __construct() {
		if ( ! Console::is_console_connected() ) {
			return;
		}

		$directory        = dirname( __FILE__ );
		$this->views_path = $directory . '/views/email-reports/';

		$url              = plugin_dir_url( __FILE__ );
		$this->assets_url = $this->do_filter( 'analytics/email_report_assets_url', $url . 'assets/' );

		$this->hooks();
	}

	/**
	 * Add filter & action hooks.
	 *
	 * @return void
	 */
	public function hooks() {
		$this->action( 'rank_math/analytics/email_report_event', 'email_report' );
		$this->action( 'template_redirect', 'maybe_debug' );

		$this->action( 'rank_math/analytics/email_report_html', 'replace_variables' );
		$this->action( 'rank_math/analytics/email_report_html', 'strip_comments' );
	}

	/**
	 * Send Analytics report or error message.
	 *
	 * @return void
	 */
	public function email_report() {
		$this->setup_variables();
		$this->send_report();
	}

	/**
	 * Collect variables to be used in the Report template.
	 *
	 * @return void
	 */
	public function setup_variables() {
		$stats = $this->get_stats();
		$date  = $this->get_date();

		// Translators: placeholder is "rankmath.com" as a link.
		$footer_text  = sprintf( esc_html__( 'This email was sent to you as a registered member of %s.', 'rank-math' ), '<a href="###SITE_URL###">###SITE_URL_SIMPLE###</a>' );
		$footer_text .= ' ';

		// Translators: placeholder is "click here" as a link.
		$footer_text .= sprintf( esc_html__( 'To update your email preferences, %s.', 'rank-math' ), '<a href="###SETTINGS_URL###">' . esc_html__( 'click here', 'rank-math' ) . '</a>' );

		$footer_text .= '###ADDRESS###';

		$this->variables = [
			'site_url'                    => get_home_url(),
			'site_url_simple'             => explode( '://', get_home_url() )[1],
			'settings_url'                => Helper::get_admin_url( 'options-general#setting-panel-analytics' ),
			'report_url'                  => Helper::get_admin_url( 'analytics' ),
			'assets_url'                  => $this->assets_url,
			'address'                     => '<br/> [rank_math_contact_info show="address"]',
			'logo_link'                   => KB::get( 'email-reports-logo', 'Email Report Logo' ),

			'period_days'                 => $date['period'],
			'start_date'                  => $date['start'],
			'end_date'                    => $date['end'],

			'stats_clicks'                => $stats['clicks'],
			'stats_clicks_diff'           => $stats['clicks_diff'],
			'stats_traffic'               => $stats['traffic'],
			'stats_traffic_diff'          => $stats['traffic_diff'],
			'stats_impressions'           => $stats['impressions'],
			'stats_impressions_diff'      => $stats['impressions_diff'],
			'stats_keywords'              => $stats['keywords'],
			'stats_keywords_diff'         => $stats['keywords_diff'],
			'stats_position'              => $stats['position'],
			'stats_position_diff'         => $stats['position_diff'],
			'stats_top_3_positions'       => $stats['top_3_positions'],
			'stats_top_3_positions_diff'  => $stats['top_3_positions_diff'],
			'stats_top_10_positions'      => $stats['top_10_positions'],
			'stats_top_10_positions_diff' => $stats['top_10_positions_diff'],
			'stats_top_50_positions'      => $stats['top_50_positions'],
			'stats_top_50_positions_diff' => $stats['top_50_positions_diff'],
			'stats_invalid_data'          => $stats['invalid_data'],
			'footer_html'                 => $footer_text,
		];

		$this->variables = $this->do_filter( 'analytics/email_report_variables', $this->variables );
	}

	/**
	 * Get date data.
	 *
	 * @return array
	 */
	public function get_date() {
		$period = self::get_period_from_frequency();

		// Shift 3 days prior.
		$subtract = DAY_IN_SECONDS * 3;
		$start    = strtotime( '-' . $period . ' days' ) - $subtract;
		$end      = strtotime( $this->do_filter( 'analytics/report_end_date', 'today' ) ) - $subtract;

		$start = date_i18n( 'd M Y', $start );
		$end   = date_i18n( 'd M Y', $end );
		return compact( 'start', 'end', 'period' );
	}

	/**
	 * Get Analytics stats.
	 *
	 * @return array
	 */
	public function get_stats() {
		$period = self::get_period_from_frequency();
		$stats  = Stats::get();
		$stats->set_date_range( "-{$period} days" );

		// Basic stats.
		$data = (array) $stats->get_analytics_summary();

		$analytics              = get_option( 'rank_math_google_analytic_options' );
		$is_analytics_connected = ! empty( $analytics ) && ! empty( $analytics['view_id'] );

		$out = [];

		$out['impressions']      = $data['impressions']['total'];
		$out['impressions_diff'] = $data['impressions']['difference'];

		$out['traffic']      = 0;
		$out['traffic_diff'] = 0;
		if ( $is_analytics_connected && defined( 'RANK_MATH_PRO_FILE' ) && isset( $data['pageviews'] ) ) {
			$out['traffic']      = $data['pageviews']['total'];
			$out['traffic_diff'] = $data['pageviews']['difference'];
		}

		$out['clicks']      = 0;
		$out['clicks_diff'] = 0;
		if ( ! $is_analytics_connected || ( $is_analytics_connected && ! defined( 'RANK_MATH_PRO_FILE' ) ) ) {
			$out['clicks']      = $data['clicks']['total'];
			$out['clicks_diff'] = $data['clicks']['difference'];
		}

		$out['keywords']      = $data['keywords']['total'];
		$out['keywords_diff'] = $data['keywords']['difference'];

		$out['position']      = $data['position']['total'];
		$out['position_diff'] = $data['position']['difference'];

		// Keyword stats.
		$kw_data = (array) $stats->get_top_keywords();

		$out['top_3_positions']      = $kw_data['top3']['total'];
		$out['top_3_positions_diff'] = $kw_data['top3']['difference'];

		$out['top_10_positions']      = $kw_data['top10']['total'];
		$out['top_10_positions_diff'] = $kw_data['top10']['difference'];

		$out['top_50_positions']      = $kw_data['top50']['total'];
		$out['top_50_positions_diff'] = $kw_data['top50']['difference'];

		$out['invalid_data'] = false;
		if ( ! count( array_filter( $out ) ) ) {
			$out['invalid_data'] = true;
		}

		return $out;
	}

	/**
	 * Get date period (days) from the frequency option.
	 *
	 * @param string $frequency Frequency string.
	 *
	 * @return string
	 */
	public static function get_period_from_frequency( $frequency = null ) {
		$periods = [
			'monthly' => 30,
		];

		$periods = apply_filters( 'rank_math/analytics/email_report_periods', $periods );

		if ( empty( $frequency ) ) {
			$frequency = self::get_setting( 'frequency', 'monthly' );
		}

		if ( isset( $periods[ $frequency ] ) ) {
			return absint( $periods[ $frequency ] );
		}

		return absint( reset( $periods ) );
	}

	/**
	 * Send report data.
	 *
	 * @return void
	 */
	public function send_report() {
		$account      = Admin_Helper::get_registration_data();
		$report_email = [
			'to'      => $account['email'],
			'subject' => sprintf(
				// Translators: placeholder is the site URL.
				__( 'Rank Math [SEO Report] - %s', 'rank-math' ),
				explode( '://', get_home_url() )[1]
			),
			'message' => $this->get_template( 'report' ),
			'headers' => 'Content-Type: text/html; charset=UTF-8',
		];

		/**
		 * Filter: rank_math/analytics/email_report_parameters
		 * Filters the report email parameters.
		 */
		$report_email = $this->do_filter( 'analytics/email_report_parameters', $report_email );

		wp_mail(
			$report_email['to'],
			$report_email['subject'],
			$report_email['message'],
			$report_email['headers']
		);
	}

	/**
	 * Get full HTML template for email.
	 *
	 * @param string $template Template name.
	 * @return string
	 */
	private function get_template( $template ) {
		$file = $this->locate_template( $template );

		/**
		 * Filter template file.
		 */
		$file = $this->do_filter( 'analytics/email_report_template', $file, $template );

		if ( ! file_exists( $file ) ) {
			return '';
		}

		ob_start();
		include_once $file;
		$content = ob_get_clean();

		/**
		 * Filter template HTML.
		 */
		return $this->do_filter( 'analytics/email_report_html', $content );
	}

	/**
	 * Locate and include template part.
	 *
	 * @param string $part Template part.
	 * @param array  $args Template arguments.
	 * @return mixed
	 */
	private function template_part( $part, $args = [] ) {
		$file = $this->locate_template( $part );

		/**
		 * Filter template part.
		 */
		$file = $this->do_filter( 'analytics/email_report_template_part', $file, $part, $args );

		if ( ! file_exists( $file ) ) {
			return '';
		}

		extract( $args, EXTR_SKIP ); // phpcs:ignore
		include $file;
	}

	/**
	 * Replace variables in content.
	 *
	 * @param string $content   Email content.
	 * @param string $recursion Recursion count, to account for double-encoded variables.
	 * @return string
	 */
	public function replace_variables( $content, $recursion = 1 ) {
		foreach ( $this->variables as $key => $value ) {
			if ( ! is_scalar( $value ) ) {
				continue;
			}

			// Variables must be uppercase.
			$key = mb_strtoupper( $key );

			$content = str_replace( "###$key###", $value, $content );
		}

		if ( $recursion ) {
			$recursion--;
			$content = $this->replace_variables( $content, $recursion );
		}

		return do_shortcode( $content );
	}

	/**
	 * Strip HTML & CSS comments.
	 *
	 * @param string $content Email content.
	 * @return string
	 */
	public function strip_comments( $content ) {
		$content = preg_replace( '[(<!--(.*)-->|/\*(.*)\*/)]isU', '', $content );

		return $content;
	}

	/**
	 * Init debug mode if requested and allowed.
	 *
	 * @return void
	 */
	public function maybe_debug() {
		if ( 1 !== absint( Param::get( 'rank_math_analytics_report_preview' ) ) ) {
			return;
		}

		if ( ! Helper::has_cap( 'analytics' ) ) {
			return;
		}

		$send   = boolval( Param::get( 'send' ) );
		$values = boolval( Param::get( 'values', '1' ) );

		$this->debug( $send, $values );
	}

	/**
	 * Send or output the report email.
	 *
	 * @param boolean $send   Send email or output to browser.
	 * @param boolean $values Replace variables with actual values.
	 * @return void
	 */
	private function debug( $send = false, $values = true ) {
		$this->debug = true;

		if ( $values ) {
			$this->setup_variables();
		}

		if ( $send ) {
			// Send it now.
			$this->send_report();
			$url = remove_query_arg(
				[
					'rank_math_analytics_report_preview',
					'send',
					'values',
				]
			);
			Helper::redirect( $url );
			exit;
		}

		// Output it to the browser.
		echo $this->get_template( 'report' ); // phpcs:ignore
		die();
	}

	/**
	 * Variable getter, whenever the value is needed in PHP.
	 *
	 * @param string $name Variable name.
	 * @return mixed
	 */
	public function get_variable( $name ) {
		if ( isset( $this->variables[ $name ] ) ) {
			return $this->variables[ $name ];
		}

		return "###$name###";
	}

	/**
	 * Setting getter.
	 *
	 * @param string $option  Option name.
	 * @param mixed  $default Default value.
	 * @return mixed
	 */
	public static function get_setting( $option, $default = false ) {
		return Helper::get_settings( 'general.console_email_' . $option, $default );
	}

	/**
	 * Output image inside the email template.
	 *
	 * @param string $url    Image URL.
	 * @param string $width  Image width.
	 * @param string $height Image height.
	 * @param string $alt    ALT text.
	 * @param array  $attr   Additional attributes.
	 * @return void
	 */
	public function image( $url, $width = 0, $height = 0, $alt = '', $attr = [] ) {
		$atts           = $attr;
		$atts['border'] = '0';

		if ( ! isset( $atts['src'] ) ) {
			$atts['src'] = $url;
		}

		if ( ! isset( $atts['width'] ) && $width ) {
			$atts['width'] = $width;
		}

		if ( ! isset( $atts['height'] ) && $height ) {
			$atts['height'] = $height;
		}

		if ( ! isset( $atts['alt'] ) ) {
			$atts['alt'] = $alt;
		}

		if ( ! isset( $atts['style'] ) ) {
			$atts['style'] = 'border: 0; outline: none; text-decoration: none; display: inline-block;';
		}

		if ( substr( $atts['src'], 0, 4 ) !== 'http' && substr( $atts['src'], 0, 3 ) !== '###' ) {
			$atts['src'] = $this->assets_url . 'img/' . $atts['src'];
		}

		$atts = $this->do_filter( 'analytics/email_report_image_atts', $atts, $url, $width, $height, $alt, $attr );

		$attributes = '';
		foreach ( $atts as $name => $value ) {
			if ( ! empty( $value ) ) {
				$value       = ( 'src' === $name ) ? esc_url_raw( $value ) : esc_attr( $value );
				$attributes .= ' ' . $name . '="' . $value . '"';
			}
		}

		$image = "<img $attributes>";
		$image = $this->do_filter( 'analytics/email_report_image_html', $image, $url, $width, $height, $alt, $attr );

		echo $image; // phpcs:ignore
	}

	/**
	 * Gets template path.
	 *
	 * @param string $template_name    Template name.
	 * @param bool   $return_full_path Return the full path or not.
	 * @return string
	 */
	public function locate_template( $template_name, $return_full_path = true ) {
		$default_paths  = [ $this->views_path ];
		$template_paths = $this->do_filter( 'analytics/email_report_template_paths', $default_paths );

		$paths        = array_reverse( $template_paths );
		$located      = '';
		$path_partial = '';
		foreach ( $paths as $path ) {
			if ( file_exists( $full_path = trailingslashit( $path ) . $template_name . '.php' ) ) { // phpcs:ignore
				$located      = $full_path;
				$path_partial = $path;
				break;
			}
		}

		return $return_full_path ? $located : $path_partial;
	}

	/**
	 * Load all graph data into memory.
	 *
	 * @return void
	 */
	private function load_graph_data() {
		$period = self::get_period_from_frequency();
		$stats  = Stats::get();
		$stats->set_date_range( "-{$period} days" );
		$this->graph_data = (array) $stats->get_analytics_summary_graph();
	}

	/**
	 * Get data points for graph.
	 *
	 * @param string $chart Chart to get data for.
	 * @return array
	 */
	public function get_graph_data( $chart ) {
		if ( empty( $this->graph_data ) ) {
			$this->load_graph_data();
		}

		$data  = [];
		$group = 'merged';
		$prop  = $chart;
		if ( 'traffic' === $chart ) {
			$group = 'traffic';
			$prop  = 'pageviews';
		}

		if ( empty( $this->graph_data[ $group ] ) ) {
			return $data;
		}

		foreach ( (array) $this->graph_data[ $group ] as $range_data ) {
			$range_data = (array) $range_data;
			if ( isset( $range_data[ $prop ] ) ) {
				$data[] = $range_data[ $prop ];
			}
		}

		return $data;
	}

	/**
	 * Charts API sign request.
	 *
	 * @param string $query Query.
	 * @param string $code  Code.
	 * @return string
	 */
	private function charts_api_sign( $query, $code ) {
		return hash_hmac( 'sha256', $query, $code );
	}

	/**
	 * Generate URL for the Charts API image.
	 *
	 * @param  array $graph_data Graph data points.
	 * @param  int   $width      Image height.
	 * @param  int   $height     Image width.
	 *
	 * @return string
	 */
	private function charts_api_url( $graph_data, $width = 192, $height = 102 ) {
		$params = [
			'chco' => '80ace7',
			'chds' => 'a',
			'chf'  => 'bg,s,f7f9fb',
			'chls' => 4,
			'chm'  => 'B,e2eeff,0,0,0',
			'chs'  => "{$width}x{$height}",
			'cht'  => 'ls',
			'chd'  => 'a:' . join( ',', $graph_data ),
			'icac' => $this->charts_account,
		];

		$query_string = urldecode( http_build_query( $params ) );
		$signature    = $this->charts_api_sign( $query_string, $this->charts_key );

		return 'https://charts.rankmath.com/chart?' . $query_string . '&ichm=' . $signature;
	}

	/**
	 * Check if fields should be hidden.
	 *
	 * @return bool
	 */
	public static function are_fields_hidden() {
		return apply_filters( 'rank_math/analytics/hide_email_report_options', false );
	}
}