Creative Developments

Display Custom Messages in WordPress Plugins

In the process of creating WP Image Protect Premium, I had the opportunity to explore the notification actions and filters within WordPress.

WordPress provides a nice API hook for displaying messages in the administration menu.

add_action('admin_notices', array( $my_messaging_class, 'my_function'));

For my purposes I wanted to display ‘news’ for the plugin I’ve been developing, which would read html content from a 3rd party site – the plugin’s news page. This would be displayed on the administration dashboard and plugin menus. The user would be able to dismiss the item. Unlike an RSS feed, there is only intended to be one item at a time. Furthermore, I wanted to allow the system to display different messages based on things like the version of the plugin that was being used, the url of the site the plugin was used on etc.

I have split the task into three parts:

  • Displaying an administration message on the plugin page which takes HTML as input
  • Reading HTML content from an external site
  • Dismissing messages

 

Displaying HTML admin messages, this is relatively simple, first start with something like this:

class wp_html_admin_messages{
//constructor

function wp_html_admin_messages(){
if(function_exists('add_action')){
add_action('admin_notices', array( $this, 'display_admin_message'));
}
}

function display_admin_message(){
//do display admin message function here
}

}

$wp_html_admin_messages = new wp_html_admin_messages();

So, what’s going on here? Firstly, I’m putting everything into it’s own class, which is best practice with WordPress plugin functions, as only the class name needs to be unique and not every function. The action hook is added when the plugin is activated using the add_action function. Not the use of the conditional if(function_exists(function_name)) to ensure that WordPress has loaded correctly and the add_action function is available.

Next, I want the plugin to actually display something on the relevant pages, so I’ll rig up some dummy HTML to pass and test it out.

Fleshing out the display_admin_message and adding the functionality, results in the following:

function display_admin_message(){

global $pagenow;

if($pagenow == 'plugins.php' or $pagenow == 'index.php'){
$this->display_html_message();
}

}

function display_html_message(){
printf('%s', $this->dummy_html_output());
}

function dummy_html_output(){
return "<a href = 'http://wordpress.org' title='WordPress.org site'>WordPress Blog Tool</a>";
}

 

To prove it works, here is the message on the plugins page of the site. Note how I have filtered what pages the message is displayed on by using the $pagenow global variable. The printf function is used to pre-format the string, we also need to consider Cross Site Scripting (XSS) concerns at some point by using suitable escape functions.

HTML output on Plugins page

Next, I want to be able to get that HTML, not from a function but from the 3rd party plugin development site. This way, I can provide the user with important notes and news about the plugin, and this can be updated dynamically without having to actively push new code to the plugin.

To test this, I’m going to move the dummy_html_output function into its own file on the external (admin messages) server, thus:

class admin_messages_server{

//constructor

function admin_messages_server(){
$this->display_html_message();
}

function display_html_message(){
printf('%s', $this->dummy_html_output());
}

function dummy_html_output(){
return "<a href = 'http://wordpress.org' title='WordPress.org site'>WordPress Blog Tool</a>";
}

}

$admin_messages_server_init = new admin_messages_server();

 

Done, and here it is on the admin messages server.

On the messaging server

OK, so what next? Well, the plugin needs to be able to retrieve that message from the remote site. I can do that using cURL, a tool for transferring data with URL syntax. Unfortunately cURL can be problematic as it needs to be enabled on web servers, and may not be available. Once again, WordPress provides a nice API called WP_Http, which replaces the deprecated Snoopy functions. This works on servers which don’t necessarily have the cURL function available and lets WordPress take care of the request and response mechanism.

The dummy html output function can then be replaced with a real function, which actually sends a request to the admin messages server

function get_html_output(){
$result_body = "";
if(function_exists('wp_remote_get')){
$url_query = "http://8mediacentral.com/admin_messages/admin-messages-server.php?version=3.4";
$result = wp_remote_get($url_query);
if($result['response']['code']==200){
$result_body = $result['body'];
}

}

return $result_body;

}

 

The beauty of this technique is that I can pass parameters to the admin messages server by modifying the $url_query parameter. In this example I am passing the version number, the idea being that the message server would let the user know if a new version of the plugin was available.  Note that I also check the response is valid by ensuring the result code is 200, to ensure the request has succeeded.

An important caveat to this is that I need to consider all the parameters I want before I finalise the plugin, as while I will be able to control what html is returned dynamically, I won’t be able to alter the parameters that are sent once the plugin is in the wild.

Finally, I want the user to be able to dismiss the admin messages, as it could get pretty annoying seeing that admin message all the time. There are loads of different ways of doing this, but in  this example I’m just going to have plugin deactivate when the dismiss message is sent.  This means adding another action to listen out for the disable_wp_html_admin_messages parameter to our wp_html_admin_messages class, thus.

 

//dismiss the message and deactivate the plugin if set

function dismiss_wp_html_admin_messages() {
if ( isset( $_GET['dismiss-wp-html-admin-messages'] ) and 'dismiss' == $_GET['dismiss-wp-html-admin-messages']  ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
//deactivate
if(is_plugin_active(plugin_basename(__FILE__))){
deactivate_plugins(plugin_basename(__FILE__));

wp_safe_redirect( admin_url() . 'plugins.php?deactivate=true&plugin_status=all&paged=1&s=' );
exit;
}

}

}

 

The following line of code is added to the class constructor function to ensure it listens for the dismiss-wp-html-admin-messages parameter

add_action( 'admin_init',   array( $this, 'dismiss_wp_html_admin_messages'));

To tie this all together and make this work, I need to add the following to the HTML response on the messages server, so when the user clicks on the ‘Dismiss Admin Messages’ link, the messages are no longer displayed.

<a href = '?dismiss-wp-html-admin-messages=dismiss' title = 'dismiss messages'>Dismiss Admin Messages</a>

 

Final thoughts.

This has been a simple introduction to handling admin messages for WordPress. We have looked at how admin message hook into the WordPress API, a mechanism for retrieving messages from a remote url and a simple way of dismissing messages.

You’ll probably want to style the html response so it looks nice, which is done in the WP Image Protect Install Check Plugin.

Security is a big concern here; currently what is being returned is raw HTML, which I would not recommend on a live instance due to the potential for the messages server to be compromised and malicious code sent to the plugin. For the actual messaging system I plan to use XML which is parsed by the plugin before being rendered using something like the strip_tags php function. A whitelist of tags will be used to reduce the possibility of XSS attacks.

Lastly, in this example, dismissing the messages turns the plugin off entirely. If there is an important message and the user has previously dismissed a trivial message, this will not be seen. A better approach is to implement a mechanism whereby the messages are given unique identifiers and individual messages can be dismissed and by incrementing the ID number, the user will be shown the latest message.

Comments are closed.