How to Link to the Customizer

I recently needed to link to a custom panel in the Customizer and had to figure it out. Devin Price pointed me in the right direction, but I ended up looking in the WordPress core to see how they do it and this is how.

This a simple link to Customizer:

<a href="<?php echo esc_url( admin_url( 'customize.php' ) ); ?>">Link to Customizer</a>

Linking to Panels, Sections, and Controls in the Customizer

If you want to link to a specific panel in the Customizer, you’ll want to use this:

$query['autofocus[panel]'] = 'nav_menus';
 $panel_link = add_query_arg( $query, admin_url( 'customize.php' ) );
 ?><a href="<?php echo esc_url( $panel_link ); ?>">Link to Panel</a>

If you aren’t familiar with add_query_args, its worth reading up on it. I learned about it from Fränk Klein for how to properly add Google Fonts to WordPress. Pippin Williamson also has a good post about write-up about using add_query_args too. We’ll see later how you can use it to create more advanced links into the Customizer.

One warning: always specify the URL parameter and always escape the output. The core developers discussed this issue in April of 2015. If you don’t specify the URL and don’t escape the output of this function, then someone might be able to use it as a XSS attack vector. If you’ll notice, I’m including the esc_url() function in the output of each of the examples above and the URL parameter is specified too.

In the $query array, the key is tells the Customizer what to do, then the value tells it where we’re going. The this example: autofocus in a panel called “nav_menus”. You can change the value to the panel of your choice. Here’s a list of the default panels included in WordPress (as of version 4.5.2):

  • widgets = Widget panel
  • nav_menus = Menus panel

If you want to link to a section instead, you can change part of the key:

$query['autofocus[section]'] = 'title_tagline';
 $section_link = add_query_arg( $query, admin_url( 'customize.php' ) );
 ?><a href="<?php echo esc_url( $section_link ); ?>">Link to Section</a>

Here are the IDs for the default sections in WordPress:

  • static_front_page = Static Front Page section
  • title_tagline = Site Identity section

Some of the default themes like Twenty Sixteen add their own sections and any theme can add the following sections by declaring theme support for them:

  • background_image = Background Image section
  • header_image = Header Image section

The real magic is linking to the customizer and autofocusing on a specific field (aka control):

$query['autofocus[control]'] = 'blogname';
 $control_link = add_query_arg( $query, admin_url( 'customize.php' ) );
 ?><a href="<?php echo esc_url( $control_link ); ?>">Link to Control</a>

This links to the Site Title field in the Site Identity section. If you’re asking people to go to a specific place and make an edit, go ahead and link this way to make it easier.

After the Customizer

In addition to linking to somewhere within the Customizer, you can also specify where to go after you exit. Core uses the page you’re currently on by default. You can check the link to the Customizer from different places in the admin and see that return value changes.

$query['return'] = admin_url();
 $link_with_return = add_query_arg( $query, admin_url( 'customize.php' ) );

That will link to the Customizer and return you to the Dashboard.

FYI, you cannot specify any old URL to go to afterwards. For instance, this does not work:

$query['return'] = 'http://www.cnn.com';
 $link = add_query_arg( $query, admin_url( 'customize.php' ) );
 ?><a href="<?php echo esc_url( $link ); ?>">Go to CNN after</a>

The Customizer runs these return URLs through the function wp_validate_redirect(). By default, the only allowed URL is the home_url(). Additional URLs can be added using the allowed_redirect_hosts() filter. The wp_validate_redirect() function is used for many parts of WordPress, so I’d advise caution when adding URLs to this allowed list.

Now, we can build more specific URLs for the Customizer. This could be especially useful if you’re walking a site admin through performing several steps:

$query['autofocus[control]'] = 'blogname';
 $query['return'] = admin_url( 'post-new.php' );
 $link = add_query_arg( $query, admin_url( 'customize.php' ) );
 ?><a href="<?php echo esc_url( $link ); ?>">Set title, then write post</a>

This links into the Customizer, autofocuses on the Site Title field, then once you’re done, takes you to the new post page.

