Your IP : 216.73.216.162


Current Path : /home/x/b/o/xbodynamge/namtation/wp-content/
Upload File :
Current File : /home/x/b/o/xbodynamge/namtation/wp-content/Summary.tar

Content.php000066600000050371151144412670006706 0ustar00<?php

namespace AIOSEO\Plugin\Common\EmailReports\Summary;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use AIOSEO\Plugin\Common\Models;

/**
 * Summary content class.
 *
 * @since 4.7.2
 */
class Content {
	/**
	 * The date range data.
	 *
	 * @since 4.7.2
	 *
	 * @var array
	 */
	public $dateRange;

	/**
	 * The SEO Statistics data.
	 *
	 * @since 4.7.2
	 *
	 * @var array
	 */
	private $seoStatistics = [];

	/**
	 * The Keywords data.
	 *
	 * @since 4.7.2
	 *
	 * @var array
	 */
	private $keywords = [];

	/**
	 * The Search Statistics page URL.
	 *
	 * @since 4.7.2
	 *
	 * @var string
	 */
	public $searchStatisticsUrl;

	/**
	 * The featured image placeholder URL.
	 *
	 * @since 4.7.3
	 *
	 * @var string
	 */
	public $featuredImagePlaceholder = 'https://static.aioseo.io/report/ste/featured-image-placeholder.png';

	/**
	 * Class constructor.
	 *
	 * @since 4.7.2
	 *
	 * @param  array $dateRange The date range data.
	 * @return void
	 */
	public function __construct( $dateRange ) {
		$this->dateRange           = $dateRange;
		$this->searchStatisticsUrl = admin_url( 'admin.php?page=aioseo-search-statistics' );

		$this->setSeoStatistics();
		$this->setKeywords();
	}

	/**
	 * Sets the SEO Statistics data.
	 *
	 * @since 4.7.2
	 *
	 * @return void
	 */
	private function setSeoStatistics() {
		try {
			$seoStatistics = aioseo()->searchStatistics->getSeoStatisticsData( [
				'startDate' => gmdate( 'Y-m-d', $this->dateRange['startDateRaw'] ),
				'endDate'   => gmdate( 'Y-m-d', $this->dateRange['endDateRaw'] ),
				'orderBy'   => 'clicks',
				'orderDir'  => 'desc',
				'limit'     => '5',
				'offset'    => '0',
				'filter'    => 'all',
			] );

			if ( empty( $seoStatistics['data'] ) ) {
				return;
			}

			$this->seoStatistics = $seoStatistics['data'];
		} catch ( \Exception $e ) {
			// Do nothing.
		}
	}

	/**
	 * Sets the Keywords data.
	 *
	 * @since 4.7.2
	 *
	 * @return void
	 */
	private function setKeywords() {
		try {
			$keywords = aioseo()->searchStatistics->getKeywordsData( [
				'startDate' => gmdate( 'Y-m-d', $this->dateRange['startDateRaw'] ),
				'endDate'   => gmdate( 'Y-m-d', $this->dateRange['endDateRaw'] ),
				'orderBy'   => 'clicks',
				'orderDir'  => 'desc',
				'limit'     => '5',
				'offset'    => '0',
				'filter'    => 'all',
			] );

			if ( empty( $keywords['data'] ) ) {
				return;
			}

			$this->keywords = $keywords['data'];
		} catch ( \Exception $e ) {
			// Do nothing.
		}
	}

