register_sitemap_rewrites(); flush_rewrite_rules(); } public static function deactivate() { flush_rewrite_rules(); } public function __construct() { add_action('admin_menu', [$this, 'register_admin_menu']); add_action('admin_init', [$this, 'register_settings']); add_action('admin_init', [$this, 'maybe_handle_yoast_import']); add_action('admin_init', [$this, 'maybe_handle_generate_missing_meta']); add_action('add_meta_boxes', [$this, 'register_meta_boxes']); add_action('save_post', [$this, 'save_meta_box']); add_filter('wp_insert_post_data', [$this, 'maybe_switch_post_type'], 10, 2); add_action('show_user_profile', [$this, 'render_user_fields']); add_action('edit_user_profile', [$this, 'render_user_fields']); add_action('personal_options_update', [$this, 'save_user_fields']); add_action('edit_user_profile_update', [$this, 'save_user_fields']); add_action('created_term', [$this, 'save_term_fields'], 10, 3); add_action('edited_term', [$this, 'save_term_fields'], 10, 3); add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']); add_filter('pre_get_document_title', [$this, 'filter_document_title'], 20); add_action('wp', [$this, 'maybe_disable_core_canonical'], 1); add_action('wp_head', [$this, 'output_meta_description'], 1); add_action('wp_head', [$this, 'output_frontend_meta_tags'], 2); add_filter('the_title', [$this, 'maybe_hide_page_title'], 10, 2); add_filter('single_post_title', [$this, 'maybe_hide_single_post_title'], 10, 2); add_action('admin_notices', [$this, 'settings_notice_if_no_types']); add_action('admin_notices', [$this, 'render_import_notice']); add_action('init', [$this, 'register_taxonomy_hooks']); add_action('init', [$this, 'register_sitemap_rewrites']); add_action('template_redirect', [$this, 'maybe_render_sitemap']); add_filter('query_vars', [$this, 'register_query_vars']); } public function get_settings() { $defaults = [ 'enable_meta_module' => 1, 'enable_sitemap_module' => 1, 'enabled_post_types' => $this->get_default_post_types(), 'enable_author_fields' => 1, 'enable_taxonomy_fields'=> 1, 'social_defaults' => [ 'facebook_publisher_url' => '', 'x_site_handle' => '', 'default_social_image' => '', ], 'special_pages' => [ 'date' => ['title' => '', 'description' => ''], 'search'=> ['title' => '', 'description' => ''], '404' => ['title' => '', 'description' => ''], 'home' => ['title' => '', 'description' => ''], ], 'post_type_archives' => [], ]; $settings = get_option(self::OPTION_KEY, []); if (!is_array($settings)) { $settings = []; } $settings = wp_parse_args($settings, $defaults); $settings['enable_meta_module'] = empty($settings['enable_meta_module']) ? 0 : 1; $settings['enable_sitemap_module'] = empty($settings['enable_sitemap_module']) ? 0 : 1; $settings['enabled_post_types'] = array_values(array_filter(array_map('sanitize_key', (array) $settings['enabled_post_types']))); $settings['enable_author_fields'] = empty($settings['enable_author_fields']) ? 0 : 1; $settings['enable_taxonomy_fields'] = empty($settings['enable_taxonomy_fields']) ? 0 : 1; $settings['social_defaults'] = $this->sanitize_social_defaults($settings['social_defaults'] ?? []); $settings['special_pages'] = $this->sanitize_special_pages($settings['special_pages']); $settings['post_type_archives'] = $this->sanitize_post_type_archive_settings($settings['post_type_archives']); return $settings; } private function get_public_post_types() { $post_types = get_post_types([ 'public' => true, 'show_ui' => true, ], 'objects'); unset($post_types['attachment']); return $post_types; } private function get_default_post_types() { return array_keys($this->get_public_post_types()); } private function get_public_taxonomies() { return get_taxonomies([ 'public' => true, 'show_ui' => true, ], 'objects'); } private function get_archive_post_types() { $post_types = $this->get_public_post_types(); foreach ($post_types as $key => $post_type) { if (empty($post_type->has_archive)) { unset($post_types[$key]); } } return $post_types; } private function sanitize_social_defaults($social_defaults) { $social_defaults = is_array($social_defaults) ? $social_defaults : []; return [ 'facebook_publisher_url' => isset($social_defaults['facebook_publisher_url']) ? esc_url_raw($social_defaults['facebook_publisher_url']) : '', 'x_site_handle' => isset($social_defaults['x_site_handle']) ? sanitize_text_field($social_defaults['x_site_handle']) : '', 'default_social_image' => isset($social_defaults['default_social_image']) ? esc_url_raw($social_defaults['default_social_image']) : '', 'organization_name' => isset($social_defaults['organization_name']) ? sanitize_text_field($social_defaults['organization_name']) : '', 'organization_logo' => isset($social_defaults['organization_logo']) ? esc_url_raw($social_defaults['organization_logo']) : '', ]; } private function sanitize_special_pages($special_pages) { $defaults = [ 'date' => ['title' => '', 'description' => ''], 'search' => ['title' => '', 'description' => ''], '404' => ['title' => '', 'description' => ''], 'home' => ['title' => '', 'description' => ''], ]; $special_pages = is_array($special_pages) ? $special_pages : []; foreach ($defaults as $key => $value) { $page = isset($special_pages[$key]) && is_array($special_pages[$key]) ? $special_pages[$key] : []; $defaults[$key] = [ 'title' => isset($page['title']) ? sanitize_text_field($page['title']) : '', 'description' => isset($page['description']) ? sanitize_textarea_field($page['description']) : '', ]; } return $defaults; } private function sanitize_post_type_archive_settings($archives) { $clean = []; $allowed = array_keys($this->get_archive_post_types()); $archives = is_array($archives) ? $archives : []; foreach ($allowed as $post_type) { $archive = isset($archives[$post_type]) && is_array($archives[$post_type]) ? $archives[$post_type] : []; $clean[$post_type] = [ 'title' => isset($archive['title']) ? sanitize_text_field($archive['title']) : '', 'description' => isset($archive['description']) ? sanitize_textarea_field($archive['description']) : '', ]; } return $clean; } public function register_admin_menu() { add_menu_page( __('Ansico WP Basic', 'ansico-wp-basic'), __('Ansico WP Basic', 'ansico-wp-basic'), 'manage_options', 'ansico-wp-basic', [$this, 'render_settings_page'], 'dashicons-search', 81 ); add_submenu_page( 'ansico-wp-basic', __('Settings', 'ansico-wp-basic'), __('Settings', 'ansico-wp-basic'), 'manage_options', 'ansico-wp-basic', [$this, 'render_settings_page'] ); } public function register_settings() { register_setting( 'ansico_wp_basic_settings_group', self::OPTION_KEY, [$this, 'sanitize_settings'] ); add_settings_section( 'ansico_wp_basic_main_section', __('General settings', 'ansico-wp-basic'), function () { echo '

' . 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_social_section', __('Social tags and schema', 'ansico-wp-basic'), function () { echo '

' . esc_html__('Configure site-wide defaults used for Open Graph, Twitter/X cards, canonical URLs, and schema markup output.', 'ansico-wp-basic') . '

'; }, 'ansico-wp-basic' ); add_settings_field( 'social_defaults', __('Social defaults', 'ansico-wp-basic'), [$this, 'render_social_defaults_fields'], 'ansico-wp-basic', 'ansico_wp_basic_social_section' ); add_settings_section( 'ansico_wp_basic_archives_section', __('Archives and special pages', 'ansico-wp-basic'), function () { echo '

' . 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' ); add_settings_section( 'ansico_wp_basic_tools_section', __('Import tools', 'ansico-wp-basic'), function () { echo '

' . 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') . '

'; }, 'ansico-wp-basic' ); add_settings_field( 'yoast_import', __('Import from Yoast SEO', 'ansico-wp-basic'), [$this, 'render_yoast_import_field'], 'ansico-wp-basic', 'ansico_wp_basic_tools_section' ); add_settings_field( 'generate_missing_meta', __('Generate missing META fields', 'ansico-wp-basic'), [$this, 'render_generate_missing_meta_field'], 'ansico-wp-basic', 'ansico_wp_basic_tools_section' ); } public function sanitize_settings($input) { $public_post_types = array_keys($this->get_public_post_types()); $enabled_post_types = []; if (!empty($input['enabled_post_types']) && is_array($input['enabled_post_types'])) { foreach ($input['enabled_post_types'] as $post_type) { $post_type = sanitize_key($post_type); if (in_array($post_type, $public_post_types, true)) { $enabled_post_types[] = $post_type; } } } return [ 'enable_meta_module' => empty($input['enable_meta_module']) ? 0 : 1, 'enable_sitemap_module' => empty($input['enable_sitemap_module']) ? 0 : 1, 'enabled_post_types' => array_values(array_unique($enabled_post_types)), 'enable_author_fields' => empty($input['enable_author_fields']) ? 0 : 1, 'enable_taxonomy_fields' => empty($input['enable_taxonomy_fields']) ? 0 : 1, 'social_defaults' => $this->sanitize_social_defaults(isset($input['social_defaults']) ? $input['social_defaults'] : []), 'special_pages' => $this->sanitize_special_pages(isset($input['special_pages']) ? $input['special_pages'] : []), 'post_type_archives' => $this->sanitize_post_type_archive_settings(isset($input['post_type_archives']) ? $input['post_type_archives'] : []), ]; } public function render_meta_module_toggle() { $settings = $this->get_settings(); printf( '

%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(); $sitemap_url = home_url('/sitemap.xml'); printf( '

%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__('Generate a sitemap index for supported content types.', 'ansico-wp-basic') ); if (!empty($settings['enable_sitemap_module'])) { echo '
'; echo '' . esc_html__('Sitemap URL:', 'ansico-wp-basic') . ''; echo '' . esc_html($sitemap_url) . ''; echo ''; echo '
'; echo '

' . esc_html__('Use this URL when submitting your sitemap to search engines or external tools.', '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( '', 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 '
'; echo '

' . esc_html__('Only selected post types will get Ansico SEO fields and bulk generation tools.', 'ansico-wp-basic') . '

'; } public function render_author_field_toggle() { $settings = $this->get_settings(); printf( '

%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( '

%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_social_defaults_fields() { $settings = $this->get_settings(); $social = $settings['social_defaults'] ?? []; echo '
'; echo '

'; echo '

'; echo '

'; echo '

'; echo '

'; echo '

' . 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') . '

'; echo '
'; } 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( '

%1$s

', 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 '
'; } 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 '

' . esc_html__('Yoast SEO was not detected on this site, and no Yoast SEO data was found in the database.', 'ansico-wp-basic') . '

'; 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 '
'; echo '

' . 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') . '

'; echo '

' . esc_html__('Import from Yoast SEO', 'ansico-wp-basic') . '

'; echo '

' . 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') . '

'; echo '
'; } 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 '

' . esc_html__('The SEO module is currently disabled.', 'ansico-wp-basic') . '

'; 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 '
'; echo '

' . 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') . '

'; if (!empty($labels)) { echo '

' . esc_html(sprintf(__('Runs on enabled post types: %s.', 'ansico-wp-basic'), implode(', ', $labels))) . '

'; } echo '

' . esc_html__('Generate missing META fields', 'ansico-wp-basic') . '

'; echo '

' . 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') . '

'; echo '
'; } public function maybe_handle_yoast_import() { if (!is_admin() || !current_user_can('manage_options')) { return; } if (!isset($_GET['page'], $_GET['action']) || $_GET['page'] !== 'ansico-wp-basic' || $_GET['action'] !== self::IMPORT_ACTION) { return; } check_admin_referer(self::IMPORT_ACTION, 'ansico_wp_basic_import_nonce'); $result = $this->import_yoast_meta_data(); $redirect_url = add_query_arg([ 'page' => 'ansico-wp-basic', 'tab' => 'tools', 'ansico_wp_basic_notice' => rawurlencode(wp_json_encode($result)), ], admin_url('admin.php')); wp_safe_redirect($redirect_url); exit; } private function import_yoast_meta_data() { $overwrite_existing = false; $result = [ 'type' => 'success', 'message' => __('Yoast SEO data import completed.', 'ansico-wp-basic'), 'posts_imported' => 0, 'terms_imported' => 0, 'posts_skipped' => 0, 'terms_skipped' => 0, 'settings_imported' => 0, ]; $settings = $this->get_settings(); $yoast_social = get_option('wpseo_social', []); if (is_array($yoast_social)) { $site_settings_imported = 0; if (empty($settings['social_defaults']['facebook_publisher_url']) && !empty($yoast_social['facebook_site'])) { $settings['social_defaults']['facebook_publisher_url'] = esc_url_raw($yoast_social['facebook_site']); $site_settings_imported++; } if (empty($settings['social_defaults']['x_site_handle']) && !empty($yoast_social['twitter_site'])) { $settings['social_defaults']['x_site_handle'] = $this->normalize_x_handle($yoast_social['twitter_site']); $site_settings_imported++; } if (empty($settings['social_defaults']['default_social_image']) && !empty($yoast_social['og_default_image'])) { $settings['social_defaults']['default_social_image'] = esc_url_raw($yoast_social['og_default_image']); $site_settings_imported++; } if ($site_settings_imported > 0) { update_option(self::OPTION_KEY, $settings); $result['settings_imported'] = $site_settings_imported; } } $public_post_types = array_keys($this->get_public_post_types()); if (!empty($public_post_types)) { $posts = get_posts([ 'post_type' => $public_post_types, 'post_status' => 'any', 'posts_per_page' => -1, 'fields' => 'ids', 'orderby' => 'ID', 'order' => 'ASC', 'suppress_filters' => true, ]); foreach ((array) $posts as $post_id) { $yoast_title = get_post_meta($post_id, '_yoast_wpseo_title', true); $yoast_description = get_post_meta($post_id, '_yoast_wpseo_metadesc', true); $yoast_canonical = get_post_meta($post_id, '_yoast_wpseo_canonical', true); $yoast_social_image = get_post_meta($post_id, '_yoast_wpseo_opengraph-image', true); if ($yoast_social_image === '') { $yoast_social_image = get_post_meta($post_id, '_yoast_wpseo_opengraph-image-url', true); } if ($yoast_title === '' && $yoast_description === '' && $yoast_canonical === '' && $yoast_social_image === '') { continue; } $existing_title = get_post_meta($post_id, self::META_TITLE_KEY, true); $existing_description = get_post_meta($post_id, self::META_DESC_KEY, true); $existing_canonical = get_post_meta($post_id, self::CANONICAL_KEY, true); $existing_social_image = get_post_meta($post_id, self::SOCIAL_IMAGE_KEY, true); if ($yoast_title !== '' && ($overwrite_existing || $existing_title === '')) { update_post_meta($post_id, self::META_TITLE_KEY, sanitize_text_field($yoast_title)); $result['posts_imported']++; } elseif ($yoast_title !== '') { $result['posts_skipped']++; } if ($yoast_description !== '' && ($overwrite_existing || $existing_description === '')) { update_post_meta($post_id, self::META_DESC_KEY, sanitize_textarea_field($yoast_description)); $result['posts_imported']++; } elseif ($yoast_description !== '') { $result['posts_skipped']++; } if ($yoast_canonical !== '' && ($overwrite_existing || $existing_canonical === '')) { update_post_meta($post_id, self::CANONICAL_KEY, esc_url_raw($yoast_canonical)); $result['posts_imported']++; } elseif ($yoast_canonical !== '') { $result['posts_skipped']++; } if ($yoast_social_image !== '' && ($overwrite_existing || $existing_social_image === '')) { update_post_meta($post_id, self::SOCIAL_IMAGE_KEY, esc_url_raw($yoast_social_image)); $result['posts_imported']++; } elseif ($yoast_social_image !== '') { $result['posts_skipped']++; } } } $yoast_taxonomy_meta = get_option('wpseo_taxonomy_meta', []); if (is_array($yoast_taxonomy_meta) && !empty($yoast_taxonomy_meta)) { $public_taxonomies = $this->get_public_taxonomies(); foreach ($public_taxonomies as $taxonomy => $taxonomy_obj) { if (empty($yoast_taxonomy_meta[$taxonomy]) || !is_array($yoast_taxonomy_meta[$taxonomy])) { continue; } foreach ($yoast_taxonomy_meta[$taxonomy] as $term_id => $term_meta) { $term_id = (int) $term_id; if ($term_id <= 0 || !is_array($term_meta)) { continue; } $term = get_term($term_id, $taxonomy); if (!$term || is_wp_error($term)) { continue; } $yoast_title = isset($term_meta['wpseo_title']) ? (string) $term_meta['wpseo_title'] : ''; $yoast_description = isset($term_meta['wpseo_desc']) ? (string) $term_meta['wpseo_desc'] : ''; $yoast_canonical = isset($term_meta['wpseo_canonical']) ? (string) $term_meta['wpseo_canonical'] : ''; $yoast_social_image = isset($term_meta['wpseo_opengraph-image']) ? (string) $term_meta['wpseo_opengraph-image'] : ''; if ($yoast_social_image === '' && isset($term_meta['wpseo_opengraph-image-url'])) { $yoast_social_image = (string) $term_meta['wpseo_opengraph-image-url']; } if ($yoast_title === '' && $yoast_description === '' && $yoast_canonical === '' && $yoast_social_image === '') { continue; } $existing_title = get_term_meta($term_id, self::META_TITLE_KEY, true); $existing_description = get_term_meta($term_id, self::META_DESC_KEY, true); $existing_canonical = get_term_meta($term_id, self::CANONICAL_KEY, true); $existing_social_image = get_term_meta($term_id, self::SOCIAL_IMAGE_KEY, true); if ($yoast_title !== '' && ($overwrite_existing || $existing_title === '')) { update_term_meta($term_id, self::META_TITLE_KEY, sanitize_text_field($yoast_title)); $result['terms_imported']++; } elseif ($yoast_title !== '') { $result['terms_skipped']++; } if ($yoast_description !== '' && ($overwrite_existing || $existing_description === '')) { update_term_meta($term_id, self::META_DESC_KEY, sanitize_textarea_field($yoast_description)); $result['terms_imported']++; } elseif ($yoast_description !== '') { $result['terms_skipped']++; } if ($yoast_canonical !== '' && ($overwrite_existing || $existing_canonical === '')) { update_term_meta($term_id, self::CANONICAL_KEY, esc_url_raw($yoast_canonical)); $result['terms_imported']++; } elseif ($yoast_canonical !== '') { $result['terms_skipped']++; } if ($yoast_social_image !== '' && ($overwrite_existing || $existing_social_image === '')) { update_term_meta($term_id, self::SOCIAL_IMAGE_KEY, esc_url_raw($yoast_social_image)); $result['terms_imported']++; } elseif ($yoast_social_image !== '') { $result['terms_skipped']++; } } } } if ($result['posts_imported'] === 0 && $result['terms_imported'] === 0 && $result['settings_imported'] === 0) { $result['type'] = 'warning'; $result['message'] = __('No Yoast SEO values were imported.', 'ansico-wp-basic'); } return $result; } public function maybe_handle_generate_missing_meta() { if (!is_admin() || !current_user_can('manage_options')) { return; } if (!isset($_GET['page'], $_GET['action']) || $_GET['page'] !== 'ansico-wp-basic' || $_GET['action'] !== self::GENERATE_ACTION) { return; } check_admin_referer(self::GENERATE_ACTION, 'ansico_wp_basic_generate_nonce'); $result = $this->generate_missing_meta_data(); $redirect_url = add_query_arg([ 'page' => 'ansico-wp-basic', 'tab' => 'tools', 'ansico_wp_basic_notice' => rawurlencode(wp_json_encode($result)), ], admin_url('admin.php')); wp_safe_redirect($redirect_url); exit; } private function generate_missing_meta_data() { $settings = $this->get_settings(); $post_types = !empty($settings['enabled_post_types']) ? array_values(array_filter(array_map('sanitize_key', (array) $settings['enabled_post_types']))) : []; $result = [ 'type' => 'success', 'message' => __('Missing META values were generated.', 'ansico-wp-basic'), 'posts_imported' => 0, 'terms_imported' => 0, 'posts_skipped' => 0, 'terms_skipped' => 0, 'settings_imported' => 0, ]; if (empty($settings['enable_meta_module']) || empty($post_types)) { $result['type'] = 'warning'; $result['message'] = __('No enabled post types were available for META generation.', 'ansico-wp-basic'); return $result; } $posts = get_posts([ 'post_type' => $post_types, 'post_status' => 'any', 'posts_per_page' => -1, 'fields' => 'ids', 'orderby' => 'ID', 'order' => 'ASC', 'suppress_filters' => true, ]); foreach ((array) $posts as $post_id) { $post = get_post($post_id); if (!$post || empty($post->post_type)) { continue; } $existing_title = get_post_meta($post_id, self::META_TITLE_KEY, true); $existing_description = get_post_meta($post_id, self::META_DESC_KEY, true); $updated = false; if ($existing_title === '') { $generated_title = sanitize_text_field(get_the_title($post)); if ($generated_title !== '') { update_post_meta($post_id, self::META_TITLE_KEY, $generated_title); $result['posts_imported']++; $updated = true; } } if ($existing_description === '') { $generated_description = $this->get_default_meta_description_for_post($post); if ($generated_description !== '') { update_post_meta($post_id, self::META_DESC_KEY, sanitize_textarea_field($generated_description)); $result['posts_imported']++; $updated = true; } } if (!$updated) { $result['posts_skipped']++; } } if ($result['posts_imported'] === 0) { $result['type'] = 'warning'; $result['message'] = __('No missing META values needed to be generated.', 'ansico-wp-basic'); } return $result; } public function render_post_type_archive_fields() { $settings = $this->get_settings(); if (empty($settings['enable_meta_module'])) { echo '

' . 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( '

%1$s (%2$s)

', 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_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 '

'; 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 '

'; } private function get_current_settings_tab() { $allowed_tabs = ['general', 'archives', 'tools']; $tab = isset($_GET['tab']) ? sanitize_key(wp_unslash($_GET['tab'])) : 'general'; return in_array($tab, $allowed_tabs, true) ? $tab : 'general'; } public function render_settings_page() { if (!current_user_can('manage_options')) { return; } $settings = $this->get_settings(); $current_tab = $this->get_current_settings_tab(); $base_url = admin_url('admin.php?page=ansico-wp-basic'); $tabs = [ 'general' => __('General settings', 'ansico-wp-basic'), 'archives' => __('Archives and special pages', 'ansico-wp-basic'), 'tools' => __('Tools', 'ansico-wp-basic'), ]; ?>

render_yoast_import_field(); ?>

render_generate_missing_meta_field(); ?>

render_meta_module_toggle(); ?>

render_sitemap_module_toggle(); ?>

render_post_types_field(); ?>

render_author_field_toggle(); ?>

render_taxonomy_field_toggle(); ?>

render_social_defaults_fields(); ?>

render_special_pages_fields(); ?>

render_post_type_archive_fields(); ?>
'button button-primary button-large']); ?>
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 '
'; if ($post_type === 'page') { echo '

