Body Tag User Role

Sometimes you want to add a body tag for a specific user role. This as you want to hide or show specific parts of a page, or site for a particular user. Here is how you can do just that.

add_filter( 'body_class', 'imwz_role__body_class' );
function imwz_role__body_class( $classes ) {
 $user = wp_get_current_user(); 
 if ( in_array( 'subscriber', $user->roles ) ) {     
     $classes[] = 'class-name'; // your custom class name 
  } 
  return $classes;
 }

As you can see the body_class filter is used. Inside it we get the user role properly using wp_get_current_user and an array checking the roles.

Once done you could see something like this for classes

product-template-default single single-product postid-729 logged-in admin-bar theme-Divi et-tb-has-template et-tb-has-body admin-role woocommerce woocommerce-page woocommerce-js et_bloom et_button_custom_icon et_pb_button_helper_class et_fixed_nav et_show_nav et_primary_nav_dropdown_animation_fade et_secondary_nav_dropdown_animation_fade et_header_style_left et_pb_footer_columns_1_4__1_2 et_cover_background et_pb_gutter osx et_pb_gutters3 et_divi_theme et-db et_minified_js et_minified_css customize-support chrome

waarin class admin-role op basis administrator filter is toegevoegd.

Props Mukto90 https://wordpress.stackexchange.com/a/268178/12260

Assign Specific Role on Registration

Sometimes you need a custom registration form. A registration form where more fields can be added and where you can assign a specific role based on user input.

Plugin Registration

Better to put the to be added code in a mu-plugins plugin instead of the theme’s function.php. So let’s start with that.

<?php
 /*
 Plugin Name: Custom Registration Fields
 Plugin URI:https://wpvilla.in
 Description:
 Version: 0.1
 Author: WP Villain
 Author URI: https://wpvilla.in
 License: GPLv2 or later
 License URI: http://www.gnu.org/licenses/gpl-2.0.html
 */

Custom Registration Form

Now you need to add a field to the registration form. And perhaps even more then one, but we start with one:

add_action( "register_form", function() {
  $html = '<p>'.__('Do you want to buy or sell?<br>Choose your role').'</p>';
  $html .= '<p>';
      $html .= '<label for="_customer"><input type="radio" name="user_role" id="_customer" value="customer"/>'.__('Customer').'</label><br>';
      $html .= '<label for="_vendor"><input type="radio" name="user_role" id="_business" value="_vendor"/>'.__('Business').'</label>';
  $html .= '</p>';
  echo $html;
} );

Props Kudratullah @ https://wordpress.stackexchange.com/a/253129/12260

Error Checking

We also need to make sure the choices needed are made so they do need to check for a role

add_filter( 'registration_errors', 'imwz_registration_errors', 10, 3 );
function imwz_registration_errors( $errors, $sanitized_user_login, $user_email ) {
  if ( empty( $_POST['YourFieldName'] ) ) {
    $errors->add( 'role_choice_error', __( '<strong>ERROR</strong>: Please pick a role.', 'imwz' ) );
  }
  return $errors;
}

The filtered WP_Error object may, for example, contain errors for an invalid or existing username or email address. A WP_Error object should always returned, but may or may not contain errors. https://developer.wordpress.org/reference/hooks/registration_errors/

NB Sanitizing is missing here. Will be added as soon as possible

Action to Set Role on Registration

Then we need another action hook to make sure that on registration the proper user role is added for the user based on choice made.

add_action( "user_register", function( $user_id ) {
  $allowed = array( 'customer', 'business' );
  if( isset( $_POST['YourFieldName'] ) && in_array( $_POST['YourFieldName'], $allowed ) ){
      $user = new WP_User( $user_id );
      $user->set_role($_POST['YourFieldName']);
  }
} );

Action ‘user_register‘ takes place after a user is inserted into the database.

NB If you are using ACF to add the role on registration you would have something like this when you would be using radio buttons and want to check for one of the two choices made:

add_action( "user_register", function( $user_id ) {
  $selected_radio = $_POST['field_5e44e2456a4a9'];
  $user = new WP_User( $user_id );
  if( $selected_radio == 'bedrijf') {
      $user->set_role('bedrijf');

  }
} );