	/**
	 * Retrieves the content performance data.
	 *
	 * @since 4.7.2
	 *
	 * @return array The content performance data or an empty array.
	 */
	public function getPostsStatistics() {
		if ( ! $this->seoStatistics ) {
			return [];
		}

		$result = [
			'winning' => [
				'url'   => add_query_arg( [
					'aioseo-scroll' => 'aioseo-search-statistics-post-table',
					'aioseo-tab'    => 'seo-statistics',
					'table-filter'  => 'TopWinningPages'
				], $this->searchStatisticsUrl ),
				'items' => []
			],
			'losing'  => [
				'url'   => add_query_arg( [
					'aioseo-scroll' => 'aioseo-search-statistics-post-table',
					'aioseo-tab'    => 'seo-statistics',
					'table-filter'  => 'TopLosingPages'
				], $this->searchStatisticsUrl ),
				'items' => []
			]
		];

		foreach ( array_slice( $this->seoStatistics['pages']['topWinning']['rows'], 0, 3 ) as $row ) {
			$postId                       = $row['objectId'] ?? 0;
			$result['winning']['items'][] = [
				'title'      => $row['objectTitle'],
				'url'        => get_permalink( $postId ),
				'tru_seo'    => aioseo()->helpers->isTruSeoEligible( $postId ) ? $this->parseSeoScore( $row['seoScore'] ?? 0 ) : [],
				'clicks'     => $this->parseClicks( $row['clicks'] ),
				'difference' => [
					'clicks' => $this->parseDifference( $row['difference']['clicks'] ?? '' ),
				]
			];
		}

		$result['winning']['show_tru_seo'] = ! empty( array_filter( array_column( $result['winning']['items'], 'tru_seo' ) ) );

		foreach ( array_slice( $this->seoStatistics['pages']['topLosing']['rows'], 0, 3 ) as $row ) {
			$postId                      = $row['objectId'] ?? 0;
			$result['losing']['items'][] = [
				'title'      => $row['objectTitle'],
				'url'        => get_permalink( $postId ),
				'tru_seo'    => aioseo()->helpers->isTruSeoEligible( $postId ) ? $this->parseSeoScore( $row['seoScore'] ?? 0 ) : [],
				'clicks'     => $this->parseClicks( $row['clicks'] ),
				'difference' => [
					'clicks' => $this->parseDifference( $row['difference']['clicks'] ?? '' ),
				]
			];
		}

		$result['losing']['show_tru_seo'] = ! empty( array_filter( array_column( $result['losing']['items'], 'tru_seo' ) ) );

		return $result;
	}

