diff --git a/ansico-wp-basic/ansico-wp-basic.php b/ansico-wp-basic/ansico-wp-basic.php
new file mode 100644
index 0000000..df5e36d
--- /dev/null
+++ b/ansico-wp-basic/ansico-wp-basic.php
@@ -0,0 +1,1346 @@
+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('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_head', [$this, 'output_meta_description'], 1);
+ 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('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,
+ '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['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_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 '
' . esc_html__('Choose where the SEO fields should be available.', 'ansico-wp-basic') . '
';
+ },
+ '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_archives_section',
+ __('Archives and special pages', 'ansico-wp-basic'),
+ function () {
+ echo '' . esc_html__('Set SEO title and description for archive-style pages that do not have a normal editor screen.', 'ansico-wp-basic') . '
';
+ },
+ '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'
+ );
+ }
+
+ 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,
+ '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(
+ ' %3$s%4$s
',
+ 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();
+ printf(
+ ' %3$s%4$s
',
+ esc_attr(self::OPTION_KEY),
+ checked($settings['enable_sitemap_module'], 1, false),
+ esc_html__('Enable XML sitemap generation.', 'ansico-wp-basic'),
+ esc_html__('When enabled, the sitemap index is available at /ansico-sitemap.xml.', 'ansico-wp-basic')
+ );
+ }
+
+ public function render_post_types_field() {
+ $settings = $this->get_settings();
+ $public_post_types = $this->get_public_post_types();
+
+ echo '';
+ foreach ($public_post_types as $post_type) {
+ printf(
+ ' %4$s (%2$s) ',
+ 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 ' ';
+ }
+
+ public function render_author_field_toggle() {
+ $settings = $this->get_settings();
+ printf(
+ ' %3$s%4$s
',
+ 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(
+ ' %3$s%4$s
',
+ 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_special_pages_fields() {
+ $settings = $this->get_settings();
+ if (empty($settings['enable_meta_module'])) {
+ echo '' . esc_html__('The SEO module is currently disabled.', 'ansico-wp-basic') . '
';
+ 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 '';
+ foreach ($pages as $key => $label) {
+ $title = $settings['special_pages'][$key]['title'] ?? '';
+ $description = $settings['special_pages'][$key]['description'] ?? '';
+ printf(
+ '
',
+ 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 '
';
+ }
+
+ public function render_post_type_archive_fields() {
+ $settings = $this->get_settings();
+ if (empty($settings['enable_meta_module'])) {
+ echo '' . esc_html__('The SEO module is currently disabled.', 'ansico-wp-basic') . '
';
+ return;
+ }
+ $archives = $this->get_archive_post_types();
+
+ if (empty($archives)) {
+ echo '' . esc_html__('No public post type archives were found.', 'ansico-wp-basic') . '
';
+ return;
+ }
+
+ echo '';
+ foreach ($archives as $post_type) {
+ $title = $settings['post_type_archives'][$post_type->name]['title'] ?? '';
+ $description = $settings['post_type_archives'][$post_type->name]['description'] ?? '';
+ printf(
+ '
',
+ 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 '
';
+ }
+
+ public function render_settings_page() {
+ if (!current_user_can('manage_options')) {
+ return;
+ }
+ ?>
+
+
+
+ get_settings(); ?>
+
' . esc_html(home_url('/ansico-sitemap.xml')) . '' : esc_html__('Disabled', 'ansico-wp-basic'); ?>
+
+
+ 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 '';
+ }
+
+ 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');
+ ?>
+
+ (int) floor($limit * 0.6)) {
+ $trimmed = mb_substr($trimmed, 0, $last_space);
+ } else {
+ $trimmed = mb_substr($trimmed, 0, $limit);
+ }
+ return rtrim($trimmed, "
+
+ .,;:-");
+ }
+
+ if (strlen($text) <= $limit) {
+ return $text;
+ }
+ $trimmed = substr($text, 0, $limit + 1);
+ $last_space = strrpos($trimmed, ' ');
+ if ($last_space !== false && $last_space > (int) floor($limit * 0.6)) {
+ $trimmed = substr($trimmed, 0, $last_space);
+ } else {
+ $trimmed = substr($trimmed, 0, $limit);
+ }
+ return rtrim($trimmed, "
+
+ .,;:-");
+ }
+
+ private function get_default_meta_description_for_post($post) {
+ $content = isset($post->post_content) ? (string) $post->post_content : '';
+ $first_paragraph = '';
+
+ if (preg_match('/]*>(.*?)<\/p>/is', $content, $matches)) {
+ $first_paragraph = wp_strip_all_tags($matches[1]);
+ }
+
+ if ($first_paragraph === '') {
+ $blocks = preg_split('/
+\s*
+/', wp_strip_all_tags($content));
+ if (is_array($blocks)) {
+ foreach ($blocks as $block) {
+ $block = trim((string) $block);
+ if ($block !== '') {
+ $first_paragraph = $block;
+ break;
+ }
+ }
+ }
+ }
+
+ if ($first_paragraph === '') {
+ $first_paragraph = has_excerpt($post) ? $post->post_excerpt : wp_trim_words(wp_strip_all_tags($content), 30, '');
+ }
+
+ return $this->get_trimmed_text_excerpt($first_paragraph, 150);
+ }
+
+ private function build_document_title_from_meta($meta_title) {
+ $meta_title = trim((string) $meta_title);
+ if ($meta_title === '') {
+ return '';
+ }
+
+ $site_name = trim((string) get_bloginfo('name'));
+ if ($site_name === '') {
+ return wp_strip_all_tags($meta_title);
+ }
+
+ $title_plain = wp_strip_all_tags($meta_title);
+ if (stripos($title_plain, $site_name) !== false) {
+ return $title_plain;
+ }
+
+ return $title_plain . ' - ' . $site_name;
+ }
+
+ public function render_meta_box($post) {
+ wp_nonce_field('ansico_wp_basic_save_meta_box', self::NONCE_KEY);
+
+ $saved_meta_title = get_post_meta($post->ID, self::META_TITLE_KEY, true);
+ $saved_meta_description = get_post_meta($post->ID, self::META_DESC_KEY, true);
+ $fallback_title = get_the_title($post);
+ $meta_title = $saved_meta_title !== '' ? $saved_meta_title : $fallback_title;
+ $meta_description = $saved_meta_description !== '' ? $saved_meta_description : $this->get_default_meta_description_for_post($post);
+ $permalink = get_permalink($post) ?: home_url('/');
+
+ $this->render_snippet_box([
+ 'meta_title' => $meta_title,
+ 'meta_description' => $meta_description,
+ 'fallback_title' => $fallback_title,
+ 'permalink' => $permalink,
+ ]);
+ }
+
+ public function save_meta_box($post_id) {
+ if (!isset($_POST[self::NONCE_KEY]) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST[self::NONCE_KEY])), 'ansico_wp_basic_save_meta_box')) {
+ return;
+ }
+
+ if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
+ return;
+ }
+
+ if (!current_user_can('edit_post', $post_id)) {
+ return;
+ }
+
+ $post_type = get_post_type($post_id);
+ $settings = $this->get_settings();
+ if (!in_array($post_type, $settings['enabled_post_types'], true)) {
+ return;
+ }
+
+ if ($post_type === 'page') {
+ $hide_title = isset($_POST['ansico_wp_basic_hide_title']) ? 1 : 0;
+ if ($hide_title) {
+ update_post_meta($post_id, self::HIDE_TITLE_KEY, 1);
+ } else {
+ delete_post_meta($post_id, self::HIDE_TITLE_KEY);
+ }
+ }
+
+ if (empty($settings['enable_meta_module'])) {
+ return;
+ }
+
+ $meta_title = isset($_POST['ansico_wp_basic_meta_title']) ? sanitize_text_field(wp_unslash($_POST['ansico_wp_basic_meta_title'])) : '';
+ $meta_description = isset($_POST['ansico_wp_basic_meta_description']) ? sanitize_textarea_field(wp_unslash($_POST['ansico_wp_basic_meta_description'])) : '';
+
+ if ($meta_title === '') {
+ delete_post_meta($post_id, self::META_TITLE_KEY);
+ } else {
+ update_post_meta($post_id, self::META_TITLE_KEY, $meta_title);
+ }
+
+ if ($meta_description === '') {
+ delete_post_meta($post_id, self::META_DESC_KEY);
+ } else {
+ update_post_meta($post_id, self::META_DESC_KEY, $meta_description);
+ }
+ }
+
+ public function maybe_switch_post_type($data, $postarr) {
+ if (is_admin() && isset($_POST[self::NONCE_KEY]) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST[self::NONCE_KEY])), 'ansico_wp_basic_save_meta_box')) {
+ if (!empty($postarr['ID']) && current_user_can('edit_post', (int) $postarr['ID']) && !empty($_POST['ansico_wp_basic_change_post_type'])) {
+ $new_post_type = sanitize_key(wp_unslash($_POST['ansico_wp_basic_change_post_type']));
+ $allowed_types = array_keys($this->get_switchable_post_types());
+ if (in_array($new_post_type, $allowed_types, true)) {
+ $data['post_type'] = $new_post_type;
+ }
+ }
+ }
+ return $data;
+ }
+
+ public function maybe_hide_page_title($title, $post_id = 0) {
+ if (is_admin() || !is_singular('page')) {
+ return $title;
+ }
+
+ $queried_id = get_queried_object_id();
+ if (!$queried_id || (int) $post_id !== (int) $queried_id) {
+ return $title;
+ }
+
+ if ((int) get_post_meta($queried_id, self::HIDE_TITLE_KEY, true) === 1) {
+ return '';
+ }
+
+ return $title;
+ }
+
+ public function maybe_hide_single_post_title($title, $post = null) {
+ if (is_admin() || !is_singular('page')) {
+ return $title;
+ }
+
+ $queried_id = get_queried_object_id();
+ if ($queried_id && (int) get_post_meta($queried_id, self::HIDE_TITLE_KEY, true) === 1) {
+ return '';
+ }
+
+ return $title;
+ }
+
+ public function register_taxonomy_hooks() {
+ foreach ($this->get_public_taxonomies() as $taxonomy) {
+ add_action($taxonomy->name . '_add_form_fields', [$this, 'render_term_add_fields']);
+ add_action($taxonomy->name . '_edit_form_fields', [$this, 'render_term_edit_fields']);
+ }
+ }
+
+ public function render_term_add_fields($taxonomy) {
+ $settings = $this->get_settings();
+ if (empty($settings['enable_meta_module']) || empty($settings['enable_taxonomy_fields'])) {
+ return;
+ }
+
+ $taxonomy_name = is_string($taxonomy) ? $taxonomy : '';
+ $example_url = home_url('/' . $taxonomy_name . '/example/');
+ wp_nonce_field('ansico_wp_basic_save_term_fields', self::TERM_NONCE_KEY);
+ ?>
+
+
+ render_snippet_box([
+ 'meta_title' => '',
+ 'meta_description' => '',
+ 'fallback_title' => ucfirst($taxonomy_name) . ' archive',
+ 'permalink' => $example_url,
+ ]); ?>
+
+ get_settings();
+ if (empty($settings['enable_meta_module']) || empty($settings['enable_taxonomy_fields'])) {
+ return;
+ }
+
+ $meta_title = get_term_meta($term->term_id, self::META_TITLE_KEY, true);
+ $meta_description = get_term_meta($term->term_id, self::META_DESC_KEY, true);
+ wp_nonce_field('ansico_wp_basic_save_term_fields', self::TERM_NONCE_KEY);
+ ?>
+
+
+
+ render_snippet_box([
+ 'meta_title' => $meta_title,
+ 'meta_description' => $meta_description,
+ 'fallback_title' => $term->name,
+ 'permalink' => (!is_wp_error(get_term_link($term)) ? get_term_link($term) : home_url('/')),
+ ]); ?>
+
+
+ get_settings();
+ if (empty($settings['enable_meta_module']) || empty($settings['enable_taxonomy_fields'])) {
+ return;
+ }
+
+ $taxonomy_object = $taxonomy ? get_taxonomy($taxonomy) : null;
+ if ($taxonomy_object && (!is_object($taxonomy_object) || empty($taxonomy_object->public) || empty($taxonomy_object->show_ui))) {
+ return;
+ }
+
+ if (!isset($_POST[self::TERM_NONCE_KEY]) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST[self::TERM_NONCE_KEY])), 'ansico_wp_basic_save_term_fields')) {
+ return;
+ }
+
+ if (!current_user_can('manage_categories')) {
+ return;
+ }
+
+ $meta_title = isset($_POST['ansico_wp_basic_meta_title']) ? sanitize_text_field(wp_unslash($_POST['ansico_wp_basic_meta_title'])) : '';
+ $meta_description = isset($_POST['ansico_wp_basic_meta_description']) ? sanitize_textarea_field(wp_unslash($_POST['ansico_wp_basic_meta_description'])) : '';
+
+ if ($meta_title === '') {
+ delete_term_meta($term_id, self::META_TITLE_KEY);
+ } else {
+ update_term_meta($term_id, self::META_TITLE_KEY, $meta_title);
+ }
+
+ if ($meta_description === '') {
+ delete_term_meta($term_id, self::META_DESC_KEY);
+ } else {
+ update_term_meta($term_id, self::META_DESC_KEY, $meta_description);
+ }
+ }
+
+ public function render_user_fields($user) {
+ $settings = $this->get_settings();
+ if (empty($settings['enable_meta_module']) || empty($settings['enable_author_fields'])) {
+ return;
+ }
+
+ wp_nonce_field('ansico_wp_basic_save_user_fields', self::USER_NONCE_KEY);
+ $meta_title = get_user_meta($user->ID, self::META_TITLE_KEY, true);
+ $meta_description = get_user_meta($user->ID, self::META_DESC_KEY, true);
+ ?>
+
+ render_snippet_box([
+ 'meta_title' => $meta_title,
+ 'meta_description' => $meta_description,
+ 'fallback_title' => $user->display_name,
+ 'permalink' => get_author_posts_url($user->ID),
+ ]); ?>
+ get_settings();
+ if (empty($settings['enable_meta_module']) || empty($settings['enable_author_fields'])) {
+ return;
+ }
+
+ if (!current_user_can('edit_user', $user_id)) {
+ return;
+ }
+
+ if (!isset($_POST[self::USER_NONCE_KEY]) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST[self::USER_NONCE_KEY])), 'ansico_wp_basic_save_user_fields')) {
+ return;
+ }
+
+ $meta_title = isset($_POST['ansico_wp_basic_meta_title']) ? sanitize_text_field(wp_unslash($_POST['ansico_wp_basic_meta_title'])) : '';
+ $meta_description = isset($_POST['ansico_wp_basic_meta_description']) ? sanitize_textarea_field(wp_unslash($_POST['ansico_wp_basic_meta_description'])) : '';
+
+ if ($meta_title === '') {
+ delete_user_meta($user_id, self::META_TITLE_KEY);
+ } else {
+ update_user_meta($user_id, self::META_TITLE_KEY, $meta_title);
+ }
+
+ if ($meta_description === '') {
+ delete_user_meta($user_id, self::META_DESC_KEY);
+ } else {
+ update_user_meta($user_id, self::META_DESC_KEY, $meta_description);
+ }
+ }
+
+ public function enqueue_admin_assets($hook) {
+ $allowed_hooks = [
+ 'post.php',
+ 'post-new.php',
+ 'profile.php',
+ 'user-edit.php',
+ 'edit-tags.php',
+ 'term.php',
+ 'toplevel_page_ansico-wp-basic',
+ ];
+
+ if (!in_array($hook, $allowed_hooks, true)) {
+ return;
+ }
+
+ wp_enqueue_style(
+ 'ansico-wp-basic-admin',
+ plugin_dir_url(__FILE__) . 'assets/admin.css',
+ [],
+ '0.0.0.2'
+ );
+
+ wp_enqueue_script(
+ 'ansico-wp-basic-admin',
+ plugin_dir_url(__FILE__) . 'assets/admin.js',
+ [],
+ '0.0.0.2',
+ true
+ );
+ }
+
+ private function get_context_meta_title() {
+ $settings = $this->get_settings();
+ if (empty($settings['enable_meta_module'])) {
+ return '';
+ }
+
+ if (is_singular()) {
+ $post_id = get_queried_object_id();
+ $post_type = get_post_type($post_id);
+ if ($post_id && in_array($post_type, $settings['enabled_post_types'], true)) {
+ return get_post_meta($post_id, self::META_TITLE_KEY, true);
+ }
+ }
+
+ if (is_author() && !empty($settings['enable_author_fields'])) {
+ $author = get_queried_object();
+ if ($author instanceof WP_User || (is_object($author) && isset($author->ID))) {
+ return get_user_meta((int) $author->ID, self::META_TITLE_KEY, true);
+ }
+ }
+
+ if ((is_category() || is_tag() || is_tax()) && !empty($settings['enable_taxonomy_fields'])) {
+ $term = get_queried_object();
+ if ($term instanceof WP_Term || (is_object($term) && isset($term->term_id))) {
+ return get_term_meta((int) $term->term_id, self::META_TITLE_KEY, true);
+ }
+ }
+
+ if (is_post_type_archive()) {
+ $post_type = get_query_var('post_type');
+ $post_type = is_array($post_type) ? reset($post_type) : $post_type;
+ return $settings['post_type_archives'][$post_type]['title'] ?? '';
+ }
+
+ if (is_home()) {
+ return $settings['special_pages']['home']['title'] ?? '';
+ }
+
+ if (is_date()) {
+ return $settings['special_pages']['date']['title'] ?? '';
+ }
+
+ if (is_search()) {
+ return $settings['special_pages']['search']['title'] ?? '';
+ }
+
+ if (is_404()) {
+ return $settings['special_pages']['404']['title'] ?? '';
+ }
+
+ return '';
+ }
+
+ private function get_context_meta_description() {
+ $settings = $this->get_settings();
+ if (empty($settings['enable_meta_module'])) {
+ return '';
+ }
+
+ if (is_singular()) {
+ $post_id = get_queried_object_id();
+ $post_type = get_post_type($post_id);
+ if ($post_id && in_array($post_type, $settings['enabled_post_types'], true)) {
+ return get_post_meta($post_id, self::META_DESC_KEY, true);
+ }
+ }
+
+ if (is_author() && !empty($settings['enable_author_fields'])) {
+ $author = get_queried_object();
+ if ($author instanceof WP_User || (is_object($author) && isset($author->ID))) {
+ return get_user_meta((int) $author->ID, self::META_DESC_KEY, true);
+ }
+ }
+
+ if ((is_category() || is_tag() || is_tax()) && !empty($settings['enable_taxonomy_fields'])) {
+ $term = get_queried_object();
+ if ($term instanceof WP_Term || (is_object($term) && isset($term->term_id))) {
+ return get_term_meta((int) $term->term_id, self::META_DESC_KEY, true);
+ }
+ }
+
+ if (is_post_type_archive()) {
+ $post_type = get_query_var('post_type');
+ $post_type = is_array($post_type) ? reset($post_type) : $post_type;
+ return $settings['post_type_archives'][$post_type]['description'] ?? '';
+ }
+
+ if (is_home()) {
+ return $settings['special_pages']['home']['description'] ?? '';
+ }
+
+ if (is_date()) {
+ return $settings['special_pages']['date']['description'] ?? '';
+ }
+
+ if (is_search()) {
+ return $settings['special_pages']['search']['description'] ?? '';
+ }
+
+ if (is_404()) {
+ return $settings['special_pages']['404']['description'] ?? '';
+ }
+
+ return '';
+ }
+
+ public function filter_document_title($title) {
+ if (is_admin() || is_feed()) {
+ return $title;
+ }
+
+ $meta_title = $this->get_context_meta_title();
+ if (!empty($meta_title)) {
+ return $this->build_document_title_from_meta($meta_title);
+ }
+
+ return $title;
+ }
+
+ public function output_meta_description() {
+ if (is_admin() || is_feed()) {
+ return;
+ }
+
+ $meta_description = $this->get_context_meta_description();
+ if (!empty($meta_description)) {
+ echo "\n" . ' ' . "\n";
+ }
+ }
+
+
+ public function register_query_vars($vars) {
+ $vars[] = 'ansico_wp_basic_sitemap';
+ $vars[] = 'ansico_wp_basic_sitemap_name';
+ return $vars;
+ }
+
+ public function register_sitemap_rewrites() {
+ $settings = $this->get_settings();
+ if (empty($settings['enable_sitemap_module'])) {
+ return;
+ }
+ add_rewrite_rule('^ansico-sitemap\.xml$', 'index.php?ansico_wp_basic_sitemap=index', 'top');
+ add_rewrite_rule('^ansico-sitemap-([a-z0-9_-]+)-([a-z0-9_-]+)\.xml$', 'index.php?ansico_wp_basic_sitemap=$matches[1]&ansico_wp_basic_sitemap_name=$matches[2]', 'top');
+ }
+
+ public function maybe_render_sitemap() {
+ $settings = $this->get_settings();
+ if (empty($settings['enable_sitemap_module'])) {
+ return;
+ }
+ $type = get_query_var('ansico_wp_basic_sitemap');
+ if (!$type) {
+ return;
+ }
+
+ $name = get_query_var('ansico_wp_basic_sitemap_name');
+ nocache_headers();
+ header('Content-Type: application/xml; charset=' . get_bloginfo('charset'), true);
+
+ if ($type === 'index') {
+ echo $this->get_sitemap_index_xml();
+ exit;
+ }
+
+ echo $this->get_sitemap_section_xml($type, $name);
+ exit;
+ }
+
+ private function get_sitemap_index_xml() {
+ $items = [];
+ $settings = $this->get_settings();
+ if (empty($settings['enable_sitemap_module'])) {
+ return '';
+ }
+
+ foreach ($settings['enabled_post_types'] as $post_type) {
+ $object = get_post_type_object($post_type);
+ if (!$object || !$this->post_type_has_published_content($post_type)) {
+ continue;
+ }
+ $items[] = [
+ 'loc' => home_url('/ansico-sitemap-post-' . $post_type . '.xml'),
+ 'lastmod' => $this->get_latest_post_modified_gmt($post_type),
+ ];
+ }
+
+ if (!empty($settings['enable_taxonomy_fields'])) {
+ foreach ($this->get_public_taxonomies() as $taxonomy) {
+ if (!$this->taxonomy_has_terms($taxonomy->name)) {
+ continue;
+ }
+ $items[] = [
+ 'loc' => home_url('/ansico-sitemap-taxonomy-' . $taxonomy->name . '.xml'),
+ 'lastmod' => current_time('mysql', true),
+ ];
+ }
+ }
+
+ if (!empty($settings['enable_author_fields']) && $this->site_has_authors_with_posts()) {
+ $items[] = [
+ 'loc' => home_url('/ansico-sitemap-author-users.xml'),
+ 'lastmod' => current_time('mysql', true),
+ ];
+ }
+
+ $items[] = [
+ 'loc' => home_url('/ansico-sitemap-page-archives.xml'),
+ 'lastmod' => current_time('mysql', true),
+ ];
+
+ $xml = '' . "\n";
+ $xml .= '';
+ foreach ($items as $item) {
+ $xml .= '';
+ $xml .= '' . esc_url($item['loc']) . ' ';
+ if (!empty($item['lastmod'])) {
+ $xml .= '' . esc_html(mysql2date('c', $item['lastmod'], false)) . ' ';
+ }
+ $xml .= ' ';
+ }
+ $xml .= ' ';
+ return $xml;
+ }
+
+ private function get_sitemap_section_xml($type, $name) {
+ $urls = [];
+ if ($type === 'post') {
+ $urls = $this->get_post_sitemap_urls($name);
+ } elseif ($type === 'taxonomy') {
+ $urls = $this->get_taxonomy_sitemap_urls($name);
+ } elseif ($type === 'author' && $name === 'users') {
+ $urls = $this->get_author_sitemap_urls();
+ } elseif ($type === 'page' && $name === 'archives') {
+ $urls = $this->get_archive_sitemap_urls();
+ }
+
+ $xml = '' . "\n";
+ $xml .= '';
+ foreach ($urls as $url) {
+ $xml .= '';
+ $xml .= '' . esc_url($url['loc']) . ' ';
+ if (!empty($url['lastmod'])) {
+ $xml .= '' . esc_html(mysql2date('c', $url['lastmod'], false)) . ' ';
+ }
+ if (!empty($url['changefreq'])) {
+ $xml .= '' . esc_html($url['changefreq']) . ' ';
+ }
+ if (isset($url['priority'])) {
+ $xml .= '' . esc_html(number_format((float) $url['priority'], 1, '.', '')) . ' ';
+ }
+ $xml .= ' ';
+ }
+ $xml .= ' ';
+ return $xml;
+ }
+
+ private function post_type_has_published_content($post_type) {
+ global $wpdb;
+ $count = (int) $wpdb->get_var($wpdb->prepare(
+ "SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_type = %s AND post_status = 'publish'",
+ $post_type
+ ));
+ return $count > 0;
+ }
+
+ private function get_latest_post_modified_gmt($post_type) {
+ global $wpdb;
+ $lastmod = $wpdb->get_var($wpdb->prepare(
+ "SELECT post_modified_gmt FROM {$wpdb->posts} WHERE post_type = %s AND post_status = 'publish' ORDER BY post_modified_gmt DESC LIMIT 1",
+ $post_type
+ ));
+ return $lastmod ?: current_time('mysql', true);
+ }
+
+ private function taxonomy_has_terms($taxonomy) {
+ $terms = get_terms([
+ 'taxonomy' => $taxonomy,
+ 'hide_empty' => true,
+ 'number' => 1,
+ 'fields' => 'ids',
+ ]);
+ return !is_wp_error($terms) && !empty($terms);
+ }
+
+ private function site_has_authors_with_posts() {
+ $users = get_users([
+ 'has_published_posts' => array_keys($this->get_public_post_types()),
+ 'number' => 1,
+ 'fields' => 'ids',
+ ]);
+ return !empty($users);
+ }
+
+ private function get_post_sitemap_urls($post_type) {
+ $settings = $this->get_settings();
+ if (!in_array($post_type, $settings['enabled_post_types'], true)) {
+ return [];
+ }
+
+ $posts = get_posts([
+ 'post_type' => $post_type,
+ 'post_status' => 'publish',
+ 'posts_per_page' => -1,
+ 'orderby' => 'modified',
+ 'order' => 'DESC',
+ 'fields' => 'all',
+ 'no_found_rows' => true,
+ 'update_post_meta_cache' => false,
+ 'update_post_term_cache' => false,
+ ]);
+
+ $urls = [];
+ foreach ($posts as $post) {
+ $loc = get_permalink($post);
+ if (!$loc) {
+ continue;
+ }
+ $urls[] = [
+ 'loc' => $loc,
+ 'lastmod' => get_post_modified_time('Y-m-d H:i:s', true, $post),
+ 'changefreq' => 'weekly',
+ 'priority' => $post_type === 'page' ? 0.8 : 0.6,
+ ];
+ }
+ return $urls;
+ }
+
+ private function get_taxonomy_sitemap_urls($taxonomy) {
+ $settings = $this->get_settings();
+ if (empty($settings['enable_taxonomy_fields'])) {
+ return [];
+ }
+
+ $taxonomy_object = get_taxonomy($taxonomy);
+ if (!$taxonomy_object || empty($taxonomy_object->public)) {
+ return [];
+ }
+
+ $terms = get_terms([
+ 'taxonomy' => $taxonomy,
+ 'hide_empty' => true,
+ 'number' => 0,
+ ]);
+ if (is_wp_error($terms) || empty($terms)) {
+ return [];
+ }
+
+ $urls = [];
+ foreach ($terms as $term) {
+ $loc = get_term_link($term);
+ if (is_wp_error($loc)) {
+ continue;
+ }
+ $urls[] = [
+ 'loc' => $loc,
+ 'lastmod' => current_time('mysql', true),
+ 'changefreq' => 'weekly',
+ 'priority' => 0.5,
+ ];
+ }
+ return $urls;
+ }
+
+ private function get_author_sitemap_urls() {
+ $settings = $this->get_settings();
+ if (empty($settings['enable_author_fields'])) {
+ return [];
+ }
+
+ $users = get_users([
+ 'has_published_posts' => array_keys($this->get_public_post_types()),
+ 'fields' => ['ID'],
+ ]);
+
+ $urls = [];
+ foreach ($users as $user) {
+ $urls[] = [
+ 'loc' => get_author_posts_url($user->ID),
+ 'lastmod' => current_time('mysql', true),
+ 'changefreq' => 'weekly',
+ 'priority' => 0.4,
+ ];
+ }
+ return $urls;
+ }
+
+ private function get_archive_sitemap_urls() {
+ $urls = [[
+ 'loc' => home_url('/'),
+ 'lastmod' => current_time('mysql', true),
+ 'changefreq' => 'daily',
+ 'priority' => 1.0,
+ ]];
+
+ if (get_option('show_on_front') === 'page') {
+ $page_for_posts = (int) get_option('page_for_posts');
+ if ($page_for_posts) {
+ $loc = get_permalink($page_for_posts);
+ if ($loc) {
+ $urls[] = [
+ 'loc' => $loc,
+ 'lastmod' => get_post_modified_time('Y-m-d H:i:s', true, $page_for_posts),
+ 'changefreq' => 'daily',
+ 'priority' => 0.8,
+ ];
+ }
+ }
+ }
+
+ foreach ($this->get_archive_post_types() as $post_type) {
+ $loc = get_post_type_archive_link($post_type->name);
+ if ($loc) {
+ $urls[] = [
+ 'loc' => $loc,
+ 'lastmod' => $this->get_latest_post_modified_gmt($post_type->name),
+ 'changefreq' => 'daily',
+ 'priority' => 0.7,
+ ];
+ }
+ }
+
+ return $urls;
+ }
+
+ public function settings_notice_if_no_types() {
+ $screen = function_exists('get_current_screen') ? get_current_screen() : null;
+ if (!$screen || $screen->id !== 'toplevel_page_ansico-wp-basic') {
+ return;
+ }
+
+ $settings = $this->get_settings();
+ if (!empty($settings['enabled_post_types'])) {
+ return;
+ }
+
+ echo '' . esc_html__('No post types are enabled. Select at least one post type to use the SEO fields on content edit screens.', 'ansico-wp-basic') . '
';
+ }
+}
+
+new Ansico_WP_Basic();
diff --git a/ansico-wp-basic/assets/admin.css b/ansico-wp-basic/assets/admin.css
new file mode 100644
index 0000000..65edd61
--- /dev/null
+++ b/ansico-wp-basic/assets/admin.css
@@ -0,0 +1,125 @@
+.ansico-wp-basic-metabox .description {
+ display: block;
+ margin-top: 4px;
+}
+
+.ansico-wp-basic-counter {
+ display: block;
+ margin-top: 6px;
+ font-size: 12px;
+ font-weight: 600;
+}
+
+.ansico-wp-basic-counter.is-good {
+ color: #2f6f3e;
+}
+
+.ansico-wp-basic-counter.is-warning {
+ color: #b45309;
+}
+
+.ansico-wp-basic-counter.is-danger {
+ color: #b32d2e;
+}
+
+.ansico-wp-basic-snippet-wrapper {
+ margin-top: 18px;
+ border-top: 1px solid #e0e0e0;
+ padding-top: 14px;
+}
+
+.ansico-wp-basic-snippet {
+ margin-top: 12px;
+ background: #fff;
+ padding: 6px 0;
+ max-width: 652px;
+ font-family: Arial, sans-serif;
+}
+
+.ansico-wp-basic-snippet-site {
+ color: #202124;
+ font-size: 14px;
+ line-height: 1.3;
+ margin-bottom: 1px;
+}
+
+.ansico-wp-basic-snippet-url {
+ color: #4d5156;
+ font-size: 14px;
+ line-height: 1.3;
+ margin-bottom: 3px;
+ word-break: break-word;
+}
+
+.ansico-wp-basic-snippet-title {
+ color: #1a0dab;
+ font-size: 22px;
+ line-height: 1.3;
+ margin-bottom: 4px;
+ cursor: default;
+}
+
+.ansico-wp-basic-snippet-description {
+ color: #4d5156;
+ font-size: 14px;
+ line-height: 1.58;
+}
+
+.ansico-wp-basic-truncation-note {
+ margin-top: 8px;
+}
+
+.ansico-wp-basic-truncation-note.is-warning {
+ color: #b45309;
+ font-weight: 600;
+}
+
+.ansico-wp-basic-settings-page .form-table th {
+ width: 280px;
+}
+
+.ansico-wp-basic-settings-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: 16px;
+}
+
+.ansico-wp-basic-settings-card {
+ background: #fff;
+ border: 1px solid #dcdcde;
+ border-radius: 8px;
+ padding: 14px;
+}
+
+.ansico-wp-basic-settings-card h3 {
+ margin-top: 0;
+}
+
+.ansico-wp-basic-term-fields .ansico-wp-basic-metabox,
+.ansico-wp-basic-term-fields-wrap .ansico-wp-basic-metabox {
+ background: #fff;
+}
+
+
+.ansico-wp-basic-tools-box p {
+ margin: 0 0 14px;
+}
+
+.ansico-wp-basic-tools-box label strong {
+ display: inline-block;
+ margin-bottom: 6px;
+}
+
+#ansico_wp_basic_change_post_type,
+.ansico-post-type-select {
+ width: auto;
+ min-width: 0;
+ max-width: 220px;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.ansico-wp-basic-tools-box .description {
+ display: block;
+ margin-top: 6px;
+}
diff --git a/ansico-wp-basic/assets/admin.js b/ansico-wp-basic/assets/admin.js
new file mode 100644
index 0000000..33c42d8
--- /dev/null
+++ b/ansico-wp-basic/assets/admin.js
@@ -0,0 +1,160 @@
+document.addEventListener('DOMContentLoaded', function () {
+ var titleInput = document.getElementById('ansico_wp_basic_meta_title');
+ var descInput = document.getElementById('ansico_wp_basic_meta_description');
+ var snippet = document.querySelector('.ansico-wp-basic-snippet');
+
+ if (!titleInput || !descInput || !snippet) {
+ return;
+ }
+
+ var titleTarget = snippet.querySelector('.ansico-wp-basic-snippet-title');
+ var descTextTarget = snippet.querySelector('.ansico-wp-basic-snippet-description-text');
+ var urlTarget = snippet.querySelector('.ansico-wp-basic-snippet-url');
+ var truncationNote = document.querySelector('.ansico-wp-basic-truncation-note');
+ var titleCounter = document.querySelector('[data-counter-for="title"]');
+ var descCounter = document.querySelector('[data-counter-for="description"]');
+
+ var titleMaxPixels = 580;
+ var descMaxPixels = 920;
+ var titleIdealMin = 30;
+ var titleIdealMax = 60;
+ var descIdealMin = 70;
+ var descIdealMax = 155;
+
+ var titleCanvas = document.createElement('canvas');
+ var descCanvas = document.createElement('canvas');
+ var titleContext = titleCanvas.getContext('2d');
+ var descContext = descCanvas.getContext('2d');
+ titleContext.font = '400 22px Arial';
+ descContext.font = '400 14px Arial';
+
+ function measure(context, text) {
+ return Math.round(context.measureText(text || '').width);
+ }
+
+ function truncateByPixels(text, maxPixels, context) {
+ text = (text || '').trim();
+ var fullWidth = measure(context, text);
+
+ if (!text || fullWidth <= maxPixels) {
+ return {
+ visible: text,
+ isTruncated: false,
+ hiddenChars: 0,
+ hiddenText: '',
+ width: fullWidth
+ };
+ }
+
+ var ellipsis = '…';
+ var low = 0;
+ var high = text.length;
+ var best = '';
+
+ while (low <= high) {
+ var mid = Math.floor((low + high) / 2);
+ var candidate = text.slice(0, mid).replace(/[\s.,;:-]+$/u, '');
+ var candidateWithEllipsis = candidate + ellipsis;
+ if (measure(context, candidateWithEllipsis) <= maxPixels) {
+ best = candidate;
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ }
+ }
+
+ if (!best) {
+ best = text.slice(0, 1);
+ }
+
+ var hiddenText = text.slice(best.length).trim();
+ return {
+ visible: best + ellipsis,
+ isTruncated: true,
+ hiddenChars: hiddenText.length,
+ hiddenText: hiddenText,
+ width: fullWidth
+ };
+ }
+
+ function setCounter(el, current, minIdeal, maxIdeal, pxWidth, pxLimit, overflowText) {
+ if (!el) {
+ return;
+ }
+
+ el.classList.remove('is-good', 'is-warning', 'is-danger');
+
+ var statusClass = 'is-good';
+ var notes = [];
+
+ if (current === 0) {
+ statusClass = 'is-warning';
+ notes.push('empty');
+ } else if (current < minIdeal) {
+ statusClass = 'is-warning';
+ notes.push('a bit short');
+ }
+
+ if (current > maxIdeal || pxWidth > pxLimit) {
+ statusClass = (current > maxIdeal + 15 || pxWidth > pxLimit + 120) ? 'is-danger' : 'is-warning';
+ notes.push(overflowText);
+ }
+
+ el.classList.add(statusClass);
+ el.textContent = current + ' chars · ' + pxWidth + ' px';
+ if (notes.length) {
+ el.textContent += ' — ' + notes.join(' · ');
+ }
+ }
+
+ function updateSnippet() {
+ var rawTitle = titleInput.value.trim() || snippet.dataset.fallbackTitle || '';
+ var siteName = (snippet.dataset.siteName || '').trim();
+ var title = rawTitle;
+ if (siteName && rawTitle.toLowerCase().indexOf(siteName.toLowerCase()) === -1) {
+ title += ' - ' + siteName;
+ }
+
+ var desc = descInput.value.trim() || 'This is how your page may appear in Google search results when a meta description is available.';
+ var permalink;
+
+ try {
+ permalink = new URL(snippet.dataset.permalink);
+ } catch (e) {
+ permalink = null;
+ }
+
+ var host = permalink ? permalink.hostname.replace(/^www\./, '') : '';
+ var path = permalink ? permalink.pathname.replace(/^\//, '').replace(/\/$/, '') : '';
+ var prettyUrl = host;
+ if (path) {
+ prettyUrl += ' › ' + path.replace(/\//g, ' › ');
+ }
+
+ var titleResult = truncateByPixels(title, titleMaxPixels, titleContext);
+ var descResult = truncateByPixels(desc, descMaxPixels, descContext);
+ var rawTitleWidth = measure(titleContext, title);
+ var rawDescWidth = measure(descContext, desc);
+
+ titleTarget.textContent = titleResult.visible;
+ descTextTarget.textContent = descResult.visible;
+ urlTarget.textContent = prettyUrl;
+
+ setCounter(titleCounter, rawTitle.length, titleIdealMin, titleIdealMax, rawTitleWidth, titleMaxPixels, 'will truncate in preview');
+ setCounter(descCounter, desc.length, descIdealMin, descIdealMax, rawDescWidth, descMaxPixels, 'will truncate in preview');
+
+ if (truncationNote) {
+ if (descResult.isTruncated) {
+ truncationNote.textContent = 'Preview is truncating the meta description. About ' + descResult.hiddenChars + ' characters are no longer visible.';
+ truncationNote.classList.add('is-warning');
+ } else {
+ truncationNote.textContent = 'Preview currently fits within the available description space.';
+ truncationNote.classList.remove('is-warning');
+ }
+ }
+ }
+
+ titleInput.addEventListener('input', updateSnippet);
+ descInput.addEventListener('input', updateSnippet);
+ updateSnippet();
+});
diff --git a/ansico-wp-basic/readme.txt b/ansico-wp-basic/readme.txt
new file mode 100644
index 0000000..afad00a
--- /dev/null
+++ b/ansico-wp-basic/readme.txt
@@ -0,0 +1,43 @@
+=== Ansico WP Basic ===
+Contributors: ansico
+Tags: seo, meta title, meta description, search preview
+Requires at least: 6.0
+Tested up to: 6.5
+Requires PHP: 7.4
+Stable tag: 0.0.0.2
+License: GPLv3 or later
+License URI: https://www.gnu.org/licenses/gpl-3.0.html
+
+Basic SEO fields for posts, pages, custom post types, author archives, taxonomy archives, and special archive pages.
+
+== Description ==
+
+Ansico WP Basic adds simple SEO fields to WordPress:
+
+- Meta title and meta description fields for posts, pages, and public custom post types
+- Live search result preview in the editor
+- SEO fields for author archives via user profiles
+- SEO fields for taxonomy archives such as categories, tags, and public custom taxonomies
+- Settings for archive-style pages such as blog home, date archives, search results, 404, and post type archives
+- Outputs the meta description tag in the frontend head when a description is available
+- Uses the custom meta title as the document title when one is available
+
+== Installation ==
+
+1. Upload the plugin ZIP file in WordPress.
+2. Activate the plugin.
+3. Go to Ansico WP Basic > Settings.
+4. Choose which post types should have SEO fields.
+
+== Changelog ==
+
+= 1.1.0 =
+- Improved search result preview styling
+- Made meta description output always active
+- Removed title and description output toggles from settings
+- Added support for author archives
+- Added support for taxonomy term archives
+- Added settings for archive and special pages
+
+= 1.0.0 =
+- Initial release