Set the Preview Page

If you have Customizer controls that only apply to specific pages, you can specify which page appears in the previewer:

$query['url'] = site_url( '/news' );
 $link = add_query_arg( $query, admin_url( 'customize.php' ) );
 ?><a href="<?php echo esc_url( $link ); ?>">News Page in Preview</a>

In this case, the Previewer will display the News page instead of the home page. I’m planning to do additional testing to see if this could be used to style a plugin-specific page.

All Together Now!

Those are all the parameters currently available for the Customizer. Now you can get really crazy (and specific) about your links to the Customizer. For example, go to the Menu Locations, show the About page in the previewer, then when you’re done, add a new page:

$query['autofocus[section]'] = 'menu_locations';
 $query['return'] = admin_url( 'post-new.php?post_type=page' );
 $query['url'] = site_url( '/about-us' );
 $link = add_query_arg( $query, admin_url( 'customize.php' ) );
 ?><a href="<?php echo esc_url( $link ); ?>">Craziness!</a>

To wrap up, there are three parameters you can use to link into the customizer: return, url, and autofocus. Use add_query_arg to build the URL and esc_url to display it. You can use the autofocus parameter to focus on a panel, a section, or a specific field. You can use the url parameter to display a specific page in the Previewer. You can use the return parameter to go to a particular place after going to the Customizer. Happy linking!

Here’s a gist of all the code, if you prefer that.

How to Change the Featured Image Labels

When creating a site for a client or creating a plugin, I’ve found its helpful to customize things as much as possible to the intended usage. This is especially important for client work since most clients have an internal language, or terminology they use for things. In the case of Featured Images, the site or plugin might be using the featured image differently than how one might use it on a news or blog post.

I’m currently writing a plugin with a custom post type and using the featured image as a headshot for an employee. While “featured image” may work fine, “headshot” is more specific and makes more sense in this context. I haven’t been able to find anything recent about how to change the labels on the existing Featured Image metabox. The most commonly referenced code only works some of the time. Specifically, when one removes a featured image, the label for the link changes back to referencing “featured image” instead of the customized label.

I dug through the core and found the post_type_labels_{$post_type} filter, which was added in version 3.5. This filter makes customizing the featured image labels super easy:

/**
* Changes strings referencing Featured Images for a post type
*
* In this example, the post type in the filter name is "employee"
* and the new reference in the labels is "headshot".
*
* @see https://developer.wordpress.org/reference/hooks/post_type_labels_post_type/
*
* @param object $labels Current post type labels
* @return object Modified post type labels
*/
function change_featured_image_labels( $labels ) {
$labels->featured_image = 'Headshot';
$labels->set_featured_image = 'Set headshot';
$labels->remove_featured_image = 'Remove headshot';
$labels->use_featured_image = 'Use as headshot';
return $labels;
} // change_featured_image_labels()
add_filter( 'post_type_labels_employee', 'change_featured_image_labels', 10, 1 );
view raw gistfile1.txt hosted with ❤ by GitHub

The labels are in an object and we then reset the values of each specific item to what we want. Then return the object.

Avoiding get_theme_mod Errors

If you’re getting an error related to get_theme_mod, its most likely from how you’re checking if the returned result is empty. Most developers will probably do the same thing I did: make a variable and assign it to the result of get_theme_mod. Then check if its empty using PHP’s empty() method. Sadly, this is where you’re getting an error. Instead, check if the result is equal to ”, or blank.

/**
* Do this
*/
if ( '' == get_theme_mod( 'something' ) ) { ... }
/**
* Not this
*/
if ( empty( get_theme_mod( 'something' ) ) ) { ... }

Using WordPress Plugin Boilerplate

This afternoon, I presented “Using WordPress Plugin Boilerplate” at WordCamp Dayton. My presentation covered the new 3.0 version of the boilerplate. I want to make everything easy to find, so here are the links from my presentation, including my sample plugin “Now Hiring”:

Presentation: http://slushman.com/presentations/using-wp-plugin-boilerplate

