Button to show a Random Post

Learn how to create a random post button for your WordPress site. It’ll work with any post type such as a Post, Page, Product, Event, Custom, etc.

Try a random WordPress tutorial

When we render the button’s HTML it’s probably going to end up as part of a cached page, so we can’t just inject a random URL when we render it. If we did that, the link’s URL would only change when the page cache got flushed. So, we’re going to do it in the browser, using some JavaScript. When the visitor clicks on the button, we’ll make an Ajax POST request to the back-end that asks for a random URL. We we get the response, we’ll ask the browser to navigate to that URL.

Scaffold the Code

In your WordPress child theme, create a new folder called “wpt-random-post-button”. In this folder create two empty files, called “wpt-random-post-button.css” and “wpt-random-post-button.js”. Back in your child theme’s main folder, create a file called “wpt-random-post-button.php” and paste the following into it:

<?php

/**
 * WP Tutorials : Random Post Button (WPTRPB)
 *
 * https://wp-tutorials.tech/add-functionality/random-post-button/
 */

defined('WPINC') || die();

const WPTRPB_ACTION = 'show-random-post';
const WPTRPB_DEFAULT_POST_TYPE = 'post';

/**
 * Set this to true to enable some useful diagnostics in the front-end
 * (browser dev tools JavaScript console).
 *
 * Important: Change this to false when you've finished the tutorial
 */
const WPTRPB_ENABLE_DIAGNOSTIC_MODE = true; // false;

/**
 * You can set this to false if you want to disable the HTTP referer check.
 * It's recommended to leave this enabled.
 */
const WPTRPB_ENABLE_REDERER_CHECK = true;

/**
 * Handle the Ajax request in the back-end.
 * Look for a random post based on the post type requested by the button.
 */
function wptrpb_get_random_post() {
	if (WPTRPB_ENABLE_REDERER_CHECK) {
		// Verify the Ajax request has come from this site by checking the HTTP referrer.
	}

	// Handle incoming Ajax requests.

	// Send a JSON response back to the browser.
}
add_action('wp_ajax_' . WPTRPB_ACTION, 'wptrpb_get_random_post');
add_action('wp_ajax_nopriv_' . WPTRPB_ACTION, 'wptrpb_get_random_post');

/**
 * Enqueue front-end assets for the random post buttons.
 */
function wptrpb_enqueue_assets() {
	global $wptrpb_have_assets_been_enqueued;

	if (is_null($wptrpb_have_assets_been_enqueued)) {
		$base_url = get_stylesheet_directory_uri();
		$version = wp_get_theme()->get('Version');

		$handle = 'wptrpb';

		wp_enqueue_style(
			$handle,
			$base_url . '/wpt-random-post-button/wpt-random-post-button.css',
			null, // We don't have any CSS dependencies
			$version
		);

		wp_enqueue_script(
			$handle,
			$base_url . '/wpt-random-post-button/wpt-random-post-button.js',
			array('jquery'), // Our JS code depends on jQuery
			$version
		);

		$wptrpb_have_assets_been_enqueued = true;
	}
}

/**
 * Render the shortcode for a random post button.
 */
function wptrpb_do_shortcode_button($atts) {
	$html = '';

	// Render the HTML for a random-post-button and return it as a string.
	// ...

	return $html;
}
add_shortcode('random_post_button', 'wptrpb_do_shortcode_button');

That’s the project’s scaffold in-place. Now we need to pull it in to the child theme, so open your child theme’s functions.php and add the following couple of lines to it.

// WPT Random Post Button (WPTRPB)
require_once dirname(__FILE__) . '/wpt-random-post-button.php';

Save all that and add the shortcode to your site – where you want your “Show me some random content” button.

Shortcode to show a random post
Show a random post in WordPress

Render the Button HTML

The HTML for the button is going to look something like this:

<a href="javascript:void(0);" data-rpb-args="{...}">
	<img src="loading-spinner.svg">
	Try a random WordPress tutorial
</a>

We’ll use javascript:void(0) as the href to stop the browser from trying to jump to a new URL for us. The key thing here, though, is the data-rpb-args="{...}" attribute. We’re going to take a simple PHP array and encode it as JSON so we can pick it up as an object in our JavaScript code. The JavaScript object will look like this:

{
   "button": "Try a random WordPress tutorial",
   "type": "post",
   "diagnostic": false,
   "ajaxurl": "https://wp-tutorials.tech/wp-admin/admin-ajax.php",
   "action": "show-random-post"
}

Right, let’s write some actual code. Open wpt-random-post-button.php and replace the contents of wptrpb_do_shortcode_button() with the following:

