Categories
WordPress Development

Developer Log: Best-Reloaded Theme Update

I've been thinking that I should write some development logs for some work that I do because it may be useful for others. Plus it gets me to writing more which is something I'm trying my hardest to make a habit.

This log is about some updates I'm making to a theme I have hosted in the .org theme repo.

Best Reloaded in the WordPress Theme Directory

Bootstrap Header Nav Improvements

This theme uses Bootstrap 4 for a framework. It has a top navigation bar with a menu using the navwalker class that I help maintain. It also has a search bar and is styled with a custom theme specific colored button.

The search bar is always on, first I want to make it possible to turn it off if users do not want it. Then I plan to offer color choice selections.

Adding an on/off toggle to theme options. This is easy. A checkbox in the customizer and a test for it's value at page generation.

A Checkbox On/Off Toggle In Customizer

Start with adding a section for the header nav options.

$wp_customize->add_section( 'best_reloaded_navbar', array(
	'title' => __( 'Header Navbar', 'best-reloaded' ),
	'priority' => 100,
) );

Then add a control and a setting for the checkbox.

$wp_customize->add_setting( 'display_navbar_search', array(
	'default' => 1,
	'sanitize_callback' => 'best_reloaded_sanitize_checkbox',
) );
$wp_customize->add_control( 'display_navbar_search', array(
	'label' => __( 'Toggle on/off the navbar search form. Checked = on', 'best-reloaded' ),
	'section' => 'best_reloaded_navbar',
	'settings' => 'display_navbar_search',
	'type' => 'checkbox',
) );

This uses a custom sanitization callback that simply checks value is either 1 or 0 – TRUE or FALSE.

/**
 * Sanitization for checkbox input
 *
 * @param booleen $input	we either have a value or it's empty to depeict
 *                       	a checkbox state.
 * @return booleen $output
 */
function best_reloaded_sanitize_checkbox( $input ) {
	if ( $input ) {
		$output = true;
	} else {
		$output = false;
	}
	return $output;
}

The final part of this is testing the value of the option and outputting the search form when it's set to 'on'.

// if the navbar search is on then output search form.
if ( get_theme_mod( 'display_navbar_search', true ) ) {
	get_search_form();
}

This is a screenshot of it in action in the customizer.

Navbar Brand Options

Next thing I was wanting to add was the ability to add a small branding icon to the navbar. Bootstrap has some styles and classes that allow this so let's look at what we need for this.

  • An On/Off toggle for navbar brand.
  • Option to select an image from media library
  • Checkbox to include the site title as text.

This time there are 3 options to add to the customizer. It's 2 checkboxes again and another for image upload. Sanitization for an image upload is a little different than with checkboxes.

Sanitizing Values With Image Uploads

When it comes to sanitizing the values from image uploads what you are actually working with is text strings. Urls in fact.

You get a string with the url to the file. First you want to check that you have a valid extension for the file it points to. WP has a function to do this – wp_check_filetype()

Once you're sure it's the right filetype then you can escape it as a url at return.

/**
 * Santization for image uploads.
 *
 * @param  string $input	This should be a direct url to an image file..
 * @return string          	Return an excaped url to a file.
 */
function best_reloaded_sanitize_image( $input ) {

	// allowed file types.
	$mimes = array(
		'jpg|jpeg|jpe' => 'image/jpeg',
		'gif'          => 'image/gif',
		'png'          => 'image/png',
	);

	// check file type from file name.
	$file_ext = wp_check_filetype( $input, $mimes );
	// if filetype matches the allowed types set above then cast to output,
	// otherwise pass empty string.
	$output = ( $file_ext['ext'] ? $input : '' );
	// if file has a valid mime type return it as valud url.
	return esc_url_raw( $output );
}

Controls and Settings for Branding Options and Image Upload

There's 3 sets of controls and settings here for each of the options we need set above. The most complicated one is the image upload control as it's building it's control from the class of a core control. It's a little more complicated to look at but works essentially the same.

// on/off toggle.
$wp_customize->add_setting( 'display_navbar_brand', array(
	'default' => 0,
	'sanitize_callback' => 'best_reloaded_sanitize_checkbox',
) );
$wp_customize->add_control( 'display_navbar_brand', array(
	'label' => __( 'Enable the navbar branding options which can be a small image and the site-title.', 'best-reloaded' ),
	'section' => 'best_reloaded_navbar',
	'settings' => 'display_navbar_brand',
	'type' => 'checkbox',
) );

// brand image.
$wp_customize->add_setting( 'brand_image', array(
	'default' => '',
	'sanitize_callback' => 'best_reloaded_sanitize_image',
) );
$wp_customize->add_control(
	new WP_Customize_Image_Control(
		$wp_customize,
		'brand_image',
		array(
			'label'      => __( 'Add a brand image to the navbar.', 'best-reloaded' ),
			'section'    => 'best_reloaded_navbar',
			'settings'   => 'brand_image',
			'description' => __( 'Choose an image to use for brancd image in navbar. Leave empty for no image.', 'best-reloaded' ),
		)
	)
);

