← All changes
|
includes/class-metasync-settings-registration.php
+79
-435
2.7.2
→
2.7.1
View file →
| @@ -263,14 +263,8 @@ | ||
| 263 | 263 | Metasync_Admin::option_key, |
| 264 | 264 | array(self::instance(), 'sanitize') |
| 265 | 265 | ); |
| 266 | 266 | |
| 267 | - // Analytics consent is deliberately NOT registered in the shared option | |
| 268 | - // group: options.php writes null for every registered-but-absent field, | |
| 269 | - // so saving any other page in the group would silently flip consent to | |
| 270 | - // 'no'. It is persisted only by the General tab AJAX save path, which | |
| 271 | - // handles both opt-in and opt-out explicitly. | |
| 272 | - | |
| 273 | 267 | // LLMs.txt settings are stored in a dedicated option so MCP tools and |
| 274 | 268 | // the admin UI share one source of truth. |
| 275 | 269 | register_setting( |
| 276 | 270 | Metasync_Admin::option_group, |
| @@ -695,30 +689,14 @@ | ||
| 695 | 689 | * Note: OTTO SSR is always enabled by default when connected |
| 696 | 690 | */ |
| 697 | 691 | |
| 698 | 692 | # field to accept the otto pixel |
| 699 | - # Read-only: the UUID is populated only by the trusted Search | |
| 700 | - # Atlas / heartbeat flows. The input stays in the DOM (readonly, like | |
| 701 | - # the apikey/linkgraph_token fields) so metasync-admin.js can keep | |
| 702 | - # targeting it by name to reflect connect/disconnect state, and the | |
| 703 | - # server rejects any submitted value regardless. | |
| 704 | 693 | add_settings_field( |
| 705 | 694 | 'otto_pixel_uuid', |
| 706 | 695 | $whitelabel_otto_name . ' Pixel UUID', |
| 707 | - function() use ($option_key, $whitelabel_otto_name) { | |
| 708 | - $value = Metasync::get_option('general')['otto_pixel_uuid'] ?? ''; | |
| 709 | - printf( | |
| 710 | - '<input type="text" id="otto_pixel_uuid" size="40" value="%s" name="%s" readonly="readonly" />', | |
| 711 | - esc_attr($value), | |
| 712 | - esc_attr($option_key . '[general][otto_pixel_uuid]') | |
| 713 | - ); | |
| 714 | - printf( | |
| 715 | - '<p class="description">%s</p>', | |
| 716 | - esc_html(sprintf( | |
| 717 | - 'Read-only. This UUID is assigned automatically by %s when you connect your account.', | |
| 718 | - $whitelabel_otto_name | |
| 719 | - )) | |
| 720 | - ); | |
| 696 | + function() use ($option_key) { | |
| 697 | + $value = Metasync::get_option('general')['otto_pixel_uuid'] ?? ''; | |
| 698 | + echo '<input type="text" size="40" value = "'.esc_attr($value).'" name="' . esc_attr($option_key) . '[general][otto_pixel_uuid]"/>'; | |
| 721 | 699 | }, |
| 722 | 700 | $page_slug . '_general', |
| 723 | 701 | $SECTION_METASYNC |
| 724 | 702 | ); |
| @@ -764,13 +742,12 @@ | ||
| 764 | 742 | # OTTO deployment visible on a page that also holds a custom value. |
| 765 | 743 | # |
| 766 | 744 | # This only re-orders what RENDERS. Nothing is written to or deleted |
| 767 | 745 | # from the stored post meta, so unchecking it restores the custom value |
| 768 | - # untouched. Applies to the SEO title, meta description and focus | |
| 769 | - # keyword. | |
| 746 | + # untouched. Applies to the SEO title and meta description only. | |
| 770 | 747 | add_settings_field( |
| 771 | 748 | 'seo_priority', |
| 772 | - 'SEO Meta Priority', | |
| 749 | + 'SEO Title & Description Priority', | |
| 773 | 750 | function() use ($whitelabel_otto_name, $option_key) { |
| 774 | 751 | $seo_priority = Metasync::get_option('general')['seo_priority'] ?? ''; |
| 775 | 752 | $checked = $seo_priority === Metasync_Seo_Precedence::PRIORITY_OTTO ? 'checked' : ''; |
| 776 | 753 | $name = $option_key . '[general][seo_priority]'; |
| @@ -782,9 +759,9 @@ | ||
| 782 | 759 | esc_attr($checked), |
| 783 | 760 | esc_html($whitelabel_otto_name) |
| 784 | 761 | ); |
| 785 | 762 | printf( |
| 786 | - '<p class="description" id="seo_priority_description"><strong>Off:</strong> Your custom SEO title, description and focus keyword — the values you edit on the post/page edit screen — appear first; %1$s fills in only where you have no value.<br><strong>On:</strong> Approved %1$s values appear first; your custom values remain the fallback.<br><span class="metasync-seo-priority-note">Your saved values are never deleted or overwritten.</span></p>', | |
| 763 | + '<p class="description" id="seo_priority_description"><strong>Off:</strong> Your custom title and description appear first; %1$s fills in only where you have no value.<br><strong>On:</strong> Approved %1$s values appear first; your custom values remain the fallback.<br><span class="metasync-seo-priority-note">Your saved values are never deleted or overwritten.</span></p>', | |
| 787 | 764 | esc_html($whitelabel_otto_name) |
| 788 | 765 | ); |
| 789 | 766 | echo '</div>'; |
| 790 | 767 | }, |
| @@ -791,87 +768,71 @@ | ||
| 791 | 768 | $page_slug . '_general', |
| 792 | 769 | $SECTION_METASYNC |
| 793 | 770 | ); |
| 794 | 771 | |
| 795 | - # ─────────────────────────────────────────────────────────────── | |
| 796 | - # Plugin-compatibility toggles live on the dedicated Compatibility | |
| 797 | - # page (admin.php?page=<slug>-compatibility), not the General tab. | |
| 798 | - # Each toggle is registered only while its related plugin is active, | |
| 799 | - # so the page never offers a control for a plugin that is not | |
| 800 | - # present. The stored value survives while the field is hidden — | |
| 801 | - # the save loop is isset-guarded and keeps absent keys. | |
| 802 | - # ─────────────────────────────────────────────────────────────── | |
| 803 | - $compat_page_slug = $page_slug . '_compatibility'; | |
| 772 | + # WP Rocket compatibility mode setting | |
| 773 | + add_settings_field( | |
| 774 | + 'otto_wp_rocket_compat', | |
| 775 | + 'WP Rocket Compatibility', | |
| 776 | + function() use ($whitelabel_otto_name, $option_key) { | |
| 777 | + $value = Metasync::get_option('general')['otto_wp_rocket_compat'] ?? 'auto'; | |
| 778 | + $wp_rocket_active = class_exists('WP_Rocket'); | |
| 804 | 779 | |
| 805 | - add_settings_section( | |
| 806 | - 'metasync_compat_toggles', | |
| 807 | - '', | |
| 808 | - function() {}, | |
| 809 | - $compat_page_slug | |
| 810 | - ); | |
| 780 | + echo '<select id="otto_wp_rocket_compat" name="' . esc_attr($option_key) . '[general][otto_wp_rocket_compat]">'; | |
| 781 | + echo '<option value="auto"' . selected($value, 'auto', false) . '>Auto (Recommended)</option>'; | |
| 782 | + echo '<option value="buffer"' . selected($value, 'buffer', false) . '>Buffer Mode (Faster)</option>'; | |
| 783 | + echo '<option value="http"' . selected($value, 'http', false) . '>HTTP Mode (Safer)</option>'; | |
| 784 | + echo '<option value="disable_otto"' . selected($value, 'disable_otto', false) . '>Disable ' . esc_html($whitelabel_otto_name) . ' when WP Rocket is active</option>'; | |
| 785 | + echo '</select>'; | |
| 811 | 786 | |
| 812 | - # WP Rocket compatibility toggle. | |
| 813 | - # Only registered when WP Rocket is actually present. | |
| 814 | - # WP Rocket defines WP_ROCKET_VERSION; a WP_Rocket class only exists in | |
| 815 | - # some builds, so check both. | |
| 816 | - if (defined('WP_ROCKET_VERSION') || class_exists('WP_Rocket')) { | |
| 817 | - add_settings_field( | |
| 818 | - 'otto_wp_rocket_compat', | |
| 819 | - 'WP Rocket Compatibility', | |
| 820 | - function() use ($whitelabel_otto_name, $option_key) { | |
| 821 | - $value = Metasync::get_option('general')['otto_wp_rocket_compat'] ?? 'auto'; | |
| 822 | - | |
| 823 | - echo '<select id="otto_wp_rocket_compat" name="' . esc_attr($option_key) . '[general][otto_wp_rocket_compat]">'; | |
| 824 | - echo '<option value="auto"' . selected($value, 'auto', false) . '>Auto (Recommended)</option>'; | |
| 825 | - echo '<option value="buffer"' . selected($value, 'buffer', false) . '>Buffer Mode (Faster)</option>'; | |
| 826 | - echo '<option value="http"' . selected($value, 'http', false) . '>HTTP Mode (Safer)</option>'; | |
| 827 | - echo '<option value="disable_otto"' . selected($value, 'disable_otto', false) . '>Disable ' . esc_html($whitelabel_otto_name) . ' when WP Rocket is active</option>'; | |
| 828 | - echo '</select>'; | |
| 829 | - | |
| 787 | + if ($wp_rocket_active) { | |
| 830 | 788 | echo '<p class="description" style="color: #0073aa; margin-top: 8px;">✓ WP Rocket detected - compatibility mode active</p>'; |
| 789 | + } | |
| 831 | 790 | |
| 832 | - echo '<p class="description" style="margin-top: 8px;">'; | |
| 833 | - echo '<strong>Auto (Recommended):</strong> Allows both ' . esc_html($whitelabel_otto_name) . ' and WP Rocket to work together. Does not set DONOTCACHEPAGE unless required (Brizy pages, SG Optimizer conflicts).<br>'; | |
| 834 | - echo '<strong>Buffer Mode:</strong> Forces output buffering method. Fastest but may conflict with some configurations.<br>'; | |
| 835 | - echo '<strong>HTTP Mode:</strong> Forces internal HTTP fetch method. Slower but more compatible.<br>'; | |
| 836 | - echo '<strong>Disable ' . esc_html($whitelabel_otto_name) . ':</strong> Completely disables ' . esc_html($whitelabel_otto_name) . ' when WP Rocket is detected. Use if issues persist.'; | |
| 837 | - echo '</p>'; | |
| 838 | - }, | |
| 839 | - $compat_page_slug, | |
| 840 | - 'metasync_compat_toggles' | |
| 841 | - ); | |
| 842 | - } | |
| 791 | + echo '<p class="description" style="margin-top: 8px;">'; | |
| 792 | + echo '<strong>Auto (Recommended):</strong> Allows both ' . esc_html($whitelabel_otto_name) . ' and WP Rocket to work together. Does not set DONOTCACHEPAGE unless required (Brizy pages, SG Optimizer conflicts).<br>'; | |
| 793 | + echo '<strong>Buffer Mode:</strong> Forces output buffering method. Fastest but may conflict with some configurations.<br>'; | |
| 794 | + echo '<strong>HTTP Mode:</strong> Forces internal HTTP fetch method. Slower but more compatible.<br>'; | |
| 795 | + echo '<strong>Disable ' . esc_html($whitelabel_otto_name) . ':</strong> Completely disables ' . esc_html($whitelabel_otto_name) . ' when WP Rocket is detected. Use if issues persist.'; | |
| 796 | + echo '</p>'; | |
| 797 | + }, | |
| 798 | + $page_slug . '_general', | |
| 799 | + $SECTION_METASYNC | |
| 800 | + ); | |
| 843 | 801 | |
| 844 | - # SiteGround Optimizer compatibility toggle. | |
| 845 | - # Only registered when SiteGround Optimizer is actually present. | |
| 846 | - if (defined('SiteGround_Optimizer\VERSION') || class_exists('SiteGround_Optimizer\Parser\Parser')) { | |
| 847 | - add_settings_field( | |
| 848 | - 'otto_sg_optimizer_compat', | |
| 849 | - 'SiteGround Optimizer Compatibility', | |
| 850 | - function() use ($option_key) { | |
| 851 | - $value = Metasync::get_option('general')['otto_sg_optimizer_compat'] ?? 'auto'; | |
| 802 | + # SiteGround Optimizer compatibility mode setting | |
| 803 | + add_settings_field( | |
| 804 | + 'otto_sg_optimizer_compat', | |
| 805 | + 'SiteGround Optimizer Compatibility', | |
| 806 | + function() use ($option_key) { | |
| 807 | + $value = Metasync::get_option('general')['otto_sg_optimizer_compat'] ?? 'auto'; | |
| 808 | + $sg_optimizer_active = defined('SiteGround_Optimizer\VERSION') || class_exists('SiteGround_Optimizer\Parser\Parser'); | |
| 852 | 809 | |
| 853 | - echo '<select id="otto_sg_optimizer_compat" name="' . esc_attr($option_key) . '[general][otto_sg_optimizer_compat]">'; | |
| 854 | - echo '<option value="auto"' . selected($value, 'auto', false) . '>Auto (Recommended)</option>'; | |
| 855 | - echo '<option value="buffer"' . selected($value, 'buffer', false) . '>Buffer Mode (Faster)</option>'; | |
| 856 | - echo '</select>'; | |
| 810 | + echo '<select id="otto_sg_optimizer_compat" name="' . esc_attr($option_key) . '[general][otto_sg_optimizer_compat]">'; | |
| 811 | + echo '<option value="auto"' . selected($value, 'auto', false) . '>Auto (Recommended)</option>'; | |
| 812 | + echo '<option value="buffer"' . selected($value, 'buffer', false) . '>Buffer Mode (Faster)</option>'; | |
| 813 | + echo '<option value="http"' . selected($value, 'http', false) . '>HTTP Mode (Safer)</option>'; | |
| 814 | + echo '</select>'; | |
| 857 | 815 | |
| 816 | + if ($sg_optimizer_active) { | |
| 858 | 817 | echo '<p class="description" style="color: #0073aa; margin-top: 8px;">✓ SiteGround Optimizer detected - compatibility mode active</p>'; |
| 818 | + } | |
| 859 | 819 | |
| 860 | - echo '<p class="description" style="margin-top: 8px;">'; | |
| 861 | - echo '<strong>Auto (Recommended):</strong> Uses the internal HTTP fetch method with SG Optimizer, protecting themes that defer inline CSS to the footer (e.g. Divi).<br>'; | |
| 862 | - echo '<strong>Buffer Mode (Faster):</strong> Renders in-process with no internal page fetch, roughly halving uncached render time. Best when pages can never be host-cached anyway (e.g. a plugin starts a PHP session on every request — though MetaSync already removes that automatically for BookingPress). May conflict with Divi-style themes.'; | |
| 863 | - echo '</p>'; | |
| 864 | - }, | |
| 865 | - $compat_page_slug, | |
| 866 | - 'metasync_compat_toggles' | |
| 867 | - ); | |
| 868 | - } | |
| 820 | + echo '<p class="description" style="margin-top: 8px;">'; | |
| 821 | + echo '<strong>Auto (Recommended):</strong> Uses the internal HTTP fetch method with SG Optimizer, protecting themes that defer inline CSS to the footer (e.g. Divi).<br>'; | |
| 822 | + echo '<strong>Buffer Mode (Faster):</strong> Renders in-process with no internal page fetch, roughly halving uncached render time. Best when pages can never be host-cached anyway (e.g. a plugin starts a PHP session on every request — though MetaSync already removes that automatically for BookingPress). May conflict with Divi-style themes.<br>'; | |
| 823 | + echo '<strong>HTTP Mode:</strong> Forces the internal HTTP fetch method. Slower but more compatible.'; | |
| 824 | + echo '</p>'; | |
| 825 | + }, | |
| 826 | + $page_slug . '_general', | |
| 827 | + $SECTION_METASYNC | |
| 828 | + ); | |
| 869 | 829 | |
| 870 | - # BookingPress front-end session compatibility toggle. | |
| 830 | + # BookingPress front-end session compatibility setting. | |
| 871 | 831 | # Only registered when BookingPress is actually present on the site: |
| 872 | 832 | # the shim no-ops without it, so the field would be noise for every |
| 873 | - # other install. | |
| 833 | + # other install. The stored value survives while the field is hidden | |
| 834 | + # (the save loop is isset-guarded and keeps absent keys). | |
| 874 | 835 | if (class_exists('bookingpress_spam_protection')) { |
| 875 | 836 | add_settings_field( |
| 876 | 837 | 'bookingpress_session_compat', |
| 877 | 838 | 'BookingPress Session Compatibility', |
| @@ -890,10 +851,10 @@ | ||
| 890 | 851 | echo '<strong>Auto (Recommended):</strong> Removes the unnecessary session start on public pages so page caching can work.<br>'; |
| 891 | 852 | echo '<strong>Disabled:</strong> Leaves BookingPress exactly as installed (no page caching while it stays active).'; |
| 892 | 853 | echo '</p>'; |
| 893 | 854 | }, |
| 894 | - $compat_page_slug, | |
| 895 | - 'metasync_compat_toggles' | |
| 855 | + $page_slug . '_general', | |
| 856 | + $SECTION_METASYNC | |
| 896 | 857 | ); |
| 897 | 858 | } |
| 898 | 859 | |
| 899 | 860 | /** |
| @@ -2071,71 +2032,8 @@ | ||
| 2071 | 2032 | return $base; |
| 2072 | 2033 | } |
| 2073 | 2034 | |
| 2074 | 2035 | /** |
| 2075 | - * Enforce the read-only contract on general.otto_pixel_uuid. | |
| 2076 | - * | |
| 2077 | - * The UUID is owned by trusted Search Atlas flows and explicit disconnect. | |
| 2078 | - * Any changed value submitted through the settings form is rejected and the | |
| 2079 | - * stored UUID is preserved verbatim. Programmatic trusted writers bypass | |
| 2080 | - * this form-submission guard. | |
| 2081 | - * | |
| 2082 | - * @param array $merged The layered-merge result about to be saved. | |
| 2083 | - * @param array $stored The stored option, used as the preserved value. | |
| 2084 | - * @return array The merged array with the stored otto_pixel_uuid kept. | |
| 2085 | - */ | |
| 2086 | - private static function protect_otto_pixel_uuid(array $merged, array $stored) | |
| 2087 | - { | |
| 2088 | - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- sanitize callback, the caller already verified the nonce | |
| 2089 | - if (!isset($_POST['metasync_options']['general']) | |
| 2090 | - || !is_array($_POST['metasync_options']['general']) | |
| 2091 | - || !array_key_exists('otto_pixel_uuid', $_POST['metasync_options']['general'])) { | |
| 2092 | - return $merged; | |
| 2093 | - } | |
| 2094 | - | |
| 2095 | - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- sanitize callback, the caller already verified the nonce | |
| 2096 | - $submitted = $_POST['metasync_options']['general']['otto_pixel_uuid']; | |
| 2097 | - if (is_string($submitted)) { | |
| 2098 | - $submitted = sanitize_text_field(wp_unslash($submitted)); | |
| 2099 | - } | |
| 2100 | - $candidate = $merged['general']['otto_pixel_uuid'] ?? null; | |
| 2101 | - | |
| 2102 | - // A trusted flow may have replaced the submitted value after the form | |
| 2103 | - // was posted; such a value is not user-authored and must survive. | |
| 2104 | - // The trust inference must NOT rest on the inequality alone: the | |
| 2105 | - // merged candidate comes from the raw submission layer and can differ | |
| 2106 | - // from the sanitized $submitted merely because sanitization alters | |
| 2107 | - // the payload (tags, line breaks, repeated spaces). A mismatch on | |
| 2108 | - // its own must never authorize storing the raw value, so only a | |
| 2109 | - // sanctioned shape may pass through: the empty string (the | |
| 2110 | - // disconnected state) or a strictly valid UUID — the only values | |
| 2111 | - // trusted writers ever set. Everything else falls back to stored. | |
| 2112 | - if ($candidate !== $submitted | |
| 2113 | - && ($candidate === '' || Metasync::is_valid_uuid($candidate))) { | |
| 2114 | - return $merged; | |
| 2115 | - } | |
| 2116 | - | |
| 2117 | - $stored_value = $stored['general']['otto_pixel_uuid'] ?? null; | |
| 2118 | - if ($candidate === $stored_value) { | |
| 2119 | - return $merged; | |
| 2120 | - } | |
| 2121 | - | |
| 2122 | - if (is_string($stored_value)) { | |
| 2123 | - $merged['general']['otto_pixel_uuid'] = $stored_value; | |
| 2124 | - } else { | |
| 2125 | - unset($merged['general']['otto_pixel_uuid']); | |
| 2126 | - } | |
| 2127 | - | |
| 2128 | - add_settings_error( | |
| 2129 | - 'metasync_messages', | |
| 2130 | - 'metasync_otto_pixel_uuid_readonly', | |
| 2131 | - __('OTTO Pixel UUID is read-only and can only be assigned by the Search Atlas / heartbeat synchronization.', 'metasync'), | |
| 2132 | - 'error' | |
| 2133 | - ); | |
| 2134 | - return $merged; | |
| 2135 | - } | |
| 2136 | - | |
| 2137 | - /** | |
| 2138 | 2036 | * Whether the array is a numerically-indexed list (keys 0..n-1). |
| 2139 | 2037 | * |
| 2140 | 2038 | * Answers false for the empty array — range(0, -1) counts downwards and |
| 2141 | 2039 | * yields [0, -1], not []. That is load-bearing rather than incidental: an |
| @@ -2246,9 +2144,8 @@ | ||
| 2246 | 2144 | } |
| 2247 | 2145 | } |
| 2248 | 2146 | } |
| 2249 | 2147 | $general_tab_submitted = ($active_tab === 'general'); |
| 2250 | - $compatibility_tab_submitted = ($active_tab === 'compatibility'); | |
| 2251 | 2148 | |
| 2252 | 2149 | // General Settings |
| 2253 | 2150 | if (isset($input['general']['apikey'])) { |
| 2254 | 2151 | $new_input['general']['apikey'] = sanitize_text_field($input['general']['apikey']); |
| @@ -2253,26 +2150,8 @@ | ||
| 2253 | 2150 | if (isset($input['general']['apikey'])) { |
| 2254 | 2151 | $new_input['general']['apikey'] = sanitize_text_field($input['general']['apikey']); |
| 2255 | 2152 | } |
| 2256 | 2153 | |
| 2257 | - # Plugin-compatibility toggles live on the Compatibility page, which | |
| 2258 | - # saves through options.php with a hidden active_tab=compatibility so | |
| 2259 | - # the General-tab-gated blocks below never fire. Sanitize the two | |
| 2260 | - # toggles explicitly so the raw submission never reaches storage | |
| 2261 | - # unsanitized, and so a value that is absent (toggle hidden because | |
| 2262 | - # its plugin is not active) keeps its stored value via the merge. | |
| 2263 | - if ($compatibility_tab_submitted) { | |
| 2264 | - if (isset($input['general']['otto_wp_rocket_compat'])) { | |
| 2265 | - $new_input['general']['otto_wp_rocket_compat'] = sanitize_text_field($input['general']['otto_wp_rocket_compat']); | |
| 2266 | - } | |
| 2267 | - if (isset($input['general']['otto_sg_optimizer_compat'])) { | |
| 2268 | - $new_input['general']['otto_sg_optimizer_compat'] = sanitize_text_field($input['general']['otto_sg_optimizer_compat']); | |
| 2269 | - } | |
| 2270 | - if (isset($input['general']['bookingpress_session_compat'])) { | |
| 2271 | - $new_input['general']['bookingpress_session_compat'] = sanitize_text_field($input['general']['bookingpress_session_compat']); | |
| 2272 | - } | |
| 2273 | - } | |
| 2274 | - | |
| 2275 | 2154 | // Meta Box Visibility Settings |
| 2276 | 2155 | if (isset($input['general']) && $general_tab_submitted) { |
| 2277 | 2156 | $checkbox_fields = [ |
| 2278 | 2157 | 'disable_common_robots_metabox', |
| @@ -2773,16 +2652,21 @@ | ||
| 2773 | 2652 | } |
| 2774 | 2653 | } |
| 2775 | 2654 | } |
| 2776 | 2655 | |
| 2777 | - // Indexation Control Settings. Stored as the strings 'true'/'false' (the form, | |
| 2778 | - // the setup wizard and count_index_rule_changes() all compare against those); | |
| 2779 | - // boolval('false') is true, which silently turned every unchecked box on. | |
| 2780 | - foreach (array('index_date_archives', 'index_tag_archives', 'index_author_archives', 'index_format_archives') as $index_field) { | |
| 2781 | - if (isset($input['seo_controls'][$index_field])) { | |
| 2782 | - $new_input['seo_controls'][$index_field] = filter_var($input['seo_controls'][$index_field], FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'; | |
| 2783 | - } | |
| 2656 | + // Indexation Control Settings | |
| 2657 | + if (isset($input['seo_controls']['index_date_archives'])) { | |
| 2658 | + $new_input['seo_controls']['index_date_archives'] = boolval($input['seo_controls']['index_date_archives']); | |
| 2784 | 2659 | } |
| 2660 | + if (isset($input['seo_controls']['index_tag_archives'])) { | |
| 2661 | + $new_input['seo_controls']['index_tag_archives'] = boolval($input['seo_controls']['index_tag_archives']); | |
| 2662 | + } | |
| 2663 | + if (isset($input['seo_controls']['index_author_archives'])) { | |
| 2664 | + $new_input['seo_controls']['index_author_archives'] = boolval($input['seo_controls']['index_author_archives']); | |
| 2665 | + } | |
| 2666 | + if (isset($input['seo_controls']['index_format_archives'])) { | |
| 2667 | + $new_input['seo_controls']['index_format_archives'] = boolval($input['seo_controls']['index_format_archives']); | |
| 2668 | + } | |
| 2785 | 2669 | |
| 2786 | 2670 | // Handle post-save heartbeat trigger for domain changes |
| 2787 | 2671 | if (isset($new_input['_trigger_heartbeat_after_save'])) { |
| 2788 | 2672 | $context = $new_input['_trigger_heartbeat_after_save']; |
| @@ -2818,71 +2702,13 @@ | ||
| 2818 | 2702 | if (isset($input['whitelabel'], $new_input['whitelabel'])) { |
| 2819 | 2703 | $input['whitelabel'] = $new_input['whitelabel']; |
| 2820 | 2704 | } |
| 2821 | 2705 | |
| 2822 | - // The branding contract must also hold on the native options.php | |
| 2823 | - // submit (administrators with JavaScript disabled or broken — the | |
| 2824 | - // AJAX handler never runs for them): a value that could not be | |
| 2825 | - // written to metasync.php must not be persisted either. A rejected | |
| 2826 | - // key is stripped from the raw layer so the layered merge keeps the | |
| 2827 | - // stored value, leaving options and the physical header untouched. | |
| 2828 | - // The AJAX save and the JSON import already enforce this contract | |
| 2829 | - // before their writes, so for them this is a consistent no-op. | |
| 2830 | - if (!class_exists('Metasync_Activator')) { | |
| 2831 | - // Defensive only: real requests always have the activator via the | |
| 2832 | - // plugin boot / Composer classmap. Plain dirname() (not | |
| 2833 | - // plugin_dir_path()) keeps this loadable in the isolated unit | |
| 2834 | - // harnesses that define neither. | |
| 2835 | - $activator_file = dirname(__FILE__) . '/class-metasync-activator.php'; | |
| 2836 | - if (is_readable($activator_file)) { | |
| 2837 | - require_once $activator_file; | |
| 2838 | - } | |
| 2839 | - } | |
| 2840 | - if (class_exists('Metasync_Activator') && isset($input['general']) && is_array($input['general'])) { | |
| 2841 | - $branding_contract_fields = array( | |
| 2842 | - 'white_label_plugin_name' => false, | |
| 2843 | - 'white_label_plugin_description' => false, | |
| 2844 | - 'white_label_plugin_author' => false, | |
| 2845 | - 'white_label_plugin_author_uri' => true, | |
| 2846 | - 'white_label_plugin_uri' => true, | |
| 2847 | - ); | |
| 2848 | - foreach ($branding_contract_fields as $branding_key => $branding_is_url) { | |
| 2849 | - if (!array_key_exists($branding_key, $input['general']) || !is_string($input['general'][$branding_key])) { | |
| 2850 | - continue; | |
| 2851 | - } | |
| 2852 | - $branding_value = trim(wp_unslash($input['general'][$branding_key])); | |
| 2853 | - if ($branding_value === '' | |
| 2854 | - || Metasync_Activator::is_valid_plugin_header_value($branding_value, $branding_is_url)) { | |
| 2855 | - continue; | |
| 2856 | - } | |
| 2857 | - add_settings_error( | |
| 2858 | - 'metasync_messages', | |
| 2859 | - 'metasync_branding_' . $branding_key, | |
| 2860 | - $branding_is_url | |
| 2861 | - ? sprintf('%s must be a valid http:// or https:// address.', $branding_key === 'white_label_plugin_author_uri' ? 'Author URL' : 'Plugin URL') | |
| 2862 | - : sprintf('%s contains characters that cannot be saved (line breaks, control or invisible characters, "*/", or PHP tags are not allowed).', $branding_key === 'white_label_plugin_name' ? 'Plugin Name' : ($branding_key === 'white_label_plugin_description' ? 'Plugin Description' : 'Author')), | |
| 2863 | - 'error' | |
| 2864 | - ); | |
| 2865 | - unset($input['general'][$branding_key]); | |
| 2866 | - } | |
| 2867 | - } | |
| 2868 | - | |
| 2869 | 2706 | // Layered merge: stored option, then the raw submission, then the |
| 2870 | 2707 | // sanitized values. Replaces the former shallow array_merge(), which |
| 2871 | 2708 | // let the raw submission overwrite every sanitized section wholesale. |
| 2872 | 2709 | $result = self::merge_option_layers($stored_option, $input, $new_input); |
| 2873 | 2710 | |
| 2874 | - // otto_pixel_uuid is read-only. The field is populated only by | |
| 2875 | - // the trusted Search Atlas / heartbeat flows, and sanitize_option runs | |
| 2876 | - // on every update_option() of this option — including the AJAX save and | |
| 2877 | - // a forged POST that smuggles the key in — so this is the last line of | |
| 2878 | - // defence for the shape of the stored value. An empty string (the | |
| 2879 | - // disconnected state) and a valid UUID pass through; anything else | |
| 2880 | - // (a username or other arbitrary text) falls back to the stored value. | |
| 2881 | - // The disconnect flow is unaffected: it unsets the key and writes | |
| 2882 | - // through write_options_without_stored_merge(), never through here. | |
| 2883 | - $result = self::protect_otto_pixel_uuid($result, $stored_option); | |
| 2884 | - | |
| 2885 | 2711 | if ($whitelabel_reset) { |
| 2886 | 2712 | $result['whitelabel'] = $new_input['whitelabel']; |
| 2887 | 2713 | } |
| 2888 | 2714 | |
| @@ -2941,12 +2767,9 @@ | ||
| 2941 | 2767 | 'white_label_plugin_name', 'white_label_plugin_description', |
| 2942 | 2768 | 'white_label_plugin_author', 'white_label_plugin_menu_slug', |
| 2943 | 2769 | 'white_label_plugin_menu_icon', 'enabled_plugin_css', |
| 2944 | 2770 | 'enabled_elementor_plugin_css_color','enabled_elementor_plugin_css', |
| 2945 | - // The OTTO Pixel UUID is deliberately NOT listed here. It is | |
| 2946 | - // read-only and populated only by the trusted Search Atlas / | |
| 2947 | - // heartbeat flows; sanitize() preserves the stored value. | |
| 2948 | - 'periodic_clear_otto_cache','periodic_clear_ottopage_cache', | |
| 2771 | + 'otto_pixel_uuid','periodic_clear_otto_cache','periodic_clear_ottopage_cache', | |
| 2949 | 2772 | 'periodic_clear_ottopost_cache', 'whitelabel_otto_name', 'otto_wp_rocket_compat', |
| 2950 | 2773 | 'otto_sg_optimizer_compat', |
| 2951 | 2774 | 'bookingpress_session_compat' |
| 2952 | 2775 | ]; |
| @@ -2976,21 +2799,8 @@ | ||
| 2976 | 2799 | // Whitelabel field validation issues are collected per-field and reported back |
| 2977 | 2800 | // without blocking the rest of the settings save. |
| 2978 | 2801 | $whitelabel_errors = []; |
| 2979 | 2802 | |
| 2980 | - // Branding field errors use the same contract as the physical | |
| 2981 | - // plugin-header updater; they are blocking because a value that cannot | |
| 2982 | - // be written to metasync.php must not be persisted to the options | |
| 2983 | - // either (that divergence is exactly what WP-636 closes). | |
| 2984 | - $branding_errors = []; | |
| 2985 | - | |
| 2986 | - // The five white-label branding fields share the plugin-header | |
| 2987 | - // validation contract, so the activator (which owns that contract) | |
| 2988 | - // must be available before the field loops run. | |
| 2989 | - if (!class_exists('Metasync_Activator')) { | |
| 2990 | - require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-metasync-activator.php'; | |
| 2991 | - } | |
| 2992 | - | |
| 2993 | 2803 | foreach ($text_fields as $field) { |
| 2994 | 2804 | |
| 2995 | 2805 | if (isset($_POST['metasync_options']['general'][$field])) { |
| 2996 | 2806 | |
| @@ -3004,9 +2814,10 @@ | ||
| 3004 | 2814 | 'white_label_plugin_description', |
| 3005 | 2815 | 'white_label_plugin_author', |
| 3006 | 2816 | 'white_label_plugin_menu_slug', |
| 3007 | 2817 | 'white_label_plugin_menu_icon', |
| 3008 | - 'whitelabel_otto_name' | |
| 2818 | + 'whitelabel_otto_name', | |
| 2819 | + 'otto_pixel_uuid' | |
| 3009 | 2820 | ]; |
| 3010 | 2821 | |
| 3011 | 2822 | if ($value === '' && !in_array($field, $clearable_fields)) { |
| 3012 | 2823 | continue; |
| @@ -3021,25 +2832,8 @@ | ||
| 3021 | 2832 | continue; |
| 3022 | 2833 | } |
| 3023 | 2834 | } |
| 3024 | 2835 | |
| 3025 | - // Branding text must satisfy the plugin-header contract. The | |
| 3026 | - // raw value is checked before sanitisation so multiline or | |
| 3027 | - // control-character input is rejected with an error instead of | |
| 3028 | - // being silently mangled into something else that gets saved. | |
| 3029 | - $branding_text_fields = [ | |
| 3030 | - 'white_label_plugin_name' => 'Plugin Name', | |
| 3031 | - 'white_label_plugin_description' => 'Plugin Description', | |
| 3032 | - 'white_label_plugin_author' => 'Author', | |
| 3033 | - ]; | |
| 3034 | - if (isset($branding_text_fields[$field]) | |
| 3035 | - && $value !== '' | |
| 3036 | - && !Metasync_Activator::is_valid_plugin_header_value($value, false)) { | |
| 3037 | - $branding_errors[$field] = $branding_text_fields[$field] | |
| 3038 | - . ' contains characters that cannot be saved (line breaks, control or invisible characters, "*/", or PHP tags are not allowed).'; | |
| 3039 | - continue; | |
| 3040 | - } | |
| 3041 | - | |
| 3042 | 2836 | if ($field === 'white_label_plugin_menu_icon') { |
| 3043 | 2837 | |
| 3044 | 2838 | if ($value === '') { |
| 3045 | 2839 | $metasync_options['general'][$field] = ''; |
| @@ -3128,34 +2922,16 @@ | ||
| 3128 | 2922 | $parsed_url = wp_parse_url($value); |
| 3129 | 2923 | $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; |
| 3130 | 2924 | |
| 3131 | 2925 | if (strpos($host, '.') !== false || filter_var($host, FILTER_VALIDATE_IP)) { |
| 3132 | - // Branding URLs must additionally satisfy the plugin-header | |
| 3133 | - // contract (http/https scheme, no embedded control characters); | |
| 3134 | - // anything else would be saved but silently fail to reach | |
| 3135 | - // metasync.php, leaving options inconsistent with the Plugins | |
| 3136 | - // screen branding. | |
| 3137 | - $branding_url_field_names = [ | |
| 3138 | - 'white_label_plugin_author_uri' => 'Author URL', | |
| 3139 | - 'white_label_plugin_uri' => 'Plugin URL', | |
| 3140 | - ]; | |
| 3141 | - if (!Metasync_Activator::is_valid_plugin_header_value($value, true)) { | |
| 3142 | - $branding_errors[$field] = $branding_url_field_names[$field] | |
| 3143 | - . ' must be a valid http:// or https:// address.'; | |
| 3144 | - } else { | |
| 3145 | - $metasync_options['general'][$field] = esc_url_raw($value); | |
| 3146 | - } | |
| 2926 | + $metasync_options['general'][$field] = esc_url_raw($value); | |
| 3147 | 2927 | } else { |
| 3148 | 2928 | $field_name = ($field === 'white_label_plugin_author_uri') ? 'Author URL' : 'Plugin URL'; |
| 3149 | - // Pushed into $branding_errors only: the blocking | |
| 3150 | - // merge below appends every branding message to the | |
| 3151 | - // response errors, so a second direct push would render | |
| 3152 | - // the same message twice. | |
| 3153 | - $branding_errors[$field] = 'Invalid ' . $field_name . ' format. Please use a proper domain name (e.g., example.com).'; | |
| 2929 | + $validation_errors[] = 'Invalid ' . $field_name . ' format. Please use a proper domain name (e.g., example.com).'; | |
| 3154 | 2930 | } |
| 3155 | 2931 | } else { |
| 3156 | 2932 | $field_name = ($field === 'white_label_plugin_author_uri') ? 'Author URL' : 'Plugin URL'; |
| 3157 | - $branding_errors[$field] = $field_name . ' must be a valid http:// or https:// address.'; | |
| 2933 | + $validation_errors[] = 'Invalid ' . $field_name . ' format.'; | |
| 3158 | 2934 | } |
| 3159 | 2935 | } |
| 3160 | 2936 | } |
| 3161 | 2937 | } |
| @@ -3273,67 +3049,17 @@ | ||
| 3273 | 3049 | $validation_errors[] = 'Add at least one of: Plugin Name, Logo URL, or Author to save whitelabel settings.'; |
| 3274 | 3050 | } |
| 3275 | 3051 | } |
| 3276 | 3052 | |
| 3277 | - // Branding errors block the whole save: an invalid value must never | |
| 3278 | - // be persisted, and the response carries the field keys so the client | |
| 3279 | - // can render the error beside each offending input. | |
| 3280 | - foreach ($branding_errors as $branding_error) { | |
| 3281 | - $validation_errors[] = $branding_error; | |
| 3282 | - } | |
| 3283 | - | |
| 3284 | 3053 | if (!empty($validation_errors)) { |
| 3285 | 3054 | $this->force_release_settings_recovery_lock(); |
| 3286 | 3055 | wp_send_json_error([ |
| 3287 | - 'errors' => $validation_errors, | |
| 3288 | - 'field_errors' => $branding_errors, | |
| 3056 | + 'errors' => $validation_errors | |
| 3289 | 3057 | ]); |
| 3290 | 3058 | return; |
| 3291 | 3059 | } |
| 3292 | 3060 | |
| 3293 | - // The analytics consent toggle posts at the top level (hidden fallback | |
| 3294 | - // plus checkbox), not under metasync_options, so persist it here. This | |
| 3295 | - // is the "turn it off at any time" path promised in the readme. | |
| 3296 | - if ($general_tab_submitted && isset($_POST['metasync_analytics_opt_in'])) { | |
| 3297 | - $consent = sanitize_key(wp_unslash($_POST['metasync_analytics_opt_in'])); | |
| 3298 | - update_option('metasync_analytics_opt_in', $consent === 'yes' ? 'yes' : 'no'); | |
| 3299 | - update_option('metasync_analytics_consent_explicit', 'yes'); | |
| 3300 | - } | |
| 3301 | - | |
| 3302 | 3061 | $old_options = Metasync::get_option('general') ?? []; |
| 3303 | - | |
| 3304 | - // White-label branding is persisted twice — into the options and into | |
| 3305 | - // the physical metasync.php header — so the header update is applied | |
| 3306 | - // BEFORE any option write. On failure nothing has been persisted and | |
| 3307 | - // both the file and the stored options still carry the previous | |
| 3308 | - // branding; the save is rejected instead of ending up inconsistent. | |
| 3309 | - $branding_headers_written = false; | |
| 3310 | - $original_plugin_file_content = false; | |
| 3311 | - $plugin_header_file = Metasync_Activator::plugin_header_file_path(); | |
| 3312 | - if ($general_tab_submitted || $whitelabel_tab_submitted) { | |
| 3313 | - $original_plugin_file_content = file_get_contents($plugin_header_file); | |
| 3314 | - $headers_applied = Metasync_Activator::apply_plugin_file_headers_for_save( | |
| 3315 | - $metasync_options['general'], | |
| 3316 | - is_array($old_options) ? $old_options : array() | |
| 3317 | - ); | |
| 3318 | - if ($headers_applied === false) { | |
| 3319 | - $this->force_release_settings_recovery_lock(); | |
| 3320 | - wp_send_json_error([ | |
| 3321 | - 'errors' => array( | |
| 3322 | - 'White-label branding could not be written to the plugin file (metasync.php may be read-only or damaged). Nothing was saved; the previous branding is unchanged.', | |
| 3323 | - ), | |
| 3324 | - ]); | |
| 3325 | - // The return is unreachable in WordPress (wp_send_json_error | |
| 3326 | - // terminates the request) but keeps the handler safe under the | |
| 3327 | - // non-terminating stubs the unit tests run with. | |
| 3328 | - // @phpstan-ignore-next-line deadCode.unreachable | |
| 3329 | - return; | |
| 3330 | - } | |
| 3331 | - if ($headers_applied === true && is_string($original_plugin_file_content)) { | |
| 3332 | - $branding_headers_written = file_get_contents($plugin_header_file) !== $original_plugin_file_content; | |
| 3333 | - } | |
| 3334 | - } | |
| 3335 | - | |
| 3336 | 3062 | // Stored value is encrypted at rest (or legacy plaintext/empty). Keep it as-is |
| 3337 | 3063 | // for preserve/revert, and decrypt a separate copy for plaintext comparison. |
| 3338 | 3064 | $old_stored_api_key = $old_options['searchatlas_api_key'] ?? ''; |
| 3339 | 3065 | $old_api_key = Metasync::get_searchatlas_api_key(); |
| @@ -3377,19 +3103,14 @@ | ||
| 3377 | 3103 | |
| 3378 | 3104 | if ( $ping['connected'] === true ) { |
| 3379 | 3105 | $api_key_validated = true; |
| 3380 | 3106 | $is_connected = true; |
| 3381 | - // Trusted ping flow: only a real UUID is stored. The | |
| 3382 | - // form itself can no longer submit this field, so this ping is | |
| 3383 | - // one of the few remaining sanctioned write paths for it. | |
| 3384 | - if ( ! empty( $ping['otto_pixel_uuid'] ) && Metasync::is_valid_uuid( $ping['otto_pixel_uuid'] ) ) { | |
| 3107 | + if ( ! empty( $ping['otto_pixel_uuid'] ) ) { | |
| 3385 | 3108 | $existing_uuid = $metasync_options['general']['otto_pixel_uuid'] ?? ''; |
| 3386 | 3109 | if ( empty( $existing_uuid ) || $existing_uuid !== $ping['otto_pixel_uuid'] ) { |
| 3387 | 3110 | $metasync_options['general']['otto_pixel_uuid'] = sanitize_text_field( $ping['otto_pixel_uuid'] ); |
| 3388 | 3111 | Metasync::set_option($metasync_options); |
| 3389 | 3112 | } |
| 3390 | - } elseif ( ! empty( $ping['otto_pixel_uuid'] ) ) { | |
| 3391 | - error_log('MetaSync: ignoring invalid otto_pixel_uuid from connection ping (not a UUID)'); | |
| 3392 | 3113 | } |
| 3393 | 3114 | } else { |
| 3394 | 3115 | // POST succeeded but ping didn't confirm — keep the key, warn user |
| 3395 | 3116 | $api_key_validated = true; |
| @@ -3441,15 +3162,8 @@ | ||
| 3441 | 3162 | $api_key_changed = $old_api_key !== $new_api_key; |
| 3442 | 3163 | $api_key_added = empty($old_api_key) && !empty($new_api_key); |
| 3443 | 3164 | $api_key_removed = !empty($old_api_key) && empty($new_api_key); |
| 3444 | 3165 | |
| 3445 | - if ( $api_key_removed ) { | |
| 3446 | - Metasync_PostHog::feature('searchatlas_disconnected', array( | |
| 3447 | - 'method' => 'key_removed', | |
| 3448 | - 'was_connected' => true, | |
| 3449 | - )); | |
| 3450 | - } | |
| 3451 | - | |
| 3452 | 3166 | if ( $api_key_validated === true ) { |
| 3453 | 3167 | # Use current_time('mysql') so the value is consistent with the |
| 3454 | 3168 | # cron heartbeat writer and renders correctly via time_elapsed_string. |
| 3455 | 3169 | $send_auth_token_timestamp = Metasync::get_option(); |
| @@ -3643,37 +3357,8 @@ | ||
| 3643 | 3357 | update_option('metasync_llms_txt_settings', $this->sanitize_llms_txt_settings($llms_input)); |
| 3644 | 3358 | } |
| 3645 | 3359 | } |
| 3646 | 3360 | |
| 3647 | - // The plugin file already carries the new branding; confirm the | |
| 3648 | - // options write landed the same values. On divergence, roll the file | |
| 3649 | - // back to the captured content so the Plugins screen never shows | |
| 3650 | - // branding the stored options contradict, and report the save as | |
| 3651 | - // rejected rather than a success. | |
| 3652 | - if ($branding_headers_written) { | |
| 3653 | - $stored_branding = Metasync::get_option('general'); | |
| 3654 | - $stored_branding = is_array($stored_branding) ? $stored_branding : array(); | |
| 3655 | - $proposed_branding = $metasync_options['general']; | |
| 3656 | - foreach (Metasync_Activator::BRANDING_HEADER_FIELDS as $branding_key) { | |
| 3657 | - if ((string) ($stored_branding[$branding_key] ?? '') !== (string) ($proposed_branding[$branding_key] ?? '')) { | |
| 3658 | - $restored = Metasync_Activator::restore_plugin_file_content($plugin_header_file, $original_plugin_file_content); | |
| 3659 | - $this->force_release_settings_recovery_lock(); | |
| 3660 | - wp_send_json_error([ | |
| 3661 | - 'errors' => array( | |
| 3662 | - $restored | |
| 3663 | - ? 'White-label branding could not be saved. The previous settings and plugin branding were left unchanged.' | |
| 3664 | - : 'White-label branding could not be saved and the plugin file branding could not be rolled back (metasync.php may be read-only). Re-save once the file is writable so the plugin header matches the stored settings.', | |
| 3665 | - ), | |
| 3666 | - ]); | |
| 3667 | - // The return is unreachable in WordPress (wp_send_json_error | |
| 3668 | - // terminates the request) but keeps the handler safe under the | |
| 3669 | - // non-terminating stubs the unit tests run with. | |
| 3670 | - // @phpstan-ignore-next-line deadCode.unreachable | |
| 3671 | - return; | |
| 3672 | - } | |
| 3673 | - } | |
| 3674 | - } | |
| 3675 | - | |
| 3676 | 3361 | $response_payload = array( |
| 3677 | 3362 | 'message' => 'Settings saved successfully!', |
| 3678 | 3363 | 'redirect_url' => $redirect_url, |
| 3679 | 3364 | ); |
| @@ -4360,35 +4045,8 @@ | ||
| 4360 | 4045 | } |
| 4361 | 4046 | } |
| 4362 | 4047 | |
| 4363 | 4048 | /** |
| 4364 | - * How many indexation rules a SEO Controls save actually changed. | |
| 4365 | - * | |
| 4366 | - * Older saves stored real booleans, this handler stores 'true'/'false' | |
| 4367 | - * strings, so the stored side is normalised before comparing. | |
| 4368 | - * | |
| 4369 | - * @param array $before Stored seo_controls before the save. | |
| 4370 | - * @param array $after seo_controls as just saved ('true'/'false'). | |
| 4371 | - * @param array $fields Field names the save handled. | |
| 4372 | - * @return int | |
| 4373 | - */ | |
| 4374 | - public static function count_index_rule_changes(array $before, array $after, array $fields) | |
| 4375 | - { | |
| 4376 | - $changed = 0; | |
| 4377 | - foreach ($fields as $field) { | |
| 4378 | - if (strpos($field, 'index_') !== 0 && $field !== 'noindex_empty_archives') { | |
| 4379 | - continue; | |
| 4380 | - } | |
| 4381 | - $was = filter_var($before[$field] ?? false, FILTER_VALIDATE_BOOLEAN); | |
| 4382 | - $now = ($after[$field] ?? 'false') === 'true'; | |
| 4383 | - if ($was !== $now) { | |
| 4384 | - $changed++; | |
| 4385 | - } | |
| 4386 | - } | |
| 4387 | - return $changed; | |
| 4388 | - } | |
| 4389 | - | |
| 4390 | - /** | |
| 4391 | 4049 | * AJAX handler for saving SEO / Indexation Control settings. |
| 4392 | 4050 | * |
| 4393 | 4051 | * Moved from Metasync_Admin – the admin class now delegates here. |
| 4394 | 4052 | */ |
| @@ -4463,22 +4121,8 @@ | ||
| 4463 | 4121 | $result = true; |
| 4464 | 4122 | } |
| 4465 | 4123 | |
| 4466 | 4124 | if ($result && $bing_save_result) { |
| 4467 | - // Consent-gated analytics: only when an indexation rule really | |
| 4468 | - // changed. Firing from the options sanitize callback counted | |
| 4469 | - // every unrelated write of metasync_options as a change. | |
| 4470 | - $rules_changed = self::count_index_rule_changes( | |
| 4471 | - $original_options['seo_controls'] ?? array(), | |
| 4472 | - $current_options['seo_controls'], | |
| 4473 | - $seo_control_fields | |
| 4474 | - ); | |
| 4475 | - if ($rules_changed > 0) { | |
| 4476 | - Metasync_PostHog::feature('indexation_rule_changed', array( | |
| 4477 | - 'rules_changed' => $rules_changed, | |
| 4478 | - )); | |
| 4479 | - } | |
| 4480 | - | |
| 4481 | 4125 | $success_message = 'Indexation Control settings saved successfully!'; |
| 4482 | 4126 | if (isset($_POST['metasync_bing_api_key_inline'])) { |
| 4483 | 4127 | $success_message = 'Indexation Control and Bing Instant Indexing settings saved successfully!'; |
| 4484 | 4128 | } |