A Simple WordPress Quiz Plugin

ver. 0.8, September 16, 2020

Because of recent changes to the WordPress CMS platform, I found it necessary to replace the quiz plugin I was using for my Food Bucket List. While the List itself may seem a bit lengthy to some, presenting it to readers only requires a simple Yes/No or True/False choice. Unfortunately many newer WordPress quiz plugins are either too unwieldly for this or, as I found out through numerous tests, don’t even include that kind of simple functionality.

It didn’t take long before I realized it would likely be simpler to take a couple hours and develop a simple one-file plugin to present the List. There wouldn’t need to be any database operations as I didn’t need to store anything. I could include the questions and their descriptions within the one PHP file as an array. I also didn’t intend to get any information from the user: All I needed to do was ask them to leave their final score in the comment section of the page.

Thinking it through, I mapped out a few ideas:

  • The questions only needed a checkbox, the name of the food, whether or not to “bold” the food name if it’s something I’ve had myself, and the food’s description.
  • Once the user was done and the Submit button was clicked, only count the checked checkboxes. There was no need to find out which ones were checked as I only cared about the count.
  • Any Introductory information needed to disappear when the Submit button was clicked so the user could see their result, meaning those paragraghs needed o be part of the plugin instead of being on the WordPress page.
  • It would be easier on me if the plugin could count the “bolded” items when I needed it to, instead of my having to verify my own count if I modified or added to the List.
  • The quiz should email me the answers when someone takes the quiz, maybe including their name and email address and cc’ing them too, but I don’t need to save their name, email, or IP address.

While these requirements seem complex, their implementation is rather simple. The code for the plugin’s main function is actually much smaller than the code for the question array. Everything is accomplished in only about 390 lines, 225 of those lines being for the question array, and another 17 lines being for the Introductory paragraghs.

Before we begin, you can download the current version of this plugin by clicking here. Unzip the folder, then modify the questions array in the get_available_questions() function before uploading the lpc-food-bucket-list folder to the wp-content/plugins/ directory in your WordPress installation. Then Activate the plugin in thePlugins page of the Admin area.

At the top of the PHP file we’ll first add the information that will be displayed on the Plugin page in the WordPress Admin area.

/**
 * Plugin Name: Luna Pier Cook's Food Bucket List
 * Plugin URI: http://www.lunapiercook.com
 * Description: Display Luna Pier Cook's Food Bucket List as a simple quiz
 * Date: September 16, 2020
 * Version: 0.8
 * Text Domain: lpc-food-bucket-list
 * Author: Dave Liske
 * Author URI: http://www.lunapiercook.com
 */

The following line defines the shortcode that will call the quiz, and the function that shortcode will call.

add_shortcode('lpcfoodbucketlist', 'lpc_food_bucket_list');

