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/Content.php.tar

home/xbodynamge/namtation/wp-content/plugins/all-in-one-seo-pack/app/Common/Sitemap/Content.php000064400000074527151127004460026611 0ustar00<?php
namespace AIOSEO\Plugin\Common\Sitemap;

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

use AIOSEO\Plugin\Common\Integrations\BuddyPress as BuddyPressIntegration;

/**
 * Determines which content should be included in the sitemap.
 *
 * @since 4.0.0
 */
class Content {
	/**
	 * Returns the entries for the requested sitemap.
	 *
	 * @since 4.0.0
	 *
	 * @return array The sitemap entries.
	 */
	public function get() {
		if ( ! in_array( aioseo()->sitemap->type, [ 'general', 'rss' ], true ) || ! $this->isEnabled() ) {
			return [];
		}

		if ( 'rss' === aioseo()->sitemap->type ) {
			return $this->rss();
		}

		if ( 'general' !== aioseo()->sitemap->type ) {
			return [];
		}

		$indexesEnabled = aioseo()->options->sitemap->general->indexes;
		if ( ! $indexesEnabled ) {
			if ( 'root' === aioseo()->sitemap->indexName ) {
				// If indexes are disabled, throw all entries together into one big file.
				return $this->nonIndexed();
			}

			return [];
		}

		if ( 'root' === aioseo()->sitemap->indexName ) {
			return aioseo()->sitemap->root->indexes();
		}

		// Check if requested index has a dedicated method.
		$methodName = aioseo()->helpers->dashesToCamelCase( aioseo()->sitemap->indexName );
		if ( method_exists( $this, $methodName ) ) {
			return $this->$methodName();
		}

		// Check if requested index is a registered post type.
		if ( in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedPostTypes(), true ) ) {
			return $this->posts( aioseo()->sitemap->indexName );
		}

