WordPress from Development to Production using Docker Part II

This post continues where part one left off. Topics include mysql data migration, staging and production docker configurations with optional https.

To perform these steps, you will need to either:

  1. Create the files directly on the server using nano, vim or some other command line editor.
  2. Create the files on your local machine and copy them to the server using scp.

I prefer the latter method. Creating the files locally in a folder structure that mirrors my home directory on the server makes it easier to edit and deploy them to the respective server. Not to mention the local backup this creates. Use scp from the command line or use a GUI application such as WinSCP.

Reverse Proxy

For staging and production, create a docker-compose.yml file that defines a reverse-proxy service using the official Traefik image. It is a good idea to put the Traefik Docker files in its own folder to make it easier when adding more sites later. Create a folder on the server for your Traefik files and save the compose yaml there.

docker-compose.yml
version: "3"

services:
  proxy:
    image: traefik
    restart: unless-stopped
    # command: --docker --logLevel=DEBUG
    command: --docker --logLevel=INFO
    networks:
      - webgateway
    ports:
      - "80:80"
      - "8080:8080"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./certs:/etc/traefik/certs
      # `chmod 600 acme.json`
      - ./acme.json:/etc/traefik/acme.json
      - ./traefik.toml:/etc/traefik/traefik.toml
      # - $PWD/.htpasswd:/etc/traefik/.htpasswd

networks:
  webgateway:
    driver: bridge
  • This Traefik docker-compose.yml uses Let’s Encrypt for free SSL certificate renewal. Create a traefik.toml and acme.json file to set that up. If you already have an SSL certificate for your site, use the certs folder to store the certificate files there for mounting to the Traefik container.
traefik.toml
defaultEntryPoints = ["http", "https"]

[entryPoints]
  [entryPoints.http]
  address = ":80"
  [entryPoints.https]
  address = ":443"
    [entryPoints.https.tls]
      # comment these lines if not in use
      # non let's encrypt certificates
      [[entryPoints.https.tls.certificates]]
      certFile = "/etc/traefik/certs/myothersite.com.crt"
      keyFile = "/etc/traefik/certs/myothersite.com.key"

[acme]
email = "gilfoyle@piedpiper.com"
storage = "acme.json"
onHostRule = true
# caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
  [acme.httpChallenge]
  entryPoint = "http"

The acme.json file is used by Traefik for certificate storage. Create this file and set the permissions to 600 so only the owner can read and write. For example.

# change to the folder where our Traefik docker files are stored
cd docker/traefik

# create an empty acme.json file
touch acme.json

# set permissions to 600
chmod 600 acme.json

Site

Once your docker image is deployed, you are ready to create a staging and production docker-compose.yml file for mysite.

docker-compose.yml
version: '2'

services:
  app:
    image: mysite:4.9.6-1.0
    restart: unless-stopped
    environment:
      - WORDPRESS_DB_HOST:mysql:3306
      - WORDPRESS_DB_USER=wordpress
      - WORDPRESS_DB_PASSWORD=changeme!
      - WORDPRESS_DB_NAME=wordpress
    labels:
      - traefik.frontend.rule=Host:local.vm.mysite.com
      - traefik.docker.network=traefik_webgateway
    volumes:
      - app:/var/www/html
    networks:
      - web
      - backend
    links:
      - mysql

  mysql:
    image: mariadb
    restart: unless-stopped
    labels:
      - "traefik.enable=false"
    environment:
      - MYSQL_ROOT_PASSWORD=changeme!
      - MYSQL_DATABASE=wordpress
      - MYSQL_USER=wordpress
      - MYSQL_PASSWORD=changeme!
    volumes:
      - mysql:/var/lib/mysql
    networks:
      - backend

networks:
  web:
    external:
      name: traefik_webgateway
  backend:
    driver: bridge

volumes:
  app:
  mysql:
  • For production, replace label traefik.frontend.rule=Host:local.vm.mysite.com with the registered host. You can specify both without and with www, for example: traefik.frontend.rule=Host:mysite.com,www.mysite.com.

To recap, here is how we have structured our Docker files in the users home folder on the server for mounting them to volumes and running the containers with docker-compose. The docker/traefik/certs folder is only needed for non Let’s Encrypt certificate storage, for example, certificates purchased from SSLs.com. Note that we did not create an additional site, e.g., myothersite. It is included below only to demonstrate that multiple sites can be added using this setup and they can use either Let’s Encrypt or purchased certs.

  • docker
    • mysite
      • docker-compose.yml
    • myothersite
      • docker-compose.yml
    • traefik
      • docker-compose.yml
      • acme.json
      • traefik.toml
      • certs

Run

Start the containers on the server using docker-compose. Do this by opening a secure shell using ssh. For example,

ssh gilfoyle@172.10.10.10

# change to the directory where you uploaded the
# traefik docker-compose.yml file using scp
cd docker/traefik

# start the traefik container
docker-compose up -d

# change to the directory where you uploaded the
# mysite docker-compose.yml file using scp
cd docker/mysite

# start the app and mysql containers defined in the mysite/docker-compose.yml
docker-compose up -d

To view the site on staging, update your systems hosts file to direct local.vm.mysite.com requests to the staging server IP address. For example:

172.10.10.10    local.vm.mysite.com

Load the site in a browser and install WordPress, for example, http://local.vm.mysite.com.

Data Migration

Once your WordPress site is installed, you may want to restore the data from another mysql volume. Perhaps your dev server has the data you now want on your staging instance of WordPress.

1. Perform a mysql dump to get the data from the running mysql container on the dev server. For example, using the development environment from part one, our container is named my_wordpress_project_mariadb.

# get the mysql container name by listing all of the containers
docker ps -a

# dump the mysql data into the current directory
# from the running docker container named
# my_wordpress_project_mariadb
docker exec my_wordpress_project_mariadb /usr/bin/mysqldump -u root --password=password wordpress > wordpress.sql

2. Prepare your dev server mysql dump for loading into the staging server. Open the wordpress.sql file in a code editor and replace all instances of the host:port with the staging server host. For example, replace dev.docker.mysite.com:8000 with local.vm.mysite.com.

3. Upload the updated wordpress.sql file to the staging server using scp.

4. ssh into the staging server and load the wordpress.sql into the running mysite mysql container.

# get the mysql container name by listing all of the containers
docker ps -a

# load the mysql data from the current directory
# into the running mysite_mysql_1 docker container
cat wordpress.sql | docker exec -i mysite_mysql_1 /usr/bin/mysql -u root --password=changeme! wordpress
  • After loading a new Docker image, you will need to remove the pre-existing app volume before bringing up the app container for the new image. Use docker volume ls to list the volumes and docker volume rm to remove the app image. For example, docker volume rm mysite_app.

That’s it!

WordPress from Development to Production using Docker

This post will cover how to use Docker for a local WordPress development environment and how to deploy it to an Ubuntu Linux server running docker.

What you will need.

  • Docker
  • VPS with Docker (for prod deployment)
  • WordPress

The first step is to get your local development environment set up for your WordPress site. There are quite a few ways I have setup this environment in the past. For the last year or so, I’ve been using Wodby’s Docker-based WordPress Stack with its many options.

Using git, clone docker4wordpress into a site folder. For example mysite.

git clone https://github.com/wodby/docker4wordpress.git mysite

Update the .env file, for example:

PROJECT_NAME=mysite
PROJECT_BASE_URL=dev.docker.mysite.com

Update your systems hosts file, for example:

127.0.0.1    dev.docker.mysite.com

Delete docker-compose.override.yml as it’s used to deploy vanilla WordPress

Download WordPress and uncompress it into the site folder so you end up with a wordpress folder. For example mysite/wordpress.

Optionally, you could download and extract WordPress using the CLI, for example:

cd mysite
wget http://wordpress.org/latest.tar.gz
tar xfz latest.tar.gz

Docker Compose

Update the docker-compose.yml volume paths to mount the codebase to ./wordpress instead of the root of the site folder. This step is needed for the build configuration. For example, for both of these services nodes, nginx and php, replace ./:/var/www/html with ./wordpress:/var/www/html.

...
    volumes:
      - ./wordpress:/var/www/html

Create a named volume definition to persist the mysql data. At the bottom of the docker-compose.yml file, uncomment and update the volumes node. For example, replace #volumes: with the following:

volumes:
  mysql:

Update the mariadb service to use the named mysql volume. For example, under the mariadb services node, uncomment and update the volumes node. For example:


services:
  mariadb:

    ...

    volumes:
      - mysql:/var/lib/mysql

With Docker running, from the site directory, e.g., mysite, run docker-compose up -d to start containers.

If you’re in Windows and get the following error:

ERROR: for traefik Cannot create container for service traefik: Mount denied: The source path \\\\var\\\\run\\\\docker.sock:/var/run/docker.sock is not a valid Windows path

This worked for me using Cygwin. Before running docker-compose up -d, run export COMPOSE_CONVERT_WINDOWS_PATHS=1. For PowerShell, use $Env:COMPOSE_CONVERT_WINDOWS_PATHS=1 More info: github.com/docker/for-win/issues/1829

Now you’re ready to load the site in a browser, http://dev.docker.mysite.com:8000.

The WordPress install page should appear. After selecting your language, on the following screen, if using the default settings in .env file, enter wordpress for Database Name, Username and Password. The Database Host value is the service name defined in the docker-compose.yml, e.g., mariadb.

docker4wordpress - WordPress Install - Database Configuration
WordPress Install – Database Configuration

Custom Theme

To demonstrate getting a custom theme into the build, let’s make a copy of twentyseventeen and customize it.

cp -r wordpress/wp-content/themes/twentyseventeen wordpress/wp-content/themes/mytheme/

We’re just gonna make a small change to the site title text to show what you can do to make the font size flexible for the default below desktop viewport width.

Edit mytheme/style.css. Under Layout, add font-size: 5vw below the existing font-size rules. For example.

