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.

Thrive Architect Page Export Requested

Been working for an Australian customer more recently and for several of her website she use the Thrive Architect Page Builder. Not a bad page builder though I prefer Elementor. Though both are not bad and have similar downsides I bumped into one main pain in the b*tt using Thrive Architect.

Export Trap

I had built up a great page in relatively little time. It was a large landing page with a lot of images, text, a table and loads of pricing and review buttons. Then I saw a save as template option and was like great. This way I can save it as a template and export it. And then reuse it on the live website. Then I found out only landing pages can be exported. And templates or content templates cannot.

Workarounds

The only way I could export it all was by creating a unique WordPress user, exporting the pages under that user or opening the Thrive Architect generated page in html view and copy all over. I decided to do the later. Only other pain there again was that all internal urls needed the TLD changed from test to com.

Final Notes

Why they did not have a page export option or content template option? Not sure. Seems they hadn’t come around doing just that. If I had known all this from the start I would have started with a landing page and customized, saved and exported that. For me this was however too late.

Add SSH Keys to Trellis Post Launch

Sometimes you want to work with other developers post launch on a Roots Trellis project. In those cases you need to only add these new users. And that means you need to add ssh keys to Trellis you did not add yet.

Ansible Tags

Fortunately you do not need to provision the whole server again. You can use Ansible tags and just run

ansible-playbook server.yml -e env=production --tags=users

These tags were added by the Roots team to run certain actions separately. Very convenient! In this case the tag users will add only the users from trellis/group_vars/all/users.yml to the server. The place where you can easily add the new ssh keys

keys:
- "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
- https://github.com/existingdev.keys
- https://github.com/newdev.keys

So when you do that Ansible will see new keys were added and will update the server accordingly.

NB See also Roots Trellis documentation on this here.

Trellis Setup with Laravel Valet

We often prefer working with Laravel Valet over working with Trellis. This as Trellis runs Vagrant which runs VirtualBox. Very heavy all that while you could use Laravel Valet which uses the resources your Mac has and only needs some extras using Homebrew.

Trellis Valet Driver

Now, there is a package you can use to keep the site and trellis directories and run your Bedrock based site in Laravel. It is called the Trellis Valet Driver . You simply download the driver and add it to ~/.config/valet/Drivers . Then you do a valet restart and link the site directory.

Issues

Once I added this driver I did a valet restart and then I had some issues. I did a

valet link in root project

and then removed it as that did not work and should not. So I did it in the site folder where the site files and Bedrock reside. But I only got a timeout

This site can’t be reached

and no Nginx Users/jasper/.config/valet/Log/nginx-error.log"; or PHP FPM logs at /usr/local/var/log/php-fpm.log referring to it. Then I started wondering if vagrant suspend did remove the hosts file data too. It did not so I commented it

## vagrant-hostmanager-start id: 0b926f9c-843a-4a87-9b5f-e79a1ae5ba23
# 192.168.50.36    site.test
# 192.168.50.36    www.site.test
## vagrant-hostmanager-end

Once I did that I started having a database connection issue

Warning: mysqli_real_connect(): (HY000/1045): Access denied for user 'user'@'localhost' (using password: YES) in /Users/jasper/code/site.com/site/web/wp/wp-includes/wp-db.php on line 1531

So I knew it was working. Just hadn’t added the database to the Homebrew MariaDB SQL server.

Trellis NET::ERR_CERT_REVOKED

Since my new MacOS Catalina setup I bumped into multiple Trellis issues. Issues with loading the proper interpretor as well as loading the proper Ansible version for the setup. Final issue I had now was dealing with failed SSL certificates and to be more specific NET::ERR_CERT_REVOKED .

NET::ERR_CERT_REVOKED

So how to deal with this NET::ERR_CERT_REVOKED error? Let’s first look at the error in more detail. We have the following:

Your connection is not private
Attackers might be trying to steal your information from domain.test (for example, passwords, messages, or credit cards). Learn more
NET::ERR_CERT_REVOKED
Subject: domain.test
Issuer: domain.test
Expires on: Nov 14, 2029
Current date: Nov 17, 2019
PEM encoded chain:-----BEGIN CERTIFICATE-----
MIIC7TCCAdWgAwIBAgIUYZ8d88npaf4YadiK/tt5dmovK5MwDQYJKoZIhvcNAQEL
BQAwGDEWMBQGA1UEAwwNYnJpY2ttYWcudGVzdDAeFw0xOTExMTcwNzU2NTBaFw0y
OTExMTQwNzU2NTBaMBgxFjAUBgNVBAMMDWJyaWNrbWFnLnRlc3QwggEiMA0GCSqG
------------------
LI3sU31VNnyMaq31seU+FdZjBO7LS0n4u1Rv5FKhsGC6goozx0LhVsHG659HJbMn
i0Yd3C1rl+DJClQS9LVCNzkXVrvJtwLI8LLraUmLJYWTC5cfGuL7C/J0dzqj48uG
6Vzouywoceuy5aVXvgV3cxOi9vkC2e8idkd28JvHEQ4s
-----END CERTIFICATE-----

So somehow the certificate is being revoked . This either has to do with Catalina or Chrome being stricter on local self signed certificates with locally generated authorities. So what to do about this?

Trellis Cert Trust Vagrant Plugin

Well after some Googling I bumped into this Github thread on the same SSL certificate issue. And I found out that there is a Vagrant plugin that can deal with the browser no longer accepting locally set certificates as it did before. Simply install it and run it inside the trellis directory:

vagrant plugin install vagrant-trellis-cert
vagrant trellis-cert trust

And yes, now you can run the site again with the generated SSL certificate and now you are no longer blocked by the browser!

Trellis ‘PlaybookCLI’ object has no attribute ‘options’

With latest Ansible we had an issue starting up / provisioning vagrant properly. It would not provision properly stating there was an attribute called options missing.

Attribute Options Errors

Got the following error doing a

ansible reload --provision
ERROR! Unexpected Exception, this is probably a bug: 'PlaybookCLI' object has no attribute 'options'

Suggestion at Roots Discourse was to downgrade Ansible to < 2.8 using

sudo pip uninstall ansible 
sudo pip install ansible==2.7

And although that is again not an ideal solution it is the solution if you do not want to upgrade Trellis and or work with Python 3 as discussed at this blog post.

Trellis Ansible Bad Interpreter Error

Recently upgraded to MacOS Catalina and zsh shell. Since the upgrade it seems we have Python 3 and no longer 2.x so to work with older Trellis setups using Python 2.7 you need a fix

Bad Interpreter Error

Error we got using Ansible was a bad interpreter error. Python 2.7 is not to be found:

zsh: /usr/local/bin/ansible-vault: bad interpreter: /usr/local/opt/python@2/bin/python2.7: no such file or directory

And that was correct because when we checked /usr/local/opt we only had Python 3.

Brew Installation Python 2

So we did a

brew install python@2

to install Python 2 as an option besides the natively installed Python 3. This took quite some time as we had not updated Brew in a long time and as compilation was done from source

