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/SearchStatistics.tar

KeywordRankTracker.php000066600000020037151150354630011042 0ustar00<?php

namespace AIOSEO\Plugin\Common\SearchStatistics;

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

/**
 * Keyword Rank Tracker class.
 *
 * @since 4.7.0
 */
class KeywordRankTracker {
	/**
	 * Retrieves all the keywords' statistics.
	 *
	 * @since 4.7.0
	 *
	 * @param  array $formattedKeywords The formatted keywords.
	 * @param  array $args              The arguments.
	 * @return array                    The statistics for the keywords.
	 */
	public function fetchKeywordsStatistics( &$formattedKeywords = [], $args = [] ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
		return [
			'distribution'          => [
				'top3'       => '6.86',
				'top10'      => '11.03',
				'top50'      => '52.10',
				'top100'     => '30.01',
				'difference' => [
					'top3'   => '24.31',
					'top10'  => '33.70',
					'top50'  => '-30.50',
					'top100' => '-27.51'
				]
			],
			'distributionIntervals' => [
				[
					'date'   => '2022-10-23',
					'top3'   => '30.70',
					'top10'  => '38.60',
					'top50'  => '24.50',
					'top100' => '6.20'
				],
				[
					'date'   => '2022-10-30',
					'top3'   => '31.60',
					'top10'  => '42.10',
					'top50'  => '21.00',
					'top100' => '5.30'
				],
				[
					'date'   => '2022-11-06',
					'top3'   => '31.30',
					'top10'  => '44.40',
					'top50'  => '20.30',
					'top100' => '4.00'
				],
				[
					'date'   => '2022-11-13',
					'top3'   => '31.70',
					'top10'  => '44.20',
					'top50'  => '20.20',
					'top100' => '3.90'
				],
				[
					'date'   => '2022-11-20',
					'top3'   => '31.70',
					'top10'  => '45.70',
					'top50'  => '18.00',
					'top100' => '4.60'
				],
				[
					'date'   => '2022-11-27',
					'top3'   => '32.50',
					'top10'  => '47.80',
					'top50'  => '16.80',
					'top100' => '2.90'
				],
				[
					'date'   => '2022-12-04',
					'top3'   => '32.50',
					'top10'  => '47.20',
					'top50'  => '17.90',
					'top100' => '2.40'
				],
				[
					'date'   => '2022-12-11',
					'top3'   => '31.80',
					'top10'  => '43.70',
					'top50'  => '21.00',
					'top100' => '3.50'
				],
				[
					'date'   => '2022-12-18',
					'top3'   => '30.40',
					'top10'  => '43.60',
					'top50'  => '22.40',
					'top100' => '3.60'
				],
				[
					'date'   => '2022-12-25',
					'top3'   => '26.90',
					'top10'  => '37.20',
					'top50'  => '29.70',
					'top100' => '6.20'
				],
				[
					'date'   => '2023-01-01',
					'top3'   => '27.00',
					'top10'  => '33.80',
					'top50'  => '31.60',
					'top100' => '7.60'
				],
				[
					'date'   => '2023-01-08',
					'top3'   => '26.60',
					'top10'  => '38.60',
					'top50'  => '30.00',
					'top100' => '4.80'
				],
				[
					'date'   => '2023-01-16',
					'top3'   => '31.10',
					'top10'  => '43.90',
					'top50'  => '22.50',
					'top100' => '2.50'
				]
			]
		];
	}

	/**
	 * Retrieves all the keywords, formatted.
	 *
	 * @since 4.7.0
	 *
	 * @param  array $args The arguments.
	 * @return array       The formatted keywords.
	 */
	public function getFormattedKeywords( $args = [] ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
		$statistics = [];
		for ( $i = 1; $i < 9; $i++ ) {
			$statistics[ $i ] = [
				'clicks'      => wp_rand( 1, 1000 ),
				'impressions' => wp_rand( 10, 10000 ),
				'ctr'         => wp_rand( 1, 99 ),
				'position'    => wp_rand( 1, 100 ),
				'history'     => [
					[
						'date'     => gmdate( 'Y-m-d', strtotime( '-30 days' ) ),
						'position' => wp_rand( 1, 15 ),
						'clicks'   => wp_rand( 10, 100 ),
					],
					[
						'date'     => gmdate( 'Y-m-d', strtotime( '-23 days' ) ),
						'position' => wp_rand( 1, 15 ),
						'clicks'   => wp_rand( 10, 100 ),
					],
					[
						'date'     => gmdate( 'Y-m-d', strtotime( '-16 days' ) ),
						'position' => wp_rand( 1, 15 ),
						'clicks'   => wp_rand( 10, 100 ),
					],
					[
						'date'     => gmdate( 'Y-m-d', strtotime( '-9 days' ) ),
						'position' => wp_rand( 1, 15 ),
						'clicks'   => wp_rand( 10, 100 ),
					],
					[
						'date'     => gmdate( 'Y-m-d', strtotime( '-2 days' ) ),
						'position' => wp_rand( 1, 15 ),
						'clicks'   => wp_rand( 10, 100 ),
					]
				]
			];
		}

		return [
			'rows'   => [
				[
					'id'         => 1,
					'name'       => 'best seo plugin',
					'favorited'  => false,
					'groups'     => [
						[
							'id'   => 1,
							'name' => 'Blog Pages Group'
						]
					],
					'statistics' => $statistics[1]
				],
				[
					'id'         => 2,
					'name'       => 'aioseo is the best',
					'favorited'  => true,
					'groups'     => [
						[
							'id'   => 2,
							'name' => 'Low Performance Group'
						]
					],
					'statistics' => $statistics[2]
				],
				[
					'id'         => 3,
					'name'       => 'analyze my seo',
					'favorited'  => false,
					'groups'     => [
						[
							'id'   => 3,
							'name' => 'High Performance Group'
						]
					],
					'statistics' => $statistics[3]
				],
				[
					'id'         => 4,
					'name'       => 'wordpress seo',
					'favorited'  => false,
					'groups'     => [],
					'statistics' => $statistics[4]
				],
				[
					'id'         => 5,
					'name'       => 'best seo plugin pro',
					'favorited'  => false,
					'groups'     => [],
					'statistics' => $statistics[5]
				],
				[
					'id'         => 6,
					'name'       => 'aioseo wordpress',
					'favorited'  => false,
					'groups'     => [],
					'statistics' => $statistics[6]
				],
				[
					'id'         => 7,
					'name'       => 'headline analyzer aioseo',
					'favorited'  => false,
					'groups'     => [],
					'statistics' => $statistics[7]
				],
				[
					'id'         => 8,
					'name'       => 'best seo plugin plugin',
					'favorited'  => false,
					'groups'     => [],
					'statistics' => $statistics[8]
				]
			],
			'totals' => [
				'total' => 8,
				'pages' => 1,
				'page'  => 1
			],
		];
	}

	/**
	 * Retrieves all the keyword groups, formatted.
	 *
	 * @since 4.7.0
	 *
	 * @param  array $args The arguments.
	 * @return array       The formatted keyword groups.
	 */
	public function getFormattedGroups( $args = [] ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
		$statistics = [];
		for ( $i = 1; $i < 4; $i++ ) {
			$statistics[ $i ] = [
				'clicks'      => wp_rand( 1, 1000 ),
				'impressions' => wp_rand( 10, 10000 ),
				'ctr'         => wp_rand( 1, 99 ),
				'position'    => wp_rand( 1, 100 )
			];
		}

		return [
			'rows'   => [
				[
					'id'          => 1,
					'name'        => 'Blog Pages Group',
					'keywordsQty' => 1,
					'keywords'    => [],
					'statistics'  => $statistics[1]
				],
				[
					'id'          => 2,
					'name'        => 'Low Performance Group',
					'keywordsQty' => 1,
					'keywords'    => [],
					'statistics'  => $statistics[2]
				],
				[
					'id'          => 3,
					'name'        => 'High Performance Group',
					'keywordsQty' => 1,
					'keywords'    => [],
					'statistics'  => $statistics[3]
				]
			],
			'totals' => [
				'total' => 8,
				'pages' => 1,
				'page'  => 1
			],
		];
	}

	/**
	 * Returns the data for Vue.
	 *
	 * @since 4.7.0
	 *
	 * @return array The data for Vue.
	 */
	public function getVueData() {
		$formattedKeywords = $this->getFormattedKeywords();
		$formattedGroups   = $this->getFormattedGroups();

		return [
			// Dummy data to show on the UI.
			'keywords' => [
				'all'        => $formattedKeywords,
				'paginated'  => $formattedKeywords,
				'count'      => count( $formattedKeywords['rows'] ),
				'statistics' => $this->fetchKeywordsStatistics( $formattedKeywords ),
			],
			'groups'   => [
				'all'       => $formattedGroups,
				'paginated' => $formattedGroups,
				'count'     => count( $formattedGroups['rows'] ),
			],
		];
	}

	/**
	 * Returns the data for Vue.
	 *
	 * @since 4.7.0
	 *
	 * @return array The data.
	 */
	public function getVueDataEdit() {
		$formattedKeywords = $this->getFormattedKeywords();

		return [
			// Dummy data to show on the UI.
			'keywords' => [
				'all'       => $formattedKeywords,
				'paginated' => $formattedKeywords,
				'count'     => count( $formattedKeywords['rows'] ),
			],
		];
	}
}Notices.php000066600000013237151150354630006676 0ustar00<?php
namespace AIOSEO\Plugin\Common\SearchStatistics;

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

use AIOSEO\Plugin\Common\Models;

/**
 * Handles the notices for the Search Statistics.
 *
 * @since 4.6.2
 */
class Notices {
	/**
	 * Class constructor.
	 *
	 * @since 4.6.2
	 */
	public function __construct() {
		if ( ! is_admin() ) {
			return;
		}

		add_action( 'init', [ $this, 'init' ] );
	}

	/**
	 * Initialize the class.
	 *
	 * @since 4.6.2
	 *
	 * @return void
	 */
	public function init() {
		$this->siteConnected();
		$this->siteVerified();
		$this->sitemapHasErrors();
	}

	/**
	 * Add a notice if the site is not connected.
	 *
	 * @since 4.6.2
	 *
	 * @return void
	 */
	private function siteConnected() {
		$notification = Models\Notification::getNotificationByName( 'search-console-site-not-connected' );
		if ( aioseo()->searchStatistics->api->auth->isConnected() ) {
			if ( $notification->exists() ) {
				Models\Notification::deleteNotificationByName( 'search-console-site-not-connected' );
			}

			return;
		}

		if ( $notification->exists() ) {
			return;
		}

		Models\Notification::addNotification( [
			'slug'              => uniqid(),
			'notification_name' => 'search-console-site-not-connected',
			'title'             => __( 'Have you connected your site to Google Search Console?', 'all-in-one-seo-pack' ),
			'content'           => sprintf(
				// Translators: 1 - The plugin short name ("AIOSEO").
				__( '%1$s can now verify whether your site is correctly verified with Google Search Console and that your sitemaps have been submitted correctly. Connect with Google Search Console now to ensure your content is being added to Google as soon as possible for increased rankings.', 'all-in-one-seo-pack' ), // phpcs:ignore Generic.Files.LineLength.MaxExceeded
				AIOSEO_PLUGIN_SHORT_NAME
			),
			'type'              => 'warning',
			'level'             => [ 'all' ],
			'button1_label'     => __( 'Connect to Google Search Console', 'all-in-one-seo-pack' ),
			'button1_action'    => 'https://route#aioseo-settings&aioseo-scroll=google-search-console-settings&aioseo-highlight=google-search-console-settings:webmaster-tools?activetool=googleSearchConsole', // phpcs:ignore Generic.Files.LineLength.MaxExceeded
			'start'             => gmdate( 'Y-m-d H:i:s' )
		] );
	}

