first commit
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Application\File\Commands;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders\Markdown_Builder;
|
||||
use Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_File_System_Adapter;
|
||||
use Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_Llms_Txt_Permission_Gate;
|
||||
|
||||
/**
|
||||
* Handles the population of the llms.txt.
|
||||
*/
|
||||
class Populate_File_Command_Handler {
|
||||
|
||||
public const CONTENT_HASH_OPTION = 'wpseo_llms_txt_content_hash';
|
||||
public const GENERATION_FAILURE_OPTION = 'wpseo_llms_txt_file_failure';
|
||||
|
||||
/**
|
||||
* The permission gate.
|
||||
*
|
||||
* @var WordPress_Llms_Txt_Permission_Gate $permission_gate
|
||||
*/
|
||||
private $permission_gate;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The file system adapter.
|
||||
*
|
||||
* @var WordPress_File_System_Adapter
|
||||
*/
|
||||
private $file_system_adapter;
|
||||
|
||||
/**
|
||||
* The markdown builder.
|
||||
*
|
||||
* @var Markdown_Builder
|
||||
*/
|
||||
private $markdown_builder;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param WordPress_File_System_Adapter $file_system_adapter The file system adapter.
|
||||
* @param Markdown_Builder $markdown_builder The markdown builder.
|
||||
* @param WordPress_Llms_Txt_Permission_Gate $permission_gate The editing permission checker.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options_helper,
|
||||
WordPress_File_System_Adapter $file_system_adapter,
|
||||
Markdown_Builder $markdown_builder,
|
||||
WordPress_Llms_Txt_Permission_Gate $permission_gate
|
||||
) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->file_system_adapter = $file_system_adapter;
|
||||
$this->markdown_builder = $markdown_builder;
|
||||
$this->permission_gate = $permission_gate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle() {
|
||||
if ( $this->permission_gate->is_managed_by_yoast_seo() ) {
|
||||
$content = $this->markdown_builder->render();
|
||||
$content = $this->encode_content( $content );
|
||||
$file_written = $this->file_system_adapter->set_file_content( $content );
|
||||
|
||||
if ( $file_written ) {
|
||||
// Maybe move this to a class if we need to handle this option more often.
|
||||
\update_option( self::CONTENT_HASH_OPTION, \md5( $content ) );
|
||||
\delete_option( self::GENERATION_FAILURE_OPTION );
|
||||
return;
|
||||
}
|
||||
|
||||
\update_option( self::GENERATION_FAILURE_OPTION, 'filesystem_permissions' );
|
||||
return;
|
||||
}
|
||||
|
||||
\update_option( self::GENERATION_FAILURE_OPTION, 'not_managed_by_yoast_seo' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the content by prepending it with the Byte Order Mark (BOM) for UTF-8.
|
||||
*
|
||||
* @param string $content The content to encode.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function encode_content( string $content ): string {
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_llmstxt_encoding_prefix' - Allows editing the Byte Order Mark (BOM) for UTF-8 we prepend to the llmst.txt file.
|
||||
*
|
||||
* @param string $encoding_prefix The Byte Order Mark (BOM) for UTF-8 we prepend to the llmst.txt file.
|
||||
*/
|
||||
$encoding_prefix = \apply_filters( 'wpseo_llmstxt_encoding_prefix', "\xEF\xBB\xBF" );
|
||||
|
||||
return $encoding_prefix . $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Application\File\Commands;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_File_System_Adapter;
|
||||
use Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_Llms_Txt_Permission_Gate;
|
||||
|
||||
/**
|
||||
* Handles the removal of the llms.txt
|
||||
*/
|
||||
class Remove_File_Command_Handler {
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The file system adapter.
|
||||
*
|
||||
* @var WordPress_File_System_Adapter
|
||||
*/
|
||||
private $file_system_adapter;
|
||||
|
||||
/**
|
||||
* The permission gate.
|
||||
*
|
||||
* @var WordPress_Llms_Txt_Permission_Gate $permission_gate
|
||||
*/
|
||||
private $permission_gate;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param WordPress_File_System_Adapter $file_system_adapter The file system adapter.
|
||||
* @param WordPress_Llms_Txt_Permission_Gate $permission_gate The permission gate.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options_helper,
|
||||
WordPress_File_System_Adapter $file_system_adapter,
|
||||
WordPress_Llms_Txt_Permission_Gate $permission_gate
|
||||
) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->file_system_adapter = $file_system_adapter;
|
||||
$this->permission_gate = $permission_gate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle() {
|
||||
if ( $this->permission_gate->is_managed_by_yoast_seo() ) {
|
||||
$file_removed = $this->file_system_adapter->remove_file();
|
||||
|
||||
if ( $file_removed ) {
|
||||
// Maybe move this to a class if we need to handle this option more often.
|
||||
\update_option( Populate_File_Command_Handler::CONTENT_HASH_OPTION, '' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Application\File;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
|
||||
/**
|
||||
* Responsible for scheduling and unscheduling the cron.
|
||||
*/
|
||||
class Llms_Txt_Cron_Scheduler {
|
||||
|
||||
/**
|
||||
* The name of the cron job.
|
||||
*/
|
||||
public const LLMS_TXT_POPULATION = 'wpseo_llms_txt_population';
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options_helper
|
||||
) {
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the llms txt population cron a week from now.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function schedule_weekly_llms_txt_population(): void {
|
||||
if ( $this->options_helper->get( 'enable_llms_txt', false ) !== true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! \wp_next_scheduled( self::LLMS_TXT_POPULATION ) ) {
|
||||
\wp_schedule_event( ( \time() + \WEEK_IN_SECONDS ), 'weekly', self::LLMS_TXT_POPULATION );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the llms txt population cron 5 minutes from now.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function schedule_quick_llms_txt_population(): void {
|
||||
if ( $this->options_helper->get( 'enable_llms_txt', false ) !== true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( \wp_next_scheduled( self::LLMS_TXT_POPULATION ) ) {
|
||||
$this->unschedule_llms_txt_population();
|
||||
}
|
||||
|
||||
\wp_schedule_event( ( \time() + ( \MINUTE_IN_SECONDS * 5 ) ), 'weekly', self::LLMS_TXT_POPULATION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unschedules the llms txt population cron.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unschedule_llms_txt_population() {
|
||||
$scheduled = \wp_next_scheduled( self::LLMS_TXT_POPULATION );
|
||||
if ( $scheduled ) {
|
||||
\wp_unschedule_event( $scheduled, self::LLMS_TXT_POPULATION );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Application\Health_Check;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Llms_Txt\User_Interface\Health_Check\File_Reports;
|
||||
use Yoast\WP\SEO\Services\Health_Check\Health_Check;
|
||||
|
||||
/**
|
||||
* Fails when the llms.txt file fails to be generated.
|
||||
*/
|
||||
class File_Check extends Health_Check {
|
||||
|
||||
/**
|
||||
* Runs the health check.
|
||||
*
|
||||
* @var File_Runner
|
||||
*/
|
||||
private $runner;
|
||||
|
||||
/**
|
||||
* Generates WordPress-friendly health check results.
|
||||
*
|
||||
* @var File_Reports
|
||||
*/
|
||||
private $reports;
|
||||
|
||||
/**
|
||||
* The Options_Helper instance.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param File_Runner $runner The object that implements the actual health check.
|
||||
* @param File_Reports $reports The object that generates WordPress-friendly results.
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct(
|
||||
File_Runner $runner,
|
||||
File_Reports $reports,
|
||||
Options_Helper $options_helper
|
||||
) {
|
||||
$this->runner = $runner;
|
||||
$this->reports = $reports;
|
||||
$this->options_helper = $options_helper;
|
||||
|
||||
$this->reports->set_test_identifier( $this->get_test_identifier() );
|
||||
$this->set_runner( $this->runner );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WordPress-friendly health check result.
|
||||
*
|
||||
* @return string[] The WordPress-friendly health check result.
|
||||
*/
|
||||
protected function get_result() {
|
||||
if ( $this->runner->is_successful() ) {
|
||||
return $this->reports->get_success_result();
|
||||
}
|
||||
|
||||
return $this->reports->get_generation_failure_result( $this->runner->get_generation_failure_reason() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the llms.txt feature is disabled.
|
||||
*
|
||||
* @return bool Whether the health check should be excluded from the results.
|
||||
*/
|
||||
public function is_excluded() {
|
||||
return $this->options_helper->get( 'enable_llms_txt', false ) !== true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Application\Health_Check;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Populate_File_Command_Handler;
|
||||
use Yoast\WP\SEO\Services\Health_Check\Runner_Interface;
|
||||
|
||||
/**
|
||||
* Runs the File_Generation health check.
|
||||
*/
|
||||
class File_Runner implements Runner_Interface {
|
||||
|
||||
/**
|
||||
* Is set to non-empty string when the llms.txt file failed to (re-)generate.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $generation_failure_reason = '';
|
||||
|
||||
/**
|
||||
* Runs the health check.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run() {
|
||||
$this->generation_failure_reason = \get_option( Populate_File_Command_Handler::GENERATION_FAILURE_OPTION, '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is no generation failure reason.
|
||||
*
|
||||
* @return bool The boolean indicating if the health check was succesful.
|
||||
*/
|
||||
public function is_successful() {
|
||||
return $this->generation_failure_reason === '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the generation failure reason.
|
||||
*
|
||||
* @return string The boolean indicating if the health check was succesful.
|
||||
*/
|
||||
public function get_generation_failure_reason(): string {
|
||||
return $this->generation_failure_reason;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Description;
|
||||
use Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Description_Adapter;
|
||||
|
||||
/**
|
||||
* The builder of the description section.
|
||||
*/
|
||||
class Description_Builder {
|
||||
|
||||
/**
|
||||
* The description adapter.
|
||||
*
|
||||
* @var Description_Adapter
|
||||
*/
|
||||
protected $description_adapter;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param Description_Adapter $description_adapter The description adapter.
|
||||
*/
|
||||
public function __construct(
|
||||
Description_Adapter $description_adapter
|
||||
) {
|
||||
$this->description_adapter = $description_adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the description section.
|
||||
*
|
||||
* @return Description The description section.
|
||||
*/
|
||||
public function build_description(): Description {
|
||||
return $this->description_adapter->get_description();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Intro;
|
||||
use Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Sitemap_Link_Collector;
|
||||
|
||||
/**
|
||||
* The builder of the intro section.
|
||||
*/
|
||||
class Intro_Builder {
|
||||
|
||||
/**
|
||||
* The sitemap link collector.
|
||||
*
|
||||
* @var Sitemap_Link_Collector
|
||||
*/
|
||||
protected $sitemap_link_collector;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Sitemap_Link_Collector $sitemap_link_collector The sitemap link collector.
|
||||
*/
|
||||
public function __construct(
|
||||
Sitemap_Link_Collector $sitemap_link_collector
|
||||
) {
|
||||
$this->sitemap_link_collector = $sitemap_link_collector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin version that generated the llms.txt file.
|
||||
*
|
||||
* @return string The plugin version that generated the llms.txt file.
|
||||
*/
|
||||
protected function get_generator_version(): string {
|
||||
return 'Yoast SEO v' . \WPSEO_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the intro section.
|
||||
*
|
||||
* @return Intro The intro section.
|
||||
*/
|
||||
public function build_intro(): Intro {
|
||||
$intro_content = \sprintf(
|
||||
'Generated by %s, this is an llms.txt file, meant for consumption by LLMs.',
|
||||
$this->get_generator_version()
|
||||
);
|
||||
$intro_links = [];
|
||||
|
||||
$sitemap_link = $this->sitemap_link_collector->get_link();
|
||||
if ( $sitemap_link !== null ) {
|
||||
$intro_links[] = $sitemap_link;
|
||||
|
||||
$intro_content .= \PHP_EOL . \PHP_EOL . 'This is the %s of this website.';
|
||||
}
|
||||
|
||||
return new Intro( $intro_content, $intro_links );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Link_List;
|
||||
use Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Content_Types_Collector;
|
||||
use Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Terms_Collector;
|
||||
|
||||
|
||||
/**
|
||||
* The builder of the link list sections.
|
||||
*/
|
||||
class Link_Lists_Builder {
|
||||
|
||||
/**
|
||||
* The content types collector.
|
||||
*
|
||||
* @var Content_Types_Collector
|
||||
*/
|
||||
private $content_types_collector;
|
||||
|
||||
/**
|
||||
* The terms collector.
|
||||
*
|
||||
* @var Terms_Collector
|
||||
*/
|
||||
private $terms_collector;
|
||||
|
||||
/**
|
||||
* Constructs the class.
|
||||
*
|
||||
* @param Content_Types_Collector $content_types_collector The content types collector.
|
||||
* @param Terms_Collector $terms_collector The terms collector.
|
||||
*/
|
||||
public function __construct(
|
||||
Content_Types_Collector $content_types_collector,
|
||||
Terms_Collector $terms_collector
|
||||
) {
|
||||
$this->content_types_collector = $content_types_collector;
|
||||
$this->terms_collector = $terms_collector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the link list sections.
|
||||
*
|
||||
* @return Link_List[] The link list sections.
|
||||
*/
|
||||
public function build_link_lists(): array {
|
||||
return \array_merge(
|
||||
$this->content_types_collector->get_content_types_lists(),
|
||||
$this->terms_collector->get_terms_lists()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper;
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Llms_Txt_Renderer;
|
||||
|
||||
/**
|
||||
* The builder of the markdown file.
|
||||
*/
|
||||
class Markdown_Builder {
|
||||
|
||||
/**
|
||||
* The renderer of the LLMs.txt file.
|
||||
*
|
||||
* @var Llms_Txt_Renderer
|
||||
*/
|
||||
protected $llms_txt_renderer;
|
||||
|
||||
/**
|
||||
* The intro builder.
|
||||
*
|
||||
* @var Intro_Builder
|
||||
*/
|
||||
protected $intro_builder;
|
||||
|
||||
/**
|
||||
* The title builder.
|
||||
*
|
||||
* @var Title_Builder
|
||||
*/
|
||||
protected $title_builder;
|
||||
|
||||
/**
|
||||
* The description builder.
|
||||
*
|
||||
* @var Description_Builder
|
||||
*/
|
||||
protected $description_builder;
|
||||
|
||||
/**
|
||||
* The link lists builder.
|
||||
*
|
||||
* @var Link_Lists_Builder
|
||||
*/
|
||||
protected $link_lists_builder;
|
||||
|
||||
/**
|
||||
* The markdown escaper.
|
||||
*
|
||||
* @var Markdown_Escaper
|
||||
*/
|
||||
protected $markdown_escaper;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Llms_Txt_Renderer $llms_txt_renderer The renderer of the LLMs.txt file.
|
||||
* @param Intro_Builder $intro_builder The intro builder.
|
||||
* @param Title_Builder $title_builder The title builder.
|
||||
* @param Description_Builder $description_builder The description builder.
|
||||
* @param Link_Lists_Builder $link_lists_builder The link lists builder.
|
||||
* @param Markdown_Escaper $markdown_escaper The markdown escaper.
|
||||
*/
|
||||
public function __construct(
|
||||
Llms_Txt_Renderer $llms_txt_renderer,
|
||||
Intro_Builder $intro_builder,
|
||||
Title_Builder $title_builder,
|
||||
Description_Builder $description_builder,
|
||||
Link_Lists_Builder $link_lists_builder,
|
||||
Markdown_Escaper $markdown_escaper
|
||||
) {
|
||||
$this->llms_txt_renderer = $llms_txt_renderer;
|
||||
$this->intro_builder = $intro_builder;
|
||||
$this->title_builder = $title_builder;
|
||||
$this->description_builder = $description_builder;
|
||||
$this->link_lists_builder = $link_lists_builder;
|
||||
$this->markdown_escaper = $markdown_escaper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the markdown.
|
||||
*
|
||||
* @return string The rendered markdown.
|
||||
*/
|
||||
public function render(): string {
|
||||
$this->llms_txt_renderer->add_section( $this->intro_builder->build_intro() );
|
||||
$this->llms_txt_renderer->add_section( $this->title_builder->build_title() );
|
||||
$this->llms_txt_renderer->add_section( $this->description_builder->build_description() );
|
||||
|
||||
foreach ( $this->link_lists_builder->build_link_lists() as $link_list ) {
|
||||
$this->llms_txt_renderer->add_section( $link_list );
|
||||
}
|
||||
|
||||
foreach ( $this->llms_txt_renderer->get_sections() as $section ) {
|
||||
$section->escape_markdown( $this->markdown_escaper );
|
||||
}
|
||||
|
||||
return $this->llms_txt_renderer->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Title;
|
||||
use Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Title_Adapter;
|
||||
|
||||
|
||||
/**
|
||||
* The builder of the title section.
|
||||
*/
|
||||
class Title_Builder {
|
||||
|
||||
/**
|
||||
* The title adapter.
|
||||
*
|
||||
* @var Title_Adapter
|
||||
*/
|
||||
protected $title_adapter;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Title_Adapter $title_adapter The title adapter.
|
||||
*/
|
||||
public function __construct(
|
||||
Title_Adapter $title_adapter
|
||||
) {
|
||||
$this->title_adapter = $title_adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the title section.
|
||||
*
|
||||
* @return Title The title section.
|
||||
*/
|
||||
public function build_title(): Title {
|
||||
return $this->title_adapter->get_title();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Application;
|
||||
|
||||
/**
|
||||
* The escaper of markdown.
|
||||
*/
|
||||
class Markdown_Escaper {
|
||||
|
||||
/**
|
||||
* Escapes markdown text.
|
||||
*
|
||||
* @param string $text The markdown text to escape.
|
||||
*
|
||||
* @return string The escaped markdown text.
|
||||
*/
|
||||
public function escape_markdown_content( $text ) {
|
||||
// We have to decode the text first mostly because ampersands will be escaped below.
|
||||
$text = \html_entity_decode( $text, \ENT_QUOTES, 'UTF-8' );
|
||||
|
||||
// Define a regex pattern for all the special characters in markdown that we want to escape.
|
||||
$pattern = '/[-#*+`._[\]()!&<>_{}|]/';
|
||||
|
||||
$replacement = static function ( $matches ) {
|
||||
return '\\' . $matches[0];
|
||||
};
|
||||
|
||||
return \preg_replace_callback( $pattern, $replacement, $text );
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes URLs in markdown.
|
||||
*
|
||||
* @param string $url The markdown URL to escape.
|
||||
*
|
||||
* @return string The escaped markdown URL.
|
||||
*/
|
||||
public function escape_markdown_url( $url ) {
|
||||
$escaped_url = \str_replace( [ ' ', '(', ')', '\\' ], [ '%20', '%28', '%29', '%5C' ], $url );
|
||||
|
||||
return $escaped_url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Domain\File;
|
||||
|
||||
/**
|
||||
* Interface to describe handeling the llms.txt file.
|
||||
*/
|
||||
interface Llms_File_System_Interface {
|
||||
|
||||
/**
|
||||
* Method to set the llms.txt file content.
|
||||
*
|
||||
* @param string $content The content for the file.
|
||||
*
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function set_file_content( string $content ): bool;
|
||||
|
||||
/**
|
||||
* Method to remove the llms.txt file from the file system.
|
||||
*
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function remove_file(): bool;
|
||||
|
||||
/**
|
||||
* Gets the contents of the current llms.txt file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_file_contents(): string;
|
||||
|
||||
/**
|
||||
* Checks if the llms.txt file exists.
|
||||
*
|
||||
* @return bool Whether the llms.txt file exists.
|
||||
*/
|
||||
public function file_exists(): bool;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Domain\File;
|
||||
|
||||
/**
|
||||
* This interface is responsible for defining ways to make sure we can edit/regenerate the llms.txt file.
|
||||
*/
|
||||
interface Llms_Txt_Permission_Gate_Interface {
|
||||
|
||||
/**
|
||||
* Checks if Yoast SEO manages the llms.txt.
|
||||
*
|
||||
* @return bool Checks if Yoast SEO manages the llms.txt.
|
||||
*/
|
||||
public function is_managed_by_yoast_seo(): bool;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper;
|
||||
/**
|
||||
* Represents a markdown item.
|
||||
*/
|
||||
interface Item_Interface {
|
||||
|
||||
/**
|
||||
* Renders the item.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render(): string;
|
||||
|
||||
/**
|
||||
* Escapes the markdown content.
|
||||
*
|
||||
* @param Markdown_Escaper $markdown_escaper The markdown escaper.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function escape_markdown( Markdown_Escaper $markdown_escaper ): void;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper;
|
||||
|
||||
/**
|
||||
* Represents a link markdown item.
|
||||
*/
|
||||
class Link implements Item_Interface {
|
||||
|
||||
/**
|
||||
* The description that is part of this link.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $description;
|
||||
|
||||
/**
|
||||
* The link text.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $text;
|
||||
|
||||
/**
|
||||
* The anchor text.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $anchor;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param string $text The link text.
|
||||
* @param string $anchor The anchor text.
|
||||
* @param string $description The description.
|
||||
*/
|
||||
public function __construct( string $text, string $anchor, string $description = '' ) {
|
||||
$this->text = $text;
|
||||
$this->anchor = $anchor;
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the link item.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render(): string {
|
||||
$description = ( $this->description !== '' ) ? ": $this->description" : '';
|
||||
return "[$this->text]($this->anchor)$description";
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes the markdown content.
|
||||
*
|
||||
* @param param Markdown_Escaper $markdown_escaper The markdown escaper.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function escape_markdown( Markdown_Escaper $markdown_escaper ): void {
|
||||
$this->text = $markdown_escaper->escape_markdown_content( $this->text );
|
||||
$this->description = $markdown_escaper->escape_markdown_content( $this->description );
|
||||
$this->anchor = $markdown_escaper->escape_markdown_url( $this->anchor );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Section_Interface;
|
||||
|
||||
/**
|
||||
* The renderer of the LLMs.txt file.
|
||||
*/
|
||||
class Llms_Txt_Renderer {
|
||||
|
||||
/**
|
||||
* The sections.
|
||||
*
|
||||
* @var Section_Interface[]
|
||||
*/
|
||||
private $sections;
|
||||
|
||||
/**
|
||||
* Adds a section.
|
||||
*
|
||||
* @param Section_Interface $section The section to add.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_section( Section_Interface $section ): void {
|
||||
$this->sections[] = $section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sections.
|
||||
*
|
||||
* @return Section_Interface[]
|
||||
*/
|
||||
public function get_sections(): array {
|
||||
return $this->sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the items of the bucket.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render(): string {
|
||||
if ( empty( $this->sections ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$rendered_sections = [];
|
||||
foreach ( $this->sections as $section ) {
|
||||
$section_content = $section->render();
|
||||
if ( $section_content === '' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rendered_sections[] = $section->get_prefix() . $section_content . \PHP_EOL;
|
||||
}
|
||||
|
||||
return \implode( \PHP_EOL, $rendered_sections );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper;
|
||||
/**
|
||||
* Represents the description section.
|
||||
*/
|
||||
class Description implements Section_Interface {
|
||||
|
||||
/**
|
||||
* The description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $description;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param string $description The description.
|
||||
*/
|
||||
public function __construct( string $description ) {
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prefix of the description section.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_prefix(): string {
|
||||
return '> ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the description section.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render(): string {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes the markdown content.
|
||||
*
|
||||
* @param Markdown_Escaper $markdown_escaper The markdown escaper.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function escape_markdown( Markdown_Escaper $markdown_escaper ): void {
|
||||
$this->description = $markdown_escaper->escape_markdown_content( $this->description );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper;
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items\Link;
|
||||
|
||||
/**
|
||||
* Represents the intro section.
|
||||
*/
|
||||
class Intro implements Section_Interface {
|
||||
|
||||
/**
|
||||
* The intro content.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $intro_content;
|
||||
|
||||
/**
|
||||
* The intro links.
|
||||
*
|
||||
* @var Link[]
|
||||
*/
|
||||
private $intro_links = [];
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param string $intro_content The intro content.
|
||||
* @param Link[] $intro_links The intro links.
|
||||
*/
|
||||
public function __construct( string $intro_content, array $intro_links ) {
|
||||
$this->intro_content = $intro_content;
|
||||
|
||||
foreach ( $intro_links as $link ) {
|
||||
$this->add_link( $link );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prefix of the intro section.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_prefix(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a link to the intro section.
|
||||
*
|
||||
* @param Link $link The link to add.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_link( Link $link ): void {
|
||||
$this->intro_links[] = $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the intro section.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render(): string {
|
||||
if ( \count( $this->intro_links ) === 0 ) {
|
||||
return $this->intro_content;
|
||||
}
|
||||
|
||||
$rendered_links = \array_map(
|
||||
static function ( $link ) {
|
||||
return $link->render();
|
||||
},
|
||||
$this->intro_links
|
||||
);
|
||||
|
||||
$this->intro_content = \sprintf(
|
||||
$this->intro_content,
|
||||
...$rendered_links
|
||||
);
|
||||
return $this->intro_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes the markdown content.
|
||||
*
|
||||
* @param Markdown_Escaper $markdown_escaper The markdown escaper.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function escape_markdown( Markdown_Escaper $markdown_escaper ): void {
|
||||
foreach ( $this->intro_links as $link ) {
|
||||
$link->escape_markdown( $markdown_escaper );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper;
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items\Link;
|
||||
|
||||
/**
|
||||
* Represents a link list markdown section.
|
||||
*/
|
||||
class Link_List implements Section_Interface {
|
||||
|
||||
/**
|
||||
* The type of the links.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* The links.
|
||||
*
|
||||
* @var Link[]
|
||||
*/
|
||||
private $links = [];
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param string $type The type of the links.
|
||||
* @param Link[] $links The links.
|
||||
*/
|
||||
public function __construct( string $type, array $links ) {
|
||||
$this->type = $type;
|
||||
|
||||
foreach ( $links as $link ) {
|
||||
$this->add_link( $link );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a link to the list.
|
||||
*
|
||||
* @param Link $link The link to add.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_link( Link $link ): void {
|
||||
$this->links[] = $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prefix of the link list section.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_prefix(): string {
|
||||
return '## ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the link item.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render(): string {
|
||||
if ( empty( $this->links ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$rendered_links = [];
|
||||
foreach ( $this->links as $link ) {
|
||||
$rendered_links[] = '- ' . $link->render();
|
||||
}
|
||||
|
||||
return $this->type . \PHP_EOL . \implode( \PHP_EOL, $rendered_links );
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes the markdown content.
|
||||
*
|
||||
* @param Markdown_Escaper $markdown_escaper The markdown escaper.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function escape_markdown( Markdown_Escaper $markdown_escaper ): void {
|
||||
$this->type = $markdown_escaper->escape_markdown_content( $this->type );
|
||||
|
||||
foreach ( $this->links as $link ) {
|
||||
$link->escape_markdown( $markdown_escaper );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items\Item_Interface;
|
||||
|
||||
/**
|
||||
* Represents a section.
|
||||
*/
|
||||
interface Section_Interface extends Item_Interface {
|
||||
|
||||
/**
|
||||
* Returns the prefix of the section.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_prefix(): string;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper;
|
||||
|
||||
/**
|
||||
* Represents the title section.
|
||||
*/
|
||||
class Title implements Section_Interface {
|
||||
|
||||
/**
|
||||
* The site title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $site_title;
|
||||
|
||||
/**
|
||||
* The site tagline.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $site_tagline;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param string $site_title The site title.
|
||||
* @param string $site_tagline The site tagline.
|
||||
*/
|
||||
public function __construct(
|
||||
string $site_title,
|
||||
string $site_tagline
|
||||
) {
|
||||
$this->site_title = $site_title;
|
||||
$this->site_tagline = $site_tagline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prefix of the section.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_prefix(): string {
|
||||
return '# ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the title section.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render(): string {
|
||||
if ( $this->site_tagline === '' ) {
|
||||
return $this->site_title;
|
||||
}
|
||||
|
||||
if ( $this->site_title === '' ) {
|
||||
return $this->site_tagline;
|
||||
}
|
||||
|
||||
return "$this->site_title: $this->site_tagline";
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes the markdown content.
|
||||
*
|
||||
* @param Markdown_Escaper $markdown_escaper The markdown escaper.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function escape_markdown( Markdown_Escaper $markdown_escaper ): void {
|
||||
$this->site_title = $markdown_escaper->escape_markdown_content( $this->site_title );
|
||||
$this->site_tagline = $markdown_escaper->escape_markdown_content( $this->site_tagline );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Infrastructure\File;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\File\Llms_File_System_Interface;
|
||||
|
||||
/**
|
||||
* Adapter class for handling file system operations in a WordPress environment.
|
||||
*/
|
||||
class WordPress_File_System_Adapter implements Llms_File_System_Interface {
|
||||
|
||||
/**
|
||||
* Creates a file and writes the specified content to it.
|
||||
*
|
||||
* @param string $content The content to write into the file.
|
||||
*
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function set_file_content( string $content ): bool {
|
||||
if ( $this->is_file_system_available() ) {
|
||||
global $wp_filesystem;
|
||||
$result = $wp_filesystem->put_contents(
|
||||
$this->get_llms_file_path(),
|
||||
$content,
|
||||
\FS_CHMOD_FILE
|
||||
);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the llms.txt from the filesystem.
|
||||
*
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function remove_file(): bool {
|
||||
if ( $this->is_file_system_available() ) {
|
||||
global $wp_filesystem;
|
||||
$result = $wp_filesystem->delete( $this->get_llms_file_path() );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contents of the current llms.txt file.
|
||||
*
|
||||
* @return string The content of the file.
|
||||
*/
|
||||
public function get_file_contents(): string {
|
||||
if ( $this->is_file_system_available() ) {
|
||||
global $wp_filesystem;
|
||||
|
||||
return $wp_filesystem->get_contents( $this->get_llms_file_path() );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the llms.txt file exists.
|
||||
*
|
||||
* @return bool Whether the llms.txt file exists.
|
||||
*/
|
||||
public function file_exists(): bool {
|
||||
if ( $this->is_file_system_available() ) {
|
||||
global $wp_filesystem;
|
||||
|
||||
return $wp_filesystem->exists( $this->get_llms_file_path() );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the file system is available.
|
||||
*
|
||||
* @return bool If the file system is available.
|
||||
*/
|
||||
private function is_file_system_available(): ?bool {
|
||||
if ( ! \function_exists( 'WP_Filesystem' ) ) {
|
||||
require_once \ABSPATH . 'wp-admin/includes/file.php';
|
||||
}
|
||||
|
||||
return \WP_Filesystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the path to the llms.txt file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_llms_file_path(): string {
|
||||
|
||||
$llms_filesystem_path = \get_home_path();
|
||||
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput -- Reason: This is how we used this for the robots.txt file as well.
|
||||
if ( ! \is_writable( $llms_filesystem_path ) && ! empty( $_SERVER['DOCUMENT_ROOT'] ) ) {
|
||||
$llms_filesystem_path = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
// phpcs:enable WordPress.Security.ValidatedSanitizedInput
|
||||
|
||||
/**
|
||||
* Filter: 'wpseo_llmstxt_filesystem_path' - Allows editing the filesystem path of the llmst.txt file to account for server restrictions to the filesystem.
|
||||
*
|
||||
* @param string $llms_filesystem_path The filesystem path of the llmst.txt file that defaults to get_home_path() or the $_SERVER['DOCUMENT_ROOT'] if the home path is not writeable.
|
||||
*/
|
||||
$llms_filesystem_path = \apply_filters( 'wpseo_llmstxt_filesystem_path', $llms_filesystem_path );
|
||||
|
||||
return \trailingslashit( $llms_filesystem_path ) . 'llms.txt';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Infrastructure\File;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Populate_File_Command_Handler;
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\File\Llms_Txt_Permission_Gate_Interface;
|
||||
|
||||
/**
|
||||
* Handles checks to see if we manage the llms.txt file.
|
||||
*/
|
||||
class WordPress_Llms_Txt_Permission_Gate implements Llms_Txt_Permission_Gate_Interface {
|
||||
|
||||
/**
|
||||
* The file system adapter.
|
||||
*
|
||||
* @var WordPress_File_System_Adapter
|
||||
*/
|
||||
private $file_system_adapter;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param WordPress_File_System_Adapter $file_system_adapter The file system adapter.
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct(
|
||||
WordPress_File_System_Adapter $file_system_adapter,
|
||||
Options_Helper $options_helper
|
||||
) {
|
||||
$this->file_system_adapter = $file_system_adapter;
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Yoast SEO manages the llms.txt.
|
||||
*
|
||||
* @return bool Checks if Yoast SEO manages the llms.txt.
|
||||
*/
|
||||
public function is_managed_by_yoast_seo(): bool {
|
||||
$stored_hash = \get_option( Populate_File_Command_Handler::CONTENT_HASH_OPTION, '' );
|
||||
|
||||
// If the file does not exist yet, we always regenerate/create it.
|
||||
if ( ! $this->file_system_adapter->file_exists() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// This means the file is already there (maybe hand made or another plugin created it). And since we don't have a hash it's not ours.
|
||||
if ( $stored_hash === '' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$current_content = $this->file_system_adapter->get_file_contents();
|
||||
|
||||
// If you have a hash, we want to make sure it's the same. This check makes sure the file is not edited by the user.
|
||||
return \md5( $current_content ) === $stored_hash;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services;
|
||||
|
||||
use WP_Post;
|
||||
use Yoast\WP\SEO\Helpers\Indexable_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items\Link;
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Link_List;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* The collector of content types.
|
||||
*
|
||||
* @TODO: This class could maybe be unified with
|
||||
* Yoast\WP\SEO\Dashboard\Infrastructure\Content_Types\Content_Types_Collector.
|
||||
*/
|
||||
class Content_Types_Collector {
|
||||
|
||||
/**
|
||||
* The post type helper.
|
||||
*
|
||||
* @var Post_Type_Helper
|
||||
*/
|
||||
private $post_type_helper;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
private $indexable_repository;
|
||||
|
||||
/**
|
||||
* The indexable helper.
|
||||
*
|
||||
* @var Indexable_Helper
|
||||
*/
|
||||
private $indexable_helper;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Post_Type_Helper $post_type_helper The post type helper.
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param Indexable_Helper $indexable_helper The indexable helper.
|
||||
* @param Indexable_Repository $indexable_repository The indexable repository.
|
||||
*/
|
||||
public function __construct(
|
||||
Post_Type_Helper $post_type_helper,
|
||||
Options_Helper $options_helper,
|
||||
Indexable_Helper $indexable_helper,
|
||||
Indexable_Repository $indexable_repository
|
||||
) {
|
||||
$this->post_type_helper = $post_type_helper;
|
||||
$this->options_helper = $options_helper;
|
||||
$this->indexable_helper = $indexable_helper;
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content types in a link list.
|
||||
*
|
||||
* @return Link_List[] The content types in a link list.
|
||||
*/
|
||||
public function get_content_types_lists(): array {
|
||||
$post_types = $this->post_type_helper->get_indexable_post_type_objects();
|
||||
$link_list = [];
|
||||
|
||||
foreach ( $post_types as $post_type_object ) {
|
||||
if ( $this->post_type_helper->is_indexable( $post_type_object->name ) === false ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$posts = $this->get_posts( $post_type_object->name, 5 );
|
||||
$post_links = new Link_List( $post_type_object->label, [] );
|
||||
foreach ( $posts as $post ) {
|
||||
$post_link = new Link( $post->post_title, \get_permalink( $post->ID ), $post->post_excerpt );
|
||||
$post_links->add_link( $post_link );
|
||||
}
|
||||
|
||||
$link_list[] = $post_links;
|
||||
}
|
||||
|
||||
return $link_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the posts that are relevant for the LLMs.txt.
|
||||
*
|
||||
* @param string $post_type The post type.
|
||||
* @param int $limit The maximum number of posts to return.
|
||||
*
|
||||
* @return array<int, array<WP_Post>> The posts that are relevant for the LLMs.txt.
|
||||
*/
|
||||
public function get_posts( string $post_type, int $limit ): array {
|
||||
$posts = $this->get_recent_cornerstone_content( $post_type, $limit );
|
||||
|
||||
if ( \count( $posts ) >= $limit ) {
|
||||
return $posts;
|
||||
}
|
||||
|
||||
$recent_posts = $this->get_recent_posts( $post_type, $limit );
|
||||
foreach ( $recent_posts as $recent_post ) {
|
||||
// If the post is already in the list because it's cornerstone, don't add it again.
|
||||
if ( isset( $posts[ $recent_post->ID ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$posts[ $recent_post->ID ] = $recent_post;
|
||||
|
||||
if ( \count( $posts ) >= $limit ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most recently modified cornerstone content.
|
||||
*
|
||||
* @param string $post_type The post type.
|
||||
* @param int $limit The maximum number of posts to return.
|
||||
*
|
||||
* @return array<int, array<WP_Post>> The most recently modified cornerstone content.
|
||||
*/
|
||||
private function get_recent_cornerstone_content( string $post_type, int $limit ): array {
|
||||
if ( ! $this->options_helper->get( 'enable_cornerstone_content' ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$cornerstone_limit = ( \is_post_type_hierarchical( $post_type ) ) ? null : $limit;
|
||||
$cornerstones = $this->indexable_repository->get_recent_cornerstone_for_post_type( $post_type, $cornerstone_limit );
|
||||
|
||||
$recent_cornerstone_posts = [];
|
||||
foreach ( $cornerstones as $cornerstone ) {
|
||||
$recent_cornerstone_posts[ $cornerstone->object_id ] = \get_post( $cornerstone->object_id );
|
||||
}
|
||||
|
||||
return $recent_cornerstone_posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most recently modified posts.
|
||||
*
|
||||
* @param string $post_type The post type.
|
||||
* @param int $limit The maximum number of posts to return.
|
||||
*
|
||||
* @return array<WP_Post> The most recently modified posts.
|
||||
*/
|
||||
private function get_recent_posts( string $post_type, int $limit ): array {
|
||||
$exclude_older_than_one_year = false;
|
||||
|
||||
if ( $post_type === 'post' ) {
|
||||
$exclude_older_than_one_year = true;
|
||||
}
|
||||
|
||||
if ( $this->indexable_helper->should_index_indexables() ) {
|
||||
return $this->get_recently_modified_posts_indexables( $post_type, $limit, $exclude_older_than_one_year );
|
||||
}
|
||||
|
||||
return $this->get_recently_modified_posts_wp_query( $post_type, $limit, $exclude_older_than_one_year );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns most recently modified posts of a post type, using indexables.
|
||||
*
|
||||
* @param string $post_type The post type.
|
||||
* @param int $limit The maximum number of posts to return.
|
||||
* @param bool $exclude_older_than_one_year Whether to exclude posts older than one year.
|
||||
*
|
||||
* @return array<WP_Post> The most recently modified posts.
|
||||
*/
|
||||
private function get_recently_modified_posts_indexables( string $post_type, int $limit, bool $exclude_older_than_one_year ) {
|
||||
$posts = [];
|
||||
$recently_modified_indexables = $this->indexable_repository->get_recently_modified_posts( $post_type, $limit, $exclude_older_than_one_year );
|
||||
|
||||
foreach ( $recently_modified_indexables as $indexable ) {
|
||||
$post_from_indexable = \get_post( $indexable->object_id );
|
||||
if ( $post_from_indexable instanceof WP_Post ) {
|
||||
$posts[] = $post_from_indexable;
|
||||
}
|
||||
}
|
||||
|
||||
return $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns most recently modified posts of a post type, using WP_Query.
|
||||
*
|
||||
* @param string $post_type The post type.
|
||||
* @param int $limit The maximum number of posts to return.
|
||||
* @param bool $exclude_older_than_one_year Whether to exclude posts older than one year.
|
||||
*
|
||||
* @return array<WP_Post> The most recently modified posts.
|
||||
*/
|
||||
private function get_recently_modified_posts_wp_query( string $post_type, int $limit, bool $exclude_older_than_one_year ) {
|
||||
$args = [
|
||||
'post_type' => $post_type,
|
||||
'posts_per_page' => $limit,
|
||||
'post_status' => 'publish',
|
||||
'orderby' => 'modified',
|
||||
'order' => 'DESC',
|
||||
'has_password' => false,
|
||||
];
|
||||
|
||||
if ( $exclude_older_than_one_year === true ) {
|
||||
$args['date_query'] = [
|
||||
[
|
||||
'after' => '12 months ago',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return \get_posts( $args );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Description;
|
||||
use Yoast\WP\SEO\Surfaces\Meta_Surface;
|
||||
|
||||
/**
|
||||
* The adapter of the description.
|
||||
*/
|
||||
class Description_Adapter {
|
||||
|
||||
/**
|
||||
* Holds the meta helper surface.
|
||||
*
|
||||
* @var Meta_Surface
|
||||
*/
|
||||
private $meta;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param Meta_Surface $meta The meta surface.
|
||||
*/
|
||||
public function __construct(
|
||||
Meta_Surface $meta
|
||||
) {
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the description.
|
||||
*
|
||||
* @return Description The description.
|
||||
*/
|
||||
public function get_description(): Description {
|
||||
$meta_description = $this->meta->for_home_page()->meta_description;
|
||||
|
||||
// In a lot of cases, the homepage's meta description falls back to the site's tagline.
|
||||
// But that is already used for the title section, so let's try to not have duplicate content.
|
||||
if ( $meta_description === \get_bloginfo( 'description' ) ) {
|
||||
return new Description( '' );
|
||||
}
|
||||
|
||||
return new Description( $meta_description );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services;
|
||||
|
||||
use WPSEO_Options;
|
||||
use WPSEO_Sitemaps_Router;
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items\Link;
|
||||
|
||||
/**
|
||||
* The sitemap link collector.
|
||||
*/
|
||||
class Sitemap_Link_Collector {
|
||||
|
||||
/**
|
||||
* Gets the link for the sitemap.
|
||||
*
|
||||
* @return Link The link for the sitemap.
|
||||
*/
|
||||
public function get_link(): ?Link {
|
||||
if ( WPSEO_Options::get( 'enable_xml_sitemap' ) ) {
|
||||
$sitemap_url = WPSEO_Sitemaps_Router::get_base_url( 'sitemap_index.xml' );
|
||||
return new Link( 'sitemap', $sitemap_url );
|
||||
}
|
||||
|
||||
$sitemap_url = \get_sitemap_url( 'index' );
|
||||
|
||||
if ( $sitemap_url !== false ) {
|
||||
return new Link( 'sitemap', $sitemap_url );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services;
|
||||
|
||||
use Yoast\WP\SEO\Helpers\Taxonomy_Helper;
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items\Link;
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Link_List;
|
||||
|
||||
/**
|
||||
* The collector of terms.
|
||||
*/
|
||||
class Terms_Collector {
|
||||
|
||||
/**
|
||||
* The taxonomy helper.
|
||||
*
|
||||
* @var Taxonomy_Helper
|
||||
*/
|
||||
private $taxonomy_helper;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Taxonomy_Helper $taxonomy_helper The taxonomy helper.
|
||||
*/
|
||||
public function __construct( Taxonomy_Helper $taxonomy_helper ) {
|
||||
$this->taxonomy_helper = $taxonomy_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content types in a link list.
|
||||
*
|
||||
* @return Link_List[] The content types in a link list.
|
||||
*/
|
||||
public function get_terms_lists(): array {
|
||||
$taxonomies = $this->taxonomy_helper->get_indexable_taxonomy_objects();
|
||||
$link_list = [];
|
||||
|
||||
foreach ( $taxonomies as $taxonomy ) {
|
||||
if ( $this->taxonomy_helper->is_indexable( $taxonomy->name ) === false ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$terms = \get_categories(
|
||||
[
|
||||
'taxonomy' => $taxonomy->name,
|
||||
'number' => 5,
|
||||
'orderby' => 'count',
|
||||
'order' => 'DESC',
|
||||
]
|
||||
);
|
||||
|
||||
$term_links = new Link_List( $taxonomy->label, [] );
|
||||
foreach ( $terms as $term ) {
|
||||
$term_link = new Link( $term->name, \get_term_link( $term, $taxonomy->name ) );
|
||||
$term_links->add_link( $term_link );
|
||||
}
|
||||
|
||||
$link_list[] = $term_links;
|
||||
}
|
||||
|
||||
return $link_list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services;
|
||||
|
||||
use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Title;
|
||||
use Yoast\WP\SEO\Services\Health_Check\Default_Tagline_Runner;
|
||||
|
||||
/**
|
||||
* The adapter of the title.
|
||||
*/
|
||||
class Title_Adapter {
|
||||
|
||||
/**
|
||||
* The default tagline runner.
|
||||
*
|
||||
* @var Default_Tagline_Runner
|
||||
*/
|
||||
private $default_tagline_runner;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param Default_Tagline_Runner $default_tagline_runner The default tagline runner.
|
||||
*/
|
||||
public function __construct(
|
||||
Default_Tagline_Runner $default_tagline_runner
|
||||
) {
|
||||
$this->default_tagline_runner = $default_tagline_runner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title.
|
||||
*
|
||||
* @return Title The title.
|
||||
*/
|
||||
public function get_title(): Title {
|
||||
$this->default_tagline_runner->run();
|
||||
$tagline = ( $this->default_tagline_runner->is_successful() ? \get_bloginfo( 'description' ) : '' );
|
||||
|
||||
return new Title( \get_bloginfo( 'name' ), $tagline );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Llms_Txt\User_Interface;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Remove_File_Command_Handler;
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\File\Llms_Txt_Cron_Scheduler;
|
||||
|
||||
/**
|
||||
* Trys to clean up the llms.txt file when the plugin is deactivated.
|
||||
*/
|
||||
class Cleanup_Llms_Txt_On_Deactivation implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The command handler.
|
||||
*
|
||||
* @var Remove_File_Command_Handler
|
||||
*/
|
||||
private $command_handler;
|
||||
|
||||
/**
|
||||
* The cron scheduler.
|
||||
*
|
||||
* @var Llms_Txt_Cron_Scheduler
|
||||
*/
|
||||
private $cron_scheduler;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Remove_File_Command_Handler $command_handler The command handler.
|
||||
* @param Llms_Txt_Cron_Scheduler $cron_scheduler The scheduler.
|
||||
*/
|
||||
public function __construct(
|
||||
Remove_File_Command_Handler $command_handler,
|
||||
Llms_Txt_Cron_Scheduler $cron_scheduler
|
||||
) {
|
||||
$this->command_handler = $command_handler;
|
||||
$this->cron_scheduler = $cron_scheduler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the unscheduling of the cron to the deactivation action.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'wpseo_deactivate', [ $this, 'maybe_remove_llms_file' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the command handler to remove the file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_remove_llms_file(): void {
|
||||
$this->command_handler->handle();
|
||||
$this->cron_scheduler->unschedule_llms_txt_population();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Llms_Txt\User_Interface;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\Traits\Admin_Conditional_Trait;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Populate_File_Command_Handler;
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Remove_File_Command_Handler;
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\File\Llms_Txt_Cron_Scheduler;
|
||||
|
||||
/**
|
||||
* Watches and handles changes to the LLMS.txt enabled option.
|
||||
*/
|
||||
class Enable_Llms_Txt_Option_Watcher implements Integration_Interface {
|
||||
|
||||
use Admin_Conditional_Trait;
|
||||
|
||||
/**
|
||||
* The scheduler.
|
||||
*
|
||||
* @var Llms_Txt_Cron_Scheduler
|
||||
*/
|
||||
private $scheduler;
|
||||
|
||||
/**
|
||||
* The remove file command handler.
|
||||
*
|
||||
* @var Remove_File_Command_Handler
|
||||
*/
|
||||
private $remove_file_command_handler;
|
||||
|
||||
/**
|
||||
* The populate file command handler.
|
||||
*
|
||||
* @var Populate_File_Command_Handler
|
||||
*/
|
||||
private $populate_file_command_handler;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Llms_Txt_Cron_Scheduler $scheduler The cron scheduler.
|
||||
* @param Remove_File_Command_Handler $remove_file_command_handler The remove file command handler.
|
||||
* @param Populate_File_Command_Handler $populate_file_command_handler The populate file command handler.
|
||||
*/
|
||||
public function __construct(
|
||||
Llms_Txt_Cron_Scheduler $scheduler,
|
||||
Remove_File_Command_Handler $remove_file_command_handler,
|
||||
Populate_File_Command_Handler $populate_file_command_handler
|
||||
) {
|
||||
$this->scheduler = $scheduler;
|
||||
$this->remove_file_command_handler = $remove_file_command_handler;
|
||||
$this->populate_file_command_handler = $populate_file_command_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the integration.
|
||||
*
|
||||
* This is the place to register hooks and filters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'update_option_wpseo', [ $this, 'check_toggle_llms_txt' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the LLMS.txt feature is toggled.
|
||||
*
|
||||
* @param array<string|int|bool|array<string|int|bool>> $old_value The old value of the option.
|
||||
* @param array<string|int|bool|array<string|int|bool>> $new_value The new value of the option.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function check_toggle_llms_txt( $old_value, $new_value ): void {
|
||||
$option_name = 'enable_llms_txt';
|
||||
|
||||
if ( \array_key_exists( $option_name, $old_value ) && \array_key_exists( $option_name, $new_value ) && $old_value[ $option_name ] !== $new_value[ $option_name ] ) {
|
||||
if ( $new_value[ $option_name ] === true ) {
|
||||
$this->scheduler->schedule_weekly_llms_txt_population();
|
||||
$this->populate_file_command_handler->handle();
|
||||
}
|
||||
else {
|
||||
$this->scheduler->unschedule_llms_txt_population();
|
||||
$this->remove_file_command_handler->handle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
|
||||
namespace Yoast\WP\SEO\Llms_Txt\User_Interface\Health_Check;
|
||||
|
||||
use Yoast\WP\SEO\Services\Health_Check\Report_Builder_Factory;
|
||||
use Yoast\WP\SEO\Services\Health_Check\Reports_Trait;
|
||||
|
||||
/**
|
||||
* Presents a set of different messages for the File_Generation health check.
|
||||
*/
|
||||
class File_Reports {
|
||||
|
||||
use Reports_Trait;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Report_Builder_Factory $report_builder_factory The factory for result builder objects.
|
||||
* This class uses the report builder to generate WordPress-friendly
|
||||
* health check results.
|
||||
*/
|
||||
public function __construct( Report_Builder_Factory $report_builder_factory ) {
|
||||
$this->report_builder_factory = $report_builder_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message for a successful health check.
|
||||
*
|
||||
* @return string[] The message as a WordPress site status report.
|
||||
*/
|
||||
public function get_success_result() {
|
||||
$label = \sprintf(
|
||||
/* translators: %s: Yoast SEO. */
|
||||
\__( 'Your llms.txt file is auto-generated by %s', 'wordpress-seo' ),
|
||||
'Yoast SEO',
|
||||
);
|
||||
|
||||
$description = \sprintf(
|
||||
/* translators: %s: Yoast SEO. */
|
||||
\__( '%s keeps your llms.txt file up-to-date. This helps LLMs access and provide your site\'s information more easily.', 'wordpress-seo' ),
|
||||
'Yoast SEO',
|
||||
);
|
||||
|
||||
return $this->get_report_builder()
|
||||
->set_label( $label )
|
||||
->set_status_good()
|
||||
->set_description( $description )
|
||||
->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message for a failed health check. In this case, when the llms.txt file couldn't be auto-generated.
|
||||
*
|
||||
* @param string $reason The reason why the llms.txt file couldn't be auto-generated.
|
||||
*
|
||||
* @return string[] The message as a WordPress site status report.
|
||||
*/
|
||||
public function get_generation_failure_result( $reason ) {
|
||||
switch ( $reason ) {
|
||||
case 'not_managed_by_yoast_seo':
|
||||
$title = \__( 'Your llms.txt file couldn\'t be auto-generated', 'wordpress-seo' );
|
||||
$message = \sprintf(
|
||||
/* translators: 1,3,5: expand to opening paragraph tag, 2,4,6: expand to opening paragraph tag. */
|
||||
\__( '%1$sYou have activated the Yoast llms.txt feature, but we couldn\'t generate an llms.txt file.%2$s%3$sIt looks like there is an llms.txt file already that wasn\'t created by Yoast, or the llms.txt file created by Yoast has been edited manually.%4$s%5$sWe don\'t want to overwrite this file\'s content, so if you want to let Yoast keep auto-generating the llms.txt file, you can manually delete the existing one. Otherwise, consider disabling the Yoast feature.%6$s', 'wordpress-seo' ),
|
||||
'<p>',
|
||||
'</p>',
|
||||
'<p>',
|
||||
'</p>',
|
||||
'<p>',
|
||||
'</p>'
|
||||
);
|
||||
break;
|
||||
case 'filesystem_permissions':
|
||||
$title = \__( 'Your llms.txt file couldn\'t be auto-generated', 'wordpress-seo' );
|
||||
$message = \sprintf(
|
||||
/* translators: 1,3: expand to opening paragraph tag, 2,4: expand to opening paragraph tag. */
|
||||
\__( '%1$sYou have activated the Yoast llms.txt feature, but we couldn\'t generate an llms.txt file.%2$s%3$sIt looks like there aren\'t sufficient permissions on the web server\'s filesystem.%4$s', 'wordpress-seo' ),
|
||||
'<p>',
|
||||
'</p>',
|
||||
'<p>',
|
||||
'</p>'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
$title = \__( 'Your llms.txt file couldn\'t be auto-generated', 'wordpress-seo' );
|
||||
$message = \__( 'You have activated the Yoast llms.txt feature, but we couldn\'t generate an llms.txt file, for unknown reasons.', 'wordpress-seo' );
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->get_report_builder()
|
||||
->set_label( $title )
|
||||
->set_status_recommended()
|
||||
->set_description( $message )
|
||||
->build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Llms_Txt\User_Interface;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Populate_File_Command_Handler;
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Remove_File_Command_Handler;
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\File\Llms_Txt_Cron_Scheduler;
|
||||
|
||||
/**
|
||||
* Cron Callback integration. This handles the actual process of populating the llms.txt on a cron trigger.
|
||||
*/
|
||||
class Llms_Txt_Cron_Callback_Integration implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The remove file command handler.
|
||||
*
|
||||
* @var Remove_File_Command_Handler
|
||||
*/
|
||||
private $remove_file_command_handler;
|
||||
|
||||
/**
|
||||
* The Create Populate Command Handler.
|
||||
*
|
||||
* @var Populate_File_Command_Handler
|
||||
*/
|
||||
private $populate_file_command_handler;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The scheduler.
|
||||
*
|
||||
* @var Llms_Txt_Cron_Scheduler
|
||||
*/
|
||||
private $scheduler;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
* @param Llms_Txt_Cron_Scheduler $scheduler The scheduler.
|
||||
* @param Populate_File_Command_Handler $populate_file_command_handler The populate file command handler.
|
||||
* @param Remove_File_Command_Handler $remove_file_command_handler The remove file command handler.
|
||||
*/
|
||||
public function __construct(
|
||||
Options_Helper $options_helper,
|
||||
Llms_Txt_Cron_Scheduler $scheduler,
|
||||
Populate_File_Command_Handler $populate_file_command_handler,
|
||||
Remove_File_Command_Handler $remove_file_command_handler
|
||||
) {
|
||||
$this->options_helper = $options_helper;
|
||||
$this->scheduler = $scheduler;
|
||||
$this->populate_file_command_handler = $populate_file_command_handler;
|
||||
$this->remove_file_command_handler = $remove_file_command_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the hooks with WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action(
|
||||
Llms_Txt_Cron_Scheduler::LLMS_TXT_POPULATION,
|
||||
[
|
||||
$this,
|
||||
'populate_file',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates and creates the file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function populate_file(): void {
|
||||
if ( ! \wp_doing_cron() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->options_helper->get( 'enable_llms_txt', false ) !== true ) {
|
||||
$this->scheduler->unschedule_llms_txt_population();
|
||||
$this->remove_file_command_handler->handle();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->populate_file_command_handler->handle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Llms_Txt\User_Interface;
|
||||
|
||||
use Yoast\WP\SEO\Conditionals\No_Conditionals;
|
||||
use Yoast\WP\SEO\Helpers\Options_Helper;
|
||||
use Yoast\WP\SEO\Integrations\Integration_Interface;
|
||||
use Yoast\WP\SEO\Llms_Txt\Application\File\Llms_Txt_Cron_Scheduler;
|
||||
|
||||
|
||||
/**
|
||||
* Handles the cron when the plugin is activated.
|
||||
*/
|
||||
class Schedule_Population_On_Activation_Integration implements Integration_Interface {
|
||||
|
||||
use No_Conditionals;
|
||||
|
||||
/**
|
||||
* The options helper.
|
||||
*
|
||||
* @var Options_Helper $options_helper
|
||||
*/
|
||||
private $options_helper;
|
||||
|
||||
/**
|
||||
* The scheduler.
|
||||
*
|
||||
* @var Llms_Txt_Cron_Scheduler $scheduler
|
||||
*/
|
||||
private $scheduler;
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
*
|
||||
* @param Llms_Txt_Cron_Scheduler $scheduler The cron scheduler.
|
||||
* @param Options_Helper $options_helper The options helper.
|
||||
*/
|
||||
public function __construct(
|
||||
Llms_Txt_Cron_Scheduler $scheduler,
|
||||
Options_Helper $options_helper
|
||||
) {
|
||||
$this->scheduler = $scheduler;
|
||||
$this->options_helper = $options_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the scheduling of the cron to the activation action.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
\add_action( 'wpseo_activate', [ $this, 'schedule_llms_txt_population' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the cron if the option is turned on.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function schedule_llms_txt_population() {
|
||||
if ( $this->options_helper->get( 'enable_llms_txt', false ) === true ) {
|
||||
$this->scheduler->schedule_quick_llms_txt_population();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user