| @@ -750,8 +750,28 @@ | ||
| 750 | 750 | return is_string($content) ? $content : ''; |
| 751 | 751 | } |
| 752 | 752 | |
| 753 | 753 | /** |
| 754 | + * Whether /llms.txt is currently being served, in either delivery mode. | |
| 755 | + * | |
| 756 | + * `static` publishes a file at ABSPATH; `dynamic` keeps the document in | |
| 757 | + * an option and answers from serve_llms_txt(). Callers that only need | |
| 758 | + * this yes/no must use it in preference to get_llms_txt_status(), which | |
| 759 | + * resolves the delivery mode, may fire a loopback delivery probe, asks | |
| 760 | + * the filesystem API whether ABSPATH is writable, reads the document and | |
| 761 | + * writes a transient — far too much work for a boolean, and not | |
| 762 | + * something a dashboard summary should be triggering. | |
| 763 | + * | |
| 764 | + * @since 2.2.1 | |
| 765 | + * | |
| 766 | + * @return bool | |
| 767 | + */ | |
| 768 | + public function is_published(): bool { | |
| 769 | + return file_exists(ABSPATH . 'llms.txt') | |
| 770 | + || '' !== trim($this->get_published_content()); | |
| 771 | + } | |
| 772 | + | |
| 773 | + /** | |
| 754 | 774 | * Ask the common page/CDN cache layers to drop their copy of /llms.txt. |
| 755 | 775 | * |
| 756 | 776 | * A cached response outlives a republish, so without this a mode switch or |
| 757 | 777 | * a content change keeps serving the old document (and, on the static path, |
| @@ -1174,9 +1194,9 @@ | ||
| 1174 | 1194 | $status = [ |
| 1175 | 1195 | 'file_exists' => file_exists($llms_file), |
| 1176 | 1196 | // Whether /llms.txt is actually being served, either mode. Prefer |
| 1177 | 1197 | // this over file_exists, which is only meaningful in static mode. |
| 1178 | - 'published' => file_exists($llms_file) || '' !== trim($stored), | |
| 1198 | + 'published' => $this->is_published(), | |
| 1179 | 1199 | 'delivery_mode' => $mode, |
| 1180 | 1200 | 'file_path' => 'dynamic' === $mode ? '' : $llms_file, |
| 1181 | 1201 | 'file_url' => home_url('/llms.txt'), |
| 1182 | 1202 | 'writable' => $this->is_directory_writable(dirname($llms_file)), |
| @@ -1212,12 +1232,11 @@ | ||
| 1212 | 1232 | } |
| 1213 | 1233 | |
| 1214 | 1234 | if (null !== $content) { |
| 1215 | 1235 | // Get content preview (first 200 characters) with size safety |
| 1216 | - $status['content_preview'] = substr($content, 0, 200); | |
| 1217 | - if (strlen($content) > 200) { | |
| 1218 | - $status['content_preview'] .= '...'; | |
| 1219 | - } | |
| 1236 | + // substr()/strlen() count BYTES, so this cut a multibyte character | |
| 1237 | + // in half and shipped an invalid UTF-8 sequence in the preview (#687). | |
| 1238 | + $status['content_preview'] = \ThinkRank\Core\Seo_Text::trim_to_length($content, 200); | |
| 1220 | 1239 | } |
| 1221 | 1240 | |
| 1222 | 1241 | // Cache the result for 5 minutes to improve performance |
| 1223 | 1242 | set_transient($cache_key, $status, 5 * MINUTE_IN_SECONDS); |
| @@ -2192,9 +2211,9 @@ | ||
| 2192 | 2211 | $content .= "- [Technical Stack]({$website_url}): Built with {$stack}\n"; |
| 2193 | 2212 | } |
| 2194 | 2213 | |
| 2195 | 2214 | if (!empty($user_input['development_approach'])) { |
| 2196 | - $approach_summary = wp_trim_words($user_input['development_approach'], 10); | |
| 2215 | + $approach_summary = \ThinkRank\Core\Seo_Text::trim_words($user_input['development_approach'], 10); | |
| 2197 | 2216 | $content .= "- [Development Guidelines]({$website_url}): {$approach_summary}\n"; |
| 2198 | 2217 | } |
| 2199 | 2218 | |
| 2200 | 2219 | // Add robots.txt reference |