2446 lines
105 KiB
PHP
2446 lines
105 KiB
PHP
<?php
|
||
/**
|
||
* Plugin Name: Ansico WP Basic
|
||
* Plugin URI: https://ansico.dk
|
||
* Description: Basic SEO fields for posts, pages, custom post types, author archives, taxonomy archives, and special archive pages, including meta title, meta description, and a live search result preview.
|
||
* Version: 1.0.0
|
||
* Author: Andreas Andersen (Ansico)
|
||
* Author URI: https://ansico.dk
|
||
* Support URI: https://ansico.dk/Ansico/Ansico-WP-Basic
|
||
* License: GPL-3.0-or-later
|
||
* License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||
* Text Domain: ansico-wp-basic
|
||
* Requires at least: 6.0
|
||
* Requires PHP: 7.4
|
||
*/
|
||
|
||
if (!defined('ABSPATH')) {
|
||
exit;
|
||
}
|
||
|
||
|
||
register_activation_hook(__FILE__, ['Ansico_WP_Basic', 'activate']);
|
||
register_deactivation_hook(__FILE__, ['Ansico_WP_Basic', 'deactivate']);
|
||
|
||
final class Ansico_WP_Basic {
|
||
const OPTION_KEY = 'ansico_wp_basic_settings';
|
||
const META_TITLE_KEY = '_ansico_wp_basic_meta_title';
|
||
const META_DESC_KEY = '_ansico_wp_basic_meta_description';
|
||
const HIDE_TITLE_KEY = '_ansico_wp_basic_hide_title';
|
||
const NONCE_KEY = 'ansico_wp_basic_meta_box_nonce';
|
||
const TERM_NONCE_KEY = 'ansico_wp_basic_term_nonce';
|
||
const USER_NONCE_KEY = 'ansico_wp_basic_user_nonce';
|
||
const IMPORT_ACTION = 'ansico_wp_basic_import_yoast';
|
||
const GENERATE_ACTION = 'ansico_wp_basic_generate_missing_meta';
|
||
const SOCIAL_IMAGE_KEY = '_ansico_wp_basic_social_image';
|
||
const CANONICAL_KEY = '_ansico_wp_basic_canonical';
|
||
const X_CREATOR_KEY = '_ansico_wp_basic_x_creator';
|
||
|
||
public static function activate() {
|
||
$plugin = new self();
|
||
$plugin->register_sitemap_rewrites();
|
||
flush_rewrite_rules();
|
||
}
|
||
|
||
public static function deactivate() {
|
||
flush_rewrite_rules();
|
||
}
|
||
|
||
public function __construct() {
|
||
add_action('admin_menu', [$this, 'register_admin_menu']);
|
||
add_action('admin_init', [$this, 'register_settings']);
|
||
add_action('admin_init', [$this, 'maybe_handle_yoast_import']);
|
||
add_action('admin_init', [$this, 'maybe_handle_generate_missing_meta']);
|
||
|
||
add_action('add_meta_boxes', [$this, 'register_meta_boxes']);
|
||
add_action('save_post', [$this, 'save_meta_box']);
|
||
add_filter('wp_insert_post_data', [$this, 'maybe_switch_post_type'], 10, 2);
|
||
|
||
add_action('show_user_profile', [$this, 'render_user_fields']);
|
||
add_action('edit_user_profile', [$this, 'render_user_fields']);
|
||
add_action('personal_options_update', [$this, 'save_user_fields']);
|
||
add_action('edit_user_profile_update', [$this, 'save_user_fields']);
|
||
|
||
add_action('created_term', [$this, 'save_term_fields'], 10, 3);
|
||
add_action('edited_term', [$this, 'save_term_fields'], 10, 3);
|
||
|
||
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
|
||
add_filter('pre_get_document_title', [$this, 'filter_document_title'], 20);
|
||
add_action('wp', [$this, 'maybe_disable_core_canonical'], 1);
|
||
add_action('wp_head', [$this, 'output_meta_description'], 1);
|
||
add_action('wp_head', [$this, 'output_frontend_meta_tags'], 2);
|
||
add_filter('the_title', [$this, 'maybe_hide_page_title'], 10, 2);
|
||
add_filter('single_post_title', [$this, 'maybe_hide_single_post_title'], 10, 2);
|
||
add_action('admin_notices', [$this, 'settings_notice_if_no_types']);
|
||
add_action('admin_notices', [$this, 'render_import_notice']);
|
||
add_action('init', [$this, 'register_taxonomy_hooks']);
|
||
add_action('init', [$this, 'register_sitemap_rewrites']);
|
||
add_action('template_redirect', [$this, 'maybe_render_sitemap']);
|
||
add_filter('query_vars', [$this, 'register_query_vars']);
|
||
}
|
||
|
||
public function get_settings() {
|
||
$defaults = [
|
||
'enable_meta_module' => 1,
|
||
'enable_sitemap_module' => 1,
|
||
'enabled_post_types' => $this->get_default_post_types(),
|
||
'enable_author_fields' => 1,
|
||
'enable_taxonomy_fields'=> 1,
|
||
'social_defaults' => [
|
||
'facebook_publisher_url' => '',
|
||
'x_site_handle' => '',
|
||
'default_social_image' => '',
|
||
],
|
||
'special_pages' => [
|
||
'date' => ['title' => '', 'description' => ''],
|
||
'search'=> ['title' => '', 'description' => ''],
|
||
'404' => ['title' => '', 'description' => ''],
|
||
'home' => ['title' => '', 'description' => ''],
|
||
],
|
||
'post_type_archives' => [],
|
||
];
|
||
|
||
$settings = get_option(self::OPTION_KEY, []);
|
||
if (!is_array($settings)) {
|
||
$settings = [];
|
||
}
|
||
|
||
$settings = wp_parse_args($settings, $defaults);
|
||
$settings['enable_meta_module'] = empty($settings['enable_meta_module']) ? 0 : 1;
|
||
$settings['enable_sitemap_module'] = empty($settings['enable_sitemap_module']) ? 0 : 1;
|
||
$settings['enabled_post_types'] = array_values(array_filter(array_map('sanitize_key', (array) $settings['enabled_post_types'])));
|
||
$settings['enable_author_fields'] = empty($settings['enable_author_fields']) ? 0 : 1;
|
||
$settings['enable_taxonomy_fields'] = empty($settings['enable_taxonomy_fields']) ? 0 : 1;
|
||
$settings['social_defaults'] = $this->sanitize_social_defaults($settings['social_defaults'] ?? []);
|
||
$settings['special_pages'] = $this->sanitize_special_pages($settings['special_pages']);
|
||
$settings['post_type_archives'] = $this->sanitize_post_type_archive_settings($settings['post_type_archives']);
|
||
|
||
return $settings;
|
||
}
|
||
|
||
private function get_public_post_types() {
|
||
$post_types = get_post_types([
|
||
'public' => true,
|
||
'show_ui' => true,
|
||
], 'objects');
|
||
|
||
unset($post_types['attachment']);
|
||
|
||
return $post_types;
|
||
}
|
||
|
||
private function get_default_post_types() {
|
||
return array_keys($this->get_public_post_types());
|
||
}
|
||
|
||
private function get_public_taxonomies() {
|
||
return get_taxonomies([
|
||
'public' => true,
|
||
'show_ui' => true,
|
||
], 'objects');
|
||
}
|
||
|
||
private function get_archive_post_types() {
|
||
$post_types = $this->get_public_post_types();
|
||
foreach ($post_types as $key => $post_type) {
|
||
if (empty($post_type->has_archive)) {
|
||
unset($post_types[$key]);
|
||
}
|
||
}
|
||
return $post_types;
|
||
}
|
||
|
||
private function sanitize_social_defaults($social_defaults) {
|
||
$social_defaults = is_array($social_defaults) ? $social_defaults : [];
|
||
|
||
return [
|
||
'facebook_publisher_url' => isset($social_defaults['facebook_publisher_url']) ? esc_url_raw($social_defaults['facebook_publisher_url']) : '',
|
||
'x_site_handle' => isset($social_defaults['x_site_handle']) ? sanitize_text_field($social_defaults['x_site_handle']) : '',
|
||
'default_social_image' => isset($social_defaults['default_social_image']) ? esc_url_raw($social_defaults['default_social_image']) : '',
|
||
'organization_name' => isset($social_defaults['organization_name']) ? sanitize_text_field($social_defaults['organization_name']) : '',
|
||
'organization_logo' => isset($social_defaults['organization_logo']) ? esc_url_raw($social_defaults['organization_logo']) : '',
|
||
];
|
||
}
|
||
|
||
private function sanitize_special_pages($special_pages) {
|
||
$defaults = [
|
||
'date' => ['title' => '', 'description' => ''],
|
||
'search' => ['title' => '', 'description' => ''],
|
||
'404' => ['title' => '', 'description' => ''],
|
||
'home' => ['title' => '', 'description' => ''],
|
||
];
|
||
|
||
$special_pages = is_array($special_pages) ? $special_pages : [];
|
||
|
||
foreach ($defaults as $key => $value) {
|
||
$page = isset($special_pages[$key]) && is_array($special_pages[$key]) ? $special_pages[$key] : [];
|
||
$defaults[$key] = [
|
||
'title' => isset($page['title']) ? sanitize_text_field($page['title']) : '',
|
||
'description' => isset($page['description']) ? sanitize_textarea_field($page['description']) : '',
|
||
];
|
||
}
|
||
|
||
return $defaults;
|
||
}
|
||
|
||
private function sanitize_post_type_archive_settings($archives) {
|
||
$clean = [];
|
||
$allowed = array_keys($this->get_archive_post_types());
|
||
$archives = is_array($archives) ? $archives : [];
|
||
|
||
foreach ($allowed as $post_type) {
|
||
$archive = isset($archives[$post_type]) && is_array($archives[$post_type]) ? $archives[$post_type] : [];
|
||
$clean[$post_type] = [
|
||
'title' => isset($archive['title']) ? sanitize_text_field($archive['title']) : '',
|
||
'description' => isset($archive['description']) ? sanitize_textarea_field($archive['description']) : '',
|
||
];
|
||
}
|
||
|
||
return $clean;
|
||
}
|
||
|
||
public function register_admin_menu() {
|
||
add_menu_page(
|
||
__('Ansico WP Basic', 'ansico-wp-basic'),
|
||
__('Ansico WP Basic', 'ansico-wp-basic'),
|
||
'manage_options',
|
||
'ansico-wp-basic',
|
||
[$this, 'render_settings_page'],
|
||
'dashicons-search',
|
||
81
|
||
);
|
||
|
||
add_submenu_page(
|
||
'ansico-wp-basic',
|
||
__('Settings', 'ansico-wp-basic'),
|
||
__('Settings', 'ansico-wp-basic'),
|
||
'manage_options',
|
||
'ansico-wp-basic',
|
||
[$this, 'render_settings_page']
|
||
);
|
||
}
|
||
|
||
public function register_settings() {
|
||
register_setting(
|
||
'ansico_wp_basic_settings_group',
|
||
self::OPTION_KEY,
|
||
[$this, 'sanitize_settings']
|
||
);
|
||
|
||
add_settings_section(
|
||
'ansico_wp_basic_main_section',
|
||
__('General settings', 'ansico-wp-basic'),
|
||
function () {
|
||
echo '<p>' . esc_html__('Choose where the SEO fields should be available.', 'ansico-wp-basic') . '</p>';
|
||
},
|
||
'ansico-wp-basic'
|
||
);
|
||
|
||
add_settings_field(
|
||
'enable_meta_module',
|
||
__('SEO module', 'ansico-wp-basic'),
|
||
[$this, 'render_meta_module_toggle'],
|
||
'ansico-wp-basic',
|
||
'ansico_wp_basic_main_section'
|
||
);
|
||
|
||
add_settings_field(
|
||
'enable_sitemap_module',
|
||
__('XML sitemap module', 'ansico-wp-basic'),
|
||
[$this, 'render_sitemap_module_toggle'],
|
||
'ansico-wp-basic',
|
||
'ansico_wp_basic_main_section'
|
||
);
|
||
|
||
add_settings_field(
|
||
'enabled_post_types',
|
||
__('Enable for post types', 'ansico-wp-basic'),
|
||
[$this, 'render_post_types_field'],
|
||
'ansico-wp-basic',
|
||
'ansico_wp_basic_main_section'
|
||
);
|
||
|
||
add_settings_field(
|
||
'enable_author_fields',
|
||
__('Author archive SEO fields', 'ansico-wp-basic'),
|
||
[$this, 'render_author_field_toggle'],
|
||
'ansico-wp-basic',
|
||
'ansico_wp_basic_main_section'
|
||
);
|
||
|
||
add_settings_field(
|
||
'enable_taxonomy_fields',
|
||
__('Taxonomy archive SEO fields', 'ansico-wp-basic'),
|
||
[$this, 'render_taxonomy_field_toggle'],
|
||
'ansico-wp-basic',
|
||
'ansico_wp_basic_main_section'
|
||
);
|
||
|
||
add_settings_section(
|
||
'ansico_wp_basic_social_section',
|
||
__('Social tags and schema', 'ansico-wp-basic'),
|
||
function () {
|
||
echo '<p>' . esc_html__('Configure site-wide defaults used for Open Graph, Twitter/X cards, canonical URLs, and schema markup output.', 'ansico-wp-basic') . '</p>';
|
||
},
|
||
'ansico-wp-basic'
|
||
);
|
||
|
||
add_settings_field(
|
||
'social_defaults',
|
||
__('Social defaults', 'ansico-wp-basic'),
|
||
[$this, 'render_social_defaults_fields'],
|
||
'ansico-wp-basic',
|
||
'ansico_wp_basic_social_section'
|
||
);
|
||
|
||
add_settings_section(
|
||
'ansico_wp_basic_archives_section',
|
||
__('Archives and special pages', 'ansico-wp-basic'),
|
||
function () {
|
||
echo '<p>' . esc_html__('Set SEO title and description for archive-style pages that do not have a normal editor screen.', 'ansico-wp-basic') . '</p>';
|
||
},
|
||
'ansico-wp-basic'
|
||
);
|
||
|
||
add_settings_field(
|
||
'special_pages',
|
||
__('Global archive pages', 'ansico-wp-basic'),
|
||
[$this, 'render_special_pages_fields'],
|
||
'ansico-wp-basic',
|
||
'ansico_wp_basic_archives_section'
|
||
);
|
||
|
||
add_settings_field(
|
||
'post_type_archives',
|
||
__('Post type archives', 'ansico-wp-basic'),
|
||
[$this, 'render_post_type_archive_fields'],
|
||
'ansico-wp-basic',
|
||
'ansico_wp_basic_archives_section'
|
||
);
|
||
|
||
add_settings_section(
|
||
'ansico_wp_basic_tools_section',
|
||
__('Import tools', 'ansico-wp-basic'),
|
||
function () {
|
||
echo '<p>' . esc_html__('Import existing SEO title, meta description, social defaults, canonical URLs, and social images from another plugin into Ansico WP Basic.', 'ansico-wp-basic') . '</p>';
|
||
},
|
||
'ansico-wp-basic'
|
||
);
|
||
|
||
add_settings_field(
|
||
'yoast_import',
|
||
__('Import from Yoast SEO', 'ansico-wp-basic'),
|
||
[$this, 'render_yoast_import_field'],
|
||
'ansico-wp-basic',
|
||
'ansico_wp_basic_tools_section'
|
||
);
|
||
|
||
add_settings_field(
|
||
'generate_missing_meta',
|
||
__('Generate missing META fields', 'ansico-wp-basic'),
|
||
[$this, 'render_generate_missing_meta_field'],
|
||
'ansico-wp-basic',
|
||
'ansico_wp_basic_tools_section'
|
||
);
|
||
}
|
||
|
||
public function sanitize_settings($input) {
|
||
$public_post_types = array_keys($this->get_public_post_types());
|
||
|
||
$enabled_post_types = [];
|
||
if (!empty($input['enabled_post_types']) && is_array($input['enabled_post_types'])) {
|
||
foreach ($input['enabled_post_types'] as $post_type) {
|
||
$post_type = sanitize_key($post_type);
|
||
if (in_array($post_type, $public_post_types, true)) {
|
||
$enabled_post_types[] = $post_type;
|
||
}
|
||
}
|
||
}
|
||
|
||
return [
|
||
'enable_meta_module' => empty($input['enable_meta_module']) ? 0 : 1,
|
||
'enable_sitemap_module' => empty($input['enable_sitemap_module']) ? 0 : 1,
|
||
'enabled_post_types' => array_values(array_unique($enabled_post_types)),
|
||
'enable_author_fields' => empty($input['enable_author_fields']) ? 0 : 1,
|
||
'enable_taxonomy_fields' => empty($input['enable_taxonomy_fields']) ? 0 : 1,
|
||
'social_defaults' => $this->sanitize_social_defaults(isset($input['social_defaults']) ? $input['social_defaults'] : []),
|
||
'special_pages' => $this->sanitize_special_pages(isset($input['special_pages']) ? $input['special_pages'] : []),
|
||
'post_type_archives' => $this->sanitize_post_type_archive_settings(isset($input['post_type_archives']) ? $input['post_type_archives'] : []),
|
||
];
|
||
}
|
||
|
||
|
||
public function render_meta_module_toggle() {
|
||
$settings = $this->get_settings();
|
||
printf(
|
||
'<label><input type="checkbox" name="%1$s[enable_meta_module]" value="1" %2$s> %3$s</label><p class="description">%4$s</p>',
|
||
esc_attr(self::OPTION_KEY),
|
||
checked($settings['enable_meta_module'], 1, false),
|
||
esc_html__('Enable Meta title and Meta description fields and frontend output.', 'ansico-wp-basic'),
|
||
esc_html__('Turn this off to hide the SEO editor UI and stop custom SEO output on the frontend, while preserving saved values.', 'ansico-wp-basic')
|
||
);
|
||
}
|
||
|
||
public function render_sitemap_module_toggle() {
|
||
$settings = $this->get_settings();
|
||
$sitemap_url = home_url('/sitemap.xml');
|
||
printf(
|
||
'<label><input type="checkbox" name="%1$s[enable_sitemap_module]" value="1" %2$s> %3$s</label><p class="description">%4$s</p>',
|
||
esc_attr(self::OPTION_KEY),
|
||
checked($settings['enable_sitemap_module'], 1, false),
|
||
esc_html__('Enable XML sitemap generation.', 'ansico-wp-basic'),
|
||
esc_html__('Generate a sitemap index for supported content types.', 'ansico-wp-basic')
|
||
);
|
||
|
||
if (!empty($settings['enable_sitemap_module'])) {
|
||
echo '<div class="ansico-wp-basic-sitemap-url-row">';
|
||
echo '<span class="ansico-wp-basic-sitemap-url-label"><strong>' . esc_html__('Sitemap URL:', 'ansico-wp-basic') . '</strong></span>';
|
||
echo '<code class="ansico-wp-basic-sitemap-url">' . esc_html($sitemap_url) . '</code>';
|
||
echo '<button type="button" class="button button-small ansico-wp-basic-copy-button" data-copy-text="' . esc_attr($sitemap_url) . '" aria-label="' . esc_attr__('Copy sitemap URL', 'ansico-wp-basic') . '" title="' . esc_attr__('Copy sitemap URL', 'ansico-wp-basic') . '">';
|
||
echo '<span class="dashicons dashicons-admin-page" aria-hidden="true"></span>';
|
||
echo '</button>';
|
||
echo '</div>';
|
||
echo '<p class="description">' . esc_html__('Use this URL when submitting your sitemap to search engines or external tools.', 'ansico-wp-basic') . '</p>';
|
||
}
|
||
}
|
||
|
||
public function render_post_types_field() {
|
||
$settings = $this->get_settings();
|
||
$public_post_types = $this->get_public_post_types();
|
||
|
||
echo '<fieldset class="ansico-wp-basic-checkbox-grid">';
|
||
foreach ($public_post_types as $post_type) {
|
||
printf(
|
||
'<label class="ansico-wp-basic-checkbox-item"><input type="checkbox" name="%1$s[enabled_post_types][]" value="%2$s" %3$s> <span>%4$s <code>(%2$s)</code></span></label>',
|
||
esc_attr(self::OPTION_KEY),
|
||
esc_attr($post_type->name),
|
||
checked(in_array($post_type->name, $settings['enabled_post_types'], true), true, false),
|
||
esc_html($post_type->labels->singular_name)
|
||
);
|
||
}
|
||
echo '</fieldset>';
|
||
echo '<p class="description">' . esc_html__('Only selected post types will get Ansico SEO fields and bulk generation tools.', 'ansico-wp-basic') . '</p>';
|
||
}
|
||
|
||
public function render_author_field_toggle() {
|
||
$settings = $this->get_settings();
|
||
printf(
|
||
'<label><input type="checkbox" name="%1$s[enable_author_fields]" value="1" %2$s> %3$s</label><p class="description">%4$s</p>',
|
||
esc_attr(self::OPTION_KEY),
|
||
checked($settings['enable_author_fields'], 1, false),
|
||
esc_html__('Allow SEO fields on user profile pages for author archives.', 'ansico-wp-basic'),
|
||
esc_html__('When enabled, each user profile gets Meta title and Meta description fields for that author archive.', 'ansico-wp-basic')
|
||
);
|
||
}
|
||
|
||
public function render_taxonomy_field_toggle() {
|
||
$settings = $this->get_settings();
|
||
printf(
|
||
'<label><input type="checkbox" name="%1$s[enable_taxonomy_fields]" value="1" %2$s> %3$s</label><p class="description">%4$s</p>',
|
||
esc_attr(self::OPTION_KEY),
|
||
checked($settings['enable_taxonomy_fields'], 1, false),
|
||
esc_html__('Allow SEO fields on taxonomy term pages.', 'ansico-wp-basic'),
|
||
esc_html__('Works for categories, tags, and public custom taxonomies.', 'ansico-wp-basic')
|
||
);
|
||
}
|
||
|
||
public function render_social_defaults_fields() {
|
||
$settings = $this->get_settings();
|
||
$social = $settings['social_defaults'] ?? [];
|
||
|
||
echo '<div class="ansico-wp-basic-settings-card">';
|
||
echo '<p><label><strong>' . esc_html__('Facebook publisher URL', 'ansico-wp-basic') . '</strong></label><input type="url" class="widefat" name="' . esc_attr(self::OPTION_KEY) . '[social_defaults][facebook_publisher_url]" value="' . esc_attr($social['facebook_publisher_url'] ?? '') . '" placeholder="https://www.facebook.com/yourpage/"></p>';
|
||
echo '<p><label><strong>' . esc_html__('Twitter/X site handle', 'ansico-wp-basic') . '</strong></label><input type="text" class="widefat" name="' . esc_attr(self::OPTION_KEY) . '[social_defaults][x_site_handle]" value="' . esc_attr($social['x_site_handle'] ?? '') . '" placeholder="@example"></p>';
|
||
echo '<p><label><strong>' . esc_html__('Default social image URL', 'ansico-wp-basic') . '</strong></label><input type="url" class="widefat" name="' . esc_attr(self::OPTION_KEY) . '[social_defaults][default_social_image]" value="' . esc_attr($social['default_social_image'] ?? '') . '" placeholder="https://example.com/social-image.jpg"></p>';
|
||
echo '<p><label><strong>' . esc_html__('Organization / publisher name', 'ansico-wp-basic') . '</strong></label><input type="text" class="widefat" name="' . esc_attr(self::OPTION_KEY) . '[social_defaults][organization_name]" value="' . esc_attr($social['organization_name'] ?? '') . '" placeholder="' . esc_attr(get_bloginfo('name')) . '"></p>';
|
||
echo '<p><label><strong>' . esc_html__('Organization logo URL', 'ansico-wp-basic') . '</strong></label><input type="url" class="widefat" name="' . esc_attr(self::OPTION_KEY) . '[social_defaults][organization_logo]" value="' . esc_attr($social['organization_logo'] ?? '') . '" placeholder="https://example.com/logo.png"></p>';
|
||
echo '<p class="description">' . esc_html__('Used for Open Graph, Twitter/X, canonical tags, and lightweight schema output. These values can also be imported from Yoast SEO when available.', 'ansico-wp-basic') . '</p>';
|
||
echo '</div>';
|
||
}
|
||
|
||
public function render_special_pages_fields() {
|
||
$settings = $this->get_settings();
|
||
if (empty($settings['enable_meta_module'])) {
|
||
echo '<p class="description">' . esc_html__('The SEO module is currently disabled.', 'ansico-wp-basic') . '</p>';
|
||
return;
|
||
}
|
||
$pages = [
|
||
'home' => __('Blog home / posts page', 'ansico-wp-basic'),
|
||
'date' => __('Date archives', 'ansico-wp-basic'),
|
||
'search' => __('Search results pages', 'ansico-wp-basic'),
|
||
'404' => __('404 page', 'ansico-wp-basic'),
|
||
];
|
||
|
||
echo '<div class="ansico-wp-basic-settings-grid">';
|
||
foreach ($pages as $key => $label) {
|
||
$title = $settings['special_pages'][$key]['title'] ?? '';
|
||
$description = $settings['special_pages'][$key]['description'] ?? '';
|
||
printf(
|
||
'<div class="ansico-wp-basic-settings-card"><h3>%1$s</h3><p><label><strong>%2$s</strong></label><input type="text" class="widefat" name="%3$s[special_pages][%4$s][title]" value="%5$s" placeholder="%6$s"></p><p><label><strong>%7$s</strong></label><textarea class="widefat" rows="3" name="%3$s[special_pages][%4$s][description]" placeholder="%8$s">%9$s</textarea></p></div>',
|
||
esc_html($label),
|
||
esc_html__('Meta title', 'ansico-wp-basic'),
|
||
esc_attr(self::OPTION_KEY),
|
||
esc_attr($key),
|
||
esc_attr($title),
|
||
esc_attr__('Enter a custom meta title', 'ansico-wp-basic'),
|
||
esc_html__('Meta description', 'ansico-wp-basic'),
|
||
esc_attr__('Enter a custom meta description', 'ansico-wp-basic'),
|
||
esc_textarea($description)
|
||
);
|
||
}
|
||
echo '</div>';
|
||
}
|
||
|
||
private function is_yoast_available_for_import() {
|
||
if (defined('WPSEO_VERSION') || class_exists('WPSEO_Options')) {
|
||
return true;
|
||
}
|
||
|
||
if (get_option('wpseo') || get_option('wpseo_taxonomy_meta') || get_option('wpseo_titles')) {
|
||
return true;
|
||
}
|
||
|
||
$active_plugins = (array) get_option('active_plugins', []);
|
||
if (in_array('wordpress-seo/wp-seo.php', $active_plugins, true)) {
|
||
return true;
|
||
}
|
||
|
||
if (is_multisite()) {
|
||
$network_active = array_keys((array) get_site_option('active_sitewide_plugins', []));
|
||
if (in_array('wordpress-seo/wp-seo.php', $network_active, true)) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
public function render_yoast_import_field() {
|
||
if (!current_user_can('manage_options')) {
|
||
return;
|
||
}
|
||
|
||
if (!$this->is_yoast_available_for_import()) {
|
||
echo '<p class="description">' . esc_html__('Yoast SEO was not detected on this site, and no Yoast SEO data was found in the database.', 'ansico-wp-basic') . '</p>';
|
||
return;
|
||
}
|
||
|
||
$url = wp_nonce_url(
|
||
add_query_arg([
|
||
'page' => 'ansico-wp-basic',
|
||
'tab' => 'tools',
|
||
'action' => self::IMPORT_ACTION,
|
||
], admin_url('admin.php')),
|
||
self::IMPORT_ACTION,
|
||
'ansico_wp_basic_import_nonce'
|
||
);
|
||
|
||
echo '<div class="ansico-wp-basic-tools-box">';
|
||
echo '<p>' . esc_html__('Copies filled Yoast SEO meta title, meta description, canonical URL, social image, and selected site-wide social settings into Ansico WP Basic. Existing Ansico values are kept unless you explicitly overwrite them.', 'ansico-wp-basic') . '</p>';
|
||
echo '<p><a href="' . esc_url($url) . '" class="button button-secondary">' . esc_html__('Import from Yoast SEO', 'ansico-wp-basic') . '</a></p>';
|
||
echo '<p class="description">' . esc_html__('Safe to run multiple times. This importer currently brings over title, description, canonical URL, Open Graph image, and selected Yoast social defaults.', 'ansico-wp-basic') . '</p>';
|
||
echo '</div>';
|
||
}
|
||
|
||
public function render_generate_missing_meta_field() {
|
||
if (!current_user_can('manage_options')) {
|
||
return;
|
||
}
|
||
|
||
$settings = $this->get_settings();
|
||
if (empty($settings['enable_meta_module'])) {
|
||
echo '<p class="description">' . esc_html__('The SEO module is currently disabled.', 'ansico-wp-basic') . '</p>';
|
||
return;
|
||
}
|
||
|
||
$enabled_types = !empty($settings['enabled_post_types']) ? $settings['enabled_post_types'] : [];
|
||
$labels = [];
|
||
foreach ($enabled_types as $post_type) {
|
||
$obj = get_post_type_object($post_type);
|
||
if ($obj && !empty($obj->labels->singular_name)) {
|
||
$labels[] = $obj->labels->singular_name;
|
||
} else {
|
||
$labels[] = $post_type;
|
||
}
|
||
}
|
||
|
||
$url = wp_nonce_url(
|
||
add_query_arg([
|
||
'page' => 'ansico-wp-basic',
|
||
'tab' => 'tools',
|
||
'action' => self::GENERATE_ACTION,
|
||
], admin_url('admin.php')),
|
||
self::GENERATE_ACTION,
|
||
'ansico_wp_basic_generate_nonce'
|
||
);
|
||
|
||
echo '<div class="ansico-wp-basic-tools-box">';
|
||
echo '<p>' . esc_html__('Generate missing META title and META description values for existing posts where one or both fields are empty. Existing Ansico values are kept.', 'ansico-wp-basic') . '</p>';
|
||
if (!empty($labels)) {
|
||
echo '<p class="description">' . esc_html(sprintf(__('Runs on enabled post types: %s.', 'ansico-wp-basic'), implode(', ', $labels))) . '</p>';
|
||
}
|
||
echo '<p><a href="' . esc_url($url) . '" class="button button-secondary">' . esc_html__('Generate missing META fields', 'ansico-wp-basic') . '</a></p>';
|
||
echo '<p class="description">' . esc_html__('META title is generated from the post title. META description uses the first paragraph of the content, trimmed to up to 150 characters.', 'ansico-wp-basic') . '</p>';
|
||
echo '</div>';
|
||
}
|
||
|
||
public function maybe_handle_yoast_import() {
|
||
if (!is_admin() || !current_user_can('manage_options')) {
|
||
return;
|
||
}
|
||
|
||
if (!isset($_GET['page'], $_GET['action']) || $_GET['page'] !== 'ansico-wp-basic' || $_GET['action'] !== self::IMPORT_ACTION) {
|
||
return;
|
||
}
|
||
|
||
check_admin_referer(self::IMPORT_ACTION, 'ansico_wp_basic_import_nonce');
|
||
|
||
$result = $this->import_yoast_meta_data();
|
||
|
||
$redirect_url = add_query_arg([
|
||
'page' => 'ansico-wp-basic',
|
||
'tab' => 'tools',
|
||
'ansico_wp_basic_notice' => rawurlencode(wp_json_encode($result)),
|
||
], admin_url('admin.php'));
|
||
|
||
wp_safe_redirect($redirect_url);
|
||
exit;
|
||
}
|
||
|
||
private function import_yoast_meta_data() {
|
||
$overwrite_existing = false;
|
||
$result = [
|
||
'type' => 'success',
|
||
'message' => __('Yoast SEO data import completed.', 'ansico-wp-basic'),
|
||
'posts_imported' => 0,
|
||
'terms_imported' => 0,
|
||
'posts_skipped' => 0,
|
||
'terms_skipped' => 0,
|
||
'settings_imported' => 0,
|
||
];
|
||
|
||
$settings = $this->get_settings();
|
||
$yoast_social = get_option('wpseo_social', []);
|
||
if (is_array($yoast_social)) {
|
||
$site_settings_imported = 0;
|
||
if (empty($settings['social_defaults']['facebook_publisher_url']) && !empty($yoast_social['facebook_site'])) {
|
||
$settings['social_defaults']['facebook_publisher_url'] = esc_url_raw($yoast_social['facebook_site']);
|
||
$site_settings_imported++;
|
||
}
|
||
if (empty($settings['social_defaults']['x_site_handle']) && !empty($yoast_social['twitter_site'])) {
|
||
$settings['social_defaults']['x_site_handle'] = $this->normalize_x_handle($yoast_social['twitter_site']);
|
||
$site_settings_imported++;
|
||
}
|
||
if (empty($settings['social_defaults']['default_social_image']) && !empty($yoast_social['og_default_image'])) {
|
||
$settings['social_defaults']['default_social_image'] = esc_url_raw($yoast_social['og_default_image']);
|
||
$site_settings_imported++;
|
||
}
|
||
if ($site_settings_imported > 0) {
|
||
update_option(self::OPTION_KEY, $settings);
|
||
$result['settings_imported'] = $site_settings_imported;
|
||
}
|
||
}
|
||
|
||
$public_post_types = array_keys($this->get_public_post_types());
|
||
if (!empty($public_post_types)) {
|
||
$posts = get_posts([
|
||
'post_type' => $public_post_types,
|
||
'post_status' => 'any',
|
||
'posts_per_page' => -1,
|
||
'fields' => 'ids',
|
||
'orderby' => 'ID',
|
||
'order' => 'ASC',
|
||
'suppress_filters' => true,
|
||
]);
|
||
|
||
foreach ((array) $posts as $post_id) {
|
||
$yoast_title = get_post_meta($post_id, '_yoast_wpseo_title', true);
|
||
$yoast_description = get_post_meta($post_id, '_yoast_wpseo_metadesc', true);
|
||
$yoast_canonical = get_post_meta($post_id, '_yoast_wpseo_canonical', true);
|
||
$yoast_social_image = get_post_meta($post_id, '_yoast_wpseo_opengraph-image', true);
|
||
if ($yoast_social_image === '') {
|
||
$yoast_social_image = get_post_meta($post_id, '_yoast_wpseo_opengraph-image-url', true);
|
||
}
|
||
|
||
if ($yoast_title === '' && $yoast_description === '' && $yoast_canonical === '' && $yoast_social_image === '') {
|
||
continue;
|
||
}
|
||
|
||
$existing_title = get_post_meta($post_id, self::META_TITLE_KEY, true);
|
||
$existing_description = get_post_meta($post_id, self::META_DESC_KEY, true);
|
||
$existing_canonical = get_post_meta($post_id, self::CANONICAL_KEY, true);
|
||
$existing_social_image = get_post_meta($post_id, self::SOCIAL_IMAGE_KEY, true);
|
||
|
||
if ($yoast_title !== '' && ($overwrite_existing || $existing_title === '')) {
|
||
update_post_meta($post_id, self::META_TITLE_KEY, sanitize_text_field($yoast_title));
|
||
$result['posts_imported']++;
|
||
}
|
||
elseif ($yoast_title !== '') {
|
||
$result['posts_skipped']++;
|
||
}
|
||
|
||
if ($yoast_description !== '' && ($overwrite_existing || $existing_description === '')) {
|
||
update_post_meta($post_id, self::META_DESC_KEY, sanitize_textarea_field($yoast_description));
|
||
$result['posts_imported']++;
|
||
}
|
||
elseif ($yoast_description !== '') {
|
||
$result['posts_skipped']++;
|
||
}
|
||
|
||
if ($yoast_canonical !== '' && ($overwrite_existing || $existing_canonical === '')) {
|
||
update_post_meta($post_id, self::CANONICAL_KEY, esc_url_raw($yoast_canonical));
|
||
$result['posts_imported']++;
|
||
}
|
||
elseif ($yoast_canonical !== '') {
|
||
$result['posts_skipped']++;
|
||
}
|
||
|
||
if ($yoast_social_image !== '' && ($overwrite_existing || $existing_social_image === '')) {
|
||
update_post_meta($post_id, self::SOCIAL_IMAGE_KEY, esc_url_raw($yoast_social_image));
|
||
$result['posts_imported']++;
|
||
}
|
||
elseif ($yoast_social_image !== '') {
|
||
$result['posts_skipped']++;
|
||
}
|
||
}
|
||
}
|
||
|
||
$yoast_taxonomy_meta = get_option('wpseo_taxonomy_meta', []);
|
||
if (is_array($yoast_taxonomy_meta) && !empty($yoast_taxonomy_meta)) {
|
||
$public_taxonomies = $this->get_public_taxonomies();
|
||
|
||
foreach ($public_taxonomies as $taxonomy => $taxonomy_obj) {
|
||
if (empty($yoast_taxonomy_meta[$taxonomy]) || !is_array($yoast_taxonomy_meta[$taxonomy])) {
|
||
continue;
|
||
}
|
||
|
||
foreach ($yoast_taxonomy_meta[$taxonomy] as $term_id => $term_meta) {
|
||
$term_id = (int) $term_id;
|
||
if ($term_id <= 0 || !is_array($term_meta)) {
|
||
continue;
|
||
}
|
||
|
||
$term = get_term($term_id, $taxonomy);
|
||
if (!$term || is_wp_error($term)) {
|
||
continue;
|
||
}
|
||
|
||
$yoast_title = isset($term_meta['wpseo_title']) ? (string) $term_meta['wpseo_title'] : '';
|
||
$yoast_description = isset($term_meta['wpseo_desc']) ? (string) $term_meta['wpseo_desc'] : '';
|
||
$yoast_canonical = isset($term_meta['wpseo_canonical']) ? (string) $term_meta['wpseo_canonical'] : '';
|
||
$yoast_social_image = isset($term_meta['wpseo_opengraph-image']) ? (string) $term_meta['wpseo_opengraph-image'] : '';
|
||
if ($yoast_social_image === '' && isset($term_meta['wpseo_opengraph-image-url'])) {
|
||
$yoast_social_image = (string) $term_meta['wpseo_opengraph-image-url'];
|
||
}
|
||
|
||
if ($yoast_title === '' && $yoast_description === '' && $yoast_canonical === '' && $yoast_social_image === '') {
|
||
continue;
|
||
}
|
||
|
||
$existing_title = get_term_meta($term_id, self::META_TITLE_KEY, true);
|
||
$existing_description = get_term_meta($term_id, self::META_DESC_KEY, true);
|
||
$existing_canonical = get_term_meta($term_id, self::CANONICAL_KEY, true);
|
||
$existing_social_image = get_term_meta($term_id, self::SOCIAL_IMAGE_KEY, true);
|
||
|
||
if ($yoast_title !== '' && ($overwrite_existing || $existing_title === '')) {
|
||
update_term_meta($term_id, self::META_TITLE_KEY, sanitize_text_field($yoast_title));
|
||
$result['terms_imported']++;
|
||
}
|
||
elseif ($yoast_title !== '') {
|
||
$result['terms_skipped']++;
|
||
}
|
||
|
||
if ($yoast_description !== '' && ($overwrite_existing || $existing_description === '')) {
|
||
update_term_meta($term_id, self::META_DESC_KEY, sanitize_textarea_field($yoast_description));
|
||
$result['terms_imported']++;
|
||
}
|
||
elseif ($yoast_description !== '') {
|
||
$result['terms_skipped']++;
|
||
}
|
||
|
||
if ($yoast_canonical !== '' && ($overwrite_existing || $existing_canonical === '')) {
|
||
update_term_meta($term_id, self::CANONICAL_KEY, esc_url_raw($yoast_canonical));
|
||
$result['terms_imported']++;
|
||
}
|
||
elseif ($yoast_canonical !== '') {
|
||
$result['terms_skipped']++;
|
||
}
|
||
|
||
if ($yoast_social_image !== '' && ($overwrite_existing || $existing_social_image === '')) {
|
||
update_term_meta($term_id, self::SOCIAL_IMAGE_KEY, esc_url_raw($yoast_social_image));
|
||
$result['terms_imported']++;
|
||
}
|
||
elseif ($yoast_social_image !== '') {
|
||
$result['terms_skipped']++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($result['posts_imported'] === 0 && $result['terms_imported'] === 0 && $result['settings_imported'] === 0) {
|
||
$result['type'] = 'warning';
|
||
$result['message'] = __('No Yoast SEO values were imported.', 'ansico-wp-basic');
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
public function maybe_handle_generate_missing_meta() {
|
||
if (!is_admin() || !current_user_can('manage_options')) {
|
||
return;
|
||
}
|
||
|
||
if (!isset($_GET['page'], $_GET['action']) || $_GET['page'] !== 'ansico-wp-basic' || $_GET['action'] !== self::GENERATE_ACTION) {
|
||
return;
|
||
}
|
||
|
||
check_admin_referer(self::GENERATE_ACTION, 'ansico_wp_basic_generate_nonce');
|
||
|
||
$result = $this->generate_missing_meta_data();
|
||
|
||
$redirect_url = add_query_arg([
|
||
'page' => 'ansico-wp-basic',
|
||
'tab' => 'tools',
|
||
'ansico_wp_basic_notice' => rawurlencode(wp_json_encode($result)),
|
||
], admin_url('admin.php'));
|
||
|
||
wp_safe_redirect($redirect_url);
|
||
exit;
|
||
}
|
||
|
||
private function generate_missing_meta_data() {
|
||
$settings = $this->get_settings();
|
||
$post_types = !empty($settings['enabled_post_types']) ? array_values(array_filter(array_map('sanitize_key', (array) $settings['enabled_post_types']))) : [];
|
||
|
||
$result = [
|
||
'type' => 'success',
|
||
'message' => __('Missing META values were generated.', 'ansico-wp-basic'),
|
||
'posts_imported' => 0,
|
||
'terms_imported' => 0,
|
||
'posts_skipped' => 0,
|
||
'terms_skipped' => 0,
|
||
'settings_imported' => 0,
|
||
];
|
||
|
||
if (empty($settings['enable_meta_module']) || empty($post_types)) {
|
||
$result['type'] = 'warning';
|
||
$result['message'] = __('No enabled post types were available for META generation.', 'ansico-wp-basic');
|
||
return $result;
|
||
}
|
||
|
||
$posts = get_posts([
|
||
'post_type' => $post_types,
|
||
'post_status' => 'any',
|
||
'posts_per_page' => -1,
|
||
'fields' => 'ids',
|
||
'orderby' => 'ID',
|
||
'order' => 'ASC',
|
||
'suppress_filters' => true,
|
||
]);
|
||
|
||
foreach ((array) $posts as $post_id) {
|
||
$post = get_post($post_id);
|
||
if (!$post || empty($post->post_type)) {
|
||
continue;
|
||
}
|
||
|
||
$existing_title = get_post_meta($post_id, self::META_TITLE_KEY, true);
|
||
$existing_description = get_post_meta($post_id, self::META_DESC_KEY, true);
|
||
$updated = false;
|
||
|
||
if ($existing_title === '') {
|
||
$generated_title = sanitize_text_field(get_the_title($post));
|
||
if ($generated_title !== '') {
|
||
update_post_meta($post_id, self::META_TITLE_KEY, $generated_title);
|
||
$result['posts_imported']++;
|
||
$updated = true;
|
||
}
|
||
}
|
||
|
||
if ($existing_description === '') {
|
||
$generated_description = $this->get_default_meta_description_for_post($post);
|
||
if ($generated_description !== '') {
|
||
update_post_meta($post_id, self::META_DESC_KEY, sanitize_textarea_field($generated_description));
|
||
$result['posts_imported']++;
|
||
$updated = true;
|
||
}
|
||
}
|
||
|
||
if (!$updated) {
|
||
$result['posts_skipped']++;
|
||
}
|
||
}
|
||
|
||
if ($result['posts_imported'] === 0) {
|
||
$result['type'] = 'warning';
|
||
$result['message'] = __('No missing META values needed to be generated.', 'ansico-wp-basic');
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
public function render_post_type_archive_fields() {
|
||
$settings = $this->get_settings();
|
||
if (empty($settings['enable_meta_module'])) {
|
||
echo '<p class="description">' . esc_html__('The SEO module is currently disabled.', 'ansico-wp-basic') . '</p>';
|
||
return;
|
||
}
|
||
$archives = $this->get_archive_post_types();
|
||
|
||
if (empty($archives)) {
|
||
echo '<p>' . esc_html__('No public post type archives were found.', 'ansico-wp-basic') . '</p>';
|
||
return;
|
||
}
|
||
|
||
echo '<div class="ansico-wp-basic-settings-grid">';
|
||
foreach ($archives as $post_type) {
|
||
$title = $settings['post_type_archives'][$post_type->name]['title'] ?? '';
|
||
$description = $settings['post_type_archives'][$post_type->name]['description'] ?? '';
|
||
printf(
|
||
'<div class="ansico-wp-basic-settings-card"><h3>%1$s <code>(%2$s)</code></h3><p><label><strong>%3$s</strong></label><input type="text" class="widefat" name="%4$s[post_type_archives][%2$s][title]" value="%5$s" placeholder="%6$s"></p><p><label><strong>%7$s</strong></label><textarea class="widefat" rows="3" name="%4$s[post_type_archives][%2$s][description]" placeholder="%8$s">%9$s</textarea></p></div>',
|
||
esc_html($post_type->labels->name),
|
||
esc_attr($post_type->name),
|
||
esc_html__('Meta title', 'ansico-wp-basic'),
|
||
esc_attr(self::OPTION_KEY),
|
||
esc_attr($title),
|
||
esc_attr__('Enter a custom meta title', 'ansico-wp-basic'),
|
||
esc_html__('Meta description', 'ansico-wp-basic'),
|
||
esc_attr__('Enter a custom meta description', 'ansico-wp-basic'),
|
||
esc_textarea($description)
|
||
);
|
||
}
|
||
echo '</div>';
|
||
}
|
||
|
||
public function render_import_notice() {
|
||
if (!is_admin() || !current_user_can('manage_options') || empty($_GET['ansico_wp_basic_notice'])) {
|
||
return;
|
||
}
|
||
|
||
$payload = json_decode(wp_unslash($_GET['ansico_wp_basic_notice']), true);
|
||
if (!is_array($payload)) {
|
||
return;
|
||
}
|
||
|
||
$type = (!empty($payload['type']) && in_array($payload['type'], ['success', 'warning', 'error'], true)) ? $payload['type'] : 'success';
|
||
$notice_class = $type === 'error' ? 'notice notice-error' : ($type === 'warning' ? 'notice notice-warning' : 'notice notice-success');
|
||
$message = isset($payload['message']) ? (string) $payload['message'] : __('Done.', 'ansico-wp-basic');
|
||
$posts_imported = isset($payload['posts_imported']) ? (int) $payload['posts_imported'] : 0;
|
||
$terms_imported = isset($payload['terms_imported']) ? (int) $payload['terms_imported'] : 0;
|
||
$posts_skipped = isset($payload['posts_skipped']) ? (int) $payload['posts_skipped'] : 0;
|
||
$terms_skipped = isset($payload['terms_skipped']) ? (int) $payload['terms_skipped'] : 0;
|
||
$settings_imported = isset($payload['settings_imported']) ? (int) $payload['settings_imported'] : 0;
|
||
|
||
echo '<div class="' . esc_attr($notice_class) . ' is-dismissible"><p>';
|
||
echo esc_html($message);
|
||
echo ' ';
|
||
echo esc_html(sprintf(__('Imported post fields: %1$d. Imported term fields: %2$d. Skipped existing post fields: %3$d. Skipped existing term fields: %4$d. Imported site settings: %5$d.', 'ansico-wp-basic'), $posts_imported, $terms_imported, $posts_skipped, $terms_skipped, $settings_imported));
|
||
echo '</p></div>';
|
||
}
|
||
|
||
private function get_current_settings_tab() {
|
||
$allowed_tabs = ['general', 'archives', 'tools'];
|
||
$tab = isset($_GET['tab']) ? sanitize_key(wp_unslash($_GET['tab'])) : 'general';
|
||
|
||
return in_array($tab, $allowed_tabs, true) ? $tab : 'general';
|
||
}
|
||
|
||
public function render_settings_page() {
|
||
if (!current_user_can('manage_options')) {
|
||
return;
|
||
}
|
||
|
||
$settings = $this->get_settings();
|
||
$current_tab = $this->get_current_settings_tab();
|
||
$base_url = admin_url('admin.php?page=ansico-wp-basic');
|
||
$tabs = [
|
||
'general' => __('General settings', 'ansico-wp-basic'),
|
||
'archives' => __('Archives and special pages', 'ansico-wp-basic'),
|
||
'tools' => __('Tools', 'ansico-wp-basic'),
|
||
];
|
||
?>
|
||
<div class="wrap ansico-wp-basic-settings-page">
|
||
<div class="ansico-wp-basic-page-header">
|
||
<div>
|
||
<h1><?php echo esc_html__('Ansico WP Basic', 'ansico-wp-basic'); ?></h1>
|
||
<p class="ansico-wp-basic-page-intro"><?php echo esc_html__('Configure SEO fields, social tags, archive metadata, and maintenance tools for your WordPress content.', 'ansico-wp-basic'); ?></p>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="nav-tab-wrapper ansico-wp-basic-tab-nav" aria-label="<?php echo esc_attr__('Settings sections', 'ansico-wp-basic'); ?>">
|
||
<?php foreach ($tabs as $tab_key => $tab_label) : ?>
|
||
<a href="<?php echo esc_url(add_query_arg('tab', $tab_key, $base_url)); ?>" class="nav-tab <?php echo $current_tab === $tab_key ? 'nav-tab-active' : ''; ?>"><?php echo esc_html($tab_label); ?></a>
|
||
<?php endforeach; ?>
|
||
</nav>
|
||
|
||
<?php if ($current_tab === 'tools') : ?>
|
||
<div class="ansico-wp-basic-tab-panel">
|
||
<section class="ansico-wp-basic-panel ansico-wp-basic-panel-narrow">
|
||
<div class="ansico-wp-basic-panel-header ansico-wp-basic-panel-header-intro">
|
||
<p><?php echo esc_html__('Run one-off maintenance actions without overwriting already completed SEO values.', 'ansico-wp-basic'); ?></p>
|
||
</div>
|
||
<div class="ansico-wp-basic-tool-stack">
|
||
<div class="ansico-wp-basic-tool-card">
|
||
<h3><?php echo esc_html__('Import from Yoast SEO', 'ansico-wp-basic'); ?></h3>
|
||
<?php $this->render_yoast_import_field(); ?>
|
||
</div>
|
||
<div class="ansico-wp-basic-tool-card">
|
||
<h3><?php echo esc_html__('Generate missing META fields', 'ansico-wp-basic'); ?></h3>
|
||
<?php $this->render_generate_missing_meta_field(); ?>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
<?php else : ?>
|
||
<form method="post" action="options.php" class="ansico-wp-basic-settings-form">
|
||
<?php settings_fields('ansico_wp_basic_settings_group'); ?>
|
||
|
||
<div class="ansico-wp-basic-tab-panel">
|
||
<?php if ($current_tab === 'general') : ?>
|
||
<section class="ansico-wp-basic-panel">
|
||
<div class="ansico-wp-basic-panel-header ansico-wp-basic-panel-header-intro">
|
||
<p><?php echo esc_html__('Choose where the SEO module should run and which content types should be editable.', 'ansico-wp-basic'); ?></p>
|
||
</div>
|
||
<div class="ansico-wp-basic-field-list">
|
||
<div class="ansico-wp-basic-field-row">
|
||
<div class="ansico-wp-basic-field-label"><h3><?php echo esc_html__('SEO module', 'ansico-wp-basic'); ?></h3></div>
|
||
<div class="ansico-wp-basic-field-control"><?php $this->render_meta_module_toggle(); ?></div>
|
||
</div>
|
||
<div class="ansico-wp-basic-field-row">
|
||
<div class="ansico-wp-basic-field-label"><h3><?php echo esc_html__('XML sitemap module', 'ansico-wp-basic'); ?></h3></div>
|
||
<div class="ansico-wp-basic-field-control"><?php $this->render_sitemap_module_toggle(); ?></div>
|
||
</div>
|
||
<div class="ansico-wp-basic-field-row">
|
||
<div class="ansico-wp-basic-field-label"><h3><?php echo esc_html__('Post types', 'ansico-wp-basic'); ?></h3></div>
|
||
<div class="ansico-wp-basic-field-control"><?php $this->render_post_types_field(); ?></div>
|
||
</div>
|
||
<div class="ansico-wp-basic-field-row">
|
||
<div class="ansico-wp-basic-field-label"><h3><?php echo esc_html__('Author archives', 'ansico-wp-basic'); ?></h3></div>
|
||
<div class="ansico-wp-basic-field-control"><?php $this->render_author_field_toggle(); ?></div>
|
||
</div>
|
||
<div class="ansico-wp-basic-field-row">
|
||
<div class="ansico-wp-basic-field-label"><h3><?php echo esc_html__('Taxonomy archives', 'ansico-wp-basic'); ?></h3></div>
|
||
<div class="ansico-wp-basic-field-control"><?php $this->render_taxonomy_field_toggle(); ?></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="ansico-wp-basic-panel">
|
||
<div class="ansico-wp-basic-panel-header">
|
||
<h2><?php echo esc_html__('Social tags and schema', 'ansico-wp-basic'); ?></h2>
|
||
<p><?php echo esc_html__('Configure site-wide defaults used for Open Graph, Twitter/X cards, canonical tags, and schema output.', 'ansico-wp-basic'); ?></p>
|
||
</div>
|
||
<?php $this->render_social_defaults_fields(); ?>
|
||
</section>
|
||
<?php elseif ($current_tab === 'archives') : ?>
|
||
<section class="ansico-wp-basic-panel">
|
||
<div class="ansico-wp-basic-panel-header ansico-wp-basic-panel-header-intro">
|
||
<p><?php echo esc_html__('Set SEO values for archive-like pages that do not have a standard post editor.', 'ansico-wp-basic'); ?></p>
|
||
</div>
|
||
<div class="ansico-wp-basic-subsection">
|
||
<h3><?php echo esc_html__('Global archive pages', 'ansico-wp-basic'); ?></h3>
|
||
<?php $this->render_special_pages_fields(); ?>
|
||
</div>
|
||
<div class="ansico-wp-basic-subsection">
|
||
<h3><?php echo esc_html__('Post type archives', 'ansico-wp-basic'); ?></h3>
|
||
<?php $this->render_post_type_archive_fields(); ?>
|
||
</div>
|
||
</section>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div class="ansico-wp-basic-submit-row">
|
||
<?php submit_button(__('Save Settings', 'ansico-wp-basic'), 'primary', 'submit', false, ['class' => 'button button-primary button-large']); ?>
|
||
</div>
|
||
</form>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
public function register_meta_boxes() {
|
||
$settings = $this->get_settings();
|
||
foreach ($settings['enabled_post_types'] as $post_type) {
|
||
if (!empty($settings['enable_meta_module'])) {
|
||
add_meta_box(
|
||
'ansico_wp_basic_seo',
|
||
__('Ansico WP Basic SEO', 'ansico-wp-basic'),
|
||
[$this, 'render_meta_box'],
|
||
$post_type,
|
||
'normal',
|
||
'default'
|
||
);
|
||
}
|
||
|
||
add_meta_box(
|
||
'ansico_wp_basic_tools',
|
||
__('Ansico WP Basic Tools', 'ansico-wp-basic'),
|
||
[$this, 'render_tools_meta_box'],
|
||
$post_type,
|
||
'side',
|
||
'default'
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
private function get_switchable_post_types() {
|
||
$post_types = $this->get_public_post_types();
|
||
return array_filter($post_types, function($post_type) {
|
||
return !empty($post_type->show_ui);
|
||
});
|
||
}
|
||
|
||
public function render_tools_meta_box($post) {
|
||
wp_nonce_field('ansico_wp_basic_save_meta_box', self::NONCE_KEY);
|
||
$post_type = get_post_type($post);
|
||
$switchable_types = $this->get_switchable_post_types();
|
||
$hide_title = (int) get_post_meta($post->ID, self::HIDE_TITLE_KEY, true);
|
||
echo '<div class="ansico-wp-basic-tools-box">';
|
||
if ($post_type === 'page') {
|
||
echo '<p><label><input type="checkbox" name="ansico_wp_basic_hide_title" value="1" ' . checked($hide_title, 1, false) . '> ' . esc_html__('Hide page title on the frontend for this page', 'ansico-wp-basic') . '</label></p>';
|
||
}
|
||
echo '<p><label for="ansico_wp_basic_change_post_type"><strong>' . esc_html__('Change post type', 'ansico-wp-basic') . '</strong></label>';
|
||
echo '<select id="ansico_wp_basic_change_post_type" name="ansico_wp_basic_change_post_type" class="ansico-post-type-select">';
|
||
foreach ($switchable_types as $type_obj) {
|
||
printf('<option value="%1$s" %2$s>%3$s</option>', esc_attr($type_obj->name), selected($type_obj->name, $post_type, false), esc_html($type_obj->labels->singular_name . ' (' . $type_obj->name . ')'));
|
||
}
|
||
echo '</select>';
|
||
echo '</div>';
|
||
}
|
||
|
||
private function render_snippet_box($args) {
|
||
$meta_title = isset($args['meta_title']) ? (string) $args['meta_title'] : '';
|
||
$meta_description = isset($args['meta_description']) ? (string) $args['meta_description'] : '';
|
||
$fallback_title = isset($args['fallback_title']) ? (string) $args['fallback_title'] : '';
|
||
$permalink = isset($args['permalink']) ? (string) $args['permalink'] : home_url('/');
|
||
$site_name = get_bloginfo('name');
|
||
?>
|
||
<div class="ansico-wp-basic-metabox">
|
||
<p>
|
||
<label for="ansico_wp_basic_meta_title"><strong><?php echo esc_html__('Meta title', 'ansico-wp-basic'); ?></strong></label>
|
||
<input type="text" id="ansico_wp_basic_meta_title" name="ansico_wp_basic_meta_title" class="widefat" value="<?php echo esc_attr($meta_title); ?>" placeholder="<?php echo esc_attr__('Enter a custom meta title', 'ansico-wp-basic'); ?>">
|
||
<span class="description"><?php echo esc_html__('Recommended: around 50–60 characters.', 'ansico-wp-basic'); ?></span>
|
||
<span class="ansico-wp-basic-counter" data-counter-for="title"></span>
|
||
</p>
|
||
<p>
|
||
<label for="ansico_wp_basic_meta_description"><strong><?php echo esc_html__('Meta description', 'ansico-wp-basic'); ?></strong></label>
|
||
<textarea id="ansico_wp_basic_meta_description" name="ansico_wp_basic_meta_description" class="widefat" rows="4" placeholder="<?php echo esc_attr__('Enter a custom meta description', 'ansico-wp-basic'); ?>"><?php echo esc_textarea($meta_description); ?></textarea>
|
||
<span class="description"><?php echo esc_html__('Recommended: around 140–155 characters. Longer text will usually be cut in search results.', 'ansico-wp-basic'); ?></span>
|
||
<span class="ansico-wp-basic-counter" data-counter-for="description"></span>
|
||
</p>
|
||
|
||
<p>
|
||
<label for="ansico_wp_basic_canonical"><strong><?php echo esc_html__('Canonical URL override', 'ansico-wp-basic'); ?></strong></label>
|
||
<input type="url" id="ansico_wp_basic_canonical" name="ansico_wp_basic_canonical" class="widefat" value="<?php echo esc_attr($args['canonical'] ?? ''); ?>" placeholder="<?php echo esc_attr($permalink); ?>">
|
||
<span class="description"><?php echo esc_html__('Optional. Leave empty to use the normal WordPress canonical URL.', 'ansico-wp-basic'); ?></span>
|
||
</p>
|
||
<p>
|
||
<label for="ansico_wp_basic_social_image"><strong><?php echo esc_html__('Open Graph image URL override', 'ansico-wp-basic'); ?></strong></label>
|
||
<input type="url" id="ansico_wp_basic_social_image" name="ansico_wp_basic_social_image" class="widefat" value="<?php echo esc_attr($args['social_image'] ?? ''); ?>" placeholder="https://example.com/social-image.jpg">
|
||
<span class="description"><?php echo esc_html__('Optional. Leave empty to use the featured image, default social image, or site icon.', 'ansico-wp-basic'); ?></span>
|
||
</p>
|
||
<p>
|
||
<label for="ansico_wp_basic_x_creator"><strong><?php echo esc_html__('Twitter/X creator handle override', 'ansico-wp-basic'); ?></strong></label>
|
||
<input type="text" id="ansico_wp_basic_x_creator" name="ansico_wp_basic_x_creator" class="widefat" value="<?php echo esc_attr($args['x_creator'] ?? ''); ?>" placeholder="@example">
|
||
<span class="description"><?php echo esc_html__('Optional. Leave empty to use the site-wide X handle.', 'ansico-wp-basic'); ?></span>
|
||
</p>
|
||
|
||
<div class="ansico-wp-basic-snippet-wrapper">
|
||
<strong><?php echo esc_html__('Search result preview', 'ansico-wp-basic'); ?></strong>
|
||
<div class="ansico-wp-basic-snippet" data-fallback-title="<?php echo esc_attr($fallback_title); ?>" data-permalink="<?php echo esc_url($permalink); ?>" data-site-name="<?php echo esc_attr($site_name); ?>">
|
||
<div class="ansico-wp-basic-snippet-site"><?php echo esc_html($site_name); ?></div>
|
||
<div class="ansico-wp-basic-snippet-url"></div>
|
||
<div class="ansico-wp-basic-snippet-title"></div>
|
||
<div class="ansico-wp-basic-snippet-description"><span class="ansico-wp-basic-snippet-description-text"></span><span class="ansico-wp-basic-snippet-overflow"></span></div>
|
||
</div>
|
||
<p class="description ansico-wp-basic-truncation-note"></p>
|
||
</div>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
|
||
private function get_trimmed_text_excerpt($text, $limit = 150) {
|
||
$text = trim((string) $text);
|
||
if ($text === '') {
|
||
return '';
|
||
}
|
||
|
||
$text = preg_replace('/\s+/u', ' ', wp_strip_all_tags($text));
|
||
if ($text === '') {
|
||
return '';
|
||
}
|
||
|
||
if (function_exists('mb_strlen') && function_exists('mb_substr')) {
|
||
if (mb_strlen($text) <= $limit) {
|
||
return $text;
|
||
}
|
||
$trimmed = mb_substr($text, 0, $limit + 1);
|
||
$last_space = mb_strrpos($trimmed, ' ');
|
||
if ($last_space !== false && $last_space > (int) floor($limit * 0.6)) {
|
||
$trimmed = mb_substr($trimmed, 0, $last_space);
|
||
} else {
|
||
$trimmed = mb_substr($trimmed, 0, $limit);
|
||
}
|
||
return rtrim($trimmed, "
|
||
|
||
|