Making a Font Selector

Posted April 3rd, 2007 by Mike Cherim

We recently made a font selector for this site to offer visitors the option of changing the type-face to something they’re more comfortable with. This font selector was made with a PHP Hypertext Preprocessor (PHP) script which, unlike JavaScript, is a server-side utility, meaning users don’t need to have any special add-ons or plugins for their browser to use it. We figured you might like to know how we made it in case you want to employ such a thing on your site.

But Why?

Before we get to actually showing you the script, let’s first discuss why we did it. You see, some people will suggest we don’t specify a font at all, letting the user’s browser preference dictate the type-face shown on the site — assuming the user has a preference and knows how to specify this preference. There’s nothing inherently wrong with letting the user’s preferences rule the day. There is, however, the matter of design and the designer’s own preference. In our eyes, as you probably know, design is an important part of a web site. Our artistic side feels a specific type-face is better than another, thus we specify that font in our style sheet. By doing this, though, we do take this out of the visitor’s hands. To be fair to both the visitor and the designer, a compromise is needed, and offering a scripted function like the one we offer is such a compromise. Here’s how.

How We Did It

The script is fairly straightforward. It’s made in three parts, as follows.

Part One

This part goes above the head of the document — even before the Document Type Declaration (DTD). It must be the first item on the page because the cookie must be set and negotiated before anything else happens.

 <?php
 // First we define the variables we will use
   $var_a=  "a";
   $var_b=  "b";
   $var_c=  "c";
 // Now we define our default “font”
   $font = "$var_a";
 // We check to see if the cookie has been set and we get the data, if the user has no preference
 if(isset( $_COOKIE["cookiename"] ) && !isset( $_GET["cookiename"]) ) {
     $font = $_COOKIE["cookiename"];
 }
 // If the user indicates a preference we set the cookie and redefine the font based on said preference
 if(isset( $_GET["cookiename"]) ) {
    @setcookie( "cookiename", "".$_GET["cookiename"]."", time()+60*60*24*30 );
     $font = "".$_GET["cookiename"]."";
 }
 // For security, we put the possible variables in an array
     $fonts = array( ''.$var_a.'',''.$var_b.'',''.$var_c.'' );
 // And if the variable is not what’s been identified, we revert to the default value
 if(!in_array($font, $fonts) ) {
     $font = $var_a;
 }
 // The as an added bit of security we sanitize that variable’s content (sort of unnecessary)
     $font = stripslashes(strip_tags(trim("$font")));
 // Now we negotiate the possible variables and define the values
 if($font == "a") {
     $font = "100% 'century gothic', futura, sans-serif";
 } else if($font == "b") {
     $font = "96% 'trebuchet ms', 'lucida grande', sans-serif";
 } else if($font == "c") {
     $font = "92% verdana, tahoma, sans-serif";
 } else {
     $font = "100% 'century gothic', futura, sans-serif";
 }
 ?>

Part Two

Now, within the head of the document we need to place the script’s output. In this example we specify the font used for the entire document including form elements. This needs to be placed after the normal screen media style sheet link/import statement. If you need more free downloadable fonts you may just google for “free fonts”.

 <!--Based on the data extracted, we output the preference or the default value-->
 <style type="text/css" media="screen">
   body, input, select, textarea { font : <?php echo(''.$font.''); ?>; }
 </style>

Part Three

Lastly we supply the user interface. We placed ours on our sidebar (home page only). You can style this however you like based on your needs. In our case we used a list.

 <ul>
 <!--Here we offer user the ability to set and define their cookie’s value and set their choice of output (the font)-->
  <li><h2 id="fonts">Font Selector</h2>
   <ul>
     <li><a href="<?php echo(''.htmlentities($_SERVER["PHP_SELF"]).'?cookiename='.$a.''); ?>">Century</a></li>
     <li><a href="<?php echo(''.htmlentities($_SERVER["PHP_SELF"]).'?cookiename='.$b.''); ?>">Trebuchet</a></li>
     <li><a href="<?php echo(''.htmlentities($_SERVER["PHP_SELF"]).'?cookiename='.$c.''); ?>">Verdana</a></li>
   </ul>
  </li>
 </ul>

Since we’re using WordPress which has a global header, we place Part One in a separate file and “include” it where it needs to be located. The include looks like this:

 <?php
  include("font-selector.php");
 ?>

