Component Based Carlos

Custom Archive Page with Search and Pagination

By: Carlos Cerda

Category:

You might be wondering what I am referring to when I say “custom archive page”. An archive in this context is referring to a collection of post type entries. As many of you may know, the traditional way of displaying an archive of any WordPress post type is by creating a php file named {post_slug}-archive.php in your theme’s root directory. For those of you who do not know what Im talking about, the folks over at WP Beginner have written an article on how to set something like that up here. While this is a perfectly ok way of displaying all of your post type’s entries on their own page alone, this method heavily limits the ability to add custom content on these pages.

The Limitations

Don’t get me wrong, using the traditional archive template does have its applications and it does them very well. For instance, when you want to have category archive pages or want a very structured setup for your post types, then nothing beats using the template system. But what if you want to simply drop in an archive of posts into a page, or better yet, you want to have the perks of having an archive layout with pagination and a search bar, but would like to also use a page builder to build out the rest of the page? The limitation of using the template system is that it limits the ability to really customize the page via the frontend. Page builders cannot be used with these templates so you are stuck with having to create the whole layout with code. While that isnt the end of the world, it does reduce the amount of customization that say, a client, may want or need. This is where the following approach really shines.

The Approach

The idea behind this custom archive page is to create a shortcode that can be used to create an entire archive of posts, while also throwing in the ability to have pagination, as well as a search field. Now a user can simply drop the shortcode into a page builder or a text editor and voila!

The Code

It all begins and ends with a shortcode. I wont go too much into detail about how shortcodes work, Ill leave that for  you to read about in the WordPress documentation here. I want to focus on the relevant code for this functionality. In your theme’s functions.php, you can drop something like this in:

add_shortcode('display-custom-posts-archive', 'display_custom_posts_archive');

function display_custom_posts_archive()
{
    $posts_per_page = get_option( 'posts_per_page' );
    $paged = sanitize_text_field($_GET['page_num']) ? (int)(sanitize_text_field($_GET['page_num'])) : 1;
    $search = sanitize_text_field($_GET['search']);
    $query_args = [
        'post_type'      => 'post',
        'posts_per_page' => $posts_per_page,
        'post_status'    => 'publish',
        'paged'          => $paged
    ];

    if($search) {
        $query_args['meta_query'] = [
            'relation'  => 'OR',
            [
                'key'   => 'meta1',
                'value' => $search,
                'compare' => 'LIKE'
            ],
            [
                'key'   => 'meta2',
                'value' => $search,
                'compare' => 'LIKE'
            ]         
        ];
    }

    $custom_query = new WP_Query($query_args);
    $posts_pages = $custom_query->max_num_pages;
    // creates the pagination related code
    $pagination_html = '';
    if($posts_pages && $posts_pages != 1) {
        $pagination_html .= "<div class='pagination'>";
        for($i=0;$i<$posts_pages;$i++) {
            $page_number = $i + 1;
            if($paged == $page_number) {
                $pagination_html .= "<div class='pagination-link current-page'>$page_number</div>";
                continue;
            }
            $redirectUrl = $_SERVER['REDIRECT_URL'];
            $queryArray = [];
            parse_str($_SERVER['REDIRECT_QUERY_STRING'],$queryArray);
            $queryArray['page_num'] = $page_number;
            $queryEncoded = http_build_query($queryArray);
            $pageUrl = $redirectUrl . '?' . $queryEncoded;
            $pagination_html .= "<div class='pagination-link'><a href='$pageUrl'>$page_number</a></div>";
        }
        $pagination_html .= "</div>";
    }
    $html = '';
    $formUrl = $_SERVER['REQUEST_URI'] . $search;

    // adds the search field code
    $html .= "<form action='$formUrl' method='get'><input type='text' name='search' placeholder='Search'></form>";
    if ($posts_query->have_posts()) :
      while ($posts_query->have_posts()) : $posts_query->the_post();
      // append post related code here
      endwhile;
      $html .= $pagination_html;
    endif;
    // returns $html string containing pagination, posts, and search form code
    return $html;
}

