Objectifs

Afin de renvoyer au client le contenu le plus adapté, on va examiner ses capacités. Deux objectifs pour cette première version :

  1. Examen des langues acceptées, afin de servir si possible dans la langue du client.
  2. Examen des contenus acceptés, ce qui peut permettre, par exemple, de renvoyer un contenu en XHTML ou en WML.

Code source

<?php
/*
(c)2004 David Sporn

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License version 2.1 as published by the Free Software Foundation.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

class UserAgentCapabilities
{
	var myLanguages ;
	var myAccepts ;
	
	function UserAgentCapabilities()
	{
		//Init languages capabilities
		$this->myLanguages = array() ;
		if (@$_SERVER["HTTP_ACCEPT_LANGUAGE"])
		{
			$langs_list = explode(',',@$_SERVER["HTTP_ACCEPT_LANGUAGE"]) ;// each language is separated by a ","
			foreach($langs_list as $lang_item)
			{
				$lang_parts = explode(';',$lang_item) ; //the pattern is "lang[;q=quality]"
				if (!preg_match('/^[a-z]{1,8}(?:[-][a-z]{1,8}){0,8}$/i', $lang_parts[0])) //skip invalid language names or too long names
				{
					continue ;
				}
				$quality_value = (float)1.0 ; //Default value
				if (1 < $lang_parts.length)
				{
					if (preg_match('/q=(0(?:.d{1,3})?)|(1(?:.[0]{1,3})?)/', $lang_parts[1],$values_array))
					{
						$quality_value = (float)$values_array[1] ;
					}
				}
				if ($this->myLanguages[$quality_value])
				{
					$this->myLanguages[$quality_value] += ','.$lang_parts[0] ;
				}
				else
				{
					$this->myLanguages[$quality_value] = $lang_parts[0] ;
				}
			}
			krsort($this->myLanguages) ;
		}
		
		//Init accept-content capabilities
		/*
		source : http://www.ietf.org/rfc/rfc1700.txt
		
		Recognized Content Types and Subtypes (exhaustive)
		--------------------------------------------------
		
		Type            Subtype         Description                 Reference
		----            -------         -----------                 ---------
		text            plain                                   [RFC1521,NSB]
		                xml                                               [?]
		                html                                              [?]
		
		application     octet-stream                            [RFC1521,NSB]
		                xml                                               [?]
		                xhtml+xml                                         [?]
		
		image           jpeg                                    [RFC1521,NSB]
		                gif                                     [RFC1521,NSB]
		                png                                               [?]
		

		Generic types (text/*, application/* and image/* are also recognized)
		
		Not recognised Content Types and Subtypes (non exhaustive)
		--------------------------------------------------
		
		Type            Subtype         Description                 Reference
		----            -------         -----------                 ---------
		text            richtext                                [RFC1521,NSB]
		                tab-separated-values                   [Paul Lindner]

		multipart       mixed                                   [RFC1521,NSB]
		                alternative                             [RFC1521,NSB]
		                digest                                  [RFC1521,NSB]
		                parallel                                [RFC1521,NSB]
		                appledouble                [MacMime,Patrik Faltstrom]
		                header-set                             [Dave Crocker]
		
		message         rfc822                                  [RFC1521,NSB]
		                partial                                 [RFC1521,NSB]
		                external-body                           [RFC1521,NSB]
		                news                        [RFC 1036, Henry Spencer]
		
		image           ief             Image Exchange Format       [RFC1314]
		                tiff            Tag Image File Format           [MTR]
		
		application     postscript                              [RFC1521,NSB]
		                oda                                     [RFC1521,NSB]
		                atomicmail                           [atomicmail,NSB]
		                andrew-inset                       [andrew-inset,NSB]
		                slate                           [slate,terry crowley]
		                wita              [Wang Info Transfer,Larry Campbell]
		                dec-dx            [Digital Doc Trans, Larry Campbell]
		                dca-rft        [IBM Doc Content Arch, Larry Campbell]
		                activemessage                          [Ehud Shapiro]
		                rtf                                    [Paul Lindner]
		                applefile                  [MacMime,Patrik Faltstrom]
		                mac-binhex40               [MacMime,Patrik Faltstrom]
		                news-message-id              [RFC1036, Henry Spencer]
		                news-transmission            [RFC1036, Henry Spencer]
		                wordperfect5.1                         [Paul Lindner]
		                pdf                                    [Paul Lindner]
		                zip                                    [Paul Lindner]
		                macwriteii                             [Paul Lindner]
		                msword                                 [Paul Lindner]
		                remote-printing                         [RFC1486,MTR]

		audio           basic                                   [RFC1521,NSB]
		
		video           mpeg                                    [RFC1521,NSB]
		                quicktime                              [Paul Lindner]

		*/
		$this->myAccepts = array() ;
		if (@$_SERVER["HTTP_ACCEPT"])
		{
			$pattern_any_type = '(\*/\*)' ;
			$pattern_text_type = '(text/(\*)|(html)|(xml)|(plain))' ;
			$pattern_image_type = '(image)/(\*)|(jpg)|(gif)|(jpeg)|(png)' ;
			$pattern_application_type = '(application)/(\*)|(octet-stream)|(xml)|(xhtml+xml)' ;

			$accepts_list = explode(',',@$_SERVER["HTTP_ACCEPT"]) ;// each content-type is separated by a ","
			foreach($accepts_list as $accept_item)
			{
				$accept_parts = explode(';',$accept_item) ; //the pattern is "lang[;q=quality]"
				if (!preg_match('/^'.$pattern_any_type.'|'.$pattern_text_type.'|'
					.$pattern_image_type.'|'.$pattern_application_type.'$/i'
					,$accept_parts[0])) //keep recognized type names only
				{
					continue ;
				}
				$quality_value = (float)1.0 ; //Default value
				if (1 < $accept_parts.length)
				{
					if (preg_match('/q=(0(?:.d{1,3})?)|(1(?:.[0]{1,3})?)/', $accept_parts[1],$values_array))
					{
						$quality_value = (float)$values_array[1] ;
					}
				}
				if ($this->myAccepts[$quality_value])
				{
					$this->myAccepts[$quality_value] += ','.$accept_parts[0] ;
				}
				else
				{
					$this->myAccepts[$quality_value] = $accept_parts[0] ;
				}
			}
			krsort($this->myAccepts) ;
		}
	}
	
	/**Get a comma separated list of languages.
	 * The returned list contains all the preferred languages of the user agent
	 * (with a quality value of 1).
	 */
	function getPreferredLanguages()
	{
		if ($this->myLanguages.length)
		{
			reset($this->myLanguages) ;
			return current($this->myLanguages)
		}
		else
		{
			return '' ;
		}
	}

	/**Get the best match among a list of specified language.
	 * The best match means that it has the best quality value here.
	 * If there are more that one answer, the first language in the
	 * provided list is returned.
	 * @param languages a list of language, the most preferred first.
	 * @return the language code that has the best match. Empty if the language does not exists.
	 */
	function getBestLanguage($languages)
	{
		$retour_language = '' ;
		$score_language = -1 ;
		if ($languages.length && $this->myLanguages.length)
		{
			//Looks for an exact match first
			foreach($languages as $candidate_language)
			{
				$language_index = 0 ;
				foreach($this->myLanguages.length as $languages_list)
				{
					if (0 <= $score_language && $language_index >= $score_language)
					{
						break ; //Not a better match
					}
					$languages_match = explode(',',$languages_list) ;
					foreach($languages_match as $languages_item)
					{
						if ($candidate_language == $languages_item)
						{
							$score_language = $language_index ;
							$retour_language = $candidate_language ;
							break 2; //WARNING//==> foreach($this->myLanguages.length as $languages_list)
						}
					}
					$language_index++ ;
				}
			}
			if (0 <= $score_language)
			{
				return $retour_language ; //Exact match
			}

			//Looks for an close match
			//for instance, 'fr-FR' is a close match for 'fr'
			foreach($languages as $candidate_language)
			{
				$language_index = 0 ;
				foreach($this->myLanguages.length as $languages_list)
				{
					if (0 <= $score_language && $language_index >= $score_language)
					{
						break ; //Not a better match
					}
					$languages_match = explode(',',$languages_list) ;
					foreach($languages_match as $languages_item)
					{
						if (stripos($candidate_language == $languages_item) == 0) //starts with $candidate_language
						{
							$score_language = $language_index ;
							$retour_language = $candidate_language ;
							break 2; //WARNING//==> foreach($this->myLanguages.length as $languages_list)
						}
					}
					$language_index++ ;
				}
			}
			if (0 <= $score_language)
			{
				return $retour_language ; //Exact match
			}
		}
		return '' ;
	}
	
	/**Get a comma separated list of languages.
	 * The returned list contains all the preferred languages of the user agent
	 * (with a quality value of 1).
	 */
	function getPreferredContentType()
	{
		if ($this->myAccepts.length)
		{
			reset($this->myAccepts) ;
			return current($this->myAccepts)
		}
		else
		{
			return '' ;
		}
	}
	
	/**Get the best match among a list of specified content type.
	 * The best match means that it has the best quality value here.
	 * If there are more that one answer, the first content type in the
	 * provided list is returned.
	 * @param contentTypes a list of content types, the most preferred first.
	 * @return the content type code that has the best match. Empty if the content type does not exists.
	 */
	function getBestContentType($contentTypes)
	{
		$retour_content_type = '' ;
		$score_content_type = -1 ;
		if ($contentTypes.length && $this->myAccepts.length)
		{
			//Looks for an exact match first
			foreach($contentTypes as $candidate_content_type)
			{
				$content_type_index = 0 ;
				foreach($this->myAccepts.length as $content_types_list)
				{
					if (0 <= $score_content_type && $content_type_index >= $score_content_type)
					{
						break ; //Not a better match
					}
					$content_types_match = explode(',',$content_types_list) ;
					foreach($content_types_match as $content_types_item)
					{
						if ($candidate_content_type == $content_types_item)
						{
							$score_content_type = $content_type_index ;
							$retour_content_type = $candidate_content_type ;
							break 2; //WARNING//==> foreach($this->myAccepts.length as $content_types_list)
						}
					}
					$content_type_index++ ;
				}
			}
			if (0 <= $score_content_type)
			{
				return $retour_content_type ; //Exact match
			}
		}
		return '' ;
	}
}
?>