	/**
	 * Retrieves the milestones data.
	 *
	 * @since 4.7.2
	 *
	 * @return array The milestones data or an empty array.
	 */
	public function getMilestones() { // phpcs:ignore Generic.Files.LineLength.MaxExceeded
		$milestones = [];
		if ( ! $this->seoStatistics ) {
			return $milestones;
		}

		$currentData = [
			'impressions' => $this->seoStatistics['statistics']['impressions'] ?? null,
			'clicks'      => $this->seoStatistics['statistics']['clicks'] ?? null,
			'ctr'         => $this->seoStatistics['statistics']['ctr'] ?? null,
			'keywords'    => $this->seoStatistics['statistics']['keywords'] ?? null,
		];
		$difference  = [
			'impressions' => $this->seoStatistics['statistics']['difference']['impressions'] ?? null,
			'clicks'      => $this->seoStatistics['statistics']['difference']['clicks'] ?? null,
			'ctr'         => $this->seoStatistics['statistics']['difference']['ctr'] ?? null,
			'keywords'    => $this->seoStatistics['statistics']['difference']['keywords'] ?? null,
		];

		if ( is_numeric( $currentData['impressions'] ) && is_numeric( $difference['impressions'] ) ) {
			$intDifference = intval( $difference['impressions'] );
			$message       = esc_html__( 'Your site has received the same number of impressions compared to the previous period.', 'all-in-one-seo-pack' );

			if ( $intDifference > 0 ) {
				// Translators: 1 - The number of impressions, 2 - The percentage increase.
				$message = esc_html__( 'Your site has received %1$s more impressions compared to the previous period, which is a %2$s increase.', 'all-in-one-seo-pack' );
			}

			if ( $intDifference < 0 ) {
				// Translators: 1 - The number of impressions, 2 - The percentage increase.
				$message = esc_html__( 'Your site has received %1$s fewer impressions compared to the previous period, which is a %2$s decrease.', 'all-in-one-seo-pack' );
			}

			if ( false !== strpos( $message, '%1' ) ) {
				$percentageDiff = 0 === absint( $currentData['impressions'] )
					? 100
					: round( ( absint( $intDifference ) / absint( $currentData['impressions'] ) ) * 100, 2 );
				$percentageDiff = false !== strpos( $percentageDiff, '.' )
					? number_format_i18n( $percentageDiff, count( explode( '.', $percentageDiff ) ) )
					: $percentageDiff;
				$message        = sprintf(
					$message,
					'<strong>' . aioseo()->helpers->compactNumber( absint( $intDifference ) ) . '</strong>',
					'<strong>' . $percentageDiff . '%</strong>'
				);
			}

			$milestones[] = [
				'message'    => $message,
				'background' => '#f0f6ff',
				'color'      => '#004F9D',
				'icon'       => 'icon-milestone-impressions'
			];
		}

		if ( is_numeric( $currentData['clicks'] ) && is_numeric( $difference['clicks'] ) ) {
			$intDifference = intval( $difference['clicks'] );
			$message       = esc_html__( 'Your site has received the same number of clicks compared to the previous period.', 'all-in-one-seo-pack' );

			if ( $intDifference > 0 ) {
				// Translators: 1 - The number of clicks, 2 - The percentage increase.
				$message = esc_html__( 'Your site has received %1$s more clicks compared to the previous period, which is a %2$s increase.', 'all-in-one-seo-pack' );
			}

			if ( $intDifference < 0 ) {
				// Translators: 1 - The number of clicks, 2 - The percentage increase.
				$message = esc_html__( 'Your site has received %1$s fewer clicks compared to the previous period, which is a %2$s decrease.', 'all-in-one-seo-pack' );
			}

			if ( false !== strpos( $message, '%1' ) ) {
				$percentageDiff = 0 === absint( $currentData['clicks'] )
					? 100
					: round( ( absint( $intDifference ) / absint( $currentData['clicks'] ) ) * 100, 2 );
				$percentageDiff = false !== strpos( $percentageDiff, '.' )
					? number_format_i18n( $percentageDiff, count( explode( '.', $percentageDiff ) ) )
					: $percentageDiff;
				$message        = sprintf(
					$message,
					'<strong>' . aioseo()->helpers->compactNumber( absint( $intDifference ) ) . '</strong>',
					'<strong>' . $percentageDiff . '%</strong>'
				);
			}

			$milestones[] = [
				'message'    => $message,
				'background' => '#ecfdf5',
				'color'      => '#077647',
				'icon'       => 'icon-milestone-clicks'
			];
		}

		if ( is_numeric( $currentData['ctr'] ) && is_numeric( $difference['ctr'] ) ) {
			$intDifference = floatval( $difference['ctr'] );
			$message       = esc_html__( 'Your site has the same CTR compared to the previous period.', 'all-in-one-seo-pack' );

			if ( $intDifference > 0 ) {
				// Translators: 1 - The CTR.
				$message = esc_html__( 'Your site has a %1$s higher CTR compared to the previous period.', 'all-in-one-seo-pack' );
			}

			if ( $intDifference < 0 ) {
				// Translators: 1 - The CTR.
				$message = esc_html__( 'Your site has a %1$s lower CTR compared to the previous period.', 'all-in-one-seo-pack' );
			}

			if ( false !== strpos( $message, '%1' ) ) {
				$message = sprintf(
					$message,
					'<strong>' . number_format_i18n( abs( $intDifference ), count( explode( '.', $intDifference ) ) ) . '%</strong>'
				);
			}

			$milestones[] = [
				'message'    => $message,
				'background' => '#fffbeb',
				'color'      => '#be6903',
				'icon'       => 'icon-milestone-ctr'
			];
		}

		if ( is_numeric( $currentData['keywords'] ) && is_numeric( $difference['keywords'] ) ) {
			$intDifference = intval( $difference['keywords'] );
			$message       = esc_html__( 'Your site ranked for the same number of keywords compared to the previous period.', 'all-in-one-seo-pack' );

			if ( $intDifference > 0 ) {
				// Translators: 1 - The number of keywords, 2 - The percentage increase.
				$message = esc_html__( 'Your site ranked for %1$s more keywords compared to the previous period, which is a %2$s increase.', 'all-in-one-seo-pack' );
			}

			if ( $intDifference < 0 ) {
				// Translators: 1 - The number of keywords, 2 - The percentage increase.
				$message = esc_html__( 'Your site ranked for %1$s fewer keywords compared to the previous period, which is a %2$s decrease.', 'all-in-one-seo-pack' );
			}

			if ( false !== strpos( $message, '%1' ) ) {
				$percentageDiff = 0 === absint( $currentData['keywords'] )
					? 100
					: round( ( absint( $intDifference ) / absint( $currentData['keywords'] ) ) * 100, 2 );
				$percentageDiff = false !== strpos( $percentageDiff, '.' )
					? number_format_i18n( $percentageDiff, count( explode( '.', $percentageDiff ) ) )
					: $percentageDiff;
				$message        = sprintf(
					$message,
					'<strong>' . aioseo()->helpers->compactNumber( absint( $intDifference ) ) . '</strong>',
					'<strong>' . $percentageDiff . '%</strong>'
				);
			}

			$milestones[] = [
				'message'    => $message,
				'background' => '#fef2f2',
				'color'      => '#ab2039',
				'icon'       => 'icon-milestone-keywords'
			];
		}

		return $milestones;
	}

