PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.23
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.23
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-settings-fields.php

class-metasync-settings-fields.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.23, at includes/class-metasync-settings-fields.php

2,445 lines 132.3 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 * Settings field callbacks and accordion/section helpers extracted from Metasync_Admin.
9 *
10 * @package Metasync
11 * @subpackage Metasync/includes
12 */
13 class Metasync_Settings_Fields {
14
15 private static $instance = null;
16
17 /**
18 * Reference to the admin instance for callbacks to non-extracted admin methods.
19 *
20 * @var Metasync_Admin|null
21 */
22 private $admin_instance = null;
23
24 private function __construct() {}
25
26 /**
27 * @return self
28 */
29 public static function instance() {
30 if (null === self::$instance) {
31 self::$instance = new self();
32 }
33 return self::$instance;
34 }
35
36 /**
37 * Inject the admin instance so callbacks to non-extracted methods still work.
38 *
39 * @param Metasync_Admin $admin
40 */
41 public function set_admin_instance($admin) {
42 $this->admin_instance = $admin;
43 }
44
45 // ────────────────────────────────────────────────────────────────
46 // Accordion / Section helpers
47 // ────────────────────────────────────────────────────────────────
48
49 public function get_accordion_sections_config() {
50 return array(
51 'connection' => array(
52 'title' => 'Connection & Authentication',
53 'description' => 'Manage your API connection and authentication settings',
54 'icon' => 'admin-network',
55 'priority' => 10,
56 'default_open' => true,
57 'fields' => array(
58 'searchatlas_api_key',
59 'apikey'
60 )
61 ),
62 'otto_ssr' => array(
63 'title' => Metasync::get_whitelabel_otto_name() . ' Server-Side Rendering',
64 'description' => 'Configure ' . Metasync::get_whitelabel_otto_name() . ' rendering and display options',
65 'icon' => 'performance',
66 'priority' => 20,
67 'default_open' => false,
68 'fields' => array(
69 'otto_pixel_uuid',
70 'otto_disable_on_loggedin',
71 'otto_disable_preview_button',
72 'otto_wp_rocket_compat',
73 'otto_sg_optimizer_compat',
74 'bookingpress_session_compat',
75 )
76 ),
77 'edge_cache' => array(
78 'title' => 'Edge Cache / CDN',
79 'description' => 'Configure CDN credentials for cache-tag purging',
80 'icon' => 'admin-site-alt3',
81 'priority' => 22,
82 'default_open' => false,
83 'render_callback' => array('Metasync_Edge_Cache_Settings', 'render'),
84 ),
85 'bot_detection' => array(
86 'title' => 'Bot Detection & Filtering',
87 'description' => 'Manage bot traffic and reduce unnecessary API calls',
88 'icon' => 'filter',
89 'priority' => 25,
90 'default_open' => false,
91 'fields' => array(
92 'otto_disable_for_bots',
93 'otto_bot_whitelist',
94 'otto_bot_blacklist',
95 'otto_bot_statistics_link'
96 )
97 ),
98 'breadcrumbs' => array(
99 'title' => 'Breadcrumbs',
100 'description' => 'Configure breadcrumb navigation display',
101 'icon' => 'format-links',
102 'priority' => 35,
103 'default_open' => false,
104 'render_callback' => array($this, 'render_breadcrumbs_section')
105 ),
106 'open_graph' => array(
107 'title' => 'Open Graph',
108 'description' => 'Control which Open Graph and article meta tags are output',
109 'icon' => 'share',
110 'priority' => 38,
111 'default_open' => false,
112 'fields' => array(
113 'og_image_dimensions',
114 'article_timestamps',
115 'article_author',
116 'article_section',
117 'article_tags',
118 'twitter_image_alt'
119 )
120 ),
121 'editor_settings' => array(
122 'title' => 'Post/Page Editor Settings',
123 'description' => 'Customize meta boxes and editor functionality',
124 'icon' => 'edit',
125 'priority' => 40,
126 'default_open' => false,
127 'fields' => array(
128 'disable_common_robots_metabox',
129 'disable_advance_robots_metabox',
130 'disable_redirection_metabox',
131 'disable_canonical_metabox',
132 'disable_social_opengraph_metabox',
133 'disable_schema_markup_metabox',
134 'disable_seo_metabox',
135 'open_external_links'
136 )
137 ),
138 'user_management' => array(
139 'title' => 'User Management for Content',
140 'description' => 'Configure which users are allowed to be authors of content synced.',
141 'icon' => 'groups',
142 'priority' => 50,
143 'default_open' => false,
144 'fields' => array(
145 'content_genius_sync_roles'
146 )
147 ),
148 'content_rendering' => array(
149 'title' => 'Content Rendering',
150 'description' => 'Configure how synced content is stored and rendered by your page builder',
151 'icon' => 'admin-appearance',
152 'priority' => 55,
153 'default_open' => false,
154 'fields' => array(
155 'default_page_builder'
156 )
157 ),
158 'llms_txt' => array(
159 'title' => 'LLMs.txt',
160 'description' => 'Publish a standards-compliant /llms.txt so AI search engines and assistants can discover your content',
161 'icon' => 'media-text',
162 'priority' => 56,
163 'default_open' => false,
164 'render_callback' => array($this, 'render_llms_txt_section'),
165 ),
166 'advanced' => array(
167 'title' => 'Plugin Settings',
168 'description' => 'System configuration and maintenance options',
169 'icon' => 'admin-settings',
170 'priority' => 60,
171 'default_open' => false,
172 'fields' => array(
173 'permalink_structure',
174 'hide_dashboard_framework',
175 'show_admin_bar_status',
176 'enable_auto_updates',
177 'import_external_data'
178 )
179 )
180 );
181 }
182
183 public function get_advanced_accordion_config() {
184 $config = array();
185
186 $user = wp_get_current_user();
187 if (in_array('administrator', (array) $user->roles)) {
188 $config['plugin_access'] = array(
189 'title' => 'User Roles with Plugin Access',
190 'description' => 'Control which user roles can see and access this plugin',
191 'icon' => 'admin-users',
192 'priority' => 5,
193 'default_open' => false,
194 'render_callback' => array($this, 'render_plugin_access_roles_section')
195 );
196 }
197
198 $config['debug_mode'] = array(
199 'title' => 'Debug Mode',
200 'description' => 'Manage debug mode with automatic disable and safety limits',
201 'icon' => 'info',
202 'priority' => 8,
203 'default_open' => false,
204 'render_callback' => array($this->admin_instance, 'render_debug_mode_section')
205 );
206
207 $config['error_logs'] = array(
208 'title' => 'Error Logs',
209 'description' => 'View and manage error logs to troubleshoot issues',
210 'icon' => 'warning',
211 'priority' => 10,
212 'default_open' => true,
213 'render_callback' => array($this->admin_instance, 'render_error_log_content')
214 );
215
216 $config['execution_settings'] = array(
217 'title' => 'Execution Settings',
218 'description' => 'Configure resource limits and execution parameters',
219 'icon' => 'controls-play',
220 'priority' => 15,
221 'default_open' => false,
222 'render_callback' => array($this, 'render_execution_settings_section')
223 );
224
225 $config['otto_cache'] = array(
226 'title' => 'Cache Management',
227 'description' => 'Manage ' . Metasync::get_whitelabel_otto_name() . ' cache and clear all cache plugins',
228 'icon' => 'database',
229 'priority' => 20,
230 'default_open' => false,
231 'render_callback' => array($this->admin_instance, 'render_otto_cache_management')
232 );
233
234 $config['db_cleanup'] = array(
235 'title' => 'Database Cleanup',
236 'description' => 'Remove orphaned data and schedule automated weekly cleanup',
237 'icon' => 'trash',
238 'priority' => 25,
239 'default_open' => false,
240 'render_callback' => array($this->admin_instance, 'render_db_cleanup_section')
241 );
242
243 $config['performance'] = array(
244 'title' => 'Performance & CPU Load',
245 'description' => 'Configure CPU load thresholds to prevent processing during peak server load',
246 'icon' => 'chart-line',
247 'priority' => 27,
248 'default_open' => false,
249 'render_callback' => array($this->admin_instance, 'render_cpu_monitor_section')
250 );
251
252 $config['reset_settings'] = array(
253 'title' => 'Reset Plugin Settings',
254 'description' => 'Reset all plugin settings to default values',
255 'icon' => 'update',
256 'priority' => 30,
257 'default_open' => false,
258 'render_callback' => array($this, 'render_reset_settings_section')
259 );
260
261 return $config;
262 }
263
264 public function render_advanced_accordion() {
265 $sections_config = $this->get_advanced_accordion_config();
266
267 uasort($sections_config, function($a, $b) {
268 return $a['priority'] - $b['priority'];
269 });
270
271 echo '<div class="metasync-settings-accordion">';
272
273 foreach ($sections_config as $section_key => $section_data) {
274 $section_id = 'metasync-advanced-section-' . $section_key;
275 $is_open = $section_data['default_open'];
276 $aria_expanded = $is_open ? 'true' : 'false';
277 $content_state = $is_open ? 'open' : 'closed';
278
279 echo '<div class="metasync-accordion-section" data-section="' . esc_attr($section_key) . '">';
280
281 echo '<div class="metasync-accordion-header" role="button" tabindex="0" aria-expanded="' . $aria_expanded . '" aria-controls="' . $section_id . '">';
282 echo '<div class="metasync-accordion-title">';
283 echo '<span class="metasync-accordion-icon"><span class="dashicons dashicons-' . esc_attr($section_data['icon']) . '"></span></span>';
284 echo '<div class="metasync-accordion-text">';
285 echo '<h3>' . esc_html($section_data['title']) . '</h3>';
286 echo '<p class="metasync-accordion-description">' . esc_html($section_data['description']) . '</p>';
287 echo '</div>';
288 echo '</div>';
289 echo '<button type="button" class="metasync-accordion-toggle" aria-label="Toggle section">';
290 echo '<span class="toggle-icon">▼</span>';
291 echo '</button>';
292 echo '</div>';
293
294 echo '<div class="metasync-accordion-content" id="' . $section_id . '" data-state="' . $content_state . '">';
295
296 if (isset($section_data['render_callback']) && is_callable($section_data['render_callback'])) {
297 call_user_func($section_data['render_callback']);
298 }
299
300 echo '</div>';
301 echo '</div>';
302 }
303
304 echo '</div>';
305 }
306
307 public function render_reset_settings_section() {
308 ?>
309 <div style="background: var(--dashboard-card-bg); padding: 20px; border-radius: 8px;">
310 <div style="background: rgba(255, 243, 205, 0.1); border: 1px solid rgba(255, 234, 167, 0.3); border-radius: 8px; padding: 20px; margin-bottom: 20px;">
311 <h4 style="color: var(--dashboard-warning, #f59e0b); margin: 0 0 12px 0; display: flex; align-items: center; gap: 8px;">
312 <span class="dashicons dashicons-warning" style="color:var(--dashboard-warning,#f59e0b);font-size:18px;width:18px;height:18px;"></span>
313 <span>Important Warning</span>
314 </h4>
315 <p style="color: var(--dashboard-text-secondary); margin: 0 0 12px 0;">This action will permanently delete:</p>
316 <ul style="color: var(--dashboard-text-secondary); margin: 0 0 12px 20px; line-height: 1.8;">
317 <li>All API keys and authentication tokens</li>
318 <li>White label branding settings</li>
319 <li>Plugin configuration and preferences</li>
320 <li>Instant indexing settings</li>
321 <li>Media optimization settings and cache</li>
322 <li>Code minification settings</li>
323 <li>All cached data and crawl information</li>
324 </ul>
325 <p style="color: var(--dashboard-warning, #f59e0b); margin: 0; font-weight: 600;">You will need to reconfigure the plugin completely after this reset.</p>
326 </div>
327 <form method="post" action="" onsubmit="return confirmClearSettings(event)">
328 <?php wp_nonce_field('metasync_clear_all_settings_nonce', 'clear_all_settings_nonce'); ?>
329 <input type="hidden" name="clear_all_settings" value="yes" />
330 <button type="submit" class="metasync-btn-danger" style="background: var(--dashboard-error, #ef4444); color: #ffffff; border: none; padding: 10px 20px; border-radius: 8px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 2px 4px rgba(239, 68, 68, 0.2);" onmouseover="this.style.background='#dc2626'; this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 8px rgba(239, 68, 68, 0.3)';" onmouseout="this.style.background='var(--dashboard-error, #ef4444)'; this.style.transform='translateY(0)'; this.style.boxShadow='0 2px 4px rgba(239, 68, 68, 0.2)';">
331 <span class="dashicons dashicons-trash" style="margin-top:3px;font-size:15px;width:15px;height:15px;"></span> Clear All Settings
332 </button>
333 </form>
334 </div>
335 <script>
336 function confirmClearSettings(event) {
337 event.preventDefault();
338
339 var firstConfirm = confirm("⚠️ WARNING: This will permanently delete ALL plugin settings!\n\nThis action cannot be undone. Are you sure you want to continue?");
340 if (!firstConfirm) {
341 return false;
342 }
343
344 var secondConfirm = confirm("🚨 FINAL WARNING 🚨\n\nThis will delete:\n• All API keys and authentication tokens\n• White label branding settings\n• Plugin configuration and preferences\n• Instant indexing settings\n• Media optimization settings and cache\n• Code minification settings\n• All cached data\n\nYou will need to reconfigure the entire plugin from scratch.\n\nType 'DELETE' in the next prompt to confirm.");
345 if (!secondConfirm) {
346 return false;
347 }
348
349 var typeConfirm = prompt("Type 'DELETE' (in capital letters) to confirm you want to permanently clear all settings:");
350 if (typeConfirm !== 'DELETE') {
351 alert("Settings reset cancelled. Type 'DELETE' exactly to confirm.");
352 return false;
353 }
354
355 event.target.submit();
356 return false;
357 }
358 </script>
359 <?php
360 }
361
362 public function render_google_index_section() {
363 if (!function_exists('google_index_direct')) {
364 if (file_exists(plugin_dir_path(dirname(__FILE__)) . 'google-index/google-index-init.php')) {
365 require_once plugin_dir_path(dirname(__FILE__)) . 'google-index/google-index-init.php';
366 } else {
367 error_log('MetaSync Google Index: google-index-init.php not found at ' . plugin_dir_path(dirname(__FILE__)) . 'google-index/google-index-init.php');
368 return;
369 }
370 }
371
372 $google_index = google_index_direct();
373 $service_info = $google_index->get_service_account_info();
374 $is_configured = !isset($service_info['error']);
375
376 $saved_json_display = $is_configured ? $google_index->get_redacted_config_json() : '';
377
378 include plugin_dir_path(dirname(__FILE__)) . 'views/metasync-google-index-api-settings.php';
379
380 // Render post types selection
381 $options = get_option('metasync_options_instant_indexing', ['post_types' => []]);
382 $post_types_settings = isset($options['post_types']) && is_array($options['post_types']) ? $options['post_types'] : [];
383 include plugin_dir_path(dirname(__FILE__)) . 'views/metasync-google-instant-post-types.php';
384 }
385
386 public function render_bing_index_section() {
387 require_once plugin_dir_path(dirname(__FILE__)) . 'bing-index/class-metasync-bing-instant-index.php';
388 $bing_index = new Metasync_Bing_Instant_Index();
389 $api_key = $bing_index->get_setting('api_key');
390 $endpoint = $bing_index->get_setting('endpoint', 'indexnow');
391 $post_types_settings = $bing_index->get_setting('post_types', []);
392 $is_configured = !empty($api_key);
393
394 $post_types = get_post_types(['public' => true], 'objects');
395
396 ?>
397 <div style="padding: 20px;">
398 <!-- About IndexNow (Collapsible) -->
399 <details style="margin-bottom: 20px;">
400 <summary style="cursor: pointer; font-weight: 600; color: var(--dashboard-text-primary);">
401 About IndexNow Protocol
402 </summary>
403 <div style="margin-top: 10px; padding: 15px; background: rgba(255, 255, 255, 0.03); border-radius: 4px;">
404 <p style="color: var(--dashboard-text-secondary); margin: 0 0 10px 0;">
405 IndexNow is a simple protocol that allows websites to instantly notify search engines about URL changes.
406 It's supported by Bing, Yandex, Naver, Seznam, and other search engines.
407 </p>
408 <ul style="color: var(--dashboard-text-secondary); margin: 0 0 10px 20px;">
409 <li>✓ Instant notification to multiple search engines</li>
410 <li>✓ Simple API key authentication (no OAuth required)</li>
411 <li>✓ Supports batch URL submissions (up to 10,000 URLs)</li>
412 <li>✓ Free to use with no quotas</li>
413 <li>✓ Fire-and-forget protocol (no status checking needed)</li>
414 </ul>
415 <p style="color: var(--dashboard-text-secondary); margin: 0;">
416 <strong>Resources:</strong>
417 <a href="https://www.indexnow.org/documentation" target="_blank" style="color: var(--dashboard-accent);">IndexNow Documentation</a> |
418 <a href="https://www.bing.com/webmasters" target="_blank" style="color: var(--dashboard-accent);">Bing Webmaster Tools</a>
419 </p>
420 </div>
421 </details>
422
423 <!-- Configuration Status -->
424 <div style="margin-bottom: 20px;">
425 <?php if ($is_configured): ?>
426 <div style="padding: 12px; background: rgba(76, 175, 80, 0.1); border-left: 4px solid #4caf50; border-radius: 4px;">
427 <p style="margin: 0; color: var(--dashboard-text-primary);">
428 <strong style="color: #4caf50;">IndexNow API Configured</strong><br>
429 <span style="color: var(--dashboard-text-secondary); font-size: 0.95em;">
430 API Key: <code style="padding: 2px 6px; background: rgba(0,0,0,0.1); border-radius: 3px;"><?php echo esc_html(substr($api_key, 0, 8) . '...' . substr($api_key, -8)); ?></code>
431 </span>
432 </p>
433 </div>
434 <?php else: ?>
435 <div style="padding: 12px; background: rgba(255, 152, 0, 0.1); border-left: 4px solid #ff9800; border-radius: 4px;">
436 <p style="margin: 0; color: var(--dashboard-text-primary);">
437 <strong style="color: #ff9800;">Configuration Required</strong><br>
438 <span style="color: var(--dashboard-text-secondary); font-size: 0.95em;">
439 Configure your IndexNow API key below to enable instant indexing with Bing and other search engines.
440 </span>
441 </p>
442 </div>
443 <?php endif; ?>
444 </div>
445
446 <!-- Settings Fields -->
447 <table class="form-table" style="margin-top: 0;">
448 <tr>
449 <th scope="row" style="width: 200px; padding-top: 15px;">
450 <label for="metasync_bing_api_key_inline">IndexNow API Key <span style="color: #d63638;">*</span></label>
451 <?php Metasync::render_tooltip_icon('metasync_bing_api_key_inline', 'This key lets Bing trust indexing requests from your site. Click \'Generate Random API Key\' if you don\'t have one — the plugin publishes the matching verification file automatically, no manual upload needed.'); ?>
452 </th>
453 <td style="padding-top: 15px;">
454 <input type="text"
455 name="metasync_bing_api_key_inline"
456 id="metasync_bing_api_key_inline"
457 class="large-text"
458 value="<?php echo esc_attr($api_key); ?>"
459 placeholder="Enter your IndexNow API key (32+ character hexadecimal string)" />
460 <br>
461 <button type="button"
462 id="generate-bing-api-key-inline"
463 class="button button-secondary"
464 style="margin-top: 8px;">
465 Generate Random API Key
466 </button>
467 <p class="description" style="margin-top: 8px;">
468 Your IndexNow API key is required to submit URLs for instant indexing. You can generate a random key or use your own (32+ character hexadecimal string).
469 After saving, a verification file will be automatically created at your site root.
470 </p>
471 </td>
472 </tr>
473
474 <tr>
475 <th scope="row" style="padding-top: 15px;">
476 <label>API Endpoint</label>
477 </th>
478 <td style="padding-top: 15px;">
479 <fieldset>
480 <label style="display: block; margin-bottom: 8px;">
481 <input type="radio"
482 name="metasync_bing_endpoint_inline"
483 value="indexnow"
484 <?php checked($endpoint, 'indexnow'); ?>>
485 <strong>IndexNow.org</strong> (Recommended)
486 <span style="color: var(--dashboard-text-secondary); font-size: 0.9em; display: block; margin-left: 24px;">
487 Notifies Bing, Yandex, Naver, Seznam, and other participating search engines
488 </span>
489 </label>
490 <label style="display: block;">
491 <input type="radio"
492 name="metasync_bing_endpoint_inline"
493 value="bing"
494 <?php checked($endpoint, 'bing'); ?>>
495 <strong>Bing.com</strong> (Bing-specific)
496 <span style="color: var(--dashboard-text-secondary); font-size: 0.9em; display: block; margin-left: 24px;">
497 Direct submission to Bing only
498 </span>
499 </label>
500 </fieldset>
501 <p class="description">
502 Select which endpoint to use for submitting URLs. The IndexNow.org endpoint is recommended as it notifies multiple search engines at once.
503 </p>
504 </td>
505 </tr>
506
507 <tr>
508 <th scope="row" style="padding-top: 15px;">
509 <label>Auto-Submit Post Types</label>
510 </th>
511 <td style="padding-top: 15px;">
512 <fieldset>
513 <?php foreach ($post_types as $post_type): ?>
514 <label style="display: inline-block; margin-right: 20px; margin-bottom: 8px;">
515 <input type="checkbox"
516 name="metasync_bing_post_types_inline[<?php echo esc_attr($post_type->name); ?>]"
517 value="<?php echo esc_attr($post_type->name); ?>"
518 <?php checked(in_array($post_type->name, $post_types_settings, true)); ?>>
519 <?php echo esc_html($post_type->label); ?>
520 </label>
521 <?php endforeach; ?>
522 </fieldset>
523 <p class="description">
524 Selected post types will be automatically submitted to IndexNow when published or updated.
525 </p>
526 </td>
527 </tr>
528
529 <tr>
530 <th scope="row" style="padding-top: 15px;">
531 <label>Plugin Source Control</label>
532 </th>
533 <td style="padding-top: 15px;">
534 <fieldset>
535 <label style="display: flex; align-items: flex-start; gap: 8px;">
536 <input type="checkbox"
537 name="metasync_bing_disable_other_plugins_inline"
538 value="1"
539 <?php checked($bing_index->get_setting('disable_other_plugins', true), true); ?>
540 style="margin-top: 2px;">
541 <span>
542 <strong>Disable other IndexNow plugins</strong>
543 <span style="display: block; color: var(--dashboard-text-secondary); font-size: 0.9em; margin-top: 4px; font-weight: normal;">
544 When enabled, Our plugin will be the exclusive source for IndexNow submissions in Bing Webmaster Tools.
545 This disables IndexNow features in Yoast SEO, Rank Math, and other competing plugins to prevent duplicate submissions.
546 </span>
547 </span>
548 </label>
549 </fieldset>
550 </td>
551 </tr>
552 </table>
553
554 </div>
555
556 <script>
557 jQuery(document).ready(function($) {
558 $('#generate-bing-api-key-inline').on('click', function() {
559 const array = new Uint8Array(16);
560 crypto.getRandomValues(array);
561 const hexString = Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
562 $('#metasync_bing_api_key_inline').val(hexString);
563
564 $('#enable_binginstantindex').prop('checked', true);
565
566 $('#metasync_bing_api_key_inline').trigger('change');
567 });
568
569 $('#metasync_bing_api_key_inline').on('input', function() {
570 const apiKey = $(this).val().trim();
571 if (apiKey.length >= 8) {
572 $('#enable_binginstantindex').prop('checked', true);
573 }
574 });
575
576 function toggleBingConfig() {
577 const isEnabled = $('#enable_binginstantindex').is(':checked');
578 const $apiConfig = $('#enable_binginstantindex').closest('tr').nextAll('tr').first();
579 if (isEnabled) {
580 $apiConfig.show();
581 } else {
582 $apiConfig.hide();
583 }
584 }
585
586 toggleBingConfig();
587
588 $('#enable_binginstantindex').on('change', toggleBingConfig);
589 });
590 </script>
591
592 <style>
593 .form-table th {
594 color: var(--dashboard-text-primary);
595 }
596
597 .form-table td {
598 color: var(--dashboard-text-secondary);
599 }
600
601 .form-table code {
602 padding: 2px 6px;
603 background: rgba(0, 0, 0, 0.1);
604 border-radius: 3px;
605 font-size: 0.9em;
606 }
607
608 details summary {
609 transition: color 0.2s;
610 }
611
612 details summary:hover {
613 color: var(--dashboard-accent);
614 }
615
616 details[open] summary {
617 margin-bottom: 10px;
618 }
619 </style>
620 <?php
621 }
622
623 public function render_plugin_access_roles_section() {
624 $general_options = Metasync::get_option('general');
625
626 $setting_exists = isset($general_options['plugin_access_roles']);
627 $selected_roles = $general_options['plugin_access_roles'] ?? null;
628
629 if (is_string($selected_roles)) {
630 $selected_roles = array($selected_roles);
631 } elseif (!is_array($selected_roles)) {
632 $selected_roles = null;
633 }
634
635 if (!function_exists('wp_roles')) {
636 require_once(ABSPATH . 'wp-admin/includes/user.php');
637 }
638 global $wp_roles;
639
640 if (!isset($wp_roles) || !is_object($wp_roles)) {
641 $wp_roles = new WP_Roles();
642 }
643
644 $all_roles = $wp_roles->roles;
645
646 $all_roles_checked = is_array($selected_roles) && in_array('all', $selected_roles);
647 ?>
648 <div style="background: var(--dashboard-card-bg); padding: 20px; border-radius: 8px;">
649 <p style="color: var(--dashboard-text-secondary); margin: 0 0 20px 0;">
650 Select which user roles can see and access this plugin's menu, settings, and options in the WordPress admin area.
651 </p>
652
653 <form method="post" action="<?php echo admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '&tab=advanced'); ?>" id="plugin-access-roles-form">
654 <?php wp_nonce_field('metasync_plugin_access_roles_nonce', 'plugin_access_roles_nonce'); ?>
655 <input type="hidden" name="save_plugin_access_roles" value="yes" />
656
657 <div class="metasync-role-selector-container" style="background: var(--dashboard-card-bg-alt, rgba(255,255,255,0.05)); border: 1px solid var(--dashboard-border); border-radius: 8px; padding: 16px; max-height: 300px; overflow-y: auto;">
658
659 <!-- All Roles Option -->
660 <label class="metasync-role-option-all" style="display: flex; align-items: center; gap: 10px; padding: 12px; cursor: pointer; border-radius: 6px; transition: background 0.2s ease;">
661 <input type="checkbox"
662 id="plugin-access-all-roles"
663 name="plugin_access_roles[]"
664 value="all"
665 style="width: 18px; height: 18px; cursor: pointer;"
666 <?php checked($all_roles_checked, true); ?> />
667 <strong style="color: var(--dashboard-text-primary);">All Roles</strong>
668 </label>
669
670 <!-- Divider -->
671 <hr style="border: none; border-top: 1px solid var(--dashboard-border); margin: 12px 0;">
672
673 <?php
674 if (!empty($all_roles) && is_array($all_roles)) {
675 foreach ($all_roles as $role_key => $role_details) {
676 if ($role_key === 'administrator') {
677 continue;
678 }
679
680 if (!isset($role_details['name'])) {
681 continue;
682 }
683
684 $is_checked = is_array($selected_roles) && in_array($role_key, $selected_roles);
685 $role_name = translate_user_role($role_details['name']);
686 ?>
687 <label class="metasync-role-option" style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; cursor: pointer; border-radius: 6px; transition: background 0.2s ease;">
688 <input type="checkbox"
689 class="plugin-access-individual-role"
690 name="plugin_access_roles[]"
691 value="<?php echo esc_attr($role_key); ?>"
692 style="width: 18px; height: 18px; cursor: pointer;"
693 <?php checked($is_checked, true); ?> />
694 <span style="color: var(--dashboard-text-primary);"><?php echo esc_html($role_name); ?></span>
695 </label>
696 <?php
697 }
698 }
699 ?>
700
701 </div>
702
703 <!-- Description -->
704 <div style="background: rgba(59, 130, 246, 0.1); border: 1px solid rgba(59, 130, 246, 0.3); border-radius: 8px; padding: 16px; margin: 20px 0;">
705 <p style="color: var(--dashboard-text-secondary); margin: 0; font-size: 13px;">
706 <strong style="color: var(--dashboard-info, #3b82f6);">How it works:</strong><br>
707 <strong>Administrators always have access</strong> to this plugin regardless of settings.<br>
708 <strong>By default</strong>, only Administrators can access the plugin (no roles selected).<br>
709 If <strong>"All Roles"</strong> is selected, all users will see the plugin.<br>
710 Users with unchecked roles will not see the plugin in their WordPress admin area.
711 </p>
712 </div>
713
714 <button type="submit" class="metasync-btn-primary" style="background: var(--dashboard-gradient-primary); color: #ffffff; border: none; padding: 12px 24px; border-radius: 8px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 8px rgba(0, 0, 0, 0.15)';" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 2px 4px rgba(0, 0, 0, 0.1)';">
715 Save Access Settings
716 </button>
717 </form>
718 </div>
719 <script>
720 jQuery(document).ready(function($) {
721 var $allRolesCheckbox = $('#plugin-access-all-roles');
722 var $individualRoles = $('.plugin-access-individual-role');
723
724 $allRolesCheckbox.on('change', function() {
725 if ($(this).is(':checked')) {
726 $individualRoles.prop('checked', false);
727 }
728 });
729
730 $individualRoles.on('change', function() {
731 if ($(this).is(':checked')) {
732 $allRolesCheckbox.prop('checked', false);
733 }
734 });
735 });
736 </script>
737 <?php
738 }
739
740 public function get_default_execution_settings() {
741 return array(
742 'max_execution_time' => 30,
743 'max_memory_limit' => 256,
744 'log_batch_size' => 1000,
745 'action_scheduler_batches' => 1,
746 'otto_rate_limit' => 10,
747 'otto_cache_ttl' => 30,
748 'queue_cleanup_days' => 31
749 );
750 }
751
752 public function get_execution_setting($key, $default = null) {
753 $settings = get_option('metasync_execution_settings', array());
754 $defaults = $this->get_default_execution_settings();
755
756 if (empty($settings)) {
757 return isset($defaults[$key]) ? $defaults[$key] : $default;
758 }
759
760 return isset($settings[$key]) ? $settings[$key] : (isset($defaults[$key]) ? $defaults[$key] : $default);
761 }
762
763 public function get_all_execution_settings() {
764 $settings = get_option('metasync_execution_settings', array());
765 $defaults = $this->get_default_execution_settings();
766
767 return wp_parse_args($settings, $defaults);
768 }
769
770 public function can_change_memory_limit() {
771 $current_limit = ini_get('memory_limit');
772
773 $test_result = @ini_set('memory_limit', $current_limit);
774
775 return $test_result !== false;
776 }
777
778 public function get_server_limits() {
779 $max_execution_time = ini_get('max_execution_time');
780 $memory_limit = ini_get('memory_limit');
781
782 // Prefer the server-configured value: WordPress (wp_raise_memory_limit)
783 // or this plugin (apply_memory_limit) may have already changed the
784 // runtime limit via ini_set, so ini_get no longer reflects the server
785 // configuration. get_cfg_var reads the php.ini value untouched.
786 $config_memory_limit = get_cfg_var('memory_limit');
787 if ($config_memory_limit !== false && $config_memory_limit !== '') {
788 $memory_limit = $config_memory_limit;
789 }
790
791 $memory_limit_mb = $this->parse_memory_limit_to_mb($memory_limit);
792
793 $can_change_memory = $this->can_change_memory_limit();
794
795 $memory_limit_display = $memory_limit_mb == -1 ? 'Unlimited' : $memory_limit_mb . ' MB';
796
797 return array(
798 'max_execution_time' => $max_execution_time == -1 ? 'Unlimited' : $max_execution_time . ' seconds',
799 'memory_limit' => $memory_limit_display,
800 'max_execution_time_raw' => $max_execution_time,
801 'memory_limit_raw' => $memory_limit_mb,
802 'can_change_memory' => $can_change_memory
803 );
804 }
805
806 /**
807 * Allowed range for the Max Memory Limit setting: 64 MB up to the
808 * server-configured memory limit (null max when the server is unlimited).
809 */
810 public function get_memory_limit_allowed_range() {
811 $server_limits = $this->get_server_limits();
812 $max = $server_limits['memory_limit_raw'] == -1 ? null : max(64, (int) $server_limits['memory_limit_raw']);
813
814 return array(
815 'min' => 64,
816 'max' => $max
817 );
818 }
819
820 public function apply_memory_limit() {
821 if (!$this->can_change_memory_limit()) {
822 return false;
823 }
824
825 $memory_limit_mb = $this->get_execution_setting('max_memory_limit');
826
827 $result = @ini_set('memory_limit', $memory_limit_mb . 'M');
828
829 return $result !== false;
830 }
831
832 public function parse_memory_limit_to_mb($memory_limit) {
833 if ($memory_limit == -1 || $memory_limit == '-1') {
834 return -1;
835 }
836
837 $memory_limit = trim($memory_limit);
838 $last = strtolower(substr($memory_limit, -1));
839 $value = (int) $memory_limit;
840
841 switch ($last) {
842 case 'g':
843 $value *= 1024;
844 break;
845 case 'm':
846 break;
847 case 'k':
848 $value /= 1024;
849 break;
850 }
851
852 return $value;
853 }
854
855 public function render_execution_settings_section() {
856 $settings = $this->get_all_execution_settings();
857 $server_limits = $this->get_server_limits();
858 $memory_range = $this->get_memory_limit_allowed_range();
859
860 $warnings = array();
861 if ((int)$server_limits['max_execution_time_raw'] !== -1 && (int)$settings['max_execution_time'] > (int)$server_limits['max_execution_time_raw']) {
862 $warnings['max_execution_time'] = true;
863 }
864 if ($server_limits['memory_limit_raw'] != -1 && $settings['max_memory_limit'] > $server_limits['memory_limit_raw']) {
865 $warnings['max_memory_limit'] = true;
866 }
867 ?>
868 <div style="background: var(--dashboard-card-bg); padding: 20px; border-radius: 8px;">
869 <p style="color: var(--dashboard-text-secondary); margin: 0 0 20px 0;">
870 Configure resource limits and execution parameters for plugin operations. Adjust these settings based on your server capabilities.
871 </p>
872
873 <form id="metasync-execution-settings-form" method="post">
874 <?php wp_nonce_field('metasync_execution_settings_nonce', 'execution_settings_nonce'); ?>
875
876 <!-- Execution & Memory Section -->
877 <div style="background: var(--dashboard-card-bg-alt, rgba(255,255,255,0.05)); border: 1px solid var(--dashboard-border); border-radius: 8px; padding: 20px; margin-bottom: 20px;">
878 <h3 style="color: var(--dashboard-text-primary); margin: 0 0 16px 0; font-size: 16px; font-weight: 600;">Execution & Memory</h3>
879
880 <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px;">
881 <div>
882 <label style="display: block; color: var(--dashboard-text-primary); margin-bottom: 8px; font-weight: 500;">
883 Max Execution Time:
884 <?php Metasync::render_tooltip_icon('max_execution_time', 'How many seconds a single plugin operation (like a bulk sync or scan) is allowed to run before PHP stops it. Raise this only if you see timeout errors, and only up to what your host allows.'); ?>
885 </label>
886 <div style="display: flex; align-items: center; gap: 10px;">
887 <input type="number"
888 id="max_execution_time"
889 name="max_execution_time"
890 value="<?php echo esc_attr($settings['max_execution_time']); ?>"
891 min="1"
892 max="300"
893 required
894 style="width: 100px; padding: 8px; border: 1px solid var(--dashboard-border); border-radius: 6px; background: var(--dashboard-card-bg); color: var(--dashboard-text-primary);" />
895 <span style="color: var(--dashboard-text-secondary);">seconds</span>
896 </div>
897 <p style="color: var(--dashboard-text-secondary); font-size: 12px; margin: 4px 0 0 0;">
898 Server Limit: <?php echo esc_html($server_limits['max_execution_time']); ?>
899 </p>
900 <p id="max_execution_time_warning" style="display: none; color: #f59e0b; font-size: 12px; margin: 4px 0 0 0;">
901 <span class="dashicons dashicons-warning" style="margin-right:4px;font-size:14px;width:14px;height:14px;color:#f59e0b;"></span>
902 <span>Configured value exceeds server limit</span>
903 </p>
904 <?php if (isset($warnings['max_execution_time'])): ?>
905 <script>
906 jQuery(document).ready(function($) {
907 $('#max_execution_time_warning').show();
908 });
909 </script>
910 <?php endif; ?>
911 </div>
912
913 <div>
914 <label style="display: block; color: var(--dashboard-text-primary); margin-bottom: 8px; font-weight: 500;">
915 Max Memory Limit:
916 <?php Metasync::render_tooltip_icon('max_memory_limit', 'How much memory (RAM) the plugin is allowed to use for a single operation before PHP stops it. Raise this only if you see "out of memory" errors, and only up to what your host allows.'); ?>
917 </label>
918 <div style="display: flex; align-items: center; gap: 10px;">
919 <input type="number"
920 id="max_memory_limit"
921 name="max_memory_limit"
922 value="<?php echo esc_attr($settings['max_memory_limit']); ?>"
923 min="<?php echo esc_attr($memory_range['min']); ?>"
924 <?php if ($memory_range['max'] !== null): ?>max="<?php echo esc_attr($memory_range['max']); ?>"<?php endif; ?>
925 <?php if (!$server_limits['can_change_memory']): ?>
926 readonly
927 disabled
928 <?php endif; ?>
929 required
930 style="width: 100px; padding: 8px; border: 1px solid var(--dashboard-border); border-radius: 6px; background: <?php echo $server_limits['can_change_memory'] ? 'var(--dashboard-card-bg)' : 'rgba(128, 128, 128, 0.1)'; ?>; color: <?php echo $server_limits['can_change_memory'] ? 'var(--dashboard-text-primary)' : 'var(--dashboard-text-secondary)'; ?>; cursor: <?php echo $server_limits['can_change_memory'] ? 'text' : 'not-allowed'; ?>;" />
931 <span style="color: var(--dashboard-text-secondary);">MB</span>
932 </div>
933 <p style="color: var(--dashboard-text-secondary); font-size: 12px; margin: 4px 0 0 0;">
934 Server Limit: <?php echo esc_html($server_limits['memory_limit']); ?> &middot; Allowed: <?php echo $memory_range['max'] !== null ? esc_html($memory_range['min'] . '' . $memory_range['max'] . ' MB') : esc_html($memory_range['min'] . ' MB or higher'); ?>
935 </p>
936 <?php if (!$server_limits['can_change_memory']): ?>
937 <p style="color: #f59e0b; font-size: 12px; margin: 4px 0 0 0; display: flex; align-items: center; gap: 4px;">
938 <span class="dashicons dashicons-lock" style="font-size:14px;width:14px;height:14px;color:#f59e0b;"></span>
939 <span>Server does not allow changing memory limit. This setting is read-only.</span>
940 </p>
941 <?php else: ?>
942 <p id="max_memory_limit_warning" style="display: none; color: #f59e0b; font-size: 12px; margin: 4px 0 0 0;">
943 <span class="dashicons dashicons-warning" style="margin-right:4px;font-size:14px;width:14px;height:14px;color:#f59e0b;"></span>
944 <span>Configured value exceeds server limit</span>
945 </p>
946 <?php if (isset($warnings['max_memory_limit'])): ?>
947 <script>
948 jQuery(document).ready(function($) {
949 $('#max_memory_limit_warning').show();
950 });
951 </script>
952 <?php endif; ?>
953 <?php endif; ?>
954 </div>
955 </div>
956 </div>
957
958 <!-- Batch Processing Section -->
959 <div style="background: var(--dashboard-card-bg-alt, rgba(255,255,255,0.05)); border: 1px solid var(--dashboard-border); border-radius: 8px; padding: 20px; margin-bottom: 20px;">
960 <h3 style="color: var(--dashboard-text-primary); margin: 0 0 16px 0; font-size: 16px; font-weight: 600;">Batch Processing</h3>
961
962 <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px;">
963 <div>
964 <label style="display: block; color: var(--dashboard-text-primary); margin-bottom: 8px; font-weight: 500;">
965 Log Processing Batch Size:
966 <?php Metasync::render_tooltip_icon('log_batch_size', 'How many log lines the plugin processes at once when scanning its own logs. Lower it if this causes slowdowns on smaller hosting plans; most sites can leave the default.'); ?>
967 </label>
968 <div style="display: flex; align-items: center; gap: 10px;">
969 <input type="number"
970 id="log_batch_size"
971 name="log_batch_size"
972 value="<?php echo esc_attr($settings['log_batch_size']); ?>"
973 min="100"
974 max="5000"
975 required
976 style="width: 100px; padding: 8px; border: 1px solid var(--dashboard-border); border-radius: 6px; background: var(--dashboard-card-bg); color: var(--dashboard-text-primary);" />
977 <span style="color: var(--dashboard-text-secondary);">lines</span>
978 </div>
979 </div>
980
981 <div>
982 <label style="display: block; color: var(--dashboard-text-primary); margin-bottom: 8px; font-weight: 500;">
983 Action Scheduler Concurrent Batches:
984 <?php Metasync::render_tooltip_icon('action_scheduler_batches', 'How many background sync jobs the plugin is allowed to run at the same time. Higher values finish syncing faster but use more server resources at once — lower this if your site slows down during syncs.'); ?>
985 </label>
986 <div style="display: flex; align-items: center; gap: 10px;">
987 <input type="number"
988 id="action_scheduler_batches"
989 name="action_scheduler_batches"
990 value="<?php echo esc_attr($settings['action_scheduler_batches']); ?>"
991 min="1"
992 max="10"
993 required
994 style="width: 100px; padding: 8px; border: 1px solid var(--dashboard-border); border-radius: 6px; background: var(--dashboard-card-bg); color: var(--dashboard-text-primary);" />
995 </div>
996 <p style="color: #f59e0b; font-size: 12px; margin: 4px 0 0 0; display: flex; align-items: center; gap: 4px;">
997 <span class="dashicons dashicons-warning" style="font-size:14px;width:14px;height:14px;color:#f59e0b;"></span>
998 <span>Higher values increase server load</span>
999 </p>
1000 </div>
1001 </div>
1002 </div>
1003
1004 <!-- API Rate Limiting Section -->
1005 <div style="background: var(--dashboard-card-bg-alt, rgba(255,255,255,0.05)); border: 1px solid var(--dashboard-border); border-radius: 8px; padding: 20px; margin-bottom: 20px;">
1006 <h3 style="color: var(--dashboard-text-primary); margin: 0 0 16px 0; font-size: 16px; font-weight: 600;">API Rate Limiting</h3>
1007
1008 <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px;">
1009 <div>
1010 <label style="display: block; color: var(--dashboard-text-primary); margin-bottom: 8px; font-weight: 500;">
1011 <?php echo esc_html(Metasync::get_whitelabel_otto_name()); ?> API Calls Per Minute:
1012 <?php Metasync::render_tooltip_icon('otto_rate_limit', 'The maximum number of requests per minute the plugin will send to ' . Metasync::get_whitelabel_otto_name() . '\'s servers. Lower it if you\'re seeing rate-limit errors; most sites can leave the default.'); ?>
1013 </label>
1014 <div style="display: flex; align-items: center; gap: 10px;">
1015 <input type="number"
1016 id="otto_rate_limit"
1017 name="otto_rate_limit"
1018 value="<?php echo esc_attr($settings['otto_rate_limit']); ?>"
1019 min="1"
1020 max="60"
1021 required
1022 style="width: 100px; padding: 8px; border: 1px solid var(--dashboard-border); border-radius: 6px; background: var(--dashboard-card-bg); color: var(--dashboard-text-primary);" />
1023 </div>
1024 </div>
1025
1026 <div>
1027 <label style="display: block; color: var(--dashboard-text-primary); margin-bottom: 8px; font-weight: 500;">
1028 Queue Auto-Cleanup:
1029 <?php Metasync::render_tooltip_icon('queue_cleanup_days', 'How many days a completed background job is kept before the plugin automatically deletes its record. Lower it to save database space; raise it if you need to look back further when troubleshooting.'); ?>
1030 </label>
1031 <div style="display: flex; align-items: center; gap: 10px;">
1032 <input type="number"
1033 id="queue_cleanup_days"
1034 name="queue_cleanup_days"
1035 value="<?php echo esc_attr($settings['queue_cleanup_days']); ?>"
1036 min="7"
1037 max="90"
1038 required
1039 style="width: 100px; padding: 8px; border: 1px solid var(--dashboard-border); border-radius: 6px; background: var(--dashboard-card-bg); color: var(--dashboard-text-primary);" />
1040 <span style="color: var(--dashboard-text-secondary);">days</span>
1041 </div>
1042 </div>
1043 </div>
1044 </div>
1045
1046 <!-- Save Button -->
1047 <div style="margin-top: 20px;">
1048 <button type="submit"
1049 id="metasync-execution-settings-save-btn"
1050 class="button button-primary"
1051 style="padding: 10px 20px; font-size: 14px; font-weight: 500;">
1052 <span class="save-text">Save Settings</span>
1053 <span class="save-spinner" style="display: none; margin-left: 8px;"></span>
1054 </button>
1055 </div>
1056
1057 <!-- Success/Error Messages -->
1058 <div id="metasync-execution-settings-message" style="display: none; margin-top: 16px; padding: 12px; border-radius: 6px;"></div>
1059 </form>
1060 </div>
1061
1062 <?php
1063 // Real-time validation and AJAX save for this form live in
1064 // admin/js/metasync-execution-settings.js (enqueued on this page).
1065 // The previous inline duplicate was removed to avoid a double-bound
1066 // submit handler that fired the save request twice.
1067 ?>
1068 <?php
1069 }
1070
1071 public function get_field_tooltips() {
1072 $plugin_name = Metasync::get_effective_plugin_name();
1073 $otto_name = Metasync::get_whitelabel_otto_name();
1074
1075 return array(
1076 'searchatlas_api_key' => sprintf('Connect your WordPress site to your %1$s dashboard to retrieve your API key and %2$s UUID. This does not create a WordPress login session.', $plugin_name, $otto_name),
1077 'apikey' => sprintf('Auto-generated authentication token used for secure API communication between your WordPress site and %s services. You can refresh this token if needed for security purposes.', $plugin_name),
1078 'otto_pixel_uuid' => sprintf('Your unique %s tracking pixel identifier. This UUID is used to track %s modifications and analytics on your website pages.', $otto_name, $otto_name),
1079 'otto_disable_on_loggedin' => sprintf('Disable %s modifications when you are logged in to WordPress. This allows you to see and edit the original content without %s\'s enhancements during editing sessions.', $otto_name, $otto_name),
1080 'otto_disable_preview_button' => sprintf('Hide the %s frontend toolbar that displays the status indicator, preview button, and debug button. Enable this for a cleaner frontend experience.', $otto_name),
1081 'otto_wp_rocket_compat' => sprintf('WP Rocket Compatibility Mode: Controls how %s interacts with WP Rocket. "Auto" (recommended) allows both to work together by avoiding DONOTCACHEPAGE constant unless necessary for Brizy pages or SG Optimizer conflicts. This ensures WP Rocket\'s JavaScript delay and optimization features continue working.', $otto_name),
1082 'otto_sg_optimizer_compat' => sprintf('SiteGround Optimizer Compatibility Mode: Controls how %s renders on SiteGround hosting. "Auto" (recommended) uses the internal HTTP fetch, which protects themes that defer inline CSS to the footer (e.g. Divi). "Buffer Mode" renders in-process with no internal fetch — faster on sites whose pages can never be host-cached, for example when a plugin starts a PHP session on every request (BookingPress sessions are removed automatically, so this does not apply to them).', $otto_name),
1083 'bookingpress_session_compat' => 'BookingPress Session Compatibility: Controls whether MetaSync removes the PHP session BookingPress starts on every public page, so hosts such as SiteGround can page-cache the site again.',
1084 'otto_disable_for_bots' => sprintf('Enable bot detection to automatically skip %s processing for search engine crawlers, SEO tools, and other bots. This reduces unnecessary API calls and improves performance.', $otto_name),
1085 'otto_bot_whitelist' => sprintf('Enter bot names or user-agent patterns (one per line) that should always be processed by %s, even when "Disable for Bots" is enabled. For example: Googlebot, Bingbot. This allows you to ensure specific search engines always see your optimized content.', $otto_name),
1086 'otto_bot_blacklist' => sprintf('Enter bot names or user-agent patterns (one per line) that should always be blocked from %s processing, regardless of other settings. For example: BadBot, MaliciousCrawler. Use this to exclude problematic crawlers or unwanted traffic sources.', $otto_name),
1087 'otto_bot_statistics_link' => 'View detailed bot detection statistics including total hits, API calls saved, breakdown by bot type, and unique bot entries with hit counts.',
1088 'disable_common_robots_metabox' => 'Hide the Common Robots meta box from post and page edit screens. This removes the robots meta tag controls (index/noindex, follow/nofollow) from the editor interface.',
1089 'disable_advance_robots_metabox' => 'Hide the Advanced Robots meta box from post and page edit screens. This removes advanced robots directives like max-snippet, max-image-preview, and max-video-preview settings.',
1090 'disable_redirection_metabox' => 'Hide the Redirection meta box from post and page edit screens. This removes the URL redirect configuration options from the editor interface.',
1091 'disable_canonical_metabox' => 'Hide the Canonical URL meta box from post and page edit screens. This removes the canonical URL override field from the editor interface.',
1092 'disable_social_opengraph_metabox' => 'Hide the Social Media & Open Graph meta box from post and page edit screens. This removes Facebook, Twitter, and other social media meta tag controls from the editor.',
1093 'disable_schema_markup_metabox' => 'Hide the Schema Markup meta box from post and page edit screens. This removes the structured data (Article, FAQ, Product, Recipe, etc.) configuration from the editor interface.',
1094 'disable_seo_metabox' => 'Hide the SEO Title & Meta Description meta box from post and page edit screens. This removes the Classic editor SEO fields (the Gutenberg sidebar is unaffected).',
1095 'open_external_links' => sprintf('Automatically add target="_blank" attribute to external links appearing in your posts, pages, and other post types when rendered by %s.', $otto_name),
1096 'content_genius_sync_roles' => 'Select which WordPress user roles should be synchronized with Content Genius. This determines which users will have their profiles and permissions synced for content collaboration.',
1097 'permalink_structure' => sprintf('Displays your current WordPress permalink structure. %s works best with pretty permalinks (not "Plain"). If you see a warning, visit Settings > Permalinks to change your structure.', $plugin_name),
1098 'hide_dashboard_framework' => sprintf('Hide the main %s dashboard from the WordPress admin menu. This is useful if you want to reduce menu clutter but still keep the plugin active.', $plugin_name),
1099 'show_admin_bar_status' => sprintf('Display the %s status indicator in the WordPress admin bar at the top of your screen. This provides quick visibility of plugin status and key metrics.', $plugin_name),
1100 'enable_auto_updates' => sprintf('Allow WordPress to automatically update the %s plugin when new versions are released. Recommended for security patches, but you may prefer manual updates for major versions.', $plugin_name),
1101 'import_external_data' => sprintf('Import your existing SEO settings and metadata from other popular SEO plugins like Yoast, Rank Math, or All in One SEO. This makes migration to %s seamless without losing your SEO data.', $plugin_name),
1102 'import_seo_metadata' => sprintf('Migrate your existing SEO titles and meta descriptions from Yoast, Rank Math, or All in One SEO. This one-click import preserves your search rankings by copying your optimized meta data to %s, even if the source plugin is deactivated.', $plugin_name),
1103 'default_page_builder' => 'Choose how Content Genius stores synced articles. "Gutenberg" works with all themes and page builders. Selecting a specific builder converts content to that builder\'s widget format, which inherits the builder\'s global typography and layout styles.',
1104 'og_image_dimensions' => 'Adds your share image\'s width, height, and file type to the Open Graph tags so Facebook, LinkedIn, and others render your preview instantly at the right size instead of guessing. Leave on unless another plugin already outputs these.',
1105 'article_timestamps' => 'Tells social platforms and search engines when each post was first published and last updated, so shares and rich results show accurate dates. Safe to leave enabled.',
1106 'article_author' => 'Adds a link to the author\'s profile or archive page to each post\'s social tags, so shared articles can show author attribution. You can override the author URL per-post. Turn off if you\'d rather not expose author pages.',
1107 'article_section' => 'Labels each post with its main topic (its primary category) in the social tags, helping platforms categorize your content. Falls back to the first category if no primary one is set.',
1108 'article_tags' => 'Adds each of your WordPress post tags as a separate social "tag" so platforms understand the topics a post covers. Turn off if you don\'t want your tags exposed in share metadata.',
1109 'twitter_image_alt' => 'Adds descriptive alt text to the image shown when your page is shared on X/Twitter, improving accessibility for screen-reader users. Uses your saved alt text or the image\'s own alt attribute.'
1110 );
1111 }
1112
1113 public function get_field_section($field_id) {
1114 $sections = $this->get_accordion_sections_config();
1115
1116 foreach ($sections as $section_key => $section_data) {
1117 if (in_array($field_id, $section_data['fields'])) {
1118 return $section_key;
1119 }
1120 }
1121
1122 return null;
1123 }
1124
1125 public function render_accordion_sections($page) {
1126 global $wp_settings_sections, $wp_settings_fields;
1127
1128 if (!isset($wp_settings_fields[$page])) {
1129 return;
1130 }
1131
1132 $sections_config = $this->get_accordion_sections_config();
1133
1134 echo '<div class="metasync-settings-accordion">';
1135
1136 uasort($sections_config, function($a, $b) {
1137 return $a['priority'] - $b['priority'];
1138 });
1139
1140 foreach ($sections_config as $section_key => $section_data) {
1141 $section_id = 'metasync-section-' . $section_key;
1142 $is_open = $section_data['default_open'];
1143 $aria_expanded = $is_open ? 'true' : 'false';
1144 $content_state = $is_open ? 'open' : 'closed';
1145
1146 echo '<div class="metasync-accordion-section" data-section="' . esc_attr($section_key) . '">';
1147
1148 echo '<div class="metasync-accordion-header" role="button" tabindex="0" aria-expanded="' . $aria_expanded . '" aria-controls="' . $section_id . '">';
1149 echo '<div class="metasync-accordion-title">';
1150 echo '<span class="metasync-accordion-icon"><span class="dashicons dashicons-' . esc_attr($section_data['icon']) . '"></span></span>';
1151 echo '<div class="metasync-accordion-text">';
1152 echo '<h3>' . esc_html($section_data['title']) . '</h3>';
1153 echo '<p class="metasync-accordion-description">' . esc_html($section_data['description']) . '</p>';
1154 echo '</div>';
1155 echo '</div>';
1156 echo '<button type="button" class="metasync-accordion-toggle" aria-label="Toggle section">';
1157 echo '<span class="toggle-icon">▼</span>';
1158 echo '</button>';
1159 echo '</div>';
1160
1161 echo '<div class="metasync-accordion-content" id="' . $section_id . '" data-state="' . $content_state . '">';
1162
1163 if (isset($section_data['render_callback']) && is_callable($section_data['render_callback'])) {
1164 call_user_func($section_data['render_callback']);
1165 } elseif (isset($section_data['fields']) && is_array($section_data['fields'])) {
1166 echo '<table class="form-table" role="presentation">';
1167
1168 $tooltips = $this->get_field_tooltips();
1169
1170 foreach ($section_data['fields'] as $field_id) {
1171 if (isset($wp_settings_fields[$page]['metasync_settings'][$field_id])) {
1172 $field = $wp_settings_fields[$page]['metasync_settings'][$field_id];
1173
1174 echo '<tr>';
1175 echo '<th scope="row">';
1176 if (!empty($field['title'])) {
1177 echo '<div class="metasync-field-label-wrapper">';
1178 echo '<label for="' . esc_attr($field['id']) . '">' . $field['title'] . '</label>';
1179
1180 if (isset($tooltips[$field_id])) {
1181 echo '<button type="button" class="metasync-tooltip-trigger" data-tooltip-id="' . esc_attr($field_id) . '" aria-label="More information">';
1182 echo '<svg class="metasync-info-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
1183 echo '<circle cx="12" cy="12" r="10"></circle>';
1184 echo '<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>';
1185 echo '<line x1="12" y1="17" x2="12.01" y2="17"></line>';
1186 echo '</svg>';
1187 echo '</button>';
1188
1189 echo '<div class="metasync-tooltip" id="tooltip-' . esc_attr($field_id) . '" role="tooltip">';
1190 echo '<div class="metasync-tooltip-arrow"></div>';
1191 echo '<div class="metasync-tooltip-content">' . esc_html($tooltips[$field_id]) . '</div>';
1192 echo '</div>';
1193 }
1194
1195 echo '</div>';
1196 }
1197 echo '</th>';
1198 echo '<td>';
1199 call_user_func($field['callback'], $field['args']);
1200 echo '</td>';
1201 echo '</tr>';
1202 }
1203 }
1204
1205 echo '</table>';
1206 }
1207
1208 echo '</div>';
1209 echo '</div>';
1210 }
1211
1212 echo '</div>';
1213 }
1214
1215 public function render_llms_txt_section() {
1216 global $wp_settings_fields;
1217
1218 $page = Metasync_Admin::$page_slug . '_general';
1219 $section = Metasync_Admin::SECTION_LLMS_TXT;
1220
1221 if (empty($wp_settings_fields[$page][$section])) {
1222 return;
1223 }
1224
1225 echo '<table class="form-table" role="presentation">';
1226 $llms_tooltips = array(
1227 'llms_txt_enabled' => 'Publishes a file at yoursite.com/llms.txt listing your key pages so AI assistants like ChatGPT and Perplexity can find and cite your content. Safe to leave off if you don\'t want AI crawlers guided to your pages.',
1228 'llms_full_enabled' => 'Also publishes a larger file with the full text of your posts (not just links). This exposes complete article content and can be large — keep the post limit low on smaller hosting plans.',
1229 );
1230 foreach ($wp_settings_fields[$page][$section] as $field) {
1231 echo '<tr>';
1232 echo '<th scope="row">';
1233 if (!empty($field['title'])) {
1234 echo '<div class="metasync-field-label-wrapper">';
1235 echo '<label for="' . esc_attr($field['id']) . '">' . $field['title'] . '</label>';
1236 if (isset($llms_tooltips[$field['id']])) {
1237 Metasync::render_tooltip_icon($field['id'], $llms_tooltips[$field['id']]);
1238 }
1239 echo '</div>';
1240 }
1241 echo '</th>';
1242 echo '<td>';
1243 call_user_func($field['callback'], $field['args']);
1244 echo '</td>';
1245 echo '</tr>';
1246 }
1247 echo '</table>';
1248 ?>
1249 <script>
1250 (function(){
1251 function toggleLlms(){
1252 var enabled = document.getElementById('llms_txt_enabled');
1253 var fullEnabled = document.getElementById('llms_full_enabled');
1254 var deps = document.querySelectorAll('.llms-txt-dependent');
1255 var fullDeps = document.querySelectorAll('.llms-full-dependent');
1256 var i;
1257 for(i = 0; i < deps.length; i++){
1258 var row = deps[i].closest('tr');
1259 if(row) row.style.display = enabled && enabled.checked ? '' : 'none';
1260 }
1261 for(i = 0; i < fullDeps.length; i++){
1262 var row = fullDeps[i].closest('tr');
1263 if(row) row.style.display = (enabled && enabled.checked && fullEnabled && fullEnabled.checked) ? '' : 'none';
1264 }
1265 }
1266 document.addEventListener('DOMContentLoaded', function(){
1267 var enabled = document.getElementById('llms_txt_enabled');
1268 var fullEnabled = document.getElementById('llms_full_enabled');
1269 if(enabled) enabled.addEventListener('change', toggleLlms);
1270 if(fullEnabled) fullEnabled.addEventListener('change', toggleLlms);
1271 toggleLlms();
1272 });
1273 })();
1274 </script>
1275 <?php
1276 }
1277
1278 // ────────────────────────────────────────────────────────────────
1279 // Settings field callbacks
1280 // ────────────────────────────────────────────────────────────────
1281
1282 public function metasync_settings_genkey_callback()
1283 {
1284 $current_token = Metasync::get_option('general')['apikey'] ?? '';
1285
1286 if (!empty($current_token)) {
1287 $display_value = $current_token;
1288 $status_message = 'Plugin Auth Token is active and ready for authentication.';
1289 $refresh_help = 'Click refresh to generate a new token and update the heartbeat API.';
1290 } else {
1291 $display_value = 'Auto-generated when connecting to ' . esc_html(Metasync::get_effective_plugin_name());
1292 $status_message = 'Plugin Auth Token will be automatically generated when you click "Connect to ' . esc_html(Metasync::get_effective_plugin_name()) . '".';
1293 $refresh_help = 'You can also manually generate a token by clicking refresh.';
1294 }
1295
1296 printf('<div class="metasync-auth-token-row">');
1297 printf(
1298 '<input type="text" id="apikey" name="' . Metasync_Admin::option_key . '[general][apikey]" value="%s" size="40" class="regular-text" readonly="readonly" />',
1299 esc_attr($display_value)
1300 );
1301 printf('<button type="button" id="refresh-plugin-auth-token" class="button button-secondary metasync-refresh-token-btn"><span class="dashicons dashicons-controls-repeat"></span> Refresh Token</button>');
1302 printf('</div>');
1303 printf('<p class="description">%s %s</p>', $status_message, $refresh_help);
1304 }
1305
1306 public function linkgraph_token_callback()
1307 {
1308 printf(
1309 '<input type="text" id="linkgraph_token" name="' . Metasync_Admin::option_key . '[general][linkgraph_token]" value="%s" size="25" readonly="readonly" />',
1310 isset(Metasync::get_option('general')['linkgraph_token']) ? esc_attr(Metasync::get_option('general')['linkgraph_token']) : ''
1311 );
1312
1313 printf(
1314 '<input type="text" id="linkgraph_customer_id" name="' . Metasync_Admin::option_key . '[general][linkgraph_customer_id]" value="%s" size="25" readonly="readonly" />',
1315 isset(Metasync::get_option('general')['linkgraph_customer_id']) ? esc_attr(Metasync::get_option('general')['linkgraph_customer_id']) : ''
1316 );
1317
1318 ?>
1319 <button type="button" class="button button-primary" id="lgloginbtn">Fetch Token</button>
1320 <input type="text" id="lgusername" class="input lguser hidden" placeholder="username" />
1321 <input type="text" id="lgpassword" class="input lguser hidden" placeholder="password" />
1322 <p id="lgerror" class="notice notice-error hidden" style="display: none;"></p>
1323 <?php
1324 }
1325
1326
1327 public function time_elapsed_string($datetime, $full = false)
1328 {
1329 if(empty($datetime)){
1330 return "";
1331 }
1332 # Stored timestamps use current_time('mysql') which is the WP site's
1333 # local timezone (no offset marker on the string). Constructing
1334 # DateTime without a timezone would default to UTC and produce a
1335 # bogus diff equal to the WP timezone offset (QA: "4 hours ago"
1336 # after a 4-minute sync on non-UTC sites).
1337 $wp_tz = function_exists('wp_timezone') ? wp_timezone() : new DateTimeZone('UTC');
1338 $now = new DateTime('now', $wp_tz);
1339 $ago = new DateTime($datetime, $wp_tz);
1340
1341 $diff = $now->diff($ago);
1342
1343 $string = [
1344 'y' => 'year',
1345 'm' => 'month',
1346 'w' => 'week',
1347 'd' => 'day',
1348 'h' => 'hour',
1349 'i' => 'minute',
1350 's' => 'second',
1351 ];
1352
1353 foreach ($string as $k => &$v) {
1354 if (isset($diff->$k) && $diff->$k) {
1355 $v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : '');
1356 } else {
1357 unset($string[$k]);
1358 }
1359 }
1360 if (!$full) $string = array_slice($string, 0, 1);
1361 return $string ? implode(', ', $string) . ' ago' : 'just now';
1362 }
1363
1364 public function searchatlas_api_key_callback()
1365 {
1366 // Whether a key is stored at rest (encrypted or legacy plaintext).
1367 $stored_api_key = Metasync::get_option('general')['searchatlas_api_key'] ?? '';
1368 $has_api_key = ($stored_api_key !== '');
1369
1370 // Decrypted key for masking only. Never rendered in cleartext.
1371 // false => an encrypted key exists but cannot be decrypted (salts changed).
1372 $decrypted_api_key = Metasync::get_searchatlas_api_key();
1373 $decrypt_failed = ($decrypted_api_key === false);
1374
1375 // Build a masked representation: bullets + last 4 chars. The cleartext
1376 // key is never emitted into the page.
1377 $masked_api_key = '';
1378 if (!$decrypt_failed && $decrypted_api_key !== '') {
1379 $masked_api_key = str_repeat('*', 8) . substr($decrypted_api_key, -4);
1380 }
1381
1382 $otto_uuid = isset(Metasync::get_option('general')['otto_pixel_uuid']) ? Metasync::get_option('general')['otto_pixel_uuid'] : '';
1383
1384 $has_otto_uuid = !empty($otto_uuid);
1385
1386 $is_fully_connected = $this->admin_instance->is_heartbeat_connected();
1387
1388 printf('<div class="metasync-sa-connect-container">');
1389
1390 printf('<div class="metasync-sa-connect-title">');
1391 printf('One-Click Authentication');
1392 printf('</div>');
1393
1394 printf('<div class="metasync-sa-connect-description">');
1395 if ($is_fully_connected) {
1396 printf('Your %s account is fully synced with active heartbeat API. You can re-authenticate to refresh your connection or connect a different account.', esc_html(Metasync::get_effective_plugin_name()));
1397 } elseif ($has_api_key && !$has_otto_uuid) {
1398 printf('Your %s API key is configured, but %s UUID is missing. Please re-authenticate to complete the setup.', esc_html(Metasync::get_effective_plugin_name()), esc_html(Metasync::get_whitelabel_otto_name()));
1399 } else {
1400 printf('Connect your %s account with one click. This will automatically configure your API key and %s UUID below, enabling all plugin features.', esc_html(Metasync::get_effective_plugin_name()), esc_html(Metasync::get_whitelabel_otto_name()));
1401 }
1402 printf('</div>');
1403
1404 ?>
1405 <div class="metasync-mcp-consent" style="margin-top: 15px; padding: 12px 15px; background: #f0f9ff; border-left: 3px solid #0ea5e9; border-radius: 4px;">
1406 <p style="margin: 0; color: #334155; font-size: 13px; line-height: 1.6;">
1407 <strong>AI-Powered SEO Automation:</strong> By authenticating, you authorize <strong><?php echo esc_html(Metasync::get_effective_plugin_name()); ?> Brain</strong> (our AI assistant) to access your WordPress admin capabilities through the Model Context Protocol (MCP). This enables intelligent automation for SEO optimizations, content enhancements, and performance improvements.
1408 </p>
1409 </div>
1410 <?php
1411
1412 printf('<div class="metasync-sa-connect-buttons">');
1413
1414 printf('<button type="button" id="connect-searchatlas-btn" class="metasync-sa-connect-btn">');
1415 if ($is_fully_connected) {
1416 printf('Re-authenticate with %s', esc_html(Metasync::get_effective_plugin_name()));
1417 } elseif ($has_api_key && !$has_otto_uuid) {
1418 printf('Complete Authentication Setup');
1419 } else {
1420 printf('Connect to %s', esc_html(Metasync::get_effective_plugin_name()));
1421 }
1422 printf('</button>');
1423
1424 if ($has_api_key) {
1425 printf('<button type="button" id="reset-searchatlas-auth" class="metasync-sa-reset-btn" style="margin-left: 10px;">');
1426 printf('Disconnect Account');
1427 printf('</button>');
1428 }
1429
1430 printf('</div>');
1431
1432 printf('<div style="margin-top: 15px;">');
1433 printf('<details style="margin-top: 10px;">');
1434 printf('<summary style="cursor: pointer; color: #666; font-size: 13px;">Authentication Tips</summary>');
1435 printf('<div style="padding: 10px 0; color: #666; font-size: 13px; line-height: 1.5;">');
1436 printf('• Make sure you have a %s account before connecting<br/>', esc_html(Metasync::get_effective_plugin_name()));
1437 printf('• The authentication window will open in a popup - please allow popups<br/>');
1438 printf('• The process typically takes 15-30 seconds to complete<br/>');
1439 printf('• Your API key will be stored securely and shown masked below<br/>');
1440 printf('• If you encounter issues, try disabling ad blockers temporarily<br/>');
1441 printf('• Contact <a href="mailto:%s">%s</a> if you need assistance', Metasync::SUPPORT_EMAIL, Metasync::SUPPORT_EMAIL);
1442 printf('</div>');
1443 printf('</details>');
1444 printf('</div>');
1445
1446 printf('</div>');
1447
1448 printf('<div style="margin-top: 20px;">');
1449 printf('<label for="searchatlas-api-key" style="font-weight: 600; display: block; margin-bottom: 8px;">');
1450 printf('%s API Key', esc_html(Metasync::get_effective_plugin_name()));
1451 if ($is_fully_connected) {
1452 printf('<span style="color: #46b450; margin-left: 10px; font-weight: normal;">✓ Synced</span>');
1453 } elseif ($has_api_key && !$has_otto_uuid) {
1454 printf('<span style="color: #ff8c00; margin-left: 10px; font-weight: normal;">Partial Connection (Missing %s UUID)</span>', esc_html(Metasync::get_whitelabel_otto_name()));
1455 }
1456 printf('</label>');
1457
1458 // Read-only masked display. The API key is managed via one-click
1459 // authentication only; the cleartext key is never rendered into the
1460 // page HTML (View Source / DOM). No form input is emitted, so saving
1461 // settings cannot overwrite or expose the stored key.
1462 $display_has_key = (!$decrypt_failed && $masked_api_key !== '') ? '1' : '0';
1463 printf(
1464 '<div id="searchatlas-api-key-display" class="metasync-api-key-masked" data-has-key="%s" style="display:inline-flex; align-items:center; min-width:320px; height:42px; padding:0 14px; font-family:monospace; font-size:15px; letter-spacing:4px; background:var(--dashboard-input-bg, #22272e); border:1px solid var(--dashboard-border, #3a424d); border-radius:6px; color:var(--dashboard-text-primary, #e6e8eb); cursor:default; user-select:none;">',
1465 esc_attr($display_has_key)
1466 );
1467 if ($decrypt_failed) {
1468 printf('<em style="color:#b32d2e; font-family:sans-serif; letter-spacing:normal; font-style:normal; font-size:14px;">Stored API key could not be read. Please re-authenticate above.</em>');
1469 } elseif ($masked_api_key !== '') {
1470 echo esc_html($masked_api_key);
1471 } else {
1472 printf('<em style="color:#646970;">Not configured</em>');
1473 }
1474 printf('</div>');
1475
1476 printf('<p class="description" style="margin-top: 8px;">');
1477 if ($decrypt_failed) {
1478 printf('Your stored API key could not be decrypted (your site security keys may have changed). Please re-authenticate above to reconnect.');
1479 } elseif ($is_fully_connected) {
1480 printf('Your %s API key is stored securely and used for communication with the platform. Use the authentication button above to refresh or change accounts.', esc_html(Metasync::get_effective_plugin_name()));
1481 } elseif ($has_api_key && !$has_otto_uuid) {
1482 printf('Your API key is configured but %s UUID is missing. Re-authenticate above to complete the setup and enable dashboard access.', esc_html(Metasync::get_whitelabel_otto_name()));
1483 } else {
1484 printf('Use the authentication button above to securely connect your account. Your API key is stored encrypted and shown masked here.');
1485 }
1486 printf('</p>');
1487
1488 printf('</div>');
1489
1490 if( isset(Metasync::get_option('general')['searchatlas_api_key'])&&Metasync::get_option('general')['searchatlas_api_key']!=''){
1491 $timestamp = @Metasync::get_option('general')['send_auth_token_timestamp'];
1492 printf(
1493 '<p id="sendAuthTokenTimestamp" class="descriptionValue">%s (%s)</p>',
1494 esc_attr($timestamp),
1495 $this->time_elapsed_string($timestamp)
1496 );
1497
1498
1499 }
1500 }
1501
1502 public function bing_site_verification_callback()
1503 {
1504 printf(
1505 '<input type="text" id="bing_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][bing_site_verification]" value="%s" size="50" />',
1506 isset(Metasync::get_option('searchengines')['bing_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['bing_site_verification']) : ''
1507 );
1508
1509 printf(' <br> <span class="description"> Enter Bing Webmaster Tools verification code: </span> ');
1510 printf(' <a href="https://www.bing.com/webmasters/about" target="_blank">Get from here</a> <br> ');
1511 }
1512
1513 public function yandex_site_verification_callback()
1514 {
1515 printf(
1516 '<input type="text" id="yandex_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][yandex_site_verification]" value="%s" size="50" />',
1517 isset(Metasync::get_option('searchengines')['yandex_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['yandex_site_verification']) : ''
1518 );
1519
1520 printf(' <br> <span class="description"> Enter Yandex verification code: </span>');
1521 printf(' <a href="https://passport.yandex.com/auth" target="_blank">Get from here</a> <br> ');
1522 }
1523
1524 public function google_site_verification_callback()
1525 {
1526 printf(
1527 '<input type="text" id="google_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][google_site_verification]" value="%s" size="50" />',
1528 isset(Metasync::get_option('searchengines')['google_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['google_site_verification']) : ''
1529 );
1530
1531 printf(' <br> <span class="description"> Enter Google Search Console verification code: </span>');
1532 printf(' <a href="https://www.google.com/webmasters/verification" target="_blank">Get from here</a> <br> ');
1533 }
1534
1535 public function pinterest_site_verification_callback()
1536 {
1537 printf(
1538 '<input type="text" id="pinterest_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][pinterest_site_verification]" value="%s" size="50" />',
1539 isset(Metasync::get_option('searchengines')['pinterest_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['pinterest_site_verification']) : ''
1540 );
1541
1542 printf(' <br> <span class="description"> Enter Pinterest verification code: </span>');
1543 printf(' <a href="https://in.pinterest.com/" target="_blank">Get from here</a> <br> ');
1544 }
1545
1546 public function local_seo_person_organization_callback()
1547 {
1548 $person_organization = Metasync::get_option('localseo')['local_seo_person_organization'] ?? '';
1549 ?>
1550 <div class="metasync-type-select-wrap">
1551 <select id="local_seo_person_organization" name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][local_seo_person_organization]') ?>">
1552 <?php
1553 printf('<option value="Person" %s >Person</option>', selected('Person', esc_attr($person_organization)));
1554 printf('<option value="Organization" %s >Organization</option>', selected('Organization', esc_attr($person_organization)));
1555 ?>
1556 </select>
1557 </div>
1558 <?php
1559 printf(' <br> <span class="description"> Choose whether the site represents a person or an organization. </span>');
1560 }
1561
1562 public function local_seo_name_callback()
1563 {
1564 printf(
1565 '<input type="text" id="local_seo_name" name="' . Metasync_Admin::option_key . '[localseo][local_seo_name]" value="%s" size="50" />',
1566 isset(Metasync::get_option('localseo')['local_seo_name']) ? esc_attr(Metasync::get_option('localseo')['local_seo_name']) : get_bloginfo()
1567 );
1568
1569 printf(' <br> <span class="description"> Your name or company name </span>');
1570 }
1571
1572 public function local_seo_logo_callback()
1573 {
1574 $local_seo_logo = Metasync::get_option('localseo')['local_seo_logo'] ?? '';
1575
1576 printf(
1577 '<input type="hidden" id="local_seo_logo" name="' . Metasync_Admin::option_key . '[localseo][local_seo_logo]" value="%s" size="50" />',
1578 isset(Metasync::get_option('localseo')['local_seo_logo']) ? esc_attr(Metasync::get_option('localseo')['local_seo_logo']) : ''
1579 );
1580
1581 printf(' <br> <input class="button-secondary no-loading" type="button" id="logo_upload_button" value="Add or Upload File">');
1582
1583 printf(' <br><br> <span class="description bold"> Min Size: 160Χ90px, Max Size: 1920X1080px. </span> <br> <span class="description"> A squared image is preferred by the search engines. </span> <br><br> ');
1584
1585 printf('<span class="metasync-logo-preview-wrap">');
1586 printf('<img src="%s" id="local_seo_business_logo" width="300">', wp_get_attachment_image_src($local_seo_logo, 'medium')[0] ?? '');
1587
1588 $button_type = 'hidden';
1589 if ($local_seo_logo) {
1590 $button_type = 'button';
1591 }
1592 printf('<input type="%s" class="button-secondary no-loading metasync-logo-remove-btn" id="local_seo_logo_close_btn" value="X">', $button_type);
1593 printf('</span>');
1594 }
1595
1596 public function local_seo_url_callback()
1597 {
1598 printf(
1599 '<input type="text" id="local_seo_url" name="' . Metasync_Admin::option_key . '[localseo][local_seo_url]" value="%s" size="50" />',
1600 isset(Metasync::get_option('localseo')['local_seo_url']) ? esc_attr(Metasync::get_option('localseo')['local_seo_url']) : home_url()
1601 );
1602
1603 printf(' <br> <span class="description"> URL of the item. </span>');
1604 }
1605
1606 public function local_seo_email_callback()
1607 {
1608 printf(
1609 '<input type="text" id="local_seo_email" name="' . Metasync_Admin::option_key . '[localseo][local_seo_email]" value="%s" size="50" />',
1610 isset(Metasync::get_option('localseo')['local_seo_email']) ? esc_attr(Metasync::get_option('localseo')['local_seo_email']) : ''
1611 );
1612
1613 printf(' <br> <span class="description"> Search engines display your email address. </span>');
1614 }
1615
1616 public function local_seo_phone_callback()
1617 {
1618 printf(
1619 '<input type="text" id="local_seo_phone" name="' . Metasync_Admin::option_key . '[localseo][local_seo_phone]" value="%s" size="50" />',
1620 isset(Metasync::get_option('localseo')['local_seo_phone']) ? esc_attr(Metasync::get_option('localseo')['local_seo_phone']) : ''
1621 );
1622
1623 printf(' <br> <span class="description"> Search engines may prominently display your contact phone number for mobile users. </span>');
1624 }
1625
1626 public function local_seo_address_callback()
1627 {
1628 printf(
1629 '<input type="text" id="local_seo_address_street" name="' . Metasync_Admin::option_key . '[localseo][address][street]" value="%s" size="50" placeholder="Street Address"/> <br>',
1630 isset(Metasync::get_option('localseo')['address']['street']) ? esc_attr(Metasync::get_option('localseo')['address']['street']) : ''
1631 );
1632
1633 printf(
1634 '<input type="text" id="local_seo_address_locality" name="' . Metasync_Admin::option_key . '[localseo][address][locality]" value="%s" size="50" placeholder="Locality"/> <br>',
1635 isset(Metasync::get_option('localseo')['address']['locality']) ? esc_attr(Metasync::get_option('localseo')['address']['locality']) : ''
1636 );
1637
1638 printf(
1639 '<input type="text" id="local_seo_address_region" name="' . Metasync_Admin::option_key . '[localseo][address][region]" value="%s" size="50" placeholder="Region"/> <br>',
1640 isset(Metasync::get_option('localseo')['address']['region']) ? esc_attr(Metasync::get_option('localseo')['address']['region']) : ''
1641 );
1642
1643 printf(
1644 '<input type="text" id="local_seo_address_postalcode" name="' . Metasync_Admin::option_key . '[localseo][address][postalcode]" value="%s" size="50" placeholder="Postal Code"/> <br>',
1645 isset(Metasync::get_option('localseo')['address']['postalcode']) ? esc_attr(Metasync::get_option('localseo')['address']['postalcode']) : ''
1646 );
1647
1648 printf(
1649 '<input type="text" id="local_seo_address_country" name="' . Metasync_Admin::option_key . '[localseo][address][country]" value="%s" size="50" placeholder="Country"/> <br>',
1650 isset(Metasync::get_option('localseo')['address']['country']) ? esc_attr(Metasync::get_option('localseo')['address']['country']) : ''
1651 );
1652 }
1653
1654 public function local_seo_business_type_callback()
1655 {
1656 $types = self::get_business_types();
1657 sort($types);
1658
1659 $business_type = Metasync::get_option('localseo')['local_seo_business_type'] ?? '';
1660
1661 ?>
1662 <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][local_seo_business_type]') ?>">
1663 <option value='0'>Select Business Type</option>
1664 <?php
1665 foreach ($types as $type) {
1666 printf('<option value="%s" %s >%s</option>', $type, selected($type, esc_attr($business_type)), $type);
1667 }
1668 ?>
1669 </select>
1670 <?php
1671 }
1672
1673 public function local_seo_opening_hours_callback()
1674 {
1675 $days_name = ['Monday', 'Tuseday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
1676 $days = isset(Metasync::get_option('localseo')['days']) ? Metasync::get_option('localseo')['days'] : '';
1677 $times = isset(Metasync::get_option('localseo')['times']) ? Metasync::get_option('localseo')['times'] : '';
1678
1679 ?>
1680 <ul id="daysTime">
1681 <?php
1682 $opening_days = [];
1683 if ($days && $times) {
1684 $opening_days = array_combine($days, $times);
1685 }
1686 foreach ($opening_days as $day_name => $day_time) {
1687 ?>
1688 <li>
1689 <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][days][]') ?>">
1690 <?php
1691 foreach ($days_name as $name) {
1692 printf('<option value="%s" %s >%s</option>', $name, selected(esc_attr($name), esc_attr($day_name)), esc_attr($name));
1693 }
1694 ?>
1695 </select>
1696 <input type="text" name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][times][]') ?>" value="<?php echo esc_attr($day_time) ?>">
1697 <button id="timeDelete">Delete</button>
1698 </li>
1699 <?php } ?>
1700 <?php if (empty($opening_days)) { ?>
1701 <li>
1702 <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][days][]') ?>">
1703 <?php
1704 foreach ($days_name as $name) {
1705 printf('<option value="%s" >%s</option>', esc_attr($name), esc_attr($name));
1706 }
1707 ?>
1708 </select>
1709 <input type="text" name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][times][]') ?>" value="">
1710 <button id="timeDelete">Delete</button>
1711 </li>
1712 <?php } ?>
1713 </ul>
1714 <?php
1715
1716 printf(' <input type="hidden" id="days_time_count" value="%s"/>', count($opening_days));
1717 printf(' <input class="button-secondary" type="button" id="addNewTime" value="Add Time">');
1718 printf(' <br> <span class="description"> Select opening hours. You can add multiple sets if you have different opening or closing hours on some days or if you have a mid-day break. Times are specified using 24:00 time. </span>');
1719 }
1720
1721 public function local_seo_phone_numbers_callback()
1722 {
1723 $number_types = ['Customer Service', 'Technical Support', 'Billing Support', 'Bill Payment', 'Sales', 'Reservations', 'Credit Card Support', 'Emergency', 'Baggage Tracking', 'Roadside Assistance', 'Package Tracking'];
1724 $types = isset(Metasync::get_option('localseo')['phonetype']) ? Metasync::get_option('localseo')['phonetype'] : '';
1725 $numbers = isset(Metasync::get_option('localseo')['phonenumber']) ? Metasync::get_option('localseo')['phonenumber'] : '';
1726
1727 ?>
1728
1729 <ul id="phone-numbers">
1730 <?php
1731 $phone_numbers = [];
1732 if ($types && $numbers) {
1733 $phone_numbers = array_combine($types, $numbers);
1734 }
1735 foreach ($phone_numbers as $phone_type => $phone_number) {
1736 ?>
1737 <li>
1738 <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][phonetype][]') ?>">
1739 <?php
1740 foreach ($number_types as $type) {
1741 printf('<option value="%s" %s >%s</option>', esc_attr($type), selected(esc_attr($type), esc_attr($phone_type)), esc_attr($type));
1742 }
1743 ?>
1744 </select>
1745 <input type="text" name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][phonenumber][]') ?>" value="<?php echo esc_attr($phone_number) ?>">
1746 <button id="number-delete">Delete</button>
1747 </li>
1748 <?php } ?>
1749 <?php if (empty($phone_numbers)) { ?>
1750 <li>
1751 <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][phonetype][]') ?>">
1752 <?php
1753 foreach ($number_types as $type) {
1754 printf('<option value="%s" >%s</option>', esc_attr($type), esc_attr($type));
1755 }
1756 ?>
1757 </select>
1758 <input type="text" name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][phonenumber][]') ?>" value="">
1759 <button id="number-delete">Delete</button>
1760 </li>
1761 <?php } ?>
1762 </ul>
1763 <?php
1764
1765 printf(' <input type="hidden" id="phone_number_count" value="%s"/>', count($phone_numbers));
1766 printf(' <input class="button-secondary" type="button" id="addNewNumber" value="Add Number">');
1767 printf(' <br> <span class="description"> Search engines may prominently display your contact phone number for mobile users. </span>');
1768 }
1769
1770 public function local_seo_price_range_callback()
1771 {
1772 printf(
1773 '<input type="text" id="local_seo_price_range" name="' . Metasync_Admin::option_key . '[localseo][local_seo_price_range]" value="%s" size="50" />',
1774 isset(Metasync::get_option('localseo')['local_seo_price_range']) ? esc_attr(Metasync::get_option('localseo')['local_seo_price_range']) : ''
1775 );
1776 printf(' <br> <span class="description"> The price range of the business, for example $$$. </span>');
1777 }
1778
1779 public function local_seo_about_page_callback()
1780 {
1781 ?>
1782 <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][local_seo_about_page]') ?>">
1783 <option value='0'>Select About Page</option>
1784 <?php
1785 $about_page = Metasync::get_option('localseo')['local_seo_about_page'] ?? '';
1786 $pages = get_pages();
1787 foreach ($pages as $page) {
1788 printf('<option value="%s" %s >%s</option>', $page->ID, selected($page->ID, esc_attr($about_page)), $page->post_title);
1789 }
1790 ?>
1791 </select>
1792 <?php
1793 printf(' <br> <span class="description"> Search engines tag your about us page. </span>');
1794 }
1795
1796 public function local_seo_contact_page_callback()
1797 {
1798 ?>
1799 <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][local_seo_contact_page]') ?>">
1800 <option value='0'>Select Contact Page</option>
1801 <?php
1802 $contact_page = Metasync::get_option('localseo')['local_seo_contact_page'] ?? '';
1803 $pages = get_pages();
1804 foreach ($pages as $page) {
1805 printf('<option value="%s" %s >%s</option>', $page->ID, selected($page->ID, esc_attr($contact_page)), $page->post_title);
1806 }
1807 ?>
1808 </select>
1809 <?php
1810 printf(' <br> <span class="description"> Search engines tag your contact page. </span>');
1811 }
1812
1813 public function local_seo_map_key_callback()
1814 {
1815 printf(
1816 '<input type="text" id="local_seo_map_key" name="' . Metasync_Admin::option_key . '[localseo][local_seo_map_key]" value="%s" size="50" />',
1817 isset(Metasync::get_option('localseo')['local_seo_map_key']) ? esc_attr(Metasync::get_option('localseo')['local_seo_map_key']) : ''
1818 );
1819
1820 printf(' <br> <span class="description"> An API Key is required to display embedded Google Maps on your site. </span>');
1821 }
1822
1823 public function local_seo_geo_coordinates_callback()
1824 {
1825 printf(
1826 '<input type="text" id="local_seo_geo_coordinates" name="' . Metasync_Admin::option_key . '[localseo][local_seo_geo_coordinates]" value="%s" size="50" />',
1827 isset(Metasync::get_option('localseo')['local_seo_geo_coordinates']) ? esc_attr(Metasync::get_option('localseo')['local_seo_geo_coordinates']) : ''
1828 );
1829
1830 printf(' <br> <span class="description"> Latitude and longitude values separated by comma. </span>');
1831 }
1832
1833 public function header_snippets_callback()
1834 {
1835 printf(
1836 '<textarea class="wide-text" id="header_snippets" rows="8" name="' . Metasync_Admin::option_key . '[codesnippets][header_snippet]" >%s</textarea>',
1837 isset(Metasync::get_option('codesnippets')['header_snippet']) ? esc_attr(Metasync::get_option('codesnippets')['header_snippet']) : ''
1838 );
1839 }
1840
1841 public function footer_snippets_callback()
1842 {
1843 printf(
1844 '<textarea class="wide-text" id="footer_snippets" rows="8" name="' . Metasync_Admin::option_key . '[codesnippets][footer_snippet]" >%s</textarea>',
1845 isset(Metasync::get_option('codesnippets')['footer_snippet']) ? esc_attr(Metasync::get_option('codesnippets')['footer_snippet']) : ''
1846 );
1847 }
1848
1849 public function common_robot_meta_tags_callback()
1850 {
1851 $common_robots_meta = Metasync::get_option('common_robots_meta') ?? Metasync::get_option('common_robots_mata') ?? '';
1852
1853 ?>
1854 <ul class="checkbox-list">
1855 <li>
1856 <input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[common_robots_meta][noindex]') ?>" id="robots_common2" value="noindex" <?php isset($common_robots_meta['noindex']) ? checked('noindex', $common_robots_meta['noindex']) : '' ?>>
1857 <label for="robots_common2">No Index </br>
1858 <span class="description">
1859 <span>Search engines not indexed and displayed this pages in search engine results</span>
1860 </span>
1861 </label>
1862 </li>
1863 <li>
1864 <input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[common_robots_meta][nofollow]') ?>" id="robots_common3" value="nofollow" <?php isset($common_robots_meta['nofollow']) ? checked('nofollow', $common_robots_meta['nofollow']) : '' ?>>
1865 <label for="robots_common3">No Follow </br>
1866 <span class="description">
1867 <span>Search engines not follow the links on the pages</span>
1868 </span>
1869 </label>
1870 </li>
1871 <li>
1872 <input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[common_robots_meta][noarchive]') ?>" id="robots_common4" value="noarchive" <?php isset($common_robots_meta['noarchive']) ? checked('noarchive', $common_robots_meta['noarchive']) : '' ?>>
1873 <label for="robots_common4">No Archive </br>
1874 <span class="description">
1875 <span>Search engines not showing Cached links for pages</span>
1876 </span>
1877 </label>
1878 </li>
1879 <li>
1880 <input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[common_robots_meta][noimageindex]') ?>" id="robots_common5" value="noimageindex" <?php isset($common_robots_meta['noimageindex']) ? checked('noimageindex', $common_robots_meta['noimageindex']) : '' ?>>
1881 <label for="robots_common5">No Image Index </br>
1882 <span class="description">
1883 <span>If you do not want to apear your pages as the referring page for images that appear in image search results</span>
1884 </span>
1885 </label>
1886 </li>
1887 <li>
1888 <input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[common_robots_meta][nosnippet]') ?>" id="robots_common6" value="nosnippet" <?php isset($common_robots_meta['nosnippet']) ? checked('nosnippet', $common_robots_meta['nosnippet']) : '' ?>>
1889 <label for="robots_common6">No Snippet </br>
1890 <span class="description">
1891 <span>Search engines not snippet to show in the search results</span>
1892 </span>
1893 </label>
1894 </li>
1895 </ul>
1896 <?php
1897 }
1898
1899 /**
1900 * @deprecated Use common_robot_meta_tags_callback() instead
1901 */
1902 public function common_robot_mata_tags_callback()
1903 {
1904 return $this->common_robot_meta_tags_callback();
1905 }
1906
1907 public function advance_robot_meta_tags_callback()
1908 {
1909 $advance_robots_meta = Metasync::get_option('advance_robots_meta') ?? Metasync::get_option('advance_robots_mata') ?? '';
1910
1911 $snippet_advance_robots_enable = $advance_robots_meta['max-snippet']['enable'] ?? '';
1912 $snippet_advance_robots_length = $advance_robots_meta['max-snippet']['length'] ?? '-1';
1913 $video_advance_robots_enable = $advance_robots_meta['max-video-preview']['enable'] ?? '';
1914 $video_advance_robots_length = $advance_robots_meta['max-video-preview']['length'] ?? '-1';
1915 $image_advance_robots_enable = $advance_robots_meta['max-image-preview']['enable'] ?? '';
1916 $image_advance_robots_length = $advance_robots_meta['max-image-preview']['length'] ?? '';
1917
1918 ?>
1919 <ul class="checkbox-list">
1920 <li>
1921 <label for="advanced_robots_snippet">
1922 <input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[advance_robots_meta][max-snippet][enable]') ?>" id="advanced_robots_snippet" value="1" <?php checked('1', esc_attr($snippet_advance_robots_enable)) ?>>
1923 Snippet </br>
1924 <input type="number" class="input-length" name="<?php echo esc_attr(Metasync_Admin::option_key . '[advance_robots_meta][max-snippet][length]') ?>" id="advanced_robots_snippet_value" value="<?php echo esc_attr($snippet_advance_robots_length); ?>" min="-1"> </br>
1925 <span class="description">
1926 <span>Add maximum text-length, in characters, of a snippet for your page.</span>
1927 </span>
1928 </label>
1929 </li>
1930 <li>
1931 <label for="advanced_robots_video">
1932 <input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[advance_robots_meta][max-video-preview][enable]') ?>" id="advanced_robots_video" value="1" <?php checked('1', esc_attr($video_advance_robots_enable)) ?>>
1933 Video Preview </br>
1934 <input type="number" class="input-length" name="<?php echo esc_attr(Metasync_Admin::option_key . '[advance_robots_meta][max-video-preview][length]') ?>" id="advanced_robots_video_value" value="<?php echo esc_attr($video_advance_robots_length); ?>" min="-1"> </br>
1935 <span class="description">
1936 <span>Add maximum duration in seconds of an animated video preview.</span>
1937 </span>
1938 </label>
1939 </li>
1940 <li>
1941 <label for="advanced_robots_image">
1942 <input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[advance_robots_meta][max-image-preview][enable]') ?>" id="advanced_robots_image" value="1" <?php checked('1', esc_attr($image_advance_robots_enable)); ?>>
1943 Image Preview </br>
1944 <select class="input-length" name="<?php echo esc_attr(Metasync_Admin::option_key . '[advance_robots_meta][max-image-preview][length]') ?>" id="advanced_robots_image_value">
1945 <option value="large" <?php selected('large', esc_attr($image_advance_robots_length)) ?>>Large</option>
1946 <option value="standard" <?php selected('standard', esc_attr($image_advance_robots_length)) ?>>Standard</option>
1947 <option value="none" <?php selected('none', esc_attr($image_advance_robots_length)) ?>>None</option>
1948 </select>
1949 </br>
1950 <span class="description">
1951 <span>Add maximum size of image preview to show the images on this page.</span>
1952 </span>
1953 </label>
1954 </li>
1955 </ul>
1956 <?php
1957 }
1958
1959 /**
1960 * @deprecated Use advance_robot_meta_tags_callback() instead
1961 */
1962 public function advance_robot_mata_tags_callback()
1963 {
1964 return $this->advance_robot_meta_tags_callback();
1965 }
1966
1967 public function global_twitter_card_type_callback()
1968 {
1969 $twitter_card_type = Metasync::get_option('twitter_card_type') ?? '';
1970 ?>
1971
1972 <select class="input-length" name="<?php echo esc_attr(Metasync_Admin::option_key . '[twitter_card_type]') ?>" id="twitter_card_type">
1973 <option value="summary_large_image" <?php selected('summary_large_image', esc_attr($twitter_card_type)) ?>>Summary Large Image</option>
1974 <option value="summary_card" <?php selected('summary_card', esc_attr($twitter_card_type)) ?>>Summary Card</option>
1975 </select>
1976
1977 <?php
1978 }
1979
1980 public function global_open_graph_meta_callback()
1981 {
1982 printf(
1983 '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][open_graph_meta_tags]" value="true" %s />',
1984 isset(Metasync::get_option('common_meta_settings')['open_graph_meta_tags']) && Metasync::get_option('common_meta_settings')['open_graph_meta_tags'] == 'true' ? 'checked' : ''
1985 );
1986 printf(' <br> <span class="description"> Automatically add the Open Graph meta tags in a page or post.</span>');
1987 }
1988
1989 public function global_facebook_meta_callback()
1990 {
1991 printf(
1992 '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][facebook_meta_tags]" value="true" %s />',
1993 isset(Metasync::get_option('common_meta_settings')['facebook_meta_tags']) && Metasync::get_option('common_meta_settings')['facebook_meta_tags'] == 'true' ? 'checked' : ''
1994 );
1995 printf(' <br> <span class="description"> Automatically add the Facebook meta tags in a page or post.</span>');
1996 }
1997
1998 public function global_twitter_meta_callback()
1999 {
2000 printf(
2001 '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][twitter_meta_tags]" value="true" %s />',
2002 isset(Metasync::get_option('common_meta_settings')['twitter_meta_tags']) && Metasync::get_option('common_meta_settings')['twitter_meta_tags'] == 'true' ? 'checked' : ''
2003 );
2004 printf(' <br> <span class="description"> Automatically add the Twitter meta tags in a page or post.</span>');
2005 }
2006
2007 public function og_image_dimensions_callback()
2008 {
2009 $val = Metasync::get_option('common_meta_settings')['og_image_dimensions'] ?? 'true';
2010 printf(
2011 '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][og_image_dimensions]" value="true" %s />',
2012 $val === 'true' ? 'checked' : ''
2013 );
2014 printf('<span class="description"> Output og:image:width, og:image:height, and og:image:type when OG image metadata is available.</span>');
2015 }
2016
2017 public function article_timestamps_callback()
2018 {
2019 $val = Metasync::get_option('common_meta_settings')['article_timestamps'] ?? 'true';
2020 printf(
2021 '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_timestamps]" value="true" %s />',
2022 $val === 'true' ? 'checked' : ''
2023 );
2024 printf('<span class="description"> Output article:published_time and article:modified_time from the post publish and modified dates (ISO 8601).</span>');
2025 }
2026
2027 public function article_author_callback()
2028 {
2029 $val = Metasync::get_option('common_meta_settings')['article_author'] ?? 'true';
2030 printf(
2031 '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_author]" value="true" %s />',
2032 $val === 'true' ? 'checked' : ''
2033 );
2034 printf('<span class="description"> Output article:author using the post author website URL or archive URL (overridable per-post via _metasync_og_article_author meta).</span>');
2035 }
2036
2037 public function article_section_callback()
2038 {
2039 $val = Metasync::get_option('common_meta_settings')['article_section'] ?? 'true';
2040 printf(
2041 '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_section]" value="true" %s />',
2042 $val === 'true' ? 'checked' : ''
2043 );
2044 printf('<span class="description"> Output article:section from the primary category (falls back to first category).</span>');
2045 }
2046
2047 public function article_tags_callback()
2048 {
2049 $val = Metasync::get_option('common_meta_settings')['article_tags'] ?? 'true';
2050 printf(
2051 '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_tags]" value="true" %s />',
2052 $val === 'true' ? 'checked' : ''
2053 );
2054 printf('<span class="description"> Output one article:tag meta tag per WordPress post tag.</span>');
2055 }
2056
2057 public function twitter_image_alt_callback()
2058 {
2059 $val = Metasync::get_option('common_meta_settings')['twitter_image_alt'] ?? 'true';
2060 printf(
2061 '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][twitter_image_alt]" value="true" %s />',
2062 $val === 'true' ? 'checked' : ''
2063 );
2064 printf('<span class="description"> Output twitter:image:alt using the stored alt text or the OG image attachment alt attribute.</span>');
2065 }
2066
2067 public function facebook_page_url_callback()
2068 {
2069 $facebook_page_url = Metasync::get_option('social_meta')['facebook_page_url'] ?? '';
2070 printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_page_url]" value="%s" size="50" />', esc_attr($facebook_page_url));
2071 printf('<br><span class="description"> Enter your Facebook page URL. eg: <code>https://www.facebook.com/MetaSync/</code> </span>');
2072 }
2073
2074 public function facebook_authorship_callback()
2075 {
2076 $facebook_authorship = Metasync::get_option('social_meta')['facebook_authorship'] ?? '';
2077 printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_authorship]" value="%s" size="50" />', esc_attr($facebook_authorship));
2078 printf('<br><span class="description"> Enter Facebook profile URL to show Facebook Authorship when your articles are being shared on Facebook. eg: <code>https://www.facebook.com/shahrukh/</code> </span>');
2079 }
2080
2081 public function facebook_admin_callback()
2082 {
2083 $facebook_admin = Metasync::get_option('social_meta')['facebook_admin'] ?? '';
2084 printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_admin]" value="%s" size="50" />', esc_attr($facebook_admin));
2085 printf(' <br> <span class="description"> Enter numeric user ID of Facebook. </span>');
2086 }
2087
2088 public function facebook_app_callback()
2089 {
2090 $facebook_app = Metasync::get_option('social_meta')['facebook_app'] ?? '';
2091 printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_app]" value="%s" size="50" />', esc_attr($facebook_app));
2092 printf(' <br> <span class="description"> Enter numeric app ID of Facebook </span>');
2093 }
2094
2095 public function facebook_secret_callback()
2096 {
2097 $facebook_secret = Metasync::get_option('social_meta')['facebook_secret'] ?? '';
2098 printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_secret]" value="%s" size="50" />', esc_attr($facebook_secret));
2099 printf(' <br> <span class="description"> Enter alphanumeric access token from Facebook. </span>');
2100 }
2101
2102 public function twitter_username_callback()
2103 {
2104 $twitter_username = Metasync::get_option('social_meta')['twitter_username'] ?? '';
2105 printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][twitter_username]" value="%s" size="50" />', esc_attr($twitter_username));
2106 printf(' <br> <span class="description"> Twitter username of the author to add <code>twitter:creator</code> tag to post. eg: <code>MetaSync</code> </span>');
2107 }
2108
2109 public static function get_business_types()
2110 {
2111 $business_type = [
2112 'Airline',
2113 'Consortium',
2114 'Corporation',
2115 'Educational Organization',
2116 'College Or University',
2117 'Elementary School',
2118 'High School',
2119 'Middle School',
2120 'Preschool',
2121 'School',
2122 'Funding Scheme',
2123 'Government Organization',
2124 'Library System',
2125 'Local Business',
2126 'Animal Shelter',
2127 'Archive Organization',
2128 'Automotive Business',
2129 'Auto Body Shop',
2130 'Auto Dealer',
2131 'Auto Parts Store',
2132 'Auto Rental',
2133 'Auto Repair',
2134 'Auto Wash',
2135 'Gas Station',
2136 'Motorcycle Dealer',
2137 'Motorcycle Repair',
2138 'Child Care',
2139 'Dry Cleaning Or Laundry',
2140 'Emergency Service',
2141 'Fire Station',
2142 'Hospital',
2143 'Police Station',
2144 'Employment Agency',
2145 'Entertainment Business',
2146 'Adult Entertainment',
2147 'Amusement Park',
2148 'Art Gallery',
2149 'Casino',
2150 'Comedy Club',
2151 'Movie Theater',
2152 'Night Club',
2153 'Financial Service',
2154 'Accounting Service',
2155 'Automated Teller',
2156 'Bank Or CreditUnion',
2157 'Insurance Agency',
2158 'Food Establishment',
2159 'Bakery',
2160 'Bar Or Pub',
2161 'Brewery',
2162 'Cafe Or CoffeeShop',
2163 'Distillery',
2164 'Fast Food Restaurant',
2165 'IceCream Shop',
2166 'Restaurant',
2167 'Winery',
2168 'Government Office',
2169 'Post Office',
2170 'Health And Beauty Business',
2171 'Beauty Salon',
2172 'Day Spa',
2173 'Hair Salon',
2174 'Health Club',
2175 'Nail Salon',
2176 'Tattoo Parlor',
2177 'Home And Construction Business',
2178 'Electrician',
2179 'General Contractor',
2180 'HVAC Business',
2181 'House Painter',
2182 'Locksmith',
2183 'Moving Company',
2184 'Plumber',
2185 'Roofing Contractor',
2186 'Internet Cafe',
2187 'Legal Service',
2188 'Attorney',
2189 'Notary',
2190 'Library',
2191 'Lodging Business',
2192 'Bed And Breakfast',
2193 'Campground',
2194 'Hostel',
2195 'Hotel',
2196 'Motel',
2197 'Resort',
2198 'Ski Resort',
2199 'Medical Business',
2200 'Community Health',
2201 'Dentist',
2202 'Dermatology',
2203 'Diet Nutrition',
2204 'Emergency',
2205 'Geriatric',
2206 'Gynecologic',
2207 'Medical Clinic',
2208 'Optician',
2209 'Pharmacy',
2210 'Physician',
2211 'Professional Service',
2212 'Radio Station',
2213 'Real Estate Agent',
2214 'Recycling Center',
2215 'Self Storage',
2216 'Shopping Center',
2217 'Sports Activity Location',
2218 'Bowling Alley',
2219 'Exercise Gym',
2220 'Golf Course',
2221 'Public Swimming Pool',
2222 'Ski Resort',
2223 'Sports Club',
2224 'Stadium Or Arena',
2225 'Tennis Complex',
2226 'Store',
2227 'Bike Store',
2228 'Book Store',
2229 'Clothing Store',
2230 'Computer Store',
2231 'Convenience Store',
2232 'Department Store',
2233 'Electronics Store',
2234 'Florist',
2235 'Furniture Store',
2236 'Garden Store',
2237 'Grocery Store',
2238 'Hardware Store',
2239 'Hobby Shop',
2240 'Home Goods Store',
2241 'Jewelry Store',
2242 'Liquor Store',
2243 'Mens Clothing Store',
2244 'Mobile Phone Store',
2245 'Movie Rental Store',
2246 'Music Store',
2247 'Office Equipment Store',
2248 'Outlet Store',
2249 'Pawn Shop',
2250 'Pet Store',
2251 'Shoe Store',
2252 'Sporting GoodsStore',
2253 'Tire Shop',
2254 'Toy Store',
2255 'Wholesale Store',
2256 'Television Station',
2257 'Tourist Information Center',
2258 'Travel Agency',
2259 'Tree Services',
2260 'Medical Organization',
2261 'Diagnostic Lab',
2262 'Veterinary Care',
2263 'NGO',
2264 'News Media Organization',
2265 'Performing Group',
2266 'Dance Group',
2267 'Music Group',
2268 'Theater Group',
2269 'Project',
2270 'Funding Agency',
2271 'Research Project',
2272 'Sports Organization',
2273 'Sports Team',
2274 'Workers Union',
2275 ];
2276
2277 return $business_type;
2278 }
2279
2280 /**
2281 * Render breadcrumbs settings section
2282 */
2283 public function render_breadcrumbs_section() {
2284 $options = Metasync::get_option('breadcrumbs', array());
2285
2286 $defaults = array(
2287 'enabled' => true,
2288 'separator' => '&raquo;',
2289 'home_label' => 'Home',
2290 'home_url' => '',
2291 'show_current_page' => true,
2292 'prefix_text' => '',
2293 'archive_label_format' => '{name}',
2294 'disable_schema' => false,
2295 );
2296
2297 $options = wp_parse_args($options, $defaults);
2298 ?>
2299 <div style="background: var(--dashboard-card-bg); padding: 20px; border-radius: 8px;">
2300
2301 <!-- Enable/Disable -->
2302 <div style="margin-bottom: 24px;">
2303 <label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
2304 <input type="checkbox"
2305 name="metasync_options[breadcrumbs][enabled]"
2306 value="1"
2307 <?php checked($options['enabled'], 1); ?>
2308 style="width: 18px; height: 18px; cursor: pointer;">
2309 <span style="font-weight: 500; color: var(--dashboard-text);">Enable breadcrumbs globally</span>
2310 <?php Metasync::render_tooltip_icon('breadcrumbs_enabled', 'Shows a trail of links (e.g. Home > Blog > This Post) near the top of your pages so visitors can see where they are and navigate back up a level. Turn off to hide breadcrumbs everywhere.'); ?>
2311 </label>
2312 <p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);">
2313 Enable or disable breadcrumb navigation on your site
2314 </p>
2315 </div>
2316
2317 <!-- Disable Schema Markup -->
2318 <div style="margin-bottom: 24px;">
2319 <label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
2320 <input type="checkbox"
2321 name="metasync_options[breadcrumbs][disable_schema]"
2322 value="1"
2323 <?php checked($options['disable_schema'], 1); ?>
2324 style="width: 18px; height: 18px; cursor: pointer;">
2325 <span style="font-weight: 500; color: var(--dashboard-text);">Disable breadcrumb schema markup (JSON-LD)</span>
2326 <?php Metasync::render_tooltip_icon('breadcrumbs_disable_schema', 'Leave off. Only check this if another SEO plugin (Yoast, Rank Math, etc.) already sends breadcrumb data to Google, so you don\'t send it twice.'); ?>
2327 </label>
2328 <p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);">
2329 When checked, MetaSync will not output the <code style="background: rgba(0,0,0,0.1); padding: 2px 4px; border-radius: 3px;">BreadcrumbList</code> JSON-LD in <code style="background: rgba(0,0,0,0.1); padding: 2px 4px; border-radius: 3px;">&lt;head&gt;</code>. Use this if another SEO plugin already handles breadcrumb schema.
2330 </p>
2331 </div>
2332
2333 <!-- Separator Character -->
2334 <div style="margin-bottom: 24px;">
2335 <label for="breadcrumb_separator" style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--dashboard-text);">
2336 Separator Character
2337 </label>
2338 <div style="display: flex; gap: 12px; flex-wrap: wrap;">
2339 <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
2340 <input type="radio" name="metasync_options[breadcrumbs][separator]" value="»" <?php checked($options['separator'], '»'); ?>>
2341 <span style="color: var(--dashboard-text);">»</span>
2342 </label>
2343 <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
2344 <input type="radio" name="metasync_options[breadcrumbs][separator]" value="/" <?php checked($options['separator'], '/'); ?>>
2345 <span style="color: var(--dashboard-text);">/</span>
2346 </label>
2347 <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
2348 <input type="radio" name="metasync_options[breadcrumbs][separator]" value=">" <?php checked($options['separator'], '>'); ?>>
2349 <span style="color: var(--dashboard-text);">></span>
2350 </label>
2351 <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
2352 <input type="radio" name="metasync_options[breadcrumbs][separator]" value="·" <?php checked($options['separator'], '·'); ?>>
2353 <span style="color: var(--dashboard-text);">·</span>
2354 </label>
2355 </div>
2356 <p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);">
2357 Choose how to separate breadcrumb items
2358 </p>
2359 </div>
2360
2361 <!-- Home Label -->
2362 <div style="margin-bottom: 24px;">
2363 <label for="breadcrumb_home_label" style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--dashboard-text);">
2364 Home Label
2365 </label>
2366 <input type="text"
2367 id="breadcrumb_home_label"
2368 name="metasync_options[breadcrumbs][home_label]"
2369 value="<?php echo esc_attr($options['home_label']); ?>"
2370 placeholder="Home"
2371 style="width: 100%; padding: 10px 12px; background: var(--dashboard-input-bg); border: 1px solid var(--dashboard-border); border-radius: 6px; color: var(--dashboard-text); font-size: 14px; box-sizing: border-box;">
2372 <p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);">
2373 Label for the home/root breadcrumb item
2374 </p>
2375 </div>
2376
2377 <!-- Home URL -->
2378 <div style="margin-bottom: 24px;">
2379 <label for="breadcrumb_home_url" style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--dashboard-text);">
2380 Home URL
2381 </label>
2382 <input type="url"
2383 id="breadcrumb_home_url"
2384 name="metasync_options[breadcrumbs][home_url]"
2385 value="<?php echo esc_attr($options['home_url']); ?>"
2386 placeholder="<?php echo esc_attr(home_url('/')); ?>"
2387 style="width: 100%; padding: 10px 12px; background: var(--dashboard-input-bg); border: 1px solid var(--dashboard-border); border-radius: 6px; color: var(--dashboard-text); font-size: 14px; box-sizing: border-box;">
2388 <p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);">
2389 Leave empty to use your site home URL
2390 </p>
2391 </div>
2392
2393 <!-- Show Current Page -->
2394 <div style="margin-bottom: 24px;">
2395 <label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
2396 <input type="checkbox"
2397 name="metasync_options[breadcrumbs][show_current_page]"
2398 value="1"
2399 <?php checked($options['show_current_page'], 1); ?>
2400 style="width: 18px; height: 18px; cursor: pointer;">
2401 <span style="font-weight: 500; color: var(--dashboard-text);">Show current page</span>
2402 </label>
2403 <p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);">
2404 Include the current page as the last (unlinked) breadcrumb item
2405 </p>
2406 </div>
2407
2408 <!-- Prefix Text -->
2409 <div style="margin-bottom: 24px;">
2410 <label for="breadcrumb_prefix" style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--dashboard-text);">
2411 Prefix Text
2412 </label>
2413 <input type="text"
2414 id="breadcrumb_prefix"
2415 name="metasync_options[breadcrumbs][prefix_text]"
2416 value="<?php echo esc_attr($options['prefix_text']); ?>"
2417 placeholder='e.g., "You are here:"'
2418 style="width: 100%; padding: 10px 12px; background: var(--dashboard-input-bg); border: 1px solid var(--dashboard-border); border-radius: 6px; color: var(--dashboard-text); font-size: 14px; box-sizing: border-box;">
2419 <p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);">
2420 Optional text to display before the breadcrumbs (leave empty for none)
2421 </p>
2422 </div>
2423
2424 <!-- Archive Label Format -->
2425 <div style="margin-bottom: 24px;">
2426 <label for="breadcrumb_archive_format" style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--dashboard-text);">
2427 Archive Label Format
2428 <?php Metasync::render_tooltip_icon('breadcrumb_archive_format', 'On category/archive pages, {name} is replaced with that section\'s name. Type {name} alone for just the name, or something like "Category: {name}".'); ?>
2429 </label>
2430 <input type="text"
2431 id="breadcrumb_archive_format"
2432 name="metasync_options[breadcrumbs][archive_label_format]"
2433 value="<?php echo esc_attr($options['archive_label_format']); ?>"
2434 placeholder="{name}"
2435 style="width: 100%; padding: 10px 12px; background: var(--dashboard-input-bg); border: 1px solid var(--dashboard-border); border-radius: 6px; color: var(--dashboard-text); font-size: 14px; box-sizing: border-box;">
2436 <p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);">
2437 Use <code style="background: rgba(0,0,0,0.1); padding: 2px 4px; border-radius: 3px;">{name}</code> as a placeholder for the archive name. Example: <code style="background: rgba(0,0,0,0.1); padding: 2px 4px; border-radius: 3px;">Category: {name}</code>
2438 </p>
2439 </div>
2440
2441 </div>
2442 <?php
2443 }
2444 }
2445