		// Check if requested index is a registered taxonomy.
		if (
			in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedTaxonomies(), true ) &&
			'product_attributes' !== aioseo()->sitemap->indexName
		) {
			return $this->terms( aioseo()->sitemap->indexName );
		}

		if (
			aioseo()->helpers->isWooCommerceActive() &&
			in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedTaxonomies(), true ) &&
			'product_attributes' === aioseo()->sitemap->indexName
		) {
			return $this->productAttributes();
		}

		return [];
	}

	/**
	 * Returns the total entries number for the requested sitemap.
	 *
	 * @since 4.1.5
	 *
	 * @return int The total entries number.
	 */
	public function getTotal() {
		if ( ! in_array( aioseo()->sitemap->type, [ 'general', 'rss' ], true ) || ! $this->isEnabled() ) {
			return 0;
		}

		if ( 'rss' === aioseo()->sitemap->type ) {
			return count( $this->rss() );
		}

		if ( 'general' !== aioseo()->sitemap->type ) {
			return 0;
		}

		$indexesEnabled = aioseo()->options->sitemap->general->indexes;
		if ( ! $indexesEnabled ) {
			if ( 'root' === aioseo()->sitemap->indexName ) {
				// If indexes are disabled, throw all entries together into one big file.
				return count( $this->nonIndexed() );
			}

			return 0;
		}

		if ( 'root' === aioseo()->sitemap->indexName ) {
			return count( aioseo()->sitemap->root->indexes() );
		}

		// Check if requested index has a dedicated method.
		$methodName = aioseo()->helpers->dashesToCamelCase( aioseo()->sitemap->indexName );
		if ( method_exists( $this, $methodName ) ) {
			$res = $this->$methodName();

			return ! empty( $res ) ? count( $res ) : 0;
		}

		// Check if requested index is a registered post type.
		if ( in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedPostTypes(), true ) ) {
			return aioseo()->sitemap->query->posts( aioseo()->sitemap->indexName, [ 'count' => true ] );
		}

		// Check if requested index is a registered taxonomy.
		if ( in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedTaxonomies(), true ) ) {
			return aioseo()->sitemap->query->terms( aioseo()->sitemap->indexName, [ 'count' => true ] );
		}

		return 0;
	}

	/**
	 * Checks if the requested sitemap is enabled.
	 *
	 * @since 4.0.0
	 *
	 * @return boolean Whether the sitemap is enabled.
	 */
	public function isEnabled() {
		$options = aioseo()->options->noConflict();
		if ( ! $options->sitemap->{aioseo()->sitemap->type}->enable ) {
			return false;
		}

		if ( $options->sitemap->{aioseo()->sitemap->type}->postTypes->all ) {
			return true;
		}

		$included = aioseo()->sitemap->helpers->includedPostTypes();

		return ! empty( $included );
	}

	/**
	 * Returns all sitemap entries if indexing is disabled.
	 *
	 * @since 4.0.0
	 *
	 * @return array $entries The sitemap entries.
	 */
	private function nonIndexed() {
		$additional       = $this->addl();
		$postTypes        = aioseo()->sitemap->helpers->includedPostTypes();
		$isStaticHomepage = 'page' === get_option( 'show_on_front' );
		$blogPageEntry    = [];
		$homePageEntry    = ! $isStaticHomepage ? [ array_shift( $additional ) ] : [];
		$entries          = array_merge( $additional, $this->author(), $this->date(), $this->postArchive() );

		if ( $postTypes ) {
			foreach ( $postTypes as $postType ) {
				$postTypeEntries = $this->posts( $postType );

				// If we don't have a static homepage, it's business as usual.
				if ( ! $isStaticHomepage ) {
					$entries = array_merge( $entries, $postTypeEntries );
					continue;
				}

				$homePageId = (int) get_option( 'page_on_front' );
				$blogPageId = (int) get_option( 'page_for_posts' );

				if ( 'post' === $postType && $blogPageId ) {
					$blogPageEntry[] = array_shift( $postTypeEntries );
				}

				if ( 'page' === $postType && $homePageId ) {
					$homePageEntry[] = array_shift( $postTypeEntries );
				}

				$entries = array_merge( $entries, $postTypeEntries );
			}
		}

		$taxonomies = aioseo()->sitemap->helpers->includedTaxonomies();
		if ( $taxonomies ) {
			foreach ( $taxonomies as $taxonomy ) {
				$entries = array_merge( $entries, $this->terms( $taxonomy ) );
			}
		}

		// Sort first by priority, then by last modified date.
		usort( $entries, function ( $a, $b ) {
			// If the priorities are equal, sort by last modified date.
			if ( $a['priority'] === $b['priority'] ) {
				return $a['lastmod'] > $b['lastmod'] ? -1 : 1;
			}

			return $a['priority'] > $b['priority'] ? -1 : 1;
		} );

		// Merge the arrays with the home page always first.
		return array_merge( $homePageEntry, $blogPageEntry, $entries );
	}

	/**
	 * Returns all post entries for a given post type.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $postType       The name of the post type.
	 * @param  array  $additionalArgs Any additional arguments for the post query.
	 * @return array                  The sitemap entries.
	 */
	public function posts( $postType, $additionalArgs = [] ) {
		$posts = aioseo()->sitemap->query->posts( $postType, $additionalArgs );
		if ( ! $posts ) {
			return [];
		}

		// Return if we're determining the root indexes.
		if ( ! empty( $additionalArgs['root'] ) && $additionalArgs['root'] ) {
			return $posts;
		}

		$entries          = [];
		$isStaticHomepage = 'page' === get_option( 'show_on_front' );
		$homePageId       = (int) get_option( 'page_on_front' );
		$excludeImages    = aioseo()->sitemap->helpers->excludeImages();
		foreach ( $posts as $post ) {
			$entry = [
				'loc'        => get_permalink( $post->ID ),
				'lastmod'    => aioseo()->helpers->dateTimeToIso8601( $this->getLastModified( $post ) ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'postTypes', $post, $postType ),
				'priority'   => aioseo()->sitemap->priority->priority( 'postTypes', $post, $postType ),
			];

			if ( ! $excludeImages ) {
				$entry['images'] = ! empty( $post->images ) ? json_decode( $post->images ) : [];
			}

			// Override priority/frequency for static homepage.
			if ( $isStaticHomepage && ( $homePageId === $post->ID || aioseo()->helpers->wpmlIsHomePage( $post->ID ) ) ) {
				$entry['loc']        = aioseo()->helpers->maybeRemoveTrailingSlash( aioseo()->helpers->wpmlHomeUrl( $post->ID ) ?: $entry['loc'] );
				$entry['changefreq'] = aioseo()->sitemap->priority->frequency( 'homePage' );
				$entry['priority']   = aioseo()->sitemap->priority->priority( 'homePage' );
			}

			$entries[] = apply_filters( 'aioseo_sitemap_post', $entry, $post->ID, $postType, 'post' );
		}

		// We can't remove the post type here because other plugins rely on it.
		return apply_filters( 'aioseo_sitemap_posts', $entries, $postType );
	}

	/**
	 * Returns all post archive entries.
	 *
	 * @since 4.0.0
	 *
	 * @return array $entries The sitemap entries.
	 */
	private function postArchive() {
		$entries = [];
		foreach ( aioseo()->sitemap->helpers->includedPostTypes( true ) as $postType ) {
			if (
				aioseo()->dynamicOptions->noConflict()->searchAppearance->archives->has( $postType ) &&
				! aioseo()->dynamicOptions->searchAppearance->archives->$postType->advanced->robotsMeta->default &&
				aioseo()->dynamicOptions->searchAppearance->archives->$postType->advanced->robotsMeta->noindex
			) {
				continue;
			}

			$post = aioseo()->core->db
				->start( aioseo()->core->db->db->posts . ' as p', true )
				->select( 'p.ID' )
				->where( 'p.post_status', 'publish' )
				->where( 'p.post_type', $postType )
				->limit( 1 )
				->run()
				->result();

			if ( ! $post ) {
				continue;
			}

			$url = get_post_type_archive_link( $postType );
			if ( $url ) {
				$entry = [
					'loc'        => $url,
					'lastmod'    => aioseo()->sitemap->helpers->lastModifiedPostTime( $postType ),
					'changefreq' => aioseo()->sitemap->priority->frequency( 'archive' ),
					'priority'   => aioseo()->sitemap->priority->priority( 'archive' ),
				];

				// To be consistent with our other entry filters, we need to pass the entry ID as well, but as null in this case.
				$entries[] = apply_filters( 'aioseo_sitemap_archive_entry', $entry, null, $postType, 'archive' );
			}
		}

		return apply_filters( 'aioseo_sitemap_post_archives', $entries );
	}

	/**
	 * Returns all term entries for a given taxonomy.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $taxonomy       The name of the taxonomy.
	 * @param  array  $additionalArgs Any additional arguments for the term query.
	 * @return array                  The sitemap entries.
	 */
	public function terms( $taxonomy, $additionalArgs = [] ) {
		$terms = aioseo()->sitemap->query->terms( $taxonomy, $additionalArgs );
		if ( ! $terms ) {
			return [];
		}

		// Get all registered post types for the taxonomy.
		$postTypes = [];
		foreach ( get_post_types() as $postType ) {
			$taxonomies = get_object_taxonomies( $postType );
			foreach ( $taxonomies as $name ) {
				if ( $taxonomy === $name ) {
					$postTypes[] = $postType;
				}
			}
		}

		// Return if we're determining the root indexes.
		if ( ! empty( $additionalArgs['root'] ) && $additionalArgs['root'] ) {
			return $terms;
		}

		$entries = [];
		foreach ( $terms as $term ) {
			$entry = [
				'loc'        => get_term_link( $term->term_id ),
				'lastmod'    => $this->getTermLastModified( $term ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'taxonomies', $term, $taxonomy ),
				'priority'   => aioseo()->sitemap->priority->priority( 'taxonomies', $term, $taxonomy ),
				'images'     => aioseo()->sitemap->image->term( $term )
			];

			$entries[] = apply_filters( 'aioseo_sitemap_term', $entry, $term->term_id, $term->taxonomy, 'term' );
		}

		return apply_filters( 'aioseo_sitemap_terms', $entries );
	}

	/**
	 * Returns the last modified date for a given term.
	 *
	 * @since 4.0.0
	 *
	 * @param  int|object $term The term data object.
	 * @return string           The lastmod timestamp.
	 */
	public function getTermLastModified( $term ) {
		$termRelationshipsTable = aioseo()->core->db->db->prefix . 'term_relationships';
		$termTaxonomyTable      = aioseo()->core->db->db->prefix . 'term_taxonomy';

		// If the term is an ID, get the term object.
		if ( is_numeric( $term ) ) {
			$term = aioseo()->helpers->getTerm( $term );
		}

		// First, check the count of the term. If it's 0, then we're dealing with a parent term that does not have
		// posts assigned to it. In this case, we need to get the last modified date of all its children.
		if ( empty( $term->count ) ) {
			$lastModified = aioseo()->core->db
				->start( aioseo()->core->db->db->posts . ' as p', true )
				->select( 'MAX(`p`.`post_modified_gmt`) as last_modified' )
				->where( 'p.post_status', 'publish' )
				->whereRaw( "
				( `p`.`ID` IN
					(
						SELECT CONVERT(`tr`.`object_id`, unsigned)
						FROM `$termRelationshipsTable` as tr
						JOIN `$termTaxonomyTable` as tt ON `tr`.`term_taxonomy_id` = `tt`.`term_taxonomy_id`
						WHERE `tt`.`term_id` IN
							(
								SELECT `tt`.`term_id`
								FROM `$termTaxonomyTable` as tt
								WHERE `tt`.`parent` = '{$term->term_id}'
							)
					)
				)" )
				->run()
				->result();
		} else {
			$lastModified = aioseo()->core->db
				->start( aioseo()->core->db->db->posts . ' as p', true )
				->select( 'MAX(`p`.`post_modified_gmt`) as last_modified' )
				->where( 'p.post_status', 'publish' )
				->whereRaw( "
				( `p`.`ID` IN
					(
						SELECT CONVERT(`tr`.`object_id`, unsigned)
						FROM `$termRelationshipsTable` as tr
						JOIN `$termTaxonomyTable` as tt ON `tr`.`term_taxonomy_id` = `tt`.`term_taxonomy_id`
						WHERE `tt`.`term_id` = '{$term->term_id}'
					)
				)" )
				->run()
				->result();
		}

		$lastModified = $lastModified[0]->last_modified ?? '';

		return aioseo()->helpers->dateTimeToIso8601( $lastModified );
	}

	/**
	 * Returns all additional pages.
	 *
	 * @since 4.0.0
	 *
	 * @param  bool  $shouldChunk Whether the entries should be chuncked. Is set to false when the static sitemap is generated.
	 * @return array              The sitemap entries.
	 */
	public function addl( $shouldChunk = true ) {
		$additionalPages = [];
		if ( aioseo()->options->sitemap->general->additionalPages->enable ) {
			$additionalPages = array_map( 'json_decode', aioseo()->options->sitemap->general->additionalPages->pages );
			$additionalPages = array_filter( $additionalPages, function( $additionalPage ) {
				return ! empty( $additionalPage->url );
			} );
		}

		$entries = [];
		foreach ( $additionalPages as $additionalPage ) {
			$entries[] = [
				'loc'        => $additionalPage->url,
				'lastmod'    => aioseo()->sitemap->helpers->lastModifiedAdditionalPage( $additionalPage ),
				'changefreq' => $additionalPage->frequency->value,
				'priority'   => $additionalPage->priority->value,
				'isTimezone' => true
			];
		}

		$postTypes             = aioseo()->sitemap->helpers->includedPostTypes();
		$shouldIncludeHomepage = 'posts' === get_option( 'show_on_front' ) || ! in_array( 'page', $postTypes, true );
		if ( $shouldIncludeHomepage ) {
			$frontPageId  = (int) get_option( 'page_on_front' );
			$frontPageUrl = aioseo()->helpers->localizedUrl( '/' );
			$post         = aioseo()->helpers->getPost( $frontPageId );

			$homepageEntry = [
				'loc'        => aioseo()->helpers->maybeRemoveTrailingSlash( $frontPageUrl ),
				'lastmod'    => $post ? aioseo()->helpers->dateTimeToIso8601( $this->getLastModified( $post ) ) : aioseo()->sitemap->helpers->lastModifiedPostTime(),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'homePage' ),
				'priority'   => aioseo()->sitemap->priority->priority( 'homePage' )
			];

			$translatedHomepages = aioseo()->helpers->wpmlHomePages();
			foreach ( $translatedHomepages as $languageCode => $translatedHomepage ) {
				if ( untrailingslashit( $translatedHomepage['url'] ) !== untrailingslashit( $homepageEntry['loc'] ) ) {
					$homepageEntry['languages'][] = [
						'language' => $languageCode,
						'location' => $translatedHomepage['url']
					];
				}
			}

			// Add homepage to the first position.
			array_unshift( $entries, $homepageEntry );
		}

		if ( aioseo()->options->sitemap->general->additionalPages->enable ) {
			$entries = apply_filters( 'aioseo_sitemap_additional_pages', $entries );
		}

		if ( empty( $entries ) ) {
			return [];
		}

		if ( aioseo()->options->sitemap->general->indexes && $shouldChunk ) {
			$entries = aioseo()->sitemap->helpers->chunkEntries( $entries );
			$entries = $entries[ aioseo()->sitemap->pageNumber ] ?? [];
		}

		return $entries;
	}

	/**
	 * Returns all author archive entries.
	 *
	 * @since 4.0.0
	 *
	 * @return array The sitemap entries.
	 */
	public function author() {
		if (
			! aioseo()->sitemap->helpers->lastModifiedPost() ||
			! aioseo()->options->sitemap->general->author ||
			! aioseo()->options->searchAppearance->archives->author->show ||
			(
				! aioseo()->options->searchAppearance->archives->author->advanced->robotsMeta->default &&
				aioseo()->options->searchAppearance->archives->author->advanced->robotsMeta->noindex
			) ||
			(
				aioseo()->options->searchAppearance->archives->author->advanced->robotsMeta->default &&
				(
					! aioseo()->options->searchAppearance->advanced->globalRobotsMeta->default &&
					aioseo()->options->searchAppearance->advanced->globalRobotsMeta->noindex
				)
			)
		) {
			return [];
		}

		// Allow users to filter the authors in case their sites use a membership plugin or have custom code that affect the authors on their site.
		// e.g. there might be additional roles/conditions that need to be checked here.
		$authors = apply_filters( 'aioseo_sitemap_authors', [] );
		if ( empty( $authors ) ) {
			$usersTableName = aioseo()->core->db->db->users; // We get the table name from WPDB since multisites share the same table.
			$authors        = aioseo()->core->db->start( "$usersTableName as u", true )
				->select( 'u.ID as ID, u.user_nicename as nicename, MAX(p.post_modified_gmt) as lastModified' )
				->join( 'posts as p', 'u.ID = p.post_author' )
				->where( 'p.post_status', 'publish' )
				->whereIn( 'p.post_type', aioseo()->sitemap->helpers->getAuthorPostTypes() )
				->groupBy( 'u.ID' )
				->orderBy( 'lastModified DESC' )
				->limit( aioseo()->sitemap->linksPerIndex, aioseo()->sitemap->pageNumber * aioseo()->sitemap->linksPerIndex )
				->run()
				->result();
		}

		if ( empty( $authors ) ) {
			return [];
		}

		$entries = [];
		foreach ( $authors as $authorData ) {
			$entry = [
				'loc'        => ! empty( $authorData->authorUrl )
					? $authorData->authorUrl
					: get_author_posts_url( $authorData->ID, $authorData->nicename ?: '' ),
				'lastmod'    => aioseo()->helpers->dateTimeToIso8601( $authorData->lastModified ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'author' ),
				'priority'   => aioseo()->sitemap->priority->priority( 'author' )
			];

			$entries[] = apply_filters( 'aioseo_sitemap_author_entry', $entry, $authorData->ID, $authorData->nicename, 'author' );
		}

		return apply_filters( 'aioseo_sitemap_author_archives', $entries );
	}

	/**
	 * Returns all data archive entries.
	 *
	 * @since 4.0.0
	 *
	 * @return array The sitemap entries.
	 */
	public function date() {
		if (
			! aioseo()->sitemap->helpers->lastModifiedPost() ||
			! aioseo()->options->sitemap->general->date ||
			! aioseo()->options->searchAppearance->archives->date->show ||
			(
				! aioseo()->options->searchAppearance->archives->date->advanced->robotsMeta->default &&
				aioseo()->options->searchAppearance->archives->date->advanced->robotsMeta->noindex
			) ||
			(
				aioseo()->options->searchAppearance->archives->date->advanced->robotsMeta->default &&
				(
					! aioseo()->options->searchAppearance->advanced->globalRobotsMeta->default &&
					aioseo()->options->searchAppearance->advanced->globalRobotsMeta->noindex
				)
			)
		) {
			return [];
		}

		$postsTable = aioseo()->core->db->db->posts;
		$dates      = aioseo()->core->db->execute(
			"SELECT
				YEAR(post_date) AS `year`,
				MONTH(post_date) AS `month`,
				post_date_gmt,
				post_modified_gmt
			FROM {$postsTable}
			WHERE post_type = 'post' AND post_status = 'publish'
			GROUP BY
				YEAR(post_date),
				MONTH(post_date)
			ORDER BY post_date ASC 
			LIMIT 50000",
			true
		)->result();

		if ( empty( $dates ) ) {
			return [];
		}

		$entries = [];
		$year    = '';
		foreach ( $dates as $date ) {
			$entry = [
				'lastmod'    => aioseo()->helpers->dateTimeToIso8601( $this->getLastModified( $date ) ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'date' ),
				'priority'   => aioseo()->sitemap->priority->priority( 'date' ),
			];

			// Include each year only once.
			if ( $year !== $date->year ) {
				$year         = $date->year;
				$entry['loc'] = get_year_link( $date->year );
				$entries[]    = apply_filters( 'aioseo_sitemap_date_entry', $entry, $date, 'year', 'date' );
			}

			$entry['loc'] = get_month_link( $date->year, $date->month );
			$entries[]    = apply_filters( 'aioseo_sitemap_date_entry', $entry, $date, 'month', 'date' );
		}

		return apply_filters( 'aioseo_sitemap_date_archives', $entries );
	}

	/**
	 * Returns all entries for the RSS Sitemap.
	 *
	 * @since 4.0.0
	 *
	 * @return array The sitemap entries.
	 */
	public function rss() {
		$posts = aioseo()->sitemap->query->posts(
			aioseo()->sitemap->helpers->includedPostTypes(),
			[ 'orderBy' => '`p`.`post_modified_gmt` DESC' ]
		);

		if ( ! count( $posts ) ) {
			return [];
		}

		$entries = [];
		foreach ( $posts as $post ) {
			$entry = [
				'guid'        => get_permalink( $post->ID ),
				'title'       => get_the_title( $post ),
				'description' => get_post_field( 'post_excerpt', $post->ID ),
				'pubDate'     => aioseo()->helpers->dateTimeToRfc822( $this->getLastModified( $post ) )
			];

			// If the entry is the homepage, we need to check if the permalink structure
			// does not have a trailing slash. If so, we need to strip it because WordPress adds it
			// regardless for the home_url() in get_page_link() which is used in the get_permalink() function.
			static $homeId = null;
			if ( null === $homeId ) {
				$homeId = get_option( 'page_for_posts' );
			}

			if ( aioseo()->helpers->getHomePageId() === $post->ID ) {
				$entry['guid'] = aioseo()->helpers->maybeRemoveTrailingSlash( $entry['guid'] );
			}

			$entries[] = apply_filters( 'aioseo_sitemap_post_rss', $entry, $post->ID, $post->post_type, 'post' );
		}

		usort( $entries, function( $a, $b ) {
			return $a['pubDate'] < $b['pubDate'] ? 1 : 0;
		});

		return apply_filters( 'aioseo_sitemap_rss', $entries );
	}

	/**
	 * Returns the last modified date for a given post.
	 *
	 * @since 4.6.3
	 *
	 * @param  object $post The post object.
	 *
	 * @return string The last modified date.
	 */
	public function getLastModified( $post ) {
		$publishDate      = $post->post_date_gmt;
		$lastModifiedDate = $post->post_modified_gmt;

		// Get the date which is the latest.
		return $lastModifiedDate > $publishDate ? $lastModifiedDate : $publishDate;
	}

	/**
	 * Returns all entries for the BuddyPress Activity Sitemap.
	 * This method is automagically called from {@see get()} if the current index name equals to 'bp-activity'
	 *
	 * @since 4.7.6
	 *
	 * @return array The sitemap entries.
	 */
	public function bpActivity() {
		$entries = [];
		if ( ! in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedPostTypes(), true ) ) {
			return $entries;
		}

		$postType = 'bp-activity';
		$query    = aioseo()->core->db
			->start( 'bp_activity as a' )
			->select( '`a`.`id`, `a`.`date_recorded`' )
			->whereRaw( "a.is_spam = 0 AND a.hide_sitewide = 0 AND a.type NOT IN ('activity_comment', 'last_activity')" )
			->limit( aioseo()->sitemap->linksPerIndex, aioseo()->sitemap->offset )
			->orderBy( 'a.date_recorded DESC' );

		$items = $query->run()
						->result();

		foreach ( $items as $item ) {
			$entry = [
				'loc'        => BuddyPressIntegration::getComponentSingleUrl( 'activity', $item->id ),
				'lastmod'    => aioseo()->helpers->dateTimeToIso8601( $item->date_recorded ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'postTypes', false, $postType ),
				'priority'   => aioseo()->sitemap->priority->priority( 'postTypes', false, $postType ),
			];

			$entries[] = apply_filters( 'aioseo_sitemap_post', $entry, $item->id, $postType );
		}

		$archiveUrl = BuddyPressIntegration::getComponentArchiveUrl( 'activity' );
		if (
			aioseo()->helpers->isUrl( $archiveUrl ) &&
			! in_array( $postType, aioseo()->helpers->getNoindexedObjects( 'archives' ), true )
		) {
			$lastMod = ! empty( $items[0] ) ? $items[0]->date_recorded : current_time( 'mysql' );
			$entry   = [
				'loc'        => $archiveUrl,
				'lastmod'    => $lastMod,
				'changefreq' => aioseo()->sitemap->priority->frequency( 'postTypes', false, $postType ),
				'priority'   => aioseo()->sitemap->priority->priority( 'postTypes', false, $postType ),
			];

			array_unshift( $entries, $entry );
		}

		return apply_filters( 'aioseo_sitemap_posts', $entries, $postType );
	}

	/**
	 * Returns all entries for the BuddyPress Group Sitemap.
	 * This method is automagically called from {@see get()} if the current index name equals to 'bp-group'
	 *
	 * @since 4.7.6
	 *
	 * @return array The sitemap entries.
	 */
	public function bpGroup() {
		$entries = [];
		if ( ! in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedPostTypes(), true ) ) {
			return $entries;
		}

		$postType = 'bp-group';
		$query    = aioseo()->core->db
			->start( 'bp_groups as g' )
			->select( '`g`.`id`, `g`.`date_created`, `gm`.`meta_value` as date_modified' )
			->leftJoin( 'bp_groups_groupmeta as gm', 'g.id = gm.group_id' )
			->whereRaw( "g.status = 'public' AND gm.meta_key = 'last_activity'" )
			->limit( aioseo()->sitemap->linksPerIndex, aioseo()->sitemap->offset )
			->orderBy( 'gm.meta_value DESC' )
			->orderBy( 'g.date_created DESC' );

		$items = $query->run()
						->result();

		foreach ( $items as $item ) {
			$lastMod = $item->date_modified ?: $item->date_created;
			$entry   = [
				'loc'        => BuddyPressIntegration::getComponentSingleUrl( 'group', BuddyPressIntegration::callFunc( 'bp_get_group_by', 'id', $item->id ) ),
				'lastmod'    => aioseo()->helpers->dateTimeToIso8601( $lastMod ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'postTypes', false, $postType ),
				'priority'   => aioseo()->sitemap->priority->priority( 'postTypes', false, $postType ),
			];

			$entries[] = apply_filters( 'aioseo_sitemap_post', $entry, $item->id, $postType );
		}

		$archiveUrl = BuddyPressIntegration::getComponentArchiveUrl( 'group' );
		if (
			aioseo()->helpers->isUrl( $archiveUrl ) &&
			! in_array( $postType, aioseo()->helpers->getNoindexedObjects( 'archives' ), true )
		) {
			$lastMod = ! empty( $items[0] ) ? $items[0]->date_modified : current_time( 'mysql' );
			$entry   = [
				'loc'        => $archiveUrl,
				'lastmod'    => $lastMod,
				'changefreq' => aioseo()->sitemap->priority->frequency( 'postTypes', false, $postType ),
				'priority'   => aioseo()->sitemap->priority->priority( 'postTypes', false, $postType ),
			];

			array_unshift( $entries, $entry );
		}

		return apply_filters( 'aioseo_sitemap_posts', $entries, $postType );
	}

	/**
	 * Returns all entries for the BuddyPress Member Sitemap.
	 * This method is automagically called from {@see get()} if the current index name equals to 'bp-member'
	 *
	 * @since 4.7.6
	 *
	 * @return array The sitemap entries.
	 */
	public function bpMember() {
		$entries = [];
		if ( ! in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedPostTypes(), true ) ) {
			return $entries;
		}

		$postType = 'bp-member';
		$query    = aioseo()->core->db
			->start( 'bp_activity as a' )
			->select( '`a`.`user_id` as id, `a`.`date_recorded`' )
			->whereRaw( "a.component = 'members' AND a.type = 'last_activity'" )
			->limit( aioseo()->sitemap->linksPerIndex, aioseo()->sitemap->offset )
			->orderBy( 'a.date_recorded DESC' );

		$items = $query->run()
			->result();

		foreach ( $items as $item ) {
			$entry = [
				'loc'        => BuddyPressIntegration::getComponentSingleUrl( 'member', $item->id ),
				'lastmod'    => aioseo()->helpers->dateTimeToIso8601( $item->date_recorded ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'postTypes', false, $postType ),
				'priority'   => aioseo()->sitemap->priority->priority( 'postTypes', false, $postType ),
			];

			$entries[] = apply_filters( 'aioseo_sitemap_post', $entry, $item->id, $postType );
		}

		$archiveUrl = BuddyPressIntegration::getComponentArchiveUrl( 'member' );
		if (
			aioseo()->helpers->isUrl( $archiveUrl ) &&
			! in_array( $postType, aioseo()->helpers->getNoindexedObjects( 'archives' ), true )
		) {
			$lastMod = ! empty( $items[0] ) ? $items[0]->date_recorded : current_time( 'mysql' );
			$entry   = [
				'loc'        => $archiveUrl,
				'lastmod'    => $lastMod,
				'changefreq' => aioseo()->sitemap->priority->frequency( 'postTypes', false, $postType ),
				'priority'   => aioseo()->sitemap->priority->priority( 'postTypes', false, $postType ),
			];

			array_unshift( $entries, $entry );
		}

		return apply_filters( 'aioseo_sitemap_posts', $entries, $postType );
	}

	/**
	 * Returns all entries for the WooCommerce Product Attributes sitemap.
	 * Note: This sitemap does not support pagination.
	 *
	 * @since 4.7.8
	 *
	 * @param  bool  $count Whether to return the count of the entries. This is used to determine the indexes.
	 * @return array        The sitemap entries.
	 */
	public function productAttributes( $count = false ) {
		$aioseoTermsTable           = aioseo()->core->db->prefix . 'aioseo_terms';
		$wcAttributeTaxonomiesTable = aioseo()->core->db->prefix . 'woocommerce_attribute_taxonomies';
		$termTaxonomyTable          = aioseo()->core->db->prefix . 'term_taxonomy';

		$selectClause = 'COUNT(*) as childProductAttributes';
		if ( ! $count ) {
			$selectClause = aioseo()->pro ? 'tt.term_id, tt.taxonomy, at.frequency, at.priority' : 'tt.term_id, tt.taxonomy';
		}

		$joinClause   = aioseo()->pro ? "LEFT JOIN {$aioseoTermsTable} AS at ON tt.term_id = at.term_id" : '';
		$whereClause  = aioseo()->pro ? 'AND (at.robots_noindex IS NULL OR at.robots_noindex = 0)' : '';
		$limitClause  = $count ? '' : 'LIMIT 50000';

		$result = aioseo()->core->db->execute(
			"SELECT {$selectClause}
			FROM {$termTaxonomyTable} AS tt
			JOIN {$wcAttributeTaxonomiesTable} AS wat ON tt.taxonomy = CONCAT('pa_', wat.attribute_name)
			{$joinClause}
			WHERE wat.attribute_public = 1
				{$whereClause}
				AND tt.count > 0
			{$limitClause};",
			true
		)->result();

		if ( $count ) {
			return ! empty( $result[0]->childProductAttributes ) ? (int) $result[0]->childProductAttributes : 0;
		}

		if ( empty( $result ) ) {
			return [];
		}

		$entries = [];
		foreach ( $result as $term ) {
			$term   = (object) $term;
			$termId = (int) $term->term_id;

			$entry = [
				'loc'        => get_term_link( $termId ),
				'lastmod'    => $this->getTermLastModified( $termId ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'taxonomies', $term, 'product_attributes' ),
				'priority'   => aioseo()->sitemap->priority->priority( 'taxonomies', $term, 'product_attributes' ),
				'images'     => aioseo()->sitemap->image->term( $term )
			];

			$entries[] = apply_filters( 'aioseo_sitemap_product_attributes', $entry, $termId, $term->taxonomy, 'term' );
		}

		return $entries;
	}
}xbodynamge/dev/wp-content/plugins/all-in-one-seo-pack/app/Common/EmailReports/Summary/Content.php000064400000050371151136472460027757 0ustar00home<?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;
	}
}namtation/wp-content/plugins/all-in-one-seo-pack/app/Common/EmailReports/Summary/Content.php000064400000050371151151720540031163 0ustar00home/xbodynamge<?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;
	}
}home/xbodynamge/dev/wp-content/plugins/all-in-one-seo-pack/app/Common/Sitemap/Content.php000064400000074527151155271420025400 0ustar00<?php
namespace AIOSEO\Plugin\Common\Sitemap;

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