	/**
	 * Add a notice if the site is not verified or was deleted.
	 *
	 * @since 4.6.2
	 *
	 * @return void
	 */
	private function siteVerified() {
		$notification = Models\Notification::getNotificationByName( 'search-console-site-not-verified' );
		if (
			! aioseo()->searchStatistics->api->auth->isConnected() ||
			aioseo()->internalOptions->searchStatistics->site->verified ||
			0 === aioseo()->internalOptions->searchStatistics->site->lastFetch // Not fetched yet.
		) {
			if ( $notification->exists() ) {
				Models\Notification::deleteNotificationByName( 'search-console-site-not-verified' );
			}

			return;
		}

		if ( $notification->exists() ) {
			return;
		}

		Models\Notification::addNotification( [
			'slug'              => uniqid(),
			'notification_name' => 'search-console-site-not-verified',
			'title'             => __( 'Your site was removed from Google Search Console.', 'all-in-one-seo-pack' ),
			'content'           => __( 'We detected that your site has been removed from Google Search Console. If this was done in error, click below to re-sync and resolve this issue.', 'all-in-one-seo-pack' ), // phpcs:ignore Generic.Files.LineLength.MaxExceeded
			'type'              => 'warning',
			'level'             => [ 'all' ],
			'button1_label'     => __( 'Reconnect Google Search Console', 'all-in-one-seo-pack' ),
			'button1_action'    => 'https://route#aioseo-settings&aioseo-scroll=google-search-console-settings&aioseo-highlight=google-search-console-settings:webmaster-tools?activetool=googleSearchConsole', // phpcs:ignore Generic.Files.LineLength.MaxExceeded
			'start'             => gmdate( 'Y-m-d H:i:s' )
		] );
	}

	/**
	 * Add a notice if the sitemap has errors.
	 *
	 * @since 4.6.2
	 *
	 * @return void
	 */
	private function sitemapHasErrors() {
		$notification = Models\Notification::getNotificationByName( 'search-console-sitemap-has-errors' );
		if (
			! aioseo()->searchStatistics->api->auth->isConnected() ||
			! aioseo()->internalOptions->searchStatistics->site->verified ||
			0 === aioseo()->internalOptions->searchStatistics->sitemap->lastFetch || // Not fetched yet.
			! aioseo()->searchStatistics->sitemap->getSitemapsWithErrors()
		) {
			if ( $notification->exists() ) {
				Models\Notification::deleteNotificationByName( 'search-console-sitemap-has-errors' );
			}

			return;
		}

		if ( $notification->exists() ) {
			return;
		}

		$lastFetch = aioseo()->internalOptions->searchStatistics->sitemap->lastFetch;
		$lastFetch = date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $lastFetch );

		Models\Notification::addNotification( [
			'slug'              => uniqid(),
			'notification_name' => 'search-console-sitemap-has-errors',
			'title'             => __( 'Your sitemap has errors.', 'all-in-one-seo-pack' ),
			'content'           => sprintf(
				// Translators: 1 - Last fetch date.
				__( 'We detected that your sitemap has errors. The last fetch was on %1$s. Click below to resolve this issue.', 'all-in-one-seo-pack' ), // phpcs:ignore Generic.Files.LineLength.MaxExceeded
				$lastFetch
			),
			'type'              => 'warning',
			'level'             => [ 'all' ],
			'button1_label'     => __( 'Fix Sitemap Errors', 'all-in-one-seo-pack' ),
			'button1_action'    => 'https://route#aioseo-sitemaps&open-modal=true:general-sitemap', // phpcs:ignore Generic.Files.LineLength.MaxExceeded
			'start'             => gmdate( 'Y-m-d H:i:s' )
		] );
	}
}Site.php000066600000007077151150354630006203 0ustar00<?php
namespace AIOSEO\Plugin\Common\SearchStatistics;

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

/**
 * Handles the site for the search statistics.
 *
 * @since 4.6.2
 */
class Site {
	/**
	 * The action name.
	 *
	 * @since 4.6.2
	 *
	 * @var string
	 */
	public $action = 'aioseo_search_statistics_site_check';

	/**
	 * Class constructor.
	 *
	 * @since 4.6.2
	 */
	public function __construct() {
		add_action( 'admin_init', [ $this, 'init' ] );
		add_action( $this->action, [ $this, 'worker' ] );
	}

	/**
	 * Initialize the class.
	 *
	 * @since 4.6.2
	 *
	 * @return void
	 */
	public function init() {
		if (
			! aioseo()->searchStatistics->api->auth->isConnected() ||
			aioseo()->actionScheduler->isScheduled( $this->action )
		) {
			return;
		}

		aioseo()->actionScheduler->scheduleAsync( $this->action );
	}

	/**
	 * Check whether the site is verified on Google Search Console and verifies it if needed.
	 *
	 * @since 4.6.2
	 *
	 * @return void
	 */
	public function worker() {
		if ( ! aioseo()->searchStatistics->api->auth->isConnected() ) {
			return;
		}

		$siteStatus = $this->checkStatus();
		if ( empty( $siteStatus ) ) {
			// If it failed to communicate with the server, try again in a few hours.
			aioseo()->actionScheduler->scheduleSingle( $this->action, wp_rand( HOUR_IN_SECONDS, 2 * HOUR_IN_SECONDS ), [], true );

			return;
		}

		$this->processStatus( $siteStatus );

		// Schedule a new check for the next week.
		aioseo()->actionScheduler->scheduleSingle( $this->action, WEEK_IN_SECONDS + wp_rand( 0, 3 * DAY_IN_SECONDS ), [], true );
	}

	/**
	 * Maybe verifies the site on Google Search Console.
	 *
	 * @since 4.6.2
	 *
	 * @return void
	 */
	public function maybeVerify() {
		if ( ! aioseo()->searchStatistics->api->auth->isConnected() ) {
			return;
		}

		$siteStatus = $this->checkStatus();
		if ( empty( $siteStatus ) ) {
			return;
		}

		$this->processStatus( $siteStatus );
	}

	/**
	 * Checks the site status on Google Search Console.
	 *
	 * @since 4.6.2
	 *
	 * @return array The site status.
	 */
	private function checkStatus() {
		$api      = new Api\Request( 'google-search-console/site/check/' );
		$response = $api->request();

		if ( is_wp_error( $response ) ) {
			return [];
		}

		return $response;
	}

	/**
	 * Processes the site status.
	 *
	 * @since 4.6.3
	 *
	 * @param  array $siteStatus The site status.
	 * @return void
	 */
	private function processStatus( $siteStatus ) {
		switch ( $siteStatus['code'] ) {
			case 'site_verified':
				aioseo()->internalOptions->searchStatistics->site->verified  = true;
				aioseo()->internalOptions->searchStatistics->site->lastFetch = time();
				break;
			case 'verification_needed':
				$this->verify( $siteStatus['data'] );
				break;
			case 'site_not_found':
			case 'couldnt_get_token':
			default:
				aioseo()->internalOptions->searchStatistics->site->verified  = false;
				aioseo()->internalOptions->searchStatistics->site->lastFetch = time();
		}
	}

	/**
	 * Verifies the site on Google Search Console.
	 *
	 * @since 4.6.2
	 *
	 * @param  string $token The verification token.
	 * @return void
	 */
	private function verify( $token = '' ) {
		if ( empty( $token ) ) {
			return;
		}

		aioseo()->options->webmasterTools->google = esc_attr( $token );

		$api      = new Api\Request( 'google-search-console/site/verify/' );
		$response = $api->request();

		if ( is_wp_error( $response ) || 'site_verified' !== $response['code'] ) {
			return;
		}

		aioseo()->internalOptions->searchStatistics->site->verified  = true;
		aioseo()->internalOptions->searchStatistics->site->lastFetch = time();
	}
}IndexStatus.php000066600000023210151150354630007535 0ustar00<?php
namespace AIOSEO\Plugin\Common\SearchStatistics;

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

/**
 * Index Status class.
 *
 * @since 4.8.2
 */
class IndexStatus {
	/**
	 * Retrieves the overview data.
	 *
	 * @since 4.8.2
	 *
	 * @return array The overview data.
	 */
	public function getOverview() {
		$data = [
			'post' => [
				'results' => [
					[
						'count'         => 164,
						'coverageState' => 'Submitted and Indexed', // No need to translate this. It's translated on the front-end.
					],
					[
						'count'         => 112,
						'coverageState' => 'Discovered - Currently Not Indexed',
					],
					[
						'count'         => 44,
						'coverageState' => 'Crawled - Currently Not Indexed',
					],
					[
						'count'         => 8,
						'coverageState' => 'URL is unknown to Google',
					]
				]
			]
		];

		$data['post']['total'] = array_sum( array_column( $data['post']['results'], 'count' ) );

		return $data;
	}

	/**
	 * Retrieves all the objects, formatted.
	 *
	 * @since 4.8.2
	 *
	 * @return array The formatted objects.
	 */
	public function getFormattedObjects() {
		$siteUrl = aioseo()->helpers->getSiteUrl();

		$rows = [
			[
				'objectId'             => 4,
				'objectTitle'          => 'Homepage',
				'verdict'              => 'PASS',
				'coverageState'        => 'Submitted and Indexed',
				'robotsTxtState'       => 'ALLOWED',
				'indexingState'        => 'INDEXING_ALLOWED',
				'pageFetchState'       => 'SUCCESSFUL',
				'crawledAs'            => 'MOBILE',
				'lastCrawlTime'        => aioseo()->helpers->dateToWpFormat( '2025-01-05 13:54:00' ),
				'userCanonical'        => $siteUrl,
				'googleCanonical'      => $siteUrl,
				'sitemap'              => [
					aioseo()->sitemap->helpers->getUrl( 'general' )
				],
				'referringUrls'        => [],
				'richResultsResult'    => [
					'detectedItems' => [
						[
							'richResultType' => 'Breadcrumbs',
							'items'          => [
								[
									'name' => 'Unnamed item'
								]
							]
						],
						[
							'richResultType' => 'FAQ',
							'items'          => [
								[
									'name' => 'Unnamed item'
								]
							]
						]
					]
				],
				'inspectionResultLink' => '#',
				'richResultsTestLink'  => '#'
			],
			[
				'objectId'             => 6,
				'objectTitle'          => 'About',
				'verdict'              => 'PASS',
				'coverageState'        => 'Submitted and Indexed',
				'robotsTxtState'       => 'ALLOWED',
				'indexingState'        => 'INDEXING_ALLOWED',
				'pageFetchState'       => 'SUCCESSFUL',
				'crawledAs'            => 'MOBILE',
				'lastCrawlTime'        => aioseo()->helpers->dateToWpFormat( '2025-01-06 09:22:00' ),
				'userCanonical'        => $siteUrl . '/about',
				'googleCanonical'      => $siteUrl . '/about',
				'sitemap'              => [
					aioseo()->sitemap->helpers->getUrl( 'general' )
				],
				'referringUrls'        => [
					$siteUrl
				],
				'richResultsResult'    => [
					'detectedItems' => [
						[
							'richResultType' => 'Breadcrumbs',
							'items'          => [
								[
									'name' => 'Unnamed item'
								]
							]
						]
					]
				],
				'inspectionResultLink' => '#',
				'richResultsTestLink'  => '#'
			],
			[
				'objectId'             => 1,
				'objectTitle'          => 'Contact Us',
				'verdict'              => 'PASS',
				'coverageState'        => 'Submitted and Indexed',
				'robotsTxtState'       => 'ALLOWED',
				'indexingState'        => 'INDEXING_ALLOWED',
				'pageFetchState'       => 'SUCCESSFUL',
				'crawledAs'            => 'DESKTOP',
				'lastCrawlTime'        => aioseo()->helpers->dateToWpFormat( '2025-01-02 16:47:00' ),
				'userCanonical'        => $siteUrl . '/contact-us',
				'googleCanonical'      => $siteUrl . '/contact-us',
				'sitemap'              => [
					aioseo()->sitemap->helpers->getUrl( 'general' )
				],
				'referringUrls'        => [
					$siteUrl
				],
				'richResultsResult'    => [
					'detectedItems' => [
						[
							'richResultType' => 'Breadcrumbs',
							'items'          => [
								[
									'name' => 'Unnamed item'
								]
							]
						],
						[
							'richResultType' => 'FAQ',
							'items'          => [
								[
									'name' => 'Unnamed item'
								]
							]
						]
					]
				],
				'inspectionResultLink' => '#',
				'richResultsTestLink'  => '#'
			],
			[
				'objectId'             => 2,
				'objectTitle'          => 'Pricing',
				'verdict'              => 'NEUTRAL',
				'coverageState'        => 'Crawled - Currently Not Indexed',
				'robotsTxtState'       => 'DISALLOWED',
				'indexingState'        => 'BLOCKED_BY_META_TAG',
				'pageFetchState'       => 'SUCCESSFUL',
				'crawledAs'            => 'DESKTOP',
				'lastCrawlTime'        => aioseo()->helpers->dateToWpFormat( '2024-01-15 11:00:00' ),
				'userCanonical'        => $siteUrl . '/pricing',
				'googleCanonical'      => $siteUrl . '/pricing',
				'sitemap'              => [
					aioseo()->sitemap->helpers->getUrl( 'general' )
				],
				'referringUrls'        => [
					$siteUrl
				],
				'richResultsResult'    => [
					'detectedItems' => [
						[
							'richResultType' => 'Breadcrumbs',
							'items'          => [
								[
									'name' => 'Unnamed item'
								]
							]
						],
						[
							'richResultType' => 'Product snippet',
							'items'          => [
								[
									'name'   => 'All in One SEO (AIOSEO)',
									'issues' => [
										[
											'issueMessage' => 'Missing field "priceValidUntil"',
											'severity'     => 'WARNING'
										]
									]
								]
							]
						]
					]
				],
				'inspectionResultLink' => '#',
				'richResultsTestLink'  => '#'
			],
			[
				'objectId'             => 3,
				'objectTitle'          => 'Blog',
				'verdict'              => 'PASS',
				'coverageState'        => 'Submitted and Indexed',
				'robotsTxtState'       => 'ALLOWED',
				'indexingState'        => 'INDEXED',
				'pageFetchState'       => 'SUCCESSFUL',
				'crawledAs'            => 'MOBILE',
				'lastCrawlTime'        => aioseo()->helpers->dateToWpFormat( '2024-03-01 08:00:00' ),
				'userCanonical'        => $siteUrl . '/blog',
				'googleCanonical'      => $siteUrl . '/blog',
				'sitemap'              => [
					aioseo()->sitemap->helpers->getUrl( 'general' )
				],
				'referringUrls'        => [
					$siteUrl
				],
				'inspectionResultLink' => '#',
				'richResultsTestLink'  => '#'
			],
		];

		return [
			'paginated' => [
				'rows'   => $rows,
				'totals' => [
					'total' => count( $rows ),
					'pages' => 1,
					'page'  => 1
				]
			]
		];
	}

