core->db
->start( 'posts as p' )
->select( $fields )
->where( 'p.post_status', 'publish' )
->where( 'p.post_type', $postType )
->where( 'p.post_password', '' );
$homePageId = (int) aioseo()->helpers->getHomePageId();
$postsTable = aioseo()->core->db->db->prefix . 'aioseo_posts';
// Mirrors the XML sitemap's row filter, so the exclusion notices stay honest. The static front
// page is exempt: its editor hides No Index, so a value set before it was promoted can't be
// cleared. Only while it is in effect, hence the helper over a raw page_on_front read.
//
// Both branches address aioseo_posts through a subquery rather than a join. The exemption has
// to be OR-ed in at the top level, and an `OR` beside a joined row is not null-rejecting: it
// makes the join unavoidable for every post of the type, and no index on the robots columns
// can be used. Against a subquery the same OR costs nothing.
if ( aioseo()->sitemap->helpers->isPostTypeEffectivelyNoindexed( $postType ) ) {
// Only posts that have explicitly opted in.
$query->whereRaw( "( `p`.`ID` IN (
SELECT `post_id` FROM $postsTable WHERE `robots_default` = 0 AND `robots_noindex` = 0
) OR `p`.`ID` = $homePageId )" );
} else {
// Excludes the explicitly noindexed set. The inner NOT IN is required for posts carrying
// more than one aioseo_posts row (post_id is not a unique index): the left join this
// replaces asked whether ANY row was indexable, so excluding on ANY noindexed row would
// drop posts it kept.
$query->whereRaw( "( `p`.`ID` NOT IN (
SELECT `post_id` FROM $postsTable
WHERE `robots_default` = 0 AND `robots_noindex` = 1
AND `post_id` NOT IN (
SELECT `post_id` FROM $postsTable WHERE `robots_default` = 1 OR `robots_noindex` = 0
)
) OR `p`.`ID` = $homePageId )" );
}
$excludedPosts = $this->getExcludedObjects( $attributes );
if ( $excludedPosts ) {
// An explicit per-instance exclusion outranks the front page's exemption; the site-wide
// setting does not, which is what keeps this sitemap agreeing with the XML one.
$instanceIds = array_map( 'intval', $this->getInstanceExcludedIds( $attributes, 'excluded_posts' ) );
$exemptId = in_array( $homePageId, $instanceIds, true ) ? 0 : $homePageId;
$query->whereRaw( "( `p`.`ID` NOT IN ( $excludedPosts ) OR `p`.`ID` = $exemptId )" );
}
$posts = $query->orderBy( $orderBy )
->run()
->result();
foreach ( $posts as $post ) {
$post->ID = (int) $post->ID;
}
return $posts;
}
/**
* Returns all eligible sitemap entries for a given taxonomy.
*
* @since 4.1.3
* @version 5.0.2 Compare excluded term IDs to term_id directly.
* @version 5.0.2 Filter terms by robots meta to match the XML sitemap query.
*
* @param string $taxonomy The taxonomy name.
* @param array $attributes The attributes.
* @return array The term objects.
*/
public function terms( $taxonomy, $attributes = [] ) {
$fields = 't.term_id, t.name, tt.parent';
$termTaxonomyTable = aioseo()->core->db->db->prefix . 'term_taxonomy';
$orderBy = '';
switch ( $attributes['order_by'] ) {
case 'alphabetical':
$orderBy = 't.name';
break;
// We can only sort by date after getting the terms.
case 'id':
case 'publish_date':
case 'last_updated':
default:
$orderBy = 't.term_id';
break;
}
switch ( strtolower( $attributes['order'] ) ) {
case 'desc':
$orderBy .= ' DESC';
break;
default:
$orderBy .= ' ASC';
}
$query = aioseo()->core->db
->start( 'terms as t' )
->select( $fields )
->join( 'term_taxonomy as tt', 't.term_id = tt.term_id' )
->whereRaw( "
( `t`.`term_id` IN
(
SELECT `tt`.`term_id`
FROM `$termTaxonomyTable` as tt
WHERE `tt`.`taxonomy` = '$taxonomy'
AND `tt`.`count` > 0
)
)" );
// Apply the same row filter as Pro\Sitemap\Query::terms(). Without it this sitemap contradicts
// the XML one and the exclusion notices on the Sitemaps screen: a term the settings exclude
// still renders here. Pro-only, because aioseo_terms is a Pro table.
if ( aioseo()->pro ) {
if ( aioseo()->sitemap->helpers->isTaxonomyEffectivelyNoindexed( $taxonomy ) ) {
// Only terms that have explicitly opted in.
$query->join( 'aioseo_terms as at', '`at`.`term_id` = `t`.`term_id` AND `at`.`robots_default` = 0 AND `at`.`robots_noindex` = 0' );
} else {
$query->leftJoin( 'aioseo_terms as at', '`at`.`term_id` = `t`.`term_id`' );
$query->whereRaw( '( `at`.`robots_noindex` IS NULL OR `at`.`robots_default` = 1 OR `at`.`robots_noindex` = 0 )' );
}
}
$excludedTerms = $this->getExcludedObjects( $attributes, false );
if ( $excludedTerms ) {
$query->whereRaw( "( `t`.`term_id` NOT IN ( $excludedTerms ) )" );
}
$terms = $query->orderBy( $orderBy )
->run()
->result();
foreach ( $terms as $term ) {
$term->term_id = (int) $term->term_id;
$term->taxonomy = $taxonomy;
}
$shouldSort = false;
if ( 'last_updated' === $attributes['order_by'] ) {
$shouldSort = true;
foreach ( $terms as $term ) {
$term->timestamp = strtotime( aioseo()->sitemap->content->getTermLastModified( $term->term_id ) );
}
}
if ( 'publish_date' === $attributes['order_by'] ) {
$shouldSort = true;
foreach ( $terms as $term ) {
$term->timestamp = strtotime( $this->getTermPublishDate( $term->term_id ) );
}
}
if ( $shouldSort ) {
if ( 'asc' === strtolower( $attributes['order'] ) ) {
usort( $terms, function( $term1, $term2 ) {
return $term1->timestamp > $term2->timestamp ? 1 : 0;
} );
} else {
usort( $terms, function( $term1, $term2 ) {
return $term1->timestamp < $term2->timestamp ? 1 : 0;
} );
}
}
return $terms;
}
/**
* Returns a list of date archives that can be included.
*
* @since 4.1.3
*
* @return array The date archives.
*/
public function archives() {
$result = aioseo()->core->db
->start( 'posts', false, 'SELECT DISTINCT' )
->select( 'YEAR(post_date) AS year, MONTH(post_date) AS month' )
->where( 'post_type', 'post' )
->where( 'post_status', 'publish' )
->where( 'post_password', '' )
->orderBy( 'year DESC' )
->orderBy( 'month DESC' )
->run()
->result();
$dates = [];
foreach ( $result as $date ) {
$dates[ $date->year ][ $date->month ] = 1;
}
return $dates;
}
/**
* Returns the publish date for a given term.
* This is the publish date of the oldest post that is assigned to the term.
*
* @since 4.1.3
* @version 5.0.2 Match the term ID through term_taxonomy.
*
* @param int $termId The term ID.
* @return int The publish date timestamp.
*/
public function getTermPublishDate( $termId ) {
$termRelationshipsTable = aioseo()->core->db->db->prefix . 'term_relationships';
$termTaxonomyTable = aioseo()->core->db->db->prefix . 'term_taxonomy';
$post = aioseo()->core->db
->start( 'posts as p' )
->select( 'MIN(`p`.`post_date_gmt`) as publish_date' )
->whereRaw( "
( `p`.`ID` IN
(
SELECT `tr`.`object_id`
FROM `$termRelationshipsTable` as tr
JOIN `$termTaxonomyTable` as tt ON `tr`.`term_taxonomy_id` = `tt`.`term_taxonomy_id`
WHERE `tt`.`term_id` = '$termId'
)
)" )
->run()
->result();
return ! empty( $post[0]->publish_date ) ? strtotime( $post[0]->publish_date ) : 0;
}
/**
* Returns a comma-separated string of excluded object IDs.
*
* @since 4.1.3
*
* @param array $attributes The attributes.
* @param boolean $posts Whether the objects are posts.
* @return string The excluded object IDs.
*/
private function getExcludedObjects( $attributes, $posts = true ) {
$excludedObjects = $posts
? aioseo()->sitemap->helpers->excludedPosts()
: aioseo()->sitemap->helpers->excludedTerms();
$key = $posts ? 'excluded_posts' : 'excluded_terms';
$instanceIds = $this->getInstanceExcludedIds( $attributes, $key );
if ( $instanceIds ) {
$ids = array_merge( explode( ',', $excludedObjects ), $instanceIds );
$excludedObjects = esc_sql( implode( ', ', array_filter( $ids, 'is_numeric' ) ) );
}
return $excludedObjects;
}
/**
* Returns the excluded object IDs a single embed asked for, without the site-wide setting.
*
* @since 5.0.2
*
* @param array $attributes The attributes.
* @param string $key The attribute name.
* @return array The excluded object IDs.
*/
private function getInstanceExcludedIds( $attributes, $key ) {
if ( empty( $attributes[ $key ] ) ) {
return [];
}
// The block hands over an array of IDs; the shortcode and the widget a comma-separated string.
$ids = [];
if ( is_array( $attributes[ $key ] ) ) {
$ids = $attributes[ $key ];
}
if ( is_string( $attributes[ $key ] ) ) {
$ids = array_map( 'trim', explode( ',', $attributes[ $key ] ) );
}
return array_filter( $ids, 'is_numeric' );
}
}