2026-04-16 16:52:37 +00:00
< ? 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 .
2026-04-17 21:07:12 +00:00
* Version : 1.0 . 0
2026-04-16 16:52:37 +00:00
* Author : Andreas Andersen ( Ansico )
* Author URI : https :// ansico . dk
2026-04-17 21:07:12 +00:00
* Support URI : https :// ansico . dk / Ansico / Ansico - WP - Basic
2026-04-16 16:52:37 +00:00
* 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' ;
2026-04-17 21:07:12 +00:00
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' ;
2026-04-16 16:52:37 +00:00
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' ]);
2026-04-17 21:07:12 +00:00
add_action ( 'admin_init' , [ $this , 'maybe_handle_yoast_import' ]);
add_action ( 'admin_init' , [ $this , 'maybe_handle_generate_missing_meta' ]);
2026-04-16 16:52:37 +00:00
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 );
2026-04-17 21:07:12 +00:00
add_action ( 'wp' , [ $this , 'maybe_disable_core_canonical' ], 1 );
2026-04-16 16:52:37 +00:00
add_action ( 'wp_head' , [ $this , 'output_meta_description' ], 1 );
2026-04-17 21:07:12 +00:00
add_action ( 'wp_head' , [ $this , 'output_frontend_meta_tags' ], 2 );
2026-04-16 16:52:37 +00:00
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' ]);
2026-04-17 21:07:12 +00:00
add_action ( 'admin_notices' , [ $this , 'render_import_notice' ]);
2026-04-16 16:52:37 +00:00
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 ,
2026-04-17 21:07:12 +00:00
'social_defaults' => [
'facebook_publisher_url' => '' ,
'x_site_handle' => '' ,
'default_social_image' => '' ,
],
2026-04-16 16:52:37 +00:00
'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 ;
2026-04-17 21:07:12 +00:00
$settings [ 'social_defaults' ] = $this -> sanitize_social_defaults ( $settings [ 'social_defaults' ] ? ? []);
2026-04-16 16:52:37 +00:00
$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 ;
}
2026-04-17 21:07:12 +00:00
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' ]) : '' ,
];
}
2026-04-16 16:52:37 +00:00
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'
);
2026-04-17 21:07:12 +00:00
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'
);
2026-04-16 16:52:37 +00:00
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'
);
2026-04-17 21:07:12 +00:00
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'
);
2026-04-16 16:52:37 +00:00
}
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 ,
2026-04-17 21:07:12 +00:00
'social_defaults' => $this -> sanitize_social_defaults ( isset ( $input [ 'social_defaults' ]) ? $input [ 'social_defaults' ] : []),
2026-04-16 16:52:37 +00:00
'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 ();
2026-04-17 21:07:12 +00:00
$sitemap_url = home_url ( '/sitemap.xml' );
2026-04-16 16:52:37 +00:00
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' ),
2026-04-17 21:07:12 +00:00
esc_html__ ( 'Generate a sitemap index for supported content types.' , 'ansico-wp-basic' )
2026-04-16 16:52:37 +00:00
);
2026-04-17 21:07:12 +00:00
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>' ;
}
2026-04-16 16:52:37 +00:00
}
public function render_post_types_field () {
$settings = $this -> get_settings ();
$public_post_types = $this -> get_public_post_types ();
2026-04-17 21:07:12 +00:00
echo '<fieldset class="ansico-wp-basic-checkbox-grid">' ;
2026-04-16 16:52:37 +00:00
foreach ( $public_post_types as $post_type ) {
printf (
2026-04-17 21:07:12 +00:00
'<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>' ,
2026-04-16 16:52:37 +00:00
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>' ;
2026-04-17 21:07:12 +00:00
echo '<p class="description">' . esc_html__ ( 'Only selected post types will get Ansico SEO fields and bulk generation tools.' , 'ansico-wp-basic' ) . '</p>' ;
2026-04-16 16:52:37 +00:00
}
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' )
);
}
2026-04-17 21:07:12 +00:00
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>' ;
}
2026-04-16 16:52:37 +00:00
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>' ;
}
2026-04-17 21:07:12 +00:00
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 ;
}
2026-04-16 16:52:37 +00:00
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>' ;
}
2026-04-17 21:07:12 +00:00
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' ;
}
2026-04-16 16:52:37 +00:00
public function render_settings_page () {
if ( ! current_user_can ( 'manage_options' )) {
return ;
}
2026-04-17 21:07:12 +00:00
$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' ),
];
2026-04-16 16:52:37 +00:00
?>
< div class = " wrap ansico-wp-basic-settings-page " >
2026-04-17 21:07:12 +00:00
< 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 ; ?>
2026-04-16 16:52:37 +00:00
</ div >
< ? php
}
2026-04-17 21:07:12 +00:00
public function register_meta_boxes () {
2026-04-16 16:52:37 +00:00
$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 >
2026-04-17 21:07:12 +00:00
< 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 >
2026-04-16 16:52:37 +00:00
< 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 , "