	/**
	 * Retrieves the keyword performance data.
	 *
	 * @since 4.7.2
	 *
	 * @return array The keyword performance data or an empty array.
	 */
	public function getKeywords() {
		if ( ! $this->keywords ) {
			return [];
		}

		$result = [
			'winning' => [
				'url'   => add_query_arg( [
					'aioseo-scroll' => 'aioseo-search-statistics-keywords-table',
					'aioseo-tab'    => 'keyword-rank-tracker',
					'tab'           => 'AllKeywords',
					'table-filter'  => 'TopWinningKeywords'
				], $this->searchStatisticsUrl ),
				'items' => []
			],
			'losing'  => [
				'url'   => add_query_arg( [
					'aioseo-scroll' => 'aioseo-search-statistics-keywords-table',
					'aioseo-tab'    => 'keyword-rank-tracker',
					'tab'           => 'AllKeywords',
					'table-filter'  => 'TopLosingKeywords'
				], $this->searchStatisticsUrl ),
				'items' => []
			]
		];

		foreach ( array_slice( $this->keywords['topWinning'], 0, 3 ) as $row ) {
			$result['winning']['items'][] = [
				'title'      => $row['keyword'],
				'clicks'     => $this->parseClicks( $row['clicks'] ),
				'difference' => [
					'clicks' => $this->parseDifference( $row['difference']['clicks'] ?? '' ),
				]
			];
		}

		foreach ( array_slice( $this->keywords['topLosing'], 0, 3 ) as $row ) {
			$result['losing']['items'][] = [
				'title'      => $row['keyword'],
				'clicks'     => $this->parseClicks( $row['clicks'] ),
				'difference' => [
					'clicks' => $this->parseDifference( $row['difference']['clicks'] ?? '' ),
				]
			];
		}

		return $result;
	}

	/**
	 * Retrieves the posts data.
	 *
	 * @since 4.7.2
	 *
	 * @return array The posts' data.
	 */
	public function getAioPosts() {
		$result = [
			'publish'  => [],
			'optimize' => [],
			'cta'      => [
				'text' => esc_html__( 'Create New Post', 'all-in-one-seo-pack' ),
				'url'  => admin_url( 'post-new.php' )
			],
		];

		// 1. Retrieve the published posts.
		$publishPosts = aioseo()->core->db
			->start( 'posts as wp' )
			->select( 'wp.ID, wp.post_title, aio.seo_score' )
			->join( 'aioseo_posts as aio', 'aio.post_id = wp.ID', 'INNER' )
			->whereIn( 'wp.post_type', [ 'post' ] )
			->whereIn( 'wp.post_status', [ 'publish' ] )
			->orderBy( 'wp.post_date DESC' )
			->limit( 5 )
			->run()
			->result();

		if ( $publishPosts ) {
			$items             = $this->parsePosts( $publishPosts );
			$result['publish'] = [
				'url'          => admin_url( 'edit.php?post_status=publish&post_type=post' ),
				'items'        => $items,
				'show_stats'   => ! empty( array_filter( array_column( $items, 'stats' ) ) ),
				'show_tru_seo' => ! empty( array_filter( array_column( $items, 'tru_seo' ) ) ),
			];
		}

		// 2. Retrieve the posts to optimize.
		$optimizePosts = aioseo()->searchStatistics->getContentRankingsData( [
			'endDate'  => gmdate( 'Y-m-d', $this->dateRange['endDateRaw'] ),
			'orderBy'  => 'decayPercent',
			'orderDir' => 'asc',
			'limit'    => '3',
			'offset'   => '0',
			'filter'   => 'all',
		] );

		if ( is_array( $optimizePosts['data']['paginated']['rows'] ?? '' ) ) {
			$items = [];
			foreach ( array_slice( $optimizePosts['data']['paginated']['rows'], 0, 3 ) as $i => $row ) {
				$postId      = $row['objectId'] ?? 0;
				$items[ $i ] = [
					'title'         => $row['objectTitle'],
					'url'           => get_permalink( $postId ),
					'image_url'     => $this->getThumbnailUrl( $postId ),
					'tru_seo'       => aioseo()->helpers->isTruSeoEligible( $postId ) ? $this->parseSeoScore( $row['seoScore'] ?? 0 ) : [],
					'decay_percent' => $this->parseDifference( $row['decayPercent'] ?? '', true ),
					'issues'        => [
						'url'   => add_query_arg( [
							'aioseo-tab' => 'post-detail',
							'post'       => $postId
						], $this->searchStatisticsUrl ),
						'items' => []
					]
				];

				$aioPost = Models\Post::getPost( $postId );
				if ( $aioPost ) {
					$items[ $i ]['issues']['items'] = aioseo()->searchStatistics->helpers->getSuggestedChanges( $aioPost );
				}
			}

			$result['optimize'] = [
				'url'          => add_query_arg( [
					'aioseo-tab' => 'content-rankings',
				], $this->searchStatisticsUrl ),
				'items'        => $items,
				'show_tru_seo' => ! empty( array_filter( array_column( $items, 'tru_seo' ) ) ),
			];
		}

		return $result;
	}