style.css
.site-title {
  ...

  font-size: 24px;
  font-size: 1.5rem;
  font-size: 5vw;

Login to the site and activate mytheme to see the change to the site title font size when you adjust the width of the browser below 768 pixels wide.

Plugins

To demonstrate the inclusion of plugins in the docker build, install a plugin that will be included in the codebase. For example, I like the Yoast SEO plugin. Instead of installing it using the dashboard, download and extract it. Copy the wordpress-seo folder into the wordpress/wp-content/plugins folder. You can verify the installation by logging into the site dashboard and inspecting the plugins page.

Docker Image

The docker image we build for staging and production will be based off the official WordPress image and will only need our themes, plugins and any other changes from the development environment. Create or download this Dockerfile into your site folder. The FROM instruction in this file is set to use the official WordPress base image. You should update this as needed to use the latest version of the image.

For example, to download the Dockerfile using wget

cd mysite
wget https://raw.githubusercontent.com/jimfrenette/docker4wordpress/master/Dockerfile.sh
Dockerfile
## https://github.com/docker-library/wordpress
FROM wordpress:4.9.6-php7.2-apache

## PHP extensions
## update and uncomment this next line as needed
# RUN docker-php-ext-install pdo pdo_mysql

## custom directories and files
## copy them here instead of volume
## /var/www/html/
## wordpress docker-entrypoint.sh runs
## chown -R www-data:www-data /usr/src/wordpress
COPY ./build/ /usr/src/wordpress/

I created some build scripts to accept some parameters and copy files from the development environment into a build folder for the Dockerfile. Update the script as needed, setting the THEME variable to match the folder name of your custom WordPress theme. e.g., THEME = "mysite". Use the appropriate script for your environment:

If you’re in Windows, use the docker-build.ps1 PowerShell script.

If you’re in OS X or Linux, use the docker-build.sh bash script.

Download the build script into your site folder and run it. For example:

cd mysite

# download build script
wget https://raw.githubusercontent.com/jimfrenette/docker4wordpress/master/docker-build.sh

# if initial run,
# set script as executable
chmod +x docker-build.sh

# run
./docker-build.sh

This image shows running the docker-build shell script in the VS Code integrated terminal. Note the file explorer in the left pane contains the docker/stage/mysite-4.9.6-1.0.tar file that was created.

Running docker-build.sh in the VS Code integrated terminal
Run docker-build shell script in VS Code

Deployment

After running the build script, a saved docker image .tar file should exist in the prod or stage folders. One way to deploy the image is to copy the .tar file to the server using scp and load it.

With example Ubuntu user gilfoyle, use scp from the stage folder.

# upload image
cd docker
scp mysite.tar gilfoyle@0.0.0.0:/home/gilfoyle/

Load the Docker image on the server.

# load image
ssh gilfoyle@0.0.0.0
docker load -i mysite.tar

Part two of this post includes stage and prod docker-compose.yml, migrating data, etc.

Docker WordPress Dev Environment

This post documents setting up local development environments with Docker using the official WordPress Docker repository as a base image. Page two includes configurations for remote PHP debugging with Xdebug and VS Code.

Docker Compose

Aside from making the container configuration easier to understand, Docker Compose coordinates the creation, start and stop of the containers used together in the environment.

The environment has multiple sites served on various ports including at least one WordPress site, a phpmyadmin site and mysql. For virtual host names, nginx-proxy is being used for containers with virtual host environment properties set in their docker compose data. Additionally, the mysql db container is accessible from the host at 127.0.0.1:8001

Create this docker compose yaml file in the projects root directory with the nginx-proxy configuration.

nginx-proxy.yml
version: "2"
services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    container_name: nginx-proxy
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro

Create this docker compose yaml file for the WordPress stack. This includes the linked MariaDB database and phpMyAdmin containers from their official repositories. Xdebug is not included in the official WordPress image on Docker Hub and will not be included in this configuration since it is using unmodified images. Adding xdebug and rebuilding the image is covered on page two.

wp.yml
version: "2"
services:
  db:
    image: mariadb
    volumes:
      - mysql:/var/lib/mysql
    ports:
      - "8001:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=secret
  phpmyadmin:
    image: phpmyadmin/phpmyadmin:latest
    ports:
      - "8002:80"
    links:
      - db:mysql
    environment:
      - MYSQL_ROOT_PASSWORD=secret
      - VIRTUAL_HOST=phpmyadmin.app
      - VIRTUAL_PORT=8002
  wp:
    image: wordpress
    volumes:
      - ./wordpress:/var/www/html
    ports:
      - "8003:80"
    links:
      - db:mysql
    environment:
      - WORDPRESS_DB_PASSWORD=secret
      - VIRTUAL_HOST=wordpress.dev
      - VIRTUAL_PORT=8003
volumes:
  mysql:

Update your systems hosts file.

hosts
# Docker (nginx-proxy)
127.0.0.1 phpmyadmin.app
127.0.0.1 wordpress.dev

Navigate to your project root in your CLI, such as Terminal on OS X, PowersShell or Cygwin on Windows.

Create the Containers

Create new nginx-proxy and WordPress containers using the up command with docker-compose.

docker-compose -f nginx-proxy.yml -f wp.yml up
  • The -f flags specify the compose files to use. Multiple compose files are combined into a single configuration. This multiple file solution is for demonstration purposes. Here is a single file example that can be run without a file flag.

Stop Containers

Stop the containers without removing them.

docker-compose -f wp.yml -f nginx-proxy.yml stop

Start Containers

Start the stopped containers. Include the nginx-proxy.yml first so when the WordPress containers are started the virtual hosts can be dynamically configured.

docker-compose -f nginx-proxy.yml -f wp.yml -f start
  • If you have restarted your computer and another process is using the nginx-proxy port, e.g., 80, you will need to halt that process before starting the container.

Shutdown Containers

Shutdown the environment using the down command. If your data is not stored in a volume, it will not persist since this will remove the containers.

docker-compose -f wp.yml -f nginx-proxy.yml down

The next page covers adding Xdebug and configuring VS Code for remote debugging.


WordPress Post from Front End using REST API and Vue.js Part II

The objective of this post is to demonstrate some Vue.js basics for working with the WordPress REST API. This post continues where the last post left off with the following additions.

  • List posts submitted by the current user
  • Select from the list to edit a post
  • Delete a post from the list
Source Code

Development

The original proof of concept details building the WordPress plugin, installing Vue.js, Webpack and configuring for development. Also included is information on creating the front end slug where the Vuejs app will run. The end product is a simple form to submit new posts from a custom WordPress page template. It is recommended that you read these aforementioned prerequisites:

List Posts

App.vue

Above the form, replace the hardcoded heading text with a placeholder to render heading data that will be adding to the data model.

Below the form, add this markup to display the messages for loading and errors along with the list element for the posts. Note the vue.js v-if attributes to control visibility of the elements depending on the data model values. This is called conditional rendering.

<h3>{{ heading }}</h3>
<form v-on:submit.prevent="onSubmit">

...

</form>

<div v-if="loading">
    Loading...
</div>

<div v-if="error">
    {{ error }}
</div>

<div v-if="posts">
    <ul>
        <li v-for="post in posts">
            <a href="#" @click='editPost(post)'>{{ post.title.rendered }}</a>
        </li>
    </ul>
</div>
  • Note that an ellipsis … in the code snippets are not a part of the code and are there only to denote code that is being skipped and not applicable to the example. To view the entire file, examine the source code.

In the App.vue component script block, update the data model for the new elements.

...

data() {
  return {
    error: null,
    heading: 'Submit New Post',
    loading: null,
    post_id: null,
    post_title: null,
    post_content: null,
    posts: null
  }
},

...

Insert the getPosts () method to fetch all the posts for the current user and set the posts property of the data model. The endpoints for the WordPress REST API can be looked up at its base url.

...

methods: {
  getPosts () {
    this.error = this.posts = null
    this.loading = true
    var params = '?author='+ wp_api_vuejs_poc.current_user_id +'&status=any';

    $.ajax({
      url: wp_api_vuejs_poc.rest_url + 'wp/v2/posts' + params,
      beforeSend: function ( xhr ) {
        xhr.setRequestHeader( 'X-WP-Nonce', wp_api_vuejs_poc.nonce );
      }
    })
    .done( $.proxy( function( response ) {
      this.loading = false;
      this.posts = response;
    }, this ))
    .fail( $.proxy( function( response ) {
      this.error = response;
    }, this ));
  },

...

}

Insert the mounted function to call the getPosts method when the app has rendered. If there are any posts to show when the page loads, they should be listed.

App.vue
...

methods: {
  mounted: function () {
    this.$nextTick(function () {
      this.getPosts ();
    })
  },

...

}

Development Build

In your CLI, build in development mode which enables watch on the files and incrementally recompiles as needed. This is faster than a prodcution build. Additionally, this unminified build is compatible with the Vue DevTools extension for Chrome for debugging and object inspection.

npm run dev

After the build is completed and watch is running, reload the page. Submit a new post to make sure it is working and gets added to the list.

Edit / Add Posts

In the markup that lists posts, the titles link contains a @click='editPost(post) attribute to call the editPost method passing it the post object. Insert the editPost method to accept the post object from the list and set the properties of the data model. Since this is a simple proof of concept, we are not comparing the post against the version on the server to make sure it is hasn’t been modified since the last fetch. Note that the heading property is being set as well to display the appropriate text.

...

methods: {
  editPost( post ) {
    this.heading = 'Edit Post'
    this.post_id = post.id
    this.post_title = post.title.rendered
    this.post_content = post.content.rendered
  },

...

}

When you save the changes to the App.vue file, the watch detects the changes and incrementally rebuilds the code. Refresh the browser, select a post from the list and verify that it is working as expected.

We need a way to Submit New Post without reloading the page. Add this markup to the bottom of the form under the submit input so we have a link that will call a newPost method.

...

    <span v-if="post_id">
        <a href="#" @click="newPost">New Post</a>
    </span>
</form>

Insert the newPost method to reset the post and heading properties to their default values.

...

methods: {
  newPost() {
    this.heading = 'Submit New Post'
    this.post_id = null
    this.post_title = null
    this.post_content = null
  },

...

}

In most cases, we should reset the form after fetching posts. Insert a call to newPosts inside the ajax callback function right after the posts object is populated with the response.

...

methods: {
  getPosts () {

  ...

    .done( $.proxy( function( response ) {
      this.loading = false;
      this.posts = response;
      this.newPost();
    }, this ))

...

}

Delete Posts

Insert this link markup in the list item element after the post title link.

...
    <a href="#" @click='delPost(post)' title="DELETE">[–]</a>
</li>

Lastly, insert the delPosts method.

...

methods: {
  delPost( post ) {
    $.ajax({
      method: "DELETE",
      url: wp_api_vuejs_poc.rest_url + 'wp/v2/posts/' + post.id,
      beforeSend: function ( xhr ) {
        xhr.setRequestHeader( 'X-WP-Nonce', wp_api_vuejs_poc.nonce );
      }
    })
    .done( $.proxy( function() {
      this.getPosts();
    }, this ))
    .fail( $.proxy( function( response ) {
      console.log( response );
    }, this ));
  },

...

}

Resources

WordPress Post from Front End using REST API and Vue.js

This post is a simple proof of concept for using the new WordPress REST API to submit a new post draft from the front end. The form and user inputs are built using the Vue.js framework and vue-cli to create a simple Webpack build configuration.

Environment

This example was created using a fresh install of WordPress 4.7 served by XAMMP on Windows 10.
Nodejs version 6.9.1
NPM version 3.10.8
vue-cli version 2.6.0
git version 2.7.0

Plugin Development

Change to the plugins directory. For example,

cd c:/xampp/htdocs/wordpress/wp-content/plugins

Create the directory for the new plugin. For example,

mkdir wp-api-vuejs-poc

Entry Point

Create a plugin entry point php file in this new plugin directory. For example,

# change to the new directory
cd wp-api-vuejs-poc

# create a new file
touch wp-api-vuejs-poc.php

Add the following php code to the plugin entry point. At the very least, add the comment block at the top to register the plugin with WordPress.

wp-api-vuejs-poc.php
<?php
/**
 * Plugin Name: WP API Vue.js Proof of Concept
 * Plugin URL: http://example.com
 * Description: WordPress plugin to draft posts from the front end using the new REST API and Vue.js
 * Version: 1.0
 * Author:
 * Author URI: http://example.com
 * Text Domain: wp-api-vuejs-poc
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

if ( ! class_exists( 'WP_API_Vuejs_PoC' ) ) :

/**
 * Main WP_API_Vuejs_PoC Class.
 */
final class WP_API_Vuejs_PoC {

    /**
     * The single instance of the class.
     */
    protected static $_instance = null;

    /**
     * Ensures only one instance of WP_API_Vuejs_PoC is loaded or can be loaded.
     */
    public static function instance() {
        if ( is_null( self::$_instance ) ) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }

    public function __construct() {
        $this->define_constants();
        $this->includes();
    }

    /**
     * Define Constants.
     */
    private function define_constants() {
        $this->define( 'WAVP_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
        $this->define( 'WAVP_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
    }

    private function define( $name, $value ) {
        if ( ! defined( $name ) ) {
            define( $name, $value );
        }
    }

    /**
     * What type of request is this?
     *
     * @param  string $type admin, ajax, cron or frontend.
     * @return bool
     */
    private function is_request( $type ) {
        switch ( $type ) {
            case 'admin' :
                return is_admin();
            case 'ajax' :
                return defined( 'DOING_AJAX' );
            case 'cron' :
                return defined( 'DOING_CRON' );
            case 'frontend' :
                return ( ! is_admin() || defined( 'DOING_AJAX' ) ) && ! defined( 'DOING_CRON' );
        }
    }

    public function includes() {
        if ( $this->is_request( 'frontend' ) ) {
            include( 'class-api-vpoc-page.php' );
        }
    }

}

endif;

/**
 * Main instance of WP_API_Vuejs_PoC.
 * Returns the main instance of WAVP to prevent the need to use globals.
 */
function WAVP() {
    return WP_API_Vuejs_PoC::instance();
}

// Global for backwards compatibility.
$GLOBALS['wp-api-vuejs-poc'] = WAVP();

Class for Front End Page Request

In the class of the plugin entry point file above, the includes function contains a request type check. When the request is made from the front end, include the class file for the page. Let’s create that class file now, for example,

touch class-api-vpoc-page.php

Add the following php code to create the class. The constructor method is called on the newly-created class object where we are subscribing to events using hooks.

The action hook is called during the page processing event for script loading, at this point, call our page_scripts function to load the app javascript.

The filter hook is called during data processing, when the content is being loaded, call our page_content function to create the app bootstrap element.

class-api-vpoc-page.php
<?php

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

/**
 * WP_API_Vuejs_PoC_Page class.
 */
class WP_API_Vuejs_PoC_Page {

    /**
     * Access
     */
    private static $user_can = 'edit_posts';
    private static $page_slug = 'api-test';

    /**
     * Constructor.
     */
    public function __construct() {
        add_action( 'wp_enqueue_scripts', array( $this, 'page_scripts' ) );
        add_filter( 'the_content', array( $this, 'page_content' ) );
    }

    public function page_scripts() {
        if ( is_page( self::$page_slug ) ) {
            // load the Vue.js app
            wp_enqueue_script( 'wp-api-vuejs-poc', WAVP_PLUGIN_URL . 'dist/build.js', array(), false, true );

            // localize data for script
            wp_localize_script( 'wp-api-vuejs-poc', 'wp_api_vuejs_poc', array(
                'rest_url' => esc_url_raw( rest_url() ),
                'nonce' => wp_create_nonce( 'wp_rest' ),
                'success' => __( 'Post submitted', 'wp-api-vuejs-poc' ),
                'failure' => __( 'Post could not be processed.', 'wp-api-vuejs-poc' ),
                'current_user_id' => get_current_user_id()
                )
            );
        }
    }

    public function page_content($content) {
        if ( is_page( self::$page_slug ) ) {
            // output only to logged in users who can edit posts
            if ( is_user_logged_in() && current_user_can( self::$user_can ) ) {
                // app bootstrap element
                ob_start();?>
                <div id="app"></div>
                <?php
                $content .= ob_get_clean();
            }else{
                $content .=  sprintf( '<a href="%1s">%2s</a>', esc_url( wp_login_url() ), __( 'Log in', 'wp-api-vuejs-poc' ) );
            }
        }

        return $content;
    }

}

return new WP_API_Vuejs_PoC_Page();

The next page covers the Vue.js front end.


Creating a Custom WordPress Page Template

I wanted a front-end page template to play around with the new REST API that is now in WordPress 4.7 core. A custom page template gives me the latitude I want at this stage of development, so here is how I did it.

Create the Template File

There are a couple of ways to do this.

Option 1

Clone and edit the existing page.php template. Open page.php in an editor and Save As… api-test.php. Remove the guts of the page containing the while loop for iterating through and displaying posts. For example, my clone of wp-content/themes/twentyseventeen/page.php saved as api-test.php looks like this:

api-test.php
<?php

get_header(); ?>

<div class="wrap">
    <div id="primary" class="content-area">
        <main id="main" class="site-main" role="main">


        </main><!-- #main -->
    </div><!-- #primary -->
</div><!-- .wrap -->

<?php get_footer();

The original page meta data properties in the comment at the top have been removed.

Option 2

Start from scratch with a completely blank slate. Create a new php file in the root of your active theme with a single line of code to set the Template Name meta data. Now we have a new custom template that can selected for pages when editing.

api-test.php
<?php /* Template Name: api-test */ ?>

Option 3

This is nearly the same as option 1, only with addition of the meta data property to set the Template Name property like option 2.

Clone and edit the existing page.php template. Open page.php in an editor and Save As… api-test.php. Edit api-test.php replacing the meta data at the top with the Template Name property used in the blank slate option above. Then remove the guts of the page containing the while loop for iterating through and displaying posts. For example, my clone of wp-content/themes/twentyseventeen/page.php saved as api-test.php looks like this:

api-test.php
<?php /* Template Name: api-test */

get_header(); ?>

<div class="wrap">
    <div id="primary" class="content-area">
        <main id="main" class="site-main" role="main">


        </main><!-- #main -->
    </div><!-- #primary -->
</div><!-- .wrap -->

<?php get_footer();

Create the Page Slug

Login into the dashboard and add a new page.

If using Option 1, update the permalink slug to match the page filename. For example, api-test

WordPress Page Permalink Slug
api-test page slug

Select the new template if using options 2 or 3.

WordPress Page Attributes - Custom Template
select api-test template

Resources

WordPress Twenty Seventeen Theme Sass

These Sass modules make customizing the style.css a lot easier for the new business focused Twenty Seventeen default theme for WordPress 4.7 released a couple weeks ago.

May 25, 2018 – Sass has been updated for the Twenty Seventeen theme version 1.6 which comes bundled with WordPress version 4.9.6 released on May, 17, 2018.

The individual Sass modules in this repository represent each of the WordPress Twenty Seventeen style.css stylesheet numbered and annotated sections. The responsive media queries have also been split out into modules for each of the 5 breakpoints. As was done with the Twenty Sixteen Sass modules, all of the fonts and colors have been converted into Sass variables for quick and easy customization.

Source Code

There is also a gulpfile.js included with a task for compiling the .scss files into a new style.css. This task uses cssnano and the workflow described in this post, Sass Workflow Using cssnano and Autoprefixer.

  1. Install the files into the /wp-content/themes/twentyseventeen folder.
  2. To recompile the Twenty Seventeen stylesheet style.css from the scss files, first run npm install to get the dependencies.
    # installs dependencies
    npm install
    
  3. Then run the gulp task that will compile them
    # compile into styleheet
    gulp css
    

Resources

WordPress

Custom Dashboard

WordPress plugin stub to customize the dashboard for users without administrator access.

Source Code

Custom Homepage

Clone and edit the existing theme index.php template. Open index.php in an editor and Save As… home.php. This file will automatically take over the themes index.php, and it will be displayed as the homepage.


Function Snippets

Various snippets for functions.php

Disable automatic <p> tags.

remove_filter( 'the_content', 'wpautop' );
remove_filter( 'the_excerpt', 'wpautop' );

Reference: codex.wordpress.org/Function_Reference/wpautop#Disabling_the_filter


Development Environments

Docker

WordPress from Development to Production using Docker

Vagrant

Laravel Homestead
If you develop Laravel apps on Homestead, you can also install WordPress on the same Virtual Machine. This example Homestead.yaml file has a configuration for 4 different sites. Two Laravel sites, a WordPress site and a phpmyadmin site.

Vagrant configuration designed for development of WordPress plugins, themes, or websites.

http://vccw.cc/

What’s Included

Git Setup

To provision from the master branch,

git clone https://github.com/vccw-team/vccw.git

cd vccw

cp provision/default.yml site.yml

# edit php.ini settings in site.yml as needed

vagrant up
vccw.cc zip download link uses release branch.

XAMPP, MAMP, IIS


Development Resources

wp-config.php

REPLACE

define('WP_DEBUG', false);
WITH
// Enable WP_DEBUG mode
define( 'WP_DEBUG', true );

// Enable Debug logging to the /wp-content/debug.log file
define( 'WP_DEBUG_LOG', true );

// Disable display of errors and warnings 
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );

Log Your Debug Statements

// array or object variable
error_log(print_r($myvariable, true));

// string
error_log(print_r($mystring));

WordPress Twenty Sixteen Theme Sass

I recently created individual Sass modules for each of the WordPress Twenty Sixteen style.css stylesheet numbered and annotated sections. Additionally, the fonts and colors have been converted into Sass variables. All of this makes customizing this theme easier.

May 25, 2018 – Sass has been updated for the Twenty Sixteen theme version 1.5 which comes bundled with WordPress version 4.9.6 released on May, 17, 2018.

Source Code

There is also a gulpfile.js included with a task for compiling the .scss files into a new style.css. This task uses cssnano and the workflow described in this post, Sass Workflow Using cssnano and Autoprefixer.

  1. Install the files into the /wp-content/themes/twentysixteen folder.
  2. To recompile the Twenty Sixteen stylesheet style.css from the scss files, first run npm install to get the dependencies.
    # installs dependencies
    npm install
    
  3. Then run the gulp task that will compile them
    # compile into styleheet
    gulp css
    

Resources

jQuery

A collection of useful jQuery code snippets

// get the value of a text input when modified
(function($){
    $('#sometextinput').on('change keyup paste', function() {
        console.log('VALUE', $(this).val());
    });
})(jQuery);

Window Resize

Wait for window resize to stop.

var resizeTimeout;
$(window).on('resize', function() {
    clearTimeout(resizeTimeout);
    resizeTimeout = setTimeout(doSomething(), 500);
});

If LoDash is present, you can use _.debounce instead.

$(window).on('resize', _.debounce(doSomething, 150));

with jQuery.proxy

$(window).on('resize', _.debounce( $.proxy( function() {
   // code
}, this), 150));

Vanilla JS Resize Handler

jQuery.extend

Use $.extend to merge objects.

var data = $.extend({}, {
    'artist': 'Ben Harper',
    'track': 'Gold To Me'},
    album, discography);

If LoDash is present, use _.extend instead.

var data = {
    'artist': 'Ben Harper',
    'track': 'Gold To Me'}

_.extend(data, album, discography);
jQuery.proxy

Use $.proxy on $.ajax callback to access this from lower scope

fetchData () {
    this.error = this.posts = null

    this.loading = true

    $.ajax({
        url: https://wordpress.example.com + '/wp/v2/posts'
    })
    .done( $.proxy( function( response ) {

        this.loading = false;
        this.posts = response;

    }, this ))
    .fail( $.proxy( function( response ) {

        this.loading = false;
        this.error = response;

    }, this ));
}

Various Ajax Error Responses

Use .ajaxSetup to add more precise ajax error handling.

$.ajaxSetup({
    error: function(jqXHR, exception) {
        if (jqXHR.status === 0) {
            alert('Not connect.\n Verify Network.');
        } else if (jqXHR.status == 404) {
            alert('Requested page not found. [404]');
        } else if (jqXHR.status == 500) {
            alert('Internal Server Error [500].');
        } else if (exception === 'parsererror') {
            alert('Requested JSON parse failed.');
        } else if (exception === 'timeout') {
            alert('Time out error.');
        } else if (exception === 'abort') {
            alert('Ajax request aborted.');
        } else {
            alert('Uncaught Error.\n' + jqXHR.responseText);
        }
    }
});

WordPress

// sticky header and footer
jQuery.noConflict();
    (function($) {
        // wait for resize to stop
        var resizeTimeout;
        $(window).on('resize', function() {
            clearTimeout(resizeTimeout);
            resizeTimeout = setTimeout(setContentHeight, 500);
        });
        setContentHeight = function(event) {
            cVerticalMargin = $('.site-content').outerHeight(true)
                - $('.site-content').outerHeight();
            cVerticalBorder = $('.site-content').outerHeight()
                - $('.site-content').innerHeight();
            cVerticalPadding = $('.site-content').innerHeight()
                - $('.site-content').height();
            cHeight = $(window).height()
                - $('.site-header').outerHeight(true)
                - $('.site-footer').outerHeight(true)
                - cVerticalMargin
                - cVerticalBorder
                - cVerticalPadding;
            $('.site-content').css({'height': cHeight + 'px'});
        }
        setContentHeight();
})(jQuery);

Supporting Older and Newer jQuery

If you find yourself in a messy situation where you need to provide support for jQuery earlier than 1.7 on an intermittent basis.

// for those who use jquery earlier then 1.7 when there is no $.on exist yet
if (typeof jQuery.fn.on !== 'function') {
    jQuery.fn.on = jQuery.fn.live;
    jQuery.fn.off = jQuery.fn.die;
}

WordPress Page Specific Styles or Scripts

I wanted an efficient way to load page or post specific stylesheets and or scripts which lead me to see if I could utilize custom fields for this. Using custom fields combined with wp_enqueue_style and wp_enqueue_script worked perfectly. This solution allows me to link css or javascript files for only the posts or pages I want.

1. Add a couple of custom fields, one for styles and one for scripts to a page using the editor:

Add bn_styles custom field
WordPress editor – Add bn_styles custom field
bn_styles and bn_scripts custom fields added
WordPress editor – bn_styles and bn_scripts custom fields added

Loading WordPress theme stylesheets and scripts is typically done within the functions.php file using wp_enqueue_style and wp_enqueue_script respectively. Using the Twenty Fifteen functions.php file as an example, take a look at the twentyfifteen_scripts function:

function twentyfifteen_scripts() {
    // Add custom fonts, used in the main stylesheet.
    wp_enqueue_style( 'twentyfifteen-fonts', twentyfifteen_fonts_url(), array(), null );

    // Add Genericons, used in the main stylesheet.
    wp_enqueue_style( 'genericons', get_template_directory_uri() . '/genericons/genericons.css', array(), '3.2' );

    // Load our main stylesheet.
    wp_enqueue_style( 'twentyfifteen-style', get_stylesheet_uri() );

    // Load the Internet Explorer specific stylesheet.
    wp_enqueue_style( 'twentyfifteen-ie', get_template_directory_uri() . '/css/ie.css', array( 'twentyfifteen-style' ), '20141010' );
    wp_style_add_data( 'twentyfifteen-ie', 'conditional', 'lt IE 9' );

    // Load the Internet Explorer 7 specific stylesheet.
    wp_enqueue_style( 'twentyfifteen-ie7', get_template_directory_uri() . '/css/ie7.css', array( 'twentyfifteen-style' ), '20141010' );
    wp_style_add_data( 'twentyfifteen-ie7', 'conditional', 'lt IE 8' );

    wp_enqueue_script( 'twentyfifteen-skip-link-focus-fix', get_template_directory_uri() . '/js/skip-link-focus-fix.js', array(), '20141010', true );

    if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
        wp_enqueue_script( 'comment-reply' );
    }

    if ( is_singular() && wp_attachment_is_image() ) {
        wp_enqueue_script( 'twentyfifteen-keyboard-image-navigation', get_template_directory_uri() . '/js/keyboard-image-navigation.js', array( 'jquery' ), '20141010' );
    }

    wp_enqueue_script( 'twentyfifteen-script', get_template_directory_uri() . '/js/functions.js', array( 'jquery' ), '20150330', true );
    //wp_enqueue_script( 'twentyfifteen-script', get_template_directory_uri() . '/js/main.js', array( 'jquery' ), '20150527', true );
    wp_localize_script( 'twentyfifteen-script', 'screenReaderText', array(
        'expand'   => '' . __( 'expand child menu', 'twentyfifteen' ) . '',
        'collapse' => '' . __( 'collapse child menu', 'twentyfifteen' ) . '',
    ) );
}
add_action( 'wp_enqueue_scripts', 'twentyfifteen_scripts' );

Edit functions.php

Add some code to the twentyfifteen_scripts function to get the custom field data and use it to load stylesheets and scripts.

2. At the top of the twentyfifteen_scripts function, add the get_post_custom function to retrieve the custom field data:

function twentyfifteen_scripts() {

    // retrieve custom fields from the current post
    $custom_fields = get_post_custom();

3. After the last stylesheet is queued for loading, add the code that checks for and loads our styles custom_field stylesheets. In this example, add the code after wp_style_add_data( ‘twentyfifteen-ie7’, ‘conditional’, ‘lt IE 8’ ); as shown here:

    wp_style_add_data( 'twentyfifteen-ie7', 'conditional', 'lt IE 8' );

    // styles custom_field
    if (!empty($custom_fields['bn_styles'])) {
        foreach ( $custom_fields['bn_styles'] as $key => $value ) {
            wp_enqueue_style('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/css/' . $value, false, null);
        }
    }

4. At the end of the twentyfifteen_scripts function, add the code that checks for and loads our scripts custom_field javascript as shown here:

    // scripts custom_field
    if (!empty($custom_fields['bn_scripts'])) {
        foreach ( $custom_fields['bn_scripts'] as $key => $value ) {
            wp_enqueue_script('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/js/' . $value, [], null, true);
        }
    }
}
add_action( 'wp_enqueue_scripts', 'twentyfifteen_scripts' );

5. Here is the entire twentyfifteen_scripts function after the modifications above:

function twentyfifteen_scripts() {

    // retrieve custom fields from the current post
    $custom_fields = get_post_custom();

    // Add custom fonts, used in the main stylesheet.
    wp_enqueue_style( 'twentyfifteen-fonts', twentyfifteen_fonts_url(), array(), null );

    // Add Genericons, used in the main stylesheet.
    wp_enqueue_style( 'genericons', get_template_directory_uri() . '/genericons/genericons.css', array(), '3.2' );

    // Load our main stylesheet.
    wp_enqueue_style( 'twentyfifteen-style', get_stylesheet_uri() );

    // Load the Internet Explorer specific stylesheet.
    wp_enqueue_style( 'twentyfifteen-ie', get_template_directory_uri() . '/css/ie.css', array( 'twentyfifteen-style' ), '20141010' );
    wp_style_add_data( 'twentyfifteen-ie', 'conditional', 'lt IE 9' );

    // Load the Internet Explorer 7 specific stylesheet.
    wp_enqueue_style( 'twentyfifteen-ie7', get_template_directory_uri() . '/css/ie7.css', array( 'twentyfifteen-style' ), '20141010' );
    wp_style_add_data( 'twentyfifteen-ie7', 'conditional', 'lt IE 8' );

    // styles custom_field
    if (!empty($custom_fields['bn_styles'])) {
        foreach ( $custom_fields['bn_styles'] as $key => $value ) {
            wp_enqueue_style('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/css/' . $value, false, null);
        }
    }

    wp_enqueue_script( 'twentyfifteen-skip-link-focus-fix', get_template_directory_uri() . '/js/skip-link-focus-fix.js', array(), '20141010', true );

    if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
        wp_enqueue_script( 'comment-reply' );
    }

    if ( is_singular() && wp_attachment_is_image() ) {
        wp_enqueue_script( 'twentyfifteen-keyboard-image-navigation', get_template_directory_uri() . '/js/keyboard-image-navigation.js', array( 'jquery' ), '20141010' );
    }

    wp_enqueue_script( 'twentyfifteen-script', get_template_directory_uri() . '/js/functions.js', array( 'jquery' ), '20150330', true );
    //wp_enqueue_script( 'twentyfifteen-script', get_template_directory_uri() . '/js/main.js', array( 'jquery' ), '20150527', true );
    wp_localize_script( 'twentyfifteen-script', 'screenReaderText', array(
        'expand'   => '' . __( 'expand child menu', 'twentyfifteen' ) . '',
        'collapse' => '' . __( 'collapse child menu', 'twentyfifteen' ) . '',
    ) );

    // scripts custom_field
    if (!empty($custom_fields['bn_scripts'])) {
        foreach ( $custom_fields['bn_scripts'] as $key => $value ) {
            wp_enqueue_script('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/js/' . $value, [], null, true);
        }
    }
}
add_action( 'wp_enqueue_scripts', 'twentyfifteen_scripts' );

Handle Script Dependencies

A nice feature of the wp_enqueue_script function is that it allows us to pass in dependencies as a parameter. The optional dependencies parameter consists of an array of handles that map to javascript files. To apply this feature to our scripts custom field code, we need to create a $deps array that will retrieve optional handles added to the custom field value. Replace the code block at the bottom of function twentyfifteen_scripts() as follows:

REPLACE
    // scripts custom_field
    if (!empty($custom_fields['bn_scripts'])) {
        foreach ( $custom_fields['bn_scripts'] as $key => $value ) {
            wp_enqueue_script('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/js/' . $value, [], null, true);
        }
    }
WITH
    // scripts custom_field
    if (!empty($custom_fields['bn_scripts'])) {
        foreach ( $custom_fields['bn_scripts'] as $key => $value ) {
            $script = explode(',', $value);
            $deps = array();
            foreach ( $script as $i => $v ) {
                if ($i > 0) { // values after $script[0]
                    $deps[] = trim($v);
                }
            }
            wp_enqueue_script('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/js/' . $value, $deps, null, true);
        }
    }

For a detailed list of handle names that can be used, see Handles and Their Script Paths Registered by WordPress.

6. Here is the entire twentyfifteen_scripts function after updating the scripts custom field parser to handle script dependencies:

function twentyfifteen_scripts() {

    // retrieve custom fields from the current post
    $custom_fields = get_post_custom();

    // Add custom fonts, used in the main stylesheet.
    wp_enqueue_style( 'twentyfifteen-fonts', twentyfifteen_fonts_url(), array(), null );

    // Add Genericons, used in the main stylesheet.
    wp_enqueue_style( 'genericons', get_template_directory_uri() . '/genericons/genericons.css', array(), '3.2' );

    // Load our main stylesheet.
    wp_enqueue_style( 'twentyfifteen-style', get_stylesheet_uri() );

    // Load the Internet Explorer specific stylesheet.
    wp_enqueue_style( 'twentyfifteen-ie', get_template_directory_uri() . '/css/ie.css', array( 'twentyfifteen-style' ), '20141010' );
    wp_style_add_data( 'twentyfifteen-ie', 'conditional', 'lt IE 9' );

    // Load the Internet Explorer 7 specific stylesheet.
    wp_enqueue_style( 'twentyfifteen-ie7', get_template_directory_uri() . '/css/ie7.css', array( 'twentyfifteen-style' ), '20141010' );
    wp_style_add_data( 'twentyfifteen-ie7', 'conditional', 'lt IE 8' );

    // styles custom_field
    if (!empty($custom_fields['bn_styles'])) {
        foreach ( $custom_fields['bn_styles'] as $key => $value ) {
            wp_enqueue_style('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/css/' . $value, false, null);
        }
    }

    wp_enqueue_script( 'twentyfifteen-skip-link-focus-fix', get_template_directory_uri() . '/js/skip-link-focus-fix.js', array(), '20141010', true );

    if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
        wp_enqueue_script( 'comment-reply' );
    }

    if ( is_singular() && wp_attachment_is_image() ) {
        wp_enqueue_script( 'twentyfifteen-keyboard-image-navigation', get_template_directory_uri() . '/js/keyboard-image-navigation.js', array( 'jquery' ), '20141010' );
    }

    wp_enqueue_script( 'twentyfifteen-script', get_template_directory_uri() . '/js/functions.js', array( 'jquery' ), '20150330', true );
    //wp_enqueue_script( 'twentyfifteen-script', get_template_directory_uri() . '/js/main.js', array( 'jquery' ), '20150527', true );
    wp_localize_script( 'twentyfifteen-script', 'screenReaderText', array(
        'expand'   => '' . __( 'expand child menu', 'twentyfifteen' ) . '',
        'collapse' => '' . __( 'collapse child menu', 'twentyfifteen' ) . '',
    ) );

    // scripts custom_field
    if (!empty($custom_fields['bn_scripts'])) {
        foreach ( $custom_fields['bn_scripts'] as $key => $value ) {
            $script = explode(',', $value);
            $deps = array();
            foreach ( $script as $i => $v ) {
                if ($i > 0) { // values after $script[0]
                    $deps[] = trim($v);
                }
            }
            wp_enqueue_script('twentyfifteen_custom_field_' . $key, get_template_directory_uri() . '/js/' . $value, $deps, null, true);
        }
    }
}
add_action( 'wp_enqueue_scripts', 'twentyfifteen_scripts' );
This code is also available at gist.github.com