Custom User Profile Fields

We will also need to add the fields to the backend user profile. Code below is still more of a concept.

/**
 * Back end registration
 */

add_action( 'user_new_form', 'imwz_admin_registration_form' );
function imwz_admin_registration_form( $operation ) {
  if ( 'add-new-user' !== $operation ) {
    // $operation may also be 'add-existing-user'
    return;
  }

  $year = ! empty( $_POST['customer_type'] ) ? intval( $_POST['customer_type'] ) : '';

  ?>
  <h3><?php esc_html_e( 'Personal Information', 'imwz' ); ?></h3>

  <table class="form-table">
    <tr>
      <th><label for="year_of_birth"><?php esc_html_e( 'Customer Type', 'imwz' ); ?></label> <span class="description"><?php esc_html_e( '(required)', 'imwz' ); ?></span></th>
      <td>
        <input type="radio"
         name ="user_role"
         value = "Business"
        /> <?php __('Business').'</label><br>'; ?>
      </td>
    </tr>
  </table>
  <?php
}

More will be added soon here

ACF & Registration Form

Another option would be to use ACF to add all the forms needed. Here is a basic hallo field generated in ACF that is added to the form:

if( function_exists('acf_add_local_field_group') ):
 acf_add_local_field_group(array(
     'key' => 'group_5e42248c978d2',
     'title' => 'Registration Form',
     'fields' => array(
         array(
             'key' => 'field_5e422541ddb04',
             'label' => 'Hello',
             'name' => 'hello',
             'type' => 'text',
             'instructions' => '',
             'required' => 0,
             'conditional_logic' => 0,
             'wrapper' => array(
                 'width' => '',
                 'class' => '',
                 'id' => '',
             ),
             'default_value' => '',
             'placeholder' => '',
             'prepend' => '',
             'append' => '',
             'maxlength' => '',
         ),
     ),
     'location' => array(
         array(
             array(
                 'param' => 'user_form',
                 'operator' => '==',
                 'value' => 'register',
             ),
         ),
     ),
     'menu_order' => 0,
     'position' => 'normal',
     'style' => 'seamless',
     'label_placement' => 'top',
     'instruction_placement' => 'label',
     'hide_on_screen' => '',
     'active' => true,
     'description' => '',
 ));
 endif;

And you can use it to add radiobuttons as well as conditionals. Only thing that may need tweaking is adding the data for user role to the user registration role. But for that we can still use the add_action hook mentioned earlier.

NB https://developer.wordpress.org/reference/hooks/user_new_form/

NB https://www.wpbeginner.com/plugins/how-to-create-a-custom-user-registration-form-in-wordpress/ , https://www.cssigniter.com/how-to-add-custom-fields-to-the-wordpress-registration-form/ and https://gist.github.com/jasperf/7303deb46c602a357fa4a3b671067fc9

Apply Filter to Specific User

What if you need to apply a certain filter for a certain user? What could you do to achieve this? Well, there is too things we would have to look at. The user we want to work with and the filter to use.

User Capabilities

Check if user has a certain capacity or capability can be done with

current_user_can

This is for the current user as you can see. See https://developer.wordpress.org/reference/functions/current_user_can/ Or you can use

$user->has_cap( 'edit_posts' );

to focus on any user with certain capabilities.

See https://developer.wordpress.org/reference/classes/WP_User/has_cap/

Add User Roles

In our case we want to focus on the user group or type of user. So we need to look at user roles. The following code can be used to add a new role

add_role( $role, $display_name, $capabilities );

See also https://developer.wordpress.org/reference/functions/add_role/

To add a new role called company we can use the following

add_role('company', __(
   'Company'),
   array(
       'read'            => true, // Allows a user to read
       'create_posts'      => true, // Allows user to create new posts
       'edit_posts'        => true, // Allows user to edit their own posts
       'edit_others_posts' => true, // Allows user to edit others posts too
       'publish_posts' => true, // Allows the user to publish posts
       'manage_categories' => true, // Allows user to manage post categories
       )
);

Now we do not want to execute this role all the time so better to use