➜  trellis git:(master) brew install python@2
 Updating Homebrew…
 ==> Auto-updated Homebrew!
 Updated 4 taps (heroku/brew, homebrew/core, homebrew/cask and homebrew/services).
 ==> New Formulae
 adios2                     git-delta                  mpv                        pnpm
 alp                        gleam                      mysql-connector-c++@1.1    prestosql
 appium                     gmt@5                      navi                       pylint
 arduino-cli                govc                       nbdime                     tdkjs
 atasm                      grin                       ngt                        tektoncd-cli
 awsume                     grin-wallet                node@12                    toast
 bingrep                    helm@2                     notifiers                  trader
 calceph                    javacc                     numpy@1.16                 ttyplot
 cf-tool                    jd                         oauth2l                    tweak
 comby                      kyma-cli                   onefetch                   virustotal-cli
 cups                       libtensorflow@1            openjdk                    wagyu
 dafny                      manticoresearch            openjdk@11                 wal2json
 diffr                      minikube                   openjdk@12                 xgboost
 dvc                        mpi4py                     pnetcdf
 ==> Updated Formulae
 aspell ✔                            gitless                             pdf2htmlex
 cmake ✔                             gitmoji                             pdf2json
 curl ✔                              gitup                               pdfcpu
 curl-openssl ✔                      gitversion                          pdftk-java
 git ✔                               gjs                                 pdftoipe
 glib ✔                              glade                               percona-server
 gnutls ✔                            glances                             percona-xtrabackup
 heroku/brew/heroku ✔                glib-networking                     perltidy
 heroku/brew/heroku-node ✔           glooctl                             petsc
 iproute2mac ✔                       gmic                                petsc-complex
 kubernetes-cli ✔                    gmime                               pgbadger
 libtiff ✔                           gmt                                 pgcli
 libxml2 ✔                           gnome-builder                       pgformatter
 mariadb ✔                           gnome-latex                         pgrouting
 node ✔                              gnome-recipes                       pgweb
 node@10 ✔                           gnumeric                            phoronix-test-suite
 p11-kit ✔                           gnunet                              php-code-sniffer
 php ✔                               gnuradio                            php-cs-fixer
 python ✔                            go                                  php@7.1
 redis ✔                             go-bindata                          php@7.2
 sqlite ✔                            go@1.12                             phpmyadmin
 terraform ✔                         gobby                               phpstan
 tor ✔                               gocryptfs                           phpunit
 yarn ✔                              godep                               picard-tools
 abcm2ps                             goffice                             pioneer
 ........
 gifski                              osm-gps-map                         zbar
 ginac                               osmium-tool                         zint
 git-annex                           osqp                                zita-convolver
 git-archive-all                     osquery                             znc
 git-cola                            ott                                 zola
 git-quick-stats                     oxipng                              zookeeper
 git-revise                          packer                              zrepl
 git-town                            pagmo                               zsh-completions
 gitfs                               paket                               zsh-history-substring-search
 gitg                                pango                               zshdb
 gitlab-gem                          parallel                            zydis
 gitlab-runner                       pastel
 gitleaks                            pdal
 ==> Renamed Formulae
 jupyter -> jupyterlab      kubernetes-helm -> helm    presto -> prestodb         usbmuxd -> libusbmuxd
 ==> Deleted Formulae
 aiccu             dcal              gmtl              mariadb@10.0      pound             riak
 bdsup2sub         erlang@17         hana              mysql@5.5         protobuf@3.1      supersonic
 cockroach         gmt@4             llvm@4            pbrt              raine             wine
 ==> minikube has been moved to Homebrew.
 To uninstall the cask run:
   brew cask uninstall --force minikube
 ==> Installing minikube…
 ==> Installing dependencies for minikube: kubernetes-cli
 ==> Installing minikube dependency: kubernetes-cli
 ==> Downloading https://homebrew.bintray.com/bottles/kubernetes-cli-1.16.3.catalina.bottle.tar.gz
 ==> Downloading from https://akamai.bintray.com/0f/0ffd9ee9bb5026ae526a09fe50591a94a1a987f52c5866e32aefb79b
 ################################################################## 100.0%
 ==> Pouring kubernetes-cli-1.16.3.catalina.bottle.tar.gz
 ==> Caveats
 Bash completion has been installed to:
   /usr/local/etc/bash_completion.d
 zsh completions have been installed to:
   /usr/local/share/zsh/site-functions
 ==> Summary
 🍺  /usr/local/Cellar/kubernetes-cli/1.16.3: 232 files, 52.3MB
 ==> Installing minikube
 ==> Downloading https://homebrew.bintray.com/bottles/minikube-1.5.2.catalina.bottle.tar.gz
 ==> Downloading from https://akamai.bintray.com/6c/6cab6126b65a45912587339eddf252729e4ad16b4bfd327454204b46
 ################################################################## 100.0%
 ==> Pouring minikube-1.5.2.catalina.bottle.tar.gz
 ==> minikube cask is installed, skipping link.
 ==> Caveats
 Bash completion has been installed to:
   /usr/local/etc/bash_completion.d
 zsh completions have been installed to:
   /usr/local/share/zsh/site-functions
 ==> Summary
 🍺  /usr/local/Cellar/minikube/1.5.2: 8 files, 51.5MB
 ==> brew cleanup has not been run in 30 days, running now…
 Removing: /Users/jasper/Library/Caches/Homebrew/aspell--0.60.7.mojave.bottle.tar.gz… (115.6MB)
 Removing: /Users/jasper/Library/Caches/Homebrew/curl-openssl--7.66.0.mojave.bottle.tar.gz… (1MB)
 Removing: /Users/jasper/Library/Caches/Homebrew/glib--2.62.0_1.mojave.bottle.tar.gz… (4.5MB)
 Removing: /usr/local/Cellar/kubernetes-cli/1.10.1… (178 files, 52.8MB)
 Removing: /Users/jasper/Library/Caches/Homebrew/php--7.3.10.mojave.bottle.tar.gz… (19.9MB)
 Removing: /Users/jasper/Library/Caches/Homebrew/python--3.7.4_1.mojave.bottle.tar.gz… (14.6MB)
 Removing: /Users/jasper/Library/Caches/Homebrew/python--3.7.4.mojave.bottle.tar.gz… (14.6MB)
 Removing: /Users/jasper/Library/Caches/Homebrew/sqlite--3.29.0.mojave.bottle.tar.gz… (1.9MB)
 Removing: /Users/jasper/Library/Logs/Homebrew/nghttp2… (64B)
 ......
 Removing: /Users/jasper/Library/Logs/Homebrew/openssl@1.1… (64B)
 Removing: /Users/jasper/Library/Logs/Homebrew/openldap… (64B)
 Removing: /Users/jasper/Library/Logs/Homebrew/libev… (64B)
 Pruned 17 symbolic links and 1 directories from /usr/local
 ==> Caveats
 ==> kubernetes-cli
 Bash completion has been installed to:
   /usr/local/etc/bash_completion.d
 zsh completions have been installed to:
   /usr/local/share/zsh/site-functions
 ==> minikube
 Bash completion has been installed to:
   /usr/local/etc/bash_completion.d
 zsh completions have been installed to:
   /usr/local/share/zsh/site-functions
 Linking /usr/local/Cellar/minikube/1.5.2… 3 symlinks created
 ==> Installing dependencies for python@2: sqlite
 ==> Installing python@2 dependency: sqlite
 ==> Downloading https://homebrew.bintray.com/bottles/sqlite-3.30.1.catalina.bottle.tar.gz
 ==> Downloading from https://akamai.bintray.com/38/38c39121f7634ec563bb201b483f66cf567dfe61e02624ffb06f620f
 ################################################################## 100.0%
 ==> Pouring sqlite-3.30.1.catalina.bottle.tar.gz
 ==> Caveats
 sqlite is keg-only, which means it was not symlinked into /usr/local,
 because macOS provides an older sqlite3.
 If you need to have sqlite first in your PATH run:
   echo 'export PATH="/usr/local/opt/sqlite/bin:$PATH"' >> ~/.zshrc
 For compilers to find sqlite you may need to set:
   export LDFLAGS="-L/usr/local/opt/sqlite/lib"
   export CPPFLAGS="-I/usr/local/opt/sqlite/include"
 For pkg-config to find sqlite you may need to set:
   export PKG_CONFIG_PATH="/usr/local/opt/sqlite/lib/pkgconfig"
 ==> Summary
 🍺  /usr/local/Cellar/sqlite/3.30.1: 11 files, 3.9MB
 ==> Installing python@2
 Warning: Building python@2 from source:
   The bottle needs the Apple Command Line Tools to be installed.
   You can install them, if desired, with:
     xcode-select --install
 ==> Downloading https://www.python.org/ftp/python/2.7.17/Python-2.7.17.tar.xz
 ################################################################## 100.0%
 ==> ./configure --prefix=/usr/local/Cellar/python@2/2.7.17 --enable-ipv6 --datarootdir=/usr/local/Cellar/py
 ==> make