	/**
	 * Retrieves the resources data.
	 *
	 * @since 4.7.2
	 *
	 * @return array The resources' data.
	 */
	public function getResources() {
		$items = aioseo()->helpers->fetchAioseoArticles( true );

		return array_slice( array_filter( $items ), 0, 3 );
	}

	/**
	 * Returns if Search Statistics content is allowed.
	 *
	 * @since 4.7.3
	 *
	 * @return bool Whether Search Statistics content is allowed.
	 */
	public function allowSearchStatistics() {
		static $return = null;
		if ( isset( $return ) ) {
			return $return;
		}

		$return = aioseo()->searchStatistics->api->auth->isConnected() &&
					aioseo()->license &&
					aioseo()->license->hasCoreFeature( 'search-statistics', 'seo-statistics' ) &&
					aioseo()->license->hasCoreFeature( 'search-statistics', 'keyword-rankings' );

		return $return;
	}

	/**
	 * Parses the SEO score.
	 *
	 * @since 4.7.2
	 *
	 * @param  int|string $score The SEO score.
	 * @return array             The parsed SEO score.
	 */
	private function parseSeoScore( $score ) {
		$score  = intval( $score );
		$parsed = [
			'value' => $score,
			'color' => '#a1a1a1',
			'text'  => $score ? "$score/100" : esc_html__( 'N/A', 'all-in-one-seo-pack' ),
		];

		if ( $parsed['value'] > 79 ) {
			$parsed['color'] = '#00aa63';
		} elseif ( $parsed['value'] > 49 ) {
			$parsed['color'] = '#ff8c00';
		} elseif ( $parsed['value'] > 0 ) {
			$parsed['color'] = '#df2a4a';
		}

		return $parsed;
	}

	/**
	 * Parses a difference.
	 *
	 * @since 4.7.2
	 *
	 * @param  int|string $number     The number to parse.
	 * @param  bool       $percentage Whether to return the text result as a percentage.
	 * @return array                  The parsed result.
	 */
	private function parseDifference( $number, $percentage = false ) {
		$parsed = [
			'color' => '#a1a1a1',
			'text'  => esc_html__( 'N/A', 'all-in-one-seo-pack' ),
		];
		if ( ! is_numeric( $number ) ) {
			return $parsed;
		}

		$number         = intval( $number );
		$parsed['text'] = aioseo()->helpers->compactNumber( absint( $number ) );

		if ( $percentage ) {
			$parsed['text'] = $number . '%';
		}

		if ( $number > 0 ) {
			$parsed['color'] = '#00aa63';
		} elseif ( $number < 0 ) {
			$parsed['color'] = '#df2a4a';
		}

		return $parsed;
	}

	/**
	 * Parses the clicks number.
	 *
	 * @since 4.7.2
	 *
	 * @param  float|int|string $number The number of clicks.
	 * @return string                   The parsed number of clicks.
	 */
	private function parseClicks( $number ) {
		return aioseo()->helpers->compactNumber( $number );
	}

