WordPress & Git

Many of our web projects are built on WordPress. Therefore, an efficient and seamless development workflow is vital for us. In this blog post, we will take a look at how to use WordPress in combination with git.

WordPress is one of the most popular open source content management systems currently available. A large part of the web is built with WordPress. This is due to its excellent user-friendliness and ease of use. Furthermore, it is quite easy to install and to work with. However, if your goal is to implement larger projects based on WordPress, which would involve several developers working on it simultaneously, you might want to consider using a version control system. WordPress was mainly designed to be a webblog software and not to be used as a web-framework in the first place. But now, in the course of time, it has evolved into a fully-fledged, rock solid content managment system. Due to this history, it does not provide an optimal development workflow, especially when using version control. In the following article, I would like to briefly introduce my approach of how to keep WordPress projects under version control.

Then main question we want to ask is this: when managing our projects with git, which files should be put under version control and which files should be ignored. As a rule of thumb we can say: Only application-specific source code files! In no case should configuration files, libraries on which the project depends or content-specific files be included. Now, how can we apply this idea in the case of a WordPress project? Let’s take a look at how a typical WordPress installation is usually structured:

wp-admin/
wp-content/
wp-includes/
index.php
license.txt
readme.html
wp-activate.php
wp-blog-header.php
wp-comments-post.php
wp-config-sample.php
wp-cron.php
wp-links-opml.php
wp-load.php
wp-login.php
wp-mail.php
wp-settings.php
wp-signup.php
wp-trackback.php
xmlrpc.php

The project-specific files, which are adapted or created during development, are usually part of a theme and/or plugin and are contained in the wp-content folder. Everything else belongs to the WordPress core and should not be modified.  Thus, I would consider the wordpress core a dependency and not include it in the version control. Since the theme and plugin files are located in the wp-content folder within the installation directory, our goal is now to seperate dependecies and application-specific code from each other. The wp-content directory should no longer reside in the Worpress directory. Consequently, we aim at the following file structure:

wp/
wp-content/
    - languages/
    - plugins/
    - themes/
index.php
wp-config.php

The wp folder should contain the WordPress core and the wp-content folder should contain the theme and plugin files we want to include in git. Since we just changed the directory structure, we also need to tell Worpress where to look for the wp-content folder. This can be achieved by means of the wp-config.php file, which should be copied from the WordPress core folder. The following changes have to be made:

/** Set Custom Content Directory. */
define( 'WP_CONTENT_DIR', dirname( __FILE__ ) . '/wp-content' );
define( 'WP_CONTENT_URL', 'http://' . $_SERVER['HTTP_HOST'] . '/wp-content' );


/** Set Home and Siteurl. */
define('WP_HOME','http://' . $_SERVER['HTTP_HOST']);
define('WP_SITEURL','http://' . $_SERVER['HTTP_HOST'] . '/wp');

A similar procedure must be applied to the index.php file. Again, we should copy it from the WordPress installation folder and move it up one level within the structure of the repository. Here, we only need to adapt the include path to the entry-point file wp-blog-header.php.

<?php
define('WP_USE_THEMES', true);

/** Loads the WordPress Environment and Template */
require( dirname( __FILE__ ) . '/wp/wp-blog-header.php' );

A more profound explanation of how to give WordPress its own subdirectoy can be found here.

We have now successfully achieved the desired separation of dependencies and project-specific files. Next, we need to make sure the project can be installed and set up by any developer, despite the fact that we have left out necessary dependencies. At this point, a package manager comes in handy, which keeps track of the required files and libraries and installs them automatically during setup. One such package manager, which is suitable for WordPress or PHP projects, is Composer. By means of the file composer.json, you can specify all libraries which the project depends on. For composer to work correctly with WordPress, we need to specify WP Packagist as an additional package source repository. This repository contains all themes and plugins from the WordPress plugin and theme directory. Since WordPress itself is hosted in an SVN repository and due to the fact that there exists no official composer package for WordPress, the best solution is to use a fork of WordPress, which syncs itself every 15 minutes. While many such forks are available, we will use https://github.com/johnpbloch/wordpress, since it is the most accepted package at the moment. Let’s have a look at the composer.json file:

{
    "name": "mblum/test_wp",
    "authors": [
        {
            "name": "Mathias Blum",
            "email": "email"
        }
    ],
    "repositories": [
        {
            "type": "composer",
            "url": "https://wpackagist.org"
        }
    ],
    "require": {
        "johnpbloch/wordpress": "^4.9",
        "wpackagist-theme/twentyseventeen": "^1.6"
    },
    "extra": {
        "wordpress-install-dir": "public/wp",
        "installer-paths": {
            "public/wp-content/mu-plugins/{$name}/": ["type:wordpress-muplugin"],
            "public/wp-content/plugins/{$name}/": ["type:wordpress-plugin"],
            "public/wp-content/themes/{$name}/": ["type:wordpress-theme"]
        }
    }
}

Dependencies and their corresponding version numbers can be specified utilizing the “require” property. In this case I have set WordPress and the twentyseventeen theme as required files. Because we’re using a different file structure, we also need to tell composer where to install the dependencies. By means of the “extra” property, we can set the themes, plugins and WordPress core locations. Now, the structure of our git repository looks like this:


.git/
public/
vendor/
.gitignore
composer.json
composer.lock
wp-config-development.php
wp-config-sample.php

An important thing to note is that we need to make sure that the public folder is set as the web-root of our web server. It contains the previously discussed files. The vendor directory is needed by composer to install the dependencies. The following files are now specified in the .gitignore to be versioned:

/public/wp

/vendor

wp-config-*.php
!wp-config-sample.php

!public/wp-content/themes
public/wp-content/themes/*
!public/wp-content/themes/custom-theme

!public/wp-content/plugins
public/wp-content/plugins/*
!public/wp-content/plugins/custom-plugin

public/wp-content/uploads/*
!public/wp-content/uploads/.htaccess

!public/wp-content/languages
public/wp-content/languages/*

If you want to support multiple environments such as development, staging and production, here is how you can implement a loading procedure for different configuration files. Open the wp-config.php file and add the following lines:

/** Load environment specific config file. */
if ( file_exists( dirname( __FILE__ ) . '/../wp-config-production.php' ) ) {
    define( 'WP_LOCAL_DEV', false );
    include( dirname( __FILE__ ) . '/../wp-config-production.php' );
}
else if ( file_exists( dirname( __FILE__ ) . '/../wp-config-staging.php' ) ) {
    define( 'WP_LOCAL_DEV', false );
    include( dirname( __FILE__ ) . '/../wp-config-staging.php' );
}
else {
    define( 'WP_LOCAL_DEV', true );
    include( dirname( __FILE__ ) . '/../wp-config-development.php' );
}

Finally, here is how to deal with WordPress’ file uploads, if you don’t want to always synchronise your development and production environment. By utilizing the .htaccess file, you can define URL redirects, which checks if the requested file is available locally or loads it from the production server otherwise.


<IfModule mod_rewrite.c>
  RewriteEngine on

  # Attempt to load files from production if
  # they're not in our local version
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule wp-content/uploads/(.*) \
    http://{PROD}/wp-content/uploads/$1 [NC,L]
</IfModule>

You can also view the source code on Github (https://github.com/blumma/wp-template). I have set up a WordPress project template to easily get up and running.