WP Prism Syntax Highlighter not working on archive pages

Prism is a is a lightweight and extensible syntax highlighter written in JavaScript and CSS by Lea Verou. It’s fast and the rendered code looks great.

WP Prism Syntax Highlighter is one of the WordPress plugins integrating the Prism syntax highlighter. It is relatively new and I didn’t see much updating done but it works well.

Until now, I’ve only found one real problem with this plugin: althought it does work fine on individual posts, it just doesn’t work on archive pages and on the home page. So basically it fails to work on all pages showing lists of posts. It’s not an issue if you only display excerpts on those pages. But if you display full posts on them, all you see is the standard appearance of <pre> tags. This is because Prism will not be loaded on such pages.

The problem is that the plugin tries to limit the pages on which it loads Prism to those pages which actually do display some code. It does it with the following code:

private function decide_load_prism() {
	if ( strstr( get_post()->post_content, '<code class="language-' ) !== false ) {
		$this->load_pjsh = true;
	}
}

It basically gets the content of the current post and checks whether a Prism code tag is there. Only then will it load prism. This ensures that you do not load Prism on pages where you don’t need it in the first place e.g. posts without code, disclaimers, about me pages…

The problem is that this piece of code actually only works on single posts, not on archive pages or on the home page. Anyway for such pages, you’d need to check whether the whole post is displayed, if yes go through the posts being displayed and check whether code is present there. That’s kind of complex and probably the reason why it was implemented this way.

So to solve this you could delete the “if” line and the corresponding closing bracket. But then you’d load Prism on all pages including the ones where you definitely do not need it. After loading on all pages displaying code no matter whether a single post or a list of posts, the second best solution would be to make sure that single post pages only load Prism if required and on archives / on the home page, Prism is always loaded.