function imwz__update_custom_roles() { 
if ( get_option( 'custom_roles_version' ) < 1 ) { 
add_role( 'company', 'Company', 
array('read' => true, // Allows a user to read        
'create_posts'      => true, // Allows user to create new posts        
'edit_posts'        => true, // Allows user to edit their own posts        
'edit_others_posts' => true, // Allows user to edit others posts too        
'publish_posts' => true, // Allows the user to publish posts        
'manage_categories' => true, // Allows user to manage post categories        
)  ); 
update_option( 'custom_roles_version', 1 ); } } 
add_action( 'init', 'imwz__update_custom_roles' );

Now, we need to fix this code some. Too many capabilities were added here as the code was based on code for adding a moderator. So what if we only want this role to be for customers or more specifically subscribers? Then we use this code

function imwz__update_custom_roles() {
     if ( get_option( 'custom_roles_version' ) < 1 ) {         
         add_role( 'custom_role', 'Custom Subscriber', array( 'read' => true, 'level_0' => true            ) );
         update_option( 'custom_roles_version', 1 );
     }
 }
 add_action( 'init', 'imwz__update_custom_roles' );

And if we need to add two roles right away

function imwz__update_custom_roles() { 
  if ( get_option( 'custom_roles_version' ) < 2 ) { 
  add_role( 'bedrijf', 'Bedrijf', 
  array('read' => true, // Allows a user to read        
  // 'create_posts'      => true, // Allows user to create new posts        
  // 'edit_posts'        => true, // Allows user to edit their own posts        
  // 'edit_others_posts' => true, // Allows user to edit others posts too        
  // 'publish_posts' => true, // Allows the user to publish posts        
  // 'manage_categories' => true, // Allows user to manage post categories        
  )  ); 
  add_role( 'particulier', 'Particulier', 
  array('read' => true, // Allows a user to read        
  // 'create_posts'      => true, // Allows user to create new posts        
  // 'edit_posts'        => true, // Allows user to edit their own posts        
  // 'edit_others_posts' => true, // Allows user to edit others posts too        
  // 'publish_posts' => true, // Allows the user to publish posts        
  // 'manage_categories' => true, // Allows user to manage post categories        
  )  ); 
  update_option( 'custom_roles_version', 2 ); } } 
  add_action( 'init', 'imwz__update_custom_roles' );

Props https://profiles.wordpress.org/rogertheriault/

mu-plugins loads too early, so use an action hook (like ‘init’) to wrap your add_role() call if you’re doing this in the context of an mu-plugin.

If Role Company Use Minimum 10 Items

You can apply this newly created role using any added filter really. We will use one we needed for a customer in a WooCommerce surrounding, but you can use yours.

We can use ad_filters to use a filter on the minimum product quantity set by the plugin Product Quantity for WordPress using this code:

add_filter( 'alg_wc_pq_get_product_qty_min', 'my_product_qty_min_by_user_role', 10, 2 );
function my_product_qty_min_by_user_role( $qty, $product_id ) {
  $current_user = wp_get_current_user();
  return ( in_array( 'bedrijf', $current_user->roles ) ? 10 : $qty );
}

See plugin class class-alg-wc-pq-core.php and http://hookr.io/filters/woocommerce_quantity_input_min/

The filter used here is a WooCommerce filter that is also used in the mentioned plugin. We simply use the filter if the user has the role company. You can also easily use another conditional of your own.

Redirect Certain Categories to new site

Sometimes you want to redirect posts in certain categories to a new location / domain name. You do not want to redirect the entire website to a new website, but only part of it. This because you perhaps split up your website into multiple websites. Well you are in luck. With WordPress this is possible with the use of template_redirect.

Template Redirect

Thanks to Ryan’s post we came to realize how easy this redirecting based on category is with WordPress built in template_redirect . This function is normally used to redirect / decide what template to load, but it can also be used to redirect to that same category on another website.

Example Category Redirect

Here is an example of two sets of redirect we used before. One redirecting to this current website and one to our partner WooAid:

// redirect WP stuff to WP Villain and Woo  stuff to Wooaid

