Why you should not put theme code in plugins

I regularly maintain about 40 WordPress websites. Updating plugins and themes can cause issue, but updating core usually goes without any issue. Not so this week on a new website I updated for the first time.

After the core update I got the “There has been a critical error on this website” dialog and the backend was broken. Fortunately I created a fresh backup just before the update. But as the website was not hosted on one of our servers and the FTP account was not showing all files, I first had to use the Web-FTP function from the hoster to be able to download the backup file. With a local copy of the website, the debugging session could start.

Customizing theme icons

Let’s take a look at the code that caused the issue. With debugging mode enabled, I was able to find this error message:

PHP Fatal error:  Uncaught Error: Class 'TwentyTwenty_SVG_Icons' not found in ...

This error was caused by the following lines (code simplified for the example):

function add_custom_twentytwenty_icons() {
	TwentyTwenty_SVG_Icons::$ui_icons['hamburger'] = '<svg><!-- ... --></svg>';
}
add_action( 'after_setup_theme', 'add_custom_twentytwenty_icons' );

A simple code to add a custom icon to the TwentyTwenty “ui” SVG icons group. This has been done in an action after_setup_theme which looks reasonable. But there was something essential missing. The check, if the class does exists. So how could it be done better?

Fixing the issue

There are several ways in how we can fix this specific issue. Let’s take a look at them to find a good solution.

Check for existence of the class

As the error indicated, the code fails, as the class does not exist and therefore the property cannot be extended. So why don’t we just check that first:

function add_custom_twentytwenty_icons() {
	if ( class_exists( 'TwentyTwenty_SVG_Icons' ) ) {
		TwentyTwenty_SVG_Icons::$ui_icons['hamburger'] = '<svg><!-- ... --></svg>';
	}
}
add_action( 'after_setup_theme', 'add_custom_twentytwenty_icons' );

This would work. Now we are sure that we only change the property of the class, if it exists. But there are some issues with this solution. What if the class name changes? What if the property name changes? In those cases, we either get another fatal error or a warning and broken code at least.

Overwriting the pluggable class

Guess what? This specific class of TwentyTwenty is pluggable. So if you want to change the behavior of the class, you could simply just declare the class in your own code before it is loaded by TwentyTwenty itself.

The only downside with this: if you only want to add an icon, duplicating the whole class might be a bit of an overkill. You would have to make sure that your cloned class keeps in sync with the original class on future updated of TwentyTwenty.

Use a filter!

Yes, you read that correctly, there is a filter to alter the SVG icons available in TwentyTwenty we could just use to add our own icon:

/**
 * Filters Twenty Twenty's array of icons.
 *
 * The dynamic portion of the hook name, `$group`, refers to
 * the name of the group of icons, either "ui" or "social".
 *
 * @since Twenty Twenty 1.5
 *
 * @param array $arr Array of icons.
 */
$arr = apply_filters( "twentytwenty_svg_icons_{$group}", $arr );

In our case, we wanted to add an icon to the “ui” group, so the new function could look as simple as this:

function add_custom_twentytwenty_icons( $icons ) {
	$icons['hamburger'] = '<svg><!-- ... --></svg>';

	return $icons;
}
add_action( 'twentytwenty_svg_icons_ui', 'add_custom_twentytwenty_icons' );

This is the best solution. The only downside with this is that this callback would be call for every icon that needs to be printed to the page. Maybe this is why the “clever solution” that caused the error was trying to add the icon just once to the class property.

Why was the original solution causing an error?

But my motivation for this blog post was not only to show that specific error and how it could be solved. It’s about why it became an error. The original code was not stored in a child theme, but in a mu-plugin!

The project was using a “vendor-wp-base-theme” as a child theme to TwentyTwenty. Inside of this child theme, not a wohle lot was happening. In addition, there was a mu-plugin “vendor-wp-base-theme-plugin” which used a PHP class adding widgets, enqueuing styles and scripts, adding some ACF filters and using the code from above to add this one SVG icon.

But why is this an issue? Why would the after_setup_theme action fire but the files from the theme would not have been loaded? The issue lies within the wp-settings.php file. This file will fist load all mu-plugin file, then all plugin files and finally all files from “active and valid themes”, so from both the parent and the child theme, before firing the action:

foreach ( wp_get_active_and_valid_themes() as $theme ) {
	if ( file_exists( $theme . '/functions.php' ) ) {
		include $theme . '/functions.php';
	}
}
unset( $theme );

/**
 * Fires after the theme is loaded.
 *
 * @since 3.0.0
 */
do_action( 'after_setup_theme' );

So how can we still get the issue? Let’s take a look at the wp_get_active_and_valid_themes function and what it’s (not) doing:

function wp_get_active_and_valid_themes() {
	global $pagenow;

	$themes = array();

	if ( wp_installing() && 'wp-activate.php' !== $pagenow ) {
		return $themes;
	}
	// ...
}

This function is checking, if WordPress is “installing”, which will also be true, if the core needs to upgrade the database after an update using the wp-admin/upgrade.php file. In this case, the function will return the empty themes array, which will therefore not load the functions.php files from both the parent and child theme and will still fire the after_setup_theme action causing the issue.

Conclusion: never do themes stuff in plugins!

There is a good reason why you should put code to customize a theme into a child theme and code to customize a plugin into another (mu-)plugin. This will prevent issues like the one demonstrated in the code from this blog post. Running theme code in plugins (and vice versa) can very easily cause issues like this, which might be hard to debug, especially when they only occur in rare cases (like a core database update).

If you really have to put code into a place where it would typically does not belong to, always check, if the thing you want to modify exists. And if the original code offers hooks, please always use them. They have been added just for those reasons. Even if you think you are clever and can save some function calls by directly manipulating PHP class properties (or similar things), don’t do that. Others will be very thankful that your code will not break their site with a usually uncritical task like a WordPress major core update. 😉

Posted by

Bernhard is a full time web developer who likes to write WordPress plugins in his free time and is an active member of the WP Meetups in Berlin and Potsdam.

Leave a Reply

Your email address will not be published.