Viewing technology View all News

WordPress Multi-Parameter Search

For our work on the Lichter Realty website, we were tasked with building a multi-parameter property search.

Lichter wanted their clients to be able to filter properties based on multiple criteria: contract type, property type, square footage, and availability.

Screen Shot 2015-09-28 at 6.58.55 AM

 

Since the Lichter site is built in WordPress, to achieve this we had to build out a way to dynamically modify WP Query based on the user’s selections. To make this easier, all of the properties have multiple taxonomies to handle these various parameters, and a set of custom fields to manage availability.

First, let’s build out a form so users can make their selections. We’re using GET as our method so a filtered search ends up with shareable URLs. GET is the default, but I like to set it anyway to make it easier to figure out what’s going on at a glance.

Most of the parameters are using checkbox inputs so we can select multiple values. The exceptions are contract type and availability, which have all options, so we can use radio buttons.

The checkbox options get input names with [] at the end to make sure our values get saved in an array. The values for each input is the ID of the associated taxonomy term, except in the case of the all options.

Note: In the live site, there is more markup for presentational purposes, but for simplicity I’ve stripped that out here so we can focus on the inputs.

<form method="get" class="property-filters">
	
	<!-- CONTRACT TYPE -->
	<fieldset class="contract-type">
		<div class="accent-sans">View properties that are:</div>

		<input type="radio" id="contract-type--sale" name="contract-type" value="30" />
		<label for="contract-type--sale">For Sale</label>
		
		<input type="radio" id="contract-type--lease" name="contract-type" value="31" />
		<label for="contract-type--lease">For Lease</label>

		<input type="radio" id="contract-type--all" name="contract-type" value="all" checked />
		<label for="contract-type--all">All</label>
	</fieldset>

	<!-- PROPERTY TYPE -->
	<fieldset>
		<div class="accent-sans">Property Type:</div>
		
		<input type="checkbox" id="property-type--industrial" name="property-type[]" value="4" />
		<label for="property-type--industrial">Industrial</label>

		<input type="checkbox" id="property-type--flex-space" name="property-type[]" value="6" />
		<label for="property-type--flex-space">Flex Space</label>

		<input type="checkbox" id="property-type--loft-office" name="property-type[]" value="5" />
		<label for="property-type--loft-office">Loft/Office</label>

		<input type="checkbox" id="property-type--commercial" name="property-type[]" value="3" />
		<label for="property-type--commercial">Commercial</label>

		<input type="checkbox" id="property-type--land" name="property-type[]" value="27" />
		<label for="property-type--land">Land</label>
	</fieldset>

	<!-- SQUARE FOOTAGE -->
	<fieldset>
		<div class="accent-sans">Square Footage:</div>

		<input type="checkbox" id="sqft--5kless" name="sqft[]" value="7" />
		<label for="sqft--5kless">5k &amp; Less</label>

		<input type="checkbox" id="sqft--5k10k" name="sqft[]" value="8" />
		<label for="sqft--5k10k">5K - 10K</label>

		<input type="checkbox" id="sqft--10k15k" name="sqft[]" value="9" />
		<label for="sqft--10k15k">10K - 15K</label>

		<input type="checkbox" id="sqft--15kgreater" name="sqft[]" value="10" />
		<label for="sqft--15kgreater">15k &amp; Greater</label>
	</fieldset>

	<!-- AVAILABILITY -->
	<fieldset>
		<div class="accent-sans">Availability:</div>

		<input type="radio" id="availability--now" name="availability" value="now" />
		<label for="availability--now">Now Available</label>

		<input type="radio" id="availability--soon" name="availability" value="soon" />
		<label for="availability--soon">Coming Soon</label>

		<input type="radio" id="availability--all" name="availability" value="all" />
		<label for="availability--all">All Units</label>
	</fieldset>

	<button type="submit" class="btn red">Filter</button>
</form>

You might have noticed above that availability works a bit differently than the other options. Availability is a date based filter. In the backend, we have custom fields (lovingly created through the help of Advanced Custom Fields Pro).

The first field is a checkbox to indicate that there is availability at this property, or soon will be. This is so completely unavailable properties can be left out of the listings entirely. Once that’s checked, a date field is presented, and the administrator can pick the availability date.

If the date is today or before, that property is considered available now. If the availability date is after today, it’s considered available soon. We’ll see how these work in the query later on.

Now that we have the form set up, we can work on our query.

First, let’s grab the paged variable and then reset the query so we can use it however we want. We want to make sure to capture the paged variable before the reset, otherwise it just won’t be there!

$paged = ( get_query_var('paged')  ? get_query_var('paged') : 1 );
$wp_query = null;
$wp_query = new WP_Query();

Now we can set up base arguments for our query. These arguments are used for the initial state of the properties page, as well as the base for the filtered searches.

// BASE ARGS

$args = array(
	'post_type' => 'property',
	'orderby' => 'meta_value_num',
	'order' => 'ASC',
	'meta_key' => 'availability_date',
	'meta_compare' => 'EXISTS',
	'meta_query' => array(
		array(
			'key' => 'available_now__soon',
			'value' => true,
			'compare' => 'EXISTS',
		),
	),
	'paged' => $paged
);

We’re using the first meta_key and meta_compare arguments to make sure that the availability date field actually is present. We can then set orderby to meta_value_num to order the properties by their availability date. Then we use a meta query to further ascertain that the available now or soon flag is set to true.

A property could have once had an availability date set, but then had their availability turned off. In this case, the availability date field would still evaluate as existing, so we need this double check.

Technically, we don’t need a checkbox at all to toggle availability. Instead, we could have instructed the site administrators to leave the date field blank for unavailable.

But this is a less intuitive interface for them, so we’ve included the checkbox for added usability, even if it makes our query slightly more complicated.