	/**
	 * Parses the posts data.
	 *
	 * @since 4.7.2
	 *
	 * @param  array $posts The posts.
	 * @return array        The parsed posts' data.
	 */
	private function parsePosts( $posts ) {
		$parsed = [];
		foreach ( $posts as $k => $item ) {
			$parsed[ $k ] = [
				'title'     => aioseo()->helpers->truncate( $item->post_title, 75 ),
				'url'       => get_permalink( $item->ID ),
				'image_url' => $this->getThumbnailUrl( $item->ID ),
				'tru_seo'   => aioseo()->helpers->isTruSeoEligible( $item->ID ) ? $this->parseSeoScore( $item->seo_score ?? 0 ) : [],
				'stats'     => []
			];

			try {
				$statistics = [];
				if (
					$this->allowSearchStatistics() &&
					method_exists( aioseo()->searchStatistics, 'getPostDetailSeoStatisticsData' )
				) {
					$statistics = aioseo()->searchStatistics->getPostDetailSeoStatisticsData( [
						'startDate' => gmdate( 'Y-m-d', $this->dateRange['startDateRaw'] ),
						'endDate'   => gmdate( 'Y-m-d', $this->dateRange['endDateRaw'] ),
						'postId'    => $item->ID,
					], false );
				}

				if ( isset( $statistics['data']['statistics']['position'] ) ) {
					$parsed[ $k ]['stats'][] = [
						'icon'  => 'position',
						'label' => esc_html__( 'Position', 'all-in-one-seo-pack' ),
						'value' => round( floatval( $statistics['data']['statistics']['position'] ) ),
					];
				}

				if ( isset( $statistics['data']['statistics']['ctr'] ) ) {
					$value                   = round( floatval( $statistics['data']['statistics']['ctr'] ), 2 );
					$parsed[ $k ]['stats'][] = [
						'icon'  => 'ctr',
						'label' => 'CTR',
						'value' => ( number_format_i18n( $value, count( explode( '.', $value ) ) ) ) . '%',
					];
				}

				if ( isset( $statistics['data']['statistics']['impressions'] ) ) {
					$parsed[ $k ]['stats'][] = [
						'icon'  => 'impressions',
						'label' => esc_html__( 'Impressions', 'all-in-one-seo-pack' ),
						'value' => aioseo()->helpers->compactNumber( $statistics['data']['statistics']['impressions'] ),
					];
				}
			} catch ( \Exception $e ) {
				// Do nothing.
			}
		}

		return $parsed;
	}

	/**
	 * Retrieves the thumbnail URL.
	 *
	 * @since 4.7.2
	 *
	 * @param  int    $postId The post ID.
	 * @return string         The post featured image URL (thumbnail size).
	 */
	private function getThumbnailUrl( $postId ) {
		$imageUrl = get_the_post_thumbnail_url( $postId );

		return $imageUrl ?: $this->featuredImagePlaceholder;
	}
}Summary.php000066600000022740151144412670006730 0ustar00<?php

namespace AIOSEO\Plugin\Common\EmailReports\Summary;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Summary class.
 *
 * @since 4.7.2
 */
class Summary {
	/**
	 * The action hook to execute when the event is run.
	 *
	 * @since 4.7.2
	 *
	 * @var string
	 */
	public $actionHook = 'aioseo_report_summary';

	/**
	 * Recipient for the email. Multiple recipients can be separated by a comma.
	 *
	 * @since 4.7.2
	 *
	 * @var string
	 */
	private $recipient;

	/**
	 * Email chosen frequency. Can be either 'weekly' or 'monthly'.
	 *
	 * @since 4.7.2
	 *
	 * @var string
	 */
	private $frequency;

	/**
	 * Class constructor.
	 *
	 * @since 4.7.2
	 */
	public function __construct() {
		// No need to run any of this during a WP AJAX request.
		if ( wp_doing_ajax() ) {
			return;
		}

		// No need to keep trying scheduling unless on admin.
		add_action( 'admin_init', [ $this, 'maybeSchedule' ], 20 );

		add_action( $this->actionHook, [ $this, 'cronTrigger' ] );
	}

	/**
	 * The summary cron callback.
	 * Hooked into `{@see self::$actionHook}` action hook.
	 *
	 * @since 4.7.2
	 *
	 * @param  string $frequency The frequency of the email.
	 * @return void
	 */
	public function cronTrigger( $frequency ) {
		// Keep going only if the feature is enabled.
		if (
			! aioseo()->options->advanced->emailSummary->enable ||
			! apply_filters( 'aioseo_report_summary_enable', true, $frequency )
		) {
			return;
		}

		// Get all recipients for the given frequency.
		$recipients = wp_list_filter( aioseo()->options->advanced->emailSummary->recipients, [ 'frequency' => $frequency ] );
		if ( ! $recipients ) {
			return;
		}

		try {
			// Get only the email addresses.
			$recipients = array_column( $recipients, 'email' );

			$this->run( [
				'recipient' => implode( ',', $recipients ),
				'frequency' => $frequency,
			] );
		} catch ( \Exception $e ) {
			// Do nothing.
		}
	}

