<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xml:base="http://www.dyeager.org"  xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
 <title>Frames of Reference - PHP</title>
 <link>http://www.dyeager.org/taxonomy/term/6/0</link>
 <description></description>
 <language>en</language>
<item>
 <title>MSN Live Search API V2.0 in PHP</title>
 <link>http://www.dyeager.org/post/2009/01/msn-live-search-api-v20-php</link>
 <description>&lt;p&gt;At the end of 2008, Microsoft abandoned their old search API. While older applications continue to work, new AppID&amp;#8217;s &lt;em&gt;only&lt;/em&gt; work with the new V2.0 API. If you&amp;#8217;ve created a new AppID, and wonder why none of the code samples you find on the Web work with it, now you know. It&amp;#8217;s not hard to update code to use the newer (still in beta) V2.0 API, and we&amp;#8217;ll do exactly that by &lt;a href=&quot;/post/2009/01/site-search-using-msn-api-php&quot;&gt;updating our previous API search code&lt;/a&gt;. If you&amp;#8217;ve used the old code, it&amp;#8217;s quite simple to update to the new code. Just copy the file, and change one line in your PHP page &amp;#8212; the new code requires PHP5, so be sure your server has PHP5 installed.&lt;/p&gt;

&lt;h2&gt;Quick Update&lt;/h2&gt;

&lt;p&gt;Get the &lt;a href=&quot;http://www.dyeager.org/downloads/MSNSearchV2.php.txt&quot;&gt;new Search API V2.0 PHP class&lt;/a&gt; and copy it to the same location as the older code. Find the line in your PHP script reading &lt;code&gt;include (&quot;MSNSearch.php&quot;)&lt;/code&gt; and replace it with &lt;code&gt;include (&quot;MSNSearchV2.php&quot;)&lt;/code&gt;. You&amp;#8217;ll then need to get a new &lt;a href=&quot;http://search.live.com/developers&quot;&gt;AppID from Microsoft&lt;/a&gt; and update your page (V1.0 AppID&amp;#8217;s don&amp;#8217;t work with V2.0 API, and vice-versa).&lt;/p&gt;

&lt;h2&gt;How it works&lt;/h2&gt;

&lt;p&gt;Examine the source code for the class yourself &amp;#8212; it formats a HTTP URL and sends it to Microsoft&amp;#8217;s server, receiving the search response as XML. It then uses &lt;a href=&quot;http://www.php.net/simplexml&quot;&gt;PHP&amp;#8217;s SimpleXML&lt;/a&gt; to parse the results (only in PHP5, which is why PHP5 is required). The class has a few helper functions to assist so you don&amp;#8217;t have to write search forms or format results, and CSS classes tag all HTML code for easy formatting.&lt;/p&gt;

&lt;p&gt;Microsoft&amp;#8217;s new V2.0 API&amp;#8217;s don&amp;#8217;t require SOAP (although you can use it if you want), so the previous requirement of a SOAP library disappears. The new V2.0 API&amp;#8217;s have the option to send requests as HTTP (returning results formatted as XML) without using SOAP.&lt;/p&gt;

&lt;p&gt;To get a functioning search page, place this PHP code snippet in a script and you&amp;#8217;ll have a ready-made search page, with a few customizations for your specific site.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Place the &lt;a href=&quot;http://www.dyeager.org/downloads/MSNSearchV2.php.txt&quot;&gt;Search API V2.0 PHP class&lt;/a&gt; in the same directory as your script (or change the include line to point it to the location of where you placed it). &lt;/li&gt;
&lt;li&gt;Place your AppID key instead of &amp;#8220;INSERTAPIKEYHERE&amp;#8221;. &lt;/li&gt;
&lt;li&gt;If you don&amp;#8217;t have your script on your webserver as &lt;code&gt;/search-msn.php&lt;/code&gt;, change the definition of &lt;code&gt;SEARCH_URL&lt;/code&gt;. &lt;/li&gt;
&lt;/ol&gt;

&lt;pre class=&quot;code notranslate&quot;&gt;&amp;lt;?php
define(&quot;SEARCH_URL&quot;,&quot;/search-msn.php&quot;);
include(&quot;MSNSearchV2.php&quot;);