==> make install PYTHONAPPSDIR=/usr/local/Cellar/python@2/2.7.17
 ==> make frameworkinstallextras PYTHONAPPSDIR=/usr/local/Cellar/python@2/2.7.17/share/python@2
 ==> Downloading https://files.pythonhosted.org/packages/f4/d5/a6c19dcbcbc267aca376558797f036d9bcdff344c9f78
 ################################################################## 100.0%
 ==> Downloading https://files.pythonhosted.org/packages/ce/ea/9b445176a65ae4ba22dce1d93e4b5fe182f953df71a14
 ################################################################## 100.0%
 ==> Downloading https://files.pythonhosted.org/packages/59/b0/11710a598e1e148fb7cbf9220fd2a0b82c98e94efbdec
 ################################################################## 100.0%
 ==> /usr/local/Cellar/python@2/2.7.17/bin/python -s setup.py --no-user-cfg install --force --verbose --sing
 ==> /usr/local/Cellar/python@2/2.7.17/bin/python -s setup.py --no-user-cfg install --force --verbose --sing
 ==> /usr/local/Cellar/python@2/2.7.17/bin/python -s setup.py --no-user-cfg install --force --verbose --sing
 ==> Caveats
 Pip and setuptools have been installed. To update them
   pip install --upgrade pip setuptools
 You can install Python packages with
   pip install 
 They will install into the site-package directory
   /usr/local/lib/python2.7/site-packages
 See: https://docs.brew.sh/Homebrew-and-Python
 ==> Summary
 🍺  /usr/local/Cellar/python@2/2.7.17: 6,645 files, 91.4MB, built in 4 minutes 16 seconds
 ==> Caveats
 ==> sqlite
 sqlite is keg-only, which means it was not symlinked into /usr/local,
 because macOS provides an older sqlite3.
 If you need to have sqlite first in your PATH run:
   echo 'export PATH="/usr/local/opt/sqlite/bin:$PATH"' >> ~/.zshrc
 For compilers to find sqlite you may need to set:
   export LDFLAGS="-L/usr/local/opt/sqlite/lib"
   export CPPFLAGS="-I/usr/local/opt/sqlite/include"
 For pkg-config to find sqlite you may need to set:
   export PKG_CONFIG_PATH="/usr/local/opt/sqlite/lib/pkgconfig"
 ==> python@2
 Pip and setuptools have been installed. To update them
   pip install --upgrade pip setuptools
 You can install Python packages with
   pip install 
 They will install into the site-package directory
   /usr/local/lib/python2.7/site-packages
 See: https://docs.brew.sh/Homebrew-and-Python