	/**
	 * Returns the data for Vue.
	 *
	 * @since 4.8.2
	 *
	 * @return array The data for Vue.
	 */
	public function getVueData() {
		return [
			'objects'  => $this->getFormattedObjects(),
			'overview' => $this->getOverview(),
			'options'  => $this->getUiOptions()
		];
	}

	/**
	 * Retrieves options ideally only for Vue usage on the front-end.
	 *
	 * @since 4.8.2
	 *
	 * @return array The options.
	 */
	protected function getUiOptions() {
		$postTypeOptions = [
			[
				'label' => __( 'All Post Types', 'all-in-one-seo-pack' ),
				'value' => ''
			],
			[
				'label' => __( 'Post', 'all-in-one-seo-pack' ),
				'value' => 'post'
			],
			[
				'label' => __( 'Page', 'all-in-one-seo-pack' ),
				'value' => 'page'
			]
		];

		$statusOptions = [
			[
				'label' => __( 'Status (All)', 'all-in-one-seo-pack' ),
				'value' => ''
			],
			[
				'label' => __( 'Indexed', 'all-in-one-seo-pack' ),
				'value' => 'submitted',
				'color' => '#00AA63',
			],
			[
				'label' => __( 'Crawled, Not Indexed', 'all-in-one-seo-pack' ),
				'value' => 'crawled',
				'color' => '#F18200',
			],
			[
				'label' => __( 'Discovered, Not Indexed', 'all-in-one-seo-pack' ),
				'value' => 'discovered',
				'color' => '#005AE0',
			],
			[
				'label' => __( 'Other, Not Indexed', 'all-in-one-seo-pack' ),
				'value' => 'unknown|excluded|invalid|error',
				'color' => '#DF2A4A',
			],
			[
				'label' => __( 'No Results Yet', 'all-in-one-seo-pack' ),
				'value' => 'empty',
				'color' => '#999999',
			]
		];

		$robotsTxtStateOptions = [
			[
				'label' => __( 'Robots.txt (All)', 'all-in-one-seo-pack' ),
				'value' => ''
			],
			[
				'label' => __( 'Allowed', 'all-in-one-seo-pack' ),
				'value' => 'ALLOWED'
			],
			[
				'label' => __( 'Blocked', 'all-in-one-seo-pack' ),
				'value' => 'DISALLOWED'
			]
		];

		$crawledAsOptions = [
			[
				'label' => __( 'Crawled As (All)', 'all-in-one-seo-pack' ),
				'value' => ''
			],
			[
				'label' => __( 'Desktop', 'all-in-one-seo-pack' ),
				'value' => 'DESKTOP'
			],
			[
				'label' => __( 'Mobile', 'all-in-one-seo-pack' ),
				'value' => 'MOBILE'
			]
		];

		$pageFetchStateOptions = [
			[
				'label' => __( 'Page Fetch (All)', 'all-in-one-seo-pack' ),
				'value' => ''
			],
			[
				'label' => __( 'Successful', 'all-in-one-seo-pack' ),
				'value' => 'SUCCESSFUL'
			],
			[
				'label' => __( 'Error', 'all-in-one-seo-pack' ),
				'value' => 'SOFT_404,BLOCKED_ROBOTS_TXT,NOT_FOUND,ACCESS_DENIED,SERVER_ERROR,REDIRECT_ERROR,ACCESS_FORBIDDEN,BLOCKED_4XX,INTERNAL_CRAWL_ERROR,INVALID_URL'
			]
		];

		$additionalFilters = [
			'postTypeOptions'       => [
				'name'    => 'postType',
				'options' => $postTypeOptions
			],
			'statusOptions'         => [
				'name'    => 'status',
				'options' => $statusOptions
			],
			'robotsTxtStateOptions' => [
				'name'    => 'robotsTxtState',
				'options' => $robotsTxtStateOptions
			],
			'pageFetchStateOptions' => [
				'name'    => 'pageFetchState',
				'options' => $pageFetchStateOptions
			],
			'crawledAsOptions'      => [
				'name'    => 'crawledAs',
				'options' => $crawledAsOptions
			],
		];

		return [
			'table' => [
				'additionalFilters' => $additionalFilters
			]
		];
	}
}Sitemap.php000066600000007546151150354630006702 0ustar00<?php
namespace AIOSEO\Plugin\Common\SearchStatistics;

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

/**
 * Handles the sitemaps for the search statistics.
 *
 * @since 4.6.2
 */
class Sitemap {
	/**
	 * The action name.
	 *
	 * @since 4.6.2
	 *
	 * @var string
	 */
	public $action = 'aioseo_search_statistics_sitemap_sync';

	/**
	 * Class constructor.
	 *
	 * @since 4.6.2
	 */
	public function __construct() {
		add_action( 'admin_init', [ $this, 'init' ] );
		add_action( $this->action, [ $this, 'worker' ] );
	}

	/**
	 * Initialize the class.
	 *
	 * @since 4.6.2
	 *
	 * @return void
	 */
	public function init() {
		if (
			! aioseo()->searchStatistics->api->auth->isConnected() ||
			! aioseo()->internalOptions->searchStatistics->site->verified ||
			aioseo()->actionScheduler->isScheduled( $this->action )
		) {
			return;
		}

		aioseo()->actionScheduler->scheduleAsync( $this->action );
	}

	/**
	 * Sync the sitemap.
	 *
	 * @since 4.6.3
	 *
	 * @return void
	 */
	public function worker() {
		if ( ! $this->canSync() ) {
			return;
		}

		$api      = new Api\Request( 'google-search-console/sitemap/sync/', [ 'sitemaps' => aioseo()->sitemap->helpers->getSitemapUrls() ] );
		$response = $api->request();

		if ( is_wp_error( $response ) || empty( $response['data'] ) ) {
			// If it failed to communicate with the server, try again in a few hours.
			aioseo()->actionScheduler->scheduleSingle( $this->action, wp_rand( HOUR_IN_SECONDS, 2 * HOUR_IN_SECONDS ), [], true );

			return;
		}

		aioseo()->internalOptions->searchStatistics->sitemap->list      = $response['data'];
		aioseo()->internalOptions->searchStatistics->sitemap->lastFetch = time();

		// Schedule a new sync for the next week.
		aioseo()->actionScheduler->scheduleSingle( $this->action, WEEK_IN_SECONDS + wp_rand( 0, 3 * DAY_IN_SECONDS ), [], true );
	}

	/**
	 * Maybe sync the sitemap after updating the options.
	 * It will check whether the sitemap options have changed and sync the sitemap if needed.
	 *
	 * @since 4.6.2
	 *
	 * @param array $oldSitemapOptions The old sitemap options.
	 * @param array $newSitemapOptions The new sitemap options.
	 *
	 * @return void
	 */
	public function maybeSync( $oldSitemapOptions, $newSitemapOptions ) {
		if (
			! $this->canSync() ||
			empty( $oldSitemapOptions ) ||
			empty( $newSitemapOptions )
		) {
			return;
		}

		// Ignore the HTML sitemap, since it's not actually a sitemap to be synced with Google.
		unset( $newSitemapOptions['html'] );

		$shouldResync = false;
		foreach ( $newSitemapOptions as $type => $options ) {
			if ( empty( $oldSitemapOptions[ $type ] ) ) {
				continue;
			}

			if ( $oldSitemapOptions[ $type ]['enable'] !== $options['enable'] ) {
				$shouldResync = true;
				break;
			}
		}

		if ( ! $shouldResync ) {
			return;
		}

		aioseo()->actionScheduler->unschedule( $this->action );
		aioseo()->actionScheduler->scheduleAsync( $this->action );
	}

	/**
	 * Get the sitemaps with errors.
	 *
	 * @since 4.6.2
	 *
	 * @return array
	 */
	public function getSitemapsWithErrors() {
		$sitemaps = aioseo()->internalOptions->searchStatistics->sitemap->list;
		$ignored  = aioseo()->internalOptions->searchStatistics->sitemap->ignored;
		if ( empty( $sitemaps ) ) {
			return [];
		}

		$errors         = [];
		$pluginSitemaps = aioseo()->sitemap->helpers->getSitemapUrls();
		foreach ( $sitemaps as $sitemap ) {
			if (
				empty( $sitemap['errors'] ) ||
				in_array( $sitemap['path'], $ignored, true ) || // Skip user-ignored sitemaps.
				in_array( $sitemap['path'], $pluginSitemaps, true ) // Skip plugin sitemaps.
			) {
				continue;
			}

			$errors[] = $sitemap;
		}

		return $errors;
	}

	/**
	 * Check if the sitemap can be synced.
	 *
	 * @since 4.6.2
	 *
	 * @return bool
	 */
	private function canSync() {
		return aioseo()->searchStatistics->api->auth->isConnected() && aioseo()->internalOptions->searchStatistics->site->verified;
	}
}SearchStatistics.php000066600000075513151150354630010557 0ustar00<?php
namespace AIOSEO\Plugin\Common\SearchStatistics;

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

/**
 * Class that holds our Search Statistics feature.
 *
 * @since 4.3.0
 */
class SearchStatistics {
	/**
	 * Holds the instance of the API class.
	 *
	 * @since 4.3.0
	 *
	 * @var Api\Api
	 */
	public $api;

	/**
	 * Holds the instance of the Site class.
	 *
	 * @since 4.6.2
	 *
	 * @var Site
	 */
	public $site;

	/**
	 * Holds the instance of the Sitemap class.
	 *
	 * @since 4.6.2
	 *
	 * @var Sitemap
	 */
	public $sitemap;

	/**
	 * Holds the instance of the Notices class.
	 *
	 * @since 4.6.2
	 *
	 * @var Notices
	 */
	public $notices;