add_action('template_redirect', __NAMESPACE__ . '\\post_redirect_by_custom_filters');
function post_redirect_by_custom_filters() {
    global $post;
    // this array can contain category names, slugs or even IDs.
    $catArray = ['Sage Starter Theme', 'Sage', 'WordPress', 'Trellis', 'Bedrock', 'Plugins', 'Themes'];
    if (is_single($post->ID) && has_category($catArray, $post)) {
        $new_url = "https://wpvilla.in/{$post->post_name}/";  
        wp_redirect($new_url, 301);
        exit;
    }
}

add_action('template_redirect', __NAMESPACE__ . '\\woo_post_redirect_by_custom_filters');
function woo_post_redirect_by_custom_filters() {
    global $post;
    // this array can contain category names, slugs or even IDs.
    $catArray = ['WooCommerce'];
    if (is_single($post->ID) && has_category($catArray, $post)) {
        $new_url = "https://wooaid.com/{$post->post_name}/";  
        wp_redirect($new_url, 301);
        exit;
    }
}

These two examples are used in a Sage 8 based theme so namespacing is mandatory. For themes that do not use namespacing you can leave that part out.

Block Password Recovery Attacks

Been annoyed by the recent barrage of brute force password recovery attacks. In WordPress you can block these kind of attacks by turning off password recovery. Now this is obviously not for everyone as you will not be able to reset your password if you ever lose it. And that would mean adjusting the code to reactivate this or do some cool database tweaks with knowledge of password hashing and or other stuff like salts and whatnot. But I can manage. So I added

// Block Password Recovery
function disable_reset_lost_password()
{
return false;
}
add_filter( 'allow_password_reset', 'disable_reset_lost_password');

to functions.php

WordPress Setup Digital Ocean

Digital Ocean Account Setup

To work with Digital Ocean you need an account. It can be one run by developer, but preferably one client runs and to which he invites you as a developer and or DevOps. So you need the following for starters

  • Setup of account by client with DO
  • Team Setup with access for developer

Droplet Setup

Droplet’s setup is relatively easy using the Digital Ocean’s interface. You need to choose the proper droplet, your preferred region, Droplet size and Droplet base OS.

  • Droplet Size – At least 1 GB
  • Region Choice
  • Ubuntu 18.0.4 Setup with SSH keys attached
  • FQDN domain name connected to ip Droplet
  • Domain name must be directed to Digital Ocean DNS Server from Domain Service Provider
  • Domain at Digital Ocean must be directed with A Record to Droplet’s IP
  • Digital Ocean LEMP with WordPress

WordPress Setup Digital Ocean

Once you have taken care of the Droplet and Domain setup we move onto the actual guide for setting up a WordPress LEMP. This guide will be your base. It explains all and refers to requirements needed like:

  • initial Ubuntu 18.0.4 Server Setup
  • LEMP Setup
  • Let’s Encrypt SSL Setup

before moving onto setting things up like the

  • actual database installation,
  • PHP extensions,
  • WordPress Download
  • WordPress Config Tweaks
  • WordPress GUI Installation

Digital Ocean Initial Server Setup

Digital Ocean has a great guide mentioning where need be all extra steps to take to setup a basic Ubuntu Server to get going

It will guide you through setting up a server with

  • Sudo User,
  • basic firewall,
  • external access

Digital Ocean LEMP Setup

For the LEMP setup there is another great guide. It will go through all the necessary steps to set up a LEMP with:

  • Nginx webserver,
  • MySQL Database server,
  • PHP-FPM PHP Parser

on your Ubuntu Linux Droplet.

SSL Setup

Let’s Encrypt SSL Setup to get a free SSL certificate is not hard anymore these days and DO also gots your back here with this guide. It explains the DNS needs to set you up with LE SSL using Certbot.

Database Setup

MySQL Database installation on MYSQL Server is explained in the base tutorial. It basically tells you how to add the database from the command line with needed privileges and such.

PHP Extensions

Installing the necessary PHP Extensions is also explained in the base guide just like the database setup. Just follow it, install the necessary extensions with apt:

  • sudo apt update
  • sudo apt install php-curl php-gd php-intl php-mbstring php-soap php-xml php-xmlrpc php-zip

See guide for all the details

WordPress Installation

Downloading of the needed WordPress files to run the WordPress CMS is what you need to do here. Also explained in base guide as well as on WordPress. We can use curl for this step as you will see in the guide.

