A small primer on theme mods

Oh, the many things I encounter while doing reviews is amazing.

One of the reasons I love contributing to the review team is to get a better understanding of how WordPress at its core works and it is because of this I feel like there are some things that still are not being mentioned or shared.

A fairly good example of this is what I saw today. I’ve seen this on several themes but I think I finally reached a point where it really, truly irks me.

What are settings?

WordPress themes can use two things to save user inputs – or better yet theme mods. The neat part is there are two ways those can be saved. Using option or theme_mod when registering a setting in the customizer. What I want to focus on for this is the use of theme_modsince it is the recommended method for most themes. Notice I say most. There are cases where it makes sense to use option but that’s for another time. The WordPress theme mods are saved and retrieved using a few functions.

  • get_theme_mods() – gets all theme settings
  • get_theme_mod() – gets a single setting
  • set_theme_mod() saves a theme setting

Now, there are two other functions for theme mods.

  • remove_theme_mod() – remove a single theme setting
  • remove_theme_mods() – removes all theme settings

Those are least likely to be used but they are good to know.

How it works

The WordPress customizer is magic. Okay, not completely and I only say that because if you look at how things work in the JavaScript file it quite literally says, “return magic;

The way the customizer saves things is by using a few object methods. It uses the save() method, which calls the update() method, and tfinally the set_root_value() method of the Setting class. It’s here where our setting actually gets saved.

If we look over the highlighted code we can see what is actually happening.

protected function set_root_value( $value ) {
	$id_base = $this->id_data['base'];
	if ( 'option' === $this->type ) {
		$autoload = true;
		if ( isset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'] ) ) {
			$autoload = self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'];
		}
		return update_option( $id_base, $value, $autoload );
	} elseif ( 'theme_mod' === $this->type ) {
		set_theme_mod( $id_base, $value );
		return true;
	} else {
		/*
		 * Any WP_Customize_Setting subclass implementing aggregate multidimensional
		 * will need to override this method to obtain the data from the appropriate
		 * location.
		 */
		return false;
	}
}

The default setting for registering a theme option is theme_mod and what this means is that when the user clicks that save button, the function set_theme_mod() will be triggered and the setting will be saved to the database. Super cool! Now, what does that little function actually do?

Well, let’s take a look over it:

function set_theme_mod( $name, $value ) {
	$mods = get_theme_mods();
	$old_value = isset( $mods[ $name ] ) ? $mods[ $name ] : false;

	/**
	 * Filters the theme mod value on save.
	 *
	 * The dynamic portion of the hook name, `$name`, refers to the key name of
	 * the modification array. For example, 'header_textcolor', 'header_image',
	 * and so on depending on the theme options.
	 *
	 * @since 3.9.0
	 *
	 * @param string $value     The new value of the theme mod.
	 * @param string $old_value The current value of the theme mod.
	 */
	$mods[ $name ] = apply_filters( "pre_set_theme_mod_{$name}", $value, $old_value );

	$theme = get_option( 'stylesheet' );
	update_option( "theme_mods_$theme", $mods );
}

It gets all the theme mods, then creates a filter for us to use, gets the name of the currently active theme, and finally updates an option.

An option?

Yes. An option. I know. I know. We were talking about theme mods but the way the customizer works with theme_mods is by creating an associative array per theme. For example, if my theme were called For Real Yo, the $theme would be for-real-yo.

WordPress then turns it to a slug. So, the best way to get those options would be to get the option or use the core functions get_theme_mod() and get_theme_mods() to do things for me and because we like using good practices we use get_theme_mod() in order to get the single setting – or rather theme_mod.

You are probably wondering what get_theme_mods() does. I know I did when I first read the code.

function get_theme_mods() {
	$theme_slug = get_option( 'stylesheet' );
	$mods = get_option( "theme_mods_$theme_slug" );
	if ( false === $mods ) {
		$theme_name = get_option( 'current_theme' );
		if ( false === $theme_name )
			$theme_name = wp_get_theme()->get('Name');
		$mods = get_option( "mods_$theme_name" ); // Deprecated location.
		if ( is_admin() && false !== $mods ) {
			update_option( "theme_mods_$theme_slug", $mods );
			delete_option( "mods_$theme_name" );
		}
	}
	return $mods;
}

From the core code, we can see that it gets the theme slug, gets the option ( from the options table ) for the active theme, then it checks to see if it is populated or not, and finally returns the array of theme_mods.

So, why this post?

I am making this because the code that I stumbled across looked a little like:

<?php
/**
 *  Default theme options
 */
if ( ! function_exists( 'for_real_yo_default_options' ) ) :
	function for_real_yo_default_options() {
		$default_options = array(
			/* super long list of "defaults" */
		);
		return apply_filters( 'for_real_yo_defaults', $default_options );
	}
endif;


/**
*  Get theme options
*/
if ( ! function_exists( 'for_real_yo_theme_options' ) ) :

	function for_real_yo_theme_options() {

		$for_real_yo_defaults = for_real_yo_default_options();

		$for_real_yo_option_values = get_theme_mod( 'for_real_yo' );

		if ( is_array( $for_real_yo_option_values ) ) {
			return array_merge( $for_real_yo_defaults ,$for_real_yo_option_values );
		} else {
			return $for_real_yo_defaults;
		}
	}
endif;

Looks rather harmless, right? It is until you realize how they were registering the setting.

$wp_customize->add_setting(
	'for_real_yo[rando-setting]',
	array(
		'default' 			=> 'title-text',
		'sanitize_callback' => 'for_real_yo_sanitize_select'
	)
);

If you can spot the issue, awesome. If not, it is creating an array for one setting of a setting. Yes, a little odd to say the least but I’ll show a quick example.

Let’s say we are comparing one of the core themes with our above code. Okay, let us look at the returned array from each one. Twenty Seventeen would yeild something like:

Array
(
    [header_image] => path/to/file
    [header_image_data] => stdClass Object
        (
            [attachment_id] => id
            [url] => path/to/file
            [thumbnail_url] => path/to/file
            [height] => 1198
            [width] => 2000
        )

    [page_layout] => one-column
    [header_textcolor] => cfe23d
    [colorscheme] => light
    [colorscheme_hue] => 0
)

Now, if we look at the above example’s way of registering you can see the difference much better.

Array
(
    [for_real_yo] => Array
        (
            [some_setting] => 
            [another_setting] => 
            [top_bar_left] => menu
            [right_social] => 
            [right_hours] => 
        )

)

Hopefully this does help in understanding how theme_mods are saved and retrieved and does at least help out one person.

Published by

Jose

Born in El Salvador. Loves to read, write, draw, paint, build, test, typography, hike, photography, art, design, sewing, and many other things.