	/**
	 * Holds the instance of the Keyword Rank Tracker class.
	 *
	 * @since 4.7.0
	 *
	 * @var KeywordRankTracker
	 */
	public $keywordRankTracker;

	/**
	 * Holds the instance of the Index Status class.
	 *
	 * @since 4.8.2
	 *
	 * @var IndexStatus
	 */
	public $indexStatus;

	/**
	 * Class constructor.
	 *
	 * @since 4.3.0
	 */
	public function __construct() {
		$this->api                = new Api\Api();
		$this->site               = new Site();
		$this->sitemap            = new Sitemap();
		$this->notices            = new Notices();
		$this->keywordRankTracker = new KeywordRankTracker();
		$this->indexStatus        = new IndexStatus();
	}

	/**
	 * Returns the data for Vue.
	 *
	 * @since 4.3.0
	 *
	 * @return array The data for Vue.
	 */
	public function getVueData() {
		$data = [
			'isConnected'         => aioseo()->searchStatistics->api->auth->isConnected(),
			'latestAvailableDate' => null,
			'range'               => [],
			'rolling'             => aioseo()->internalOptions->internal->searchStatistics->rolling,
			'authedSite'          => aioseo()->searchStatistics->api->auth->getAuthedSite(),
			'sitemapsWithErrors'  => aioseo()->searchStatistics->sitemap->getSitemapsWithErrors(),
			'data'                => [
				'seoStatistics'   => $this->getSeoOverviewData(),
				'keywords'        => $this->getKeywordsData(),
				'contentRankings' => $this->getContentRankingsData()
			]
		];

		return $data;
	}

	/**
	 * Resets the Search Statistics.
	 *
	 * @since 4.6.2
	 *
	 * @return void
	 */
	public function reset() {
		aioseo()->internalOptions->searchStatistics->sitemap->reset();
		aioseo()->internalOptions->searchStatistics->site->reset();

		// Clear the cache for the Search Statistics.
		aioseo()->searchStatistics->clearCache();
	}

	/**
	 * Returns the data for the SEO Overview.
	 *
	 * @since 4.3.0
	 *
	 * @param  array $dateRange The date range.
	 * @return array            The data for the SEO Overview.
	 */
	protected function getSeoOverviewData( $dateRange = [] ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
		$pageRows = [
			'/'                       => [
				'ctr'              => '1.25',
				'page'             => '/',
				'clicks'           => 15460,
				'position'         => '74.01',
				'difference'       => [
					'ctr'         => '-0.23',
					'decay'       => 192211,
					'clicks'      => -26,
					'current'     => true,
					'position'    => '19.66',
					'comparison'  => true,
					'impressions' => 192237
				],
				'impressions'      => 1235435,
				'context'          => [],
				'objectId'         => 0,
				'objectTitle'      => '/',
				'objectType'       => 'post',
				'inspectionResult' => $this->getInspectionResult(),
				'seoScore'         => 65
			],
			'/test-page/'             => [
				'ctr'              => '0.30',
				'page'             => '/test-page/',
				'clicks'           => 5688,
				'position'         => '35.28',
				'difference'       => [
					'ctr'         => '0.05',
					'decay'       => 378973,
					'clicks'      => 1941,
					'current'     => true,
					'position'    => '-2.33',
					'comparison'  => true,
					'impressions' => 377032
				],
				'impressions'      => 1881338,
				'context'          => [],
				'objectId'         => 0,
				'objectTitle'      => '/test-page/',
				'objectType'       => 'post',
				'inspectionResult' => $this->getInspectionResult(),
				'seoScore'         => 95
			],
			'/high-ranking-page/'     => [
				'ctr'              => '6.03',
				'page'             => '/high-ranking-page/',
				'clicks'           => 3452,
				'position'         => '22.85',
				'difference'       => [
					'ctr'         => '-0.95',
					'decay'       => -5986,
					'clicks'      => -898,
					'current'     => true,
					'position'    => '-0.22',
					'comparison'  => true,
					'impressions' => -5088
				],
				'impressions'      => 57248,
				'context'          => [],
				'objectId'         => 0,
				'objectTitle'      => '/high-ranking-page/',
				'objectType'       => 'post',
				'inspectionResult' => $this->getInspectionResult(),
				'seoScore'         => 100
			],
			'/pricing/'               => [
				'ctr'              => '1.35',
				'page'             => '/pricing/',
				'clicks'           => 2749,
				'position'         => '40.40',
				'difference'       => [
					'ctr'         => '-0.16',
					'decay'       => 15991,
					'clicks'      => -94,
					'current'     => true,
					'position'    => '9.77',
					'comparison'  => true,
					'impressions' => 16085
				],
				'impressions'      => 203794,
				'context'          => [],
				'objectId'         => 0,
				'objectTitle'      => '/pricing/',
				'objectType'       => 'post',
				'inspectionResult' => $this->getInspectionResult(),
				'seoScore'         => 100
			],
			'/features-and-benefits/' => [
				'ctr'              => '2.48',
				'page'             => '/features-and-benefits/',
				'clicks'           => 2600,
				'position'         => '15.53',
				'difference'       => [
					'ctr'         => '0.99',
					'decay'       => 23466,
					'clicks'      => 1367,
					'current'     => true,
					'position'    => '1.51',
					'comparison'  => true,
					'impressions' => 22099
				],
				'impressions'      => 104769,
				'context'          => [],
				'objectId'         => 0,
				'objectTitle'      => '/features-and-benefits/',
				'objectType'       => 'post',
				'inspectionResult' => $this->getInspectionResult(),
				'seoScore'         => 90
			],
			'/documentation/'         => [
				'ctr'              => '2.64',
				'page'             => '/documentation/',
				'clicks'           => 1716,
				'position'         => '27.85',
				'difference'       => [
					'ctr'         => '-0.04',
					'decay'       => 7274,
					'clicks'      => 167,
					'current'     => true,
					'position'    => '-9.51',
					'comparison'  => true,
					'impressions' => 7107
				],
				'impressions'      => 64883,
				'context'          => [],
				'objectId'         => 0,
				'objectTitle'      => '/documentation/',
				'objectType'       => 'post',
				'inspectionResult' => $this->getInspectionResult(),
				'seoScore'         => 93
			],
			'/blog/'                  => [
				'ctr'              => '3.75',
				'page'             => '/blog/',
				'clicks'           => 1661,
				'position'         => '36.60',
				'difference'       => [
					'ctr'         => '0.42',
					'decay'       => -3145,
					'clicks'      => 77,
					'current'     => true,
					'position'    => '-9.40',
					'comparison'  => true,
					'impressions' => -3222
				],
				'impressions'      => 44296,
				'context'          => [],
				'objectId'         => 0,
				'objectTitle'      => '/blog/',
				'objectType'       => 'post',
				'inspectionResult' => $this->getInspectionResult(),
				'seoScore'         => 97
			],
			'/blog/my-best-content/'  => [
				'ctr'              => '7.08',
				'page'             => '/blog/my-best-content/',
				'clicks'           => 1573,
				'position'         => '9.61',
				'difference'       => [
					'ctr'         => '0.16',
					'decay'       => -201,
					'clicks'      => 22,
					'current'     => true,
					'position'    => '-2.03',
					'comparison'  => true,
					'impressions' => -223
				],
				'impressions'      => 22203,
				'context'          => [],
				'objectId'         => 0,
				'objectTitle'      => '/blog/my-best-content/',
				'objectType'       => 'post',
				'inspectionResult' => $this->getInspectionResult(),
				'seoScore'         => 56
			],
			'/contact-us/'            => [
				'ctr'              => '1.45',
				'page'             => '/contact-us/',
				'clicks'           => 1550,
				'position'         => '32.05',
				'difference'       => [
					'ctr'         => '0.12',
					'decay'       => 1079,
					'clicks'      => 140,
					'current'     => true,
					'position'    => '-3.47',
					'comparison'  => true,
					'impressions' => 939
				],
				'impressions'      => 106921,
				'context'          => [],
				'objectId'         => 0,
				'objectTitle'      => '/contact-us/',
				'objectType'       => 'post',
				'inspectionResult' => $this->getInspectionResult(),
				'seoScore'         => 78
			],
			'/support/'               => [
				'ctr'              => '5.94',
				'page'             => '/support/',
				'clicks'           => 1549,
				'position'         => '25.83',
				'difference'       => [
					'ctr'         => '-0.74',
					'decay'       => 3885,
					'clicks'      => 62,
					'current'     => true,
					'position'    => '-1.48',
					'comparison'  => true,
					'impressions' => 3823
				],
				'impressions'      => 26099,
				'context'          => [],
				'objectId'         => 0,
				'objectTitle'      => '/support/',
				'objectType'       => 'post',
				'inspectionResult' => $this->getInspectionResult(),
				'seoScore'         => 86
			]
		];

		// Get the 10 most recent posts.
		$recentPosts = aioseo()->db->db->get_results(
			sprintf(
				'SELECT ID, post_title FROM %1$s WHERE post_status = "publish" AND post_type = "post" ORDER BY post_date DESC LIMIT 10',
				aioseo()->db->db->posts
			)
		);

		// Loop through the default page rows and update the key with the permalink from the most recent posts.
		$i = 0;
		foreach ( $pageRows as $key => $pageRow ) {
			// Get the permalink of the recent post that matches the $i index.
			$permalink = isset( $recentPosts[ $i ] ) ? get_permalink( $recentPosts[ $i ]->ID ) : '';

			// If we don't have a permalink, continue to the next row.
			if ( empty( $permalink ) ) {
				continue;
			}

			// Remove the domain from the permalink by parsing the URL and getting the path.
			$permalink = wp_parse_url( $permalink, PHP_URL_PATH );

			// If the permalink already exists, continue to the next row.
			if ( isset( $pageRows[ $permalink ] ) ) {
				// Update the objectId and objectTitle with the recent post ID and title.
				$pageRows[ $permalink ]['objectId']    = $recentPosts[ $i ]->ID;
				$pageRows[ $permalink ]['objectTitle'] = $recentPosts[ $i ]->post_title;

				continue;
			}

			$pageRows[ $permalink ] = $pageRows[ $key ];

			// Remove the old key.
			unset( $pageRows[ $key ] );

			// Update the objectId and objectTitle with the recent post ID and title.
			$pageRows[ $permalink ]['objectId']    = $recentPosts[ $i ]->ID;
			$pageRows[ $permalink ]['objectTitle'] = $recentPosts[ $i ]->post_title;

			$i++;
		}

		return [
			'statistics' => [
				'ctr'         => '0.74',
				'clicks'      => 111521,
				'keywords'    => 19335,
				'position'    => '49.28',
				'difference'  => [
					'ctr'         => '0.03',
					'clicks'      => 1736,
					'keywords'    => 2853,
					'position'    => '1.01',
					'impressions' => -475679
				],
				'impressions' => 14978074
			],
			'intervals'  => [
				[
					'ctr'         => '0.72',
					'date'        => '2022-10-23',
					'clicks'      => 7091,
					'position'    => '48.88',
					'impressions' => 985061
				],
				[
					'ctr'         => '0.77',
					'date'        => '2022-10-30',
					'clicks'      => 8544,
					'position'    => '46.48',
					'impressions' => 1111602
				],
				[
					'ctr'         => '0.73',
					'date'        => '2022-11-06',
					'clicks'      => 9087,
					'position'    => '48.44',
					'impressions' => 1247506
				],
				[
					'ctr'         => '0.75',
					'date'        => '2022-11-13',
					'clicks'      => 9952,
					'position'    => '50.03',
					'impressions' => 1326910
				],
				[
					'ctr'         => '0.73',
					'date'        => '2022-11-20',
					'clicks'      => 9696,
					'position'    => '50.28',
					'impressions' => 1324633
				],
				[
					'ctr'         => '0.69',
					'date'        => '2022-11-27',
					'clicks'      => 9590,
					'position'    => '51.03',
					'impressions' => 1382602
				],
				[
					'ctr'         => '0.71',
					'date'        => '2022-12-04',
					'clicks'      => 9691,
					'position'    => '51.07',
					'impressions' => 1365509
				],
				[
					'ctr'         => '0.71',
					'date'        => '2022-12-11',
					'clicks'      => 9291,
					'position'    => '51.22',
					'impressions' => 1316184
				],
				[
					'ctr'         => '0.80',
					'date'        => '2022-12-18',
					'clicks'      => 8659,
					'position'    => '48.20',
					'impressions' => 1081944
				],
				[
					'ctr'         => '0.75',
					'date'        => '2022-12-25',
					'clicks'      => 6449,
					'position'    => '49.31',
					'impressions' => 857591
				],
				[
					'ctr'         => '0.66',
					'date'        => '2023-01-01',
					'clicks'      => 5822,
					'position'    => '48.16',
					'impressions' => 876828
				],
				[
					'ctr'         => '0.77',
					'date'        => '2023-01-08',
					'clicks'      => 7501,
					'position'    => '47.34',
					'impressions' => 975764
				],
				[
					'ctr'         => '0.90',
					'date'        => '2023-01-16',
					'clicks'      => 10148,
					'position'    => '48.29',
					'impressions' => 1125940
				]
			],
			'pages'      => [
				'topPages'   => [
					'rows' => $pageRows
				],
				'paginated'  => [
					'rows'              => $pageRows,
					'totals'            => [
						'page'  => 1,
						'pages' => 1,
						'total' => 10
					],
					'filters'           => [
						[
							'slug'   => 'all',
							'name'   => 'All',
							'active' => true
						],
						[
							'slug'   => 'topLosing',
							'name'   => 'Top Losing',
							'active' => false
						],
						[
							'slug'   => 'topWinning',
							'name'   => 'Top Winning',
							'active' => false
						]
					],
					'additionalFilters' => [
						[
							'name'    => 'postType',
							'options' => [
								[
									'label' => __( 'All Content Types', 'all-in-one-seo-pack' ),
									'value' => ''
								]
							]
						]
					]
				],
				'topLosing'  => [
					'rows' => $pageRows
				],
				'topWinning' => [
					'rows' => $pageRows
				]
			]
		];
	}