7. Now the bn_scripts custom field can be updated to include dependencies. List the dependency handles after the script separated by a comma as shown below.

bn_styles and bn_scripts custom fields added
WordPress editor – bn_scripts custom field dependencies added

More often than not jQuery will already be loaded. However, if your script is dependent upon jQuery you should add its handle to the dependencies comma separated list in the bn_scripts custom field value. wp_enqueue_script prevents double loading of scripts and their dependencies.

Given the default theme location, if you view source of the page that has the custom fields added and search for tree.css or tree.js, they should be linked to /wp-content/themes/twentyfifteen/css/tree.css and /wp-content/themes/twentyfifteen/js/tree.js. Any dependencies will also be linked.


Bourboneat version

If you would like to explore using this code with the bourboneat WordPress starter theme, here is the modified /functions/asset.php assets function that is used to enqueue stylesheets and scripts:

function assets() {
    wp_enqueue_style('bn_css', asset_path('styles/main.css'), false, null);

    $custom_fields = get_post_custom();

    // styles custom_field
    if (!empty($custom_fields['bn_styles'])) {
        foreach ( $custom_fields['bn_styles'] as $key => $value ) {
            wp_enqueue_style('bn_custom_field_' . $key, asset_path('styles/' . $value), false, null);           
        }
    }

    if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
        wp_enqueue_script( 'comment-reply' );
    }

    //wp_enqueue_script('modernizr', asset_path('scripts/modernizr.js'), [], null, true);
    wp_enqueue_script('bn_js', asset_path('scripts/main.js'), ['jquery'], null, true);

    // scripts custom_field
    if (!empty($custom_fields['bn_scripts'])) {
        foreach ( $custom_fields['bn_scripts'] as $key => $value ) {
            $script = explode(',', $value);
            $deps = array();
            foreach ( $script as $i => $v ) {
                if ($i > 0) { // values after $script[0]
                    $deps[] = trim($v);
                }
            }
            wp_enqueue_script('bn_custom_field_' . $key, asset_path('scripts/' . $script[0] ), $deps, null, true);
        }
    }
}