Now, let’s check if anything has been searched, or if we can just go along our merry way. We’re going to need to check whether each parameter has been toggled more than once in the following code, so let’s save the answers in variables before we start our checks. We want to check:

  1. if the array key exists in GET for this parameter
  2. if we’re expecting the values to be an array (all the radio inputs in our form), verify that it is an array and
  3. if it is an array, that there is at least one value present in the array

If all the appropriate criteria are met, the variable is set to true.

// ARE WE FILTERED?

$property = (array_key_exists('property-type', $_GET) && is_array($_GET['property-type']) && count($_GET['property-type']));
$sqft = (array_key_exists('sqft', $_GET) && is_array($_GET['sqft']) && count($_GET['sqft']));
$availability = (array_key_exists('availability', $_GET));
$contract = (array_key_exists('contract-type', $_GET));

While we’re setting up variables, let’s set one up to help us keep track of whether or not any of the taxonomy-based filters have been toggled. This will help us check whether or not we need to add a tax query to WP Query later on.

// set up an array to hold our active taxonomy based filters
$taxables = array();

Now that we have our variables set up, let’s open up a big if statement. We’re going to check if any of the filters have been toggled. All of our query modifications will happen inside this if statement.

You’ll notice that for availability and contract type, we not only check their existence, but if they’re set to all. all is the default, so it technically counts as untoggled.

if($property || $sqft || ($availability && $_GET['availability'] !== 'all') || ($contract && $_GET['contract-type'] !== 'all')) {

}

If any one of the above criteria are met, we move forward.

Before we get going with the heavy lifting, let’s refresh our memories about taxonomy queries. They generally look like this:

$args = array(
	'tax_query' => array(
		'relation' => 'AND',
		array(
			'taxonomy' => 'movie_genre',
			'field' => 'slug',
			'terms' => array( 'action', 'comedy' ),
		),
		array(
			'taxonomy' => 'actor',
			'field' => 'term_id',
			'terms' => array( 103, 115, 206 ),
		),
	),
);
$query = new WP_Query( $args );

We now know that a tax query is an array, with each taxonomy filter its own array within that, and that we should set the relation to AND in case we have more than one. Let’s set up a variable to hold our tax query parameters:

$tax_query = array('relation' => 'AND');

Now let’s get property and square footage going. They work exactly the same way except we’ll pass different values to them. We want to iterate through all the values passed in GET for that parameter and add them to a $terms array.

We can then compose an array inside $tax_query with the taxonomy and the values needed. We’re also adding our check value ($property or $sqft) to the $taxables array.


// IF PROPERTY TYPE FILTER IS SELECTED

if($property) {
	$taxables[] = $property;
	$terms = null;

	foreach($_GET['property-type'] as $property_type) {
		$terms[] = $property_type;
	}

	$tax_query[] = array(
		'taxonomy' => 'property_type',
		'field' => 'term_id',
		'terms' => $terms
	);
}

// IF SQUARE FOOTAGE FILTER IS SELECTED

if($sqft) {
	$taxables[] = $sqft;
	$terms = null;

	foreach($_GET['sqft'] as $sqft) {
		$terms[] = $sqft;
	}

	$tax_query[] = array(
		'taxonomy' => 'square_footage',
		'field' => 'term_id',
		'terms' => $terms
	);
}

Contract type works similarly, except we also need to check that the value isn’t all before proceeding, and we don’t need to iterate through terms since availability isn’t an array.

// IF CONTRACT TYPE FILTER IS SELECTED

if($contract && $_GET['contract-type'] !== 'all') {
	$taxables[] = $contract;

	$tax_query[] = array(
		'taxonomy' => 'contract_type',
		'field' => 'term_id',
		'terms' => $_GET['contract-type']
	);
}

Now that we’ve run through all the possible taxonomy-based filters, we can use that $taxables array. We had been adding items to it if a given taxonomy parameter had values toggled within it. Now we can use that to check if we need to add our $tax_query to the our query arguments.

// IF ANY TAXONOMY FILTERS ARE SELECTED, WE ADD THE TAX ARGS TO OUR QUERY
if(count($taxables)) {
	$args['tax_query'] = $tax_query;
}

All that’s left is availability. For availability, we’re going to be adding to the existing meta query. We want to pass today’s value and the availablity_date key. We have to make sure to set the type to date or our comparisons won’t work properly. The comparison will vary between less than or equal to for now or greater than for soon.

// IF AVAILABILITY FILTER IS SELECTED

if($availability && $_GET['availability'] !== 'all') {
	$meta_query = array(
	'key' => 'availability_date',
	'value' => date('Y-m-d', current_time('timestamp')),
	'type' => 'date'
	);

	if($_GET['availability'] === 'now') {
		$meta_query['compare'] = '<=';
	}

	else {
		$meta_query['compare'] = '>';
	}

	$args['meta_query'][] = $meta_query;
}

And that’s it for our if statement. Let’s take a look at it all together:

if($property || $sqft || ($availability && $_GET['availability'] !== 'all') || ($contract && $_GET['contract-type'] !== 'all')) {

	$tax_query = array('relation' => 'AND');


	// IF PROPERTY TYPE FILTER IS SELECTED

	if($property) {
		$taxables[] = $property;
		$terms = null;

		foreach($_GET['property-type'] as $property_type) {
			$terms[] = $property_type;
		}

		$tax_query[] = array(
			'taxonomy' => 'property_type',
			'field' => 'term_id',
			'terms' => $terms
		);
	}


	// IF SQUARE FOOTAGE FILTER IS SELECTED

	if($sqft) {
		$taxables[] = $sqft;
		$terms = null;

		foreach($_GET['sqft'] as $sqft) {
			$terms[] = $sqft;
		}

		$tax_query[] = array(
			'taxonomy' => 'square_footage',
			'field' => 'term_id',
			'terms' => $terms
		);
	}


	// IF CONTRACT TYPE FILTER IS SELECTED

	if($contract && $_GET['contract-type'] !== 'all') {
		$taxables[] = $contract;

		$tax_query[] = array(
			'taxonomy' => 'contract_type',
			'field' => 'term_id',
			'terms' => $_GET['contract-type']
		);
	}


	// IF ANY TAXONOMY FILTERS ARE SELECTED, WE ADD THE TAX ARGS TO OUR QUERY

	if(count($taxables)) {
		$args['tax_query'] = $tax_query;
	}
	

	// IF AVAILABILITY FILTER IS SELECTED
	
	if($availability && $_GET['availability'] !== 'all') {
		$meta_query = array(
			'key' => 'availability_date',
			'value' => date('Y-m-d', current_time('timestamp')),
			'type' => 'DATE'
		);

		if($_GET['availability'] === 'now') {
			$meta_query['compare'] = '<=';
		}

		else {
			$meta_query['compare'] = '>';
		}

		$args['meta_query'][] = $meta_query;
	}
}

After that, all we need to do is add call a new query based on our arguments:

$wp_query->query($args);

And that’s it! It even works with that mysterious devil, pagination.

Be sure to check out the live search on Lichter Realty.

Posted By
Tiffany Stoik

WordPress Object Cache

It is not unusual for WordPress to run dozens or even hundreds (yikes!) of database queries when putting together a page. What’s worse, these queries aren’t the sort from children’s bedtime stories. They’re the dirty ones, full of temporary tables, JOINs on JOINs on JOINs, generic data types, and insufficient indexing. They’re the MySQL equivalent of The Wire.

As traffic loads increase, these computationally-heavy operations can quickly lead to a bottleneck, making your site sluggish or bringing it offline altogether.

To get a better idea of your own database use (or abuse), install a plugin like Query Monitor, which puts detailed query stats in the admin toolbar.

If you’re nearing the limit of what MySQL can handle, there are a few basic things you should consider doing:

  1. Go through your plugins and deactivate and delete anything that isn’t being used.
  2. Set reasonable post limits (10 or fewer) for archive pages.
  3. If you have a custom theme, try to minimize the number of secondary queries being run inside The Loop.

Beyond that, well, that’s the purpose of this article: Object Caching!

WordPress has the ability to cache query results (and other key/value data) in memory for later retrieval so it doesn’t have to pester MySQL on repeat requests.

This can dramatically reduce the load on MySQL, while maintaining comparable speed.

The main disadvantage of Object Cache is that objects are, well, cached. This means that changes made in the backend might not be immediately reflected on the front end.

This can be a deal breaker for applications with transactional data like e-commerce sites, unless the code contains strategic calls to wp_cache_flush(). But for content-driven sites like blogs and portfolios, it can be the perfect solution.

Object Cache also takes over handling of “transient” data, which otherwise get stored in the wp_options table. This will both speed up plugins and themes that rely on the transient API, and also reduce bloat in the database (there is no automatic garbage collection process run against wp_options, so data remains forever, and ever, and ever… (unless the same cache key is requested again)).

WordPress Object Caching is designed with extensibility in mind. All it needs is an API for key/value storage and retrieval.

There are innumerable candidates for the job, so let’s just focus on some of the most common:

XCache

XCache is my favorite PHP opcode cacher (opcode cache, incidentally, will also speed up the execution of your PHP scripts in general).

It is lightweight, scalable, and supports the latest versions of PHP.

To get started, install the XCache PHP extension (available in most Linux repositories), tweak the INI settings* (variable cache is the relevant feature here), and restart PHP.

After that, you can then enable Object Caching in WordPress by installing XCache Object Cache Backend or a more comprehensive caching solution like W3 Total Cache.

If you have multiple sites running on the same server, you need to ensure you are running version 3.0.3 or later.

The XCache extension comes bundled with admin scripts you can copy to your web root to get an idea of how cache is being utilized. You should install this after it has been running for a while to see if the memory allocations need to be raised or lowered (just make sure you restrict access, and delete it when you’re done!). Remember, the memory settings are per-process, so don’t get carried away.

APC

APC is another PHP opcode cacher, but it is no longer actively developed.

Unless you are running an older version of PHP or your hosting environment comes bundled with it, XCache is a better option.

To get started, install the APC PHP extension, tweak the INI settings, and restart PHP. You can install a standalone plugin like APC Object Cache Backend or, again, go with something like W3TC.

There remains, however, one excellent use case for APC: Facebook’s HHVM engine supports it out of the box! If you have already replaced PHP with HHVM, just install the APC Object Cache Backend plugin and you’re good to go.

Redis

Redis is a powerful key/value caching solution with support for multiple servers.

For complex web applications consisting of multiple servers, it is an ideal solution as the cache can be shared across more than one machine.

For more simple setups, it is a bit much, and noticeably slower than XCache or APC.

To get started, install the Redis server and PHP extension (if you are running HHVM instead of PHP, the “extension” is built-in). Once you have that configured, you can install the Redis Object Cache plugin.

Posted By
Josh Stoik

How-to: Uncle Goose Create-a-Name

An exciting part of last year’s Uncle Goose redesign was the “Create-a-Name” block generator. This had been the most user-requested feature, so it was important for us to make it happen.

Uncle Goose wanted a simple, intuitive way for customers to order individual blocks from one of three sets, and have each block’s color be customizable.

create-a-name-demo

Here’s a look at how we built it.

HTML

First, let’s get our container set up. We need a text input so people can type in the letters they want. We also have an empty div where our letters will be stored.

The class set-* will dictate which set style the letters have. The data-set attribute will store the set information for use in the shopping cart.

<div class="container letters-container">
	<input type="text" class="create-a-name-text" maxlength="26" id="create-a-name-text" placeholder="Enter text" autofocus />
	<div class="letters set-abc clear" data-set="set-abc"></div><!-- /.letters -->