	/**
	 * Returns the data for the Keywords.
	 *
	 * @since 4.3.0
	 *
	 * @param  array $args The arguments.
	 * @return array       The data for the Keywords.
	 */
	public function getKeywordsData( $args = [] ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
		$keywordsRows = [
			[
				'ctr'         => '4.89',
				'clicks'      => 5000,
				'keyword'     => 'best seo plugin',
				'position'    => '1.93',
				'difference'  => [
					'ctr'         => '-1.06',
					'decay'       => 6590,
					'clicks'      => -652,
					'position'    => '0.07',
					'impressions' => 7242
				],
				'impressions' => 102233
			],
			[
				'ctr'         => '7.06',
				'clicks'      => 4404,
				'keyword'     => 'aioseo is the best',
				'position'    => '1.32',
				'difference'  => [
					'ctr'         => '0.13',
					'decay'       => 8586,
					'clicks'      => 633,
					'position'    => '0.12',
					'impressions' => 7953
				],
				'impressions' => 62357
			],
			[
				'ctr'         => '2.81',
				'clicks'      => 1715,
				'keyword'     => 'analyze my seo',
				'position'    => '6.29',
				'difference'  => [
					'ctr'         => '-0.03',
					'decay'       => 13217,
					'clicks'      => 347,
					'position'    => '-0.34',
					'impressions' => 12870
				],
				'impressions' => 61102
			],
			[
				'ctr'         => '7.46',
				'clicks'      => 717,
				'keyword'     => 'wordpress seo',
				'position'    => '1.18',
				'difference'  => [
					'ctr'         => '-0.69',
					'decay'       => 2729,
					'clicks'      => 144,
					'position'    => '-0.08',
					'impressions' => 2585
				],
				'impressions' => 9614
			],
			[
				'ctr'         => '6.66',
				'clicks'      => 446,
				'keyword'     => 'best seo plugin pro',
				'position'    => '1.65',
				'difference'  => [
					'ctr'         => '0.36',
					'decay'       => -121,
					'clicks'      => 16,
					'position'    => '0.33',
					'impressions' => -137
				],
				'impressions' => 6693
			],
			[
				'ctr'         => '7.39',
				'clicks'      => 409,
				'keyword'     => 'aioseo wordpress',
				'position'    => '1.77',
				'difference'  => [
					'ctr'         => '-0.39',
					'decay'       => 534,
					'clicks'      => 19,
					'position'    => '-0.13',
					'impressions' => 515
				],
				'impressions' => 5531
			],
			[
				'ctr'         => '1.11',
				'clicks'      => 379,
				'keyword'     => 'headline analyzer aioseo',
				'position'    => '8.41',
				'difference'  => [
					'ctr'         => '0.43',
					'decay'       => 134,
					'clicks'      => 147,
					'position'    => '-1.36',
					'impressions' => -13
				],
				'impressions' => 34043
			],
			[
				'ctr'         => '2.63',
				'clicks'      => 364,
				'keyword'     => 'best seo plugin plugin',
				'position'    => '2.38',
				'difference'  => [
					'ctr'         => '0.06',
					'decay'       => 836,
					'clicks'      => 29,
					'position'    => '0.20',
					'impressions' => 807
				],
				'impressions' => 13837
			],
			[
				'ctr'         => '1.52',
				'clicks'      => 326,
				'keyword'     => 'best seo plugin pack',
				'position'    => '4.14',
				'difference'  => [
					'ctr'         => '-0.19',
					'decay'       => -1590,
					'clicks'      => -66,
					'position'    => '0.64',
					'impressions' => -1524
				],
				'impressions' => 21450
			],
			[
				'ctr'         => '6.70',
				'clicks'      => 264,
				'keyword'     => 'youtube title analyzer aioseo',
				'position'    => '7.19',
				'difference'  => [
					'ctr'         => '4.73',
					'decay'       => 3842,
					'clicks'      => 257,
					'position'    => '-4.18',
					'impressions' => 3585
				],
				'impressions' => 3940
			]
		];

		return [
			'paginated'             => [
				'rows'    => $keywordsRows,
				'totals'  => [
					'page'  => 1,
					'pages' => 1,
					'total' => 10
				],
				'filters' => [
					[
						'slug'   => 'all',
						'name'   => 'All',
						'active' => true
					],
					[
						'slug'   => 'topLosing',
						'name'   => 'Top Losing',
						'active' => false
					],
					[
						'slug'   => 'topWinning',
						'name'   => 'Top Winning',
						'active' => false
					]
				]
			],
			'topLosing'             => $keywordsRows,
			'topWinning'            => $keywordsRows,
			'topKeywords'           => $keywordsRows,
			'distribution'          => [
				'top3'       => '6.86',
				'top10'      => '11.03',
				'top50'      => '52.10',
				'top100'     => '30.01',
				'difference' => [
					'top3'   => '24.31',
					'top10'  => '33.70',
					'top50'  => '-30.50',
					'top100' => '-27.51'
				]
			],
			'distributionIntervals' => [
				[
					'date'   => '2022-10-23',
					'top3'   => '30.70',
					'top10'  => '38.60',
					'top50'  => '24.50',
					'top100' => '6.20'
				],
				[
					'date'   => '2022-10-30',
					'top3'   => '31.60',
					'top10'  => '42.10',
					'top50'  => '21.00',
					'top100' => '5.30'
				],
				[
					'date'   => '2022-11-06',
					'top3'   => '31.30',
					'top10'  => '44.40',
					'top50'  => '20.30',
					'top100' => '4.00'
				],
				[
					'date'   => '2022-11-13',
					'top3'   => '31.70',
					'top10'  => '44.20',
					'top50'  => '20.20',
					'top100' => '3.90'
				],
				[
					'date'   => '2022-11-20',
					'top3'   => '31.70',
					'top10'  => '45.70',
					'top50'  => '18.00',
					'top100' => '4.60'
				],
				[
					'date'   => '2022-11-27',
					'top3'   => '32.50',
					'top10'  => '47.80',
					'top50'  => '16.80',
					'top100' => '2.90'
				],
				[
					'date'   => '2022-12-04',
					'top3'   => '32.50',
					'top10'  => '47.20',
					'top50'  => '17.90',
					'top100' => '2.40'
				],
				[
					'date'   => '2022-12-11',
					'top3'   => '31.80',
					'top10'  => '43.70',
					'top50'  => '21.00',
					'top100' => '3.50'
				],
				[
					'date'   => '2022-12-18',
					'top3'   => '30.40',
					'top10'  => '43.60',
					'top50'  => '22.40',
					'top100' => '3.60'
				],
				[
					'date'   => '2022-12-25',
					'top3'   => '26.90',
					'top10'  => '37.20',
					'top50'  => '29.70',
					'top100' => '6.20'
				],
				[
					'date'   => '2023-01-01',
					'top3'   => '27.00',
					'top10'  => '33.80',
					'top50'  => '31.60',
					'top100' => '7.60'
				],
				[
					'date'   => '2023-01-08',
					'top3'   => '26.60',
					'top10'  => '38.60',
					'top50'  => '30.00',
					'top100' => '4.80'
				],
				[
					'date'   => '2023-01-16',
					'top3'   => '31.10',
					'top10'  => '43.90',
					'top50'  => '22.50',
					'top100' => '2.50'
				]
			]
		];
	}

	/**
	 * Returns the content performance data.
	 *
	 * @since 4.7.2
	 *
	 * @return array The content performance data.
	 */
	public function getSeoStatisticsData( $args = [] ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
		return [];
	}