To implement it, open wp-prism-syntax-highlighter.php (http://YOURSITE/wp-admin/plugin-editor.php?file=wp-prism-syntax-highlighter/wp-prism-syntax-highlighter.php&a=te), search for “decide_load_prism” and replace this function by this one:

private function decide_load_prism() {
	if ( !is_single() || strstr( get_post()->post_content, '<code class="language-' ) !== false ) {
		$this->load_pjsh = true;
	}
}

This is almost the same as the previous one. The only difference is that it also loads Prism if is_single() is false i.e. we’re displaying a list of posts.

Now, the code displayed in my category archives and on my homepage also looks great !! Of course, if you don’t have a problem in the first place e.g. because you only display excerpts on these pages, then there is no point in implementing this since it will unnecessarily load Prism on these pages.

Please note that when the changes you make in a plugin will be overwritten after every update of the plugin. So until this issue is fixed in the plugin, you will need to reapply it after every update.

Update: There are no support requests on the plugin page on wordpress.org but I’ve just seen that there is a bug report on github.

WordPress: Menu items combining multiple categories

On one of the websites I am administrating, posts are assigned two kinds of categories:

  • the age of children
  • for boys or for girls

In the navigation menu, you need to be able to select menu items corresponding to the following types of criteria:

  • Display all posts for boys or all posts for girls
  • Display all posts for children in a specific age category
  • Display all posts for boys in a specific age category or for girls in a specific age category

The first two requirements are easy. You just need to add the corresponding category archives to the menu. For the last one, there is no way to select a combination of categories in the WordPress menu builder.

So you need to add links to the menu and point to the results of a query for a combination of categories. Fortunately, you do not need to write any code to have such a list of post displayed. This can all be done by providing the appropriate parameters in the URL e.g.:

http://mysite.com/?cat=for-boys,6-12-months

for-boys and 6-12-months are the slugs of the corresponding categories.

If you have pretty URLs, it’d look like this:

http://mysite.com/category/for-boys,6-12-months

This will show you posts belonging to either category. What we actually want are posts belonging to both categories. In order to get this result, you need to replace the comma by a plus sign:

http://mysite.com/?cat=for-boys+6-12-months

If you have pretty URLs, it’d look like this:

http://mysite.com/category/for-boys+6-12-months

Instead of “category” you can use “tag” for post tags or “author” for post authors.

WordPress Spam Fighting

Until September 2014, I was mostly relying on Akismet to prevent comment spam on my blog. But some time last summer, I noticed that there were actually quite a few spam comments which weren’t identified as such. The reason for this was that the total number of spam comments reached about 1000 a day. So of course having a few of them slip through every day wasn’t actually much. But it was still a pain to go through them as I was only checking comments once in a while.

So I decided to find out what for methods I could use to prevent comment spam and not only rely on Akismet. This led me to write a plugin called WP Spam Fighter.

In this plugin, I’ve implemented a few methods designed to address different vectors used to create comment spam.

Spam Bots vs. Normal Users

First, most comment spam is not generated by humans but by bots just creating comments on thousands of sites. It makes it easier to trick them, since they are not designed to workaround all possible tricks used to identify them but to create as many comments as possible on as many sites as possible.

So how do you identify Spam bots ?

Well, first you have to understand how to identify a normal user:

  1. A normal user sees a rendered web page and not only the HTML code.
  2. A normal user actually reads your posts.
  3. A normal user also understands the fields contained in a comment form.

You should also note that the second characteristic of normal users also differentiate them from human spammers. Human spammers do see a rendered site and do understand the comment form fields but do not actually read your post.

Ok, so now we know how to identify a normal user, how do we use this knowledge to stop comment spam ?

A normal user sees a rendered web page

This basically means that a normal users will only fill in form fields which are actually visible on the rendered page. Spam Bots will fetch the HTML code and will not apply any CSS styles. So if you add a field in the form and make it invisible, normal users will not fill it but Spam bots might.

This leads us to a spam fighting method usually called a honeypot-based mechanism. Spam bots will usually go through all fields in the form and try to put in some value (since they do not know which fields are mandatory fields and which ones are optional).

The more the additional fields looks like a normal form fields the better the chances that even a half-intelligent Spam bot will not identify it as a honeypot.

A normal user actually reads your posts

Spam bots as well as human spammer actually do not care about the contents of your post. There is almost no targetted Spam which makes sure that the spam comments are added to a post which is actually really related to their comment. So even human spammers will just scroll down to the bottom of the post and enter some text in the form fields. Their goal is to produce as much spam as possible in the shortest period of time. So the chances are that they will try to spend as little time as possible on your page.

So a way to fight both human spammers and spam bots is to make sure that a comment can only be posted after the user has spent a certain amount of time on your page. How long this is basically depends on the type of content you have on your page. If you just post short jokes, it could be that a legitimate user posts a smiley as comment after only 20 seconds on your page. If you have long and complex articles on your site, the chances are that nobody will comment on your posts without spending at least a minute on the page.

This spam protection mechanism can be implemented by adding some JavaScript code which will return an error message when you try to submit a comment to early. Of course anything which resides purely on the client side is out of your control, so it can be circumvented by a spammer. That’s why you need to also have a check in the backend making sure that the JavaScript check has run (i.e. by adding data to the form before posting it).

A normal user also understands the fields contained in a comment form

Of course a human spammer also does. So developping a spam protection mechanism based on this will only help against spam bots not specifically targetting your site.

This just involves adding a form field and expecting a given value. It’s simplest form is having a checkbox labelled “I am NOT a spammer”. The most complex form is having some kind of captcha. Since I hate captchas, I’ve only implemented the simple checkbox option in my plugin. It’s not much additional work for a legitimate user and will still block stupid spam bots.

Additionally to this (or as an alternative), you can also implement a second similar mechanism involving automatically adding some kind of token to the form field using JavaScript (when the form is submitted). In the backend you then check the presence of this token. Spam bots ususally do not run JavaScript code on your page, so they will submit the form without this token.

Additional ways to identify legitimate users

If you want to make 100% sure that you do not get comment spam, there are a few additional methods you can use to prevent spam:

  1. Check whether a gravatar is associated with the provided email address
  2. Only allow registered users to comment
  3. Completely disable commenting

Of course, for a blog, I wouldn’t recommend disabling commenting as comments are often a valuable input both for you and for your visitors. Also many spam fighting mechanisms also make life more difficult for legitimate visitors who want to comment on a page. I personally hate Captchas and every time I get a captcha wrong on first entry or get a text I need to enter which I can hardly read, I just move away.

So forcing users to have a gravatar or registering in order to comment, could reduce the number of visitors who will actually take the time to post a comment.

Conclusion

Many of the mechanisms presented in this article are fairly simple and not at all bullet-proof. A human spammer would be able to workaround them all and it would also be possible to create a spam bot being able to work around them. What you need to keep in mind is:

  • Spam is usually not targetted. The spam comments you get are posted on millions of sites.
  • The goal of spammers is to get through with least effort.

This basically means that even though a spammer might implement a bot to workaround these mechanisms, why should he waste his time ? There are millions of sites out there and you are only one of them. Even spending an hour implementing a way to workaround you spam protection would be a waste of time.

So, of course, a human spammer could wait for 30 seconds before posting a comment on your site. But instead of spending 5 minutes posting 10 spam comments on your site, it’d make more sense to post 30 spam comments on another sites which doesn’t have this kind of protection.

On this site, I’ve configure the WP Spam Fighter plugin to enable the following mechanisms:

  • Time based protection
  • Honeypot protection
  • “Not a spammer” checkbox
  • JavaScript human check

And the results are not bad. After switching to this plugin from Akismet, the number of spam comments which went through reduced from 1 or 2 a day to 1 or 2 spam comments a month. Since I still do get a spam comment every 5 minutes (which is automatically marked as Spam by the plugin) and I only check the spam comment folder once in a while, it’s difficult to say whether there are any false positives. But there should actually not be any, since none of these mechanisms interpret comment data to identify spam. Moreover the number of comments I get on the site didn’t change after introducing this plugin.

And the great advantage versus Akismet is that this plugin doesn’t need to store or transmit any data related to the visitors of this site, which can be a problem in some countries.

WordPress: How to only load scripts if widget is displayed

I have written a few WordPress plugins to which I keep adding functionality. Very often the number of scripts and styles I need to load to support these enhancements keep growing. Usually they are still not so large. But if you have pages where the widgets are not displayed, you’re just loading scripts and style that are actually not required and thus slowing down the page load without any additional benefit.

So I was looking for a way to only load the scripts and styles I require for one of my plugin when the widget provided by this plugin is actually displayed. Pretty quickly I came across the WordPress built-in is_active_widget Widget function. This is the description of this function in the WordPress codex:

This Conditional Tag checks whether widget is displayed on the front-end.

Sounds like exactly what I was looking for ! Unfortunately the description of the function is wrong. As the name of the function says, it tells you whether a widget is active, not whether it is displayed on this particular page (or on any page for that matter). An active widget is a widget which has been instantiated in a sidebar. This means that you created an instance of the widget in a sidebar but it doesn’t tell you whether this sidebar is displayed anywhere and even less whether this sidebar is displayed in the current page.

So you will probably find some suggestions to handle the problem using something like:

function check_widget() {
    if(is_active_widget( '', '', 'my_widget_id')){
        wp_enqueue_script(...);

    }
}
add_action('wp_footer', 'check_widget');

The only case I can think of where this will effectively prevent your scripts from being unnecessarily loaded is if you do not use this widget in any sidebar. Of course, if the sole purpose of your plugin is to provide this widget, your users could just deactivate it instead of relying on this method…

But if the widget exists in a sidebar and a full-width page (without sidebar) is being displayed, it will still load the scripts.

On the other hand, if you allow your users to display you widgets only outside of a sidebar by using a short code or by calling a PHP function displaying the same contents as the widget, the scripts will not be loaded and it will not work anymore…

So the solution I went for is a different one. This might not work for all plugins but for the one I was updating, it works perfectly. All you need to do is not to enqueue the scripts in wp_head or before it, but to enqueue them when displaying something (whether as a widget or a short code).

I have done the following:

  1. Add a plugin setting called do-not-load-scripts which allows the administrator to defined that the scripts should not be loaded using the wp_enqueue_scripts hook but only when a widget is being displayed.
  2. In my wp_enqueue_scripts hook, I check the value of this setting. If it’s set, I enqueue scripts and styles if is_admin() returns true.
  3. In the widget() function of my widget, I check the value of the setting. If set, I enqueue the scripts and styles.

If multiple instances of the widget are displayed. the enqueue functions will be called multiple times but it will still only output the scripts and styles only once. Of course you could also set a global variable to only enqueue once but I doubt this would have much of a positive impact on performance or load.

Since my short code or the PHP function I provide basically just call the widget() function with some parameters, this will handle all cases where the widget is displayed whether in a sidebar or not.

When using this method, you need to make sure that your widget doesn’t rely on the scripts and styles being loaded in header (because they will be loaded in the footer).

The code for the wp_enqueue_scripts hook looks like this:

if ( is_admin() ) {
	wp_enqueue_style( ... );
	wp_enqueue_script( ... );
} elseif (<setting NOT set by admin>) {
	wp_enqueue_style( ... );
	wp_enqueue_script( ... );
}

And the code in the widget() function like this:

if (<setting set by admin>) {
	wp_enqueue_style( ... );
	wp_enqueue_script( ... );
}

 

About WordPress Multisite

WordPress multisite has been aroung for quite some time now. But since many users still have a single-site installation and it’s not so easy to install WP Multisite using alternative ports (see this post), you will see that first not all plugins behave well in a multisite environment. Second, if you have a problem and google for it you will most likely find solutions working on single site installations but it’s not always to find out whether it will work in a multisite environment and if not, how to modify it to work in such an environment.

That’s the reason why I’ve started writing this post. First, I am a plugin author and need to have my plugins work in a multisite environment. Second, I am considering moving from multiple single sites to a multisite installation myself.

What’s WordPress Multisite?

WordPress Multisite was introduced in WordPress 3.0. It basically allows you to host multiple sites in a single WordPress installation. This means that once your WordPress installation is converted to multisite, it becomes a network of sites, with one central site used to administrate the network and a series of virtual sites building up your network.

The sites in this network are then not completely independent of another. They share some features. But they also have their own data and settings. So it is important to understand what’s shared and what not.

Just before we really start: in the WordPress multi-user solution which was available before Multisite was introduced in WordPress 3.0 the wording was a little bit different. That why I tend to sometimes mix it all. I’ll try to stick to the WP 3.0 wording but can’t promise I’ll always manage to do it.

What used to be called a Blog before WP 3.0 is now a site.
What used to be called a site before WP 3.0 is now a network.

What’s different with WordPress Multisite?

There are a few differences between a multisite based network of sites and a network of individual WordPress installations. Except for the WordPress PHP scripts themselves, a WordPress installation is basically made of some database content and the contents of the wp-content subdirectory.

wp-content

Let’s start with wp-content. It contains your plugins, themes and uploads (in three different directories).

Plugins

The plugins are installed for the whole network. Since you cannot install plugins per sites, there is no difference difference in the directory structure.

Note that this doesn’t mean that when you install a plugin it has to be active on all sites in the network. But it does mean that if a plugin is not installed globally, a site administrator cannot install this plugin just for a single site.

It also means that when a plugin is updated, it’s updated for all sites in the network. This is a good thing since you do not have to update each plugin for each site individually. On the other side, if you know that one site has a problem with the new version of a plugin and another site needs the new version, there is no way to handle this in a WordPress Multisite installation.

But even though the installation files are the same for all sites in the network give a specific plugin, this doesn’t mean that the settings of this plugins have to be the same for all sites. Depending on the plugin, you may want to have a site specific configuration or a network-wide configuration. See this post to learn more about network-wide plugin settings.

Also since the plugin files are available globally, this means that as a plugin developer, you do not need to take care of Multisite installations when enqueuing files (JavaScript or CSS files).

Themes

Themes work the same way as plugins in this case. Themes are installed globally and a user administrating a site in the network can activate these themes for his site. You can also restrict themes for individual sites and make them only available to a subset of the sites.

When working with themes and Multisite, it is important to keep in mind that whenever you update a file of the theme, you’re not modifying the file for one site but for all sites using this theme. So if you use a theme and would like to change a few things e.g. some CSS, instead of modifying the theme itself, you should do one of the following:

  • Create child theme to use in the individual sites
  • If it is already a child theme, you should create a copy of the theme before modifying it. The drawback is that you will not get automatic updates from wordpress.org anymore since it’s not the original theme anymore
  • Use themes which provide you with means to make the adaptations you need in their settings
  • If all you want to change are CSS styles, there are also plugins allowing you to load some additional styles
Uploads

The uploads work in a different way. Unlike the plugins and themes, upload can either be available on the network level or for a specific site.

So under the uploads directory, you will find one subfolder per year for the network-level uploads and a “sites” subdirectory. The “sites” subdirectory contains in turn one subdirectory per site. The names of this subdirectory are numbers representing the site ID. And in these sites subdirectories, you will again find a subdirectory per year.

The database

First of all, even in a Multisite installation, all tables, whether central tables or tables for individual sites in the network, are stored in a single MySQL database.

Assuming you chose the prefix “wp_” when installing WordPress, all central tables will be called wp_something and the site specific tables will be called wp_X_something, where X is the site ID.

The central site of your network will contain all tables you’d normally see in a single site WordPress installation. The individual sites in the network will only have a subset of these tables, containing the data which are stored per site:

  • wp_X_comments and wp_X_commentmeta contain data related to comments
  • wp_X_posts and wp_X_postmeta contain data related to posts
  • wp_X_terms, wp_X_term_relationships and wp_X_term_taxonomy contain data related to terms and taxonomies
  • wp_X_links contains site specific links
  • wp_X_options contains sites specific settings and options

wp_X_options also includes data about plugins and themes available on individual sites.

As said before, the central site also has all these tables. Additionally, it also has a few more database tables:

  • wp_blogs contains a list of all sites in the network. It’s still called “blogs” as in the old wording.
  • wp_blog_versions contains the db version of each site. I guess you usually don’t care about this table.
  • wp_users contains all users in the network
  • wp_usermeta contains data related to the users in wp_users
  • wp_registration_log contains a list of which users are registered on which site and when they registered
  • wp_signups contains a list of users which signed up on you network along with the activation key and when/whether they were activated
  • wp_site contains a list of networks and their primary domain. So you could have WordPress installation with multiple networks with each network having multiple sites
  • wp_sitemeta contains additional information about the networks

Handling WordPress Multisite in your code

First of all, most WordPress functions you might be using are related to the current site. So when you get e.g. a list of categories using get_categories(), you will see only the categories for this one site. If you need to get e.g. the categories for all sites, it will require some extra work.

There are 5 functions which are relevant for such things:

  • get_current_site()
  • switch_to_blog()
  • restore_current_blog()
  • wp_get_sites()

get_current_site

This function returns a site object containing the following public variables:

  • id: this is the site ID (number)
  • domain: this is the domain name of the current site
  • path: this is the URL path of the current site
  • site_name: this is the name of the current site

You can also get the ID of the current site using get_current_blog_id().

switch_to_blog

This function takes a site ID as parameter. If the provided site ID doesn’t exist, it will return false.

Once you’re done doing whatever you need to do with this other site, you can get back to the original site using restore_current_blog().

restore_current_blog

You should call restore_current_blog() after every call to switch_to_blog(). The reason is that WordPress keeps track of site switches and if you do not use restore_current_blog, the global variable $GLOBALS[‘_wp_switched_stack’] will be left with some values in there.

An alternative is to clean up $GLOBALS[‘_wp_switched_stack’] yourself:

$GLOBALS['_wp_switched_stack'] = array();
$GLOBALS['switched'] = false;

wp_get_sites

This function returns an array with the list of all sites in the network. You can use this function to get a list of all sites and iterate through them (and switching from one blog to the next one using switch_to_blog()).

Iterating through sites

In order to iterate through sites e.g. in order to get all categories for all sites, you can do the following:

// store the original current site
$original_site = get_original_site();

// get all sites
$sites = wp_get_sites();

// loop through the sites
foreach ( $sites as $site ) {

	// switch to this one site
	switch_to_blog( $site['blog_id'] );

	// do something here

}

// return to the original site
switch_to_blog( $original_site->id );

Note that by doing this, the global variables tracking the site switches will not be reset. So it is recommended to rather do:

// get all sites
$sites = wp_get_sites();

// loop through the sites
foreach ( $sites as $site ) {

	// switch to this one site
	switch_to_blog( $site['blog_id'] );

	// do something here

	// restore the original site
	restore_current_blog();
}

Or something like this:

// store the original current site
$original_site = get_original_site();

// get all sites
$sites = wp_get_sites();

// loop through the sites
foreach ( $sites as $site ) {

	// switch to this one site
	switch_to_blog( $site['blog_id'] );

	// do something here

}

// return to the original site
switch_to_blog( $original_site->id );

// manually reset the global variables tracking site switching
unset ( $GLOBALS['_wp_switched_stack'] );
$GLOBALS['switched'] = false; 

 TO BE CONTINUED…

WordPress: Network-wide plugin settings

When developing a plugin, which will be used on single site, as well as multi-site, there are two ways of supporting multi-site deployments:

  1. The plugin is activated per site and provides per-site settings only.
  2. The plugin can be activated for the whole network and provides network-wide settings

I’m currently working on a plugin which needs to support both scenarios. The first scenario is pretty straight forward. But the second one is a little bit trickier.

  • Proper support of the network activation
  • Activation for new sites
  • Network-wide settings
  • Proper support of the network deactivation

Proper support of the network activation

To activate the plugin in a multisite environment, you’ll need go through all the blogs and activate them individually e.g.:

register_activation_hook( __FILE__, 'my_plugin_activate' );

function my_plugin_activate($network_wide)
{
	global $wpdb;

	if (function_exists('is_multisite') && is_multisite() && $network_wide) {
		$current_blog = $wpdb->blogid;
		$blogs = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
		foreach ($blogs as $blog) {
			switch_to_blog($blog);
			my_plugin_activate();
		}
		switch_to_blog($current_blog);
	} else {
		my_plugin_activate();
	}
}

If we are in a multisite environment but the plugin is only activated to for a blog, a normal activation is performed. Otherwise we fetch the list of all blogs and activate them one by one before reverting to the current blog.

Note that you should not use restore current blog() to get back to the original blog since it will revert to the blog active before the last switch_to_blog(). So if switch_to_blog() is called twice it will not revert to the blog which was active before this all started.

Activation for new sites

When a new blog is added, you’ll need to specifically activate the plugin for this blog since this blog was not present, when the activation occurred.

add_action( 'wpmu_new_blog', 'activate_new_blog' );
 
function activate_new_blog($blog_id) {
    global $wpdb;
 
    if (is_plugin_active_for_network('plugin_name/plugin_main_file.php')) {
	switch_to_blog($blog_id);
	my_plugin_activate();
	restore_current_blog();
    }
}

So first we check whether the plugin was activated for the whole network. If it is the case, we switch to the new blog, activate the plugin for this blog and switch back to the previous blog. Here it is fine to use restore_current_blog() since we only use one switch_to_blog().

Network-wide settings

In single site environment, you would use the functions get_option() and update_option() to respectively read and write your plugin settings. In a multi-site environment, you should rather use the get_site_option() and update_site_option() functions instead.

So instead of:

get_option('my_settings', array())
...
update_option( 'my_settings', $settings );

Use:

get_site_option('my_settings', array())
...
update_site_option( 'my_settings', $settings );

When registering your settings page, instead of using options-general.php as parent slug, when calling add_submenu_page, if your plugin has been network activated, you’ll need to add the submenu page using settings.php as parent slug.

Also when linking to your settings page (i.e. from the plugins list), in a single site environment, you’d do it this way:

<a href="options-general.php?page=my_settings">Settings</a>

But to link to the network settings page, you’ll need to use the following

<a href="settings.php?page=my_settings">Settings</a>

The hook for adding the link is also different. Instead of adding a filter for ‘plugin_action_links_pluginname_pluginfile’, you’ll have to add a filter for ‘network_admin_plugin_action_links_pluginname_pluginfile’.

Also the hook to use to call your registering function is different, instead of adding an action to the ‘admin_menu’ hook, you should add an action to the ‘network_admin_menu’ hook in a network activation scenario.

In your settings page, when in a single site environment, you can use the settings API and do not need to update your settings manually, by using options.php as form action:

<form method="post" action="options.php">

For network-wide settings it’s not so easy. There is no direct equivalent to options.php for such settings. You will need to direct to edit.php and provide an action name which will be linked to one of your functions where you will update the settings. Here the registration and definition of this function:

add_action('network_admin_edit_my_settings', __FILE__, 'update_network_setting');

function update_network_setting() {
	update_site_option( 'my_settings', $_POST[ 'my_settings' ] );
	wp_redirect( add_query_arg( array( 'page' => my_settings, 'updated' => 'true' ), network_admin_url( 'settings.php' ) ) );
	exit;
}

Of course instead of my_settings, you should use the name of your settings. The name of the action hook is network_admin_edit_ followed by an action name which will be used as parameter to edit.php in the form definition e.g.:

<form method="post" action="edit.php?action=my_settings">

Of course, since you want to support both the normal and the network activation scenarios, you’ll have to use is_plugin_active_for_network() to figure out in which scenario you are and trigger the appropriate logic.

Proper support of the network deactivation

For the deactivation, you need to do exactly the same you’ve already done for the activation i.e.:

register_deactivation_hook( __FILE__, 'my_plugin_deactivate' );

function my_plugin_deactivate($network_wide)
{
	global $wpdb;

	if (function_exists('is_multisite') && is_multisite() && $network_wide) {
		$current_blog = $wpdb->blogid;
		$blogs = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
		foreach ($blogs as $blog) {
			switch_to_blog($blog);
			my_plugin_deactivate();
		}
		switch_to_blog($current_blog);
	} else {
		my_plugin_deactivate();
	}
}

function my_plugin_deactivate()
{
	//deactivate the plugin for the current blog
}

Of course in a network-wide deactivation scenario, you may want to add some additional cleanup after the plugin has been deactivated for all blogs.

Conclusion

So supporting all three scenarios (single site, multisite with per blog activation and multisite with network activation) is not so difficult. The only tricky part is handling network-wide settings. Unfortunately, the settings API does not help you much here. But once you’ve done it for a plugin, it’s just a matter of copy&paste.

Note that if you do not perform anything special as part of your activation, you do not need to handle the multisite activation since WordPress will activate the plugins appropriately. It is only required if your plugin does some additional magic since WordPress will not call you for each blog.

WordPress: Running multisite on different port

Since I’m maintaining a few WordPress plugins I wanted to have a local installation where I can also make sure that new versions of the plugins also behave well in a multisite environment (site network). Unfortunately, I cannot use ports 80 and 443 since I need them for some other projects I’m working on (using IIS). So my local apache server runs on ports 8080 (http) and 8443 (https).

The problem is that this is not supported by WordPress. You need to run on ports 80 and/or 443. Otherwise WordPress won’t let you create a site network and you’ll get a message saying:

You cannot install a network of sites with your server address.
You cannot use port numbers such as 8080.

A single site installation of WordPress works fine on port 8080 but for some reason, it isn’t support for WordPres Site Networks… Well, that’s not a reason not to use it 🙂

Since it’s all PHP, you can easily just fix it. Of course, you need to keep in mind that any update of WordPress might remove the changes and you’ll need to do it all again. But since it’s just a test installation, it’s not a problem for me.

So, to find out how to fix it, we first need to find out where this message is displayed. Using grep, you can quickly find that it comes from the function network_step1 in wp-admin\network.php. The relevant piece of code looks like this:

if ( ( false !== $has_ports && ! in_array( $has_ports, array( ':80', ':443' ) ) ) ) {
	echo '<div class="error"><p><strong>' . __( 'ERROR:') . '</strong> ' . __( 'You cannot install a network of sites with your server address.' ) . '</p></div>';
	echo '<p>' . sprintf( __( 'You cannot use port numbers such as <code>%s</code>.' ), $has_ports ) . '</p>';
	echo '<a href="' . esc_url( admin_url() ) . '">' . __( 'Return to Dashboard' ) . '</a>';
	echo '</div>';
	include( ABSPATH . 'wp-admin/admin-footer.php' );
	die();
}

If you use an extra :80 or :443, WordPress will not complain but it will if it is another port number. So in order not to get this error message, you just need to add the ports you need to the list, replacing:

if ( ( false !== $has_ports && ! in_array( $has_ports, array( ':80', ':443' ) ) ) ) {

by:

if ( ( false !== $has_ports && ! in_array( $has_ports, array( ':80', ':443', ':8080', ':8443' ) ) ) ) {

Of course, I assumed there was a reason why only ports 80 and 443 were supported. So I searched in the code for 80 and 443 and found this in wp-includes\ms-settings.php:

if ( substr( $domain, -3 ) == ':80' ) {
	$domain = substr( $domain, 0, -3 );
	$_SERVER['HTTP_HOST'] = substr( $_SERVER['HTTP_HOST'], 0, -3 );
} elseif ( substr( $domain, -4 ) == ':443' ) {
	$domain = substr( $domain, 0, -4 );
	$_SERVER['HTTP_HOST'] = substr( $_SERVER['HTTP_HOST'], 0, -4 );
}

In a previous version of this post I was adding some code for ports 8080 and 8443 but I realized it was actually making things worse. The only purpose of this piece of code is to remove unnecessary port numbers which are not relevant for comparing domain names. But ports 8080 and 8443 are relevant (https://benohead.com:8443 doesn’t point to the same web server as https://benohead.com, but https://benohead.com:443 does).

With the changes above I could create the site network. But when I tried to login, I was redirected to the IIS, to http://localhost/wordpress/wp-login.php instead of http://localhost:8080/wordpress/wp-login.php. Since I couldn’t find anything in code which would explain this, I had a look at the wp_options. And saw that the siteurl option contained http://localhost/wordpress/ instead of http://localhost:8080/wordpress/. So I just updated it in MySQL using the following SQL statement:

UPDATE `my_wordpress_db`.`wp_options` SET `option_value` = 'http://localhost:8080/wordpress/' WHERE `wp_options`.`option_name` = 'siteurl';

And tried again. And this time I got to the right login page ! Remember to replace “my_wordpress_db” by the name of your WordPress database.

I then noticed that there were still some links pointing to http://localhost/wordpress. Finally I saw that the “home” option in wp_options was still pointing to the wrong URL. After fixing it, everything looked fine:

UPDATE `my_wordpress_db`.`wp_options` SET `option_value` = 'http://127.0.0.1:8080/wordpress/' WHERE `wp_options`.`option_name` = 'home';

So now everything was working fine, the admin page, the blog post/pages. But then I realized that when creating a new site, I saw “localhost:8080” in the UI but it was saving localhost8080 instead. So something was removing the colon. And I found the following in the function wpmu_create_blog in wp-includes\ms-functions.php:

$domain = preg_replace( '/\s+/', '', sanitize_user( $domain, true ) );

With the second parameter is true, only alphanumeric characters plus these: _, space, ., -, *, and @ are returned. Of course, one solution would be to add colon to the list but then it would allow usernames to contain colons as well. So I ended up just setting the second parameter to false, or rather commenting it out, since false is the default:

$domain = preg_replace( '/\s+/', '', sanitize_user( $domain/*, true*/ ) );

Now everything seems to be working.

While looking for a solution, I did find a few links explaining how to do it in version prior to WordPress 3.7. But I needed it for WordPress 3.9.1. So I had to find a solution by myself. Of course I have only tested it on WP 3.9.1 and haven’t checked when was last time these pieces of code were changed. So it might work on WP 3.7 and WP 3.8 as well but I cannot guarantee anything.

WordPress: Deactivating Jetpack Comments

When activating the Jetpack module called Jetpack Comments, you will see the following banner after activation:

Jetpack Comments Activated

This actually clearly states how to disable it. But for some reason it took me a long time to find it, so I thought I’d write a post about it.

As stated in the banner, you need to go to the Jetpack modules page and click on “Learn More”:

Jetpack Comments Learn More

The “Deactivate” option will then be displayed:

Jetpack Comments Deactivate

The reason I couldn’t find it for a while is that a new area called “Jetpack Comments” is made visible and I was thus only looking at this area. But the “Deactivate” button is shown next to the “Learn More” button:

Jetpack Comments Deactivate Button

Just click on this button and Jetpack Comments will be deactivated.

By the way, after activating this module, there were absolutely no change on my site. So that’s the reason I deactivated it again. I just didn’t feel like trying to find out why it didn’t work…

WordPress asking for FTP connection information when updating plugins

This morning I saw that a few plugins I used had a new version and wanted to update them. I do this quite regularly on all my webpages. This time instead of seeing the update progress, I was presented a web page where I was asked to enter some FTP information:

WordPress Update Plugin FTP info

There could be two reasons for this:

  1. You want to be updating through FTP/SFTP but no credentials are added to wp_config.php
  2. You want to the updates to be directly done on the file system but WordPress cannot write to the filesystem

For the first one, the fix is easy. You just need to add your credentials to wp_config.php:

define('FTP_HOST', 'myhostname');
define('FTP_USER', 'myusername');
define('FTP_PASS', 'mypassword');

Once the file is updated, WordPress should be able to update itself, plugins and themes without asking for credentials.

Now, if like me you do not want to use FTP or SFTP but want WordPress to directly write the files to the server filesystem, you need to make sure that the user used to run WordPress has write access to the wp-content directory.

First you need to figure out which user is being used to run WordPress. The easiest way to do it (assuming you are on a Unix/Linux/Mac system) is to write a short PHP script which calls the whoami command. For this create a file called checkuser.php in the root folder of your webserver (e.g. /var/www/vhosts/benohead.com/httpdocs/checkuser.php) with the following contents:

<?php echo "user=".exec("whoami")."<br/>"; ?>

This will write something like (when you go to http://yourwebsite/checkuser.pnp):

user=www-data

So your web user is www-data.

If you cannot use whoami, another way to do it is to create a file info.php with the following contents:

<?php phpinfo(); ?>

And when you go to http://yourwebsite/info.php and there should be some entry showing you the web user (search e.g. for “APACHE_RUN_USER” or “User/Group”).

Now you need to give this user the rights to wp-content. There are a few ways to do it:

Give it the ownership of the files in wp-content e.g.:

chown -R www-data /var/www/vhosts/benohead.com/httpdocs/wp-content

Or make sure that this user belongs to the group owning wp-content and its children and give write access to the group:

chmod -R g+w /var/www/vhosts/benohead.com/httpdocs/wp-content

Now the user should have write access.

If it still doesn’t work, we’ll need to gather more info in checkuser.php:

<?php

echo "user=".exec("whoami")."<br/>";
echo "getmyuid=".getmyuid()."<br/>";

$context="/var/www/vhosts/benohead.com/httpdocs/wp-content/plugins/";
               
$temp_file_name = $context . 'temp-write-test-' . time();
echo "temp_file_name=".$temp_file_name."<br/>";
                
$temp_handle = @fopen($temp_file_name, 'w');
echo "temp_handle=".$temp_handle."<br/>";
                
if ( $temp_handle ) {
    echo "fileowner=".@fileowner($temp_file_name)."<br/>";
                        
     if ( getmyuid() == @fileowner($temp_file_name) )
         $method = 'direct';    
     @fclose($temp_handle);
     @unlink($temp_file_name);
}

echo "method=".$method."<br/>";
?>

This basically performs the same check as wordpress to determine whether the filesystem can be written to directly.

When you go to http://yourwebsite/checkuser.php, you should now see more info e.g.:

user=www-data
getmyuid=33
temp_file_name=/var/www/vhosts/benohead.com/httpdocs/wp-content/plugins/temp-write-test-1387703780
temp_handle=Resource id #4
fileowner=33
method=direct

If getmyuid and fileowner have the same value and method is direct, this means that WordPress has already seen that the filesystem is directly writeable. If they two values are different and method is empty (or if you only see the first few lines above), then you need to check the ownership of the directory again.

The problem could be that the getmyuid() function used by WordPress does not actually return the uid of the user running the script but the uid of the user owning the file being run. So you might need to issue the chown command above not only for wp-content but for all files (or at least for ./wp-admin/update.php).

If the ouput of checkuser.php is fine but it still doesn’t work, the problem might be that WordPress does figure out it can write directly but is forced to use FTP. You can check this by updating the function used to determine the filesystem method. It’s called get_filesystem_method and is located in ./wp-admin/includes/file.php.

Search for the following line:

return apply_filters('filesystem_method', $method, $args);

and replace it by:

# return apply_filters('filesystem_method', $method, $args);
echo "method before filters = ".$method."<br/>";
$method = apply_filters('filesystem_method', $method, $args);
echo "method after filters = ".$method."<br/>";
return $method;

Then try to update WordPress again. You should see two additional lines at the top e.g.:

method before filters = direct
method after filters = ftpext

The first one shows the method WordPress has determined it could use. The second one is the final method to be used after applying all filters. In this case shown above, you see that WordPress figured out it can use the direct method but some filter replaced it by the FTP method.

So then you just need to go to your WordPress root directory and search for files defining a filesystem_method filter e.g.:

# grep -R "add_filter.*filesystem_method" *
wp-config.php:	add_filter('filesystem_method', create_function('$a', 'return "direct";' ));
wp-config.php:	add_filter('filesystem_method', create_function('$a', 'return "ftpext";' ));

Now you see that two filters are defined in wp-config.php: one forcing the direct method and one forcing the FTP method. So to fix it, you just need to edit wp-config.php and remove the following lines:

if(is_admin()) {
        add_filter('filesystem_method', create_function('$a', 'return "ftpext";' ));
        define( 'FS_CHMOD_DIR', 0755 );
}

These lines seem to have been automatically added by Plesk in my case. Yesterday, I switched off the automatic WordPress updates in Plesk and it probably inserted these lines. What’s strange is that I made the same change in Plesk for 7 other sites and none of them had this problem…

WordPress: Remove query strings from static resources

When checking your site with PageSpeed Insights, you might get the following finding:

Remove query strings from static resources

Enabling public caching in the HTTP headers for static resources allows the browser to download resources from a nearby proxy server rather than from a remote origin server.

Suggestions for this page

Resources with a “?” in the URL are not cached by some proxy caching servers. Remove the query string and encode the parameters into the URL.

It will report the finding for CSS or JavaScript files referenced with a specific version number in your site e.g.:

http://spielsachenweb.de/wp-content/plugins/google-analyticator/external-tracking.min.js?ver=6.4.3

In many cases, you will see that the version number is not required as you actually only have one version available at that location and you’d get exactly the same file with or without the version number. But the version specification as a parameter does have a negative impact on caching on proxy servers (especially old Squid proxies). That’s why PageSpeed Insights recommends dropping the version specification from the URL.

If you have written the <script> or <link> tag directly in some template files, you can just delete the part starting from the question mark (?). But most probably it will come from themes or plugins you use.

1. The first step is to check whether you really do need the version number or not. You can do this by copying the URL from the PageSpeed Insights report and open it in a browser. Do it twice, once with the version number and once without. Copy each of them and compare them (e.g. using WinMerge). If you see no difference, then you’re safe removing the version number.

2. Once you’re done with the step above. You have a list of files which can do without the version number and a list of those which cannot. Now you can add a very simple function in functions.php in your current theme (in the theme editor) to remove the parameters for specific URLs:

function remove_style_and_script_version($url) {
	$url_to_fix = array('http://spielsachenweb.de/wp-content/plugins/google-analyticator/external-tracking.min.js?ver=6.4.3',
					    'http://spielsachenweb.de/wp-includes/js/jquery/jquery.js?ver=1.8.3');

	if (in_array($url, $url_to_fix)) {
		//if it is one of the URLs to process, remove everything after the question mark
		$parts = explode('?', $url);
		return $parts[0];
	}
	//otherwise return the URL as is
	return $url;
}
add_filter('script_loader_src', 'remove_style_and_script_version', 20, 1);
add_filter('style_loader_src', 'remove_style_and_script_version', 20, 1);

Note that the two last parameters of the add_filter calls (20, 1) mean that the new function should be called with priority 20 (default is 10) and will thus happen after all other filters on the script or style loader which have a priority of less than 20 (so including the ones using the default priority).

Also note that this trick only works if the scripts and styles are loaded using the WordPress enqueueing mechanism, which is almost always the case.

Now if you want to do it the other way around and strip the part after the question mark for all script and style URLs except for the ones for which you known you’d get the wrong file, you can do the following instead:

function remove_style_and_script_version($url) {
	$url_not_to_fix = array('http://spielsachenweb.de/wp-content/plugins/google-analyticator/external-tracking.min.js?ver=6.4.3',
					    'http://spielsachenweb.de/wp-includes/js/jquery/jquery.js?ver=1.8.3');

	if (in_array($url, $url_to_fix)) {
		//if it is one of the URLs to exclude, return the URL as is
		return $url;
	}
	//otherwise, remove everything after the question mark
	$parts = explode('?', $url);
	return $parts[0];
}
add_filter('script_loader_src', 'remove_style_and_script_version', 20, 1);
add_filter('style_loader_src', 'remove_style_and_script_version', 20, 1);

If you just want to remove everything after the question mark for all script and style URLs:

function remove_style_and_script_version($url) {
	$parts = explode('?', $url);
	return $parts[0];
}
add_filter('script_loader_src', 'remove_style_and_script_version', 20, 1);
add_filter('style_loader_src', 'remove_style_and_script_version', 20, 1);

Note that you might also get parameters added to the URL of scripts and styles when using W3 Total Cache. Please have a look at this post in this case.

There is also an additional special case. If you only want to remove the script or style URLs called with the WordPress version number e.g. because it shows which version you use and if it’s an older one, it might give away some information which can be used by a hacker (although the best solution in this case would be to upgrade to the latest version), you can specifically process only these URLs:

function remove_style_and_script_version($url) {
	global $wp_version;
	return str_replace("?ver=$wp_version", '', $url);
}
add_filter('script_loader_src', 'remove_style_and_script_version', 20, 1);
add_filter('style_loader_src', 'remove_style_and_script_version', 20, 1);

Or you can show a different version:

function remove_style_and_script_version($url) {
	global $wp_version;
	return str_replace("?ver=$wp_version", '?ver=X.XX', $url);
}
add_filter('script_loader_src', 'remove_style_and_script_version', 20, 1);
add_filter('style_loader_src', 'remove_style_and_script_version', 20, 1);

Replace X.XX by the version you want to show.