WordPress drill-down admin pages II

January 18, 2010

wordpress-logo-300x300I never did do a follow-up on the WordPress drill-down admin menus post. A lot of people continue to hit that article so I’ll take the time to give an example of how I easily resolved the issue.

The problem was that after an upgrade to WordPress Mu v 2.8.4a all my dynamically created, drill-down admin pages quit working because they now needed to be registered with WordPress for — completely understandable — security reasons.  As far as I can tell, the only way to register an admin page is through the menu system. Since I don’t want my drill down pages to be on the menu I needed a better way to do it.

One solution suggested was to just use the registered menu page as the callback to handle requests and that is for the most part what I did. The only issue with the solution is that the callback page has to also serve all the content since it can’t request an admin page that is not registered. Using one page to parse all requests and serve the appropriate content will get unwieldy for anything but the simplest of applications and if your application was that simple you wouldn’t be trying to figure out how to add custom drill-down admin menus.

I like modularity and easily managed code so my philosophy is that multiple small files are better than one huge file. I needed a way to solve the problem without breaking my philosophy or changing a lot of my existing code, which was already in multiple small files.

The idea is to build a container page to register on the admin menu and have the container page decide on which code to include based on analyzing the url. The included code can then handle and GET or POST requests and serve the appropriate page output.

<?php
/*
 * Descrition: gcgc admin page. This is the main container page
 * for all admin panels. All POSTS and GETS from all pages route
 * through this page and are handled by the appropriate included file.
*/
global $gcgc_Manager;
echo $gcgc_Manager->getHeader();
//analyze the url to determine which page to load

if (isset($_GET['panel'])){
    switch ($_GET['panel']){

    	case 'cache-admin' :
    	    include (dirname(__FILE__)."/gcgc-admin-cache.php");
    			break;

    	case 'vector-admin' :
           include (dirname(__FILE__)."/gcgc-admin-vector.php");
           break;

    	default :
           include (dirname(__FILE__)."/gcgc-admin-home.php");
    }
}else{
    include (dirname(__FILE__)."/gcgc-admin-home.php");
}

$gcgc_Manager->getFooter();
?>

I kept the above example brief but you can imagine that there can be an unlimited number of admin pages. And this way I didn’t have to change the included files at all — all I had to do was add the ‘&panel=’ parameter to existing request urls and problem solved.

  • Share/Bookmark

WordPress drill-down admin pages

October 8, 2009

I was gaining momentum on the Geocache Manager WordPress plugin last week after solving a couple of longstanding problems with unauthenticated Ajax calls and a few Javascript quirks with Internet Explorer when yet another pesky problem surfaces. When I say pesky it means that I have been fooling around with it for several days without zeroing in on a solution, thus making zero headway on the project. To make matters worse, the problem lies with a feature that has existed from the beginning and had worked until I upgraded from WordPress Mu 2.7 to 2.8.4a last week.

In the admin panel I have a custom top level menu with two entries: a page for the map management and a page for options. The following bit of code…

function addAdminMenu(){
	add_action('admin_menu', array(&$this, 'gcgc_plugin_menu'));
}

function gcgc_plugin_menu() {
	$gcgc_relpath  = str_replace('\\','/',dirname(__FILE__));
	$parent = $gcgc_relpath.'/gcgc-admin-form.php';
  	add_menu_page('Private Geocache Manager', 'Geocache', 1,
                                 $parent, array(&$this, 'gcgc_admin_page'));
  	add_submenu_page($parent, 'Geocache Options', 'Options', 1,
                                 'gcgc_options', array(&$this, 'gcgc_options_page'));
}

function gcgc_options_page(){
	$gcgc_relpath  = str_replace('\\','/',dirname(__FILE__));
	include($gcgc_relpath.'/gcgc-options-form.php');
}		

function gcgc_admin_page() {
	$gcgc_relpath  = str_replace('\\','/',dirname(__FILE__));
	include($gcgc_relpath.'/gcgc-admin-form.php');
}

…will give you a menu like so:
geocache-menu

That all works. The geocache admin form lists all the courses defined and for each course there is a link to load a new admin page for that course. This is basic functionality that has existed since the 1st week of development and suddenly the link no longer works. The link now generates a redirect to a sub-blog with a page not found. WTF! Where do you start at on that.

I admit I am a little weak on WordPress redirects and I am having a difficult time understanding why the redirect would go to a sub-blog in the WordPress Mu installation instead of the current blog. But aside from that I don’t understand why it started issuing a redirect in the first place when the code that generates the link hasn’t changed in like five months.

UPDATE: I fixed the redirect problem by deleting the sub-blogs. Now I am just getting a mangled url, like so:

http://gulfcoastgeocache.com/wp-admin/?c=1
So let's go over what I am thinking so far. I am of course using the wp-admin framework and have the faulty link set up similar to the pages accessed from the admin menu. The URL on the Geocache admin link is:
.../wp-admin/admin.php?page=private-geocache-manager/gcgc-admin-form.php
Then on that page there is an entry for each geocache course and each course has an anchor link to its own admin page, like so:
<a href=".../wp-admin/admin.php?
        page=private-geocache-manager/gcgc-admin-cache.php&id=20">
