PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.2
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.2
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 / admin / class-metasync-seo-sync-settings.php

class-metasync-seo-sync-settings.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.7.2, at admin/class-metasync-seo-sync-settings.php

660 lines 31.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // If this file is called directly, abort.
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * "Sync to Other SEO Plugins" settings section.
9 *
10 * Renders the consent switch that decides whether MetaSync may copy its values
11 * into Yoast / Rank Math / AIOSEO storage, plus a plain-language status line
12 * saying what is being written and how many originals are on file.
13 *
14 * This lives in Settings → General rather than Advanced Settings on purpose.
15 * Advanced is a troubleshooting page and can be hidden three separate ways —
16 * the access-control feature flag, the whitelabel hide_advanced flag, and the
17 * password lock. A switch that rewrites another plugin's data must never be
18 * concealable from the site owner, which is the exact problem this section
19 * exists to solve.
20 *
21 * @package Metasync
22 * @subpackage Metasync/admin
23 */
24 class Metasync_Seo_Sync_Settings {
25
26 /**
27 * SEO plugins whose storage MetaSync writes into, matched the same way the
28 * writers match them so the status line can never disagree with reality.
29 *
30 * @var array<string,array<int,string>>
31 */
32 const DETECTED_PLUGINS = array(
33 'Rank Math' => array('seo-by-rank-math/rank-math.php', 'seo-by-rankmath/rank-math.php'),
34 'Yoast SEO' => array('wordpress-seo/wp-seo.php', 'wordpress-seo-premium/wp-seo-premium.php'),
35 'All in One SEO' => array('all-in-one-seo-pack/all_in_one_seo_pack.php', 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php'),
36 );
37
38 /**
39 * Whether the section should appear at all.
40 *
41 * Shown when an SEO plugin is active, OR the switch is on, OR originals are
42 * already on file. The last two conditions matter: without them the section
43 * — and with it the only view onto the saved originals — would vanish the
44 * day someone deactivated Rank Math.
45 *
46 * @return bool
47 */
48 public static function should_display() {
49 if (!empty(self::get_active_plugin_names())) {
50 return true;
51 }
52
53 if (class_exists('Metasync_Seo_Backup') && Metasync_Seo_Backup::is_enabled()) {
54 return true;
55 }
56
57 $counts = self::get_backup_counts();
58
59 return ($counts['posts'] + $counts['terms']) > 0;
60 }
61
62 /**
63 * Render the section body.
64 */
65 public static function render() {
66 $option_key = Metasync_Admin::option_key;
67 $field_name = $option_key . '[general][' . Metasync_Seo_Backup::CONSENT_OPTION_KEY . ']';
68 $field_id = 'metasync_' . Metasync_Seo_Backup::CONSENT_OPTION_KEY;
69 $enabled = class_exists('Metasync_Seo_Backup') && Metasync_Seo_Backup::is_enabled();
70 $product = Metasync::get_effective_plugin_name();
71 $otto = Metasync::get_whitelabel_otto_name();
72 $active = self::get_active_plugin_names();
73 $counts = self::get_backup_counts();
74 $total_saved = $counts['posts'] + $counts['terms'];
75 $batch = class_exists('Metasync_Seo_Restore') ? Metasync_Seo_Restore::get_progress() : ['status' => 'idle', 'total' => 0, 'processed' => 0, 'failed' => 0, 'fields_restored' => 0];
76 $is_running = ($batch['status'] === 'running');
77 ?>
78 <style>
79 .metasync-seo-sync { background: var(--dashboard-card-bg); padding: 20px; border-radius: 8px; }
80 .metasync-seo-sync p { margin: 0; }
81 .metasync-seo-sync-intro { color: var(--dashboard-text-secondary); margin: 0 0 18px 0 !important; line-height: 1.6; }
82
83 /* Consent row — the switch is the one control in this section, so it is
84 given a card of its own rather than sitting in the body text. */
85 .metasync-seo-sync-consent {
86 display: flex; align-items: flex-start; gap: 14px;
87 padding: 16px; border-radius: 8px; cursor: pointer;
88 background: var(--dashboard-input-bg);
89 border: 1px solid var(--dashboard-border);
90 transition: border-color .15s ease, background .15s ease;
91 }
92 .metasync-seo-sync-consent:hover { border-color: var(--dashboard-accent); }
93 .metasync-seo-sync-consent.is-on { border-color: var(--dashboard-accent); }
94
95 .metasync-seo-sync-switch { position: relative; flex: 0 0 auto; width: 40px; height: 22px; margin-top: 1px; }
96 .metasync-seo-sync-switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; z-index: 2; }
97 .metasync-seo-sync-slider {
98 position: absolute; inset: 0; border-radius: 999px;
99 background: var(--dashboard-border); transition: background .2s ease; pointer-events: none;
100 }
101 .metasync-seo-sync-slider::before {
102 content: ''; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px;
103 border-radius: 50%; background: #fff; transition: transform .2s ease;
104 box-shadow: 0 1px 2px rgba(0,0,0,.35);
105 }
106 .metasync-seo-sync-switch input:checked + .metasync-seo-sync-slider { background: var(--dashboard-accent); }
107 .metasync-seo-sync-switch input:checked + .metasync-seo-sync-slider::before { transform: translateX(18px); }
108 .metasync-seo-sync-switch input:focus-visible + .metasync-seo-sync-slider { box-shadow: 0 0 0 2px var(--dashboard-accent); }
109
110 .metasync-seo-sync-consent-label { color: var(--dashboard-text-primary); font-weight: 600; display: block; }
111 .metasync-seo-sync-consent-state { color: var(--dashboard-text-secondary); font-size: 13px; display: block; margin-top: 4px; }
112
113 /* Facts panel: what is being written, and what can be undone. */
114 .metasync-seo-sync-facts {
115 display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; margin-top: 16px;
116 }
117 .metasync-seo-sync-fact {
118 padding: 12px 14px; border-radius: 8px;
119 background: var(--dashboard-input-bg); border: 1px solid var(--dashboard-border);
120 }
121 .metasync-seo-sync-fact-label {
122 display: block; font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
123 color: var(--dashboard-text-secondary); margin-bottom: 8px;
124 }
125 .metasync-seo-sync-fact-value { color: var(--dashboard-text-primary); font-size: 14px; font-weight: 600; }
126 .metasync-seo-sync-fact-value.is-muted { color: var(--dashboard-text-secondary); font-weight: 500; }
127 .metasync-seo-sync-pill {
128 display: inline-block; margin: 0 6px 6px 0; padding: 3px 10px; border-radius: 999px;
129 font-size: 12px; font-weight: 600; color: var(--dashboard-text-primary);
130 background: rgba(59, 130, 246, .16); border: 1px solid rgba(59, 130, 246, .38);
131 }
132 .metasync-seo-sync-count { font-size: 18px; font-weight: 700; color: var(--dashboard-text-primary); }
133 .metasync-seo-sync-count-unit { font-size: 13px; font-weight: 500; color: var(--dashboard-text-secondary); }
134
135 .metasync-seo-sync-restore { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--dashboard-border); }
136 .metasync-seo-sync-restore-hint { color: var(--dashboard-text-secondary); font-size: 13px; margin-top: 8px !important; line-height: 1.5; }
137
138 /* The restore button inherits the core .button styling, which is close
139 to invisible against the dark dashboard card. Restating it here keeps
140 it legible in both themes without touching the shared button rules. */
141 #metasync-seo-restore-btn.button {
142 background: transparent;
143 color: var(--dashboard-text-primary);
144 border: 1px solid var(--dashboard-accent);
145 border-radius: 6px;
146 padding: 6px 16px;
147 height: auto; line-height: 1.8; font-weight: 600;
148 }
149 #metasync-seo-restore-btn.button:hover:not(:disabled),
150 #metasync-seo-restore-btn.button:focus:not(:disabled) {
151 background: var(--dashboard-accent);
152 border-color: var(--dashboard-accent);
153 color: #fff;
154 }
155 #metasync-seo-restore-btn.button:disabled { opacity: .55; }
156
157 .metasync-seo-sync-progress {
158 margin-top: 16px; padding: 14px 16px; border-radius: 8px;
159 background: var(--dashboard-input-bg); border: 1px solid var(--dashboard-border);
160 border-left: 3px solid var(--dashboard-accent);
161 }
162 .metasync-seo-sync-progress-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 10px; }
163 .metasync-seo-sync-progress-track { width: 100%; height: 6px; background: var(--dashboard-border); border-radius: 3px; overflow: hidden; margin-bottom: 8px; }
164 .metasync-seo-sync-progress-fill { height: 100%; background: var(--dashboard-gradient-primary); border-radius: 3px; transition: width .25s ease; }
165 .metasync-seo-sync-progress-meta { font-size: 12px; color: var(--dashboard-text-secondary); }
166
167 .metasync-seo-sync-complete {
168 margin-top: 16px; padding: 12px 16px; border-radius: 8px; font-size: 13px;
169 color: var(--dashboard-text-primary);
170 background: rgba(16, 185, 129, .14); border-left: 3px solid var(--dashboard-success);
171 }
172 </style>
173
174 <div class="metasync-seo-sync">
175 <p class="metasync-seo-sync-intro">
176 <?php
177 printf(
178 /* translators: 1: product name, 2: OTTO product name. */
179 esc_html__('%1$s can copy the SEO values %2$s generates into whichever SEO plugin your site uses, so that plugin renders them. This overwrites what those fields hold today, so it happens only while you allow it.', 'metasync'),
180 esc_html($product),
181 esc_html($otto)
182 );
183 ?>
184 </p>
185
186 <label for="<?php echo esc_attr($field_id); ?>"
187 class="metasync-seo-sync-consent<?php echo $enabled ? ' is-on' : ''; ?>"
188 id="metasync-seo-sync-consent">
189 <span class="metasync-seo-sync-switch">
190 <input type="checkbox"
191 id="<?php echo esc_attr($field_id); ?>"
192 name="<?php echo esc_attr($field_name); ?>"
193 value="true"
194 <?php checked($enabled); ?> />
195 <span class="metasync-seo-sync-slider"></span>
196 </span>
197 <span>
198 <span class="metasync-seo-sync-consent-label">
199 <?php
200 printf(
201 /* translators: %s: OTTO product name. */
202 esc_html__('Sync the %s-suggested SEO values into other plugins', 'metasync'),
203 esc_html($otto)
204 );
205 ?>
206 </span>
207 <span class="metasync-seo-sync-consent-state">
208 <?php echo esc_html(self::get_intro_line($product, $enabled)); ?>
209 </span>
210 </span>
211 </label>
212
213 <div class="metasync-seo-sync-facts">
214 <div class="metasync-seo-sync-fact">
215 <span class="metasync-seo-sync-fact-label"><?php esc_html_e('Currently writing to', 'metasync'); ?></span>
216 <?php if (empty($active)): ?>
217 <span class="metasync-seo-sync-fact-value is-muted">
218 <?php esc_html_e('No supported SEO plugin detected', 'metasync'); ?>
219 </span>
220 <?php else: ?>
221 <?php foreach ($active as $plugin_name): ?>
222 <span class="metasync-seo-sync-pill"><?php echo esc_html($plugin_name); ?></span>
223 <?php endforeach; ?>
224 <?php endif; ?>
225 </div>
226
227 <div class="metasync-seo-sync-fact">
228 <span class="metasync-seo-sync-fact-label"><?php esc_html_e('Originals saved for restore', 'metasync'); ?></span>
229 <span class="metasync-seo-sync-fact-value">
230 <span class="metasync-seo-sync-count"><?php echo esc_html((string) $counts['posts']); ?></span>
231 <span class="metasync-seo-sync-count-unit"><?php esc_html_e('posts', 'metasync'); ?></span>
232 &nbsp;
233 <span class="metasync-seo-sync-count"><?php echo esc_html((string) $counts['terms']); ?></span>
234 <span class="metasync-seo-sync-count-unit"><?php esc_html_e('terms', 'metasync'); ?></span>
235 </span>
236 </div>
237 </div>
238
239 <!-- Restore Button & Action Area -->
240 <div class="metasync-seo-sync-restore">
241 <?php if ($total_saved > 0 || $is_running): ?>
242 <button type="button"
243 id="metasync-seo-restore-btn"
244 class="button button-secondary"
245 <?php disabled($is_running); ?>
246 style="cursor: pointer;">
247 <?php esc_html_e('Restore Original SEO Values', 'metasync'); ?>
248 </button>
249 <p class="metasync-seo-sync-restore-hint">
250 <?php
251 printf(
252 /* translators: %d: number of objects with saved originals. */
253 esc_html(_n(
254 'Puts the saved original value back on %d object and turns syncing off.',
255 'Puts the saved original values back on all %d objects and turns syncing off.',
256 $total_saved,
257 'metasync'
258 )),
259 (int) $total_saved
260 );
261 ?>
262 </p>
263 <?php else: ?>
264 <p class="metasync-seo-sync-restore-hint" style="margin-top: 0 !important;">
265 <?php esc_html_e('Nothing to restore yet. Originals are saved the first time a value is overwritten, and the restore button appears here once there is something to put back.', 'metasync'); ?>
266 </p>
267 <?php endif; ?>
268 </div>
269
270 <!-- Restore Batch Progress Card -->
271 <div id="metasync-seo-restore-progress"
272 class="metasync-seo-sync-progress"
273 <?php echo $is_running ? '' : 'style="display:none;"'; ?>>
274 <div class="metasync-seo-sync-progress-head">
275 <span id="metasync-seo-restore-text" style="color: var(--dashboard-text-primary); font-size: 13px; font-weight: 600;">
276 <?php
277 if ($is_running) {
278 printf(
279 /* translators: 1: number of items already restored, 2: total number of items. */
280 esc_html__('Restoring %1$d of %2$d items...', 'metasync'),
281 (int) $batch['processed'],
282 (int) $batch['total']
283 );
284 }
285 ?>
286 </span>
287 <button type="button" class="button button-small" id="metasync-seo-restore-cancel-btn">
288 <?php esc_html_e('Cancel', 'metasync'); ?>
289 </button>
290 </div>
291 <div class="metasync-seo-sync-progress-track">
292 <div id="metasync-seo-restore-fill"
293 class="metasync-seo-sync-progress-fill"
294 style="width: <?php
295 echo ($batch['total'] > 0) ? esc_attr((string) round(($batch['processed'] / $batch['total']) * 100)) : '0';
296 ?>%;"></div>
297 </div>
298 <div class="metasync-seo-sync-progress-meta">
299 <span id="metasync-seo-restore-processed"><?php echo esc_html($batch['processed']); ?></span> /
300 <span id="metasync-seo-restore-total"><?php echo esc_html($batch['total']); ?></span>
301 <?php esc_html_e('items processed', 'metasync'); ?>
302 </div>
303 </div>
304
305 <!-- Restore Batch Complete Notice -->
306 <div id="metasync-seo-restore-complete" class="metasync-seo-sync-complete" style="display:none;">
307 <span id="metasync-seo-restore-complete-text"></span>
308 </div>
309 </div>
310
311 <script type="text/javascript">
312 (function () {
313 var toggle = document.getElementById('<?php echo esc_js($field_id); ?>');
314 var consentRow = document.getElementById('metasync-seo-sync-consent');
315 var restoreBtn = document.getElementById('metasync-seo-restore-btn');
316 var cancelBtn = document.getElementById('metasync-seo-restore-cancel-btn');
317 var progressCard = document.getElementById('metasync-seo-restore-progress');
318 var completeCard = document.getElementById('metasync-seo-restore-complete');
319 var progressText = document.getElementById('metasync-seo-restore-text');
320 var progressFill = document.getElementById('metasync-seo-restore-fill');
321 var processedSpan = document.getElementById('metasync-seo-restore-processed');
322 var totalSpan = document.getElementById('metasync-seo-restore-total');
323 var completeText = document.getElementById('metasync-seo-restore-complete-text');
324
325 var ajaxUrl = <?php echo wp_json_encode(admin_url('admin-ajax.php')); ?>;
326 var nonce = <?php echo wp_json_encode(wp_create_nonce('metasync_seo_restore_nonce')); ?>;
327 var BATCH_MAX_ERRORS = 5;
328 var batchActive = <?php echo $is_running ? 'true' : 'false'; ?>;
329 var batchErrorCount = 0;
330 var fallbackPoll = null;
331
332 if (toggle) {
333 // The input sits inside its label for the larger click target.
334 // Stop the input click from bubbling back to the label, which
335 // would activate the checkbox a second time in some browsers.
336 toggle.addEventListener('click', function (event) {
337 event.stopPropagation();
338 });
339
340 // Turning this on starts permanent writes into another plugin's
341 // data, so it is confirmed before the form is even submitted.
342 // Turning it off needs no confirmation: it only stops future writes.
343 toggle.addEventListener('change', function () {
344 if (toggle.checked && !window.confirm(<?php echo wp_json_encode(self::get_confirmation_text($product, $otto)); ?>)) {
345 toggle.checked = false;
346 }
347
348 if (consentRow) {
349 consentRow.classList.toggle('is-on', toggle.checked);
350 }
351 });
352 }
353
354 if (restoreBtn) {
355 restoreBtn.addEventListener('click', function () {
356 var confirmMsg = <?php echo wp_json_encode(
357 sprintf(
358 /* translators: %d: number of objects that will be restored. */
359 __('This will restore the original SEO values on all %d objects and disable syncing to other plugins. Are you sure you want to proceed?', 'metasync'),
360 $total_saved
361 )
362 ); ?>;
363
364 if (!window.confirm(confirmMsg)) {
365 return;
366 }
367
368 restoreBtn.disabled = true;
369 if (toggle) {
370 toggle.checked = false;
371 }
372 if (consentRow) {
373 consentRow.classList.remove('is-on');
374 }
375
376 var params = new URLSearchParams();
377 params.append('action', 'metasync_seo_restore_start');
378 params.append('nonce', nonce);
379
380 fetch(ajaxUrl, {
381 method: 'POST',
382 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
383 body: params.toString()
384 })
385 .then(function (r) { return r.json(); })
386 .then(function (resp) {
387 if (!resp.success) {
388 alert(resp.data || <?php echo wp_json_encode(__('Failed to start restoration.', 'metasync')); ?>);
389 restoreBtn.disabled = false;
390 return;
391 }
392 if (resp.data && resp.data.error) {
393 // e.g. consent could not be disabled — server refused to start.
394 alert(resp.data.error);
395 restoreBtn.disabled = false;
396 return;
397 }
398 batchActive = true;
399 batchErrorCount = 0;
400 updateProgressUI(resp.data);
401 startFallbackPoll();
402 processNextTick();
403 })
404 .catch(function () {
405 alert(<?php echo wp_json_encode(__('Network error starting restoration.', 'metasync')); ?>);
406 restoreBtn.disabled = false;
407 });
408 });
409 }
410
411 if (cancelBtn) {
412 cancelBtn.addEventListener('click', function () {
413 cancelBtn.disabled = true;
414 var params = new URLSearchParams();
415 params.append('action', 'metasync_seo_restore_cancel');
416 params.append('nonce', nonce);
417
418 fetch(ajaxUrl, {
419 method: 'POST',
420 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
421 body: params.toString()
422 })
423 .then(function () {
424 batchActive = false;
425 stopFallbackPoll();
426 if (progressCard) progressCard.style.display = 'none';
427 if (restoreBtn) restoreBtn.disabled = false;
428 cancelBtn.disabled = false;
429 location.reload();
430 });
431 });
432 }
433
434 function updateProgressUI(progress) {
435 if (!progressCard) return;
436 progressCard.style.display = 'block';
437 if (completeCard) completeCard.style.display = 'none';
438
439 var total = progress.total || 0;
440 var processed = progress.processed || 0;
441 var pct = total > 0 ? Math.round((processed / total) * 100) : 0;
442
443 if (progressFill) progressFill.style.width = pct + '%';
444 if (processedSpan) processedSpan.textContent = processed;
445 if (totalSpan) totalSpan.textContent = total;
446 if (progressText) {
447 progressText.textContent = <?php echo wp_json_encode(__('Restoring', 'metasync')); ?> + ' ' + processed + ' / ' + total + ' (' + pct + '%)';
448 }
449 }
450
451 function processNextTick() {
452 if (!batchActive) return;
453
454 var params = new URLSearchParams();
455 params.append('action', 'metasync_seo_restore_process_tick');
456 params.append('nonce', nonce);
457
458 fetch(ajaxUrl, {
459 method: 'POST',
460 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
461 body: params.toString()
462 })
463 .then(function (r) { return r.json(); })
464 .then(function (resp) {
465 if (!resp.success) {
466 batchErrorCount++;
467 if (batchErrorCount >= BATCH_MAX_ERRORS) {
468 onBatchError();
469 return;
470 }
471 setTimeout(processNextTick, 3000);
472 return;
473 }
474
475 batchErrorCount = 0;
476 var data = resp.data;
477 updateProgressUI(data);
478
479 if (data.status === 'completed' || data.status === 'cancelled' || data.processed >= data.total) {
480 onBatchFinished(data);
481 } else {
482 setTimeout(processNextTick, 300);
483 }
484 })
485 .catch(function () {
486 if (!batchActive) return;
487 batchErrorCount++;
488 if (batchErrorCount >= BATCH_MAX_ERRORS) {
489 onBatchError();
490 return;
491 }
492 setTimeout(processNextTick, 5000);
493 });
494 }
495
496 function onBatchError() {
497 batchActive = false;
498 stopFallbackPoll();
499 if (progressCard) progressCard.style.display = 'none';
500 if (restoreBtn) restoreBtn.disabled = false;
501 alert(<?php echo wp_json_encode(__('Restoration paused after repeated errors. Please reload the page to check status or resume.', 'metasync')); ?>);
502 }
503
504 function onBatchFinished(progress) {
505 batchActive = false;
506 stopFallbackPoll();
507 if (progressCard) progressCard.style.display = 'none';
508
509 if (progress.error) {
510 // e.g. consent could not be kept off, or could not be
511 // switched back on after an empty run.
512 alert(progress.error);
513 }
514
515 if (progress.status === 'completed') {
516 if (completeCard && completeText) {
517 completeText.textContent = <?php echo wp_json_encode(__('Restoration complete!', 'metasync')); ?> + ' ' +
518 progress.processed + ' ' + <?php echo wp_json_encode(__('objects restored.', 'metasync')); ?> +
519 (progress.failed > 0 ? ' (' + progress.failed + ' failed)' : '');
520 completeCard.style.display = 'block';
521 }
522 }
523
524 if (restoreBtn) restoreBtn.disabled = false;
525 setTimeout(function () { location.reload(); }, 2000);
526 }
527
528 function startFallbackPoll() {
529 if (fallbackPoll) return;
530 fallbackPoll = setInterval(function () {
531 var params = new URLSearchParams();
532 params.append('action', 'metasync_seo_restore_progress');
533 params.append('nonce', nonce);
534
535 fetch(ajaxUrl, {
536 method: 'POST',
537 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
538 body: params.toString()
539 })
540 .then(function (r) { return r.json(); })
541 .then(function (resp) {
542 if (!resp.success) return;
543 updateProgressUI(resp.data);
544 if (resp.data.status !== 'running') {
545 onBatchFinished(resp.data);
546 }
547 });
548 }, 10000);
549 }
550
551 function stopFallbackPoll() {
552 if (fallbackPoll) {
553 clearInterval(fallbackPoll);
554 fallbackPoll = null;
555 }
556 }
557
558 if (batchActive) {
559 startFallbackPoll();
560 processNextTick();
561 }
562 })();
563 </script>
564 <?php
565 }
566
567 /**
568 * One-line statement of the current consent state, shown under the switch.
569 *
570 * @param string $product Effective (whitelabel-aware) product name.
571 * @param bool $enabled Whether the switch is on.
572 * @return string
573 */
574 private static function get_intro_line($product, $enabled) {
575 if ($enabled) {
576 /* translators: %s: product name. */
577 return sprintf(__('%s is allowed to write into other SEO plugins.', 'metasync'), $product);
578 }
579
580 /* translators: %s: product name. */
581 return sprintf(__('%s is not writing into other SEO plugins.', 'metasync'), $product);
582 }
583
584 /**
585 * Text of the confirmation shown when the switch is turned on.
586 *
587 * Names the fields that are actually covered. Image alt text, heading
588 * rewrites and link corrections are deliberately absent: they are edits to
589 * WordPress's own attachment meta and to post content, never writes into
590 * another SEO plugin, so this switch does not govern them and the copy must
591 * not suggest that it does.
592 *
593 * @param string $product Effective (whitelabel-aware) product name.
594 * @param string $otto Effective (whitelabel-aware) OTTO name.
595 * @return string
596 */
597 private static function get_confirmation_text($product, $otto) {
598 return sprintf(
599 /* translators: 1: product name, 2: OTTO product name. */
600 __(
601 "%1\$s will write the SEO title, meta description, focus keyword, Open Graph and Twitter values, canonical URL and schema %2\$s suggests into your active SEO plugin's fields, replacing what is there now.
602
603 The value each field held before is saved first, so it can be restored.
604
605 Turning this off later stops further writes. It does not undo writes already made — restoring the saved originals does that.
606
607 This does not cover image alt text, heading rewrites or link corrections. Those change WordPress content directly and are controlled separately.",
608 'metasync'
609 ),
610 $product,
611 $otto
612 );
613 }
614
615 /**
616 * Names of the SEO plugins currently active.
617 *
618 * @return array<int,string>
619 */
620 private static function get_active_plugin_names() {
621 if (!function_exists('is_plugin_active')) {
622 require_once ABSPATH . 'wp-admin/includes/plugin.php';
623 }
624
625 $names = array();
626
627 foreach (self::DETECTED_PLUGINS as $name => $files) {
628 foreach ($files as $file) {
629 if (is_plugin_active($file)) {
630 $names[] = $name;
631 break;
632 }
633 }
634 }
635
636 return $names;
637 }
638
639 /**
640 * How many posts and terms hold saved originals.
641 *
642 * @return array{posts:int,terms:int}
643 */
644 private static function get_backup_counts() {
645 if (!class_exists('Metasync_Seo_Backup')) {
646 return array('posts' => 0, 'terms' => 0);
647 }
648
649 // Memoised for the request: should_display() and render() both need it,
650 // and each call is two indexed COUNT queries we have no reason to repeat.
651 static $counts = null;
652
653 if ($counts === null) {
654 $counts = Metasync_Seo_Backup::count_objects_with_backups();
655 }
656
657 return $counts;
658 }
659 }
660