PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / trunk
Search Atlas SEO – OTTO AI SEO Automation for WordPress vtrunk
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 / class-metasync-feature-flags.php

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

205 lines 5.8 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 <support@searchatlas.com>
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
42 /**
43 * Maps each feature to the key it is stored under in the `general` option
44 * group. The option name still carries the historical "_metabox" suffix
45 * because the checkbox started life as an editor-only toggle.
46 *
47 * @var array<string, string>
48 */
49 private static $option_keys = [
50 self::COMMON_ROBOTS => 'disable_common_robots_metabox',
51 self::ADVANCE_ROBOTS => 'disable_advance_robots_metabox',
52 self::REDIRECTION => 'disable_redirection_metabox',
53 self::CANONICAL => 'disable_canonical_metabox',
54 self::SOCIAL_OG => 'disable_social_opengraph_metabox',
55 self::SCHEMA => 'disable_schema_markup_metabox',
56 ];
57
58 /**
59 * Per-request memo of resolved values, keyed by feature.
60 *
61 * The OTTO output buffer re-reads these while walking the DOM, so the same
62 * flag can be consulted dozens of times in one render.
63 *
64 * Keyed "<blog id>:<feature>" so a switch_to_blog() loop cannot answer for
65 * the wrong site.
66 *
67 * @var array<string, bool>
68 */
69 private static $memo = [];
70
71 /**
72 * True when the feature has been switched off by the user.
73 *
74 * An unknown feature name is treated as enabled: a typo must never silently
75 * disable SEO output.
76 *
77 * @param string $feature One of the class constants.
78 * @return bool
79 */
80 public static function is_disabled($feature)
81 {
82 if (!isset(self::$option_keys[$feature])) {
83 return false;
84 }
85
86 $key = self::memo_key($feature);
87 if (!isset(self::$memo[$key])) {
88 self::$memo[$key] = self::read($feature);
89 }
90
91 return self::$memo[$key];
92 }
93
94 /**
95 * Memo key for the current site.
96 *
97 * On multisite a switch_to_blog() loop reads a different site's options
98 * through the same static, so the blog id has to be part of the key or one
99 * site's settings would answer for another's.
100 *
101 * @param string $feature One of the class constants.
102 * @return string
103 */
104 private static function memo_key($feature)
105 {
106 $blog_id = function_exists('get_current_blog_id') ? (int) get_current_blog_id() : 0;
107
108 return $blog_id . ':' . $feature;
109 }
110
111 /**
112 * Convenience inverse of is_disabled().
113 *
114 * @param string $feature One of the class constants.
115 * @return bool
116 */
117 public static function is_enabled($feature)
118 {
119 return !self::is_disabled($feature);
120 }
121
122 /**
123 * True when both robots features are off, i.e. MetaSync should leave the
124 * robots meta tag entirely alone — including core's own `wp_robots` output.
125 *
126 * Common and advanced directives share a single `<meta name="robots">` tag,
127 * so the tag is only surrendered when neither half wants to write to it.
128 *
129 * @return bool
130 */
131 public static function robots_fully_disabled()
132 {
133 return self::is_disabled(self::COMMON_ROBOTS)
134 && self::is_disabled(self::ADVANCE_ROBOTS);
135 }
136
137 /**
138 * Reads the raw stored value for a feature.
139 *
140 * The class_exists() guard matters because this file is required directly
141 * from the plugin bootstrap, which runs before the main Metasync class is
142 * autoloaded; treating that window as "enabled" preserves current output.
143 *
144 * @param string $feature One of the class constants.
145 * @return bool
146 */
147 private static function read($feature)
148 {
149 if (!class_exists('Metasync')) {
150 return false;
151 }
152
153 $general = Metasync::get_option('general', []);
154 if (!is_array($general)) {
155 return false;
156 }
157
158 return !empty($general[self::$option_keys[$feature]]);
159 }
160
161 /**
162 * Clears the per-request memo.
163 *
164 * Settings saves and the test suite both change the underlying option
165 * mid-request, after a flag may already have been read.
166 *
167 * @return void
168 */
169 public static function reset_cache()
170 {
171 self::$memo = [];
172 }
173
174 /**
175 * Drops the memo whenever the settings option is written.
176 *
177 * Hooking the generic option actions covers every save path — the Settings
178 * API, the admin AJAX handler, REST and any direct update_option() call —
179 * rather than relying on each one to remember to invalidate.
180 *
181 * @return void
182 */
183 public static function register_invalidation()
184 {
185 if (!function_exists('add_action')) {
186 return;
187 }
188
189 $option = class_exists('Metasync') ? Metasync::option_name : 'metasync_options';
190
191 // updated_option only fires when the stored value actually changed, so
192 // a no-op save costs nothing.
193 add_action('updated_option', static function ($name) use ($option) {
194 if ($name === $option) {
195 self::reset_cache();
196 }
197 });
198 add_action('added_option', static function ($name) use ($option) {
199 if ($name === $option) {
200 self::reset_cache();
201 }
202 });
203 }
204 }
205