Emma Goodwin is a Drupal developer based in Bristol. She specialises in backend development, Linux server administration and making the perfect cup of tea.

She's currently working with a luxury travel agency in London, helping them port their old legacy site over to Drupal.

This site tumblr feed will focus on Emma's biggest interests:
Bash scripts
Drupal 6, 7 & 8
Continuous Integration
Behaviour Driven Development

Using mod_rewrite to preserve url parameters (such as gclid) throughout your site

I had a problem where I needed to preserve a parameter from a referring URL, throughout the site, but only if it existed.

At the time, I couldn’t find an elegant solution online. The best solution I found used JQuery to modify the internal links. I wasn’t keen on this solution as it was, in my opinion, adding unnecessary load to the front end. It also felt incredibly ‘hacky’ and of course, this wouldn’t work if the user had disabled javascript.

After a bit of trial and error, I figured out a solution which utilised mod_rewrite:

By using the QSA flag, this rewrite rule also preserves any existing arguments:

Current URL: www.example.com/my-ad-campaign?gclid=1234

Requested URL: www.example.com/articles/sunrise?type=foo

Delivered URL: www.example.com/articles/sunrise?type=foo&gclid=1234

My 2 minute lightweight OmniFocus 2 sync server setup using WebDAV on Mavericks

There are a few tutorials online that show you how to setup a WebDAV share for OmniFocus 2. However, they involve installing additional software (such as OSX Server or Mamp).

This two minute tutorial shows you how to setup a WebDAV share by utilising the apache HTTP server pre-installed on OS X Mavericks.

Enable WebDAV
Edit the apache config and uncomment the following lines:
sudo vim /etc/apache2/httpd.conf
LoadModule dav_module libexec/apache2/mod_dav.so
Include /private/etc/apache2/extra/httpd-dav.conf

Configure WebDAV
Edit the WebDAV config to create your WebDAV share:
sudo vim /etc/apache2/extra/httpd-dav.conf

Create user account and password
sudo htpasswd -c /usr/omnifocus.passwd "insparrow"

Create directories and set permissions
sudo mkdir -p /Library/WebServer/OmniFocus /usr/var
sudo chown -R www:www /Library/WebServer/OmniFocus /usr/var
sudo chgrp www /usr/omnifocus.passwd

Start Apache
sudo apachectl start

Configure OmniFocus to look at your new WebDAV share and you’re good to go:


You can also sync your iPhone / iPad to the same WebDAV share by replacing with the IP address of your Mac. I have setup a static IP address for this:


Or better still, use your Mac’s hostname: 

Set dynamic $SERVER variable according to your Wifi SSID

This bash tweak sets a $SERVER variable which holds your internal / external IP address according to the Wifi network you are currently connected to.

A great time saver when you frequently connect to a home / office server from a remote connection. This tweak also lends itself to simplifying any remote scripts which use rsync or scp.

Simple add the following to your ~/.bash_profile

Reload bash with . ~/.bash_profile and you’re good to go:
ssh user@$SERVER

Credit: I originally got the idea from @climagic:

How to utilise pbcopy on a remote server

Brett Terpstra’s post details the best way to setup remote pbcopy. This post is merely a 2 minute setup guide for the TL;DR among us.

On your local machine
vim ~/Library/LaunchAgents/pbcopy.plist

launchctl load ~/Library/LaunchAgents/pbcopy.plist
echo 'RemoteForward 2224' >> ~/.ssh/config

On your remote machine
ssh user@remote-mac.co.uk
sudo vim /usr/local/bin/rpbcopy

sudo chmod a+x /usr/local/bin/rpbcopy
echo 'alias pbcopy="/usr/local/bin/rpbcopy"' >> ~/.bash_profile
. ~/.bash_profile

Setup complete. Anything piped to pbcopy will be available in your local clipboard:

echo "Lorem ipsum was here" >> file.txt
cat file.txt | pbcopy

How to use password protected SSH keys in cron jobs

Anybody who knows me well enough will know that I would not entertain the thought of a password-less SSH Key, let alone use one. However, creating an automated backup script which uses a password protected SSH Key on a Mac was tricker than I thought. Thanks to this this blog post, I figured out a solution which works on a Mac running Mountain Lion:

