PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.9.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.9.0
2.9.0 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 All 50 releases
← All changes | includes/core/class-activator.php +122 -7 2.1.02.9.0 View file →
@@ -48,8 +48,12 @@
48 48 delete_option(self::UNINSTALLED_OPTION);
49 49
50 50 $this->check_requirements();
51 51 $this->create_database_tables();
52 + // Both must precede set_default_options(): they read
53 + // `thinkrank_version`, which that method creates.
54 + $this->retire_sitemap_legacy_fallback();
55 + $this->seed_feed_defaults();
52 56 $this->set_default_options();
53 57 $this->setup_indexnow_key();
54 58 $this->schedule_cron_jobs();
55 59 $this->restore_webroot_artifacts();
@@ -132,15 +136,34 @@
132 136 throw new \Exception(sprintf("Required PHP extension '%s' is not loaded", esc_html($extension)));
133 137 }
134 138 }
135 139
136 - // Check if we can write to WordPress root directory (for robots.txt, llms.txt, sitemaps)
137 - if (!wp_is_writable(ABSPATH)) {
138 - // Log warning but don't block activation — some hosts restrict ABSPATH writes
139 - // and file-writing features will gracefully degrade via WP_Filesystem checks
140 + // Check if we can write to WordPress root directory (for robots.txt, llms.txt,
141 + // the Instant Indexing key file). Never blocks activation — some hosts restrict
142 + // ABSPATH writes by design.
143 + //
144 + // The result is recorded rather than only logged. It used to reach error_log()
145 + // and only under WP_DEBUG, so on a production site nobody was ever told, and the
146 + // features that need it failed later with messages describing the symptom rather
147 + // than the cause (#753). Webroot_Writable_Notice re-evaluates the condition live —
148 + // permissions change without a reactivation — and this value only distinguishes
149 + // "never worked here" from "worked until the host changed something".
150 + $writable = wp_is_writable(ABSPATH);
151 +
152 + update_option(
153 + \ThinkRank\Admin\Webroot_Writable_Notice::OPT_ACTIVATION_STATE,
154 + $writable ? 'writable' : 'not-writable',
155 + false
156 + );
157 +
158 + if (!$writable) {
159 + // A fresh activation on a broken root should warn even if a previous
160 + // install's dismissal is still on record.
161 + delete_option(\ThinkRank\Admin\Webroot_Writable_Notice::OPT_DISMISSED);
162 +
140 163 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
141 164 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
142 - error_log('ThinkRank: WordPress root directory is not writable. Some features (robots.txt, llms.txt, sitemaps) may not work.');
165 + error_log('ThinkRank: WordPress root directory is not writable. robots.txt, llms.txt and the Instant Indexing key file cannot be published; the sitemap falls back to dynamic delivery.');
143 166 }
144 167 }
145 168 }
146 169
@@ -179,8 +202,100 @@
179 202
180 203
181 204
182 205 /**
206 + * On a brand-new install, close the pre-2.1.1 sitemap ownership fallback
207 + * before it can ever open.
208 + *
209 + * {@see thinkrank_webroot_sitemap_is_ours()} keeps one narrow escape hatch:
210 + * a sitemap written before 2.1.1 with `enable_styling` off carries neither
211 + * the marker nor our XSL href, so it can only be recognised by the name the
212 + * stored settings derive. That fallback is gated on this install never
213 + * having written a marked sitemap — but "never written one" describes two
214 + * completely different sites:
215 + *
216 + * - a pre-2.1.1 install that has not regenerated since upgrading, which
217 + * is exactly what the fallback exists to recover; and
218 + * - a fresh install that simply has not generated yet, which cannot have
219 + * a legacy file of ours on disk at all.
220 + *
221 + * On the second, the fallback has nothing to recover and can only delete
222 + * somebody else's sitemap from one of the canonical names — #515 again, in
223 + * a site that never had the problem the fallback addresses. It is not a
224 + * narrow window either: `regenerate_sitemap_from_settings()` returns early
225 + * while the master `enabled` flag is off, so a site with sitemaps disabled
226 + * and styling saved off never records a marked write, and stays exposed for
227 + * as long as it stays in that configuration.
228 + *
229 + * Recording the marker here on a fresh install separates the two cases. An
230 + * upgrade does not reach this code — WordPress does not re-run the
231 + * activation hook on update — so a genuine pre-2.1.1 site keeps the
232 + * fallback until its first marked write, exactly as before.
233 + *
234 + * `thinkrank_version` is the signal: set_default_options() adds it only
235 + * when absent and never updates it, so it is missing on the very first
236 + * activation and present on every one after.
237 + *
238 + * @since 2.1.1
239 + *
240 + * @return void
241 + */
242 + private function retire_sitemap_legacy_fallback(): void {
243 + if (get_option('thinkrank_version') !== false) {
244 + return;
245 + }
246 +
247 + require_once THINKRANK_PLUGIN_DIR . 'includes/cleanup-webroot.php';
248 +
249 + add_option(THINKRANK_SITEMAP_MARKED_WRITE_OPTION, '1', '', false);
250 + }
251 +
252 + /**
253 + * Give a brand-new install the feed posture the competitors ship with.
254 + *
255 + * The feed controls (#635) default to off in
256 + * {@see Site_Identity_Manager::get_default_settings()}, and they have to:
257 + * two of the three change what a site already publishes. Signing every
258 + * entry adds a line to what existing subscribers receive, and noindexing
259 + * feeds withdraws URLs a site may have had indexed for years — on a podcast
260 + * site, whose feed has to stay indexable, silently at that. Neither belongs
261 + * in a plugin update.
262 + *
263 + * A first install has no subscribers and no indexed feed, so there is
264 + * nothing to change and the protective defaults are simply the right
265 + * starting point — which is what The SEO Framework, Yoast and Rank Math all
266 + * ship. Seeding them here rather than in the defaults is what separates the
267 + * two cases.
268 + *
269 + * Excerpt-only is left off even here: it changes what readers get rather
270 + * than what scrapers can take, and that is the site owner's call.
271 + *
272 + * Same signal and same reasoning as {@see self::retire_sitemap_legacy_fallback()}:
273 + * `thinkrank_version` is absent only on the very first activation, and an
274 + * upgrade does not re-run the activation hook at all.
275 + *
276 + * @since 2.7.0
277 + *
278 + * @return void
279 + */
280 + private function seed_feed_defaults(): void {
281 + if (get_option('thinkrank_version') !== false) {
282 + return;
283 + }
284 +
285 + $manager = new \ThinkRank\SEO\Site_Identity_Manager();
286 +
287 + $manager->save_settings(
288 + 'site',
289 + null,
290 + [
291 + 'feed_source_link' => true,
292 + 'feed_noindex' => true,
293 + ]
294 + );
295 + }
296 +
297 + /**
183 298 * Set default plugin options
184 299 *
185 300 * @return void
186 301 */
@@ -187,11 +302,11 @@
187 302 private function set_default_options(): void {
188 303 $default_options = [
189 304 'thinkrank_version' => THINKRANK_VERSION,
190 305
191 - 'thinkrank_ai_provider' => 'openai',
306 + 'thinkrank_ai_provider' => \ThinkRank\Core\Settings::AI_PROVIDER_NONE,
192 307 'thinkrank_cache_duration' => 3600, // 1 hour
193 - 'thinkrank_max_requests_per_minute' => 10,
308 + 'thinkrank_max_requests_per_minute' => 0,
194 309 'thinkrank_enable_logging' => true,
195 310 'thinkrank_auto_optimize' => false,
196 311 'thinkrank_seo_score_threshold' => 70,
197 312 ];