Advanced Custom Fields in Sage

Sometimes you need to work with custom fields for a theme. This as you work with a template that consists of many different content fields and that tend to need different layouts too. And sometimes you do not feel like doing it all from scratch because you are lazy, or because you simply have more to do. Then Advanced Custom Fields or ACF is your friend. Simply the best GUI plugin to deal with these pesky meta boxes. So how do you put Advanced Custom Fields in Sage?

Adding ACF to Sage

Just add the entire folder inside the theme root and call it acf. If you pick another directory name make sure to change stuff accordingly here below. ACF as a name is nice and sweet and is what the guys at Advanced Custom Fields use as well.

advanced custom fields directory


Loading Advanced Custom Fields in Sage

Well just like in any other theme you would use the snippet suggested by ACF here. Only you would need to use namespaces like you would for working with actions and filters in general as Sage works with those. So after adjusting the snippet you would have:


// 1. customize ACF path
add_filter('acf/settings/path', __NAMESPACE__. '\my_acf_settings_path');
function my_acf_settings_path( $path ) {
 // update path
 $path = get_stylesheet_directory() . '/acf/';
 // return
 return $path; 
// 2. customize ACF dir
add_filter('acf/settings/dir', __NAMESPACE__ . '\my_acf_settings_dir');
function my_acf_settings_dir( $dir ) {
 // update path
 $dir = get_stylesheet_directory_uri() . '/acf/';
 // return
 return $dir; 
// 3. Hide ACF field group menu item
//add_filter('acf/settings/show_admin', '__return_false');
// 4. Include ACF
include_once( get_stylesheet_directory() . '/acf/acf.php' );

this snippet I would add to extras.php which is automatically included in the functions.php. Best place for custom code besides custom libraries or the customizer for which new files / directories would be better.

Well, once all that is done and you reload the admin you will see you are up and running. Go ahead and create your custom fields and start adding them to your pages or templates. Have fun!

NB You can uncomment hiding AC in admin for live sites where you do not want clients to play with it.

Custom PHP Settings in Trellis

Often when you set up a Trellis server you find out your PHP settings are not good enough for the WordPress app you are building. Simply because you run a plugin like WooCommerce that needs more than the default 96MB PHP memory. Or because you are a fan of a slider like Revolution Slider that needs a larger upload max. file size or larger max. post size than the standard 25M. So how do you deal with custom PHP settings in Trellis?

Default PHP Values Trellis

The default values for PHP in Trellis are currently:

disable_default_pool: true
 memcached_sessions: false

php_error_reporting: 'E_ALL & ~E_DEPRECATED & ~E_STRICT'
 php_display_errors: 'Off'
 php_display_startup_errors: 'Off'
 php_max_execution_time: 120
 php_max_input_time: 300
 php_max_input_vars: 1000
 php_memory_limit: 96M
 php_mysqlnd_collect_memory_statistics: 'Off'
 php_post_max_size: 25M
 php_sendmail_path: /usr/sbin/ssmtp -t
 php_session_save_path: /tmp
 php_upload_max_filesize: 25M
 php_track_errors: 'Off'
 php_default_timezone: '{{ default_timezone }}'

php_opcache_enable: 1
 php_opcache_enable_cli: 1
 php_opcache_fast_shutdown: 1
 php_opcache_interned_strings_buffer: 8
 php_opcache_max_accelerated_files: 4000
 php_opcache_memory_consumption: 128
 php_opcache_revalidate_freq: 60

php_xdebug_remote_enable: "false"
 php_xdebug_remote_connect_back: "false"
 php_xdebug_remote_host: localhost
 php_xdebug_remote_port: "9000"
 php_xdebug_remote_log: /tmp/xdebug.log
 php_xdebug_idekey: XDEBUG
 php_max_nesting_level: 200

NB You can always check the latest setup for it here at the Trellis repository

This main.yml file is located at roles /php/defaults/main.yml . Based upon it Trellis sets up your PHP.ini values for you.

Setting Custom PHP Settings in Trellis

So what I normally do is that I change three options to have the following values:

php_memory_limit: 256M
php_post_max_size: 32M
php_upload_max_filesize: 32M

This to run WooCommerce well and allow Revolution Slider to work well with larger files and PHP post_max_size. Sometimes I also change the maximum execution time:

php_max_execution_time: 120

to a value double the size. This if I need more time for the execution of certain scripts.

Updating Trellis

Now that you have set your own new values you will also have to re-provision your Trellis server. To do this on production use:

ansible-playbook server.yml -e env=production

To do it locally on your Vagrant box you just have to run

vagrant provision

WordPress FYI

WooCommerce requires at least 256M memory. To obtain this it also states the following code snippet:

define('WP_MEMORY_LIMIT', '256M');

needs to be added to wp-config.php. This besides making sure your server allows this via php.ini or a .htaccess for example. Why? Because you need to tell WordPress to actually use the 256M of RAM. WordPress allocates 40MB for single setups by default only.

Read more about this in the WordPress Codex here.

Contact Form 7 Mail From Field – How to Set it Properly

Every time I use Contact Form 7 I fall for it. I commit the same error over and over again. I enter the wrong data for the from field on the mail tab. And every time it takes me precious time figuring out this silly error. And it is isn’t correctly told in the documentation either I am afraid. So let’s write it down here so we all remember how to properly configure the Contact Form 7 Mail From Field.

Form Data

Let’s say you create a standard form like so:

<label> Your Name (required)
 [text* your-name size:30 maxlength:60]</label>

<label> Your Email (required)
 [email* your-email]</label>

<label> Your Phone Number
[tel your-tel "012345678"]</label>

<label> Subject
 [text your-subject]</label>

<label>Your Message
 [textarea your-message] </label>

[submit "Send"]

Mail Tab Data

You also add all this data to the mail tab:


NB localhost domain




iauna "[your-subject]"

additional headers:

Reply-To: [your-email]

message body:

From: [your-name] <[your-email]>
Subject: [your-subject]

Message Body:

This e-mail was sent from a contact form on Iauna (//localhost:3000/ianua/site/web)

You check all and see all is well. Well, you think..

Configuration Error Found

Then you save the form and see:


What? So you go to Contact Form 7 Documentation to resolve configuration errors and see nothing that is wrong. Zilch! So you check all again. You decide to go to the mail tab and you see:

This email address does not belong to the same domain as the site.


But…but the your-email tag should load the email address of the client right? It says so here in the documentation that you can. Nope. It is the one connected to the domain. It should be the email address you use to send email to clients. As DavidFB said here:

On the Form, [your-email] is the form fillers address.
On the Mail tab, the settings are for Routing the mail. To is who you want the form data to go to and From is the address SENDing the form data. In this case, it’s not actually the person sending, it’s the web site. So From should match the server domain so it’s not seen by mail servers as spoofing or relaying.

So what you need to add is:

Company Name<name@domain.tld>

After that all will be well. Emails will be sent properly and clients will be happy. Let’s hope we can all avoid this mistake the next time!


Do not forget to add the additional headers:

Reply-To: [your-email]

This will make it loads easier to reply to clients when they email you. Saves you time copying their address from the body of the email!

Let us Set things up for you

[product id=”6974″]

Slider Revolution Slide could not be loaded – Possible Fix

Recently I updated my main slideshow on the homepage. I use Slider Revolution. It is awesome and goes well with the theme I use. For most themes I use really. It is a very versatile slider plugin. But this time around I bumped into an issue I did not see until my CDN loaded the new image. A Slider Revolution Slide could not be loaded.

Slider Revolution Slide could not be loaded

Somehow Slider Revolution did not appreciate the new first slide being 974KB. This caused the slider plugin not to load that slide properly. Here the error in the Chrome inspector console I found:

jquery.themepunch.revolution.min.js?ver=5.2.6:8 Could not be loaded !

This error caused a huge white hole on my homepage! I talked to MaxCDN to figure out why the image would not be loaded from their side. They figured it was a JavaScript issue. So not a CDN issue. This as the asset or image could be loaded from their end and from my VPS. And in the end it was. It was not the new live chat which I was testing though. I thought it was because it got added recently.

Slider Revolution Issues

Slider Revolution somehow blocked the loading. I realized this checking the Performance and SEO Optimization tab for that slider.

Performance and SEO Optimization tab

It said there was an issue with my first slide. I decided to optimize it some more with Imagify. The slider plugin somehow still loaded the old slide worth 974KB by itself. That is no longer the case in the screenshot above where the total of the 3 slides is only 379.53 KB. Afterwards it was lighter and loaded well. The warning went away too.

SEO Sidenote

I do know SEO wise many out there are not big fans of sliders. But I do still think when you do offer something on the slides like the initial one does and the titles of services on the others slides can contribute to the whole UX. Also make sure you do not slide right away or not too quickly at least. It annoys repeat visitors. And of course do cache things well so visitors do not have to wait too long.

Furthermore, do remember, the eye wants something too and that it provides. I do see a trend popping up every now and then where sites are told to be stripped to the bare minimum and drop slideshows and large images. People are not bots people!

Slider Revolution still a keeper

Finally, the enormous options Slider Revolution offers are incredible. They way you can display slides, effects you can choose and control over desktop, tablet and mobile you have are simply terrific. Too bad the issue was not clear for me from the start. And, perhaps, Slider Revolution should tell me clearer when an added image causes issues.. Still, happy user of SR and not going away anytime soon.

Anyways, that is that. I hope this helps others!

Need help setting things up?

[product id=”7324″]

Changing Site URL with WP-CLI

If you quickly need to change the WordPress site url on a VPS or dedicated server with WP CLI installed or installable WP-CLI is your friend. It is a great command line based option dealing with url changes during site migration. There are of course great plugins to help out too, but believe me with full server access or VPS access a command line tool like wp-cli can deal with the url issues in no time!

Development to Production

If you just need to move the database from development to production as you will go live with the content you can do this. You replace the database on the production server with the one on the development box. Then you use WP-CLI and then change the urls in a jiffy. Just ssh into your production server and run the search-replace command like this:

wp search-replace '' '' --skip-columns=guid

Afterwards all will work well. All content will be loaded properly with the right urls. Images should be loaded too as long as you moved those to the server as well. I do this often for all new Trellis LEMP box setups at Digital Ocean and I can tell you it works really well. A lot better than the WordPress Importer tool from the Dashboard. Though the importer does work quite well from the command line you still need to deal with images.

Dry Run – Better Safe than Sorry

You can also do a dry run first with –dry-run added. This to see what urls will be changed and to make sure you are running the correct command. So the command will then be

wp search-replace '' '' --skip-columns=guid --dry-run

Actually pretty smart to use. Especially if you are talking a lot data and or complicated url. Better safe than sorry. Will safe you restoring it all with or without backup.

Backup, backup!

Do remember it is always good to backup the database before you do this. Just in case you do not fill in the urls properly once you run things live. Sequel Pro is a great OSX database management tool for backups, changes and replacements of databases.

There is more..

You can do way more with wp-cli than just searching and replacing urls. You can set up full WordPress installations, install themes, plugins, add fields, backup setups and more. I will be sure to write some other applications again as soon as I have the time. Stay tuned!

Sage Theme Sidebars – How to set sidebars up properly in Sage

Sage Theme Sidebars are WordPress sidebars set up DRY using the theme’s wrapper. Basically sidebars done the right way. You will need to add the code to register a new sidebar and add the code to the theme file where you want the sidebar to be loaded. Just like you would normally add a sidebar to a regular theme. There are however a few things you need to look at. These include:

  • the location to add the various code snippets,
  • namespaces and the
  • Sage specific loading and hiding of the general sidebar.

OK let’s dive into the matter and take it a step at the time.

Registering Sidebars in Sage

In your theme folder under theme/lib you can find the file setup.php. It combines what used to be called config.php and init.php. It basically is a config file containing code to add thumbnail sizes, sidebar, menus, theme assets as well as some tweaks when you use soil. Inside this file you will find this piece of code:

function widgets_init() {

 'name' => __('Primary', 'sage'),
 'id' => 'sidebar-primary',
 'before_widget' => '<section class="widget %1$s %2$s">',
 'after_widget' => '</section>',
 'before_title' => '<h3>',
 'after_title' => '</h3>'
 'name' => __('Footer', 'sage'),
 'id' => 'sidebar-footer',
 'before_widget' => '<section class="widget %1$s %2$s">',
 'after_widget' => '</section>',
 'before_title' => '<h3>',
 'after_title' => '</h3>'
add_action('widgets_init', __NAMESPACE__ . '\\widgets_init')

This is where Sage registers its sidebars. Here you could copy the code of one of the Sage theme sidebars and customize it to your liking. The action to add these sidebars uses coding you may not be familiar with as Sage uses namespaces which are available since PHP 5.3. Really useful to organize code the way you organize files in folders.

Loading Setup File

Also, as you may have noticed, the sidebars are not added to functions.php, but to a separate file. In functions.php this file is loaded with the following array:

$sage_includes = [
 'lib/assets.php', // Scripts and stylesheets
 'lib/extras.php', // Custom functions 
 'lib/media.php', // Media functions
 'lib/setup.php', // Theme setup
 'lib/titles.php', // Page titles
 'lib/wrapper.php', // Theme wrapper class
 'lib/customizer.php', // Theme customizer 
 'lib/widget.php' // Theme customizer

Sage does it this way to not overload the functions.php with all code and organize all more logically. And I like it. Otherwise I would not be using this awesome starters theme in the first place.

NB widgets_init() might be confusing here as it is when you start out with sidebars or asides. But sidebars load widgets after all so that is the function we need and action we use to load all the registered sidebars.

Displaying the General Sidebars

Displaying the sidebars is just like with any other sidebar. You could use the code:

<?php if ( dynamic_sidebar('example_widget_area_name') ) : else : endif; ?>

in any theme file for example. However, Sage does apply the DRY principle and does not like to repeat itself. So it uses base.php to load all code that is used cross files to load template files with or without a general sidebar (vertical one). Here a snippet from that bare file:

<div class="wrap container" role="document">
 <div class="content row">
 <main class="main">
 <?php include Wrapper\template_path(); ?>
 </main><!-- /.main -->
 <?php if (Setup\display_sidebar()) : ?>
 <aside class="sidebar">
 <?php include Wrapper\sidebar_path(); ?>
 </aside><!-- /.sidebar -->
 <?php endif; ?>
 </div><!-- /.content -->
 </div><!-- /.wrap -->

As you can see it determines whether or not the sidebar should be loaded. This being the general sidebar.

Sidebars File

And then the base.php file  load the sidebars from templates/sidebar.php. It does this using the sidebar_path() function. That function is inside lib.wrapper.php and states:

function sidebar_path() {
 return new SageWrapping('templates/sidebar.php');

This loads a new instance of SageWrapping with the sidebar file. I could write a whole section on the wrapper, but I won’t here to keep this article within bounds. More on the Sage wrapper here. In that file sidebar.php you will initially find only:

<?php dynamic_sidebar('sidebar-primary'); ?>

This is the main sidebar that is loaded selectively using the soon to be mentioned display_sidebar() function. One for my theme in progress has:

 <?php if (!is_woocommerce()) : ?>
 <?php dynamic_sidebar('sidebar-primary'); ?>
 <?php endif; ?> 
 <?php if (taxonomy_exists('product_cat')) : ?>
 <?php dynamic_sidebar('shop-main'); ?>
 <?php endif; ?>

As you can see I added some WooCommerce specific code for loading Sage theme sidebars for the shop part of things.

Hiding the Main Sidebar

Sage has an added function / filter you can easily use to show or hide the general sidebar that comes with the theme. It is inside the file lib/setup.php:

 * Determine which pages should NOT display the sidebar
function display_sidebar() {
 static $display;
 isset($display) || $display = !in_array(true, [
 // The sidebar will NOT be displayed if ANY of the following return true.
 // @link
 return apply_filters('sage/display_sidebar', $display);

You just add the WordPress conditional tag of the page, post, custom post type you do not want to see loaded with a sidebar et voila!

Under extras.php you can see a class is added when the above mentioned array does not contain the conditional posts/pages/custom post types:

 // Add class if sidebar is active
 if (Setup\display_sidebar()) {
 $classes[] = 'sidebar-primary';

This as most pages either have a sidebar or aside or do not. Again, this is not relevant for footer or header sidebars or special sidebar areas.

Other Widgetized Areas

Other sidebars that are not content sidebars or asides loaded left or right of the main content are loaded in template files like they would normally be. They are normally added to the template parts where they should be loaded like the footer sidebar which is loaded from a template part using the following snippet in the base.php file:


This as they will not be part of the display_sidebars function as they are not content asides. In the footer template_part you will find:

<footer class="content-info">
 <div class="container">
 <?php dynamic_sidebar('sidebar-footer'); ?>

which loads the sidebar-footer. For most of our themes we have at least four footer widget areas or sidebars.


Well, that’s it. Now you know how Sage loads content sidebars or asides and other widgetized areas. You also know a bit more about the Sage theme wrapper used to keep things DRY. This to make sure we do not repeat ourselves. In the upcoming posts I will talk some more about the wrapper as well as creating template parts and general Sage theme development.

Keeping your Trellis Server Updated

When you manage client servers with Trellis you will every now and then have to update the server. This to keep Ubuntu, NGINX, MariaDB and PHP updated. But also to patch security holes and update the latest Linux libraries. So keeping your Trellis server updated is pretty important. And fortunately with Trellis it is all pretty straight forward.

Local Server Update

When there is a new update of the Ubuntu Bento Box you will see that when you start your Vagrant Box from the command line:

==> default: Removing hosts
==> default: Checking if box 'bento/ubuntu-16.04' is up to date...
==> default: A newer version of the box 'bento/ubuntu-16.04' is available! You currently
==> default: have version '2.2.9'. The latest is version '2.3.0'. Run
==> default: `vagrant box update` to update.

I really love that. No need to ssh into your box to do updates unless you want specific tweaks. Just keep up with the latest Bento Box updates and you should be just fine in the majority of the cases.

You can do a local update using the following command:

vagrant box update

This will then download the latest Bento Box from the Hashicorp Atlas Server and update your Vagrant Box. Bento is a great box based on an encapsulated Hashicorp Packer template for Vagrant Boxes maintained by the Chef team. The repo is here.

When all goes well you will see a message like:

==> default: Successfully added box 'bento/ubuntu-16.04' (v2.3.0) for 'virtualbox'!

If something did go wrong you will see a warning and or errors. Often this means you have to do it all again. From my location that will mean you will be spending another 20-30 minutes. So keep your fingers crossed at all times!

Remote Staging or Production Server Update

To update your live or production server or your staging server – if you use one – you will need to fire the following command:

ansible-playbook server.yml -e env=<environment>

This command is for provisioning a server, but also for updating the Trellis server. It is an Ansible playbook as Trellis is basically a set of playbooks to manage your server locally as well as remotely. To do this, this means you have to open the terminal, go to the trellis folder and type in

ansible-playbook server.yml -e env=production

NB server.yml can be viewed here

for example to update the production box. Do remember the remote site will not be accessible or fully accessible when you do this. So make sure you do this during slow traffic hours. Sometimes the update does not go well. Due to connection issues, issues with the Let’s Encrypt certificates or other issues. Run it again and all should work out well in the end. If things do go well you will see the following:

PLAY RECAP *********************************************************************               : ok=102  changed=6    unreachable=0    failed=0   

localhost                  : ok=0    changed=0    unreachable=0    failed=0   

So as you can see it all went well and six packages were changed:

TASK [fail2ban : ensure fail2ban is configured] ********************************
changed: [] => (item=jail.local)
ok: [] => (item=fail2ban.local)

TASK [ferm : ensure iptables INPUT rules are added] ****************************
ok: [] => (item={u'dport': [u'http', u'https'], u'type': u'dport_accept', u'filename': u'nginx_accept'})
changed: [] => (item={u'dport': [u'ssh'], u'type': u'dport_accept', u'saddr': [u'']})
ok: [] => (item={u'dport': [u'ssh'], u'seconds': 300, u'hits': 20, u'type': u'dport_limit'})

TASK [users : Setup users] *****************************************************
changed: [] => (item={u'keys': [u'ssh-rsa 
key==', u''], u'name': u'web', u'groups': [u'www-data']})
changed: [] => (item={u'keys': [u'ssh-rsa 
keyw==', u''], u'name': u'dhc-user', u'groups': [u'sudo']})

TASK [users : Add SSH keys] ****************************************************
changed: [] => (item=({u'name': u'web', u'groups': [u'www-data']}, u'ssh-rsa A
changed: [] => (item=({u'name': u'web', u'groups': [u'www-data']}, u''))
changed: [] => (item=({u'name': u'dhc-user', u'groups': [u'sudo']}, u'ssh-rsa 
changed: [] => (item=({u'name': u'dhc-user', u'groups': [u'sudo']}, u''))

TASK [mariadb : Add MariaDB MySQL deb and deb-src] *****************************
ok: [] => (item=deb trusty main)
ok: [] => (item=deb-src trusty main)
TASK [mariadb : Restart MariaDB MySQL Server] **********************************
changed: []

RUNNING HANDLER [fail2ban : restart fail2ban] **********************************
changed: []

In this case keys were changed, MariaDB upgraded and Fail2Ban configured anew.

Ansible Package Updates

Sometimes you do need quicker updates or just want to take care of other package updates. You may want to patch security holes or update packages you need the latest version for urgently. There is a Roots Discourse thread on updates where this is mentioned here. Swalkinshaw mentions here you can do three things for manual updates:

  • Add apt upgrade as a task for a server wide upgrade – did it before myself but do test locally or on staging first!
  • manually specify version for Ansible tasks being run for provisioning – see apt Ansible Books
  • or add latest=yes to any apt action

Real Guess has some scripts that I may implement in the future for Ubuntu updates here. It uses the mentioned Ansible apt module. But the examples Ansible mentions are also very useful.

Quick Security Updates

I do ran updates manually every now and then. And I sometimes do them on the server. To deal with security issues. Not as deterministic as I would like, but it does do the trick. I will incorporate some commands into tasks as soon as I can and write another blog post on it.

Earlier I had this shown on logging into one of my servers at Digital Ocean:

Welcome to Ubuntu 16.04.1 LTS (GNU/Linux 4.4.0-34-generic x86_64)

* Documentation:
 * Management:
 * Support:

System information as of Thu Oct 6 06:49:26 UTC 2016

System load: 0.09 Processes: 119
 Usage of /: 20.8% of 24.58GB Users logged in: 0
 Memory usage: 48% IP address for eth0:
 Swap usage: 1%

Graph this data and manage this system at:

Get cloud support with Ubuntu Advantage Cloud Guest:

95 packages can be updated.
44 updates are security updates.

This was after a general provisioning. So to take care of the security packages I did a:

sudo unattended-upgrades -d
  • add the -d flag to get extra information

which works when the unattend-upgrades package is installed. It is on the Bento box. Running this package will update all unattended security packages – SO Thread. Sometimes including regular packages submitted by security maintainers or so it seems. And when I logged in again I saw:

59 packages can be updated.
6 updates are security updates.

Packages that were updated were:


So quite a few security packages were updated including SSL. New ones popped up and those can be dealt with the same way. This way is also pretty safe and clean. Better to build it into your Trellis workflow of course. I am working on another task for that. However, all in all a good solution to deal with security on your server in a semi automated and clean way. There are ways to run this in the background too, but so far I have preferred to do it this way, especially with the -d flag to keep everything under control.

So there you have it – keeping your Trellis server updated in a nutshell. You got feedback or questions? Leave a comment below and feel free to retweet or post to Facebook.

PS WordPress Core, plugins and theme updates are handled with

./ production site.domain

following composer update. Another blog post on that later on.