We’ll then start the lpc_food_bucket_list() function by defining a couple variables to manage the counts we need, and get the Introductory paragraghs and the question array.

 function lpc_food_bucket_list($atts) {
	
	// Reset the counters and get the introduction and the questions
	$question_count = 0;	
	$bolded_count = 0;
	$introduction_paragraghs = get_instruction_paragraghs();
	$availablequestions = get_available_questions();

I’ll get into the detail on those “get” functions after finishing what the main function does.

Next we’ll display the Introduction paragraghs, get the form initialized to host the questions, then set up the questions themselves as being an ordered list.

	// Display the Introduction here so it will disappear on Submit
	$content = $introduction_paragraghs;
	
	$content .= '<form action="" name="frm" method="post">';
	
	// The questions should be in an ordered list
	$content .= '<ol>';

We’ll use a foreach loop to go through the question array, count the questions as they go by, and extract each food name, whether or not to “bold” it if I’ve already had it, and the food’s description. We’ll then “bold” the name if necessary, and count each of the bolded questions as they’re processed.

	// Loop through the question array
	foreach($availablequestions as $as=>$ap):		
		$str_foodname = $ap["vname"];
		$str_bold = $ap["bold"];
		$str_description = $ap["description"];
		
		// Count the total number of questions so it doesn't have to be done manually,
		// and to also provide the sequence numbering via the array key on Submit
		$question_count = $question_count + 1;
		
		// Set Bold if necessary
		if ($str_bold == "yes") {
			// also count the number of Bolded items
			$str_namedisplay = '<strong>' . $str_foodname . '</strong>';
			$bolded_count = $bolded_count + 1;
		} else {
			$str_namedisplay = $str_foodname;
		}

It’s then a matter of giving each question its own checkbox within an array of checkboxes, named “answers”, and setting up each question correctly within our ordered list. Each checkbox in the array gets its key from the current question count. We’ll use this after the Submit button is clicked.

		// Display the question, with its checkbox first
		$content .= '<li>';
		// force the array value key to be the current question count, which will duplicate the ordered list
		$content .= '<input type="checkbox" name="answers[' . $question_count . ']" value="' . $str_foodname . '"> ';
		$content .= $str_namedisplay . '<br>';
		$content .= $str_description;
		$content .= '</li><br>';
	endforeach;
	$content .= '</ol>';

Once we’re done looping through the questions, we’ll add a couple fields for the name and email address, then add the Submit button and close out the form.

	// add the email fields
	$content .= '<hr>';
	$content .= '<p>Please include your name and email.</p>';
	$content .= '<p><strong>Name: </strong><input type="text" id="user_name" name="user_name"></p>';
	$content .= '<p><strong>Email: </strong><input type="text" id="user_email" name="user_email"></p>';
	$content .= '<hr>';
	
	$content .= '<input type="submit" name="submit" value="Submit">';
	$content .= '</form>';

Still within the same function, once the Submit button is clicked we’ll loop through the answers() checkbox array and count how many were checked, then calculate the percentage.

	if(isset($_POST['submit'])) {
		
		// make sure to reset the Answers counter, and get the answers
		$selected_count = 0;
		$answers = $_POST['answers'];
		
		// Count the number of checkboxes that were checked
		foreach ($answers as $answers=>$value) {
			$selected_count = $selected_count + 1;
		}
		// calculate the percentage		
		$selected_percent = ((round($selected_count / $question_count, 2)) * 100);

We’ll use a custom function, which is a modified version of the implode function provided in PHP, to extract the key=>value pair from the checkbox array and format it correctly as an ordered list.

		// Call the custom implode function to get the key=>value pair, and format them as an ordered list
		$answers_to_email = implodeKV( ') ' , ',' , '<br>', $_POST['answers']);

The custom function returns the key=>value pair of the checked boxes as “key) value” with an HTML line break after each one.

function implodeKV($glueKV, $gluePair, $KVLine, $KVarray){
	// custom function to return the key from the checkbox array with the checked value
	// usage implodeKV( ')' , ',' , '<br>', $array);
    $t = array();
    foreach($KVarray as $key=>$val) {
        $t[] = $key . $glueKV . $val . $KVLine;
    }
    return implode($KVLine, $t);
}

We can then finish up by replacing the Introductory paragraghs and questions form with the count of the selected items, as well as the count of how many questions there were, the percentage checked, and the formatted listed of checked answers. There’s also a reminder for the user to leave their score in the comments, an exercise for them to develop their own Food Bucket List, and commented code for exposing the count of “bolded” items as needed.

		// Display the number of questions checked, along with the total question count and the formatted answers list
		$content = '<p><strong>Number Of Items Selected:</strong> ' . $selected_count . ' out of ' . $question_count . ', or ' . $selected_percent . '&#37</p>';
		$content .= '<p>Remember, please let me know how you did by adding your score in the comments at the bottom of this page.</p>';
		$content .= '<blockquote><strong>Exercise:</strong> What would your own Food Bucket List look like? My own rule is, “You cant say you don’t like a food 
		until you actually try it.” That means there’s no looking at something reasonable and saying “I’m not eating that.” 
		Someone in this world is probably eating that particular item right now.</blockquote>';
		$content .= '<p>Your answers were:</p><p>' . $answers_to_email . '</p>';
		
		// Uncomment to display the count of the Bolded items
		// $content .= '<br><strong>Personal Count:</strong> ' . $bolded_count;		

The email code adds the name, email, and score before the impoded answers() array, then emails them to me from my own email address as well as cc’ing them to the email provided at the end of the quiz.

Note that I’m not verifying that the name or email are formatted correctly. To check that first then notify the user that they need to correct something is too complicated for what I needed here. Briefly though, it does require using the answers() array to check the user’s previously-checked boxes so they don’t have to take the quiz again just to correct the name or email.

Once that’s complete we can return all the generated HTML and close out the function.

		// create and send the HTML email
		$username = $_POST['user_name'];
		$useremail = $_POST['user_email'];
		$score = '<p>' . $selected_count . ' out of ' . $question_count . ', or ' . $selected_percent . '%</p>';
		$body = '<p>' . $username . '</p><p>' . $useremail . '</p><p>' . $score . '</p><hr>' . $answers_to_email;

		$to = 'dave@micuisine.com';		
		$from_user = "=?UTF-8?B?".base64_encode('Luna Pier Cook')."?=";
		$from_email = 'dave@micuisine.com';
		$subject = "=?UTF-8?B?".base64_encode('LPC Food Bucket List Submission Submission')."?=";

		$headers = "From: $from_user <$from_email>\r\n" .
			"CC: $username <$useremail>\r\n" .
			"MIME-Version: 1.0" . "\r\n" .
			"Content-type: text/html; charset=UTF-8" . "\r\n";
		mail ($to, $subject, $body, $headers);
	}    
	return $content;
}

