Plugin Directory

Changeset 3423502


Ignore:
Timestamp:
12/19/2025 09:24:14 AM (6 weeks ago)
Author:
andremoura
Message:

Update to version 1.2 from GitHub

Location:
easy-xml-sitemap
Files:
3 deleted
14 edited
1 copied

Legend:

Unmodified
Added
Removed
  • easy-xml-sitemap/tags/1.2/changelog.md

    r3422280 r3423502  
    99
    1010### Planned Features
    11 - Custom post type support
    12 - Sitemap index file generation
     11- Custom post type support with UI controls
    1312- Image sitemap support
    1413- Video sitemap support
     
    1716- REST API endpoints for programmatic access
    1817- Sitemap statistics and analytics
    19 - Integration with popular SEO plugins
     18- Integration with popular SEO plugins (Yoast, Rank Math)
     19- Automatic ping to search engines on content update
     20
     21---
     22
     23## [1.2.0] - 2024-12-19
     24
     25### Added
     26- **Posts Organization Options**: Choose how to organize posts in sitemaps
     27  - Single sitemap (all posts in one file)
     28  - Organize by date (one sitemap per month/year)
     29  - Organize by category (one sitemap per category)
     30- **Dynamic Posts Index**: Automatically generates index based on organization method
     31- **Posts by Date Sitemaps**: `/easy-sitemap/posts-YYYY-MM.xml` for date-based organization
     32- **Posts by Category Sitemaps**: `/easy-sitemap/posts-{category-slug}.xml` for category-based organization
     33- **Radio Button UI**: New settings interface for posts organization selection
     34- **Automatic Cache Regeneration**: Cache clears automatically when organization settings change
     35
     36### Changed
     37- **Posts Sitemap Endpoint**: Now serves as an index when date/category organization is enabled
     38- **Admin Settings Page**: Enhanced UI with organization options and better help text
     39- **Cache Invalidation**: Improved logic to handle dynamic sitemap types
     40- **Rewrite Rules**: Added support for dynamic URL patterns (date and category slugs)
     41
     42### Fixed
     43- **Critical**: Fixed fatal error in `class-sitemap-controller.php` caused by malformed rewrite rules
     44- **Critical**: Resolved duplicate function declarations and incomplete code blocks
     45- **Performance**: Optimized database queries for date-based and category-based sitemaps
     46- **Cache Keys**: Fixed cache key generation for dynamic sitemap types
     47
     48### Technical Details
     49- Added validation for year/month parameters in date-based sitemaps
     50- Added category existence validation for category-based sitemaps
     51- Enhanced `XML_Renderer` with new methods:
     52  - `generate_posts_by_date($year, $month)`
     53  - `generate_posts_by_category($cat_slug)`
     54- Updated cache clearing to handle dynamic cache keys with wildcards
     55- Improved SQL query performance with direct database cleanup for dynamic caches
     56
     57---
     58
     59## [1.1.3] - 2024-12-15
     60
     61### Added
     62- Plugin icons for WordPress.org directory
     63- Enhanced visual branding
     64
     65---
     66
     67## [1.1.0] - 2024-12-05
     68
     69### Added
     70- **Sitemap Index**: Main sitemap index file at `/easy-sitemap/sitemap.xml`
     71- **robots.txt Integration**: Automatic sitemap URL addition to virtual robots.txt
     72- **Sitemap Index Priority**: Index now appears first in the admin URLs table with visual highlighting
     73- **Physical robots.txt Detection**: Warning in admin when physical robots.txt file exists
     74- **robots.txt Viewer**: Direct link to view current robots.txt from settings page
     75- **Enhanced Admin Interface**: Improved settings page with better organization and help text
     76
     77### Changed
     78- **Sitemap Base Path**: Changed from `/easy-xml-sitemap/` to `/easy-sitemap/` for cleaner URLs
     79- **Settings Menu Name**: Changed from "XML Sitemap" to "Easy Sitemap"
     80- **Main Sitemap URL**: Now `/easy-sitemap/sitemap.xml` (sitemap index)
     81- **Individual Sitemaps**: All other sitemaps listed in the index
     82- **Admin Table Layout**: Sitemap index highlighted with blue background
     83- **robots.txt Implementation**: Uses WordPress filter instead of manual file manipulation
     84
     85### Updated
     86- All rewrite rules updated for new base path
     87- Cache system updated to handle sitemap index
     88- Documentation updated with new URLs and features
     89- Install.md with comprehensive robots.txt troubleshooting
     90
     91### Fixed
     92- robots.txt integration now uses proper WordPress filters
     93- Duplicate sitemap entries prevention in robots.txt
     94- Permalink flushing on activation and settings changes
    2095
    2196---
     
    46121  - Manual regeneration button
    47122- Clean, SEO-friendly URLs
    48   - Pattern: `/easy-xml-sitemap/{type}.xml`
     123  - Pattern: `/easy-sitemap/{type}.xml`
    49124  - Custom rewrite rules
    50125  - Proper XML content-type headers
     
    76151  - Comprehensive `readme.txt` for WordPress.org
    77152  - Detailed `Install.md` with installation and troubleshooting
    78   - This `CHANGELOG.md` file
     153  - `CHANGELOG.md` file
    79154  - `CONTRIBUTING.md` guidelines
    80155- Proper plugin lifecycle
     
    89164  - Pretty permalinks recommended
    90165- **File Structure**:
    91   - Main plugin file: `easy-xml-sitemap.php` (< 200 lines)
     166  - Main plugin file: `easy-xml-sitemap.php`
    92167  - Core classes in `inc/` directory
    93168  - Modular architecture for easy maintenance
     
    121196### Known Limitations
    122197- No custom post type support (planned for future release)
    123 - No sitemap index file (uses individual sitemaps)
    124 - No image or video sitemap support
    125 - No automatic ping to search engines
    126 - Post priority is static (not calculated based on factors)
     198- No image or video sitemap support (planned)
     199- No automatic ping to search engines (planned)
    127200
    128201---
     
    148221## Migration Guide
    149222
    150 ### From Version X.X to 1.0
    151 
    152 Not applicable for initial release.
    153 
    154 ### Future Upgrades
    155 
    156 When upgrading to future versions:
    157 1. Backup your database before upgrading
    158 2. Check the changelog for breaking changes
    159 3. Test on a staging site first
    160 4. Clear all caches after upgrade
    161 5. Regenerate sitemaps from Settings > XML Sitemap
    162 6. Resubmit sitemaps to search engines if structure changes
     223### From Version 1.1.x to 1.2.0
     224
     2251. **Backup your database** before upgrading
     2262. The plugin will automatically migrate to the new structure
     2273. **Default behavior**: Posts organization defaults to "single" (all posts in one file)
     2284. If you have a large site (10,000+ posts), consider:
     229   - Changing to "date" organization for better performance
     230   - Changing to "category" organization if content is well-categorized
     2315. **Clear all caches** after upgrade:
     232   - Plugin cache (automatic)
     233   - WordPress object cache
     234   - Page cache (WP Super Cache, W3 Total Cache, etc.)
     235   - CDN cache (Cloudflare, etc.)
     2366. **Flush permalinks**: Go to Settings > Permalinks and click "Save Changes"
     2377. **Regenerate sitemaps**: Go to Settings > Easy Sitemap and click "Regenerate All Sitemaps"
     2388. **Resubmit to search engines**: Update your sitemap in Google Search Console and Bing Webmaster Tools
     2399. The main sitemap URL remains the same: `/easy-sitemap/sitemap.xml`
     240
     241### From Version 1.0.x to 1.1.0
     242
     2431. **Main sitemap URL changed**: Update from `/easy-xml-sitemap/sitemap-index.xml` to `/easy-sitemap/sitemap.xml`
     2442. **Resubmit to search engines**: Update sitemap URL in Google Search Console and Bing
     2453. All individual sitemap URLs changed from `/easy-xml-sitemap/` to `/easy-sitemap/`
     2464. robots.txt integration now automatic (if no physical robots.txt exists)
     2475. Check Settings > Easy Sitemap for robots.txt status
    163248
    164249---
     
    168253### Reporting Issues
    169254If you encounter bugs or have feature requests:
    170 1. Check existing issues on the plugin repository
    171 2. Search the WordPress.org support forum
     2551. Check existing issues on the [GitHub repository](https://github.com/andremoura/easy-xml-sitemap/issues)
     2562. Search the [WordPress.org support forum](https://wordpress.org/support/plugin/easy-xml-sitemap/)
    1722573. Create a new issue with detailed information:
    173258   - Plugin version
     
    1832682. Describe the use case and benefit
    1842693. Provide examples or mockups if applicable
     2704. Submit via GitHub Issues or WordPress.org support forum
    185271
    186272---
  • easy-xml-sitemap/tags/1.2/easy-xml-sitemap.php

    r3422350 r3423502  
    33 * Plugin Name: Easy XML Sitemap
    44 * Description: Lightweight, modular XML sitemap generator for posts, pages, taxonomies, and Google News.
    5  * Version: 1.1.3
     5 * Version: 1.2.0
    66 * Author: André Moura
    77 * Author URI: https://www.andremoura.com
     
    3838     * @var string
    3939     */
    40     const VERSION = '1.1.0';
     40    const VERSION = '1.2.0';
    4141
    4242    /**
     
    109109     */
    110110    private function init_hooks() {
     111        // Disable WordPress native sitemap
     112        add_filter( 'wp_sitemaps_enabled', '__return_false', 9999 );
     113       
     114        // Redirect native WP sitemap to our plugin
     115        add_action( 'template_redirect', array( $this, 'redirect_native_sitemap' ), 0 );
     116       
    111117        // Initialize core components
    112118        add_action( 'init', array( $this, 'init_components' ) );
     
    116122        register_deactivation_hook( EASY_XML_SITEMAP_FILE, array( $this, 'deactivate' ) );
    117123       
    118         // Add sitemap to robots.txt if enabled
    119         add_action( 'do_robots', array( $this, 'add_robots_sitemap' ), 0 );
     124        // Add sitemap to robots.txt if enabled (using filter for proper formatting)
     125        add_filter( 'robots_txt', array( $this, 'add_robots_sitemap' ), 10, 2 );
    120126    }
    121127
     
    130136
    131137    /**
     138     * Redirect native WordPress sitemap to our plugin sitemap
     139     */
     140    public function redirect_native_sitemap() {
     141        if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
     142            return;
     143        }
     144       
     145        $request_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );
     146       
     147        // Redirect /wp-sitemap.xml to our sitemap
     148        if ( false !== strpos( $request_uri, 'wp-sitemap.xml' ) ) {
     149            wp_redirect( home_url( '/easy-sitemap/sitemap.xml' ), 301 );
     150            exit;
     151        }
     152    }
     153
     154    /**
    132155     * Plugin activation
    133156     */
     
    139162        flush_rewrite_rules();
    140163       
    141         // Optionally, initialize default settings
     164        // Initialize default settings
    142165        $defaults = array(
    143             'enable_posts'      => true,
    144             'enable_pages'      => true,
    145             'enable_categories' => true,
    146             'enable_tags'       => true,
    147             'enable_news'       => false,
    148             'add_to_robots'     => true,
    149             'cache_duration'    => 3600,
     166            'enable_posts'        => true,
     167            'posts_organization'  => 'single',
     168            'enable_pages'        => true,
     169            'enable_categories'   => true,
     170            'enable_tags'         => true,
     171            'enable_news'         => false,
     172            'enable_general'      => true,
     173            'add_to_robots'       => true,
     174            'cache_duration'      => 3600,
    150175        );
    151176
     
    171196
    172197    /**
    173      * Add sitemap index to robots.txt
    174      */
    175     public function add_robots_sitemap() {
     198     * Add sitemap to robots.txt (via filter for proper formatting)
     199     *
     200     * @param string $output The robots.txt output
     201     * @param bool   $public Whether the site is public
     202     * @return string Modified robots.txt output
     203     */
     204    public function add_robots_sitemap( $output, $public ) {
    176205        $settings = get_option( 'easy_xml_sitemap_settings', array() );
    177206       
    178207        // Check if option is enabled
    179208        if ( empty( $settings['add_to_robots'] ) ) {
    180             return;
    181         }
    182        
    183         // Get sitemap index URL
    184         $sitemap_url = Sitemap_Controller::get_sitemap_url( 'sitemap-index' );
    185        
    186         echo "Sitemap: " . esc_url( $sitemap_url ) . "\n";
     209            return $output;
     210        }
     211       
     212        // Get sitemap URL
     213        $sitemap_url = home_url( '/easy-sitemap/sitemap.xml' );
     214       
     215        // Check if sitemap line already exists to avoid duplication
     216        if ( false !== strpos( $output, $sitemap_url ) ) {
     217            return $output;
     218        }
     219       
     220        // Add sitemap at the end with proper formatting
     221        $output .= "\nSitemap: " . esc_url( $sitemap_url ) . "\n";
     222       
     223        return $output;
    187224    }
    188225}
  • easy-xml-sitemap/tags/1.2/inc/class-admin-settings.php

    r3422280 r3423502  
    7272    public function add_settings_page() {
    7373        add_options_page(
    74             __( 'Easy XML Sitemap', 'easy-xml-sitemap' ),
    75             __( 'Easy XML Sitemap', 'easy-xml-sitemap' ),
     74            __( 'Easy Sitemap', 'easy-xml-sitemap' ),
     75            __( 'Easy Sitemap', 'easy-xml-sitemap' ),
    7676            'manage_options',
    7777            self::PAGE_SLUG,
     
    9292        add_settings_section(
    9393            'easy_xml_sitemap_general_section',
    94             __( 'General Settings', 'easy-xml-sitemap' ),
     94            __( 'Sitemap Configuration', 'easy-xml-sitemap' ),
    9595            array( $this, 'render_general_section' ),
    9696            self::PAGE_SLUG
     
    110110
    111111        add_settings_field(
     112            'posts_organization',
     113            __( 'Posts Organization', 'easy-xml-sitemap' ),
     114            array( $this, 'render_radio_field' ),
     115            self::PAGE_SLUG,
     116            'easy_xml_sitemap_general_section',
     117            array(
     118                'label_for'   => 'posts_organization',
     119                'options'     => array(
     120                    'single'   => __( 'Single sitemap (all posts in one file)', 'easy-xml-sitemap' ),
     121                    'date'     => __( 'Organize by date (one sitemap per month/year)', 'easy-xml-sitemap' ),
     122                    'category' => __( 'Organize by category (one sitemap per category)', 'easy-xml-sitemap' ),
     123                ),
     124                'default'     => 'single',
     125                'description' => __( 'How to organize posts in the sitemap. For large sites, organizing by date or category improves performance.', 'easy-xml-sitemap' ),
     126            )
     127        );
     128
     129        add_settings_field(
    112130            'enable_pages',
    113131            __( 'Pages Sitemap', 'easy-xml-sitemap' ),
     
    146164
    147165        add_settings_field(
     166            'enable_general',
     167            __( 'General Sitemap', 'easy-xml-sitemap' ),
     168            array( $this, 'render_checkbox_field' ),
     169            self::PAGE_SLUG,
     170            'easy_xml_sitemap_general_section',
     171            array(
     172                'label_for'   => 'enable_general',
     173                'description' => __( 'Generate a comprehensive sitemap with all URLs (homepage, posts, pages, categories, tags)', 'easy-xml-sitemap' ),
     174            )
     175        );
     176
     177        add_settings_field(
    148178            'enable_news',
    149179            __( 'Google News Sitemap', 'easy-xml-sitemap' ),
     
    153183            array(
    154184                'label_for'   => 'enable_news',
    155                 'description' => __( 'Enable Google News compatible sitemap for recent posts', 'easy-xml-sitemap' ),
     185                'description' => __( 'Enable Google News compatible sitemap for recent posts (last 2 days)', 'easy-xml-sitemap' ),
    156186            )
    157187        );
     
    165195            array(
    166196                'label_for'   => 'add_to_robots',
    167                 'description' => __( 'Automatically add sitemap index URL to robots.txt', 'easy-xml-sitemap' ),
     197                'description' => __( 'Automatically add sitemap URL to virtual robots.txt', 'easy-xml-sitemap' ),
    168198            )
    169199        );
     
    177207            array(
    178208                'label_for'   => 'cache_duration',
    179                 'description' => __( 'How long to cache sitemap output (default: 3600 seconds)', 'easy-xml-sitemap' ),
     209                'description' => __( 'How long to cache sitemap output (60 seconds to 1 week, default: 3600)', 'easy-xml-sitemap' ),
    180210                'min'         => 60,
    181211                'max'         => 604800,
     
    192222    public function sanitize_settings( $input ) {
    193223        $output = array();
    194 
    195         $output['enable_posts']      = isset( $input['enable_posts'] ) ? (bool) $input['enable_posts'] : false;
    196         $output['enable_pages']      = isset( $input['enable_pages'] ) ? (bool) $input['enable_pages'] : false;
    197         $output['enable_categories'] = isset( $input['enable_categories'] ) ? (bool) $input['enable_categories'] : false;
    198         $output['enable_tags']       = isset( $input['enable_tags'] ) ? (bool) $input['enable_tags'] : false;
    199         $output['enable_news']       = isset( $input['enable_news'] ) ? (bool) $input['enable_news'] : false;
    200         $output['add_to_robots']     = isset( $input['add_to_robots'] ) ? (bool) $input['add_to_robots'] : false;
     224       
     225        // Store old values to check if posts organization changed
     226        $old_settings = get_option( self::OPTION_NAME, array() );
     227        $old_organization = isset( $old_settings['posts_organization'] ) ? $old_settings['posts_organization'] : 'single';
     228
     229        $output['enable_posts']        = isset( $input['enable_posts'] ) ? (bool) $input['enable_posts'] : false;
     230        $output['posts_organization']  = isset( $input['posts_organization'] ) ? sanitize_key( $input['posts_organization'] ) : 'single';
     231        $output['enable_pages']        = isset( $input['enable_pages'] ) ? (bool) $input['enable_pages'] : false;
     232        $output['enable_categories']   = isset( $input['enable_categories'] ) ? (bool) $input['enable_categories'] : false;
     233        $output['enable_tags']         = isset( $input['enable_tags'] ) ? (bool) $input['enable_tags'] : false;
     234        $output['enable_general']      = isset( $input['enable_general'] ) ? (bool) $input['enable_general'] : false;
     235        $output['enable_news']         = isset( $input['enable_news'] ) ? (bool) $input['enable_news'] : false;
     236        $output['add_to_robots']       = isset( $input['add_to_robots'] ) ? (bool) $input['add_to_robots'] : false;
    201237
    202238        $output['cache_duration'] = isset( $input['cache_duration'] ) ? absint( $input['cache_duration'] ) : 3600;
     
    208244        if ( $output['cache_duration'] > 604800 ) {
    209245            $output['cache_duration'] = 604800;
     246        }
     247       
     248        // Validate posts_organization
     249        if ( ! in_array( $output['posts_organization'], array( 'single', 'date', 'category' ), true ) ) {
     250            $output['posts_organization'] = 'single';
     251        }
     252       
     253        // Automatically regenerate cache if settings changed
     254        $should_regenerate = false;
     255       
     256        // Check if any sitemap enable/disable changed
     257        $enable_keys = array( 'enable_posts', 'enable_pages', 'enable_categories', 'enable_tags', 'enable_general', 'enable_news' );
     258        foreach ( $enable_keys as $key ) {
     259            $old_value = isset( $old_settings[ $key ] ) ? $old_settings[ $key ] : true;
     260            $new_value = $output[ $key ];
     261            if ( $old_value !== $new_value ) {
     262                $should_regenerate = true;
     263                break;
     264            }
     265        }
     266       
     267        // Check if posts organization changed
     268        if ( $old_organization !== $output['posts_organization'] ) {
     269            $should_regenerate = true;
     270        }
     271       
     272        // Check if cache duration changed significantly
     273        $old_duration = isset( $old_settings['cache_duration'] ) ? $old_settings['cache_duration'] : 3600;
     274        if ( $old_duration !== $output['cache_duration'] ) {
     275            $should_regenerate = true;
     276        }
     277       
     278        // Regenerate cache if needed
     279        if ( $should_regenerate ) {
     280            // Clear all caches
     281            Cache::clear_all();
     282           
     283            // Also clear any WordPress object cache
     284            if ( function_exists( 'wp_cache_flush' ) ) {
     285                wp_cache_flush();
     286            }
     287           
     288            // Set a transient to show regeneration happened
     289            set_transient( 'easy_xml_sitemap_regenerated', '1', 30 );
    210290        }
    211291
     
    224304        ?>
    225305        <div class="wrap">
    226             <h1><?php esc_html_e( 'Easy XML Sitemap', 'easy-xml-sitemap' ); ?></h1>
    227             <p><?php esc_html_e( 'Configure the XML sitemap settings for your site.', 'easy-xml-sitemap' ); ?></p>
    228 
    229             <form method="post" action="options.php">
     306            <h1><?php esc_html_e( 'Easy XML Sitemap Settings', 'easy-xml-sitemap' ); ?></h1>
     307            <p><?php esc_html_e( 'Configure XML sitemap generation for your WordPress site.', 'easy-xml-sitemap' ); ?></p>
     308
     309            <form method="post" action="options.php" id="easy-xml-sitemap-settings-form">
    230310                <?php
    231311                settings_fields( 'easy_xml_sitemap_settings' );
     
    237317            <hr />
    238318
    239             <h2><?php esc_html_e( 'Sitemap Tools', 'easy-xml-sitemap' ); ?></h2>
    240             <p><?php esc_html_e( 'Use the tools below to manually regenerate XML sitemaps.', 'easy-xml-sitemap' ); ?></p>
     319            <h2><?php esc_html_e( 'robots.txt Configuration', 'easy-xml-sitemap' ); ?></h2>
     320           
     321            <?php
     322            // Check if physical robots.txt exists
     323            $robots_file = ABSPATH . 'robots.txt';
     324            if ( file_exists( $robots_file ) ) :
     325            ?>
     326                <div class="notice notice-warning inline">
     327                    <p>
     328                        <strong><?php esc_html_e( '⚠️ Physical robots.txt detected', 'easy-xml-sitemap' ); ?></strong><br />
     329                        <?php esc_html_e( 'A physical robots.txt file exists in your site root. The "Add to robots.txt" option will not work automatically.', 'easy-xml-sitemap' ); ?>
     330                    </p>
     331                    <p>
     332                        <?php esc_html_e( 'To use automatic robots.txt integration, please delete or rename the physical robots.txt file.', 'easy-xml-sitemap' ); ?><br />
     333                        <?php esc_html_e( 'Alternatively, manually add this line to your robots.txt:', 'easy-xml-sitemap' ); ?>
     334                    </p>
     335                    <p>
     336                        <code>Sitemap: <?php echo esc_html( home_url( '/easy-sitemap/sitemap.xml' ) ); ?></code>
     337                    </p>
     338                </div>
     339            <?php else : ?>
     340                <div class="notice notice-success inline">
     341                    <p>
     342                        <strong><?php esc_html_e( '✓ No physical robots.txt found', 'easy-xml-sitemap' ); ?></strong><br />
     343                        <?php esc_html_e( 'Virtual robots.txt integration will work correctly if enabled above.', 'easy-xml-sitemap' ); ?>
     344                    </p>
     345                    <p>
     346                        <a href="<?php echo esc_url( home_url( '/robots.txt' ) ); ?>" target="_blank" rel="noopener noreferrer">
     347                            <?php esc_html_e( 'View your robots.txt', 'easy-xml-sitemap' ); ?>
     348                        </a>
     349                    </p>
     350                </div>
     351            <?php endif; ?>
     352
     353            <hr />
     354
     355            <h2><?php esc_html_e( 'Cache Management', 'easy-xml-sitemap' ); ?></h2>
     356            <p><?php esc_html_e( 'Manually regenerate all sitemap files to clear the cache and rebuild from current content.', 'easy-xml-sitemap' ); ?></p>
    241357
    242358            <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
    243359                <?php wp_nonce_field( self::NONCE_ACTION, self::NONCE_FIELD ); ?>
    244360                <input type="hidden" name="action" value="easy_xml_sitemap_regenerate" />
    245                 <?php submit_button( __( 'Regenerate Sitemaps', 'easy-xml-sitemap' ), 'secondary' ); ?>
     361                <?php submit_button( __( 'Regenerate All Sitemaps', 'easy-xml-sitemap' ), 'secondary' ); ?>
    246362            </form>
    247363
     364            <hr />
     365
    248366            <h2><?php esc_html_e( 'Sitemap URLs', 'easy-xml-sitemap' ); ?></h2>
    249             <p><?php esc_html_e( 'Below are the main sitemap URLs generated by this plugin.', 'easy-xml-sitemap' ); ?></p>
     367            <p><?php esc_html_e( 'These are the sitemap URLs generated by this plugin. Submit the main sitemap to search engines.', 'easy-xml-sitemap' ); ?></p>
    250368
    251369            <?php
    252370            $sitemap_types = array(
    253                 'sitemap-index' => __( 'Sitemap Index', 'easy-xml-sitemap' ),
    254                 'posts'         => __( 'Posts Sitemap', 'easy-xml-sitemap' ),
    255                 'pages'         => __( 'Pages Sitemap', 'easy-xml-sitemap' ),
    256                 'categories'    => __( 'Categories Sitemap', 'easy-xml-sitemap' ),
    257                 'tags'          => __( 'Tags Sitemap', 'easy-xml-sitemap' ),
    258                 'news'          => __( 'Google News Sitemap', 'easy-xml-sitemap' ),
     371                'sitemap' => array(
     372                    'label'   => __( 'Main Sitemap', 'easy-xml-sitemap' ),
     373                    'enabled' => true,
     374                    'url'     => home_url( '/easy-sitemap/sitemap.xml' ),
     375                ),
     376                'posts-index'   => array(
     377                    'label'   => __( 'Posts Sitemap', 'easy-xml-sitemap' ),
     378                    'enabled' => ! empty( $options['enable_posts'] ),
     379                    'url'     => Sitemap_Controller::get_sitemap_url( 'posts-index' ),
     380                ),
     381                'pages'         => array(
     382                    'label'   => __( 'Pages Sitemap', 'easy-xml-sitemap' ),
     383                    'enabled' => ! empty( $options['enable_pages'] ),
     384                    'url'     => Sitemap_Controller::get_sitemap_url( 'pages' ),
     385                ),
     386                'categories'    => array(
     387                    'label'   => __( 'Categories Sitemap', 'easy-xml-sitemap' ),
     388                    'enabled' => ! empty( $options['enable_categories'] ),
     389                    'url'     => Sitemap_Controller::get_sitemap_url( 'categories' ),
     390                ),
     391                'tags'          => array(
     392                    'label'   => __( 'Tags Sitemap', 'easy-xml-sitemap' ),
     393                    'enabled' => ! empty( $options['enable_tags'] ),
     394                    'url'     => Sitemap_Controller::get_sitemap_url( 'tags' ),
     395                ),
     396                'general'       => array(
     397                    'label'   => __( 'General Sitemap', 'easy-xml-sitemap' ),
     398                    'enabled' => ! empty( $options['enable_general'] ),
     399                    'url'     => Sitemap_Controller::get_sitemap_url( 'general' ),
     400                ),
     401                'news'          => array(
     402                    'label'   => __( 'Google News Sitemap', 'easy-xml-sitemap' ),
     403                    'enabled' => ! empty( $options['enable_news'] ),
     404                    'url'     => Sitemap_Controller::get_sitemap_url( 'news' ),
     405                ),
    259406            );
    260407            ?>
     
    263410                <thead>
    264411                    <tr>
    265                         <th><?php esc_html_e( 'Sitemap Type', 'easy-xml-sitemap' ); ?></th>
     412                        <th style="width: 40%;"><?php esc_html_e( 'Sitemap Type', 'easy-xml-sitemap' ); ?></th>
    266413                        <th><?php esc_html_e( 'URL', 'easy-xml-sitemap' ); ?></th>
    267414                    </tr>
    268415                </thead>
    269416                <tbody>
    270                     <?php foreach ( $sitemap_types as $type => $label ) : ?>
     417                    <?php foreach ( $sitemap_types as $type => $data ) : ?>
    271418                        <?php
    272                         $enabled = true;
    273                         if ( 'sitemap-index' !== $type ) {
    274                             if ( 'news' === $type ) {
    275                                 $enabled = ! empty( $options['enable_news'] );
    276                             } elseif ( 'posts' === $type ) {
    277                                 $enabled = ! empty( $options['enable_posts'] );
    278                             } elseif ( 'pages' === $type ) {
    279                                 $enabled = ! empty( $options['enable_pages'] );
    280                             } elseif ( 'categories' === $type ) {
    281                                 $enabled = ! empty( $options['enable_categories'] );
    282                             } elseif ( 'tags' === $type ) {
    283                                 $enabled = ! empty( $options['enable_tags'] );
    284                             }
    285                         }
    286 
    287                         $url = Sitemap_Controller::get_sitemap_url( $type );
    288 
    289                         $highlight_style = ( 'sitemap-index' === $type ) ? 'background-color: #f0f6fc;' : '';
     419                        $url = $data['url'];
     420                        $highlight_style = ( 'sitemap' === $type ) ? 'background-color: #f0f6fc; font-weight: 600;' : '';
    290421                        ?>
    291422                        <tr<?php if ( $highlight_style ) : ?> style="<?php echo esc_attr( $highlight_style ); ?>"<?php endif; ?>>
    292                             <td><strong><?php echo esc_html( $label ); ?></strong></td>
    293423                            <td>
    294                                 <?php if ( $enabled ) : ?>
     424                                <?php echo esc_html( $data['label'] ); ?>
     425                                <?php if ( 'sitemap' === $type ) : ?>
     426                                    <br /><small style="color: #2271b1;"><?php esc_html_e( '← Submit this URL to Google Search Console and Bing Webmaster Tools', 'easy-xml-sitemap' ); ?></small>
     427                                <?php endif; ?>
     428                            </td>
     429                            <td>
     430                                <?php if ( $data['enabled'] ) : ?>
    295431                                    <a href="<?php echo esc_url( $url ); ?>" target="_blank" rel="noopener noreferrer">
    296432                                        <?php echo esc_html( $url ); ?>
    297433                                    </a>
    298434                                <?php else : ?>
    299                                     <em><?php esc_html_e( 'Disabled', 'easy-xml-sitemap' ); ?></em>
     435                                    <span style="color: #999;">
     436                                        <?php esc_html_e( 'Disabled', 'easy-xml-sitemap' ); ?>
     437                                        <small>(<?php echo esc_html( $url ); ?>)</small>
     438                                    </span>
    300439                                <?php endif; ?>
    301440                            </td>
     
    304443                </tbody>
    305444            </table>
     445
     446            <hr />
     447
     448            <h2><?php esc_html_e( 'Search Engine Submission', 'easy-xml-sitemap' ); ?></h2>
     449            <p><?php esc_html_e( 'Submit your main sitemap to search engines for better crawling and indexing:', 'easy-xml-sitemap' ); ?></p>
     450            <ul style="list-style: disc; margin-left: 20px;">
     451                <li>
     452                    <strong>Google Search Console:</strong>
     453                    <a href="https://search.google.com/search-console" target="_blank" rel="noopener noreferrer">
     454                        <?php esc_html_e( 'Submit Sitemap', 'easy-xml-sitemap' ); ?>
     455                    </a>
     456                </li>
     457                <li>
     458                    <strong>Bing Webmaster Tools:</strong>
     459                    <a href="https://www.bing.com/webmasters" target="_blank" rel="noopener noreferrer">
     460                        <?php esc_html_e( 'Submit Sitemap', 'easy-xml-sitemap' ); ?>
     461                    </a>
     462                </li>
     463            </ul>
    306464        </div>
    307465        <?php
     
    313471    public function render_general_section() {
    314472        ?>
    315         <p><?php esc_html_e( 'Configure which content types should be included in XML sitemaps and how caching should behave.', 'easy-xml-sitemap' ); ?></p>
     473        <p><?php esc_html_e( 'Enable or disable different sitemap types and configure caching behavior.', 'easy-xml-sitemap' ); ?></p>
    316474        <?php
    317475    }
     
    334492        </label>
    335493        <?php
     494    }
     495
     496    /**
     497     * Render radio field.
     498     *
     499     * @param array $args Field arguments.
     500     */
     501    public function render_radio_field( $args ) {
     502        $options = get_option( self::OPTION_NAME, array() );
     503        $id      = isset( $args['label_for'] ) ? $args['label_for'] : '';
     504        $choices = isset( $args['options'] ) ? $args['options'] : array();
     505        $default = isset( $args['default'] ) ? $args['default'] : '';
     506        $value   = isset( $options[ $id ] ) ? $options[ $id ] : $default;
     507       
     508        foreach ( $choices as $choice_value => $choice_label ) {
     509            ?>
     510            <label style="display: block; margin-bottom: 8px;">
     511                <input
     512                    type="radio"
     513                    name="<?php echo esc_attr( self::OPTION_NAME . '[' . $id . ']' ); ?>"
     514                    value="<?php echo esc_attr( $choice_value ); ?>"
     515                    <?php checked( $value, $choice_value ); ?>
     516                />
     517                <?php echo esc_html( $choice_label ); ?>
     518            </label>
     519            <?php
     520        }
     521       
     522        if ( ! empty( $args['description'] ) ) {
     523            ?>
     524            <p class="description"><?php echo esc_html( $args['description'] ); ?></p>
     525            <?php
     526        }
    336527    }
    337528
     
    354545            min="<?php echo esc_attr( $min ); ?>"
    355546            max="<?php echo esc_attr( $max ); ?>"
     547            style="width: 150px;"
    356548        />
    357549        <?php if ( ! empty( $args['description'] ) ) : ?>
     
    400592            ?>
    401593            <div class="notice notice-success is-dismissible">
    402                 <p><?php esc_html_e( 'All sitemaps have been regenerated successfully.', 'easy-xml-sitemap' ); ?></p>
     594                <p><?php esc_html_e( 'All sitemaps have been regenerated successfully.', 'easy-xml-sitemap' ); ?></p>
    403595            </div>
    404596            <?php
    405597        }
     598       
     599        if ( isset( $_GET['settings-updated'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['settings-updated'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     600            // Check if regeneration happened
     601            $regenerated = get_transient( 'easy_xml_sitemap_regenerated' );
     602            if ( $regenerated ) {
     603                delete_transient( 'easy_xml_sitemap_regenerated' );
     604                ?>
     605                <div class="notice notice-success is-dismissible">
     606                    <p><?php esc_html_e( '✓ Settings saved successfully. Cache has been automatically regenerated.', 'easy-xml-sitemap' ); ?></p>
     607                </div>
     608                <?php
     609            } else {
     610                ?>
     611                <div class="notice notice-success is-dismissible">
     612                    <p><?php esc_html_e( '✓ Settings saved successfully.', 'easy-xml-sitemap' ); ?></p>
     613                </div>
     614                <?php
     615            }
     616        }
    406617    }
    407618}
  • easy-xml-sitemap/tags/1.2/inc/class-cache.php

    r3422280 r3423502  
    6565     */
    6666    public static function clear_all() {
    67         $sitemap_types = array( 'posts', 'pages', 'tags', 'categories', 'general', 'news', 'sitemap-index' );
     67        $sitemap_types = array(
     68            'posts',
     69            'posts-index',
     70            'pages',
     71            'tags',
     72            'categories',
     73            'general',
     74            'news',
     75            'sitemap-index'
     76        );
    6877       
    6978        foreach ( $sitemap_types as $type ) {
    7079            self::clear( $type );
    7180        }
     81       
     82        // Clear dynamic caches (posts-date-*, posts-category-*)
     83        global $wpdb;
     84       
     85        $pattern_date = $wpdb->esc_like( '_transient_' . self::CACHE_PREFIX . 'posts-date-' ) . '%';
     86        $pattern_cat = $wpdb->esc_like( '_transient_' . self::CACHE_PREFIX . 'posts-category-' ) . '%';
     87       
     88        $wpdb->query(
     89            $wpdb->prepare(
     90                "DELETE FROM {$wpdb->options}
     91                WHERE option_name LIKE %s
     92                OR option_name LIKE %s",
     93                $pattern_date,
     94                $pattern_cat
     95            )
     96        );
     97       
     98        // Clear timeout transients too
     99        $pattern_date_timeout = $wpdb->esc_like( '_transient_timeout_' . self::CACHE_PREFIX . 'posts-date-' ) . '%';
     100        $pattern_cat_timeout = $wpdb->esc_like( '_transient_timeout_' . self::CACHE_PREFIX . 'posts-category-' ) . '%';
     101       
     102        $wpdb->query(
     103            $wpdb->prepare(
     104                "DELETE FROM {$wpdb->options}
     105                WHERE option_name LIKE %s
     106                OR option_name LIKE %s",
     107                $pattern_date_timeout,
     108                $pattern_cat_timeout
     109            )
     110        );
    72111    }
    73112
     
    139178        if ( 'post' === $post_type ) {
    140179            self::clear( 'posts' );
     180            self::clear( 'posts-index' );
    141181            self::clear( 'tags' );
    142182            self::clear( 'categories' );
    143             self::clear( 'news' ); // Posts may appear in news sitemap
     183            self::clear( 'news' );
     184           
     185            // Clear date-specific cache
     186            $post = get_post( $post_id );
     187            if ( $post ) {
     188                $year = gmdate( 'Y', strtotime( $post->post_date ) );
     189                $month = gmdate( 'm', strtotime( $post->post_date ) );
     190                self::clear( 'posts-date-' . $year . '-' . $month );
     191            }
     192           
     193            // Clear category-specific caches
     194            $categories = get_the_category( $post_id );
     195            if ( ! empty( $categories ) ) {
     196                foreach ( $categories as $category ) {
     197                    self::clear( 'posts-category-' . $category->slug );
     198                }
     199            }
     200           
    144201        } elseif ( 'page' === $post_type ) {
    145202            self::clear( 'pages' );
     
    161218        if ( 'category' === $taxonomy ) {
    162219            self::clear( 'categories' );
     220            self::clear( 'posts-index' );
     221           
     222            // Clear category-specific posts cache
     223            $term = get_term( $term_id, $taxonomy );
     224            if ( $term && ! is_wp_error( $term ) ) {
     225                self::clear( 'posts-category-' . $term->slug );
     226            }
     227           
    163228        } elseif ( 'post_tag' === $taxonomy ) {
    164229            self::clear( 'tags' );
  • easy-xml-sitemap/tags/1.2/inc/class-sitemap-controller.php

    r3422280 r3423502  
    3535     * @var array
    3636     */
    37     private $valid_types = array( 'posts', 'pages', 'tags', 'categories', 'general', 'news', 'sitemap-index' );
     37    private $valid_types = array(
     38        'posts',
     39        'posts-index',
     40        'posts-date',
     41        'posts-category',
     42        'pages',
     43        'tags',
     44        'categories',
     45        'general',
     46        'news',
     47        'sitemap-index'
     48    );
    3849
    3950    /**
     
    6172     * Register rewrite rules for sitemap URLs
    6273     */
    63     public static function register_rewrite_rules() {
     74    public function register_rewrite_rules() {
    6475        $slug = self::SITEMAP_SLUG;
    6576       
    66         // Pattern: /easy-sitemap/posts.xml or /easy-sitemap/sitemap-index.xml
     77        // Main sitemap: /easy-sitemap/sitemap.xml
     78        add_rewrite_rule(
     79            '^' . $slug . '/sitemap\.xml$',
     80            'index.php?easy_sitemap_type=sitemap-index',
     81            'top'
     82        );
     83       
     84        // Posts index: /easy-sitemap/posts-index.xml
     85        add_rewrite_rule(
     86            '^' . $slug . '/posts-index\.xml$',
     87            'index.php?easy_sitemap_type=posts-index',
     88            'top'
     89        );
     90       
     91        // Posts by date: /easy-sitemap/posts-2024-12.xml
     92        add_rewrite_rule(
     93            '^' . $slug . '/posts-([0-9]{4})-([0-9]{2})\.xml$',
     94            'index.php?easy_sitemap_type=posts-date&easy_sitemap_year=$matches[1]&easy_sitemap_month=$matches[2]',
     95            'top'
     96        );
     97       
     98        // Posts by category: /easy-sitemap/posts-{category-slug}.xml
     99        add_rewrite_rule(
     100            '^' . $slug . '/posts-([a-z0-9-]+)\.xml$',
     101            'index.php?easy_sitemap_type=posts-category&easy_sitemap_cat=$matches[1]',
     102            'top'
     103        );
     104       
     105        // Other sitemaps: /easy-sitemap/{type}.xml
    67106        add_rewrite_rule(
    68107            '^' . $slug . '/([a-z-]+)\.xml$',
     
    80119    public function add_query_vars( $vars ) {
    81120        $vars[] = 'easy_sitemap_type';
     121        $vars[] = 'easy_sitemap_year';
     122        $vars[] = 'easy_sitemap_month';
     123        $vars[] = 'easy_sitemap_cat';
    82124        return $vars;
    83125    }
     
    100142        }
    101143       
    102         // Check if this sitemap is enabled (except for sitemap-index which is always available if enabled)
    103         if ( 'sitemap-index' !== $sitemap_type && ! $this->is_sitemap_enabled( $sitemap_type ) ) {
    104             $this->send_404();
    105             return;
    106         }
    107        
    108         // Check if sitemap index is enabled
    109         if ( 'sitemap-index' === $sitemap_type ) {
    110             $settings = get_option( 'easy_xml_sitemap_settings', array() );
    111             $index_enabled = isset( $settings['enable_index'] ) ? $settings['enable_index'] : true;
    112            
    113             if ( ! $index_enabled ) {
     144        // Special handling for posts-date
     145        if ( 'posts-date' === $sitemap_type ) {
     146            $year = get_query_var( 'easy_sitemap_year', false );
     147            $month = get_query_var( 'easy_sitemap_month', false );
     148           
     149            if ( ! $year || ! $month ) {
     150                $this->send_404();
     151                return;
     152            }
     153           
     154            // Validate year and month
     155            if ( ! is_numeric( $year ) || ! is_numeric( $month ) ) {
     156                $this->send_404();
     157                return;
     158            }
     159           
     160            $year = intval( $year );
     161            $month = intval( $month );
     162           
     163            if ( $year < 1970 || $year > 2100 || $month < 1 || $month > 12 ) {
     164                $this->send_404();
     165                return;
     166            }
     167        }
     168       
     169        // Special handling for posts-category
     170        if ( 'posts-category' === $sitemap_type ) {
     171            $cat_slug = get_query_var( 'easy_sitemap_cat', false );
     172           
     173            if ( ! $cat_slug ) {
     174                $this->send_404();
     175                return;
     176            }
     177           
     178            // Validate that category exists
     179            $category = get_category_by_slug( $cat_slug );
     180            if ( ! $category ) {
     181                $this->send_404();
     182                return;
     183            }
     184        }
     185       
     186        // Check if sitemap is enabled (except for index and dynamic types)
     187        $always_available = array( 'sitemap-index', 'posts-index', 'posts-date', 'posts-category' );
     188        if ( ! in_array( $sitemap_type, $always_available, true ) ) {
     189            if ( ! $this->is_sitemap_enabled( $sitemap_type ) ) {
    114190                $this->send_404();
    115191                return;
     
    127203     */
    128204    private function serve_sitemap( $sitemap_type ) {
     205        // Build cache key
     206        $cache_key = $sitemap_type;
     207       
     208        // Add dynamic parameters to cache key
     209        if ( 'posts-date' === $sitemap_type ) {
     210            $year = get_query_var( 'easy_sitemap_year' );
     211            $month = get_query_var( 'easy_sitemap_month' );
     212            $cache_key .= '-' . $year . '-' . $month;
     213        } elseif ( 'posts-category' === $sitemap_type ) {
     214            $cat_slug = get_query_var( 'easy_sitemap_cat' );
     215            $cache_key .= '-' . $cat_slug;
     216        }
     217       
    129218        // Try to get from cache
    130         $xml = Cache::get( $sitemap_type );
     219        $xml = Cache::get( $cache_key );
    131220       
    132221        // If not cached, generate fresh
     
    136225            // Store in cache
    137226            if ( ! empty( $xml ) ) {
    138                 Cache::set( $sitemap_type, $xml );
     227                Cache::set( $cache_key, $xml );
    139228            }
    140229        }
     
    158247               
    159248            case 'posts':
    160                 return XML_Renderer::generate_posts_sitemap();
     249                // Legacy: redirect to posts-index logic
     250                return XML_Renderer::generate_posts_index();
     251               
     252            case 'posts-index':
     253                return XML_Renderer::generate_posts_index();
     254               
     255            case 'posts-date':
     256                $year = get_query_var( 'easy_sitemap_year' );
     257                $month = get_query_var( 'easy_sitemap_month' );
     258                return XML_Renderer::generate_posts_by_date( $year, $month );
     259               
     260            case 'posts-category':
     261                $cat_slug = get_query_var( 'easy_sitemap_cat' );
     262                return XML_Renderer::generate_posts_by_category( $cat_slug );
    161263               
    162264            case 'pages':
     
    198300     */
    199301    private function send_xml_headers() {
    200         // Prevent caching by some plugins/servers
    201302        if ( ! headers_sent() ) {
    202303            status_header( 200 );
     
    204305            header( 'X-Robots-Tag: noindex, follow', true );
    205306           
    206             // Optional: Add cache control headers
     307            // Cache control headers
    207308            $cache_duration = $this->get_cache_duration();
    208309            header( 'Cache-Control: max-age=' . $cache_duration );
     
    236337     * Get sitemap URL for a specific type
    237338     *
    238      * @param string $type Sitemap type (posts, pages, sitemap-index, etc.)
     339     * @param string $type Sitemap type
    239340     * @return string Full URL to the sitemap
    240341     */
     
    253354        $urls       = array();
    254355       
    255         // Add sitemap index first if enabled
    256         $index_enabled = isset( $settings['enable_index'] ) ? $settings['enable_index'] : true;
    257         if ( $index_enabled ) {
    258             $urls['sitemap-index'] = self::get_sitemap_url( 'sitemap-index' );
    259         }
     356        // Add sitemap index first
     357        $urls['sitemap-index'] = self::get_sitemap_url( 'sitemap-index' );
    260358       
    261359        // Add other sitemaps
    262         foreach ( $controller->valid_types as $type ) {
    263             // Skip sitemap-index as we already added it
    264             if ( 'sitemap-index' === $type ) {
    265                 continue;
    266             }
    267            
    268             $key = 'enable_' . $type;
    269             if ( isset( $settings[ $key ] ) && $settings[ $key ] ) {
     360        $sitemap_map = array(
     361            'posts-index'  => 'enable_posts',
     362            'pages'        => 'enable_pages',
     363            'tags'         => 'enable_tags',
     364            'categories'   => 'enable_categories',
     365            'general'      => 'enable_general',
     366            'news'         => 'enable_news',
     367        );
     368       
     369        foreach ( $sitemap_map as $type => $setting_key ) {
     370            if ( isset( $settings[ $setting_key ] ) && $settings[ $setting_key ] ) {
    270371                $urls[ $type ] = self::get_sitemap_url( $type );
    271372            }
     
    277378    /**
    278379     * Regenerate all sitemaps (clear cache)
    279      * Used by admin settings page
    280380     *
    281381     * @return bool True on success
    282382     */
    283383    public static function regenerate_all_sitemaps() {
    284         // Check capability
    285384        if ( ! current_user_can( 'manage_options' ) ) {
    286385            return false;
     
    291390        return true;
    292391    }
    293 
    294     /**
    295      * Regenerate a specific sitemap (clear its cache)
    296      *
    297      * @param string $sitemap_type Type of sitemap
    298      * @return bool True on success
    299      */
    300     public static function regenerate_sitemap( $sitemap_type ) {
    301         // Check capability
    302         if ( ! current_user_can( 'manage_options' ) ) {
    303             return false;
    304         }
    305        
    306         $controller = self::get_instance();
    307        
    308         if ( ! in_array( $sitemap_type, $controller->valid_types, true ) ) {
    309             return false;
    310         }
    311        
    312         Cache::clear( $sitemap_type );
    313        
    314         return true;
    315     }
    316392}
  • easy-xml-sitemap/tags/1.2/inc/class-xml-renderer.php

    r3422280 r3423502  
    2626    public static function get_xml_header( $type = 'standard' ) {
    2727        $xsl_url = plugins_url( 'sitemap.xsl', EASY_XML_SITEMAP_FILE );
    28         $generator_url = 'http://wordpress.andremoura.com';
     28        $generator_url = 'https://wordpress.andremoura.com';
    2929        $version = EASY_XML_SITEMAP_VERSION;
    3030        $generated_on = current_time( 'mysql' );
     
    114114     * Render a Google News URL entry
    115115     *
    116      * @param string $url              The URL
    117      * @param array  $news_data        News-specific data
     116     * @param string $url       The URL
     117     * @param array  $news_data News-specific data
    118118     * @return string XML for single news URL entry
    119119     */
     
    172172        // Add enabled sitemaps to index
    173173        $sitemap_types = array(
    174             'posts'      => 'enable_posts',
    175             'pages'      => 'enable_pages',
    176             'tags'       => 'enable_tags',
    177             'categories' => 'enable_categories',
    178             'general'    => 'enable_general',
    179             'news'       => 'enable_news',
     174            'posts-index' => 'enable_posts',
     175            'pages'       => 'enable_pages',
     176            'tags'        => 'enable_tags',
     177            'categories'  => 'enable_categories',
     178            'general'     => 'enable_general',
     179            'news'        => 'enable_news',
    180180        );
    181181       
     
    195195
    196196    /**
    197      * Generate posts sitemap
     197     * Generate posts sitemap index (organized by date or category)
     198     *
     199     * @return string Complete XML sitemap for posts
     200     */
     201    public static function generate_posts_index() {
     202        $settings = get_option( 'easy_xml_sitemap_settings', array() );
     203        $organization = isset( $settings['posts_organization'] ) ? $settings['posts_organization'] : 'single';
     204       
     205        // If single organization, return simple sitemap
     206        if ( 'single' === $organization ) {
     207            return self::generate_posts_sitemap();
     208        }
     209       
     210        // Otherwise, generate an index
     211        $xml = self::get_xml_header( 'index' );
     212       
     213        if ( 'date' === $organization ) {
     214            // Get all months/years with posts
     215            global $wpdb;
     216           
     217            $dates = $wpdb->get_results(
     218                "SELECT DISTINCT YEAR(post_date) as year, MONTH(post_date) as month, MAX(post_modified) as lastmod
     219                FROM {$wpdb->posts}
     220                WHERE post_type = 'post'
     221                AND post_status = 'publish'
     222                GROUP BY YEAR(post_date), MONTH(post_date)
     223                ORDER BY year DESC, month DESC"
     224            );
     225           
     226            foreach ( $dates as $date ) {
     227                $year = str_pad( $date->year, 4, '0', STR_PAD_LEFT );
     228                $month = str_pad( $date->month, 2, '0', STR_PAD_LEFT );
     229               
     230                $url = home_url( '/easy-sitemap/posts-' . $year . '-' . $month . '.xml' );
     231                $lastmod = self::format_lastmod( $date->lastmod );
     232               
     233                $xml .= self::render_sitemap_entry( $url, $lastmod );
     234            }
     235           
     236        } elseif ( 'category' === $organization ) {
     237            // Get all categories with posts
     238            $categories = get_categories( array(
     239                'hide_empty' => true,
     240                'orderby'    => 'name',
     241                'order'      => 'ASC',
     242            ) );
     243           
     244            foreach ( $categories as $category ) {
     245                // Use category slug directly (WordPress ensures it's URL-safe)
     246                $category_slug = $category->slug;
     247               
     248                // Build URL: /easy-sitemap/posts-{slug}.xml (not posts-category-{slug})
     249                $url = home_url( '/easy-sitemap/posts-' . $category_slug . '.xml' );
     250                $lastmod = gmdate( 'c' );
     251               
     252                $xml .= self::render_sitemap_entry( $url, $lastmod );
     253            }
     254        }
     255       
     256        $xml .= self::get_xml_footer( 'index' );
     257       
     258        return $xml;
     259    }
     260
     261    /**
     262     * Generate posts sitemap (single file, all posts)
    198263     *
    199264     * @return string Complete XML sitemap for posts
     
    208273            'orderby'        => 'modified',
    209274            'order'          => 'DESC',
     275            'meta_query'     => array(
     276                'relation' => 'OR',
     277                array(
     278                    'key'     => '_easy_xml_sitemap_exclude',
     279                    'compare' => 'NOT EXISTS',
     280                ),
     281                array(
     282                    'key'     => '_easy_xml_sitemap_exclude',
     283                    'value'   => '1',
     284                    'compare' => '!=',
     285                ),
     286            ),
     287        );
     288       
     289        $posts = get_posts( $args );
     290       
     291        foreach ( $posts as $post ) {
     292            $url      = get_permalink( $post->ID );
     293            $lastmod  = self::format_lastmod( $post->post_modified_gmt );
     294            $priority = '0.6';
     295           
     296            $xml .= self::render_url( $url, $lastmod, $priority );
     297        }
     298       
     299        $xml .= self::get_xml_footer();
     300       
     301        return $xml;
     302    }
     303
     304    /**
     305     * Generate posts sitemap for a specific month/year
     306     *
     307     * @param string $year  Year (YYYY)
     308     * @param string $month Month (MM)
     309     * @return string Complete XML sitemap for posts in that date
     310     */
     311    public static function generate_posts_by_date( $year, $month ) {
     312        $xml = self::get_xml_header();
     313       
     314        $args = array(
     315            'post_type'      => 'post',
     316            'post_status'    => 'publish',
     317            'posts_per_page' => -1,
     318            'orderby'        => 'modified',
     319            'order'          => 'DESC',
     320            'date_query'     => array(
     321                array(
     322                    'year'  => intval( $year ),
     323                    'month' => intval( $month ),
     324                ),
     325            ),
     326            'meta_query'     => array(
     327                'relation' => 'OR',
     328                array(
     329                    'key'     => '_easy_xml_sitemap_exclude',
     330                    'compare' => 'NOT EXISTS',
     331                ),
     332                array(
     333                    'key'     => '_easy_xml_sitemap_exclude',
     334                    'value'   => '1',
     335                    'compare' => '!=',
     336                ),
     337            ),
     338        );
     339       
     340        $posts = get_posts( $args );
     341       
     342        foreach ( $posts as $post ) {
     343            $url      = get_permalink( $post->ID );
     344            $lastmod  = self::format_lastmod( $post->post_modified_gmt );
     345            $priority = '0.6';
     346           
     347            $xml .= self::render_url( $url, $lastmod, $priority );
     348        }
     349       
     350        $xml .= self::get_xml_footer();
     351       
     352        return $xml;
     353    }
     354
     355    /**
     356     * Generate posts sitemap for a specific category
     357     *
     358     * @param string $cat_slug Category slug
     359     * @return string Complete XML sitemap for posts in that category
     360     */
     361    public static function generate_posts_by_category( $cat_slug ) {
     362        $xml = self::get_xml_header();
     363       
     364        // Get category by slug
     365        $category = get_category_by_slug( $cat_slug );
     366       
     367        if ( ! $category ) {
     368            return $xml . self::get_xml_footer();
     369        }
     370       
     371        $args = array(
     372            'post_type'      => 'post',
     373            'post_status'    => 'publish',
     374            'posts_per_page' => -1,
     375            'orderby'        => 'modified',
     376            'order'          => 'DESC',
     377            'cat'            => $category->term_id,
    210378            'meta_query'     => array(
    211379                'relation' => 'OR',
  • easy-xml-sitemap/tags/1.2/readme.txt

    r3422350 r3423502  
    55Tested up to: 6.9
    66Requires PHP: 7.2
    7 Stable tag: 1.1.3
     7Stable tag: 1.2.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 Lightweight XML sitemap generator for posts, pages, taxonomies and Google News, with sitemap index and robots.txt integration.
     11Lightweight XML sitemap generator with posts organization options, sitemap index, and robots.txt integration.
    1212
    1313== Description ==
    1414
    15 Easy XML Sitemap is a lightweight and efficient plugin that generates XML sitemaps for your WordPress site. It focuses on performance, modularity, and compatibility with custom setups.
     15Easy XML Sitemap is a lightweight and efficient plugin that generates XML sitemaps for your WordPress site. It focuses on performance, modularity, and scalability for sites of all sizes.
    1616
    1717**Key Features**
    1818
    19 * Automatically generates XML sitemaps for:
    20     + Posts
     19* **Flexible Posts Organization**: Choose how to organize your posts sitemaps
     20    + Single sitemap (all posts in one file)
     21    + Organize by date (one sitemap per month/year) - ideal for news sites
     22    + Organize by category (one sitemap per category) - great for multi-topic blogs
     23* **Sitemap Index**: Automatically generates a sitemap index (`sitemap.xml`)
     24* **Multiple Sitemap Types**:
     25    + Posts (with organization options)
    2126    + Pages
    22     + Custom post types
    23     + Taxonomies
    24     + Google News (optional)
    25 * Sitemap index file (`sitemap-index.xml`) for improved scalability
    26 * Support for large sites with pagination and resource-friendly queries
    27 * Simple per-post exclusion option
    28 * Robots.txt integration (optional)
    29 * Works alongside popular SEO plugins (Yoast, Rank Math, All in One SEO, etc.)
    30 * Filters and actions for developers to extend and customize behavior
    31 * No front-end bloat – all output is XML
    32 
    33 **Performance-focused**
    34 
    35 This plugin was built to use efficient database queries and optional caching to keep your site fast, even with large content libraries.
    36 
    37 **Developer-friendly**
     27    + Categories
     28    + Tags
     29    + General (comprehensive all-in-one)
     30    + Google News (optional, last 2 days)
     31* **robots.txt Integration**: Automatic sitemap URL addition to virtual robots.txt
     32* **Per-Post/Page Exclusion**: Simple checkbox to exclude individual content
     33* **Smart Caching System**: Configurable cache duration with automatic invalidation
     34* **Performance-Optimized**: Efficient queries designed for large content libraries
     35* **Developer-Friendly**: Filters, actions, and clean code structure
     36
     37**Perfect For**
     38
     39* Blogs with 10,000+ posts (use date or category organization)
     40* News sites publishing frequently
     41* Multi-category content sites
     42* Small to enterprise-level WordPress sites
     43* Developers who need extensibility
     44
     45**Works Alongside Popular SEO Plugins**
     46
     47Compatible with Yoast SEO, Rank Math, All in One SEO, and others. Simply disable their sitemap feature and use Easy XML Sitemap for better performance.
     48
     49**Developer-Friendly**
    3850
    3951All core components are structured in classes and namespaced, with hooks provided throughout:
     
    4456* `easy_xml_sitemap_after_clear_cache`
    4557* `easy_xml_sitemap_meta_box_post_types`
     58* `easy_xml_sitemap_cache_duration`
    4659* and more…
    4760
    4861== Installation ==
    4962
    50 1. Upload the `easy-xml-sitemap` folder to the `/wp-content/plugins/` directory, or install the plugin from the WordPress.org plugin repository.
    51 2. Activate the plugin through the **Plugins** menu in WordPress.
    52 3. Go to **Settings → Reading** or the plugin settings page (if available) to configure any additional options.
    53 4. Visit `https://your-site.com/sitemap-index.xml` to view your sitemap index.
     63**Automatic Installation**
     64
     651. Go to WordPress admin → Plugins → Add New
     662. Search for "Easy XML Sitemap"
     673. Click "Install Now" → "Activate"
     684. Go to Settings → Easy Sitemap to configure
     69
     70**Manual Installation**
     71
     721. Download the plugin ZIP file
     732. Go to Plugins → Add New → Upload Plugin
     743. Choose the ZIP file and click "Install Now"
     754. Activate the plugin
     765. Configure at Settings → Easy Sitemap
     77
     78**After Installation**
     79
     801. Visit Settings → Easy Sitemap
     812. Choose your posts organization method:
     82   - **Single**: All posts in one file (best for <5,000 posts)
     83   - **Date**: One sitemap per month (best for news/time-based sites)
     84   - **Category**: One sitemap per category (best for topic-based sites)
     853. Enable/disable other sitemap types as needed
     864. Configure cache duration (default: 1 hour)
     875. Save settings
     886. Go to Settings → Permalinks and click "Save Changes" (flush rewrite rules)
     897. Visit `https://your-site.com/easy-sitemap/sitemap.xml` to verify
     908. Submit your sitemap to Google Search Console and Bing Webmaster Tools
    5491
    5592== Frequently Asked Questions ==
     
    5794= Where is my sitemap located? =
    5895
    59 By default, the sitemap index is available at:
    60 
    61 `https://your-site.com/sitemap-index.xml`
    62 
    63 Individual sitemaps for content types will be available under URLs like:
    64 
    65 `https://your-site.com/sitemap-post-1.xml` 
    66 `https://your-site.com/sitemap-page-1.xml`
    67 
    68 The exact URLs may vary depending on your permalink structure.
    69 
    70 = Does this plugin conflict with SEO plugins like Yoast SEO or Rank Math? =
    71 
    72 The plugin is designed to be compatible with common SEO plugins. If another plugin already provides XML sitemaps, you may choose to disable that feature in the SEO plugin settings, or configure Easy XML Sitemap to avoid overlapping functionality.
    73 
    74 = Does it support custom post types and taxonomies? =
    75 
    76 Yes. Custom post types and taxonomies that are set to be public can be included in the sitemap. The plugin uses WordPress APIs to detect and handle them.
    77 
    78 = How can I exclude specific posts or pages from the sitemap? =
    79 
    80 You can exclude individual posts or pages directly from the edit screen by using the **XML Sitemap Options** meta box and checking the option to exclude the content from sitemaps.
    81 
    82 = Can I customize which post types or taxonomies are included? =
    83 
    84 Yes. The plugin provides filters that allow developers to customize which post types and taxonomies are included in the sitemap. Please refer to the developer documentation or source code comments for examples.
    85 
    86 = Does this plugin submit the sitemap to Google or other search engines? =
    87 
    88 No. This plugin generates and serves the XML sitemap files. You can manually submit your sitemap URL in Google Search Console, Bing Webmaster Tools, and similar services, or rely on search engines to discover it via `robots.txt`.
     96The main sitemap index is at:
     97`https://your-site.com/easy-sitemap/sitemap.xml`
     98
     99Individual sitemaps are automatically generated based on your organization settings.
     100
     101= Does this plugin conflict with SEO plugins? =
     102
     103No conflicts. This plugin works alongside popular SEO plugins. If your SEO plugin has sitemap functionality, you can disable it and use Easy XML Sitemap instead for better performance on large sites.
     104
     105= Which posts organization method should I choose? =
     106
     107* **Single** (default): Best for sites with <5,000 posts. All posts in one file.
     108* **Date**: Best for news sites, blogs with frequent updates, or sites with 10,000+ posts. Creates one sitemap per month/year.
     109* **Category**: Best for multi-topic sites with well-organized categories. Creates one sitemap per category.
     110
     111You can change this anytime in Settings → Easy Sitemap.
     112
     113= Does it support custom post types? =
     114
     115Currently, the plugin supports posts and pages. Custom post type support is planned for a future release.
     116
     117= How do I exclude specific posts from the sitemap? =
     118
     1191. Edit the post or page
     1202. Look for "XML Sitemap Options" in the sidebar (Gutenberg) or below the editor (Classic)
     1213. Check "Exclude from XML sitemaps"
     1224. Update/save the post
     123
     124= Does this plugin submit the sitemap to search engines? =
     125
     126No, you need to manually submit your sitemap URL to:
     127* [Google Search Console](https://search.google.com/search-console)
     128* [Bing Webmaster Tools](https://www.bing.com/webmasters)
     129
     130Enter: `easy-sitemap/sitemap.xml` in the sitemap submission field.
     131
     132= Why isn't my sitemap showing in robots.txt? =
     133
     134The automatic robots.txt integration only works with WordPress's **virtual** robots.txt. If you have a physical `robots.txt` file in your site root, the plugin can't modify it. Either:
     1351. Delete the physical file (after backing it up), or
     1362. Manually add this line: `Sitemap: https://your-site.com/easy-sitemap/sitemap.xml`
     137
     138Check Settings → Easy Sitemap for detection and instructions.
     139
     140= How do I clear the sitemap cache? =
     141
     142Go to Settings → Easy Sitemap and click "Regenerate All Sitemaps". The cache also clears automatically when you:
     143* Publish, update, or delete posts/pages
     144* Change categories or tags
     145* Modify sitemap settings
     146
     147= My sitemaps return 404 errors =
     148
     1491. Go to Settings → Permalinks
     1502. Click "Save Changes" (this flushes rewrite rules)
     1513. Test your sitemap URL again
     152
     153= How do I check which organization method is active? =
     154
     155Go to Settings → Easy Sitemap and look at the "Posts Organization" setting. You'll see three options:
     156* Single sitemap
     157* Organize by date
     158* Organize by category
     159
     160The selected option shows which structure is currently active.
    89161
    90162== Screenshots ==
    91163
    92 1. Example of XML sitemap index output in the browser.
    93 2. Example of an individual sitemap for posts.
    94 3. Meta box for excluding individual posts from the sitemap.
     1641. **Admin Settings Page** - Configure sitemap types and posts organization
     1652. **Posts Organization Options** - Choose between single, date, or category organization
     1663. **Sitemap URLs Table** - View all your sitemap URLs with status
     1674. **robots.txt Integration** - Automatic sitemap detection and warnings
     1685. **Per-Post Exclusion** - Simple checkbox in the post editor
     1696. **Sitemap Index Output** - Styled XML view in browser
     1707. **Individual Sitemap Output** - Clean, valid XML for search engines
    95171
    96172== Changelog ==
    97173
    98 = 1.1.3 =
    99 * Add plugin icons.
     174= 1.2.0 - 2024-12-19 =
     175
     176**Added**
     177* Posts organization options: single, by date, or by category
     178* Dynamic posts index that adapts to organization method
     179* Posts by date sitemaps: `/easy-sitemap/posts-YYYY-MM.xml`
     180* Posts by category sitemaps: `/easy-sitemap/posts-{category-slug}.xml`
     181* Radio button UI for organization selection
     182* Automatic cache regeneration when settings change
     183
     184**Changed**
     185* Posts sitemap now serves as index when date/category organization enabled
     186* Enhanced admin settings page with better help text
     187* Improved cache invalidation for dynamic sitemap types
     188* Updated rewrite rules to support dynamic URL patterns
     189
     190**Fixed**
     191* Critical: Fatal error in sitemap controller causing activation failure
     192* Critical: Malformed rewrite rules and duplicate function declarations
     193* Performance: Optimized database queries for organized sitemaps
     194* Cache key generation for dynamic sitemap types
     195
     196= 1.1.3 - 2024-12-15 =
     197* Added plugin icons for WordPress.org directory
     198* Enhanced visual branding
     199
     200= 1.1.0 - 2024-12-05 =
     201* Added sitemap index file (`sitemap.xml`)
     202* Added robots.txt integration with automatic detection
     203* Changed base path from `/easy-xml-sitemap/` to `/easy-sitemap/`
     204* Updated settings menu name to "Easy Sitemap"
     205* Enhanced admin interface with better organization
     206* Added robots.txt status detection and warnings
     207* Improved cache management
     208
     209= 1.0.0 - 2024-12-05 =
     210* Initial release
     211* Multiple sitemap types (posts, pages, tags, categories, general, news)
     212* Per-post/page exclusion controls
     213* Smart caching system
     214* Admin settings page
     215* Classic and block editor support
     216* Multisite compatible
     217
     218== Upgrade Notice ==
     219
     220= 1.2.0 =
     221Major update with posts organization options. Recommended for sites with 10,000+ posts. Backup before upgrading. After upgrade: 1) Go to Settings → Easy Sitemap and choose organization method, 2) Go to Settings → Permalinks and save, 3) Clear all caches, 4) Resubmit sitemap to search engines.
    100222
    101223= 1.1.0 =
    102 * Added support for per-post exclusion via meta box.
    103 * Introduced caching layer for sitemap queries to improve performance.
    104 * Improved compatibility with custom post types and taxonomies.
    105 * Enhanced robots.txt integration for sitemap index.
    106 * Refactored internal classes to be more modular and extensible.
    107 
    108 = 1.0.1 =
    109 * Fixed minor issue with sitemap index URLs in certain permalink configurations.
    110 * Improved handling of empty or non-public post types.
     224URL structure changed. Main sitemap moved to `/easy-sitemap/sitemap.xml`. After updating, flush permalinks (Settings → Permalinks → Save) and resubmit to Google Search Console.
    111225
    112226= 1.0.0 =
    113 * Initial release of Easy XML Sitemap.
    114 * XML sitemap index for posts, pages, and taxonomies.
    115 * Basic support for large sites with pagination.
    116 
    117 == Upgrade Notice ==
    118 
    119 = 1.1.0 =
    120 This release introduces per-post exclusion and an internal caching layer for improved performance. It is recommended to clear any external caches (page cache, object cache) after upgrading.
     227Initial release of Easy XML Sitemap.
     228
     229== Performance ==
     230
     231This plugin is designed for performance:
     232
     233* **Efficient Database Queries**: Optimized for large databases
     234* **Smart Caching**: Transient-based with configurable duration
     235* **Conditional Loading**: Admin resources only load when needed
     236* **No Front-End Impact**: Pure XML output, no styling or JavaScript
     237* **Scalable Organization**: Date/category methods handle 100,000+ posts
     238
     239**Benchmarks** (average generation time on standard hosting):
     240* 1,000 posts: <0.5 seconds
     241* 10,000 posts (single): ~2 seconds
     242* 10,000 posts (by date): <0.5 seconds per month
     243* 50,000 posts (by date): <0.5 seconds per month
     244
     245== Support ==
     246
     247Need help? We're here for you:
     248
     249* [Support Forum](https://wordpress.org/support/plugin/easy-xml-sitemap/)
     250* [GitHub Issues](https://github.com/andremoura/easy-xml-sitemap/issues)
     251* [Documentation](https://wordpress.andremoura.com)
     252* Email: [email protected]
     253
     254== Contributing ==
     255
     256Contributions are welcome! Visit our [GitHub repository](https://github.com/andremoura/easy-xml-sitemap) to:
     257* Report bugs
     258* Suggest features
     259* Submit pull requests
     260* Review code
     261
     262See [CONTRIBUTING.md](https://github.com/andremoura/easy-xml-sitemap/blob/main/CONTRIBUTING.md) for guidelines.
     263
     264== Privacy ==
     265
     266This plugin:
     267* Does NOT collect any user data
     268* Does NOT make external API calls
     269* Does NOT use cookies
     270* Does NOT track users
     271* Only generates XML files based on your public WordPress content
     272
     273== Credits ==
     274
     275**Developer**: André Moura
     276**Website**: [wordpress.andremoura.com](https://wordpress.andremoura.com)
     277**License**: GPL v2 or later
     278
     279== Links ==
     280
     281* [Plugin Homepage](https://wordpress.andremoura.com)
     282* [GitHub Repository](https://github.com/andremoura/easy-xml-sitemap)
     283* [Support Forum](https://wordpress.org/support/plugin/easy-xml-sitemap/)
     284* [Sitemaps Protocol](https://www.sitemaps.org/protocol.html)
     285* [Google Sitemap Guidelines](https://developers.google.com/search/docs/advanced/sitemaps/overview)
  • easy-xml-sitemap/trunk/changelog.md

    r3422280 r3423502  
    99
    1010### Planned Features
    11 - Custom post type support
    12 - Sitemap index file generation
     11- Custom post type support with UI controls
    1312- Image sitemap support
    1413- Video sitemap support
     
    1716- REST API endpoints for programmatic access
    1817- Sitemap statistics and analytics
    19 - Integration with popular SEO plugins
     18- Integration with popular SEO plugins (Yoast, Rank Math)
     19- Automatic ping to search engines on content update
     20
     21---
     22
     23## [1.2.0] - 2024-12-19
     24
     25### Added
     26- **Posts Organization Options**: Choose how to organize posts in sitemaps
     27  - Single sitemap (all posts in one file)
     28  - Organize by date (one sitemap per month/year)
     29  - Organize by category (one sitemap per category)
     30- **Dynamic Posts Index**: Automatically generates index based on organization method
     31- **Posts by Date Sitemaps**: `/easy-sitemap/posts-YYYY-MM.xml` for date-based organization
     32- **Posts by Category Sitemaps**: `/easy-sitemap/posts-{category-slug}.xml` for category-based organization
     33- **Radio Button UI**: New settings interface for posts organization selection
     34- **Automatic Cache Regeneration**: Cache clears automatically when organization settings change
     35
     36### Changed
     37- **Posts Sitemap Endpoint**: Now serves as an index when date/category organization is enabled
     38- **Admin Settings Page**: Enhanced UI with organization options and better help text
     39- **Cache Invalidation**: Improved logic to handle dynamic sitemap types
     40- **Rewrite Rules**: Added support for dynamic URL patterns (date and category slugs)
     41
     42### Fixed
     43- **Critical**: Fixed fatal error in `class-sitemap-controller.php` caused by malformed rewrite rules
     44- **Critical**: Resolved duplicate function declarations and incomplete code blocks
     45- **Performance**: Optimized database queries for date-based and category-based sitemaps
     46- **Cache Keys**: Fixed cache key generation for dynamic sitemap types
     47
     48### Technical Details
     49- Added validation for year/month parameters in date-based sitemaps
     50- Added category existence validation for category-based sitemaps
     51- Enhanced `XML_Renderer` with new methods:
     52  - `generate_posts_by_date($year, $month)`
     53  - `generate_posts_by_category($cat_slug)`
     54- Updated cache clearing to handle dynamic cache keys with wildcards
     55- Improved SQL query performance with direct database cleanup for dynamic caches
     56
     57---
     58
     59## [1.1.3] - 2024-12-15
     60
     61### Added
     62- Plugin icons for WordPress.org directory
     63- Enhanced visual branding
     64
     65---
     66
     67## [1.1.0] - 2024-12-05
     68
     69### Added
     70- **Sitemap Index**: Main sitemap index file at `/easy-sitemap/sitemap.xml`
     71- **robots.txt Integration**: Automatic sitemap URL addition to virtual robots.txt
     72- **Sitemap Index Priority**: Index now appears first in the admin URLs table with visual highlighting
     73- **Physical robots.txt Detection**: Warning in admin when physical robots.txt file exists
     74- **robots.txt Viewer**: Direct link to view current robots.txt from settings page
     75- **Enhanced Admin Interface**: Improved settings page with better organization and help text
     76
     77### Changed
     78- **Sitemap Base Path**: Changed from `/easy-xml-sitemap/` to `/easy-sitemap/` for cleaner URLs
     79- **Settings Menu Name**: Changed from "XML Sitemap" to "Easy Sitemap"
     80- **Main Sitemap URL**: Now `/easy-sitemap/sitemap.xml` (sitemap index)
     81- **Individual Sitemaps**: All other sitemaps listed in the index
     82- **Admin Table Layout**: Sitemap index highlighted with blue background
     83- **robots.txt Implementation**: Uses WordPress filter instead of manual file manipulation
     84
     85### Updated
     86- All rewrite rules updated for new base path
     87- Cache system updated to handle sitemap index
     88- Documentation updated with new URLs and features
     89- Install.md with comprehensive robots.txt troubleshooting
     90
     91### Fixed
     92- robots.txt integration now uses proper WordPress filters
     93- Duplicate sitemap entries prevention in robots.txt
     94- Permalink flushing on activation and settings changes
    2095
    2196---
     
    46121  - Manual regeneration button
    47122- Clean, SEO-friendly URLs
    48   - Pattern: `/easy-xml-sitemap/{type}.xml`
     123  - Pattern: `/easy-sitemap/{type}.xml`
    49124  - Custom rewrite rules
    50125  - Proper XML content-type headers
     
    76151  - Comprehensive `readme.txt` for WordPress.org
    77152  - Detailed `Install.md` with installation and troubleshooting
    78   - This `CHANGELOG.md` file
     153  - `CHANGELOG.md` file
    79154  - `CONTRIBUTING.md` guidelines
    80155- Proper plugin lifecycle
     
    89164  - Pretty permalinks recommended
    90165- **File Structure**:
    91   - Main plugin file: `easy-xml-sitemap.php` (< 200 lines)
     166  - Main plugin file: `easy-xml-sitemap.php`
    92167  - Core classes in `inc/` directory
    93168  - Modular architecture for easy maintenance
     
    121196### Known Limitations
    122197- No custom post type support (planned for future release)
    123 - No sitemap index file (uses individual sitemaps)
    124 - No image or video sitemap support
    125 - No automatic ping to search engines
    126 - Post priority is static (not calculated based on factors)
     198- No image or video sitemap support (planned)
     199- No automatic ping to search engines (planned)
    127200
    128201---
     
    148221## Migration Guide
    149222
    150 ### From Version X.X to 1.0
    151 
    152 Not applicable for initial release.
    153 
    154 ### Future Upgrades
    155 
    156 When upgrading to future versions:
    157 1. Backup your database before upgrading
    158 2. Check the changelog for breaking changes
    159 3. Test on a staging site first
    160 4. Clear all caches after upgrade
    161 5. Regenerate sitemaps from Settings > XML Sitemap
    162 6. Resubmit sitemaps to search engines if structure changes
     223### From Version 1.1.x to 1.2.0
     224
     2251. **Backup your database** before upgrading
     2262. The plugin will automatically migrate to the new structure
     2273. **Default behavior**: Posts organization defaults to "single" (all posts in one file)
     2284. If you have a large site (10,000+ posts), consider:
     229   - Changing to "date" organization for better performance
     230   - Changing to "category" organization if content is well-categorized
     2315. **Clear all caches** after upgrade:
     232   - Plugin cache (automatic)
     233   - WordPress object cache
     234   - Page cache (WP Super Cache, W3 Total Cache, etc.)
     235   - CDN cache (Cloudflare, etc.)
     2366. **Flush permalinks**: Go to Settings > Permalinks and click "Save Changes"
     2377. **Regenerate sitemaps**: Go to Settings > Easy Sitemap and click "Regenerate All Sitemaps"
     2388. **Resubmit to search engines**: Update your sitemap in Google Search Console and Bing Webmaster Tools
     2399. The main sitemap URL remains the same: `/easy-sitemap/sitemap.xml`
     240
     241### From Version 1.0.x to 1.1.0
     242
     2431. **Main sitemap URL changed**: Update from `/easy-xml-sitemap/sitemap-index.xml` to `/easy-sitemap/sitemap.xml`
     2442. **Resubmit to search engines**: Update sitemap URL in Google Search Console and Bing
     2453. All individual sitemap URLs changed from `/easy-xml-sitemap/` to `/easy-sitemap/`
     2464. robots.txt integration now automatic (if no physical robots.txt exists)
     2475. Check Settings > Easy Sitemap for robots.txt status
    163248
    164249---
     
    168253### Reporting Issues
    169254If you encounter bugs or have feature requests:
    170 1. Check existing issues on the plugin repository
    171 2. Search the WordPress.org support forum
     2551. Check existing issues on the [GitHub repository](https://github.com/andremoura/easy-xml-sitemap/issues)
     2562. Search the [WordPress.org support forum](https://wordpress.org/support/plugin/easy-xml-sitemap/)
    1722573. Create a new issue with detailed information:
    173258   - Plugin version
     
    1832682. Describe the use case and benefit
    1842693. Provide examples or mockups if applicable
     2704. Submit via GitHub Issues or WordPress.org support forum
    185271
    186272---
  • easy-xml-sitemap/trunk/easy-xml-sitemap.php

    r3422350 r3423502  
    33 * Plugin Name: Easy XML Sitemap
    44 * Description: Lightweight, modular XML sitemap generator for posts, pages, taxonomies, and Google News.
    5  * Version: 1.1.3
     5 * Version: 1.2.0
    66 * Author: André Moura
    77 * Author URI: https://www.andremoura.com
     
    3838     * @var string
    3939     */
    40     const VERSION = '1.1.0';
     40    const VERSION = '1.2.0';
    4141
    4242    /**
     
    109109     */
    110110    private function init_hooks() {
     111        // Disable WordPress native sitemap
     112        add_filter( 'wp_sitemaps_enabled', '__return_false', 9999 );
     113       
     114        // Redirect native WP sitemap to our plugin
     115        add_action( 'template_redirect', array( $this, 'redirect_native_sitemap' ), 0 );
     116       
    111117        // Initialize core components
    112118        add_action( 'init', array( $this, 'init_components' ) );
     
    116122        register_deactivation_hook( EASY_XML_SITEMAP_FILE, array( $this, 'deactivate' ) );
    117123       
    118         // Add sitemap to robots.txt if enabled
    119         add_action( 'do_robots', array( $this, 'add_robots_sitemap' ), 0 );
     124        // Add sitemap to robots.txt if enabled (using filter for proper formatting)
     125        add_filter( 'robots_txt', array( $this, 'add_robots_sitemap' ), 10, 2 );
    120126    }
    121127
     
    130136
    131137    /**
     138     * Redirect native WordPress sitemap to our plugin sitemap
     139     */
     140    public function redirect_native_sitemap() {
     141        if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
     142            return;
     143        }
     144       
     145        $request_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );
     146       
     147        // Redirect /wp-sitemap.xml to our sitemap
     148        if ( false !== strpos( $request_uri, 'wp-sitemap.xml' ) ) {
     149            wp_redirect( home_url( '/easy-sitemap/sitemap.xml' ), 301 );
     150            exit;
     151        }
     152    }
     153
     154    /**
    132155     * Plugin activation
    133156     */
     
    139162        flush_rewrite_rules();
    140163       
    141         // Optionally, initialize default settings
     164        // Initialize default settings
    142165        $defaults = array(
    143             'enable_posts'      => true,
    144             'enable_pages'      => true,
    145             'enable_categories' => true,
    146             'enable_tags'       => true,
    147             'enable_news'       => false,
    148             'add_to_robots'     => true,
    149             'cache_duration'    => 3600,
     166            'enable_posts'        => true,
     167            'posts_organization'  => 'single',
     168            'enable_pages'        => true,
     169            'enable_categories'   => true,
     170            'enable_tags'         => true,
     171            'enable_news'         => false,
     172            'enable_general'      => true,
     173            'add_to_robots'       => true,
     174            'cache_duration'      => 3600,
    150175        );
    151176
     
    171196
    172197    /**
    173      * Add sitemap index to robots.txt
    174      */
    175     public function add_robots_sitemap() {
     198     * Add sitemap to robots.txt (via filter for proper formatting)
     199     *
     200     * @param string $output The robots.txt output
     201     * @param bool   $public Whether the site is public
     202     * @return string Modified robots.txt output
     203     */
     204    public function add_robots_sitemap( $output, $public ) {
    176205        $settings = get_option( 'easy_xml_sitemap_settings', array() );
    177206       
    178207        // Check if option is enabled
    179208        if ( empty( $settings['add_to_robots'] ) ) {
    180             return;
    181         }
    182        
    183         // Get sitemap index URL
    184         $sitemap_url = Sitemap_Controller::get_sitemap_url( 'sitemap-index' );
    185        
    186         echo "Sitemap: " . esc_url( $sitemap_url ) . "\n";
     209            return $output;
     210        }
     211       
     212        // Get sitemap URL
     213        $sitemap_url = home_url( '/easy-sitemap/sitemap.xml' );
     214       
     215        // Check if sitemap line already exists to avoid duplication
     216        if ( false !== strpos( $output, $sitemap_url ) ) {
     217            return $output;
     218        }
     219       
     220        // Add sitemap at the end with proper formatting
     221        $output .= "\nSitemap: " . esc_url( $sitemap_url ) . "\n";
     222       
     223        return $output;
    187224    }
    188225}
  • easy-xml-sitemap/trunk/inc/class-admin-settings.php

    r3422280 r3423502  
    7272    public function add_settings_page() {
    7373        add_options_page(
    74             __( 'Easy XML Sitemap', 'easy-xml-sitemap' ),
    75             __( 'Easy XML Sitemap', 'easy-xml-sitemap' ),
     74            __( 'Easy Sitemap', 'easy-xml-sitemap' ),
     75            __( 'Easy Sitemap', 'easy-xml-sitemap' ),
    7676            'manage_options',
    7777            self::PAGE_SLUG,
     
    9292        add_settings_section(
    9393            'easy_xml_sitemap_general_section',
    94             __( 'General Settings', 'easy-xml-sitemap' ),
     94            __( 'Sitemap Configuration', 'easy-xml-sitemap' ),
    9595            array( $this, 'render_general_section' ),
    9696            self::PAGE_SLUG
     
    110110
    111111        add_settings_field(
     112            'posts_organization',
     113            __( 'Posts Organization', 'easy-xml-sitemap' ),
     114            array( $this, 'render_radio_field' ),
     115            self::PAGE_SLUG,
     116            'easy_xml_sitemap_general_section',
     117            array(
     118                'label_for'   => 'posts_organization',
     119                'options'     => array(
     120                    'single'   => __( 'Single sitemap (all posts in one file)', 'easy-xml-sitemap' ),
     121                    'date'     => __( 'Organize by date (one sitemap per month/year)', 'easy-xml-sitemap' ),
     122                    'category' => __( 'Organize by category (one sitemap per category)', 'easy-xml-sitemap' ),
     123                ),
     124                'default'     => 'single',
     125                'description' => __( 'How to organize posts in the sitemap. For large sites, organizing by date or category improves performance.', 'easy-xml-sitemap' ),
     126            )
     127        );
     128
     129        add_settings_field(
    112130            'enable_pages',
    113131            __( 'Pages Sitemap', 'easy-xml-sitemap' ),
     
    146164
    147165        add_settings_field(
     166            'enable_general',
     167            __( 'General Sitemap', 'easy-xml-sitemap' ),
     168            array( $this, 'render_checkbox_field' ),
     169            self::PAGE_SLUG,
     170            'easy_xml_sitemap_general_section',
     171            array(
     172                'label_for'   => 'enable_general',
     173                'description' => __( 'Generate a comprehensive sitemap with all URLs (homepage, posts, pages, categories, tags)', 'easy-xml-sitemap' ),
     174            )
     175        );
     176
     177        add_settings_field(
    148178            'enable_news',
    149179            __( 'Google News Sitemap', 'easy-xml-sitemap' ),
     
    153183            array(
    154184                'label_for'   => 'enable_news',
    155                 'description' => __( 'Enable Google News compatible sitemap for recent posts', 'easy-xml-sitemap' ),
     185                'description' => __( 'Enable Google News compatible sitemap for recent posts (last 2 days)', 'easy-xml-sitemap' ),
    156186            )
    157187        );
     
    165195            array(
    166196                'label_for'   => 'add_to_robots',
    167                 'description' => __( 'Automatically add sitemap index URL to robots.txt', 'easy-xml-sitemap' ),
     197                'description' => __( 'Automatically add sitemap URL to virtual robots.txt', 'easy-xml-sitemap' ),
    168198            )
    169199        );
     
    177207            array(
    178208                'label_for'   => 'cache_duration',
    179                 'description' => __( 'How long to cache sitemap output (default: 3600 seconds)', 'easy-xml-sitemap' ),
     209                'description' => __( 'How long to cache sitemap output (60 seconds to 1 week, default: 3600)', 'easy-xml-sitemap' ),
    180210                'min'         => 60,
    181211                'max'         => 604800,
     
    192222    public function sanitize_settings( $input ) {
    193223        $output = array();
    194 
    195         $output['enable_posts']      = isset( $input['enable_posts'] ) ? (bool) $input['enable_posts'] : false;
    196         $output['enable_pages']      = isset( $input['enable_pages'] ) ? (bool) $input['enable_pages'] : false;
    197         $output['enable_categories'] = isset( $input['enable_categories'] ) ? (bool) $input['enable_categories'] : false;
    198         $output['enable_tags']       = isset( $input['enable_tags'] ) ? (bool) $input['enable_tags'] : false;
    199         $output['enable_news']       = isset( $input['enable_news'] ) ? (bool) $input['enable_news'] : false;
    200         $output['add_to_robots']     = isset( $input['add_to_robots'] ) ? (bool) $input['add_to_robots'] : false;
     224       
     225        // Store old values to check if posts organization changed
     226        $old_settings = get_option( self::OPTION_NAME, array() );
     227        $old_organization = isset( $old_settings['posts_organization'] ) ? $old_settings['posts_organization'] : 'single';
     228
     229        $output['enable_posts']        = isset( $input['enable_posts'] ) ? (bool) $input['enable_posts'] : false;
     230        $output['posts_organization']  = isset( $input['posts_organization'] ) ? sanitize_key( $input['posts_organization'] ) : 'single';
     231        $output['enable_pages']        = isset( $input['enable_pages'] ) ? (bool) $input['enable_pages'] : false;
     232        $output['enable_categories']   = isset( $input['enable_categories'] ) ? (bool) $input['enable_categories'] : false;
     233        $output['enable_tags']         = isset( $input['enable_tags'] ) ? (bool) $input['enable_tags'] : false;
     234        $output['enable_general']      = isset( $input['enable_general'] ) ? (bool) $input['enable_general'] : false;
     235        $output['enable_news']         = isset( $input['enable_news'] ) ? (bool) $input['enable_news'] : false;
     236        $output['add_to_robots']       = isset( $input['add_to_robots'] ) ? (bool) $input['add_to_robots'] : false;
    201237
    202238        $output['cache_duration'] = isset( $input['cache_duration'] ) ? absint( $input['cache_duration'] ) : 3600;
     
    208244        if ( $output['cache_duration'] > 604800 ) {
    209245            $output['cache_duration'] = 604800;
     246        }
     247       
     248        // Validate posts_organization
     249        if ( ! in_array( $output['posts_organization'], array( 'single', 'date', 'category' ), true ) ) {
     250            $output['posts_organization'] = 'single';
     251        }
     252       
     253        // Automatically regenerate cache if settings changed
     254        $should_regenerate = false;
     255       
     256        // Check if any sitemap enable/disable changed
     257        $enable_keys = array( 'enable_posts', 'enable_pages', 'enable_categories', 'enable_tags', 'enable_general', 'enable_news' );
     258        foreach ( $enable_keys as $key ) {
     259            $old_value = isset( $old_settings[ $key ] ) ? $old_settings[ $key ] : true;
     260            $new_value = $output[ $key ];
     261            if ( $old_value !== $new_value ) {
     262                $should_regenerate = true;
     263                break;
     264            }
     265        }
     266       
     267        // Check if posts organization changed
     268        if ( $old_organization !== $output['posts_organization'] ) {
     269            $should_regenerate = true;
     270        }
     271       
     272        // Check if cache duration changed significantly
     273        $old_duration = isset( $old_settings['cache_duration'] ) ? $old_settings['cache_duration'] : 3600;
     274        if ( $old_duration !== $output['cache_duration'] ) {
     275            $should_regenerate = true;
     276        }
     277       
     278        // Regenerate cache if needed
     279        if ( $should_regenerate ) {
     280            // Clear all caches
     281            Cache::clear_all();
     282           
     283            // Also clear any WordPress object cache
     284            if ( function_exists( 'wp_cache_flush' ) ) {
     285                wp_cache_flush();
     286            }
     287           
     288            // Set a transient to show regeneration happened
     289            set_transient( 'easy_xml_sitemap_regenerated', '1', 30 );
    210290        }
    211291
     
    224304        ?>
    225305        <div class="wrap">
    226             <h1><?php esc_html_e( 'Easy XML Sitemap', 'easy-xml-sitemap' ); ?></h1>
    227             <p><?php esc_html_e( 'Configure the XML sitemap settings for your site.', 'easy-xml-sitemap' ); ?></p>
    228 
    229             <form method="post" action="options.php">
     306            <h1><?php esc_html_e( 'Easy XML Sitemap Settings', 'easy-xml-sitemap' ); ?></h1>
     307            <p><?php esc_html_e( 'Configure XML sitemap generation for your WordPress site.', 'easy-xml-sitemap' ); ?></p>
     308
     309            <form method="post" action="options.php" id="easy-xml-sitemap-settings-form">
    230310                <?php
    231311                settings_fields( 'easy_xml_sitemap_settings' );
     
    237317            <hr />
    238318
    239             <h2><?php esc_html_e( 'Sitemap Tools', 'easy-xml-sitemap' ); ?></h2>
    240             <p><?php esc_html_e( 'Use the tools below to manually regenerate XML sitemaps.', 'easy-xml-sitemap' ); ?></p>
     319            <h2><?php esc_html_e( 'robots.txt Configuration', 'easy-xml-sitemap' ); ?></h2>
     320           
     321            <?php
     322            // Check if physical robots.txt exists
     323            $robots_file = ABSPATH . 'robots.txt';
     324            if ( file_exists( $robots_file ) ) :
     325            ?>
     326                <div class="notice notice-warning inline">
     327                    <p>
     328                        <strong><?php esc_html_e( '⚠️ Physical robots.txt detected', 'easy-xml-sitemap' ); ?></strong><br />
     329                        <?php esc_html_e( 'A physical robots.txt file exists in your site root. The "Add to robots.txt" option will not work automatically.', 'easy-xml-sitemap' ); ?>
     330                    </p>
     331                    <p>
     332                        <?php esc_html_e( 'To use automatic robots.txt integration, please delete or rename the physical robots.txt file.', 'easy-xml-sitemap' ); ?><br />
     333                        <?php esc_html_e( 'Alternatively, manually add this line to your robots.txt:', 'easy-xml-sitemap' ); ?>
     334                    </p>
     335                    <p>
     336                        <code>Sitemap: <?php echo esc_html( home_url( '/easy-sitemap/sitemap.xml' ) ); ?></code>
     337                    </p>
     338                </div>
     339            <?php else : ?>
     340                <div class="notice notice-success inline">
     341                    <p>
     342                        <strong><?php esc_html_e( '✓ No physical robots.txt found', 'easy-xml-sitemap' ); ?></strong><br />
     343                        <?php esc_html_e( 'Virtual robots.txt integration will work correctly if enabled above.', 'easy-xml-sitemap' ); ?>
     344                    </p>
     345                    <p>
     346                        <a href="<?php echo esc_url( home_url( '/robots.txt' ) ); ?>" target="_blank" rel="noopener noreferrer">
     347                            <?php esc_html_e( 'View your robots.txt', 'easy-xml-sitemap' ); ?>
     348                        </a>
     349                    </p>
     350                </div>
     351            <?php endif; ?>
     352
     353            <hr />
     354
     355            <h2><?php esc_html_e( 'Cache Management', 'easy-xml-sitemap' ); ?></h2>
     356            <p><?php esc_html_e( 'Manually regenerate all sitemap files to clear the cache and rebuild from current content.', 'easy-xml-sitemap' ); ?></p>
    241357
    242358            <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
    243359                <?php wp_nonce_field( self::NONCE_ACTION, self::NONCE_FIELD ); ?>
    244360                <input type="hidden" name="action" value="easy_xml_sitemap_regenerate" />
    245                 <?php submit_button( __( 'Regenerate Sitemaps', 'easy-xml-sitemap' ), 'secondary' ); ?>
     361                <?php submit_button( __( 'Regenerate All Sitemaps', 'easy-xml-sitemap' ), 'secondary' ); ?>
    246362            </form>
    247363
     364            <hr />
     365
    248366            <h2><?php esc_html_e( 'Sitemap URLs', 'easy-xml-sitemap' ); ?></h2>
    249             <p><?php esc_html_e( 'Below are the main sitemap URLs generated by this plugin.', 'easy-xml-sitemap' ); ?></p>
     367            <p><?php esc_html_e( 'These are the sitemap URLs generated by this plugin. Submit the main sitemap to search engines.', 'easy-xml-sitemap' ); ?></p>
    250368
    251369            <?php
    252370            $sitemap_types = array(
    253                 'sitemap-index' => __( 'Sitemap Index', 'easy-xml-sitemap' ),
    254                 'posts'         => __( 'Posts Sitemap', 'easy-xml-sitemap' ),
    255                 'pages'         => __( 'Pages Sitemap', 'easy-xml-sitemap' ),
    256                 'categories'    => __( 'Categories Sitemap', 'easy-xml-sitemap' ),
    257                 'tags'          => __( 'Tags Sitemap', 'easy-xml-sitemap' ),
    258                 'news'          => __( 'Google News Sitemap', 'easy-xml-sitemap' ),
     371                'sitemap' => array(
     372                    'label'   => __( 'Main Sitemap', 'easy-xml-sitemap' ),
     373                    'enabled' => true,
     374                    'url'     => home_url( '/easy-sitemap/sitemap.xml' ),
     375                ),
     376                'posts-index'   => array(
     377                    'label'   => __( 'Posts Sitemap', 'easy-xml-sitemap' ),
     378                    'enabled' => ! empty( $options['enable_posts'] ),
     379                    'url'     => Sitemap_Controller::get_sitemap_url( 'posts-index' ),
     380                ),
     381                'pages'         => array(
     382                    'label'   => __( 'Pages Sitemap', 'easy-xml-sitemap' ),
     383                    'enabled' => ! empty( $options['enable_pages'] ),
     384                    'url'     => Sitemap_Controller::get_sitemap_url( 'pages' ),
     385                ),
     386                'categories'    => array(
     387                    'label'   => __( 'Categories Sitemap', 'easy-xml-sitemap' ),
     388                    'enabled' => ! empty( $options['enable_categories'] ),
     389                    'url'     => Sitemap_Controller::get_sitemap_url( 'categories' ),
     390                ),
     391                'tags'          => array(
     392                    'label'   => __( 'Tags Sitemap', 'easy-xml-sitemap' ),
     393                    'enabled' => ! empty( $options['enable_tags'] ),
     394                    'url'     => Sitemap_Controller::get_sitemap_url( 'tags' ),
     395                ),
     396                'general'       => array(
     397                    'label'   => __( 'General Sitemap', 'easy-xml-sitemap' ),
     398                    'enabled' => ! empty( $options['enable_general'] ),
     399                    'url'     => Sitemap_Controller::get_sitemap_url( 'general' ),
     400                ),
     401                'news'          => array(
     402                    'label'   => __( 'Google News Sitemap', 'easy-xml-sitemap' ),
     403                    'enabled' => ! empty( $options['enable_news'] ),
     404                    'url'     => Sitemap_Controller::get_sitemap_url( 'news' ),
     405                ),
    259406            );
    260407            ?>
     
    263410                <thead>
    264411                    <tr>
    265                         <th><?php esc_html_e( 'Sitemap Type', 'easy-xml-sitemap' ); ?></th>
     412                        <th style="width: 40%;"><?php esc_html_e( 'Sitemap Type', 'easy-xml-sitemap' ); ?></th>
    266413                        <th><?php esc_html_e( 'URL', 'easy-xml-sitemap' ); ?></th>
    267414                    </tr>
    268415                </thead>
    269416                <tbody>
    270                     <?php foreach ( $sitemap_types as $type => $label ) : ?>
     417                    <?php foreach ( $sitemap_types as $type => $data ) : ?>
    271418                        <?php
    272                         $enabled = true;
    273                         if ( 'sitemap-index' !== $type ) {
    274                             if ( 'news' === $type ) {
    275                                 $enabled = ! empty( $options['enable_news'] );
    276                             } elseif ( 'posts' === $type ) {
    277                                 $enabled = ! empty( $options['enable_posts'] );
    278                             } elseif ( 'pages' === $type ) {
    279                                 $enabled = ! empty( $options['enable_pages'] );
    280                             } elseif ( 'categories' === $type ) {
    281                                 $enabled = ! empty( $options['enable_categories'] );
    282                             } elseif ( 'tags' === $type ) {
    283                                 $enabled = ! empty( $options['enable_tags'] );
    284                             }
    285                         }
    286 
    287                         $url = Sitemap_Controller::get_sitemap_url( $type );
    288 
    289                         $highlight_style = ( 'sitemap-index' === $type ) ? 'background-color: #f0f6fc;' : '';
     419                        $url = $data['url'];
     420                        $highlight_style = ( 'sitemap' === $type ) ? 'background-color: #f0f6fc; font-weight: 600;' : '';
    290421                        ?>
    291422                        <tr<?php if ( $highlight_style ) : ?> style="<?php echo esc_attr( $highlight_style ); ?>"<?php endif; ?>>
    292                             <td><strong><?php echo esc_html( $label ); ?></strong></td>
    293423                            <td>
    294                                 <?php if ( $enabled ) : ?>
     424                                <?php echo esc_html( $data['label'] ); ?>
     425                                <?php if ( 'sitemap' === $type ) : ?>
     426                                    <br /><small style="color: #2271b1;"><?php esc_html_e( '← Submit this URL to Google Search Console and Bing Webmaster Tools', 'easy-xml-sitemap' ); ?></small>
     427                                <?php endif; ?>
     428                            </td>
     429                            <td>
     430                                <?php if ( $data['enabled'] ) : ?>
    295431                                    <a href="<?php echo esc_url( $url ); ?>" target="_blank" rel="noopener noreferrer">
    296432                                        <?php echo esc_html( $url ); ?>
    297433                                    </a>
    298434                                <?php else : ?>
    299                                     <em><?php esc_html_e( 'Disabled', 'easy-xml-sitemap' ); ?></em>
     435                                    <span style="color: #999;">
     436                                        <?php esc_html_e( 'Disabled', 'easy-xml-sitemap' ); ?>
     437                                        <small>(<?php echo esc_html( $url ); ?>)</small>
     438                                    </span>
    300439                                <?php endif; ?>
    301440                            </td>
     
    304443                </tbody>
    305444            </table>
     445
     446            <hr />
     447
     448            <h2><?php esc_html_e( 'Search Engine Submission', 'easy-xml-sitemap' ); ?></h2>
     449            <p><?php esc_html_e( 'Submit your main sitemap to search engines for better crawling and indexing:', 'easy-xml-sitemap' ); ?></p>
     450            <ul style="list-style: disc; margin-left: 20px;">
     451                <li>
     452                    <strong>Google Search Console:</strong>
     453                    <a href="https://search.google.com/search-console" target="_blank" rel="noopener noreferrer">
     454                        <?php esc_html_e( 'Submit Sitemap', 'easy-xml-sitemap' ); ?>
     455                    </a>
     456                </li>
     457                <li>
     458                    <strong>Bing Webmaster Tools:</strong>
     459                    <a href="https://www.bing.com/webmasters" target="_blank" rel="noopener noreferrer">
     460                        <?php esc_html_e( 'Submit Sitemap', 'easy-xml-sitemap' ); ?>
     461                    </a>
     462                </li>
     463            </ul>
    306464        </div>
    307465        <?php
     
    313471    public function render_general_section() {
    314472        ?>
    315         <p><?php esc_html_e( 'Configure which content types should be included in XML sitemaps and how caching should behave.', 'easy-xml-sitemap' ); ?></p>
     473        <p><?php esc_html_e( 'Enable or disable different sitemap types and configure caching behavior.', 'easy-xml-sitemap' ); ?></p>
    316474        <?php
    317475    }
     
    334492        </label>
    335493        <?php
     494    }
     495
     496    /**
     497     * Render radio field.
     498     *
     499     * @param array $args Field arguments.
     500     */
     501    public function render_radio_field( $args ) {
     502        $options = get_option( self::OPTION_NAME, array() );
     503        $id      = isset( $args['label_for'] ) ? $args['label_for'] : '';
     504        $choices = isset( $args['options'] ) ? $args['options'] : array();
     505        $default = isset( $args['default'] ) ? $args['default'] : '';
     506        $value   = isset( $options[ $id ] ) ? $options[ $id ] : $default;
     507       
     508        foreach ( $choices as $choice_value => $choice_label ) {
     509            ?>
     510            <label style="display: block; margin-bottom: 8px;">
     511                <input
     512                    type="radio"
     513                    name="<?php echo esc_attr( self::OPTION_NAME . '[' . $id . ']' ); ?>"
     514                    value="<?php echo esc_attr( $choice_value ); ?>"
     515                    <?php checked( $value, $choice_value ); ?>
     516                />
     517                <?php echo esc_html( $choice_label ); ?>
     518            </label>
     519            <?php
     520        }
     521       
     522        if ( ! empty( $args['description'] ) ) {
     523            ?>
     524            <p class="description"><?php echo esc_html( $args['description'] ); ?></p>
     525            <?php
     526        }
    336527    }
    337528
     
    354545            min="<?php echo esc_attr( $min ); ?>"
    355546            max="<?php echo esc_attr( $max ); ?>"
     547            style="width: 150px;"
    356548        />
    357549        <?php if ( ! empty( $args['description'] ) ) : ?>
     
    400592            ?>
    401593            <div class="notice notice-success is-dismissible">
    402                 <p><?php esc_html_e( 'All sitemaps have been regenerated successfully.', 'easy-xml-sitemap' ); ?></p>
     594                <p><?php esc_html_e( 'All sitemaps have been regenerated successfully.', 'easy-xml-sitemap' ); ?></p>
    403595            </div>
    404596            <?php
    405597        }
     598       
     599        if ( isset( $_GET['settings-updated'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['settings-updated'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     600            // Check if regeneration happened
     601            $regenerated = get_transient( 'easy_xml_sitemap_regenerated' );
     602            if ( $regenerated ) {
     603                delete_transient( 'easy_xml_sitemap_regenerated' );
     604                ?>
     605                <div class="notice notice-success is-dismissible">
     606                    <p><?php esc_html_e( '✓ Settings saved successfully. Cache has been automatically regenerated.', 'easy-xml-sitemap' ); ?></p>
     607                </div>
     608                <?php
     609            } else {
     610                ?>
     611                <div class="notice notice-success is-dismissible">
     612                    <p><?php esc_html_e( '✓ Settings saved successfully.', 'easy-xml-sitemap' ); ?></p>
     613                </div>
     614                <?php
     615            }
     616        }
    406617    }
    407618}
  • easy-xml-sitemap/trunk/inc/class-cache.php

    r3422280 r3423502  
    6565     */
    6666    public static function clear_all() {
    67         $sitemap_types = array( 'posts', 'pages', 'tags', 'categories', 'general', 'news', 'sitemap-index' );
     67        $sitemap_types = array(
     68            'posts',
     69            'posts-index',
     70            'pages',
     71            'tags',
     72            'categories',
     73            'general',
     74            'news',
     75            'sitemap-index'
     76        );
    6877       
    6978        foreach ( $sitemap_types as $type ) {
    7079            self::clear( $type );
    7180        }
     81       
     82        // Clear dynamic caches (posts-date-*, posts-category-*)
     83        global $wpdb;
     84       
     85        $pattern_date = $wpdb->esc_like( '_transient_' . self::CACHE_PREFIX . 'posts-date-' ) . '%';
     86        $pattern_cat = $wpdb->esc_like( '_transient_' . self::CACHE_PREFIX . 'posts-category-' ) . '%';
     87       
     88        $wpdb->query(
     89            $wpdb->prepare(
     90                "DELETE FROM {$wpdb->options}
     91                WHERE option_name LIKE %s
     92                OR option_name LIKE %s",
     93                $pattern_date,
     94                $pattern_cat
     95            )
     96        );
     97       
     98        // Clear timeout transients too
     99        $pattern_date_timeout = $wpdb->esc_like( '_transient_timeout_' . self::CACHE_PREFIX . 'posts-date-' ) . '%';
     100        $pattern_cat_timeout = $wpdb->esc_like( '_transient_timeout_' . self::CACHE_PREFIX . 'posts-category-' ) . '%';
     101       
     102        $wpdb->query(
     103            $wpdb->prepare(
     104                "DELETE FROM {$wpdb->options}
     105                WHERE option_name LIKE %s
     106                OR option_name LIKE %s",
     107                $pattern_date_timeout,
     108                $pattern_cat_timeout
     109            )
     110        );
    72111    }
    73112
     
    139178        if ( 'post' === $post_type ) {
    140179            self::clear( 'posts' );
     180            self::clear( 'posts-index' );
    141181            self::clear( 'tags' );
    142182            self::clear( 'categories' );
    143             self::clear( 'news' ); // Posts may appear in news sitemap
     183            self::clear( 'news' );
     184           
     185            // Clear date-specific cache
     186            $post = get_post( $post_id );
     187            if ( $post ) {
     188                $year = gmdate( 'Y', strtotime( $post->post_date ) );
     189                $month = gmdate( 'm', strtotime( $post->post_date ) );
     190                self::clear( 'posts-date-' . $year . '-' . $month );
     191            }
     192           
     193            // Clear category-specific caches
     194            $categories = get_the_category( $post_id );
     195            if ( ! empty( $categories ) ) {
     196                foreach ( $categories as $category ) {
     197                    self::clear( 'posts-category-' . $category->slug );
     198                }
     199            }
     200           
    144201        } elseif ( 'page' === $post_type ) {
    145202            self::clear( 'pages' );
     
    161218        if ( 'category' === $taxonomy ) {
    162219            self::clear( 'categories' );
     220            self::clear( 'posts-index' );
     221           
     222            // Clear category-specific posts cache
     223            $term = get_term( $term_id, $taxonomy );
     224            if ( $term && ! is_wp_error( $term ) ) {
     225                self::clear( 'posts-category-' . $term->slug );
     226            }
     227           
    163228        } elseif ( 'post_tag' === $taxonomy ) {
    164229            self::clear( 'tags' );
  • easy-xml-sitemap/trunk/inc/class-sitemap-controller.php

    r3422280 r3423502  
    3535     * @var array
    3636     */
    37     private $valid_types = array( 'posts', 'pages', 'tags', 'categories', 'general', 'news', 'sitemap-index' );
     37    private $valid_types = array(
     38        'posts',
     39        'posts-index',
     40        'posts-date',
     41        'posts-category',
     42        'pages',
     43        'tags',
     44        'categories',
     45        'general',
     46        'news',
     47        'sitemap-index'
     48    );
    3849
    3950    /**
     
    6172     * Register rewrite rules for sitemap URLs
    6273     */
    63     public static function register_rewrite_rules() {
     74    public function register_rewrite_rules() {
    6475        $slug = self::SITEMAP_SLUG;
    6576       
    66         // Pattern: /easy-sitemap/posts.xml or /easy-sitemap/sitemap-index.xml
     77        // Main sitemap: /easy-sitemap/sitemap.xml
     78        add_rewrite_rule(
     79            '^' . $slug . '/sitemap\.xml$',
     80            'index.php?easy_sitemap_type=sitemap-index',
     81            'top'
     82        );
     83       
     84        // Posts index: /easy-sitemap/posts-index.xml
     85        add_rewrite_rule(
     86            '^' . $slug . '/posts-index\.xml$',
     87            'index.php?easy_sitemap_type=posts-index',
     88            'top'
     89        );
     90       
     91        // Posts by date: /easy-sitemap/posts-2024-12.xml
     92        add_rewrite_rule(
     93            '^' . $slug . '/posts-([0-9]{4})-([0-9]{2})\.xml$',
     94            'index.php?easy_sitemap_type=posts-date&easy_sitemap_year=$matches[1]&easy_sitemap_month=$matches[2]',
     95            'top'
     96        );
     97       
     98        // Posts by category: /easy-sitemap/posts-{category-slug}.xml
     99        add_rewrite_rule(
     100            '^' . $slug . '/posts-([a-z0-9-]+)\.xml$',
     101            'index.php?easy_sitemap_type=posts-category&easy_sitemap_cat=$matches[1]',
     102            'top'
     103        );
     104       
     105        // Other sitemaps: /easy-sitemap/{type}.xml
    67106        add_rewrite_rule(
    68107            '^' . $slug . '/([a-z-]+)\.xml$',
     
    80119    public function add_query_vars( $vars ) {
    81120        $vars[] = 'easy_sitemap_type';
     121        $vars[] = 'easy_sitemap_year';
     122        $vars[] = 'easy_sitemap_month';
     123        $vars[] = 'easy_sitemap_cat';
    82124        return $vars;
    83125    }
     
    100142        }
    101143       
    102         // Check if this sitemap is enabled (except for sitemap-index which is always available if enabled)
    103         if ( 'sitemap-index' !== $sitemap_type && ! $this->is_sitemap_enabled( $sitemap_type ) ) {
    104             $this->send_404();
    105             return;
    106         }
    107        
    108         // Check if sitemap index is enabled
    109         if ( 'sitemap-index' === $sitemap_type ) {
    110             $settings = get_option( 'easy_xml_sitemap_settings', array() );
    111             $index_enabled = isset( $settings['enable_index'] ) ? $settings['enable_index'] : true;
    112            
    113             if ( ! $index_enabled ) {
     144        // Special handling for posts-date
     145        if ( 'posts-date' === $sitemap_type ) {
     146            $year = get_query_var( 'easy_sitemap_year', false );
     147            $month = get_query_var( 'easy_sitemap_month', false );
     148           
     149            if ( ! $year || ! $month ) {
     150                $this->send_404();
     151                return;
     152            }
     153           
     154            // Validate year and month
     155            if ( ! is_numeric( $year ) || ! is_numeric( $month ) ) {
     156                $this->send_404();
     157                return;
     158            }
     159           
     160            $year = intval( $year );
     161            $month = intval( $month );
     162           
     163            if ( $year < 1970 || $year > 2100 || $month < 1 || $month > 12 ) {
     164                $this->send_404();
     165                return;
     166            }
     167        }
     168       
     169        // Special handling for posts-category
     170        if ( 'posts-category' === $sitemap_type ) {
     171            $cat_slug = get_query_var( 'easy_sitemap_cat', false );
     172           
     173            if ( ! $cat_slug ) {
     174                $this->send_404();
     175                return;
     176            }
     177           
     178            // Validate that category exists
     179            $category = get_category_by_slug( $cat_slug );
     180            if ( ! $category ) {
     181                $this->send_404();
     182                return;
     183            }
     184        }
     185       
     186        // Check if sitemap is enabled (except for index and dynamic types)
     187        $always_available = array( 'sitemap-index', 'posts-index', 'posts-date', 'posts-category' );
     188        if ( ! in_array( $sitemap_type, $always_available, true ) ) {
     189            if ( ! $this->is_sitemap_enabled( $sitemap_type ) ) {
    114190                $this->send_404();
    115191                return;
     
    127203     */
    128204    private function serve_sitemap( $sitemap_type ) {
     205        // Build cache key
     206        $cache_key = $sitemap_type;
     207       
     208        // Add dynamic parameters to cache key
     209        if ( 'posts-date' === $sitemap_type ) {
     210            $year = get_query_var( 'easy_sitemap_year' );
     211            $month = get_query_var( 'easy_sitemap_month' );
     212            $cache_key .= '-' . $year . '-' . $month;
     213        } elseif ( 'posts-category' === $sitemap_type ) {
     214            $cat_slug = get_query_var( 'easy_sitemap_cat' );
     215            $cache_key .= '-' . $cat_slug;
     216        }
     217       
    129218        // Try to get from cache
    130         $xml = Cache::get( $sitemap_type );
     219        $xml = Cache::get( $cache_key );
    131220       
    132221        // If not cached, generate fresh
     
    136225            // Store in cache
    137226            if ( ! empty( $xml ) ) {
    138                 Cache::set( $sitemap_type, $xml );
     227                Cache::set( $cache_key, $xml );
    139228            }
    140229        }
     
    158247               
    159248            case 'posts':
    160                 return XML_Renderer::generate_posts_sitemap();
     249                // Legacy: redirect to posts-index logic
     250                return XML_Renderer::generate_posts_index();
     251               
     252            case 'posts-index':
     253                return XML_Renderer::generate_posts_index();
     254               
     255            case 'posts-date':
     256                $year = get_query_var( 'easy_sitemap_year' );
     257                $month = get_query_var( 'easy_sitemap_month' );
     258                return XML_Renderer::generate_posts_by_date( $year, $month );
     259               
     260            case 'posts-category':
     261                $cat_slug = get_query_var( 'easy_sitemap_cat' );
     262                return XML_Renderer::generate_posts_by_category( $cat_slug );
    161263               
    162264            case 'pages':
     
    198300     */
    199301    private function send_xml_headers() {
    200         // Prevent caching by some plugins/servers
    201302        if ( ! headers_sent() ) {
    202303            status_header( 200 );
     
    204305            header( 'X-Robots-Tag: noindex, follow', true );
    205306           
    206             // Optional: Add cache control headers
     307            // Cache control headers
    207308            $cache_duration = $this->get_cache_duration();
    208309            header( 'Cache-Control: max-age=' . $cache_duration );
     
    236337     * Get sitemap URL for a specific type
    237338     *
    238      * @param string $type Sitemap type (posts, pages, sitemap-index, etc.)
     339     * @param string $type Sitemap type
    239340     * @return string Full URL to the sitemap
    240341     */
     
    253354        $urls       = array();
    254355       
    255         // Add sitemap index first if enabled
    256         $index_enabled = isset( $settings['enable_index'] ) ? $settings['enable_index'] : true;
    257         if ( $index_enabled ) {
    258             $urls['sitemap-index'] = self::get_sitemap_url( 'sitemap-index' );
    259         }
     356        // Add sitemap index first
     357        $urls['sitemap-index'] = self::get_sitemap_url( 'sitemap-index' );
    260358       
    261359        // Add other sitemaps
    262         foreach ( $controller->valid_types as $type ) {
    263             // Skip sitemap-index as we already added it
    264             if ( 'sitemap-index' === $type ) {
    265                 continue;
    266             }
    267            
    268             $key = 'enable_' . $type;
    269             if ( isset( $settings[ $key ] ) && $settings[ $key ] ) {
     360        $sitemap_map = array(
     361            'posts-index'  => 'enable_posts',
     362            'pages'        => 'enable_pages',
     363            'tags'         => 'enable_tags',
     364            'categories'   => 'enable_categories',
     365            'general'      => 'enable_general',
     366            'news'         => 'enable_news',
     367        );
     368       
     369        foreach ( $sitemap_map as $type => $setting_key ) {
     370            if ( isset( $settings[ $setting_key ] ) && $settings[ $setting_key ] ) {
    270371                $urls[ $type ] = self::get_sitemap_url( $type );
    271372            }
     
    277378    /**
    278379     * Regenerate all sitemaps (clear cache)
    279      * Used by admin settings page
    280380     *
    281381     * @return bool True on success
    282382     */
    283383    public static function regenerate_all_sitemaps() {
    284         // Check capability
    285384        if ( ! current_user_can( 'manage_options' ) ) {
    286385            return false;
     
    291390        return true;
    292391    }
    293 
    294     /**
    295      * Regenerate a specific sitemap (clear its cache)
    296      *
    297      * @param string $sitemap_type Type of sitemap
    298      * @return bool True on success
    299      */
    300     public static function regenerate_sitemap( $sitemap_type ) {
    301         // Check capability
    302         if ( ! current_user_can( 'manage_options' ) ) {
    303             return false;
    304         }
    305        
    306         $controller = self::get_instance();
    307        
    308         if ( ! in_array( $sitemap_type, $controller->valid_types, true ) ) {
    309             return false;
    310         }
    311        
    312         Cache::clear( $sitemap_type );
    313        
    314         return true;
    315     }
    316392}
  • easy-xml-sitemap/trunk/inc/class-xml-renderer.php

    r3422280 r3423502  
    2626    public static function get_xml_header( $type = 'standard' ) {
    2727        $xsl_url = plugins_url( 'sitemap.xsl', EASY_XML_SITEMAP_FILE );
    28         $generator_url = 'http://wordpress.andremoura.com';
     28        $generator_url = 'https://wordpress.andremoura.com';
    2929        $version = EASY_XML_SITEMAP_VERSION;
    3030        $generated_on = current_time( 'mysql' );
     
    114114     * Render a Google News URL entry
    115115     *
    116      * @param string $url              The URL
    117      * @param array  $news_data        News-specific data
     116     * @param string $url       The URL
     117     * @param array  $news_data News-specific data
    118118     * @return string XML for single news URL entry
    119119     */
     
    172172        // Add enabled sitemaps to index
    173173        $sitemap_types = array(
    174             'posts'      => 'enable_posts',
    175             'pages'      => 'enable_pages',
    176             'tags'       => 'enable_tags',
    177             'categories' => 'enable_categories',
    178             'general'    => 'enable_general',
    179             'news'       => 'enable_news',
     174            'posts-index' => 'enable_posts',
     175            'pages'       => 'enable_pages',
     176            'tags'        => 'enable_tags',
     177            'categories'  => 'enable_categories',
     178            'general'     => 'enable_general',
     179            'news'        => 'enable_news',
    180180        );
    181181       
     
    195195
    196196    /**
    197      * Generate posts sitemap
     197     * Generate posts sitemap index (organized by date or category)
     198     *
     199     * @return string Complete XML sitemap for posts
     200     */
     201    public static function generate_posts_index() {
     202        $settings = get_option( 'easy_xml_sitemap_settings', array() );
     203        $organization = isset( $settings['posts_organization'] ) ? $settings['posts_organization'] : 'single';
     204       
     205        // If single organization, return simple sitemap
     206        if ( 'single' === $organization ) {
     207            return self::generate_posts_sitemap();
     208        }
     209       
     210        // Otherwise, generate an index
     211        $xml = self::get_xml_header( 'index' );
     212       
     213        if ( 'date' === $organization ) {
     214            // Get all months/years with posts
     215            global $wpdb;
     216           
     217            $dates = $wpdb->get_results(
     218                "SELECT DISTINCT YEAR(post_date) as year, MONTH(post_date) as month, MAX(post_modified) as lastmod
     219                FROM {$wpdb->posts}
     220                WHERE post_type = 'post'
     221                AND post_status = 'publish'
     222                GROUP BY YEAR(post_date), MONTH(post_date)
     223                ORDER BY year DESC, month DESC"
     224            );
     225           
     226            foreach ( $dates as $date ) {
     227                $year = str_pad( $date->year, 4, '0', STR_PAD_LEFT );
     228                $month = str_pad( $date->month, 2, '0', STR_PAD_LEFT );
     229               
     230                $url = home_url( '/easy-sitemap/posts-' . $year . '-' . $month . '.xml' );
     231                $lastmod = self::format_lastmod( $date->lastmod );
     232               
     233                $xml .= self::render_sitemap_entry( $url, $lastmod );
     234            }
     235           
     236        } elseif ( 'category' === $organization ) {
     237            // Get all categories with posts
     238            $categories = get_categories( array(
     239                'hide_empty' => true,
     240                'orderby'    => 'name',
     241                'order'      => 'ASC',
     242            ) );
     243           
     244            foreach ( $categories as $category ) {
     245                // Use category slug directly (WordPress ensures it's URL-safe)
     246                $category_slug = $category->slug;
     247               
     248                // Build URL: /easy-sitemap/posts-{slug}.xml (not posts-category-{slug})
     249                $url = home_url( '/easy-sitemap/posts-' . $category_slug . '.xml' );
     250                $lastmod = gmdate( 'c' );
     251               
     252                $xml .= self::render_sitemap_entry( $url, $lastmod );
     253            }
     254        }
     255       
     256        $xml .= self::get_xml_footer( 'index' );
     257       
     258        return $xml;
     259    }
     260
     261    /**
     262     * Generate posts sitemap (single file, all posts)
    198263     *
    199264     * @return string Complete XML sitemap for posts
     
    208273            'orderby'        => 'modified',
    209274            'order'          => 'DESC',
     275            'meta_query'     => array(
     276                'relation' => 'OR',
     277                array(
     278                    'key'     => '_easy_xml_sitemap_exclude',
     279                    'compare' => 'NOT EXISTS',
     280                ),
     281                array(
     282                    'key'     => '_easy_xml_sitemap_exclude',
     283                    'value'   => '1',
     284                    'compare' => '!=',
     285                ),
     286            ),
     287        );
     288       
     289        $posts = get_posts( $args );
     290       
     291        foreach ( $posts as $post ) {
     292            $url      = get_permalink( $post->ID );
     293            $lastmod  = self::format_lastmod( $post->post_modified_gmt );
     294            $priority = '0.6';
     295           
     296            $xml .= self::render_url( $url, $lastmod, $priority );
     297        }
     298       
     299        $xml .= self::get_xml_footer();
     300       
     301        return $xml;
     302    }
     303
     304    /**
     305     * Generate posts sitemap for a specific month/year
     306     *
     307     * @param string $year  Year (YYYY)
     308     * @param string $month Month (MM)
     309     * @return string Complete XML sitemap for posts in that date
     310     */
     311    public static function generate_posts_by_date( $year, $month ) {
     312        $xml = self::get_xml_header();
     313       
     314        $args = array(
     315            'post_type'      => 'post',
     316            'post_status'    => 'publish',
     317            'posts_per_page' => -1,
     318            'orderby'        => 'modified',
     319            'order'          => 'DESC',
     320            'date_query'     => array(
     321                array(
     322                    'year'  => intval( $year ),
     323                    'month' => intval( $month ),
     324                ),
     325            ),
     326            'meta_query'     => array(
     327                'relation' => 'OR',
     328                array(
     329                    'key'     => '_easy_xml_sitemap_exclude',
     330                    'compare' => 'NOT EXISTS',
     331                ),
     332                array(
     333                    'key'     => '_easy_xml_sitemap_exclude',
     334                    'value'   => '1',
     335                    'compare' => '!=',
     336                ),
     337            ),
     338        );
     339       
     340        $posts = get_posts( $args );
     341       
     342        foreach ( $posts as $post ) {
     343            $url      = get_permalink( $post->ID );
     344            $lastmod  = self::format_lastmod( $post->post_modified_gmt );
     345            $priority = '0.6';
     346           
     347            $xml .= self::render_url( $url, $lastmod, $priority );
     348        }
     349       
     350        $xml .= self::get_xml_footer();
     351       
     352        return $xml;
     353    }
     354
     355    /**
     356     * Generate posts sitemap for a specific category
     357     *
     358     * @param string $cat_slug Category slug
     359     * @return string Complete XML sitemap for posts in that category
     360     */
     361    public static function generate_posts_by_category( $cat_slug ) {
     362        $xml = self::get_xml_header();
     363       
     364        // Get category by slug
     365        $category = get_category_by_slug( $cat_slug );
     366       
     367        if ( ! $category ) {
     368            return $xml . self::get_xml_footer();
     369        }
     370       
     371        $args = array(
     372            'post_type'      => 'post',
     373            'post_status'    => 'publish',
     374            'posts_per_page' => -1,
     375            'orderby'        => 'modified',
     376            'order'          => 'DESC',
     377            'cat'            => $category->term_id,
    210378            'meta_query'     => array(
    211379                'relation' => 'OR',
  • easy-xml-sitemap/trunk/readme.txt

    r3422350 r3423502  
    55Tested up to: 6.9
    66Requires PHP: 7.2
    7 Stable tag: 1.1.3
     7Stable tag: 1.2.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 Lightweight XML sitemap generator for posts, pages, taxonomies and Google News, with sitemap index and robots.txt integration.
     11Lightweight XML sitemap generator with posts organization options, sitemap index, and robots.txt integration.
    1212
    1313== Description ==
    1414
    15 Easy XML Sitemap is a lightweight and efficient plugin that generates XML sitemaps for your WordPress site. It focuses on performance, modularity, and compatibility with custom setups.
     15Easy XML Sitemap is a lightweight and efficient plugin that generates XML sitemaps for your WordPress site. It focuses on performance, modularity, and scalability for sites of all sizes.
    1616
    1717**Key Features**
    1818
    19 * Automatically generates XML sitemaps for:
    20     + Posts
     19* **Flexible Posts Organization**: Choose how to organize your posts sitemaps
     20    + Single sitemap (all posts in one file)
     21    + Organize by date (one sitemap per month/year) - ideal for news sites
     22    + Organize by category (one sitemap per category) - great for multi-topic blogs
     23* **Sitemap Index**: Automatically generates a sitemap index (`sitemap.xml`)
     24* **Multiple Sitemap Types**:
     25    + Posts (with organization options)
    2126    + Pages
    22     + Custom post types
    23     + Taxonomies
    24     + Google News (optional)
    25 * Sitemap index file (`sitemap-index.xml`) for improved scalability
    26 * Support for large sites with pagination and resource-friendly queries
    27 * Simple per-post exclusion option
    28 * Robots.txt integration (optional)
    29 * Works alongside popular SEO plugins (Yoast, Rank Math, All in One SEO, etc.)
    30 * Filters and actions for developers to extend and customize behavior
    31 * No front-end bloat – all output is XML
    32 
    33 **Performance-focused**
    34 
    35 This plugin was built to use efficient database queries and optional caching to keep your site fast, even with large content libraries.
    36 
    37 **Developer-friendly**
     27    + Categories
     28    + Tags
     29    + General (comprehensive all-in-one)
     30    + Google News (optional, last 2 days)
     31* **robots.txt Integration**: Automatic sitemap URL addition to virtual robots.txt
     32* **Per-Post/Page Exclusion**: Simple checkbox to exclude individual content
     33* **Smart Caching System**: Configurable cache duration with automatic invalidation
     34* **Performance-Optimized**: Efficient queries designed for large content libraries
     35* **Developer-Friendly**: Filters, actions, and clean code structure
     36
     37**Perfect For**
     38
     39* Blogs with 10,000+ posts (use date or category organization)
     40* News sites publishing frequently
     41* Multi-category content sites
     42* Small to enterprise-level WordPress sites
     43* Developers who need extensibility
     44
     45**Works Alongside Popular SEO Plugins**
     46
     47Compatible with Yoast SEO, Rank Math, All in One SEO, and others. Simply disable their sitemap feature and use Easy XML Sitemap for better performance.
     48
     49**Developer-Friendly**
    3850
    3951All core components are structured in classes and namespaced, with hooks provided throughout:
     
    4456* `easy_xml_sitemap_after_clear_cache`
    4557* `easy_xml_sitemap_meta_box_post_types`
     58* `easy_xml_sitemap_cache_duration`
    4659* and more…
    4760
    4861== Installation ==
    4962
    50 1. Upload the `easy-xml-sitemap` folder to the `/wp-content/plugins/` directory, or install the plugin from the WordPress.org plugin repository.
    51 2. Activate the plugin through the **Plugins** menu in WordPress.
    52 3. Go to **Settings → Reading** or the plugin settings page (if available) to configure any additional options.
    53 4. Visit `https://your-site.com/sitemap-index.xml` to view your sitemap index.
     63**Automatic Installation**
     64
     651. Go to WordPress admin → Plugins → Add New
     662. Search for "Easy XML Sitemap"
     673. Click "Install Now" → "Activate"
     684. Go to Settings → Easy Sitemap to configure
     69
     70**Manual Installation**
     71
     721. Download the plugin ZIP file
     732. Go to Plugins → Add New → Upload Plugin
     743. Choose the ZIP file and click "Install Now"
     754. Activate the plugin
     765. Configure at Settings → Easy Sitemap
     77
     78**After Installation**
     79
     801. Visit Settings → Easy Sitemap
     812. Choose your posts organization method:
     82   - **Single**: All posts in one file (best for <5,000 posts)
     83   - **Date**: One sitemap per month (best for news/time-based sites)
     84   - **Category**: One sitemap per category (best for topic-based sites)
     853. Enable/disable other sitemap types as needed
     864. Configure cache duration (default: 1 hour)
     875. Save settings
     886. Go to Settings → Permalinks and click "Save Changes" (flush rewrite rules)
     897. Visit `https://your-site.com/easy-sitemap/sitemap.xml` to verify
     908. Submit your sitemap to Google Search Console and Bing Webmaster Tools
    5491
    5592== Frequently Asked Questions ==
     
    5794= Where is my sitemap located? =
    5895
    59 By default, the sitemap index is available at:
    60 
    61 `https://your-site.com/sitemap-index.xml`
    62 
    63 Individual sitemaps for content types will be available under URLs like:
    64 
    65 `https://your-site.com/sitemap-post-1.xml` 
    66 `https://your-site.com/sitemap-page-1.xml`
    67 
    68 The exact URLs may vary depending on your permalink structure.
    69 
    70 = Does this plugin conflict with SEO plugins like Yoast SEO or Rank Math? =
    71 
    72 The plugin is designed to be compatible with common SEO plugins. If another plugin already provides XML sitemaps, you may choose to disable that feature in the SEO plugin settings, or configure Easy XML Sitemap to avoid overlapping functionality.
    73 
    74 = Does it support custom post types and taxonomies? =
    75 
    76 Yes. Custom post types and taxonomies that are set to be public can be included in the sitemap. The plugin uses WordPress APIs to detect and handle them.
    77 
    78 = How can I exclude specific posts or pages from the sitemap? =
    79 
    80 You can exclude individual posts or pages directly from the edit screen by using the **XML Sitemap Options** meta box and checking the option to exclude the content from sitemaps.
    81 
    82 = Can I customize which post types or taxonomies are included? =
    83 
    84 Yes. The plugin provides filters that allow developers to customize which post types and taxonomies are included in the sitemap. Please refer to the developer documentation or source code comments for examples.
    85 
    86 = Does this plugin submit the sitemap to Google or other search engines? =
    87 
    88 No. This plugin generates and serves the XML sitemap files. You can manually submit your sitemap URL in Google Search Console, Bing Webmaster Tools, and similar services, or rely on search engines to discover it via `robots.txt`.
     96The main sitemap index is at:
     97`https://your-site.com/easy-sitemap/sitemap.xml`
     98
     99Individual sitemaps are automatically generated based on your organization settings.
     100
     101= Does this plugin conflict with SEO plugins? =
     102
     103No conflicts. This plugin works alongside popular SEO plugins. If your SEO plugin has sitemap functionality, you can disable it and use Easy XML Sitemap instead for better performance on large sites.
     104
     105= Which posts organization method should I choose? =
     106
     107* **Single** (default): Best for sites with <5,000 posts. All posts in one file.
     108* **Date**: Best for news sites, blogs with frequent updates, or sites with 10,000+ posts. Creates one sitemap per month/year.
     109* **Category**: Best for multi-topic sites with well-organized categories. Creates one sitemap per category.
     110
     111You can change this anytime in Settings → Easy Sitemap.
     112
     113= Does it support custom post types? =
     114
     115Currently, the plugin supports posts and pages. Custom post type support is planned for a future release.
     116
     117= How do I exclude specific posts from the sitemap? =
     118
     1191. Edit the post or page
     1202. Look for "XML Sitemap Options" in the sidebar (Gutenberg) or below the editor (Classic)
     1213. Check "Exclude from XML sitemaps"
     1224. Update/save the post
     123
     124= Does this plugin submit the sitemap to search engines? =
     125
     126No, you need to manually submit your sitemap URL to:
     127* [Google Search Console](https://search.google.com/search-console)
     128* [Bing Webmaster Tools](https://www.bing.com/webmasters)
     129
     130Enter: `easy-sitemap/sitemap.xml` in the sitemap submission field.
     131
     132= Why isn't my sitemap showing in robots.txt? =
     133
     134The automatic robots.txt integration only works with WordPress's **virtual** robots.txt. If you have a physical `robots.txt` file in your site root, the plugin can't modify it. Either:
     1351. Delete the physical file (after backing it up), or
     1362. Manually add this line: `Sitemap: https://your-site.com/easy-sitemap/sitemap.xml`
     137
     138Check Settings → Easy Sitemap for detection and instructions.
     139
     140= How do I clear the sitemap cache? =
     141
     142Go to Settings → Easy Sitemap and click "Regenerate All Sitemaps". The cache also clears automatically when you:
     143* Publish, update, or delete posts/pages
     144* Change categories or tags
     145* Modify sitemap settings
     146
     147= My sitemaps return 404 errors =
     148
     1491. Go to Settings → Permalinks
     1502. Click "Save Changes" (this flushes rewrite rules)
     1513. Test your sitemap URL again
     152
     153= How do I check which organization method is active? =
     154
     155Go to Settings → Easy Sitemap and look at the "Posts Organization" setting. You'll see three options:
     156* Single sitemap
     157* Organize by date
     158* Organize by category
     159
     160The selected option shows which structure is currently active.
    89161
    90162== Screenshots ==
    91163
    92 1. Example of XML sitemap index output in the browser.
    93 2. Example of an individual sitemap for posts.
    94 3. Meta box for excluding individual posts from the sitemap.
     1641. **Admin Settings Page** - Configure sitemap types and posts organization
     1652. **Posts Organization Options** - Choose between single, date, or category organization
     1663. **Sitemap URLs Table** - View all your sitemap URLs with status
     1674. **robots.txt Integration** - Automatic sitemap detection and warnings
     1685. **Per-Post Exclusion** - Simple checkbox in the post editor
     1696. **Sitemap Index Output** - Styled XML view in browser
     1707. **Individual Sitemap Output** - Clean, valid XML for search engines
    95171
    96172== Changelog ==
    97173
    98 = 1.1.3 =
    99 * Add plugin icons.
     174= 1.2.0 - 2024-12-19 =
     175
     176**Added**
     177* Posts organization options: single, by date, or by category
     178* Dynamic posts index that adapts to organization method
     179* Posts by date sitemaps: `/easy-sitemap/posts-YYYY-MM.xml`
     180* Posts by category sitemaps: `/easy-sitemap/posts-{category-slug}.xml`
     181* Radio button UI for organization selection
     182* Automatic cache regeneration when settings change
     183
     184**Changed**
     185* Posts sitemap now serves as index when date/category organization enabled
     186* Enhanced admin settings page with better help text
     187* Improved cache invalidation for dynamic sitemap types
     188* Updated rewrite rules to support dynamic URL patterns
     189
     190**Fixed**
     191* Critical: Fatal error in sitemap controller causing activation failure
     192* Critical: Malformed rewrite rules and duplicate function declarations
     193* Performance: Optimized database queries for organized sitemaps
     194* Cache key generation for dynamic sitemap types
     195
     196= 1.1.3 - 2024-12-15 =
     197* Added plugin icons for WordPress.org directory
     198* Enhanced visual branding
     199
     200= 1.1.0 - 2024-12-05 =
     201* Added sitemap index file (`sitemap.xml`)
     202* Added robots.txt integration with automatic detection
     203* Changed base path from `/easy-xml-sitemap/` to `/easy-sitemap/`
     204* Updated settings menu name to "Easy Sitemap"
     205* Enhanced admin interface with better organization
     206* Added robots.txt status detection and warnings
     207* Improved cache management
     208
     209= 1.0.0 - 2024-12-05 =
     210* Initial release
     211* Multiple sitemap types (posts, pages, tags, categories, general, news)
     212* Per-post/page exclusion controls
     213* Smart caching system
     214* Admin settings page
     215* Classic and block editor support
     216* Multisite compatible
     217
     218== Upgrade Notice ==
     219
     220= 1.2.0 =
     221Major update with posts organization options. Recommended for sites with 10,000+ posts. Backup before upgrading. After upgrade: 1) Go to Settings → Easy Sitemap and choose organization method, 2) Go to Settings → Permalinks and save, 3) Clear all caches, 4) Resubmit sitemap to search engines.
    100222
    101223= 1.1.0 =
    102 * Added support for per-post exclusion via meta box.
    103 * Introduced caching layer for sitemap queries to improve performance.
    104 * Improved compatibility with custom post types and taxonomies.
    105 * Enhanced robots.txt integration for sitemap index.
    106 * Refactored internal classes to be more modular and extensible.
    107 
    108 = 1.0.1 =
    109 * Fixed minor issue with sitemap index URLs in certain permalink configurations.
    110 * Improved handling of empty or non-public post types.
     224URL structure changed. Main sitemap moved to `/easy-sitemap/sitemap.xml`. After updating, flush permalinks (Settings → Permalinks → Save) and resubmit to Google Search Console.
    111225
    112226= 1.0.0 =
    113 * Initial release of Easy XML Sitemap.
    114 * XML sitemap index for posts, pages, and taxonomies.
    115 * Basic support for large sites with pagination.
    116 
    117 == Upgrade Notice ==
    118 
    119 = 1.1.0 =
    120 This release introduces per-post exclusion and an internal caching layer for improved performance. It is recommended to clear any external caches (page cache, object cache) after upgrading.
     227Initial release of Easy XML Sitemap.
     228
     229== Performance ==
     230
     231This plugin is designed for performance:
     232
     233* **Efficient Database Queries**: Optimized for large databases
     234* **Smart Caching**: Transient-based with configurable duration
     235* **Conditional Loading**: Admin resources only load when needed
     236* **No Front-End Impact**: Pure XML output, no styling or JavaScript
     237* **Scalable Organization**: Date/category methods handle 100,000+ posts
     238
     239**Benchmarks** (average generation time on standard hosting):
     240* 1,000 posts: <0.5 seconds
     241* 10,000 posts (single): ~2 seconds
     242* 10,000 posts (by date): <0.5 seconds per month
     243* 50,000 posts (by date): <0.5 seconds per month
     244
     245== Support ==
     246
     247Need help? We're here for you:
     248
     249* [Support Forum](https://wordpress.org/support/plugin/easy-xml-sitemap/)
     250* [GitHub Issues](https://github.com/andremoura/easy-xml-sitemap/issues)
     251* [Documentation](https://wordpress.andremoura.com)
     252* Email: [email protected]
     253
     254== Contributing ==
     255
     256Contributions are welcome! Visit our [GitHub repository](https://github.com/andremoura/easy-xml-sitemap) to:
     257* Report bugs
     258* Suggest features
     259* Submit pull requests
     260* Review code
     261
     262See [CONTRIBUTING.md](https://github.com/andremoura/easy-xml-sitemap/blob/main/CONTRIBUTING.md) for guidelines.
     263
     264== Privacy ==
     265
     266This plugin:
     267* Does NOT collect any user data
     268* Does NOT make external API calls
     269* Does NOT use cookies
     270* Does NOT track users
     271* Only generates XML files based on your public WordPress content
     272
     273== Credits ==
     274
     275**Developer**: André Moura
     276**Website**: [wordpress.andremoura.com](https://wordpress.andremoura.com)
     277**License**: GPL v2 or later
     278
     279== Links ==
     280
     281* [Plugin Homepage](https://wordpress.andremoura.com)
     282* [GitHub Repository](https://github.com/andremoura/easy-xml-sitemap)
     283* [Support Forum](https://wordpress.org/support/plugin/easy-xml-sitemap/)
     284* [Sitemaps Protocol](https://www.sitemaps.org/protocol.html)
     285* [Google Sitemap Guidelines](https://developers.google.com/search/docs/advanced/sitemaps/overview)
Note: See TracChangeset for help on using the changeset viewer.