How to enqueue scripts and styles effectively

When using custom JavaScript and CSS files, you usually use the wp_enqueue_script() and wp_enqueue_style() functions. If not, please start doing so. Using them is really easy, but when it comes to effective usage, there are some little things to keep in mind.

Simple usage

The easiest way in using them is by only defining a “handle” and the URL to the file you want to enqueue:

wp_enqueue_script(
	'chart-js',
	'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js'
);

This example would load a JavaScript files hosted on an external server into your page. This can be all fine, but for many things you would rather want to have them locally, so you avoid issues when the CDN is down or with privacy.

Using a local file – bad example

To load a file in a plugin or theme, it would be enough to reference it with a relative path like this:

wp_enqueue_script(
	'chart-js',
	'/wp-content/plugins/plugin-name/assets/chart.min.js'
);

This would enqueue it in the source code in the following way:

<script type='text/javascript' src='https://theme-tests.docker.test/wp-content/plugins/plugin-name/assets/chart.min.js?ver=5.9'></script>

While this would work, there are some issues with this simple way.

1. Always use dynamic paths

In the example above, we use a relative path to the wp-content/plugins folder. This might seem OK for most of you, but as the wp-content folder can have a different name (some security plugins do that – which is usually not a great idea), you should always use helper functions to get the relative path to that folder. If you enqueue a file in a plugin or theme, there are different functions you can use. These are the ones, you would usually use:

2. Always provide a version to the file

As you can see in the example above, WordPress will add a version number. If you don’t define one yourself, it would append the current version number of WordPress, which today would be 5.9, to the end of the URL. This version number is meant to help you with caching. As your files will probably not (only) change when WordPress gets updated, you should use a different version string. This could be one of the following:

  • A static version number matching the version of the plugin
  • A static modification date of the plugin
  • A static modification date of the file that’s enqueued
  • A dynamic modification date of the file that’s enqueued

In the past, I have used the first or third option. But then you have to update all these string (for all files) manually and it’s easy to forget one, which will result in the browser loading old files from it’s cache. Nowadays I usually use the latest apporach. This also has the benefit that every time you save any file while still developing it, you don’t have to take care of the cache, your browser will always load the latest file. An example of this approach – combined with dynamic paths – could look like this:

wp_enqueue_script(
	'chart-js',
	plugins_url( 'assets/chart.min.js', __FILE__ ),
	array(),
	filemtime( plugin_dir_path( __FILE__ ) . 'assets/chart.min.js' )
);

This would then result in the file being included like this:

<script type='text/javascript' src='https://theme-tests.docker.test/wp-content/plugins/plugin-name/assets/chart.min.js?ver=1644792351' id='chart-js-js'></script>

As you can see, we now have a UNIX timestamp appended to the end of the URL. As this changes with every safe of the file, the browser will always load the most recent file version.

Conclusion

This blog post was covering a very basic concept, but I have seen so many plugins and themes out there still enqueuing files not in the best way. With these little tricks, you can avoid a lot of stress finding issues with your code, when in the end, it was just an old file being loaded from your browser’s cache.

Dynamic form specific hooks for GravityForms

I really like using GravityForms to create highly dynamic forms. The form builder offer a wide range of possibilities. But sometimes you need to use some code to hook into parts of the form processing. In this blog post I want to show you how to make this work even across multiple installations of a form.

Using a hook for all instances

Let’s take the gform_pre_submission as an example. If you want to hook into it, you would do as with any other hook in WordPress:

function my_pre_submission( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission', 'my_pre_submission' );

This code snippet would update every form field with ID 1 on every form. This is probably not what you want to do.

Using a hook for only one form

When changing a value, you usually do that for a specific form. This can be done by adding the form ID to the end of the hook:

function my_pre_submission( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission_1', 'my_pre_submission' );

Now, the value only get updated when the form with ID 1 is submitted. That could be a good solution, until you export/import your forms.

Handling different form IDs

Let’s say you have created a form for one project, and now you want to use it in another project. You can imply use the export/import of forms to copy over all settings of a form (without the entries). But if your other project already has forms, then your imported form might have a different ID on this new project. The same could happen, if you copy the form from a development environment to a live site, or if you are not the only one creating new forms.

In this case, you would have to find out your new form ID and change the values for the hooks. But what if you have these hooks in version control, and you can’t change it to a new static value?

Using a constant for the form ID

In a project with this issue, I’ve decided to use constants to store the form IDs. All hooks now look something like this:

function my_pre_submission_contact( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission_' . PREFIX_CONTACT_FORM_ID, 'my_pre_submission_contact' );

function my_pre_submission_form_event( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission_' . PREFIX_EVENT_FORM_ID, 'my_pre_submission_form_event' );

This is not the actual code, but I hope you get the idea. In using a constant per form (using the form name, not the ID), you can dynamically set the ID of the given form depending on the system the form is uses.

Set constant defaults in the plugin (or theme)

I usually store such code in a plugin. In the main file of this plugin, I would set the values for the constants on the system I’ve first used/created these forms:

if ( ! defined( 'PREFIX_CONTACT_FORM_ID' ) ) {
	define( 'PREFIX_CONTACT_FORM_ID', 1 );
}
if ( ! defined( 'PREFIX_EVENT_FORM_ID' ) ) {
	define( 'PREFIX_EVENT_FORM_ID', 2 );
}

By adding the ! defined() check, this would only set the constant, if it was not set earlier in another file. So on the second project, where the IDs are different, you would just define them in your wp-config.php for example:

define( 'PREFIX_CONTACT_FORM_ID', 4 );
define( 'PREFIX_EVENT_FORM_ID', 5 );

Conclusion

While GravityForm really offer a lot of hooks and using them is really flexible, it can be an issue when using a static form ID calling a hook. With a constant per dynamic ID value, you can use the same form and the same custom hooks in different projects.

The same would apply to all the other hooks from GravityForms where you can append an ID to only target a specific form, field, etc.

Sorting category pages alphabetically

In a Facebook group someone wanted to know how to order the posts on a category page alphabetically. In this blog post want to show, how you can easily sort posts on a category (or any other) archive page.

Sorting post on all category pages

Even though I don’t know why someone would like to order posts by name (archives of a different post type would probably make more sense), this is the code to order them:

function aa_sort_all_archives( $query ) {
	// Only sort main query.
	if ( ! $query->is_main_query() ) {
		return;
	}
	// Only sort category archives.
	if ( ! is_category() ) {
		return;
	}

	$query->set( 'order', 'ASC' );
	$query->set( 'orderby', 'post_title' );
}

add_action( 'pre_get_posts', 'aa_sort_all_archives' );

Anytime you want to alter the query, you would preferably use a callback function to the pre_get_posts hook. In this callback you would first check, if the main query is run. Then you check for what ever condition makes sense for your use case. If this condition is not met, exit the function. If all check succeeded, alter the query. So in line 7 we check, if we are on a category archive. If so, we change the order of posts to “ascending” (line 11) and the field by which we want to sort (line 12) to the post_title field. That’s all.

Order only posts in a specific category

As for some post types it might be unlikely, that you want to sort posts of all categories, you can also pass the category slug (or it’s name, ID) to the is_category() function. In this example, I’m using a dedicated category with the slug “alphabetical” and sort only posts on this archive page:

function aa_sort_alphabetical_archives( $query ) {
	// Only sort main query.
	if ( ! $query->is_main_query() ) {
		return;
	}
	// Only sort category archives.
	if ( ! is_category( 'alphabetical' ) ) {
		return;
	}

	$query->set( 'order', 'ASC' );
	$query->set( 'orderby', 'post_title' );
}

add_action( 'pre_get_posts', 'aa_sort_alphabetical_archives' );

In the same way, you can have check for many other archive pages using different “Conditional Tags“. In the CODEX you will also find a page about “Alphabetizing Posts” but for category pages it tells you to use a secondary query. Please never do this! And also never show all posts! Just don’t do, what the page tells you 😉

Conclusion

Sorting posts (or other post types) on an archive page is best done by using the pre_get_posts hook. When you first use this hook, it might not work in the way you want it to work, but it’s worth learning how to use it correctly.

You can find the code of this blog post as a working plugin in a GIST where you can also download it as a ZIP and install it to your site.

2022: An eventful year

I usually don’t post “New Years” blog posts – I usually only have a post on the blog’s birthday – but as it’s still the last calendar week of 2021, and I’m unsure if I have to post something for #project26, I will tell you a bit about this new year from my perspective.

#project26 continues

This is either my last blog post of last year or the first one of this year 😉 Even though there are some things coming up this year, I will try to continue posting bi-weekly. I will also try to comment on other blogs, but I really struggle even reading lots of blog posts.

WordPress releases and Full Site Editing (FSE)

This year might be the first one with four major releases of WordPress. With the upcoming version 5.9 later this month, we will get a new default theme: TwentyTwentyTwo! So this year I’m finally planning to create my first FSE theme, either in a client project or by rewriting my currently used theme.

Retiring a plugin

With the release of 5.9 there will also be a new feature: the language switcher on the login screen. With this new feature, one of my first two plugins can finally be retired.

WordCamp Europe 2022 … in Porto!

The main “event” for me will be WordCamp Europe 2022, which will finally be held in Porto! Our organizing team is working hard to bring the community together. But as corona/covid will still be around, we will make sure, to also make it as safe for everyone as possible. After this edition, I will also retire from organizing WCEU – at least for some time.

Conclusion

The last two years have been tough for many of us. Finally, 2022 looks like the year we can enjoy some of the things we love! I can’t wait to see many of you either in Porto or on another WordCamp this year! With FSE being almost there, I also think that I will have some great topics to write about.

Using Block Patterns for new entries of a Custom Post Type

When developing projects, I often use Custom Post Types (CPT). In a new project, the content of the entries were build completely with core blocks. In the past, I typically used custom meta fields and a fixed page template. But as creating such a complex content of a CPT is quite a lot of work and error-prone, I wanted to use a template for new entries.

Creating a block pattern

The easiest way to create a pattern for a post type is by creating the content first using the block editor and then exporting the HTML markup using the “Copy all content” option:

Now you can use this markup to register a block pattern with the default content of new entries:

function cpt_register_block_pattern() {
	register_block_pattern(
		'my-cpt/my-cpt-pattern',
		array(
			'title'       => __( 'My CPT pattern', 'my-cpt' ),
			'description' => _x( 'The custom template for my_cpt', 'Block pattern description', 'my-cpt' ),
			'content'     => '
<!-- wp:paragraph {"placeholder":"Custom Post Type ..."} -->
<p></p>
<!-- /wp:paragraph -->

<!-- wp:columns -->
<div class="wp-block-columns"><!-- wp:column -->
<div class="wp-block-column"><!-- wp:image -->
<figure class="wp-block-image"><img alt=""/></figure>
<!-- /wp:image --></div>
<!-- /wp:column -->

<!-- wp:column -->
<div class="wp-block-column"><!-- wp:heading {"placeholder":"Name ..."} -->
<h2></h2>
<!-- /wp:heading -->

<!-- wp:paragraph {"placeholder":"Phone ..."} -->
<p></p>
<!-- /wp:paragraph --></div>
<!-- /wp:column --></div>
<!-- /wp:columns -->',
		)
	);
}
add_action( 'init', 'cpt_register_block_pattern' );

Creating a template for new entries

When registering a new CPT, you can can also create default content by passing a template argument with a multidimensional array defining the template:

function cpt_register_post_type() {
	register_post_type(
		'my_cpt',
		array(
			'label'                 => __( 'Custom Post Type', 'my-cpt' ),
			'supports'              => array( 'title', 'editor' ),
			'public'                => true,
			'show_ui'               => true,
			// ... other settings
			'has_archive'           => true,
			'show_in_rest'          => true,
			'template'              => array(
				array(
					'core/paragraph',
					array(
						'placeholder' => __( 'Custom Post Type ...', 'my-cpt' ),
					),
				),
				array(
					'core/columns',
					array(),
					array(
						array(
							'core/column',
							array(),
							array(
								array(
									'core/image',
								),
							),
						),
						array(
							'core/column',
							array(),
							array(
								array(
									'core/heading',
									array(
										'level'       => 2,
										'placeholder' => __( 'Name ...', 'my-cpt' ),
									),
								),
								array(
									'core/paragraph',
									array(
										'placeholder' => __( 'Phone ...', 'my-cpt' ),
									),
								),
							),
						),
					),
				),
			),
		)
	);
}
add_action( 'init', 'cpt_register_post_type' );

As you can see in this little example, the array can get very complex, unreadable and hard to maintain. You would also have to convert the Block Pattern HTML to this PHP array format. So any change to the pattern would require you to do the work twice, trying not to make any mistake.

Combining Block Patterns and Custom Post Types templates

As the solution with the PHP array was not really something I wanted to use (the actual template was a lot more complex than the one shown in the example), I first tried the following:

// ...
'template'              => array(
	array(
		'my-cpt/my-cpt-pattern',
	),
),
// ...

I thought, that maybe the template can also use Block Patterns in the array and not only blocks. But this didn’t work. When searching for a solution, I found multiple issues on GitHub. There I found the core/pattern block, which makes the task very easy:

'template'              => array(
	array(
		'core/pattern',
		array(
			'slug' => 'my-cpt/my-cpt-pattern',
		),
	),
),

Unfortunately, the core/pattern block is not available in WordPress 5.8, but you can already use it with the Gutenberg plugin installed and activated. I really hope that it’s going to be available with WordPress 5.9 expected next month.

Conclusion

Both Block Patterns and templates for new Custom Post Type entries are really helpful. Combining them with the new core/pattern block can really increase usability a lot.

Debugging PHP functions in WordPress

When developing a plugin or theme, you sometimes want to debug one specific function. This function might be used in a larger context, which makes debugging quite difficult and/or slow. Let’s take the example of a function used in a shortcode (or server side rendered block) for example:

function my_shortcode_callback( $atts ) {
	$atts = shortcode_atts( array(
		'post_id' => '',
		// ...
	), $atts, 'my_shortcode' );

	$result = my_function_to_debug( $atts );

	return sprintf(
		__( 'Posted on %1$s by %2$s', 'my-textdomain' ),
		date_i18n( get_option( 'date_format' ), $result['date'] ),
		get_the_author_meta( 'display_name', $result['author'] )
	);
}
add_shortcode( 'my_shortcode', 'my_shortcode_callback' );

In this callback, we now call the function we want to debug. This function can be simple or very complex. Let’s take this one as an example:

function my_function_to_debug( $atts ) {
	$post = get_post( (int) $atts['post_id'] );
	// Maybe do something with the post and create some result
	// ...

	// Dummy result: select two values from the post
	$result = [
		'date'   => $post->post_date,
		'author' => $post->post_author,
	];

	return $result;
}

If you do this in a normal way, you would have to create a page/post and add the shortcode, so the callback and the function to debug get called. Let’s assume you want to debug the shortcode with a lot of different arguments. Then you have to insert multiple shortcodes on one page/post or even create multiple posts/pages.

Debug using the WP-CLI

One of the methods I’m using to debug such a function is using the WP-CLI. Here you can run code “with a WordPress environment” using the wp shell command. This is how this may look like:

$ wp shell
wp> my_function_to_debug( [ 'post_id' => 1 ] );
=> array(2) {
  ["date"]=>
  string(19) "2021-09-26 21:57:32"
  ["author"]=>
  string(1) "1"
}

After starting the shell, we just call the function and pass the arguments we want to test the function with. As the function returns an array, we get the result with a var_dump visualization.

In the same way we could also debug the shortcode callback, or any other function from any plugin/theme or WordPress core:

$ wp shell
wp> my_shortcode_callback( [ 'post_id' => 1 ] );
=> string(34) "Posted on 5 December 2021 by wapuu"

The benefit of this debugging technique is that it will not have to render a (frontend) page/post. It will “only” load the WordPress environment and then execute the function. This is a done lot faster and easier. You also don’t have to create a page using this shortcode.

Debug the function using an AJAX callback

The only issue with this approach can be, that debugging a function using XDEBUG is not (easily) possible, as XDEBUG usually only works in combination with an HTTP request. You could run such a request on the shell using curl, but then you also have to create a page/post with the shortcode and the arguments. Instead, you can “misuse” an AJAX callback function to execute the function in an HTTP request:

function my_ajax_callback() {
	$result = my_function_to_debug( [ 'post_id' => 1 ] );
	var_dump( $result );
}
add_action( 'wp_ajax_my_ajax_action', 'my_ajax_callback' );

Now you run an HTTP request to the URL /wp-admin/admin-ajax.php?action=my_ajax_action (either in a browser or the terminal) and you will get the same var_dump result. This time tough, you can set breakpoints for your debugging with XDEBUG.

Conclusion

Debugging a function does not always require a huge setup of pages/posts to call functions you want to test. With the help of the amazing WP-CLI or the use of an AJAX callback, this can be done a lot easier and faster.

Video documentation of a WordPress installation in the backend

While working on a client’s website this week, I found one very nice thing, I want to share with all of you. On the dashboard I found a widget with a YouTube video of a recorded training session.

Adding a video to the dashboard

The solution used on this client website was done with a custom dashboard widget, and the URL to the video could be set on a large settings page the agency was using to define various options for different features of the WordPress installation.

I thought that this video was really an amazing idea, and my first intention was to implement something like this myself. But “unfortunately”, there is usually a plugin for such a thing implemented by someone else. So while I could not code another plugin, I found some potential plugin. The most basic one is Video Dashboard by Brian Johnson. Here you can add up to 50 videos from YouTube or Vimeo and also control the minimum role someone needs to have to see the videos. A similar plugin is Videos on Admin Dashboard. Here you can only add up to 2 YouTube or Vimeo videos and also control the role. Interestingly, the plugin seems to use the same option names as the other plugin, because if you set video URLs in the other plugin, they will also be used in this one.

Conclusion

Adding a video to the admin dashboard is really a very clever and useful way to help people getting a documentation of a WordPress installation. Using a platform like YouTube or Vimeo, you probably want to make sure that those (individual) videos are not listed or limited to a specific domain (e.g. with Vimeo Pro).

So, how do you offer documentation to WordPress installations? Do you also use (hosted) videos, another type of WordPress documentation plugin or an external documentation tool?

Overwriting the “Register” link

In a previous blog post, I wrote about how I used Gravity Forms to register users. This was done on a static page and not on the default WordPress registration page. When you want users to register using a different form, you should link to this page on many different places. In this blog post I want to show you how you can link to your own registration page from the login page.

The WordPress login page

In the “General Settings” you can activate the “Membership” option “Anyone can register” so that anyone can find a link on the login page that links to the default registration page:

The WordPress login page

If you just want to overwrite the URL of that link, you can use a filter. This get passed the HTML code of the link, which you can overwrite like this:

function custom_register_link( $link ) {
	return sprintf(
		'<a href="%s">%s</a>',
		esc_url( '/register/' ),
		esc_html__( 'Register' )
	);
}
add_filter( 'register', 'custom_register_link' );

This will set the link to the static URL /register/ using the default link text.

This approach only work then you have activated the registrations in the settings. But this would also make the default registration form available. If you haven’t activated the registration, you can still use Gravity Forms (or probably other plugins) to register users. But as the “Register” link is completely removes, you cannot simply overwrite its URL. In this case, we can also place a link on a different place using the following code:

function custom_login_site_html_link( $home_link ) {
	$register_link = sprintf(
		'<a href="%s">%s</a>',
		esc_url( '/register/' ),
		esc_html__( 'Register' )
	);

	return sprintf(
		'%s<br /><br />%s',
		$register_link,
		$home_link
	);
}
add_filter( 'login_site_html_link', 'custom_login_site_html_link' );

This filter is usually used to hook into the “← Go to <sitename>” link. This is the only place we can insert a custom link into. The result would look like this:

The WordPress login page with a custom “Register” link

This link might even be a bit more visible than the link on the default position, just before the “Lost your password?” link.

Conclusion

Allowing users to register themselves with your own registration form can really be beneficial. If you use a custom form, then make sure to always link users to this form.

Fixing the “Illegal mix of collations” WordPress database error

When updating projects I also check the log files. In a project I maintained, I recently found the following error in the log files I have never seen before:

WordPress database error Illegal mix of collations (utf8_general_ci,IMPLICIT) and (utf8mb4_unicode_520_ci,COERCIBLE) for operation 'like' ...

As WordPress is not very strict with collations, your own WordPress website might also use different collations on different tables. In this project, there were six collations!

Fixing the collations

I order to eliminate this error, we have to convert all tables to use compatible collactions. It’s usually best to use only one collation for all of them. With WordPress 4.2.0 the collation utf8mb4 became the new standard. All database tables created after this update will be created with one of these collation variants. So for our single new collation, we use one of them.

Backup your database

STOP! Before we do anything with our database, we should create a backup. Changing the collation should not be an issue, but in any case it’s always best to create a backup. Use your favorite tool to create the backup. I usually use the WP-CLI for that:

wp db export

Now we can safely do the changes and roll back if things go wrong.

Changing the collation of one table

When changing anything for a database or table, we usually use the ALTER statement which we can also use here as followed:

ALTER DATABASE wp_project_name CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;

This first statement will set the collation of the whole database, so when you create a new table using a database management tool, this collation will be used. But it will not change it for any of the tables. To do that, we have to alter the tables as well like this:

ALTER TABLE wp_project_name.wp_posts CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;

This one statement would alter the wp_posts. Repeat the same for all other tables. Alternatively you can use the options provided by your database tool to change the collations.

Updating databases with lots of tables

The project I got this error was a multisite installation with 12 sites and 344 tables! That’s not fun updating all of these manually. Fortunately I found a nice little trick in a which will generate all the ALTER TABLE statements for all your tables:

SELECT CONCAT("ALTER TABLE ",TABLE_SCHEMA,".",TABLE_NAME," CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;") 
FROM information_schema.TABLES
WHERE TABLE_SCHEMA="wp_project_name";

Just set your database name in the last line and run it. Then copy all the “generated” queries form the result and run them. Your might run into errors like this:

Invalid default value for 'comment_date'

Then just set the sql_mode temporarily by adding this statement before all the ALTER TABLE statements:

SET sql_mode = '';

Once all statements have finished successfully all tables should now have the same collation. In this example I’ve used utf8mb4_unicode_520_ci but on your server it might be a different variant.

Updating collations using a plugin

While writing this blog post I stumbled upon the Database Collation Fix plugin which can also do this task for you, in case you cannot use a database management tool or you don’t feel like running SQL statements. The only “downside” is, that the plugin will only convert tables to utf8mb4_unicode_ci (or three alternative collations) and not to the newer utf8mb4_unicode_520_ci collation. But this should generally not be an issue.

Conclusion

Even after working with WordPress for so many years there are still some errors I have never seen. In this case it was not an error specific for WordPress, but the way WordPress is handling collations can lead to this issue. Especially if you work with WordPress installations that have been around for a while (probably installed before version 4.2.0). So if you find the same error in your log files, now you should have an idea on how to solve it. And if you haven’t looked into the log file for a while, this blog post might be a reminder to check them.

Generate a dynamic username with the Gravity Forms User Registration add-on in a multisite

As mentioned in previous blog post, I use Gravity Forms quite often when creating forms. It also offers some great add-ons. One of this add-ons is the User Registation. With the help of this extension, you can create a WordPress user, after the form was submitted.

The “username” field

You basically set up a regular form. In this form you should ask for some manadtory fields you would need to create a user account. As a bare minimum, your should ask for an email address, so users can activate their account. Other fields might be first and last name. But what about a username? The add-on offers a special field for usernames. When you want to enable users to pick their own name, this is a perfect field to add.

Use the “email” field

As WordPress allows any user to login either with their username or with their email address, they might not even need to know their username. So when creating the “Feed” to create a user, you could just use the email field. Unless you are creating users for a multisite.

Create a dynamic username using other fields

In a multisite installation, a username can only contain lower case letters and numbers. This is because the username “can” be used for a subsite per user. But even if you don’t plan to have a site per user (which the add-on could also create for you), this constraint still applies. So how to deal with that without asking your users to pick their own username? Simply create a dynamic username using other fields.

Use the first and last name fields

On many platforms, your username will automatically be a combination of your first and last name. So we can use a filter to create such a username. But as this username may still contain some invalid characters, we have to “sanitize” it. Fortunately WordPress offers a function for that. And as two new users may have the same first and last name, we need to find a way to solve that as well. But let’s start with the callback function to the Gravity Forms hook first:

function gf_ms_get_unique_username( $username, $feed, $form, $entry ) {
        // Get first and last name from the form submission.
        $first_name = rgar( $entry, rgars( $feed, 'meta/first_name' ) );
        $last_name  = rgar( $entry, rgars( $feed, 'meta/last_name' ) );

        // Generate a new username making it compatible with multisite constraints.
        $new_username = gf_ms_uf_get_unique_username( sanitize_user( $first_name . $last_name ) );

        if ( ! empty ( $new_username ) ) {
                $username = $new_username;
        }

        return $username;
}
add_filter( 'gform_username', 'gf_ms_uf_set_username', 10, 4 );

Here we use the hook gform_username which is used for the special username field. In the callback we get the values from the first and last name fields and pass them to our helper function to generate a unique username. If we get a new username, we return it.

Generating a unique username

Now how do we get this unique username? Well, we just need a method similar to the one that WordPress uses to get unique permalinks for posts/pages/terms with the same name. We use a “counter suffix” until the username is unique:

function gf_ms_get_unique_username( $username ) {
        $number            = 2;
        $original_username = $username;

        while ( username_exists( $username ) || gf_ms_signup_exists( $username ) ) {
                $username = $original_username . $number ++;
        }

        return $username;
}

function gf_ms_signup_exists( $username ) {
        global $wpdb;

        return $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM $wpdb->signups WHERE user_login = %s",
                $username
            )
        );
}

In the while loop we check not only if the username already exists, but also if the username has been used by a previous user who already signed up, but hasn’t confirmed his account using the activation link received by mail. To check this, we use another helper function with code similar to the one in Core.

Conclusion

WordPress does offer a native way to sign up new users – to single sites or multisite. But if you want to ask the new users for additional information, an add-on like the one from Gravity Forms can really improve the sign-up process. Signing up users for a multisite can be tricky, but with some good helper functions, this can also be solved. As always, you can find the code from this blog post in a GIST where you can also download it as a ZIP and install it as a plugin.