	/**
	 * Returns the Content Rankings data.
	 *
	 * @since 4.3.6
	 *
	 * @param  array $args The arguments.
	 * @return array       The Content Rankings data.
	 */
	public function getContentRankingsData( $args = [] ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
		return [
			'paginated' => [
				'rows'              => [
					'/'                       => [
						'points'           => [
							'2022-04' => 13655,
							'2022-05' => 12683,
							'2022-06' => 13923,
							'2022-07' => 13031,
							'2022-08' => 10978,
							'2022-09' => 9726,
							'2022-10' => 13943,
							'2022-11' => 21813,
							'2022-12' => 11163,
							'2023-01' => 4442,
							'2023-02' => 4798,
							'2023-03' => 5405
						],
						'page'             => '/',
						'peak'             => 21813,
						'decayPercent'     => 75,
						'decay'            => 16407,
						'recovering'       => false,
						'context'          => [
							'lastUpdated' => 'December 6, 2021'
						],
						'objectTitle'      => 'Homepage',
						'objectType'       => 'post',
						'inspectionResult' => $this->getInspectionResult(),
						'objectId'         => 0
					],
					'/high-ranking-page/'     => [
						'points'           => [
							'2022-04' => 18426,
							'2022-05' => 18435,
							'2022-06' => 19764,
							'2022-07' => 14769,
							'2022-08' => 6486,
							'2022-09' => 11984,
							'2022-10' => 11539,
							'2022-11' => 9939,
							'2022-12' => 5340,
							'2023-01' => 3965,
							'2023-02' => 3799,
							'2023-03' => 5440
						],
						'page'             => '/high-ranking-page/',
						'peak'             => 19764,
						'decayPercent'     => 72,
						'decay'            => 14323,
						'recovering'       => false,
						'context'          => [
							'lastUpdated' => 'November 17, 2022'
						],
						'objectTitle'      => 'High Ranking Page',
						'objectType'       => 'post',
						'inspectionResult' => $this->getInspectionResult(),
						'objectId'         => 0
					],
					'/pricing/'               => [
						'points'           => [
							'2022-04' => 5356,
							'2022-05' => 5425,
							'2022-06' => 5165,
							'2022-07' => 5479,
							'2022-08' => 4995,
							'2022-09' => 4466,
							'2022-10' => 4545,
							'2022-11' => 5361,
							'2022-12' => 3092,
							'2023-01' => 1948,
							'2023-02' => 1630,
							'2023-03' => 2341
						],
						'page'             => '/pricing/',
						'peak'             => 5479,
						'decayPercent'     => 57,
						'decay'            => 3137,
						'recovering'       => false,
						'context'          => [
							'lastUpdated' => 'December 8, 2021'
						],
						'objectTitle'      => 'Pricing',
						'objectType'       => 'post',
						'inspectionResult' => $this->getInspectionResult(),
						'objectId'         => 0
					],
					'/features-and-benefits/' => [
						'points'           => [
							'2022-04' => 1272,
							'2022-05' => 4151,
							'2022-06' => 6953,
							'2022-07' => 7785,
							'2022-08' => 4177,
							'2022-09' => 3378,
							'2022-10' => 2553,
							'2022-11' => 3971,
							'2022-12' => 2143,
							'2023-01' => 2335,
							'2023-02' => 1666,
							'2023-03' => 4892
						],
						'page'             => '/features-and-benefits/',
						'peak'             => 7785,
						'decayPercent'     => 37,
						'decay'            => 2893,
						'recovering'       => false,
						'context'          => [
							'lastUpdated' => 'February 7, 2022'
						],
						'objectTitle'      => 'Features and Benefits',
						'objectType'       => 'post',
						'inspectionResult' => $this->getInspectionResult(),
						'objectId'         => 0
					],
					'/documentation/'         => [
						'points'           => [
							'2022-04' => 594,
							'2022-05' => 385,
							'2022-06' => 337,
							'2022-07' => 378,
							'2022-08' => 714,
							'2022-09' => 2637,
							'2022-10' => 2831,
							'2022-11' => 2907,
							'2022-12' => 1851,
							'2023-01' => 277,
							'2023-02' => 226,
							'2023-03' => 175
						],
						'page'             => '/documentation/',
						'peak'             => 2907,
						'decayPercent'     => 93,
						'decay'            => 2731,
						'recovering'       => false,
						'context'          => [
							'lastUpdated' => 'January 7, 2022'
						],
						'objectTitle'      => 'Documentation',
						'objectType'       => 'post',
						'inspectionResult' => $this->getInspectionResult(),
						'objectId'         => 0
					],
					'/blog/'                  => [
						'points'           => [
							'2022-04' => 2956,
							'2022-05' => 2363,
							'2022-06' => 2347,
							'2022-07' => 2154,
							'2022-08' => 2604,
							'2022-09' => 1995,
							'2022-10' => 1528,
							'2022-11' => 1578,
							'2022-12' => 1458,
							'2023-01' => 927,
							'2023-02' => 629,
							'2023-03' => 592
						],
						'page'             => '/blog/',
						'peak'             => 2956,
						'decayPercent'     => 79,
						'decay'            => 2363,
						'recovering'       => false,
						'context'          => [
							'lastUpdated' => 'April 21, 2022'
						],
						'objectTitle'      => 'Blog',
						'objectType'       => 'post',
						'inspectionResult' => $this->getInspectionResult(),
						'objectId'         => 0
					],
					'/blog/my-best-content/'  => [
						'points'           => [
							'2022-04' => 1889,
							'2022-05' => 1714,
							'2022-06' => 2849,
							'2022-07' => 4175,
							'2022-08' => 5343,
							'2022-09' => 6360,
							'2022-10' => 6492,
							'2022-11' => 6955,
							'2022-12' => 6930,
							'2023-01' => 5880,
							'2023-02' => 5211,
							'2023-03' => 4683
						],
						'page'             => '/blog/my-best-content/',
						'peak'             => 6955,
						'decayPercent'     => 32,
						'decay'            => 2272,
						'recovering'       => false,
						'context'          => [
							'lastUpdated' => 'April 22, 2022'
						],
						'objectTitle'      => 'My Best Content',
						'objectType'       => 'post',
						'inspectionResult' => $this->getInspectionResult(),
						'objectId'         => 0
					],
					'/contact-us/'            => [
						'points'           => [
							'2022-04' => 3668,
							'2022-05' => 3699,
							'2022-06' => 4934,
							'2022-07' => 5488,
							'2022-08' => 5092,
							'2022-09' => 5526,
							'2022-10' => 4694,
							'2022-11' => 4791,
							'2022-12' => 3989,
							'2023-01' => 4089,
							'2023-02' => 4189,
							'2023-03' => 4289
						],
						'page'             => '/contact-us/',
						'peak'             => 5526,
						'decayPercent'     => 34,
						'decay'            => 1907,
						'recovering'       => true,
						'context'          => [
							'lastUpdated' => 'January 28, 2022'
						],
						'objectTitle'      => 'Contact Us',
						'objectType'       => 'post',
						'inspectionResult' => $this->getInspectionResult(),
						'objectId'         => 0
					],
					'/support/'               => [
						'points'           => [
							'2022-04' => 2715,
							'2022-05' => 2909,
							'2022-06' => 2981,
							'2022-07' => 2988,
							'2022-08' => 2586,
							'2022-09' => 2592,
							'2022-10' => 2391,
							'2022-11' => 2446,
							'2022-12' => 2045,
							'2023-01' => 1239,
							'2023-02' => 1077,
							'2023-03' => 1198
						],
						'page'             => '/support/',
						'peak'             => 2988,
						'decayPercent'     => 59,
						'decay'            => 1789,
						'recovering'       => false,
						'context'          => [
							'lastUpdated' => 'February 21, 2021'
						],
						'objectTitle'      => 'Support',
						'objectType'       => 'post',
						'inspectionResult' => $this->getInspectionResult(),
						'objectId'         => 0
					],
					'/blog/top-10-contents/'  => [
						'points'           => [
							'2022-04' => 1889,
							'2022-05' => 1714,
							'2022-06' => 2849,
							'2022-07' => 4175,
							'2022-08' => 5343,
							'2022-09' => 6360,
							'2022-10' => 6492,
							'2022-11' => 6955,
							'2022-12' => 6930,
							'2023-01' => 5880,
							'2023-02' => 5211,
							'2023-03' => 4683
						],
						'page'             => '/blog/top-10-contents/',
						'peak'             => 6955,
						'decayPercent'     => 32,
						'decay'            => 2272,
						'recovering'       => false,
						'context'          => [
							'lastUpdated' => 'October 14, 2022'
						],
						'objectTitle'      => 'Top 10 Contents',
						'objectType'       => 'post',
						'inspectionResult' => $this->getInspectionResult(),
						'objectId'         => 0
					],
				],
				'totals'            => [
					'page'  => 1,
					'pages' => 1,
					'total' => 10
				],
				'additionalFilters' => [
					[
						'name'    => 'postType',
						'options' => [
							[
								'label' => __( 'All Content Types', 'all-in-one-seo-pack' ),
								'value' => ''
							]
						]
					]
				]
			]
		];
	}

	/**
	 * Get minimum required values for the inspection result.
	 *
	 * @since 4.5.0
	 *
	 * @return array The inspection result.
	 */
	private function getInspectionResult() {
		$verdicts = [
			'PASS',
			'FAIL',
			'NEUTRAL'
		];

		return [
			'indexStatusResult' => [
				'verdict' => $verdicts[ array_rand( $verdicts ) ],
			]
		];
	}

	/**
	 * Clears the Search Statistics cache.
	 *
	 * @since   4.5.0
	 * @version 4.6.2 Moved from Pro to Common.
	 *
	 * @return void
	 */
	public function clearCache() {
		aioseo()->core->cache->clearPrefix( 'aioseo_search_statistics_' );
		aioseo()->core->cache->clearPrefix( 'search_statistics_' );
	}

	/**
	 * Returns all scheduled Search Statistics related actions.
	 *
	 * @since 4.6.2
	 *
	 * @return array The Search Statistics actions.
	 */
	protected function getActionSchedulerActions() {
		return [
			$this->site->action,
			$this->sitemap->action
		];
	}

	/**
	 * Cancels all scheduled Search Statistics related actions.
	 *
	 * @since   4.3.3
	 * @version 4.6.2 Moved from Pro to Common.
	 *
	 * @return void
	 */
	public function cancelActions() {
		foreach ( $this->getActionSchedulerActions() as $actionName ) {
			as_unschedule_all_actions( $actionName );
		}
	}
}Api/Request.php000066600000022526151150354630007434 0ustar00<?php
namespace AIOSEO\Plugin\Common\SearchStatistics\Api;

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

/**
 * Constructs requests to our microservice.
 *
 * @since   4.3.0
 * @version 4.6.2 Moved from Pro to Common.
 */
class Request {
	/**
	 * The base API route.
	 *
	 * @since 4.3.0
	 *
	 * @var string
	 */
	private $base = '';

	/**
	 * The URL scheme.
	 *
	 * @since 4.3.0
	 *
	 * @var string
	 */
	private $scheme = 'https://';

	/**
	 * The current API route.
	 *
	 * @since 4.3.0
	 *
	 * @var string
	 */
	private $route = '';

	/**
	 * The full API URL endpoint.
	 *
	 * @since 4.3.0
	 *
	 * @var string
	 */
	private $url = '';

	/**
	 * The current API method.
	 *
	 * @since 4.3.0
	 *
	 * @var string
	 */
	private $method = '';

	/**
	 * The API token.
	 *
	 * @since 4.3.0
	 *
	 * @var string
	 */
	private $token = '';

	/**
	 * The API key.
	 *
	 * @since 4.3.0
	 *
	 * @var string
	 */
	private $key = '';

	/**
	 * The API trust token.
	 *
	 * @since 4.3.0
	 *
	 * @var string
	 */
	private $tt = '';

	/**
	 * Plugin slug.
	 *
	 * @since 4.3.0
	 *
	 * @var bool|string
	 */
	private $plugin = false;

	/**
	 * URL to test connection with.
	 *
	 * @since 4.3.0
	 *
	 * @var string
	 */
	private $testurl = '';

	/**
	 * The site URL.
	 *
	 * @since 4.3.0
	 *
	 * @var string
	 */
	private $siteurl = '';

	/**
	 * The plugin version.
	 *
	 * @since 4.3.0
	 *
	 * @var string
	 */
	private $version = '';

	/**
	 * The site identifier.
	 *
	 * @since 4.3.0
	 *
	 * @var string
	 */
	private $sitei = '';

	/**
	 * The request args.
	 *
	 * @since 4.3.0
	 *
	 * @var array
	 */
	private $args = [];

	/**
	 * Additional data to append to request body.
	 *
	 * @since 4.3.0
	 *
	 * @var array
	 */
	protected $additionalData = [];

	/**
	 * Class constructor.
	 *
	 * @since 4.3.0
	 *
	 * @param string $route  The API route.
	 * @param array  $args   List of arguments.
	 * @param string $method The API method.
	 */
	public function __construct( $route, $args = [], $method = 'POST' ) {
		$this->base    = trailingslashit( aioseo()->searchStatistics->api->getApiUrl() ) . trailingslashit( aioseo()->searchStatistics->api->getApiVersion() );
		$this->route   = trailingslashit( $route );
		$this->url     = trailingslashit( $this->scheme . $this->base . $this->route );
		$this->method  = $method;
		$this->token   = ! empty( $args['token'] ) ? $args['token'] : aioseo()->searchStatistics->api->auth->getToken();
		$this->key     = ! empty( $args['key'] ) ? $args['key'] : aioseo()->searchStatistics->api->auth->getKey();
		$this->tt      = ! empty( $args['tt'] ) ? $args['tt'] : '';
		$this->args    = ! empty( $args ) ? $args : [];
		$this->siteurl = site_url();
		$this->plugin  = 'aioseo-' . strtolower( aioseo()->versionPath );
		$this->version = aioseo()->version;
		$this->sitei   = ! empty( $args['sitei'] ) ? $args['sitei'] : '';
		$this->testurl = ! empty( $args['testurl'] ) ? $args['testurl'] : '';
	}