	/**
	 * Trigger the sending of the summary.
	 *
	 * @since 4.7.2
	 *
	 * @param  array      $data All the initial data needed for the summary to be sent.
	 * @throws \Exception       If the email could not be sent.
	 * @return void
	 */
	public function run( $data ) {
		try {
			$this->recipient = $data['recipient'] ?? '';
			$this->frequency = $data['frequency'] ?? '';

			aioseo()->emailReports->mail->send( $this->getRecipient(), $this->getSubject(), $this->getContentHtml(), $this->getHeaders() );
		} catch ( \Exception $e ) {
			throw new \Exception( esc_html( $e->getMessage() ), esc_html( $e->getCode() ) );
		}
	}

	/**
	 * Maybe (re)schedule the summary.
	 *
	 * @since 4.7.2
	 *
	 * @return void
	 */
	public function maybeSchedule() {
		$allowedFrequencies = $this->getAllowedFrequencies();

		// Add at least 6 hours after the day starts.
		$addToStart = HOUR_IN_SECONDS * 6;
		// Add the timezone offset.
		$addToStart -= aioseo()->helpers->getTimeZoneOffset();
		// Add a random time offset to avoid all emails being sent at the same time. 1440 * 3 = 3 days range.
		$addToStart += aioseo()->helpers->generateRandomTimeOffset( aioseo()->helpers->getSiteDomain( true ), 1440 * 3 ) * MINUTE_IN_SECONDS;

		foreach ( $allowedFrequencies as $frequency => $data ) {
			aioseo()->actionScheduler->scheduleRecurrent( $this->actionHook, $data['start'] + $addToStart, $data['interval'], compact( 'frequency' ) );
		}
	}

	/**
	 * Get one or more valid recipients.
	 *
	 * @since 4.7.2
	 *
	 * @throws \Exception If no valid recipient was set for the email.
	 * @return string     The valid recipients.
	 */
	private function getRecipient() {
		$recipients = array_map( 'trim', explode( ',', $this->recipient ) );
		$recipients = array_filter( $recipients, 'is_email' );

		if ( empty( $recipients ) ) {
			throw new \Exception( 'No valid recipient was set for the email.' ); // Not shown to the user.
		}

		return implode( ',', $recipients );
	}

	/**
	 * Get email subject.
	 *
	 * @since 4.7.2
	 *
	 * @return string The email subject.
	 */
	private function getSubject() {
		// Translators: 1 - Date range.
		$out = esc_html__( 'Your SEO Performance Report for %1$s', 'all-in-one-seo-pack' );

		$dateRange = $this->getDateRange();
		$suffix    = date_i18n( 'F', $dateRange['endDateRaw'] );
		if ( 'weekly' === $this->frequency ) {
			$suffix = $dateRange['range'];
		}

		return sprintf( $out, $suffix );
	}