Sample plugin: https://github.com/slushman/now-hiring

WP Plugin Boilerplate: http://wppb.io

Generator: http://wppb.me

 

 

WordCamp Dayton 2015

I’ve been accepted to speak at WordCamp Dayton this year! I’ll be speaking on using WordPress Plugin Boilerplate. I’ve been using this for the past few months for all my plugins and found it be extremely useful and well done. I’ll be going over the basics of how it works, why you’d want to use it, and showing code for basic plugin things like widgets, metaboxes, post types, and plugin settings.

Look forward to seeing you there!

SimpleMap Shortcode Reference

I’ve been using a plugin called SimpleMap for clients recently. SimpleMap creates a custom post type for managing locations (think store locations) and displays a Google Map with those locations. It also uses Google Map’s radius search to help people find locations near them. Everything on the front-end is managed using a shortcode. Practically every setting in SimpleMap can overridden in the shortcode, which is really cool for us developers. Plus, they have tons of filters throughout, so any text or label can be customized for your usage. While the “Help” tab in the plugin settings shows you some of the options available with the shortcode, there are tons more and the documentation is practically non-existent. I’m putting this out there, at least for my own reference and I’ll keep adding to it as I learn more and/or have time to expand it.

The gist below explains how to use the shortcode and the more common attributes to use with it.

/**
* Basic shortcode
*/
[simplemap]
/**
* Hide the search result list
*
* @param bool true: hide the list
* false: show the list, this is the default
*/
[simplemap hide_list=true]
/**
* Hide the map
*
* @param bool true: hide the map
* false: show the map, this is the default
*/
[simplemap hide_map=true]
/**
* Hide the search form
*
* @param bool true: hide the search form
* false: show the search form, this is the default
*/
[simplemap hide_search=true]
/**
* Limits the search results list to locations from the specified category or categories.
*
* @param int A comma-separated list of category IDs.
* Do not put spaces between the IDs, just a comma
*/
[simplemap sm_category=8] // one category
[simplemap sm_category=8,9,10] // mutiple categories
/**
* Displays a checkbox for each category as part of the search form.
* Allows the site visitor to filter the search results by category.
*
* @param bool true: show the category filters
* false: hide the category filters, this is the default
*/
[simplemap show_sm_category_filter=true]
/**
* Specify which fields are included in the search form
*
* The search form is built in a table.
*
* Put "||" between each field.
*
* Here are the options for requesting a field:
* "labelbr_": puts the label with a break return tag afterwards
* "labelsp_": puts the label with a space afterwards
* "labeltd_": puts the label and field in separate table cells.
* "" (blank): puts no label, just the field itself.
*
* The fields that you can include are:
* street
* city
* state
* zip
* country
* distance: the select menu for the search radius
* empty: puts an empty table cell (useful for formatting the search form properly)
* submit: the submit button. If you don't include this, a hidden button will be added for you.searc
* taxonomy: include the filters for a specific taxonomy. There are four defaults:
* "sm-category"
* "sm-tags"
* "sm-day"
* "sm-time"
*/
[simplemap search_fields=labelbr_street||labelbr_city||labelbr_state||labelbr_zip||labelbr_country||labelbr_distance]
/**
* Changes the name of the search form.
* Default is "Find Locations Near:"
*/
[simplemap search_title="Search this"]
/**
* Changes the number of default table cells.
*
* Default value is 3.
*
* NOTE: This one doesn't seem to work for me.
*/
[simplemap search_form_cols=4]
/**
* Changes the field format for the taxonomies.
*
* Accepts "checkboxes" or "select". Default is "checkboxes".
*/
[simplemap taxonomy_field_type="select"]
/**
* Sets the default latitude. Default is "44.968684".
* Ideally, use with default_lng.
*/
[simplemap default_lat="39.840315"]
/**
* Sets the default longitude. Default is "-93.215561".
* Ideally, use with default_lat.
*/
[simplemap default_lng="-88.954800"]
/**
* Sets the map width. Default is 100%.
*/
[simplemap map_width="60%"]
/**
* Sets the map height. Default is 350px.
*/
[simplemap map_height="300px"]
/**
* Sets the measurement units.
* Accepts "km" for kilometers or "mi" for miles. Default is "mi".
*/
[simplemap units="mi"]
/**
* Sets the radius for the search. Default is 10.
* Use an integer and no unit.
*/
[simplemap radius=12]
/**
* Limits the quantity of how results are returned.
* Use an integer. Default is 20.
*/
[simplemap limit=12]
/**
* Quantity of results to autoload.
* Use an integer. Default is "all".
*/
[simplemap autoload=12]
/**
* Like most shortcodes, to add multiple attributes, put a space between them
*/
[simplemap hide_list=true hide_map=true hide_search=true sm_category=8,9,10]
view raw simplemap-shortcode hosted with ❤ by GitHub

