PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.1
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.1
2.7.1 2.7.2 2.7.0 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 All 141 releases
metasync / includes / class-metasync-feature-flags.php

class-metasync-feature-flags.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.7.1, at includes/class-metasync-feature-flags.php

207 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 /**
4 * Resolves the "Post/Page Editor Settings" disable switches.
5 *
6 * These six settings are presented to the user as "Disable <X> Meta Box". They
7 * hide the editor meta box, and they also switch off the matching MetaSync
8 * front-end behaviour: a disabled feature must not emit its own tags, and must
9 * equally stop suppressing WordPress core or a third-party SEO plugin's tags.
10 * Suppressing without emitting would strip the page of the tag altogether,
11 * which is worse than either extreme.
12 *
13 * Nothing here deletes stored configuration. A disabled feature keeps its saved
14 * post meta and options untouched, so re-enabling it restores the previous
15 * behaviour exactly.
16 *
17 * @link https://searchatlas.com
18 * @since 2.7.0
19 * @package Metasync
20 * @subpackage Metasync/includes
21 * @author Engineering Team <[email protected]>
22 */
23
24 // Abort if this file is accessed directly.
25 if (!defined('ABSPATH')) {
26 exit;
27 }
28
29 class Metasync_Feature_Flags
30 {
31 /**
32 * Feature identifiers. Callers pass these rather than raw option keys so the
33 * stored key names stay an implementation detail of this class.
34 */
35 const COMMON_ROBOTS = 'common_robots';
36 const ADVANCE_ROBOTS = 'advance_robots';
37 const REDIRECTION = 'redirection';
38 const CANONICAL = 'canonical';
39 const SOCIAL_OG = 'social_opengraph';
40 const SCHEMA = 'schema_markup';
41 const LANGUAGE_ALTERNATES = 'language_alternates';
42
43 /**
44 * Maps each feature to the key it is stored under in the `general` option
45 * group. The option name still carries the historical "_metabox" suffix
46 * because the checkbox started life as an editor-only toggle.
47 *
48 * @var array<string, string>
49 */
50 private static $option_keys = [
51 self::COMMON_ROBOTS => 'disable_common_robots_metabox',
52 self::ADVANCE_ROBOTS => 'disable_advance_robots_metabox',
53 self::REDIRECTION => 'disable_redirection_metabox',
54 self::CANONICAL => 'disable_canonical_metabox',
55 self::SOCIAL_OG => 'disable_social_opengraph_metabox',
56 self::SCHEMA => 'disable_schema_markup_metabox',
57 self::LANGUAGE_ALTERNATES => 'disable_language_alternates_metabox',
58 ];
59
60 /**
61 * Per-request memo of resolved values, keyed by feature.
62 *
63 * The OTTO output buffer re-reads these while walking the DOM, so the same
64 * flag can be consulted dozens of times in one render.
65 *
66 * Keyed "<blog id>:<feature>" so a switch_to_blog() loop cannot answer for
67 * the wrong site.
68 *
69 * @var array<string, bool>
70 */
71 private static $memo = [];
72
73 /**
74 * True when the feature has been switched off by the user.
75 *
76 * An unknown feature name is treated as enabled: a typo must never silently
77 * disable SEO output.
78 *
79 * @param string $feature One of the class constants.
80 * @return bool
81 */
82 public static function is_disabled($feature)
83 {
84 if (!isset(self::$option_keys[$feature])) {
85 return false;
86 }
87
88 $key = self::memo_key($feature);
89 if (!isset(self::$memo[$key])) {
90 self::$memo[$key] = self::read($feature);
91 }
92
93 return self::$memo[$key];
94 }
95
96 /**
97 * Memo key for the current site.
98 *
99 * On multisite a switch_to_blog() loop reads a different site's options
100 * through the same static, so the blog id has to be part of the key or one
101 * site's settings would answer for another's.
102 *
103 * @param string $feature One of the class constants.
104 * @return string
105 */
106 private static function memo_key($feature)
107 {
108 $blog_id = function_exists('get_current_blog_id') ? (int) get_current_blog_id() : 0;
109
110 return $blog_id . ':' . $feature;
111 }
112
113 /**
114 * Convenience inverse of is_disabled().
115 *
116 * @param string $feature One of the class constants.
117 * @return bool
118 */
119 public static function is_enabled($feature)
120 {
121 return !self::is_disabled($feature);
122 }
123
124 /**
125 * True when both robots features are off, i.e. MetaSync should leave the
126 * robots meta tag entirely alone — including core's own `wp_robots` output.
127 *
128 * Common and advanced directives share a single `<meta name="robots">` tag,
129 * so the tag is only surrendered when neither half wants to write to it.
130 *
131 * @return bool
132 */
133 public static function robots_fully_disabled()
134 {
135 return self::is_disabled(self::COMMON_ROBOTS)
136 && self::is_disabled(self::ADVANCE_ROBOTS);
137 }
138
139 /**
140 * Reads the raw stored value for a feature.
141 *
142 * The class_exists() guard matters because this file is required directly
143 * from the plugin bootstrap, which runs before the main Metasync class is
144 * autoloaded; treating that window as "enabled" preserves current output.
145 *
146 * @param string $feature One of the class constants.
147 * @return bool
148 */
149 private static function read($feature)
150 {
151 if (!class_exists('Metasync')) {
152 return false;
153 }
154
155 $general = Metasync::get_option('general', []);
156 if (!is_array($general)) {
157 return false;
158 }
159
160 return !empty($general[self::$option_keys[$feature]]);
161 }
162
163 /**
164 * Clears the per-request memo.
165 *
166 * Settings saves and the test suite both change the underlying option
167 * mid-request, after a flag may already have been read.
168 *
169 * @return void
170 */
171 public static function reset_cache()
172 {
173 self::$memo = [];
174 }
175
176 /**
177 * Drops the memo whenever the settings option is written.
178 *
179 * Hooking the generic option actions covers every save path — the Settings
180 * API, the admin AJAX handler, REST and any direct update_option() call —
181 * rather than relying on each one to remember to invalidate.
182 *
183 * @return void
184 */
185 public static function register_invalidation()
186 {
187 if (!function_exists('add_action')) {
188 return;
189 }
190
191 $option = class_exists('Metasync') ? Metasync::option_name : 'metasync_options';
192
193 // updated_option only fires when the stored value actually changed, so
194 // a no-op save costs nothing.
195 add_action('updated_option', static function ($name) use ($option) {
196 if ($name === $option) {
197 self::reset_cache();
198 }
199 });
200 add_action('added_option', static function ($name) use ($option) {
201 if ($name === $option) {
202 self::reset_cache();
203 }
204 });
205 }
206 }
207