brew install keychain
vim $HOME/.bash_profile

keychain $HOME/.ssh/id_rsa
source $HOME/.keychain/$HOSTNAME-sh

Open up terminal, you will be prompted for your ssh key password:


Once keychain has added your ssh key, you will not need to enter your password again until you reboot your server:


After you have modified your backup script to use keychain:

You can then add a cron job for your backup script as normal:
crontab -e
*/15 * * * * /path/to/backup.sh

Profiling PHP with XDebug, QCacheGrind (KCacheGrind) and Vagrant on Mountain Lion

Installing XDebug and KCacheGrind is normally a straightforward task. However, I found it particularly fiddly to get working on a Mac (running Mountain Lion) with Vagrant.

A few VMs later, I figured out the quickest way to achieve this setup:

SSH onto Vagrant VM and install XDebug
cd vbox
vagrant ssh 
sudo apt-get install php5-xdebug

Create a writable directory for cachegrind files
mkdir /vagrant/cachegrind
chmod 777 /vagrant/cachegrind

Add the following to php.ini to enable XDebug profiler
sudo vim /etc/php5/apache2/php.ini

xdebug.profiler_enable = 1
xdebug.profiler_append = 1
xdebug.profiler_output_dir = "/vagrant/cachegrind"
xdebug.profiler_output_name = "cachegrind.out.%t.%p"

Restart apache
sudo service apache2 restart

Install QCacheGrind on Mac
brew install qcachegrind
brew linkapps

Install Graphviz (req. to produce graphs)
brew install graphviz

By default, Graphviz installs the dot binary to /usr/local/bin/dot, which QCachGrind can’t find. To get the graphs working, you need to add the following symlink:
sudo ln -s /usr/local/bin/dot /usr/bin/dot

Refresh website, fire up QCacheGrind.app, open cachegrind file and you’re good to go:


A quicker way to revert all Drupal features

Reverting all features seems to take a very long time. Intrigued whether any time could be saved by only reverting the features marked as ‘Overridden’, I created a quick bash script:

Reverting only the ‘Overridden’ features is around 45 seconds quicker (when compared to executing drush fra -y):


On average, the Drupal site I am working on is rebuilt around 20 times a day. That’s a 1¼ hr time saving across a working week!

I don’t know if this method will provide time savings as large as this to all versions of features. This has only been tested with 7.x-1.0-rc3. However, it is definitely worth trying.

Quick < 10 second guide to installing and using Slate (window manager for OSX)

I will be the first to admit that I am a bit of a ‘productivity geek’ when it comes to my mac workflow. I believe that having the right tools to multi task well and using your mouse less, are key to a productive day in the office.

I have used both Optimal Layout and Divvy for some time, but I have been left somewhat dissatisfied with the lack of configuration available. Slate on the other hand is a programmer’s dream.

cd /Applications && curl http://www.ninjamonkeysoftware.com/slate/versions/slate-latest.tar.gz | tar -xz && curl https://raw.github.com/jigish/slate/master/Slate/default.slate > ~/.slate && echo -e "\n # Show Grid \n bind g:cmd grid\n" >> ~/.slate && echo "Slate is now installed"

Launch Slate, hit CMD + G and you’re good to go!


Slate is not only more configurable than the popular alternatives, it is also free!

This post is only intended as a quick guide to get you up and running. To configure Slate further, I highly recommend reading Tristan Hume’s post.

How to delete / remove a field from a content type stored in a feature (Drupal)

When you remove a field from a content type and update the feature, this does not remove the fieldRemoving a field which is shared across several content types presents an even bigger problem. Normally you can just delete a field using field_delete_fieldTo remove a shared field, you need to remove an instance of the field.

After looking up how Drush perfoms this task, it appears that you have to first mark the field for deletion, then call cron or field_purge_batch to actually remove the field.

What worked for me
After removing the field from the feature (by deleting it from the content type and executing drush fu my_feature -y),  adding the following gist in an update hook did the trick for me.