Site-Wide Admin Bar for WPMU

This plugin is deprecated and no longer maintained by the developer.

The new 2.7 version of WordPress MU is pretty awesome and I’m glad to have it running on both of my sites right now. There’s a lot 2.7 has to offer in terms of usability boosts, and while I balked at the redesign when it first started, I have to confess that there is much to love about the new layout.

However, one thing that’s been nagging me is the new Admin Bar functionality. It suffers from two things, in my opinion:

  1. It’s a blog by blog setting, not sitewide. This is fine for sites where you want savvy people to start blogging on your network. It’s less so for community based sites like mine, where the idea is a sort of news magazine site where consistency is key.
  2. Because plugins can add their own top-level menus, the Admin Bar can get a bit crowded and I’d like the option to add or subtract items from the list as I see fit. Again, it would be nice to have these settings happen globally rather than by site.

While I have not worked out the second problem just yet, I thought I’d go ahead and post my modified code to this site which allows site-wide settings rather than by-site. It also tucks the Admin Bar settings under the Admin section rather than Options.

There’s nothing magical here: all I did was change every occurrence of “get_option” and “update_option” to “get_site_option” and “update_site_option.” Then, I just modified the menu statement to look like the following:
// Register the settings page
function AddAdminMenu() {
add_submenu_page('wpmu-admin.php', __('WordPress Admin Bar', 'wordpress-admin-bar'), __('Admin Bar', 'wordpress-admin-bar'), 'read', 'wordpress-admin-bar', array(&$this, 'SettingsPage') );
}

Download the file here and replace the one currently sitting in /wp-includes/wordpress-admin-bar/

Meanwhile, I’m going to work on tweaking the plugin to make it more flexible for MU as I go.

Tags: , , ,

9 comments so far (is that a lot?)

Scheduling with WordPress Cron Functions

I’ve been struggling to understand exactly how the cron functions of WordPress work.

I’ve been working on a plugin for some friends of mine that need a mailing list manager, this plugin in turn requires the ability to schedule tasks. So, I spent a long time puzzling over the various bits of documentation and posts on other blogs about WP-Cron, pouring over code and experimenting with my own, until I finally got a working plugin.

And while I’m sure that the information that is out there now about Cron functions is adequate for some, it’s clear to me that others may need a different set of explanations, which is where this blog post comes in. I’m going to attempt to lay out the WP-Cron world in a different way in hopes of improving slightly on what’s out there.

Let me start by saying that the best article I’ve read on the subject of crons is definitely “Timing is everything: scheduling in WordPress,” by Glenn Slaven. I highly recommend that, even before reading this article, you take a gander at that one. There are really just a few things on which I would like to expand and revise slightly, because they threw me off. I’m also slightly reorganizing the order of discussion into something more akin to a step-by-step set of instructions.

The Scenario

The example we’re going to work with is the mailing list manager plugin I’m developing. We’re not going to get into too much of the actual code of that plugin, but the scenario is doubtless a common need. We’ll be assuming that we’re working with a blog which has many, many users. (real world example: 400+) We want to keep our users up to date with periodic email blasts. Rather than those blasts being separated from the regular content, we want to specify that certain posts will become both posts on the site and email messages.

But of course, sending emails out to 400+ recipients at once is going to make the SMTP server very, very suspicious. In order to avoid problems with the mail server, we need to throttle the messages by sending blasts every twenty minutes ((or at least, approximately twenty minutes, remember that WordPress cannot be exact about timing)) until the last user group has been emailed. To do this, we will be using the wp_schedule_single_event() function within the WP-Cron suite of tools.

Scheduling: What You’ll Need

In order to schedule an event of any kind, you’re going to need a few basic components. We’ll get into more detail further down the line, but it’s important to be able to identify the landmarks as we proceed:

  • Your Cron Function ~ This is the actual function that does the stuff you want when the scheduled event is fired
  • A Custom Action Hook ~ Here’s where we get into the nitty-gritty of how WordPress operates. It’s very simple, actually, but understanding how to create custom action hooks is key to understanding WordPress cron functions. And a great deal more about WordPress!!
  • Your Scheduling Function ~ This is the function that initially assigns the scheduled task.


Your Cron Function

my_scheduled_function([$arg1, [$arg2, [$arg3. . . ]]])

This function lies at the heart of your scheduling world! It is here that you put all the stuff you want to get done at a specified time. In our example, this is where you actually send the email to your latest batch of users. Note that this function can include an arbitrary number of arguments. We’ll get into how you pass those arguments on to the function later. For now, just notice that this is a standard function like pretty much any other. In fact, in the case of the mailing list manager I created, it’s as simple a function as you could ask for:

function bhd_cron_send($sender_email, $subject, $mail_text, $send_headers) {
	wp_mail($sender_email, $subject, $mail_text, $send_headers);
}

It’s probably advisable to create your function and make sure it works without adding in the cron functionality first. This is because a) there’s no need adding extra complexity to the system until you’re sure the function works and b) you’ll need to know how many arguments will need to be passed to the function and in what order.

Your Custom Action Hook