Python Crashing Hard

Next, on Ansible version check I got another error

➜  trellis git:(master) ansible --version    
[1]    19153 abort      ansible --version

It was somehow crashing Python 2.7 though it should just work with it. I decided to upgrade Ansible as well

➜  ~ sudo pip install ansible --upgrade
.....
Requirement already satisfied, skipping upgrade: six>=1.4.1 in /usr/local/lib/python2.7/site-packages (from cryptography->ansible) (1.11.0)
 Requirement already satisfied, skipping upgrade: pycparser in /usr/local/lib/python2.7/site-packages (from cffi>=1.7; platform_python_implementation != "PyPy"->cryptography->ansible) (2.18)
 Installing collected packages: ansible
   Found existing installation: ansible 2.7.5
     Uninstalling ansible-2.7.5:
       Successfully uninstalled ansible-2.7.5
 Successfully installed ansible-2.9.1

Still I had the Python error and iTerm was showing a MacOS popup that Python was crashing unexpectedly:

Python quit unexpectedly.
Click Reopen to open the application again. Click Report to see more detailed information and send a report to Apple.
Application Specific Information:
 /usr/lib/libcrypto.dylib
 abort() called
 Invalid dylib load. Clients should not load the unversioned libcrypto dylib as it does not have a stable ABI.

Invalid DyLib

Found https://stackoverflow.com/questions/58272830/python-crashing-on-macos-10-15-beta-19a582a-with-usr-lib-libcrypto-dylib on the error with the Dynamic library loaded being the wrong one and decided to install openssl

brew install openssl

But it was already installed so no need for that. So then I added this line to .zshrc to load the correct library inside zsh:

# Python crash fix
export DYLD_LIBRARY_PATH=/usr/local/opt/openssl/lib:$DYLD_LIBRARY_PATH

All Working

And then restarted iTerm and once I had done that I was in the clear

➜  ~ ansible --version
ansible 2.9.1
config file = None
configured module search path = [u'/Users/jasper/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/lib/python2.7/site-packages/ansible
executable location = /usr/local/bin/ansible
python version = 2.7.17 (default, Nov 17 2019, 10:31:11) [GCC 4.2.1 Compatible Apple LLVM 11.0.0 (clang-1100.0.33.12)]

I was also able to view the encrypted content again using

ansible-vault view group_vars/all/vault.yml

Will definitely upgrade the Trellis package soon so I can work with Python 3 as next year Python 2 will be completely abandoned. But for now I can work with this setup.

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.