Also, we have limited the interface location to just the home page and, since it’s WordPress, we have to re-include the script file into the file in which the interface exists, like so (this is Part Three, modified):

 <?php
   if(is_home() && !is_paged()) {
   include("font-selector.php");
 // The list of links and script include is only added to the “home page”
 ?>
 <ul>
 <!--Here we offer user the ability to set and define their cookie’s value and set their choice of output (the font)-->
  <li><h2 id="fonts">Font Selector</h2>
   <ul>
     <li><a href="<?php echo(''.htmlentities($_SERVER["PHP_SELF"]).'?cookiename='.$a.''); ?>">Century</a></li>
     <li><a href="<?php echo(''.htmlentities($_SERVER["PHP_SELF"]).'?cookiename='.$b.''); ?>">Trebuchet</a></li>
     <li><a href="<?php echo(''.htmlentities($_SERVER["PHP_SELF"]).'?cookiename='.$c.''); ?>">Verdana</a></li>
   </ul>
  </li>
 </ul>
 <?php
  } // Close the conditional output
 ?>

That’s it. It’s geared towards a WordPress installation, but even if you don’t use WordPress you’ll hopefully you find this useful. This should be a pretty secure script but there’s always room for improvement so if anyone has suggestions, please make them known.

Update: Team Access member Tommy Olsson suggested that for added security, $_SERVER["REQUEST_URI"] be used in lieu of $_SERVER["PHP_SELF"]. Or, perhaps just hard-coding the interface links could be done instead:

<a href="yoursite.com?cookiename=<?php echo(''.$a.''); ?>">Century</a>.

I often make scripts meant for public consumption so I don’t normally hardcode variables but this is good advice. Thanks Tommy.

Another Update: I added the htmlentities() function to the 'PHP_SELF' which also solves the security issue. I changed the main script accordingly in case it’s copied and pasted without reading the note above. htmlentities($_SERVER["PHP_SELF"]) Since this is redundant, though, I’d probably do this another way in practice.


