Welcome to the second part of the series. In the first article, we explained the WordPress Coding Standards, how to avoid namespaces collisions, comments in the code, and some basic security tips.
Today, we are going to go a bit deeper and write some more code and learn some techniques to improve performance and security of our plugins. The topics we’ll cover are as follows:
- How and when should to include my scripts/styles
- How to properly performing Ajax calls
- Filters and action to give freedom to your users
Grab your code editor and get ready to play with WordPress!
How and When Should I Include My Scripts?
The first thing you have to keep in mind is that WordPress has two wonderful functions (wp_enqueue_script
and wp_enqueue_style
) to let you include your scripts and styles. To that end, please don’t add them manually in the header or using wp_head
action hook.
If you use the WordPress methods, caching and minifying plugins will be able to include your scripts in their functions, you will be able to choose to place them in footer or header very easily, you will be able to add dependent scripts and also use all tricks explained below along with other features.
Incorrect:
add_action( 'wp_head' , 'my_custom_scripts' );
function my_custom_scripts() echo '<script src="src/to/my.js"></script>';
Correct:
add_action( 'wp_enqueue_scripts', 'my_customs_scripts' );
function my_customs_scripts() wp_enqueue_script( 'script-handler', get_template_directory_uri() . '/js/my.js', array('jquery'), '1.0.0', true );
I won’t give you full a tutorial on how to use wp_enqueue_xxxx as there are many of them available (and the Codex is a great resource!) and I’m trying to simply point out how you should add scripts in your themes or plugins.
What you need to always have in mind is to leave the lightest footprint possible. This means that you shouldn’t include files where they are not supposed to be.
You may think “it’s just a file, it won’t affect the website” and with that mindset it’s like throwing a paper in the ground in the park because it’s just a single piece of paper, but that’s not the way things work: If all developers leave their scripts everywhere, the final user will end with a totally bloated site and we don’t want that.
Now that we’ve covered that, that let’s see some tips on how to include your scripts only when needed.
1. Never Include Front End Files in the Backend and Vice Versa
// FRONT END ONLY
add_action( 'wp_enqueue_scripts', 'my_front_customs_scripts' );
function my_customs_scripts() wp_enqueue_script( 'script-handler', get_template_directory_uri() . '/js/my.js', array('jquery'), '1.0.0', true ); // BACKEND ONLY
add_action( 'admin_enqueue_scripts', 'my_back_customs_scripts' );
function my_customs_scripts() wp_enqueue_script( 'script-handler', get_template_directory_uri() . '/js/my.js', array('jquery'), '1.0.0', true );
But wait! Let’s go one step further and only include scripts in the pages you really need them. A good method is to register them first and then enqueue in the needed pages
2. Include Files Only on the Needed Pages
//FRONT END ONLY
add_action( 'wp_enqueue_scripts', 'my_front_customs_scripts' );
function my_customs_scripts() wp_register_script( 'script-handler', get_template_directory_uri() . '/js/my.js', array('jquery'), '1.0.0', true ); if( is_page('my-page') ) wp_enqueue_script( 'script-handler' ); // Be creative to include files only when needed // if( is_single() ) // if( is_home() ) // if( 'cpt-name' == get_post_type() ) //BACKENDEND ONLY
add_action( 'admin_enqueue_scripts', 'my_back_customs_scripts' );
function my_customs_scripts( $hook ) wp_register_script( 'script-handler', get_template_directory_uri() . '/js/my.js', array('jquery'), '1.0.0', true ); //To include it only when editing a post if( 'edit.php' == $hook ) wp_enqueue_script( 'script-handler' ); // If you added an options page like this // $plugin_screen_id = add_options_page(...) // you can do $screen = get_current_screen(); if ( $plugin_screen_id == $screen->id ) wp_enqueue_script( 'script-handler' );
/* Another way to use screen id */
add_action( 'admin_print_styles-' . $plugin_screen_id, 'my_back_customs_scripts' );
3. Are You Enqueueing Scripts to Use With Shortcodes?
//FRONT END ONLY
add_action( 'wp_enqueue_scripts', 'my_front_customs_scripts' );
function my_customs_scripts() wp_register_script( 'script-handler', get_template_directory_uri() . '/js/my.js', array('jquery'), '1.0.0', true ); // if you need a script to be included for a shortcode // Don't add it everywhere // Instead you can include it only when needed global $post; if( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'custom-shortcode') ) wp_enqueue_script( 'script-handler');
4. Include Styles With Conditionals
While the other code snippets applies to both, scripts and styles, the following will only work with wp_enqueue_style
(at least for now).
//FRONT END ONLY
add_action( 'wp_enqueue_scripts', 'my_front_customs_styles' );
function my_front_customs_styles() wp_register_style( 'my-plugin-css', plugins_url( 'my-plugin/css/plugin.css' ) ); // Let's include this css only for old (and crappy) browsers wp_enqueue_style( 'my-plugin-css' ); global $wp_styles; $wp_styles->add_data('my-plugin-css', 'conditional', 'lte IE 9');
You can see some more examples in the Codex. Another excellent example that you can use to start all your plugins is the WordPress Plugin Boilerplate. Take a look at the code to see how scripts and styles are included. The Wpfavs plugin (based of WordPress Plugin Boilerplate) it’s a good example on how to create a plugin for the backend of WordPress.
2. How to Properly Perform Ajax Calls
For this topic I will share some bad habits that I seen a lot when doing Ajax in WordPress and they could be divided in the following sentences:
- Ajax calls shouldn’t be done directly to a file
- Ajax calls should use a nonce value
- Check for user permissions, if needed
- JavaScript variables should be included with
wp_localize_script
I know jQuery makes Ajax calls a piece of cake, and we can just simply create a file called ajax.php
, includewp-load.php
on it and do our Ajax there. But that’s not the “WordPress way” - it’s not future-proof. Even more so, it’s less secure and you will miss out on a lot of WordPress functionality.
The correct way is to use the wp_ajax_my_action
and wp_ajax_nopriv_my_action
action hooks. The main difference between the two is that the first one is used for logged users and the second one is used for non-logged users.
Note that ”my_action” should be changed for your action name. We will see how that works in a moment.
Let’s see all this in a simple example with a bit of code and imagination. Let’s imagine that we want to display 5 posts when any user (logged or not ) clicks on a button. We are going to name this action as cool_ajax_example
, so let’s start adding the Ajax callback functions that will return the data.
You can include this code on your functions.php
or inside your plugin.
//First we add the actions hooks
add_action('wp_ajax_cool_ajax_example', 'cool_ajax_example_callback' );
add_action('wp_ajax_nopriv_cool_ajax_example', 'cool_ajax_example_callback' ); function cool_ajax_example_callback() ...
As you can see both hooks use the same callback function that will return the data. Also notice that our action name its at the end of the hook name wp_ajax_cool_ajax_example
It’s very important to write the action name right everywhere or this won’t work at all.
Before we continue with the callback function let’s move to the jQuery needed for the Ajax call. The jQuery script will fire when we press a simple HTML button, and we need to send that request to the admin-ajax.php
file that it’s the script that handle all AJAX requests in WordPress. Also we need to add a nonce as we already said that we want to make it secure so here comes to action the wp_localize_script
function.
If you included your scripts the right way as explained at the beginning of this article you have a script handler name ( In our examples above ‘script-handler’ ), so let’s use it to pass our PHP values to our javascript file. You can include this bit of code right after the wp_enqueue_function
used to include the JavaScript.
wp_localize_script( 'script-handler', 'MyVarName', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'return_posts' ) ) );
As you can see wp_localize_script
is a pretty easy way to pass any PHP variable to our JavaScript files and it will print valid xhtml because of the <![CDATA[
tags. The code above will be printed in the header when the JavaScript file with script-handler
as name is loaded and it will look something like:
<script type="text/javascript">/* <![CDATA[ */
var myVarName = "ajax_url":"http:\/\/www.tutsplus.com\/wp-admin\/admin-ajax.php","nonce":"dsfsdf123r";
/* ]]> */</script>
Now it's time to create our javascript file, let's call it my.js
and it will look something like:
(function( $ ) $(function() $('#button').click(function(e) e.preventDefault(); $.ajax( url: myVarName.ajax_url, data: action: 'cool_ajax_example', nonce : myVarName.nonce , success: function( response ) $('#response-container').html( response ); , type: "POST", ); ); );
)( jQuery );
Please note how we are using the nonce and ajax_url
that we passed with wp_localize_script
. Also check that we are passing a POST value called "action" that matches the action name we used in the wp_ajax
hooks.
Now it's time to finish our callback function. The first thing we need to do is check that the nonce is right and then we can return some posts.
function cool_ajax_example_callback() $nonce = $_POST['nonce']; //The first thing we do is check the nonce and kill the script if wrong if ( ! wp_verify_nonce( $nonce, 'return_posts' ) ) die ( 'Wrong nonce!'); $args = array( 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 5, ); // The Query $the_query = new WP_Query( $args ); // The Loop if ( $the_query->have_posts() ) echo '<div class="myposts">'; while ( $the_query->have_posts() ) $the_query->the_post(); echo '<h3>' . get_the_title() . '</h3>'; echo '</div>'; else echo 'Posts not found'; /* Restore original Post Data */ wp_reset_postdata(); // don't forget this // We are doing a simple call we don't want anything else to run die();
If case that you need to something more complex like deleting a post always check for user permissions, as I mentioned at the begging of this topic. For example, if you want only admins to do certain action you can do in your script something like:
if( current_user_can( 'administrator' ) ) wp_delete_post( $postid, $force_delete );
With all the tips above you are now a master in WordPress Ajax and you can perform any action you want. Try playing with the above and adjust it to your needs. I personally like using json
datatype and I do echo json_encode()
instead of doing simple echo's on my scripts, but that's another matter.
3. Filters and Actions
The purpose of this section won't be to teach you how filters and actions work, there are excellent tutorials around that topic that explain it in great detail. What I'm going to explain your here is why you should add filters and actions on your plugins and show you some examples for easy understanding.
But first let's do an introduction to these concepts:
- Hooks: An action hook is added at a certain point of your plugin, usually around important actions ( Eg: before content, after content ). Any user can then "hook" into it with functions in order to have their code executed at that point. When an action hook runs, all functions that are connected, or "hooked", to it will run as well.
- Filters: A filter hook is also placed in your plugin for other functions to tie into. On this case filters allow data to be manipulated or modified before it is used. So you usually place it with variables that you want to let users manipulate.
Check the Plugin API for more information on action hooks and filters.
Why I Should Make My Plugin Extensible?
It's plain simple: Because it makes your plugin better. That way developers and normal users all together will be able to extend, adapt or improve your plugin beyond it original purpose without affecting the core of it.
Take for example an eCommerce plugin. Because of the hooks and filters you are able to create new plugins that hooks into it, like for example new payment gateways, shipping functions, and much much more.
Sounds Nice, but Where I Should Add Them on My Plugin?
Don't get crazy adding actions and filters everywhere. At the beginning you will find this a bit difficult or annoying, so try to think like if you were another developer looking at a new plugin and ask yourself "Do I need here an action hook?". Also a huge percent of action and filters will be added on demand once you start getting support request (yes, you always get them!) or feedback from your users.
Like everything in life, once this becomes an habit you will see much clearly where to include them.
A few recommendations for where to include filters:
- When arrays are setup, it's a good option to add a filter to let users modify them.
- When data objects are setup same thing happens. You may want to let users modify the object before it's used
- When data strings are setup you could add filters to let users change them.
Let's use the callback function used on this article to see an example of the recommendations above. To create filterable variables on this case we use apply_filters()
function cool_ajax_example_callback() $nonce = $_POST['nonce']; //The first thing we do is check the nonce and kill the script if wrong if ( ! wp_verify_nonce( $nonce, 'return_posts' ) ) die ( 'Wrong nonce!' ); $args = array( 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 5, ); // The Query $the_query = new WP_Query( apply_filters( 'cool_ajax_query', $args ) ); // The Loop if ( $the_query->have_posts() ) echo '<div class="myposts">'; while ( $the_query->have_posts() ) $the_query->the_post(); echo '<h3>' . get_the_title() . '</h3>'; echo '</div>'; else echo 'Posts not found'; /* Restore original Post Data */ wp_reset_postdata(); // don't forget this // We are doing a simple call we don't want anything else to run die();
As you can see I added a filter to the $args
variable that will run right before WP_Query
runs the query. With a simple filter any user can change for example how many posts are returned.
function wpt_alter_coolajax_query( $args ) $args['posts_per_page'] = 10; return $args;
add_filter( 'cool_ajax_query', 'wpt_alter_coolajax_query' );
You can use filters in different scenarios, just use your imagination. For example on my plugin WordPress Social Invitations I let users change the popup stylesheet with a filter in case they way a whole different style.
<link rel="stylesheet" href="<?php echo apply_filters('collector_css_file',plugins_url( 'assets/css/collector.css?v='.$this->WPB_VERSION, __FILE__ ));?>" type="text/css" media="all">
Or on this occasion when the plugin send emails I give the opportunity to change the "From email" that wp_mail()
is going to use.
function get_from_email() if ( isset( $this->_user_data ) ) return apply_filters( 'wsi_from_email', $this->_user_data->user_email ); return apply_filters( 'wsi_from_email', get_bloginfo( 'admin_email' ) );
When it comes to actions this changes a bit. You want to add action hooks in the following scenarios (not limited to) :
- before task are executed,
- after tasks are executed,
- during task are executed to for example extend markup.
To create these hookable areas we use the do_action()
function. Let's apply them in our example.
function cool_ajax_example_callback() $nonce = $_POST['nonce']; //The first thing we do is check the nonce and kill the script if wrong if ( ! wp_verify_nonce( $nonce, 'return_posts' ) ) die ( 'Wrong nonce!'); $args = array( 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 5, ); // The Query $the_query = new WP_Query( apply_filters( 'cool_ajax_query', $args ) ); // The Loop if ( $the_query->have_posts() ) // we run the hook before the loop is proccesed do_action( 'cool_ajax_before_loop', get_the_ID() ); echo '<div class="myposts">'; while ( $the_query->have_posts() ) $the_query->the_post(); // we run the hook before the title is printed do_action( 'cool_ajax_before_title', get_the_ID() ); echo '<h3>' . get_the_title() . '</h3>'; // we run the hook after the title is printed do_action( 'cool_ajax_after_title', get_the_ID() ); echo '</div>'; // we run the hook after the loop is proccesed do_action( 'cool_ajax_after_loop', get_the_ID() ); else echo 'Posts not found'; /* Restore original Post Data */ wp_reset_postdata(); // don't forget this // We are doing a simple call we don't want anything else to run die();
As you can see Im passing one argument to the hooks with get_the_ID()
. Any function that hooks into our action hook will be able to use that argument. Let's see some examples:
/**
* Show post featured image before title
*/
function wpt_show_post_image( $post_id ) echo get_the_post_thumbnail( $post_id, 'thumbnail' );
add_action( 'cool_ajax_before_title', 'wpt_show_post_image' ); /**
* Show post categories after title
*/
function wpt_show_post_cats( $post_id ) echo get_the_category_list( '', '', $post_id );
add_action( 'cool_ajax_after_title', 'wpt_show_post_cats' );
Also you can use do_action()
hooks to run others actions instead of adding new markup like the example above. For example, in WordPress Social Invitations I have an action hook that fires every time a message is sent. Then using myCRED plugin I can hook the action of give points to the user that just sent the message.
As you can see adding hooks and filters will not only benefit your plugin, it will also benefit other developers and users. So why not you start adding some filters and actions around?
Conclusion
Today we learned how to include scripts and styles, how to do Ajax calls the WordPress way and some basic tips for filter and actions. We already have a lot of info to develop a nice plugin and I can now say these series are close to the end.
In the upcoming and finale article, I'm going to talk about internationalization, debugging, and documentation to wrap things up on what I think are the top things to consider while developing a plugin.
More Tips for Best Practices in WordPress Development
Geen opmerkingen:
Een reactie posten