Using the restrict_manage_users Action Hook in WordPress 4.4

I recently participated in an interesting discussion on Stack Exchange about how to add a filter button just above the Users list table on the Users page in WordPress Admin. To sum up the whole issue, if you used the restrict_manage_users hook to do this in an earlier version of WordPress, your code for filtering the users list will probably not work correctly in WordPress 4.4 without making a small change.

Examining The Issue

In WordPress 4.4, the restrict_manage_users hook is called twice when loading the Users page.   (It is only called once in WordPress 4.3.1)  This means that if you use that hook to add a drop down select and filter button (or any other input field or html code), your additions will actually show up on the Users page twice, both above the table and below it.

The restrict_manage_users action hook is called twice in WordPress 4.4
The restrict_manage_users action hook is called twice in WordPress 4.4

Now, normally you would think that this addition is a great convenience. However, unless you change your code, when the user makes a selection in one of the dropdowns, the other dropdown remains the same. Then when the Filter button is clicked, the form is submitted with two values for that input. One value will contain the selection and the other will be blank. If you use Chrome Developer tools, you can inspect the query vars sent with the get request and you will see what I mean:

Analyzing the Query Vars sent with the GET request
Analyzing the Query Vars sent with the GET request

 

Tip:  Follow these instructions to inspect the query string parameters:

  • Open the page in Google Chrome Web Browser and press CTRL+SHIFT+I to open Developer Tools
  • At the top of the tools, select the Network tab.
  • With the developer tools still open, click on the filter button or submit button or whatever other button that you want to examine the request for.  You should see a whole bunch of requests appear.
  • Click on the request you are interested in.  In this case, it’s going to start with users.php.
  • Click on the Headers tab and scroll down to “Query String Parameters.”

How this Effects Your Code

If you wrote a WordPress plugin that uses both the restrict_manage_users hook and the pre_get_users filter, you likely looked at the $_GET global variable to alter the WP_User_Query.  Because of this change, the $_GET variable will be different, and your code might only work if the user uses the bottom dropdown menu and not the top one.  This is a very big bug, but fortunately there are several different ways to fix this issue.

The Solution

There are at least 3 different solutions to handling this problem.  Visit this WordPress Stack Exchange discussion for example code on how to fix this using PHP.

Here I’m going to present a solution that uses Javascript.  Simply add this to your theme’s functions.php file and change the NAME_OF_YOUR_INPUT_FIELD to the name of your input field!  Since WordPress automatically loads jQuery on the admin side, you do not have to enqueue any scripts.  This snippet of code simply adds a change listener to the dropdown inputs and then automatically updates the other dropdown to match the same value.


add_action( 'in_admin_footer', function() {
	?>
	<script type="text/javascript">
		var el = jQuery("[name='NAME_OF_YOUR_INPUT_FIELD']");
		el.change(function() {
			el.val(jQuery(this).val());
		});
	</script>
	<?php
} );

Questions?

If you tried this solution and are still having an issue, please comment below.  There may be use cases where this doesn’t work but I can’t think of any right now.  I’m interested in learning and your feedback is important.

Even if you use my javascript solution, I suggest that you read over the other solutions on Stack Exchange because they provide a lot of insight into how to use a static variable and/or your own custom action hook to deal with the issue of WordPress core calling a hook multiple times.   Using a static variable is a great way to manage how often your plugin performs certain actions, and doing so could help you future-proof your code.

 

WordPress Plugin or Theme Development with Ajax and jQuery

A little knowledge of Javascript, or the popular Javascript library jQuery, can go a long way in making your plugin or theme “do” things.  As you may know, WordPress is written in PHP.  However, for client-side code, Javascript is the typical solution.  How much you do in Javascript and how much you do in PHP depends on your particular task, but at the very least you will likely use Javascript to send POST requests and parse the data received from the XMLHttpRequest object.

WordPress has an action hook called wp_ajax that allows you to access the WordPress core functions when handling HTTP post requests.

The jQuery functions jQuery.post and jQuery.ajax can be used to send the requests. The requests need to be sent to the ajaxurl, which is a javascript variable already defined on the admin side. On the front end, you need to define this variable yourself to point to the location of admin-ajax.php.

For a complete tutorial on how to use jQuery & Ajax with WordPress, see my article: How to use Ajax with your WordPress Plugin or Theme?

Another option for using Javascript in your theme or plugin is to use WordPress’s Customizer API, which is something I plan to address in a future tutorial.

How to make your plugin have universal settings on Multisite WordPress?

While updating my Check Amazon Links plugin to work with WordPress Multisite, I struggled with how I could make my plugin settings universal across all network installs.  More specifically, I wanted it to be an option that could be turned on and off.  I tried to find a way to implement a hook or filter for when the plugin options were being updated via the settings page, but I had so much trouble with that, that I finally decided to use some Javascript.  Yay for javascript!  Sometimes it’s the easier option.

First, I added the option to my settings page, but only on multisite installs.  Here’s what it looks like:

Multisite Option on my WordPress Plugin (Check Amazon Links)

Next, I wrote the Javascript (jQuery) code to respond to submit events:


jQuery(document).ready(function () {
    var option_page_name = jQuery('input[name=option_page]').val();
    if (typeof(option_page_name) != "undefined") {
        if(option_page_name === 'amazon_link_plugin_options') {
            jQuery('form').submit(function(e) {
                var update_all = jQuery('select[name="azlc_multisite_same[toggle]"]').val();
                if(update_all === '1') {
                    jQuery.post(ajaxurl, 'action=azlc_ms', function(){});
                }

            });
        }
    }

});

As you see, my code checks the page name to insure that it only sends the AJAX request if it’s MY plugin’s page.   I add that javascript code to a javascript file I already enqueue.

Here’s the PHP WordPress Backend code for handling the Ajax request:


if(is_multisite()) {
	add_action('wp_ajax_azlc_ms', 'azlc_ms');
}

function azlc_ms() {
	add_site_option( 'azlc_update_from', get_current_blog_id() );
}

 

And here’s the PHP WordPress code the updates the options:



if(is_multisite()) {
	add_action('plugins_loaded', 'azlc_multisite');
}


function azlc_multisite() {
	global $wpdb;

	// check for flag 
        // I call this a flag because its existence means that this code needs to run.
        // You could write this to make it more human readable
	$blog_id = get_site_option('azlc_update_from');
	if($blog_id) {
		// switch to the blog to copy the actions from
		switch_to_blog($blog_id);
		$options = get_option('azlc_plugin_options');
		// copy settings to all sites
		AmazonLinkCheckerCore::copy_settings_from($blog_id, 'azlc_plugin_options', $options );
		// remove flag
		delete_site_option('azlc_update_from');
		// switch back
		restore_current_blog();
	}
}

 

And here’s the code that actually does the copying:


	public static function copy_settings_from($source_id, $option_name, $option_value) {
				global $wpdb;
				$sql = "SELECT blog_id FROM $wpdb->blogs";
				$blog_ids = $wpdb->get_col($sql);
				foreach($blog_ids as $blog_id) {
					// update option on other blogs
					if($blog_id!=$source_id) {
						switch_to_blog($blog_id);
						update_option($option_name, $option_value);
					}
				}
				restore_current_blog();
	}

 

Related Tutorial

Don’t miss this related article: How to Enable WordPress Multisite for your Plugin

Your feedback is important

I write this blog with sincere hope that I can help other WordPress developers.  Please let me know if this helped you, or if I made a bad error.  Thanks!