Use acf_form to update custom user fields

Today we are going to show you how to use acf_form to update custom user fields created with Advanced Custom Fields (ACF). We will create some custom fields using ACF then add the fields to a form on the front-end of the website. We will then demonstrate how to add, modify and save the custom user fields for the current user. The form will be processed using admin-post.php, which is the recommended way to process form data in WordPress. Let’s get started!

Creating Custom Fields

The first thing we must do is create the custom fields in ACF. Select “Custom Fields” from the main menu and select “Add New” at the top of the page. We will call our custom field group “User Profile”. Next, add a field to the newly created group and name it “Profile Image” and make the field type “Image”. The last thing to do here is to set the display rules. I have selected to show the field group when the “Add/Edit User Form” is displayed. This will ensure that the field group shows up on the user’s profile page in the admin area.

Now that we have created our custom field group, it’s time to add the form to our page template. In order to make sure our form displays and functions correctly, we must ensure all ACF scripts and styles are enqueued by adding the following line to the top of your template file.

acf_form_head();

To tell acf_form which fields to display, we must first find the IDs for the Field and Field Group you just created. Navigate to the edit page for the “User Profile” custom field and select “Screen Options” near the top of the page. To show the Field Group ID make sure “Slug” is checked. To show the Field IDs, make sure that “Field Keys” is checked. The field keys will now be displayed next to each individual Field and the Field Group ID will be displayed under “Slug” near the bottom of the page. The Field Group ID should look something like group_5cbd99ef0f584 and the Field IDs should look something like field_5cbd99f3922ce.

Setting up acf_form

Now we must create an array called “options”, that we will pass to acf_form() allowing us to customise the form. The “options” array accepts either a “field_groups” or “fields” index to determine which fields to display. Use one of the IDs you found in the last step to set either the “fields” or “field_groups” index. In our example, we have used the “fields” index, but it is up to you which one you choose. Paste the below code into your template file, making sure to substitute the Field or Group ID you found in the last step.

$user = wp_get_current_user();

$options = array(
  // 'field_groups' => ['group_5cbd99ef0f584'],
  'fields' => ['field_5cbd99f3922ce'],
  'form_attributes' => array(
    'method' => 'POST',
    'action' => admin_url("admin-post.php"),
  ),
  'html_before_fields' => sprintf(
    '<input type="hidden" name="action" value="adaptiveweb_save_profile_form">
    <input type="hidden" name="user_id" value="user_%s">',
    $user->ID
  ),
  'post_id' => "user_{$user->ID}",
  'form' => true,
  'html_submit_button' => '<button type="submit" class="acf-button button button-primary button-large" value="Update Profile">Update Profile</button>',
);

acf_form($options);

In order to make sure the form data is saved to the current user, the post_id index must be in the format “user_{$user_id}”. To get the current user_id we use the wp_get_current_user() function. The following snippet shows how to get the current user_id.

$user = wp_get_current_user();
$user_id = $user->ID;

We make the “form” index true so that ACF creates all the form markup for us using the options we pass to acf_form(). We also create a custom submit button by adding the markup to the “html_submit_button” index.

As we mentioned earlier, we will be processing the form the recommended way, using admin-post.php. To learn more about this method of processing forms in WordPress, you can read our post Handle POST and GET requests in WordPress using admin-post.php.

Processing the form

Next, we will create a custom function to save the user’s data. The following function will be called when a form is submitted with a (usually hidden) field with the name “action” with a value of “adaptiveweb_save_profile_form”. The function gets the user_id from the form data and calls the ‘acf/save_post’ action passing the user_id as the second parameter. Place this function in your functions.php file.

add_action( 'admin_post_adaptiveweb_save_profile_form', 'adaptiveweb_save_profile_form' );
function adaptiveweb_save_profile_form() {
  if(!isset($_REQUEST['user_id'])) return;

  do_action('acf/save_post', $_REQUEST['user_id']);

  wp_redirect(add_query_arg('updated', 'success', wp_get_referer()));
  exit;
}