# Values from HTTP GET, or reasonable defaults
$start = isset($_GET[&#039;start&#039;]) ? intval($_GET[&#039;start&#039;]) : 1;
$q = isset($_GET[&#039;q&#039;]) ? $_GET[&#039;q&#039;] : &quot;&quot;;
if ($start &amp;lt; 1)
    $start = 1;

print searchform($q,&quot;form_top&quot;,SEARCH_URL);
if (strlen($q) &amp;gt; 1) {
  $msnsearch = new MSNSearch(&#039;INSERTAPIKEYHERE&#039;);
  $sresult = false;
  $msnsearch-&amp;gt;setQuery($q);
  $msnsearch-&amp;gt;setPage($start);
  $sresult = $msnsearch-&amp;gt;search();
  if (($sresult === true) &amp;amp;&amp;amp; ($msnsearch-&amp;gt;totalRecords &amp;gt; 0)) {
    print $msnsearch-&amp;gt;search_header($q);
    print $msnsearch-&amp;gt;search_results();
    print $msnsearch-&amp;gt;search_navagation($q);
    print searchform($q,&quot;form_bottom&quot;,SEARCH_URL);
  } else 
    print &quot;&amp;lt;p&amp;gt;Sorry, no results found for &amp;lt;b&amp;gt;$q&amp;lt;/b&amp;gt;.&amp;lt;/p&amp;gt;\n&quot;;
}
?&amp;gt;&lt;/pre&gt;

&lt;p&gt;All PHP code on this page licensed under a &lt;a href=&quot;http://www.dyeager.org/downloads/license-bsd.php&quot;&gt;BSD license&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.dyeager.org/articles/msn-search-api/&quot;&gt;MSN API V1.0 Search in PHP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://search.live.com/developers&quot;&gt;MS Live API V2.0 Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://search.live.com/developer&quot;&gt;MS Live API V1 Documentation&lt;/a&gt; (Deprecated)&lt;/li&gt;
&lt;/ul&gt;</description>
 <comments>http://www.dyeager.org/post/2009/01/msn-live-search-api-v20-php#comments</comments>
 <category domain="http://www.dyeager.org/category/tags/php">PHP</category>
 <category domain="http://www.dyeager.org/category/tags/programming">Programming</category>
 <pubDate>Sat, 03 Jan 2009 21:59:45 +0000</pubDate>
 <dc:creator>yeager</dc:creator>
 <guid isPermaLink="false">52 at http://www.dyeager.org</guid>
</item>
<item>
 <title>PHP, XHTML MIME type and Caching</title>
 <link>http://www.dyeager.org/post/2009/01/php-xhtml-mime-type-caching</link>
 <description>&lt;p&gt;May 2005 — Updated Dec 2005, July 2006&lt;/p&gt;

&lt;h2&gt;Introduction&lt;/h2&gt;

&lt;p&gt;Using PHP for your XHTML web site is great, but if you’re not 
considering possible effects you could be violating Internet 
standards and reducing 
the usability for your users. You might not know your 
validated and tested XHTML is treated as “tag soup” by browsers,
or that without sending the proper headers your pages caching ability is severely
reduced, or perhaps some browsers (due to bugs) won’t cache your page at all.
When using PHP with XHTML, several issues need to be considered.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;XHTML and MIME type.&lt;/strong&gt; You should be serving XHTML 
as &lt;span class=&quot;code&quot;&gt;application/xhtml+xml&lt;/span&gt; instead of 
&lt;span class=&quot;code&quot;&gt;text/html&lt;/span&gt;. But not all clients 
support it. How to serve it properly to clients following standards, without 
making the site unusable to outdated clients? 
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHP and Caching.&lt;/strong&gt; A small amount of work is 
required to improve the caching ability of your site, reduce bandwidth 
and improve response time.
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default Caching&lt;/strong&gt; of other static items (CSS, images, 
etc). This requires &lt;span class=&quot;code&quot;&gt;mod_expires&lt;/span&gt; and a few 
&lt;span class=&quot;code&quot;&gt;htaccess&lt;/span&gt; lines.
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Problems caused by buggy browsers&lt;/strong&gt;. 
Internet Explorer has some nasty bugs existing from 4.x all the way to 6.x. 
This complicates your job as
you’ll have to work-around IE’s bugs. Yes, it’s true we shouldn’t have 
to do this (as it’s the clients fault, not ours) if they fail to follow 
standards, but in the real world IE is extremely common, and we don’t 
want to penalize those people just because a company in Washington can’t 
(or won’t) follow Internet standards.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This article assumes you’ve got a background on Apache and some basic 
HTTP protocol knowledge; if you don’t know what etag, 304, gzip, 
If-Modified-Since, If-None-Match, HTTP headers and MIME types are see 
the references at the end of this article (As always, Google is your friend).
&lt;/p&gt;

&lt;p&gt;We’ll also assume you’re using Apache on *nix with a recent PHP 
(4.3.x) version, are interested in writing valid XHTML, following web 
standards, and improving bandwidth usage of your site.
&lt;/p&gt;

&lt;h2&gt;Caching&lt;/h2&gt;

&lt;p&gt;Caching makes your site more responsive to the user, but 
caching involves two different functions. First, avoiding trips to 
the server if possible (using cache-control and expires), and second, 
if we must go to the server, avoid transferring the document 
(using validation and 304 responses).
&lt;/p&gt;

&lt;p&gt;Unfortunately with PHP caching will generally not work “out 
of the box”. We’ve got to do a little work to get browsers to optimally 
cache our documents. Then Apache should be configured such that 
static documents (CSS, JS, images, etc) will be cached. This requires 
&lt;span class=&quot;code&quot;&gt;mod_expires&lt;/span&gt; and a few simple 
&lt;span class=&quot;code&quot;&gt;htaccess&lt;/span&gt; lines.&lt;/p&gt;

&lt;h2&gt;XHTML and mimetype&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://www.w3.org/TR/xhtml-media-types/&quot;&gt;W3C&lt;/a&gt; says to serve XHTML as 
&lt;span class=&quot;code&quot;&gt;application/xhtml+xml&lt;/span&gt;, but IE can’t handle it, 
while modern browsers (Firefox, Opera, etc) can. We’ve got to change 
the MIME type for modern browsers, while falling back gracefully to 
outdated browsers like IE. However, we don’t want to “browser sniff” 
the User-Agent as this is unreliable at best.
&lt;/p&gt;

&lt;p&gt;Fortunately, the HTTP protocol has a means to do exactly what we want: 
the &lt;span class=&quot;code&quot;&gt;Accept&lt;/span&gt; header. Each request by a client has 
the option to specify to 
the server exactly what it wants. A few examples make this clear.
&lt;/p&gt;

&lt;pre class=&quot;code&quot;&gt;Firefox 1.0.3 
text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,
text/plain;q=0.8,image/png,*/*;q=0.5
&lt;/pre&gt;

&lt;pre class=&quot;code&quot;&gt;IE 6.0 SP1
image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, 
application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
&lt;/pre&gt;

&lt;div class=&quot;warning&quot;&gt;
&lt;h5&gt;Warning!&lt;/h5&gt;
&lt;p&gt;You should &lt;strong&gt;&lt;em&gt;*never*&lt;/em&gt;&lt;/strong&gt; try out code on your production server. &lt;strong&gt;Always&lt;/strong&gt; have a test or development box for trying out new code. Experimenting on your live server is asking for trouble!&lt;/p&gt;
&lt;/div&gt;


&lt;p&gt;Its easy to determine Firefox prefers 
&lt;span class=&quot;code&quot;&gt;application/xhtml+xml&lt;/span&gt;, while IE can’t handle it. 
Notice the line in Firefox saying &lt;span class=&quot;code&quot;&gt;q=0.9&lt;/span&gt;? That’s 
a Q-Value, and it’s used so a client can say what they &lt;em&gt;prefer&lt;/em&gt;, while also 
saying what the can &lt;em&gt;accept&lt;/em&gt;. Q values range from 0.000 to 1.000, although 
most only use one decimal. 
&lt;/p&gt;

&lt;p&gt;For firefox, it has &lt;span class=&quot;code&quot;&gt;application/xhtml+xml&lt;/span&gt; 
with no q-value (assume 1), while &lt;span class=&quot;code&quot;&gt;text/html&lt;/span&gt; 
has q-value of 0.9. Thus Firefox prefers 
&lt;span class=&quot;code&quot;&gt;application/xhtml+xml&lt;/span&gt; (since 1.0 &amp;gt; .9), 
although it can accept &lt;span class=&quot;code&quot;&gt;text/html&lt;/span&gt;. 
By using the &lt;span class=&quot;code&quot;&gt;Accept&lt;/span&gt; header the client sends us,
we avoid problematic browser-sniffing and allow the client to 
tell us what MIME type they prefer.
&lt;/p&gt;

&lt;h2&gt;Solution (The Code)&lt;/h2&gt;

&lt;p&gt;Here’s the code; we’ll explain it after you’ve looked at 
it. Save this as 
&lt;span class=&quot;code&quot;&gt;include-mime.php&lt;/span&gt; and later we’ll have an example 
that uses it. You can also grab the &lt;a href=&quot;/downloads/php_xhtml.tar.gz&quot;&gt;php_xhtml.tar.gz&lt;/a&gt; 
file available for download.&lt;/p&gt;

&lt;pre class=&quot;code&quot;&gt;&amp;lt;?php
##############################################################################
#     XHTML and mimetype script for PHP                                      #
#     Copyright (C) 2005  Darrin Yeager                                      #
#     All rights reserved.                                                   #
#     http://www.dyeager.org                                                 #
#                                                                            #
# Redistribution and use in source and binary forms, with or without         #
# modification, are permitted provided that the following conditions         #
# are met:                                                                   #
#                                                                            #
#   1. Redistributions of source code must retain the above copyright        #
#      notice, this list of conditions and the following disclaimer.         #
#                                                                            #
#   2. Redistributions in binary form must reproduce the above copyright     #
#      notice, this list of conditions and the following disclaimer in the   #
#      documentation and/or other materials provided with the distribution.  #
#                                                                            #
#   3. Redistributions of modified versions must carry prominent notices     #
#      stating that you changed the files and the date of any change.        #
#                                                                            #
#   4. Neither the name of Darrin Yeager nor the names of any contributors   #
#      may be used to endorse or promote products derived from this software #
#      without specific prior written permission.                            #
#                                                                            #
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS        #
# &quot;AS IS&quot; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT          #
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR      # 
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT       #
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,      #
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED   #
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR     #
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF     #
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING       #
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS         #
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.               #
#                                                                            #
##############################################################################

$charset = &quot;utf-8&quot;;
$mime = &quot;text/html&quot;;
$is304 = false;

# NOTE: To allow for q-values with one space (text/html; q=0.5), 
# use the following regex:
# &quot;/text\/html;[\ ]{0,1}q=([0-1]{0,1}\.\d{0,4})/i&quot;
if((isset($_SERVER[&quot;HTTP_ACCEPT&quot;])) &amp;amp;&amp;amp; (stristr($_SERVER[&quot;HTTP_ACCEPT&quot;],&quot;application/xhtml+xml&quot;)))  {
   if(preg_match(&quot;/application\/xhtml\+xml;q=([0-1]{0,1}\.\d{0,4})/i&quot;,$_SERVER[&quot;HTTP_ACCEPT&quot;],$matches)) {
      $xhtml_q = $matches[1];
      if(preg_match(&quot;/text\/html;q=([0-1]{0,1}\.\d{0,4})/i&quot;,$_SERVER[&quot;HTTP_ACCEPT&quot;],$matches)) {
         $html_q = $matches[1];
         if((float)$xhtml_q &amp;gt;= (float)$html_q)
            $mime = &quot;application/xhtml+xml&quot;;
      }
   }
   else
	  $mime = &quot;application/xhtml+xml&quot;;
}

# Get the file stats and compute last-modified time.
$filestats = @stat($_SERVER[&quot;SCRIPT_FILENAME&quot;]);
$lastmod = $filestats[9] - date(&#039;Z&#039;);  #Convert Local time -&amp;gt; GMT

# ETag is &quot;inode-lastmodtime-filesize&quot; - See PHP stat function for more detail
$etag = &#039;&quot;&#039; . dechex($filestats[1]) . &quot;-&quot; . dechex($lastmod) . &quot;-&quot; . dechex($filestats[7]) . &#039;&quot;&#039;;

# Check HTTP_IF_NONE_MATCH
# and report a 304 Not Modified header if they match.
if (isset ($_SERVER[&quot;HTTP_IF_NONE_MATCH&quot;])) {
	if ($etag === stripslashes($_SERVER[&quot;HTTP_IF_NONE_MATCH&quot;])) 
		$is304 = true;
}

if ($is304) {
	if (isset($_SERVER[&quot;SERVER_PROTOCOL&quot;]) &amp;amp;&amp;amp; $_SERVER[&quot;SERVER_PROTOCOL&quot;] == &quot;HTTP/1.1&quot;) 
		header(&quot;HTTP/1.1 304 Not Modified&quot;);
	else
		header(&quot;HTTP/1.0 304 Not Modified&quot;);
	header(&quot;ETag: &quot; . $etag);
	header(&quot;Vary: Accept&quot;);
	header(&quot;Connection: close&quot;);
	exit;
}

header(&quot;Content-Type: $mime;charset=$charset&quot;);
header(&quot;Cache-Control: max-age=86400, s-maxage=86400&quot;);
header(&quot;Vary: Accept&quot;);
# If for some reason we didn&#039;t get a valid file modification time
# from the stat function, or it errored out, DO NOT send the ETag
# header as it will not be valid. Valid in this since is defined
# as modified AFTER Dec 24, 1999.
if ($lastmod &amp;gt; 946080000) {        # 946080000 = Dec 24, 1999 4PM
	header(&quot;ETag: &quot; . $etag);
}

if (DOCTYPE == &quot;strict&quot;) { ?&amp;gt;
&amp;lt;!DOCTYPE html 
     PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot;
     &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&amp;gt;
&amp;lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;en&quot; lang=&quot;en&quot;&amp;gt;
&amp;lt;?php } else if (DOCTYPE == &quot;math&quot;) { ?&amp;gt;
&amp;lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN&quot;
               &quot;http://www.w3.org/TR/MathML2/dtd/xhtml-math11-f.dtd&quot;&amp;gt;
&amp;lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;en&quot;&amp;gt;
&amp;lt;?php } else { ?&amp;gt;
&amp;lt;!DOCTYPE html
   PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot;
   &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&amp;gt;
&amp;lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;en&quot; lang=&quot;en&quot;&amp;gt;
&amp;lt;?php } ?&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;&amp;lt;?php echo $mime ?&amp;gt;;charset=&amp;lt;?php echo $charset ?&amp;gt;&quot; /&amp;gt;
&lt;/pre&gt;


&lt;p&gt;Now that you’ve reviewed the code, let’s examine it line by line and see how it works.&lt;/p&gt;

&lt;pre class=&quot;code&quot;&gt;$charset = &quot;utf-8&quot;;
$mime = &quot;text/html&quot;;
$is304 = false;
&lt;/pre&gt;
&lt;p&gt;Set a few default variables. The default MIME type is 
&lt;span class=&quot;code&quot;&gt;text/html&lt;/span&gt; — unless it’s been changed by this script later to 
&lt;span class=&quot;code&quot;&gt;application/xhtml+xml&lt;/span&gt;. The default character set is 
&lt;span class=&quot;code&quot;&gt;utf-8&lt;/span&gt;, but if you use some other character set, change 
the variable here. The last variable is just a flag to indicate if we’re going to return a 
&lt;span class=&quot;code&quot;&gt;304 Not Modified&lt;/span&gt; header. By default we won’t.&lt;/p&gt;

&lt;pre class=&quot;code&quot;&gt;# NOTE: To allow for q-values with one space (text/html; q=0.5), 
# use the following regex:
# &quot;/text\/html;[\ ]{0,1}q=([0-1]{0,1}\.\d{0,4})/i&quot;
if((isset($_SERVER[&quot;HTTP_ACCEPT&quot;])) &amp;amp;&amp;amp; (stristr($_SERVER[&quot;HTTP_ACCEPT&quot;],&quot;application/xhtml+xml&quot;)))  {
   if(preg_match(&quot;/application\/xhtml\+xml;q=([0-1]{0,1}\.\d{0,4})/i&quot;,$_SERVER[&quot;HTTP_ACCEPT&quot;],$matches)) {
      $xhtml_q = $matches[1];
      if(preg_match(&quot;/text\/html;q=([0-1]{0,1}\.\d{0,4})/i&quot;,$_SERVER[&quot;HTTP_ACCEPT&quot;],$matches)) {
         $html_q = $matches[1];
         if((float)$xhtml_q &amp;gt;= (float)$html_q)
            $mime = &quot;application/xhtml+xml&quot;;
      }
   }
   else
	  $mime = &quot;application/xhtml+xml&quot;;
}
&lt;/pre&gt;
&lt;p&gt;We’ve already discussed the &lt;span class=&quot;code&quot;&gt;Accept&lt;/span&gt; header above 
and what it does, so now just determine what the &lt;span class=&quot;code&quot;&gt;Accept&lt;/span&gt; 
header contains, and if the client wants 
&lt;span class=&quot;code&quot;&gt;application/xhtml+xml&lt;/span&gt; or just 
&lt;span class=&quot;code&quot;&gt;text/html&lt;/span&gt;. The regex code is similar to other code
on the web, but this regex checks  
the entire float decimal, not just the integer value after the decimal, and looks 
for up to 4 decimal points of precision. It then compares as float, not integer.
The code isn’t as bad as it looks — if you strip out the regex parts you get the 
following algorithm:
&lt;/p&gt;

&lt;pre&gt;If Accept header was sent and application/xhtml+xml exists in it
     If q-value exists for application/xhtml+xml
          get qvalue for text/html (if it exists)
          if q-application/xhtml+xml &amp;gt;= q-text/html
               mimetype = application/xhtml+xml
     else
          mimetype = application/xhtml+xml
else
     mimetype = text/html
&lt;/pre&gt;

&lt;pre class=&quot;code&quot;&gt;# Get the file stats and compute last-modified time.
$filestats = @stat($_SERVER[&quot;SCRIPT_FILENAME&quot;]);
$lastmod = $filestats[9] - date(&#039;Z&#039;);  #Convert Local time -&amp;gt; GMT

# ETag is &quot;inode-lastmodtime-filesize&quot; - See PHP stat function for more detail
$etag = &#039;&quot;&#039; . dechex($filestats[1]) . &quot;-&quot; . dechex($lastmod) . &quot;-&quot; . dechex($filestats[7]) . &#039;&quot;&#039;;
&lt;/pre&gt;
&lt;p&gt;Calculate the ETag the HTTP protocol returns to the client. If any of the document 
changes, this ETag &lt;strong&gt;must&lt;/strong&gt; also. The ETag is a string, so you’re free to determine 
it any way you want. For this use, we use the *nix INode, Last modified time (converted 
to GMT), and 
the file size in bytes, all converted to hex, which is similar to the method Apache 
uses for static HTML files. &lt;/p&gt;
&lt;p&gt;However, if this is not specific for your needs, you’ll need to find another 
way to generate a unique ETag. For example, sites mainly using databases may use one 
script for many different pages. If so, you must find a unique ETag algorithm. 
This ETag will be returned to us if a client has a copy of the page, but doesn’t 
know if it’s current or not. That’s why it must be unique.
&lt;/p&gt;

&lt;pre class=&quot;code&quot;&gt;# Check HTTP_IF_NONE_MATCH
# and report a 304 Not Modified header if they match.
if (isset ($_SERVER[&quot;HTTP_IF_NONE_MATCH&quot;])) {
	if ($etag === stripslashes($_SERVER[&quot;HTTP_IF_NONE_MATCH&quot;])) 
		$is304 = true;
}
if ($is304) {
	if (isset($_SERVER[&quot;SERVER_PROTOCOL&quot;]) &amp;amp;&amp;amp; $_SERVER[&quot;SERVER_PROTOCOL&quot;] == &quot;HTTP/1.1&quot;) 
		header(&quot;HTTP/1.1 304 Not Modified&quot;);
	else
		header(&quot;HTTP/1.0 304 Not Modified&quot;);
	header(&quot;ETag: &quot; . $etag);
	header(&quot;Vary: Accept&quot;);
	header(&quot;Connection: close&quot;);
	exit;
}

&lt;/pre&gt;

&lt;p&gt;If the client sent us back an ETag for verification, check and see if 
it’s the same one we just calculated. If it is, we will not need to send 
the document, just a 
&lt;span class=&quot;code&quot;&gt;304 Not Modified&lt;/span&gt; header, and exit the script. 
No body will be (or needs to be) sent. However, you should send the 
&lt;span class=&quot;code&quot;&gt;Vary&lt;/span&gt; header so the client knows the response
depends on the original &lt;span class=&quot;code&quot;&gt;Accept&lt;/span&gt; header.
&lt;/p&gt;

&lt;pre class=&quot;code&quot;&gt;header(&quot;Content-Type: $mime;charset=$charset&quot;);
header(&quot;Cache-Control: max-age=86400, s-maxage=86400&quot;);
header(&quot;Vary: Accept&quot;);
# If for some reason we didn&#039;t get a valid file modification time
# from the stat function, or it errored out, DO NOT send the ETag
# header as it will not be valid. Valid in this since is defined
# as modified AFTER Dec 24, 1999.
if ($lastmod &amp;gt; 946080000) { 				# 946080000 = Dec 24, 1999 4PM
	header(&quot;ETag: &quot; . $etag);
}
&lt;/pre&gt;

&lt;p&gt;We’ve now obtained a valid ETag, determined the client needs the full 
body of the page, and  are ready to send it. We set a header with the 
correct MIME type we’ve determined earlier and a header for caching. The 
&lt;span class=&quot;code&quot;&gt;cache-control&lt;/span&gt; header allows clients and proxies 
to store this page for 1 day and use it as fresh, &lt;em&gt;without checking 
with the server again&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;The &lt;span class=&quot;code&quot;&gt;Vary&lt;/span&gt; header tells the client and any 
proxy the content can vary depending on what the client can Accept. 
Unfortunately IE has a long-standing bug with regards to the 
&lt;span class=&quot;code&quot;&gt;Vary&lt;/span&gt; header 
which prevents caching any content with a &lt;span class=&quot;code&quot;&gt;Vary&lt;/span&gt; 
header other than &lt;span class=&quot;code&quot;&gt;User-Agent&lt;/span&gt;. We’ll workaround 
this bug by enabling gzip compression of 
the page later in Apache which forces IE to work as it should. &lt;em&gt;If 
you don’t enable compression, IE will never locally cache your page.&lt;/em&gt; 
This bug has existed from IE 4.x until 6.x. Some day Microsoft might fix it. 
(July 2006 — see update at end for IE7)&lt;/p&gt;

&lt;pre class=&quot;code&quot;&gt;if (DOCTYPE == &quot;strict&quot;) { ?&amp;gt;
&amp;lt;!DOCTYPE html 
     PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot;
     &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&amp;gt;
&amp;lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;en&quot; lang=&quot;en&quot;&amp;gt;
&amp;lt;?php } else if (DOCTYPE == &quot;math&quot;) { ?&amp;gt;
&amp;lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN&quot;
               &quot;http://www.w3.org/TR/MathML2/dtd/xhtml-math11-f.dtd&quot;&amp;gt;
&amp;lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;en&quot;&amp;gt;
&amp;lt;?php } else { ?&amp;gt;
&amp;lt;!DOCTYPE html
   PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot;
   &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&amp;gt;
&amp;lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;en&quot; lang=&quot;en&quot;&amp;gt;
&amp;lt;?php } ?&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;&amp;lt;?php echo $mime ?&amp;gt;;charset=&amp;lt;?php echo $charset ?&amp;gt;&quot; /&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Now we send a correct DOCTYPE and output a META tag with the MIME 
type and the correct character set. Which DOCTYPE is used depends on 
the DOCTYPE variable in your PHP page. The remainder of your script 
follows as normal.&lt;/p&gt;

&lt;h2&gt;Configure Apache&lt;/h2&gt;

&lt;p&gt;Our PHP page is now setup to be cached for a period of time of 
our choosing, and to respond to validation requests. But we still 
must deal with static content. Apache will do the heavy lifting for us, 
as long as we tell it what we want it to do. This is simple — to 
configure Apache for cache-control of static content, add lines like the following in 
&lt;span class=&quot;code&quot;&gt;.htaccess&lt;/span&gt; or put them in your 
&lt;span class=&quot;code&quot;&gt;httpd.conf&lt;/span&gt;.
&lt;/p&gt;

&lt;pre class=&quot;code&quot;&gt;ExpiresActive On
ExpiresByType text/css A1209600
ExpiresByType image/png A2592000
ExpiresByType image/gif A2592000
ExpiresByType image/x-icon A5184000

php_flag zlib.output_compression On
php_value zlib.output_compression_level 1
&lt;/pre&gt;

&lt;p&gt;You must have &lt;span class=&quot;code&quot;&gt;mod_expires&lt;/span&gt; in your Apache 
(most do) for this to work. Using &lt;span class=&quot;code&quot;&gt;mod_expires&lt;/span&gt; 
you can set default cache 
retention times for various types of static files. The numbers are the 
expiration in seconds since the client requested it. So 
&lt;span class=&quot;code&quot;&gt;A86400&lt;/span&gt; means the content is valid for one 
day — the client can just use it from the cache without checking the 
server again. Nothing needs to be sent over the network for one day. 
Naturally, you’ll want to fine-tune these numbers for your own use.&lt;/p&gt;

&lt;p&gt;For the compression level, just like gzip it can be a level from 1-9. 
Level 1 is the lowest, while 9 is the highest level of compression. 
However, going from 1 to 9 dramatically increases the CPU time to compress, 
while not yielding much size improvement. For example, on level 1, you might 
get 60% compression, while on level 9 it might increase to 67% or so, 
but at the cost of much more CPU time. Since web servers can be busy, 
leave it at level one to make life easy for your CPU. 
&lt;/p&gt;

&lt;p&gt;It’s worth mentioning again, IE has a bug with regards to the &lt;span class=&quot;code&quot;&gt;Vary&lt;/span&gt; 
header. We &lt;strong&gt;MUST&lt;/strong&gt; enable GZIP or IE will never cache our page. 
Other browsers (Firefox, Opera, etc) handle this correctly — it’s 
just IE with the difficulty. 
GZIP is expressed in &lt;span class=&quot;code&quot;&gt;Accept-Encoding&lt;/span&gt; header 
sent by client. If that header indicates the client can handle gzip 
compressed content, PHP does all the work, including sending the appropriate 
&lt;span class=&quot;code&quot;&gt;Vary:Accept-Encoding&lt;/span&gt; header. If the client 
indicates they can’t handle compressed content, PHP sends the file uncompressed.
&lt;/p&gt;

&lt;h2&gt;Example&lt;/h2&gt;

&lt;pre class=&quot;code&quot;&gt;&amp;lt;?php
# to use XHTML 1.0 Strict DTD
define(&quot;DOCTYPE&quot;,&quot;strict&quot;);

# to use XHTML 1.1 MathML DTD
#define(&quot;DOCTYPE&quot;,&quot;math&quot;);

# if DOCTYPE is not defined, the transitional
# XHTML 1.0 DTD will be used.
require_once(&quot;include-mime.php&quot;);
?&amp;gt;
&amp;lt;title&amp;gt;Test Page&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;

&amp;lt;h1&amp;gt;Test Page&amp;lt;/h1&amp;gt;

&amp;lt;h2&amp;gt;Server Variables&amp;lt;/h2&amp;gt;
&amp;lt;pre&amp;gt;
&amp;lt;?php
foreach ($_SERVER as $key =&amp;gt; $value)
  print &quot;$key: $value\n&quot;;
?&amp;gt;
&amp;lt;/pre&amp;gt;


&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Save this file off and try it! By changing the 
&lt;span class=&quot;code&quot;&gt;DOCTYPE&lt;/span&gt; variable, you can get 
&lt;span class=&quot;code&quot;&gt;strict, transitional&lt;/span&gt; or the new 
&lt;span class=&quot;code&quot;&gt;MathML&lt;/span&gt; DTD.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;That’s it. You’ll need to modify the cache times for your site, but 
with GZIP compression, appropriate cache-control and &lt;span class=&quot;code&quot;&gt;304 Not Modified&lt;/span&gt; 
responses, you should see bandwidth reduction, and your users will see a 
snappier responding web site. We’ve also worked around Microsoft’s crippled browser.
&lt;/p&gt;

&lt;div class=&quot;olive-box&quot;&gt;
&lt;h3&gt;Update on If-Modified-Since (Dec 2005)&lt;/h3&gt;
&lt;p&gt;Someone noticed I don’t mention &lt;span class=&quot;code&quot;&gt;If-Modified-Since&lt;/span&gt; 
in this code. The reason is two-fold. First, it’s the older protocol, 
and most modern browsers should support ETags. 
Second, it’s not as flexible. For example, suppose you’re dealing with a blog 
 — how do you handle last modified date? You could use the last article date 
(which is reasonable), but that presents you with a problem. How do you deal 
with different pages being served depending on whether the user is logged 
in or not? The article dates are the same, yet the page served is different. 
This situation is difficult to deal with using only dates, but with ETags 
it’s easy — just add some reference (like user ID) to your ETag, and the 
browser cache will work correctly.&lt;/p&gt;
&lt;p&gt;If you want to modify the code on this page to handle 
&lt;span class=&quot;code&quot;&gt;If-Modified-Since&lt;/span&gt; it’s not really that hard, 
and is left as an exercise for the interested reader.&lt;/p&gt;
&lt;/div&gt;

&lt;div class=&quot;olive-box&quot;&gt;
&lt;h3&gt;Update on IE7 (July 2006)&lt;/h3&gt;
&lt;p&gt;Unfortunately, testing with IE7 beta 3 reveals Microsoft still hasn’t fixed the caching bug (at least in the betas, maybe in the final release?). IE 7 won&#039;t have &lt;span class=&quot;code&quot;&gt;application/xhtml+xml&lt;/span&gt; support either. Or a truly fixed CSS implementation. Sigh. It appears IE7, instead of truly being a step forward (after 5 years since IE 6), should really be considered IE6 SP2.&lt;/p&gt;
&lt;p&gt;We can only hope IE8 fixes these bugs and truly supports XHTML and CSS. After all, the standards have been around for a decade, and Microsoft certainly has the resources (if not the desire) to fully implement web standards.&lt;/p&gt;
&lt;/div&gt;

&lt;div class=&quot;olive-box&quot;&gt;
&lt;h3&gt;License Change (September 2006)&lt;/h3&gt;
&lt;p&gt;Previously released under GPL, this code is now under a License &lt;a href=&quot;/downloads/license-1.php&quot;&gt;similar to BSD&lt;/a&gt;. The actual license is in the code on this page.&lt;/p&gt;
&lt;p&gt;The download file contains the GPL license. You many choose either the GPL license in the download file or the BSD-style license contained in the code on this page. It’s your option.&lt;/p&gt;
&lt;/div&gt;

&lt;h2&gt;References&lt;/h2&gt;

&lt;p&gt;
&lt;a href=&quot;http://www.mnot.net/cache_docs/&quot;&gt;http://www.mnot.net/cache_docs/&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://lists.over.net/pipermail/mod_gzip/2002-December/006826.html&quot;&gt;http://lists.over.net/pipermail/mod_gzip/2002-December/006826.html&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://www.sitepoint.com/forums/printthread.php?t=158442&quot;&gt;http://www.sitepoint.com/forums/printthread.php?t=158442&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://www.w3.org/TR/xhtml-media-types/&quot;&gt;http://www.w3.org/TR/xhtml-media-types/&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://hixie.ch/advocacy/xhtml&quot;&gt;http://hixie.ch/advocacy/xhtml&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://www.xml.com/pub/a/2003/03/19/dive-into-xml.html&quot;&gt;http://www.xml.com/pub/a/2003/03/19/dive-into-xml.html&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://keystonewebsites.com/articles/mime_type.php&quot;&gt;http://keystonewebsites.com/articles/mime_type.php&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://simon.incutio.com/archive/2003/04/23/conditionalGet&quot;&gt;http://simon.incutio.com/archive/2003/04/23/conditionalGet&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://alexandre.alapetite.net/doc-alex/php-http-304/index.en.html&quot;&gt;http://alexandre.alapetite.net/doc-alex/php-http-304/index.en.html&lt;/a&gt;
&lt;/p&gt;

&lt;p class=&quot;footer&quot;&gt;All PHP code on this page is licensed under a License &lt;a href=&quot;/downloads/license-1.php&quot;&gt;similar to BSD&lt;/a&gt;. See the license contained in the code on this page for more information.&lt;/p&gt;</description>
 <comments>http://www.dyeager.org/post/2009/01/php-xhtml-mime-type-caching#comments</comments>
 <category domain="http://www.dyeager.org/category/tags/php">PHP</category>
 <category domain="http://www.dyeager.org/category/tags/programming">Programming</category>
 <pubDate>Thu, 01 Jan 2009 08:00:00 +0000</pubDate>
 <dc:creator>yeager</dc:creator>
 <guid isPermaLink="false">139 at http://www.dyeager.org</guid>
</item>
<item>
 <title>Automatic Web Page Translation with Google</title>
 <link>http://www.dyeager.org/post/2008/12/automatic-web-page-translation-google</link>
 <description>&lt;p&gt;Previously, we wrote about getting the &lt;a href=&quot;/post/2008/10/getting-browser-default-language-php&quot;&gt;browser default language&lt;/a&gt; in PHP. But so what? What can you do with it? In this article, we&amp;#8217;ll use the previous code, and expand on it to create a page which can be automatically translated into many languages &amp;#8212; with the help of the Google&amp;nbsp;translator.&lt;/p&gt;

&lt;h2&gt;Method&lt;/h2&gt;

&lt;p&gt;The method is simple. Create a drop down box and fill it with the names of languages you want to translate to, then call Google&amp;#8217;s translator with the selected language. Not really that hard at all, if you take it piece by&amp;nbsp;piece.&lt;/p&gt;

&lt;p&gt;But how good is Google&amp;#8217;s translator? If you understand multiple languages, check it out for yourself. Naturally, it&amp;#8217;s not as good as a manual translation, but when faced with a choice of nothing at all, Google&amp;#8217;s machine translation isn&amp;#8217;t really so&amp;nbsp;bad.&lt;/p&gt;

&lt;p&gt;But how can a person know you offer such a service, if they can&amp;#8217;t read the message about selecting alternative languages? That&amp;#8217;s where the previous detection of the browser&amp;#8217;s default language comes in, as we&amp;#8217;ll create a message in whatever the default language is, prompting them to choose a translation. Thus, even if they can&amp;#8217;t read anything on your site, they at least can read a prompt (in their native language) allowing them to translate your page into their&amp;nbsp;language.&lt;/p&gt;

&lt;p&gt;We&amp;#8217;ll tackle the job in several&amp;nbsp;steps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a list of drop down&amp;nbsp;languages.&lt;/li&gt;
&lt;li&gt;Create javascript event on the drop down list so when it changes, automatic translation is&amp;nbsp;performed.&lt;/li&gt;
&lt;li&gt;Tie it all up to load when the page loads, using unobtrusive&amp;nbsp;javascript.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;The&amp;nbsp;Code&lt;/h2&gt;

&lt;p&gt;The following is &lt;a href=&quot;http://www.dyeager.org/downloads/license-bsd.php&quot;&gt;BSD-Licensed&lt;/a&gt; Javascript and PHP&amp;nbsp;code.&lt;/p&gt;

&lt;p&gt;First, create a standard HTML option box, with one snippet of PHP code to get a prompt in whatever the users default language&amp;nbsp;is.&lt;/p&gt;

&lt;p&gt;&lt;pre class=&quot;code notranslate&quot;&gt;
&amp;lt;select id=&quot;gtrans&quot;&amp;gt;
    &amp;lt;option&amp;gt;&amp;lt;?php print getLanguage();?&amp;gt;&amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;fr&quot;&amp;gt;Français (French)&amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;de&quot;&amp;gt;Deutsch (German)&amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;el&quot;&amp;gt;Ελληνικά (Greek)&amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;iw&quot;&amp;gt;עברית (Hebrew)&amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;it&quot;&amp;gt;Italiano (Italian)&amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;ru&quot;&amp;gt;Русский (Russian)&amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;es&quot;&amp;gt;Español (Spanish)&amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;vi&quot;&amp;gt;Việt (Vietnamese)&amp;lt;/option&amp;gt;
&amp;lt;/select&amp;gt;
&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;getLanguage()&lt;/code&gt; code is simple, and guarantees even if a user can&amp;#8217;t read anything on your page, they can at least read a prompt for translation. It uses the &lt;code&gt;getDefaultLanguage()&lt;/code&gt; from our previous article on &lt;a href=&quot;/post/2008/10/getting-browser-default-language-php&quot;&gt;PHP browser language detection&lt;/a&gt;, so we won&amp;#8217;t discuss it further here &amp;#8212; you can refer to the previous article for&amp;nbsp;more.&lt;/p&gt;

&lt;p&gt;&lt;pre class=&quot;code notranslate&quot;&gt;
function getLanguage() {
   # Drop any region from the default language.
   #   i.e. en-us becomes just en.
   $lang = getDefaultLanguage();
   $x = explode(&quot;-&quot;,$lang);
   switch ($x[0]) {
      case &quot;de&quot;: return &quot;Wählen Sie eine Sprache&quot;;
      case &quot;el&quot;: return &quot;Επιλέξτε Γλώσσα&quot;;
      case &quot;es&quot;: return &quot;Seleccione Idioma&quot;;
      case &quot;fr&quot;: return &quot;Sélection de la langue&quot;;
      case &quot;it&quot;: return &quot;Seleziona Lingua&quot;;
      case &quot;iw&quot;: return &quot;בחר שפה&quot;;
      case &quot;ru&quot;: return &quot;Выбор языка&quot;;
      case &quot;vi&quot;: return &quot;Chọn ngôn ngữ&quot;;
      default: return &quot;Select Language&quot;;
   }
}
&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;So now we&amp;#8217;ve got a list of languages, along with a prompt in the users own language so they can read the prompt, even if they can&amp;#8217;t read the rest of the page. We now need to attach a javascript onchange event to the box, and create the code to call Google&amp;#8217;s translator when the language selection&amp;nbsp;changes.&lt;/p&gt;

&lt;p&gt;&lt;pre class=&quot;code notranslate&quot;&gt;
&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
function init() {
   var e = document.getElementById(&quot;gtrans&quot;);
   if (e)
      e.onchange = function() {
         gtranslate(this.options[this.selectedIndex].value)
      };
}
//
function gtranslate(lang) {
   var url = &quot;http://translate.google.com/translate?u=&quot; +
      encodeURIComponent(location.host + location.pathname) +
      &quot;&amp;amp;hl=en&amp;amp;ie=UTF-8&amp;amp;sl=en&amp;amp;tl=&quot; + lang;
   //window.location opens in current window, window.open is new window
   //window.location = url;
   window.open(url);
}
//Be careful if you integrate this into other code so it won&#039;t drop the 
// other code&#039;s window.onload event.
window.onload = init;
&amp;lt;/script&amp;gt;
&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;In the window.onload event, we add an event handler to the combo box, and create the event handler to call Google&amp;#8217;s&amp;nbsp;translator.&lt;/p&gt;

&lt;p&gt;And that&amp;#8217;s it! Note: Your web server has to be a server available to Google for the translation to work, as Google will retrieve the page in order to translate&amp;nbsp;it.&lt;/p&gt;

&lt;h2&gt;Caveats&lt;/h2&gt;

&lt;p&gt;If a user has javascript disabled, the box will appear, but won&amp;#8217;t work. To solve this, you can use DOM methods and create the drop down entirely in javascript, thus if a user has javascript disabled, they&amp;#8217;ll see nothing. That solution is just straight javascript, and isn&amp;#8217;t included here as it would complicate the solution. It&amp;#8217;s left to the interested&amp;nbsp;reader.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Be sure&lt;/em&gt; to use UTF-8 as your character encoding. If you&amp;#8217;re not using UTF-8 right now, convert all your documents to it &amp;#8212; you&amp;#8217;ll be glad you did&amp;nbsp;later.&lt;/p&gt;

&lt;p&gt;Second, if you&amp;#8217;re sending different language-specific content at the same URL, be sure to send the appropriate Vary header. If you don&amp;#8217;t, intermediate proxy caches might be confused and serve the wrong language to some people. To do that, just use the following first in your PHP code: &lt;code&gt;header(&quot;Vary: Accept-Language&quot;)&lt;/code&gt;. But be warned Internet Explorer has some bugs with the Vary header you should be aware&amp;nbsp;of.&lt;/p&gt;

&lt;p&gt;For more on q-values, IE bugs, and more explanation on the regular expressions and headers in general, read our previous article &lt;a href=&quot;/post/2009/01/php-xhtml-mime-type-caching&quot;&gt;Serving XHTML With the Correct MIMETYPE&lt;/a&gt; for discussions of similar issues. You &lt;em&gt;are&lt;/em&gt; serving your XHTML correctly as &lt;code&gt;application/xhtml+xml&lt;/code&gt; aren&amp;#8217;t&amp;nbsp;you?&lt;/p&gt;

&lt;h2&gt;References&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://translate.google.com/&quot;&gt;Google&amp;nbsp;Translator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/post/2008/10/getting-browser-default-language-php&quot;&gt;Getting Broswer Default Langauge with&amp;nbsp;PHP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.w3.org/International/articles/language-tags/&quot;&gt;Language Tags in HTML and XHTML&amp;nbsp;(w3c)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p class=&quot;footer&quot;&gt;All PHP and Javascript code on this page is licensed under a &lt;a href=&quot;/downloads/license-bsd.php&quot;&gt;BSD License&lt;/a&gt;.&lt;/p&gt;</description>
 <comments>http://www.dyeager.org/post/2008/12/automatic-web-page-translation-google#comments</comments>
 <category domain="http://www.dyeager.org/category/tags/php">PHP</category>
 <category domain="http://www.dyeager.org/category/tags/programming">Programming</category>
 <pubDate>Fri, 26 Dec 2008 19:37:29 +0000</pubDate>
 <dc:creator>yeager</dc:creator>
 <guid isPermaLink="false">51 at http://www.dyeager.org</guid>
</item>
<item>
 <title>Getting the Browser Default Language in PHP</title>
 <link>http://www.dyeager.org/post/2008/10/getting-browser-default-language-php</link>
 <description>&lt;p&gt;If you&amp;#8217;re doing international (i18n or Iñtërnâtiônàlizætiøn) work (or just want to make your site available in several languages), you&amp;#8217;ll likely need to determine the users default language in your PHP code to determine which language to serve up. Searching the web yields one common code piece frequently; unfortunately as you&amp;#8217;ll soon see it may not give you the results you need as it ignores parts of the HTTP spec which may or may not be critical to the accuracy of the results.&lt;/p&gt;

&lt;h2&gt;HTTP Language Headers&lt;/h2&gt;

&lt;p&gt;The interchange between browser and server transfers information about the client and its capabilities in headers &amp;#8212; user agent, what it will accept, and (what we&amp;#8217;re interested in) language. The browser sends language information in a header called &lt;code&gt;HTTP_ACCEPT_LANGUAGE&lt;/code&gt;, which looks something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; es,en-us;q=0.3,de;q=0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Those values state the browser accepts Spanish (es), US English (en-us), and German (de). Obviously, most browsers don&amp;#8217;t send so many possibilities, but you get the idea. Most of the code you can find to determine default language simply searches the header for the first 2-letter language code  and returns the first it finds. But looking at the example, you&amp;#8217;ll note some additional information &lt;code&gt;q=0.3&lt;/code&gt; &amp;#8212; what&amp;#8217;s that?&lt;/p&gt;
&lt;h2&gt;HTTP Header Q-Values for HTTP_ACCEPT_LANGUAGE&lt;/h2&gt;

&lt;p&gt;As part of the HTTP spec, those are Q-Values, and must be a number between 0 and 1 (if no number appears, you can assume the value as 1). Q-Values provide not only information to what a browser &lt;em&gt;supports&lt;/em&gt;, but what it &lt;em&gt;prefers&lt;/em&gt;. In the previous example, &lt;code&gt;es&lt;/code&gt; has no q-value, so it&amp;#8217;s 1.0, while &lt;code&gt;en-us&lt;/code&gt; is 0.3 and &lt;code&gt;de&lt;/code&gt; is 0.1 so that means this client can handle Spanish, US English, or German &amp;#8212; but &lt;em&gt;prefers&lt;/em&gt; Spanish if it&amp;#8217;s available. If it&amp;#8217;s not, the server is free to send any of the other supported choices.&lt;/p&gt;

&lt;p&gt;Now you see the problem &amp;#8212; if you only search the &lt;code&gt;HTTP_ACCEPT_LANGUAGE&lt;/code&gt; header for a match and ignore the q-value, you have no way to determine what language the client &lt;em&gt;prefers&lt;/em&gt; &amp;#8212; you&amp;#8217;ll only get a match for support. Or at worse, if a q-value is 0 (meaning no support at all), you&amp;#8217;ll get a language the client specifically tells you &lt;em&gt;not&lt;/em&gt; to send. Why simple reg-ex solutions work becomes obvious after examining some actual &lt;code&gt;HTTP_ACCEPT_LANGUAGE&lt;/code&gt; headers sent by popular browsers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;en-us,en;q=0.5&lt;/code&gt;  (Mozilla)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;en-US,en;q=0.9&lt;/code&gt; (Opera)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;en-us&lt;/code&gt; (Internet Explorer)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;en&lt;/code&gt; (Lynx)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In those cases a simple string match works, even if q-values are ignored. But if the actual &lt;code&gt;HTTP_ACCEPT_LANGUAGE&lt;/code&gt; HTTP header contains multiple languages with differing q-values like &lt;code&gt;&quot;en,de;q=0.9&quot;&lt;/code&gt; (a person whose primary language is English, but knows German) simple string searches fail spectacularly. Obviously, we must consider q-values if our results are to be correct.&lt;/p&gt;

&lt;h2&gt;Algorithm&lt;/h2&gt;

&lt;p&gt;The solution is simple. Break apart the string into it&amp;#8217;s language components (they&amp;#8217;re separated by commas), and then pick the one with the highest q-value to use (assume any language lacking q-values have a value of 1.0). In our example, we&amp;#8217;ll split the string and get the following array back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;es&lt;/code&gt; &amp;#8212; Spanish, assume q-value = 1.0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;en-us;q=0.3&lt;/code&gt; &amp;#8212; US English, with q-value of 0.3&lt;/li&gt;
&lt;li&gt;&lt;code&gt;de;q=0.1&lt;/code&gt; &amp;#8212; German, with q-value of 0.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now with the languages identified,use regular expressions to extract the q-value, if it exists. Once all the q-values are assigned, select the one with the highest q-value, if it exists. If multiple languages have the same q-value, it&amp;#8217;s safe to use any of them equally.&lt;/p&gt;

&lt;h2&gt;The Code&lt;/h2&gt;

&lt;p&gt;The following is &lt;a href=&quot;http://www.dyeager.org/downloads/license-bsd.php&quot;&gt;BSD-Licensed&lt;/a&gt; PHP code.&lt;/p&gt;

&lt;pre class=&quot;code notranslate&quot;&gt;
#########################################################
# Copyright © 2008 Darrin Yeager                        #
# http://www.dyeager.org/                               #
# Licensed under BSD license.                           #
#   http://www.dyeager.org/downloads/license-bsd.php    #
#########################################################

function getDefaultLanguage() {
   if (isset($_SERVER[&quot;HTTP_ACCEPT_LANGUAGE&quot;]))
      return parseDefaultLanguage($_SERVER[&quot;HTTP_ACCEPT_LANGUAGE&quot;]);
   else
      return parseDefaultLanguage(NULL);
   }

function parseDefaultLanguage($http_accept, $deflang = &quot;en&quot;) {
   if(isset($http_accept) &amp;amp;&amp;amp; strlen($http_accept) &amp;gt; 1)  {
      # Split possible languages into array
      $x = explode(&quot;,&quot;,$http_accept);
      foreach ($x as $val) {
         #check for q-value and create associative array. No q-value means 1 by rule
         if(preg_match(&quot;/(.*);q=([0-1]{0,1}\.\d{0,4})/i&quot;,$val,$matches))
            $lang[$matches[1]] = (float)$matches[2];
         else
            $lang[$val] = 1.0;
      }

      #return default language (highest q-value)
      $qval = 0.0;
      foreach ($lang as $key =&amp;gt; $value) {
         if ($value &amp;gt; $qval) {
            $qval = (float)$value;
            $deflang = $key;
         }
      }
   }
   return strtolower($deflang);
}
&lt;/pre&gt;

&lt;p&gt;Then in your code, just call &lt;code&gt;getDefaultLanguage()&lt;/code&gt; and you&amp;#8217;ll get a string back with the highest q-value language sent by the browser in the &lt;code&gt;HTTP_ACCEPT_LANGUAGE&lt;/code&gt; header.&lt;/p&gt;

&lt;h2&gt;Caveats&lt;/h2&gt;

&lt;p&gt;First,  &lt;em&gt;be sure&lt;/em&gt; to use UTF-8 as your character encoding. If you&amp;#8217;re not using UTF-8 right now, convert all your documents to it &amp;#8212; you&amp;#8217;ll be glad you did later.&lt;/p&gt;

&lt;p&gt;Second, if you&amp;#8217;re sending different language-specific content at the same URL, be sure to send the appropriate Vary header. If you don&amp;#8217;t, intermediate proxy caches might be confused and serve the wrong language to some people. To do that, just use the following first in your PHP code: &lt;code&gt;header(&quot;Vary: Accept-Language&quot;)&lt;/code&gt;. But be warned Internet Explorer has some bugs with the Vary header you should be aware of.&lt;/p&gt;

&lt;p&gt;For more on q-values, IE bugs, and more explanation on the regular expressions and headers in general, read our previous article &lt;a href=&quot;/post/2009/01/php-xhtml-mime-type-caching&quot;&gt;Serving XHTML With the Correct MIMETYPE&lt;/a&gt; for discussions of similar issues. You &lt;em&gt;are&lt;/em&gt; serving your XHTML correctly as &lt;code&gt;application/xhtml+xml&lt;/code&gt; aren&amp;#8217;t you?&lt;/p&gt;

&lt;h2&gt;So what?&lt;/h2&gt;

&lt;p&gt;What&amp;#8217;s this good for? In a future article, we&amp;#8217;ll demonstrate how to use this method to get instant translations of your web pages into many different languages &amp;#8212; automatically.&lt;/p&gt;

&lt;h2&gt;References&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/post/2009/01/php-xhtml-mime-type-caching&quot;&gt;Serving XHTML With Correct MIMETYPE&lt;/a&gt; (More on q-values)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4&quot;&gt;HTTP 1.1 Specification&lt;/a&gt; (Section 14.4 Accept-Language header)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.w3.org/International/articles/language-tags/&quot;&gt;Language Tags in HTML and XHTML (w3c)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.rfc-editor.org/rfc/rfc4646.txt&quot;&gt;RFC 4646&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.iana.org/assignments/language-subtag-registry&quot;&gt;List of Language Tags&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p class=&quot;footer&quot;&gt;All PHP code on this page is licensed under a &lt;a href=&quot;/downloads/license-bsd.php&quot;&gt;BSD License&lt;/a&gt;.&lt;/p&gt;</description>
 <comments>http://www.dyeager.org/post/2008/10/getting-browser-default-language-php#comments</comments>
 <category domain="http://www.dyeager.org/category/tags/php">PHP</category>
 <category domain="http://www.dyeager.org/category/tags/programming">Programming</category>
 <pubDate>Sat, 18 Oct 2008 18:41:55 +0000</pubDate>
 <dc:creator>yeager</dc:creator>
 <guid isPermaLink="false">43 at http://www.dyeager.org</guid>
</item>
<item>
 <title>Optimizing PHP Code for Caching with Movable Type</title>
 <link>http://www.dyeager.org/post/2008/03/optimizing-php-code-caching-movable-type</link>
 <description>&lt;p&gt;One of the advantages of Movable Type over Wordpress comes from publishing static &lt;span class=&quot;caps&quot;&gt;HTML &lt;/span&gt;files (fetching data from the database for each page request doesn&#039;t scale well). By creating static &lt;span class=&quot;caps&quot;&gt;HTML &lt;/span&gt;files, round trips to the database are eliminated and your blog is more likely to handle sudden bursts of high traffic. How many times have you gone to a popular Wordpress blog and seen database errors like &quot;too many database connections&quot;?&lt;/p&gt;

&lt;p&gt;Additionally, Apache generates &lt;span class=&quot;code&quot;&gt;ETag&lt;/span&gt; and &lt;span class=&quot;code&quot;&gt;Last-Modified&lt;/span&gt; headers for you on static &lt;span class=&quot;caps&quot;&gt;HTML &lt;/span&gt;files, and automatically sends &lt;span class=&quot;code&quot;&gt;304 Not Modified&lt;/span&gt; headers as well. If you don&#039;t know why this is important, read the &lt;a href=&quot;http://www.mnot.net/cache_docs/&quot;&gt;definitive introduction to caching&lt;/a&gt; -- we&#039;ll assume you have a solid foundation in the basics and won&#039;t review that material here. Suffice it to say, caching and proper headers are important to save server &lt;span class=&quot;caps&quot;&gt;CPU &lt;/span&gt;and bandwidth.&lt;/p&gt;

&lt;p&gt;However, static &lt;span class=&quot;caps&quot;&gt;HTML &lt;/span&gt;files are, well, static. Converting Movable Type blogs to &lt;span class=&quot;caps&quot;&gt;PHP &lt;/span&gt;is simple, but if you&#039;ve already published as &lt;span class=&quot;caps&quot;&gt;HTML &lt;/span&gt;you&#039;ll have all new &lt;span class=&quot;caps&quot;&gt;URL&#039;&lt;/span&gt;s after converting to &lt;span class=&quot;caps&quot;&gt;PHP &lt;/span&gt;-- a situation we want to avoid. But the big problem for scripting languages like &lt;span class=&quot;caps&quot;&gt;PHP, ASP &lt;/span&gt;or &lt;span class=&quot;caps&quot;&gt;CGI &lt;/span&gt;is Apache won&#039;t automatically handle &lt;span class=&quot;code&quot;&gt;ETag&#039;s&lt;/span&gt; or &lt;span class=&quot;code&quot;&gt;304 Not Modified&lt;/span&gt; headers. &lt;em&gt;Every&lt;/em&gt; time a page is requested, the full page is sent -- even if it&#039;s not needed. That wastes bandwidth.&lt;/p&gt;

&lt;p&gt;We want to avoid that, and it&#039;s simple -- we&#039;ll tackle it in two parts.&lt;/p&gt;&lt;h2&gt;Configure Apache to parse &lt;span class=&quot;caps&quot;&gt;HTML &lt;/span&gt;files with &lt;span class=&quot;caps&quot;&gt;PHP&lt;/span&gt;&lt;/h2&gt;

&lt;p&gt;First, in your &lt;span class=&quot;code&quot;&gt;.htaccess&lt;/span&gt; file, add the line&lt;/p&gt;

&lt;p class=&quot;code&quot;&gt;AddType application/x-httpd-php .php .html&lt;/p&gt;

&lt;p&gt;This configures Apache to treat both php &lt;span class=&quot;caps&quot;&gt;AND &lt;/span&gt;html files as &lt;span class=&quot;caps&quot;&gt;PHP.&lt;/span&gt; If you don&#039;t have any &lt;span class=&quot;caps&quot;&gt;PHP &lt;/span&gt;code in your &lt;span class=&quot;caps&quot;&gt;HTML &lt;/span&gt;files, the &lt;span class=&quot;caps&quot;&gt;PHP &lt;/span&gt;interpreter simply passes the &lt;span class=&quot;caps&quot;&gt;HTML &lt;/span&gt;along. However, by doing this you&#039;ll notice the &lt;span class=&quot;code&quot;&gt;ETag&lt;/span&gt; and &lt;span class=&quot;code&quot;&gt;Last-Modified&lt;/span&gt; headers are gone. And thus your ability to properly cache the page as well. That&#039;s bad, but not hard to fix.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;caps&quot;&gt;NOTE&lt;/span&gt;: &lt;span class=&quot;caps&quot;&gt;NEVER EVER &lt;/span&gt;test on your production server. &lt;em&gt;Always&lt;/em&gt; have a test environment to play with. Testing on your production server is begging for problems. You should read and understand the code before using it.&lt;/p&gt;

&lt;p&gt;To get proper headers back when using &lt;span class=&quot;caps&quot;&gt;PHP, &lt;/span&gt;use the following &lt;a href=&quot;http://www.dyeager.org/downloads/php-caching.php.txt&quot;&gt;script&lt;/a&gt; and place it at the top of your &lt;span class=&quot;caps&quot;&gt;PHP &lt;/span&gt;files with a line like the following: &lt;span class=&quot;code&quot;&gt;&amp;lt;?php require($_SERVER[&#039;DOCUMENT_ROOT&#039;] . &quot;/php-caching.php&quot;); ?&amp;gt;&lt;/span&gt; (assuming you&#039;ve placed the script in your web servers root directory).&lt;/p&gt;

&lt;p&gt;That&#039;s it. You&#039;ll now have appropriate headers and caching is enabled. Read the script and note you can control the Expires header (default expiration is 1 minute). Also note the script uses the file date of the page as the Last-Modified time. Normally this is what you want, but if it&#039;s not you&#039;ll have to change the code.&lt;/p&gt;

&lt;h2&gt;Movable Type and &lt;span class=&quot;caps&quot;&gt;PHP&lt;/span&gt;&lt;/h2&gt;

&lt;p&gt;One more thing for Movable Type needs to be addressed. Some of Movable Type uses Perl &lt;span class=&quot;caps&quot;&gt;CGI, &lt;/span&gt;and we don&#039;t want to inject &lt;span class=&quot;caps&quot;&gt;PHP &lt;/span&gt;on those pages. Dynamic &lt;span class=&quot;caps&quot;&gt;CGI &lt;/span&gt;isn&#039;t cachable anyway, so it&#039;s not a big issue, but &lt;span class=&quot;caps&quot;&gt;PHP &lt;/span&gt;code won&#039;t work on Perl pages. Depending on how modified your Movable Type installation is (and what version), it &lt;em&gt;could&lt;/em&gt; be as simple as placing the following in your header template:&lt;/p&gt;

&lt;pre&gt;&amp;lt;MTUnless name=&amp;quot;system_template&amp;quot;&amp;gt;
&amp;lt; ?php include($_SERVER[&#039;DOCUMENT_ROOT&#039;] . &amp;quot;/php-caching.php&amp;quot;); ?&amp;gt;
&amp;lt;/MTUnless&amp;gt;&lt;/pre&gt;

&lt;p&gt;(Remove the space between the &lt;span class=&quot;code&quot;&gt;&amp;lt;&lt;/span&gt; and &lt;span class=&quot;code&quot;&gt;?php&lt;/span&gt;)&lt;/p&gt;

&lt;p&gt;Of course, you&#039;ll need to experiment with your specific setup to be sure if works for you. But you&#039;ll notice the Perl &lt;span class=&quot;caps&quot;&gt;CGI&#039;&lt;/span&gt;s are marked as system templates. Of course, &lt;span class=&quot;caps&quot;&gt;YMMV.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;As always, &lt;span class=&quot;caps&quot;&gt;TEST TEST TEST&lt;/span&gt;! Every installation is different and requires testing to be sure it all works in your unique situation -- MT is highly customizable so it&#039;s not a one size fits all proposition. The &lt;a href=&quot;http://www.dyeager.org/downloads/php-caching.php.txt&quot;&gt;script&lt;/a&gt; is only about 50 lines so should be easy to follow. Read the code before using it and be sure you understand what it&#039;s doing.&lt;/p&gt;

&lt;h2&gt;References&lt;/h2&gt;


&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/post/2009/01/php-xhtml-mime-type-caching&quot;&gt;&lt;span class=&quot;caps&quot;&gt;PHP XHTML &lt;/span&gt;and &lt;span class=&quot;caps&quot;&gt;MIME &lt;/span&gt;type&lt;/a&gt; Another article on this site with introduction to caching, and includes &lt;span class=&quot;caps&quot;&gt;XHTML &lt;/span&gt;mime type detection to properly serve &lt;span class=&quot;caps&quot;&gt;XHTML &lt;/span&gt;as &lt;span class=&quot;code&quot;&gt;application/xhtml+xml&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/post/2009/01/php-caching-part-2&quot;&gt;&lt;span class=&quot;caps&quot;&gt;PHP XHTML &lt;/span&gt;and &lt;span class=&quot;caps&quot;&gt;MIME &lt;/span&gt;type Part II&lt;/a&gt; Builds on the previous article with improvements.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.mnot.net/cache_docs/&quot;&gt;Caching Introduction&lt;/a&gt; The best caching information on the Internet. Period. Read it -- it&#039;s really that good.&lt;/li&gt;
&lt;/ul&gt;</description>
 <category domain="http://www.dyeager.org/category/tags/php">PHP</category>
 <category domain="http://www.dyeager.org/category/tags/programming">Programming</category>
 <pubDate>Sat, 01 Mar 2008 08:25:02 +0000</pubDate>
 <dc:creator>yeager</dc:creator>
 <guid isPermaLink="false">5 at http://www.dyeager.org</guid>
</item>
</channel>
</rss>