</div><!-- /.container.letters-container -->

We also need a way for the customer to select which set they want:

<div class="set-selector">
 	<label>Set:</label>
	<select id="set-filter">
 		<option selected value="set-abc">Classic ABC</option>
 		<option value="set-upperlower">Upper Lower</option>
 		<option value="set-lower">Lowercase</option>
 	</select>
 </div><!-- /.set-selector -->

We also need to store all the info about which blocks and options the customer has selected. We’re going to use a link with all the data stored in data-* attributes that can be parsed by the cart on submission. Our cart needs a product ID, which is just the WordPress post ID, a block quantity to calculate the price, and all the letter, set, and color details are stored in a product note.

<a href="#" class="add-to-cart btn green icon-cart" data-id="<?=get_the_ID()?>" data-qty="0" data-note="">Add To Cart</a>

Let’s get a template going for our letters. The template letter will be hidden but will be used by our javascript to create new letters on the fly. This is neater and simpler than writing out all this HTML in the javascript. We’re also going to store the color and set information in data attributes so they’re easy to use in our javascript later on.

We’re using PHP to import the SVGs we need for two of the sets. The third set has a solid background color so we can use CSS alone to style it.

<!-- This block serves as a template for any blocks we dynamically create. -->
<div class="letter-wrapper letter-template" data-color="" data-letter="" style="display: none">
 	<div class="letter-inner">
 	 	<?php @include(dirname(__FILE__) . '/img/filigree-abc.svg'); ?>
 	 	<?php @include(dirname(__FILE__) . '/img/filigree-lower.svg'); ?>
 	 	<span class="letter"></span>
 	</div><!-- /.letter-inner -->

 	<div class="color-select">
 	 	<div class="color-option blue" data-color="blue">Blue</div>
 	 	<div class="color-option green" data-color="green">Green</div>
 	 	<div class="color-option orange" data-color="orange">Orange</div>
 	 	<div class="color-option yellow" data-color="yellow">Yellow</div>
 	 </div><!-- /.color-select -->
</div><!-- /.letter-wrapper -->

CSS

Now that we have a letter going, we can start styling it. We’re going to use ems for all our size values since eventually the font size of the blocks will change dynamically, and we want the blocks to be sized accordingly.

We need a letter wrapper with a width of 2ems, and a :before with top padding of 100% to both preserve our aspect ratio, and to position the color selection element more easily. The letter wrapper also has an active state for when the color selection tool is active. Let’s set backface-visibility: hidden to avoid repainting issues, and set some basic transition properties. For browsers that support it, we can also warn them that this element is going to have some transforms applied to it.

.letter-wrapper {
 	position: relative;
 	display: inline-block;
 	width: 2em;
 	margin: 0 0 0.5em;
 	z-index: 1;

 	cursor: pointer;

 	transition: all 0.3s;
 	backface-visibility: hidden;
 	will-change: transform;

 	&:before {
 	 	display: block;
 	 	padding-top: 100%;

 	 	content: '';
 	}

 	&.is-active {
 	 	z-index: 2;

 	 	transform: translateY(-0.75em);

 	 	transition: all 0.3s;
 	}
}

.letter-inner is going to be our block-looking element. We’re going to position it absolutely in the wrapper, give it a base background color, a box shadow, and a border radius. All styles for elements inside .letter-wrapper are nested inside .letter-wrapper in our Sass file, so we can sanely group together modifications based on the set chosen or any state changes.

.letter-inner {
 	position: absolute;
 	top: 0;
 	left: 0;
 	right: 0;
 	bottom: 0;

 	background: #feedda;
 	box-shadow: 0 -0.1em #feedda;
 	border-radius: 0.075em;
}

&.bounce .letter-inner {
	animation: bounce 0.5s;
}

This part of the block also gets a bounce animation during certain interactions. Let’s write that up now.

@keyframes bounce {
 	0% { transform: translateY(0); }
 	33% { transform: translateY(-0.25em); }
 	66% { transform: translateY(0.06em); }
 	100% { transform: translateY(0); }
}

For the blocks’ filigree, we have two SVGs that we’ll be toggling depending on which set is chosen. In an ideal world, we could use an SVG sprite and change viewBox with CSS, but at this time we can only do that with javascript. We already have a ton going on in javascript so let’s avoid adding more complexity to that file. Since we need to be able to dynamically change the SVG’s fill color, we can’t use one SVG as regular background image sprite either. So two files it is.

svg {
 	position: absolute;
 	top: 0;
 	left: 0;

 	display: none;
 	width: 100%;
 	height: 100%;
 	border-radius: 0.075em;
 	transition: all 0.3s;
}

Let’s place the actual letter in the middle of our block. We’ll be making some adjustments to individual letters based on the set/x-height later to make sure all letters are always centered, but for now let’s get the basics down.

.letter {
 	position: absolute;
 	top: 50%;
 	left: 50%;

 	text-transform: uppercase;
 	color: #fff;

 	transform: translate(-50%,-50%);
 	transition: color 0.3s;
}

Now that we have the basics of a block down, we can style the different block colors and different sets. Let’s leverage Sass and set up some color variables so we can easily loop through each one.


// block colors
$blockColors: (
	'blue': #3b83db,
	'green' : #81c81b,
	'orange': #ff900c,
	'yellow': #f5c008
);

@each $name, $color in $blockColors {
	&.#{$name} {
		svg { fill: $color; }
 		.letter { color: $color; }
 		.set-upperlower & .letter-inner { background: $color; }
	}
}

.set-abc & {
	.filigree-abc { display: block; }

	.letter {
		top: 49%;
		left: 51%;
		font-family: "Century Schoolbook", sans-serif;
	}
}

.set-upperlower & {
	.letter {
		top: 48%;

		margin-top: -1px;
		font-family: "Century Gothic", sans-serif;

		color: #feedda;
		font-size: 1.75em;
	}
}