This is what really threw me. I’ve never created my own custom action hooks and never really understood them, but you’ll need to in order to get things done with cron, so this is important. Creating a custom hook is as simple as typing the following:

add_action('my_custom_hook', 'some_function' [, $priority, [$num_args]]);

WordPress assumes that anything added as the first argument exists without any additional registration of that new hook. In other words, ‘my_custom_hook’ actually is the declaration of the action hook in this example. The second argument should be a valid function name and will be the function associated with ‘my_custom_hook’ whenever ‘my_custom_hook’ is invoked anywhere in the system.

Obviously, since you’re writing your custom action, you’ll also need to invoke that custom action somewhere. That’s the last piece of the puzzle, which is explained down the page a bit.

But before we leave this topic, it’s worth pointing out that, just like any other action, it’s entirely possible to hook more than one function into the custom action you’ve created. It’s also possible to invoke the hook somewhere else in your code, separate from your Cron function. This bit of WordPress theory may prove helpful to you down the line, so it’s just an FYI.

The last two arguments of the add_action function are important. Recall the number of arguments you needed for your scheduling function. That’s the number you’re going to want to put in the $num_args add_action parameter. Otherwise, add_action doesn’t look for and doesn’t accept any arguments provided, which will doubtless render your function either inoperable or ineffective.

Late Update: Per jeremyclark in the comments, this is not really the reason to mess with priority! Thanks Jeremy! If you’re function is particularly computationally expensive (if it’s complex and relies on a lot of processing), it might be worth it to back the priority down a bit. You can do this by setting the $priority argument. The default is 10. In our case, the actual function is trivial, so this is what the add_action looks like for our mailing list:

add_action( 'bhd_cron_send_hook', 'bhd_cron_send', 10, 4 );


Your Scheduling Function


Finally, there is the function which will do the scheduling. Depending on the complexity of your plugin, this may not be very complex at all. But the one thing that is required of your function is that it bundle up all the arguments to your cron function into an associative array in the proper order. In our example, the parameters for our sending function are $sender_email, $subject, $mail_text, $send_headers. Thus, we create an array like so:

$mail_bundle = array(
     'sender_email' => $sender_email,
     'subject' => $subject,
     'mail_text' => $mailtext,
     'send_headers' => $send_headers
);

Next, we need to assign the scheduled task. For this, we use our custom action hook. Now we understand why it was so important to pay close attention to the number of parameters and their order: the hook only allows you to set the number of arguments and the function – which is now removed by one degree of separation from the schedule – needs those arguments sent to it in the proper order. We call the WordPress scheduling function like so:

wp_schedule_single_event(time()+$time, 'bhd_cron_send_hook', $mail_bundle);

The first argument is simply the Unix system time plus an interval of seconds after which we would like the scheduled task to be fired. Since we want the first of our batch email events to happen twenty or so minutes after the post has been published, $time was defined as follows:

$time = rand(1000, 1200);

“Wait,” you say, “why the random number?”

If you look at the multi-dimensional array of scheduled events created by the WP-Cron system, you will find that the primary key for identifying events is the Unix timestamp. The second-level identifier is an MD5 of the arguments. In our example, it’s possible that someone could inadvertently set up the cron events twice by correcting the post after they’ve published it: both events fire the ‘publish_post’ event, which the mailing list uses to start it’s process.

However it happens, the possibility is that two events could have exactly the same timestamp and arguments, which would mean that one overwrites the other in the array. To avoid this, I included the slight randomization, which only alters the schedule by a maximum of three minutes.

So, now let’s look at the entire process in code (with some pseudo-code) in order to understand how everything fits together:

//======================================
// Description: Our scheduling function.  Sends the email in 50-recipient chunks
function bhd_schedule_message($sender_email, $subject, $list, $headers, $mailtext) {
// Establish our random time interval between the publishing of the post and the first email blast:
	$time = rand(1000, 1200);
	for($x = 0; $x  $sender_email,
				'subject' => $subject,
				'mail_text' => $mailtext,
				'send_headers' => $send_headers
			);
// schedule the email blast, increment the $time variable by twenty minutes for the next loop:
			wp_schedule_single_event(time()+$time, 'bhd_cron_send_hook', $mail_bundle);
			$time = $time + 1200;
		}
	}
}

//======================================
// Description: here is our actual cron job.
function bhd_cron_send($sender_email, $subject, $mail_text, $send_headers) {
	wp_mail($sender_email, $subject, $mail_text, $send_headers);
}

//======================================
// Description: Finally, here is our custom action.
add_action( 'bhd_cron_send_hook', 'bhd_cron_send', 10, 4 );

Note: we have not included the function that will be called when the post is published, because of course we’re not dealing with adding actions onto that particular hook. But for reference’s sake, such a function would look something like this:

add_action('publish_post', 'my_mailinglist_function');
Tags: , , , , ,

18 comments so far (is that a lot?)

WPMU Site-Wide Latest, With Gravatar

This plugin no longer supported by the developer.

Download Here