	/**
	 * Sends and processes the API request.
	 *
	 * @since 4.3.0
	 *
	 * @return mixed The response.
	 */
	public function request() {
		// Make sure we're not blocked.
		$blocked = $this->isBlocked( $this->url );
		if ( is_wp_error( $blocked ) ) {
			return new \WP_Error(
				'api-error',
				sprintf(
					'The firewall of the server is blocking outbound calls. Please contact your hosting provider to fix this issue. %s',
					$blocked->get_error_message()
				)
			);
		}

		// 1. BUILD BODY
		$body = [];
		if ( ! empty( $this->args ) ) {
			foreach ( $this->args as $name => $value ) {
				$body[ $name ] = $value;
			}
		}

		foreach ( [ 'sitei', 'siteurl', 'version', 'key', 'token', 'tt' ] as $key ) {
			if ( ! empty( $this->{$key} ) ) {
				$body[ $key ] = $this->{$key};
			}
		}

		// If this is a plugin API request, add the data.
		if ( 'info' === $this->route || 'update' === $this->route ) {
			$body['aioseoapi-plugin'] = $this->plugin;
		}

		// Add in additional data if needed.
		if ( ! empty( $this->additionalData ) ) {
			$body['aioseoapi-data'] = maybe_serialize( $this->additionalData );
		}

		if ( 'GET' === $this->method ) {
			$body['time'] = time(); // Add a timestamp to avoid caching.
		}

		$body['timezone']        = gmdate( 'e' );
		$body['ip']              = ! empty( $_SERVER['SERVER_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_ADDR'] ) ) : '';

		// 2. SET HEADERS
		$headers = array_merge( [
			'Content-Type'      => 'application/json',
			'Cache-Control'     => 'no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0',
			'Pragma'            => 'no-cache',
			'Expires'           => 0,
			'AIOSEOAPI-Referer' => site_url(),
			'AIOSEOAPI-Sender'  => 'WordPress',
			'X-AIOSEO-Key'      => aioseo()->internalOptions->internal->siteAnalysis->connectToken,
		], aioseo()->helpers->getApiHeaders() );

		// 3. COMPILE REQUEST DATA
		$data = [
			'headers'    => $headers,
			'body'       => wp_json_encode( $body ),
			'timeout'    => 3000,
			'user-agent' => aioseo()->helpers->getApiUserAgent()
		];

		// 4. EXECUTE REQUEST
		if ( 'GET' === $this->method ) {
			$queryString = http_build_query( $body, '', '&' );

			unset( $data['body'] );

			$response = wp_remote_get( esc_url_raw( $this->url ) . '?' . $queryString, $data );
		} else {
			$response = wp_remote_post( esc_url_raw( $this->url ), $data );
		}

		// 5. VALIDATE RESPONSE
		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$responseCode = wp_remote_retrieve_response_code( $response );
		$responseBody = json_decode( wp_remote_retrieve_body( $response ), true );

		if ( is_wp_error( $responseBody ) ) {
			return false;
		}

		if ( 200 !== $responseCode ) {
			$type = ! empty( $responseBody['type'] ) ? $responseBody['type'] : 'api-error';

			if ( empty( $responseCode ) ) {
				return new \WP_Error(
					$type,
					'The API was unreachable.'
				);
			}

			if ( empty( $responseBody ) || ( empty( $responseBody['message'] ) && empty( $responseBody['error'] ) ) ) {
				return new \WP_Error(
					$type,
					sprintf(
						'The API returned a <strong>%s</strong> response',
						$responseCode
					)
				);
			}

			if ( ! empty( $responseBody['message'] ) ) {
				return new \WP_Error(
					$type,
					sprintf(
						'The API returned a <strong>%1$d</strong> response with this message: <strong>%2$s</strong>',
						$responseCode, stripslashes( $responseBody['message'] )
					)
				);
			}

			if ( ! empty( $responseBody['error'] ) ) {
				return new \WP_Error(
					$type,
					sprintf(
						'The API returned a <strong>%1$d</strong> response with this message: <strong>%2$s</strong>', $responseCode,
						stripslashes( $responseBody['error'] )
					)
				);
			}
		}

		// Check if the trust token is required.
		if (
			! empty( $this->tt ) &&
			( empty( $responseBody['tt'] ) || ! hash_equals( $this->tt, $responseBody['tt'] ) )
		) {
			return new \WP_Error( 'validation-error', 'Invalid API request.' );
		}

		return $responseBody;
	}

	/**
	 * Sets additional data for the request.
	 *
	 * @since 4.3.0
	 *
	 * @param  array $data The additional data.
	 * @return void
	 */
	public function setAdditionalData( array $data ) {
		$this->additionalData = array_merge( $this->additionalData, $data );
	}

	/**
	 * Checks if the given URL is blocked for a request.
	 *
	 * @since 4.3.0
	 *
	 * @param  string         $url The URL to test against.
	 * @return bool|\WP_Error      False or WP_Error if it is blocked.
	 */
	private function isBlocked( $url = '' ) {
		// The below page is a test HTML page used for firewall/router login detection
		// and for image linking purposes in Google Images. We use it to test outbound connections
		// It's on Google's main CDN so it loads in most countries in 0.07 seconds or less. Perfect for our
		// use case of testing outbound connections.
		$testurl = ! empty( $this->testurl ) ? $this->testurl : 'https://www.google.com/blank.html';
		if ( defined( 'WP_HTTP_BLOCK_EXTERNAL' ) && WP_HTTP_BLOCK_EXTERNAL ) {
			if ( defined( 'WP_ACCESSIBLE_HOSTS' ) ) {
				$wpHttp      = new \WP_Http();
				$onBlacklist = $wpHttp->block_request( $url );
				if ( $onBlacklist ) {
					return new \WP_Error( 'api-error', 'The API was unreachable because the API url is on the WP HTTP blocklist.' );
				} else {
					$params = [
						'sslverify'  => false,
						'timeout'    => 2,
						'user-agent' => aioseo()->helpers->getApiUserAgent(),
						'body'       => ''
					];

					$response = wp_remote_get( $testurl, $params );
					if ( ! is_wp_error( $response ) && $response['response']['code'] >= 200 && $response['response']['code'] < 300 ) {
						return false;
					} else {
						if ( is_wp_error( $response ) ) {
							return $response;
						} else {
							return new \WP_Error( 'api-error', 'The API was unreachable because the call to Google failed.' );
						}
					}
				}
			} else {
				return new \WP_Error( 'api-error', 'The API was unreachable because no external hosts are allowed on this site.' );
			}
		} else {
			$params = [
				'sslverify'  => false,
				'timeout'    => 2,
				'user-agent' => aioseo()->helpers->getApiUserAgent(),
				'body'       => ''
			];

			$response = wp_remote_get( $testurl, $params );
			if ( ! is_wp_error( $response ) && $response['response']['code'] >= 200 && $response['response']['code'] < 300 ) {
				return false;
			} else {
				if ( is_wp_error( $response ) ) {
					return $response;
				} else {
					return new \WP_Error( 'api-error', 'The API was unreachable because the call to Google failed.' );
				}
			}
		}
	}
}Api/Api.php000066600000004550151150354630006512 0ustar00<?php
namespace AIOSEO\Plugin\Common\SearchStatistics\Api;

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

/**
 * API class.
 *
 * @since   4.3.0
 * @version 4.6.2 Moved from Pro to Common.
 */
class Api {
	/**
	 * Holds the instance of the Auth class.
	 *
	 * @since 4.3.0
	 *
	 * @var Auth
	 */
	public $auth;

	/**
	 * Holds the instance of the TrustToken class.
	 *
	 * @since 4.3.0
	 *
	 * @var TrustToken
	 */
	public $trustToken;

	/**
	 * Holds the instance of the Listener class.
	 *
	 * @since 4.3.0
	 *
	 * @var Listener
	 */
	public $listener;

	/**
	 * The base URL for the Search Statistics microservice.
	 *
	 * @since 4.3.0
	 *
	 * @var string
	 */
	private $url = 'google.aioseo.com';

	/**
	 * The API version for the Search Statistics microservice.
	 *
	 * @since 4.3.0
	 *
	 * @var string
	 */
	private $version = 'v1';

	/**
	 * Class constructor.
	 *
	 * @since 4.3.0
	 */
	public function __construct() {
		$this->auth       = new Auth();
		$this->trustToken = new TrustToken();
		$this->listener   = new Listener();
	}

	/**
	 * Returns the site identifier key according to the WordPress keys.
	 *
	 * @since 4.3.0
	 *
	 * @return string The site identifier key.
	 */
	public function getSiteIdentifier() {
		$authKey       = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
		$secureAuthKey = defined( 'SECURE_AUTH_KEY' ) ? SECURE_AUTH_KEY : '';
		$loggedInKey   = defined( 'LOGGED_IN_KEY' ) ? LOGGED_IN_KEY : '';

		$siteIdentifier = $authKey . $secureAuthKey . $loggedInKey;
		$siteIdentifier = preg_replace( '/[^a-zA-Z0-9]/', '', (string) $siteIdentifier );
		$siteIdentifier = sanitize_text_field( $siteIdentifier );
		$siteIdentifier = trim( $siteIdentifier );
		$siteIdentifier = ( strlen( $siteIdentifier ) > 30 ) ? substr( $siteIdentifier, 0, 30 ) : $siteIdentifier;

		return $siteIdentifier;
	}

	/**
	 * Returns the URL of the remote endpoint.
	 *
	 * @since 4.3.0
	 *
	 * @return string The URL.
	 */
	public function getApiUrl() {
		if ( defined( 'AIOSEO_SEARCH_STATISTICS_API_URL' ) ) {
			return AIOSEO_SEARCH_STATISTICS_API_URL;
		}

		return $this->url;
	}

	/**
	 * Returns the version of the remote endpoint.
	 *
	 * @since 4.3.0
	 *
	 * @return string The version.
	 */
	public function getApiVersion() {
		if ( defined( 'AIOSEO_SEARCH_STATISTICS_API_VERSION' ) ) {
			return AIOSEO_SEARCH_STATISTICS_API_VERSION;
		}

		return $this->version;
	}
}Api/Listener.php000066600000016145151150354630007571 0ustar00<?php
namespace AIOSEO\Plugin\Common\SearchStatistics\Api;

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

// phpcs:disable WordPress.Security.NonceVerification.Recommended
// phpcs:disable HM.Security.NonceVerification.Recommended

/**
 * Class that holds our listeners for the microservice.
 *
 * @since   4.3.0
 * @version 4.6.2 Moved from Pro to Common.
 */
class Listener {
	/**
	 * Class constructor.
	 *
	 * @since 4.3.0
	 */
	public function __construct() {
		add_action( 'admin_init', [ $this, 'listenForAuthentication' ] );
		add_action( 'admin_init', [ $this, 'listenForReauthentication' ] );
		add_action( 'admin_init', [ $this, 'listenForReturningBack' ] );

		add_action( 'wp_ajax_nopriv_aioseo_is_installed', [ $this, 'isInstalled' ] );
		add_action( 'wp_ajax_nopriv_aioseo_rauthenticate', [ $this, 'reauthenticate' ] );
	}

	/**
	 * Listens to the response from the microservice server.
	 *
	 * @since 4.3.0
	 *
	 * @return void
	 */
	public function listenForAuthentication() {
		if ( empty( $_REQUEST['aioseo-oauth-action'] ) || 'auth' !== $_REQUEST['aioseo-oauth-action'] ) {
			return;
		}

		if (
			! aioseo()->access->hasCapability( 'aioseo_search_statistics_settings' ) ||
			! aioseo()->access->hasCapability( 'aioseo_general_settings' ) ||
			! aioseo()->access->hasCapability( 'aioseo_setup_wizard' )
		) {
			return;
		}

		if ( empty( $_REQUEST['tt'] ) || empty( $_REQUEST['key'] ) || empty( $_REQUEST['token'] ) || empty( $_REQUEST['authedsite'] ) ) {
			return;
		}

		if ( ! aioseo()->searchStatistics->api->trustToken->validate( sanitize_text_field( wp_unslash( $_REQUEST['tt'] ) ) ) ) {
			return;
		}

		$profile = [
			'key'        => sanitize_text_field( wp_unslash( $_REQUEST['key'] ) ),
			'token'      => sanitize_text_field( wp_unslash( $_REQUEST['token'] ) ),
			'siteurl'    => site_url(),
			'authedsite' => esc_url_raw( wp_unslash( $this->getAuthenticatedDomain() ) )
		];

		$success = aioseo()->searchStatistics->api->auth->verify( $profile );
		if ( ! $success ) {
			return;
		}

		$this->saveAndRedirect( $profile );
	}