Bourboneat WordPress Theme

I have created a new WordPress starter theme for building custom themes designed for WordPress version 4.1 and later. The theme uses the Bourbon Sass library and it’s Neat semantic grid components for a lightweight and modular responsive design. The header and footer designs were taken from the Bourbon Refills collection. Also included are IcoMoon font icons which can be easily updated using the IcoMoon app. Gulp’s build system is used to compile and minify Sass and Javascript modules into optimized CSS and Javascript. Clone, fork or download this responsive starter theme at GitHub and modify it as you wish into the theme you want.

https://github.com/jimfrenette/bourboneat

Features

New functions and feature enhancements in WordPress version 4.1 include:

Menus

The theme contains four wp_nav_menu’s, the main menu supports nested drop downs. Below are desktop previews of these responsive menus which collapse as needed on mobile screen sizes.

Main Menu (header.php)
Main Menu preview (header.php)

The footer contains three of the four registered wp_nav menus. Social links and terms menus are shown below. Also shown here above the footer is the pagination nav courtesy of WordPress – Twenty Fifteen. This is a new feature available in WordPress 4.1 and later.

Social and Terms Menu (footer.php)
Social and Terms Menu preview (footer.php)

Populating the social links menu is easy. All that is needed is a menu that contains custom links to social networks. The href attribute of the link is selected to determine which icon is rendered. Here is a snippet of the Sass that shows how this works:

// snippet from _nav-social.scss

a[href*="codepen.io"]:before {
    @include icon-codepen;
}
a[href*="dribbble.com"]:before {
    @include icon-dribbble;
}
a[href*="dropbox.com"]:before {
    @include icon-dropbox;
}
a[href*="facebook.com"]:before {
    @include icon-facebook;
}
a[href*="plus.google.com"]:before {
    @include icon-google-plus;
}
a[href*="github.com"]:before {
    @include icon-github;
}
a[href*="instagram.com"]:before {
    @include icon-instagram;
}
a[href*="linkedin.com"]:before {
    @include icon-linkedin;
}

This figure shows the footer menu setup:

Footer menu edit screen
Footer menu edit screen

This figure shows the terms footer menu setup:

Terms footer menu edit screen
Terms footer menu edit screen

This figure shows the manage menu locations setup:

Terms footer menu edit screen
Manage menu locations screen
UPDATE – October 7, 2015

The bourboneat theme recently passed the WordPress.org code review and approval process and is now included in the WordPress.org Theme Directory at WordPress – bourboneat – Free WordPress Themes.

WordPress Theme Javascript Optimization

This post shows how to combine and minify multiple javascript files in a WordPress theme into one javascript file. The benefit is a single request to a javascript file that has been compressed by minification instead of multiple request to larger javascript files.

