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

Twenty Seventeen 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.

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.


Development Environments

Docker

docker-compose.yml

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));

Twenty Sixteen 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.

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('http://jimfrenette.com/wp-content/includes/sql.htm #sp_Procedure2');
});
</script>

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