	/**
	 * Listens to for the reauthentication response from the microservice.
	 *
	 * @since 4.3.0
	 *
	 * @return void
	 */
	public function listenForReauthentication() {
		if ( empty( $_REQUEST['aioseo-oauth-action'] ) || 'reauth' !== $_REQUEST['aioseo-oauth-action'] ) {
			return;
		}

		if (
			! aioseo()->access->hasCapability( 'aioseo_search_statistics_settings' ) ||
			! aioseo()->access->hasCapability( 'aioseo_general_settings' ) ||
			! aioseo()->access->hasCapability( 'aioseo_setup_wizard' )
		) {
			return;
		}

		if ( empty( $_REQUEST['tt'] ) || empty( $_REQUEST['authedsite'] ) ) {
			return;
		}

		if ( ! aioseo()->searchStatistics->api->trustToken->validate( sanitize_text_field( wp_unslash( $_REQUEST['tt'] ) ) ) ) {
			return;
		}

		$existingProfile = aioseo()->searchStatistics->api->auth->getProfile( true );
		if ( empty( $existingProfile['key'] ) || empty( $existingProfile['token'] ) ) {
			return;
		}

		$profile = [
			'key'        => $existingProfile['key'],
			'token'      => $existingProfile['token'],
			'siteurl'    => site_url(),
			'authedsite' => esc_url_raw( wp_unslash( $this->getAuthenticatedDomain() ) )
		];

		$this->saveAndRedirect( $profile );
	}

	/**
	 * Listens for the response from the microservice when the user returns back.
	 *
	 * @since 4.6.2
	 *
	 * @return void
	 */
	public function listenForReturningBack() {
		if ( empty( $_REQUEST['aioseo-oauth-action'] ) || 'back' !== $_REQUEST['aioseo-oauth-action'] ) {
			return;
		}

		if (
			! aioseo()->access->hasCapability( 'aioseo_search_statistics_settings' ) ||
			! aioseo()->access->hasCapability( 'aioseo_general_settings' ) ||
			! aioseo()->access->hasCapability( 'aioseo_setup_wizard' )
		) {
			return;
		}

		wp_safe_redirect( $this->getRedirectUrl() );
		exit;
	}

	/**
	 * Return a success status code indicating that the plugin is installed.
	 *
	 * @since 4.3.0
	 *
	 * @return void
	 */
	public function isInstalled() {
		wp_send_json_success( [
			'version' => aioseo()->version,
			'pro'     => aioseo()->pro
		] );
	}

	/**
	 * Validate the trust token and tells the microservice that we can reauthenticate.
	 *
	 * @since 4.3.0
	 *
	 * @return void
	 */
	public function reauthenticate() {
		foreach ( [ 'key', 'token', 'tt' ] as $arg ) {
			if ( empty( $_REQUEST[ $arg ] ) ) {
				wp_send_json_error( [
					'error'   => 'authenticate_missing_arg',
					'message' => 'Authentication request missing parameter: ' . $arg,
					'version' => aioseo()->version,
					'pro'     => aioseo()->pro
				] );
			}
		}

		$trustToken = ! empty( $_REQUEST['tt'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['tt'] ) ) : '';
		if ( ! aioseo()->searchStatistics->api->trustToken->validate( $trustToken ) ) {
			wp_send_json_error( [
				'error'   => 'authenticate_invalid_tt',
				'message' => 'Invalid TT sent',
				'version' => aioseo()->version,
				'pro'     => aioseo()->pro
			] );
		}

		// If the trust token is validated, send a success response to trigger the regular auth process.
		wp_send_json_success();
	}

	/**
	 * Saves the authenticated account, clear the existing data and redirect back to the settings page.
	 *
	 * @since 4.3.0
	 *
	 * @return void
	 */
	private function saveAndRedirect( $profile ) {
		// Reset the search statistics data.
		aioseo()->searchStatistics->reset();

		// Save the authenticated profile.
		aioseo()->searchStatistics->api->auth->setProfile( $profile );

		// Reset dismissed alerts.
		$dismissedAlerts = aioseo()->settings->dismissedAlerts;
		foreach ( $dismissedAlerts as $key => $alert ) {
			if ( in_array( $key, [ 'searchConsoleNotConnected', 'searchConsoleSitemapErrors' ], true ) ) {
				$dismissedAlerts[ $key ] = false;
			}
		}
		aioseo()->settings->dismissedAlerts = $dismissedAlerts;

		// Maybe verifies the site.
		aioseo()->searchStatistics->site->maybeVerify();

		// Redirects to the original page.
		wp_safe_redirect( $this->getRedirectUrl() );
		exit;
	}

	/**
	 * Returns the authenticated domain.
	 *
	 * @since 4.3.0
	 *
	 * @return string The authenticated domain.
	 */
	private function getAuthenticatedDomain() {
		if ( empty( $_REQUEST['authedsite'] ) ) {
			return '';
		}

		$authedSite = sanitize_text_field( wp_unslash( $_REQUEST['authedsite'] ) );
		if ( false !== aioseo()->helpers->stringIndex( $authedSite, 'sc-domain:' ) ) {
			$authedSite = str_replace( 'sc-domain:', '', $authedSite );
		}

		return $authedSite;
	}

	/**
	 * Gets the redirect URL.
	 *
	 * @since 4.6.2
	 *
	 * @return string The redirect URL.
	 */
	private function getRedirectUrl() {
		$returnTo    = ! empty( $_REQUEST['return-to'] ) ? sanitize_key( $_REQUEST['return-to'] ) : '';
		$redirectUrl = 'admin.php?page=aioseo';

		switch ( $returnTo ) {
			case 'webmaster-tools':
				$redirectUrl = 'admin.php?page=aioseo-settings#/webmaster-tools?activetool=googleSearchConsole';
				break;
			case 'setup-wizard':
				$redirectUrl = 'index.php?page=aioseo-setup-wizard#/' . aioseo()->standalone->setupWizard->getNextStage();
				break;
			case 'search-statistics':
				$redirectUrl = 'admin.php?page=aioseo-search-statistics/#search-statistics';
				break;
		}

		return admin_url( $redirectUrl );
	}
}Api/TrustToken.php000066600000003001151150354630010111 0ustar00<?php
namespace AIOSEO\Plugin\Common\SearchStatistics\Api;

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

/**
 * Handles the trust token.
 *
 * @since   4.3.0
 * @version 4.6.2 Moved from Pro to Common.
 */
class TrustToken {
	/**
	 * Returns the trust token from the database or creates a new one & stores it.
	 *
	 * @since 4.3.0
	 *
	 * @return string The trust token.
	 */
	public function get() {
		$trustToken = aioseo()->internalOptions->internal->searchStatistics->trustToken;
		if ( empty( $trustToken ) ) {
			$trustToken = $this->generate();
			aioseo()->internalOptions->internal->searchStatistics->trustToken = $trustToken;
		}

		return $trustToken;
	}

	/**
	 * Rotates the trust token.
	 *
	 * @since 4.3.0
	 *
	 * @return void
	 */
	public function rotate() {
		$trustToken = $this->generate();
		aioseo()->internalOptions->internal->searchStatistics->trustToken = $trustToken;
	}

	/**
	 * Generates a new trust token.
	 *
	 * @since 4.3.0
	 *
	 * @return string The trust token.
	 */
	public function generate() {
		return hash( 'sha512', wp_generate_password( 128, true, true ) . uniqid( '', true ) );
	}

	/**
	 * Verifies whether the passed trust token is valid or not.
	 *
	 * @since 4.3.0
	 *
	 * @param  string $passedTrustToken The trust token to validate.
	 * @return bool                     Whether the trust token is valid or not.
	 */
	public function validate( $passedTrustToken = '' ) {
		$trustToken = $this->get();

		return hash_equals( $trustToken, $passedTrustToken );
	}
}Api/Auth.php000066600000010064151150354630006677 0ustar00<?php
namespace AIOSEO\Plugin\Common\SearchStatistics\Api;

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

/**
 * Handles the authentication/connection to our microservice.
 *
 * @since   4.3.0
 * @version 4.6.2 Moved from Pro to Common.
 */
class Auth {
	/**
	 * The authenticated profile data.
	 *
	 * @since 4.3.0
	 *
	 * @var array
	 */
	private $profile = [];

	/**
	 * The type of authentication.
	 *
	 * @since 4.6.2
	 *
	 * @var string
	 */
	public $type = 'lite';

	/**
	 * Class constructor.
	 *
	 * @since 4.3.0
	 */
	public function __construct() {
		$this->profile = $this->getProfile();

		if ( aioseo()->pro ) {
			$this->type = 'pro';
		}
	}

	/**
	 * Returns the authenticated profile.
	 *
	 * @since 4.3.0
	 *
	 * @param  bool  $force Busts the cache and forces an update of the profile data.
	 * @return array        The authenticated profile data.
	 */
	public function getProfile( $force = false ) {
		if ( ! empty( $this->profile ) && ! $force ) {
			return $this->profile;
		}

		$this->profile = aioseo()->internalOptions->internal->searchStatistics->profile;

		return $this->profile;
	}

	/**
	 * Returns the profile key.
	 *
	 * @since 4.3.0
	 *
	 * @return string The profile key.
	 */
	public function getKey() {
		return ! empty( $this->profile['key'] ) ? $this->profile['key'] : '';
	}

	/**
	 * Returns the profile token.
	 *
	 * @since 4.3.0
	 *
	 * @return string The profile token.
	 */
	public function getToken() {
		return ! empty( $this->profile['token'] ) ? $this->profile['token'] : '';
	}

	/**
	 * Returns the authenticated site.
	 *
	 * @since 4.3.0
	 *
	 * @return string The authenticated site.
	 */
	public function getAuthedSite() {
		return ! empty( $this->profile['authedsite'] ) ? $this->profile['authedsite'] : '';
	}

	/**
	 * Sets the profile data.
	 *
	 * @since 4.3.0
	 *
	 * @return void
	 */
	public function setProfile( $data = [] ) {
		$this->profile = $data;

		aioseo()->internalOptions->internal->searchStatistics->profile = $this->profile;
	}

	/**
	 * Deletes the profile data.
	 *
	 * @since 4.3.0
	 *
	 * @return void
	 */
	public function deleteProfile() {
		$this->setProfile( [] );
	}

	/**
	 * Check whether we are connected.
	 *
	 * @since 4.3.0
	 *
	 * @return bool Whether we are connected or not.
	 */
	public function isConnected() {
		return ! empty( $this->profile['key'] );
	}

	/**
	 * Verifies whether the authentication details are valid.
	 *
	 * @since 4.3.0
	 *
	 * @return bool Whether the data is valid or not.
	 */
	public function verify( $credentials = [] ) {
		$creds = ! empty( $credentials ) ? $credentials : aioseo()->internalOptions->internal->searchStatistics->profile;

		if ( empty( $creds['key'] ) ) {
			return new \WP_Error( 'validation-error', 'Authentication key is missing.' );
		}

		$request = new Request( "auth/verify/{$this->type}/", [
			'tt'      => aioseo()->searchStatistics->api->trustToken->get(),
			'key'     => $creds['key'],
			'token'   => $creds['token'],
			'testurl' => 'https://' . aioseo()->searchStatistics->api->getApiUrl() . '/v1/test/',
		] );
		$response = $request->request();

		aioseo()->searchStatistics->api->trustToken->rotate();

		return ! is_wp_error( $response );
	}

	/**
	 * Removes all authentication data.
	 *
	 * @since 4.3.0
	 *
	 * @return bool Whether the authentication data was deleted or not.
	 */
	public function delete() {
		if ( ! $this->isConnected() ) {
			return false;
		}

		$creds = aioseo()->searchStatistics->api->auth->getProfile( true );
		if ( empty( $creds['key'] ) ) {
			return false;
		}

		( new Request( "auth/delete/{$this->type}/", [
			'tt'      => aioseo()->searchStatistics->api->trustToken->get(),
			'key'     => $creds['key'],
			'token'   => $creds['token'],
			'testurl' => 'https://' . aioseo()->searchStatistics->api->getApiUrl() . '/v1/test/',
		] ) )->request();

		aioseo()->searchStatistics->api->trustToken->rotate();
		aioseo()->searchStatistics->api->auth->deleteProfile();
		aioseo()->searchStatistics->reset();

		// Resets the results for the Google meta tag.
		aioseo()->options->webmasterTools->google = '';

		return true;
	}
}