What We’ll Do
In this first part, we’ll set up the back end of our plugin. This will involve setting up the files and folders as well as implementing all of the features our plugin requires in PHP. We’ll need to:
- Register a Custom Post Type – for our Questions
- Add a meta box that will allow us to enter answers on the same page
- Save information from the meta boxes when the post is saved
- Save information from our ajax requests (via Backbone)
Then in the Second Part…
- Outputting base HTML for the meta box
- Outputting a client-side template along with the answers in JSON
I hope this small series sounds interesting to you, and I’m looking forward to helping you get up and running with using Backbone.js within a WordPress plugin.
What We’ll Build
This small plugin will use a Custom Post Type to save Questions. Then in a meta box, we’ll create four inputs that will allow users to enter possible answers to the current question and select which of those is the correct answer. When an answer is changed, the corresponding save button will become active. When clicked, we’ll use Backbone’s built in
model.save() method to save the data back to the WordPress database. Also, when the answers are being written in the inputs, the select box below it will automatically update its values as it will be looking out for changes to the models. All of these things are relatively trivial to do with Backbone and after reading this tutorial, you’ll be able to start taking your plugins to the next level by using them within WordPress.
There’s a lot to cover, so let’s get started!
1. Create the Plugin
We need to do all the usual first steps involved with any plugin – create the files folders.
- Create a folder called wp_quiz
- Create a PHP file inside with the same name
- Create a folder called js
- Create a folder called src
Your folder structure should look like this.
2. Add the Plugin Header
Inside of wp_quiz.php.
/* Plugin Name: WP Quiz Plugin URI: http://wp.tutsplus.com/author/shaneosbourne/ Description: An example of using Backbone within a plugin. Author: Shane Osbourne Version: 0.1 Author URI: http://wp.tutsplus.com/author/shaneosbourne/ */
3. Add Hooks to Instantiate the Plugin
Still inside of wp_quiz.php, we need to do the following things:
- Include our main plugin class
- Create a function that will create an instance of the class
- Add a hook to only call the function when the user is an admin
/** wp_quiz.php **/ include 'src/WpQuiz.php'; // Class File // Create an instance of the Plugin Class function call_wp_quiz() return new WpQuiz( 'admin' ); // Only when the current user is an Admin if ( is_admin ) add_action( 'init', 'call_wp_quiz' ); // Helper function if ( ! function_exists( 'pp' ) ) function pp() return plugin_dir_url( __FILE__ ); }
Putting the helper function
pp() within this file will allow us to reference other files relative to the root of the plugin folder (you’ll see that in action shortly).
4. Create the Plugin Class
Inside of the src folder, create a file called WpQuiz.php.
In this plugin class, we’ll be needing a few different methods to accomplish all of the following:
- Register the Custom Post Type
- Add a meta box
- Retrieve the content for the meta box and output both HTML and some JSON data into it
- Listen for PUT requests and save data to the database
- Save our meta box data upon regular ‘save’ actions
Before we write the methods though, we’re going to be storing some information as class properties. We store this information at the top of our class file so that modifications are easier to make later on. The
answerIds array contains the keys that we’ll be using throughout this plugin to save data using the built-in
Add the Properties
Add the Constructor
- First we register the Custom Post Type using another helper method (which will be seen later on)
- Then we are registering a hook to load our meta box
- We also need a separate method for accepting our ajax requests
- Finally, when a page is loaded we’ll want to save info from our meta box
/** src/WpQuiz.php **/ public function __construct( $type ) switch ( $type ) case 'admin' : // Register the Post Type $this->registerPostType( $this->postTypeNameSingle, $this->postTypeNamePlural ); // Add the Meta Box add_action( 'add_meta_boxes', array( $this, 'addMetaBox' ) ); // Accept an Ajax Request add_action( 'wp_ajax_save_answer', array( $this, 'saveAnswers' ) ); // Watch for Post being saved add_action( 'save_post', array( $this, 'savePost' ) ); }
Add the Meta Box
- Create a unique ID for this plugin based on the name of the post type
- Add the meta box using the properties we set earlier
Get the Meta Box Content
Here we are looping through our Answer IDs and constructing an array that contains post meta fetched with our helper method
getOneAnswer. We make this new array so that we can encode it and send it to our template in JSON format – just the way Backbone likes it. We send data to our template using the
$viewData array seen below. This keeps all of the HTML out of harm’s way and allows us to work on it in a separate file. We’ll take a quick look at the
getTemplatePart method later on, but if you want an in-depth explanation about why I use it, please check out Improving Your Work-Flow – Separate Your Mark-Up From Your Logic!
/** src/WpQuiz.php **/ public function getMetaBox( $post ) // Get the current values for the questions $json = array(); foreach ( $this->answerIds as $id ) $json = $this->getOneAnswer( $post->ID, $id ); // Set data needed in the template $viewData = array( 'post' => $post, 'answers' => json_encode( $json ), 'correct' => json_encode( get_post_meta( $post->ID, 'correct_answer' ) ) ); echo $this->getTemplatePart( $this->metaBoxTempl, $viewData ); }
Get a Single Answer – Helper
We are just returning an array of the data needed in our template. You can think of this as creating a single model that is needed on the front end.
/** src/WpQuiz.php **/ public function getOneAnswer( $post_id, $answer_id ) return array( 'answer_id' => $answer_id, 'answer' => get_post_meta( $post_id, $answer_id, true ) );
When a user clicks to save a post that our meta box is currently in, we need to do a couple of checks to ensure we are saving our Custom Post Type and that the current user has the correct permissions – if both checks are ok then we save the four answers from the meta box and the correct answer.
/** src/WpQuiz.php **/ public function savePost( $post_id ) // Check that we are saving our Custom Post type if ( $_POST'post_type' !== strtolower( $this->postTypeNameSingle ) ) return; // Check that the user has correct permissions if ( ! $this->canSaveData( $post_id ) ) return; // Access the data from the $_POST global and create a new array containing // the info needed to make the save $fields = array(); foreach ( $this->answerIds as $id ) $fields$id = $_POST$id; // Loop through the new array and save/update each one foreach ( $fields as $id => $field ) add_post_meta( $post_id, $id, $field, true ); // or update_post_meta( $post_id, $id, $field ); // Save/update the correct answer add_post_meta( $post_id, 'correct_answer', $_POST'correct_answer', true ); // or update_post_meta( $post_id, 'correct_answer', $_POST'correct_answer' ); }
Save Answers From Ajax Requests
This is where we will receive data passed to the server from Backbone. We need to:
- Access data sent as a PUT request. As it will be in JSON format, we need to decode it
- Again check that the current user has relevant permissions
- Go ahead and attempt the save
- If either Add or Update was successful, we can simply return the newly saved data and Backbone will view this as a successful save
- If neither were successful, we just return 0 to indicate a failure
/** src/WpQuiz.php **/ public function saveAnswers() // Get PUT data and decode it $model = json_decode( file_get_contents( "php://input" ) ); // Ensure that this user has the correct permissions if ( ! $this->canSaveData( $model->post_id ) ) return; // Attempt an insert/update $update = add_post_meta( $model->post_id, $model->answer_id, $model->answer, true ); // or $update = update_post_meta( $model->post_id, $model->answer_id, $model->answer ); // If a save or update was successful, return the model in JSON format if ( $update ) echo json_encode( $this->getOneAnswer( $model->post_id, $model->answer_id ) ); else echo 0; die();
The Helper Methods
Here are the four helpers mentioned in the above snippets.
canSaveData()– This just ensures that the current user has the relevant permissions to edit / update this post.
addScripts()– Note that here we are ensuring that we pass the 5th param to the
registerPostType()– This is something I use often in plugins. It just makes life easier when adding a new Custom Post Type. It accepts both singular and plural versions of the name because it’s not always as easy as just adding an ‘s’.
getTemplatePart()– I’ve never been fond of having mark-up inside of my methods. This little helper will allow the use of a separate template file.
/** src/WpQuiz.php **/ /** * Determine if the current user has the relevant permissions * * @param $post_id * @return bool */ private function canSaveData( $post_id ) if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return false; if ( 'page' == $_POST'post_type' ) if ( ! current_user_can( 'edit_page', $post_id ) ) return false; else if ( ! current_user_can( 'edit_post', $post_id ) ) return false; return true; } private function addScripts() wp_register_script( 'wp_quiz_main_js', pp() . $this->jsAdmin , array( 'backbone' ), null, true ); wp_enqueue_script( 'wp_quiz_main_js' ); /** * Register a Custom Post Type * * @param $single * @param $plural * @param null $supports */ private function registerPostType( $single, $plural, $supports = null ) $labels = array( 'name' => _x( $plural, 'post type general name' ), 'singular_name' => _x( "$single", 'post type singular name' ), 'add_new' => _x( "Add New $single", "$single" ), 'add_new_item' => __( "Add New $single" ), 'edit_item' => __( "Edit $single" ), 'new_item' => __( "New $single" ), 'all_items' => __( "All $plural" ), 'view_item' => __( "View $single" ), 'search_items' => __( "Search $plural" ), 'not_found' => __( "No $plural found" ), 'not_found_in_trash' => __( "No $single found in Trash" ), 'parent_item_colon' => '', 'menu_name' => $plural ); $args = array( 'labels' => $labels, 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => true, 'capability_type' => 'post', 'has_archive' => true, 'hierarchical' => false, 'menu_position' => null, 'supports' => ( $supports ) ? $supports : array( 'title', 'editor', 'page-attributes' ) ); register_post_type( $single, $args ); /** * Render a Template File * * @param $filePath * @param null $viewData * @return string */ public function getTemplatePart( $filePath, $viewData = null ) ( $viewData ) ? extract( $viewData ) : null; ob_start(); include ( "$filePath" ); $template = ob_get_contents(); ob_end_clean(); return $template;
5. On to the Front End