Archive permalinks

The really easy way: Change your permalink structure to include a “front” part. Eg. /news/%postname%

The slightly more complicated and inflexible way: WordPress holds it’s data permastruct in a property of the global WP_Rewrite object stored in $wp_rewrite. You can hook in sometime early-ish (around init) and change this.

<?php
add_action('init', 'wpse39274_change_date');
function wpse39274_change_date()
{
    global $wp_rewrite;
    $wp_rewrite->date_structure="/news/%year%/%monthnum%/%day%";
}

Inflexible because you’ve hard-coded the date base (news). This works fine, and if you don’t need the flexibility and want a quick fix, you should do this. Just be sure to flush your rewrite rules: visit Settings > Permalinks and hit save.

The totally awesome, but a little bit complex way: Add a field to the permalinks options page where you can specify the date base.

A class to wrap things up in.

<?php
class Custom_Date_Base
{
    const SETTING = 'custom_date_base';

    private static $ins = null;

    public static function init()
    {
        add_action('plugins_loaded', array(self::instance(), '_setup'));
    }

    public static function instance()
    {
        is_null(self::$ins) && self::$ins = new self;
        return self::$ins;
    }

    public function _setup()
    {
        // we'll hook stuff in here later
    }
}

Now we need to hook into admin_init and use the settings API to add our field to the “Optional” section. We only need to use add_settings_field because Permalink options doesn’t actually use the settings API.

<?php
class Custom_Date_Base
{
    const SETTING = 'custom_date_base';

    private static $ins = null;

    public static function init()
    {
        add_action('plugins_loaded', array(self::instance(), '_setup'));
    }

    public static function instance()
    {
        is_null(self::$ins) && self::$ins = new self;
        return self::$ins;
    }

    public function _setup()
    {
        add_action('admin_init', array($this, 'settings'));
    }

    public function settings()
    {
        add_settings_field(
            'custom-date-base',
            __('Date Base', 'custom-date-base'),
            array($this, 'field_cb'),
            'permalink',
            'optional',
            array('label_for' => self::SETTING)
        );
    }

    public function field_cb()
    {
        printf(
            '<input type="text" class="regular-text" id="%1$s" name="%1$s" value="%2$s" />',
            esc_attr(self::SETTING),
            esc_attr(get_option(self::SETTING))
        );
    }
}

We also need to hook into load-options-permalink.php to save stuff (due to the lack of settings API support).

<?php
class Custom_Date_Base
{
    // snip snip

    public function _setup()
    {
        add_action('admin_init', array($this, 'settings'));
        add_action('load-options-permalink.php', array($this, 'save'));
        add_action('init', array($this, 'set_date_base'));
    }

    // snip snip

    // apparently options-permalink only halfways uses the settings api?
    public function save()
    {
        // make sure it's actually an update request.
        if('POST' != $_SERVER['REQUEST_METHOD'])
            return;

        // since this fires before the actual update stuff,
        // validate the permalink nonce.
        check_admin_referer('update-permalink');

        if(!empty($_POST[self::SETTING]))
        {
            update_option(
                self::SETTING,
                sanitize_title_with_dashes($_POST[self::SETTING])
            );
        }
        else
        {
            // remove it.
            delete_option(self::SETTING);
        }
    }
}

And finally hook into init and change the data permastruct. We can piggy-back on some build in validation that WordPress does by using WP_Rewrite::get_date_permastruct. Then it’s just a matter of some regex replacement.

<?php
class Custom_Date_Base
{
    // snip snip

    public function _setup()
    {
        add_action('admin_init', array($this, 'settings'));
        add_action('load-options-permalink.php', array($this, 'save'));
        add_action('init', array($this, 'set_date_base'));
    }

    // snip snip

    public function set_date_base()
    {
        if($db = get_option(self::SETTING))
        {
            global $wp_rewrite;

            // current date permastruct
            $date_s = $wp_rewrite->get_date_permastruct();

            // get the "front" -- stuff before rewrite tags in post links
            $front = isset($wp_rewrite->front) ? $wp_rewrite->front : "https://wordpress.stackexchange.com/";

            // build some regex. We need to account for the global rewrite 
            // "front" as well as a possible "/date" that WP appends for
            // %post_id% permalink structure where the numbers of a Post ID
            // might conflict with with year/month/day numbers.
            $regex = '#^' . preg_quote($front, '#') . '(/date)?#ui';

            $wp_rewrite->date_structure = preg_replace($regex, "/{$db}/", $date_s);

            // apprently we need to make sure this happens?
            flush_rewrite_rules();
        }
    }
}

Here is the third option wrapped up in a plugin.

Leave a Comment