In order to call this function when the form is submitted, we must use the “form_attributes” and “html_before_fields” indexes of the options array that we pass to acf_form(). We add an array to the “form_attributes” index with 2 indexes, method and action. The action index calls the script ‘wp-admin/admin-post.php’ using the admin_url function. We pass a string to the “html_before_fields” index with a hidden input field named action with the value adaptiveweb_save_profile_form as well as a hidden field named user_id with a value of $user->ID. Now when the form is submitted, the custom function we created will be called and the form data will be saved to the current user.

So there you have it, we have learned how to use acf_form to update custom user fields using a custom front-end form. If there is anything I have missed, please feel free to leave a comment. Thanks for reading!

References

Add a logout button to a WordPress navigation menu

Today we are going to look at how to add a logout button to a WordPress navigation menu. In fact, we will be showing either a logout or a login button, depending on whether the user is currently logged in. As well as being convenient for all users, this may also be necessary if you are preventing some users from accessing the WordPress Admin and would still like them to be able to log out.

In order to modify our navigation menu we need to hook into the wp_nav_menu_items filter. This hook gives us an array of menu items as the first parameter and an array of arguments as the second parameter. First, we do a quick check to see if it is the top (or main) menu being displayed using if ($args->theme_location == 'top') { (the location string may be different for your theme), before adding either a logout or login navigation item to the $items array, depending on the user’s logged in status.

add_filter( 'wp_nav_menu_items', 'tattoo_loginout_menu_link', 10, 2 ); function tattoo_loginout_menu_link( $items, $args ) {   if( $args->theme_location == 'top' ) {     if( is_user_logged_in() ) {       $items .= '<li class="logout"><a href="'. wp_logout_url() .'">'. __("Log Out") .'</a></li>';     } else {       $items .= '<li class="login"><a href="'. wp_login_url(get_permalink()) .'">'. __("Log In") .'</a></li>';     }   }    return $items; }

To add a logout button to a WordPress navigation menu, we simply add any additional items to the $items array inside the wp_nav_menu_items filter. The method described above, can be used to add any number of additional items to a sites navigation. This method is especially useful for plugin development, as navigation items required by a plugin can be added without the need to manually modify a menu in the WordPress Admin.

References

Creating pages automatically on plugin activation in WordPress

If you are creating a WordPress plugin, often times you will need to reference a specific page in order to display some kind of data or other information. Although it is possible to manually create pages in WordPress, required by a plugin, creating pages automatically on plugin activation will ensure your plugin works out of the box.

The first step is to register a function in your plugin that will fire upon activation. The following code uses the register_activation_hook function. The function accepts two parameters $file and $function. $file is a string representing the path to the main plugin file; $function is a reference to the function that will contain our activation code.

define( 'BEARDBOT_PLUGIN_FILE', __FILE__ );
register_activation_hook( BEARDBOT_PLUGIN_FILE, 'beardbot_plugin_activation' );

function beardbot_plugin_activation() {
  // activation code goes here
}

The first line uses a constant to store the path to the main plugin file. This will usually be a file in the plugin root directory with the same name as the plugin folder. Next we use the register_activation_hook function to register our activation function, referencing the path to the plugin file.

The first thing we do on activation is check that the current user is allowed to activate plugins. We do this using the current_user_can function. The first and only required parameter accepts a string representing a role or capability that you would like to check that the current user has. A detailed list of roles and their associated capabilities can be found here.

define( 'BEARDBOT_PLUGIN_FILE', __FILE__ );
register_activation_hook( BEARDBOT_PLUGIN_FILE, 'beardbot_plugin_activation' );
function beardbot_plugin_activation() {
  if ( ! current_user_can( 'activate_plugins' ) ) return;
}

Finally, we create our new page, after we check that a page with the same name does not exist.

define( 'BEARDBOT_PLUGIN_FILE', __FILE__ );
register_activation_hook( BEARDBOT_PLUGIN_FILE, 'beardbot_plugin_activation' );
function beardbot_plugin_activation() {
  
  if ( ! current_user_can( 'activate_plugins' ) ) return;
  
  global $wpdb;
  
  if ( null === $wpdb->get_row( "SELECT post_name FROM {$wpdb->prefix}posts WHERE post_name = 'new-page-slug'", 'ARRAY_A' ) ) {
     
    $current_user = wp_get_current_user();
    
    // create post object
    $page = array(
      'post_title'  => __( 'New Page' ),
      'post_status' => 'publish',
      'post_author' => $current_user->ID,
      'post_type'   => 'page',
    );
    
    // insert the post into the database
    wp_insert_post( $page );
  }
}

Here is a full list of parameters accepted by the wp_insert_post function.

Creating pages automatically on plugin activation in WordPress is the best way to ensure maximum compatibility with any WordPress website.

References

Handle POST and GET requests in WordPress using admin-post.php

Today we are going to learn how to handle POST and GET requests in WordPress using custom functions and admin-post.php. There are various ways to process and handle POST requests in WordPress. You could use a page template or even a custom script. When using a custom script, you do not have access to WordPress or its functions by default. If you are using a page template, you jeopardise maintainability by mixing logic and display code. WordPress offers an elegant method for processing POST (or GET) requests using custom functions via admin-post.php.

In this example we will be processing POST data from a simple contact form submission. We first add the HTML for our form.

<form method="post" action="<?php admin_url( 'admin-post.php' )' ?>">
  <input type="hidden" name="action" value="process_form">
  <label for="name">Name:</label>
  <input type="text" name="name" id="name">
  <label for="email">Email:</label>
  <input type="text" name="email" id="email">
  <input type="submit" name="submit" value="Submit">
</form>

As mentioned earlier we will be processing the post request via admin-post.php. To get the URL for this script we use the WordPress function admin_url() and add it to our forms action attribute. The hidden input element will be used to hook into the admin-post.php script and our custom form processing function.

Next we will create a custom function for processing the form data and hook it into our form submission via the admin_post hook.

<?php
add_action( 'admin_post_nopriv_process_form', 'process_form_data' );
add_action( 'admin_post_process_form', 'process_form_data' );
function process_form_data() {
  // form processing code here
}
?>

We mentioned earlier that the hidden input element in our form would allow us to hook into admin_post. The first 2 lines above use the format admin_post_nopriv_$action and admin_post_$action respectively as the hook, where $action is the value of the hidden input in our form. The hidden input element must have action as the name attribute’s value for this to work.

admin_post_$action fires when a user is logged in and admin_post_nopriv_$action fires when a user is not logged in, which means you can create multiple custom functions to handle requests, depending on whether a user is logged in or not.

You will now have access to the form data via the $_POST (or $_GET) array(s) in your custom function(s). Once you have processed, validated or sanitised the form data you can redirect to a front-end page using wp_redirect.

Using this method to handle POST and GET requests in WordPress, when submitting a form, will prevent browsers from displaying a form re-submission warning when a user hits the back button after submission.

References

Cache busting CSS and JS assets in WordPress

Today we are going to learn about cache busting CSS and JS assets in WordPress the easy way.

Have you ever struggled to make sure updated versions of your CSS or JavaScript assets are being sent to users of your WordPress website? One way of solving this problem in the past, was to give each asset a unique name after updating it, forcing browsers to grab the new version when users visited your website. If you have ever done this, you will know it is a tedious process. Thankfully there is a much easier way by using the file meta data for each assets and the handy PHP function filemtime.

WordPress gives us the tools out of the box, to append unique version numbers to our CSS and JS assets, in the form of a query string. The wp_enqueue_style and wp_enqueue_script functions have a version parameter. With the use of the filemtime PHP function, we can automate the entire process by passing the file modified string as the value for the version parameter.

The filemtime function returns the file modified time from a files meta data. By using the $ver parameter of both the wp_enqueue_style and wp_enqueue_script WordPress functions, we can append the file modified time to our CSS and JS asset URLs.

The method for adding the file modified time to your stylesheets is shown below.

wp_enqueue_style(
  'theme-styles', // $handle
  get_bloginfo( 'stylesheet_url' ), // $src
  array(), // $deps
  filemtime( get_stylesheet_directory() . '/style.css' ) // $ver
);

The method for adding the file modified time to your scripts is shown below.

wp_enqueue_script(
  'theme-scripts', // $handle
  get_template_directory_uri() . '/js/scripts.js', // $src
  array( 'jquery' ), // $deps
  filemtime( get_template_directory() . '/js/scripts.js' ), // $ver
  true // $in_footer
);

This method of cache busting CSS and JS assets in WordPress means you will never have to worry about users not seeing updates you have made.

References

Making an AJAX call to a custom WordPress plugin script

Today we are going to learn about making an AJAX call to a custom WordPress plugin. This method will allow you to make a call to your plugin using JavaScript. The call will be processed by a custom PHP function and a response returned.

To make AJAX calls to a custom plugin script, that has access to WordPress objects and functions, two hooks must be used; wp_ajax_$action and wp_ajax_nopriv_$action; where $action refers to an AJAX requests action property.

Each AJAX request must be made to the admin-ajax.php script. To get the URL for the admin-ajax.php script, the admin_url() function can be used, as shown below.

$ajax_url = admin_url( 'admin-ajax.php' );

The plugin function that will process the AJAX request must be hooked to both of the wp_ajax actions above. custom_action_name should match the AJAX requests action property and custom_function_name is the name of the plugin function that will process the AJAX request, as shown below.

add_action( 'wp_ajax_custom_action_name', 'custom_function_name' );
add_action( 'wp_ajax_nopriv_custom_action_name', 'custom_function_name' );

function custom_function_name() {
  // code to process AJAX request
}

In order to make the URL to the admin-ajax.php script available in your JavaScript file you can use the wp_localize_script function after registering, but before enqueueing a script, as shown below.

wp_register_script( 'ajax-script', plugin_dir_url(__FILE__) . 'js/ajax-script-name.js' );
$wp_vars = array(
  'ajax_url' => admin_url( 'admin-ajax.php' ) ,
);
wp_localize_script( 'ajax-script', 'wp_vars', $wp_vars );
wp_enqueue_script( 'ajax-script' );

The admin-ajax.php URL can now be accessed inside your JavaScript file as a property of the wp_vars object, as shown below.

var ajax_url = wp_vars.ajax_url;

Now all that’s left to do is to make the AJAX request. For brevity I will not go into the complexities of creating a cross-browser AJAX request. The code below should work in most modern browsers.

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
  if( xhr.readyState === 4 && xhr.status === 200 ) {
    console.log( xhr.responseText );
  }
}
var ajax_url = wp_vars.ajax_url;
xhr.open( 'POST', ajax_url, true );
xhr.setRequestHeader( 'Content-type', 'application/x-www-form-urlencoded' );
var params = 'action=custom_action_name';
xhr.send( params );

