Blog / How To

ExpressionEngine, Wygwam, CKFinder and MediaTemple problems…

I recently got bitten by a nasty problem on an ExpressionEngine site hosted at MediaTemple. All of a sudden they weren't able to upload images to a Wygwam field using the CKFinder setting for the File Browser. The error message stated:

The file browser is disabled for security reasons.
Please contact your system administrator and check the CKFinder configuration file.

Not exactly a helpful error message and definitely not the right solution. I went down the rabbit hole trying to sort this one out and it all comes down to how Wygwam figures out the path it should upload your file to when you use CKFinder as your file browser tool.

It does appear to be isolated to MediaTemple servers for some reason, but I am not entirely sure why.

So first, a little information about how Wygwam determines where to put your file. Wygwam depends on PHP session variables to know which folders in your file system are available to upload to for any given field. PHP creates a PHPSESSID cookie which matches a file saved to the server in your PHP session directory. Within that file are all the relevant details about the upload folders. When Wygwam goes to upload a file, it checks the session for the logged in user and figures out where to put the files.

My problem was due to the session data file being created on the server, but never being updated. The file was blank, so when Wygwam went to check for the directories it should have access to, it found a blank file and assumed that there weren't any files to upload to, which leads to the error.

So why was the file blank?

This took some digging, but I was able to solve the problem by removing a single line of code in a Wygwam file. Specifically /system/expressionengine/third_party/wygwam/helper.php. In version 3.3.2 of Wygwam, the line in question is 675:

	if (! isset($_SESSION)) @session_start();

This seems like a reasonable conditional, if the $_SESSION superglobal isn't set, let's create a session. However, it just doesn't work on the MediaTemple server this site is on. The $_SESSION IS set at this point, but there isn't anything in it. For whatever reason, on this server, the @session_start() call NEEDS to happen here or the rest of the script in helper.php will fail. So by changing line 675 to


All is well.

I'm not enough of a server admin to know exactly why this would be, but I'm pretty sure it has to be related to some configuration on the MediaTemple side of things. I'd love to know why this is so if anyone has a server side solution instead of changing the code, please leave it in the comments below!

For what it's worth. This error is present on:

  • ExpressionEngine 2.9.2
  • Wygwam 3.3.2
  • MediaTemple (gs) Grid-Service (PHP 5.3.29 CGI)

Make WordPress more like ExpressionEngine

The two CMS tools I use most often are WordPress and ExpressionEngine. They both have their pros and cons, but with WordPress being vastly more popular, I am constantly trying to figure out ways to bring some of the great ExpressionEngine features into WordPress.

Custom Content Types

One of the nicest aspects of EE is how easy it is to control a vast number of different types of content, and organize your site around the content differences and how they relate to each other. Out of the box, WordPress doesn't support this without getting your hands dirty in PHP, but it is possible to use a plugin to enable the creation of these Custom Post Types in WordPress. There are a bunch out there, but I think Custom Post Types UI is the simplest and easiest to use. It brings all the custom post type options into an easy to use graphical interface. You can also create new taxonomies (categories) to support your new content types. The ability to have custom content types to specifically support the goals of the site you're building is a requirement for a useful CMS. Once you have these content types set up, the real value lies in customizing the fields that make up the content type.

One downside I've found is that you don't have complete control over your URL structure when using custom post types so you have to give it some thought ahead of time. You also can't change the name of a post type on the fly and have it update the actual content so you need to be versed in MySQL to do changes like that once you have content in your site. ExpressionEngine handles all of this for you.

Custom Content Fields

Out of the box, WordPress supports a very basic custom field system, but it isn't nearly as intuitive or flexible as ExpressionEngine. Luckily, an amazing plugin is available to fill the gap and even exceed the EE functionality. This plugin has absolutely changed the way I build WordPress sites, to the point that I actually miss it when I'm back in an ExpressionEngine site. The plugin is Advanced Custom Fields and it is FREE. ACF lets you define custom fields in an unlimited number of ways: by type of content, by taxonomy, by single page id, site-wide, etc. Whereas EE only allows you to assign a custom field group to a single Channel, ACF and WordPress gives you complete control of your custom fields and how they are applied to your site structure. By default ACF comes with a ton of functionality, but you can also upgrade to their new ACF PRO tool and get four additional custom field types that really enhance what you can do with ACF. ACF PRO includes The Repeater field, The Gallery field, The Flexible Content field, and the Options Pages field. These addons approximate Matrix/Grid, Channel Images, Content Elements, and Low Variables.

