id, 'active-twist-seo') === false) {
return;
}
wp_enqueue_media();
wp_enqueue_style('active-twist-seo-admin', plugin_dir_url(dirname(__DIR__)) . 'assets/css/admin.css', [], '1.0.0');
wp_enqueue_script('active-twist-seo-admin', plugin_dir_url(dirname(__DIR__)) . 'assets/js/admin.js', ['jquery'], '1.0.0', true);
}
// add_settings_page removed in favor of AdminMenu
public function register_settings()
{
register_setting(
'active_twist_seo_group',
Options::OPTION_NAME,
[$this, 'sanitize_options']
);
// -- Tab: General (Page Slug: active-twist-seo-general) --
$page_general = 'active-twist-seo-general';
add_settings_section('at_seo_general', esc_html__('General Defaults', 'active-twist-seo-json-ld'), '__return_empty_string', $page_general);
add_settings_section('at_seo_social', esc_html__('Social Defaults', 'active-twist-seo-json-ld'), '__return_empty_string', $page_general);
add_settings_section('at_seo_schema', esc_html__('Structured Data', 'active-twist-seo-json-ld'), '__return_empty_string', $page_general);
add_settings_section('at_seo_media', esc_html__('Media', 'active-twist-seo-json-ld'), '__return_empty_string', $page_general);
// Fields - General
add_settings_field('seo_title_suffix', esc_html__('Title Suffix', 'active-twist-seo-json-ld'), [$this, 'render_text_field'], $page_general, 'at_seo_general', [
'label_for' => 'seo_title_suffix',
'description' => esc_html__('Appended to auto-generated titles (e.g. " - Site Name").', 'active-twist-seo-json-ld')
]);
add_settings_field('default_description', esc_html__('Default Description', 'active-twist-seo-json-ld'), [$this, 'render_textarea_field'], $page_general, 'at_seo_general', [
'label_for' => 'default_description',
'description' => esc_html__('Fallback description for pages with no manual description or excerpt.', 'active-twist-seo-json-ld')
]);
// Fields - Social
add_settings_field('default_twitter_card', esc_html__('Twitter Card Type', 'active-twist-seo-json-ld'), [$this, 'render_select_field'], $page_general, 'at_seo_social', [
'label_for' => 'default_twitter_card',
'options' => [
'summary_large_image' => esc_html__('Summary Large Image', 'active-twist-seo-json-ld'),
'summary' => esc_html__('Summary', 'active-twist-seo-json-ld')
],
'description' => esc_html__('Default Twitter card type.', 'active-twist-seo-json-ld')
]);
add_settings_field('social_urls', esc_html__('Social Profiles', 'active-twist-seo-json-ld'), [$this, 'render_social_repeater'], $page_general, 'at_seo_social', [
'label_for' => 'social_urls',
'description' => esc_html__('Enter your social profile URLs. These will be added to your Organization\'s "sameAs" schema property, helping search engines associate these profiles with your site.', 'active-twist-seo-json-ld')
]);
// Fields - Schema
add_settings_field('org_name', esc_html__('Organization Name', 'active-twist-seo-json-ld'), [$this, 'render_text_field'], $page_general, 'at_seo_schema', [
'label_for' => 'org_name',
'description' => esc_html__('Name of the organization/website.', 'active-twist-seo-json-ld')
]);
add_settings_field('org_url', esc_html__('Organization URL', 'active-twist-seo-json-ld'), [$this, 'render_text_field'], $page_general, 'at_seo_schema', [
'label_for' => 'org_url',
'description' => esc_html__('URL of the organization/website.', 'active-twist-seo-json-ld')
]);
add_settings_field('org_logo', esc_html__('Organization Logo', 'active-twist-seo-json-ld'), [$this, 'render_image_field'], $page_general, 'at_seo_schema', [
'label_for' => 'org_logo',
'description' => esc_html__('URL of the organization logo.', 'active-twist-seo-json-ld')
]);
// Fields - Media
add_settings_field('default_og_image', esc_html__('Default Social Image', 'active-twist-seo-json-ld'), [$this, 'render_image_field'], $page_general, 'at_seo_media', [
'label_for' => 'default_og_image',
'description' => esc_html__('Fallback image when no featured image or manual OG image is set. Recommended size: 1200x630 pixels.', 'active-twist-seo-json-ld')
]);
// -- Tab: Business (Page Slug: active-twist-seo-business) --
$page_business = 'active-twist-seo-business';
add_settings_section('at_seo_local', esc_html__('Local Business (Schema)', 'active-twist-seo-json-ld'), '__return_empty_string', $page_business);
// Fields - Local Business
add_settings_field('local_business_enabled', esc_html__('Enable Local Business', 'active-twist-seo-json-ld'), [$this, 'render_checkbox_field'], $page_business, 'at_seo_local', [
'label_for' => 'local_business_enabled',
'description' => esc_html__('Output LocalBusiness schema on organization/website pages.', 'active-twist-seo-json-ld')
]);
add_settings_field('local_business_name', esc_html__('Business Name', 'active-twist-seo-json-ld'), [$this, 'render_text_field'], $page_business, 'at_seo_local', [
'label_for' => 'local_business_name',
'description' => esc_html__('Defaults to Organization Name if empty.', 'active-twist-seo-json-ld')
]);
add_settings_field('local_business_address', esc_html__('Address', 'active-twist-seo-json-ld'), [$this, 'render_textarea_field'], $page_business, 'at_seo_local', [
'label_for' => 'local_business_address',
'description' => esc_html__('Full address. Can include newlines.', 'active-twist-seo-json-ld')
]);
add_settings_field('local_business_phone', esc_html__('Telephone', 'active-twist-seo-json-ld'), [$this, 'render_text_field'], $page_business, 'at_seo_local', [
'label_for' => 'local_business_phone',
'description' => esc_html__('International format preferred (e.g. +1-555-555-5555).', 'active-twist-seo-json-ld')
]);
add_settings_field('local_business_hours', esc_html__('Opening Hours', 'active-twist-seo-json-ld'), [$this, 'render_text_field'], $page_business, 'at_seo_local', [
'label_for' => 'local_business_hours',
'description' => esc_html__('Simple text description (e.g. "Mo-Fr 09:00-17:00"). For complex hours, Schema syntax is managed manually.', 'active-twist-seo-json-ld')
]);
add_settings_field('local_business_image', esc_html__('Business Image', 'active-twist-seo-json-ld'), [$this, 'render_image_field'], $page_business, 'at_seo_local', [
'label_for' => 'local_business_image',
'description' => esc_html__('Representative image of the business location.', 'active-twist-seo-json-ld')
]);
// -- Tab: Advanced (Page Slug: active-twist-seo-advanced) --
$page_advanced = 'active-twist-seo-advanced';
add_settings_section('at_seo_toggles', esc_html__('Output Control (Advanced)', 'active-twist-seo-json-ld'), '__return_empty_string', $page_advanced);
add_settings_section('at_seo_danger', esc_html__('Danger Zone', 'active-twist-seo-json-ld'), [$this, 'render_danger_section'], $page_advanced);
// Fields - Output Toggles
add_settings_field('enable_canonical_output', esc_html__('Canonical Tags', 'active-twist-seo-json-ld'), [$this, 'render_checkbox_field'], $page_advanced, 'at_seo_toggles', [
'label_for' => 'enable_canonical_output',
/* translators: %s: HTML code tag. */
'description' => sprintf(esc_html__('Output %s tags.', 'active-twist-seo-json-ld'), 'rel="canonical"')
]);
add_settings_field('enable_json_ld_output', esc_html__('JSON-LD Schema', 'active-twist-seo-json-ld'), [$this, 'render_checkbox_field'], $page_advanced, 'at_seo_toggles', [
'label_for' => 'enable_json_ld_output',
'description' => esc_html__('Output structured data script blocks.', 'active-twist-seo-json-ld')
]);
add_settings_field('enable_social_output', esc_html__('Social Metadata', 'active-twist-seo-json-ld'), [$this, 'render_checkbox_field'], $page_advanced, 'at_seo_toggles', [
'label_for' => 'enable_social_output',
'description' => esc_html__('Output Open Graph and Twitter Card meta tags.', 'active-twist-seo-json-ld')
]);
add_settings_field('enable_robots_output', esc_html__('Robots Meta', 'active-twist-seo-json-ld'), [$this, 'render_checkbox_field'], $page_advanced, 'at_seo_toggles', [
'label_for' => 'enable_robots_output',
/* translators: %s: HTML code tag. */
'description' => sprintf(esc_html__('Output %s and other crawler directives.', 'active-twist-seo-json-ld'), 'robots')
]);
}
public function render_danger_section()
{
?>
delete all Active Twist SEO data, including settings, custom meta, and scan logs. It will also reset the "First Run" state.', 'active-twist-seo-json-ld')); ?>
' . esc_html($args['description']) . '
'; } } public function render_textarea_field($args) { $options = Options::get_all(); $key = $args['label_for']; $value = isset($options[$key]) ? $options[$key] : ''; echo ''; if (!empty($args['description'])) { echo '' . esc_html($args['description']) . '
'; } } public function render_social_repeater($args) { $options = Options::get_all(); $key = $args['label_for']; $stored = isset($options[$key]) ? $options[$key] : ''; // Parse stored string into array $current_urls = []; if (is_string($stored)) { $current_urls = array_filter(array_map('trim', explode("\n", $stored))); } elseif (is_array($stored)) { $current_urls = $stored; } $platforms = [ 'linkedin.com' => 'LinkedIn', 'twitter.com' => 'X (Twitter)', 'x.com' => 'X (Twitter)', 'facebook.com' => 'Facebook', 'instagram.com' => 'Instagram', 'youtube.com' => 'YouTube', 'github.com' => 'GitHub' ]; // Map current URLs to platforms $matched_urls = []; $custom_urls = []; foreach ($current_urls as $url) { $matched = false; foreach ($platforms as $domain => $name) { if (strpos($url, $domain) !== false) { // Avoid duplicate matches for same platform in this simple logic (or allow multiple?) // Let's allow multiple, but typically 1 per platform. // For UI simplicity, we pre-fill the "known platform" row. // If multiple URLs exist for same platform, only the LAST one effectively wins in this simple UI (key collision in visual map), // unless we use $matched_urls as array of ['platform' => ..., 'url' => ...]. // But we want fixed rows. // Let's populate the fixed row with the FIRST match found. if (!isset($matched_urls[$domain])) { $matched_urls[$domain] = $url; $matched = true; break; } } } if (!$matched) { $custom_urls[] = $url; } } // Render Known Platforms // Using `[]` in name allows multiple values. echo ''; // close repeater echo '' . esc_html($args['description']) . '
'; } public function render_select_field($args) { $options = Options::get_all(); $key = $args['label_for']; $value = isset($options[$key]) ? $options[$key] : ''; echo ''; if (!empty($args['description'])) { echo '' . esc_html($args['description']) . '
'; } } public function render_image_field($args) { $options = Options::get_all(); $key = $args['label_for']; $value = isset($options[$key]) ? $options[$key] : ''; echo '' . esc_html($args['description']) . '
'; } public function render_checkbox_field($args) { $options = Options::get_all(); $key = $args['label_for']; $checked = !empty($options[$key]); echo ''; if (!empty($args['description'])) { echo '' . wp_kses_post($args['description']) . '
'; } } public function handle_nuclear_reset() { check_admin_referer('at_seo_nuclear_reset', 'at_seo_reset_nonce'); $confirm = isset($_POST['reset_confirm']) ? sanitize_text_field(wp_unslash($_POST['reset_confirm'])) : ''; if ($confirm !== 'reset') { wp_die('Confirmation failed.'); } // 1. Delete Option delete_option(Options::OPTION_NAME); // 2. Clear 404 Log (and other transients/custom tables if any) delete_option('at_seo_404_monitor_log'); // 3. Reset First Run State (Actually done by deleting valid option, but ensure default returns false) // Deleting the option means next get() returns defaults, which has first_run_complete = false. // 4. Redirect to First Run (or Status which triggers First Run logic) // Since first_run_complete will be false, AdminMenu will show the First Run page. // Let's redirect to main dashboard or settings to restart flow. wp_safe_redirect(admin_url('admin.php?page=active-twist-seo-first-run')); exit; } }