Stylesheet Switching with PHP

All the hubub over the recent Wired News reformulation in XHTML and CSS has brought up a lot of questions. A recent one on css-discuss concerned the implementation of stylesheet switchers: bits of JavaScript or server-side logic that change the appearance of pages by loading different screen stylesheets. In the interest of advancing this technology, I've whipped up a PHP solution which I'd like to share with you.

It's kinda funny that, in between when I started this project (the date for this entry indicates the first time I saved it, not when it was published) and finished documenting it (approx. 2002-10-14 19:41), two completely separate implementations have been released. It's a strange coincidence, and by no means is the technique shared by all three revolutionary or ground-breaking. Regardless, check them all out and let me know which one you think is better. And remember: competition breeds innovation. ;)

The concept is simple: give the visitor a choice of styles from some predetermined list, and when they choose a new one, display those changes, and set a cookie so they don't have to make that choice on each subsequent request. This implementation consists of 4 separate chunks of code in 3 files that work together to save and display the users's preference for alternative stylesheets. Keeping with my slightly narcissistic tendency to name projects after myself, I've dubbed the collection of files "S3: Shawn's Style Switcher".

Configuration: s3.cfg.php

Our configuration file sets variables used by the other scripts. Centralizing this information allows you to add new stylesheet options to your entire site by changing a single file. Let's take a look at it:

<?php

$style_path = '/';

$available_styles = array(
    'default',
    'blue',
    'green'
);

$default_referrer = '/';

?>

Let's take a look at these variables:

string $style_path
the path (relative to the document root) of your stylesheets. If you keep all your stylesheets at the root, this should just be "/".
array $available_styles
An array of stylesheet names in your $style_path, sans file extension (e.g. "green" instead of "green.css"). The first value in this list should be the default (used if the supplied style doesn't exist in the array).
string $default_referrer
If somebody loads the switching script manually, the HTTP referrer won't be set. In this case, the redirect which sends them to the referring page would fail. Set this string to the location you'd like them sent to if this happens (I recommend either your site's root, or an error document). This can be either a full URL ("http://alterior.net"), or a URI relative to your domain ("/").

The configuration file will be read by all other scripts to determine which stylesheet to store as the cookie and to link to in your HTML. Now, let's take a look at the script that sets the cookie.

The Cookie: s3.php

The s3.php script take a request variable named "style", compares it with our configuration variable $available_styles, and attempts to set a cookie if the new value differs from the current cookie. If an HTTP referrer was supplied, the user is redirected back to that page, so they won't lose their place when they change styles. Otherwise, they'll be sent to the location indicated by $default_referrer.

<?php

if (!@include('s3.cfg.php')) 
    die("Unable to include config file!");

$set_cookie = true;

if (isset($_REQUEST['style'])) {

    $style = $_REQUEST['style'];

} elseif (isset($_COOKIE['style'])) {

    $style = $_COOKIE['style'];
    $set_cookie = false;

}

// if no style supplied, or invalid supplied...
if (!isset($style)
    || !in_array($style, $available_styles)) {
    // set style to default (first available)
    $style = $available_styles[0];
}

// our cookie will last 2 years, and will apply to
// all directories of all subdomains of example.com
if ($set_cookie)
    setcookie('style', $style,
              mktime() + 2 * 356 * 24 * 60 * 60,
              '/', '.example.com');

$referrer = (!empty($_SERVER['HTTP_REFERER']))
    ? $_SERVER['HTTP_REFERER']
    : $default_referrer;

header('Location: ' . $referrer); die();

?>

Obviously, you'll want to modify the arguments to setcookie() to suit your needs (at the very least, change "example.com" to your domain name, and remove the preceding "." if you don't have any subdomains). For a thorough explanation, take a look at PHP's function reference.

If all goes well, a cookie should have been set using the supplied request variable (either a GET with a query string variable, or a POST), and the user will be redirected back to the referring page. Now we can use that cookie on any subsequent request to write out a style declaration or link in any document on our site.

The Switch

The rest of our scripting will take place in the "guts" of your site. Try working off a copy of an existing HTML document, and remember to create easily distinguishable (for instance, using a different body background-color) copies of your default stylesheet (according to the names you supplied in $available_styles) so you can be sure things are working.

First, we need to include our configuration file again, so we can get our default style if the cookie hasn't been set. We'll also need the $style_path to form a path to the stylesheet. Replace the line that links your current stylesheet (in the <head> of your document) with the following:

<?php

$path = $_SERVER['DOCUMENT_ROOT'];

if (@include($path . '/s3.cfg.php')) {

    $style = (isset($_COOKIE['style']))
        ? $_COOKIE['style']
        : $available_styles[0];

    printf('<style type="text/css" media="screen">
           @import url(%s%s.css);
           </style>',
           $style_path, $style);
}

?>

The first line should set $path to the filesystem path of your site's root. If you've placed s3.cfg.php somwehere else, or this doesn't work, you may need to supply the full filesystem path to the file. If you have shell access to your webserver, cd to the directory and type pwd to find it. The line that sets $path should then look like:

$path = '/absolute/filesystem/path/to/s3.cfg.php';

If the configuration file is found (which means you can simply remove the configuration if you want to disable style switching altogether), a style element is written out that imports the stylesheet named either by the cookie, or the first string in our $available_styles array. If you normally use <link> to link your stylesheets, replace that printf() statement with the following:

    printf('<link rel="stylesheet" type="text/css"
           media="screen" href="%s%s.css" />',
           $style_path, $style);

You may choose to link multiple stylesheets, or to change the media from "screen" to "all" if you're interested in supporting Netscape 4. You may even wish to move the formatting string to the configuration file. This is left as an exercise to the reader. :)

We still need to give the user a way to change the stylesheets. I've chosen to do it with a form and a <select> input, but it would be perfectly reasonable to do it with a list of links. Try placing the following anywhere in your document (preferably with the rest of the navigation):

<form
    action="/s3.php"
    method="get">
    <p><label for="style">Style:</label>
        <select name="style">
        <?php
            foreach ($available_styles as $_style) {
                printf(
                    '<option value="%s"%s>%s</option>',
                    $_style,
                    ($_style == $style)
                        ? ' selected="selected"'
                        : '',
                    ucfirst($_style));
            }
        ?>
        </select>
        <input type="submit" value="switch" />
    </p>
</form>

If you've given the switching script another name, be sure to change the form's action attribute. I've chosen the GET method because it uses less bandwidth and it's easier to debug, but our receiving script will accept either GET or POST requests, as long as the supplied variable is named "style". If for some reason your webserver is running an ancient version of PHP (anything before 4.1), you may have to twiddle some variable names. Refer to the PHP manual for more info.

That's it! If you want to see it in action, I've implemented the whole thing on my site with stylesheets that change the color of the navigation and the permalinks. You can view their source (with PHP's ugly syntax highlighting) here: s3.cfg.php, s3.php, and index.php. The relevant stylesheets are red.css, blue.css, and green.css, which all import default.css. If you're short on time or bandwidth, download the demo tarball, which is just a snapshot example of this site. Take a look, play with the styles, and be sure to email me (shawn@alterior.net) if you've got any questions or recommendations for changes!

Everybody's doing it: XML (NetNewsWire recommended).

100% Valid or your money back.

Thanks, Movable Type!