PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.21
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.21
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / includes / metasync-helpers.php

metasync-helpers.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.21, at includes/metasync-helpers.php

150 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Shared MetaSync helper functions.
4 *
5 * Always loaded (in all request contexts: admin, REST, MCP, AJAX) so that the
6 * custom/LPS page detection rule lives in exactly one place and every SEO
7 * surface applies the same exclusion without copy-paste drift.
8 *
9 * @package Search_Atlas
10 */
11
12 if (!defined('ABSPATH')) {
13 exit;
14 }
15
16 if (!function_exists('metasync_is_custom_or_lps_page')) {
17 /**
18 * Determine whether a post is a MetaSync-built custom page that ships its own
19 * complete, self-contained SEO (Custom HTML pages and LPS-imported pages).
20 *
21 * These pages already include a full SEO head (title, meta description, OG,
22 * Twitter, JSON-LD schema) generated by LPS, so OTTO must not run on them —
23 * otherwise it injects/overwrites SEO from a different/older OTTO project and
24 * produces a page with two contradictory SEO identities.
25 *
26 * @param int $post_id Post ID to inspect.
27 * @return bool True when the post carries any custom-page / LPS marker.
28 */
29 function metasync_is_custom_or_lps_page($post_id){
30 $post_id = (int) $post_id;
31 if ($post_id <= 0 || !class_exists('Metasync_Custom_Pages')) {
32 return false;
33 }
34
35 if (get_post_meta($post_id, Metasync_Custom_Pages::META_IS_CUSTOM_HTML_PAGE, true) === '1') {
36 return true;
37 }
38 if (!empty(get_post_meta($post_id, Metasync_Custom_Pages::META_LPS_IMPORT, true))) {
39 return true;
40 }
41 if (!empty(get_post_meta($post_id, Metasync_Custom_Pages::META_CREATED_VIA_API, true))) {
42 return true;
43 }
44
45 return false;
46 }
47 }
48
49 if (!function_exists('metasync_is_scrape_request')) {
50 /**
51 * Detect WordPress core's internal file-editor "scrape" self-check request.
52 *
53 * When an admin saves a file in the Plugin/Theme Editor, WP core
54 * (wp_edit_theme_plugin_file()) fires a loopback request carrying
55 * wp_scrape_key + wp_scrape_nonce to detect whether the edit white-screened
56 * the site. For a THEME edit that loopback targets home_url('/') — a
57 * front-end GET that is NOT is_admin() — so it slips past OTTO's admin/AJAX/
58 * REST guards in metasync_start_otto(). OTTO must never buffer/rewrite it:
59 * the SimpleHtmlDom rewrite, and any fatal thrown inside the
60 * ob_start() display handler, corrupt the scrape and surface as the
61 * misleading "preg_match(): Cannot use output buffering in output buffering
62 * display handlers" error.
63 *
64 * @return bool True when the current request is a WP core scrape self-check.
65 */
66 function metasync_is_scrape_request(){
67 return isset($_GET['wp_scrape_key']) || isset($_GET['wp_scrape_nonce']);
68 }
69 }
70
71 if (!function_exists('metasync_get_custom_page_exclusion_meta_query')) {
72 /**
73 * Build a WP_Query meta_query fragment that excludes custom/LPS pages.
74 *
75 * When AND-combined with any query, this drops posts whose
76 * _metasync_is_custom_html_page marker is set to '1' while keeping all
77 * WordPress-managed posts (where the key is absent or set to anything else).
78 *
79 * @return array meta_query fragment.
80 */
81 function metasync_get_custom_page_exclusion_meta_query(){
82 return array(
83 'relation' => 'OR',
84 array(
85 'key' => '_metasync_is_custom_html_page',
86 'compare' => 'NOT EXISTS',
87 ),
88 array(
89 'key' => '_metasync_is_custom_html_page',
90 'value' => '1',
91 'compare' => '!=',
92 ),
93 );
94 }
95 }
96
97 if (!function_exists('metasync_discard_buffered_output')) {
98 /**
99 * Discard any pending output buffers before emitting a machine-readable body.
100 *
101 * Endpoints that serve XML/plain text (sitemaps, llms.txt, the IndexNow key
102 * file) must start at the very first byte of the response. When another
103 * plugin emits output earlier in the request — most commonly a PHP
104 * Deprecated/Notice/Warning rendered as HTML because display_errors or
105 * WP_DEBUG_DISPLAY is on — that text sits in the output buffer and gets
106 * flushed ahead of the `<?xml` declaration, making the response invalid and
107 * causing crawlers to reject the whole document.
108 *
109 * Call immediately before the header()/echo pair. On a healthy request there
110 * is nothing buffered and this is a no-op.
111 *
112 * @return bool True when the output is guaranteed clean; false when stray
113 * bytes could not be discarded (see the two cases below).
114 */
115 function metasync_discard_buffered_output(){
116 // Headers already sent means the buffer was flushed to the client, so the
117 // stray bytes are on the wire and cannot be recalled. Discarding buffers
118 // now would only drop legitimate content.
119 if (headers_sent()) {
120 return false;
121 }
122
123 while (ob_get_level() > 0) {
124 $status = ob_get_status();
125 $flags = isset($status['flags']) ? (int) $status['flags'] : 0;
126 $needed = PHP_OUTPUT_HANDLER_CLEANABLE | PHP_OUTPUT_HANDLER_REMOVABLE;
127
128 // Some buffers cannot be discarded at all — zlib.output_compression
129 // and handlers started without the cleanable/removable flags. Check
130 // first, because calling ob_end_clean() on one emits a PHP notice,
131 // which would add to the very corruption this guards against.
132 if (($flags & $needed) !== $needed) {
133 return false;
134 }
135
136 $level_before = ob_get_level();
137 ob_end_clean();
138
139 // Only keep going while the level is actually falling. Looping on
140 // ob_get_level() alone would spin forever against a buffer that
141 // refuses to close.
142 if (ob_get_level() >= $level_before) {
143 return false;
144 }
145 }
146
147 return true;
148 }
149 }
150