By using a few built-in WordPress functions, making an AJAX call to a custom WordPress plugin script is made possible.

References

Sorting WordPress posts using multiple meta keys

Today we are going to learn about sorting WordPress posts using multiple meta keys. The method uses WP_Query and can be used in page templates, shortcodes functions, custom plugin scripts and a variety of other ways.

Occasionally it may be necessary to custom order posts using multiple meta keys. This can be achieved using the meta_query parameter of the WP_Query object.

Below I will show you how to order a custom post type called member, inside a page template, using 2 meta keys; board_position and current_employer. The example also shows you how to sort the results using both meta values.

$args = array(
  'post_type' => 'member',
  'meta_query' => array(
    'relation' => 'AND',
    'board_position_query' => array(
      'key' => 'board_position',
    ) ,
    'current_employer_query' => array(
      'key' => 'current_employer',
    ) ,
  ) ,
  'orderby' => array(
    'board_position_query' => 'ASC',
    'current_employer_query' => 'ASC',
  ) ,
);
$member_posts = new WP_Query( $args );

By using the meta_query parameter we can pass an array of arrays containing the meta keys we want to sort our posts with. The key index of each nested array matches the meta_key stored in the wp_postmeta database table.

If you are using this query in a page template and want to make use of the loop, the functions have_posts() and the_post() must be made methods of the $member_post object, as shown below.