14 Responses to: “Making a Font Selector”

  1. Rich Pedley responds:
    Posted: April 3rd, 2007 at 1:58 pm

    Hmmm, I see a few usability problems with this myself. You currently have:
    Century, Trebuchet, Helvetica, Verdana, Georgia, Gill Sans, Lucida and Arial in the list for Accessites.

    I don’t know about you but I can’t tell by the name what a font actually looks like. Surely the text for the font names should be in the relevant font to make things clearer? Which should be easily enough achieved via a span.
    (and apologies I missed the original post about this on Accessites.)

  2. Craig Francis responds:
    Posted: April 4th, 2007 at 3:22 am

    Its a very good script, just a couple of questions though…

    # Why use a GET variable called ‘cookiename’, instead of something like ‘font’?

    # What is the ’stripslashes’ etc used for? its only being used on an “if” condition, which should not cause any security problems… if someone tries to add html code into the variable (for an XSS attack), then it will fall back to the safe ‘else’ option.

    # In the ‘if’ condition, you could remove the first test (font == ‘a’), as the ‘else’ will catch it, this will also remove the need to use the ‘in_array’ check, as it will also fall back to the ‘else’ default.

    # I am a little unsure why there are quotes around the variables, ('’.$var_a.'’)… typically you don’t need to do this ($var_a)… however I have seen a couple of programmers doing this now, so perhaps I’m missing something.

    # When you are doing the assignment of the font string, I would personally use a different variable instead of over-writing the $font value, as this helps distinguish the two roles.

    Please note that this is not any criticism of your work, it seems to be perfectly good code, and I really like that your using the ‘isset’ function to catch undefined variables.

    Best Regards,
    Craig

  3. Rich Pedley responds:
    Posted: April 4th, 2007 at 4:20 am

    Looks better, though how many people will realise they are in different fonts is another matter.

  4. Craig Francis responds:
    Posted: April 5th, 2007 at 3:27 am

    @Mike - All very good comments… it good to see bits of code being shared like this.

    I’m just still a little unsure about the comment about ‘all variables should be quoted’.

    Using the PEAR sample file, it looks like they don’t use this convention…

    http://pear.php.net/manual/en/standards.sample.php

    However, that is not to say its not a convention, I just don’t understand the purpose… I wonder if perhaps this might have been taken out of context, as a similar rule is that you should quote all variables which will be sent in an SQL statement. In its briefest form…

    mysql_query('DELETE FROM table WHERE id="' . mysql_real_escape_string($_GET['id']) . '"');

    In this (far from perfect) example, the escape string function stops the ID from being set to…

    http://example.com/?id=1%22%20OR%20%22%22%20=%20%22

    As after removing the URL encoding and adding it to the above SQL without the quoting, this will produce

    DELETE FROM table WHERE id="1" OR "" = "";

    This will effectively delete every record in the table… on the other hand, if the quotes were used, then it will form:

    DELETE FROM table WHERE id="1\" OR \"\" = \"";

    Which should not delete any records, taking that the ID is an integer.

    Hopefully that describes the basis of SQL injection, which can get messy when you start talking about using.

    However, most of this is irrelevant… its still good work… well done!

  5. Craig Francis responds:
    Posted: April 6th, 2007 at 3:28 am

    @Mike To be honest, your overkill method is the one I tend to prefer… its better to cover as many potential security holes as possible, than to only do ‘just enough’, or in other words ‘just about works’.

    As a general rule, you should not trust any variables your script has not created (user input, server variables, values from the database, etc)… which you seem to have covered quite well… but its even questionable if you should even trust the variables your code has created (who knows who will edit the script in the future and put un-safe data in that variable).

    So all my variables go though mysql_escape_string() before being sent to the database, even those which I have just defined myself… anything that goes in a URL, goes though urlencode()… anything that gets printed to the page goes though htmlentities()… anything that is used in a perl regular expression goes though preg_quote()

    Also, anything which is used to open a file is cleaned using a regular expression like ’s/[^a-z0-9]//i’, as this stops a variable being set like:

    $_GET['path'] = '../../../../etc/passwd';
    $myPath = preg_replace('/[^a-z0-9]/i', '', $_GET['path']);
    fpassthru('/www/files/' . $myPath);

    I find it handy to use a variable prefix to help define the role of the variable… for example, if I have a variable that needs to contain a link, then I cannot pass it though htmlentities()… so to mark that variable as holding ‘HTML safe data’, its prefixed with $html… for example $htmlError… any variables which are passed into that variable MUST go though htmlentities()… then I know its contents are safe to be printed out onto the page… for example:

    $htmlError = 'This article already exists (view)’;
    echo $htmlError;

    Its the same with SQL… if I’m building up a dynamic WHERE clause, I usually create it using an $sqlWhere variable.

  6. Craig Francis responds:
    Posted: April 7th, 2007 at 3:02 am

    @Mike: If your up for the challenge, I suggest you get your servers error log emailed to you on a weekly basis (logrotate)… and if you have one, the PHP error log… then try to use Aliases to clean out the usual scanning requests (/MSOffice/cltreq.asp) and if necessary, some RewriteRule’s for the old URL’s.

    Once you start getting clean-ish error logs, then it provides another source to check for broken links, etc.

    NOTE: For PHP on a live server, set:
    error_reporting = E_ALL
    display_errors = Off
    log_errors = On
    error_log = /var/log/php.log

  7. Best of March 2007 | Smashing Magazine responds:
    Posted: April 11th, 2007 at 11:05 am

    […] Making a Font Selector “We recently made a font selector for this site to offer visitors the option of changing the type-face to something they’re more comfortable with. This font selector was made with a PHP Hypertext Preprocessor (PHP) script which, unlike JavaScript, is a server-side utility, meaning users don’t need to have any special add-ons or plugins for their browser to use it.” […]

  8. Webdesign (css, grafica e altro) » Blog Archive » Best of March 2007 responds:
    Posted: April 18th, 2007 at 11:43 am

    […] Making a Font Selector “We recently made a font selector for this site to offer visitors the option of changing the type-face to something they’re more comfortable with. This font selector was made with a PHP Hypertext Preprocessor (PHP) script which, unlike JavaScript, is a server-side utility, meaning users don’t need to have any special add-ons or plugins for their browser to use it.” […]

  9. Marie ALHOMME responds:
    Posted: May 8th, 2007 at 2:06 pm

    Hello !

    I just LOVED the article, so I translated it to French, it’s available at the following url :
    french translation.
    Hope you find it useful !

    Marie

    Bonjour !

    J’ai ADOR2 cet article et l’ai donc traduit en Français, disponible à l’adresse suivante :
    traduction française.
    J’espère qu’elle vous rendra service !

    Marie

Sorry. Comments are closed.




Note: This is the end of the usable page. The image(s) below are preloaded for performance only.