wpmind/modules/geo/includes/MarkdownFeed.php
LinuxJoy 02cb582d6c chore: 补全 strict_types 声明 + CHANGELOG 2.5.0-3.2.1
- 29 个 PHP 文件添加 declare(strict_types=1)
- CHANGELOG 补全 2.5.0、3.0.0、3.1.0、3.2.0、3.2.1 版本记录

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-06 13:09:28 +08:00

268 lines
6.5 KiB
PHP

<?php
/**
* Markdown Feed (Standalone Mode)
*
* Provides standalone Markdown feed when official AI Experiments plugin is not installed.
*
* @package WPMind\Modules\Geo
* @since 3.0.0
*/
declare(strict_types=1);
namespace WPMind\Modules\Geo;
/**
* Class MarkdownFeed
*
* Mode B: Standalone Markdown feed implementation.
* Only activates when official plugin is not installed and user enables it.
*
* @since 3.1.0 Refactored to use unified MarkdownProcessor.
*/
class MarkdownFeed {
/**
* HTML to Markdown converter.
*
* @var HtmlToMarkdown
*/
private HtmlToMarkdown $converter;
/**
* Unified Markdown processor.
*
* @var MarkdownProcessor
*/
private MarkdownProcessor $processor;
/**
* Constructor.
*/
public function __construct() {
$this->converter = new HtmlToMarkdown();
$this->processor = new MarkdownProcessor();
// Use init action with early priority to ensure feed registration.
add_action( 'init', array( $this, 'maybe_init' ), 1 );
}
/**
* Initialize if standalone mode should be active.
*/
public function maybe_init(): void {
// Don't activate if official plugin is handling this.
if ( class_exists( 'AI_Experiments\\Experiments\\Markdown_Feeds' ) ) {
return;
}
// Check if standalone mode is enabled.
if ( ! get_option( 'wpmind_standalone_markdown_feed', false ) ) {
return;
}
// Register feed - must happen during init.
add_feed( 'markdown', array( $this, 'render_feed' ) );
// Register rewrite rules for .md suffix.
$this->register_rewrite_rules();
// Handle Accept header content negotiation.
add_action( 'parse_request', array( $this, 'handle_accept_header' ) );
// Handle .md suffix rendering.
add_action( 'template_redirect', array( $this, 'handle_md_template' ) );
}
/**
* Register rewrite rules for .md suffix.
*
* Codex review fix: Added proper rewrite rules.
*/
private function register_rewrite_rules(): void {
// Register query var.
add_filter(
'query_vars',
function ( $vars ) {
$vars[] = 'wpmind_markdown';
return $vars;
}
);
// Handle .md suffix via request filter (works with any permalink structure).
add_filter( 'request', array( $this, 'handle_md_request' ) );
}
/**
* Handle .md suffix in request.
*
* @param array $query_vars The query variables.
* @return array Modified query variables.
*/
public function handle_md_request( array $query_vars ): array {
// Check if request URI ends with .md.
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
if ( preg_match( '/\.md(\?.*)?$/', $request_uri ) ) {
// Remove .md suffix to get the real path.
$clean_uri = preg_replace( '/\.md(\?.*)?$/', '$1', $request_uri );
// Parse the clean URI to get post.
$post_id = url_to_postid( home_url( $clean_uri ) );
if ( $post_id ) {
$query_vars['p'] = $post_id;
$query_vars['wpmind_markdown'] = 1;
// Clear other query vars that might conflict.
unset( $query_vars['error'], $query_vars['name'], $query_vars['category_name'] );
}
}
return $query_vars;
}
/**
* Handle Accept header content negotiation.
*
* Codex review fix: Added Accept header support.
*
* @param \WP $wp The WordPress environment instance.
*/
public function handle_accept_header( \WP $wp ): void {
// Check for Accept: text/markdown header.
$accept = isset( $_SERVER['HTTP_ACCEPT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT'] ) ) : '';
if ( false !== strpos( $accept, 'text/markdown' ) ) {
// Check if this is a singular post request.
if ( ! empty( $wp->query_vars['name'] ) || ! empty( $wp->query_vars['p'] ) ) {
$wp->query_vars['wpmind_markdown'] = 1;
}
}
}
/**
* Handle .md template rendering.
*
* Codex review fix: Added template_redirect handler.
*/
public function handle_md_template(): void {
if ( ! get_query_var( 'wpmind_markdown' ) ) {
return;
}
// Get the current post.
$post = get_queried_object();
if ( ! $post instanceof \WP_Post ) {
return;
}
// Render as Markdown.
$this->render_singular( $post );
exit;
}
/**
* Render the markdown feed.
*/
public function render_feed(): void {
// Set content type.
header( 'Content-Type: text/markdown; charset=utf-8' );
// Get posts.
$posts = get_posts(
array(
'numberposts' => apply_filters( 'wpmind_markdown_feed_posts_count', 10 ),
'post_status' => 'publish',
'post_type' => apply_filters( 'wpmind_markdown_feed_post_types', array( 'post' ) ),
)
);
// Feed header.
$site_name = get_bloginfo( 'name' );
$site_url = home_url();
echo "# {$site_name} - Markdown Feed\n\n";
echo "来源: {$site_url}\n";
echo "生成时间: " . gmdate( 'Y-m-d H:i:s' ) . " UTC\n\n";
echo "---\n\n";
// Render each post.
foreach ( $posts as $post ) {
echo $this->post_to_markdown( $post );
echo "\n\n---\n\n";
}
}
/**
* Render a singular post as Markdown.
*
* @param \WP_Post $post The post to render.
*/
private function render_singular( \WP_Post $post ): void {
header( 'Content-Type: text/markdown; charset=utf-8' );
echo $this->post_to_markdown( $post );
}
/**
* Convert a post to Markdown.
*
* @param \WP_Post $post The post to convert.
* @return string Markdown content.
*/
private function post_to_markdown( \WP_Post $post ): string {
// Setup post data.
setup_postdata( $post );
// Build sections (convert HTML to Markdown).
$sections = array(
'title' => '# ' . esc_html( get_the_title( $post ) ) . "\n\n",
'content' => $this->converter->convert( $post->post_content ),
);
// Use unified processor for GEO enhancements.
$sections = $this->processor->process( $sections, $post );
// Reset post data.
wp_reset_postdata();
/**
* Filter the markdown sections.
*
* @param array $sections The content sections.
* @param \WP_Post $post The post object.
*/
$sections = apply_filters( 'wpmind_markdown_post_sections', $sections, $post );
// Combine sections (exclude internal markers).
$output = '';
foreach ( $sections as $key => $content ) {
// Skip internal markers.
if ( str_starts_with( $key, '_' ) ) {
continue;
}
$output .= $content;
}
return $output;
}
/**
* Flush rewrite rules on activation.
*/
public static function activate(): void {
// Register rules first.
$instance = new self();
$instance->register_rewrite_rules();
// Flush rules.
flush_rewrite_rules();
}
/**
* Flush rewrite rules on deactivation.
*/
public static function deactivate(): void {
flush_rewrite_rules();
}
}