WordPress Configuration

Configuring the WordPress configuration file to work with the set up database, necessary constants for security like the AUTH_KEY.

Installation via Interface

Installation of WordPress following the instructions in the browser. Explained in base guide as well as on WordPress and actually pretty self explanatory.

 

New WordPress Admin User w/ PHP

To add a New WordPress Admin User w/ PHP you can tweak the database, but you can also simply do it with PHP code. I prefer the latter as I am better at PHP than MySQL and as messing with the database once a site is up is always tricky.

PHP Snippet

Here is a snippet from WP Scholar to achieve this as a WordPress Must Use Plugin:

<?php

add_action( 'init', function () {
  
	$username = 'admin';
	$password = 'password';
	$email_address = 'webmaster@mydomain.com';

	if ( ! username_exists( $username ) ) {
		$user_id = wp_create_user( $username, $password, $email_address );
		$user = new WP_User( $user_id );
		$user->set_role( 'administrator' );
	}
	
} );

As you can see it adds three variables:

  • user name
  • password
  • email address

It also makes sure to only add it when it exists and assigns it the role of administrator which is what we were aiming for.

Application

Just add it to the following directory:

wp-content/mu-plugins

If the mu plugins folder is missing add it. This setup loads it as a must use plugin that wil be loaded on the fly by WordPress. Once that is done you should be able to log in with your new details. Once you checked it worked do not forget to remove this file!

And there you have it. A new admin level user with a new password without touching existing users nor the database.

Laravel Valet Nginx 502 errors

Sometimes your WordPress website won’t load properly in Laravel Valet. You keep on getting Laravel Valet Nginx 502 errors. What can be done about it?

Telltale Signs

In your .valet/Log/nginx-error.log  you will see something like:

HTTP/2.0", upstream
2018/07/24 09:18:54 [error] 15573#0: *328 upstream sent too big header while reading response header from upstream, client: 127.0.0.1, server:

That means that your Nginx defaults are not good enough to handle the needs of the local setup.

Nginx Configuration Tweaks

To deal with this you can change your Nginx confix under .valet/Nginx/site.test and add the following to the php block:

# 24 july 2018 parameters to avoid Nginx 502 errors
  fastcgi_temp_file_write_size 10m;
  fastcgi_busy_buffers_size 512k;
  fastcgi_buffer_size 512k;
  fastcgi_buffers 16 512k;
  fastcgi_connect_timeout 300;
  fastcgi_send_timeout 300;
  fastcgi_read_timeout 300;
  fastcgi_intercept_errors on;
  fastcgi_next_upstream error invalid_header timeout http_500;

source SO pacofelc

Restart Brew Nginx Services

You will then also need to restart Nginx for Valet and you can do this like so:

brew services restart nginx

Digital Ocean Volume for WordPress Media

Adding Digital Ocean Volume for WordPress Media can be very useful. Especially if you have a lot of images and or other media and need a cheap way to store them. Block Storage at Digital Ocean is fast and easy to work with. It allows you to get a cheap Droplet and add cheap storage.

We already wrote about Setting up Trellis on Digital Ocean. Now you can expand upon this with a volume for your media!

Format the Volume

To format the volume I followed Digital Ocean’s instructions. These you will get once a volume has been setup. In a popup you will get the line needed for your volume in your region. Idid a

sudo mkfs.ext4 -F /dev/disk/by-id/scsi-0DO_Volume_volume-lon1-01

Add directory to store media

If you have this WordPress setup from scratch and just start a website you can skip this as the media directory is yours to use

If you do not already have the media directory where you store the media you can create one. Most likely you do. We however moved the old sites one inside the wp-content/uploads for our multisite media to a backup location and then recreated it using:

sudo mkdir -p sites

This as we would later sync all that data from the production server.

Mounting the Volume

Next we had to mount that directory so that it would load all the data from the volume instead of the actual Droplet. We did that using:

sudo mount -o discard,defaults /dev/disk/by-id/scsi-0DO_Volume_volume-lon1-01 /srv/www/staging.domain.com/shared/uploads/sites;

And to make the mount permanent do a:

echo /dev/disk/by-id/scsi-0DO_Volume_volume-lon1-01 /srv/www/staging.domain.com/shared/uploads/sites ext4 defaults,nofail,discard 0 0 | sudo tee -a /etc/fstab

Then we checked if the mounting was successful:

cat /proc/mounts |grep staging
/dev/sda /srv/www/staging.domain.com/shared/uploads/sites ext4 rw,relatime,discard,data=ordered 0 0

As you see it was. And this means for most of you that you are done. You will have a Digital Ocean Volume for WordPress Media. We however needed to get the media copied over still.

Rsync Data

If you have this setup from scratch and just start a website you can skip this

Normally if you have a media volume already and need one for staging or another clone of the server you can just clone the volume and set up a new one and attach it. We only had an issue as the production server and volume were in a different region than staging so we had to set up a volume from scratch at both locations and sync the data between them:

ssh -o ForwardAgent=yes web@xxx.xxx.xxx.xx "rsync -aze 'ssh -o StrictHostKeyChecking=no' --progress /srv/www/domain.com/current/web/app/uploads/sites/ web@xx.xxx.xx.xx:/srv/www/staging.domain.com/shared/uploads/sites/"

Responsive Images with Width Based on Actual Image Width

WordPress has responsive images built in these days. Since 4.4 as a matter of fact. But what if we want to have WordPress Responsive images with width based on actual image width?

Current State and Needs

Can we generate something like WordPress responsive image (comments added to html):

<img width="1024" height="445" 
src="https://example.com/app/uploads/2016/04/name.jpg" // old browser support using src
class="d-block w-100" alt="" 
srcset="https://example.com/app/uploads/2016/04/name.jpg 1024w, // 1024 width image 
https://example.com/app/uploads/2016/04/name-300x130.jpg 300w, // 300px width image
https://example.com/app/uploads/2016/04/name-768x334.jpg 768w" / /768px width image
sizes="(max-width: 1024px) 100vw, 1024px">

where the 1024px can be based on the actual width of the image? That was the question we had and so we started looking around.

Additional Width

Somehow we had quite a few images that were smaller than 1024px on our site and these now got stretched as they are not 1024px wide.
As WordPress sees that the full image is 1024px by 445px that is what is uses to generate the images and though this works well it does not work for all. Looking for solution as suggested by Mark Root-Wiley like:

<img
 src="example.gif",
 srcset="example.gif 200w"
 sizes="(min-width: 400px) 400px, 100vw"
 width="200" /* <=== TA-DA! */
 class="logo">

This would be a possible solution if the width was not static and the $attr variable would not overwrite the image attributes. Still we looked further.

Custom Sizes

Well, WordPress did suggest a solution for custom sizes:

<?php
$img_src = wp_get_attachment_image_url( $attachment_id, 'medium' );
$img_srcset = wp_get_attachment_image_srcset( $attachment_id, 'medium' );
?>
<img src="<?php echo esc_url( $img_src ); ?>"
     srcset="<?php echo esc_attr( $img_srcset ); ?>"
     sizes="(max-width: 50em) 87vw, 680px" alt="A rad wolf">

But that still did not load a width based on actual image width. And that set the srcset image to a another image size which we didn’t really need. Full width was fine for us.

WordPress Image Width

We could use

$image_data = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), "thumbnail" ); ?>

to grab the image meta data and then the width using:

$image_width = $image_data[1];

NB Great Explanation by Jonathan here

Custom Responsive Image with Actual Width

So something like:

<?php
$alt_text = get_post_meta($post->ID, '_wp_attachment_image_alt', true);
$image_data = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), "thumbnail" );
$image_width = $image_data[1];
$img_src = wp_get_attachment_image_url( $attachment_id, 'full' ); 
$img_srcset = wp_get_attachment_image_srcset( $attachment_id, 'full' ); ?> 
<img src="<?php echo esc_url( $img_src ); ?>" srcset="<?php echo esc_attr( $img_srcset ); ?>" sizes="(max-width:<?php $echo image_width;?>) 100vw,<?php$ echo image_width;?>" alt="<?php echo $alt_text;?>">

NB Not tested yet!

should do the trick. This would allow a fully customized loading of the image with the actual width loaded instead of the full image width as set in the theme.

To be continued…