Index: trunk/phpgwapi/templates/default/login_default.php
===================================================================
--- trunk/phpgwapi/templates/default/login_default.php	(revision 5281)
+++ trunk/phpgwapi/templates/default/login_default.php	(revision 6528)
@@ -177,4 +177,47 @@
 			else
 				$_POST['login'] = $_POST['user'];
+
+			/**
+			 * LOGIN OAUTH POR CURL
+			 */
+			$ch = curl_init();
+
+			$restConf = parse_ini_file( __DIR__ . '/../../../prototype/config/REST.ini', true );
+
+			$param  = 'grant_type=password';
+			$param .= '&client_id=' . $restConf['oauth']['client_id'];
+			$param .= '&client_secret=' . $restConf['oauth']['client_secret'];
+			$param .= '&username=' . $_POST['user'];
+			$param .= '&password=' . $_POST['passwd'];
+
+			// set URL and other appropriate options
+			curl_setopt($ch, CURLOPT_URL, $restConf['oauth']['url_token']);
+			curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: */*'));
+			curl_setopt($ch, CURLOPT_POST, TRUE);
+			curl_setopt($ch, CURLOPT_POSTFIELDS, $param);
+			curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);  //configura para nao imprimir a saida na tela
+			curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2);//Passe um nï¿œmero long como parï¿œmetro que contï¿œm o limite de tempo, em segundos, que vocï¿œ permite as funï¿œï¿œes CURL levar. 
+
+			// grab URL and pass it to the browser
+			$res = curl_exec($ch);
+			
+			// close cURL resource, and free up system resources
+			curl_close($ch);
+			$a = json_decode($res);
+	
+			if ( isset($a->access_token) ) {
+				$_SESSION['oauth']['access_token'] = $a->access_token;
+				$_SESSION['oauth']['expires_in'] = $a->expires_in;
+				$_SESSION['oauth']['token_type'] = $a->token_type;
+				$_SESSION['oauth']['scope'] = $a->scope;
+				$_SESSION['oauth']['refresh_token'] = $a->refresh_token;
+				
+			}
+			else {
+			}
+			/**
+			 * ####################
+			 */
+
 		}
 		if(getenv('REQUEST_METHOD') != 'POST' && $_SERVER['REQUEST_METHOD'] != 'POST' &&
@@ -598,5 +641,5 @@
 	if(isset($ultima_revisao)) $tmpl->set_var('ultima_rev','<br>' . $ultima_revisao);
 
-	// Adiciona código personalizado de outro template
+	// Adiciona cï¿œdigo personalizado de outro template
 	// que esteja utilizando o login_default.php
 	if(is_file('.'.$template_dir.'/login.inc.php')) {
Index: trunk/services/class.db.php
===================================================================
--- trunk/services/class.db.php	(revision 5842)
+++ trunk/services/class.db.php	(revision 6528)
@@ -263,4 +263,5 @@
 		$query = 'select'
 						. ' G.oid,'
+						. ' G.id_group,'
 						. ' G.title,'
 						. ' G.short_name';
@@ -288,4 +289,5 @@
 				$all_contacts[ $object[ 'oid' ] ]['title'] = $object['title'];
 				$all_contacts[ $object[ 'oid' ] ]['short_name'] = $object['short_name'];
+				$all_contacts[ $object[ 'oid' ] ]['id'] = $object[ 'id_group' ];
 		}
 		return array_values($all_contacts);
Index: trunk/.htaccess
===================================================================
--- trunk/.htaccess	(revision 6098)
+++ trunk/.htaccess	(revision 6528)
@@ -126,4 +126,6 @@
 RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK|OPTIONS|HEAD)$ [NC]
 RewriteRule ^.*$ - [F] 
+RewriteRule ^rest(.*)$ prototype/rest/$1 [QSA,L]
+
 
 #
Index: trunk/rest/setup/default_records.inc.php
===================================================================
--- trunk/rest/setup/default_records.inc.php	(revision 6528)
+++ trunk/rest/setup/default_records.inc.php	(revision 6528)
@@ -0,0 +1,43 @@
+<?php
+
+  /**************************************************************************\
+  * eGroupWare - Setup                                                       *
+  * http://www.egroupware.org                                                *
+  * --------------------------------------------                             *
+  *  This program is free software; you can redistribute it and/or modify it *
+  *  under the terms of the GNU General Public License as published by the   *
+  *  Free Software Foundation; either version 2 of the License, or (at your  *
+  *  option) any later version.                                              *
+  \**************************************************************************/
+
+$secret = substr(md5(rand().rand()), 0, 16); //Gera senha aleatoria
+
+$oProc->query("INSERT INTO rest_client (client_id, client_secret) values (1, '$secret');");
+$dados = array();
+$oProc->query("select * from phpgw_config where config_app='phpgwapi'");
+while ($oProc->next_record())
+{
+    $test = @unserialize($oProc->f('config_value'));
+    if($test)
+        $dados[$oProc->f('config_name')] = $test;
+    else
+        $dados[$oProc->f('config_name')] = $oProc->f('config_value');
+}
+
+/*
+Cria e configura o arquivo REST.ini 
+*/
+require_once dirname(__FILE__) . '/../../prototype/api/config.php';
+
+use prototype\api\Config as Config;
+
+$config = array();
+$config['baseUri'] = $dados['webserver_url'].'/rest';
+$config['oauth']['url_token'] = 'http://' . $_SERVER['HTTP_HOST'] . $dados['webserver_url'].'/rest/token';
+$config['oauth']['client_id'] = 1;
+$config['oauth']['client_secret'] = $secret;
+
+Config::writeIniFile($config , dirname(__FILE__) . '/../../prototype/config/REST.ini', true);
+    
+
+?>
Index: trunk/rest/setup/tables_current.inc.php
===================================================================
--- trunk/rest/setup/tables_current.inc.php	(revision 6528)
+++ trunk/rest/setup/tables_current.inc.php	(revision 6528)
@@ -0,0 +1,87 @@
+<?php
+  /**************************************************************************\
+  * eGroupWare                                                               *
+  * http://www.egroupware.org                                                *
+  * --------------------------------------------                             *
+  *  This program is free software; you can redistribute it and/or modify it *
+  *  under the terms of the GNU General Public License as published by the   *
+  *  Free Software Foundation; either version 2 of the License, or (at your  *
+  *  option) any later version.                                              *
+  \**************************************************************************/
+
+//Verifica se tem permissÃ£o na pasta de configuraÃ§Ã£o antes de comeÃ§ar a instalaÃ§Ã£o do modulo.
+require_once dirname(__FILE__) . '/../../prototype/api/config.php';
+use prototype\api\Config as Config;
+
+if( Config::writeIniFile(array() , dirname(__FILE__) . '/../../prototype/config/REST.ini', true) === false )
+{
+	echo "<div style='color:red;font-size:14px'>Permission failure when trying to write in folder \"/prototype/config/\". Grant write permission in folder and try again. </div>";
+	die(); //Mata o restante da execuÃ§Ã£o.
+}
+///
+
+	$phpgw_baseline = array(
+		
+		'rest_access_token' => array(
+			'fd' => array(
+				'id' => array('type' => 'auto','nullable' => False),
+				'oauth_token' => array('type' => 'varchar','precision' => '40', 'nullable' => false),
+				'client_id' => array('type' => 'varchar', 'precision' => '40','nullable' => false),
+				'user_id' => array('type' => 'int', 'precision' => '16','nullable' => false),
+				'expires' => array('type' => 'int', 'varchar' => '8','nullable' => false),
+				'scope' => array('type' => 'varchar', 'varchar' => '255','nullable' => True),
+				'refresh_token' => array('type' => 'varchar', 'precision' => '40','nullable' => false)
+			),
+			'pk' => array('id'),
+			'fk' => array(),
+			'ix' => array(),
+			'uc' => array()
+		),
+		
+		'rest_auth_code' => array(
+			'fd' => array(
+				'id' => array('type' => 'auto','nullable' => False),
+				'redirect_uri' => array('type' => 'varchar','precision' => '255', 'nullable' => true),
+				'client_id' => array('type' => 'varchar', 'precision' => '40','nullable' => false),
+				'user_id' => array('type' => 'int', 'precision' => '16','nullable' => false),
+				'expires' => array('type' => 'int', 'varchar' => '8','nullable' => false),
+				'scope' => array('type' => 'varchar', 'varchar' => '255','nullable' => True),
+				'refresh_token' => array('type' => 'varchar', 'precision' => '40','nullable' => false)
+			),
+			'pk' => array('id'),
+			'fk' => array(),
+			'ix' => array(),
+			'uc' => array()
+		),
+	
+		'rest_client' => array(
+			'fd' => array(
+				'id' => array('type' => 'auto','nullable' => False),
+				'redirect_uri' => array('type' => 'varchar','precision' => '255', 'nullable' => true),
+				'client_id' => array('type' => 'varchar', 'precision' => '40','nullable' => false),
+				'client_secret' => array('type' => 'varchar', 'precision' => '40','nullable' => false)
+			),
+			'pk' => array('id'),
+			'fk' => array(),
+			'ix' => array(),
+			'uc' => array()
+		),
+
+		'rest_refresh_token' => array(
+			'fd' => array(
+				'id' => array('type' => 'auto','nullable' => False),
+				'refresh_token' => array('type' => 'varchar','precision' => '40', 'nullable' => false),
+				'client_id' => array('type' => 'varchar', 'precision' => '40','nullable' => false),
+				'user_id' => array('type' => 'int', 'precision' => '16','nullable' => false),
+				'expires' => array('type' => 'int', 'varchar' => '8','nullable' => false),
+				'scope' => array('type' => 'varchar', 'varchar' => '255','nullable' => True),
+				'refresh_token' => array('type' => 'varchar', 'precision' => '40','nullable' => false)
+			),
+			'pk' => array('id'),
+			'fk' => array(),
+			'ix' => array(),
+			'uc' => array()
+		),
+
+	);
+?>
Index: trunk/rest/setup/tables_update.inc.php
===================================================================
--- trunk/rest/setup/tables_update.inc.php	(revision 6528)
+++ trunk/rest/setup/tables_update.inc.php	(revision 6528)
@@ -0,0 +1,14 @@
+<?php
+	/**************************************************************************\
+	* ExpressoLivre - Setup                                                     *
+	* http://www.expressolivre.org                                              *
+	* --------------------------------------------                             *
+	* This program is free software; you can redistribute it and/or modify it  *
+	* under the terms of the GNU General Public License as published by the    *
+	* Free Software Foundation; either version 2 of the License, or (at your   *
+	* option) any later version.                                               *
+	\**************************************************************************/
+
+	
+        
+?>
Index: trunk/rest/setup/setup.inc.php
===================================================================
--- trunk/rest/setup/setup.inc.php	(revision 6528)
+++ trunk/rest/setup/setup.inc.php	(revision 6528)
@@ -0,0 +1,34 @@
+<?php
+	/***********************************************************************************\
+	* Expresso REST API                										   
+	* 
+	* ---------------------------------------------------------------------------------*
+	*  This program is free software; you can redistribute it and/or modify it		   *
+	*  under the terms of the GNU General Public License as published by the		   *
+	*  Free Software Foundation; either version 2 of the License, or (at your		   *
+	*  option) any later version.													   *
+	\***********************************************************************************/
+
+	$setup_info['rest']['name']      	= 'rest';
+	$setup_info['rest']['title']     	= 'REST API' ;
+	/* Ao incrementar versão, não esquecer de declarar função do tables_update.inc.php*/
+	$setup_info['rest']['version']   	= '1.000';
+	$setup_info['rest']['app_order']	= 9;
+
+	$setup_info['rest']['tables'][]		=  'rest_access_token';
+	$setup_info['rest']['tables'][]		=  'rest_auth_code';
+	$setup_info['rest']['tables'][]		=  'rest_client';
+	$setup_info['rest']['tables'][]		=  'rest_refresh_token';
+
+
+	$setup_info['rest']['enable']		= 1;
+
+	$setup_info['rest']['author'] = 'autor';
+	$setup_info['rest']['maintainer'] = 'mantedor';
+
+	$setup_info['rest']['license']  = 'GPL';
+	$setup_info['rest']['description'] = 'Modulo de Suport a REST ExpressoLivre';
+
+    
+    
+?>
Index: trunk/admin/setup/phpgw_pt-br.lang
===================================================================
--- trunk/admin/setup/phpgw_pt-br.lang	(revision 5988)
+++ trunk/admin/setup/phpgw_pt-br.lang	(revision 6528)
@@ -565,2 +565,5 @@
 There are users with an quantity greater than of markers. Respect the minimum number indicated.	admin	pt-br	Existem usuários com uma quantidade superior de marcadores. Respeito o número mínimo indicado.
 Minimum number of labels allowed	admin	pt-br	Número mínimo de marcadores permitido
+Identifier of the recipient of a message	admin	pt-br	Identificador do destinatário de uma mensagem
+LDAP attribute used to replacement	admin	pt-br	Atributo LDAP utilizado para substituição
+None	admin	pt-br	Nenhum
Index: trunk/admin/inc/class.uiconfig.inc.php
===================================================================
--- trunk/admin/inc/class.uiconfig.inc.php	(revision 5988)
+++ trunk/admin/inc/class.uiconfig.inc.php	(revision 6528)
@@ -11,4 +11,6 @@
   \**************************************************************************/
 
+require_once('prototype/api/config.php');
+use prototype\api\Config as Config;
 
 	class uiconfig
@@ -337,4 +339,23 @@
 			/* Seta o valor padrão para a configuração de número máximo de marcadores */
 			$current_config['expressoMail_limit_labels'] = (isset($current_config['expressoMail_limit_labels']) && !!$current_config['expressoMail_limit_labels'] ) ? $current_config['expressoMail_limit_labels'] : 20;
+			//Pegar os todos os Atributos LDAP mapeados no arquivo user.ini
+			$map = Config::get('user', 'OpenLDAP.mapping');
+			$validate = false;	
+			$options = "<option value=''>".lang('None')."</option>";
+			foreach($map as $value){
+				$options .= "<option value='".$value."'";
+				if($current_config['expressoMail_ldap_identifier_recipient'] == $value){
+					$validate = true;
+					$options .= " selected='selected'";
+				}
+				$options .= ">". $value . "</option>";
+			}
+
+			if(!$validate){
+				// Limpa Atributo LDAP do banco de dados caso a atribuição não exista mais. 	
+				$db = '';
+				$db = $db ? $db : $GLOBALS['phpgw']->db;	// this is to allow setup to set the db
+				$db->query("DELETE FROM phpgw_config WHERE config_app = '".$appname."' AND config_name = 'expressoMail_ldap_identifier_recipient'");
+			}
 			/* Recupera o número mínimo de marcadores que pode ser definido */
 			$db = '';
@@ -420,4 +441,5 @@
 			$t->set_var('min_labels',$cont_labels);
 			
+			$t->set_var('rows_ldap_identifier',$options);
 			$t->pfp('out','body');
 
Index: trunk/setup/applications.php
===================================================================
--- trunk/setup/applications.php	(revision 3620)
+++ trunk/setup/applications.php	(revision 6528)
@@ -402,5 +402,5 @@
 		function allow_remove( $app )
 		{
-			$never_remove = array( 'phpgwapi', 'preferences' );
+			$never_remove = array( 'phpgwapi', 'preferences' , 'rest' );
 
 			return ( in_array( $app, $never_remove ) ) ? '&nbsp;' : '<input type="checkbox" name="remove[' . $app . ']">';
Index: trunk/prototype/plugins/jq-raty/test/runner.htm
===================================================================
--- trunk/prototype/plugins/jq-raty/test/runner.htm	(revision 6528)
+++ trunk/prototype/plugins/jq-raty/test/runner.htm	(revision 6528)
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+
+<html>
+	<head>
+		<title>jQuery Test Runner</title>
+
+		<link rel="stylesheet" type="text/css" href="lib/jasmine.css">
+		
+		<script type="text/javascript" charset="utf-8" src="../js/jquery.min.js"></script>
+		<script type="text/javascript" charset="utf-8" src="../js/jquery.raty.js"></script>
+
+		<script type="text/javascript" charset="utf-8" src="lib/jasmine.js"></script>
+		<script type="text/javascript" charset="utf-8" src="lib/jasmine-html.js"></script>
+		<script type="text/javascript" charset="utf-8" src="lib/jasmine-jquery.js"></script>
+	</head>
+	<body>
+		<script type="text/javascript">
+		describe('', function() {
+
+			beforeEach(function() {
+			    $('body').append('<div id="star"></div><div class="star">1</div><div class="star">2</div><div class="star">3</div>');
+			});
+
+			it ('[score / readOnly] should not set score when it is read-only', function() {
+				// given
+				var star = $('#star').raty({ readOnly: true });
+
+				// when
+				star.raty('score', 1);
+
+				// then
+				var imgs = star.children('img');
+
+				expect(imgs.eq(0)).toHaveAttr('src', 'img/star-off.png');
+				expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+				expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+				expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+				expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+			});
+
+		});
+
+			jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
+			jasmine.getEnv().execute();
+		</script>
+	</body>
+</html>
Index: trunk/prototype/plugins/jq-raty/test/spec.js
===================================================================
--- trunk/prototype/plugins/jq-raty/test/spec.js	(revision 6528)
+++ trunk/prototype/plugins/jq-raty/test/spec.js	(revision 6528)
@@ -0,0 +1,2993 @@
+describe('Using ID', function() {
+
+	beforeEach(function() {
+		$('body').append('<div id="star"></div>');
+	});
+
+	afterEach(function() {
+		$('#star').remove();
+	});
+
+	it ('should chainable', function() {
+		// given
+		var star		= $('#star'),
+			className	= 'my-class';
+
+		// when
+		star.raty().addClass(className);
+
+		// then
+	    expect(star).toHaveClass(className);
+	});
+
+	it ('should change the path', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ path: '../img' });
+
+		var imgs = star.children('img');
+
+		// then
+	    expect(imgs.eq(0)).toHaveAttr('src', '../img/star-off.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', '../img/star-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', '../img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', '../img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', '../img/star-off.png');
+	});
+
+	it ('should create the default stars', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty();
+
+		var imgs = star.children('img');
+
+		// then
+	    expect(imgs.eq(0)).toHaveAttr('title', 'bad');
+	    expect(imgs.eq(1)).toHaveAttr('title', 'poor');
+	    expect(imgs.eq(2)).toHaveAttr('title', 'regular');
+	    expect(imgs.eq(3)).toHaveAttr('title', 'good');
+	    expect(imgs.eq(4)).toHaveAttr('title', 'gorgeous');
+
+	    expect(imgs.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(star.children('input').val()).toEqual('');
+	});
+
+	it ('should create 7 stars', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ number: 7 });
+
+		var imgs = star.children('img');
+
+		// then
+	    expect(imgs.eq(0)).toHaveAttr('title', 'bad');
+	    expect(imgs.eq(1)).toHaveAttr('title', 'poor');
+	    expect(imgs.eq(2)).toHaveAttr('title', 'regular');
+	    expect(imgs.eq(3)).toHaveAttr('title', 'good');
+	    expect(imgs.eq(4)).toHaveAttr('title', 'gorgeous');
+	    expect(imgs.eq(5)).toHaveAttr('title', '6');
+	    expect(imgs.eq(6)).toHaveAttr('title', '7');
+
+	    expect(imgs.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(5)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(6)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should create the score field', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty();
+
+		var $score = star.children('input');
+
+		// then
+	    expect($score).toHaveAttr('type', 'hidden');
+	    expect($score).toHaveAttr('name', 'score');
+	    expect($score).toHaveValue(0);
+	});
+
+	it ('should create a custom score name field', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ scoreName: 'entity.score' });
+
+		var $score = star.children('input');
+
+		// then
+		expect($score).toHaveAttr('type', 'hidden');
+		expect($score).toHaveAttr('name', 'entity.score');
+		expect($score).toHaveValue(0);
+	});
+
+	it ('should start readOnly with not rated yet message', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ readOnly: true });
+
+		var imgs = star.children('img');
+
+		// then
+	    expect(imgs.eq(0)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs.eq(1)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs.eq(2)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs.eq(3)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs.eq(4)).toHaveAttr('title', 'not rated yet');
+	});
+
+	it ('should be started with 3 stars', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ score: 3 });
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should be started with 3 stars and readOnly', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ readOnly: true, score: 3});
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('title', 'regular');
+	    expect(imgs.eq(1)).toHaveAttr('title', 'regular');
+	    expect(imgs.eq(2)).toHaveAttr('title', 'regular');
+	    expect(imgs.eq(3)).toHaveAttr('title', 'regular');
+	    expect(imgs.eq(4)).toHaveAttr('title', 'regular');
+
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should be hovered 2 stars', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty();
+
+		var imgs = star.children('img');
+
+		imgs.eq(1).mouseover();
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should be clicked 2 stars', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty();
+
+		var imgs = star.children('img');
+
+		imgs.eq(1).mouseover().click().mouseleave();
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(star.children('input')).toHaveValue(2);
+	});
+
+	it ('should not be hovered with readOnly', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ readOnly: true });
+
+		var imgs = star.children('img');
+
+		imgs.eq(1).mouseover();
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should not be clicked with readOnly', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ readOnly: true });
+
+		var imgs = star.children('img');
+
+		imgs.eq(1).mouseover().click().mouseleave();
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(star.children('input').val()).toEqual('');
+	});
+
+	it ('should execute click callback', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({
+			click: function(score, evt) {
+				$(this).attr('title', score);
+			}
+		});
+
+		star.children('img').eq(3).mouseover().click().mouseleave();
+
+		// then
+		expect(star).toHaveAttr('title', 4);
+	});
+
+	it ('should have this as self element', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({
+			click: function(score, evt) {
+				$(this).attr('title', score);
+			}
+		});
+
+		star.children('img').eq(3).mouseover().click().mouseleave();
+
+		// then
+		expect(star).toHaveAttr('title', 4);
+	});
+
+	it ('should create default cancel button', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ cancel: true });
+
+		var cancel = star.children('img:first');
+		
+		// then
+		expect(cancel).toHaveClass('raty-cancel');
+		expect(cancel).toHaveAttr('title', 'cancel this rating!');
+		expect(cancel).toHaveAttr('alt', 'x');
+		expect(cancel).toHaveAttr('src', 'img/cancel-off.png');
+	});
+
+	it ('should change cancel off button', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ cancel: true, cancelOff: 'new-cancel-off.png' });
+
+		var cancel = star.children('img:first');
+
+		// then
+		expect(cancel).toHaveClass('raty-cancel');
+		expect(cancel).toHaveAttr('title', 'cancel this rating!');
+		expect(cancel).toHaveAttr('alt', 'x');
+		expect(cancel).toHaveAttr('src', 'img/new-cancel-off.png');
+	});
+
+	it ('should change cancel on button', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ cancel: true, cancelOn: 'new-cancel-on.png' });
+
+		var cancel = star.children('img:first').mouseenter();
+
+		// then
+		expect(cancel).toHaveClass('raty-cancel');
+		expect(cancel).toHaveAttr('title', 'cancel this rating!');
+		expect(cancel).toHaveAttr('alt', 'x');
+		expect(cancel).toHaveAttr('src', 'img/new-cancel-on.png');
+	});
+
+	it ('should change cancel hint', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ cancel: true, cancelHint: 'my-hint' });
+
+		var cancel = star.children('img:first').mouseenter();
+
+		// then
+		expect(cancel).toHaveAttr('title', 'my-hint');
+	});
+
+	it ('should turn off the stars on mouseover on cancel button', function() {
+		// given
+		var star	= $('#star').raty({ score: 3, cancel: true }),
+			imgs	= star.children('img');
+		
+		// when
+		star.children('img:first').mouseenter();
+
+		// then
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(5)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should display noRatedMsg when readOnly', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ readOnly: true });
+
+		// then
+		var imgs = star.children('img');
+
+		expect(imgs.eq(0)).toHaveAttr('title', 'not rated yet');
+		expect(imgs.eq(1)).toHaveAttr('title', 'not rated yet');
+		expect(imgs.eq(2)).toHaveAttr('title', 'not rated yet');
+		expect(imgs.eq(3)).toHaveAttr('title', 'not rated yet');
+		expect(imgs.eq(4)).toHaveAttr('title', 'not rated yet');
+	});
+
+	it ('should cancel the rating', function() {
+		// given
+		var star	= $('#star').raty({ score: 5, cancel: true }),
+			imgs	= star.children('img');
+
+		// when
+		star.children('img:first').mouseover().click().mouseleave();
+
+		// then
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(5)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should create right cancel button', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ cancel: true, cancelPlace: 'right' });
+
+		var cancel = star.children('img:last');
+
+		// then
+		expect(cancel).toHaveClass('raty-cancel');
+		expect(cancel).toHaveAttr('title', 'cancel this rating!');
+		expect(cancel).toHaveAttr('alt', 'x');
+		expect(cancel).toHaveAttr('src', 'img/cancel-off.png');
+	});
+
+	it ('should set hint for cancel button', function() {
+		// given
+		var star	= $('#star'),
+			hint	= 'remove my rating!';
+
+		// when
+		star.raty({ cancel: true, cancelHint: hint });
+
+		var cancel = star.children('img:first');
+
+		// then
+		expect(cancel).toHaveClass('raty-cancel');
+		expect(cancel).toHaveAttr('title', hint);
+		expect(cancel).toHaveAttr('alt', 'x');
+		expect(cancel).toHaveAttr('src', 'img/cancel-off.png');
+	});
+
+	it ('should execute cancel click callback', function() {
+		// given
+		var star = $('#star').raty({ cancel: true, click: function(score, evt) { $(this).attr('title', (score === null)); } });
+
+		// when
+		star.children('img:first').mouseover().click().mouseleave();
+
+		// then
+		expect(star).toHaveAttr('title', 'true');
+		expect(star.children('input').val()).toEqual('');
+	});
+
+	it ('should round down on max limit down with no half and halfShow', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ half: false, halfShow: false, score: 1.5 });
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should round up on min limit up with no half and halfShow', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ half: false, halfShow: false, score: 1.6 });
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should round down on max limit down with half and halfShow', function() {
+		// given
+		var star = $('#star');
+		
+		// when
+		star.raty({ half: true, halfShow: true, score: 1.25 });
+		
+		var imgs = star.children('img');
+		
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should show half on min limit half with half and halfShow', function() {
+		// given
+		var star = $('#star');
+		
+		// when
+		star.raty({ half: true, halfShow: true, score: 1.26 });
+		
+		var imgs = star.children('img');
+		
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-half.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should show half on max limit half with half and halfShow', function() {
+		// given
+		var star = $('#star');
+		
+		// when
+		star.raty({ half: true, halfShow: true, score: 1.75 });
+		
+		var imgs = star.children('img');
+		
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-half.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should show round up on min limit up with half and halfShow', function() {
+		// given
+		var star = $('#star');
+		
+		// when
+		star.raty({ half: true, halfShow: true, score: 1.76 });
+		
+		var imgs = star.children('img');
+		
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should round down on max limit down with half and no halfShow', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ half: true, halfShow: false, score: 1.25 });
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should round down on min limit half with half and no halfShow', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ half: true, halfShow: false, score: 1.26 });
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should round up on max limit half with half and no halfShow', function() {
+		// given
+		var star = $('#star');
+		
+		// when
+		star.raty({ half: true, halfShow: false, score: 1.75 });
+		
+		var imgs = star.children('img');
+		
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should round up on min limit up with half and no halfShow', function() {
+		// given
+		var star = $('#star');
+		
+		// when
+		star.raty({ half: true, halfShow: false, score: 1.76 });
+		
+		var imgs = star.children('img');
+		
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should round down on max limit down with no half and halfShow', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ half: false, halfShow: true, score: 1.25 });
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should show half on min limit half with no half and halfShow', function() {
+		// given
+		var star = $('#star');
+		
+		// when
+		star.raty({ half: false, halfShow: true, score: 1.26 });
+		
+		var imgs = star.children('img');
+		
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-half.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should show half on max limit half with no half and halfShow', function() {
+		// given
+		var star = $('#star');
+		
+		// when
+		star.raty({ half: false, halfShow: true, score: 1.75 });
+		
+		var imgs = star.children('img');
+		
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-half.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should show round up on min limit up with no half and halfShow', function() {
+		// given
+		var star = $('#star');
+		
+		// when
+		star.raty({ half: false, halfShow: true, score: 1.76 });
+		
+		var imgs = star.children('img');
+		
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should set custom hints', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ hints: ['a', '', null, 'd', '5'] });
+
+		var imgs = star.children('img');
+
+		// then
+	    expect(imgs.eq(0)).toHaveAttr('title', 'a');
+	    expect(imgs.eq(1)).toHaveAttr('title', '');
+	    expect(imgs.eq(2)).toHaveAttr('title', 3);
+	    expect(imgs.eq(3)).toHaveAttr('title', 'd');
+	    expect(imgs.eq(4)).toHaveAttr('title', '5');
+	});
+
+	it ('should change the star off', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ starOff: 'medal-off.png' });
+
+		var imgs = star.children('img');
+
+		// then
+	    expect(imgs.eq(0)).toHaveAttr('src', 'img/medal-off.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/medal-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/medal-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/medal-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/medal-off.png');
+	});
+
+	it ('should change the star on', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ starOn: 'medal-on.png' });
+
+		var imgs = star.children('img');
+
+		imgs.eq(3).mouseover();
+
+		// then
+	    expect(imgs.eq(0)).toHaveAttr('src', 'img/medal-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/medal-on.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/medal-on.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/medal-on.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should use range icons', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({
+			iconRange: [
+	            { range: 2, on: 'face-a.png', off: 'face-a-off.png' },
+	            { range: 3, on: 'face-b.png', off: 'face-b-off.png' },
+	            { range: 4, on: 'face-c.png', off: 'face-c-off.png' },
+	            { range: 5, on: 'face-d.png', off: 'face-d-off.png' }
+			]
+		});
+
+		var imgs = star.children('img');
+
+		// then
+	    expect(imgs.eq(0)).toHaveAttr('src', 'img/face-a-off.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/face-a-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/face-b-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/face-c-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/face-d-off.png');
+	});
+
+	it ('should use range icons and apply not found icons default', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({
+			iconRange: [
+	            { range: 2, off: 'face-a-off.png' },
+	            { range: 3, off: 'face-b-off.png' },
+	            { range: 4, on: 'face-c.png', off: 'face-c-off.png' },
+	            { range: 5 }
+			]
+		});
+
+		var imgs = star.children('img');
+
+		imgs.eq(3).mouseover();
+
+		// then
+	    expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/face-c.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should hover range icons', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({
+			iconRange: [
+				{ range: 2, on: 'face-a.png', off: 'face-a-off.png' },
+				{ range: 3, on: 'face-b.png', off: 'face-b-off.png' },
+				{ range: 4, on: 'face-c.png', off: 'face-c-off.png' },
+				{ range: 5, on: 'face-d.png', off: 'face-d-off.png' }
+			],
+            starOff: 'face-off.png'
+		});
+
+		var imgs = star.children('img');
+
+		imgs.eq(4).mouseover();
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/face-a.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/face-a.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/face-b.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/face-c.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/face-d.png');
+	});
+
+	it ('should set off range icons on mouseleave', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({
+			iconRange: [
+				{ range: 2, on: 'face-a.png', off: 'face-a-off.png' },
+				{ range: 3, on: 'face-b.png', off: 'face-b-off.png' },
+				{ range: 4, on: 'face-c.png', off: 'face-c-off.png' },
+				{ range: 5, on: 'face-d.png', off: 'face-d-off.png' },
+			],
+            starOff: 'face-off.png'
+		});
+
+		var imgs = star.children('img');
+
+		imgs.eq(4).mouseover();
+		star.mouseleave();
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/face-a-off.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/face-a-off.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/face-b-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/face-c-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/face-d-off.png');
+	});
+
+	it ('should keep the score value range icons on mouseleave', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({
+			iconRange: [
+				{ range: 2, on: 'face-a.png', off: 'face-a-off.png' },
+				{ range: 3, on: 'face-b.png', off: 'face-b-off.png' },
+				{ range: 4, on: 'face-c.png', off: 'face-c-off.png' },
+				{ range: 5, on: 'face-d.png', off: 'face-d-off.png' }
+			],
+            starOff: 'face-off.png',
+            score: 1
+		});
+
+		var imgs = star.children('img');
+
+		imgs.eq(4).mouseover();
+		star.mouseleave();
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/face-a.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/face-a-off.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/face-b-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/face-c-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/face-d-off.png');
+	});
+
+	it ('should calculate the right icon size', function() {
+		// given
+		var star	= $('#star'),
+			size	= 24,
+			number	= 5;
+
+		// when
+		star.raty({ size: size });
+
+		// then
+		expect(star.width()).toEqual(number * size + number * 4);
+	});
+
+	it ('should set custom width', function() {
+		// given
+		var star	= $('#star'),
+			size	= 24;
+
+		// when
+		star.raty({ size: size, width: 200 });
+
+		// then
+		expect(star.width()).toEqual(200);
+	});
+
+	it ('should set custom width when readOnly', function() {
+		// given
+		var star	= $('#star'),
+			size	= 24;
+
+		// when
+		star.raty({ readOnly: true, size: size, width: 200 });
+
+		// then
+		expect(star.width()).toEqual(200);
+	});
+
+	it ('should calculate the right icon size with cancel button', function() {
+		// given
+		var star	= $('#star'),
+			size	= 24,
+			number	= 5;
+
+		// when
+		star.raty({ cancel: true, size: size });
+
+		// then
+		expect(star.width()).toEqual(number * size + number * 4 + (size + 4));
+	});
+
+	it ('should set a target on div with mouseover', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint = $('#hint'),
+			star = $('#star').raty({ target: '#hint' });
+
+		// when
+		star.children('img').eq(3).mouseover();
+
+		// then
+		expect(hint).toHaveHtml('good');
+
+		hint.remove();
+	});
+
+	it ('should set a target on div with mouseover but take off when mouseleave', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint = $('#hint'),
+			star = $('#star').raty({ target: '#hint' });
+
+		// when
+		star.children('img').eq(3).mouseover().mouseleave();
+
+		// then
+		expect(hint).toBeEmpty();
+
+		hint.remove();
+	});
+
+	it ('should change the cancel hint using target', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint = $('#hint'),
+			star = $('#star').raty({
+			cancel:     true,
+			cancelHint: 'none',
+			target:     '#hint',
+		});
+
+		// when
+		star.children('img:first').mouseenter();
+
+		// then
+		expect(hint).toHaveHtml('none');
+
+		hint.remove();
+	});
+
+	it ('should set hint on a combobox element', function() {
+		$('body').append('<select id="hint"><option value="">--</option><option value="good">good</option></select>');
+
+		// given
+		var hint = $('#hint'),
+			star = $('#star').raty({ target: '#hint' });
+
+		// when
+		star.children('img').eq(3).mouseover();
+
+		// then
+		expect(hint.val()).toEqual('good');
+
+		hint.remove();
+	});
+
+	it ('should set hint on a text element', function() {
+		$('body').append('<input id="hint" type="text" />');
+
+		// given
+		var hint = $('#hint'),
+			star = $('#star').raty({ target: '#hint' });
+
+		// when
+		star.children('img').eq(3).mouseover();
+
+		// then
+		expect(hint.val()).toEqual('good');
+
+		hint.remove();
+	});
+
+	it ('should set hint on a textarea element', function() {
+		$('body').append('<textarea id="hint"></textarea>');
+
+		// given
+		var hint = $('#hint'),
+			star = $('#star').raty({ target: '#hint' });
+
+		// when
+		star.children('img').eq(3).mouseover();
+
+		// then
+		expect(hint.val()).toEqual('good');
+
+		hint.remove();
+	});
+
+	it ('should set number hint on a combobox element', function() {
+		$('body').append('<select id="hint"><option value="">--</option><option value="4">4</option></select>');
+
+		// given
+		var hint = $('#hint'),
+			star = $('#star').raty({ target: '#hint', targetType: 'number' });
+
+		// when
+		star.children('img').eq(3).mouseover();
+
+		// then
+		expect(hint.val()).toEqual('4');
+
+		hint.remove();
+	});
+
+	it ('should set number hint and keep it on mouseleave', function() {
+		$('body').append('<select id="hint"><option value="">--</option><option value="4">4</option></select>');
+
+		// given
+		var hint = $('#hint'),
+			star = $('#star').raty({ target: '#hint', targetType: 'number', targetKeep: true });
+
+		// when
+		star.children('img').eq(3).mouseover().click().mouseleave();
+
+		// then
+		expect(hint.val()).toEqual('4');
+
+		hint.remove();
+	});
+
+	it ('should create stars without space', function() {
+		// given
+		var star	= $('#star');
+			size	= 16,
+			number	= 5;
+
+		// when
+		star.raty({ space: false });
+
+		// then
+		expect(star.width()).toEqual(number * size + number * (4 * 0));
+	});
+
+	it ('should create stars with cancel without space', function() {
+		// given
+		var star	= $('#star');
+			size	= 16,
+			number	= 5;
+
+		// when
+		star.raty({ cancel: true, space: false });
+
+		// then
+		expect(star.width()).toEqual(number * size + number * (4 * 0) + 16 + (4 * 0));
+	});
+
+	it ('should not set target with targetText when has started score and targetKeep is true with mouseout', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star').raty({ score: 3, target: '#hint', targetKeep: true, targetText: 'my-hint-message' });
+
+		// when
+		star.children('img').eq(3).mouseover().mouseleave();
+
+		// then
+		expect(hint).toHaveHtml('regular');
+
+		hint.remove();
+	});
+
+	it ('should not set target with targetText when has clicked score and targetKeep is true with mouseout', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star').raty({ target: '#hint', targetKeep: true, targetText: 'my-hint-message' });
+
+		// when
+		star.children('img').eq(3).mouseover().click().mouseleave();
+
+		// then
+		expect(hint).toHaveHtml('good');
+
+		hint.remove();
+	});
+
+	it ('should set target with targetText when has scoreed score and targetKeep is false with mouseout', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var message	= 'my-hint-message',
+			hint	= $('#hint'),
+			star	= $('#star').raty({ score: 3, target: '#hint', targetKeep: false, targetText: message });
+
+		// when
+		star.children('img').eq(3).mouseover().mouseleave();
+
+		// then
+		expect(hint).toHaveHtml(message);
+
+		hint.remove();
+	});
+
+	it ('should set target with targetText when has clicked score and targetKeep false with mouseout', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var message	= 'my-hint-message',
+			hint	= $('#hint'),
+			star	= $('#star').raty({ target: '#hint', targetKeep: false, targetText: message });
+
+		// when
+		star.children('img').eq(3).mouseover().click().mouseleave();
+
+		// then
+		expect(hint).toHaveHtml(message);
+
+		hint.remove();
+	});
+
+	it ('should set target with targetText when has started score and targetKeep is false without mouseout', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var message	= 'my-hint-message',
+			hint	= $('#hint'),
+			star	= $('#star');
+
+		// when
+		star.raty({ score: 3, target: '#hint', targetKeep: false, targetText: message });
+
+		// then
+		expect(hint).toHaveHtml(message);
+
+		hint.remove();
+	});
+
+	it ('should set score as readonly when start with readOnly', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ readOnly: true });
+
+		// then
+		expect(star.children('input')).toHaveAttr('readonly', 'readonly');
+	});
+
+	it ('should be down with round option', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ halfShow: true, score: 1.9, round: { down: .9, full: .98, up: .99 } });
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should be half with round option', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ halfShow: true, score: 1.91, round: { down: .9, full: .98, up: .99 } });
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-half.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should be up with round option', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ halfShow: true, score: 1.99, round: { down: .9, full: .98, up: .99 } });
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should be full with round option and halfShow off', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ halfShow: false, score: 1.98, round: { down: .9, full: .98, up: .99 } });
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should be down with round option and others options default', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ halfShow: true, score: 1.9, round: { down: .9 } });
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should be half with round option and others options default', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ halfShow: true, score: 1.26, round: { } });
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-half.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should be up with round option and others options default', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ halfShow: true, score: 1.99, round: { up: .99 } });
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should be full with round option and halfShow off and others options default', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ halfShow: false, score: 1.98, round: { full: .98 } });
+
+		var imgs = star.children('img');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should set hint target with start option and targetKeep true', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star');
+
+		// when
+		star.raty({ target: '#hint', precision: true, score: 3, targetKeep: true });
+
+		// then
+		expect(hint).toHaveHtml('regular');
+
+		hint.remove();
+	});
+
+	it ('should set precision number target with just one fractional number', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star');
+
+		// when
+		star.raty({ target: '#hint', precision: true, targetType: 'number', score: 1.2333, targetKeep: true });
+
+		// then
+		expect(hint).toHaveHtml('1.2');
+
+		hint.remove();
+	});
+
+	it ('should set precision number target with start options and targetKeep true', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star');
+
+		// when
+		star.raty({ target: '#hint', precision: true, targetType: 'number', score: 1.2, targetKeep: true });
+
+		// then
+		expect(hint).toHaveHtml('1.2');
+
+		hint.remove();
+	});
+
+	it ('should set target with format on mouseover', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star').raty({ target: '#hint', targetFormat: 'score: {score}', precision: true });
+
+		// when
+		star.children('img:first').mouseover();
+
+		// then
+		expect(hint).toHaveHtml('score: bad');
+
+		hint.remove();
+	});
+
+	it ('should set score with precision and half disabled', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star').raty({ target: '#hint', targetKeep: true, precision: true });
+
+		// when
+		star.children('img:first').mouseover().click().mouseleave();
+
+		// then
+		expect(hint).toHaveHtml('bad');
+
+		hint.remove();
+	});
+
+	it ('should set and keep target with format on click and mouseleave', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star').raty({ target: '#hint', targetFormat: 'score: {score}', targetKeep: true });
+
+		// when
+		star.children('img:first').mouseover().click().mouseleave();
+
+		// then
+		expect(hint).toHaveHtml('score: bad');
+
+		hint.remove();
+	});
+
+	it ('should set target with format and precision', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star');
+
+		// when
+		star.raty({ target: '#hint', targetFormat: 'score: {score}', targetKeep: true, targetType: 'number', precision: true, score: 1.2 });
+
+		// then
+		expect(hint).toHaveHtml('score: 1.2');
+
+		hint.remove();
+	});
+
+	it ('should set target with none value', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star');
+
+		// when
+		star.raty({ target: '#hint', targetFormat: 'score: {score}', targetKeep: true, targetType: 'number', targetText: 'none' });
+
+		// then
+		expect(hint).toHaveHtml('score: none');
+
+		hint.remove();
+	});
+
+	it ('should not to use format template on cancel mouseover', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star').raty({ cancel: true, target: '#hint', targetFormat: 'score: {score}', targetKeep: true });
+
+		// when
+		star.children('img:first').mouseenter();
+
+		// then
+		expect(hint).toHaveHtml('cancel this rating!');
+
+		hint.remove();
+	});
+
+	it ('should show single icon selection on mouseover', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ single: true });
+
+		var imgs = star.children('img');
+
+		imgs.eq(2).mouseover();
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should show single icon selection on click', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ single: true });
+
+		var imgs = star.children('img');
+
+		imgs.eq(2).mouseover().click().mouseleave();
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should show single icon selection with iconRange enabled on mouseover', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({
+			iconRange: [
+				{ range: 2, on: 'face-a.png', off: 'face-a-off.png' },
+				{ range: 3, on: 'face-b.png', off: 'face-b-off.png' },
+				{ range: 4, on: 'face-c.png', off: 'face-c-off.png' },
+				{ range: 5, on: 'face-d.png', off: 'face-d-off.png' }
+			],
+			single: true
+		});
+
+		var imgs = star.children('img');
+
+		imgs.eq(3).mouseover();
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/face-a-off.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/face-a-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/face-b-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/face-c.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/face-d-off.png');
+	});
+
+	it ('should show single icon selection with iconRange enabled on click', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({
+			iconRange: [
+				{ range: 2, on: 'face-a.png', off: 'face-a-off.png' },
+				{ range: 3, on: 'face-b.png', off: 'face-b-off.png' },
+				{ range: 4, on: 'face-c.png', off: 'face-c-off.png' },
+				{ range: 5, on: 'face-d.png', off: 'face-d-off.png' }
+			],
+			single: true
+		});
+
+		var imgs = star.children('img');
+
+		imgs.eq(3).mouseover().click().mouseleave();
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/face-a-off.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/face-a-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/face-b-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/face-c.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/face-d-off.png');
+	});
+
+	it ('score should get it as int', function() {
+		// given
+		var star = $('#star').raty({ score: 1 });
+
+		// when
+		var score = star.raty('score');
+
+		// then
+		expect(score).toEqual(1);
+	});
+
+	it ('score should get it as float', function() {
+		// given
+		var star = $('#star').raty({ score: 1.5 });
+
+		// when
+		var score = star.raty('score');
+
+		// then
+		expect(score).toEqual(1.5);
+	});
+
+	it ('[score] should not have score when it is setted to 0', function() {
+		// given
+		var star = $('#star').raty({ score: 0 });
+
+		// when
+		var score = star.raty('score');
+
+		// then
+		expect(score).toEqual(null);
+	});
+
+	it ('[score] should have 20 as max', function() {
+		// given
+		var star = $('#star').raty({ number: 30, score: 30 });
+
+		// when
+		var score = star.raty('score');
+
+		// then
+		expect(score).toEqual(20);
+	});
+
+	it ('[score] should have 0 as min', function() {
+		// given
+		var star = $('#star').raty({ score: -30 });
+
+		// when
+		var score = star.raty('score');
+
+		// then
+		expect(score).toEqual(null);
+	});
+
+	it ('[set] should be chainable', function() {
+		// given
+		var star	= $('#star').raty(),
+			clazz	= 'some-class';
+
+		// when
+		var clone = star.raty('set', { readOnly: true }).addClass(clazz);
+
+		// then
+		expect(clone).toHaveClass(clazz);
+	});
+
+	it ('[set] should change just the passed parameters', function() {
+		// given
+		var star = $('#star').raty({ number: 6 });
+
+		// when
+		star.raty('set', { scoreName: 'changed' });
+
+		// then
+		var clone = $('#star');
+
+		expect(clone.children('input')).toHaveAttr('name', 'changed');
+		expect(clone.children('img').length).toEqual(6);
+	});
+
+	it ('[reload] should be chainable', function() {
+		// given
+		var star	= $('#star').raty(),
+			clazz	= 'some-class';
+
+		// when
+		var clone = star.raty('reload').addClass(clazz);
+
+		// then
+		expect(clone).toHaveClass(clazz);
+	});
+
+	it ('[reload] should reload with same configuration', function() {
+		// given
+		var star = $('#star').raty({ number: 6 });
+
+		// when
+		star.raty('reload');
+
+		// then
+		expect($('#star').children('img').length).toEqual(6);
+	});
+
+	it ('[mouseover] should display the value', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star').raty({
+				mouseover: function(score, evt) {
+					hint.html(score);
+				}
+			});
+
+		// when
+		star.children('img:eq(1)').mouseover();
+
+		// then
+		expect(hint).toHaveHtml(2);
+
+		hint.remove();
+	});
+
+	it ('[mouseover] should display the cancel value', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star').raty({
+				cancel		: true,
+				mouseover	: function(score, evt) {
+					hint.html(score === null ? 'null' : score);
+				}
+			});
+
+		// when
+		star.children('.raty-cancel').mouseover();
+
+		// then
+		expect(hint).toHaveHtml('null');
+
+		hint.remove();
+	});
+
+	it ('[mouseover] should pass the original value on cancel mouseout', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star').raty({
+				cancel		: true,
+				score		: 1,
+				mouseover	: function(score, evt) {
+					hint.html(score === null ? 'null' : score);
+				}
+			});
+
+		// when
+		star.children('.raty-cancel').mouseenter().mouseleave();
+
+		// then
+		expect(hint).toHaveHtml(1);
+
+		hint.remove();
+	});
+
+	it ('[mouseover] should pass the original value on star mouseout', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star').raty({
+				score		: 1,
+				cancel		: true,
+				mouseover	: function(score, evt) {
+					hint.html(score);
+				}
+			});
+
+		// when
+		star.children('img:eq(4)').mouseover().mouseleave();
+
+		// then
+		expect(hint).toHaveHtml(1);
+
+		hint.remove();
+	});
+
+	it ('[score] should accept int string', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ number: 10, score: '10' });
+
+		// then
+		expect(star.children('input')).toHaveValue(10);
+	});
+
+	it ('[score] should accept float string', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ number: 11, score: '10.5' });
+
+		// then
+		expect(star.children('input')).toHaveValue(10.5);
+	});
+
+	it ('[number] should accept string', function() {
+		// given
+		var star = $('#star');
+
+		// when
+		star.raty({ number: '10' });
+
+		// then
+		expect(star.children('img').length).toEqual(10);
+	});
+
+	it ('[mouseover] handle null on cancel over', function() {
+		var isOver = true;
+
+		// given
+		var star = $('#star').raty({
+			cancel		: true,
+			mouseover	: function(score, evt) {
+				if (isOver) {
+					// then
+					expect(score === null).toBeTruthy();
+
+					isOver = false;
+				}
+			}
+		});
+
+		// when
+		star.children('.raty-cancel').mouseover();
+	});
+
+	it ('[mouseover] handle undefined on mouse out of cancel', function() {
+		var nextOut = false;
+
+		// given
+		var star = $('#star').raty({
+			cancel		: true,
+			mouseover	: function(score, evt) {
+				if (nextOut) {
+					// then
+					expect(score === undefined).toBeTruthy();
+				}
+
+				nextOut = true;
+			}
+		});
+
+		// when
+		star.children('.raty-cancel').mouseleave();
+	});
+
+	it ('[mouseover] handle undefined on star out', function() {
+		var nextOut = false;
+
+		// given
+		var star = $('#star').raty({
+			mouseover	: function(score, evt) {
+				if (nextOut) {
+					// then
+					expect(score === undefined).toBeTruthy();
+				}
+
+				nextOut = true;
+			}
+		});
+
+		// when
+		star.children('img:first').mouseleave();
+	});
+
+	it ('[mouseover] handle 1 on star out', function() {
+		var nextOut = false;
+
+		// given
+		var star = $('#star').raty({
+			score		: 1,
+			mouseover	: function(score, evt) {
+				if (nextOut) {
+					// then
+					expect(score).toEqual(1);
+				}
+
+				nextOut = true;
+			}
+		});
+
+		// when
+		star.children('img:first').mouseleave();
+	});
+
+	it ('[mouseover] handle 1 on cancel out', function() {
+		var nextOut = false;
+
+		// given
+		var star = $('#star').raty({
+			cancel		: true,
+			score		: 1,
+			mouseover	: function(score, evt) {
+				if (nextOut) {
+					// then
+					expect(parseInt(score)).toEqual(1);
+				}
+
+				nextOut = true;
+			}
+		});
+
+		// when
+		star.children('.raty-cancel').mouseleave();
+	});
+
+});
+
+describe('Using class', function() {
+
+	beforeEach(function() {
+		$('body').append('<div class="star"></div><div class="star"></div><div class="star"></div>');
+	});
+
+	afterEach(function() {
+		$('.star').remove();
+	});
+
+	it ('should create the default stars', function() {
+		// given
+		var stars = $('.star');
+
+		// when
+		stars.raty();
+
+		// then
+		var imgs1 = stars.eq(0).children('img'),
+			imgs2 = stars.eq(1).children('img'),
+			imgs3 = stars.eq(2).children('img');
+
+	    expect(imgs1.eq(0)).toHaveAttr('title', 'bad');
+	    expect(imgs1.eq(1)).toHaveAttr('title', 'poor');
+	    expect(imgs1.eq(2)).toHaveAttr('title', 'regular');
+	    expect(imgs1.eq(3)).toHaveAttr('title', 'good');
+	    expect(imgs1.eq(4)).toHaveAttr('title', 'gorgeous');
+
+	    expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(imgs2.eq(0)).toHaveAttr('title', 'bad');
+	    expect(imgs2.eq(1)).toHaveAttr('title', 'poor');
+	    expect(imgs2.eq(2)).toHaveAttr('title', 'regular');
+	    expect(imgs2.eq(3)).toHaveAttr('title', 'good');
+	    expect(imgs2.eq(4)).toHaveAttr('title', 'gorgeous');
+
+	    expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(imgs3.eq(0)).toHaveAttr('title', 'bad');
+	    expect(imgs3.eq(1)).toHaveAttr('title', 'poor');
+	    expect(imgs3.eq(2)).toHaveAttr('title', 'regular');
+	    expect(imgs3.eq(3)).toHaveAttr('title', 'good');
+	    expect(imgs3.eq(4)).toHaveAttr('title', 'gorgeous');
+
+	    expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should select first raty', function() {
+		// given
+		var stars = $('.star').raty(),
+			imgs1 = stars.eq(0).children('img'),
+			imgs2 = stars.eq(1).children('img'),
+			imgs3 = stars.eq(2).children('img');
+
+		// when
+		imgs1.eq(3).mouseover();
+
+		// then
+	    expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should click second raty', function() {
+		// given
+		var stars = $('.star').raty(),
+			imgs1 = stars.eq(0).children('img'),
+			imgs2 = stars.eq(1).children('img'),
+			imgs3 = stars.eq(2).children('img');
+
+		// when
+
+		imgs2.eq(3).mouseover().click().mouseleave();
+
+		// then
+	    expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should click on first and third raty', function() {
+		// given
+		var stars = $('.star').raty(),
+			imgs1 = stars.eq(0).children('img'),
+			imgs2 = stars.eq(1).children('img'),
+			imgs3 = stars.eq(2).children('img');
+
+		// when
+		imgs1.eq(3).mouseover().click().mouseleave();
+		imgs3.eq(3).mouseover().click().mouseleave();
+
+		// then
+		expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should hover all and out', function() {
+		// given
+		var stars = $('.star').raty(),
+			imgs1 = stars.eq(0).children('img'),
+			imgs2 = stars.eq(1).children('img'),
+			imgs3 = stars.eq(2).children('img');
+
+		// when
+		imgs1.eq(1).mouseover().mouseleave();
+		imgs2.eq(2).mouseover().mouseleave();
+		imgs3.eq(3).mouseover().mouseleave();
+
+		// then
+		expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should set score as readonly when start with readOnly', function() {
+		// given
+		var stars = $('.star');
+
+		// when
+		stars.eq(0).raty({ readOnly: true });
+		stars.eq(1).raty({ readOnly: true });
+		stars.eq(2).raty({ readOnly: true });
+
+		// then
+		expect(stars.eq(0).children('input')).toHaveAttr('readonly', 'readonly');
+		expect(stars.eq(1).children('input')).toHaveAttr('readonly', 'readonly');
+		expect(stars.eq(2).children('input')).toHaveAttr('readonly', 'readonly');
+	});
+
+	it ('score should get it as int', function() {
+		// given
+		var stars = $('.star');;
+
+		stars.eq(0).raty({ score: 1 });
+		stars.eq(1).raty({ score: 2 });
+		stars.eq(2).raty({ score: 3 });
+
+		// when
+		var score1 = stars.eq(0).raty('score'),
+			score2 = stars.eq(1).raty('score'),
+			score3 = stars.eq(2).raty('score');
+
+		// then
+		expect(score1).toEqual(1);
+		expect(score2).toEqual(2);
+		expect(score3).toEqual(3);
+	});
+
+	it ('score should get it as float', function() {
+		// given
+		var stars = $('.star');;
+
+		stars.eq(0).raty({ score: 1.1 });
+		stars.eq(1).raty({ score: 2.2 });
+		stars.eq(2).raty({ score: 3.3 });
+
+		// when
+		var score1 = stars.eq(0).raty('score'),
+			score2 = stars.eq(1).raty('score'),
+			score3 = stars.eq(2).raty('score');
+
+		// then
+		expect(score1).toEqual(1.1);
+		expect(score2).toEqual(2.2);
+		expect(score3).toEqual(3.3);
+	});
+
+});
+
+describe('Using function with id', function() {
+
+	beforeEach(function() {
+		$('body').append('<div id="star"></div>');
+	});
+
+	afterEach(function() {
+		$('#star').remove();
+	});
+
+	it ('should start the start with 3 stars', function() {
+		// given
+		var star = $('#star').raty();
+
+		// when
+		star.raty('score', 3);
+
+		// then
+		var imgs = star.children('img');
+
+	    expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should not call click callback when start function run without true option', function() {
+		// given
+		var star = $('#star').raty({ click: function(score, evt) { $(this).attr('title', score); }});
+
+		// when
+		star.raty('score', 3);
+
+		// then
+		var imgs = star.children('img');
+
+	    expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(star.attr('title')).toEqual(undefined);
+	});
+
+	it ('should set readOnly and fix the hints', function() {
+		// given
+		var star = $('#star').raty({ score: 1 });
+
+		// when
+		star.raty('readOnly', true);
+
+		var imgs = star.children('img');
+
+		imgs.eq(3).mouseover().click();
+
+		// then
+	    expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(imgs.eq(0)).toHaveAttr('title', 'bad');
+	    expect(imgs.eq(1)).toHaveAttr('title', 'bad');
+	    expect(imgs.eq(2)).toHaveAttr('title', 'bad');
+	    expect(imgs.eq(3)).toHaveAttr('title', 'bad');
+	    expect(imgs.eq(4)).toHaveAttr('title', 'bad');
+	});
+
+	it ('should set readOnly and hide cancel button', function() {
+		// given
+		var star = $('#star').raty({ cancel: true, path: '../img' });
+
+		// when
+		star.raty('readOnly', true);
+
+		// then
+	    expect(star.children('.raty-cancel')).not.toBeVisible();
+	});
+
+	it ('should unset readOnly and show cancel button', function() {
+		// given
+		var star = $('#star').raty({ cancel: true, readOnly: true, path: '../img' });
+
+		// when
+		star.raty('readOnly', false);
+
+		// then
+	    expect(star.children('.raty-cancel')).toBeVisible();
+	});
+
+	it ('should unset readOnly and put back the titles', function() {
+		// given
+		var star = $('#star').raty();
+
+		// when
+		star.raty('readOnly', true).raty('readOnly', false);
+
+		var imgs = star.children('img');
+
+		imgs.eq(1).mouseover().click();
+
+		// then
+	    expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(imgs.eq(0)).toHaveAttr('title', 'bad');
+	    expect(imgs.eq(1)).toHaveAttr('title', 'poor');
+	    expect(imgs.eq(2)).toHaveAttr('title', 'regular');
+	    expect(imgs.eq(3)).toHaveAttr('title', 'good');
+	    expect(imgs.eq(4)).toHaveAttr('title', 'gorgeous');
+	});
+
+	it ('should cancel without click', function() {
+		// given
+		var star = $('#star').raty({ score: 1 });
+
+		// when
+		star.raty('cancel');
+
+		var imgs = star.children('img');
+
+		// then
+	    expect(imgs.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(star.children('input').val()).toEqual('');
+	});
+
+	it ('should cancel with click', function() {
+		// given
+		var star = $('#star').raty({
+			click: function(score, evt) {
+				$(this).attr('title', (score === null) ? 'null' : score);
+			},
+			score: 1
+		});
+
+		// when
+		star.raty('cancel', true);
+
+		var imgs = star.children('img');
+
+		// then
+	    expect(imgs.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(star).toHaveAttr('title', 'null');
+	    expect(star.children('input').val()).toEqual('');
+	});
+
+	it ('should click the star 3', function() {
+		// given
+		var star = $('#star').raty({
+			click: function(score, evt) {
+				$(this).attr('title', score);
+			}
+		});
+
+		// when
+		star.raty('click', 3);
+
+		// then
+		var imgs = star.children('img');
+
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(star).toHaveAttr('title', 3);
+	});
+
+	it ('should set a target on div with mouseover', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint = $('#hint'),
+			star = $('#star').raty({ target: '#hint', targetKeep: true, click: function(score, evt) { } });
+
+		// when
+		star.raty('click', 4);
+
+		// then
+		expect(hint).toHaveHtml('good');
+
+		hint.remove();
+	});
+
+	it ('should set a target on div with mouseover', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint = $('#hint'),
+			star = $('#star').raty({ target: '#hint', targetKeep: true });
+
+		// when
+		star.raty('score', 4);
+
+		// then
+		expect(hint).toHaveHtml('good');
+
+		hint.remove();
+	});
+
+	it ('should set a target on div with cancel', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint = $('#hint'),
+			star = $('#star').raty({ cancel: true, target: '#hint', targetKeep: true });
+
+		// when
+		star.raty('cancel').mouseout();
+
+		// then
+		expect(hint).toBeEmpty();
+
+		hint.remove();
+	});
+
+	it ('[score / readOnly] should not set score when it is read-only', function() {
+		// given
+		var star = $('#star').raty({ readOnly: true });
+
+		// when
+		star.raty('score', 1);
+
+		// then
+		var imgs = star.children('img');
+
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should not click when it is readOnly', function() {
+		// given
+		var star = $('#star').raty({ readOnly: true, click: function(score, evt) { } });
+
+		// when
+		star.raty('click', 1);
+
+		// then
+		var imgs = star.children('img');
+
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should not cancel when it is readOnly', function() {
+		// given
+		var star = $('#star').raty({ readOnly: true, score: 1 });
+
+		// when
+		star.raty('cancel');
+
+		// then
+		var imgs = star.children('img');
+
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(star.children('input').val()).toEqual('1');
+	});
+
+	it ('should set score as readonly when start with readOnly', function() {
+		// given
+		var star = $('#star').raty();
+
+		// when
+		star.raty('readOnly', true);
+
+		// then
+		expect(star.children('input')).toHaveAttr('readonly', 'readonly');
+	});
+
+	it ('should set the cancel hint on target', function() {
+		$('body').append('<div id="hint"></div>');
+
+		// given
+		var hint	= $('#hint'),
+			star	= $('#star').raty({ cancel: true, target: '#hint', targetFormat: 'score: {score}', targetKeep: true });
+
+		// when
+		star.raty('cancel').mouseleave();
+
+		// then
+		expect(hint).toHaveHtml('score: ');
+
+		hint.remove();
+	});
+
+	it ('should rebind the cancel button after readOnly be enabled', function() {
+		// given
+		var star = $('#star').raty({ cancel: true });
+
+		// when
+		star.raty('readOnly', true).raty('readOnly', false).children('.raty-cancel').mouseenter();
+
+		var imgs = star.children('img').not('.raty-cancel');
+
+		// then
+		expect(imgs.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('[cancel] should cancel after a read-only false', function() {
+		// given
+		var star = $('#star').raty({ score: 1 }).raty('readOnly', true).raty('readOnly', false);
+
+		// when
+		star.raty('cancel');
+
+		var imgs = star.children('img');
+
+		// then
+	    expect(imgs.eq(0)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(1)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(2)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(3)).toHaveAttr('src', 'img/star-off.png');
+	    expect(imgs.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+	    expect(star.children('input').val()).toEqual('');
+	});
+
+});
+
+describe('Using function with class', function() {
+	
+	beforeEach(function() {
+		$('body').append('<div class="star"></div><div class="star"></div><div class="star"></div>');
+	});
+
+	afterEach(function() {
+		$('.star').remove();
+	});
+
+	it ('should start the starts with 3 stars', function() {
+		// given
+		var stars = $('.star').raty();
+
+		// when
+		stars.raty('score', 3);
+
+		// then
+		var imgs1 = stars.eq(0).children('img'),
+			imgs2 = stars.eq(1).children('img'),
+			imgs3 = stars.eq(2).children('img');
+
+		expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+	
+	it ('should set readOnly and fix the hint', function() {
+		// given
+		var stars = $('.star').raty();
+
+		// when
+		stars.raty('readOnly', true);
+
+		var imgs1 = stars.eq(0).children('img'),
+			imgs2 = stars.eq(1).children('img'),
+			imgs3 = stars.eq(2).children('img');
+	
+		imgs1.eq(3).mouseover().click();
+		imgs2.eq(3).mouseover().click();
+		imgs3.eq(3).mouseover().click();
+
+		// then
+		expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(imgs1.eq(0)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs1.eq(1)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs1.eq(2)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs1.eq(3)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs1.eq(4)).toHaveAttr('title', 'not rated yet');
+
+		expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(imgs2.eq(0)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs2.eq(1)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs2.eq(2)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs2.eq(3)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs2.eq(4)).toHaveAttr('title', 'not rated yet');
+
+		expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(imgs3.eq(0)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs3.eq(1)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs3.eq(2)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs3.eq(3)).toHaveAttr('title', 'not rated yet');
+	    expect(imgs3.eq(4)).toHaveAttr('title', 'not rated yet');
+	});
+
+	it ('should unset readOnly and put back the titles', function() {
+		// given
+		var stars = $('.star').raty();
+
+		// when
+		stars.raty('readOnly', true).raty('readOnly', false);
+
+		var imgs1 = stars.eq(0).children('img'),
+			imgs2 = stars.eq(1).children('img'),
+			imgs3 = stars.eq(2).children('img');
+
+		imgs1.eq(3).mouseover().click();
+		imgs2.eq(3).mouseover().click();
+		imgs3.eq(3).mouseover().click();
+
+		// then
+		expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(imgs1.eq(0)).toHaveAttr('title', 'bad');
+	    expect(imgs1.eq(1)).toHaveAttr('title', 'poor');
+	    expect(imgs1.eq(2)).toHaveAttr('title', 'regular');
+	    expect(imgs1.eq(3)).toHaveAttr('title', 'good');
+	    expect(imgs1.eq(4)).toHaveAttr('title', 'gorgeous');
+
+		expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(imgs2.eq(0)).toHaveAttr('title', 'bad');
+	    expect(imgs2.eq(1)).toHaveAttr('title', 'poor');
+	    expect(imgs2.eq(2)).toHaveAttr('title', 'regular');
+	    expect(imgs2.eq(3)).toHaveAttr('title', 'good');
+	    expect(imgs2.eq(4)).toHaveAttr('title', 'gorgeous');
+
+		expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(imgs3.eq(0)).toHaveAttr('title', 'bad');
+	    expect(imgs3.eq(1)).toHaveAttr('title', 'poor');
+	    expect(imgs3.eq(2)).toHaveAttr('title', 'regular');
+	    expect(imgs3.eq(3)).toHaveAttr('title', 'good');
+	    expect(imgs3.eq(4)).toHaveAttr('title', 'gorgeous');
+	});
+	
+	it ('should cancel without click', function() {
+		// given
+		var stars = $('.star').raty({ score: 1 });
+
+		// when
+		stars.raty('cancel');
+
+		// then
+		var imgs1 = stars.eq(0).children('img'),
+			imgs2 = stars.eq(1).children('img'),
+			imgs3 = stars.eq(2).children('img');
+
+		expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(stars.eq(0).children('input').val()).toEqual('');
+
+		expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(stars.eq(1).children('input').val()).toEqual('');
+
+		expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(stars.eq(2).children('input').val()).toEqual('');
+	});
+	
+	it ('should cancel with click', function() {
+		// given
+		var stars = $('.star').raty({
+			click: function(score, evt) {
+				$(this).attr('title', (score === null) ? 'null' : score);
+			},
+			score: 1
+		});
+
+		// when
+		stars.raty('cancel', true);
+
+		// then
+		var imgs1 = stars.eq(0).children('img'),
+			imgs2 = stars.eq(1).children('img'),
+			imgs3 = stars.eq(2).children('img');
+	
+		expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(stars.eq(0)).toHaveAttr('title', 'null');
+		expect(stars.eq(0).children('input').val()).toEqual('');
+
+		expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(stars.eq(1)).toHaveAttr('title', 'null');
+		expect(stars.eq(1).children('input').val()).toEqual('');
+
+		expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(stars.eq(1)).toHaveAttr('title', 'null');
+		expect(stars.eq(2).children('input').val()).toEqual('');
+	});
+
+	it ('should click the star 3', function() {
+		// given
+		var stars = $('.star').raty({
+			click: function(score, evt) {
+				$(this).attr('title', score);
+			}
+		});
+
+		// when
+		stars.raty('click', 3);
+
+		// then
+		var imgs1 = stars.eq(0).children('img'),
+			imgs2 = stars.eq(1).children('img'),
+			imgs3 = stars.eq(2).children('img');
+	
+		expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(stars.eq(0)).toHaveAttr('title', 3);
+
+		expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(stars.eq(1)).toHaveAttr('title', 3);
+
+		expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(stars.eq(2)).toHaveAttr('title', 3);
+	});
+	
+	it ('should set a target on div with click', function() {
+		$('body').append('<div id="hint1"></div><div id="hint2"></div><div id="hint3"></div>');
+
+		// given
+		var hint1	= $('#hint1'),
+			hint2	= $('#hint2'),
+			hint3	= $('#hint3'),
+			stars	= $('.star');
+
+		stars.eq(0).raty({ target: '#hint1', targetKeep: true, click: function(score, evt) { } });
+		stars.eq(1).raty({ target: '#hint2', targetKeep: true, click: function(score, evt) { } });
+		stars.eq(2).raty({ target: '#hint3', targetKeep: true, click: function(score, evt) { } });
+
+		// when
+		stars.raty('click', 4);
+
+		// then
+		expect(hint1).toHaveHtml('good');
+		expect(hint2).toHaveHtml('good');
+		expect(hint3).toHaveHtml('good');
+
+		hint1.remove();
+		hint2.remove();
+		hint3.remove();
+	});
+	
+	it ('should set a target on div with start', function() {
+		$('body').append('<div id="hint1"></div><div id="hint2"></div><div id="hint3"></div>');
+
+		// given
+		var hint1	= $('#hint1'),
+			hint2	= $('#hint2'),
+			hint3	= $('#hint3'),
+			stars	= $('.star');
+	
+		stars.eq(0).raty({ target: '#hint1', targetKeep: true, click: function(score, evt) { } });
+		stars.eq(1).raty({ target: '#hint2', targetKeep: true, click: function(score, evt) { } });
+		stars.eq(2).raty({ target: '#hint3', targetKeep: true, click: function(score, evt) { } });
+
+		// when
+		stars.raty('score', 4);
+
+		// then
+		expect(hint1).toHaveHtml('good');
+		expect(hint2).toHaveHtml('good');
+		expect(hint3).toHaveHtml('good');
+
+		hint1.remove();
+		hint2.remove();
+		hint3.remove();
+	});
+
+	it ('should set a target on div with cancel', function() {
+		$('body').append('<div id="hint1"></div><div id="hint2"></div><div id="hint3"></div>');
+
+		// given
+		var hint1	= $('#hint1'),
+			hint2	= $('#hint2'),
+			hint3	= $('#hint3'),
+			stars	= $('.star');
+	
+		stars.eq(0).raty({ cancel: true, target: '#hint1', targetKeep: true, click: function(score, evt) { } });
+		stars.eq(1).raty({ cancel: true, target: '#hint2', targetKeep: true, click: function(score, evt) { } });
+		stars.eq(2).raty({ cancel: true, target: '#hint3', targetKeep: true, click: function(score, evt) { } });
+
+		// when
+		stars.raty('cancel').mouseout();
+
+		// then
+		expect(hint1).toBeEmpty();
+		expect(hint2).toBeEmpty();
+		expect(hint3).toBeEmpty();
+
+		hint1.remove();
+		hint2.remove();
+		hint3.remove();
+	});
+
+	it ('should not start when it is readOnly', function() {
+		// given
+		var stars = $('.star').raty({ readOnly: true });
+
+		// when
+		stars.eq(0).raty('score', 1);
+		stars.eq(1).raty('score', 1);
+		stars.eq(2).raty('score', 1);
+
+		// then
+		var imgs1 = stars.eq(0).children('img'),
+			imgs2 = stars.eq(1).children('img'),
+			imgs3 = stars.eq(2).children('img');
+
+		expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should not click when it is readOnly', function() {
+		// given
+		var stars = $('.star').raty({ readOnly: true, click: function(score, evt) { } });
+
+		// when
+		stars.eq(0).raty('click', 1);
+		stars.eq(1).raty('click', 1);
+		stars.eq(2).raty('click', 1);
+
+		// then
+		var imgs1 = stars.eq(0).children('img'),
+			imgs2 = stars.eq(1).children('img'),
+			imgs3 = stars.eq(2).children('img');
+
+		expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+
+		expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('should not cancel when it is readOnly', function() {
+		// given
+		var stars = $('.star').raty({ readOnly: true, score: 1 });
+
+		// when
+		stars.eq(0).raty('cancel');
+		stars.eq(1).raty('cancel');
+		stars.eq(2).raty('cancel');
+
+		// then
+		var imgs1 = stars.eq(0).children('img'),
+			imgs2 = stars.eq(1).children('img'),
+			imgs3 = stars.eq(2).children('img');
+
+		expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(stars.eq(0).children('input').val()).toEqual('1');
+
+		expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(stars.eq(1).children('input').val()).toEqual('1');
+
+		expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-on.png');
+		expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+		expect(stars.eq(2).children('input').val()).toEqual('1');
+	});
+
+	it ('should set score as readonly when start with readOnly', function() {
+		// given
+		var stars = $('.star').raty();
+
+		// when
+		stars.eq(0).raty('readOnly', true);
+		stars.eq(1).raty('readOnly', true);
+		stars.eq(2).raty('readOnly', true);
+
+		// then
+		expect(stars.eq(0).children('input')).toHaveAttr('readonly', 'readonly');
+		expect(stars.eq(1).children('input')).toHaveAttr('readonly', 'readonly');
+		expect(stars.eq(2).children('input')).toHaveAttr('readonly', 'readonly');
+	});
+
+	it ('should rebind the cancel button after readOnly be enabled', function() {
+		// given
+		var stars = $('.star').raty({ cancel: true });
+
+		// when
+		stars.raty('readOnly', true).raty('readOnly', false).children('.raty-cancel').mouseenter();
+
+		// then
+		var imgs1 = stars.eq(0).children('img').not('.raty-cancel'),
+			imgs2 = stars.eq(1).children('img').not('.raty-cancel'),
+			imgs3 = stars.eq(2).children('img').not('.raty-cancel');
+
+		expect(imgs1.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs1.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	
+		expect(imgs2.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs2.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	
+		expect(imgs3.eq(0)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(1)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(2)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(3)).toHaveAttr('src', 'img/star-off.png');
+		expect(imgs3.eq(4)).toHaveAttr('src', 'img/star-off.png');
+	});
+
+	it ('[score] should set score on rating that is not read-only', function() {
+		// given
+		var stars = $('.star');
+
+		stars.raty();
+		stars.eq(1).raty('readOnly', true);
+
+		// when
+		stars.raty('score', 2);
+
+		// then
+		expect(stars.eq(0).children('input')).toHaveValue('2');
+	    expect(stars.eq(1).children('input').val()).toEqual('');
+	    expect(stars.eq(2).children('input')).toHaveValue('2');
+	});
+
+	it ('[click] should click on rating that is not read-only', function() {
+		// given
+		var stars = $('.star');
+
+		stars.raty({ click: function() { } });
+		stars.eq(1).raty('readOnly', true);
+
+		// when
+		stars.raty('click', 2);
+
+		// then
+		expect(stars.eq(0).children('input')).toHaveValue('2');
+	    expect(stars.eq(1).children('input').val()).toEqual('');
+	    expect(stars.eq(2).children('input')).toHaveValue('2');
+	});
+
+	it ('[cancel] should cancel all rating that is not read-only', function() {
+		// given
+		var stars = $('.star');
+
+		stars.raty({ score: 2 });
+		stars.eq(1).raty('readOnly', true);
+
+		// when
+		stars.raty('cancel');
+
+		// then
+		expect(stars.eq(0).children('input').val()).toEqual('');
+	    expect(stars.eq(1).children('input')).toHaveValue('2');
+	    expect(stars.eq(2).children('input').val()).toEqual('');
+	});
+
+});
Index: trunk/prototype/plugins/jq-raty/test/lib/jasmine.js
===================================================================
--- trunk/prototype/plugins/jq-raty/test/lib/jasmine.js	(revision 6528)
+++ trunk/prototype/plugins/jq-raty/test/lib/jasmine.js	(revision 6528)
@@ -0,0 +1,2476 @@
+var isCommonJS = typeof window == "undefined";
+
+/**
+ * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
+ *
+ * @namespace
+ */
+var jasmine = {};
+if (isCommonJS) exports.jasmine = jasmine;
+/**
+ * @private
+ */
+jasmine.unimplementedMethod_ = function() {
+  throw new Error("unimplemented method");
+};
+
+/**
+ * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
+ * a plain old variable and may be redefined by somebody else.
+ *
+ * @private
+ */
+jasmine.undefined = jasmine.___undefined___;
+
+/**
+ * Show diagnostic messages in the console if set to true
+ *
+ */
+jasmine.VERBOSE = false;
+
+/**
+ * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
+ *
+ */
+jasmine.DEFAULT_UPDATE_INTERVAL = 250;
+
+/**
+ * Default timeout interval in milliseconds for waitsFor() blocks.
+ */
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+jasmine.getGlobal = function() {
+  function getGlobal() {
+    return this;
+  }
+
+  return getGlobal();
+};
+
+/**
+ * Allows for bound functions to be compared.  Internal use only.
+ *
+ * @ignore
+ * @private
+ * @param base {Object} bound 'this' for the function
+ * @param name {Function} function to find
+ */
+jasmine.bindOriginal_ = function(base, name) {
+  var original = base[name];
+  if (original.apply) {
+    return function() {
+      return original.apply(base, arguments);
+    };
+  } else {
+    // IE support
+    return jasmine.getGlobal()[name];
+  }
+};
+
+jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
+jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
+jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
+jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
+
+jasmine.MessageResult = function(values) {
+  this.type = 'log';
+  this.values = values;
+  this.trace = new Error(); // todo: test better
+};
+
+jasmine.MessageResult.prototype.toString = function() {
+  var text = "";
+  for (var i = 0; i < this.values.length; i++) {
+    if (i > 0) text += " ";
+    if (jasmine.isString_(this.values[i])) {
+      text += this.values[i];
+    } else {
+      text += jasmine.pp(this.values[i]);
+    }
+  }
+  return text;
+};
+
+jasmine.ExpectationResult = function(params) {
+  this.type = 'expect';
+  this.matcherName = params.matcherName;
+  this.passed_ = params.passed;
+  this.expected = params.expected;
+  this.actual = params.actual;
+  this.message = this.passed_ ? 'Passed.' : params.message;
+
+  var trace = (params.trace || new Error(this.message));
+  this.trace = this.passed_ ? '' : trace;
+};
+
+jasmine.ExpectationResult.prototype.toString = function () {
+  return this.message;
+};
+
+jasmine.ExpectationResult.prototype.passed = function () {
+  return this.passed_;
+};
+
+/**
+ * Getter for the Jasmine environment. Ensures one gets created
+ */
+jasmine.getEnv = function() {
+  var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
+  return env;
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isArray_ = function(value) {
+  return jasmine.isA_("Array", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isString_ = function(value) {
+  return jasmine.isA_("String", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isNumber_ = function(value) {
+  return jasmine.isA_("Number", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param {String} typeName
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isA_ = function(typeName, value) {
+  return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+};
+
+/**
+ * Pretty printer for expecations.  Takes any object and turns it into a human-readable string.
+ *
+ * @param value {Object} an object to be outputted
+ * @returns {String}
+ */
+jasmine.pp = function(value) {
+  var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
+  stringPrettyPrinter.format(value);
+  return stringPrettyPrinter.string;
+};
+
+/**
+ * Returns true if the object is a DOM Node.
+ *
+ * @param {Object} obj object to check
+ * @returns {Boolean}
+ */
+jasmine.isDomNode = function(obj) {
+  return obj.nodeType > 0;
+};
+
+/**
+ * Returns a matchable 'generic' object of the class type.  For use in expecations of type when values don't matter.
+ *
+ * @example
+ * // don't care about which function is passed in, as long as it's a function
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
+ *
+ * @param {Class} clazz
+ * @returns matchable object of the type clazz
+ */
+jasmine.any = function(clazz) {
+  return new jasmine.Matchers.Any(clazz);
+};
+
+/**
+ * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
+ *
+ * Spies should be created in test setup, before expectations.  They can then be checked, using the standard Jasmine
+ * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
+ *
+ * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
+ *
+ * Spies are torn down at the end of every spec.
+ *
+ * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
+ *
+ * @example
+ * // a stub
+ * var myStub = jasmine.createSpy('myStub');  // can be used anywhere
+ *
+ * // spy example
+ * var foo = {
+ *   not: function(bool) { return !bool; }
+ * }
+ *
+ * // actual foo.not will not be called, execution stops
+ * spyOn(foo, 'not');
+
+ // foo.not spied upon, execution will continue to implementation
+ * spyOn(foo, 'not').andCallThrough();
+ *
+ * // fake example
+ * var foo = {
+ *   not: function(bool) { return !bool; }
+ * }
+ *
+ * // foo.not(val) will return val
+ * spyOn(foo, 'not').andCallFake(function(value) {return value;});
+ *
+ * // mock example
+ * foo.not(7 == 7);
+ * expect(foo.not).toHaveBeenCalled();
+ * expect(foo.not).toHaveBeenCalledWith(true);
+ *
+ * @constructor
+ * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
+ * @param {String} name
+ */
+jasmine.Spy = function(name) {
+  /**
+   * The name of the spy, if provided.
+   */
+  this.identity = name || 'unknown';
+  /**
+   *  Is this Object a spy?
+   */
+  this.isSpy = true;
+  /**
+   * The actual function this spy stubs.
+   */
+  this.plan = function() {
+  };
+  /**
+   * Tracking of the most recent call to the spy.
+   * @example
+   * var mySpy = jasmine.createSpy('foo');
+   * mySpy(1, 2);
+   * mySpy.mostRecentCall.args = [1, 2];
+   */
+  this.mostRecentCall = {};
+
+  /**
+   * Holds arguments for each call to the spy, indexed by call count
+   * @example
+   * var mySpy = jasmine.createSpy('foo');
+   * mySpy(1, 2);
+   * mySpy(7, 8);
+   * mySpy.mostRecentCall.args = [7, 8];
+   * mySpy.argsForCall[0] = [1, 2];
+   * mySpy.argsForCall[1] = [7, 8];
+   */
+  this.argsForCall = [];
+  this.calls = [];
+};
+
+/**
+ * Tells a spy to call through to the actual implemenatation.
+ *
+ * @example
+ * var foo = {
+ *   bar: function() { // do some stuff }
+ * }
+ *
+ * // defining a spy on an existing property: foo.bar
+ * spyOn(foo, 'bar').andCallThrough();
+ */
+jasmine.Spy.prototype.andCallThrough = function() {
+  this.plan = this.originalValue;
+  return this;
+};
+
+/**
+ * For setting the return value of a spy.
+ *
+ * @example
+ * // defining a spy from scratch: foo() returns 'baz'
+ * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() returns 'baz'
+ * spyOn(foo, 'bar').andReturn('baz');
+ *
+ * @param {Object} value
+ */
+jasmine.Spy.prototype.andReturn = function(value) {
+  this.plan = function() {
+    return value;
+  };
+  return this;
+};
+
+/**
+ * For throwing an exception when a spy is called.
+ *
+ * @example
+ * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
+ * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
+ * spyOn(foo, 'bar').andThrow('baz');
+ *
+ * @param {String} exceptionMsg
+ */
+jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
+  this.plan = function() {
+    throw exceptionMsg;
+  };
+  return this;
+};
+
+/**
+ * Calls an alternate implementation when a spy is called.
+ *
+ * @example
+ * var baz = function() {
+ *   // do some stuff, return something
+ * }
+ * // defining a spy from scratch: foo() calls the function baz
+ * var foo = jasmine.createSpy('spy on foo').andCall(baz);
+ *
+ * // defining a spy on an existing property: foo.bar() calls an anonymnous function
+ * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
+ *
+ * @param {Function} fakeFunc
+ */
+jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
+  this.plan = fakeFunc;
+  return this;
+};
+
+/**
+ * Resets all of a spy's the tracking variables so that it can be used again.
+ *
+ * @example
+ * spyOn(foo, 'bar');
+ *
+ * foo.bar();
+ *
+ * expect(foo.bar.callCount).toEqual(1);
+ *
+ * foo.bar.reset();
+ *
+ * expect(foo.bar.callCount).toEqual(0);
+ */
+jasmine.Spy.prototype.reset = function() {
+  this.wasCalled = false;
+  this.callCount = 0;
+  this.argsForCall = [];
+  this.calls = [];
+  this.mostRecentCall = {};
+};
+
+jasmine.createSpy = function(name) {
+
+  var spyObj = function() {
+    spyObj.wasCalled = true;
+    spyObj.callCount++;
+    var args = jasmine.util.argsToArray(arguments);
+    spyObj.mostRecentCall.object = this;
+    spyObj.mostRecentCall.args = args;
+    spyObj.argsForCall.push(args);
+    spyObj.calls.push({object: this, args: args});
+    return spyObj.plan.apply(this, arguments);
+  };
+
+  var spy = new jasmine.Spy(name);
+
+  for (var prop in spy) {
+    spyObj[prop] = spy[prop];
+  }
+
+  spyObj.reset();
+
+  return spyObj;
+};
+
+/**
+ * Determines whether an object is a spy.
+ *
+ * @param {jasmine.Spy|Object} putativeSpy
+ * @returns {Boolean}
+ */
+jasmine.isSpy = function(putativeSpy) {
+  return putativeSpy && putativeSpy.isSpy;
+};
+
+/**
+ * Creates a more complicated spy: an Object that has every property a function that is a spy.  Used for stubbing something
+ * large in one call.
+ *
+ * @param {String} baseName name of spy class
+ * @param {Array} methodNames array of names of methods to make spies
+ */
+jasmine.createSpyObj = function(baseName, methodNames) {
+  if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
+    throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
+  }
+  var obj = {};
+  for (var i = 0; i < methodNames.length; i++) {
+    obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
+  }
+  return obj;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
+ *
+ * Be careful not to leave calls to <code>jasmine.log</code> in production code.
+ */
+jasmine.log = function() {
+  var spec = jasmine.getEnv().currentSpec;
+  spec.log.apply(spec, arguments);
+};
+
+/**
+ * Function that installs a spy on an existing object's method name.  Used within a Spec to create a spy.
+ *
+ * @example
+ * // spy example
+ * var foo = {
+ *   not: function(bool) { return !bool; }
+ * }
+ * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
+ *
+ * @see jasmine.createSpy
+ * @param obj
+ * @param methodName
+ * @returns a Jasmine spy that can be chained with all spy methods
+ */
+var spyOn = function(obj, methodName) {
+  return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
+};
+if (isCommonJS) exports.spyOn = spyOn;
+
+/**
+ * Creates a Jasmine spec that will be added to the current suite.
+ *
+ * // TODO: pending tests
+ *
+ * @example
+ * it('should be true', function() {
+ *   expect(true).toEqual(true);
+ * });
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var it = function(desc, func) {
+  return jasmine.getEnv().it(desc, func);
+};
+if (isCommonJS) exports.it = it;
+
+/**
+ * Creates a <em>disabled</em> Jasmine spec.
+ *
+ * A convenience method that allows existing specs to be disabled temporarily during development.
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var xit = function(desc, func) {
+  return jasmine.getEnv().xit(desc, func);
+};
+if (isCommonJS) exports.xit = xit;
+
+/**
+ * Starts a chain for a Jasmine expectation.
+ *
+ * It is passed an Object that is the actual value and should chain to one of the many
+ * jasmine.Matchers functions.
+ *
+ * @param {Object} actual Actual value to test against and expected value
+ */
+var expect = function(actual) {
+  return jasmine.getEnv().currentSpec.expect(actual);
+};
+if (isCommonJS) exports.expect = expect;
+
+/**
+ * Defines part of a jasmine spec.  Used in cominbination with waits or waitsFor in asynchrnous specs.
+ *
+ * @param {Function} func Function that defines part of a jasmine spec.
+ */
+var runs = function(func) {
+  jasmine.getEnv().currentSpec.runs(func);
+};
+if (isCommonJS) exports.runs = runs;
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+var waits = function(timeout) {
+  jasmine.getEnv().currentSpec.waits(timeout);
+};
+if (isCommonJS) exports.waits = waits;
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+  jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
+};
+if (isCommonJS) exports.waitsFor = waitsFor;
+
+/**
+ * A function that is called before each spec in a suite.
+ *
+ * Used for spec setup, including validating assumptions.
+ *
+ * @param {Function} beforeEachFunction
+ */
+var beforeEach = function(beforeEachFunction) {
+  jasmine.getEnv().beforeEach(beforeEachFunction);
+};
+if (isCommonJS) exports.beforeEach = beforeEach;
+
+/**
+ * A function that is called after each spec in a suite.
+ *
+ * Used for restoring any state that is hijacked during spec execution.
+ *
+ * @param {Function} afterEachFunction
+ */
+var afterEach = function(afterEachFunction) {
+  jasmine.getEnv().afterEach(afterEachFunction);
+};
+if (isCommonJS) exports.afterEach = afterEach;
+
+/**
+ * Defines a suite of specifications.
+ *
+ * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
+ * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
+ * of setup in some tests.
+ *
+ * @example
+ * // TODO: a simple suite
+ *
+ * // TODO: a simple suite with a nested describe block
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var describe = function(description, specDefinitions) {
+  return jasmine.getEnv().describe(description, specDefinitions);
+};
+if (isCommonJS) exports.describe = describe;
+
+/**
+ * Disables a suite of specifications.  Used to disable some suites in a file, or files, temporarily during development.
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var xdescribe = function(description, specDefinitions) {
+  return jasmine.getEnv().xdescribe(description, specDefinitions);
+};
+if (isCommonJS) exports.xdescribe = xdescribe;
+
+
+// Provide the XMLHttpRequest class for IE 5.x-6.x:
+jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
+  function tryIt(f) {
+    try {
+      return f();
+    } catch(e) {
+    }
+    return null;
+  }
+
+  var xhr = tryIt(function() {
+    return new ActiveXObject("Msxml2.XMLHTTP.6.0");
+  }) ||
+    tryIt(function() {
+      return new ActiveXObject("Msxml2.XMLHTTP.3.0");
+    }) ||
+    tryIt(function() {
+      return new ActiveXObject("Msxml2.XMLHTTP");
+    }) ||
+    tryIt(function() {
+      return new ActiveXObject("Microsoft.XMLHTTP");
+    });
+
+  if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
+
+  return xhr;
+} : XMLHttpRequest;
+/**
+ * @namespace
+ */
+jasmine.util = {};
+
+/**
+ * Declare that a child class inherit it's prototype from the parent class.
+ *
+ * @private
+ * @param {Function} childClass
+ * @param {Function} parentClass
+ */
+jasmine.util.inherit = function(childClass, parentClass) {
+  /**
+   * @private
+   */
+  var subclass = function() {
+  };
+  subclass.prototype = parentClass.prototype;
+  childClass.prototype = new subclass();
+};
+
+jasmine.util.formatException = function(e) {
+  var lineNumber;
+  if (e.line) {
+    lineNumber = e.line;
+  }
+  else if (e.lineNumber) {
+    lineNumber = e.lineNumber;
+  }
+
+  var file;
+
+  if (e.sourceURL) {
+    file = e.sourceURL;
+  }
+  else if (e.fileName) {
+    file = e.fileName;
+  }
+
+  var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
+
+  if (file && lineNumber) {
+    message += ' in ' + file + ' (line ' + lineNumber + ')';
+  }
+
+  return message;
+};
+
+jasmine.util.htmlEscape = function(str) {
+  if (!str) return str;
+  return str.replace(/&/g, '&amp;')
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;');
+};
+
+jasmine.util.argsToArray = function(args) {
+  var arrayOfArgs = [];
+  for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
+  return arrayOfArgs;
+};
+
+jasmine.util.extend = function(destination, source) {
+  for (var property in source) destination[property] = source[property];
+  return destination;
+};
+
+/**
+ * Environment for Jasmine
+ *
+ * @constructor
+ */
+jasmine.Env = function() {
+  this.currentSpec = null;
+  this.currentSuite = null;
+  this.currentRunner_ = new jasmine.Runner(this);
+
+  this.reporter = new jasmine.MultiReporter();
+
+  this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
+  this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
+  this.lastUpdate = 0;
+  this.specFilter = function() {
+    return true;
+  };
+
+  this.nextSpecId_ = 0;
+  this.nextSuiteId_ = 0;
+  this.equalityTesters_ = [];
+
+  // wrap matchers
+  this.matchersClass = function() {
+    jasmine.Matchers.apply(this, arguments);
+  };
+  jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
+
+  jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
+};
+
+
+jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
+jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
+jasmine.Env.prototype.setInterval = jasmine.setInterval;
+jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
+
+/**
+ * @returns an object containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.version = function () {
+  if (jasmine.version_) {
+    return jasmine.version_;
+  } else {
+    throw new Error('Version not set');
+  }
+};
+
+/**
+ * @returns string containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.versionString = function() {
+  if (!jasmine.version_) {
+    return "version unknown";
+  }
+
+  var version = this.version();
+  var versionString = version.major + "." + version.minor + "." + version.build;
+  if (version.release_candidate) {
+    versionString += ".rc" + version.release_candidate;
+  }
+  versionString += " revision " + version.revision;
+  return versionString;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSpecId = function () {
+  return this.nextSpecId_++;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSuiteId = function () {
+  return this.nextSuiteId_++;
+};
+
+/**
+ * Register a reporter to receive status updates from Jasmine.
+ * @param {jasmine.Reporter} reporter An object which will receive status updates.
+ */
+jasmine.Env.prototype.addReporter = function(reporter) {
+  this.reporter.addReporter(reporter);
+};
+
+jasmine.Env.prototype.execute = function() {
+  this.currentRunner_.execute();
+};
+
+jasmine.Env.prototype.describe = function(description, specDefinitions) {
+  var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
+
+  var parentSuite = this.currentSuite;
+  if (parentSuite) {
+    parentSuite.add(suite);
+  } else {
+    this.currentRunner_.add(suite);
+  }
+
+  this.currentSuite = suite;
+
+  var declarationError = null;
+  try {
+    specDefinitions.call(suite);
+  } catch(e) {
+    declarationError = e;
+  }
+
+  if (declarationError) {
+    this.it("encountered a declaration exception", function() {
+      throw declarationError;
+    });
+  }
+
+  this.currentSuite = parentSuite;
+
+  return suite;
+};
+
+jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
+  if (this.currentSuite) {
+    this.currentSuite.beforeEach(beforeEachFunction);
+  } else {
+    this.currentRunner_.beforeEach(beforeEachFunction);
+  }
+};
+
+jasmine.Env.prototype.currentRunner = function () {
+  return this.currentRunner_;
+};
+
+jasmine.Env.prototype.afterEach = function(afterEachFunction) {
+  if (this.currentSuite) {
+    this.currentSuite.afterEach(afterEachFunction);
+  } else {
+    this.currentRunner_.afterEach(afterEachFunction);
+  }
+
+};
+
+jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
+  return {
+    execute: function() {
+    }
+  };
+};
+
+jasmine.Env.prototype.it = function(description, func) {
+  var spec = new jasmine.Spec(this, this.currentSuite, description);
+  this.currentSuite.add(spec);
+  this.currentSpec = spec;
+
+  if (func) {
+    spec.runs(func);
+  }
+
+  return spec;
+};
+
+jasmine.Env.prototype.xit = function(desc, func) {
+  return {
+    id: this.nextSpecId(),
+    runs: function() {
+    }
+  };
+};
+
+jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
+  if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
+    return true;
+  }
+
+  a.__Jasmine_been_here_before__ = b;
+  b.__Jasmine_been_here_before__ = a;
+
+  var hasKey = function(obj, keyName) {
+    return obj !== null && obj[keyName] !== jasmine.undefined;
+  };
+
+  for (var property in b) {
+    if (!hasKey(a, property) && hasKey(b, property)) {
+      mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+    }
+  }
+  for (property in a) {
+    if (!hasKey(b, property) && hasKey(a, property)) {
+      mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
+    }
+  }
+  for (property in b) {
+    if (property == '__Jasmine_been_here_before__') continue;
+    if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
+      mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
+    }
+  }
+
+  if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
+    mismatchValues.push("arrays were not the same length");
+  }
+
+  delete a.__Jasmine_been_here_before__;
+  delete b.__Jasmine_been_here_before__;
+  return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+};
+
+jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
+  mismatchKeys = mismatchKeys || [];
+  mismatchValues = mismatchValues || [];
+
+  for (var i = 0; i < this.equalityTesters_.length; i++) {
+    var equalityTester = this.equalityTesters_[i];
+    var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
+    if (result !== jasmine.undefined) return result;
+  }
+
+  if (a === b) return true;
+
+  if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
+    return (a == jasmine.undefined && b == jasmine.undefined);
+  }
+
+  if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
+    return a === b;
+  }
+
+  if (a instanceof Date && b instanceof Date) {
+    return a.getTime() == b.getTime();
+  }
+
+  if (a instanceof jasmine.Matchers.Any) {
+    return a.matches(b);
+  }
+
+  if (b instanceof jasmine.Matchers.Any) {
+    return b.matches(a);
+  }
+
+  if (jasmine.isString_(a) && jasmine.isString_(b)) {
+    return (a == b);
+  }
+
+  if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
+    return (a == b);
+  }
+
+  if (typeof a === "object" && typeof b === "object") {
+    return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
+  }
+
+  //Straight check
+  return (a === b);
+};
+
+jasmine.Env.prototype.contains_ = function(haystack, needle) {
+  if (jasmine.isArray_(haystack)) {
+    for (var i = 0; i < haystack.length; i++) {
+      if (this.equals_(haystack[i], needle)) return true;
+    }
+    return false;
+  }
+  return haystack.indexOf(needle) >= 0;
+};
+
+jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
+  this.equalityTesters_.push(equalityTester);
+};
+/** No-op base class for Jasmine reporters.
+ *
+ * @constructor
+ */
+jasmine.Reporter = function() {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecResults = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.log = function(str) {
+};
+
+/**
+ * Blocks are functions with executable code that make up a spec.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {Function} func
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Block = function(env, func, spec) {
+  this.env = env;
+  this.func = func;
+  this.spec = spec;
+};
+
+jasmine.Block.prototype.execute = function(onComplete) {  
+  try {
+    this.func.apply(this.spec);
+  } catch (e) {
+    this.spec.fail(e);
+  }
+  onComplete();
+};
+/** JavaScript API reporter.
+ *
+ * @constructor
+ */
+jasmine.JsApiReporter = function() {
+  this.started = false;
+  this.finished = false;
+  this.suites_ = [];
+  this.results_ = {};
+};
+
+jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
+  this.started = true;
+  var suites = runner.topLevelSuites();
+  for (var i = 0; i < suites.length; i++) {
+    var suite = suites[i];
+    this.suites_.push(this.summarize_(suite));
+  }
+};
+
+jasmine.JsApiReporter.prototype.suites = function() {
+  return this.suites_;
+};
+
+jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
+  var isSuite = suiteOrSpec instanceof jasmine.Suite;
+  var summary = {
+    id: suiteOrSpec.id,
+    name: suiteOrSpec.description,
+    type: isSuite ? 'suite' : 'spec',
+    children: []
+  };
+  
+  if (isSuite) {
+    var children = suiteOrSpec.children();
+    for (var i = 0; i < children.length; i++) {
+      summary.children.push(this.summarize_(children[i]));
+    }
+  }
+  return summary;
+};
+
+jasmine.JsApiReporter.prototype.results = function() {
+  return this.results_;
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
+  return this.results_[specId];
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
+  this.finished = true;
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
+  this.results_[spec.id] = {
+    messages: spec.results().getItems(),
+    result: spec.results().failedCount > 0 ? "failed" : "passed"
+  };
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.log = function(str) {
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
+  var results = {};
+  for (var i = 0; i < specIds.length; i++) {
+    var specId = specIds[i];
+    results[specId] = this.summarizeResult_(this.results_[specId]);
+  }
+  return results;
+};
+
+jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
+  var summaryMessages = [];
+  var messagesLength = result.messages.length;
+  for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
+    var resultMessage = result.messages[messageIndex];
+    summaryMessages.push({
+      text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
+      passed: resultMessage.passed ? resultMessage.passed() : true,
+      type: resultMessage.type,
+      message: resultMessage.message,
+      trace: {
+        stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
+      }
+    });
+  }
+
+  return {
+    result : result.result,
+    messages : summaryMessages
+  };
+};
+
+/**
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param actual
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Matchers = function(env, actual, spec, opt_isNot) {
+  this.env = env;
+  this.actual = actual;
+  this.spec = spec;
+  this.isNot = opt_isNot || false;
+  this.reportWasCalled_ = false;
+};
+
+// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
+jasmine.Matchers.pp = function(str) {
+  throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
+};
+
+// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
+jasmine.Matchers.prototype.report = function(result, failing_message, details) {
+  throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
+};
+
+jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
+  for (var methodName in prototype) {
+    if (methodName == 'report') continue;
+    var orig = prototype[methodName];
+    matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
+  }
+};
+
+jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
+  return function() {
+    var matcherArgs = jasmine.util.argsToArray(arguments);
+    var result = matcherFunction.apply(this, arguments);
+
+    if (this.isNot) {
+      result = !result;
+    }
+
+    if (this.reportWasCalled_) return result;
+
+    var message;
+    if (!result) {
+      if (this.message) {
+        message = this.message.apply(this, arguments);
+        if (jasmine.isArray_(message)) {
+          message = message[this.isNot ? 1 : 0];
+        }
+      } else {
+        var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+        message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
+        if (matcherArgs.length > 0) {
+          for (var i = 0; i < matcherArgs.length; i++) {
+            if (i > 0) message += ",";
+            message += " " + jasmine.pp(matcherArgs[i]);
+          }
+        }
+        message += ".";
+      }
+    }
+    var expectationResult = new jasmine.ExpectationResult({
+      matcherName: matcherName,
+      passed: result,
+      expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
+      actual: this.actual,
+      message: message
+    });
+    this.spec.addMatcherResult(expectationResult);
+    return jasmine.undefined;
+  };
+};
+
+
+
+
+/**
+ * toBe: compares the actual to the expected using ===
+ * @param expected
+ */
+jasmine.Matchers.prototype.toBe = function(expected) {
+  return this.actual === expected;
+};
+
+/**
+ * toNotBe: compares the actual to the expected using !==
+ * @param expected
+ * @deprecated as of 1.0. Use not.toBe() instead.
+ */
+jasmine.Matchers.prototype.toNotBe = function(expected) {
+  return this.actual !== expected;
+};
+
+/**
+ * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toEqual = function(expected) {
+  return this.env.equals_(this.actual, expected);
+};
+
+/**
+ * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
+ * @param expected
+ * @deprecated as of 1.0. Use not.toNotEqual() instead.
+ */
+jasmine.Matchers.prototype.toNotEqual = function(expected) {
+  return !this.env.equals_(this.actual, expected);
+};
+
+/**
+ * Matcher that compares the actual to the expected using a regular expression.  Constructs a RegExp, so takes
+ * a pattern or a String.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toMatch = function(expected) {
+  return new RegExp(expected).test(this.actual);
+};
+
+/**
+ * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
+ * @param expected
+ * @deprecated as of 1.0. Use not.toMatch() instead.
+ */
+jasmine.Matchers.prototype.toNotMatch = function(expected) {
+  return !(new RegExp(expected).test(this.actual));
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeDefined = function() {
+  return (this.actual !== jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeUndefined = function() {
+  return (this.actual === jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to null.
+ */
+jasmine.Matchers.prototype.toBeNull = function() {
+  return (this.actual === null);
+};
+
+/**
+ * Matcher that boolean not-nots the actual.
+ */
+jasmine.Matchers.prototype.toBeTruthy = function() {
+  return !!this.actual;
+};
+
+
+/**
+ * Matcher that boolean nots the actual.
+ */
+jasmine.Matchers.prototype.toBeFalsy = function() {
+  return !this.actual;
+};
+
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called.
+ */
+jasmine.Matchers.prototype.toHaveBeenCalled = function() {
+  if (arguments.length > 0) {
+    throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
+  }
+
+  if (!jasmine.isSpy(this.actual)) {
+    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+  }
+
+  this.message = function() {
+    return [
+      "Expected spy " + this.actual.identity + " to have been called.",
+      "Expected spy " + this.actual.identity + " not to have been called."
+    ];
+  };
+
+  return this.actual.wasCalled;
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
+jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was not called.
+ *
+ * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
+ */
+jasmine.Matchers.prototype.wasNotCalled = function() {
+  if (arguments.length > 0) {
+    throw new Error('wasNotCalled does not take arguments');
+  }
+
+  if (!jasmine.isSpy(this.actual)) {
+    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+  }
+
+  this.message = function() {
+    return [
+      "Expected spy " + this.actual.identity + " to not have been called.",
+      "Expected spy " + this.actual.identity + " to have been called."
+    ];
+  };
+
+  return !this.actual.wasCalled;
+};
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
+ *
+ * @example
+ *
+ */
+jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
+  var expectedArgs = jasmine.util.argsToArray(arguments);
+  if (!jasmine.isSpy(this.actual)) {
+    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+  }
+  this.message = function() {
+    if (this.actual.callCount === 0) {
+      // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
+      return [
+        "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
+        "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
+      ];
+    } else {
+      return [
+        "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
+        "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
+      ];
+    }
+  };
+
+  return this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
+
+/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasNotCalledWith = function() {
+  var expectedArgs = jasmine.util.argsToArray(arguments);
+  if (!jasmine.isSpy(this.actual)) {
+    throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+  }
+
+  this.message = function() {
+    return [
+      "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
+      "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
+    ];
+  };
+
+  return !this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/**
+ * Matcher that checks that the expected item is an element in the actual Array.
+ *
+ * @param {Object} expected
+ */
+jasmine.Matchers.prototype.toContain = function(expected) {
+  return this.env.contains_(this.actual, expected);
+};
+
+/**
+ * Matcher that checks that the expected item is NOT an element in the actual Array.
+ *
+ * @param {Object} expected
+ * @deprecated as of 1.0. Use not.toNotContain() instead.
+ */
+jasmine.Matchers.prototype.toNotContain = function(expected) {
+  return !this.env.contains_(this.actual, expected);
+};
+
+jasmine.Matchers.prototype.toBeLessThan = function(expected) {
+  return this.actual < expected;
+};
+
+jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
+  return this.actual > expected;
+};
+
+/**
+ * Matcher that checks that the expected item is equal to the actual item
+ * up to a given level of decimal precision (default 2).
+ *
+ * @param {Number} expected
+ * @param {Number} precision
+ */
+jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
+  if (!(precision === 0)) {
+    precision = precision || 2;
+  }
+  var multiplier = Math.pow(10, precision);
+  var actual = Math.round(this.actual * multiplier);
+  expected = Math.round(expected * multiplier);
+  return expected == actual;
+};
+
+/**
+ * Matcher that checks that the expected exception was thrown by the actual.
+ *
+ * @param {String} expected
+ */
+jasmine.Matchers.prototype.toThrow = function(expected) {
+  var result = false;
+  var exception;
+  if (typeof this.actual != 'function') {
+    throw new Error('Actual is not a function');
+  }
+  try {
+    this.actual();
+  } catch (e) {
+    exception = e;
+  }
+  if (exception) {
+    result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
+  }
+
+  var not = this.isNot ? "not " : "";
+
+  this.message = function() {
+    if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
+      return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
+    } else {
+      return "Expected function to throw an exception.";
+    }
+  };
+
+  return result;
+};
+
+jasmine.Matchers.Any = function(expectedClass) {
+  this.expectedClass = expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.matches = function(other) {
+  if (this.expectedClass == String) {
+    return typeof other == 'string' || other instanceof String;
+  }
+
+  if (this.expectedClass == Number) {
+    return typeof other == 'number' || other instanceof Number;
+  }
+
+  if (this.expectedClass == Function) {
+    return typeof other == 'function' || other instanceof Function;
+  }
+
+  if (this.expectedClass == Object) {
+    return typeof other == 'object';
+  }
+
+  return other instanceof this.expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.toString = function() {
+  return '<jasmine.any(' + this.expectedClass + ')>';
+};
+
+/**
+ * @constructor
+ */
+jasmine.MultiReporter = function() {
+  this.subReporters_ = [];
+};
+jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
+
+jasmine.MultiReporter.prototype.addReporter = function(reporter) {
+  this.subReporters_.push(reporter);
+};
+
+(function() {
+  var functionNames = [
+    "reportRunnerStarting",
+    "reportRunnerResults",
+    "reportSuiteResults",
+    "reportSpecStarting",
+    "reportSpecResults",
+    "log"
+  ];
+  for (var i = 0; i < functionNames.length; i++) {
+    var functionName = functionNames[i];
+    jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
+      return function() {
+        for (var j = 0; j < this.subReporters_.length; j++) {
+          var subReporter = this.subReporters_[j];
+          if (subReporter[functionName]) {
+            subReporter[functionName].apply(subReporter, arguments);
+          }
+        }
+      };
+    })(functionName);
+  }
+})();
+/**
+ * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
+ *
+ * @constructor
+ */
+jasmine.NestedResults = function() {
+  /**
+   * The total count of results
+   */
+  this.totalCount = 0;
+  /**
+   * Number of passed results
+   */
+  this.passedCount = 0;
+  /**
+   * Number of failed results
+   */
+  this.failedCount = 0;
+  /**
+   * Was this suite/spec skipped?
+   */
+  this.skipped = false;
+  /**
+   * @ignore
+   */
+  this.items_ = [];
+};
+
+/**
+ * Roll up the result counts.
+ *
+ * @param result
+ */
+jasmine.NestedResults.prototype.rollupCounts = function(result) {
+  this.totalCount += result.totalCount;
+  this.passedCount += result.passedCount;
+  this.failedCount += result.failedCount;
+};
+
+/**
+ * Adds a log message.
+ * @param values Array of message parts which will be concatenated later.
+ */
+jasmine.NestedResults.prototype.log = function(values) {
+  this.items_.push(new jasmine.MessageResult(values));
+};
+
+/**
+ * Getter for the results: message & results.
+ */
+jasmine.NestedResults.prototype.getItems = function() {
+  return this.items_;
+};
+
+/**
+ * Adds a result, tracking counts (total, passed, & failed)
+ * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
+ */
+jasmine.NestedResults.prototype.addResult = function(result) {
+  if (result.type != 'log') {
+    if (result.items_) {
+      this.rollupCounts(result);
+    } else {
+      this.totalCount++;
+      if (result.passed()) {
+        this.passedCount++;
+      } else {
+        this.failedCount++;
+      }
+    }
+  }
+  this.items_.push(result);
+};
+
+/**
+ * @returns {Boolean} True if <b>everything</b> below passed
+ */
+jasmine.NestedResults.prototype.passed = function() {
+  return this.passedCount === this.totalCount;
+};
+/**
+ * Base class for pretty printing for expectation results.
+ */
+jasmine.PrettyPrinter = function() {
+  this.ppNestLevel_ = 0;
+};
+
+/**
+ * Formats a value in a nice, human-readable string.
+ *
+ * @param value
+ */
+jasmine.PrettyPrinter.prototype.format = function(value) {
+  if (this.ppNestLevel_ > 40) {
+    throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
+  }
+
+  this.ppNestLevel_++;
+  try {
+    if (value === jasmine.undefined) {
+      this.emitScalar('undefined');
+    } else if (value === null) {
+      this.emitScalar('null');
+    } else if (value === jasmine.getGlobal()) {
+      this.emitScalar('<global>');
+    } else if (value instanceof jasmine.Matchers.Any) {
+      this.emitScalar(value.toString());
+    } else if (typeof value === 'string') {
+      this.emitString(value);
+    } else if (jasmine.isSpy(value)) {
+      this.emitScalar("spy on " + value.identity);
+    } else if (value instanceof RegExp) {
+      this.emitScalar(value.toString());
+    } else if (typeof value === 'function') {
+      this.emitScalar('Function');
+    } else if (typeof value.nodeType === 'number') {
+      this.emitScalar('HTMLNode');
+    } else if (value instanceof Date) {
+      this.emitScalar('Date(' + value + ')');
+    } else if (value.__Jasmine_been_here_before__) {
+      this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
+    } else if (jasmine.isArray_(value) || typeof value == 'object') {
+      value.__Jasmine_been_here_before__ = true;
+      if (jasmine.isArray_(value)) {
+        this.emitArray(value);
+      } else {
+        this.emitObject(value);
+      }
+      delete value.__Jasmine_been_here_before__;
+    } else {
+      this.emitScalar(value.toString());
+    }
+  } finally {
+    this.ppNestLevel_--;
+  }
+};
+
+jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+  for (var property in obj) {
+    if (property == '__Jasmine_been_here_before__') continue;
+    fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && 
+                                         obj.__lookupGetter__(property) !== null) : false);
+  }
+};
+
+jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
+
+jasmine.StringPrettyPrinter = function() {
+  jasmine.PrettyPrinter.call(this);
+
+  this.string = '';
+};
+jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
+
+jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
+  this.append(value);
+};
+
+jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
+  this.append("'" + value + "'");
+};
+
+jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
+  this.append('[ ');
+  for (var i = 0; i < array.length; i++) {
+    if (i > 0) {
+      this.append(', ');
+    }
+    this.format(array[i]);
+  }
+  this.append(' ]');
+};
+
+jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
+  var self = this;
+  this.append('{ ');
+  var first = true;
+
+  this.iterateObject(obj, function(property, isGetter) {
+    if (first) {
+      first = false;
+    } else {
+      self.append(', ');
+    }
+
+    self.append(property);
+    self.append(' : ');
+    if (isGetter) {
+      self.append('<getter>');
+    } else {
+      self.format(obj[property]);
+    }
+  });
+
+  this.append(' }');
+};
+
+jasmine.StringPrettyPrinter.prototype.append = function(value) {
+  this.string += value;
+};
+jasmine.Queue = function(env) {
+  this.env = env;
+  this.blocks = [];
+  this.running = false;
+  this.index = 0;
+  this.offset = 0;
+  this.abort = false;
+};
+
+jasmine.Queue.prototype.addBefore = function(block) {
+  this.blocks.unshift(block);
+};
+
+jasmine.Queue.prototype.add = function(block) {
+  this.blocks.push(block);
+};
+
+jasmine.Queue.prototype.insertNext = function(block) {
+  this.blocks.splice((this.index + this.offset + 1), 0, block);
+  this.offset++;
+};
+
+jasmine.Queue.prototype.start = function(onComplete) {
+  this.running = true;
+  this.onComplete = onComplete;
+  this.next_();
+};
+
+jasmine.Queue.prototype.isRunning = function() {
+  return this.running;
+};
+
+jasmine.Queue.LOOP_DONT_RECURSE = true;
+
+jasmine.Queue.prototype.next_ = function() {
+  var self = this;
+  var goAgain = true;
+
+  while (goAgain) {
+    goAgain = false;
+    
+    if (self.index < self.blocks.length && !this.abort) {
+      var calledSynchronously = true;
+      var completedSynchronously = false;
+
+      var onComplete = function () {
+        if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
+          completedSynchronously = true;
+          return;
+        }
+
+        if (self.blocks[self.index].abort) {
+          self.abort = true;
+        }
+
+        self.offset = 0;
+        self.index++;
+
+        var now = new Date().getTime();
+        if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
+          self.env.lastUpdate = now;
+          self.env.setTimeout(function() {
+            self.next_();
+          }, 0);
+        } else {
+          if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
+            goAgain = true;
+          } else {
+            self.next_();
+          }
+        }
+      };
+      self.blocks[self.index].execute(onComplete);
+
+      calledSynchronously = false;
+      if (completedSynchronously) {
+        onComplete();
+      }
+      
+    } else {
+      self.running = false;
+      if (self.onComplete) {
+        self.onComplete();
+      }
+    }
+  }
+};
+
+jasmine.Queue.prototype.results = function() {
+  var results = new jasmine.NestedResults();
+  for (var i = 0; i < this.blocks.length; i++) {
+    if (this.blocks[i].results) {
+      results.addResult(this.blocks[i].results());
+    }
+  }
+  return results;
+};
+
+
+/**
+ * Runner
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ */
+jasmine.Runner = function(env) {
+  var self = this;
+  self.env = env;
+  self.queue = new jasmine.Queue(env);
+  self.before_ = [];
+  self.after_ = [];
+  self.suites_ = [];
+};
+
+jasmine.Runner.prototype.execute = function() {
+  var self = this;
+  if (self.env.reporter.reportRunnerStarting) {
+    self.env.reporter.reportRunnerStarting(this);
+  }
+  self.queue.start(function () {
+    self.finishCallback();
+  });
+};
+
+jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
+  beforeEachFunction.typeName = 'beforeEach';
+  this.before_.splice(0,0,beforeEachFunction);
+};
+
+jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
+  afterEachFunction.typeName = 'afterEach';
+  this.after_.splice(0,0,afterEachFunction);
+};
+
+
+jasmine.Runner.prototype.finishCallback = function() {
+  this.env.reporter.reportRunnerResults(this);
+};
+
+jasmine.Runner.prototype.addSuite = function(suite) {
+  this.suites_.push(suite);
+};
+
+jasmine.Runner.prototype.add = function(block) {
+  if (block instanceof jasmine.Suite) {
+    this.addSuite(block);
+  }
+  this.queue.add(block);
+};
+
+jasmine.Runner.prototype.specs = function () {
+  var suites = this.suites();
+  var specs = [];
+  for (var i = 0; i < suites.length; i++) {
+    specs = specs.concat(suites[i].specs());
+  }
+  return specs;
+};
+
+jasmine.Runner.prototype.suites = function() {
+  return this.suites_;
+};
+
+jasmine.Runner.prototype.topLevelSuites = function() {
+  var topLevelSuites = [];
+  for (var i = 0; i < this.suites_.length; i++) {
+    if (!this.suites_[i].parentSuite) {
+      topLevelSuites.push(this.suites_[i]);
+    }
+  }
+  return topLevelSuites;
+};
+
+jasmine.Runner.prototype.results = function() {
+  return this.queue.results();
+};
+/**
+ * Internal representation of a Jasmine specification, or test.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {jasmine.Suite} suite
+ * @param {String} description
+ */
+jasmine.Spec = function(env, suite, description) {
+  if (!env) {
+    throw new Error('jasmine.Env() required');
+  }
+  if (!suite) {
+    throw new Error('jasmine.Suite() required');
+  }
+  var spec = this;
+  spec.id = env.nextSpecId ? env.nextSpecId() : null;
+  spec.env = env;
+  spec.suite = suite;
+  spec.description = description;
+  spec.queue = new jasmine.Queue(env);
+
+  spec.afterCallbacks = [];
+  spec.spies_ = [];
+
+  spec.results_ = new jasmine.NestedResults();
+  spec.results_.description = description;
+  spec.matchersClass = null;
+};
+
+jasmine.Spec.prototype.getFullName = function() {
+  return this.suite.getFullName() + ' ' + this.description + '.';
+};
+
+
+jasmine.Spec.prototype.results = function() {
+  return this.results_;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the spec's output.
+ *
+ * Be careful not to leave calls to <code>jasmine.log</code> in production code.
+ */
+jasmine.Spec.prototype.log = function() {
+  return this.results_.log(arguments);
+};
+
+jasmine.Spec.prototype.runs = function (func) {
+  var block = new jasmine.Block(this.env, func, this);
+  this.addToQueue(block);
+  return this;
+};
+
+jasmine.Spec.prototype.addToQueue = function (block) {
+  if (this.queue.isRunning()) {
+    this.queue.insertNext(block);
+  } else {
+    this.queue.add(block);
+  }
+};
+
+/**
+ * @param {jasmine.ExpectationResult} result
+ */
+jasmine.Spec.prototype.addMatcherResult = function(result) {
+  this.results_.addResult(result);
+};
+
+jasmine.Spec.prototype.expect = function(actual) {
+  var positive = new (this.getMatchersClass_())(this.env, actual, this);
+  positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
+  return positive;
+};
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+jasmine.Spec.prototype.waits = function(timeout) {
+  var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
+  this.addToQueue(waitsFunc);
+  return this;
+};
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+  var latchFunction_ = null;
+  var optional_timeoutMessage_ = null;
+  var optional_timeout_ = null;
+
+  for (var i = 0; i < arguments.length; i++) {
+    var arg = arguments[i];
+    switch (typeof arg) {
+      case 'function':
+        latchFunction_ = arg;
+        break;
+      case 'string':
+        optional_timeoutMessage_ = arg;
+        break;
+      case 'number':
+        optional_timeout_ = arg;
+        break;
+    }
+  }
+
+  var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
+  this.addToQueue(waitsForFunc);
+  return this;
+};
+
+jasmine.Spec.prototype.fail = function (e) {
+  var expectationResult = new jasmine.ExpectationResult({
+    passed: false,
+    message: e ? jasmine.util.formatException(e) : 'Exception',
+    trace: { stack: e.stack }
+  });
+  this.results_.addResult(expectationResult);
+};
+
+jasmine.Spec.prototype.getMatchersClass_ = function() {
+  return this.matchersClass || this.env.matchersClass;
+};
+
+jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
+  var parent = this.getMatchersClass_();
+  var newMatchersClass = function() {
+    parent.apply(this, arguments);
+  };
+  jasmine.util.inherit(newMatchersClass, parent);
+  jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
+  this.matchersClass = newMatchersClass;
+};
+
+jasmine.Spec.prototype.finishCallback = function() {
+  this.env.reporter.reportSpecResults(this);
+};
+
+jasmine.Spec.prototype.finish = function(onComplete) {
+  this.removeAllSpies();
+  this.finishCallback();
+  if (onComplete) {
+    onComplete();
+  }
+};
+
+jasmine.Spec.prototype.after = function(doAfter) {
+  if (this.queue.isRunning()) {
+    this.queue.add(new jasmine.Block(this.env, doAfter, this));
+  } else {
+    this.afterCallbacks.unshift(doAfter);
+  }
+};
+
+jasmine.Spec.prototype.execute = function(onComplete) {
+  var spec = this;
+  if (!spec.env.specFilter(spec)) {
+    spec.results_.skipped = true;
+    spec.finish(onComplete);
+    return;
+  }
+
+  this.env.reporter.reportSpecStarting(this);
+
+  spec.env.currentSpec = spec;
+
+  spec.addBeforesAndAftersToQueue();
+
+  spec.queue.start(function () {
+    spec.finish(onComplete);
+  });
+};
+
+jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
+  var runner = this.env.currentRunner();
+  var i;
+
+  for (var suite = this.suite; suite; suite = suite.parentSuite) {
+    for (i = 0; i < suite.before_.length; i++) {
+      this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
+    }
+  }
+  for (i = 0; i < runner.before_.length; i++) {
+    this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
+  }
+  for (i = 0; i < this.afterCallbacks.length; i++) {
+    this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
+  }
+  for (suite = this.suite; suite; suite = suite.parentSuite) {
+    for (i = 0; i < suite.after_.length; i++) {
+      this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
+    }
+  }
+  for (i = 0; i < runner.after_.length; i++) {
+    this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
+  }
+};
+
+jasmine.Spec.prototype.explodes = function() {
+  throw 'explodes function should not have been called';
+};
+
+jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
+  if (obj == jasmine.undefined) {
+    throw "spyOn could not find an object to spy upon for " + methodName + "()";
+  }
+
+  if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
+    throw methodName + '() method does not exist';
+  }
+
+  if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
+    throw new Error(methodName + ' has already been spied upon');
+  }
+
+  var spyObj = jasmine.createSpy(methodName);
+
+  this.spies_.push(spyObj);
+  spyObj.baseObj = obj;
+  spyObj.methodName = methodName;
+  spyObj.originalValue = obj[methodName];
+
+  obj[methodName] = spyObj;
+
+  return spyObj;
+};
+
+jasmine.Spec.prototype.removeAllSpies = function() {
+  for (var i = 0; i < this.spies_.length; i++) {
+    var spy = this.spies_[i];
+    spy.baseObj[spy.methodName] = spy.originalValue;
+  }
+  this.spies_ = [];
+};
+
+/**
+ * Internal representation of a Jasmine suite.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {String} description
+ * @param {Function} specDefinitions
+ * @param {jasmine.Suite} parentSuite
+ */
+jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
+  var self = this;
+  self.id = env.nextSuiteId ? env.nextSuiteId() : null;
+  self.description = description;
+  self.queue = new jasmine.Queue(env);
+  self.parentSuite = parentSuite;
+  self.env = env;
+  self.before_ = [];
+  self.after_ = [];
+  self.children_ = [];
+  self.suites_ = [];
+  self.specs_ = [];
+};
+
+jasmine.Suite.prototype.getFullName = function() {
+  var fullName = this.description;
+  for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+    fullName = parentSuite.description + ' ' + fullName;
+  }
+  return fullName;
+};
+
+jasmine.Suite.prototype.finish = function(onComplete) {
+  this.env.reporter.reportSuiteResults(this);
+  this.finished = true;
+  if (typeof(onComplete) == 'function') {
+    onComplete();
+  }
+};
+
+jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
+  beforeEachFunction.typeName = 'beforeEach';
+  this.before_.unshift(beforeEachFunction);
+};
+
+jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
+  afterEachFunction.typeName = 'afterEach';
+  this.after_.unshift(afterEachFunction);
+};
+
+jasmine.Suite.prototype.results = function() {
+  return this.queue.results();
+};
+
+jasmine.Suite.prototype.add = function(suiteOrSpec) {
+  this.children_.push(suiteOrSpec);
+  if (suiteOrSpec instanceof jasmine.Suite) {
+    this.suites_.push(suiteOrSpec);
+    this.env.currentRunner().addSuite(suiteOrSpec);
+  } else {
+    this.specs_.push(suiteOrSpec);
+  }
+  this.queue.add(suiteOrSpec);
+};
+
+jasmine.Suite.prototype.specs = function() {
+  return this.specs_;
+};
+
+jasmine.Suite.prototype.suites = function() {
+  return this.suites_;
+};
+
+jasmine.Suite.prototype.children = function() {
+  return this.children_;
+};
+
+jasmine.Suite.prototype.execute = function(onComplete) {
+  var self = this;
+  this.queue.start(function () {
+    self.finish(onComplete);
+  });
+};
+jasmine.WaitsBlock = function(env, timeout, spec) {
+  this.timeout = timeout;
+  jasmine.Block.call(this, env, null, spec);
+};
+
+jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
+
+jasmine.WaitsBlock.prototype.execute = function (onComplete) {
+  if (jasmine.VERBOSE) {
+    this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
+  }
+  this.env.setTimeout(function () {
+    onComplete();
+  }, this.timeout);
+};
+/**
+ * A block which waits for some condition to become true, with timeout.
+ *
+ * @constructor
+ * @extends jasmine.Block
+ * @param {jasmine.Env} env The Jasmine environment.
+ * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
+ * @param {Function} latchFunction A function which returns true when the desired condition has been met.
+ * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
+ * @param {jasmine.Spec} spec The Jasmine spec.
+ */
+jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
+  this.timeout = timeout || env.defaultTimeoutInterval;
+  this.latchFunction = latchFunction;
+  this.message = message;
+  this.totalTimeSpentWaitingForLatch = 0;
+  jasmine.Block.call(this, env, null, spec);
+};
+jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
+
+jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
+
+jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
+  if (jasmine.VERBOSE) {
+    this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
+  }
+  var latchFunctionResult;
+  try {
+    latchFunctionResult = this.latchFunction.apply(this.spec);
+  } catch (e) {
+    this.spec.fail(e);
+    onComplete();
+    return;
+  }
+
+  if (latchFunctionResult) {
+    onComplete();
+  } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
+    var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
+    this.spec.fail({
+      name: 'timeout',
+      message: message
+    });
+
+    this.abort = true;
+    onComplete();
+  } else {
+    this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
+    var self = this;
+    this.env.setTimeout(function() {
+      self.execute(onComplete);
+    }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
+  }
+};
+// Mock setTimeout, clearTimeout
+// Contributed by Pivotal Computer Systems, www.pivotalsf.com
+
+jasmine.FakeTimer = function() {
+  this.reset();
+
+  var self = this;
+  self.setTimeout = function(funcToCall, millis) {
+    self.timeoutsMade++;
+    self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
+    return self.timeoutsMade;
+  };
+
+  self.setInterval = function(funcToCall, millis) {
+    self.timeoutsMade++;
+    self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
+    return self.timeoutsMade;
+  };
+
+  self.clearTimeout = function(timeoutKey) {
+    self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+  };
+
+  self.clearInterval = function(timeoutKey) {
+    self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+  };
+
+};
+
+jasmine.FakeTimer.prototype.reset = function() {
+  this.timeoutsMade = 0;
+  this.scheduledFunctions = {};
+  this.nowMillis = 0;
+};
+
+jasmine.FakeTimer.prototype.tick = function(millis) {
+  var oldMillis = this.nowMillis;
+  var newMillis = oldMillis + millis;
+  this.runFunctionsWithinRange(oldMillis, newMillis);
+  this.nowMillis = newMillis;
+};
+
+jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
+  var scheduledFunc;
+  var funcsToRun = [];
+  for (var timeoutKey in this.scheduledFunctions) {
+    scheduledFunc = this.scheduledFunctions[timeoutKey];
+    if (scheduledFunc != jasmine.undefined &&
+        scheduledFunc.runAtMillis >= oldMillis &&
+        scheduledFunc.runAtMillis <= nowMillis) {
+      funcsToRun.push(scheduledFunc);
+      this.scheduledFunctions[timeoutKey] = jasmine.undefined;
+    }
+  }
+
+  if (funcsToRun.length > 0) {
+    funcsToRun.sort(function(a, b) {
+      return a.runAtMillis - b.runAtMillis;
+    });
+    for (var i = 0; i < funcsToRun.length; ++i) {
+      try {
+        var funcToRun = funcsToRun[i];
+        this.nowMillis = funcToRun.runAtMillis;
+        funcToRun.funcToCall();
+        if (funcToRun.recurring) {
+          this.scheduleFunction(funcToRun.timeoutKey,
+              funcToRun.funcToCall,
+              funcToRun.millis,
+              true);
+        }
+      } catch(e) {
+      }
+    }
+    this.runFunctionsWithinRange(oldMillis, nowMillis);
+  }
+};
+
+jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
+  this.scheduledFunctions[timeoutKey] = {
+    runAtMillis: this.nowMillis + millis,
+    funcToCall: funcToCall,
+    recurring: recurring,
+    timeoutKey: timeoutKey,
+    millis: millis
+  };
+};
+
+/**
+ * @namespace
+ */
+jasmine.Clock = {
+  defaultFakeTimer: new jasmine.FakeTimer(),
+
+  reset: function() {
+    jasmine.Clock.assertInstalled();
+    jasmine.Clock.defaultFakeTimer.reset();
+  },
+
+  tick: function(millis) {
+    jasmine.Clock.assertInstalled();
+    jasmine.Clock.defaultFakeTimer.tick(millis);
+  },
+
+  runFunctionsWithinRange: function(oldMillis, nowMillis) {
+    jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
+  },
+
+  scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
+    jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
+  },
+
+  useMock: function() {
+    if (!jasmine.Clock.isInstalled()) {
+      var spec = jasmine.getEnv().currentSpec;
+      spec.after(jasmine.Clock.uninstallMock);
+
+      jasmine.Clock.installMock();
+    }
+  },
+
+  installMock: function() {
+    jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
+  },
+
+  uninstallMock: function() {
+    jasmine.Clock.assertInstalled();
+    jasmine.Clock.installed = jasmine.Clock.real;
+  },
+
+  real: {
+    setTimeout: jasmine.getGlobal().setTimeout,
+    clearTimeout: jasmine.getGlobal().clearTimeout,
+    setInterval: jasmine.getGlobal().setInterval,
+    clearInterval: jasmine.getGlobal().clearInterval
+  },
+
+  assertInstalled: function() {
+    if (!jasmine.Clock.isInstalled()) {
+      throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
+    }
+  },
+
+  isInstalled: function() {
+    return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
+  },
+
+  installed: null
+};
+jasmine.Clock.installed = jasmine.Clock.real;
+
+//else for IE support
+jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
+  if (jasmine.Clock.installed.setTimeout.apply) {
+    return jasmine.Clock.installed.setTimeout.apply(this, arguments);
+  } else {
+    return jasmine.Clock.installed.setTimeout(funcToCall, millis);
+  }
+};
+
+jasmine.getGlobal().setInterval = function(funcToCall, millis) {
+  if (jasmine.Clock.installed.setInterval.apply) {
+    return jasmine.Clock.installed.setInterval.apply(this, arguments);
+  } else {
+    return jasmine.Clock.installed.setInterval(funcToCall, millis);
+  }
+};
+
+jasmine.getGlobal().clearTimeout = function(timeoutKey) {
+  if (jasmine.Clock.installed.clearTimeout.apply) {
+    return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
+  } else {
+    return jasmine.Clock.installed.clearTimeout(timeoutKey);
+  }
+};
+
+jasmine.getGlobal().clearInterval = function(timeoutKey) {
+  if (jasmine.Clock.installed.clearTimeout.apply) {
+    return jasmine.Clock.installed.clearInterval.apply(this, arguments);
+  } else {
+    return jasmine.Clock.installed.clearInterval(timeoutKey);
+  }
+};
+
+jasmine.version_= {
+  "major": 1,
+  "minor": 1,
+  "build": 0,
+  "revision": 1315677058
+};
Index: trunk/prototype/plugins/jq-raty/test/lib/jasmine-html.js
===================================================================
--- trunk/prototype/plugins/jq-raty/test/lib/jasmine-html.js	(revision 6528)
+++ trunk/prototype/plugins/jq-raty/test/lib/jasmine-html.js	(revision 6528)
@@ -0,0 +1,190 @@
+jasmine.TrivialReporter = function(doc) {
+  this.document = doc || document;
+  this.suiteDivs = {};
+  this.logRunningSpecs = false;
+};
+
+jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
+  var el = document.createElement(type);
+
+  for (var i = 2; i < arguments.length; i++) {
+    var child = arguments[i];
+
+    if (typeof child === 'string') {
+      el.appendChild(document.createTextNode(child));
+    } else {
+      if (child) { el.appendChild(child); }
+    }
+  }
+
+  for (var attr in attrs) {
+    if (attr == "className") {
+      el[attr] = attrs[attr];
+    } else {
+      el.setAttribute(attr, attrs[attr]);
+    }
+  }
+
+  return el;
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
+  var showPassed, showSkipped;
+
+  this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
+      this.createDom('div', { className: 'banner' },
+        this.createDom('div', { className: 'logo' },
+            this.createDom('span', { className: 'title' }, "Jasmine"),
+            this.createDom('span', { className: 'version' }, runner.env.versionString())),
+        this.createDom('div', { className: 'options' },
+            "Show ",
+            showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
+            this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
+            showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
+            this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
+            )
+          ),
+
+      this.runnerDiv = this.createDom('div', { className: 'runner running' },
+          this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
+          this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
+          this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
+      );
+
+  this.document.body.appendChild(this.outerDiv);
+
+  var suites = runner.suites();
+  for (var i = 0; i < suites.length; i++) {
+    var suite = suites[i];
+    var suiteDiv = this.createDom('div', { className: 'suite' },
+        this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
+        this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
+    this.suiteDivs[suite.id] = suiteDiv;
+    var parentDiv = this.outerDiv;
+    if (suite.parentSuite) {
+      parentDiv = this.suiteDivs[suite.parentSuite.id];
+    }
+    parentDiv.appendChild(suiteDiv);
+  }
+
+  this.startedAt = new Date();
+
+  var self = this;
+  showPassed.onclick = function(evt) {
+    if (showPassed.checked) {
+      self.outerDiv.className += ' show-passed';
+    } else {
+      self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
+    }
+  };
+
+  showSkipped.onclick = function(evt) {
+    if (showSkipped.checked) {
+      self.outerDiv.className += ' show-skipped';
+    } else {
+      self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
+    }
+  };
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
+  var results = runner.results();
+  var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
+  this.runnerDiv.setAttribute("class", className);
+  //do it twice for IE
+  this.runnerDiv.setAttribute("className", className);
+  var specs = runner.specs();
+  var specCount = 0;
+  for (var i = 0; i < specs.length; i++) {
+    if (this.specFilter(specs[i])) {
+      specCount++;
+    }
+  }
+  var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
+  message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
+  this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
+
+  this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
+};
+
+jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
+  var results = suite.results();
+  var status = results.passed() ? 'passed' : 'failed';
+  if (results.totalCount === 0) { // todo: change this to check results.skipped
+    status = 'skipped';
+  }
+  this.suiteDivs[suite.id].className += " " + status;
+};
+
+jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
+  if (this.logRunningSpecs) {
+    this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+  }
+};
+
+jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
+  var results = spec.results();
+  var status = results.passed() ? 'passed' : 'failed';
+  if (results.skipped) {
+    status = 'skipped';
+  }
+  var specDiv = this.createDom('div', { className: 'spec '  + status },
+      this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
+      this.createDom('a', {
+        className: 'description',
+        href: '?spec=' + encodeURIComponent(spec.getFullName()),
+        title: spec.getFullName()
+      }, spec.description));
+
+
+  var resultItems = results.getItems();
+  var messagesDiv = this.createDom('div', { className: 'messages' });
+  for (var i = 0; i < resultItems.length; i++) {
+    var result = resultItems[i];
+
+    if (result.type == 'log') {
+      messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+    } else if (result.type == 'expect' && result.passed && !result.passed()) {
+      messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+      if (result.trace.stack) {
+        messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+      }
+    }
+  }
+
+  if (messagesDiv.childNodes.length > 0) {
+    specDiv.appendChild(messagesDiv);
+  }
+
+  this.suiteDivs[spec.suite.id].appendChild(specDiv);
+};
+
+jasmine.TrivialReporter.prototype.log = function() {
+  var console = jasmine.getGlobal().console;
+  if (console && console.log) {
+    if (console.log.apply) {
+      console.log.apply(console, arguments);
+    } else {
+      console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+    }
+  }
+};
+
+jasmine.TrivialReporter.prototype.getLocation = function() {
+  return this.document.location;
+};
+
+jasmine.TrivialReporter.prototype.specFilter = function(spec) {
+  var paramMap = {};
+  var params = this.getLocation().search.substring(1).split('&');
+  for (var i = 0; i < params.length; i++) {
+    var p = params[i].split('=');
+    paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+  }
+
+  if (!paramMap.spec) {
+    return true;
+  }
+  return spec.getFullName().indexOf(paramMap.spec) === 0;
+};
Index: trunk/prototype/plugins/jq-raty/test/lib/jasmine.css
===================================================================
--- trunk/prototype/plugins/jq-raty/test/lib/jasmine.css	(revision 6528)
+++ trunk/prototype/plugins/jq-raty/test/lib/jasmine.css	(revision 6528)
@@ -0,0 +1,166 @@
+body {
+  font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
+}
+
+
+.jasmine_reporter a:visited, .jasmine_reporter a {
+  color: #303; 
+}
+
+.jasmine_reporter a:hover, .jasmine_reporter a:active {
+  color: blue; 
+}
+
+.run_spec {
+  float:right;
+  padding-right: 5px;
+  font-size: .8em;
+  text-decoration: none;
+}
+
+.jasmine_reporter {
+  margin: 0 5px;
+}
+
+.banner {
+  color: #303;
+  background-color: #fef;
+  padding: 5px;
+}
+
+.logo {
+  float: left;
+  font-size: 1.1em;
+  padding-left: 5px;
+}
+
+.logo .version {
+  font-size: .6em;
+  padding-left: 1em;
+}
+
+.runner.running {
+  background-color: yellow;
+}
+
+
+.options {
+  text-align: right;
+  font-size: .8em;
+}
+
+
+
+
+.suite {
+  border: 1px outset gray;
+  margin: 5px 0;
+  padding-left: 1em;
+}
+
+.suite .suite {
+  margin: 5px; 
+}
+
+.suite.passed {
+  background-color: #dfd;
+}
+
+.suite.failed {
+  background-color: #fdd;
+}
+
+.spec {
+  margin: 5px;
+  padding-left: 1em;
+  clear: both;
+}
+
+.spec.failed, .spec.passed, .spec.skipped {
+  padding-bottom: 5px;
+  border: 1px solid gray;
+}
+
+.spec.failed {
+  background-color: #fbb;
+  border-color: red;
+}
+
+.spec.passed {
+  background-color: #bfb;
+  border-color: green;
+}
+
+.spec.skipped {
+  background-color: #bbb;
+}
+
+.messages {
+  border-left: 1px dashed gray;
+  padding-left: 1em;
+  padding-right: 1em;
+}
+
+.passed {
+  background-color: #cfc;
+  display: none;
+}
+
+.failed {
+  background-color: #fbb;
+}
+
+.skipped {
+  color: #777;
+  background-color: #eee;
+  display: none;
+}
+
+
+/*.resultMessage {*/
+  /*white-space: pre;*/
+/*}*/
+
+.resultMessage span.result {
+  display: block;
+  line-height: 2em;
+  color: black;
+}
+
+.resultMessage .mismatch {
+  color: black;
+}
+
+.stackTrace {
+  white-space: pre;
+  font-size: .8em;
+  margin-left: 10px;
+  max-height: 5em;
+  overflow: auto;
+  border: 1px inset red;
+  padding: 1em;
+  background: #eef;
+}
+
+.finished-at {
+  padding-left: 1em;
+  font-size: .6em;
+}
+
+.show-passed .passed,
+.show-skipped .skipped {
+  display: block;
+}
+
+
+#jasmine_content {
+  position:fixed;
+  right: 100%;
+}
+
+.runner {
+  border: 1px solid gray;
+  display: block;
+  margin: 5px 0;
+  padding: 2px 0 2px 10px;
+}
Index: trunk/prototype/plugins/jq-raty/test/lib/jasmine-jquery.js
===================================================================
--- trunk/prototype/plugins/jq-raty/test/lib/jasmine-jquery.js	(revision 6528)
+++ trunk/prototype/plugins/jq-raty/test/lib/jasmine-jquery.js	(revision 6528)
@@ -0,0 +1,288 @@
+var readFixtures = function() {
+  return jasmine.getFixtures().proxyCallTo_('read', arguments);
+};
+
+var preloadFixtures = function() {
+  jasmine.getFixtures().proxyCallTo_('preload', arguments);
+};
+
+var loadFixtures = function() {
+  jasmine.getFixtures().proxyCallTo_('load', arguments);
+};
+
+var setFixtures = function(html) {
+  jasmine.getFixtures().set(html);
+};
+
+var sandbox = function(attributes) {
+  return jasmine.getFixtures().sandbox(attributes);
+};
+
+var spyOnEvent = function(selector, eventName) {
+  jasmine.JQuery.events.spyOn(selector, eventName);
+}
+
+jasmine.getFixtures = function() {
+  return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures();
+};
+
+jasmine.Fixtures = function() {
+  this.containerId = 'jasmine-fixtures';
+  this.fixturesCache_ = {};
+  this.fixturesPath = 'spec/javascripts/fixtures';
+};
+
+jasmine.Fixtures.prototype.set = function(html) {
+  this.cleanUp();
+  this.createContainer_(html);
+};
+
+jasmine.Fixtures.prototype.preload = function() {
+  this.read.apply(this, arguments);
+};
+
+jasmine.Fixtures.prototype.load = function() {
+  this.cleanUp();
+  this.createContainer_(this.read.apply(this, arguments));
+};
+
+jasmine.Fixtures.prototype.read = function() {
+  var htmlChunks = [];
+
+  var fixtureUrls = arguments;
+  for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
+    htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]));
+  }
+
+  return htmlChunks.join('');
+};
+
+jasmine.Fixtures.prototype.clearCache = function() {
+  this.fixturesCache_ = {};
+};
+
+jasmine.Fixtures.prototype.cleanUp = function() {
+  jQuery('#' + this.containerId).remove();
+};
+
+jasmine.Fixtures.prototype.sandbox = function(attributes) {
+  var attributesToSet = attributes || {};
+  return jQuery('<div id="sandbox" />').attr(attributesToSet);
+};
+
+jasmine.Fixtures.prototype.createContainer_ = function(html) {
+  var container;
+  if(html instanceof jQuery) {
+    container = jQuery('<div id="' + this.containerId + '" />');
+    container.html(html);
+  } else {
+    container = '<div id="' + this.containerId + '">' + html + '</div>'
+  }
+  jQuery('body').append(container);
+};
+
+jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) {  
+  if (typeof this.fixturesCache_[url] == 'undefined') {
+    this.loadFixtureIntoCache_(url);
+  }
+  return this.fixturesCache_[url];
+};
+
+jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
+  var self = this;
+  var url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl;
+  jQuery.ajax({
+    async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
+    cache: false,
+    dataType: 'html',
+    url: url,
+    success: function(data) {
+      self.fixturesCache_[relativeUrl] = data;
+    },
+    error: function(jqXHR, status, errorThrown) {
+        throw Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')');
+    }
+  });
+};
+
+jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
+  return this[methodName].apply(this, passedArguments);
+};
+
+
+jasmine.JQuery = function() {};
+
+jasmine.JQuery.browserTagCaseIndependentHtml = function(html) {
+  return jQuery('<div/>').append(html).html();
+};
+
+jasmine.JQuery.elementToString = function(element) {
+  return jQuery('<div />').append(element.clone()).html();
+};
+
+jasmine.JQuery.matchersClass = {};
+
+(function(namespace) {
+  var data = {
+    spiedEvents: {},
+    handlers:    []
+  };
+
+  namespace.events = {
+    spyOn: function(selector, eventName) {
+      var handler = function(e) {
+        data.spiedEvents[[selector, eventName]] = e;
+      };
+      jQuery(selector).bind(eventName, handler);
+      data.handlers.push(handler);
+    },
+
+    wasTriggered: function(selector, eventName) {
+      return !!(data.spiedEvents[[selector, eventName]]);
+    },
+
+    cleanUp: function() {
+      data.spiedEvents = {};
+      data.handlers    = [];
+    }
+  }
+})(jasmine.JQuery);
+
+(function(){
+  var jQueryMatchers = {
+    toHaveClass: function(className) {
+      return this.actual.hasClass(className);
+    },
+
+    toBeVisible: function() {
+      return this.actual.is(':visible');
+    },
+
+    toBeHidden: function() {
+      return this.actual.is(':hidden');
+    },
+
+    toBeSelected: function() {
+      return this.actual.is(':selected');
+    },
+
+    toBeChecked: function() {
+      return this.actual.is(':checked');
+    },
+
+    toBeEmpty: function() {
+      return this.actual.is(':empty');
+    },
+
+    toExist: function() {
+      return this.actual.size() > 0;
+    },
+
+    toHaveAttr: function(attributeName, expectedAttributeValue) {
+      return hasProperty(this.actual.attr(attributeName), expectedAttributeValue);
+    },
+
+    toHaveId: function(id) {
+      return this.actual.attr('id') == id;
+    },
+
+    toHaveHtml: function(html) {
+      return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html);
+    },
+
+    toHaveText: function(text) {
+      if (text && jQuery.isFunction(text.test)) {
+        return text.test(this.actual.text());
+      } else {
+        return this.actual.text() == text;
+      }
+    },
+
+    toHaveValue: function(value) {
+      return this.actual.val() == value;
+    },
+
+    toHaveData: function(key, expectedValue) {
+      return hasProperty(this.actual.data(key), expectedValue);
+    },
+
+    toBe: function(selector) {
+      return this.actual.is(selector);
+    },
+
+    toContain: function(selector) {
+      return this.actual.find(selector).size() > 0;
+    },
+
+    toBeDisabled: function(selector){
+      return this.actual.is(':disabled');
+    },
+
+    // tests the existence of a specific event binding
+    toHandle: function(eventName) {
+      var events = this.actual.data("events");
+      return events && events[eventName].length > 0;
+    },
+    
+    // tests the existence of a specific event binding + handler
+    toHandleWith: function(eventName, eventHandler) {
+      var stack = this.actual.data("events")[eventName];
+      var i;
+      for (i = 0; i < stack.length; i++) {
+        if (stack[i].handler == eventHandler) {
+          return true;
+        }
+      }
+      return false;
+    }
+  };
+
+  var hasProperty = function(actualValue, expectedValue) {
+    if (expectedValue === undefined) {
+      return actualValue !== undefined;
+    }
+    return actualValue == expectedValue;
+  };
+
+  var bindMatcher = function(methodName) {
+    var builtInMatcher = jasmine.Matchers.prototype[methodName];
+
+    jasmine.JQuery.matchersClass[methodName] = function() {
+      if (this.actual instanceof jQuery) {
+        var result = jQueryMatchers[methodName].apply(this, arguments);
+        this.actual = jasmine.JQuery.elementToString(this.actual);
+        return result;
+      }
+
+      if (builtInMatcher) {
+        return builtInMatcher.apply(this, arguments);
+      }
+
+      return false;
+    };
+  };
+
+  for(var methodName in jQueryMatchers) {
+    bindMatcher(methodName);
+  }
+})();
+
+beforeEach(function() {
+  this.addMatchers(jasmine.JQuery.matchersClass);
+  this.addMatchers({
+    toHaveBeenTriggeredOn: function(selector) {
+      this.message = function() {
+        return [
+          "Expected event " + this.actual + " to have been triggered on" + selector,
+          "Expected event " + this.actual + " not to have been triggered on" + selector
+        ];
+      };
+      return jasmine.JQuery.events.wasTriggered(selector, this.actual);
+    }
+  })
+});
+
+afterEach(function() {
+  jasmine.getFixtures().cleanUp();
+  jasmine.JQuery.events.cleanUp();
+});
Index: trunk/prototype/plugins/jq-raty/test/runner.html
===================================================================
--- trunk/prototype/plugins/jq-raty/test/runner.html	(revision 6528)
+++ trunk/prototype/plugins/jq-raty/test/runner.html	(revision 6528)
@@ -0,0 +1,24 @@
+	<!DOCTYPE html>
+
+<html>
+	<head>
+		<title>jQuery Test Runner</title>
+
+		<link rel="stylesheet" type="text/css" href="lib/jasmine.css">
+
+		<script type="text/javascript" charset="utf-8" src="../js/jquery.min.js"></script>
+		<script type="text/javascript" charset="utf-8" src="../js/jquery.raty.js"></script>
+
+		<script type="text/javascript" charset="utf-8" src="lib/jasmine.js"></script>
+		<script type="text/javascript" charset="utf-8" src="lib/jasmine-html.js"></script>
+		<script type="text/javascript" charset="utf-8" src="lib/jasmine-jquery.js"></script>
+
+		<script type="text/javascript" charset="utf-8" src="spec.js"></script>
+	</head>
+	<body>
+		<script type="text/javascript">
+			jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
+			jasmine.getEnv().execute();
+		</script>
+	</body>
+</html>
Index: trunk/prototype/plugins/jq-raty/doc/css/stylesheet.css
===================================================================
--- trunk/prototype/plugins/jq-raty/doc/css/stylesheet.css	(revision 6528)
+++ trunk/prototype/plugins/jq-raty/doc/css/stylesheet.css	(revision 6528)
@@ -0,0 +1,72 @@
+@CHARSET 'UTF-8'; 
+
+/* This CSS does not belong to the plugin. */
+
+body { background: url('../img/background.gif'); font: normal 11px verdana; }
+
+a { color: #2C8CBD; text-decoration: none; }
+a:hover { color: #48A5D4; }
+
+hr { border-top: medium none; height: 1px; margin-bottom: 16px; margin-top: 15px; }
+
+#coffee { background: url('../img/coffee.png') 6px 2px no-repeat #DC5; border: 1px solid #D9C640; color: #FFF; display: block; float: right; font-size: 10px; font-weight: bold; letter-spacing: .9px; margin-right: 9px; padding: 4px 5px 4px 26px; text-decoration: none; -khtml-border-radius: 4px; -moz-border-radius: 4px; -opera-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; }
+#coffee:hover { text-decoration: underline; }
+
+#content { background-color: #FFF; border: 1px solid #DEDEDE; box-shadow: 0 1px 3px rgba(100, 100, 100, 0.4); margin: 0 auto; padding: 15px; width: 1100px; min-height: 510px; }
+
+#menu-feature { color: #AAA; float: left; margin-bottom: 20px; }
+#menu-feature a { float: left; width: 120px }
+#menu-feature a.disabled { color: #999; cursor: default; }
+#menu-feature a.selected { color: #2C8CBD; cursor: default; font-weight: bold; }
+
+#footer { clear: both; height: 25px; margin-top: 5px; width: 100%; }
+#footer div { margin: 0 auto; text-align: center; width: 1100px; }
+
+#header { margin: 0 auto; padding-left: 20px; width: 1130px; }
+
+#menu { color: #AB9927; font: bold 14px 'Lucida Grande', 'Helvetica', 'Times New Roman', serif; text-shadow: 1px 1px 1px #FFF; text-transform: uppercase; }
+#menu a { color: #EA9C00; font: bold 10px verdana; letter-spacing: .9px; text-decoration: none; }
+#menu a:hover { color: #DC5; font-weight: bold; letter-spacing: .9px; text-decoration: underline; }
+
+#title { font: bold 17px verdana; color: #269; letter-spacing: .7px; margin-bottom: 10px; text-align: left; }
+#title span { color: #777; font: 10px verdana; }
+
+#version { color: #777; font: 10px verdana; }
+
+.action-field { border: 1px solid #CCC; height: 20px; padding-left: 3px; width: 80px; }
+.action-link { margin-left: 5px; }
+.action-wrapper { margin-top: 15px; }
+
+.comment { color: #444; font: 11px verdana; letter-spacing: 0.4px; line-height: 1.6; padding-bottom: 5px; text-align: left; }
+.comment-personal { color: #444; font: italic 11px verdana; letter-spacing: 0.4px; line-height: 1.6; text-align: left; }
+
+.description { color: #555; letter-spacing: .1px; margin-bottom: 15px; margin-top: 10px; text-align: left; }
+.description-code { color: #555; letter-spacing: .1px; margin-bottom: 10px; text-indent: 7px; }
+
+.highlight { color: #369; }
+
+.session { clear: both;  font: bold 13px verdana; border-bottom: 1px solid #EFEFEF; color: #444; letter-spacing: .7px; margin-bottom: 18px; margin-top: 24px; text-align: left; }
+.session-first { clear: both; font: bold 13px verdana; border-bottom: 1px solid #EFEFEF; color: #444; letter-spacing: .7px; margin-bottom: 18px; text-align: left; }
+.session-image { display: block; margin: 5px auto 0; }
+.session-option { font: bold 10px verdana; color: #444; letter-spacing: .7px; }
+.session-option span { color: #BBB; font: 11px arial; }
+
+.source { background: #FFF7FF; border: 1px solid #EFEFEF; border-left: 3px solid #CCC; clear: both; color: #444; font: 12px monospace; border-radius: 2px; letter-spacing: .1px; margin-bottom: 7px; margin-top: 15px; padding: 7px; width: 1081px; -khtml-border-radius: 2px; -moz-border-radius: 2px; -opera-border-radius: 2px; -webkit-border-radius: 2px; }
+
+/* custom */
+
+.left { float: left; }
+
+#target, #target-out, #function, #format, #mouseover { float: left; }
+
+#target-number { float: left; }
+
+#hint, #hint-out, #hint-function, #hint-format, #mouseover-target { background-color: #F0F0F0; border-radius: 3px; float: left; height: 15px; margin-left: 5px; padding-bottom: 2px; padding-left: 8px; padding-right: 8px; text-align: center; width: 70px; }
+
+#hint-format { width: 120px; }
+
+#precision { float: left; }
+
+#precision-target { background-color: #F0F0F0; border-radius: 3px; float: left; height: 20px; margin-left: 5px; padding-left: 8px; padding-right: 8px; padding-top: 4px; text-align: center; width: 50px; }
+
+#score-target { float: left; margin-left: 5px }
Index: trunk/prototype/plugins/jq-raty/changelog.md
===================================================================
--- trunk/prototype/plugins/jq-raty/changelog.md	(revision 6528)
+++ trunk/prototype/plugins/jq-raty/changelog.md	(revision 6528)
@@ -0,0 +1,174 @@
+# jQuery Raty - A Star Rating Plugin - http://wbotelhos.com/raty
+
+### 2.4.5
+
++ Now the error messages is displayed in place of stars to be more visible;
++ Fixed the 'score' function to handle undefined score when we have no vote;
++ Fixed the mouseover function to handle undefined score when we have no vote;
+	- For cancel button we get 'null' to know when mouse over it;
++ Fixed multiple mouseout actions on cancel function and mouseout bind;
++ Fixed functions that was not applying not continuing to apply if someone was invalid; 
++ Fixed the flag that indicates read-only or not.
+
+### 2.4.0
+
++ Added the function 'reload' to reload the rating with the current configuration;
++ Added the function 'set' to reload the rating applying new configurations;
++ Added the option 'mouseover' to handle a callback on mouseover the stars (by packowitz);
++ Fixed error when 'start' options receives a string number (by eskimoblood);
++ Fixed multiples events when readOnly is applied more then time by public function (by janapol).
++ Now attribute 'hintList' is called just of 'hints';
++ Now attribute 'start' is called as 'score' to make more sense;
++ Now the method 'start' is called as 'score' too.
+
+### 2.1.0
+
++ Now Raty keeps the state of the elements and no longer depends on ID for each one:
+	+ The score no longer has ID;
+	+ The stars images no longer has ID;
+	+ The stars images no longer has class.
++ Added function 'score' to recover the current score. 
+
+### 2.0.0
+
++ Added option 'halfShow' to just display and separate from the option to vote 'half';
++ Added option 'targetText' to choose default value when there is no score or targetKepp is off.
++ Added option 'precision' to be able rating with precision values, without star representation;
++ Added option 'space' to be able take off the spaces between the star;
++ Added option 'round' to customize the visual rounding of values;
++ Added option 'targetFormat' to customize the target with a template;
++ Added option 'single' to present only the selected star; (suggestion by newcube)
++ Fixed bug in IE 7 that returns void instead undefined when there is no an attribute;
++ Fixed bug that not set custom width when 'readOnly' is enabled;
++ Fixed bug that not set back the hints after disable read only option;
++ Fixed bug that not create cancel button when starts with 'readOnly';
++ Fixed the function cancel() to set the right hint on target score;
++ Fixed the functions start() and click() to set the value on target when 'target' option is enabled;
++ Fixed the functions start() and click() not to be executed when 'readOnly' is enabled;
++ Fixed the 'target' option to work with 'half' and 'precision' option;
++ Fixed the index number given to Raty binded by class without id; (by "Remember me, please?")
++ Refactored the code to make it cleanner and faster;
++ Changed the class of the cancel button to 'raty-cancel' to avoid CSS conflicts;
++ Now is possible to use 'start' option as callback function to get start value; (by hpgihan)
++ Now the scope of click callback is the raty element as DOM, not jQuery selection, to follow the usual;
++ Now the field score is set to readonly when 'readOnly' is enabled.
++ Now attribute 'iconRange' is represented as a list of object with option to choose starOn and starOff;
++ Now we using the best pratice to build the plugin;
++ Now the plugin is under test with Jasmine and Jasmine jQuery.
+
+### 1.4.3
+
++ Now public function return the context to be able the chaining;
++ Fixed: the option 'readOnly' brokes the plugin;
++ Prevented the processing of the set of classes in public function.
+
+### 1.4.0
+
++ Added attribute 'target' to choose a element to display the score when the mouse is on the star;
++ Added attribute 'targetKeep' to keep the selected value on the target element;
++ Added attribute 'targetType' to choose what display in the target element: hint or number;
++ New function called $.fn.raty.cancel() to cancel the rating;
++ Now the action of cancel removes the score value instead set it to zero;
++ Using mouseover when the half star is disabled to avoid to waste actions trigger;
++ Now is possible pass a empty string in the $.fn.raty.start() to cancel the rating;
++ Public functions without specify ID or class is no longer supported.
+
+### 1.3.3
+
++ Fixed the icon presentation when the start attribute is setted and the iconRange is enabled;
++ Now the click function receives the event as argument. (by Eric Wendelin)
+
+### 1.3.2
+
++ Fixed: the last Raty configuration will not be applied in others targets Raty anymore;
++ Now is possible to use the element as identifier like div.star on direct actions.
+
+### 1.3.0
+
++ Added attribute size for to choose the size of the icons that will be used;
++ Added attribute width for to choose the container width of the stars;
++ Changed the name of the attribute showCancel to just cancel.
+
+### 1.2.1
+
++ Fixed half star when click function is enabled.
+
+### 1.2.0
+
++ Added support to half star selection;
++ Changed the name of the attribute onClick to just click;
++ Changed the name of the attribute showHalf to half, because of the selection support.
+
+### 1.0.1
+
++ Fixed the ID's auto generation to work in IE6 and IE7.
+
+### 1.0.0
+
++ Now you can pass a optionally ID or a class to be the target of the public function's actions;
++ Fixed the read-only that wasn't begin applied by the public function readOnly.
+
+### 0.9
+
++ Improved the selector to accept bind by class name;
++ Now if you don't pass a ID for the element, then it will be created;
++ Now the hint list will be applied even if the quantity are less than the stars.
+
+### 0.8
+
++ Added iconRange attribute. Now you can set custom icons for especific ranges;
++ Fixed the cancel button that didn't hidden when using the public function readOnly.
+
+### 0.7
+
++ Added noRatedMsg attribute. A hint for no rated elements when it's read-only;
++ Avoided negative star number in public function start;
++ Avoided a number bigger than the number of star in public function start;
++ Fixed the public function start when the star are read-only and is not passed a start value;
++ Fixed the half star function on it is made by public function start;
+
+### 0.6
+
++ Now you can use the key "this" to refer the star element itself in the onClick handler;
++ Fixed the reference context when using public functions or local functions;
++ When the stars are read-only, all titles are transformed in the corresponding title score;
++ Avoided negative star number;
++ Avoided more than 20 stars. But it's can be changed in the script if you need.
+
+### 0.5
+
++ Now you can put a cancel button to cancel your rating;
++ Choose the cancel image off;
++ Choose the cancel image on;
++ Choose the left or right side position for the cancel button;
++ Change the hint of the cancel button.
+
+### 0.4
+
++ Added support to display half star.
+
+### 0.3
+
++ Fixed some mistakes to work on IE browser. 
+
+### 0.2
+
++ Added the public function click, that do the click on a star; 
++ Was removed the execution of the onClick when used the function start;
++ Added onClick function that enable a callback when a star is clicked.
+
+### 0.1
+
++ Change the path of images;
++ Change the name image files;
++ Choose the number of stars that will be presented;
++ Choose a hint information of each star;
++ Start with a default score;
++ Set the stars read-only;
++ Choose the name of the hidden score field.
+
+### known Bugs
+
++ On versions before 1.6 if a attribute not exists, then empty string is returned instead undefined;
++ On 1.5.2 the opt.click callback is never called, because this version always returns undefined to .call() and .apply();
++ On 1.5.2 the attribute readonly is setted with empty value as following readonly="".
Index: trunk/prototype/plugins/jq-raty/index.html
===================================================================
--- trunk/prototype/plugins/jq-raty/index.html	(revision 6528)
+++ trunk/prototype/plugins/jq-raty/index.html	(revision 6528)
@@ -0,0 +1,916 @@
+<!DOCTYPE html>
+
+<html lang="en" dir="ltr">
+	<head>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta http-equiv="Pragma" content="no-cache">
+		<meta http-equiv="Expires" content="-1">
+		<meta name="keywords" content="jquery,rating,raty,voto,star,staring,classificacao,classificar,votar,plugin" />
+		<meta name="description" content="jQuery Raty - A Star Rating Plugin" />
+
+		<title>jQuery Raty - A Star Rating Plugin</title>
+
+		<!-- This CSS does not belong to the plugin. -->
+		<link type="text/css" rel="stylesheet" href="doc/css/stylesheet.css"/>
+
+		<script type="text/javascript" charset="utf-8" src="js/jquery.min.js"></script>
+		<script type="text/javascript" charset="utf-8" src="js/jquery.raty.min.js"></script>
+
+		<script type="text/javascript">
+			// This code does NOT belong the plugin. See the example code at the bottom of this page.
+			var _gaq = _gaq || [];
+			_gaq.push(['_setAccount', 'UA-194992347-3']);
+			_gaq.push(['_trackPageview']);
+			
+			(function() {
+				var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+				ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+				var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+			})();
+		</script>
+	</head>
+	<body>
+		<div id="header">
+			<div id="title">
+				<a href="http://wbotelhos.com/raty">jQuery Raty - A Star Rating Plugin</a>
+				<span id="version">(current version: 2.4.5)</span>
+			</div>
+
+			<div id="menu">
+				<a href="http://github.com/downloads/wbotelhos/raty/jquery.raty-2.4.5.zip" target="_blank">Download</a> |
+				<a href="http://github.com/wbotelhos/raty" target="_blank">Github</a> |
+				<a href="http://www.wbotelhos.com/raty/changelog.md" target="_blank">Change Log</a> |
+				<a href="http://www.wbotelhos.com/raty/README.md" target="_blank">Readme</a> |
+				<a href="http://www.wbotelhos.com/2010/07/14/jquery-raty-a-star-rating-plugin" target="_blank">Article</a> |
+				<a href="http://www.opensource.org/licenses/mit-license.php" target="_blank">License</a> |
+				<a href="http://www.wbotelhos.com/2010/07/14/jquery-raty-a-star-rating-plugin#comments" target="_blank" style="color: #2C8CBD;">Doubt</a> |
+				<a href="http://www.wbotelhos.com/lab" target="_blank" style="color: #000;">yLab</a>
+			</div>
+
+			<a id="coffee" href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&amp;business=X8HEP2878NDEG&amp;item_name=jQuery%20Raty" target="_blank">buy me a coffee</a>
+
+			<div class="description">jQuery <strong>Raty</strong> is a plugin that generates a customizable star rating.</div>
+		</div>
+
+		<div id="content">
+			<div id="default-demo" class="session-first">Default options:</div>
+
+			<div id="default"></div>
+
+			<div class="source">
+				$('#star').raty();<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;
+			</div>
+
+			<div id="fixed-demo" class="session">Started with a score and read only value:</div>
+			<div id="fixed"></div>
+
+			<div class="source">
+			 	$('#star').raty({<br/>
+				&#160;&#160;readOnly : true,<br/>
+				&#160;&#160;score&#160;&#160;&#160; : 2<br/>
+				});<br/><br/>
+				
+				&lt;div id="star"&gt;&lt;/div&gt;
+			</div>
+
+			<div id="score-demo" class="session">Starting with a callback:</div>
+			<div id="score" data-rating="3"></div>
+
+			<div class="source">
+			 	$('#score').raty({<br/>
+				&#160;&#160;score: function() {<br/>
+				&#160;&#160;&#160;&#160;return $(this).attr('data-rating');<br/>
+				&#160;&#160;}<br/>
+				});<br/><br/>
+
+				&lt;div id="star" data-rating="3"&gt;&lt;/div&gt;
+			</div>
+
+			<div id="anyone-demo" class="session">A hint for no rated elements when it's read-only:</div>
+			<div id="anyone"></div>
+
+			<div class="source">
+			 	$('#star').raty({<br/>
+				&#160;&#160;readOnly&#160;&#160; : true,<br/>
+				&#160;&#160;noRatedMsg : 'anyone rated this product yet!'<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;
+			</div>
+
+			<div id="number-demo" class="session">Custom score name and a number of stars:</div>
+			<div id="number"></div>
+
+			<div class="source">
+			 	$('#star').raty({<br/>
+				&#160;&#160;scoreName : 'entity.score',<br/>
+				&#160;&#160;number&#160;&#160;&#160; : 10<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;
+			</div>
+
+			<div id="click-demo" class="session">Using click function:</div>
+			<div id="click"></div>
+
+			<div class="source">
+			 	$('#star').raty({<br/>
+				&#160;&#160;click: function(score, evt) {<br/>
+				&#160;&#160;&#160;&#160;alert('ID: ' + $(this).attr('id') + '\nscore: ' + score + '\nevent: ' + evt);<br/>
+				&#160;&#160;}<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;
+			</div><br/>
+
+			<span class="comment">
+				 - The argument score is the selected value;<br/>
+				 - The argument evt is the click event;<br/>
+				 - You can mension the star element (DOM) itself using 'this'.<br/>
+			</span>
+
+			<div id="cancel-demo" class="session">Default cancel button:</div>
+			<div id="cancel"></div>
+
+			<div class="source">
+			 	$('#star').raty({<br/>
+				&#160;&#160;cancel: true<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;
+			</div><br/>
+
+			<span class="comment">
+				 - The score value for the click on cancel button is null.<br/>
+			</span>
+
+			<div id="cancel-custom-demo" class="session">Custom cancel button:</div>
+			<div id="cancel-custom"></div>
+
+			<div class="source">
+			 	$('#star').raty({<br/>
+				&#160;&#160;cancel&#160;&#160;&#160;&#160;&#160; : true,<br/>
+				&#160;&#160;cancelHint&#160; : 'remove my rating!',<br/>
+				&#160;&#160;cancelPlace : 'right',<br/>
+				&#160;&#160;click&#160;&#160;&#160;&#160;&#160;&#160; : function(score, evt) {<br/>
+				&#160;&#160;&#160;&#160;alert('score: ' + score);<br/>
+				&#160;&#160;}<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;
+			</div>
+
+			<div id="half-demo" class="session">Half star voting:</div>
+			<div id="half"></div>
+
+			<div class="source">
+			 	$('#star').raty({<br/>
+				&#160;&#160;half&#160; : true,<br/>
+				&#160;&#160;score : 3.3<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;
+			</div><br/>
+
+			<span class="comment">
+				- You can disable the 'halfShow' option to just vote with half star but not show it.<br/>
+				- If 'halfShow' is disabled, then score &gt;= x.6 will be rounded up visually.<br/>
+				- The interval can be:<br/>
+				-- Rounded down: [x.00 .. x.25]<br/>
+				-- Half star:&#160;&#160;&#160; [x.26 .. x.75]<br/>
+				-- Rounded up:&#160;&#160; [x.76 .. x.99]
+			</span>
+
+			<div id="round-demo" class="session">Custom round option:</div>
+			<div id="round"></div>
+
+			<div class="source">
+			 	$('#star').raty({<br/>
+				&#160;&#160;score : 1.26,<br/>
+				&#160;&#160;round : { down: .25, full: .6, up: .76 }<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;
+			</div><br/>
+
+			<span class="comment">
+				- This example use the default round values;<br/>
+				- down: with halfShow enabled, score &lt;= x.25 will be rounded down; (inclusive)<br/>
+				- up: with halfShow enabled, score &gt;= x.76 will be rounded up; (inclusive)<br/>
+				- down-up: with halfShow enabled, score &gt; x.25 and score &lt; .76 will be half star; (inclusive)<br/>
+				- full: with halfShow disabled, score &gt;= x.6 will be rounded up; (inclusive)<br/>
+			</span>
+
+			<div id="icon-demo" class="session">Custom hint and icons:</div>
+			<div id="icon"></div>
+
+			<div class="source">
+			 	$('#star').raty({<br/>
+				&#160;&#160;hints&#160;&#160; : ['a', '', null, 'd', '5'],<br/>
+				&#160;&#160;starOn&#160; : 'medal-on.png',<br/>
+				&#160;&#160;starOff : 'medal-off.png'<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;
+			</div><br/>
+
+			<span class="comment">
+				- To display the number of the star, set null.
+			</span>
+
+			<div id="range-demo" class="session">Range of custom icons:</div>
+			<div id="range"></div>
+
+			<div class="source">
+			 	$('#star').raty({<br/>
+			 	&#160;&#160;iconRange: [<br/>
+				&#160;&#160;&#160;&#160;{ range: 2, on: 'face-a.png', off: 'face-a-off.png' },<br/>
+				&#160;&#160;&#160;&#160;{ range: 3, on: 'face-b.png', off: 'face-b-off.png' },<br/>
+				&#160;&#160;&#160;&#160;{ range: 4, on: 'face-c.png', off: 'face-c-off.png' },<br/>
+				&#160;&#160;&#160;&#160;{ range: 5, on: 'face-d.png', off: 'face-d-off.png' }<br/>
+				&#160;&#160;]<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;
+			</div><br/>
+
+			<span class="comment">
+				- It's a array of objects where each one represents a custom icon;<br/>
+				- The value range is until wich position the icon will be displayed;<br/>
+				- The value on is the active icon;<br/>
+				- The value off is the inactive icon;<br/>
+				- The sequence of the range interval should be in a ascending order;<br/>
+				- If the value on or off is omitted then the attribute starOn or starOff will be used.
+			</span>
+
+			<div id="big-demo" class="session">Bigger icon:</div>
+			<div id="big"></div>
+
+			<div class="source">
+			 	$('#star').raty({<br/>
+			 	&#160;&#160;cancel&#160;&#160;&#160; : true,<br/>
+				&#160;&#160;cancelOff : 'cancel-off-big.png',<br/>
+				&#160;&#160;cancelOn&#160; : 'cancel-on-big.png',<br/>
+				&#160;&#160;half&#160;&#160;&#160;&#160;&#160; : true,<br/>
+				&#160;&#160;size&#160;&#160;&#160;&#160;&#160; : 24,<br/>
+				&#160;&#160;starHalf&#160; : 'star-half-big.png',<br/>
+				&#160;&#160;starOff&#160;&#160; : 'star-off-big.png',<br/>
+				&#160;&#160;starOn&#160;&#160;&#160; : 'star-on-big.png'<br/>
+				});<br/><br/>
+			   
+				&lt;div id="star"&gt;&lt;/div&gt;
+			</div><br/>
+
+			<span class="comment">
+				- You can specify your own width as following: width: 120.
+			</span>
+
+			<div id="group-demo" class="session">Group of elements:</div>
+			<div class="group"></div>
+			<div class="group"></div>
+			<div class="group"></div>
+
+			<div class="source">
+				$('.star').raty();<br/><br/>
+	
+				&lt;div class="star"&gt;&lt;/div&gt;<br/>
+				&lt;div class="star"&gt;&lt;/div&gt;<br/>
+				&lt;div class="star"&gt;&lt;/div&gt;
+			</div><br/>
+
+			<span class="comment">
+				- The ID is optional and must be unique;<br/>
+				- If you don't pass a ID for the element, then it will be created.
+			</span>
+
+			<div id="target-demo" class="session">Displaying hint in a target element:</div>
+
+			<div id="target"></div>
+			<div id="hint"></div><br/>
+
+			<div class="source">
+				$('#star').raty({<br/>
+				&#160;&#160;cancel&#160;&#160;&#160;&#160; : true,<br/>
+				&#160;&#160;cancelHint : 'none',<br/>
+				&#160;&#160;target&#160;&#160;&#160;&#160; : '#hint'<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;<br/>
+				&lt;div id="target"&gt;&lt;/div&gt;
+			</div>
+
+			<div id="target-number-demo" class="session">Displaying and keeping the score in a target element:</div>
+
+			<div id="target-number"></div>
+
+			<select id="score-target">
+				<option value="">--</option>
+				<option value="1">1</option>
+				<option value="2">2</option>
+				<option value="3">3</option>
+				<option value="4">4</option>
+				<option value="5">5</option>
+			</select>
+
+			<div class="source left">
+				$('#star').raty({<br/>
+				&#160;&#160;cancel&#160;&#160;&#160;&#160; : true,<br/>
+				&#160;&#160;target&#160;&#160;&#160;&#160; : '#score',<br/>
+				&#160;&#160;targetKeep : true,<br/>
+				&#160;&#160;targetType : 'number'<br/>
+				});<br/><br/>
+
+				&lt;select id="target"&gt;<br/>
+					&#160;&#160;&lt;option value=""&gt;0&lt;/option&gt;<br/>
+					&#160;&#160;&lt;option value="1"&gt;1&lt;/option&gt;<br/>
+					&#160;&#160;&lt;option value="2"&gt;2&lt;/option&gt;<br/>
+					&#160;&#160;&lt;option value="3"&gt;3&lt;/option&gt;<br/>
+					&#160;&#160;&lt;option value="4"&gt;4&lt;/option&gt;<br/>
+					&#160;&#160;&lt;option value="5"&gt;5&lt;/option&gt;<br/>
+				&lt;/select&gt;<br/>
+			</div><br/>
+
+			<span class="comment">
+				- You can to choose the target types 'hint' or 'number'.
+			</span>
+
+			<div id="target-out-demo" class="session">Setting default value to the target on mouseout:</div>
+
+			<div id="target-out"></div>
+			<div id="hint-out"></div><br/>
+
+			<div class="source">
+				$('#star').raty({<br/>
+				&#160;&#160;target&#160;&#160;&#160;&#160; : '#hint',<br/>
+				&#160;&#160;targetText : '--'<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;<br/>
+				&lt;div id="target"&gt;&lt;/div&gt;
+			</div>
+
+			<div id="format-demo" class="session">Displaying hint with format template:</div>
+
+			<div id="format"></div>
+			<div id="hint-format"></div><br/>
+
+			<div class="source">
+				$('#star').raty({<br/>
+				&#160;&#160;cancel&#160;&#160;&#160;&#160;&#160;&#160; : true,<br/>
+				&#160;&#160;cancelHint&#160;&#160; : 'Sure?',<br/>
+				&#160;&#160;target&#160;&#160;&#160;&#160;&#160;&#160; : '#hint',<br/>
+				&#160;&#160;targetFormat : 'your score: {score}',<br/>
+				&#160;&#160;targetText&#160;&#160; : 'none',<br/>
+				&#160;&#160;targetKeep&#160;&#160; : true<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;<br/>
+				&lt;div id="target"&gt;&lt;/div&gt;
+			</div>
+
+			<div id="mouseover-demo" class="session">Using mouseover callback:</div>
+
+			<div id="mouseover"></div>
+			<div id="mouseover-target"></div><br/>
+
+			<div class="source" style="margin-top: 15px;">
+				$('#star').raty({<br/>
+				&#160;&#160;cancel&#160;&#160;&#160; : true,<br />
+				&#160;&#160;mouseover : function(score, evt) {<br />
+				&#160;&#160;&#160;&#160;var target = $('#mouseover-target');<br /><br />
+
+				&#160;&#160;&#160;&#160;if (score === null) {<br />
+				&#160;&#160;&#160;&#160;&#160;&#160;target.html('Boring!');<br />
+				&#160;&#160;&#160;&#160;} else if (score === undefined) {<br />
+				&#160;&#160;&#160;&#160;&#160;&#160;target.empty();<br />
+				&#160;&#160;&#160;&#160;} else {<br />
+				&#160;&#160;&#160;&#160;&#160;&#160;target.html('score: ' + score);<br />
+				&#160;&#160;&#160;&#160;}<br />
+				&#160;&#160;}<br />
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;<br/>
+				&lt;div id="target"&gt;&lt;/div&gt;
+			</div>
+
+			<span class="comment">
+				- The options target, targetFormat, targetKeep, targetText and targetType are abstractions of this callback.
+			</span>
+
+			<div id="precision-demo" class="session">Half star voting precision:</div>
+
+			<div id="precision"></div>
+			<div id="precision-target"></div><br/>
+
+			<div class="source" style="margin-top: 15px;">
+				$('#star').raty({<br/>
+				&#160;&#160;half&#160;&#160;&#160;&#160;&#160;&#160; : true,<br/>
+				&#160;&#160;precision&#160; : true,<br/>
+				&#160;&#160;size&#160;&#160;&#160;&#160;&#160;&#160; : 24,<br/>
+				&#160;&#160;starHalf&#160;&#160; : 'star-half-big.png',<br/>
+				&#160;&#160;starOff&#160;&#160;&#160; : 'star-off-big.png',<br/>
+				&#160;&#160;starOn&#160;&#160;&#160;&#160; : 'star-on-big.png'<br/>
+				&#160;&#160;target&#160;&#160;&#160;&#160; : '#precision-target'<br/>
+				&#160;&#160;targetType : 'number'<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;<br/>
+				&lt;div id="target"&gt;&lt;/div&gt;
+			</div>
+
+			<div id="space-demo" class="session">Without space between stars:</div>
+
+			<div id="space"></div>
+
+			<div class="source">
+				$('#star').raty({<br/>
+				&#160;&#160;space: false<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;<br/>
+			</div>
+
+			<div id="single-demo" class="session">Single icon:</div>
+
+			<div id="single"></div>
+
+			<div class="source">
+				$('#star').raty({<br/>
+				&#160;&#160;single: true<br/>
+				});<br/><br/>
+
+				&lt;div id="star"&gt;&lt;/div&gt;<br/>
+			</div>
+
+			<div id="action-demo" class="session">Directed actions with public functions:</div>
+
+			love:
+			<div class="action"></div>
+
+			happy:
+			<div class="action"></div><br/>
+
+			your last rate:
+			<div id="result"></div>
+
+			<div class="source">
+				$('.star').raty({<br/>
+				&#160;&#160;half&#160; : true,<br/>
+				&#160;&#160;click : function(score, evt) {<br/>
+				&#160;&#160;&#160;&#160;$(this).raty('cancel');<br/>
+				&#160;&#160;&#160;&#160;$('#result').raty('score', score);<br/>
+				&#160;&#160;}<br/>
+				});<br/><br/>
+
+				&lt;div class="star"&gt;&lt;/div&gt;<br/>
+				&lt;div class="star"&gt;&lt;/div&gt;<br/><br/>
+				&lt;div id="result"&gt;&lt;/div&gt;
+			</div><br/>
+
+			<span class="comment">
+				- All functions have a optional second parameter to specify which container will be executed;<br/>
+				- You can pass a ID or a class to be the target of the action;<br/>
+				- If the ID or class are not specified, then the last element Raty will be takes.
+			</span>
+
+			<div id="function-demo" class="session">Functions Demo:</div>
+
+			<div id="function"></div>
+			<div id="hint-function"></div><br/>
+
+			<div style="color: #CCC; margin-top: 15px;">
+				<a href="javascript:void(0);" title="1" class="score">Score 1</a> |
+				<a href="javascript:void(0);" title="2" class="score">Score 2</a> |
+				<a href="javascript:void(0);" title="3" class="score">Score 3</a> |
+				<a href="javascript:void(0);" title="4" class="score">Score 4</a> |
+				<a href="javascript:void(0);" title="5" class="score">Score 5</a>
+			</div>
+
+			<div style="color: #CCC; margin-top: 15px;">
+				<a href="javascript:void(0);" title="1" class="click">Click 1</a> |
+				<a href="javascript:void(0);" title="2" class="click">Click 2</a> |
+				<a href="javascript:void(0);" title="3" class="click">Click 3</a> |
+				<a href="javascript:void(0);" title="4" class="click">Click 4</a> |
+				<a href="javascript:void(0);" title="5" class="click">Click 5</a>
+			</div>
+
+			<div style="color: #CCC; margin-top: 15px;">
+				<a href="javascript:void(0);" title="true" class="readOnly">ReadOnly (true)</a> |
+				<a href="javascript:void(0);" title="false" class="readOnly">ReadOnly (false)</a>
+			</div>
+
+			<div style="color: #CCC; margin-top: 15px;">
+				<a href="javascript:void(0);" title="false" class="cancel">Cancel</a> |
+				<a href="javascript:void(0);" title="true" class="cancel">Cancel (trigger)</a>
+			</div>
+
+			<div style="color: #CCC; margin-top: 15px;">
+				<a id="reload" href="javascript:void(0);" title="reload">Reload</a> |
+				<a id="get-score" href="javascript:void(0);" title="score">Score</a>
+			</div>
+
+			<div class="action-wrapper">
+				settings: <input id="set-id" type="text" value="{ number: 3 }" class="action-field" />
+				<a id="set-action" href="javascript:void(0);" title="set" class="action-link">set</a>
+			</div>
+
+			<div id="global-demo" class="session">Changing the settings globally:</div>
+
+			<div class="source">
+				$.fn.raty.defaults.starOn = 'star-on.gif';<br/>
+				$.fn.raty.defaults.starOff = 'star-off.gif';
+			</div>
+
+			<span class="comment">
+				- You can change any option mention the scope <b>$.fn.raty.defaults.</b> + <i>option_name</i>;<br/>
+				- This setup must be called before you bind the Raty, of course.
+			</span>
+
+			<div class="session">Default options:</div>
+
+			<div class="session-option">cancel: <span>false</span></div>
+			<div class="comment">If will be showed a button to cancel the rating.</div>
+
+			<hr />
+
+			<div class="session-option">cancelHint: <span>'cancel this rating!'</span></div>
+			<div class="comment">The hint information.</div>
+
+			<hr />
+
+		 	<div class="session-option">cancelHint: 'cancel this rating!'<br/>
+		 	<div class="comment">The hint information.</div>
+
+			<hr />
+
+		 	<div class="session-option">cancelOff: <span>'cancel-off.png'</span></div>
+		 	<div class="comment">Name of the cancel image off.</div>
+
+			<hr />
+
+		 	<div class="session-option">cancelOn: <span>'cancel-on.png'</span></div>
+		 	<div class="comment">Name of the cancel image on.</div>
+
+			<hr />
+
+		 	<div class="session-option">cancelPlace: <span>'left'</span></div>
+		 	<div class="comment">Position of the cancel button.</div>
+
+			<hr />
+
+			<div class="session-option">click: <span>undefined</span></div>
+		 	<div class="comment">Function that returns the selected value.</div>
+
+			<hr />
+
+			<div class="session-option">half: <span>false</span></div>
+		 	<div class="comment">Enables half star selection.</div>
+
+			<hr />
+
+			<div class="session-option">halfShow: <span>true</span></div>
+		 	<div class="comment">Enables half star display.</div>
+
+			<hr />
+
+		 	<div class="session-option">hints: <span>['bad', 'poor', 'regular', 'good', 'gorgeous']</span></div>
+		 	<div class="comment">List of names that will be used as a hint on each star.</div>
+
+			<hr />
+
+			<div class="session-option">iconRange: <span>undefined</span></div>
+			<div class="comment">List of object that represent each icon with position and names.</div>
+
+			<hr />
+
+			<div class="session-option">noRatedMsg: <span>'not rated yet'</span></div>
+		 	<div class="comment">A hint for no rated elements when it's read-only.</div>
+
+			<hr />
+
+			<div class="session-option">number: <span>5</span></div>
+		 	<div class="comment">Number of stars that will be presented.</div>
+
+			<hr />
+
+			<div class="session-option">path: <span>'img/'</span></div>
+		 	<div class="comment">A range of custom icons that you can set.</div>
+
+			<hr />
+
+			<div class="session-option">precision: <span>false</span></div>
+		 	<div class="comment">Enables the selection of a precision score.</div>
+
+			<hr />
+
+			<div class="session-option">readOnly: <span>false</span></div>
+		 	<div class="comment">If the stars will be read-only.</div>
+
+			<hr />
+
+			<div class="session-option">round: <span>{ down: <span>.25, full: <span>.6, up: <span>.76 }</span></div>
+		 	<div class="comment">Configuration to set the round rules.</div>
+
+			<hr />
+
+			<div class="session-option">score: <span>undefined</span></div>
+			<div class="comment">Number of stars to be selected.</div>
+
+			<hr />
+
+			<div class="session-option">scoreName: <span>'score'</span></div>
+		 	<div class="comment">Name of the hidden field that holds the score value.</div>
+
+			<hr />
+
+			<div class="session-option">single: <span>false</span></div>
+		 	<div class="comment">Enables the single star selection.</div>
+
+			<hr />
+
+			<div class="session-option">space: <span>true</span></div>
+		 	<div class="comment">Puts space between the stars.</div>
+
+			<hr />
+
+			<div class="session-option">size: <span>16</span></div>
+		 	<div class="comment">The icons size.</div>
+
+			<hr />
+
+			<div class="session-option">starHalf: <span>'star-half.png'</span></div>
+		 	<div class="comment">The name of the half star image.</div>
+
+			<hr />
+
+			<div class="session-option">starOff: <span>'star-off.png'</span></div>
+		 	<div class="comment">Name of the star image off.</div>
+
+			<hr />
+
+			<div class="session-option">starOn: <span>'star-on.png'</span></div>
+		 	<div class="comment">Name of the star image on.</div>
+
+			<hr />
+
+			<div class="session-option">target: <span>undefined</span></div>
+			<div class="comment">Element selector where the rating will be displayed.</div>
+
+			<hr />
+
+			<div class="session-option">targetFormat: <span>'{score}'</span></div>
+			<div class="comment">Template to interpolate the score with some thing.</div>
+
+			<hr />
+
+			<div class="session-option">targetKeep: <span>false</span></div>
+			<div class="comment">If the last choose value will be keeped on mouseout.</div>
+
+			<hr />
+
+			<div class="session-option">targetText: <span>''</span></div>
+			<div class="comment">Default value when there is no score or targetKeep is off.</div>
+
+			<hr />
+
+			<div class="session-option">targetType: <span>'hint'</span></div>
+			<div class="comment">What display on target element hint or number.</div>
+
+			<hr />
+
+			<div class="session-option">width&#160;: <span>undefined</span></div>
+			<div class="comment">The container width of the stars.</div>
+
+			<div class="session">Functions:</div>
+
+			<div class="source">$('#star').raty('score');</div>
+			<div class="comment">Recovers the current score or undefined for no rated. Class returns an array of score</div>
+
+			<div class="source">$('#star').raty('score', 3);</div>
+			<div class="comment">Set the score with 3 stars.</div>
+
+			<div class="source">$('#star').raty('click', 2);</div>
+			<div class="comment">Click on the second star of the Raty with ID called 'raty'.</div>
+
+			<div class="source">$('.star').raty('readOnly', true);</div>
+			<div class="comment">Adjusts all Raty with class called 'raty' for read-only.</div>
+
+			<div class="source">$('#star').raty('cancel', true);</div>
+			<div class="comment">Cancel the rating. The second optional parameter enable thes click callback.</div>
+
+			<div class="source">$('#star').raty('reload');</div>
+			<div class="comment">Reload the rating with the current configuration.</div>
+
+			<div class="source">$('#star').raty('set', { number: 10 });</div>
+			<div class="comment">Reload the rating applying new configurations.</div>
+		</div>
+
+		<div id="footer">
+			<div id="copy">&copy; 2010 <a href="http://www.wbotelhos.com/" target="_blank">wbotelhos.com</a></div>
+		</div>
+
+		<script type="text/javascript">
+			$(function() {
+
+				$('#default').raty();
+
+				$('#fixed').raty({
+					readOnly	: true,
+					score		: 2
+				});
+
+				$('#score').raty({
+				    score: function() {
+				        return $(this).attr('data-rating');
+				    }
+				});
+
+				$('#anyone').raty({
+					readOnly	: true,
+					noRatedMsg	: 'anyone rated this product yet!'
+				});
+
+				$('#number').raty({
+					scoreName	: 'entity.score',
+					number		: 10
+				});
+
+				$('#click').raty({
+					click: function(score, evt) {
+						alert('ID: ' + $(this).attr('id') + '\nscore: ' + score + '\nevent: ' + evt);
+					}
+				});
+
+				$('#cancel').raty({
+					cancel: true
+				});
+
+				$('#cancel-custom').raty({
+					cancel		: true,
+					cancelHint	: 'remove my rating!',
+					cancelPlace	: 'right',
+					click		: function(score, evt) {
+						alert('score: ' + score);
+					}
+				});
+
+				$('#half').raty({
+					half	: true,
+					score	: 3.3
+				});
+
+				$('#round').raty({
+					score	: 1.26,
+					round	: { down: .25, full: .6, up: .76 }
+				});
+
+				$('#icon').raty({
+					hints		: ['a', '', null, 'd', '5'],
+					path		: 'doc/img/',
+				   	starOn		: 'medal-on.png',
+				   	starOff		: 'medal-off.png'
+				});
+
+				$('#range').raty({
+					iconRange	: [
+						{ range: 2, on: 'face-a.png', off: 'face-a-off.png' },
+						{ range: 3, on: 'face-b.png', off: 'face-b-off.png' },
+						{ range: 4, on: 'face-c.png', off: 'face-c-off.png' },
+						{ range: 5, on: 'face-d.png', off: 'face-d-off.png' }
+					],
+					path		: 'doc/img/'
+				});
+
+				$('#big').raty({
+					cancel		: true,
+					cancelOff	: 'cancel-off-big.png',
+					cancelOn	: 'cancel-on-big.png',
+					half		: true,
+					path		: 'doc/img/',
+					size		: 24,
+					starOff		: 'star-off-big.png',
+					starOn		: 'star-on-big.png',
+					starHalf	: 'star-half-big.png'
+				});
+
+				$('.group').raty();
+
+				$('#target').raty({
+					cancel		: true,
+					cancelHint	: 'none',
+					target		: '#hint'
+				});
+
+				$('#format').raty({
+					cancel			: true,
+					cancelHint		: 'Sure?',
+					target			: '#hint-format',
+					targetFormat	: 'your score: {score}',
+					targetKeep		: true,
+					targetText		: 'none'
+				});
+
+				$('#target-number').raty({
+					cancel		: true,
+					target		: '#score-target',
+					targetKeep	: true,
+					targetType	: 'number'
+				});
+
+				$('#target-out').raty({
+					target		: '#hint-out',
+					targetText	: '--'
+				});
+
+				$('#mouseover').raty({
+  					cancel		: true,
+  					mouseover	: function(score, evt) {
+  						var target = $('#mouseover-target');
+
+  						if (score === null) {
+  							target.html('Boring!');
+  						} else if (score === undefined) {
+  							target.empty();
+  						} else {
+  							target.html('score: ' + score);
+  						}
+  					}
+  				});
+
+				$('#precision').raty({
+					half		: true,
+					path		: 'doc/img/',
+					precision	: true,
+					size		: 24,
+					starOff		: 'star-off-big.png',
+					starOn		: 'star-on-big.png',
+					starHalf	: 'star-half-big.png',
+					target		: '#precision-target',
+					targetType	: 'number'
+				});
+
+				$('#space').raty({
+					space: false
+				});
+
+				$('#single').raty({
+					single: true
+				});
+
+				var $result = $('#result').raty();
+
+				$('.action').raty({
+					click: function(score, evt) {
+						$(this).raty('cancel');
+						$result.raty('score', score);
+					}
+				});
+
+				$('#function').raty({
+					cancel		: true,
+					cancelHint	: 'Boring!',
+					click		: function(score, evt) {
+						$(this).fadeOut(function() { $(this).fadeIn(); });
+					},
+					targetKeep	: true,
+					score		: 2,
+					target		: '#hint-function',
+					targetText	: '--'
+				});
+
+				$('.score').click(function() {
+					$('#function').raty('score', this.title);
+				});
+
+				$('.click').click(function() {
+					$('#function').raty('click', this.title);
+				});
+
+				$('.readOnly').click(function() {
+					$('#function').raty('readOnly', (this.title == 'true') ? true : false);
+				});
+
+				$('.cancel').click(function() {
+					$('#function').raty('cancel', (this.title == 'true') ? true : false);
+				});
+
+				$('#reload').click(function() {
+					$('#function').raty('reload');
+				});
+
+				$('#get-score').click(function() {
+					alert($('#function').raty('score'));
+				});
+
+				$('#set-action').on('click', function() {
+					var options = $('#set-id').val();
+
+					eval("$('#function').raty('set', " + options + ");");
+				});
+
+			});
+		</script>
+	</body>
+</html>
Index: trunk/prototype/plugins/jq-raty/README.md
===================================================================
--- trunk/prototype/plugins/jq-raty/README.md	(revision 6528)
+++ trunk/prototype/plugins/jq-raty/README.md	(revision 6528)
@@ -0,0 +1,108 @@
+# jQuery Raty - A Star Rating Plugin - http://wbotelhos.com/raty
+
+jQuery Raty is a plugin that generates a customizable star rating.
+
+## Version
+
+	@version        2.4.5
+	@since          2010.06.11
+	@author         Washington Botelho
+	@documentation  wbotelhos.com/raty
+	@twitter        twitter.com/wbotelhos
+
+## Required Files
+
++ jquery.raty.min.js
++ star-on.png
++ star-off.png
+
+## Default values
+
+	cancel       : false                                          // Show a button to cancel the rating or not.   
+	cancelHint   : 'cancel this rating!'                          // The hint information.
+	cancelOff    : 'cancel-off.png'                               // Name of the cancel image off.
+	cancelOn     : 'cancel-on.png'                                // Name of the cancel image on.
+	cancelPlace  : 'left'                                         // Position of the cancel button.
+	click        : undefined                                      // Callback for click actions.
+	half         : false                                          // Active the half star.
+	halfShow     : true                                           // Enables half star display.
+	hints        : ['bad', 'poor', 'regular', 'good', 'gorgeous'] // A hint information for default 5 stars.
+	iconRange    : undefined                                      // Object list representing each icon with position and names.
+	mouseover    : undefined                                      // Callback for mouseover actions.
+	noRatedMsg   : 'not rated yet'                                // A hint for no rated elements when it's read-only.
+	number       : 5                                              // Number of star.
+	path         : 'img                                           // Path of images.
+	precision    : false                                          // Enables the selection of a precision score.
+	readOnly     : false                                          // read-only or not.
+	round        : { down: .25, full: .6, up: .76 }               // Configuration to set the round rules.
+	score        : undefined                                      // Initial score value.
+	scoreName    : 'score'                                        // The name of target score.
+	single       : false                                          // Enables the single star selection.
+	size         : 16                                             // The icons size.
+	space        : true                                           // Puts space between the stars.
+	starHalf     : 'star-half.png'                                // The image of the half star.
+	starOff      : 'star-off.png'                                 // Name of the star image off.
+	starOn       : 'star-on.png'                                  // Name of the star image on.
+	target       : undefined                                      // Element selector where the rating will be displayed.
+	targetFormat : '{score}'                                      // Template to interpolate the score with some thing.
+	targetKeep   : false                                          // If the last choose value will be keeped on mouseout.
+	targetText   : ''                                             // Default value when there's no score or targetKeep is off.
+	targetType   : 'hint'                                         // What display on target element: hint or number.
+	width        : undefined                                      // The container width of the stars.
+
+## Usage
+
+	$('#star').raty();
+
+	<div id="star"></div>
+
+	$('.star').raty();
+
+	<div class="star"></div>
+	<div class="star"></div>
+	<div class="star"></div>
+
+## Functions
+
+	$('#star').raty('score');                // Recovers the current score or undefined for no rated. Class returns an array of score.
+
+	$('#star').raty('score', 3);             // Set the score to 3 stars.
+
+	$('#star').raty('click', 2);             // Click on the second star.
+
+	$('.star').raty('readOnly', true);       // Adjusts all elements with class called 'star' for read-only.
+
+	$('#star').raty('cancel', true);         // Cancel the rating. The second optional parameter enables the callback.
+
+	$('#star').raty('reload');               // Reload the rating with the current configuration.
+
+	$('#star').raty('set', { number: 10 });  // Reload the rating applying new configurations.
+
+## Contributors
+
++ Andreas KÃ¶berle
++ Daniel Faria
++ Eric Wendelin
++ Francisco Souza
++ Gabriel Benz
++ hpgihan
++ janapol
++ jeongee
++ Olle Jonsson
++ packowitz
+
+## Licence
+
+The MIT License
+
+Copyright (c) 2010 Washington Botelho
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+## Buy me a coffee
+
+You can do it by [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=X8HEP2878NDEG&item_name=jQuery%20Raty). Thanks! (:
Index: trunk/prototype/plugins/jq-raty/js/jquery.raty.js
===================================================================
--- trunk/prototype/plugins/jq-raty/js/jquery.raty.js	(revision 6528)
+++ trunk/prototype/plugins/jq-raty/js/jquery.raty.js	(revision 6528)
@@ -0,0 +1,460 @@
+/*!
+ * jQuery Raty - A Star Rating Plugin - http://wbotelhos.com/raty
+ * -------------------------------------------------------------------
+ *
+ * jQuery Raty is a plugin that generates a customizable star rating.
+ *
+ * Licensed under The MIT License
+ *
+ * @version        2.4.5
+ * @since          2010.06.11
+ * @author         Washington Botelho
+ * @documentation  wbotelhos.com/raty
+ * @twitter        twitter.com/wbotelhos
+ *
+ * Usage:
+ * -------------------------------------------------------------------
+ * $('#star').raty();
+ *
+ * <div id="star"></div>
+ *
+ */
+
+;(function($) {
+
+	var methods = {
+		init: function(settings) {
+			return this.each(function() {
+				var self	= this,
+					$this	= $(self).empty();
+	
+				self.opt = $.extend(true, {}, $.fn.raty.defaults, settings);
+
+				$this.data('settings', self.opt);
+
+				self.opt.number = methods.between(self.opt.number, 0, 20);
+
+				if (self.opt.path.substring(self.opt.path.length - 1, self.opt.path.length) != '/') {
+					self.opt.path += '/';
+				}
+
+				if (typeof self.opt.score == 'function') {
+					self.opt.score = self.opt.score.call(self);
+				}
+
+				if (self.opt.score) {
+					self.opt.score = methods.between(self.opt.score, 0, self.opt.number);					
+				}
+
+				for (var i = 1; i <= self.opt.number; i++) {
+					$('<img />', {
+						src		: self.opt.path + ((!self.opt.score || self.opt.score < i) ? self.opt.starOff : self.opt.starOn),
+						alt		: i,
+						title	: (i <= self.opt.hints.length && self.opt.hints[i - 1] !== null) ? self.opt.hints[i - 1] : i
+					}).appendTo(self);
+
+					if (self.opt.space) {
+						$this.append((i < self.opt.number) ? '&#160;' : '');
+					}
+				}
+
+				self.stars = $this.children('img:not(".raty-cancel")');
+				self.score = $('<input />', { type: 'hidden', name: self.opt.scoreName }).appendTo(self);
+
+				if (self.opt.score && self.opt.score > 0) {
+					self.score.val(self.opt.score);
+					methods.roundStar.call(self, self.opt.score);
+				}
+
+				if (self.opt.iconRange) {
+					methods.fill.call(self, self.opt.score);	
+				}
+
+				methods.setTarget.call(self, self.opt.score, self.opt.targetKeep);
+
+				var space	= self.opt.space ? 4 : 0,
+					width	= self.opt.width || (self.opt.number * self.opt.size + self.opt.number * space);
+
+				if (self.opt.cancel) {
+					self.cancel = $('<img />', { src: self.opt.path + self.opt.cancelOff, alt: 'x', title: self.opt.cancelHint, 'class': 'raty-cancel' });
+
+					if (self.opt.cancelPlace == 'left') {
+						$this.prepend('&#160;').prepend(self.cancel);
+					} else {
+						$this.append('&#160;').append(self.cancel);
+					}
+
+					width += (self.opt.size + space);
+				}
+
+				if (self.opt.readOnly) {
+					methods.fixHint.call(self);
+
+					if (self.cancel) {
+						self.cancel.hide();
+					}
+				} else {
+					$this.css('cursor', 'pointer');
+
+					methods.bindAction.call(self);
+				}
+
+				$this.css('width', width);
+			});
+		}, between: function(value, min, max) {
+			return Math.min(Math.max(parseFloat(value), min), max);
+		}, bindAction: function() {
+			var self	= this,
+				$this	= $(self);
+
+			$this.mouseleave(function() {
+				var score = self.score.val() || undefined;
+
+				methods.initialize.call(self, score);
+				methods.setTarget.call(self, score, self.opt.targetKeep);
+
+				if (self.opt.mouseover) {
+					self.opt.mouseover.call(self, score);
+				}
+			});
+
+			var action = self.opt.half ? 'mousemove' : 'mouseover';
+
+			if (self.opt.cancel) {
+				self.cancel.mouseenter(function() {
+					$(this).attr('src', self.opt.path + self.opt.cancelOn);
+
+					self.stars.attr('src', self.opt.path + self.opt.starOff);
+
+					methods.setTarget.call(self, null, true);
+
+					if (self.opt.mouseover) {
+						self.opt.mouseover.call(self, null);
+					}
+				}).mouseleave(function() {
+					$(this).attr('src', self.opt.path + self.opt.cancelOff);
+
+					if (self.opt.mouseover) {
+						self.opt.mouseover.call(self, self.score.val() || null);
+					}
+				}).click(function(evt) {
+					self.score.removeAttr('value');
+
+					if (self.opt.click) {
+			          self.opt.click.call(self, null, evt);
+			        }
+				});
+			}
+
+			self.stars.bind(action, function(evt) {
+				var value = parseInt(this.alt, 10);
+
+				if (self.opt.half) {
+					var position	= parseFloat((evt.pageX - $(this).offset().left) / self.opt.size),
+						diff		= (position > .5) ? 1 : .5;
+
+					value = parseFloat(this.alt) - 1 + diff;
+
+					methods.fill.call(self, value);
+
+					if (self.opt.precision) {
+						value = value - diff + position;
+					}
+
+					methods.showHalf.call(self, value);
+				} else {
+					methods.fill.call(self, value);
+				}
+
+				$this.data('score', value);
+
+				methods.setTarget.call(self, value, true);
+
+				if (self.opt.mouseover) {
+					self.opt.mouseover.call(self, value, evt);
+				}
+			}).click(function(evt) {
+				self.score.val((self.opt.half || self.opt.precision) ? $this.data('score') : this.alt);
+
+				if (self.opt.click) {
+					self.opt.click.call(self, self.score.val(), evt);
+				}
+			});
+		}, cancel: function(isClick) {
+			return $(this).each(function() {
+				var self	= this, 
+					$this	= $(self);
+
+				if ($this.data('readonly') === true) {
+					return this;
+				}
+
+				if (isClick) {
+					methods.click.call(self, null);
+				} else {
+					methods.score.call(self, null);
+				}
+
+				self.score.removeAttr('value');
+			});
+		}, click: function(score) {
+			return $(this).each(function() {
+				if ($(this).data('readonly') === true) {
+					return this;
+				}
+
+				methods.initialize.call(this, score);
+
+				if (this.opt.click) {
+					this.opt.click.call(this, score);
+				} else {
+					methods.error.call(this, 'you must add the "click: function(score, evt) { }" callback.');
+				}
+
+				methods.setTarget.call(this, score, true);
+			});
+		}, error: function(message) {
+			$(this).html(message);
+
+			$.error(message);
+		}, fill: function(score) {
+			var self	= this,
+				number	= self.stars.length,
+				count	= 0,
+				$star	,
+				star	,
+				icon	;
+
+			for (var i = 1; i <= number; i++) {
+				$star = self.stars.eq(i - 1);
+
+				if (self.opt.iconRange && self.opt.iconRange.length > count) {
+					star = self.opt.iconRange[count];
+
+					if (self.opt.single) {
+						icon = (i == score) ? (star.on || self.opt.starOn) : (star.off || self.opt.starOff);
+					} else {
+						icon = (i <= score) ? (star.on || self.opt.starOn) : (star.off || self.opt.starOff);
+					}
+
+					if (i <= star.range) {
+						$star.attr('src', self.opt.path + icon);
+					}
+
+					if (i == star.range) {
+						count++;
+					}
+				} else {
+					if (self.opt.single) {
+						icon = (i == score) ? self.opt.starOn : self.opt.starOff;
+					} else {
+						icon = (i <= score) ? self.opt.starOn : self.opt.starOff;
+					}
+
+					$star.attr('src', self.opt.path + icon);
+				}
+			}
+		}, fixHint: function() {
+			var $this	= $(this),
+				score	= parseInt(this.score.val(), 10),
+				hint	= this.opt.noRatedMsg;
+
+			if (!isNaN(score) && score > 0) {
+				hint = (score <= this.opt.hints.length && this.opt.hints[score - 1] !== null) ? this.opt.hints[score - 1] : score;
+			}
+
+			$this.data('readonly', true).css('cursor', 'default').attr('title', hint);
+
+			this.score.attr('readonly', 'readonly');
+			this.stars.attr('title', hint);
+		}, getScore: function() {
+			var score	= [],
+				value	;
+
+			$(this).each(function() {
+				value = this.score.val();
+
+				score.push(value ? parseFloat(value) : undefined);
+			});
+
+			return (score.length > 1) ? score : score[0];
+		}, readOnly: function(isReadOnly) {
+			return this.each(function() {
+				var $this = $(this);
+
+				if ($this.data('readonly') === isReadOnly) {
+					return this;
+				}
+
+				if (this.cancel) {
+					if (isReadOnly) {
+						this.cancel.hide();
+					} else {
+						this.cancel.show();
+					}
+				}
+
+				if (isReadOnly) {
+					$this.unbind();
+
+					$this.children('img').unbind();
+
+					methods.fixHint.call(this);
+				} else {
+					methods.bindAction.call(this);
+					methods.unfixHint.call(this);
+				}
+
+				$this.data('readonly', isReadOnly);
+			});
+		}, reload: function() {
+			return methods.set.call(this, {});
+		}, roundStar: function(score) {
+			var diff = (score - Math.floor(score)).toFixed(2);
+
+			if (diff > this.opt.round.down) {
+				var icon = this.opt.starOn;								// Full up: [x.76 .. x.99]
+
+				if (diff < this.opt.round.up && this.opt.halfShow) {	// Half: [x.26 .. x.75]
+					icon = this.opt.starHalf;
+				} else if (diff < this.opt.round.full) {				// Full down: [x.00 .. x.5]
+					icon = this.opt.starOff;
+				}
+
+				this.stars.eq(Math.ceil(score) - 1).attr('src', this.opt.path + icon);
+			}															// Full down: [x.00 .. x.25]
+		}, score: function() {
+			return arguments.length ? methods.setScore.apply(this, arguments) : methods.getScore.call(this);
+		}, set: function(settings) {
+			this.each(function() {
+				var $this	= $(this),
+					actual	= $this.data('settings'),
+					clone	= $this.clone().removeAttr('style').insertBefore($this);
+
+				$this.remove();
+
+				clone.raty($.extend(actual, settings));
+			});
+
+			return $(this.selector);
+		}, setScore: function(score) {
+			return $(this).each(function() {
+				if ($(this).data('readonly') === true) {
+					return this;
+				}
+
+				methods.initialize.call(this, score);
+				methods.setTarget.call(this, score, true);
+			});
+		}, setTarget: function(value, isKeep) {
+			if (this.opt.target) {
+				var $target = $(this.opt.target);
+
+				if ($target.length == 0) {
+					methods.error.call(this, 'target selector invalid or missing!');
+				}
+
+				var score = value;
+
+				if (!isKeep || score === undefined) {
+					score = this.opt.targetText;
+				} else {
+					if (this.opt.targetType == 'hint') {
+						score = (score === null && this.opt.cancel)
+								? this.opt.cancelHint
+								: this.opt.hints[Math.ceil(score - 1)];
+					} else {
+						score = this.opt.precision
+								? parseFloat(score).toFixed(1)
+								: parseInt(score, 10);
+					}
+				}
+
+				if (this.opt.targetFormat.indexOf('{score}') < 0) {
+					methods.error.call(this, 'template "{score}" missing!');
+				}
+
+				if (value !== null) {
+					score = this.opt.targetFormat.toString().replace('{score}', score);
+				}
+
+				if ($target.is(':input')) {
+					$target.val(score);
+				} else {
+					$target.html(score);
+				}
+			}
+		}, showHalf: function(score) {
+			var diff = (score - Math.floor(score)).toFixed(1);
+
+			if (diff > 0 && diff < .6) {
+				this.stars.eq(Math.ceil(score) - 1).attr('src', this.opt.path + this.opt.starHalf);
+			}
+		}, initialize: function(score) {
+			score = !score ? 0 : methods.between(score, 0, this.opt.number);
+
+			methods.fill.call(this, score);
+
+			if (score > 0) {
+				if (this.opt.halfShow) {
+					methods.roundStar.call(this, score);
+				}
+
+				this.score.val(score);
+			}
+		}, unfixHint: function() {
+			for (var i = 0; i < this.opt.number; i++) {
+				this.stars.eq(i).attr('title', (i < this.opt.hints.length && this.opt.hints[i] !== null) ? this.opt.hints[i] : i);
+			}
+
+			$(this).data('readonly', false).css('cursor', 'pointer').removeAttr('title');
+
+			this.score.attr('readonly', 'readonly');
+		}
+	};
+
+	$.fn.raty = function(method) {
+		if (methods[method]) {
+			return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+		} else if (typeof method === 'object' || !method) {
+			return methods.init.apply(this, arguments);
+		} else {
+			$.error('Method ' + method + ' does not exist!');
+		} 
+	};
+
+	$.fn.raty.defaults = {
+		cancel			: false,
+		cancelHint		: 'cancel this rating!',
+		cancelOff		: 'cancel-off.png',
+		cancelOn		: 'cancel-on.png',
+		cancelPlace		: 'left',
+		click			: undefined,
+		half			: false,
+		halfShow		: true,
+		hints			: ['bad', 'poor', 'regular', 'good', 'gorgeous'],
+		iconRange		: undefined,
+		mouseover		: undefined,
+		noRatedMsg		: 'not rated yet',
+		number			: 5,
+		path			: 'img/',
+		precision		: false,
+		round			: { down: .25, full: .6, up: .76 },
+		readOnly		: false,
+		score			: undefined,
+		scoreName		: 'score',
+		single			: false,
+		size			: 16,
+		space			: true,
+		starHalf		: 'star-half.png',
+		starOff			: 'star-off.png',
+		starOn			: 'star-on.png',
+		target			: undefined,
+		targetFormat	: '{score}',
+		targetKeep		: false,
+		targetText		: '',
+		targetType		: 'hint',
+		width			: undefined
+	};
+
+})(jQuery);
Index: trunk/prototype/plugins/jq-raty/js/jquery.raty.min.js
===================================================================
--- trunk/prototype/plugins/jq-raty/js/jquery.raty.min.js	(revision 6528)
+++ trunk/prototype/plugins/jq-raty/js/jquery.raty.min.js	(revision 6528)
@@ -0,0 +1,12 @@
+/*!
+ * jQuery Raty - A Star Rating Plugin
+ *
+ * Licensed under The MIT License
+ *
+ * @version        2.4.5
+ * @author         Washington Botelho
+ * @documentation  wbotelhos.com/raty
+ * 
+ */
+
+;(function(b){var a={init:function(c){return this.each(function(){var d=this,h=b(d).empty();d.opt=b.extend(true,{},b.fn.raty.defaults,c);h.data("settings",d.opt);d.opt.number=a.between(d.opt.number,0,20);if(d.opt.path.substring(d.opt.path.length-1,d.opt.path.length)!="/"){d.opt.path+="/";}if(typeof d.opt.score=="function"){d.opt.score=d.opt.score.call(d);}if(d.opt.score){d.opt.score=a.between(d.opt.score,0,d.opt.number);}for(var e=1;e<=d.opt.number;e++){b("<img />",{src:d.opt.path+((!d.opt.score||d.opt.score<e)?d.opt.starOff:d.opt.starOn),alt:e,title:(e<=d.opt.hints.length&&d.opt.hints[e-1]!==null)?d.opt.hints[e-1]:e}).appendTo(d);if(d.opt.space){h.append((e<d.opt.number)?"&#160;":"");}}d.stars=h.children('img:not(".raty-cancel")');d.score=b("<input />",{type:"hidden",name:d.opt.scoreName}).appendTo(d);if(d.opt.score&&d.opt.score>0){d.score.val(d.opt.score);a.roundStar.call(d,d.opt.score);}if(d.opt.iconRange){a.fill.call(d,d.opt.score);}a.setTarget.call(d,d.opt.score,d.opt.targetKeep);var g=d.opt.space?4:0,f=d.opt.width||(d.opt.number*d.opt.size+d.opt.number*g);if(d.opt.cancel){d.cancel=b("<img />",{src:d.opt.path+d.opt.cancelOff,alt:"x",title:d.opt.cancelHint,"class":"raty-cancel"});if(d.opt.cancelPlace=="left"){h.prepend("&#160;").prepend(d.cancel);}else{h.append("&#160;").append(d.cancel);}f+=(d.opt.size+g);}if(d.opt.readOnly){a.fixHint.call(d);if(d.cancel){d.cancel.hide();}}else{h.css("cursor","pointer");a.bindAction.call(d);}h.css("width",f);});},between:function(e,d,c){return Math.min(Math.max(parseFloat(e),d),c);},bindAction:function(){var c=this,e=b(c);e.mouseleave(function(){var f=c.score.val()||undefined;a.initialize.call(c,f);a.setTarget.call(c,f,c.opt.targetKeep);if(c.opt.mouseover){c.opt.mouseover.call(c,f);}});var d=c.opt.half?"mousemove":"mouseover";if(c.opt.cancel){c.cancel.mouseenter(function(){b(this).attr("src",c.opt.path+c.opt.cancelOn);c.stars.attr("src",c.opt.path+c.opt.starOff);a.setTarget.call(c,null,true);if(c.opt.mouseover){c.opt.mouseover.call(c,null);}}).mouseleave(function(){b(this).attr("src",c.opt.path+c.opt.cancelOff);if(c.opt.mouseover){c.opt.mouseover.call(c,c.score.val()||null);}}).click(function(f){c.score.removeAttr("value");if(c.opt.click){c.opt.click.call(c,null,f);}});}c.stars.bind(d,function(g){var h=parseInt(this.alt,10);if(c.opt.half){var f=parseFloat((g.pageX-b(this).offset().left)/c.opt.size),i=(f>0.5)?1:0.5;h=parseFloat(this.alt)-1+i;a.fill.call(c,h);if(c.opt.precision){h=h-i+f;}a.showHalf.call(c,h);}else{a.fill.call(c,h);}e.data("score",h);a.setTarget.call(c,h,true);if(c.opt.mouseover){c.opt.mouseover.call(c,h,g);}}).click(function(f){c.score.val((c.opt.half||c.opt.precision)?e.data("score"):this.alt);if(c.opt.click){c.opt.click.call(c,c.score.val(),f);}});},cancel:function(c){return b(this).each(function(){var d=this,e=b(d);if(e.data("readonly")===true){return this;}if(c){a.click.call(d,null);}else{a.score.call(d,null);}d.score.removeAttr("value");});},click:function(c){return b(this).each(function(){if(b(this).data("readonly")===true){return this;}a.initialize.call(this,c);if(this.opt.click){this.opt.click.call(this,c);}else{a.error.call(this,'you must add the "click: function(score, evt) { }" callback.');}a.setTarget.call(this,c,true);});},error:function(c){b(this).html(c);b.error(c);},fill:function(k){var c=this,h=c.stars.length,g=0,d,j,f;for(var e=1;e<=h;e++){d=c.stars.eq(e-1);if(c.opt.iconRange&&c.opt.iconRange.length>g){j=c.opt.iconRange[g];if(c.opt.single){f=(e==k)?(j.on||c.opt.starOn):(j.off||c.opt.starOff);}else{f=(e<=k)?(j.on||c.opt.starOn):(j.off||c.opt.starOff);}if(e<=j.range){d.attr("src",c.opt.path+f);}if(e==j.range){g++;}}else{if(c.opt.single){f=(e==k)?c.opt.starOn:c.opt.starOff;}else{f=(e<=k)?c.opt.starOn:c.opt.starOff;}d.attr("src",c.opt.path+f);}}},fixHint:function(){var c=b(this),e=parseInt(this.score.val(),10),d=this.opt.noRatedMsg;if(!isNaN(e)&&e>0){d=(e<=this.opt.hints.length&&this.opt.hints[e-1]!==null)?this.opt.hints[e-1]:e;}c.data("readonly",true).css("cursor","default").attr("title",d);this.score.attr("readonly","readonly");this.stars.attr("title",d);},getScore:function(){var d=[],c;b(this).each(function(){c=this.score.val();d.push(c?parseFloat(c):undefined);});return(d.length>1)?d:d[0];},readOnly:function(c){return this.each(function(){var d=b(this);if(d.data("readonly")===c){return this;}if(this.cancel){if(c){this.cancel.hide();}else{this.cancel.show();}}if(c){d.unbind();d.children("img").unbind();a.fixHint.call(this);}else{a.bindAction.call(this);a.unfixHint.call(this);}d.data("readonly",c);});},reload:function(){return a.set.call(this,{});},roundStar:function(e){var d=(e-Math.floor(e)).toFixed(2);if(d>this.opt.round.down){var c=this.opt.starOn;if(d<this.opt.round.up&&this.opt.halfShow){c=this.opt.starHalf;}else{if(d<this.opt.round.full){c=this.opt.starOff;}}this.stars.eq(Math.ceil(e)-1).attr("src",this.opt.path+c);}},score:function(){return arguments.length?a.setScore.apply(this,arguments):a.getScore.call(this);},set:function(c){this.each(function(){var d=b(this),f=d.data("settings"),e=d.clone().removeAttr("style").insertBefore(d);d.remove();e.raty(b.extend(f,c));});return b(this.selector);},setScore:function(c){return b(this).each(function(){if(b(this).data("readonly")===true){return this;}a.initialize.call(this,c);a.setTarget.call(this,c,true);});},setTarget:function(e,d){if(this.opt.target){var c=b(this.opt.target);if(c.length==0){a.error.call(this,"target selector invalid or missing!");}var f=e;if(!d||f===undefined){f=this.opt.targetText;}else{if(this.opt.targetType=="hint"){f=(f===null&&this.opt.cancel)?this.opt.cancelHint:this.opt.hints[Math.ceil(f-1)];}else{f=this.opt.precision?parseFloat(f).toFixed(1):parseInt(f,10);}}if(this.opt.targetFormat.indexOf("{score}")<0){a.error.call(this,'template "{score}" missing!');}if(e!==null){f=this.opt.targetFormat.toString().replace("{score}",f);}if(c.is(":input")){c.val(f);}else{c.html(f);}}},showHalf:function(d){var c=(d-Math.floor(d)).toFixed(1);if(c>0&&c<0.6){this.stars.eq(Math.ceil(d)-1).attr("src",this.opt.path+this.opt.starHalf);}},initialize:function(c){c=!c?0:a.between(c,0,this.opt.number);a.fill.call(this,c);if(c>0){if(this.opt.halfShow){a.roundStar.call(this,c);}this.score.val(c);}},unfixHint:function(){for(var c=0;c<this.opt.number;c++){this.stars.eq(c).attr("title",(c<this.opt.hints.length&&this.opt.hints[c]!==null)?this.opt.hints[c]:c);}b(this).data("readonly",false).css("cursor","pointer").removeAttr("title");this.score.attr("readonly","readonly");}};b.fn.raty=function(c){if(a[c]){return a[c].apply(this,Array.prototype.slice.call(arguments,1));}else{if(typeof c==="object"||!c){return a.init.apply(this,arguments);}else{b.error("Method "+c+" does not exist!");}}};b.fn.raty.defaults={cancel:false,cancelHint:"cancel this rating!",cancelOff:"cancel-off.png",cancelOn:"cancel-on.png",cancelPlace:"left",click:undefined,half:false,halfShow:true,hints:["bad","poor","regular","good","gorgeous"],iconRange:undefined,mouseover:undefined,noRatedMsg:"not rated yet",number:5,path:"img/",precision:false,round:{down:0.25,full:0.6,up:0.76},readOnly:false,score:undefined,scoreName:"score",single:false,size:16,space:true,starHalf:"star-half.png",starOff:"star-off.png",starOn:"star-on.png",target:undefined,targetFormat:"{score}",targetKeep:false,targetText:"",targetType:"hint",width:undefined};})(jQuery);
Index: trunk/prototype/plugins/jq-raty/js/jquery.min.js
===================================================================
--- trunk/prototype/plugins/jq-raty/js/jquery.min.js	(revision 6528)
+++ trunk/prototype/plugins/jq-raty/js/jquery.min.js	(revision 6528)
@@ -0,0 +1,23 @@
+/*!
+ * jQuery JavaScript Library v1.7.2
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Wed Mar 21 12:46:34 2012 -0700
+ */
+;(function(bd,L){var av=bd.document,bu=bd.navigator,bm=bd.location;var b=(function(){var bF=function(b0,b1){return new bF.fn.init(b0,b1,bD);},bU=bd.jQuery,bH=bd.$,bD,bY=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,bM=/\S/,bI=/^\s+/,bE=/\s+$/,bA=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,bN=/^[\],:{}\s]*$/,bW=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,bP=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,bJ=/(?:^|:|,)(?:\s*\[)+/g,by=/(webkit)[ \/]([\w.]+)/,bR=/(opera)(?:.*version)?[ \/]([\w.]+)/,bQ=/(msie) ([\w.]+)/,bS=/(mozilla)(?:.*? rv:([\w.]+))?/,bB=/-([a-z]|[0-9])/ig,bZ=/^-ms-/,bT=function(b0,b1){return(b1+"").toUpperCase();},bX=bu.userAgent,bV,bC,e,bL=Object.prototype.toString,bG=Object.prototype.hasOwnProperty,bz=Array.prototype.push,bK=Array.prototype.slice,bO=String.prototype.trim,bv=Array.prototype.indexOf,bx={};bF.fn=bF.prototype={constructor:bF,init:function(b0,b4,b3){var b2,b5,b1,b6;if(!b0){return this;}if(b0.nodeType){this.context=this[0]=b0;this.length=1;return this;}if(b0==="body"&&!b4&&av.body){this.context=av;this[0]=av.body;this.selector=b0;this.length=1;return this;}if(typeof b0==="string"){if(b0.charAt(0)==="<"&&b0.charAt(b0.length-1)===">"&&b0.length>=3){b2=[null,b0,null];}else{b2=bY.exec(b0);}if(b2&&(b2[1]||!b4)){if(b2[1]){b4=b4 instanceof bF?b4[0]:b4;b6=(b4?b4.ownerDocument||b4:av);b1=bA.exec(b0);if(b1){if(bF.isPlainObject(b4)){b0=[av.createElement(b1[1])];bF.fn.attr.call(b0,b4,true);}else{b0=[b6.createElement(b1[1])];}}else{b1=bF.buildFragment([b2[1]],[b6]);b0=(b1.cacheable?bF.clone(b1.fragment):b1.fragment).childNodes;}return bF.merge(this,b0);}else{b5=av.getElementById(b2[2]);if(b5&&b5.parentNode){if(b5.id!==b2[2]){return b3.find(b0);}this.length=1;this[0]=b5;}this.context=av;this.selector=b0;return this;}}else{if(!b4||b4.jquery){return(b4||b3).find(b0);}else{return this.constructor(b4).find(b0);}}}else{if(bF.isFunction(b0)){return b3.ready(b0);}}if(b0.selector!==L){this.selector=b0.selector;this.context=b0.context;}return bF.makeArray(b0,this);},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length;},toArray:function(){return bK.call(this,0);},get:function(b0){return b0==null?this.toArray():(b0<0?this[this.length+b0]:this[b0]);},pushStack:function(b1,b3,b0){var b2=this.constructor();if(bF.isArray(b1)){bz.apply(b2,b1);}else{bF.merge(b2,b1);}b2.prevObject=this;b2.context=this.context;if(b3==="find"){b2.selector=this.selector+(this.selector?" ":"")+b0;}else{if(b3){b2.selector=this.selector+"."+b3+"("+b0+")";}}return b2;},each:function(b1,b0){return bF.each(this,b1,b0);},ready:function(b0){bF.bindReady();bC.add(b0);return this;},eq:function(b0){b0=+b0;return b0===-1?this.slice(b0):this.slice(b0,b0+1);},first:function(){return this.eq(0);},last:function(){return this.eq(-1);},slice:function(){return this.pushStack(bK.apply(this,arguments),"slice",bK.call(arguments).join(","));},map:function(b0){return this.pushStack(bF.map(this,function(b2,b1){return b0.call(b2,b1,b2);}));},end:function(){return this.prevObject||this.constructor(null);},push:bz,sort:[].sort,splice:[].splice};bF.fn.init.prototype=bF.fn;bF.extend=bF.fn.extend=function(){var b9,b2,b0,b1,b6,b7,b5=arguments[0]||{},b4=1,b3=arguments.length,b8=false;if(typeof b5==="boolean"){b8=b5;b5=arguments[1]||{};b4=2;}if(typeof b5!=="object"&&!bF.isFunction(b5)){b5={};}if(b3===b4){b5=this;--b4;}for(;b4<b3;b4++){if((b9=arguments[b4])!=null){for(b2 in b9){b0=b5[b2];b1=b9[b2];if(b5===b1){continue;}if(b8&&b1&&(bF.isPlainObject(b1)||(b6=bF.isArray(b1)))){if(b6){b6=false;b7=b0&&bF.isArray(b0)?b0:[];}else{b7=b0&&bF.isPlainObject(b0)?b0:{};}b5[b2]=bF.extend(b8,b7,b1);}else{if(b1!==L){b5[b2]=b1;}}}}}return b5;};bF.extend({noConflict:function(b0){if(bd.$===bF){bd.$=bH;}if(b0&&bd.jQuery===bF){bd.jQuery=bU;}return bF;},isReady:false,readyWait:1,holdReady:function(b0){if(b0){bF.readyWait++;}else{bF.ready(true);}},ready:function(b0){if((b0===true&&!--bF.readyWait)||(b0!==true&&!bF.isReady)){if(!av.body){return setTimeout(bF.ready,1);}bF.isReady=true;if(b0!==true&&--bF.readyWait>0){return;}bC.fireWith(av,[bF]);if(bF.fn.trigger){bF(av).trigger("ready").off("ready");}}},bindReady:function(){if(bC){return;}bC=bF.Callbacks("once memory");if(av.readyState==="complete"){return setTimeout(bF.ready,1);}if(av.addEventListener){av.addEventListener("DOMContentLoaded",e,false);bd.addEventListener("load",bF.ready,false);}else{if(av.attachEvent){av.attachEvent("onreadystatechange",e);bd.attachEvent("onload",bF.ready);var b0=false;try{b0=bd.frameElement==null;}catch(b1){}if(av.documentElement.doScroll&&b0){bw();}}}},isFunction:function(b0){return bF.type(b0)==="function";},isArray:Array.isArray||function(b0){return bF.type(b0)==="array";},isWindow:function(b0){return b0!=null&&b0==b0.window;},isNumeric:function(b0){return !isNaN(parseFloat(b0))&&isFinite(b0);},type:function(b0){return b0==null?String(b0):bx[bL.call(b0)]||"object";},isPlainObject:function(b2){if(!b2||bF.type(b2)!=="object"||b2.nodeType||bF.isWindow(b2)){return false;}try{if(b2.constructor&&!bG.call(b2,"constructor")&&!bG.call(b2.constructor.prototype,"isPrototypeOf")){return false;}}catch(b1){return false;}var b0;for(b0 in b2){}return b0===L||bG.call(b2,b0);},isEmptyObject:function(b1){for(var b0 in b1){return false;}return true;},error:function(b0){throw new Error(b0);},parseJSON:function(b0){if(typeof b0!=="string"||!b0){return null;}b0=bF.trim(b0);if(bd.JSON&&bd.JSON.parse){return bd.JSON.parse(b0);}if(bN.test(b0.replace(bW,"@").replace(bP,"]").replace(bJ,""))){return(new Function("return "+b0))();}bF.error("Invalid JSON: "+b0);},parseXML:function(b2){if(typeof b2!=="string"||!b2){return null;}var b0,b1;try{if(bd.DOMParser){b1=new DOMParser();b0=b1.parseFromString(b2,"text/xml");}else{b0=new ActiveXObject("Microsoft.XMLDOM");b0.async="false";b0.loadXML(b2);}}catch(b3){b0=L;}if(!b0||!b0.documentElement||b0.getElementsByTagName("parsererror").length){bF.error("Invalid XML: "+b2);}return b0;},noop:function(){},globalEval:function(b0){if(b0&&bM.test(b0)){(bd.execScript||function(b1){bd["eval"].call(bd,b1);})(b0);}},camelCase:function(b0){return b0.replace(bZ,"ms-").replace(bB,bT);},nodeName:function(b1,b0){return b1.nodeName&&b1.nodeName.toUpperCase()===b0.toUpperCase();},each:function(b3,b6,b2){var b1,b4=0,b5=b3.length,b0=b5===L||bF.isFunction(b3);if(b2){if(b0){for(b1 in b3){if(b6.apply(b3[b1],b2)===false){break;}}}else{for(;b4<b5;){if(b6.apply(b3[b4++],b2)===false){break;}}}}else{if(b0){for(b1 in b3){if(b6.call(b3[b1],b1,b3[b1])===false){break;}}}else{for(;b4<b5;){if(b6.call(b3[b4],b4,b3[b4++])===false){break;}}}}return b3;},trim:bO?function(b0){return b0==null?"":bO.call(b0);}:function(b0){return b0==null?"":b0.toString().replace(bI,"").replace(bE,"");},makeArray:function(b3,b1){var b0=b1||[];if(b3!=null){var b2=bF.type(b3);if(b3.length==null||b2==="string"||b2==="function"||b2==="regexp"||bF.isWindow(b3)){bz.call(b0,b3);}else{bF.merge(b0,b3);}}return b0;},inArray:function(b2,b3,b1){var b0;if(b3){if(bv){return bv.call(b3,b2,b1);}b0=b3.length;b1=b1?b1<0?Math.max(0,b0+b1):b1:0;for(;b1<b0;b1++){if(b1 in b3&&b3[b1]===b2){return b1;}}}return -1;},merge:function(b4,b2){var b3=b4.length,b1=0;if(typeof b2.length==="number"){for(var b0=b2.length;b1<b0;b1++){b4[b3++]=b2[b1];}}else{while(b2[b1]!==L){b4[b3++]=b2[b1++];}}b4.length=b3;return b4;},grep:function(b1,b6,b0){var b2=[],b5;b0=!!b0;for(var b3=0,b4=b1.length;b3<b4;b3++){b5=!!b6(b1[b3],b3);if(b0!==b5){b2.push(b1[b3]);}}return b2;},map:function(b0,b7,b8){var b5,b6,b4=[],b2=0,b1=b0.length,b3=b0 instanceof bF||b1!==L&&typeof b1==="number"&&((b1>0&&b0[0]&&b0[b1-1])||b1===0||bF.isArray(b0));if(b3){for(;b2<b1;b2++){b5=b7(b0[b2],b2,b8);if(b5!=null){b4[b4.length]=b5;}}}else{for(b6 in b0){b5=b7(b0[b6],b6,b8);if(b5!=null){b4[b4.length]=b5;}}}return b4.concat.apply([],b4);},guid:1,proxy:function(b4,b3){if(typeof b3==="string"){var b2=b4[b3];b3=b4;b4=b2;}if(!bF.isFunction(b4)){return L;}var b0=bK.call(arguments,2),b1=function(){return b4.apply(b3,b0.concat(bK.call(arguments)));};b1.guid=b4.guid=b4.guid||b1.guid||bF.guid++;return b1;},access:function(b0,b6,b9,b7,b4,ca,b8){var b2,b5=b9==null,b3=0,b1=b0.length;if(b9&&typeof b9==="object"){for(b3 in b9){bF.access(b0,b6,b3,b9[b3],1,ca,b7);}b4=1;}else{if(b7!==L){b2=b8===L&&bF.isFunction(b7);if(b5){if(b2){b2=b6;b6=function(cc,cb,cd){return b2.call(bF(cc),cd);};}else{b6.call(b0,b7);b6=null;}}if(b6){for(;b3<b1;b3++){b6(b0[b3],b9,b2?b7.call(b0[b3],b3,b6(b0[b3],b9)):b7,b8);}}b4=1;}}return b4?b0:b5?b6.call(b0):b1?b6(b0[0],b9):ca;},now:function(){return(new Date()).getTime();},uaMatch:function(b1){b1=b1.toLowerCase();var b0=by.exec(b1)||bR.exec(b1)||bQ.exec(b1)||b1.indexOf("compatible")<0&&bS.exec(b1)||[];return{browser:b0[1]||"",version:b0[2]||"0"};},sub:function(){function b0(b3,b4){return new b0.fn.init(b3,b4);}bF.extend(true,b0,this);b0.superclass=this;b0.fn=b0.prototype=this();b0.fn.constructor=b0;b0.sub=this.sub;b0.fn.init=function b2(b3,b4){if(b4&&b4 instanceof bF&&!(b4 instanceof b0)){b4=b0(b4);}return bF.fn.init.call(this,b3,b4,b1);};b0.fn.init.prototype=b0.fn;var b1=b0(av);return b0;},browser:{}});bF.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(b1,b0){bx["[object "+b0+"]"]=b0.toLowerCase();});bV=bF.uaMatch(bX);if(bV.browser){bF.browser[bV.browser]=true;bF.browser.version=bV.version;}if(bF.browser.webkit){bF.browser.safari=true;}if(bM.test("\xA0")){bI=/^[\s\xA0]+/;bE=/[\s\xA0]+$/;}bD=bF(av);if(av.addEventListener){e=function(){av.removeEventListener("DOMContentLoaded",e,false);bF.ready();};}else{if(av.attachEvent){e=function(){if(av.readyState==="complete"){av.detachEvent("onreadystatechange",e);bF.ready();}};}}function bw(){if(bF.isReady){return;}try{av.documentElement.doScroll("left");}catch(b0){setTimeout(bw,1);return;}bF.ready();}return bF;})();var a3={};function X(e){var bv=a3[e]={},bw,bx;e=e.split(/\s+/);for(bw=0,bx=e.length;bw<bx;bw++){bv[e[bw]]=true;}return bv;}b.Callbacks=function(bx){bx=bx?(a3[bx]||X(bx)):{};var bC=[],bD=[],by,e,bz,bw,bA,bB,bF=function(bG){var bH,bK,bJ,bI,bL;for(bH=0,bK=bG.length;bH<bK;bH++){bJ=bG[bH];bI=b.type(bJ);if(bI==="array"){bF(bJ);}else{if(bI==="function"){if(!bx.unique||!bE.has(bJ)){bC.push(bJ);}}}}},bv=function(bH,bG){bG=bG||[];by=!bx.memory||[bH,bG];e=true;bz=true;bB=bw||0;bw=0;bA=bC.length;for(;bC&&bB<bA;bB++){if(bC[bB].apply(bH,bG)===false&&bx.stopOnFalse){by=true;break;}}bz=false;if(bC){if(!bx.once){if(bD&&bD.length){by=bD.shift();bE.fireWith(by[0],by[1]);}}else{if(by===true){bE.disable();}else{bC=[];}}}},bE={add:function(){if(bC){var bG=bC.length;bF(arguments);if(bz){bA=bC.length;}else{if(by&&by!==true){bw=bG;bv(by[0],by[1]);}}}return this;},remove:function(){if(bC){var bG=arguments,bI=0,bJ=bG.length;for(;bI<bJ;bI++){for(var bH=0;bH<bC.length;bH++){if(bG[bI]===bC[bH]){if(bz){if(bH<=bA){bA--;if(bH<=bB){bB--;}}}bC.splice(bH--,1);if(bx.unique){break;}}}}}return this;},has:function(bH){if(bC){var bG=0,bI=bC.length;for(;bG<bI;bG++){if(bH===bC[bG]){return true;}}}return false;},empty:function(){bC=[];return this;},disable:function(){bC=bD=by=L;return this;},disabled:function(){return !bC;},lock:function(){bD=L;if(!by||by===true){bE.disable();}return this;},locked:function(){return !bD;},fireWith:function(bH,bG){if(bD){if(bz){if(!bx.once){bD.push([bH,bG]);}}else{if(!(bx.once&&by)){bv(bH,bG);}}}return this;},fire:function(){bE.fireWith(this,arguments);return this;},fired:function(){return !!e;}};return bE;};var aK=[].slice;b.extend({Deferred:function(by){var bx=b.Callbacks("once memory"),bw=b.Callbacks("once memory"),bv=b.Callbacks("memory"),e="pending",bA={resolve:bx,reject:bw,notify:bv},bC={done:bx.add,fail:bw.add,progress:bv.add,state:function(){return e;},isResolved:bx.fired,isRejected:bw.fired,then:function(bE,bD,bF){bB.done(bE).fail(bD).progress(bF);return this;},always:function(){bB.done.apply(bB,arguments).fail.apply(bB,arguments);return this;},pipe:function(bF,bE,bD){return b.Deferred(function(bG){b.each({done:[bF,"resolve"],fail:[bE,"reject"],progress:[bD,"notify"]},function(bI,bL){var bH=bL[0],bK=bL[1],bJ;if(b.isFunction(bH)){bB[bI](function(){bJ=bH.apply(this,arguments);if(bJ&&b.isFunction(bJ.promise)){bJ.promise().then(bG.resolve,bG.reject,bG.notify);}else{bG[bK+"With"](this===bB?bG:this,[bJ]);}});}else{bB[bI](bG[bK]);}});}).promise();},promise:function(bE){if(bE==null){bE=bC;}else{for(var bD in bC){bE[bD]=bC[bD];}}return bE;}},bB=bC.promise({}),bz;for(bz in bA){bB[bz]=bA[bz].fire;bB[bz+"With"]=bA[bz].fireWith;}bB.done(function(){e="resolved";},bw.disable,bv.lock).fail(function(){e="rejected";},bx.disable,bv.lock);if(by){by.call(bB,bB);}return bB;},when:function(bA){var bx=aK.call(arguments,0),bv=0,e=bx.length,bB=new Array(e),bw=e,by=e,bC=e<=1&&bA&&b.isFunction(bA.promise)?bA:b.Deferred(),bE=bC.promise();function bD(bF){return function(bG){bx[bF]=arguments.length>1?aK.call(arguments,0):bG;if(!(--bw)){bC.resolveWith(bC,bx);}};}function bz(bF){return function(bG){bB[bF]=arguments.length>1?aK.call(arguments,0):bG;bC.notifyWith(bE,bB);};}if(e>1){for(;bv<e;bv++){if(bx[bv]&&bx[bv].promise&&b.isFunction(bx[bv].promise)){bx[bv].promise().then(bD(bv),bC.reject,bz(bv));}else{--bw;}}if(!bw){bC.resolveWith(bC,bx);}}else{if(bC!==bA){bC.resolveWith(bC,e?[bA]:[]);}}return bE;}});b.support=(function(){var bI,bH,bE,bF,bx,bD,bC,bz,bJ,bA,by,bw,bv=av.createElement("div"),bG=av.documentElement;bv.setAttribute("className","t");bv.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";bH=bv.getElementsByTagName("*");bE=bv.getElementsByTagName("a")[0];if(!bH||!bH.length||!bE){return{};}bF=av.createElement("select");bx=bF.appendChild(av.createElement("option"));bD=bv.getElementsByTagName("input")[0];bI={leadingWhitespace:(bv.firstChild.nodeType===3),tbody:!bv.getElementsByTagName("tbody").length,htmlSerialize:!!bv.getElementsByTagName("link").length,style:/top/.test(bE.getAttribute("style")),hrefNormalized:(bE.getAttribute("href")==="/a"),opacity:/^0.55/.test(bE.style.opacity),cssFloat:!!bE.style.cssFloat,checkOn:(bD.value==="on"),optSelected:bx.selected,getSetAttribute:bv.className!=="t",enctype:!!av.createElement("form").enctype,html5Clone:av.createElement("nav").cloneNode(true).outerHTML!=="<:nav></:nav>",submitBubbles:true,changeBubbles:true,focusinBubbles:false,deleteExpando:true,noCloneEvent:true,inlineBlockNeedsLayout:false,shrinkWrapBlocks:false,reliableMarginRight:true,pixelMargin:true};b.boxModel=bI.boxModel=(av.compatMode==="CSS1Compat");bD.checked=true;bI.noCloneChecked=bD.cloneNode(true).checked;bF.disabled=true;bI.optDisabled=!bx.disabled;try{delete bv.test;}catch(bB){bI.deleteExpando=false;}if(!bv.addEventListener&&bv.attachEvent&&bv.fireEvent){bv.attachEvent("onclick",function(){bI.noCloneEvent=false;});bv.cloneNode(true).fireEvent("onclick");}bD=av.createElement("input");bD.value="t";bD.setAttribute("type","radio");bI.radioValue=bD.value==="t";bD.setAttribute("checked","checked");bD.setAttribute("name","t");bv.appendChild(bD);bC=av.createDocumentFragment();bC.appendChild(bv.lastChild);bI.checkClone=bC.cloneNode(true).cloneNode(true).lastChild.checked;bI.appendChecked=bD.checked;bC.removeChild(bD);bC.appendChild(bv);if(bv.attachEvent){for(by in {submit:1,change:1,focusin:1}){bA="on"+by;bw=(bA in bv);if(!bw){bv.setAttribute(bA,"return;");bw=(typeof bv[bA]==="function");}bI[by+"Bubbles"]=bw;}}bC.removeChild(bv);bC=bF=bx=bv=bD=null;b(function(){var bM,bV,bW,bU,bO,bP,bR,bL,bK,bQ,bN,e,bT,bS=av.getElementsByTagName("body")[0];if(!bS){return;}bL=1;bT="padding:0;margin:0;border:";bN="position:absolute;top:0;left:0;width:1px;height:1px;";e=bT+"0;visibility:hidden;";bK="style='"+bN+bT+"5px solid #000;";bQ="<div "+bK+"display:block;'><div style='"+bT+"0;display:block;overflow:hidden;'></div></div><table "+bK+"' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";bM=av.createElement("div");bM.style.cssText=e+"width:0;height:0;position:static;top:0;margin-top:"+bL+"px";bS.insertBefore(bM,bS.firstChild);bv=av.createElement("div");bM.appendChild(bv);bv.innerHTML="<table><tr><td style='"+bT+"0;display:none'></td><td>t</td></tr></table>";bz=bv.getElementsByTagName("td");bw=(bz[0].offsetHeight===0);bz[0].style.display="";bz[1].style.display="none";bI.reliableHiddenOffsets=bw&&(bz[0].offsetHeight===0);if(bd.getComputedStyle){bv.innerHTML="";bR=av.createElement("div");bR.style.width="0";bR.style.marginRight="0";bv.style.width="2px";bv.appendChild(bR);bI.reliableMarginRight=(parseInt((bd.getComputedStyle(bR,null)||{marginRight:0}).marginRight,10)||0)===0;}if(typeof bv.style.zoom!=="undefined"){bv.innerHTML="";bv.style.width=bv.style.padding="1px";bv.style.border=0;bv.style.overflow="hidden";bv.style.display="inline";bv.style.zoom=1;bI.inlineBlockNeedsLayout=(bv.offsetWidth===3);bv.style.display="block";bv.style.overflow="visible";bv.innerHTML="<div style='width:5px;'></div>";bI.shrinkWrapBlocks=(bv.offsetWidth!==3);}bv.style.cssText=bN+e;bv.innerHTML=bQ;bV=bv.firstChild;bW=bV.firstChild;bO=bV.nextSibling.firstChild.firstChild;bP={doesNotAddBorder:(bW.offsetTop!==5),doesAddBorderForTableAndCells:(bO.offsetTop===5)};bW.style.position="fixed";bW.style.top="20px";bP.fixedPosition=(bW.offsetTop===20||bW.offsetTop===15);bW.style.position=bW.style.top="";bV.style.overflow="hidden";bV.style.position="relative";bP.subtractsBorderForOverflowNotVisible=(bW.offsetTop===-5);bP.doesNotIncludeMarginInBodyOffset=(bS.offsetTop!==bL);if(bd.getComputedStyle){bv.style.marginTop="1%";bI.pixelMargin=(bd.getComputedStyle(bv,null)||{marginTop:0}).marginTop!=="1%";}if(typeof bM.style.zoom!=="undefined"){bM.style.zoom=1;}bS.removeChild(bM);bR=bv=bM=null;b.extend(bI,bP);});return bI;})();var aT=/^(?:\{.*\}|\[.*\])$/,aA=/([A-Z])/g;b.extend({cache:{},uuid:0,expando:"jQuery"+(b.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},hasData:function(e){e=e.nodeType?b.cache[e[b.expando]]:e[b.expando];return !!e&&!S(e);},data:function(bx,bv,bz,by){if(!b.acceptData(bx)){return;}var bG,bA,bD,bE=b.expando,bC=typeof bv==="string",bF=bx.nodeType,e=bF?b.cache:bx,bw=bF?bx[bE]:bx[bE]&&bE,bB=bv==="events";if((!bw||!e[bw]||(!bB&&!by&&!e[bw].data))&&bC&&bz===L){return;}if(!bw){if(bF){bx[bE]=bw=++b.uuid;}else{bw=bE;}}if(!e[bw]){e[bw]={};if(!bF){e[bw].toJSON=b.noop;}}if(typeof bv==="object"||typeof bv==="function"){if(by){e[bw]=b.extend(e[bw],bv);}else{e[bw].data=b.extend(e[bw].data,bv);}}bG=bA=e[bw];if(!by){if(!bA.data){bA.data={};}bA=bA.data;}if(bz!==L){bA[b.camelCase(bv)]=bz;}if(bB&&!bA[bv]){return bG.events;}if(bC){bD=bA[bv];if(bD==null){bD=bA[b.camelCase(bv)];}}else{bD=bA;}return bD;},removeData:function(bx,bv,by){if(!b.acceptData(bx)){return;}var bB,bA,bz,bC=b.expando,bD=bx.nodeType,e=bD?b.cache:bx,bw=bD?bx[bC]:bC;if(!e[bw]){return;}if(bv){bB=by?e[bw]:e[bw].data;if(bB){if(!b.isArray(bv)){if(bv in bB){bv=[bv];}else{bv=b.camelCase(bv);if(bv in bB){bv=[bv];}else{bv=bv.split(" ");}}}for(bA=0,bz=bv.length;bA<bz;bA++){delete bB[bv[bA]];}if(!(by?S:b.isEmptyObject)(bB)){return;}}}if(!by){delete e[bw].data;if(!S(e[bw])){return;}}if(b.support.deleteExpando||!e.setInterval){delete e[bw];}else{e[bw]=null;}if(bD){if(b.support.deleteExpando){delete bx[bC];}else{if(bx.removeAttribute){bx.removeAttribute(bC);}else{bx[bC]=null;}}}},_data:function(bv,e,bw){return b.data(bv,e,bw,true);},acceptData:function(bv){if(bv.nodeName){var e=b.noData[bv.nodeName.toLowerCase()];if(e){return !(e===true||bv.getAttribute("classid")!==e);}}return true;}});b.fn.extend({data:function(bD,bC){var by,bv,bB,e,bx,bw=this[0],bA=0,bz=null;if(bD===L){if(this.length){bz=b.data(bw);if(bw.nodeType===1&&!b._data(bw,"parsedAttrs")){bB=bw.attributes;for(bx=bB.length;bA<bx;bA++){e=bB[bA].name;if(e.indexOf("data-")===0){e=b.camelCase(e.substring(5));a6(bw,e,bz[e]);}}b._data(bw,"parsedAttrs",true);}}return bz;}if(typeof bD==="object"){return this.each(function(){b.data(this,bD);});}by=bD.split(".",2);by[1]=by[1]?"."+by[1]:"";bv=by[1]+"!";return b.access(this,function(bE){if(bE===L){bz=this.triggerHandler("getData"+bv,[by[0]]);if(bz===L&&bw){bz=b.data(bw,bD);bz=a6(bw,bD,bz);}return bz===L&&by[1]?this.data(by[0]):bz;}by[1]=bE;this.each(function(){var bF=b(this);bF.triggerHandler("setData"+bv,by);b.data(this,bD,bE);bF.triggerHandler("changeData"+bv,by);});},null,bC,arguments.length>1,null,false);},removeData:function(e){return this.each(function(){b.removeData(this,e);});}});function a6(bx,bw,by){if(by===L&&bx.nodeType===1){var bv="data-"+bw.replace(aA,"-$1").toLowerCase();by=bx.getAttribute(bv);if(typeof by==="string"){try{by=by==="true"?true:by==="false"?false:by==="null"?null:b.isNumeric(by)?+by:aT.test(by)?b.parseJSON(by):by;}catch(bz){}b.data(bx,bw,by);}else{by=L;}}return by;}function S(bv){for(var e in bv){if(e==="data"&&b.isEmptyObject(bv[e])){continue;}if(e!=="toJSON"){return false;}}return true;}function bj(by,bx,bA){var bw=bx+"defer",bv=bx+"queue",e=bx+"mark",bz=b._data(by,bw);if(bz&&(bA==="queue"||!b._data(by,bv))&&(bA==="mark"||!b._data(by,e))){setTimeout(function(){if(!b._data(by,bv)&&!b._data(by,e)){b.removeData(by,bw,true);bz.fire();}},0);}}b.extend({_mark:function(bv,e){if(bv){e=(e||"fx")+"mark";b._data(bv,e,(b._data(bv,e)||0)+1);}},_unmark:function(by,bx,bv){if(by!==true){bv=bx;bx=by;by=false;}if(bx){bv=bv||"fx";var e=bv+"mark",bw=by?0:((b._data(bx,e)||1)-1);if(bw){b._data(bx,e,bw);}else{b.removeData(bx,e,true);bj(bx,bv,"mark");}}},queue:function(bv,e,bx){var bw;if(bv){e=(e||"fx")+"queue";bw=b._data(bv,e);if(bx){if(!bw||b.isArray(bx)){bw=b._data(bv,e,b.makeArray(bx));}else{bw.push(bx);}}return bw||[];}},dequeue:function(by,bx){bx=bx||"fx";var bv=b.queue(by,bx),bw=bv.shift(),e={};if(bw==="inprogress"){bw=bv.shift();}if(bw){if(bx==="fx"){bv.unshift("inprogress");}b._data(by,bx+".run",e);bw.call(by,function(){b.dequeue(by,bx);},e);}if(!bv.length){b.removeData(by,bx+"queue "+bx+".run",true);bj(by,bx,"queue");}}});b.fn.extend({queue:function(e,bv){var bw=2;if(typeof e!=="string"){bv=e;e="fx";bw--;}if(arguments.length<bw){return b.queue(this[0],e);}return bv===L?this:this.each(function(){var bx=b.queue(this,e,bv);if(e==="fx"&&bx[0]!=="inprogress"){b.dequeue(this,e);}});},dequeue:function(e){return this.each(function(){b.dequeue(this,e);});},delay:function(bv,e){bv=b.fx?b.fx.speeds[bv]||bv:bv;e=e||"fx";return this.queue(e,function(bx,bw){var by=setTimeout(bx,bv);bw.stop=function(){clearTimeout(by);};});},clearQueue:function(e){return this.queue(e||"fx",[]);},promise:function(bD,bw){if(typeof bD!=="string"){bw=bD;bD=L;}bD=bD||"fx";var e=b.Deferred(),bv=this,by=bv.length,bB=1,bz=bD+"defer",bA=bD+"queue",bC=bD+"mark",bx;function bE(){if(!(--bB)){e.resolveWith(bv,[bv]);}}while(by--){if((bx=b.data(bv[by],bz,L,true)||(b.data(bv[by],bA,L,true)||b.data(bv[by],bC,L,true))&&b.data(bv[by],bz,b.Callbacks("once memory"),true))){bB++;bx.add(bE);}}bE();return e.promise(bw);}});var aQ=/[\n\t\r]/g,ag=/\s+/,aV=/\r/g,g=/^(?:button|input)$/i,C=/^(?:button|input|object|select|textarea)$/i,l=/^a(?:rea)?$/i,ao=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,E=b.support.getSetAttribute,bf,aZ,aG;b.fn.extend({attr:function(e,bv){return b.access(this,b.attr,e,bv,arguments.length>1);},removeAttr:function(e){return this.each(function(){b.removeAttr(this,e);});},prop:function(e,bv){return b.access(this,b.prop,e,bv,arguments.length>1);},removeProp:function(e){e=b.propFix[e]||e;return this.each(function(){try{this[e]=L;delete this[e];}catch(bv){}});},addClass:function(by){var bA,bw,bv,bx,bz,bB,e;if(b.isFunction(by)){return this.each(function(bC){b(this).addClass(by.call(this,bC,this.className));});}if(by&&typeof by==="string"){bA=by.split(ag);for(bw=0,bv=this.length;bw<bv;bw++){bx=this[bw];if(bx.nodeType===1){if(!bx.className&&bA.length===1){bx.className=by;}else{bz=" "+bx.className+" ";for(bB=0,e=bA.length;bB<e;bB++){if(!~bz.indexOf(" "+bA[bB]+" ")){bz+=bA[bB]+" ";}}bx.className=b.trim(bz);}}}}return this;},removeClass:function(bz){var bA,bw,bv,by,bx,bB,e;if(b.isFunction(bz)){return this.each(function(bC){b(this).removeClass(bz.call(this,bC,this.className));});}if((bz&&typeof bz==="string")||bz===L){bA=(bz||"").split(ag);for(bw=0,bv=this.length;bw<bv;bw++){by=this[bw];if(by.nodeType===1&&by.className){if(bz){bx=(" "+by.className+" ").replace(aQ," ");for(bB=0,e=bA.length;bB<e;bB++){bx=bx.replace(" "+bA[bB]+" "," ");}by.className=b.trim(bx);}else{by.className="";}}}}return this;},toggleClass:function(bx,bv){var bw=typeof bx,e=typeof bv==="boolean";if(b.isFunction(bx)){return this.each(function(by){b(this).toggleClass(bx.call(this,by,this.className,bv),bv);});}return this.each(function(){if(bw==="string"){var bA,bz=0,by=b(this),bB=bv,bC=bx.split(ag);while((bA=bC[bz++])){bB=e?bB:!by.hasClass(bA);by[bB?"addClass":"removeClass"](bA);}}else{if(bw==="undefined"||bw==="boolean"){if(this.className){b._data(this,"__className__",this.className);}this.className=this.className||bx===false?"":b._data(this,"__className__")||"";}}});},hasClass:function(e){var bx=" "+e+" ",bw=0,bv=this.length;for(;bw<bv;bw++){if(this[bw].nodeType===1&&(" "+this[bw].className+" ").replace(aQ," ").indexOf(bx)>-1){return true;}}return false;},val:function(bx){var e,bv,by,bw=this[0];if(!arguments.length){if(bw){e=b.valHooks[bw.type]||b.valHooks[bw.nodeName.toLowerCase()];if(e&&"get" in e&&(bv=e.get(bw,"value"))!==L){return bv;}bv=bw.value;return typeof bv==="string"?bv.replace(aV,""):bv==null?"":bv;}return;}by=b.isFunction(bx);return this.each(function(bA){var bz=b(this),bB;if(this.nodeType!==1){return;}if(by){bB=bx.call(this,bA,bz.val());}else{bB=bx;}if(bB==null){bB="";}else{if(typeof bB==="number"){bB+="";}else{if(b.isArray(bB)){bB=b.map(bB,function(bC){return bC==null?"":bC+"";});}}}e=b.valHooks[this.type]||b.valHooks[this.nodeName.toLowerCase()];if(!e||!("set" in e)||e.set(this,bB,"value")===L){this.value=bB;}});}});b.extend({valHooks:{option:{get:function(e){var bv=e.attributes.value;return !bv||bv.specified?e.value:e.text;}},select:{get:function(e){var bA,bv,bz,bx,by=e.selectedIndex,bB=[],bC=e.options,bw=e.type==="select-one";if(by<0){return null;}bv=bw?by:0;bz=bw?by+1:bC.length;for(;bv<bz;bv++){bx=bC[bv];if(bx.selected&&(b.support.optDisabled?!bx.disabled:bx.getAttribute("disabled")===null)&&(!bx.parentNode.disabled||!b.nodeName(bx.parentNode,"optgroup"))){bA=b(bx).val();if(bw){return bA;}bB.push(bA);}}if(bw&&!bB.length&&bC.length){return b(bC[by]).val();}return bB;},set:function(bv,bw){var e=b.makeArray(bw);b(bv).find("option").each(function(){this.selected=b.inArray(b(this).val(),e)>=0;});if(!e.length){bv.selectedIndex=-1;}return e;}}},attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(bA,bx,bB,bz){var bw,e,by,bv=bA.nodeType;if(!bA||bv===3||bv===8||bv===2){return;}if(bz&&bx in b.attrFn){return b(bA)[bx](bB);}if(typeof bA.getAttribute==="undefined"){return b.prop(bA,bx,bB);}by=bv!==1||!b.isXMLDoc(bA);if(by){bx=bx.toLowerCase();e=b.attrHooks[bx]||(ao.test(bx)?aZ:bf);}if(bB!==L){if(bB===null){b.removeAttr(bA,bx);return;}else{if(e&&"set" in e&&by&&(bw=e.set(bA,bB,bx))!==L){return bw;}else{bA.setAttribute(bx,""+bB);return bB;}}}else{if(e&&"get" in e&&by&&(bw=e.get(bA,bx))!==null){return bw;}else{bw=bA.getAttribute(bx);return bw===null?L:bw;}}},removeAttr:function(by,bA){var bz,bB,bw,e,bv,bx=0;if(bA&&by.nodeType===1){bB=bA.toLowerCase().split(ag);e=bB.length;for(;bx<e;bx++){bw=bB[bx];if(bw){bz=b.propFix[bw]||bw;bv=ao.test(bw);if(!bv){b.attr(by,bw,"");}by.removeAttribute(E?bw:bz);if(bv&&bz in by){by[bz]=false;}}}}},attrHooks:{type:{set:function(e,bv){if(g.test(e.nodeName)&&e.parentNode){b.error("type property can't be changed");}else{if(!b.support.radioValue&&bv==="radio"&&b.nodeName(e,"input")){var bw=e.value;e.setAttribute("type",bv);if(bw){e.value=bw;}return bv;}}}},value:{get:function(bv,e){if(bf&&b.nodeName(bv,"button")){return bf.get(bv,e);}return e in bv?bv.value:null;},set:function(bv,bw,e){if(bf&&b.nodeName(bv,"button")){return bf.set(bv,bw,e);}bv.value=bw;}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(bz,bx,bA){var bw,e,by,bv=bz.nodeType;if(!bz||bv===3||bv===8||bv===2){return;}by=bv!==1||!b.isXMLDoc(bz);if(by){bx=b.propFix[bx]||bx;e=b.propHooks[bx];}if(bA!==L){if(e&&"set" in e&&(bw=e.set(bz,bA,bx))!==L){return bw;}else{return(bz[bx]=bA);}}else{if(e&&"get" in e&&(bw=e.get(bz,bx))!==null){return bw;}else{return bz[bx];}}},propHooks:{tabIndex:{get:function(bv){var e=bv.getAttributeNode("tabindex");return e&&e.specified?parseInt(e.value,10):C.test(bv.nodeName)||l.test(bv.nodeName)&&bv.href?0:L;}}}});b.attrHooks.tabindex=b.propHooks.tabIndex;aZ={get:function(bv,e){var bx,bw=b.prop(bv,e);return bw===true||typeof bw!=="boolean"&&(bx=bv.getAttributeNode(e))&&bx.nodeValue!==false?e.toLowerCase():L;},set:function(bv,bx,e){var bw;if(bx===false){b.removeAttr(bv,e);}else{bw=b.propFix[e]||e;if(bw in bv){bv[bw]=true;}bv.setAttribute(e,e.toLowerCase());}return e;}};if(!E){aG={name:true,id:true,coords:true};bf=b.valHooks.button={get:function(bw,bv){var e;e=bw.getAttributeNode(bv);return e&&(aG[bv]?e.nodeValue!=="":e.specified)?e.nodeValue:L;},set:function(bw,bx,bv){var e=bw.getAttributeNode(bv);if(!e){e=av.createAttribute(bv);bw.setAttributeNode(e);}return(e.nodeValue=bx+"");}};b.attrHooks.tabindex.set=bf.set;b.each(["width","height"],function(bv,e){b.attrHooks[e]=b.extend(b.attrHooks[e],{set:function(bw,bx){if(bx===""){bw.setAttribute(e,"auto");return bx;}}});});b.attrHooks.contenteditable={get:bf.get,set:function(bv,bw,e){if(bw===""){bw="false";}bf.set(bv,bw,e);}};}if(!b.support.hrefNormalized){b.each(["href","src","width","height"],function(bv,e){b.attrHooks[e]=b.extend(b.attrHooks[e],{get:function(bx){var bw=bx.getAttribute(e,2);return bw===null?L:bw;}});});}if(!b.support.style){b.attrHooks.style={get:function(e){return e.style.cssText.toLowerCase()||L;},set:function(e,bv){return(e.style.cssText=""+bv);}};}if(!b.support.optSelected){b.propHooks.selected=b.extend(b.propHooks.selected,{get:function(bv){var e=bv.parentNode;if(e){e.selectedIndex;if(e.parentNode){e.parentNode.selectedIndex;}}return null;}});}if(!b.support.enctype){b.propFix.enctype="encoding";}if(!b.support.checkOn){b.each(["radio","checkbox"],function(){b.valHooks[this]={get:function(e){return e.getAttribute("value")===null?"on":e.value;}};});}b.each(["radio","checkbox"],function(){b.valHooks[this]=b.extend(b.valHooks[this],{set:function(e,bv){if(b.isArray(bv)){return(e.checked=b.inArray(b(e).val(),bv)>=0);}}});});var be=/^(?:textarea|input|select)$/i,n=/^([^\.]*)?(?:\.(.+))?$/,J=/(?:^|\s)hover(\.\S+)?\b/,aP=/^key/,bg=/^(?:mouse|contextmenu)|click/,T=/^(?:focusinfocus|focusoutblur)$/,U=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,Y=function(e){var bv=U.exec(e);if(bv){bv[1]=(bv[1]||"").toLowerCase();bv[3]=bv[3]&&new RegExp("(?:^|\\s)"+bv[3]+"(?:\\s|$)");}return bv;},j=function(bw,e){var bv=bw.attributes||{};return((!e[1]||bw.nodeName.toLowerCase()===e[1])&&(!e[2]||(bv.id||{}).value===e[2])&&(!e[3]||e[3].test((bv["class"]||{}).value)));},bt=function(e){return b.event.special.hover?e:e.replace(J,"mouseenter$1 mouseleave$1");};b.event={add:function(bx,bC,bJ,bA,by){var bD,bB,bK,bI,bH,bF,e,bG,bv,bz,bw,bE;if(bx.nodeType===3||bx.nodeType===8||!bC||!bJ||!(bD=b._data(bx))){return;}if(bJ.handler){bv=bJ;bJ=bv.handler;by=bv.selector;}if(!bJ.guid){bJ.guid=b.guid++;}bK=bD.events;if(!bK){bD.events=bK={};}bB=bD.handle;if(!bB){bD.handle=bB=function(bL){return typeof b!=="undefined"&&(!bL||b.event.triggered!==bL.type)?b.event.dispatch.apply(bB.elem,arguments):L;};bB.elem=bx;}bC=b.trim(bt(bC)).split(" ");for(bI=0;bI<bC.length;bI++){bH=n.exec(bC[bI])||[];bF=bH[1];e=(bH[2]||"").split(".").sort();bE=b.event.special[bF]||{};bF=(by?bE.delegateType:bE.bindType)||bF;bE=b.event.special[bF]||{};bG=b.extend({type:bF,origType:bH[1],data:bA,handler:bJ,guid:bJ.guid,selector:by,quick:by&&Y(by),namespace:e.join(".")},bv);bw=bK[bF];if(!bw){bw=bK[bF]=[];bw.delegateCount=0;if(!bE.setup||bE.setup.call(bx,bA,e,bB)===false){if(bx.addEventListener){bx.addEventListener(bF,bB,false);}else{if(bx.attachEvent){bx.attachEvent("on"+bF,bB);}}}}if(bE.add){bE.add.call(bx,bG);if(!bG.handler.guid){bG.handler.guid=bJ.guid;}}if(by){bw.splice(bw.delegateCount++,0,bG);}else{bw.push(bG);}b.event.global[bF]=true;}bx=null;},global:{},remove:function(bJ,bE,bv,bH,bB){var bI=b.hasData(bJ)&&b._data(bJ),bF,bx,bz,bL,bC,bA,bG,bw,by,bK,bD,e;if(!bI||!(bw=bI.events)){return;}bE=b.trim(bt(bE||"")).split(" ");for(bF=0;bF<bE.length;bF++){bx=n.exec(bE[bF])||[];bz=bL=bx[1];bC=bx[2];if(!bz){for(bz in bw){b.event.remove(bJ,bz+bE[bF],bv,bH,true);}continue;}by=b.event.special[bz]||{};bz=(bH?by.delegateType:by.bindType)||bz;bD=bw[bz]||[];bA=bD.length;bC=bC?new RegExp("(^|\\.)"+bC.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(bG=0;bG<bD.length;bG++){e=bD[bG];if((bB||bL===e.origType)&&(!bv||bv.guid===e.guid)&&(!bC||bC.test(e.namespace))&&(!bH||bH===e.selector||bH==="**"&&e.selector)){bD.splice(bG--,1);if(e.selector){bD.delegateCount--;}if(by.remove){by.remove.call(bJ,e);}}}if(bD.length===0&&bA!==bD.length){if(!by.teardown||by.teardown.call(bJ,bC)===false){b.removeEvent(bJ,bz,bI.handle);}delete bw[bz];}}if(b.isEmptyObject(bw)){bK=bI.handle;if(bK){bK.elem=null;}b.removeData(bJ,["events","handle"],true);}},customEvent:{getData:true,setData:true,changeData:true},trigger:function(bv,bD,bA,bJ){if(bA&&(bA.nodeType===3||bA.nodeType===8)){return;}var bG=bv.type||bv,bx=[],e,bw,bC,bH,bz,by,bF,bE,bB,bI;if(T.test(bG+b.event.triggered)){return;}if(bG.indexOf("!")>=0){bG=bG.slice(0,-1);bw=true;}if(bG.indexOf(".")>=0){bx=bG.split(".");bG=bx.shift();bx.sort();}if((!bA||b.event.customEvent[bG])&&!b.event.global[bG]){return;}bv=typeof bv==="object"?bv[b.expando]?bv:new b.Event(bG,bv):new b.Event(bG);bv.type=bG;bv.isTrigger=true;bv.exclusive=bw;bv.namespace=bx.join(".");bv.namespace_re=bv.namespace?new RegExp("(^|\\.)"+bx.join("\\.(?:.*\\.)?")+"(\\.|$)"):null;by=bG.indexOf(":")<0?"on"+bG:"";if(!bA){e=b.cache;for(bC in e){if(e[bC].events&&e[bC].events[bG]){b.event.trigger(bv,bD,e[bC].handle.elem,true);}}return;}bv.result=L;if(!bv.target){bv.target=bA;}bD=bD!=null?b.makeArray(bD):[];bD.unshift(bv);bF=b.event.special[bG]||{};if(bF.trigger&&bF.trigger.apply(bA,bD)===false){return;}bB=[[bA,bF.bindType||bG]];if(!bJ&&!bF.noBubble&&!b.isWindow(bA)){bI=bF.delegateType||bG;bH=T.test(bI+bG)?bA:bA.parentNode;bz=null;for(;bH;bH=bH.parentNode){bB.push([bH,bI]);bz=bH;}if(bz&&bz===bA.ownerDocument){bB.push([bz.defaultView||bz.parentWindow||bd,bI]);}}for(bC=0;bC<bB.length&&!bv.isPropagationStopped();bC++){bH=bB[bC][0];bv.type=bB[bC][1];bE=(b._data(bH,"events")||{})[bv.type]&&b._data(bH,"handle");if(bE){bE.apply(bH,bD);}bE=by&&bH[by];if(bE&&b.acceptData(bH)&&bE.apply(bH,bD)===false){bv.preventDefault();}}bv.type=bG;if(!bJ&&!bv.isDefaultPrevented()){if((!bF._default||bF._default.apply(bA.ownerDocument,bD)===false)&&!(bG==="click"&&b.nodeName(bA,"a"))&&b.acceptData(bA)){if(by&&bA[bG]&&((bG!=="focus"&&bG!=="blur")||bv.target.offsetWidth!==0)&&!b.isWindow(bA)){bz=bA[by];if(bz){bA[by]=null;}b.event.triggered=bG;bA[bG]();b.event.triggered=L;if(bz){bA[by]=bz;}}}}return bv.result;},dispatch:function(bH){bH=b.event.fix(bH||bd.event);var bD=((b._data(this,"events")||{})[bH.type]||[]),bC=bD.delegateCount,bx=[].slice.call(arguments,0),bE=!bH.exclusive&&!bH.namespace,bz=b.event.special[bH.type]||{},bv=[],bJ,bG,by,bA,bK,bI,bB,bw,e,bF,bL;bx[0]=bH;bH.delegateTarget=this;if(bz.preDispatch&&bz.preDispatch.call(this,bH)===false){return;}if(bC&&!(bH.button&&bH.type==="click")){bA=b(this);bA.context=this.ownerDocument||this;for(by=bH.target;by!=this;by=by.parentNode||this){if(by.disabled!==true){bI={};bw=[];bA[0]=by;for(bJ=0;bJ<bC;bJ++){e=bD[bJ];bF=e.selector;if(bI[bF]===L){bI[bF]=(e.quick?j(by,e.quick):bA.is(bF));}if(bI[bF]){bw.push(e);}}if(bw.length){bv.push({elem:by,matches:bw});}}}}if(bD.length>bC){bv.push({elem:this,matches:bD.slice(bC)});}for(bJ=0;bJ<bv.length&&!bH.isPropagationStopped();bJ++){bB=bv[bJ];bH.currentTarget=bB.elem;for(bG=0;bG<bB.matches.length&&!bH.isImmediatePropagationStopped();bG++){e=bB.matches[bG];if(bE||(!bH.namespace&&!e.namespace)||bH.namespace_re&&bH.namespace_re.test(e.namespace)){bH.data=e.data;bH.handleObj=e;bK=((b.event.special[e.origType]||{}).handle||e.handler).apply(bB.elem,bx);if(bK!==L){bH.result=bK;if(bK===false){bH.preventDefault();bH.stopPropagation();}}}}}if(bz.postDispatch){bz.postDispatch.call(this,bH);}return bH.result;},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(bv,e){if(bv.which==null){bv.which=e.charCode!=null?e.charCode:e.keyCode;}return bv;}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(bx,bw){var by,bz,e,bv=bw.button,bA=bw.fromElement;if(bx.pageX==null&&bw.clientX!=null){by=bx.target.ownerDocument||av;bz=by.documentElement;e=by.body;bx.pageX=bw.clientX+(bz&&bz.scrollLeft||e&&e.scrollLeft||0)-(bz&&bz.clientLeft||e&&e.clientLeft||0);bx.pageY=bw.clientY+(bz&&bz.scrollTop||e&&e.scrollTop||0)-(bz&&bz.clientTop||e&&e.clientTop||0);}if(!bx.relatedTarget&&bA){bx.relatedTarget=bA===bx.target?bw.toElement:bA;}if(!bx.which&&bv!==L){bx.which=(bv&1?1:(bv&2?3:(bv&4?2:0)));}return bx;}},fix:function(bw){if(bw[b.expando]){return bw;}var bv,bz,e=bw,bx=b.event.fixHooks[bw.type]||{},by=bx.props?this.props.concat(bx.props):this.props;bw=b.Event(e);for(bv=by.length;bv;){bz=by[--bv];bw[bz]=e[bz];}if(!bw.target){bw.target=e.srcElement||av;}if(bw.target.nodeType===3){bw.target=bw.target.parentNode;}if(bw.metaKey===L){bw.metaKey=bw.ctrlKey;}return bx.filter?bx.filter(bw,e):bw;},special:{ready:{setup:b.bindReady},load:{noBubble:true},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(bw,bv,e){if(b.isWindow(this)){this.onbeforeunload=e;}},teardown:function(bv,e){if(this.onbeforeunload===e){this.onbeforeunload=null;}}}},simulate:function(bw,by,bx,bv){var bz=b.extend(new b.Event(),bx,{type:bw,isSimulated:true,originalEvent:{}});if(bv){b.event.trigger(bz,null,by);}else{b.event.dispatch.call(by,bz);}if(bz.isDefaultPrevented()){bx.preventDefault();}}};b.event.handle=b.event.dispatch;b.removeEvent=av.removeEventListener?function(bv,e,bw){if(bv.removeEventListener){bv.removeEventListener(e,bw,false);}}:function(bv,e,bw){if(bv.detachEvent){bv.detachEvent("on"+e,bw);}};b.Event=function(bv,e){if(!(this instanceof b.Event)){return new b.Event(bv,e);}if(bv&&bv.type){this.originalEvent=bv;this.type=bv.type;this.isDefaultPrevented=(bv.defaultPrevented||bv.returnValue===false||bv.getPreventDefault&&bv.getPreventDefault())?i:bl;}else{this.type=bv;}if(e){b.extend(this,e);}this.timeStamp=bv&&bv.timeStamp||b.now();this[b.expando]=true;};function bl(){return false;}function i(){return true;}b.Event.prototype={preventDefault:function(){this.isDefaultPrevented=i;var bv=this.originalEvent;if(!bv){return;}if(bv.preventDefault){bv.preventDefault();}else{bv.returnValue=false;}},stopPropagation:function(){this.isPropagationStopped=i;var bv=this.originalEvent;if(!bv){return;}if(bv.stopPropagation){bv.stopPropagation();}bv.cancelBubble=true;},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=i;this.stopPropagation();},isDefaultPrevented:bl,isPropagationStopped:bl,isImmediatePropagationStopped:bl};b.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(bv,e){b.event.special[bv]={delegateType:e,bindType:e,handle:function(bz){var bB=this,bA=bz.relatedTarget,by=bz.handleObj,bw=by.selector,bx;if(!bA||(bA!==bB&&!b.contains(bB,bA))){bz.type=by.origType;bx=by.handler.apply(this,arguments);bz.type=e;}return bx;}};});if(!b.support.submitBubbles){b.event.special.submit={setup:function(){if(b.nodeName(this,"form")){return false;}b.event.add(this,"click._submit keypress._submit",function(bx){var bw=bx.target,bv=b.nodeName(bw,"input")||b.nodeName(bw,"button")?bw.form:L;if(bv&&!bv._submit_attached){b.event.add(bv,"submit._submit",function(e){e._submit_bubble=true;});bv._submit_attached=true;}});},postDispatch:function(e){if(e._submit_bubble){delete e._submit_bubble;if(this.parentNode&&!e.isTrigger){b.event.simulate("submit",this.parentNode,e,true);}}},teardown:function(){if(b.nodeName(this,"form")){return false;}b.event.remove(this,"._submit");}};}if(!b.support.changeBubbles){b.event.special.change={setup:function(){if(be.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio"){b.event.add(this,"propertychange._change",function(e){if(e.originalEvent.propertyName==="checked"){this._just_changed=true;}});b.event.add(this,"click._change",function(e){if(this._just_changed&&!e.isTrigger){this._just_changed=false;b.event.simulate("change",this,e,true);}});}return false;}b.event.add(this,"beforeactivate._change",function(bw){var bv=bw.target;if(be.test(bv.nodeName)&&!bv._change_attached){b.event.add(bv,"change._change",function(e){if(this.parentNode&&!e.isSimulated&&!e.isTrigger){b.event.simulate("change",this.parentNode,e,true);}});bv._change_attached=true;}});},handle:function(bv){var e=bv.target;if(this!==e||bv.isSimulated||bv.isTrigger||(e.type!=="radio"&&e.type!=="checkbox")){return bv.handleObj.handler.apply(this,arguments);}},teardown:function(){b.event.remove(this,"._change");return be.test(this.nodeName);}};}if(!b.support.focusinBubbles){b.each({focus:"focusin",blur:"focusout"},function(bx,e){var bv=0,bw=function(by){b.event.simulate(e,by.target,b.event.fix(by),true);};b.event.special[e]={setup:function(){if(bv++===0){av.addEventListener(bx,bw,true);}},teardown:function(){if(--bv===0){av.removeEventListener(bx,bw,true);}}};});}b.fn.extend({on:function(bw,e,bz,by,bv){var bA,bx;if(typeof bw==="object"){if(typeof e!=="string"){bz=bz||e;e=L;}for(bx in bw){this.on(bx,e,bz,bw[bx],bv);}return this;}if(bz==null&&by==null){by=e;bz=e=L;}else{if(by==null){if(typeof e==="string"){by=bz;bz=L;}else{by=bz;bz=e;e=L;}}}if(by===false){by=bl;}else{if(!by){return this;}}if(bv===1){bA=by;by=function(bB){b().off(bB);return bA.apply(this,arguments);};by.guid=bA.guid||(bA.guid=b.guid++);}return this.each(function(){b.event.add(this,bw,by,bz,e);});},one:function(bv,e,bx,bw){return this.on(bv,e,bx,bw,1);},off:function(bw,e,by){if(bw&&bw.preventDefault&&bw.handleObj){var bv=bw.handleObj;b(bw.delegateTarget).off(bv.namespace?bv.origType+"."+bv.namespace:bv.origType,bv.selector,bv.handler);return this;}if(typeof bw==="object"){for(var bx in bw){this.off(bx,e,bw[bx]);}return this;}if(e===false||typeof e==="function"){by=e;e=L;}if(by===false){by=bl;}return this.each(function(){b.event.remove(this,bw,by,e);});},bind:function(e,bw,bv){return this.on(e,null,bw,bv);},unbind:function(e,bv){return this.off(e,null,bv);},live:function(e,bw,bv){b(this.context).on(e,this.selector,bw,bv);return this;},die:function(e,bv){b(this.context).off(e,this.selector||"**",bv);return this;},delegate:function(e,bv,bx,bw){return this.on(bv,e,bx,bw);},undelegate:function(e,bv,bw){return arguments.length==1?this.off(e,"**"):this.off(bv,e,bw);},trigger:function(e,bv){return this.each(function(){b.event.trigger(e,bv,this);});},triggerHandler:function(e,bv){if(this[0]){return b.event.trigger(e,bv,this[0],true);}},toggle:function(bx){var bv=arguments,e=bx.guid||b.guid++,bw=0,by=function(bz){var bA=(b._data(this,"lastToggle"+bx.guid)||0)%bw;b._data(this,"lastToggle"+bx.guid,bA+1);bz.preventDefault();return bv[bA].apply(this,arguments)||false;};by.guid=e;while(bw<bv.length){bv[bw++].guid=e;}return this.click(by);},hover:function(e,bv){return this.mouseenter(e).mouseleave(bv||e);}});b.each(("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu").split(" "),function(bv,e){b.fn[e]=function(bx,bw){if(bw==null){bw=bx;bx=null;}return arguments.length>0?this.on(e,null,bx,bw):this.trigger(e);};if(b.attrFn){b.attrFn[e]=true;}if(aP.test(e)){b.event.fixHooks[e]=b.event.keyHooks;}if(bg.test(e)){b.event.fixHooks[e]=b.event.mouseHooks;}});
+/*!
+ * Sizzle CSS Selector Engine
+ *  Copyright 2011, The Dojo Foundation
+ *  Released under the MIT, BSD, and GPL Licenses.
+ *  More information: http://sizzlejs.com/
+ */
+;(function(){var bH=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,bC="sizcache"+(Math.random()+"").replace(".",""),bI=0,bL=Object.prototype.toString,bB=false,bA=true,bK=/\\/g,bO=/\r\n/g,bQ=/\W/;[0,0].sort(function(){bA=false;return 0;});var by=function(bV,e,bY,bZ){bY=bY||[];e=e||av;var b1=e;if(e.nodeType!==1&&e.nodeType!==9){return[];}if(!bV||typeof bV!=="string"){return bY;}var bS,b3,b6,bR,b2,b5,b4,bX,bU=true,bT=by.isXML(e),bW=[],b0=bV;do{bH.exec("");bS=bH.exec(b0);if(bS){b0=bS[3];bW.push(bS[1]);if(bS[2]){bR=bS[3];break;}}}while(bS);if(bW.length>1&&bD.exec(bV)){if(bW.length===2&&bE.relative[bW[0]]){b3=bM(bW[0]+bW[1],e,bZ);}else{b3=bE.relative[bW[0]]?[e]:by(bW.shift(),e);while(bW.length){bV=bW.shift();if(bE.relative[bV]){bV+=bW.shift();}b3=bM(bV,b3,bZ);}}}else{if(!bZ&&bW.length>1&&e.nodeType===9&&!bT&&bE.match.ID.test(bW[0])&&!bE.match.ID.test(bW[bW.length-1])){b2=by.find(bW.shift(),e,bT);e=b2.expr?by.filter(b2.expr,b2.set)[0]:b2.set[0];}if(e){b2=bZ?{expr:bW.pop(),set:bF(bZ)}:by.find(bW.pop(),bW.length===1&&(bW[0]==="~"||bW[0]==="+")&&e.parentNode?e.parentNode:e,bT);b3=b2.expr?by.filter(b2.expr,b2.set):b2.set;if(bW.length>0){b6=bF(b3);}else{bU=false;}while(bW.length){b5=bW.pop();b4=b5;if(!bE.relative[b5]){b5="";}else{b4=bW.pop();}if(b4==null){b4=e;}bE.relative[b5](b6,b4,bT);}}else{b6=bW=[];}}if(!b6){b6=b3;}if(!b6){by.error(b5||bV);}if(bL.call(b6)==="[object Array]"){if(!bU){bY.push.apply(bY,b6);}else{if(e&&e.nodeType===1){for(bX=0;b6[bX]!=null;bX++){if(b6[bX]&&(b6[bX]===true||b6[bX].nodeType===1&&by.contains(e,b6[bX]))){bY.push(b3[bX]);}}}else{for(bX=0;b6[bX]!=null;bX++){if(b6[bX]&&b6[bX].nodeType===1){bY.push(b3[bX]);}}}}}else{bF(b6,bY);}if(bR){by(bR,b1,bY,bZ);by.uniqueSort(bY);}return bY;};by.uniqueSort=function(bR){if(bJ){bB=bA;bR.sort(bJ);if(bB){for(var e=1;e<bR.length;e++){if(bR[e]===bR[e-1]){bR.splice(e--,1);}}}}return bR;};by.matches=function(e,bR){return by(e,null,null,bR);};by.matchesSelector=function(e,bR){return by(bR,null,null,[e]).length>0;};by.find=function(bX,e,bY){var bW,bS,bU,bT,bV,bR;if(!bX){return[];}for(bS=0,bU=bE.order.length;bS<bU;bS++){bV=bE.order[bS];if((bT=bE.leftMatch[bV].exec(bX))){bR=bT[1];bT.splice(1,1);if(bR.substr(bR.length-1)!=="\\"){bT[1]=(bT[1]||"").replace(bK,"");bW=bE.find[bV](bT,e,bY);if(bW!=null){bX=bX.replace(bE.match[bV],"");break;}}}}if(!bW){bW=typeof e.getElementsByTagName!=="undefined"?e.getElementsByTagName("*"):[];}return{set:bW,expr:bX};};by.filter=function(b1,b0,b4,bU){var bW,e,bZ,b6,b3,bR,bT,bV,b2,bS=b1,b5=[],bY=b0,bX=b0&&b0[0]&&by.isXML(b0[0]);while(b1&&b0.length){for(bZ in bE.filter){if((bW=bE.leftMatch[bZ].exec(b1))!=null&&bW[2]){bR=bE.filter[bZ];bT=bW[1];e=false;bW.splice(1,1);if(bT.substr(bT.length-1)==="\\"){continue;}if(bY===b5){b5=[];}if(bE.preFilter[bZ]){bW=bE.preFilter[bZ](bW,bY,b4,b5,bU,bX);if(!bW){e=b6=true;}else{if(bW===true){continue;}}}if(bW){for(bV=0;(b3=bY[bV])!=null;bV++){if(b3){b6=bR(b3,bW,bV,bY);b2=bU^b6;if(b4&&b6!=null){if(b2){e=true;}else{bY[bV]=false;}}else{if(b2){b5.push(b3);e=true;}}}}}if(b6!==L){if(!b4){bY=b5;}b1=b1.replace(bE.match[bZ],"");if(!e){return[];}break;}}}if(b1===bS){if(e==null){by.error(b1);}else{break;}}bS=b1;}return bY;};by.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e);};var bw=by.getText=function(bU){var bS,bT,e=bU.nodeType,bR="";if(e){if(e===1||e===9||e===11){if(typeof bU.textContent==="string"){return bU.textContent;}else{if(typeof bU.innerText==="string"){return bU.innerText.replace(bO,"");}else{for(bU=bU.firstChild;bU;bU=bU.nextSibling){bR+=bw(bU);}}}}else{if(e===3||e===4){return bU.nodeValue;}}}else{for(bS=0;(bT=bU[bS]);bS++){if(bT.nodeType!==8){bR+=bw(bT);}}}return bR;};var bE=by.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(e){return e.getAttribute("href");},type:function(e){return e.getAttribute("type");}},relative:{"+":function(bW,bR){var bT=typeof bR==="string",bV=bT&&!bQ.test(bR),bX=bT&&!bV;if(bV){bR=bR.toLowerCase();}for(var bS=0,e=bW.length,bU;bS<e;bS++){if((bU=bW[bS])){while((bU=bU.previousSibling)&&bU.nodeType!==1){}bW[bS]=bX||bU&&bU.nodeName.toLowerCase()===bR?bU||false:bU===bR;}}if(bX){by.filter(bR,bW,true);}},">":function(bW,bR){var bV,bU=typeof bR==="string",bS=0,e=bW.length;if(bU&&!bQ.test(bR)){bR=bR.toLowerCase();for(;bS<e;bS++){bV=bW[bS];if(bV){var bT=bV.parentNode;bW[bS]=bT.nodeName.toLowerCase()===bR?bT:false;}}}else{for(;bS<e;bS++){bV=bW[bS];if(bV){bW[bS]=bU?bV.parentNode:bV.parentNode===bR;}}if(bU){by.filter(bR,bW,true);}}},"":function(bT,bR,bV){var bU,bS=bI++,e=bN;if(typeof bR==="string"&&!bQ.test(bR)){bR=bR.toLowerCase();bU=bR;e=bv;}e("parentNode",bR,bS,bT,bU,bV);},"~":function(bT,bR,bV){var bU,bS=bI++,e=bN;if(typeof bR==="string"&&!bQ.test(bR)){bR=bR.toLowerCase();bU=bR;e=bv;}e("previousSibling",bR,bS,bT,bU,bV);}},find:{ID:function(bR,bS,bT){if(typeof bS.getElementById!=="undefined"&&!bT){var e=bS.getElementById(bR[1]);return e&&e.parentNode?[e]:[];}},NAME:function(bS,bV){if(typeof bV.getElementsByName!=="undefined"){var bR=[],bU=bV.getElementsByName(bS[1]);for(var bT=0,e=bU.length;bT<e;bT++){if(bU[bT].getAttribute("name")===bS[1]){bR.push(bU[bT]);}}return bR.length===0?null:bR;}},TAG:function(e,bR){if(typeof bR.getElementsByTagName!=="undefined"){return bR.getElementsByTagName(e[1]);}}},preFilter:{CLASS:function(bT,bR,bS,e,bW,bX){bT=" "+bT[1].replace(bK,"")+" ";if(bX){return bT;}for(var bU=0,bV;(bV=bR[bU])!=null;bU++){if(bV){if(bW^(bV.className&&(" "+bV.className+" ").replace(/[\t\n\r]/g," ").indexOf(bT)>=0)){if(!bS){e.push(bV);}}else{if(bS){bR[bU]=false;}}}}return false;},ID:function(e){return e[1].replace(bK,"");},TAG:function(bR,e){return bR[1].replace(bK,"").toLowerCase();},CHILD:function(e){if(e[1]==="nth"){if(!e[2]){by.error(e[0]);}e[2]=e[2].replace(/^\+|\s*/g,"");var bR=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(e[2]==="even"&&"2n"||e[2]==="odd"&&"2n+1"||!/\D/.test(e[2])&&"0n+"+e[2]||e[2]);e[2]=(bR[1]+(bR[2]||1))-0;e[3]=bR[3]-0;}else{if(e[2]){by.error(e[0]);}}e[0]=bI++;return e;},ATTR:function(bU,bR,bS,e,bV,bW){var bT=bU[1]=bU[1].replace(bK,"");if(!bW&&bE.attrMap[bT]){bU[1]=bE.attrMap[bT];}bU[4]=(bU[4]||bU[5]||"").replace(bK,"");if(bU[2]==="~="){bU[4]=" "+bU[4]+" ";}return bU;},PSEUDO:function(bU,bR,bS,e,bV){if(bU[1]==="not"){if((bH.exec(bU[3])||"").length>1||/^\w/.test(bU[3])){bU[3]=by(bU[3],null,null,bR);}else{var bT=by.filter(bU[3],bR,bS,true^bV);if(!bS){e.push.apply(e,bT);}return false;}}else{if(bE.match.POS.test(bU[0])||bE.match.CHILD.test(bU[0])){return true;}}return bU;},POS:function(e){e.unshift(true);return e;}},filters:{enabled:function(e){return e.disabled===false&&e.type!=="hidden";},disabled:function(e){return e.disabled===true;},checked:function(e){return e.checked===true;},selected:function(e){if(e.parentNode){e.parentNode.selectedIndex;}return e.selected===true;},parent:function(e){return !!e.firstChild;},empty:function(e){return !e.firstChild;},has:function(bS,bR,e){return !!by(e[3],bS).length;},header:function(e){return(/h\d/i).test(e.nodeName);},text:function(bS){var e=bS.getAttribute("type"),bR=bS.type;return bS.nodeName.toLowerCase()==="input"&&"text"===bR&&(e===bR||e===null);},radio:function(e){return e.nodeName.toLowerCase()==="input"&&"radio"===e.type;},checkbox:function(e){return e.nodeName.toLowerCase()==="input"&&"checkbox"===e.type;},file:function(e){return e.nodeName.toLowerCase()==="input"&&"file"===e.type;},password:function(e){return e.nodeName.toLowerCase()==="input"&&"password"===e.type;},submit:function(bR){var e=bR.nodeName.toLowerCase();return(e==="input"||e==="button")&&"submit"===bR.type;},image:function(e){return e.nodeName.toLowerCase()==="input"&&"image"===e.type;},reset:function(bR){var e=bR.nodeName.toLowerCase();return(e==="input"||e==="button")&&"reset"===bR.type;},button:function(bR){var e=bR.nodeName.toLowerCase();return e==="input"&&"button"===bR.type||e==="button";},input:function(e){return(/input|select|textarea|button/i).test(e.nodeName);},focus:function(e){return e===e.ownerDocument.activeElement;}},setFilters:{first:function(bR,e){return e===0;},last:function(bS,bR,e,bT){return bR===bT.length-1;},even:function(bR,e){return e%2===0;},odd:function(bR,e){return e%2===1;},lt:function(bS,bR,e){return bR<e[3]-0;},gt:function(bS,bR,e){return bR>e[3]-0;},nth:function(bS,bR,e){return e[3]-0===bR;},eq:function(bS,bR,e){return e[3]-0===bR;}},filter:{PSEUDO:function(bS,bX,bW,bY){var e=bX[1],bR=bE.filters[e];if(bR){return bR(bS,bW,bX,bY);}else{if(e==="contains"){return(bS.textContent||bS.innerText||bw([bS])||"").indexOf(bX[3])>=0;}else{if(e==="not"){var bT=bX[3];for(var bV=0,bU=bT.length;bV<bU;bV++){if(bT[bV]===bS){return false;}}return true;}else{by.error(e);}}}},CHILD:function(bS,bU){var bT,b0,bW,bZ,e,bV,bY,bX=bU[1],bR=bS;switch(bX){case"only":case"first":while((bR=bR.previousSibling)){if(bR.nodeType===1){return false;}}if(bX==="first"){return true;}bR=bS;case"last":while((bR=bR.nextSibling)){if(bR.nodeType===1){return false;}}return true;case"nth":bT=bU[2];b0=bU[3];if(bT===1&&b0===0){return true;}bW=bU[0];bZ=bS.parentNode;if(bZ&&(bZ[bC]!==bW||!bS.nodeIndex)){bV=0;for(bR=bZ.firstChild;bR;bR=bR.nextSibling){if(bR.nodeType===1){bR.nodeIndex=++bV;}}bZ[bC]=bW;}bY=bS.nodeIndex-b0;if(bT===0){return bY===0;}else{return(bY%bT===0&&bY/bT>=0);}}},ID:function(bR,e){return bR.nodeType===1&&bR.getAttribute("id")===e;},TAG:function(bR,e){return(e==="*"&&bR.nodeType===1)||!!bR.nodeName&&bR.nodeName.toLowerCase()===e;},CLASS:function(bR,e){return(" "+(bR.className||bR.getAttribute("class"))+" ").indexOf(e)>-1;},ATTR:function(bV,bT){var bS=bT[1],e=by.attr?by.attr(bV,bS):bE.attrHandle[bS]?bE.attrHandle[bS](bV):bV[bS]!=null?bV[bS]:bV.getAttribute(bS),bW=e+"",bU=bT[2],bR=bT[4];return e==null?bU==="!=":!bU&&by.attr?e!=null:bU==="="?bW===bR:bU==="*="?bW.indexOf(bR)>=0:bU==="~="?(" "+bW+" ").indexOf(bR)>=0:!bR?bW&&e!==false:bU==="!="?bW!==bR:bU==="^="?bW.indexOf(bR)===0:bU==="$="?bW.substr(bW.length-bR.length)===bR:bU==="|="?bW===bR||bW.substr(0,bR.length+1)===bR+"-":false;},POS:function(bU,bR,bS,bV){var e=bR[2],bT=bE.setFilters[e];if(bT){return bT(bU,bS,bR,bV);}}}};var bD=bE.match.POS,bx=function(bR,e){return"\\"+(e-0+1);};for(var bz in bE.match){bE.match[bz]=new RegExp(bE.match[bz].source+(/(?![^\[]*\])(?![^\(]*\))/.source));bE.leftMatch[bz]=new RegExp(/(^(?:.|\r|\n)*?)/.source+bE.match[bz].source.replace(/\\(\d+)/g,bx));}bE.match.globalPOS=bD;var bF=function(bR,e){bR=Array.prototype.slice.call(bR,0);if(e){e.push.apply(e,bR);return e;}return bR;};try{Array.prototype.slice.call(av.documentElement.childNodes,0)[0].nodeType;}catch(bP){bF=function(bU,bT){var bS=0,bR=bT||[];if(bL.call(bU)==="[object Array]"){Array.prototype.push.apply(bR,bU);}else{if(typeof bU.length==="number"){for(var e=bU.length;bS<e;bS++){bR.push(bU[bS]);}}else{for(;bU[bS];bS++){bR.push(bU[bS]);}}}return bR;};}var bJ,bG;if(av.documentElement.compareDocumentPosition){bJ=function(bR,e){if(bR===e){bB=true;return 0;}if(!bR.compareDocumentPosition||!e.compareDocumentPosition){return bR.compareDocumentPosition?-1:1;}return bR.compareDocumentPosition(e)&4?-1:1;};}else{bJ=function(bY,bX){if(bY===bX){bB=true;return 0;}else{if(bY.sourceIndex&&bX.sourceIndex){return bY.sourceIndex-bX.sourceIndex;}}var bV,bR,bS=[],e=[],bU=bY.parentNode,bW=bX.parentNode,bZ=bU;if(bU===bW){return bG(bY,bX);}else{if(!bU){return -1;}else{if(!bW){return 1;}}}while(bZ){bS.unshift(bZ);bZ=bZ.parentNode;}bZ=bW;while(bZ){e.unshift(bZ);bZ=bZ.parentNode;}bV=bS.length;bR=e.length;for(var bT=0;bT<bV&&bT<bR;bT++){if(bS[bT]!==e[bT]){return bG(bS[bT],e[bT]);}}return bT===bV?bG(bY,e[bT],-1):bG(bS[bT],bX,1);};bG=function(bR,e,bS){if(bR===e){return bS;}var bT=bR.nextSibling;while(bT){if(bT===e){return -1;}bT=bT.nextSibling;}return 1;};}(function(){var bR=av.createElement("div"),bS="script"+(new Date()).getTime(),e=av.documentElement;bR.innerHTML="<a name='"+bS+"'/>";e.insertBefore(bR,e.firstChild);if(av.getElementById(bS)){bE.find.ID=function(bU,bV,bW){if(typeof bV.getElementById!=="undefined"&&!bW){var bT=bV.getElementById(bU[1]);return bT?bT.id===bU[1]||typeof bT.getAttributeNode!=="undefined"&&bT.getAttributeNode("id").nodeValue===bU[1]?[bT]:L:[];}};bE.filter.ID=function(bV,bT){var bU=typeof bV.getAttributeNode!=="undefined"&&bV.getAttributeNode("id");return bV.nodeType===1&&bU&&bU.nodeValue===bT;};}e.removeChild(bR);e=bR=null;})();(function(){var e=av.createElement("div");e.appendChild(av.createComment(""));if(e.getElementsByTagName("*").length>0){bE.find.TAG=function(bR,bV){var bU=bV.getElementsByTagName(bR[1]);if(bR[1]==="*"){var bT=[];for(var bS=0;bU[bS];bS++){if(bU[bS].nodeType===1){bT.push(bU[bS]);}}bU=bT;}return bU;};}e.innerHTML="<a href='#'></a>";if(e.firstChild&&typeof e.firstChild.getAttribute!=="undefined"&&e.firstChild.getAttribute("href")!=="#"){bE.attrHandle.href=function(bR){return bR.getAttribute("href",2);};}e=null;})();if(av.querySelectorAll){(function(){var e=by,bT=av.createElement("div"),bS="__sizzle__";bT.innerHTML="<p class='TEST'></p>";if(bT.querySelectorAll&&bT.querySelectorAll(".TEST").length===0){return;}by=function(b4,bV,bZ,b3){bV=bV||av;if(!b3&&!by.isXML(bV)){var b2=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b4);if(b2&&(bV.nodeType===1||bV.nodeType===9)){if(b2[1]){return bF(bV.getElementsByTagName(b4),bZ);}else{if(b2[2]&&bE.find.CLASS&&bV.getElementsByClassName){return bF(bV.getElementsByClassName(b2[2]),bZ);}}}if(bV.nodeType===9){if(b4==="body"&&bV.body){return bF([bV.body],bZ);}else{if(b2&&b2[3]){var bY=bV.getElementById(b2[3]);if(bY&&bY.parentNode){if(bY.id===b2[3]){return bF([bY],bZ);}}else{return bF([],bZ);}}}try{return bF(bV.querySelectorAll(b4),bZ);}catch(b0){}}else{if(bV.nodeType===1&&bV.nodeName.toLowerCase()!=="object"){var bW=bV,bX=bV.getAttribute("id"),bU=bX||bS,b6=bV.parentNode,b5=/^\s*[+~]/.test(b4);if(!bX){bV.setAttribute("id",bU);}else{bU=bU.replace(/'/g,"\\$&");}if(b5&&b6){bV=bV.parentNode;}try{if(!b5||b6){return bF(bV.querySelectorAll("[id='"+bU+"'] "+b4),bZ);}}catch(b1){}finally{if(!bX){bW.removeAttribute("id");}}}}}return e(b4,bV,bZ,b3);};for(var bR in e){by[bR]=e[bR];}bT=null;})();}(function(){var e=av.documentElement,bS=e.matchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.msMatchesSelector;if(bS){var bU=!bS.call(av.createElement("div"),"div"),bR=false;try{bS.call(av.documentElement,"[test!='']:sizzle");}catch(bT){bR=true;}by.matchesSelector=function(bW,bY){bY=bY.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!by.isXML(bW)){try{if(bR||!bE.match.PSEUDO.test(bY)&&!/!=/.test(bY)){var bV=bS.call(bW,bY);if(bV||!bU||bW.document&&bW.document.nodeType!==11){return bV;}}}catch(bX){}}return by(bY,null,null,[bW]).length>0;};}})();(function(){var e=av.createElement("div");e.innerHTML="<div class='test e'></div><div class='test'></div>";if(!e.getElementsByClassName||e.getElementsByClassName("e").length===0){return;}e.lastChild.className="e";if(e.getElementsByClassName("e").length===1){return;}bE.order.splice(1,0,"CLASS");bE.find.CLASS=function(bR,bS,bT){if(typeof bS.getElementsByClassName!=="undefined"&&!bT){return bS.getElementsByClassName(bR[1]);}};e=null;})();function bv(bR,bW,bV,bZ,bX,bY){for(var bT=0,bS=bZ.length;bT<bS;bT++){var e=bZ[bT];if(e){var bU=false;e=e[bR];while(e){if(e[bC]===bV){bU=bZ[e.sizset];break;}if(e.nodeType===1&&!bY){e[bC]=bV;e.sizset=bT;}if(e.nodeName.toLowerCase()===bW){bU=e;break;}e=e[bR];}bZ[bT]=bU;}}}function bN(bR,bW,bV,bZ,bX,bY){for(var bT=0,bS=bZ.length;bT<bS;bT++){var e=bZ[bT];if(e){var bU=false;e=e[bR];while(e){if(e[bC]===bV){bU=bZ[e.sizset];break;}if(e.nodeType===1){if(!bY){e[bC]=bV;e.sizset=bT;}if(typeof bW!=="string"){if(e===bW){bU=true;break;}}else{if(by.filter(bW,[e]).length>0){bU=e;break;}}}e=e[bR];}bZ[bT]=bU;}}}if(av.documentElement.contains){by.contains=function(bR,e){return bR!==e&&(bR.contains?bR.contains(e):true);};}else{if(av.documentElement.compareDocumentPosition){by.contains=function(bR,e){return !!(bR.compareDocumentPosition(e)&16);};}else{by.contains=function(){return false;};}}by.isXML=function(e){var bR=(e?e.ownerDocument||e:0).documentElement;return bR?bR.nodeName!=="HTML":false;};var bM=function(bS,e,bW){var bV,bX=[],bU="",bY=e.nodeType?[e]:e;while((bV=bE.match.PSEUDO.exec(bS))){bU+=bV[0];bS=bS.replace(bE.match.PSEUDO,"");}bS=bE.relative[bS]?bS+"*":bS;for(var bT=0,bR=bY.length;bT<bR;bT++){by(bS,bY[bT],bX,bW);}return by.filter(bU,bX);};by.attr=b.attr;by.selectors.attrMap={};b.find=by;b.expr=by.selectors;b.expr[":"]=b.expr.filters;b.unique=by.uniqueSort;b.text=by.getText;b.isXMLDoc=by.isXML;b.contains=by.contains;})();var ab=/Until$/,aq=/^(?:parents|prevUntil|prevAll)/,bb=/,/,bp=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,H=b.expr.match.globalPOS,ay={children:true,contents:true,next:true,prev:true};b.fn.extend({find:function(e){var bw=this,by,bv;if(typeof e!=="string"){return b(e).filter(function(){for(by=0,bv=bw.length;by<bv;by++){if(b.contains(bw[by],this)){return true;}}});}var bx=this.pushStack("","find",e),bA,bB,bz;for(by=0,bv=this.length;by<bv;by++){bA=bx.length;b.find(e,this[by],bx);if(by>0){for(bB=bA;bB<bx.length;bB++){for(bz=0;bz<bA;bz++){if(bx[bz]===bx[bB]){bx.splice(bB--,1);break;}}}}}return bx;},has:function(bv){var e=b(bv);return this.filter(function(){for(var bx=0,bw=e.length;bx<bw;bx++){if(b.contains(this,e[bx])){return true;}}});},not:function(e){return this.pushStack(aH(this,e,false),"not",e);},filter:function(e){return this.pushStack(aH(this,e,true),"filter",e);},is:function(e){return !!e&&(typeof e==="string"?H.test(e)?b(e,this.context).index(this[0])>=0:b.filter(e,this).length>0:this.filter(e).length>0);},closest:function(by,bx){var bv=[],bw,e,bz=this[0];if(b.isArray(by)){var bB=1;while(bz&&bz.ownerDocument&&bz!==bx){for(bw=0;bw<by.length;bw++){if(b(bz).is(by[bw])){bv.push({selector:by[bw],elem:bz,level:bB});}}bz=bz.parentNode;bB++;}return bv;}var bA=H.test(by)||typeof by!=="string"?b(by,bx||this.context):0;for(bw=0,e=this.length;bw<e;bw++){bz=this[bw];while(bz){if(bA?bA.index(bz)>-1:b.find.matchesSelector(bz,by)){bv.push(bz);break;}else{bz=bz.parentNode;if(!bz||!bz.ownerDocument||bz===bx||bz.nodeType===11){break;}}}}bv=bv.length>1?b.unique(bv):bv;return this.pushStack(bv,"closest",by);},index:function(e){if(!e){return(this[0]&&this[0].parentNode)?this.prevAll().length:-1;}if(typeof e==="string"){return b.inArray(this[0],b(e));}return b.inArray(e.jquery?e[0]:e,this);},add:function(e,bv){var bx=typeof e==="string"?b(e,bv):b.makeArray(e&&e.nodeType?[e]:e),bw=b.merge(this.get(),bx);return this.pushStack(B(bx[0])||B(bw[0])?bw:b.unique(bw));},andSelf:function(){return this.add(this.prevObject);}});function B(e){return !e||!e.parentNode||e.parentNode.nodeType===11;}b.each({parent:function(bv){var e=bv.parentNode;return e&&e.nodeType!==11?e:null;},parents:function(e){return b.dir(e,"parentNode");},parentsUntil:function(bv,e,bw){return b.dir(bv,"parentNode",bw);},next:function(e){return b.nth(e,2,"nextSibling");},prev:function(e){return b.nth(e,2,"previousSibling");},nextAll:function(e){return b.dir(e,"nextSibling");},prevAll:function(e){return b.dir(e,"previousSibling");},nextUntil:function(bv,e,bw){return b.dir(bv,"nextSibling",bw);},prevUntil:function(bv,e,bw){return b.dir(bv,"previousSibling",bw);},siblings:function(e){return b.sibling((e.parentNode||{}).firstChild,e);},children:function(e){return b.sibling(e.firstChild);},contents:function(e){return b.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:b.makeArray(e.childNodes);}},function(e,bv){b.fn[e]=function(by,bw){var bx=b.map(this,bv,by);if(!ab.test(e)){bw=by;}if(bw&&typeof bw==="string"){bx=b.filter(bw,bx);}bx=this.length>1&&!ay[e]?b.unique(bx):bx;if((this.length>1||bb.test(bw))&&aq.test(e)){bx=bx.reverse();}return this.pushStack(bx,e,P.call(arguments).join(","));};});b.extend({filter:function(bw,e,bv){if(bv){bw=":not("+bw+")";}return e.length===1?b.find.matchesSelector(e[0],bw)?[e[0]]:[]:b.find.matches(bw,e);},dir:function(bw,bv,by){var e=[],bx=bw[bv];while(bx&&bx.nodeType!==9&&(by===L||bx.nodeType!==1||!b(bx).is(by))){if(bx.nodeType===1){e.push(bx);}bx=bx[bv];}return e;},nth:function(by,e,bw,bx){e=e||1;var bv=0;for(;by;by=by[bw]){if(by.nodeType===1&&++bv===e){break;}}return by;},sibling:function(bw,bv){var e=[];for(;bw;bw=bw.nextSibling){if(bw.nodeType===1&&bw!==bv){e.push(bw);}}return e;}});function aH(bx,bw,e){bw=bw||0;if(b.isFunction(bw)){return b.grep(bx,function(bz,by){var bA=!!bw.call(bz,by,bz);return bA===e;});}else{if(bw.nodeType){return b.grep(bx,function(bz,by){return(bz===bw)===e;});}else{if(typeof bw==="string"){var bv=b.grep(bx,function(by){return by.nodeType===1;});if(bp.test(bw)){return b.filter(bw,bv,!e);}else{bw=b.filter(bw,bv);}}}}return b.grep(bx,function(bz,by){return(b.inArray(bz,bw)>=0)===e;});}function a(e){var bw=aS.split("|"),bv=e.createDocumentFragment();if(bv.createElement){while(bw.length){bv.createElement(bw.pop());}}return bv;}var aS="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ah=/ jQuery\d+="(?:\d+|null)"/g,ar=/^\s+/,R=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,d=/<([\w:]+)/,v=/<tbody/i,W=/<|&#?\w+;/,ae=/<(?:script|style)/i,O=/<(?:script|object|embed|option|style)/i,ai=new RegExp("<(?:"+aS+")[\\s/>]","i"),o=/checked\s*(?:[^=]|=\s*.checked.)/i,bn=/\/(java|ecma)script/i,aO=/^\s*<!(?:\[CDATA\[|\-\-)/,ax={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},ac=a(av);ax.optgroup=ax.option;ax.tbody=ax.tfoot=ax.colgroup=ax.caption=ax.thead;ax.th=ax.td;if(!b.support.htmlSerialize){ax._default=[1,"div<div>","</div>"];}b.fn.extend({text:function(e){return b.access(this,function(bv){return bv===L?b.text(this):this.empty().append((this[0]&&this[0].ownerDocument||av).createTextNode(bv));},null,e,arguments.length);},wrapAll:function(e){if(b.isFunction(e)){return this.each(function(bw){b(this).wrapAll(e.call(this,bw));});}if(this[0]){var bv=b(e,this[0].ownerDocument).eq(0).clone(true);if(this[0].parentNode){bv.insertBefore(this[0]);}bv.map(function(){var bw=this;while(bw.firstChild&&bw.firstChild.nodeType===1){bw=bw.firstChild;}return bw;}).append(this);}return this;},wrapInner:function(e){if(b.isFunction(e)){return this.each(function(bv){b(this).wrapInner(e.call(this,bv));});}return this.each(function(){var bv=b(this),bw=bv.contents();if(bw.length){bw.wrapAll(e);}else{bv.append(e);}});},wrap:function(e){var bv=b.isFunction(e);return this.each(function(bw){b(this).wrapAll(bv?e.call(this,bw):e);});},unwrap:function(){return this.parent().each(function(){if(!b.nodeName(this,"body")){b(this).replaceWith(this.childNodes);}}).end();},append:function(){return this.domManip(arguments,true,function(e){if(this.nodeType===1){this.appendChild(e);}});},prepend:function(){return this.domManip(arguments,true,function(e){if(this.nodeType===1){this.insertBefore(e,this.firstChild);}});},before:function(){if(this[0]&&this[0].parentNode){return this.domManip(arguments,false,function(bv){this.parentNode.insertBefore(bv,this);});}else{if(arguments.length){var e=b.clean(arguments);e.push.apply(e,this.toArray());return this.pushStack(e,"before",arguments);}}},after:function(){if(this[0]&&this[0].parentNode){return this.domManip(arguments,false,function(bv){this.parentNode.insertBefore(bv,this.nextSibling);});}else{if(arguments.length){var e=this.pushStack(this,"after",arguments);e.push.apply(e,b.clean(arguments));return e;}}},remove:function(e,bx){for(var bv=0,bw;(bw=this[bv])!=null;bv++){if(!e||b.filter(e,[bw]).length){if(!bx&&bw.nodeType===1){b.cleanData(bw.getElementsByTagName("*"));b.cleanData([bw]);}if(bw.parentNode){bw.parentNode.removeChild(bw);}}}return this;},empty:function(){for(var e=0,bv;(bv=this[e])!=null;e++){if(bv.nodeType===1){b.cleanData(bv.getElementsByTagName("*"));}while(bv.firstChild){bv.removeChild(bv.firstChild);}}return this;},clone:function(bv,e){bv=bv==null?false:bv;e=e==null?bv:e;return this.map(function(){return b.clone(this,bv,e);});},html:function(e){return b.access(this,function(by){var bx=this[0]||{},bw=0,bv=this.length;if(by===L){return bx.nodeType===1?bx.innerHTML.replace(ah,""):null;}if(typeof by==="string"&&!ae.test(by)&&(b.support.leadingWhitespace||!ar.test(by))&&!ax[(d.exec(by)||["",""])[1].toLowerCase()]){by=by.replace(R,"<$1></$2>");try{for(;bw<bv;bw++){bx=this[bw]||{};if(bx.nodeType===1){b.cleanData(bx.getElementsByTagName("*"));bx.innerHTML=by;}}bx=0;}catch(bz){}}if(bx){this.empty().append(by);}},null,e,arguments.length);},replaceWith:function(e){if(this[0]&&this[0].parentNode){if(b.isFunction(e)){return this.each(function(bx){var bw=b(this),bv=bw.html();bw.replaceWith(e.call(this,bx,bv));});}if(typeof e!=="string"){e=b(e).detach();}return this.each(function(){var bw=this.nextSibling,bv=this.parentNode;b(this).remove();if(bw){b(bw).before(e);}else{b(bv).append(e);}});}else{return this.length?this.pushStack(b(b.isFunction(e)?e():e),"replaceWith",e):this;}},detach:function(e){return this.remove(e,true);},domManip:function(bB,bF,bE){var bx,by,bA,bD,bC=bB[0],bv=[];if(!b.support.checkClone&&arguments.length===3&&typeof bC==="string"&&o.test(bC)){return this.each(function(){b(this).domManip(bB,bF,bE,true);});}if(b.isFunction(bC)){return this.each(function(bH){var bG=b(this);bB[0]=bC.call(this,bH,bF?bG.html():L);bG.domManip(bB,bF,bE);});}if(this[0]){bD=bC&&bC.parentNode;if(b.support.parentNode&&bD&&bD.nodeType===11&&bD.childNodes.length===this.length){bx={fragment:bD};}else{bx=b.buildFragment(bB,this,bv);}bA=bx.fragment;if(bA.childNodes.length===1){by=bA=bA.firstChild;}else{by=bA.firstChild;}if(by){bF=bF&&b.nodeName(by,"tr");for(var bw=0,e=this.length,bz=e-1;bw<e;bw++){bE.call(bF?bc(this[bw],by):this[bw],bx.cacheable||(e>1&&bw<bz)?b.clone(bA,true,true):bA);}}if(bv.length){b.each(bv,function(bG,bH){if(bH.src){b.ajax({type:"GET",global:false,url:bH.src,async:false,dataType:"script"});}else{b.globalEval((bH.text||bH.textContent||bH.innerHTML||"").replace(aO,"/*$0*/"));}if(bH.parentNode){bH.parentNode.removeChild(bH);}});}}return this;}});function bc(e,bv){return b.nodeName(e,"table")?(e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody"))):e;}function s(bB,bv){if(bv.nodeType!==1||!b.hasData(bB)){return;}var by,bx,e,bA=b._data(bB),bz=b._data(bv,bA),bw=bA.events;if(bw){delete bz.handle;bz.events={};for(by in bw){for(bx=0,e=bw[by].length;bx<e;bx++){b.event.add(bv,by,bw[by][bx]);}}}if(bz.data){bz.data=b.extend({},bz.data);}}function aj(bv,e){var bw;if(e.nodeType!==1){return;}if(e.clearAttributes){e.clearAttributes();}if(e.mergeAttributes){e.mergeAttributes(bv);}bw=e.nodeName.toLowerCase();if(bw==="object"){e.outerHTML=bv.outerHTML;}else{if(bw==="input"&&(bv.type==="checkbox"||bv.type==="radio")){if(bv.checked){e.defaultChecked=e.checked=bv.checked;}if(e.value!==bv.value){e.value=bv.value;}}else{if(bw==="option"){e.selected=bv.defaultSelected;}else{if(bw==="input"||bw==="textarea"){e.defaultValue=bv.defaultValue;}else{if(bw==="script"&&e.text!==bv.text){e.text=bv.text;}}}}}e.removeAttribute(b.expando);e.removeAttribute("_submit_attached");e.removeAttribute("_change_attached");}b.buildFragment=function(bz,bx,bv){var by,e,bw,bA,bB=bz[0];if(bx&&bx[0]){bA=bx[0].ownerDocument||bx[0];}if(!bA.createDocumentFragment){bA=av;}if(bz.length===1&&typeof bB==="string"&&bB.length<512&&bA===av&&bB.charAt(0)==="<"&&!O.test(bB)&&(b.support.checkClone||!o.test(bB))&&(b.support.html5Clone||!ai.test(bB))){e=true;bw=b.fragments[bB];if(bw&&bw!==1){by=bw;}}if(!by){by=bA.createDocumentFragment();b.clean(bz,bA,by,bv);}if(e){b.fragments[bB]=bw?by:1;}return{fragment:by,cacheable:e};};b.fragments={};b.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,bv){b.fn[e]=function(bw){var bz=[],bC=b(bw),bB=this.length===1&&this[0].parentNode;if(bB&&bB.nodeType===11&&bB.childNodes.length===1&&bC.length===1){bC[bv](this[0]);return this;}else{for(var bA=0,bx=bC.length;bA<bx;bA++){var by=(bA>0?this.clone(true):this).get();b(bC[bA])[bv](by);bz=bz.concat(by);}return this.pushStack(bz,e,bC.selector);}};});function bh(e){if(typeof e.getElementsByTagName!=="undefined"){return e.getElementsByTagName("*");}else{if(typeof e.querySelectorAll!=="undefined"){return e.querySelectorAll("*");}else{return[];}}}function az(e){if(e.type==="checkbox"||e.type==="radio"){e.defaultChecked=e.checked;}}function D(e){var bv=(e.nodeName||"").toLowerCase();if(bv==="input"){az(e);}else{if(bv!=="script"&&typeof e.getElementsByTagName!=="undefined"){b.grep(e.getElementsByTagName("input"),az);}}}function am(e){var bv=av.createElement("div");ac.appendChild(bv);bv.innerHTML=e.outerHTML;return bv.firstChild;}b.extend({clone:function(by,bA,bw){var e,bv,bx,bz=b.support.html5Clone||b.isXMLDoc(by)||!ai.test("<"+by.nodeName+">")?by.cloneNode(true):am(by);if((!b.support.noCloneEvent||!b.support.noCloneChecked)&&(by.nodeType===1||by.nodeType===11)&&!b.isXMLDoc(by)){aj(by,bz);e=bh(by);bv=bh(bz);for(bx=0;e[bx];++bx){if(bv[bx]){aj(e[bx],bv[bx]);}}}if(bA){s(by,bz);if(bw){e=bh(by);bv=bh(bz);for(bx=0;e[bx];++bx){s(e[bx],bv[bx]);}}}e=bv=null;return bz;},clean:function(bI,bw,bv,bx){var bA,bH,bD,bJ=[];bw=bw||av;if(typeof bw.createElement==="undefined"){bw=bw.ownerDocument||bw[0]&&bw[0].ownerDocument||av;}for(var bE=0,bG;(bG=bI[bE])!=null;bE++){if(typeof bG==="number"){bG+="";}if(!bG){continue;}if(typeof bG==="string"){if(!W.test(bG)){bG=bw.createTextNode(bG);}else{bG=bG.replace(R,"<$1></$2>");var bN=(d.exec(bG)||["",""])[1].toLowerCase(),bz=ax[bN]||ax._default,bK=bz[0],bB=bw.createElement("div"),bL=ac.childNodes,bM;if(bw===av){ac.appendChild(bB);}else{a(bw).appendChild(bB);}bB.innerHTML=bz[1]+bG+bz[2];while(bK--){bB=bB.lastChild;}if(!b.support.tbody){var by=v.test(bG),e=bN==="table"&&!by?bB.firstChild&&bB.firstChild.childNodes:bz[1]==="<table>"&&!by?bB.childNodes:[];for(bD=e.length-1;bD>=0;--bD){if(b.nodeName(e[bD],"tbody")&&!e[bD].childNodes.length){e[bD].parentNode.removeChild(e[bD]);}}}if(!b.support.leadingWhitespace&&ar.test(bG)){bB.insertBefore(bw.createTextNode(ar.exec(bG)[0]),bB.firstChild);}bG=bB.childNodes;if(bB){bB.parentNode.removeChild(bB);if(bL.length>0){bM=bL[bL.length-1];if(bM&&bM.parentNode){bM.parentNode.removeChild(bM);}}}}}var bF;if(!b.support.appendChecked){if(bG[0]&&typeof(bF=bG.length)==="number"){for(bD=0;bD<bF;bD++){D(bG[bD]);}}else{D(bG);}}if(bG.nodeType){bJ.push(bG);}else{bJ=b.merge(bJ,bG);}}if(bv){bA=function(bO){return !bO.type||bn.test(bO.type);};for(bE=0;bJ[bE];bE++){bH=bJ[bE];if(bx&&b.nodeName(bH,"script")&&(!bH.type||bn.test(bH.type))){bx.push(bH.parentNode?bH.parentNode.removeChild(bH):bH);}else{if(bH.nodeType===1){var bC=b.grep(bH.getElementsByTagName("script"),bA);bJ.splice.apply(bJ,[bE+1,0].concat(bC));}bv.appendChild(bH);}}}return bJ;},cleanData:function(bv){var by,bw,e=b.cache,bB=b.event.special,bA=b.support.deleteExpando;for(var bz=0,bx;(bx=bv[bz])!=null;bz++){if(bx.nodeName&&b.noData[bx.nodeName.toLowerCase()]){continue;}bw=bx[b.expando];if(bw){by=e[bw];if(by&&by.events){for(var bC in by.events){if(bB[bC]){b.event.remove(bx,bC);}else{b.removeEvent(bx,bC,by.handle);}}if(by.handle){by.handle.elem=null;}}if(bA){delete bx[b.expando];}else{if(bx.removeAttribute){bx.removeAttribute(b.expando);}}delete e[bw];}}}});var al=/alpha\([^)]*\)/i,au=/opacity=([^)]*)/,y=/([A-Z]|^ms)/g,bo=/^[\-+]?(?:\d*\.)?\d+$/i,a1=/^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,I=/^([\-+])=([\-+.\de]+)/,aE=/^margin/,a9={position:"absolute",visibility:"hidden",display:"block"},G=["Top","Right","Bottom","Left"],Z,aJ,aY;b.fn.css=function(e,bv){return b.access(this,function(bx,bw,by){return by!==L?b.style(bx,bw,by):b.css(bx,bw);},e,bv,arguments.length>1);};b.extend({cssHooks:{opacity:{get:function(bw,bv){if(bv){var e=Z(bw,"opacity");return e===""?"1":e;}else{return bw.style.opacity;}}}},cssNumber:{fillOpacity:true,fontWeight:true,lineHeight:true,opacity:true,orphans:true,widows:true,zIndex:true,zoom:true},cssProps:{"float":b.support.cssFloat?"cssFloat":"styleFloat"},style:function(bx,bw,bD,by){if(!bx||bx.nodeType===3||bx.nodeType===8||!bx.style){return;}var bB,bC,bz=b.camelCase(bw),bv=bx.style,bE=b.cssHooks[bz];bw=b.cssProps[bz]||bz;if(bD!==L){bC=typeof bD;if(bC==="string"&&(bB=I.exec(bD))){bD=(+(bB[1]+1)*+bB[2])+parseFloat(b.css(bx,bw));bC="number";}if(bD==null||bC==="number"&&isNaN(bD)){return;}if(bC==="number"&&!b.cssNumber[bz]){bD+="px";}if(!bE||!("set" in bE)||(bD=bE.set(bx,bD))!==L){try{bv[bw]=bD;}catch(bA){}}}else{if(bE&&"get" in bE&&(bB=bE.get(bx,false,by))!==L){return bB;}return bv[bw];}},css:function(by,bx,bv){var bw,e;bx=b.camelCase(bx);e=b.cssHooks[bx];bx=b.cssProps[bx]||bx;if(bx==="cssFloat"){bx="float";}if(e&&"get" in e&&(bw=e.get(by,true,bv))!==L){return bw;}else{if(Z){return Z(by,bx);}}},swap:function(by,bx,bz){var e={},bw,bv;for(bv in bx){e[bv]=by.style[bv];by.style[bv]=bx[bv];}bw=bz.call(by);for(bv in bx){by.style[bv]=e[bv];}return bw;}});b.curCSS=b.css;if(av.defaultView&&av.defaultView.getComputedStyle){aJ=function(bA,bw){var bv,bz,e,by,bx=bA.style;bw=bw.replace(y,"-$1").toLowerCase();if((bz=bA.ownerDocument.defaultView)&&(e=bz.getComputedStyle(bA,null))){bv=e.getPropertyValue(bw);if(bv===""&&!b.contains(bA.ownerDocument.documentElement,bA)){bv=b.style(bA,bw);}}if(!b.support.pixelMargin&&e&&aE.test(bw)&&a1.test(bv)){by=bx.width;bx.width=bv;bv=e.width;bx.width=by;}return bv;};}if(av.documentElement.currentStyle){aY=function(bz,bw){var bA,e,by,bv=bz.currentStyle&&bz.currentStyle[bw],bx=bz.style;if(bv==null&&bx&&(by=bx[bw])){bv=by;}if(a1.test(bv)){bA=bx.left;e=bz.runtimeStyle&&bz.runtimeStyle.left;if(e){bz.runtimeStyle.left=bz.currentStyle.left;}bx.left=bw==="fontSize"?"1em":bv;bv=bx.pixelLeft+"px";bx.left=bA;if(e){bz.runtimeStyle.left=e;}}return bv===""?"auto":bv;};}Z=aJ||aY;function af(by,bw,bv){var bz=bw==="width"?by.offsetWidth:by.offsetHeight,bx=bw==="width"?1:0,e=4;if(bz>0){if(bv!=="border"){for(;bx<e;bx+=2){if(!bv){bz-=parseFloat(b.css(by,"padding"+G[bx]))||0;}if(bv==="margin"){bz+=parseFloat(b.css(by,bv+G[bx]))||0;}else{bz-=parseFloat(b.css(by,"border"+G[bx]+"Width"))||0;}}}return bz+"px";}bz=Z(by,bw);if(bz<0||bz==null){bz=by.style[bw];}if(a1.test(bz)){return bz;}bz=parseFloat(bz)||0;if(bv){for(;bx<e;bx+=2){bz+=parseFloat(b.css(by,"padding"+G[bx]))||0;if(bv!=="padding"){bz+=parseFloat(b.css(by,"border"+G[bx]+"Width"))||0;}if(bv==="margin"){bz+=parseFloat(b.css(by,bv+G[bx]))||0;}}}return bz+"px";}b.each(["height","width"],function(bv,e){b.cssHooks[e]={get:function(by,bx,bw){if(bx){if(by.offsetWidth!==0){return af(by,e,bw);}else{return b.swap(by,a9,function(){return af(by,e,bw);});}}},set:function(bw,bx){return bo.test(bx)?bx+"px":bx;}};});if(!b.support.opacity){b.cssHooks.opacity={get:function(bv,e){return au.test((e&&bv.currentStyle?bv.currentStyle.filter:bv.style.filter)||"")?(parseFloat(RegExp.$1)/100)+"":e?"1":"";},set:function(by,bz){var bx=by.style,bv=by.currentStyle,e=b.isNumeric(bz)?"alpha(opacity="+bz*100+")":"",bw=bv&&bv.filter||bx.filter||"";bx.zoom=1;if(bz>=1&&b.trim(bw.replace(al,""))===""){bx.removeAttribute("filter");if(bv&&!bv.filter){return;}}bx.filter=al.test(bw)?bw.replace(al,e):bw+" "+e;}};}b(function(){if(!b.support.reliableMarginRight){b.cssHooks.marginRight={get:function(bv,e){return b.swap(bv,{display:"inline-block"},function(){if(e){return Z(bv,"margin-right");}else{return bv.style.marginRight;}});}};}});if(b.expr&&b.expr.filters){b.expr.filters.hidden=function(bw){var bv=bw.offsetWidth,e=bw.offsetHeight;return(bv===0&&e===0)||(!b.support.reliableHiddenOffsets&&((bw.style&&bw.style.display)||b.css(bw,"display"))==="none");};b.expr.filters.visible=function(e){return !b.expr.filters.hidden(e);};}b.each({margin:"",padding:"",border:"Width"},function(e,bv){b.cssHooks[e+bv]={expand:function(by){var bx,bz=typeof by==="string"?by.split(" "):[by],bw={};for(bx=0;bx<4;bx++){bw[e+G[bx]+bv]=bz[bx]||bz[bx-2]||bz[0];}return bw;}};});var k=/%20/g,ap=/\[\]$/,bs=/\r?\n/g,bq=/#.*$/,aD=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,a0=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,aN=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,aR=/^(?:GET|HEAD)$/,c=/^\/\//,M=/\?/,a7=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,p=/^(?:select|textarea)/i,h=/\s+/,br=/([?&])_=[^&]*/,K=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,z=b.fn.load,aa={},q={},aF,r,aW=["*/"]+["*"];try{aF=bm.href;}catch(aw){aF=av.createElement("a");aF.href="";aF=aF.href;}r=K.exec(aF.toLowerCase())||[];function f(e){return function(by,bA){if(typeof by!=="string"){bA=by;by="*";}if(b.isFunction(bA)){var bx=by.toLowerCase().split(h),bw=0,bz=bx.length,bv,bB,bC;for(;bw<bz;bw++){bv=bx[bw];bC=/^\+/.test(bv);if(bC){bv=bv.substr(1)||"*";}bB=e[bv]=e[bv]||[];bB[bC?"unshift":"push"](bA);}}};}function aX(bv,bE,bz,bD,bB,bx){bB=bB||bE.dataTypes[0];bx=bx||{};bx[bB]=true;var bA=bv[bB],bw=0,e=bA?bA.length:0,by=(bv===aa),bC;for(;bw<e&&(by||!bC);bw++){bC=bA[bw](bE,bz,bD);if(typeof bC==="string"){if(!by||bx[bC]){bC=L;}else{bE.dataTypes.unshift(bC);bC=aX(bv,bE,bz,bD,bC,bx);}}}if((by||!bC)&&!bx["*"]){bC=aX(bv,bE,bz,bD,"*",bx);}return bC;}function an(bw,bx){var bv,e,by=b.ajaxSettings.flatOptions||{};for(bv in bx){if(bx[bv]!==L){(by[bv]?bw:(e||(e={})))[bv]=bx[bv];}}if(e){b.extend(true,bw,e);}}b.fn.extend({load:function(bw,bz,bA){if(typeof bw!=="string"&&z){return z.apply(this,arguments);}else{if(!this.length){return this;}}var by=bw.indexOf(" ");if(by>=0){var e=bw.slice(by,bw.length);bw=bw.slice(0,by);}var bx="GET";if(bz){if(b.isFunction(bz)){bA=bz;bz=L;}else{if(typeof bz==="object"){bz=b.param(bz,b.ajaxSettings.traditional);bx="POST";}}}var bv=this;b.ajax({url:bw,type:bx,dataType:"html",data:bz,complete:function(bC,bB,bD){bD=bC.responseText;if(bC.isResolved()){bC.done(function(bE){bD=bE;});bv.html(e?b("<div>").append(bD.replace(a7,"")).find(e):bD);}if(bA){bv.each(bA,[bD,bB,bC]);}}});return this;},serialize:function(){return b.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return this.elements?b.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||p.test(this.nodeName)||a0.test(this.type));}).map(function(e,bv){var bw=b(this).val();return bw==null?null:b.isArray(bw)?b.map(bw,function(by,bx){return{name:bv.name,value:by.replace(bs,"\r\n")};}):{name:bv.name,value:bw.replace(bs,"\r\n")};}).get();}});b.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,bv){b.fn[bv]=function(bw){return this.on(bv,bw);};});b.each(["get","post"],function(e,bv){b[bv]=function(bw,by,bz,bx){if(b.isFunction(by)){bx=bx||bz;bz=by;by=L;}return b.ajax({type:bv,url:bw,data:by,success:bz,dataType:bx});};});b.extend({getScript:function(e,bv){return b.get(e,L,bv,"script");},getJSON:function(e,bv,bw){return b.get(e,bv,bw,"json");},ajaxSetup:function(bv,e){if(e){an(bv,b.ajaxSettings);}else{e=bv;bv=b.ajaxSettings;}an(bv,e);return bv;},ajaxSettings:{url:aF,isLocal:aN.test(r[1]),global:true,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:true,async:true,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":aW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":bd.String,"text html":true,"text json":b.parseJSON,"text xml":b.parseXML},flatOptions:{context:true,url:true}},ajaxPrefilter:f(aa),ajaxTransport:f(q),ajax:function(bz,bx){if(typeof bz==="object"){bx=bz;bz=L;}bx=bx||{};var bD=b.ajaxSetup({},bx),bS=bD.context||bD,bG=bS!==bD&&(bS.nodeType||bS instanceof b)?b(bS):b.event,bR=b.Deferred(),bN=b.Callbacks("once memory"),bB=bD.statusCode||{},bC,bH={},bO={},bQ,by,bL,bE,bI,bA=0,bw,bK,bJ={readyState:0,setRequestHeader:function(bT,bU){if(!bA){var e=bT.toLowerCase();bT=bO[e]=bO[e]||bT;bH[bT]=bU;}return this;},getAllResponseHeaders:function(){return bA===2?bQ:null;},getResponseHeader:function(bT){var e;if(bA===2){if(!by){by={};while((e=aD.exec(bQ))){by[e[1].toLowerCase()]=e[2];}}e=by[bT.toLowerCase()];}return e===L?null:e;},overrideMimeType:function(e){if(!bA){bD.mimeType=e;}return this;},abort:function(e){e=e||"abort";if(bL){bL.abort(e);}bF(0,e);return this;}};function bF(bZ,bU,b0,bW){if(bA===2){return;}bA=2;if(bE){clearTimeout(bE);}bL=L;bQ=bW||"";bJ.readyState=bZ>0?4:0;var bT,b4,b3,bX=bU,bY=b0?bk(bD,bJ,b0):L,bV,b2;if(bZ>=200&&bZ<300||bZ===304){if(bD.ifModified){if((bV=bJ.getResponseHeader("Last-Modified"))){b.lastModified[bC]=bV;}if((b2=bJ.getResponseHeader("Etag"))){b.etag[bC]=b2;}}if(bZ===304){bX="notmodified";bT=true;}else{try{b4=F(bD,bY);bX="success";bT=true;}catch(b1){bX="parsererror";b3=b1;}}}else{b3=bX;if(!bX||bZ){bX="error";if(bZ<0){bZ=0;}}}bJ.status=bZ;bJ.statusText=""+(bU||bX);if(bT){bR.resolveWith(bS,[b4,bX,bJ]);}else{bR.rejectWith(bS,[bJ,bX,b3]);}bJ.statusCode(bB);bB=L;if(bw){bG.trigger("ajax"+(bT?"Success":"Error"),[bJ,bD,bT?b4:b3]);}bN.fireWith(bS,[bJ,bX]);if(bw){bG.trigger("ajaxComplete",[bJ,bD]);if(!(--b.active)){b.event.trigger("ajaxStop");}}}bR.promise(bJ);bJ.success=bJ.done;bJ.error=bJ.fail;bJ.complete=bN.add;bJ.statusCode=function(bT){if(bT){var e;if(bA<2){for(e in bT){bB[e]=[bB[e],bT[e]];}}else{e=bT[bJ.status];bJ.then(e,e);}}return this;};bD.url=((bz||bD.url)+"").replace(bq,"").replace(c,r[1]+"//");bD.dataTypes=b.trim(bD.dataType||"*").toLowerCase().split(h);if(bD.crossDomain==null){bI=K.exec(bD.url.toLowerCase());bD.crossDomain=!!(bI&&(bI[1]!=r[1]||bI[2]!=r[2]||(bI[3]||(bI[1]==="http:"?80:443))!=(r[3]||(r[1]==="http:"?80:443))));}if(bD.data&&bD.processData&&typeof bD.data!=="string"){bD.data=b.param(bD.data,bD.traditional);}aX(aa,bD,bx,bJ);if(bA===2){return false;}bw=bD.global;bD.type=bD.type.toUpperCase();bD.hasContent=!aR.test(bD.type);if(bw&&b.active++===0){b.event.trigger("ajaxStart");}if(!bD.hasContent){if(bD.data){bD.url+=(M.test(bD.url)?"&":"?")+bD.data;delete bD.data;}bC=bD.url;if(bD.cache===false){var bv=b.now(),bP=bD.url.replace(br,"$1_="+bv);bD.url=bP+((bP===bD.url)?(M.test(bD.url)?"&":"?")+"_="+bv:"");}}if(bD.data&&bD.hasContent&&bD.contentType!==false||bx.contentType){bJ.setRequestHeader("Content-Type",bD.contentType);}if(bD.ifModified){bC=bC||bD.url;if(b.lastModified[bC]){bJ.setRequestHeader("If-Modified-Since",b.lastModified[bC]);}if(b.etag[bC]){bJ.setRequestHeader("If-None-Match",b.etag[bC]);}}bJ.setRequestHeader("Accept",bD.dataTypes[0]&&bD.accepts[bD.dataTypes[0]]?bD.accepts[bD.dataTypes[0]]+(bD.dataTypes[0]!=="*"?", "+aW+"; q=0.01":""):bD.accepts["*"]);for(bK in bD.headers){bJ.setRequestHeader(bK,bD.headers[bK]);}if(bD.beforeSend&&(bD.beforeSend.call(bS,bJ,bD)===false||bA===2)){bJ.abort();return false;}for(bK in {success:1,error:1,complete:1}){bJ[bK](bD[bK]);}bL=aX(q,bD,bx,bJ);if(!bL){bF(-1,"No Transport");}else{bJ.readyState=1;if(bw){bG.trigger("ajaxSend",[bJ,bD]);}if(bD.async&&bD.timeout>0){bE=setTimeout(function(){bJ.abort("timeout");},bD.timeout);}try{bA=1;bL.send(bH,bF);}catch(bM){if(bA<2){bF(-1,bM);}else{throw bM;}}}return bJ;},param:function(e,bw){var bv=[],by=function(bz,bA){bA=b.isFunction(bA)?bA():bA;bv[bv.length]=encodeURIComponent(bz)+"="+encodeURIComponent(bA);};if(bw===L){bw=b.ajaxSettings.traditional;}if(b.isArray(e)||(e.jquery&&!b.isPlainObject(e))){b.each(e,function(){by(this.name,this.value);});}else{for(var bx in e){u(bx,e[bx],bw,by);}}return bv.join("&").replace(k,"+");}});function u(bw,by,bv,bx){if(b.isArray(by)){b.each(by,function(bA,bz){if(bv||ap.test(bw)){bx(bw,bz);}else{u(bw+"["+(typeof bz==="object"?bA:"")+"]",bz,bv,bx);}});}else{if(!bv&&b.type(by)==="object"){for(var e in by){u(bw+"["+e+"]",by[e],bv,bx);}}else{bx(bw,by);}}}b.extend({active:0,lastModified:{},etag:{}});function bk(bD,bC,bz){var bv=bD.contents,bB=bD.dataTypes,bw=bD.responseFields,by,bA,bx,e;for(bA in bw){if(bA in bz){bC[bw[bA]]=bz[bA];}}while(bB[0]==="*"){bB.shift();if(by===L){by=bD.mimeType||bC.getResponseHeader("content-type");}}if(by){for(bA in bv){if(bv[bA]&&bv[bA].test(by)){bB.unshift(bA);break;}}}if(bB[0] in bz){bx=bB[0];}else{for(bA in bz){if(!bB[0]||bD.converters[bA+" "+bB[0]]){bx=bA;break;}if(!e){e=bA;}}bx=bx||e;}if(bx){if(bx!==bB[0]){bB.unshift(bx);}return bz[bx];}}function F(bH,bz){if(bH.dataFilter){bz=bH.dataFilter(bz,bH.dataType);}var bD=bH.dataTypes,bG={},bA,bE,bw=bD.length,bB,bC=bD[0],bx,by,bF,bv,e;for(bA=1;bA<bw;bA++){if(bA===1){for(bE in bH.converters){if(typeof bE==="string"){bG[bE.toLowerCase()]=bH.converters[bE];}}}bx=bC;bC=bD[bA];if(bC==="*"){bC=bx;}else{if(bx!=="*"&&bx!==bC){by=bx+" "+bC;bF=bG[by]||bG["* "+bC];if(!bF){e=L;for(bv in bG){bB=bv.split(" ");if(bB[0]===bx||bB[0]==="*"){e=bG[bB[1]+" "+bC];if(e){bv=bG[bv];if(bv===true){bF=e;}else{if(e===true){bF=bv;}}break;}}}}if(!(bF||e)){b.error("No conversion from "+by.replace(" "," to "));}if(bF!==true){bz=bF?bF(bz):e(bv(bz));}}}}return bz;}var aC=b.now(),t=/(\=)\?(&|$)|\?\?/i;b.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return b.expando+"_"+(aC++);}});b.ajaxPrefilter("json jsonp",function(bD,bA,bC){var bx=(typeof bD.data==="string")&&/^application\/x\-www\-form\-urlencoded/.test(bD.contentType);if(bD.dataTypes[0]==="jsonp"||bD.jsonp!==false&&(t.test(bD.url)||bx&&t.test(bD.data))){var bB,bw=bD.jsonpCallback=b.isFunction(bD.jsonpCallback)?bD.jsonpCallback():bD.jsonpCallback,bz=bd[bw],e=bD.url,by=bD.data,bv="$1"+bw+"$2";if(bD.jsonp!==false){e=e.replace(t,bv);if(bD.url===e){if(bx){by=by.replace(t,bv);}if(bD.data===by){e+=(/\?/.test(e)?"&":"?")+bD.jsonp+"="+bw;}}}bD.url=e;bD.data=by;bd[bw]=function(bE){bB=[bE];};bC.always(function(){bd[bw]=bz;if(bB&&b.isFunction(bz)){bd[bw](bB[0]);}});bD.converters["script json"]=function(){if(!bB){b.error(bw+" was not called");}return bB[0];};bD.dataTypes[0]="json";return"script";}});b.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){b.globalEval(e);return e;}}});b.ajaxPrefilter("script",function(e){if(e.cache===L){e.cache=false;}if(e.crossDomain){e.type="GET";e.global=false;}});b.ajaxTransport("script",function(bw){if(bw.crossDomain){var e,bv=av.head||av.getElementsByTagName("head")[0]||av.documentElement;return{send:function(bx,by){e=av.createElement("script");e.async="async";if(bw.scriptCharset){e.charset=bw.scriptCharset;}e.src=bw.url;e.onload=e.onreadystatechange=function(bA,bz){if(bz||!e.readyState||/loaded|complete/.test(e.readyState)){e.onload=e.onreadystatechange=null;if(bv&&e.parentNode){bv.removeChild(e);}e=L;if(!bz){by(200,"success");}}};bv.insertBefore(e,bv.firstChild);},abort:function(){if(e){e.onload(0,1);}}};}});var A=bd.ActiveXObject?function(){for(var e in N){N[e](0,1);}}:false,x=0,N;function aM(){try{return new bd.XMLHttpRequest();}catch(bv){}}function ak(){try{return new bd.ActiveXObject("Microsoft.XMLHTTP");}catch(bv){}}b.ajaxSettings.xhr=bd.ActiveXObject?function(){return !this.isLocal&&aM()||ak();}:aM;(function(e){b.extend(b.support,{ajax:!!e,cors:!!e&&("withCredentials" in e)});})(b.ajaxSettings.xhr());if(b.support.ajax){b.ajaxTransport(function(e){if(!e.crossDomain||b.support.cors){var bv;return{send:function(bB,bw){var bA=e.xhr(),bz,by;if(e.username){bA.open(e.type,e.url,e.async,e.username,e.password);}else{bA.open(e.type,e.url,e.async);}if(e.xhrFields){for(by in e.xhrFields){bA[by]=e.xhrFields[by];}}if(e.mimeType&&bA.overrideMimeType){bA.overrideMimeType(e.mimeType);}if(!e.crossDomain&&!bB["X-Requested-With"]){bB["X-Requested-With"]="XMLHttpRequest";}try{for(by in bB){bA.setRequestHeader(by,bB[by]);}}catch(bx){}bA.send((e.hasContent&&e.data)||null);bv=function(bK,bE){var bF,bD,bC,bI,bH;try{if(bv&&(bE||bA.readyState===4)){bv=L;if(bz){bA.onreadystatechange=b.noop;if(A){delete N[bz];}}if(bE){if(bA.readyState!==4){bA.abort();}}else{bF=bA.status;bC=bA.getAllResponseHeaders();bI={};bH=bA.responseXML;if(bH&&bH.documentElement){bI.xml=bH;}try{bI.text=bA.responseText;}catch(bK){}try{bD=bA.statusText;}catch(bJ){bD="";}if(!bF&&e.isLocal&&!e.crossDomain){bF=bI.text?200:404;}else{if(bF===1223){bF=204;}}}}}catch(bG){if(!bE){bw(-1,bG);}}if(bI){bw(bF,bD,bI,bC);}};if(!e.async||bA.readyState===4){bv();}else{bz=++x;if(A){if(!N){N={};b(bd).unload(A);}N[bz]=bv;}bA.onreadystatechange=bv;}},abort:function(){if(bv){bv(0,1);}}};}});}var Q={},ba,m,aB=/^(?:toggle|show|hide)$/,aU=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,a4,aI=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],a5;b.fn.extend({show:function(bx,bA,bz){var bw,by;if(bx||bx===0){return this.animate(a2("show",3),bx,bA,bz);}else{for(var bv=0,e=this.length;bv<e;bv++){bw=this[bv];if(bw.style){by=bw.style.display;if(!b._data(bw,"olddisplay")&&by==="none"){by=bw.style.display="";}if((by===""&&b.css(bw,"display")==="none")||!b.contains(bw.ownerDocument.documentElement,bw)){b._data(bw,"olddisplay",w(bw.nodeName));}}}for(bv=0;bv<e;bv++){bw=this[bv];if(bw.style){by=bw.style.display;if(by===""||by==="none"){bw.style.display=b._data(bw,"olddisplay")||"";}}}return this;}},hide:function(bx,bA,bz){if(bx||bx===0){return this.animate(a2("hide",3),bx,bA,bz);}else{var bw,by,bv=0,e=this.length;for(;bv<e;bv++){bw=this[bv];if(bw.style){by=b.css(bw,"display");if(by!=="none"&&!b._data(bw,"olddisplay")){b._data(bw,"olddisplay",by);}}}for(bv=0;bv<e;bv++){if(this[bv].style){this[bv].style.display="none";}}return this;}},_toggle:b.fn.toggle,toggle:function(bw,bv,bx){var e=typeof bw==="boolean";if(b.isFunction(bw)&&b.isFunction(bv)){this._toggle.apply(this,arguments);}else{if(bw==null||e){this.each(function(){var by=e?bw:b(this).is(":hidden");b(this)[by?"show":"hide"]();});}else{this.animate(a2("toggle",3),bw,bv,bx);}}return this;},fadeTo:function(e,bx,bw,bv){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:bx},e,bw,bv);},animate:function(bz,bw,by,bx){var e=b.speed(bw,by,bx);if(b.isEmptyObject(bz)){return this.each(e.complete,[false]);}bz=b.extend({},bz);function bv(){if(e.queue===false){b._mark(this);}var bE=b.extend({},e),bL=this.nodeType===1,bJ=bL&&b(this).is(":hidden"),bB,bG,bD,bK,bN,bF,bI,bC,bH,bM,bA;bE.animatedProperties={};for(bD in bz){bB=b.camelCase(bD);if(bD!==bB){bz[bB]=bz[bD];delete bz[bD];}if((bN=b.cssHooks[bB])&&"expand" in bN){bF=bN.expand(bz[bB]);delete bz[bB];for(bD in bF){if(!(bD in bz)){bz[bD]=bF[bD];}}}}for(bB in bz){bG=bz[bB];if(b.isArray(bG)){bE.animatedProperties[bB]=bG[1];bG=bz[bB]=bG[0];}else{bE.animatedProperties[bB]=bE.specialEasing&&bE.specialEasing[bB]||bE.easing||"swing";}if(bG==="hide"&&bJ||bG==="show"&&!bJ){return bE.complete.call(this);}if(bL&&(bB==="height"||bB==="width")){bE.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY];if(b.css(this,"display")==="inline"&&b.css(this,"float")==="none"){if(!b.support.inlineBlockNeedsLayout||w(this.nodeName)==="inline"){this.style.display="inline-block";}else{this.style.zoom=1;}}}}if(bE.overflow!=null){this.style.overflow="hidden";}for(bD in bz){bK=new b.fx(this,bE,bD);bG=bz[bD];if(aB.test(bG)){bA=b._data(this,"toggle"+bD)||(bG==="toggle"?bJ?"show":"hide":0);if(bA){b._data(this,"toggle"+bD,bA==="show"?"hide":"show");bK[bA]();}else{bK[bG]();}}else{bI=aU.exec(bG);bC=bK.cur();if(bI){bH=parseFloat(bI[2]);bM=bI[3]||(b.cssNumber[bD]?"":"px");if(bM!=="px"){b.style(this,bD,(bH||1)+bM);bC=((bH||1)/bK.cur())*bC;b.style(this,bD,bC+bM);}if(bI[1]){bH=((bI[1]==="-="?-1:1)*bH)+bC;}bK.custom(bC,bH,bM);}else{bK.custom(bC,bG,"");}}}return true;}return e.queue===false?this.each(bv):this.queue(e.queue,bv);},stop:function(bw,bv,e){if(typeof bw!=="string"){e=bv;bv=bw;bw=L;}if(bv&&bw!==false){this.queue(bw||"fx",[]);}return this.each(function(){var bx,by=false,bA=b.timers,bz=b._data(this);if(!e){b._unmark(true,this);}function bB(bE,bF,bD){var bC=bF[bD];b.removeData(bE,bD,true);bC.stop(e);}if(bw==null){for(bx in bz){if(bz[bx]&&bz[bx].stop&&bx.indexOf(".run")===bx.length-4){bB(this,bz,bx);}}}else{if(bz[bx=bw+".run"]&&bz[bx].stop){bB(this,bz,bx);}}for(bx=bA.length;bx--;){if(bA[bx].elem===this&&(bw==null||bA[bx].queue===bw)){if(e){bA[bx](true);}else{bA[bx].saveState();}by=true;bA.splice(bx,1);}}if(!(e&&by)){b.dequeue(this,bw);}});}});function bi(){setTimeout(at,0);return(a5=b.now());}function at(){a5=L;}function a2(bv,e){var bw={};b.each(aI.concat.apply([],aI.slice(0,e)),function(){bw[this]=bv;});return bw;}b.each({slideDown:a2("show",1),slideUp:a2("hide",1),slideToggle:a2("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,bv){b.fn[e]=function(bw,by,bx){return this.animate(bv,bw,by,bx);};});b.extend({speed:function(bw,bx,bv){var e=bw&&typeof bw==="object"?b.extend({},bw):{complete:bv||!bv&&bx||b.isFunction(bw)&&bw,duration:bw,easing:bv&&bx||bx&&!b.isFunction(bx)&&bx};e.duration=b.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in b.fx.speeds?b.fx.speeds[e.duration]:b.fx.speeds._default;if(e.queue==null||e.queue===true){e.queue="fx";}e.old=e.complete;e.complete=function(by){if(b.isFunction(e.old)){e.old.call(this);}if(e.queue){b.dequeue(this,e.queue);}else{if(by!==false){b._unmark(this);}}};return e;},easing:{linear:function(e){return e;},swing:function(e){return(-Math.cos(e*Math.PI)/2)+0.5;}},timers:[],fx:function(bv,e,bw){this.options=e;this.elem=bv;this.prop=bw;e.orig=e.orig||{};}});b.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this);}(b.fx.step[this.prop]||b.fx.step._default)(this);},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop];}var e,bv=b.css(this.elem,this.prop);return isNaN(e=parseFloat(bv))?!bv||bv==="auto"?0:bv:e;},custom:function(bz,by,bx){var e=this,bw=b.fx;this.startTime=a5||bi();this.end=by;this.now=this.start=bz;this.pos=this.state=0;this.unit=bx||this.unit||(b.cssNumber[this.prop]?"":"px");function bv(bA){return e.step(bA);}bv.queue=this.options.queue;bv.elem=this.elem;bv.saveState=function(){if(b._data(e.elem,"fxshow"+e.prop)===L){if(e.options.hide){b._data(e.elem,"fxshow"+e.prop,e.start);}else{if(e.options.show){b._data(e.elem,"fxshow"+e.prop,e.end);}}}};if(bv()&&b.timers.push(bv)&&!a4){a4=setInterval(bw.tick,bw.interval);}},show:function(){var e=b._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=e||b.style(this.elem,this.prop);this.options.show=true;if(e!==L){this.custom(this.cur(),e);}else{this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());}b(this.elem).show();},hide:function(){this.options.orig[this.prop]=b._data(this.elem,"fxshow"+this.prop)||b.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0);},step:function(by){var bA,bB,bv,bx=a5||bi(),e=true,bz=this.elem,bw=this.options;if(by||bx>=bw.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();bw.animatedProperties[this.prop]=true;for(bA in bw.animatedProperties){if(bw.animatedProperties[bA]!==true){e=false;}}if(e){if(bw.overflow!=null&&!b.support.shrinkWrapBlocks){b.each(["","X","Y"],function(bC,bD){bz.style["overflow"+bD]=bw.overflow[bC];});}if(bw.hide){b(bz).hide();}if(bw.hide||bw.show){for(bA in bw.animatedProperties){b.style(bz,bA,bw.orig[bA]);b.removeData(bz,"fxshow"+bA,true);b.removeData(bz,"toggle"+bA,true);}}bv=bw.complete;if(bv){bw.complete=false;bv.call(bz);}}return false;}else{if(bw.duration==Infinity){this.now=bx;}else{bB=bx-this.startTime;this.state=bB/bw.duration;this.pos=b.easing[bw.animatedProperties[this.prop]](this.state,bB,0,1,bw.duration);this.now=this.start+((this.end-this.start)*this.pos);}this.update();}return true;}};b.extend(b.fx,{tick:function(){var bw,bv=b.timers,e=0;for(;e<bv.length;e++){bw=bv[e];if(!bw()&&bv[e]===bw){bv.splice(e--,1);}}if(!bv.length){b.fx.stop();}},interval:13,stop:function(){clearInterval(a4);a4=null;},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(e){b.style(e.elem,"opacity",e.now);},_default:function(e){if(e.elem.style&&e.elem.style[e.prop]!=null){e.elem.style[e.prop]=e.now+e.unit;}else{e.elem[e.prop]=e.now;}}}});b.each(aI.concat.apply([],aI),function(e,bv){if(bv.indexOf("margin")){b.fx.step[bv]=function(bw){b.style(bw.elem,bv,Math.max(0,bw.now)+bw.unit);};}});if(b.expr&&b.expr.filters){b.expr.filters.animated=function(e){return b.grep(b.timers,function(bv){return e===bv.elem;}).length;};}function w(bx){if(!Q[bx]){var e=av.body,bv=b("<"+bx+">").appendTo(e),bw=bv.css("display");bv.remove();if(bw==="none"||bw===""){if(!ba){ba=av.createElement("iframe");ba.frameBorder=ba.width=ba.height=0;}e.appendChild(ba);if(!m||!ba.createElement){m=(ba.contentWindow||ba.contentDocument).document;m.write((b.support.boxModel?"<!doctype html>":"")+"<html><body>");m.close();}bv=m.createElement(bx);m.body.appendChild(bv);bw=b.css(bv,"display");e.removeChild(ba);}Q[bx]=bw;}return Q[bx];}var a8,V=/^t(?:able|d|h)$/i,ad=/^(?:body|html)$/i;if("getBoundingClientRect" in av.documentElement){a8=function(by,bH,bw,bB){try{bB=by.getBoundingClientRect();}catch(bF){}if(!bB||!b.contains(bw,by)){return bB?{top:bB.top,left:bB.left}:{top:0,left:0};}var bC=bH.body,bD=aL(bH),bA=bw.clientTop||bC.clientTop||0,bE=bw.clientLeft||bC.clientLeft||0,bv=bD.pageYOffset||b.support.boxModel&&bw.scrollTop||bC.scrollTop,bz=bD.pageXOffset||b.support.boxModel&&bw.scrollLeft||bC.scrollLeft,bG=bB.top+bv-bA,bx=bB.left+bz-bE;return{top:bG,left:bx};};}else{a8=function(bz,bE,bx){var bC,bw=bz.offsetParent,bv=bz,bA=bE.body,bB=bE.defaultView,e=bB?bB.getComputedStyle(bz,null):bz.currentStyle,bD=bz.offsetTop,by=bz.offsetLeft;while((bz=bz.parentNode)&&bz!==bA&&bz!==bx){if(b.support.fixedPosition&&e.position==="fixed"){break;}bC=bB?bB.getComputedStyle(bz,null):bz.currentStyle;bD-=bz.scrollTop;by-=bz.scrollLeft;if(bz===bw){bD+=bz.offsetTop;by+=bz.offsetLeft;if(b.support.doesNotAddBorder&&!(b.support.doesAddBorderForTableAndCells&&V.test(bz.nodeName))){bD+=parseFloat(bC.borderTopWidth)||0;by+=parseFloat(bC.borderLeftWidth)||0;}bv=bw;bw=bz.offsetParent;}if(b.support.subtractsBorderForOverflowNotVisible&&bC.overflow!=="visible"){bD+=parseFloat(bC.borderTopWidth)||0;by+=parseFloat(bC.borderLeftWidth)||0;}e=bC;}if(e.position==="relative"||e.position==="static"){bD+=bA.offsetTop;by+=bA.offsetLeft;}if(b.support.fixedPosition&&e.position==="fixed"){bD+=Math.max(bx.scrollTop,bA.scrollTop);by+=Math.max(bx.scrollLeft,bA.scrollLeft);}return{top:bD,left:by};};}b.fn.offset=function(e){if(arguments.length){return e===L?this:this.each(function(bx){b.offset.setOffset(this,e,bx);});}var bv=this[0],bw=bv&&bv.ownerDocument;if(!bw){return null;}if(bv===bw.body){return b.offset.bodyOffset(bv);}return a8(bv,bw,bw.documentElement);};b.offset={bodyOffset:function(e){var bw=e.offsetTop,bv=e.offsetLeft;if(b.support.doesNotIncludeMarginInBodyOffset){bw+=parseFloat(b.css(e,"marginTop"))||0;bv+=parseFloat(b.css(e,"marginLeft"))||0;}return{top:bw,left:bv};},setOffset:function(bx,bG,bA){var bB=b.css(bx,"position");if(bB==="static"){bx.style.position="relative";}var bz=b(bx),bv=bz.offset(),e=b.css(bx,"top"),bE=b.css(bx,"left"),bF=(bB==="absolute"||bB==="fixed")&&b.inArray("auto",[e,bE])>-1,bD={},bC={},bw,by;if(bF){bC=bz.position();bw=bC.top;by=bC.left;}else{bw=parseFloat(e)||0;by=parseFloat(bE)||0;}if(b.isFunction(bG)){bG=bG.call(bx,bA,bv);}if(bG.top!=null){bD.top=(bG.top-bv.top)+bw;}if(bG.left!=null){bD.left=(bG.left-bv.left)+by;}if("using" in bG){bG.using.call(bx,bD);}else{bz.css(bD);}}};b.fn.extend({position:function(){if(!this[0]){return null;}var bw=this[0],bv=this.offsetParent(),bx=this.offset(),e=ad.test(bv[0].nodeName)?{top:0,left:0}:bv.offset();bx.top-=parseFloat(b.css(bw,"marginTop"))||0;bx.left-=parseFloat(b.css(bw,"marginLeft"))||0;e.top+=parseFloat(b.css(bv[0],"borderTopWidth"))||0;e.left+=parseFloat(b.css(bv[0],"borderLeftWidth"))||0;return{top:bx.top-e.top,left:bx.left-e.left};},offsetParent:function(){return this.map(function(){var e=this.offsetParent||av.body;while(e&&(!ad.test(e.nodeName)&&b.css(e,"position")==="static")){e=e.offsetParent;}return e;});}});b.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(bw,bv){var e=/Y/.test(bv);b.fn[bw]=function(bx){return b.access(this,function(by,bB,bA){var bz=aL(by);if(bA===L){return bz?(bv in bz)?bz[bv]:b.support.boxModel&&bz.document.documentElement[bB]||bz.document.body[bB]:by[bB];}if(bz){bz.scrollTo(!e?bA:b(bz).scrollLeft(),e?bA:b(bz).scrollTop());}else{by[bB]=bA;}},bw,bx,arguments.length,null);};});function aL(e){return b.isWindow(e)?e:e.nodeType===9?e.defaultView||e.parentWindow:false;}b.each({Height:"height",Width:"width"},function(bw,bx){var bv="client"+bw,e="scroll"+bw,by="offset"+bw;b.fn["inner"+bw]=function(){var bz=this[0];return bz?bz.style?parseFloat(b.css(bz,bx,"padding")):this[bx]():null;};b.fn["outer"+bw]=function(bA){var bz=this[0];return bz?bz.style?parseFloat(b.css(bz,bx,bA?"margin":"border")):this[bx]():null;};b.fn[bx]=function(bz){return b.access(this,function(bC,bB,bD){var bF,bE,bG,bA;if(b.isWindow(bC)){bF=bC.document;bE=bF.documentElement[bv];return b.support.boxModel&&bE||bF.body&&bF.body[bv]||bE;}if(bC.nodeType===9){bF=bC.documentElement;if(bF[bv]>=bF[e]){return bF[bv];}return Math.max(bC.body[e],bF[e],bC.body[by],bF[by]);}if(bD===L){bG=b.css(bC,bB);bA=parseFloat(bG);return b.isNumeric(bA)?bA:bG;}b(bC).css(bB,bD);},bx,bz,arguments.length,null);};});bd.jQuery=bd.$=b;if(typeof define==="function"&&define.amd&&define.amd.jQuery){define("jquery",[],function(){return b;});}})(window);
Index: trunk/prototype/services/OpenLDAP.php
===================================================================
--- trunk/prototype/services/OpenLDAP.php	(revision 5804)
+++ trunk/prototype/services/OpenLDAP.php	(revision 6528)
@@ -1,3 +1,5 @@
 <?php
+
+use prototype\api\Config as Config;
 
 class OpenLDAP implements Service
@@ -13,5 +15,5 @@
 	if( !isset($criteria["limit"]) )
 		$criteria["limit"] = $this->limit;
-        
+      
 	$sr =  ldap_search( $this->con , $this->config['context'] , self::parseCriteria($criteria , $map) , self::parseJustthese($justthese, $map) , 0 , $criteria["limit"]); 
 	if(!$sr) return false; 
@@ -156,5 +158,5 @@
         $as = array_shift( $filter );
 	$op = self::parseOperator( $as );
-                
+
 	if( is_array($filter[0]) )
 	{
Index: trunk/prototype/services/PostgreSQL.php
===================================================================
--- trunk/prototype/services/PostgreSQL.php	(revision 6299)
+++ trunk/prototype/services/PostgreSQL.php	(revision 6528)
@@ -40,4 +40,6 @@
  */
 
+use prototype\api\Config as Config;
+
 class PostgreSQL implements Service
 {
@@ -87,4 +89,5 @@
             $map = Config::get($uri['concept'], 'PostgreSQL.mapping');
 	    $criteria = ($criteria !== false) ? $this->parseCriteria ( $criteria , $map , ' WHERE '.$map['id'].' = \''.addslashes( $uri['id'] ).'\'') : ' WHERE '.$map['id'].' = \''.addslashes( $uri['id'] ).'\'';
+
 	    return $this->execSql('UPDATE '.(Config::get($uri['concept'],'PostgreSQL.concept')).' '. self::parseUpdateData( $data ,$map).$criteria);
     }
@@ -197,6 +200,5 @@
 		    $val[] = '\''.addslashes($v).'\'';
 	    }
-	    
-	    return '('.implode(',', $ind).') VALUES ('.implode(',', $val).') RETURNING '.$map['id'].' as id';
+	    return '('.implode(',', $ind).') VALUES ('.implode(',', $val).') RETURNING '.$map['id'].' as id';	    
     }
 	
@@ -244,9 +246,14 @@
 		    $query .= ' GROUP BY '.( is_array($criteria["group"]) ? implode(', ', $criteria["group"]) : $criteria["group"] ).' ';
 	    }
-	    
+   
 	    if( isset($criteria["order"]) )
 	    {
-		    $query .= ' ORDER BY '.self::parseOrder( $criteria["order"], $map ).' ';
-	    }
+		    //Verificar se os atributos para o ORDER BY serao ordenados em ordem decrescente [DESC]
+		    $orderDesc = ( isset($criteria["orderDesc"]) && count($criteria["order"]) == count($criteria["orderDesc"]) ) ? $criteria["orderDesc"] : false;
+		
+		    $query .= ' ORDER BY '.self::parseOrder( $criteria["order"], $map, $orderDesc ).' ';
+	    
+	    }
+
 	    if( isset($criteria["limit"]) )
 	    {
@@ -339,9 +346,21 @@
     }
 
-   private static function parseOrder($order , &$map)
+    private static function parseOrder($order , &$map, $orderDesc=false)
     {
-                  
+
         if($notArray = !is_array($order)) //Caso seja um full select pegar todas as keys
             $order = array( $order );
+
+	//Caso seja feita ordenacao em ordem descrescente
+	//concatenar DESC em cada atributo
+	if($orderDesc !== false){
+		if(!is_array($orderDesc)){
+			$orderDesc = array( $orderDesc );
+		}
+
+		for($i=0; $i<count($order); $i++){
+			$order[$i] .= ($orderDesc[$i] === true) ? ' DESC' : '';
+		}
+	}
 
         $return = array();
@@ -356,4 +375,5 @@
         return ( $notArray ?  $order[0] : implode(', ', $order) );
     }
+
 }
 
Index: trunk/prototype/services/ImapServiceAdapter.php
===================================================================
--- trunk/prototype/services/ImapServiceAdapter.php	(revision 6457)
+++ trunk/prototype/services/ImapServiceAdapter.php	(revision 6528)
@@ -42,4 +42,6 @@
 include_once ROOTPATH."/../expressoMail1_2/inc/class.imap_functions.inc.php";
 
+use prototype\api\Config as Config;
+
 /**
  *
Index: trunk/prototype/services/iCal.php
===================================================================
--- trunk/prototype/services/iCal.php	(revision 6346)
+++ trunk/prototype/services/iCal.php	(revision 6528)
@@ -4,4 +4,6 @@
 require_once ROOTPATH . '/plugins/icalcreator/iCalcreator.class.php';
 require_once ROOTPATH . '/modules/calendar/constants.php';
+
+use prototype\api\Config as Config;
 
 //TODO:Timeout request
Index: trunk/prototype/me.php
===================================================================
--- trunk/prototype/me.php	(revision 5399)
+++ trunk/prototype/me.php	(revision 6528)
@@ -1,8 +1,48 @@
 <?php
-
 require_once  (dirname(__FILE__).'/api/controller.php');
+use prototype\api\Config as Config;
 
 $me = Controller::read(array('concept' => 'user', 'service' => 'OpenLDAP'  , 'id' => Config::me('uidNumber')));
 
+if(isset($_POST['refreshToken'])){
+    
+    $ch = curl_init();
+
+    $restConf = parse_ini_file( __DIR__ . '/config/REST.ini', true );
+
+    $param  = 'grant_type=refresh_token';
+    $param .= '&client_id=' . $restConf['oauth']['client_id'];
+    $param .= '&client_secret=' . $restConf['oauth']['client_secret'];
+    $param .= '&refresh_token=' . $_SESSION['oauth']['refresh_token'];
+
+    // set URL and other appropriate options
+    curl_setopt($ch, CURLOPT_URL, $restConf['oauth']['url_token']);
+    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: */*'));
+    curl_setopt($ch, CURLOPT_POST, TRUE);
+    curl_setopt($ch, CURLOPT_POSTFIELDS, $param);
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);  //configura para nao imprimir a saida na tela
+    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2);//Passe um número long como parâmetro que contêm o limite de tempo, em segundos, que você permite as funções CURL levar. 
+
+    // grab URL and pass it to the browser
+    $res = curl_exec($ch);
+
+    // close cURL resource, and free up system resources
+    curl_close($ch);
+    $a = json_decode($res);
+
+    if ( isset($a->access_token) ) {
+	$_SESSION['oauth']['access_token'] = $a->access_token;
+	$_SESSION['oauth']['expires_in'] = $a->expires_in;
+	$_SESSION['oauth']['token_type'] = $a->token_type;
+	$_SESSION['oauth']['scope'] = $a->scope;
+	$_SESSION['oauth']['refresh_token'] = $a->refresh_token;
+	$_SESSION['oauth']['client_secret'] = $restConf['oauth']['client_secret'];
+    }  else {
+	echo json_encode(null);
+	return;
+    }
+}
+
+$me['token'] = $_SESSION['oauth']['access_token'];
 echo json_encode( $me );
 
Index: trunk/prototype/library/oauth2/LICENSE.txt
===================================================================
--- trunk/prototype/library/oauth2/LICENSE.txt	(revision 6528)
+++ trunk/prototype/library/oauth2/LICENSE.txt	(revision 6528)
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2010 Tim Ridgely <tim@opendining.net>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
Index: trunk/prototype/library/oauth2/tests/All_OAuth2_Tests.php
===================================================================
--- trunk/prototype/library/oauth2/tests/All_OAuth2_Tests.php	(revision 6528)
+++ trunk/prototype/library/oauth2/tests/All_OAuth2_Tests.php	(revision 6528)
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * Static test suite.
+ */
+
+class All_OAuth2_Tests extends PHPUnit_Framework_TestSuite {
+  
+  /**
+   * Constructs the test suite handler.
+   */
+  public function __construct() {
+    $this->setName ( 'OAuth2Suite' );
+
+    foreach (glob(__DIR__.'/*Test.php') as $filename) {
+      require $filename;
+      $class = basename($filename, '.php');
+      $this->addTestSuite($class);
+    }
+  }
+  
+  /**
+   * Creates the suite.
+   */
+  public static function suite() {
+    return new self ();
+  }
+}
+
Index: trunk/prototype/library/oauth2/tests/OAuth2Test.php
===================================================================
--- trunk/prototype/library/oauth2/tests/OAuth2Test.php	(revision 6528)
+++ trunk/prototype/library/oauth2/tests/OAuth2Test.php	(revision 6528)
@@ -0,0 +1,453 @@
+<?php
+require __DIR__ . '/../lib/OAuth2.php';
+require __DIR__ . '/../lib/IOAuth2Storage.php';
+require __DIR__ . '/../lib/IOAuth2GrantCode.php';
+
+/**
+ * OAuth2 test case.
+ */
+class OAuth2Test extends PHPUnit_Framework_TestCase {
+  
+  /**
+   * @var OAuth2
+   */
+  private $fixture;
+  
+  /**
+   * The actual token ID is irrelevant, so choose one:
+   * @var string
+   */
+  private $tokenId = 'my_token';
+  
+  /**
+   * Tests OAuth2->verifyAccessToken() with a missing token
+   */
+  public function testVerifyAccessTokenWithNoParam() {
+    $mockStorage = $this->getMock('IOAuth2Storage');
+    $this->fixture = new OAuth2($mockStorage);
+    
+    $scope = null;
+    $this->setExpectedException('OAuth2AuthenticateException');
+    $this->fixture->verifyAccessToken('', $scope);
+  }
+  
+  /**
+   * Tests OAuth2->verifyAccessToken() with a invalid token
+   */
+  public function testVerifyAccessTokenInvalidToken() {
+    
+    // Set up the mock storage to say this token does not exist
+    $mockStorage = $this->getMock('IOAuth2Storage');
+    $mockStorage->expects($this->once())
+      ->method('getAccessToken')
+      ->will($this->returnValue(false));
+      
+    $this->fixture = new OAuth2($mockStorage);
+    
+    $scope = null;
+    $this->setExpectedException('OAuth2AuthenticateException');
+    $this->fixture->verifyAccessToken($this->tokenId, $scope);
+  }
+  
+  /**
+   * Tests OAuth2->verifyAccessToken() with a malformed token
+   * 
+   * @dataProvider generateMalformedTokens
+   */
+  public function testVerifyAccessTokenMalformedToken($token) {
+    
+    // Set up the mock storage to say this token does not exist
+    $mockStorage = $this->getMock('IOAuth2Storage');
+    $mockStorage->expects($this->once())
+      ->method('getAccessToken')
+      ->will($this->returnValue($token));
+      
+    $this->fixture = new OAuth2($mockStorage);
+    
+    $scope = null;
+    $this->setExpectedException('OAuth2AuthenticateException');
+    $this->fixture->verifyAccessToken($this->tokenId, $scope);
+  }
+  
+	/**
+   * Tests OAuth2->verifyAccessToken() with different expiry dates
+   * 
+   * @dataProvider generateExpiryTokens
+   */
+  public function testVerifyAccessTokenCheckExpiry($token, $expectedToPass) {
+    
+    // Set up the mock storage to say this token does not exist
+    $mockStorage = $this->getMock('IOAuth2Storage');
+    $mockStorage->expects($this->once())
+      ->method('getAccessToken')
+      ->will($this->returnValue($token));
+      
+    $this->fixture = new OAuth2($mockStorage);
+    
+    $scope = null;
+    
+    
+    // When valid, we just want any sort of token
+    if ($expectedToPass) { 
+      $actual = $this->fixture->verifyAccessToken($this->tokenId, $scope);
+      $this->assertNotEmpty($actual, "verifyAccessToken() was expected to PASS, but it failed");
+      $this->assertInternalType('array', $actual);
+    }
+    else {
+      $this->setExpectedException('OAuth2AuthenticateException');
+      $this->fixture->verifyAccessToken($this->tokenId, $scope);
+    }
+  }
+  
+	/**
+   * Tests OAuth2->verifyAccessToken() with different scopes
+   * 
+   * @dataProvider generateScopes
+   */
+  public function testVerifyAccessTokenCheckScope($scopeRequired, $token, $expectedToPass) {
+    
+    // Set up the mock storage to say this token does not exist
+    $mockStorage = $this->getMock('IOAuth2Storage');
+    $mockStorage->expects($this->once())
+      ->method('getAccessToken')
+      ->will($this->returnValue($token));
+      
+    $this->fixture = new OAuth2($mockStorage);
+    
+    // When valid, we just want any sort of token
+    if ($expectedToPass) {
+      $actual = $this->fixture->verifyAccessToken($this->tokenId, $scopeRequired);
+      $this->assertNotEmpty($actual, "verifyAccessToken() was expected to PASS, but it failed");
+      $this->assertInternalType('array', $actual);
+    }
+    else {
+      $this->setExpectedException('OAuth2AuthenticateException');
+      $this->fixture->verifyAccessToken($this->tokenId, $scopeRequired);
+    }
+  }
+  
+  /**
+   * Tests OAuth2->grantAccessToken() for missing data
+   * 
+   * @dataProvider generateEmptyDataForGrant
+   */
+  public function testGrantAccessTokenMissingData($inputData, $authHeaders) {
+    $mockStorage = $this->getMock('IOAuth2Storage');
+    $this->fixture = new OAuth2($mockStorage);
+    
+    $this->setExpectedException('OAuth2ServerException');
+    $this->fixture->grantAccessToken($inputData, $authHeaders);
+  }
+  
+  /**
+   * Tests OAuth2->grantAccessToken()
+   * 
+   * Tests the different ways client credentials can be provided.
+   */
+  public function testGrantAccessTokenCheckClientCredentials() {
+    $mockStorage = $this->getMock('IOAuth2Storage');
+    $mockStorage->expects($this->any())
+      ->method('checkClientCredentials')
+      ->will($this->returnValue(TRUE)); // Always return true for any combination of user/pass
+    $this->fixture = new OAuth2($mockStorage);
+    
+    $inputData = array('grant_type' => OAuth2::GRANT_TYPE_AUTH_CODE);
+    $authHeaders = array();
+    
+    // First, confirm that an non-client related error is thrown:
+    try {
+      $this->fixture->grantAccessToken($inputData, $authHeaders);
+      $this->fail('The expected exception OAuth2ServerException was not thrown');
+    } catch ( OAuth2ServerException $e ) {
+      $this->assertEquals(OAuth2::ERROR_INVALID_CLIENT, $e->getMessage());
+    }
+
+    // Confirm Auth header
+    $authHeaders = array('PHP_AUTH_USER' => 'dev-abc', 'PHP_AUTH_PW' => 'pass');
+    $inputData = array('grant_type' => OAuth2::GRANT_TYPE_AUTH_CODE, 'client_id' => 'dev-abc'); // When using auth, client_id must match
+    try {
+      $this->fixture->grantAccessToken($inputData, $authHeaders);
+      $this->fail('The expected exception OAuth2ServerException was not thrown');
+    } catch ( OAuth2ServerException $e ) {
+      $this->assertNotEquals(OAuth2::ERROR_INVALID_CLIENT, $e->getMessage());
+    }
+    
+    // Confirm GET/POST
+    $authHeaders = array();
+    $inputData = array('grant_type' => OAuth2::GRANT_TYPE_AUTH_CODE, 'client_id' => 'dev-abc', 'client_secret' => 'foo'); // When using auth, client_id must match
+    try {
+      $this->fixture->grantAccessToken($inputData, $authHeaders);
+      $this->fail('The expected exception OAuth2ServerException was not thrown');
+    } catch ( OAuth2ServerException $e ) {
+      $this->assertNotEquals(OAuth2::ERROR_INVALID_CLIENT, $e->getMessage());
+    }
+  }
+  
+  /**
+   * Tests OAuth2->grantAccessToken() with Auth code grant
+   * 
+   */
+  public function testGrantAccessTokenWithGrantAuthCodeMandatoryParams() {
+    $mockStorage = $this->createBaseMock('IOAuth2GrantCode');
+    $inputData = array('grant_type' => OAuth2::GRANT_TYPE_AUTH_CODE, 'client_id' => 'a', 'client_secret' => 'b');
+    $fakeAuthCode = array('client_id' => $inputData['client_id'], 'redirect_uri' => '/foo', 'expires' => time() + 60);
+    $fakeAccessToken = array('access_token' => 'abcde');
+    
+    // Ensure redirect URI and auth-code is mandatory
+    try {
+      $this->fixture = new OAuth2($mockStorage);
+      $this->fixture->setVariable(OAuth2::CONFIG_ENFORCE_INPUT_REDIRECT, true); // Only required when this is set
+      $this->fixture->grantAccessToken($inputData + array('code' => 'foo'), array());
+      $this->fail('The expected exception OAuth2ServerException was not thrown');
+    } catch ( OAuth2ServerException $e ) {
+      $this->assertEquals(OAuth2::ERROR_INVALID_REQUEST, $e->getMessage());
+    }
+    try {
+      $this->fixture = new OAuth2($mockStorage);
+      $this->fixture->grantAccessToken($inputData + array('redirect_uri' => 'foo'), array());
+      $this->fail('The expected exception OAuth2ServerException was not thrown');
+    } catch ( OAuth2ServerException $e ) {
+      $this->assertEquals(OAuth2::ERROR_INVALID_REQUEST, $e->getMessage());
+    }
+  }
+  
+   /**
+   * Tests OAuth2->grantAccessToken() with Auth code grant
+   * 
+   */
+  public function testGrantAccessTokenWithGrantAuthCodeNoToken() {
+    $mockStorage = $this->createBaseMock('IOAuth2GrantCode');
+    $inputData = array('grant_type' => OAuth2::GRANT_TYPE_AUTH_CODE, 'client_id' => 'a', 'client_secret' => 'b', 'redirect_uri' => 'foo', 'code'=> 'foo');
+    
+    // Ensure missing auth code raises an error
+    try {
+      $this->fixture = new OAuth2($mockStorage);
+      $this->fixture->grantAccessToken($inputData + array(), array());
+      $this->fail('The expected exception OAuth2ServerException was not thrown');
+    }
+    catch ( OAuth2ServerException $e ) {
+      $this->assertEquals(OAuth2::ERROR_INVALID_GRANT, $e->getMessage());
+    }
+  }
+  
+  /**
+   * Tests OAuth2->grantAccessToken() with checks the redirect URI
+   * 
+   */
+  public function testGrantAccessTokenWithGrantAuthCodeRedirectChecked() {
+    $inputData = array('redirect_uri' => 'http://www.crossdomain.com/my/subdir', 'grant_type' => OAuth2::GRANT_TYPE_AUTH_CODE, 'client_id' => 'my_little_app', 'client_secret' => 'b', 'code'=> 'foo');
+    $storedToken = array('redirect_uri' => 'http://www.example.com', 'client_id' => 'my_little_app', 'expires' => time() + 60);
+    
+    $mockStorage = $this->createBaseMock('IOAuth2GrantCode');
+    $mockStorage->expects($this->any())
+      ->method('getAuthCode')
+      ->will($this->returnValue($storedToken));
+      
+    // Ensure that the redirect_uri is checked
+    try {
+      $this->fixture = new OAuth2($mockStorage);
+      $this->fixture->grantAccessToken($inputData, array());
+      
+      $this->fail('The expected exception OAuth2ServerException was not thrown');
+    }
+    catch ( OAuth2ServerException $e ) {
+      $this->assertEquals(OAuth2::ERROR_REDIRECT_URI_MISMATCH, $e->getMessage());
+    }
+  }
+  
+	/**
+   * Tests OAuth2->grantAccessToken() with checks the client ID is matched
+   * 
+   */
+  public function testGrantAccessTokenWithGrantAuthCodeClientIdChecked() {
+    $inputData = array('client_id' => 'another_app', 'grant_type' => OAuth2::GRANT_TYPE_AUTH_CODE, 'redirect_uri' => 'http://www.example.com/my/subdir', 'client_secret' => 'b', 'code'=> 'foo');
+    $storedToken = array('client_id' => 'my_little_app', 'redirect_uri' => 'http://www.example.com', 'expires' => time() + 60);
+    
+    $mockStorage = $this->createBaseMock('IOAuth2GrantCode');
+    $mockStorage->expects($this->any())
+      ->method('getAuthCode')
+      ->will($this->returnValue($storedToken));
+      
+    // Ensure the client ID is checked
+    try {
+      $this->fixture = new OAuth2($mockStorage);
+      $this->fixture->grantAccessToken($inputData, array());
+      
+      $this->fail('The expected exception OAuth2ServerException was not thrown');
+    }
+    catch ( OAuth2ServerException $e ) {
+      $this->assertEquals(OAuth2::ERROR_INVALID_GRANT, $e->getMessage());
+    }
+  }
+  
+  /**
+   * Tests OAuth2->grantAccessToken() with implicit
+   * 
+   */
+  public function testGrantAccessTokenWithGrantImplicit() {
+    $this->markTestIncomplete ( "grantAccessToken test not implemented" );
+    
+    $this->fixture->grantAccessToken(/* parameters */);
+  }
+  
+	/**
+   * Tests OAuth2->grantAccessToken() with user credentials
+   * 
+   */
+  public function testGrantAccessTokenWithGrantUser() {
+    $this->markTestIncomplete ( "grantAccessToken test not implemented" );
+    
+    $this->fixture->grantAccessToken(/* parameters */);
+  }
+  
+  
+	/**
+   * Tests OAuth2->grantAccessToken() with client credentials
+   * 
+   */
+  public function testGrantAccessTokenWithGrantClient() {
+    $this->markTestIncomplete ( "grantAccessToken test not implemented" );
+    
+    $this->fixture->grantAccessToken(/* parameters */);
+  }
+  
+	/**
+   * Tests OAuth2->grantAccessToken() with refresh token
+   * 
+   */
+  public function testGrantAccessTokenWithGrantRefresh() {
+    $this->markTestIncomplete ( "grantAccessToken test not implemented" );
+    
+    $this->fixture->grantAccessToken(/* parameters */);
+  }
+  
+	/**
+   * Tests OAuth2->grantAccessToken() with extension
+   * 
+   */
+  public function testGrantAccessTokenWithGrantExtension() {
+    $this->markTestIncomplete ( "grantAccessToken test not implemented" );
+    
+    $this->fixture->grantAccessToken(/* parameters */);
+  }
+  
+  /**
+   * Tests OAuth2->getAuthorizeParams()
+   */
+  public function testGetAuthorizeParams() {
+    // TODO Auto-generated OAuth2Test->testGetAuthorizeParams()
+    $this->markTestIncomplete ( "getAuthorizeParams test not implemented" );
+    
+    $this->fixture->getAuthorizeParams(/* parameters */);
+  
+  }
+  
+  /**
+   * Tests OAuth2->finishClientAuthorization()
+   */
+  public function testFinishClientAuthorization() {
+    // TODO Auto-generated OAuth2Test->testFinishClientAuthorization()
+    $this->markTestIncomplete ( "finishClientAuthorization test not implemented" );
+    
+    $this->fixture->finishClientAuthorization(/* parameters */);
+  
+  }
+
+  // Utility methods
+  
+  /**
+   * 
+   * @param string $interfaceName
+   */
+  protected function createBaseMock($interfaceName) {
+    $mockStorage = $this->getMock($interfaceName);
+    $mockStorage->expects($this->any())
+      ->method('checkClientCredentials')
+      ->will($this->returnValue(TRUE)); // Always return true for any combination of user/pass
+    $mockStorage->expects($this->any())
+      ->method('checkRestrictedGrantType')
+      ->will($this->returnValue(TRUE)); // Always return true for any combination of user/pass
+      
+     return $mockStorage;
+  }
+  
+  // Data Providers below:
+  
+  /**
+   * Dataprovider for testVerifyAccessTokenMalformedToken().
+   * 
+   * Produces malformed access tokens
+   */
+  public function generateMalformedTokens() {
+    return array(
+      array(array()), // an empty array as a token
+      array(array('expires' => 5)), // missing client_id
+      array(array('client_id' => 6)), // missing expires
+      array(array('something' => 6)), // missing both 'expires' and 'client_id'
+    );
+  }
+  
+  /**
+   * Dataprovider for testVerifyAccessTokenCheckExpiry().
+   * 
+   * Produces malformed access tokens
+   */
+  public function generateExpiryTokens() {
+    return array(
+      array(array('client_id' => 'blah', 'expires' => time() - 30),                 FALSE), // 30 seconds ago should fail
+      array(array('client_id' => 'blah', 'expires' => time() - 1),                  FALSE), // now-ish should fail
+      array(array('client_id' => 'blah', 'expires' => 0),                           FALSE), // 1970 should fail
+      array(array('client_id' => 'blah', 'expires' => time() + 30),                 TRUE),  // 30 seconds in the future should be valid
+      array(array('client_id' => 'blah', 'expires' => time() + 86400),              TRUE),  // 1 day in the future should be valid
+      array(array('client_id' => 'blah', 'expires' => time() + (365 * 86400)),      TRUE),  // 1 year should be valid
+      array(array('client_id' => 'blah', 'expires' => time() + (10 * 365 * 86400)), TRUE),  // 10 years should be valid
+    );
+  }
+  
+  /**
+   * Dataprovider for testVerifyAccessTokenCheckExpiry().
+   * 
+   * Produces malformed access tokens
+   */
+  public function generateScopes() {
+    $baseToken = array('client_id' => 'blah', 'expires' => time() + 60);
+    
+    return array(
+      array(null,   $baseToken + array(),                               TRUE), // missing scope is valif
+      array(null,   $baseToken + array('scope' => null),                TRUE), // null scope is valid
+      array('',     $baseToken + array('scope' => ''),                  TRUE), // empty scope is valid
+      array('read', $baseToken + array('scope' => 'read'),              TRUE), // exact same scope is valid
+      array('read', $baseToken + array('scope' => ' read '),            TRUE), // exact same scope is valid
+      array(' read ', $baseToken + array('scope' => 'read'),            TRUE), // exact same scope is valid
+      array('read', $baseToken + array('scope' => 'read write delete'), TRUE), // contains scope 
+      array('read', $baseToken + array('scope' => 'write read delete'), TRUE), // contains scope 
+      array('read', $baseToken + array('scope' => 'delete write read'), TRUE), // contains scope
+      
+      // Invalid combinations
+      array('read', $baseToken + array('scope' => 'write'),            FALSE),
+      array('read', $baseToken + array('scope' => 'apple banana'),     FALSE),
+      array('read', $baseToken + array('scope' => 'apple read-write'), FALSE),
+      array('read', $baseToken + array('scope' => 'apple read,write'), FALSE),
+      array('read', $baseToken + array('scope' => null),               FALSE),
+      array('read', $baseToken + array('scope' => ''),                 FALSE),
+    );
+  }
+  
+  /**
+   * Provider for OAuth2->grantAccessToken()
+   */
+  public function generateEmptyDataForGrant() {
+    return array(
+      array(
+        array(), array()
+      ),
+      array(
+        array(), array('grant_type' => OAuth2::GRANT_TYPE_AUTH_CODE) // grant_type in auth headers should be ignored
+      ),
+      array(
+        array('not_grant_type' => 5), array()
+      ),
+    );
+  }
+}
+
Index: trunk/prototype/library/oauth2/tests/OAuth2OutputTest.php
===================================================================
--- trunk/prototype/library/oauth2/tests/OAuth2OutputTest.php	(revision 6528)
+++ trunk/prototype/library/oauth2/tests/OAuth2OutputTest.php	(revision 6528)
@@ -0,0 +1,72 @@
+<?php
+require __DIR__ . '/../lib/OAuth2.php';
+require __DIR__ . '/../lib/IOAuth2Storage.php';
+require __DIR__ . '/../lib/IOAuth2GrantCode.php';
+
+/**
+ * OAuth2 test cases that invovle capturing output.
+ */
+class OAuth2OutputTest extends PHPUnit_Extensions_OutputTestCase {
+  
+  /**
+   * @var OAuth2
+   */
+  private $fixture;
+  
+  /**
+   * Tests OAuth2->grantAccessToken() with successful Auth code grant
+   * 
+   */
+  public function testGrantAccessTokenWithGrantAuthCodeSuccess() {
+    $inputData = array('grant_type' => OAuth2::GRANT_TYPE_AUTH_CODE, 'redirect_uri' => 'http://www.example.com/my/subdir', 'client_id' => 'my_little_app', 'client_secret' => 'b', 'code'=> 'foo');
+    $storedToken = array('redirect_uri' => 'http://www.example.com', 'client_id' => 'my_little_app', 'expires' => time() + 60);
+    
+    $mockStorage = $this->createBaseMock('IOAuth2GrantCode');
+    $mockStorage->expects($this->any())
+      ->method('getAuthCode')
+      ->will($this->returnValue($storedToken));
+      
+    // Successful token grant will return a JSON encoded token:
+    $this->expectOutputRegex('/{"access_token":".*","expires_in":\d+,"token_type":"bearer"/');
+    $this->fixture = new OAuth2($mockStorage);
+    $this->fixture->grantAccessToken($inputData, array());
+  }
+  
+  /**
+   * Tests OAuth2->grantAccessToken() with successful Auth code grant, but without redreict_uri in the input
+   */
+  public function testGrantAccessTokenWithGrantAuthCodeSuccessWithoutRedirect() {
+    $inputData = array('grant_type' => OAuth2::GRANT_TYPE_AUTH_CODE, 'client_id' => 'my_little_app', 'client_secret' => 'b', 'code'=> 'foo');
+    $storedToken = array('redirect_uri' => 'http://www.example.com', 'client_id' => 'my_little_app', 'expires' => time() + 60);
+    
+    $mockStorage = $this->createBaseMock('IOAuth2GrantCode');
+    $mockStorage->expects($this->any())
+      ->method('getAuthCode')
+      ->will($this->returnValue($storedToken));
+      
+    // Successful token grant will return a JSON encoded token:
+    $this->expectOutputRegex('/{"access_token":".*","expires_in":\d+,"token_type":"bearer"/');
+    $this->fixture = new OAuth2($mockStorage);
+    $this->fixture->setVariable(OAuth2::CONFIG_ENFORCE_INPUT_REDIRECT, false); 
+    $this->fixture->grantAccessToken($inputData, array());
+  }
+  
+// Utility methods
+  
+  /**
+   * 
+   * @param string $interfaceName
+   */
+  protected function createBaseMock($interfaceName) {
+    $mockStorage = $this->getMock($interfaceName);
+    $mockStorage->expects($this->any())
+      ->method('checkClientCredentials')
+      ->will($this->returnValue(TRUE)); // Always return true for any combination of user/pass
+    $mockStorage->expects($this->any())
+      ->method('checkRestrictedGrantType')
+      ->will($this->returnValue(TRUE)); // Always return true for any combination of user/pass
+      
+     return $mockStorage;
+  }
+  
+}
Index: trunk/prototype/library/oauth2/CHANGELOG.txt
===================================================================
--- trunk/prototype/library/oauth2/CHANGELOG.txt	(revision 6528)
+++ trunk/prototype/library/oauth2/CHANGELOG.txt	(revision 6528)
@@ -0,0 +1,98 @@
+oauth2-php revision xxx, xxxx-xx-xx (development version)
+----------------------
+
+oauth2-php revision 23, 2011-01-25
+----------------------
+* introduce Drupal style getVariable() and setVariable, replace legacy
+  variable get/set functions.
+* remove hardcode PHP display_error and errror_reporting, as this should
+  be manually implement within 3rd party integration.
+* make verbose error as configurable and default disable, as this should
+  be manually enable within 3rd party integration.
+* add lib/OAuth2Client.inc and lib/OAuth2Exception.inc for client-side
+  implementation.
+
+oauth2-php revision 21, 2010-12-18
+----------------------
+* cleanup tabs and trailing whitespace at the end.
+* remove server/examples/mongo/lib/oauth.php and
+  server/examples/pdo/lib/oauth.php, so only keep single copy as
+  lib /oauth.php.
+* issue #5: Wrong variable name in get_access_token() in pdo_oatuh.php.
+* issue #6: mysql_create_tables.sql should allow scope to be NULL.
+* issue #7: authorize_client_response_type() is never used.
+* issue #9: Change "redirect_uri" filtering from FILTER_VALIDATE_URL to
+  FILTER_SANITIZE_URL.
+* better coding syntax for error() and callback_error().
+* better pdo_oauth2.php variable naming with change to
+  mysql_create_tables.sql.
+* change REGEX_CLIENT_ID as 3-32 characters long, so will work with md5()
+  result directly.
+* debug linkage to oauth2.php during previous commit.
+* debug redirect_uri check for AUTH_CODE_GRANT_TYPE, clone from
+  get_authorize_params().
+* update mysql_create_tables.sql with phpmyadmin export format.
+* rename library files, prepare for adding client-side implementation.
+* code cleanup with indent and spacing.
+* code cleanup true/false/null with TRUE/FALSE/NULL.
+* rename constants with OAUTH2_ prefix, prevent 3rd party integration
+  conflict.
+* remove HTTP 400 response constant, as useless refer to draft v10.
+* merge ERROR_INVALID_CLIENT_ID and ERROR_UNAUTHORIZED_CLIENT as
+  OAUTH2_ERROR_INVALID_CLIENT, as refer to that of draft v9 to v10 changes.
+* improve constants comment with doxygen syntax.
+* update class function call naming.
+* coding style clean up.
+* update part of documents.
+* change expirseRefreshToken() as unsetRefreshToken().
+* update token and auth code generation as md5() result, simpler for manual
+  debug with web browser.
+* update all documents.
+* restructure @ingroup.
+* rename checkRestrictedClientResponseTypes() as
+  checkRestrictedAuthResponseType().
+* rename checkRestrictedClientGrantTypes() as checkRestrictedGrantType().
+* rename error() as errorJsonResponse().
+* rename errorCallback() as errorDoRedirectUriCallback().
+* rename send401Unauthorized() as errorWWWAuthenticateResponseHeader(),
+  update support with different HTTP status code.
+* update __construct() with array input.
+* update finishClientAuthorization() with array input.
+* add get/set functions for $access_token_lifetime, $auth_code_lifetime and
+  $refresh_token_lifetime.
+* fix a lots of typos.
+* document all sample server implementation.
+* more documents.
+* add config.doxy for doxygen default setup.
+* add MIT LICENSE.txt.
+* add CHANGELOG.txt.
+
+oauth2-php revision 9, 2010-09-04
+----------------------
+- fixes for issues #2 and #4, updates oauth lib in the example folders to
+  the latest version in the 'lib' folder.
+- updates server library to revision 10 of the OAuth 2.0 spec.
+- adds an option for more verbose error messages to be returned in the JSON
+  response.
+- adds method to be overridden for expiring used refresh tokens.
+- fixes bug checking token expiration.
+- makes some more methods protected instead of private so they can be
+  overridden.
+- fixes issue #1 http://code.google.com/p/oauth2-php/issues/detail?id=1
+
+oauth2-php revision 7, 2010-06-29
+----------------------
+- fixed mongo connection constants.
+- updated store_refresh_token to include expires time.
+- changed example server directory structure
+- corrected "false" return result on get_stored_auth_code.
+- implemented PDO example adapter.
+- corrected an error in assertion grant type.
+- updated for ietf draft v9:
+  http://tools.ietf.org/html/draft-ietf-oauth-v2-09.
+- updated updated to support v9 lib.
+- added mysql table creation script.
+
+oauth2-php revision 0, 2010-06-27
+----------------------
+- initial commit.
Index: trunk/prototype/library/oauth2/lib/IOAuth2RefreshTokens.php
===================================================================
--- trunk/prototype/library/oauth2/lib/IOAuth2RefreshTokens.php	(revision 6528)
+++ trunk/prototype/library/oauth2/lib/IOAuth2RefreshTokens.php	(revision 6528)
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * Storage engines that want to support refresh tokens should
+ * implement this interface.
+ * 
+ * @author Dave Rochwerger <catch.dave@gmail.com>
+ * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-6
+ * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-1.5
+ */
+interface IOAuth2RefreshTokens extends IOAuth2Storage {
+
+	/**
+	 * Grant refresh access tokens.
+	 *
+	 * Retrieve the stored data for the given refresh token.
+	 *
+	 * Required for OAuth2::GRANT_TYPE_REFRESH_TOKEN.
+	 *
+	 * @param $refresh_token
+	 * Refresh token to be check with.
+	 *
+	 * @return
+	 * An associative array as below, and NULL if the refresh_token is
+	 * invalid:
+	 * - client_id: Stored client identifier.
+	 * - expires: Stored expiration unix timestamp.
+	 * - scope: (optional) Stored scope values in space-separated string.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-6
+	 *
+	 * @ingroup oauth2_section_6
+	 */
+	public function getRefreshToken($refresh_token);
+
+	/**
+	 * Take the provided refresh token values and store them somewhere.
+	 *
+	 * This function should be the storage counterpart to getRefreshToken().
+	 *
+	 * If storage fails for some reason, we're not currently checking for
+	 * any sort of success/failure, so you should bail out of the script
+	 * and provide a descriptive fail message.
+	 *
+	 * Required for OAuth2::GRANT_TYPE_REFRESH_TOKEN.
+	 *
+	 * @param $refresh_token
+	 * Refresh token to be stored.
+	 * @param $client_id
+	 * Client identifier to be stored.
+	 * @param $expires
+	 * expires to be stored.
+	 * @param $scope
+	 * (optional) Scopes to be stored in space-separated string.
+	 *
+	 * @ingroup oauth2_section_6
+	 */
+	public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = NULL);
+
+	/**
+	 * Expire a used refresh token.
+	 *
+	 * This is not explicitly required in the spec, but is almost implied.
+	 * After granting a new refresh token, the old one is no longer useful and
+	 * so should be forcibly expired in the data store so it can't be used again.
+	 *
+	 * If storage fails for some reason, we're not currently checking for
+	 * any sort of success/failure, so you should bail out of the script
+	 * and provide a descriptive fail message.
+	 *
+	 * @param $refresh_token
+	 * Refresh token to be expirse.
+	 *
+	 * @ingroup oauth2_section_6
+	 */
+	public function unsetRefreshToken($refresh_token);
+}
Index: trunk/prototype/library/oauth2/lib/IOAuth2Storage.php
===================================================================
--- trunk/prototype/library/oauth2/lib/IOAuth2Storage.php	(revision 6528)
+++ trunk/prototype/library/oauth2/lib/IOAuth2Storage.php	(revision 6528)
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * All storage engines need to implement this interface in order to use OAuth2 server
+ * 
+ * @author David Rochwerger <catch.dave@gmail.com>
+ */
+interface IOAuth2Storage {
+
+	/**
+	 * Make sure that the client credentials is valid.
+	 * 
+	 * @param $client_id
+	 * Client identifier to be check with.
+	 * @param $client_secret
+	 * (optional) If a secret is required, check that they've given the right one.
+	 *
+	 * @return
+	 * TRUE if the client credentials are valid, and MUST return FALSE if it isn't.
+	 * @endcode
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-3.1
+	 *
+	 * @ingroup oauth2_section_3
+	 */
+	public function checkClientCredentials($client_id, $client_secret = NULL);
+
+	/**
+	 * Get client details corresponding client_id.
+	 *
+	 * OAuth says we should store request URIs for each registered client.
+	 * Implement this function to grab the stored URI for a given client id.
+	 *
+	 * @param $client_id
+	 * Client identifier to be check with.
+	 *
+	 * @return array
+	 * Client details. Only mandatory item is the "registered redirect URI", and MUST
+	 * return FALSE if the given client does not exist or is invalid.
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	public function getClientDetails($client_id);
+
+	/**
+	 * Look up the supplied oauth_token from storage.
+	 *
+	 * We need to retrieve access token data as we create and verify tokens.
+	 *
+	 * @param $oauth_token
+	 * oauth_token to be check with.
+	 *
+	 * @return
+	 * An associative array as below, and return NULL if the supplied oauth_token
+	 * is invalid:
+	 * - client_id: Stored client identifier.
+	 * - expires: Stored expiration in unix timestamp.
+	 * - scope: (optional) Stored scope values in space-separated string.
+	 *
+	 * @ingroup oauth2_section_7
+	 */
+	public function getAccessToken($oauth_token);
+
+	/**
+	 * Store the supplied access token values to storage.
+	 *
+	 * We need to store access token data as we create and verify tokens.
+	 *
+	 * @param $oauth_token
+	 * oauth_token to be stored.
+	 * @param $client_id
+	 * Client identifier to be stored.
+	 * @param $user_id
+	 * User identifier to be stored.
+	 * @param $expires
+	 * Expiration to be stored.
+	 * @param $scope
+	 * (optional) Scopes to be stored in space-separated string.
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = NULL, $refresh_token);
+
+	/**
+	 * Check restricted grant types of corresponding client identifier.
+	 *
+	 * If you want to restrict clients to certain grant types, override this
+	 * function.
+	 *
+	 * @param $client_id
+	 * Client identifier to be check with.
+	 * @param $grant_type
+	 * Grant type to be check with, would be one of the values contained in
+	 * OAuth2::GRANT_TYPE_REGEXP.
+	 *
+	 * @return
+	 * TRUE if the grant type is supported by this client identifier, and
+	 * FALSE if it isn't.
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	public function checkRestrictedGrantType($client_id, $grant_type);
+}
Index: trunk/prototype/library/oauth2/lib/OAuth2AuthenticateException.php
===================================================================
--- trunk/prototype/library/oauth2/lib/OAuth2AuthenticateException.php	(revision 6528)
+++ trunk/prototype/library/oauth2/lib/OAuth2AuthenticateException.php	(revision 6528)
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * Send an error header with the given realm and an error, if provided.
+ * Suitable for the bearer token type.
+ *
+ * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-04#section-2.4
+ *
+ * @ingroup oauth2_error
+ */
+class OAuth2AuthenticateException extends OAuth2ServerException {
+	
+	protected $header;
+
+	/**
+	 * 
+	 * @param $http_status_code
+	 * HTTP status code message as predefined.
+	 * @param $error
+	 * The "error" attribute is used to provide the client with the reason
+	 * why the access request was declined.
+	 * @param $error_description
+	 * (optional) The "error_description" attribute provides a human-readable text
+	 * containing additional information, used to assist in the understanding
+	 * and resolution of the error occurred.
+	 * @param $scope
+	 * A space-delimited list of scope values indicating the required scope
+	 * of the access token for accessing the requested resource.
+	 */
+	public function __construct($httpCode, $tokenType, $realm, $error, $error_description = NULL, $scope = NULL) {
+		parent::__construct($httpCode, $error, $error_description);
+		
+		if ($scope) {
+			$this->errorData['scope'] = $scope;
+		}
+		
+		// Build header
+		$this->header = sprintf('WWW-Authenticate: %s realm="%s"', ucwords($tokenType), $realm);
+		foreach ( $this->errorData as $key => $value ) {
+			$this->header .= ", $key=\"$value\"";
+		}
+	}
+
+	/**
+	 * Send out HTTP headers for JSON.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2
+	 *
+	 * @ingroup oauth2_section_5
+	 */
+	protected function sendHeaders() {
+		header($this->header);
+	}
+}
Index: trunk/prototype/library/oauth2/lib/OAuth2Client.php
===================================================================
--- trunk/prototype/library/oauth2/lib/OAuth2Client.php	(revision 6528)
+++ trunk/prototype/library/oauth2/lib/OAuth2Client.php	(revision 6528)
@@ -0,0 +1,692 @@
+<?php
+
+/**
+ * OAuth2.0 draft v10 client-side implementation.
+ *
+ * @author Originally written by Naitik Shah <naitik@facebook.com>.
+ * @author Update to draft v10 by Edison Wong <hswong3i@pantarei-design.com>.
+ *
+ * @sa <a href="https://github.com/facebook/php-sdk">Facebook PHP SDK</a>.
+ */
+abstract class OAuth2Client {
+	
+	/**
+	 * The default Cache Lifetime (in seconds).
+	 */
+	const DEFAULT_EXPIRES_IN = 3600;
+	
+	/**
+	 * The default Base domain for the Cookie.
+	 */
+	const DEFAULT_BASE_DOMAIN = '';
+	
+	/**
+	 * Array of persistent variables stored.
+	 */
+	protected $conf = array();
+
+	/**
+	 * Returns a persistent variable.
+	 *
+	 * To avoid problems, always use lower case for persistent variable names.
+	 *
+	 * @param $name
+	 * The name of the variable to return.
+	 * @param $default
+	 * The default value to use if this variable has never been set.
+	 *
+	 * @return
+	 * The value of the variable.
+	 */
+	public function getVariable($name, $default = NULL) {
+		return isset($this->conf[$name]) ? $this->conf[$name] : $default;
+	}
+
+	/**
+	 * Sets a persistent variable.
+	 *
+	 * To avoid problems, always use lower case for persistent variable names.
+	 *
+	 * @param $name
+	 * The name of the variable to set.
+	 * @param $value
+	 * The value to set.
+	 */
+	public function setVariable($name, $value) {
+		$this->conf[$name] = $value;
+		return $this;
+	}
+
+	// Stuff that should get overridden by subclasses.
+	//
+	// I don't want to make these abstract, because then subclasses would have
+	// to implement all of them, which is too much work.
+	//
+	// So they're just stubs. Override the ones you need.
+	
+
+	/**
+	 * Initialize a Drupal OAuth2.0 Application.
+	 *
+	 * @param $config
+	 * An associative array as below:
+	 * - base_uri: The base URI for the OAuth2.0 endpoints.
+	 * - code: (optional) The authorization code.
+	 * - username: (optional) The username.
+	 * - password: (optional) The password.
+	 * - client_id: (optional) The application ID.
+	 * - client_secret: (optional) The application secret.
+	 * - authorize_uri: (optional) The end-user authorization endpoint URI.
+	 * - access_token_uri: (optional) The token endpoint URI.
+	 * - services_uri: (optional) The services endpoint URI.
+	 * - cookie_support: (optional) TRUE to enable cookie support.
+	 * - base_domain: (optional) The domain for the cookie.
+	 * - file_upload_support: (optional) TRUE if file uploads are enabled.
+	 */
+	public function __construct($config = array()) {
+		// We must set base_uri first.
+		$this->setVariable('base_uri', $config['base_uri']);
+		unset($config['base_uri']);
+		
+		// Use predefined OAuth2.0 params, or get it from $_REQUEST.
+		foreach ( array('code', 'username', 'password') as $name ) {
+			if (isset($config[$name])) {
+				$this->setVariable($name, $config[$name]);
+			} else if (isset($_REQUEST[$name]) && !empty($_REQUEST[$name])) {
+				$this->setVariable($name, $_REQUEST[$name]);
+			}
+			unset($config[$name]);
+		}
+		
+		// Endpoint URIs.
+		foreach ( array('authorize_uri', 'access_token_uri', 'services_uri') as $name ) {
+			if (isset($config[$name]))
+				if (substr($config[$name], 0, 4) == "http") {
+					$this->setVariable($name, $config[$name]);
+				} else {
+					$this->setVariable($name, $this->getVariable('base_uri') . $config[$name]);
+				}
+			unset($config[$name]);
+		}
+		
+		// Other else configurations.
+		foreach ( $config as $name => $value ) {
+			$this->setVariable($name, $value);
+		}
+	}
+
+	/**
+	 * Try to get session object from custom method.
+	 *
+	 * By default we generate session object based on access_token response, or
+	 * if it is provided from server with $_REQUEST. For sure, if it is provided
+	 * by server it should follow our session object format.
+	 *
+	 * Session object provided by server can ensure the correct expires and
+	 * base_domain setup as predefined in server, also you may get more useful
+	 * information for custom functionality, too. BTW, this may require for
+	 * additional remote call overhead.
+	 *
+	 * You may wish to override this function with your custom version due to
+	 * your own server-side implementation.
+	 *
+	 * @param $access_token
+	 * (optional) A valid access token in associative array as below:
+	 * - access_token: A valid access_token generated by OAuth2.0
+	 * authorization endpoint.
+	 * - expires_in: (optional) A valid expires_in generated by OAuth2.0
+	 * authorization endpoint.
+	 * - refresh_token: (optional) A valid refresh_token generated by OAuth2.0
+	 * authorization endpoint.
+	 * - scope: (optional) A valid scope generated by OAuth2.0
+	 * authorization endpoint.
+	 *
+	 * @return
+	 * A valid session object in associative array for setup cookie, and
+	 * NULL if not able to generate it with custom method.
+	 */
+	protected function getSessionObject($access_token = NULL) {
+		$session = NULL;
+		
+		// Try generate local version of session cookie.
+		if (!empty($access_token) && isset($access_token['access_token'])) {
+			$session['access_token'] = $access_token['access_token'];
+			$session['base_domain'] = $this->getVariable('base_domain', self::DEFAULT_BASE_DOMAIN);
+			$session['expires'] = isset($access_token['expires_in']) ? time() + $access_token['expires_in'] : time() + $this->getVariable('expires_in', self::DEFAULT_EXPIRES_IN);
+			$session['refresh_token'] = isset($access_token['refresh_token']) ? $access_token['refresh_token'] : '';
+			$session['scope'] = isset($access_token['scope']) ? $access_token['scope'] : '';
+			$session['secret'] = md5(base64_encode(pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), uniqid())));
+			
+			// Provide our own signature.
+			$sig = self::generateSignature($session, $this->getVariable('client_secret'));
+			$session['sig'] = $sig;
+		}
+		
+		// Try loading session from $_REQUEST.
+		if (!$session && isset($_REQUEST['session'])) {
+			$session = json_decode(get_magic_quotes_gpc() ? stripslashes($_REQUEST['session']) : $_REQUEST['session'], TRUE);
+		}
+		
+		return $session;
+	}
+
+	/**
+	 * Make an API call.
+	 *
+	 * Support both OAuth2.0 or normal GET/POST API call, with relative
+	 * or absolute URI.
+	 *
+	 * If no valid OAuth2.0 access token found in session object, this function
+	 * will automatically switch as normal remote API call without "oauth_token"
+	 * parameter.
+	 *
+	 * Assume server reply in JSON object and always decode during return. If
+	 * you hope to issue a raw query, please use makeRequest().
+	 *
+	 * @param $path
+	 * The target path, relative to base_path/service_uri or an absolute URI.
+	 * @param $method
+	 * (optional) The HTTP method (default 'GET').
+	 * @param $params
+	 * (optional The GET/POST parameters.
+	 *
+	 * @return
+	 * The JSON decoded response object.
+	 *
+	 * @throws OAuth2Exception
+	 */
+	public function api($path, $method = 'GET', $params = array()) {
+		if (is_array($method) && empty($params)) {
+			$params = $method;
+			$method = 'GET';
+		}
+		
+		// json_encode all params values that are not strings.
+		foreach ( $params as $key => $value ) {
+			if (!is_string($value)) {
+				$params[$key] = json_encode($value);
+			}
+		}
+		
+		$result = json_decode($this->makeOAuth2Request($this->getUri($path), $method, $params), TRUE);
+		
+		// Results are returned, errors are thrown.
+		if (is_array($result) && isset($result['error'])) {
+			$e = new OAuth2Exception($result);
+			switch ($e->getType()) {
+				// OAuth 2.0 Draft 10 style.
+				case 'invalid_token':
+					$this->setSession(NULL);
+				default :
+					$this->setSession(NULL);
+			}
+			throw $e;
+		}
+		return $result;
+	}
+	
+	// End stuff that should get overridden.
+	
+
+	/**
+	 * Default options for cURL.
+	 */
+	public static $CURL_OPTS = array(
+		CURLOPT_CONNECTTIMEOUT => 10,
+		CURLOPT_RETURNTRANSFER => TRUE,
+		CURLOPT_HEADER => TRUE,
+		CURLOPT_TIMEOUT => 60,
+		CURLOPT_USERAGENT => 'oauth2-draft-v10',
+		CURLOPT_HTTPHEADER => array("Accept: application/json")
+	);
+
+	/**
+	 * Set the Session.
+	 *
+	 * @param $session
+	 * (optional) The session object to be set. NULL if hope to frush existing
+	 * session object.
+	 * @param $write_cookie
+	 * (optional) TRUE if a cookie should be written. This value is ignored
+	 * if cookie support has been disabled.
+	 *
+	 * @return
+	 * The current OAuth2.0 client-side instance.
+	 */
+	public function setSession($session = NULL, $write_cookie = TRUE) {
+		$this->setVariable('_session', $this->validateSessionObject($session));
+		$this->setVariable('_session_loaded', TRUE);
+		if ($write_cookie) {
+			$this->setCookieFromSession($this->getVariable('_session'));
+		}
+		return $this;
+	}
+
+	/**
+	 * Get the session object.
+	 *
+	 * This will automatically look for a signed session via custom method,
+	 * OAuth2.0 grant type with authorization_code, OAuth2.0 grant type with
+	 * password, or cookie that we had already setup.
+	 *
+	 * @return
+	 * The valid session object with OAuth2.0 infomration, and NULL if not
+	 * able to discover any cases.
+	 */
+	public function getSession() {
+		if (!$this->getVariable('_session_loaded')) {
+			$session = NULL;
+			$write_cookie = TRUE;
+			
+			// Try obtain login session by custom method.
+			$session = $this->getSessionObject(NULL);
+			$session = $this->validateSessionObject($session);
+			
+			// grant_type == authorization_code.
+			if (!$session && $this->getVariable('code')) {
+				$access_token = $this->getAccessTokenFromAuthorizationCode($this->getVariable('code'));
+				$session = $this->getSessionObject($access_token);
+				$session = $this->validateSessionObject($session);
+			}
+			
+			// grant_type == password.
+			if (!$session && $this->getVariable('username') && $this->getVariable('password')) {
+				$access_token = $this->getAccessTokenFromPassword($this->getVariable('username'), $this->getVariable('password'));
+				$session = $this->getSessionObject($access_token);
+				$session = $this->validateSessionObject($session);
+			}
+			
+			// Try loading session from cookie if necessary.
+			if (!$session && $this->getVariable('cookie_support')) {
+				$cookie_name = $this->getSessionCookieName();
+				if (isset($_COOKIE[$cookie_name])) {
+					$session = array();
+					parse_str(trim(get_magic_quotes_gpc() ? stripslashes($_COOKIE[$cookie_name]) : $_COOKIE[$cookie_name], '"'), $session);
+					$session = $this->validateSessionObject($session);
+					// Write only if we need to delete a invalid session cookie.
+					$write_cookie = empty($session);
+				}
+			}
+			
+			$this->setSession($session, $write_cookie);
+		}
+		
+		return $this->getVariable('_session');
+	}
+
+	/**
+	 * Gets an OAuth2.0 access token from session.
+	 *
+	 * This will trigger getSession() and so we MUST initialize with required
+	 * configuration.
+	 *
+	 * @return
+	 * The valid OAuth2.0 access token, and NULL if not exists in session.
+	 */
+	public function getAccessToken() {
+		$session = $this->getSession();
+		return isset($session['access_token']) ? $session['access_token'] : NULL;
+	}
+
+	/**
+	 * Get access token from OAuth2.0 token endpoint with authorization code.
+	 *
+	 * This function will only be activated if both access token URI, client
+	 * identifier and client secret are setup correctly.
+	 *
+	 * @param $code
+	 * Authorization code issued by authorization server's authorization
+	 * endpoint.
+	 *
+	 * @return
+	 * A valid OAuth2.0 JSON decoded access token in associative array, and
+	 * NULL if not enough parameters or JSON decode failed.
+	 */
+	private function getAccessTokenFromAuthorizationCode($code) {
+		if ($this->getVariable('access_token_uri') && $this->getVariable('client_id') && $this->getVariable('client_secret')) {
+			return json_decode($this->makeRequest(
+				$this->getVariable('access_token_uri'),
+				'POST',
+				array(
+					'grant_type' => 'authorization_code',
+					'client_id' => $this->getVariable('client_id'),
+					'client_secret' => $this->getVariable('client_secret'),
+					'code' => $code,
+					'redirect_uri' => $this->getCurrentUri()
+				)
+			), TRUE);
+		}
+		return NULL;
+	}
+
+	/**
+	 * Get access token from OAuth2.0 token endpoint with basic user
+	 * credentials.
+	 *
+	 * This function will only be activated if both username and password
+	 * are setup correctly.
+	 *
+	 * @param $username
+	 * Username to be check with.
+	 * @param $password
+	 * Password to be check with.
+	 *
+	 * @return
+	 * A valid OAuth2.0 JSON decoded access token in associative array, and
+	 * NULL if not enough parameters or JSON decode failed.
+	 */
+	private function getAccessTokenFromPassword($username, $password) {
+		if ($this->getVariable('access_token_uri') && $this->getVariable('client_id') && $this->getVariable('client_secret')) {
+			return json_decode($this->makeRequest(
+				$this->getVariable('access_token_uri'),
+				'POST',
+				array(
+					'grant_type' => 'password',
+					'client_id' => $this->getVariable('client_id'),
+					'client_secret' => $this->getVariable('client_secret'),
+					'username' => $username,
+					'password' => $password
+				)
+			), TRUE);
+		}
+		return NULL;
+	}
+
+	/**
+	 * Make an OAuth2.0 Request.
+	 *
+	 * Automatically append "oauth_token" in query parameters if not yet
+	 * exists and able to discover a valid access token from session. Otherwise
+	 * just ignore setup with "oauth_token" and handle the API call AS-IS, and
+	 * so may issue a plain API call without OAuth2.0 protection.
+	 *
+	 * @param $path
+	 * The target path, relative to base_path/service_uri or an absolute URI.
+	 * @param $method
+	 * (optional) The HTTP method (default 'GET').
+	 * @param $params
+	 * (optional The GET/POST parameters.
+	 *
+	 * @return
+	 * The JSON decoded response object.
+	 *
+	 * @throws OAuth2Exception
+	 */
+	protected function makeOAuth2Request($path, $method = 'GET', $params = array()) {
+		if ((!isset($params['oauth_token']) || empty($params['oauth_token'])) && $oauth_token = $this->getAccessToken()) {
+			$params['oauth_token'] = $oauth_token;
+		}
+		return $this->makeRequest($path, $method, $params);
+	}
+
+	/**
+	 * Makes an HTTP request.
+	 *
+	 * This method can be overriden by subclasses if developers want to do
+	 * fancier things or use something other than cURL to make the request.
+	 *
+	 * @param $path
+	 * The target path, relative to base_path/service_uri or an absolute URI.
+	 * @param $method
+	 * (optional) The HTTP method (default 'GET').
+	 * @param $params
+	 * (optional The GET/POST parameters.
+	 * @param $ch
+	 * (optional) An initialized curl handle
+	 *
+	 * @return
+	 * The JSON decoded response object.
+	 */
+	protected function makeRequest($path, $method = 'GET', $params = array(), $ch = NULL) {
+		if (!$ch)
+			$ch = curl_init();
+		
+		$opts = self::$CURL_OPTS;
+		if ($params) {
+			switch ($method) {
+				case 'GET':
+					$path .= '?' . http_build_query($params, NULL, '&');
+					break;
+				// Method override as we always do a POST.
+				default :
+					if ($this->getVariable('file_upload_support')) {
+						$opts[CURLOPT_POSTFIELDS] = $params;
+					} else {
+						$opts[CURLOPT_POSTFIELDS] = http_build_query($params, NULL, '&');
+					}
+			}
+		}
+		$opts[CURLOPT_URL] = $path;
+		
+		// Disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
+		// for 2 seconds if the server does not support this header.
+		if (isset($opts[CURLOPT_HTTPHEADER])) {
+			$existing_headers = $opts[CURLOPT_HTTPHEADER];
+			$existing_headers[] = 'Expect:';
+			$opts[CURLOPT_HTTPHEADER] = $existing_headers;
+		} else {
+			$opts[CURLOPT_HTTPHEADER] = array('Expect:');
+		}
+		
+		curl_setopt_array($ch, $opts);
+		$result = curl_exec($ch);
+		
+		if (curl_errno($ch) == 60) { // CURLE_SSL_CACERT
+			error_log('Invalid or no certificate authority found, using bundled information');
+			curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__) . '/fb_ca_chain_bundle.crt');
+			$result = curl_exec($ch);
+		}
+		
+		if ($result === FALSE) {
+			$e = new OAuth2Exception(array('code' => curl_errno($ch), 'message' => curl_error($ch)));
+			curl_close($ch);
+			throw $e;
+		}
+		curl_close($ch);
+		
+		// Split the HTTP response into header and body.
+		list($headers, $body) = explode("\r\n\r\n", $result);
+		$headers = explode("\r\n", $headers);
+		
+		// We catch HTTP/1.1 4xx or HTTP/1.1 5xx error response.
+		if (strpos($headers[0], 'HTTP/1.1 4') !== FALSE || strpos($headers[0], 'HTTP/1.1 5') !== FALSE) {
+			$result = array('code' => 0, 'message' => '');
+			
+			if (preg_match('/^HTTP\/1.1 ([0-9]{3,3}) (.*)$/', $headers[0], $matches)) {
+				$result['code'] = $matches[1];
+				$result['message'] = $matches[2];
+			}
+			
+			// In case retrun with WWW-Authenticate replace the description.
+			foreach ( $headers as $header ) {
+				if (preg_match("/^WWW-Authenticate:.*error='(.*)'/", $header, $matches)) {
+					$result['error'] = $matches[1];
+				}
+			}
+			
+			return json_encode($result);
+		}
+		
+		return $body;
+	}
+
+	/**
+	 * The name of the cookie that contains the session object.
+	 *
+	 * @return
+	 * The cookie name.
+	 */
+	private function getSessionCookieName() {
+		return 'oauth2_' . $this->getVariable('client_id');
+	}
+
+	/**
+	 * Set a JS Cookie based on the _passed in_ session.
+	 *
+	 * It does not use the currently stored session - you need to explicitly
+	 * pass it in.
+	 *
+	 * @param $session
+	 * The session to use for setting the cookie.
+	 */
+	protected function setCookieFromSession($session = NULL) {
+		if (!$this->getVariable('cookie_support'))
+			return;
+		
+		$cookie_name = $this->getSessionCookieName();
+		$value = 'deleted';
+		$expires = time() - 3600;
+		$base_domain = $this->getVariable('base_domain', self::DEFAULT_BASE_DOMAIN);
+		if ($session) {
+			$value = '"' . http_build_query($session, NULL, '&') . '"';
+			$base_domain = isset($session['base_domain']) ? $session['base_domain'] : $base_domain;
+			$expires = isset($session['expires']) ? $session['expires'] : time() + $this->getVariable('expires_in', self::DEFAULT_EXPIRES_IN);
+		}
+		
+		// Prepend dot if a domain is found.
+		if ($base_domain)
+			$base_domain = '.' . $base_domain;
+		
+		// If an existing cookie is not set, we dont need to delete it.
+		if ($value == 'deleted' && empty($_COOKIE[$cookie_name]))
+			return;
+		
+		if (headers_sent())
+			error_log('Could not set cookie. Headers already sent.');
+		else
+			setcookie($cookie_name, $value, $expires, '/', $base_domain);
+	}
+
+	/**
+	 * Validates a session_version = 3 style session object.
+	 *
+	 * @param $session
+	 * The session object.
+	 *
+	 * @return
+	 * The session object if it validates, NULL otherwise.
+	 */
+	protected function validateSessionObject($session) {
+		// Make sure some essential fields exist.
+		if (is_array($session) && isset($session['access_token']) && isset($session['sig'])) {
+			// Validate the signature.
+			$session_without_sig = $session;
+			unset($session_without_sig['sig']);
+			
+			$expected_sig = self::generateSignature($session_without_sig, $this->getVariable('client_secret'));
+			
+			if ($session['sig'] != $expected_sig) {
+				error_log('Got invalid session signature in cookie.');
+				$session = NULL;
+			}
+		} else {
+			$session = NULL;
+		}
+		return $session;
+	}
+
+	/**
+	 * Since $_SERVER['REQUEST_URI'] is only available on Apache, we
+	 * generate an equivalent using other environment variables.
+	 */
+	function getRequestUri() {
+		if (isset($_SERVER['REQUEST_URI'])) {
+			$uri = $_SERVER['REQUEST_URI'];
+		} else {
+			if (isset($_SERVER['argv'])) {
+				$uri = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['argv'][0];
+			} elseif (isset($_SERVER['QUERY_STRING'])) {
+				$uri = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING'];
+			} else {
+				$uri = $_SERVER['SCRIPT_NAME'];
+			}
+		}
+		// Prevent multiple slashes to avoid cross site requests via the Form API.
+		$uri = '/' . ltrim($uri, '/');
+		
+		return $uri;
+	}
+
+	/**
+	 * Returns the Current URL.
+	 *
+	 * @return
+	 * The current URL.
+	 */
+	protected function getCurrentUri() {
+		$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 'https://' : 'http://';
+		$current_uri = $protocol . $_SERVER['HTTP_HOST'] . $this->getRequestUri();
+		$parts = parse_url($current_uri);
+		
+		$query = '';
+		if (!empty($parts['query'])) {
+			$params = array();
+			parse_str($parts['query'], $params);
+			$params = array_filter($params);
+			if (!empty($params)) {
+				$query = '?' . http_build_query($params, NULL, '&');
+			}
+		}
+		
+		// Use port if non default.
+		$port = '';
+		if (isset($parts['port']) && (($protocol === 'http://' && $parts['port'] !== 80) || ($protocol === 'https://' && $parts['port'] !== 443))) {
+			$port = ':' . $parts['port'];
+		}
+		
+		
+		// Rebuild.
+		return $protocol . $parts['host'] . $port . $parts['path'] . $query;
+	}
+
+	/**
+	 * Build the URL for given path and parameters.
+	 *
+	 * @param $path
+	 * (optional) The path.
+	 * @param $params
+	 * (optional) The query parameters in associative array.
+	 *
+	 * @return
+	 * The URL for the given parameters.
+	 */
+	protected function getUri($path = '', $params = array()) {
+		$url = $this->getVariable('services_uri') ? $this->getVariable('services_uri') : $this->getVariable('base_uri');
+		
+		if (!empty($path))
+			if (substr($path, 0, 4) == "http")
+				$url = $path;
+			else
+				$url = rtrim($url, '/') . '/' . ltrim($path, '/');
+		
+		if (!empty($params))
+			$url .= '?' . http_build_query($params, NULL, '&');
+		
+		return $url;
+	}
+
+	/**
+	 * Generate a signature for the given params and secret.
+	 *
+	 * @param $params
+	 * The parameters to sign.
+	 * @param $secret
+	 * The secret to sign with.
+	 *
+	 * @return
+	 * The generated signature
+	 */
+	protected function generateSignature($params, $secret) {
+		// Work with sorted data.
+		ksort($params);
+		
+		// Generate the base string.
+		$base_string = '';
+		foreach ( $params as $key => $value ) {
+			$base_string .= $key . '=' . $value;
+		}
+		$base_string .= $secret;
+		
+		return md5($base_string);
+	}
+}
Index: trunk/prototype/library/oauth2/lib/OAuth2Exception.php
===================================================================
--- trunk/prototype/library/oauth2/lib/OAuth2Exception.php	(revision 6528)
+++ trunk/prototype/library/oauth2/lib/OAuth2Exception.php	(revision 6528)
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * OAuth2.0 draft v10 exception handling.
+ *
+ * @author Originally written by Naitik Shah <naitik@facebook.com>.
+ * @author Update to draft v10 by Edison Wong <hswong3i@pantarei-design.com>.
+ *
+ * @sa <a href="https://github.com/facebook/php-sdk">Facebook PHP SDK</a>.
+ */
+class OAuth2Exception extends Exception {
+	
+	/**
+	 * The result from the API server that represents the exception information.
+	 */
+	protected $result;
+
+	/**
+	 * Make a new API Exception with the given result.
+	 *
+	 * @param $result
+	 * The result from the API server.
+	 */
+	public function __construct($result) {
+		$this->result = $result;
+		
+		$code = isset($result['code']) ? $result['code'] : 0;
+		
+		if (isset($result['error'])) {
+			// OAuth 2.0 Draft 10 style
+			$message = $result['error'];
+		} elseif (isset($result['message'])) {
+			// cURL style
+			$message = $result['message'];
+		} else {
+			$message = 'Unknown Error. Check getResult()';
+		}
+		
+		parent::__construct($message, $code);
+	}
+
+	/**
+	 * Return the associated result object returned by the API server.
+	 *
+	 * @returns
+	 * The result from the API server.
+	 */
+	public function getResult() {
+		return $this->result;
+	}
+
+	/**
+	 * Returns the associated type for the error. This will default to
+	 * 'Exception' when a type is not available.
+	 *
+	 * @return
+	 * The type for the error.
+	 */
+	public function getType() {
+		if (isset($this->result['error'])) {
+			$message = $this->result['error'];
+			if (is_string($message)) {
+				// OAuth 2.0 Draft 10 style
+				return $message;
+			}
+		}
+		return 'Exception';
+	}
+
+	/**
+	 * To make debugging easier.
+	 *
+	 * @returns
+	 * The string representation of the error.
+	 */
+	public function __toString() {
+		$str = $this->getType() . ': ';
+		if ($this->code != 0) {
+			$str .= $this->code . ': ';
+		}
+		return $str . $this->message;
+	}
+}
Index: trunk/prototype/library/oauth2/lib/IOAuth2GrantCode.php
===================================================================
--- trunk/prototype/library/oauth2/lib/IOAuth2GrantCode.php	(revision 6528)
+++ trunk/prototype/library/oauth2/lib/IOAuth2GrantCode.php	(revision 6528)
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * Storage engines that support the "Authorization Code"
+ * grant type should implement this interface
+ * 
+ * @author Dave Rochwerger <catch.dave@gmail.com>
+ * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1
+ */
+interface IOAuth2GrantCode extends IOAuth2Storage {
+	
+	/**
+	 * The Authorization Code grant type supports a response type of "code". 
+	 * 
+	 * @var string
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-1.4.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2
+	 */
+	const RESPONSE_TYPE_CODE = OAuth2::RESPONSE_TYPE_AUTH_CODE;
+
+	/**
+	 * Fetch authorization code data (probably the most common grant type).
+	 *
+	 * Retrieve the stored data for the given authorization code.
+	 *
+	 * Required for OAuth2::GRANT_TYPE_AUTH_CODE.
+	 *
+	 * @param $code
+	 * Authorization code to be check with.
+	 *
+	 * @return
+	 * An associative array as below, and NULL if the code is invalid:
+	 * - client_id: Stored client identifier.
+	 * - redirect_uri: Stored redirect URI.
+	 * - expires: Stored expiration in unix timestamp.
+	 * - scope: (optional) Stored scope values in space-separated string.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	public function getAuthCode($code);
+
+	/**
+	 * Take the provided authorization code values and store them somewhere.
+	 *
+	 * This function should be the storage counterpart to getAuthCode().
+	 *
+	 * If storage fails for some reason, we're not currently checking for
+	 * any sort of success/failure, so you should bail out of the script
+	 * and provide a descriptive fail message.
+	 *
+	 * Required for OAuth2::GRANT_TYPE_AUTH_CODE.
+	 *
+	 * @param $code
+	 * Authorization code to be stored.
+	 * @param $client_id
+	 * Client identifier to be stored.
+	 * @param $user_id
+	 * User identifier to be stored.
+	 * @param $redirect_uri
+	 * Redirect URI to be stored.
+	 * @param $expires
+	 * Expiration to be stored.
+	 * @param $scope
+	 * (optional) Scopes to be stored in space-separated string.
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	public function setAuthCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = NULL);
+
+}
Index: trunk/prototype/library/oauth2/lib/IOAuth2GrantImplicit.php
===================================================================
--- trunk/prototype/library/oauth2/lib/IOAuth2GrantImplicit.php	(revision 6528)
+++ trunk/prototype/library/oauth2/lib/IOAuth2GrantImplicit.php	(revision 6528)
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Storage engines that support the "Implicit"
+ * grant type should implement this interface
+ * 
+ * @author Dave Rochwerger <catch.dave@gmail.com>
+ * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2
+ */
+interface IOAuth2GrantImplicit extends IOAuth2Storage {
+	
+	/**
+	 * The Implicit grant type supports a response type of "token". 
+	 * 
+	 * @var string
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-1.4.2
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2
+	 */
+	const RESPONSE_TYPE_TOKEN = OAuth2::RESPONSE_TYPE_ACCESS_TOKEN;
+}
Index: trunk/prototype/library/oauth2/lib/OAuth2.php
===================================================================
--- trunk/prototype/library/oauth2/lib/OAuth2.php	(revision 6528)
+++ trunk/prototype/library/oauth2/lib/OAuth2.php	(revision 6528)
@@ -0,0 +1,1367 @@
+<?php
+require_once ROOTPATH . '/library/oauth2/lib/OAuth2ServerException.php';
+require_once ROOTPATH . '/library/oauth2/lib/OAuth2AuthenticateException.php';
+require_once ROOTPATH . '/library/oauth2/lib/OAuth2RedirectException.php';
+
+/**
+ * @mainpage
+ * OAuth 2.0 server in PHP, originally written for
+ * <a href="http://www.opendining.net/"> Open Dining</a>. Supports
+ * <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-20">IETF draft v20</a>.
+ *
+ * Source repo has sample servers implementations for
+ * <a href="http://php.net/manual/en/book.pdo.php"> PHP Data Objects</a> and
+ * <a href="http://www.mongodb.org/">MongoDB</a>. Easily adaptable to other
+ * storage engines.
+ *
+ * PHP Data Objects supports a variety of databases, including MySQL,
+ * Microsoft SQL Server, SQLite, and Oracle, so you can try out the sample
+ * to see how it all works.
+ *
+ * We're expanding the wiki to include more helpful documentation, but for
+ * now, your best bet is to view the oauth.php source - it has lots of
+ * comments.
+ *
+ * @author Tim Ridgely <tim.ridgely@gmail.com>
+ * @author Aaron Parecki <aaron@parecki.com>
+ * @author Edison Wong <hswong3i@pantarei-design.com>
+ * @author David Rochwerger <catch.dave@gmail.com>
+ *
+ * @see http://code.google.com/p/oauth2-php/
+ * @see https://github.com/quizlet/oauth2-php
+ */
+
+/**
+ * OAuth2.0 draft v20 server-side implementation.
+ * 
+ * @todo Add support for Message Authentication Code (MAC) token type.
+ *
+ * @author Originally written by Tim Ridgely <tim.ridgely@gmail.com>.
+ * @author Updated to draft v10 by Aaron Parecki <aaron@parecki.com>.
+ * @author Debug, coding style clean up and documented by Edison Wong <hswong3i@pantarei-design.com>.
+ * @author Refactored (including separating from raw POST/GET) and updated to draft v20 by David Rochwerger <catch.dave@gmail.com>.
+ */
+class OAuth2 {
+	
+	/**
+	 * Array of persistent variables stored.
+	 */
+	protected $conf = array();
+	
+	/**
+	 * Storage engine for authentication server
+	 * 
+	 * @var IOAuth2Storage
+	 */
+	protected $storage;
+	
+	/**
+	 * Keep track of the old refresh token. So we can unset
+	 * the old refresh tokens when a new one is issued.
+	 * 
+	 * @var string
+	 */
+	protected $oldRefreshToken;
+	
+	/**
+	 * Default values for configuration options.
+	 * 
+	 * @var int
+	 * @see OAuth2::setDefaultOptions()
+	 */
+	const DEFAULT_ACCESS_TOKEN_LIFETIME = 3600;
+	const DEFAULT_REFRESH_TOKEN_LIFETIME = 1209600;
+	const DEFAULT_AUTH_CODE_LIFETIME = 3600;
+	const DEFAULT_WWW_REALM = 'Service';
+	
+	/**
+	 * Configurable options.
+	 * 
+	 * @var string
+	 */
+	const CONFIG_ACCESS_LIFETIME = 3600; // The lifetime of access token in seconds.
+	const CONFIG_REFRESH_LIFETIME = 3600; // The lifetime of refresh token in seconds.
+	const CONFIG_AUTH_LIFETIME = 3600; // The lifetime of auth code in seconds.
+	const CONFIG_SUPPORTED_SCOPES = 'supported_scopes'; // Array of scopes you want to support
+	const CONFIG_TOKEN_TYPE = 'token_type'; // Token type to respond with. Currently only "Bearer" supported.
+	const CONFIG_WWW_REALM = 'realm';
+	const CONFIG_ENFORCE_INPUT_REDIRECT = 'enforce_redirect'; // Set to true to enforce redirect_uri on input for both authorize and token steps.
+	const CONFIG_ENFORCE_STATE = 'enforce_state'; // Set to true to enforce state to be passed in authorization (see http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-10.12)
+	
+
+	/**
+	 * Regex to filter out the client identifier (described in Section 2 of IETF draft).
+	 *
+	 * IETF draft does not prescribe a format for these, however I've arbitrarily
+	 * chosen alphanumeric strings with hyphens and underscores, 3-32 characters
+	 * long.
+	 *
+	 * Feel free to change.
+	 */
+	const CLIENT_ID_REGEXP = '/^[a-z0-9-_]{3,32}$/i';
+	
+	/**
+	 * @defgroup oauth2_section_5 Accessing a Protected Resource
+	 * @{
+	 *
+	 * Clients access protected resources by presenting an access token to
+	 * the resource server. Access tokens act as bearer tokens, where the
+	 * token string acts as a shared symmetric secret. This requires
+	 * treating the access token with the same care as other secrets (e.g.
+	 * end-user passwords). Access tokens SHOULD NOT be sent in the clear
+	 * over an insecure channel.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-7
+	 */
+	
+	/**
+	 * Used to define the name of the OAuth access token parameter
+	 * (POST & GET). This is for the "bearer" token type.
+	 * Other token types may use different methods and names.
+	 *
+	 * IETF Draft section 2 specifies that it should be called "access_token"
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-06#section-2.2
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-06#section-2.3
+	 */
+	const TOKEN_PARAM_NAME = 'access_token';
+	
+	/**
+	 * When using the bearer token type, there is a specifc Authorization header
+	 * required: "Bearer"
+	 * 
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-04#section-2.1
+	 */
+	const TOKEN_BEARER_HEADER_NAME = 'Bearer';
+	
+	/**
+	 * @}
+	 */
+	
+	/**
+	 * @defgroup oauth2_section_4 Obtaining Authorization
+	 * @{
+	 *
+	 * When the client interacts with an end-user, the end-user MUST first
+	 * grant the client authorization to access its protected resources.
+	 * Once obtained, the end-user authorization grant is expressed as an
+	 * authorization code which the client uses to obtain an access token.
+	 * To obtain an end-user authorization, the client sends the end-user to
+	 * the end-user authorization endpoint.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4
+	 */
+	
+	/**
+	 * List of possible authentication response types.
+	 * The "authorization_code" mechanism exclusively supports 'code'
+	 * and the "implicit" mechanism exclusively supports 'token'.
+	 * 
+	 * @var string
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.1
+	 */
+	const RESPONSE_TYPE_AUTH_CODE = 'code';
+	const RESPONSE_TYPE_ACCESS_TOKEN = 'token';
+	
+	/**
+	 * @}
+	 */
+	
+	/**
+	 * @defgroup oauth2_section_5 Obtaining an Access Token
+	 * @{
+	 *
+	 * The client obtains an access token by authenticating with the
+	 * authorization server and presenting its authorization grant (in the form of
+	 * an authorization code, resource owner credentials, an assertion, or a
+	 * refresh token).
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4
+	 */
+	
+	/**
+	 * Grant types support by draft 20
+	 */
+	const GRANT_TYPE_AUTH_CODE = 'authorization_code';
+	const GRANT_TYPE_IMPLICIT = 'token';
+	const GRANT_TYPE_USER_CREDENTIALS = 'password';
+	const GRANT_TYPE_CLIENT_CREDENTIALS = 'client_credentials';
+	const GRANT_TYPE_REFRESH_TOKEN = 'refresh_token';
+	const GRANT_TYPE_EXTENSIONS = 'extensions';
+	
+	/**
+	 * Regex to filter out the grant type.
+	 * NB: For extensibility, the grant type can be a URI
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.5
+	 */
+	const GRANT_TYPE_REGEXP = '#^(authorization_code|token|password|client_credentials|refresh_token|http://.*)$#';
+	
+	/**
+	 * @}
+	 */
+	
+	/**
+	 * Possible token types as defined by draft 20.
+	 * 
+	 * TODO: Add support for mac (and maybe other types?)
+	 * 
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-7.1 
+	 */
+	const TOKEN_TYPE_BEARER = 'bearer';
+	const TOKEN_TYPE_MAC = 'mac'; // Currently unsupported
+	
+
+	/**
+	 * @defgroup self::HTTP_status HTTP status code
+	 * @{
+	 */
+	
+	/**
+	 * HTTP status codes for successful and error states as specified by draft 20.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2
+	 */
+	const HTTP_FOUND = '302 Found';
+	const HTTP_BAD_REQUEST = '400 Bad Request';
+	const HTTP_UNAUTHORIZED = '401 Unauthorized';
+	const HTTP_FORBIDDEN = '403 Forbidden';
+	const HTTP_UNAVAILABLE = '503 Service Unavailable';
+	
+	/**
+	 * @}
+	 */
+	
+	/**
+	 * @defgroup oauth2_error Error handling
+	 * @{
+	 *
+	 * @todo Extend for i18n.
+	 * @todo Consider moving all error related functionality into a separate class.
+	 */
+	
+	/**
+	 * The request is missing a required parameter, includes an unsupported
+	 * parameter or parameter value, or is otherwise malformed.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.2.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2
+	 */
+	const ERROR_INVALID_REQUEST = 'invalid_request';
+	
+	/**
+	 * The client identifier provided is invalid.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2
+	 */
+	const ERROR_INVALID_CLIENT = 'invalid_client';
+	
+	/**
+	 * The client is not authorized to use the requested response type.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.2.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2
+	 */
+	const ERROR_UNAUTHORIZED_CLIENT = 'unauthorized_client';
+	
+	/**
+	 * The redirection URI provided does not match a pre-registered value.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-3.1.2.4
+	 */
+	const ERROR_REDIRECT_URI_MISMATCH = 'redirect_uri_mismatch';
+	
+	/**
+	 * The end-user or authorization server denied the request.
+	 * This could be returned, for example, if the resource owner decides to reject
+	 * access to the client at a later point.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.2.1
+	 */
+	const ERROR_USER_DENIED = 'access_denied';
+	
+	/**
+	 * The requested response type is not supported by the authorization server.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.2.1
+	 */
+	const ERROR_UNSUPPORTED_RESPONSE_TYPE = 'unsupported_response_type';
+	
+	/**
+	 * The requested scope is invalid, unknown, or malformed.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.2.1
+	 */
+	const ERROR_INVALID_SCOPE = 'invalid_scope';
+	
+	/**
+	 * The provided authorization grant is invalid, expired,
+	 * revoked, does not match the redirection URI used in the
+	 * authorization request, or was issued to another client.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2
+	 */
+	const ERROR_INVALID_GRANT = 'invalid_grant';
+	
+	/**
+	 * The authorization grant is not supported by the authorization server.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2
+	 */
+	const ERROR_UNSUPPORTED_GRANT_TYPE = 'unsupported_grant_type';
+	
+	/**
+	 * The request requires higher privileges than provided by the access token.
+	 * The resource server SHOULD respond with the HTTP 403 (Forbidden) status
+	 * code and MAY include the "scope" attribute with the scope necessary to
+	 * access the protected resource.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.2.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2
+	 */
+	const ERROR_INSUFFICIENT_SCOPE = 'invalid_scope';
+
+	/**
+	 * @}
+	 */
+	
+	/**
+	 * Creates an OAuth2.0 server-side instance.
+	 *
+	 * @param $config - An associative array as below of config options. See CONFIG_* constants.
+	 */
+	public function __construct(IOAuth2Storage $storage, $config = array()) {
+		$this->storage = $storage;
+		
+		// Configuration options
+		$this->setDefaultOptions();
+		foreach ( $config as $name => $value ) {
+			$this->setVariable($name, $value);
+		}
+	}
+
+	/**
+	 * Default configuration options are specified here.
+	 */
+	protected function setDefaultOptions() {
+		$this->conf = array(
+			self::CONFIG_ACCESS_LIFETIME => self::DEFAULT_ACCESS_TOKEN_LIFETIME,
+			self::CONFIG_REFRESH_LIFETIME => self::DEFAULT_REFRESH_TOKEN_LIFETIME,
+			self::CONFIG_AUTH_LIFETIME => self::DEFAULT_AUTH_CODE_LIFETIME,
+			self::CONFIG_WWW_REALM => self::DEFAULT_WWW_REALM,
+			self::CONFIG_TOKEN_TYPE => self::TOKEN_TYPE_BEARER,
+			self::CONFIG_ENFORCE_INPUT_REDIRECT => FALSE,
+			self::CONFIG_ENFORCE_STATE => FALSE,
+			self::CONFIG_SUPPORTED_SCOPES => array() // This is expected to be passed in on construction. Scopes can be an aribitrary string.
+		);  
+	}
+
+	/**
+	 * Returns a persistent variable.
+	 *
+	 * @param $name
+	 * The name of the variable to return.
+	 * @param $default
+	 * The default value to use if this variable has never been set.
+	 *
+	 * @return
+	 * The value of the variable.
+	 */
+	public function getVariable($name, $default = NULL) {
+		$name = strtolower($name);
+		
+		return isset($this->conf[$name]) ? $this->conf[$name] : $default;
+	}
+
+	/**
+	 * Sets a persistent variable.
+	 *
+	 * @param $name
+	 * The name of the variable to set.
+	 * @param $value
+	 * The value to set.
+	 */
+	public function setVariable($name, $value) {
+		$name = strtolower($name);
+		
+		$this->conf[$name] = $value;
+		return $this;
+	}
+
+	// Resource protecting (Section 5).
+	
+
+	/**
+	 * Check that a valid access token has been provided.
+	 * The token is returned (as an associative array) if valid.
+	 *
+	 * The scope parameter defines any required scope that the token must have.
+	 * If a scope param is provided and the token does not have the required
+	 * scope, we bounce the request.
+	 *
+	 * Some implementations may choose to return a subset of the protected
+	 * resource (i.e. "public" data) if the user has not provided an access
+	 * token or if the access token is invalid or expired.
+	 *
+	 * The IETF spec says that we should send a 401 Unauthorized header and
+	 * bail immediately so that's what the defaults are set to. You can catch
+	 * the exception thrown and behave differently if you like (log errors, allow
+	 * public access for missing tokens, etc)
+	 *
+	 * @param $scope
+	 * A space-separated string of required scope(s), if you want to check
+	 * for scope.
+	 * @return array
+	 * Token
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-7
+	 *
+	 * @ingroup oauth2_section_7
+	 */
+	public function verifyAccessToken($token_param, $scope = NULL) {
+		$tokenType = $this->getVariable(self::CONFIG_TOKEN_TYPE);
+		$realm = $this->getVariable(self::CONFIG_WWW_REALM);
+		
+		if (!$token_param) { // Access token was not provided
+			throw new OAuth2AuthenticateException(self::HTTP_BAD_REQUEST, $tokenType, $realm, self::ERROR_INVALID_REQUEST, 'The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.', $scope);
+		}
+		
+		// Get the stored token data (from the implementing subclass)
+		$token = $this->storage->getAccessToken($token_param);
+		if ($token === NULL) {
+			throw new OAuth2AuthenticateException(self::HTTP_UNAUTHORIZED, $tokenType, $realm, self::ERROR_INVALID_GRANT, 'The access token provided is invalid.', $scope);
+		}
+		
+		// Check we have a well formed token
+		if (!isset($token["expires"]) || !isset($token["client_id"])) {
+			throw new OAuth2AuthenticateException(self::HTTP_UNAUTHORIZED, $tokenType, $realm, self::ERROR_INVALID_GRANT, 'Malformed token (missing "expires" or "client_id")', $scope);
+		}
+		
+		// Check token expiration (expires is a mandatory paramter)
+		if (isset($token["expires"]) && time() > $token["expires"]) {
+			throw new OAuth2AuthenticateException(self::HTTP_UNAUTHORIZED, $tokenType, $realm, self::ERROR_INVALID_GRANT, 'The access token provided has expired.', $scope);
+		}
+		
+		// Check scope, if provided
+		// If token doesn't have a scope, it's NULL/empty, or it's insufficient, then throw an error
+		if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->checkScope($scope, $token["scope"]))) {
+			throw new OAuth2AuthenticateException(self::HTTP_FORBIDDEN, $tokenType, $realm, self::ERROR_INSUFFICIENT_SCOPE, 'The request requires higher privileges than provided by the access token.', $scope);
+		}
+		
+		
+		
+		session_write_close(); //Fecha session anterior
+		session_id($token['refresh_token']); //Define o id da session o mesmo do resfresh token
+		@session_start();
+		
+		/* TODO - Verificar melhor solução para verificação de tratamento para sessions já startadas
+		if(!isset($_SESSION))
+		    session_start();
+		*/
+		return $token;
+	}
+
+	/**
+	 * This is a convenience function that can be used to get the token, which can then
+	 * be passed to verifyAccessToken(). The constraints specified by the draft are
+	 * attempted to be adheared to in this method.
+	 * 
+	 * As per the Bearer spec (draft 8, section 2) - there are three ways for a client
+	 * to specify the bearer token, in order of preference: Authorization Header,
+	 * POST and GET.
+	 * 
+	 * NB: Resource servers MUST accept tokens via the Authorization scheme
+	 * (http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08#section-2).
+	 * 
+	 * @todo Should we enforce TLS/SSL in this function?
+	 * 
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08#section-2.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08#section-2.2
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08#section-2.3
+	 * 
+	 * Old Android version bug (at least with version 2.2)
+	 * @see http://code.google.com/p/android/issues/detail?id=6684
+	 * 
+	 * We don't want to test this functionality as it relies on superglobals and headers:
+	 * @codeCoverageIgnoreStart
+	 */
+	public function getBearerToken() {
+	    
+		if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
+			$headers = trim($_SERVER["HTTP_AUTHORIZATION"]);
+		} elseif (function_exists('apache_request_headers')) {
+			$requestHeaders = apache_request_headers();
+			
+			// Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization)
+			$requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders));
+			
+			if (isset($requestHeaders['Authorization'])) {
+				$headers = trim($requestHeaders['Authorization']);
+			}
+		}
+		
+		$tokenType = $this->getVariable(self::CONFIG_TOKEN_TYPE);
+		$realm = $this->getVariable(self::CONFIG_WWW_REALM);
+		// Check that exactly one method was used
+		$methodsUsed = !empty($headers) + isset($_GET[self::TOKEN_PARAM_NAME]) + isset($_POST[self::TOKEN_PARAM_NAME]);
+		if ($methodsUsed > 1) { 
+			throw new OAuth2AuthenticateException(self::HTTP_BAD_REQUEST, $tokenType, $realm, self::ERROR_INVALID_REQUEST, 'Only one method may be used to authenticate at a time (Auth header, GET or POST).');
+		} elseif ($methodsUsed == 0) {
+			throw new OAuth2AuthenticateException(self::HTTP_BAD_REQUEST, $tokenType, $realm, self::ERROR_INVALID_REQUEST, 'The access token was not found.');
+		}
+		
+		// HEADER: Get the access token from the header
+		if (!empty($headers)) {
+			if (!preg_match('/' . self::TOKEN_BEARER_HEADER_NAME . '\s(\S+)/', $headers, $matches)) {
+				throw new OAuth2AuthenticateException(self::HTTP_BAD_REQUEST, $tokenType, $realm, self::ERROR_INVALID_REQUEST, 'Malformed auth header');
+			}
+			
+			return $matches[1];
+		}		
+		// POST: Get the token from POST data
+		if (isset($_POST[self::TOKEN_PARAM_NAME])) {
+			if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+				throw new OAuth2AuthenticateException(self::HTTP_BAD_REQUEST, $tokenType, $realm, self::ERROR_INVALID_REQUEST, 'When putting the token in the body, the method must be POST.');
+			}
+			
+			// IETF specifies content-type. NB: Not all webservers populate this _SERVER variable
+			if (isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] != 'application/x-www-form-urlencoded') {
+				throw new OAuth2AuthenticateException(self::HTTP_BAD_REQUEST, $tokenType, $realm, self::ERROR_INVALID_REQUEST, 'The content type for POST requests must be "application/x-www-form-urlencoded"');
+			}
+			
+			return $_POST[self::TOKEN_PARAM_NAME];
+		}
+		
+		// GET method
+		return $_GET[self::TOKEN_PARAM_NAME];
+	}
+
+	/** @codeCoverageIgnoreEnd */
+	
+	/**
+	 * Check if everything in required scope is contained in available scope.
+	 *
+	 * @param $required_scope
+	 * Required scope to be check with.
+	 *
+	 * @return
+	 * TRUE if everything in required scope is contained in available scope,
+	 * and False if it isn't.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-7
+	 *
+	 * @ingroup oauth2_section_7
+	 */
+	private function checkScope($required_scope, $available_scope) {
+		// The required scope should match or be a subset of the available scope
+		if (!is_array($required_scope)) {
+			$required_scope = explode(' ', trim($required_scope));
+		}
+		
+		if (!is_array($available_scope)) {
+			$available_scope = explode(' ', trim($available_scope));
+		}
+		
+		return (count(array_diff($required_scope, $available_scope)) == 0);
+	}
+
+	// Access token granting (Section 4).
+	
+
+	/**
+	 * Grant or deny a requested access token.
+	 * This would be called from the "/token" endpoint as defined in the spec.
+	 * Obviously, you can call your endpoint whatever you want.
+	 * 
+	 * @param $inputData - The draft specifies that the parameters should be
+	 * retrieved from POST, but you can override to whatever method you like.
+	 * @throws OAuth2ServerException
+	 * 
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-10.6
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-4.1.3
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+//ORIGINAL URI	
+//	public function grantAccessToken(array $inputData = NULL, array $authHeaders = NULL) {
+//		$filters = array(
+//			"grant_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => self::GRANT_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR),
+//			"scope" => array("flags" => FILTER_REQUIRE_SCALAR),
+//			"code" => array("flags" => FILTER_REQUIRE_SCALAR),
+//			"redirect_uri" => array("filter" => FILTER_SANITIZE_URL),
+//			"username" => array("flags" => FILTER_REQUIRE_SCALAR),
+//			"password" => array("flags" => FILTER_REQUIRE_SCALAR),
+//			"refresh_token" => array("flags" => FILTER_REQUIRE_SCALAR),
+//		);
+//		
+//		// Input data by default can be either POST or GET
+//		if (!isset($inputData)) {
+//			$inputData = ($_SERVER['REQUEST_METHOD'] == 'POST') ? $_POST : $_GET;
+//		}
+//		
+//		// Basic authorization header
+//		$authHeaders = isset($authHeaders) ? $authHeaders : $this->getAuthorizationHeader();
+//		
+//                
+//                
+//		// Filter input data
+//		$input = filter_var_array($inputData, $filters);
+//		
+//              
+//                
+//		// Grant Type must be specified.
+//		if (!$input["grant_type"]) {
+//			throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing');
+//		}
+//		
+//		// Authorize the client
+//		$client = $this->getClientCredentials($inputData, $authHeaders);
+//          
+//		if ($this->storage->checkClientCredentials($client[0], $client[1]) === FALSE) {
+//			throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, 'The client credentials are invalid');
+//		}
+//		
+//		if (!$this->storage->checkRestrictedGrantType($client[0], $input["grant_type"])) {
+//			throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNAUTHORIZED_CLIENT, 'The grant type is unauthorized for this client_id');
+//		}
+//		
+//		// Do the granting
+//		//
+//		switch ($input["grant_type"]) {
+//			case self::GRANT_TYPE_AUTH_CODE:
+//				if (!($this->storage instanceof IOAuth2GrantCode)) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE);
+//				}
+//				
+//				if (!$input["code"]) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Missing parameter. "code" is required');
+//				}
+//				
+//				if ($this->getVariable(self::CONFIG_ENFORCE_INPUT_REDIRECT) && !$input["redirect_uri"]) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, "The redirect URI parameter is required.");
+//				}
+//				
+//				$stored = $this->storage->getAuthCode($input["code"]);
+//				
+//				// Check the code exists
+//				if ($stored === NULL || $client[0] != $stored["client_id"]) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, "Refresh token doesn't exist or is invalid for the client");
+//				}
+//				
+//				// Validate the redirect URI. If a redirect URI has been provided on input, it must be validated
+//				if ($input["redirect_uri"] && !$this->validateRedirectUri($input["redirect_uri"], $stored["redirect_uri"])) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_REDIRECT_URI_MISMATCH, "The redirect URI is missing or do not match");
+//				}
+//				
+//				if ($stored["expires"] < time()) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, "The authorization code has expired");
+//				}
+//				break;
+//			
+//			case self::GRANT_TYPE_USER_CREDENTIALS:
+//				if (!($this->storage instanceof IOAuth2GrantUser)) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE);
+//				}
+//				
+//				if (!$input["username"] || !$input["password"]) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Missing parameters. "username" and "password" required');
+//				}
+//				
+//				$stored = $this->storage->checkUserCredentials($client[0], $input["username"], $input["password"]);
+//
+//				if ($stored === FALSE) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT);
+//				}
+//				break;
+//			
+//			case self::GRANT_TYPE_CLIENT_CREDENTIALS:
+//				if (!($this->storage instanceof IOAuth2GrantClient)) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE);
+//				}
+//				
+//				if (empty($client[1])) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, 'The client_secret is mandatory for the "client_credentials" grant type');
+//				}
+//				// NB: We don't need to check for $stored==false, because it was checked above already
+//				$stored = $this->storage->checkClientCredentialsGrant($client[0], $client[1]);
+//				break;
+//			
+//			case self::GRANT_TYPE_REFRESH_TOKEN:
+//				if (!($this->storage instanceof IOAuth2RefreshTokens)) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE);
+//				}
+//				
+//				if (!$input["refresh_token"]) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'No "refresh_token" parameter found');
+//				}
+//				
+//				$stored = $this->storage->getRefreshToken($input["refresh_token"]);
+//				
+//				if ($stored === NULL || $client[0] != $stored["client_id"]) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, 'Invalid refresh token');
+//				}
+//				
+//				if ($stored["expires"] < time()) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, 'Refresh token has expired');
+//				}
+//				
+//				// store the refresh token locally so we can delete it when a new refresh token is generated
+//				$this->oldRefreshToken = $stored["refresh_token"];
+//				break;
+//			
+//			case self::GRANT_TYPE_IMPLICIT:
+//				/* TODO: NOT YET IMPLEMENTED */
+//				throw new OAuth2ServerException('501 Not Implemented', 'This OAuth2 library is not yet complete. This functionality is not implemented yet.');
+//				if (!($this->storage instanceof IOAuth2GrantImplicit)) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE);
+//				}
+//				
+//				break;
+//			
+//			// Extended grant types:
+//			case filter_var($input["grant_type"], FILTER_VALIDATE_URL):
+//				if (!($this->storage instanceof IOAuth2GrantExtension)) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE);
+//				}
+//				$uri = filter_var($input["grant_type"], FILTER_VALIDATE_URL);
+//				$stored = $this->storage->checkGrantExtension($uri, $inputData, $authHeaders);
+//				
+//				if ($stored === FALSE) {
+//					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT);
+//				}
+//				break;
+//			
+//			default :
+//				throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing');
+//		}
+//		
+//		if (!isset($stored["scope"])) {
+//			$stored["scope"] = NULL;
+//		}
+//		
+//		// Check scope, if provided
+//		if ($input["scope"] && (!is_array($stored) || !isset($stored["scope"]) || !$this->checkScope($input["scope"], $stored["scope"]))) {
+//			throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_SCOPE, 'An unsupported scope was requested.');
+//		}
+//		
+//		$user_id = isset($stored['user_id']) ? $stored['user_id'] : null;
+//		$token = $this->createAccessToken($client[0], $user_id, $stored['scope']);
+//		
+//	
+//		// Send response
+//		$this->sendJsonHeaders();
+//		echo json_encode($token);
+//	}
+	
+	public function grantAccessToken(array $inputData = NULL, array $authHeaders = NULL) {
+		$filters = array(
+			"grant_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => self::GRANT_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR),
+			"scope" => array("flags" => FILTER_REQUIRE_SCALAR),
+			"code" => array("flags" => FILTER_REQUIRE_SCALAR),
+			"redirect_uri" => array("filter" => FILTER_SANITIZE_URL),
+			"username" => array("flags" => FILTER_REQUIRE_SCALAR),
+			"password" => array("flags" => FILTER_REQUIRE_SCALAR),
+			"refresh_token" => array("flags" => FILTER_REQUIRE_SCALAR),
+		);
+		
+		// Input data by default can be either POST or GET
+		if (!isset($inputData)) {
+			$inputData = ($_SERVER['REQUEST_METHOD'] == 'POST') ? $_POST : $_GET;
+		}
+		
+		// Basic authorization header
+		$authHeaders = isset($authHeaders) ? $authHeaders : $this->getAuthorizationHeader();
+		
+                
+                
+		// Filter input data
+		$input = filter_var_array($inputData, $filters);
+		
+              
+                
+		// Grant Type must be specified.
+		if (!$input["grant_type"]) {
+			throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing');
+		}
+		
+		// Authorize the client
+		$client = $this->getClientCredentials($inputData, $authHeaders);
+          
+		if ($this->storage->checkClientCredentials($client[0], $client[1]) === FALSE) {
+			throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, 'The client credentials are invalid');
+		}
+		
+		if (!$this->storage->checkRestrictedGrantType($client[0], $input["grant_type"])) {
+			throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNAUTHORIZED_CLIENT, 'The grant type is unauthorized for this client_id');
+		}
+		
+		// Do the granting
+		//
+		switch ($input["grant_type"]) {
+			case self::GRANT_TYPE_AUTH_CODE:
+				if (!($this->storage instanceof IOAuth2GrantCode)) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE);
+				}
+				
+				if (!$input["code"]) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Missing parameter. "code" is required');
+				}
+				
+				if ($this->getVariable(self::CONFIG_ENFORCE_INPUT_REDIRECT) && !$input["redirect_uri"]) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, "The redirect URI parameter is required.");
+				}
+				
+				$stored = $this->storage->getAuthCode($input["code"]);
+				
+				// Check the code exists
+				if ($stored === NULL || $client[0] != $stored["client_id"]) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, "Refresh token doesn't exist or is invalid for the client");
+				}
+				
+				// Validate the redirect URI. If a redirect URI has been provided on input, it must be validated
+				if ($input["redirect_uri"] && !$this->validateRedirectUri($input["redirect_uri"], $stored["redirect_uri"])) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_REDIRECT_URI_MISMATCH, "The redirect URI is missing or do not match");
+				}
+				
+				if ($stored["expires"] < time()) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, "The authorization code has expired");
+				}
+				break;
+			
+			case self::GRANT_TYPE_USER_CREDENTIALS:
+				if (!($this->storage instanceof IOAuth2GrantUser)) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE);
+				}
+				
+				if (!$input["username"] || !$input["password"]) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Missing parameters. "username" and "password" required');
+				}
+				
+				$stored = $this->storage->checkUserCredentials($client[0], $input["username"], $input["password"]);
+
+				if ($stored === FALSE) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT);
+				}
+				break;
+			
+			case self::GRANT_TYPE_CLIENT_CREDENTIALS:
+				if (!($this->storage instanceof IOAuth2GrantClient)) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE);
+				}
+				
+				if (empty($client[1])) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, 'The client_secret is mandatory for the "client_credentials" grant type');
+				}
+				// NB: We don't need to check for $stored==false, because it was checked above already
+				$stored = $this->storage->checkClientCredentialsGrant($client[0], $client[1]);
+				break;
+			
+			case self::GRANT_TYPE_REFRESH_TOKEN:
+				if (!($this->storage instanceof IOAuth2RefreshTokens)) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE);
+				}
+				
+				if (!$input["refresh_token"]) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'No "refresh_token" parameter found');
+				}
+				
+				$stored = $this->storage->getRefreshToken($input["refresh_token"]);
+				
+				if ($stored === NULL || $client[0] != $stored["client_id"]) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, 'Invalid refresh token');
+				}
+				
+				if ($stored["expires"] < time()) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT, 'Refresh token has expired');
+				}
+				
+				// store the refresh token locally so we can delete it when a new refresh token is generated
+				$this->oldRefreshToken = $stored["refresh_token"];
+				
+				session_write_close(); //Fecha session anterior
+				session_id($stored['refresh_token']); //Define o id da session o mesmo do resfresh token
+				session_start();
+				
+				break;
+			
+			case self::GRANT_TYPE_IMPLICIT:
+				/* TODO: NOT YET IMPLEMENTED */
+				throw new OAuth2ServerException('501 Not Implemented', 'This OAuth2 library is not yet complete. This functionality is not implemented yet.');
+				if (!($this->storage instanceof IOAuth2GrantImplicit)) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE);
+				}
+				
+				break;
+			
+			// Extended grant types:
+			case filter_var($input["grant_type"], FILTER_VALIDATE_URL):
+				if (!($this->storage instanceof IOAuth2GrantExtension)) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_UNSUPPORTED_GRANT_TYPE);
+				}
+				$uri = filter_var($input["grant_type"], FILTER_VALIDATE_URL);
+				$stored = $this->storage->checkGrantExtension($uri, $inputData, $authHeaders);
+				
+				if ($stored === FALSE) {
+					throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_GRANT);
+				}
+				break;
+			
+			default :
+				throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing');
+		}
+		
+		if (!isset($stored["scope"])) {
+			$stored["scope"] = NULL;
+		}
+		
+		// Check scope, if provided
+		if ($input["scope"] && (!is_array($stored) || !isset($stored["scope"]) || !$this->checkScope($input["scope"], $stored["scope"]))) {
+			throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_SCOPE, 'An unsupported scope was requested.');
+		}
+		
+		$user_id = isset($stored['user_id']) ? $stored['user_id'] : null;
+		$token = $this->createAccessToken($client[0], $user_id, $stored['scope']);
+		
+		//Gerando Session Para o refresh_token
+		session_write_close(); //Fecha session anterior
+		session_start();
+		session_id($token['refresh_token']); //Define o id da session o mesmo do resfresh token
+		session_cache_expire(self::CONFIG_REFRESH_LIFETIME); //Define o tempo da session o mesmo do Refresh token	
+		
+		if(isset($input["username"]) && isset($input["password"]))
+		{
+		    $_SESSION['wallet']['user']['uid'] =     $input["username"];
+		    $_SESSION['wallet']['user']['password']       =  $input["password"];
+		    $_SESSION['wallet']['user']['uidNumber']       =  $user_id;
+		}
+		
+		
+		
+		//session_write_close(); //Fecha nova session
+		
+		// Send response
+		$this->sendJsonHeaders();
+		echo json_encode($token);
+	}
+
+	/**
+	 * Internal function used to get the client credentials from HTTP basic
+	 * auth or POST data.
+	 * 
+	 * According to the spec (draft 20), the client_id can be provided in
+	 * the Basic Authorization header (recommended) or via GET/POST.
+	 *
+	 * @return
+	 * A list containing the client identifier and password, for example
+	 * @code
+	 * return array(
+	 * CLIENT_ID,
+	 * CLIENT_SECRET
+	 * );
+	 * @endcode
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-2.4.1
+	 *
+	 * @ingroup oauth2_section_2
+	 */
+	protected function getClientCredentials(array $inputData, array $authHeaders) {
+		
+		// Basic Authentication is used
+		if (!empty($authHeaders['PHP_AUTH_USER'])) {
+			return array($authHeaders['PHP_AUTH_USER'], $authHeaders['PHP_AUTH_PW']);
+		} elseif (empty($inputData['client_id'])) { // No credentials were specified
+			throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, 'Client id was not found in the headers or body');
+		} else {
+			// This method is not recommended, but is supported by specification
+			return array($inputData['client_id'], $inputData['client_secret']);
+		}
+	}
+
+	// End-user/client Authorization (Section 2 of IETF Draft).
+	
+
+	/**
+	 * Pull the authorization request data out of the HTTP request.
+	 * - The redirect_uri is OPTIONAL as per draft 20. But your implementation can enforce it
+	 * by setting CONFIG_ENFORCE_INPUT_REDIRECT to true.
+	 * - The state is OPTIONAL but recommended to enforce CSRF. Draft 21 states, however, that
+	 * CSRF protection is MANDATORY. You can enforce this by setting the CONFIG_ENFORCE_STATE to true.
+	 *
+	 * @param $inputData - The draft specifies that the parameters should be
+	 * retrieved from GET, but you can override to whatever method you like.
+	 * @return
+	 * The authorization parameters so the authorization server can prompt
+	 * the user for approval if valid.
+	 *
+	 * @throws OAuth2ServerException
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-21#section-10.12
+	 * 
+	 * @ingroup oauth2_section_3
+	 */
+	public function getAuthorizeParams(array $inputData = NULL) {
+		$filters = array(
+			"client_id" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => self::CLIENT_ID_REGEXP), "flags" => FILTER_REQUIRE_SCALAR),
+			"response_type" => array("flags" => FILTER_REQUIRE_SCALAR),
+			"redirect_uri" => array("filter" => FILTER_SANITIZE_URL),
+			"state" => array("flags" => FILTER_REQUIRE_SCALAR),
+			"scope" => array("flags" => FILTER_REQUIRE_SCALAR)
+		);
+		
+		if (!isset($inputData)) {
+			$inputData = $_GET;
+		}
+		$input = filter_var_array($inputData, $filters);
+		
+		// Make sure a valid client id was supplied (we can not redirect because we were unable to verify the URI)
+		if (!$input["client_id"]) {
+			throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, "No client id supplied"); // We don't have a good URI to use
+		}
+		
+		// Get client details
+		$stored = $this->storage->getClientDetails($input["client_id"]);
+		if ($stored === FALSE) {
+			throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_INVALID_CLIENT, "Client id does not exist");
+		}
+		
+		// Make sure a valid redirect_uri was supplied. If specified, it must match the stored URI.
+		// @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-3.1.2
+		// @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1
+		// @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2.2.1
+		if (!$input["redirect_uri"] && !$stored["redirect_uri"]) {
+			throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_REDIRECT_URI_MISMATCH, 'No redirect URL was supplied or stored.');
+		}
+		if ($this->getVariable(self::CONFIG_ENFORCE_INPUT_REDIRECT) && !$input["redirect_uri"]) {
+			throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_REDIRECT_URI_MISMATCH, 'The redirect URI is mandatory and was not supplied.');
+		}
+		// Only need to validate if redirect_uri provided on input and stored.
+		if ($stored["redirect_uri"] && $input["redirect_uri"] && !$this->validateRedirectUri($input["redirect_uri"], $stored["redirect_uri"])) {
+			throw new OAuth2ServerException(self::HTTP_BAD_REQUEST, self::ERROR_REDIRECT_URI_MISMATCH, 'The redirect URI provided is missing or does not match');
+		}
+		
+		// Select the redirect URI
+		$input["redirect_uri"] = isset($input["redirect_uri"]) ? $input["redirect_uri"] : $stored["redirect_uri"];
+		
+		// type and client_id are required
+		if (!$input["response_type"]) {
+			throw new OAuth2RedirectException($input["redirect_uri"], self::ERROR_INVALID_REQUEST, 'Invalid or missing response type.', $input["state"]);
+		}
+		
+		// Check requested auth response type against interfaces of storage engine
+		$reflect = new ReflectionClass($this->storage);
+		if (!$reflect->hasConstant('RESPONSE_TYPE_' . strtoupper($input['response_type']))) {
+			throw new OAuth2RedirectException($input["redirect_uri"], self::ERROR_UNSUPPORTED_RESPONSE_TYPE, NULL, $input["state"]);
+		}
+		
+		// Validate that the requested scope is supported
+		if ($input["scope"] && !$this->checkScope($input["scope"], $this->getVariable(self::CONFIG_SUPPORTED_SCOPES))) {
+			throw new OAuth2RedirectException($input["redirect_uri"], self::ERROR_INVALID_SCOPE, 'An unsupported scope was requested.', $input["state"]);
+		}
+		
+		// Validate state parameter exists (if configured to enforce this)
+		if ($this->getVariable(self::CONFIG_ENFORCE_STATE) && !$input["state"]) {
+			throw new OAuth2RedirectException($input["redirect_uri"], self::ERROR_INVALID_REQUEST, "The state parameter is required.");
+		}
+		
+		// Return retrieved client details together with input
+		return ($input + $stored);
+	}
+
+	/**
+	 * Redirect the user appropriately after approval.
+	 *
+	 * After the user has approved or denied the access request the
+	 * authorization server should call this function to redirect the user
+	 * appropriately.
+	 *
+	 * @param $is_authorized
+	 * TRUE or FALSE depending on whether the user authorized the access.
+	 * @param $user_id
+	 * Identifier of user who authorized the client
+	 * @param $params
+	 * An associative array as below:
+	 * - response_type: The requested response: an access token, an
+	 * authorization code, or both.
+	 * - client_id: The client identifier as described in Section 2.
+	 * - redirect_uri: An absolute URI to which the authorization server
+	 * will redirect the user-agent to when the end-user authorization
+	 * step is completed.
+	 * - scope: (optional) The scope of the access request expressed as a
+	 * list of space-delimited strings.
+	 * - state: (optional) An opaque value used by the client to maintain
+	 * state between the request and callback.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	public function finishClientAuthorization($is_authorized, $user_id = NULL, $params = array()) {
+		
+		// We repeat this, because we need to re-validate. In theory, this could be POSTed
+		// by a 3rd-party (because we are not internally enforcing NONCEs, etc)
+		$params = $this->getAuthorizeParams($params);
+		
+		$params += array('scope' => NULL, 'state' => NULL);
+		extract($params);
+		
+		if ($state !== NULL) {
+			$result["query"]["state"] = $state;
+		}
+		
+		if ($is_authorized === FALSE) {
+			throw new OAuth2RedirectException($redirect_uri, self::ERROR_USER_DENIED, "The user denied access to your application", $state);
+		} else {
+			if ($response_type == self::RESPONSE_TYPE_AUTH_CODE) {
+				$result["query"]["code"] = $this->createAuthCode($client_id, $user_id, $redirect_uri, $scope);
+			} elseif ($response_type == self::RESPONSE_TYPE_ACCESS_TOKEN) {
+				$result["fragment"] = $this->createAccessToken($client_id, $user_id, $scope);
+			}
+		}
+		
+		$this->doRedirectUriCallback($redirect_uri, $result);
+	}
+
+	// Other/utility functions.
+	
+
+	/**
+	 * Redirect the user agent.
+	 *
+	 * Handle both redirect for success or error response.
+	 *
+	 * @param $redirect_uri
+	 * An absolute URI to which the authorization server will redirect
+	 * the user-agent to when the end-user authorization step is completed.
+	 * @param $params
+	 * Parameters to be pass though buildUri().
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	private function doRedirectUriCallback($redirect_uri, $params) {
+		header("HTTP/1.1 " . self::HTTP_FOUND);
+		header("Location: " . $this->buildUri($redirect_uri, $params));
+		exit();
+	}
+
+	/**
+	 * Build the absolute URI based on supplied URI and parameters.
+	 *
+	 * @param $uri
+	 * An absolute URI.
+	 * @param $params
+	 * Parameters to be append as GET.
+	 *
+	 * @return
+	 * An absolute URI with supplied parameters.
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	private function buildUri($uri, $params) {
+		$parse_url = parse_url($uri);
+		
+		// Add our params to the parsed uri
+		foreach ( $params as $k => $v ) {
+			if (isset($parse_url[$k])) {
+				$parse_url[$k] .= "&" . http_build_query($v);
+			} else {
+				$parse_url[$k] = http_build_query($v);
+			}
+		}
+		
+		// Put humpty dumpty back together
+		return
+			((isset($parse_url["scheme"])) ? $parse_url["scheme"] . "://" : "")
+			. ((isset($parse_url["user"])) ? $parse_url["user"]
+			. ((isset($parse_url["pass"])) ? ":" . $parse_url["pass"] : "") . "@" : "")
+			. ((isset($parse_url["host"])) ? $parse_url["host"] : "")
+			. ((isset($parse_url["port"])) ? ":" . $parse_url["port"] : "")
+			. ((isset($parse_url["path"])) ? $parse_url["path"] : "")
+			. ((isset($parse_url["query"])) ? "?" . $parse_url["query"] : "")
+			. ((isset($parse_url["fragment"])) ? "#" . $parse_url["fragment"] : "")
+		;
+	}
+
+	/**
+	 * Handle the creation of access token, also issue refresh token if support.
+	 *
+	 * This belongs in a separate factory, but to keep it simple, I'm just
+	 * keeping it here.
+	 *
+	 * @param $client_id
+	 * Client identifier related to the access token.
+	 * @param $scope
+	 * (optional) Scopes to be stored in space-separated string.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5
+	 * @ingroup oauth2_section_5
+	 */
+// FunÃ§Ã£o ORIGINAL;	
+//	protected function createAccessToken($client_id, $user_id, $scope = NULL) {
+//		
+//		$token = array(
+//			"access_token" => $this->genAccessToken(),
+//			"expires_in" => $this->getVariable(self::CONFIG_ACCESS_LIFETIME),
+//			"token_type" => $this->getVariable(self::CONFIG_TOKEN_TYPE),
+//			"scope" => $scope
+//		);
+//		
+//		$this->storage->setAccessToken($token["access_token"], $client_id, $user_id, time() + $this->getVariable(self::CONFIG_ACCESS_LIFETIME), $scope);
+//		
+//		// Issue a refresh token also, if we support them
+//		if ($this->storage instanceof IOAuth2RefreshTokens) {
+//			$token["refresh_token"] = $this->genAccessToken();
+//			$this->storage->setRefreshToken($token["refresh_token"], $client_id, $user_id, time() + $this->getVariable(self::CONFIG_REFRESH_LIFETIME), $scope);
+//			
+//			// If we've granted a new refresh token, expire the old one
+//			if ($this->oldRefreshToken) {
+//				$this->storage->unsetRefreshToken($this->oldRefreshToken);
+//				unset($this->oldRefreshToken);
+//			}
+//		}
+//		
+//		return $token;
+//	}
+	protected function createAccessToken($client_id, $user_id, $scope = NULL) {
+		
+		$token = array(
+			"access_token" => $this->genAccessToken(),
+			"expires_in" => $this->getVariable(self::CONFIG_ACCESS_LIFETIME),
+			"token_type" => $this->getVariable(self::CONFIG_TOKEN_TYPE),
+			"scope" => $scope
+		);
+		$token["refresh_token"] = $this->genAccessToken();
+		
+		$this->storage->setAccessToken($token["access_token"], $client_id, $user_id, time() + $this->getVariable(self::CONFIG_ACCESS_LIFETIME), $scope , $token['refresh_token']);
+		
+		// Issue a refresh token also, if we support them
+		if ($this->storage instanceof IOAuth2RefreshTokens) {
+			$this->storage->setRefreshToken($token["refresh_token"], $client_id, $user_id, time() + $this->getVariable(self::CONFIG_REFRESH_LIFETIME), $scope);
+			
+			// If we've granted a new refresh token, expire the old one
+			if ($this->oldRefreshToken) {
+				$this->storage->unsetRefreshToken($this->oldRefreshToken);
+				unset($this->oldRefreshToken);
+			}
+		}
+		
+		return $token;
+	}
+
+	/**
+	 * Handle the creation of auth code.
+	 *
+	 * This belongs in a separate factory, but to keep it simple, I'm just
+	 * keeping it here.
+	 *
+	 * @param $client_id
+	 * Client identifier related to the access token.
+	 * @param $redirect_uri
+	 * An absolute URI to which the authorization server will redirect the
+	 * user-agent to when the end-user authorization step is completed.
+	 * @param $scope
+	 * (optional) Scopes to be stored in space-separated string.
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	private function createAuthCode($client_id, $user_id, $redirect_uri, $scope = NULL) {
+		$code = $this->genAuthCode();
+		$this->storage->setAuthCode($code, $client_id, $user_id, $redirect_uri, time() + $this->getVariable(self::CONFIG_AUTH_LIFETIME), $scope);
+		return $code;
+	}
+
+	/**
+	 * Generates an unique access token.
+	 *
+	 * Implementing classes may want to override this function to implement
+	 * other access token generation schemes.
+	 *
+	 * @return
+	 * An unique access token.
+	 *
+	 * @ingroup oauth2_section_4
+	 * @see OAuth2::genAuthCode()
+	 */
+	protected function genAccessToken() {
+		$tokenLen = 40;
+		if (file_exists('/dev/urandom')) { // Get 100 bytes of random data
+			$randomData = file_get_contents('/dev/urandom', false, null, 0, 100) . uniqid(mt_rand(), true);
+		} else {
+			$randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true);
+		}
+		return substr(hash('sha512', $randomData), 0, $tokenLen);
+	}
+
+	/**
+	 * Generates an unique auth code.
+	 *
+	 * Implementing classes may want to override this function to implement
+	 * other auth code generation schemes.
+	 *
+	 * @return
+	 * An unique auth code.
+	 *
+	 * @ingroup oauth2_section_4
+	 * @see OAuth2::genAccessToken()
+	 */
+	protected function genAuthCode() {
+		return $this->genAccessToken(); // let's reuse the same scheme for token generation
+	}
+
+	/**
+	 * Pull out the Authorization HTTP header and return it.
+	 * According to draft 20, standard basic authorization is the only
+	 * header variable required (this does not apply to extended grant types).
+	 *
+	 * Implementing classes may need to override this function if need be.
+	 * 
+	 * @todo We may need to re-implement pulling out apache headers to support extended grant types
+	 *
+	 * @return
+	 * An array of the basic username and password provided.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-2.4.1
+	 * @ingroup oauth2_section_2
+	 */
+	protected function getAuthorizationHeader() {
+		return array(
+			'PHP_AUTH_USER' => isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : '',
+			'PHP_AUTH_PW' => isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''
+		);
+	}
+
+	/**
+	 * Send out HTTP headers for JSON.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2
+	 *
+	 * @ingroup oauth2_section_5
+	 */
+	private function sendJsonHeaders() {
+		if (php_sapi_name() === 'cli' || headers_sent()) {
+			return;
+		}
+		
+		header("Content-Type: application/json");
+		header("Cache-Control: no-store");
+	}
+
+	/**
+	 * Internal method for validating redirect URI supplied 
+	 * @param string $inputUri
+	 * @param string $storedUri
+	 */
+	protected function validateRedirectUri($inputUri, $storedUri) {
+		if (!$inputUri || !$storedUri) {
+			return false; // if either one is missing, assume INVALID
+		}
+		return strcasecmp(substr($inputUri, 0, strlen($storedUri)), $storedUri) === 0;
+	}
+}
Index: trunk/prototype/library/oauth2/lib/OAuth2RedirectException.php
===================================================================
--- trunk/prototype/library/oauth2/lib/OAuth2RedirectException.php	(revision 6528)
+++ trunk/prototype/library/oauth2/lib/OAuth2RedirectException.php	(revision 6528)
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * Redirect the end-user's user agent with error message.
+ *
+ * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1
+ *
+ * @ingroup oauth2_error
+ */
+class OAuth2RedirectException extends OAuth2ServerException {
+	
+	protected $redirectUri;
+
+	/**
+	 * @param $redirect_uri
+	 * An absolute URI to which the authorization server will redirect the
+	 * user-agent to when the end-user authorization step is completed.
+	 * @param $error
+	 * A single error code as described in Section 4.1.2.1
+	 * @param $error_description
+	 * (optional) A human-readable text providing additional information,
+	 * used to assist in the understanding and resolution of the error
+	 * occurred.
+	 * @param $state
+	 * (optional) REQUIRED if the "state" parameter was present in the client
+	 * authorization request. Set to the exact value received from the client.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.2.1
+	 *
+	 * @ingroup oauth2_error
+	 */
+	public function __construct($redirect_uri, $error, $error_description = NULL, $state = NULL) {
+		parent::__construct(OAuth2::HTTP_FOUND, $error, $error_description);
+		
+		$this->redirectUri = $redirect_uri;
+		if ($state) {
+			$this->errorData['state'] = $state;
+		}
+	
+	}
+
+	/**
+	 * Redirect the user agent.
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	protected function sendHeaders() {
+		$params = array('query' => $this->errorData);
+		header("Location: " . $this->buildUri($this->redirectUri, $params));
+		exit(); // No point in printing out data if we're redirecting
+	}
+
+	/**
+	 * Build the absolute URI based on supplied URI and parameters.
+	 *
+	 * @param $uri
+	 * An absolute URI.
+	 * @param $params
+	 * Parameters to be append as GET.
+	 *
+	 * @return
+	 * An absolute URI with supplied parameters.
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	protected function buildUri($uri, $params) {
+		$parse_url = parse_url($uri);
+		
+		// Add our params to the parsed uri
+		foreach ( $params as $k => $v ) {
+			if (isset($parse_url[$k]))
+				$parse_url[$k] .= "&" . http_build_query($v);
+			else
+				$parse_url[$k] = http_build_query($v);
+		}
+		
+		// Put humpty dumpty back together
+		return ((isset($parse_url["scheme"])) ? $parse_url["scheme"] . "://" : "") . ((isset($parse_url["user"])) ? $parse_url["user"] . ((isset($parse_url["pass"])) ? ":" . $parse_url["pass"] : "") . "@" : "") . ((isset($parse_url["host"])) ? $parse_url["host"] : "") . ((isset($parse_url["port"])) ? ":" . $parse_url["port"] : "") . ((isset($parse_url["path"])) ? $parse_url["path"] : "") . ((isset($parse_url["query"])) ? "?" . $parse_url["query"] : "") . ((isset($parse_url["fragment"])) ? "#" . $parse_url["fragment"] : "");
+	}
+}
Index: trunk/prototype/library/oauth2/lib/IOAuth2GrantExtension.php
===================================================================
--- trunk/prototype/library/oauth2/lib/IOAuth2GrantExtension.php	(revision 6528)
+++ trunk/prototype/library/oauth2/lib/IOAuth2GrantExtension.php	(revision 6528)
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * Storage engines that support the "Extensible"
+ * grant types should implement this interface
+ * 
+ * @author Dave Rochwerger <catch.dave@gmail.com>
+ * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.5
+ */
+interface IOAuth2GrantExtension extends IOAuth2Storage {
+
+	/**
+	 * Check any extended grant types.
+	 * 
+	 * @param string $uri
+	 * URI of the grant type definition
+	 * @param array $inputData
+	 * Unfiltered input data. The source is *not* guaranteed to be POST (but
+	 * is likely to be).
+	 * @param array $authHeaders
+	 * Authorization headers
+	 * @return
+	 * FALSE if the authorization is rejected or not support.
+	 * TRUE or an associative array if you wantto verify the scope:
+	 * @code
+	 * return array(
+	 * 'scope' => <stored scope values (space-separated string)>,
+	 * );
+	 * @endcode
+	 * 
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-1.4.5
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.2
+	 */
+	public function checkGrantExtension($uri, array $inputData, array $authHeaders);
+}
Index: trunk/prototype/library/oauth2/lib/IOAuth2GrantClient.php
===================================================================
--- trunk/prototype/library/oauth2/lib/IOAuth2GrantClient.php	(revision 6528)
+++ trunk/prototype/library/oauth2/lib/IOAuth2GrantClient.php	(revision 6528)
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * Storage engines that support the "Client Credentials"
+ * grant type should implement this interface
+ * 
+ * @author Dave Rochwerger <catch.dave@gmail.com>
+ * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.4
+ */
+interface IOAuth2GrantClient extends IOAuth2Storage {
+
+	/**
+	 * Required for OAuth2::GRANT_TYPE_CLIENT_CREDENTIALS.
+	 *
+	 * @param $client_id
+	 * Client identifier to be check with.
+	 * @param $client_secret
+	 * (optional) If a secret is required, check that they've given the right one.
+	 *
+	 * @return
+	 * TRUE if the client credentials are valid, and MUST return FALSE if it isn't.
+	 * When using "client credentials" grant mechanism and you want to
+	 * verify the scope of a user's access, return an associative array
+	 * with the scope values as below. We'll check the scope you provide
+	 * against the requested scope before providing an access token:
+	 * @code
+	 * return array(
+	 * 'scope' => <stored scope values (space-separated string)>,
+	 * );
+	 * @endcode
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.4.2
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	public function checkClientCredentialsGrant($client_id, $client_secret);
+}
Index: trunk/prototype/library/oauth2/lib/IOAuth2GrantUser.php
===================================================================
--- trunk/prototype/library/oauth2/lib/IOAuth2GrantUser.php	(revision 6528)
+++ trunk/prototype/library/oauth2/lib/IOAuth2GrantUser.php	(revision 6528)
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * Storage engines that support the "Resource Owner Password Credentials"
+ * grant type should implement this interface
+ * 
+ * @author Dave Rochwerger <catch.dave@gmail.com>
+ * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.3
+ */
+interface IOAuth2GrantUser extends IOAuth2Storage {
+
+	/**
+	 * Grant access tokens for basic user credentials.
+	 *
+	 * Check the supplied username and password for validity.
+	 *
+	 * You can also use the $client_id param to do any checks required based
+	 * on a client, if you need that.
+	 *
+	 * Required for OAuth2::GRANT_TYPE_USER_CREDENTIALS.
+	 *
+	 * @param $client_id
+	 * Client identifier to be check with.
+	 * @param $username
+	 * Username to be check with.
+	 * @param $password
+	 * Password to be check with.
+	 *
+	 * @return
+	 * TRUE if the username and password are valid, and FALSE if it isn't.
+	 * Moreover, if the username and password are valid, and you want to
+	 * verify the scope of a user's access, return an associative array
+	 * with the scope values as below. We'll check the scope you provide
+	 * against the requested scope before providing an access token:
+	 * @code
+	 * return array(
+	 * 'scope' => <stored scope values (space-separated string)>,
+	 * );
+	 * @endcode
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.3
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	public function checkUserCredentials($client_id, $username, $password);
+}
Index: trunk/prototype/library/oauth2/lib/OAuth2ServerException.php
===================================================================
--- trunk/prototype/library/oauth2/lib/OAuth2ServerException.php	(revision 6528)
+++ trunk/prototype/library/oauth2/lib/OAuth2ServerException.php	(revision 6528)
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * OAuth2 errors that require termination of OAuth2 due to
+ * an error.
+ *
+ */
+class OAuth2ServerException extends Exception {
+	
+	protected $httpCode;
+	protected $errorData = array();
+
+	/**
+	 * @param $http_status_code
+	 * HTTP status code message as predefined.
+	 * @param $error
+	 * A single error code.
+	 * @param $error_description
+	 * (optional) A human-readable text providing additional information,
+	 * used to assist in the understanding and resolution of the error
+	 * occurred.
+	 */
+	public function __construct($http_status_code, $error, $error_description = NULL) {
+		parent::__construct($error);
+		
+		$this->httpCode = $http_status_code;
+		
+		$this->errorData['error'] = $error;
+		if ($error_description) {
+			$this->errorData['error_description'] = $error_description;
+		}
+	}
+
+	/**
+	 * @return string 
+	 */
+	public function getDescription() {
+		return isset($this->errorData['error_description']) ? $this->errorData['error_description'] : null;
+	}
+
+	/**
+	 * @return string 
+	 */
+	public function getHttpCode() {
+		return $this->httpCode;
+	}
+
+	/**
+	 * Send out error message in JSON.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2
+	 *
+	 * @ingroup oauth2_error
+	 */
+	public function sendHttpResponse() {
+		header("HTTP/1.1 " . $this->httpCode);
+		$this->sendHeaders();
+		echo (string) $this;
+		exit();
+	}
+
+	/**
+	 * Send out HTTP headers for JSON.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.1
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-5.2
+	 *
+	 * @ingroup oauth2_section_5
+	 */
+	protected function sendHeaders() {
+		header("Content-Type: application/json");
+		header("Cache-Control: no-store");
+	}
+
+	/**
+	 * @see Exception::__toString()
+	 */
+	public function __toString() {
+		return json_encode($this->errorData);
+	}
+}
Index: trunk/prototype/library/oauth2/server/examples/mongo/authorize.php
===================================================================
--- trunk/prototype/library/oauth2/server/examples/mongo/authorize.php	(revision 6528)
+++ trunk/prototype/library/oauth2/server/examples/mongo/authorize.php	(revision 6528)
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Sample authorize endpoint.
+ *
+ * Obviously not production-ready code, just simple and to the point.
+ *
+ * In reality, you'd probably use a nifty framework to handle most of the crud for you.
+ */
+
+require "lib/MongoOAuth2.php";
+
+$oauth = new MongoOAuth2();
+
+if ($_POST) {
+	$oauth->finishClientAuthorization($_POST["accept"] == "Yep", $_POST);
+}
+
+try {
+	$auth_params = $oauth->getAuthorizeParams();
+} catch (OAuth2ServerException $oauthError) {
+	$oauthError->sendHttpResponse();
+}
+
+?>
+<html>
+<head>
+Authorize
+</head>
+<body>
+<form method="post" action="authorize.php">
+      <?php foreach ($auth_params as $k => $v) { ?>
+      <input type="hidden" name="<?php echo $k ?>"
+	value="<?php echo $v ?>" />
+      <?php } ?>
+      Do you authorize the app to do its thing?
+      <p><input type="submit" name="accept" value="Yep" /> <input
+	type="submit" name="accept" value="Nope" /></p>
+</form>
+</body>
+</html>
Index: trunk/prototype/library/oauth2/server/examples/mongo/lib/OAuth2StorageMongo.php
===================================================================
--- trunk/prototype/library/oauth2/server/examples/mongo/lib/OAuth2StorageMongo.php	(revision 6528)
+++ trunk/prototype/library/oauth2/server/examples/mongo/lib/OAuth2StorageMongo.php	(revision 6528)
@@ -0,0 +1,175 @@
+<?php
+
+/**
+ * @file
+ * Sample OAuth2 Library Mongo DB Implementation.
+ * 
+ */
+
+require __DIR__ . '/../../../../lib/OAuth2.php';
+require __DIR__ . '/../../../../lib/IOAuth2Storage.php';
+require __DIR__ . '/../../../../lib/IOAuth2GrantCode.php';
+require __DIR__ . '/../../../../lib/IOAuth2RefreshTokens.php';
+
+/**
+ * WARNING: This example file has not been kept up to date like the PDO example has.
+ * FIXME: Update the Mongo examples
+ * 
+ * Mongo storage engine for the OAuth2 Library.
+ */
+class OAuth2StorageMongo implements IOAuth2GrantCode, IOAuth2RefreshTokens {
+	
+	/**
+	 * Change this to something unique for your system
+	 * @var string
+	 */
+	const SALT = 'CHANGE_ME!';
+	
+	const CONNECTION = 'mongodb://user:pass@mongoserver/mydb';
+	const DB = 'mydb';
+	
+	/**
+	 * @var Mongo
+	 */
+	private $db;
+
+	/**
+	 * Implements OAuth2::__construct().
+	 */
+	public function __construct(PDO $db) {
+		
+		$mongo = new Mongo(self::CONNECTION);
+		$this->db = $mongo->selectDB(self::DB);
+	}
+
+	/**
+	 * Release DB connection during destruct.
+	 */
+	function __destruct() {
+		$this->db = NULL; // Release db connection
+	}
+
+	/**
+	 * Handle PDO exceptional cases.
+	 */
+	private function handleException($e) {
+		echo 'Database error: ' . $e->getMessage();
+		exit();
+	}
+
+	/**
+	 * Little helper function to add a new client to the database.
+	 *
+	 * @param $client_id
+	 * Client identifier to be stored.
+	 * @param $client_secret
+	 * Client secret to be stored.
+	 * @param $redirect_uri
+	 * Redirect URI to be stored.
+	 */
+	public function addClient($client_id, $client_secret, $redirect_uri) {
+		$this->db->clients->insert(array("_id" => $client_id, "pw" => $this->hash($client_secret, $client_id), "redirect_uri" => $redirect_uri));
+	}
+
+	/**
+	 * Implements IOAuth2Storage::checkClientCredentials().
+	 *
+	 */
+	public function checkClientCredentials($client_id, $client_secret = NULL) {
+		$client = $this->db->clients->findOne(array("_id" => $client_id, "pw" => $client_secret));
+		return $this->checkPassword($client_secret, $result['client_secret'], $client_id);
+	}
+
+	/**
+	 * Implements IOAuth2Storage::getRedirectUri().
+	 */
+	public function getClientDetails($client_id) {
+		$result = $this->db->clients->findOne(array("_id" => $client_id), array("redirect_uri"));
+	}
+
+	/**
+	 * Implements IOAuth2Storage::getAccessToken().
+	 */
+	public function getAccessToken($oauth_token) {
+		return $this->db->tokens->findOne(array("_id" => $oauth_token));
+	}
+
+	/**
+	 * Implements IOAuth2Storage::setAccessToken().
+	 */
+	public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = NULL) {
+		$this->db->tokens->insert(array("_id" => $oauth_token, "client_id" => $client_id, "expires" => $expires, "scope" => $scope));
+	}
+
+	/**
+	 * @see IOAuth2Storage::getRefreshToken()
+	 */
+	public function getRefreshToken($refresh_token) {
+		return $this->getToken($refresh_token, TRUE);
+	}
+
+	/**
+	 * @see IOAuth2Storage::setRefreshToken()
+	 */
+	public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = NULL) {
+		return $this->setToken($refresh_token, $client_id, $user_id, $expires, $scope, TRUE);
+	}
+
+	/**
+	 * @see IOAuth2Storage::unsetRefreshToken()
+	 */
+	public function unsetRefreshToken($refresh_token) {
+		try {
+			$sql = 'DELETE FROM ' . self::TABLE_TOKENS . ' WHERE refresh_token = :refresh_token';
+			$stmt = $this->db->prepare($sql);
+			$stmt->bindParam(':refresh_token', $refresh_token, PDO::PARAM_STR);
+			$stmt->execute();
+		} catch (PDOException $e) {
+			$this->handleException($e);
+		}
+	}
+
+	/**
+	 * Implements IOAuth2Storage::getAuthCode().
+	 */
+	public function getAuthCode($code) {
+		$stored_code = $this->db->auth_codes->findOne(array("_id" => $code));
+		return $stored_code !== NULL ? $stored_code : FALSE;
+	}
+
+	/**
+	 * Implements IOAuth2Storage::setAuthCode().
+	 */
+	public function setAuthCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = NULL) {
+		$this->db->auth_codes->insert(array("_id" => $code, "client_id" => $client_id, "redirect_uri" => $redirect_uri, "expires" => $expires, "scope" => $scope));
+	}
+
+	/**
+	 * @see IOAuth2Storage::checkRestrictedGrantType()
+	 */
+	public function checkRestrictedGrantType($client_id, $grant_type) {
+		return TRUE; // Not implemented
+	}
+
+	/**
+	 * Change/override this to whatever your own password hashing method is.
+	 * 
+	 * @param string $secret
+	 * @return string
+	 */
+	protected function hash($client_secret, $client_id) {
+		return hash('blowfish', $client_id . $client_secret . self::SALT);
+	}
+
+	/**
+	 * Checks the password.
+	 * Override this if you need to
+	 * 
+	 * @param string $client_id
+	 * @param string $client_secret
+	 * @param string $actualPassword
+	 */
+	protected function checkPassword($try, $client_secret, $client_id) {
+		return $try == $this->hash($client_secret, $client_id);
+	}
+}
Index: trunk/prototype/library/oauth2/server/examples/mongo/protected_resource.php
===================================================================
--- trunk/prototype/library/oauth2/server/examples/mongo/protected_resource.php	(revision 6528)
+++ trunk/prototype/library/oauth2/server/examples/mongo/protected_resource.php	(revision 6528)
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Sample protected resource.
+ *
+ * Obviously not production-ready code, just simple and to the point.
+ *
+ * In reality, you'd probably use a nifty framework to handle most of the crud for you.
+ */
+
+require "lib/OAuth2StorageMongo.php";
+
+$token = isset($_GET[OAuth2::TOKEN_PARAM_NAME]) ? $_GET[OAuth2::TOKEN_PARAM_NAME] : null;
+
+try {
+	$oauth = new OAuth2(new OAuth2StorageMongo());
+	$token = $oauth->getBearerToken();
+	$oauth->verifyAccessToken($token);
+} catch (OAuth2ServerException $oauthError) {
+	$oauthError->sendHttpResponse();
+}
+
+// With a particular scope, you'd do:
+// $oauth->verifyAccessToken("scope_name");
+
+
+?>
+
+<html>
+	<head>
+		<title>Hello!</title>
+	</head>
+	<body>
+		<p>This is a secret.</p>
+	</body>
+</html>
Index: trunk/prototype/library/oauth2/server/examples/mongo/token.php
===================================================================
--- trunk/prototype/library/oauth2/server/examples/mongo/token.php	(revision 6528)
+++ trunk/prototype/library/oauth2/server/examples/mongo/token.php	(revision 6528)
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Sample token endpoint.
+ *
+ * Obviously not production-ready code, just simple and to the point.
+ *
+ * In reality, you'd probably use a nifty framework to handle most of the crud for you.
+ */
+
+require "lib/MongoOAuth2.php";
+
+$oauth = new MongoOAuth2();
+try {
+	$oauth->grantAccessToken();
+} catch (OAuth2ServerException $oauthError) {
+	$oauthError->sendHttpResponse();
+}
Index: trunk/prototype/library/oauth2/server/examples/mongo/addclient.php
===================================================================
--- trunk/prototype/library/oauth2/server/examples/mongo/addclient.php	(revision 6528)
+++ trunk/prototype/library/oauth2/server/examples/mongo/addclient.php	(revision 6528)
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Sample client add script.
+ *
+ * Obviously not production-ready code, just simple and to the point.
+ */
+
+require "lib/MongoOAuth2.php";
+
+if ($_POST && isset($_POST["client_id"]) && isset($_POST["client_secret"]) && isset($_POST["redirect_uri"])) {
+	$oauth = new MongoOAuth2();
+	$oauth->addClient($_POST["client_id"], $_POST["client_secret"], $_POST["redirect_uri"]);
+}
+
+?>
+
+<html>
+<head>
+Add Client
+</head>
+<body>
+<form method="post" action="addclient.php">
+<p><label for="client_id">Client ID:</label> <input type="text"
+	name="client_id" id="client_id" /></p>
+<p><label for="client_secret">Client Secret (password/key):</label> <input
+	type="text" name="client_secret" id="client_secret" /></p>
+<p><label for="redirect_uri">Redirect URI:</label> <input type="text"
+	name="redirect_uri" id="redirect_uri" /></p>
+<input type="submit" value="Submit" /></form>
+</body>
+</html>
Index: trunk/prototype/library/oauth2/server/examples/pdo/authorize.php
===================================================================
--- trunk/prototype/library/oauth2/server/examples/pdo/authorize.php	(revision 6528)
+++ trunk/prototype/library/oauth2/server/examples/pdo/authorize.php	(revision 6528)
@@ -0,0 +1,67 @@
+<?php
+/**
+ * @file
+ * Sample authorize endpoint.
+ *
+ * This sample provides two click-jacking prevention methods, neither which are perfect. 
+ * The javascript solution is similar to what facebook used to have (but can be defeated with a 
+ * specially crafted frame-wrapper).
+ */
+
+// Clickjacking prevention (supported by IE8+, FF3.6.9+, Opera10.5+, Safari4+, Chrome 4.1.249.1042+)
+header('X-Frame-Options: DENY');
+
+require "lib/OAuth2StoragePdo.php";
+
+/*
+ * You would need to authenticate the user before authorization.
+ * 
+ * Below is some psudeo-code to show what you might do:
+ * 
+session_start();
+if (!isLoggedIn()) {
+	redirectToLoginPage();
+	exit();
+}
+ */
+
+$oauth = new OAuth2(new OAuth2StoragePDO());
+
+if ($_POST) {
+	$userId = '12345'; // Use whatever method you have for identifying users.
+	$oauth->finishClientAuthorization($_POST["accept"] == "Yep", $userId, $_POST);
+}
+
+try {
+	$auth_params = $oauth->getAuthorizeParams();
+} catch (OAuth2ServerException $oauthError) {
+	$oauthError->sendHttpResponse();
+}
+
+?>
+<html>
+<head>
+<title>Authorize</title>
+<script>
+	if (top != self) {
+		window.document.write("<div style='background:black; opacity:0.5; filter: alpha (opacity = 50); position: absolute; top:0px; left: 0px;"
+		+ "width: 9999px; height: 9999px; zindex: 1000001' onClick='top.location.href=window.location.href'></div>");
+	}
+  </script>
+</head>
+<body>
+<form method="post" action="authorize.php">
+ 
+    
+      <?php 
+      foreach ($auth_params as $key => $value) : ?>
+      	<input type="hidden"
+	name="<?php echo htmlspecialchars($key, ENT_QUOTES); ?>"
+	value="<?php echo htmlspecialchars($value, ENT_QUOTES); ?>" />
+      <?php endforeach; ?>
+      Do you authorize the app to do its thing?
+      <p><input type="submit" name="accept" value="Yep" /> <input
+	type="submit" name="accept" value="Nope" /></p>
+</form>
+</body>
+</html>
Index: trunk/prototype/library/oauth2/server/examples/pdo/mysql_create_tables.sql
===================================================================
--- trunk/prototype/library/oauth2/server/examples/pdo/mysql_create_tables.sql	(revision 6528)
+++ trunk/prototype/library/oauth2/server/examples/pdo/mysql_create_tables.sql	(revision 6528)
@@ -0,0 +1,36 @@
+SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
+
+CREATE TABLE `auth_codes` (
+  `code` varchar(40) NOT NULL,
+  `client_id` varchar(40) NOT NULL,
+  `user_id` int(11) UNSIGNED NOT NULL,
+  `redirect_uri` varchar(200) NOT NULL,
+  `expires` int(11) NOT NULL,
+  `scope` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`code`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `clients` (
+  `client_id` varchar(40) NOT NULL,
+  `client_secret` varchar(20) NOT NULL,
+  `redirect_uri` varchar(255) NOT NULL,
+  PRIMARY KEY (`client_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `access_tokens` (
+  `oauth_token` varchar(40) NOT NULL,
+  `client_id` varchar(40) NOT NULL,
+  `user_id` int(11) UNSIGNED NOT NULL,
+  `expires` int(11) NOT NULL,
+  `scope` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`oauth_token`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `refresh_tokens` (
+  `refresh_token` varchar(40) NOT NULL,
+  `client_id` varchar(40) NOT NULL,
+  `user_id` int(11) UNSIGNED NOT NULL,
+  `expires` int(11) NOT NULL,
+  `scope` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`refresh_token`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Index: trunk/prototype/library/oauth2/server/examples/pdo/lib/OAuth2StoragePdo.php
===================================================================
--- trunk/prototype/library/oauth2/server/examples/pdo/lib/OAuth2StoragePdo.php	(revision 6528)
+++ trunk/prototype/library/oauth2/server/examples/pdo/lib/OAuth2StoragePdo.php	(revision 6528)
@@ -0,0 +1,311 @@
+<?php
+
+/**
+ * @file
+ * Sample OAuth2 Library PDO DB Implementation.
+ * 
+ * Simply pass in a configured PDO class, eg:
+ * new OAuth2StoragePDO( new PDO('mysql:dbname=mydb;host=localhost', 'user', 'pass') );
+ */
+session_start();
+require __DIR__ . '/../../../../lib/OAuth2.php';
+require __DIR__ . '/../../../../lib/IOAuth2Storage.php';
+require __DIR__ . '/../../../../lib/IOAuth2GrantCode.php';
+require __DIR__ . '/../../../../lib/IOAuth2RefreshTokens.php';
+
+/**
+ * PDO storage engine for the OAuth2 Library.
+ * 
+ * IMPORTANT: This is provided as an example only. In production you may implement
+ * a client-specific salt in the OAuth2StoragePDO::hash() and possibly other goodies.
+ * 
+ *** The point is, use this as an EXAMPLE ONLY. ***
+ */
+class OAuth2StoragePDO implements IOAuth2GrantCode, IOAuth2RefreshTokens {
+	
+	/**
+	 * Change this to something unique for your system
+	 * @var string
+	 */
+	const SALT = 'CHANGE_ME!';
+	
+	/**@#+
+	 * Centralized table names
+	 * 
+	 * @var string
+	 */
+	const TABLE_CLIENTS = 'clients';
+	const TABLE_CODES = 'auth_codes';
+	const TABLE_TOKENS = 'access_tokens';
+	const TABLE_REFRESH = 'refresh_tokens';
+	/**@#-*/
+	
+	/**
+	 * @var PDO
+	 */
+	private $db;
+
+	/**
+	 * Implements OAuth2::__construct().
+	 */
+	public function __construct() {
+		
+		try {
+			$this->db = new PDO('pgsql:dbname=oauth;host=/tmp', 'expressodev', 'prognus');
+		} catch (PDOException $e) {
+			die('Connection failed: ' . $e->getMessage());
+		}
+	}
+
+	/**
+	 * Release DB connection during destruct.
+	 */
+	function __destruct() {
+		$this->db = NULL; // Release db connection
+	}
+
+	/**
+	 * Handle PDO exceptional cases.
+	 */
+	private function handleException($e) {
+		echo 'Database error: ' . $e->getMessage();
+		exit();
+	}
+
+	/**
+	 * Little helper function to add a new client to the database.
+	 *
+	 * Do NOT use this in production! This sample code stores the secret
+	 * in plaintext!
+	 *
+	 * @param $client_id
+	 * Client identifier to be stored.
+	 * @param $client_secret
+	 * Client secret to be stored.
+	 * @param $redirect_uri
+	 * Redirect URI to be stored.
+	 */
+	public function addClient($client_id, $client_secret, $redirect_uri) {
+		try {
+			$client_secret = $this->hash($client_secret, $client_id);
+			
+			$sql = 'INSERT INTO ' . self::TABLE_CLIENTS . ' (client_id, client_secret, redirect_uri) VALUES (:client_id, :client_secret, :redirect_uri)';
+			$stmt = $this->db->prepare($sql);
+			$stmt->bindParam(':client_id', $client_id, PDO::PARAM_STR);
+			$stmt->bindParam(':client_secret', $client_secret, PDO::PARAM_STR);
+			$stmt->bindParam(':redirect_uri', $redirect_uri, PDO::PARAM_STR);
+			$stmt->execute();
+		} catch (PDOException $e) {
+			$this->handleException($e);
+		}
+	}
+
+	/**
+	 * Implements IOAuth2Storage::checkClientCredentials().
+	 *
+	 */
+	public function checkClientCredentials($client_id, $client_secret = NULL) {
+		try {
+			$sql = 'SELECT client_secret FROM ' . self::TABLE_CLIENTS . ' WHERE client_id = :client_id';
+			$stmt = $this->db->prepare($sql);
+			$stmt->bindParam(':client_id', $client_id, PDO::PARAM_STR);
+			$stmt->execute();
+			
+			$result = $stmt->fetch(PDO::FETCH_ASSOC);
+			
+			if ($client_secret === NULL) {
+				return $result !== FALSE;
+			}
+			
+			return $this->checkPassword($client_secret, $result['client_secret'], $client_id);
+		} catch (PDOException $e) {
+			$this->handleException($e);
+		}
+	}
+
+	/**
+	 * Implements IOAuth2Storage::getRedirectUri().
+	 */
+	public function getClientDetails($client_id) {
+		try {
+			$sql = 'SELECT redirect_uri FROM ' . self::TABLE_CLIENTS . ' WHERE client_id = :client_id';
+			$stmt = $this->db->prepare($sql);
+			$stmt->bindParam(':client_id', $client_id, PDO::PARAM_STR);
+			$stmt->execute();
+			
+			$result = $stmt->fetch(PDO::FETCH_ASSOC);
+			
+			if ($result === FALSE) {
+				return FALSE;
+			}
+			
+			return isset($result['redirect_uri']) && $result['redirect_uri'] ? $result : NULL;
+		} catch (PDOException $e) {
+			$this->handleException($e);
+		}
+	}
+
+	/**
+	 * Implements IOAuth2Storage::getAccessToken().
+	 */
+	public function getAccessToken($oauth_token) {
+		return $this->getToken($oauth_token, FALSE);
+	}
+
+	/**
+	 * Implements IOAuth2Storage::setAccessToken().
+	 */
+	public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = NULL) {
+		$this->setToken($oauth_token, $client_id, $user_id, $expires, $scope, FALSE);
+	}
+
+	/**
+	 * @see IOAuth2Storage::getRefreshToken()
+	 */
+	public function getRefreshToken($refresh_token) {
+		return $this->getToken($refresh_token, TRUE);
+	}
+
+	/**
+	 * @see IOAuth2Storage::setRefreshToken()
+	 */
+	public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = NULL) {
+		return $this->setToken($refresh_token, $client_id, $user_id, $expires, $scope, TRUE);
+	}
+
+	/**
+	 * @see IOAuth2Storage::unsetRefreshToken()
+	 */
+	public function unsetRefreshToken($refresh_token) {
+		try {
+			$sql = 'DELETE FROM ' . self::TABLE_TOKENS . ' WHERE refresh_token = :refresh_token';
+			$stmt = $this->db->prepare($sql);
+			$stmt->bindParam(':refresh_token', $refresh_token, PDO::PARAM_STR);
+			$stmt->execute();
+		} catch (PDOException $e) {
+			$this->handleException($e);
+		}
+	}
+
+	/**
+	 * Implements IOAuth2Storage::getAuthCode().
+	 */
+	public function getAuthCode($code) {
+		try {
+			$sql = 'SELECT code, client_id, user_id, redirect_uri, expires, scope FROM ' . self::TABLE_CODES . ' auth_codes WHERE code = :code';
+			$stmt = $this->db->prepare($sql);
+			$stmt->bindParam(':code', $code, PDO::PARAM_STR);
+			$stmt->execute();
+			
+			$result = $stmt->fetch(PDO::FETCH_ASSOC);
+			
+			return $result !== FALSE ? $result : NULL;
+		} catch (PDOException $e) {
+			$this->handleException($e);
+		}
+	}
+
+	/**
+	 * Implements IOAuth2Storage::setAuthCode().
+	 */
+	public function setAuthCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = NULL) {
+		try {
+			$sql = 'INSERT INTO ' . self::TABLE_CODES . ' (code, client_id, user_id, redirect_uri, expires, scope) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope)';
+			$stmt = $this->db->prepare($sql);
+			$stmt->bindParam(':code', $code, PDO::PARAM_STR);
+			$stmt->bindParam(':client_id', $client_id, PDO::PARAM_STR);
+			$stmt->bindParam(':user_id', $user_id, PDO::PARAM_STR);
+			$stmt->bindParam(':redirect_uri', $redirect_uri, PDO::PARAM_STR);
+			$stmt->bindParam(':expires', $expires, PDO::PARAM_INT);
+			$stmt->bindParam(':scope', $scope, PDO::PARAM_STR);
+			
+			$stmt->execute();
+		} catch (PDOException $e) {
+			$this->handleException($e);
+		}
+	}
+
+	/**
+	 * @see IOAuth2Storage::checkRestrictedGrantType()
+	 */
+	public function checkRestrictedGrantType($client_id, $grant_type) {
+		return TRUE; // Not implemented
+	}
+
+	/**
+	 * Creates a refresh or access token
+	 * 
+	 * @param string $token - Access or refresh token id
+	 * @param string $client_id
+	 * @param mixed $user_id
+	 * @param int $expires
+	 * @param string $scope
+	 * @param bool $isRefresh
+	 */
+	protected function setToken($token, $client_id, $user_id, $expires, $scope, $isRefresh = TRUE) {
+		try {
+			$tableName = $isRefresh ? self::TABLE_REFRESH : self::TABLE_TOKENS;
+			$tokenName = $isRefresh ? 'refresh_token' : 'oauth_token';
+                        
+			$sql = "INSERT INTO $tableName ($tokenName, client_id, user_id, expires, scope) VALUES (:token, :client_id, :user_id, :expires, :scope)";
+			$stmt = $this->db->prepare($sql);
+			$stmt->bindParam(':token', $token, PDO::PARAM_STR);
+			$stmt->bindParam(':client_id', $client_id, PDO::PARAM_STR);
+			$stmt->bindParam(':user_id', $user_id, PDO::PARAM_STR);
+			$stmt->bindParam(':expires', $expires, PDO::PARAM_INT);
+			$stmt->bindParam(':scope', $scope, PDO::PARAM_STR);
+			
+			$stmt->execute();
+		} catch (PDOException $e) {
+			$this->handleException($e);
+		}
+	}
+
+	/**
+	 * Retrieves an access or refresh token.
+	 * 
+	 * @param string $token
+	 * @param bool $refresh
+	 */
+	protected function getToken($token, $isRefresh = true) {
+		try {
+			$tableName = $isRefresh ? self::TABLE_REFRESH : self::TABLE_TOKENS;
+			$tokenName = $isRefresh ? 'refresh_token' : 'oauth_token';
+			
+			$sql = "SELECT $tokenName, client_id, expires, scope, user_id FROM $tableName WHERE $tokenName = :token";
+			$stmt = $this->db->prepare($sql);
+			$stmt->bindParam(':token', $token, PDO::PARAM_STR);
+			$stmt->execute();
+			
+			$result = $stmt->fetch(PDO::FETCH_ASSOC);
+			
+			return $result !== FALSE ? $result : NULL;
+		} catch (PDOException $e) {
+			$this->handleException($e);
+		}
+	}
+
+	/**
+	 * Change/override this to whatever your own password hashing method is.
+	 * 
+	 * In production you might want to a client-specific salt to this function. 
+	 * 
+	 * @param string $secret
+	 * @return string
+	 */
+	protected function hash($client_secret, $client_id) {            
+            return hash('md5', $client_id . $client_secret);
+	}
+
+	/**
+	 * Checks the password.
+	 * Override this if you need to
+	 * 
+	 * @param string $client_id
+	 * @param string $client_secret
+	 * @param string $actualPassword
+	 */
+	protected function checkPassword($try, $client_secret, $client_id) {
+		return ($client_secret == $this->hash($try, $client_id) )? TRUE : FALSE;
+	}
+}
Index: trunk/prototype/library/oauth2/server/examples/pdo/lib/OAuth2StorageUserCredential.php
===================================================================
--- trunk/prototype/library/oauth2/server/examples/pdo/lib/OAuth2StorageUserCredential.php	(revision 6528)
+++ trunk/prototype/library/oauth2/server/examples/pdo/lib/OAuth2StorageUserCredential.php	(revision 6528)
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Sample OAuth2 Library PDO DB Implementation.
+ * 
+ * Simply pass in a configured PDO class, eg:
+ * new OAuth2StoragePDO( new PDO('mysql:dbname=mydb;host=localhost', 'user', 'pass') );
+ */
+//session_start();
+//require __DIR__ . '/../../../../lib/OAuth2.php';
+///require __DIR__ . '/../../../../lib/IOAuth2Storage.php';
+//require __DIR__ . '/../../../../lib/IOAuth2GrantCode.php';
+//require __DIR__ . '/../../../../lib/IOAuth2RefreshTokens.php';
+require __DIR__ . '/../../../../lib/IOAuth2GrantUser.php';
+
+/**
+ * PDO storage engine for the OAuth2 Library.
+ * 
+ * IMPORTANT: This is provided as an example only. In production you may implement
+ * a client-specific salt in the OAuth2StoragePDO::hash() and possibly other goodies.
+ * 
+ *** The point is, use this as an EXAMPLE ONLY. ***
+ */
+class OAuth2StorageUserCredential implements IOAuth2GrantUser, IOAuth2RefreshTokens {
+
+	private $valid_client_id = 666;
+
+	public function checkUserCredentials($client_id, $username, $password) {
+		if( $client_id == $this->valid_client_id && $username == 'adirkuhn' && $password == '666' ) {
+			//return true; 
+			return array('scope' => 'all'); 
+		}
+		else {
+			return false;
+		}
+	}
+
+	public function checkClientCredentials($client_id, $client_secret = NULL) {
+	}
+
+	public function getClientDetails($client_id) {
+	}
+
+	public function getAccessToken($oauth_token) {
+	}
+
+	
+	public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = NULL) {
+		var_dump(func_get_args());
+	}
+
+	
+	public function checkRestrictedGrantType($client_id, $grant_type) {
+		if($client_id == $this->valid_client_id	&& $grant_type == 'password') {
+			return true;
+		}
+		else {
+			return false;
+		}
+	}
+
+
+	public function getRefreshToken($refresh_token) {
+	}
+
+	
+	public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = NULL) {
+	}
+
+
+	public function unsetRefreshToken($refresh_token) {
+	}
+
+}
+
+
Index: trunk/prototype/library/oauth2/server/examples/pdo/protected_resource.php
===================================================================
--- trunk/prototype/library/oauth2/server/examples/pdo/protected_resource.php	(revision 6528)
+++ trunk/prototype/library/oauth2/server/examples/pdo/protected_resource.php	(revision 6528)
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Sample protected resource.
+ *
+ * Obviously not production-ready code, just simple and to the point.
+ *
+ * In reality, you'd probably use a nifty framework to handle most of the crud for you.
+ */
+
+require "lib/OAuth2StoragePdo.php";
+
+try {
+	$oauth = new OAuth2(new OAuth2StoragePDO());
+	$token = $oauth->getBearerToken();
+	$oauth->verifyAccessToken($token);
+} catch (OAuth2ServerException $oauthError) {
+	$oauthError->sendHttpResponse();
+}
+
+// With a particular scope, you'd do:
+// $oauth->verifyAccessToken("scope_name");
+
+
+?>
+
+<html>
+	<head>
+		<title>Hello!</title>
+	</head>
+	<body>
+		<p>This is a secret.</p>
+	</body>
+</html>
Index: trunk/prototype/library/oauth2/server/examples/pdo/token.php
===================================================================
--- trunk/prototype/library/oauth2/server/examples/pdo/token.php	(revision 6528)
+++ trunk/prototype/library/oauth2/server/examples/pdo/token.php	(revision 6528)
@@ -0,0 +1,26 @@
+<?php
+ini_set('display_errors', 'on');
+/**
+ * @file
+ * Sample token endpoint.
+ *
+ * Obviously not production-ready code, just simple and to the point.
+ *
+ * In reality, you'd probably use a nifty framework to handle most of the crud for you.
+ */
+
+require "lib/OAuth2StoragePdo.php";
+require "lib/OAuth2StorageUserCredential.php";
+
+if(isset($_GET['grant_type']) && $_GET['grant_type'] === 'password') {
+	$oauth = new OAuth2(new Oauth2StorageUserCredential());
+}
+else {
+	$oauth = new OAuth2(new OAuth2StoragePDO());
+}
+
+try {
+	$oauth->grantAccessToken();
+} catch (OAuth2ServerException $oauthError) {
+	$oauthError->sendHttpResponse();
+}
Index: trunk/prototype/library/oauth2/server/examples/pdo/hello.php
===================================================================
--- trunk/prototype/library/oauth2/server/examples/pdo/hello.php	(revision 6528)
+++ trunk/prototype/library/oauth2/server/examples/pdo/hello.php	(revision 6528)
@@ -0,0 +1,4 @@
+<?php
+
+
+?>
Index: trunk/prototype/library/oauth2/server/examples/pdo/addclient.php
===================================================================
--- trunk/prototype/library/oauth2/server/examples/pdo/addclient.php	(revision 6528)
+++ trunk/prototype/library/oauth2/server/examples/pdo/addclient.php	(revision 6528)
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Sample client add script.
+ *
+ * Obviously not production-ready code, just simple and to the point.
+ */
+
+require "lib/OAuth2StoragePdo.php";
+
+if ($_POST && isset($_POST["client_id"]) && isset($_POST["client_secret"]) && isset($_POST["redirect_uri"])) {
+	$oauth = new OAuth2StoragePDO();
+	$oauth->addClient($_POST["client_id"], $_POST["client_secret"], $_POST["redirect_uri"]);
+}
+
+?>
+
+<html>
+  <head>
+  	<title>Add Client</title>
+  </head>
+  <body>
+    <form method="post" action="addclient.php">
+      <p>
+        <label for="client_id">Client ID:</label>
+        <input type="text" name="client_id" id="client_id" />
+      </p>
+      <p>
+        <label for="client_secret">Client Secret (password/key):</label>
+        <input type="text" name="client_secret" id="client_secret" />
+      </p>
+      <p>
+        <label for="redirect_uri">Redirect URI:</label>
+        <input type="text" name="redirect_uri" id="redirect_uri" />
+      </p>
+      <input type="submit" value="Submit" />
+    </form>
+  </body>
+</html>
Index: trunk/prototype/library/oauth2/config.doxy
===================================================================
--- trunk/prototype/library/oauth2/config.doxy	(revision 6528)
+++ trunk/prototype/library/oauth2/config.doxy	(revision 6528)
@@ -0,0 +1,1631 @@
+# Doxyfile 1.7.1
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = oauth2-php
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         = draft-ietf-oauth-v2-10
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = docs
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 2
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen to replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penality.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will rougly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = YES
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = YES
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.  This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. The create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE            =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = .
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS          = *.php \ *.module \ *.inc \ *.install \ *.js \ *.theme \ *.test
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.  Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.  The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = NO
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.  Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = YES
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the stylesheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+# will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = YES
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES       = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT    = YES
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvances is that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.  This is useful
+# if you want to understand what is going on.  On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#   TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#   TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS        = 0
+
+# By default doxygen will write a font called FreeSans.ttf to the output
+# directory and reference it in all dot files that doxygen generates. This
+# font does not include all possible unicode characters however, so when you need
+# these (or just want a differently looking font) you can specify the font name
+# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME           = FreeSans.ttf
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
Index: trunk/prototype/library/oauth2/README.txt
===================================================================
--- trunk/prototype/library/oauth2/README.txt	(revision 6528)
+++ trunk/prototype/library/oauth2/README.txt	(revision 6528)
@@ -0,0 +1,3 @@
+OAuth2 Server now implements draft 20 of OAuth 2.0
+
+The client is still only draft-10.
Index: trunk/prototype/library/tonic/phpdoctor.ini
===================================================================
--- trunk/prototype/library/tonic/phpdoctor.ini	(revision 6528)
+++ trunk/prototype/library/tonic/phpdoctor.ini	(revision 6528)
@@ -0,0 +1,14 @@
+; Tonic configuration file for PHPDoctor
+
+files = "*.php"
+ignore = ".git"
+doclet = standard
+default_package = "Tonic"
+overview = README.markdown
+
+d = "apidocs"
+windowtitle = "Tonic"
+doctitle = "Tonic: A RESTful Web App Development PHP Library"
+header = "Tonic"
+footer = "Tonic"
+
Index: trunk/prototype/library/tonic/LICENSE
===================================================================
--- trunk/prototype/library/tonic/LICENSE	(revision 6528)
+++ trunk/prototype/library/tonic/LICENSE	(revision 6528)
@@ -0,0 +1,18 @@
+Copyright (c) 2009 Paul James
+ 
+Permission is hereby granted, free of charge, to any person obtaining a copy of 
+this software and associated documentation files (the "Software"), to deal in 
+the Software without restriction, including without limitation the rights to 
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 
+the Software, and to permit persons to whom the Software is furnished to do so, 
+subject to the following conditions:
+ 
+The above copyright notice and this permission notice shall be included in all 
+copies or substantial portions of the Software.
+ 
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Index: trunk/prototype/library/tonic/tests/response.php
===================================================================
--- trunk/prototype/library/tonic/tests/response.php	(revision 6528)
+++ trunk/prototype/library/tonic/tests/response.php	(revision 6528)
@@ -0,0 +1,56 @@
+<?php
+
+require_once('def/response.php');
+
+/**
+ * @namespace Tonic\Tests
+ */
+class ResponseTester extends UnitTestCase {
+    
+    function testDefaultCacheHeader() {
+        
+        $config = array(
+            'uri' => '/responsetest'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        $response->addCacheHeader();
+        
+        $this->assertEqual($response->headers['Cache-Control'], 'max-age=86400, must-revalidate');
+        
+    }
+    
+    function testNoCacheHeader() {
+        
+        $config = array(
+            'uri' => '/responsetest'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        $response->addCacheHeader(0);
+        
+        $this->assertEqual($response->headers['Cache-Control'], 'no-cache');
+        
+    }
+    
+    function testAddEtag() {
+        
+        $config = array(
+            'uri' => '/responsetest'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        $response->addEtag("123123");
+        
+        $this->assertEqual($response->headers['Etag'], '"123123"');
+        
+    }
+    
+}
+
Index: trunk/prototype/library/tonic/tests/test.php
===================================================================
--- trunk/prototype/library/tonic/tests/test.php	(revision 6528)
+++ trunk/prototype/library/tonic/tests/test.php	(revision 6528)
@@ -0,0 +1,43 @@
+<?php
+
+error_reporting(error_reporting() & ~2048 & ~8192); // Make sure E_STRICT and E_DEPRECATED are disabled
+
+require_once('simpletest/unit_tester.php');
+require_once('simpletest/reporter.php');
+
+$core = new GroupTest('Core');
+
+require_once('../lib/tonic.php');
+
+$core->addTestFile('request.php');
+$core->addTestFile('resource.php');
+$core->addTestFile('response.php');
+$core->addTestFile('filesystem.php');
+$core->addTestFile('filesystemcollection.php');
+
+$test = new GroupTest('Tonic');
+$test->addTestCase($core);
+
+//*
+@include_once 'PHP/CodeCoverage.php';
+if (class_exists('PHP_CodeCoverage')) {
+    $coverage = new PHP_CodeCoverage;
+    $coverage->start('Tonic');
+}
+//*/
+
+if (TextReporter::inCli()) {
+	$test->run(new TextReporter());
+} else {
+    $test->run(new HtmlReporter());
+}
+
+if (isset($coverage)) {
+    $coverage->stop();
+    
+    require_once 'PHP/CodeCoverage/Report/HTML.php';
+    
+    $writer = new PHP_CodeCoverage_Report_HTML;
+    $writer->process($coverage, 'report');
+}
+
Index: trunk/prototype/library/tonic/tests/filesystemcollection.php
===================================================================
--- trunk/prototype/library/tonic/tests/filesystemcollection.php	(revision 6528)
+++ trunk/prototype/library/tonic/tests/filesystemcollection.php	(revision 6528)
@@ -0,0 +1,137 @@
+<?php
+
+require_once('../lib/tonic.php');
+require_once('../examples/filesystem/filesystem.php');
+require_once('../examples/filesystem/filesystemcollection.php');
+
+/**
+ * @namespace Tonic\Tests
+ */
+class FilesystemCollectionTester extends UnitTestCase {
+    
+    var $testPath;
+    
+    function __construct() {
+        $this->testPath = sys_get_temp_dir();
+    }
+    
+    function setUp() {
+        @mkdir($this->testPath.DIRECTORY_SEPARATOR.'collection');
+        file_put_contents($this->testPath.DIRECTORY_SEPARATOR.'collectionIndex', '<ul>{{resources}}</ul>');
+        file_put_contents($this->testPath.DIRECTORY_SEPARATOR.'collection'.DIRECTORY_SEPARATOR.'1', 'one');
+        file_put_contents($this->testPath.DIRECTORY_SEPARATOR.'collection'.DIRECTORY_SEPARATOR.'2', 'two');
+    }
+    
+    function tearDown() {
+        unlink($this->testPath.DIRECTORY_SEPARATOR.'collectionIndex');
+        unlink($this->testPath.DIRECTORY_SEPARATOR.'collection'.DIRECTORY_SEPARATOR.'1');
+        unlink($this->testPath.DIRECTORY_SEPARATOR.'collection'.DIRECTORY_SEPARATOR.'2');
+        @unlink($this->testPath.DIRECTORY_SEPARATOR.'collection'.DIRECTORY_SEPARATOR.'3');
+        rmdir($this->testPath.DIRECTORY_SEPARATOR.'collection');
+    }
+    
+    function testCollection() {
+        
+        $config = array(
+            'uri' => '/filesystemcollection/collectionIndex'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        
+        $this->assertEqual($response->code, 200);
+        $this->assertEqual($response->body, '<ul><li><a href="/filesystemcollection/collection/1">1</a></li><li><a href="/filesystemcollection/collection/2">2</a></li></ul>');
+        
+    }
+    
+    function testReadCollectionItem() {
+        
+        $config = array(
+            'uri' => '/filesystemcollection/collection/1'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        
+        $this->assertEqual($response->code, 200);
+        $this->assertEqual($response->body, 'one');
+        
+    }
+    
+    function testCreateCollectionItem() {
+        
+        $config = array(
+            'uri' => '/filesystemcollection/collectionIndex',
+            'method' => 'POST'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        
+        $this->assertEqual($response->code, 411);
+        
+        $config = array(
+            'uri' => '/filesystemcollection/collectionIndex',
+            'method' => 'POST',
+            'data' => 'three'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        
+        $this->assertEqual($response->code, 201);
+        $this->assertEqual($response->headers['Location'], '/filesystemcollection/collection/3');
+        
+        $config = array(
+            'uri' => '/filesystemcollection/collectionIndex'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        
+        $this->assertEqual($response->code, 200);
+        $this->assertEqual($response->body, '<ul><li><a href="/filesystemcollection/collection/1">1</a></li><li><a href="/filesystemcollection/collection/2">2</a></li><li><a href="/filesystemcollection/collection/3">3</a></li></ul>');
+        
+    }
+    
+}
+
+
+/* Test resource definitions */
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /filesystemcollection/collection/.*
+ */
+class TestFileSystemCollectionItem extends FilesystemResource {
+    
+    function __construct() {
+        
+        $this->path = sys_get_temp_dir();
+        $this->uriStub = '/filesystemcollection';
+        
+    }
+    
+}
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /filesystemcollection/collectionIndex
+ */
+class TestFileSystemCollection extends FilesystemCollection {
+    
+    function __construct() {
+        
+        $this->path = sys_get_temp_dir();
+        $this->uriStub = '/filesystemcollection';
+        $this->collection = sys_get_temp_dir().'/collection';
+        
+    }
+    
+}
+
Index: trunk/prototype/library/tonic/tests/filesystem.php
===================================================================
--- trunk/prototype/library/tonic/tests/filesystem.php	(revision 6528)
+++ trunk/prototype/library/tonic/tests/filesystem.php	(revision 6528)
@@ -0,0 +1,149 @@
+<?php
+
+require_once('../lib/tonic.php');
+require_once('../examples/filesystem/filesystem.php');
+
+/**
+ * @namespace Tonic\Tests
+ */
+class FilesystemTester extends UnitTestCase {
+    
+    function testReadFile() {
+        
+        $testFilename = sys_get_temp_dir().DIRECTORY_SEPARATOR.'tonicFilesystemTest';
+        file_put_contents($testFilename, 'test');
+        
+        $config = array(
+            'uri' => '/filesystem/one/tonicFilesystemTest'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        
+        $this->assertEqual($response->code, 200);
+        $this->assertEqual($response->body, 'test');
+        
+        unlink($testFilename);
+    }
+    
+    function testReadDefaultDocument() {
+        
+        $testFilename = sys_get_temp_dir().DIRECTORY_SEPARATOR.'default.html';
+        file_put_contents($testFilename, 'test');
+        
+        $config = array(
+            'uri' => '/filesystem/one/'
+        );
+        
+        $request = new Request($config);
+        
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        
+        $this->assertEqual($response->code, 200);
+        $this->assertEqual($response->body, 'test');
+        
+        unlink($testFilename);
+        
+    }
+    
+    function testReadNoResource() {
+        
+        $config = array(
+            'uri' => '/filesystem/one/tonicFilesystemTestDoesntExist'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        
+        $this->assertEqual($response->code, 404);
+        
+    }
+    
+    function testCreateResource() {
+        
+        $testFilename = sys_get_temp_dir().DIRECTORY_SEPARATOR.'tonicFilesystemTest';
+        
+        $config = array(
+            'uri' => '/filesystem/one/tonicFilesystemTest',
+            'method' => 'PUT'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        
+        $this->assertEqual($response->code, 411);
+        
+        $config['data'] = 'test';
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        
+        $this->assertEqual($response->code, 204);
+        $this->assertTrue(file_exists($testFilename));
+        $this->assertEqual(file_get_contents($testFilename), 'test');
+        
+        unlink($testFilename);
+        
+    }
+    
+    function testDeleteResource() {
+        
+        $testFilename = sys_get_temp_dir().DIRECTORY_SEPARATOR.'tonicFilesystemTest';
+        file_put_contents($testFilename, 'test');
+        
+        $config = array(
+            'uri' => '/filesystem/one/tonicFilesystemTest',
+            'method' => 'DELETE'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        
+        $this->assertEqual($response->code, 204);
+        $this->assertFalse(file_exists($testFilename));
+        
+    }
+    
+    function testDeleteNoResource() {
+        
+        $config = array(
+            'uri' => '/filesystem/one/tonicFilesystemTestDoesntExist',
+            'method' => 'DELETE'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        
+        $this->assertEqual($response->code, 404);
+        
+    }
+    
+}
+
+
+/* Test resource definitions */
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /filesystem/one
+ * @uri /filesystem/one/.*
+ */
+class TestFileSystem extends FilesystemResource {
+    
+    function __construct($parameters) {
+        
+        parent::__construct($parameters);
+        $this->path = sys_get_temp_dir().DIRECTORY_SEPARATOR;
+        $this->uriStub = '/filesystem/one/';
+        
+    }
+    
+}
+
Index: trunk/prototype/library/tonic/tests/resource.php
===================================================================
--- trunk/prototype/library/tonic/tests/resource.php	(revision 6528)
+++ trunk/prototype/library/tonic/tests/resource.php	(revision 6528)
@@ -0,0 +1,54 @@
+<?php
+
+require_once('def/resource.php');
+
+/**
+ * @namespace Tonic\Tests
+ */
+class ResourceTester extends UnitTestCase {
+    
+    function testFunctioningResourceExec() {
+        
+        $config = array(
+            'uri' => '/resourcetest/one'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        
+        $this->assertEqual($response->code, '200');
+        $this->assertEqual($response->body, 'test');
+        
+    }
+    
+    function testNoParametersArgumentToResourceConstructor() {
+        
+        $config = array(
+            'uri' => '/resourcetest/badconstructor'
+        );
+        
+        $this->expectError(new PatternExpectation('/Missing argument 1 for Resource::__construct/'));
+        $this->expectError(new PatternExpectation('/Undefined variable: parameters/'));
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+    }
+    
+    function testMethodDoesNotReturnResponseObject() {
+        
+        $config = array(
+            'uri' => '/resourcetest/badmethodresponse'
+        );
+        
+        $this->expectException(new PatternExpectation('/Method GET of (.+\\\\)?TestBadMethodResponse did not return a Response object/'));
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        $response = $resource->exec($request);
+        
+    }
+
+}
+
Index: trunk/prototype/library/tonic/tests/def/response.php
===================================================================
--- trunk/prototype/library/tonic/tests/def/response.php	(revision 6528)
+++ trunk/prototype/library/tonic/tests/def/response.php	(revision 6528)
@@ -0,0 +1,20 @@
+<?php
+
+/* Test resource definitions */
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /responsetest
+ */
+class TestResponse extends Resource {
+    
+    function get($request) {
+        
+        $response = new Response($request);
+        $response->body = 'test';
+        return $response;
+        
+    }
+    
+}
+
Index: trunk/prototype/library/tonic/tests/def/resource.php
===================================================================
--- trunk/prototype/library/tonic/tests/def/resource.php	(revision 6528)
+++ trunk/prototype/library/tonic/tests/def/resource.php	(revision 6528)
@@ -0,0 +1,47 @@
+<?php
+
+/* Test resource definitions */
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /resourcetest/one
+ */
+class TestResource extends Resource {
+    
+    function get($request) {
+        
+        $response = new Response($request);
+        $response->body = 'test';
+        return $response;
+        
+    }
+    
+}
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /resourcetest/badconstructor
+ */
+class TestBadResourceConstructor extends Resource {
+    
+    function __construct() {
+        
+        parent::__construct();
+        
+    }
+    
+}
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /resourcetest/badmethodresponse
+ */
+class TestBadMethodResponse extends Resource {
+    
+    function get($request) {
+        
+        return TRUE;
+        
+    }
+    
+}
Index: trunk/prototype/library/tonic/tests/def/Autoload.php
===================================================================
--- trunk/prototype/library/tonic/tests/def/Autoload.php	(revision 6528)
+++ trunk/prototype/library/tonic/tests/def/Autoload.php	(revision 6528)
@@ -0,0 +1,8 @@
+<?php
+
+/**
+ * @namespace Tonic\Tests
+ */
+class Autoload extends Resource {
+
+}
Index: trunk/prototype/library/tonic/tests/def/request.php
===================================================================
--- trunk/prototype/library/tonic/tests/def/request.php	(revision 6528)
+++ trunk/prototype/library/tonic/tests/def/request.php	(revision 6528)
@@ -0,0 +1,135 @@
+<?php
+
+/* Test resource definitions */
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /requesttest/one
+ * @uri /requesttest/three/(.+)/four 12
+ */
+class NewResource extends Resource {
+
+}
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /requesttest/one/two
+ */
+class ChildResource extends NewResource {
+
+}
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /requesttest/railsstyle/:param/:param2
+ * @uri /requesttest/uritemplatestyle/{param}/{param2}
+ */
+class TwoUriParams extends Resource {
+
+    var $receivedParams;
+    
+    function get($request, $param, $param2) {
+        $this->receivedParams = array(
+            'param' => $param,
+            'param2' => $param2
+        );
+        return new Response($request);
+    }
+    
+}
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /requesttest/railsmixedstyle/{param}/(.+)/{param2}/(.+)
+ * @uri /requesttest/uritemplatemixedstyle/{param}/(.+)/{param2}/(.+)
+ * @uri /requesttest/mixedstyle/:param/(.+)/{param2}/(.+)
+ */
+class FourUriParams extends Resource {
+    
+    var $receivedParams;
+    
+    function get($request, $something, $otherthing, $param, $param2) {
+        $this->receivedParams = array(
+            'param' => $param,
+            'param2' => $param2,
+            'something' => $something,
+            'otherthing' => $otherthing
+        );
+        return new Response($request);
+    }
+    
+}
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /requesttest/trailingslashurl/
+ */
+class TrailingSlashUrl extends Resource {
+
+}
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /requesttest/optional(?:/param1/([^/]*))?
+ * @uri /requesttest/optional(?:/([a-z]+))?
+ */
+class OptionalParams extends Resource {
+    
+}
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /requesttest/squiggly/(\d+-[0-9a-f]{8}-[0-9a-f]{6})
+ * @uri /requesttest/noncapture/(?:something)?
+ */
+class SquigglyRegexResource extends Resource {
+
+}
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /requesttest/httpmethods
+ */
+class MethodTestResource extends Resource {
+    
+    function options($request) {
+        return new Response($request);
+    }
+    
+    function woot($request) {
+        return new Response($request);
+    }
+    
+}
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /requesttest/priority 1
+  */
+class PriorityTestLessImportantResource extends Resource {
+
+}
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /requesttest/priority 2
+  */
+class PriorityTestMoreImportantResource extends Resource {
+
+}
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /requesttest/differenturi(priority) 1
+  */
+class PriorityTestDifferntURILessImportantResource extends Resource {
+
+}
+
+/**
+ * @namespace Tonic\Tests
+ * @uri /requesttest/differenturipriority 2
+  */
+class PriorityTestDifferntURIMoreImportantResource extends Resource {
+
+}
Index: trunk/prototype/library/tonic/tests/request.php
===================================================================
--- trunk/prototype/library/tonic/tests/request.php	(revision 6528)
+++ trunk/prototype/library/tonic/tests/request.php	(revision 6528)
@@ -0,0 +1,826 @@
+<?php
+
+require_once('def/request.php');
+
+/**
+ * @namespace Tonic\Tests
+ */
+class RequestTester extends UnitTestCase {
+    
+    function testRequestURI() {
+        
+        $config = array(
+            'uri' => '/requesttest/one/two'
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->uri, $config['uri']);
+        
+        $_SERVER['REQUEST_URI'] = '/requesttest/one/two';
+        $request = new Request();
+        $this->assertEqual($request->uri, $config['uri']);
+        
+    }
+    
+    function testRequestBaseUri() {
+        
+        $config = array(
+            'baseUri' => '/some/sub/dir'
+        );
+        
+        $request = new Request($config);
+        
+        $this->assertEqual($request->baseUri, $config['baseUri']);
+        
+    }
+    
+    function testAssignMimetypes() {
+        
+        $config = array(
+            'mimetypes' => array(
+                'some' => 'text/something'
+            )
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->mimetypes['some'], 'text/something');
+        
+    }
+    
+    function testGetRequestMethod() {
+        
+        $config = array();
+        
+        $request = new Request($config);
+        
+        $this->assertEqual($request->method, 'GET');
+        
+    }
+    
+    function testPostRequestMethodWithData() {
+        
+        $config = array(
+            'method' => 'post',
+            'data' => 'some data'
+        );
+        
+        $request = new Request($config);
+        
+        $this->assertEqual($request->method, 'POST');
+        $this->assertEqual($request->data, 'some data');
+        
+    }
+    
+    function testPutRequestMethodWithData() {
+        
+        $config = array(
+            'method' => 'put',
+            'data' => 'some data'
+        );
+        
+        $request = new Request($config);
+        
+        $this->assertEqual($request->method, 'PUT');
+        $this->assertEqual($request->data, 'some data');
+        
+    }
+    
+    function testConnegOnBareURI() {
+        
+        $config = array(
+            'uri' => '/requesttest/one/two',
+            'accept' => '',
+            'acceptLang' => ''
+        );
+        
+        $request = new Request($config);
+        $this->assertEqual($request->negotiatedUris, array(
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->formatNegotiatedUris, array(
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->languageNegotiatedUris, array(
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->uri, '/requesttest/one/two');
+    
+    }
+    
+    function testConnegOnExtensionURI() {
+        
+        $config = array(
+            'uri' => '/requesttest/one/two.html',
+            'accept' => '',
+            'acceptLang' => ''
+        );
+        
+        $request = new Request($config);
+        $this->assertEqual($request->negotiatedUris, array(
+            '/requesttest/one/two.html',
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->formatNegotiatedUris, array(
+            '/requesttest/one/two.html',
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->languageNegotiatedUris, array(
+            '/requesttest/one/two.html',
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->uri, '/requesttest/one/two');
+        
+    }
+    
+    function testConnegOnBareURIWithAccept() {
+        
+        $config = array(
+            'uri' => '/requesttest/one/two',
+            'accept' => 'image/png;q=0.5,text/html',
+            'acceptLang' => ''
+        );
+        
+        $request = new Request($config);
+        $this->assertEqual($request->negotiatedUris, array(
+            '/requesttest/one/two.html',
+            '/requesttest/one/two.png',
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->formatNegotiatedUris, array(
+            '/requesttest/one/two.html',
+            '/requesttest/one/two.png',
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->languageNegotiatedUris, array(
+            '/requesttest/one/two'
+        ));
+        
+    }
+    
+    function testConnegOnExtensionURIWithAccept() {
+        
+        $config = array(
+            'uri' => '/requesttest/one/two.html',
+            'accept' => 'image/png;q=0.5,text/html',
+            'acceptLang' => ''
+        );
+        
+        $request = new Request($config);
+        $this->assertEqual($request->negotiatedUris, array(
+            '/requesttest/one/two.html',
+            '/requesttest/one/two.png.html',
+            '/requesttest/one/two.png',
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->formatNegotiatedUris, array(
+            '/requesttest/one/two.html',
+            '/requesttest/one/two.png',
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->languageNegotiatedUris, array(
+            '/requesttest/one/two.html',
+            '/requesttest/one/two'
+        ));
+        
+    }
+    
+    function testConnegOnBareURIWithAcceptLang() {
+        
+        $config = array(
+            'uri' => '/requesttest/one/two',
+            'accept' => '',
+            'acceptLang' => 'fr;q=0.5,en'
+        );
+        
+        $request = new Request($config);
+        $this->assertEqual($request->negotiatedUris, array(
+            '/requesttest/one/two.en',
+            '/requesttest/one/two.fr',
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->formatNegotiatedUris, array(
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->languageNegotiatedUris, array(
+            '/requesttest/one/two.en',
+            '/requesttest/one/two.fr',
+            '/requesttest/one/two'
+        ));
+        
+    }
+    
+    function testConnegOnExtensionURIWithAcceptLang() {
+        
+        $config = array(
+            'uri' => '/requesttest/one/two.html',
+            'accept' => '',
+            'acceptLang' => 'fr;q=0.5,en'
+        );
+        
+        $request = new Request($config);
+        $this->assertEqual($request->negotiatedUris, array(
+            '/requesttest/one/two.html.en',
+            '/requesttest/one/two.html.fr',
+            '/requesttest/one/two.html',
+            '/requesttest/one/two.en',
+            '/requesttest/one/two.fr',
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->formatNegotiatedUris, array(
+            '/requesttest/one/two.html',
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->languageNegotiatedUris, array(
+            '/requesttest/one/two.html',
+            '/requesttest/one/two.en',
+            '/requesttest/one/two.fr',
+            '/requesttest/one/two'
+        ));
+        
+    }
+    
+    function testConnegOnBareURIWithAcceptAndAcceptLang() {
+        
+        $config = array(
+            'uri' => '/requesttest/one/two',
+            'accept' => 'image/png;q=0.5,text/html',
+            'acceptLang' => 'fr;q=0.5,en'
+        );
+        
+        $request = new Request($config);
+        $this->assertEqual($request->negotiatedUris, array(
+            '/requesttest/one/two.html.en',
+            '/requesttest/one/two.html.fr',
+            '/requesttest/one/two.html',
+            '/requesttest/one/two.png.en',
+            '/requesttest/one/two.png.fr',
+            '/requesttest/one/two.png',
+            '/requesttest/one/two.en',
+            '/requesttest/one/two.fr',
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->formatNegotiatedUris, array(
+            '/requesttest/one/two.html',
+            '/requesttest/one/two.png',
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->languageNegotiatedUris, array(
+            '/requesttest/one/two.en',
+            '/requesttest/one/two.fr',
+            '/requesttest/one/two'
+        ));
+        
+    }
+    
+    function testConnegOnExtensionURIWithAcceptAndAcceptLang() {
+        
+        $config = array(
+            'uri' => '/requesttest/one/two.html',
+            'accept' => 'image/png;q=0.5,text/html',
+            'acceptLang' => 'fr;q=0.5,en'
+        );
+        
+        $request = new Request($config);
+        $this->assertEqual($request->negotiatedUris, array(
+            '/requesttest/one/two.html.en',
+            '/requesttest/one/two.html.fr',
+            '/requesttest/one/two.html',
+            '/requesttest/one/two.png.html',
+            '/requesttest/one/two.png.en',
+            '/requesttest/one/two.png.fr',
+            '/requesttest/one/two.png',
+            '/requesttest/one/two.en',
+            '/requesttest/one/two.fr',
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->formatNegotiatedUris, array(
+            '/requesttest/one/two.html',
+            '/requesttest/one/two.png',
+            '/requesttest/one/two'
+        ));
+        $this->assertEqual($request->languageNegotiatedUris, array(
+            '/requesttest/one/two.html',
+            '/requesttest/one/two.en',
+            '/requesttest/one/two.fr',
+            '/requesttest/one/two'
+        ));
+        
+    }
+    
+    function testResourceLoaderWithNoResources() {
+        
+        $config = array(
+            'uri' => '/three'
+        );
+        
+        $request = new Request($config);
+        
+        try {
+            $resource = $request->loadResource();
+            $this->fail('Expected ResponseException to be thrown');
+        } catch (ResponseException $e) {
+            $this->assertEqual($e->getCode(), 404);
+        }
+        
+    }
+    
+    function testResourceLoaderWithAResources() {
+        
+        $config = array(
+            'uri' => '/requesttest/one'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/NewResource/', get_class($resource));
+        
+    }
+    
+    function testResourceLoaderWithAChildResources() {
+        
+        $config = array(
+            'uri' => '/requesttest/one/two'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/ChildResource/', get_class($resource));
+        
+    }
+    
+    function testResourceLoaderWithRegexURIMatch() {
+        
+        $config = array(
+            'uri' => '/requesttest/three/something/four'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/NewResource/', get_class($resource));
+        $this->assertPattern('/0: something/', $resource);
+        
+    }
+    
+    function testIfMatch() {
+        
+        $config = array(
+            'uri' => '/requesttest/one',
+            'ifMatch' => '123123'
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->ifMatch, array('123123'));
+        
+        $config = array(
+            'uri' => '/requesttest/one',
+            'ifMatch' => '"123123"'
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->ifMatch, array('123123'));
+        
+        $config = array(
+            'uri' => '/requesttest/one',
+            'ifMatch' => '"123123","456456"'
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->ifMatch, array('123123', '456456'));
+        
+        $config = array(
+            'uri' => '/requesttest/one',
+            'ifMatch' => '"123123", "456456"'
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->ifMatch, array('123123', '456456'));
+        $this->assertTrue($request->ifMatch('123123'));
+        $this->assertFalse($request->ifMatch('123456'));
+        
+        $config = array(
+            'uri' => '/requesttest/one',
+            'ifMatch' => '*'
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->ifMatch, array('*'));
+        $this->assertTrue($request->ifMatch('123123'));
+        $this->assertTrue($request->ifMatch('123456'));
+        
+    }
+    
+    function testIfNoneMatch() {
+        
+        $config = array(
+            'uri' => '/requesttest/one',
+            'ifNoneMatch' => '123123'
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->ifNoneMatch, array('123123'));
+        
+        $config = array(
+            'uri' => '/requesttest/one',
+            'ifNoneMatch' => '"123123"'
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->ifNoneMatch, array('123123'));
+        
+        $config = array(
+            'uri' => '/requesttest/one',
+            'ifNoneMatch' => '"123123","456456"'
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->ifNoneMatch, array('123123', '456456'));
+        
+        $config = array(
+            'uri' => '/requesttest/one',
+            'ifNoneMatch' => '123123, 456456'
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->ifNoneMatch, array('123123', '456456'));
+        $this->assertTrue($request->ifNoneMatch('123123'));
+        $this->assertFalse($request->ifNoneMatch('123456'));
+        
+        $config = array(
+            'uri' => '/requesttest/one',
+            'ifNoneMatch' => '*'
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->ifNoneMatch, array('*'));
+        $this->assertFalse($request->ifNoneMatch('123123'));
+        $this->assertFalse($request->ifNoneMatch('123456'));
+        
+        $config = array(
+            'uri' => '/requesttest/one',
+            'ifNoneMatch' => '123123, 456456',
+            'ifMatch' => '*'
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->ifMatch, array('*'));
+        $this->assertTrue($request->ifMatch('123123'));
+        $this->assertTrue($request->ifMatch('123456'));
+        $this->assertFalse($request->ifNoneMatch('123123'));
+        $this->assertFalse($request->ifNoneMatch('123456'));
+        
+    }
+    
+    function test404NotFound() {
+        
+        $config = array(
+            'uri' => '/requesttest/notfound'
+        );
+        $request = new Request($config);
+        
+        try {
+            $resource = $request->loadResource();
+            $this->fail('Expected ResponseException to be thrown');
+        } catch (ResponseException $e) {
+            $this->assertEqual($e->getCode(), 404);
+        }
+        
+    }
+    
+    function testResourceDataLoading() {
+        
+        $config = array(
+            'uri' => '/requesttest/one/two'
+        );
+        
+        $request = new Request($config);
+        
+        $this->assertEqual($request->resources['/requesttest/one/two']['namespace'], 'Tonic\Tests');
+        $this->assertPattern('/ChildResource/', $request->resources['/requesttest/one/two']['class']);
+        $this->assertPattern('|def/request\.php|', $request->resources['/requesttest/one/two']['filename']);
+        $this->assertEqual($request->resources['/requesttest/one/two']['priority'], 0);
+        
+    }
+    
+    function testNamespaceMounting() {
+        
+        $config = array(
+            'uri' => '/foo/bar/requesttest/one',
+            'mount' => array(
+                'Tonic\Tests' => '/foo/bar'
+            )
+        );
+        
+        $request = new Request($config);
+        
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/NewResource/', get_class($resource));
+        
+    }
+    
+    function testURITemplateStyleUriParameters() {
+        
+        $config = array(
+            'uri' => '/requesttest/uritemplatestyle/woo/yay'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/TwoUriParams/', get_class($resource));
+        $this->assertPattern('/param: woo/', $resource);
+        $this->assertPattern('/param2: yay/', $resource);
+        
+        $resource->exec($request);
+        $this->assertEqual($resource->receivedParams['param'], 'woo');
+        $this->assertEqual($resource->receivedParams['param2'], 'yay');
+        
+        
+        $config = array(
+            'uri' => '/requesttest/uritemplatemixedstyle/woo/yay/foo/bar'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/FourUriParams/', get_class($resource));
+        $this->assertPattern('/param: woo/', $resource);
+        $this->assertPattern('/1: yay/', $resource);
+        $this->assertPattern('/param2: foo/', $resource);
+        $this->assertPattern('/3: bar/', $resource);
+        
+        $resource->exec($request);
+        $this->assertEqual($resource->receivedParams['param'], 'woo');
+        $this->assertEqual($resource->receivedParams['something'], 'yay');
+        $this->assertEqual($resource->receivedParams['param2'], 'foo');
+        $this->assertEqual($resource->receivedParams['otherthing'], 'bar');
+    }
+    
+    function testRailsStyleUriParameters() {
+        
+        $config = array(
+            'uri' => '/requesttest/railsstyle/woo/yay'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/TwoUriParams/', get_class($resource));
+        $this->assertPattern('/param: woo/', $resource);
+        $this->assertPattern('/param2: yay/', $resource);
+        
+        $resource->exec($request);
+        $this->assertEqual($resource->receivedParams['param'], 'woo');
+        $this->assertEqual($resource->receivedParams['param2'], 'yay');
+        
+        
+        $config = array(
+            'uri' => '/requesttest/railsmixedstyle/woo/yay/foo/bar'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/FourUriParams/', get_class($resource));
+        $this->assertPattern('/param: woo/', $resource);
+        $this->assertPattern('/1: yay/', $resource);
+        $this->assertPattern('/param2: foo/', $resource);
+        $this->assertPattern('/3: bar/', $resource);
+        
+        $resource->exec($request);
+        $this->assertEqual($resource->receivedParams['param'], 'woo');
+        $this->assertEqual($resource->receivedParams['something'], 'yay');
+        $this->assertEqual($resource->receivedParams['param2'], 'foo');
+        $this->assertEqual($resource->receivedParams['otherthing'], 'bar');
+    }
+    
+    function testMixedStyleUriParameters() {
+        
+        $config = array(
+            'uri' => '/requesttest/mixedstyle/woo/yay/foo/bar'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/FourUriParams/', get_class($resource));
+        $this->assertPattern('/param: woo/', $resource);
+        $this->assertPattern('/1: yay/', $resource);
+        $this->assertPattern('/param2: foo/', $resource);
+        $this->assertPattern('/3: bar/', $resource);
+        
+        $resource->exec($request);
+        $this->assertEqual($resource->receivedParams['param'], 'woo');
+        $this->assertEqual($resource->receivedParams['something'], 'yay');
+        $this->assertEqual($resource->receivedParams['param2'], 'foo');
+        $this->assertEqual($resource->receivedParams['otherthing'], 'bar');
+    }
+    
+    function testAutoloadingOfResourcesWithoutAutoloadFunction() {
+        
+        $config = array(
+            'uri' => '/requesttest/autoload',
+            'autoload' => array(
+                '/requesttest/autoload' => 'Autoload'
+            )
+        );
+        
+        $request = new Request($config);
+        
+        $this->expectException(new PatternExpectation('/Unable to load resource/'));
+        $resource = $request->loadResource();
+        
+    }
+    
+    function testAutoloadingOfResources() {
+        
+        $config = array(
+            'uri' => '/requesttest/autoload',
+            'autoload' => array(
+                '/requesttest/autoload' => 'Autoload'
+            )
+        );
+        
+        function __autoload($className) {
+            if (file_exists('def/'.$className.'.php')) {
+                require_once 'def/'.$className.'.php';
+            }
+        }
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/Autoload/', get_class($resource));
+        $this->assertPattern('/Class: Autoload/', $request);
+        $this->assertPattern('|File: '.dirname(__FILE__).DIRECTORY_SEPARATOR.'def/Autoload.php|', $request);
+        
+    }
+    
+    function testTrailingSlashRemoval() {
+        
+        $config = array(
+            'uri' => '/requesttest/trailingslashurl/'
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->uri, substr($config['uri'], 0, -1));
+        
+    }
+    
+    function testRequestRootURI() {
+        
+        $config = array(
+            'uri' => '/'
+        );
+        $request = new Request($config);
+        $this->assertEqual($request->uri, $config['uri']);
+        
+        $_SERVER['REQUEST_URI'] = '/';
+        $request = new Request();
+        $this->assertEqual($request->uri, $config['uri']);
+        
+    }
+    
+    function testMethodNotAllowed() {
+        
+        $config = array(
+            'uri' => '/requesttest/one',
+            'method' => 'PUT'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        try {
+            $response = $resource->exec($request);
+            $this->fail('Expected ResponseException to be thrown');
+        } catch (ResponseException $e) {
+            $this->assertEqual($e->getCode(), 405);
+        }
+        
+    }
+    
+    function testOptionalRegexURIParams() {
+        
+        $config = array(
+            'uri' => '/requesttest/optional'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/OptionalParams/', get_class($resource));
+        $this->assertNoPattern('/0: /', $resource);
+        
+        $config = array(
+            'uri' => '/requesttest/optional/param1/woo'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/OptionalParams/', get_class($resource));
+        $this->assertPattern('/0: woo/', $resource);
+        
+        $config = array(
+            'uri' => '/requesttest/optional/something'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/OptionalParams/', get_class($resource));
+        $this->assertPattern('/0: something/', $resource);
+        
+    }
+    
+    function testResourceLoaderWithSquigglyBraceRegexURIMatch() {
+        
+        $config = array(
+            'uri' => '/requesttest/squiggly/9999-aaaaaaaa-bbbbbb'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/SquigglyRegexResource/', get_class($resource));
+        
+        $config = array(
+            'uri' => '/requesttest/squiggly/9999-aaa-bbb'
+        );
+        
+        $request = new Request($config);
+        
+        try {
+            $resource = $request->loadResource();
+            $this->fail('Expected ResponseException to be thrown');
+        } catch (ResponseException $e) {
+            $this->assertTrue(TRUE);
+        }
+        
+    }
+    
+    function testResourceLoaderWithNonCapturingGroupRegexURIMatch() {
+        
+        $config = array(
+            'uri' => '/requesttest/noncapture/something'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertPattern('/SquigglyRegexResource/', get_class($resource));
+        
+    }
+    
+    function testAllowedHTTPMethods() {
+        
+        $config = array(
+            'uri' => '/requesttest/httpmethods',
+            'method' => 'OPTIONS'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        try {
+            $response = $resource->exec($request);
+            $this->assertTrue(TRUE);
+        } catch (ResponseException $e) {
+            $this->fail('Did not expect ResponseException to be thrown');
+        }
+        
+        $config = array(
+            'uri' => '/requesttest/httpmethods',
+            'method' => 'WOOT'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        try {
+            $response = $resource->exec($request);
+            $this->fail('Expected ResponseException to be thrown');
+        } catch (ResponseException $e) {
+            $this->assertTrue(TRUE);
+        }
+        
+    }
+    
+    function testURIPriority() {
+        
+        // with same @uri
+        
+        $config = array(
+            'uri' => '/requesttest/priority'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertEqual(get_class($resource), 'PriorityTestMoreImportantResource');
+        
+        // with different @uri
+        
+        $config = array(
+            'uri' => '/requesttest/differenturipriority'
+        );
+        
+        $request = new Request($config);
+        $resource = $request->loadResource();
+        
+        $this->assertEqual(get_class($resource), 'PriorityTestDifferntURIMoreImportantResource');
+        
+    }
+    
+}
+
Index: trunk/prototype/library/tonic/.travis.yml
===================================================================
--- trunk/prototype/library/tonic/.travis.yml	(revision 6528)
+++ trunk/prototype/library/tonic/.travis.yml	(revision 6528)
@@ -0,0 +1,5 @@
+language: php
+php:
+  - 5.2
+  - 5.3
+  - 5.4
Index: trunk/prototype/library/tonic/lib/tonic.php
===================================================================
--- trunk/prototype/library/tonic/lib/tonic.php	(revision 6528)
+++ trunk/prototype/library/tonic/lib/tonic.php	(revision 6528)
@@ -0,0 +1,889 @@
+<?php
+
+use prototype\api\Config as Config;
+
+/*
+ * This file is part of the Tonic.
+ * (c) Paul James <paul@peej.co.uk>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/* Turn on namespace in PHP5.3 if you have namespace collisions from the Tonic classnames
+namespace Tonic;
+use \ReflectionClass as ReflectionClass;
+use \ReflectionMethod as ReflectionMethod;
+use \Exception as Exception;
+//*/
+
+/**
+ * Model the data of the incoming HTTP request
+ * @namespace Tonic\Lib
+ */
+class Request {
+    
+    /**
+     * The requested URI
+     * @var str
+     */
+    public $uri;
+    
+    /**
+     * The URI where the front controller is positioned in the server URI-space
+     * @var str
+     */
+    public $baseUri = '';
+    
+    /**
+     * Array of possible URIs based upon accept and accept-language request headers in order of preference
+     * @var str[]
+     */
+    public $negotiatedUris = array();
+    
+    /**
+     * Array of possible URIs based upon accept request headers in order of preference
+     * @var str[]
+     */
+    public $formatNegotiatedUris = array();
+    
+    /**
+     * Array of possible URIs based upon accept-language request headers in order of preference
+     * @var str[]
+     */
+    public $languageNegotiatedUris = array();
+    
+    /**
+     * Array of accept headers in order of preference
+     * @var str[][]
+     */
+    public $accept = array();
+    
+    /**
+     * Array of accept-language headers in order of preference
+     * @var str[][]
+     */
+    public $acceptLang = array();
+    
+    /**
+     * Array of accept-encoding headers in order of preference
+     * @var str[]
+     */
+    public $acceptEncoding = array();
+    
+    /**
+     * Map of file/URI extensions to mimetypes
+     * @var str[]
+     */
+    public $mimetypes = array(
+        'html' => 'text/html',
+        'txt' => 'text/plain',
+        'php' => 'application/php',
+        'css' => 'text/css',
+        'js' => 'application/javascript',
+        'json' => 'application/json',
+        'xml' => 'application/xml',
+        'rss' => 'application/rss+xml',
+        'atom' => 'application/atom+xml',
+        'gz' => 'application/x-gzip',
+        'tar' => 'application/x-tar',
+        'zip' => 'application/zip',
+        'gif' => 'image/gif',
+        'png' => 'image/png',
+        'jpg' => 'image/jpeg',
+        'ico' => 'image/x-icon',
+        'swf' => 'application/x-shockwave-flash',
+        'flv' => 'video/x-flv',
+        'avi' => 'video/mpeg',
+        'mpeg' => 'video/mpeg',
+        'mpg' => 'video/mpeg',
+        'mov' => 'video/quicktime',
+        'mp3' => 'audio/mpeg'
+    );
+    
+    /**
+     * Supported HTTP methods
+     * @var str[]
+     */
+    public $HTTPMethods = array(
+        'GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'
+    );
+    
+    /**
+     * Allowed resource methods
+     * @var str[]
+     */
+    public $allowedMethods = array();
+    
+    /**
+     * HTTP request method of incoming request
+     * @var str
+     */
+    public $method = 'GET';
+    
+    /**
+     * Body data of incoming request
+     * @var str
+     */
+    public $data;
+    
+    /**
+     * Body data of incoming request
+     * @var array
+     */
+    public $dataDecoded;
+    
+    /**
+     * Array of if-match etags
+     * @var str[]
+     */
+    public $ifMatch = array();
+    
+    /**
+     * Array of if-none-match etags
+     * @var str[]
+     */
+    public $ifNoneMatch = array();
+    
+    /**
+     * The resource classes loaded and how they are wired to URIs
+     * @var str[]
+     */
+    public $resources = array();
+    
+    /**
+     * A list of URL to namespace/package mappings for routing requests to a
+     * group of resources that are wired into a different URL-space
+     * @var str[]
+     */
+    public $mounts = array();
+    
+    /**
+     * Set a default configuration option
+     */
+    protected function getConfig($config, $configVar, $serverVar = NULL, $default = NULL) {
+        if (isset($config[$configVar])) {
+            return $config[$configVar];
+        } elseif (isset($_SERVER[$serverVar]) && $_SERVER[$serverVar] != '') {
+            return $_SERVER[$serverVar];
+        } else {
+            return $default;
+        }
+    }
+    
+    /**
+     * Create a request object using the given configuration options.
+     *
+     * The configuration options array can contain the following:
+     *
+     * <dl>
+     * <dt>uri</dt> <dd>The URI of the request</dd>
+     * <dt>method</dt> <dd>The HTTP method of the request</dd>
+     * <dt>data</dt> <dd>The body data of the request</dd>
+     * <dt>accept</dt> <dd>An accept header</dd>
+     * <dt>acceptLang</dt> <dd>An accept-language header</dd>
+     * <dt>acceptEncoding</dt> <dd>An accept-encoding header</dd>
+     * <dt>ifMatch</dt> <dd>An if-match header</dd>
+     * <dt>ifNoneMatch</dt> <dd>An if-none-match header</dd>
+     * <dt>mimetypes</dt> <dd>A map of file/URI extenstions to mimetypes, these
+     * will be added to the default map of mimetypes</dd>
+     * <dt>baseUri</dt> <dd>The base relative URI to use when dispatcher isn't
+     * at the root of the domain. Do not put a trailing slash</dd>
+     * <dt>mounts</dt> <dd>an array of namespace to baseUri prefix mappings</dd>
+     * <dt>HTTPMethods</dt> <dd>an array of HTTP methods to support</dd>
+     * </dl>
+     *
+     * @param mixed[] config Configuration options
+     */
+    function __construct($config = array()) {
+        
+        // set defaults
+        $config['uri'] = parse_url($this->getConfig($config, 'uri', 'REQUEST_URI'), PHP_URL_PATH);
+        $config['baseUri'] = $this->getConfig($config, 'baseUri', '');
+        $config['accept'] = $this->getConfig($config, 'accept', 'HTTP_ACCEPT');
+        $config['acceptLang'] = $this->getConfig($config, 'acceptLang', 'HTTP_ACCEPT_LANGUAGE');
+        $config['acceptEncoding'] = $this->getConfig($config, 'acceptEncoding', 'HTTP_ACCEPT_ENCODING');
+        $config['ifMatch'] = $this->getConfig($config, 'ifMatch', 'HTTP_IF_MATCH');
+        $config['ifNoneMatch'] = $this->getConfig($config, 'ifNoneMatch', 'HTTP_IF_NONE_MATCH');
+        
+        if (isset($config['mimetypes']) && is_array($config['mimetypes'])) {
+            foreach ($config['mimetypes'] as $ext => $mimetype) {
+                $this->mimetypes[$ext] = $mimetype;
+            }
+        }
+        
+        // set baseUri
+        $this->baseUri = $config['baseUri'];
+        
+        // get request URI
+        $parts = explode('/', $config['uri']);
+        $lastPart = array_pop($parts);
+        $this->uri = join('/', $parts);
+        
+        $parts = explode('.', $lastPart);
+        $this->uri .= '/'.$parts[0];
+        
+        if ($this->uri != '/' && substr($this->uri, -1, 1) == '/') { // remove trailing slash problem
+            $this->uri = substr($this->uri, 0, -1);
+        }
+        
+        array_shift($parts);
+        foreach ($parts as $part) {
+            $this->accept[10][] = $part;
+            $this->acceptLang[10][] = $part;
+        }
+        
+        // sort accept headers
+        $accept = explode(',', strtolower($config['accept']));
+        foreach ($accept as $mimetype) {
+            $parts = explode(';q=', $mimetype);
+            if (isset($parts) && isset($parts[1]) && $parts[1]) {
+                $num = $parts[1] * 10;
+            } else {
+                $num = 10;
+            }
+            $key = array_search($parts[0], $this->mimetypes);
+            if ($key) {
+                $this->accept[$num][] = $key;
+            }
+        }
+        krsort($this->accept);
+        
+        // sort lang accept headers
+        $accept = explode(',', strtolower($config['acceptLang']));
+        foreach ($accept as $mimetype) {
+            $parts = explode(';q=', $mimetype);
+            if (isset($parts) && isset($parts[1]) && $parts[1]) {
+                $num = $parts[1] * 10;
+            } else {
+                $num = 10;
+            }
+            $this->acceptLang[$num][] = $parts[0];
+        }
+        krsort($this->acceptLang);
+        
+        // get encoding accept headers
+        if ($config['acceptEncoding']) {
+            foreach (explode(',', $config['acceptEncoding']) as $key => $accept) {
+                $this->acceptEncoding[$key] = trim($accept);
+            }
+        }
+        
+        // create negotiated URI lists from accept headers and request URI
+        foreach ($this->accept as $typeOrder) {
+            foreach ($typeOrder as $type) {
+                if ($type) {
+                    foreach ($this->acceptLang as $langOrder) {
+                        foreach ($langOrder as $lang) {
+                            if ($lang && $lang != $type) {
+                                $this->negotiatedUris[] = $this->uri.'.'.$type.'.'.$lang;
+                            }
+                        }
+                    }
+                    $this->negotiatedUris[] = $this->uri.'.'.$type;
+                    $this->formatNegotiatedUris[] = $this->uri.'.'.$type;
+                }
+            }
+        }
+        foreach ($this->acceptLang as $langOrder) {
+            foreach ($langOrder as $lang) {
+                if ($lang) {
+                    $this->negotiatedUris[] = $this->uri.'.'.$lang;
+                    $this->languageNegotiatedUris[] = $this->uri.'.'.$lang;
+                }
+            }
+        }
+        $this->negotiatedUris[] = $this->uri;
+        $this->formatNegotiatedUris[] = $this->uri;
+        $this->languageNegotiatedUris[] = $this->uri;
+        
+        $this->negotiatedUris = array_values(array_unique($this->negotiatedUris));
+        $this->formatNegotiatedUris = array_values(array_unique($this->formatNegotiatedUris));
+        $this->languageNegotiatedUris = array_values(array_unique($this->languageNegotiatedUris));
+        
+        $this->HTTPMethods = $this->getConfig($config, 'HTTPMethods', NULL, $this->HTTPMethods);
+        
+        // get HTTP method
+        $this->method = strtoupper($this->getConfig($config, 'method', 'REQUEST_METHOD', $this->method));
+        
+	// get HTTP request data
+	$this->data = $this->getConfig($config, 'data', NULL, file_get_contents("php://input"));
+
+        // get HTTP request dataDecode into array
+	$this->dataDecoded  = $this->__decodeData( $this->data );
+	
+        // conditional requests
+        if ($config['ifMatch']) {
+            $ifMatch = explode(',', $config['ifMatch']);
+            foreach ($ifMatch as $etag) {
+                $this->ifMatch[] = trim($etag, '" ');
+            }
+        }
+        if ($config['ifNoneMatch']) {
+            $ifNoneMatch = explode(',', $config['ifNoneMatch']);
+            foreach ($ifNoneMatch as $etag) {
+                $this->ifNoneMatch[] = trim($etag, '" ');
+            }
+        }
+        
+        // mounts
+        if (isset($config['mount']) && is_array($config['mount'])) {
+            $this->mounts = $config['mount'];
+        }
+        
+        // prime named resources for autoloading
+        if (isset($config['autoload']) && is_array($config['autoload'])) {
+            foreach ($config['autoload'] as $uri => $className) {
+                $this->resources[$uri] = array(
+                    'class' => $className,
+                    'loaded' => FALSE
+                );
+            }
+        }
+        
+        // load definitions of already loaded resource classes
+        $resourceClassName = class_exists('Tonic\\Resource', false) ? 'Tonic\\Resource' : 'Resource';
+        foreach (get_declared_classes() as $className) {
+            if (is_subclass_of($className, $resourceClassName)) {
+                
+                $resourceDetails = $this->getResourceClassDetails($className);
+                
+                preg_match_all('/@uri\s+([^\s]+)(?:\s([0-9]+))?/', $resourceDetails['comment'], $annotations);
+                if (isset($annotations[1]) && $annotations[1]) {
+                    $uris = $annotations[1];
+                } else {
+                    $uris = array();
+                }
+                
+                foreach ($uris as $index => $uri) {
+                    if ($uri != '/' && substr($uri, -1, 1) == '/') { // remove trailing slash problem
+                        $uri = substr($uri, 0, -1);
+                    }
+                    if (isset($annotations[2][$index]) && is_numeric($annotations[2][$index])) {
+                        $priority = intval($annotations[2][$index]);
+                    } else {
+                        $priority = 0;
+                    }
+                    if (
+                        !isset($this->resources[$resourceDetails['mountPoint'].$uri]) ||
+                        $this->resources[$resourceDetails['mountPoint'].$uri]['priority'] < $priority
+                    ) {
+                        $this->resources[$resourceDetails['mountPoint'].$uri] = array(
+                            'namespace' => $resourceDetails['namespaceName'],
+                            'class' => $resourceDetails['className'],
+                            'filename' => $resourceDetails['filename'],
+                            'line' => $resourceDetails['line'],
+                            'priority' => $priority,
+                            'loaded' => TRUE
+                        );
+                    }
+                }
+            }
+        }
+        
+    }
+    
+    /**
+     * Get the details of a Resource class by reflection
+     * @param str className
+     * @return str[]
+     */
+    protected function getResourceClassDetails($className) {
+        
+        $resourceReflector = new ReflectionClass($className);
+        $comment = $resourceReflector->getDocComment();
+        
+        $className = $resourceReflector->getName();
+        if (method_exists($resourceReflector, 'getNamespaceName')) {
+            $namespaceName = $resourceReflector->getNamespaceName();
+        } else {
+            // @codeCoverageIgnoreStart
+            $namespaceName = FALSE;
+            // @codeCoverageIgnoreEnd
+        }
+        
+        if (!$namespaceName) {
+            preg_match('/@(?:package|namespace)\s+([^\s]+)/', $comment, $package);
+            if (isset($package[1])) {
+                $namespaceName = $package[1];
+            }
+        }
+        
+        // adjust URI for mountpoint
+        if (isset($this->mounts[$namespaceName])) {
+            $mountPoint = $this->mounts[$namespaceName];
+        } else {
+            $mountPoint = '';
+        }
+        
+        return array(
+            'comment' => $comment,
+            'className' => $className,
+            'namespaceName' => $namespaceName,
+            'filename' => $resourceReflector->getFileName(),
+            'line' => $resourceReflector->getStartLine(),
+            'mountPoint' => $mountPoint
+        );
+    
+    }
+
+     /**
+     * Convert the string data into a array data
+     * @return array
+     * @codeCoverageIgnore
+     */
+    function __decodeData($data) {
+	$arrayData = array();
+	if($data){
+	    $arrayIndex = explode('&', urldecode($data));
+	    foreach($arrayIndex as $key => $value){
+		list($i,$val) = explode ('=', $value);
+		$arrayData[$i] = $val;
+	    }
+	}
+	return $arrayData;
+    }
+
+    /**
+     * Convert the object into a string suitable for printing
+     * @return str
+     * @codeCoverageIgnore
+     */
+    function __toString() {
+        $str = 'URI: '.$this->uri."\n";
+        $str .= 'Method: '.$this->method."\n";
+        if ($this->data) {
+            $str .= 'Data: '.$this->data."\n";
+        }
+        $str .= 'Acceptable Formats:';
+        foreach ($this->accept as $accept) {
+            foreach ($accept as $a) {
+                $str .= ' .'.$a;
+                if (isset($this->mimetypes[$a])) $str .= ' ('.$this->mimetypes[$a].')';
+            }
+        }
+        $str .= "\n";
+        $str .= 'Acceptable Languages:';
+        foreach ($this->acceptLang as $accept) {
+            foreach ($accept as $a) {
+                $str .= ' '.$a;
+            }
+        }
+        $str .= "\n";
+        $str .= 'Negotated URIs:'."\n";
+        foreach ($this->negotiatedUris as $uri) {
+            $str .= "\t".$uri."\n";
+        }
+        $str .= 'Format Negotated URIs:'."\n";
+        foreach ($this->formatNegotiatedUris as $uri) {
+            $str .= "\t".$uri."\n";
+        }
+        $str .= 'Language Negotated URIs:'."\n";
+        foreach ($this->languageNegotiatedUris as $uri) {
+            $str .= "\t".$uri."\n";
+        }
+        if ($this->ifMatch) {
+            $str .= 'If Match:';
+            foreach ($this->ifMatch as $etag) {
+                $str .= ' '.$etag;
+            }
+            $str .= "\n";
+        }
+        if ($this->ifNoneMatch) {
+            $str .= 'If None Match:';
+            foreach ($this->ifNoneMatch as $etag) {
+                $str .= ' '.$etag;
+            }
+            $str .= "\n";
+        }
+        $str .= 'Loaded Resources:'."\n";
+        foreach ($this->resources as $uri => $resource) {
+            $str .= "\t".$uri."\n";
+            if (isset($resource['namespace']) && $resource['namespace']) $str .= "\t\tNamespace: ".$resource['namespace']."\n";
+            $str .= "\t\tClass: ".$resource['class']."\n";
+            $str .= "\t\tFile: ".$resource['filename'];
+            if (isset($resource['line']) && $resource['line']) $str .= '#'.$resource['line'];
+            $str .= "\n";
+        }
+        return $str;
+    }
+    
+    /**
+     * Instantiate the resource class that matches the request URI the best
+     * @return Resource
+     * @throws ResponseException If the resource does not exist, a 404 exception is thrown
+     */
+    function loadResource() {
+        
+        $uriMatches = array();
+        foreach ($this->resources as $uri => $resource) {
+            
+            preg_match_all('#((?<!\?):[^/]+|{[^0-9][^}]*}|\(.+?\))#', $uri, $params, PREG_PATTERN_ORDER);
+            
+            $uri = $this->baseUri.$uri;
+            if ($uri != '/' && substr($uri, -1, 1) == '/') { // remove trailing slash problem
+                $uri = substr($uri, 0, -1);
+            }
+            $uriRegex = preg_replace('#((?<!\?):[^(/]+|{[^0-9][^}]*})#', '(.+)', $uri);
+            
+            if (preg_match('#^'.$uriRegex.'$#', $this->uri, $matches)) {
+                array_shift($matches);
+                
+                if (isset($params[1])) {
+                    foreach ($params[1] as $index => $param) {
+                        if (isset($matches[$index])) {
+                            if (substr($param, 0, 1) == ':') {
+                                $matches[substr($param, 1)] = $matches[$index];
+                                unset($matches[$index]);
+                            } elseif (substr($param, 0, 1) == '{' && substr($param, -1, 1) == '}') {
+                                $matches[substr($param, 1, -1)] = $matches[$index];
+                                unset($matches[$index]);
+                            }
+                        }
+                    }
+                }
+                
+                $uriMatches[isset($resource['priority']) ? $resource['priority'] : 0] = array(
+                    $uri,
+                    $resource,
+                    $matches
+                );
+                
+            }
+        }
+        krsort($uriMatches);
+        
+        if ($uriMatches) {
+            list($uri, $resource, $parameters) = array_shift($uriMatches);
+            if (!$resource['loaded']) { // autoload
+                if (!class_exists($resource['class'])) {
+                    throw new Exception('Unable to load resource' . $resource['class']);
+                }
+                $resourceDetails = $this->getResourceClassDetails($resource['class']);
+                $resource = $this->resources[$uri] = array(
+                    'namespace' => $resourceDetails['namespaceName'],
+                    'class' => $resourceDetails['className'],
+                    'filename' => $resourceDetails['filename'],
+                    'line' => $resourceDetails['line'],
+                    'priority' => 0,
+                    'loaded' => TRUE
+                );
+            }
+            
+            $this->allowedMethods = array_intersect(array_map('strtoupper', get_class_methods($resource['class'])), $this->HTTPMethods);
+            
+            return new $resource['class']($parameters);
+        }
+        
+        // no resource found, throw response exception
+        throw new ResponseException('A resource matching URI "'.$this->uri.'" was not found', Response::NOTFOUND);
+        
+    }
+    
+    /**
+     * Check if an etag matches the requests if-match header
+     * @param str etag Etag to match
+     * @return bool
+     */
+    function ifMatch($etag) {
+        if (isset($this->ifMatch[0]) && $this->ifMatch[0] == '*') {
+            return TRUE;
+        }
+        return in_array($etag, $this->ifMatch);
+    }
+    
+    /**
+     * Check if an etag matches the requests if-none-match header
+     * @param str etag Etag to match
+     * @return bool
+     */
+    function ifNoneMatch($etag) {
+        if (isset($this->ifMatch[0]) && $this->ifMatch[0] == '*') {
+            return FALSE;
+        }
+        return in_array($etag, $this->ifNoneMatch);
+    }
+    
+    /**
+     * Return the most acceptable of the given formats based on the accept array
+     * @param str[] formats
+     * @param str default The default format if the requested format does not match $formats
+     * @return str
+     */
+    function mostAcceptable($formats, $default = NULL) {
+        foreach (call_user_func_array('array_merge', $this->accept) as $format) {
+            if (in_array($format, $formats)) {
+                return $format;
+            }
+        }
+        return $default;
+    }
+    
+}
+
+/**
+ * Base resource class
+ * @namespace Tonic\Lib
+ */
+class Resource {
+    
+    private $parameters;
+    
+    /**
+     * Resource constructor
+     * @param str[] parameters Parameters passed in from the URL as matched from the URI regex
+     */
+    function  __construct($parameters) {
+        $this->parameters = $parameters;
+    }
+    
+    /**
+     * Convert the object into a string suitable for printing
+     * @return str
+     * @codeCoverageIgnore
+     */
+    function __toString() {
+        $str = get_class($this);
+        foreach ($this->parameters as $name => $value) {
+            $str .= "\n".$name.': '.$value;
+        }
+        return $str;
+    }
+    
+    function secured() 
+    {  
+	try 
+	{
+	    $oauth = new OAuth2(new OAuth2StorageUserCredential());
+	    $token = $oauth->getBearerToken();
+	    $oauth->verifyAccessToken($token);
+
+	    if(!Config::me('uidNumber'))
+		throw new ResponseException('Session experired.', Response::UNAUTHORIZED);
+	} 
+	catch (OAuth2ServerException $oauthError) 
+	{
+		$oauthError->sendHttpResponse();
+	}
+	
+    }
+    
+    /**
+     * Execute a request on this resource.
+     * @param Request request The request to execute the resource in the context of
+     * @return Response
+     * @throws ResponseException If the HTTP method is not allowed on the resource, a 405 exception is thrown
+     */
+    function exec($request) {
+        
+        if (
+            in_array(strtoupper($request->method), $request->HTTPMethods) &&
+            method_exists($this, $request->method)
+        ) {
+            
+            $method = new ReflectionMethod($this, $request->method);
+            $parameters = array();
+            foreach ($method->getParameters() as $param) {
+                if ($param->name == 'request') {
+                    $parameters[] = $request;
+                } elseif (isset($this->parameters[$param->name])) {
+                    $parameters[] = $this->parameters[$param->name];
+                    unset($this->parameters[$param->name]);
+                } else {
+                    $parameters[] = reset($this->parameters);
+                    array_shift($this->parameters);
+                }
+            }
+            
+            $response = call_user_func_array(
+                array($this, $request->method),
+                $parameters
+            );
+            
+            $responseClassName = class_exists('Tonic\\Response', false) ? 'Tonic\\Response' : 'Response';
+            if (!$response || !($response instanceof $responseClassName)) {
+                throw new Exception('Method '.$request->method.' of '.get_class($this).' did not return a Response object');
+            }
+            
+        } else {
+            
+            // send 405 method not allowed
+            throw new ResponseException(
+                'The HTTP method "'.$request->method.'" is not allowed for the resource "'.$request->uri.'".',
+                Response::METHODNOTALLOWED
+            );
+            
+        }
+        
+        # good for debugging, remove this at some point
+        $response->addHeader('X-Resource', get_class($this));
+        
+        return $response;
+        
+    }
+    
+}
+
+/**
+ * Model the data of the outgoing HTTP response
+ * @namespace Tonic\Lib
+ */
+class Response {
+    
+    /**
+     * HTTP response code constant
+     */
+    const OK = 200,
+          CREATED = 201,
+          NOCONTENT = 204,
+          MOVEDPERMANENTLY = 301,
+          FOUND = 302,
+          SEEOTHER = 303,
+          NOTMODIFIED = 304,
+          TEMPORARYREDIRECT = 307,
+          BADREQUEST = 400,
+          UNAUTHORIZED = 401,
+          FORBIDDEN = 403,
+          NOTFOUND = 404,
+          METHODNOTALLOWED = 405,
+          NOTACCEPTABLE = 406,
+          GONE = 410,
+          LENGTHREQUIRED = 411,
+          PRECONDITIONFAILED = 412,
+          UNSUPPORTEDMEDIATYPE = 415,
+          INTERNALSERVERERROR = 500;
+    
+    /**
+     * The request object generating this response
+     * @var Request
+     */
+    public $request;
+    
+    /**
+     * The HTTP response code to send
+     * @var int
+     */
+    public $code = Response::OK;
+    
+    /**
+     * The HTTP headers to send
+     * @var str[]
+     */
+    public $headers = array();
+    
+    /**
+     * The HTTP response body to send
+     * @var str
+     */
+    public $body;
+    
+    /**
+     * Create a response object.
+     * @param Request request The request object generating this response
+     * @param str uri The URL of the actual resource being used to build the response
+     */
+    function __construct($request, $uri = NULL) {
+        
+        $this->request = $request;
+        
+        if ($uri && $uri != $request->uri) { // add content location header
+            $this->addHeader('Content-Location', $uri);
+            $this->addVary('Accept');
+            $this->addVary('Accept-Language');
+        }
+        $this->addHeader('Allow', implode(', ', $request->allowedMethods));
+        
+    }
+    
+    /**
+     * Convert the object into a string suitable for printing
+     * @return str
+     * @codeCoverageIgnore
+     */
+    function __toString() {
+        $str = 'HTTP/1.1 '.$this->code;
+        foreach ($this->headers as $name => $value) {
+            $str .= "\n".$name.': '.$value;
+        }
+        return $str;
+    }
+    
+    /**
+     * Add a header to the response
+     * @param str header
+     * @param str value
+     */
+    function addHeader($header, $value) {
+        $this->headers[$header] = $value;
+    }
+    
+    /**
+     * Send a cache control header with the response
+     * @param int time Cache length in seconds
+     */
+    function addCacheHeader($time = 86400) {
+        if ($time) {
+            $this->addHeader('Cache-Control', 'max-age='.$time.', must-revalidate');
+        } else {
+            $this->addHeader('Cache-Control', 'no-cache');
+        }
+    }
+    
+    /**
+     * Send an etag with the response
+     * @param str etag Etag value
+     */
+    function addEtag($etag) {
+        $this->addHeader('Etag', '"'.$etag.'"');
+    }
+    
+    function addVary($header) {
+        if (isset($this->headers['Vary'])) {
+            $this->headers['Vary'] .= ', '.$header;
+        } else {
+            $this->addHeader('Vary', $header);
+        }
+    }
+    
+    /**
+     * Output the response
+     * @codeCoverageIgnore
+     */
+    function output() {
+        
+        if (php_sapi_name() != 'cli' && !headers_sent()) {
+            
+            header('HTTP/1.1 '.$this->code);
+            foreach ($this->headers as $header => $value) {
+                header($header.': '.$value);
+            }
+        }
+        
+        if (strtoupper($this->request->method) !== 'HEAD') {
+            echo $this->body;
+        }
+        
+    }
+    
+}
+
+/**
+ * Exception class for HTTP response errors
+ * @namespace Tonic\Lib
+ */
+class ResponseException extends Exception {
+    
+    /**
+     * Generate a default response for this exception
+     * @param Request request
+     * @return Response
+     */
+    function response($request) {
+        $response = new Response($request);
+        $response->code = $this->code;
+        $response->body = $this->message;
+        return $response;
+    }
+    
+}
+
Index: trunk/prototype/library/tonic/docroot/dispatch.php
===================================================================
--- trunk/prototype/library/tonic/docroot/dispatch.php	(revision 6528)
+++ trunk/prototype/library/tonic/docroot/dispatch.php	(revision 6528)
@@ -0,0 +1,26 @@
+<?php
+
+// load Tonic library
+require_once '../lib/tonic.php';
+
+// load examples
+require_once '../examples/examples.php';
+
+// handle request
+$request = new Request();
+try {
+    $resource = $request->loadResource();
+    $response = $resource->exec($request);
+
+} catch (ResponseException $e) {
+    switch ($e->getCode()) {
+    case Response::UNAUTHORIZED:
+        $response = $e->response($request);
+        $response->addHeader('WWW-Authenticate', 'Basic realm="Tonic"');
+        break;
+    default:
+        $response = $e->response($request);
+    }
+}
+$response->output();
+
Index: trunk/prototype/library/tonic/README.markdown
===================================================================
--- trunk/prototype/library/tonic/README.markdown	(revision 6528)
+++ trunk/prototype/library/tonic/README.markdown	(revision 6528)
@@ -0,0 +1,233 @@
+PHP library/framework for building Web apps while respecting the 5 principles
+of RESTful design.
+
+ * Give every "thing" an ID (aka URIs)
+ * Link things together (HATEOAS)
+ * Use standard methods (aka the standard interface)
+ * Resources with multiple representations (aka standard document formats)
+ * Communicate statelessly
+
+[See the Tonic site for more info](http://peej.github.com/tonic/).
+
+
+How it works
+============
+
+Everything is a resource, and a resource is defined as a PHP class. An annotation
+wires a URI (or a collection of URIs) to the resource, and methods that match
+the HTTP methods by name allow interaction with it.
+
+    /**
+     * This class defines an example resource that is wired into the URI /example
+     * @uri /example
+     */
+    class ExampleResource extends Resource { }
+
+The incoming HTTP request is turned into a list of negotiated URIs based on the
+accept request headers which can then be used to pick the best representation
+for the response.
+
+    /**
+     * This class defines an example resource that is wired into the URI /example
+     * @uri /example
+     */
+    class ExampleResource extends Resource {
+        
+        function get($request) {
+            
+            $response = new Response($request);
+            
+            $response->code = Response::OK;
+            $response->body = 'Example response';
+            
+            return $response;
+            
+        }
+      
+    }
+
+
+How to get started
+==================
+
+The best place to get started is to get the hello world example running on your
+system, to do this you will need a web server running PHP5.1+.
+
+Place all of the Tonic files into your PHP include path so that other scripts can
+find it. By default on Windows this will probably be in "c:\php\includes\tonic" or
+on Linux/Unix it will be "/usr/share/php/tonic"
+
+Copy "docroot/dispatch.php" into your servers document root and edit it so that the
+require_once statement paths point to the Tonic library and the examples.
+
+Finally you need to route all incoming requests to dispatch.php. How you do this
+depends on your web server. If you are using Apache, the simplest way is to copy
+the .htaccess file from "docroot/.htaccess" into your Apache document root.
+
+
+Features
+========
+
+
+Request URI
+-----------
+
+The URI that is processed for the request when you create the Tonic Request object
+is gather by default from the REQUEST_URI Apache variable. If you need to gather
+the URI from another $_SERVER variable or somewhere else then you can pass it into
+the Request objects constructor as a configuration option:
+
+    $request = new Request(array(
+        'uri' => $_SERVER['PATH_INFO']
+    ));
+
+
+Base URI
+--------
+
+If you want to put your Tonic dispatcher at a URL that isn't the root of a domain
+then you'll need to let the Request object know so that the @uri annotations ignore
+it:
+
+    $request = new Request(array(
+        'baseUri' => '/some/base/uri'
+    ));
+
+Don't put a trailing slash on the end.
+
+
+URI annotations
+---------------
+
+Resources are attached to their URL by their @uri annotation:
+
+    /**
+     * @uri /example
+     */
+    class ExampleResource extends Resource { }
+
+As well as a straight forward URI string, you can also use a regular expression
+so that a resource is tied to a range of URIs:
+
+    /**
+     * @uri /example/([a-z]+)
+     */
+    class ExampleResource extends Resource {
+        function get($request, $parameter) {
+            ...
+        }
+    }
+
+URL template and Rails route style @uri annotations are also supported:
+
+    /**
+     * @uri /users/{username}
+     */
+    class ExampleResource extends Resource {
+        function get($request, $username) {
+            ...
+        }
+    }
+    
+    /**
+     * @uri /users/:username
+     */
+    class ExampleResource extends Resource {
+        function get($request, $username) {
+            ...
+        }
+    }
+
+It is also possible for multiple resource to match the same URI, so you can
+prioritise which resource should be used by specifying a priority level as part
+of the annotation:
+
+    /**
+     * @uri /example/([a-z]+)
+     */
+    class ExampleResource extends Resource { }
+
+    /**
+     * @uri /example/apple 2
+     */
+    class ExampleResource extends Resource { }
+
+By postfixing the @uri annotation with a number, of all the matching resources,
+the one with the highest postfixed number will be used.
+
+
+Mimetypes
+---------
+
+To handle content negotiation via filename style extensions to URLs as well the
+HTTP Accept header, a mapping between extensions and mimetypes can be provided.
+By default this list contains a number of common mappings, if you need to add one
+or more of your own, pass them into the constructor as an array:
+
+    $request = new Request(array(
+        'mimetypes' => array(
+            'ogv' => 'video/ogg'
+        )
+    ));
+
+
+Mount points
+------------
+
+To make resources more portable, it is possible to "mount" them into your URL-space
+by providing a namespace name to URL-space mapping. Every resource within that
+namespace will in effect have the URL-space prefixed to their @uri annotation.
+
+    $request = new Request(array(
+        'mount' => array(
+            'namespaceName' => '/some/mounted/uri'
+        )
+    ));
+
+Again, don't put a trailing slash on the end, and if you aren't using PHP5.3 and
+namespaces, you can use the @namespace annotation.
+
+
+Response exceptions
+-------------------
+
+The Request object and Resource objects can throw ResponseExceptions when a problem
+occurs that the object does not want to handle and so relinquishes control back
+to the dispatcher.
+
+The ResponseException has its code value set to the HTTP response code of the problem
+and its message set to a human readable reason for throwing the exception. The
+ResponseException::response() method can be used to produce a default Response object
+expressing the exception if required.
+
+The Request object throws a 404 ResponseException when the resource to be loaded
+does not exist.
+
+The Resource object throws a 405 ResponseException when the HTTP method from the
+request is not able to be handled by the resource.
+
+If you don't want to handle a problem within your Resource class, you can throw your
+own ResponseException and handle it in the dispatcher. Look at the auth example for
+an example of how.
+
+
+Autoloading classes
+-------------------
+
+If you've got lots of resource classes and don't fancy including them all in your
+dispatcher, you can use the autoload function to load a resource class for a given
+URL-space.
+
+    $request = new Request(array(
+        'autoload' => array(
+            '/example/[a-z]+' => 'ClassName'
+        )
+    ));
+
+In this example, the class ClassName will be autoloaded via [the standard PHP
+__autoload method](http://php.net/manual/en/language.oop5.autoload.php).
+
+
+
+For more information, read the code. Start with the dispatcher "docroot/dispatch.php"
+and then the examples in the "examples" directory.
Index: trunk/prototype/library/tonic/examples/smarty/representations/default.html
===================================================================
--- trunk/prototype/library/tonic/examples/smarty/representations/default.html	(revision 6528)
+++ trunk/prototype/library/tonic/examples/smarty/representations/default.html	(revision 6528)
@@ -0,0 +1,2 @@
+<p>This is the default content for the Smarty resource.</p>
+<p>Today is {""|@time|date_format}</p>
Index: trunk/prototype/library/tonic/examples/smarty/representations/shell.html
===================================================================
--- trunk/prototype/library/tonic/examples/smarty/representations/shell.html	(revision 6528)
+++ trunk/prototype/library/tonic/examples/smarty/representations/shell.html	(revision 6528)
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>{$title|escape}</title>
+    </head>
+    <body>
+        <h1>{$title|escape}</h1>
+        {$body}
+    </body>
+</html>
Index: trunk/prototype/library/tonic/examples/smarty/smarty.php
===================================================================
--- trunk/prototype/library/tonic/examples/smarty/smarty.php	(revision 6528)
+++ trunk/prototype/library/tonic/examples/smarty/smarty.php	(revision 6528)
@@ -0,0 +1,60 @@
+<?php
+
+@include_once 'smarty/Smarty.class.php';
+
+/**
+ * Smarty template example
+ *
+ * Using Smarty for representation generation
+ *
+ * @namespace Tonic\Examples\Filesystem
+ * @uri /smarty
+ */
+class SmartyResource extends Resource {
+    
+    protected $smarty;
+    
+    function __construct() {
+        
+        $this->smarty = new Smarty();
+        $this->smarty->template_dir = '../examples/smarty/representations';
+        $this->smarty->compile_dir = sys_get_temp_dir();
+        
+    }
+    
+    function get($request) {
+        
+        $response = new Response($request);
+        
+        $this->smarty->assign('title', 'Smarty template');
+        $body = $this->render('default');
+        
+        $etag = md5($body);
+        if ($request->ifNoneMatch($etag)) {
+            
+            $response->code = Response::NOTMODIFIED;
+            
+        } else {
+        
+            $response->code = Response::OK;
+            $response->addHeader('Content-type', 'text/html');
+            $response->body = $body;
+            
+        }
+        
+        return $response;
+        
+    }
+    
+    protected function render($view, $format = 'html', $useShell = TRUE) {
+        
+        if ($format == 'html' && $useShell) {
+            $this->smarty->assign('body', $this->smarty->fetch($view.'.'.$format));
+            return $this->smarty->fetch('shell.'.$format);
+        } else {
+            return $this->smarty->fetch($view.'.'.$format);
+        }
+        
+    }
+    
+}
Index: trunk/prototype/library/tonic/examples/auth/auth.php
===================================================================
--- trunk/prototype/library/tonic/examples/auth/auth.php	(revision 6528)
+++ trunk/prototype/library/tonic/examples/auth/auth.php	(revision 6528)
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Authentication example
+ *
+ * An example authentication resource, the isSecured() method can be used to ensure
+ * that only authorised users can access the resource.
+ *
+ * username: user
+ * password: pass
+ *
+ * @namespace Tonic\Examples\Auth
+ * @uri /auth
+ */
+class AuthResource extends Resource {
+    
+    const USERNAME = 'user';
+    const PASSWORD = 'pass';
+    
+    function isSecured() {
+        
+        if (
+            isset($_SERVER['PHP_AUTH_USER']) && $_SERVER['PHP_AUTH_USER'] == AuthResource::USERNAME &&
+            isset($_SERVER['PHP_AUTH_PW']) && $_SERVER['PHP_AUTH_PW'] == AuthResource::PASSWORD
+        ) {
+            return;
+        }
+        
+        throw new ResponseException('Incorrect username and password', Response::UNAUTHORIZED);
+        
+    }
+    
+    /**
+     * Handle a GET request for this resource
+     * @param Request request
+     * @return Response
+     */
+    function get($request) {
+        
+        $this->isSecured();
+        
+        $response = new Response($request);
+        
+        $response->body = 'You have access to the secret';
+        
+        return $response;
+        
+    }
+    
+}
+
Index: trunk/prototype/library/tonic/examples/multirep/multirep.php
===================================================================
--- trunk/prototype/library/tonic/examples/multirep/multirep.php	(revision 6528)
+++ trunk/prototype/library/tonic/examples/multirep/multirep.php	(revision 6528)
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * Multiple representation example
+ *
+ * A representation is an output format for a resource. A single resource can have
+ * many output representations that can differ depending on the request data.
+ *
+ * Representations are built up as strings within the Response object and it is
+ * down to the programmer to implement the desired logic for choosing and building
+ * a required representation.
+ *
+ * This example produces three different representations of the resource data
+ * depending on the request accept header information.
+ *
+ * @namespace Tonic\Examples\Multirep
+ * @uri /multirep
+ */
+class MultiRepresentationResource extends Resource {
+    
+    var $data = array(
+        'secret' => 'monkey',
+        'hidden' => 'squirrel'
+    );
+    
+    /**
+     * Handle a GET request for this resource
+     * @param Request request
+     * @return Response
+     */
+    function get($request) {
+        
+        $response = new Response($request);
+        $response->code = Response::OK;
+        
+        // select the most acceptable format of the given types from the request
+        $format = $request->mostAcceptable(array(
+            'json', 'html', 'txt'
+        ));
+        
+        switch ($format) {
+        
+        // obviously it might make more sense to put the HTML in another file
+        // and use some kind of templating system
+        case 'html':
+            $response->addHeader('Content-type', 'text/html');
+            $response->body = '<!DOCTYPE html>';
+            $response->body .= '<html>';
+            $response->body .= '<head>';
+            $response->body .= '<title>HTML Representation</title>';
+            $response->body .= '</head>';
+            $response->body .= '<body>';
+            $response->body .= '<h1>HTML Representation</h1>';
+            $response->body .= '<table border="1">';
+            foreach ($this->data as $field => $value) {
+                $response->body .= '<tr>';
+                $response->body .= '<th>'.htmlspecialchars($field).'</th>';
+                $response->body .= '<td>'.htmlspecialchars($value).'</td>';
+                $response->body .= '</tr>';
+            }
+            $response->body .= '</table>';
+            $response->body .= '<h2>Explicit links to available formats</h2>';
+            $response->body .= '<a href="/multirep.txt">Plain text</a> ';
+            $response->body .= '<a href="/multirep.json">JSON</a> ';
+            $response->body .= '<a href="/multirep.html">HTML</a>';
+            $response->body .= '</body>';
+            $response->body .= '</html>';
+            break;
+        
+        case 'json':
+            $response->addHeader('Content-type', 'application/json');
+            $response->body = json_encode($this->data);
+            break;
+        
+        case 'txt':
+            $response->addHeader('Content-type', 'text/plain');
+            $response->body = "Plain Text Representation\n";
+            $response->body .= "\n";
+            ob_start();
+            var_dump($this->data);
+            $response->body .= ob_get_contents();
+            ob_end_clean();
+            break;
+            
+        // we don't have a suitable format, so do a 406 response instead
+        default:
+            $response->code = Response::NOTACCEPTABLE;
+            $response->addHeader('Content-type', 'text/plain');
+            $response->body = "406 Not Acceptable\n";
+            break;
+            
+        }
+        
+        return $response;
+        
+    }
+    
+}
+
Index: trunk/prototype/library/tonic/examples/examples.php
===================================================================
--- trunk/prototype/library/tonic/examples/examples.php	(revision 6528)
+++ trunk/prototype/library/tonic/examples/examples.php	(revision 6528)
@@ -0,0 +1,60 @@
+<?php
+
+require_once 'helloworld/helloworld.php';
+require_once 'filesystem/filesystem.php';
+require_once 'filesystem/filesystemcollection.php';
+require_once 'smarty/smarty.php';
+require_once 'htmlform/htmlform.php';
+require_once 'auth/auth.php';
+require_once 'multirep/multirep.php';
+
+/**
+ * Examples listing
+ * @namespace Tonic\Examples
+ * @uri /
+ */
+class ExamplesListResource extends Resource {
+    
+    function get($request) {
+        
+        $response = new Response($request);
+        
+        $examples = '';
+        $dirs = glob(dirname(__FILE__).DIRECTORY_SEPARATOR.'*', GLOB_ONLYDIR);
+        if ($dirs) {
+            foreach ($dirs as $path) {
+                $location = basename($path);
+                $readme = $path.DIRECTORY_SEPARATOR.$location.'.php';
+                if (file_exists($readme)) {
+                    preg_match('|/\*\*\s*\*\s*(.+?)\*/|s', file_get_contents($readme), $match);
+                    $comment = preg_replace('|\s*\*\s*(@.+)?|', "\n", $match[1]);
+                    $parts = explode("\n\n", $comment);
+                    $name = array_shift($parts);
+                    $description = join(' ', $parts);
+                } else {
+                    $name = $location;
+                    $description = '';
+                }
+                $examples .= 
+                    '<li>'.
+                    '<h3><a href="'.$location.'">'.$name.'</a></h3>'.
+                    '<p>'.$description.'</p>'.
+                    '</li>';
+            }
+        } else {
+            $examples .= '<li>No examples</li>';
+        }
+        
+        $response->body = <<<END
+<h1>Welcome to Tonic</h1>
+<p>Below is a list of example uses of the Tonic library. View each example to see it in action.</p>
+<h2>Examples</h2>
+END;
+        $response->body .= '<ul>'.$examples.'</ul>';
+        
+        return $response;
+        
+    }
+    
+}
+
Index: trunk/prototype/library/tonic/examples/filesystem/representations/default.html
===================================================================
--- trunk/prototype/library/tonic/examples/filesystem/representations/default.html	(revision 6528)
+++ trunk/prototype/library/tonic/examples/filesystem/representations/default.html	(revision 6528)
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Default Document</title>
+    </head>
+    <body>
+        <p>Welcome to the filesystem example.</p>
+        <p>The resource "FilesystemResource" in filesystem.php is wired to any URI starting /filesystem and then maps the remaining part of the URI to the filenames on the filesystem within "examples/filesystem/representations".</p>
+        <p>This page is being read from "examples/filesystem/default.html" on the filesystem as it is the default document
+        for the current URL. <a href="filesystem/default.html">You'll also be able to see it at this URL</a>.</p>
+        <p>The FilesystemResource also allows updating of the files by sending PUT and DELETE requests to their mapped URI.</p>
+        <p>The resource "FilesystemCollection" in filesystemcollection.php 
+        <p><a href="filesystem/collection">View a collection resource for the directory "examples/filesystem/representations/collection"</a>.</p>
+        <p>The FilesystemCollection also allows adding of a file to the collection by sending a POST request to the collection URI.</p>
+    </body>
+</html>
Index: trunk/prototype/library/tonic/examples/filesystem/representations/collection/one.txt
===================================================================
--- trunk/prototype/library/tonic/examples/filesystem/representations/collection/one.txt	(revision 6528)
+++ trunk/prototype/library/tonic/examples/filesystem/representations/collection/one.txt	(revision 6528)
@@ -0,0 +1,1 @@
+One
Index: trunk/prototype/library/tonic/examples/filesystem/representations/collection/one.html
===================================================================
--- trunk/prototype/library/tonic/examples/filesystem/representations/collection/one.html	(revision 6528)
+++ trunk/prototype/library/tonic/examples/filesystem/representations/collection/one.html	(revision 6528)
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>One</title>
+    </head>
+    <body>
+        One
+    </body>
+</html>
Index: trunk/prototype/library/tonic/examples/filesystem/representations/collection/two.html
===================================================================
--- trunk/prototype/library/tonic/examples/filesystem/representations/collection/two.html	(revision 6528)
+++ trunk/prototype/library/tonic/examples/filesystem/representations/collection/two.html	(revision 6528)
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Two</title>
+    </head>
+    <body>
+        Two
+    </body>
+</html>
Index: trunk/prototype/library/tonic/examples/filesystem/filesystemcollection.php
===================================================================
--- trunk/prototype/library/tonic/examples/filesystem/filesystemcollection.php	(revision 6528)
+++ trunk/prototype/library/tonic/examples/filesystem/filesystemcollection.php	(revision 6528)
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * Collection resource
+ * @namespace Tonic\Examples\Filesystem
+ * @uri /filesystem/collection
+ */
+class FilesystemCollection extends FilesystemResource {
+    
+    /**
+     * Path to the collections files
+     * @var str
+     */
+    var $collection;
+    
+    function __construct($parameters) {
+        parent::__construct($parameters);
+        $this->collection = dirname(__FILE__).'/representations/collection';
+    }
+    
+    /**
+     * Handle a GET request
+     * @param Request request
+     * @return Response
+     */
+    function get($request) {
+        
+        $response = new Response($request);
+        $collection = str_replace('/', DIRECTORY_SEPARATOR, $this->collection);
+        
+        $resourceUris = '';
+        $files = glob($collection.DIRECTORY_SEPARATOR.'*');
+        if ($files) {
+            foreach ($files as $filepath) {
+            	$filepath = str_replace(DIRECTORY_SEPARATOR, '/', $filepath);
+                $resourceUris .= '<li><a href="'.$this->turnFilePathIntoUri($filepath, $request).'">'.basename($filepath).'</a></li>';
+            }
+        } else {
+            $resourceUris .= '<li>Empty collection</li>';
+        }
+        
+        $response->body = '<ul>'.$resourceUris.'</ul>';
+        
+        return $response;
+        
+    }
+    
+    protected function getNextAvailableItemUri() {
+    	$collection = str_replace('/', DIRECTORY_SEPARATOR, $this->collection);
+        $filename = 1;
+        while (file_exists($collection.DIRECTORY_SEPARATOR.$filename)) {
+            $filename++;
+        }
+        return $this->uriStub.substr($this->collection, strlen($this->path)).'/'.$filename;
+    }
+    
+    function post($request) {
+        
+        $response = new Response($request);
+        
+        if ($request->data) {
+            $uri = $this->getNextAvailableItemUri();
+            $filePath = $this->turnUriIntoFilePath($uri, $request);
+            if (file_put_contents($filePath, $request->data)) {
+                $response->code = Response::CREATED;
+                $response->addHeader('Location', $uri);
+            } else {
+                $response->code = Response::INTERNALSERVERERROR;
+            }
+        } else {
+            $response->code = Response::LENGTHREQUIRED;
+        }
+        
+        return $response;
+        
+    }
+    
+}
+
Index: trunk/prototype/library/tonic/examples/filesystem/filesystem.php
===================================================================
--- trunk/prototype/library/tonic/examples/filesystem/filesystem.php	(revision 6528)
+++ trunk/prototype/library/tonic/examples/filesystem/filesystem.php	(revision 6528)
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * Use the filesystem as resource representations
+ *
+ * This example loads files from the filesystem to be used as resource representations
+ * and provides a resource collection to contain them within. It also demonstrates
+ * the ability to add resources to the collection, and update and delete resources
+ * from within the collection.
+ *
+ * @namespace Tonic\Examples\Filesystem
+ * @uri /filesystem(/.*)?
+ */
+class FilesystemResource extends Resource {
+    
+    /**
+     * Path to the files to use
+     * @var str
+     */
+    var $path;
+    
+    /**
+     * URI stub
+     * @var str
+     */
+    var $uriStub = '/filesystem/';
+    
+    /**
+     * The default document to use if the request is for a URI that maps to a directory
+     * @var str
+     */
+    var $defaultDocument = 'default.html';
+    
+    function __construct($parameters) {
+        parent::__construct($parameters);
+        $this->path = dirname(__FILE__).'/representations/';
+    }
+    
+    protected function turnUriIntoFilePath($uri, $request) {
+        return $this->path.DIRECTORY_SEPARATOR.substr($uri, strlen($request->baseUri.$this->uriStub));
+    }
+    
+    protected function turnFilePathIntoUri($path, $request) {
+        return $request->baseUri.$this->uriStub.substr($path, strlen($this->path));
+    }
+    
+    /**
+     * Handle a GET request for this resource by returning the contents of a file matching the request URI
+     * @param Request request
+     * @return Response
+     */
+    function get($request) {
+        
+        // look at all candidate URIs in turn and stop when we find a file that matches one
+        foreach ($request->negotiatedUris as $uri) {
+            
+            // convert URI into filesystem path
+            $filePath = $this->turnUriIntoFilePath($uri, $request);
+            
+            if (substr($filePath, -1, 1) == DIRECTORY_SEPARATOR) { // add a default filename to the path
+                $filePath .= $this->defaultDocument;
+                $uri .= $this->defaultDocument;
+            }
+            
+            if (file_exists($filePath)) { // use this file
+                
+                $response = new Response($request, $uri);
+                
+                // generate etag for the resource based on the files modified date
+                $etag = md5(filemtime($filePath));
+                if ($request->ifNoneMatch($etag)) { // client has matching etag
+                    
+                    $response->code = Response::NOTMODIFIED;
+                    
+                } else {
+                    
+                    $explodedPath = explode('.', $filePath);
+                    $extension = array_pop($explodedPath);
+                    if (isset($request->mimetypes[$extension])) { // add content type header
+                        $response->addHeader('Content-Type', $request->mimetypes[$extension]);
+                    }
+                    
+                    $response->addEtag($etag); // add etag header
+                    
+                    $response->body = file_get_contents($filePath); // set contents
+                    
+                }
+                return $response;
+                
+            }
+            
+        }
+        
+        // nothing found, send 404 response
+        $response = new Response($request);
+        $response->code = Response::NOTFOUND;
+        $response->addHeader('Content-Type', $request->mimetypes['html']);
+        $response->body = '<p>404, nothing found</p>';
+        return $response;
+        
+    }
+    
+    /**
+     * Handle a PUT request for this resource by overwriting the resources contents
+     * @param Request request
+     * @return Response
+     */
+    function put($request) {
+        
+        $response = new Response($request);
+        
+        if ($request->data) {
+            
+            $filePath = $this->turnUriIntoFilePath($request->uri, $request);
+            
+            file_put_contents($filePath, $request->data);
+            
+            $response->code = Response::NOCONTENT;
+            
+        } else {
+            
+            $response->code = Response::LENGTHREQUIRED;
+            
+        }
+        
+        return $response;
+        
+    }
+    
+    /**
+     * Handle a DELETE request for this resource by removing the resources file
+     * @param Request request
+     * @return Response
+     */
+    function delete($request) {
+        
+        $response = new Response($request);
+        
+        $filePath = $this->turnUriIntoFilePath($request->uri, $request);
+        
+        if (file_exists($filePath)) {
+            
+            if (unlink($filePath)) {
+                $response->code = Response::NOCONTENT;
+            } else {
+                $response->code = Response::INTERNALSERVERERROR;
+            }
+            
+            return $response;
+            
+        } else { // nothing found, send 404 response
+            $response->code = Response::NOTFOUND;
+        }
+        
+        return $response;
+        
+    }
+    
+}
+
Index: trunk/prototype/library/tonic/examples/htmlform/htmlform.php
===================================================================
--- trunk/prototype/library/tonic/examples/htmlform/htmlform.php	(revision 6528)
+++ trunk/prototype/library/tonic/examples/htmlform/htmlform.php	(revision 6528)
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * Display and process a HTML form via a HTTP POST request
+ *
+ * This example outputs a simple HTML form and gathers the POSTed data
+ *
+ * @namespace Tonic\Examples\HTMLForm
+ * @uri /htmlform
+ */
+class HTMLForm extends Resource {
+    
+    /**
+     * Handle a GET request for this resource
+     * @param Request request
+     * @return Response
+     */
+    function get($request, $name) {
+        
+        $response = new Response($request);
+        
+        if ($name) {
+            $welcome = "<p>Hello $name.</p>".
+                "<p>Raw POST data:</p>".
+                "<blockquote>$request->data</blockquote>";
+        } else {
+            $welcome = "<p>Enter your name.</p>";
+        }
+        
+        $response->addHeader('Content-type', 'text/html');
+        $response->body = <<<EOF
+<!DOCTYPE html>
+<html>
+    <body>
+        <form action="htmlform" method="post">
+            <label>Name: <input type="text" name="name"></label>
+            <input type="submit">
+        </form>
+        $welcome
+    </body>
+</html>
+EOF;
+        
+        return $response;
+        
+    }
+    
+    /**
+     * Handle a POST request for this resource
+     * @param Request request
+     * @return Response
+     */
+    function post($request) {
+        
+        if (isset($_POST['name'])) {
+            $name = htmlspecialchars($_POST['name']);
+        } else {
+            $name = '';
+        }
+        
+        return $this->get($request, $name);
+        
+    }
+    
+}
+
Index: trunk/prototype/library/tonic/examples/helloworld/helloworld.php
===================================================================
--- trunk/prototype/library/tonic/examples/helloworld/helloworld.php	(revision 6528)
+++ trunk/prototype/library/tonic/examples/helloworld/helloworld.php	(revision 6528)
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * The Hello World of Tonic
+ *
+ * This example outputs a simple hello world message and the details of the
+ * request and response as generated by Tonic. It also demonstrates etags and
+ * "if none match" functionality.
+ *
+ * @namespace Tonic\Examples\Helloworld
+ * @uri /helloworld
+ */
+class HelloWorldResource extends Resource {
+    
+    /**
+     * Handle a GET request for this resource
+     * @param Request request
+     * @return Response
+     */
+    function get($request) {
+        
+        $response = new Response($request);
+        
+        $etag = md5($request->uri);
+        if ($request->ifNoneMatch($etag)) {
+            
+            $response->code = Response::NOTMODIFIED;
+            
+        } else {
+            
+            $response->code = Response::OK;
+            $response->addHeader('Content-type', 'text/plain');
+            $response->addEtag($etag);
+            $response->body =
+                "Hello world!\n".
+                "\n".
+                "This request:\n".
+                "\n".
+                $request->__toString()."\n".
+                "\n".
+                "This response:\n".
+                "\n".
+                $response->__toString();
+            
+        }
+        
+        return $response;
+        
+    }
+    
+}
+
Index: trunk/prototype/library/tonic/features/resource.feature
===================================================================
--- trunk/prototype/library/tonic/features/resource.feature	(revision 6528)
+++ trunk/prototype/library/tonic/features/resource.feature	(revision 6528)
@@ -0,0 +1,21 @@
+Feature: Resource object
+  In order to execute code
+  As a PHP developer
+  I want a PHP object that represents a HTTP resource
+  
+  Scenario: Load a non existant resource
+    Given the request URI of "/resourcetest"
+    When I create a request object
+    And I load the resource
+    And execute the request
+    Then the response code should be "404"
+    And the response body should be 'Nothing was found for the resource "/resourcetest".'
+  
+  Scenario: Load a resource that exists
+    Given the request URI of "/resourcetest/one"
+    When I create a request object
+    And I load the resource
+    And execute the request
+    Then the response code should be "200"
+    And the response body should be 'test'
+
Index: trunk/prototype/library/tonic/features/request.feature
===================================================================
--- trunk/prototype/library/tonic/features/request.feature	(revision 6528)
+++ trunk/prototype/library/tonic/features/request.feature	(revision 6528)
@@ -0,0 +1,198 @@
+Feature: HTTP request object
+  In order to handle a request
+  As a PHP developer
+  I want a PHP object that represents the state of the incoming HTTP request
+  
+  Scenario: Have access to the request URI
+    Given the request URI of "/something/otherthing"
+    When I create a request object
+    Then I should see a request URI of "/something/otherthing"
+    
+  Scenario: Have access to the HTTP request method
+    Given the request method of "GET"
+    When I create a request object
+    Then I should see a request method of "GET"
+    
+  Scenario: Receive the request querystring
+    Given the request URI of "/something/otherthing"
+    And the request method of "GET"
+    And the querystring is "?foo=bar"
+    When I create a request object
+    Then I should see a querystring of "foo=bar"
+    
+  Scenario: Have access to the HTTP request method
+    Given the request method of "post"
+    And the request data of "some data"
+    When I create a request object
+    Then I should see a request method of "POST"
+    And I should see the request data "some data"
+    
+  Scenario: Have access to the HTTP request data
+    Given the request method of "PUT"
+    And the request data of "some data"
+    When I create a request object
+    Then I should see a request method of "PUT"
+    And I should see the request data "some data"
+    
+  Scenario: Have bare request URI content negotiated correctly
+    Given the request URI of "/requesttest/one/two"
+    When I create a request object
+    Then I should see a request URI of "/requesttest/one/two"
+    And I should see a negotiated URI of "/requesttest/one/two"
+    And I should see a format negotiated URI of "/requesttest/one/two"
+    And I should see a language negotiated URI of "/requesttest/one/two"
+    
+  Scenario: Have extension request URI content negotiated correctly
+    Given the request URI of "/requesttest/one/two.html"
+    When I create a request object
+    Then I should see a request URI of "/requesttest/one/two"
+    And I should see a negotiated URI of "/requesttest/one/two.html,/requesttest/one/two"
+    And I should see a format negotiated URI of "/requesttest/one/two.html,/requesttest/one/two"
+    And I should see a language negotiated URI of "/requesttest/one/two.html,/requesttest/one/two"
+    
+  Scenario: Have bare request URI with accept header content negotiated correctly
+    Given the request URI of "/requesttest/one/two"
+    And the accept header of "image/png;q=0.5,text/html"
+    When I create a request object
+    Then I should see a request URI of "/requesttest/one/two"
+    And I should see a negotiated URI of "/requesttest/one/two.html,/requesttest/one/two.png,/requesttest/one/two"
+    And I should see a format negotiated URI of "/requesttest/one/two.html,/requesttest/one/two.png,/requesttest/one/two"
+    And I should see a language negotiated URI of "/requesttest/one/two"
+    
+  Scenario: Have extension request URI with accept header content negotiated correctly
+    Given the request URI of "/requesttest/one/two.html"
+    And the accept header of "image/png;q=0.5,text/html"
+    When I create a request object
+    Then I should see a request URI of "/requesttest/one/two"
+    And I should see a negotiated URI of "/requesttest/one/two.html,/requesttest/one/two.png.html,/requesttest/one/two.png,/requesttest/one/two"
+    And I should see a format negotiated URI of "/requesttest/one/two.html,/requesttest/one/two.png,/requesttest/one/two"
+    And I should see a language negotiated URI of "/requesttest/one/two.html,/requesttest/one/two"
+    
+  Scenario: Have bare request URI with language accept header content negotiated correctly
+    Given the request URI of "/requesttest/one/two"
+    And the language accept header of "fr;q=0.5,en"
+    When I create a request object
+    Then I should see a request URI of "/requesttest/one/two"
+    And I should see a negotiated URI of "/requesttest/one/two.en,/requesttest/one/two.fr,/requesttest/one/two"
+    And I should see a format negotiated URI of "/requesttest/one/two"
+    And I should see a language negotiated URI of "/requesttest/one/two.en,/requesttest/one/two.fr,/requesttest/one/two"
+    
+  Scenario: Have extension request URI with language accept header content negotiated correctly
+    Given the request URI of "/requesttest/one/two.html"
+    And the language accept header of "fr;q=0.5,en"
+    When I create a request object
+    Then I should see a request URI of "/requesttest/one/two"
+    And I should see a negotiated URI of "/requesttest/one/two.html.en,/requesttest/one/two.html.fr,/requesttest/one/two.html,/requesttest/one/two.en,/requesttest/one/two.fr,/requesttest/one/two"
+    And I should see a format negotiated URI of "/requesttest/one/two.html,/requesttest/one/two"
+    And I should see a language negotiated URI of "/requesttest/one/two.html,/requesttest/one/two.en,/requesttest/one/two.fr,/requesttest/one/two"
+    
+  Scenario: Have bare request URI with accept and language header content negotiated correctly
+    Given the request URI of "/requesttest/one/two"
+    And the accept header of "image/png;q=0.5,text/html"
+    And the language accept header of "fr;q=0.5,en"
+    When I create a request object
+    Then I should see a request URI of "/requesttest/one/two"
+    And I should see a negotiated URI of "/requesttest/one/two.html.en,/requesttest/one/two.html.fr,/requesttest/one/two.html,/requesttest/one/two.png.en,/requesttest/one/two.png.fr,/requesttest/one/two.png,/requesttest/one/two.en,/requesttest/one/two.fr,/requesttest/one/two"
+    And I should see a format negotiated URI of "/requesttest/one/two.html,/requesttest/one/two.png,/requesttest/one/two"
+    And I should see a language negotiated URI of "/requesttest/one/two.en,/requesttest/one/two.fr,/requesttest/one/two"
+    
+  Scenario: Have extension request URI with accept and language header content negotiated correctly
+    Given the request URI of "/requesttest/one/two.html"
+    And the accept header of "image/png;q=0.5,text/html"
+    And the language accept header of "fr;q=0.5,en"
+    When I create a request object
+    Then I should see a request URI of "/requesttest/one/two"
+    And I should see a negotiated URI of "/requesttest/one/two.html.en,/requesttest/one/two.html.fr,/requesttest/one/two.html,/requesttest/one/two.png.html,/requesttest/one/two.png.en,/requesttest/one/two.png.fr,/requesttest/one/two.png,/requesttest/one/two.en,/requesttest/one/two.fr,/requesttest/one/two"
+    And I should see a format negotiated URI of "/requesttest/one/two.html,/requesttest/one/two.png,/requesttest/one/two"
+    And I should see a language negotiated URI of "/requesttest/one/two.html,/requesttest/one/two.en,/requesttest/one/two.fr,/requesttest/one/two"
+    
+  Scenario: Have if match header
+    Given an if match header of '123123'
+    When I create a request object
+    Then I should see an if match header of "123123"
+    And if match should match "123123"
+    
+  Scenario: Have if match header in quotes
+    Given an if match header of '"123123"'
+    When I create a request object
+    Then I should see an if match header of "123123"
+    And if match should match "123123"
+    
+  Scenario: Have multiple if match headers in quotes
+    Given an if match header of '"123123","456456"'
+    When I create a request object
+    Then I should see an if match header of "123123,456456"
+    And if match should match "123123"
+    And if match should match "456456"
+    
+  Scenario: Have star if match header
+    Given an if match header of '*'
+    When I create a request object
+    Then I should see an if match header of "*"
+    And if match should match "123123"
+    And if match should match "123456"
+    
+  Scenario: Have if none match header
+    Given an if none match header of '123123'
+    When I create a request object
+    Then I should see an if none match header of "123123"
+    And if none match should match "123123"
+    
+  Scenario: Have multiple if none match headers in quotes
+    Given an if none match header of '"123123","456456"'
+    When I create a request object
+    Then I should see an if none match header of "123123,456456"
+    And if none match should match "123123"
+    And if none match should match "456456"
+  
+  Scenario: Have star if none match header
+    Given an if none match header of '*'
+    When I create a request object
+    Then I should see an if none match header of "*"
+    And if none match should not match "123123"
+    And if none match should not match "123456"
+    
+  Scenario: Load a non-existent resource
+    Given the request URI of "/three"
+    When I create a request object
+    And I load the resource
+    Then I should have a response of type "NoResource"
+    
+  Scenario: Load a resource
+    Given the request URI of "/requesttest/one"
+    When I create a request object
+    And I load the resource
+    Then I should have a response of type "NewResource"
+    
+  Scenario: Load a child resource
+    Given the request URI of "/requesttest/one/two"
+    When I create a request object
+    And I load the resource
+    Then I should have a response of type "ChildResource"
+    
+  Scenario: Load a resource with a regex match URI
+    Given the request URI of "/requesttest/three/something/four"
+    When I create a request object
+    And I load the resource
+    Then I should have a response of type "NewResource"
+  
+  Scenario: Load a non-existent resource with a new 404 resource class
+    Given the request URI of "/three"
+    And a 404 resource classname of "NewNoResource"
+    When I create a request object
+    And I load the resource
+    Then I should have a response of type "NewNoResource"
+  
+  Scenario: Resource data loading
+    Given the request URI of "/requesttest/one/two"
+    When I create a request object
+    Then I should see resource "namespace" metadata of "Tonic/Tests"
+    And I should see resource "class" metadata of "ChildResource"
+    And I should see resource "priority" metadata of "0"
+    
+  Scenario: Mounting in a namespace to a URI
+    Given the request URI of "/foo/bar/requesttest/one"
+    And a mounting of "Tonic/Tests" to "/foo/bar"
+    When I create a request object
+    And I load the resource
+    Then I should have a response of type "NewResource"
Index: trunk/prototype/library/tonic/features/response.feature
===================================================================
--- trunk/prototype/library/tonic/features/response.feature	(revision 6528)
+++ trunk/prototype/library/tonic/features/response.feature	(revision 6528)
@@ -0,0 +1,58 @@
+Feature: Response object
+  In order to output a response representation
+  As a PHP developer
+  I want a PHP object that represents a HTTP response
+  
+  Scenario: Output should be gzipped if the client supports it
+    Given the request URI of "/resourcetest/one"
+    And an accept encoding of "gzip"
+    When I create a request object
+    And I load the resource
+    And execute the request
+    And I process content encoding
+    Then the response header "Content-Encoding" should contain 'gzip'
+    And the response body should be gzipped and be "test"
+
+  Scenario: Output should be deflated if the client supports it
+    Given the request URI of "/resourcetest/one"
+    And an accept encoding of "deflate"
+    When I create a request object
+    And I load the resource
+    And execute the request
+    And I process content encoding
+    Then the response header "Content-Encoding" should contain 'deflate'
+    And the response body should be deflated and be "test"
+
+  Scenario: Output should be compress if the client supports it
+    Given the request URI of "/resourcetest/one"
+    And an accept encoding of "compress"
+    When I create a request object
+    And I load the resource
+    And execute the request
+    And I process content encoding
+    Then the response header "Content-Encoding" should contain 'compress'
+    And the response body should be compressed and be "test"
+
+  Scenario: Responses should have a default cache header
+    Given the request URI of "/resourcetest/one"
+    When I create a request object
+    And I load the resource
+    And execute the request
+    And I add a cache header of ""
+    Then the response header "Cache-Control" should contain 'max-age=86400, must-revalidate'
+
+  Scenario: Responses told not to cache should send a zero cache header
+    Given the request URI of "/resourcetest/one"
+    When I create a request object
+    And I load the resource
+    And execute the request
+    And I add a cache header of "0"
+    Then the response header "Cache-Control" should contain 'no-cache'
+
+  Scenario: Responses given an etag should send an etag response header
+    Given the request URI of "/resourcetest/one"
+    When I create a request object
+    And I load the resource
+    And execute the request
+    And I add an etag header of "123123"
+    Then the response header "Etag" should contain '"123123"'
Index: trunk/prototype/library/tonic/features/support/env.php
===================================================================
--- trunk/prototype/library/tonic/features/support/env.php	(revision 6528)
+++ trunk/prototype/library/tonic/features/support/env.php	(revision 6528)
@@ -0,0 +1,63 @@
+<?php
+
+require_once __DIR__.'/../../lib/tonic.php';
+
+$world->config = array();
+
+/* Test resource definitions */
+
+if (!class_exists('NewResource')) {
+    
+    /**
+     * @namespace Tonic\Tests
+     * @uri /requesttest/one
+     * @uri /requesttest/three/.+/four 12
+     */
+    class NewResource extends Resource {
+    
+    }
+
+}
+
+if (!class_exists('ChildResource')) {
+
+    /**
+     * @namespace Tonic\Tests
+     * @uri /requesttest/one/two
+     */
+    class ChildResource extends NewResource {
+    
+    }
+
+}
+
+if (!class_exists('NewNoResource')) {
+
+    /**
+     * @namespace Tonic\Tests
+     */
+    class NewNoResource extends NoResource {
+    
+    }
+
+}
+
+if (!class_exists('TestResource')) {
+    
+    /**
+     * @namespace Tonic\Tests
+     * @uri /resourcetest/one
+     */
+    class TestResource extends Resource {
+        
+        function get($request) {
+            
+            $response = new Response($request);
+            $response->body = 'test';
+            return $response;
+            
+        }
+        
+    }
+
+}
Index: trunk/prototype/library/tonic/features/steps/response.php
===================================================================
--- trunk/prototype/library/tonic/features/steps/response.php	(revision 6528)
+++ trunk/prototype/library/tonic/features/steps/response.php	(revision 6528)
@@ -0,0 +1,41 @@
+<?php
+
+$steps->Given('/^an accept encoding of "([^"]*)"$/', function($world, $arg1) {
+    $world->config['acceptEncoding'] = $arg1;
+});
+
+$steps->When('/^I process content encoding$/', function($world) {
+    $world->response->doContentEncoding();
+});
+
+$steps->Then('/^the response header "([^"]*)" should contain \'([^\']*)\'$/', function($world, $arg1, $arg2) {
+    if ($world->response->headers[$arg1] != $arg2) throw new Exception;
+});
+
+$steps->Then('/^the response body should be ([^ ]*) and be "([^"]*)"$/', function($world, $arg1, $arg2) {
+    switch ($arg1) {
+    case 'gzipped':
+        var_dump($world->response->body, $arg2, gzencode($arg2));
+        if ($world->response->body != gzencode($arg2)) throw new Exception;
+        break;
+    case 'deflated':
+        if ($world->response->body != gzdeflate($arg2)) throw new Exception;
+        break;
+    case 'compressed':
+        if ($world->response->body != gzcompress($arg2)) throw new Exception;
+        break;
+    }
+});
+
+$steps->Then('/^I add a cache header of "([^"]*)"$/', function($world, $arg1) {
+    if ($arg1 == '') {
+        $world->response->addCacheHeader();
+    } else {
+        $world->response->addCacheHeader($arg1);
+    }
+});
+
+$steps->Then('/^I add an etag header of "([^"]*)"$/', function($world, $arg1) {
+    $world->response->addEtag($arg1);
+});
+
Index: trunk/prototype/library/tonic/features/steps/resource.php
===================================================================
--- trunk/prototype/library/tonic/features/steps/resource.php	(revision 6528)
+++ trunk/prototype/library/tonic/features/steps/resource.php	(revision 6528)
@@ -0,0 +1,14 @@
+<?php
+
+$steps->When('/^execute the request$/', function($world) {
+    $world->response = $world->resource->exec($world->request);
+});
+
+$steps->Then('/^the response code should be "([^"]*)"$/', function($world, $arg1) {
+    if ($world->response->code != $arg1) throw new Exception;
+});
+
+$steps->Then('/^the response body should be \'([^\']*)\'$/', function($world, $arg1) {
+    if ($world->response->body != $arg1) throw new Exception;
+});
+
Index: trunk/prototype/library/tonic/features/steps/request.php
===================================================================
--- trunk/prototype/library/tonic/features/steps/request.php	(revision 6528)
+++ trunk/prototype/library/tonic/features/steps/request.php	(revision 6528)
@@ -0,0 +1,106 @@
+<?php
+
+$steps->Given('/^the request URI of "([^"]*)"$/', function($world, $arg1) {
+    $world->config['uri'] = $arg1;
+});
+
+$steps->Given('/^the request method of "([^"]*)"$/', function($world, $arg1) {
+    $world->config['method'] = $arg1;
+});
+
+$steps->Given('/^the request data of "([^"]*)"$/', function($world, $arg1) {
+    $world->config['data'] = $arg1;
+});
+
+$steps->Given('/^the accept header of "([^"]*)"$/', function($world, $arg1) {
+    $world->config['accept'] = $arg1;
+});
+
+$steps->Given('/^the language accept header of "([^"]*)"$/', function($world, $arg1) {
+    $world->config['acceptLang'] = $arg1;
+});
+
+$steps->Given('/^an if match header of \'([^\']*)\'$/', function($world, $arg1) {
+    $world->config['ifMatch'] = $arg1;
+});
+
+$steps->Given('/^an if none match header of \'([^\']*)\'$/', function($world, $arg1) {
+    $world->config['ifNoneMatch'] = $arg1;
+});
+
+$steps->Given('/^a 404 resource classname of "([^"]*)"$/', function($world, $arg1) {
+    $world->config['404'] = $arg1;
+});
+
+$steps->Given('/^a mounting of "([^"]*)" to "([^"]*)"$/', function($world, $arg1, $arg2) {
+    $world->config['mount'][$arg1] = $arg2;
+});
+
+$steps->Given('/^the querystring is "([^"]*)"$/', function($world, $arg1) {
+    $_SERVER['QUERY_STRING'] = $arg1;
+});
+
+$steps->When('/^I create a request object$/', function($world) {
+    $world->request = new Request($world->config);
+});
+
+$steps->When('/^I load the resource$/', function($world) {
+    $world->resource = $world->request->loadResource();
+});
+
+$steps->Then('/^I should see a request URI of "([^"]*)"$/', function($world, $arg1) {
+    if ($world->request->uri != $arg1) throw new Exception;
+});
+
+$steps->Then('/^I should see a request method of "([^"]*)"$/', function($world, $arg1) {
+    if ($world->request->method != $arg1) throw new Exception;
+});
+
+$steps->Then('/^I should see the request data "([^"]*)"$/', function($world, $arg1) {
+    if ($world->request->data != $arg1) throw new Exception;
+});
+
+$steps->Then('/^I should see a negotiated URI of "([^"]*)"$/', function($world, $arg1) {
+    if ($world->request->negotiatedUris != explode(',', $arg1)) throw new Exception;
+});
+
+$steps->Then('/^I should see a format negotiated URI of "([^"]*)"$/', function($world, $arg1) {
+    if ($world->request->formatNegotiatedUris != explode(',', $arg1)) throw new Exception;
+});
+
+$steps->Then('/^I should see a language negotiated URI of "([^"]*)"$/', function($world, $arg1) {
+    if ($world->request->languageNegotiatedUris != explode(',', $arg1)) throw new Exception;
+});
+
+$steps->Then('/^I should have a response of type "([^"]*)"$/', function($world, $arg1) {
+    if (get_class($world->resource) != $arg1) throw new Exception;
+});
+
+$steps->Then('/^I should see an if match header of "([^"]*)"$/', function($world, $arg1) {
+    if ($world->request->ifMatch != explode(',', $arg1)) throw new Exception;
+});
+
+$steps->Then('/^if match should match "([^"]*)"$/', function($world, $arg1) {
+    if (!$world->request->ifMatch($arg1)) throw new Exception;
+});
+
+$steps->Then('/^I should see an if none match header of "([^"]*)"$/', function($world, $arg1) {
+    if ($world->request->ifNoneMatch != explode(',', $arg1)) throw new Exception;
+});
+
+$steps->Then('/^if none match should match "([^"]*)"$/', function($world, $arg1) {
+    if (!$world->request->ifNoneMatch($arg1)) throw new Exception;
+});
+
+$steps->Then('/^if none match should not match "([^"]*)"$/', function($world, $arg1) {
+    if ($world->request->ifNoneMatch($arg1)) throw new Exception;
+});
+
+$steps->Then('/^I should see resource "([^"]*)" metadata of "([^"]*)"$/', function($world, $arg1, $arg2) {
+    if ($world->request->resources[$world->request->uri][$arg1] != $arg2) throw new Exception;
+});
+
+$steps->Then('/^I should see a querystring of "([^"]*)"$/', function($world, $arg1) {
+    if ($world->request->data != '?'.$arg1) throw new Exception("$arg1 does not equal {$world->request->data}");
+});
+
Index: trunk/prototype/config/Tonic.srv
===================================================================
--- trunk/prototype/config/Tonic.srv	(revision 6528)
+++ trunk/prototype/config/Tonic.srv	(revision 6528)
@@ -0,0 +1,26 @@
+[/token]
+TokenResource = /oauth/TokenResource.php
+
+[/usersldap]
+UsersResource = /user/UsersResource.php
+
+[/userldap/:id]
+UserResource = /user/UserResource.php
+
+[/contacts]
+ContactsResource = /catalog/ContactsResource.php
+
+[/contact/:id]
+ContactResource = /catalog/ContactResource.php
+
+[/groups]
+GroupsResource = /catalog/GroupsResource.php
+
+[/group/:id]
+GroupResource = /catalog/GroupResource.php
+
+[/dynamiccontacts]
+DynamicContactsResource = /catalog/DynamicContactsResource.php
+
+[/dynamiccontact/:id]
+DynamicContactResource = /catalog/DynamicContactResource.php
Index: trunk/prototype/config/contactGroup.ini
===================================================================
--- trunk/prototype/config/contactGroup.ini	(revision 6528)
+++ trunk/prototype/config/contactGroup.ini	(revision 6528)
@@ -0,0 +1,11 @@
+service = PostgreSQL
+PostgreSQL.concept = phpgw_cc_groups
+
+[after.find]
+findGroupConnections = modules/catalog/interceptors/DBMapping.php
+
+[PostgreSQL.mapping]
+id  = id_group
+user = owner
+name = title
+email = short_name 
Index: trunk/prototype/config/oauthCode.ini
===================================================================
--- trunk/prototype/config/oauthCode.ini	(revision 6528)
+++ trunk/prototype/config/oauthCode.ini	(revision 6528)
@@ -0,0 +1,10 @@
+service = PostgreSQL
+PostgreSQL.concept = rest_auth_code
+
+[PostgreSQL.mapping]
+redirect_uri = redirect_uri
+scope = scope
+expires = expires
+code = code
+client_id = client_id
+user_id = user_id
Index: trunk/prototype/config/oauthToken.ini
===================================================================
--- trunk/prototype/config/oauthToken.ini	(revision 6528)
+++ trunk/prototype/config/oauthToken.ini	(revision 6528)
@@ -0,0 +1,11 @@
+service = PostgreSQL
+PostgreSQL.concept = rest_access_token
+
+[PostgreSQL.mapping]
+id = oauth_token
+oauth_token = oauth_token
+client_id = client_id
+user_id = user_id
+expires = expires
+scope = scope
+refresh_token = refresh_token
Index: trunk/prototype/config/contact.ini
===================================================================
--- trunk/prototype/config/contact.ini	(revision 6528)
+++ trunk/prototype/config/contact.ini	(revision 6528)
@@ -0,0 +1,11 @@
+service = PostgreSQL
+PostgreSQL.concept = phpgw_cc_contact
+
+[after.find]
+findConnections = modules/catalog/interceptors/DBMapping.php
+
+[PostgreSQL.mapping]
+id  = id_contact
+user = id_owner
+name = names_ordered 
+nick = alias
Index: trunk/prototype/config/oauthRefreshToken.ini
===================================================================
--- trunk/prototype/config/oauthRefreshToken.ini	(revision 6528)
+++ trunk/prototype/config/oauthRefreshToken.ini	(revision 6528)
@@ -0,0 +1,11 @@
+service = PostgreSQL
+PostgreSQL.concept = rest_refresh_token
+
+[PostgreSQL.mapping]
+id = refresh_token
+refresh_token = refresh_token
+client_id = client_id
+user_id = user_id
+expires = expires
+scope = scope
+refresh_token = refresh_token
Index: trunk/prototype/config/Cyrus.srv
===================================================================
--- trunk/prototype/config/Cyrus.srv	(revision 6528)
+++ trunk/prototype/config/Cyrus.srv	(revision 6528)
@@ -0,0 +1,4 @@
+[config]
+host = 127.0.0.1
+port = 143
+tlsEncryption = true
Index: trunk/prototype/config/dynamicContact.ini
===================================================================
--- trunk/prototype/config/dynamicContact.ini	(revision 6528)
+++ trunk/prototype/config/dynamicContact.ini	(revision 6528)
@@ -0,0 +1,11 @@
+service = PostgreSQL
+PostgreSQL.concept = expressomail_dynamic_contact
+
+
+[PostgreSQL.mapping]
+id = id
+name = name
+mail = mail
+owner = owner
+number_of_messages = number_of_messages
+timestamp = timestamp
Index: trunk/prototype/config/user.ini
===================================================================
--- trunk/prototype/config/user.ini	(revision 6459)
+++ trunk/prototype/config/user.ini	(revision 6528)
@@ -55,2 +55,3 @@
 phpgwAccountVisible = phpgwAccountVisible
 gidNumber = gidNumber
+telephoneNumber=telephoneNumber
Index: trunk/prototype/config/oauthCliente.ini
===================================================================
--- trunk/prototype/config/oauthCliente.ini	(revision 6528)
+++ trunk/prototype/config/oauthCliente.ini	(revision 6528)
@@ -0,0 +1,7 @@
+service = PostgreSQL
+PostgreSQL.concept = rest_client
+
+[PostgreSQL.mapping]
+client_id = client_id
+client_secret = client_secret
+redirect_uri = redirect_uri
Index: trunk/prototype/modules/mail/interceptors/Attachments.php
===================================================================
--- trunk/prototype/modules/mail/interceptors/Attachments.php	(revision 6130)
+++ trunk/prototype/modules/mail/interceptors/Attachments.php	(revision 6528)
@@ -47,4 +47,7 @@
 * @since      Classe disponibilizada na versÃ£o 2.4
 */
+
+use prototype\api\Config as Config;
+
 class Attachments {	
 	
Index: trunk/prototype/modules/mail/interceptors/Helpers.php
===================================================================
--- trunk/prototype/modules/mail/interceptors/Helpers.php	(revision 6385)
+++ trunk/prototype/modules/mail/interceptors/Helpers.php	(revision 6528)
@@ -1,4 +1,6 @@
 <?php
 include_once ROOTPATH."/../expressoMail1_2/inc/class.imap_functions.inc.php";
+
+use prototype\api\Config as Config;
 
 class Helpers {    
Index: trunk/prototype/modules/mail/interceptors/LabelSecure.php
===================================================================
--- trunk/prototype/modules/mail/interceptors/LabelSecure.php	(revision 5540)
+++ trunk/prototype/modules/mail/interceptors/LabelSecure.php	(revision 6528)
@@ -1,3 +1,6 @@
 <?php
+
+use prototype\api\Config as Config;
+
 class LabelSecure {	
 
Index: trunk/prototype/modules/mail/interceptors/FollowupflagSecure.php
===================================================================
--- trunk/prototype/modules/mail/interceptors/FollowupflagSecure.php	(revision 5611)
+++ trunk/prototype/modules/mail/interceptors/FollowupflagSecure.php	(revision 6528)
@@ -1,3 +1,6 @@
 <?php
+
+use prototype\api\Config as Config;
+
 class FollowupflagSecure {	
 	
Index: trunk/prototype/modules/mail/templates/emailBox.ejs
===================================================================
--- trunk/prototype/modules/mail/templates/emailBox.ejs	(revision 6528)
+++ trunk/prototype/modules/mail/templates/emailBox.ejs	(revision 6528)
@@ -0,0 +1,13 @@
+<div class="box <%= (!data.valid ? "invalid-email-box" : "") %>" tabindex="0">
+	<span class="email-box-value" style="padding: 5px;">
+		<% 
+			var presentation = (data.name != "" ? data.name : data.email);
+			var input_value = "&quot;"+data.name+"&quot; <"+data.email+">";
+		%> 
+		<%= 
+			(presentation.length > 18 ? presentation.substring(0, 15)+"..." : presentation)
+		%> 
+	</span>
+	<input class="box-input" value="<%= input_value %>"></input>
+	<span class="box-loading">&nbsp;&nbsp;&nbsp;</span>
+</div>
Index: trunk/prototype/modules/mail/templates/listContacts.ejs
===================================================================
--- trunk/prototype/modules/mail/templates/listContacts.ejs	(revision 6528)
+++ trunk/prototype/modules/mail/templates/listContacts.ejs	(revision 6528)
@@ -0,0 +1,17 @@
+<%if (data.type == 'linha'){%>
+<li>
+	<div class="line-separator">
+		&nbsp;
+	</div>
+<%}else{%>
+<li class="dynamic-<%=(data.type =='' ? 'recent' : (data.type == 'P' ? 'personal' : 'group'))%>">
+	<a style="width:<%= (data.type == '' ? '91%' : '97.5%')%>; display: inline-block;">
+		<img style="position:relative; top:2px; "src="../prototype/modules/mail/img/<%=data.type == '' ? 'recent' : (data.type== 'P' ? 'personal' : 'group')%>.png" title="<%=data.type == '' ? 'Contato Recente' : (data.type== 'P' ? 'Contato Pessoal' : 'Grupo de Contatos')%>"/>
+		<%= (data.name ? data.name + " - " : "") %><%= data.email %>
+		<% if(data.type == ''){ %>
+			<div class="dynamic-stars" style="display: inline-block;float: right;"/>
+		<% } %>
+	</a>
+	<span style="width:16px; height:16px; top:5px; <%=data.type == '' ?  '': 'display:none'%>">Excluir contato recente</span>	
+<%}%>
+</li>
Index: trunk/prototype/modules/mail/templates/new_message.ejs
===================================================================
--- trunk/prototype/modules/mail/templates/new_message.ejs	(revision 6220)
+++ trunk/prototype/modules/mail/templates/new_message.ejs	(revision 6528)
@@ -61,5 +61,8 @@
 				</td>
 				<td class="value" style="width: 100%;">
-					<textarea class="new-message-input to" name="input_to" style="width:99%; resize: none;"></textarea>
+					<div class="email-area">
+						<input class="new-message-input to email-text" name="input_aux_to" type="text" value="" autocomplete="off"/>	
+					</div>
+					<textarea class="new-message-input to" name="input_to" style="width:99%; resize: none;display : none;"></textarea>
 				</td>
 			</tr>
@@ -71,5 +74,8 @@
 				</td>
 				<td class="value">
-					<textarea class="new-message-input cc" name="input_cc" style="width:99%; resize: none;"></textarea>
+					<div class="email-area">
+						<input class="new-message-input cc email-text" name="input_aux_cc" type="text" value="" autocomplete="off"/>	
+					</div>
+					<textarea class="new-message-input cc" name="input_cc" style="width:99%; resize: none;display : none;"></textarea>
 				</td>
 			</tr>
@@ -81,5 +87,8 @@
 				</td>
 				<td class="value">
-					<textarea class="new-message-input cco" name="input_cco" style="width:99%; resize: none;"></textarea>
+					<div class="email-area">
+						<input class="new-message-input cco email-text" name="input_aux_cco" type="text" value="" autocomplete="off"/>	
+					</div>
+					<textarea class="new-message-input cco" name="input_cco" style="width:99%; resize: none;display : none;"></textarea>
 				</td>
 			</tr>
Index: trunk/prototype/modules/home/css/home.css
===================================================================
--- trunk/prototype/modules/home/css/home.css	(revision 6528)
+++ trunk/prototype/modules/home/css/home.css	(revision 6528)
@@ -0,0 +1,123 @@
+.portlets dl{ float:left; margin: 10px 5px; }
+
+.portlets .event-list dt,
+.portlets .mail-list dt,
+.portlets .news-list dt { display:none; }
+
+.portlets .event-list dd,
+.portlets .mail-list dd,
+.portlets .news-list dd { 
+	min-height: 55px; 
+	height: 55px; 
+	width: 400px; 
+	margin:0 0 5px 0; 
+	padding:8px; 
+	overflow:hidden;
+	background-color:#DDD; 
+	border-radius:8px; 
+	-moz-border-radius:8px; 
+	-webkit-border-radius:4px; 
+}
+
+.portlets .event-list .duration { color:#FF7777; font-size:9px;}
+.portlets .event-list .duration:before{ color:#FF7777; content:"[ "}
+.portlets .event-list .duration:after { color:#FF7777; content:" ]"}
+.portlets .event-list .duration .start:after { color:#FF7777; content:" - "}
+
+.portlets .event-list .title,
+.portlets .news-list .title,
+.portlets .mail-list .title
+{ color:#555; font-weight:bold; font-size:13px;}
+
+.portlets .event-list .description,
+.portlets .news-list .description,
+.portlets .mail-list .description
+{ color:#888;}
+
+.portlets .event-list .month {
+    color: #DDDDDD;
+    display: block;
+    font-size: 11px;
+    line-height: 11px;
+    margin-left: -3px;
+    padding-top: 5px;
+    text-align: center;
+    text-transform: uppercase;
+}
+.portlets .event-list .day {
+    color: #999999;
+    display: block;
+    font-size: 18px;
+    line-height: 18px;
+    margin-left: -3px;
+    padding-top: 8px;
+    text-align: center;
+    text-transform: uppercase;
+}
+.portlets .event-list .date {
+    background-attachment: scroll;
+    background-color: transparent;
+    background-image: url("../img/date-bg-red.png");
+    background-position: 0 0;
+    background-repeat: no-repeat;
+    float: left;
+    height: 49px;
+    width: 45px;
+	margin-right: 10px;
+}
+
+/* ----------- */
+
+.portlets .news-list dd { 
+	min-height: 55px; 
+	height: 130px; 
+	width: 400px; 
+}
+
+.portlets .news-list .date { 
+    background-attachment: scroll;
+    background-color: transparent;
+    background-image: url("../img/boletim.png");
+    background-position: 0 0;
+    background-repeat: no-repeat;
+    float: left;
+    height: 125px;
+    width: 110px;
+	margin-right: 10px;
+}
+.portlets .news-list .date .full{ 
+	font-size:8px;
+	color:#1E5AC6;
+	display:block;
+	margin:10px 0 0 15px;	
+}
+.portlets .news-list .duration {color:#FF7777; font-size:9px;}
+
+/* ----------- */
+
+.portlets .mail-list .date { 
+    background-attachment: scroll;
+    background-color: transparent;
+    background-image: url("../img/mail_unread.png");
+    background-position: 0 0;
+    background-repeat: no-repeat;
+    float: left;
+    height: 72px;
+    width: 72px;
+	margin-right: 10px;
+}
+.portlets .mail-list .date .hasCountdown {
+    color: #1E5AC6;
+    display: block;
+    font-size: 8px;
+    margin-bottom: 0;
+    margin-left: 0;
+    margin-right: 0;
+    margin-top: 44px;
+    overflow: hidden;
+    text-align: center;
+    width: 72px;
+}
+.portlets .mail-list .duration {color:#FF7777; font-size:9px;}
+.portlets .mail-list .from:before {content: "De: "; color:#555; font-size:10px; font-weight:normal;}
+.portlets .mail-list .from { color:#555; font-weight:bold; font-size:11px;}
Index: trunk/prototype/modules/home/js/home.js
===================================================================
--- trunk/prototype/modules/home/js/home.js	(revision 6528)
+++ trunk/prototype/modules/home/js/home.js	(revision 6528)
@@ -0,0 +1,30 @@
+$(document).ready(function() {
+	$("#divAppboxHeader").css({fontStyle:'normal'});
+
+//	var carlendarResource = DataLayer.read("/calendarlast");
+//	var tmplCalendar = DataLayer.render("prototype/modules/home/templates/calendarlast.ejs", {resource: carlendarResource});
+
+
+	var mailResource = DataLayer.read("/maillast");
+	var tmplMail = DataLayer.render("prototype/modules/home/templates/maillast.ejs", {resource: mailResource});
+
+//	var newsResource = DataLayer.read("/newslast");
+//	var tmplNews = DataLayer.render("prototype/modules/home/templates/newslast.ejs", {resource: newsResource});
+
+
+//	$(".portlets").append(tmplCalendar);
+	$(".portlets").append(tmplMail);
+//	$(".portlets").append(tmplNews);
+
+
+	jQuery('.date .timable').each(function (i) {
+	    jQuery(this).countdown({
+		    since: new Date(parseInt($(this).text()) * 1000), 
+		    significant: 1,
+		    layout: 'h&aacute; {d<}{dn} {dl} {d>}{h<}{hn} {hl} {h>}{m<}{mn} {ml} {m>}{s<}{sn} {sl}{s>}', 
+		    description: ' atr&aacute;s'
+	    });					
+	});
+
+
+});
Index: trunk/prototype/modules/home/templates/newslast.ejs
===================================================================
--- trunk/prototype/modules/home/templates/newslast.ejs	(revision 6528)
+++ trunk/prototype/modules/home/templates/newslast.ejs	(revision 6528)
@@ -0,0 +1,20 @@
+<dl class="news-list" title="Últimas notícias"> 
+    <%if(data.resource && data.resource.length){
+	for(var i = 0; i < data.resource.length; i++){
+	var date = new Date(parseInt(data.resource[i].startTime+'000'));%>
+	    <dt><%=data.resource[i].subject %></dt>
+	    <dd>
+		<div class="date">
+		    <span class="full"><%= date.toString("ddd, d &#100;e MMMM") %></span>
+		</div>
+		<div class="duration">
+			<span class="start"><%= date.toString("ddd, d &#100;e MMMM &#100;e yyyy") %></span>
+		</div>
+		<div class="title"><%=data.resource[i].subject %></div>
+		<div class="description"><%=data.resource[i].content%></div>
+	    </dd>
+	<%}%>
+	<%}else{%>
+	    <dt>Sem resultados</dt>
+	<%}%>   
+</dl>
Index: trunk/prototype/modules/home/templates/maillast.ejs
===================================================================
--- trunk/prototype/modules/home/templates/maillast.ejs	(revision 6528)
+++ trunk/prototype/modules/home/templates/maillast.ejs	(revision 6528)
@@ -0,0 +1,16 @@
+<dl class="mail-list" title="Últimas e-mails">
+    <%if(data.resource && data.resource.length){
+	for(var i = 0; i < data.resource.length; i++){%>
+	    <dt><%=data.resource[i].subject %></dt>
+	    <dd>
+		<div class="date">
+		    <span class="timestamp timable"><%=data.resource[i].date%></span>
+		</div>
+		<div class="from" title="<%=data.resource[i].mail%>"><%=data.resource[i].from.name%></div>
+		<div class="description"><%=data.resource[i].subject%></div>
+	    </dd>
+	<%}%>
+	<%}else{%>
+	    <dt>Sem resultados</dt>
+	<%}%>    
+</dl> 
Index: trunk/prototype/modules/home/templates/calendarlast.ejs
===================================================================
--- trunk/prototype/modules/home/templates/calendarlast.ejs	(revision 6528)
+++ trunk/prototype/modules/home/templates/calendarlast.ejs	(revision 6528)
@@ -0,0 +1,26 @@
+<dl class="event-list" title="Últimas eventos">
+    <%if(data.resource && data.resource.length){
+	for(var i = 0; i < data.resource.length; i++){
+	var date = new Date(parseInt(data.resource[i].startTime));%>
+	    <dt><%=data.resource[i].summary %></dt>
+	    <dd>
+		<div class="date">
+		    <span class="month"><%= date.toString("MMM") %></span>
+		    <span class="day"><%= date.toString("d") %></span>
+		</div>
+		<div class="duration">
+		    <%if(parseInt(data.resource[i].allDay)){%>
+			<span class="allDay">Durante o dia todo</span>
+		    <%}else{%>
+			<span class="start"><%= date.toString("HH:mm") %></span>
+			<span class="end"><%= new Date(parseInt(data.resource[i].endTime)).toString("HH:mm") %></span>
+		    <%}%>
+		</div>
+		<div class="title"><%=data.resource[i].summary %></div>
+		<div class="description"><%=data.resource[i].description%></div>
+	    </dd>
+	<%}%>
+	<%}else{%>
+	    <dt>Sem resultados</dt>
+	<%}%>    
+</dl>
Index: trunk/prototype/modules/calendar/interceptors/Notifications.php
===================================================================
--- trunk/prototype/modules/calendar/interceptors/Notifications.php	(revision 6331)
+++ trunk/prototype/modules/calendar/interceptors/Notifications.php	(revision 6528)
@@ -5,4 +5,6 @@
 require_once ROOTPATH . '/plugins/icalcreator/iCalcreator.class.php';
 require_once ROOTPATH . '/api/parseTPL.php';
+
+use prototype\api\Config as Config;
 
 class Notifications extends Helpers {
Index: trunk/prototype/modules/calendar/interceptors/DBMapping.php
===================================================================
--- trunk/prototype/modules/calendar/interceptors/DBMapping.php	(revision 6498)
+++ trunk/prototype/modules/calendar/interceptors/DBMapping.php	(revision 6528)
@@ -5,4 +5,6 @@
 
 require_once ROOTPATH . '/modules/calendar/interceptors/Helpers.php';
+
+use prototype\api\Config as Config;
 
 class DBMapping extends Helpers {
Index: trunk/prototype/modules/calendar/interceptors/DAViCalAdapter.php
===================================================================
--- trunk/prototype/modules/calendar/interceptors/DAViCalAdapter.php	(revision 6010)
+++ trunk/prototype/modules/calendar/interceptors/DAViCalAdapter.php	(revision 6528)
@@ -1,4 +1,5 @@
 <?php
 require_once ROOTPATH.'/modules/calendar/constants.php';
+use prototype\api\Config as Config;
 
 class DAViCalAdapter { 
Index: trunk/prototype/modules/calendar/alarms.php
===================================================================
--- trunk/prototype/modules/calendar/alarms.php	(revision 6378)
+++ trunk/prototype/modules/calendar/alarms.php	(revision 6528)
@@ -7,4 +7,6 @@
 require_once ROOTPATH.'/modules/calendar/constants.php';
 require_once ROOTPATH.'/api/parseTPL.php';
+
+use prototype\api\Config as Config;
 
 $target = (gmdate('U') - 300 ).'000';
Index: trunk/prototype/modules/catalog/interceptors/DBMapping.php
===================================================================
--- trunk/prototype/modules/catalog/interceptors/DBMapping.php	(revision 6528)
+++ trunk/prototype/modules/catalog/interceptors/DBMapping.php	(revision 6528)
@@ -0,0 +1,94 @@
+ <?php
+
+//Definindo Constantes
+require_once ROOTPATH . '/modules/catalog/constants.php';
+
+//require_once ROOTPATH . '/modules/calendar/interceptors/Helpers.php';
+
+use prototype\api\Config as Config;
+
+    /**
+     * Métodos que são chamados conforme definições no arquivo contact.ini & contactGroup.ini
+     *
+     * @license    http://www.gnu.org/copyleft/gpl.html GPL
+     * @author     Consórcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+     * @sponsor    Caixa Econômica Federal
+     * @author     José Vicente Tezza Jr. e Gustavo Pereira dos Santos Stabelini
+     * @return     Métodos que são chamados conforme definições no arquivo contact.ini & contactGroup.ini
+     * @access     public
+     * */
+	 
+class DBMapping {
+
+
+    public function findConnections(&$uri, &$params, &$criteria, $original) {
+
+		if(count($params)>0 && isset($params[0]['id'])){
+			for($i=0; $i < count($params); $i++){
+				//Retorna o telefone e o e-mail padrao de um determinado contato
+				$sql = ' SELECT phpgw_cc_contact_conns.id_typeof_contact_connection as type, phpgw_cc_connections.connection_value as value '
+					.'FROM phpgw_cc_contact_conns '
+					.'JOIN phpgw_cc_connections '
+					.'ON (phpgw_cc_connections.id_connection = phpgw_cc_contact_conns.id_connection) '
+					.'WHERE phpgw_cc_contact_conns.id_contact = ' . $params[$i]['id'] . ' AND '
+					.'phpgw_cc_connections.connection_is_default = TRUE ';
+
+				$array = Controller::service('PostgreSQL')->execResultSql($sql);
+				if(count($array)>0){
+					foreach($array as $connection){
+						switch($connection['type']){
+							case TYPE_EMAIL 	: $params[$i][INDEX_EMAIL] 	= $connection['value']; break;
+							case TYPE_TELEPHONE 	: $params[$i][INDEX_TELEPHONE] 	= $connection['value']; break;
+							default			: $params[$i][INDEX_EMAIL] = $params[$i][INDEX_TELEPHONE] = '';
+						}
+
+					}
+				}
+				else{
+					$params[$i][INDEX_EMAIL] = $params[$i][INDEX_TELEPHONE] = '';
+				}
+			}
+		}
+    }
+
+	public function findGroupConnections(&$uri, &$params, &$criteria, $original) {
+
+		if(count($params)>0 && isset($params[0]['id'])){
+		$z = 0;
+		$count = count($params);
+		for($i=0; $i < $count; $i++){
+				//Retorna o telefone e o e-mail padrao de um determinado contato
+				$sql = 'SELECT contato.names_ordered as name, contato.id_contact as id, conexao.connection_value as value '
+					.'FROM phpgw_cc_groups grupo '
+					.'JOIN phpgw_cc_contact_grps grupo_contato '
+					.'ON (grupo.id_group = grupo_contato.id_group) '
+					.'JOIN phpgw_cc_connections conexao '
+					.'ON (grupo_contato.id_connection = conexao.id_connection) '
+					.'JOIN phpgw_cc_contact_conns conexaoCon '
+					.'ON (conexao.id_connection = conexaoCon.id_connection) '
+					.'JOIN phpgw_cc_contact contato '
+					.'ON (contato.id_contact = conexaoCon.id_contact) '
+					.'WHERE grupo.id_group = ' . $params[$i]['id'] . ' AND '
+					.'conexao.connection_is_default = TRUE';
+
+				$array = Controller::service('PostgreSQL')->execResultSql($sql);
+
+				if(count($array)>0){
+					$params[$i]['contacts'][$z] = array();
+					foreach($array as $connection){
+						$params[$i]['contacts'][$z]['id'] = $connection['id'];		
+						$params[$i]['contacts'][$z]['name'] = $connection['name'];							
+						$params[$i]['contacts'][$z][INDEX_EMAIL] = $connection['value'];							
+						$z++;
+					}
+				}
+				else{					
+					$params[$i]['contacts'] = null;
+				}
+			}
+		}
+    }
+	
+}
+
+?>
Index: trunk/prototype/modules/catalog/constants.php
===================================================================
--- trunk/prototype/modules/catalog/constants.php	(revision 6528)
+++ trunk/prototype/modules/catalog/constants.php	(revision 6528)
@@ -0,0 +1,19 @@
+<?php
+
+    /**
+     * Define constantes usadas pelo DBMapping
+     *
+     * @license    http://www.gnu.org/copyleft/gpl.html GPL
+     * @author     Consórcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+     * @sponsor    Caixa Econômica Federal
+     * @author     José Vicente Tezza Jr. 
+     * @return     Define constantes usadas pelo DBMapping
+     * @access     public
+     * */
+	 
+define('TYPE_EMAIL', 1);
+define('TYPE_TELEPHONE', 2);
+
+define('INDEX_EMAIL', 'email' );
+define('INDEX_TELEPHONE', 'telephone');
+?>
Index: trunk/prototype/api/newcontroller.php
===================================================================
--- trunk/prototype/api/newcontroller.php	(revision 5715)
+++ trunk/prototype/api/newcontroller.php	(revision 6528)
@@ -5,4 +5,5 @@
 
 require_once(ROOTPATH.'/api/config.php');
+use prototype\api\Config as Config;
 
 /**
Index: trunk/prototype/api/rest.js
===================================================================
--- trunk/prototype/api/rest.js	(revision 6528)
+++ trunk/prototype/api/rest.js	(revision 6528)
@@ -0,0 +1,172 @@
+REST = {
+    
+    resquests: {},
+    
+    taksResquests: {},
+    
+    isProcessRefreshToken: false,
+
+    /**
+    * This method is used to read resources from the server.
+    * 
+    * @param uri Uri of the resource that gonna be readed.
+    * @param callback A function that is called when the resource is loaded successfully. When the parameter is ignored the request is made synchrounsly.
+    * @param accept The attribute accept that is used to ask the target format to the server
+    * @return The target resource when the callback is ommitted ( made it synchronous )
+    */
+
+    get: function( uri, data, callback, accept ){
+	return this.send( (this.dispatchPath + '../rest' + uri), [ 'get', accept || 'json' ], data, callback, !!!callback);
+      
+    },
+
+    /**
+    * This method is used to create resources from the server.
+    * 
+    * @param uri Uri of the resource that gonna be readed.
+    * @param callback A function that is called when the resource is created on the server successfully. When the parameter is ignored the request is made synchrounsly.
+    * @param accept The attribute accept that is used to ask the target format to the server.
+    * @return The result response of the create from the server when the callback is ommitted ( made it synchronous )
+    */
+    
+    post: function( uri, data, callback, accept ){
+	return this.send( (this.dispatchPath + '../rest' + uri), [ 'post', accept || 'json' ], data, callback, !!!callback);      
+    },
+
+    /**
+    * This method is used to update resources from the server.
+    * 
+    * @param uri Uri of the resource that gonna be readed.
+    * @param callback A function that is called when the resource is update on the server successfully. When the parameter is ignored the request is made synchrounsly.
+    * @param accept The attribute accept that is used to ask the target format to the server
+    * @return The result response of the update from the server when the callback is ommitted ( made it synchronous )
+    */
+
+    put: function( uri, data, callback, accept ){
+	return this.send( (this.dispatchPath + '../rest' + uri), [ 'put', accept || 'json' ], data, callback, !!!callback);
+      
+    },
+
+    /**
+    * This method is used to delete resources from the server.
+    * 
+    * @param uri Uri of the resource that gonna be readed.
+    * @param callback A function that is called when the resource is deleted successfully in the server. When the parameter is ignored the request is made synchrounsly.
+    * @param accept The attribute accept that is used to ask the target format to the server
+    * @return The result response of the delete from the server when the callback is ommitted ( made it synchronous )
+    */
+
+    "delete": function( uri, callback, accept ){
+	return this.send( (this.dispatchPath + '../rest' + uri), [ 'delete', accept || 'json' ], false, callback, !!!callback);
+    },
+    
+    send: function( url, type, data, callback, sync, extraOptions ){
+	this.id = url;
+
+	if(REST.isProcessRefreshToken && !!callback){
+
+	    REST.taksResquests[REST.taksResquests.length] = {
+		url: url, 
+		type: type, 
+		data: data, 
+		callback: callback, 
+		sync: sync, 
+		extraOptions: extraOptions
+	    };
+
+	}else if(REST.isProcessRefreshToken)
+	    setTimeout(REST.send(url, type, data, callback, sync, extraOptions), 100);
+
+	REST.resquests[url] = {
+	    url: url, 
+	    type: type, 
+	    data: data, 
+	    callback: callback, 
+	    sync: sync, 
+	    extraOptions: extraOptions
+	};    
+      
+	var result = false;      
+	var envelope = {
+
+	    async: ( typeof sync !== "undefined" ? !sync : !!callback ),
+	    url: url,
+	    success: function( dt, textStatus, jqXHR ){
+
+		if(REST.isInvalidToken(dt)){
+		    REST.load('', true);
+		    
+		    if(!REST.me)
+			return {error: 'Empty session', title: 'Error in refresh token', description: 'Error in refresh token.'};
+		    
+		    var ref = REST.resquests[this.url];
+		    result =  REST.send(ref.url, ref.type, ref.data, ref.callback, ref.sync, ref.extraOptions)
+		}
+		delete REST.resquests[this.url];
+		
+		if(result == false){
+
+		    if( callback )
+		    {
+			result = callback( dt, textStatus, jqXHR );
+		    }
+		    else
+			result = dt;
+		    }
+
+	    },
+	    error: function( dt, textStatus, jqXHR ){
+		
+		var response = {error: 'error', status: dt.status, description: dt.responseText, statusText: dt.responseText};
+
+		if( callback )
+		{
+		    result = callback( response, textStatus, jqXHR );
+		}
+		else
+		    result = response;
+
+	    },
+
+	    type: $.isArray( type ) ? type[0] : type,
+	    data: data
+
+	};
+
+	if( $.isArray( type ) && type[1] )
+	    envelope['dataType'] = type[1];
+
+	if(REST.me)
+	    envelope = $.extend( envelope, {
+		beforeSend: function (xhr){
+		    xhr.setRequestHeader('Authorization', "OAUTH Bearer " + REST.me.token)
+		}
+	    });
+
+	if( extraOptions )
+	    envelope = $.extend( envelope, extraOptions );
+
+	$.ajax( envelope );
+      
+	return( result );
+    },
+    
+    isInvalidToken: function(data){
+	return ((data) && (data.error && data.error == 'invalid_grant') && (data.error_description == 'The access token provided has expired.')) ? true : false
+    },
+    
+    dispatch: function( dispatcher, data, callback, isPost, dataType ){
+	return this.send( this.dispatchPath + dispatcher + ".php", 
+	    [ ( isPost ? 'post' : 'get' ), dataType || 'json' ],
+	    data, callback );
+    },
+
+    dispatchPath: '../prototype',
+
+    load: function(url, isRefresh){
+	this.me = this.dispatch( (url || '') + "me", (isRefresh ? {
+	    refreshToken: true
+	} : {}), false, true)	
+    }
+    
+}
Index: trunk/prototype/api/controller.php
===================================================================
--- trunk/prototype/api/controller.php	(revision 6419)
+++ trunk/prototype/api/controller.php	(revision 6528)
@@ -44,5 +44,5 @@
 
 require_once(ROOTPATH.'/api/config.php');
-
+use prototype\api\Config as Config;
 /**
 TODO list:
Index: trunk/prototype/api/config.php
===================================================================
--- trunk/prototype/api/config.php	(revision 5764)
+++ trunk/prototype/api/config.php	(revision 6528)
@@ -1,100 +1,113 @@
 <?php
+
+namespace prototype\api;
 
 class Config 
 {
-    static $register;
-    static $sessionStarted;
+	static $register;
+	static $sessionStarted;
   
-    static function module($config , $module = false)
-    {
-        //Todo: registrar na nova api o currentapp
-        if(!$module)
-            $module =  $_SESSION['flags']['currentapp'];
+	static function module($config , $module = false)
+	{
+		//Todo: registrar na nova api o currentapp
+		if(!$module)
+				$module =  $_SESSION['flags']['currentapp'];
   
-        if( !isset( $_SESSION['config'][$module] ) || !isset( $_SESSION['config'][$module][$config] ))
-            $_SESSION['config'][$module] = parse_ini_file( ROOTPATH."/config/$module.ini", true );
+		if( !isset( $_SESSION['config'][$module] ) || !isset( $_SESSION['config'][$module][$config] ))
+				$_SESSION['config'][$module] = parse_ini_file( ROOTPATH."/config/$module.ini", true );
         
-        return isset($_SESSION['config'][$module][$config]) ? $_SESSION['config'][$module][$config] : false;
+		return isset($_SESSION['config'][$module][$config]) ? $_SESSION['config'][$module][$config] : false;
      
-    }
+	}
     
-    static function me($config)
-    {
+	static function me($config)
+	{
         
-        return isset($_SESSION['wallet']['user'][$config]) ? $_SESSION['wallet']['user'][$config] : false;
-    }
+		return isset($_SESSION['wallet']['user'][$config]) ? $_SESSION['wallet']['user'][$config] : false;
+	}
     
-    static function service( $service , $config )
-    {
-        if( !isset( $_SESSION['wallet'][$service] ) || !isset( $_SESSION['wallet'][$service][$config] ))
-            $_SESSION['wallet'][$service] = parse_ini_file( ROOTPATH."/config/$service.srv", true );
+	static function service( $service , $config )
+	{
+		if( !isset( $_SESSION['wallet'][$service] ) || !isset( $_SESSION['wallet'][$service][$config] ))
+				$_SESSION['wallet'][$service] = parse_ini_file( ROOTPATH."/config/$service.srv", true );
        
-        return (isset($_SESSION['wallet'][$service][$config])) ? $_SESSION['wallet'][$service][$config] : false;
-    }
+		return (isset($_SESSION['wallet'][$service][$config])) ? $_SESSION['wallet'][$service][$config] : false;
+	}
     
-    static function get( $concept , $config = false , $module = false )
-    {
-        $load = parse_ini_file( ROOTPATH."/config/$concept.ini", true );
+	static function get( $concept , $config = false , $module = false )
+	{
+		$load = parse_ini_file( ROOTPATH."/config/$concept.ini", true );
         
-        if($config === false) return $load;
+		if($config === false) return $load;
         
-        return (isset($load[$config])) ? $load[$config] : false;
-    }
+		return (isset($load[$config])) ? $load[$config] : false;
+	}
     
-    static function regSet( $name , $value)
-    {
-        self::$register[$name] = $value;
-    }
-    static function regGet ($name )
-    {
-        return (isset(self::$register[$name]) ? self::$register[$name] : false );
-    }
+	static function regSet( $name , $value)
+	{
+		self::$register[$name] = $value;
+	}
+	static function regGet ($name )
+	{
+		return (isset(self::$register[$name]) ? self::$register[$name] : false );
+	}
 
-    static function init( )
-    {
+	static function init( )
+	{
+    
+		if( !defined( 'ROOTPATH' ) )
+				define( 'ROOTPATH', dirname(__FILE__).'/..' );
         
-        if( !defined( 'ROOTPATH' ) )
-            define( 'ROOTPATH', dirname(__FILE__).'/..' );
-        
-        if ( isset( $_COOKIE[ 'sessionid' ] ) ) 
-        {
-            session_id( $_COOKIE[ 'sessionid' ] ); 
-            $GLOBALS['phpgw']->session->sessionid = $_COOKIE[ 'sessionid' ];
-        }
+		if ( isset( $_COOKIE[ 'sessionid' ] ) ) 
+		{
+				session_id( $_COOKIE[ 'sessionid' ] ); 
+				$GLOBALS['phpgw']->session->sessionid = $_COOKIE[ 'sessionid' ];
+		}
 		
 	if( !self::$sessionStarted )
-            self::$sessionStarted = session_start();
-//		
-//	if( $header === true )
-//	{
-//            require_once (dirname(__FILE__).'/../../header.inc.php');
-//
-//            $_SESSION['wallet']['Sieve']['user']          =  $GLOBALS['phpgw_info']['user']['account_lid'];
-//            $_SESSION['wallet']['Sieve']['password']      =  $GLOBALS['phpgw_info']['user']['passwd'];
-//
-//            $_SESSION['wallet']['Cyrus']['user']          =  $GLOBALS['phpgw_info']['user']['account_lid'];
-//            $_SESSION['wallet']['Cyrus']['password']      =  $GLOBALS['phpgw_info']['user']['passwd'];
-//
-//            $_SESSION['wallet']['user']['uid']	      	  =  $GLOBALS['phpgw_info']['user']['userid'];
-//            $_SESSION['wallet']['user']['uidNumber']      =  $GLOBALS['phpgw_info']['user']['account_id'];
-//            $_SESSION['wallet']['user']['password']       =  $GLOBALS['phpgw_info']['user']['passwd'];
-//            $_SESSION['wallet']['user']['cn']             =  $GLOBALS['phpgw_info']['user']['cn'];
-//            $_SESSION['wallet']['user']['mail']           =  $GLOBALS['phpgw_info']['user']['email'];
-//
-//            $_SESSION['wallet']['PostgreSQL']['user']     = $GLOBALS['phpgw_info']['server']['db_user'];
-//            $_SESSION['wallet']['PostgreSQL']['password'] = $GLOBALS['phpgw_info']['server']['db_pass'];
-//            $_SESSION['wallet']['PostgreSQL']['dbname']   = $GLOBALS['phpgw_info']['server']['db_name'];
-//            $_SESSION['wallet']['PostgreSQL']['host']     = $GLOBALS['phpgw_info']['server']['db_host'];
-//            
-//            $_SESSION['wallet']['OpenLDAP']['context']   =  $GLOBALS['phpgw_info']['server']['ldap_context'];
-//            $_SESSION['wallet']['OpenLDAP']['host']      =  $GLOBALS['phpgw_info']['server']['ldap_host'];
-//           
-//	}
+				self::$sessionStarted = session_start();
 		
-    }
+	}
+    
+	public static function writeIniFile($assoc_arr, $path, $has_sections)
+	{
+		$content = '';	
+		self::_writeIniFile($content, $assoc_arr, $has_sections);
+		if( file_put_contents($path, $content) === false)
+		{
+			trigger_error("Permission failure when trying to write in the file: $path ", E_USER_WARNING);
+			return false;
+		}
+		return true;
+	}
+
+	private static function _writeIniFile(&$content, $assoc_arr, $has_sections)
+	{
+		foreach ($assoc_arr as $key => $val)
+		{
+			if (is_array($val))
+			{
+				if($has_sections)
+				{
+					$content .= "[$key]\n";
+					self::_writeIniFile(&$content, $val, false);
+				}
+				else				
+					foreach($val as $iKey => $iVal)
+					{
+						if (is_int($iKey))
+							$content .= $key ."[] = $iVal\n";
+						else
+							$content .= $key ."[$iKey] = $iVal\n";
+					}
+			}
+			else
+				$content .= "$key = $val\n";
+		}
+	}
 
 }
 
-    Config::init();
+	Config::init();
 	
 
Index: trunk/prototype/rest/oauth/authorize.php
===================================================================
--- trunk/prototype/rest/oauth/authorize.php	(revision 6528)
+++ trunk/prototype/rest/oauth/authorize.php	(revision 6528)
@@ -0,0 +1,67 @@
+<?php
+/**
+ * @file
+ * Sample authorize endpoint.
+ *
+ * This sample provides two click-jacking prevention methods, neither which are perfect. 
+ * The javascript solution is similar to what facebook used to have (but can be defeated with a 
+ * specially crafted frame-wrapper).
+ */
+
+// Clickjacking prevention (supported by IE8+, FF3.6.9+, Opera10.5+, Safari4+, Chrome 4.1.249.1042+)
+header('X-Frame-Options: DENY');
+
+require "lib/OAuth2StoragePdo.php";
+
+/*
+ * You would need to authenticate the user before authorization.
+ * 
+ * Below is some psudeo-code to show what you might do:
+ * 
+session_start();
+if (!isLoggedIn()) {
+	redirectToLoginPage();
+	exit();
+}
+ */
+
+$oauth = new OAuth2(new OAuth2StoragePDO());
+
+if ($_POST) {
+	$userId = '12345'; // Use whatever method you have for identifying users.
+	$oauth->finishClientAuthorization($_POST["accept"] == "Yep", $userId, $_POST);
+}
+
+try {
+	$auth_params = $oauth->getAuthorizeParams();
+} catch (OAuth2ServerException $oauthError) {
+	$oauthError->sendHttpResponse();
+}
+
+?>
+<html>
+<head>
+<title>Authorize</title>
+<script>
+	if (top != self) {
+		window.document.write("<div style='background:black; opacity:0.5; filter: alpha (opacity = 50); position: absolute; top:0px; left: 0px;"
+		+ "width: 9999px; height: 9999px; zindex: 1000001' onClick='top.location.href=window.location.href'></div>");
+	}
+  </script>
+</head>
+<body>
+<form method="post" action="authorize.php">
+ 
+    
+      <?php 
+      foreach ($auth_params as $key => $value) : ?>
+      	<input type="hidden"
+	name="<?php echo htmlspecialchars($key, ENT_QUOTES); ?>"
+	value="<?php echo htmlspecialchars($value, ENT_QUOTES); ?>" />
+      <?php endforeach; ?>
+      Do you authorize the app to do its thing?
+      <p><input type="submit" name="accept" value="Yep" /> <input
+	type="submit" name="accept" value="Nope" /></p>
+</form>
+</body>
+</html>
Index: trunk/prototype/rest/oauth/protected_resource.php
===================================================================
--- trunk/prototype/rest/oauth/protected_resource.php	(revision 6528)
+++ trunk/prototype/rest/oauth/protected_resource.php	(revision 6528)
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Sample protected resource.
+ *
+ * Obviously not production-ready code, just simple and to the point.
+ *
+ * In reality, you'd probably use a nifty framework to handle most of the crud for you.
+ */
+
+require "lib/OAuth2StoragePdo.php";
+
+try {
+	$oauth = new OAuth2(new OAuth2StoragePDO());
+	$token = $oauth->getBearerToken();
+	$oauth->verifyAccessToken($token);
+} catch (OAuth2ServerException $oauthError) {
+	$oauthError->sendHttpResponse();
+}
+
+// With a particular scope, you'd do:
+// $oauth->verifyAccessToken("scope_name");
+
+
+?>
+
+<html>
+	<head>
+		<title>Hello!</title>
+	</head>
+	<body>
+		<p>This is a secret.</p>
+	</body>
+</html>
Index: trunk/prototype/rest/oauth/OAuth2StorageUserCredential.php
===================================================================
--- trunk/prototype/rest/oauth/OAuth2StorageUserCredential.php	(revision 6528)
+++ trunk/prototype/rest/oauth/OAuth2StorageUserCredential.php	(revision 6528)
@@ -0,0 +1,279 @@
+<?php
+
+require ROOTPATH . '/library/oauth2/lib/OAuth2.php';
+require ROOTPATH . '/library/oauth2/lib/IOAuth2Storage.php';
+require ROOTPATH . '/library/oauth2/lib/IOAuth2GrantCode.php';
+require ROOTPATH . '/library/oauth2/lib/IOAuth2RefreshTokens.php';
+
+require ROOTPATH . '/library/oauth2/lib/IOAuth2GrantUser.php';
+
+
+class OAuth2StorageUserCredential implements IOAuth2GrantUser, IOAuth2RefreshTokens {
+
+	public function checkUserCredentials($client_id, $username, $password) {
+	    
+		//Authentica no LDAP
+		$usuario =  Controller::find(array('concept'=>'user' , 'service' => 'OpenLDAP'), false , array('filter' => array('AND', array('=' , 'uid' , $username ), array('=' , 'password', '{md5}' . base64_encode(pack("H*",md5($password))))  ) ));
+
+		if(isset($usuario[0]['id']))
+		{
+		    return array('scope' => 'all' , 'user_id' => $usuario[0]['id']);
+		}
+
+		return false;
+	}
+	
+	/**
+	 * Make sure that the client credentials is valid.
+	 * 
+	 * @param $client_id
+	 * Client identifier to be check with.
+	 * @param $client_secret
+	 * (optional) If a secret is required, check that they've given the right one.
+	 *
+	 * @return
+	 * TRUE if the client credentials are valid, and MUST return FALSE if it isn't.
+	 * @endcode
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-3.1
+	 *
+	 * @ingroup oauth2_section_3
+	 */
+	public function checkClientCredentials($client_id, $client_secret = NULL) {
+		if($client_secret == NULL) {
+			return false;
+		}
+
+		$res =  Controller::find(array('concept' => 'oauthCliente'), array('client_secret') , array('filter' => array('=' , 'client_id' , $client_id)) );
+
+		if(isset($res[0]['client_secret']) && $res[0]['client_secret'] == $client_secret) {
+			return true;
+		}
+		else {
+			return false;
+		}
+	}
+
+	/**
+	 * Get client details corresponding client_id.
+	 *
+	 * OAuth says we should store request URIs for each registered client.
+	 * Implement this function to grab the stored URI for a given client id.
+	 *
+	 * @param $client_id
+	 * Client identifier to be check with.
+	 *
+	 * @return array
+	 * Client details. Only mandatory item is the "registered redirect URI", and MUST
+	 * return FALSE if the given client does not exist or is invalid.
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	public function getClientDetails($client_id) {
+		$res =  Controller::find(array('concept' => 'oauthCliente'), array('redirect_uri') , array('filter' => array('=' , 'client_id' , $client_id)));
+		if(isset($res[0]['redirect_uri'])) {
+			return $res[0]['redirect_uri'];
+		}
+		else {
+			return false;
+		}
+	}
+
+	/**
+	 * Look up the supplied oauth_token from storage.
+	 *
+	 * We need to retrieve access token data as we create and verify tokens.
+	 *
+	 * @param $oauth_token
+	 * oauth_token to be check with.
+	 *
+	 * @return
+	 * An associative array as below, and return NULL if the supplied oauth_token
+	 * is invalid:
+	 * - client_id: Stored client identifier.
+	 * - expires: Stored expiration in unix timestamp.
+	 * - scope: (optional) Stored scope values in space-separated string.
+	 *
+	 * @ingroup oauth2_section_7
+	 */
+	public function getAccessToken($oauth_token) {
+		return $this->getToken($oauth_token, false);
+	}
+
+	/**
+	 * Take the provided refresh token values and store them somewhere.
+	 *
+	 * This function should be the storage counterpart to getRefreshToken().
+	 *
+	 * If storage fails for some reason, we're not currently checking for
+	 * any sort of success/failure, so you should bail out of the script
+	 * and provide a descriptive fail message.
+	 *
+	 * Required for OAuth2::GRANT_TYPE_REFRESH_TOKEN.
+	 *
+	 * @param $refresh_token
+	 * Refresh token to be stored.
+	 * @param $client_id
+	 * Client identifier to be stored.
+	 * @param $expires
+	 * expires to be stored.
+	 * @param $scope
+	 * (optional) Scopes to be stored in space-separated string.
+	 *
+	 * @ingroup oauth2_section_6
+	 */
+	public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = NULL) {
+		
+		$data = array();
+		$data['refresh_token']  = $refresh_token;
+		$data['client_id'] = $client_id;
+		$data['user_id'] = $user_id;
+		$data['expires'] = $expires;
+		$data['scope'] = $scope;
+
+	        Controller::create(array('concept' => 'oauthRefreshToken'), $data);
+	}
+	
+	/**
+	 * Store the supplied access token values to storage.
+	 *
+	 * We need to store access token data as we create and verify tokens.
+	 *
+	 * @param $oauth_token
+	 * oauth_token to be stored.
+	 * @param $client_id
+	 * Client identifier to be stored.
+	 * @param $user_id
+	 * User identifier to be stored.
+	 * @param $expires
+	 * Expiration to be stored.
+	 * @param $scope
+	 * (optional) Scopes to be stored in space-separated string.
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = NULL, $refresh_token) {
+	   
+		$data = array();
+		$data['oauth_token']  = $oauth_token;
+		$data['client_id'] = $client_id;
+		$data['user_id'] = $user_id;
+		$data['expires'] = $expires;
+		$data['scope'] = $scope;
+		$data['refresh_token'] = $refresh_token;
+		
+		Controller::create(array('concept' => 'oauthToken'), $data);
+
+	}
+
+	/**
+	 * Check restricted grant types of corresponding client identifier.
+	 *
+	 * If you want to restrict clients to certain grant types, override this
+	 * function.
+	 *
+	 * @param $client_id
+	 * Client identifier to be check with.
+	 * @param $grant_type
+	 * Grant type to be check with, would be one of the values contained in
+	 * OAuth2::GRANT_TYPE_REGEXP.
+	 *
+	 * @return
+	 * TRUE if the grant type is supported by this client identifier, and
+	 * FALSE if it isn't.
+	 *
+	 * @ingroup oauth2_section_4
+	 */
+	public function checkRestrictedGrantType($client_id, $grant_type) {
+		/*TODO: essa funÃ§Ã£o deve verificar se o cliente tem permissao para realizar o login via 'password'
+		 * deixe para implementar no prox. sprint, quando trataremos de seguranca. Deve-se criar um campo extra no banco para guardar quais tipos
+		 * de grant_type o cliente esta autorizado a fazer
+		*/
+
+		return true;		
+	}
+
+	/**
+	 * Grant refresh access tokens.
+	 *
+	 * Retrieve the stored data for the given refresh token.
+	 *
+	 * Required for OAuth2::GRANT_TYPE_REFRESH_TOKEN.
+	 *
+	 * @param $refresh_token
+	 * Refresh token to be check with.
+	 *
+	 * @return
+	 * An associative array as below, and NULL if the refresh_token is
+	 * invalid:
+	 * - client_id: Stored client identifier.
+	 * - expires: Stored expiration unix timestamp.
+	 * - scope: (optional) Stored scope values in space-separated string.
+	 *
+	 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-6
+	 *
+	 * @ingroup oauth2_section_6
+	 */
+	public function getRefreshToken($refresh_token) {
+		return $this->getToken($refresh_token, true);
+	}
+
+	/**
+	 * Expire a used refresh token.
+	 *
+	 * This is not explicitly required in the spec, but is almost implied.
+	 * After granting a new refresh token, the old one is no longer useful and
+	 * so should be forcibly expired in the data store so it can't be used again.
+	 *
+	 * If storage fails for some reason, we're not currently checking for
+	 * any sort of success/failure, so you should bail out of the script
+	 * and provide a descriptive fail message.
+	 *
+	 * @param $refresh_token
+	 * Refresh token to be expirse.
+	 *
+	 * @ingroup oauth2_section_6
+	 */
+	public function unsetRefreshToken($refresh_token) {
+		Controller::delete(
+			array('concept' => 'oauthRefreshToken'),
+			false,
+			array('filter' => array('=', 'refresh_token', $refresh_token ))
+		);
+	}
+	public function unsetAccessToken($refresh_token) {
+		Controller::delete(
+			array('concept' => 'oauthToken'),
+			false,
+			array('filter' => array('=', 'oauth_token', $refresh_token ))
+		);
+	}
+
+	/**
+	 * Retrieves an access or refresh token.
+	 * 
+	 * @param string $token
+	 * @param bool $refresh
+	 */
+	protected function getToken($token, $isRefresh = true) {
+
+		$tokenConcept = $isRefresh ? 'oauthRefreshToken' : 'oauthToken';
+		$filter =  $isRefresh ? 'refresh_token' : 'oauth_token';
+		
+		$res = Controller::find(
+			array('concept' => $tokenConcept), 
+			false, 
+			array('filter' => array('=' , $filter , $token))
+		);
+
+		/*return (!empty($res[0]))? $res[0] : false;*/
+		if(!empty($res[0])) {
+			return $res[0];
+		}
+		else {
+			return false;
+		}
+	}
+}
+
+
Index: trunk/prototype/rest/oauth/addclient.php
===================================================================
--- trunk/prototype/rest/oauth/addclient.php	(revision 6528)
+++ trunk/prototype/rest/oauth/addclient.php	(revision 6528)
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Sample client add script.
+ *
+ * Obviously not production-ready code, just simple and to the point.
+ */
+
+require "lib/OAuth2StoragePdo.php";
+
+if ($_POST && isset($_POST["client_id"]) && isset($_POST["client_secret"]) && isset($_POST["redirect_uri"])) {
+	$oauth = new OAuth2StoragePDO();
+	$oauth->addClient($_POST["client_id"], $_POST["client_secret"], $_POST["redirect_uri"]);
+}
+
+?>
+
+<html>
+  <head>
+  	<title>Add Client</title>
+  </head>
+  <body>
+    <form method="post" action="addclient.php">
+      <p>
+        <label for="client_id">Client ID:</label>
+        <input type="text" name="client_id" id="client_id" />
+      </p>
+      <p>
+        <label for="client_secret">Client Secret (password/key):</label>
+        <input type="text" name="client_secret" id="client_secret" />
+      </p>
+      <p>
+        <label for="redirect_uri">Redirect URI:</label>
+        <input type="text" name="redirect_uri" id="redirect_uri" />
+      </p>
+      <input type="submit" value="Submit" />
+    </form>
+  </body>
+</html>
Index: trunk/prototype/rest/oauth/TokenResource.php
===================================================================
--- trunk/prototype/rest/oauth/TokenResource.php	(revision 6528)
+++ trunk/prototype/rest/oauth/TokenResource.php	(revision 6528)
@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * @uri /token
+ */
+
+require_once dirname(__FILE__).'/../../api/controller.php';
+
+require_once 'OAuth2StorageUserCredential.php';
+
+class TokenResource extends Resource {
+
+	public function post($request) {
+		$res = new Response($request);
+
+		try {
+			$oauth = new OAuth2(new Oauth2StorageUserCredential());
+			$oauth->grantAccessToken($_POST);
+		} 
+		catch (OAuth2ServerException $oauthError) {
+			$oauthError->sendHttpResponse();
+		}
+		
+		return $res;
+	}
+
+}
+
+?>
Index: trunk/prototype/rest/mail/MailLastResource.php
===================================================================
--- trunk/prototype/rest/mail/MailLastResource.php	(revision 6528)
+++ trunk/prototype/rest/mail/MailLastResource.php	(revision 6528)
@@ -0,0 +1,99 @@
+<?php
+ 
+use prototype\api\Config as Config;
+
+class MailLastResource extends Resource {
+ 
+    /**
+    * Busca as Ãºltimas 20 menssagens nÃ£o lidas do usuÃ¡rio
+    *
+    * @license    http://www.gnu.org/copyleft/gpl.html GPL
+    * @author     ConsÃ³rcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+    * @sponsor    Caixa EconÃŽmica Federal
+    * @author     Cristiano CorrÃªa Schmidt
+    * @return     Lista das ultimas 10 menssagens do usuÃ¡rio nÃ£o lidas
+    * @access     public
+    **/
+    function get($request) 
+    {
+	$this->secured();
+	
+        $response = new Response($request);
+        $response->code = Response::OK;
+        $response->addHeader('Content-type', 'aplication/json');
+		
+	$cyrus = Config::service('Cyrus', 'config');
+	$options = ($cyrus['tlsEncryption']) ? '/tls/novalidate-cert' : '/notls/novalidate-cert'; 
+	$mbox = imap_open( '{'.$cyrus['host'].":".$cyrus['port'].$options.'}INBOX' , Config::me('uid') , Config::me('password') );
+	
+	$msgIds = imap_sort( $mbox , SORTDATE , 1 , null , "UNSEEN" , 'UTF-8');
+	$msgIds = array_splice($msgIds , 0 , 20);
+	
+	$return = array();
+	
+	foreach ($msgIds as $key => &$value) 
+	{    
+	      $header = imap_headerinfo( $mbox, $value );
+	      $return[$key]['subject'] = ( isset($header->subject) && trim($header->subject) !== '' ) ?  self::decodeMimeString($header->subject) : 'No Subject';
+	      $return[$key]['date'] =  $header->udate;
+	      $return[$key]['from'] =  (isset( $header->from[0] )) ? self::formatMailObject( $header->from[0] ) : array( 'name' => '' , 'mail' => '');
+	}
+
+	$response->body = json_encode($return);
+	
+	imap_close($mbox);
+	return $response;
+    }
+
+    
+    
+    /**
+    *  Decodifica os tokens encontrados na funÃ§Ã£o decodeMimeString
+    * 
+    * @license    http://www.gnu.org/copyleft/gpl.html GPL
+    * @author     ConsÃ³rcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+    * @sponsor    Caixa EconÃ³mica Federal
+    * @author     Cristiano CorrÃªa Schmidt
+    * @return     bool
+    * @access     public
+    */
+    static private function decodeMimeStringCallback( $mathes )
+    {
+       $str = (strtolower($mathes[2]) == 'q') ?  quoted_printable_decode(str_replace('_','=20',$mathes[3])) : base64_decode( $mathes[3]) ;
+       return ( strtoupper($mathes[1]) == 'ISO-8859-1' ) ? mb_convert_encoding(  $str , 'UTF-8' , 'ISO-8859-1') : $str;
+    }
+
+    /**
+    *  Decodifica uma string no formato mime RFC2047
+    *
+    * @license    http://www.gnu.org/copyleft/gpl.html GPL
+    * @author     ConsÃ³rcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+    * @sponsor    Caixa EconÃ³mica Federal
+    * @author     Cristiano CorrÃªa Schmidt
+    * @return     bool
+    * @access     public
+    */
+    static private function decodeMimeString( $string )
+    {
+      $string =  preg_replace('/\?\=(\s)*\=\?/', '?==?', $string);
+      return preg_replace_callback( '/\=\?([^\?]*)\?([qb])\?([^\?]*)\?=/i' ,array( 'self' , 'decodeMimeStringCallback'), $string);
+    }
+    
+     /**
+    *  Formata um mailObject para um array com name e email
+    *
+    * @license    http://www.gnu.org/copyleft/gpl.html GPL
+    * @author     ConsÃ³rcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+    * @sponsor    Caixa EconÃ³mica Federal
+    * @author     Cristiano CorrÃªa Schmidt
+    * @return     bool
+    * @access     public
+    */
+    static private function formatMailObject( $obj )
+    {
+	$return = array();
+	$return['mail'] = self::decodeMimeString($obj->mailbox) . (( isset( $obj->host) && $obj->host != ('unspecified-domain') &&  $obj->host !=  '.SYNTAX-ERROR.')? '@'. $obj->host : '');
+	$return['name'] = ( isset( $obj->personal ) && trim($obj->personal) !== '' ) ? self::decodeMimeString($obj->personal) :  $return['mail'];
+	return $return;
+    }
+}
Index: trunk/prototype/rest/dispatch.php
===================================================================
--- trunk/prototype/rest/dispatch.php	(revision 6528)
+++ trunk/prototype/rest/dispatch.php	(revision 6528)
@@ -0,0 +1,61 @@
+<?php
+// load Tonic library
+require_once __DIR__ . '/../library/tonic/lib/tonic.php';
+require_once __DIR__ . '/../api/controller.php';
+require_once ROOTPATH . '/rest/oauth/OAuth2StorageUserCredential.php';
+
+//Retrieveing the mapping of the URIs and his respectives classNames and classPath
+$config = parse_ini_file( __DIR__ . '/../config/Tonic.srv', true );
+
+//looping through the mapping to create 2 separated maps:
+// First indexed by the uri that carry the classNames, 
+// that its used by Tonic to autoload them when routed by him accordingly;
+// Second indexed by the className that carry the classPaths,
+// used by the autoload register to find the correct path of the class that's going to
+// be loaded;
+
+$autoload = array();
+$classpath = array();
+
+foreach( $config as $uri => $classFile ){
+    foreach( $classFile as $className => $filePath )
+    {
+	$autoload[ $uri ] = $className;
+	$classpath[ $className ] = $filePath;
+    }
+}
+
+//The autoload function that's called by the PHP when Tonic routes a class not declared previously
+function __autoload($class) {
+
+	global $classpath;
+
+	if(isset($classpath[ $class ])){
+		require_once(__DIR__ . $classpath[ $class ] );
+	}
+}
+
+// handle request, passing the current env baseUri and autoload mapping;
+
+$restConf = parse_ini_file( __DIR__ . '/../config/REST.ini', true );
+$request = new Request(array(
+	'baseUri'=> $restConf['baseUri'],
+	'autoload' => $autoload,
+));
+
+try {
+    $resource = $request->loadResource();
+    $response = $resource->exec($request);
+
+} catch (ResponseException $e) {
+    switch ($e->getCode()) {
+    case Response::UNAUTHORIZED:
+        $response = $e->response($request);
+        $response->addHeader('WWW-Authenticate', 'Basic realm="Tonic"');
+        break;
+    default:
+        $response = $e->response($request);
+    }
+}
+$response->output();
+
Index: trunk/prototype/rest/hypermedia/data.php
===================================================================
--- trunk/prototype/rest/hypermedia/data.php	(revision 6528)
+++ trunk/prototype/rest/hypermedia/data.php	(revision 6528)
@@ -0,0 +1,71 @@
+<?php
+
+class Data {
+
+    public $name;
+    public $value;
+    public $prompt;
+    public $dataType;
+    public $maxLength;
+    public $minLength;
+    public $required;
+
+    function setName($name) {
+	$this->name = $name;
+    }
+
+    function getName() {
+	return $this->name;
+    }
+
+    function setValue($value) {
+	$this->value = $value;
+    }
+
+    function getVame() {
+	return $this->value;
+    }
+
+    function setPrompt($prompt) {
+	$this->prompt = $prompt;
+    }
+
+    function getPrompt() {
+	return $this->prompt;
+    }
+
+    function setDataType($dataType) {
+	$this->dataType = $dataType;
+    }
+
+    function getDataType() {
+	return $this->dataType;
+    }
+
+    function setMaxLength($maxLength) {
+	$this->maxLength = $maxLength;
+    }
+
+    function getMaxLength() {
+	return $this->maxLength;
+    }
+
+    function setMinLength($minLength) {
+	$this->minLength = $minLength;
+    }
+
+    function getMinLength() {
+	return $this->minLength;
+    }
+
+    function setRequired($required) {
+	$this->required = $required;
+    }
+
+    function getRequired() {
+	return $this->required;
+    }
+
+}
+
+?>
Index: trunk/prototype/rest/hypermedia/error.php
===================================================================
--- trunk/prototype/rest/hypermedia/error.php	(revision 6528)
+++ trunk/prototype/rest/hypermedia/error.php	(revision 6528)
@@ -0,0 +1,35 @@
+<?php
+
+class Error {
+
+    public $code;
+    public $description;
+    public $title;
+
+    public function setCode($code) {
+	$this->code = $code;
+    }
+
+    public function setDescription($description) {
+	$this->description = $description;
+    }
+
+    public function setTitle($title) {
+	$this->title = $title;
+    }
+
+    public function getCode() {
+	return $this->code;
+    }
+
+    public function getDescription() {
+	return $this->description;
+    }
+
+    public function getTitle() {
+	return $this->title;
+    }
+
+}
+
+?>
Index: trunk/prototype/rest/hypermedia/collection.php
===================================================================
--- trunk/prototype/rest/hypermedia/collection.php	(revision 6528)
+++ trunk/prototype/rest/hypermedia/collection.php	(revision 6528)
@@ -0,0 +1,101 @@
+<?php
+
+include_once('item.php');
+include_once('link.php');
+include_once('data.php');
+include_once('template.php');
+include_once('query.php');
+include_once('error.php');
+/*
+Valores padrão
+
+Números são enviados como números, sem aspas;
+Atributos não obrigatórios sem valor serão nulos;
+Strings vazias serão nulos;
+Todos os indices / chaves estarão sempre presentes na mensagem;
+Arrays vazios serão nulos;
+Objetos vazios serão nulos;
+Números sem valor serão nulos;
+valores booleanos são escritos como booleanos (sem aspas);
+*/
+class Collection {
+
+    public $itens;
+    public $href;
+    public $error;
+    public $pagination;
+    public $queries;
+    public $template;
+    public $type;
+    public $data;
+    public $links;
+
+    function __construct($config, $className, $id = null) {
+	foreach ($config as $key => $value) {
+	    if ($value['class'] == $className) {
+		$uri = ($id ? (preg_replace('/\/[:][a-zA-Z-0-9]+/', '', $key).'/'.$id ) : $key);
+		break;
+	    }
+	}
+	$this->href = $uri;
+    }
+
+	function addLink($link) {
+	$this->links[] = $link;
+    }
+	
+    function addItem($item) {
+	$this->itens[] = $item;
+    }
+	
+	function addData($data) {
+	$this->data[] = $data;
+    }
+
+    function getData() {
+	return $this->data;
+    }
+    
+    function setTemplate($template) {
+	$this->template = $template;
+    }
+
+    function getTemplate() {
+	return $this->template;
+    }
+
+    function setType($type) {
+	$this->type = $type;
+    }
+
+    function getType() {
+	return $this->type;
+    }
+
+    function addQueries($queries) {
+	$this->queries[] = $queries;
+    }
+
+    function getQueries() {
+	return $this->queries;
+    }
+
+    function setError($error) {
+	$this->error = $error;
+    }
+
+    function getError() {
+	return $this->error;
+    }
+
+    function setPagination($pagination) {
+	$this->pagination = $pagination;
+    }
+
+    function getPagination() {
+	return $this->pagination;
+    }
+
+}
+
+?>
Index: trunk/prototype/rest/hypermedia/template.php
===================================================================
--- trunk/prototype/rest/hypermedia/template.php	(revision 6528)
+++ trunk/prototype/rest/hypermedia/template.php	(revision 6528)
@@ -0,0 +1,13 @@
+<?php
+
+class Template {
+
+    public $data = array();
+
+    public function addData($data) {
+	$this->data[] = $data;
+    }
+
+}
+
+?>
Index: trunk/prototype/rest/hypermedia/link.php
===================================================================
--- trunk/prototype/rest/hypermedia/link.php	(revision 6528)
+++ trunk/prototype/rest/hypermedia/link.php	(revision 6528)
@@ -0,0 +1,53 @@
+<?php
+
+class Link {
+
+    public $href;
+    public $rel;
+    public $alt;
+    public $prompt;
+    public $render;
+
+    function setHref($href) {
+	$this->href = $href;
+    }
+
+    function getHref() {
+	return $this->href;
+    }
+
+    function setRel($rel) {
+	$this->rel = $rel;
+    }
+
+    function getRel() {
+	return $this->rel;
+    }
+
+    function setAlt($alt) {
+	$this->alt = $alt;
+    }
+
+    function getAlt() {
+	return $this->alt;
+    }
+
+    function setPrompt($prompt) {
+	$this->prompt = $prompt;
+    }
+
+    function getPrompt() {
+	return $this->prompt;
+    }
+
+    function setRender($render) {
+	$this->render = $render;
+    }
+
+    function getRender() {
+	return $this->render;
+    }
+
+}
+
+?>
Index: trunk/prototype/rest/hypermedia/item.php
===================================================================
--- trunk/prototype/rest/hypermedia/item.php	(revision 6528)
+++ trunk/prototype/rest/hypermedia/item.php	(revision 6528)
@@ -0,0 +1,38 @@
+<?php
+
+class Item {
+
+    public $href;
+    public $data = array();
+    public $links = array();
+
+    function __construct($config, $className, $id) {
+	foreach ($config as $key => $value) {
+	    if ($value['class'] == $className) {
+		//TODO - Verificar expressão regular
+		$uri = preg_replace('/\/[:][a-zA-Z-0-9]+/', '', $key);
+		break;
+	    }
+	}
+	$this->href = $uri .'/'. $id;
+    }
+
+    function setHref($href) {
+	$this->href = $href;
+    }
+
+    function getHref() {
+	return $this->href;
+    }
+
+    function addData($data) {
+	$this->data[] = $data;
+    }
+
+    function addLink($link) {
+	$this->links[] = $link;
+    }
+
+}
+
+?>  
Index: trunk/prototype/rest/hypermedia/query.php
===================================================================
--- trunk/prototype/rest/hypermedia/query.php	(revision 6528)
+++ trunk/prototype/rest/hypermedia/query.php	(revision 6528)
@@ -0,0 +1,25 @@
+<?php
+
+class Querie {
+	public $href;
+	public $data = array();
+
+	public function getHref(){
+		return $this->href;
+	}
+
+	public function setHref($href){
+		$this->href = $href;
+	}
+
+	public function setData($field, $value, $required=false){
+		$this->data[] = array(
+					'name' => $field,
+					'value' => $value,
+					'required' => $required
+		);
+	}
+    
+}
+
+?>
Index: trunk/prototype/rest/hypermedia/hypermedia.php
===================================================================
--- trunk/prototype/rest/hypermedia/hypermedia.php	(revision 6528)
+++ trunk/prototype/rest/hypermedia/hypermedia.php	(revision 6528)
@@ -0,0 +1,68 @@
+<?php
+
+require_once('collection.php');
+
+class Hypermedia {
+
+    public $collection;
+    public $version = '0.1';
+
+    function setCollection($collection) {
+	$this->collection = $collection;
+    }
+
+    function getCollection() {
+	return $this->collection;
+    }
+
+    function getHypermedia($accept = 'json') {
+
+	$data = get_object_vars($this);
+
+	switch ($accept) {
+	    case 'json':
+		return json_encode($data);
+		break;
+	    case 'xml':
+		return $this->generateValidXmlFromArray($data);
+	    default :
+		return json_encode($data);
+	}
+    }
+
+    function generateValidXmlFromObj(stdClass $obj, $node_block = 'nodes', $node_name = 'node') {
+	$arr = get_object_vars($obj);
+	return self::generateValidXmlFromArray($arr, $node_block, $node_name);
+    }
+
+    function generateValidXmlFromArray($array, $node_block = 'nodes', $node_name = 'node') {
+	$xml = '<?xml version="1.0" encoding="UTF-8" ?>';
+
+	$xml .= '<' . $node_block . '>';
+	$xml .= self::generateXmlFromArray($array, $node_name);
+	$xml .= '</' . $node_block . '>';
+
+	return $xml;
+    }
+
+    function generateXmlFromArray($array, $node_name) {
+	$xml = '';
+
+	if (is_array($array) || is_object($array)) {
+	    foreach ($array as $key => $value) {
+		if (is_numeric($key)) {
+		    $key = $node_name;
+		}
+
+		$xml .= '<' . $key . '>' . self::generateXmlFromArray($value, $node_name) . '</' . $key . '>';
+	    }
+	} else {
+	    $xml = htmlspecialchars($array, ENT_QUOTES);
+	}
+
+	return $xml;
+    }
+
+}
+
+?>
Index: trunk/prototype/rest/calendar/CalendarLastResource.php
===================================================================
--- trunk/prototype/rest/calendar/CalendarLastResource.php	(revision 6528)
+++ trunk/prototype/rest/calendar/CalendarLastResource.php	(revision 6528)
@@ -0,0 +1,53 @@
+<?php
+
+use prototype\api\Config as Config;
+
+class CalendarLastResource extends Resource{
+
+    
+    /**
+    * Busca os eventos contidos nas agendas assinas do usuÃ¡rio
+    *
+    * @license    http://www.gnu.org/copyleft/gpl.html GPL
+    * @author     ConsÃ³rcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+    * @sponsor    Caixa EconÃŽmica Federal
+    * @author     Adriano Coutinho da Silva
+    * @return     Lista dos ultimos 10 eventos do usuÃ¡rio
+    * @access     public
+    **/
+    function GET($request){
+	$this->secured();
+
+	$calendar = Controller::find( array( 'concept' => 'calendarSignature' ), array('calendar'), array( 'filter' => array( '=', 'user',  Config::me("uidNumber") ) ) );
+
+	
+	
+	
+	if($calendar){
+	    $calendars = array();
+	    $timezones = array();
+
+	    foreach($calendar as $key => $value)
+		array_push($calendars, $value['calendar']);
+
+	    $calendarTimezone = Controller::find( array( 'concept' => 'calendar' ), array('id', 'timezone'), array( 'filter' => array( 'IN', 'id',  $calendars ) ) );
+
+	    foreach($calendarTimezone as $key => $value)
+		$timezones[ $value['id'] ] = $value['timezone'];
+
+
+	    $schedulables = Controller::find( array( 'concept' => 'schedulable' ), false, 
+		    array( 'filter' => array('AND', array('>=','rangeStart', (time() - 86400).'000'), array('<=','rangeEnd', (time() + 2592000).'000') ,array( 'IN', 'calendar', $calendars )), 'deepness' => 2, 'timezones' => $timezones) );
+
+	}
+	
+	$response = new Response($request);
+	$response->code = Response::OK;
+	$response->addHeader('Content-type', 'aplication/json');
+
+	$response->body = json_encode(( isset($schedulables) && $schedulables) ?  $schedulables : array());
+	return $response;
+    }
+}
+
+?>
Index: trunk/prototype/rest/catalog/DynamicContactsResource.php
===================================================================
--- trunk/prototype/rest/catalog/DynamicContactsResource.php	(revision 6528)
+++ trunk/prototype/rest/catalog/DynamicContactsResource.php	(revision 6528)
@@ -0,0 +1,258 @@
+<?php
+
+if (!defined('ROOTPATH'))
+    define('ROOTPATH', dirname(__FILE__) . '/..');
+
+require_once(ROOTPATH . '/rest/hypermedia/hypermedia.php');
+
+use prototype\api\Config as Config;
+
+class DynamicContactsResource extends Resource {
+
+    /**
+     * Retorna uma lista de contatos recentes 
+     *
+     * @license    http://www.gnu.org/copyleft/gpl.html GPL
+     * @author     Consórcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+     * @sponsor    Caixa Econômica Federal
+     * @author     José Vicente Tezza Jr. 
+     * @return     Retorna uma lista de contatos recentes
+     * @access     public
+     * */
+    function get($request) {
+
+	$this->secured();
+
+	$response = new Response($request);
+	$response->addHeader('Content-type', 'aplication/json');
+	$response->code = Response::OK;
+
+	$h = new Hypermedia();
+	$c = new Collection($request->resources, 'DynamicContactsResource');
+
+	try {
+
+	    $dynamicContacts = Controller::find(
+			    array('concept' => 'dynamicContact'), false, array('filter' => array('=', 'owner', Config::me("uidNumber")),
+			'order' => array('number_of_messages', 'name', 'mail'),
+			'orderDesc' => array(true, false, false))
+	    );
+
+	    //Se nao foi encontrado contatos na consulta
+	    if (!$dynamicContacts) {
+		$this->createException($request, $response, Response::NOTFOUND, 'Bad request', 'Dynamic Contact not found.');
+		return $response;
+	    }
+
+	    foreach ($dynamicContacts as $value) {
+		$d = new Data();
+		$i = new Item($request->resources, 'DynamicContactsResource', $value['id']);
+
+		$d->setName('name');
+		$d->setValue($value['name']);
+		$d->setPrompt('Nome do Contato Recente');
+		$d->setDataType('string');
+		$d->setMaxLength('100');
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$i->addData($d);
+
+		$d = new Data();
+		$d->setName('mail');
+		$d->setValue($value['mail']);
+		$d->setPrompt('Email do Contato Recente');
+		$d->setDataType('string');
+		$d->setMaxLength('100');
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$i->addData($d);
+
+		$d = new Data();
+		$d->setName('number_of_messages');
+		$d->setValue($value['number_of_messages']);
+		$d->setPrompt('Quantidade de mensagens enviadas');
+		$d->setDataType('integer');
+		$d->setMaxLength('100');
+		$d->setMinLength(null);
+		$d->setRequired(false);
+
+		$i->addData($d);
+
+		$d = new Data();
+		$d->setName('id');
+		$d->setValue($value['id']);
+		$d->setPrompt('Id do contato dinamico');
+		$d->setDataType('integer');
+		$d->setMaxLength('100');
+		$d->setMinLength(null);
+		$d->setRequired(false);
+
+		$i->addData($d);
+
+		$l = new Link();
+
+		$l->setHref('');
+		$l->setRel('delete');
+		$l->setAlt('Remover');
+		$l->setPrompt('Remover');
+		$l->setRender('link');
+
+		$i->addLink($l);
+
+		$l = new Link();
+		$l->setHref('');
+		$l->setRel('put');
+		$l->setAlt('Atualizar');
+		$l->setPrompt('Atualizar');
+		$l->setRender('link');
+
+		$i->addLink($l);
+
+		$l = new Link();
+		$l->setHref('/dynamiccontact/' . $value['id']);
+		$l->setRel('get');
+		$l->setAlt('Buscar');
+		$l->setPrompt('Buscar');
+		$l->setRender('link');
+
+		$i->addLink($l);
+		$c->addItem($i);
+	    }
+
+	    $t = new Template();
+	    $d = new Data();
+
+	    $d->setName('name');
+	    $d->setValue(null);
+	    $d->setPrompt('Nome do Contato Recente');
+	    $d->setDataType('string');
+	    $d->setMaxLength(100);
+	    $d->setMinLength(null);
+	    $d->setRequired(false);
+
+	    $t->addData($d);
+
+	    $d = new Data();
+	    $d->setName('mail');
+	    $d->setValue(null);
+	    $d->setPrompt('Email do Contato Recente');
+	    $d->setDataType('string');
+	    $d->setMaxLength(100);
+	    $d->setMinLength(null);
+	    $d->setRequired(true);
+
+	    $t->addData($d);
+
+	    $d = new Data();
+	    $d->setName('number_of_messages');
+	    $d->setValue(null);
+	    $d->setPrompt('Quantidade de mensagens enviadas');
+	    $d->setDataType('integer');
+	    $d->setMaxLength(100);
+	    $d->setMinLength(null);
+	    $d->setRequired(false);
+
+	    $t->addData($d);
+
+	    $c->setTemplate($t);
+
+	    $h->setCollection($c);
+	} catch (Exception $ex) {
+	    $this->createException($request, $response, Response::INTERNALSERVERERROR, 'Internal Server Error', $ex);
+	    return $response;
+	}
+
+	$response->body = $h->getHypermedia($request->accept[10][0]);
+	return $response;
+    }
+
+    /**
+     * Salva um contato recente
+     *
+     * @license    http://www.gnu.org/copyleft/gpl.html GPL
+     * @author     Consórcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+     * @sponsor    Caixa Econômica Federal
+     * @author     José Vicente Tezza Jr. 
+     * @return     retorna a uri do contato recente
+     * @access     public
+     * */
+    function post($request) {
+
+	$this->secured();
+	
+	if (count($_POST) == 0) {
+	    $response = new Response($request);
+	    $response->code = Response::INTERNALSERVERERROR;
+	    return $response;
+	}
+
+	//recupera os atributos definidos no conceito 'user'
+	$map = Config::get('dynamicContact', 'PostgreSQL.mapping');
+
+	$params = array();
+	foreach ($_POST as $key => $value) {
+
+	    if (!isset($map[$key]) || $key == 'id' || $key == 'timestamp' || $key == 'number_of_messages') {
+		continue;
+	    }
+	    $params[$key] = $value;
+	}
+
+	if (count($params) == 0) {
+	    $response = new Response($request);
+	    $response->code = Response::INTERNALSERVERERROR;
+	    return $response;
+	}
+
+
+	$response = new Response($request);
+	$response->addHeader('Content-type', 'aplication/json');
+	$response->code = Response::CREATED;
+
+
+	//completar os atributos
+	$params['owner'] = Config::me("uidNumber");
+	$params['number_of_messages'] = '1';
+	$params['timestamp'] = time();
+
+
+	try {
+	    $idDynamicContact = Controller::create(
+			    array('concept' => 'dynamicContact'), $params
+	    );
+
+	    if (!$idDynamicContact) {
+		throw new Exception(Controller::service('PostgreSQL')->error);
+	    }
+	} catch (Exception $ex) {
+	    $response->code = Response::INTERNALSERVERERROR;
+	    return $response;
+	}
+
+	$response->body = json_encode(null);
+
+	return $response;
+    }
+
+    private function createException($request, &$response, $code, $title, $description) {
+	$response->code = $code;
+
+	$h = new Hypermedia();
+	$c = new Collection($request->resources, 'DynamicContactResource');
+	$e = new Error();
+
+	$e->setCode($code);
+	$e->setTitle($title);
+	$e->setDescription($description);
+
+	$c->setError($e);
+	$h->setCollection($c);
+
+	$response->body = $h->getHypermedia($request->accept[10][0]);
+    }
+
+}
+
+?>
Index: trunk/prototype/rest/catalog/ContactResource.php
===================================================================
--- trunk/prototype/rest/catalog/ContactResource.php	(revision 6528)
+++ trunk/prototype/rest/catalog/ContactResource.php	(revision 6528)
@@ -0,0 +1,168 @@
+<?php
+
+if( !defined( 'ROOTPATH' ) )
+    define( 'ROOTPATH', dirname(__FILE__).'/..' );
+
+require_once(ROOTPATH.'/rest/hypermedia/hypermedia.php');
+
+use prototype\api\Config as Config;
+
+class ContactResource extends Resource {
+
+    /**
+     * Retorna uma lista de usuários
+     *
+     * @license    http://www.gnu.org/copyleft/gpl.html GPL
+     * @author     Consórcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+     * @sponsor    Caixa Econômica Federal
+     * @author     José Vicente Tezza Jr. 
+     * @return     Retorna uma lista de usuários do LDAP
+     * @access     public
+     * */
+    function get($request, $id) {
+
+	$response = new Response($request);
+	$response->addHeader('Content-type', 'aplication/json');
+	$response->code = Response::OK;
+
+	$h = new Hypermedia();
+	$c = new Collection($request->resources, 'ContactResource');
+
+	try {
+	    $this->secured();
+
+            $contact = Controller::find( array( 'concept' => 'contact' ), false, array( 'filter' => array( 'AND', array('=', 'user',  Config::me("uidNumber") ), array('=', 'id', $id) )  ) );
+
+	    //Se nao foi encontrado contatos na consulta
+	    if($contact===false){
+		$error = new Error();
+		$error->setCode(Response::NOTFOUND);
+		$error->setTitle('Contact not found');
+		$error->setDescription('Contact not found.');
+
+		$c->setError($error);
+		$h->setCollection($c);
+
+		$response->code = Response::NOTFOUND;
+		$response->body = $h->getHypermedia($request->accept[10][0]);
+		return $response;
+	    }
+			
+		$t = new Template();
+		$d = new Data();
+
+		$d->setName('name');
+		$d->setValue(null);
+		$d->setPrompt('Nome do Contato');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$t->addData($d);
+
+		$d = new Data();
+		$d->setName('email');
+		$d->setValue(null);
+		$d->setPrompt('Email do Contato');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$t->addData($d);
+
+		$d = new Data();
+		$d->setName('telefone');
+		$d->setValue(null);
+		$d->setPrompt('Telefone do Contato');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$t->addData($d);
+
+		$c->setTemplate($t);
+		
+		$d = new Data();
+		$d->setName('name');
+		$d->setValue($contact[0]['name']);
+		$d->setPrompt('Nome do Contato');
+		$d->setDataType('string');
+		$d->setMaxLength('100');
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$c->addData($d);
+		
+		$d = new Data();
+		$d->setName('email');
+		$d->setValue($contact[0]['email']);
+		$d->setPrompt('Email do Contato');
+		$d->setDataType('string');
+		$d->setMaxLength('100');
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$c->addData($d);
+		
+		$d = new Data();
+		$d->setName('telephone');
+		$d->setValue($contact[0]['telephone']);
+		$d->setPrompt('Telefone do Contato');
+		$d->setDataType('string');
+		$d->setMaxLength('100');
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$c->addData($d);
+		
+		$l = new Link();
+		$l->setHref('');
+		$l->setRel('delete');
+		$l->setAlt('Remover');
+		$l->setPrompt('Remover');
+		$l->setRender('link');
+		
+		$c->addLink($l);
+		
+		$l = new Link();
+		$l->setHref('');
+		$l->setRel('put');
+		$l->setAlt('Atualizar');
+		$l->setPrompt('Atualizar');
+		$l->setRender('link');
+		
+		$c->addLink($l);
+		
+		$l = new Link();
+		$l->setHref('/contact/'.$value['id']);
+		$l->setRel('get');
+		$l->setAlt('Buscar');
+		$l->setPrompt('Buscar');
+		$l->setRender('link');
+
+		$c->addLink($l);
+			
+		$h->setCollection($c);
+
+	}catch (Exception $ex){
+		$error = new Error();
+		$error->setCode(Response::INTERNALSERVERERROR);
+		$error->setTitle('Internal Server Error');
+		$error->setDescription($ex);
+
+		$c->setError($error);
+		$h->setCollection($c);
+
+		$response->code = Response::INTERNALSERVERERROR;
+		$response->body = $h->getHypermedia($request->accept[10][0]);
+		return $response;
+	}
+
+	$response->body = $h->getHypermedia($request->accept[10][0]);
+	return $response;
+  }
+}
+?>
Index: trunk/prototype/rest/catalog/GroupResource.php
===================================================================
--- trunk/prototype/rest/catalog/GroupResource.php	(revision 6528)
+++ trunk/prototype/rest/catalog/GroupResource.php	(revision 6528)
@@ -0,0 +1,243 @@
+<?php
+
+if( !defined( 'ROOTPATH' ) )
+    define( 'ROOTPATH', dirname(__FILE__).'/..' );
+
+require_once(ROOTPATH.'/rest/hypermedia/hypermedia.php');
+
+use prototype\api\Config as Config;
+
+class GroupResource extends Resource {
+
+    /**
+     * Retorna um grupo pelo seu identificador e seus parcitipantes
+     *
+     * @license    http://www.gnu.org/copyleft/gpl.html GPL
+     * @author     Consórcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+     * @sponsor    Caixa Econômica Federal
+     * @author     Gustavo Pereira dos Santos Stabelini
+     * @return     Retorna um grupo pelo seu identificador e seus parcitipantes
+     * @access     public
+     * */
+    function get($request, $id) {
+
+	$response = new Response($request);
+	$response->addHeader('Content-type', 'aplication/json');
+	$response->code = Response::OK;
+
+	$h = new Hypermedia();
+	$c = new Collection($request->resources, 'GroupResource');
+
+	try {
+	    $this->secured();
+
+            $group = Controller::find( array( 'concept' => 'contactGroup' ), false, array( 'filter' => array( 'AND', array('=', 'user',  Config::me("uidNumber") ), array('=', 'id', $id) )  ) );
+
+	    //Se nao foi encontrado contatos na consulta
+	    if($group===false){
+		$error = new Error();
+		$error->setCode(Response::NOTFOUND);
+		$error->setTitle('Group not found');
+		$error->setDescription('Group not found.');
+
+		$c->setError($error);
+		$h->setCollection($c);
+
+		$response->code = Response::NOTFOUND;
+		$response->body = $h->getHypermedia($request->accept[10][0]);
+		return $response;
+	    }
+		
+		foreach($group[0]['contacts'] as $value){
+			$d = new Data();
+			$i = new Item($request->resources, 'ContactResource', $value['id']);
+
+			$d->setName('name');
+			$d->setValue($value['name']);
+			$d->setPrompt('Nome do Grupo');
+			$d->setDataType('string');
+			$d->setMaxLength('100');
+			$d->setMinLength(null);
+			$d->setRequired(true);
+
+			$i->addData($d);
+
+			$d = new Data();
+			$d->setName('id');
+			$d->setValue($value['id']);
+			$d->setPrompt('Id do Contato');
+			$d->setDataType('string');
+			$d->setMaxLength('100');
+			$d->setMinLength(null);
+			$d->setRequired(true);
+
+			$i->addData($d);
+
+			$d = new Data();
+			$d->setName('email');
+			$d->setValue($value['email']);
+			$d->setPrompt('Email do Contato');
+			$d->setDataType('string');
+			$d->setMaxLength('100');
+			$d->setMinLength(null);
+			$d->setRequired(true);
+
+			$i->addData($d);
+
+			$l = new Link();
+
+			$l->setHref('');
+			$l->setRel('delete');
+			$l->setAlt('Remover');
+			$l->setPrompt('Remover');
+			$l->setRender('link');
+
+			$i->addLink($l);
+
+			$l = new Link();
+			$l->setHref('');
+			$l->setRel('put');
+			$l->setAlt('Atualizar');
+			$l->setPrompt('Atualizar');
+			$l->setRender('link');
+
+			$i->addLink($l);
+
+			$l = new Link();
+			$l->setHref('/contact/'.$value['id']);
+			$l->setRel('get');
+			$l->setAlt('Buscar');
+			$l->setPrompt('Buscar');
+			$l->setRender('link');
+
+			$i->addLink($l);
+			$c->addItem($i);
+	    }
+
+		$t = new Template();
+		$d = new Data();
+
+		$d->setName('name');
+		$d->setValue(null);
+		$d->setPrompt('Nome do Contato');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$t->addData($d);
+
+		$d = new Data();
+		$d->setName('email');
+		$d->setValue(null);
+		$d->setPrompt('Email do Contato');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$t->addData($d);
+
+		$d = new Data();
+		$d->setName('telefone');
+		$d->setValue(null);
+		$d->setPrompt('Telefone do Contato');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$t->addData($d);
+
+		$c->setTemplate($t);
+
+		$d = new Data();
+		$d->setName('id');
+		$d->setValue($group[0]['id']);
+		$d->setPrompt('Id do Grupo');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$c->addData($d);
+
+		$d = new Data();
+		$d->setName('name');
+		$d->setValue($group[0]['name']);
+		$d->setPrompt('Nome do Grupo');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$c->addData($d);
+		
+		$d = new Data();
+		$d->setName('email');
+		$d->setValue($group[0]['email']);
+		$d->setPrompt('Email do Grupo');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$c->addData($d);	
+		
+		$l = new Link();
+		$l->setHref('/group/'.$group[0]['id']);
+		$l->setRel('get');
+		$l->setAlt('Buscar');
+		$l->setPrompt('Buscar');
+		$l->setRender('link');
+
+		$c->addLink($l);
+			
+		$l = new Link();
+		$l->setHref(null);
+		$l->setRel('put');
+		$l->setAlt('Edit');
+		$l->setRender('link');
+		$l->setPrompt('Editar');
+
+		$c->addLink($l);
+		
+		$l = new Link();
+		$l->setHref(null);
+		$l->setRel('post');
+		$l->setAlt('Create');
+		$l->setRender('link');
+		$l->setPrompt('Criar');
+
+		$c->addLink($l);
+		
+		$l = new Link();
+		$l->setHref(null);
+		$l->setRel('delete');
+		$l->setAlt('Delete');
+		$l->setRender('link');
+		$l->setPrompt('Remover');
+
+		$c->addLink($l);
+		
+		$h->setCollection($c);
+
+	}catch (Exception $ex){
+		$error = new Error();
+		$error->setCode(Response::INTERNALSERVERERROR);
+		$error->setTitle('Internal Server Error');
+		$error->setDescription($ex);
+
+		$c->setError($error);
+		$h->setCollection($c);
+
+		$response->code = Response::INTERNALSERVERERROR;
+		$response->body = $h->getHypermedia($request->accept[10][0]);
+		return $response;
+	}
+
+	$response->body = $h->getHypermedia($request->accept[10][0]);
+	return $response;
+  }
+}
+?>
Index: trunk/prototype/rest/catalog/ContactsResource.php
===================================================================
--- trunk/prototype/rest/catalog/ContactsResource.php	(revision 6528)
+++ trunk/prototype/rest/catalog/ContactsResource.php	(revision 6528)
@@ -0,0 +1,184 @@
+<?php
+
+if( !defined( 'ROOTPATH' ) )
+    define( 'ROOTPATH', dirname(__FILE__).'/..' );
+
+require_once(ROOTPATH.'/rest/hypermedia/hypermedia.php');
+
+use prototype\api\Config as Config;
+
+class ContactsResource extends Resource {
+
+    /**
+     * Retorna uma lista de usuários
+     *
+     * @license    http://www.gnu.org/copyleft/gpl.html GPL
+     * @author     Consórcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+     * @sponsor    Caixa Econômica Federal
+     * @author     José Vicente Tezza Jr. 
+     * @return     Retorna uma lista de usuários do LDAP
+     * @access     public
+     * */
+    function get($request) {
+
+	$response = new Response($request);
+	$response->addHeader('Content-type', 'aplication/json');
+	$response->code = Response::OK;
+
+	$h = new Hypermedia();
+	$c = new Collection($request->resources, 'ContactsResource');
+
+	try {
+	    $this->secured();
+
+            $contacts = Controller::find( array( 'concept' => 'contact' ), false, array( 'filter' => array( '=', 'user',  Config::me("uidNumber") ) ) );
+
+	    //Se nao foi encontrado contatos na consulta
+	    if($contacts===false){
+		$error = new Error();
+		$error->setCode(Response::NOTFOUND);
+		$error->setTitle('Contacts not found');
+		$error->setDescription('Contacts not found.');
+
+		$c->setError($error);
+		$h->setCollection($c);
+
+		$response->code = Response::NOTFOUND;
+		$response->body = $h->getHypermedia($request->accept[10][0]);
+		return $response;
+	    }
+
+            foreach($contacts as $value){
+		$d = new Data();
+                $i = new Item($request->resources, 'ContactsResource', $value['id']);
+		$d->setName('id');
+                $d->setValue($value['id']);
+                $d->setPrompt('Id do Contato');
+                $d->setDataType('string');
+                $d->setMaxLength('100');
+                $d->setMinLength(null);
+                $d->setRequired(true);
+
+                $i->addData($d);
+		
+		$d = new Data();		
+		$d->setName('name');
+                $d->setValue($value['name']);
+                $d->setPrompt('Nome do Contato');
+                $d->setDataType('string');
+                $d->setMaxLength('100');
+                $d->setMinLength(null);
+                $d->setRequired(true);
+
+                $i->addData($d);
+
+		$d = new Data();
+                $d->setName('email');
+                $d->setValue($value['email']);
+                $d->setPrompt('Email do Contato');
+                $d->setDataType('string');
+                $d->setMaxLength('100');
+                $d->setMinLength(null);
+                $d->setRequired(true);
+
+                $i->addData($d);
+
+		$d = new Data();
+                $d->setName('telephone');
+                $d->setValue($value['telephone']);
+                $d->setPrompt('Telefone do Contato');
+                $d->setDataType('string');
+                $d->setMaxLength('100');
+                $d->setMinLength(null);
+                $d->setRequired(true);
+
+                $i->addData($d);
+
+                $l = new Link();
+
+                $l->setHref('');
+                $l->setRel('delete');
+                $l->setAlt('Remover');
+                $l->setPrompt('Remover');
+                $l->setRender('link');
+
+                $i->addLink($l);
+
+                $l = new Link();
+                $l->setHref('');
+                $l->setRel('put');
+                $l->setAlt('Atualizar');
+                $l->setPrompt('Atualizar');
+                $l->setRender('link');
+
+                $i->addLink($l);
+
+                $l = new Link();
+                $l->setHref('/contact/'.$value['id']);
+                $l->setRel('get');
+                $l->setAlt('Buscar');
+                $l->setPrompt('Buscar');
+                $l->setRender('link');
+
+                $i->addLink($l);
+                $c->addItem($i);
+	    }
+
+            $t = new Template();
+            $d = new Data();
+
+            $d->setName('name');
+            $d->setValue(null);
+            $d->setPrompt('Nome do Contato');
+            $d->setDataType('string');
+            $d->setMaxLength(100);
+            $d->setMinLength(null);
+            $d->setRequired(true);
+
+            $t->addData($d);
+
+            $d = new Data();
+            $d->setName('email');
+            $d->setValue(null);
+            $d->setPrompt('Email do Contato');
+            $d->setDataType('string');
+            $d->setMaxLength(100);
+            $d->setMinLength(null);
+            $d->setRequired(true);
+
+            $t->addData($d);
+
+            $d = new Data();
+            $d->setName('telefone');
+            $d->setValue(null);
+            $d->setPrompt('Telefone do Contato');
+            $d->setDataType('string');
+            $d->setMaxLength(100);
+            $d->setMinLength(null);
+            $d->setRequired(true);
+
+            $t->addData($d);
+
+            $c->setTemplate($t);
+
+            $h->setCollection($c);
+
+	}catch (Exception $ex){
+		$error = new Error();
+		$error->setCode(Response::INTERNALSERVERERROR);
+		$error->setTitle('Internal Server Error');
+		$error->setDescription($ex);
+
+		$c->setError($error);
+		$h->setCollection($c);
+
+		$response->code = Response::INTERNALSERVERERROR;
+		$response->body = $h->getHypermedia($request->accept[10][0]);
+		return $response;
+	}
+
+	$response->body = $h->getHypermedia($request->accept[10][0]);
+	return $response;
+  }
+}
+?>
Index: trunk/prototype/rest/catalog/GroupsResource.php
===================================================================
--- trunk/prototype/rest/catalog/GroupsResource.php	(revision 6528)
+++ trunk/prototype/rest/catalog/GroupsResource.php	(revision 6528)
@@ -0,0 +1,183 @@
+<?php
+
+if( !defined( 'ROOTPATH' ) )
+    define( 'ROOTPATH', dirname(__FILE__).'/..' );
+
+require_once(ROOTPATH.'/rest/hypermedia/hypermedia.php');
+
+use prototype\api\Config as Config;
+
+class GroupsResource extends Resource {
+
+    /**
+     * Retorna uma lista de grupos
+     *
+     * @license    http://www.gnu.org/copyleft/gpl.html GPL
+     * @author     Consórcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+     * @sponsor    Caixa Econômica Federal
+     * @author     Douglas Zilli. 
+     * @return     Retorna uma lista de Grupos
+     * @access     public
+     * */
+    function get($request) {
+
+	$response = new Response($request);
+	$response->addHeader('Content-type', 'aplication/json');
+	$response->code = Response::OK;
+
+	$h = new Hypermedia();
+	$c = new Collection($request->resources, 'GroupsResource');
+	
+	try {
+	    $this->secured();
+
+        $groups = Controller::find( array( 'concept' => 'contactGroup' ), false, array( 'filter' => array('=', 'user',  Config::me("uidNumber") ), 'order' => array('name') ) );			
+			
+	    //Se nao foi encontrado contatos na consulta
+	    if($groups===false){
+			$error = new Error();
+			$error->setCode(Response::NOTFOUND);
+			$error->setTitle('Group not found');
+			$error->setDescription('Group not found.');
+			
+			$c->setError($error);
+			$h->setCollection($c);
+
+			$response->code = Response::NOTFOUND;
+			$response->body = $h->getHypermedia($request->accept[10][0]);
+			return $response;
+	    }
+
+		foreach($groups as $value){
+	
+				$i = new Item($request->resources, 'GroupsResource', $value['id']);
+				
+				$d = new Data();
+				$d->setName('id');
+				$d->setValue($value['id']);
+				$d->setPrompt('Id do Grupo');
+				$d->setDataType('string');
+				$d->setMaxLength('100');
+				$d->setMinLength(null);
+				$d->setRequired(true);
+				$i->addData($d);
+
+				$d = new Data();
+				$d->setName('user');
+				$d->setValue($value['user']);
+				$d->setPrompt('Id Dono do Grupo');
+				$d->setDataType('string');
+				$d->setMaxLength('100');
+				$d->setMinLength(null);
+				$d->setRequired(true);
+				$i->addData($d);
+
+				$d = new Data();				
+				$d->setName('name');
+				$d->setValue($value['name']);
+				$d->setPrompt('Nome do Grupo');
+				$d->setDataType('string');
+				$d->setMaxLength('100');
+				$d->setMinLength(null);
+				$d->setRequired(true);
+				$i->addData($d);
+
+				$d = new Data();				
+				$d->setName('email');
+				$d->setValue($value['email']);
+				$d->setPrompt('Email do Grupo');
+				$d->setDataType('string');
+				$d->setMaxLength('100');
+				$d->setMinLength(null);
+				$d->setRequired(true);
+				$i->addData($d);
+				
+				$l = new Link();
+
+				$l->setHref('');
+				$l->setRel('delete');
+				$l->setAlt('Remover');
+				$l->setPrompt('Remover');
+				$l->setRender('link');
+
+				$i->addLink($l);
+
+				$l = new Link();
+				$l->setHref('');
+				$l->setRel('put');
+				$l->setAlt('Atualizar');
+				$l->setPrompt('Atualizar');
+				$l->setRender('link');
+
+				$i->addLink($l);
+
+				$l = new Link();
+				$l->setHref('/group/'.$value['id']);
+				$l->setRel('get');
+				$l->setAlt('Buscar');
+				$l->setPrompt('Buscar');
+				$l->setRender('link');
+
+				$i->addLink($l);
+				$c->addItem($i);
+	    }
+
+            $t = new Template();
+
+            $d = new Data();
+			
+			$d->setName('id');
+			$d->setValue(null);
+			$d->setPrompt('Id do Grupo');
+			$d->setDataType('string');
+			$d->setMaxLength('100');
+			$d->setMinLength(null);
+			$d->setRequired(true);
+			
+			$t->addData($d);
+
+			$d = new Data();
+			$d->setName('user');
+			$d->setValue(null);
+			$d->setPrompt('Id Dono do Grupo');
+			$d->setDataType('string');
+			$d->setMaxLength('100');
+			$d->setMinLength(null);
+			$d->setRequired(true);
+			
+			$t->addData($d);
+
+			$d = new Data();				
+			$d->setName('name');
+			$d->setValue(null);
+			$d->setPrompt('Nome do Grupo');
+			$d->setDataType('string');
+			$d->setMaxLength('100');
+			$d->setMinLength(null);
+			$d->setRequired(true);
+			
+            $t->addData($d);
+
+            $c->setTemplate($t);
+			
+            $h->setCollection($c);
+
+	}catch (Exception $ex){
+		$error = new Error();
+		$error->setCode(Response::INTERNALSERVERERROR);
+		$error->setTitle('Internal Server Error');
+		$error->setDescription($ex);
+
+		$c->setError($error);
+		$h->setCollection($c);
+
+		$response->code = Response::INTERNALSERVERERROR;
+		$response->body = $h->getHypermedia($request->accept[10][0]);
+		return $response;
+	}
+
+	$response->body = $h->getHypermedia($request->accept[10][0]);
+	return $response;
+  }
+}
+?>
Index: trunk/prototype/rest/catalog/DynamicContactResource.php
===================================================================
--- trunk/prototype/rest/catalog/DynamicContactResource.php	(revision 6528)
+++ trunk/prototype/rest/catalog/DynamicContactResource.php	(revision 6528)
@@ -0,0 +1,311 @@
+<?php
+
+if (!defined('ROOTPATH'))
+    define('ROOTPATH', dirname(__FILE__) . '/..');
+
+require_once(ROOTPATH . '/rest/hypermedia/hypermedia.php');
+
+use prototype\api\Config as Config;
+
+class DynamicContactResource extends Resource {
+
+    /**
+     * Retorna um contato recente 
+     *
+     * @license    http://www.gnu.org/copyleft/gpl.html GPL
+     * @author     Consórcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+     * @sponsor    Caixa Econômica Federal
+     * @author     José Vicente Tezza Jr. 
+     * @return     Retorna um contato recente
+     * @access     public
+     * */
+    function get($request, $id) {
+
+	$this->secured();
+
+	$response = new Response($request);
+	$response->addHeader('Content-type', 'aplication/json');
+	$response->code = Response::OK;
+
+	$h = new Hypermedia();
+	$c = new Collection($request->resources, 'DynamicContactResource', $id);
+
+	try {
+	    $dynamicContact = Controller::read(
+			    array('concept' => 'dynamicContact'), false, array('filter' => array('AND', array('=', 'owner', Config::me("uidNumber")), array('=', 'id', $id)))
+	    );
+
+	    //Se nao foi encontrado contatos na consulta
+	    if (!$dynamicContact) {
+		$this->createException($request, $response, Response::NOTFOUND, 'Bad request', 'Dynamic Contact not found.');
+		return $response;
+	    }
+	    
+	    //Normaliza dado
+	    if(is_array($dynamicContact))
+		$dynamicContact = $dynamicContact[0];
+
+
+	    $t = new Template();
+	    $d = new Data();
+
+	    $d->setName('name');
+	    $d->setValue(null);
+	    $d->setPrompt('Nome do Contato Recente');
+	    $d->setDataType('string');
+	    $d->setMaxLength(100);
+	    $d->setMinLength(null);
+	    $d->setRequired(true);
+
+	    $t->addData($d);
+
+	    $d = new Data();
+	    $d->setName('mail');
+	    $d->setValue(null);
+	    $d->setPrompt('Email do Contato Recente');
+	    $d->setDataType('string');
+	    $d->setMaxLength(100);
+	    $d->setMinLength(null);
+	    $d->setRequired(true);
+
+	    $t->addData($d);
+
+	    $d = new Data();
+	    $d->setName('number_of_messages');
+	    $d->setValue(null);
+	    $d->setPrompt('Quantidade de mensagens enviadas');
+	    $d->setDataType('integer');
+	    $d->setMaxLength(100);
+	    $d->setMinLength(null);
+	    $d->setRequired(false);
+
+	    $t->addData($d);
+
+	    $c->setTemplate($t);
+
+	    $d = new Data();
+	    $d->setName('id');
+	    $d->setValue($dynamicContact['id']);
+	    $d->setPrompt('Identificador do Contato Recente');
+	    $d->setDataType('integer');
+	    $d->setMaxLength(null);
+	    $d->setMinLength(null);
+	    $d->setRequired(true);
+
+	    $c->addData($d);
+
+	    $d = new Data();
+
+	    $d->setName('name');
+	    $d->setValue($dynamicContact['name']);
+	    $d->setPrompt('Nome do Contato Recente');
+	    $d->setDataType('string');
+	    $d->setMaxLength('100');
+	    $d->setMinLength(null);
+	    $d->setRequired(true);
+
+	    $c->addData($d);
+
+	    $d = new Data();
+	    $d->setName('mail');
+	    $d->setValue($dynamicContact['mail']);
+	    $d->setPrompt('Email do Contato Recente');
+	    $d->setDataType('string');
+	    $d->setMaxLength('100');
+	    $d->setMinLength(null);
+	    $d->setRequired(true);
+
+	    $c->addData($d);
+
+	    $d = new Data();
+	    $d->setName('number_of_messages');
+	    $d->setValue($dynamicContact['number_of_messages']);
+	    $d->setPrompt('Quantidade de mensagens enviadas');
+	    $d->setDataType('integer');
+	    $d->setMaxLength('100');
+	    $d->setMinLength(null);
+	    $d->setRequired(false);
+
+	    $c->addData($d);
+
+
+	    $l = new Link();
+
+	    $l->setHref('');
+	    $l->setRel('delete');
+	    $l->setAlt('Remover');
+	    $l->setPrompt('Remover');
+	    $l->setRender('link');
+
+	    $c->addLink($l);
+
+	    $l = new Link();
+	    $l->setHref('');
+	    $l->setRel('put');
+	    $l->setAlt('Atualizar');
+	    $l->setPrompt('Atualizar');
+	    $l->setRender('link');
+
+	    $c->addLink($l);
+
+	    $h->setCollection($c);
+	} catch (Exception $ex) {
+	    $this->createException($request, $response, Response::INTERNALSERVERERROR, 'Internal Server Error', 'Internal Server Error');
+	    return $response;
+	}
+
+	$response->body = $h->getHypermedia($request->accept[10][0]);
+	return $response;
+    }
+
+    /**
+     * Atualiza um contato recente
+     *
+     * @license    http://www.gnu.org/copyleft/gpl.html GPL
+     * @author     Consórcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+     * @sponsor    Caixa Econômica Federal
+     * @author     José Vicente Tezza Jr. 
+     * @return     retorna a uri do contato recente
+     * @access     public
+     * */
+    function put($request, $id) {
+	
+	$this->secured();
+
+	$post = $request->dataDecoded;
+	$response = new Response($request);
+
+	if (count($post) == 0){
+	    $this->createException($request, $response, Response::BADREQUEST, 'Bad request', 'Invalid template data');
+	    return $response;
+	}
+
+	//recupera os atributos definidos no conceito 'user'
+	$map = Config::get('dynamicContact', 'PostgreSQL.mapping');
+
+	$params = array();
+	foreach ($post as $key => $value) {
+
+	    if (!isset($map[$key]) || $key == 'id' || $key == 'timestamp' || $key == 'number_of_messages') {
+		continue;
+	    }
+	    $params[$key] = $value;
+	}
+
+	if (count($params) == 0) {
+	    $this->createException($request, $response, Response::BADREQUEST, 'Bad request', 'Invalid template data');
+	    return $response;
+	}
+
+	//completar os atributos
+	$params['owner'] = Config::me("uidNumber");
+	$params['timestamp'] = time();
+	$params['id'] = $id;
+
+	$response->addHeader('Content-type', 'aplication/json');
+	$response->code = Response::NOCONTENT;
+
+	try {
+
+	    $dynamicContact = Controller::read(
+			    array('concept' => 'dynamicContact'), false, array('filter' => array(
+			    'AND',
+			    array('=', 'owner', Config::me("uidNumber")),
+			    array('=', 'id', $id)))
+	    );
+
+
+	    //Se existe o recurso
+	    if ($dynamicContact) {
+		//Normaliza o recurso
+		if(is_array($dynamicContact))
+		    $dynamicContact = $dynamicContact[0];
+		
+		$params['number_of_messages'] = $dynamicContact['number_of_messages'] + 1; 
+
+		$dynamicContact = Controller::update(array('concept' => 'dynamicContact', 'id' => $id), $params);
+
+		if (!$dynamicContact) {
+		    $this->createException($request, $response, Response::INTERNALSERVERERROR, 'Internal Server Error', Controller::service('PostgreSQL')->error);
+		    return $response;
+		}
+	    } else {
+		/*
+		$idDynamicContact = Controller::create(	array('concept' => 'dynamicContact'), $params);
+		*/
+		//if (!$idDynamicContact) {
+		    $this->createException($request, $response, Response::NOTFOUND, 'Bad request', 'Invalid data');
+		    return $response;
+		//}
+	    }
+	} catch (Exception $ex) {
+	    $this->createException($request, $response, Response::INTERNALSERVERERROR, 'Internal Server Error', 'Internal Server Error');
+	    return $response;
+	}
+	$response->body = json_encode(null);
+	return $response;
+    }
+
+    /**
+     * Remove um contato recente
+     *
+     * @license    http://www.gnu.org/copyleft/gpl.html GPL
+     * @author     Consórcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+     * @sponsor    Caixa Econômica Federal
+     * @author     José Vicente Tezza Jr. 
+     * @access     public
+     * */
+    function delete($request, $id) {
+
+	$this->secured();
+
+	$response = new Response($request);
+	$response->addHeader('Content-type', 'aplication/json');
+	$response->code = Response::NOCONTENT;
+
+	try {
+	    //Verifica se o recurso existe
+	    $dinamicContact = Controller::read(array('concept' => 'dynamicContact', 'id' => $id));
+
+	    //Se existe o recurso
+	    if ($dinamicContact) {
+
+		$delete = Controller::delete(array('concept' => 'dynamicContact', 'id' => $id));
+
+		if (!$delete) {
+		    $this->createException($request, $response, Response::INTERNALSERVERERROR, 'Internal Server Error', Controller::service('PostgreSQL')->error);
+		    return $response;
+		}
+	    } else {
+		$this->createException($request, $response, Response::NOTFOUND, 'Bad request', 'Invalid data');
+		return $response;
+	    }
+	} catch (Exception $ex) {
+	    $this->createException($request, $response, Response::INTERNALSERVERERROR, 'Internal Server Error', 'Internal Server Error');
+	    return $response;
+	}
+
+	$response->body = json_encode(null);
+	return $response;
+    }
+    
+     private function createException($request, &$response, $code, $title, $description) {
+	$response->code = $code;
+	 
+	$h = new Hypermedia();
+	$c = new Collection($request->resources, 'DynamicContactResource');
+	$e = new Error();
+
+	$e->setCode($code);
+	$e->setTitle($title);
+	$e->setDescription($description);
+	
+	$c->setError($e);
+	$h->setCollection($c);
+
+	$response->body = $h->getHypermedia($request->accept[10][0]);
+    }
+
+}
+
+?>
Index: trunk/prototype/rest/.htaccess
===================================================================
--- trunk/prototype/rest/.htaccess	(revision 6528)
+++ trunk/prototype/rest/.htaccess	(revision 6528)
@@ -0,0 +1,6 @@
+<IfModule mod_rewrite.c>
+    RewriteEngine On
+    RewriteCond %{REQUEST_URI} !dispatch\.php$
+    RewriteCond %{REQUEST_FILENAME} !-f
+    RewriteRule .* dispatch.php [L,QSA]
+</IfModule>
Index: trunk/prototype/rest/news/NewsLastResource.php
===================================================================
--- trunk/prototype/rest/news/NewsLastResource.php	(revision 6528)
+++ trunk/prototype/rest/news/NewsLastResource.php	(revision 6528)
@@ -0,0 +1,39 @@
+<?php
+
+class NewsLastResource extends Resource{
+    
+    /**
+    * Busca os Ãºltimos 10 boletins em que o usuÃ¡rio tem acesso
+    *
+    * @license    http://www.gnu.org/copyleft/gpl.html GPL
+    * @author     ConsÃ³rcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+    * @sponsor    Caixa EconÃŽmica Federal
+    * @author     Adriano Coutinho da Silva
+    * @return     Lista dos ultimos 10 boletins do usuÃ¡rio
+    * @access     public
+    **/
+    function GET($request){
+	$this->secured();
+
+	$news = Controller::service('PostgreSQL')->execResultSql( 'SELECT news_subject  as "subject" , 
+	    news_content as "content" ,
+	    news_date as "startTime", 
+	    news_end as "endTime", 
+	    news_id as "id" 
+	    FROM phpgw_news WHERE news_cat IN (1) AND news_begin <= '.time().' AND 
+	    news_end >= '.time().' ORDER BY news_date DESC limit 10');
+
+	if(isset($news) && count($news) > 0)
+	    foreach($news as $key => &$value)
+		$value['subject'] = utf8_encode($value['subject']);
+
+	$response = new Response($request);
+	$response->code = Response::OK;
+	$response->addHeader('Content-type', 'aplication/json');
+
+	$response->body = json_encode(( is_array($news) && $news) ? $news : array());
+	return $response;
+    }
+}
+
+?>
Index: trunk/prototype/rest/user/UsersResource.php
===================================================================
--- trunk/prototype/rest/user/UsersResource.php	(revision 6528)
+++ trunk/prototype/rest/user/UsersResource.php	(revision 6528)
@@ -0,0 +1,214 @@
+<?php
+
+if( !defined( 'ROOTPATH' ) )
+    define( 'ROOTPATH', dirname(__FILE__).'/..' );
+
+require_once(ROOTPATH.'/rest/hypermedia/hypermedia.php');
+
+use prototype\api\Config as Config;
+
+class UsersResource extends Resource {
+
+    /**
+     * Retorna uma lista de usuários
+     *
+     * @license    http://www.gnu.org/copyleft/gpl.html GPL
+     * @author     Consórcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+     * @sponsor    Caixa Econômica Federal
+     * @author     José Vicente Tezza Jr. 
+     * @return     Retorna uma lista de usuários do LDAP
+     * @access     public
+     * */
+    function get($request) {
+
+	$response = new Response($request);
+	$response->addHeader('Content-type', 'aplication/json');
+	$response->code = Response::OK;
+
+	$h = new Hypermedia();
+	$c = new Collection($request->resources, 'UsersResource');
+
+	try {
+	    $this->secured();
+
+	    //Executa uma consulta de usuários do LDAP a partir de um determinado atributo e valor
+	    if(isset($_GET['field']) && isset($_GET['value'])){
+
+		    //recupera os atributos definidos no conceito 'user'
+		    $map = Config::get('user', 'OpenLDAP.mapping');
+
+		    //verifica se o campo(atributo) passado pelo usuário está definido no conceito 'user'
+		    if(isset($map[ $_GET['field'] ])){
+			    $users = Controller::find(array('concept' => 'user','service'=>'OpenLDAP'), false, array('filter' => array('=', $_GET['field'],$_GET['value'] )));
+		    }
+		    else{
+				//lança warning no log do Expresso
+				trigger_error("Invalid field (".$_GET['field'].") in the query.", E_USER_WARNING);
+
+				//formata os atributos LDAP do conceito 'user'
+				$attributes = implode(', ', $map); 
+
+				//Configura o erro na hypermedia
+				$error = new Error();
+				$error->setCode(Response::NOTFOUND);
+				$error->setTitle('UserLDAP not found');
+				$error->setDescription("Invalid field (".$_GET['field'].") in the query. Use of these: ".$attributes);
+
+				$c->setError($error);
+				$h->setCollection($c);
+
+				//retorna a hypermedia
+				$response->code = Response::NOTFOUND;
+				$response->body = $h->getHypermedia($request->accept[10][0]);
+				return $response;
+		    }
+	    }
+	    else{
+		    //Executa a consulta dos primeiros 20 usuarios do LDAP
+		    $users = Controller::find(array('concept' => 'user','service'=>'OpenLDAP'), false, array('filter' => array('=', 'phpgwAccountType', 'u'),'limit'=>20));
+	    }
+
+	    //Se nao foi encontrado usuarios na consulta
+	    if($users===false){
+			$error = new Error();
+			$error->setCode(Response::NOTFOUND);
+			$error->setTitle('UserLDAP not found');
+			$error->setDescription('Users not found.');
+
+			$c->setError($error);
+			$h->setCollection($c);
+
+			$response->code = Response::NOTFOUND;
+			$response->body = $h->getHypermedia($request->accept[10][0]);
+			return $response;
+	    }
+
+		foreach($users as $value){
+			$d = new Data();
+			$i = new Item($request->resources, 'UsersResource', $value['uid']);
+
+			$d->setName('name');
+			$d->setValue($value['name']);
+			$d->setPrompt('Nome do Usuario');
+			$d->setDataType('string');
+			$d->setMaxLength('100');
+			$d->setMinLength(null);
+			$d->setRequired(true);
+
+			$i->addData($d);
+
+			$d = new Data();
+			$d->setName('email');
+			$d->setValue($value['mail']);
+			$d->setPrompt('Email do Usuario');
+			$d->setDataType('string');
+			$d->setMaxLength('100');
+			$d->setMinLength(null);
+			$d->setRequired(true);
+
+			$i->addData($d);
+
+			$d = new Data();
+			$d->setName('telephoneNumber');
+			$d->setValue($value['telephoneNumber']);
+			$d->setPrompt('Telefone do Usuario');
+			$d->setDataType('string');
+			$d->setMaxLength('100');
+			$d->setMinLength(null);
+			$d->setRequired(true);
+
+			$i->addData($d);
+
+			$l = new Link();
+
+			$l->setHref('');
+			$l->setRel('delete');
+			$l->setAlt('Remover');
+			$l->setPrompt('Remover');
+			$l->setRender('link');
+
+			$i->addLink($l);
+
+			$l = new Link();
+			$l->setHref('');
+			$l->setRel('put');
+			$l->setAlt('Atualizar');
+			$l->setPrompt('Atualizar');
+			$l->setRender('link');
+
+			$i->addLink($l);
+
+			$l = new Link();
+			$l->setHref('/userldap/'.$value['uid']);
+			$l->setRel('get');
+			$l->setAlt('Buscar');
+			$l->setPrompt('Buscar');
+			$l->setRender('link');
+
+			$i->addLink($l);
+			$c->addItem($i);
+	    }
+
+		$t = new Template();
+		$d = new Data();
+
+		$d->setName('name');
+		$d->setValue(null);
+		$d->setPrompt('Nome do Usuario');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$t->addData($d);
+
+		$d = new Data();
+		$d->setName('email');
+		$d->setValue(null);
+		$d->setPrompt('Email do Usuario');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$t->addData($d);
+
+		$d = new Data();
+		$d->setName('telefone');
+		$d->setValue(null);
+		$d->setPrompt('Telefone do Usuario');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$t->addData($d);
+
+	    $queries = new Querie();    
+		$queries->setHref($c->href);
+	    $queries->setData('field','',true);
+	    $queries->setData('value','',true);
+	    $c->addQueries($queries);
+
+
+		$h->setCollection($c);
+
+	}catch (Exception $ex){
+		$error = new Error();
+		$error->setCode(Response::INTERNALSERVERERROR);
+		$error->setTitle('Internal Server Error');
+		$error->setDescription($ex);
+
+		$c->setError($error);
+		$h->setCollection($c);
+
+		$response->code = Response::INTERNALSERVERERROR;
+		$response->body = $h->getHypermedia($request->accept[10][0]);
+		return $response;
+	}
+
+	$response->body = $h->getHypermedia($request->accept[10][0]);
+	return $response;
+  }
+}
+?>
Index: trunk/prototype/rest/user/UserResource.php
===================================================================
--- trunk/prototype/rest/user/UserResource.php	(revision 6528)
+++ trunk/prototype/rest/user/UserResource.php	(revision 6528)
@@ -0,0 +1,158 @@
+<?php
+
+if( !defined( 'ROOTPATH' ) )
+    define( 'ROOTPATH', dirname(__FILE__).'/..' );
+
+require_once(ROOTPATH.'/rest/hypermedia/hypermedia.php');
+
+class UserResource extends Resource {
+
+    /**
+     * Retorna uma lista de usuários
+     *
+     * @license    http://www.gnu.org/copyleft/gpl.html GPL
+     * @author     Consórcio Expresso Livre - 4Linux (www.4linux.com.br) e Prognus Software Livre (www.prognus.com.br)
+     * @sponsor    Caixa Econômica Federal
+     * @author     José Vicente Tezza Jr. 
+     * @return     Retorna um usuário do LDAP filtrado pelo atributo 'uid'
+     * @access     public
+     * */
+    function get($request, $id) {
+
+	$response = new Response($request);
+	$response->addHeader('Content-type', 'aplication/json');
+	$response->code = Response::OK;
+
+	$h = new Hypermedia();
+	$c = new Collection($request->resources, 'UserResource');
+
+	try {
+
+	    $this->secured();
+
+	    $user = Controller::find(array('concept' => 'user','service'=>'OpenLDAP'), false, array('filter' => array('=', 'uid', $id )));
+
+	    //Se nao foi encontrado usuario na consulta
+	    if($user===false){
+			$error = new Error();
+			$error->setCode(Response::NOTFOUND);
+			$error->setTitle('UserLDAP not found');
+			$error->setDescription('User not found.');
+
+			$c->setError($error);
+			$h->setCollection($c);
+
+			$response->code = Response::NOTFOUND;
+			$response->body = $h->getHypermedia($request->accept[10][0]);
+			return $response;
+	    }
+
+		$t = new Template();
+		$d = new Data();
+
+		$d->setName('name');
+		$d->setValue(null);
+		$d->setPrompt('Nome do Usuário');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$t->addData($d);
+		$d = new Data();
+
+		$d->setName('email');
+		$d->setValue(null);
+		$d->setPrompt('Email do Usuário');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$t->addData($d);
+
+		$d = new Data();
+		$d->setName('telefone');
+		$d->setValue(null);
+		$d->setPrompt('Telefone do Usuário');
+		$d->setDataType('string');
+		$d->setMaxLength(100);
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$t->addData($d);
+
+		$c->setTemplate($t);
+		
+		$d = new Data();
+		$d->setName('name');
+		$d->setValue($user[0]['name']);
+		$d->setPrompt('Nome do Usuário');
+		$d->setDataType('string');
+		$d->setMaxLength('100');
+		$d->setMinLength(null);
+		$d->setRequired(true);
+
+		$c->addData($d);
+		
+		$d = new Data();
+		$d->setName('email');
+		$d->setValue($user[0]['mail']);
+		$d->setPrompt('Email do Usuário');
+		$d->setDataType('string');
+		$d->setMaxLength('100');
+		$d->setMinLength(null);
+		$d->setRequired(true);
+		
+		$c->addData($d);
+		
+		$d = new Data();
+		$d->setName('telephoneNumber');
+		$d->setValue($user[0]['telephoneNumber']);
+		$d->setPrompt('Telefone do Usuário');
+		$d->setDataType('string');
+		$d->setMaxLength('100');
+		$d->setMinLength(null);
+		$d->setRequired(true);
+			
+		$c->addData($d);
+		
+		$l = new Link();
+		$l->setHref('');
+		$l->setRel('delete');
+		$l->setAlt('Remover');
+		$l->setPrompt('Remover');
+		$l->setRender('link');
+
+		$c->addLink($l);
+
+		$l = new Link();
+		$l->setHref('');
+		$l->setRel('put');
+		$l->setAlt('Atualizar');
+		$l->setPrompt('Atualizar');
+		$l->setRender('link');
+
+		$c->addLink($l);
+
+		$h->setCollection($c);
+
+	}catch (Exception $ex){
+		$error = new Error();
+		$error->setCode(Response::INTERNALSERVERERROR);
+		$error->setTitle('Internal Server Error');
+		$error->setDescription($ex);
+
+		$c->setError($error);
+		$h->setCollection($c);
+
+		$response->code = Response::INTERNALSERVERERROR;
+		$response->body = $h->getHypermedia($request->accept[10][0]);
+		return $response;
+	}
+
+	$response->body = $h->getHypermedia($request->accept[10][0]);
+	return $response;
+  }
+}
+?>
Index: trunk/expressoMail1_2/index.php
===================================================================
--- trunk/expressoMail1_2/index.php	(revision 6487)
+++ trunk/expressoMail1_2/index.php	(revision 6528)
@@ -38,6 +38,11 @@
 		<script src="../prototype/plugins/farbtastic/farbtastic.js" language="javascript"></script>
 		<script src="../prototype/api/datalayer.js" language="javascript"></script>					
+		<script src="../prototype/api/rest.js" language="javascript"></script>					
 		<script type="text/javascript" src="../prototype/plugins/scrollto/jquery.scrollTo.js"></script>
-		<script language="javascript">DataLayer.dispatchPath = "../prototype/";</script>					
+		<script language="javascript">
+			DataLayer.dispatchPath = "../prototype/";
+			REST.dispatchPath = "../prototype/";
+			REST.load("");
+		</script>					
 		<script src="../library/ckeditor/ckeditor.js" language="javascript" charset="utf-8"></script> 
 		<script src="../library/ckeditor/adapters/jquery.js" language="javascript"></script> 
@@ -56,4 +61,5 @@
 		<script type="text/javascript" src="../prototype/plugins/datejs/sugarpak.js"></script>
 		<script type="text/javascript" src="../prototype/plugins/datejs/parser.js"></script>
+		<script type="text/javascript" src="../prototype/plugins/jq-raty/js/jquery.raty.min.js"></script>
 
 		<script type="text/javascript" src="../prototype/plugins/watermark/jquery.watermarkinput.js"></script>
@@ -71,8 +77,8 @@
 		<script type="text/javascript" src="../prototype/modules/calendar/js/calendar.date.js"></script>
 		<script type="text/javascript" src="../prototype/modules/calendar/js/calendar.codecs.js"></script>
-		
 		<link rel="stylesheet" type="text/css" href="../prototype/plugins/freeow/style/freeow/freeow.css" >
 		<script type="text/javascript" src="../prototype/plugins/freeow/jquery.freeow.min.js"></script>
 		<script type="text/javascript" src="../prototype/plugins/freeow/jquery.freeow.js"></script>
+
 
 		<script src="js/rich_text_editor.js" type="text/javascript"></script>
@@ -197,4 +203,5 @@
 	$_SESSION['phpgw_info']['user']['preferences']['expressoMail']['telephone_number'] = $GLOBALS['phpgw_info']['user']['telephonenumber'];
 	$_SESSION['phpgw_info']['user']['preferences']['expressoMail']['use_cache'] = $current_config['expressoMail_enable_cache'];
+	$_SESSION['phpgw_info']['user']['preferences']['expressoMail']['expressoMail_ldap_identifier_recipient'] = $current_config['expressoMail_ldap_identifier_recipient'];
 	$_SESSION['phpgw_info']['user']['preferences']['expressoMail']['use_x_origin'] = $current_config['expressoMail_use_x_origin'];
 	$_SESSION['phpgw_info']['user']['preferences']['expressoMail']['number_of_contacts'] = $current_config['expressoMail_Number_of_dynamic_contacts'] ? $current_config['expressoMail_Number_of_dynamic_contacts'] : "0";
@@ -218,5 +225,4 @@
 	$_SESSION['phpgw_info']['user']['preferences']['expressoMail']['auto_create_local'] = $GLOBALS['phpgw_info']['user']['preferences']['expressoMail']['auto_create_local'] ? $GLOBALS['phpgw_info']['user']['preferences']['expressoMail']['auto_create_local'] : "0";
 	$_SESSION['phpgw_info']['user']['preferences']['expressoMail']['return_recipient_deafault'] = $GLOBALS['phpgw_info']['user']['preferences']['expressoMail']['return_recipient_deafault'] ? $GLOBALS['phpgw_info']['user']['preferences']['expressoMail']['return_recipient_deafault'] : "0";
-
 	$_SESSION['phpgw_info']['user']['preferences']['expressoMail']['quick_search_default'] = $GLOBALS['phpgw_info']['user']['preferences']['expressoMail']['quick_search_default'] ? $GLOBALS['phpgw_info']['user']['preferences']['expressoMail']['quick_search_default'] : 1;
 	// 	ACL for block edit Personal Data.
@@ -277,5 +283,6 @@
     $_SESSION['phpgw_info']['server']['expressomail']['expressoMail_use_spam_filter'] = $current_config['expressoMail_use_spam_filter'];   
     echo '<script> var use_spam_filter = \''.$current_config['expressoMail_use_spam_filter'].'\'
-           var sieve_forward_domains = \''.$current_config['expressoMail_sieve_forward_domains'].'\' </script>';
+           var sieve_forward_domains = \''.$current_config['expressoMail_sieve_forward_domains'].'\' 
+		  </script>';
 	// End Set Anti-Spam options.
 
Index: trunk/expressoMail1_2/templates/default/listContacts.ejs
===================================================================
--- trunk/expressoMail1_2/templates/default/listContacts.ejs	(revision 6528)
+++ trunk/expressoMail1_2/templates/default/listContacts.ejs	(revision 6528)
@@ -0,0 +1,13 @@
+
+<%if (data.type == 'linha'){%>
+<li>
+	<div class="line-separator">
+		&nbsp;
+	</div>
+<%}else{%>
+<li>
+	<img style="position:relative; top:2px; "src="templates/default/images/<%=data.type == 'pessoal' ? 'contato_pessoal' : 'contato_recente'%>.png" title="Contato <%=data.type == 'pessoal' ? data.type : 'recente'%>"/>
+	<a style="width:80%; display: inline-block;"><%= data.name %> - <%= data.email %></a>
+	<span style="width:16px; height:16px; top:5px; <%=data.type == 'pessoal' ? 'display:none' : ''%>">Excluir contato recente</span>	
+<%}%>
+</li>
Index: trunk/expressoMail1_2/templates/default/config.tpl
===================================================================
--- trunk/expressoMail1_2/templates/default/config.tpl	(revision 5988)
+++ trunk/expressoMail1_2/templates/default/config.tpl	(revision 6528)
@@ -98,11 +98,11 @@
     </td>
     </tr>
+<!--    <tr bgcolor="{row_on}">
+    <td>{lang_Number_of_dynamic_contacts}</td>
+    <td>
+    <input size="1" name="newsettings[expressoMail_Number_of_dynamic_contacts]" value="{value_expressoMail_Number_of_dynamic_contacts}"> 
+    </td>
+    </tr> -->
     <tr bgcolor="{row_on}">
-    <td>{lang_Number_of_dynamic_contacts}</td>
-    <td>
-    <input size="1" name="newsettings[expressoMail_Number_of_dynamic_contacts]" value="{value_expressoMail_Number_of_dynamic_contacts}"> 
-    </td>
-    </tr>
-    <tr bgcolor="{row_off}">
     <td>{lang_imap_max_folders}:</td>
     <td>
@@ -110,5 +110,5 @@
     </td>
     </tr>
-    <tr bgcolor="{row_on}">
+    <tr bgcolor="{row_off}">
     <td>{lang_Max_attachment_size}</td>
     <td>
@@ -118,5 +118,5 @@
     </td>
     </tr>
-	<tr bgcolor="{row_off}"> 
+	<tr bgcolor="{row_on}"> 
  	    <td>{lang_allow_hidden_copy}</td> 
  	    <td> 
@@ -133,5 +133,5 @@
     </td>
     </tr>
-        <tr bgcolor="{row_off}">
+        <tr bgcolor="{row_on}">
         <td>{lang_gears_firefox_windows_url}</td>
         <td>
@@ -139,5 +139,5 @@
         </td>
     </tr>
-    <tr bgcolor="{row_on}">
+    <tr bgcolor="{row_off}">
         <td>{lang_gears_firefox_linux_url}</td>
         <td>
@@ -145,5 +145,5 @@
         </td>
     </tr>
-   <tr bgcolor="{row_off}">
+   <tr bgcolor="{row_on}">
         <td>{lang_gears_ie_url}</td>
         <td>
@@ -151,5 +151,5 @@
         </td>
     </tr>
-    <tr bgcolor="{row_on}">
+    <tr bgcolor="{row_off}">
     <td>{lang_Do_you_want_to_use_x_origin_in_source_menssage?}</td>
     <td>
@@ -160,5 +160,5 @@
     </td>
     </tr>
-	 <tr bgcolor="{row_off}">
+	 <tr bgcolor="{row_on}">
 		<td>{lang_Number_max_of_labels}</td>
 		<td>
@@ -244,4 +244,30 @@
         </td>
     </tr>
+	
+	<tr bgcolor="{th_bg}"> 
+		<td colspan="2"> 
+			&nbsp; 
+		</td> 
+	</tr>
+	<tr bgcolor="{row_on}"> 
+		<td colspan="2"> 
+			<label style="font-weight:bold;">{lang_Identifier_of_the_recipient_of_a_message}</label> 
+		</td> 
+	</tr>
+	<tr bgcolor="{row_off}">
+        <td>{lang_LDAP_attribute_used_to_replacement}</td>
+        <td>
+            <select id="identifier_recipient" name="newsettings[expressoMail_ldap_identifier_recipient]">
+				{rows_ldap_identifier}
+            </select>
+        </td>
+    </tr> 
+	
+	<!-- <tr bgcolor="{row_off}">
+        <td>{lang_LDAP_attribute_used_to_replacement}</td>
+        <td>
+            <input type="text" id="identifier_recipient " value="{value_expressoMail_ldap_identifier_recipient}" name="newsettings[expressoMail_ldap_identifier_recipient]" size=10 maxlength=10 />
+        </td>
+    </tr> -->
     <!--tr bgcolor="{row_on}">
         <td>{lang_Days_interval_to_show_balloon_for_user}</td>
Index: trunk/expressoMail1_2/templates/default/main.css
===================================================================
--- trunk/expressoMail1_2/templates/default/main.css	(revision 6459)
+++ trunk/expressoMail1_2/templates/default/main.css	(revision 6528)
@@ -127,4 +127,7 @@
 .context-menu-item.icon-normal { background-image: url(images/door.png); }
 .context-menu-item.icon-followupflag { background-image: url(images/door.png); }
+.context-menu-item.icon-quick-add { background-image: url(images/vcard_add.png); }
+.context-menu-item.icon-delete-box{ background-image: url(images/user_delete.png); }
+.context-menu-item.icon-quick-search-contact{ background-image: url(images/zoom.png); }
 
 
@@ -700,5 +703,5 @@
 }
 .message_options_trash {
-	background-image: url(../../../phpgwapi/templates/default/images/foldertree_trash.png);
+	background-image: url(images/page_white_delete.png);
 	background-repeat: no-repeat;
 	background-position:left center;
@@ -1274,3 +1277,91 @@
 } 
 
-#detalhes_contato img{ margin-bottom: -4px;} 
+#detalhes_contato img{ margin-bottom: -4px;}
+
+ button.expressomail-button-icon-ative{color:#E17009 !important; border: 1px solid #E17009 !important;background: none repeat scroll 0 0 #FFE1CC !important;}
+ 
+.box{
+	background-color: #96B3D3;
+	border-radius: 5px;
+	border: 1px solid #201b41; 
+	display: inline-block;
+	cursor : pointer;
+	margin-top : 1px;
+	margin-right: 1px;
+	outline : none;
+	padding : 1px;
+}
+
+
+.box span{
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 13px;
+	color : #201b41;
+	outline : none;
+}
+
+.invalid-email-box{
+	background-color: #F08080;
+	border : 1px solid #540303 !important;
+}
+
+.invalid-email-box span{
+	color: #540303;
+}
+
+.email-area{
+	border-radius: 5px; 
+	width: 98.5%;
+	padding: 5px;
+	border : 1px solid #BBBBBB; 
+	heigth : auto;
+	max-height : 115px;
+	overflow-y: auto;
+}
+.email-text{
+	outline:none;
+	width:15px;
+	border : none;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 13px;
+}
+
+.box-input{
+	outline:none;
+	border : none;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 13px;
+	display : none;
+}
+
+.box-selected{
+	background-color : #BBBBBB;
+}
+
+.hidden {
+	display : none;
+}
+
+.loading {
+	background: url(../../../prototype/modules/mail/img/loader.gif) !important;
+	background-position: 0 0 !important;
+	background-repeat : no-repeat !important;
+}
+
+.line-separator{
+	background: url("images/linha.png") repeat-x scroll 0 8px transparent;
+	clear : both;
+}
+
+.box-draggable-hover{
+	border : 1px solid #474747;
+}
+
+.box-loading{
+	background : url("../../../prototype/modules/mail/img/loading.gif") no-repeat !important;
+	background-size: 17px auto !important;
+}
+
+.box-info{
+	background : url("images/information.png") no-repeat !important;
+}
Index: trunk/expressoMail1_2/templates/default/searchMails.ejs
===================================================================
--- trunk/expressoMail1_2/templates/default/searchMails.ejs	(revision 5751)
+++ trunk/expressoMail1_2/templates/default/searchMails.ejs	(revision 6528)
@@ -3,5 +3,5 @@
 			
 				<label><%=data.From%>:</label>
-				<input style="margin-left: 6px;" type="text" id="txt_de" size="20" />
+				<input style="margin-left: 6px;" type="text" id="txt_de" size="20" value="<%=data.From_value%>"/>
 				<br style="margin-bottom:15px" />
 
Index: trunk/expressoMail1_2/setup/default_records.inc.php
===================================================================
--- trunk/expressoMail1_2/setup/default_records.inc.php	(revision 5988)
+++ trunk/expressoMail1_2/setup/default_records.inc.php	(revision 6528)
@@ -1,3 +1,13 @@
 <?php
+  /**************************************************************************\
+  * eGroupWare - Setup                                                       *
+  * http://www.egroupware.org                                                *
+  * --------------------------------------------                             *
+  *  This program is free software; you can redistribute it and/or modify it *
+  *  under the terms of the GNU General Public License as published by the   *
+  *  Free Software Foundation; either version 2 of the License, or (at your  *
+  *  option) any later version.                                              *
+  \**************************************************************************/
+
 			$oProc->query("ALTER TABLE expressomail_message_followupflag ADD CONSTRAINT expressomail_message_followupflag_followupflag_id_fkey FOREIGN KEY (followupflag_id) REFERENCES expressomail_followupflag (id);");
 			
@@ -15,4 +25,5 @@
 			$oProc->query("INSERT INTO phpgw_hooks( \"hook_appname\", \"hook_location\", \"hook_filename\") VALUES ('expressoMail1_2', 'config_validate', 'hook_config_validate.inc.php')");
 
-		
+			/* Cria um indice unico para um owner e mail para nao ocorrer duplicidade em e-mails para um mesmo owner  */
+			$oProc->query("ALTER TABLE expressomail_dynamic_contact ADD CONSTRAINT owner_mail UNIQUE (owner, mail)");
 ?>
Index: trunk/expressoMail1_2/setup/tables_current.inc.php
===================================================================
--- trunk/expressoMail1_2/setup/tables_current.inc.php	(revision 5981)
+++ trunk/expressoMail1_2/setup/tables_current.inc.php	(revision 6528)
@@ -10,14 +10,19 @@
 	\**************************************************************************/
 	$phpgw_baseline = array(
-		'phpgw_expressomail_contacts' => array(
+		'expressomail_dynamic_contact' => array(
 			'fd' => array(
-				'id_owner' => array( 'type' => 'int', 'precision' => 8, 'nullable' => false),
-				'data' => array( 'type' => 'text')
+				'id' => array('type' => 'auto','nullable' => False),
+				'owner' => array('type' => 'int','precision' => '16','nullable' => False),
+				'name' => array('type' => 'varchar','precision' => '100','nullable' => true),
+				'mail' => array('type' => 'varchar','precision' => '100','nullable' => False),
+				'number_of_messages' => array('type' => 'int','precision' => '16','nullable' => False),
+				'timestamp' => array('type' => 'int','precision' => '16','nullable' => False),
 			),
-			'pk' => array('id_owner'),
+			'pk' => array('id'),
 			'fk' => array(),
 			'ix' => array(),
 			'uc' => array()
 		),
+		
         'phpgw_certificados' => array(
             'fd' => array(
Index: trunk/expressoMail1_2/setup/tables_update.inc.php
===================================================================
--- trunk/expressoMail1_2/setup/tables_update.inc.php	(revision 6255)
+++ trunk/expressoMail1_2/setup/tables_update.inc.php	(revision 6528)
@@ -267,4 +267,51 @@
 	    return $GLOBALS['setup_info']['expressoMail1_2']['currentver'];
 	}
-	
+
+	$test[] = '2.4.7';
+	function expressoMail1_2_upgrade2_4_7() {
+		$oProc = $GLOBALS['phpgw_setup']->oProc;
+
+		//Criando nova tabela de contatos dinamicos
+		$oProc->CreateTable('expressomail_dynamic_contact',array(
+			'fd' => array(
+				'id' => array('type' => 'auto','nullable' => False),
+				'owner' => array('type' => 'int','precision' => '16','nullable' => False),
+				'name' => array('type' => 'varchar','precision' => '100','nullable' => true),
+				'mail' => array('type' => 'varchar','precision' => '100','nullable' => False),
+				'number_of_messages' => array('type' => 'int','precision' => '16','nullable' => False),
+				'timestamp' => array('type' => 'int','precision' => '16','nullable' => False),
+			),
+			'pk' => array('id'),
+			'fk' => array(),
+			'ix' => array(),
+			'uc' => array()
+			)
+		);
+
+		/* Cria um indice unico para um owner e mail para nao ocorrer duplicidade em e-mails para um mesmo owner  */
+		$oProc->query("ALTER TABLE expressomail_dynamic_contact ADD CONSTRAINT owner_mail UNIQUE (owner, mail)");
+
+		//Migra dados antigos para nova tabela
+		$oProc->query('SELECT * FROM phpgw_expressomail_contacts');
+		$return = array();
+	        while($oProc->next_record())
+	        	$return[$oProc->f('id_owner')] = $oProc->f('data');
+
+	        foreach ($return as $owner => &$value) {
+        		$contacts = unserialize($value);
+        		foreach ($contacts as &$contact) {
+	        		$info = explode('#', $contact['email']);
+        			$oProc->query("INSERT INTO expressomail_dynamic_contact (owner, name ,mail , number_of_messages ,timestamp) values ('".$owner."', '".$info[0]."', '".$info[1]."', 1, '".$contact['timestamp']."');");
+	        	}
+        	}
+
+	        //Deleta tabela antiga
+		$oProc->DropTable('phpgw_expressomail_contacts');
+
+		/* Remove a restricao de quantidade de contatos recentes */
+		$oProc->query("DELETE FROM phpgw_config WHERE config_app = 'expressoMail1_2' AND config_name = 'expressoMail_Number_of_dynamic_contacts'");
+
+		$GLOBALS['setup_info']['expressoMail1_2']['currentver'] = '2.4.8';
+	    return $GLOBALS['setup_info']['expressoMail1_2']['currentver'];
+	}
 ?>
Index: trunk/expressoMail1_2/setup/setup.inc.php
===================================================================
--- trunk/expressoMail1_2/setup/setup.inc.php	(revision 6255)
+++ trunk/expressoMail1_2/setup/setup.inc.php	(revision 6528)
@@ -13,7 +13,6 @@
 $setup_info['expressoMail1_2']['name'] = 'expressoMail1_2';
 $setup_info['expressoMail1_2']['title'] = 'Expresso Mail';
-$setup_info['expressoMail1_2']['version'] = '2.4.7';
+$setup_info['expressoMail1_2']['version'] = '2.4.8';
 $setup_info['expressoMail1_2']['app_order'] = 2;
-$setup_info['expressoMail1_2']['tables'][] = 'phpgw_expressomail_contacts';
 $setup_info['expressoMail1_2']['tables'][] = 'phpgw_certificados';
 
@@ -22,4 +21,5 @@
 $setup_info['expressoMail1_2']['tables'][] = 'expressomail_message_followupflag';
 $setup_info['expressoMail1_2']['tables'][] = 'expressomail_followupflag';
+$setup_info['expressoMail1_2']['tables'][] = 'expressomail_dynamic_contact';
 
 
@@ -48,3 +48,9 @@
     'versions' => Array('2.4')
 );
+
+$setup_info['expressoMail1_2']['depends'][] = array(
+    'appname' => 'rest',
+    'versions' => Array('1.0')
+);
+
 ?>
Index: trunk/expressoMail1_2/inc/class.db_functions.inc.php
===================================================================
--- trunk/expressoMail1_2/inc/class.db_functions.inc.php	(revision 6331)
+++ trunk/expressoMail1_2/inc/class.db_functions.inc.php	(revision 6528)
@@ -24,5 +24,4 @@
 	include_once(PHPGW_API_INC.'/class.db.inc.php'); 
 } 
-include_once('class.dynamic_contacts.inc.php');
 	
 class db_functions
@@ -249,5 +248,5 @@
 
 	//Gera lista de contatos para ser gravado e acessado pelo expresso offline.
-	function get_dropdown_contacts_to_cache() {
+	/*function get_dropdown_contacts_to_cache() {
 		return $this->get_dropdown_contacts();
 	}
@@ -275,5 +274,5 @@
 		}
 		return $stringDropDownContacts;	
-	}
+	}*/
 	function getUserByEmail($params){	
 		// Follow the referral
@@ -308,5 +307,5 @@
 		return $result;
 	}
-	
+	/*
 	function get_dynamic_contacts()
 	{				
@@ -333,4 +332,6 @@
 		return $contacts;
 	}
+	*/
+	/*
 	function update_contacts($contacts=array())
 	{			
@@ -364,5 +365,6 @@
 		}
 		return $contacts;
-	}	
+	}	*/
+	
 	function update_preferences($params){
 		$string_serial = urldecode($params['prefe_string']);				
@@ -377,4 +379,5 @@
 	}
 	
+	/*
 	function insert_contact($contact)	
 	{
@@ -396,5 +399,5 @@
 		$this->db->delete('phpgw_expressomail_contacts',$where,$line,$file);	
 	}
-	
+	*/ 
 	function import_vcard($params){
             include_once('class.imap_functions.inc.php');
Index: trunk/expressoMail1_2/inc/class.imap_functions.inc.php
===================================================================
--- trunk/expressoMail1_2/inc/class.imap_functions.inc.php	(revision 6487)
+++ trunk/expressoMail1_2/inc/class.imap_functions.inc.php	(revision 6528)
@@ -3087,14 +3087,7 @@
                     error_log("$now - $userip - $sent [$subject] - $userid => $addrs\r\n", 3, "/home/expressolivre/mail_senders.log");
                 }
-                if ($_SESSION['phpgw_info']['user']['preferences']['expressoMail']['use_dynamic_contacts']) {
-                    $contacts = new dynamic_contacts();
-                    $new_contacts = $contacts->add_dynamic_contacts($toaddress . "," . $ccaddress . "," . $ccoaddress);
-                    return array("success" => true, "new_contacts" => $new_contacts);
-                }
-                
-                   if($params['uids_save'] )
-                        $this->delete_msgs(array('folder'=> $params['save_folder'] , 'msgs_number' => $params['uids_save']));
+                if($params['uids_save'] )
+					$this->delete_msgs(array('folder'=> $params['save_folder'] , 'msgs_number' => $params['uids_save']));
                        
-                
                 //return array("success" => true, "folder" => $folder_list);
 				return array("success" => true, "load" => $has_new_folder);
Index: trunk/expressoMail1_2/inc/class.ldap_functions.inc.php
===================================================================
--- trunk/expressoMail1_2/inc/class.ldap_functions.inc.php	(revision 6459)
+++ trunk/expressoMail1_2/inc/class.ldap_functions.inc.php	(revision 6528)
@@ -564,5 +564,5 @@
 			'<tr class="quicksearchcontacts_unselected">' .
 				'<td class="cc" width="1%">' .
-					'<a title="'.$this->functions->getLang("Write message").'" onClick="javascript:QuickSearchUser.create_new_message(\''.$contacts_result["cn"].'\', \''.$contacts_result["mail"].'\')">' .
+					'<a title="'.$this->functions->getLang("Write message").'" onClick="javascript:QuickSearchUser.create_new_message(\''.$contacts_result["cn"].'\', \''.$contacts_result["mail"].'\', \''.$contacts_result["uid"].'\')">' .
 						$photo_link .
 					'</a>' .
@@ -570,5 +570,5 @@
 				'<td class="cc">' .
 					'<span name="cn">' . ($empNumber != "" ? $empNumber : $uid) . $contacts_result['cn'] . '</span>' . '<br>' .
-					'<a title="'.$functions->getLang("Write message").'" onClick="javascript:QuickSearchUser.create_new_message(\''.$contacts_result["cn"].'\', \''.$contacts_result["mail"].'\')">' .
+					'<a title="'.$functions->getLang("Write message").'" onClick="javascript:QuickSearchUser.create_new_message(\''.$contacts_result["cn"].'\', \''.$contacts_result["mail"].'\', \''.$contacts_result["uid"].'\')">' .
 						'<font color=blue>' .
 						'<span name="mail">' . $contacts_result['mail'] . '</span></a></font>'.
Index: trunk/expressoMail1_2/js/common_functions.js
===================================================================
--- trunk/expressoMail1_2/js/common_functions.js	(revision 6291)
+++ trunk/expressoMail1_2/js/common_functions.js	(revision 6528)
@@ -775,5 +775,5 @@
 }
 
-function search_emails(value){
+function search_emails(value, data){
 	var resize = false;
         resize = resize_borders();
@@ -815,8 +815,8 @@
 	connector.loadScript("search");
 	if (typeof(EsearchE) == 'undefined' || typeof(ttree) == 'undefined'){
-		setTimeout("search_emails('"+value+"')",500);
+		setTimeout("search_emails('"+value+"', '"+data+"')",500);
 		return false;
 	}
-	EsearchE.showForms(value);
+	EsearchE.showForms(value, data);
 	$("#em_message_search").val("");
 	}
Index: trunk/expressoMail1_2/js/main.js
===================================================================
--- trunk/expressoMail1_2/js/main.js	(revision 6494)
+++ trunk/expressoMail1_2/js/main.js	(revision 6528)
@@ -9,9 +9,15 @@
 var checkAlarmsFilter = false;
 
+var dynamicPersonalContacts = new Array();
+var dynamicPersonalGroups = new Array();
+var dynamicContacts = new Array();
+var dynamicContactList = new Array();
+var topContact = 0;
+
 //Os IE's < 9 não possui suporte a trim() introduzida no JavaScript 1.8.1
 if(!String.prototype.trim){  
 	String.prototype.trim = function(){
-								return this.replace(/^\s+|\s+$/g,'');
-							} 
+		return this.replace(/^\s+|\s+$/g,'');
+	} 
 }
 	
@@ -21,9 +27,72 @@
 	return folders.join(cyrus_delimiter);
 } 
+
+function updateDynamicContactList(){
+	dynamicContactList = new Array();
+	$.merge(dynamicContactList, dynamicContacts);
+	$.merge(dynamicContactList, dynamicPersonalGroups);
+	$.merge(dynamicContactList, dynamicPersonalContacts);
+}
+
+function updateDynamicContact(){
+	dynamicContacts = new Array();
+	var dynamicData = REST.get("/dynamiccontacts").collection.itens;
+	if(dynamicData){
+		$.each(dynamicData, function(index, value){
+			if(index ==0){
+				topContact = parseInt(value.data[2].value);
+			}
+			var dynamic = {
+				name : value.data[0].value,
+				email : value.data[1].value,
+				value: value.data[0].value + " - " + value.data[1].value,
+				type: "",
+				id: parseInt(value.data[3].value),
+				qtd : parseInt(value.data[2].value)
+			};
+			dynamicContacts.push(dynamic);
+		});
+	}
+}
+
+function updateDynamicPersonalGroups(){
+	dynamicPersonalGroups = new Array();
+	var groupsData = REST.get("/groups").collection.itens;
+	if(groupsData){
+		$.each(groupsData, function(index, value){
+			var group = {
+				id : parseInt(value.data[0].value),
+				name : value.data[2].value,
+				email : value.data[2].value,
+				value: value.data[2].value + " - " + value.data[2].value,
+				type: "G"
+			};
+			dynamicPersonalGroups.push(group);
+		});
+	}
+}
+
+function updateDynamicPersonalContacts(){
+	dynamicPersonalContacts = new Array();
+	var contactsData = REST.get("/contacts").collection.itens;
+	if(contactsData){
+		$.each(contactsData, function(index, value){
+			var contact = {
+				id : parseInt(value.data[0].value),
+				name : value.data[1].value,
+				email : value.data[2].value,
+				value: value.data[1].value + " - " + value.data[2].value,
+				type: "P"
+			};
+			dynamicPersonalContacts.push(contact);
+		});
+	}
+}
+
 function init(){
 	if (!is_ie)
 		Element('tableDivAppbox').width = '100%';
 
-	var save_contacts = function(data){
+	/*var save_contacts = function(data){
 		contacts = data;
 		if (preferences.use_local_messages == 1 && window.google && google.gears)
@@ -31,4 +100,13 @@
 				expresso_local_messages.capt_url('controller.php?action=$this.db_functions.get_dropdown_contacts_to_cache');
 
+	}*/
+	if(parseInt(preferences.use_dynamic_contacts)){
+		var load_dynamics = function(){
+			updateDynamicContact();
+			updateDynamicPersonalGroups();
+			updateDynamicPersonalContacts();
+			updateDynamicContactList();
+		};
+		load_dynamics();
 	}
 	var save_preferences = function(data){
@@ -71,5 +149,5 @@
 		//Substituido por padrão Jquery
 		cExecute ("$this.imap_functions.get_range_msgs2&folder=INBOX&msg_range_begin=1&msg_range_end="+preferences.max_email_per_page+"&sort_box_type=SORTARRIVAL&search_box_type=ALL&sort_box_reverse=1", handler_draw_box);
-		cExecute ("$this.db_functions.get_dropdown_contacts", save_contacts); //Save contacts needs preferences.
+		//cExecute ("$this.db_functions.get_dropdown_contacts", save_contacts); //Save contacts needs preferences.
 		if(preferences.hide_folders == "1")
 			Element('divAppboxHeader').innerHTML =  title_app_menu;
@@ -193,5 +271,5 @@
 	cyrus_delimiter = Element('cyrus_delimiter').value;
 
-	cExecute ("$this.db_functions.get_dropdown_contacts_to_cache", function(data) {contacts = data;});
+	//cExecute ("$this.db_functions.get_dropdown_contacts_to_cache", function(data) {contacts = data;});
 	//cExecute ("$this.functions.get_preferences", save_preferences);
 	}
@@ -1760,5 +1838,8 @@
 		case "reply_without_history":
 			RichTextEditor.replyController = true; //Seta o editor como modo reply
-			content.find('[name="input_to"]').val(data.to);
+			content.find('[name="input_to"]').val(data.to);			
+			
+			draw_reply_boxes_by_field("to", data.to, content);
+			
 			title = "Re: " + html_entities(data.subject);
 			content.find(".subject").val("Re: " + data.subject);			
@@ -1777,4 +1858,7 @@
 			content.find(".subject").val("Re: " + data.subject);
 			content.find('[name="input_to"]').val(data.to);
+			
+			draw_reply_boxes_by_field("to", data.to, content);
+			
 			content.find('[name="msg_reply_from"]').val($("#msg_number_" + border_ID).val());	
 
@@ -1807,5 +1891,7 @@
 			else
 				content.find('[name="input_to"]').val(data.to + ',' + data.to_all);
-				
+			
+			draw_reply_boxes_by_field("to", data.to_all, content);
+			
 			if (data.cc){
 				data.cc = new Array();
@@ -1818,11 +1904,10 @@
 				if(data.cc != ""){
 					content.find('[name="input_cc"]').val(data.cc);
-					input_binds(content.find('[name="input_cc"]'), new_border_ID);
+					input_binds(content.find('[name="input_cc"]').parent(), new_border_ID);
 					content.find(".cc-tr").show();//cc-button
 					//document.getElementById("a_cc_link" + new_border_ID).value = data.cc;
 					content.find(".cc-button").toggleClass("expressomail-button-icon-ative");
 					content.find(".cc-button").find("span").html("Remover CC");
-					content.find('[name="input_cc"]').elastic().unbind('blur');
-					content.find('[name="input_cc"]').trigger("update");
+					draw_reply_boxes_by_field("cc", data.cc, content);
 				}
 			}
@@ -1846,4 +1931,7 @@
 			else
 				data.to_all = "";
+
+			draw_reply_boxes_by_field("to", data.to_all, content);
+			
 			title = "Re: " + html_entities(data.subject);
 			
@@ -1862,10 +1950,10 @@
 				if(data.cc != ""){
 					content.find('[name="input_cc"]').val(data.cc);
-					input_binds(content.find('[name="input_cc"]'), new_border_ID);
+					input_binds(content.find('[name="input_cc"]').parent(), new_border_ID);
 					content.find(".cc-tr").show();
 					content.find(".cc-button").toggleClass("expressomail-button-icon-ative");
 					content.find(".cc-button").find("span").html("Remover CC");
-					content.find('[name="input_cc"]').elastic().unbind('blur');
-					content.find('[name="input_cc"]').trigger("update");
+					
+					draw_reply_boxes_by_field("cc", data.cc, content);
 				}
 			}
@@ -1922,4 +2010,5 @@
 				}
 				content.find('[name="input_to"]').val(_to +',');
+				draw_email_box(_to, content.find(".to").filter("input"));
 				Element('msg_number').value = '';
 			}
@@ -1947,4 +2036,6 @@
 			}
 
+			draw_reply_boxes_by_field("to", data.to, content);
+			
 			content.find('[name="input_to"]').val(data.to);
 			if (data.cc){
@@ -1952,10 +2043,9 @@
 				data.cc = data.cc.replace(/&gt;/gi,">");
 				content.find('[name="input_cc"]').val(data.cc);
-				input_binds(content.find('[name="input_cc"]'), new_border_ID);
+				input_binds(content.find('[name="input_cc"]').parent(), new_border_ID);
 				content.find(".cc-tr").show();
 				content.find(".cc-button").toggleClass("expressomail-button-icon-ative");
 				content.find(".cc-button").find("span").html(get_lang('Remove CC'));
-				content.find('[name="input_cc"]').elastic().unbind('blur');
-				content.find('[name="input_cc"]').trigger("update");
+				draw_reply_boxes_by_field("cc", data.cc, content);
 			}
 			if (data.cco){
@@ -1965,6 +2055,6 @@
 					content.find(".cco-button").toggleClass("expressomail-button-icon-ative");
 					content.find(".cco-button").find("span").html(get_lang('Remove CCo'));
-					content.find('[name="input_cco"]').elastic().unbind('blur');
-					content.find('[name="input_cco"]').trigger("update");
+					input_binds(content.find('[name="input_cco"]').parent(), new_border_ID);
+					draw_reply_boxes_by_field("cco", data.cco, content);
 				}
 			}
@@ -2006,4 +2096,12 @@
 	resizeWindow();
 	return new_border_ID; //Preciso retornar o ID da nova mensagem.
+}
+
+//DESENHA OS RETANGULOS PARA OS E-MAIL NA OPÇÃO REPLY
+function draw_reply_boxes_by_field(field, value, context){
+	array = value.split(",");
+	$.each(array, function(index, value){
+		draw_email_box(value, context.find("."+field).filter("input"));
+	});
 }
 
@@ -2241,6 +2339,8 @@
 	watch_changes_in_msg(ID);
 
+	var content = $("#content_id_"+ID);
 	var sign = false;
 	var crypt = false;
+	var reComplexEmail = /<([^<]*)>[\s]*$/;
 	if ((preferences.use_assinar_criptografar != '0') && (preferences.use_signature_digital_cripto != '0')){
 		var checkSign = document.getElementById('return_digital_'+ID)
@@ -2282,6 +2382,6 @@
 	if(data && data.success == true ){
 		// if send ok, set a flag as answered or forwarded
-		var msg_number_replied = $("#content_id_" + ID).find('[name="msg_reply_from"]');
-		var msg_number_forwarded = $("#content_id_" + ID).find('[name="msg_forward_from"]');
+		var msg_number_replied = content.find('[name="msg_reply_from"]');
+		var msg_number_forwarded = content.find('[name="msg_forward_from"]');
 
 		if (msg_number_replied.val()){
@@ -2304,6 +2404,9 @@
 			}
 		}
+		
+		$
+		//REFAZER ISTO COM UMA CHAMADA ASSINCRONA PARA REGISTRAR E ATUALIZAR A LISTA DOS NOVOS CONTATOS DINAMICOS
 		// If new dynamic contacts were added, update the autocomplete ....
-		if(data.new_contacts){
+		/*if(data.new_contacts){
 			var ar_contacts = data.new_contacts.split(',;');
 			for(var j in ar_contacts){
@@ -2313,12 +2416,70 @@
 			}
 		}
+		var dynamicPersonalContacts = new Array();
+		var dynamicPersonalGroups = new Array();
+		var dynamicContacts = new Array();
+		var dynamicContactList = new Array();
+
+		*/
+		var array = content.find(".to-tr").find(".box").clone();
 		delete_border(ID,'true'); 
+		if(parseInt(preferences.use_dynamic_contacts)){
+			$.each(array, function(i, value){
+				var stop = false;
+				$.each(dynamicPersonalContacts, function(x, valuex){
+					if(valuex.email == $(value).find("input").val().match(reComplexEmail)[1]){
+						stop = true;
+						return false;
+					}
+				});
+				if(!stop){
+					var exist = 0;
+					$.each(dynamicContacts, function(x, valuex){
+						if(valuex.email == $(value).find("input").val().match(reComplexEmail)[1]){
+							exist = valuex.id;
+							return false;
+						}
+					});
+					if(exist){
+						REST.put("/dynamiccontact/"+exist, {name: $(value).find("input").val().split('"')[1], mail:$(value).find("input").val().match(reComplexEmail)[1]});
+					}else{
+						REST.post("/dynamiccontacts", {name: $(value).find("input").val().split('"')[1], mail:$(value).find("input").val().match(reComplexEmail)[1]});
+					}
+				}
+			});
+			updateDynamicContact();
+			updateDynamicContactList();
+		}
  	}
 	else{
 		if(data == 'Post-Content-Length')
 			write_msg(get_lang('The size of this message has exceeded  the limit (%1B).',Element('upload_max_filesize').value));
-		else if(data)
+		else if(data){
+			var error_mail = $.trim(data.split(":")[data.split(":").length-1]);
+			var array = content.find(".to-tr").find(".box");
+			//$(value).find("input").val()
+			$.each(array, function(index, value){
+				if(error_mail == $(value).find("input").val().match(reComplexEmail)[1])
+					$(value).addClass("invalid-email-box");
+			});
+			if ( content.find('[name="input_cco"]').length){
+				if(content.find(".cco-tr").css("display") != "none"){
+					var array = content.find(".cco-tr").find(".box");
+					$.each(array, function(index, value){
+						if(error_mail == $(value).find("input").val().match(reComplexEmail)[1])
+							$(value).addClass("invalid-email-box");
+					});
+				}
+			}
+			if(content.find(".cc-tr").css("display") != "none")
+			{
+				var array = content.find(".cc-tr").find(".box");
+				$.each(array, function(index, value){
+					if(error_mail == $(value).find("input").val().match(reComplexEmail)[1])
+						$(value).addClass("invalid-email-box");
+				});				
+			} 
 			write_msg(data);
-		else
+		}else
 			write_msg(get_lang("Connection failed with %1 Server. Try later.", "Web"));
 		
@@ -2525,12 +2686,29 @@
 		return;
 	}
-	stringEmail = content.find('[name="input_to"]').val();
-	
-	if ( content.find('[name="input_cco"]').length)
-		if(content.find(".cco-tr").css("display") != "none")
-			stringEmail += content.find('[name="input_cco"]').val() =='' ? "":", "+content.find('[name="input_cco"]').val();
+	//stringEmail = content.find('[name="input_to"]').val();
+	var stringEmail = "";
+	var array = content.find(".to-tr").find(".box");
+	$.each(array, function(index, value){
+		stringEmail += $(value).find("input").val() + ",";
+	});
+	content.find('[name="input_to"]').val(stringEmail);
+	if ( content.find('[name="input_cco"]').length){
+		if(content.find(".cco-tr").css("display") != "none"){
+			var array = content.find(".cco-tr").find(".box");
+			$.each(array, function(index, value){
+				stringEmail += $(value).find("input").val() + ",";
+			});
+			content.find('[name="input_cco"]').val(stringEmail);
+		}
+	}
 			
 	if(content.find(".cc-tr").css("display") != "none")
-		stringEmail += content.find('[name="input_cc"]').val() =='' ? "":", "+content.find('[name="input_cc"]').val();
+	{
+		var array = content.find(".cc-tr").find(".box");
+		$.each(array, function(index, value){
+			stringEmail += $(value).find("input").val() + ",";
+		});
+		content.find('[name="input_cc"]').val(stringEmail);
+	}
 		
 	if (expresso_offline) {
@@ -2632,5 +2810,33 @@
        autoSaveControl.status[border_id] = true;
    ///////////////////////////////////////////
- 
+    var content = $("#content_id_"+border_id);
+	
+   	var stringEmail = "";
+	var array = content.find(".to-tr").find(".box");
+	$.each(array, function(index, value){
+		stringEmail += $(value).find("input").val() + ",";
+	});
+	content.find('[name="input_to"]').val(stringEmail);
+	stringEmail = "";
+	if ( content.find('[name="input_cco"]').length){
+		if(content.find(".cco-tr").css("display") != "none"){
+			var array = content.find(".cco-tr").find(".box");
+			$.each(array, function(index, value){
+				stringEmail += $(value).find("input").val() + ",";
+			});
+			content.find('[name="input_cco"]').val(stringEmail);
+		}
+	}
+	
+	stringEmail = "";	
+	if(content.find(".cc-tr").css("display") != "none")
+	{
+		var array = content.find(".cc-tr").find(".box");
+		$.each(array, function(index, value){
+			stringEmail += $(value).find("input").val() + ",";
+		});
+		content.find('[name="input_cc"]').val(stringEmail);
+	}
+	
    var idJavascript = saveBorderError[border_id];
    
Index: trunk/expressoMail1_2/js/ccQuickAdd.js
===================================================================
--- trunk/expressoMail1_2/js/ccQuickAdd.js	(revision 6341)
+++ trunk/expressoMail1_2/js/ccQuickAdd.js	(revision 6528)
@@ -33,5 +33,5 @@
 										data = data + $(this).find('#quickAddOne_firstName').val() + ',';
 										data = data + $(this).find('#quickAddOne_lastName').val() + ',,';
-										data = data + $(this).find('#quickAddOne_email').val();	
+										data = data + $(this).find('#quickAddOne_email').val();
 										if(ccQuickAddOne.send(data))
 											$(this).dialog("close");
@@ -102,4 +102,7 @@
 		
 		connector.newRequest('cQuickAdd.Send', CC_url+'quick_add', 'POST', handler, sdata);
+		
+		updateDynamicPersonalContacts();
+		updateDynamicContactList();
 		return true;
 	}
Index: trunk/expressoMail1_2/js/QuickSearchUser.js
===================================================================
--- trunk/expressoMail1_2/js/QuickSearchUser.js	(revision 5477)
+++ trunk/expressoMail1_2/js/QuickSearchUser.js	(revision 6528)
@@ -53,17 +53,17 @@
 	}
 	
-	emQuickSearchUser.prototype.create_new_message = function (cn, mail)
+	emQuickSearchUser.prototype.create_new_message = function (cn, mail, uid)
 	{
 		QuickSearchUser.closeWindow();
+		var ldap_id = preferences.expressoMail_ldap_identifier_recipient;
 		
-		if (openTab.type[currentTab] != 4)
-		{
-			Element("msg_number").value = "\""+cn+"\" <"+mail+">";
+		if (openTab.type[currentTab] != 4){
 			new_message("new","null");
 		}
-		else
-		{
-			var ToField = Element('to_'+currentTab);
-			ToField.value = ToField.value +"\""+cn+"\" <"+mail+">,";
+		
+		if(ldap_id){
+			draw_email_box(uid, $("#content_id_"+currentTab).find(".to").filter("input")); 
+		}else{
+			draw_email_box("\""+cn+"\" <"+mail+">", $("#content_id_"+currentTab).find(".to").filter("input")); 
 		}
 	}
Index: trunk/expressoMail1_2/js/search.js
===================================================================
--- trunk/expressoMail1_2/js/search.js	(revision 6456)
+++ trunk/expressoMail1_2/js/search.js	(revision 6528)
@@ -20,5 +20,5 @@
 	
 	//Monta os forms dentro da janela;
-	searchE.prototype.showForms = function(value)
+	searchE.prototype.showForms = function(value, data)
 	{
 		if( trim(value) != "" )
@@ -39,5 +39,5 @@
 			var div		= document.createElement("div");
 			var args	= null;
-	
+			
 			args = 
 			{
@@ -63,7 +63,8 @@
 				"Old" 						: get_lang('Old'),
 				"Search_the_messages_in_these_folders" : get_lang('Search the messages in these folders'),
-				"In_all_the_folders"		: get_lang('In all the folders')
-			}
-			
+				"In_all_the_folders"		: get_lang('In all the folders'),
+				"From_value" : (data != "undefined" ? data : "")
+			}
+				
 			$(div).html(DataLayer.render("./templates/default/searchMails.ejs", args ));
 			div.setAttribute( "style","overflow:hidden");
@@ -515,8 +516,8 @@
 				td.id = "td_message_answered_"+uid_msg;
 				if (aux.flag.match('X'))
-					td1 = '<img src=templates/'+template+'/images/forwarded.gif title="'+get_lang('Forwarded')+'">';
+					td1 = '<img src=templates/'+template+'/images/forwarded.png title="'+get_lang('Forwarded')+'">';
 				else
 					if (aux.flag.match('A'))
-						td1 = '<img src=templates/'+template+'/images/answered.gif title="'+get_lang('Answered')+'">';
+						td1 = '<img src=templates/'+template+'/images/answered.png title="'+get_lang('Answered')+'">';
 					else
 						td1 = '';
Index: trunk/expressoMail1_2/js/draw_api.js
===================================================================
--- trunk/expressoMail1_2/js/draw_api.js	(revision 6487)
+++ trunk/expressoMail1_2/js/draw_api.js	(revision 6528)
@@ -1495,5 +1495,5 @@
 		if ((headers_msgs.Forwarded == 'F')  || (headers_msgs.Draft == 'X' && headers_msgs.Answered == 'A')){
 			td_element21.onclick=function(){search_emails(headers_msgs.subject.replace(/^(re: ?|fw: ?|enc: ?|res: ?|fwd: ?)*/gi,''),true);};
-                        td_element21.innerHTML = "<img src ='templates/"+template+"/images/forwarded.gif' title='"+get_lang('Forwarded')+"'>";
+                        td_element21.innerHTML = "<img src ='templates/"+template+"/images/forwarded.png' title='"+get_lang('Forwarded')+"'>";
 			headers_msgs.Draft = ''
 			headers_msgs.Answered = '';
@@ -1501,8 +1501,8 @@
 		}
 		else if (headers_msgs.Draft == 'X')
-			td_element21.innerHTML = "<img src ='templates/"+template+"/images/draft.gif' title='"+get_lang('Draft')+"'>";
+			td_element21.innerHTML = "<img src ='templates/"+template+"/images/draft.png' title='"+get_lang('Draft')+"'>";
 		else if (headers_msgs.Answered == 'A'){
                         td_element21.onclick=function(){search_emails(headers_msgs.subject.replace(/^(re: ?|fw: ?|enc: ?|res: ?|fwd: ?)*/gi,''),true);};
-			td_element21.innerHTML = "<img src ='templates/"+template+"/images/answered.gif' title='"+get_lang('Answered')+"'>";
+			td_element21.innerHTML = "<img src ='templates/"+template+"/images/answered.png' title='"+get_lang('Answered')+"'>";
                     }else
 			td_element21.innerHTML = "&nbsp;&nbsp;&nbsp;";
@@ -3540,54 +3540,521 @@
 }
 
-function input_binds(input, ID){
-	var mySource = new Array();
-	var myArray = contacts.split(",");
-	for(var i in myArray){
-		var teste = myArray[i].split(";");
-		if(teste.length > 1)
-			mySource.push({name : teste[0], email: teste[1], value : (teste[0] +" - "+teste[1])});
-		else
-			mySource.push({name : "", email: teste[0], value :teste[0]});
-	}
-	input.bind( "keydown", function( event ) {
-		if ( event.keyCode === $.ui.keyCode.TAB && $( this ).data( "autocomplete" ).menu.active ) {
-			event.preventDefault();
-		}
-		if((event.keyCode) == 120){
+//DESENHO DAS CAIXA DE EMAIL
+function draw_email_box(input_data, location, personal){
+	if($.trim(input_data) != ""){
+		var box_data = valid_emails(input_data);
+		DataLayer.render("../prototype/modules/mail/templates/emailBox.ejs", box_data, function(html){
+			var newBox = location.before(html).prev();
+			box_actions(newBox);
+			if((preferences.expressoMail_ldap_identifier_recipient || personal)&& $(newBox).hasClass("invalid-email-box")){
+				//$(newBox).find(".loading").css("background-image", "../prototype/modules/mail/img/ajax-loader.gif");
+				show_detais(newBox, input_data, personal);
+			}else{
+				$(newBox).find(".box-loading").remove();
+			}
+		});
+	}
+}
+
+function valid_emails(email){
+	var ContactBox = {name:"", email:"", valid : false};
+	var reSimpleEmail = /^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[_a-z0-9-]+(\.[_a-z0-9-]+)+$/;
+	var reComplexEmail = /<([^<]*)>[\s]*$/;
+	var validation = email.split('"');
+	
+	//FUNÇÃO QUE VALIDA OS DADOS QUANDO O EMAIL É DIGITADO COM ("NOME SOBRENOME" <Email@dominio.com>)
+	var complexValidation = function(complexMail){
+		var Objct = {};
+		if($.trim(complexMail[1]).match(reComplexEmail)){
+			if($.trim(complexMail[1]).match(reComplexEmail).length){
+				Objct['email'] = $.trim(complexMail[1]).match(reComplexEmail)[1];
+			}
+		}else{
+			Objct['email'] = $.trim(complexMail[1]);
+		}
+		Objct['name'] = complexMail[0];
+		Objct['valid'] = reSimpleEmail.test(Objct['email'].toLowerCase());
+		return Objct;
+	}
+	switch (validation.length) {
+		//PEGA TODO O CONTEUDO E SETA COMO SE FOSSE O EMAIL
+		case 1:
+			validation.unshift("");
+			ContactBox = complexValidation(validation);
+			break;
+		//CORRIGI ERRO DE DIGITAÇÃO COMO ( huahua"<huhau@hauhau.com>) ou (hahahaha"huahua@email.com) ou ainda (hahahaha"huahua@ema  il.com)
+		case 2: 
+			ContactBox = complexValidation(validation);
+			break;
+		//RECEBE O EMAIL CORRETAMENTE SÓ VALIDA POSSIVEIS ERROS COMO O DE CIMA E OS CORRIGI CASO ACONTEÇAM
+		case 3:
+			//RETIRA O PRIMEIRO INDICE QUE FICOU "INUTIL"
+			validation.shift();
+			ContactBox = complexValidation(validation);
+			break;
+		//SE EXISTIREM MAIS DO QUE 2 (")
+		default:
+			if($.trim(validation[validation.length-1]).match(reComplexEmail)){
+				if($.trim(validation[validation.length-1]).match(reComplexEmail).length){
+					ContactBox.mail = $.trim(validation[validation.length-1]).match(reComplexEmail)[1];
+				}
+			}else{
+				ContactBox.mail = $.trim(complexMail[1]);
+			}
+			ContactBox.valid = reSimpleEmail.test(ContactBox.mail.toLowerCase());	
+	}
+	return ContactBox;
+}
+
+//EVENTO DOS INPUTS PARA - CC - CCO
+function input_keydowns(input, ID){
+	var f9 = false;
+	input.keydown(function(e){
+		f9 = false;
+		var focusing = input.parent().find(".email-text");
+		//SE OS CONTATOS DINAMICOS ESTAO ATIVOS
+		if(parseInt(preferences.use_dynamic_contacts) && !input.hasClass("box-input")){
+			//SELECIONA O CONTATO E EVITA OUTROS COMANDOS
+			if ( e.keyCode === $.ui.keyCode.TAB && $( this ).data( "catcomplete" ).menu.active ) {
+				e.preventDefault();
+				return false;
+			}		
+			
+			//FECHA OS CONTATOS DINÂMICOS
+			if( (e.keyCode == 27) && $( this ).data( "catcomplete" ).menu.active ){
+				   e.stopPropagation();
+				   e.preventDefault();
+			}
+			
+			//SELECIONA O CONTATO E EVITA OUTROS COMANDOS
+			if(e.keyCode == $.ui.keyCode.ENTER && $( this ).data( "catcomplete" ).menu.active){
+				e.preventDefault();
+				return false;
+			}
+			
+			if(e.keyCode == $.ui.keyCode.DELETE && $( this ).data( "catcomplete" ).menu.active){
+				if($($( this ).data( "catcomplete" ).menu.element).find(".ui-state-hover").parents("li:first").hasClass("dynamic-recent"))
+					$($( this ).data( "catcomplete" ).menu.element).find(".ui-state-hover").next().trigger("click");
+				return false;
+			}
+		}
+		//BUSCA COM A TECLA F9
+		if((e.keyCode) == 120){
+			f9 = true;
 			emQuickSearch($(this).val(), "."+$(this).parents("tr:first").attr('class').split("-")[0], ID, undefined, true);
-		}else if( (event.keyCode == 27) && $( this ).data( "autocomplete" ).menu.active ){
-			   event.stopPropagation();
-               event.preventDefault();
-		}
-	}).autocomplete({
-		minLength: 0,			
-		source: function( request, response ) {
-			response( $.ui.autocomplete.filter(
-				mySource, extractLast( request.term ) ) );
+			e.preventDefault();
+			return false;
+		}
+
+		//AO DIGITAR ENTER, ";", "," "	"
+		if(e.keyCode == 13 || e.keyCode == 9 || (e.keyCode == 188 && !e.shiftKey) || (e.keyCode == 191 && !e.shiftKey) ){
+			if(e.keyCode == 13){
+				e.preventDefault();
+			}
+			if(input.val() != ""){
+				if(e.keyCode != 9)
+					e.preventDefault();
+				draw_email_box(input.val(), input);
+				if(input.hasClass("box-input")){
+					input.remove();
+					focusing.focus();
+					return;
+				}
+			}
+			if(input.length)
+				input.val("");
+		}
+		if((e.keyCode == 8 || e.keyCode == 37) && input.val() == "" && input.prev().hasClass("box")){
+			e.preventDefault();
+			input.prev().focus();
+			return;
+		}
+		//AO DIGITAR " "
+		if(e.keyCode == 32){
+			if(!input.val().length){
+				e.preventDefault();
+				return;
+			}else{
+				var texto = input.val();
+				texto = texto.substring(0, getPosition(input[0]));
+				if(texto.split('"').length-1 == 0){
+					if(input.val().split('"').length-1 == 0){
+						e.preventDefault();
+						e.stopPropagation();
+						draw_email_box(input.val(), input);
+						input.val("");
+						if(input.hasClass("box-input")){
+							input.remove();
+							focusing.focus();
+							return;
+						}
+						return;
+					}
+				}
+			}
+		}
+		//AO DIGITAR ">"
+		if(e.keyCode == 190 && e.shiftKey && input.val().length == getPosition(input[0])){
+			input.val(input.val()+">");
+			draw_email_box(input.val(), input);
+			e.preventDefault();
+			input.val("");
+			if(input.hasClass("box-input")){
+				input.remove();
+				focusing.focus();
+				return;
+			}
+		}
+		
+		//INPUT AUTO RESIZE	
+		setTimeout(function(){
+			input.css("width", 15+(input.val().length * 9));	
+			input.parent().scrollTo(":last");	
+		}, 100);
+	})
+	//AO SAIR DO FOCO MONTAGEM DA CAIXA DE EMAIL
+	.focusout(function(){
+		if(!$(this).data('is_open')){
+			if(!(f9 || $(this).parents("tr:first").find("button").hasClass("ui-state-active"))){
+				if($(input).val() != "")
+					draw_email_box(input.val(), input);
+				if(input.hasClass("box-input"))
+					input.remove();
+			}
+		
+			input_search = $(input).val();
+			$(input).val("");
+		}
+	})
+	//AO COLAR UM TEXTO NO CAMPO
+	.bind("paste", function(e){
+		$(this).trigger("keydown");
+		var pthis = $(this);
+		setTimeout(function() {
+			var str = pthis.val().replace(/[,;\t\n]/gi, ",");
+			str = str.split(",");
+			$.each(str, function(index, value){
+				draw_email_box(value, pthis);
+			});
+			pthis.val("");
+		}, 50);
+	});
+	//SE FOR EDIÇÃO DE EMAILS RECALCULA O INPUT E SETA O FOCO
+	if(input.hasClass("box-input")){
+		input.trigger("keydown");
+		input.focus();
+	}
+}
+var input_search = "";
+//EVENTOS DA CAIXA
+function box_actions(box){
+	//AO PRESSIONAR UMA TECLA COM A CAIXA SELECIONADA	
+	box.keydown(function(e){
+		switch (e.keyCode) {
+			case $.ui.keyCode.LEFT:
+				//VERIFICA SE EXISTE ALGUMA CAIXA A ESQUERDA
+				if($(this).prev().hasClass("box"))
+					$(this).removeClass("box-selected").prev().focus();
+				break;
+			case $.ui.keyCode.RIGHT:
+				//VERIFICA SE EXISTE ALGUMA CAIXA A DIREITA 
+				if($(this).next().hasClass("box"))
+					$(this).removeClass("box-selected").next().focus();
+				//SENAO FOCO O INPUT DO EMAIL
+				else
+					$(this).removeClass("box-selected").next().focus();
+				break;
+			case $.ui.keyCode.HOME:
+				//SELECIONO A PRIMEIRA CAIXA
+				e.preventDefault();
+				$(this).parents(".email-area").find("div:first").focus();
+				break;
+			case $.ui.keyCode.END:
+				//SELECIONO A ULTIMA CAIXA
+				e.preventDefault();
+				$(this).parents(".email-area").find("div:last").focus();
+				break;
+			case $.ui.keyCode.DELETE:
+				//VERIFICA SE EXISTE ALGUMA CAIXA A DIREITA
+				if($(this).next().hasClass("box"))
+					$(this).next().focus();
+				//SENAO FOCO O INPUT DO EMAIL
+				else
+					$(this).next().focus();
+				//REMOVO ESTA CAIXA
+				$(this).remove();
+				break;
+			case $.ui.keyCode.BACKSPACE:
+				//VERIFICA SE EXISTE ALGUMA CAIXA A ESQUERDA
+				if($(this).prev().hasClass("box"))
+					$(this).removeClass("box-selected").prev().focus();
+				//SENAO HOUVER VERIFICA SE EXISTE ALGUMA CAIXA A DIREITA
+				else if($(this).next().hasClass("box"))
+					$(this).next().focus();
+				//SENAO HOUVER NEM A DIREITA NEM A ESQUERDA SETO O FOCO NO INPUT DO EMAIL
+				else
+					$(this).next().focus();
+				//REMOVO ESTA CAIXA	
+				$(this).remove();
+				e.preventDefault();
+				break;
+			case $.ui.keyCode.ENTER:
+				e.preventDefault();
+				$(this).trigger("dblclick");
+				break;
+		}
+	})
+	//AO FAZER UM DUPLO CLICK NA CAIXA
+	.dblclick(function(e){
+		var input = $(this).find("input").clone();
+		input.css("display" , "inline-block");
+		$(this).before(input);
+		input_keydowns(input, currentTab);
+		$(this).remove();
+	//CLICK SIMPLES NA CAIXA
+	}).click(function(){
+		$(this).focus();
+	//AO DAR O FOCO NA CAIXA
+	}).focus(function(){
+		$(this).parent().find("div").removeClass("box-selected");
+		$(this).addClass("box-selected");
+	}).focusout(function(){
+		$(this).removeClass("box-selected");
+	}).draggable({
+		revert: 'invalid',
+		helper : 'clone',
+		stack: "body",
+		containment : ".new-msg-head-data",
+		start: function(e, ui){
+			$(this).parent().droppable( "disable" );
 		},
-		focus: function() {
-			return false;
-		},
-		select: function( event, ui ) {
-			var terms = mySplit( this.value );
-			terms.pop();
-			terms.push( (ui.item.name != "" ? "\""+ui.item.name+"\" " : "") + (ui.item.email ? "<"+ui.item.email+">" : ""));
-			terms.push( "" );
-			this.value = terms.join( ", " );
-			return false;
-		},
-		autoFocus: true
-	}).data( "autocomplete" )._renderItem = function( ul, item ) {
-		ul.css({"max-height" : "115px", "overflow-y" : "auto"});
-		if( $(ul).find("li").length > 10 ){
-			return;
-		}
-		return $( "<li></li>" )
-			.data( "item.autocomplete", item )
-			.append( "<a>" + item.name + " - " + item.email + "</a>" )
-			.appendTo( ul );
-	};
-	input.parents("tr:first").find("button").button().click(function(){
-		emQuickSearch($(this).parents("tr:first").find("textarea").val(), "."+$(this).parents("tr:first").attr('class').split("-")[0], ID, undefined, true);
+		stop : function(e, ui){
+			$(this).parent().droppable( "enable" );
+		}
+	});
+}
+
+//MOSTRA OS DETALHES DAS CAIXA DE EMAIL NOS CAMPOS PARA - CC - CCO
+function show_detais(box, value, personal){
+	var ldap_id = preferences.expressoMail_ldap_identifier_recipient;
+	var group = (personal != undefined ? (personal == "G" ? true : false) : false);
+	
+	if(group){
+		REST.get("/group/"+value, {}, function(data){
+			if(!data.error){
+				if(data.collection.error)
+					box.find(".box-loading").remove();
+				else{
+					//box.find(".box-loading").css("background-image", "url(templates/default/images/information.png) no-repeat! !important");
+					box.find(".box-loading").removeClass("box-loading").addClass("box-info");
+					box.addClass("box-"+value).removeClass("invalid-email-box");
+					loadGroupBox(data.collection, ".box-"+value);
+					box.unbind("dblclick").bind("dblclick", function(e){
+						new $.Zebra_Dialog('<strong>Impossivel editar</strong> um contato do catálogo pessoal\n' +
+							'<strong>Porém</strong> é possivel remove-lo', {
+							'buttons':  false,
+							'modal': false,
+							'position': ['right - 20', 'top + 20'],
+							'auto_close': 3000
+						});
+					}).find(".box-input").val("\""+data.collection.data[1].value+"\" <"+data.collection.data[1].value+">");
+					box.find(".email-box-value").html( (data.collection.data[1].value.length > 18 ? data.collection.data[1].value.substring(0, 15)+"...": data.collection.data[1].value))
+				}
+			}else{
+				box.find(".box-loading").remove();
+			}
+		});
+		return;
+	}
+	
+	if(personal){
+		REST.get("/contact/"+value, {}, function(data){
+			if(!data.error){
+				if(data.collection.error)
+					box.find(".box-loading").remove();
+				else{
+					//box.find(".box-loading").css("background-image", "url(templates/default/images/information.png) no-repeat! !important");
+					box.find(".box-loading").removeClass("box-loading").addClass("box-info");
+					box.addClass("box-"+value).removeClass("invalid-email-box");
+					loadExtraLDAPBox(data.collection.data, ".box-"+value);
+					box.unbind("dblclick").bind("dblclick", function(e){
+						new $.Zebra_Dialog('<strong>Impossivel editar</strong> um contato do catálogo pessoal\n' +
+							'<strong>Porém</strong> é possivel remove-lo', {
+							'buttons':  false,
+							'modal': false,
+							'position': ['right - 20', 'top + 20'],
+							'auto_close': 3000
+						});
+					}).find(".box-input").val("\""+data.collection.data[0].value+"\" <"+data.collection.data[1].value+">");
+					box.find(".email-box-value").html( (data.collection.data[0].value.length > 18 ? data.collection.data[0].value.substring(0, 15)+"...": data.collection.data[0].value))
+				}
+			}else{
+				box.find(".box-loading").remove();
+			}
+		});
+		return;
+	}
+	
+	REST.get("/usersldap", {field : ldap_id,value: value}, function(data){
+		if(!data.error){
+			if(data.collection.error)
+				box.find(".box-loading").remove();
+			else{
+				//box.find(".box-loading").css("background-image", "url(templates/default/images/information.png) no-repeat! !important");
+				box.find(".box-loading").removeClass("box-loading").addClass("box-info");
+				box.addClass("box-"+value).removeClass("invalid-email-box");
+				loadExtraLDAPBox(data.collection.itens[0].data, ".box-"+value);
+				box.unbind("dblclick").bind("dblclick", function(e){
+					new $.Zebra_Dialog('<strong>Impossivel editar</strong> um contato do catálogo geral\n' +
+						'<strong>Porém</strong> é possivel remove-lo', {
+						'buttons':  false,
+						'modal': false,
+						'position': ['right - 20', 'top + 20'],
+						'auto_close': 3000
+					});
+				}).find(".box-input").val("\""+data.collection.itens[0].data[0].value+"\" <"+data.collection.itens[0].data[1].value+">");
+				box.find(".email-box-value").html( (data.collection.itens[0].data[0].value.length > 18 ? data.collection.itens[0].data[0].value.substring(0, 15)+"...": data.collection.itens[0].data[0].value))
+			}
+		}else{
+			box.find(".box-loading").remove();
+		}
+	});
+}
+
+//FUNÇÃO QUE "SETA" OS BINDS DOS CAMPOS PARA - CC - CCO
+function input_binds(div, ID){
+
+	//AO CLICAR NA DIV SETA O FOCO NO INPUT
+	div.click(function(e){
+		if(e.target == $(this)[0]){
+			$(this).find("input:last").focus();
+			$(this).find("div").removeClass("box-selected");
+		}
+	})
+	
+	//AO SAIR DO FOCO DA DIV ELE RETIRA TODAS AS CLASSES DE CAIXAS SELECIONADAS
+	.focusout(function(e){
+		if(!$(e.target).parents(".email-area:first").length)
+			$(this).find("div").removeClass("box-selected");
+	}).droppable({
+		hoverClass: "box-draggable-hover",
+		accept : ".box",
+		drop : function(e, ui){
+			ui.draggable.parent().droppable( "enable" );
+			var box = ui.draggable.clone();
+			box_actions(box);
+			if(box.find(".box-info").length){
+				box.unbind("dblclick").bind("dblclick", function(e){
+					new $.Zebra_Dialog('<strong>Impossivel editar</strong> um contato do ldap\n' +
+						'<strong>Porém</strong>é possivel remove-lo', {
+						'buttons':  false,
+						'modal': false,
+						'position': ['right - 20', 'top + 20'],
+						'auto_close': 3000
+					});
+				});
+			}
+			$(this).prepend(box);
+			ui.draggable.remove();
+		}
+	});
+	
+	//MAKE KEYDOWN
+	input_keydowns(div.find("input"), ID);
+	
+	
+	//VERIFICA PREFERENCIA DE CONTATOS DINÂMICOS ESTA ATIVA
+	if(parseInt(preferences.use_dynamic_contacts)){
+		//PREPARAÇÃO DA ARRAY DOS CONTATOS DINÂMICOS
+
+		$.widget( "custom.catcomplete", $.ui.autocomplete, {
+			_renderMenu: function( ul, items ) {
+				var self = this,
+				currentType = "";
+				$.each( items, function( index, item ) {
+					if ( item.type != currentType) {
+						if(item.type == "G" && $(ul).find(".dynamic-recent").length)
+							self._renderItem( ul, {name:"", value:"", type:"linha"} );
+						if(item.type == "P" && $(ul).find(".dynamic-group").length)
+							self._renderItem( ul, {name:"", value:"", type:"linha"} );	
+						currentType = item.type;
+					}
+					self._renderItem( ul, item );
+					$(ul).find("li:last").find(".dynamic-stars").raty({
+						readOnly : true,
+						half : true,
+						hints : ['','','','',''],
+						score    : ((parseInt(item.qtd)*5)/topContact),
+						starOn  : '../../prototype/plugins/jq-raty/img/star-on.png',
+						starOff : '../../prototype/plugins/jq-raty/img/star-off.png',
+						starHalf : '../../prototype/plugins/jq-raty/img/star-half.png'
+					});
+				});
+			}
+		});
+		//INSERÇÃO DO AUTO COMPLETE AO INPUT
+		div.find("input").catcomplete({
+			minLength: 1,			
+			source: function(request, response){
+				response( $.ui.autocomplete.filter(dynamicContactList, request.term ) );
+			},
+			focus: function() {
+				return false;
+			},
+			
+			//EVENTO AO SELECIONAR UM CONTATO DINÂMICO
+			select: function( event, ui ) {
+				event.preventDefault();
+				$(this).val("");
+				if(ui.item.type == "P")
+					draw_email_box(""+ui.item.id, $(this), true);
+				else if(ui.item.type == "G")
+					draw_email_box(""+ui.item.id, $(this), "G");
+				else
+					draw_email_box(ui.item.name ? "\""+ui.item.name+"\" <"+ui.item.email+">" : ui.item.email, $(this));
+				return false;
+			},
+			autoFocus: true,
+			position : { my: "left top", at: "left bottom", collision: "fit" }
+		}).bind('catcompleteopen', function(event, ui) {
+			$(this).data('is_open',true);
+		}).bind('catcompleteclose', function(event, ui) {
+			$(this).data('is_open',false);
+		})
+		
+		//MONTAGEM DA LISTA DE CONTATOS DINÂMICOS DO AUTO COMPLETE
+		.data( "catcomplete" )._renderItem = function( ul, item ) {
+			ul.css({"min-width":"400px", "width":"50%", "max-height" : "180px", "overflow-y" : "auto", "min-height": "30px"});
+			var listContacts = DataLayer.render("../prototype/modules/mail/templates/listContacts.ejs", item);
+			return $(listContacts).data( "item.autocomplete", item ).appendTo( ul ).find("span:last").button({
+					icons : {
+					primary : "ui-icon-close"
+				},
+				text: false
+			}).click(function(){
+				var removeLi = $(this).parents("li:first");
+				$.Zebra_Dialog('Deseja remover <b>'+(item.name ? item.name+" - " : "")+ item.email+'</b>?', {
+						'type':     'question',
+						'custom_class': (is_ie ? 'configure-zebra-dialog' : ''),
+						'title':    'Atenção',
+						'buttons': ['Sim','Não'],		
+						'overlay_opacity': '0.5',
+						'onClose':  function(caption) {
+							if(caption == 'Sim'){
+								$(removeLi).remove();
+								REST.delete("/dynamiccontact/"+item.id);
+								updateDynamicContact();
+								updateDynamicContactList();
+							}
+						}
+				});
+			});
+		};
+	}
+	
+	//FUNÇÃO DOS BOTÕES PARA - CC - CCO
+	div.parents("tr:first").find("button").button().click(function(){
+		if(!$(":focus").hasClass("new-message-input"))
+			emQuickSearch(($(this).parents("tr:first").find("input").val() ? $(this).parents("tr:first").find("input").val() : input_search), "."+$(this).parents("tr:first").attr('class').split("-")[0], ID, undefined, true);
 	});
 }
@@ -3596,4 +4063,5 @@
 	connector.loadScript("color_palette");
 	connector.loadScript('wfolders');
+	connector.loadScript("ccQuickAdd"); 
 	
 	if(typeof(RichTextEditor) == 'undefined' || typeof(ColorPalette) == 'undefined' || typeof(wfolders) == 'undefined')
@@ -3643,12 +4111,13 @@
 		button.toggleClass("expressomail-button-icon-ative");
 		field.toggle();
-		field.find("textarea").val("").focus();
-		if(!field.find("textarea").hasClass("elastic")){
-			field.find("textarea").css({"max-height" : "115px", "overflow-y" : "auto"}).addClass("elastic").elastic().unbind('blur');	
-			input_binds(field.find("textarea"), ID);
+		field.find("textarea").val("").parent().find("input").focus();
+		field.find(".email-area div").remove();
+		if(!field.find("textarea").hasClass("track")){
+			field.find("textarea").css({"max-height" : "115px", "overflow-y" : "auto"}).addClass("track");	
+			input_binds(field.find(".email-area"), ID);
 		}
 	} 
 	
-	input_binds(content.find('[name="input_to"]').css({"max-height" : "115px", "overflow-y" : "auto"}).addClass("elastic").elastic().unbind('blur').focus(), ID);	
+	input_binds(content.find('[name="input_aux_to"]').css("max-width" , parseInt($(".email-area").css("width"))-5).focus().parent().css({"max-height" : "115px", "overflow-y" : "auto"}), ID);	
 	
 	//Botão TextoRico/TextoSimples
Index: trunk/expressoMail1_2/js/doiMenuData.js
===================================================================
--- trunk/expressoMail1_2/js/doiMenuData.js	(revision 5866)
+++ trunk/expressoMail1_2/js/doiMenuData.js	(revision 6528)
@@ -195,5 +195,16 @@
 	items: menuToolsItems
 });
-
+var reComplexEmail = /<([^<]*)>[\s]*$/;
+$.contextMenu({
+	selector: ".box",
+	autoHide:true,
+	items: {
+		"add" : {name:"Adicao Rapida", icon : "quick-add",callback: function(key, opt){ var fname = $(opt.$trigger).find("input").val().split('"')[1];ccQuickAddOne.showList(','+fname+', ,'+$.trim($(opt.$trigger).find("input").val()).match(reComplexEmail)[1]); }},
+		"remove" : {name:"Remover Destinatario", icon:"delete-box",callback: function(key, opt){ $(opt.$trigger).remove(); }},
+		"sep1": "---------",
+		"quick_search" : {name:"Busca Rapida de Mensagens", icon: "quick-search-contact",callback: function(key, opt){ search_emails($.trim($(opt.$trigger).find("input").val()).match(reComplexEmail)[1]); }},
+		"full_search" : {name:"Buscar Mensagens de ...", icon: "quick-search-contact",callback: function(key, opt){ search_emails("", $.trim($(opt.$trigger).find("input").val()).match(reComplexEmail)[1]);}}		
+	}
+});
 
 function updateLabelsColumn(messageInfo) {
@@ -294,8 +305,51 @@
 }
 
-
-
-
-
-
-
+function loadExtraLDAPBox(data, element){
+	menuItensLabel = {};
+	menuItensLabel["Name"] = {name: "<b>"+data[0].value+"</b>", disabled: true};		
+	menuItensLabel["Email"] = {name: data[1].value, disabled: true};	
+	if(data[2].value){
+		menuItensLabel["TelefoneLabel"] = {name: "<b>Telefone</b>", disabled: true};
+		menuItensLabel["TelefoneValue"] = {name: data[2].value, disabled: true};
+	}
+	$.contextMenu({
+		selector: "#content_id_"+currentTab+" "+element+" .box-info",
+		trigger: 'hover',
+		delay:100,
+		autoHide:true,
+		items: menuItensLabel
+	});	
+}
+
+function loadGroupBox(data, element){
+	menuItensLabel = {};
+	menuItensLabel["ContactGroupLabelAll"] = {name:"<b>Contatos do Grupo</b>", disabled: true};
+	menuItensLabel["sep1"] = "---------";
+	if(data.itens){
+		var aux = 0;
+		for(var item in data.itens){
+			if(parseInt(item) <= 4){
+				menuItensLabel["ContactGroupLabel"+item] = {name: "<b>"+data.itens[item].data[0].value+"</b>", disabled: true};
+				menuItensLabel["ContactGroupValue"+item] = {name: data.itens[item].data[2].value, disabled: true};
+			}else{
+				aux++;
+				if(aux == 1)
+					menuItensLabel["MoreContactGroupValue"] = {name : "E mais "+aux+" contato...", disabled: true };
+				else
+					menuItensLabel["MoreContactGroupValue"] = {name : "E mais "+aux+" contatos...", disabled: true };
+			}
+		}
+	}
+	$.contextMenu({
+		selector: "#content_id_"+currentTab+" "+element+" .box-info",
+		trigger: 'hover',
+		delay:100,
+		autoHide:true,
+		items: menuItensLabel
+	});	
+}
+
+
+
+
+
Index: trunk/expressoMail1_2/js/QuickCatalogSearch.js
===================================================================
--- trunk/expressoMail1_2/js/QuickCatalogSearch.js	(revision 6480)
+++ trunk/expressoMail1_2/js/QuickCatalogSearch.js	(revision 6528)
@@ -290,6 +290,11 @@
 			var signal = "+";
 			var btnClass = "add";
-			var emailList = content.find(field_).val();
-			var emails_adicionados = emailList.split(",");
+			emails_adicionados = "";
+			var emailList = content.find(field_).filter("input").parent().find("div input");
+			//	var array = content.find(".to-tr").find(".box");
+			$.each(emailList, function(index, value){
+				emails_adicionados += $(value).val() + ",";
+			});
+			emails_adicionados = emails_adicionados.split(",");
 			for(aux=0; aux<emails_adicionados.length -1; aux++) {
 				if(emails_adicionados[aux].match(/<([^<]*)>[\s]*$/)){
@@ -697,5 +702,4 @@
 		var nome = $(contact).clone().find('.name').text();
 		var email = $(contact).clone().find('.email').text();
-		
 		if(email == get_lang("No mail")){
 			alert(get_lang("It is not possible to add this contact as a recipient because it does not have email"));
@@ -704,39 +708,29 @@
 			return;
 		}
-		
 		verifyEmails(email, divs);
-		
-		final_contact = "\""+nome +"\" <"+email+">, ";
-		
-		var emailList      = content.find(field_).val();
-		//remove os caracteres da busca
-		emailList = emailList.substr(0,emailList.lastIndexOf(',')+1); 
-		
-        if (emailList[0] != '"' && emailList.indexOf(",") < 0)
-			emailList = emailList.substr(end_);
-        if(emailList.lastIndexOf(",") > 0){
-		if(emailList.substr(emailList.length-1, emailList.length) == "," || emailList.substr(emailList.length-2, 1) == ","){
-			emailList = emailList.substr(0, emailList.lastIndexOf(",") + 1);
-        	} else {
-			emailList = emailList.substr(0, emailList.lastIndexOf("") + 1);		
-			final_contact = ","+final_contact;
+		final_contact = "\""+nome +"\" <"+email+">";
+
+		final_contact = final_contact.replace(/\/n/, ""); 
+		var index = parseInt(divs.split("_")[1])-1;
+		if(data_[index].type_contact == "G"){
+			var ldap_id = preferences.expressoMail_ldap_identifier_recipient;
+			if(ldap_id){
+				draw_email_box(
+					(data_[index][ldap_id.toLowerCase()] ? data_[index][ldap_id.toLowerCase()][0] : final_contact)
+					, content.find(field_).filter("input")
+				);
+			}else{
+				draw_email_box(final_contact, content.find(field_).filter("input"));
+			}
+		}else{
+			draw_email_box((data_[index].id_contact ? data_[index].id_contact : data_[index].id), content.find(field_).filter("input"), (data_[index].id_contact ? true : "G"));
 		}
-	}
-
-		final_contact = final_contact.replace(/\/n/, "");
-		new_emailList = emailList + final_contact;    
-        content.find(field_).val(new_emailList);
-		
-		content.find(field_).trigger("update");
-		
 		button.onclick = function(){
 			remove_contact_field(document.getElementById(divs).innerHTML, button, divs);
 		};
 		var div = document.getElementById(divs);
-		
 		div.ondblclick = function(){
 			remove_contact_field(div.innerHTML, button, divs);
 		};
-		
 		button.innerHTML = '<span class="ui-button-text" style="">x</span>';
     }
@@ -748,43 +742,21 @@
 	function remove_contact_field(contact, button, divs) {
 		var email = $(contact).clone().find('.email').text();
-		var exist = false;
-		
-		var emailList = content.find(field_).val();
-		var emails_adicionados = emailList.split(",");
-		
-		verifyEmails(email, divs);
-		
-		for(i=0; i<emails_adicionados.length -1; i++) {
-
-			if(emails_adicionados[i].match(/<([^<]*)>[\s]*$/)){
-				if(emails_adicionados[i].match(/<([^<]*)>[\s]*$/)[1].toLowerCase() == email.toLowerCase()) {
-					emails_adicionados[i] = "%";
-					exist = true;
-				}
-			}else{
-				if(emails_adicionados[i].toLowerCase() == email.toLowerCase()) {
-					emails_adicionados[i] = "%";
-					exist = true;
-				}
-			}
-		}
-		
-		if (exist == false) 
-			return false;
-		var novos_contatos = emails_adicionados.join(",");  
-		novos_contatos = novos_contatos.replace(/%,/g, "");
-		content.find(field_).val(novos_contatos);
-		
+		var array = content.find(field_).parent().find("div input");
+		$.each(array, function(index, value){
+			var validated_email = $(value).val();
+			if(validated_email.match(/<([^<]*)>[\s]*$/)){
+				if(validated_email.match(/<([^<]*)>[\s]*$/)[1].toLowerCase() == email.toLowerCase()) {
+					$(value).parent().remove();
+				}
+			}
+		});
+	
 		button.onclick = function(){
 			add_contact_field(document.getElementById(divs).innerHTML, button, divs);
 		};
-		
 		var div = document.getElementById(divs);
-		
 		div.ondblclick = function(){
 			add_contact_field(div.innerHTML, button, divs);
 		};
-		
-		content.find(field_).trigger("update");
 		button.innerHTML = '<span class="ui-button-text" style="">+</span>';
 	}
Index: trunk/home.php
===================================================================
--- trunk/home.php	(revision 5042)
+++ trunk/home.php	(revision 6528)
@@ -98,88 +98,90 @@
 		echo parse_navbar();
 	}
-	// Default Applications (Home Page) 
-	$default_apps = Array(			
-			'workflow',			
-			'expressoMail1_2',
-			'calendar',
-			'news_admin'
-		);
-	$sorted_apps = array();
-	$user_apps = $GLOBALS['phpgw_info']['user']['apps']; 
-	@reset($user_apps);	
-	for($i = 0; $i < count($default_apps);$i++) {
-		if(array_key_exists($default_apps[$i], $user_apps)){
-			$sorted_apps[] = $default_apps[$i];
-		}		
-	}
-	
-	foreach($GLOBALS['phpgw_info']['user']['apps'] as $i => $p) {
-		$sorted_apps[] = $p['name'];
-	}
-	
+
+        // Default Applications (Home Page) 
+        $default_apps = Array(                  
+                        'workflow',                     
+                        'expressoMail1_2',
+                        'calendar',
+                        'news_admin'
+                );
+        $sorted_apps = array();
+        $user_apps = $GLOBALS['phpgw_info']['user']['apps']; 
+        @reset($user_apps);     
+        for($i = 0; $i < count($default_apps);$i++) {
+                if(array_key_exists($default_apps[$i], $user_apps)){
+                        $sorted_apps[] = $default_apps[$i];
+                }               
+        }
+        
+        foreach($GLOBALS['phpgw_info']['user']['apps'] as $i => $p) {
+                $sorted_apps[] = $p['name'];
+        }
+
 	$portal_oldvarnames = array('mainscreen_showevents', 'homeShowEvents','homeShowLatest','mainscreen_showmail','mainscreen_showbirthdays','mainscreen_show_new_updated');
-	$done = array();
-	// Display elements, within appropriate table cells	
-	@reset($sorted_apps);
-	$idx = 1;
-	echo "<table width='100%' cellpadding=5>";
-	foreach($sorted_apps as $appname)
-	{
-		if((int)$done[$appname] == 1 || empty($appname)){
-			continue;
-		}		
-		$varnames = $portal_oldvarnames;
-		$varnames[] = 'homepage_display';
-		$thisd = 0;
-		$tmp = '';
-		
-		foreach($varnames as $varcheck)
-		{
-									
-			/*if($appname == 'expressoMail1_2') {
-				$tmp = $appname;
-				$appname = 'expressoMail';
-			}*/				
+        $done = array();
+        // Display elements, within appropriate table cells     
+        @reset($sorted_apps);
+        $idx = 1;
+        echo "<table width='100%' cellpadding=5>";
+        foreach($sorted_apps as $appname)
+        {
+                if((int)$done[$appname] == 1 || empty($appname)){
+                        continue;
+                }
+                $varnames = $portal_oldvarnames;
+                $varnames[] = 'homepage_display';
+                $thisd = 0;
+                $tmp = '';
 
-			if(array_search($appname, $default_apps) !== False){
-				$thisd = 1;
-				break;
-			}
-			if($GLOBALS['phpgw_info']['user']['preferences'][$appname][$varcheck]=='True') {
-				$thisd = 1;
-				break;
-			}
-			else  {
-				$_thisd = (int)$GLOBALS['phpgw_info']['user']['preferences'][$appname][$varcheck];
-				if($_thisd > 0) {
-					$thisd = $_thisd;
-					break;
-				}
-			}
-		}
+                foreach($varnames as $varcheck)
+                {
 
-		if($thisd > 0)
-		{
-			if($tmp) {
-               	$appname = $tmp;
-				$tmp = '';
-			}
-			if($idx == 0) {				
-				print '<tr>';
-			}
-			print '<td style="vertical-align:top;" width="45%">';
-			$GLOBALS['phpgw']->hooks->single('home',$appname);			
-			print '</td>';
-			
-			if($idx == 2){
-				$idx = 0;
-				print '</tr>';
-			}			
-			$idx++;
-			$neworder[] = $appname;
-		}
-		$done[$appname] = 1;
-	}
-	print '</table>';
+                        /*if($appname == 'expressoMail1_2') {
+                                $tmp = $appname;
+                                $appname = 'expressoMail';
+                        }*/
+
+                        if(array_search($appname, $default_apps) !== False){
+                                $thisd = 1;
+                                break;
+                        }
+                        if($GLOBALS['phpgw_info']['user']['preferences'][$appname][$varcheck]=='True') {
+                                $thisd = 1;
+                                break;
+                        }
+                        else  {
+                                $_thisd = (int)$GLOBALS['phpgw_info']['user']['preferences'][$appname][$varcheck];
+                                if($_thisd > 0) {
+                                        $thisd = $_thisd;
+                                        break;
+                                }
+                        }
+                }
+
+               if($thisd > 0)
+                {
+                        if($tmp) {
+                $appname = $tmp;
+                                $tmp = '';
+                        }
+                        if($idx == 0) {
+                                print '<tr>';
+                        }
+                        print '<td style="vertical-align:top;" width="45%">';
+                        $GLOBALS['phpgw']->hooks->single('home',$appname);
+                        print '</td>';
+
+                        if($idx == 2){
+                                $idx = 0;
+                                print '</tr>';
+                        }
+                        $idx++;
+                        $neworder[] = $appname;
+                }
+                $done[$appname] = 1;
+        }
+        print '</table>';
+
 	$GLOBALS['phpgw']->common->phpgw_footer();
 ?>