function wptrpb_do_shortcode_button($atts) {
	$html = '';

	if (is_admin() || wp_doing_ajax()) {
		// Don't do anything.
	} else {
		wptrpb_enqueue_assets();

		// Take the shortcode parameters and apply sensible defaults.
		// We're going to pass these into the front-end in the
		// data-rbp-args attribute.
		$args = shortcode_atts(
			array(
				'button' => '',
				'type' => WPTRPB_DEFAULT_POST_TYPE,
				'diagnostic' => WPTRPB_ENABLE_DIAGNOSTIC_MODE,
			),
			$atts
		);

		// If the button text isn't specified, auto-create some button text now.
		if (empty($args['button'])) {
			$args['button'] = 'Show me a random ' . $args['type'];
		}

		// This is the standard WordPress Ajax URL.
		$args['ajaxurl'] = admin_url('admin-ajax.php');

		// Every Ajax URL call needs an "action".
		$args['action'] = WPTRPB_ACTION;

		$base_url = get_stylesheet_directory_uri();
		$spinner_url = $base_url . '/wpt-random-post-button/spinner.svg';

		$html .= sprintf(
			'<a href="javascript:void(0);" class="button" data-rpb-args="%s" rel="nofollow"><img src="%s" alt="Loading content" class="wpt-spinner" style="display:none;" />%s</a>',
			esc_attr(json_encode($args)), // Pass the button's parameters to the front-end as JSON
			esc_url($spinner_url),
			esc_html($args['button'])
		);
	}

	return $html;
}

Save your changes, then reload the page with your button on it. If you right-click on the button and go to “Inspect Element”, you’ll see data-rpb-args="{...}" in the browser’s dev tools. Other than the data attribute, there’s not much to the shortcode function. It just renders HTML for a link.

Handling the Ajax Request in the Back-end

WordPress uses the “wp_ajax_” (for logged-in users) and wp_ajax_nopriv_” (for non-logged-in users) actions to process incoming Ajax requests. We need to define our own action (any name we want), and setup the action handler. Here, we’ve called our action “show-random-post“, defined in WPTRPB_ACTION. We’ve already used add_action() to set up wptrpb_get_random_post() as our action-handler, so all we need to do now is populate the function. Open wpt-random-post-button.php and replace the contents of wptrpb_get_random_post() with the following:

function wptrpb_get_random_post() {
	// Check that the incoming Ajax request has a HTTP Referrer that matches
	// this site (i.e. the POST request hasn't come from somewhere else, or
	// from a bot).
	if (WPTRPB_ENABLE_REDERER_CHECK) {
		$site_url = site_url('/');
		$referrer_url = wp_get_referer();
		if (parse_url($site_url, PHP_URL_HOST) !== parse_url($referrer_url, PHP_URL_HOST)) {
			error_log(__FUNCTION__ . ' : Bad referrer');
			die();
		}
	}

	$response = array(
		'errors' => array(),
	);

	// Get the WP Post Type from the incoming POST request. It should always be
	// specified. If it's not in the request, refault to what's specified in
	// WPTRPB_DEFAULT_POST_TYPE ('post').
	$post_type = WPTRPB_DEFAULT_POST_TYPE;
	if (array_key_exists('postType', $_POST)) {
		$post_type = sanitize_text_field($_POST['postType']);
	}

	// Arguments for get_posts (i.e. WP_Query).
	$post_query_args = array(
		'post_type' => $post_type,
		'post_status' => 'publish',
		'orderby' => 'rand',
		'numberposts' => 1,
	);

	if (!post_type_exists($post_type)) {
		$response['errors'][] = 'Invalid post type: ' . $post_type;
	} elseif (!is_array($posts = get_posts($post_query_args))) {
		$response['errors'][] = 'No posts found (A): ' . $post_type;
	} elseif (count($posts) <= 0) {
		$response['errors'][] = 'No posts found (B): ' . $post_type;
	} else {
		// We should have an array with exactly one post in it.
		$post = $posts[0];

		// Grab the post's title and URL.
		$response['post'] = array(
			'title' => get_the_title($post),
			'url' => get_the_permalink($post),
		);
	}

	// HTTP Response 200 means OK
	$status_code = 200;

	// Send the response back to the browser.
	wp_send_json($response, $status_code);
}

The function’s logic breaks down like this:

  • Get the site’s domain, and the domain from HTTP_REFERER…
    • if they don’t match, abort now.
  • Declare the response array that we’re going to populate and send back to the client (the browser)
  • The incoming AJax request has its object properties set in the standard PHP $_POST array, so extract “postType” from there
    • importantwhen you extract data from $_POST, be sure to sanitize the values, even if you’re not going to insert them into the database. We’re using sanitize_text_field to make sure there’s nothing nasty in there
  • Create the argument array that we’re going to pass into get_posts (to retrieve our random post)
    • We’re going to ask WordPress for a single published post, sorted in a random order, of the specified post type
  • Fail if the post type is not valid
  • If the call to get_posts() doesn’t return any posts, then fail
  • If we’ve got a single post back from get_posts(), then…
    • Add the post’s URL and title to the response array
  • The last thing we do is call wp_send_json() to send the response array back to the browser
    • A HTTP response code of 200 means OK
    • Inside wp_send_json(), WordPress calls exit(), to halt PHP execution. If you add any code after wp_send_json(), it won’t get executed.

