Category Archives: Wordpress

Setting the correct forwarded IP address in WordPress Stream plugin

If you are using the WordPress Stream plugin and have a reverse proxy like Varnish in front of it, you might be seeing the IP of your Varnish machines rather than the client IP. This snippet lets you set the proper IP address that Varnish or other reverse proxies forward.

This code only works for Apache. If you are running Nginx you will need the getallheaders polyfill.

//Add proper forwarded IP to log records
add_filter('wp_stream_record_array', function($record) {
   //Get proper IP
   if(function_exists('getallheaders')) {
      $headers = getallheaders();

      //Exchange IP if header set
      if(isset($headers['X-Forwarded-For'])) {
        $record['ip'] = filter_var($headers['X-Forwarded-For'], FILTER_VALIDATE_IP);

    return $record;

Upgrading from Deployer 3 to Deployer 4

If you are upgrading Deployer from version 3 to 4 you will see issues when trying to deploy, for example:

[ERROR] Error: Call to undefined function set() in /Users/stakhr/site/deploy.php:5    
       Stack trace:                                                                                
       #0 phar:///Users/stakhr/site/deployer.phar/bin/dep(114): require()            
       #1 phar:///Users/stakhr/site/deployer.phar/bin/dep(115): {closure}()          
       #2 /Users/stakhr/site/deployer.phar(4): require('phar:///Users/s...')         
       #3 {main}  

To fix these issues, you need to do a few things:

  • Add a Deployer namespace declaration at the top of your deploy.php file, like this: namespace Deployer;
  • The env() function has been deprecated. Replace it with set() when setting variables (two parameters) and get() when getting variables (one parameter)
  • The underlying SSH implementation will be changed in Deployer 5, so add set('ssh_type', 'native'); to fix the nag that shows up.

Here is a full example of deploying a WordPress site using Deployer 4

PS. I’ve also noted that running deployer self-update on version 3 does not upgrade to version 4.

Creating dynamic sliders with Soliloquy for free

I wanted to create a simple dynamic slider using Advanced Custom Fields, but Soliloquy does not have this option out of the box. After poking around the source of Soliloquy I found the filter soliloquy_custom_slider_data which lets us add our own slider data. Here is the required code to dynamically generate a slider. (This code is not ACF-specific, you can use any data source.)

Your slider will be available using the shortcode:

[soliloquy type="frontpage"]

The code to generate this dynamic slider:

add_filter('soliloquy_custom_slider_data', function($data, $atts, $post) {

    //Bail early if not our slider type
    if(isset($atts['type']) && $atts['type'] !== 'frontpage') {
        return $data;

    $data_dynamic = [
        'id' => 0,
        'slider' => [
            //This is where you enter all your dynamic slides!
            //72 and 73 below are the IDs of the image attachments used in the slider. 
            72 => [
                'status' => 'active',
                'id' => 72,
                'attachment_id' => 72,
                'title' => 'Image title',
                'link' => '',
                'alt' => 'Alt text',
                'caption' => 'Caption',
                'type' => 'image',
                'linktab' => 0
            73 => [
                'status' => 'active',
                'id' => 73,
                'attachment_id' => 73,
                'title' => 'Image title',
                'link' => '',
                'alt' => 'Alt text',
                'caption' => 'Caption',
                'type' => 'image',
                'linktab' => 0
        'config' => [
            //This is the general slider config
            'type' => 'default',
            'slider_theme' => 'base',
            'slider_width' => 1080,
            'slider_height' => 400,
            'transition' => 'fade',
            'duration' => 5000,
            'speed' => 400,
            'gutter' => 20,
            'slider' => 1,
            'aria_live' => 'polite',
            'classes' => [
            'title' => '',
            'slug' => '',
            'rtl' => 0

    return $data_dynamic;
}, 11, 3);

I am using Soliloquy Lite, although I’m sure this works with the paid options as well. You can also easily create multiple dynamic sliders, each pulling data from different sources.

Add custom CSS style to Advanced Custom Fields WYSIWYG field

Example of adding styles from a theme. The code below works on frontend (if you are using acf_form()) as well as in the backend.

add_filter( 'tiny_mce_before_init', function($mce_init) {
  $content_css = get_stylesheet_directory_uri() . '/your-custom-css.css';

  if (isset($mce_init[ 'content_css' ])) {
    $mce_init[ 'content_css' ] = "{$mce_init['content_css']},{$content_css}";

  return $mce_init;


Building the right administration interface for every user

WordPress Stockholm meetup group, January 2017

When building websites a lot of work is put into the frontend of the site – with good reason! But often the experience for content editors and site administrators isn’t given as much thought. However WordPress has a wide array of tools to easily make great admin interfaces.

In this talk we’ll take a look at the ecosystem of administration tools available for WordPress, as well as the different types of users that are likely to use the backend of the sites you build.


Migrating a WordPress Site From HTTP to HTTPS with WP-CLI

It’s as easy as:

Take a backup

wp db export

Test rewriting from HTTP to HTTPS

wp search-replace '' '' --dry-run

If everything is looking good, it’s time to rewrite:

wp search-replace '' ''

Some tutorials recommend adding the --skip-columns=guid flag. The train of thought is that old posts will be visible to RSS readers as new ones. I don’t think this is a big problem, and it’s much cleaner to not keep non-SSL urls in your database. It can also cause issues if you are using the GUID field to grab image urls/ids from attachments. (It’s a very popular way of mapping a URL to an attachment ID). With that in mind, I find it’s best to omit this flag.

Installing WordPress on a tiny wireless router

I recently got a Gl.Inet 6416 router off AliExpress for about $25. It’s an interesting little box that includes many features that routers ten times more expensive lack. (Thanks OpenWRT and open source!) The box is powered by the Atheros AR9331, an aging MIPS architecture processor clocked at a measly 400mhz with 64MB of RAM and 16MB of storage. In computer years, MIPS is stone-age technology, first launched in 1981(!), and the MIPS 24K standard this particular processor implements was first launched in 2004.


But the router is still quite capable even by 2016 standards! There is an SSH server and a package manager, opkg, which has packages for PHP 5.6. I immediately thought of something crazy to do – namely to install WordPress on this poor little machine.

So let’s see how that works.

Scaling things down

64MB of memory can’t accomodate either Apache or MySQL, so we have to find a way to run without them. Luckily, PHP has a built-in web server since version 5.4 that we can use, and WordPress has a SQLite plugin. This is a truly cool plugin that hooks into the db.php drop-ins and dynamically rewrites all WordPress database calls from MySQL to SQLite-compatible syntax.


Increasing the storage

16 MB of internal storage won’t get us far. Luckily, you can insert a USB drive and it happily mounts it under /mnt/sda1, so let’s do that:

IMG_20160717_031209-01 - Copy

Installing dependencies

Let’s SSH into the router and install SQLite, PHP and some useful tools like wget…

opkg install zoneinfo-core sqlite3-cli sqlite3 wget htop ca-certificates php5-cli php5 php5-mod-session php5-mod-pdo-mysql php5-mod-gd php5-mod-hash php5-mod-mbstring php5-mod-json php5-mod-opcache php5-mod-sqlite3 php5-mod-pdo-sqlite

Installing WordPress

Unfortunately, there is no support for Phar archives and hence no simple way to run WP-CLI or Composer, so let’s install WP manually:

cd /mnt/sda1
mv sqlite-integration wordpress/wp-content/plugins

Now let’s move the db.php drop-in into place:

cp wordpress/wp-content/plugins/sqlite-integration/db.php wordpress/wp-content/db.php

Let’s also create a default wp-config.php file:

cp wordpress/wp-config-sample.php wordpress/wp-config.php

Tweaking the PHP config

Some PHP defaults aren’t a good fit for this ancient architecture. Let’s decrease opcache.memory_consumption from 64MB to 8MB and increase the max_execution_time from 30 to 300 in /etc/php.ini

Booting it up

Now let’s finally start this baby up on the built-in PHP web server:

cd wordpress
php-cli -S

Loading up the browser the router IP on port 8080 gives us a familiar sight:

Thanks to the SQLite adapter, we don’t need to provide database credential, an on-disk database has already been created for us in wordpress/wp-content/database/.ht.sqlite.

After about 40 seconds, the install finally finishes!

2016-07-17 02_52_28-Program Manager

So what’s the speed like?

It comes as no surprise that this is suh-low! A pageload takes 5-15 seconds. Sometimes even longer if you are saving a draft or publishing a post:

2016-07-17 02_56_33-Program Manager

Other than that, it does actually run! Although uploading large images tends to make the built-in server crash with an out-of-memory error. Not surprising, considering we only have about 40 MB of RAM to play with (The operating system takes up approximately 25MB RAM).


Even though this is a silly project it shows that it is not only possible to run WordPress on OpenWRT, but that it is fairly pain-free to set up. There are more powerful MIPS processors out there (like the Qulcomm QCA9531 with 650mhz/128MB RAM) and those might actually make this usable for a real site. Hope you learned about some techniques to use WordPress in low-memory environments!

Disable preview in the WordPress theme Customizer

The WordPress Customizer preview can be notoriously buggy. Here is a snippet for easily disabling the customizer preview:

add_action( 'customize_preview_init', function() {
    die("The customizer is disabled. Please save and preview your site on the frontend.");
}, 1);

After adding this code to your themes functions.php file or a plugin, the live preview will simply show a message that it is disabled.

Screen Shot 2016-05-06 at 15.34.11