Again, a downside: If you change the name of a custom field or remove it, the content still exists in the database so you'll need to remove it manually via MySQL. ExpressionEngine handles all of this for you.

Search Engine Optimization

While in ExpressionEngine you have complete control over how your site is templated and presented to search engines, it's not simple and often has various caveats that get in the way. There are a few SEO related plugins available, but in my mind, none of them come close to the usefulness of Yoast's SEO plugin for WordPress. Once installed, it gives an enormous amount of control over site SEO. A few niceties include automatic Facebook Open Graph meta data, Twitter card meta data and Google+ publisher tags. It also has a useful breadcrumb generator, an automatic XML sitemap generator, and very simple config tools for a variety of display options.

Forms and accepting data

ExpressionEngine has quite a few good form tools that allow you to accept data from site visitors, but none of them is as easy to use and extensible as Gravity Forms for WordPress. Getting a developer license for Gravity forms that you can use on unlimited sites is a no brainer. It easily allows integration with mailing lists like Campaign Monitor or Mailchimp, and even basic ecommerce through Stripe, PayPal or The backend form creation tool isn't perfect, but it's very easy to use and allows very quick form creation that just works.

ExpressionEngine and WordPress

I will continue adding to this post as I figure out more ways to bring some EE goodness into my WordPress sites.

Simple ExpressionEngine Stash Parse Order

Stash is a great addition to the ExpressionEngine toolkit, but sometimes the parse order can get a little confusing. One great thing about Stash is the ability to set variables at the top of a template that can be used later on in your code. This allows you to keep a nice separation between the data and the template itself. I'm not going to be talking about template partials at all as there are a bunch of great articles about that floating around. This post is just outlining a specific problem and it's solution.

If you need to use a Stash variable that was created within a Channel Entries loop earlier in a template as a parameter in a Channel Entries loop, you can delay the parse order of the second Channel Entries loop such that the variable gets set prior to the loop being executed. For example if you got a list of entry IDs of related entries (from Relationships or Playa) you could put those in the entry_id parameter and then print out the entries in the correct order. This could be used for categories, statuses, search values, whatever.

There are always a bunch of ways to do something in EE but this method has proven handy in several recent builds to get around needing embeds or other more complex nesting.

{exp:stash:parse process="end"}
  {exp:channel:entries channel="XXX" fixed_order="{stash:places}" entry_id="{stash:places}" dynamic="no"}

ExpressionEngine Pagination with Structure and Zoo Triggers

Using Structure with ExpressionEngine is one of the most client-friendly things you can do for your EE sites. It allows you to set up a nice tree view of your site content, complete with drag & drop, page additions, and a slew of nice display functions for the front end.

One big sticking point when using Structure is that it overrides your default EE URL system, which can be troublesome when you want to get things like categories and date based archives working within Structure.

Say you want to set up a WordPress-style date/category area of your site, like a blog or a news area. When I first got this working within Structure, it required a ton of ugly PHP to check segment values and display different content based on the results. Not very elegant and inefficient. I knew there had to be a better way.

After looking around for some solutions, I stumbled across Zoo Triggers. From the developer:

No more fiddling around and wasting time trying to get categories and archives to play nice with your Structure setup. Zoo Triggers is the add-on that fills the gap between Structure and 'categories and archives'. Within seconds you have 'category, archive and tags' filters on your base Structure install.

You can read more about the Zoo Triggers setup on their site, but it is dead simple. You just add a simple tag to your channel entries loop and it will automatically update your entries based on the URL. They even have handy Category and Date based tags to output your sub navigations. These come with a bunch of options to tailor your sub navigation to your site. The documentation could use some updating, as a bunch of options are not currently described. I'm sure this will be updated soon.

Using Zoo Triggers with Structure works remarkably well. Your entries are all controlled through Structure (as a listing beneath a main "blog" page). All you need are two templates. One template (blog_list) for both the main page and any date & category listing pages. This one page simply gets updated based on the URL structure to show the appropriate entries. Then a second template for the single entry page. You could probably do it all in one, but I like the separation. Set up your listing in Structure and then set the default template for the listing entries to be the single entry template (blog_single). Then just insert the navigation tags you want to use and you are pretty much done.

