WordPress Page Add Custom Endpoint

Scenario is, you have a page http://example.com/popular/, and you want to add custom query variable for the page which can be retrieve using get_query_var() function to modify template or filter query. So a request to http://example.com/popular/music/2014/usa will make new query variables – (<popular_category = music, popular_year = 2014, popular_location = usa) available for you.

A solution can be achieved using add_rewrite_rule() & add_rewrite_tag() function, but there is a more easier way which will only use one function to make the custom query variable available for the specific page, and won’t effect any other pages or anything.


The Code

function popular_page_parse_request( $wp ) {
    // custom variables will only be available on popular page.
    $pageslug = 'popular';

    // get the actual request and parse it down
    if ( isset( $wp->request ) && strpos( $wp->request, '/' ) ) {
        $requests = explode( '/', $wp->request );

        // array_shift takes out the first path what is the page slug/name.
        // and make sure it's the popular page.
        if ( $pageslug == array_shift( $requests ) ) {
            // set pagename to make sure we have found a page with the name.
            $wp->set_query_var( 'pagename', $pageslug );

            // wordpress automatically try to check if it's an attachment when a parameter is present
            // after pageslug, so we set it false as we do the parsing ourselves.
            $wp->set_query_var( 'attachment', false );

            // we understand this is a valid page, and let WordPress understand there's no error.
            $wp->set_query_var( 'error', false );

            $count = count( $requests );

            if ( $count > 0 ) {
                // stop the canonical redirection, if a url request isn't same as get_permalink( ) or
                // the_permalink( ), then wordpress redirects it to the actual page
                // and removes all parameter after slug name.
                remove_action( 'template_redirect', 'redirect_canonical' );

                // the first parameter will definitely be the category.
                $wp->set_query_var( 'popular_category', $requests[0] );

                // for second one, we check if it could be a year, or it could be the country name.
                if ( isset( $requests['1'] ) ) {
                    // pretty basic checks.
                    if ( 4 == strlen( $requests['1'] ) && is_numeric( $requests['1'] ) ) {
                        $wp->set_query_var( 'popular_year', $requests[1] );
                    } else {
                        $wp->set_query_var( 'popular_location', $requests[1] );
                    }
                }

                // if location wasn't found yet, and we have third parameter variable.
                if ( isset( $requests['2'] ) && !isset( $wp->query_vars['popular_location'] ) ) {
                    if ( ! is_numeric( $requests['2'] ) ) {
                        $wp->set_query_var( 'popular_location', $requests[2] );
                    }
                }
            }

            // the final step to set some defaults if no value was available.
            if ( ! isset( $wp->query_vars['popular_category'] ) ) {
                $wp->set_query_var( 'popular_category', 'any' );
            }

            if ( !isset( $wp->query_vars['popular_year'] ) ) {
                $wp->set_query_var( 'popular_year', 'any' );
            }

            if ( !isset( $wp->query_vars['popular_location'] ) ) {
                $wp->set_query_var( 'popular_location', 'any' );
            }
        }
    }
}
add_action( 'parse_request', 'popular_page_parse_request' );

Above function processes has been commented for better understanding. Now we can use get_query_var( 'popular_category' ) or get_query_var( 'popular_year' ) or get_query_var( 'popular_location' ) to retrieve specific url custom query variable.


Example

We will create a list of posts based on our custom query variable. For that, first we will create a shortcode which will generate content using the custom query variable. And then place the shortcode on the Popular page.

function popular_posts_shortcode( $attrs ) {
    $html  = '';
    $query_args = array( 
        'order'          => 'DESC',
        'orderby'        => 'comment_count',
        'post_type'      => 'post',
        'post_status'    => 'publish',
        'posts_per_page' => -1,
        'meta_query'     => array()
    );

    if ( 'any' != get_query_var( 'popular_category' ) ) {
        $query_args['category_name'] = get_query_var( 'popular_category' );
    }

    if ( 'any' != get_query_var( 'popular_year' ) ) {
        $query_args['year'] = get_query_var( 'popular_year' );
    }

    // i use post custom field to add one or multiple location name with post
    // if you don't have any similar thing, don't use it. Otherwise you will always see empty result.
    if ( 'any' != get_query_var( 'popular_location' ) ) {
        $query_args['meta_query'][] = array( 
            'key' => 'location',
            'value' => array( get_query_var( 'popular_location' ) ),
            'compare' => 'IN'
        );
    }

    $query = new WP_Query( $query_args );

    if ( $query->have_posts() ) {
        $html .= '<ul>';
        while( $query->have_posts() ) : $query->the_post();
        $html .= sprintf( 
            '<li><a href="%1$s">%2$s</a> - <span title="posted %3$s ago">posted %3$s ago</span></li>',
            get_permalink(),
            get_the_title(),
            human_time_diff( strtotime( get_post()->post_date ), current_time( 'timestamp' ) )
        );
        endwhile;
        $html .= '</ul>';
    }

    wp_reset_query();

    return $html;
}
add_shortcode( 'popular_posts', 'popular_posts_shortcode' );

Ok, now if you use the shortcode [popular_posts] in your Page (Popular), you should see a list. Try manually entering url appending the popular page and check the results. You can also add some links to you nav menu using direct url of a popular page for quick access by you or user.

For ordering/sorting posts, we have used comment_count.