Files
2026-04-28 15:13:50 +02:00

134 lines
5.0 KiB
PHP

<?php
namespace AIOSEO\Plugin\Pro\Sitemap;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use AIOSEO\Plugin\Common\Sitemap as CommonSitemap;
/**
* Handles all complex queries for the sitemap.
*
* @since 4.0.0
*/
class Query extends CommonSitemap\Query {
/**
* Returns all eligible sitemap entries for a given taxonomy.
*
* @since 4.0.0
*
* @param string $taxonomy The taxonomy.
* @param array $additionalArgs Any additional arguments for the term query.
* @return array|int The term objects or the term count.
*/
public function terms( $taxonomy, $additionalArgs = [] ) {
// Let's just make sure the tables exist. This should never be a problem outside of our dev environments.
if ( ! aioseo()->core->db->tableExists( 'aioseo_terms' ) ) {
aioseo()->updates->addInitialCustomTablesForV4();
}
// Set defaults.
$fields = 't.term_id, at.priority, at.frequency, tt.count';
$offset = aioseo()->sitemap->offset;
// Include term name for llms sitemap type
if ( 'llms' === aioseo()->sitemap->type ) {
$fields .= ', t.name';
}
// Override defaults if passed as additional arg.
foreach ( $additionalArgs as $name => $value ) {
$$name = esc_sql( $value );
if ( 'root' === $name && $value ) {
$fields = 't.term_id';
}
if ( 'count' === $name && $value ) {
$fields = 'count(t.term_id) as total';
}
}
$termRelationshipsTable = aioseo()->core->db->db->prefix . 'term_relationships';
// Determine the robots meta conditions based on whether the taxonomy is noindexed.
// For noindexed taxonomies, we only include terms that have explicitly set robots_noindex to false.
// For indexed taxonomies, we include terms where robots_noindex is null, default, or explicitly false.
$isTaxonomyNoindexed = aioseo()->helpers->isTaxonomyNoindexed( $taxonomy );
// Include all terms that have assigned posts or whose children have assigned posts.
// The term also needs to be indexed and not excluded.
// Optimized query: replaced IN subquery with direct JOIN on term_taxonomy,
// and replaced EXISTS subquery with LEFT JOIN for child term checking.
if ( $isTaxonomyNoindexed ) {
// For noindexed taxonomies, only include terms that have explicitly opted in (robots_default = 0 AND robots_noindex = 0).
// Use INNER JOIN since we need matching records in aioseo_terms.
$query = aioseo()->core->db
->start( aioseo()->core->db->db->terms . ' as t', true )
->select( $fields )
->join( 'aioseo_terms as at', '`at`.`term_id` = `t`.`term_id` AND `at`.`robots_default` = 0 AND `at`.`robots_noindex` = 0' )
->join( 'term_taxonomy as tt', "`tt`.`term_id` = `t`.`term_id` AND `tt`.`taxonomy` = '$taxonomy'" )
->leftJoin( 'term_taxonomy as child', '`child`.`parent` = `tt`.`term_id` AND `child`.`count` > 0' );
} else {
// For indexed taxonomies, include terms where noindex is null, default, or explicitly false.
// Use LEFT JOIN on aioseo_terms to include terms without AIOSEO records.
$query = aioseo()->core->db
->start( aioseo()->core->db->db->terms . ' as t', true )
->select( $fields )
->leftJoin( 'aioseo_terms as at', '`at`.`term_id` = `t`.`term_id`' )
->join( 'term_taxonomy as tt', "`tt`.`term_id` = `t`.`term_id` AND `tt`.`taxonomy` = '$taxonomy'" )
->leftJoin( 'term_taxonomy as child', '`child`.`parent` = `tt`.`term_id` AND `child`.`count` > 0' );
// For indexed taxonomies, filter to include terms where noindex is null, default, or explicitly false.
$query->whereRaw( '( `at`.`robots_noindex` IS NULL OR `at`.`robots_default` = 1 OR `at`.`robots_noindex` = 0 )' );
}
// Include terms that have posts or whose children have posts.
$query->whereRaw( '( `tt`.`count` > 0 OR `child`.`term_id` IS NOT NULL )' );
// Group by term_id to prevent duplicate rows from the LEFT JOIN on child terms.
$query->groupBy( 't.term_id' );
// Exclude terms with custom canonical URLs pointing to different URLs.
$query->whereRaw( "( `at`.`canonical_url` IS NULL OR `at`.`canonical_url` = '' )" );
$excludedTerms = aioseo()->sitemap->helpers->excludedTerms();
if ( $excludedTerms ) {
$query->whereRaw("
( `t`.`term_id` NOT IN
(
SELECT `tr`.`term_taxonomy_id`
FROM `$termRelationshipsTable` as tr
WHERE `tr`.`term_taxonomy_id` IN ( $excludedTerms )
)
)" );
}
if (
aioseo()->sitemap->indexes &&
empty( $additionalArgs['root'] ) &&
( empty( $additionalArgs['count'] ) || ! $additionalArgs['count'] )
) {
$query->limit( aioseo()->sitemap->linksPerIndex, $offset );
}
// Return the total if we are just counting the terms.
if ( ! empty( $additionalArgs['count'] ) && $additionalArgs['count'] ) {
return (int) $query->run( true, 'var' )
->result();
}
$terms = $query->orderBy( 't.term_id ASC' )
->run()
->result();
foreach ( $terms as $term ) {
// Convert ID from string to int.
$term->term_id = (int) $term->term_id;
// Add taxonomy name to object manually instead of querying it to prevent redundant join.
$term->taxonomy = $taxonomy;
}
return $terms;
}
}