The WPMU Site-Wide Latest plugin provides two widgets for your site. The first, labeled “Newest Post,” creates an 80-word teaser of the single most recently published blog post on any public blog across the entire site. The second, called “Recent Posts,” creates a list of the most recently updated blogs with their most recent posts, one post per blog. Both plugins provide a vehicle for those using the standard Gravatar plugin to obtain and display the Gravatar of the post author in each case.

The teaser widget takes the post title as the title of the widget, whereas the listing widget allows you to set your own title. Both allow you to ignore blogs, if you wish. The list widget also allows you to specify how many blogs you want to display and an offset. This is in case you use both together, that way the most recent comment is in the “Newest Post” widget and the next most recent begins the list in the “Recent Posts” widget.

The plugin also uses the WP-Cache, and both widgets allow you to set a time-out.

WP-Functions Used in This Plugin:

switch_to_blog()
restore_blog()
wp_cache_set()
wp_cache_get()
get_permalink()

Installation:

Unzip, upload (to either /mu-plugins or /plugins, your preference), activate and configure. Simple as that!

Tags: , ,

4 comments so far (is that a lot?)

Balancing Plugins in WordPress MU

When setting up your WordPress MU environment, it can be difficult to determine whether you want a plugin to go into the /mu-plugins or the /plugins folder. I’ve discussed the difference between the two locations in the past. At this point, I’m talking about deciding which to use based on the knowledge of how they work.

From my perspective, it makes no sense to include any more plugins than necessary across all blogs on your site. All you’re doing is front-loading the page load process and slowing the entire site down. Moreover, having to maintain code that not everyone uses makes no sense, especially if in doing so, you risk crashing your whole site with one bad upload. The best option for both efficiency and stability is to limit available plugins both as mandatory and optional plugins. » Continue Reading…

No tag for this post.

No comments yet

MU Plugins, What’s the Difference?

As I’ve continued to learn about WordPress MU, I have discovered a world which is surprisingly undiscovered, it seems.  The veterans at WordPress are already familiar with how things work, but the documentation isn’t there quite yet (in truth, there’s precious little on even plain WordPress).  Meanwhile, the site WPMUDEV.org has been assembled to help out developers, but that’s woefully incomplete and often inaccurate.

The funny thing is: the WPMUDEV people have recently announced a premium service for those who want to spend a bit of extra cash.  Well, given the inconsistencies of the publicly-available content, I have trouble understanding why anyone would pay extra, but to each his own.

Case in point: the Post Ratings plugin that has allegedly been retooled to work with WordPress MU.  I won’t bore you with the gory details, but suffice it to say, the plugin was most definitely not working on this very basic site.  I decided to take the opportunity to play around with it and see if I could get it to work, and in doing so, learn a little bit more about the way MU deals with plugins.  I’ve decided to go over some basic concepts here before eventually putting them into the WikiMU.

/plugins vs. /mu-plugins

One very confusing concept behind MU plugins is the difference between the two plugin directories.  This seems to be a fairly frequent subject of discussion on the MU forums, but really, it’s not that difficult when you get down to brass tacks.

Plugins stored in the /mu-plugins folder are site-wide, mandatory plugins that the Administrator of the site controls.  Administrators of individual blogs have no control over these plugins and do not see them except where they have an effect on the final product.

Plugins stored in the /plugins folder are available site wide, but Administrators of individual blogs can determine whether or not to include the plugin on their blogs.  Now, the tricky part about this is that there is an option under Site Admin that allows or disallows the “Plugins” menu for Administrators site-wide.  If you don’t check this box, plugins saved to the /plugins folder won’t be available to site Admins to use.

Well, now, hold on a minute. . .  What’s the point of the check box to enable plugin control?  I don’t know the answer to that, but it seems a bit superfluous.  Whatever is the case, none of this is at all obvious when you install MU.

A second challenge is the fact that plugins installed in the /mu- directory shouldn’t just be dropped in and forgotten about. You’d like to be able to set options for these plugins, but so far, I’ve not seen a single plugin that actually provides control panels.

Developing Plugins for MU

It is the lack of control panels that makes working with MU plugins so challenging.  In fact, as near as I can tell, it is precisely this barrier that is preventing me from working with the Ratings plugin in MU.  The plugin requires you to set a default rating icon (stars, bars or squares) and since I don’t have an Admin panel that I can see, I cannot set this option along with who-knows-what other options.

So going forward, I’m going to need to figure out how the Post Ratings plugin creates its panels and hack that to move the panel somewhere accessible.  Any other future plugins will also need to take this need into account.  I still don’t know how they do this yet; I asked the question in the MU Forums and was told that there are plugins on WPMUDEV.org that do this, so I should just check them out.  Well, considering the luck I’ve had with the crappy plugins on this page, I think I can be forgiven if I don’t care to keep pluging in stuff that doesn’t work until I find the right one.

I think perhaps I’m going to try to work on some sort of convention that allows for a plugin to be stored in either the /plugins directory or the mu-plugins directory, changing the admin panel appropriately to match.  But of course, this is all very far in the future.  I’m going to need to have more than an hour to sit down and play if I’m going to work on a project like that, which at the moment is all I have.

No tag for this post.

2 comments so far (is that a lot?)

 

Bad Behavior has blocked 337 access attempts in the last 7 days.