Need to Get Latest Archive with Custom Year/Month format

I have a code that I currently use; however, I need more customization.

$latestarchive = wp_get_archives([
    "type" => "monthly",
    "limit" => "1",
    "echo" => 0,
    "order" => "DESC"]);
echo $latestarchive;

This produced:

<a href="https://gp-cpdev/2021/04/">April 2021</a>

However, is there a way I can modify it in such a way as to produce the following code…

<a href="https://gp-cpdev/2021/04/">Home</a>

Or to output a year/month string such as 2021/04?

I would even want to display only a text such as “April 2021” that is not a link­—useful for pagination by month. Here’s an example (note that “April 2021” will not be a link:

Previous month    April 2021    Next month

Now if I could set a custom text for the latest year/month, I could figure out the pagination by month and go from there. My thinking is that the archives will be listed in an array format instead of outputting as HTML code. So then I could determine if it’s the first month or the last month in an array by determining an index of an array. Something like this:

$currentMonth = array_search("2021/04", $archiveByMonth); // Returns 1; "2021/04" would be from a URL.
if($currentMonth > 0)
  // Display the "Previous Month" link.
if($currentMonth < count($archiveByMonth)
  // Display the "Next Month" link.

So yeah, I think what I want is something similar to wp_get_archives($args) but for outputting as an array instead of HTML. I don’t care for the limit as I can get the string from the upper bound of an array.

After I did some digging, I started to think I need to write a custom function. I came across this web page:
https://core.trac.wordpress.org/browser/tags/4.1.1/src/wp-includes/general-template.php#L1321

And I modified the line of code to something like this:

function get_monthly_archive_array() {
    global $wpdb;
    $r['type'] = 'monthly';
    $where = apply_filters( 'getarchives_where',
        "WHERE post_type = 'post' AND post_status = 'publish'", $r );

    $last_changed = wp_cache_get( 'last_changed', 'posts' );
    if ( ! $last_changed ) {
        $last_changed = microtime();
        wp_cache_set( 'last_changed', $last_changed, 'posts' );
    }

    /**
     * Filter the SQL JOIN clause for retrieving archives.
     *
     * @since 2.2.0
     *
     * @param string $sql_join Portion of SQL query containing JOIN clause.
     * @param array  $r        An array of default arguments.
     */
    $join = apply_filters( 'getarchives_join', '', $r );

    $query = "SELECT YEAR(post_date) AS `year`, MONTH(post_date) AS `month`, count(ID) as posts FROM $wpdb->posts $join $where GROUP BY YEAR(post_date), MONTH(post_date) ORDER BY post_date ASC";
    $key = md5( $query );
    $key = "wp_get_archives:$key:$last_changed";
    if ( ! $results = wp_cache_get( $key, 'posts' ) ) {
        $results = $wpdb->get_results( $query );
        wp_cache_set( $key, $results, 'posts' );
    }
    if ( $results ) {
        return (array)$results;
    }
}

Is this the right approach or is there a similar function that would output as an array?

I am using my own custom theme, so I would like to avoid plugins whenever I can. I do know some SQL and I have experience with PHP, if that helps. So how can I go about setting a custom text as described above or when getting only the custom-formatted year and month for a URL?

UPDATE as of 2001/04/29 11:05 AM: I think I got it! The function I made from the wordpress.org webpage exposes the array such as this:

array(2) { [0]=> object(stdClass)#605 (3) { ["year"]=> string(4) "2021" ["month"]=> string(1) "2" 
["posts"]=> string(1) "6" } [1]=> object(stdClass)#606 (3) { ["year"]=> string(4) "2021" ["month"]=> 
string(1) "4" ["posts"]=> string(1) "2" } }

I believe I have answered my question. I then can go ahead and format the year and month to my heart’s content. Although the code from the WordPress website does not have a lot of comments describing how this all works including $key = "wp_get_archives:$key:$last_changed";.

UPDATE as of 11:13 AM: I also added the $join code. Maybe that’s needed as well.

2 Likes

Glad you got it working, this looks like a good start to me.

If you only need the previous and next months then you should consider limiting the query by post_date as well as the existing post_type and post_status conditions. This should ensure that the index KEY type_status_date (post_type,post_status,post_date,ID) on the posts table is used. Otherwise, what you have will probably work OK for small sites, but not for general use or for large sites with lots of posts.

The calls to apply_filters (including the $join code) are needed if a plugin on your site wants to modify the list of posts that is returned by that query.

This is for caching, specifically the object cache which can store the results of expensive operations (queries, etc) for a while. It’s not configured to do much by default but it’s a good performance booster for high-traffic sites. Read more here: Core Caching Concepts in WordPress

Since the cache key changes each time any part of the query changes, you don’t need to change that part.

1 Like