The Introductory paragraghs are only set aside in their own function to keep the main function from being too messy.

function get_instruction_paragraghs() {
	
	$introduction = "<p><em>Last updated September 16, 2020</em></p>";
	$introduction .= "<p>Plenty of web sites offer some kind of “foodie quiz” to supposedly determine someone's level of “foodieness”. 
	I fell victim to the trend of creating such a list myself a few years back, but have since realized the nonsense of it. 
	One I saw recently really set my teeth on edge because it contained so many common items, such as hot dogs, ketchup, and pineapple. 
	I decided something else was needed.</p>";
	$introduction .= "<p>So here it is, my version of a “Food Bucket List.” I've set it up as a “quiz” so you can determine 
	how many on my list you've tried. Remember, this is my own list, and yours or anyone else's will certainly be different. 
	There isn’t any result from this “quiz” that's a bad or negative result as there are no right or wrong answers.</p>";
	$introduction .= "<p>While the quiz doesn't retain or save any personal information, it does email me your answers with your name and email,  
	as well as cc'ing the email you provide, without saving your name, email address, IP address, or any other data from you or your computer.</p>";
	$introduction .= "<p>Items in bold are items I've had so far, which is 70 of the 110 items listed. The average is 41. 
	When you've completed the quiz, let me know how you did by adding your score in the comments at the bottom of this page.</p>";
	
	return $introduction;
	
}

The majority of the PHP file is taken up by the questions array. Each question requires two lines in this function to keep things organized. The questions are also separated into groups of ten for better readability. These are only the first 20 questions, with a template for the questions being commented out at the beginning of the array. Like a console in a recording studio, it only looks like a mess until you realize there are simply dozens of instances of a single item, and you only need to really deal with one at a time while keeping your mind on the whole of it.