ArtistDataPress updated to v0.72!

Its been a long year. I’ve been trying to get back to ArtistDataPress and finally had a break in the schedule. So I resolved the larger issues people have brought up on the WordPress support forums. First off, if anyone feels I abandoned the plugin, please forgive me. Paid gigs come before free plugins. However, my schedule has freed up quite a bit now, so I should be able to make regular updates.

Version 0.72 brings the following changes:

  • BUG FIX: Resolved the “undefined function get_example_data()” error
  • BUG FIX: Removed the transient caching – causing too many errors and complaints
  • Removed “Custom” template option; added custom template explanation instead.
  • Changed Twitter account info
  • Added plugin icon to assets

ArtistDataPress is also compatible with the current version of WordPress, so there’s also that. Happy updating!

Inline SVG the Sane Way

Like everyone else these days, I’ve totally jumped onboard the SVG train. They’re scalable and easy to use. If you use inline SVG, they are style-able and reduce server requests, which can help your SEO.

Sadly, inline SVG also borks up your code. There’s nothing more disappointing than combing through neatly organized code thats completely marred by a giant block of inline SVG gibberish in the middle of it.

Recently, I discovered a simple way to have my cake and eat it too (mmm, cake!). Check this out:

https://gist.github.com/slushman/4c1295adc18a98678b05

Simple, right? The function has one parameter that determines which SVG is returned. While this one function may look out of control (although in my editor, each SVG is on one looooooong line, so it still remains neat and tidy), the SVGs and their craziness are contained to one place in your code.

This still gives you all the advantages of inline SVG, without borking up your nicely indented, organized code. In my next post, I’ll show how combining this with some other simple functions can give you inline SVGs in menu items!

As a footnote, the $output being initiated and returned outside the switch means there’s a fallback. If the requested icon isn’t in the switch, nothing is returned. You could do that with a “default” statement within the switch, but I’ve also found being able to append the $output variable is more dummy-proof that other methods.

Child themes for WooThemes

I’ve been using child themes for several years now, but I’d never used one with a theme by WooThemes. Most of my clients needed simple customizing, so I used WooTheme’s backend options to make the changes.

A recent client needed a bit more than I could do through their backend though. I started off making the customization directly in the theme, which is never a good idea. Catching my mistake, I decided to throw my changes in a child theme and work from there. However, reloading with the child theme activated, everything was borked. Looking at the source, I noticed how the stylesheets were loading because my child theme stylesheet should be last in the cascade, therefore overriding all other styles. There were two files from the parent theme loading after mine. I tried several solutions, hoping a newer one using wp_enqueue_style to load the parent stylesheet instead of @import would work, but that didn’t affect the other two stylesheets.

Thankfully (before pulling my hair out), I found this page in the WooThemes support docs. What they don’t spell out on the page (not helpful WooThemes!) is that you don’t actually make CSS changes in the child theme style.css. You make a second CSS file called custom.css and make your changes there. The custom.css file is loaded next-to-last, only followed by styles put into their custom CSS field on the backend forms, and therefore overrides all the other stylesheets.

The Trail of the Saved Option

WARNING: In this article, I ask you to alter core WordPress files while troubleshooting. Please remember to undo any changes you make so WordPress continues working correctly. I do NOT advise making these changes on a public-facing website.