package.json

The package.json file contains meta data about your app or module and it includes the list of dependencies to install from Node Package Manager (NPM) when running npm install. NPM is bundled with Node.js; if you have not done so already, install Node.js so you have it. Then create and save this file in your themes root directory, for example /wp-content/my-theme/package.json. Then when you run npm install, the package.json file is read and the respective node modules are installed. More information is available here.

/* package.json */
{
  "name": "my-theme",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "~0.4.5",
    "grunt-contrib-jshint": "~0.10.0",
    "grunt-contrib-uglify": "~0.6.0"
  }
}
# install node_modules
$ npm install
gruntfile.js

The Grunt Javascript Task Runner will be used to configure and run the minification of /js/src/*.js into /js/main.js using UglifyJs.

/* gruntfile.js */
module.exports = function(grunt) {
  'use strict';
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    uglify: {
      build: {
        files: {
          'js/main.js': ['js/src/*.js']
        }
      }
    }
  });
  // Load the plugin
  grunt.loadNpmTasks('grunt-contrib-uglify');
  // Default task(s).
  grunt.registerTask('default', ['uglify']);
};
file structure

Here is what the themes javascript file structure looks like for this example. The /js/src folder contains all of the individual javascript files for your theme. When you run grunt, these are all combined and minified into /js/main.js

javascript file location
Partial view of themes files showing location of javascript
functions.php

The functions.php file controls the loading of the themes javascript resources. In the functions.php file, the wp_enqueue_script() function links the script to the page. The pre-existing wp_enqueue_script() function calls are no longer valid since the javascript files have been moved to the /js/src/ folder. Here is an example of the code from the pre-existing functions.php that was linking the scripts:

function my-theme_scripts() {
  wp_enqueue_style( 'my-theme-style', get_stylesheet_uri() );

  wp_enqueue_script( 'my-theme-deflist', get_template_directory_uri() . '/js/deflist.js', array('jquery'), '20141011', true );

  wp_enqueue_script( 'my-theme-navigation', get_template_directory_uri() . '/js/navigation.js', array(), '20120206', true );
  
  wp_enqueue_script( 'my-theme-skip-link-focus-fix', get_template_directory_uri() . '/js/skip-link-focus-fix.js', array(), '20130115', true );

  if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
	wp_enqueue_script( 'comment-reply' );
  }
}
add_action( 'wp_enqueue_scripts', 'my-theme_scripts' );

Here is an example of the code from functions.php after making the changes to link /js/main.js instead. Since /js/src/deflist.js depends on jQuery, so does /js/main.js and the $deps array parameter is set to ‘jquery’. For more information, refer to the wp_enqueue_script function reference

function my-theme_scripts() {
  wp_enqueue_style( 'my-theme-style', get_stylesheet_uri() );

  wp_enqueue_script( 'my-theme-main', get_template_directory_uri() . '/js/main.js', array('jquery'), '20141011', true );

  if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
	wp_enqueue_script( 'comment-reply' );
  }
}
add_action( 'wp_enqueue_scripts', 'my-theme_scripts' );

Time to Grunt

Run Grunt when you are ready to create or overwrite /js/main.js

# run the default task(s)
$ grunt

Resources

Virtual Machine for LAMP Development

This blog post addresses a common scenario — a local LAMP development environment on your Windows or OS X computer. You could use XAMPP on either Windows or OS X, MAMP on OS X or native Apache in OS X. Here, we will create a Linux virtual machine with Samba configured to share the Virtual Machine file system with the host computer. We will also create virtual hosts, install and configure WordPress and Xdebug.

First thing you will need is Virtual Machine software. For my Virtual Machine, I chose to install the free VMware Player. If your host operating system is OS X, you can install VirtualBox if you do not want to purchase VMware Fusion or Parallels. You could also use VirtualBox on a Windows host instead of VMware if you prefer.

Next, you will need to create a Linux virtual machine. I decided to Download Ubuntu Server 32 bit since it is compatible with the intel processor of my host computer.

Install SSH

After creating a new VM and installing Linux, time to get our dev environment setup. Install SSH so we can run commands from the host. Later we will use ssh to to tunnel the Xdebug connection from the VM back to the host.

# update the distro
$ apt-get update
$ apt-get upgrade -y

# install SSH
$ sudo apt-get -y install openssh-client openssh-server

Get the IP address of the Linux virtual machine.

# lookup inet addr ip 
$ ifconfig eth0

From the host, open a terminal and SSH into the virtual machine. In a Windows host, you can use Cygwin, PuTTY or a Git Bash.

$ ssh jim@192.168.59.129
...
jim@192.168.59.129's password:
Welcome to Ubuntu 12.04.3 LTS (GNU/Linux 3.8.0-29-generic i686)

 * Documentation:  https://help.ubuntu.com/
Last login: Sat Nov  2 12:55:45 2013
jim@ubuntu:~$

Install LAMP server

Read my blog post for more on how to install the LAMP server and phpMyAdmin: Mint LAMP Development Environment

# install LAMP server 
$ sudo apt-get install lamp-server^

Restart Apache and test the web server root URL from a host browser using the virtual machines IP address, e.g., http://192.168.59.129/.

$ sudo /etc/init.d/apache2 restart

Virtual Hosts

# edit the Apache config
$ sudo nano /etc/apache2/apache2.conf

# in apache2.conf, add these virtual host settings below the files node
NameVirtualHost *:80

<VirtualHost *:80>
   DocumentRoot /var/www
   ServerName www.ubuntu.vm
   ServerAlias ubuntu.vm
</VirtualHost>

<VirtualHost *:80>
   DocumentRoot /var/www/wordpress
   ServerName www.wordpress.vm
   ServerAlias wordpress.vm
