Book Image

Wordpress Web Application Development - Third Edition

By : Rakhitha Nimesh Ratnayake
Book Image

Wordpress Web Application Development - Third Edition

By: Rakhitha Nimesh Ratnayake

Overview of this book

WordPress is one of the most rapidly expanding markets on the Web. Learning how to build complex and scalable web applications will give you the ability and knowledge to step into the future of WordPress. WordPress 4.7 introduces some exciting new improvements and several bug fixes, which further improve the entire development process.This book is a practical, scenario-based guide to expanding the power of the WordPress core modules to develop modular and maintainable real-world applications from scratch. This book consistently emphasizes adapting WordPress features into web applications. It will walk you through the advanced usages of existing features such as access controlling; database handling; custom post types; pluggable plugins; content restrictions; routing; translation; caching; and many more, while you build the backend of a forum management application. This book begins by explaining how to plan the development of a web application using WordPress' core features. Once the core features are explained, you will learn how to build an application by extending them through custom plugin development. Finally, you will explore advanced non-functional features and application integration. After reading this book, you will have the ability to develop powerful web applications rapidly within limited time frames.
Table of Contents (14 chapters)

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 a developer. Being human, we always prefer a practical approach to learning new things over the more conventional theoretical approach.

So, I will complete this chapter by converting default WordPress functionality into a simple question-answer interface, such as Stack Overflow, to give you a glimpse of what we will develop throughout this book.

Prerequisites for building a question-answer interface

We will be using version 4.7.2 as the latest stable version; this is available at the time of writing this book. I suggest that you set up a fresh WordPress installation for this book, if you haven't already done so.