Recently, I’ve been troubleshooting why my options for a new plugin aren’t saving correctly. So far, I’ve discovered, or should I say “re-discovered” some interesting items that other devs might find useful. For starters, when saving your plugin options, ideally, you should combine them into an array before saving them. This saves the database from having tons of random options entries just for your plugin. There are exceptions, like if you need options separated from each other, but that’s another post.

When saving data into an array, you’ll need to make the name of the field something like: name_of_the_registered_setting[name_of_the_form_field]. Notice those brackets? That helps the options save process place this field’s data into the correct place within the array.

While troubleshooting options, I began to trace where the data goes and how it gets processed by WordPress, then your own plugin, so I’m going to lay it out here.

First, when you create your settings page, your form tag has these attributes:

method="post" action="options.php"

This means, the form will use the Post method to send the data to the options.php file.

The action check in wp-admin > options.php.
The action check and the added wp_die() in wp-admin > options.php.

Within the options.php file (version 3.9.1 as of this writing), line 133 checks if the action was “update”. There’s lots of code before this mostly dealing with making sure WordPress will be saving to the correct set of options, which changes if you’re saving options for Multi-site vs a single site installation.

If one checks the $_POST PHP superglobal on line 134, you’ll see the raw posted information you typed into the fields. To do this, one would use this bit of code within the root > wp-admin > options.php file:

wp_die( serialize( $_POST ) );

wp_die() stops WordPress right where it is and the first argument allows you to enter an optional message. In this case, we’re serializing the $_POST array so we can read it and using that as our message. From my test data, here’s what I see:

a:6:{s:11:"option_page";s:19:"slushman_wpmi_first";s:6:"action";s:6:"update";s:8:"_wpnonce";s:10:"4d37626dc4";s:16:"_wp_http_referer";s:77:"/showcase/wp-admin/options-general.php?page=wp-maximage&settings-updated=true";s:19:"slushman_wpmi_first";a:2:{s:15:"wpmi_test_field";s:9:"save this";s:17:"wpmi_test_field_2";s:0:"";}s:6:"submit";s:12:"Save Changes";}

In my test settings page, I have a field called  ‘slushman_wpmi_first’ and I entered ‘save this’ and clicked the Save Changes button.

After checking if the action is “update”, options.php then checks the admin referrer, which in our test case is ‘/showcase/wp-admin/options-general.php?page=wp-maximage&settings-updated=true‘. You can comment out and copy the wp_die() statement then paste it  just after this admin referrer check, but it should be the same stuff.

After this, it checks the whitelisted options. Most of the code above the update check in line 133 is about compiling that list of whitelisted options. If you used the register_setting() function for your plugin options, your options should be automatically added to the whitelisted options. It then checks your capabilities for multisite installations or whitelists your settings. If you’re not having capabilities issues, copy/pasting the wp_die() statement after line 153 really shouldn’t show anything different.

The next batch of code has to do with checking the time and date settings from the WordPress general page, so skip that block of code.

Line 168 checks if the variable $options is valid and has data. If all the whitelisting code passed, this variable should have been assigned in line 151. You can move the wp_die() statement to line 169 and change $_POST to $options to see your whitelisted options. Here’s what my test settings page shows:

a:1:{i:0;s:19:"slushman_wpmi_first";}
The option value processing block in wp-admin > options.php.
The option value processing block in wp-admin > options.php.

This is the first bit where the data starts to be processed and possibly changed. After checking the $options variable, it breaks each option apart and processed them separately (in PHP, that’s a foreach loop). If you copy/paste the wp_die() within the foreach loop and change $options to $option, you should see the first one:

s:19:"slushman_wpmi_first";

First, it checks each option to see if its unregistered, which was assigned to the $unregistered variable back in lines 135 or 138. Since you’ve used the register_setting() function, this should be false because your setting is registered, otherwise you’d see a WordPress error once the processing reaches this point.

The option is trimmed to remove extra white space and the $value variable set to NULL in lines 172 and 173.