That’s the back-end sorted out. Now we need to create the JavaScript code that sends the Ajax request to the back-end, so WordPress can call wptrpb_get_random_post() for us.

Sending the Ajax Request from the Front-end

The JavaScript (jQuery) code is pretty straightforward, and works like this:

  • After the page has loaded, find all elements with a data-rpb-args attribute
  • For each of these elements…
    • Get the contents of data-rpb-args and extract the requested postType & action
    • Create the request object that we’re going to POST to the back-end
    • If diagnostic mode is enabled, then…
      • Show some useful info in the JavaScript console (browser dev tools)
    • Show the spinner that’s nested inside the button
    • Send the request to the back-end using jQuery post()
    • When we receive a response to our request…
      • If there are any errors in the response then…
        • Show the errors in an alert popup
      • Else if we’re in diagnostic mode, then…
        • Show the random post URL in an alert popup
      • Else…
        • Set the browser’s URL to the random URL returned by the back-end

Open wpt-random-post-button/wpt-random-post-button.js and paste the following into it:

/**
 * WP Tutorials : Random Post Button (WPTRPB)
 *
 * https://wp-tutorials.tech/add-functionality/random-post-button/
 */
(function($) {
	'use strict';

	$(window).on('load', function() {
		$('[data-rpb-args]').click(function(event) {
			// Stop the browser from doing its default
			// event-handling for a link-click.
			event.preventDefault();

			const button = $(this);
			const spinner = $(button).find('.wpt-spinner');
			const args = $(button).data('rpb-args');
			let isJumpingToPost = false;

			// Create the request object, to send to the back-end.
			const request = {
				action: args.action,
				postType: args.type
			};

			if (args.diagnostic) {
				console.log(`click: ${args.ajaxurl}`);
				console.log(request);
			}

			$(spinner).show();

			$.post(args.ajaxurl, request)
				.done((response) => {
					if (response.errors.length) {
						alert(response.errors.join("\n"));
					} else if (args.diagnostic) {
						alert(response.post.url);
					} else {
						window.location.href = response.post.url;
						isJumpingToPost = true;
					}
				})
				.always(() => {
					if (!isJumpingToPost) {
						$(spinner).fadeOut();
					}
				});

		});
	});
})(jQuery);

Styles and the SVG Spinner

Grab a loading spinner SVG and save it in the “wpt-random-post-button” folder as “spinner.svg”. You can use any spinner you like, of course.

We should also add a bit of style to layout the spinner, so open wpt-random-post-button/wpt-random-post-button.css and paste the following into it:

/**
 * WP Tutorials : Random Post Button (WPTRPB)
 *
 * https://wp-tutorials.tech/add-functionality/random-post-button/
 */

[data-rpb-args] {
	/*
	 * The button has to be relatively positioned so we
	 * can use absolute positioning for the spinner.
	 */
	position: relative;
}

[data-rpb-args] .wpt-spinner {
	width: 1.5em;
	height: 1.5em;
	position: absolute;
	right: 0.5em;
	top: 50%;
	transform: translateY(-50%);
	/* background-color:  white; */
	border-radius: 50%;
}

Finish & Test

Make sure everything is saved, then reload the page that’s got your random post button on it. When you click the button, the spinner will appear while the Ajax request-and-response is in transit. When the browser received the response, you should see the random URL displayed in a popup.

Dismiss the popup, click the button a few more times and you should see different/random URLs coming back in the response.

If you’ve got WooCommerce installed on your site, try passing type="product" in the button’s shortcode. This should cause the button to return random products instead of random posts.

A random WordPress post URL
Random post URL in the Ajax response
Shortcode to jump to a random WooCommerce product
Show a random WooCommerce product

When you’re happy it’s all working properly, come out of diagnostic mode by setting WPTRPB_ENABLE_DIAGNOSTIC_MODE=false near the top of “wpt-random-post-button.php”. Next time you click the random post button, the browser should navigate to the post, instead of showing the URL in a popup.

If you want to expand things a bit, try adding taxonomy and term to the shortcode parameters so you can jump to random posts in specific categories, or with specific tags. This would require a bit of work when preparing $post_query_args for get_posts(), but it won’t be difficult.

Have fun with the randomness 😎👍

Like This Tutorial?

loading

Let us know

Leave a Comment

Your email address will not be published.