Also, we will be using the Twenty Seventeen theme, which is available with default WordPress installation. Make sure that you activate the Twenty Seventeen 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 of each question
  • Customize the question list to include a 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 off, 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, a post can be considered as the question and 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 the custom post types plugin, which you can find at http://codex.wordpress.org/Post_Types#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 will create a plugin to implement the necessary tasks for our application:

  1. First off, create a folder called wpwa-questions inside the /wp-content/plugins folder and add a new file called wpwa-questions.php.
  2. Next, we need to add the block comment to define our file as a plugin:
        /* 
Plugin Name: WPWA Questions
Plugin URI: -
Description: Question and Answer Interface using WordPress Custom Post Types and Comments
Version: 1.0
Author: Rakhitha Nimesh
Author URI: http://www.wpexpertdeveloper.com/
License: GPLv2 or later
Text Domain: wpwa-questions
*/
  1. Having created the main plugin file, we can now create the structure of our plugin with the necessary settings as shown in the following code section:
        if( !class_exists( 'WPWA_Questions' ) ) {     
class WPWA_Questions{
private static $instance;
public static function instance() {

if ( ! isset( self::$instance ) && ! ( self::$instance instanceof WPWA_Questions ) ) {
self::$instance = new WPWA_Questions();
self::$instance->setup_constants();
self::$instance->includes();

add_action('admin_enqueue_scripts',array(self::$instance,'load_admin_scripts'),9);
add_action('wp_enqueue_scripts',array(self::$instance,'load_scripts'),9);

}
return self::$instance;
}
public function setup_constants() {
if ( ! defined( 'WPWA_VERSION' ) ) {
define( 'WPWA_VERSION', '1.0' );
}
if ( ! defined( 'WPWA_PLUGIN_DIR' ) ) {
define('WPWA_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
}
if ( ! defined( 'WPWA_PLUGIN_URL' ) ) {
define( 'WPWA_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
}
}
public function load_scripts(){ }
public function load_admin_scripts(){ }
private function includes() { }
public function load_textdomain() { }
}
}
  1. First, we create a class called WPWA_Questions as the main class of the question-answer plugin. Then we define a variable to hold the instance of the class and use the instance function to generate an object from this class. This static function and the private instance variables make sure that we only have one instance of our plugin class. We have also included the necessary function calls and filters in this function.
  2. These functions are used to handle the most basic requirements of any WordPress plugin. Since they are common to all plugins, we keep these functions inside the main file. Let's look at the functionality of these functions:
  • setup_constants: This function is used to define the constants of the application such as version, plugin directory path, and plugin directory URL
  • load_scripts: This function is used to load all the plugin specific scripts and styles on the frontend of the application
  • load_admin_scripts: This function is used to load all the plugin specific scripts and styles on the backend of the application
  • includes: This function is used to load all the other files of the plugin
  • load_text_domain: This function is used to configure the language settings of the plugin
  1. Next, we initialize the plugin by calling the following code after the class definition:
        function WPWA_Questions() {
global $wpwa;
$wpwa = WPWA_Questions::instance();
}
WPWA_Questions();
  1. Having created the main plugin file, we can move into creating a custom post type called wpwa-question using the following code snippet. Include this code snippet in your WPWA_Questions class file of the plugin:
        public function register_wp_questions() { 
$labels = array(
'name' => __( 'Questions', 'wpwa_questions' ),
'singular_name' => __( 'Question',
'wpwa_questions'),
'add_new' => __( 'Add New', 'wpwa_questions'),
'add_new_item' => __( 'Add New Question',
'wpwa_questions'),
'edit_item' => __( 'Edit Questions',
'wpwa_questions'),
'new_item' => __( 'New Question',
'wpwa_questions'),
'view_item' => __( 'View Question',
'wpwa_questions'),
'search_items' => __( 'Search Questions',
'wpwa_questions'),
'not_found' => __( 'No Questions found',
'wpwa_questions'),
'not_found_in_trash' => __( 'No Questions found in
Trash', 'wpwa_questions'),
'parent_item_colon' => __( 'Parent Question:',
'wpwa_questions'),
'menu_name' => __( 'Questions', 'wpwa_questions'),
);
$args = array(
'labels' => $labels,
'hierarchical' => true,
'description' => __( 'Questions and Answers',
'wpwa_questions'),
'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( 'wpwa_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 as the roles 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 to have a look at documentation on custom post creation at http://codex.wordpress.org/Function_Reference/register_post_type.

Beginner to intermediate level developers and designers tend to include the logic inside the functions.php file in the theme. This is considered a bad practice as it becomes extremely difficult to maintain because 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 later chapters.
  1. Then, you have to add the following code inside the instance function of WPWA_Questions class to initialize the custom post type creation code:
        add_action('init',
array(self::$instance,'register_wp_questions'));
  1. Once the code is included, you will get a new section on the admin area for creating questions. This section will be similar to the posts section inside the WordPress admin. Add a few questions and insert some comments using different users before we move into the next stage.

Before we go into the development of questions and answers, we need to make some configurations so that our plugin works without any issues. Let's look at the configuration process:

  1. First, we have to look at the comment-related settings inside Discussion Settings in the WordPress Settings section. Here, you can find a setting called Before a comment appears.
  2. Disable both checkboxes so that users can answer and get their answers displayed without the approval process. Depending on the complexity of application, you can decide whether to enable these checkboxes and change the implementation.
  1. The second setting we have to change is the Permalinks. Once we create a new custom post type and view it in a browser, it will redirect you to a 404 page not found page. Therefore, we have to go to the Permalinks section of WordPress Settings and update the Permalinks using the Save Changes button. This won't change the Permalinks. However, this will flush the rewrite rules so that we can use the new custom post type without 404 errors.

Now, we can start working with the answer-related features.

Customizing the comments template

Usually, the comments section is designed to show the comments of a normal post.

While using comments for custom features such as answers, we need to customize the existing template and use our own designs by performing the following steps:

  1. So, open the comments.php file inside the Twenty Seventeen theme.
  2. Navigate through the code and you will find a code section similar to the following one:
        wp_list_comments( array( 
'avatar_size' => 100,
'style' => 'ol',
'short_ping' => true,
'reply_text' => twentyseventeen_get_svg( array( 'icon' => 'mail- reply' ) ) . __( 'Reply', 'twentyseventeen' ),
) );
  1. We need to modify this section of code to suit the requirements of the answers list. The simplest method is to edit the comments.php file of the theme and add the custom code changes. However, modifying core theme or plugin files is considered a bad practice since you lose the custom changes on theme or plugin updates. So, we have to provide this template within our plugin to make sure that we can update the theme when needed.
  2. Let's copy the comments.php file from the Twenty Seventeen theme to the root of our plugin folder. 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 will change the previous implementation to the following in the comments.php file we added inside our plugin:
        <?php 
global $wpwa;

if(get_post_type( $post ) == "wpwa_question"){
wp_list_comments(array('avatar_size' => 100 , 'type' => 'comment', 'callback' => array($wpwa, 'comment_list')));

}else{
wp_list_comments( array(
'avatar_size' => 100,
'style' => 'ol',
'short_ping' => true,
'reply_text' => twentyseventeen_get_svg( array( 'icon' => 'mail-reply' ) ) . __( 'Reply', 'twentyseventeen' ),
) );
}
?>
  1. Here, we will include a conditional check for the post type in order to choose the correct answer list generation function. When the post type is wpwa_question, we call the wp_list_comments function with the callback parameter defined as comment_list, which will be the custom function for generating the answers list.
Arguments of the wp_list_comments function can be either an array or string. Here, we have preferred array-based arguments over string-based arguments.
  1. We have a custom comments.php file inside the plugin. However, WordPress is not aware of the existence of this file and hence will load the original comments.php file within the theme. So, we have to include our custom template by adding the following filter code to instance the function:
        add_filter('comments_template',  
array(self::$instance,'load_comments_template'));
  1. Finally, we have to implement the load_comments_template function inside the main class of the plugin to use our comments template from the plugins folder:
        public function load_comments_template($template){ 
return WPWA_PLUGIN_DIR.'comments.php';
}

In the next section, we will be completing the customization of the comments template by adding answer statuses and allowing users to change the statuses.

Changing the status of answers

Once the users provide their answers, the creator of the question should be able to mark them as correct or incorrect answers. So, we will implement a button for each answer to mark the 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.

Implementation of the comment_list function goes inside the main class of wpwa-questions.php file of our plugin. This function contains lengthy code, which is not necessary for our explanations. Hence, I'll be explaining the important sections of the code. It's ideal to work with the full code for the comment_list function from the source code folder:

        function comment_list( $comment, $args, $depth ) { 
global $post;
$GLOBALS['comment'] = $comment;

$current_user = wp_get_current_user();
$author_id = $post->post_author;
$show_answer_status = false;

if ( is_user_logged_in() && $current_user->ID == $author_id ){
$show_answer_status = true;
}
$comment_id = get_comment_ID();
$answer_status = get_comment_meta( $comment_id,
"_wpwa_answer_status", true );

// Rest of the Code
}

The comment_list function 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 be only visible to the creator of the question.

In order to change the status of answers, follow these steps:

  1. First, we will 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.
  2. Next, we will check whether the logged-in user created the question. If so, we will set show_answer_status to true. Also, we have to retrieve the status of the current answer by passing the comment_id and _wpwa_answer_status keys to the get_comment_meta function.
  3. Then, we will have to include the common code for generating a comments list with the necessary condition checks.
  4. Open the wpwa-questions.php file of the plugin and go through the rest of the comment_list function to get an idea of how the comments loop works.
  5. Next, we have to highlight the correct answers of 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>";
}
?>
  1. In the source code, you will see a <div> element with the class reply for creating the comment reply link. We will need to insert our answer button status code right after this, 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','wpwa_questions');
} else {
$question_status = 'valid';
$question_status_text = __('Mark as
Correct','wpwa_questions');
}
?>
<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>
  1. 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 will get the status of answer as true or false using the _wpwa_answer_status key from the wp_commentmeta table.
  2. Based on the returned value, we will define buttons for either Mark as Incorrect or Mark as Correct. Also, we will specify some CSS classes and HTML5 data attributes to be used later with jQuery.
  3. Finally, we keep the comment_id in a hidden variable called hcomment.
  1. Once you include the code, the button will be displayed for the author of the question, as shown in the following screen:
  1. Next, we need to implement the AJAX request for marking the status of the answer as true or false.

Before this, we need to see how we can include our scripts and styles into WordPress plugins. We added empty functions to include the scripts and styles while preparing the structure of this plugin. Here is the code for including custom scripts and styles for our plugin inside the load_scripts function we created earlier. Copy the following code into the load_scripts function of the wpwa-questions.php file of your plugin:

    public function load_scripts() { 
wp_enqueue_script( 'jquery' );
wp_register_script( 'wpwa-questions', plugins_url(
'js/questions.js', __FILE__ ), array('jquery'), '1.0', TRUE );
wp_enqueue_script( 'wpwa-questions' );
wp_register_style( 'wpwa-questions-css', plugins_url(
'css/questions.css', __FILE__ ) );
wp_enqueue_style( 'wpwa-questions-css' );
$config_array = array(
'ajaxURL' => admin_url( 'admin-ajax.php' ),
'ajaxNonce' => wp_create_nonce( 'ques-nonce' )
);
wp_localize_script( 'wpwa-questions', 'wpwaconf', $config_array
);
}

WordPress comes with an action hook built-in called wp_enqueue_scripts for adding JavaScript and CSS files. The wp_enqueue_script action is used to include script files into the page while the wp_register_script action is used to add custom files. Since jQuery is built-in to WordPress, we can just use the wp_enqueue_script action 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 parameters 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, we discussed previously. Now, everything is set up properly to create the AJAX request.

Saving the status of answers

Once the author clicks 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:

    jQuery(document).ready(function($) { 
$(".answer-status").click( function() {
$(body).on("click", ".answer-status" , function() {

var answer_button = $(this);
var answer_status = $(this).attr("data-ques-status");
// Get the ID of the clicked answer using hidden field
var comment_id = $(this).parent().find(".hcomment").val();
var data = {
"comment_id":comment_id,
"status": answer_status
};

$.post( wpwaconf.ajaxURL, {
action:"mark_answer_status",
nonce:wpwaconf.ajaxNonce,
data : data,
}, function( data ) {
if("success" == data.status){
if("valid" == answer_status){
answer_buttonval("Mark as Incorrect");
answer_button.attr("
data-ques-status","invalid");
}else{
answer_button.val("Mark as Correct");
answer_button.attr("
data-ques-status","valid");
}
}
}, "json");
});
});

Let's understand the implementation of saving the answer status. This code snippet executes every time a user clicks the button to change the status. We get the current status of the answer by using the data-ques-status attribute of the button. Next, we get the ID of the answer using the hidden field with hcomment as the CSS class. Then, we send the data through an AJAX request to the mark_answer_status action.

Once the AJAX response is received, we display the new status of the answer and update the data attribute of the button with the new status.

The important thing to note here is that we have used the configuration settings assigned in the previous section, using the wpwaconf variable. Once a server returns the response with success status, the button will be updated to contain the new status and display text.

The next step of this process is to implement the server-side code for handling the AJAX request. First, we need to define AJAX handler functions using the WordPress add_action function. Since only logged-in users are permitted to mark the status, we don't need to implement the add_action function for wp_ajax_nopriv_{action}. Let's add the AJAX action to the instance function of the main class of the plugin:

    add_action('wp_ajax_mark_answer_status',  
array(self::$instance,'mark_answer_status'));

Then, we need to add the mark_answer_status function inside the main class. Implementation of the mark_answer_status function is given in the following code:

    function 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 who asked 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.

Generating a 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. Here, we will create a customized layout for our questions. The simplest solution is to copy the archive file and create a new file called archive-{post type}.php inside the theme. However, it's not the ideal way to create a custom template due to the reasons we discussed while creating the comments.php file. So, we have to use a plugin specific template and override the default behavior of the archive file. Refer to the following steps:

  1. Make a copy of the existing archive.php file of the TwentySeventeen theme, copy it to the root folder of the plugin, and rename it questions-list-template.php. Here, you will find the following code section:
        get_template_part( 'template-parts/post/content', get_post_format() ); 
  1. The TwentySeventeen theme uses a separate template for generating the content of each post type. We have to replace this with our own template inside the plugin. The get_template_part function is only used to load the theme specific template. So, we can't use it to load a template file from a plugin. The solution is to include the template file using the following code:
        require WPWA_PLUGIN_DIR . 'content.php'; 
  1. Next, we have to create the content template by copying the content.php file of the theme from the template-parts/post/content folder to the root folder of our plugin.
  2. Finally, we need to consider the implementation of the content.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 the_excerpt and the_content functions of the template. We can comment the following line within this template:
        the_content( sprintf( 
__( 'Continue reading<span class="screen-reader-text"> "%s"</span>', 'twentyseventeen' ), get_the_title()
) );
  1. Then, we will create our own metadata by adding the following code to the <div> element with the entry-content class:
        <div class="answer_controls"><?php 
comments_popup_link(__('No Answers &darr;', 'twentyseventeen '),
__('1 Answer &darr;', 'responsive'), __('% Answers &darr;',
'twentyseventeen')); ?>
</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.

  1. Then, we need to display the number of correct answers of each question. The custom function called get_correct_answers is created to get the correct answers. The following code contains the implementation of the get_correct_answers function inside the main class of the plugin:
        function 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 to retrieve the approved answers of each post, which contains the correct answers. The number of results generated from the get_comments function will be returned as correct answers.

We have completed the code for displaying the questions list. However, you will still see a list similar to the default posts list. The reason for that is we created a custom template and WordPress is not aware of its existence. So, the default template file is loaded instead of our custom template.

  1. We need to override the existing archive template of WordPress by adding the following action to the instance function of the main plugin class:
        add_filter( 'archive_template',  
array(self::$instance,'questions_list_template' ));
  1. Here, we have the implementation of the questions_list_template function inside the main class of the plugin:
        public function questions_list_template($template){ 
global $post;

if ( is_post_type_archive ( 'wpwa_question' ) ) {
$template = WPWA_PLUGIN_DIR . '/questions-list-template.php';
}
return $template;
}

This function overrides the archive template when the questions list is displayed and keeps the default template for posts lists or any other custom post type lists.

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 HTML and PHP code inside both, themes and plugins.

I suggest that you go through the Chapter 1, WordPress as a Web Application Framework, 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 develop a web application from scratch using a detailed explanation in the following chapters.