'; } echo '

'; echo ''; 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); $saved_canonical = get_post_meta($post->ID, self::CANONICAL_KEY, true); $saved_social_image = get_post_meta($post->ID, self::SOCIAL_IMAGE_KEY, true); $saved_x_creator = get_post_meta($post->ID, self::X_CREATOR_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, 'canonical' => $saved_canonical, 'social_image' => $saved_social_image, 'x_creator' => $saved_x_creator, ]); } 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'])) : ''; $canonical = isset($_POST['ansico_wp_basic_canonical']) ? esc_url_raw(wp_unslash($_POST['ansico_wp_basic_canonical'])) : ''; $social_image = isset($_POST['ansico_wp_basic_social_image']) ? esc_url_raw(wp_unslash($_POST['ansico_wp_basic_social_image'])) : ''; $x_creator = isset($_POST['ansico_wp_basic_x_creator']) ? sanitize_text_field(wp_unslash($_POST['ansico_wp_basic_x_creator'])) : ''; 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); } if ($canonical === '') { delete_post_meta($post_id, self::CANONICAL_KEY); } else { update_post_meta($post_id, self::CANONICAL_KEY, $canonical); } if ($social_image === '') { delete_post_meta($post_id, self::SOCIAL_IMAGE_KEY); } else { update_post_meta($post_id, self::SOCIAL_IMAGE_KEY, $social_image); } if ($x_creator === '') { delete_post_meta($post_id, self::X_CREATOR_KEY); } else { update_post_meta($post_id, self::X_CREATOR_KEY, $this->normalize_x_handle($x_creator)); } } 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, 'canonical' => '', 'social_image' => '', 'x_creator' => '', ]); ?>
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); $canonical = get_term_meta($term->term_id, self::CANONICAL_KEY, true); $social_image = get_term_meta($term->term_id, self::SOCIAL_IMAGE_KEY, true); $x_creator = get_term_meta($term->term_id, self::X_CREATOR_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('/')), 'canonical' => $canonical, 'social_image' => $social_image, 'x_creator' => $x_creator, ]); ?> 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'])) : ''; $canonical = isset($_POST['ansico_wp_basic_canonical']) ? esc_url_raw(wp_unslash($_POST['ansico_wp_basic_canonical'])) : ''; $social_image = isset($_POST['ansico_wp_basic_social_image']) ? esc_url_raw(wp_unslash($_POST['ansico_wp_basic_social_image'])) : ''; $x_creator = isset($_POST['ansico_wp_basic_x_creator']) ? sanitize_text_field(wp_unslash($_POST['ansico_wp_basic_x_creator'])) : ''; 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); } if ($canonical === '') { delete_term_meta($term_id, self::CANONICAL_KEY); } else { update_term_meta($term_id, self::CANONICAL_KEY, $canonical); } if ($social_image === '') { delete_term_meta($term_id, self::SOCIAL_IMAGE_KEY); } else { update_term_meta($term_id, self::SOCIAL_IMAGE_KEY, $social_image); } if ($x_creator === '') { delete_term_meta($term_id, self::X_CREATOR_KEY); } else { update_term_meta($term_id, self::X_CREATOR_KEY, $this->normalize_x_handle($x_creator)); } } 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.1.4' ); wp_enqueue_script( 'ansico-wp-basic-admin', plugin_dir_url(__FILE__) . 'assets/admin.js', [], '0.0.1.4', 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 maybe_disable_core_canonical() { $settings = $this->get_settings(); if (!empty($settings['enable_meta_module'])) { remove_action('wp_head', 'rel_canonical'); } } private function get_context_url() { if (is_singular()) { $post_id = get_queried_object_id(); if ($post_id) { return get_permalink($post_id) ?: home_url('/'); } } if (is_author()) { $author = get_queried_object(); if ($author instanceof WP_User || (is_object($author) && isset($author->ID))) { return get_author_posts_url((int) $author->ID); } } if ((is_category() || is_tag() || is_tax()) && ($term = get_queried_object()) && (isset($term->term_id))) { $link = get_term_link($term); return !is_wp_error($link) ? $link : home_url('/'); } if (is_post_type_archive()) { $post_type = get_query_var('post_type'); $post_type = is_array($post_type) ? reset($post_type) : $post_type; return $post_type ? get_post_type_archive_link($post_type) : home_url('/'); } if (is_home()) { $page_for_posts = (int) get_option('page_for_posts'); if ($page_for_posts) { return get_permalink($page_for_posts) ?: home_url('/'); } } global $wp; if (isset($wp->request)) { return home_url(add_query_arg([], $wp->request)); } return home_url('/'); } private function get_canonical_url() { if (is_singular()) { $post_id = get_queried_object_id(); if ($post_id) { $override = get_post_meta($post_id, self::CANONICAL_KEY, true); if (!empty($override)) { return esc_url_raw($override); } } $canonical = wp_get_canonical_url(); if (!empty($canonical)) { return $canonical; } } return $this->get_context_url(); } private function get_og_locale() { $locale = get_locale(); return str_replace('-', '_', (string) $locale); } private function get_og_type() { if (is_singular() && !is_page()) { return 'article'; } return 'website'; } private function normalize_x_handle($handle) { $handle = trim((string) $handle); if ($handle === '') { return ''; } return strpos($handle, '@') === 0 ? $handle : '@' . ltrim($handle, '@'); } private function get_author_name_for_context() { if (is_singular()) { $post = get_queried_object(); if ($post instanceof WP_Post) { return get_the_author_meta('display_name', (int) $post->post_author); } } return ''; } private function get_author_url_for_context() { if (is_singular()) { $post = get_queried_object(); if ($post instanceof WP_Post) { return get_author_posts_url((int) $post->post_author); } } return ''; } private function get_social_image_data() { $settings = $this->get_settings(); if (is_singular()) { $post_id = get_queried_object_id(); if ($post_id) { $override_url = get_post_meta($post_id, self::SOCIAL_IMAGE_KEY, true); if (!empty($override_url)) { return [ 'url' => esc_url_raw($override_url), 'width' => 0, 'height' => 0, 'type' => '', ]; } } $thumb_id = $post_id ? get_post_thumbnail_id($post_id) : 0; if ($thumb_id) { $src = wp_get_attachment_image_src($thumb_id, 'full'); if ($src) { $file = get_attached_file($thumb_id); $mime = $file && function_exists('wp_check_filetype') ? wp_check_filetype($file) : ['type' => '']; return [ 'url' => $src[0], 'width' => (int) $src[1], 'height' => (int) $src[2], 'type' => !empty($mime['type']) ? $mime['type'] : '', ]; } } } if (is_tax() || is_category() || is_tag()) { $term = get_queried_object(); if ($term instanceof WP_Term) { $override_url = get_term_meta($term->term_id, self::SOCIAL_IMAGE_KEY, true); if (!empty($override_url)) { return [ 'url' => esc_url_raw($override_url), 'width' => 0, 'height' => 0, 'type' => '', ]; } } } if (!empty($settings['social_defaults']['default_social_image'])) { return [ 'url' => esc_url_raw($settings['social_defaults']['default_social_image']), 'width' => 0, 'height' => 0, 'type' => '', ]; } $icon_id = (int) get_option('site_icon'); if ($icon_id) { $src = wp_get_attachment_image_src($icon_id, 'full'); if ($src) { $file = get_attached_file($icon_id); $mime = $file && function_exists('wp_check_filetype') ? wp_check_filetype($file) : ['type' => '']; return [ 'url' => $src[0], 'width' => (int) $src[1], 'height' => (int) $src[2], 'type' => !empty($mime['type']) ? $mime['type'] : '', ]; } } return []; } private function get_reading_time_label($post) { if (!($post instanceof WP_Post)) { return ''; } $word_count = str_word_count(wp_strip_all_tags((string) $post->post_content)); if ($word_count <= 0) { return ''; } $minutes = max(1, (int) ceil($word_count / 200)); return sprintf(_n('%d minute', '%d minutes', $minutes, 'ansico-wp-basic'), $minutes); } private function get_x_creator_for_context($fallback = '') { if (is_singular()) { $post_id = get_queried_object_id(); if ($post_id) { $override = get_post_meta($post_id, self::X_CREATOR_KEY, true); if (!empty($override)) { return $this->normalize_x_handle($override); } } } if (is_tax() || is_category() || is_tag()) { $term = get_queried_object(); if ($term instanceof WP_Term) { $override = get_term_meta($term->term_id, self::X_CREATOR_KEY, true); if (!empty($override)) { return $this->normalize_x_handle($override); } } } return $this->normalize_x_handle($fallback); } public function output_frontend_meta_tags() { if (is_admin() || is_feed()) { return; } $settings = $this->get_settings(); if (empty($settings['enable_meta_module'])) { return; } $canonical = $this->get_canonical_url(); $title = $this->get_context_meta_title(); if ($title === '') { $title = wp_get_document_title(); } else { $title = $this->build_document_title_from_meta($title); } $description = $this->get_context_meta_description(); $url = $this->get_context_url(); $site_name = get_bloginfo('name'); $og_locale = $this->get_og_locale(); $og_type = $this->get_og_type(); $social = $settings['social_defaults'] ?? []; $facebook_publisher = !empty($social['facebook_publisher_url']) ? esc_url($social['facebook_publisher_url']) : ''; $x_site_handle = $this->normalize_x_handle($social['x_site_handle'] ?? ''); $x_creator_handle = $this->get_x_creator_for_context($x_site_handle); $author_name = $this->get_author_name_for_context(); $author_url = $this->get_author_url_for_context(); $image = $this->get_social_image_data(); $twitter_card = !empty($image['url']) ? 'summary_large_image' : 'summary'; echo " " . '' . " "; echo '' . " "; echo '' . " "; echo '' . " "; if ($description !== '') { echo '' . " "; } echo '' . " "; echo '' . " "; if ($facebook_publisher !== '') { echo '' . " "; } if ($author_url !== '') { echo '' . " "; } if (is_singular()) { $post = get_queried_object(); if ($post instanceof WP_Post) { if (!empty($post->post_date_gmt)) { echo '' . " "; } if (!empty($post->post_modified_gmt)) { echo '' . " "; } } } if (!empty($image['url'])) { echo '' . " "; if (!empty($image['width'])) { echo '' . " "; } if (!empty($image['height'])) { echo '' . " "; } if (!empty($image['type'])) { echo '' . " "; } } if ($author_name !== '') { echo '' . " "; } echo '' . " "; if ($x_creator_handle !== '') { echo '' . " "; } if ($x_site_handle !== '') { echo '' . " "; } if ($author_name !== '') { echo '' . " "; echo '' . " "; } if (is_singular()) { $post = get_queried_object(); $reading_time = $this->get_reading_time_label($post); if ($reading_time !== '') { echo '' . " "; echo '' . " "; } } $organization_name = !empty($social['organization_name']) ? $social['organization_name'] : $site_name; $organization_logo = !empty($social['organization_logo']) ? esc_url_raw($social['organization_logo']) : ''; $schema_graph = [ [ '@type' => 'WebSite', '@id' => trailingslashit(home_url('/')) . '#website', 'url' => home_url('/'), 'name' => $site_name, 'publisher' => ['@id' => trailingslashit(home_url('/')) . '#organization'], 'inLanguage' => str_replace('_', '-', $og_locale), ], [ '@type' => 'Organization', '@id' => trailingslashit(home_url('/')) . '#organization', 'name' => $organization_name, 'url' => home_url('/'), 'sameAs' => array_values(array_filter([$facebook_publisher])), 'logo' => $organization_logo !== '' ? [ '@type' => 'ImageObject', 'url' => $organization_logo, ] : null, ], [ '@type' => is_singular() && !is_page() ? 'Article' : 'WebPage', '@id' => trailingslashit($url) . '#webpage', 'url' => $url, 'name' => wp_strip_all_tags($title), 'isPartOf' => ['@id' => trailingslashit(home_url('/')) . '#website'], 'description' => $description, 'inLanguage' => str_replace('_', '-', $og_locale), ], ]; if ($author_name !== '') { $schema_graph[2]['author'] = [ '@type' => 'Person', 'name' => $author_name, 'url' => $author_url, ]; } if (!empty($image['url'])) { $schema_graph[] = [ '@type' => 'ImageObject', '@id' => trailingslashit($url) . '#primaryimage', 'url' => $image['url'], 'contentUrl' => $image['url'], 'width' => !empty($image['width']) ? (int) $image['width'] : null, 'height' => !empty($image['height']) ? (int) $image['height'] : null, ]; $schema_graph[2]['image'] = ['@id' => trailingslashit($url) . '#primaryimage']; } if (is_singular()) { $post = get_queried_object(); if ($post instanceof WP_Post) { $schema_graph[1]['datePublished'] = get_post_time('c', true, $post); $schema_graph[1]['dateModified'] = get_post_modified_time('c', true, $post); } } $schema_graph = array_map(function($item) { return array_filter($item, function($value) { return !($value === '' || $value === null || $value === []); }); }, $schema_graph); echo '' . " "; } 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();