.set-lower & {
	.filigree-lower { display: block; }

	.letter {
		margin-top: -0.125em;
		font-family: "Century Gothic", sans-serif;
		text-transform: lowercase;
	}

	&.letter-high .letter { margin-top: -0.02em; }
	&.letter-low .letter { margin-top: -0.175em; }
	&.letter-mid .letter { margin-top: -0.1em; }
	&.letter-a .letter { margin-top: -0.12em; }
}

You may have noticed some letter-based modifications on the lowercase set. Once the letters are all lowercase, while their baselines may line up, they don’t look centered in the block’s face. In our javascript, we will assign classes to the block based on the letter typed and adjust its positioning accordingly.

The last thing we need to style is the color selection tool. It’s a basic menu, with a triangle pointing toward its block and some color indicators.

.color-select {
	position: absolute;
	display: none;
	padding: 5px 0;
	margin-top: 0.4em;
	left: 50%;
	z-index: 1;

	background: $black;

	text-align: left;

	transform: translateX(-50%);
	backface-visibility: hidden;

	// triangle
	&:before {
		position: absolute;
		top: -19px;
		left: 50%;
		margin-left: -10px;

		border: 10px solid transparent;
		border-bottom-color: $black;

		content: '';
	}

	.color-option {
		padding: 5px 15px;

		white-space: nowrap;
		@include font-size(18); // font-size mixin which spits out both px and rem

		transition: background 0.3s;

		// color bullet
		&:before {
			display: inline-block;
			width: 8px;
			height: 8px;
			margin: -5px 10px 0 0;

			border-radius: 50%;

			content: '';
			vertical-align: middle;
		}

		@each $name, $color in $blockColors {
			&.#{$name}:before {  background: $color; }
		}

	 	&:hover,
		&.is-active { background: $grey-darkest; }
	}
}

Javascript

Now let’s switch over to javascript. First off, we know a lot is going to happen in this generator, so let’s set up a basic structure to our file. We can fill in the functions later. We’ll need two lock variables for controlling when events can happen, one for resize, and one for the various functions that can happen to a block. We also need functions for interactive, resize, add/edit blocks, opening/closing the color selection UI, color selecting, changing the set, and updating the cart information.

(function($){
 	var createAName = {

 	 	//------------------------------------------------------------------------
 	 	// Variables
 	 	//------------------------------------------------------------------------

 	 	resizeLock: null,
 	 	blockLock: true,

 	 	//------------------------------------------------------------------------
 	 	// Functions 
 	 	//------------------------------------------------------------------------

 	 	interactive: function() {

 	 	},

 	 	resizeEvents: function() {

 	 	},

 	 	addEditBlock: function(){

 	 	},

 	 	popColorSelection: function() {

 	 	},

 	 	colorSelection: function() {

 	 	},

 	 	changeSet: function() {

 	 	},

 	 	updateCreateAName: function() {

 	 	}
 	};

 	$(document).ready(function(){

 	 	// bind interactive events
 	 	createAName.interactive();

 	 	// resize events
 	 	$(window).resize(function(){ createAName.resizeEvents(); }).resize();
 	});

})(jQuery);

First off, let’s make some blocks. Inside our interactive function, let’s watch for typing on the input we created earlier, and trigger the addEditBlock function when someone types.

$('#create-a-name-text').keyup(function(){
	createAName.addEditBlock();
});

Before we can create the blocks, we need to strip out any non-alphabet letters, trim the length of the text if it’s beyond the max length, correct the letter case to match the set, check whether the letters typed have actually changed since the last input, and if there are any valid characters at all.

addEditBlock: function() {
	var name = $(this).val().replace(/[^a-zA-Z]/g, ''),   	//create a nice version of letters to play with
	    block_prototype = $('.letter-template'),          	//html template for a letter
	    block_wrapper = $('.letters'),                    	//the letters wrapper
	    block_set = block_wrapper.attr('data-set'),       	//the set we're using
	    block_colors = ['blue','green','orange','yellow'],	//colors

	    // group letters based on required position modification
	    block_letters_high = ['b', 'd', 'f', 'h', 'i', 'k', 'l', 't', 'B', 'D', 'F', 'H', 'I', 'K', 'L', 'T'],
	    block_letters_low = ['g', 'p', 'q', 'y', 'G', 'P', 'Q', 'Y'];
	    block_letters_mid = ['u', 'v', 'w', 'x', 'U', 'V', 'W', 'X'];

	//keep name short... 26 is plenty
	if(name.length > 26) {
		name = name.substr(0, 26);
	}

	//correct the case
	if(block_set === 'set-lower'){
		name = name.toLowerCase();
	}

	//upercase
	else {
		name = name.toUpperCase();
	}

	//exit if nothing has changed
	if(name === block_wrapper.attr('data-name')) {
		return true;
	}

	//if nothing has been typed, this is easy
	if(!name.length) {
		block_wrapper.html('');
	}

Now for the meat of this function, actually making our blocks. When blocks are initially created, colors are assigned by looping through the color array. But customers can edit their inputted text, so if they’ve already made a color selection for a letter, edit the text, and that letter is still in their edited text, we want to preserve the color selection. And though we haven’t written these functions yet, we know we want to update the cart information, and trigger the resize functions to make sure the blocks are being sized properly.

	//if there are letters, let's figure out how to draw them
	else {
		//some more variables
		var letters = name.split(''),	//each letter
		    block_new = null,        	//new block
		    color_index = -1,        	//color
		    blocks_old = [],         	//all existing blocks
		    blocks_new = [];         	//all new blocks

		//store the old blocks in an array.
		if($('.letter-wrapper', block_wrapper).length) {
			$('.letter-wrapper', block_wrapper).each(function(){
					blocks_old.push($(this));
			});
		}

		//cycle through each letter
		$.each(letters, function(k,v){
			color_index++;

			//if there is an existing letter matching this one,
			//preferentially use it (this preserves color selections)
			if(blocks_old.length > k && blocks_old[k] !== undefined && blocks_old[k].attr('data-letter') === v){
				blocks_new.push(blocks_old[k].clone());
			}
						
			//otherwise let's make a new block
			else {
				//start with the prototype
				block_new = block_prototype.clone();
				block_new.removeClass('letter-template');
				block_new.removeAttr('style');

				var xHeight = 'letter-middle';
				//check what x-height based class we should assign to it
				if($.inArray(v, block_letters_high) > -1){
					xHeight = 'letter-high';
				}

				if($.inArray(v, block_letters_low) > -1){
					xHeight = 'letter-low';
				}

				if($.inArray(v, block_letters_mid) > -1){
						xHeight = 'letter-mid';
				}

				//customize it
				$('.letter', block_new).text(v);
				$('.color-option.' + block_colors[color_index], block_new).addClass('is-active');
				block_new.attr({'data-letter': v, 'data-color': block_colors[color_index]});
				block_new.addClass(block_colors[color_index]);
				block_new.addClass(xHeight);
				block_new.addClass('letter-' + v.toLowerCase());

				//and add to our list
				blocks_new.push(block_new);
			}

			//and start over with the colors
			if(color_index + 1 === block_colors.length){
				color_index = -1;
			}
		});

		//now add each block to our set!
		block_wrapper.html('');
		$.each(blocks_new, function(k,v){
			block_wrapper.append(v);
			//if this is not the last item, add a space
			if(k + 1 < blocks_new.length){
				block_wrapper.append(' ');
			}
		});
	}//letters

	//save the name so we can skip all this work for non-changey-keys
	block_wrapper.attr('data-name', name);

	//and update the cart data
	createAName.updateCreateAName();

	//one last thing, trigger resize events so the blocks get scaled correctly
	if(createAName.resizeLock) {
		clearTimeout(createAName.resizeLock);
	}

	createAName.resizeLock = setTimeout(function(){ createAName.resizeEvents(); }, 100);
}, // end addEditBlock

