PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.21
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.21
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.21, at includes/class-metasync-settings-fields.php

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