| @@ -52,8 +52,9 @@ | ||
| 52 | 52 | // Both must precede set_default_options(): they read |
| 53 | 53 | // `thinkrank_version`, which that method creates. |
| 54 | 54 | $this->retire_sitemap_legacy_fallback(); |
| 55 | 55 | $this->seed_feed_defaults(); |
| 56 | + $this->skip_key_features_migration(); | |
| 56 | 57 | $this->set_default_options(); |
| 57 | 58 | $this->setup_indexnow_key(); |
| 58 | 59 | $this->schedule_cron_jobs(); |
| 59 | 60 | $this->restore_webroot_artifacts(); |
| @@ -136,15 +137,38 @@ | ||
| 136 | 137 | throw new \Exception(sprintf("Required PHP extension '%s' is not loaded", esc_html($extension))); |
| 137 | 138 | } |
| 138 | 139 | } |
| 139 | 140 | |
| 140 | - // Check if we can write to WordPress root directory (for robots.txt, llms.txt, sitemaps) | |
| 141 | - if (!wp_is_writable(ABSPATH)) { | |
| 142 | - // Log warning but don't block activation — some hosts restrict ABSPATH writes | |
| 143 | - // and file-writing features will gracefully degrade via WP_Filesystem checks | |
| 141 | + // Check if we can write to WordPress root directory (for robots.txt, llms.txt, | |
| 142 | + // the Instant Indexing key file). Never blocks activation — some hosts restrict | |
| 143 | + // ABSPATH writes by design. | |
| 144 | + // | |
| 145 | + // The result is recorded rather than only logged. It used to reach error_log() | |
| 146 | + // and only under WP_DEBUG, so on a production site nobody was ever told, and the | |
| 147 | + // features that need it failed later with messages describing the symptom rather | |
| 148 | + // than the cause (#753). Webroot_Writable_Notice re-evaluates the condition live — | |
| 149 | + // permissions change without a reactivation — and this value only distinguishes | |
| 150 | + // "never worked here" from "worked until the host changed something". | |
| 151 | + $writable = wp_is_writable(ABSPATH); | |
| 152 | + | |
| 153 | + update_option( | |
| 154 | + \ThinkRank\Admin\Webroot_Writable_Notice::OPT_ACTIVATION_STATE, | |
| 155 | + $writable ? 'writable' : 'not-writable', | |
| 156 | + false | |
| 157 | + ); | |
| 158 | + | |
| 159 | + if (!$writable) { | |
| 160 | + // A fresh activation on a broken root should warn even if a previous | |
| 161 | + // install's dismissal is still on record. | |
| 162 | + delete_option(\ThinkRank\Admin\Webroot_Writable_Notice::OPT_DISMISSED); | |
| 163 | + | |
| 144 | 164 | if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { |
| 165 | + // Not a fixed feature list: robots.txt, llms.txt and the Instant | |
| 166 | + // Indexing key all have a PHP path, so naming them as broken | |
| 167 | + // was untrue since #756. Webroot_Writable_Notice works out | |
| 168 | + // what, if anything, is actually affected. | |
| 145 | 169 | // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log |
| 146 | - error_log('ThinkRank: WordPress root directory is not writable. Some features (robots.txt, llms.txt, sitemaps) may not work.'); | |
| 170 | + error_log('ThinkRank: WordPress root directory is not writable. Features on Automatic delivery are served from WordPress; any feature explicitly set to write files cannot publish them.'); | |
| 147 | 171 | } |
| 148 | 172 | } |
| 149 | 173 | } |
| 150 | 174 | |
| @@ -275,8 +299,35 @@ | ||
| 275 | 299 | ); |
| 276 | 300 | } |
| 277 | 301 | |
| 278 | 302 | /** |
| 303 | + * Mark the llms.txt Key Features migration done on a fresh install. | |
| 304 | + * | |
| 305 | + * {@see \ThinkRank\SEO\LLMs_Txt_Manager::maybe_migrate_legacy_key_features()} | |
| 306 | + * converts a value saved while commas separated features. A brand-new | |
| 307 | + * install never saved one, so anything it stores later follows the | |
| 308 | + * one-per-line rule and must not be split on its commas by a migration | |
| 309 | + * that runs after the user typed it. | |
| 310 | + * | |
| 311 | + * Same signal as {@see self::seed_feed_defaults()}: `thinkrank_version` is | |
| 312 | + * absent only on the very first activation. | |
| 313 | + * | |
| 314 | + * @since 2.10.0 | |
| 315 | + * | |
| 316 | + * @return void | |
| 317 | + */ | |
| 318 | + private function skip_key_features_migration(): void { | |
| 319 | + if (get_option('thinkrank_version') !== false) { | |
| 320 | + return; | |
| 321 | + } | |
| 322 | + | |
| 323 | + add_option( | |
| 324 | + \ThinkRank\SEO\LLMs_Txt_Manager::KEY_FEATURES_MIGRATION_OPTION, | |
| 325 | + \ThinkRank\SEO\LLMs_Txt_Manager::KEY_FEATURES_MIGRATION_VERSION | |
| 326 | + ); | |
| 327 | + } | |
| 328 | + | |
| 329 | + /** | |
| 279 | 330 | * Set default plugin options |
| 280 | 331 | * |
| 281 | 332 | * @return void |
| 282 | 333 | */ |
| @@ -285,9 +336,9 @@ | ||
| 285 | 336 | 'thinkrank_version' => THINKRANK_VERSION, |
| 286 | 337 | |
| 287 | 338 | 'thinkrank_ai_provider' => \ThinkRank\Core\Settings::AI_PROVIDER_NONE, |
| 288 | 339 | 'thinkrank_cache_duration' => 3600, // 1 hour |
| 289 | - 'thinkrank_max_requests_per_minute' => 10, | |
| 340 | + 'thinkrank_max_requests_per_minute' => 0, | |
| 290 | 341 | 'thinkrank_enable_logging' => true, |
| 291 | 342 | 'thinkrank_auto_optimize' => false, |
| 292 | 343 | 'thinkrank_seo_score_threshold' => 70, |
| 293 | 344 | ]; |