Let’s tackle the resizing. We want to fit as many blocks on one line as possible while maintaining legibility. We also want to make the blocks as big as possible up to a certain point. So we need a min and a max font size, and figure out what size in that range our blocks should be given the container size and the amount of blocks. Let’s also include a little wiggle room so the blocks aren’t flush up against the container, and we need to account for the fact that the blocks are 2ems wide, so our final font size will actually be half the number we come up with.

resizeEvents: function() {
	var containerWidth = $('.letters').width(),                      // container width
	    letterAmount = $('.letters').find('.letter-wrapper').size(), // how many letters do we have
	    baseSize = containerWidth / letterAmount,                    // how wide can the blocks be to still fit in the container
	    wiggleSize = (baseSize - (baseSize * 0.15),                  // wiggle room
	    fontSize = wiggleSize / 2,                                   // blocks are 2ems wide, so font size is half size of block
	    maxSize =  96,                                               // max font size
	    minSize = 38;                                                // min font size

	if(fontSize > maxSize) {
		fontSize = maxSize;
	}

	if(fontSize < minSize) {
		fontSize = minSize;
	}

	fontSize = Math.floor(fontSize);

	$('.letters').css('font-size', fontSize + 'px');
}; // end resizeEvents and end 

Now that we’ve got the basics of block making and resizing down, we can work on the options. First up, let’s add a triggering mechanism to our interactive function for changing the block set.

$('#set-filter').on('change', function(){
	var oldSet = $('.letters').attr('data-set'),
	    newSet = $(this).val();

	createAName.changeSet(oldSet, newSet);
});

And let’s fill out our changeSet function. We want to remove the old set’s class and add the new one to all the letters, as well as update the case if necessary, and update the cart information.

changeSet: function(oldSet, newSet){
	$('.letters').removeClass(oldSet).addClass(newSet).attr('data-set', newSet);

	//update data so we're using the right case
	var block_wrapper = $('.letters');

	//lowercase
	if(newSet === 'set-lower'){
		block_wrapper.attr('data-name', block_wrapper.attr('data-name').toLowerCase());
	}
	
	//uppercase
	else {
		block_wrapper.attr('data-name', block_wrapper.attr('data-name').toUpperCase());
	}

	$('.letter-wrapper', block_wrapper).each(function(){
		//lowercase
		if(newSet === 'set-lower'){
			$('.letter', $(this)).text($('.letter', $(this)).text().toLowerCase());
			$(this).attr('data-letter', $(this).attr('data-letter').toLowerCase());
		}

		//upercase
		else {
			$('.letter', $(this)).text($('.letter', $(this)).text().toUpperCase());
			$(this).attr('data-letter', $(this).attr('data-letter').toUpperCase());
		}
	});

	//and update the cart data
	createAName.updateCreateAName();
}, // end changeSet

Moving on to color selection. The first step is to actually show the color selection menu when someone clicks on a block. We also want to close it when they click on the same block again, or on a different block. On top of that, we want to close open blocks when they click on anything that isn’t a block. To accomplish this, we’re using jQuery Outside Events to check whether a user has clicked outside an open block. This is also where our blockLock variable comes in. We just want to make sure we’re not triggering any outside events when there isn’t a block open, so we’re going to set blockLock to true initially, and set it to false when a block is open.

Let’s set up a block click event, and monitor outside events in our interactive function. As a note, anytime we’re binding an event to a block, we have to use .on() on the parent element rather than something like .click() on the element itself, since the blocks are dynamically created and .click() only binds to elements that are present when the event is first bound.

$('.letters').on('click', '.letter-inner', function(e){
	e.preventDefault();
	createAName.popColorSelection($(this));
});

// close block when you click outside of it
$('.letters').bind( 'clickoutside', function(){
	if(createAName.blockLock === false){
		$('.letter-wrapper.is-active .letter-inner').click();
	}
});

And now for our popColorSelection function.

popColorSelection: function(block){
	var parent = block.parents('.letter-wrapper');

	// if there is an active block but it is NOT the block we clicked on, close that block				
	if($('.letter-wrapper.is-active') && !parent.hasClass('is-active')){
		$('.color-select', $('.letter-wrapper.is-active')).fadeOut();
		$('.letter-wrapper.is-active').removeClass('is-active');
	}

	// toggle active class, fade toggle the menu
	parent.toggleClass('is-active');
	$('.color-select', parent).fadeToggle();

	// set our blockLock appropriately
	if(createAName.blockLock === true){
		createAName.blockLock = false;
	}

	else {
		createAName.blockLock = true;
	}
}, //end popColorSelection

Awesome. Now we can start doing all the things that need doing when someone changes the color. First up, let’s toss an event trigger in our interactive function and pass the block and menu item info to the colorSelection function.

$('.letters').on('click', '.color-option', function(e){
	var block = $(this).parents('.letter-wrapper');	
	createAName.colorSelection(block, $(this));
});

When someone selects a new color, we need to capture the old color, remove its class, add the new color class, update data-color attribute, and update the cart information. We also need to update the color menu to reflect the active color. The block also gets a little bounce animation when a new color is selected.

colorSelection: function(block, menuItem) {
	var currColor = block.attr('data-color');
	var color = menuItem.attr('data-color');

	e.preventDefault();

	block.removeClass(currColor).addClass(color).attr('data-color', color);
	block.addClass('bounce'); // add a bounce animation

	setTimeout(function(){
		block.removeClass('bounce');
	}, 500); // length of our bounce animation

	$('.color-option.is-active', block).removeClass('is-active');
	menuItem.addClass('is-active');

	//and update the cart data
	createAName.updateCreateAName();
}, //end colorSelection

The last thing we need to do is build out our function that updates the cart information. It’s pretty simple. We just run through all the blocks and gather all the info we need, and pop it back in the cart button element.

updateCreateAName: function(){
	//build the add-to-cart data
	var block_wrapper = $('.letters'),
	    block_set = block_wrapper.attr('data-set'),
	    blocks = $('.letter-wrapper', block_wrapper),
	    qty = blocks.length,
	    notes = [],
	    button = $('.add-to-cart');

	//no quantity, we're done!
	if(!qty) {
		button.attr({'data-qty': 0, 'data-note': ''});
	}

	//yes quantity
	else {
		notes.push($('#set-filter option[value="' + block_set + '"]').text());
		blocks.each(function(){
			notes.push('[' + $(this).attr('data-letter') + '] ' + $(this).attr('data-color'));
		});

		button.attr({'data-qty': qty, 'data-note': notes.join(';')});
	}
}, // end updateCreateAName

That’s it. Be sure to check out the Create-a-Name generator on the Uncle Goose website, along with all their other awesome block sets!

Posted By
Tiffany Stoik

HHVM: The Gift That Keeps On Giving

Over the past 20 years, PHP has become the de facto server-side programming language of the web.

From leading open-source software like WordPress to some of the Internet’s most heavily-trafficked sites (Facebook, Twitter, and Wikipedia to name a few), you’ll find it everywhere.

But its developer-friendly syntax comes at a cost.

Before any Web Magic™ can happen, the human-readable PHP source code must be run through an interpreter. This process is expensive (particularly in terms of memory usage) and time-consuming. Ultimately, this sets an upper-bound on the number of simultaneous page requests a given server can handle. As a site nears this ceiling, it becomes necessary to either make the site more efficient, or upgrade the hardware.

For some sites, the answer is to sidestep PHP altogether and implement static page caching. But for others, where dynamic processing is unavoidable, the solution might lie in the interpreter itself.

As system administrators know, there is no shortage of PHP interpreters on the market.

Each has its own quirks, configurations, and compatibility issues, so there is no one-size-fits-all solution. But with a little experimentation and a lot of testing, performance gains can be substantial.

One of the most promising interpreters available today is the Hip-Hop Virtual Machine (HHVM). Developed in-house by Facebook, the name of the game for HHVM is speed. For one thing, HHVM meets the machine halfway, converting PHP source code into an intermediate bytecode (this bytecode, then, is what’s left to dynamically interpret at runtime).

This is similar to what PHP extensions like Xcache bring to the table. Another trick up its sleeves: HHVM uses a single multi-threaded process to handle requests (unlike, for example, PHP-FPM, which spawns multiple processes). This leads to much more stable CPU and memory usage for servers operating under heavy loads.

A couple months ago, one of the more resource-heavy sites managed by Bright Bright Great was suffering from a substantial increase in traffic. While publicity is one of the better problems to have, it did pose difficulties for the server, which simply wasn’t configured for that level of interest.

After some (read: lots of) compatibility testing, we successfully transitioned from PHP-FPM to HHVM. The results were astounding.

The average DOM-Ready time dropped by 46%. Simultaneous page serves increased by 112%. And CPU usage under load, once maxed out, remained stable at a healthy 55%.

In fact, the PHP gains were so successful the bottleneck was shifted to MySQL. After some code rewrites to help the site take advantage of object caching (via HHVM’s implementation of APC), the simultaneous page serving capacity was nearly doubled again.

But wait, there’s more!

As part of HHVM’s regular development cycle, its engineers are locked in a room with a surfeit of junk food and coffee (I imagine) and tasked to further optimize the code already in place. By focusing on improvements that directly benefit widely-used software, the real-world impact of any gains made is maximized. With the recent release of version 3.8, HHVM was able to serve 9.7% more pages per second for WordPress, 4.8% for MediaWiki, and 13.9% for Drupal8 (uncached).

We ran quick before and after tests ourselves (against the site previously mentioned) using the command line program siege. Sure enough, the upgrade was able to serve 4.3% more pages per second.

HHVM is indeed the gift that keeps on giving!

Posted By
Josh Stoik

How To Achieve an A+ SSL Configuration on Nginx

Qualys SSL Labs has a wonderful tool to help evaluate your server’s SSL configuration. I recommend you take a moment to scan your site and see how you fare. Go do that now. I’ll wait. If you didn’t get a perfect score, you aren’t alone.

The default configurations for popular server-side software like OpenSSL often support cryptographically weak standards that can put users at risk. Even major corporations have room for improvement.

Keep your server secure

If you’re on a shared server or at the mercy of control panel software like cPanel or Plesk, unfortunately there isn’t anything you can do. But if you have root access and a little knowing of the command-line, this article will help you harden your configuration so you can get a perfect A+.

While the following examples are specific to Nginx, they should be able to help point any readers running Apache or Lighttpd servers in the right direction.

Obtaining a Certificate

First, let’s start with a quick refresher on how you go about getting a certificate in the first place. Make a directory outside your web root, and once in that directory, generate a private key and certificate signing request (CSR). Your key should be at least 2048 bytes. In this example we’ll generate one that is 4096 bytes. If you have an existing key and can’t remember how strong it is, you can type:

openssl rsa -in domain.com.key -text -noout | head -n 1

To generate a new key, use:

mkdir /var/www/domain.com/ssl
cd /var/www/domain.com/ssl
openssl req -new -newkey rsa:4096 -nodes -keyout domain.com.key -out domain.com.csr

The openssl command will quiz you about your domain and organization. The Common Name (CN) needs to be the fully-qualified-domain-name for which you are purchasing the certificate. Remember, domain.com and www.domain.com are technically two different domains, so enter the CN exactly as visitors are expected to reach it. Some certificates, such as Comodo’s PositiveSSL, are magically valid for both www and non-www variants (aren’t they sweet?).

You should now find two files in your ssl directory: domain.com.key and domain.com.csr. That key is meant to be private, so take a moment to update its permissions. If 600 is too restrictive for your environment, 640 might do the trick.

chmod 0600 domain.com.key

Now go buy a certificate. Namecheap offers decent Comodo certificates from as low as $9, or you could spend more money elsewhere. It doesn’t much matter. As part of the certificate activation process, you’ll be asked for the CSR you just created, so keep that handy. Different Certificate Authorities (CA) have different validation processes, so just follow whatever instructions you’re given.

Once your certificate has been issued, upload them to the directory containing your key and CSR. If you are issued two or more bundled certificates (which is a common practice), they must be stitched together into a single file for Nginx. The order is important, so double-check the CA’s documentation. For Comodo PositiveSSL certificates, run the following:

cat domain.com.crt COMODORSADomainValidationSecureServerCA.crt COMODORSAAddTrustCA.crt AddTrustExternalCARoot.crt > domain.com.combined.crt

Nginx Set Up

By default, OpenSSL uses a weak 1024 byte key for Diffie Hellman key exchanges.  Let’s bump this up to 4096 by running the following command.  It can take a while to complete, so go make a sandwich:

openssl dhparam -out /etc/nginx/dhparams.pem 4096

Now let’s make Nginx’s SSL configuration a little more secure by adding the following code to the http{} block of your /etc/nginx/nginx.conf file:

http {
	...

	##
	# SSL Settings
	##

	## force modern protocols and ciphers
	## and enable ssl cache for a small
	## performance boost

	ssl_prefer_server_ciphers On;
	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
	ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS;
	ssl_session_cache shared:ssl_session_cache:10m;
	ssl_dhparam /etc/nginx/dhparams.pem;

	...
}

Now that we have the general out of the way, let’s move onto site-specific configurations. Open the configuration file corresponding to your site, something like /etc/nginx/sites-enabled/domain.com.conf.

Let’s start by adding a server block to redirect www.domain.com to domain.com, and also force HTTPS connections for everything. If www.domain.com is your main domain, simply reverse the www and non-www in the following examples.

server {
	listen 80;
	listen [::]:80 ipv6only=on;

	server_name www.domain.com domain.com;

	rewrite ^(.*) https://domain.com$1 permanent;
}

Now in your main server block for the domain, add the following:

server {
	listen 443 ssl;
	listen [::]:443 ssl ipv6only=on;

	server_name domain.com www.domain.com;

	#point to the combined certificate and key we generated earlier.
	ssl_certificate /home/domain.com/ssl/domain.com.combined.crt;
	ssl_certificate_key /home/domain.com/ssl/domain.com.key;

	#enable HSTS (in supported browsers) to make sure all subsequent
	#user requests are done over HTTPS regardless of protocol typed.
	add_header Strict-Transport-Security "max-age=31536000";

	#redirect non-canonical domain over SSL.
	#this will only work if your SSL certificate also covers www.domain.com.
	if ($host = 'www.domain.com' ) {
		rewrite ^(.*)$ https://domain.com$1 permanent;
	}
	...
}

If your server is not configured for IPv6, remove the second listen lines from the above examples. Speaking of IPv6, Nginx only wants to see a single ipv6only=on attribute per port across all your server blocks. If you have additional server blocks defined, simply omit that string from their definitions.

That’s it! Restart Nginx to apply the changes:

service nginx restart

Last Thoughts

You should now have pretty darn good SSL support! But the fun doesn’t end here!

New threats or vulnerabilities can pop up any time, to say nothing of the inevitable march of progress (advances in technology will eventually make your once great setup laughably inadequate).  Make sure you regularly apply any security patches made available to your distribution.  You should also periodically rerun the Qualys SSL Labs scan to see if any tweaks are needed to stay on top.

Posted By
Josh Stoik