Okay, so let’s say I have a couple of months listed in the archive. Here’s an example:

  • November, 2020
  • December, 2020
  • January, 2021
  • February, 2021
  • March, 2021
  • May, 2021
  • June, 2021

And the reader is viewing my blog posts dated January, 2021. Taking the current month and year into account, how can I query the database so I can limit to three rows based on the current month and year? So for example:

  • Previous month: December, 2020
  • Current month: January, 2021
  • Next month: February, 2021

If June, 2021 is the current month a reader is currently viewing, then it would make sense to only display two rows instead (previous month and current month). For anything general, how would I accomplish that?

At least I do have the code for starters.

function showMonthYearLocale($monthYear) {
    global $wp_locale;
    return $wp_locale->get_month( $monthYear[1] ).', '.$monthYear[0];
}

function constructLinkFromYearMonth($array, $index, $nextMonth) {
    $offset = ($nextMonth === true) ? 1 : -1;
    $spanlsaquote =  ($offset === -1) ? "<span class='visualonly'>&lsaquo;&nbsp;</span>" : "";
    $spanrsaquote =  ($offset ===  1) ? "<span class='visualonly'>&nbsp;&rsaquo;</span>" : "";
    $prevNextMonth = ($offset === -1) ? "Previous" : "Next";
    echo "<a href='".get_month_link($array[$index + $offset]->year,$array[$index + $offset]->month )
        ."'>".$spanlsaquote."<span class='screenreader'>".$prevNextMonth." month: </span>"
        .showMonthYearLocale(
        [$array[$index + $offset]->year,$array[$index + $offset]->month]).$spanrsaquote."</a>";
}

function show_pagination() {
    // ...
    // Get the list of months and years from the archive in an array.
    $monthlyArchive = get_monthly_archive_array();
    // If there is year/month in URL, get it and trim the leading and
    // trailing slashes. Example: 2021/04
    $currentMonthYear = trim($_SERVER['REQUEST_URI'],'/');
    // array[0] = year, array[1] = month
    // Example: array[0] = "2021", array[1] = "04"
    $curMonthYearArray = explode('/',$currentMonthYear);
    // The two strings are put back together along with "/" in between for regex testing.
    if(preg_match("/^[0-9]{4}\/(0[1-9]|1[0-2])$/",$curMonthYearArray[0].'/'.$curMonthYearArray[1])) {
        // array[0] = year, array[1] = month
        // Example: array[0] = "2021", array[1] = "04"
        $curMonthYearArray = explode('/',$currentMonthYear);
        // Initialize a blank array for integers.
        $intCurMonthYearArray = Array();
        // Convert strings to integers in an array in a new variable.
        foreach($curMonthYearArray as $curMonthYear)
            $intCurMonthYearArray[] = (int)$curMonthYear;
        // Initialize the integer for the index.
        $indexOfMonthYearArray = 0;
        foreach ($monthlyArchive as $key => $val) {
           if ((int)$val->year === $intCurMonthYearArray[0] &&
               (int)$val->month === $intCurMonthYearArray[1]) {
               $indexOfMonthYearArray = $key;
           }
        }

        echo "<ul class='pagination_month'>";
        echo "<li class='month_current'>";
        echo "<a>".showMonthYearLocale($curMonthYearArray)."</a>";
        echo "</li>";
        if($indexOfMonthYearArray > 0) {
            echo "<li class='month_prev'>";
            constructLinkFromYearMonth($monthlyArchive, $indexOfMonthYearArray, false);
            echo "</li>";
        } else echo "<li class='month_prev smallfontsize'><a>Beginning of current month</a></li>";
        if($indexOfMonthYearArray + 1 < count($monthlyArchive)) {
            echo "<li class='month_next'>";
            constructLinkFromYearMonth($monthlyArchive, $indexOfMonthYearArray, true);
            echo "</li>";
        } else echo "<li class='month_next smallfontsize'><a>End of current month</a></li>";
        echo "</ul>";
    } else {
        $latest = $monthlyArchive[count($monthlyArchive) - 1];
        echo "<div class='pagination_month msg'><span>View latest posts since "
            ."<a href='".get_month_link($latest->year,$latest->month )
            ."'>".showMonthYearLocale([$latest->year,$latest->month])."</a></span>.</div>";
    }
    // ...
}

And here is what I came up with so far:

Update: I had to re-upload my screenshot without the portion of the email address exposed by the browser tab. Well, it’s too late, but I did it anyway… Hopefully I won’t embarrass myself again in the future.

If you really want to code this yourself, take a look at code that already works, like the Calendar widget. There are also some plugins for different looking archives, like
Adjacent Archive Links
In Over Your Archives
Snazzy Archives
Smart Archives Reloaded

Thanks. I actually like to code myself just for learning experience.

2 Likes