Changeset 3705859
- Timestamp:
- 09/21/2026 03:23:50 PM (4 weeks ago)
- Location:
- sitehale/trunk
- Files:
-
- 5 edited
-
changelog.txt (modified) (1 diff)
-
includes/class-sitehale-seo-guard.php (modified) (3 diffs)
-
includes/class-sitehale-seo-head.php (modified) (2 diffs)
-
readme.txt (modified) (2 diffs)
-
sitehale.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
sitehale/trunk/changelog.txt
r3699407 r3705859 3 3 readme.txt carries only recent releases: wp.org truncates a Changelog 4 4 section past 5,000 words, silently, at import. This file is the full 5 record, newest first (0.7.65 down to 0.1.0). 5 record, newest first (0.7.66 down to 0.1.0). 6 7 = 0.7.66 = 8 * Fixes your pages' search-engine details going missing after a plugin change. Before SiteHale describes your pages to search engines, it checks that no other plugin is already doing so, and it repeats that check whenever the site changes. It re-ran the check by itself only after a SiteHale update. After you switched another plugin on or off, or when the check expired after 30 days, your pages had no custom titles, descriptions or social-sharing details until enough visitors had arrived to complete the check. On a quiet site that could take days. SiteHale now finishes the check itself as soon as it's needed. Sites where another plugin describes your pages, and sites that have SiteHale's search-engine features switched off, are unaffected. 6 9 7 10 = 0.7.65 = -
sitehale/trunk/includes/class-sitehale-seo-guard.php
r3676052 r3705859 300 300 // response has already gone to the browser so the loopbacks cost the 301 301 // visitor nothing. 302 self::schedule_warm(); 303 } 304 305 /** 306 * Queue the warm loopbacks for this request's `shutdown`. 307 * 308 * The ONE place a warm is dispatched from, whichever event asked for it. 309 * Both callers need the same deferral (see above) and the same callback, and 310 * WordPress collapses an identical callback at an identical priority into a 311 * single registration, so a request that trips both triggers — a SiteHale 312 * update changes the fingerprint too — still sends one set of loopbacks. 313 */ 314 private static function schedule_warm() { 302 315 if ( class_exists( 'SiteHale_SEO_Handover' ) ) { 303 316 add_action( 'shutdown', array( 'SiteHale_SEO_Handover', 'warm_guard' ), 5 ); 317 } 318 } 319 320 /** Last dark-state warm: array( 'fp' => fingerprint, 'at' => unix time ). */ 321 const OPT_LAST_REWARM = 'sitehale_seo_guard_rewarm'; 322 323 /** Minimum gap between retries while the SAME dark state persists. */ 324 const REWARM_RETRY = 21600; // 6 hours. 325 326 /** 327 * Whether a dark guard should send the warm loopbacks now. PURE. 328 * 329 * 🔴 THE VERSION TRIGGER ABOVE COVERED ONE CAUSE OF A DARK HEAD OUT OF FOUR. 330 * `is_fresh()` discards the evidence when the plugin set changes, when the 331 * theme changes, when our version changes, and when it is 30 days old. Only 332 * the third ever woke the module. Found 2026-09-21 retiring cognihance's 333 * store: deactivating nine WooCommerce plugins took the title, description, 334 * OpenGraph and JSON-LD off every page, and the guard read 335 * "evidence is stale — the plugin set, theme or plugin version changed". 336 * Nothing was going to bring them back but visitors, and nothing reads the 337 * rendered head to notice they were gone. The 30-day TTL does the same to 338 * EVERY client once a month, with nobody touching anything. 339 * 340 * So this keys on the STATE, not on a list of causes: an undecided guard is 341 * a dark head whatever made it undecided, and a cause nobody has thought of 342 * yet is covered without anyone having to think of it. 343 * 344 * clear never — the head is rendering. 345 * conflict never — dark ON PURPOSE; a rival owns the head, and warming 346 * cannot change that (the fingerprint notices when it goes). 347 * unknown at once if the site has changed since the last warm, so a 348 * deactivation is answered on the very next request; otherwise 349 * no more often than REWARM_RETRY, so a site that can never 350 * reach `clear` — a one-page site with no singular URL, a host 351 * that refuses its own loopbacks — costs four loopbacks every 352 * six hours rather than four on every page view. 353 * 354 * @param string $state Guard verdict: clear|conflict|unknown. 355 * @param string $fingerprint The site's fingerprint now. 356 * @param mixed $last Stored OPT_LAST_REWARM value (any shape). 357 * @param int $now Unix time. 358 * @param int $retry Seconds between retries of an unchanged site. 359 * @return bool 360 */ 361 public static function rewarm_due( $state, $fingerprint, $last, $now, $retry = self::REWARM_RETRY ) { 362 if ( 'unknown' !== $state ) { 363 return false; 364 } 365 // Malformed or absent history is "never warmed", not a reason to wait. 366 if ( ! is_array( $last ) || ! isset( $last['fp'], $last['at'] ) ) { 367 return true; 368 } 369 if ( (string) $last['fp'] !== (string) $fingerprint ) { 370 return true; 371 } 372 return ( (int) $now - (int) $last['at'] ) >= (int) $retry; 373 } 374 375 /** 376 * Send the warm loopbacks when the guard is undecided. See rewarm_due(). 377 * 378 * ⚠️ NEVER UNDER WP-CLI. `--skip-plugins` and `--skip-themes` filter 379 * `active_plugins` and `stylesheet` inside the CLI process, so the 380 * fingerprint a CLI command computes is not the site's. Recording it would 381 * flip-flop against every web request — and this repo's own automation 382 * passes skip flags — firing a spurious warm on both sides of each CLI call, 383 * and a read-only command such as `plugin list` would start writing options. 384 * A change made FROM the CLI is still answered: the next web request sees 385 * the fingerprint it did not record and warms from there. 386 * 387 * ⚠️ WRITTEN BEFORE DISPATCH, for the same reason as the version option: the 388 * loopbacks are themselves requests to this site, still undecided, and they 389 * must find this warm already recorded or each would dispatch another. 390 */ 391 private static function maybe_rewarm_when_dark() { 392 if ( defined( 'WP_CLI' ) && WP_CLI ) { 393 return; 394 } 395 // Only where a head is WANTED: warming a site that renders nothing by 396 // design restores nothing. See SiteHale_SEO_Head::wants_render(). 397 if ( ! class_exists( 'SiteHale_SEO_Head' ) || ! SiteHale_SEO_Head::wants_render() ) { 398 return; 399 } 400 $state = self::state(); 401 if ( 'unknown' !== $state['state'] ) { 402 return; // The common case costs no query beyond the verdict itself. 403 } 404 $fp = self::fingerprint(); 405 $now = time(); 406 $last = get_option( self::OPT_LAST_REWARM, array() ); 407 if ( ! self::rewarm_due( $state['state'], $fp, $last, $now ) ) { 408 return; 409 } 410 update_option( self::OPT_LAST_REWARM, array( 'fp' => $fp, 'at' => $now ), false ); 411 self::schedule_warm(); 412 } 413 414 /** 415 * A decided verdict ends the dark episode, so it ends the warm history. 416 * 417 * Called from store(), the one place evidence is written — which only runs 418 * while the guard is still observing, so this costs nothing on a site that 419 * is simply rendering. It reads the verdict to decide; it never persists 420 * one (the stored record stays evidence, not a conclusion). 421 * 422 * ⚠️ rewarm_due() waits out REWARM_RETRY when the fingerprint matches the 423 * last warm — right within ONE episode, wrong across two. Measured on the 424 * testbox 2026-09-21: a plugin activated then deactivated returns the site 425 * to the fingerprint of a warm from twenty minutes earlier, the evidence 426 * was discarded in between, and the head stayed dark with the retry window 427 * reading "already tried". Plugin off, plugin back on is the most ordinary 428 * troubleshooting there is. Clearing here means every new episode warms at 429 * once, whatever caused it. `conflict` counts as decided too: a rival 430 * toggled on and back off is the same trap from the other side. 431 * 432 * @param array $rec The evidence record just written. 433 */ 434 private static function end_episode_if_decided( $rec ) { 435 $decided = self::verdict( 436 isset( $rec['contexts'] ) ? $rec['contexts'] : array(), 437 isset( $rec['rival'] ) ? $rec['rival'] : '' 438 ); 439 if ( 'unknown' !== $decided['state'] ) { 440 delete_option( self::OPT_LAST_REWARM ); 304 441 } 305 442 } … … 310 447 } 311 448 self::maybe_reobserve_after_update(); 449 self::maybe_rewarm_when_dark(); 312 450 // Outside every other emitter. `sitehale-term-seo.php` opens its own 313 451 // buffer at priority 1 and closes it at 999; ours must enclose that … … 725 863 // belongs in the autoload cache. 726 864 update_option( self::OPTION, $rec, true ); 865 866 // Ends the dark episode's warm history once the evidence decides. 867 self::end_episode_if_decided( $rec ); 727 868 } 728 869 } -
sitehale/trunk/includes/class-sitehale-seo-head.php
r3699407 r3705859 165 165 return false; 166 166 } 167 $opts = get_option( SiteHale_SEO_Store::OPTION, array() ); 168 // An explicit stored value always wins, in both directions. Absent, the 169 // default depends on who is running this site — see renders_by_default(). 170 $on = isset( $opts['render'] ) ? (bool) $opts['render'] : self::renders_by_default(); 167 $on = self::wants_render(); 171 168 if ( $on && ! SiteHale_SEO_Guard::clear() ) { 172 169 $on = false; … … 178 175 */ 179 176 return (bool) apply_filters( 'sitehale_seo_render', $on ); 177 } 178 179 /** 180 * Whether this site WANTS its head rendered, before the guard has its say. 181 * 182 * Split out of should_render() so the guard can ask it: a dark guard is only 183 * worth waking on a site that would render once it is clear. A free 184 * wordpress.org install renders nothing by design, so there is no head to 185 * restore and its footprint stays what it was — a warm on update, no more. 186 * 187 * @return bool 188 */ 189 public static function wants_render() { 190 if ( ! SiteHale_SEO_Store::enabled() ) { 191 return false; 192 } 193 $opts = get_option( SiteHale_SEO_Store::OPTION, array() ); 194 // An explicit stored value always wins, in both directions. Absent, the 195 // default depends on who is running this site — see renders_by_default(). 196 return isset( $opts['render'] ) ? (bool) $opts['render'] : self::renders_by_default(); 180 197 } 181 198 -
sitehale/trunk/readme.txt
r3699407 r3705859 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 0.7.6 57 Stable tag: 0.7.66 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 195 195 == Changelog == 196 196 197 = 0.7.66 = 198 * Fixes your pages' search-engine details going missing after a plugin change. Before SiteHale describes your pages to search engines, it checks that no other plugin is already doing so, and it repeats that check whenever the site changes. It re-ran the check by itself only after a SiteHale update. After you switched another plugin on or off, or when the check expired after 30 days, your pages had no custom titles, descriptions or social-sharing details until enough visitors had arrived to complete the check. On a quiet site that could take days. SiteHale now finishes the check itself as soon as it's needed. Sites where another plugin describes your pages, and sites that have SiteHale's search-engine features switched off, are unaffected. 199 197 200 = 0.7.65 = 198 201 * Keeps your RSS feeds out of Google's index again. The SEO plugin SiteHale replaced told search engines not to index feeds, and SiteHale did not carry that over. Every post and product has its own comments feed, so Google began finding feed copies of your pages and reporting them in Search Console as "Duplicate without user-selected canonical". SiteHale now sends the same noindex instruction on every feed that the previous plugin did. Your pages, and the feeds themselves, are unchanged for readers and feed apps. -
sitehale/trunk/sitehale.php
r3699407 r3705859 4 4 * Plugin URI: https://sitehale.com/how-it-works/ 5 5 * Description: Free Site Checkup (cron, PHP errors, database, updates, admins, SEO basics, disk, mail) plus the companion to the SiteHale managed care service. Sends nothing anywhere until you click Connect. 6 * Version: 0.7.6 56 * Version: 0.7.66 7 7 * Requires at least: 5.7 8 8 * Requires PHP: 7.4 … … 30 30 } 31 31 32 define( 'SITEHALE_VERSION', '0.7.6 5' );32 define( 'SITEHALE_VERSION', '0.7.66' ); 33 33 define( 'SITEHALE_PLUGIN_FILE', __FILE__ ); 34 34 define( 'SITEHALE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)