	/**
	 * Get content html.
	 *
	 * @since 4.7.2
	 *
	 * @return string The email content.
	 */
	private function getContentHtml() { // phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
		$dateRange        = $this->getDateRange();
		$content          = new Content( $dateRange );
		$upsell           = [
			'search-statistics' => []
		];
		$preHeader        = sprintf(
			// Translators: 1 - The plugin short name ("AIOSEO").
			esc_html__( 'Dive into your top-performing pages with %1$s and uncover growth opportunities.', 'all-in-one-seo-pack' ),
			AIOSEO_PLUGIN_SHORT_NAME
		);
		$iconCalendar     = 'weekly' === $this->frequency
			? 'icon-calendar-weekly'
			: 'icon-calendar-monthly';
		$heading          = 'weekly' === $this->frequency
			? esc_html__( 'Your Weekly SEO Email Summary', 'all-in-one-seo-pack' )
			: esc_html__( 'Your Monthly SEO Email Summary', 'all-in-one-seo-pack' );
		$subheading       = 'weekly' === $this->frequency
			? esc_html__( 'Let\'s take a look at your SEO updates and content progress this week.', 'all-in-one-seo-pack' )
			: esc_html__( 'Let\'s take a look at your SEO updates and content progress this month.', 'all-in-one-seo-pack' );
		$statisticsReport = [
			'posts'      => [],
			'keywords'   => [],
			'milestones' => [],
			'cta'        => [
				'text' => esc_html__( 'See All SEO Statistics', 'all-in-one-seo-pack' ),
				'url'  => $content->searchStatisticsUrl
			],
		];

		if ( ! $content->allowSearchStatistics() ) {
			$upsell['search-statistics'] = [
				'cta' => [
					'text' => esc_html__( 'Unlock Search Statistics', 'all-in-one-seo-pack' ),
					'url'  => $content->searchStatisticsUrl,
				],
			];
		}

		if ( ! $upsell['search-statistics'] ) {
			$subheading = 'weekly' === $this->frequency
				? esc_html__( 'Let\'s take a look at how your site has performed in search results this week.', 'all-in-one-seo-pack' )
				: esc_html__( 'Let\'s take a look at how your site has performed in search results this month.', 'all-in-one-seo-pack' );

			$statisticsReport['posts']      = $content->getPostsStatistics();
			$statisticsReport['keywords']   = $content->getKeywords();
			$statisticsReport['milestones'] = $content->getMilestones();
		}

		$mktUrl = trailingslashit( AIOSEO_MARKETING_URL );
		$medium = 'email-report-summary';

		$posts     = $content->getAioPosts();
		$resources = [
			'posts' => array_map( function ( $item ) use ( $medium, $content ) {
				return array_merge( $item, [
					'url'   => aioseo()->helpers->utmUrl( $item['url'], $medium ),
					'image' => [
						'url' => ! empty( $item['image']['sizes']['medium']['source_url'] )
							? $item['image']['sizes']['medium']['source_url']
							: $content->featuredImagePlaceholder
					]
				] );
			}, $content->getResources() ),
			'cta'   => [
				'text' => esc_html__( 'See All Resources', 'all-in-one-seo-pack' ),
				'url'  => aioseo()->helpers->utmUrl( 'https://aioseo.com/blog/', $medium ),
			],
		];
		$links     = [
			'disable'        => admin_url( 'admin.php?page=aioseo-settings&aioseo-scroll=aioseo-email-summary-row&aioseo-highlight=aioseo-email-summary-row&aioseo-tab=advanced' ),
			'update'         => admin_url( 'update-core.php' ),
			'marketing-site' => aioseo()->helpers->utmUrl( $mktUrl, $medium ),
			'facebook'       => aioseo()->helpers->utmUrl( $mktUrl . 'plugin/facebook', $medium ),
			'linkedin'       => aioseo()->helpers->utmUrl( $mktUrl . 'plugin/linkedin', $medium ),
			'youtube'        => aioseo()->helpers->utmUrl( $mktUrl . 'plugin/youtube', $medium ),
			'twitter'        => aioseo()->helpers->utmUrl( $mktUrl . 'plugin/twitter', $medium ),
		];

		ob_start();
		require AIOSEO_DIR . '/app/Common/Views/report/summary.php';

		return ob_get_clean();
	}

	/**
	 * Get email headers.
	 *
	 * @since 4.7.2
	 *
	 * @return array The email headers.
	 */
	private function getHeaders() {
		return [ 'Content-Type: text/html; charset=UTF-8' ];
	}

	/**
	 * Get all allowed frequencies.
	 *
	 * @since 4.7.2
	 *
	 * @return array The email allowed frequencies.
	 */
	private function getAllowedFrequencies() {
		$time           = time();
		$secondsTillNow = $time - strtotime( 'today' );

		return [
			'weekly'  => [
				'interval' => WEEK_IN_SECONDS,
				'start'    => strtotime( 'next Monday' ) - $time
			],
			'monthly' => [
				'interval' => MONTH_IN_SECONDS,
				'start'    => ( strtotime( 'first day of next month' ) + ( DAY_IN_SECONDS * 2 ) - $secondsTillNow ) - $time
			]
		];
	}

	/**
	 * Retrieves the date range data based on the frequency.
	 *
	 * @since 4.7.3
	 *
	 * @return array The date range data.
	 */
	private function getDateRange() {
		$dateFormat = get_option( 'date_format' );

		// If frequency is 'monthly'.
		$endDateRaw   = strtotime( 'last day of last month' );
		$startDateRaw = strtotime( 'first day of last month' );

		// If frequency is 'weekly'.
		if ( 'weekly' === $this->frequency ) {
			$endDateRaw   = strtotime( 'last Saturday' );
			$startDateRaw = strtotime( 'last Sunday', $endDateRaw );
		}

		$endDate   = date_i18n( $dateFormat, $endDateRaw );
		$startDate = date_i18n( $dateFormat, $startDateRaw );

		return [
			'endDate'      => $endDate,
			'endDateRaw'   => $endDateRaw,
			'startDate'    => $startDate,
			'startDateRaw' => $startDateRaw,
			'range'        => "$startDate - $endDate",
		];
	}
}