while( $member_posts->have_posts() ): $member_posts->the_post();
  // loop code goes here...
endwhile;

As you can see, sorting WordPress posts using multiple meta keys is a breeze thanks to WordPress.

References

Displaying custom post types on a WordPress blog page

Today we are going to learn about displaying custom post types on a WordPress blog page using the pre_get_posts hook.

If you are using custom post types via a theme or plugin you may want to display one or more custom post types on your site’s blog page.

Below I will show you how to modify the main query of a blog page using the pre_get_posts hook.

In the example below I will be modifying the main query to display the custom post type news. The benefit of using the pre_get_posts hook is that the query is modified before it is run, avoiding possible performance implications from rerunning the main query. To display the custom post type news add the following code to your site’s functions.php file.

add_action( 'pre_get_posts', 'custom_post_query_vars' );
function custom_post_query_vars( $query ) {
  if( $query->is_main_query() && is_home() ) {
    $query->set( 'post_type', 'news' );
  }
}

All parameters accepted by the WP_Query object can be used with the pre_get_posts hook via the $query->set() method to modify the main query.

The $query->is_main_query() method ensures that only the main query (such as the loop) is modified and not a secondary query.

The is_home() function returns true when the blog post index page is being displayed.

To display multiple custom posts types you can pass an array of custom post types to the $query->set() method. To display the post types post, news and reviews add the following to your site’s functions.php file.