use AIOSEO\Plugin\Common\Integrations\BuddyPress as BuddyPressIntegration;

/**
 * Determines which content should be included in the sitemap.
 *
 * @since 4.0.0
 */
class Content {
	/**
	 * Returns the entries for the requested sitemap.
	 *
	 * @since 4.0.0
	 *
	 * @return array The sitemap entries.
	 */
	public function get() {
		if ( ! in_array( aioseo()->sitemap->type, [ 'general', 'rss' ], true ) || ! $this->isEnabled() ) {
			return [];
		}

		if ( 'rss' === aioseo()->sitemap->type ) {
			return $this->rss();
		}

		if ( 'general' !== aioseo()->sitemap->type ) {
			return [];
		}

		$indexesEnabled = aioseo()->options->sitemap->general->indexes;
		if ( ! $indexesEnabled ) {
			if ( 'root' === aioseo()->sitemap->indexName ) {
				// If indexes are disabled, throw all entries together into one big file.
				return $this->nonIndexed();
			}

			return [];
		}

		if ( 'root' === aioseo()->sitemap->indexName ) {
			return aioseo()->sitemap->root->indexes();
		}

		// Check if requested index has a dedicated method.
		$methodName = aioseo()->helpers->dashesToCamelCase( aioseo()->sitemap->indexName );
		if ( method_exists( $this, $methodName ) ) {
			return $this->$methodName();
		}

		// Check if requested index is a registered post type.
		if ( in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedPostTypes(), true ) ) {
			return $this->posts( aioseo()->sitemap->indexName );
		}

