Building a question-answer interface
Throughout the previous sections, we learned the basics of web application frameworks while looking at how WordPress fits into web development. By now, you should be able to visualize the potential of WordPress for application development and how it can change your career as developers. Being human, we always prefer a practical approach to learning new things over the more conventional theoretical approach.
So, I am going to complete this chapter by converting the default WordPress functionality into a simple question-answer interface such as Stack Overflow, to show you a glimpse into what we are going to develop throughout this book.
Prerequisites
We will be using Version 3.6 as the latest stable version, available at the time of writing this book. I suggest you to set up a fresh WordPress installation for this book, if you haven't already done so.
Also we will be using the TwentyTwelve theme, which is available with the default WordPress installation. Make sure to activate the TwentyTwelve theme in your WordPress installation.
First, we have to create an outline containing the list of tasks to be implemented for this scenario:
- Create questions using the admin section of WordPress
- Allow users to answer questions using comments
- Allow question creators to mark each answer as correct or incorrect
- Highlight the correct answers for each question
- Customize the question list to include the number of answers and number of correct answers
Now it's time to get things started.
Creating questions
The goal of this application is to let people submit questions and get answers from various experts in the same field. First, we need to create a method to add questions and answers. By default, WordPress allows us to create posts and submit comments to the posts. In this scenario, the post can be considered as the question and the comments can be considered as the answers. Therefore, we have the capability of directly using normal post creation for building this interface.
However, I would like to choose a slightly different approach by using custom post types in order to keep the default functionality of posts and let the new functionality be implemented separately without affecting the existing ones.
We are going to create a plugin to implement the necessary tasks for our application. First, create a folder called wpwa-questions
inside the /wp-content/plugins
folder and add a new file called index.php
. Next, we need to add the block comment to define our file as a plugin:
/* Plugin Name: WP Questions Plugin URI: - Description: Question and Answer interface for developers Version: 1.0 Author: Rakhitha Nimesh Author URI: http://www.innovativephp.com/ License: GPLv2 or later */
Having created the main plugin file, we can move into creating a custom post type called wp_question
using the following code snippet. Include the code snippet in your index.php
file of the plugin:
add_action('init', 'register_wp_questions'); function register_wp_questions() { $labels = array( 'name' => __( 'Questions', 'wp_question' ), 'singular_name' => __( 'Question', 'wp_question' ), 'add_new' => __( 'Add New', 'wp_question' ), 'add_new_item' => __( 'Add New Question', 'wp_question' ), 'edit_item' => __( 'Edit Questions', 'wp_question' ), 'new_item' => __( 'New Question', 'wp_question' ), 'view_item' => __( 'View Question', 'wp_question' ), 'search_items' => __( 'Search Questions', 'wp_question' ), 'not_found' => __( 'No Questions found', 'wp_question' ), 'not_found_in_trash' => __( 'No Questions found in Trash', 'wp_question' ), 'parent_item_colon' => __( 'Parent Question:', 'wp_question' ), 'menu_name' => __( 'Questions', 'wp_question' ), ); $args = array( 'labels' => $labels, 'hierarchical' => true, 'description' => __( 'Questions and Answers', 'wp_question' ), 'supports' => array( 'title', 'editor', 'comments' ), 'public' => true, 'show_ui' => true, 'show_in_menu' => true, 'show_in_nav_menus' => true, 'publicly_queryable' => true, 'exclude_from_search' => false, 'has_archive' => true, 'query_var' => true, 'can_export' => true, 'rewrite' => true, 'capability_type' => 'post' ); register_post_type( 'wp_question', $args ); }
This is the most basic and default code for custom post type creation and I assume that you are familiar with the syntax. We have enabled title, editor, and comments in the support section of the configuration. These fields will act in the role of question title, question description, and answers. Other configurations contain the default values and hence explanations will be omitted. If you are not familiar, make sure you have a look at the documentation on custom post creation at http://codex.wordpress.org/Function_Reference/register_post_type.
Note
Beginner to intermediate level developers and designers tend to include the logic inside the functions.php
file in the theme. It is considered bad practice as it becomes extremely difficult to maintain as your application becomes larger, so we will be using plugins to add functionality throughout this book, and the drawbacks of the functions.php
technique will be discussed in the later chapters.
Once the code is included, you will get a new section in the admin area for creating questions. Add a few questions and put some comments in using different users, before we move on to the next stage.
Changing the status of answers
Once users provide their answers, the creator of the question should be able to mark them as correct or incorrect. So, we are going to implement a button for each answer to mark its status. Only the creator of the questions will be able to mark the answers. Once the button is clicked, an AJAX request will be made to store the status of the answer in the database.
First, we need to customize the existing comments list to suit the requirements of the answers list. By default, WordPress will use the wp_list_comments
function inside the comments.php
file to show the list of answers for each question. We need to modify the answers list in order to include the answer status button.
So we implement our own version of wp_list_comments
using a custom function. First, you have to open the comments.php
file of the theme and look for the call to the wp_list_comments
function. You should see something similar to the following code:
<?php wp_list_comments( array( 'callback' => 'twentytwelve_comment', 'style' => 'ol' ) ); ?>
This function is used to generate a comments list for all types of posts, but we need a slightly modified version to suit the answers list. So we call the wp_list_comments
function with different arguments, as shown in the following code:
<?php if( get_post_type( $post ) == "wp_question" ){ wp_list_comments( array( 'type' => 'comment', 'callback' => 'wpwa_comment_list', 'style' => 'ol' ) ); } else{ wp_list_comments( array( 'type' => 'comment', 'callback' => 'twentytwelve_comment', 'style' => 'ol' ) ); } ?>
Note
Arguments of the wp_list_comments
function can be either an array or a string. Here we have preferred array-based arguments over string-based arguments.
Here we include a conditional check for the post type in order to choose the correct answer list generation function. When the post type is wp_question
, we call the wp_list_comments
function with the callback parameter defined as wpwa_comment_list
, which will be the custom function for generating the answers list.
Implementation of the wpwa_comment_list
function goes inside the wpwa-questions.php
file of our plugin. This function contains lengthy code, which is not necessary for our explanations, so I'll just be explaining the important sections of the code. It's best to work with the full code for the wpwa_comment_list
function from the source code folder. Have a look at the following code snippet:
function wpwa_comment_list( $comment, $args, $depth ) { global $post; $GLOBALS['comment'] = $comment; // Get current logged in user and author of question $current_user = wp_get_current_user(); $author_id = $post->post_author; $show_answer_status = false; // Set the button status for authors of the question if ( is_user_logged_in() && $current_user->ID == $author_id ) { $show_answer_status = true; } // Get the correct/incorrect status of the answer $comment_id = get_comment_ID(); $answer_status = get_comment_meta( $comment_id, "_wpwa_answer_status", true ); // Rest of the Code }
wpwa_comment_list
is used as the callback function of the comments list and hence it will contain three parameters by default. Remember that the button for marking the answer status should only be visible to the creator of the question.
First, we get the current logged in user from the wp_get_current_user
function. Also we can get the creator of the question using the global $post
object. Next, we check whether the logged in user created the question. If so, we set the $show_answer_status
variable to true
. Also, we have to retrieve the status of the current answer by passing the comment ID and the _wpwa_answer_status
key to the get_comment_meta
function.
Then we will have to include the common code for generating the comments list with the necessary condition checks. Open the wpwa-questions.php
file of the plugin and go through the rest of the wpwa_comment_list
function to get an idea of how the comments loop works.
Next, we have to highlight the correct answers for each question and I'll be using an image as the highlighter. In the source code, we use the following code after the header tag to show the correct answer highlighter:
<?php // Display image of a tick for correct answers if ( $answer_status ) { echo "<div class='tick'><img src='".plugins_url( 'img/tick.png', __FILE__ )."' alt='Answer Status' /></div>"; } ?>
In the source code, you will see a DIV element with the class reply for creating the comment reply link. We need to insert our answer button status code right after that, as shown in the following code:
<div> <?php // Display the button for authors to make the answer as correct or incorrect if ( $show_answer_status ) { $question_status = ''; $question_status_text = ''; if ( $answer_status ) { $question_status = 'invalid'; $question_status_text = 'Mark as Incorrect'; } else { $question_status = 'valid'; $question_status_text = 'Mark as Correct'; } ?> <input type="button" value="<?php echo $question_status_text; ?>" class="answer-status answer_status-<?php echo $comment_id; ?>" data-ques-status="<?php echo $question_status; ?>" /> <input type="hidden" value="<?php echo $comment_id; ?>" class="hcomment" /> <?php } ?> </div>
If the $show_answer_status
variable is set to true
, we get the comment ID, which will be our answer ID, using the get_comment_ID
function. Then we get the status of the answer as true or false using the _wpwa_answer_status
key from the commentmeta
table. Based on the returned value, we define buttons for either Mark as Incorrect
or Mark as Correct
. Also, we specify some CSS classes and HTML5 data attributes to be used later with jQuery. Finally, we keep the comment ID in a hidden variable called hcomment
.
Once you include the code, the button will be displayed for the author of the question, as shown in the following screenshot:
Next, we need to implement the AJAX request for marking the status of the answer as true or false. Before that, we need to see how we can include our scripts and styles into WordPress plugins.
Here is the code for including custom scripts and styles for our plugin. Copy the following code into the wpwa-questions.php
file of your plugin:
function wpwa_frontend_scripts() { wp_enqueue_script( 'jquery' ); wp_register_script( 'wp-questions', plugins_url( 'js/questions.js', __FILE__ ), array('jquery'), '1.0',true ); wp_enqueue_script( 'wp-questions' ); wp_register_style( 'questions', plugins_url( 'css/questions.css', __FILE__ ) ); wp_enqueue_style( 'questions' ); $config_array = array( 'ajaxURL' => admin_url( 'admin-ajax.php' ), 'ajaxNonce' => wp_create_nonce( 'ques-nonce' ) ); wp_localize_script( 'wp-questions', 'wpwaconf', $config_array ); } add_action( 'wp_ajax_mark_answer_status', 'wpwa_mark_answer_status' );
WordPress comes in-built with an action hook called wp_enqueue_scripts
, for adding JavaScript and CSS files. wp_enqueue_script
is used to include script files into the page while wp_register_script
is used to add custom files. Since jQuery is built into WordPress, we can just use wp_enqueue_script
to include jQuery into the page. We also have a custom JavaScript file called questions.js
, which will contain the functions for our application.
Inside JavaScript files, we cannot access the PHP variables directly. WordPress provides a function called wp_localize_script
, to pass PHP variables into script files. The first parameter contains the handle of the script for binding data, which will be wp-questions
in this scenario. The second parameter is the variable name to be used inside JavaScript files to access these values. The third and final parameter will be the configuration array with the values.
Then we can include our questions.css
file using the wp_register_style
and wp_enqueue_style
functions, which will be similar to JavaScript, file inclusion syntax. Now everything is set up properly to create the AJAX request.
Saving the status of answers
Once the author clicks on the button, the status has to be saved to the database as true or false depending on the current status of the answer. Let's go through the jQuery code located inside the questions.js
file for making the AJAX request to the server.
$jq =jQuery.noConflict(); $jq(document).ready( function() { $jq(".answer-status").click( function() { // Get the button object and current status of the answer var answer_button = $jq(this); var answer_status = $jq(this).attr("data-ques-status"); // Get the ID of the clicked answer using hidden field var comment_id = $jq(this).parent().find(".hcomment").val(); var data = { "comment_id":comment_id, "status": answer_status }; // Create the AJAX request to save the status to database $jq.post( wpwaconf.ajaxURL, { action:"mark_answer_status", nonce:wpwaconf.ajaxNonce, data : data, }, function( data ) { if("success" == data.status){ /*Changes the display text of answer status button and toggles the answer status between valid and invalid to be displayed in frontend.*/ if("valid" == answer_status){ $jq(answer_button).val("Mark as Incorrect"); $jq(answer_button).attr("data-ques-status","invalid"); }else{ $jq(answer_button).val("Mark as Correct"); $jq(answer_button).attr("data-ques-status","valid"); } } }, "json"); }); });
The preceding code creates a basic AJAX request to the mark_answer_status
action. Most of the code is self-explanatory and code comments will help you to understand the process.
The important thing to note here is that we have used the configuration settings assigned in the previous section, using the wpwaconf
variable. Once the server returns the response with success status, the button will be updated to contain the new status and display the text.
The next step of this process is to implement the server-side code for handling AJAX requests. First, we need to define AJAX handler functions using the WordPress add_action
function. Since logged in users are permitted to mark the status, we don't need to implement the add_action
function for wp_ajax_nopriv_{action}
.
add_action( 'wp_ajax_mark_answer_status', 'wpwa_mark_answer_status' );
Implementation of the wpwa_mark_answer_status
function is given in the following code:
function wpwa_mark_answer_status() { $data = isset( $_POST['data'] ) ? $_POST['data'] : array(); $comment_id = isset( $data["comment_id"] ) ? absint($data["comment_id"]) : 0; $answer_status = isset( $data["status"] ) ? $data["status"] : 0; // Mark answers in correct status to incorrect // or incorrect status to correct if ("valid" == $answer_status) { update_comment_meta( $comment_id, "_wpwa_answer_status", 1 ); } else { update_comment_meta( $comment_id, "_wpwa_answer_status", 0 ); } echo json_encode( array("status" => "success") ); exit; }
We can get the necessary data from the $_POST
array and use it to mark the status of the answer using the update_comment_meta
function. This example contains the most basic implementation of the data saving process. In real applications, we need to implement necessary validations and error handling.
Now, the author of the question has the ability to mark answers as correct or incorrect, so we have implemented a nice and simple interface for creating a question-answer site with WordPress. The final task of the process will be the implementation of the questions list.
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
Generating the question list
Usually WordPress uses the archive.php
file of the theme for generating post lists of any type. We can use a file called archive-{post type}.php
for creating different layouts for different post types. In this file we are going to create a customized layout for our questions. Make a copy of the existing archive.php
file of the TwentyTwelve theme and rename it archive-wp_question.php
. In this file, you will find the following code section:
get_template_part( 'content', get_post_format() );
The TwentyTwelve theme uses a separate template for generating the content of each post type. We cannot modify the existing content.php
file as it affects all kinds of posts, so create custom templates called content-questions.php
by duplicating the content.php
file and change the preceding code to the following:
get_template_part( 'content-questions', get_post_format() );
Finally, we need to consider the implementation of the content-questions.php
file. In the questions list, only the question title will be displayed and therefore we don't need the content of the post, so we have to either remove or comment the functions the_excerpt
and the_content
in the template.
Then we also have to remove the twentytwelve_entry_meta
function and create our own metadata using the following code:
<div class="answer_controls"><?php comments_popup_link(__('No Answers ↓', 'responsive'), __('1 Answer ↓', 'responsive'), __('% Answers ↓', 'responsive')); ?> </div> <div class="answer_controls"> <?php wpwa_get_correct_answers(get_the_ID()); ?> </div> <div class="answer_controls"> <?php echo get_the_date(); ?> </div> <div style="clear: both"></div>
The first container will make use of the existing comments_popup_link
function to get the number of answers given for the questions. Then we need to display the number of correct answers for each question. The custom function called wpwa_get_correct_answers
is created to get the correct answers. The following code contains the implementation of the wpwa_get_correct_answers
function inside the plugin:
function wpwa_get_correct_answers( $post_id ) { $args = array( 'post_id' => $post_id, 'status' => 'approve', 'meta_key' => '_wpwa_answer_status', 'meta_value'=> 1, ); // Get number of correct answers for given question $comments = get_comments( $args ); printf(__('<cite class="fn">%s</cite> correct answers'), count( $comments ) ); }
We can set the array of arguments to include the conditions for retrieving the approved answers of each post, which also contains the correct answers. The number of results generated from the get_comments
function will be returned as correct answers. Now you should have a question list similar to the following screenshot:
Throughout this section we looked at how we can convert the existing functionalities of WordPress for building a simple question-answer interface. We took the quick-and-dirty path for this implementation by mixing the HTML and PHP code inside both themes and plugins.
Note
I suggest you go through the Chapter 1
source code folder and try this implementation on your own test server. This demonstration was created to show the flexibility of WordPress. Some of you might not understand the whole implementation. Don't worry as we will be developing a web application from scratch using detailed explanation in the upcoming chapters.
In the upcoming chapters, we'll see the limitations in this approach in complex web applications and how we can organize things better to write high quality, maintainable code.