NOTE: Make sure you do NOT have Dynamic Channel Caching on. If you do, the pagination will not work. I'm not sure if this is a by product of Structure, Zoo Triggers, or just a problem with EE, but it tripped me up for a while. Basically the query for the first page gets cached and when you visit any other "page" in the section, it just shows the original entries that were cached. You can turn it off in Admin > Channel Administration > Global Preferences.

ExpressionEngine config.php

One of the biggest takeaways from the EECI 2011 Conference in Brooklyn for me was the use of a config.php file in ExpressionEngine builds. Matt Weinberg from the Vector Media Group gave a great presentation titled 10 Ways to Rock ExpressionEngine. Using a config.php file allows you to control the numerous configuration options in ExpressionEngine from a single text file. This allows for quick and easy updates, instant provisioning of a new site install, and easy movement from server to server.

I spent some time with different config.php approaches and have settled on a hybrid of some existing versions:

There are several other great resources out there for EE2 configuration:

I'm going to outline the different items that Pilotmade uses in our EE projects. We're always tweaking this, so this approach is where we are in January of 2015.

The idea is to create a file titled config.php and place it at the root level of your site. Within that file you enter the following sections of configuration. Once the file is setup, it has to be included (required) from both /system/expressionengine/config/config.php and /system/expressionengine/config/database.php using the following code. Just place this at the bottom of each file.

require(realpath(dirname(__FILE__) . '/../../../config.php'));

Note: when you go to update your ExpressionEngine install, you MAY have to comment out this code or your upgrade MAY go into an endless loop. If that is the case, your standard config and database files MUST be valid and have the correct database credentials for the server you are upgrading on. 

1. Dynamic paths

The first thing to do is to set some dynamic file system paths based on the server the site is located on. This allows us to automatically set things like URL folder paths later on.

