| @@ -63,9 +63,9 @@ | ||
| 63 | 63 | * |
| 64 | 64 | * @return bool True when the current request is a WP core scrape self-check. |
| 65 | 65 | */ |
| 66 | 66 | function metasync_is_scrape_request(){ |
| 67 | - return isset($_GET['wp_scrape_key']) || isset($_GET['wp_scrape_nonce']); | |
| 67 | + return isset($_GET['wp_scrape_key']) || isset($_GET['wp_scrape_nonce']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only front-end flag to skip buffering | |
| 68 | 68 | } |
| 69 | 69 | } |
| 70 | 70 | |
| 71 | 71 | if (!function_exists('metasync_get_custom_page_exclusion_meta_query')) { |
| @@ -147,9 +147,10 @@ | ||
| 147 | 147 | if (!isset($wpdb) || !is_object($wpdb) || empty($wpdb->postmeta) || empty($wpdb->posts)) { |
| 148 | 148 | return ''; |
| 149 | 149 | } |
| 150 | 150 | |
| 151 | - $sql = " AND NOT EXISTS (\n" | |
| 151 | + return $wpdb->prepare( | |
| 152 | + " AND NOT EXISTS (\n" | |
| 152 | 153 | . "\tSELECT 1 FROM {$wpdb->postmeta} AS _ms_ex_html\n" |
| 153 | 154 | . "\tWHERE _ms_ex_html.post_id = {$wpdb->posts}.ID\n" |
| 154 | 155 | . "\t\tAND _ms_ex_html.meta_key = %s\n" |
| 155 | 156 | . "\t\tAND _ms_ex_html.meta_value = %s\n" |
| @@ -164,12 +165,9 @@ | ||
| 164 | 165 | . "\tSELECT 1 FROM {$wpdb->postmeta} AS _ms_ex_api\n" |
| 165 | 166 | . "\tWHERE _ms_ex_api.post_id = {$wpdb->posts}.ID\n" |
| 166 | 167 | . "\t\tAND _ms_ex_api.meta_key = %s\n" |
| 167 | 168 | . "\t\tAND _ms_ex_api.meta_value NOT IN ('', '0')\n" |
| 168 | - . ")"; | |
| 169 | - | |
| 170 | - return $wpdb->prepare( | |
| 171 | - $sql, | |
| 169 | + . ")", | |
| 172 | 170 | '_metasync_is_custom_html_page', |
| 173 | 171 | '1', |
| 174 | 172 | '_metasync_lps_import', |
| 175 | 173 | '_metasync_created_via_api' |
| @@ -490,5 +488,41 @@ | ||
| 490 | 488 | } |
| 491 | 489 | |
| 492 | 490 | return $result; |
| 493 | 491 | } |
| 492 | +} | |
| 493 | + | |
| 494 | +/** | |
| 495 | + * Is headless mode active right now? | |
| 496 | + * | |
| 497 | + * Single skew-proof entry point for every caller that needs the headless flag. | |
| 498 | + * | |
| 499 | + * Metasync_Headless_Config is reached ONLY through the committed Composer | |
| 500 | + * classmap — nothing require_once's it, and vendor/composer/autoload_psr4.php | |
| 501 | + * is empty, so there is no fallback lookup path. A partially updated install | |
| 502 | + * (an interrupted upgrade, a half-finished file sync, or stale opcache bytecode | |
| 503 | + * for a single file) can therefore leave a newer caller beside a missing or | |
| 504 | + * older copy of the class, and an unguarded static call fatals. Several callers | |
| 505 | + * run on wp/wp_head, where that fatal is a white screen on every page view | |
| 506 | + * rather than a degraded feature. | |
| 507 | + * | |
| 508 | + * This file IS explicitly require_once'd by metasync.php, so it cannot itself | |
| 509 | + * be the unresolvable one — which is why the guard lives here rather than in a | |
| 510 | + * class that would have the same problem it is trying to solve. | |
| 511 | + * | |
| 512 | + * Treating an unresolvable class as "not headless" is the safe default: the | |
| 513 | + * site keeps rendering exactly as it did before headless mode existed. | |
| 514 | + * | |
| 515 | + * @return bool True only when the class resolves AND reports headless active. | |
| 516 | + */ | |
| 517 | +function metasync_headless_is_active() | |
| 518 | +{ | |
| 519 | + // class_exists() is redundant to static analysis — the class ships in this | |
| 520 | + // same plugin, so PHPStan proves the call always resolves. It is kept for | |
| 521 | + // the upgrade window described above, which exists only at runtime. | |
| 522 | + // @phpstan-ignore-next-line function.alreadyNarrowedType | |
| 523 | + if (!class_exists('Metasync_Headless_Config')) { | |
| 524 | + return false; | |
| 525 | + } | |
| 526 | + | |
| 527 | + return (bool) Metasync_Headless_Config::is_active(); | |
| 494 | 528 | } |