</VirtualHost>

Edit your hosts file in your host operating system to map to the virtual hosts you specified in the Apache config of the Linux virtual machine.

192.168.59.129  ubuntu.vm
192.168.59.129  wordpress.vm

Since we have set the hosts file to map 192.168.59.129 to ubuntu.vm, once you disconnect your current ssh session (Ctrl+D), you will need to use the host alias you specified in the hosts file to reconnect.

ssh jim@ubuntu.vm

Samba Network Share

You can use Samba to share the /var folder in you virtual machine.

# install Samba 
$ sudo apt-get install samba

Linux system permissions take precedence over Samba permissions. For example if a directory does not have Linux write permission, setting samba writeable = Yes will not allow to write to shared directory / share.

# edit Samba config 
$ sudo nano /etc/samba/smb.conf

####### Authentication #######
  security = user

# bottom of smb.conf
[etc]
    path = /etc
    browsable = yes 
    read only = no 
    create mask = 0755 
    admin users = jim

[home]
    path = /home
    browsable = yes 
    read only = no 
    create mask = 0755 
    admin users = jim

[var]
    path = /var
    browsable = yes 
    read only = no 
    create mask = 0755 
    admin users = jim 

Ctrl+C to exit nano, press Y to save and return to write your changes to the file.

Install the PAM authentication module for Samba which will sync the system users to the Samba user database.

# Install libpam-smbpass package
$ sudo apt-get install libpam-smbpass

# restart Samba for the new settings to take effect
$ sudo restart smbd
$ sudo restart nmbd

# set user jim as the owner of share directory
$ sudo chown -R jim /var/www

For OS X, open Finder, select Go > Connect to Server (command + K). Enter the server address: smb://ubuntu.vm and select Connect

OS X - Connect to Server Dialog
OS X – Connect to Server Dialog

For Windows, select Run from the Start menu or open Windows Explorer and enter:

\\ubuntu.vm\www
Windows Run Dialog
Windows Run Dialog

Server Time Synchronisation

Server time is likely to become inaccurate in our development scenario. We can use Ubuntu’s NTP server and NTP’s server pool to keep the server datetime accurate.

# install ntp
$ sudo apt-get install ntp

# edit /etc/ntp.conf
$ sudo nano /etc/ntp.conf
# add these two lines
server ntp.ubuntu.com
server pool.ntp.org

# change server time zone
$ sudo dpkg-reconfigure tzdata

Enabling Module Rewrite

The mod_rewrite Apache module is a rule-based rewriting engine to rewrite requested URLs. It is very useful for genarating SEO friendly URLs.

# enable the module 
$ sudo a2enmod rewrite

# restart apache to activate the configuration change
$ sudo service apache2 restart

Install WordPress

# download the latest WordPress to a downloads directory
$ mkdir ~/downloads
$ cd ~/downloads

$ wget http://wordpress.org/latest.tar.gz

# -z : Uncompress the resulting archive with gzip command.
# -x : Extract to disk from the archive.
# -v : Produce verbose output i.e. show progress and file names while extracting files.
# -f latest.tar.gz : Read the archive from the specified file.
# -C : To change the directory (current directory is default)

# this will create /var/www/wordpress since the archive root directory is wordpress 
$ sudo tar -zxvf latest.tar.gz -C /var/www

# make yourself the owner
sudo chown jim /var/www/wordpress

Create Database

Create MySql database, user and grant permissions in one line.
base_user is typically root
base_user_pass is password for base_user (no space between -p and base_user_pass is important)

mysql -u base_user -pbase_user_pass -e "create database new_db; GRANT ALL PRIVILEGES ON new_db.* TO new_db_user@localhost IDENTIFIED BY 'new_db_user_pass'"

phpMyAdmin

If phpMyAdmin is installed, you can use it to create the wordpress database as described in this next section. Read my earlier blog post if you need help doing this, Mint LAMP Development Environment

In the browser, goto http://ubuntu.vm/phpmyadmin/. Select the Databases tab and under “Create new database”, enter in a database name such as “wordpress”, and select Create. Collation will automatically be assigned by MySQL when the database tables are created, during the WordPress installation.

# copy WordPress sample config
$ cd /var/www/wordpress
$ cp wp-config-sample.php wp-config.php

In the browser, goto http://wordpress.vm and start the installation. If you need to manually edit the wp-config.php file, you can do so with nano.

$ nano wp-config.php 

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'database_name_here');

/** MySQL database username */
define('DB_USER', 'username_here');

/** MySQL database password */
define('DB_PASSWORD', 'password_here');

/** MySQL hostname */
define('DB_HOST', 'localhost');

After saving wp-config, complete the WordPress installation at http://wordpress.vm

You can backup the MySql database using mysqldump within your ssh session. For example, replace mysql_username with your username and mysql_password with your password:

$ mysqldump -h localhost -u mysql_username -pmysql_password wordpress > /home/wordpress-$(date +"%Y-%m-%d").sql

WordPress Revisions Cleanup

Run this sql query in phpMyAdmin to delete all revisions from each post, including all of its meta data.

DELETE a,b,c
FROM wp_posts a
LEFT JOIN wp_term_relationships b ON (a.ID = b.object_id)
LEFT JOIN wp_postmeta c ON (a.ID = c.post_id)
WHERE a.post_type = 'revision'

Xdebug

$ sudo apt-get install php5-xdebug

Check Xdebug

$ php --version

PHP 5.3.10-1ubuntu3.8 with Suhosin-Patch (cli) (built: Sep  4 2013 20:05:42)
Copyright (c) 1997-2012 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2012 Zend Technologies
    with Xdebug v2.1.0, Copyright (c) 2002-2010, by Derick Rethans

Configure Xdebug

$ sudo nano /etc/php5/conf.d/xdebug.ini

Add the following xdebug config values after the zend_extension path value so it looks like this.

zend_extension=/usr/lib/php5/20090626+lfs/xdebug.so
xdebug.remote_enable=1
xdebug.remote_handler=dbgp
xdebug.remote_mode=req
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9000

When debugging from the host computer, use ssh to tunnel the debug-connection back to it.

# ssh remote port forwarding
ssh -R 9000:localhost:9000 jim@ubuntu.vm

Using the “-R” argument tells ssh to listen on TCP port 9000 on the ubuntu server and forward it back through the ssh connection to the TCP port specified (localhost:9000). So from Xdebug’s perspective on the Ubuntu server, it is talking to localhost on the local computer.

More info: Mint LAMP Development Environment – Part Two | Xdebug

Other PHP debugging options

You could use the FirePHP Extension for Firefox to log to your Firebug Console using a simple PHP method call. There is also a SublimeTextXdebug package for Sublime Text.

If all you have is ssh and nano

<?php var_dump(__FILE__, __LINE__, $_REQUEST); ?>
or
<?php
echo '<pre>';
print_r( $somevariable );
echo '</pre>';
?>

Resources

Using jQuery in WordPress Posts

This post by Chris Coyier shows how to use jQuery inside a WordPress post. This helped me work around an Apache web server security configuration issue that was not allowing me to post SQL code snippets inside of my posts.

403 response when I try to post certain SQL code snippets:


Forbidden

You don’t have permission to access /wp-admin/post.php on this server.


First, follow the instructions in Chris’s post which basically has you modify the your themes header.php to load jQuery that is included with WordPress just before the wp_head() function call as follows.

Replace
<?php wp_head(); ?>;
With
<?php wp_enqueue_script("jquery");
wp_head(); ?>;

Next, create a htm file to hold SQL code snippets. The pre element id attribute allows for jQuery loading of individual fragments instead of the entire .htm file. Additional SQL code snippets can be added to this file each separated by a pre element with a unique id attribute:

<html>
	<body>
		<pre id="sp_Procedure1">
		<!-- insert SQL Code here -->
		</pre>
		<pre id="sp_Procedure2">
		<!-- insert SQL Code here -->
		</pre>
	</body>
</html>

Add a placeholder div to your post for the jQuery load target:

<div id="sql"></div>

Now add this JavaScript to the bottom of your post to load the .htm fragment*.

<script type="text/javascript">
var $j = jQuery.noConflict();
$j(function(){
	$j('#sql').load('//jimfrenette.com/wp-content/includes/sql.htm #sp_Procedure2');
});
</script>

*Change the path as needed. More info: jQuery .load().