add_action( 'pre_get_posts', 'custom_post_query_vars' );
function custom_post_query_vars( $query ) {
  if( $query->is_main_query() && is_home() ) {
    $query->set( 'post_type', array(
      'post',
      'news',
      'reviews'
    ));
  }
}

Displaying custom post types on a WordPress blog page is made easy using the pre_get_posts hook. If you would like additional information about any of the WordPress hooks or function used, please use the reference links provided below.

References

Hiding the Posts menu in the WordPress admin

Today we are going to learn about hiding the posts menu in the WordPress admin. We will remove the Posts menu item from the main menu, as well as the New Post menu item from the menu bar.

If you do not want your admin users to see the menu link to create standard posts, you can remove the Posts menu item from the WordPress admin menu. You may want to do this if you are making use of custom posts types via a theme or plugin or do not intend to use a blog on your WordPress site.

Below I will show you how to remove the Posts menu item from the main menu and the New Post menu item from the menu bar.

To remove the Posts menu item from the main menu, add the following code to your theme’s functions.php file.

add_action( 'admin_menu', 'hide_admin_post_main_menu' );
function hide_admin_post_main_menu() {
  remove_menu_page( 'edit.php' );
}

It is worth mentioning that this method does not stop a user accessing these screens. This method is purely aesthetic and is designed to simplify the UI for admin users. If you need to block access to these pages, you will need to use a plugin or some additional code to filter each user’s permissions.

To remove the New Posts menu item from the menu bar, add the following code to your theme’s functions.php file.

add_action( 'admin_bar_menu', 'hide_admin_post_bar_menu', 999 );
function hide_admin_post_bar_menu() {
  global $wp_admin_bar;
  $wp_admin_bar->remove_node( 'new-post' );
}

Hiding the posts menu in the WordPress admin is not crucial, but can allow you to improve the usability of your website’s admin by simplifying the admin UI.

It is usually a good idea to append a namespace to functions you add to your theme’s functions.php file to avoid conflicts with plugins and other code. For example; function adaptive_hide_admin_post_main_menu() {...}.

References