Geocache Name</a>

After checking the usual suspects -- verifying paths, searching for typos, confirming no function parameters had been inadvertently changed -- the only thing I can think of is that the drill-down admin page was working previously only due to a flaw in previous WordPress. I remember when putting it in that it seemed easy. Too easy.

Over the past few days I've done quite a bit of searching using every term anyone could probably think of having to do with registering an admin page that is not on a menu. Compared to other development platforms out there (Make no mistake, WordPress is a development platform) there just is not that much in-depth technical documentation outside of your standard stuff. There are many redundant articles on adding an admin menu, for example, but not much on adding a multi-page, drill-down admin system. You have to figure that out on your own. Either not many people are doing advanced development or they are very tight-lipped about their stuff.

This morning I finally came upon an article that at least verifies my thinking:

WordPress 2.8.1 contains changes to improve the security of plugins by ensuring that only correctly registered plugin pages can be accessed as well as only showing the link to the page to users who have the capability required in the add_x_page call.

The article makes no mention of dynamically created sub-pages accessed by means other than the WordPress menu structure but down in the comments I find a fellow traveler:

I already have add_action(’admin_menu’, ‘FUNCTION’); for my main settings page. However, there’s a plugin page which is not a menu link (but an inside plugin page which is referenced when the plugin settings are saved).

And the answer to my problem, if not the solution:

You cannot just link to pages that are not registered as this would make it easy to load up files which should not be loaded so you have two choices to fix your plugin.

He goes onto say that the workaround is to use a WordPress admin action hook or a call back to the main page to process the main form submissions.

I like to keep my pages small and manageable and this plugin will ultimately have half a dozen admin pages when all the features are added. As of now there are only three. But since I want many small files instead on one big file here is what I am going to do: I am going to create a main admin container page that includes the proper admin panel when loading based on the URL. Should have seen this problem coming and set up like this in the first place. Oh well. I'll report back when I get the changes complete.

Read the follow-up:

WordPress drill-down admin pages II

  • Share/Bookmark

WordPress ajax and $wpdb

September 20, 2009

Anyone who does any tinkering with WordPress templates or plugins is likely familiar with the WordPress class for interfacing with the WordPress MySQL database, $wpdb. In the previous article here in The Garage, Viewer-facing Ajax in WordPress Plugin, I described a problem I was having with Ajax requests from the public side of a website from a user who is not logged in. Pesky little problem it has been.

In a nutshell, if the user is not logged in they do not have access to any of the built-in WordPress Ajax functionality so you have to write your own Ajax handler, which is not a problem until you need to access the database using $wpdb since the $wpdb class is not normally available to a page or script that is loaded outside the WordPress context. My research led me to a couple tricks around this limitation such as a template redirect or loading the core Worpdress files in the external Ajax handling script, eg,

include_once(‘wp-config.php’);
include_once(‘wp-load.php’);
include_once(‘wp-includes/wp-db.php’);

In theory, after loading those core files I can now make calls to the MySQL tables using the $wpdb class just as if I was logged in to the admin panel. In the back of my mind I am thinking that maintaining extra scripts for anonymous db access is a lot of work and loading the core WordPress files is a lot of overhead just to run a couple of simple queries. Really not optimal.

So I was still looking for a better solution even as I was working on making the above work when I stumbled upon the better solution. Somewhere around March WordPress released an update (v2.8 I believe) which has support for Ajax actions initiated by a user who is not logged in. Along with the ‘wp_ajax’ hook there is now also a ‘wp_ajax_nopriv’ hook that provides the same functionality but for users that are not logged in. So when your main plugin code is loading all you need to do is check to see if the user is logged in or not and then initialize the appropriate action hook.

//load nopriv ajax handlers for front-end ajax users not logged in
if ( is_user_logged_in() ) {
    add_action('wp_ajax_getVectorMarkers', 'getVectorXML');
    add_action('wp_ajax_getVectorMarkersText', 'getVectorXMLText');
} else {
    add_action('wp_ajax_nopriv_getVectorMarkers', 'getVectorXML');
    add_action('wp_ajax_nopriv_getVectorMarkersText', 'getVectorXMLText');
};

Using the ‘wp_ajax_nopriv’ hook allowed me get rid of a lot of code that was written specifically for displaying custom maps to users who are not logged in. It makes me very happy!

Evidently not too many people know about ‘nopriv’ as the info was difficult for me to Google down. (Click here to see the thread where I found the information.) The alternative methods are also discussed in the thread if you are not in the position to upgrade to the latest WordPress version with the ‘nopriv’ option. (Here is the WordPress Trac nopriv change documentation.)

I can verify that wp_ajax_nopriv also works with WordPress Mu version 2.8.2.

  • Share/Bookmark