function get_available_questions() {	
	return array(
		// Available questions have an identifier, the food name, whether or not to Bold it, and a description	
		// "IDENTIFIER"=>array("vname"=>"FOOD NAME","bold"=>"YES/NO",
		//		"description"=>"DESCRIPTION"),
	
		"absinthe"				=>array("vname"=>"Absinthe","bold"=>"no",
			"description"=>"Banned in the U.S. from 1915 till 2007, Absinthe can have anywhere from 47% to 74% alcohol content. Rumors have always been that it’s a hallucinogenic."),
		"alligator"				=>array("vname"=>"Alligator","bold"=>"yes",
			"description"=>"Prepared correctly, a very sweet meat that can be served as an entrée, or cut into chunks and served with a dipping sauce as an appetizer."),
		"anchovies"				=>array("vname"=>"Anchovies","bold"=>"yes",
			"description"=>"These small or tiny fish are decried as the worst pizza topping possible, mainly because of poor quality of the fish, far too much salt, or the canning. Better fish and better canning operations make for a much better experience, which I&#39ve thoroughly enjoyed."),
		"bear"					=>array("vname"=>"Bear","bold"=>"no",
			"description"=>"States such as Michigan and Maine have official Black Bear hunting seasons. The meat is good in stews, sausages and soups. It must be cooked thoroughly and should not be even medium rare, with fall kills being better than spring as a bear coming from hibernation might be parasitic."),
		"blackpudding"			=>array("vname"=>"Black Pudding, aka Blood Sausage","bold"=>"yes",
			"description"=>"Blood from livestock or game birds is combined with a filler of meat, nut or grain-based filled until it’s thick enough to congeal before cooking."),
		"bonemarrow"			=>array("vname"=>"Bone Marrow","bold"=>"yes",
			"description"=>"More commonly available than people realize. Simply marinade cut beef or veal bones in a solution of sea salt water for 24 hours to remove the blood (changing the solution every 4 – 6 hours), then roast in the oven at 450F for 15 – 25 minutes (depending on size) until the marrow puffs. Gelatinous greatness awaits: just eat it with a spoon or have it on toast or crackers."),
		"borscht"				=>array("vname"=>"Borscht","bold"=>"no",
			"description"=>"A hot or cold soup based on beetroot, with numerous variations."),
		"britishfishchips"		=>array("vname"=>"British Fish & Chips","bold"=>"yes",
			"description"=>"I don’t care if you think you’ve had the best fish & chips you’ve ever tasted. Unless it was the British version that was developed in the 1850s, it doesn’t count. It’s a select few species of fish with a certain batter fried in a specific oil with potatoes prepared in one of a few accepted manners. Anything else is an imitation, and likely a sad one at that. Oh, and don’t forget the actual British malt vinegar."),
		"buffalo"				=>array("vname"=>"Buffalo","bold"=>"yes",
			"description"=>"A rich cattle meat similar to beef and prepared the same. Gets very dry if overcooked."),
		"calamari"				=>array("vname"=>"Calamari","bold"=>"yes",
			"description"=>"Generally slices of the body of squid that have been coated with batter and deep-fried."),
		//
		"camelhump"				=>array("vname"=>"Camel Hump","bold"=>"no",
			"description"=>"Must be from a very young camel. It’s marinated and then roasted similar to a beef sirloin roast."),
		"camembertcheese"		=>array("vname"=>"Camembert Cheese","bold"=>"yes",
			"description"=>"A very soft and creamy cheese that’s stronger than brie, with a thick outer layer of mold."),
		"carp"					=>array("vname"=>"Carp","bold"=>"no",
			"description"=>"Can be fried or baked (be sure to score whole fish first), made into cakes similar to crab cakes, and is good for stews, chowders and pickling."),
		"catfish"				=>array("vname"=>"Catfish","bold"=>"yes",
			"description"=>"Mostly used as poached in tomato or cream, or fried in a light batter or cornmeal dredge."),
		"caviar"				=>array("vname"=>"Caviar","bold"=>"yes",
			"description"=>"Salt-cured eggs (“roe”) from various fish and mammals including sturgeon, beluga, salmon, trout and other specific species."),
		"chickenfeet"			=>array("vname"=>"Chicken Feet, Glazed","bold"=>"yes",
			"description"=>"Not much meat, but quite rich in flavor. Another item that’s like candy and difficult to stop eating."),
		"chickengizzardsfried"	=>array("vname"=>"Chicken Gizzards, Fried","bold"=>"yes",
			"description"=>"A soul food specialty, there was for many years an annual Gizzardfest in the month of June in Potterville, Michigan. The ones I enjoyed were made fresh at the Pit Stop Pantry, a gas station/truck stop just north of Monticello, Indiana, on NW Shafer Dr."),
		"chickenlivers"			=>array("vname"=>"Chicken Livers","bold"=>"yes",
			"description"=>"These have been so common for me, either sautéed in butter or breaded with corn meal and fried, that I at first had neglected to add them to the list. I first had these as a teen and still enjoy them four decades on."),
		"chitlins"				=>array("vname"=>"Chitlins, aka Chitterlings or Tripas","bold"=>"yes",
			"description"=>"The intestines of pigs, and possibly other livestock, that has been thoroughly cleaned with multiple rinses, then simmered for hours before being made into a soup or battered and fried."),
		"clamssteamers"			=>array("vname"=>"Clams, Steamed (Steamers)","bold"=>"yes",
			"description"=>"Countless places throughout the U.S. offer fried clams as part of seafood platters. Fewer places offer them steamed, which is popular in New England states. This is also a soft-shelled clam, which is a different beast altogether.")
		//
	);		
}

I don’t claim to be any extreme kind of programmer. But that this only took about 3.5 hours to implement, with most of that time being spent on organizing the questions array, shows how simple a True/False quiz plugin with an emailer in WordPress can be.