/ toggle text on/off in brand.
$wp_customize->add_setting( 'display_brand_text', array(
	'default' => 0,
	'sanitize_callback' => 'best_reloaded_sanitize_checkbox',
) );
$wp_customize->add_control( 'display_brand_text', array(
	'label' => __( 'Select the checkbox to display the site title in the navbar as brand text.', 'best-reloaded' ),
	'section' => 'best_reloaded_navbar',
	'settings' => 'display_brand_text',
	'type' => 'checkbox',
) );

Outputting Navbar Brand in a Bootstrap Theme

Now at this point I realised that output would be slightly more complicated than just echoing values. I also spotted that very long titles could break layout of navbar quite easily so I needed to account for that.

When the brand is turned on you can output 3 things.

  • The Brand Image
  • The Site Title
  • Brand Image + Site Title

Some logic for deciding what is output is needed at runtime so instead of echoing values to in the template file I added an action hook instead. The hook will trigger, check if we should output a brand, try to build the brand and then ultimately output it if we have a brand to use.

The Hook & Action

The hook is a standard action hook for WP.

/**
 * Fires the navbar-brand action hook.
 *
 * @since 1.2.0
 */
function best_reloaded_do_navbar_brand() {
	/**
	 * Used to output whatever featured content is desired in for the navbar brand.
	 */
	do_action( 'best_reloaded_do_navbar_brand' );
}

The action calls a function to perform the output logic and stores the value. It then tests if it has a value, sanitizes it against a list of accepted html tags and attributes then echoes it to the page.

/**
 * Echos the markup output by navbar branding function.
 *
 * @return void
 */
function best_reloaded_output_navbar_brand() {
	// try get the branding markup.
	$output = best_reloaded_navbar_branding();
	// if we have output to use then sanitize and echo it.
	if ( $output ) {
		$allowed_brand_tags = array(
			'span' => array(
				'class' => array(),
			),
			'img' => array(
				'id'	=> array(),
				'class'	=> array(),
				'src' => array(),
				'alt' => array(),
				'width' => array(),
				'height' => array(),
				'style' => array(),
			),
		);
		echo wp_kses( apply_filters( 'best_reloaded_filter_navbar_brand', best_reloaded_navbar_branding() ), $allowed_brand_tags );
	}
}
add_action( 'best_reloaded_do_navbar_brand', 'best_reloaded_output_navbar_brand' );

Function to Generate Navbar Brand Markup

The function that generates the markup also handles the logic of what is output and deals with the issue of long titles breaking things.

I added a character cap by default of 30 chars and another customizer option for an override to allow long titles if the site owner wants to.

$wp_customize->add_setting( 'allow_long_brand', array(
	'default' => 0,
	'sanitize_callback' => 'best_reloaded_sanitize_checkbox',
) );
$wp_customize->add_control( 'allow_long_brand', array(
	'label' => __( 'Very long titles break the default navbar layout, if you want to allow very long titles here then check this box. NOTE: You can also turn off the search form for more space.', 'best-reloaded' ),
	'section' => 'best_reloaded_navbar',
	'settings' => 'allow_long_brand',
	'type' => 'checkbox',
) );

The function that returns the markup looks like this:

/**
 * Builds out a .navbar-brand based on options set in the theme.
 *
 * @return string containing html markup for brand
 */
function best_reloaded_navbar_branding() {
	// initial value for the output is false.
	$brand_output = false;
	// check for image set in theme options theme options.
	$brand_image = get_theme_mod( 'brand_image', '' );
	// Did we get an image or is the brand text turned on?
	if ( $brand_image || get_theme_mod( 'display_brand_text', false ) ) {
		// since we have at least 1 of the items then start the output.
		$brand_output = '<span class="h1 navbar-brand mb-0">';
		if ( $brand_image ) {
			// we have an image.
			$brand_output .= '<img id="brand-img" class="d-inline-block align-top mr-2" src="' . esc_url( $brand_image ) . '" >';
		}
		if ( get_theme_mod( 'display_brand_text' ) ) {
			// text is toggled on, get site title.
			$site_title = get_bloginfo( 'name', 'display' );
			// very long site titles break the navbar so cap it at a generous 50 chars.
			if ( strlen( $site_title ) <= 50 || get_theme_mod( 'allow_long_brand', false ) ) {
				$brand_output .= esc_html( $site_title );
			}
		}
		$brand_output .= '</span>';
	}
	// this will return the markup if we have any or it will return false.
	return $brand_output;
}

Next Steps

Now that this works and I've tested it I will push the update to the .org repo and think about my next set of tweaks and changes.