447 lines
12 KiB
PHP
447 lines
12 KiB
PHP
<?php
|
|
namespace AIOSEO\Plugin\Common\Llms;
|
|
|
|
// Exit if accessed directly.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Handles the LLMS.txt generation.
|
|
*
|
|
* @since 4.8.4
|
|
*/
|
|
class Llms {
|
|
/**
|
|
* Site title
|
|
*
|
|
* since 4.8.4
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $title;
|
|
|
|
/**
|
|
* Site description
|
|
*
|
|
* since 4.8.4
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $description;
|
|
|
|
/**
|
|
* Site link
|
|
*
|
|
* since 4.8.4
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $link;
|
|
|
|
/**
|
|
* Plugin version
|
|
*
|
|
* since 4.8.4
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $version;
|
|
|
|
/**
|
|
* LLMS file recurrent action name.
|
|
*
|
|
* since 4.8.8
|
|
*
|
|
* @var string
|
|
*/
|
|
public $llmsTxtRecurrentAction = 'aioseo_generate_llms_txt';
|
|
|
|
/**
|
|
* LLMS file single action name.
|
|
*
|
|
* since 4.8.8
|
|
*
|
|
* @var string
|
|
*/
|
|
public $llmsTxtSingleAction = 'aioseo_generate_llms_txt_single';
|
|
|
|
/**
|
|
* Class constructor.
|
|
*
|
|
* @since 4.8.8
|
|
*
|
|
* @return void
|
|
*/
|
|
public function __construct() {
|
|
add_action( 'admin_init', [ $this, 'scheduleRecurrentGenerationForLlmsTxt' ] );
|
|
|
|
add_action( 'wp_insert_post', [ $this, 'scheduleSingleGenerationForLlmsTxt' ] );
|
|
add_action( 'edited_term', [ $this, 'scheduleSingleGenerationForLlmsTxt' ] );
|
|
|
|
add_action( $this->llmsTxtRecurrentAction, [ $this, 'generateLlmsTxt' ] );
|
|
add_action( $this->llmsTxtSingleAction, [ $this, 'generateLlmsTxt' ] );
|
|
}
|
|
|
|
/**
|
|
* Schedules the LLMS file generation.
|
|
*
|
|
* @since 4.8.8
|
|
*
|
|
* @return void
|
|
*/
|
|
public function scheduleRecurrentGenerationForLlmsTxt() {
|
|
if (
|
|
! aioseo()->options->sitemap->llms->enable ||
|
|
aioseo()->actionScheduler->isScheduled( $this->llmsTxtRecurrentAction )
|
|
) {
|
|
return;
|
|
}
|
|
|
|
aioseo()->actionScheduler->scheduleRecurrent( $this->llmsTxtRecurrentAction, 10, DAY_IN_SECONDS );
|
|
}
|
|
|
|
/**
|
|
* Schedules a single LLMS file generation.
|
|
*
|
|
* @since 4.8.8
|
|
*
|
|
* @return void
|
|
*/
|
|
public function scheduleSingleGenerationForLlmsTxt() {
|
|
if (
|
|
! aioseo()->options->sitemap->llms->enable ||
|
|
aioseo()->actionScheduler->isScheduled( $this->llmsTxtSingleAction )
|
|
) {
|
|
return;
|
|
}
|
|
|
|
aioseo()->actionScheduler->scheduleSingle( $this->llmsTxtSingleAction, 10 );
|
|
}
|
|
|
|
/**
|
|
* Sets the site info.
|
|
*
|
|
* @since 4.8.4
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function setSiteInfo() {
|
|
$isMultisite = is_multisite();
|
|
|
|
// Check for LLMS custom title setting
|
|
$llmsTitle = aioseo()->options->sitemap->llms->advancedSettings->title;
|
|
if ( ! empty( $llmsTitle ) ) {
|
|
// Use LLMS title with hashtag tag replacement
|
|
$this->title = aioseo()->tags->replaceTags( $llmsTitle );
|
|
} else {
|
|
// Fallback to default site title
|
|
$this->title = $isMultisite
|
|
? get_blog_option( get_current_blog_id(), 'blogname' )
|
|
: get_bloginfo( 'name' );
|
|
$this->title = $this->title ?: aioseo()->meta->title->getHomePageTitle();
|
|
}
|
|
|
|
// Check for LLMS custom description setting
|
|
$llmsDescription = aioseo()->options->sitemap->llms->advancedSettings->description;
|
|
if ( ! empty( $llmsDescription ) ) {
|
|
// Use LLMS description with hashtag tag replacement
|
|
$this->description = aioseo()->tags->replaceTags( $llmsDescription );
|
|
} else {
|
|
// Fallback to default site description
|
|
$this->description = $isMultisite
|
|
? get_blog_option( get_current_blog_id(), 'blogdescription' )
|
|
: get_bloginfo( 'description' );
|
|
$this->description = $this->description ?: aioseo()->meta->description->getHomePageDescription();
|
|
}
|
|
|
|
$this->link = $isMultisite
|
|
? get_blog_option( get_current_blog_id(), 'siteurl' )
|
|
: home_url();
|
|
|
|
$this->version = aioseo()->helpers->getAioseoVersion();
|
|
}
|
|
|
|
/**
|
|
* Generates the LLMS.txt file.
|
|
*
|
|
* @since 4.8.4
|
|
*
|
|
* @return void
|
|
*/
|
|
public function generateLlmsTxt() {
|
|
if ( isset( aioseo()->options->sitemap->llms->enable ) && ! aioseo()->options->sitemap->llms->enable ) {
|
|
aioseo()->actionScheduler->unschedule( $this->llmsTxtSingleAction );
|
|
aioseo()->actionScheduler->unschedule( $this->llmsTxtRecurrentAction );
|
|
$this->deleteLlmsFile();
|
|
|
|
return;
|
|
}
|
|
|
|
$fs = aioseo()->core->fs;
|
|
$file = $this->getFilePath();
|
|
|
|
// Generate the full content
|
|
$this->setSiteInfo();
|
|
$content = $this->getHeader();
|
|
$content .= $this->getSiteDescription();
|
|
$content .= $this->getSitemapUrl();
|
|
$content .= $this->getContent();
|
|
|
|
// Add UTF-8 BOM to help browsers recognize the encoding
|
|
$content = "\xEF\xBB\xBF" . $content;
|
|
$fs->putContents( $file, $content );
|
|
}
|
|
|
|
/**
|
|
* Gets the header section of the llms.txt file.
|
|
*
|
|
* @since 4.8.4
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function getHeader( $llmsFull = false ) {
|
|
$fileName = $llmsFull ? 'llms-full.txt' : 'llms.txt';
|
|
|
|
$introText = sprintf(
|
|
/* Translators: 1 - The plugin name ("All in One SEO"), 2 - The version number */
|
|
esc_html__( 'Generated by %1$s v%2$s, this is an %3$s file, used by LLMs to index the site.', 'all-in-one-seo-pack' ),
|
|
esc_html( AIOSEO_PLUGIN_NAME ),
|
|
esc_html( aioseo()->version ),
|
|
esc_html( $fileName )
|
|
);
|
|
|
|
if ( $this->title ) {
|
|
$introText .= esc_html( "\n\n# {$this->title}\n\n" );
|
|
}
|
|
|
|
return $introText;
|
|
}
|
|
|
|
/**
|
|
* Gets the site description section of the llms.txt file.
|
|
*
|
|
* @since 4.8.4
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function getSiteDescription() {
|
|
if ( $this->description ) {
|
|
return "{$this->description}\n\n";
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Gets the sitemap link section of the llms.txt file.
|
|
*
|
|
* @since 4.8.4
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function getSitemapUrl() {
|
|
if ( ! aioseo()->options->sitemap->general->enable ) {
|
|
return '';
|
|
}
|
|
|
|
$sitemapUrl = aioseo()->sitemap->helpers->getUrl( 'general' );
|
|
|
|
return "## Sitemaps\n\n- [XML Sitemap]({$sitemapUrl}): Contains all public & indexable URLs for this website.\n\n";
|
|
}
|
|
|
|
/**
|
|
* Gets the recent content section of the llms.txt file.
|
|
*
|
|
* @since 4.8.4
|
|
*
|
|
* @param bool $llmsFull Whether to include the llms-full.txt file.
|
|
* @return string The content of the llms.txt file.
|
|
*/
|
|
protected function getContent( $llmsFull = false ) {
|
|
// Get LLMS post types settings
|
|
$includeAllPostTypes = aioseo()->options->sitemap->llms->advancedSettings->postTypes->all;
|
|
$includedPostTypes = aioseo()->options->sitemap->llms->advancedSettings->postTypes->included;
|
|
$includeAllTaxonomies = aioseo()->options->sitemap->llms->advancedSettings->taxonomies->all;
|
|
$includedTaxonomies = aioseo()->options->sitemap->llms->advancedSettings->taxonomies->included;
|
|
|
|
// Determine which post types to include
|
|
if ( $includeAllPostTypes ) {
|
|
// Include all public post types except attachments
|
|
$postTypes = array_filter( aioseo()->helpers->getPublicPostTypes( true ), function( $type ) {
|
|
return 'attachment' !== $type;
|
|
} );
|
|
} else {
|
|
// Only include the specifically selected post types, but still exclude attachments
|
|
$postTypes = array_filter( $includedPostTypes, function( $type ) {
|
|
return 'attachment' !== $type;
|
|
} );
|
|
}
|
|
if ( $includeAllTaxonomies ) {
|
|
$taxonomies = aioseo()->helpers->getPublicTaxonomies( true );
|
|
} else {
|
|
$taxonomies = $includedTaxonomies;
|
|
}
|
|
$originalSitemapType = aioseo()->sitemap->type;
|
|
$originalLinksPerIndex = aioseo()->sitemap->linksPerIndex;
|
|
$originalIndexes = aioseo()->sitemap->indexes;
|
|
|
|
aioseo()->sitemap->type = 'llms';
|
|
aioseo()->sitemap->indexes = true;
|
|
aioseo()->sitemap->linksPerIndex = aioseo()->options->sitemap->llms->advancedSettings->linksPerPostTax
|
|
? aioseo()->options->sitemap->llms->advancedSettings->linksPerPostTax :
|
|
20;
|
|
|
|
$content = '';
|
|
foreach ( $postTypes as $postType ) {
|
|
$postTypeObject = get_post_type_object( $postType );
|
|
if ( ! $postTypeObject ) {
|
|
continue;
|
|
}
|
|
|
|
$posts = aioseo()->sitemap->query->posts( $postType );
|
|
|
|
if ( ! empty( $posts ) ) {
|
|
$content .= '## ' . $postTypeObject->labels->name . "\n\n";
|
|
foreach ( $posts as $postObject ) {
|
|
$post = get_post( $postObject->ID );
|
|
if ( ! is_a( $post, 'WP_Post' ) ) {
|
|
continue;
|
|
}
|
|
|
|
aioseo()->helpers->setWpQueryPost( $post );
|
|
|
|
$content .= $this->getPostContent( $post, $llmsFull );
|
|
|
|
aioseo()->helpers->restoreWpQuery();
|
|
}
|
|
|
|
$content .= "\n";
|
|
}
|
|
}
|
|
|
|
// Initialize sitemap settings again for terms
|
|
aioseo()->sitemap->type = 'llms';
|
|
aioseo()->sitemap->indexes = true;
|
|
aioseo()->sitemap->linksPerIndex = aioseo()->options->sitemap->llms->advancedSettings->linksPerPostTax
|
|
? aioseo()->options->sitemap->llms->advancedSettings->linksPerPostTax :
|
|
20;
|
|
|
|
// Get recent terms for each taxonomy using sitemap query
|
|
foreach ( $taxonomies as $taxonomy ) {
|
|
$taxonomyObject = get_taxonomy( $taxonomy );
|
|
if ( ! $taxonomyObject ) {
|
|
continue;
|
|
}
|
|
|
|
$terms = aioseo()->sitemap->query->terms( $taxonomy );
|
|
|
|
if ( ! empty( $terms ) ) {
|
|
$content .= '## ' . $taxonomyObject->labels->name . "\n\n";
|
|
foreach ( $terms as $termObject ) {
|
|
if ( is_object( $termObject ) && ! empty( $termObject->term_id ) ) {
|
|
$term = get_term( $termObject->term_id, $taxonomy );
|
|
if ( is_wp_error( $term ) ) {
|
|
continue;
|
|
}
|
|
|
|
aioseo()->helpers->setWpQueryTerm( $term, $taxonomy );
|
|
|
|
$content .= $this->getTermContent( $term, $taxonomy, $llmsFull );
|
|
|
|
aioseo()->helpers->restoreWpQuery();
|
|
}
|
|
}
|
|
$content .= "\n";
|
|
}
|
|
}
|
|
|
|
// Restore original sitemap settings
|
|
aioseo()->sitemap->type = $originalSitemapType;
|
|
aioseo()->sitemap->linksPerIndex = $originalLinksPerIndex;
|
|
aioseo()->sitemap->indexes = $originalIndexes;
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Gets the post content section of the llms.txt file.
|
|
*
|
|
* @since 4.8.8
|
|
*
|
|
* @param \WP_Post $post The post object.
|
|
* @param bool $llmsFull Whether to include the llms-full.txt file.
|
|
* @return string The content of the llms.txt file.
|
|
*/
|
|
protected function getPostContent( $post, $llmsFull = false ) { // phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
|
$title = apply_filters( 'aioseo_llms_post_title', $post->post_title, $post );
|
|
$content = '- [' . aioseo()->helpers->decodeHtmlEntities( $title ) . '](' . aioseo()->helpers->decodeUrl( get_permalink( $post ) ) . ')';
|
|
|
|
$description = aioseo()->meta->description->getPostDescription( $post );
|
|
$description = apply_filters( 'aioseo_llms_post_description', $description, $post );
|
|
if ( ! empty( $description ) ) {
|
|
$content .= ' - ' . aioseo()->helpers->decodeHtmlEntities( $description );
|
|
}
|
|
|
|
$content .= "\n";
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Gets the term content section of the llms.txt file.
|
|
*
|
|
* @since 4.9.3
|
|
*
|
|
* @param \WP_Term $term The term object.
|
|
* @param string $taxonomy The taxonomy name.
|
|
* @param bool $llmsFull Whether to include the llms-full.txt file.
|
|
* @return string The content of the llms.txt file.
|
|
*/
|
|
protected function getTermContent( $term, $taxonomy, $llmsFull = false ) { // phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
|
$title = apply_filters( 'aioseo_llms_term_title', $term->name, $term );
|
|
$content = '- [' . aioseo()->helpers->decodeHtmlEntities( $title ) . '](' . aioseo()->helpers->decodeUrl( get_term_link( $term, $taxonomy ) ) . ')';
|
|
|
|
$description = aioseo()->meta->description->getTermDescription( $term );
|
|
$description = apply_filters( 'aioseo_llms_term_description', $description, $term );
|
|
if ( ! empty( $description ) ) {
|
|
$content .= ' - ' . aioseo()->helpers->decodeHtmlEntities( $description );
|
|
}
|
|
|
|
$content .= "\n";
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Deletes the LLMS.txt file.
|
|
*
|
|
* @since 4.8.8
|
|
*
|
|
* @return void
|
|
*/
|
|
public function deleteLlmsFile() {
|
|
$fs = aioseo()->core->fs;
|
|
$file = $this->getFilePath();
|
|
if ( $fs->isWpfsValid() ) {
|
|
$fs->fs->delete( $file, false, 'f' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the file path for the llms.txt or llms-full.txt file.
|
|
*
|
|
* Uses `dirname( WP_CONTENT_DIR )` instead of `ABSPATH` to support non-standard
|
|
* WordPress installations where `ABSPATH` doesn't point to the web root.
|
|
*
|
|
* @since 4.9.5
|
|
*
|
|
* @param bool $full Whether to get the full version path.
|
|
* @return string The file path.
|
|
*/
|
|
public function getFilePath( $full = false ) {
|
|
$filename = $full ? 'llms-full.txt' : 'llms.txt';
|
|
|
|
return trailingslashit( dirname( WP_CONTENT_DIR ) ) . sanitize_file_name( $filename );
|
|
}
|
|
} |