		// Check if requested index is a registered taxonomy.
		if (
			in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedTaxonomies(), true ) &&
			'product_attributes' !== aioseo()->sitemap->indexName
		) {
			return $this->terms( aioseo()->sitemap->indexName );
		}

		if (
			aioseo()->helpers->isWooCommerceActive() &&
			in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedTaxonomies(), true ) &&
			'product_attributes' === aioseo()->sitemap->indexName
		) {
			return $this->productAttributes();
		}

		return [];
	}

	/**
	 * Returns the total entries number for the requested sitemap.
	 *
	 * @since 4.1.5
	 *
	 * @return int The total entries number.
	 */
	public function getTotal() {
		if ( ! in_array( aioseo()->sitemap->type, [ 'general', 'rss' ], true ) || ! $this->isEnabled() ) {
			return 0;
		}

		if ( 'rss' === aioseo()->sitemap->type ) {
			return count( $this->rss() );
		}

		if ( 'general' !== aioseo()->sitemap->type ) {
			return 0;
		}

		$indexesEnabled = aioseo()->options->sitemap->general->indexes;
		if ( ! $indexesEnabled ) {
			if ( 'root' === aioseo()->sitemap->indexName ) {
				// If indexes are disabled, throw all entries together into one big file.
				return count( $this->nonIndexed() );
			}

			return 0;
		}

		if ( 'root' === aioseo()->sitemap->indexName ) {
			return count( aioseo()->sitemap->root->indexes() );
		}

		// Check if requested index has a dedicated method.
		$methodName = aioseo()->helpers->dashesToCamelCase( aioseo()->sitemap->indexName );
		if ( method_exists( $this, $methodName ) ) {
			$res = $this->$methodName();

			return ! empty( $res ) ? count( $res ) : 0;
		}

		// Check if requested index is a registered post type.
		if ( in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedPostTypes(), true ) ) {
			return aioseo()->sitemap->query->posts( aioseo()->sitemap->indexName, [ 'count' => true ] );
		}

		// Check if requested index is a registered taxonomy.
		if ( in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedTaxonomies(), true ) ) {
			return aioseo()->sitemap->query->terms( aioseo()->sitemap->indexName, [ 'count' => true ] );
		}

		return 0;
	}

	/**
	 * Checks if the requested sitemap is enabled.
	 *
	 * @since 4.0.0
	 *
	 * @return boolean Whether the sitemap is enabled.
	 */
	public function isEnabled() {
		$options = aioseo()->options->noConflict();
		if ( ! $options->sitemap->{aioseo()->sitemap->type}->enable ) {
			return false;
		}

		if ( $options->sitemap->{aioseo()->sitemap->type}->postTypes->all ) {
			return true;
		}

		$included = aioseo()->sitemap->helpers->includedPostTypes();

		return ! empty( $included );
	}

	/**
	 * Returns all sitemap entries if indexing is disabled.
	 *
	 * @since 4.0.0
	 *
	 * @return array $entries The sitemap entries.
	 */
	private function nonIndexed() {
		$additional       = $this->addl();
		$postTypes        = aioseo()->sitemap->helpers->includedPostTypes();
		$isStaticHomepage = 'page' === get_option( 'show_on_front' );
		$blogPageEntry    = [];
		$homePageEntry    = ! $isStaticHomepage ? [ array_shift( $additional ) ] : [];
		$entries          = array_merge( $additional, $this->author(), $this->date(), $this->postArchive() );

		if ( $postTypes ) {
			foreach ( $postTypes as $postType ) {
				$postTypeEntries = $this->posts( $postType );

				// If we don't have a static homepage, it's business as usual.
				if ( ! $isStaticHomepage ) {
					$entries = array_merge( $entries, $postTypeEntries );
					continue;
				}

				$homePageId = (int) get_option( 'page_on_front' );
				$blogPageId = (int) get_option( 'page_for_posts' );

				if ( 'post' === $postType && $blogPageId ) {
					$blogPageEntry[] = array_shift( $postTypeEntries );
				}

				if ( 'page' === $postType && $homePageId ) {
					$homePageEntry[] = array_shift( $postTypeEntries );
				}

				$entries = array_merge( $entries, $postTypeEntries );
			}
		}

		$taxonomies = aioseo()->sitemap->helpers->includedTaxonomies();
		if ( $taxonomies ) {
			foreach ( $taxonomies as $taxonomy ) {
				$entries = array_merge( $entries, $this->terms( $taxonomy ) );
			}
		}

		// Sort first by priority, then by last modified date.
		usort( $entries, function ( $a, $b ) {
			// If the priorities are equal, sort by last modified date.
			if ( $a['priority'] === $b['priority'] ) {
				return $a['lastmod'] > $b['lastmod'] ? -1 : 1;
			}

			return $a['priority'] > $b['priority'] ? -1 : 1;
		} );

		// Merge the arrays with the home page always first.
		return array_merge( $homePageEntry, $blogPageEntry, $entries );
	}

	/**
	 * Returns all post entries for a given post type.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $postType       The name of the post type.
	 * @param  array  $additionalArgs Any additional arguments for the post query.
	 * @return array                  The sitemap entries.
	 */
	public function posts( $postType, $additionalArgs = [] ) {
		$posts = aioseo()->sitemap->query->posts( $postType, $additionalArgs );
		if ( ! $posts ) {
			return [];
		}

		// Return if we're determining the root indexes.
		if ( ! empty( $additionalArgs['root'] ) && $additionalArgs['root'] ) {
			return $posts;
		}

		$entries          = [];
		$isStaticHomepage = 'page' === get_option( 'show_on_front' );
		$homePageId       = (int) get_option( 'page_on_front' );
		$excludeImages    = aioseo()->sitemap->helpers->excludeImages();
		foreach ( $posts as $post ) {
			$entry = [
				'loc'        => get_permalink( $post->ID ),
				'lastmod'    => aioseo()->helpers->dateTimeToIso8601( $this->getLastModified( $post ) ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'postTypes', $post, $postType ),
				'priority'   => aioseo()->sitemap->priority->priority( 'postTypes', $post, $postType ),
			];

			if ( ! $excludeImages ) {
				$entry['images'] = ! empty( $post->images ) ? json_decode( $post->images ) : [];
			}

			// Override priority/frequency for static homepage.
			if ( $isStaticHomepage && ( $homePageId === $post->ID || aioseo()->helpers->wpmlIsHomePage( $post->ID ) ) ) {
				$entry['loc']        = aioseo()->helpers->maybeRemoveTrailingSlash( aioseo()->helpers->wpmlHomeUrl( $post->ID ) ?: $entry['loc'] );
				$entry['changefreq'] = aioseo()->sitemap->priority->frequency( 'homePage' );
				$entry['priority']   = aioseo()->sitemap->priority->priority( 'homePage' );
			}

			$entries[] = apply_filters( 'aioseo_sitemap_post', $entry, $post->ID, $postType, 'post' );
		}

		// We can't remove the post type here because other plugins rely on it.
		return apply_filters( 'aioseo_sitemap_posts', $entries, $postType );
	}

	/**
	 * Returns all post archive entries.
	 *
	 * @since 4.0.0
	 *
	 * @return array $entries The sitemap entries.
	 */
	private function postArchive() {
		$entries = [];
		foreach ( aioseo()->sitemap->helpers->includedPostTypes( true ) as $postType ) {
			if (
				aioseo()->dynamicOptions->noConflict()->searchAppearance->archives->has( $postType ) &&
				! aioseo()->dynamicOptions->searchAppearance->archives->$postType->advanced->robotsMeta->default &&
				aioseo()->dynamicOptions->searchAppearance->archives->$postType->advanced->robotsMeta->noindex
			) {
				continue;
			}

			$post = aioseo()->core->db
				->start( aioseo()->core->db->db->posts . ' as p', true )
				->select( 'p.ID' )
				->where( 'p.post_status', 'publish' )
				->where( 'p.post_type', $postType )
				->limit( 1 )
				->run()
				->result();

			if ( ! $post ) {
				continue;
			}

			$url = get_post_type_archive_link( $postType );
			if ( $url ) {
				$entry = [
					'loc'        => $url,
					'lastmod'    => aioseo()->sitemap->helpers->lastModifiedPostTime( $postType ),
					'changefreq' => aioseo()->sitemap->priority->frequency( 'archive' ),
					'priority'   => aioseo()->sitemap->priority->priority( 'archive' ),
				];

				// To be consistent with our other entry filters, we need to pass the entry ID as well, but as null in this case.
				$entries[] = apply_filters( 'aioseo_sitemap_archive_entry', $entry, null, $postType, 'archive' );
			}
		}

		return apply_filters( 'aioseo_sitemap_post_archives', $entries );
	}

	/**
	 * Returns all term entries for a given taxonomy.
	 *
	 * @since 4.0.0
	 *
	 * @param  string $taxonomy       The name of the taxonomy.
	 * @param  array  $additionalArgs Any additional arguments for the term query.
	 * @return array                  The sitemap entries.
	 */
	public function terms( $taxonomy, $additionalArgs = [] ) {
		$terms = aioseo()->sitemap->query->terms( $taxonomy, $additionalArgs );
		if ( ! $terms ) {
			return [];
		}

		// Get all registered post types for the taxonomy.
		$postTypes = [];
		foreach ( get_post_types() as $postType ) {
			$taxonomies = get_object_taxonomies( $postType );
			foreach ( $taxonomies as $name ) {
				if ( $taxonomy === $name ) {
					$postTypes[] = $postType;
				}
			}
		}

		// Return if we're determining the root indexes.
		if ( ! empty( $additionalArgs['root'] ) && $additionalArgs['root'] ) {
			return $terms;
		}

		$entries = [];
		foreach ( $terms as $term ) {
			$entry = [
				'loc'        => get_term_link( $term->term_id ),
				'lastmod'    => $this->getTermLastModified( $term ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'taxonomies', $term, $taxonomy ),
				'priority'   => aioseo()->sitemap->priority->priority( 'taxonomies', $term, $taxonomy ),
				'images'     => aioseo()->sitemap->image->term( $term )
			];

			$entries[] = apply_filters( 'aioseo_sitemap_term', $entry, $term->term_id, $term->taxonomy, 'term' );
		}

		return apply_filters( 'aioseo_sitemap_terms', $entries );
	}

	/**
	 * Returns the last modified date for a given term.
	 *
	 * @since 4.0.0
	 *
	 * @param  int|object $term The term data object.
	 * @return string           The lastmod timestamp.
	 */
	public function getTermLastModified( $term ) {
		$termRelationshipsTable = aioseo()->core->db->db->prefix . 'term_relationships';
		$termTaxonomyTable      = aioseo()->core->db->db->prefix . 'term_taxonomy';

		// If the term is an ID, get the term object.
		if ( is_numeric( $term ) ) {
			$term = aioseo()->helpers->getTerm( $term );
		}

		// First, check the count of the term. If it's 0, then we're dealing with a parent term that does not have
		// posts assigned to it. In this case, we need to get the last modified date of all its children.
		if ( empty( $term->count ) ) {
			$lastModified = aioseo()->core->db
				->start( aioseo()->core->db->db->posts . ' as p', true )
				->select( 'MAX(`p`.`post_modified_gmt`) as last_modified' )
				->where( 'p.post_status', 'publish' )
				->whereRaw( "
				( `p`.`ID` IN
					(
						SELECT CONVERT(`tr`.`object_id`, unsigned)
						FROM `$termRelationshipsTable` as tr
						JOIN `$termTaxonomyTable` as tt ON `tr`.`term_taxonomy_id` = `tt`.`term_taxonomy_id`
						WHERE `tt`.`term_id` IN
							(
								SELECT `tt`.`term_id`
								FROM `$termTaxonomyTable` as tt
								WHERE `tt`.`parent` = '{$term->term_id}'
							)
					)
				)" )
				->run()
				->result();
		} else {
			$lastModified = aioseo()->core->db
				->start( aioseo()->core->db->db->posts . ' as p', true )
				->select( 'MAX(`p`.`post_modified_gmt`) as last_modified' )
				->where( 'p.post_status', 'publish' )
				->whereRaw( "
				( `p`.`ID` IN
					(
						SELECT CONVERT(`tr`.`object_id`, unsigned)
						FROM `$termRelationshipsTable` as tr
						JOIN `$termTaxonomyTable` as tt ON `tr`.`term_taxonomy_id` = `tt`.`term_taxonomy_id`
						WHERE `tt`.`term_id` = '{$term->term_id}'
					)
				)" )
				->run()
				->result();
		}

		$lastModified = $lastModified[0]->last_modified ?? '';

		return aioseo()->helpers->dateTimeToIso8601( $lastModified );
	}

	/**
	 * Returns all additional pages.
	 *
	 * @since 4.0.0
	 *
	 * @param  bool  $shouldChunk Whether the entries should be chuncked. Is set to false when the static sitemap is generated.
	 * @return array              The sitemap entries.
	 */
	public function addl( $shouldChunk = true ) {
		$additionalPages = [];
		if ( aioseo()->options->sitemap->general->additionalPages->enable ) {
			$additionalPages = array_map( 'json_decode', aioseo()->options->sitemap->general->additionalPages->pages );
			$additionalPages = array_filter( $additionalPages, function( $additionalPage ) {
				return ! empty( $additionalPage->url );
			} );
		}

		$entries = [];
		foreach ( $additionalPages as $additionalPage ) {
			$entries[] = [
				'loc'        => $additionalPage->url,
				'lastmod'    => aioseo()->sitemap->helpers->lastModifiedAdditionalPage( $additionalPage ),
				'changefreq' => $additionalPage->frequency->value,
				'priority'   => $additionalPage->priority->value,
				'isTimezone' => true
			];
		}

		$postTypes             = aioseo()->sitemap->helpers->includedPostTypes();
		$shouldIncludeHomepage = 'posts' === get_option( 'show_on_front' ) || ! in_array( 'page', $postTypes, true );
		if ( $shouldIncludeHomepage ) {
			$frontPageId  = (int) get_option( 'page_on_front' );
			$frontPageUrl = aioseo()->helpers->localizedUrl( '/' );
			$post         = aioseo()->helpers->getPost( $frontPageId );

			$homepageEntry = [
				'loc'        => aioseo()->helpers->maybeRemoveTrailingSlash( $frontPageUrl ),
				'lastmod'    => $post ? aioseo()->helpers->dateTimeToIso8601( $this->getLastModified( $post ) ) : aioseo()->sitemap->helpers->lastModifiedPostTime(),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'homePage' ),
				'priority'   => aioseo()->sitemap->priority->priority( 'homePage' )
			];

			$translatedHomepages = aioseo()->helpers->wpmlHomePages();
			foreach ( $translatedHomepages as $languageCode => $translatedHomepage ) {
				if ( untrailingslashit( $translatedHomepage['url'] ) !== untrailingslashit( $homepageEntry['loc'] ) ) {
					$homepageEntry['languages'][] = [
						'language' => $languageCode,
						'location' => $translatedHomepage['url']
					];
				}
			}

			// Add homepage to the first position.
			array_unshift( $entries, $homepageEntry );
		}

		if ( aioseo()->options->sitemap->general->additionalPages->enable ) {
			$entries = apply_filters( 'aioseo_sitemap_additional_pages', $entries );
		}

		if ( empty( $entries ) ) {
			return [];
		}

		if ( aioseo()->options->sitemap->general->indexes && $shouldChunk ) {
			$entries = aioseo()->sitemap->helpers->chunkEntries( $entries );
			$entries = $entries[ aioseo()->sitemap->pageNumber ] ?? [];
		}

		return $entries;
	}

	/**
	 * Returns all author archive entries.
	 *
	 * @since 4.0.0
	 *
	 * @return array The sitemap entries.
	 */
	public function author() {
		if (
			! aioseo()->sitemap->helpers->lastModifiedPost() ||
			! aioseo()->options->sitemap->general->author ||
			! aioseo()->options->searchAppearance->archives->author->show ||
			(
				! aioseo()->options->searchAppearance->archives->author->advanced->robotsMeta->default &&
				aioseo()->options->searchAppearance->archives->author->advanced->robotsMeta->noindex
			) ||
			(
				aioseo()->options->searchAppearance->archives->author->advanced->robotsMeta->default &&
				(
					! aioseo()->options->searchAppearance->advanced->globalRobotsMeta->default &&
					aioseo()->options->searchAppearance->advanced->globalRobotsMeta->noindex
				)
			)
		) {
			return [];
		}

		// Allow users to filter the authors in case their sites use a membership plugin or have custom code that affect the authors on their site.
		// e.g. there might be additional roles/conditions that need to be checked here.
		$authors = apply_filters( 'aioseo_sitemap_authors', [] );
		if ( empty( $authors ) ) {
			$usersTableName = aioseo()->core->db->db->users; // We get the table name from WPDB since multisites share the same table.
			$authors        = aioseo()->core->db->start( "$usersTableName as u", true )
				->select( 'u.ID as ID, u.user_nicename as nicename, MAX(p.post_modified_gmt) as lastModified' )
				->join( 'posts as p', 'u.ID = p.post_author' )
				->where( 'p.post_status', 'publish' )
				->whereIn( 'p.post_type', aioseo()->sitemap->helpers->getAuthorPostTypes() )
				->groupBy( 'u.ID' )
				->orderBy( 'lastModified DESC' )
				->limit( aioseo()->sitemap->linksPerIndex, aioseo()->sitemap->pageNumber * aioseo()->sitemap->linksPerIndex )
				->run()
				->result();
		}

		if ( empty( $authors ) ) {
			return [];
		}

		$entries = [];
		foreach ( $authors as $authorData ) {
			$entry = [
				'loc'        => ! empty( $authorData->authorUrl )
					? $authorData->authorUrl
					: get_author_posts_url( $authorData->ID, $authorData->nicename ?: '' ),
				'lastmod'    => aioseo()->helpers->dateTimeToIso8601( $authorData->lastModified ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'author' ),
				'priority'   => aioseo()->sitemap->priority->priority( 'author' )
			];

			$entries[] = apply_filters( 'aioseo_sitemap_author_entry', $entry, $authorData->ID, $authorData->nicename, 'author' );
		}

		return apply_filters( 'aioseo_sitemap_author_archives', $entries );
	}

	/**
	 * Returns all data archive entries.
	 *
	 * @since 4.0.0
	 *
	 * @return array The sitemap entries.
	 */
	public function date() {
		if (
			! aioseo()->sitemap->helpers->lastModifiedPost() ||
			! aioseo()->options->sitemap->general->date ||
			! aioseo()->options->searchAppearance->archives->date->show ||
			(
				! aioseo()->options->searchAppearance->archives->date->advanced->robotsMeta->default &&
				aioseo()->options->searchAppearance->archives->date->advanced->robotsMeta->noindex
			) ||
			(
				aioseo()->options->searchAppearance->archives->date->advanced->robotsMeta->default &&
				(
					! aioseo()->options->searchAppearance->advanced->globalRobotsMeta->default &&
					aioseo()->options->searchAppearance->advanced->globalRobotsMeta->noindex
				)
			)
		) {
			return [];
		}

		$postsTable = aioseo()->core->db->db->posts;
		$dates      = aioseo()->core->db->execute(
			"SELECT
				YEAR(post_date) AS `year`,
				MONTH(post_date) AS `month`,
				post_date_gmt,
				post_modified_gmt
			FROM {$postsTable}
			WHERE post_type = 'post' AND post_status = 'publish'
			GROUP BY
				YEAR(post_date),
				MONTH(post_date)
			ORDER BY post_date ASC 
			LIMIT 50000",
			true
		)->result();

		if ( empty( $dates ) ) {
			return [];
		}

		$entries = [];
		$year    = '';
		foreach ( $dates as $date ) {
			$entry = [
				'lastmod'    => aioseo()->helpers->dateTimeToIso8601( $this->getLastModified( $date ) ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'date' ),
				'priority'   => aioseo()->sitemap->priority->priority( 'date' ),
			];

			// Include each year only once.
			if ( $year !== $date->year ) {
				$year         = $date->year;
				$entry['loc'] = get_year_link( $date->year );
				$entries[]    = apply_filters( 'aioseo_sitemap_date_entry', $entry, $date, 'year', 'date' );
			}

			$entry['loc'] = get_month_link( $date->year, $date->month );
			$entries[]    = apply_filters( 'aioseo_sitemap_date_entry', $entry, $date, 'month', 'date' );
		}

		return apply_filters( 'aioseo_sitemap_date_archives', $entries );
	}

	/**
	 * Returns all entries for the RSS Sitemap.
	 *
	 * @since 4.0.0
	 *
	 * @return array The sitemap entries.
	 */
	public function rss() {
		$posts = aioseo()->sitemap->query->posts(
			aioseo()->sitemap->helpers->includedPostTypes(),
			[ 'orderBy' => '`p`.`post_modified_gmt` DESC' ]
		);

		if ( ! count( $posts ) ) {
			return [];
		}

		$entries = [];
		foreach ( $posts as $post ) {
			$entry = [
				'guid'        => get_permalink( $post->ID ),
				'title'       => get_the_title( $post ),
				'description' => get_post_field( 'post_excerpt', $post->ID ),
				'pubDate'     => aioseo()->helpers->dateTimeToRfc822( $this->getLastModified( $post ) )
			];

			// If the entry is the homepage, we need to check if the permalink structure
			// does not have a trailing slash. If so, we need to strip it because WordPress adds it
			// regardless for the home_url() in get_page_link() which is used in the get_permalink() function.
			static $homeId = null;
			if ( null === $homeId ) {
				$homeId = get_option( 'page_for_posts' );
			}

			if ( aioseo()->helpers->getHomePageId() === $post->ID ) {
				$entry['guid'] = aioseo()->helpers->maybeRemoveTrailingSlash( $entry['guid'] );
			}

			$entries[] = apply_filters( 'aioseo_sitemap_post_rss', $entry, $post->ID, $post->post_type, 'post' );
		}

		usort( $entries, function( $a, $b ) {
			return $a['pubDate'] < $b['pubDate'] ? 1 : 0;
		});

		return apply_filters( 'aioseo_sitemap_rss', $entries );
	}

	/**
	 * Returns the last modified date for a given post.
	 *
	 * @since 4.6.3
	 *
	 * @param  object $post The post object.
	 *
	 * @return string The last modified date.
	 */
	public function getLastModified( $post ) {
		$publishDate      = $post->post_date_gmt;
		$lastModifiedDate = $post->post_modified_gmt;

		// Get the date which is the latest.
		return $lastModifiedDate > $publishDate ? $lastModifiedDate : $publishDate;
	}

	/**
	 * Returns all entries for the BuddyPress Activity Sitemap.
	 * This method is automagically called from {@see get()} if the current index name equals to 'bp-activity'
	 *
	 * @since 4.7.6
	 *
	 * @return array The sitemap entries.
	 */
	public function bpActivity() {
		$entries = [];
		if ( ! in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedPostTypes(), true ) ) {
			return $entries;
		}

		$postType = 'bp-activity';
		$query    = aioseo()->core->db
			->start( 'bp_activity as a' )
			->select( '`a`.`id`, `a`.`date_recorded`' )
			->whereRaw( "a.is_spam = 0 AND a.hide_sitewide = 0 AND a.type NOT IN ('activity_comment', 'last_activity')" )
			->limit( aioseo()->sitemap->linksPerIndex, aioseo()->sitemap->offset )
			->orderBy( 'a.date_recorded DESC' );

		$items = $query->run()
						->result();

		foreach ( $items as $item ) {
			$entry = [
				'loc'        => BuddyPressIntegration::getComponentSingleUrl( 'activity', $item->id ),
				'lastmod'    => aioseo()->helpers->dateTimeToIso8601( $item->date_recorded ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'postTypes', false, $postType ),
				'priority'   => aioseo()->sitemap->priority->priority( 'postTypes', false, $postType ),
			];

			$entries[] = apply_filters( 'aioseo_sitemap_post', $entry, $item->id, $postType );
		}

		$archiveUrl = BuddyPressIntegration::getComponentArchiveUrl( 'activity' );
		if (
			aioseo()->helpers->isUrl( $archiveUrl ) &&
			! in_array( $postType, aioseo()->helpers->getNoindexedObjects( 'archives' ), true )
		) {
			$lastMod = ! empty( $items[0] ) ? $items[0]->date_recorded : current_time( 'mysql' );
			$entry   = [
				'loc'        => $archiveUrl,
				'lastmod'    => $lastMod,
				'changefreq' => aioseo()->sitemap->priority->frequency( 'postTypes', false, $postType ),
				'priority'   => aioseo()->sitemap->priority->priority( 'postTypes', false, $postType ),
			];

			array_unshift( $entries, $entry );
		}

		return apply_filters( 'aioseo_sitemap_posts', $entries, $postType );
	}

	/**
	 * Returns all entries for the BuddyPress Group Sitemap.
	 * This method is automagically called from {@see get()} if the current index name equals to 'bp-group'
	 *
	 * @since 4.7.6
	 *
	 * @return array The sitemap entries.
	 */
	public function bpGroup() {
		$entries = [];
		if ( ! in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedPostTypes(), true ) ) {
			return $entries;
		}

		$postType = 'bp-group';
		$query    = aioseo()->core->db
			->start( 'bp_groups as g' )
			->select( '`g`.`id`, `g`.`date_created`, `gm`.`meta_value` as date_modified' )
			->leftJoin( 'bp_groups_groupmeta as gm', 'g.id = gm.group_id' )
			->whereRaw( "g.status = 'public' AND gm.meta_key = 'last_activity'" )
			->limit( aioseo()->sitemap->linksPerIndex, aioseo()->sitemap->offset )
			->orderBy( 'gm.meta_value DESC' )
			->orderBy( 'g.date_created DESC' );

		$items = $query->run()
						->result();

		foreach ( $items as $item ) {
			$lastMod = $item->date_modified ?: $item->date_created;
			$entry   = [
				'loc'        => BuddyPressIntegration::getComponentSingleUrl( 'group', BuddyPressIntegration::callFunc( 'bp_get_group_by', 'id', $item->id ) ),
				'lastmod'    => aioseo()->helpers->dateTimeToIso8601( $lastMod ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'postTypes', false, $postType ),
				'priority'   => aioseo()->sitemap->priority->priority( 'postTypes', false, $postType ),
			];

			$entries[] = apply_filters( 'aioseo_sitemap_post', $entry, $item->id, $postType );
		}

		$archiveUrl = BuddyPressIntegration::getComponentArchiveUrl( 'group' );
		if (
			aioseo()->helpers->isUrl( $archiveUrl ) &&
			! in_array( $postType, aioseo()->helpers->getNoindexedObjects( 'archives' ), true )
		) {
			$lastMod = ! empty( $items[0] ) ? $items[0]->date_modified : current_time( 'mysql' );
			$entry   = [
				'loc'        => $archiveUrl,
				'lastmod'    => $lastMod,
				'changefreq' => aioseo()->sitemap->priority->frequency( 'postTypes', false, $postType ),
				'priority'   => aioseo()->sitemap->priority->priority( 'postTypes', false, $postType ),
			];

			array_unshift( $entries, $entry );
		}

		return apply_filters( 'aioseo_sitemap_posts', $entries, $postType );
	}

	/**
	 * Returns all entries for the BuddyPress Member Sitemap.
	 * This method is automagically called from {@see get()} if the current index name equals to 'bp-member'
	 *
	 * @since 4.7.6
	 *
	 * @return array The sitemap entries.
	 */
	public function bpMember() {
		$entries = [];
		if ( ! in_array( aioseo()->sitemap->indexName, aioseo()->sitemap->helpers->includedPostTypes(), true ) ) {
			return $entries;
		}

		$postType = 'bp-member';
		$query    = aioseo()->core->db
			->start( 'bp_activity as a' )
			->select( '`a`.`user_id` as id, `a`.`date_recorded`' )
			->whereRaw( "a.component = 'members' AND a.type = 'last_activity'" )
			->limit( aioseo()->sitemap->linksPerIndex, aioseo()->sitemap->offset )
			->orderBy( 'a.date_recorded DESC' );

		$items = $query->run()
			->result();

		foreach ( $items as $item ) {
			$entry = [
				'loc'        => BuddyPressIntegration::getComponentSingleUrl( 'member', $item->id ),
				'lastmod'    => aioseo()->helpers->dateTimeToIso8601( $item->date_recorded ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'postTypes', false, $postType ),
				'priority'   => aioseo()->sitemap->priority->priority( 'postTypes', false, $postType ),
			];

			$entries[] = apply_filters( 'aioseo_sitemap_post', $entry, $item->id, $postType );
		}

		$archiveUrl = BuddyPressIntegration::getComponentArchiveUrl( 'member' );
		if (
			aioseo()->helpers->isUrl( $archiveUrl ) &&
			! in_array( $postType, aioseo()->helpers->getNoindexedObjects( 'archives' ), true )
		) {
			$lastMod = ! empty( $items[0] ) ? $items[0]->date_recorded : current_time( 'mysql' );
			$entry   = [
				'loc'        => $archiveUrl,
				'lastmod'    => $lastMod,
				'changefreq' => aioseo()->sitemap->priority->frequency( 'postTypes', false, $postType ),
				'priority'   => aioseo()->sitemap->priority->priority( 'postTypes', false, $postType ),
			];

			array_unshift( $entries, $entry );
		}

		return apply_filters( 'aioseo_sitemap_posts', $entries, $postType );
	}

	/**
	 * Returns all entries for the WooCommerce Product Attributes sitemap.
	 * Note: This sitemap does not support pagination.
	 *
	 * @since 4.7.8
	 *
	 * @param  bool  $count Whether to return the count of the entries. This is used to determine the indexes.
	 * @return array        The sitemap entries.
	 */
	public function productAttributes( $count = false ) {
		$aioseoTermsTable           = aioseo()->core->db->prefix . 'aioseo_terms';
		$wcAttributeTaxonomiesTable = aioseo()->core->db->prefix . 'woocommerce_attribute_taxonomies';
		$termTaxonomyTable          = aioseo()->core->db->prefix . 'term_taxonomy';

		$selectClause = 'COUNT(*) as childProductAttributes';
		if ( ! $count ) {
			$selectClause = aioseo()->pro ? 'tt.term_id, tt.taxonomy, at.frequency, at.priority' : 'tt.term_id, tt.taxonomy';
		}

		$joinClause   = aioseo()->pro ? "LEFT JOIN {$aioseoTermsTable} AS at ON tt.term_id = at.term_id" : '';
		$whereClause  = aioseo()->pro ? 'AND (at.robots_noindex IS NULL OR at.robots_noindex = 0)' : '';
		$limitClause  = $count ? '' : 'LIMIT 50000';

		$result = aioseo()->core->db->execute(
			"SELECT {$selectClause}
			FROM {$termTaxonomyTable} AS tt
			JOIN {$wcAttributeTaxonomiesTable} AS wat ON tt.taxonomy = CONCAT('pa_', wat.attribute_name)
			{$joinClause}
			WHERE wat.attribute_public = 1
				{$whereClause}
				AND tt.count > 0
			{$limitClause};",
			true
		)->result();

		if ( $count ) {
			return ! empty( $result[0]->childProductAttributes ) ? (int) $result[0]->childProductAttributes : 0;
		}

		if ( empty( $result ) ) {
			return [];
		}

		$entries = [];
		foreach ( $result as $term ) {
			$term   = (object) $term;
			$termId = (int) $term->term_id;

			$entry = [
				'loc'        => get_term_link( $termId ),
				'lastmod'    => $this->getTermLastModified( $termId ),
				'changefreq' => aioseo()->sitemap->priority->frequency( 'taxonomies', $term, 'product_attributes' ),
				'priority'   => aioseo()->sitemap->priority->priority( 'taxonomies', $term, 'product_attributes' ),
				'images'     => aioseo()->sitemap->image->term( $term )
			];

			$entries[] = apply_filters( 'aioseo_sitemap_product_attributes', $entry, $termId, $term->taxonomy, 'term' );
		}

		return $entries;
	}
}