| 1 |
<?php |
| 2 |
if (!defined('ABSPATH')) { |
| 3 |
exit; |
| 4 |
} |
| 5 |
|
| 6 |
/** |
| 7 |
* Settings Registration manager. |
| 8 |
* |
| 9 |
* Extracted from Metasync_Admin to keep the admin class focused on UI concerns. |
| 10 |
* Handles register_setting / add_settings_section / add_settings_field calls |
| 11 |
* (settings_page_init) and the sanitize callback for the main option group. |
| 12 |
* |
| 13 |
* @package Metasync |
| 14 |
* @subpackage Metasync/includes |
| 15 |
*/ |
| 16 |
class Metasync_Settings_Registration |
| 17 |
{ |
| 18 |
/** @var self|null */ |
| 19 |
private static $instance = null; |
| 20 |
|
| 21 |
/** |
| 22 |
* Get singleton instance. |
| 23 |
* |
| 24 |
* @return self |
| 25 |
*/ |
| 26 |
public static function instance() |
| 27 |
{ |
| 28 |
if (self::$instance === null) { |
| 29 |
self::$instance = new self(); |
| 30 |
} |
| 31 |
return self::$instance; |
| 32 |
} |
| 33 |
|
| 34 |
private function __construct() {} |
| 35 |
|
| 36 |
/** |
| 37 |
* Helper – replicate Metasync_Admin::get_effective_menu_title() without |
| 38 |
* needing an admin instance. |
| 39 |
*/ |
| 40 |
private function get_effective_menu_title() |
| 41 |
{ |
| 42 |
$whitelabel_company_name = Metasync::get_whitelabel_company_name(); |
| 43 |
if ($whitelabel_company_name) { |
| 44 |
return $whitelabel_company_name . ' SEO'; |
| 45 |
} |
| 46 |
return Metasync::get_effective_plugin_name(); |
| 47 |
} |
| 48 |
|
| 49 |
/** |
| 50 |
* Handle saving plugin access roles from Advanced Settings. |
| 51 |
* Moved from Metasync_Admin – only called from settings_page_init(). |
| 52 |
*/ |
| 53 |
private function handle_plugin_access_roles_save() |
| 54 |
{ |
| 55 |
if (isset($_POST['save_plugin_access_roles']) && $_POST['save_plugin_access_roles'] === 'yes') { |
| 56 |
if (isset($_POST['plugin_access_roles_nonce']) && wp_verify_nonce($_POST['plugin_access_roles_nonce'], 'metasync_plugin_access_roles_nonce')) { |
| 57 |
|
| 58 |
if (!Metasync::current_user_has_plugin_access()) { |
| 59 |
wp_redirect(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '&tab=advanced&access_roles_error=1&message=' . urlencode('Insufficient permissions'))); |
| 60 |
exit; |
| 61 |
} |
| 62 |
|
| 63 |
$metasync_options = Metasync::get_option(); |
| 64 |
if (!is_array($metasync_options)) { |
| 65 |
$metasync_options = array(); |
| 66 |
} |
| 67 |
if (!isset($metasync_options['general']) || !is_array($metasync_options['general'])) { |
| 68 |
$metasync_options['general'] = array(); |
| 69 |
} |
| 70 |
|
| 71 |
if (isset($_POST['plugin_access_roles']) && is_array($_POST['plugin_access_roles'])) { |
| 72 |
$metasync_options['general']['plugin_access_roles'] = array_map('sanitize_text_field', $_POST['plugin_access_roles']); |
| 73 |
} else { |
| 74 |
$metasync_options['general']['plugin_access_roles'] = array(); |
| 75 |
} |
| 76 |
|
| 77 |
Metasync::set_option($metasync_options); |
| 78 |
|
| 79 |
$redirect_url = admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '&tab=advanced&access_roles_saved=1'); |
| 80 |
wp_redirect($redirect_url); |
| 81 |
exit; |
| 82 |
} else { |
| 83 |
$redirect_url = admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '&tab=advanced&access_roles_error=1&message=' . urlencode('Invalid security token')); |
| 84 |
wp_redirect($redirect_url); |
| 85 |
exit; |
| 86 |
} |
| 87 |
} |
| 88 |
} |
| 89 |
|
| 90 |
/** |
| 91 |
* Register and add settings – formerly Metasync_Admin::settings_page_init(). |
| 92 |
*/ |
| 93 |
public function settings_page_init() |
| 94 |
{ |
| 95 |
Metasync_Connect_Manager::instance()->handle_session_management_early(); |
| 96 |
Metasync_Connect_Manager::instance()->handle_whitelabel_password_early(); |
| 97 |
|
| 98 |
Metasync_Debug_Manager::instance()->handle_debug_mode_operations(); |
| 99 |
Metasync_Debug_Manager::instance()->handle_error_log_operations(); |
| 100 |
Metasync_Debug_Manager::instance()->handle_clear_all_settings(); |
| 101 |
|
| 102 |
$this->handle_plugin_access_roles_save(); |
| 103 |
|
| 104 |
$SECTION_FEATURES = Metasync_Admin::SECTION_FEATURES; |
| 105 |
$SECTION_METASYNC = Metasync_Admin::SECTION_METASYNC; |
| 106 |
$SECTION_SEARCHENGINE = Metasync_Admin::SECTION_SEARCHENGINE; |
| 107 |
$SECTION_LOCALSEO = Metasync_Admin::SECTION_LOCALSEO; |
| 108 |
$SECTION_CODESNIPPETS = Metasync_Admin::SECTION_CODESNIPPETS; |
| 109 |
$SECTION_OPTIMAL_SETTINGS = Metasync_Admin::SECTION_OPTIMAL_SETTINGS; |
| 110 |
$SECTION_SITE_SETTINGS = Metasync_Admin::SECTION_SITE_SETTINGS; |
| 111 |
$SECTION_COMMON_SETTINGS = Metasync_Admin::SECTION_COMMON_SETTINGS; |
| 112 |
$SECTION_COMMON_META_SETTINGS = Metasync_Admin::SECTION_COMMON_META_SETTINGS; |
| 113 |
$SECTION_SOCIAL_META = Metasync_Admin::SECTION_SOCIAL_META; |
| 114 |
$SECTION_SEO_CONTROLS = Metasync_Admin::SECTION_SEO_CONTROLS; |
| 115 |
$SECTION_SEO_CONTROLS_ADVANCED = Metasync_Admin::SECTION_SEO_CONTROLS_ADVANCED; |
| 116 |
$SECTION_SEO_CONTROLS_INSTANT_INDEX = Metasync_Admin::SECTION_SEO_CONTROLS_INSTANT_INDEX; |
| 117 |
$SECTION_PLUGIN_VISIBILITY = Metasync_Admin::SECTION_PLUGIN_VISIBILITY; |
| 118 |
$SECTION_BREADCRUMBS = Metasync_Admin::SECTION_BREADCRUMBS; |
| 119 |
$SECTION_LLMS_TXT = Metasync_Admin::SECTION_LLMS_TXT; |
| 120 |
|
| 121 |
$option_key = Metasync_Admin::option_key; |
| 122 |
$page_slug = Metasync_Admin::$page_slug; |
| 123 |
|
| 124 |
# Use whitelabel OTTO name if configured, fallback to 'OTTO' |
| 125 |
$whitelabel_otto_name = Metasync::get_whitelabel_otto_name(); |
| 126 |
|
| 127 |
register_setting( |
| 128 |
Metasync_Admin::option_group, |
| 129 |
Metasync_Admin::option_key, |
| 130 |
array(self::instance(), 'sanitize') |
| 131 |
); |
| 132 |
|
| 133 |
// LLMs.txt settings are stored in a dedicated option so MCP tools and |
| 134 |
// the admin UI share one source of truth. |
| 135 |
register_setting( |
| 136 |
Metasync_Admin::option_group, |
| 137 |
'metasync_llms_txt_settings', |
| 138 |
array( |
| 139 |
'sanitize_callback' => array(self::instance(), 'sanitize_llms_txt_settings'), |
| 140 |
'default' => array(), |
| 141 |
) |
| 142 |
); |
| 143 |
|
| 144 |
|
| 145 |
add_settings_section( |
| 146 |
$SECTION_METASYNC, // ID |
| 147 |
'', // Title - removed to prevent duplication with dashboard card |
| 148 |
function(){}, // Callback |
| 149 |
$page_slug . '_general' // Page |
| 150 |
); |
| 151 |
|
| 152 |
add_settings_section( |
| 153 |
$SECTION_LLMS_TXT, |
| 154 |
esc_html__('LLMs.txt Settings', 'metasync'), |
| 155 |
function () { |
| 156 |
echo '<p>' . esc_html__('Publish a standards-compliant /llms.txt so AI search engines and assistants can discover your most important pages.', 'metasync') . '</p>'; |
| 157 |
}, |
| 158 |
$page_slug . '_general' |
| 159 |
); |
| 160 |
|
| 161 |
add_settings_field( |
| 162 |
'llms_txt_enabled', |
| 163 |
esc_html__('Enable /llms.txt', 'metasync'), |
| 164 |
function () { |
| 165 |
$settings = get_option('metasync_llms_txt_settings', array()); |
| 166 |
$checked = !empty($settings['enabled']); |
| 167 |
printf( |
| 168 |
'<input type="checkbox" id="llms_txt_enabled" name="metasync_llms_txt_settings[enabled]" value="1" %s />', |
| 169 |
$checked ? 'checked' : '' |
| 170 |
); |
| 171 |
echo '<span class="description">' . esc_html__('Serve /llms.txt via a virtual route.', 'metasync') . '</span>'; |
| 172 |
}, |
| 173 |
$page_slug . '_general', |
| 174 |
$SECTION_LLMS_TXT |
| 175 |
); |
| 176 |
|
| 177 |
add_settings_field( |
| 178 |
'llms_txt_post_types', |
| 179 |
esc_html__('Post types to include', 'metasync'), |
| 180 |
function () { |
| 181 |
$settings = get_option('metasync_llms_txt_settings', array()); |
| 182 |
$selected = isset($settings['post_types']) && is_array($settings['post_types']) |
| 183 |
? $settings['post_types'] |
| 184 |
: array('page', 'post'); |
| 185 |
$public_types = get_post_types(array('public' => true), 'objects'); |
| 186 |
echo '<div class="llms-txt-dependent">'; |
| 187 |
foreach ($public_types as $type) { |
| 188 |
if (in_array($type->name, array('attachment'), true)) { |
| 189 |
continue; |
| 190 |
} |
| 191 |
printf( |
| 192 |
'<label style="display:inline-block;margin-right:12px;"><input type="checkbox" name="metasync_llms_txt_settings[post_types][]" value="%1$s" %2$s /> %3$s</label>', |
| 193 |
esc_attr($type->name), |
| 194 |
in_array($type->name, $selected, true) ? 'checked' : '', |
| 195 |
esc_html($type->labels->singular_name ?: $type->name) |
| 196 |
); |
| 197 |
} |
| 198 |
echo '</div>'; |
| 199 |
}, |
| 200 |
$page_slug . '_general', |
| 201 |
$SECTION_LLMS_TXT |
| 202 |
); |
| 203 |
|
| 204 |
add_settings_field( |
| 205 |
'llms_txt_max_posts', |
| 206 |
esc_html__('Max posts to include', 'metasync'), |
| 207 |
function () { |
| 208 |
$settings = get_option('metasync_llms_txt_settings', array()); |
| 209 |
$value = isset($settings['max_posts']) ? (int) $settings['max_posts'] : 50; |
| 210 |
echo '<div class="llms-txt-dependent">'; |
| 211 |
printf( |
| 212 |
'<input type="number" id="llms_txt_max_posts" name="metasync_llms_txt_settings[max_posts]" min="1" max="500" value="%d" />', |
| 213 |
$value |
| 214 |
); |
| 215 |
echo '</div>'; |
| 216 |
}, |
| 217 |
$page_slug . '_general', |
| 218 |
$SECTION_LLMS_TXT |
| 219 |
); |
| 220 |
|
| 221 |
add_settings_field( |
| 222 |
'llms_txt_excluded_ids', |
| 223 |
esc_html__('Excluded post/page IDs', 'metasync'), |
| 224 |
function () { |
| 225 |
$settings = get_option('metasync_llms_txt_settings', array()); |
| 226 |
$ids = isset($settings['excluded_ids']) && is_array($settings['excluded_ids']) |
| 227 |
? implode(', ', array_map('absint', $settings['excluded_ids'])) |
| 228 |
: ''; |
| 229 |
echo '<div class="llms-txt-dependent">'; |
| 230 |
printf( |
| 231 |
'<input type="text" id="llms_txt_excluded_ids" name="metasync_llms_txt_settings[excluded_ids]" value="%s" size="40" />', |
| 232 |
esc_attr($ids) |
| 233 |
); |
| 234 |
echo '<p class="description">' . esc_html__('Comma-separated list of IDs to exclude from the listing.', 'metasync') . '</p>'; |
| 235 |
echo '</div>'; |
| 236 |
}, |
| 237 |
$page_slug . '_general', |
| 238 |
$SECTION_LLMS_TXT |
| 239 |
); |
| 240 |
|
| 241 |
add_settings_field( |
| 242 |
'llms_txt_custom_description', |
| 243 |
esc_html__('Custom description', 'metasync'), |
| 244 |
function () { |
| 245 |
$settings = get_option('metasync_llms_txt_settings', array()); |
| 246 |
$value = isset($settings['custom_description']) ? (string) $settings['custom_description'] : ''; |
| 247 |
echo '<div class="llms-txt-dependent">'; |
| 248 |
printf( |
| 249 |
'<input type="text" id="llms_txt_custom_description" name="metasync_llms_txt_settings[custom_description]" value="%s" size="60" />', |
| 250 |
esc_attr($value) |
| 251 |
); |
| 252 |
echo '<p class="description">' . esc_html__('Overrides the site tagline in the /llms.txt header.', 'metasync') . '</p>'; |
| 253 |
echo '</div>'; |
| 254 |
}, |
| 255 |
$page_slug . '_general', |
| 256 |
$SECTION_LLMS_TXT |
| 257 |
); |
| 258 |
|
| 259 |
add_settings_field( |
| 260 |
'llms_full_enabled', |
| 261 |
esc_html__('Enable /llms-full.txt', 'metasync'), |
| 262 |
function () { |
| 263 |
$settings = get_option('metasync_llms_txt_settings', array()); |
| 264 |
$checked = !empty($settings['llms_full_enabled']); |
| 265 |
echo '<div class="llms-txt-dependent">'; |
| 266 |
printf( |
| 267 |
'<input type="checkbox" id="llms_full_enabled" name="metasync_llms_txt_settings[llms_full_enabled]" value="1" %s />', |
| 268 |
$checked ? 'checked' : '' |
| 269 |
); |
| 270 |
echo '<span class="description">' . esc_html__('Also serve /llms-full.txt with full post bodies converted to markdown.', 'metasync') . '</span>'; |
| 271 |
echo '</div>'; |
| 272 |
}, |
| 273 |
$page_slug . '_general', |
| 274 |
$SECTION_LLMS_TXT |
| 275 |
); |
| 276 |
|
| 277 |
add_settings_field( |
| 278 |
'llms_txt_max_posts_full', |
| 279 |
esc_html__('Max posts for full-text', 'metasync'), |
| 280 |
function () { |
| 281 |
$settings = get_option('metasync_llms_txt_settings', array()); |
| 282 |
$value = isset($settings['max_posts_full']) ? (int) $settings['max_posts_full'] : 25; |
| 283 |
echo '<div class="llms-full-dependent">'; |
| 284 |
printf( |
| 285 |
'<input type="number" id="llms_txt_max_posts_full" name="metasync_llms_txt_settings[max_posts_full]" min="1" max="500" value="%d" />', |
| 286 |
$value |
| 287 |
); |
| 288 |
echo '<p class="description">' . esc_html__('Maximum posts for /llms-full.txt (lower than summary to protect server performance).', 'metasync') . '</p>'; |
| 289 |
echo '</div>'; |
| 290 |
}, |
| 291 |
$page_slug . '_general', |
| 292 |
$SECTION_LLMS_TXT |
| 293 |
); |
| 294 |
|
| 295 |
|
| 296 |
add_settings_section( |
| 297 |
$SECTION_METASYNC, // ID |
| 298 |
'', // Title - removed to prevent duplication with dashboard card |
| 299 |
function(){}, // Callback |
| 300 |
$page_slug . '_branding' // Page |
| 301 |
); |
| 302 |
|
| 303 |
add_settings_section( |
| 304 |
$SECTION_METASYNC, // ID |
| 305 |
$this->get_effective_menu_title() . ' Caching Settings:', // Title |
| 306 |
function(){}, // Callback |
| 307 |
$page_slug . '_otto_cache' // Page |
| 308 |
); |
| 309 |
|
| 310 |
add_settings_section( |
| 311 |
$SECTION_SEO_CONTROLS, // ID |
| 312 |
'', // Title - removed to prevent duplication with dashboard card |
| 313 |
function(){}, // Callback |
| 314 |
$page_slug . '_seo-controls' // Page |
| 315 |
); |
| 316 |
|
| 317 |
// Archive Indexation Control Fields |
| 318 |
add_settings_field( |
| 319 |
'noindex_empty_archives', |
| 320 |
'Disallow Empty Archives', |
| 321 |
function() use ($option_key) { |
| 322 |
$noindex_empty_archives = Metasync::get_option('seo_controls')['noindex_empty_archives'] ?? 'false'; |
| 323 |
printf( |
| 324 |
'<input type="checkbox" id="noindex_empty_archives" name="' . $option_key . '[seo_controls][noindex_empty_archives]" value="true" %s />', |
| 325 |
isset($noindex_empty_archives) && $noindex_empty_archives == 'true' ? 'checked' : '' |
| 326 |
); |
| 327 |
printf('<span class="description"><strong>Empty Archives:</strong> When checked, automatically adds noindex to category, tag, author, and format archive pages that have no posts. Once posts are added to these archives, they will automatically be allowed for indexing. This prevents thin content pages from being indexed.</span>'); |
| 328 |
}, |
| 329 |
$page_slug . '_seo-controls', |
| 330 |
$SECTION_SEO_CONTROLS |
| 331 |
); |
| 332 |
|
| 333 |
add_settings_field( |
| 334 |
'index_date_archives', |
| 335 |
'Disallow Date Archives', |
| 336 |
function() use ($option_key) { |
| 337 |
$index_date_archives = Metasync::get_option('seo_controls')['index_date_archives'] ?? 'false'; |
| 338 |
printf( |
| 339 |
'<input type="checkbox" id="index_date_archives" name="' . $option_key . '[seo_controls][index_date_archives]" value="true" %s />', |
| 340 |
isset($index_date_archives) && $index_date_archives == 'true' ? 'checked' : '' |
| 341 |
); |
| 342 |
printf('<span class="description"><strong>Date Archives:</strong> When checked, prevents search engines from indexing date-based archive pages (e.g., /2024/01/, /2024/01/15/). These pages often have thin content and can dilute your site\'s SEO value. Recommended for most sites.</span>'); |
| 343 |
}, |
| 344 |
$page_slug . '_seo-controls', |
| 345 |
$SECTION_SEO_CONTROLS |
| 346 |
); |
| 347 |
|
| 348 |
add_settings_field( |
| 349 |
'index_tag_archives', |
| 350 |
'Disallow Tag Archives', |
| 351 |
function() use ($option_key) { |
| 352 |
$index_tag_archives = Metasync::get_option('seo_controls')['index_tag_archives'] ?? 'false'; |
| 353 |
printf( |
| 354 |
'<input type="checkbox" id="index_tag_archives" name="' . $option_key . '[seo_controls][index_tag_archives]" value="true" %s />', |
| 355 |
isset($index_tag_archives) && $index_tag_archives == 'true' ? 'checked' : '' |
| 356 |
); |
| 357 |
printf('<span class="description"><strong>Tag Archives:</strong> When checked, prevents search engines from indexing tag archive pages (e.g., /tag/technology/). Useful if you have many low-quality tag pages or want to focus on category-based organization instead.</span>'); |
| 358 |
}, |
| 359 |
$page_slug . '_seo-controls', |
| 360 |
$SECTION_SEO_CONTROLS |
| 361 |
); |
| 362 |
|
| 363 |
add_settings_field( |
| 364 |
'index_author_archives', |
| 365 |
'Disallow Author Archives', |
| 366 |
function() use ($option_key) { |
| 367 |
$index_author_archives = Metasync::get_option('seo_controls')['index_author_archives'] ?? 'false'; |
| 368 |
printf( |
| 369 |
'<input type="checkbox" id="index_author_archives" name="' . $option_key . '[seo_controls][index_author_archives]" value="true" %s />', |
| 370 |
isset($index_author_archives) && $index_author_archives == 'true' ? 'checked' : '' |
| 371 |
); |
| 372 |
printf('<span class="description"><strong>Author Archives:</strong> When checked, prevents search engines from indexing author archive pages (e.g., /author/john-doe/). Recommended for single-author sites or when author pages don\'t provide unique value. Multi-author sites may want to keep this unchecked.</span>'); |
| 373 |
}, |
| 374 |
$page_slug . '_seo-controls', |
| 375 |
$SECTION_SEO_CONTROLS |
| 376 |
); |
| 377 |
|
| 378 |
add_settings_field( |
| 379 |
'index_format_archives', |
| 380 |
'Disallow Format Archives', |
| 381 |
function() use ($option_key) { |
| 382 |
$index_format_archives = Metasync::get_option('seo_controls')['index_format_archives'] ?? 'false'; |
| 383 |
printf( |
| 384 |
'<input type="checkbox" id="index_format_archives" name="' . $option_key . '[seo_controls][index_format_archives]" value="true" %s />', |
| 385 |
isset($index_format_archives) && $index_format_archives == 'true' ? 'checked' : '' |
| 386 |
); |
| 387 |
printf('<span class="description"><strong>Format Archives:</strong> When checked, prevents search engines from indexing post format archive pages (e.g., /type/aside/, /type/gallery/). These are rarely useful for SEO and can create duplicate content issues. Recommended for most sites.</span>'); |
| 388 |
}, |
| 389 |
$page_slug . '_seo-controls', |
| 390 |
$SECTION_SEO_CONTROLS |
| 391 |
); |
| 392 |
|
| 393 |
add_settings_field( |
| 394 |
'index_category_archives', |
| 395 |
'Disallow Category Archives', |
| 396 |
function() use ($option_key) { |
| 397 |
$index_category_archives = Metasync::get_option('seo_controls')['index_category_archives'] ?? 'false'; |
| 398 |
printf( |
| 399 |
'<input type="checkbox" id="index_category_archives" name="' . $option_key . '[seo_controls][index_category_archives]" value="true" %s />', |
| 400 |
isset($index_category_archives) && $index_category_archives == 'true' ? 'checked' : '' |
| 401 |
); |
| 402 |
printf('<span class="description"><strong>Category Archives:</strong> When checked, prevents search engines from indexing category archive pages (e.g., /category/news/). This may be useful if your category pages have thin content or if you want to consolidate SEO value on main pages instead. Use with caution as category pages can be valuable for site organization.</span>'); |
| 403 |
}, |
| 404 |
$page_slug . '_seo-controls', |
| 405 |
$SECTION_SEO_CONTROLS |
| 406 |
); |
| 407 |
|
| 408 |
add_settings_field( |
| 409 |
'add_nofollow_to_external_links', |
| 410 |
'Add No-follow to External Links', |
| 411 |
function() use ($option_key) { |
| 412 |
$add_nofollow_to_external_links = Metasync::get_option('seo_controls')['add_nofollow_to_external_links'] ?? 'false'; |
| 413 |
printf( |
| 414 |
'<input type="checkbox" id="add_nofollow_to_external_links" name="' . $option_key . '[seo_controls][add_nofollow_to_external_links]" value="true" %s />', |
| 415 |
isset($add_nofollow_to_external_links) && $add_nofollow_to_external_links == 'true' ? 'checked' : '' |
| 416 |
); |
| 417 |
printf('<span class="description"><strong>No-follow External Links:</strong> When checked, automatically adds <code>rel="nofollow"</code> attribute to all external links appearing in posts, pages, and other post types when rendered by Otto. This tells search engines not to follow these links, which can help preserve your site\'s SEO value and prevent passing link juice to external sites.</span>'); |
| 418 |
}, |
| 419 |
$page_slug . '_seo-controls', |
| 420 |
$SECTION_SEO_CONTROLS |
| 421 |
); |
| 422 |
|
| 423 |
// Advanced SEO Controls Section |
| 424 |
add_settings_section( |
| 425 |
$SECTION_SEO_CONTROLS_ADVANCED, // ID |
| 426 |
'', // Title - will be handled by custom rendering |
| 427 |
function(){ |
| 428 |
echo '</div>'; // Close main Indexation Control card |
| 429 |
echo '<div class="dashboard-card" style="margin-top: 20px;">'; |
| 430 |
echo '<h2>Advanced Settings</h2>'; |
| 431 |
echo '<p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how ' . esc_html(Metasync_Settings_Registration::instance()->get_effective_menu_title()) . ' interacts with other SEO plugins.</p>'; |
| 432 |
}, |
| 433 |
$page_slug . '_seo-controls' // Page |
| 434 |
); |
| 435 |
|
| 436 |
add_settings_field( |
| 437 |
'override_robots_tags', |
| 438 |
'Override Other Plugins\' Robots Tags', |
| 439 |
function() use ($option_key) { |
| 440 |
$override_robots_tags = Metasync::get_option('seo_controls')['override_robots_tags'] ?? 'false'; |
| 441 |
printf( |
| 442 |
'<input type="checkbox" id="override_robots_tags" name="' . $option_key . '[seo_controls][override_robots_tags]" value="true" %s />', |
| 443 |
isset($override_robots_tags) && $override_robots_tags == 'true' ? 'checked' : '' |
| 444 |
); |
| 445 |
printf('<span class="description"><strong>Override Robots Tags:</strong> When checked, ' . Metasync_Settings_Registration::instance()->get_effective_menu_title() . ' will take precedence over robots meta tags from other SEO plugins (Yoast, Rank Math, All in One SEO, etc.). This removes their noindex tags when you want to allow indexing on archive pages. Only enable this if other plugins are conflicting with your indexation settings.</span>'); |
| 446 |
}, |
| 447 |
$page_slug . '_seo-controls', |
| 448 |
$SECTION_SEO_CONTROLS_ADVANCED |
| 449 |
); |
| 450 |
|
| 451 |
// Google Instant Indexing Section |
| 452 |
add_settings_section( |
| 453 |
$SECTION_SEO_CONTROLS_INSTANT_INDEX, // ID |
| 454 |
'', // Title - will be handled by custom rendering |
| 455 |
function(){ |
| 456 |
echo '</div>'; // Close Advanced Settings card |
| 457 |
echo '<div class="dashboard-card" style="margin-top: 20px;">'; |
| 458 |
echo '<h2>Google Instant Indexing</h2>'; |
| 459 |
echo '<p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure Google Indexing API for faster URL indexing. Enable this feature to access the Instant Indexing page.</p>'; |
| 460 |
}, |
| 461 |
$page_slug . '_seo-controls' // Page |
| 462 |
); |
| 463 |
|
| 464 |
add_settings_field( |
| 465 |
'enable_googleinstantindex', |
| 466 |
'Enable Google Instant Indexing', |
| 467 |
function() use ($option_key) { |
| 468 |
$enable_googleinstantindex = Metasync::get_option('seo_controls')['enable_googleinstantindex'] ?? 'false'; |
| 469 |
printf( |
| 470 |
'<input type="checkbox" id="enable_googleinstantindex" name="' . $option_key . '[seo_controls][enable_googleinstantindex]" value="true" %s />', |
| 471 |
isset($enable_googleinstantindex) && $enable_googleinstantindex == 'true' ? 'checked' : '' |
| 472 |
); |
| 473 |
printf('<span class="description"><strong>Enable Instant Indexing:</strong> When checked, enables the Google Instant Indexing feature which allows you to submit URLs directly to Google for faster indexing. A new "Instant Indexing" menu item will appear in the navigation.</span>'); |
| 474 |
}, |
| 475 |
$page_slug . '_seo-controls', |
| 476 |
$SECTION_SEO_CONTROLS_INSTANT_INDEX |
| 477 |
); |
| 478 |
|
| 479 |
add_settings_field( |
| 480 |
'google_index_api_config', |
| 481 |
'Google Index API Configuration', |
| 482 |
array(Metasync_Settings_Fields::instance(), 'render_google_index_section'), |
| 483 |
$page_slug . '_seo-controls', |
| 484 |
$SECTION_SEO_CONTROLS_INSTANT_INDEX |
| 485 |
); |
| 486 |
|
| 487 |
// Bing Instant Indexing Section |
| 488 |
add_settings_section( |
| 489 |
'seo_controls_bing_instant_index', // ID |
| 490 |
'', // Title - will be handled by custom rendering |
| 491 |
function(){ |
| 492 |
echo '</div>'; // Close Google Instant Indexing card |
| 493 |
echo '<div class="dashboard-card" style="margin-top: 20px;">'; |
| 494 |
echo '<h2>Bing Instant Indexing (IndexNow)</h2>'; |
| 495 |
echo '<p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure IndexNow API for instant URL submission to Bing, Yandex, and other search engines that support the IndexNow protocol.</p>'; |
| 496 |
}, |
| 497 |
$page_slug . '_seo-controls' // Page |
| 498 |
); |
| 499 |
|
| 500 |
add_settings_field( |
| 501 |
'enable_binginstantindex', |
| 502 |
'Enable Bing Instant Indexing', |
| 503 |
function() use ($option_key) { |
| 504 |
// Auto-enable if API key is configured |
| 505 |
$bing_settings = get_option('metasync_options_bing_instant_indexing', []); |
| 506 |
$has_api_key = !empty($bing_settings['api_key']); |
| 507 |
|
| 508 |
$enable_binginstantindex = Metasync::get_option('seo_controls')['enable_binginstantindex'] ?? 'false'; |
| 509 |
|
| 510 |
// Auto-check if API key exists |
| 511 |
if ($has_api_key && $enable_binginstantindex !== 'true') { |
| 512 |
$enable_binginstantindex = 'true'; |
| 513 |
} |
| 514 |
|
| 515 |
printf( |
| 516 |
'<input type="checkbox" id="enable_binginstantindex" name="' . $option_key . '[seo_controls][enable_binginstantindex]" value="true" %s %s />', |
| 517 |
isset($enable_binginstantindex) && $enable_binginstantindex == 'true' ? 'checked' : '', |
| 518 |
$has_api_key ? 'data-auto-enabled="true"' : '' |
| 519 |
); |
| 520 |
printf('<span class="description"><strong>Enable Bing Instant Indexing:</strong> Automatically enabled when you configure an API key below. When enabled, this activates the IndexNow protocol to instantly notify Bing, Yandex, and other search engines about URL changes.</span>'); |
| 521 |
}, |
| 522 |
$page_slug . '_seo-controls', |
| 523 |
'seo_controls_bing_instant_index' |
| 524 |
); |
| 525 |
|
| 526 |
// Bing Index API configuration |
| 527 |
add_settings_field( |
| 528 |
'bing_index_api_config', |
| 529 |
'IndexNow API Configuration', |
| 530 |
array(Metasync_Settings_Fields::instance(), 'render_bing_index_section'), |
| 531 |
$page_slug . '_seo-controls', |
| 532 |
'seo_controls_bing_instant_index' |
| 533 |
); |
| 534 |
|
| 535 |
add_settings_field( |
| 536 |
'searchatlas_api_key', |
| 537 |
$this->get_effective_menu_title() . ' API Key', |
| 538 |
array(Metasync_Settings_Fields::instance(), 'searchatlas_api_key_callback'), |
| 539 |
$page_slug . '_general', |
| 540 |
$SECTION_METASYNC |
| 541 |
); |
| 542 |
|
| 543 |
add_settings_field( |
| 544 |
'apikey', // ID |
| 545 |
'Plugin Auth Token', // Title |
| 546 |
array(Metasync_Settings_Fields::instance(), 'metasync_settings_genkey_callback'), // Callback |
| 547 |
$page_slug . '_general', // Page |
| 548 |
$SECTION_METASYNC // Section |
| 549 |
); |
| 550 |
|
| 551 |
/** |
| 552 |
* SERVER SIDE RENDERING SETTING OPTIONS |
| 553 |
* @see SSR functionality |
| 554 |
* Note: OTTO SSR is always enabled by default when connected |
| 555 |
*/ |
| 556 |
|
| 557 |
# field to accept the otto pixel |
| 558 |
add_settings_field( |
| 559 |
'otto_pixel_uuid', |
| 560 |
$whitelabel_otto_name . ' Pixel UUID', |
| 561 |
function() use ($option_key) { |
| 562 |
$value = Metasync::get_option('general')['otto_pixel_uuid'] ?? ''; |
| 563 |
printf('<input type="text" size="40" value = "'.esc_attr($value).'" name="' . $option_key . '[general][otto_pixel_uuid]"/>'); |
| 564 |
}, |
| 565 |
$page_slug . '_general', |
| 566 |
$SECTION_METASYNC |
| 567 |
); |
| 568 |
|
| 569 |
|
| 570 |
# check box to toggle on and off disabling OTTO for logged in users |
| 571 |
add_settings_field( |
| 572 |
'otto_disable_on_loggedin', |
| 573 |
'Disable ' . $whitelabel_otto_name . ' for Logged in Users', |
| 574 |
function() use ($whitelabel_otto_name, $option_key) { |
| 575 |
$otto_disable_on_loggedin = Metasync::get_option('general')['otto_disable_on_loggedin'] ?? ''; |
| 576 |
printf( |
| 577 |
'<input type="checkbox" id="otto_disable_on_loggedin" name="' . $option_key . '[general][otto_disable_on_loggedin]" value="true" %s />', |
| 578 |
isset($otto_disable_on_loggedin) && $otto_disable_on_loggedin == 'true' ? 'checked' : '' |
| 579 |
); |
| 580 |
printf('<span class="description"> This disables '.$whitelabel_otto_name.' when logged in to allow editing original page contents</span>'); |
| 581 |
}, |
| 582 |
$page_slug . '_general', |
| 583 |
$SECTION_METASYNC |
| 584 |
); |
| 585 |
|
| 586 |
# check box to disable OTTO frontend toolbar |
| 587 |
add_settings_field( |
| 588 |
'otto_disable_preview_button', |
| 589 |
'Disable ' . $whitelabel_otto_name . ' Frontend Toolbar', |
| 590 |
function() use ($whitelabel_otto_name, $option_key) { |
| 591 |
$otto_disable_toolbar = Metasync::get_option('general')['otto_disable_preview_button'] ?? false; |
| 592 |
printf( |
| 593 |
'<input type="checkbox" id="otto_disable_preview_button" name="' . $option_key . '[general][otto_disable_preview_button]" value="true" %s />', |
| 594 |
filter_var($otto_disable_toolbar, FILTER_VALIDATE_BOOLEAN) ? 'checked' : '' |
| 595 |
); |
| 596 |
printf('<span class="description"> Hide the entire frontend toolbar (status indicator, preview button, and debug button) on the frontend. %s functionality will still work, but the toolbar controls will be hidden.</span>', esc_html($whitelabel_otto_name)); |
| 597 |
}, |
| 598 |
$page_slug . '_general', |
| 599 |
$SECTION_METASYNC |
| 600 |
); |
| 601 |
|
| 602 |
# WP Rocket compatibility mode setting |
| 603 |
add_settings_field( |
| 604 |
'otto_wp_rocket_compat', |
| 605 |
'WP Rocket Compatibility', |
| 606 |
function() use ($whitelabel_otto_name, $option_key) { |
| 607 |
$value = Metasync::get_option('general')['otto_wp_rocket_compat'] ?? 'auto'; |
| 608 |
$wp_rocket_active = class_exists('WP_Rocket'); |
| 609 |
|
| 610 |
echo '<select id="otto_wp_rocket_compat" name="' . $option_key . '[general][otto_wp_rocket_compat]">'; |
| 611 |
echo '<option value="auto"' . selected($value, 'auto', false) . '>Auto (Recommended)</option>'; |
| 612 |
echo '<option value="buffer"' . selected($value, 'buffer', false) . '>Buffer Mode (Faster)</option>'; |
| 613 |
echo '<option value="http"' . selected($value, 'http', false) . '>HTTP Mode (Safer)</option>'; |
| 614 |
echo '<option value="disable_otto"' . selected($value, 'disable_otto', false) . '>Disable ' . esc_html($whitelabel_otto_name) . ' when WP Rocket is active</option>'; |
| 615 |
echo '</select>'; |
| 616 |
|
| 617 |
if ($wp_rocket_active) { |
| 618 |
echo '<p class="description" style="color: #0073aa; margin-top: 8px;">✓ WP Rocket detected - compatibility mode active</p>'; |
| 619 |
} |
| 620 |
|
| 621 |
echo '<p class="description" style="margin-top: 8px;">'; |
| 622 |
echo '<strong>Auto (Recommended):</strong> Allows both ' . esc_html($whitelabel_otto_name) . ' and WP Rocket to work together. Does not set DONOTCACHEPAGE unless required (Brizy pages, SG Optimizer conflicts).<br>'; |
| 623 |
echo '<strong>Buffer Mode:</strong> Forces output buffering method. Fastest but may conflict with some configurations.<br>'; |
| 624 |
echo '<strong>HTTP Mode:</strong> Forces internal HTTP fetch method. Slower but more compatible.<br>'; |
| 625 |
echo '<strong>Disable ' . esc_html($whitelabel_otto_name) . ':</strong> Completely disables ' . esc_html($whitelabel_otto_name) . ' when WP Rocket is detected. Use if issues persist.'; |
| 626 |
echo '</p>'; |
| 627 |
}, |
| 628 |
$page_slug . '_general', |
| 629 |
$SECTION_METASYNC |
| 630 |
); |
| 631 |
|
| 632 |
/** |
| 633 |
* BOT DETECTION SETTINGS FOR OTTO |
| 634 |
* @since 1.0.0 |
| 635 |
*/ |
| 636 |
|
| 637 |
# Checkbox to disable OTTO for bot traffic |
| 638 |
add_settings_field( |
| 639 |
'otto_disable_for_bots', |
| 640 |
'Disable ' . $whitelabel_otto_name . ' for Bot Traffic', |
| 641 |
function() use ($whitelabel_otto_name, $option_key) { |
| 642 |
$otto_disable_for_bots = Metasync::get_option('general')['otto_disable_for_bots'] ?? false; |
| 643 |
printf( |
| 644 |
'<input type="checkbox" id="otto_disable_for_bots" name="' . $option_key . '[general][otto_disable_for_bots]" value="true" %s />', |
| 645 |
filter_var($otto_disable_for_bots, FILTER_VALIDATE_BOOLEAN) ? 'checked' : '' |
| 646 |
); |
| 647 |
printf('<span class="description"> Skip %s processing for detected bots (search engines, crawlers, SEO tools) to reduce unnecessary API calls. View bot statistics in the Bot Statistics page under the SEO dropdown.</span>', esc_html($whitelabel_otto_name)); |
| 648 |
}, |
| 649 |
$page_slug . '_general', |
| 650 |
$SECTION_METASYNC |
| 651 |
); |
| 652 |
|
| 653 |
# Textarea for bot whitelist |
| 654 |
add_settings_field( |
| 655 |
'otto_bot_whitelist', |
| 656 |
'Bot Whitelist', |
| 657 |
function() use ($whitelabel_otto_name, $option_key) { |
| 658 |
$otto_bot_whitelist = Metasync::get_option('general')['otto_bot_whitelist'] ?? ''; |
| 659 |
printf( |
| 660 |
'<textarea id="otto_bot_whitelist" name="' . $option_key . '[general][otto_bot_whitelist]" rows="5" cols="50" style="width: 100%%; max-width: 500px; font-family: monospace;">%s</textarea>', |
| 661 |
esc_textarea($otto_bot_whitelist) |
| 662 |
); |
| 663 |
echo '<p class="description">'; |
| 664 |
echo 'Enter bot names or user-agent patterns (one per line) that should <strong>always</strong> be processed by ' . esc_html($whitelabel_otto_name) . ', even when "Disable for Bots" is enabled.<br>'; |
| 665 |
echo '<strong>Example:</strong><br>'; |
| 666 |
echo 'Googlebot<br>'; |
| 667 |
echo 'Bingbot'; |
| 668 |
echo '</p>'; |
| 669 |
}, |
| 670 |
$page_slug . '_general', |
| 671 |
$SECTION_METASYNC |
| 672 |
); |
| 673 |
|
| 674 |
# Textarea for bot blacklist |
| 675 |
add_settings_field( |
| 676 |
'otto_bot_blacklist', |
| 677 |
'Bot Blacklist', |
| 678 |
function() use ($whitelabel_otto_name, $option_key) { |
| 679 |
$otto_bot_blacklist = Metasync::get_option('general')['otto_bot_blacklist'] ?? ''; |
| 680 |
printf( |
| 681 |
'<textarea id="otto_bot_blacklist" name="' . $option_key . '[general][otto_bot_blacklist]" rows="5" cols="50" style="width: 100%%; max-width: 500px; font-family: monospace;">%s</textarea>', |
| 682 |
esc_textarea($otto_bot_blacklist) |
| 683 |
); |
| 684 |
echo '<p class="description">'; |
| 685 |
echo 'Enter bot names or user-agent patterns (one per line) that should <strong>always</strong> be blocked from ' . esc_html($whitelabel_otto_name) . ' processing, regardless of other settings.<br>'; |
| 686 |
echo '<strong>Example:</strong><br>'; |
| 687 |
echo 'BadBot<br>'; |
| 688 |
echo 'MaliciousCrawler'; |
| 689 |
echo '</p>'; |
| 690 |
}, |
| 691 |
$page_slug . '_general', |
| 692 |
$SECTION_METASYNC |
| 693 |
); |
| 694 |
|
| 695 |
|
| 696 |
# Link button to Bot Statistics page |
| 697 |
add_settings_field( |
| 698 |
'otto_bot_statistics_link', |
| 699 |
'Bot Detection Statistics', |
| 700 |
function() use ($page_slug) { |
| 701 |
printf( |
| 702 |
'<a href="%s" class="button button-secondary"><span class="dashicons dashicons-chart-bar" style="margin-top:3px;font-size:15px;width:15px;height:15px;"></span> View Bot Statistics</a>', |
| 703 |
esc_url(admin_url('admin.php?page=' . $page_slug . '-bot-statistics')) |
| 704 |
); |
| 705 |
printf('<p class="description">View detailed bot detection statistics, breakdown by bot type, and unique bot entries with hit counts.</p>'); |
| 706 |
}, |
| 707 |
$page_slug . '_general', |
| 708 |
$SECTION_METASYNC |
| 709 |
); |
| 710 |
|
| 711 |
# END BOT DETECTION SETTINGS |
| 712 |
|
| 713 |
add_settings_field( |
| 714 |
'periodic_clear_ottopage_cache', |
| 715 |
'Clear Page Cache', |
| 716 |
function() use ($option_key) { |
| 717 |
$periodic_clear_ottopage_cache = Metasync::get_option('general')['periodic_clear_ottopage_cache'] ?? 'default'; |
| 718 |
printf('<select style = "width : 250px" name="' . $option_key . '[general][periodic_clear_ottopage_cache]" id="heading_style">'); |
| 719 |
printf('<option value="24" '. selected($periodic_clear_ottopage_cache, '24', false) . '>Clear Daily</option>'); |
| 720 |
printf('<option value="36" '. selected($periodic_clear_ottopage_cache, '36', false) . '>Clear Every 2 days</option>'); |
| 721 |
printf('<option value="40" '. selected($periodic_clear_ottopage_cache, '40', false) . '>Clear Weekly</option>'); |
| 722 |
printf('<option value="0"'.selected($periodic_clear_ottopage_cache, '0', false).'>Clear Monthly (Default)</option>'); |
| 723 |
printf('</select>'); |
| 724 |
|
| 725 |
# get last cleared timestamp |
| 726 |
$timestamp = get_option('metasync_refresh_all_caches')['pages'] ?? false; |
| 727 |
|
| 728 |
if($timestamp > 0){ |
| 729 |
printf( |
| 730 |
'<p class="descriptionValue">last Cleared : '.date('y-m-d H:i:s', $timestamp).'</p>' |
| 731 |
); |
| 732 |
} |
| 733 |
|
| 734 |
}, |
| 735 |
$page_slug . '_otto_cache', |
| 736 |
$SECTION_METASYNC |
| 737 |
); |
| 738 |
|
| 739 |
add_settings_field( |
| 740 |
'periodic_clear_ottopost_cache', |
| 741 |
'Clear Post Cache', |
| 742 |
function() use ($option_key) { |
| 743 |
$periodic_clear_ottopost_cache = Metasync::get_option('general')['periodic_clear_ottopost_cache'] ?? 'default'; |
| 744 |
printf('<select style = "width : 250px" name="' . $option_key . '[general][periodic_clear_ottopost_cache]" id="heading_style">'); |
| 745 |
printf('<option value="24" '. selected($periodic_clear_ottopost_cache, '24', false) . '>Clear Daily</option>'); |
| 746 |
printf('<option value="36" '. selected($periodic_clear_ottopost_cache, '36', false) . '>Clear Every 2 days</option>'); |
| 747 |
printf('<option value="40" '. selected($periodic_clear_ottopost_cache, '40', false) . '>Clear Weekly</option>'); |
| 748 |
printf('<option value="0"'.selected($periodic_clear_ottopost_cache, '0', false).'>Clear Monthly (Default)</option>'); |
| 749 |
printf('</select>'); |
| 750 |
|
| 751 |
# get last cleared timestamp |
| 752 |
$timestamp = get_option('metasync_refresh_all_caches')['posts'] ?? false; |
| 753 |
|
| 754 |
if($timestamp > 0){ |
| 755 |
printf( |
| 756 |
'<p class="descriptionValue">last Cleared : '.date('y-m-d H:i:s', $timestamp).'</p>' |
| 757 |
); |
| 758 |
} |
| 759 |
}, |
| 760 |
$page_slug . '_otto_cache', |
| 761 |
$SECTION_METASYNC |
| 762 |
); |
| 763 |
|
| 764 |
add_settings_field( |
| 765 |
'periodic_clear_otto_cache', |
| 766 |
'Clear all cache', |
| 767 |
function() use ($option_key) { |
| 768 |
$periodic_clear_otto_cache = Metasync::get_option('general')['periodic_clear_otto_cache'] ?? 'default'; |
| 769 |
printf('<select style = "width : 250px" name="' . $option_key . '[general][periodic_clear_otto_cache]" id="heading_style">'); |
| 770 |
printf('<option value="24" '. selected($periodic_clear_otto_cache, '24', false) . '>Clear Daily</option>'); |
| 771 |
printf('<option value="36" '. selected($periodic_clear_otto_cache, '36', false) . '>Clear Every 2 days</option>'); |
| 772 |
printf('<option value="40" '. selected($periodic_clear_otto_cache, '40', false) . '>Clear Weekly</option>'); |
| 773 |
printf('<option value="0"'.selected($periodic_clear_otto_cache, '0', false).'>Clear Monthly (Default)</option>'); |
| 774 |
printf('</select>'); |
| 775 |
|
| 776 |
# get last cleared timestamp |
| 777 |
$timestamp = get_option('metasync_refresh_all_caches')['general'] ?? false; |
| 778 |
|
| 779 |
if($timestamp > 0){ |
| 780 |
printf( |
| 781 |
'<p class="descriptionValue">last Cleared : '.date('y-m-d H:i:s', $timestamp).'</p>' |
| 782 |
); |
| 783 |
} |
| 784 |
|
| 785 |
}, |
| 786 |
$page_slug . '_otto_cache', |
| 787 |
$SECTION_METASYNC |
| 788 |
); |
| 789 |
|
| 790 |
# END SERVER SIDE RENDERING SETTINGS |
| 791 |
|
| 792 |
// Meta Box Visibility Controls |
| 793 |
add_settings_field( |
| 794 |
'disable_common_robots_metabox', |
| 795 |
'Disable Common Robots Meta Box', |
| 796 |
function() use ($option_key) { |
| 797 |
$disabled = Metasync::get_option('general')['disable_common_robots_metabox'] ?? false; |
| 798 |
printf( |
| 799 |
'<input type="checkbox" id="disable_common_robots_metabox" name="' . $option_key . '[general][disable_common_robots_metabox]" value="1" %s />', |
| 800 |
$disabled ? 'checked' : '' |
| 801 |
); |
| 802 |
printf('<span class="description"> Hide the Common Robots Meta box on post/page edit screens</span>'); |
| 803 |
}, |
| 804 |
$page_slug . '_general', |
| 805 |
$SECTION_METASYNC |
| 806 |
); |
| 807 |
|
| 808 |
add_settings_field( |
| 809 |
'disable_advance_robots_metabox', |
| 810 |
'Disable Advance Robots Meta Box', |
| 811 |
function() use ($option_key) { |
| 812 |
$disabled = Metasync::get_option('general')['disable_advance_robots_metabox'] ?? false; |
| 813 |
printf( |
| 814 |
'<input type="checkbox" id="disable_advance_robots_metabox" name="' . $option_key . '[general][disable_advance_robots_metabox]" value="1" %s />', |
| 815 |
$disabled ? 'checked' : '' |
| 816 |
); |
| 817 |
printf('<span class="description"> Hide the Advance Robots Meta box on post/page edit screens</span>'); |
| 818 |
}, |
| 819 |
$page_slug . '_general', |
| 820 |
$SECTION_METASYNC |
| 821 |
); |
| 822 |
|
| 823 |
add_settings_field( |
| 824 |
'disable_redirection_metabox', |
| 825 |
'Disable Redirection Meta Box', |
| 826 |
function() use ($option_key) { |
| 827 |
$disabled = Metasync::get_option('general')['disable_redirection_metabox'] ?? false; |
| 828 |
printf( |
| 829 |
'<input type="checkbox" id="disable_redirection_metabox" name="' . $option_key . '[general][disable_redirection_metabox]" value="1" %s />', |
| 830 |
$disabled ? 'checked' : '' |
| 831 |
); |
| 832 |
printf('<span class="description"> Hide the Redirection meta box on post/page edit screens</span>'); |
| 833 |
}, |
| 834 |
$page_slug . '_general', |
| 835 |
$SECTION_METASYNC |
| 836 |
); |
| 837 |
|
| 838 |
add_settings_field( |
| 839 |
'disable_canonical_metabox', |
| 840 |
'Disable Canonical Meta Box', |
| 841 |
function() use ($option_key) { |
| 842 |
$disabled = Metasync::get_option('general')['disable_canonical_metabox'] ?? false; |
| 843 |
printf( |
| 844 |
'<input type="checkbox" id="disable_canonical_metabox" name="' . $option_key . '[general][disable_canonical_metabox]" value="1" %s />', |
| 845 |
$disabled ? 'checked' : '' |
| 846 |
); |
| 847 |
printf('<span class="description"> Hide the Canonical meta box on post/page edit screens</span>'); |
| 848 |
}, |
| 849 |
$page_slug . '_general', |
| 850 |
$SECTION_METASYNC |
| 851 |
); |
| 852 |
|
| 853 |
add_settings_field( |
| 854 |
'disable_social_opengraph_metabox', |
| 855 |
'Disable Social Media & Open Graph Meta Box', |
| 856 |
function() use ($option_key) { |
| 857 |
$disabled = Metasync::get_option('general')['disable_social_opengraph_metabox'] ?? false; |
| 858 |
printf( |
| 859 |
'<input type="checkbox" id="disable_social_opengraph_metabox" name="' . $option_key . '[general][disable_social_opengraph_metabox]" value="1" %s />', |
| 860 |
$disabled ? 'checked' : '' |
| 861 |
); |
| 862 |
printf('<span class="description"> Hide the Social Media & Open Graph meta box on post/page edit screens</span>'); |
| 863 |
}, |
| 864 |
$page_slug . '_general', |
| 865 |
$SECTION_METASYNC |
| 866 |
); |
| 867 |
|
| 868 |
add_settings_field( |
| 869 |
'disable_schema_markup_metabox', |
| 870 |
'Disable Schema Markup Meta Box', |
| 871 |
function() use ($option_key) { |
| 872 |
$disabled = Metasync::get_option('general')['disable_schema_markup_metabox'] ?? false; |
| 873 |
printf( |
| 874 |
'<input type="checkbox" id="disable_schema_markup_metabox" name="' . $option_key . '[general][disable_schema_markup_metabox]" value="1" %s />', |
| 875 |
$disabled ? 'checked' : '' |
| 876 |
); |
| 877 |
printf('<span class="description"> Hide the Schema Markup meta box on post/page edit screens</span>'); |
| 878 |
}, |
| 879 |
$page_slug . '_general', |
| 880 |
$SECTION_METASYNC |
| 881 |
); |
| 882 |
|
| 883 |
add_settings_field( |
| 884 |
'disable_seo_metabox', |
| 885 |
'Disable SEO Meta Box', |
| 886 |
function() use ($option_key) { |
| 887 |
$disabled = Metasync::get_option('general')['disable_seo_metabox'] ?? false; |
| 888 |
printf( |
| 889 |
'<input type="checkbox" id="disable_seo_metabox" name="' . $option_key . '[general][disable_seo_metabox]" value="1" %s />', |
| 890 |
$disabled ? 'checked' : '' |
| 891 |
); |
| 892 |
printf('<span class="description"> Hide the SEO Title & Meta Description meta box on post/page edit screens</span>'); |
| 893 |
}, |
| 894 |
$page_slug . '_general', |
| 895 |
$SECTION_METASYNC |
| 896 |
); |
| 897 |
|
| 898 |
add_settings_field( |
| 899 |
'open_external_links', |
| 900 |
'Open External Links in New Tab/Window', |
| 901 |
function() use ($option_key) { |
| 902 |
$enabled = Metasync::get_option('general')['open_external_links'] ?? false; |
| 903 |
printf( |
| 904 |
'<input type="checkbox" id="open_external_links" name="' . $option_key . '[general][open_external_links]" value="1" %s />', |
| 905 |
$enabled ? 'checked' : '' |
| 906 |
); |
| 907 |
printf('<span class="description"> Automatically add <code>target="_blank"</code> attribute to external links appearing in your posts, pages, and other post types when rendered by Otto. The attribute is applied when the url is displayed.</span>'); |
| 908 |
}, |
| 909 |
$page_slug . '_general', |
| 910 |
$SECTION_METASYNC |
| 911 |
); |
| 912 |
|
| 913 |
add_settings_field( |
| 914 |
'permalink_structure', |
| 915 |
'The Permalink setting of website', |
| 916 |
function() { |
| 917 |
|
| 918 |
$current_permalink_structure = get_option('permalink_structure'); |
| 919 |
$current_rewrite_rules = get_option('rewrite_rules'); |
| 920 |
// Check if the current permalink structure is set to "Plain" |
| 921 |
if (($current_permalink_structure == '/%post_id%/' || $current_permalink_structure == '') && $current_rewrite_rules == '') { |
| 922 |
printf('<span class="description" style="color:#ff0000;opacity:1;">To ensure compatibility, Please Update your Permalink structure to any option other than "plain. For any Inquiries contact support <a href="' . get_admin_url() . 'options-permalink.php">Check Setting</a> </span>'); |
| 923 |
} else { |
| 924 |
printf('<span class="description" style="color:#008000;opacity:1;">Permalink is Okay </span>'); |
| 925 |
} |
| 926 |
}, |
| 927 |
$page_slug . '_general', |
| 928 |
$SECTION_METASYNC |
| 929 |
); |
| 930 |
|
| 931 |
# Add Hide Dashboard Framework setting |
| 932 |
add_settings_field( |
| 933 |
'hide_dashboard_framework', |
| 934 |
'Hide Dashboard', |
| 935 |
function() use ($option_key) { |
| 936 |
$hide_dashboard = Metasync::get_option('general')['hide_dashboard_framework'] ?? ''; |
| 937 |
printf( |
| 938 |
'<input type="checkbox" id="hide_dashboard_framework" name="' . $option_key . '[general][hide_dashboard_framework]" value="true" %s />', |
| 939 |
isset($hide_dashboard) && $hide_dashboard == 'true' ? 'checked' : '' |
| 940 |
); |
| 941 |
printf('<span class="description"> Hide the %s dashboard</span>', esc_html(Metasync_Settings_Registration::instance()->get_effective_menu_title())); |
| 942 |
}, |
| 943 |
$page_slug . '_general', |
| 944 |
$SECTION_METASYNC |
| 945 |
); |
| 946 |
|
| 947 |
// REMOVED (CVE-2025-14386): disable_single_signup_login setting was removed |
| 948 |
|
| 949 |
# Adding the "Show Admin Bar Status" setting |
| 950 |
add_settings_field( |
| 951 |
'show_admin_bar_status', |
| 952 |
'Show ' . $this->get_effective_menu_title() . ' Status in Admin Bar', |
| 953 |
function() use ($option_key) { |
| 954 |
$show_admin_bar = Metasync::get_option('general')['show_admin_bar_status'] ?? true; |
| 955 |
printf( |
| 956 |
'<input type="checkbox" id="show_admin_bar_status" name="' . $option_key . '[general][show_admin_bar_status]" value="true" %s />', |
| 957 |
$show_admin_bar ? 'checked' : '' |
| 958 |
); |
| 959 |
printf('<span class="description">Show the %s status indicator in the WordPress admin bar.</span>', esc_html(Metasync::get_effective_plugin_name())); |
| 960 |
}, |
| 961 |
$page_slug . '_general', |
| 962 |
$SECTION_METASYNC |
| 963 |
); |
| 964 |
|
| 965 |
# Adding the "Enable Auto-updates" setting |
| 966 |
add_settings_field( |
| 967 |
'enable_auto_updates', |
| 968 |
'Enable Automatic Updates', |
| 969 |
function() use ($option_key) { |
| 970 |
$enable_auto_updates = Metasync::get_option('general')['enable_auto_updates'] ?? false; |
| 971 |
printf( |
| 972 |
'<input type="checkbox" id="enable_auto_updates" name="' . $option_key . '[general][enable_auto_updates]" value="true" %s />', |
| 973 |
$enable_auto_updates ? 'checked' : '' |
| 974 |
); |
| 975 |
printf('<span class="description">Allow WordPress to automatically update this plugin when new versions are available.</span>'); |
| 976 |
}, |
| 977 |
$page_slug . '_general', |
| 978 |
$SECTION_METASYNC |
| 979 |
); |
| 980 |
|
| 981 |
# Add User Role Sync Setting |
| 982 |
add_settings_field( |
| 983 |
'content_genius_sync_roles', |
| 984 |
'Content Genius User Roles to Sync', |
| 985 |
function() use ($option_key) { |
| 986 |
$selected_roles = Metasync::get_option('general')['content_genius_sync_roles'] ?? array(); |
| 987 |
|
| 988 |
# If it's a string (single role from old version), convert to array |
| 989 |
if (!is_array($selected_roles)) { |
| 990 |
$selected_roles = array($selected_roles); |
| 991 |
} |
| 992 |
|
| 993 |
# Get all WordPress user roles safely |
| 994 |
if (!function_exists('wp_roles')) { |
| 995 |
require_once(ABSPATH . 'wp-admin/includes/user.php'); |
| 996 |
} |
| 997 |
global $wp_roles; |
| 998 |
|
| 999 |
# Safety check for $wp_roles |
| 1000 |
if (!isset($wp_roles) || !is_object($wp_roles)) { |
| 1001 |
$wp_roles = new WP_Roles(); |
| 1002 |
} |
| 1003 |
|
| 1004 |
$all_roles = $wp_roles->roles; |
| 1005 |
|
| 1006 |
# Start container |
| 1007 |
?> |
| 1008 |
<div class="metasync-role-selector-container"> |
| 1009 |
|
| 1010 |
<!-- All Roles Option --> |
| 1011 |
<label class="metasync-role-option-all"> |
| 1012 |
<input type="checkbox" |
| 1013 |
name="<?php echo esc_attr($option_key); ?>[general][content_genius_sync_roles][]" |
| 1014 |
value="all" |
| 1015 |
<?php checked(empty($selected_roles) || in_array('all', $selected_roles), true); ?> /> |
| 1016 |
<strong>All Roles</strong> |
| 1017 |
</label> |
| 1018 |
|
| 1019 |
<!-- Divider --> |
| 1020 |
<hr class="metasync-role-divider"> |
| 1021 |
|
| 1022 |
<?php |
| 1023 |
# Display each role as a checkbox |
| 1024 |
if (!empty($all_roles) && is_array($all_roles)) { |
| 1025 |
foreach ($all_roles as $role_key => $role_details) { |
| 1026 |
# Safety check for role data |
| 1027 |
if (!isset($role_details['name'])) { |
| 1028 |
continue; |
| 1029 |
} |
| 1030 |
|
| 1031 |
# Only list roles that can author content (have the edit_posts capability). |
| 1032 |
# Keep any role already in the saved selection even if it lacks the cap, |
| 1033 |
# so existing configurations are preserved and not silently dropped on re-save. |
| 1034 |
$caps = isset($role_details['capabilities']) ? $role_details['capabilities'] : array(); |
| 1035 |
if (empty($caps['edit_posts']) && !in_array($role_key, $selected_roles)) { |
| 1036 |
continue; |
| 1037 |
} |
| 1038 |
|
| 1039 |
$is_checked = in_array($role_key, $selected_roles); |
| 1040 |
$role_name = translate_user_role($role_details['name']); |
| 1041 |
?> |
| 1042 |
<label class="metasync-role-option"> |
| 1043 |
<input type="checkbox" |
| 1044 |
name="<?php echo esc_attr($option_key); ?>[general][content_genius_sync_roles][]" |
| 1045 |
value="<?php echo esc_attr($role_key); ?>" |
| 1046 |
<?php checked($is_checked, true); ?> /> |
| 1047 |
<span class="metasync-role-label"><?php echo esc_html($role_name); ?></span> |
| 1048 |
</label> |
| 1049 |
<?php |
| 1050 |
} |
| 1051 |
} |
| 1052 |
?> |
| 1053 |
|
| 1054 |
</div> |
| 1055 |
|
| 1056 |
<!-- Description --> |
| 1057 |
<p class="description" style="margin-top: 10px;"> |
| 1058 |
<strong>How it works:</strong> |
| 1059 |
Select which user roles should be synced with Content Genius. |
| 1060 |
If <strong>"All Roles"</strong> is selected or none are selected, all users will be synced. |
| 1061 |
Otherwise, only users with the selected roles will be included in the sync. |
| 1062 |
</p> |
| 1063 |
<?php |
| 1064 |
}, |
| 1065 |
$page_slug . '_general', |
| 1066 |
$SECTION_METASYNC |
| 1067 |
); |
| 1068 |
|
| 1069 |
# Default Page Builder setting |
| 1070 |
add_settings_field( |
| 1071 |
'default_page_builder', |
| 1072 |
'Default Page Builder', |
| 1073 |
function() use ($option_key) { |
| 1074 |
require_once plugin_dir_path(dirname(__FILE__)) . 'custom-pages/class-metasync-html-to-builder-converter.php'; |
| 1075 |
$current = Metasync::get_option('general')['default_page_builder'] ?? ''; |
| 1076 |
$builders = Metasync_HTML_To_Builder_Converter::get_available_builders(); |
| 1077 |
$auto_detected = Metasync_HTML_To_Builder_Converter::auto_detect_builder(); |
| 1078 |
|
| 1079 |
echo '<div class="metasync-page-builder-setting">'; |
| 1080 |
echo '<style>#default_page_builder:focus { color: #fff; }</style>'; |
| 1081 |
echo '<select id="default_page_builder" name="' . esc_attr($option_key) . '[general][default_page_builder]" style="min-width: 300px;">'; |
| 1082 |
|
| 1083 |
foreach ($builders as $key => $builder) { |
| 1084 |
$is_detected = $builder['detected']; |
| 1085 |
$label = esc_html($builder['label']); |
| 1086 |
if ($is_detected && $key !== 'gutenberg') { |
| 1087 |
$label .= ' — Detected'; |
| 1088 |
} elseif (!$is_detected) { |
| 1089 |
$label .= ' — Not Detected'; |
| 1090 |
} |
| 1091 |
if ($key === 'gutenberg' && empty($current)) { |
| 1092 |
$label .= ' — Default'; |
| 1093 |
} |
| 1094 |
printf( |
| 1095 |
'<option value="%s" %s%s>%s</option>', |
| 1096 |
esc_attr($key), |
| 1097 |
selected(empty($current) ? 'gutenberg' : $current, $key, false), |
| 1098 |
$is_detected ? '' : ' disabled', |
| 1099 |
$label |
| 1100 |
); |
| 1101 |
} |
| 1102 |
|
| 1103 |
echo '</select>'; |
| 1104 |
echo '<p class="description" style="margin-top: 6px;">Only theme builders detected on your site can be selected. Undetected builders are shown as disabled.</p>'; |
| 1105 |
|
| 1106 |
# Show current auto-detection result |
| 1107 |
if (!empty($builders[$auto_detected]) && $auto_detected !== 'gutenberg') { |
| 1108 |
printf( |
| 1109 |
'<p class="description" style="margin-top: 8px; color: #0073aa;">🔍 Auto-detected: <strong>%s</strong> is active on this site.</p>', |
| 1110 |
esc_html($builders[$auto_detected]['label']) |
| 1111 |
); |
| 1112 |
} |
| 1113 |
|
| 1114 |
# Show description for the selected builder |
| 1115 |
echo '<div id="metasync-builder-descriptions" style="margin-top: 10px;">'; |
| 1116 |
foreach ($builders as $key => $builder) { |
| 1117 |
$is_visible = (empty($current) ? 'gutenberg' : $current) === $key; |
| 1118 |
printf( |
| 1119 |
'<p class="description metasync-builder-desc" data-builder="%s" style="%s">%s</p>', |
| 1120 |
esc_attr($key), |
| 1121 |
$is_visible ? '' : 'display:none;', |
| 1122 |
esc_html($builder['description']) |
| 1123 |
); |
| 1124 |
} |
| 1125 |
echo '</div>'; |
| 1126 |
|
| 1127 |
# Inline JS to toggle description on select change |
| 1128 |
?> |
| 1129 |
<script> |
| 1130 |
document.getElementById('default_page_builder')?.addEventListener('change', function() { |
| 1131 |
document.querySelectorAll('.metasync-builder-desc').forEach(function(el) { |
| 1132 |
el.style.display = el.dataset.builder === this.value ? '' : 'none'; |
| 1133 |
}.bind(this)); |
| 1134 |
}); |
| 1135 |
</script> |
| 1136 |
<?php |
| 1137 |
}, |
| 1138 |
$page_slug . '_general', |
| 1139 |
$SECTION_METASYNC |
| 1140 |
); |
| 1141 |
|
| 1142 |
add_settings_field( |
| 1143 |
'import_external_data', |
| 1144 |
'Import settings and data from SEO Plugins', |
| 1145 |
function() { |
| 1146 |
printf( |
| 1147 |
'<a href="%s" class="button button-secondary"><span class="dashicons dashicons-download" style="margin-top:3px;font-size:15px;width:15px;height:15px;"></span> Import from SEO Plugins</a>', |
| 1148 |
esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-import-external')) |
| 1149 |
); |
| 1150 |
printf('<p class="description">Import settings and data from other SEO plugins (Yoast, Rank Math, AIOSEO, etc).</p>'); |
| 1151 |
}, |
| 1152 |
$page_slug . '_general', |
| 1153 |
$SECTION_METASYNC |
| 1154 |
); |
| 1155 |
|
| 1156 |
|
| 1157 |
|
| 1158 |
/* |
| 1159 |
add field to save color for elementor and assign custom color to the heading |
| 1160 |
*/ |
| 1161 |
if(is_admin()){ |
| 1162 |
add_settings_field( |
| 1163 |
'white_label_plugin_name', |
| 1164 |
'Plugin Name', |
| 1165 |
function() use ($option_key) { |
| 1166 |
$value = Metasync::get_option('general')['white_label_plugin_name'] ?? ''; |
| 1167 |
printf('<input type="text" name="' . $option_key . '[general][white_label_plugin_name]" value="' . esc_attr($value) . '" maxlength="16" />'); |
| 1168 |
printf('<p class="description">This name will be used for general plugin branding (WordPress menus, page titles, and system messages). Maximum 16 characters.</p>'); |
| 1169 |
}, |
| 1170 |
$page_slug . '_branding', |
| 1171 |
$SECTION_METASYNC |
| 1172 |
); |
| 1173 |
|
| 1174 |
add_settings_field( |
| 1175 |
'whitelabel_otto_name', |
| 1176 |
'OTTO Name', |
| 1177 |
function() use ($option_key) { |
| 1178 |
$value = Metasync::get_option('general')['whitelabel_otto_name'] ?? ''; |
| 1179 |
printf('<input type="text" name="' . $option_key . '[general][whitelabel_otto_name]" value="' . esc_attr($value) . '" />'); |
| 1180 |
$example_name = !empty($value) ? $value : 'OTTO'; |
| 1181 |
printf('<p class="description">This name will be used for OTTO feature references (e.g., "Enable %s Server Side Rendering").</p>', esc_html($example_name)); |
| 1182 |
}, |
| 1183 |
$page_slug . '_branding', |
| 1184 |
$SECTION_METASYNC |
| 1185 |
); |
| 1186 |
|
| 1187 |
add_settings_field( |
| 1188 |
'whitelabel_logo_light_url', |
| 1189 |
'Logo (Light Theme)', |
| 1190 |
function() use ($option_key) { |
| 1191 |
$whitelabel_settings = Metasync::get_whitelabel_settings(); |
| 1192 |
$value = $whitelabel_settings['logo_light'] ?? ''; |
| 1193 |
printf('<input type="url" name="' . $option_key . '[whitelabel][logo_light]" value="' . esc_attr($value) . '" size="60" />'); |
| 1194 |
printf('<p class="description">Displayed when the admin UI is in light mode. Leave blank to use the default %s logo.</p>', esc_html(Metasync::get_effective_plugin_name())); |
| 1195 |
}, |
| 1196 |
$page_slug . '_branding', |
| 1197 |
$SECTION_METASYNC |
| 1198 |
); |
| 1199 |
|
| 1200 |
add_settings_field( |
| 1201 |
'whitelabel_logo_dark_url', |
| 1202 |
'Logo (Dark Theme)', |
| 1203 |
function() use ($option_key) { |
| 1204 |
$whitelabel_settings = Metasync::get_whitelabel_settings(); |
| 1205 |
$value = $whitelabel_settings['logo_dark'] ?? ''; |
| 1206 |
printf('<input type="url" name="' . $option_key . '[whitelabel][logo_dark]" value="' . esc_attr($value) . '" size="60" />'); |
| 1207 |
printf('<p class="description">Displayed when the admin UI is in dark mode. Leave blank to use the default %s logo.</p>', esc_html(Metasync::get_effective_plugin_name())); |
| 1208 |
}, |
| 1209 |
$page_slug . '_branding', |
| 1210 |
$SECTION_METASYNC |
| 1211 |
); |
| 1212 |
|
| 1213 |
add_settings_field( |
| 1214 |
'whitelabel_domain_url', |
| 1215 |
'Dashboard URL', |
| 1216 |
function() use ($option_key) { |
| 1217 |
$whitelabel_settings = Metasync::get_whitelabel_settings(); |
| 1218 |
$value = $whitelabel_settings['domain'] ?? ''; |
| 1219 |
printf('<input type="url" name="' . $option_key . '[whitelabel][domain]" value="' . esc_attr($value) . '" size="60" />'); |
| 1220 |
printf('<p class="description">Enter your whitelabel dashboard URL (e.g., https://yourdashboard.com). Used for branding purposes.</p>'); |
| 1221 |
}, |
| 1222 |
$page_slug . '_branding', |
| 1223 |
$SECTION_METASYNC |
| 1224 |
); |
| 1225 |
add_settings_field( |
| 1226 |
'white_label_plugin_description', |
| 1227 |
'Plugin Description', |
| 1228 |
function() use ($option_key) { |
| 1229 |
$value = Metasync::get_option('general')['white_label_plugin_description'] ?? ''; |
| 1230 |
printf('<input type="text" name="' . $option_key . '[general][white_label_plugin_description]" value="' . esc_attr($value) . '" />'); |
| 1231 |
}, |
| 1232 |
$page_slug . '_branding', |
| 1233 |
$SECTION_METASYNC |
| 1234 |
); |
| 1235 |
|
| 1236 |
add_settings_field( |
| 1237 |
'white_label_plugin_author', |
| 1238 |
'Author', |
| 1239 |
function() use ($option_key) { |
| 1240 |
$value = Metasync::get_option('general')['white_label_plugin_author'] ?? ''; |
| 1241 |
printf('<input type="text" name="' . $option_key . '[general][white_label_plugin_author]" value="' . esc_attr($value) . '" />'); |
| 1242 |
}, |
| 1243 |
$page_slug . '_branding', |
| 1244 |
$SECTION_METASYNC |
| 1245 |
); |
| 1246 |
|
| 1247 |
add_settings_field( |
| 1248 |
'white_label_plugin_author_uri', |
| 1249 |
'Author URI', |
| 1250 |
function() use ($option_key) { |
| 1251 |
$value = Metasync::get_option('general')['white_label_plugin_author_uri'] ?? ''; |
| 1252 |
# printf('<input type="text" name="' . $option_key . '[general][white_label_plugin_author_uri]" value="' . esc_attr($value) . '" />'); |
| 1253 |
# Fixed printf usage |
| 1254 |
printf('<input type="text" name="%s" value="%s" />', esc_attr($option_key . '[general][white_label_plugin_author_uri]'), esc_attr($value) ); |
| 1255 |
|
| 1256 |
}, |
| 1257 |
$page_slug . '_branding', |
| 1258 |
$SECTION_METASYNC |
| 1259 |
); |
| 1260 |
add_settings_field( |
| 1261 |
'white_label_plugin_uri', |
| 1262 |
'Plugin URI', |
| 1263 |
function() use ($option_key) { |
| 1264 |
$value = Metasync::get_option('general')['white_label_plugin_uri'] ?? ''; // New option for Plugin URI |
| 1265 |
printf('<input type="text" name="' . $option_key . '[general][white_label_plugin_uri]" value="' . esc_attr($value) . '" />'); |
| 1266 |
}, |
| 1267 |
$page_slug . '_branding', |
| 1268 |
$SECTION_METASYNC |
| 1269 |
); |
| 1270 |
register_setting(Metasync_Admin::option_group, |
| 1271 |
Metasync_Admin::option_key, |
| 1272 |
array(self::instance(), 'sanitize') |
| 1273 |
); |
| 1274 |
|
| 1275 |
// DEPRECATED: Menu Name and Menu Title fields removed |
| 1276 |
add_settings_field( |
| 1277 |
'white_label_plugin_menu_slug', |
| 1278 |
'Menu Slug', |
| 1279 |
function() use ($option_key) { |
| 1280 |
$value = Metasync::get_option('general')['white_label_plugin_menu_slug'] ?? ''; |
| 1281 |
printf('<input type="text" name="' . $option_key . '[general][white_label_plugin_menu_slug]" value="' . esc_attr($value) . '" placeholder="hip-advanced-seo" />'); |
| 1282 |
printf('<p class="description">Short identifier for the plugin\'s admin URL — lowercase letters, numbers, and dashes only (e.g. <code>hip-advanced-seo</code>). Don\'t paste a full web address.</p>'); |
| 1283 |
}, |
| 1284 |
$page_slug . '_branding', |
| 1285 |
$SECTION_METASYNC |
| 1286 |
); |
| 1287 |
add_settings_field( |
| 1288 |
'white_label_plugin_menu_icon', |
| 1289 |
'Menu Icon', |
| 1290 |
function() use ($option_key) { |
| 1291 |
$value = Metasync::get_option('general')['white_label_plugin_menu_icon'] ?? ''; |
| 1292 |
printf('<input type="text" name="' . $option_key . '[general][white_label_plugin_menu_icon]" value="' . esc_attr($value) . '" />'); |
| 1293 |
}, |
| 1294 |
$page_slug . '_branding', |
| 1295 |
$SECTION_METASYNC |
| 1296 |
); |
| 1297 |
} |
| 1298 |
// HIDDEN: Choose Style Option setting |
| 1299 |
/* |
| 1300 |
add_settings_field( |
| 1301 |
'enabled_plugin_css', |
| 1302 |
'Choose Style Option', |
| 1303 |
function() use ($option_key) { |
| 1304 |
$enabled_plugin_css = Metasync::get_option('general')['enabled_plugin_css'] ?? ''; |
| 1305 |
|
| 1306 |
// Output radio button for Default Style.css active |
| 1307 |
|
| 1308 |
printf( |
| 1309 |
'<input type="radio" id="enable_default" name="' . $option_key . '[general][enabled_plugin_css]" value="default" %s />', |
| 1310 |
($enabled_plugin_css == 'default'||$enabled_plugin_css =='') ? 'checked' : '' |
| 1311 |
); |
| 1312 |
printf('<label for="enable_default">Default</label><br>'); |
| 1313 |
|
| 1314 |
|
| 1315 |
// Output radio button for Metasync Style |
| 1316 |
printf( |
| 1317 |
'<input type="radio" id="enable_metasync" name="' . $option_key . '[general][enabled_plugin_css]" value="metasync" %s />', |
| 1318 |
($enabled_plugin_css == 'metasync') ? 'checked' : '' |
| 1319 |
); |
| 1320 |
printf('<label for="enable_metasync">Metasync Style</label>'); |
| 1321 |
|
| 1322 |
printf('<p class="description"> Choose the default page Style Sheet: Default or MetaSync.</p>'); |
| 1323 |
}, |
| 1324 |
$page_slug . '_branding', |
| 1325 |
$SECTION_METASYNC |
| 1326 |
); |
| 1327 |
*/ |
| 1328 |
|
| 1329 |
add_settings_field( |
| 1330 |
'enabled_elementor_plugin_css', |
| 1331 |
'Choose Elementor Font Style', |
| 1332 |
function() use ($option_key) { |
| 1333 |
$enabled_elementor_plugin_css = Metasync::get_option('general')['enabled_elementor_plugin_css'] ?? 'default'; |
| 1334 |
printf('<select name="' . $option_key . '[general][enabled_elementor_plugin_css]" id="heading_style">'); |
| 1335 |
printf('<option value="default"'.selected($enabled_elementor_plugin_css, 'default', false).'>Default</option>'); |
| 1336 |
printf('<option value="custom" '. selected($enabled_elementor_plugin_css, 'custom', false) . '>Custom</option>'); |
| 1337 |
printf('</select>'); |
| 1338 |
}, |
| 1339 |
$page_slug . '_branding', |
| 1340 |
$SECTION_METASYNC |
| 1341 |
); |
| 1342 |
add_settings_field( |
| 1343 |
'enabled_elementor_plugin_css_color', |
| 1344 |
'Choose Elementor Font Color', |
| 1345 |
function() use ($option_key) { |
| 1346 |
$enabled_elementor_plugin_css_color = Metasync::get_option('general')['enabled_elementor_plugin_css_color'] ?? '#000000'; |
| 1347 |
printf('<input type="color" id="elementor_default_color_metasync" name="' . $option_key . '[general][enabled_elementor_plugin_css_color]" value="'.$enabled_elementor_plugin_css_color.'">'); |
| 1348 |
}, |
| 1349 |
$page_slug . '_branding', |
| 1350 |
$SECTION_METASYNC |
| 1351 |
); |
| 1352 |
|
| 1353 |
add_settings_field( |
| 1354 |
'whitelabel_settings_password', |
| 1355 |
'Settings Password', |
| 1356 |
function() use ($option_key) { |
| 1357 |
$whitelabel_settings = Metasync::get_whitelabel_settings(); |
| 1358 |
$value = $whitelabel_settings['settings_password'] ?? ''; |
| 1359 |
printf('<input type="password" name="' . $option_key . '[whitelabel][settings_password]" value="' . esc_attr($value) . '" size="30" autocomplete="new-password" />'); |
| 1360 |
printf('<p class="description">Set a custom password to protect the branding settings section.</p>'); |
| 1361 |
}, |
| 1362 |
$page_slug . '_branding', |
| 1363 |
$SECTION_METASYNC |
| 1364 |
); |
| 1365 |
|
| 1366 |
add_settings_field( |
| 1367 |
'whitelabel_recovery_email', |
| 1368 |
'Recovery Email', |
| 1369 |
function() use ($option_key) { |
| 1370 |
$whitelabel_settings = Metasync::get_whitelabel_settings(); |
| 1371 |
$value = $whitelabel_settings['recovery_email'] ?? ''; |
| 1372 |
$has_password = !empty($whitelabel_settings['settings_password']); |
| 1373 |
printf('<input type="email" name="' . $option_key . '[whitelabel][recovery_email]" value="' . esc_attr($value) . '" size="30" autocomplete="email" %s />', $has_password ? 'required' : ''); |
| 1374 |
printf('<p class="description">Email address to receive password recovery. <strong>Required when password is set.</strong></p>'); |
| 1375 |
}, |
| 1376 |
$page_slug . '_branding', |
| 1377 |
$SECTION_METASYNC |
| 1378 |
); |
| 1379 |
|
| 1380 |
add_settings_field( |
| 1381 |
'whitelabel_quick_links', |
| 1382 |
'Quick Links', |
| 1383 |
function() use ($option_key) { |
| 1384 |
$whitelabel_settings = Metasync::get_whitelabel_settings(); |
| 1385 |
$links = isset($whitelabel_settings['quick_links']) && is_array($whitelabel_settings['quick_links']) |
| 1386 |
? $whitelabel_settings['quick_links'] |
| 1387 |
: []; |
| 1388 |
// Ensure at least 5 rows |
| 1389 |
while (count($links) < 5) { |
| 1390 |
$links[] = ['label' => '', 'url' => '', 'external' => false]; |
| 1391 |
} |
| 1392 |
echo '<p class="description" style="margin-bottom:8px;">Add up to 5 links to show in the sidebar Quick Links widget. Leave blank to hide the widget when whitelabeling is active.</p>'; |
| 1393 |
echo '<style>.metasync-quick-links-table.widefat, .metasync-quick-links-table.widefat th, .metasync-quick-links-table.widefat td { background:transparent; border-color:var(--dashboard-border, #374151); }</style>'; |
| 1394 |
echo '<table class="widefat metasync-quick-links-table" style="max-width:560px;">'; |
| 1395 |
echo '<thead><tr><th>Label</th><th>URL</th><th>Open in new tab</th></tr></thead><tbody>'; |
| 1396 |
foreach (array_slice($links, 0, 5) as $i => $link) { |
| 1397 |
$label = esc_attr($link['label'] ?? ''); |
| 1398 |
$url = esc_attr($link['url'] ?? ''); |
| 1399 |
$external = !empty($link['external']); |
| 1400 |
printf( |
| 1401 |
'<tr> |
| 1402 |
<td><input type="text" name="%s[whitelabel][quick_links][%d][label]" value="%s" placeholder="Link label" style="width:100%%" /></td> |
| 1403 |
<td><input type="url" name="%s[whitelabel][quick_links][%d][url]" value="%s" placeholder="https://…" style="width:100%%" /></td> |
| 1404 |
<td style="text-align:center"><input type="checkbox" name="%s[whitelabel][quick_links][%d][external]" value="1" %s /></td> |
| 1405 |
</tr>', |
| 1406 |
esc_attr($option_key), $i, $label, |
| 1407 |
esc_attr($option_key), $i, $url, |
| 1408 |
esc_attr($option_key), $i, checked($external, true, false) |
| 1409 |
); |
| 1410 |
} |
| 1411 |
echo '</tbody></table>'; |
| 1412 |
}, |
| 1413 |
$page_slug . '_branding', |
| 1414 |
$SECTION_METASYNC |
| 1415 |
); |
| 1416 |
|
| 1417 |
// ====================================================================== |
| 1418 |
// Schema Markup Settings — dedicated page (SEO → Schema Markup) |
| 1419 |
// ====================================================================== |
| 1420 |
$schema_page = $page_slug . '_schema-markup'; |
| 1421 |
|
| 1422 |
add_settings_section( |
| 1423 |
'metasync_schema_org', |
| 1424 |
'Organization Schema (Site-Wide)', |
| 1425 |
function() { |
| 1426 |
echo '<p>This Organization schema is injected on every page of your site.</p>'; |
| 1427 |
}, |
| 1428 |
$schema_page |
| 1429 |
); |
| 1430 |
|
| 1431 |
add_settings_field( |
| 1432 |
'schema_org_name', |
| 1433 |
'Organization Name', |
| 1434 |
function() use ($option_key) { |
| 1435 |
$schema = Metasync::get_option('schema') ?? []; |
| 1436 |
$value = $schema['org_name'] ?? ''; |
| 1437 |
printf('<input type="text" name="' . $option_key . '[schema][org_name]" value="%s" size="40" placeholder="Your organization name" />', esc_attr($value)); |
| 1438 |
}, |
| 1439 |
$schema_page, |
| 1440 |
'metasync_schema_org' |
| 1441 |
); |
| 1442 |
|
| 1443 |
add_settings_field( |
| 1444 |
'schema_org_url', |
| 1445 |
'Organization URL', |
| 1446 |
function() use ($option_key) { |
| 1447 |
$schema = Metasync::get_option('schema') ?? []; |
| 1448 |
$value = $schema['org_url'] ?? ''; |
| 1449 |
printf('<input type="url" name="' . $option_key . '[schema][org_url]" value="%s" size="40" placeholder="https://example.com" />', esc_attr($value)); |
| 1450 |
}, |
| 1451 |
$schema_page, |
| 1452 |
'metasync_schema_org' |
| 1453 |
); |
| 1454 |
|
| 1455 |
add_settings_field( |
| 1456 |
'schema_org_logo', |
| 1457 |
'Organization Logo URL', |
| 1458 |
function() use ($option_key) { |
| 1459 |
$schema = Metasync::get_option('schema') ?? []; |
| 1460 |
$value = $schema['org_logo'] ?? ''; |
| 1461 |
printf('<input type="url" name="' . $option_key . '[schema][org_logo]" value="%s" size="40" placeholder="https://example.com/logo.png" />', esc_attr($value)); |
| 1462 |
}, |
| 1463 |
$schema_page, |
| 1464 |
'metasync_schema_org' |
| 1465 |
); |
| 1466 |
|
| 1467 |
add_settings_field( |
| 1468 |
'schema_org_contact_telephone', |
| 1469 |
'Contact Telephone', |
| 1470 |
function() use ($option_key) { |
| 1471 |
$schema = Metasync::get_option('schema') ?? []; |
| 1472 |
$value = $schema['org_contact_telephone'] ?? ''; |
| 1473 |
printf('<input type="text" name="' . $option_key . '[schema][org_contact_telephone]" value="%s" size="30" placeholder="+1-555-1234" />', esc_attr($value)); |
| 1474 |
}, |
| 1475 |
$schema_page, |
| 1476 |
'metasync_schema_org' |
| 1477 |
); |
| 1478 |
|
| 1479 |
add_settings_field( |
| 1480 |
'schema_org_contact_type', |
| 1481 |
'Contact Type', |
| 1482 |
function() use ($option_key) { |
| 1483 |
$schema = Metasync::get_option('schema') ?? []; |
| 1484 |
$value = $schema['org_contact_type'] ?? ''; |
| 1485 |
printf('<input type="text" name="' . $option_key . '[schema][org_contact_type]" value="%s" size="30" placeholder="customer support" />', esc_attr($value)); |
| 1486 |
}, |
| 1487 |
$schema_page, |
| 1488 |
'metasync_schema_org' |
| 1489 |
); |
| 1490 |
|
| 1491 |
add_settings_field( |
| 1492 |
'schema_org_same_as', |
| 1493 |
'Social Profile URLs', |
| 1494 |
function() use ($option_key) { |
| 1495 |
$schema = Metasync::get_option('schema') ?? []; |
| 1496 |
$value = $schema['org_same_as'] ?? ''; |
| 1497 |
printf('<textarea name="' . $option_key . '[schema][org_same_as]" rows="4" cols="50" placeholder="One URL per line https://facebook.com/... https://twitter.com/...">%s</textarea>', esc_textarea($value)); |
| 1498 |
echo '<p class="description">Enter one social profile URL per line.</p>'; |
| 1499 |
}, |
| 1500 |
$schema_page, |
| 1501 |
'metasync_schema_org' |
| 1502 |
); |
| 1503 |
|
| 1504 |
add_settings_section( |
| 1505 |
'metasync_schema_website', |
| 1506 |
'WebSite Schema (Homepage Only)', |
| 1507 |
function() { |
| 1508 |
echo '<p>This WebSite schema is injected on the homepage only. Enables the Google Sitelinks Searchbox.</p>'; |
| 1509 |
}, |
| 1510 |
$schema_page |
| 1511 |
); |
| 1512 |
|
| 1513 |
add_settings_field( |
| 1514 |
'schema_website_name', |
| 1515 |
'WebSite Name', |
| 1516 |
function() use ($option_key) { |
| 1517 |
$schema = Metasync::get_option('schema') ?? []; |
| 1518 |
$value = $schema['website_name'] ?? ''; |
| 1519 |
printf('<input type="text" name="' . $option_key . '[schema][website_name]" value="%s" size="40" placeholder="Your website name" />', esc_attr($value)); |
| 1520 |
}, |
| 1521 |
$schema_page, |
| 1522 |
'metasync_schema_website' |
| 1523 |
); |
| 1524 |
|
| 1525 |
add_settings_field( |
| 1526 |
'schema_website_url', |
| 1527 |
'WebSite URL', |
| 1528 |
function() use ($option_key) { |
| 1529 |
$schema = Metasync::get_option('schema') ?? []; |
| 1530 |
$value = $schema['website_url'] ?? ''; |
| 1531 |
printf('<input type="url" name="' . $option_key . '[schema][website_url]" value="%s" size="40" placeholder="https://example.com" />', esc_attr($value)); |
| 1532 |
}, |
| 1533 |
$schema_page, |
| 1534 |
'metasync_schema_website' |
| 1535 |
); |
| 1536 |
|
| 1537 |
add_settings_field( |
| 1538 |
'schema_website_searchbox', |
| 1539 |
'Enable Sitelinks Searchbox', |
| 1540 |
function() use ($option_key) { |
| 1541 |
$schema = Metasync::get_option('schema') ?? []; |
| 1542 |
$value = $schema['website_searchbox'] ?? false; |
| 1543 |
printf( |
| 1544 |
'<input type="checkbox" name="' . $option_key . '[schema][website_searchbox]" value="1" %s />', |
| 1545 |
checked($value, true, false) |
| 1546 |
); |
| 1547 |
echo '<span class="description">Adds SearchAction to WebSite schema for Google Sitelinks Searchbox.</span>'; |
| 1548 |
}, |
| 1549 |
$schema_page, |
| 1550 |
'metasync_schema_website' |
| 1551 |
); |
| 1552 |
|
| 1553 |
add_settings_section( |
| 1554 |
'metasync_schema_compat', |
| 1555 |
'Compatibility', |
| 1556 |
function() { |
| 1557 |
echo '<p>Control how MetaSync interacts with other plugins that output structured data.</p>'; |
| 1558 |
}, |
| 1559 |
$schema_page |
| 1560 |
); |
| 1561 |
|
| 1562 |
add_settings_field( |
| 1563 |
'schema_override_woocommerce', |
| 1564 |
'Override WooCommerce Product Schema', |
| 1565 |
function() use ($option_key) { |
| 1566 |
$schema = Metasync::get_option('schema') ?? []; |
| 1567 |
$value = $schema['override_woocommerce_schema'] ?? false; |
| 1568 |
printf( |
| 1569 |
'<input type="checkbox" name="' . $option_key . '[schema][override_woocommerce_schema]" value="1" %s />', |
| 1570 |
checked($value, true, false) |
| 1571 |
); |
| 1572 |
echo '<span class="description">When unchecked (default), MetaSync suppresses its Product schema when WooCommerce is active to avoid duplication. Check to override WooCommerce\'s native Product schema.</span>'; |
| 1573 |
}, |
| 1574 |
$schema_page, |
| 1575 |
'metasync_schema_compat' |
| 1576 |
); |
| 1577 |
|
| 1578 |
// ---------------------------------------------------------------- |
| 1579 |
// Site Verification page |
| 1580 |
// ---------------------------------------------------------------- |
| 1581 |
add_settings_section( |
| 1582 |
$SECTION_SEARCHENGINE, |
| 1583 |
'', |
| 1584 |
function() {}, |
| 1585 |
$page_slug . '_searchengines-verification' |
| 1586 |
); |
| 1587 |
|
| 1588 |
add_settings_field( |
| 1589 |
'google_site_verification', |
| 1590 |
'Google Search Console', |
| 1591 |
array( Metasync_Settings_Fields::instance(), 'google_site_verification_callback' ), |
| 1592 |
$page_slug . '_searchengines-verification', |
| 1593 |
$SECTION_SEARCHENGINE |
| 1594 |
); |
| 1595 |
|
| 1596 |
add_settings_field( |
| 1597 |
'bing_site_verification', |
| 1598 |
'Bing Webmaster Tools', |
| 1599 |
array( Metasync_Settings_Fields::instance(), 'bing_site_verification_callback' ), |
| 1600 |
$page_slug . '_searchengines-verification', |
| 1601 |
$SECTION_SEARCHENGINE |
| 1602 |
); |
| 1603 |
|
| 1604 |
add_settings_field( |
| 1605 |
'yandex_site_verification', |
| 1606 |
'Yandex Webmaster', |
| 1607 |
array( Metasync_Settings_Fields::instance(), 'yandex_site_verification_callback' ), |
| 1608 |
$page_slug . '_searchengines-verification', |
| 1609 |
$SECTION_SEARCHENGINE |
| 1610 |
); |
| 1611 |
|
| 1612 |
add_settings_field( |
| 1613 |
'pinterest_site_verification', |
| 1614 |
'Pinterest', |
| 1615 |
array( Metasync_Settings_Fields::instance(), 'pinterest_site_verification_callback' ), |
| 1616 |
$page_slug . '_searchengines-verification', |
| 1617 |
$SECTION_SEARCHENGINE |
| 1618 |
); |
| 1619 |
|
| 1620 |
add_settings_field( |
| 1621 |
'baidu_site_verification', |
| 1622 |
'Baidu', |
| 1623 |
function() use ($option_key) { |
| 1624 |
$value = Metasync::get_option('searchengines')['baidu_site_verification'] ?? ''; |
| 1625 |
printf( '<input type="text" id="baidu_site_verification" name="' . $option_key . '[searchengines][baidu_site_verification]" value="%s" size="50" />', esc_attr( $value ) ); |
| 1626 |
}, |
| 1627 |
$page_slug . '_searchengines-verification', |
| 1628 |
$SECTION_SEARCHENGINE |
| 1629 |
); |
| 1630 |
|
| 1631 |
add_settings_field( |
| 1632 |
'alexa_site_verification', |
| 1633 |
'Alexa', |
| 1634 |
function() use ($option_key) { |
| 1635 |
$value = Metasync::get_option('searchengines')['alexa_site_verification'] ?? ''; |
| 1636 |
printf( '<input type="text" id="alexa_site_verification" name="' . $option_key . '[searchengines][alexa_site_verification]" value="%s" size="50" />', esc_attr( $value ) ); |
| 1637 |
}, |
| 1638 |
$page_slug . '_searchengines-verification', |
| 1639 |
$SECTION_SEARCHENGINE |
| 1640 |
); |
| 1641 |
|
| 1642 |
add_settings_field( |
| 1643 |
'norton_save_site_verification', |
| 1644 |
'Norton Safe Web', |
| 1645 |
function() use ($option_key) { |
| 1646 |
$value = Metasync::get_option('searchengines')['norton_save_site_verification'] ?? ''; |
| 1647 |
printf( '<input type="text" id="norton_save_site_verification" name="' . $option_key . '[searchengines][norton_save_site_verification]" value="%s" size="50" />', esc_attr( $value ) ); |
| 1648 |
}, |
| 1649 |
$page_slug . '_searchengines-verification', |
| 1650 |
$SECTION_SEARCHENGINE |
| 1651 |
); |
| 1652 |
|
| 1653 |
// ---------------------------------------------------------------- |
| 1654 |
// Code Snippets page |
| 1655 |
// ---------------------------------------------------------------- |
| 1656 |
add_settings_section( |
| 1657 |
$SECTION_CODESNIPPETS, |
| 1658 |
'', |
| 1659 |
function() {}, |
| 1660 |
$page_slug . '_code-snippets' |
| 1661 |
); |
| 1662 |
|
| 1663 |
add_settings_field( |
| 1664 |
'header_snippet', |
| 1665 |
'Header Code', |
| 1666 |
array( Metasync_Settings_Fields::instance(), 'header_snippets_callback' ), |
| 1667 |
$page_slug . '_code-snippets', |
| 1668 |
$SECTION_CODESNIPPETS |
| 1669 |
); |
| 1670 |
|
| 1671 |
add_settings_field( |
| 1672 |
'footer_snippet', |
| 1673 |
'Footer Code', |
| 1674 |
array( Metasync_Settings_Fields::instance(), 'footer_snippets_callback' ), |
| 1675 |
$page_slug . '_code-snippets', |
| 1676 |
$SECTION_CODESNIPPETS |
| 1677 |
); |
| 1678 |
|
| 1679 |
// ---------------------------------------------------------------- |
| 1680 |
// Local Business page |
| 1681 |
// ---------------------------------------------------------------- |
| 1682 |
add_settings_section( |
| 1683 |
$SECTION_LOCALSEO, |
| 1684 |
'', |
| 1685 |
function() {}, |
| 1686 |
$page_slug . '_local-seo' |
| 1687 |
); |
| 1688 |
|
| 1689 |
$fields_instance = Metasync_Settings_Fields::instance(); |
| 1690 |
|
| 1691 |
add_settings_field( 'local_seo_person_organization', 'Type', array( $fields_instance, 'local_seo_person_organization_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1692 |
add_settings_field( 'local_seo_name', 'Name', array( $fields_instance, 'local_seo_name_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1693 |
add_settings_field( 'local_seo_logo', 'Logo', array( $fields_instance, 'local_seo_logo_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1694 |
add_settings_field( 'local_seo_url', 'Website URL', array( $fields_instance, 'local_seo_url_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1695 |
add_settings_field( 'local_seo_email', 'Email', array( $fields_instance, 'local_seo_email_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1696 |
add_settings_field( 'local_seo_phone', 'Phone', array( $fields_instance, 'local_seo_phone_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1697 |
add_settings_field( 'local_seo_address', 'Address', array( $fields_instance, 'local_seo_address_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1698 |
add_settings_field( 'local_seo_business_type', 'Business Type', array( $fields_instance, 'local_seo_business_type_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1699 |
add_settings_field( 'local_seo_opening_hours', 'Opening Hours', array( $fields_instance, 'local_seo_opening_hours_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1700 |
add_settings_field( 'local_seo_phone_numbers', 'Phone Numbers', array( $fields_instance, 'local_seo_phone_numbers_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1701 |
add_settings_field( 'local_seo_price_range', 'Price Range', array( $fields_instance, 'local_seo_price_range_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1702 |
add_settings_field( 'local_seo_about_page', 'About Page', array( $fields_instance, 'local_seo_about_page_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1703 |
add_settings_field( 'local_seo_contact_page', 'Contact Page', array( $fields_instance, 'local_seo_contact_page_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1704 |
add_settings_field( 'local_seo_map_key', 'Google Maps Key', array( $fields_instance, 'local_seo_map_key_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1705 |
add_settings_field( 'local_seo_geo_coordinates', 'Geo Coordinates', array( $fields_instance, 'local_seo_geo_coordinates_callback' ), $page_slug . '_local-seo', $SECTION_LOCALSEO ); |
| 1706 |
|
| 1707 |
// ---------------------------------------------------------------- |
| 1708 |
// Breadcrumbs page |
| 1709 |
// ---------------------------------------------------------------- |
| 1710 |
add_settings_section( |
| 1711 |
$SECTION_BREADCRUMBS, |
| 1712 |
'', |
| 1713 |
function() {}, |
| 1714 |
$page_slug . '_breadcrumbs' |
| 1715 |
); |
| 1716 |
|
| 1717 |
add_settings_field( |
| 1718 |
'breadcrumbs_enabled', |
| 1719 |
'Enable Breadcrumbs', |
| 1720 |
function() use ($option_key) { |
| 1721 |
$value = Metasync::get_option('breadcrumbs')['enabled'] ?? true; |
| 1722 |
printf( |
| 1723 |
'<input type="checkbox" id="breadcrumbs_enabled" name="' . $option_key . '[breadcrumbs][enabled]" value="1" %s />', |
| 1724 |
checked(1, $value, false) |
| 1725 |
); |
| 1726 |
echo '<p class="description">Output breadcrumb trail HTML on your site.</p>'; |
| 1727 |
}, |
| 1728 |
$page_slug . '_breadcrumbs', |
| 1729 |
$SECTION_BREADCRUMBS |
| 1730 |
); |
| 1731 |
|
| 1732 |
add_settings_field( |
| 1733 |
'breadcrumbs_separator', |
| 1734 |
'Separator', |
| 1735 |
function() use ($option_key) { |
| 1736 |
$value = Metasync::get_option('breadcrumbs')['separator'] ?? '»'; |
| 1737 |
printf( |
| 1738 |
'<input type="text" id="breadcrumbs_separator" name="' . $option_key . '[breadcrumbs][separator]" value="%s" size="10" />', |
| 1739 |
esc_attr($value) |
| 1740 |
); |
| 1741 |
echo '<p class="description">Character or HTML entity shown between crumbs (e.g. » or /).</p>'; |
| 1742 |
}, |
| 1743 |
$page_slug . '_breadcrumbs', |
| 1744 |
$SECTION_BREADCRUMBS |
| 1745 |
); |
| 1746 |
|
| 1747 |
add_settings_field( |
| 1748 |
'breadcrumbs_home_label', |
| 1749 |
'Home Label', |
| 1750 |
function() use ($option_key) { |
| 1751 |
$value = Metasync::get_option('breadcrumbs')['home_label'] ?? 'Home'; |
| 1752 |
printf( |
| 1753 |
'<input type="text" id="breadcrumbs_home_label" name="' . $option_key . '[breadcrumbs][home_label]" value="%s" size="30" />', |
| 1754 |
esc_attr($value) |
| 1755 |
); |
| 1756 |
}, |
| 1757 |
$page_slug . '_breadcrumbs', |
| 1758 |
$SECTION_BREADCRUMBS |
| 1759 |
); |
| 1760 |
|
| 1761 |
add_settings_field( |
| 1762 |
'breadcrumbs_home_url', |
| 1763 |
'Home URL', |
| 1764 |
function() use ($option_key) { |
| 1765 |
$value = Metasync::get_option('breadcrumbs')['home_url'] ?? ''; |
| 1766 |
printf( |
| 1767 |
'<input type="url" id="breadcrumbs_home_url" name="' . $option_key . '[breadcrumbs][home_url]" value="%s" size="50" />', |
| 1768 |
esc_attr($value) |
| 1769 |
); |
| 1770 |
echo '<p class="description">Leave blank to use the site home URL.</p>'; |
| 1771 |
}, |
| 1772 |
$page_slug . '_breadcrumbs', |
| 1773 |
$SECTION_BREADCRUMBS |
| 1774 |
); |
| 1775 |
|
| 1776 |
add_settings_field( |
| 1777 |
'breadcrumbs_show_current_page', |
| 1778 |
'Show Current Page', |
| 1779 |
function() use ($option_key) { |
| 1780 |
$value = Metasync::get_option('breadcrumbs')['show_current_page'] ?? true; |
| 1781 |
printf( |
| 1782 |
'<input type="checkbox" id="breadcrumbs_show_current_page" name="' . $option_key . '[breadcrumbs][show_current_page]" value="1" %s />', |
| 1783 |
checked(1, $value, false) |
| 1784 |
); |
| 1785 |
echo '<p class="description">Include the current page as the last (non-linked) crumb.</p>'; |
| 1786 |
}, |
| 1787 |
$page_slug . '_breadcrumbs', |
| 1788 |
$SECTION_BREADCRUMBS |
| 1789 |
); |
| 1790 |
|
| 1791 |
add_settings_field( |
| 1792 |
'breadcrumbs_prefix_text', |
| 1793 |
'Prefix Text', |
| 1794 |
function() use ($option_key) { |
| 1795 |
$value = Metasync::get_option('breadcrumbs')['prefix_text'] ?? ''; |
| 1796 |
printf( |
| 1797 |
'<input type="text" id="breadcrumbs_prefix_text" name="' . $option_key . '[breadcrumbs][prefix_text]" value="%s" size="30" />', |
| 1798 |
esc_attr($value) |
| 1799 |
); |
| 1800 |
echo '<p class="description">Optional text before the breadcrumb trail (e.g. "You are here:").</p>'; |
| 1801 |
}, |
| 1802 |
$page_slug . '_breadcrumbs', |
| 1803 |
$SECTION_BREADCRUMBS |
| 1804 |
); |
| 1805 |
|
| 1806 |
add_settings_field( |
| 1807 |
'breadcrumbs_archive_label_format', |
| 1808 |
'Archive Label Format', |
| 1809 |
function() use ($option_key) { |
| 1810 |
$value = Metasync::get_option('breadcrumbs')['archive_label_format'] ?? '{name}'; |
| 1811 |
printf( |
| 1812 |
'<input type="text" id="breadcrumbs_archive_label_format" name="' . $option_key . '[breadcrumbs][archive_label_format]" value="%s" size="30" />', |
| 1813 |
esc_attr($value) |
| 1814 |
); |
| 1815 |
echo '<p class="description">Format for archive crumb labels. Use <code>{name}</code> as placeholder for the archive name.</p>'; |
| 1816 |
}, |
| 1817 |
$page_slug . '_breadcrumbs', |
| 1818 |
$SECTION_BREADCRUMBS |
| 1819 |
); |
| 1820 |
|
| 1821 |
# Open Graph / Article meta toggles — surface each new tag family as its |
| 1822 |
# own opt-out checkbox on the main Settings page (Open Graph accordion). |
| 1823 |
add_settings_field( |
| 1824 |
'og_image_dimensions', |
| 1825 |
'OG Image Dimensions', |
| 1826 |
array(Metasync_Settings_Fields::instance(), 'og_image_dimensions_callback'), |
| 1827 |
$page_slug . '_general', |
| 1828 |
$SECTION_METASYNC |
| 1829 |
); |
| 1830 |
|
| 1831 |
add_settings_field( |
| 1832 |
'article_timestamps', |
| 1833 |
'Article Timestamps', |
| 1834 |
array(Metasync_Settings_Fields::instance(), 'article_timestamps_callback'), |
| 1835 |
$page_slug . '_general', |
| 1836 |
$SECTION_METASYNC |
| 1837 |
); |
| 1838 |
|
| 1839 |
add_settings_field( |
| 1840 |
'article_author', |
| 1841 |
'Article Author', |
| 1842 |
array(Metasync_Settings_Fields::instance(), 'article_author_callback'), |
| 1843 |
$page_slug . '_general', |
| 1844 |
$SECTION_METASYNC |
| 1845 |
); |
| 1846 |
|
| 1847 |
add_settings_field( |
| 1848 |
'article_section', |
| 1849 |
'Article Section', |
| 1850 |
array(Metasync_Settings_Fields::instance(), 'article_section_callback'), |
| 1851 |
$page_slug . '_general', |
| 1852 |
$SECTION_METASYNC |
| 1853 |
); |
| 1854 |
|
| 1855 |
add_settings_field( |
| 1856 |
'article_tags', |
| 1857 |
'Article Tags', |
| 1858 |
array(Metasync_Settings_Fields::instance(), 'article_tags_callback'), |
| 1859 |
$page_slug . '_general', |
| 1860 |
$SECTION_METASYNC |
| 1861 |
); |
| 1862 |
|
| 1863 |
add_settings_field( |
| 1864 |
'twitter_image_alt', |
| 1865 |
'Twitter Image Alt', |
| 1866 |
array(Metasync_Settings_Fields::instance(), 'twitter_image_alt_callback'), |
| 1867 |
$page_slug . '_general', |
| 1868 |
$SECTION_METASYNC |
| 1869 |
); |
| 1870 |
|
| 1871 |
} |
| 1872 |
|
| 1873 |
/** |
| 1874 |
* Sanitize each setting field as needed. |
| 1875 |
* |
| 1876 |
* Formerly Metasync_Admin::sanitize(). |
| 1877 |
* |
| 1878 |
* @param array $input Contains all settings fields as array keys |
| 1879 |
* @return array Sanitized settings |
| 1880 |
*/ |
| 1881 |
public function sanitize($input) |
| 1882 |
{ |
| 1883 |
$new_input = Metasync::get_option(); |
| 1884 |
$whitelabel_validation_failed = false; |
| 1885 |
|
| 1886 |
# Determine which tab is being submitted (same logic as AJAX handler) |
| 1887 |
# Check POST first (from AJAX), then GET (from form action URL) |
| 1888 |
$active_tab = 'general'; |
| 1889 |
if (isset($_POST['active_tab'])) { |
| 1890 |
$active_tab = sanitize_text_field($_POST['active_tab']); |
| 1891 |
} elseif (isset($_GET['tab'])) { |
| 1892 |
$active_tab = sanitize_text_field($_GET['tab']); |
| 1893 |
} elseif (isset($_POST['_wp_http_referer'])) { |
| 1894 |
# Parse referer URL to get tab parameter |
| 1895 |
$referer = wp_parse_url($_POST['_wp_http_referer']); |
| 1896 |
if (isset($referer['query'])) { |
| 1897 |
parse_str($referer['query'], $referer_params); |
| 1898 |
if (isset($referer_params['tab'])) { |
| 1899 |
$active_tab = sanitize_text_field($referer_params['tab']); |
| 1900 |
} |
| 1901 |
} |
| 1902 |
} |
| 1903 |
$general_tab_submitted = ($active_tab === 'general'); |
| 1904 |
|
| 1905 |
// General Settings |
| 1906 |
if (isset($input['general']['apikey'])) { |
| 1907 |
$new_input['general']['apikey'] = sanitize_text_field($input['general']['apikey']); |
| 1908 |
} |
| 1909 |
|
| 1910 |
// Meta Box Visibility Settings |
| 1911 |
if (isset($input['general']) && $general_tab_submitted) { |
| 1912 |
$checkbox_fields = [ |
| 1913 |
'disable_common_robots_metabox', |
| 1914 |
'disable_advance_robots_metabox', |
| 1915 |
'disable_redirection_metabox', |
| 1916 |
'disable_canonical_metabox', |
| 1917 |
'disable_social_opengraph_metabox', |
| 1918 |
'disable_schema_markup_metabox', |
| 1919 |
'disable_seo_metabox', |
| 1920 |
'open_external_links' |
| 1921 |
]; |
| 1922 |
|
| 1923 |
foreach ($checkbox_fields as $field) { |
| 1924 |
if (isset($input['general'][$field])) { |
| 1925 |
$new_input['general'][$field] = filter_var($input['general'][$field], FILTER_VALIDATE_BOOLEAN); |
| 1926 |
} else { |
| 1927 |
$new_input['general'][$field] = false; |
| 1928 |
} |
| 1929 |
// The final array_merge($new_input, $input) is shallow, so |
| 1930 |
// $input['general'] replaces $new_input['general'] wholesale. |
| 1931 |
// Write the sanitized boolean back into $input so it survives. |
| 1932 |
$input['general'][$field] = $new_input['general'][$field]; |
| 1933 |
} |
| 1934 |
} |
| 1935 |
|
| 1936 |
// Site Verification Settings |
| 1937 |
if (isset($input['searchengines']['bing_site_verification'])) { |
| 1938 |
$new_input['searchengines']['bing_site_verification'] = sanitize_text_field($input['searchengines']['bing_site_verification']); |
| 1939 |
} |
| 1940 |
if (isset($input['searchengines']['baidu_site_verification'])) { |
| 1941 |
$new_input['searchengines']['baidu_site_verification'] = sanitize_text_field($input['searchengines']['baidu_site_verification']); |
| 1942 |
} |
| 1943 |
if (isset($input['searchengines']['alexa_site_verification'])) { |
| 1944 |
$new_input['searchengines']['alexa_site_verification'] = sanitize_text_field($input['searchengines']['alexa_site_verification']); |
| 1945 |
} |
| 1946 |
if (isset($input['searchengines']['yandex_site_verification'])) { |
| 1947 |
$new_input['searchengines']['yandex_site_verification'] = sanitize_text_field($input['searchengines']['yandex_site_verification']); |
| 1948 |
} |
| 1949 |
if (isset($input['searchengines']['google_site_verification'])) { |
| 1950 |
$new_input['searchengines']['google_site_verification'] = sanitize_text_field($input['searchengines']['google_site_verification']); |
| 1951 |
} |
| 1952 |
if (isset($input['searchengines']['pinterest_site_verification'])) { |
| 1953 |
$new_input['searchengines']['pinterest_site_verification'] = sanitize_text_field($input['searchengines']['pinterest_site_verification']); |
| 1954 |
} |
| 1955 |
if (isset($input['searchengines']['norton_save_site_verification'])) { |
| 1956 |
$new_input['searchengines']['norton_save_site_verification'] = sanitize_text_field($input['searchengines']['norton_save_site_verification']); |
| 1957 |
} |
| 1958 |
|
| 1959 |
// Local Business SEO Settings |
| 1960 |
if (isset($input['localseo']['local_seo_person_organization'])) { |
| 1961 |
$new_input['localseo']['local_seo_person_organization'] = sanitize_text_field($input['localseo']['local_seo_person_organization']); |
| 1962 |
} |
| 1963 |
if (isset($input['localseo']['local_seo_name'])) { |
| 1964 |
$new_input['localseo']['local_seo_name'] = sanitize_text_field($input['localseo']['local_seo_name']); |
| 1965 |
} |
| 1966 |
if (isset($input['localseo']['local_seo_logo'])) { |
| 1967 |
$new_input['localseo']['local_seo_logo'] = sanitize_url($input['localseo']['local_seo_logo']); |
| 1968 |
} |
| 1969 |
if (isset($input['localseo']['local_seo_url'])) { |
| 1970 |
$new_input['localseo']['local_seo_url'] = sanitize_url($input['localseo']['local_seo_url']); |
| 1971 |
} |
| 1972 |
if (isset($input['localseo']['local_seo_email'])) { |
| 1973 |
$new_input['localseo']['local_seo_email'] = sanitize_email($input['localseo']['local_seo_email']); |
| 1974 |
} |
| 1975 |
if (isset($input['localseo']['local_seo_phone'])) { |
| 1976 |
$new_input['localseo']['local_seo_phone'] = sanitize_text_field($input['localseo']['local_seo_phone']); |
| 1977 |
} |
| 1978 |
if (isset($input['localseo']['address']['street'])) { |
| 1979 |
$new_input['localseo']['address']['street'] = sanitize_text_field($input['localseo']['address']['street']); |
| 1980 |
} |
| 1981 |
if (isset($input['localseo']['address']['locality'])) { |
| 1982 |
$new_input['localseo']['address']['locality'] = sanitize_text_field($input['localseo']['address']['locality']); |
| 1983 |
} |
| 1984 |
if (isset($input['localseo']['address']['region'])) { |
| 1985 |
$new_input['localseo']['address']['region'] = sanitize_text_field($input['localseo']['address']['region']); |
| 1986 |
} |
| 1987 |
if (isset($input['localseo']['address']['postancode'])) { |
| 1988 |
$new_input['localseo']['address']['postancode'] = sanitize_text_field($input['localseo']['address']['postancode']); |
| 1989 |
} |
| 1990 |
if (isset($input['localseo']['address']['country'])) { |
| 1991 |
$new_input['localseo']['address']['country'] = sanitize_text_field($input['localseo']['address']['country']); |
| 1992 |
} |
| 1993 |
if (isset($input['localseo']['local_seo_business_type'])) { |
| 1994 |
$new_input['localseo']['local_seo_business_type'] = sanitize_text_field($input['localseo']['local_seo_business_type']); |
| 1995 |
} |
| 1996 |
if (isset($input['localseo']['local_seo_hours_format'])) { |
| 1997 |
$new_input['localseo']['local_seo_hours_format'] = sanitize_text_field($input['localseo']['local_seo_hours_format']); |
| 1998 |
} |
| 1999 |
if (isset($input['localseo']['days'])) { |
| 2000 |
$new_input['localseo']['days'] = sanitize_text_field($input['localseo']['days']); |
| 2001 |
} |
| 2002 |
if (isset($input['localseo']['times'])) { |
| 2003 |
$new_input['localseo']['times'] = sanitize_text_field($input['localseo']['times']); |
| 2004 |
} |
| 2005 |
if (isset($input['localseo']['phonetype'])) { |
| 2006 |
$new_input['localseo']['phonetype'] = sanitize_text_field($input['localseo']['phonetype']); |
| 2007 |
} |
| 2008 |
if (isset($input['localseo']['phonenumber'])) { |
| 2009 |
$new_input['localseo']['phonenumber'] = sanitize_text_field($input['localseo']['phonenumber']); |
| 2010 |
} |
| 2011 |
if (isset($input['localseo']['local_seo_price_range'])) { |
| 2012 |
$new_input['localseo']['local_seo_price_range'] = sanitize_text_field($input['localseo']['local_seo_price_range']); |
| 2013 |
} |
| 2014 |
if (isset($input['localseo']['local_seo_about_page'])) { |
| 2015 |
$new_input['localseo']['local_seo_about_page'] = sanitize_text_field($input['localseo']['local_seo_about_page']); |
| 2016 |
} |
| 2017 |
if (isset($input['localseo']['local_seo_contact_page'])) { |
| 2018 |
$new_input['localseo']['local_seo_contact_page'] = sanitize_text_field($input['localseo']['local_seo_contact_page']); |
| 2019 |
} |
| 2020 |
if (isset($input['localseo']['local_seo_map_key'])) { |
| 2021 |
$new_input['localseo']['local_seo_map_key'] = sanitize_text_field($input['localseo']['local_seo_map_key']); |
| 2022 |
} |
| 2023 |
if (isset($input['localseo']['local_seo_geo_coordinates'])) { |
| 2024 |
$new_input['localseo']['local_seo_geo_coordinates'] = sanitize_text_field($input['localseo']['local_seo_geo_coordinates']); |
| 2025 |
} |
| 2026 |
|
| 2027 |
// Breadcrumbs Settings |
| 2028 |
if (isset($input['breadcrumbs'])) { |
| 2029 |
$new_input['breadcrumbs']['enabled'] = !empty($input['breadcrumbs']['enabled']); |
| 2030 |
$new_input['breadcrumbs']['show_current_page'] = !empty($input['breadcrumbs']['show_current_page']); |
| 2031 |
$new_input['breadcrumbs']['disable_schema'] = !empty($input['breadcrumbs']['disable_schema']); |
| 2032 |
if (isset($input['breadcrumbs']['separator'])) { |
| 2033 |
$new_input['breadcrumbs']['separator'] = sanitize_text_field($input['breadcrumbs']['separator']); |
| 2034 |
} |
| 2035 |
if (isset($input['breadcrumbs']['home_label'])) { |
| 2036 |
$new_input['breadcrumbs']['home_label'] = sanitize_text_field($input['breadcrumbs']['home_label']); |
| 2037 |
} |
| 2038 |
if (isset($input['breadcrumbs']['home_url'])) { |
| 2039 |
$new_input['breadcrumbs']['home_url'] = esc_url_raw($input['breadcrumbs']['home_url']); |
| 2040 |
} |
| 2041 |
if (isset($input['breadcrumbs']['prefix_text'])) { |
| 2042 |
$new_input['breadcrumbs']['prefix_text'] = sanitize_text_field($input['breadcrumbs']['prefix_text']); |
| 2043 |
} |
| 2044 |
if (isset($input['breadcrumbs']['archive_label_format'])) { |
| 2045 |
$new_input['breadcrumbs']['archive_label_format'] = sanitize_text_field($input['breadcrumbs']['archive_label_format']); |
| 2046 |
} |
| 2047 |
// Remove raw breadcrumbs so sanitized values (especially checkbox |
| 2048 |
// booleans) survive the shallow array_merge($new_input, $input). |
| 2049 |
unset($input['breadcrumbs']); |
| 2050 |
} |
| 2051 |
|
| 2052 |
// Code Snippets Settings |
| 2053 |
if (isset($input['codesnippets']['header_snippet'])) { |
| 2054 |
$new_input['codesnippets']['header_snippet'] = sanitize_text_field($input['codesnippets']['header_snippet']); |
| 2055 |
} |
| 2056 |
if (isset($input['codesnippets']['footer_snippet'])) { |
| 2057 |
$new_input['codesnippets']['footer_snippet'] = sanitize_text_field($input['codesnippets']['footer_snippet']); |
| 2058 |
} |
| 2059 |
|
| 2060 |
|
| 2061 |
// Optimal Settings |
| 2062 |
if (isset($input['optimal_settings']['no_index_posts'])) { |
| 2063 |
$new_input['optimal_settings']['no_index_posts'] = boolval($input['optimal_settings']['no_index_posts']); |
| 2064 |
} |
| 2065 |
if (isset($input['optimal_settings']['no_follow_links'])) { |
| 2066 |
$new_input['optimal_settings']['no_follow_links'] = boolval($input['optimal_settings']['no_follow_links']); |
| 2067 |
} |
| 2068 |
if (isset($input['optimal_settings']['open_external_links'])) { |
| 2069 |
$new_input['optimal_settings']['open_external_links'] = boolval($input['optimal_settings']['open_external_links']); |
| 2070 |
} |
| 2071 |
if (isset($input['optimal_settings']['add_alt_image_tags'])) { |
| 2072 |
$new_input['optimal_settings']['add_alt_image_tags'] = boolval($input['optimal_settings']['add_alt_image_tags']); |
| 2073 |
} |
| 2074 |
if (isset($input['optimal_settings']['add_title_image_tags'])) { |
| 2075 |
$new_input['optimal_settings']['add_title_image_tags'] = boolval($input['optimal_settings']['add_title_image_tags']); |
| 2076 |
} |
| 2077 |
|
| 2078 |
// Site Information - Optimal Settings |
| 2079 |
if (isset($input['optimal_settings']['site_info']['type'])) { |
| 2080 |
$new_input['optimal_settings']['site_info']['type'] = sanitize_text_field($input['optimal_settings']['site_info']['type']); |
| 2081 |
} |
| 2082 |
if (isset($input['optimal_settings']['site_info']['business_type'])) { |
| 2083 |
$new_input['optimal_settings']['site_info']['business_type'] = sanitize_text_field($input['optimal_settings']['site_info']['business_type']); |
| 2084 |
} |
| 2085 |
if (isset($input['optimal_settings']['site_info']['company_name'])) { |
| 2086 |
$new_input['optimal_settings']['site_info']['company_name'] = sanitize_text_field($input['optimal_settings']['site_info']['company_name']); |
| 2087 |
} |
| 2088 |
if (isset($input['optimal_settings']['site_info']['google_logo'])) { |
| 2089 |
$new_input['optimal_settings']['site_info']['google_logo'] = sanitize_url($input['optimal_settings']['site_info']['google_logo']); |
| 2090 |
} |
| 2091 |
if (isset($input['optimal_settings']['site_info']['social_share_image'])) { |
| 2092 |
$new_input['optimal_settings']['site_info']['social_share_image'] = sanitize_url($input['optimal_settings']['site_info']['social_share_image']); |
| 2093 |
} |
| 2094 |
|
| 2095 |
// Common Setting - Global Settings |
| 2096 |
$common_robots_field = isset($input['common_robots_meta']) ? 'common_robots_meta' : 'common_robots_mata'; |
| 2097 |
|
| 2098 |
if (isset($input[$common_robots_field]['index'])) { |
| 2099 |
$new_input['common_robots_meta']['index'] = boolval($input[$common_robots_field]['index']); |
| 2100 |
} |
| 2101 |
if (isset($input[$common_robots_field]['noindex'])) { |
| 2102 |
$new_input['common_robots_meta']['noindex'] = boolval($input[$common_robots_field]['noindex']); |
| 2103 |
} |
| 2104 |
if (isset($input[$common_robots_field]['nofollow'])) { |
| 2105 |
$new_input['common_robots_meta']['nofollow'] = boolval($input[$common_robots_field]['nofollow']); |
| 2106 |
} |
| 2107 |
if (isset($input[$common_robots_field]['noarchive'])) { |
| 2108 |
$new_input['common_robots_meta']['noarchive'] = boolval($input[$common_robots_field]['noarchive']); |
| 2109 |
} |
| 2110 |
if (isset($input[$common_robots_field]['noimageindex'])) { |
| 2111 |
$new_input['common_robots_meta']['noimageindex'] = boolval($input[$common_robots_field]['noimageindex']); |
| 2112 |
} |
| 2113 |
if (isset($input[$common_robots_field]['nosnippet'])) { |
| 2114 |
$new_input['common_robots_meta']['nosnippet'] = boolval($input[$common_robots_field]['nosnippet']); |
| 2115 |
} |
| 2116 |
|
| 2117 |
// Advance Setting - Global Settings |
| 2118 |
$advance_robots_field = isset($input['advance_robots_meta']) ? 'advance_robots_meta' : 'advance_robots_mata'; |
| 2119 |
|
| 2120 |
if (isset($input[$advance_robots_field]['max-snippet']['enable'])) { |
| 2121 |
$new_input['advance_robots_meta']['max-snippet']['enable'] = boolval($input[$advance_robots_field]['max-snippet']['enable']); |
| 2122 |
} |
| 2123 |
if (isset($input[$advance_robots_field]['max-snippet']['length'])) { |
| 2124 |
$new_input['advance_robots_meta']['max-snippet']['length'] = sanitize_text_field($input[$advance_robots_field]['max-snippet']['length']); |
| 2125 |
} |
| 2126 |
if (isset($input[$advance_robots_field]['max-video-preview']['enable'])) { |
| 2127 |
$new_input['advance_robots_meta']['max-video-preview']['enable'] = boolval($input[$advance_robots_field]['max-video-preview']['enable']); |
| 2128 |
} |
| 2129 |
if (isset($input[$advance_robots_field]['max-video-preview']['length'])) { |
| 2130 |
$new_input['advance_robots_meta']['max-video-preview']['length'] = sanitize_text_field($input[$advance_robots_field]['max-video-preview']['length']); |
| 2131 |
} |
| 2132 |
if (isset($input[$advance_robots_field]['max-image-preview']['enable'])) { |
| 2133 |
$new_input['advance_robots_meta']['max-image-preview']['enable'] = boolval($input[$advance_robots_field]['max-image-preview']['enable']); |
| 2134 |
} |
| 2135 |
if (isset($input[$advance_robots_field]['max-image-preview']['length'])) { |
| 2136 |
$new_input['advance_robots_meta']['max-image-preview']['length'] = sanitize_text_field($input[$advance_robots_field]['max-image-preview']['length']); |
| 2137 |
} |
| 2138 |
|
| 2139 |
// Social meta settings |
| 2140 |
if (isset($input['social_meta']['facebook_page_url'])) { |
| 2141 |
$new_input['social_meta']['facebook_page_url'] = sanitize_text_field($input['social_meta']['facebook_page_url']); |
| 2142 |
} |
| 2143 |
if (isset($input['social_meta']['facebook_authorship'])) { |
| 2144 |
$new_input['social_meta']['facebook_authorship'] = sanitize_text_field($input['social_meta']['facebook_authorship']); |
| 2145 |
} |
| 2146 |
if (isset($input['social_meta']['facebook_admin'])) { |
| 2147 |
$new_input['social_meta']['facebook_admin'] = sanitize_text_field($input['social_meta']['facebook_admin']); |
| 2148 |
} |
| 2149 |
if (isset($input['social_meta']['facebook_app'])) { |
| 2150 |
$new_input['social_meta']['facebook_app'] = sanitize_text_field($input['social_meta']['facebook_app']); |
| 2151 |
} |
| 2152 |
if (isset($input['social_meta']['facebook_secret'])) { |
| 2153 |
$new_input['social_meta']['facebook_secret'] = sanitize_text_field($input['social_meta']['facebook_secret']); |
| 2154 |
} |
| 2155 |
if (isset($input['social_meta']['twitter_username'])) { |
| 2156 |
$new_input['social_meta']['twitter_username'] = sanitize_text_field($input['social_meta']['twitter_username']); |
| 2157 |
} |
| 2158 |
|
| 2159 |
// Common Meta Settings — Open Graph / article toggle checkboxes. |
| 2160 |
// Unchecked HTML checkboxes are absent from $input, so an explicit |
| 2161 |
// 'true'/'false' round-trip is required to persist opt-outs. |
| 2162 |
if (isset($input['common_meta_settings'])) { |
| 2163 |
$og_toggle_fields = [ |
| 2164 |
'og_image_dimensions', |
| 2165 |
'article_timestamps', |
| 2166 |
'article_author', |
| 2167 |
'article_section', |
| 2168 |
'article_tags', |
| 2169 |
'twitter_image_alt', |
| 2170 |
]; |
| 2171 |
foreach ($og_toggle_fields as $field) { |
| 2172 |
$new_input['common_meta_settings'][$field] = (isset($input['common_meta_settings'][$field]) && $input['common_meta_settings'][$field] === 'true') ? 'true' : 'false'; |
| 2173 |
} |
| 2174 |
// Remove raw common_meta_settings so sanitized toggle values |
| 2175 |
// survive the shallow array_merge($new_input, $input). |
| 2176 |
unset($input['common_meta_settings']); |
| 2177 |
} |
| 2178 |
|
| 2179 |
# Handle whitelabel URL fields with improved empty value handling |
| 2180 |
if (isset($input['whitelabel'])) { |
| 2181 |
$existing_whitelabel = Metasync::get_option()['whitelabel'] ?? []; |
| 2182 |
|
| 2183 |
$general_input = $input['general'] ?? []; |
| 2184 |
$plugin_name = isset($general_input['white_label_plugin_name']) ? trim((string) $general_input['white_label_plugin_name']) : ''; |
| 2185 |
$logo_light = isset($input['whitelabel']['logo_light']) ? trim((string) $input['whitelabel']['logo_light']) : ''; |
| 2186 |
$logo_dark = isset($input['whitelabel']['logo_dark']) ? trim((string) $input['whitelabel']['logo_dark']) : ''; |
| 2187 |
$author = isset($general_input['white_label_plugin_author']) ? trim((string) $general_input['white_label_plugin_author']) : ''; |
| 2188 |
$author_uri = isset($general_input['white_label_plugin_author_uri']) ? trim((string) $general_input['white_label_plugin_author_uri']) : ''; |
| 2189 |
$plugin_uri = isset($general_input['white_label_plugin_uri']) ? trim((string) $general_input['white_label_plugin_uri']) : ''; |
| 2190 |
$domain = isset($input['whitelabel']['domain']) ? trim((string) $input['whitelabel']['domain']) : ''; |
| 2191 |
$description = isset($general_input['white_label_plugin_description']) ? trim((string) $general_input['white_label_plugin_description']) : ''; |
| 2192 |
|
| 2193 |
$has_core_field = (!empty($plugin_name) || (!empty($logo_light) && filter_var($logo_light, FILTER_VALIDATE_URL)) || (!empty($logo_dark) && filter_var($logo_dark, FILTER_VALIDATE_URL)) || !empty($author)); |
| 2194 |
$has_optional_only = (!empty($author_uri) || !empty($plugin_uri) || (!empty($domain) && filter_var($domain, FILTER_VALIDATE_URL)) || !empty($description)); |
| 2195 |
|
| 2196 |
if ($has_optional_only && !$has_core_field) { |
| 2197 |
add_settings_error( |
| 2198 |
'metasync_options', |
| 2199 |
'whitelabel_partial', |
| 2200 |
'Add at least one of: Plugin Name, Logo URL, or Author to save whitelabel settings.', |
| 2201 |
'error' |
| 2202 |
); |
| 2203 |
$new_input['whitelabel'] = $existing_whitelabel; |
| 2204 |
$whitelabel_validation_failed = true; |
| 2205 |
} else { |
| 2206 |
$new_input['whitelabel'] = $existing_whitelabel; |
| 2207 |
|
| 2208 |
if (isset($input['whitelabel']['logo'])) { |
| 2209 |
$logo_value = trim($input['whitelabel']['logo']); |
| 2210 |
|
| 2211 |
if (!empty($logo_value) && filter_var($logo_value, FILTER_VALIDATE_URL)) { |
| 2212 |
$new_input['whitelabel']['logo'] = esc_url_raw($logo_value); |
| 2213 |
|
| 2214 |
} else { |
| 2215 |
$new_input['whitelabel']['logo'] = ''; |
| 2216 |
|
| 2217 |
} |
| 2218 |
} |
| 2219 |
|
| 2220 |
if (isset($input['whitelabel']['logo_light'])) { |
| 2221 |
$logo_light_value = trim($input['whitelabel']['logo_light']); |
| 2222 |
|
| 2223 |
if (!empty($logo_light_value) && filter_var($logo_light_value, FILTER_VALIDATE_URL)) { |
| 2224 |
$new_input['whitelabel']['logo_light'] = esc_url_raw($logo_light_value); |
| 2225 |
} else { |
| 2226 |
$new_input['whitelabel']['logo_light'] = ''; |
| 2227 |
} |
| 2228 |
} |
| 2229 |
|
| 2230 |
if (isset($input['whitelabel']['logo_dark'])) { |
| 2231 |
$logo_dark_value = trim($input['whitelabel']['logo_dark']); |
| 2232 |
|
| 2233 |
if (!empty($logo_dark_value) && filter_var($logo_dark_value, FILTER_VALIDATE_URL)) { |
| 2234 |
$new_input['whitelabel']['logo_dark'] = esc_url_raw($logo_dark_value); |
| 2235 |
} else { |
| 2236 |
$new_input['whitelabel']['logo_dark'] = ''; |
| 2237 |
} |
| 2238 |
} |
| 2239 |
|
| 2240 |
if (isset($input['whitelabel']['domain'])) { |
| 2241 |
$domain_value = trim($input['whitelabel']['domain']); |
| 2242 |
|
| 2243 |
if (!empty($domain_value) && filter_var($domain_value, FILTER_VALIDATE_URL)) { |
| 2244 |
$new_input['whitelabel']['domain'] = esc_url_raw($domain_value); |
| 2245 |
|
| 2246 |
} else { |
| 2247 |
$old_domain = $existing_whitelabel['domain'] ?? ''; |
| 2248 |
$new_input['whitelabel']['domain'] = ''; |
| 2249 |
|
| 2250 |
if (!empty($old_domain)) { |
| 2251 |
$new_input['_trigger_heartbeat_after_save'] = 'Domain cleared from: ' . $old_domain; |
| 2252 |
} |
| 2253 |
} |
| 2254 |
} |
| 2255 |
|
| 2256 |
if (isset($input['whitelabel']['settings_password'])) { |
| 2257 |
$password_value = trim($input['whitelabel']['settings_password']); |
| 2258 |
$new_input['whitelabel']['settings_password'] = sanitize_text_field($password_value); |
| 2259 |
} else { |
| 2260 |
if (isset($existing_whitelabel['settings_password'])) { |
| 2261 |
$new_input['whitelabel']['settings_password'] = $existing_whitelabel['settings_password']; |
| 2262 |
} |
| 2263 |
} |
| 2264 |
|
| 2265 |
if (isset($input['whitelabel']['recovery_email'])) { |
| 2266 |
$recovery_email = trim($input['whitelabel']['recovery_email']); |
| 2267 |
if (!empty($recovery_email)) { |
| 2268 |
$sanitized_email = sanitize_email($recovery_email); |
| 2269 |
if (is_email($sanitized_email)) { |
| 2270 |
$new_input['whitelabel']['recovery_email'] = $sanitized_email; |
| 2271 |
} else { |
| 2272 |
add_settings_error( |
| 2273 |
'metasync_messages', |
| 2274 |
'metasync_message', |
| 2275 |
__('Invalid recovery email address.', 'metasync'), |
| 2276 |
'error' |
| 2277 |
); |
| 2278 |
} |
| 2279 |
} else { |
| 2280 |
$new_input['whitelabel']['recovery_email'] = ''; |
| 2281 |
} |
| 2282 |
} else { |
| 2283 |
if (isset($existing_whitelabel['recovery_email'])) { |
| 2284 |
$new_input['whitelabel']['recovery_email'] = $existing_whitelabel['recovery_email']; |
| 2285 |
} |
| 2286 |
} |
| 2287 |
|
| 2288 |
$has_password = !empty($new_input['whitelabel']['settings_password']); |
| 2289 |
$has_recovery_email = !empty($new_input['whitelabel']['recovery_email']); |
| 2290 |
if ($has_password && !$has_recovery_email) { |
| 2291 |
add_settings_error( |
| 2292 |
'metasync_messages', |
| 2293 |
'metasync_message', |
| 2294 |
__('Password Recovery Email is required when a password is set.', 'metasync'), |
| 2295 |
'error' |
| 2296 |
); |
| 2297 |
$new_input['whitelabel']['settings_password'] = ''; |
| 2298 |
} |
| 2299 |
|
| 2300 |
if (isset($input['whitelabel']['access_control'])) { |
| 2301 |
$new_input['whitelabel']['access_control'] = Metasync_Access_Control::sanitize_access_control($input['whitelabel']['access_control']); |
| 2302 |
} |
| 2303 |
|
| 2304 |
// Sanitize quick_links (up to 5 entries) |
| 2305 |
if (isset($input['whitelabel']['quick_links']) && is_array($input['whitelabel']['quick_links'])) { |
| 2306 |
$sanitized_links = []; |
| 2307 |
foreach (array_slice($input['whitelabel']['quick_links'], 0, 5) as $link) { |
| 2308 |
$url = isset($link['url']) ? esc_url_raw(trim((string) $link['url'])) : ''; |
| 2309 |
if (empty($url)) { |
| 2310 |
continue; // skip blank rows |
| 2311 |
} |
| 2312 |
$sanitized_links[] = [ |
| 2313 |
'label' => sanitize_text_field($link['label'] ?? ''), |
| 2314 |
'url' => $url, |
| 2315 |
'external' => !empty($link['external']), |
| 2316 |
]; |
| 2317 |
} |
| 2318 |
$new_input['whitelabel']['quick_links'] = $sanitized_links; |
| 2319 |
} else { |
| 2320 |
// Preserve existing links when not submitted |
| 2321 |
if (isset($existing_whitelabel['quick_links'])) { |
| 2322 |
$new_input['whitelabel']['quick_links'] = $existing_whitelabel['quick_links']; |
| 2323 |
} |
| 2324 |
} |
| 2325 |
|
| 2326 |
// Sanitize color palette |
| 2327 |
if (isset($input['whitelabel']['color_palette']) && is_array($input['whitelabel']['color_palette'])) { |
| 2328 |
$allowed_vars = array( |
| 2329 |
'dashboard-bg', 'dashboard-card-bg', 'dashboard-card-hover', |
| 2330 |
'dashboard-text-primary', 'dashboard-text-secondary', |
| 2331 |
'dashboard-accent', 'dashboard-accent-hover', |
| 2332 |
'dashboard-success', 'dashboard-warning', 'dashboard-error', |
| 2333 |
'dashboard-border', |
| 2334 |
'dashboard-gradient-primary-from', 'dashboard-gradient-primary-to', |
| 2335 |
'dashboard-gradient-accent-from', 'dashboard-gradient-accent-to' |
| 2336 |
); |
| 2337 |
$sanitized_palette = array(); |
| 2338 |
foreach (array('dark', 'light') as $theme_key) { |
| 2339 |
if (isset($input['whitelabel']['color_palette'][$theme_key]) && is_array($input['whitelabel']['color_palette'][$theme_key])) { |
| 2340 |
$sanitized_palette[$theme_key] = array(); |
| 2341 |
foreach ($input['whitelabel']['color_palette'][$theme_key] as $var_name => $color_value) { |
| 2342 |
if (in_array($var_name, $allowed_vars, true)) { |
| 2343 |
$color_value = trim(sanitize_text_field($color_value)); |
| 2344 |
if ($color_value !== '' && preg_match('/^#[0-9a-fA-F]{3,8}$/', $color_value)) { |
| 2345 |
$sanitized_palette[$theme_key][$var_name] = $color_value; |
| 2346 |
} |
| 2347 |
} |
| 2348 |
} |
| 2349 |
} |
| 2350 |
} |
| 2351 |
$new_input['whitelabel']['color_palette'] = $sanitized_palette; |
| 2352 |
} else { |
| 2353 |
if (isset($existing_whitelabel['color_palette'])) { |
| 2354 |
$new_input['whitelabel']['color_palette'] = $existing_whitelabel['color_palette']; |
| 2355 |
} |
| 2356 |
} |
| 2357 |
|
| 2358 |
$new_input['whitelabel']['updated_at'] = time(); |
| 2359 |
} |
| 2360 |
} else { |
| 2361 |
$existing_whitelabel = Metasync::get_option()['whitelabel'] ?? []; |
| 2362 |
$has_existing_whitelabel = !empty($existing_whitelabel['domain']) || !empty($existing_whitelabel['logo']); |
| 2363 |
|
| 2364 |
if ($has_existing_whitelabel) { |
| 2365 |
$new_input['whitelabel'] = [ |
| 2366 |
'is_whitelabel' => false, |
| 2367 |
'domain' => '', |
| 2368 |
'logo' => '', |
| 2369 |
'company_name' => '', |
| 2370 |
'updated_at' => time() |
| 2371 |
]; |
| 2372 |
|
| 2373 |
if (!empty($existing_whitelabel['domain'])) { |
| 2374 |
delete_transient('metasync_heartbeat_status_cache'); |
| 2375 |
do_action('metasync_trigger_immediate_heartbeat', 'Whitelabel settings cleared - domain changed to default'); |
| 2376 |
} |
| 2377 |
} |
| 2378 |
} |
| 2379 |
|
| 2380 |
// Indexation Control Settings |
| 2381 |
if (isset($input['seo_controls']['index_date_archives'])) { |
| 2382 |
$new_input['seo_controls']['index_date_archives'] = boolval($input['seo_controls']['index_date_archives']); |
| 2383 |
} |
| 2384 |
if (isset($input['seo_controls']['index_tag_archives'])) { |
| 2385 |
$new_input['seo_controls']['index_tag_archives'] = boolval($input['seo_controls']['index_tag_archives']); |
| 2386 |
} |
| 2387 |
if (isset($input['seo_controls']['index_author_archives'])) { |
| 2388 |
$new_input['seo_controls']['index_author_archives'] = boolval($input['seo_controls']['index_author_archives']); |
| 2389 |
} |
| 2390 |
if (isset($input['seo_controls']['index_format_archives'])) { |
| 2391 |
$new_input['seo_controls']['index_format_archives'] = boolval($input['seo_controls']['index_format_archives']); |
| 2392 |
} |
| 2393 |
|
| 2394 |
// Handle post-save heartbeat trigger for domain changes |
| 2395 |
if (isset($new_input['_trigger_heartbeat_after_save'])) { |
| 2396 |
$context = $new_input['_trigger_heartbeat_after_save']; |
| 2397 |
unset($new_input['_trigger_heartbeat_after_save']); |
| 2398 |
|
| 2399 |
add_action('updated_option_metasync_options', function() use ($context) { |
| 2400 |
delete_transient('metasync_heartbeat_status_cache'); |
| 2401 |
do_action('metasync_trigger_immediate_heartbeat', 'Whitelabel domain change - ' . $context); |
| 2402 |
}); |
| 2403 |
} |
| 2404 |
|
| 2405 |
$result = array_merge($new_input, $input); |
| 2406 |
|
| 2407 |
if ($whitelabel_validation_failed) { |
| 2408 |
$result['whitelabel'] = Metasync::get_option()['whitelabel'] ?? []; |
| 2409 |
} |
| 2410 |
|
| 2411 |
delete_transient('metasync_otto_js_detected'); |
| 2412 |
|
| 2413 |
return $result; |
| 2414 |
} |
| 2415 |
|
| 2416 |
/** |
| 2417 |
* AJAX handler for saving general/whitelabel/advanced settings. |
| 2418 |
* |
| 2419 |
* Moved from Metasync_Admin – the admin class now delegates here. |
| 2420 |
*/ |
| 2421 |
public function meta_sync_save_settings() |
| 2422 |
{ |
| 2423 |
if (!isset($_POST['meta_sync_nonce']) || !wp_verify_nonce($_POST['meta_sync_nonce'], 'meta_sync_general_setting_nonce')) { |
| 2424 |
wp_send_json_error(array('message' => 'Invalid nonce')); |
| 2425 |
return; |
| 2426 |
} |
| 2427 |
|
| 2428 |
if (!Metasync::current_user_has_plugin_access()) { |
| 2429 |
wp_send_json_error(['message' => 'Insufficient permissions.'], 403); |
| 2430 |
return; |
| 2431 |
} |
| 2432 |
|
| 2433 |
$text_fields = [ |
| 2434 |
'searchatlas_api_key', 'apikey', |
| 2435 |
'white_label_plugin_name', 'white_label_plugin_description', |
| 2436 |
'white_label_plugin_author', 'white_label_plugin_menu_slug', |
| 2437 |
'white_label_plugin_menu_icon', 'enabled_plugin_css', |
| 2438 |
'enabled_elementor_plugin_css_color','enabled_elementor_plugin_css', |
| 2439 |
'otto_pixel_uuid','periodic_clear_otto_cache','periodic_clear_ottopage_cache', |
| 2440 |
'periodic_clear_ottopost_cache', 'whitelabel_otto_name', 'otto_wp_rocket_compat' |
| 2441 |
]; |
| 2442 |
|
| 2443 |
$url_fields = [ |
| 2444 |
'white_label_plugin_author_uri', 'white_label_plugin_uri' |
| 2445 |
]; |
| 2446 |
|
| 2447 |
$bool_fields = ['otto_disable_on_loggedin', 'otto_disable_preview_button', 'otto_disable_for_bots' , 'hide_dashboard_framework', 'show_admin_bar_status', 'enable_auto_updates', 'disable_common_robots_metabox', 'disable_advance_robots_metabox', 'disable_redirection_metabox', 'disable_canonical_metabox', 'disable_social_opengraph_metabox', 'disable_schema_markup_metabox', 'disable_seo_metabox', 'open_external_links']; |
| 2448 |
|
| 2449 |
$url_fields = ['white_label_plugin_author_uri', 'white_label_plugin_uri']; |
| 2450 |
|
| 2451 |
$metasync_options = Metasync::get_option(); |
| 2452 |
if (!is_array($metasync_options)) { |
| 2453 |
$metasync_options = array(); |
| 2454 |
} |
| 2455 |
if (!isset($metasync_options['general']) || !is_array($metasync_options['general'])) { |
| 2456 |
$metasync_options['general'] = array(); |
| 2457 |
} |
| 2458 |
|
| 2459 |
$validation_errors = []; |
| 2460 |
|
| 2461 |
$active_tab = isset($_POST['active_tab']) ? sanitize_text_field($_POST['active_tab']) : 'general'; |
| 2462 |
$general_tab_submitted = ($active_tab === 'general'); |
| 2463 |
$whitelabel_tab_submitted = ($active_tab === 'whitelabel'); |
| 2464 |
|
| 2465 |
// Whitelabel field validation issues are collected per-field and reported back |
| 2466 |
// without blocking the rest of the settings save (WP-413). |
| 2467 |
$whitelabel_errors = []; |
| 2468 |
|
| 2469 |
foreach ($text_fields as $field) { |
| 2470 |
|
| 2471 |
if (isset($_POST['metasync_options']['general'][$field])) { |
| 2472 |
|
| 2473 |
$value = trim($_POST['metasync_options']['general'][$field]); |
| 2474 |
|
| 2475 |
// searchatlas_api_key must be clearable to support key removal (WP-334) |
| 2476 |
$clearable_fields = [ |
| 2477 |
'searchatlas_api_key', |
| 2478 |
'white_label_plugin_name', |
| 2479 |
'white_label_plugin_description', |
| 2480 |
'white_label_plugin_author', |
| 2481 |
'white_label_plugin_menu_slug', |
| 2482 |
'white_label_plugin_menu_icon', |
| 2483 |
'whitelabel_otto_name', |
| 2484 |
'otto_pixel_uuid' |
| 2485 |
]; |
| 2486 |
|
| 2487 |
if ($value === '' && !in_array($field, $clearable_fields)) { |
| 2488 |
continue; |
| 2489 |
} |
| 2490 |
|
| 2491 |
if ($field === 'white_label_plugin_name') { |
| 2492 |
$stored_name = (string) ($metasync_options['general']['white_label_plugin_name'] ?? ''); |
| 2493 |
// Only enforce the 16-char limit when the value actually changes so an |
| 2494 |
// already-saved longer legacy name can be re-saved without an error (WP-413). |
| 2495 |
if (strlen($value) > 16 && $value !== $stored_name && wp_unslash($value) !== $stored_name) { |
| 2496 |
$whitelabel_errors[$field] = 'Plugin name must not exceed 16 characters'; |
| 2497 |
continue; |
| 2498 |
} |
| 2499 |
} |
| 2500 |
|
| 2501 |
if ($field === 'white_label_plugin_menu_icon') { |
| 2502 |
|
| 2503 |
if ($value === '') { |
| 2504 |
$metasync_options['general'][$field] = ''; |
| 2505 |
} elseif (preg_match('/^dashicons-[a-z0-9-]+$/', $value)) { |
| 2506 |
// Accept dashicon names (e.g. dashicons-location) in addition to image URLs (WP-413) |
| 2507 |
$metasync_options['general'][$field] = sanitize_text_field($value); |
| 2508 |
} elseif (filter_var($value, FILTER_VALIDATE_URL)) { |
| 2509 |
|
| 2510 |
$image_extensions = ['png', 'svg']; |
| 2511 |
|
| 2512 |
$path = parse_url($value, PHP_URL_PATH); |
| 2513 |
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); |
| 2514 |
|
| 2515 |
if (in_array($extension, $image_extensions, true)) { |
| 2516 |
$metasync_options['general'][$field] = esc_url_raw($value); |
| 2517 |
} else { |
| 2518 |
$whitelabel_errors[$field] = 'Invalid Menu icon format. Only PNG, SVG, or a dashicon name (e.g. dashicons-location) are allowed.'; |
| 2519 |
} |
| 2520 |
} else { |
| 2521 |
$whitelabel_errors[$field] = 'Invalid Menu icon URL format.'; |
| 2522 |
} |
| 2523 |
} elseif ($field === 'white_label_plugin_menu_slug') { |
| 2524 |
// Self-correcting slug (WP-413): a URL-shaped value is reduced to a valid WP |
| 2525 |
// menu slug so it can never break sidebar link generation. |
| 2526 |
$metasync_options['general'][$field] = sanitize_title($value); |
| 2527 |
} else { |
| 2528 |
$metasync_options['general'][$field] = sanitize_text_field($_POST['metasync_options']['general'][$field]); |
| 2529 |
} |
| 2530 |
} |
| 2531 |
} |
| 2532 |
|
| 2533 |
$textarea_fields = ['otto_bot_whitelist', 'otto_bot_blacklist']; |
| 2534 |
if ($general_tab_submitted) { |
| 2535 |
foreach ($textarea_fields as $field) { |
| 2536 |
if (isset($_POST['metasync_options']['general'][$field])) { |
| 2537 |
$metasync_options['general'][$field] = sanitize_textarea_field($_POST['metasync_options']['general'][$field]); |
| 2538 |
} else { |
| 2539 |
$metasync_options['general'][$field] = ''; |
| 2540 |
} |
| 2541 |
} |
| 2542 |
} |
| 2543 |
|
| 2544 |
if ($general_tab_submitted) { |
| 2545 |
foreach ($bool_fields as $field) { |
| 2546 |
if (isset($_POST['metasync_options']['general'][$field])) { |
| 2547 |
$metasync_options['general'][$field] = filter_var($_POST['metasync_options']['general'][$field], FILTER_VALIDATE_BOOLEAN); |
| 2548 |
}else { |
| 2549 |
$metasync_options['general'][$field] = false; |
| 2550 |
} |
| 2551 |
} |
| 2552 |
} |
| 2553 |
|
| 2554 |
if ($general_tab_submitted || $whitelabel_tab_submitted) { |
| 2555 |
foreach ($url_fields as $field) { |
| 2556 |
if (isset($_POST['metasync_options']['general'][$field])) { |
| 2557 |
|
| 2558 |
$value = trim($_POST['metasync_options']['general'][$field]); |
| 2559 |
|
| 2560 |
$whitelabel_clearable_url_fields = [ |
| 2561 |
'white_label_plugin_author_uri', |
| 2562 |
'white_label_plugin_uri' |
| 2563 |
]; |
| 2564 |
|
| 2565 |
if ($value === '' && !in_array($field, $whitelabel_clearable_url_fields)) { |
| 2566 |
continue; |
| 2567 |
} |
| 2568 |
|
| 2569 |
if ($value === '' && in_array($field, $whitelabel_clearable_url_fields)) { |
| 2570 |
$metasync_options['general'][$field] = ''; |
| 2571 |
continue; |
| 2572 |
} |
| 2573 |
|
| 2574 |
if (filter_var($value, FILTER_VALIDATE_URL)) { |
| 2575 |
|
| 2576 |
$parsed_url = parse_url($value); |
| 2577 |
$host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; |
| 2578 |
|
| 2579 |
if (strpos($host, '.') !== false || filter_var($host, FILTER_VALIDATE_IP)) { |
| 2580 |
$metasync_options['general'][$field] = esc_url_raw($value); |
| 2581 |
} else { |
| 2582 |
$field_name = ($field === 'white_label_plugin_author_uri') ? 'Author URL' : 'Plugin URL'; |
| 2583 |
$validation_errors[] = 'Invalid ' . $field_name . ' format. Please use a proper domain name (e.g., example.com).'; |
| 2584 |
} |
| 2585 |
} else { |
| 2586 |
$field_name = ($field === 'white_label_plugin_author_uri') ? 'Author URL' : 'Plugin URL'; |
| 2587 |
$validation_errors[] = 'Invalid ' . $field_name . ' format.'; |
| 2588 |
} |
| 2589 |
} |
| 2590 |
} |
| 2591 |
} |
| 2592 |
|
| 2593 |
if ($general_tab_submitted) { |
| 2594 |
if (isset($_POST['metasync_options']['general']['content_genius_sync_roles']) && is_array($_POST['metasync_options']['general']['content_genius_sync_roles'])) { |
| 2595 |
$metasync_options['general']['content_genius_sync_roles'] = array_map('sanitize_text_field', $_POST['metasync_options']['general']['content_genius_sync_roles']); |
| 2596 |
} else { |
| 2597 |
$metasync_options['general']['content_genius_sync_roles'] = array(); |
| 2598 |
} |
| 2599 |
} |
| 2600 |
|
| 2601 |
if ($general_tab_submitted) { |
| 2602 |
$breadcrumbs_input = isset($_POST['metasync_options']['breadcrumbs']) && is_array($_POST['metasync_options']['breadcrumbs']) |
| 2603 |
? $_POST['metasync_options']['breadcrumbs'] |
| 2604 |
: array(); |
| 2605 |
|
| 2606 |
if (!isset($metasync_options['breadcrumbs']) || !is_array($metasync_options['breadcrumbs'])) { |
| 2607 |
$metasync_options['breadcrumbs'] = array(); |
| 2608 |
} |
| 2609 |
|
| 2610 |
$metasync_options['breadcrumbs']['enabled'] = !empty($breadcrumbs_input['enabled']); |
| 2611 |
$metasync_options['breadcrumbs']['disable_schema'] = !empty($breadcrumbs_input['disable_schema']); |
| 2612 |
$metasync_options['breadcrumbs']['show_current_page'] = !empty($breadcrumbs_input['show_current_page']); |
| 2613 |
$metasync_options['breadcrumbs']['separator'] = isset($breadcrumbs_input['separator']) ? sanitize_text_field($breadcrumbs_input['separator']) : '»'; |
| 2614 |
$metasync_options['breadcrumbs']['home_label'] = isset($breadcrumbs_input['home_label']) ? sanitize_text_field($breadcrumbs_input['home_label']) : 'Home'; |
| 2615 |
$metasync_options['breadcrumbs']['home_url'] = isset($breadcrumbs_input['home_url']) ? esc_url_raw($breadcrumbs_input['home_url']) : ''; |
| 2616 |
$metasync_options['breadcrumbs']['prefix_text'] = isset($breadcrumbs_input['prefix_text']) ? sanitize_text_field($breadcrumbs_input['prefix_text']) : ''; |
| 2617 |
$metasync_options['breadcrumbs']['archive_label_format'] = isset($breadcrumbs_input['archive_label_format']) ? sanitize_text_field($breadcrumbs_input['archive_label_format']) : '{name}'; |
| 2618 |
} |
| 2619 |
|
| 2620 |
// Open Graph / article toggle checkboxes live under common_meta_settings. |
| 2621 |
// Unchecked HTML checkboxes are absent from $_POST, so we explicitly |
| 2622 |
// write 'false' for any missing toggle to persist opt-outs. |
| 2623 |
if ($general_tab_submitted) { |
| 2624 |
$og_toggle_fields = [ |
| 2625 |
'open_graph_meta_tags', |
| 2626 |
'facebook_meta_tags', |
| 2627 |
'twitter_meta_tags', |
| 2628 |
'og_image_dimensions', |
| 2629 |
'article_timestamps', |
| 2630 |
'article_author', |
| 2631 |
'article_section', |
| 2632 |
'article_tags', |
| 2633 |
'twitter_image_alt', |
| 2634 |
]; |
| 2635 |
if (!isset($metasync_options['common_meta_settings']) || !is_array($metasync_options['common_meta_settings'])) { |
| 2636 |
$metasync_options['common_meta_settings'] = []; |
| 2637 |
} |
| 2638 |
foreach ($og_toggle_fields as $field) { |
| 2639 |
$posted = isset($_POST['metasync_options']['common_meta_settings'][$field]) |
| 2640 |
? sanitize_text_field($_POST['metasync_options']['common_meta_settings'][$field]) |
| 2641 |
: ''; |
| 2642 |
$metasync_options['common_meta_settings'][$field] = ($posted === 'true') ? 'true' : 'false'; |
| 2643 |
} |
| 2644 |
} |
| 2645 |
|
| 2646 |
if ($general_tab_submitted && isset($_POST['metasync_options']['general']['default_page_builder'])) { |
| 2647 |
require_once plugin_dir_path(dirname(__FILE__)) . 'custom-pages/class-metasync-html-to-builder-converter.php'; |
| 2648 |
$available_builders = Metasync_HTML_To_Builder_Converter::get_available_builders(); |
| 2649 |
$builder_value = sanitize_text_field($_POST['metasync_options']['general']['default_page_builder']); |
| 2650 |
if (isset($available_builders[$builder_value]) && $available_builders[$builder_value]['detected']) { |
| 2651 |
$metasync_options['general']['default_page_builder'] = $builder_value; |
| 2652 |
} else { |
| 2653 |
$metasync_options['general']['default_page_builder'] = 'gutenberg'; |
| 2654 |
if (isset($available_builders[$builder_value]) && !$available_builders[$builder_value]['detected']) { |
| 2655 |
$validation_errors[] = sprintf(__('Builder "%s" is not detected on this site. Reverted to Gutenberg.', 'metasync'), $available_builders[$builder_value]['label']); |
| 2656 |
} |
| 2657 |
} |
| 2658 |
} |
| 2659 |
|
| 2660 |
if ($whitelabel_tab_submitted && isset($_POST['metasync_options']['whitelabel'])) { |
| 2661 |
$gp = isset($_POST['metasync_options']['general']) && is_array($_POST['metasync_options']['general']) ? $_POST['metasync_options']['general'] : []; |
| 2662 |
$wp = isset($_POST['metasync_options']['whitelabel']) && is_array($_POST['metasync_options']['whitelabel']) ? $_POST['metasync_options']['whitelabel'] : []; |
| 2663 |
$plugin_name = isset($gp['white_label_plugin_name']) ? trim((string) $gp['white_label_plugin_name']) : ''; |
| 2664 |
$logo_light = isset($wp['logo_light']) ? trim((string) $wp['logo_light']) : ''; |
| 2665 |
$logo_dark = isset($wp['logo_dark']) ? trim((string) $wp['logo_dark']) : ''; |
| 2666 |
$author = isset($gp['white_label_plugin_author']) ? trim((string) $gp['white_label_plugin_author']) : ''; |
| 2667 |
$author_uri = isset($gp['white_label_plugin_author_uri']) ? trim((string) $gp['white_label_plugin_author_uri']) : ''; |
| 2668 |
$plugin_uri = isset($gp['white_label_plugin_uri']) ? trim((string) $gp['white_label_plugin_uri']) : ''; |
| 2669 |
$domain = isset($wp['domain']) ? trim((string) $wp['domain']) : ''; |
| 2670 |
$description = isset($gp['white_label_plugin_description']) ? trim((string) $gp['white_label_plugin_description']) : ''; |
| 2671 |
|
| 2672 |
$has_core_field = (!empty($plugin_name) || (!empty($logo_light) && filter_var($logo_light, FILTER_VALIDATE_URL)) || (!empty($logo_dark) && filter_var($logo_dark, FILTER_VALIDATE_URL)) || !empty($author)); |
| 2673 |
$has_optional_only = (!empty($author_uri) || !empty($plugin_uri) || (!empty($domain) && filter_var($domain, FILTER_VALIDATE_URL)) || !empty($description)); |
| 2674 |
|
| 2675 |
if ($has_optional_only && !$has_core_field) { |
| 2676 |
$validation_errors[] = 'Add at least one of: Plugin Name, Logo URL, or Author to save whitelabel settings.'; |
| 2677 |
} |
| 2678 |
} |
| 2679 |
|
| 2680 |
if (!empty($validation_errors)) { |
| 2681 |
wp_send_json_error([ |
| 2682 |
'errors' => $validation_errors |
| 2683 |
]); |
| 2684 |
return; |
| 2685 |
} |
| 2686 |
|
| 2687 |
$old_options = Metasync::get_option('general') ?? []; |
| 2688 |
$old_api_key = $old_options['searchatlas_api_key'] ?? ''; |
| 2689 |
|
| 2690 |
$api_key_field_present = isset( $_POST['metasync_options']['general']['searchatlas_api_key'] ); |
| 2691 |
$api_key_validated = null; // null = not checked, true = valid, false = rejected |
| 2692 |
$is_connected = null; |
| 2693 |
$api_key_warning = null; |
| 2694 |
$proposed_new_api_key = $metasync_options['general']['searchatlas_api_key'] ?? ''; |
| 2695 |
|
| 2696 |
// WP-308: When the API key changes, save it first, then POST heartbeat to register |
| 2697 |
// the WPWebsiteHeartbeat row on the backend, then GET ping to verify. |
| 2698 |
// The old flow (WP-239) did GET ping first, which failed because the row didn't exist yet. |
| 2699 |
if ( $api_key_field_present && $proposed_new_api_key !== '' && $proposed_new_api_key !== $old_api_key ) { |
| 2700 |
// Step 1: Save the new key so SyncCustomerParams can use it |
| 2701 |
Metasync::set_option($metasync_options); |
| 2702 |
|
| 2703 |
// Step 2: POST heartbeat to register the site (creates WPWebsiteHeartbeat row) |
| 2704 |
$sync_request = new Metasync_Sync_Requests(); |
| 2705 |
$sync_response = $sync_request->SyncCustomerParams(); |
| 2706 |
$sync_status = Metasync_Sync_Requests::get_response_code($sync_response); |
| 2707 |
|
| 2708 |
if ( $sync_status == 200 ) { |
| 2709 |
// Step 3: GET ping to verify registration succeeded |
| 2710 |
$ping = Metasync_Heartbeat_Manager::instance()->validate_api_key( $proposed_new_api_key ); |
| 2711 |
|
| 2712 |
if ( $ping['connected'] === true ) { |
| 2713 |
$api_key_validated = true; |
| 2714 |
$is_connected = true; |
| 2715 |
if ( ! empty( $ping['otto_pixel_uuid'] ) ) { |
| 2716 |
$existing_uuid = $metasync_options['general']['otto_pixel_uuid'] ?? ''; |
| 2717 |
if ( empty( $existing_uuid ) || $existing_uuid !== $ping['otto_pixel_uuid'] ) { |
| 2718 |
$metasync_options['general']['otto_pixel_uuid'] = sanitize_text_field( $ping['otto_pixel_uuid'] ); |
| 2719 |
Metasync::set_option($metasync_options); |
| 2720 |
} |
| 2721 |
} |
| 2722 |
} else { |
| 2723 |
// POST succeeded but ping didn't confirm — keep the key, warn user |
| 2724 |
$api_key_validated = true; |
| 2725 |
$is_connected = true; |
| 2726 |
$api_key_warning = 'API key saved and heartbeat sent. Connection verification will complete on the next heartbeat.'; |
| 2727 |
} |
| 2728 |
} elseif ( is_wp_error($sync_response) ) { |
| 2729 |
// Network error during POST — keep the key, warn user |
| 2730 |
$api_key_warning = 'Could not register with the server (network error). The key was saved — it will be verified on the next heartbeat.'; |
| 2731 |
} else { |
| 2732 |
// POST returned non-200 — the key is likely invalid, revert |
| 2733 |
$metasync_options['general']['searchatlas_api_key'] = $old_api_key; |
| 2734 |
Metasync::set_option($metasync_options); |
| 2735 |
$api_key_validated = false; |
| 2736 |
$is_connected = false; |
| 2737 |
$api_key_warning = empty( $old_api_key ) |
| 2738 |
? 'The API key could not be verified. Please check it and try again.' |
| 2739 |
: 'The API key could not be verified. Your API key has been reverted to the previously saved key.'; |
| 2740 |
} |
| 2741 |
} else { |
| 2742 |
// Key unchanged — just save options |
| 2743 |
Metasync::set_option($metasync_options); |
| 2744 |
} |
| 2745 |
|
| 2746 |
// Update connection state immediately when validated |
| 2747 |
if ( $api_key_validated === true ) { |
| 2748 |
update_option( 'metasync_last_known_connection_state', true ); |
| 2749 |
set_transient( 'metasync_heartbeat_status_cache', [ |
| 2750 |
'status' => true, |
| 2751 |
'timestamp' => time(), |
| 2752 |
'cached_until' => time() + 300, |
| 2753 |
'updated_by' => 'manual_save_ping_connected', |
| 2754 |
], 300 ); |
| 2755 |
} |
| 2756 |
|
| 2757 |
$data = Metasync::get_option('general'); |
| 2758 |
$new_api_key = $data['searchatlas_api_key'] ?? ''; |
| 2759 |
|
| 2760 |
$api_key_changed = $old_api_key !== $new_api_key; |
| 2761 |
$api_key_added = empty($old_api_key) && !empty($new_api_key); |
| 2762 |
$api_key_removed = !empty($old_api_key) && empty($new_api_key); |
| 2763 |
|
| 2764 |
if ( $api_key_validated === true ) { |
| 2765 |
# Use current_time('mysql') so the value is consistent with the |
| 2766 |
# cron heartbeat writer and renders correctly via time_elapsed_string. |
| 2767 |
$send_auth_token_timestamp = Metasync::get_option(); |
| 2768 |
$send_auth_token_timestamp['general']['send_auth_token_timestamp'] = current_time('mysql'); |
| 2769 |
Metasync::set_option($send_auth_token_timestamp); |
| 2770 |
|
| 2771 |
Metasync_Heartbeat_Manager::instance()->maybe_schedule_heartbeat_cron(); |
| 2772 |
do_action('metasync_trigger_immediate_heartbeat', 'Manual API key update - validated via ping'); |
| 2773 |
} else { |
| 2774 |
// Key unchanged or removed — run sync as before |
| 2775 |
$sync_request = new Metasync_Sync_Requests(); |
| 2776 |
$response = $sync_request->SyncCustomerParams(); |
| 2777 |
$responseCode = Metasync_Sync_Requests::get_response_code($response); |
| 2778 |
|
| 2779 |
if ($responseCode == 200) { |
| 2780 |
# Use current_time('mysql') for consistency with cron heartbeat. |
| 2781 |
$send_auth_token_timestamp = Metasync::get_option(); |
| 2782 |
$send_auth_token_timestamp['general']['send_auth_token_timestamp'] = current_time('mysql'); |
| 2783 |
Metasync::set_option($send_auth_token_timestamp); |
| 2784 |
|
| 2785 |
if ($api_key_added) { |
| 2786 |
Metasync_Heartbeat_Manager::instance()->maybe_schedule_heartbeat_cron(); |
| 2787 |
do_action('metasync_trigger_immediate_heartbeat', 'Manual API key update - new key added'); |
| 2788 |
} elseif ($api_key_removed) { |
| 2789 |
Metasync_Heartbeat_Manager::instance()->unschedule_heartbeat_cron(); |
| 2790 |
delete_transient('metasync_heartbeat_status_cache'); |
| 2791 |
} elseif ($api_key_changed && !empty($new_api_key)) { |
| 2792 |
do_action('metasync_trigger_immediate_heartbeat', 'Manual API key update - key changed'); |
| 2793 |
} |
| 2794 |
} |
| 2795 |
} |
| 2796 |
|
| 2797 |
if (isset($_POST['metasync_options']['whitelabel'])) { |
| 2798 |
|
| 2799 |
$whitelabel_data = $_POST['metasync_options']['whitelabel']; |
| 2800 |
$existing_whitelabel = $metasync_options['whitelabel'] ?? []; |
| 2801 |
|
| 2802 |
if (isset($whitelabel_data['logo'])) { |
| 2803 |
$logo_value = trim($whitelabel_data['logo']); |
| 2804 |
|
| 2805 |
if (!empty($logo_value) && filter_var($logo_value, FILTER_VALIDATE_URL)) { |
| 2806 |
$metasync_options['whitelabel']['logo'] = esc_url_raw($logo_value); |
| 2807 |
} else { |
| 2808 |
$metasync_options['whitelabel']['logo'] = ''; |
| 2809 |
} |
| 2810 |
} |
| 2811 |
|
| 2812 |
if (isset($whitelabel_data['logo_light'])) { |
| 2813 |
$logo_light_value = trim($whitelabel_data['logo_light']); |
| 2814 |
|
| 2815 |
if (!empty($logo_light_value) && filter_var($logo_light_value, FILTER_VALIDATE_URL)) { |
| 2816 |
$metasync_options['whitelabel']['logo_light'] = esc_url_raw($logo_light_value); |
| 2817 |
} else { |
| 2818 |
$metasync_options['whitelabel']['logo_light'] = ''; |
| 2819 |
} |
| 2820 |
} |
| 2821 |
|
| 2822 |
if (isset($whitelabel_data['logo_dark'])) { |
| 2823 |
$logo_dark_value = trim($whitelabel_data['logo_dark']); |
| 2824 |
|
| 2825 |
if (!empty($logo_dark_value) && filter_var($logo_dark_value, FILTER_VALIDATE_URL)) { |
| 2826 |
$metasync_options['whitelabel']['logo_dark'] = esc_url_raw($logo_dark_value); |
| 2827 |
} else { |
| 2828 |
$metasync_options['whitelabel']['logo_dark'] = ''; |
| 2829 |
} |
| 2830 |
} |
| 2831 |
|
| 2832 |
if (isset($whitelabel_data['domain'])) { |
| 2833 |
$domain_value = trim($whitelabel_data['domain']); |
| 2834 |
if (!empty($domain_value) && filter_var($domain_value, FILTER_VALIDATE_URL)) { |
| 2835 |
$metasync_options['whitelabel']['domain'] = esc_url_raw($domain_value); |
| 2836 |
} else { |
| 2837 |
$metasync_options['whitelabel']['domain'] = ''; |
| 2838 |
} |
| 2839 |
} |
| 2840 |
|
| 2841 |
if (isset($whitelabel_data['settings_password'])) { |
| 2842 |
$password_value = trim($whitelabel_data['settings_password']); |
| 2843 |
|
| 2844 |
$hide_settings_enabled = isset($whitelabel_data['hide_settings']) && $whitelabel_data['hide_settings'] == '1'; |
| 2845 |
|
| 2846 |
if ($hide_settings_enabled && empty($password_value)) { |
| 2847 |
if (isset($existing_whitelabel['settings_password'])) { |
| 2848 |
$metasync_options['whitelabel']['settings_password'] = $existing_whitelabel['settings_password']; |
| 2849 |
} |
| 2850 |
} else { |
| 2851 |
$metasync_options['whitelabel']['settings_password'] = sanitize_text_field($password_value); |
| 2852 |
} |
| 2853 |
} else { |
| 2854 |
if (isset($existing_whitelabel['settings_password'])) { |
| 2855 |
$metasync_options['whitelabel']['settings_password'] = $existing_whitelabel['settings_password']; |
| 2856 |
} |
| 2857 |
} |
| 2858 |
|
| 2859 |
if (isset($whitelabel_data['hide_settings'])) { |
| 2860 |
$metasync_options['whitelabel']['hide_settings'] = filter_var($whitelabel_data['hide_settings'], FILTER_VALIDATE_BOOLEAN) ? 1 : 0; |
| 2861 |
} else { |
| 2862 |
$metasync_options['whitelabel']['hide_settings'] = 0; |
| 2863 |
} |
| 2864 |
|
| 2865 |
if (isset($whitelabel_data['recovery_email'])) { |
| 2866 |
$recovery_email = trim($whitelabel_data['recovery_email']); |
| 2867 |
if (!empty($recovery_email)) { |
| 2868 |
$sanitized_email = sanitize_email($recovery_email); |
| 2869 |
if (is_email($sanitized_email)) { |
| 2870 |
$metasync_options['whitelabel']['recovery_email'] = $sanitized_email; |
| 2871 |
} |
| 2872 |
} else { |
| 2873 |
$metasync_options['whitelabel']['recovery_email'] = ''; |
| 2874 |
} |
| 2875 |
} else { |
| 2876 |
if (isset($existing_whitelabel['recovery_email'])) { |
| 2877 |
$metasync_options['whitelabel']['recovery_email'] = $existing_whitelabel['recovery_email']; |
| 2878 |
} |
| 2879 |
} |
| 2880 |
|
| 2881 |
$has_password = !empty($metasync_options['whitelabel']['settings_password']); |
| 2882 |
$has_recovery_email = !empty($metasync_options['whitelabel']['recovery_email']); |
| 2883 |
if ($has_password && !$has_recovery_email) { |
| 2884 |
$metasync_options['whitelabel']['settings_password'] = ''; |
| 2885 |
} |
| 2886 |
|
| 2887 |
if (isset($whitelabel_data['access_control'])) { |
| 2888 |
$metasync_options['whitelabel']['access_control'] = Metasync_Access_Control::sanitize_access_control($whitelabel_data['access_control']); |
| 2889 |
} |
| 2890 |
|
| 2891 |
if (!empty($metasync_options['whitelabel']['hide_settings']) && empty($metasync_options['whitelabel']['settings_password'])) { |
| 2892 |
$metasync_options['whitelabel']['hide_settings'] = 0; |
| 2893 |
} |
| 2894 |
|
| 2895 |
// Sanitize color palette |
| 2896 |
if (isset($whitelabel_data['color_palette']) && is_array($whitelabel_data['color_palette'])) { |
| 2897 |
$allowed_vars = array( |
| 2898 |
'dashboard-bg', 'dashboard-card-bg', 'dashboard-card-hover', |
| 2899 |
'dashboard-text-primary', 'dashboard-text-secondary', |
| 2900 |
'dashboard-accent', 'dashboard-accent-hover', |
| 2901 |
'dashboard-success', 'dashboard-warning', 'dashboard-error', |
| 2902 |
'dashboard-border', |
| 2903 |
'dashboard-gradient-primary-from', 'dashboard-gradient-primary-to', |
| 2904 |
'dashboard-gradient-accent-from', 'dashboard-gradient-accent-to' |
| 2905 |
); |
| 2906 |
$sanitized_palette = array(); |
| 2907 |
foreach (array('dark', 'light') as $theme_key) { |
| 2908 |
if (isset($whitelabel_data['color_palette'][$theme_key]) && is_array($whitelabel_data['color_palette'][$theme_key])) { |
| 2909 |
$sanitized_palette[$theme_key] = array(); |
| 2910 |
foreach ($whitelabel_data['color_palette'][$theme_key] as $var_name => $color_value) { |
| 2911 |
if (in_array($var_name, $allowed_vars, true)) { |
| 2912 |
$color_value = trim(sanitize_text_field($color_value)); |
| 2913 |
if ($color_value !== '' && preg_match('/^#[0-9a-fA-F]{3,8}$/', $color_value)) { |
| 2914 |
$sanitized_palette[$theme_key][$var_name] = $color_value; |
| 2915 |
} |
| 2916 |
} |
| 2917 |
} |
| 2918 |
} |
| 2919 |
} |
| 2920 |
$metasync_options['whitelabel']['color_palette'] = $sanitized_palette; |
| 2921 |
} else { |
| 2922 |
if (isset($existing_whitelabel['color_palette'])) { |
| 2923 |
$metasync_options['whitelabel']['color_palette'] = $existing_whitelabel['color_palette']; |
| 2924 |
} |
| 2925 |
} |
| 2926 |
|
| 2927 |
$metasync_options['whitelabel']['updated_at'] = time(); |
| 2928 |
|
| 2929 |
Metasync::set_option($metasync_options); |
| 2930 |
|
| 2931 |
} |
| 2932 |
|
| 2933 |
$redirect_url = isset($_GET['tab']) ? |
| 2934 |
admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-settings&tab='.$_GET['tab']) : |
| 2935 |
admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-settings'); |
| 2936 |
|
| 2937 |
// Also save edge cache settings if present in the payload (section renders inside this form) |
| 2938 |
if (class_exists('Metasync_Edge_Cache_Settings')) { |
| 2939 |
Metasync_Edge_Cache_Settings::save_from_post(); |
| 2940 |
} |
| 2941 |
|
| 2942 |
if ($general_tab_submitted && isset($_POST['metasync_llms_txt_settings'])) { |
| 2943 |
$llms_input = wp_unslash($_POST['metasync_llms_txt_settings']); |
| 2944 |
if (is_array($llms_input)) { |
| 2945 |
update_option('metasync_llms_txt_settings', $this->sanitize_llms_txt_settings($llms_input)); |
| 2946 |
} |
| 2947 |
} |
| 2948 |
|
| 2949 |
$response_payload = array( |
| 2950 |
'message' => 'Settings saved successfully!', |
| 2951 |
'redirect_url' => $redirect_url, |
| 2952 |
); |
| 2953 |
|
| 2954 |
// Per-field whitelabel validation issues (non-blocking): the save succeeded for all |
| 2955 |
// other fields, but the user must be told which values were rejected (WP-413). |
| 2956 |
if (!empty($whitelabel_errors)) { |
| 2957 |
$response_payload['whitelabel_errors'] = $whitelabel_errors; |
| 2958 |
} |
| 2959 |
|
| 2960 |
// Final slug after sanitization so the client redirects to the page WP actually |
| 2961 |
// registered, even when the submitted value was cleaned up (WP-413). |
| 2962 |
if (isset($_POST['metasync_options']['general']['white_label_plugin_menu_slug'])) { |
| 2963 |
$response_payload['saved_menu_slug'] = $metasync_options['general']['white_label_plugin_menu_slug'] ?? ''; |
| 2964 |
} |
| 2965 |
|
| 2966 |
if ( $api_key_field_present && $proposed_new_api_key !== '' && $proposed_new_api_key !== $old_api_key ) { |
| 2967 |
if ( $api_key_validated !== null ) { |
| 2968 |
$response_payload['api_key_validated'] = $api_key_validated; |
| 2969 |
$response_payload['is_connected'] = $is_connected; |
| 2970 |
} |
| 2971 |
if ( $api_key_warning !== null ) { |
| 2972 |
$response_payload['warning'] = $api_key_warning; |
| 2973 |
} |
| 2974 |
if ( $api_key_validated === false ) { |
| 2975 |
$response_payload['previous_api_key'] = $old_api_key; |
| 2976 |
} |
| 2977 |
} elseif ( $api_key_field_present && $proposed_new_api_key === '' && $old_api_key !== '' ) { |
| 2978 |
$response_payload['api_key_removed'] = true; |
| 2979 |
$response_payload['warning'] = 'The API key has been removed. The plugin will no longer sync with the server.'; |
| 2980 |
} |
| 2981 |
|
| 2982 |
delete_transient('metasync_otto_js_detected'); |
| 2983 |
|
| 2984 |
wp_send_json_success( $response_payload ); |
| 2985 |
} |
| 2986 |
|
| 2987 |
/** |
| 2988 |
* AJAX handler for saving execution settings. |
| 2989 |
* |
| 2990 |
* Moved from Metasync_Admin – the admin class now delegates here. |
| 2991 |
*/ |
| 2992 |
public function ajax_save_execution_settings() |
| 2993 |
{ |
| 2994 |
if (!isset($_POST['execution_settings_nonce']) || !wp_verify_nonce($_POST['execution_settings_nonce'], 'metasync_execution_settings_nonce')) { |
| 2995 |
wp_send_json_error(array('message' => 'Invalid security token. Please refresh the page and try again.')); |
| 2996 |
return; |
| 2997 |
} |
| 2998 |
|
| 2999 |
if (!Metasync::current_user_has_plugin_access()) { |
| 3000 |
wp_send_json_error(array('message' => 'Insufficient permissions to save settings.')); |
| 3001 |
return; |
| 3002 |
} |
| 3003 |
|
| 3004 |
$existing_settings = get_option('metasync_execution_settings', array()); |
| 3005 |
|
| 3006 |
$settings = array( |
| 3007 |
'max_execution_time' => isset($_POST['max_execution_time']) ? absint($_POST['max_execution_time']) : 30, |
| 3008 |
'max_memory_limit' => isset($_POST['max_memory_limit']) ? absint($_POST['max_memory_limit']) : 256, |
| 3009 |
'log_batch_size' => isset($_POST['log_batch_size']) ? absint($_POST['log_batch_size']) : 1000, |
| 3010 |
'action_scheduler_batches' => isset($_POST['action_scheduler_batches']) ? absint($_POST['action_scheduler_batches']) : 1, |
| 3011 |
'otto_rate_limit' => isset($_POST['otto_rate_limit']) ? absint($_POST['otto_rate_limit']) : 10, |
| 3012 |
'otto_cache_ttl' => isset($existing_settings['otto_cache_ttl']) ? absint($existing_settings['otto_cache_ttl']) : 30, |
| 3013 |
'queue_cleanup_days' => isset($_POST['queue_cleanup_days']) ? absint($_POST['queue_cleanup_days']) : 31 |
| 3014 |
); |
| 3015 |
|
| 3016 |
$server_limits = Metasync_Settings_Fields::instance()->get_server_limits(); |
| 3017 |
|
| 3018 |
if ($settings['max_execution_time'] < 1 || $settings['max_execution_time'] > 300) { |
| 3019 |
wp_send_json_error(array('message' => 'Max Execution Time must be between 1 and 300 seconds.')); |
| 3020 |
return; |
| 3021 |
} |
| 3022 |
|
| 3023 |
if ((int)$server_limits['max_execution_time_raw'] !== -1 && (int)$settings['max_execution_time'] > (int)$server_limits['max_execution_time_raw']) { |
| 3024 |
wp_send_json_error(array('message' => sprintf('Max Execution Time exceeds server limit of %d seconds. Please reduce the value.', (int)$server_limits['max_execution_time_raw']))); |
| 3025 |
return; |
| 3026 |
} |
| 3027 |
|
| 3028 |
$can_change_memory = Metasync_Settings_Fields::instance()->can_change_memory_limit(); |
| 3029 |
$memory_range = Metasync_Settings_Fields::instance()->get_memory_limit_allowed_range(); |
| 3030 |
if ($can_change_memory && $settings['max_memory_limit'] < $memory_range['min']) { |
| 3031 |
wp_send_json_error(array('message' => sprintf('Max Memory Limit must be at least %d MB.', $memory_range['min']))); |
| 3032 |
return; |
| 3033 |
} |
| 3034 |
|
| 3035 |
if ($can_change_memory && $memory_range['max'] !== null && $settings['max_memory_limit'] > $memory_range['max']) { |
| 3036 |
wp_send_json_error(array('message' => sprintf('Max Memory Limit must be between %d and %d MB.', $memory_range['min'], $memory_range['max']))); |
| 3037 |
return; |
| 3038 |
} |
| 3039 |
|
| 3040 |
if (!$can_change_memory) { |
| 3041 |
$settings['max_memory_limit'] = isset($existing_settings['max_memory_limit']) |
| 3042 |
? $existing_settings['max_memory_limit'] |
| 3043 |
: 256; |
| 3044 |
} |
| 3045 |
|
| 3046 |
if ($settings['log_batch_size'] < 100 || $settings['log_batch_size'] > 5000) { |
| 3047 |
wp_send_json_error(array('message' => 'Log Batch Size must be between 100 and 5000 lines.')); |
| 3048 |
return; |
| 3049 |
} |
| 3050 |
|
| 3051 |
if ($settings['action_scheduler_batches'] < 1 || $settings['action_scheduler_batches'] > 10) { |
| 3052 |
wp_send_json_error(array('message' => 'Action Scheduler Concurrent Batches must be between 1 and 10.')); |
| 3053 |
return; |
| 3054 |
} |
| 3055 |
|
| 3056 |
if ($settings['otto_rate_limit'] < 1 || $settings['otto_rate_limit'] > 60) { |
| 3057 |
wp_send_json_error(array('message' => 'OTTO API Calls Per Minute must be between 1 and 60.')); |
| 3058 |
return; |
| 3059 |
} |
| 3060 |
|
| 3061 |
if ($settings['queue_cleanup_days'] < 7 || $settings['queue_cleanup_days'] > 90) { |
| 3062 |
wp_send_json_error(array('message' => 'Queue Auto-Cleanup Days must be between 7 and 90 days.')); |
| 3063 |
return; |
| 3064 |
} |
| 3065 |
|
| 3066 |
// update_option() returns false both on real failure AND when the |
| 3067 |
// submitted values are identical to what's already stored (a no-op |
| 3068 |
// write). An unchanged save is not an error, so short-circuit to |
| 3069 |
// success when nothing actually changed. This also covers the case |
| 3070 |
// where the form fires twice in quick succession: the second request |
| 3071 |
// sees no change and would otherwise report a spurious failure. |
| 3072 |
if ($settings == $existing_settings) { |
| 3073 |
wp_send_json_success(array( |
| 3074 |
'message' => 'Execution settings saved successfully!' |
| 3075 |
)); |
| 3076 |
return; |
| 3077 |
} |
| 3078 |
|
| 3079 |
$saved = update_option('metasync_execution_settings', $settings); |
| 3080 |
|
| 3081 |
if ($saved !== false) { |
| 3082 |
wp_send_json_success(array( |
| 3083 |
'message' => 'Execution settings saved successfully!' |
| 3084 |
)); |
| 3085 |
} else { |
| 3086 |
wp_send_json_error(array('message' => 'Failed to save settings. Please try again.')); |
| 3087 |
} |
| 3088 |
} |
| 3089 |
|
| 3090 |
/** |
| 3091 |
* AJAX handler for saving SEO / Indexation Control settings. |
| 3092 |
* |
| 3093 |
* Moved from Metasync_Admin – the admin class now delegates here. |
| 3094 |
*/ |
| 3095 |
public function meta_sync_save_seo_controls() |
| 3096 |
{ |
| 3097 |
try { |
| 3098 |
if (!isset($_POST['meta_sync_seo_controls_nonce']) || !wp_verify_nonce($_POST['meta_sync_seo_controls_nonce'], 'meta_sync_seo_controls_nonce')) { |
| 3099 |
wp_send_json_error(array('message' => 'Invalid nonce')); |
| 3100 |
return; |
| 3101 |
} |
| 3102 |
|
| 3103 |
$current_options = Metasync::get_option(); |
| 3104 |
|
| 3105 |
$original_options = json_decode(json_encode($current_options), true); |
| 3106 |
|
| 3107 |
if (!isset($current_options['seo_controls'])) { |
| 3108 |
$current_options['seo_controls'] = []; |
| 3109 |
} |
| 3110 |
|
| 3111 |
$seo_control_fields = [ |
| 3112 |
'index_date_archives', |
| 3113 |
'index_tag_archives', |
| 3114 |
'index_author_archives', |
| 3115 |
'index_category_archives', |
| 3116 |
'index_format_archives', |
| 3117 |
'override_robots_tags', |
| 3118 |
'add_nofollow_to_external_links', |
| 3119 |
'enable_googleinstantindex', |
| 3120 |
'enable_binginstantindex', |
| 3121 |
'noindex_empty_archives' |
| 3122 |
]; |
| 3123 |
|
| 3124 |
foreach ($seo_control_fields as $field) { |
| 3125 |
if (isset($_POST['metasync_options']['seo_controls'][$field]) && $_POST['metasync_options']['seo_controls'][$field] === 'true') { |
| 3126 |
$current_options['seo_controls'][$field] = 'true'; |
| 3127 |
} else { |
| 3128 |
$current_options['seo_controls'][$field] = 'false'; |
| 3129 |
} |
| 3130 |
} |
| 3131 |
|
| 3132 |
$bing_save_result = true; |
| 3133 |
if (isset($_POST['metasync_bing_api_key_inline'])) { |
| 3134 |
$bing_save_result = $this->save_bing_inline_settings_ajax(); |
| 3135 |
} |
| 3136 |
|
| 3137 |
// Save Google Instant Indexing post types |
| 3138 |
if (isset($_POST['metasync_post_types']) && is_array($_POST['metasync_post_types'])) { |
| 3139 |
$google_post_types = array_values(array_map('sanitize_title', wp_unslash($_POST['metasync_post_types']))); |
| 3140 |
$google_settings = get_option('metasync_options_instant_indexing', ['post_types' => []]); |
| 3141 |
$google_settings['post_types'] = $google_post_types; |
| 3142 |
update_option('metasync_options_instant_indexing', $google_settings); |
| 3143 |
} |
| 3144 |
|
| 3145 |
$options_changed = (json_encode($original_options) !== json_encode($current_options)); |
| 3146 |
|
| 3147 |
$result = Metasync::set_option($current_options); |
| 3148 |
|
| 3149 |
if (!$result && !$options_changed) { |
| 3150 |
$result = true; |
| 3151 |
} |
| 3152 |
|
| 3153 |
if ($result && $bing_save_result) { |
| 3154 |
$success_message = 'Indexation Control settings saved successfully!'; |
| 3155 |
if (isset($_POST['metasync_bing_api_key_inline'])) { |
| 3156 |
$success_message = 'Indexation Control and Bing Instant Indexing settings saved successfully!'; |
| 3157 |
} |
| 3158 |
wp_send_json_success(array( |
| 3159 |
'message' => $success_message, |
| 3160 |
'saved_data' => $current_options['seo_controls'] |
| 3161 |
)); |
| 3162 |
} else { |
| 3163 |
$error_message = 'Failed to save Indexation Control settings'; |
| 3164 |
if (!$bing_save_result) { |
| 3165 |
$error_message = 'Failed to save Bing Instant Indexing settings'; |
| 3166 |
} |
| 3167 |
wp_send_json_error(array('message' => $error_message)); |
| 3168 |
} |
| 3169 |
} catch (Exception $e) { |
| 3170 |
error_log('Indexation Control AJAX Exception: ' . $e->getMessage()); |
| 3171 |
error_log('Indexation Control AJAX Stack trace: ' . $e->getTraceAsString()); |
| 3172 |
wp_send_json_error(array('message' => 'Error saving settings: ' . $e->getMessage())); |
| 3173 |
} |
| 3174 |
} |
| 3175 |
|
| 3176 |
/** |
| 3177 |
* Save Bing IndexNow inline settings from SEO Controls page. |
| 3178 |
* |
| 3179 |
* Moved from Metasync_Admin – the admin class now delegates here. |
| 3180 |
* |
| 3181 |
* @return bool True on success, false on failure |
| 3182 |
*/ |
| 3183 |
public function save_bing_inline_settings_ajax() |
| 3184 |
{ |
| 3185 |
$api_key = isset($_POST['metasync_bing_api_key_inline']) ? sanitize_text_field(wp_unslash($_POST['metasync_bing_api_key_inline'])) : ''; |
| 3186 |
$endpoint = isset($_POST['metasync_bing_endpoint_inline']) ? sanitize_text_field(wp_unslash($_POST['metasync_bing_endpoint_inline'])) : 'indexnow'; |
| 3187 |
$post_types = isset($_POST['metasync_bing_post_types_inline']) && is_array($_POST['metasync_bing_post_types_inline']) ? array_map('sanitize_title', wp_unslash($_POST['metasync_bing_post_types_inline'])) : []; |
| 3188 |
$disable_other_plugins = isset($_POST['metasync_bing_disable_other_plugins_inline']) ? true : false; |
| 3189 |
|
| 3190 |
$existing_settings = get_option('metasync_options_bing_instant_indexing', []); |
| 3191 |
|
| 3192 |
$new_settings = [ |
| 3193 |
'api_key' => $api_key, |
| 3194 |
'endpoint' => $endpoint, |
| 3195 |
'post_types' => array_values($post_types), |
| 3196 |
'disable_other_plugins' => $disable_other_plugins, |
| 3197 |
]; |
| 3198 |
|
| 3199 |
$settings = array_merge($existing_settings, $new_settings); |
| 3200 |
|
| 3201 |
$settings_changed = (json_encode($existing_settings) !== json_encode($settings)); |
| 3202 |
|
| 3203 |
$result = update_option('metasync_options_bing_instant_indexing', $settings); |
| 3204 |
|
| 3205 |
if (!$result && !$settings_changed) { |
| 3206 |
$result = true; |
| 3207 |
} |
| 3208 |
|
| 3209 |
if (!empty($api_key)) { |
| 3210 |
$safe_key = sanitize_file_name( $api_key ); |
| 3211 |
$file_path = ABSPATH . $safe_key . '.txt'; |
| 3212 |
$real_path = realpath( ABSPATH ); |
| 3213 |
if ( false === $real_path || 0 !== strpos( realpath( dirname( $file_path ) ), $real_path ) ) { |
| 3214 |
error_log( 'Bing IndexNow: Refusing to write outside ABSPATH' ); |
| 3215 |
return false; |
| 3216 |
} |
| 3217 |
// WP-511: serve the key virtually via template_redirect instead of |
| 3218 |
// writing a physical {key}.txt. An on-disk file is served statically |
| 3219 |
// by the web server and 403'd before PHP on some nginx setups, and |
| 3220 |
// cannot be written on read-only roots. Register the key (the saved |
| 3221 |
// api_key is also always part of the served set) and remove any stale |
| 3222 |
// physical file so the PHP route governs. |
| 3223 |
if (!class_exists('Metasync_Bing_Instant_Index')) { |
| 3224 |
require_once plugin_dir_path(dirname(__FILE__)) . 'bing-index/class-metasync-bing-instant-index.php'; |
| 3225 |
} |
| 3226 |
Metasync_Bing_Instant_Index::register_virtual_key($safe_key); |
| 3227 |
|
| 3228 |
if (file_exists($file_path)) { |
| 3229 |
@unlink($file_path); |
| 3230 |
} |
| 3231 |
|
| 3232 |
$current_options = Metasync::get_option(); |
| 3233 |
if (!isset($current_options['seo_controls']['enable_binginstantindex']) || $current_options['seo_controls']['enable_binginstantindex'] !== 'true') { |
| 3234 |
$current_options['seo_controls']['enable_binginstantindex'] = 'true'; |
| 3235 |
Metasync::set_option($current_options); |
| 3236 |
} |
| 3237 |
} |
| 3238 |
|
| 3239 |
return $result; |
| 3240 |
} |
| 3241 |
|
| 3242 |
/** |
| 3243 |
* Sanitize the metasync_llms_txt_settings option. |
| 3244 |
* |
| 3245 |
* The Settings API delivers raw form data; we coerce it into a predictable |
| 3246 |
* shape so the generator and MCP tools can rely on the structure. |
| 3247 |
* |
| 3248 |
* @param mixed $input Raw form data. |
| 3249 |
* @return array |
| 3250 |
*/ |
| 3251 |
public function sanitize_llms_txt_settings($input) |
| 3252 |
{ |
| 3253 |
if (!is_array($input)) { |
| 3254 |
$input = array(); |
| 3255 |
} |
| 3256 |
|
| 3257 |
$existing = get_option('metasync_llms_txt_settings', array()); |
| 3258 |
if (!is_array($existing)) { |
| 3259 |
$existing = array(); |
| 3260 |
} |
| 3261 |
|
| 3262 |
$output = $existing; |
| 3263 |
|
| 3264 |
$output['enabled'] = !empty($input['enabled']) |
| 3265 |
? (bool) filter_var($input['enabled'], FILTER_VALIDATE_BOOLEAN) |
| 3266 |
: false; |
| 3267 |
|
| 3268 |
if (isset($input['post_types']) && is_array($input['post_types'])) { |
| 3269 |
$output['post_types'] = array_values(array_filter(array_map('sanitize_text_field', $input['post_types']))); |
| 3270 |
} else { |
| 3271 |
$output['post_types'] = array('page', 'post'); |
| 3272 |
} |
| 3273 |
|
| 3274 |
if (isset($input['max_posts'])) { |
| 3275 |
$max = absint($input['max_posts']); |
| 3276 |
if ($max < 1) { |
| 3277 |
$max = 1; |
| 3278 |
} |
| 3279 |
if ($max > 500) { |
| 3280 |
$max = 500; |
| 3281 |
} |
| 3282 |
$output['max_posts'] = $max; |
| 3283 |
} |
| 3284 |
|
| 3285 |
if (isset($input['excluded_ids'])) { |
| 3286 |
if (is_array($input['excluded_ids'])) { |
| 3287 |
$ids = $input['excluded_ids']; |
| 3288 |
} else { |
| 3289 |
$ids = preg_split('/[\s,]+/', (string) $input['excluded_ids']); |
| 3290 |
} |
| 3291 |
$output['excluded_ids'] = array_values(array_filter(array_map('absint', (array) $ids))); |
| 3292 |
} |
| 3293 |
|
| 3294 |
if (isset($input['custom_description'])) { |
| 3295 |
$output['custom_description'] = sanitize_text_field((string) $input['custom_description']); |
| 3296 |
} |
| 3297 |
|
| 3298 |
$output['llms_full_enabled'] = !empty($input['llms_full_enabled']) |
| 3299 |
? (bool) filter_var($input['llms_full_enabled'], FILTER_VALIDATE_BOOLEAN) |
| 3300 |
: false; |
| 3301 |
|
| 3302 |
if (isset($input['max_posts_full'])) { |
| 3303 |
$max_full = absint($input['max_posts_full']); |
| 3304 |
if ($max_full < 1) { |
| 3305 |
$max_full = 1; |
| 3306 |
} |
| 3307 |
if ($max_full > 500) { |
| 3308 |
$max_full = 500; |
| 3309 |
} |
| 3310 |
$output['max_posts_full'] = $max_full; |
| 3311 |
} |
| 3312 |
|
| 3313 |
return $output; |
| 3314 |
} |
| 3315 |
} |
| 3316 |
|