Line 175 checks the $_POST superglobal for the option. If its not in $_POST there’s no reason to attempt to update it, so it would skip any further processing and go back to the settings page.

Otherwise in line 175, it assigns the $value variable the value of the option from $_POST. You can check this value for yourself by using this on line 176:

wp_die( serialize( $_POST['name_of_your_option'] ) );

In my test page, I get:

a:2:{s:15:"wpmi_test_field";s:9:"save this";s:17:"wpmi_test_field_2";s:0:"";}

It sees that both fields were submitted for the option ‘slushman_wpmi_first’. In the next line, it check  the data type of the $value and if its not an array, it trims it to remove white space, the sends it off to wp_unslash() to remove any slashes.

After this, the $option and the $value are sent to update_option(). One might think the processing ends there, but no. update_option() is located in the wp-includes/option.php file around line 231. Pasting this at the top of the update_option() function:

wp_die( serialize( $value ) );

Yields this result for my test page:

a:2:{s:15:"wpmi_test_field";s:0:"save this";s:17:"wpmi_test_field_2";s:0:"";}

First, it trims $option again. If $option is empty, it returns FALSE. Then, it sends $option off to wp_protect_special_option(), which is around line 136 in this same file. It prevents you from updating two options used by WordPress (since WordPress 2.2): alloptions and notoptions. These are used by WordPress in conjunction with transients to autoload options. I’d welcome any additional comments about their roles.

Next, if the $value is an object, it creates a clone of the object and works that with the clone rather than the original.

It then sends $option and $value off to sanitize_option(), which is in the wp-includes/formatting.php file. Looking at the code for sanitize_option(), most of it consists of a switch statement that looks at the $option parameter and acts accordingly. It already includes processing instructions for all the standard WordPress options, but after the switch statement, it includes a filter, ‘sanitize_option_{$option}‘. The sanitize_option_ filter is created and defined in register_setting() (yet another reason to use the Settings API). When you define your sanitization callback function, it adds that function to the sanitize_option_ filter.

At this point, your option’s $value is filtered by whatever you put into your sanitization callback function. After it finishes processing in your sanitization callback, its then returned back to the update_option function. If you add this to the top of your sanitization function:

wp_die( serialize( $input ) );

You can now see exactly what’s being passed into your sanitization callback function. In my case:

a:2:{s:15:"wpmi_test_field";s:9:"save this";s:17:"wpmi_test_field_2";s:0:"";}

After the new value is sanitized in your callback function, the current “old” value is recovered from the database using get_option().

The new value is now passed through two more optional filters, ‘pre_update_option_’ . $option‘, and ‘pre_update_option‘.

Now the new and old values are compared. If they aren’t different, there’s no reason to update the value, so it exits. If the old value doesn’t exist, The new value is sent off to add_option() to be created, the result is returned to this function as $value.

The $value is now sent off to maybe_serialize(), which is in the wp-includes/functions.php file, to possibly serialize it, or in other words, prep it to be stored in the database safely.

Updating the database in option.php
Updating the database in option.php

After this, an action is added for ‘update_option‘. One might get this mixed up with the function update_option(). The action happens right before the option is actually updated, while the function does all the checks and processing to make the updated data safe.

Finally, the updated value is written to the database using the $wpdb object and its update() method. The update() method does not alter the data.

After saving the data to the database, it refreshes the options caches and added to the auto-loading process.

There are two final actions, which allow for processing the value after its been saved to the database: ‘update_option_{$option}‘, which happens after a specific value has been updated, and ‘updated_option’, which happens after the value an option has been updated.

Lastly, if all has gone to plan, update_option() return TRUE.

Back in options.php, if errors occurred at some point in this process, they are handled and displayed appropriately.

Then, finally, the user is sent back to the plugin’s settings page.

Obviously, there are lots of places for things to go badly when saving your plugin settings. Hopefully, this breakdown can help narrow down where things happen and you can use the wp_die() function to discover where issues are happening. In all cases, I’d highly recommend using the Settings API as much as possible!