$protocol                         = (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") ? "https://" : "http://";
$base_url                         = $protocol . $_SERVER['SERVER_NAME'];
$base_path                        = rtrim($_SERVER['DOCUMENT_ROOT'], "/");
$system_folder                    = "system";
$themes_folder                    = "themes";
$images_folder                    = "images";

$system_path                      = $base_path . "/" . $system_folder . "/";
$system_url                       = $base_url . "/" . $system_folder . "/";
$themes_path                      = $base_path . "/" . $themes_folder . "/";
$themes_url                       = $base_url . "/" . $themes_folder . "/";     
$images_path                      = $base_path . "/" . $images_folder . "/";
$images_url                       = $base_url . "/" . $images_folder . "/";

$user_agent                       = $_SERVER['HTTP_USER_AGENT'];

2. Environmental Variables (database)

This allows you to enter multiple database credentials based on the server you are on. This makes it very easy to move from one server to another when you launch a new site. This method uses the IP address of the server to determine which credentials to use. Depending on your setup, you may need to use a different method to check the server (hostname etc.), especially if your development and production installs are on the same server.

switch ( $_SERVER['SERVER_ADDR'] ) {

/* Local DB Config */ 
case '' : 
$db['expressionengine']['hostname'] = "localhost";
$db['expressionengine']['username'] = "username";
$db['expressionengine']['password'] = "password";
$db['expressionengine']['database'] = "database";
$env_global_vars = array(
    'global:env' => 'local'

/* Development DB Config */
case '111.222.333.444' :
$db['expressionengine']['hostname'] = "localhost";
$db['expressionengine']['username'] = "username";
$db['expressionengine']['password'] = "password";
$db['expressionengine']['database'] = "database";
$env_global_vars = array(
    'global:env' => 'dev'

/* Production DB Config */
case '444.333.222.111' : 
$db['expressionengine']['hostname'] = "localhost";
$db['expressionengine']['username'] = "username";
$db['expressionengine']['password'] = "password";
$db['expressionengine']['database'] = "database";
$env_global_vars = array(
    'global:env' => 'prod'


3. Debugging and Performance

Quick access to debugging controls. This is always helpful if something goes awry and your site stops showing up.

$config['show_profiler']                = 'n'; # y/n
$config['template_debugging']           = 'n'; # y/n
$config['debug']                        = '1'; # 0: no PHP/SQL errors shown. 1: Errors shown to Super Admins. 2: Errors shown to everyone.
$config['disable_all_tracking']         = 'y'; # y/n
$config['enable_sql_caching']           = 'n'; # Cache Dynamic Channel Queries?
$config['email_debug']                  = 'n'; # y/n
$config['autosave_interval_seconds']    = '0'; # Disabling entry autosave
$config['gzip_output']                  = 'y'; # y/n

4. General Paths

Standard site paths.

$config['index_page']             = "/";
$config['site_index']             = "/";
$config['base_url']               = $base_url . "/";
$config['site_url']               = $config['base_url'];
$config['cp_url']                 = $system_url . "/index.php";
$config['theme_folder_path']      = $themes_path;
$config['theme_folder_url']       = $themes_url;

5. Universal Database Settings

These are mostly the values from the standard database.php file.

$db['expressionengine']['dbdriver']  = "mysql";
$db['expressionengine']['dbprefix']  = "exp_";
$db['expressionengine']['pconnect']  = FALSE;
$db['expressionengine']['swap_pre']  = "exp_";
$db['expressionengine']['db_debug']  = FALSE;
$db['expressionengine']['cache_on']  = TRUE;
$db['expressionengine']['autoinit']  = FALSE;
$db['expressionengine']['char_set']  = "utf8";
$db['expressionengine']['dbcollat']  = "utf8_general_ci";
$db['expressionengine']['cachedir']  = $system_path ."expressionengine/cache/db_cache/";

6. Template Preferences

This lets you set up the defaults for how templates are treated. I save templates as flat files so I have that set to yes. I also move the templates folder out of /system/ into the root level of the site. Just make sure you have an htaccess file set to deny from all so your templates folder isn't open to the world.

$config['strict_urls']               = "y";
$config['site_404']                  = "site/404";
$config['save_tmpl_revisions']       = "y";
$config['save_tmpl_files']           = "y";
$config['tmpl_file_basepath']        = $base_path . "/assets/templates/";
$config['hidden_template_indicator'] = "_";

7. Member Directory paths and URLs

Moving from server to server means paths get will likely get updated. Having these dynamic is a big help.

$config['emoticon_path']        = $images_url . "smileys/";
$config['captcha_path']         = $images_path . "captchas/";
$config['captcha_url']          = $images_url . "captchas/";
$config['avatar_path']          = $images_path . "avatars/";
$config['avatar_url']           = $images_url . "avatars/";
$config['photo_path']           = $images_path . "member_photos/";
$config['photo_url']            = $images_url . "member_photos/";
$config['sig_img_path']         = $images_path . "signature_attachments/";
$config['sig_img_url']          = $images_url . "signature_attachments/";
$config['prv_msg_upload_path']  = $images_path . "pm_attachments/";

8. Global Variables

This section allows you to set up some really handy global variables to use throughout your site. You can easily add to this to create nice shortcuts during development.

$default_global_vars = array(

	// Tag parameters - Short hand tag params
	'global:disable_default'   => 'disable="categories|pagination|member_data"',
	'global:disable_all'       => 'disable="categories|custom_fields|member_data|pagination"',
	'global:cache'        => 'cache="yes" refresh="10"',
	'-global:cache'       => '-cache="yes" refresh="10"', // disable by adding a '-' to the front of the global

	// Date and time - Short hand date and time
	'global:date_time'          => '%g:%i %a',
	'global:date_short'         => '%F %d, %Y',
	'global:date_full'          => '%F %d %Y, %g:%i %a',

	'global:isipad'    => (bool) strpos($user_agent,'iPad'),
	'global:isiphone'   => (bool) strpos($user_agent,'iPhone')


// Make this global so we can add some of the config variables here

global $assign_to_config;

if(!isset($assign_to_config['global_vars'])) {
	$assign_to_config['global_vars'] = array();

$assign_to_config['global_vars'] = array_merge($assign_to_config['global_vars'], $default_global_vars, $env_global_vars);

Using a config.php file can speed up initial site installs, simplify server moves, and add convenient ExpressionEngine configuration options. Definitely worth the effort.

Apache Query String URL to SEO Friendly URL

On one of my current migration projects. I need to rewrite a bunch of clunky old dynamic query string URLs to nice clean search engine friend URLs. Took a bit of digging through Apache documentation, but here it is.

The goal was to go from




If you want to rewrite just one URL, simply type it in as written below. The downside to this approach is that, it's just one URL. You would have to retype this for each string you wanted to rewrite.

RewriteEngine On
RewriteCond %{QUERY_STRING} ^tag=blah&Template=feed&IncludeBlogs=71$
RewriteRule ^index.php$ /feeds/topic/blah/? [L,R=301]

Or if you want to get fancy and some piece of the original URL actually matches the new URL, You can store it and then :

RewriteEngine On
RewriteCond %{QUERY_STRING} ^tag=([a-z]+)&Template=feed&IncludeBlogs=71$
RewriteRule ^index.php$ /feeds/topic/%1/? [L,R=301]

In both examples, index.php is an example and would be whatever your script address is.

Gmail + iPhone + Apple Mail

For a while now I’ve been using several different Gmail/Google Apps accounts with Apple Mail and the iPhone. There are an abundance of guides out there describing the best way to integrate everything so you have the same user experience in webmail, desktop, and mobile. I’ve tried a bunch of these but they all seem to fall short of doing things how they should. I wanted the experience to be as close to a normal IMAP server as possible, where the mindset is that emails should be deleted, NOT archived. This post comes to the same conclusions, albeit with some screenshots and some elaboration.

A few necessary aspects of my “perfect” setup.

  1. Delete a message on any platform, it gets put in the trash, NOT archived.
  2. Move a message to a local folder in Apple Mail, it gets put in the trash on the server, NOT archived.
  3. Get rid of All Mail so duplicate messages aren’t downloaded and the badge count is correct.
  4. Clean up the folder hierarchy in Apple Mail such that there isn’t a [Gmail] folder and all Gmail folders are correctly mapped (Spam, Sent, Trash etc.).
  5. Nested Folders.

Gmail / Google Apps Setup

First, we need to get your Gmail / Google Apps account set up correctly with IMAP to allow us to access things from Apple Mail and the iPhone. This involves enabling two features from the Labs section, customizing the IMAP behaviors, and customizing which IMAP folders display in Apple Mail. Follow the instructions below.

  1. Settings > Forwarding and POP/IMAP > IMAP Access > Status > Enable IMAP
  2. Settings > Forwarding and POP/IMAP > IMAP Access > When I mark a message in IMAP as deleted: > Auto-Expunge off
  3. Settings > Forwarding and POP/IMAP > IMAP Access > When a message is marked as deleted and expunged from the last visible IMAP folder: > Move the message to the Trash
  4. Click “Save Changes.” Then go to:
  5. Settings > Labels > System Labels > Starred > Uncheck “Show in IMAP”
  6. Settings > Labels > System Labels > Chats > Uncheck “Show in IMAP”
  7. Settings > Labels > System Labels > All Mail > Uncheck “Show in IMAP”

You’re done, now open Apple Mail.

Apple Mail Version 6.2 (1499) Setup

My goal was to map all the Gmail folders to the appropriate Apple Mail folders so things like Drafts, Sent Mail, Trash and Junk were available on the left mailboxes view in the main sections, NOT within the account folders below. This setup differs greatly from Google’s suggestions but it has been working well for me. This method will leave the [Gmail] folder in Apple Mail, but it will be empty. If you actually want to remove that, you have to get a bit more complicated. I plan to outline that method in another post.

  1. Create a new account. You can figure out how to do this on the Gmail Help page.
  2. Go to: Mail > Preferences > Accounts > The account you added > Mailbox Behaviors > Make sure everything is checked.
  3. Go to: Mail > Preferences > Junk Mail > Enable Junk Mail filtering and select “Move it to the Junk mailbox” option. Close the Preferences window.
  4. In the left hand Mail folder pane, find your account name and expand the [Gmail] folder
  5. Map the folders within to their correct functions in Apple Mail by selecting the appropriate folder and then going to: Mailbox > Use This Mailbox For:
    • Drafts > Drafts
    • Sent Mail > Sent
    • Spam > Junk
    • Trash > Trash

Apple iOS 6.1 Setup

Now on the iPhone we want to have the same clean folder hierarchy and have deleted messages actually get put in the trash instead of archived.

  1. Set up a new email account: Settings > Mail, Contacts, Calendars > Add Account… > Gmail > Enter your info
  2. Settings > Mail, Contacts, Calendars > Select your account > Archive Messages > OFF
  3. Settings > Mail, Contacts, Calendars > Select your account > Account > Advanced > Mailbox Behaviors
    • Drafts Mailbox > Set it as the Gmail Drafts folder
    • Deleted Mailbox > Set it as the Gmail Trash folder

What did all this do?

We’ve successfully mapped all folders correctly in Apple Mail and on the iPhone. Deleting works as it should, actually moving the email into the Trash instead of just Archiving it.