diff --git a/citecue.php b/citecue.php index 0fbea71..b78e1f8 100644 --- a/citecue.php +++ b/citecue.php @@ -3,7 +3,7 @@ * Plugin Name: CiteCue AI Auto-Fix * Plugin URI: https://github.com/citecue/wordpress-plugin * Description: Serves CiteCue-optimized versions of your pages to AI bots and crawlers, publishes your llms.txt, and lets CiteCue push brand-building draft content into WordPress. - * Version: 1.0.1 + * Version: 1.0.2 * Requires at least: 5.8 * Requires PHP: 7.4 * Author: CiteCue @@ -56,7 +56,7 @@ static function () { return; } -define( 'CITECUE_VERSION', '1.0.1' ); +define( 'CITECUE_VERSION', '1.0.2' ); define( 'CITECUE_PLUGIN_FILE', __FILE__ ); define( 'CITECUE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); diff --git a/includes/class-citecue-llms-txt.php b/includes/class-citecue-llms-txt.php index 4b68d09..89af704 100644 --- a/includes/class-citecue-llms-txt.php +++ b/includes/class-citecue-llms-txt.php @@ -178,7 +178,9 @@ private function is_llms_txt_request() { return false; } - $path = (string) wp_parse_url( wp_unslash( $_SERVER['REQUEST_URI'] ), PHP_URL_PATH ); + // esc_url_raw() rather than sanitize_text_field(), which strips + // percent-encoded sequences and would corrupt the path being matched. + $path = (string) wp_parse_url( esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ), PHP_URL_PATH ); $home_path = (string) wp_parse_url( home_url( '/' ), PHP_URL_PATH ); $target = untrailingslashit( $home_path ) . '/llms.txt'; diff --git a/includes/class-citecue-plugin.php b/includes/class-citecue-plugin.php index 1761897..fc4f658 100644 --- a/includes/class-citecue-plugin.php +++ b/includes/class-citecue-plugin.php @@ -102,13 +102,16 @@ private function __construct() { } /** - * Init: translations + cron self-heal. + * Init: cron self-heal. + * + * There is deliberately no load_plugin_textdomain() call. Since WordPress + * 4.6 a plugin hosted on WordPress.org has its translations loaded for it, + * keyed by the slug — which is exactly what the text domain is now. The + * call was pointing at a languages/ directory this plugin does not ship. * * @return void */ public function on_init() { - load_plugin_textdomain( 'citecue-ai-auto-fix', false, dirname( plugin_basename( CITECUE_PLUGIN_FILE ) ) . '/languages' ); - if ( ! wp_next_scheduled( self::CRON_HOOK ) ) { wp_schedule_event( time() + MINUTE_IN_SECONDS, 'daily', self::CRON_HOOK ); } diff --git a/includes/class-citecue-proxy.php b/includes/class-citecue-proxy.php index cec994b..c77ec3b 100644 --- a/includes/class-citecue-proxy.php +++ b/includes/class-citecue-proxy.php @@ -308,7 +308,10 @@ private function current_url() { } $scheme = is_ssl() ? 'https' : 'http'; $host = sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ); - $uri = wp_unslash( $_SERVER['REQUEST_URI'] ); + // esc_url_raw(), not sanitize_text_field(): the latter deletes every + // percent-encoded sequence it finds, so /caf%C3%A9/ would reach CiteCue + // as /caf/ and be cached under the wrong key. + $uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ); return esc_url_raw( $scheme . '://' . $host . $uri ); } diff --git a/readme.txt b/readme.txt index 9ce22d4..609cde8 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: ai, llms.txt, gptbot, ai-seo, woocommerce Requires at least: 5.8 Tested up to: 7.0 Requires PHP: 7.4 -Stable tag: 1.0.1 +Stable tag: 1.0.2 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -100,6 +100,10 @@ The plugin folder is now citecue-ai-auto-fix. If you installed 1.0.0 by uploadin == Changelog == += 1.0.2 = +* Translations no longer depend on the plugin loading them by hand; WordPress.org supplies them for the plugin slug. +* The requested URL is sanitized on the way to CiteCue in a way that preserves percent-encoding, so pages with accented or non-Latin characters in the address are served and cached under the address they actually have. + = 1.0.1 = * First release in the WordPress.org plugin directory. * An install that has not been connected to CiteCue now makes no outbound requests of any kind: the daily crawler-registry refresh waits for a connection. diff --git a/tests/cases/test-proxy-delivery.php b/tests/cases/test-proxy-delivery.php index 703c215..1851a12 100644 --- a/tests/cases/test-proxy-delivery.php +++ b/tests/cases/test-proxy-delivery.php @@ -75,6 +75,27 @@ public function test_delivery_request_is_addressed_correctly() { $this->assertSame( 'wordpress', $request['args']['headers']['X-Citecue-Channel'] ); } + /** + * The requested URL reaches CiteCue with its percent-encoding intact. + * + * This is a standing trap rather than a hypothetical: REQUEST_URI has to + * be sanitized to satisfy Plugin Check, and the reflex choice — + * sanitize_text_field() — deletes every %xx sequence it finds. Under it + * this URL would arrive as /caf/, which is a different page, cached under + * a key CiteCue never answers for. + * + * @return void + */ + public function test_a_percent_encoded_url_is_not_mangled_on_the_way_out() { + $encoded = $this->fake_crawler_request( '/caf%C3%A9/' ); + $this->http->queue( 'page', 200, 'optimized' ); + + $this->proxy()->decide(); + + $this->assertStringContainsString( '%C3%A9', rawurldecode( $this->http->last( 'page' )['url'] ) ); + $this->assertStringContainsString( 'caf%C3%A9', $encoded ); + } + /** * A second hit revalidates with the stored ETag instead of re-downloading. *