This may seem like alot at first glance but lets break down the code. Lets start with  this snippet:

$posts_per_page = get_option( 'posts_per_page' );
    $paged = sanitize_text_field($_GET['page_num']) ? (int)(sanitize_text_field($_GET['page_num'])) : 1;
    $search = sanitize_text_field($_GET['search']);
    $query_args = [
        'post_type'      => 'post',
        'posts_per_page' => $posts_per_page,
        'post_status'    => 'publish',
        'paged'          => $paged
    ];

    if($search) {
        $query_args['meta_query'] = [
            'relation'  => 'OR',
            [
                'key'   => 'meta1',
                'value' => $search,
                'compare' => 'LIKE'
            ],
            [
                'key'   => 'meta2',
                'value' => $search,
                'compare' => 'LIKE'
            ]         
        ];
    }

    $custom_query = new WP_Query($query_args);

In this snippet, a few variables are being defined and a query is being built. The $posts_per_page and $paged variables are being defined here. These are later used to determined how many posts should be displayed and what page of posts should currently be displayed. The $search variable is also being defined here, which is then used in the query arguments to search for posts related to that $search string. Notice that both $paged and $search are being sanitized because these are being pulled from the $_GET array.

Moving along, a query is being created with the relevant post type defined(in this case its just the standard ‘post’ type) alongside the other arguments. Here we see the $posts_per_page and $paged variables being used.

We also see if the $search string is not empty, a ‘meta_query’ argument is being added to the query. In this case, the $search string is being used to search any relevant meta values within the post type, but this can vary case by case depending on what you actually want to use the search field for. And at the end of the snipped the query is created.The next part of the function can be seen here:

$posts_pages = $custom_query->max_num_pages;
    // creates the pagination related code
    $pagination_html = '';
    if($posts_pages && $posts_pages != 1) {
        $pagination_html .= "<div class='pagination'>";
        for($i=0;$i<$posts_pages;$i++) {
            $page_number = $i + 1;
            if($paged == $page_number) {
                $pagination_html .= "<div class='pagination-link current-page'>$page_number</div>";
                continue;
            }
            $redirectUrl = $_SERVER['REDIRECT_URL'];
            $queryArray = [];
            parse_str($_SERVER['REDIRECT_QUERY_STRING'],$queryArray);
            $queryArray['page_num'] = $page_number;
            $queryEncoded = http_build_query($queryArray);
            $pageUrl = $redirectUrl . '?' . $queryEncoded;
            $pagination_html .= "<div class='pagination-link'><a href='$pageUrl'>$page_number</a></div>";
        }
        $pagination_html .= "</div>";
    }

This part takes care of creating the pagination html code. The $post_pages is iterated and in each iteration case, a new page link is created for each page number. The query encoding and decoding is used here to preserve any existing URL parameters when going to a new page.This is the last part of the code snippet:

 $html = '';
    $formUrl = $_SERVER['REQUEST_URI'] . $search;

    // adds the search field code
    $html .= "<form action='$formUrl' method='get'><input type='text' name='search' placeholder='Search'></form>";
    if ($posts_query->have_posts()) :
      while ($posts_query->have_posts()) : $posts_query->the_post();
      // append post related code here
      endwhile;
      $html .= $pagination_html;
    endif;
    // returns $html string containing pagination, posts, and search form code
    return $html;

Here the $html string, which is the string of html returned from the shortcode, is initialized and appended to. This begins by creating a search form and appending it to the $html string. It then jumps into the while loop of posts and here is where you would drop in code related to how/what you want to render for each post entry. And it ends with appending the $pagination_html string and returning the $html string.

Conclusion

This wraps up my argument for a custom archive page. To summarize, this is not meant to be a complete replacement of the archive template system but it come in handy is cases where a page builder is needed or if one simply wants to drop in a shortcode and render an archive.

As a disclaimer, the code mentioned above is in no way a finished product but instead serves as a great starting point for this approach. If you have any questions or feedback about the article, please feel free to leave a comment below!