From 2854426e8609e0f9e3ceac2e27327532bf00a408 Mon Sep 17 00:00:00 2001
From: mattpiwik <matthieu.aubry@gmail.com>
Date: Mon, 4 Aug 2008 00:11:03 +0000
Subject: [PATCH] oops i totally screwed up my last commit, deleting /modules
 instead of renaming it...

git-svn-id: http://dev.piwik.org/svn/trunk@587 59fd770c-687e-43c8-a1e3-f5a4ff64c105
---
 core/API/APIable.php                          |   55 +
 core/API/Proxy.php                            |  485 +++++
 core/API/Request.php                          |  588 ++++++
 core/Access.php                               |  326 ++++
 core/Archive.php                              |  224 +++
 core/Archive/Array.php                        |  161 ++
 core/Archive/Array/IndexedByDate.php          |  122 ++
 core/Archive/Array/IndexedBySite.php          |   98 +
 core/Archive/Single.php                       |  463 +++++
 core/ArchiveProcessing.php                    |  570 ++++++
 core/ArchiveProcessing/Day.php                |  348 ++++
 core/ArchiveProcessing/Period.php             |  270 +++
 core/ArchiveProcessing/Record.php             |   55 +
 core/ArchiveProcessing/Record/Blob.php        |   33 +
 core/ArchiveProcessing/Record/BlobArray.php   |   63 +
 core/ArchiveProcessing/Record/Manager.php     |  109 ++
 core/ArchiveProcessing/Record/Numeric.php     |   30 +
 core/Auth.php                                 |   52 +
 core/Common.php                               |  643 +++++++
 core/Config.php                               |  223 +++
 core/Controller.php                           |  270 +++
 core/Cookie.php                               |  282 +++
 core/DataFiles/Browsers.php                   |   59 +
 core/DataFiles/Countries.php                  |  240 +++
 core/DataFiles/OS.php                         |   86 +
 core/DataFiles/SearchEngines.php              | 1080 +++++++++++
 core/DataTable.php                            | 1014 ++++++++++
 core/DataTable/Array.php                      |  152 ++
 core/DataTable/Filter.php                     |   58 +
 core/DataTable/Filter/AddConstantMetadata.php |   43 +
 core/DataTable/Filter/AddSummaryRow.php       |   67 +
 .../Filter/ColumnCallbackAddMetadata.php      |   49 +
 .../Filter/ColumnCallbackDeleteRow.php        |   44 +
 .../Filter/ColumnCallbackReplace.php          |   42 +
 .../DataTable/Filter/ExcludeLowPopulation.php |   50 +
 core/DataTable/Filter/Limit.php               |   58 +
 .../Filter/MetadataCallbackAddMetadata.php    |   48 +
 core/DataTable/Filter/Null.php                |   35 +
 core/DataTable/Filter/Pattern.php             |   44 +
 core/DataTable/Filter/PatternRecursive.php    |   77 +
 core/DataTable/Filter/ReplaceColumnNames.php  |   93 +
 .../Filter/ReplaceSummaryRowLabel.php         |   42 +
 core/DataTable/Filter/Sort.php                |  121 ++
 core/DataTable/Manager.php                    |  101 +
 core/DataTable/Renderer.php                   |  104 ++
 core/DataTable/Renderer/Console.php           |  125 ++
 core/DataTable/Renderer/Csv.php               |  237 +++
 core/DataTable/Renderer/Html.php              |  186 ++
 core/DataTable/Renderer/Json.php              |   52 +
 core/DataTable/Renderer/Php.php               |  203 ++
 core/DataTable/Renderer/Rss.php               |  169 ++
 core/DataTable/Renderer/Xml.php               |  300 +++
 core/DataTable/Row.php                        |  404 ++++
 core/DataTable/Row/DataTableSummary.php       |   33 +
 core/DataTable/Simple.php                     |   61 +
 core/Date.php                                 |  341 ++++
 core/ErrorHandler.php                         |   68 +
 core/ExceptionHandler.php                     |   43 +
 core/Form.php                                 |  101 +
 core/FrontController.php                      |  316 ++++
 core/Log.php                                  |  162 ++
 core/Log/APICall.php                          |  118 ++
 core/Log/Error.php                            |  118 ++
 core/Log/Exception.php                        |  109 ++
 core/Log/Message.php                          |   76 +
 core/LogStats.php                             |  303 +++
 core/LogStats/Action.php                      |  242 +++
 core/LogStats/Config.php                      |   80 +
 core/LogStats/Db.php                          |  261 +++
 core/LogStats/Generator.php                   |  666 +++++++
 core/LogStats/Generator/LogStats.php          |   58 +
 core/LogStats/Generator/Visit.php             |   51 +
 core/LogStats/Visit.php                       |  837 +++++++++
 core/LogStats/javascriptTag.tpl               |   18 +
 core/Mail.php                                 |   31 +
 core/Period.php                               |  239 +++
 core/Period/Day.php                           |   40 +
 core/Period/Month.php                         |   48 +
 core/Period/Range.php                         |  165 ++
 core/Period/Week.php                          |   41 +
 core/Period/Year.php                          |   49 +
 core/Piwik.php                                | 1045 +++++++++++
 core/Plugin.php                               |  102 +
 core/PluginsFunctions/AdminMenu.php           |   33 +
 core/PluginsFunctions/Menu.php                |  106 ++
 core/PluginsFunctions/Sql.php                 |   34 +
 core/PluginsFunctions/Widget.php              |   18 +
 core/PluginsManager.php                       |  502 +++++
 core/Site.php                                 |   67 +
 core/SmartyPlugins/function.assignTopBar.php  |   17 +
 core/SmartyPlugins/function.hiddenurl.php     |   46 +
 .../function.loadJavascriptTranslations.php   |   61 +
 core/SmartyPlugins/function.postEvent.php     |   42 +
 core/SmartyPlugins/function.url.php           |   31 +
 core/SmartyPlugins/modifier.sumtime.php       |   54 +
 core/SmartyPlugins/modifier.translate.php     |   34 +
 .../modifier.urlRewriteBasicView.php          |   40 +
 .../modifier.urlRewriteWithParameters.php     |   23 +
 core/TablePartitioning.php                    |  132 ++
 core/Timer.php                                |   50 +
 core/Translate.php                            |  155 ++
 core/Url.php                                  |  159 ++
 core/View.php                                 |  143 ++
 core/ViewDataTable.php                        |  799 ++++++++
 core/ViewDataTable/Cloud.php                  |  108 ++
 core/ViewDataTable/GenerateGraphData.php      |  140 ++
 .../GenerateGraphData/ChartEvolution.php      |   21 +
 .../GenerateGraphData/ChartPie.php            |   16 +
 .../GenerateGraphData/ChartVerticalBar.php    |   16 +
 core/ViewDataTable/GenerateGraphHTML.php      |  141 ++
 .../GenerateGraphHTML/ChartEvolution.php      |   31 +
 .../GenerateGraphHTML/ChartPie.php            |   15 +
 .../GenerateGraphHTML/ChartVerticalBar.php    |   16 +
 core/ViewDataTable/Html.php                   |  274 +++
 core/ViewDataTable/Sparkline.php              |   67 +
 core/Visualization/Chart.php                  |   88 +
 core/Visualization/ChartEvolution.php         |   58 +
 core/Visualization/ChartPie.php               |   41 +
 core/Visualization/ChartVerticalBar.php       |   38 +
 core/Visualization/Cloud.php                  |  169 ++
 core/Visualization/OpenFlashChart.php         | 1647 +++++++++++++++++
 core/Visualization/Sparkline.php              |   89 +
 core/iView.php                                |   27 +
 core/testMinimumPhpVersion.php                |   98 +
 124 files changed, 22321 insertions(+)
 create mode 100644 core/API/APIable.php
 create mode 100644 core/API/Proxy.php
 create mode 100644 core/API/Request.php
 create mode 100644 core/Access.php
 create mode 100644 core/Archive.php
 create mode 100644 core/Archive/Array.php
 create mode 100644 core/Archive/Array/IndexedByDate.php
 create mode 100644 core/Archive/Array/IndexedBySite.php
 create mode 100644 core/Archive/Single.php
 create mode 100644 core/ArchiveProcessing.php
 create mode 100644 core/ArchiveProcessing/Day.php
 create mode 100644 core/ArchiveProcessing/Period.php
 create mode 100644 core/ArchiveProcessing/Record.php
 create mode 100644 core/ArchiveProcessing/Record/Blob.php
 create mode 100644 core/ArchiveProcessing/Record/BlobArray.php
 create mode 100644 core/ArchiveProcessing/Record/Manager.php
 create mode 100644 core/ArchiveProcessing/Record/Numeric.php
 create mode 100644 core/Auth.php
 create mode 100644 core/Common.php
 create mode 100644 core/Config.php
 create mode 100644 core/Controller.php
 create mode 100644 core/Cookie.php
 create mode 100644 core/DataFiles/Browsers.php
 create mode 100644 core/DataFiles/Countries.php
 create mode 100644 core/DataFiles/OS.php
 create mode 100644 core/DataFiles/SearchEngines.php
 create mode 100644 core/DataTable.php
 create mode 100644 core/DataTable/Array.php
 create mode 100644 core/DataTable/Filter.php
 create mode 100644 core/DataTable/Filter/AddConstantMetadata.php
 create mode 100644 core/DataTable/Filter/AddSummaryRow.php
 create mode 100644 core/DataTable/Filter/ColumnCallbackAddMetadata.php
 create mode 100644 core/DataTable/Filter/ColumnCallbackDeleteRow.php
 create mode 100644 core/DataTable/Filter/ColumnCallbackReplace.php
 create mode 100644 core/DataTable/Filter/ExcludeLowPopulation.php
 create mode 100644 core/DataTable/Filter/Limit.php
 create mode 100644 core/DataTable/Filter/MetadataCallbackAddMetadata.php
 create mode 100644 core/DataTable/Filter/Null.php
 create mode 100644 core/DataTable/Filter/Pattern.php
 create mode 100644 core/DataTable/Filter/PatternRecursive.php
 create mode 100644 core/DataTable/Filter/ReplaceColumnNames.php
 create mode 100644 core/DataTable/Filter/ReplaceSummaryRowLabel.php
 create mode 100644 core/DataTable/Filter/Sort.php
 create mode 100644 core/DataTable/Manager.php
 create mode 100644 core/DataTable/Renderer.php
 create mode 100644 core/DataTable/Renderer/Console.php
 create mode 100644 core/DataTable/Renderer/Csv.php
 create mode 100644 core/DataTable/Renderer/Html.php
 create mode 100644 core/DataTable/Renderer/Json.php
 create mode 100644 core/DataTable/Renderer/Php.php
 create mode 100644 core/DataTable/Renderer/Rss.php
 create mode 100644 core/DataTable/Renderer/Xml.php
 create mode 100644 core/DataTable/Row.php
 create mode 100644 core/DataTable/Row/DataTableSummary.php
 create mode 100644 core/DataTable/Simple.php
 create mode 100644 core/Date.php
 create mode 100644 core/ErrorHandler.php
 create mode 100644 core/ExceptionHandler.php
 create mode 100644 core/Form.php
 create mode 100644 core/FrontController.php
 create mode 100644 core/Log.php
 create mode 100644 core/Log/APICall.php
 create mode 100644 core/Log/Error.php
 create mode 100644 core/Log/Exception.php
 create mode 100644 core/Log/Message.php
 create mode 100644 core/LogStats.php
 create mode 100644 core/LogStats/Action.php
 create mode 100644 core/LogStats/Config.php
 create mode 100644 core/LogStats/Db.php
 create mode 100644 core/LogStats/Generator.php
 create mode 100644 core/LogStats/Generator/LogStats.php
 create mode 100644 core/LogStats/Generator/Visit.php
 create mode 100644 core/LogStats/Visit.php
 create mode 100644 core/LogStats/javascriptTag.tpl
 create mode 100644 core/Mail.php
 create mode 100644 core/Period.php
 create mode 100644 core/Period/Day.php
 create mode 100644 core/Period/Month.php
 create mode 100644 core/Period/Range.php
 create mode 100644 core/Period/Week.php
 create mode 100644 core/Period/Year.php
 create mode 100644 core/Piwik.php
 create mode 100644 core/Plugin.php
 create mode 100644 core/PluginsFunctions/AdminMenu.php
 create mode 100644 core/PluginsFunctions/Menu.php
 create mode 100644 core/PluginsFunctions/Sql.php
 create mode 100644 core/PluginsFunctions/Widget.php
 create mode 100644 core/PluginsManager.php
 create mode 100644 core/Site.php
 create mode 100644 core/SmartyPlugins/function.assignTopBar.php
 create mode 100644 core/SmartyPlugins/function.hiddenurl.php
 create mode 100644 core/SmartyPlugins/function.loadJavascriptTranslations.php
 create mode 100644 core/SmartyPlugins/function.postEvent.php
 create mode 100644 core/SmartyPlugins/function.url.php
 create mode 100644 core/SmartyPlugins/modifier.sumtime.php
 create mode 100644 core/SmartyPlugins/modifier.translate.php
 create mode 100644 core/SmartyPlugins/modifier.urlRewriteBasicView.php
 create mode 100644 core/SmartyPlugins/modifier.urlRewriteWithParameters.php
 create mode 100644 core/TablePartitioning.php
 create mode 100644 core/Timer.php
 create mode 100644 core/Translate.php
 create mode 100644 core/Url.php
 create mode 100644 core/View.php
 create mode 100644 core/ViewDataTable.php
 create mode 100644 core/ViewDataTable/Cloud.php
 create mode 100644 core/ViewDataTable/GenerateGraphData.php
 create mode 100644 core/ViewDataTable/GenerateGraphData/ChartEvolution.php
 create mode 100644 core/ViewDataTable/GenerateGraphData/ChartPie.php
 create mode 100644 core/ViewDataTable/GenerateGraphData/ChartVerticalBar.php
 create mode 100644 core/ViewDataTable/GenerateGraphHTML.php
 create mode 100644 core/ViewDataTable/GenerateGraphHTML/ChartEvolution.php
 create mode 100644 core/ViewDataTable/GenerateGraphHTML/ChartPie.php
 create mode 100644 core/ViewDataTable/GenerateGraphHTML/ChartVerticalBar.php
 create mode 100644 core/ViewDataTable/Html.php
 create mode 100644 core/ViewDataTable/Sparkline.php
 create mode 100644 core/Visualization/Chart.php
 create mode 100644 core/Visualization/ChartEvolution.php
 create mode 100644 core/Visualization/ChartPie.php
 create mode 100644 core/Visualization/ChartVerticalBar.php
 create mode 100644 core/Visualization/Cloud.php
 create mode 100644 core/Visualization/OpenFlashChart.php
 create mode 100644 core/Visualization/Sparkline.php
 create mode 100644 core/iView.php
 create mode 100644 core/testMinimumPhpVersion.php

diff --git a/core/API/APIable.php b/core/API/APIable.php
new file mode 100644
index 0000000000..16c35f6746
--- /dev/null
+++ b/core/API/APIable.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: APIable.php 482 2008-05-18 17:22:35Z matt $
+ * 
+ * @package Piwik_API
+ */
+
+
+require_once "Archive.php";
+/**
+ * This class is the parent class of all the plugins that can be called using the API Proxy. 
+ * For example a plugin "Provider" can publish its API by creating a file plugins/Provider/API.php
+ * that is extending this Piwik_Apiable class.
+ * All the Piwik_Apiable classes are read and loaded by the Piwik_API_Proxy class. 
+ * The public methods of this class are published in the API and are then callable using the API module.
+ * The parameters of the function are read directly from the GET request (they must have the same name).
+ * 
+ * For example
+ *  public function helloWorld($text) { return "hello " . $text; } 
+ * call be called using
+ *  ?module=API&method=PluginName.helloWorld&text=world! 
+ * 
+ * See the documentation on http://dev.piwik.org > API
+ * 
+ * @package Piwik_API
+ * @see Piwik_API_Proxy
+ */
+
+abstract class Piwik_Apiable 
+{
+	/**
+	 * This array contains the name of the methods of the class we don't want to publish in the API.
+	 * By default only public methods are published. Names of public methods in this array won't be published.
+	 *
+	 * @var array of strings
+	 */
+	static public $methodsNotToPublish = array();
+	
+	/**
+	 * @see self::$methodsNotToPublish
+	 * @param string Method name not to be published
+	 */
+	protected function doNotPublishMethod( $methodName )
+	{
+		if(!method_exists($this, $methodName))
+		{
+			throw new Exception("The method $methodName doesn't exist so it can't be added to the list of the methods not to be published in the API.");
+		}
+		$this->methodsNotToPublish[] = $methodName;
+	}
+}
diff --git a/core/API/Proxy.php b/core/API/Proxy.php
new file mode 100644
index 0000000000..6522780c51
--- /dev/null
+++ b/core/API/Proxy.php
@@ -0,0 +1,485 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Proxy.php 581 2008-07-27 23:07:52Z matt $
+ * 
+ * @package Piwik_API
+ */
+
+
+/**
+ * The API Proxy receives all the API calls requests and forwards them to the given module.
+ *  
+ * It registers all the APIable plugins (@see Piwik_Apiable)
+ * The class checks that a call to the API has the correct number of parameters.
+ * The proxy is a singleton that has the knowledge of every method available, their parameters and default values.
+ * 
+ * It can also log the performances of the API calls (time spent, parameter values, etc.)
+ * 
+ * @package Piwik_API
+ */
+class Piwik_API_Proxy
+{
+	static $classCalled = null;
+	
+	// array of already registered plugins names
+	protected $alreadyRegistered = array();
+	
+	private $api = array();
+	
+	// when a parameter doesn't have a default value we use this constant
+	const NO_DEFAULT_VALUE = null;
+
+	static private $instance = null;
+	protected function __construct()
+	{}
+	
+	/**
+	 * Singleton, returns instance
+	 *
+	 * @return Piwik_API_Proxy
+	 */
+	static public function getInstance()
+	{
+		if (self::$instance == null)
+		{            
+			$c = __CLASS__;
+			self::$instance = new $c();
+		}
+		return self::$instance;
+	}
+	
+	/**
+	 * Registers the API information of a given module.
+	 * 
+	 * The module to be registered must be
+	 * - extending the Piwik_Apiable class
+	 * - a singleton (providing a getInstance() method)
+	 * - the API file must be located in plugins/ModuleName/API.php
+	 *   for example plugins/Referers/API.php
+	 * 
+	 * The method will introspect the methods, their parameters, etc. 
+	 * 
+	 * @param string ModuleName eg. "UserSettings"
+	 */
+	public function registerClass( $fileName )
+	{
+		if(isset($this->alreadyRegistered[$fileName]))
+		{
+			return;
+		}
+		
+		$potentialPaths = array( "plugins/". $fileName ."/API.php", );
+		
+		$found = false;
+		foreach($potentialPaths as $path)
+		{
+			if(Zend_Loader::isReadable($path))
+			{
+				require_once $path;
+				$found = true;
+				break;
+			}
+		}
+		
+		if(!$found)
+		{
+			throw new Exception("API module $fileName not found.");
+		}
+
+		$class= $this->getClassNameFromModule($fileName);
+			
+		$rClass = new ReflectionClass($class);
+		
+		// check that it is a subclass of Piwik_APIable
+		if(!$rClass->isSubclassOf(new ReflectionClass("Piwik_Apiable")))
+		{
+			throw new Exception("To publish its public methods in the API, the class '$class' must be a subclass of 'Piwik_Apiable'.");
+		}
+		
+		// check that is is singleton
+		$this->checkClassIsSingleton($class);
+		
+		$rMethods = $rClass->getMethods();
+		foreach($rMethods as $method)
+		{
+			// use this trick to read the static attribute of the class
+			// $class::$methodsNotToPublish doesn't work
+			$variablesClass = get_class_vars($class);
+			$variablesClass['methodsNotToPublish'][] = 'getInstance';
+
+			if($method->isPublic() 
+				&& !$method->isConstructor()
+				&& !in_array($method->getName(), $variablesClass['methodsNotToPublish'] )
+			)
+			{
+				$name = $method->getName();
+				
+				$parameters = $method->getParameters();
+				
+				$aParameters = array();
+				foreach($parameters as $parameter)
+				{
+					$nameVariable = $parameter->getName();
+					
+					$defaultValue = Piwik_API_Proxy::NO_DEFAULT_VALUE;
+					if($parameter->isDefaultValueAvailable())
+					{
+						$defaultValue = $parameter->getDefaultValue();
+					}
+					
+					$aParameters[$nameVariable] = $defaultValue;
+				}
+				$this->api[$class][$name]['parameters'] = $aParameters;
+				$this->api[$class][$name]['numberOfRequiredParameters'] = $method->getNumberOfRequiredParameters();
+			}
+		}
+		
+		$this->alreadyRegistered[$fileName] = true;
+	}
+	
+	/**
+	 * Returns the  'moduleName' part of 'Piwik_moduleName_API' classname 
+	 * 
+	 * @param string moduleName
+	 * @return string className 
+	 */ 
+	protected function getModuleNameFromClassName( $className )
+	{
+		$start = strpos($className, '_') + 1;
+		return substr($className, $start , strrpos($className, '_') - $start);
+	}
+	
+	/**
+	 * Returns a string containing links to examples on how to call a given method on a given API
+	 * It will export links to XML, CSV, HTML, JSON, PHP, etc.
+	 * It will not export links for methods such as deleteSite or deleteUser 
+	 *
+	 * @param string the class 
+	 * @param methodName the method
+	 * @return string|false when not possible
+	 */
+	public function getExampleUrl($class, $methodName, $parametersToSet = array())
+	{
+		$knowExampleDefaultParametersValues = array(
+			'access' => 'view',
+			'userLogin' => 'test',
+			'password' => 'passwordExample',
+			'passwordMd5ied' => 'passwordExample',
+			'email' => 'test@example.org',
+		
+			'siteName' => 'new example website',
+			'urls' => 'http://example.org', // used in addSite, updateSite
+		);
+		
+		foreach($parametersToSet as $name => $value)
+		{
+			$knowExampleDefaultParametersValues[$name] = $value;
+		}
+		
+		// no links for these method names
+		$doNotPrintExampleForTheseMethods = array(
+			'deleteSite',
+			'deleteUser',
+		);
+		
+		if(in_array($methodName,$doNotPrintExampleForTheseMethods))
+		{
+			return false;
+		}
+		
+		
+		// we try to give an URL example to call the API
+		$aParameters = $this->getParametersList($class, $methodName);
+		$moduleName = $this->getModuleNameFromClassName($class);
+		$urlExample = '?module=API&method='.$moduleName.'.'.$methodName.'&';
+		foreach($aParameters as $nameVariable=> $defaultValue)
+		{
+			// if there isn't a default value for a given parameter, 
+			// we need a 'know default value' or we can't generate the link
+			if($defaultValue === Piwik_API_Proxy::NO_DEFAULT_VALUE)
+			{
+				if(isset($knowExampleDefaultParametersValues[$nameVariable]))
+				{
+					$exampleValue = $knowExampleDefaultParametersValues[$nameVariable];
+					$urlExample .= $nameVariable . '=' . $exampleValue . '&';
+				}
+				else
+				{
+					return false;
+				}
+			}
+			
+		}
+		
+		return substr($urlExample,0,-1);
+	}
+	
+	/**
+	 * Returns a HTML page containing help for all the successfully loaded APIs.
+	 * 
+	 * For each module it will return a mini help with the method names, parameters to give, 
+	 * links to get the result in Xml/Csv/etc
+	 *
+	 * @return string
+	 */
+	public function getAllInterfaceString( $outputExampleUrls = true, $prefixUrls = '' )
+	{
+		$str = '';
+		$token_auth = "&token_auth=" . Piwik::getCurrentUserTokenAuth();
+		$parametersToSet = array(
+								'idSite' 	=> Piwik_Common::getRequestVar('idSite', 1, 'int'),
+								'period' 	=> Piwik_Common::getRequestVar('period', 'day', 'string'),
+								'date'		=> Piwik_Common::getRequestVar('date', 'today', 'string')
+							);
+		
+		foreach($this->api as $class => $info)
+		{
+			$moduleName = $this->getModuleNameFromClassName($class);
+			$str .= "\n<h2 id='$moduleName'>Module ".$moduleName."</h2>";
+			
+			foreach($info as $methodName => $infoMethod)
+			{
+				$params = $this->getStrListParameters($class, $methodName);
+				$str .= "\n" . "- <b>$moduleName.$methodName " . $params . "</b>";
+				$str .= '<small>';
+				
+				if($outputExampleUrls)
+				{
+					// we prefix all URLs with $prefixUrls
+					// used when we include this output in the Piwik official documentation for example
+					$str .= "<span class=\"example\">";
+					$exampleUrl = $this->getExampleUrl($class, $methodName, $parametersToSet);
+					if($exampleUrl !== false)
+					{
+						$lastNUrls = '';
+						if( ereg('(&period)|(&date)',$exampleUrl))
+						{
+							$exampleUrlRss1 = $prefixUrls . $this->getExampleUrl($class, $methodName, $parametersToSet + array('date' => 'last10')) ;
+							$exampleUrlRss2 = $prefixUrls . $this->getExampleUrl($class, $methodName, $parametersToSet + array('date' => 'last5','period' => 'week',));
+							$lastNUrls = ",	RSS of the last <a target=_blank href='$exampleUrlRss1&format=rss$token_auth'>10 days</a>, <a target=_blank href='$exampleUrlRss2&format=Rss'>5 weeks</a>,
+									XML of the <a target=_blank href='$exampleUrlRss1&format=xml$token_auth'>last 10 days</a>";
+						}
+						$exampleUrl = $prefixUrls . $exampleUrl ;
+						$str .= " [ Example in  
+									<a target=_blank href='$exampleUrl&format=xml$token_auth'>XML</a>, 
+									<a target=_blank href='$exampleUrl&format=PHP&prettyDisplay=true$token_auth'>PHP</a>, 
+									<a target=_blank href='$exampleUrl&format=JSON$token_auth'>Json</a>, 
+									<a target=_blank href='$exampleUrl&format=Csv$token_auth'>Csv</a>, 
+									<a target=_blank href='$exampleUrl&format=Html$token_auth'>Basic html</a> 
+									$lastNUrls
+									]";
+					}
+					else
+					{
+						$str .= " [ No example available ]";
+					}
+					$str .= "</span>";
+				}
+				$str .= '</small>';
+				$str .= "\n<br>";
+			}
+		}
+		return $str;
+	}
+	
+	/**
+	 * Returns the methods $class.$name parameters (and default value if provided) as a string.
+	 * 
+	 * @param string The class name
+	 * @param string The method name
+	 * @return string For example "(idSite, period, date = 'today')"
+	 */
+	private function getStrListParameters($class, $name)
+	{
+		$aParameters = $this->getParametersList($class, $name);
+		$asParameters = array();
+		foreach($aParameters as $nameVariable=> $defaultValue)
+		{
+			$str = $nameVariable;
+			if($defaultValue !== Piwik_API_Proxy::NO_DEFAULT_VALUE)
+			{
+				$str .= " = '$defaultValue'";
+			}
+			$asParameters[] = $str;
+		}
+		$sParameters = implode(", ", $asParameters);
+		return "($sParameters)";
+	}
+	
+	/**
+	 * Returns the parameters names and default values for the method $name 
+	 * of the class $class
+	 * 
+	 * @param string The class name
+	 * @param string The method name
+	 * @return array Format array(
+	 * 					'testParameter'		=> null, // no default value
+	 * 					'life'				=> 42, // default value = 42
+	 * 					'date'				=> 'yesterday',
+	 * 				);
+	 */
+	public function getParametersList($class, $name)
+	{
+		return $this->api[$class][$name]['parameters'];
+	}
+	
+	/**
+	 * Returns the number of required parameters (parameters without default values).
+	 * 
+	 * @param string The class name
+	 * @param string The method name
+	 * @return int The number of required parameters
+	 */
+	private function getNumberOfRequiredParameters($class, $name)
+	{
+		return $this->api[$class][$name]['numberOfRequiredParameters'];
+	}
+	
+	/**
+	 * Returns true if the method is found in the API of the given class name. 
+	 * 
+	 * @param string The class name
+	 * @param string The method name
+	 * @return bool 
+	 */
+	private function isMethodAvailable( $className, $methodName)
+	{
+		return isset($this->api[$className][$methodName]);
+	}
+	
+	
+	/**
+	 * Checks that the count of the given parameters do match with the count of the required ones
+	 * 
+	 * @param string The class name
+	 * @param string The method name
+	 * @param array 
+	 * @throws exception If less parameters than required were given
+	 */
+	private function checkNumberOfParametersMatch($className, $methodName, $parameters)
+	{
+		$nbParamsGiven = count($parameters);
+		$nbParamsRequired = $this->getNumberOfRequiredParameters($className, $methodName);
+		
+		if($nbParamsGiven < $nbParamsRequired)
+		{
+			throw new Exception("The number of parameters provided ($nbParamsGiven) is less than the number of required parameters ($nbParamsRequired) for this method.
+							Please check the method API.");
+		}
+	}
+	
+	/**
+	 * Checks that the class is a Singleton (presence of the getInstance() method)
+	 * 
+	 * @param string The class name
+	 * @throws exception If the class is not a Singleton
+	 */
+	private function checkClassIsSingleton($className)
+	{
+		if(!method_exists($className, "getInstance"))
+		{
+			throw new Exception("Objects that provide an API must be Singleton and have a 'static public function getInstance()' method.");
+		}
+	}
+	
+	/**
+	 * Checks that the method exists in the class 
+	 * 
+	 * @param string The class name
+	 * @param string The method name
+	 * @throws exception If the method is not found
+	 */	
+	public function checkMethodExists($className, $methodName)
+	{
+		if(!$this->isMethodAvailable($className, $methodName))
+		{
+			throw new Exception("The method '$methodName' does not exist or is not available in the module '".$className."'.");
+		}
+	}
+	
+	/**
+	 * Returns the API class name given the module name.
+	 * 
+	 * For exemple for $module = 'Referers' it returns 'Piwik_Referers_API' 
+	 * Piwik_Referers_API is the class that extends Piwik_Apiable 
+	 * and that contains the methods to be published in the API.
+	 * 
+	 * @param string module name
+	 * @return string class name
+	 */
+	protected  function getClassNameFromModule($module)
+	{
+		$class = Piwik::prefixClass($module ."_API");
+		return $class;
+	}
+	
+	/**
+	 * Magic method used to set a flag telling the module named currently being called
+	 *
+	 */
+	public function __get($name)
+	{
+		self::$classCalled = $name;
+		return $this;
+	}	
+
+	/**
+	 * Method always called when making an API request.
+	 * It checks several things before actually calling the real method on the given module.
+	 * 
+	 * It also logs the API calls, with the parameters values, the returned value, the performance, etc.
+	 * You can enable logging in config/global.ini.php (log_api_call)
+	 * 
+	 * @param string The method name
+	 * @param array The parameters
+	 * 
+	 * @throws Piwik_Access_NoAccessException 
+	 */
+	public function __call($methodName, $parameterValues )
+	{
+		$returnedValue = null;
+		
+		try {
+			$this->registerClass(self::$classCalled);
+						
+			$className = $this->getClassNameFromModule(self::$classCalled);
+
+			// instanciate the object
+			$object = call_user_func(array($className, "getInstance"));
+
+			// check method exists
+			$this->checkMethodExists($className, $methodName);
+			
+			// first check number of parameters do match
+			$this->checkNumberOfParametersMatch($className, $methodName, $parameterValues);
+			
+			// start the timer
+			$timer = new Piwik_Timer;
+			
+			// call the method
+			$returnedValue = call_user_func_array(array($object, $methodName), $parameterValues);
+			
+			// log the API Call
+			$parameterNamesDefaultValues  = $this->getParametersList($className, $methodName);
+			Zend_Registry::get('logger_api_call')->log(
+								self::$classCalled,
+								$methodName,
+								$parameterNamesDefaultValues,
+								$parameterValues,
+								$timer->getTimeMs(),
+								$returnedValue
+							);
+		}
+		catch( Piwik_Access_NoAccessException $e) {
+			throw $e;
+		}
+
+		self::$classCalled = null;
+		
+		return $returnedValue;
+	}
+}
diff --git a/core/API/Request.php b/core/API/Request.php
new file mode 100644
index 0000000000..e32173dc60
--- /dev/null
+++ b/core/API/Request.php
@@ -0,0 +1,588 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Request.php 506 2008-06-06 01:18:47Z matt $
+ * 
+ * 
+ * @package Piwik_API
+ */
+
+
+/**
+ * An API request is the object used to make a call to the API and get the result.
+ * The request has the format of a normal GET request, ie. parameter_1=X&parameter_2=Y
+ * 
+ * You can use this object from anywhere in piwik (inside plugins for example).
+ * You can even call it outside of piwik  using the REST API over http
+ * or in a php script on the same server as piwik, by including piwik/index.php
+ * (see examples in the documentation http://dev.piwik.org/trac/wiki/API)
+ * 
+ * Example: 
+ * $request = new Piwik_API_Request('
+ * 				method=UserSettings.getWideScreen
+ * 				&idSite=1
+ *  			&date=yesterday
+ * 				&period=week
+ *				&format=xml
+ *				&filter_limit=5
+ *				&filter_offset=0
+ *	');
+ *	$result = $request->process();
+ *  echo $result;
+ * 
+ * @see http://dev.piwik.org/trac/wiki/API
+ * @package Piwik_API
+ */
+class Piwik_API_Request
+{	
+	protected $outputFormatRequested;
+	
+	/**
+	 * Constructs the request to the API, given the request url
+	 * 
+	 * @param string GET request that defines the API call (must at least contain a "method" parameter) 
+	 *  Example: method=UserSettings.getWideScreen&idSite=1&date=yesterday&period=week&format=xml
+	 * 	If a request is not provided, then we use the $_REQUEST superglobal and fetch
+	 * 	the values directly from the HTTP GET query.
+	 */
+	function __construct($request = null)
+	{
+		$requestArray = $_REQUEST;
+		
+		// If an array is specified we use it
+		if(!is_null($request))
+		{
+			$request = trim($request);
+			$request = str_replace(array("\n","\t"),'', $request);
+			parse_str($request, $requestArray);
+				
+			// but we handle the case when an array is specified but we also want
+			// to look for the value in the _REQUEST
+			$requestArray = array_merge( $_REQUEST, $requestArray);
+		}
+		
+		// remove all spaces from parameters values (when calling internally the API for example)
+		foreach($requestArray as &$element)
+		{
+			// sometimes GET parameters can be arrays but we assume module accepting arrays are correctly handling spaces
+			if(!is_array($element))
+			{
+				$element = trim($element);
+			}			
+		}
+		
+		$this->requestToUse = $requestArray;
+	}
+	
+	/**
+	 * Returns array( $class, $method) from the given string $class.$method
+	 * 
+	 * @return array
+	 * @throws exception if the name is invalid
+	 */
+	private function extractModuleAndMethod($parameter)
+	{
+		$a = explode('.',$parameter);
+		if(count($a) != 2)
+		{
+			throw new Exception("The method name is invalid. Must be on the form 'module.methodName'");
+		}
+		return $a;
+	}
+
+	
+	/**
+	 * Handles the request to the API.
+	 * It first checks that the method called (parameter 'method') is available in the module (it means that the method exists and is public)
+	 * It then reads the parameters from the request string and throws an exception if there are missing parameters.
+	 * It then calls the API Proxy which will call the requested method.
+	 * 
+	 * @see the method handleReturnedValue() for the data post process logic 
+	 * 
+	 * @return mixed The data resulting from the API call  
+	 */
+	public function process()
+	{
+		try {
+			
+			// read the format requested for the output data
+			$this->outputFormatRequested = Piwik_Common::getRequestVar('format', 'xml', 'string', $this->requestToUse);
+			$this->outputFormatRequested = strtolower($this->outputFormatRequested);
+		
+			// read parameters
+			$moduleMethod = Piwik_Common::getRequestVar('method', null, null, $this->requestToUse);
+			
+			list($module, $method) = $this->extractModuleAndMethod($moduleMethod); 
+			
+			if(!Piwik_PluginsManager::getInstance()->isPluginActivated($module))
+			{
+				throw new Exception_PluginDeactivated($module);
+			}
+			// call the method via the API_Proxy class
+			$api = Piwik_Api_Proxy::getInstance();
+			$api->registerClass($module);
+			
+			// read method to call meta information
+			$className = "Piwik_" . $module . "_API";
+			
+			// check method exists
+			$api->checkMethodExists($className, $method);
+			
+			// get the list of parameters required by the method
+			$parameters = $api->getParametersList($className, $method);
+			
+			// load the parameters from the request URL
+			$finalParameters = $this->getRequestParametersArray( $parameters );
+			
+			// call the method 
+			$returnedValue = call_user_func_array( array( $api->$module, $method), $finalParameters );
+			
+			// post process the data
+			$toReturn = $this->handleReturnedValue( $returnedValue );
+			
+			
+		} catch(Exception $e ) {
+			
+			// if it is not a direct API call, we are requesting the original data structure
+			// and we actually are handling this exception at the top level in the FrontController
+			if($this->outputFormatRequested == 'original')
+			{
+				throw $e;
+			}
+			$message = $e->getMessage();
+						
+			$toReturn =  $this->getExceptionOutput( $message, $this->outputFormatRequested);
+			
+		}
+		
+		return $toReturn;
+	}
+	
+	/**
+	 * Returns the values of the current request
+	 *
+	 * @param array Parameters array of the method called. Contains name and default values of the required parameters
+	 * @return array Values of the given parameters
+	 * @throws exception If there is a missing parameter
+	 */
+	protected function getRequestParametersArray( $parameters )
+	{
+		$finalParameters = array();
+		foreach($parameters as $name => $defaultValue)
+		{
+			try{
+				// there is a default value specified
+				if($defaultValue !== Piwik_API_Proxy::NO_DEFAULT_VALUE)
+				{
+					$requestValue = Piwik_Common::getRequestVar($name, $defaultValue, null, $this->requestToUse);
+				}
+				else
+				{
+					$requestValue = Piwik_Common::getRequestVar($name, null, null, $this->requestToUse);				
+				}
+			} catch(Exception $e) {
+				throw new Exception("The required variable '$name' is not correct or has not been found in the API Request. Add the parameter '&$name=' (with a value) in the URL.");
+			}			
+			$finalParameters[] = $requestValue;
+		}
+		return $finalParameters;
+	}
+	
+	/**
+	 * This method post processes the data resulting from the API call.
+	 * 
+	 * - If the data resulted from the API call is a Piwik_DataTable then 
+	 * 		- we apply the standard filters if the parameters have been found
+	 * 		  in the URL. For example to offset,limit the Table you can add the following parameters to any API
+	 *  	  call that returns a DataTable: filter_limit=10&filter_offset=20
+	 * 		- we apply the filters that have been previously queued on the DataTable
+	 *        @see Piwik_DataTable::queueFilter()
+	 * 		- we apply the renderer that generate the DataTable in a given format (XML, PHP, HTML, JSON, etc.) 
+	 * 		  the format can be changed using the 'format' parameter in the request.
+	 *        Example: format=xml
+	 * 
+	 * - If there is nothing returned (void) we display a standard success message
+	 * 
+	 * - If there is a PHP array returned, we try to convert it to a dataTable 
+	 *   It is then possible to convert this datatable to any requested format (xml/etc)
+	 * 
+	 * - If a bool is returned we convert to a string (true is displayed as 'true' false as 'false')
+	 * 
+	 * - If an integer / float is returned, we simply return it
+	 * 
+	 * @throws Exception If an object/resource is returned, if any of conversion fails, etc. 
+	 * 
+	 * @param mixed The initial returned value, before post process
+	 * @return mixed Usually a string, but can still be a PHP data structure if the format requested is 'original'
+	 */
+	protected function handleReturnedValue( $returnedValue ) 
+	{
+		$toReturn = $returnedValue;
+		
+		// If the returned value is an object DataTable we
+		// apply the set of generic filters if asked in the URL
+		// and we render the DataTable according to the format specified in the URL
+		if($returnedValue instanceof Piwik_DataTable
+			|| $returnedValue instanceof Piwik_DataTable_Array)
+		{
+			if($returnedValue instanceof Piwik_DataTable)
+			{
+				$this->applyDataTableGenericFilters($returnedValue);
+			}
+			elseif($returnedValue instanceof Piwik_DataTable_Array)
+			{
+				$tables = $returnedValue->getArray();
+				foreach($tables as $table)
+				{
+					$this->applyDataTableGenericFilters($table);
+				}
+			}
+			
+			// if the flag disable_queued_filters is defined we skip the filters that were queued
+			// useful in some very rare cases but better to use this than a bad hack on the data returned...
+			if(Piwik_Common::getRequestVar('disable_queued_filters', 'false', 'string', $this->requestToUse) == 'false')
+			{
+				$returnedValue->applyQueuedFilters();
+			}			
+			
+			$toReturn = $this->getRenderedDataTable($returnedValue);
+		}
+		
+		// Case nothing returned (really nothing was 'return'ed), 
+		// => the operation was successful
+		elseif(!isset($toReturn))
+		{
+			$toReturn = $this->getStandardSuccessOutput($this->outputFormatRequested);
+		}
+		
+		// Case an array is returned from the API call, we convert it to the requested format
+		// - if calling from inside the application (format = original)
+		//    => the data stays unchanged (ie. a standard php array or whatever data structure)
+		// - if any other format is requested, we have to convert this data structure (which we assume 
+		//   to be an array) to a DataTable in order to apply the requested DataTable_Renderer (for example XML)
+		elseif(is_array($toReturn))
+		{
+			if($this->outputFormatRequested == 'original')
+			{
+				// we handle the serialization. Because some php array have a very special structure that 
+				// couldn't be converted with the automatic DataTable->loadFromSimpleArray
+				// the user may want to request the original PHP data structure serialized by the API
+				// in case he has to setup serialize=1 in the URL
+				if($this->caseRendererPHPSerialize( $defaultSerialize = 0))
+				{
+					$toReturn = serialize($toReturn);
+				}
+			}
+			else
+			{
+				$dataTable = new Piwik_DataTable();
+				$dataTable->loadFromSimpleArray($toReturn);
+				$toReturn = $this->getRenderedDataTable($dataTable);
+			}
+		}
+		// bool // integer // float // object is serialized
+		// NB: null value is already handled by the isset() test above
+		else
+		{
+			// original data structure requested, we return without process
+			if( $this->outputFormatRequested == 'original' )
+			{
+				return $toReturn;
+			}
+			
+			if( $toReturn === true )
+			{
+				$toReturn = 'true';
+			}
+			elseif( $toReturn === false )
+			{
+				$toReturn = 'false';
+			}
+			elseif( is_object($toReturn)
+						|| is_resource($toReturn)
+						)
+			{
+				return $this->getExceptionOutput( ' The API cannot handle this data structure. You can get the data internally by directly using the class.', $this->outputFormatRequested);
+			}
+			
+			require_once "DataTable/Simple.php";
+			$dataTable = new Piwik_DataTable_Simple();
+			$dataTable->loadFromArray( array($toReturn) );
+			$toReturn = $this->getRenderedDataTable($dataTable);
+		}
+		return $toReturn;
+	}
+	
+	/**
+	 * Returns a success $message in the requested $format 
+	 *
+	 * @param string $format xml/json/php/csv
+	 * @param string $message
+	 * @return string
+	 */
+	protected function getStandardSuccessOutput($format, $message = 'ok')
+	{
+		switch($format)
+		{
+			case 'xml':
+				@header("Content-Type: text/xml;charset=utf-8");
+				$return = 
+					'<?xml version="1.0" encoding="utf-8" ?>'.
+					'<result>'.
+					'	<success message="'.$message.'" />'.
+					'</result>';
+			break;
+			case 'json':
+				@header( "Content-type: application/json" );
+				$return = '{"result":"success", "message":"'.$message.'"}';
+			break;
+			case 'php':
+				$return = array('result' => 'success', 'message' => $message);
+				if($this->caseRendererPHPSerialize())
+				{
+					$return = serialize($return);
+				}
+			break;
+			
+			case 'csv':
+				header("Content-type: application/vnd.ms-excel");
+				header("Content-Disposition: attachment; filename=piwik-report-export.csv");	
+				$return = "message\n".$message;
+			break;
+			
+			default:
+				$return = 'Success:'.$message;
+			break;
+		}
+		
+		return $return;
+	}
+	
+	/**
+	 * Returns an error $message in the requested $format 
+	 *
+	 * @param string $format xml/json/php/csv
+	 * @param string $message
+	 * @return string
+	 */
+	function getExceptionOutput($message, $format)
+	{
+		switch($format)
+		{
+			case 'xml':
+				@header("Content-Type: text/xml;charset=utf-8");
+				$return = 
+					'<?xml version="1.0" encoding="utf-8" ?>'.
+					'<result>'.
+					'	<error message="'.htmlentities($message).'" />'.
+					'</result>';
+			break;
+			case 'json':
+				@header( "Content-type: application/json" );
+				// we remove the \n from the resulting string as this is not allowed in json
+				$message = str_replace("\n","",$message);
+				$return = '{"result":"error", "message":"'.htmlentities($message).'"}';
+			break;
+			case 'php':
+				$return = array('result' => 'error', 'message' => $message);
+				if($this->caseRendererPHPSerialize())
+				{
+					$return = serialize($return);
+				}
+			break;
+			default:
+				$return = 'Error: '.$message;
+			break;
+		}
+		
+		return $return;
+	}
+
+	/**
+	 * Apply the specified renderer to the DataTable
+	 * 
+	 * @param Piwik_DataTable
+	 * @return string
+	 */
+	protected function getRenderedDataTable($dataTable)
+	{
+		// Renderer
+		$format = Piwik_Common::getRequestVar('format', 'php', 'string', $this->requestToUse);
+		$format = strtolower($format);
+		
+		// if asked for original dataStructure
+		if($format == 'original')
+		{
+			// if the original dataStructure is a simpleDataTable and has only one row, we return the value
+			if($dataTable instanceof Piwik_DataTable_Simple
+				&& $dataTable->getRowsCount() == 1)
+			{
+				return $dataTable->getRowFromId(0)->getColumn('value');
+			}
+			
+			// the original data structure can be asked as serialized. 
+			// but by default it's not serialized
+			if($this->caseRendererPHPSerialize( $defaultSerialize = 0))
+			{
+				$dataTable = serialize($dataTable);
+			}
+			return $dataTable;
+		}
+		
+		$renderer = Piwik_DataTable_Renderer::factory($format);
+		$renderer->setTable($dataTable);
+		
+		if($format == 'php')
+		{
+			$renderer->setSerialize( $this->caseRendererPHPSerialize());
+		}
+		
+		$toReturn = $renderer->render();
+		return $toReturn;
+	}
+	
+	/**
+	 * Returns true if the user requested to serialize the output data (&serialize=1 in the request)
+	 *
+	 * @param $defaultSerializeValue Default value in case the user hasn't specified a value
+	 * @return bool
+	 */	
+	protected function caseRendererPHPSerialize($defaultSerializeValue = 1)
+	{
+		$serialize = Piwik_Common::getRequestVar('serialize', $defaultSerializeValue, 'int', $this->requestToUse);
+		if($serialize)
+		{
+			return true;
+		}
+		else
+		{
+			return false;		
+		}
+	}
+	
+	/**
+	 * Returns an array containing the information of the generic Piwik_DataTable_Filter 
+	 * to be applied automatically to the data resulting from the API calls.
+	 *
+	 * @return array See the code for spec
+	 */
+	public static function getGenericFiltersInformation()
+	{
+		$genericFilters = array(
+			
+			'Pattern' => array(
+								'filter_column' 			=> array('string'), 
+								'filter_pattern' 			=> array('string'),
+						),
+			'PatternRecursive' => array(
+								'filter_column_recursive' 	=> array('string'), 
+								'filter_pattern_recursive' 	=> array('string'),
+						),
+			'ExcludeLowPopulation'	=> array(
+								'filter_excludelowpop' 		=> array('string'), 
+								'filter_excludelowpop_value'=> array('float'),
+						),
+			'Sort' => array(
+								'filter_sort_column' 		=> array('string', Piwik_Archive::INDEX_NB_VISITS),
+								'filter_sort_order' 		=> array('string', Zend_Registry::get('config')->General->dataTable_default_sort_order),
+						),
+			'Limit' => array(
+								'filter_offset' 			=> array('integer', '0'),
+								'filter_limit' 				=> array('integer', Zend_Registry::get('config')->General->dataTable_default_limit),
+						),
+		);
+		
+		return $genericFilters;
+	}
+	
+	
+	/**
+	 * Apply generic filters to the DataTable object resulting from the API Call.
+	 * Disable this feature by setting the parameter disable_generic_filters to 1 in the API call request.
+	 * 
+	 * @param Piwik_DataTable
+	 * @return void
+	 */
+	protected function applyDataTableGenericFilters($dataTable)
+	{
+		if($dataTable instanceof Piwik_DataTable_Array )
+		{
+			$tables = $dataTable->getArray();
+			foreach($tables as $table)
+			{
+				$this->applyDataTableGenericFilters($table);
+			}
+			return;
+		}
+		
+		// Generic filters
+		// PatternFileName => Parameter names to match to constructor parameters
+		/*
+		 * Order to apply the filters:
+		 * 1 - Filter that remove filtered rows
+		 * 2 - Filter that sort the remaining rows
+		 * 3 - Filter that keep only a subset of the results
+		 */
+		$genericFilters = Piwik_API_Request::getGenericFiltersInformation();
+		
+		// if the flag disable_generic_filters is defined we skip the generic filters
+		if(Piwik_Common::getRequestVar('disable_generic_filters', 'false', 'string', $this->requestToUse) != 'false')
+		{
+			return;
+		}
+		
+		foreach($genericFilters as $filterName => $parameters)
+		{
+			$filterParameters = array();
+			$exceptionRaised = false;
+			
+			foreach($parameters as $name => $info)
+			{
+				// parameter type to cast to
+				$type = $info[0];
+				
+				// default value if specified, when the parameter doesn't have a value
+				$defaultValue = null;
+				if(isset($info[1]))
+				{
+					$defaultValue = $info[1];
+				}
+				
+				try {
+					$value = Piwik_Common::getRequestVar($name, $defaultValue, $type, $this->requestToUse);
+					settype($value, $type);
+					$filterParameters[] = $value;
+				}
+				catch(Exception $e)
+				{
+					$exceptionRaised = true;
+					break;
+				}
+			}
+			
+			if(!$exceptionRaised)
+			{				
+				// a generic filter class name must follow this pattern
+				$class = "Piwik_DataTable_Filter_".$filterName;
+				
+				if($filterName == 'Limit')
+				{
+					$dataTable->setRowsCountBeforeLimitFilter();
+				}
+				
+				// build the set of parameters for the filter					
+				$filterParameters = array_merge(array($dataTable), $filterParameters);
+
+				// make a reflection object
+				$reflectionObj = new ReflectionClass($class);
+				
+				// use Reflection to create a new instance, using the $args
+				$filter = $reflectionObj->newInstanceArgs($filterParameters); 
+			}
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/core/Access.php b/core/Access.php
new file mode 100644
index 0000000000..cfe06ad7e6
--- /dev/null
+++ b/core/Access.php
@@ -0,0 +1,326 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Access.php 581 2008-07-27 23:07:52Z matt $
+ *
+ * @package Piwik
+ *
+ */
+
+require_once 'SitesManager/API.php';
+
+/**
+ * Class to handle User Access:
+ * - loads user access from the Piwik_Auth_Result object 
+ * - provides easy to use API to check the permissions for the current (check* methods)
+ * 
+ * In Piwik there are mainly 4 access levels
+ * - no access
+ * - VIEW access
+ * - ADMIN access
+ * - Super admin access
+ *
+ * An access level is on a per website basis.
+ * A given user has a given access level for a given website.
+ * For example:
+ * User Noemie has
+ * 	- VIEW access on the website 1,
+ *  - ADMIN on the website 2 and 4, and
+ *  - NO access on the website 3 and 5
+ *
+ * There is only one Super User. He has ADMIN access to all the websites 
+ * and he only can change the main configuration settings.
+ *
+ * @package Piwik
+ */
+
+class Piwik_Access
+{	
+	/**
+	 * Array of idsites available to the current user, indexed by permission level
+	 * @see getSitesIdWith*()
+	 *
+	 * @var array
+	 */
+	protected $idsitesByAccess = null;
+	
+	/**
+	 * Login of the current user
+	 *
+	 * @var string
+	 */
+	protected $login = null;
+	
+	/**
+	 * token_auth of the current user
+	 *
+	 * @var string
+	 */
+	protected $token_auth = null;
+	
+	/**
+	 * Defines if the current user is the super user
+	 * @see isSuperUser()
+	 * 
+	 * @var bool
+	 */
+	protected $isSuperUser = false;
+
+	/**
+	 * List of available permissions in Piwik
+	 *
+	 * @var array
+	 */
+	static private $availableAccess = array('noaccess', 'view', 'admin', 'superuser');
+
+	/**
+	 * Authentification object (see Piwik_Auth)
+	 *
+	 * @var Piwik_Auth
+	 */
+	private $auth;
+	
+	/**
+	 * Returns the list of the existing Access level.
+	 * Useful when a given API method requests a given acccess Level.
+	 * We first check that the required access level exists.
+	 */
+	static public function getListAccess()
+	{
+		return self::$availableAccess;
+	}
+
+	/**
+	 * @param Piwik_Auth The authentification object
+	 */
+	public function __construct( Piwik_Auth $auth )
+	{
+		$this->auth = $auth;
+	}
+
+	/**
+	 * Loads the access levels for the current user.
+	 *
+	 * Calls the authentication method to try to log the user in the system.
+	 * If the user credentials are not correct we don't load anything.
+	 * If the login/password is correct the user is either the SuperUser or a normal user.
+	 * We load the access levels for this user for all the websites.
+	 * 
+	 */
+	public function loadAccess()
+	{
+		$idsitesByAccess = array( 'view' => array(), 'admin'  => array(), 'superuser'  => array());
+
+		// access = array ( idsite => accessIdSite, idsite2 => accessIdSite2)
+		$result = $this->auth->authenticate();
+
+		if($result->isValid())
+		{
+			$this->login = $result->getIdentity();
+			$this->token_auth = $result->getTokenAuth();
+				
+			// case the superUser is logged in
+			if($result->getCode() == Piwik_Auth_Result::SUCCESS_SUPERUSER_AUTH_CODE)
+			{
+				$this->isSuperUser = true;
+				$idsitesByAccess['superuser'] = Piwik_SitesManager_API::getAllSitesId();
+			}
+			// valid authentification (normal user logged in)
+			else
+			{
+				$db = Zend_Registry::get('db');
+
+				// we join with site in case there are rows in access for an idsite that doesn't exist anymore
+				// (backward compatibility ; before we deleted the site without deleting rows in _access table)
+				$accessRaw = $db->fetchAll("SELECT access, t2.idsite
+								  FROM ".Piwik::prefixTable('access'). " as t1 
+									JOIN ".Piwik::prefixTable('site')." as t2 USING (idsite) ".
+								" WHERE login=?", $this->login);
+
+				foreach($accessRaw as $access)
+				{
+					$idsitesByAccess[$access['access']][] = $access['idsite'];
+				}
+			}
+		}
+
+		$this->idsitesByAccess = $idsitesByAccess;
+	}
+	
+	/**
+	 * We bypass the normal auth method and give the current user Super User rights.
+	 * This should be very carefully used.
+	 * 
+	 * @return void
+	 */
+	public function setSuperUser()
+	{
+		$this->isSuperUser = true;
+		$this->idsitesByAccess['superuser'] = Piwik_SitesManager_API::getAllSitesId();
+	}
+	
+	/**
+	 * Returns true if the current user is logged in as the super user
+	 *
+	 * @return bool
+	 */
+	public function isSuperUser()
+	{
+		return $this->isSuperUser;
+	}
+	
+	/**
+	 * Returns the current user login
+	 * @return string
+	 */
+	public function getLogin()
+	{
+		return $this->login;
+	}
+
+	/**
+	 * Returns the token_auth used to authenticate this user in the API
+	 * @return string  
+	 */
+	public function getTokenAuth()
+	{
+		return $this->token_auth;
+	}
+	
+	/**
+	 * Returns an array of ID sites for which the user has at least a VIEW access.
+	 * Which means VIEW or ADMIN or SUPERUSER.
+	 *
+	 * @return array Example if the user is ADMIN for 4
+	 *              and has VIEW access for 1 and 7, it returns array(1, 4, 7);
+	 */
+	public function getSitesIdWithAtLeastViewAccess()
+	{
+		return array_unique(array_merge(
+		$this->idsitesByAccess['view'],
+		$this->idsitesByAccess['admin'],
+		$this->idsitesByAccess['superuser']));
+	}
+
+
+	/**
+	 * Returns an array of ID sites for which the user has an ADMIN access.
+	 *
+	 * @return array Example if the user is ADMIN for 4 and 8
+	 *              and has VIEW access for 1 and 7, it returns array(4, 8);
+	 */
+	public function getSitesIdWithAdminAccess()
+	{
+		return array_unique(array_merge(
+		$this->idsitesByAccess['admin'],
+		$this->idsitesByAccess['superuser']));
+	}
+
+
+	/**
+	 * Returns an array of ID sites for which the user has a VIEW access only.
+	 *
+	 * @return array Example if the user is ADMIN for 4
+	 *              and has VIEW access for 1 and 7, it returns array(1, 7);
+	 * @see getSitesIdWithAtLeastViewAccess()
+	 */
+	public function getSitesIdWithViewAccess()
+	{
+		return 	$this->idsitesByAccess['view'];
+	}
+
+	/**
+	 * Throws an exception if the user is not the SuperUser
+	 * 
+	 * @throws Exception
+	 */
+	public function checkUserIsSuperUser()
+	{
+		if($this->isSuperUser === false)
+		{
+			throw new Piwik_Access_NoAccessException("You can't access this resource as it requires a 'superuser' access.");
+		}
+	}
+
+	/**
+	 * If the user doesn't have an ADMIN access for at least one website, throws an exception
+	 * 
+	 * @throws Exception
+	 */
+	public function checkUserHasSomeAdminAccess()
+	{
+		$idSitesAccessible = $this->getSitesIdWithAdminAccess();
+		if(count($idSitesAccessible) == 0)
+		{
+			throw new Piwik_Access_NoAccessException("You can't access this resource as it requires an 'admin' access for at least one website.");
+		}
+	}
+
+	/**
+	 * This method checks that the user has ADMIN access for the given list of websites.
+	 * If the user doesn't have ADMIN access for at least one website of the list, we throw an exception.
+	 * 
+	 * @param int|arrayOfIntegers List of ID sites to check
+	 * @throws Exception If for any of the websites the user doesn't have an ADMIN access
+	 */
+	public function checkUserHasAdminAccess( $idSites )
+	{
+		if($idSites === 'all')
+		{
+			$idSites = $this->getSitesIdWithAtLeastViewAccess();
+		}
+		if(!is_array($idSites))
+		{
+			$idSites = Piwik_Site::getIdSitesFromIdSitesString($idSites);
+		}
+		$idSitesAccessible = $this->getSitesIdWithAdminAccess();
+		foreach($idSites as $idsite)
+		{
+			if(!in_array($idsite, $idSitesAccessible))
+			{
+				throw new Piwik_Access_NoAccessException("You can't access this resource as it requires an 'admin' access for the website id = $idsite.");
+			}
+		}
+	}
+
+	/**
+	 * This method checks that the user has VIEW or ADMIN access for the given list of websites.
+	 * If the user doesn't have VIEW or ADMIN access for at least one website of the list, we throw an exception.
+	 * 
+	 * @param int|arrayOfIntegers|string List of ID sites to check (integer, array of integers, string comma separated list of integers)
+	 * @throws Exception If for any of the websites the user doesn't have an VIEW or ADMIN access
+	 */
+	public function checkUserHasViewAccess( $idSites )
+	{
+		if($idSites === 'all')
+		{
+			$idSites = $this->getSitesIdWithAtLeastViewAccess();
+		}
+		
+		if(!is_array($idSites))
+		{
+			$idSites = Piwik_Site::getIdSitesFromIdSitesString($idSites);
+		}
+		$idSitesAccessible = $this->getSitesIdWithAtLeastViewAccess();
+
+		foreach($idSites as $idsite)
+		{
+			if(!in_array($idsite, $idSitesAccessible))
+			{
+				throw new Piwik_Access_NoAccessException("You can't access this resource as it requires a 'view' access for the website id = $idsite.");
+			}
+		}
+	}
+}
+
+/**
+ *
+ * Exception thrown when a user doesn't  have sufficient access.
+ * 
+ * @package Piwik
+ */
+class Piwik_Access_NoAccessException extends Exception
+{}
\ No newline at end of file
diff --git a/core/Archive.php b/core/Archive.php
new file mode 100644
index 0000000000..8218c9aa33
--- /dev/null
+++ b/core/Archive.php
@@ -0,0 +1,224 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Archive.php 585 2008-07-28 00:56:50Z matt $
+ * 
+ * @package Piwik
+ */
+
+ 
+require_once 'Period.php';
+require_once 'Date.php';
+require_once 'ArchiveProcessing.php';
+require_once 'Archive/Single.php';
+
+/**
+ * The archive object is used to query specific data for a day or a period of statistics for a given website.
+ * 
+ * Example:
+ * <pre>
+ * 		$archive = Piwik_Archive::build($idSite = 1, $period = 'week', '2008-03-08' );
+ * 		$dataTable = $archive->getDataTable('Provider_hostnameExt');
+ * 		$dataTable->queueFilter('Piwik_DataTable_Filter_ReplaceColumnNames');
+ * 		return $dataTable;
+ * </pre>
+ * 
+ * Example bis:
+ * <pre>
+ * 		$archive = Piwik_Archive::build($idSite = 3, $period = 'day', $date = 'today' );
+ * 		$nbVisits = $archive->getNumeric('nb_visits');
+ * 		return $nbVisits;		
+ * </pre>
+ * 
+ * If the requested statistics are not yet processed, Archive uses ArchiveProcessing to archive the statistics.
+ * 
+ * @package Piwik
+ * @subpackage Piwik_Archive
+ */
+abstract class Piwik_Archive
+{
+	/**
+	 * When saving DataTables in the DB, we sometimes replace the columns name by these IDs so we save up lots of bytes
+	 * Eg. INDEX_NB_UNIQ_VISITORS is an integer: 4 bytes, but 'nb_uniq_visitors' is 16 bytes at least
+	 * (in php it's actually even much more) 
+	 *
+	 */
+	const INDEX_NB_UNIQ_VISITORS = 1;
+	const INDEX_NB_VISITS = 2;
+	const INDEX_NB_ACTIONS = 3;
+	const INDEX_MAX_ACTIONS = 4;
+	const INDEX_SUM_VISIT_LENGTH = 5;
+	const INDEX_BOUNCE_COUNT = 6;
+
+	/**
+	 * Website Piwik_Site
+	 *
+	 * @var Piwik_Site
+	 */
+	protected $site = null;
+	
+	/**
+	 * Stores the already built archives.
+	 * Act as a big caching array
+	 *
+	 * @var array of Piwik_Archive
+	 */
+	static protected $alreadyBuilt = array();
+	
+	/**
+	 * Builds an Archive object or returns the same archive if previously built.
+	 *
+	 * @param string|int idSite integer, or comma separated list of integer
+	 * @param string|Piwik_Date $date 'YYYY-MM-DD' or magic keywords 'today' @see Piwik_Date::factory()
+	 * @param string $period 'week' 'day' etc.
+	 * 
+	 * @return Piwik_Archive
+	 */
+	static public function build($idSite, $period, $strDate )
+	{
+		if($idSite === 'all')
+		{
+			$sites = Piwik_SitesManager_API::getSitesIdWithAtLeastViewAccess();
+		}
+		else
+		{
+			$sites = Piwik_Site::getIdSitesFromIdSitesString($idSite);
+		}
+		
+		// idSite=1,3 or idSite=all
+		if( count($sites) > 1 
+			|| $idSite === 'all' )
+		{
+			require_once 'Archive/Array/IndexedBySite.php';
+			$archive = new Piwik_Archive_Array_IndexedBySite($sites, $period, $strDate);
+		}
+		// if a period date string is detected: either 'last30', 'previous10' or 'YYYY-MM-DD,YYYY-MM-DD'
+		elseif(is_string($strDate) 
+			&& (
+				ereg('^(last|previous){1}([0-9]*)$', $strDate, $regs)
+				|| ereg('^([0-9]{4}-[0-9]{1,2}-[0-9]{1,2}),([0-9]{4}-[0-9]{1,2}-[0-9]{1,2})$', $strDate, $regs)
+				)
+			)
+		{
+			$oSite = new Piwik_Site($idSite);
+			require_once 'Archive/Array/IndexedByDate.php';
+			$archive = new Piwik_Archive_Array_IndexedByDate($oSite, $period, $strDate);
+		}
+		// case we request a single archive
+		else
+		{
+			if(is_string($strDate))
+			{
+				$oDate = Piwik_Date::factory($strDate);
+			}
+			else
+			{
+				$oDate = $strDate;
+			}
+			$date = $oDate->toString();
+			
+			if(isset(self::$alreadyBuilt[$idSite][$date][$period]))
+			{
+				return self::$alreadyBuilt[$idSite][$date][$period];
+			}
+			
+			$oPeriod = Piwik_Period::factory($period, $oDate);
+			
+			$archive = new Piwik_Archive_Single;
+			$archive->setPeriod($oPeriod);
+			$archive->setSite(new Piwik_Site($idSite));
+			
+			self::$alreadyBuilt[$idSite][$date][$period] = $archive;
+		}
+		
+		return $archive;
+	}
+	
+	abstract public function prepareArchive();
+	
+	/**
+	 * Returns the value of the element $name from the current archive 
+	 * The value to be returned is a numeric value and is stored in the archive_numeric_* tables
+	 *
+	 * @param string $name For example Referers_distinctKeywords 
+	 * @return float|int|false False if no value with the given name
+	 */
+	abstract public function getNumeric( $name );
+	
+	/**
+	 * Returns the value of the element $name from the current archive
+	 * 
+	 * The value to be returned is a blob value and is stored in the archive_numeric_* tables
+	 * 
+	 * It can return anything from strings, to serialized PHP arrays or PHP objects, etc.
+	 *
+	 * @param string $name For example Referers_distinctKeywords 
+	 * @return mixed False if no value with the given name
+	 */
+	abstract public function getBlob( $name );
+	
+	abstract public function getDataTableFromNumeric( $fields );
+
+	/**
+	 * This method will build a dataTable from the blob value $name in the current archive.
+	 * 
+	 * For example $name = 'Referers_searchEngineByKeyword' will return a  Piwik_DataTable containing all the keywords
+	 * If a idSubTable is given, the method will return the subTable of $name 
+	 * 
+	 * @param string $name
+	 * @param int $idSubTable or null if requesting the parent table
+	 * @return Piwik_DataTable
+	 * @throws exception If the value cannot be found
+	 */
+	abstract public function getDataTable( $name, $idSubTable = null );
+
+	/**
+	 * Same as getDataTable() except that it will also load in memory
+	 * all the subtables for the DataTable $name. 
+	 * You can then access the subtables by using the Piwik_DataTable_Manager getTable() 
+	 *
+	 * @param string $name
+	 * @param int $idSubTable or null if requesting the parent table
+	 * @return Piwik_DataTable
+	 */
+	abstract public function getDataTableExpanded($name, $idSubTable = null);
+
+	/**
+	 * Sets the site
+	 *
+	 * @param Piwik_Site $site
+	 */
+	public function setSite( Piwik_Site $site )
+	{
+		$this->site = $site;
+	}
+	
+	/**
+	 * Gets the site
+	 *
+	 * @param Piwik_Site $site
+	 */
+	public function getSite()
+	{
+		return $this->site;
+	}
+	
+	/**
+	 * Returns the Id site associated with this archive
+	 *
+	 * @return int
+	 */
+	public function getIdSite()
+	{
+		return $this->site->getId();
+	}
+	
+}
+
+
+
+
+
diff --git a/core/Archive/Array.php b/core/Archive/Array.php
new file mode 100644
index 0000000000..479cbd39ed
--- /dev/null
+++ b/core/Archive/Array.php
@@ -0,0 +1,161 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Request.php 380 2008-03-17 14:59:24Z matt $
+ * 
+ * 
+ * @package Piwik_Archive
+ */
+
+require_once "DataTable/Simple.php";
+require_once "DataTable/Array.php";
+/**
+ * This class is used to store multiple archives, when the user requests a period's archive.
+ *
+ */
+abstract class Piwik_Archive_Array extends Piwik_Archive
+{	
+	/**
+	 * This array contains one Piwik_Archive per entry in the period
+	 * 
+	 * @var array
+	 */
+	protected $archives = array();
+	
+	abstract protected function getIndexName();
+	abstract protected function getDataTableLabelValue( $archive );
+	
+	public function prepareArchive()
+	{
+		foreach($this->archives as $archive)
+		{
+			$archive->prepareArchive();
+		}
+	}
+	
+	/**
+	 * Returns a newly created Piwik_DataTable_Array.
+	 *
+	 * @return Piwik_DataTable_Array
+	 */
+	protected function getNewDataTableArray()
+	{
+		$table = new Piwik_DataTable_Array;
+		$table->setKeyName($this->getIndexName());
+		return $table;
+	}
+	
+	
+	
+	/**
+	 * Adds metadata information to the Piwik_DataTable_Array 
+	 * using the information given by the Archive
+	 *
+	 * @param Piwik_DataTable_Array $table
+	 * @param unknown_type $archive
+	 */
+	protected function loadMetadata(Piwik_DataTable_Array $table, $archive)
+	{
+	}
+	
+	/**
+	 * Returns a DataTable_Array containing numeric values 
+	 * of the element $name from the archives in this Archive_Array.
+	 *
+	 * @param string $name Name of the mysql table field to load eg. Referers_distinctKeywords
+	 * 
+	 * @return Piwik_DataTable_Array containing the requested numeric value for each Archive
+	 */
+	public function getNumeric( $name )
+	{
+		$table = $this->getNewDataTableArray();
+		
+		foreach($this->archives as $archive)
+		{
+			$numeric = $archive->getNumeric( $name ) ;
+			$subTable = new Piwik_DataTable_Simple();
+			$subTable->loadFromArray( array( $numeric ) );
+			$table->addTable($subTable, $this->getDataTableLabelValue($archive));
+			
+			$this->loadMetadata($table, $archive);
+		}
+		
+		return $table;
+	}
+	
+	
+	/**
+	 * Returns a DataTable_Array containing values 
+	 * of the element $name from the archives in this Archive_Array.
+	 *
+	 * The value to be returned are blob values (stored in the archive_numeric_* tables in the DB).	 * 
+	 * It can return anything from strings, to serialized PHP arrays or PHP objects, etc.
+	 *
+	 * @param string $name Name of the mysql table field to load eg. Referers_keywordBySearchEngine 
+	 * 
+	 * @return Piwik_DataTable_Array containing the requested blob values for each Archive
+	 */
+	public function getBlob( $name )
+	{
+		$table = $this->getNewDataTableArray();
+		
+		foreach($this->archives as $archive)
+		{
+			$blob = $archive->getBlob( $name ) ;
+			$subTable = new Piwik_DataTable_Simple();
+			$subTable->loadFromArray( array('blob' => $blob));
+			$table->addTable($subTable, $this->getDataTableLabelValue($archive));
+			
+			$this->loadMetadata($table, $archive);
+		}
+		return $table;
+	}
+	
+	/**
+	 * Given a BLOB field name (eg. 'Referers_searchEngineByKeyword'), it will return a Piwik_DataTable_Array
+	 * which is an array of Piwik_DataTable, ordered by chronological order
+	 * 
+	 * @param string $name Name of the mysql table field to load
+	 * @param int $idSubTable optional idSubDataTable
+	 * @return Piwik_DataTable_Array
+	 * @throws exception If the value cannot be found
+	 */
+	public function getDataTable( $name, $idSubTable = null )
+	{		
+		$table = $this->getNewDataTableArray();
+		foreach($this->archives as $archive)
+		{
+			$subTable =  $archive->getDataTable( $name, $idSubTable ) ;
+			$table->addTable($subTable, $this->getDataTableLabelValue($archive));
+			
+			$this->loadMetadata($table, $archive);
+		}
+		return $table;
+	}
+	
+	
+	/**
+	 * Same as getDataTable() except that it will also load in memory
+	 * all the subtables for the DataTable $name. 
+	 * You can then access the subtables by using the Piwik_DataTable_Manager::getInstance()->getTable($idSubTable);
+	 *
+	 * @param string $name Name of the mysql table field to load
+	 * @param int $idSubTable optional idSubDataTable
+	 * @return Piwik_DataTable_Array
+	 */
+	public function getDataTableExpanded($name, $idSubTable = null)
+	{
+		$table = $this->getNewDataTableArray();
+		foreach($this->archives as $archive)
+		{
+			$subTable =  $archive->getDataTableExpanded( $name, $idSubTable ) ;
+			$table->addTable($subTable, $this->getDataTableLabelValue($archive));
+			
+			$this->loadMetadata($table, $archive);
+		}
+		return $table;
+	}
+}
diff --git a/core/Archive/Array/IndexedByDate.php b/core/Archive/Array/IndexedByDate.php
new file mode 100644
index 0000000000..87e57491d2
--- /dev/null
+++ b/core/Archive/Array/IndexedByDate.php
@@ -0,0 +1,122 @@
+<?php
+require_once "Archive/Array.php";
+
+class Piwik_Archive_Array_IndexedByDate extends Piwik_Archive_Array {
+	
+	/**
+	 * Builds an array of Piwik_Archive of a given date range
+	 *
+	 * @param Piwik_Site $oSite 
+	 * @param string $strPeriod eg. 'day' 'week' etc.
+	 * @param string $strDate A date range, eg. 'last10', 'previous5' or 'YYYY-MM-DD,YYYY-MM-DD'
+	 */
+	function __construct(Piwik_Site $oSite, $strPeriod, $strDate)
+	{
+		$rangePeriod = new Piwik_Period_Range($strPeriod, $strDate);
+		foreach($rangePeriod->getSubperiods() as $subPeriod)
+		{
+			$startDate = $subPeriod->getDateStart();
+			$archive = Piwik_Archive::build($oSite->getId(), $strPeriod, $startDate );
+			$archive->prepareArchive();
+			$timestamp = $archive->getTimestampStartDate();
+			$this->archives[$timestamp] = $archive;
+		}
+		ksort( $this->archives );
+	}
+	
+	protected function getIndexName()
+	{
+		return 'date';
+	}
+	
+	protected function loadMetadata(Piwik_DataTable_Array $table, $archive)
+	{
+		$table->metadata[$archive->getPrettyDate()] = array( 
+				'timestamp' => $archive->getTimestampStartDate(),
+				'site' => $archive->getSite(),
+			);
+	}
+	protected function getDataTableLabelValue( $archive )
+	{
+		return $archive->getPrettyDate();
+	}
+	
+	/**
+	 * Given a list of fields defining numeric values, it will return a Piwik_DataTable_Array
+	 * which is an array of Piwik_DataTable_Simple, ordered by chronological order
+	 *
+	 * @param array|string $fields array( fieldName1, fieldName2, ...)  Names of the mysql table fields to load
+	 * @return Piwik_DataTable_Array
+	 */
+	public function getDataTableFromNumeric( $fields )
+	{
+		if(!is_array($fields))
+		{
+			$fields = array($fields);
+		}
+		
+		$inName = "'" . implode("', '",$fields) . "'";
+		
+		// we select in different shots
+		// one per distinct table (case we select last 300 days, maybe we will  select from 10 different tables)
+		$queries = array();
+		foreach($this->archives as $archive) 
+		{
+			if(!$archive->isThereSomeVisits)
+			{
+				continue;
+			}
+			
+			$table = $archive->archiveProcessing->getTableArchiveNumericName();
+
+			// for every query store IDs
+			$queries[$table][] = $archive->getIdArchive();
+		}
+		// we select the requested value
+		$db = Zend_Registry::get('db');
+		
+		// date => array( 'field1' =>X, 'field2'=>Y)
+		// date2 => array( 'field1' =>X2, 'field2'=>Y2)		
+		
+		$arrayValues = array();
+		foreach($queries as $table => $aIds)
+		{
+			$inIds = implode(', ', $aIds);
+			$sql = "SELECT value, name, idarchive, UNIX_TIMESTAMP(date1) as timestamp
+									FROM $table
+									WHERE idarchive IN ( $inIds )
+										AND name IN ( $inName )";
+
+			$values = $db->fetchAll($sql);
+			
+			foreach($values as $value)
+			{
+				$arrayValues[$value['timestamp']][$value['name']] = $value['value'];
+			}			
+		}
+		
+		$contentArray = array();
+		// we add empty tables so that every requested date has an entry, even if there is nothing
+		// example: <result date="2007-01-01" />
+		foreach($this->archives as $timestamp => $archive)
+		{
+			$strDate = $this->archives[$timestamp]->getPrettyDate();
+			$contentArray[$timestamp]['table'] = new Piwik_DataTable_Simple();
+			$contentArray[$timestamp]['prettyDate'] = $strDate;
+		}
+
+		foreach($arrayValues as $timestamp => $aNameValues)
+		{
+			$contentArray[$timestamp]['table']->loadFromArray($aNameValues);
+		}
+		ksort( $contentArray );
+				
+		$tableArray = $this->getNewDataTableArray();
+		foreach($contentArray as $timestamp => $aData)
+		{
+			$tableArray->addTable($aData['table'], $aData['prettyDate']);
+			$this->loadMetadata($tableArray, $this->archives[$timestamp]);
+		}
+		return $tableArray;
+	}
+}
\ No newline at end of file
diff --git a/core/Archive/Array/IndexedBySite.php b/core/Archive/Array/IndexedBySite.php
new file mode 100644
index 0000000000..8d1f984809
--- /dev/null
+++ b/core/Archive/Array/IndexedBySite.php
@@ -0,0 +1,98 @@
+<?php
+require_once "Archive/Array.php";
+
+class Piwik_Archive_Array_IndexedBySite extends Piwik_Archive_Array {
+	
+	/**
+	 *
+	 * @param Piwik_Site $oSite 
+	 * @param string $strPeriod eg. 'day' 'week' etc.
+	 * @param string $strDate A date range, eg. 'last10', 'previous5' or 'YYYY-MM-DD,YYYY-MM-DD'
+	 */
+	function __construct($sites, $strPeriod, $strDate)
+	{
+		foreach($sites as $idSite)
+		{
+			$archive = Piwik_Archive::build($idSite, $strPeriod, $strDate );
+			$archive->setSite(new Piwik_Site($idSite));
+			$archive->prepareArchive();
+			$this->archives[$idSite] = $archive;
+		}
+		ksort( $this->archives );
+	}
+	
+	protected function getIndexName()
+	{
+		return 'idSite';
+	}
+	
+	protected function getDataTableLabelValue( $archive )
+	{
+		return $archive->getIdSite();
+	}
+	
+	/**
+	 * Given a list of fields defining numeric values, it will return a Piwik_DataTable_Array
+	 * which is an array of Piwik_DataTable_Simple, ordered by chronological order
+	 *
+	 * @param array|string $fields array( fieldName1, fieldName2, ...)  Names of the mysql table fields to load
+	 * @return Piwik_DataTable_Array
+	 */
+	public function getDataTableFromNumeric( $fields )
+	{
+		if(!is_array($fields))
+		{
+			$fields = array($fields);
+		}
+		$inName = "'" . implode("', '",$fields) . "'";
+		
+		$numericTable = null;
+		$aIds = array();
+		foreach($this->archives as $archive)
+		{
+			if(is_null($numericTable))
+			{
+				$numericTable = $archive->archiveProcessing->getTableArchiveNumericName();
+			}
+			else if( $numericTable != $archive->archiveProcessing->getTableArchiveNumericName())
+			{
+				throw new Exception("Piwik_Archive_Array_IndexedBySite::getDataTableFromNumeric() algorithm won't work if data is stored in different tables");
+			}
+			$aIds[] = $archive->getIdArchive();
+		}
+		
+		$inIds = implode(', ', $aIds);
+		$sql = "SELECT value, name, idarchive, idsite
+								FROM $numericTable
+								WHERE idarchive IN ( $inIds )
+									AND name IN ( $inName )";
+		$values = Zend_Registry::get('db')->fetchAll($sql);
+			
+		$arrayValues = array();
+		foreach($values as $value)
+		{
+			$arrayValues[$value['idsite']][$value['name']] = $value['value'];
+		}			
+		
+		// we add empty tables so that every requested date has an entry, even if there is nothing
+		// example: <result idSite="159" />
+		$contentArray = array();
+		foreach($this->archives as $idSite => $archive)
+		{
+			$contentArray[$idSite]['table'] = new Piwik_DataTable_Simple();
+		}
+		
+		foreach($arrayValues as $idSite => $aNameValues)
+		{
+			$contentArray[$idSite]['table']->loadFromArray($aNameValues);
+		}
+		ksort( $contentArray );
+				
+		$tableArray = $this->getNewDataTableArray();
+		foreach($contentArray as $idSite => $aData)
+		{
+			$tableArray->addTable($aData['table'], $idSite);
+		}
+		return $tableArray;
+	}
+}
\ No newline at end of file
diff --git a/core/Archive/Single.php b/core/Archive/Single.php
new file mode 100644
index 0000000000..be4fbde7e4
--- /dev/null
+++ b/core/Archive/Single.php
@@ -0,0 +1,463 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Request.php 380 2008-03-17 14:59:24Z matt $
+ * 
+ * 
+ * @package Piwik_Archive
+ */
+
+/**
+ * This class is used to store the data of a single archive, 
+ * for example the statistics for the 'day' '2008-02-21' for the website idSite '2' 
+ *
+ */
+class Piwik_Archive_Single extends Piwik_Archive
+{
+	/**
+	 * The Piwik_ArchiveProcessing object used to check that the archive is available
+	 * and launch the processing if the archive was not yet processed
+	 * 
+	 * @var Piwik_ArchiveProcessing
+	 */
+	public $archiveProcessing = null;
+	
+	/**
+	 * @var bool Set to true if the archive has at least 1 visit
+	 */
+	public $isThereSomeVisits = false;
+
+	/**
+	 * Period of this Archive
+	 *
+	 * @var Piwik_Period
+	 */
+	protected $period = null;
+	
+	/**
+	 * Set to true will activate numeric value caching for this archive.
+	 *
+	 * @var bool
+	 */
+	protected $cacheEnabledForNumeric = true;
+	
+	/**
+	 * Array of cached numeric values, used to make requests faster 
+	 * when requesting the same value again and again
+	 *
+	 * @var array of numeric
+	 */
+	protected $numericCached = array();
+	
+	/**
+	 * Array of cached blob, used to make requests faster when requesting the same blob again and again
+	 *
+	 * @var array of mixed
+	 */
+	protected $blobCached = array();
+	
+	/**
+	 * idarchive of this Archive in the database
+	 *
+	 * @var int
+	 */
+	protected $idArchive = null;
+	
+	/**
+	 * Flag set to true once the archive has been checked (when we make sure it is archived)
+	 *
+	 * @var bool
+	 */
+	protected $alreadyChecked = false;
+	
+	/**
+	 * Returns the pretty date of this Archive, eg. 'Thursday 20th March 2008'
+	 *
+	 * @return string
+	 */
+	public function getPrettyDate()
+	{
+		return $this->period->getPrettyString();
+	}
+	
+	/**
+	 * Returns the idarchive of this Archive used to index this archive in the DB
+	 *
+	 * @return int
+	 */
+	public function getIdArchive()
+	{
+		if(is_null($this->idArchive))
+		{
+			throw new Exception("idArchive is null");
+		}
+		return $this->idArchive;
+	}
+	
+	/**
+	 * Set the period 
+	 *
+	 * @param Piwik_Period $period
+	 */
+	public function setPeriod( Piwik_Period $period )
+	{
+		$this->period = $period;
+	}
+	
+	/**
+	 * Returns the timestamp of the first date in the period for this Archive.
+	 * This is used to sort archives by date when working on a Archive_Array
+	 *
+	 * @return int Unix timestamp
+	 */
+	public function getTimestampStartDate()
+	{
+		if(!is_null($this->archiveProcessing))
+		{
+			return $this->archiveProcessing->getTimestampStartDate();
+		}
+		
+		return $this->period->getDateStart()->getTimestamp();
+	}
+		
+	/**
+	 * Prepares the archive. Gets the idarchive from the ArchiveProcessing.
+	 * 
+	 * This will possibly launch the archiving process if the archive was not available.
+	 * 
+	 * @return void
+	 */
+	public function prepareArchive()
+	{
+		if(!$this->alreadyChecked)
+		{
+			$this->isThereSomeVisits = false;
+			$this->alreadyChecked = true;
+			
+			// if the END of the period is BEFORE the website creation date
+			// we already know there are no stats for this period
+			// we add one day to make sure we don't miss the day of the website creation
+			if( $this->period->getDateEnd()->addDay(2)->isEarlier( $this->site->getCreationDate() ) )
+			{
+				return;				
+			}
+			
+			// if the starting date is in the future we know there is no visit
+			if( $this->period->getDateStart()->subDay(1)->isLater( Piwik_Date::today() ) )
+			{
+				return;
+			}
+			
+			// we make sure the archive is available for the given date
+			$periodLabel = $this->period->getLabel();
+			$archiveProcessing = Piwik_ArchiveProcessing::factory($periodLabel);
+			$archiveProcessing->setSite($this->site);
+			$archiveProcessing->setPeriod($this->period);
+			
+			$idArchive = $archiveProcessing->loadArchive();
+			$this->isThereSomeVisits = $archiveProcessing->isThereSomeVisits;
+			
+			$this->archiveProcessing = $archiveProcessing; 
+
+			$this->idArchive = $idArchive;
+			$this->alreadyChecked = true;
+		}
+	}
+	
+	/**
+	 * Returns a value from the current archive with the name = $name 
+	 * Method used by getNumeric or getBlob
+	 *
+	 * @param string $name
+	 * @param string $typeValue numeric|blob
+	 * @return mixed|false if no result
+	 */
+	protected function get( $name, $typeValue = 'numeric' )
+	{
+		// values previously "get" and now cached
+		if($typeValue == 'numeric'
+			&& $this->cacheEnabledForNumeric
+			&& isset($this->numericCached[$name])
+			)
+		{
+			return $this->numericCached[$name];
+		}
+		
+		// During archiving we prefetch the blobs recursively
+		// and we get them faster from memory after
+		if($typeValue == 'blob'
+			&& isset($this->blobCached[$name]))
+		{
+			return $this->blobCached[$name];
+		}
+		
+		$this->prepareArchive();
+				
+		if($name == 'idarchive')
+		{
+			return $this->idArchive;
+		}
+		
+//		Piwik::log("-- get '$name'");
+		
+		if(!$this->isThereSomeVisits)
+		{
+			return false;
+		}
+
+		// select the table to use depending on the type of the data requested		
+		switch($typeValue)
+		{
+			case 'blob':
+				$table = $this->archiveProcessing->getTableArchiveBlobName();
+			break;
+
+			case 'numeric':
+			default:
+				$table = $this->archiveProcessing->getTableArchiveNumericName();
+			break;
+		}
+
+		// we select the requested value
+		$db = Zend_Registry::get('db');
+		$value = $db->fetchOne("SELECT value 
+								FROM $table
+								WHERE idarchive = ?
+									AND name = ?",	
+								array( $this->idArchive , $name) 
+							);
+
+		// no result, returns false
+		if($value === false)
+		{
+			if($typeValue == 'numeric' 
+				&& $this->cacheEnabledForNumeric)
+			{
+				// we cache the results
+				$this->numericCached[$name] = false;
+			}	
+			return $value;
+		}
+		
+		// uncompress when selecting from the BLOB table
+		if($typeValue == 'blob')
+		{
+			$value = gzuncompress($value);
+		}
+		
+		if($typeValue == 'numeric' 
+			&& $this->cacheEnabledForNumeric)
+		{
+			// we cache the results
+			$this->numericCached[$name] = $value;
+		}
+		return $value;
+	}
+	
+	
+	/**
+	 * This method loads in memory all the subtables for the main table called $name.
+	 * You have to give it the parent table $dataTableToLoad so we can lookup the sub tables ids to load.
+	 * 
+	 * If $addMetadataSubtableId set to true, it will add for each row a 'metadata' called 'databaseSubtableId' 
+	 *  containing the child ID of the subtable  associated to this row.
+	 *
+	 * @param string $name
+	 * @param Piwik_DataTable $dataTableToLoad
+	 * @param bool $addMetadataSubtableId
+	 * 
+	 * @return void
+	 */
+	public function loadSubDataTables($name, Piwik_DataTable $dataTableToLoad, $addMetadataSubtableId = false)
+	{
+		// we have to recursively load all the subtables associated to this table's rows
+		// and update the subtableID so that it matches the newly instanciated table 
+		foreach($dataTableToLoad->getRows() as $row)
+		{
+			$subTableID = $row->getIdSubDataTable();
+			
+			if($subTableID !== null)
+			{
+				$subDataTableLoaded = $this->getDataTable($name, $subTableID);
+				
+				$this->loadSubDataTables($name, $subDataTableLoaded);
+				
+				// we edit the subtable ID so that it matches the newly table created in memory
+				// NB:
+				// we dont do that in the case we are displaying the table expanded.
+				// in this case we want the user to see the REAL dataId in the database
+				if($addMetadataSubtableId)
+				{
+					$row->addMetadata('databaseSubtableId', $row->getIdSubDataTable());
+				}
+				$row->setSubtable( $subDataTableLoaded );
+			}
+		}
+	}
+
+	
+	/**
+	 * Free the blob cache memory array
+	 *
+	 * @return void
+	 */
+	public function freeBlob( $name )
+	{
+		// we delete the blob
+		$this->blobCached = null; 
+		$this->blobCached = array(); 
+	}
+	
+	/**
+	 * Fetches all blob fields name_* at once for the current archive for performance reasons.
+	 * 
+	 * @return void
+	 */
+	public function preFetchBlob( $name )
+	{
+		if(!$this->isThereSomeVisits)
+		{
+			return false;
+		}
+
+		$tableBlob = $this->archiveProcessing->getTableArchiveBlobName();
+
+		// we select the requested value
+		$db = Zend_Registry::get('db');
+		$query = $db->query("SELECT value, name
+								FROM $tableBlob
+								WHERE idarchive = ?
+									AND name LIKE '$name%'",	
+								array( $this->idArchive ) 
+							);
+
+		while($row = $query->fetch())
+		{
+			$value = $row['value'];
+			$name = $row['name'];
+						
+			$this->blobCached[$name] = gzuncompress($value);
+		}
+	}
+	
+	/**
+	 * Returns a numeric value from this Archive, with the name '$name'
+	 *
+	 * @param string $name
+	 * @return int|float
+	 */
+	public function getNumeric( $name )
+	{
+		// we cast the result as float because returns false when no visitors
+		return (float)$this->get($name, 'numeric');
+	}
+
+	
+	/**
+	 * Returns a blob value from this Archive, with the name '$name'
+	 * Blob values are all values except int and float.
+	 *
+	 * @param string $name
+	 * @return mixed
+	 */
+	public function getBlob( $name )
+	{
+		return $this->get($name, 'blob');		
+	}
+	
+	/**
+	 * Given a list of fields defining numeric values, it will return a Piwik_DataTable_Simple 
+	 * containing one row per field name.
+	 * 
+	 * For example $fields = array( 	'max_actions',
+	 *						'nb_uniq_visitors', 
+	 *						'nb_visits',
+	 *						'nb_actions', 
+	 *						'sum_visit_length',
+	 *						'bounce_count',
+	 *					); 
+	 *
+	 * @param string|array $fields Name or array of names of Archive fields 
+	 * 
+	 * @return Piwik_DataTable_Simple
+	 */
+	public function getDataTableFromNumeric( $fields )
+	{
+		require_once "DataTable/Simple.php";
+		if(!is_array($fields))
+		{
+			$fields = array($fields);
+		}
+		
+		$values = array();
+		foreach($fields as $field)
+		{
+			$values[$field] = $this->getNumeric($field);
+		}
+		
+		$table = new Piwik_DataTable_Simple;
+		$table->loadFromArray($values);
+		return $table;
+	}
+	
+	/**
+	 * Returns a DataTable that has the name '$name' from the current Archive.
+	 * If $idSubTable is specified, returns the subDataTable called '$name_$idSubTable'
+	 *
+	 * @param string $name
+	 * @param int $idSubTable optional id SubDataTable
+	 * @return Piwik_DataTable
+	 */
+	public function getDataTable( $name, $idSubTable = null )
+	{
+		if(!is_null($idSubTable))
+		{
+			$name .= "_$idSubTable";
+		}
+		
+		$data = $this->get($name, 'blob');
+		
+		$table = new Piwik_DataTable;
+		
+		if($data !== false)
+		{
+			$table->loadFromSerialized($data);
+		}
+		
+		if($data === false 
+			&& $idSubTable !== null)
+		{
+			throw new Exception("You are requesting a precise subTable but there is not such data in the Archive.");
+		}
+	
+		return $table;
+	}
+	
+	/**
+	 * Returns a DataTable that has the name '$name' from the current Archive.
+	 * Also loads in memory all subDataTable for this DataTable.
+	 * 
+	 * For example, if $name = 'Referers_keywordBySearchEngine' it will load all DataTable
+	 *  named 'Referers_keywordBySearchEngine_*' and they will be set as subDataTable to the
+	 *  rows. You can then go through the rows 
+	 * 		$rows = DataTable->getRows();
+	 *  and for each row request the subDataTable (in this case the DataTable of the keywords for each search engines)
+	 * 		$idSubTable = $row->getIdSubDataTable();
+	 * 		$subTable = Piwik_DataTable_Manager::getInstance()->getTable($idSubTable);
+	 *  
+	 * @param string $name
+	 * @param int $idSubTable Optional subDataTable to load instead of loading the parent DataTable
+	 * @return Piwik_DataTable
+	 */
+	public function getDataTableExpanded($name, $idSubTable = null)
+	{
+		$this->preFetchBlob($name);
+		$dataTableToLoad = $this->getDataTable($name, $idSubTable);
+		$this->loadSubDataTables($name, $dataTableToLoad, $addMetadataSubtableId = true);
+		return $dataTableToLoad;		
+	}
+}
+?>
\ No newline at end of file
diff --git a/core/ArchiveProcessing.php b/core/ArchiveProcessing.php
new file mode 100644
index 0000000000..b0e71a2114
--- /dev/null
+++ b/core/ArchiveProcessing.php
@@ -0,0 +1,570 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: ArchiveProcessing.php 536 2008-06-27 01:32:25Z matt $
+ * 
+ * @package Piwik_ArchiveProcessing
+ */
+
+require_once 'TablePartitioning.php';
+require_once 'ArchiveProcessing/Record.php';
+require_once 'DataTable.php';
+
+/**
+ * The ArchiveProcessing module is a module that reads the Piwik logs from the DB and
+ * compute all the reports, which are then stored in the database.
+ * 
+ * The ArchiveProcessing class is used by the Archive object to make sure the given Archive is processed and available in the DB.
+ * 
+ * A record in the Database for a given report is defined by
+ * - idarchive	= unique ID that is associated to all the data of this archive (idsite+period+date)
+ * - idsite		= the ID of the website 
+ * - date1 		= starting day of the period
+ * - date2 		= ending day of the period
+ * - period 	= integer that defines the period (day/week/etc.). @see period::getId()
+ * - ts_archived = timestamp when the archive was processed
+ * - name 		= the name of the report (ex: uniq_visitors or search_keywords_by_search_engines)
+ * - value 		= the actual data
+ * 
+ * @package Piwik_ArchiveProcessing
+ */
+abstract class Piwik_ArchiveProcessing
+{
+	/**
+	 * Flag stored at the end of the archiving
+	 *
+	 * @var int
+	 */
+	const DONE_OK = 1;
+	
+	/**
+	 * Flag stored at the start of the archiving
+	 * When requesting an Archive, we make sure that non-finished archive are not considered valid
+	 *
+	 * @var int
+	 */
+	const DONE_ERROR = 2;
+
+	/**
+	 * Idarchive in the DB for the requested archive
+	 *
+	 * @var int
+	 */
+	protected $idArchive;
+	
+	/**
+	 * Period id @see Piwik_Period::getId()
+	 *
+	 * @var int
+	 */
+	protected $periodId;
+	
+	/**
+	 * Timestamp for the first date of the period
+	 *
+	 * @var int unix timestamp
+	 */
+	protected $timestampDateStart = null;
+	
+	/**
+	 * Starting date of the archive
+	 * 
+	 * @var Piwik_Date
+	 */
+	protected $dateStart;
+	
+	/**
+	 * Ending date of the archive
+	 * 
+	 * @var Piwik_Date
+	 */
+	protected $dateEnd;
+	
+	/**
+	 * Object used to generate (depending on the $dateStart) the name of the DB table to use to store numeric values
+	 * 
+	 * @var Piwik_TablePartitioning
+	 */
+	protected $tableArchiveNumeric;
+	
+	/**
+	 * Object used to generate (depending on the $dateStart)  the name of the DB table to use to store numeric values
+	 * 
+	 * @var Piwik_TablePartitioning
+	 */
+	protected $tableArchiveBlob;
+	
+	/**
+	 * Maximum timestamp above which a given archive is considered out of date 
+	 *
+	 * @var int
+	 */
+	protected $maxTimestampArchive;
+	
+	/**
+	 * Id of the current site
+	 * Can be accessed by plugins (that is why it's public)
+	 * 
+	 * @var int
+	 */
+	public $idsite	= null;
+	
+	/**
+	 * Period of the current archive
+	 * Can be accessed by plugins (that is why it's public)
+	 * 
+	 * @var Piwik_Period
+	 */
+	public $period 	= null;
+	
+	/**
+	 * Site of the current archive
+	 * Can be accessed by plugins (that is why it's public)
+	 * 
+	 * @var Piwik_Site
+	 */
+	public $site 	= null;
+	
+	/**
+	 * Starting date @see Piwik_Date::toString()
+	 *
+	 * @var string
+	 */
+	public $strDateStart;
+	
+	/**
+	 * Ending date @see Piwik_Date::toString()
+	 *
+	 * @var string
+	 */
+	public $strDateEnd;
+	
+	/**
+	 * Name of the DB table _log_visit
+	 *
+	 * @var string
+	 */
+	public $logTable;
+	
+	/**
+	 * Name of the DB table _log_link_visit_action
+	 *
+	 * @var string
+	 */
+	public $logVisitActionTable;
+	
+	/**
+	 * Name of the DB table _log_action
+	 *
+	 * @var string
+	 */
+	public $logActionTable;
+	
+	/**
+	 * When set to true, we always archive, even if the archive is already available.
+	 * You can change this settings automatically in the config/global.ini.php always_archive_data under the [Debug] section
+	 *
+	 * @var bool
+	 */
+	protected $debugAlwaysArchive = false;
+	
+	/**
+	 * Builds the archive processing object, 
+	 * Reads some configuration value from the config file
+	 *
+	 */
+	public function __construct()
+	{
+		$this->debugAlwaysArchive = Zend_Registry::get('config')->Debug->always_archive_data;
+	}
+	
+	/**
+	 * Returns the Piwik_ArchiveProcessing_Day or Piwik_ArchiveProcessing_Period object
+	 * depending on $name period string
+	 *
+	 * @param string $name day|week|month|year
+	 * @return Piwik_ArchiveProcessing Piwik_ArchiveProcessing_Day|Piwik_ArchiveProcessing_Period
+	 */
+	static function factory($name )
+	{
+		switch($name)
+		{
+			case 'day':
+				require_once 'ArchiveProcessing/Day.php';			
+				$process = new Piwik_ArchiveProcessing_Day;
+			break;
+			
+			case 'week':
+			case 'month':
+			case 'year':
+				require_once 'ArchiveProcessing/Period.php';	
+				$process = new Piwik_ArchiveProcessing_Period;
+			break;
+			
+			default:
+				throw new Exception("Unknown period specified $name");
+			break;
+		}
+		return $process;
+	}
+	
+	/**
+	 * Inits the object
+	 * 
+	 * @return void
+	 */
+	protected function loadArchiveProperties()
+	{		
+		$this->idsite = $this->site->getId();
+		
+		$this->periodId = $this->period->getId();
+		
+		$this->dateStart = $this->period->getDateStart();
+		$this->dateEnd = $this->period->getDateEnd();
+		
+		$this->tableArchiveNumeric = new Piwik_TablePartitioning_Monthly('archive_numeric');
+		$this->tableArchiveNumeric->setTimestamp($this->dateStart->get());
+		$this->tableArchiveBlob = new Piwik_TablePartitioning_Monthly('archive_blob');
+		$this->tableArchiveBlob->setTimestamp($this->dateStart->get());
+
+		$this->strDateStart = $this->dateStart->toString();
+		$this->strDateEnd = $this->dateEnd->toString();
+		
+		// if the current archive is a DAY and if it's today,
+		// we set this maxTimestampArchive that defines the lifetime value of today's archive
+		$this->maxTimestampArchive = 0;
+		if( $this->period->getNumberOfSubperiods() == 0
+			&& $this->period->toString() == date("Y-m-d")
+			)
+		{
+			//TODO this TIMESTAMP should be a mysql NOW()!!!!
+			$this->maxTimestampArchive = time() - Zend_Registry::get('config')->General->time_before_archive_considered_outdated;
+		}
+		// either
+		// - if the period we're looking for is finished, we look for a ts_archived that 
+		//   is greater than the last day of the archive 
+		// - if the period we're looking for is not finished, we look for a recent enough archive
+		//   recent enough means maxTimestampArchive = 00:00:01 this morning
+		else
+		{
+			if($this->period->isFinished())
+			{
+				$this->maxTimestampArchive = $this->period->getDateEnd()->setTime('00:00:00')->addDay(1)->getTimestamp();
+			}
+			else
+			{
+				$this->maxTimestampArchive = Piwik_Date::today()->getTimestamp();
+			}
+		}
+	}
+	
+	/**
+	 * This method returns the idArchive ; if necessary, it triggers the archiving process.
+	 * 
+	 * If the archive was not processed yet, it will launch the archiving process.
+	 * If the current archive needs sub-archives (eg. a month archive needs all the days archive)
+	 *  it will recursively launch the archiving (using this loadArchive() on the sub-periods)
+	 *
+	 * @return int The idarchive of the archive
+	 */
+	public function loadArchive()
+	{
+		$this->loadArchiveProperties();
+		$this->idArchive = $this->isArchived();
+	
+		if($this->idArchive === false
+			&& $this->isArchivingDisabled())
+		{
+			$this->isThereSomeVisits = false;
+		}
+		elseif($this->idArchive === false
+				||	$this->debugAlwaysArchive)
+		{
+			$this->launchArchiving();
+		}
+		else
+		{
+			$this->isThereSomeVisits = true;
+		}
+		
+		return $this->idArchive;
+	}
+	
+	/**
+	 * @see loadArchive()
+	 *
+	 */
+	protected function launchArchiving()
+	{
+		$this->initCompute();
+		$this->compute();
+		$this->postCompute();
+		// we execute again the isArchived that does some initialization work
+		$this->idArchive = $this->isArchived();
+	}
+	
+	/**
+	 * This methods reads the subperiods if necessary, 
+	 * and computes the archive of the current period.
+	 */
+	abstract protected function compute();
+	
+	/**
+	 * Init the object before launching the real archive processing
+	 * 
+	 * @return void
+	 */
+	protected function initCompute()
+	{
+		$this->loadNextIdarchive();
+		
+		$record = new Piwik_ArchiveProcessing_Record_Numeric('done', Piwik_ArchiveProcessing::DONE_ERROR);
+		$this->insertRecord($record);
+		$record->delete();
+		
+		$this->logTable 			= Piwik::prefixTable('log_visit');
+		$this->logVisitActionTable 	= Piwik::prefixTable('log_link_visit_action');
+		$this->logActionTable	 	= Piwik::prefixTable('log_action');
+	}
+	
+	/**
+	 * Post processing called at the end of the main archive processing.
+	 * Makes sure the new archive is marked as "successful" in the DB
+	 * 
+	 * We also try to delete some stuff from memory but really there is still a lot...
+	 * 
+	 * @return void
+	 */
+	protected function postCompute()
+	{
+		// delete the first done = ERROR 
+		Zend_Registry::get('db')->query("
+							DELETE FROM ".$this->tableArchiveNumeric->getTableName()." 
+							WHERE idarchive = ? AND name = 'done'",
+					array($this->idArchive)
+				);
+		
+		$record = new Piwik_ArchiveProcessing_Record_Numeric('done', Piwik_ArchiveProcessing::DONE_OK);
+		
+		// save in the database the records
+		$records = Piwik_ArchiveProcessing_Record_Manager::getInstance()->getRecords();
+		
+		foreach($records as $record)
+		{
+			$this->insertRecord( $record);	
+		}
+		
+		// delete the records from the global manager
+		foreach($records as $record)
+		{
+			$record->delete();	
+		}
+		unset($records);
+		
+		// we delete all tables from the table register
+		Piwik_ArchiveProcessing_Record_Manager::getInstance()->deleteAll();
+	} 
+	
+	/**
+	 * Returns the name of the numeric table where the archive numeric values are stored
+	 *
+	 * @return string 
+	 */
+	public function getTableArchiveNumericName()
+	{
+		return $this->tableArchiveNumeric->getTableName();
+	}
+	
+	/**
+	 * Returns the name of the blob table where the archive blob values are stored
+	 *
+	 * @return string 
+	 */
+	public function getTableArchiveBlobName()
+	{
+		return $this->tableArchiveBlob->getTableName();
+	}
+	
+	/**
+	 * Set the period
+	 *
+	 * @param Piwik_Period $period
+	 */
+	public function setPeriod( Piwik_Period $period ) 
+	{
+		$this->period = $period;
+	}
+	
+	/**
+	 * Set the site
+	 *
+	 * @param Piwik_Site $site
+	 */
+	public function setSite( Piwik_Site $site )
+	{
+		$this->site = $site;
+	}
+	
+	/**
+	 * Returns the timestamp of the first date of the period
+	 *
+	 * @return int
+	 */
+	public function getTimestampStartDate()
+	{
+		// case when archive processing is in the past or the future, the starting date has not been set or processed yet
+		if(is_null($this->timestampDateStart))
+		{
+			return Piwik_Date::factory($this->strDateStart)->getTimestamp();
+		}
+		return $this->timestampDateStart;
+	}
+	
+	/**
+	 * Returns the idArchive we will use for the current archive
+	 *
+	 * @return int IdArchive to use when saving the current Archive
+	 */
+	protected function loadNextIdarchive()
+	{
+		$db = Zend_Registry::get('db');
+		$id = $db->fetchOne("SELECT max(idarchive) FROM ".$this->tableArchiveNumeric->getTableName());
+		if(empty($id))
+		{
+			$id = 0;
+		}
+		$this->idArchive = $id + 1;
+		
+	}
+	
+	/**
+	 * Inserts a record in the right table (either NUMERIC or BLOB)
+	 *
+	 * @param Piwik_ArchiveProcessing_Record $record
+	 */
+	protected function insertRecord($record)
+	{
+		// table to use to save the data
+		if(Piwik::isNumeric($record->value))
+		{
+			$table = $this->tableArchiveNumeric;
+		}
+		else
+		{
+			$table = $this->tableArchiveBlob;
+		}
+		
+		$query = "INSERT INTO ".$table->getTableName()." (idarchive, idsite, date1, date2, period, ts_archived, name, value)
+					VALUES (?,?,?,?,?,?,?,?)";
+		Zend_Registry::get('db')->query($query, 
+							array(	$this->idArchive,
+									$this->idsite, 
+									$this->strDateStart, 
+									$this->strDateEnd, 
+									$this->periodId, 
+									date("Y-m-d H:i:s"),
+									$record->name,
+									$record->value,
+							)
+					);
+	}
+	
+	/**
+	 * Returns the idArchive if the archive is available in the database.
+	 * Returns false if the archive needs to be computed.
+	 * 
+	 * An archive is available if
+	 * - for today, the archive was computed less than maxTimestampArchive seconds ago
+	 * - for any other day, if the archive was computed once this day was finished
+	 * - for other periods, if the archive was computed once the period was finished
+	 *
+	 * @return int|false
+	 */
+	protected function isArchived()
+	{
+		$bindSQL = array(	$this->idsite, 
+								$this->strDateStart, 
+								$this->strDateEnd, 
+								$this->periodId, 
+								);
+		$timeStampWhere = " AND UNIX_TIMESTAMP(ts_archived) >= ? ";
+		$bindSQL[] = $this->maxTimestampArchive;
+			
+		$sqlQuery = "	SELECT idarchive, value, name, UNIX_TIMESTAMP(date1) as timestamp
+						FROM ".$this->tableArchiveNumeric->getTableName()."
+						WHERE idsite = ?
+							AND date1 = ?
+							AND date2 = ?
+							AND period = ?
+							AND ( (name = 'done' AND value = ".Piwik_ArchiveProcessing::DONE_OK.")
+									OR name = 'nb_visits')
+							$timeStampWhere
+						ORDER BY ts_archived DESC";
+		
+		$results = Zend_Registry::get('db')->fetchAll($sqlQuery, $bindSQL );
+		if(empty($results))
+		{
+			return false;
+		}
+		
+		$idarchive = false;
+		// we look for the more recent idarchive
+		foreach($results as $result)
+		{
+			if($result['name'] == 'done')
+			{
+				$idarchive = $result['idarchive'];
+				$this->timestampDateStart = $result['timestamp'];
+				break;
+			}
+		}
+		
+		// case when we have a nb_visits entry in the archive, but the process is not finished yet or failed to finish
+		// therefore we don't have the done=OK
+		if($idarchive === false)
+		{
+			return false;
+		}
+		
+		// we look for the nb_visits result for this more recent archive
+		foreach($results as $result)
+		{
+			if($result['name'] == 'nb_visits' 
+				&& $result['idarchive'] == $idarchive)
+			{
+				$this->isThereSomeVisits = ($result['value'] != 0);
+				break;
+			}
+		}
+		return $idarchive;
+	}
+	
+	/**
+	 * Returns true if, for some reasons, triggering the archiving is disabled.
+	 *
+	 * @return bool
+	 */
+	protected function isArchivingDisabled()
+	{
+		static $archivingIsDisabled = null;
+		
+		if(is_null($archivingIsDisabled))
+		{
+			$archivingIsDisabled = false;
+			
+			$enableBrowserArchivingTriggering = (bool)Zend_Registry::get('config')->General->enable_browser_archiving_triggering;
+			if($enableBrowserArchivingTriggering == false)
+			{
+				if( !Piwik::isPhpCliMode())
+				{
+					$archivingIsDisabled = true;
+				}
+			}
+		}
+		
+		return $archivingIsDisabled;
+	}
+}
diff --git a/core/ArchiveProcessing/Day.php b/core/ArchiveProcessing/Day.php
new file mode 100644
index 0000000000..e0433820ce
--- /dev/null
+++ b/core/ArchiveProcessing/Day.php
@@ -0,0 +1,348 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Day.php 504 2008-06-01 20:19:28Z matt $
+ * 
+ * @package Piwik_ArchiveProcessing
+ */
+
+
+/**
+ * Handles the archiving process for a day.
+ * The class provides generic methods to manipulate data from the DB, easily create Piwik_DataTable objects.
+ * 
+ * All the logic of the archiving is done inside the plugins listening to the event 'ArchiveProcessing_Day.compute'
+ * 
+ * @package Piwik_ArchiveProcessing
+ * 
+ */
+class Piwik_ArchiveProcessing_Day extends Piwik_ArchiveProcessing
+{
+	/**
+	 * If the archive has at least 1 visit, this is set to true.
+	 *
+	 * @var bool
+	 */
+	public $isThereSomeVisits = false;
+	
+	/**
+	 * Constructor
+	 */
+	function __construct()
+	{
+		parent::__construct();
+		$this->db = Zend_Registry::get('db');
+	}
+	
+	/**
+	 * Main method to process logs for a day. The only logic done here is computing the number of visits, actions, etc.
+	 * All the other reports are computed inside plugins listening to the event 'ArchiveProcessing_Day.compute'.
+	 * See some of the plugins for an example eg. 'Provider'
+	 * 
+	 * @return void
+	 */
+	protected function compute()
+	{
+		$query = "SELECT 	count(distinct visitor_idcookie) as nb_uniq_visitors, 
+							count(*) as nb_visits,
+							sum(visit_total_actions) as nb_actions, 
+							max(visit_total_actions) as max_actions, 
+							sum(visit_total_time) as sum_visit_length,
+							sum(case visit_total_actions when 1 then 1 else 0 end) as bounce_count 
+					FROM ".$this->logTable."
+					WHERE visit_server_date = ?
+						AND idsite = ?
+					GROUP BY visit_server_date
+					ORDER BY NULL
+				 ";
+		$row = $this->db->fetchRow($query, array($this->strDateStart,$this->idsite ) );
+		
+		if($row === false)
+		{
+			return;
+		}
+		$this->isThereSomeVisits = true;
+	
+		foreach($row as $name => $value)
+		{
+			$record = new Piwik_ArchiveProcessing_Record_Numeric($name, $value);
+		}
+		
+		Piwik_PostEvent('ArchiveProcessing_Day.compute', $this);
+	}
+	
+	/**
+	 * Called at the end of the archiving process.
+	 * Does some cleaning job in the database.
+	 * 
+	 * @return void
+	 */
+	protected function postCompute()
+	{
+		parent::postCompute();
+		
+		// we delete out of date records
+		// = archives that for day N computed on day N (means they are only partial)
+		$blobTable = $this->tableArchiveBlob->getTableName();
+		$numericTable = $this->tableArchiveNumeric->getTableName();
+		
+		$query = "	DELETE 
+					FROM %s
+					WHERE period = ? 
+						AND date1 = DATE(ts_archived)
+						AND DATE(ts_archived) <> CURRENT_DATE()
+					";
+		
+		Zend_Registry::get('db')->query(sprintf($query, $blobTable), $this->periodId);
+		Zend_Registry::get('db')->query(sprintf($query, $numericTable), $this->periodId);
+	}
+	
+	/**
+	 * Helper function that returns a DataTable containing the $select fields / value pairs.
+	 * IMPORTANT: The $select must return only one row!!
+	 * 
+	 * Example $select = "count(distinct( config_os )) as countDistinctOs, 
+	 * 						sum( config_flash ) / count(distinct(idvisit)) as percentFlash "
+	 * 		   $labelCount = "test_column_name"
+	 * will return a dataTable that looks like
+	 * 		label  				test_column_name  	
+	 * 		CountDistinctOs 	9 	
+	 * 		PercentFlash 		0.5676
+	 * 						
+	 *
+	 * @param string $select 
+	 * @param string $labelCount
+	 * @return Piwik_DataTable
+	 */
+	public function getSimpleDataTableFromSelect($select, $labelCount)
+	{
+		$query = "SELECT $select 
+			 	FROM ".$this->logTable." 
+			 	WHERE visit_server_date = ?
+			 		AND idsite = ?";
+		$data = $this->db->fetchRow($query, array( $this->strDateStart, $this->idsite ));
+		
+		foreach($data as $label => &$count)
+		{
+			$count = array($labelCount => $count);
+		}
+		$table = new Piwik_DataTable;
+		$table->loadFromArrayLabelIsKey($data);
+		return $table;
+	}
+	
+	/**
+	 * Helper function that returns common statistics for a given database field distinct values.
+	 * 
+	 * The statistics returned are:
+	 *  - number of unique visitors
+	 *  - number of visits
+	 *  - number of actions
+	 *  - maximum number of action for a visit
+	 *  - sum of the visits' length in sec
+	 *  - count of bouncing visits (visits with one page view)
+	 * 
+	 * For example if $label = 'config_os' it will return the statistics for every distinct Operating systems
+	 * The returned DataTable will have a row per distinct operating systems, 
+	 *  and a column per stat (nb of visits, max  actions, etc)
+	 * 
+	 * label	nb_uniq_visitors	nb_visits	nb_actions	max_actions	sum_visit_length	bounce_count	
+	 * Linux	27	66	66	1	660	66	
+	 * Windows XP	12	39	39	1	390	39	
+	 * Mac OS	15	36	36	1	360	36	
+	 * 
+	 * @param string $label Table log_visit field name to be use to compute common stats
+	 * @return Piwik_DataTable
+	 */
+	public function getDataTableInterestForLabel( $label )
+	{
+		$query = "SELECT 	$label as label,
+							count(distinct visitor_idcookie) as nb_uniq_visitors, 
+							count(*) as nb_visits,
+							sum(visit_total_actions) as nb_actions, 
+							max(visit_total_actions) as max_actions, 
+							sum(visit_total_time) as sum_visit_length,
+							sum(case visit_total_actions when 1 then 1 else 0 end) as bounce_count
+				FROM ".$this->logTable."
+				WHERE visit_server_date = ?
+					AND idsite = ?
+				GROUP BY label";
+
+		$query = $this->db->query($query, array( $this->strDateStart, $this->idsite ) );
+
+		$interest = array();
+		while($rowBefore = $query->fetch())
+		{
+			$row = array(
+				Piwik_Archive::INDEX_NB_UNIQ_VISITORS 	=> $rowBefore['nb_uniq_visitors'], 
+				Piwik_Archive::INDEX_NB_VISITS 			=> $rowBefore['nb_visits'], 
+				Piwik_Archive::INDEX_NB_ACTIONS 		=> $rowBefore['nb_actions'], 
+				Piwik_Archive::INDEX_MAX_ACTIONS 		=> $rowBefore['max_actions'], 
+				Piwik_Archive::INDEX_SUM_VISIT_LENGTH 	=> $rowBefore['sum_visit_length'], 
+				Piwik_Archive::INDEX_BOUNCE_COUNT 		=> $rowBefore['bounce_count'],
+				'label'									=> $rowBefore['label']
+				);
+				
+			if(!isset($interest[$row['label']])) $interest[$row['label']]= $this->getNewInterestRow();
+			$this->updateInterestStats( $row, $interest[$row['label']]);
+		}
+
+		$table = new Piwik_DataTable;
+		$table->loadFromArrayLabelIsKey($interest);
+		return $table;
+	}
+	
+	/**
+	 * Generates a dataTable given a multidimensional PHP array that associates LABELS to Piwik_DataTableRows
+	 * This is used for the "Actions" DataTable, where a line is the aggregate of all the subtables
+	 * Example: the category /blog has 3 visits because it has /blog/index (2 visits) + /blog/about (1 visit) 
+	 *
+	 * @param array $table
+	 * @return Piwik_DataTable
+	 */
+	static public function generateDataTable( $table )
+	{
+		$dataTableToReturn = new Piwik_DataTable;
+		
+		foreach($table as $label => $maybeDatatableRow)
+		{
+			// case the aInfo is a subtable-like array
+			// it means that we have to go recursively and process it
+			// then we build the row that is an aggregate of all the children
+			// and we associate this row to the subtable
+			if( !($maybeDatatableRow instanceof Piwik_DataTable_Row) )
+			{
+				$subTable = self::generateDataTable($maybeDatatableRow);
+				$row = new Piwik_DataTable_Row_DataTableSummary( $subTable );
+				$row->addSubtable($subTable);
+				$row->setColumn('label', $label);
+			}
+			// if aInfo is a simple Row we build it
+			else
+			{
+				$row = $maybeDatatableRow;
+			}
+			
+			$dataTableToReturn->addRow($row);
+		}
+		
+		return $dataTableToReturn;
+	}
+	
+	/**
+	 * Helper function that returns the serialized DataTable of the given PHP array.
+	 * The array must have the format of Piwik_DataTable::loadFromArrayLabelIsKey()
+	 * Example: 	array (
+	 * 	 				LABEL => array(col1 => X, col2 => Y),
+	 * 	 				LABEL2 => array(col1 => X, col2 => Y),
+	 * 				)
+	 * 
+	 * @param array $array at the given format
+	 * @return array Array with one element: the serialized data table string
+	 */
+	public function getDataTableSerialized( $array )
+	{
+		$table = new Piwik_DataTable;
+		$table->loadFromArrayLabelIsKey($array );
+		$toReturn = $table->getSerialized();
+		return $toReturn;
+	}
+	
+	
+	/**
+	 * Helper function that returns the multiple serialized DataTable of the given PHP array.
+	 * The DataTable here associates a subtable to every row of the level 0 array.
+	 * This is used for example for search engines. Every search engine (level 0) has a subtable containing the
+	 * keywords.
+	 * 
+	 * The $arrayLevel0 must have the format 
+	 * Example: 	array (
+	 * 	 				LABEL => array(col1 => X, col2 => Y),
+	 * 	 				LABEL2 => array(col1 => X, col2 => Y),
+	 * 				)
+	 * 
+	 * The $subArrayLevel1ByKey must have the format
+	 * Example: 	array(
+	 * 					LABEL => #Piwik_DataTable_ForLABEL,
+	 * 					LABEL2 => #Piwik_DataTable_ForLABEL2,
+	 * 				)
+	 * 
+	 * 
+	 * @param array $arrayLevel0 
+	 * @param array of Piwik_DataTable $subArrayLevel1ByKey 
+	 * @return array Array with N elements: the strings of the datatable serialized 
+	 */
+	public function getDataTablesSerialized( $arrayLevel0, $subArrayLevel1ByKey, $maximumRowsInDataTableLevelZero = null, $maximumRowsInSubDataTable = null)
+	{
+		$tablesByLabel = array();
+
+		foreach($arrayLevel0 as $label => $aAllRowsForThisLabel)
+		{
+			$table = new Piwik_DataTable;
+			$table->loadFromArrayLabelIsKey($aAllRowsForThisLabel);
+			$tablesByLabel[$label] = $table;
+		}
+		$parentTableLevel0 = new Piwik_DataTable;
+		$parentTableLevel0->loadFromArrayLabelIsKey($subArrayLevel1ByKey, $tablesByLabel);
+
+		$toReturn = $parentTableLevel0->getSerialized($maximumRowsInDataTableLevelZero, $maximumRowsInSubDataTable);
+		return $toReturn;
+	}
+	
+	/**
+	 * Returns an empty row containing default values for the common stat
+	 *
+	 * @return array
+	 */
+	public function getNewInterestRow()
+	{
+		return array(	Piwik_Archive::INDEX_NB_UNIQ_VISITORS 	=> 0, 
+						Piwik_Archive::INDEX_NB_VISITS 			=> 0, 
+						Piwik_Archive::INDEX_NB_ACTIONS 		=> 0, 
+						Piwik_Archive::INDEX_MAX_ACTIONS 		=> 0, 
+						Piwik_Archive::INDEX_SUM_VISIT_LENGTH 	=> 0, 
+						Piwik_Archive::INDEX_BOUNCE_COUNT 		=> 0
+						);
+	}
+	
+	
+	/**
+	 * Returns a Piwik_DataTable_Row containing default values for common stat, 
+	 * plus a column 'label' with the value $label
+	 *
+	 * @param string $label
+	 * @return Piwik_DataTable_Row
+	 */
+	public function getNewInterestRowLabeled( $label )
+	{
+		return new Piwik_DataTable_Row(
+				array( 
+					Piwik_DataTable_Row::COLUMNS => 		array(	'label' => $label) 
+															+ $this->getNewInterestRow()
+					)
+				); 
+	}
+	
+	/**
+	 * Adds the given row $newRowToAdd to the existing  $oldRowToUpdate passed by reference
+	 *
+	 * The rows are php arrays Name => value
+	 * 
+	 * @param array $newRowToAdd
+	 * @param array $oldRowToUpdate
+	 */
+	public function updateInterestStats( $newRowToAdd, &$oldRowToUpdate)
+	{		
+		$oldRowToUpdate[Piwik_Archive::INDEX_NB_UNIQ_VISITORS]	+= $newRowToAdd[Piwik_Archive::INDEX_NB_UNIQ_VISITORS];
+		$oldRowToUpdate[Piwik_Archive::INDEX_NB_VISITS] 		+= $newRowToAdd[Piwik_Archive::INDEX_NB_VISITS];
+		$oldRowToUpdate[Piwik_Archive::INDEX_NB_ACTIONS] 		+= $newRowToAdd[Piwik_Archive::INDEX_NB_ACTIONS];
+		$oldRowToUpdate[Piwik_Archive::INDEX_MAX_ACTIONS] 		 = (float)max($newRowToAdd[Piwik_Archive::INDEX_MAX_ACTIONS], $oldRowToUpdate[Piwik_Archive::INDEX_MAX_ACTIONS]);
+		$oldRowToUpdate[Piwik_Archive::INDEX_SUM_VISIT_LENGTH]	+= $newRowToAdd[Piwik_Archive::INDEX_SUM_VISIT_LENGTH];
+		$oldRowToUpdate[Piwik_Archive::INDEX_BOUNCE_COUNT] 		+= $newRowToAdd[Piwik_Archive::INDEX_BOUNCE_COUNT];
+	}
+}
+
+
diff --git a/core/ArchiveProcessing/Period.php b/core/ArchiveProcessing/Period.php
new file mode 100644
index 0000000000..7e834bec62
--- /dev/null
+++ b/core/ArchiveProcessing/Period.php
@@ -0,0 +1,270 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Period.php 536 2008-06-27 01:32:25Z matt $
+ * 
+ * @package Piwik_ArchiveProcessing
+ */
+
+/**
+ * Handles the archiving process for a period
+ * 
+ * This class provides generic methods to archive data for a period (week / month / year).
+ * 
+ * These methods are called by the plugins that do the logic of archiving their own data. \
+ * They hook on the event 'ArchiveProcessing_Period.compute'
+ * 
+ * @package Piwik_ArchiveProcessing
+ */
+class Piwik_ArchiveProcessing_Period extends Piwik_ArchiveProcessing
+{
+	/**
+	 * Sums all values for the given field names $aNames over the period
+	 * See @archiveNumericValuesGeneral for more information
+	 * 
+	 * @param string|array 
+	 * @return Piwik_ArchiveProcessing_Record_Numeric
+	 * 
+	 */
+	public function archiveNumericValuesSum( $aNames )
+	{
+		return $this->archiveNumericValuesGeneral($aNames, 'sum');
+	}
+	
+	/**
+	 * Get the maximum value for all values for the given field names $aNames over the period
+	 * See @archiveNumericValuesGeneral for more information
+	 * 
+	 * @param string|array 
+	 * @return Piwik_ArchiveProcessing_Record_Numeric
+	 * 
+	 */
+	public function archiveNumericValuesMax( $aNames )
+	{
+		return $this->archiveNumericValuesGeneral($aNames, 'max');
+	}
+	
+	/**
+	 * Given a list of fields names, the method will fetch all their values over the period, and archive them using the given operation.
+	 * 
+	 * For example if $operationToApply = 'sum' and $aNames = array('nb_visits', 'sum_time_visit')
+	 *  it will sum all values of nb_visits for the period (for example give the number of visits for the month by summing the visits of every day)
+	 * 
+	 * @param array|string $aNames Array of strings or string containg the field names to select
+	 * @param string $operationToApply Available operations = sum, max, min 
+	 * @return Piwik_ArchiveProcessing_Record_Numeric Returns the record if $aNames is a string, 
+	 *  an array of Piwik_ArchiveProcessing_Record_Numeric indexed by their field names if aNames is an array of strings
+	 */
+	private function archiveNumericValuesGeneral($aNames, $operationToApply)
+	{
+		if(!is_array($aNames))
+		{
+			$aNames = array($aNames);
+		}
+		
+		// fetch the numeric values and apply the operation on them
+		$results = array();
+		foreach($this->archives as $archive)
+		{
+			foreach($aNames as $name)
+			{
+				if(!isset($results[$name]))
+				{
+					$results[$name] = 0;
+				}
+				$valueToSum = $archive->getNumeric($name);
+				
+				if($valueToSum !== false)
+				{
+					switch ($operationToApply) {
+						case 'sum':
+							$results[$name] += $valueToSum;	
+							break;
+						case 'max':
+							$results[$name] = max($results[$name], $valueToSum);		
+							break;
+						case 'min':
+							$results[$name] = min($results[$name], $valueToSum);		
+							break;
+						default:
+							throw new Exception("Operation not applicable.");
+							break;
+					}								
+				}
+			}
+		}
+		
+		// build the Record Numeric objects
+		$records = array();
+		foreach($results as $name => $value)
+		{
+			$records[$name] = new Piwik_ArchiveProcessing_Record_Numeric(
+													$name, 
+													$value
+												);
+		}
+		
+		// if asked for only one field to sum
+		if(count($records) == 1)
+		{
+			return $records[$name];
+		}
+		
+		// returns the array of records once summed
+		return $records;
+	}
+	
+	
+	/**
+	 * This powerful method will compute the sum of DataTables over the period for the given fields $aRecordName.
+	 * The resulting DataTable will be then added to queue of data to be recorded in the database.
+	 * It will usually be called in a plugin that listens to the hook 'ArchiveProcessing_Period.compute'
+	 * 
+	 * For example if $aRecordName = 'UserCountry_country' the method will select all UserCountry_country DataTable for the period
+	 * (eg. the 31 dataTable of the last month), sum them, and create the Piwik_ArchiveProcessing_Record_BlobArray so that
+	 * the resulting dataTable is AUTOMATICALLY recorded in the database.
+	 * 
+	 * 
+	 * This method works on recursive dataTable. For example for the 'Actions' it will select all subtables of all dataTable of all the sub periods
+	 *  and get the sum.
+	 * 
+	 * It returns an array that gives information about the "final" DataTable. The array gives for every field name, the number of rows in the 
+	 *  final DataTable (ie. the number of distinct LABEL over the period) (eg. the number of distinct keywords over the last month)
+	 * 
+	 * @param string|array Field name(s) of DataTable to select so we can get the sum 
+	 * @return array  array (
+	 * 					nameTable1 => number of rows, 
+	 *  				nameTable2 => number of rows,
+	 * 				)
+	 */
+	public function archiveDataTable( $aRecordName, $maximumRowsInDataTableLevelZero = null, $maximumRowsInSubDataTable = null )
+	{
+		if(!is_array($aRecordName))
+		{
+			$aRecordName = array($aRecordName);
+		}
+		
+		$nameToCount = array();
+		foreach($aRecordName as $recordName)
+		{
+			$table = $this->getRecordDataTableSum($recordName);
+			
+			$nameToCount[$recordName]['level0'] =  $table->getRowsCount();
+			$nameToCount[$recordName]['recursive'] =  $table->getRowsCountRecursive();
+			
+			$record = new Piwik_ArchiveProcessing_Record_BlobArray($recordName, $table->getSerialized( $maximumRowsInDataTableLevelZero, $maximumRowsInSubDataTable ));
+		}
+		return $nameToCount;
+	}
+	
+	/**
+	 * This method selects all DataTables that have the name $name over the period.
+	 * It calls the appropriate methods that sum all these tables together.
+	 * The resulting DataTable is returned.
+	 *
+	 * @param string $name
+	 * @return Piwik_DataTable
+	 */
+	protected function getRecordDataTableSum( $name )
+	{
+		$table = new Piwik_DataTable;
+		foreach($this->archives as $archive)
+		{
+			$archive->preFetchBlob($name);
+			$datatableToSum = $archive->getDataTable($name);
+			$archive->loadSubDataTables($name, $datatableToSum);
+			$table->addDataTable($datatableToSum);
+			$archive->freeBlob($name);
+		}
+		return $table;
+	}
+	
+	protected function initCompute()
+	{
+		parent::initCompute();
+		$this->archives = $this->loadSubperiodsArchive();
+	}
+
+	/**
+	 * Returns the ID of the archived subperiods.
+	 * 
+	 * @return array Array of the idArchive of the subperiods
+	 */
+	protected function loadSubperiodsArchive()
+	{
+		$periods = array();
+		
+		// we first compute every subperiod of the archive
+		foreach($this->period->getSubperiods() as $period)
+		{
+			$archivePeriod = new Piwik_Archive_Single;
+			$archivePeriod->setSite( $this->site );
+			$archivePeriod->setPeriod( $period );
+			$archivePeriod->prepareArchive();
+			
+			$periods[] = $archivePeriod;
+		}
+		return $periods;
+	}
+	
+	/**
+	 * Main method to process logs for a period. 
+	 * The only logic done here is computing the number of visits, actions, etc.
+	 * 
+	 * All the other reports are computed inside plugins listening to the event 'ArchiveProcessing_Period.compute'.
+	 * See some of the plugins for an example.
+	 * 
+	 * @return void
+	 */
+	protected function compute()
+	{		
+		$this->archiveNumericValuesMax( 'max_actions' ); 
+		$toSum = array(
+			'nb_uniq_visitors', 
+			'nb_visits',
+			'nb_actions', 
+			'sum_visit_length',
+			'bounce_count',
+		);
+		$record = $this->archiveNumericValuesSum($toSum);
+		
+		$this->isThereSomeVisits = ($record['nb_visits']->value != 0);
+		if($this->isThereSomeVisits === false)
+		{
+			return;
+		}
+		
+		Piwik_PostEvent('ArchiveProcessing_Period.compute', $this);		
+	}
+	
+	/**
+	 * Called at the end of the archiving process.
+	 * Does some cleaning job in the database.
+	 * 
+	 * @return void
+	 */
+	protected function postCompute()
+	{
+		parent::postCompute();
+		
+		// we delete records that are now out of date
+		// in the case of a period we delete archives that were archived before the end of the period
+		// and only if they are at least 1 day old (so we don't delete archives computed today that may be stil valid) 
+		$blobTable = $this->tableArchiveBlob->getTableName();
+		$numericTable = $this->tableArchiveNumeric->getTableName();
+		
+		$query = "	DELETE 
+					FROM %s
+					WHERE period > ? 
+						AND DATE(ts_archived) <= date2
+						AND date(ts_archived) < date_sub(CURRENT_DATE(), INTERVAL 1 DAY)
+					";
+		
+		Zend_Registry::get('db')->query(sprintf($query, $blobTable), Piwik::$idPeriods['day']);
+		Zend_Registry::get('db')->query(sprintf($query, $numericTable), Piwik::$idPeriods['day']);
+	}
+	
+}
diff --git a/core/ArchiveProcessing/Record.php b/core/ArchiveProcessing/Record.php
new file mode 100644
index 0000000000..04a402bf27
--- /dev/null
+++ b/core/ArchiveProcessing/Record.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Record.php 444 2008-04-11 13:38:22Z johmathe $
+ * 
+ * @package Piwik_ArchiveProcessing
+ */
+
+require_once "ArchiveProcessing/Record/Blob.php";
+require_once "ArchiveProcessing/Record/BlobArray.php";
+require_once "ArchiveProcessing/Record/Numeric.php";
+require_once "ArchiveProcessing/Record/Manager.php";
+
+
+/**
+ * A Record is a tuple (name, value) to be saved in the database.
+ * At its creation, the record registers itself to the RecordManager. 
+ * The record will then be automatically saved in the DB once the Archiving process is finished. 
+ * 
+ * We have two record types available:
+ * - numeric ; the value will be saved as float in the DB.
+ * 	 It should be used for INTEGER, FLOAT
+ * - blob ; the value will be saved in a binary field in the DB
+ * 	 It should be used for all the other types: PHP variables, STRING, serialized OBJECTS or ARRAYS, etc.
+ * 
+ * @package Piwik_ArchiveProcessing
+ * @subpackage Piwik_ArchiveProcessing_Record
+ */
+abstract class Piwik_ArchiveProcessing_Record
+{
+	public $name;
+	public $value;
+	
+	function __construct( $name, $value)
+	{
+		$this->name = $name;
+		$this->value = $value;
+		Piwik_ArchiveProcessing_Record_Manager::getInstance()->registerRecord($this);
+	}
+	
+	public function delete()
+	{
+		Piwik_ArchiveProcessing_Record_Manager::getInstance()->unregister($this);
+	}
+	
+	public function __destruct()
+	{
+	}
+}
+
+
+
diff --git a/core/ArchiveProcessing/Record/Blob.php b/core/ArchiveProcessing/Record/Blob.php
new file mode 100644
index 0000000000..fd805c31d5
--- /dev/null
+++ b/core/ArchiveProcessing/Record/Blob.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Record.php 180 2008-01-17 16:32:37Z matt $
+ * 
+ * @package Piwik_ArchiveProcessing
+ */
+
+/**
+ * Blob record.
+ * Example: $record = new Piwik_ArchiveProcessing_Record_Blob('visitor_names', serialize(array('piwik-fan', 'php', 'stevie-vibes')));
+ * The value will be compressed before being saved in the DB.
+ * 
+ * @package Piwik_ArchiveProcessing
+ * @subpackage Piwik_ArchiveProcessing_Record
+ */
+class Piwik_ArchiveProcessing_Record_Blob extends Piwik_ArchiveProcessing_Record
+{
+	public $name;
+	public $value;
+	function __construct( $name, $value)
+	{
+		$value = gzcompress($value);
+		parent::__construct( $name, $value );
+	}
+	public function __toString()
+	{
+		return $this->name ." = BLOB";//". gzuncompress($this->value);
+	}
+}
diff --git a/core/ArchiveProcessing/Record/BlobArray.php b/core/ArchiveProcessing/Record/BlobArray.php
new file mode 100644
index 0000000000..4e3165489e
--- /dev/null
+++ b/core/ArchiveProcessing/Record/BlobArray.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Record.php 180 2008-01-17 16:32:37Z matt $
+ * 
+ * @package Piwik_ArchiveProcessing
+ */
+
+/**
+ * Array of blob records.
+ * Useful for easily saving splited data in the DB.
+ *  
+ * Example: $record = new Piwik_ArchiveProcessing_Record_BlobArray(
+ * 				'veryLongBook', 
+ * 				0 => serialize(	array( '1st chapter very long, 6MB of data we dont want to save' )),
+ * 				1 => serialize(	array( '2nd chapter very long, 8MB of data we dont want to save' )),
+ * 				2 => serialize(	array( '3rd chapter very long, 7MB of data we dont want to save' )),
+ * 				3 => serialize(	array( '4th chapter very long, 10MB of data we dont want to save' )),
+ * 		);
+ * 
+ * Will be saved in the DB as 
+ * 		veryLongBook   => X
+ * 		veryLongBook_1 => Y
+ * 		veryLongBook_2 => Z
+ * 		veryLongBook_3 => M
+ * 
+ * @package Piwik_ArchiveProcessing
+ * @subpackage Piwik_ArchiveProcessing_Record
+ */
+class Piwik_ArchiveProcessing_Record_BlobArray extends Piwik_ArchiveProcessing_Record
+{
+
+	function __construct( $name, $aValue)
+	{		
+		foreach($aValue as $id => $value)
+		{
+			// for the parent Table we keep the name
+			// for example for the Table of searchEngines we keep the name 'referer_search_engine'
+			// but for the child table of 'Google' which has the ID = 9 the name would be 'referer_search_engine_9'
+			if($id == 0)
+			{
+				$newName = $name;
+			}
+			else
+			{
+				$newName = $name . '_' . $id;
+			}
+			$record = new Piwik_ArchiveProcessing_Record_Blob( $newName,  $value );
+			
+		}
+	}
+	public function __toString()
+	{
+		throw new Exception( 'Not valid' );
+	}
+	public function delete()
+	{
+		throw new Exception( 'Not valid' );
+	}
+}
diff --git a/core/ArchiveProcessing/Record/Manager.php b/core/ArchiveProcessing/Record/Manager.php
new file mode 100644
index 0000000000..1520492273
--- /dev/null
+++ b/core/ArchiveProcessing/Record/Manager.php
@@ -0,0 +1,109 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Record.php 180 2008-01-17 16:32:37Z matt $
+ * 
+ * @package Piwik_ArchiveProcessing
+ */
+
+/**
+ * Every new Piwik_ArchiveProcessing_Record will be recorded to this manager when created.
+ * At the end of the archiving process, the ArchiveProcessing will getRecords() to save them in the db.
+ * This class is singleton. 
+ *  
+ * @package Piwik_ArchiveProcessing
+ * @subpackage Piwik_ArchiveProcessing_Record
+ */
+class Piwik_ArchiveProcessing_Record_Manager
+{
+	// array of Piwik_ArchiveProcessing_Record to be recorded in the DB
+	protected $records = array();
+	
+	static private $instance = null;
+	protected function __construct()
+	{}
+	
+	/**
+	 * Singleton, returns instance
+	 *
+	 * @return Piwik_ArchiveProcessing_Record_Manager
+	 */
+	static public function getInstance()
+	{
+		if (self::$instance == null)
+		{            
+			$c = __CLASS__;
+			self::$instance = new $c();
+		}
+		return self::$instance;
+	}
+
+	/**
+	 * Method called by Record objects to register themselves.
+	 * All records registered here will be saved in the DB at the end of the archiving process. 
+	 * @return void
+	 */
+	public function registerRecord( $record )
+	{
+		$this->records[$record->name] = $record;
+	}
+	
+	/**
+	 * Removes a record from the Record Manager.
+	 * 
+	 * @return void
+	 */
+	public function unregister( $deleteRecord )
+	{
+		unset($this->records[$deleteRecord->name]);
+	}
+	
+	/**
+	 * Returns a string containing the "name : value" of the record
+	 * @return string
+	 */
+	public function toString()
+	{
+		$str = '';
+		foreach($this->records as $record)
+		{
+			$str .= $record . "<br>\n";
+		}
+		return $str;
+	}
+	
+	/**
+	 * @return string
+	 */
+	public function __toString()
+	{
+		return $this->toString();
+	}
+	
+	/**
+	 * Returns the list of all the records that have to created in the database.
+	 * 
+	 * @return array of Records
+	 */
+	public function getRecords()
+	{
+		return $this->records;
+	}
+	
+	/**
+	 * Delete all records saved in the Manager.
+	 * @return void
+	 */
+	public function deleteAll()
+	{
+		foreach($this->records as $key => $record)
+		{
+			unset($this->records[$key]);
+		}
+		$this->records = array();
+	}
+}
+ 
diff --git a/core/ArchiveProcessing/Record/Numeric.php b/core/ArchiveProcessing/Record/Numeric.php
new file mode 100644
index 0000000000..f7f206e14e
--- /dev/null
+++ b/core/ArchiveProcessing/Record/Numeric.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Record.php 180 2008-01-17 16:32:37Z matt $
+ * 
+ * @package Piwik_ArchiveProcessing
+ */
+
+/**
+ * Numeric record.
+ * Example: $record = new Piwik_ArchiveProcessing_Record_Numeric('nb_visitors_live', 15);
+ * 
+ * @package Piwik_ArchiveProcessing
+ * @subpackage Piwik_ArchiveProcessing_Record
+ */
+class Piwik_ArchiveProcessing_Record_Numeric extends Piwik_ArchiveProcessing_Record
+{	
+	function __construct( $name, $value)
+	{
+		parent::__construct( $name, $value );
+	}
+	
+	public function __toString()
+	{
+		return $this->name ." = ". $this->value;
+	}
+}
diff --git a/core/Auth.php b/core/Auth.php
new file mode 100644
index 0000000000..b3d99d5d26
--- /dev/null
+++ b/core/Auth.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Auth.php 581 2008-07-27 23:07:52Z matt $
+ *
+ * @package Piwik
+ */
+
+interface Piwik_Auth {
+	/**
+	 * @return Piwik_Auth_Result
+	 */
+	public function authenticate();
+}
+
+/**
+ *
+ * @package Piwik
+ */
+class Piwik_Auth_Result extends Zend_Auth_Result
+{
+	/**
+	 * token_auth parameter used to authenticate in the API
+	 *
+	 * @var string
+	 */
+	protected $_token_auth = null;
+	
+	const SUCCESS_SUPERUSER_AUTH_CODE = 42;
+	
+	public function __construct($code, $login, $token_auth, array $messages = array())
+	{
+		// Piwik_Auth_Result::SUCCESS_SUPERUSER_AUTH_CODE, Piwik_Auth_Result::SUCCESS, Piwik_Auth_Result::FAILURE  
+		$this->_code		= (int)$code;
+		$this->_identity	= $login;
+		$this->_messages	= $messages;
+		$this->_token_auth	= $token_auth;
+	}
+	
+    /**
+     * Returns the token_auth to authenticate the current user in the API
+     *
+     * @return string
+     */
+    public function getTokenAuth()
+    {
+    	return $this->_token_auth;
+    }
+}
diff --git a/core/Common.php b/core/Common.php
new file mode 100644
index 0000000000..f02c7abde0
--- /dev/null
+++ b/core/Common.php
@@ -0,0 +1,643 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Common.php 581 2008-07-27 23:07:52Z matt $
+ *
+ * @package Piwik_Helper
+ */
+
+/**
+ * Static class providing functions used by both the CORE of Piwik and the visitor logging engine.
+ *
+ * This is the only external class loaded by the /piwik.php file.
+ * This class should contain only the functions that are used in
+ * both the CORE and the piwik.php statistics logging engine.
+ *
+ * @package Piwik_Helper
+ */
+class Piwik_Common
+{
+	/**
+	 * Const used to map the referer type to an integer in the log_visit table
+	 *
+	 */
+	const REFERER_TYPE_DIRECT_ENTRY		= 1;
+	const REFERER_TYPE_SEARCH_ENGINE	= 2;
+	const REFERER_TYPE_WEBSITE			= 3;
+	const REFERER_TYPE_PARTNER			= 4;
+	const REFERER_TYPE_NEWSLETTER		= 5;
+	const REFERER_TYPE_CAMPAIGN			= 6;
+
+	/**
+	 * Flag used with htmlspecialchar
+	 * See php.net/htmlspecialchars
+	 *
+	 */
+	const HTML_ENCODING_QUOTE_STYLE		= ENT_COMPAT;
+
+
+	/**
+	 * Returns the path and query part from a URL.
+	 * Eg. http://piwik.org/test/index.php?module=CoreHome will return /test/index.php?module=CoreHome
+	 *
+	 * @param string $url either http://piwik.org/test or /
+	 * @return string
+	 */
+	static function getPathAndQueryFromUrl($url)
+	{
+		$parsedUrl = parse_url( $url );
+
+		$result = '';
+
+		if(isset($parsedUrl['path']))
+		{
+			$result .= substr($parsedUrl['path'], 1);
+		}
+
+		if(isset($parsedUrl['query']))
+		{
+			$result .= '?'.$parsedUrl['query'];
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Returns the value of a GET parameter $parameter in an URL query $urlQuery
+	 *
+	 * @param string $urlQuery result of parse_url()['query'] and htmlentitied (& is &amp;) eg. module=test&amp;action=toto or ?page=test
+	 * @param string $param
+	 *
+	 * @return string|bool Parameter value if found (can be the empty string!), false if not found
+	 */
+	static public function getParameterFromQueryString( $urlQuery, $parameter)
+	{
+		$nameToValue = self::getArrayFromQueryString($urlQuery);
+
+		if(isset($nameToValue[$parameter]))
+		{
+			return $nameToValue[$parameter];
+		}
+		return false;
+	}
+
+	/**
+	 * Returns an URL query string in an array format
+	 * The input query string should be htmlspecialchar'ed
+	 *
+	 * @param string urlQuery
+	 * @return array array( param1=> value1, param2=>value2)
+	 */
+	static public function getArrayFromQueryString( $urlQuery )
+	{
+		if(strlen($urlQuery) == 0)
+		{
+			return array();
+		}
+		if($urlQuery[0] == '?')
+		{
+			$urlQuery = substr($urlQuery, 1);
+		}
+
+		$separator = '&amp;';
+
+		$urlQuery = $separator . $urlQuery;
+		//		$urlQuery = str_replace(array('%20'), ' ', $urlQuery);
+		$refererQuery = trim($urlQuery);
+
+		$values = explode($separator, $refererQuery);
+
+		$nameToValue = array();
+
+		foreach($values as $value)
+		{
+			if( false !== strpos($value, '='))
+			{
+				$exploded = explode('=',$value);
+				$nameToValue[$exploded[0]] = $exploded[1];
+			}
+		}
+		return $nameToValue;
+	}
+
+	/**
+	 * Returns true if the string is a valid filename
+	 * File names that start with a-Z or 0-9 and contain a-Z, 0-9, underscore(_), dash(-), and dot(.) will be accepted.
+	 * File names beginning with anything but a-Z or 0-9 will be rejected (including .htaccess for example).
+	 * File names containing anything other than above mentioned will also be rejected (file names with spaces won't be accepted).
+	 *
+	 * @param string filename
+	 * @return bool
+	 *
+	 */
+	static public function isValidFilename($filename)
+	{
+		return (false !== ereg("(^[a-zA-Z0-9]+([a-zA-Z\_0-9\.-]*))$" , $filename));
+	}
+	/**
+	 * Returns true if the string passed may be a URL.
+	 * We don't need a precise test here because the value comes from the website
+	 * tracked source code and the URLs may look very strange.
+	 *
+	 * @param string $url
+	 * @return bool
+	 */
+	static function isLookLikeUrl( $url )
+	{
+		return ereg('^(ftp|news|http|https)?://[A-Za-z0-9\/_.-?&]*', $url);
+	}
+
+	/**
+	 * Returns the variable after cleaning operations.
+	 * NB: The variable still has to be escaped before going into a SQL Query!
+	 *
+	 * If an array is passed the cleaning is done recursively on all the sub-arrays. \
+	 * The keys of the array are filtered as well!
+	 *
+	 * How this method works:
+	 * - The variable returned has been htmlspecialchars to avoid the XSS security problem.
+	 * - The single quotes are not protected so "Piwik's amazing" will still be "Piwik's amazing".
+	 *
+	 * - Transformations are:
+	 * 		- '&' (ampersand) becomes '&amp;'
+	 *  	- '"'(double quote) becomes '&quot;'
+	 * 		- '<' (less than) becomes '&lt;'
+	 * 		- '>' (greater than) becomes '&gt;'
+	 * - It handles the magic_quotes setting.
+	 * - A non string value is returned without modification
+	 *
+	 * @param mixed The variable to be cleaned
+	 * @return mixed The variable after cleaning
+	 */
+	static public function sanitizeInputValues($value)
+	{
+		if(is_numeric($value))
+		{
+			return $value;
+		}
+		elseif(is_string($value))
+		{
+			$value = htmlspecialchars($value, Piwik_Common::HTML_ENCODING_QUOTE_STYLE, 'UTF-8');
+
+			// Undo the damage caused by magic_quotes -- only before php 5.3 as it is now deprecated
+			if ( version_compare(phpversion(), '5.3') === -1 
+				&& get_magic_quotes_gpc())
+			{
+				$value = stripslashes($value);
+			}
+		}
+		elseif (is_array($value))
+		{
+			foreach (array_keys($value) as $key)
+			{
+				$newKey = $key;
+				$newKey = Piwik_Common::sanitizeInputValues($newKey);
+				if ($key != $newKey)
+				{
+					$value[$newKey] = $value[$key];
+					unset($value[$key]);
+				}
+
+				$value[$newKey] = Piwik_Common::sanitizeInputValues($value[$newKey]);
+			}
+		}
+		elseif( !is_null($value)
+		&& !is_bool($value)
+		)
+		{
+			throw new Exception("The value to escape has not a supported type. Value = ".var_export($value, true));
+		}
+		return $value;
+	}
+
+	/**
+	 * Returns a variable from the $_REQUEST superglobal.
+	 * If the variable doesn't have a value or an empty value, returns the defaultValue if specified.
+	 * If the variable doesn't have neither a value nor a default value provided, an exception is raised.
+	 *
+	 * @param string $varName name of the variable
+	 * @param string $varDefault default value. If '', and if the type doesn't match, exit() !
+	 * @param string $varType Expected type, the value must be one of the following: array, numeric, int, integer, string
+	 *
+	 * @exception if the variable type is not known
+	 * @exception if the variable we want to read doesn't have neither a value nor a default value specified
+	 *
+	 * @return mixed The variable after cleaning
+	 */
+	static public function getRequestVar($varName, $varDefault = null, $varType = null, $requestArrayToUse = null)
+	{
+		if(is_null($requestArrayToUse))
+		{
+			$requestArrayToUse = $_REQUEST;
+		}
+
+		$varDefault = self::sanitizeInputValues( $varDefault );
+
+		if($varType == 'int')
+		{
+			// settype accepts only integer
+			// 'int' is simply a shortcut for 'integer'
+			$varType = 'integer';
+		}
+
+		// there is no value $varName in the REQUEST so we try to use the default value
+		if(empty($varName)
+			|| !isset($requestArrayToUse[$varName])
+			|| (	!is_array($requestArrayToUse[$varName])
+				&& strlen($requestArrayToUse[$varName]) === 0
+				)
+		)
+		{
+			if( is_null($varDefault))
+			{
+				throw new Exception("\$varName '$varName' doesn't have value in \$_REQUEST and doesn't have a" .
+						" \$varDefault value");
+			}
+			else
+			{
+				if( !is_null($varType)
+					&& in_array($varType, array('string', 'integer', 'array'))
+				)
+				{
+					settype($varDefault, $varType);
+				}
+				return $varDefault;
+			}
+		}
+
+		// Normal case, there is a value available in REQUEST for the requested varName
+		$value = self::sanitizeInputValues( $requestArrayToUse[$varName] );
+
+		if( !is_null($varType))
+		{
+			$ok = false;
+				
+			if($varType == 'string')
+			{
+				if(is_string($value)) $ok = true;
+			}
+			elseif($varType == 'numeric')
+			{
+				if(is_numeric($value) || $value==(int)$value || $value==(float)$value) $ok = true;
+			}
+			elseif($varType == 'integer')
+			{
+				if(is_int($value) || $value==(int)$value) $ok = true;
+			}
+			elseif($varType == 'float')
+			{
+				if(is_float($value) || $value==(float)$value) $ok = true;
+			}
+			elseif($varType == 'array')
+			{
+				if(is_array($value)) $ok = true;
+			}
+			else
+			{
+				throw new Exception("\$varType specified is not known. It should be one of the following: array, numeric, int, integer, float, string");
+			}
+				
+			// The type is not correct
+			if($ok === false)
+			{
+				if($varDefault === null)
+				{
+					throw new Exception("\$varName '$varName' doesn't have a correct type in \$_REQUEST and doesn't " .
+							"have a \$varDefault value");
+				}
+				// we return the default value with the good type set
+				else
+				{
+					settype($varDefault, $varType);
+					return $varDefault;
+				}
+			}
+		}
+
+		return $value;
+	}
+
+	/**
+	 * Returns a 32 characters long uniq ID
+	 *
+	 * @return string 32 chars
+	 */
+	static public function generateUniqId()
+	{
+		return md5(uniqid(rand(), true));
+	}
+
+	/**
+	 * Returns a 3 letters ID for the operating system part, given a user agent string.
+	 * @see core/DataFiles/OS.php for the list of OS (also available in $GLOBALS['Piwik_Oslist'])
+	 * If the OS cannot be identified in the user agent, returns 'UNK'
+	 * 
+	 * @param string $userAgent
+	 * 
+	 * @return string
+	 */
+	static public function getOs($userAgent)
+	{
+
+		require_once "core/DataFiles/OS.php";
+		$osNameToId = $GLOBALS['Piwik_Oslist'];
+
+		foreach($osNameToId as $key => $value)
+		{
+			if ($ok = ereg($key, $userAgent))
+			{
+				return $value;
+			}
+		}
+		return 'UNK';
+	}
+
+	/**
+	 * Returns the browser information from the user agent string.
+	 * 
+	 * @see core/DataFiles/Browsers.php for the list of OS (also available in $GLOBALS['Piwik_BrowserList'])
+	 * 
+	 * @param string $userAgent
+	 * @return array array(		'name' 			=> '', // 2 letters ID or 'UNK' for an unknown browser
+	 * 							'major_number' 	=> '', // 2 in firefox 2.0.12
+	 * 							'minor_number' 	=> '', // 0 in firefox 2.0.12
+	 * 							'version' 		=> ''  // major_number.minor_number
+	 * 				);
+	 */
+	static public function getBrowserInfo($userAgent)
+	{
+
+		require_once "core/DataFiles/Browsers.php";
+
+		$browsers = $GLOBALS['Piwik_BrowserList'];
+
+		$info = array(
+			'name' 			=> 'UNK',
+			'major_number' 	=> '',
+			'minor_number' 	=> '',
+			'version' 		=> ''
+			);
+
+			$browser = '';
+			foreach($browsers as $key => $value)
+			{
+				if(!empty($browser)) $browser .= "|";
+				$browser .= $key;
+			}
+
+			$results = array();
+
+			// added fix for Mozilla Suite detection
+			if ((preg_match_all("/(mozilla)[\/\sa-z;.0-9-(]+rv:([0-9]+)([.0-9a-z]+)\) gecko\/[0-9]{8}$/i", $userAgent, $results))
+			||	(preg_match_all("/($browser)[\/\sa-z(]*([0-9]+)([\.0-9a-z]+)?/i", $userAgent, $results))
+			)
+		 {
+		 	$count = count($results[0])-1;
+		 		
+		 	// browser code
+		 	$info['name'] = $browsers[strtolower($results[1][$count])];
+		 		
+		 	// majeur version number (7 in mozilla 1.7
+		 	$info['major_number'] = $results[2][$count];
+		 		
+		 	// is an minor version number ? If not, 0
+		 	$match = array();
+		 		
+		 	preg_match('/([.\0-9]+)?([\.a-z0-9]+)?/i', $results[3][$count], $match);
+		 		
+		 	if(isset($match[1]))
+		 	{
+		 		// find minor version number (7 in mozilla 1.7, 9 in firefox 0.9.3)
+		 		$info['minor_number'] = substr($match[1], 0, 2);
+		 	}
+		 	else
+		 	{
+		 		$info['minor_number'] = '.0';
+		 	}
+		 		
+		 	$info['version'] = $info['major_number'] . $info['minor_number'];
+		 }
+		 return $info;
+	}
+
+
+	/**
+	 * Returns the best possible IP of the current user, in the format A.B.C.D
+	 *
+	 * @return string ip
+	 */
+	static public function getIp()
+	{
+		if(isset($_SERVER['HTTP_CLIENT_IP'])
+		&& ($ip = Piwik_Common::getFirstIpFromList($_SERVER['HTTP_CLIENT_IP']))
+		&& strpos($ip, "unknown") === false)
+		{
+			return $ip;
+		}
+		elseif(isset($_SERVER['HTTP_X_FORWARDED_FOR'])
+		&& $ip = Piwik_Common::getFirstIpFromList($_SERVER['HTTP_X_FORWARDED_FOR'])
+		&& isset($ip)
+		&& !empty($ip)
+		&& strpos($ip, "unknown")===false )
+		{
+			return $ip;
+		}
+		elseif( isset($_SERVER['HTTP_CLIENT_IP'])
+		&& strlen( Piwik_Common::getFirstIpFromList($_SERVER['HTTP_CLIENT_IP']) ) != 0 )
+		{
+			return Piwik_Common::getFirstIpFromList($_SERVER['HTTP_CLIENT_IP']);
+		}
+		else if( isset($_SERVER['HTTP_X_FORWARDED_FOR'])
+		&& strlen ($ip = Piwik_Common::getFirstIpFromList($_SERVER['HTTP_X_FORWARDED_FOR'])) != 0)
+		{
+			return $ip;
+		}
+		elseif(isset($_SERVER['REMOTE_ADDR']))
+		{
+			return Piwik_Common::getFirstIpFromList($_SERVER['REMOTE_ADDR']);	
+		}
+		else
+		{
+			return '0.0.0.0';	
+		}
+	}
+
+
+	/**
+	 * Returns the first element of a comma separated list of IPs
+	 *
+	 * @param string $ip
+	 *
+	 * @return string first element before ','
+	 */
+	static private function getFirstIpFromList($ip)
+	{
+		$p = strpos($ip, ',');
+		if($p!==false)
+		{
+			return trim(Piwik_Common::sanitizeInputValues(substr($ip, 0, $p)));
+		}
+		return trim(Piwik_Common::sanitizeInputValues($ip));
+	}
+
+
+	/**
+	 * Returns the continent of a given country
+	 *
+	 * @param string Country 2 letters isocode
+	 *
+	 * @return string Continent (3 letters code : afr, asi, eur, amn, ams, oce)
+	 */
+	static public function getContinent($country)
+	{
+		require_once "core/DataFiles/Countries.php";
+
+		$countryList = $GLOBALS['Piwik_CountryList'];
+
+		if(isset($countryList[$country][0]))
+		{
+			return $countryList[$country][0];
+		}
+		else
+		{
+			return 'unk';
+		}
+	}
+
+	/**
+	 * Returns the visitor country based only on the Browser 'accepted language' information
+	 *
+	 * @param string $lang browser lang
+	 *
+	 * @return string 2 letters ISO code 
+	 */
+	static public function getCountry( $lang )
+	{
+		require_once "core/DataFiles/Countries.php";
+
+		$countryList = $GLOBALS['Piwik_CountryList'];
+
+		$replaceLangCodeByCountryCode = array(
+		// replace cs language (Serbia Montenegro country code) with czech country code
+			'cs' => 'cz',
+		// replace sv language (El Salvador country code) with sweden country code
+			'sv' => 'se',
+		// replace fa language (Unknown country code) with Iran country code
+			'fa' => 'ir',
+		// replace ja language (Unknown country code) with japan country code
+			'ja' => 'jp',
+		// replace ko language (Unknown country code) with corée country code
+			'ko' => 'kr',
+		// replace he language (Unknown country code) with Israel country code
+			'he' => 'il',
+		// replace da language (Unknown country code) with Danemark country code
+			'da' => 'dk',
+		// replace gb code with UK country code
+			'gb' => 'uk',
+		);
+
+
+		if(empty($lang) || strlen($lang) < 2)
+		{
+			return 'xx';
+		}
+
+		$lang = str_replace(	array_keys($replaceLangCodeByCountryCode),
+		array_values($replaceLangCodeByCountryCode),
+		$lang
+		);
+
+		// Ex: "fr"
+		if(strlen($lang) == 2)
+		{
+			if(isset($countryList[$lang]))
+			{
+				return $lang;
+			}
+		}
+
+		// when comma
+		$offcomma = strpos($lang, ',');
+
+		if($offcomma == 2)
+		{
+			// in 'fr,en-us', keep first two chars
+			$domain = substr($lang, 0, 2);
+			if(isset($countryList[$domain]))
+			{
+				return $domain;
+			}
+
+			// catch the second language Ex: "fr" in "en,fr"
+			$domain = substr($lang, 3, 2);
+			if(isset($countryList[$domain]))
+			{
+				return $domain;
+			}
+		}
+
+		// detect second code Ex: "be" in "fr-be"
+		$off = strpos($lang, '-');
+		if($off!==false)
+		{
+			$domain = substr($lang, $off+1, 2);
+				
+			if(isset($countryList[$domain]))
+			{
+				return $domain;
+			}
+		}
+
+		// catch the second language Ex: "fr" in "en;q=1.0,fr;q=0.9"
+		if(preg_match("/^[a-z]{2};q=[01]\.[0-9],(?P<domain>[a-z]{2});/", $lang, $parts))
+		{
+			$domain = $parts['domain'];
+
+			if(isset($GLOBALS['countryList'][$domain][0]))
+			{
+				return $domain;
+			}
+		}
+
+		// finally try with the first ever langage code
+		$domain = substr($lang, 0, 2);
+		if(isset($countryList[$domain]))
+		{
+			return $domain;
+		}
+
+		// at this point we really can't guess the country
+		return 'xx';
+	}
+	
+	
+	/**
+	 * Generate random string 
+	 *
+	 * @param string $length string length
+	 * @param string $alphabet characters allowed in random string
+	 *
+	 * @return string random string with given length
+	 */	
+	public static function getRandomString($length = 16, $alphabet = "abcdefghijklmnoprstuvwxyz0123456789")
+	{
+		$chars = $alphabet;
+		$str = '';
+
+		list($usec, $sec) = explode(" ", microtime());
+		$seed = ((float)$sec+(float)$usec)*100000;
+		mt_srand($seed);
+		
+		for($i = 0; $i < $length; $i++)
+		{
+			$rand_key = mt_rand(0, strlen($chars)-1);
+			$str  .= substr($chars, $rand_key, 1);
+		}
+		return str_shuffle($str);
+	}
+}
+
+
diff --git a/core/Config.php b/core/Config.php
new file mode 100644
index 0000000000..3c6d27d9ff
--- /dev/null
+++ b/core/Config.php
@@ -0,0 +1,223 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Config.php 546 2008-07-02 07:13:16Z matt $
+ * 
+ * @package Piwik_Helper
+ */
+
+require_once "Zend/Config/Ini.php";
+require_once "Zend/Registry.php";
+
+/**
+ * This class is used to access configuration files values.
+ * You can also set these values, the updated configuration files will be written at the end of the script execution.
+ * 
+ * Example reading a value from the configuration file:
+ * 	$minValue = Zend_Registry::get('config')->General->minimum_memory_limit;
+ * 
+ * will read the value minimumMemoryLimit under the [General] section of the config file
+ * 
+ * @package Piwik_Helper
+ */
+class Piwik_Config
+{
+	/**
+	 * When the user modifies the configuration file and there is one value missing, we suggest the default config file
+	 *
+	 * @var string
+	 */
+	protected $urlToPiwikHelpMissingValueInConfigurationFile = 
+		'http://dev.piwik.org/trac/browser/trunk/config/global.ini.php?format=raw';
+
+	protected $defaultConfig 				= null;
+	protected $userConfig 					= null;
+	protected $pathIniFileUserConfig 		= null;
+	protected $pathIniFileDefaultConfig 	= null;
+	protected $configFileUpdated 			= false;
+	public    $doWriteFileWhenUpdated		= true;
+	
+	/**
+	 * Storing the correct cwd() because the value is not correct in the destructor
+	 * "The working directory in the script shutdown phase can be different with some SAPIs (e.g. Apache)."
+	 * 
+	 * @see http://bugs.php.net/bug.php?id=34206
+	 */
+	protected $correctCwd;
+	
+	/**
+	 * Returns default relative path for configuration file
+	 *
+	 * @return string
+	 */
+	static public function getDefaultUserConfigPath()
+	{
+		return 'config/config.ini.php';
+	}
+
+	/**
+	 * Builds the Config object, given the optional path for the user INI file
+	 * If not specified, it will use the default path
+	 *
+	 * @param string $pathIniFileUserConfig
+	 */
+	function __construct($pathIniFileUserConfig = null)
+	{
+		Zend_Registry::set('config', $this);
+		
+		$this->pathIniFileDefaultConfig = 'config/global.ini.php';
+		if(is_null($pathIniFileUserConfig))
+		{	
+			$this->pathIniFileUserConfig = self::getDefaultUserConfigPath();
+		}
+		else
+		{
+			$this->pathIniFileUserConfig = $pathIniFileUserConfig;
+		}
+		
+		$this->defaultConfig = new Zend_Config_Ini($this->pathIniFileDefaultConfig, null, true);
+		
+		if(!Zend_Loader::isReadable($this->pathIniFileUserConfig))
+		{
+			throw new Exception("The configuration file {$this->pathIniFileUserConfig} has not been found.");
+		}
+		$this->userConfig = new Zend_Config_Ini($this->pathIniFileUserConfig, null, true);
+		
+		// see http://bugs.php.net/bug.php?id=34206
+		$this->correctCwd = getcwd();
+	}
+	
+	/**
+	 * At the script shutdown, we save the new configuration file, if the user has set some values 
+	 *
+	 */
+	function __destruct()
+	{
+		if($this->configFileUpdated === true 
+			&& $this->doWriteFileWhenUpdated === true)
+		{
+			$configFile = "; <?php exit; ?> DO NOT REMOVE THIS LINE\n";
+			$configFile .= "; file automatically generated during the piwik installation process (and updated later by some other plugins)\n";
+			
+			foreach($this->userConfig as $section => $arraySection)
+			{
+				$arraySection = $arraySection->toArray();
+				$configFile .= "[$section]\n";
+				foreach($arraySection as $name => $value)
+				{
+					if(is_numeric($name))
+					{
+						$name = $section;
+						$value = array($value);
+					}
+					
+					if(is_array($value))
+					{
+						foreach($value as $currentValue)
+						{
+							$configFile .= $name."[] = $currentValue\n";
+						}
+					}
+					else
+					{	
+						// hack: we add " " around the password because when requesting this data using Zend_Config
+						// the toArray removes the " around the value
+						if( ($section == 'database' || $section == 'database_tests')
+							&& $name == 'password')
+						{
+							$value = '"'.$value.'"';	
+						}
+						
+						$configFile .= $name." = $value\n";						
+					}
+				}
+				$configFile .= "\n";
+			}
+
+			chdir($this->correctCwd);
+			file_put_contents($this->getDefaultUserConfigPath(), $configFile );
+		}
+	}
+	
+	/**
+	 * If called, we use the "testing" environment, which means using the database_tests and log_tests sections 
+	 * for DB & Log configuration.
+	 * 
+	 * @return void
+	 *
+	 */
+	public function setTestEnvironment()
+	{
+		$this->database = $this->database_tests;
+		$this->log = $this->log_tests;
+	}
+	
+	/**
+	 * Called when setting configuration values eg. 
+	 * 	Zend_Registry::get('config')->superuser = $_SESSION['superuser_infos'];
+	 *
+	 * The values will be saved in the configuration file at the end of the script @see __destruct()
+	 * 
+	 * @param string $name
+	 * @param mixed $value
+	 */
+	public function __set($name, $value)
+	{
+		$this->checkWritePermissionOnFile();
+		if(!is_null($this->userConfig))
+		{
+			if($this->userConfig->$name != $value)
+			{
+				$this->configFileUpdated = true;
+			}
+			$this->userConfig->$name = $value;
+		}
+		else
+		{
+			$this->defaultConfig->$name = $value;
+		}
+	}
+	
+	protected function checkWritePermissionOnFile() 
+	{
+		static $enoughPermission = null;
+		
+		if(is_null($enoughPermission))
+		{
+			if($this->doWriteFileWhenUpdated)
+			{
+				Piwik_FrontController::checkDirectoriesWritableOrDie( array('/config') );
+			}
+			$enoughPermission = true;
+		}
+		return $enoughPermission;
+	}
+	
+	/**
+	 * Called when getting a configuration value, eg. 	Zend_Registry::get('config')->superuser->login
+	 *
+	 * @param string $name
+	 * @return mixed value 
+	 * 
+	 * @throws exception if the value was not found in the configuration file
+	 */
+	public function __get($name)
+	{
+		if( !is_null($this->userConfig)
+		&& null !== ($valueInUserConfig = $this->userConfig->$name))
+		{
+			return $valueInUserConfig;
+		}
+		if(null !== ($valueInDefaultConfig = $this->defaultConfig->$name))
+		{
+			return $valueInDefaultConfig;
+		}
+
+		throw new Exception("The configuration parameter $name couldn't be found in your configuration file.
+						<br>Try to replace your default configuration file ({$this->pathIniFileDefaultConfig}) with 
+					the <a href='".$this->urlToPiwikHelpMissingValueInConfigurationFile."'>default piwik configuration file</a> ");
+	}
+}
diff --git a/core/Controller.php b/core/Controller.php
new file mode 100644
index 0000000000..de1a1b2d53
--- /dev/null
+++ b/core/Controller.php
@@ -0,0 +1,270 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Controller.php 561 2008-07-21 00:00:35Z matt $
+ * 
+ * @package Piwik
+ */
+
+/**
+ * Parent class of all plugins Controllers (located in /plugins/PluginName/Controller.php
+ * It defines some helper functions controllers can use.
+ * 
+ * @package Piwik
+ */
+abstract class Piwik_Controller
+{
+	/**
+	 * Plugin name, eg. Referers
+	 * @var string
+	 */
+	protected $pluginName;
+	
+	/**
+	 * Date string
+	 * 
+	 * @var string
+	 */
+	protected $strDate;
+	
+	/**
+	 * Piwik_Date object or null if the requested date is a range
+	 * 
+	 * @var Piwik_Date|null 
+	 */
+	protected $date;
+	
+	/**
+	 * Builds the controller object, reads the date from the request, extracts plugin name from 
+	 *
+	 */
+	function __construct()
+	{
+		$aPluginName = explode('_', get_class($this));
+		$this->pluginName = $aPluginName[1];
+		$this->strDate = Piwik_Common::getRequestVar('date', 'yesterday','string');
+		
+		// the date looks like YYYY-MM-DD we can build it
+		try{
+			$this->date = Piwik_Date::factory($this->strDate);
+			$this->strDate = $this->date->toString();
+		} catch(Exception $e){
+			// the date looks like YYYY-MM-DD,YYYY-MM-DD or other format
+			// case the date looks like a range
+			$this->date = null;
+		}
+	}
+	
+	/**
+	 * Returns the name of the default method that will be called 
+	 * when visiting: index.php?module=PluginName without the action parameter
+	 * 
+	 * @return string
+	 */
+	function getDefaultAction()
+	{
+		return 'index';
+	}
+	
+	/**
+	 * Given an Object implementing Piwik_iView interface, we either:
+	 * - echo the output of the rendering if fetch = false
+	 * - returns the output of the rendering if fetch = true
+	 *
+	 * @param Piwik_ViewDataTable $view
+	 * @param bool $fetch
+	 * @return string|void
+	 */
+	protected function renderView( Piwik_ViewDataTable $view, $fetch)
+	{
+		$view->main();
+		$rendered = $view->getView()->render();
+		if($fetch)
+		{
+			return $rendered;
+		}
+		echo $rendered;
+	}
+	
+	/**
+	 * Returns a ViewDataTable object of an Evolution graph 
+	 * for the last30 days/weeks/etc. of the current period, relative to the current date.
+	 *
+	 * @param string $currentModuleName
+	 * @param string $currentControllerAction
+	 * @param string $apiMethod
+	 * @return Piwik_ViewDataTable_GenerateGraphHTML_ChartEvolution
+	 */
+	protected function getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod)
+	{
+		require_once "ViewDataTable/GenerateGraphHTML.php";
+		$view = Piwik_ViewDataTable::factory('graphEvolution');
+		$view->init( $currentModuleName, $currentControllerAction, $apiMethod );
+		
+		// if the date is not yet a nicely formatted date range ie. YYYY-MM-DD,YYYY-MM-DD we build it
+		// otherwise the current controller action is being called with the good date format already so it's fine
+		// see constructor
+		if( !is_null($this->date))
+		{
+			$view->setParametersToModify( 
+				$this->getGraphParamsModified( array('date'=>$this->strDate))
+				);
+		}
+		
+		return $view;
+	}
+	
+	
+	/**
+	 * Returns the array of new processed parameters once the parameters are applied.
+	 * For example: if you set range=last30 and date=2008-03-10, 
+	 *  the date element of the returned array will be "2008-02-10,2008-03-10"
+	 * 
+	 * Parameters you can set:
+	 * - range: last30, previous10, etc.
+	 * - date: YYYY-MM-DD, today, yesterday
+	 * - period: day, week, month, year
+	 * 
+	 * @param array  paramsToSet = array( 'date' => 'last50', 'viewDataTable' =>'sparkline' )
+	 */
+	protected function getGraphParamsModified($paramsToSet = array())
+	{
+		if(!isset($paramsToSet['range']))
+		{
+			$range = 'last30';
+		}
+		else
+		{
+			$range = $paramsToSet['range'];
+		}
+		
+		if(!isset($paramsToSet['date']))
+		{
+			$endDate = $this->strDate;
+		}
+		else
+		{
+			$endDate = $paramsToSet['date'];
+		}
+		
+		if(!isset($paramsToSet['period']))
+		{
+			$period = Piwik_Common::getRequestVar('period');
+		}
+		else
+		{
+			$period = $paramsToSet['period'];
+		}
+		
+		$last30Relative = new Piwik_Period_Range($period, $range );
+		
+		$last30Relative->setDefaultEndDate(Piwik_Date::factory($endDate));
+		
+		$paramDate = $last30Relative->getDateStart()->toString() . "," . $last30Relative->getDateEnd()->toString();
+		
+		$params = array_merge($paramsToSet , array(	'date' => $paramDate ) );
+		
+		return $params;
+	}
+	
+	/**
+	 * Returns a numeric value from the API.
+	 * Works only for API methods that originally returns numeric values (there is no cast here)
+	 *
+	 * @param string $methodToCall, eg. Referers.getNumberOfDistinctSearchEngines
+	 * @return int|float
+	 */
+	protected function getNumericValue( $methodToCall )
+	{
+		$requestString = 'method='.$methodToCall.'&format=original';
+		$request = new Piwik_API_Request($requestString);
+		return $request->process();
+	}
+
+	/**
+	 * Returns the current URL to use in a <img src=X> to display a sparkline.
+	 * $action must be the name of a Controller method that requests data using the Piwik_ViewDataTable::factory
+	 * It will automatically build a sparkline by setting the viewDataTable=sparkline parameter in the URL.
+	 * It will also computes automatically the 'date' for the 'last30' days/weeks/etc. 
+	 *
+	 * @param string $action, eg. method name of the controller to call in the img src
+	 * @return string the generated URL
+	 */
+	protected function getUrlSparkline( $action )
+	{
+		$params = $this->getGraphParamsModified( 
+					array(	'viewDataTable' => 'sparkline', 
+							'action' => $action,
+							'module' => $this->pluginName)
+				);
+		$url = Piwik_Url::getCurrentQueryStringWithParametersModified($params);
+		return $url;
+	}
+	
+	protected function setGeneralVariablesView($view)
+	{
+		$oDate = Piwik_Date::factory($this->strDate);
+		$localizedDateFormat = Piwik_Translate('CoreHome_LocalizedDateFormat');
+		$view->prettyDate = $oDate->getLocalized($localizedDateFormat);
+		$view->date = $this->strDate;
+		
+		try {
+			$currentPeriod = Piwik_Common::getRequestVar('period');
+			$view->idSite = Piwik_Common::getRequestVar('idSite');
+		} catch(Exception $e) {
+			self::redirectToIndex(Piwik::getModule(), Piwik::getAction());
+		}
+		$otherPeriodsAvailable = array('day', 'week', 'month', 'year');
+		$otherPeriodsNames = array(
+			'day' => Piwik_Translate('CoreHome_PeriodDay'),
+			'week' => Piwik_Translate('CoreHome_PeriodWeek'),
+			'month' => Piwik_Translate('CoreHome_PeriodMonth'),
+			'year' => Piwik_Translate('CoreHome_PeriodYear')
+			);
+		
+		$found = array_search($currentPeriod,$otherPeriodsAvailable);
+		if($found !== false)
+		{
+			unset($otherPeriodsAvailable[$found]);
+		}
+		
+		$view->period = $currentPeriod;
+		$view->otherPeriods = $otherPeriodsAvailable;
+		$view->periodsNames = $otherPeriodsNames;
+	}
+	
+	function redirectToIndex($moduleToRedirect = 'CoreHome', $actionToRedirect = 'index')
+	{
+		$sitesId = Piwik_SitesManager_API::getSitesIdWithAtLeastViewAccess();
+		if(!empty($sitesId))
+		{
+			$firstSiteId = $sitesId[0];
+			$firstSite = new Piwik_Site($firstSiteId);
+			if ($firstSite->getCreationDate()->isToday()) 
+			{
+				$defaultDate = 'today';
+			}
+			else
+			{
+				$defaultDate = Zend_Registry::get('config')->General->default_day;
+			}
+			header("Location:index.php?module=".$moduleToRedirect."&action=".$actionToRedirect."&idSite=$firstSiteId&period=day&date=$defaultDate");
+		}
+		else
+		{
+			if(($currentLogin = Piwik::getCurrentUserLogin()) != 'anonymous')
+			{
+				Piwik_ExitWithMessage( sprintf(Piwik_Translate('CoreHome_NoPrivileges'),$currentLogin).
+				"<br /><br />&nbsp;&nbsp;&nbsp;<b><a href='?module=Login&amp;action=logout'>&rsaquo; ".Piwik_Translate('General_Logout')."</a></b><br />");
+			}
+			else
+			{
+				Piwik_FrontController::dispatch('Login');
+			}
+		}
+		exit;
+	}
+}
\ No newline at end of file
diff --git a/core/Cookie.php b/core/Cookie.php
new file mode 100644
index 0000000000..7bcbe7b92c
--- /dev/null
+++ b/core/Cookie.php
@@ -0,0 +1,282 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Cookie.php 444 2008-04-11 13:38:22Z johmathe $
+ * 
+ * @package Piwik_Helper
+ */
+
+
+/**
+ * Simple class to handle the cookies:
+ * - read a cookie values
+ * - edit an existing cookie and save it
+ * - create a new cookie, set values, expiration date, etc. and save it
+ * 
+ * @package Piwik_Helper
+ */
+class Piwik_Cookie
+{
+	/**
+	 * The name of the cookie 
+	 */
+	protected $name = null;
+	
+	/**
+	 * The expire time for the cookie (expressed in UNIX Timestamp)
+	 */
+	protected $expire = null;
+	
+	/**
+	 * The content of the cookie
+	 */
+	protected $value = array();
+	
+	/**
+	 * The character used to separate the tuple name=value in the cookie 
+	 */
+	const VALUE_SEPARATOR = ':';
+	
+	/**
+	 * Instanciate a new Cookie object and tries to load the cookie content if the cookie
+	 * exists already.
+	 * 
+	 * @param string cookie Name
+	 * @param int The timestamp after which the cookie will expire, eg time() + 86400
+	 */
+	public function __construct( $cookieName, $expire = null)
+	{
+		$this->name = $cookieName;
+		
+		if(is_null($expire)
+			|| !is_numeric($expire)
+			|| $expire <= 0)
+		{
+			$this->expire = $this->getDefaultExpire();
+		}
+		
+		if($this->isCookieFound())
+		{
+			$this->loadContentFromCookie();
+		}
+	}
+	
+	/**
+	 * Returns true if the visitor already has the cookie.
+	 * @return bool 
+	 */
+	public function isCookieFound()
+	{
+		return isset($_COOKIE[$this->name]);
+	}
+	
+	/**
+	 * Returns the default expiry time, 10 years
+	 * @return int Timestamp in 10 years
+	 */
+	protected function getDefaultExpire()
+	{
+		return time() + 86400*365*10;
+	}	
+	
+	/**
+	 * We don't use the setcookie function because it is buggy for some PHP versions.
+	 * 
+	 * Taken from http://php.net/setcookie
+	 */
+	protected function setCookie($Name, $Value, $Expires, $Path = '', $Domain = '', $Secure = false, $HTTPOnly = false)
+	{
+		if (!empty($Domain))
+		{	
+			// Fix the domain to accept domains with and without 'www.'.
+			if (strtolower(substr($Domain, 0, 4)) == 'www.')  $Domain = substr($Domain, 4);
+			
+			$Domain = '.' . $Domain;
+			
+			// Remove port information.
+			$Port = strpos($Domain, ':');
+			if ($Port !== false)  $Domain = substr($Domain, 0, $Port);
+		}
+		
+		$header = 'Set-Cookie: ' . rawurlencode($Name) . '=' . rawurlencode($Value)
+					 . (empty($Expires) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', $Expires) . ' GMT')
+					 . (empty($Path) ? '' : '; path=' . $Path)
+					 . (empty($Domain) ? '' : '; domain=' . $Domain)
+					 . (!$Secure ? '' : '; secure')
+					 . (!$HTTPOnly ? '' : '; HttpOnly');
+		 
+		 header($header, false);
+	}
+	
+	/**
+	 * We set the privacy policy header
+	 * 
+	 * @return void
+	 */
+	protected function setP3PHeader()
+	{
+		header("P3P: CP='OTI DSP COR NID STP UNI OTPa OUR'");
+	}
+	
+	/**
+	 * Delete the cookie
+	 * 
+	 * @return void
+	 */
+	public function delete()
+	{
+		$this->setP3PHeader();
+		setcookie($this->name, false, time() - 86400);
+	}
+	
+	/**
+	 * Saves the cookie (set the Cookie header).
+	 * You have to call this method before sending any text to the browser or you would get the 
+	 * "Header already sent" error.
+	 */
+	public function save()
+	{
+		$this->setP3PHeader();
+		$this->setCookie( $this->name, $this->generateContentString(), $this->expire);
+	}
+	
+	/**
+	 * Load the cookie content into a php array.
+	 * Parses the cookie string to extract the different variables.
+	 * Unserialize the array when necessary.
+	 * Decode the non numeric values that were base64 encoded.
+	 * 
+	 * @return void
+	 */
+	protected function loadContentFromCookie()
+	{
+		$cookieStr = $_COOKIE[$this->name];
+		
+		$values = explode( self::VALUE_SEPARATOR, $cookieStr);
+		foreach($values as $nameValue)
+		{
+			$equalPos = strpos($nameValue, '=');
+			$varName = substr($nameValue,0,$equalPos);
+			$varValue = substr($nameValue,$equalPos+1);
+			
+			// no numeric value are base64 encoded so we need to decode them
+			if(!is_numeric($varValue))
+			{
+				$varValue = base64_decode($varValue);
+
+				// some of the values may be serialized array so we try to unserialize it
+				if( ($arrayValue = @unserialize($varValue)) !== false
+					// we set the unserialized version only for arrays as you can have set a serialized string on purpose
+					&& is_array($arrayValue)
+					)
+				{
+					$varValue = $arrayValue;
+				}
+			}
+			
+			$this->set($varName, $varValue);
+		}
+	}
+	
+	/**
+	 * Returns the string to save in the cookie from the $this->value array of values.
+	 * It goes through the array and generates the cookie content string.
+	 * @return string Cookie content
+	 */
+	protected function generateContentString()
+	{
+		$cookieStr = '';
+		foreach($this->value as $name=>$value)
+		{
+			if(is_array($value))
+			{
+				$value = serialize($value);
+			}
+			$value = base64_encode($value);
+			
+			$cookieStr .= "$name=$value" . self::VALUE_SEPARATOR;
+		}
+		$cookieStr = substr($cookieStr, 0, strlen($cookieStr)-1);
+		return $cookieStr;
+	}
+	
+	/**
+	 * Registers a new name => value association in the cookie.
+	 * 
+	 * Registering new values is optimal if the value is a numeric value.
+	 * If the value is a string, it will be saved as a base64 encoded string.
+	 * If the value is an array, it will be saved as a serialized and base64 encoded 
+	 * string which is not very good in terms of bytes usage. 
+	 * You should save arrays only when you are sure about their maximum data size.
+	 * A cookie has to stay small and its size shouldn't increase over time!
+	 * 
+	 * @param string Name of the value to save; the name will be used to retrieve this value
+	 * @param string|array|numeric Value to save
+	 * 
+ 	 */
+	public function set( $name, $value )
+	{
+		$name = self::escapeValue($name);
+		$this->value[$name] = $value;
+	}
+	
+	/**
+	 * Returns the value defined by $name from the cookie.
+	 * 
+	 * @param string|integer Index name of the value to return
+	 * @return mixed The value if found, false if the value is not found
+	 */
+	public function get( $name )
+	{
+		$name = self::escapeValue($name);
+		return isset($this->value[$name]) ? self::escapeValue($this->value[$name]) : false;
+	}
+	
+	/**
+	 * Returns an easy to read cookie dump
+	 * 
+	 * @return string The cookie dump
+	 */
+	public function __toString()
+	{
+		$str = "<-- Content of the cookie '{$this->name}' <br>\n";
+		foreach($this->value as $name => $value )
+		{
+			$str .= $name . " = " . var_export($this->get($name), true) . "<br>\n";
+		}
+		$str .= "--> <br>\n";
+		return $str;
+	}
+	
+	/**
+	 * Escape values from the cookie before sending them back to the client 
+	 * (when using the get() method).
+	 * 
+	 * @return mixed The value once cleaned.
+	 */
+	static protected function escapeValue( $value )
+	{
+		return Piwik_Common::sanitizeInputValues($value);
+	}	
+}
+
+
+//$c = new Piwik_Cookie( 'piwik_logstats', 86400);
+//echo $c;
+//$c->set(1,1);
+//$c->set('test',1);
+//$c->set('test2','test=432:gea785');
+//$c->set('test3',array('test=432:gea785'));
+//$c->set('test4',array(array(0=>1),1=>'test'));
+//echo $c;
+//echo "<br>";
+//$v=$c->get('more!');
+//if(empty($v)) $c->set('more!',1);
+//$c->set('more!', array($c->get('more!')));
+//$c->save();
+//$c->delete();
+
+
diff --git a/core/DataFiles/Browsers.php b/core/DataFiles/Browsers.php
new file mode 100644
index 0000000000..9744f7f6e5
--- /dev/null
+++ b/core/DataFiles/Browsers.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Browsers.php 444 2008-04-11 13:38:22Z johmathe $
+ * 
+ * @package Piwik_UserSettings
+ */
+
+/**
+ * Browser list.
+ * If you want to add a new entry, please email us at hello at piwik.org
+ * 
+ */
+if(!isset($GLOBALS['Piwik_BrowserList'] ))
+{
+	$GLOBALS['Piwik_BrowserList'] = array(
+					'msie'							=> 'IE',
+					'microsoft internet explorer'	=> 'IE',
+					'internet explorer'				=> 'IE',
+					'netscape6'						=> 'NS',
+					'netscape'						=> 'NS',
+					'galeon'						=> 'GA',
+					'phoenix'						=> 'PX',
+					'firefox'						=> 'FF',
+					'mozilla firebird'				=> 'FB',
+					'firebird'						=> 'FB',
+					'seamonkey'						=> 'SM',
+					'chimera'						=> 'CH',
+					'camino'						=> 'CA',
+					'safari'						=> 'SF',
+					'k-meleon'						=> 'KM',
+					'mozilla'						=> 'MO',
+					'opera'							=> 'OP',
+					'konqueror'						=> 'KO',
+					'icab'							=> 'IC',
+					'lynx'							=> 'LX',
+					'links'							=> 'LI',
+					'ncsa mosaic'					=> 'MC',
+					'amaya'							=> 'AM',
+					'omniweb'						=> 'OW',
+					'hotjava'						=> 'HJ',
+					'browsex'						=> 'BX',
+					'amigavoyager'					=> 'AV',
+					'amiga-aweb'					=> 'AW',
+					'ibrowse'						=> 'IB',
+					'unknown'						=> 'UNK'
+			);	
+			
+			
+	$GLOBALS['Piwik_BrowserList_IdToLabel'] 
+		= array_map('ucwords',array_flip($GLOBALS['Piwik_BrowserList']));
+	
+	$GLOBALS['Piwik_BrowserList_IdToShortLabel'] = $GLOBALS['Piwik_BrowserList_IdToLabel'];
+	$GLOBALS['Piwik_BrowserList_IdToShortLabel']['IE'] = "IE";
+	$GLOBALS['Piwik_BrowserList_IdToShortLabel']['FB'] = "Firebird";
+}
diff --git a/core/DataFiles/Countries.php b/core/DataFiles/Countries.php
new file mode 100644
index 0000000000..af02827723
--- /dev/null
+++ b/core/DataFiles/Countries.php
@@ -0,0 +1,240 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Countries.php 444 2008-04-11 13:38:22Z johmathe $
+ * 
+ * @package Piwik_UserCountry
+ */
+
+/**
+ * Country code and continent database.
+ * If you want to add a new entry, please email us at hello at piwik.org
+ * 
+ */
+if(!isset($GLOBALS['Piwik_CountryList']))
+{
+	$GLOBALS['Piwik_CountryList'] = array(
+			'xx' => array('unk'),
+			'ac' => array('afr'),
+			'ad' => array('eur'),
+			'ae' => array('asi'),
+			'af' => array('asi'),
+			'ag' => array('ams'),
+			'ai' => array('ams'),
+			'al' => array('eur'),
+			'am' => array('asi'),
+			'an' => array('ams'),
+			'ao' => array('afr'),
+			'aq' => array('aut'),
+			'ar' => array('ams'),
+			'as' => array('oce'),
+			'at' => array('eur'),
+			'au' => array('oce'),
+			'aw' => array('ams'),
+			'az' => array('asi'),
+			'ba' => array('eur'),
+			'bb' => array('ams'),
+			'bd' => array('asi'),
+			'be' => array('eur'),
+			'bf' => array('afr'),
+			'bg' => array('eur'),
+			'bh' => array('asi'),
+			'bi' => array('afr'),
+			'bj' => array('afr'),
+			'bm' => array('ams'),
+			'bn' => array('asi'),
+			'bo' => array('ams'),
+			'br' => array('ams'),
+			'bs' => array('ams'),
+			'bt' => array('asi'),
+			'bw' => array('afr'),
+			'by' => array('eur'),
+			'bz' => array('ams'),
+			'ca' => array('amn'),
+			'cc' => array('oce'),
+			'cd' => array('afr'),
+			'cf' => array('afr'),
+			'cg' => array('afr'),
+			'ch' => array('eur'),
+			'ci' => array('afr'),
+			'ck' => array('asi'),
+			'cl' => array('ams'),
+			'cm' => array('afr'),
+			'cn' => array('asi'),
+			'co' => array('ams'),
+			'cs' => array('eur'),
+			'cr' => array('ams'),
+			'cu' => array('ams'),
+			'cv' => array('afr'),
+			'cy' => array('eur'),
+			'cz' => array('eur'),
+			'de' => array('eur'),
+			'dj' => array('afr'),
+			'dk' => array('eur'),
+			'dm' => array('ams'),
+			'do' => array('ams'),
+			'dz' => array('afr'),
+			'ec' => array('ams'),
+			'ee' => array('eur'),
+			'eg' => array('afr'),
+			'eh' => array('afr'),
+			'er' => array('afr'),
+			'es' => array('eur'),
+			'et' => array('afr'),
+			'fi' => array('eur'),
+			'fj' => array('oce'),
+			'fk' => array('ams'),
+			'fm' => array('oce'),
+			'fr' => array('eur'),
+			'ga' => array('afr'),
+			'gb' => array('eur'),
+			'gd' => array('ams'),
+			'ge' => array('asi'),
+			'gf' => array('ams'),
+			'gg' => array('eur'),
+			'gh' => array('afr'),
+			'gi' => array('afr'),
+			'gl' => array('amn'),
+			'gm' => array('afr'),
+			'gn' => array('afr'),
+			'gp' => array('ams'),
+			'gq' => array('afr'),
+			'gr' => array('eur'),
+			'gs' => array('eur'),
+			'gt' => array('ams'),
+			'gw' => array('afr'),
+			'gy' => array('ams'),
+			'hk' => array('asi'),
+			'hn' => array('ams'),
+			'hr' => array('eur'),
+			'ht' => array('ams'),
+			'hu' => array('eur'),
+			'id' => array('asi'),
+			'ie' => array('eur'),
+			'il' => array('asi'),
+			'in' => array('asi'),
+			'iq' => array('asi'),
+			'ir' => array('asi'),
+			'is' => array('eur'),
+			'it' => array('eur'),
+			'jm' => array('ams'),
+			'jo' => array('asi'),
+			'jp' => array('asi'),
+			'ke' => array('afr'),
+			'kg' => array('asi'),
+			'kh' => array('asi'),
+			'ki' => array('oce'),
+			'km' => array('afr'),
+			'kp' => array('asi'),
+			'kr' => array('asi'),
+			'kw' => array('asi'),
+			'ky' => array('ams'),
+			'kz' => array('asi'),
+			'la' => array('asi'),
+			'lb' => array('asi'),
+			'li' => array('eur'),
+			'lk' => array('asi'),
+			'lr' => array('afr'),
+			'ls' => array('afr'),
+			'lt' => array('eur'),
+			'lu' => array('eur'),
+			'lv' => array('eur'),
+			'ly' => array('afr'),
+			'ma' => array('afr'),
+			'mc' => array('eur'),
+			'md' => array('eur'),
+			'mg' => array('afr'),
+			'mh' => array('oce'),
+			'mk' => array('eur'),
+			'ml' => array('afr'),
+			'mm' => array('asi'),
+			'mn' => array('asi'),
+			'mo' => array('asi'),
+			'mq' => array('ams'),
+			'mr' => array('afr'),
+			'mt' => array('eur'),
+			'mu' => array('afr'),
+			'mv' => array('asi'),
+			'mw' => array('afr'),
+			'mx' => array('ams'),
+			'my' => array('asi'),
+			'mz' => array('afr'),
+			'na' => array('afr'),
+			'nc' => array('oce'),
+			'ne' => array('afr'),
+			'ng' => array('afr'),
+			'ni' => array('ams'),
+			'nl' => array('eur'),
+			'no' => array('eur'),
+			'np' => array('asi'),
+			'nr' => array('oce'),
+			'nz' => array('oce'),
+			'om' => array('asi'),
+			'pa' => array('ams'),
+			'pe' => array('ams'),
+			'pf' => array('oce'),
+			'pg' => array('oce'),
+			'ph' => array('asi'),
+			'pk' => array('asi'),
+			'pl' => array('eur'),
+			'pm' => array('amn'),
+			'pr' => array('ams'),
+			'pt' => array('eur'),
+			'pw' => array('oce'),
+			'py' => array('ams'),
+			'qa' => array('asi'),
+			're' => array('afr'),
+			'ro' => array('eur'),
+			'ru' => array('asi'),
+			'rs' => array('asi'),
+			'rw' => array('afr'),
+			'sa' => array('asi'),
+			'sb' => array('oce'),
+			'sc' => array('afr'),
+			'sd' => array('afr'),
+			'se' => array('eur'),
+			'sg' => array('asi'),
+			'si' => array('eur'),
+			'sk' => array('eur'),
+			'sl' => array('afr'),
+			'sm' => array('eur'),
+			'sn' => array('afr'),
+			'so' => array('afr'),
+			'sr' => array('ams'),
+			'sv' => array('ams'),
+			'sy' => array('asi'),
+			'sz' => array('afr'),
+			'td' => array('afr'),
+			'tg' => array('afr'),
+			'th' => array('asi'),
+			'tj' => array('asi'),
+			'tm' => array('asi'),
+			'tn' => array('afr'),
+			'to' => array('oce'),
+			'tp' => array('oce'),
+			'tr' => array('eur'),
+			'tt' => array('ams'),
+			'tw' => array('asi'),
+			'tz' => array('afr'),
+			'ua' => array('eur'),
+			'ug' => array('afr'),
+			'uk' => array('eur'),
+			'us' => array('amn'),
+			'uy' => array('ams'),
+			'uz' => array('asi'),
+			'va' => array('eur'),
+			've' => array('ams'),
+			'vn' => array('asi'),
+			'vu' => array('oce'),
+			'wf' => array('oce'),
+			'ye' => array('asi'),
+			'yu' => array('eur'),
+			'za' => array('afr'),
+			'zm' => array('afr'),
+			'zw' => array('afr'),
+		);
+}
+
diff --git a/core/DataFiles/OS.php b/core/DataFiles/OS.php
new file mode 100644
index 0000000000..f3e5e816da
--- /dev/null
+++ b/core/DataFiles/OS.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: OS.php 444 2008-04-11 13:38:22Z johmathe $
+ * 
+ * @package Piwik_UserSettings
+ */
+
+/**
+ * Operating systems database.
+ * If you want to add a new entry, please email us at hello at piwik.org
+ * 
+ */
+if(!isset($GLOBALS['Piwik_Oslist']))
+{
+	$GLOBALS['Piwik_Oslist'] = array(
+						'Nintendo Wii'	 => 'WII',
+						'PlayStation Portable' => 'PSP',
+						'PLAYSTATION 3'  => 'PS3',
+						'Windows NT 6.0' => 'WVI',
+						'Windows Vista'  => 'WVI',
+						'Windows NT 5.2' => 'WS3',
+						'Windows Server 2003' => 'WS3',
+						'Windows NT 5.1' => 'WXP',
+						'Windows XP'     => 'WXP',
+						'Win98'          => 'W98',
+						'Windows 98'     => 'W98',
+						'Windows NT 5.0' => 'W2K',
+						'Windows 2000'   => 'W2K',
+						'Windows NT 4.0' => 'WNT',
+						'WinNT'          => 'WNT',
+						'Windows NT'     => 'WNT',
+						'Win 9x 4.90'    => 'WME',
+						'Win 9x 4.90'    => 'WME',
+						'Windows Me'     => 'WME',
+						'Win32'          => 'W95',
+						'Win95'          => 'W95',		
+						'Windows 95'     => 'W95',
+						'Mac_PowerPC'    => 'MAC', 
+						'Mac PPC'        => 'MAC',
+						'PPC'            => 'MAC',
+						'Mac PowerPC'    => 'MAC',
+						'Mac OS'         => 'MAC',
+						'Linux'          => 'LIN',
+						'SunOS'          => 'SOS', 
+						'FreeBSD'        => 'BSD', 
+						'AIX'            => 'AIX', 
+						'IRIX'           => 'IRI', 
+						'HP-UX'          => 'HPX', 
+						'OS/2'           => 'OS2', 
+						'NetBSD'         => 'NBS',
+						'Unknown'        => 'XXX' 
+		);
+		
+		
+	$GLOBALS['Piwik_Oslist_IdToLabel'] = array_flip($GLOBALS['Piwik_Oslist']);
+	
+	$GLOBALS['Piwik_Oslist_IdToShortLabel'] = array(
+		'PS3' => 'PS3',
+		'PSP' => 'PSP',
+		'WII' => 'WII',
+		'WVI' => 'Win Vista',
+		'WS3' => 'Win S2003',
+		'WXP' => 'Win XP',
+		'W98' => 'Win 98',
+		'W2K' => 'Win 2000', 
+		'WNT' => 'Win NT',
+		'WME' => 'Win Me',
+		'W95' => 'Win 95',		
+		'WCE' => 'Win CE',
+		'MAC' => 'Mac OS',
+		'LIN' => 'Linux', 
+		'INC' => 'Inconnu', 
+		'SOS' => 'SunOS', 
+		'BSD' => 'FreeBSD', 
+		'AIX' => 'AIX',
+		'IRI' => 'IRIX', 
+		'HPX' => 'HPX', 
+		'OS2' => 'OS/2', 
+		'NBS' => 'NetBSD',
+		'XXX' => 'Unknown',
+		);
+}
diff --git a/core/DataFiles/SearchEngines.php b/core/DataFiles/SearchEngines.php
new file mode 100644
index 0000000000..c3e9f97b21
--- /dev/null
+++ b/core/DataFiles/SearchEngines.php
@@ -0,0 +1,1080 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: SearchEngines.php 569 2008-07-22 23:12:58Z matt $
+ * 
+ * @package Piwik_Referers
+ */
+/**
+ * Search Engine database
+ * 
+ * ======================================
+ * HOW TO ADD A SEARCH ENGINE TO THE LIST
+ * ======================================
+ * If you want to add a new entry, please email us the information + icon at hello at piwik.org
+ * 
+ * Detail of a line:
+ * Url => array( SearchEngineName, VariableKeyword, [charset used by the search engine])
+ * 
+ * The main search engine URL has to be at the top of the list for the given search Engine.
+ * 
+ * You can add new search engines icons by adding the icon 
+ * in the plugins/Referers/images/SearchEngines directory 
+ * using the format "mainSearchEngineUrl.png". Example: www.google.com.png
+ *  
+ * 
+ */
+if(!isset($GLOBALS['Piwik_SearchEngines'] ))
+{
+	$GLOBALS['Piwik_SearchEngines'] = array(
+	
+		//" "		=> array(" ", " " [, " "]),
+		
+		// 1
+		"1.cz" 						=> array("1.cz", "q", "iso-8859-2"),
+		"www.1.cz" 					=> array("1.cz", "q", "iso-8859-2"),
+		
+		// 1und1
+		"portal.1und1.de"			=> array("1und1", "search"),
+		
+		// 3271
+		"nmsearch.3721.com"			=> array("3271", "p"),
+		"seek.3721.com"				=> array("3271", "p"),
+		
+		// A9
+		"www.a9.com"				=> array("A9", ""),
+		"a9.com"					=> array("A9", ""),
+		
+		// Abacho
+		"search.abacho.com"		=> array("Abacho", "q"),
+		
+		// about
+		"search.about.com"		=> array("About", "terms"),
+		
+		//Acoon
+		"www.acoon.de"			=> array("Acoon", "begriff"),
+		
+		//Acont
+		"acont.de"			=> array("Acont", "query"),
+		
+		//Alexa
+		"www.alexa.com"		        => array("Alexa", "q"),
+		"alexa.com"		        => array("Alexa", "q"),
+		
+		//Alice Adsl
+		"rechercher.aliceadsl.fr"	=> array("Alice Adsl", "qs"),
+		"search.alice.it"		      => array("Alice (Virgilio)", "qt"),
+		
+		//Allesklar
+		"www.allesklar.de"		=> array("Allesklar", "words"),
+		
+		// AllTheWeb 
+		"www.alltheweb.com"		    => array("AllTheWeb", "q"),
+		
+		// all.by
+		"all.by"			=> array("All.by", "query"),
+		
+		// Altavista
+		"www.altavista.com"		=> array("AltaVista", "q"),
+		"listings.altavista.com"        => array("AltaVista", "q"),
+		"www.altavista.de"		=> array("AltaVista", "q"),
+		"altavista.fr"			=> array("AltaVista", "q"),
+		"de.altavista.com"		=> array("AltaVista", "q"),
+		"fr.altavista.com"		=> array("AltaVista", "q"),
+		"es.altavista.com"		=> array("AltaVista", "q"),
+		"www.altavista.fr"		=> array("AltaVista", "q"),
+		"search.altavista.com"		=> array("AltaVista", "q"),
+		"search.fr.altavista.com"	=> array("AltaVista", "q"),
+		"se.altavista.com"		=> array("AltaVista", "q"),
+		"be-nl.altavista.com" 		=> array("AltaVista", "q"),
+		"be-fr.altavista.com" 		=> array("AltaVista", "q"),
+		"it.altavista.com" 		=> array("AltaVista", "q"),
+		"us.altavista.com" 		=> array("AltaVista", "q"),
+		"nl.altavista.com" 		=> array("Altavista", "q"),
+		"ch.altavista.com" 		=> array("AltaVista", "q"),
+		
+		// APOLLO7
+		"www.apollo7.de"		=> array("Apollo7", "query"),
+		"apollo7.de"			=> array("Apollo7", "query"),
+		
+		// AOL
+		"search.aol.com"		=> array("AOL", "query"),
+		"aolsearch.aol.com"		=> array("AOL", "query"),
+		"www.aolrecherche.aol.fr"	=> array("AOL", "q"),
+		"www.aolrecherches.aol.fr" 	=> array("AOL", "query"),
+		"www.aolimages.aol.fr"   	=> array("AOL", "query"),
+		"www.recherche.aol.fr"		=> array("AOL", "q"),
+		"aolsearcht.aol.com"		=> array("AOL", "query"),
+		"find.web.aol.com"		=> array("AOL", "query"),
+		"recherche.aol.ca"		=> array("AOL", "query"),
+		"aolsearch.aol.co.uk"		=> array("AOL", "query"),
+		"search.aol.co.uk"		=> array("AOL", "query"),
+		"aolrecherche.aol.fr"		=> array("AOL", "q"),
+		"sucheaol.aol.de"		=> array("AOL", "q"),
+		"suche.aol.de"			=> array("AOL", "q"),
+		"suche.aolsvc.de"		=> array("AOL", "q"),
+		
+		"aolbusqueda.aol.com.mx"	=> array("AOL", "query"),
+		
+		// Aport
+		"sm.aport.ru"			=> array("Aport", "r"),
+		
+		// Arcor
+		"www.arcor.de"			=> array("Arcor", "Keywords"),
+		
+		// Arianna (Libero.it)
+		"arianna.libero.it" 		=> array("Arianna", "query"),
+		
+		// Ask
+		"www.ask.com"			=> array("Ask", "ask"),
+		"web.ask.com"			=> array("Ask", "ask"),
+		"www.ask.co.uk"			=> array("Ask", "q"),
+		"uk.ask.com"			=> array("Ask", "q"),
+		"fr.ask.com"			=> array("Ask", "q"),
+		"de.ask.com"			=> array("Ask", "q"),
+		"es.ask.com"			=> array("Ask", "q"),
+		"it.ask.com"			=> array("Ask", "q"),
+		"nl.ask.com"			=> array("Ask", "q"),
+		"ask.jp"			=> array("Ask", "q"),
+		
+		// Atlas
+		"search.atlas.cz" 		=> array("Atlas", "q", "windows-1250"),
+		
+		// Austronaut
+		"www2.austronaut.at"		=> array("Austronaut", "begriff"),
+		
+		// Baidu
+		"www.baidu.com"			=> array("Baidu", "wd"),
+		"www1.baidu.com"		=> array("Baidu", "wd"),
+		
+		// BBC
+		"search.bbc.co.uk"	        => array("BBC", "q"),
+		
+		// Bellnet
+		"www.suchmaschine.com"		 => array("Bellnet", "suchstr"),
+		
+		// Biglobe
+		"cgi.search.biglobe.ne.jp"	=> array("Biglobe", "q"),
+		
+		// Bild
+		"www.bild.t-online.de"	        => array("Bild.de (enhanced by Google)", "query"),
+		
+		//Blogdigger
+		"www.blogdigger.com"		=> array("Blogdigger","q"),
+		
+		//Bloglines
+		"www.bloglines.com"		=> array("Bloglines","q"),
+		
+		//Blogpulse
+		"www.blogpulse.com"		=> array("Blogpulse","query"),
+		
+		//Bluewin
+		"search.bluewin.ch"		=> array("Bluewin","query"),
+		
+		// Caloweb
+		"www.caloweb.de"		=> array("Caloweb", "q"),
+		
+		// Cegetel (Google)
+		"www.cegetel.net" 		=> array("Cegetel (Google)", "q"),
+		
+		// Centrum
+		"fulltext.centrum.cz" 		=> array("Centrum", "q", "windows-1250"),
+		"morfeo.centrum.cz" 		=> array("Centrum", "q", "windows-1250"),
+		"search.centrum.cz" 		=> array("Centrum", "q", "windows-1250"),
+		
+		// Chello
+		"www.chello.fr"		 	=> array("Chello", "q1"),
+		
+		// Club Internet
+		"recherche.club-internet.fr"    => array("Club Internet", "q"),
+		
+		// Comcast
+		"www.comcast.net" 		=> array("Comcast", "query"),
+		
+		// Comet systems
+		"search.cometsystems.com"	=> array("CometSystems", "q"),
+		
+		// Compuserve
+		"suche.compuserve.de"	        => array("Compuserve.de (Powered by Google)", "q"),
+		"websearch.cs.com"		     => array("Compuserve.com (Enhanced by Google)", "query"),
+		
+		// Copernic
+		"metaresults.copernic.com"	=> array("Copernic", " "),
+		
+		// Crossbot
+		"www.crossbot.de"		=> array("Crossbot", "q"),
+	
+		// DasOertliche
+		"www.dasoertliche.de"	        => array("DasOertliche", "kw"),
+		
+		// DasTelefonbuch
+		"www.4call.dastelefonbuch.de"	=> array("DasTelefonbuch", "kw"),
+		
+		// Defind.de
+		"suche.defind.de"	        => array("Defind.de", "search"),
+		
+		// Deskfeeds
+		"www.deskfeeds.com"	        => array("Deskfeeds", "sx"),
+		
+		// Dino
+		"www.dino-online.de"		=> array("Dino", "query"),
+		
+		// dir.com
+		"fr.dir.com" 			=> array("dir.com", "req"),
+		
+		// dmoz
+		"dmoz.org"			=> array("dmoz", "search"),
+		"editors.dmoz.org"		=> array("dmoz", "search"),
+		"search.dmoz.org"		=> array("dmoz", "search"),
+		"www.dmoz.org"			=> array("dmoz", "search"),
+		
+		// Dogpile
+		"search.dogpile.com"		=> array("Dogpile", "q"),
+		"nbci.dogpile.com"		=> array("Dogpile", "q"),
+		
+		// earthlink
+		"search.earthlink.net"		=> array("Earthlink", "q"),
+		
+		// Eniro
+		"www.eniro.se" 			=> array("Eniro", "q"),
+		
+		// Espotting 
+		"affiliate.espotting.fr"	=> array("Espotting", "keyword"),
+		
+		// Eudip
+		"www.eudip.com"			=> array("Eudip", " "),
+		
+		// Eurip
+		"www.eurip.com"			=> array("Eurip", "q"),
+		
+		// Euroseek
+		"www.euroseek.com"		=> array("Euroseek", "string"),
+		
+		// Excite
+		"www.excite.it" 		=> array("Excite", "q"),
+		"msxml.excite.com"		=> array("Excite", "qkw"),
+		"www.excite.fr"			=> array("Excite", "search"),
+		
+		// Exalead
+		"www.exalead.fr"		=> array("Exalead", "q"),
+		"www.exalead.com"		=> array("Exalead", "q"),
+		
+		// eo
+		"eo.st"				=> array("eo", "q"),
+		
+		// Feedminer
+		"www.feedminer.com"		=> array("Feedminer", "q"),
+		
+		// Feedster
+		"www.feedster.com"		=> array("Feedster", ""),
+		
+		// Francite
+		"recherche.francite.com"	=> array("Francite", "name"),
+		"antisearch.francite.com"	=> array("Francite", "KEYWORDS"),
+		
+		// Fireball
+		"suche.fireball.de"		=> array("Fireball", "query"),
+		
+		
+		// Firstfind
+		"www.firstsfind.com"		=> array("Firstsfind", "qry"),
+		
+		// Fixsuche
+		"www.fixsuche.de"		=> array("Fixsuche", "q"),
+		
+		// Flix
+		"www.flix.de"			=> array("Flix.de", "keyword"),
+		
+		// Free
+		"search.free.fr"		=> array("Free", "q"),
+		"search1-2.free.fr"		=> array("Free", "q"),
+		"search1-1.free.fr"		=> array("Free", "q"),
+		
+		// Freenet
+		"suche.freenet.de"		=> array("Freenet", "query"),
+		
+		//Froogle
+		"froogle.google.com" 		=> array("Google (Froogle)", "q"),
+		"froogle.google.de" 		=> array("Google (Froogle)", "q"),
+		"froogle.google.co.uk" 		=> array("Google (Froogle)", "q"),
+		
+		//GAIS
+		"gais.cs.ccu.edu.tw" 		=> array("GAIS)", "query"),
+		
+		// Gigablast
+		"www.gigablast.com" 		=> array("Gigablast", "q"),
+		"blogs.gigablast.com" 		=> array("Gigablast (Blogs)", "q"),
+		"travel.gigablast.com" 		=> array("Gigablast (Travel)", "q"),
+		"dir.gigablast.com" 		=> array("Gigablast (Directory)", "q"),
+		"gov.gigablast.com" 		=> array("Gigablast (Gov)", "q"),
+		
+		// GMX
+		"suche.gmx.net"			=> array("GMX", "su"),
+		"www.gmx.net"			=> array("GMX", "su"),
+		
+		// goo
+		"search.goo.ne.jp"		=> array("goo", "mt"),
+		"ocnsearch.goo.ne.jp"		=> array("goo", "mt"),
+		
+		
+		// Google
+		"www.google.com"		=> array("Google", "q"),
+		"gogole.fr"				=> array("Google", "q"),
+		"www.gogole.fr"			=> array("Google", "q"),
+		"wwwgoogle.fr"			=> array("Google", "q"),
+		"ww.google.fr"			=> array("Google", "q"),
+		"w.google.fr"			=> array("Google", "q"),
+		"www.google.fr"			=> array("Google", "q"),
+		"www.google.fr."		=> array("Google", "q"),
+		"google.fr"				=> array("Google", "q"),
+		"www2.google.com"		=> array("Google", "q"),
+		"w.google.com"			=> array("Google", "q"),
+		"ww.google.com"			=> array("Google", "q"),
+		"wwwgoogle.com"		    => array("Google", "q"),
+		"www.gogole.com"		=> array("Google", "q"),
+		"www.gppgle.com"		=> array("Google", "q"),
+		"go.google.com"			=> array("Google", "q"),
+		"www.google.ae"			=> array("Google", "q"),
+		"www.google.as"			=> array("Google", "q"),
+		"www.google.at"			=> array("Google", "q"),
+		"wwwgoogle.at"			=> array("Google", "q"),
+		"ww.google.at"			=> array("Google", "q"),
+		"w.google.at"			=> array("Google", "q"),
+		"www.google.az"			=> array("Google", "q"),
+		"www.google.be"			=> array("Google", "q"),
+		"www.google.bg"			=> array("Google", "q"),
+		"www.google.ba"			=> array("Google", "q"),
+		"google.bg"				=> array("Google", "q"),
+		"www.google.bi"			=> array("Google", "q"),
+		"www.google.ca"			=> array("Google", "q"),
+		"ww.google.ca"			=> array("Google", "q"),
+		"w.google.ca"			=> array("Google", "q"),
+		"www.google.cc"			=> array("Google", "q"),
+		"www.google.cd"			=> array("Google", "q"),
+		"www.google.cg"			=> array("Google", "q"),
+		"www.google.ch"			=> array("Google", "q"),
+		"ww.google.ch"			=> array("Google", "q"),
+		"w.google.ch"			=> array("Google", "q"),
+		"www.google.ci"			=> array("Google", "q"),
+		"www.google.cl"			=> array("Google", "q"),
+		"www.google.cn"			=> array("Google", "q"),
+		"www.google.co"			=> array("Google", "q"),
+		"www.google.cz"			=> array("Google", "q"),
+		"wwwgoogle.cz"			=> array("Google", "q"),
+		"www.google.de"			=> array("Google", "q"),
+		"ww.google.de"			=> array("Google", "q"),
+		"w.google.de"			=> array("Google", "q"),
+		"wwwgoogle.de" 			=> array("Google", "q"),
+		"www.googleearth.de" 	=> array("Google", "q"),
+		"googleearth.de"		=> array("Google", "q"),
+		"google.gr"				=> array("Google", "q"),
+		"google.hr"				=> array("Google", "q"),
+		"www.google.dj"			=> array("Google", "q"),
+		"www.google.dk"			=> array("Google", "q"),
+		"www.google.es"			=> array("Google", "q"),
+		"www.google.fi"			=> array("Google", "q"),
+		"www.google.fm"			=> array("Google", "q"),
+		"www.google.gg"			=> array("Google", "q"),
+		"www.googel.fi"			=> array("Google", "q"),
+		"www.googleearth.fr"	=> array("Google", "q"),
+		"www.google.gl"			=> array("Google", "q"),
+		"www.google.gm"			=> array("Google", "q"),
+		"www.google.gr"			=> array("Google", "q"),
+		"www.google.hn"			=> array("Google", "q"),
+		"www.google.hr"			=> array("Google", "q"),
+		"www.google.hu"			=> array("Google", "q"),
+		"www.google.ie"			=> array("Google", "q"),
+		"www.google.is"			=> array("Google", "q"),
+		"www.google.it"			=> array("Google", "q"),
+		"www.google.jo"			=> array("Google", "q"),
+		"www.google.kz"			=> array("Google", "q"),
+		"www.google.li"			=> array("Google", "q"),
+		"www.google.lt"			=> array("Google", "q"),
+		"www.google.lu"			=> array("Google", "q"),
+		"www.google.lv"			=> array("Google", "q"),
+		"www.google.ms"			=> array("Google", "q"),
+		"www.google.mu"			=> array("Google", "q"),
+		"www.google.mw"			=> array("Google", "q"),
+		"www.google.md"			=> array("Google", "q"),
+		"www.google.nl"			=> array("Google", "q"),
+		"www.google.no"			=> array("Google", "q"),
+		"www.google.pl"			=> array("Google", "q"),
+		"www.google.sk" 		=> array("Google", "q"),
+		"www.google.pn"			=> array("Google", "q"),
+		"www.google.pt"			=> array("Google", "q"),
+		"www.google.dk"			=> array("Google", "q"),
+		"www.google.ro"			=> array("Google", "q"),
+		"www.google.ru"			=> array("Google", "q"),
+		"www.google.rw"			=> array("Google", "q"),
+		"www.google.se"			=> array("Google", "q"),
+		"www.google.sn"			=> array("Google", "q"),
+		"www.google.sh"			=> array("Google", "q"),
+		"www.google.si"			=> array("Google", "q"),
+		"www.google.sm" 		=> array("Google", "q"),
+		"www.google.td"			=> array("Google", "q"),
+		"www.google.tt"			=> array("Google", "q"),
+		"www.google.uz"			=> array("Google", "q"),
+		"www.google.vg"			=> array("Google", "q"),
+		"www.google.com.ar"		=> array("Google", "q"),
+		"www.google.com.au"		=> array("Google", "q"),
+		"www.google.com.bo"		=> array("Google", "q"),
+		"www.google.com.br"		=> array("Google", "q"),
+		"www.google.com.co"		=> array("Google", "q"),
+		"www.google.com.cu"		=> array("Google", "q"),
+		"www.google.com.ec"		=> array("Google", "q"),
+		"www.google.com.eg"		=> array("Google", "q"),
+		"www.google.com.do"		=> array("Google", "q"),
+		"www.google.com.fj"		=> array("Google", "q"),
+		"www.google.com.gr" 	=> array("Google", "q"),
+		"www.google.com.gt" 	=> array("Google", "q"),
+		"www.google.com.hk"		=> array("Google", "q"),
+		"www.google.com.ly"		=> array("Google", "q"),
+		"www.google.com.mt"		=> array("Google", "q"),
+		"www.google.com.mx"		=> array("Google", "q"),
+		"www.google.com.my"		=> array("Google", "q"),
+		"www.google.com.nf"		=> array("Google", "q"),
+		"www.google.com.ni"		=> array("Google", "q"),
+		"www.google.com.np"		=> array("Google", "q"),
+		"www.google.com.pa"		=> array("Google", "q"),
+		"www.google.com.pe" 	=> array("Google", "q"),
+		"www.google.com.ph"		=> array("Google", "q"),
+		"www.google.com.pk"		=> array("Google", "q"),
+		"www.google.com.pl"		=> array("Google", "q"),
+		"www.google.com.pr"		=> array("Google", "q"),
+		"www.google.com.py"		=> array("Google", "q"),
+		"www.google.com.qa"		=> array("Google", "q"),
+		"www.google.com.om"		=> array("Google", "q"),
+		"www.google.com.ru"		=> array("Google", "q"),
+		"www.google.com.sg"		=> array("Google", "q"),
+		"www.google.com.sa"		=> array("Google", "q"),
+		"www.google.com.sv"		=> array("Google", "q"),
+		"www.google.com.tr"		=> array("Google", "q"),
+		"www.google.com.tw"		=> array("Google", "q"),
+		"www.google.com.ua"		=> array("Google", "q"),
+		"www.google.com.uy"		=> array("Google", "q"),
+		"www.google.com.vc"		=> array("Google", "q"),
+		"www.google.com.vn"		=> array("Google", "q"),
+		"www.google.co.cr"		=> array("Google", "q"),
+		"www.google.co.gg"		=> array("Google", "q"),
+		"www.google.co.hu"		=> array("Google", "q"),
+		"www.google.co.id"		=> array("Google", "q"),
+		"www.google.co.il"		=> array("Google", "q"),
+		"www.google.co.in" 		=> array("Google", "q"),
+		"www.google.co.je"		=> array("Google", "q"),
+		"www.google.co.jp"		=> array("Google", "q"),
+		"www.google.co.ls"		=> array("Google", "q"),
+		"www.google.co.ke" 		=> array("Google", "q"),
+		"www.google.co.kr"		=> array("Google", "q"),
+		"www.google.co.nz"		=> array("Google", "q"),
+		"www.google.co.th"		=> array("Google", "q"),
+		"www.google.co.uk"		=> array("Google", "q"),
+		"www.google.co.ve"		=> array("Google", "q"),
+		"www.google.co.za" 		=> array("Google", "q"),
+		"www.google.co.ma"		=> array("Google", "q"),
+		"www.goggle.com"		=> array("Google", "q"),
+		
+		
+		// Powered by Google 
+		"www.charter.net" 		=> array("Google", "q"),
+		"brisbane.t-online.de" 	        => array("Google", "q"),
+		"miportal.bellsouth.net"        => array("Google", "string"),
+		"home.bellsouth.net"	        => array("Google", "string"),
+		"pesquisa.clix.pt" 		=> array("Google", "q"),
+		"google.startsiden.no" 	        => array("Google", "q"),
+		"google.startpagina.nl"		=> array("Google", "q"),
+		"search.peoplepc.com" 	        => array("Google", "q"),
+		"www.google.interia.pl"		=> array("Google", "q"),
+		"buscador.terra.es" 	        => array("Google", "query"),
+		"buscador.terra.cl" 	        => array("Google", "query"),
+		"buscador.terra.com.br"		=> array("Google", "query"),
+		"www.icq.com" 			=> array("Google", "q"),
+		"www.adelphia.net" 		=> array("Google", "q"),
+		"so.qq.com" 			=> array("Google", "word"),
+		"misc.skynet.be" 		=> array("Google", "keywords"),
+		"www.start.no" 			=> array("Google", "q"),
+		"verden.abcsok.no"		=> array("Google", "q"),
+		"search.sweetim.com"	        => array("Google", "q"),
+		
+		
+		//Google Blogsearch
+		"blogsearch.google.com"		=> array("Google Blogsearch", "q"),
+		"blogsearch.google.de"		=> array("Google Blogsearch", "q"),
+		"blogsearch.google.fr"		=> array("Google Blogsearch", "q"),
+		"blogsearch.google.co.uk"	=> array("Google Blogsearch", "q"),
+		"blogsearch.google.it"		=> array("Google Blogsearch", "q"),
+		"blogsearch.google.net"		=> array("Google Blogsearch", "q"),
+		"blogsearch.google.es"		=> array("Google Blogsearch", "q"),
+		"blogsearch.google.ru"		=> array("Google Blogsearch", "q"),
+		"blogsearch.google.be"		=> array("Google Blogsearch", "q"),
+		"blogsearch.google.nl"		=> array("Google Blogsearch", "q"),
+		"blogsearch.google.at"		=> array("Google Blogsearch", "q"),
+		"blogsearch.google.ch"		=> array("Google Blogsearch", "q"),
+		"blogsearch.google.pl"		=> array("Google Blogsearch", "q"),
+		
+		
+		// Google translation
+		"translate.google.com"		=> array("Google Translations", "q"),
+		
+		// Google Directory
+		"directory.google.com"		=> array("Google Directory", " "),
+		
+		// Google Images
+		"images.google.fr"		=> array("Google Images", "q"),
+		"images.google.be" 		=> array("Google Images", "q"),
+		"images.google.ca" 		=> array("Google Images", "q"),
+		"images.google.co.uk"		=> array("Google Images", "q"),
+		"images.google.de" 		=> array("Google Images", "q"),
+		"images.google.it"    		=> array("Google Images", "q"),
+		"images.google.at"		=> array("Google Images", "q"),
+		"images.google.bg"		=> array("Google Images", "q"),
+		"images.google.ch"		=> array("Google Images", "q"),
+		"images.google.ci"		=> array("Google Images", "q"),
+		"images.google.com.au"		=> array("Google Images", "q"),
+		"images.google.com.cu"		=> array("Google Images", "q"),
+		"images.google.co.id"		=> array("Google Images", "q"),
+		"images.google.co.il"		=> array("Google Images", "q"),
+		"images.google.co.in"		=> array("Google Images", "q"),
+		"images.google.co.jp"		=> array("Google Images", "q"),
+		"images.google.co.hu"		=> array("Google Images", "q"),
+		"images.google.co.kr"		=> array("Google Images", "q"),
+		"images.google.co.nz"		=> array("Google Images", "q"),
+		"images.google.co.th"		=> array("Google Images", "q"),
+		"images.google.co.tw"		=> array("Google Images", "q"),
+		"images.google.co.ve"		=> array("Google Images", "q"),
+		"images.google.com.ar"		=> array("Google Images", "q"),
+		"images.google.com.br"		=> array("Google Images", "q"),
+		"images.google.com.cu"		=> array("Google Images", "q"),
+		"images.google.com.do"		=> array("Google Images", "q"),
+		"images.google.com.gr"		=> array("Google Images", "q"),
+		"images.google.com.hk"		=> array("Google Images", "q"),
+		"images.google.com.mx"		=> array("Google Images", "q"),
+		"images.google.com.my"		=> array("Google Images", "q"),
+		"images.google.com.pe"		=> array("Google Images", "q"),
+		"images.google.com.tr"		=> array("Google Images", "q"),
+		"images.google.com.tw"		=> array("Google Images", "q"),
+		"images.google.com.ua"		=> array("Google Images", "q"),
+		"images.google.com.vn"		=> array("Google Images", "q"),
+		"images.google.dk"		=> array("Google Images", "q"),
+		"images.google.es"		=> array("Google Images", "q"),
+		"images.google.fi"		=> array("Google Images", "q"),
+		"images.google.gg"		=> array("Google Images", "q"),
+		"images.google.gr"		=> array("Google Images", "q"),
+		"images.google.it"		=> array("Google Images", "q"),
+		"images.google.ms"		=> array("Google Images", "q"),
+		"images.google.nl"		=> array("Google Images", "q"),
+		"images.google.no"		=> array("Google Images", "q"),
+		"images.google.pl"		=> array("Google Images", "q"),
+		"images.google.pt"		=> array("Google Images", "q"),
+		"images.google.ro"		=> array("Google Images", "q"),
+		"images.google.ru"		=> array("Google Images", "q"),
+		"images.google.se"		=> array("Google Images", "q"),
+		"images.google.sk"		=> array("Google Images", "q"),
+		"images.google.com"		=> array("Google Images", "q"),
+		
+		// Google News
+		"news.google.com" 		=> array("Google News", "q"),
+		"news.google.se" 		=> array("Google News", "q"),
+		"news.google.com" 		=> array("Google News", "q"),
+		"news.google.es" 		=> array("Google News", "q"),
+		"news.google.ch" 		=> array("Google News", "q"),
+		"news.google.lt" 		=> array("Google News", "q"),
+		"news.google.ie" 		=> array("Google News", "q"),
+		"news.google.de" 		=> array("Google News", "q"),
+		"news.google.cl" 		=> array("Google News", "q"),
+		"news.google.com.ar" 		=> array("Google News", "q"),
+		"news.google.fr" 		=> array("Google News", "q"),
+		"news.google.ca" 		=> array("Google News", "q"),
+		"news.google.co.uk" 		=> array("Google News", "q"),
+		"news.google.co.jp" 		=> array("Google News", "q"),
+		"news.google.com.pe" 		=> array("Google News", "q"),
+		"news.google.com.au" 		=> array("Google News", "q"),
+		"news.google.com.mx" 		=> array("Google News", "q"),
+		"news.google.com.hk" 		=> array("Google News", "q"),
+		"news.google.co.in" 		=> array("Google News", "q"),
+		"news.google.at" 		=> array("Google News", "q"),
+		"news.google.com.tw" 		=> array("Google News", "q"),
+		"news.google.com.co" 		=> array("Google News", "q"),
+		"news.google.co.ve" 		=> array("Google News", "q"),
+		"news.google.lu" 		=> array("Google News", "q"),
+		"news.google.com.ly" 		=> array("Google News", "q"),
+		"news.google.it" 		=> array("Google News", "q"),
+		"news.google.sm" 		=> array("Google News", "q"),
+		
+		// Goyellow.de
+		"www.goyellow.de"	        => array("GoYellow.de", "MDN"),
+		
+		// HighBeam
+		"www.highbeam.com"	        => array("HighBeam", "Q"),
+		
+		// Hit-Parade
+		"recherche.hit-parade.com"	=> array("Hit-Parade", "p7"),
+		"class.hit-parade.com"		=> array("Hit-Parade", "p7"),
+		
+		// Hotbot via Lycos
+		"hotbot.lycos.com"		=> array("Hotbot (Lycos)", "query"),
+		"search.hotbot.de"		=> array("Hotbot", "query"),
+		"search.hotbot.fr"		=> array("Hotbot", "query"),
+		"www.hotbot.com"		=> array("Hotbot", "query"),
+		
+		// 1stekeuze
+		"zoek.1stekeuze.nl" 		=> array("1stekeuze", "terms"),
+		
+		// Infoseek
+		"search.www.infoseek.co.jp"     => array("Infoseek", "qt"),
+		
+		// Icerocket
+		"blogs.icerocket.com"		  => array("Icerocket", "qt"),
+		
+		// ICQ
+		"www.icq.com"			=> array("ICQ", "q"),
+		"search.icq.com"		=> array("ICQ", "q"),
+		
+		// Ilse
+		"spsearch.ilse.nl" 		=> array("Startpagina", "search_for"),
+		"be.ilse.nl" 			=> array("Ilse BE", "query"),
+		"search.ilse.nl" 		=> array("Ilse NL", "search_for"),
+		
+		// Iwon
+		"search.iwon.com"		=> array("Iwon", "searchfor"),
+		
+		// Ixquick
+		"ixquick.com"			=> array("Ixquick", "query"),
+		"www.eu.ixquick.com"		=> array("Ixquick", "query"),
+		"us.ixquick.com"		=> array("Ixquick", "query"),
+		"s1.us.ixquick.com"		=> array("Ixquick", "query"),
+		"s2.us.ixquick.com"		=> array("Ixquick", "query"),
+		"s3.us.ixquick.com"		=> array("Ixquick", "query"),
+		"s4.us.ixquick.com"		=> array("Ixquick", "query"),
+		"s5.us.ixquick.com"		=> array("Ixquick", "query"),
+		"eu.ixquick.com" 		=> array("Ixquick","query"),
+		
+		// Jyxo
+		"jyxo.cz" 			=> array("Jyxo", "q"),
+		
+		// Jungle Spider
+		"www.jungle-spider.de"		=> array("Jungle Spider", "qry"),
+		
+		// Kartoo
+		"kartoo.com"			=> array("Kartoo", ""),
+		"kartoo.de"			=> array("Kartoo", ""),
+		"kartoo.fr"			=> array("Kartoo", ""),
+		
+		
+		// Kataweb
+		"www.kataweb.it" 		=> array("Kataweb", "q"),
+		
+		// Klug suchen
+		"www.klug-suchen.de"		   => array("Klug suchen!", "query"),
+		
+		// La Toile Du Québec via Google
+		"google.canoe.com"		=> array("La Toile Du Québec (Google)", "q"),
+		"www.toile.com"			=> array("La Toile Du Québec (Google)", "q"),	
+		"web.toile.com"			=> array("La Toile Du Québec (Google)", "q"),
+		
+		// La Toile Du Québec 
+		"recherche.toile.qc.ca"		=> array("La Toile Du Québec", "query"),
+		
+		// Live.com
+		"www.live.com"			=> array("Live", "q"),
+		"beta.search.live.com"	=> array("Live", "q"),
+		"search.live.com"		=> array("Live", "q"),
+		"g.msn.com"		        => array("Live", " "),
+		
+		// Looksmart
+		"www.looksmart.com"		=> array("Looksmart", "key"),
+		
+		// Lycos
+		"search.lycos.com"		=> array("Lycos", "query"),
+		"vachercher.lycos.fr"		=> array("Lycos", "query"),
+		"www.lycos.fr"			=> array("Lycos", "query"),
+		"suche.lycos.de"		=> array("Lycos", "query"),
+		"search.lycos.de"		=> array("Lycos", "query"),
+		"sidesearch.lycos.com"		=> array("Lycos", "query"),
+		"www.multimania.lycos.fr" 	=> array("Lycos", "query"),
+		"buscador.lycos.es" 	=> array("Lycos", "query"),
+		
+		// Mail.ru
+		"go.mail.ru"			=> array("Mailru", "q"),
+		
+		// Mamma
+		"mamma.com"			=> array("Mamma", "query"),
+		"mamma75.mamma.com"		=> array("Mamma", "query"),
+		"www.mamma.com"			=> array("Mamma", "query"),
+		
+		// Meceoo
+		"www.meceoo.fr" 		=> array("Meceoo", "kw"),
+		
+		// Mediaset
+		"servizi.mediaset.it" 		=> array("Mediaset", "searchword"),
+		
+		// Metacrawler
+		"search.metacrawler.com"	=> array("Metacrawler", "general"),
+		
+		// Metager
+		"mserv.rrzn.uni-hannover.de"	=> array("Metager", "eingabe"),
+		"www.metager.de"		=> array("Metager", "eingabe"),
+		
+		// Metager2
+		"www.metager2.de"	        => array("Metager2", "q"),
+		"metager2.de"			       => array("Metager2", "q"),
+		
+		// Meinestadt
+		"www.meinestadt.de"	        => array("Meinestadt.de", "words"),
+		
+		// Monstercrawler
+		"www.monstercrawler.com" 	=> array("Monstercrawler", "qry"),
+		
+		// Mozbot
+		"www.mozbot.fr"			=> array("mozbot", "q"),
+		"www.mozbot.co.uk" 		=> array("mozbot", "q"),
+		"www.mozbot.com"		=> array("mozbot", "q"),
+		
+		// MSN
+		"search.msn.com"		=> array("MSN", "q"),
+		"beta.search.msn.fr"		=> array("MSN", "q"),
+		"search.msn.fr"			=> array("MSN", "q"),
+		"search.msn.es"			=> array("MSN", "q"),
+		"search.msn.se"			=> array("MSN", "q"),
+		"search.latam.msn.com"		=> array("MSN", "q"),
+		"search.msn.nl" 		=> array("MSN", "q"),
+		"leguide.fr.msn.com"		=> array("MSN", "s"),
+		"leguide.msn.fr"		=> array("MSN", "s"),
+		"search.msn.co.jp"		=> array("MSN", "q"),
+		"search.msn.no"			=> array("MSN", "q"),
+		"search.msn.at"			=> array("MSN", "q"),
+		"search.msn.com.hk"		=> array("MSN", "q"),
+		"search.t1msn.com.mx"		=> array("MSN", "q"),
+		"fr.ca.search.msn.com"		=> array("MSN", "q"),
+		"search.msn.be" 		=> array("MSN", "q"),
+		"search.fr.msn.be" 		=> array("MSN", "q"),
+		"search.msn.it" 		=> array("MSN", "q"),
+		"sea.search.msn.it" 		=> array("MSN", "q"),
+		"sea.search.msn.fr" 		=> array("MSN", "q"),
+		"sea.search.msn.de" 		=> array("MSN", "q"),
+		"sea.search.msn.com" 		=> array("MSN", "q"),
+		"sea.search.fr.msn.be" 		=> array("MSN", "q"),
+		"search.msn.com.tw" 		=> array("MSN", "q"),
+		"search.msn.de" 		=> array("MSN", "q"),
+		"search.msn.co.uk" 		=> array("MSN", "q"),
+		"search.msn.co.za"		=> array("MSN", "q"),
+		"search.msn.ch" 		=> array("MSN", "q"),
+		"search.msn.es" 		=> array("MSN", "q"),
+		"search.msn.com.br"		=> array("MSN", "q"),
+		"search.ninemsn.com.au"		=> array("MSN", "q"),
+		"search.msn.dk"			=> array("MSN", "q"),
+		"search.arabia.msn.com"		=> array("MSN", "q"),
+		"search.prodigy.msn.com"	=> array("MSN", "q"),
+		
+		// El Mundo
+		"ariadna.elmundo.es" 	=> array("El Mundo", "q"),
+		
+		// MyWebSearch
+		"kf.mysearch.myway.com" 	=> array("MyWebSearch", "searchfor"),
+		"ms114.mysearch.com" 		=> array("MyWebSearch", "searchfor"),
+		"ms146.mysearch.com"	 	=> array("MyWebSearch", "searchfor"),
+		"mysearch.myway.com"		=> array("MyWebSearch", "searchfor"),
+		"searchfr.myway.com"		=> array("MyWebSearch", "searchfor"),
+		"ki.mysearch.myway.com" 	=> array("MyWebSearch", "searchfor"),
+		"search.mywebsearch.com"	=> array("MyWebSearch", "searchfor"),
+		"www.mywebsearch.com"		=> array("MyWebSearch", "searchfor"),
+		
+		// Najdi
+		"www.najdi.si" 			=> array("Najdi.si", "q"),
+		
+		// Needtofind
+		"ko.search.need2find.com"	=> array("Needtofind", "searchfor"),
+		
+		// Netster
+		"www.netster.com"		=> array("Netster", "keywords"),
+		
+		// Netscape
+		"search-intl.netscape.com"	=> array("Netscape", "search"),
+		"www.netscape.fr"		=> array("Netscape", "q"),
+		"suche.netscape.de"		=> array("Netscape", "q"),
+		"search.netscape.com"		=> array("Netscape", "query"),
+		
+		// Nomade
+		"ie4.nomade.fr"			=> array("Nomade", "s"),
+		"rechercher.nomade.aliceadsl.fr"=> array("Nomade (AliceADSL)", "s"),
+		"rechercher.nomade.fr"		=> array("Nomade", "s"),
+		
+		// Northern Light
+		"www.northernlight.com"		=> array("Northern Light", "qr"),
+		
+		// Numéricable
+		"www.numericable.fr" 		=> array("Numéricable", "query"),
+		
+		// Onet
+		"szukaj.onet.pl" 		=> array("Onet.pl", "qt"),
+		
+		// Opera
+		"search.opera.com" 		=> array("Opera", "search"),
+		
+		// Openfind
+		"wps.openfind.com.tw" 		=> array("Openfind (Websearch)", "query"),
+		"bbs2.openfind.com.tw" 		=> array("Openfind (BBS)", "query"),
+		"news.openfind.com.tw" 		=> array("Openfind (News)", "query"),
+		
+		// Overture
+		"www.overture.com"		=> array("Overture", "Keywords"),
+		"www.fr.overture.com"		=> array("Overture", "Keywords"),
+		
+		// Paperball
+		"suche.paperball.de" 		=> array("Paperball", "query"),
+		
+		// Picsearch
+		"www.picsearch.com" 		=> array("Picsearch", "q"),
+		
+		// Plazoo
+		"www.plazoo.com" 		=> array("Plazoo", "q"),
+		
+		// Postami
+		"www.postami.com" 		=> array("Postami", "query"),
+		
+		// Quick searches
+		"data.quicksearches.net"	=> array("QuickSearches", "q"),
+		
+		// Qualigo
+		"www.qualigo.de"	        => array("Qualigo", "q"),
+		"www.qualigo.ch"	        => array("Qualigo", "q"),
+		"www.qualigo.at"	        => array("Qualigo", "q"),
+		"www.qualigo.nl"	        => array("Qualigo", "q"),
+		
+		// Rambler
+		"search.rambler.ru" 		=> array("Rambler", "words"),
+		
+		// Reacteur.com
+		"www.reacteur.com"		=> array("Reacteur", "kw"),
+		
+		// Sapo
+		"pesquisa.sapo.pt" 		=> array("Sapo","q"),
+		
+		// Search.com
+		"www.search.com"		=> array("Search.com", "q"),
+		
+		// Search.ch
+		"www.search.ch"			=> array("Search.ch", "q"),
+		
+		// Search a lot
+		"www.searchalot.com"		=> array("Searchalot", "query"),
+		
+		// Seek
+		"www.seek.fr"			=> array("Searchalot", "qry_str"),
+		
+		// Seekport
+		"www.seekport.de"		=> array("Seekport", "query"),
+		"www.seekport.co.uk"		=> array("Seekport", "query"),
+		"www.seekport.fr"		=> array("Seekport", "query"),
+		"www.seekport.at"		=> array("Seekport", "query"),
+		"www.seekport.es"		=> array("Seekport", "query"),
+		"www.seekport.it"		=> array("Seekport", "query"),
+		
+		// Seekport (blogs)
+		"blogs.seekport.de"		=> array("Seekport (Blogs)", "query"),
+		"blogs.seekport.co.uk"		=> array("Seekport (Blogs)", "query"),
+		"blogs.seekport.fr"		=> array("Seekport (Blogs)", "query"),
+		"blogs.seekport.at"		=> array("Seekport (Blogs)", "query"),
+		"blogs.seekport.es"		=> array("Seekport (Blogs)", "query"),
+		"blogs.seekport.it"		=> array("Seekport (Blogs)", "query"),
+		
+		// Seekport (news)
+		"news.seekport.de"		=> array("Seekport (News)", "query"),
+		"news.seekport.co.uk"		=> array("Seekport (News)", "query"),
+		"news.seekport.fr"		=> array("Seekport (News)", "query"),
+		"news.seekport.at"		=> array("Seekport (News)", "query"),
+		"news.seekport.es"		=> array("Seekport (News)", "query"),
+		"news.seekport.it"		=> array("Seekport (News)", "query"),
+		
+		// Searchscout
+		"www.searchscout.com"		=> array("Search Scout", "gt_keywords"),
+		
+		// Searchy
+		"www.searchy.co.uk"		=> array("Searchy", "search_term"),
+		
+		// Seznam
+		"search1.seznam.cz" 		=> array("Seznam", "q"),
+		"search2.seznam.cz" 		=> array("Seznam", "q"),
+		"search.seznam.cz" 		=> array("Seznam", "q"),
+		
+		// Sharelook
+		"www.sharelook.fr"		=> array("Sharelook", "keyword"),
+		"www.sharelook.de"		=> array("Sharelook", "keyword"),
+		
+		// Skynet
+		"search.skynet.be" 		=> array("Skynet", "keywords"),
+		
+		// Sphere
+		"www.sphere.com" 		=> array("Sphere", "q"),
+		
+		// Startpagina
+		"startgoogle.startpagina.nl" 	=> array("Startpagina (Google)", "q"),
+		
+		// Suchnase
+		"www.suchnase.de" 		=> array("Suchnase", "qkw"),
+		
+		// Supereva
+		"search.supereva.com" 		=> array("Supereva", "q"),
+		
+		// Sympatico
+		"search.sympatico.msn.ca"	=> array("Sympatico", "q"),
+		"search.sli.sympatico.ca"       => array("Sympatico", "q"),
+		"search.fr.sympatico.msn.ca"    => array("Sympatico", "q"),
+		"sea.search.fr.sympatico.msn.ca"=> array("Sympatico", "q"),
+		
+		// Suchmaschine.com
+		"www.suchmaschine.com"		=> array("Suchmaschine.com", "suchstr"),
+		
+		//Technorati
+		"www.technorati.com"		=> array("Technorati", " "),
+		
+		// Teoma
+		"www.teoma.com"			=> array("Teoma", "t"),
+		
+		// Tiscali
+		"rechercher.nomade.tiscali.fr"  => array("Tiscali", "s"),
+		"search-dyn.tiscali.it" 	=> array("Tiscali", "key"),
+		"www.tiscali.co.uk"		=> array("Tiscali", "query"),
+		"search-dyn.tiscali.de"		=> array("Tiscali", "key"),
+		"hledani.tiscali.cz" 		=> array("Tiscali", "query", "windows-1250"),
+		
+		// T-Online
+		"suche.t-online.de"		=> array("T-Online", "q"),
+		
+		// Trouvez.com
+		"www.trouvez.com"		=> array("Trouvez.com", "query"),
+		
+		// Trusted-Search
+		
+		"www.trusted--search.com"       => array("Trusted Search", "w"),
+		 
+		// Vinden
+		"zoek.vinden.nl" 		=> array("Vinden", "query"),
+		
+		// Vindex
+		"www.vindex.nl" 		=> array("Vindex","search_for"),
+		
+		// Virgilio
+		"search.virgilio.it"		=> array("Virgilio", "qs"),
+		
+		// Voila
+		"search.voila.com"		=> array("Voila", "kw"),
+		"search.ke.voila.fr"		=> array("Voila", "rdata"),
+		"moteur.voila.fr"		=> array("Voila", "kw"),
+		"search.voila.fr"		=> array("Voila", "kw"),
+		"beta.voila.fr"			=> array("Voila", "kw"),
+		
+		// Volny
+		"web.volny.cz" 			=> array("Volny", "search", "windows-1250"),
+		
+		// Wanadoo
+		"search.ke.wanadoo.fr"		=> array("Wanadoo", "kw"),
+		"busca.wanadoo.es"		=> array("Wanadoo", "buscar"),
+		
+		// Web.de
+		"suche.web.de"			=> array("Web.de (Websuche)", "su"),
+		"dir.web.de"			=> array("Web.de (Directory)", "su"),
+		
+		// Webtip
+		"www.webtip.de"			=> array("Webtip", "keyword"),
+		
+		// X-recherche
+		"www.x-recherche.com" 		=> array("X-Recherche", "mots"),
+		
+		// Yahoo
+		"search.yahoo.com"		=> array("Yahoo!", "p"),
+		"ink.yahoo.com"			=> array("Yahoo!", "p"),
+		"ink.yahoo.fr"			=> array("Yahoo!", "p"),
+		"fr.ink.yahoo.com"		=> array("Yahoo!", "p"),
+		"search.yahoo.co.jp" 		=> array("Yahoo!", "p"),
+		"search.yahoo.fr"		=> array("Yahoo!", "p"),
+		"ar.search.yahoo.com" 		=> array("Yahoo!", "p"),
+		"br.search.yahoo.com" 		=> array("Yahoo!", "p"),
+		"ch.search.yahoo.com" 		=> array("Yahoo!", "p"),
+		"de.search.yahoo.com"		=> array("Yahoo!", "p"),
+		"ca.search.yahoo.com"		=> array("Yahoo!", "p"),
+		"cf.search.yahoo.com"		=> array("Yahoo!", "p"),
+		"fr.search.yahoo.com"		=> array("Yahoo!", "p"),
+		"espanol.search.yahoo.com"	=> array("Yahoo!", "p"),
+		"es.search.yahoo.com" 		=> array("Yahoo!", "p"),
+		"id.search.yahoo.com"		  => array("Yahoo!", "p"),
+		"it.search.yahoo.com" 		=> array("Yahoo!", "p"),
+		"kr.search.yahoo.com" 		=> array("Yahoo!", "p"),
+		"mx.search.yahoo.com" 		=> array("Yahoo!", "p"),
+		"nl.search.yahoo.com" 		=> array("Yahoo!", "p"),
+		"uk.search.yahoo.com" 		=> array("Yahoo!", "p"),
+		"cade.search.yahoo.com"		=> array("Yahoo!", "p"),
+		"tw.search.yahoo.com" 		=> array("Yahoo!", "p"),
+		"www.yahoo.com.cn" 		=> array("Yahoo!", "p"),
+		
+		"de.dir.yahoo.com"		     => array("Yahoo! Webverzeichnis", ""),   
+		"cf.dir.yahoo.com"		=> array("Yahoo! Directory", ""),
+		"fr.dir.yahoo.com"		=> array("Yahoo! Directory", ""),
+		
+		// Yandex
+		"www.yandex.ru" 		=> array("Yandex", "text"),
+		"yandex.ru" 			=> array("Yandex", "text"),
+		"search.yaca.yandex.ru" 	=> array("Yandex", "text"),
+		"ya.ru" 			=> array("Yandex", "text"),
+		"www.ya.ru" 			=> array("Yandex", "text"),
+		"images.yandex.ru"		=> array("Yandex Images","text"),
+		
+		//Yellowmap
+		
+		"www.yellowmap.de"	        => array("Yellowmap", " "),
+		"yellowmap.de"			       => array("Yellowmap", " "),
+		
+		// Wanadoo
+		"search.ke.wanadoo.fr"		=> array("Wanadoo", "kw"),
+		"busca.wanadoo.es"		=> array("Wanadoo", "buscar"),
+		
+		// Wedoo
+		"fr.wedoo.com"			=> array("Wedoo", "keyword"),
+		
+		// Web.nl
+		"www.web.nl" 			=> array("Web.nl","query"),
+		
+		// Weborama
+		"www.weborama.fr"		=> array("weborama", "query"),
+		
+		// WebSearch
+		"is1.websearch.com"		=> array("WebSearch", "qkw"),
+		"www.websearch.com"		=> array("WebSearch", "qkw"),
+		"websearch.cs.com"		=> array("WebSearch", "query"),
+		
+		// Witch
+		"www.witch.de"		        => array("Witch", "search"),
+		
+		// WXS
+		"wxsl.nl" 			=> array("Planet Internet","q"),
+		
+		// Zoek
+		"www3.zoek.nl" 			=> array("Zoek","q"),
+		
+		// Zhongsou
+		"p.zhongsou.com" 		=> array("Zhongsou","w"),
+		
+		// Zoeken
+		"www.zoeken.nl" 		=> array("Zoeken","query"),
+		
+		// Zoohoo
+		"zoohoo.cz" 			=> array("Zoohoo", "q", "windows-1250"),
+		"www.zoohoo.cz" 		=> array("Zoohoo", "q", "windows-1250"),
+		
+		// Zoznam
+		"www.zoznam.sk" 		=> array("Zoznam", "s"),
+	);
+	
+	$GLOBALS['Piwik_SearchEngines_NameToUrl'] = array();
+	foreach($GLOBALS['Piwik_SearchEngines'] as $url => $info)
+	{
+		if(!isset($GLOBALS['Piwik_SearchEngines_NameToUrl'][$info[0]]))
+		{
+			$GLOBALS['Piwik_SearchEngines_NameToUrl'][$info[0]] = $url;
+		}
+	}
+	
+}
+
diff --git a/core/DataTable.php b/core/DataTable.php
new file mode 100644
index 0000000000..4ed4226944
--- /dev/null
+++ b/core/DataTable.php
@@ -0,0 +1,1014 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: DataTable.php 578 2008-07-27 00:15:21Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+require_once "DataTable/Renderer.php";
+require_once "DataTable/Renderer/Console.php";
+require_once "DataTable/Filter.php";
+require_once "DataTable/Row.php";
+require_once "DataTable/Manager.php";
+
+/**
+ * 
+ * ---- DataTable
+ * A DataTable is a data structure used to store complex tables of data.
+ * 
+ * A DataTable is composed of multiple DataTable_Row.
+ * A DataTable can be applied one or several DataTable_Filter.
+ * A DataTable can be given to a DataTable_Renderer that would export the data under a given format (XML, HTML, etc.).
+ * 
+ * A DataTable has the following features:
+ * - serializable to be stored in the DB
+ * - loadable from the serialized version
+ * - efficient way of loading data from an external source (from a PHP array structure)
+ * - very simple interface to get data from the table
+ * 
+ * ---- DataTable_Row
+ * A DataTableRow in the table is defined by
+ * - multiple columns (a label, multiple values, ...)
+ * - optional metadata
+ * - optional - a sub DataTable associated to this row
+ * 
+ * Simple row example:
+ * - columns = array(   'label' => 'Firefox', 
+ * 						'visitors' => 155, 
+ * 						'pages' => 214, 
+ * 						'bounce_rate' => 67)
+ * - metadata = array('logo' => '/img/browsers/FF.png')
+ * - no sub DataTable
+ * 
+ * A more complex example would be a DataTable_Row that is associated to a sub DataTable.
+ * For example, for the row of the search engine Google, 
+ * we want to get the list of keywords associated, with their statistics.
+ * - columns = array(   'label' => 'Google',
+ * 						'visits' => 1550, 
+ * 						'visits_length' => 514214, 
+ * 						'returning_visits' => 77)
+ * - metadata = array(	'logo' => '/img/search/google.png', 
+ * 						'url' => 'http://google.com')
+ * - DataTable = DataTable containing several DataTable_Row containing the keywords information for this search engine
+ * 			Example of one DataTable_Row
+ * 			- the keyword columns specific to this search engine = 
+ * 					array(  'label' => 'Piwik', // the keyword 
+ * 							'visitors' => 155,  // Piwik has been searched on Google by 155 visitors
+ * 							'pages' => 214 // Visitors coming from Google with the kwd Piwik have seen 214 pages
+ * 					)
+ * 			- the keyword metadata = array() // nothing here, but we could imagining storing the URL of the search in Google for example
+ * 			- no subTable
+ *  
+ * 
+ * ---- DataTable_Filter
+ * A DataTable_Filter is a applied to a DataTable and so 
+ * can filter information in the multiple DataTable_Row.
+ * 
+ * For example a DataTable_Filter can:
+ * - remove rows from the table, 
+ * 		for example the rows' labels that do not match a given searched pattern
+ * 		for example the rows' values that are less than a given percentage (low population)
+ * - return a subset of the DataTable 
+ * 		for example a function that apply a limit: $offset, $limit
+ * - add / remove columns
+ * 		for example adding a column that gives the percentage of a given value
+ * - add some metadata
+ * 		for example the 'logo' path if the filter detects the logo
+ * - edit the value, the label
+ * - change the rows order
+ * 		for example if we want to sort by Label alphabetical order, or by any column value
+ * 
+ * When several DataTable_Filter are to be applied to a DataTable they are applied sequentially.
+ * A DataTable_Filter is assigned a priority. 
+ * For example, filters that 
+ * 	- sort rows should be applied with the highest priority
+ * 	- remove rows should be applied with a high priority as they prune the data and improve performance.
+ * 	
+ * ---- Code example
+ * 
+ * $table = new DataTable;
+ * $table->loadFromArray( array(...) );
+ * 
+ * # sort the table by visits asc
+ * $filter = new DataTable_Filter_Sort( $table, 'visits', 'asc');
+ * $tableFiltered = $filter->getTableFiltered();
+ * 
+ * # add a filter to select only the website with a label matching '*.com' (regular expression)
+ * $filter = new DataTable_Filter_Pattern( $table, 'label', '*(.com)');
+ * $tableFiltered = $filter->getTableFiltered();
+ * 
+ * # keep the 20 elements from offset 15
+ * $filter = new DataTable_Filter_Limit( $tableFiltered, 15, 20);
+ * $tableFiltered = $filter->getTableFiltered();
+ * 
+ * # add a column computing the percentage of visits
+ * # params = table, column containing the value, new column name to add, number of total visits to use to compute the %
+ * $filter = new DataTable_Filter_AddColumnPercentage( $tableFiltered, 'visits', 'visits_percentage', 2042);
+ * $tableFiltered = $filter->getTableFiltered();
+ * 
+ * # we get the table as XML
+ * $xmlOutput = new DataTable_Exporter_Xml( $table );
+ * $xmlOutput->setHeader( ... );
+ * $xmlOutput->setColumnsToExport( array('visits', 'visits_percent', 'label') );
+ * $XMLstring = $xmlOutput->getOutput();
+ * 
+ * 
+ * ---- Other (ideas)
+ * We can also imagine building a DataTable_Compare which would take N DataTable that have the same
+ * structure and would compare them, by computing the percentages of differences, etc.
+ * 
+ * For example 
+ * DataTable1 = [ keyword1, 1550 visits]
+ * 				[ keyword2, 154 visits ]
+ * DataTable2 = [ keyword1, 1004 visits ]
+ * 				[ keyword3, 659 visits ]
+ * DataTable_Compare = result of comparison of table1 with table2
+ * 						[ keyword1, +154% ]
+ * 						[ keyword2, +1000% ]
+ * 						[ keyword3, -430% ]
+ * 
+ * @see Piwik_DataTable_Row A Piwik_DataTable is composed of Piwik_DataTable_Row
+ * 
+ * @package Piwik
+ * @subpackage Piwik_DataTable
+ * 
+ */
+
+class Piwik_DataTable
+{	
+	/**
+	 * Array of Piwik_DataTable_Row
+	 *
+	 * @var array
+	 */
+	protected $rows = array();
+	
+	/**
+	 * Id assigned to the DataTable, used to lookup the table using the DataTable_Manager
+	 *
+	 * @var int
+	 */
+	protected $currentId;
+	
+	/**
+	 * Current depth level of this data table
+	 * 0 is the parent data table
+	 * 
+	 * @var int
+	 */
+	protected $depthLevel = 0;
+	
+	/**
+	 * This flag is set to false once we modify the table in a way that outdates the index 
+	 * 
+	 * @var bool
+	 */
+	protected $indexNotUpToDate = false;
+	
+	/**
+	 * List of Piwik_DataTable_Filter queued to this table
+	 *
+	 * @var array
+	 */
+	protected $queuedFilters = array();
+	
+	/**
+	 * We keep track of the number of rows before applying the LIMIT filter that deletes some rows
+	 *
+	 * @var int
+	 */
+	protected $rowsCountBeforeLimitFilter = 0;
+	
+	/**
+	 * Defaults to false for performance reasons (most of the time we don't need recursive sorting so we save a looping over the dataTable)
+	 *
+	 * @var bool
+	 */
+	protected $enableRecursiveSort = false;
+
+	/*
+	 * @var Piwik_DataTable_Row
+	 */
+	protected $summaryRow = null;
+
+	const ID_SUMMARY_ROW = -1;
+	const LABEL_SUMMARY_ROW = -1;
+
+	/**
+	 * Maximum nesting level
+	 * 
+	 * @var int
+	 */
+	const MAXIMUM_DEPTH_LEVEL_ALLOWED = 20;
+
+	/**
+	 * Builds the DataTable, registers itself to the manager
+	 *
+	 */
+	public function __construct()
+	{
+		$this->currentId = Piwik_DataTable_Manager::getInstance()->addTable($this);
+	}
+
+	/**
+	 * Sort the dataTable rows using the php callback function 
+	 *
+	 * @param string $functionCallback
+	 */
+	public function sort( $functionCallback )
+	{
+		$this->indexNotUpToDate = true;
+		usort( $this->rows, $functionCallback );
+		
+		if($this->enableRecursiveSort === true)
+		{
+			foreach($this->getRows() as $row)
+			{
+				if(($idSubtable = $row->getIdSubDataTable()) !== null)
+				{
+					$table = Piwik_DataTable_Manager::getInstance()->getTable($idSubtable);
+					$table->enableRecursiveSort();
+					$table->sort($functionCallback);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Enables the recursive sort. Means that when using $table->sort() 
+	 * it will also sort all subtables using the same callback
+	 * 
+	 * @return void
+	 */
+	public function enableRecursiveSort()
+	{
+		$this->enableRecursiveSort = true;
+	}
+
+	/**
+	 * Returns the number of rows before we applied the limit filter
+	 *
+	 * @return int
+	 */
+	public function getRowsCountBeforeLimitFilter()
+	{
+		$toReturn = $this->rowsCountBeforeLimitFilter;
+		if($toReturn == 0)
+		{
+			return $this->getRowsCount();
+		}
+		return $toReturn;
+	}
+
+	/**
+	 * Saves the current number of rows
+	 * 
+	 * @return void
+	 *
+	 */
+	function setRowsCountBeforeLimitFilter()
+	{
+		$this->rowsCountBeforeLimitFilter = $this->getRowsCount();
+	}
+
+	/**
+	 * Queue a DataTable_Filter that will be applied at the end of the process 
+	 * (just before sending the datatable back to the browser (or API, etc.)
+	 *
+	 * @param string $className The class name of the filter, eg. Piwik_DataTable_Filter_Limit
+	 * @param array $parameters The parameters to give to the filter, eg. array( $offset, $limit) for the filter Piwik_DataTable_Filter_Limit
+	 */
+	public function queueFilter( $className, $parameters = array() )
+	{
+		if(!is_array($parameters))
+		{
+			$parameters = array($parameters);
+		}
+		$this->queuedFilters[] = array('className' => $className, 'parameters' => $parameters);
+	}
+
+	/**
+	 * Apply all filters that were previously queued to this table
+	 * @see queueFilter()
+	 * @return void
+	 */
+	public function applyQueuedFilters()
+	{
+		foreach($this->queuedFilters as $filter)
+		{
+			if($filter['className'] == 'Piwik_DataTable_Filter_Limit')
+			{
+				$this->setRowsCountBeforeLimitFilter();
+			}
+			
+			$reflectionObj = new ReflectionClass($filter['className']);
+			
+			// the first parameter of a filter is the DataTable
+			// we add the current datatable as the parameter
+			$filter['parameters'] = array_merge(array($this), $filter['parameters']);
+			
+			$filter = $reflectionObj->newInstanceArgs($filter['parameters']); 
+		}
+		$this->queuedFilters = array();
+	}
+
+	/**
+	 * Adds a new DataTable to this DataTable
+	 * Go through all the rows of the new DataTable and applies the algorithm:
+	 * - if a row in $table doesnt exist in $this we add the new row to $this
+	 * - if a row exists in both $table and $this we sum the columns values into $this
+	 * - if a row in $this doesnt exist in $table we keep the row of $this without modification
+	 * 
+	 * A common row to 2 DataTable is defined by the same label
+	 * 	
+	 * @example @see tests/plugins/DataTable.test.php
+	 */
+	public function addDataTable( Piwik_DataTable $tableToSum )
+	{
+		foreach($tableToSum->getRows() as $row)
+		{
+			$labelToLookFor = $row->getColumn('label');
+			$rowFound = $this->getRowFromLabel( $labelToLookFor );
+			if($rowFound === false)
+			{
+				if( $labelToLookFor === self::LABEL_SUMMARY_ROW )
+				{
+					$this->addSummaryRow($row );
+				}
+				else
+				{
+					$this->addRow( $row );
+				}
+			}
+			else
+			{
+				$rowFound->sumRow( $row );
+
+				// if the row to add has a subtable whereas the current row doesn't
+				// we simply add it (cloning the subtable)
+				// if the row has the subtable already 
+				// then we have to recursively sum the subtables
+				if(($idSubTable = $row->getIdSubDataTable()) !== null)
+				{
+					$rowFound->sumSubtable( Piwik_DataTable_Manager::getInstance()->getTable($idSubTable) );
+				}
+			}
+		}
+	}
+
+	/**
+	 * Returns the Piwik_DataTable_Row that has a column 'label' with the value $label
+	 *
+	 * @param string $label Value of the column 'label' of the row to return
+	 * @return Piwik_DataTable_Row|false The row if found, false otherwise
+	 */
+	public function getRowFromLabel( $label )
+	{
+		if($this->indexNotUpToDate)
+		{
+			$this->rebuildIndex();
+		}
+		
+		if($label === self::LABEL_SUMMARY_ROW
+			&& !is_null($this->summaryRow))
+		{
+			return $this->summaryRow;
+		}
+		
+		$label = (string)$label;
+		if(!isset($this->rowsIndexByLabel[$label]))
+		{
+			return false;
+		}
+		return $this->rows[$this->rowsIndexByLabel[$label]];
+	}
+
+	/**
+	 * Rebuilds the index used to lookup a row by label
+	 *
+	 * @return void
+	 */
+	private function rebuildIndex()
+	{
+		foreach($this->rows as $id => $row)
+		{
+			$label = $row->getColumn('label');
+		
+			if($label !== false)
+			{
+				$this->rowsIndexByLabel[$label] = $id;
+			}
+		}
+		$this->indexNotUpToDate = false;
+	}
+
+	/**
+	 * Returns the ith row in the array
+	 *
+	 * @param int $id
+	 * @return Piwik_DataTable_Row or false if not found
+	 */
+	public function getRowFromId($id)
+	{
+		if(!isset($this->rows[$id]))
+		{
+			if($id == self::ID_SUMMARY_ROW
+				&& !is_null($this->summaryRow))
+			{
+				return $this->summaryRow;
+			}
+			return false;
+		}
+		return $this->rows[$id];
+	}
+
+	/**
+	 * Shortcut function used for performance reasons
+	 * 
+	 * @param Piwik_DataTable_Row $row to add at the end of the array
+	 */
+	public function addRow( Piwik_DataTable_Row $row )
+	{
+		$this->rows[] = $row;	
+		$this->indexNotUpToDate = true;
+	}
+
+	/**
+	 * Sets the summary row (a dataTable can have only one summary row)
+	 *
+	 * @param Piwik_DataTable_Row $row
+	 */
+	public function addSummaryRow( Piwik_DataTable_Row $row )
+	{
+		$this->summaryRow = $row;
+	}
+
+	/**
+	 * Returns the dataTable ID
+	 *
+	 * @return int
+	 */
+	public function getId()
+	{
+		return $this->currentId;
+	}
+
+	/**
+	 * Adds a new row from a PHP array data structure
+	 * 
+	 * @param array $row, eg. array(Piwik_DataTable_Row::COLUMNS => array( 'visits' => 13, 'test' => 'toto'),)
+	 */
+	public function addRowFromArray( $row )
+	{
+		$this->loadFromArray(array($row));
+	}
+
+	/**
+	 * Adds a new row a PHP array data structure
+	 * 
+	 * @param array $row, eg.  array('name' => 'google analytics', 'license' => 'commercial')
+	 */
+	public function addRowFromSimpleArray( $row )
+	{
+		$this->loadFromSimpleArray(array($row));
+	}
+
+	/**
+	 * Returns the array of Piwik_DataTable_Row
+	 * 
+	 * @return array of Piwik_DataTable_Row
+	 */
+	public function getRows()
+	{
+		if(is_null($this->summaryRow))
+		{
+			return $this->rows;
+		}
+		else
+		{
+			return $this->rows + array(self::ID_SUMMARY_ROW => $this->summaryRow);
+		}
+	}
+
+	/**
+	 * Returns the number of rows in the table
+	 * 
+	 * @return int
+	 */
+	public function getRowsCount()
+	{
+		$count = count($this->rows);
+		if(is_null($this->summaryRow))
+		{
+			return $count;
+		}
+		else
+		{
+			return $count + 1;
+		}
+	}
+
+	/**
+	 * Returns the first row of the DataTable
+	 *
+	 * @return Piwik_DataTable_Row
+	 */
+	public function getFirstRow()
+	{
+		if(count($this->rows) == 0)
+		{
+			if(!is_null($this->summaryRow))
+			{
+				return $this->summaryRow;
+			}
+			return false;
+		}
+		$row = array_slice($this->rows, 0, 1);
+		return $row[0];
+	}
+
+	/**
+	 * Returns the last row of the DataTable
+	 *
+	 * @return Piwik_DataTable_Row
+	 */
+	public function getLastRow()
+	{
+		if(!is_null($this->summaryRow))
+		{
+			return $this->summaryRow;
+		}
+		
+		if(count($this->rows) == 0)
+		{
+			return false;
+		}
+		$row = array_slice($this->rows, -1);
+		return $row[0];
+	}
+
+	/**
+	 * Returns the sum of the number of rows of all the subtables 
+	 * 		+ the number of rows in the parent table
+	 * 
+	 * @return int
+	 */
+	public function getRowsCountRecursive()
+	{
+		$totalCount = 0;
+		foreach($this->rows as $row)
+		{
+			if(($idSubTable = $row->getIdSubDataTable()) !== null)
+			{
+				$subTable = Piwik_DataTable_Manager::getInstance()->getTable($idSubTable);
+				$count = $subTable->getRowsCountRecursive();
+				$totalCount += $count;
+			}
+		}
+		
+		$totalCount += $this->getRowsCount();
+		return $totalCount;
+	}
+
+	/**
+	 * Delete a given column $name in all the rows
+	 *
+	 * @param string $name
+	 */
+	public function deleteColumn( $name )
+	{
+		foreach($this->getRows() as $row)
+		{
+			$row->deleteColumn($name);
+		}
+		if(!is_null($this->summaryRow))
+		{
+			$this->summaryRow->deleteColumn($name);
+		}
+	}
+
+	/**
+	 * Deletes the ith row 
+	 *
+	 * @param int $key
+	 * @throws Exception if the row $id cannot be found
+	 */
+	public function deleteRow( $id )
+	{
+		if($id === self::ID_SUMMARY_ROW)
+		{
+			$this->summaryRow = null;
+			return;
+		}
+		if(!isset($this->rows[$id]))
+		{
+			throw new Exception("Trying to delete unknown row with idkey = $id");
+		}
+		unset($this->rows[$id]);
+	}
+
+	/**
+	 * Deletes all row from offset, offset + limit.
+	 * If limit is null then limit = $table->getRowsCount()
+	 *
+	 * @param int $offset
+	 * @param int $limit
+	 */
+	public function deleteRowsOffset( $offset, $limit = null )
+	{
+		if($limit === 0)
+		{
+			return;
+		}
+
+		$count = $this->getRowsCount();
+		if($offset >= $count)
+		{
+			return;
+		}
+
+		// if we delete until the end, we delete the summary row as well
+		if( is_null($limit)
+			|| $limit >= $count )
+		{
+			$this->summaryRow = null;
+		}
+
+		if(is_null($limit))
+		{
+			array_splice($this->rows, $offset);
+		}
+		else
+		{
+			array_splice($this->rows, $offset, $limit);
+		}
+	}
+
+	/**
+	 * Deletes the rows from the list of rows ID 
+	 *
+	 * @param array $aKeys ID of the rows to delete
+	 * @throws Exception if any of the row to delete couldn't be found
+	 */
+	public function deleteRows( array $aKeys )
+	{
+		foreach($aKeys as $key)
+		{
+			$this->deleteRow($key);
+		}
+	}
+
+	/**
+	 * Returns a simple output of the DataTable for easy visualization
+	 * Example: echo $datatable;
+	 *
+	 * @return string
+	 */
+	public function __toString()
+	{
+		$renderer = new Piwik_DataTable_Renderer_Console($this);
+		return (string)$renderer;
+	}
+
+	/**
+	 * Returns true if both DataTable are exactly the same.
+	 * Used in unit tests.
+	 * 
+	 * @param Piwik_DataTable $table1
+	 * @param Piwik_DataTable $table2
+	 * @return bool
+	 */
+	static public function isEqual(Piwik_DataTable $table1, Piwik_DataTable $table2)
+	{
+		$rows1 = $table1->getRows();
+		$rows2 = $table2->getRows();
+		
+		$table1->rebuildIndex();
+		$table2->rebuildIndex();
+		
+		$countrows1 = $table1->getRowsCount();
+		$countrows2 = $table2->getRowsCount();
+		
+		if($countrows1 != $countrows2)
+		{
+			return false;
+		}
+		
+		foreach($rows1 as $row1)
+		{
+			$row2 = $table2->getRowFromLabel($row1->getColumn('label'));
+			if($row2 === false)
+			{
+				return false;
+			}
+			if( !Piwik_DataTable_Row::isEqual($row1,$row2) )
+			{
+				return false;
+			}
+		}
+		
+		return true;
+	}
+
+	/**
+	 * The serialization returns a one dimension array containing all the 
+	 * serialized DataTable contained in this DataTable.
+	 * We save DataTable in serialized format in the Database.
+	 * Each row of this returned PHP array will be a row in the DB table.
+	 * 
+	 * The keys of the array are very important as they are used to define the DataTable
+	 * 
+	 * IMPORTANT: The main table (level 0, parent of all tables) will always be indexed by 0
+	 * 	even it was created after some other tables.
+	 * 	It also means that all the parent tables (level 0) will be indexed with 0 in their respective 
+	 *  serialized arrays. You should never lookup a parent table using the getTable( $id = 0) as it 
+	 *  won't work.
+	 * 
+	 * @throws Exception if an infinite recursion is found (a table row's has a subtable that is one of its parent table)
+	 * @param int If not null, defines the number of rows maximum of the serialized dataTable
+	 * 	          If $addSummaryRowAfterNRows is less than the size of the table, a SummaryRow will be added at the end of the table, that
+	 *            is the sum of the values of all the rows after the Nth row. All the rows after the Nth row will be deleted.
+	 * 
+	 * @return array Serialized arrays	
+	 * 			array( 	// Datatable level0
+	 * 					0 => 'eghuighahgaueytae78yaet7yaetae', 
+	 * 
+	 * 					// first Datatable level1
+	 * 					1 => 'gaegae gh gwrh guiwh uigwhuige',
+	 * 					
+	 * 					//second Datatable level1 
+	 * 					2 => 'gqegJHUIGHEQjkgneqjgnqeugUGEQHGUHQE',  
+	 * 					
+	 * 					//first Datatable level3 (child of second Datatable level1 for example)
+ 	 *					3 => 'eghuighahgaueytae78yaet7yaetaeGRQWUBGUIQGH&QE',
+	 * 					);
+	 * 
+	 */
+	public function getSerialized( $maximumRowsInDataTable = null, $maximumRowsInSubDataTable = null )
+	{
+		static $depth = 0;
+		
+		if($depth > self::MAXIMUM_DEPTH_LEVEL_ALLOWED)
+		{
+			throw new Exception("Maximum recursion level of ".self::MAXIMUM_DEPTH_LEVEL_ALLOWED. " reached. You have probably set a DataTable_Row with an associated DataTable which belongs already to its parent hierarchy.");
+		}
+		
+		if( !is_null($maximumRowsInDataTable) )
+		{
+			$filter = new Piwik_DataTable_Filter_AddSummaryRow($this, $maximumRowsInDataTable - 1);
+		}
+		
+		// For each row, get the serialized row
+		// If it is associated to a sub table, get the serialized table recursively ;
+		// but returns all serialized tables and subtable in an array of 1 dimension!
+		$aSerializedDataTable = array();
+		foreach($this->rows as $row)
+		{
+			if(($idSubTable = $row->getIdSubDataTable()) !== null)
+			{
+				$subTable = Piwik_DataTable_Manager::getInstance()->getTable($idSubTable);
+				$depth++;
+				$aSerializedDataTable = $aSerializedDataTable + $subTable->getSerialized( $maximumRowsInSubDataTable, $maximumRowsInSubDataTable );
+				$depth--;
+			}
+		}
+		// we load the current Id of the DataTable
+		$forcedId = $this->getId();
+		
+		// if the datatable is the parent we force the Id at 0 (this is part of the specification)
+		if($depth == 0)
+		{
+			$forcedId = 0;
+		}
+		
+		// we then serialize the rows and store them in the serialized dataTable
+		$aSerializedDataTable[$forcedId] = serialize($this->rows + array( self::ID_SUMMARY_ROW => $this->summaryRow));
+		
+		return $aSerializedDataTable;
+	}
+
+	 /**
+	  * Load a serialized string of a datatable.
+	  * 
+	  * Does not load recursively all the sub DataTable.
+	  * They will be loaded only when requesting them specifically.
+	  * 
+	  * The function creates all the necessary DataTable_Row
+	  * 
+	  * @param string Serialized string of a datatable
+	  * @return void
+	  */
+	public function loadFromSerialized( $stringSerialized )
+	{
+		$serialized = unserialize($stringSerialized);
+		if($serialized === false)
+		{
+			throw new Exception("The unserialization has failed!");
+		}
+		$this->loadFromArray($serialized);
+	}
+
+	/**
+	 * Loads the DataTable from a PHP array data structure
+	 * 
+	 * @param array Array with the following structure
+	 * 			array(
+ 	 * 				// row1
+	 * 				array( 
+	 * 				Piwik_DataTable_Row::COLUMNS => array( col1_name => value1, col2_name => value2, ...),
+	 * 				Piwik_DataTable_Row::METADATA => array( metadata1_name => value1,  ...), // see Piwik_DataTable_Row
+	 * 
+	 * 				),
+	 * 					
+	 * 				// row2
+	 * 				array( ... ), 
+	 * 				
+	 * 			)
+	 * @return void
+	 */
+	public function loadFromArray( $array )
+	{
+		foreach($array as $id => $row)
+		{
+			if(is_array($row))
+			{
+				$row = new Piwik_DataTable_Row($row);
+			}
+			if($id == self::ID_SUMMARY_ROW)
+			{
+				$this->summaryRow = $row;
+			}
+			else 
+			{
+				$this->addRow($row);
+			}
+		}
+	}
+
+	/**
+	 * Loads the data from a simple php array.
+	 * Basically maps a simple multidimensional php array to a DataTable.
+	 * Not recursive (if a row contains a php array itself, it won't be loaded)
+	 * 
+	 * @param array Array with the simple structure:
+	 * 		array(
+	 * 			array( col1_name => valueA, col2_name => valueC, ...),
+	 * 			array( col1_name => valueB, col2_name => valueD, ...), 
+	 *		)
+	 */
+	public function loadFromSimpleArray( $array )
+	{
+		if(count($array) === 0)
+		{
+			return;
+		}
+		
+		// we define an exception we may throw if at one point we notice that we cannot handle the data structure
+		$e = new Exception(" Data structure returned is not convertible in the requested format.".
+						" Try to call this method with the parameters '&format=original&serialize=1'".
+						"; you will get the original php data structure serialized.".
+						" The data structure looks like this: \n \$data = " . var_export($array, true) . "; ");
+				
+		
+		// first pass to see if the array has the structure
+		// array(col1_name => val1, col2_name => val2, etc.)
+		// with val* that are never arrays (only strings/numbers/bool/etc.)
+		// if we detect such a "simple" data structure we convert it to a row with the correct columns' names
+		$thisIsNotThatSimple = false;
+		
+		foreach($array as $columnName => $columnValue )
+		{
+			if(is_array($columnValue) || is_object($columnValue)) 
+			{
+				$thisIsNotThatSimple = true;
+				break;
+			}
+		}
+		if($thisIsNotThatSimple === false)
+		{
+			// case when the array is indexed by the default numeric index
+			if( array_keys($array) == array_keys(array_fill(0, count($array), true)) )
+			{
+				foreach($array as $row)
+				{
+					$this->addRow( new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => array($row) ) ) );					
+				}
+			}
+			else
+			{
+				$this->addRow( new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => $array ) ) );
+			}
+			// we have converted our simple array to one single row
+			// => we exit the method as the job is now finished 
+			return;
+		}
+		
+		
+		foreach($array as $key => $row)
+		{
+			// stuff that looks like a line
+			if(is_array($row))
+			{
+				/**
+				 * We make sure we can convert this PHP array without losing information.
+				 * We are able to convert only simple php array (no strings keys, no sub arrays, etc.)
+				 * 
+				 */
+				
+				// if the key is a string it means that some information was contained in this key. 
+				// it cannot be lost during the conversion. Because we are not able to handle properly
+				// this key, we throw an explicit exception.
+				if(is_string($key))
+				{
+					throw $e;
+				}
+				// if any of the sub elements of row is an array we cannot handle this data structure...
+				foreach($row as $subRow)
+				{
+					if(is_array($subRow))
+					{
+						throw $e;						
+					}
+				}
+				$row = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => $row ) );		
+			}
+			// other (string, numbers...) => we build a line from this value
+			else
+			{
+				$row = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => array($key => $row)) );
+			}				
+			$this->addRow($row);
+		}
+	}
+
+	/**
+	 * Rewrites the input $array 
+	 * array (
+	 * 	 LABEL => array(col1 => X, col2 => Y),
+	 * 	 LABEL2 => array(col1 => X, col2 => Y),
+	 * )
+	 * 
+	 * to the structure 
+	 * array (
+	 * 	 array( Piwik_DataTable_Row::COLUMNS => array('label' => LABEL, col1 => X, col2 => Y)),
+	 * 	 array( Piwik_DataTable_Row::COLUMNS => array('label' => LABEL2, col1 => X, col2 => Y)),
+	 * )
+	 * 
+	 * The optional parameter $subtablePerLabel is an array of subTable associated to the rows of the $array
+	 * For example if $subtablePerLabel is given
+	 * array(
+	 * 		LABEL => #Piwik_DataTable_ForLABEL,
+	 * 		LABEL2 => #Piwik_DataTable_ForLABEL2,
+	 * )
+	 * 
+	 * the $array would become 
+	 * array (
+	 * 	 array( 	Piwik_DataTable_Row::COLUMNS => array('label' => LABEL, col1 => X, col2 => Y),
+	 * 				Piwik_DataTable_Row::DATATABLE_ASSOCIATED => #ID DataTable For LABEL
+	 * 		),
+	 * 	 array( 	Piwik_DataTable_Row::COLUMNS => array('label' => LABEL2, col1 => X, col2 => Y)
+	 * 				Piwik_DataTable_Row::DATATABLE_ASSOCIATED => #ID2 DataTable For LABEL2
+	 * 		),
+	 * )
+	 * 
+	 * @param array $array See method description
+	 * @param array|null $subtablePerLabel see method description
+	 * 
+	 * @return void
+	 */
+	public function loadFromArrayLabelIsKey( $array, $subtablePerLabel = null)
+	{
+		$cleanRow = array();
+		foreach($array as $label => $row)
+		{
+			// we make sure that the label column is first in the list! 
+			// important for the UI javascript mainly...
+			// array_merge doesn't work here as it reindex the numeric value
+			// see the test testMergeArray in PHP_Related.test.php
+			$cleanRow[Piwik_DataTable_Row::COLUMNS] = array('label' => $label) + $row;
+			if(!is_null($subtablePerLabel)
+				// some rows of this table don't have subtables 
+				// (for examplecase of the campaign without keywords )
+				&& isset($subtablePerLabel[$label]) 
+			)
+			{
+				$cleanRow[Piwik_DataTable_Row::DATATABLE_ASSOCIATED] = $subtablePerLabel[$label];
+			}
+			$this->addRow( new Piwik_DataTable_Row($cleanRow) );
+		}
+	}
+
+	/**
+	 * At destruction we try to free memory
+	 * But php doesn't give us much control on this
+	 */
+	public function __destruct()
+	{
+		unset($this->rows);
+	}
+	
+}
diff --git a/core/DataTable/Array.php b/core/DataTable/Array.php
new file mode 100644
index 0000000000..c4624d0840
--- /dev/null
+++ b/core/DataTable/Array.php
@@ -0,0 +1,152 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Simple.php 168 2008-01-14 05:26:43Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * The DataTable_Array is a way to store an array of dataTable.
+ * The Piwik_DataTable_Array implements some of the features of the Piwik_DataTable such as queueFilter, getRowsCount.
+ * 
+ * @package Piwik_DataTable
+ */
+class Piwik_DataTable_Array
+{
+	/**
+	 * Used to store additional information about the DataTable Array.
+	 * For example if the Array is used to store multiple DataTable of UserCountry,
+	 * we can add the metadata of the 'idSite' they refer to, so we can access it later if necessary.
+	 *
+	 * @var array of mixed
+	 */
+	public $metadata = array();
+	
+	/**
+	 * Array containing the DataTable withing this Piwik_DataTable_Array
+	 *
+	 * @var array of Piwik_DataTable
+	 */
+	protected $array = array();
+	
+	/**
+	 * This is the label used to index the tables.
+	 * For example if the tables are indexed using the timestamp of each period
+	 * eg. $this->array[1045886960] = new Piwik_DataTable;
+	 * the keyName would be 'timestamp'.
+	 * 
+	 * This label is used in the Renderer (it becomes a column name or the XML description tag)
+	 *
+	 * @var string
+	 */
+	protected $keyName = 'defaultKeyName';
+	
+	/**
+	 * Returns the keyName string @see self::$keyName
+	 *
+	 * @return string
+	 */
+	public function getKeyName()
+	{
+		return $this->keyName;
+	}
+	
+	/**
+	 * Set the keyName @see self::$keyName
+	 *
+	 * @param string $name
+	 */
+	public function setKeyName($name)
+	{
+		$this->keyName = $name;
+	}
+	
+	/**
+	 * Returns the number of DataTable in this DataTable_Array
+	 *
+	 * @return int
+	 */
+	public function getRowsCount()
+	{
+		return count($this->array);
+	}
+	
+	/**
+	 * Queue a filter to the DataTable_Array will queue this filter to every DataTable of the DataTable_Array.
+	 *
+	 * @param string $className Filter name, eg. Piwik_DataTable_Filter_Limit
+	 * @param array $parameters Filter parameters, eg. array( 50, 10 )
+	 * 
+	 * @return void
+	 */
+	public function queueFilter( $className, $parameters = array() )
+	{
+		foreach($this->array as $table)
+		{
+			$table->queueFilter($className, $parameters);
+		}
+	}
+	
+	/**
+	 * Apply the filters previously queued to each of the DataTable of this DataTable_Array.
+	 *
+	 * @return void
+	 */
+	public function applyQueuedFilters()
+	{
+		foreach($this->array as $table)
+		{
+			$table->applyQueuedFilters();
+		}
+	}
+	
+	/**
+	 * Returns the array of DataTable
+	 *
+	 * @return array of Piwik_DataTable
+	 */
+	public function getArray()
+	{
+		return $this->array;
+	}
+	
+	/**
+	 * Adds a new DataTable to the DataTable_Array
+	 *
+	 * @param Piwik_DataTable $table
+	 * @param string $label Label used to index this table in the array
+	 */
+	public function addTable( $table, $label )
+	{
+		$this->array[$label] = $table;
+	}
+	
+	/**
+	 * Returns a string output of this DataTable_Array (applying the default renderer to every DataTable
+	 * of this DataTable_Array).
+	 *
+	 * @return string
+	 */
+	public function __toString()
+	{
+		$renderer = new Piwik_DataTable_Renderer_Console($this);
+		return (string)$renderer;
+	}
+
+	/**
+	 * @see Piwik_DataTable::enableRecursiveSort()
+	 */
+	public function enableRecursiveSort()
+	{
+		foreach($this->array as $table)
+		{
+			$table->enableRecursiveSort();
+		}
+	}
+}
+
+
diff --git a/core/DataTable/Filter.php b/core/DataTable/Filter.php
new file mode 100644
index 0000000000..9394b88be1
--- /dev/null
+++ b/core/DataTable/Filter.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Filter.php 519 2008-06-09 01:59:24Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * A filter is applied instantly to a given DataTable and can 
+ * - remove rows 
+ * - change columns values (lowercase the strings, truncate, etc.)
+ * - add/remove columns or metadata (compute percentage values, add an 'icon' metadata based on the label, etc.)
+ * - add/remove/edit sub DataTable associated to some rows
+ * - whatever you can imagine
+ * 
+ * The concept is very simple: the filter is given the DataTable 
+ * and can do whatever is necessary on the data (in the filter() method).
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter
+ */
+abstract class Piwik_DataTable_Filter
+{
+	/*
+	 * @var Piwik_DataTable
+	 */
+	protected $table;
+	
+	public function __construct($table)
+	{
+		if(!($table instanceof Piwik_DataTable))
+		{
+			throw new Exception("The filter accepts only a Piwik_DataTable object.");
+		}
+		$this->table = $table;
+	}
+	
+	abstract protected function filter();
+}
+
+require_once "DataTable/Filter/ColumnCallbackDeleteRow.php";
+require_once "DataTable/Filter/ColumnCallbackAddMetadata.php";
+require_once "DataTable/Filter/ColumnCallbackReplace.php";
+require_once "DataTable/Filter/MetadataCallbackAddMetadata.php";
+require_once "DataTable/Filter/AddConstantMetadata.php";
+require_once "DataTable/Filter/Null.php";
+require_once "DataTable/Filter/ExcludeLowPopulation.php";
+require_once "DataTable/Filter/Limit.php";
+require_once "DataTable/Filter/Pattern.php";
+require_once "DataTable/Filter/PatternRecursive.php";
+require_once "DataTable/Filter/ReplaceColumnNames.php";
+require_once "DataTable/Filter/Sort.php";
+require_once "DataTable/Filter/AddSummaryRow.php";
+require_once "DataTable/Filter/ReplaceSummaryRowLabel.php";
diff --git a/core/DataTable/Filter/AddConstantMetadata.php b/core/DataTable/Filter/AddConstantMetadata.php
new file mode 100644
index 0000000000..d306ff9950
--- /dev/null
+++ b/core/DataTable/Filter/AddConstantMetadata.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: AddConstantMetadata.php 519 2008-06-09 01:59:24Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * Add a new metadata column to the table.
+ * 
+ * This is used to add a column containing the logo width and height of the countries flag icons.
+ * This value is fixed for all icons so we simply add the same value for all rows.
+ *  
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter 
+ */
+class Piwik_DataTable_Filter_AddConstantMetadata extends Piwik_DataTable_Filter
+{
+	private $metadataToRead;
+	private $functionToApply;
+	private $metadataToAdd;
+	
+	public function __construct( $table, $metadataName, $metadataValue )
+	{
+		parent::__construct($table);
+		$this->name = $metadataName;
+		$this->value = $metadataValue;
+		$this->filter();
+	}
+	
+	protected function filter()
+	{
+		foreach($this->table->getRows() as $row)
+		{
+			$row->addMetadata($this->name, $this->value);
+		}
+	}
+}
+
diff --git a/core/DataTable/Filter/AddSummaryRow.php b/core/DataTable/Filter/AddSummaryRow.php
new file mode 100644
index 0000000000..b8d956dd43
--- /dev/null
+++ b/core/DataTable/Filter/AddSummaryRow.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Limit.php 168 2008-01-14 05:26:43Z matt $
+ *
+ * @package Piwik_DataTable
+ */
+
+/**
+ * Add a new row to the table containing a summary
+ * of the rows from StartRowToSummarize to EndRowToSummarize.
+ * It then deletes the rows from StartRowToSummarize to EndRowToSummarize.
+ * The new row created has a label = 'other'
+ *
+ * This filter is useful to build a more compact view of a table,
+ * keeping the first records unchanged.
+ *
+ * For example we use this for the pie chart, to build the last pie part
+ * which is the sum of all the remaining data after the top 5 data.
+ * This row is assigned a label of 'Others'.
+ *
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter
+ */
+class Piwik_DataTable_Filter_AddSummaryRow extends Piwik_DataTable_Filter
+{
+	public function __construct( $table, $startRowToSummarize, $labelSummaryRow = Piwik_DataTable::LABEL_SUMMARY_ROW, $columnToSortByBeforeTruncating = Piwik_Archive::INDEX_NB_VISITS )
+	{
+		parent::__construct($table);
+		$this->startRowToSummarize = $startRowToSummarize;
+		$this->labelSummaryRow = $labelSummaryRow;
+		$this->columnToSortByBeforeTruncating = $columnToSortByBeforeTruncating;
+
+		if($table->getRowsCount() > $startRowToSummarize + 1)
+		{
+			$this->filter();
+		}
+	}
+
+	protected function filter()
+	{
+		$filter = new Piwik_DataTable_Filter_Sort($this->table, $this->columnToSortByBeforeTruncating, 'desc');
+
+		$rows = $this->table->getRows();
+		$count = $this->table->getRowsCount();
+		$newRow = new Piwik_DataTable_Row();
+		for($i = $this->startRowToSummarize; $i < $count; $i++)
+		{
+			if(!isset($rows[$i]))
+			{
+				// case when the last row is a summary row, it is not indexed by $cout but by Piwik_DataTable::ID_SUMMARY_ROW
+				$summaryRow = $this->table->getRowFromId(Piwik_DataTable::ID_SUMMARY_ROW);
+				$newRow->sumRow($summaryRow);
+			}
+			else
+			{
+				$newRow->sumRow($rows[$i]);
+			}
+		}
+		$newRow->addColumn('label', $this->labelSummaryRow);
+		$filter = new Piwik_DataTable_Filter_Limit($this->table, 0, $this->startRowToSummarize);
+		$this->table->addSummaryRow($newRow);
+	}
+}
diff --git a/core/DataTable/Filter/ColumnCallbackAddMetadata.php b/core/DataTable/Filter/ColumnCallbackAddMetadata.php
new file mode 100644
index 0000000000..36558c357f
--- /dev/null
+++ b/core/DataTable/Filter/ColumnCallbackAddMetadata.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: ColumnCallbackAddMetadata.php 515 2008-06-08 20:03:21Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+
+/**
+ * Add a new 'metadata' column to the table based on the value resulting 
+ * from a callback function with the parameter being another column's value
+ * 
+ * For example from the "label" column we can to create an "icon" 'metadata' column 
+ * with the icon URI built from the label (LINUX => UserSettings/icons/linux.png)
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter 
+ */
+
+class Piwik_DataTable_Filter_ColumnCallbackAddMetadata extends Piwik_DataTable_Filter
+{
+	private $columnToRead;
+	private $functionToApply;
+	private $metadataToAdd;
+	
+	public function __construct( $table, $columnToRead, $metadataToAdd, $functionToApply )
+	{
+		parent::__construct($table);
+		$this->functionToApply = $functionToApply;
+		$this->columnToRead = $columnToRead;
+		$this->metadataToAdd = $metadataToAdd;
+		$this->filter();
+	}
+	
+	protected function filter()
+	{
+		foreach($this->table->getRows() as $key => $row)
+		{
+			$oldValue = $row->getColumn($this->columnToRead);
+			$newValue = call_user_func( $this->functionToApply, $oldValue);
+			$row->addMetadata($this->metadataToAdd, $newValue);
+		}
+	}
+}
+
diff --git a/core/DataTable/Filter/ColumnCallbackDeleteRow.php b/core/DataTable/Filter/ColumnCallbackDeleteRow.php
new file mode 100644
index 0000000000..33c743c890
--- /dev/null
+++ b/core/DataTable/Filter/ColumnCallbackDeleteRow.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: ColumnCallbackDeleteRow.php 444 2008-04-11 13:38:22Z johmathe $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * Delete all rows for which a given function returns false for a given column.
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter 
+ */
+class Piwik_DataTable_Filter_ColumnCallbackDeleteRow extends Piwik_DataTable_Filter
+{
+	private $columnToFilter;
+	private $function;
+	
+	public function __construct( $table, $columnToFilter, $function )
+	{
+		parent::__construct($table);
+		$this->function = $function;
+		$this->columnToFilter = $columnToFilter;
+		$this->filter();
+	}
+	
+	protected function filter()
+	{
+		foreach($this->table->getRows() as $key => $row)
+		{
+			$columnValue = $row->getColumn($this->columnToFilter);
+			if( $columnValue !== false 
+				&& !call_user_func( $this->function, $columnValue))
+			{
+				$this->table->deleteRow($key);
+			}
+		}
+	}
+}
+
diff --git a/core/DataTable/Filter/ColumnCallbackReplace.php b/core/DataTable/Filter/ColumnCallbackReplace.php
new file mode 100644
index 0000000000..63d3671f24
--- /dev/null
+++ b/core/DataTable/Filter/ColumnCallbackReplace.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: ColumnCallbackReplace.php 444 2008-04-11 13:38:22Z johmathe $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * Replace a column value with a new value resulting 
+ * from the function called with the column's value
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter 
+ */
+class Piwik_DataTable_Filter_ColumnCallbackReplace extends Piwik_DataTable_Filter
+{
+	private $columnToFilter;
+	private $functionToApply;
+	
+	public function __construct( $table, $columnToFilter, $functionToApply )
+	{
+		parent::__construct($table);
+		$this->functionToApply = $functionToApply;
+		$this->columnToFilter = $columnToFilter;
+		$this->filter();
+	}
+	
+	protected function filter()
+	{
+		foreach($this->table->getRows() as $key => $row)
+		{
+			$oldValue = $row->getColumn($this->columnToFilter);
+			$newValue = call_user_func( $this->functionToApply, $oldValue);
+			$row->setColumn($this->columnToFilter, $newValue);
+		}
+	}
+}
+
diff --git a/core/DataTable/Filter/ExcludeLowPopulation.php b/core/DataTable/Filter/ExcludeLowPopulation.php
new file mode 100644
index 0000000000..cb038d6d78
--- /dev/null
+++ b/core/DataTable/Filter/ExcludeLowPopulation.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: ExcludeLowPopulation.php 519 2008-06-09 01:59:24Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * Delete all rows that have a $columnToFilter value less than the $minimumValue 
+ * 
+ * For example we delete from the countries report table all countries that have less than 3 visits.
+ * It is very useful to exclude noise from the reports.
+ * You can obviously apply this filter on a percentaged column, eg. remove all countries with the column 'percent_visits' less than 0.05
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter 
+ */
+class Piwik_DataTable_Filter_ExcludeLowPopulation extends Piwik_DataTable_Filter
+{
+	static public $minimumValue;
+	public function __construct( $table, $columnToFilter, $minimumValue )
+	{
+		$this->columnToFilter = $columnToFilter;
+		self::$minimumValue = $minimumValue;
+		parent::__construct($table);
+		$this->filter();
+	}
+	
+	function filter()
+	{
+		$function = array("Piwik_DataTable_Filter_ExcludeLowPopulation",
+							"excludeLowPopulation");		
+
+		$filter = new Piwik_DataTable_Filter_ColumnCallbackDeleteRow(
+												$this->table, 
+												$this->columnToFilter, 
+												$function
+											);
+	}
+	
+	static public function excludeLowPopulation($value)
+	{
+		return $value >= self::$minimumValue;
+	}
+}
+
diff --git a/core/DataTable/Filter/Limit.php b/core/DataTable/Filter/Limit.php
new file mode 100644
index 0000000000..b292a39aeb
--- /dev/null
+++ b/core/DataTable/Filter/Limit.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Limit.php 503 2008-06-01 19:16:56Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * Delete all rows from the table that are not in the offset,offset+limit range
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter 
+ */
+
+class Piwik_DataTable_Filter_Limit extends Piwik_DataTable_Filter
+{	
+	/**
+	 * Filter constructor.
+	 * 
+	 * @param Piwik_DataTable $table
+	 * @param int $offset Starting row (indexed from 0)
+	 * @param int $limit Number of rows to keep (specify -1 to keep all rows)
+	 */
+	public function __construct( $table, $offset, $limit = null )
+	{
+		parent::__construct($table);
+		$this->offset = $offset;
+		
+		if(is_null($limit))
+		{
+			$limit = -1;
+		}
+		$this->limit = $limit;
+		
+		$this->filter();
+	}	
+	
+	protected function filter()
+	{
+		$table = $this->table;
+		$rowsCount = $table->getRowsCount();
+		
+		// we delete from 0 to offset
+		$table->deleteRowsOffset( 0, $this->offset );
+		
+		// at this point the array has offset less elements. We delete from limit to the end
+		if( $this->limit >= 0 )
+		{
+			$table->deleteRowsOffset( $this->limit );
+		}
+	}
+}
+
+
diff --git a/core/DataTable/Filter/MetadataCallbackAddMetadata.php b/core/DataTable/Filter/MetadataCallbackAddMetadata.php
new file mode 100644
index 0000000000..8e133923a7
--- /dev/null
+++ b/core/DataTable/Filter/MetadataCallbackAddMetadata.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: MetadataCallbackAddMetadata.php 515 2008-06-08 20:03:21Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * Add a new metadata to the table based on the value resulting 
+ * from a callback function with the parameter being another metadata value
+ * 
+ * For example for the searchEngine we have a "metadata" information that gives 
+ * the URL of the search engine. We use this URL to add a new "metadata" that gives 
+ * the path of the logo for this search engine URL (which has the format URL.png). 
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter 
+ */
+class Piwik_DataTable_Filter_MetadataCallbackAddMetadata extends Piwik_DataTable_Filter
+{
+	private $metadataToRead;
+	private $functionToApply;
+	private $metadataToAdd;
+	
+	public function __construct( $table, $metadataToRead, $metadataToAdd, $functionToApply )
+	{
+		parent::__construct($table);
+		$this->functionToApply = $functionToApply;
+		$this->metadataToRead = $metadataToRead;
+		$this->metadataToAdd = $metadataToAdd;
+		$this->filter();
+	}
+	
+	protected function filter()
+	{
+		foreach($this->table->getRows() as $key => $row)
+		{
+			$oldValue = $row->getMetadata($this->metadataToRead);
+			$newValue = call_user_func( $this->functionToApply, $oldValue);
+			$row->addMetadata($this->metadataToAdd, $newValue);
+		}
+	}
+}
+
diff --git a/core/DataTable/Filter/Null.php b/core/DataTable/Filter/Null.php
new file mode 100644
index 0000000000..723c1a4529
--- /dev/null
+++ b/core/DataTable/Filter/Null.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Null.php 482 2008-05-18 17:22:35Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * Filter template.
+ * You can use it if you want to create a new filter.
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter 
+ */
+class Piwik_DataTable_Filter_Null extends Piwik_DataTable_Filter
+{
+	
+	public function __construct( $table )
+	{
+		parent::__construct($table);
+		$this->filter();
+	}
+	
+	protected function filter()
+	{
+		foreach($this->table->getRows() as $key => $row)
+		{
+		}
+	}
+}
+
diff --git a/core/DataTable/Filter/Pattern.php b/core/DataTable/Filter/Pattern.php
new file mode 100644
index 0000000000..aabccd1911
--- /dev/null
+++ b/core/DataTable/Filter/Pattern.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Pattern.php 444 2008-04-11 13:38:22Z johmathe $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * Delete all rows for which the given $columnToFilter do not contain the $patternToSearch
+ * This filter is to be used on columns containing strings. 
+ * Exemple: fron the keyword report, keep only the rows for which the label contains "piwik"
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter 
+ */
+class Piwik_DataTable_Filter_Pattern extends Piwik_DataTable_Filter
+{
+	private $columnToFilter;
+	private $patternToSearch;
+	
+	public function __construct( $table, $columnToFilter, $patternToSearch )
+	{
+		parent::__construct($table);
+		$this->patternToSearch = $patternToSearch;
+		$this->columnToFilter = $columnToFilter;
+		$this->filter();
+	}
+	
+	protected function filter()
+	{
+		foreach($this->table->getRows() as $key => $row)
+		{
+			if( stripos($row->getColumn($this->columnToFilter), $this->patternToSearch) === false)
+			{
+				$this->table->deleteRow($key);
+			}
+		}
+	}
+}
+
diff --git a/core/DataTable/Filter/PatternRecursive.php b/core/DataTable/Filter/PatternRecursive.php
new file mode 100644
index 0000000000..a0743aa116
--- /dev/null
+++ b/core/DataTable/Filter/PatternRecursive.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: PatternRecursive.php 519 2008-06-09 01:59:24Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * Delete all rows for which 
+ * - the given $columnToFilter do not contain the $patternToSearch 
+ * - AND all the subTables associated to this row do not contain the $patternToSearch
+ * 
+ * This filter is to be used on columns containing strings. 
+ * Exemple: from the pages viewed report, keep only the rows that contain "piwik" or for which a subpage contains "piwik".
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter 
+ */
+class Piwik_DataTable_Filter_PatternRecursive extends Piwik_DataTable_Filter
+{
+	private $columnToFilter;
+	private $patternToSearch;
+	
+	public function __construct( $table, $columnToFilter, $patternToSearch )
+	{
+		parent::__construct($table);
+		$this->patternToSearch = $patternToSearch;//preg_quote($patternToSearch);
+		$this->columnToFilter = $columnToFilter;
+		$this->filter();
+	}
+	
+	protected function filter( $table = null )
+	{
+		if(is_null($table))
+		{
+			$table = $this->table;
+		}
+		$rows = $table->getRows();
+		
+		foreach($rows as $key => $row)
+		{
+			// A row is deleted if
+			// 1 - its label doesnt contain the pattern 
+			// AND 2 - the label is not found in the children
+			$patternNotFoundInChildren = false;
+			
+			try{
+				$idSubTable = $row->getIdSubDataTable();
+				$subTable = Piwik_DataTable_Manager::getInstance()->getTable($idSubTable);
+				
+				// we delete the row if we couldn't find the pattern in any row in the 
+				// children hierarchy
+				if( $this->filter($subTable) == 0 )
+				{
+					$patternNotFoundInChildren = true;
+				}
+			} catch(Exception $e) {
+				// there is no subtable loaded for example
+				$patternNotFoundInChildren = true;
+			}
+
+			if( $patternNotFoundInChildren
+				&& (stripos($row->getColumn($this->columnToFilter), $this->patternToSearch) === false)	
+			)
+			{
+				$table->deleteRow($key);
+			}
+		}
+		
+		return $table->getRowsCount();
+	}
+}
+
diff --git a/core/DataTable/Filter/ReplaceColumnNames.php b/core/DataTable/Filter/ReplaceColumnNames.php
new file mode 100644
index 0000000000..1e5d41a065
--- /dev/null
+++ b/core/DataTable/Filter/ReplaceColumnNames.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: ReplaceColumnNames.php 519 2008-06-09 01:59:24Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * This filter replaces column names using a mapping table that maps from the old name to the new name.
+ * 
+ * Why this filter?
+ * For saving bytes in the database, you can change all the columns labels by an integer value.
+ * Exemple instead of saving 10000 rows with the column name 'nb_uniq_visitors' which would cost a lot of memory,
+ * we map it to the integer 1 before saving in the DB.
+ * After selecting the DataTable from the DB though, you need to restore back the real names so that
+ * it shows nicely in the report (XML for example).
+ * 
+ * You can specify the mapping array to apply in the constructor.
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter 
+ */
+class Piwik_DataTable_Filter_ReplaceColumnNames extends Piwik_DataTable_Filter
+{
+	/*
+	 * Old column name => new column name
+	 */
+	protected $mappingToApply = array(
+				Piwik_Archive::INDEX_NB_UNIQ_VISITORS 	=> 'nb_uniq_visitors',
+				Piwik_Archive::INDEX_NB_VISITS			=> 'nb_visits',
+				Piwik_Archive::INDEX_NB_ACTIONS			=> 'nb_actions',
+				Piwik_Archive::INDEX_MAX_ACTIONS		=> 'max_actions',
+				Piwik_Archive::INDEX_SUM_VISIT_LENGTH	=> 'sum_visit_length',
+				Piwik_Archive::INDEX_BOUNCE_COUNT		=> 'bounce_count',
+			);
+	
+	/**
+	 * @param DataTable Table
+	 * @param array Mapping to apply. Must have the format 	
+	 * 				array( 	OLD_COLUMN_NAME => NEW_COLUMN NAME,
+	 * 						OLD_COLUMN_NAME2 => NEW_COLUMN NAME2,
+	 * 					)
+	 */
+	public function __construct( $table, $mappingToApply = null )
+	{
+		parent::__construct($table);
+		if(!is_null($mappingToApply))
+		{
+			$this->mappingToApply = $mappingToApply;
+		}
+		
+		$this->filter();
+	}
+	
+	protected function filter()
+	{
+		$this->filterTable($this->table);
+	}
+	
+	protected function filterTable($table)
+	{
+		foreach($table->getRows() as $key => $row)
+		{
+			$this->renameColumns($row);
+			
+			try {
+				$subTable = Piwik_DataTable_Manager::getInstance()->getTable( $row->getIdSubDataTable() );
+				$this->filterTable($subTable);
+			} catch(Exception $e){
+				// case idSubTable == null, or if the table is not loaded in memory
+			}
+		}
+	}
+	
+	protected function renameColumns($row) 
+	{
+		$columns = $row->getColumns();
+		foreach($this->mappingToApply as $oldName => $newName)
+		{
+			if(isset($columns[$oldName]))
+			{
+				$columns[$newName] = $columns[$oldName];
+				unset($columns[$oldName]);
+			}
+		}
+		$row->setColumns($columns);
+	}
+}
+
diff --git a/core/DataTable/Filter/ReplaceSummaryRowLabel.php b/core/DataTable/Filter/ReplaceSummaryRowLabel.php
new file mode 100644
index 0000000000..ed3b627ddc
--- /dev/null
+++ b/core/DataTable/Filter/ReplaceSummaryRowLabel.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: ReplaceColumnNames.php 482 2008-05-18 17:22:35Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter 
+ */
+class Piwik_DataTable_Filter_ReplaceSummaryRowLabel extends Piwik_DataTable_Filter
+{
+	public function __construct( $table, $newLabel = null)
+	{
+		parent::__construct($table);
+		if(is_null($newLabel))
+		{
+			$newLabel = Piwik_Translate('General_Others');
+		}
+		$this->newLabel = $newLabel;
+		$this->filter();
+	}
+	
+	protected function filter()
+	{
+		foreach($this->table->getRows() as $row)
+		{
+			if($row->getColumn('label') === Piwik_DataTable::LABEL_SUMMARY_ROW)
+			{
+				$row->setColumn('label', $this->newLabel);
+				break;
+			}
+		}
+	}
+}
+
diff --git a/core/DataTable/Filter/Sort.php b/core/DataTable/Filter/Sort.php
new file mode 100644
index 0000000000..f84ca21b1f
--- /dev/null
+++ b/core/DataTable/Filter/Sort.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Sort.php 519 2008-06-09 01:59:24Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * Sort the DataTable based on the value of column $columnToSort ordered by $order.
+ * Possible to specify a natural sorting (see php.net/natsort for details)
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Filter 
+ */
+class Piwik_DataTable_Filter_Sort extends Piwik_DataTable_Filter
+{
+	protected $columnToSort;
+	protected $order;
+	
+	public function __construct( $table, $columnToSort, $order = 'desc', $naturalSort = false )
+	{
+		parent::__construct($table);
+		
+		// hack... But I can't see how to do properly
+		if($columnToSort == '0')
+		{
+			$columnToSort = 'label';
+		}
+		
+		$this->columnToSort = $columnToSort;
+		$this->naturalSort = $naturalSort;
+		$this->setOrder($order);
+		$this->filter();
+	}
+	
+	function setOrder($order)
+	{
+		if($order == 'asc')
+		{
+			$this->order = 'asc';
+			$this->sign = 1;
+		}
+		else
+		{
+			$this->order = 'desc';
+			$this->sign = -1;
+		}
+	}
+	
+	function sort($a, $b)
+	{
+		return $this->sign * 
+				($a->c[Piwik_DataTable_Row::COLUMNS][$this->columnToSort] 
+					< $b->c[Piwik_DataTable_Row::COLUMNS][$this->columnToSort] 
+				? -1 
+				: 1
+			);
+	}
+	
+	function naturalSort($a, $b)
+	{
+		return $this->sign * strnatcasecmp( 
+				$a->c[Piwik_DataTable_Row::COLUMNS][$this->columnToSort], 
+				$b->c[Piwik_DataTable_Row::COLUMNS][$this->columnToSort]
+			);
+	}
+	
+	
+	function sortString($a, $b)
+	{
+		return $this->sign * 
+				strcasecmp($a->c[Piwik_DataTable_Row::COLUMNS][$this->columnToSort], 
+					$b->c[Piwik_DataTable_Row::COLUMNS][$this->columnToSort] 
+			);
+	}
+	
+	protected function filter()
+	{
+		if($this->table instanceof Piwik_DataTable_Simple)
+		{
+			return;
+		}
+		$rows = $this->table->getRows();
+		
+		if(count($rows) == 0)
+		{
+			return;
+		}
+		$row = current($rows);
+		$value = $row->getColumn($this->columnToSort);
+		
+		if($value === false)
+		{
+			// we don't throw the exception because we sometimes export a DataTable without a column labelled '2'
+			// and when the generic filters tries to sort by default using this column 2, this shouldnt raise an exception...
+			//throw new Exception("The column to sort by '".$this->columnToSort."' is unknown in the row ". implode(array_keys($row->getColumns()), ','));
+			return;
+		}
+		
+		if( Piwik::isNumeric($value))
+		{
+			$methodToUse = "sort";
+		}
+		else
+		{
+			if($this->naturalSort)
+			{
+				$methodToUse = "naturalSort";
+			}
+			else
+			{
+				$methodToUse = "sortString";
+			}
+		}
+		$this->table->sort( array($this,$methodToUse) );
+	}
+}
+
diff --git a/core/DataTable/Manager.php b/core/DataTable/Manager.php
new file mode 100644
index 0000000000..5811cb6068
--- /dev/null
+++ b/core/DataTable/Manager.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Manager.php 519 2008-06-09 01:59:24Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * The DataTable_Manager registers all the instanciated DataTable and provides an 
+ * easy way to access them. This is used to store all the DataTable during the archiving process.
+ * At the end of archiving, the ArchiveProcessing will read the stored datatable and record them in the DB.
+ * 
+ * @package Piwik_DataTable
+ */
+class Piwik_DataTable_Manager
+{
+	static private $instance = null;
+	/**
+	 * Returns instance
+	 *
+	 * @return Piwik_DataTable_Manager
+	 */
+	static public function getInstance()
+	{
+		if (self::$instance == null)
+		{            
+			$c = __CLASS__;
+			self::$instance = new $c();
+		}
+		return self::$instance;
+	}
+	
+	/**
+	 * Array used to store the DataTable
+	 *
+	 * @var array
+	 */
+	protected $tables = array();
+		
+	/**
+	 * Add a DataTable to the registry
+	 * 
+	 * @param Piwik_DataTable
+	 * @return int Number of tables registered in the manager (including the one just added)
+	 */
+	public function addTable( $table )
+	{
+		$this->tables[] = $table;
+		return count($this->tables) - 1;
+	}
+	
+	/**
+	 * Returns the DataTable associated to the ID $idTable.
+	 * NB: The datatable has to have been instanciated before! 
+	 * This method will not fetch the DataTable from the DB.
+	 * 
+	 * @exception If the table can't be found
+	 * @return Piwik_DataTable The table 
+	 */
+	public function getTable( $idTable )
+	{
+		if(!isset($this->tables[$idTable]))
+		{
+			throw new Exception(sprintf("The requested table (id = %d) couldn't be found in the DataTable Manager", $idTable));
+		}
+		return $this->tables[$idTable];
+	}
+	
+	/**
+	 * Delete all the registered DataTables from the manager
+	 * 
+	 * @return void
+	 */
+	public function deleteAll()
+	{
+		$this->tables = array();
+	}
+	
+	public function deleteTable( $id )
+	{
+		if(isset($this->tables[$id]))
+		{
+			$this->tables[$id] = null;
+		}
+	}
+	
+	/**
+	 * Returns the number of DataTable currently registered.
+	 * 
+	 * @return int
+	 */
+	public function count()
+	{
+		return count($this->tables);
+	}
+}
+
diff --git a/core/DataTable/Renderer.php b/core/DataTable/Renderer.php
new file mode 100644
index 0000000000..942c30d8bb
--- /dev/null
+++ b/core/DataTable/Renderer.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Renderer.php 519 2008-06-09 01:59:24Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * A DataTable Renderer can produce an output given a DataTable object.
+ * All new Renderers must be copied in DataTable/Renderer and added to the factory() method.
+ * To use a renderer, simply do:
+ *  $render = new Piwik_DataTable_Renderer_Xml( $myTable );
+ *  echo $render;
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Renderer
+ */
+abstract class Piwik_DataTable_Renderer
+{
+	protected $table;
+	protected $renderSubTables;
+	
+	/**
+	 * Builds the renderer.
+	 * Works with any kind of DataTable if the renderer used handles this DataTable.
+	 *
+	 * @param Piwik_DataTable|Piwik_DataTable_Simple|Piwik_DataTable_Array $table to be rendered
+	 */
+	function __construct($table = null, $renderSubTables = null)
+	{
+		if(!is_null($table))
+		{
+			$this->setTable($table);
+		}
+		if(is_null($renderSubTables))
+		{
+			$this->renderSubTables = (bool)Piwik_Common::getRequestVar('expanded', false);
+		}
+		else
+		{
+			$this->renderSubTables = $renderSubTables; 
+		}
+	}
+	
+	/**
+	 * Computes the dataTable output and returns the string/binary
+	 * 
+	 * @return string
+	 */
+	abstract public function render();
+	
+	/**
+	 * @see render()
+	 * @return string
+	 */
+	public function __toString()
+	{
+		return $this->render();
+	}
+	
+	/**
+	 * Set the DataTable to be rendered
+	 * 
+	 * @param Piwik_DataTable|Piwik_DataTable_Simple|Piwik_DataTable_Array $table to be rendered
+	 */
+	public function setTable($table)
+	{
+		if(!($table instanceof Piwik_DataTable)
+			&& !($table instanceof Piwik_DataTable_Array))
+		{
+			throw new Exception("The renderer accepts only a Piwik_DataTable or an array of DataTable (Piwik_DataTable_Array) object.");
+		}
+		$this->table = $table;
+	}
+	
+	/**
+	 * Returns the DataTable associated to the output format $name
+	 * 
+	 * @throws exception If the renderer is unknown
+	 * @return Piwik_DataTable_Renderer
+	 */
+	static public function factory( $name )
+	{
+		$name = ucfirst(strtolower($name));
+		$path = "core/DataTable/Renderer/".$name.".php";
+		$className = 'Piwik_DataTable_Renderer_' . $name;
+		
+		if( Piwik_Common::isValidFilename($name)
+			&& Zend_Loader::isReadable($path) )
+		{
+			require_once $path;
+			return new $className;			
+		}
+		else
+		{
+			throw new Exception("Renderer format '$name' not valid. Try 'xml' or 'json' or 'csv' or 'html' or 'php' or 'original' instead.");
+		}		
+	}	
+}
+
diff --git a/core/DataTable/Renderer/Console.php b/core/DataTable/Renderer/Console.php
new file mode 100644
index 0000000000..59ea9bc55f
--- /dev/null
+++ b/core/DataTable/Renderer/Console.php
@@ -0,0 +1,125 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Console.php 525 2008-06-25 23:49:13Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * Simple output
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Renderer
+ */
+class Piwik_DataTable_Renderer_Console extends Piwik_DataTable_Renderer
+{
+	protected $prefixRows;
+	function __construct($table = null)
+	{
+		parent::__construct($table);
+		$this->setPrefixRow('#');
+	}
+	
+	function render()
+	{
+		return $this->renderTable($this->table);
+	}
+	
+	function setPrefixRow($str)
+	{
+		$this->prefixRows = $str;
+	}
+	
+	protected function renderDataTableArray(Piwik_DataTable_Array $table, $prefix )
+	{
+		$output = "Piwik_DataTable_Array<hr>";
+		$prefix = $prefix . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
+		foreach($table->getArray() as $descTable => $table)
+		{
+			$output .= $prefix . "<b>". $descTable. "</b><br>";
+			$output .= $prefix . $this->renderTable($table, $prefix . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;');
+			$output .= "<hr>";
+		}
+		return $output;
+	}
+	
+	protected function renderTable($table, $prefix = "")
+	{
+		if($table instanceof Piwik_DataTable_Array)
+		{
+			return $this->renderDataTableArray($table, $prefix);
+		}
+		
+		if($table->getRowsCount() == 0)
+		{
+			return "Empty table <br>\n";
+		}
+		
+		static $depth=0;
+		$output = '';
+		$i = 1;
+		foreach($table->getRows() as $row)
+		{
+			$dataTableArrayBreak = false;
+			$columns=array();
+			foreach($row->getColumns() as $column => $value)
+			{
+				if($value instanceof Piwik_DataTable_Array )
+				{
+					$output .= $this->renderDataTableArray($value, $prefix);
+					$dataTableArrayBreak = true;
+					break;
+				}
+				if(is_string($value)) $value = "'$value'";
+				
+				$columns[] = "'$column' => $value";
+			}
+			if($dataTableArrayBreak === true)
+			{
+				continue;
+			}
+			$columns = implode(", ", $columns);
+			
+			$metadata = array();
+			foreach($row->getMetadata() as $name => $value)
+			{
+				if(is_string($value))
+				{
+					$value = "'$value'";
+				}
+				$metadata[] = "'$name' => $value";
+			}
+			$metadata = implode(", ", $metadata);
+			
+			$output.= str_repeat($this->prefixRows, $depth) 
+						. "- $i [".$columns."] [".$metadata."] [idsubtable = " 
+						. $row->getIdSubDataTable()."]<br>\n";
+			
+			if($row->getIdSubDataTable() !== null)
+			{
+				$depth++;
+				try{
+					$output.= $this->renderTable( 
+									Piwik_DataTable_Manager::getInstance()->getTable(
+												$row->getIdSubDataTable()
+											),
+											$prefix . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'
+										);
+				} catch(Exception $e) {
+					$output.= "-- Sub DataTable not loaded<br>\n";
+				}
+				$depth--;
+			}
+			$i++;
+		}
+		
+		return $output;
+		
+	}	
+}
+
+
diff --git a/core/DataTable/Renderer/Csv.php b/core/DataTable/Renderer/Csv.php
new file mode 100644
index 0000000000..e686ba9023
--- /dev/null
+++ b/core/DataTable/Renderer/Csv.php
@@ -0,0 +1,237 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Csv.php 558 2008-07-20 23:10:38Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+require_once "DataTable/Renderer/Php.php";
+/**
+ * CSV export
+ * 
+ * When rendered using the default settings, a CSV report has the following characteristics:
+ * The first record contains headers for all the columns in the report.
+ * All rows have the same number of columns.
+ * The default field delimiter string is a comma (,).
+ * Formatting and layout are ignored.
+ * 
+ * Note that CSV output doesn't handle recursive dataTable. It will output only the first parent level of the tables.
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Renderer
+ * 
+ */
+
+class Piwik_DataTable_Renderer_Csv extends Piwik_DataTable_Renderer
+{
+	/**
+	 * Column separator
+	 *
+	 * @var string
+	 */
+	public $separator = ',';
+	
+	/**
+	 * Line end 
+	 *
+	 * @var string
+	 */
+	public $lineEnd = "\n";
+	
+	/**
+	 * 'metadata' columns will be exported, prefixed by 'metadata_'
+	 *
+	 * @var bool
+	 */
+	public $exportMetadata = true;
+	
+	/**
+	 * Converts the content to unicode so that UTF8 characters (eg. chinese) can be imported in Excel
+	 *
+	 * @var bool
+	 */
+	public $convertToUnicode = true;
+	
+	/**
+	 * idSubtable will be exported in a column called 'idsubdatatable'
+	 *
+	 * @var bool
+	 */
+	public $exportIdSubtable = true;
+	
+	function __construct($table = null)
+	{
+		parent::__construct($table);
+	}
+	
+	function render()
+	{
+		return $this->renderTable($this->table);
+	}
+	
+	protected function renderTable($table)
+	{
+		if($table instanceof Piwik_DataTable_Array)
+		{
+			$str = $header = '';
+			$prefixColumns = $table->getKeyName() . $this->separator;
+			foreach($table->getArray() as $currentLinePrefix => $dataTable)
+			{
+				$returned = explode("\n",$this->renderTable($dataTable));
+				// get the columns names
+				if(empty($header))
+				{
+					$header = $returned[0];
+				}
+				$returned = array_slice($returned,1);
+				
+				// case empty datatable we dont print anything in the CSV export
+				// when in xml we would output <result date="2008-01-15" />
+				if(!empty($returned))
+				{
+					foreach($returned as &$row)
+					{
+						$row = $currentLinePrefix . $this->separator . $row;
+					}
+					$str .= "\n" .  implode("\n", $returned);
+				}
+			}
+			if(!empty($header))
+			{
+				$str = $prefixColumns . $header . $str;
+			}
+		}
+		else
+		{
+			$str = $this->renderDataTable($table);
+		}
+		
+		return $this->output($str);
+	}
+	
+	protected function renderDataTable( $table )
+	{	
+		if($table instanceof Piwik_DataTable_Simple 
+			&& $table->getRowsCount() == 1)
+		{
+			$str = 'value' . $this->lineEnd . $table->getRowFromId(0)->getColumn('value');
+			return $str;
+		}
+		
+		$csv = array();		
+
+		$allColumns = array();
+		foreach($table->getRows() as $row)
+		{
+			$csvRow = array();
+			
+			$columns = $row->getColumns();
+			foreach($columns as $name => $value)
+			{
+				if(!isset($allColumns[$name]))
+				{
+					$allColumns[$name] = true;
+				}
+				$csvRow[$name] = $value;
+			}
+			
+			if($this->exportMetadata)
+			{
+				$metadata = $row->getMetadata();
+				foreach($metadata as $name => $value)
+				{
+					//if a metadata and a column have the same name make sure they dont overwrite
+					$name = 'metadata_'.$name;
+					
+					$allColumns[$name] = true;
+					$csvRow[$name] = $value;
+				}
+			}		
+			
+			if($this->exportIdSubtable)
+			{
+				$idsubdatatable = $row->getIdSubDataTable();
+				if($idsubdatatable !== false)
+				{
+					$csvRow['idsubdatatable'] = $idsubdatatable;
+				}
+			}
+			
+			$csv[] = $csvRow;
+		}
+		
+		// now we make sure that all the rows in the CSV array have all the columns
+		foreach($csv as &$row)
+		{
+			foreach($allColumns as $columnName => $true)
+			{
+				if(!isset($row[$columnName]))
+				{
+					$row[$columnName] = '';
+				}
+			}
+		}
+		$str = '';		
+		
+		// specific case, we have only one column and this column wasn't named properly (indexed by a number)
+		// we don't print anything in the CSV file => an empty line
+		if(sizeof($allColumns) == 1 
+			&& reset($allColumns) 
+			&& !is_string(key($allColumns)))
+		{
+			$str .= '';
+		}
+		else
+		{
+			$keys = array_keys($allColumns);
+			$str .= implode($this->separator, $keys);
+			$str .= $this->lineEnd;
+		}
+		
+		// we render the CSV
+		foreach($csv as $theRow)
+		{
+			$rowStr = '';
+			foreach($allColumns as $columnName => $true)
+			{
+				$rowStr .= $this->formatValue($theRow[$columnName]) . $this->separator;
+			}
+			// remove the last separator
+			$rowStr = substr_replace($rowStr,"",-strlen($this->separator));
+			$str .= $rowStr . $this->lineEnd;
+		}
+		$str = substr($str, 0, -strlen($this->lineEnd));
+		return $str;
+	}
+
+	protected function formatValue($value)
+	{
+		if(is_string($value)
+			&& !is_numeric($value)) 
+		{
+			$value = html_entity_decode($value, ENT_COMPAT, 'UTF-8');
+		}
+		return $value;
+	}
+	
+	protected function output( $str )
+	{
+		if(empty($str))
+		{
+			return 'No data available';
+		}
+		// silent fail otherwise unit tests fail
+		@header("Content-type: application/vnd.ms-excel");
+		@header("Content-Disposition: attachment; filename=piwik-report-export.csv");
+		if($this->convertToUnicode 
+			&& function_exists('mb_convert_encoding'))
+		{
+			$str = chr(255) . chr(254) . mb_convert_encoding($str, 'UTF-16LE', 'UTF-8');
+		}
+		return $str;
+	}
+}
\ No newline at end of file
diff --git a/core/DataTable/Renderer/Html.php b/core/DataTable/Renderer/Html.php
new file mode 100644
index 0000000000..cea46596fc
--- /dev/null
+++ b/core/DataTable/Renderer/Html.php
@@ -0,0 +1,186 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Html.php 519 2008-06-09 01:59:24Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * Simple HTML output
+ * Works with recursive DataTable (when a row can be associated with a subDataTable).
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Renderer
+ */
+class Piwik_DataTable_Renderer_Html extends Piwik_DataTable_Renderer
+{
+	protected $prefixRows;
+	function __construct($table = null)
+	{
+		parent::__construct($table);
+	}
+	
+	function render()
+	{
+		return $this->renderTable($this->table);
+	}
+	
+	protected function renderTable($table)
+	{
+		if($table instanceof Piwik_DataTable_Array)
+		{
+			$columnPrefixToAdd = $table->getKeyName();
+			$out = "<table border=1>";
+			foreach($table->getArray() as $date => $subtable )
+			{
+				$out .= "<tr><td><h2>$columnPrefixToAdd = $date</h2>";
+				$out .= $this->renderDataTable($subtable);
+				$out .= "</td></tr>";
+			}
+			$out .= "</table>";
+		}
+		else
+		{
+			$out = $this->renderDataTable($table);
+		}
+		return $out;
+	}	
+	
+	protected function renderDataTable($table)
+	{
+		if($table->getRowsCount() == 0)
+		{
+			return "<b><i>Empty table</i></b> <br>\n";
+		}
+		if($table instanceof Piwik_DataTable_Simple 
+			&& $table->getRowsCount() ==1)
+		{
+			$table->deleteColumn('label');
+		}
+		
+		static $depth=0;
+		$i = 1;
+		$someMetadata = false;
+		$someIdSubTable = false;
+		
+		$tableStructure = array();
+		
+		/*
+		 * table = array
+		 * ROW1 = col1 | col2 | col3 | metadata | idSubTable
+		 * ROW2 = col1 | col2 (no value but appears) | col3 | metadata | idSubTable
+		 * 		subtable here
+		 */
+		$allColumns = array();
+		foreach($table->getRows() as $row)
+		{
+			foreach($row->getColumns() as $column => $value)
+			{
+				$allColumns[$column] = true;
+				$tableStructure[$i][$column] = $value;
+			}
+
+			$metadata=array();
+			foreach($row->getMetadata() as $name => $value)
+			{
+				if(is_string($value)) $value = "'$value'";
+				$metadata[] = "'$name' => $value";
+			}
+			
+			if(count($metadata) != 0)
+			{
+				$someMetadata = true;
+				$metadata = implode("<br>", $metadata);
+				$tableStructure[$i]['_metadata'] = $metadata;
+			}
+			
+			$idSubtable = $row->getIdSubDataTable();
+			if(!is_null($idSubtable))
+			{
+				$someIdSubTable = true;
+				$tableStructure[$i]['_idSubtable'] = $idSubtable;
+			}
+			
+			if($row->getIdSubDataTable() !== null)
+			{
+				$depth++;
+				try{
+					$tableStructure[$i]['_subtable']['html'] =  $this->renderTable( Piwik_DataTable_Manager::getInstance()->getTable($row->getIdSubDataTable()));
+				} catch(Exception $e) {
+					$tableStructure[$i]['_subtable']['html'] = "-- Sub DataTable not loaded";
+				}
+				$tableStructure[$i]['_subtable']['depth'] = $depth;
+				$depth--;
+			}
+			$i++;
+		}
+		
+		$allColumns['_metadata'] = $someMetadata;
+		$allColumns['_idSubtable'] = $someIdSubTable;
+		$html = "\n";
+		$html .= "<table border=1 width=70%>";
+		$html .= "\n<tr>";
+		foreach($allColumns as $name => $toDisplay)
+		{
+			if($toDisplay !== false)
+			{
+				if($name === 0)
+				{
+					$name = 'value';
+				}
+				$html .= "\n\t<td><b>$name</b></td>";
+			}
+		}
+		$colspan = count($allColumns);
+		
+		foreach($tableStructure as $row)
+		{
+			$html .= "\n\n<tr>";
+			foreach($allColumns as $name => $toDisplay)
+			{
+				if($toDisplay !== false)
+				{
+					$value = "-";
+					if(isset($row[$name]))
+					{
+						$value = $row[$name];
+					}
+					
+					$html .= "\n\t<td>$value</td>";
+				}
+			}
+			$html .= "</tr>";
+			
+			if(isset($row['_subtable']))
+			{
+				$html .= "<tr>
+						<td class=l{$row['_subtable']['depth']} colspan=$colspan>{$row['_subtable']['html']}</td></tr>";
+			}
+		}
+		$html .= "\n\n</table>";
+		
+		// display styles if there is a subtable displayed
+		if($someIdSubTable)
+		{
+			$styles="\n\n<style>\n";
+			for($i=0;$i<11;$i++)
+			{
+				$padding=$i*2;
+				$styles.= "\t TD.l$i { padding-left:{$padding}em; } \n";
+			}
+			$styles.="</style>\n\n";
+			if($depth == 0)
+			{
+				$html = $styles . $html;
+			}
+		}
+		return $html;
+	}
+}
+
+
+
diff --git a/core/DataTable/Renderer/Json.php b/core/DataTable/Renderer/Json.php
new file mode 100644
index 0000000000..729f2af98d
--- /dev/null
+++ b/core/DataTable/Renderer/Json.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Json.php 516 2008-06-08 20:06:43Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+require_once "DataTable/Renderer/Php.php";
+/**
+ * JSON export. Using the php 5.2 feature json_encode.
+ * Works with recursive DataTable (when a row can be associated with a subDataTable).
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Renderer
+ */
+class Piwik_DataTable_Renderer_Json extends Piwik_DataTable_Renderer
+{
+	function __construct($table = null, $renderSubTables = null)
+	{
+		parent::__construct($table, $renderSubTables);
+	}
+	
+	function render()
+	{
+		return $this->renderTable($this->table);
+	}
+
+	protected function renderTable($table)
+	{
+		$renderer = new Piwik_DataTable_Renderer_Php($table, $this->renderSubTables, $serialize = false);
+		$array = $renderer->flatRender();
+		
+		if(!is_array($array))
+		{
+			$array = array('value' => $array);
+		}
+		$str = json_encode($array);
+		
+		if(($jsonCallback = Piwik_Common::getRequestVar('jsoncallback', false)) !== false)
+		{
+			if(preg_match('/^[0-9a-zA-Z]*$/', $jsonCallback) > 0)
+			{
+				$str = $jsonCallback . "(" . $str . ")";
+			}
+		}
+		return $str;
+	}
+}
\ No newline at end of file
diff --git a/core/DataTable/Renderer/Php.php b/core/DataTable/Renderer/Php.php
new file mode 100644
index 0000000000..5e472d7519
--- /dev/null
+++ b/core/DataTable/Renderer/Php.php
@@ -0,0 +1,203 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Php.php 519 2008-06-09 01:59:24Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * Returns the equivalent PHP array for a given DataTable.
+ * You can specify in the constructor if you want the serialized version.
+ * Please note that by default it will produce a flat version of the array.
+ * See the method flatRender() for details. @see flatRender();
+ * 
+ * Works with recursive DataTable (when a row can be associated with a subDataTable).
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Renderer
+ */
+class Piwik_DataTable_Renderer_Php extends Piwik_DataTable_Renderer
+{
+	protected $serialize;
+	
+	public function __construct($table = null, $renderSubTables = null, $serialize = true)
+	{
+		parent::__construct($table, $renderSubTables);
+		$this->setSerialize($serialize);
+	}
+	
+	public function setSerialize( $bool )
+	{
+		$this->serialize = $bool;
+	}
+	
+	public function __toString()
+	{
+		$data = $this->render();
+		if(!is_string($data))
+		{
+			$data = serialize($data);
+		}
+		return $data;
+	}
+
+	public function render( $dataTable = null )
+	{
+		if(is_null($dataTable))
+		{
+			$dataTable = $this->table;
+		}
+		$toReturn = $this->flatRender( $dataTable );
+		
+		if( false !== Piwik_Common::getRequestVar('prettyDisplay', false) )
+		{
+			if(!is_array($toReturn))
+			{
+				$toReturn = unserialize($toReturn);
+			}
+			$toReturn =  "<pre>" . var_export($toReturn, true ) . "</pre>";
+		}
+		return $toReturn;
+	}
+	
+	/**
+	 * Produces a flat php array from the DataTable, putting "columns" and "metadata" on the same level.
+	 * 
+	 * For example, when  a originalRender() would be 
+	 * 	array( 'columns' => array( 'col1_name' => value1, 'col2_name' => value2 ),
+	 * 	       'metadata' => array( 'metadata1_name' => value_metadata) )
+	 * 
+	 * a flatRender() is
+	 * 	array( 'col1_name' => value1, 
+	 * 	       'col2_name' => value2,
+	 * 	       'metadata1_name' => value_metadata )
+	 *  
+	 * @return array Php array representing the 'flat' version of the datatable
+	 *
+	 */
+	public function flatRender( $dataTable = null )
+	{
+		if(is_null($dataTable))
+		{
+			$dataTable = $this->table;
+		}
+		
+		if($dataTable instanceof Piwik_DataTable_Array)
+		{
+			$flatArray = array();
+			foreach($dataTable->getArray() as $keyName => $table)
+			{
+				$serializeSave = $this->serialize;
+				$this->serialize = false;
+				$flatArray[$keyName] = $this->flatRender($table);
+				$this->serialize = $serializeSave;
+			}
+		}
+		
+		// A DataTable_Simple is already flattened so no need to do some crazy stuff to convert it
+		else if($dataTable instanceof Piwik_DataTable_Simple)
+		{
+			$flatArray = $this->renderSimpleTable($dataTable);
+			
+			// if we return only one numeric value then we print out the result in a simple <result> tag
+			// keep it simple!
+			if(count($flatArray) == 1)
+			{
+				$flatArray = current($flatArray);
+			}
+			
+		}
+		// A normal DataTable needs to be handled specifically
+		else
+		{
+			$array = $this->renderTable($dataTable);
+			$flatArray = $this->flattenArray($array);
+		}
+		
+		if($this->serialize)
+		{
+			$flatArray = serialize($flatArray);
+		}
+		
+		return $flatArray;
+	}
+	
+	protected function flattenArray($array)
+	{
+		$flatArray = array();
+		foreach($array as $row)
+		{
+			$newRow = $row['columns'] + $row['metadata'];
+			if(isset($row['idsubdatatable']))
+			{
+				$newRow += array('idsubdatatable' => $row['idsubdatatable']);
+				if(isset($row['subtable']))
+				{
+					$newRow += array('subtable' => $this->flattenArray($row['subtable']) );
+				}
+			}
+			$flatArray[] = $newRow;
+		}		
+		return $flatArray;
+	}
+	
+	public function originalRender()
+	{
+		if($this->table instanceof Piwik_DataTable_Simple)
+		{
+			$array = $this->renderSimpleTable($this->table);
+		}
+		else
+		{
+			$array = $this->renderTable($this->table);
+		}
+				
+		if($this->serialize)
+		{
+			$array = serialize($array);
+		}
+		return $array;
+	}
+	
+	protected function renderTable($table)
+	{
+		$array = array();
+
+		foreach($table->getRows() as $row)
+		{
+			$newRow = array(
+				'columns' => $row->getColumns(),
+				'metadata' => $row->getMetadata(),
+				'idsubdatatable' => $row->getIdSubDataTable(),
+				);
+			
+			if($this->renderSubTables
+				&& $row->getIdSubDataTable() !== null)
+			{
+				try{
+					$subTable =  $this->renderTable( Piwik_DataTable_Manager::getInstance()->getTable($row->getIdSubDataTable()));
+					$newRow['subtable'] = $subTable;
+				} catch (Exception $e) {
+					// the subtables are not loaded we dont do anything 
+				}
+			}
+			
+			$array[] = $newRow;
+		}
+		return $array;
+	}
+	
+	protected function renderSimpleTable($table)
+	{
+		$array = array();
+		foreach($table->getRows() as $row)
+		{
+			$array[$row->getColumn('label')] = $row->getColumn('value');
+		}
+		return $array;
+	}
+}
\ No newline at end of file
diff --git a/core/DataTable/Renderer/Rss.php b/core/DataTable/Renderer/Rss.php
new file mode 100644
index 0000000000..18574dfcd5
--- /dev/null
+++ b/core/DataTable/Renderer/Rss.php
@@ -0,0 +1,169 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Html.php 180 2008-01-17 16:32:37Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * RSS Feed. 
+ * The RSS renderer can be used only on Piwik_DataTable_Array that are arrays of Piwik_DataTable.
+ * A RSS feed contains one dataTable per element in the Piwik_DataTable_Array.
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Renderer
+ */
+class Piwik_DataTable_Renderer_Rss extends Piwik_DataTable_Renderer
+{
+	function __construct($table = null)
+	{
+		parent::__construct($table);
+	}
+	
+	function render()
+	{
+		return $this->renderTable($this->table);
+	}
+	
+	protected function renderTable($table)
+	{
+		if(!($table instanceof Piwik_DataTable_Array)
+			|| $table->getKeyName() != 'date')
+		{
+			throw new Exception("RSS Feed only used on Piwik_DataTable_Array with keyName = 'date'");
+		}
+		
+		$idSite = Piwik_Common::getRequestVar('idSite', 1);
+		$period = Piwik_Common::getRequestVar('period');
+		$currentUrl = Piwik_Url::getCurrentUrlWithoutFileName();
+		
+		$piwikUrl = $currentUrl . "?module=CoreHome&action=index&idSite=" . $idSite . "&period=" . $period;
+		
+		$out = "";
+		$moreRecentFirst = array_reverse($table->getArray(), true);
+		foreach($moreRecentFirst as $date => $subtable )
+		{
+			$timestamp = $table->metadata[$date]['timestamp'];
+			$site = $table->metadata[$date]['site'];
+	
+			$pudDate = date('r', $timestamp);
+			$dateUrl = date('Y-m-d', $timestamp);
+			$thisPiwikUrl = htmlentities($piwikUrl . "&date=$dateUrl");
+			$siteName = $site->getName();
+			$title = $siteName . " on ". $date;
+			
+			$out .= "\t<item>
+		<pubDate>$pudDate</pubDate>
+		<guid>$thisPiwikUrl</guid>
+		<link>$thisPiwikUrl</link>
+		<title>$title</title>
+		<author>http://piwik.org</author>
+		<description>";	
+			
+			$out .= htmlspecialchars( $this->renderDataTable($subtable) );
+			$out .= "</description>\n\t</item>\n";
+		}
+		
+		$header = $this->getRssHeader();
+		$footer = $this->getRssFooter();
+		
+		return $this->output( $header . $out . $footer);
+	}
+	protected function output($str)
+	{
+		@header("Content-Type: text/xml;charset=utf-8");
+		return $str;
+	}
+	protected function getRssFooter()
+	{
+		return "\t</channel>\n</rss>";
+	}
+	protected function getRssHeader()
+	{
+		$generationDate = date('r');
+		$header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
+<rss version=\"2.0\">
+  <channel>
+    <title>piwik statistics - RSS</title>
+    <link>http://piwik.org</link>
+    <description>Piwik RSS feed</description>
+    <pubDate>$generationDate</pubDate>
+    <generator>piwik</generator>
+    <language>en</language>
+    <lastBuildDate>$generationDate</lastBuildDate>";
+    	return $header;
+	}
+	
+	protected function renderDataTable($table)
+	{
+	
+		if($table->getRowsCount() == 0)
+		{
+			return "<b><i>Empty table</i></b> <br>\n";
+		}
+		if($table instanceof Piwik_DataTable_Simple 
+			&& $table->getRowsCount() ==1)
+		{
+			$table->deleteColumn('label');
+		}
+		
+		$i = 1;		
+		$tableStructure = array();
+		
+		/*
+		 * table = array
+		 * ROW1 = col1 | col2 | col3 | metadata | idSubTable
+		 * ROW2 = col1 | col2 (no value but appears) | col3 | metadata | idSubTable
+		 * 		subtable here
+		 */
+		$allColumns = array();
+		foreach($table->getRows() as $row)
+		{
+			foreach($row->getColumns() as $column => $value)
+			{
+				$allColumns[$column] = true;
+				$tableStructure[$i][$column] = $value;
+			}
+			$i++;
+		}
+		$html = "\n";
+		$html .= "<table border=1 width=70%>";
+		$html .= "\n<tr>";
+		foreach($allColumns as $name => $toDisplay)
+		{
+			if($toDisplay !== false)
+			{
+				$html .= "\n\t<td><b>$name</b></td>";
+			}
+		}
+		$html .= "\n</tr>";
+		$colspan = count($allColumns);
+		
+		foreach($tableStructure as $row)
+		{
+			$html .= "\n\n<tr>";
+			foreach($allColumns as $name => $toDisplay)
+			{
+				if($toDisplay !== false)
+				{
+					$value = "-";
+					if(isset($row[$name]))
+					{
+						$value = urldecode($row[$name]);
+					}
+					
+					$html .= "\n\t<td>$value</td>";
+				}
+			}
+			$html .= "</tr>";
+			
+		}
+		$html .= "\n\n</table>";
+		return $html;
+	}
+}
+
diff --git a/core/DataTable/Renderer/Xml.php b/core/DataTable/Renderer/Xml.php
new file mode 100644
index 0000000000..425f506ae3
--- /dev/null
+++ b/core/DataTable/Renderer/Xml.php
@@ -0,0 +1,300 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Xml.php 558 2008-07-20 23:10:38Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+require_once "DataTable/Renderer/Php.php";
+/**
+ * XML export of a given DataTable.
+ * See the tests cases for more information about the XML format (/tests/core/DataTable/Renderer.test.php)
+ * Or have a look at the API calls examples.
+ * 
+ * Works with recursive DataTable (when a row can be associated with a subDataTable).
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Renderer
+ */
+class Piwik_DataTable_Renderer_Xml extends Piwik_DataTable_Renderer
+{
+	function __construct($table = null, $renderSubTables = null)
+	{
+		parent::__construct($table, $renderSubTables);
+	}
+	
+	function render()
+	{
+		return $this->renderTable($this->table);
+	}
+	
+	protected function getArrayFromDataTable($table)
+	{
+		$renderer = new Piwik_DataTable_Renderer_Php($table, $this->renderSubTables, $serialize = false);
+		return $renderer->flatRender();
+	}
+	
+	protected function renderTable($table, $returnOnlyDataTableXml = false, $prefixLines = '')
+	{
+		$array = $this->getArrayFromDataTable($table);
+		
+		if($table instanceof Piwik_DataTable_Array)
+		{
+			$out = $this->renderDataTableArray($table, $array, $prefixLines);
+			
+			if($returnOnlyDataTableXml)
+			{
+				return $out;
+			}
+			$out = "<results>\n$out</results>";
+			return $this->output($out);
+		}
+	
+		// integer value of ZERO is a value we want to display
+		if($array != 0 && empty($array))
+		{
+			if($returnOnlyDataTableXml)
+			{
+				throw new Exception("Illegal state, what xml shall we return?");
+			}
+			$out = "<result />";
+			return $this->output($out);
+		}
+		if($table instanceof Piwik_DataTable_Simple)
+		{
+			if(is_array($array))
+			{
+				$out = $this->renderDataTableSimple($array);
+			}
+			else
+			{
+				$out = $array;
+			}
+			if($returnOnlyDataTableXml)
+			{
+				return $out;
+			}
+			
+			if(is_array($array))
+			{
+				$out = "<result>\n".$out."</result>";
+			}
+			else
+			{
+				$out = "<result>".$out."</result>";
+			}
+			return $this->output($out);
+		}
+		
+		if($table instanceof Piwik_DataTable)
+		{
+			$out = $this->renderDataTable($array);
+			if($returnOnlyDataTableXml)
+			{
+				return $out;
+			}
+			$out = "<result>\n$out</result>";
+			return $this->output($out);
+		}
+		
+		
+	}
+	
+	protected function renderDataTableArray($table, $array, $prefixLines = "")
+	{
+		// CASE 1
+		//array
+  		//  'day1' => string '14' (length=2)
+  		//  'day2' => string '6' (length=1)
+		$firstTable = current($array);
+		if(!is_array( $firstTable ))
+		{
+			$xml = '';
+	  		$nameDescriptionAttribute = $table->getKeyName();
+	  		foreach($array as $valueAttribute => $value)
+	  		{
+	  			if(empty($value))
+	  			{
+		  			$xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\" />\n";	  				
+	  			}
+	  			elseif($value instanceof Piwik_DataTable_Array )
+	  			{
+	  				$out = $this->renderTable($value, true);
+		  			//TODO somehow this code is not tested, cover this case
+	  				$xml .= "\t<result $nameDescriptionAttribute=\"$valueAttribute\">\n$out</result>\n";
+	  			}
+	  			else
+	  			{
+		  			$xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\">$value</result>\n";	  				
+	  			}
+	  		}
+	  		return $xml;
+		}
+	
+		$subTables = $table->getArray();
+		$firstTable = current($subTables);
+		
+		// CASE 2
+		//array
+  		//  'day1' => 
+  		//    array
+  		//      'nb_uniq_visitors' => string '18'
+  		//      'nb_visits' => string '101' 
+  		//  'day2' => 
+  		//    array
+  		//      'nb_uniq_visitors' => string '28' 
+  		//      'nb_visits' => string '11' 
+		if( $firstTable instanceof Piwik_DataTable_Simple)
+		{
+			$xml = '';
+			$nameDescriptionAttribute = $table->getKeyName();
+	  		foreach($array as $valueAttribute => $dataTableSimple)
+	  		{
+	  			if(count($dataTableSimple) == 0)
+				{
+					$xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\" />\n";
+				}
+	  			else
+	  			{
+		  			if(is_array($dataTableSimple))
+		  			{
+			  			$dataTableSimple = "\n" . $this->renderDataTableSimple($dataTableSimple, $prefixLines . "\t") . "\t" ;
+		  			}
+		  			$xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\">".$dataTableSimple. $prefixLines . "</result>\n";
+	  			}
+	  		}
+	  		return $xml;
+		}
+		
+		// CASE 3
+		//array
+		//  'day1' => 
+		//    array
+		//      0 => 
+		//        array
+		//          'label' => string 'phpmyvisites'
+		//          'nb_uniq_visitors' => int 11
+		//          'nb_visits' => int 13
+		//      1 => 
+		//        array
+		//          'label' => string 'phpmyvisits'
+		//          'nb_uniq_visitors' => int 2
+		//          'nb_visits' => int 2
+		//  'day2' => 
+		//    array
+		//      0 => 
+		//        array
+		//          'label' => string 'piwik'
+		//          'nb_uniq_visitors' => int 121
+		//          'nb_visits' => int 130
+		//      1 => 
+		//        array
+		//          'label' => string 'piwik bis'
+		//          'nb_uniq_visitors' => int 20
+		//          'nb_visits' => int 120
+		if($firstTable instanceof Piwik_DataTable)
+		{
+			$xml = '';
+			$nameDescriptionAttribute = $table->getKeyName();
+			foreach($array as $keyName => $arrayForSingleDate)
+			{
+				$dataTableOut = $this->renderDataTable( $arrayForSingleDate, $prefixLines . "\t" );
+				if(empty($dataTableOut))
+				{
+					$xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$keyName\" />\n";
+				}
+				else
+				{
+					$xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$keyName\">\n";
+					$xml .= $dataTableOut;
+					$xml .= $prefixLines . "\t</result>\n";
+				}
+			}
+			return $xml;
+		}
+		
+		if($firstTable instanceof Piwik_DataTable_Array)
+		{
+			$xml = '';
+			$tables = $table->getArray();
+			$nameDescriptionAttribute = $table->getKeyName();
+			foreach( $tables as $valueAttribute => $tableInArray)
+			{
+				$out = $this->renderTable($tableInArray, true, $prefixLines . "\t");
+				$xml .= $prefixLines . "\t<result $nameDescriptionAttribute=\"$valueAttribute\">\n".$out.$prefixLines."\t</result>\n";
+	  			
+			}
+			return $xml;
+		}
+	}
+	
+	protected function renderDataTable( $array, $prefixLine = "" )
+	{
+		$out = '';
+		foreach($array as $row)
+		{
+			$out .= $prefixLine."\t<row>";
+			
+			if(count($row) === 1
+				&& key($row) === 0)
+			{
+				$value = current($row);
+				$out .= $prefixLine . $value;				
+			}
+			else
+			{
+				$out .= "\n";
+				foreach($row as $name => $value)
+				{
+					// handle the recursive dataTable case by XML outputting the recursive table
+					if(is_array($value))
+					{
+						$value = "\n".$this->renderDataTable($value, $prefixLine."\t\t");
+						$value .= $prefixLine."\t\t"; 
+					}
+					else
+					{
+						$value = $this->formatValue($value);
+					}
+					$out .= $prefixLine."\t\t<$name>".$value."</$name>\n";
+				} 
+				$out .= "\t";
+			}
+			$out .= $prefixLine."</row>\n";
+		}
+		return $out;
+	}
+	
+	protected function renderDataTableSimple( $array, $prefixLine = "")
+	{
+		$out = '';
+		foreach($array as $keyName => $value)
+		{
+			$out .= $prefixLine."\t<$keyName>".$this->formatValue($value)."</$keyName>\n"; 
+		}
+		return $out;
+	}
+	
+	protected function formatValue($value)
+	{
+		if(is_string($value)
+			&& !is_numeric($value)) 
+		{
+			$value = html_entity_decode($value, ENT_COMPAT, 'UTF-8');
+			$value = htmlspecialchars($value);
+		}
+		return $value;
+	}
+	
+	protected function output( $xml )
+	{
+		// silent fail because otherwise it throws an exception in the unit tests
+		@header("Content-Type: text/xml;charset=utf-8");
+		$xml = '<?xml version="1.0" encoding="utf-8" ?>' .  "\n" . $xml;
+		return $xml;
+	}
+}
\ No newline at end of file
diff --git a/core/DataTable/Row.php b/core/DataTable/Row.php
new file mode 100644
index 0000000000..b2e3e599d2
--- /dev/null
+++ b/core/DataTable/Row.php
@@ -0,0 +1,404 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Row.php 519 2008-06-09 01:59:24Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * A DataTable is composed of rows.
+ * 
+ * A row is composed of:
+ * - columns often at least a 'label' column containing the description 
+ * 		of the row, and some numeric values ('nb_visits', etc.)
+ * - metadata: other information never to be shown as 'columns'
+ * - idSubtable: a row can be linked to a SubTable
+ * 
+ * IMPORTANT: Make sure that the column named 'label' contains at least one non-numeric character.
+ * Otherwise the method addDataTable() or sumRow() would fail because they would consider
+ * the 'label' as being a numeric column to sum.
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Row
+ * 
+ */
+class Piwik_DataTable_Row
+{
+	/**
+	 * This array contains the row information:
+	 * - array indexed by self::COLUMNS contains the columns, pairs of (column names, value) 
+	 * - (optional) array indexed by self::METADATA contains the metadata,  pairs of (metadata name, value)
+	 * - (optional) integer indexed by self::DATATABLE_ASSOCIATED contains the ID of the Piwik_DataTable associated to this row. 
+	 *   This ID can be used to read the DataTable from the DataTable_Manager.
+	 * 
+	 * @var array
+	 * @see constructor for more information
+	 */
+	public $c = array();
+	
+	const COLUMNS = 0;
+	const METADATA = 1;
+	const DATATABLE_ASSOCIATED = 3;
+
+
+	/**
+	 * Efficient load of the Row structure from a well structured php array
+	 * 
+	 * @param array The row array has the structure
+	 * 					array( 
+	 * 						Piwik_DataTable_Row::COLUMNS => array( 
+	 * 										'label' => 'Piwik',
+	 * 										'column1' => 42,
+	 * 										'visits' => 657,
+	 * 										'time_spent' => 155744,	
+	 * 									),
+	 * 						Piwik_DataTable_Row::METADATA => array(
+	 * 										'logo' => 'test.png'
+	 * 									),
+	 * 						Piwik_DataTable_Row::DATATABLE_ASSOCIATED => #Piwik_DataTable object (but in the row only the ID will be stored)
+	 * 					)
+	 */
+	public function __construct( $row = array() )
+	{
+		$this->c[self::COLUMNS] = array();
+		$this->c[self::METADATA] = array();
+		$this->c[self::DATATABLE_ASSOCIATED] = null;
+		
+		if(isset($row[self::COLUMNS]))
+		{
+			$this->c[self::COLUMNS] = $row[self::COLUMNS];
+		}
+		if(isset($row[self::METADATA]))
+		{
+			$this->c[self::METADATA] = $row[self::METADATA];
+		}
+		if(isset($row[self::DATATABLE_ASSOCIATED])
+			&& $row[self::DATATABLE_ASSOCIATED] instanceof Piwik_DataTable)
+		{
+			$this->c[self::DATATABLE_ASSOCIATED] = $row[self::DATATABLE_ASSOCIATED]->getId();
+		}
+	}
+	
+	/**
+	 * When destroyed, a row destroys its associated subTable if there is one
+	 */
+	public function __destruct()
+	{
+		$idSubtable = $this->c[self::DATATABLE_ASSOCIATED];
+		if($idSubtable !== null)
+		{
+			Piwik_DataTable_Manager::getInstance()->deleteTable($idSubtable);
+			$idSubtable = null;
+		}
+	}
+
+	/**
+	 * Applys a basic rendering to the Row and returns the output
+	 *
+	 * @return string characterizing the row. Example: - 1 ['label' => 'piwik', 'nb_uniq_visitors' => 1685, 'nb_visits' => 1861, 'nb_actions' => 2271, 'max_actions' => 13, 'sum_visit_length' => 920131, 'bounce_count' => 1599] [] [idsubtable = 1375]
+	 */
+	public function __toString()
+	{
+		$columns = array();
+		foreach($this->getColumns() as $column => $value)
+		{
+			if(is_string($value)) $value = "'$value'";
+			$columns[] = "'$column' => $value";
+		}
+		$columns = implode(", ", $columns);
+		$metadata = array();
+		foreach($this->getMetadata() as $name => $value)
+		{
+			if(is_string($value))
+			{
+				$name = "'$value'";
+			}
+			$metadata[] = "'$name' => $value";
+		}
+		$metadata = implode(", ", $metadata);
+		$output = "# [".$columns."] [".$metadata."] [idsubtable = " . $this->getIdSubDataTable()."]<br>\n";
+		return $output;
+	}
+	
+	/**
+	 * Deletes the given column 
+	 *
+	 * @param string Column name
+	 * @return bool True on success, false if the column didn't exist
+	 */
+	public function deleteColumn( $name )
+	{
+		if(!isset($this->c[self::COLUMNS][$name]))
+		{
+			return false;
+		}
+		unset($this->c[self::COLUMNS][$name]);
+		return true;
+	}
+	
+	/**
+	 * Returns the given column
+	 * 
+	 * @param string Column name
+	 * @return mixed|false The column value  
+	 */
+	public function getColumn( $name )
+	{
+		if(!isset($this->c[self::COLUMNS][$name]))
+		{
+			return false;
+		}
+		return $this->c[self::COLUMNS][$name];
+	}
+	
+	/**
+	 * Returns the array of all metadata,
+	 * or the specified metadata  
+	 * 
+	 * @param string Metadata name
+	 * @return mixed|array|false 
+	 */
+	public function getMetadata( $name = null )
+	{
+		if(is_null($name))
+		{
+			return $this->c[self::METADATA];
+		}
+		if(!isset($this->c[self::METADATA][$name]))
+		{
+			return false;
+		}
+		return $this->c[self::METADATA][$name];
+	}
+	
+	/**
+	 * Returns the array containing all the columns
+	 * 
+	 * @return array Example: array( 
+	 * 					'column1' 	=> VALUE,
+	 * 					'label' 	=> 'www.php.net'
+	 * 					'nb_visits' => 15894,
+	 * 			)
+	 */
+	public function getColumns()
+	{
+		return $this->c[self::COLUMNS];
+	}
+	
+	/**
+	 * Returns the ID of the subDataTable. 
+	 * If there is no such a table, returns null.
+	 * 
+	 * @return int|null
+	 */
+	public function getIdSubDataTable()
+	{
+		return $this->c[self::DATATABLE_ASSOCIATED];
+	}
+	
+	/**
+	 * Sums a DataTable to this row subDataTable.
+	 * If this row doesn't have a SubDataTable yet, we create a new one.
+	 * Then we add the values of the given DataTable to this row's DataTable.
+	 * 	 
+	 * @param Piwik_DataTable Table to sum to this row's subDatatable
+	 * @see Piwik_DataTable::addDataTable() for the algorithm used for the sum 
+	 */
+	public function sumSubtable(Piwik_DataTable $subTable)
+	{
+		$thisSubtableID = $this->getIdSubDataTable();
+		if($thisSubtableID === null)
+		{
+			$thisSubTable = new Piwik_DataTable;
+			$this->addSubtable($thisSubTable);
+		}
+		else
+		{
+			$thisSubTable = Piwik_DataTable_Manager::getInstance()->getTable( $thisSubtableID );
+		}
+		
+		$thisSubTable->addDataTable($subTable);
+	}
+	
+	
+	/**
+	 * Set a DataTable to be associated to this row.
+	 * If the row already has a DataTable associated to it, throws an Exception.
+	 * 
+	 * @param Piwik_DataTable DataTable to associate to this row
+	 * @throws Exception 
+	 * 
+	 */
+	public function addSubtable(Piwik_DataTable $subTable)
+	{
+		if(!is_null($this->c[self::DATATABLE_ASSOCIATED]))
+		{
+			throw new Exception("Adding a subtable to the row, but it already has a subtable associated.");
+		}
+		$this->c[self::DATATABLE_ASSOCIATED] = $subTable->getId();
+	}
+	
+	/**
+	 * Set a DataTable to this row. If there is already 
+	 * a DataTable associated, it is simply overwritten.
+	 * 
+	 * @param Piwik_DataTable DataTable to associate to this row
+	 */
+	public function setSubtable(Piwik_DataTable $subTable)
+	{
+		$this->c[self::DATATABLE_ASSOCIATED] = $subTable->getId();
+	}
+	
+	/**
+	 * Set all the columns at once. Overwrites previously set columns.
+	 * 
+	 * @param array array( 
+	 * 					'label' 	=> 'www.php.net'
+	 * 					'nb_visits' => 15894,
+	 * 			)
+	 */
+	public function setColumns( $columns )
+	{
+		$this->c[self::COLUMNS] = $columns;
+	}
+	
+	/**
+	 * Set the value $value to the column called $name.
+	 * 
+	 * @param string $name of the column to set
+	 * @param mixed $value of the column to set
+	 */
+	public function setColumn($name, $value)
+	{
+		if(isset($this->c[self::COLUMNS][$name])
+			|| $name != 'label')
+		{
+			$this->c[self::COLUMNS][$name] = $value;
+		}
+		// we make sure when adding the label it goes first in the table
+		else
+		{
+			$this->c[self::COLUMNS] = array($name => $value) + $this->c[self::COLUMNS];
+		}
+	}
+	
+	/**
+	 * Add a new column to the row. If the column already exists, throws an exception
+	 * 
+	 * @param string $name of the column to add
+	 * @param mixed $value of the column to set
+	 * @throws Exception
+	 */
+	public function addColumn($name, $value)
+	{
+		if(isset($this->c[self::COLUMNS][$name]))
+		{
+			throw new Exception("Column $name already in the array!");
+		}
+		$this->c[self::COLUMNS][$name] = $value;
+	}
+	
+	
+	/**
+	 * Add a new metadata to the row. If the column already exists, throws an exception.
+	 * 
+	 * @param string $name of the metadata to add
+	 * @param mixed $value of the metadata to set
+	 * @throws Exception
+	 */
+	public function addMetadata($name, $value)
+	{
+		if(isset($this->c[self::METADATA][$name]))
+		{
+			throw new Exception("Metadata $name already in the array!");
+		}
+		$this->c[self::METADATA][$name] = $value;
+	}
+	
+	/**
+	 * Sums the given $row columns values to the existing row' columns values.
+	 * It will sum only the int or float values of $row.
+	 * It will not sum the column 'label' even if it has a numeric value.
+	 * 
+	 * If a given column doesn't exist in $this then it is added with the value of $row.
+	 * If the column already exists in $this then we have
+	 * 		this.columns[idThisCol] += $row.columns[idThisCol]
+	 */
+	public function sumRow( Piwik_DataTable_Row $rowToSum )
+	{
+		foreach($rowToSum->getColumns() as $name => $value)
+		{
+			if($name != 'label' 
+				&& Piwik::isNumeric($value))
+			{
+				$current = $this->getColumn($name);
+				if($current === false)
+				{
+					$current = 0;
+				}
+				$this->setColumn( $name, $current + $value);
+			}
+		}
+	}
+	
+	
+	/**
+	 * Helper function to test if two rows are equal.
+	 * 
+	 * Two rows are equal 
+	 * - if they have exactly the same columns / metadata
+	 * - if they have a subDataTable associated, then we check that both of them are the same.
+	 * 
+	 * @param Piwik_DataTable_Row row1 to compare
+	 * @param Piwik_DataTable_Row row2 to compare
+	 * 
+	 * @return bool
+	 */
+	static public function isEqual( Piwik_DataTable_Row $row1, Piwik_DataTable_Row $row2 )
+	{		
+		//same columns
+		$cols1 = $row1->getColumns();
+		$cols2 = $row2->getColumns();
+		
+		uksort($cols1, 'strnatcasecmp');
+		uksort($cols2, 'strnatcasecmp');
+		
+		if($cols1 != $cols2)
+		{
+			return false;
+		}
+		
+		$dets1 = $row1->getMetadata();
+		$dets2 = $row2->getMetadata();
+		
+		ksort($dets1);
+		ksort($dets2);
+		
+		if($dets1 != $dets2)
+		{
+			return false;
+		}
+		
+		// either both are null
+		// or both have a value
+		if( !(is_null($row1->getIdSubDataTable()) 
+				&& is_null($row2->getIdSubDataTable())
+			)
+		)
+		{
+			$subtable1 = Piwik_DataTable_Manager::getInstance()->getTable($row1->getIdSubDataTable());
+			$subtable2 = Piwik_DataTable_Manager::getInstance()->getTable($row2->getIdSubDataTable());
+			if(!Piwik_DataTable::isEqual($subtable1, $subtable2))
+			{
+				return false;
+			}
+		}
+		return true;
+	}
+}
+
+require_once "Row/DataTableSummary.php";
diff --git a/core/DataTable/Row/DataTableSummary.php b/core/DataTable/Row/DataTableSummary.php
new file mode 100644
index 0000000000..6440340b14
--- /dev/null
+++ b/core/DataTable/Row/DataTableSummary.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: DataTableSummary.php 519 2008-06-09 01:59:24Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * This class creates a row from a given DataTable. 
+ * The row contains 
+ * - for each numeric column, the returned "summary" column is the sum of all the subRows
+ * - for every other column, it is ignored and will not be in the "summary row"
+ * 
+ * @see Piwik_DataTable_Row::sumRow() for more information on the algorithm
+ * 
+ * @package Piwik_DataTable
+ * @subpackage Piwik_DataTable_Row
+ */
+class Piwik_DataTable_Row_DataTableSummary extends Piwik_DataTable_Row
+{
+	function __construct($subTable)
+	{
+		parent::__construct();
+		foreach($subTable->getRows() as $row)
+		{
+			$this->sumRow($row);
+		}
+	}
+}
diff --git a/core/DataTable/Simple.php b/core/DataTable/Simple.php
new file mode 100644
index 0000000000..b010fe9abf
--- /dev/null
+++ b/core/DataTable/Simple.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Simple.php 519 2008-06-09 01:59:24Z matt $
+ * 
+ * @package Piwik_DataTable
+ */
+
+/**
+ * The DataTable_Simple is used to provide an easy way to create simple DataGrid.
+ * A DataTable_Simple actually is a DataTable with 2 columns: 'label' and 'value'.
+ * 
+ * It is usually best to return a DataTable_Simple instead of 
+ * a PHP array (or other custom data structure) in API methods:
+ * - the generic filters can be applied automatically (offset, limit, pattern search, sort, etc.)
+ * - the renderer can be applied (XML, PHP, HTML, etc.)
+ * So you don't have to write specific renderer for your data, it is already available in all the formats supported natively by Piwik.
+ * 
+ * @package Piwik_DataTable
+ */
+class Piwik_DataTable_Simple extends Piwik_DataTable
+{
+	/**
+	 * Loads in the DataTable the array information
+	 * @param array Array containing the rows information
+	 * 		array(
+	 * 			'Label row 1' => Value row 1,
+	 * 			'Label row 2' => Value row 2,
+	 * 	)
+	 * @return void
+	 */
+	function loadFromArray($array)
+	{
+		foreach($array as $label => $value)
+		{
+			$row = new Piwik_DataTable_Row;
+			$row->addColumn('label', $label);
+			$row->addColumn('value', $value);
+			$this->addRow($row);
+		}
+	}
+	
+	/**
+	 * Returns the 'value' column of the row that has a label '$label'. 
+	 *
+	 * @param string Label of the row we want the value
+	 * @return false|mixed The 'value' column of the row labelled $label
+	 */
+	function getColumn( $label )
+	{
+		$row = $this->getRowFromLabel($label);
+		if($row === false)
+		{
+			return false;
+		}
+		return $row->getColumn('value');
+	}
+}
diff --git a/core/Date.php b/core/Date.php
new file mode 100644
index 0000000000..e2331f0e68
--- /dev/null
+++ b/core/Date.php
@@ -0,0 +1,341 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Date.php 561 2008-07-21 00:00:35Z matt $
+ * 
+ * @package Piwik_Helper
+ */
+
+/**
+ * Date object widely used in Piwik.
+ * 
+ * @package Piwik_Helper
+ */
+class Piwik_Date
+{
+	/**
+	 * Returns a Piwik_Date objects. 
+	 * Accepts strings 'today' 'yesterday' or any YYYY-MM-DD or timestamp
+	 *
+	 * @param string $strDate
+	 * @return Piwik_Date
+	 */
+	static public function factory($strDate)
+	{
+		if(is_int($strDate)) 
+		{
+			return new Date($strDate);
+		}
+		if(is_string($strDate))
+		{
+			if($strDate == 'today') 
+			{
+				return self::today();
+			}
+			elseif($strDate == 'yesterday')
+			{
+				return self::yesterday();
+			}
+			else
+			{
+				if (($timestamp = strtotime($strDate)) === false) 
+				{
+					throw new Exception("The date '$strDate' is not correct. The date format is YYYY-MM-DD or you can also use magic keywords such as 'today' or 'yesterday' or any keyword supported by the strtotime function (see http://php.net/strtotime for more information)");
+				}
+				return new Piwik_Date($timestamp);
+			}
+		}
+	}
+	
+	/**
+	 * Builds a Piwik_Date object
+	 * 
+	 * @param int timestamp
+	 */
+	public function __construct( $date )
+	{
+		if(!is_int( $date ))
+		{
+			throw new Exception("Piwik_Date is expecting a unix timestamp");
+		}
+		$this->timestamp =  $date ;
+	}
+	
+	/**
+	 * Sets the time part of the date
+	 * Doesn't modify $this
+     * 
+	 * @param string $time HH:MM:SS
+	 * @return Piwik_Date The new date with the time part set
+	 */
+	//TODO test this method
+	public function setTime($time)
+	{
+		return new Piwik_Date( strtotime( $this->get("j F Y") . " $time"));
+	}
+	
+	/**
+	 * Returns the unix timestamp of the date
+	 *
+	 * @return int
+	 */
+	public function getTimestamp()
+	{
+		return $this->timestamp;
+	}
+	
+	/**
+	 * Returns true if the current date is older than the given $date
+	 *
+	 * @param Piwik_Date $date
+	 * @return bool
+	 */
+	public function isLater( Piwik_Date $date)
+	{
+		return $this->getTimestamp() > $date->getTimestamp();
+	}
+	
+	/**
+	 * Returns true if the current date is earlier than the given $date
+	 *
+	 * @param Piwik_Date $date
+	 * @return bool
+	 */
+	public function isEarlier(Piwik_Date $date)
+	{
+		return $this->getTimestamp() < $date->getTimestamp();
+	}
+	
+	/**
+	 * Returns the Y-m-d representation of the string.
+	 * You can specify the output, see the list on php.net/date
+	 *
+	 * @param string $part
+	 * @return string
+	 */
+	public function toString($part = 'Y-m-d')
+	{
+		return date($part, $this->getTimestamp());
+	}
+	
+	/**
+	 * @see toString()
+	 *
+	 * @return string
+	 */
+	public function __toString()
+	{
+		return $this->toString();
+	}
+
+    /**
+     * Sets a new day
+     * Returned is the new date object
+     * Doesn't modify $this
+     * 
+     * @param int Day eg. 31
+     * @return Piwik_Date  new date
+     */
+	public function setDay( $day )
+	{
+		$ts = $this->getTimestamp();
+		$result = mktime( 
+						date('H', $ts),
+						date('i', $ts),
+						date('s', $ts),
+						date('n', $ts),
+						1,
+						date('Y', $ts)
+					);
+		return new Piwik_Date( $result );
+	}
+	
+    /**
+     * Sets a new year
+     * Returned is the new date object
+     * Doesn't modify $this
+     * 
+     * @param int 2010
+     * @return Piwik_Date  new date
+     */
+	public function setYear( $year )
+	{
+		$ts = $this->getTimestamp();
+		$result = mktime( 
+						date('H', $ts),
+						date('i', $ts),
+						date('s', $ts),
+						date('n', $ts),
+						date('j', $ts),
+						$year
+					);
+		return new Piwik_Date( $result );
+	}
+	
+
+
+    /**
+     * Subtracts days from the existing date object and returns a new Piwik_Date object
+     * Doesn't modify $this
+     * 
+     * Returned is the new date object
+     * @return Piwik_Date  new date
+     */
+    public function subDay( $n )
+    {
+    	if($n === 0) 
+    	{
+    		return clone $this;
+    	}
+    	$ts = strtotime("-$n day", $this->getTimestamp());
+		return new Piwik_Date( $ts );
+    }
+    
+    /**
+     * Subtracts a month from the existing date object.
+     * Returned is the new date object
+     * Doesn't modify $this
+     * 
+     * @return Piwik_Date  new date
+     */
+    public function subMonth( $n )
+    {
+    	if($n === 0) 
+    	{
+    		return clone $this;
+    	}
+		$ts = $this->getTimestamp();
+		$result = mktime( 
+						date('H', $ts),
+						date('i', $ts),
+						date('s', $ts),
+						date('n', $ts) - $n,
+						1, // we set the day to 1
+						date('Y', $ts)
+					);
+		return new Piwik_Date( $result );
+    }
+    
+    /**
+     * Returns a representation of a date or datepart
+     *
+     * @param  string  OPTIONAL Part of the date to return, if null the timestamp is returned
+     * @return integer|string  date or datepart
+     */
+	public function get($part = null)
+	{
+		if(is_null($part))
+		{
+			return $this->getTimestamp();
+		}
+		return date($part, $this->getTimestamp());
+	}
+	
+	/**
+	 * Returns a localized representation of a date or datepart
+	 *
+	 * @param string OPTIONAL Part of the date to return (in strftime format), if null timestamp is returned
+	 * @return integer|string date or datepart
+	 */
+	public function getLocalized($part = null)
+	{
+		if(is_null($part))
+		{
+			return $this->getTimestamp();
+		}
+		return strftime($part, $this->getTimestamp());
+	}
+	
+    /**
+     * Adds days to the existing date object.
+     * Returned is the new date object
+     * Doesn't modify $this
+     * 
+     * @param int Number of days to add
+     * @return  Piwik_Date new date
+     */
+	public function addDay( $n )
+	{
+		$ts = strtotime("+$n day", $this->getTimestamp());
+		return new Piwik_Date( $ts );
+	}
+
+    /**
+     * Compares the week of the current date against the given $date
+     * Returns 0 if equal, -1 if current week is earlier or 1 if current week is later
+     * Example: 09.Jan.2007 13:07:25 -> compareWeek(2); -> 0
+     *
+     * @param Piwik_Date $date
+     * @return integer  0 = equal, 1 = later, -1 = earlier
+     */
+    public function compareWeek(Piwik_Date $date)
+    {
+		$currentWeek = date('W', $this->getTimestamp());
+		$toCompareWeek = date('W', $date->getTimestamp());
+		if( $currentWeek == $toCompareWeek)
+		{
+			return 0;
+		}
+		if( $currentWeek < $toCompareWeek)
+		{
+			return -1;
+		}
+		return 1;
+    }
+    /**
+     * Compares the month of the current date against the given $date month
+     * Returns 0 if equal, -1 if current month is earlier or 1 if current month is later
+     * For example: 10.03.2000 -> 15.03.1950 -> 0
+     *
+     * @param  Piwik_Date  $month   Month to compare
+     * @return integer  0 = equal, 1 = later, -1 = earlier
+     */
+	function compareMonth( Piwik_Date $date )
+	{
+		$currentMonth = date('n', $this->getTimestamp());
+		$toCompareMonth = date('n', $date->getTimestamp());
+		if( $currentMonth == $toCompareMonth)
+		{
+			return 0;
+		}
+		if( $currentMonth < $toCompareMonth)
+		{
+			return -1;
+		}
+		return 1;
+	}
+	
+	/**
+	 * Returns true if current date is today
+	 * 
+	 * @return bool
+	 */
+	public function isToday()
+	{
+		return $this->get('Y-m-d') === date('Y-m-d', time());
+	}
+	
+	/**
+	 * Returns a date object set to today midnight
+	 * 
+	 * @return Piwik_Date
+	 */
+	static public function today()
+	{
+		return new Piwik_Date(strtotime(date("Y-m-d 00:00:00")));
+	}
+	
+	/**
+	 * Returns a date object set to yesterday midnight
+	 * @return Piwik_Date
+	 */
+	static public function yesterday()
+	{
+		return new Piwik_Date(strtotime("yesterday"));
+	}
+	
+}
+
diff --git a/core/ErrorHandler.php b/core/ErrorHandler.php
new file mode 100644
index 0000000000..a985e14e6e
--- /dev/null
+++ b/core/ErrorHandler.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: ErrorHandler.php 476 2008-05-11 18:35:43Z matt $
+ * 
+ * @package Piwik_Helper
+ */
+
+require_once "Zend/Registry.php"; 
+
+if(!defined('E_STRICT'))            define('E_STRICT', 2048);
+if(!defined('E_RECOVERABLE_ERROR')) define('E_RECOVERABLE_ERROR', 4096);
+if(!defined('E_EXCEPTION')) 		define('E_EXCEPTION', 8192);
+
+/**
+ * Error handler used to display nicely errors in Piwik
+ * 
+ * @package Piwik_Helper
+ */
+function Piwik_ErrorHandler($errno, $errstr, $errfile, $errline)
+{
+	// if the error has been suppressed by the @ we don't handle the error
+	if( error_reporting() == 0 )
+	{
+		return;
+	}
+	
+	ob_start();
+	debug_print_backtrace();
+	$backtrace = ob_get_contents();
+	ob_end_clean();
+
+
+	try {
+		Zend_Registry::get('logger_error')->log($errno, $errstr, $errfile, $errline, $backtrace);
+	}catch(Exception $e){
+		// in case the error occurs before the logger creation, we simply display it
+		print("<pre>$errstr \nin '$errfile' at the line $errline\n\n$backtrace\n</pre>");
+		exit;
+	}
+	switch($errno)
+	{
+		case E_ERROR:
+		case E_PARSE:
+		case E_CORE_ERROR:
+		case E_CORE_WARNING:
+		case E_COMPILE_ERROR:
+		case E_COMPILE_WARNING:
+		case E_USER_ERROR:
+		case E_EXCEPTION:
+			exit;
+		break;
+		
+		case E_WARNING:
+		case E_NOTICE:
+		case E_USER_WARNING:
+		case E_USER_NOTICE:
+		case E_STRICT:
+		case E_RECOVERABLE_ERROR:
+		default:
+			// do not exit
+		break;
+	}
+}
+
diff --git a/core/ExceptionHandler.php b/core/ExceptionHandler.php
new file mode 100644
index 0000000000..27ad00a9c7
--- /dev/null
+++ b/core/ExceptionHandler.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: ExceptionHandler.php 444 2008-04-11 13:38:22Z johmathe $
+ * 
+ * @package Piwik_Helper
+ */
+
+require_once "core/Piwik.php";
+
+/**
+ * Exception handler used to display nicely exceptions in Piwik
+ * 
+ * @package Piwik_Helper
+ */
+function Piwik_ExceptionHandler(Exception $exception) 
+{
+	try	{
+		Zend_Registry::get('logger_exception')->log($exception);
+	} catch(Exception $e) {
+		// case when the exception is raised before the logger being ready
+		// we handle the exception a la mano, but using the Logger formatting properties
+		require_once "Log/Exception.php";
+		
+		$event = array();
+		$event['errno'] 	= $exception->getCode();
+		$event['message'] 	= $exception->getMessage();
+		$event['errfile'] 	= $exception->getFile();
+		$event['errline'] 	= $exception->getLine();
+		$event['backtrace'] = $exception->getTraceAsString();
+		
+		$formatter = new Piwik_Log_Formatter_Exception_ScreenFormatter;
+		
+		$message = $formatter->format($event);
+		$message .= "<br><br>And this exception raised another exception \"". $e->getMessage()."\"";
+		
+		Piwik::exitWithErrorMessage( $message );
+	}
+}
+
diff --git a/core/Form.php b/core/Form.php
new file mode 100644
index 0000000000..4b931c9ec6
--- /dev/null
+++ b/core/Form.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Form.php 444 2008-04-11 13:38:22Z johmathe $
+ * 
+ * @package Piwik_Helper
+ */
+
+
+require_once "HTML/QuickForm.php";
+require_once "HTML/QuickForm/Renderer/ArraySmarty.php";
+
+/**
+ * Parent class for forms to be included in Smarty
+ * 
+ * For an example, @see Piwik_Login_Form
+ * 
+ * @package Piwik_Helper
+ */
+abstract class Piwik_Form extends HTML_QuickForm
+{
+	protected $a_formElements = array();
+	
+	function __construct( $action = '' )
+	{
+		if(empty($action))
+		{
+			$action = Piwik_Url::getCurrentUrl();
+		}
+		parent::HTML_QuickForm('form', 'POST', $action);
+		
+		$this->registerRule( 'checkEmail', 'function', 'Piwik_Form_isValidEmailString');
+		$this->registerRule( 'fieldHaveSameValue', 'function', 'Piwik_Form_fieldHaveSameValue');
+	
+		$this->init();
+	}
+	
+	abstract function init();
+	
+	function getElementList()
+	{
+		$listElements=array();
+		foreach($this->a_formElements as $title =>  $a_parameters)
+		{
+			foreach($a_parameters as $parameters)
+			{
+				if($parameters[1] != 'headertext' 
+					&& $parameters[1] != 'submit')
+				{					
+					// case radio : there are two labels but only record once, unique name
+					if( !isset($listElements[$title]) 
+					|| !in_array($parameters[1], $listElements[$title]))
+					{
+						$listElements[$title][] = $parameters[1];
+					}
+				}
+			}
+		}
+		return $listElements;
+	}
+	
+	function addElements( $a_formElements, $sectionTitle = '' )
+	{
+		foreach($a_formElements as $parameters)
+		{
+			call_user_func_array(array(&$this , "addElement"), $parameters );
+		}
+		
+		$this->a_formElements = 
+					array_merge(
+							$this->a_formElements, 
+							array( 
+								$sectionTitle =>  $a_formElements
+								)
+						);
+	}
+	
+	function addRules( $a_formRules)
+	{
+		foreach($a_formRules as $parameters)
+		{
+			call_user_func_array(array(&$this , "addRule"), $parameters );
+		}
+		
+	}
+	
+}
+
+function Piwik_Form_fieldHaveSameValue($element, $value, $arg) 
+{
+	$value2 = Piwik_Common::getRequestVar( $arg, '', 'string');
+	return $value === $value2;
+}
+
+function Piwik_Form_isValidEmailString( $element, $value )
+{
+	return Piwik::isValidEmailString($value);
+}
\ No newline at end of file
diff --git a/core/FrontController.php b/core/FrontController.php
new file mode 100644
index 0000000000..0e51db1c7e
--- /dev/null
+++ b/core/FrontController.php
@@ -0,0 +1,316 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: FrontController.php 583 2008-07-28 00:37:19Z matt $
+ * 
+ * @package Piwik
+ */
+
+
+/**
+ * Zend classes
+ */
+require_once "Zend/Exception.php";
+require_once "Zend/Loader.php"; 
+require_once "Zend/Auth.php";
+require_once "Zend/Auth/Adapter/DbTable.php";
+
+/**
+ * Piwik classes
+ */
+require_once "Timer.php";
+require_once "core/Piwik.php";
+require_once "API/APIable.php";
+require_once "Access.php";
+require_once "Auth.php";
+require_once "API/Proxy.php";
+require_once "Site.php";
+require_once "Translate.php";
+require_once "Mail.php";
+require_once "Url.php";
+require_once "Controller.php";
+
+require_once "PluginsFunctions/Menu.php";
+require_once "PluginsFunctions/AdminMenu.php";
+require_once "PluginsFunctions/Widget.php";
+require_once "PluginsFunctions/Sql.php";
+
+/**
+ * Front controller.
+ * This is the class hit in the first place.
+ * It dispatches the request to the right controller.
+ * 
+ * For a detailed explanation, see the documentation on http://dev.piwik.org/trac/wiki/MainSequenceDiagram
+ * 
+ * @package Piwik
+ */
+class Piwik_FrontController
+{
+	/**
+	 * Set to false and the Front Controller will not dispatch the request
+	 *
+	 * @var bool
+	 */
+	static public $enableDispatch = true;
+	
+	static private $instance = null;
+	
+	/**
+	 * returns singleton
+	 * 
+	 * @return Piwik_FrontController
+	 */
+	static public function getInstance()
+	{
+		if (self::$instance == null)
+		{			
+			$c = __CLASS__;
+			self::$instance = new $c();
+		}
+		return self::$instance;
+	}
+	
+	/**
+	 * Dispatches the request to the right plugin and executes the requested action on the plugin controller.
+	 * 
+	 * @throws Exception in case the plugin doesn't exist, the action doesn't exist, there is not enough permission, etc.
+	 *
+	 * @param string $module
+	 * @param string $action
+	 * @param array $parameters
+	 * @return mixed The returned value of the calls, often nothing as the module print but don't return data
+	 * @see fetchDispatch() 
+	 */
+	function dispatch( $module = null, $action = null, $parameters = null)
+	{
+		if( self::$enableDispatch === false)
+		{
+			return;
+		}
+		
+		if(is_null($module))
+		{
+			$defaultModule = 'CoreHome';
+			$module = Piwik_Common::getRequestVar('module', $defaultModule, 'string');
+		}
+		
+		if(is_null($action))
+		{
+			$action = Piwik_Common::getRequestVar('action', false);
+		}
+		
+		if(is_null($parameters))
+		{
+			$parameters = array();
+		}
+		
+		if(!ctype_alnum($module))
+		{
+			throw new Exception("Invalid module name '$module'");
+		}
+		
+		if( ! Piwik_PluginsManager::getInstance()->isPluginActivated( $module )) 
+		{
+			throw new Exception_PluginDeactivated($module);
+		}
+				
+		$controllerClassName = "Piwik_".$module."_Controller";
+		if(!class_exists($controllerClassName))
+		{
+			$moduleController = "plugins/" . $module . "/Controller.php";
+			if( !Zend_Loader::isReadable($moduleController))
+			{
+				throw new Exception("Module controller $moduleController not found!");
+			}
+			require_once $moduleController;
+		}
+		
+		$controller = new $controllerClassName;
+		if($action === false)
+		{
+			$action = $controller->getDefaultAction();
+		}
+		
+		if( !is_callable(array($controller, $action)))
+		{
+			throw new Exception("Action $action not found in the controller $controllerClassName.");				
+		}
+		
+		try {
+			return call_user_func_array( array($controller, $action ), $parameters);
+		} catch(Piwik_Access_NoAccessException $e) {
+			Piwik_PostEvent('FrontController.NoAccessException', $e);					
+		}
+	}
+	
+	/**
+	 * Often plugins controller display stuff using echo/print.
+	 * Using this function instead of dispath() returns the output string form the actions calls.
+	 *
+	 * @param string $controllerName
+	 * @param string $actionName
+	 * @param array $parameters
+	 * @return string
+	 */
+	function fetchDispatch( $controllerName = null, $actionName = null, $parameters = null)
+	{
+		ob_start();
+		$output = $this->dispatch( $controllerName, $actionName, $parameters);
+		// if nothing returned we try to load something that was printed on the screen
+		if(empty($output))
+		{
+			$output = ob_get_contents();
+		}
+	    ob_end_clean();
+	    return $output;
+	}
+	
+	/**
+	 * Called at the end of the page generation
+	 *
+	 */
+	function __destruct()
+	{
+		try {
+			Piwik::printSqlProfilingReportZend();
+			Piwik::printQueryCount();
+		} catch(Exception $e) {}
+		
+		if(Piwik::getModule() !== 'API')
+		{
+//			Piwik::printMemoryUsage();
+//			Piwik::printTimer();
+		}
+	}
+	
+	/**
+	 * Checks that the directories Piwik needs write access are actually writable
+	 * Displays a nice error page if permissions are missing on some directories
+	 * 
+	 * @return void
+	 */
+	static public function checkDirectoriesWritableOrDie( $directoriesToCheck = null )
+	{
+		$resultCheck = Piwik::checkDirectoriesWritable( $directoriesToCheck );
+		if( array_search(false, $resultCheck) !== false )
+		{ 
+			$directoryList = '';
+			foreach($resultCheck as $dir => $bool)
+			{
+				$realpath = Piwik::realpath($dir);
+				if(!empty($realpath) && $bool === false)
+				{
+					$directoryList .= "<code>chmod 777 $realpath</code><br>";
+				}
+			}
+			$directoryList .= '';
+			$directoryMessage = "<p><b>Piwik couldn't write to some directories</b>.</p> <p>Try to Execute the following commands on your Linux server:</P>";
+			$directoryMessage .= $directoryList;
+			$directoryMessage .= "<p>If this doesn't work, you can try to create the directories with your FTP software, and set the CHMOD to 777 (with your FTP software, right click on the directories, permissions).";
+			$directoryMessage .= "<p>After applying the modifications, you can <a href='index.php'>refresh the page</a>.";
+			$directoryMessage .= "<p>If you need more help, try <a href='misc/redirectToUrl.php?url=http://piwik.org'>Piwik.org</a>.";
+			
+			Piwik_ExitWithMessage($directoryMessage);
+		}
+	}
+	
+	/**
+	 * Must be called before dispatch()
+	 * - checks that directories are writable,
+	 * - loads the configuration file,
+	 * - loads the plugin, 
+	 * - inits the DB connection,
+	 * - etc.
+	 * 
+	 * @return void 
+	 */
+	function init()
+	{
+		try {
+			Zend_Registry::set('timer', new Piwik_Timer);
+			
+			$directoriesToCheck = array(
+					'/tmp', 
+					'/tmp/templates_c',
+					'/tmp/cache',
+			);
+			
+			self::checkDirectoriesWritableOrDie($directoriesToCheck);
+			self::assignCliParametersToRequest();
+			
+			$exceptionToThrow = false;
+			
+			try {
+				Piwik::createConfigObject();
+			} catch(Exception $e) {
+				Piwik_PostEvent('FrontController.NoConfigurationFile', $e);
+				$exceptionToThrow = $e;
+			}
+			
+			Piwik::loadPlugins();
+			if($exceptionToThrow)
+			{
+				throw $exceptionToThrow;
+			}
+			Piwik::createDatabaseObject();
+			Piwik::createLogObject();
+			Piwik::installLoadedPlugins();
+			Piwik::install();
+			
+			Piwik_PostEvent('FrontController.initAuthenticationObject');
+			try {
+				$authAdapter = Zend_Registry::get('auth');
+			} catch(Exception $e){
+				throw new Exception("Object 'auth' cannot be found in the Registry. Maybe the Login plugin is not activated?
+									<br>You can activate the plugin by adding:<br>
+									<code>Plugins[] = Login</code><br>
+									under the <code>[Plugins]</code> section in your config/config.inc.php");
+			}
+			
+			$access = new Piwik_Access($authAdapter);
+			Zend_Registry::set('access', $access);		
+			Zend_Registry::get('access')->loadAccess();
+	
+			Piwik::raiseMemoryLimitIfNecessary();
+		} catch(Exception $e) {
+			Piwik_ExitWithMessage($e->getMessage());
+		}
+	}
+	
+	/**
+	 * Assign CLI parameters as if they were REQUEST or GET parameters.
+	 * You can trigger Piwik from the command line by
+	 * # /usr/bin/php5 /path/to/piwik/index.php -- "module=API&method=Actions.getActions&idSite=1&period=day&date=previous8&format=php"
+	 *
+	 * @return void
+	 */
+	static protected function assignCliParametersToRequest()
+	{
+		if(isset($_SERVER['argc'])
+			&& $_SERVER['argc'] > 0)
+		{
+			for ($i=1; $i < $_SERVER['argc']; $i++)
+			{
+				parse_str($_SERVER['argv'][$i],$tmp);
+				$_REQUEST = array_merge($_REQUEST, $tmp);
+				$_GET = array_merge($_GET, $tmp);
+			}
+		}				
+	}
+}
+
+/**
+ * Exception thrown when the requested plugin is not activated in the config file
+ *
+ * @package Piwik
+ */
+class Exception_PluginDeactivated extends Exception
+{
+	function __construct($module)
+	{
+		parent::__construct("The plugin '$module' is not activated. You can activate the plugin on the <a href='?module=CorePluginsAdmin'>Plugins admin page</a>.");
+	}
+}
diff --git a/core/Log.php b/core/Log.php
new file mode 100644
index 0000000000..3291065e26
--- /dev/null
+++ b/core/Log.php
@@ -0,0 +1,162 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Log.php 558 2008-07-20 23:10:38Z matt $
+ * 
+ * @package Piwik_Log
+ */
+
+require_once "Zend/Log.php";
+require_once "Zend/Log/Formatter/Interface.php";
+require_once "Zend/Log/Writer/Stream.php";
+require_once "Zend/Log/Writer/Db.php";
+
+require_once "Common.php";
+
+/**
+ * 
+ * @package Piwik_Log
+ */
+abstract class Piwik_Log extends Zend_Log
+{
+	protected $logToDatabaseTableName = null;
+	protected $logToDatabaseColumnMapping = null;
+	protected $logToFileFilename = null;
+	protected $fileFormatter = null;
+	protected $screenFormatter = null;
+	
+	function __construct( 	$logToFileFilename, 
+							$fileFormatter,
+							$screenFormatter,
+							$logToDatabaseTableName, 
+							$logToDatabaseColumnMapping )
+	{
+		parent::__construct();
+		
+		
+		$this->logToFileFilename = Zend_Registry::get('config')->path->log . $logToFileFilename;
+		$this->fileFormatter = $fileFormatter;
+		$this->screenFormatter = $screenFormatter;
+		$this->logToDatabaseTableName = Piwik::prefixTable($logToDatabaseTableName);
+		$this->logToDatabaseColumnMapping = $logToDatabaseColumnMapping;
+	}
+	
+	static public function dump($var)
+	{
+		Zend_Registry::get('logger_message')->log(var_export($var, true), Piwik_Log::DEBUG);
+	}
+	
+	function addWriteToFile()
+	{
+		$writerFile = new Zend_Log_Writer_Stream($this->logToFileFilename);
+		Piwik::mkdir(Zend_Registry::get('config')->path->log);
+		$writerFile->setFormatter( $this->fileFormatter );
+		$this->addWriter($writerFile);
+	}
+	
+	function addWriteToNull()
+	{
+		Zend_Loader::loadClass('Zend_Log_Writer_Null');
+		$this->addWriter( new Zend_Log_Writer_Null );
+	}
+	
+	function addWriteToDatabase()
+	{
+		$writerDb = new Zend_Log_Writer_Db(
+								Zend_Registry::get('db'), 
+								$this->logToDatabaseTableName, 
+								$this->logToDatabaseColumnMapping);
+		
+		$this->addWriter($writerDb);
+	}
+	
+	function addWriteToScreen()
+	{
+		$writerScreen = new Zend_Log_Writer_Stream('php://output');
+		$writerScreen->setFormatter( $this->screenFormatter );
+		$this->addWriter($writerScreen);
+	}
+	
+	public function getWritersCount()
+	{
+		return count($this->_writers);
+	}
+
+	/**
+	 * Log an event
+	 * Overload Zend_log::log
+	 */
+	public function log($event)
+	{
+		// sanity checks
+		if (empty($this->_writers)) {
+			throw new Zend_Log_Exception('No writers were added');
+		}
+
+		$event['timestamp'] = date('c');
+
+		// pack into event required by filters and writers
+		$event = array_merge( $event, $this->_extras);
+
+		// abort if rejected by the global filters
+		foreach ($this->_filters as $filter) {
+			if (! $filter->accept($event)) {
+				return;
+			}
+		}
+
+		// send to each writer
+		foreach ($this->_writers as $writer) {
+			$writer->write($event);
+		}
+	}
+
+}
+
+/**
+ * 
+ * 
+ * @package Piwik_Log
+ */
+class Piwik_Log_Formatter_FileFormatter implements Zend_Log_Formatter_Interface
+{
+	/**
+	 * Formats data into a single line to be written by the writer.
+	 *
+	 * @param  array    $event    event data
+	 * @return string             formatted line to write to the log
+	 */
+	public function format($event)
+	{
+		foreach($event as &$value)
+		{
+			$value = str_replace("\n", '\n', $value);
+			$value = '"'.$value.'"';
+		}
+		$str = implode(" ", $event) . "\n";
+		return $str;
+	}
+}
+
+class Piwik_Log_Formatter_ScreenFormatter implements Zend_Log_Formatter_Interface
+{
+	function format($string)
+	{
+		$string = self::getFormattedString($string);
+		return $string;
+	}
+	
+	static public function getFormattedString($string)
+	{
+		if(Piwik::isPhpCliMode())
+		{
+			$string = str_replace(array('<br>','<br />','<br/>'), "\n", $string);
+			$string = strip_tags($string);
+		}
+		return $string;
+	}
+}
+
diff --git a/core/Log/APICall.php b/core/Log/APICall.php
new file mode 100644
index 0000000000..33cbea423b
--- /dev/null
+++ b/core/Log/APICall.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: APICall.php 485 2008-05-19 22:29:55Z matt $
+ * 
+ * @package Piwik_Log
+ * @subpackage Piwik_Log_APICall
+ */
+
+/**
+ * Class used to log all the API Calls information (class / method / parameters / returned value / time spent)
+ * 
+ * @package Piwik_Log
+ * @subpackage Piwik_Log_APICall
+ */
+class Piwik_Log_APICall extends Piwik_Log
+{
+	const ID = 'logger_api_call';
+
+	function __construct()
+	{
+		$logToFileFilename = self::ID;
+		$logToDatabaseTableName = self::ID;
+		$logToDatabaseColumnMapping = null;
+		$screenFormatter = new Piwik_Log_Formatter_APICall_ScreenFormatter;
+		$fileFormatter = new Piwik_Log_Formatter_FileFormatter;
+		
+		parent::__construct($logToFileFilename, 
+							$fileFormatter,
+							$screenFormatter,
+							$logToDatabaseTableName, 
+							$logToDatabaseColumnMapping );
+		
+		$this->setEventItem('caller_ip', ip2long( Piwik_Common::getIp() ) );
+	}
+	
+	function log( $className, $methodName, $parameterNames,	$parameterValues, $executionTime, $returnedValue)
+	{
+		$event = array();
+		$event['class_name'] = $className;
+		$event['method_name'] = $methodName;
+		$event['parameter_names_default_values'] = serialize($parameterNames);
+		$event['parameter_values'] = serialize($parameterValues);
+		$event['execution_time'] = $executionTime;
+		$event['returned_value'] = is_array($returnedValue) ? serialize($returnedValue) : $returnedValue;
+		
+		parent::log($event);
+	}
+}
+
+/**
+ * Class used to format the API Call log on the screen. 
+ * 
+ * @package Piwik_Log
+ * @subpackage Piwik_Log_APICall
+ */
+class Piwik_Log_Formatter_APICall_ScreenFormatter extends Piwik_Log_Formatter_ScreenFormatter 
+{
+	/**
+     * Formats data into a single line to be written by the writer.
+     *
+     * @param  array    $event    event data
+     * @return string             formatted line to write to the log
+     */
+    public function format($event)
+    {
+    	$str =  "\n<br> ";
+    	$str .= "Called: {$event['class_name']}.{$event['method_name']} (took {$event['execution_time']}ms) \n<br>";
+    	$str .= "Parameters: ";
+    	$parameterNamesAndDefault = unserialize($event['parameter_names_default_values']);
+    	$parameterValues = unserialize($event['parameter_values']);
+    	
+    	$i = 0; 
+    	foreach($parameterNamesAndDefault as $pName => $pDefault)
+    	{
+    		if(isset($parameterValues[$i]))
+    		{
+	    		$currentValue = $parameterValues[$i];
+    		}
+    		else
+    		{
+    			$currentValue = $pDefault;
+    		}
+    		
+    		$currentValue = $this->formatValue($currentValue);
+    		$str .= "$pName = $currentValue, ";
+    		
+    		$i++;
+    	}
+    	$str .=  "\n<br> ";
+    	
+//    	$str .= "Returned: ".$this->formatValue($event['returned_value']);
+    	$str .=  "\n<br> ";
+    	return parent::format($str);
+    }
+    
+    private function formatValue( $value )
+    {
+    	if(is_string($value))
+		{
+			$value = "'$value'";
+		}
+		if(is_null($value))
+		{
+			$value= 'null';
+		}
+		if(is_array($value))
+		{
+			$value = "array( ".implode(", ", $value). ")";
+		}
+		return $value;
+		
+    }
+}
+
diff --git a/core/Log/Error.php b/core/Log/Error.php
new file mode 100644
index 0000000000..c2ae08f9fd
--- /dev/null
+++ b/core/Log/Error.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Error.php 485 2008-05-19 22:29:55Z matt $
+ * 
+ * @package Piwik_Log
+ * @subpackage Piwik_Log_Error
+ */
+
+/**
+ * Class used to log an error event.
+ * 
+ * @package Piwik_Log
+ * @subpackage Piwik_Log_Error
+ */
+class Piwik_Log_Error extends Piwik_Log
+{
+	const ID = 'logger_error';
+	function __construct()
+	{
+		$logToFileFilename = self::ID;
+		$logToDatabaseTableName = self::ID;
+		$logToDatabaseColumnMapping = null;
+		$screenFormatter = new Piwik_Log_Formatter_Error_ScreenFormatter;
+		$fileFormatter = new Piwik_Log_Formatter_FileFormatter;
+		
+		parent::__construct($logToFileFilename, 
+							$fileFormatter,
+							$screenFormatter,
+							$logToDatabaseTableName, 
+							$logToDatabaseColumnMapping );
+	}
+	
+	function addWriteToScreen()
+	{
+		parent::addWriteToScreen();
+		$writerScreen = new Zend_Log_Writer_Stream('php://stderr');
+		$writerScreen->setFormatter( $this->screenFormatter );
+		$this->addWriter($writerScreen);
+	}
+	
+	public function log($errno, $errstr, $errfile, $errline, $backtrace)
+	{
+		$event = array();
+		$event['errno'] = $errno;
+		$event['message'] = $errstr;
+		$event['errfile'] = $errfile;
+		$event['errline'] = $errline;
+		$event['backtrace'] = $backtrace;
+		
+		parent::log($event);
+	}
+}
+
+
+
+/**
+ * Format an error event to be displayed on the screen.
+ * 
+ * @package Piwik_Log
+ * @subpackage Piwik_Log_Error
+ */
+class Piwik_Log_Formatter_Error_ScreenFormatter extends Piwik_Log_Formatter_ScreenFormatter
+{
+	/**
+     * Formats data into a single line to be written by the writer.
+     *
+     * @param  array    $event    event data
+     * @return string             formatted line to write to the log
+     */
+    public function format($event)
+    {
+		$errno = $event['errno'] ;
+		$errstr = $event['message'] ;
+		$errfile = $event['errfile'] ;
+		$errline = $event['errline'] ;
+		$backtrace = $event['backtrace'] ;
+		
+		$strReturned = '';
+	    $errno = $errno & error_reporting();
+	    
+	    // problem when using error_reporting with the @ silent fail operator
+	    // it gives an errno 0, and in this case the objective is to NOT display anything on the screen!
+	    // is there any other case where the errno is zero at this point?
+	    if($errno == 0) return '';
+	    $strReturned .= "\n<div style='word-wrap: break-word; border: 3px solid red; padding:4px; width:70%; background-color:#FFFF96;'><b>";
+	    switch($errno)
+	    {
+	        case E_ERROR:               $strReturned .=  "Error";                  break;
+	        case E_WARNING:             $strReturned .=  "Warning";                break;
+	        case E_PARSE:               $strReturned .=  "Parse Error";            break;
+	        case E_NOTICE:              $strReturned .=  "Notice";                 break;
+	        case E_CORE_ERROR:          $strReturned .=  "Core Error";             break;
+	        case E_CORE_WARNING:        $strReturned .=  "Core Warning";           break;
+	        case E_COMPILE_ERROR:       $strReturned .=  "Compile Error";          break;
+	        case E_COMPILE_WARNING:     $strReturned .=  "Compile Warning";        break;
+	        case E_USER_ERROR:          $strReturned .=  "User Error";             break;
+	        case E_USER_WARNING:        $strReturned .=  "User Warning";           break;
+	        case E_USER_NOTICE:         $strReturned .=  "User Notice";            break;
+	        case E_STRICT:              $strReturned .=  "Strict Notice";          break;
+	        case E_RECOVERABLE_ERROR:   $strReturned .=  "Recoverable Error";      break;
+	        case E_EXCEPTION:   		$strReturned .=  "Exception";				break;
+	        default:                    $strReturned .=  "Unknown error ($errno)"; break;
+	    }
+	    $strReturned .= ":</b> <i>$errstr</i> in <b>$errfile</b> on line <b>$errline</b>\n";
+	    $strReturned .= "<br><br>Backtrace --><DIV style='font-family:Courier;font-size:10pt'>";
+	    $strReturned .= str_replace("\n", "<br>\n", $backtrace);
+	    $strReturned .= "</div><br><br>";
+	    $strReturned .= "\n</pre></div><br>";
+	    
+	    return parent::format($strReturned);
+    }
+}
+
+
diff --git a/core/Log/Exception.php b/core/Log/Exception.php
new file mode 100644
index 0000000000..d41147dcbf
--- /dev/null
+++ b/core/Log/Exception.php
@@ -0,0 +1,109 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Exception.php 583 2008-07-28 00:37:19Z matt $
+ *
+ * @package Piwik_Log
+ * @subpackage Piwik_Log_Exception
+ */
+require_once "Log.php";
+
+/**
+ * Class used to log an exception event.
+ * Displays the exception with a user friendly error message, suggests to get support from piwik.org
+ *
+ * @package Piwik_Log
+ * @subpackage Piwik_Log_Exception
+ */
+class Piwik_Log_Exception extends Piwik_Log
+{
+	const ID = 'logger_exception';
+	function __construct()
+	{
+		$logToFileFilename = self::ID;
+		$logToDatabaseTableName = self::ID;
+		$logToDatabaseColumnMapping = null;
+		$screenFormatter = new Piwik_Log_Formatter_Exception_ScreenFormatter;
+		$fileFormatter = new Piwik_Log_Formatter_FileFormatter;
+
+		parent::__construct($logToFileFilename,
+		$fileFormatter,
+		$screenFormatter,
+		$logToDatabaseTableName,
+		$logToDatabaseColumnMapping );
+	}
+
+	function addWriteToScreen()
+	{
+		parent::addWriteToScreen();
+		$writerScreen = new Zend_Log_Writer_Stream('php://stderr');
+		$writerScreen->setFormatter( $this->screenFormatter );
+		$this->addWriter($writerScreen);
+	}
+
+	public function log($exception)
+	{
+
+		$event = array();
+		$event['errno'] 	= $exception->getCode();
+		$event['message'] 	= $exception->getMessage();
+		$event['errfile'] 	= $exception->getFile();
+		$event['errline'] 	= $exception->getLine();
+		$event['backtrace'] = $exception->getTraceAsString();
+
+		parent::log($event);
+	}
+}
+
+
+/**
+ * Format an exception event to be displayed on the screen.
+ *
+ * @package Piwik_Log
+ * @subpackage Piwik_Log_Exception
+ */
+class Piwik_Log_Formatter_Exception_ScreenFormatter extends Piwik_Log_Formatter_ScreenFormatter
+{
+	/**
+	 * Formats data into a single line to be written by the writer.
+	 *
+	 * @param  array    $event    event data
+	 * @return string             formatted line to write to the log
+	 */
+	public function format($event)
+	{
+		$errno = $event['errno'] ;
+		$errstr = $event['message'] ;
+		$errfile = $event['errfile'] ;
+		$errline = $event['errline'] ;
+		$backtrace = $event['backtrace'] ;
+
+		$divId = 'div'.$errline.$errno.rand(1,2000);
+
+		$message = "<b>Uncaught exception</b>: '". $errstr."'";
+		$message .= "<br><a onclick=\"if(document.getElementById('$divId').style.display=='none') { document.getElementById('$divId').style.display='inline' } else { document.getElementById('$divId').style.display = 'none' }\" href='#'>".
+					"\nMore information</a>".
+					"<div style='display:inline' id='$divId'>".
+					"<br>In <b>$errfile</b> on line <b>$errline</b>".
+					"<br><small>Backtrace:<br><pre>";
+		$message .= str_replace("\n", "<br>", $backtrace);
+		$message .= "</pre>";
+		$message .= "</small></div>";
+
+		// without javascript it displays the full error message
+		// but with javascript we hide the DIV and onclick we show it
+		$message .= "<script>document.getElementById('$divId').style.display='none';</script>";
+
+		$message .= "<br>You can get help from <a href='misc/redirectToUrl.php?url=http://piwik.org'>Piwik.org</a> (give us the full error message + your PHP and Mysql version)";
+
+		return parent::format($message);
+	}
+}
+
+
+
+
+
diff --git a/core/Log/Message.php b/core/Log/Message.php
new file mode 100644
index 0000000000..37e44a1a24
--- /dev/null
+++ b/core/Log/Message.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Message.php 492 2008-05-23 01:08:12Z matt $
+ * 
+ * @package Piwik_Log
+ * @subpackage Piwik_Log_Message
+ */
+
+/**
+ * Class used to log a standard message event.
+ * 
+ * @package Piwik_Log
+ * @subpackage Piwik_Log_Message
+ */
+class Piwik_Log_Message extends Piwik_Log
+{
+	const ID = 'logger_message';
+	function __construct()
+	{
+		$logToFileFilename = self::ID;
+		$logToDatabaseTableName = self::ID;
+		$logToDatabaseColumnMapping = null;
+		$screenFormatter = new Piwik_Log_Formatter_Message_ScreenFormatter;
+		$fileFormatter = new Piwik_Log_Formatter_FileFormatter;
+		
+		parent::__construct($logToFileFilename, 
+							$fileFormatter,
+							$screenFormatter,
+							$logToDatabaseTableName, 
+							$logToDatabaseColumnMapping );
+	}
+	
+	public function log( $message )
+	{
+		$event = array();
+		$event['message'] = $message;
+		
+		parent::log($event);
+	}
+}
+
+
+/**
+ * Format a standard message event to be displayed on the screen.
+ * The message can be a PHP array or a string.
+ * 
+ * @package Piwik_Log
+ * @subpackage Piwik_Log_Message
+ */
+class Piwik_Log_Formatter_Message_ScreenFormatter extends Piwik_Log_Formatter_ScreenFormatter 
+{
+	/**
+     * Formats data into a single line to be written by the writer.
+     *
+     * @param  array    $event    event data
+     * @return string             formatted line to write to the log
+     */
+    public function format($event)
+    {
+    	if(is_array($event['message']))
+    	{
+    		$message = "<pre>".var_export($event['message'], true)."</pre>";
+    	}
+    	else
+    	{
+    		$message = $event['message'];
+    	}
+    	
+    	return parent::format($message);
+    }
+}
+
diff --git a/core/LogStats.php b/core/LogStats.php
new file mode 100644
index 0000000000..e9519da763
--- /dev/null
+++ b/core/LogStats.php
@@ -0,0 +1,303 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: LogStats.php 575 2008-07-26 23:08:32Z matt $
+ * 
+ * @package Piwik_LogStats
+ */
+
+/**
+ * Class used by the logging script piwik.php called by the javascript tag.
+ * Handles the visitor & his/her actions on the website, saves the data in the DB, saves information in the cookie, etc.
+ * 
+ * To maximise the performance of the logging module, we use different techniques.
+ * 
+ * On the PHP-only side:
+ * - minimize the number of external files included. 
+ * 	 Ideally only one (the configuration file) in all the normal cases.
+ *   We load the Loggers only when an error occurs ; this error is logged in the DB/File/etc
+ *   depending on the loggers settings in the configuration file.
+ * - we may have to include external classes but we try to include only very 
+ *   simple code without any dependency, so that we could simply write a script
+ *   that would merge all this simple code into a big piwik.php file.
+ * 
+ * On the Database-related side:
+ * - write all the SQL queries without using any DB abstraction layer.
+ * 	 Of course we carefully filter all input values.
+ * - minimize the number of SQL queries necessary to complete the algorithm.
+ * - carefully index the tables used
+ * - try to have fixed length rows
+ * 
+ * [ - use a partitionning by date for the tables ]
+ *   
+ * - handle the timezone settings??
+ * 
+ * We could also imagine a batch system that would read a log file every 5min,
+ * and which prepares the file containg the rows to insert, then we load DATA INFILE 
+ * 
+ * 
+ * Configuration options for the statsLogEngine module:
+ * - use_cookie  ; defines if we try to get/set a cookie to help recognize a unique visitor
+ * 
+ * @package Piwik_LogStats
+ */
+class Piwik_LogStats
+{	
+	protected $stateValid;
+	
+	protected $urlToRedirect;
+	
+	/**
+	 *
+	 * @var Piwik_LogStats_Db
+	 */
+	static protected $db = null;
+	
+	const STATE_NOTHING_TO_NOTICE = 1;
+	const STATE_TO_REDIRECT_URL = 2;
+	const STATE_LOGGING_DISABLE = 10;
+	const STATE_NO_GET_VARIABLE = 11;
+		
+	const COOKIE_INDEX_IDVISITOR 				= 1;
+	const COOKIE_INDEX_TIMESTAMP_LAST_ACTION 	= 2;
+	const COOKIE_INDEX_TIMESTAMP_FIRST_ACTION 	= 3;
+	const COOKIE_INDEX_ID_VISIT 				= 4;
+	const COOKIE_INDEX_ID_LAST_ACTION 			= 5;
+	
+	public function __construct()
+	{
+		$this->stateValid = self::STATE_NOTHING_TO_NOTICE;
+	}
+	
+	static function connectDatabase()
+	{
+		if( !is_null(self::$db))
+		{
+			return;
+		}
+		
+		$configDb = Piwik_LogStats_Config::getInstance()->database;
+		
+		// we decode the password. Password is html encoded because it's enclosed between " double quotes
+		$configDb['password'] = htmlspecialchars_decode($configDb['password']);
+		if(!isset($configDb['port']))
+		{
+			// before 0.2.4 there is no port specified in config file
+			$configDb['port'] = '3306';  
+		}
+		self::$db = new Piwik_LogStats_Db( 	$configDb['host'], 
+										$configDb['username'], 
+										$configDb['password'], 
+										$configDb['dbname'],
+										$configDb['port'] );
+		self::$db->connect();
+	}
+	
+	public static function getDb()
+	{
+		return self::$db;
+	}
+
+	static function disconnectDb()
+	{
+		if(isset(self::$db))
+		{
+			self::$db->disconnect();
+		}
+	}
+	
+	private function initProcess()
+	{
+		try{
+			$pluginsLogStats = Piwik_LogStats_Config::getInstance()->Plugins_LogStats;
+			if(is_array($pluginsLogStats)
+				&& count($pluginsLogStats) != 0)
+			{
+				Piwik_PluginsManager::getInstance()->doNotLoadAlwaysActivatedPlugins();
+				Piwik_PluginsManager::getInstance()->setPluginsToLoad( $pluginsLogStats['Plugins_LogStats'] );
+			}
+		} catch(Exception $e) {		
+		}
+		
+		$saveStats = Piwik_LogStats_Config::getInstance()->LogStats['record_statistics'];
+
+		if($saveStats == 0)
+		{
+			$this->setState(self::STATE_LOGGING_DISABLE);
+		}
+
+		if( count($_GET) == 0)
+		{
+			$this->setState(self::STATE_NO_GET_VARIABLE);			
+		}
+
+		$downloadVariableName = Piwik_LogStats_Config::getInstance()->LogStats['download_url_var_name'];
+		$urlDownload = Piwik_Common::getRequestVar( $downloadVariableName, '', 'string');
+
+		if( !empty($urlDownload) )
+		{
+			if( Piwik_Common::getRequestVar( 'redirect', 1, 'int') == 1)
+			{
+				$this->setState( self::STATE_TO_REDIRECT_URL );
+			}
+			$this->setUrlToRedirect ( $urlDownload);
+		}
+		
+		$outlinkVariableName = Piwik_LogStats_Config::getInstance()->LogStats['outlink_url_var_name'];
+		$urlOutlink = Piwik_Common::getRequestVar( $outlinkVariableName, '', 'string');
+		
+		if( !empty($urlOutlink) )
+		{
+			if( Piwik_Common::getRequestVar( 'redirect', 1, 'int') == 1)
+			{
+				$this->setState( self::STATE_TO_REDIRECT_URL );
+			}
+			$this->setUrlToRedirect ( $urlOutlink);
+		}
+	}
+	
+	private function processVisit()
+	{
+		return $this->stateValid !== self::STATE_LOGGING_DISABLE
+				&&  $this->stateValid !== self::STATE_NO_GET_VARIABLE;
+	}
+	
+	private function getState()
+	{
+		return $this->stateValid;
+	}
+	
+	private function setUrlToRedirect( $url )
+	{
+		$this->urlToRedirect = $url;
+	}
+	
+	private function getUrlToRedirect()
+	{
+		return $this->urlToRedirect;
+	}
+	
+	private function setState( $value )
+	{
+		$this->stateValid = $value;
+	}
+	
+	/**
+	 * Returns the LogStats_Visit object.
+	 * This method can be overwritten so that we use a different LogStats_Visit object
+	 *
+	 * @return Piwik_LogStats_Visit
+	 */
+	protected function getNewVisitObject()
+	{
+		$visit = null;
+		Piwik_PostEvent('LogStats.getNewVisitObject', $visit);
+	
+		if(is_null($visit))
+		{
+			$visit = new Piwik_LogStats_Visit();
+		}
+		elseif(!($visit instanceof Piwik_LogStats_Visit_Interface ))
+		{
+			throw new Exception("The Visit object set in the plugin must implement Piwik_LogStats_Visit_Interface");
+		}
+		
+		$visit->setDb(self::$db);
+		return $visit;
+	}
+	
+	// main algorithm 
+	// => input : variables filtered
+	// => action : read cookie, read database, database logging, cookie writing
+	function main()
+	{
+		$this->initProcess();
+		
+		if( $this->processVisit() )
+		{
+			try {
+				self::connectDatabase();
+				$visit = $this->getNewVisitObject();
+				$visit->handle();
+			} catch (PDOException $e) {
+				$this->setState(self::STATE_LOGGING_DISABLE);
+			}				
+		}
+		$this->endProcess();
+	}	
+
+	// display the logo or pixel 1*1 GIF
+	// or a marketing page if no parameters in the url
+	// or redirect to a url
+	// or load a URL (rss feed) (transmit the cookie as well)
+	protected function endProcess()
+	{
+		switch($this->getState())
+		{
+			case self::STATE_LOGGING_DISABLE:
+				printDebug("Logging disabled, display transparent logo");
+				$this->outputTransparentGif();
+			break;
+			
+			case self::STATE_NO_GET_VARIABLE:
+				printDebug("No get variables => piwik page");
+				echo "<a href='index.php'>Piwik</a> is a free open source <a href='http://piwik.org'>web analytics</a> alternative to Google analytics.";
+			break;
+			
+			
+			case self::STATE_TO_REDIRECT_URL:
+				$this->sendHeader('Location: ' . $this->getUrlToRedirect());
+			break;
+			
+			
+			case self::STATE_NOTHING_TO_NOTICE:
+			default:
+				printDebug("Nothing to notice => default behaviour");
+				$this->outputTransparentGif();
+			break;
+		}
+		printDebug("End of the page.");
+		
+		if($GLOBALS['DEBUGPIWIK'] === true)
+		{
+			Piwik::printSqlProfilingReportLogStats(self::$db);
+		}
+		
+		self::disconnectDb();
+	}
+	
+	protected function outputTransparentGif()
+	{
+		if( !isset($GLOBALS['DEBUGPIWIK']) || !$GLOBALS['DEBUGPIWIK'] ) 
+		{
+			$trans_gif_64 = "R0lGODlhAQABAJEAAAAAAP///////wAAACH5BAEAAAIALAAAAAABAAEAAAICVAEAOw==";
+			header("Content-type: image/gif");
+			print(base64_decode($trans_gif_64));
+		}
+	}
+	
+	protected function sendHeader($header)
+	{
+		header($header);
+	}	
+}
+
+function printDebug( $info = '' )
+{
+	if(isset($GLOBALS['DEBUGPIWIK']) && $GLOBALS['DEBUGPIWIK'])
+	{
+		if(is_array($info))
+		{
+			print("<PRE>");
+			print(var_export($info,true));
+			print("</PRE>");
+		}
+		else
+		{
+			print($info . "<br>\n");
+		}
+	}
+}
diff --git a/core/LogStats/Action.php b/core/LogStats/Action.php
new file mode 100644
index 0000000000..942f1899c9
--- /dev/null
+++ b/core/LogStats/Action.php
@@ -0,0 +1,242 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Action.php 558 2008-07-20 23:10:38Z matt $
+ * 
+ * @package Piwik_LogStats
+ */
+
+/**
+ * Interface of the Action object.
+ * New Action classes can be defined in plugins and used instead of the default one.
+ * 
+ * @package Piwik_LogStats
+ */
+interface Piwik_LogStats_Action_Interface {
+	public function getActionId();
+	public function record( $idVisit, $idRefererAction, $timeSpentRefererAction );
+}
+
+/**
+ * Handles an action by the visitor.
+ * A request to the piwik.php script is associated with one Action.
+ * This class is used to build the Action Name (which can be built from the URL, 
+ * or can be directly specified in the JS code, etc.).
+ * It also saves the Action when necessary in the DB. 
+ *  
+ * About the Action concept:
+ * - An action is defined by a name.
+ * - The name can be specified in the JS Code in the variable 'action_name'
+ *    For example you can decide to use the javascript value document.title as an action_name
+ * - If the name is not specified, we use the URL(path+query) to build a default name.
+ *    For example for "http://piwik.org/test/my_page/test.html" 
+ *    the name would be "test/my_page/test.html"
+ * - If the name is empty we set it to default_action_name found in global.ini.php
+ * - Handling UTF8 in the action name
+ * PLUGIN_IDEA - An action is associated to URLs and link to the URL from the reports (currently actions do not link to the url of the pages)
+ * PLUGIN_IDEA - An action hit by a visitor is associated to the HTML title of the page that triggered the action and this HTML title is displayed in the interface
+ * 
+ * 
+ * @package Piwik_LogStats
+ */
+class Piwik_LogStats_Action implements Piwik_LogStats_Action_Interface
+{
+	private $actionName;
+	private $url;
+	private $defaultActionName;
+	private $nameDownloadOutlink;
+	
+	/**
+	 * 3 types of action, Standard action / Download / Outlink click
+	 */
+	const TYPE_ACTION   = 1;
+	const TYPE_DOWNLOAD = 3;
+	const TYPE_OUTLINK  = 2;
+	
+	/**
+	 * @param Piwik_LogStats_Db Database object to be used
+	 */
+	function __construct( $db )
+	{
+		$this->actionName = Piwik_Common::getRequestVar( 'action_name', '', 'string');
+		
+		$downloadVariableName = Piwik_LogStats_Config::getInstance()->LogStats['download_url_var_name'];
+		$this->downloadUrl = Piwik_Common::getRequestVar( $downloadVariableName, '', 'string');
+		
+		$outlinkVariableName = Piwik_LogStats_Config::getInstance()->LogStats['outlink_url_var_name'];
+		$this->outlinkUrl = Piwik_Common::getRequestVar( $outlinkVariableName, '', 'string');
+		
+		$nameVariableName = Piwik_LogStats_Config::getInstance()->LogStats['download_outlink_name_var'];
+		$this->nameDownloadOutlink = Piwik_Common::getRequestVar( $nameVariableName, '', 'string');
+		
+		$this->url = Piwik_Common::getRequestVar( 'url', '', 'string');
+		$this->db = $db;
+		$this->defaultActionName = Piwik_LogStats_Config::getInstance()->LogStats['default_action_name'];
+	}
+	
+	
+	/**
+	 * Returns the idaction of the current action name.
+	 * This idaction is used in the visitor logging table to link the visit information 
+	 * (entry action, exit action) to the actions.
+	 * This idaction is also used in the table that links the visits and their actions.
+	 * 
+	 * The methods takes care of creating a new record in the action table if the existing 
+	 * action name doesn't exist yet.
+	 * 
+	 * @return int Id action that is associated to this action name in the Actions table lookup
+	 */
+	function getActionId()
+	{
+		$this->loadActionId();
+		return $this->idAction;
+	}
+	
+	
+	/**
+	 * Records in the DB the association between the visit and this action.
+	 * 
+	 * @param int idVisit is the ID of the current visit in the DB table log_visit
+	 * @param int idRefererAction is the ID of the last action done by the current visit. 
+	 * @param int timeSpentRefererAction is the number of seconds since the last action was done. 
+	 * 				It is directly related to idRefererAction.
+	 */
+	 public function record( $idVisit, $idRefererAction, $timeSpentRefererAction)
+	 {
+	 	$this->db->query("INSERT INTO ".$this->db->prefixTable('log_link_visit_action')
+						." (idvisit, idaction, idaction_ref, time_spent_ref_action) VALUES (?,?,?,?)",
+					array($idVisit, $this->idAction, $idRefererAction, $timeSpentRefererAction)
+					);
+	 }
+	 
+	 /**
+	 * Generates the name of the action from the URL or the specified name.
+	 * Sets the name as $this->finalActionName
+	 * 
+	 * @return void
+	 */
+	private function generateInfo()
+	{
+		$actionName = '';
+		if(!empty($this->downloadUrl))
+		{
+			$this->actionType = self::TYPE_DOWNLOAD;
+			$url = $this->downloadUrl;
+			//$actionName = $this->nameDownloadOutlink;
+			$actionName = $url;
+		}
+		elseif(!empty($this->outlinkUrl))
+		{
+			$this->actionType = self::TYPE_OUTLINK;
+			$url = $this->outlinkUrl;
+			//remove the last '/' character if it's present
+			if(substr($url,-1) == '/')
+			{
+				$url = substr($url,0,-1);
+			}
+			$actionName = $this->nameDownloadOutlink;
+			if( empty($actionName) )
+			{
+				$actionName = $url;
+			}
+		}
+		else
+		{
+			$this->actionType = self::TYPE_ACTION;
+			$url = $this->url;
+			$actionName = $this->actionName;
+		}		
+		
+		// the ActionName wasn't specified
+		if( empty($actionName) )
+		{
+			$actionName = trim(Piwik_Common::getPathAndQueryFromUrl($url));
+			
+			// in case the $actionName is ending with a slash, 
+			// which means that it is the index page of a category 
+			// we append the defaultActionName 
+			// toto/tata/ becomes toto/tata/index 
+			if(strlen($actionName) > 0 
+				&& $actionName[strlen($actionName)-1] == '/'
+			)
+			{
+				$actionName.=$this->defaultActionName;
+			}
+		}
+		
+		/*
+		 * Clean the action name
+		 */
+		 
+		// get the delimiter, by default '/'
+		$actionCategoryDelimiter = Piwik_LogStats_Config::getInstance()->General['action_category_delimiter'];
+		
+		// case the name is an URL we dont clean the name the same way
+		if(Piwik_Common::isLookLikeUrl($actionName))
+		{
+			$actionName = trim($actionName);
+		}
+		else
+		{
+			// create an array of the categories delimited by the delimiter
+			$split = explode($actionCategoryDelimiter, $actionName);
+			
+			// trim every category
+			$split = array_map('trim', $split);
+			
+			// remove empty categories
+			$split = array_filter($split);
+			
+			// rebuild the name from the array of cleaned categories
+			$actionName = implode($actionCategoryDelimiter, $split);
+		}
+		
+		// remove the extra bad characters if any (shouldn't be any at this point...)
+		$actionName = str_replace(array("\n", "\r"), '', $actionName);
+		
+		if(empty($actionName))
+		{
+			$actionName = $this->defaultActionName;
+		}
+		
+		$this->finalActionName = $actionName;
+	}
+	
+	/**
+	 * Sets the attribute $idAction based on $finalActionName and $actionType.
+	 * 
+	 * @see getActionId()
+	 */
+	private function loadActionId()
+	{		
+		$this->generateInfo();
+		
+		$name = $this->finalActionName;
+		$type = $this->actionType;
+		
+		$idAction = $this->db->fetch("	SELECT idaction 
+							FROM ".$this->db->prefixTable('log_action')
+						."  WHERE name = ? AND type = ?", 
+						array($name, $type) 
+					);
+		
+		// the action name has not been found, create it
+		if($idAction === false)
+		{
+			$this->db->query("INSERT INTO ". $this->db->prefixTable('log_action'). "( name, type ) 
+								VALUES (?,?)",array($name,$type) );
+			$idAction = $this->db->lastInsertId();
+		}
+		else
+		{
+			$idAction = $idAction['idaction'];
+		}
+		
+		$this->idAction = $idAction;
+	}
+	
+}
+
diff --git a/core/LogStats/Config.php b/core/LogStats/Config.php
new file mode 100644
index 0000000000..65c38d9144
--- /dev/null
+++ b/core/LogStats/Config.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Config.php 450 2008-04-20 22:33:27Z matt $
+ * 
+ * @package Piwik_LogStats
+ */
+
+/**
+ * Simple class to access the configuration file
+ * 
+ * This is essentially a simple version of Zend_Config that we wrote 
+ * because of performance reasons. 
+ * The LogStats module can't afford a dependency with the Zend_Framework.
+ * 
+ * It's using the php.net/parse_ini_file function to parse the configuration files.
+ * It can be used to access both user config.ini.php and piwik global.ini.php config file.
+ * 
+ * @package Piwik_LogStats
+ */
+class Piwik_LogStats_Config
+{
+	static private $instance = null;
+	
+	/**
+	 * Returns singleton
+	 *
+	 * @return Piwik_LogStats_Config
+	 */
+	static public function getInstance()
+	{
+		if (self::$instance == null)
+		{			
+			$c = __CLASS__;
+			self::$instance = new $c();
+		}
+		return self::$instance;
+	}
+	
+	/**
+	 * Contains configuration files values
+	 *
+	 * @var array
+	 */
+	public $config = array();
+	
+	private function __construct()
+	{
+		$pathIniFileUser = 'config/config.ini.php';
+		$pathIniFileGlobal = 'config/global.ini.php';
+		$this->configUser = parse_ini_file($pathIniFileUser, true);
+		$this->configGlobal = parse_ini_file($pathIniFileGlobal, true);
+	}
+	
+	/**
+	 * Magic get methods catching calls to $config->var_name
+	 * Returns the value if found in the 
+	 *
+	 * @param string $name
+	 * @return mixed The value requested, usually a string
+	 * @throws exception if the value requested not found in both files
+	 */
+	public function __get( $name )
+	{
+		if(isset($this->configUser[$name]))
+		{
+			return $this->configUser[$name];
+		}
+		if(isset($this->configGlobal[$name]))
+		{
+			return $this->configGlobal[$name];
+		}
+		throw new Exception("The config element $name is not available in the configuration (check the configuration file).");
+	}
+}
+
+
diff --git a/core/LogStats/Db.php b/core/LogStats/Db.php
new file mode 100644
index 0000000000..f190c0c61c
--- /dev/null
+++ b/core/LogStats/Db.php
@@ -0,0 +1,261 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Db.php 522 2008-06-11 00:31:03Z matt $
+ * 
+ * @package Piwik_LogStats
+ */
+
+/**
+ * Simple database PDO wrapper.
+ * We can't afford to have a dependency with the Zend_Db module in LogStats.
+ * We wrote this simple class 
+ * 
+ * @package Piwik_LogStats
+ */
+
+class Piwik_LogStats_Db 
+{
+	private $connection = null;
+	private $username;
+	private $password;
+	
+	static private $profiling = false;
+
+	protected $queriesProfiling = array();
+	
+	/**
+	 * Builds the DB object
+	 */
+	public function __construct( $host, $username, $password, $dbname, $port, $driverName = 'mysql') 
+	{
+		$this->dsn = $driverName.":dbname=$dbname;host=$host;port=$port";
+		$this->username = $username;
+		$this->password = $password;
+	}
+
+	
+	/**
+	 * Returns true if the SQL profiler is enabled
+	 * Only used by the unit test that tests that the profiler is off on a  production server
+	 * 
+	 * @return bool 
+	 */
+	static public function isProfilingEnabled()
+	{
+		return self::$profiling;
+	}
+	
+	/**
+	 * Enables the SQL profiling. 
+	 * For each query, saves in the DB the time spent on this query. 
+	 * Very useful to see the slow query under heavy load.
+	 * You can then use Piwik::printSqlProfilingReportLogStats(); 
+	 * to display the SQLProfiling report and see which queries take time, etc.
+	 */
+	static public function enableProfiling()
+	{
+		self::$profiling = true;
+	}
+	
+	/** 
+	 * Disables the SQL profiling logging.
+	 */
+	static public function disableProfiling()
+	{
+		self::$profiling = false;
+	}
+	
+	/**
+	 * Connects to the DB
+	 * 
+	 * @throws Exception if there was an error connecting the DB
+	 */
+	public function connect() 
+	{
+		if(self::$profiling)
+		{
+			$timer = $this->initProfiler();
+		}
+		
+		$this->connection = new PDO($this->dsn, $this->username, $this->password);
+		$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+		// we may want to setAttribute(PDO::ATTR_TIMEOUT ) to a few seconds (default is 60) in case the DB is locked
+		// the piwik.php would stay waiting for the database... bad!
+		// we delete the password from this object "just in case" it could be printed 
+		$this->password = '';
+		
+		if(self::$profiling)
+		{
+			$this->recordQueryProfile('connect', $timer);
+		}
+	}
+	
+	/**
+	 * Disconnects from the Mysql server
+	 *
+	 * @return void
+	 */
+	public function disconnect()
+	{
+		if(self::$profiling)
+		{
+			$this->recordProfiling();
+		}
+		$this->connection = null;
+	}
+
+	/**
+	 * Returns the table name prefixed by the table prefix.
+	 * 
+	 * @param string The table name to prefix, ie "log_visit"
+	 * @return string The table name prefixed, ie "piwik-production_log_visit"
+	 */
+	public function prefixTable( $suffix )
+	{
+		static $prefix;
+		if (!isset($prefix)) {
+			$prefix = Piwik_LogStats_Config::getInstance()->database['tables_prefix'];
+		}		
+		return $prefix . $suffix;
+	}
+	
+	/**
+	 * Returns an array containing all the rows of a query result, using optional bound parameters.
+	 * 
+	 * @param string Query 
+	 * @param array Parameters to bind
+	 * @see also query()
+	 * @throws Exception if an exception occured
+	 */
+	public function fetchAll( $query, $parameters = array() )
+	{
+		try {
+			$sth = $this->query( $query, $parameters );
+			if($sth === false)
+			{
+				return false;
+			}
+			return $sth->fetchAll(PDO::FETCH_ASSOC);
+		} catch (PDOException $e) {
+			throw new Exception("Error query: ".$e->getMessage());
+		}
+	}
+	
+	/**
+	 * Returns the first row of a query result, using optional bound parameters.
+	 * 
+	 * @param string Query 
+	 * @param array Parameters to bind
+	 * @see also query()
+	 * 
+	 * @throws Exception if an exception occured
+	 */
+	public function fetch( $query, $parameters = array() )
+	{
+		try {
+			$sth = $this->query( $query, $parameters );
+			if($sth === false)
+			{
+				return false;
+			}
+			return $sth->fetch(PDO::FETCH_ASSOC);			
+		} catch (PDOException $e) {
+			throw new Exception("Error query: ".$e->getMessage());
+		}
+	}
+	
+	/**
+	 * Executes a query, using optional bound parameters.
+	 * 
+	 * @param string Query 
+	 * @param array Parameters to bind
+	 * 
+	 * @return PDOStatement or false if failed
+	 * @throw Exception if an exception occured
+	 */
+	public function query($query, $parameters = array()) 
+	{
+		if(is_null($this->connection))
+		{
+			return false;
+		}
+		try {
+			if(self::$profiling)
+			{
+				$timer = $this->initProfiler();
+			}
+			
+			$sth = $this->connection->prepare($query);
+			$sth->execute( $parameters );
+			
+			if(self::$profiling)
+			{
+				$this->recordQueryProfile($query, $timer);
+			}
+			return $sth;
+		} catch (PDOException $e) {
+			throw new Exception("Error query: ".$e->getMessage());
+		}
+	}
+	
+	protected function initProfiler()
+	{
+		require_once "Timer.php";
+		return new Piwik_Timer;
+	}
+	
+	protected function recordQueryProfile( $query, $timer )
+	{
+		if(!isset($this->queriesProfiling[$query])) $this->queriesProfiling[$query] = array('sum_time_ms' => 0, 'count' => 0);
+		$time = $timer->getTimeMs(2);
+		$time += $this->queriesProfiling[$query]['sum_time_ms'];
+		$count = $this->queriesProfiling[$query]['count'] + 1;
+		$this->queriesProfiling[$query]	= array('sum_time_ms' => $time, 'count' => $count);
+	}
+	
+	/**
+	 * Returns the last inserted ID in the DB
+	 * Wrapper of PDO::lastInsertId()
+	 * 
+	 * @return int
+	 */
+	public function lastInsertId()
+	{
+		return $this->connection->lastInsertId();
+	}
+	
+	/**
+	 * When destroyed, if SQL profiled enabled, logs the SQL profiling information
+	 */
+	public function recordProfiling()
+	{
+		if(is_null($this->connection)) 
+		{
+			return;
+		}
+	
+		// turn off the profiler so we don't profile the following queries 
+		self::$profiling = false;
+		
+		foreach($this->queriesProfiling as $query => $info)
+		{
+			$time = $info['sum_time_ms'];
+			$count = $info['count'];
+
+			$queryProfiling = "INSERT INTO ".$this->prefixTable('log_profiling')."
+						(query,count,sum_time_ms) VALUES (?,$count,$time)
+						ON DUPLICATE KEY 
+							UPDATE count=count+$count,sum_time_ms=sum_time_ms+$time";
+			$this->query($queryProfiling,array($query));
+		}
+		
+		// turn back on profiling
+		self::$profiling = true;
+	}
+}
+
+
diff --git a/core/LogStats/Generator.php b/core/LogStats/Generator.php
new file mode 100644
index 0000000000..dfc6b03052
--- /dev/null
+++ b/core/LogStats/Generator.php
@@ -0,0 +1,666 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Generator.php 492 2008-05-23 01:08:12Z matt $
+ * 
+ * @package Piwik_LogStats
+ */
+
+/**
+ * Class used to generate fake visits. 
+ * Useful to test performances, general functional testing, etc.
+ * 
+ * Objective:
+ * Generate thousands of visits / actions per visitor using 
+ *  a single request to misc/generateVisits.php
+ * 
+ * Requirements of the visits generator script. Fields that can be edited:
+ * - url => campaigns
+ * 		- newsletter
+ * 		- partner
+ * 		- campaign CPC
+ * - referer
+ * 		- search engine
+ * 		- misc site
+ * 		- same website
+ * - url => multiple directories, page names
+ * - multiple idsite
+ * - multiple settings configurations 
+ * - action_name 
+ * - HTML title
+ * 
+ *  
+ * @package Piwik_LogStats
+ * @subpackage Piwik_LogStats_Generator
+ * 
+ * 											"Le Generator, il est trop Fort!"
+ * 											- Random fan
+ */
+
+class Piwik_LogStats_Generator
+{
+	/**
+	 * GET parameters array of values to be used for the current visit
+	 *
+	 * @var array ('res' => '1024x768', 'urlref' => 'http://google.com/search?q=piwik', ...)
+	 */
+	protected $currentget	=	array();
+	
+	/**
+	 * Array of all the potential values for the visit parameters
+	 * Values of 'resolution', 'urlref', etc. will be randomly read from this array
+	 *
+	 * @var array ( 
+	 * 			'res' => array('1024x768','800x600'), 
+	 * 			'urlref' => array('google.com','intel.com','amazon.com'),
+	 * 			....)
+	 */
+	protected $allget		=	array();
+	
+	/**
+	 * See @see setMaximumUrlDepth
+	 *
+	 * @var int
+	 */
+	protected $maximumUrlDepth = 1;
+	
+	/**
+	 * Unix timestamp to use for the generated visitor 
+	 *
+	 * @var int Unix timestamp
+	 */
+	protected $timestampToUse;
+	
+	/**
+	 * See @see disableProfiler()
+	 * The profiler is enabled by default
+	 *
+	 * @var bool
+	 */
+	protected $profiling 	= true;
+	
+	/**
+	 * If set to true, this will TRUNCATE the profiling tables at every new generated visit 
+	 * @see initProfiler()
+	 * 
+	 * @var bool
+	 */
+	public $reinitProfilingAtEveryRequest = true;
+	
+	/**
+	 * Hostname used to prefix all the generated URLs
+	 * we could make this variable dynamic so that a visitor can make hit on several hosts and
+	 * only the good ones should be kept (feature not yet implemented in piwik)
+	 * 
+	 * @var string
+	 */
+	public $host = 'http://localhost';
+	
+	/**
+	 * IdSite to generate visits for (@see setIdSite())
+	 *
+	 * @var int
+	 */
+	public $idSite = 1;
+	
+	/**
+	 * Overwrite the global GET/POST/COOKIE variables and set the fake ones @see setFakeRequest()
+	 * Reads the configuration file but disables write to this file
+	 * Creates the database object & enable profiling by default (@see disableProfiler())
+	 *
+	 */
+	public function __construct()
+	{
+		$_COOKIE = $_GET = $_REQUEST = $_POST = array();
+		
+		// init GET and REQUEST to the empty array
+		$this->setFakeRequest();
+		
+		require_once "core/Piwik.php";
+		Piwik::createConfigObject('../config/config.ini.php');
+		Zend_Registry::get('config')->doWriteFileWhenUpdated = false;
+		
+		// setup database	
+		Piwik::createDatabaseObject();
+		
+		Piwik_LogStats_Db::enableProfiling();
+		
+		$this->timestampToUse = time();
+	}
+	
+	/**
+	 * Sets the depth level of the generated URLs
+	 * value = 1 => path OR path/page1
+	 * value = 2 => path OR path/pageRand OR path/dir1/pageRand
+	 * 
+	 * @param int Depth
+	 */
+	public function setMaximumUrlDepth($value)
+	{
+		$this->maximumUrlDepth = (int)$value;
+	}
+	
+	/**
+	 * Set the timestamp to use as the starting time for the visitors times
+	 * You have to call this method for every day you want to generate data
+	 * 
+	 * @param int Unix timestamp
+	 */
+	public function setTimestampToUse($timestamp)
+	{
+		$this->timestampToUse = $timestamp;
+	}
+	
+	/**
+	 * Returns the timestamp to be used as the visitor timestamp
+	 * 
+	 * @return int Unix timestamp
+	 */
+	public function getTimestampToUse()
+	{
+		return $this->timestampToUse;
+	}
+
+	/**
+	 * Set the idsite to generate the visits for
+	 * To be called before init()
+	 * 
+	 * @param int idSite
+	 */
+	public function setIdSite($idSite)
+	{
+		$this->idSite = $idSite;
+	}
+	
+	/**
+	 * Add a value to the GET global array.
+	 * The generator script will then randomly read a value from this array.
+	 * 
+	 * For example, $name = 'res' $aValue = '1024x768' 
+	 * 
+	 * @param string Name of the parameter _GET[$name]
+	 * @param array|mixed Value of the parameter
+	 * @return void
+	 */
+	protected function addParam( $name, $aValue)
+	{
+		if(is_array($aValue))
+		{	
+			$this->allget[$name] = array_merge(	$aValue,
+												(array)@$this->allget[$name]);
+		}
+		else
+		{
+			$this->allget[$name][] = $aValue;
+		}
+	}
+	
+	/**
+	 * TRUNCATE all logs related tables to start a fresh logging database.
+	 * Be careful, any data deleted this way is deleted forever
+	 * 
+	 * @return void
+	 */
+	public function emptyAllLogTables()
+	{
+		$db = Zend_Registry::get('db');
+		$db->query('TRUNCATE TABLE '.Piwik::prefixTable('log_action'));
+		$db->query('TRUNCATE TABLE '.Piwik::prefixTable('log_visit'));
+		$db->query('TRUNCATE TABLE '.Piwik::prefixTable('log_link_visit_action'));
+	}
+	
+	/**
+	 * Call this method to disable the SQL query profiler
+	 */
+	public function disableProfiler()
+	{
+		$this->profiling = false;
+		Piwik_LogStats_Db::disableProfiling();
+	}
+	
+	/**
+	 * This is called at the end of the Generator script.
+	 * Calls the Profiler output if the profiler is enabled.
+	 * 
+	 * @return void
+	 */
+	public function end()
+	{
+		Piwik_LogStats::disconnectDb();
+		if($this->profiling)
+		{
+			Piwik::printSqlProfilingReportLogStats();
+		}
+	}
+	
+	/**
+	 * Init the Generator script:
+	 * - init the SQL profiler
+	 * - init the random generator
+	 * - setup the different possible values for parameters such as 'resolution',
+	 * 		'color', 'hour', 'minute', etc.
+	 * - load from DataFiles and setup values for the other parameters such as UserAgent, Referers, AcceptedLanguages, etc.
+	 *  @see /misc/generateVisitsData/
+	 * 
+	 * @return void
+	 */
+	public function init()
+	{
+		Piwik::createLogObject();
+		
+		$this->initProfiler();
+		
+		/*
+		 * Init the random number generator 
+		 */ 
+		function make_seed()
+		{
+		  list($usec, $sec) = explode(' ', microtime());
+		  return (float) $sec + ((float) $usec * 100000);
+		}
+		mt_srand(make_seed());
+		
+		/*
+		 * Sets values for: resolutions, colors, idSite, times
+		 */
+		$common = array(
+			'res' => array('1289x800','1024x768','800x600','564x644','200x100','50x2000',),
+			'col' => array(24,32,16),
+			'idsite'=> $this->idSite,
+			'h' => range(0,23),
+			'm' => range(0,59),
+			's' => range(0,59),
+		);
+		
+		foreach($common as $label => $values)
+		{
+			$this->addParam($label,$values);
+		}
+		
+		/*
+		 * Sets values for: outlinks, downloads, campaigns
+		 */
+		// we get the name of the Download/outlink variables
+		$downloadOrOutlink = array(
+						Piwik_LogStats_Config::getInstance()->LogStats['download_url_var_name'],
+						Piwik_LogStats_Config::getInstance()->LogStats['outlink_url_var_name'],
+		);
+		// we have a 20% chance to add a download or outlink variable to the URL 
+		$this->addParam('piwik_downloadOrOutlink', $downloadOrOutlink);
+		$this->addParam('piwik_downloadOrOutlink', array_fill(0,8,''));
+		
+		// we get the variables name for the campaign parameters
+		$campaigns = array(
+						Piwik_LogStats_Config::getInstance()->LogStats['campaign_var_name'],
+						Piwik_LogStats_Config::getInstance()->LogStats['newsletter_var_name'],
+						Piwik_LogStats_Config::getInstance()->LogStats['partner_var_name'],
+		);
+		// we generate a campaign in the URL in 3/18 % of the generated URls
+		$this->addParam('piwik_vars_campaign', $campaigns);
+		$this->addParam('piwik_vars_campaign', array_fill(0,15,''));
+		
+		
+		/*
+		 * Sets values for: Referers, user agents, accepted languages
+		 */
+		// we load some real referers to be used by the generator
+		$referers = array();
+		require_once "misc/generateVisitsData/Referers.php";
+
+		$this->addParam('urlref',$referers);
+
+		// and we add 2000 empty referers so that some visitors don't come using a referer (direct entry)
+		$this->addParam('urlref',array_fill(0,2000,''));
+		
+		// load some user agent and accept language
+		$userAgent = $acceptLanguages = array();
+		require_once "misc/generateVisitsData/UserAgent.php";
+		require_once "misc/generateVisitsData/AcceptLanguage.php";
+		$this->userAgents=$userAgent;
+		$this->acceptLanguage=$acceptLanguages;
+	}
+	
+	/**
+	 * If the SQL profiler is enabled and if the reinit at every request is set to true,
+	 * then we TRUNCATE the profiling information so that we only profile one visitor at a time
+	 * 
+	 * @return void
+	 */
+	protected function initProfiler()
+	{
+		/*
+		 * Inits the profiler
+		 */
+		if($this->profiling)
+		{
+			if($this->reinitProfilingAtEveryRequest)
+			{
+				$all = Zend_Registry::get('db')->query('TRUNCATE TABLE '.Piwik::prefixTable('log_profiling').'' );
+			}
+		}
+	}
+	/**
+	 * Launches the process and generates an exact number of nbVisits
+	 * For each visit, we setup the timestamp to the common timestamp
+	 * Then we generate between 1 and nbActionsMaxPerVisit actions for this visit
+	 * The generated actions will have a growing timestamp so it looks like a real visit
+	 * 
+	 * @param int The number of visits to generate
+	 * @param int The maximum number of actions to generate per visit
+	 * 
+	 * @return int The number of total actions generated
+	 */
+	public function generate( $nbVisits, $nbActionsMaxPerVisit )
+	{
+		$nbActionsTotal = 0;
+		for($i = 0; $i < $nbVisits; $i++)
+		{
+			$nbActions = mt_rand(1, $nbActionsMaxPerVisit);
+			
+			Piwik_LogStats_Generator_Visit::setTimestampToUse($this->getTimestampToUse());
+						
+			$this->generateNewVisit();
+			for($j = 1; $j <= $nbActions; $j++)
+			{
+				$this->generateActionVisit();
+				$this->saveVisit();
+			}
+			
+			$nbActionsTotal += $nbActions;
+		}
+		return $nbActionsTotal;
+	}
+	
+	/**
+	 * Generates a new visitor. 
+	 * Loads random values for all the necessary parameters (resolution, local time, referers, etc.) from the fake GET array.
+	 * Also generates a random IP.
+	 * 
+	 * We change the superglobal values of HTTP_USER_AGENT, HTTP_CLIENT_IP, HTTP_ACCEPT_LANGUAGE to the generated value.
+	 * 
+	 * @return void
+	 */
+	protected function generateNewVisit()
+	{
+		$this->setCurrentRequest( 'urlref' , $this->getRandom('urlref'));
+		$this->setCurrentRequest( 'idsite', $this->getRandom('idsite'));
+		$this->setCurrentRequest( 'res' ,$this->getRandom('res'));
+		$this->setCurrentRequest( 'col' ,$this->getRandom('col'));
+		$this->setCurrentRequest( 'h' ,$this->getRandom('h'));
+		$this->setCurrentRequest( 'm' ,$this->getRandom('m'));
+		$this->setCurrentRequest( 's' ,$this->getRandom('s'));
+		$this->setCurrentRequest( 'fla' ,$this->getRandom01());
+		$this->setCurrentRequest( 'dir' ,$this->getRandom01());
+		$this->setCurrentRequest( 'qt' ,$this->getRandom01());
+		$this->setCurrentRequest( 'realp' ,$this->getRandom01());
+		$this->setCurrentRequest( 'pdf' ,$this->getRandom01());
+		$this->setCurrentRequest( 'wma' ,$this->getRandom01());
+		$this->setCurrentRequest( 'java' ,$this->getRandom01());
+		$this->setCurrentRequest( 'cookie',$this->getRandom01());
+
+		$_SERVER['HTTP_CLIENT_IP'] = mt_rand(0,255).".".mt_rand(0,255).".".mt_rand(0,255).".".mt_rand(0,255);
+		$_SERVER['HTTP_USER_AGENT'] = $this->userAgents[mt_rand(0,count($this->userAgents)-1)];
+		$_SERVER['HTTP_ACCEPT_LANGUAGE'] = $this->acceptLanguage[mt_rand(0,count($this->acceptLanguage)-1)];
+	}
+	
+	/**
+	 * Generates a new action for the current visitor.
+	 * We random generate some campaigns, action names, download or outlink clicks, etc.
+	 * We generate a new Referer, that would be read in the case the visit last page is older than 30 minutes.
+	 * 
+	 * This function tries to generate actions that use the features of Piwik (campaigns, downloads, outlinks, action_name set in the JS tag, etc.)
+	 * 
+	 * @return void
+	 * 
+	 */
+	protected function generateActionVisit()
+	{		
+		// we don't keep the previous action values 
+		// reinit them to empty string
+		$this->setCurrentRequest( Piwik_LogStats_Config::getInstance()->LogStats['download_outlink_name_var'],'');
+		$this->setCurrentRequest( Piwik_LogStats_Config::getInstance()->LogStats['download_url_var_name'],'');
+		$this->setCurrentRequest( Piwik_LogStats_Config::getInstance()->LogStats['outlink_url_var_name'],'');
+		$this->setCurrentRequest( 'action_name', '');
+
+		// generate new url referer ; case the visitor stays more than 30min
+		// (when the visit is known this value will simply be ignored)
+		$this->setCurrentRequest( 'urlref' , $this->getRandom('urlref'));
+		
+		// generates the current URL 
+		$url = $this->getRandomUrlFromHost($this->host);
+		
+		// we generate a campaign (partner or newsletter or campaign)
+		$urlVars = $this->getRandom('piwik_vars_campaign');
+		
+		// if we actually generated a campaign
+		if(!empty($urlVars))
+		{
+			// campaign name
+			$urlValue = $this->getRandomString(5,3,'lower');
+			
+			// add the parameter to the url
+			$url .= '?'. $urlVars . '=' . $urlValue;
+			
+			// for a campaign of the CPC kind, we sometimes generate a keyword 
+			if($urlVars == Piwik_LogStats_Config::getInstance()->LogStats['campaign_var_name']
+				&& mt_rand(0,1)==0)
+			{
+				$url .= '&'. Piwik_LogStats_Config::getInstance()->LogStats['campaign_keyword_var_name'] 
+							. '=' . $this->getRandomString(6,3,'ALL');;
+			}
+		}
+		else
+		{
+			// we generate a download Or Outlink parameter in the GET request so that 
+			// the current action is counted as a download action OR a outlink click action
+			$GETParamToAdd = $this->getRandom('piwik_downloadOrOutlink');
+			if(!empty($GETParamToAdd))
+			{
+				
+				$possibleDownloadHosts = array('http://piwik.org/',$this->host);
+				$nameDownload = $this->getRandomUrlFromHost($possibleDownloadHosts[mt_rand(0,1)]);
+				$extensions = array('.zip','.tar.gz');
+				$nameDownload .= $extensions[mt_rand(0,1)];
+				$urlValue = $nameDownload;
+				
+				// add the parameter to the url
+				$this->setCurrentRequest( $GETParamToAdd , $urlValue);
+				
+				// in 50% we give a special name to the download/outlink 
+				if(mt_rand(0,1)==0)
+				{
+					$nameDownload = $this->getRandomString(6,3,'ALL');
+					
+					$this->setCurrentRequest( Piwik_LogStats_Config::getInstance()->LogStats['download_outlink_name_var'] 
+											, $nameDownload);
+				}
+			}
+			
+			// if we didn't set any campaign NOR any download click
+			// then we sometimes set a special action name to the current action
+			elseif(rand(0,2)==1)
+			{
+				$this->setCurrentRequest( 'action_name' , $this->getRandomString(1,1));
+			}
+		}
+		
+		$this->setCurrentRequest( 'url' ,$url);
+		
+		// setup the title of the page
+		$this->setCurrentRequest( 'title',$this->getRandomString(15,5));
+	}
+	
+	/**
+	 * Returns a random URL using the $host as the URL host.
+	 * Depth level depends on @see setMaximumUrlDepth()
+	 * 
+	 * @param string Hostname of the URL to generate, eg. http://example.com/
+	 * 
+	 * @return string The generated URL
+	 */
+	protected function getRandomUrlFromHost( $host )
+	{
+		$url = $host;
+		
+		$deep = mt_rand(0,$this->maximumUrlDepth);
+		for($i=0;$i<$deep;$i++)
+		{
+			$name = $this->getRandomString(1,1,'alnum');
+			
+			$url .= '/'.$name;
+		}
+		return $url;
+	}
+	
+	/**
+	 * Generates a random string from minLength to maxLength using a specified set of characters
+	 * 
+	 * Taken from php.net and then badly hacked by some unknown monkey
+	 * 
+	 * @param int (optional) Maximum length of the string to generate
+	 * @param int (optional) Minimum length of the string to generate
+	 * @param string (optional) Characters set to use, 'ALL' or 'lower' or 'upper' or 'numeric' or 'ALPHA' or 'ALNUM'
+	 * 
+	 * @return string The generated random string
+	 */
+	protected function getRandomString($maxLength = 15, $minLength = 5, $type = 'ALL')
+	{
+		$len = mt_rand($minLength, $maxLength);
+		
+	    // Register the lower case alphabet array
+	    $alpha = array('a', 'd', 'e', 'f', 'g');
+	
+	    // Register the upper case alphabet array                    
+	    $ALPHA = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+	                     'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z');
+	       
+	    // Register the numeric array              
+	    $num = array('1', '2', '3',  '8', '9', '0');
+	    
+	    // Register the strange array              
+	    $strange = array('/', '?', '!','"','£','$','%','^','&','*','(',')',' ');
+	   
+	    // Initialize the keyVals array for use in the for loop
+	    $keyVals = array();
+	   
+	    // Initialize the key array to register each char
+	    $key = array();   
+	   
+	    // Loop through the choices and register
+	    // The choice to keyVals array
+	    switch ($type)
+	    {
+	        case 'lower' :
+	            $keyVals = $alpha;
+	            break;
+	        case 'upper' :
+	            $keyVals = $ALPHA;
+	            break;
+	        case 'numeric' :
+	            $keyVals = $num;
+	            break;
+	        case 'ALPHA' :
+	            $keyVals = array_merge($alpha, $ALPHA);
+	            break;
+	        case 'alnum' :
+	            $keyVals = array_merge($alpha, $num);
+	            break;
+	        case 'ALNUM' :
+	            $keyVals = array_merge($alpha, $ALPHA, $num);
+	            break;
+	        case 'ALL' :
+	            $keyVals = array_merge($alpha, $ALPHA, $num, $strange);
+	            break;
+	    }
+	   
+	    // Loop as many times as specified
+	    // Register each value to the key array
+	    for($i = 0; $i <= $len-1; $i++)
+	    {
+	        $r = mt_rand(0,count($keyVals)-1);
+	        $key[$i] = $keyVals[$r];
+	    }
+	   
+	    // Glue the key array into a string and return it
+	    return join("", $key);
+	}
+
+	/**
+	 * Sets the _GET and _REQUEST superglobal to the current generated array of values.
+	 * @see setCurrentRequest()
+	 * This method is called once the current action parameters array has been generated from 
+	 * the global parameters array
+	 * 
+	 * @return void
+	 */
+	protected function setFakeRequest()
+	{
+		$_REQUEST = $_GET = $this->currentget;
+	}
+	
+	/**
+	 * Sets a value in the current action request array.
+	 * 
+	 * @param string Name of the parameter to set
+	 * @param string Value of the parameter
+	 */
+	protected function setCurrentRequest($name,$value)
+	{
+		$this->currentget[$name] = $value;
+	}
+	
+	/**
+	 * Returns a value for the given parameter $name read randomly from the global parameter array.
+	 * @see init()
+	 * 
+	 * @param string Name of the parameter value to randomly load and return
+	 * @return mixed Random value for the parameter named $name
+	 * @throws Exception if the parameter asked for has never been set
+	 * 
+	 */
+	protected function getRandom( $name )
+	{		
+		if(!isset($this->allget[$name]))
+		{
+			throw new exception("You are asking for $name which doesnt exist");
+		}
+		else
+		{
+			$index = mt_rand(0,count($this->allget[$name])-1);
+			$value =$this->allget[$name][$index];
+			return $value;
+		}
+	}
+
+	/**
+	 * Returns either 0 or 1
+	 * 
+	 * @return int 0 or 1
+	 */	
+	protected function getRandom01()
+	{
+		return mt_rand(0,1);
+	}
+	
+	/**
+	 * Saves the visit 
+	 * - replaces GET and REQUEST by the fake generated request
+	 * - load the LogStats class and call the method to launch the recording
+	 * 
+	 * This will save the visit in the database
+	 * 
+	 * @return void
+	 */
+	protected function saveVisit()
+	{
+		$this->setFakeRequest();
+		$process = new Piwik_LogStats_Generator_LogStats;
+		$process->main();
+	}
+	
+}
+require_once "Generator/LogStats.php";
+require_once "Generator/Visit.php";
diff --git a/core/LogStats/Generator/LogStats.php b/core/LogStats/Generator/LogStats.php
new file mode 100644
index 0000000000..92a8d29c39
--- /dev/null
+++ b/core/LogStats/Generator/LogStats.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Generator.php 404 2008-03-23 01:09:59Z matt $
+ * 
+ * @package Piwik_LogStats
+ */
+
+
+/**
+ * Fake Piwik_LogStats that:
+ * - overwrite the sendHeader method so that no headers are sent.
+ * - doesn't print the 1pixel transparent GIF at the end of the visit process
+ * - overwrite the logstat_visit object to use so we use our own logstats_visit @see Piwik_LogStats_Generator_Visit
+ * 
+ * @package Piwik_LogStats
+ * @subpackage Piwik_LogStats_Generator
+ */
+class Piwik_LogStats_Generator_LogStats extends Piwik_LogStats
+{
+	/**
+	 * Does nothing instead of sending headers
+	 *
+	 * @return void
+	 */
+	protected function sendHeader($header)
+	{
+	}
+	
+	/**
+	 * Does nothing instead of displaying a 1x1 transparent pixel GIF
+	 *
+	 * @return void
+	 */
+	protected function endProcess()
+	{
+	}
+	
+	/**
+	 * Returns our 'generator home made' Piwik_LogStats_Generator_Visit object.
+	 *
+	 * @return Piwik_LogStats_Generator_Visit
+	 */
+	protected function getNewVisitObject()
+	{
+		$visit = new Piwik_LogStats_Generator_Visit();
+		$visit->setDb(self::$db);
+		return $visit;
+	}	
+	
+	static function disconnectDb()
+	{
+		return;
+	}
+}
\ No newline at end of file
diff --git a/core/LogStats/Generator/Visit.php b/core/LogStats/Generator/Visit.php
new file mode 100644
index 0000000000..01396d126e
--- /dev/null
+++ b/core/LogStats/Generator/Visit.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Generator.php 404 2008-03-23 01:09:59Z matt $
+ * 
+ * @package Piwik_LogStats
+ */
+
+
+/**
+ * Fake Piwik_LogStats_Visit class that overwrite all the Time related method to be able
+ * to setup a given timestamp for the generated visitor and actions.
+ * 
+ * 
+ * @package Piwik_LogStats
+ * @subpackage Piwik_LogStats_Generator
+ */
+class Piwik_LogStats_Generator_Visit extends Piwik_LogStats_Visit
+{
+	static protected $timestampToUse;
+	
+	static public function setTimestampToUse($time)
+	{
+		self::$timestampToUse = $time;
+	}
+	protected function getCurrentDate( $format = "Y-m-d")
+	{
+		return date($format, $this->getCurrentTimestamp() );
+	}
+	
+	protected function getCurrentTimestamp()
+	{
+		self::$timestampToUse = max(@$this->visitorInfo['visit_last_action_time'],self::$timestampToUse);
+		self::$timestampToUse += mt_rand(4,1840);
+		return self::$timestampToUse;
+	}
+		
+	protected function getDatetimeFromTimestamp($timestamp)
+	{
+		return date("Y-m-d H:i:s",$timestamp);
+	}
+	
+	protected function updateCookie()
+	{
+		@parent::updateCookie();
+	}
+	
+}
diff --git a/core/LogStats/Visit.php b/core/LogStats/Visit.php
new file mode 100644
index 0000000000..a148ab7d4c
--- /dev/null
+++ b/core/LogStats/Visit.php
@@ -0,0 +1,837 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Visit.php 575 2008-07-26 23:08:32Z matt $
+ * 
+ * @package Piwik_LogStats
+ */
+
+
+interface Piwik_LogStats_Visit_Interface {
+	function handle();
+	function setDb($db);
+}
+
+/**
+ * Class used to handle a Visit.
+ * A visit is either NEW or KNOWN.
+ * - If a visit is NEW then we process the visitor information (settings, referers, etc.) and save
+ * a new line in the log_visit table.
+ * - If a visit is KNOWN then we update the visit row in the log_visit table, updating the number of pages
+ * views, time spent, etc.
+ * 
+ * Whether a visit is NEW or KNOWN we also save the action in the DB. 
+ * One request to the piwik.php script is associated to one action.
+ * 
+ * @package Piwik_LogStats
+ */
+
+class Piwik_LogStats_Visit implements Piwik_LogStats_Visit_Interface
+{
+	protected $cookieLog = null;
+	protected $visitorInfo = array();
+	protected $userSettingsInformation = null;
+	protected $db = null;
+
+	function __construct()
+	{
+		$idsite = Piwik_Common::getRequestVar('idsite', 0, 'int');
+		if($idsite <= 0)
+		{
+			throw new Exception("The 'idsite' in the request is invalid.");
+		}
+		
+		$this->idsite = $idsite;
+	}
+	
+	public function setDb($db)
+	{
+		$this->db = $db;
+	}
+	
+	/**
+	 * Returns the current date in the "Y-m-d" PHP format
+	 * @return string
+	 */
+	protected function getCurrentDate( $format = "Y-m-d")
+	{
+		return date($format, $this->getCurrentTimestamp() );
+	}
+	
+	/**
+	 * Returns the current Timestamp
+	 * @return int
+	 */
+	protected function getCurrentTimestamp()
+	{
+		return time();
+	}
+	
+	/**
+	 * Returns the date in the "Y-m-d H:i:s" PHP format
+	 * @return string
+	 */
+	protected function getDatetimeFromTimestamp($timestamp)
+	{
+		return date("Y-m-d H:i:s", $timestamp);
+	}
+
+	/**
+	 * Test if the current visitor is excluded from the statistics.
+	 * 
+	 * Plugins can for example exclude visitors based on the 
+	 * - IP
+	 * - If a given cookie is found
+	 * 
+	 * @return bool True if the visit must not be saved, false otherwise
+	 */
+	protected function isExcluded()
+	{
+		$excluded = 0;
+		Piwik_PostEvent('LogStats.Visit.isExcluded', $excluded);
+		if($excluded)
+		{
+			printDebug("Visitor excluded.");
+			return true;
+		}
+		
+		return false;
+	}
+	
+	/**
+	 * Returns the cookie name used for the Piwik LogStats cookie
+	 * @return string
+	 */
+	protected function getCookieName()
+	{
+		return Piwik_LogStats_Config::getInstance()->LogStats['cookie_name'] . $this->idsite;
+	}
+	
+	
+	/**
+	 * This methods tries to see if the visitor has visited the website before.
+	 * 
+	 * We have to split the visitor into one of the category 
+	 * - Known visitor
+	 * - New visitor
+	 * 
+	 * A known visitor is a visitor that has already visited the website in the current month.
+	 * We define a known visitor using the algorithm:
+	 * 
+	 * 1) Checking if a cookie contains
+	 * 		// a unique id for the visitor
+	 * 		- id_visitor 
+	 * 
+	 * 		// the timestamp of the last action in the most recent visit
+	 * 		- timestamp_last_action 
+	 * 
+ 	 *  	// the timestamp of the first action in the most recent visit
+	 * 		- timestamp_first_action
+	 * 
+	 * 		// the ID of the most recent visit (which could be in the past or the current visit)
+	 * 		- id_visit 
+	 * 
+	 * 		// the ID of the most recent action
+	 * 		- id_last_action
+	 * 
+	 * 2) If the visitor doesn't have a cookie, we try to look for a similar visitor configuration.
+	 * 	  We search for a visitor with the same plugins/OS/Browser/Resolution for today for this website.
+	 */
+	protected function recognizeTheVisitor()
+	{
+		$this->visitorKnown = false;
+		
+		$this->cookieLog = new Piwik_Cookie( $this->getCookieName() );
+		/*
+		 * Case the visitor has the piwik cookie.
+		 * We make sure all the data that should saved in the cookie is available.
+		 */
+		
+		if( false !== ($idVisitor = $this->cookieLog->get( Piwik_LogStats::COOKIE_INDEX_IDVISITOR )) )
+		{
+			$timestampLastAction = $this->cookieLog->get( Piwik_LogStats::COOKIE_INDEX_TIMESTAMP_LAST_ACTION );
+			$timestampFirstAction = $this->cookieLog->get( Piwik_LogStats::COOKIE_INDEX_TIMESTAMP_FIRST_ACTION );
+			$idVisit = $this->cookieLog->get( Piwik_LogStats::COOKIE_INDEX_ID_VISIT );
+			$idLastAction = $this->cookieLog->get( Piwik_LogStats::COOKIE_INDEX_ID_LAST_ACTION );
+			
+			if(		$timestampLastAction !== false && is_numeric($timestampLastAction)
+				&& 	$timestampFirstAction !== false && is_numeric($timestampFirstAction)
+				&& 	$idVisit !== false && is_numeric($idVisit)
+				&& 	$idLastAction !== false && is_numeric($idLastAction)
+			)
+			{
+				$this->visitorInfo['visitor_idcookie'] = $idVisitor;
+				$this->visitorInfo['visit_last_action_time'] = $timestampLastAction;
+				$this->visitorInfo['visit_first_action_time'] = $timestampFirstAction;
+				$this->visitorInfo['idvisit'] = $idVisit;
+				$this->visitorInfo['visit_exit_idaction'] = $idLastAction;
+				
+				$this->visitorKnown = true;								
+				
+				printDebug("The visitor is known because he has the piwik cookie (idcookie = {$this->visitorInfo['visitor_idcookie']}, idvisit = {$this->visitorInfo['idvisit']}, last action = ".date("r", $this->visitorInfo['visit_last_action_time']).") ");
+			}
+		}
+
+		/*
+		 * If the visitor doesn't have the piwik cookie, we look for a visitor that has exactly the same configuration
+		 * and that visited the website today.
+		 */
+		if( !$this->visitorKnown )
+		{
+			$userInfo = $this->getUserSettingsInformation();
+			$md5Config = $userInfo['config_md5config'];
+
+			$visitRow = $this->db->fetch( 
+										" SELECT  	visitor_idcookie, 
+													UNIX_TIMESTAMP(visit_last_action_time) as visit_last_action_time,
+													UNIX_TIMESTAMP(visit_first_action_time) as visit_first_action_time,
+													idvisit,
+													visit_exit_idaction 
+										FROM ".$this->db->prefixTable('log_visit').
+										" WHERE visit_server_date = ?
+											AND idsite = ?
+											AND config_md5config = ?
+										ORDER BY visit_last_action_time DESC
+										LIMIT 1",
+										array( $this->getCurrentDate(), $this->idsite, $md5Config));
+			if($visitRow 
+				&& count($visitRow) > 0)
+			{
+				$this->visitorInfo['visitor_idcookie'] = $visitRow['visitor_idcookie'];
+				$this->visitorInfo['visit_last_action_time'] = $visitRow['visit_last_action_time'];
+				$this->visitorInfo['visit_first_action_time'] = $visitRow['visit_first_action_time'];
+				$this->visitorInfo['idvisit'] = $visitRow['idvisit'];
+				$this->visitorInfo['visit_exit_idaction'] = $visitRow['visit_exit_idaction'];
+				
+				$this->visitorKnown = true;
+				
+				printDebug("The visitor is known because of his userSettings+IP (idcookie = {$visitRow['visitor_idcookie']}, idvisit = {$this->visitorInfo['idvisit']}, last action = ".date("r", $this->visitorInfo['visit_last_action_time']).") ");
+			}
+		}
+	}
+	
+	/**
+	 * Gets the UserSettings information and returns them in an array of name => value
+	 * 
+	 * @return array
+	 */
+	protected function getUserSettingsInformation()
+	{
+		// we already called this method before, simply returns the result
+		if(is_array($this->userSettingsInformation))
+		{
+			return $this->userSettingsInformation;
+		}
+		
+		
+		$plugin_Flash 			= Piwik_Common::getRequestVar( 'fla', 0, 'int');
+		$plugin_Director 		= Piwik_Common::getRequestVar( 'dir', 0, 'int');
+		$plugin_Quicktime		= Piwik_Common::getRequestVar( 'qt', 0, 'int');
+		$plugin_RealPlayer 		= Piwik_Common::getRequestVar( 'realp', 0, 'int');
+		$plugin_Pdf 			= Piwik_Common::getRequestVar( 'pdf', 0, 'int');
+		$plugin_WindowsMedia 	= Piwik_Common::getRequestVar( 'wma', 0, 'int');
+		$plugin_Java 			= Piwik_Common::getRequestVar( 'java', 0, 'int');
+		$plugin_Cookie 			= Piwik_Common::getRequestVar( 'cookie', 0, 'int');
+		
+		$userAgent		= Piwik_Common::sanitizeInputValues(@$_SERVER['HTTP_USER_AGENT']);
+		$aBrowserInfo	= Piwik_Common::getBrowserInfo($userAgent);
+		$browserName	= $aBrowserInfo['name'];
+		$browserVersion	= $aBrowserInfo['version'];
+		
+		$os				= Piwik_Common::getOs($userAgent);
+		
+		$resolution		= Piwik_Common::getRequestVar('res', 'unknown', 'string');
+		$colorDepth		= Piwik_Common::getRequestVar('col', 32, 'numeric');
+		
+
+		$ip				= Piwik_Common::getIp();
+		$ip 			= ip2long($ip);
+
+		$browserLang	= substr(Piwik_Common::sanitizeInputValues(@$_SERVER['HTTP_ACCEPT_LANGUAGE']), 0, 20);
+		if(is_null($browserLang))
+		{
+			$browserLang = '';
+		}
+		
+
+		$configurationHash = $this->getConfigHash( 
+												$os,
+												$browserName,
+												$browserVersion,
+												$resolution,
+												$colorDepth,
+												$plugin_Flash,
+												$plugin_Director,
+												$plugin_RealPlayer,
+												$plugin_Pdf,
+												$plugin_WindowsMedia,
+												$plugin_Java,
+												$plugin_Cookie,
+												$ip,
+												$browserLang);
+												
+		$this->userSettingsInformation = array(
+			'config_md5config' => $configurationHash,
+			'config_os' 			=> $os,
+			'config_browser_name' 	=> $browserName,
+			'config_browser_version' => $browserVersion,
+			'config_resolution' 	=> $resolution,
+			'config_color_depth' 	=> $colorDepth,
+			'config_pdf' 			=> $plugin_Pdf,
+			'config_flash' 			=> $plugin_Flash,
+			'config_java' 			=> $plugin_Java,
+			'config_director' 		=> $plugin_Director,
+			'config_quicktime' 		=> $plugin_Quicktime,
+			'config_realplayer' 	=> $plugin_RealPlayer,
+			'config_windowsmedia' 	=> $plugin_WindowsMedia,
+			'config_cookie' 		=> $plugin_RealPlayer,
+			'location_ip' 			=> $ip,
+			'location_browser_lang' => $browserLang,			
+		);
+		
+		return $this->userSettingsInformation;
+	}
+	
+	/**
+	 * Returns true if the last action was done during the last 30 minutes
+	 * @return bool
+	 */
+	protected function isLastActionInTheSameVisit()
+	{
+		return $this->visitorInfo['visit_last_action_time'] 
+					>= ($this->getCurrentTimestamp() - Piwik_LogStats_Config::getInstance()->LogStats['visit_standard_length']);
+	}
+
+	/**
+	 * Returns true if the recognizeTheVisitor() method did recognize the visitor
+	 */
+	protected function isVisitorKnown()
+	{
+		return $this->visitorKnown === true;
+	}
+	
+	/**
+	 *	Main algorith to handle the visit. 
+	 *
+	 *  Once we have the visitor information, we have to define if the visit is a new or a known visit.
+	 * 
+	 * 1) When the last action was done more than 30min ago, 
+	 * 	  or if the visitor is new, then this is a new visit.
+	 *	
+	 * 2) If the last action is less than 30min ago, then the same visit is going on. 
+	 *	Because the visit goes on, we can get the time spent during the last action.
+	 *
+	 * NB:
+	 *  - In the case of a new visit, then the time spent 
+	 *	during the last action of the previous visit is unknown. 
+	 * 
+	 *	- In the case of a new visit but with a known visitor, 
+	 *	we can set the 'returning visitor' flag.
+	 *
+	 * In all the cases we set a cookie to the visitor with the new information.
+	 */
+	public function handle()
+	{
+		if($this->isExcluded())
+		{
+			return;
+		}
+		
+		$this->recognizeTheVisitor();
+		if( $this->isVisitorKnown() 
+			&& $this->isLastActionInTheSameVisit())
+		{
+			$this->handleKnownVisit();
+		}
+		else
+		{
+			$this->handleNewVisit();
+		}
+		
+		// we update the cookie with the new visit information
+		$this->updateCookie();
+	}
+
+	/**
+	 * Update the cookie information.
+	 */
+	protected function updateCookie()
+	{
+		printDebug("We manage the cookie...");
+		
+		// idcookie has been generated in handleNewVisit or we simply propagate the old value
+		$this->cookieLog->set( 	Piwik_LogStats::COOKIE_INDEX_IDVISITOR, 
+								$this->visitorInfo['visitor_idcookie'] );
+		
+		// the last action timestamp is the current timestamp
+		$this->cookieLog->set( 	Piwik_LogStats::COOKIE_INDEX_TIMESTAMP_LAST_ACTION, 	
+								$this->visitorInfo['visit_last_action_time'] );
+		
+		// the first action timestamp is the timestamp of the first action of the current visit
+		$this->cookieLog->set( 	Piwik_LogStats::COOKIE_INDEX_TIMESTAMP_FIRST_ACTION, 	
+								$this->visitorInfo['visit_first_action_time'] );
+		
+		// the idvisit has been generated by mysql in handleNewVisit or simply propagated here
+		$this->cookieLog->set( 	Piwik_LogStats::COOKIE_INDEX_ID_VISIT, 	
+								$this->visitorInfo['idvisit'] );
+		
+		// the last action ID is the current exit idaction
+		$this->cookieLog->set( 	Piwik_LogStats::COOKIE_INDEX_ID_LAST_ACTION, 	
+								$this->visitorInfo['visit_exit_idaction'] );
+								
+		$this->cookieLog->save();
+	}
+	
+	
+	/**
+	 * In the case of a known visit, we have to do the following actions:
+	 * 
+	 * 1) Insert the new action
+	 * 
+	 * 2) Update the visit information
+	 */
+	protected function handleKnownVisit()
+	{
+		printDebug("Visit known.");		
+		
+		/**
+		 * Init the action
+		 */
+		$action = $this->getActionObject();
+		$actionId = $action->getActionId();
+		printDebug("idAction = $actionId");
+				
+		$serverTime 	= $this->getCurrentTimestamp();
+		$datetimeServer = $this->getDatetimeFromTimestamp($serverTime);
+	
+		$this->db->query("UPDATE ". $this->db->prefixTable('log_visit')." 
+							SET visit_last_action_time = ?,
+								visit_exit_idaction = ?,
+								visit_total_actions = visit_total_actions + 1,
+								visit_total_time = UNIX_TIMESTAMP(visit_last_action_time) - UNIX_TIMESTAMP(visit_first_action_time)
+							WHERE idvisit = ?
+							LIMIT 1",
+							array( 	$datetimeServer,
+									$actionId,
+									$this->visitorInfo['idvisit'] )
+				);
+		/**
+		 * Save the action
+		 */
+		$timespentLastAction = $serverTime - $this->visitorInfo['visit_last_action_time'];
+		
+		$action->record( 	$this->visitorInfo['idvisit'], 
+							$this->visitorInfo['visit_exit_idaction'],
+							$timespentLastAction
+			);
+		
+		
+		/**
+		 * Cookie fields to be updated
+		 */
+		$this->visitorInfo['visit_last_action_time'] = $serverTime;
+		$this->visitorInfo['visit_exit_idaction'] = $actionId;
+		
+
+	}
+	
+	/**
+	 * In the case of a new visit, we have to do the following actions:
+	 * 
+	 * 1) Insert the new action
+	 * 
+	 * 2) Insert the visit information
+	 */
+	protected function handleNewVisit()
+	{
+		printDebug("New Visit.");
+		
+		/**
+		 * Get the variables from the REQUEST 
+		 */
+		$localTime				= Piwik_Common::getRequestVar( 'h', $this->getCurrentDate("H"), 'numeric')
+							.':'. Piwik_Common::getRequestVar( 'm', $this->getCurrentDate("i"), 'numeric')
+							.':'. Piwik_Common::getRequestVar( 's', $this->getCurrentDate("s"), 'numeric');
+		
+		$serverTime 	= $this->getCurrentTimestamp();	
+		$serverDate 	= $this->getCurrentDate();	
+		
+		if($this->isVisitorKnown())
+		{
+			$idcookie = $this->visitorInfo['visitor_idcookie'];
+			$returningVisitor = 1;
+		}
+		else
+		{
+			$idcookie = $this->getVisitorUniqueId();			
+			$returningVisitor = 0;
+		}
+		
+		$defaultTimeOnePageVisit = Piwik_LogStats_Config::getInstance()->LogStats['default_time_one_page_visit'];
+		
+		$userInfo = $this->getUserSettingsInformation();
+		$country 		= Piwik_Common::getCountry($userInfo['location_browser_lang']);				
+		$continent		= Piwik_Common::getContinent( $country );
+														
+		$refererInfo = $this->getRefererInformation();
+		
+		/**
+		 * Init the action
+		 */
+		$action = $this->getActionObject();
+		$actionId = $action->getActionId();
+		
+		printDebug("idAction = $actionId");		
+		
+		
+		/**
+		 * Save the visitor
+		 */
+		$informationToSave = array(
+			'idsite' 				=> $this->idsite,
+			'visitor_localtime' 	=> $localTime,
+			'visitor_idcookie' 		=> $idcookie,
+			'visitor_returning' 	=> $returningVisitor,
+			'visit_first_action_time' => $this->getDatetimeFromTimestamp($serverTime),
+			'visit_last_action_time' =>  $this->getDatetimeFromTimestamp($serverTime),
+			'visit_server_date' 	=> $serverDate,
+			'visit_entry_idaction' 	=> $actionId,
+			'visit_exit_idaction' 	=> $actionId,
+			'visit_total_actions' 	=> 1,
+			'visit_total_time' 		=> $defaultTimeOnePageVisit,
+			'referer_type' 			=> $refererInfo['referer_type'],
+			'referer_name' 			=> $refererInfo['referer_name'],
+			'referer_url' 			=> $refererInfo['referer_url'],
+			'referer_keyword' 		=> $refererInfo['referer_keyword'],
+			'config_md5config' 		=> $userInfo['config_md5config'],
+			'config_os' 			=> $userInfo['config_os'],
+			'config_browser_name' 	=> $userInfo['config_browser_name'],
+			'config_browser_version' => $userInfo['config_browser_version'],
+			'config_resolution' 	=> $userInfo['config_resolution'],
+			'config_color_depth' 	=> $userInfo['config_color_depth'],
+			'config_pdf' 			=> $userInfo['config_pdf'],
+			'config_flash' 			=> $userInfo['config_flash'],
+			'config_java' 			=> $userInfo['config_java'],
+			'config_director' 		=> $userInfo['config_director'],
+			'config_quicktime' 		=> $userInfo['config_quicktime'],
+			'config_realplayer' 	=> $userInfo['config_realplayer'],
+			'config_windowsmedia' 	=> $userInfo['config_windowsmedia'],
+			'config_cookie' 		=> $userInfo['config_cookie'],
+			'location_ip' 			=> $userInfo['location_ip'],
+			'location_browser_lang' => $userInfo['location_browser_lang'],
+			'location_country' 		=> $country,
+			'location_continent' 	=> $continent,
+		);
+		
+		Piwik_PostEvent('LogStats.newVisitorInformation', $informationToSave);
+		
+		$fields = implode(", ", array_keys($informationToSave));
+		$values = substr(str_repeat( "?,",count($informationToSave)),0,-1);
+		
+		$this->db->query( "INSERT INTO ".$this->db->prefixTable('log_visit').
+						" ($fields) VALUES ($values)", array_values($informationToSave));
+						
+		$idVisit = $this->db->lastInsertId();
+		
+		// Update the visitor information attribute with this information array
+		$this->visitorInfo = $informationToSave;
+		$this->visitorInfo['idvisit'] = $idVisit;
+
+		// we have to save timestamp in the object properties, whereas mysql eats some other datetime format
+		$this->visitorInfo['visit_first_action_time'] = $serverTime;
+		$this->visitorInfo['visit_last_action_time'] = $serverTime;
+		
+		// saves the action
+		$action->record( $idVisit, 0, 0 );
+		
+	}
+	
+	/**
+	 * Returns an object able to handle the current action
+	 * Plugins can return an override Action that for example, does not record the action in the DB
+	 *
+	 * @return Piwik_LogStats_Action child or fake but with same public interface
+	 */
+	protected function getActionObject()
+	{
+		$action = null;
+		Piwik_PostEvent('LogStats.newAction', $action);
+	
+		if(is_null($action))
+		{
+			$action = new Piwik_LogStats_Action( $this->db );
+		}
+		elseif(!($action instanceof Piwik_LogStats_Action_Interface))
+		{
+			throw new Exception("The Action object set in the plugin must implement the interface Piwik_LogStats_Action_Interface");
+		}
+		
+		return $action;
+	}
+	
+	/**
+	 * Returns an array containing the following information:
+	 * - referer_type
+	 *		- direct			-- absence of referer URL OR referer URL has the same host
+	 *		- site				-- based on the referer URL
+	 *		- search_engine		-- based on the referer URL
+	 *		- campaign			-- based on campaign URL parameter
+	 *		- newsletter		-- based on newsletter URL parameter
+	 *		- partner			-- based on partner URL parameter
+	 *
+	 * - referer_name
+	 * 		- ()
+	 * 		- piwik.net			-- site host name
+	 * 		- google.fr			-- search engine host name
+	 * 		- adwords-search	-- campaign name
+	 * 		- beta-release		-- newsletter name
+	 * 		- my-nice-partner	-- partner name
+	 * 		
+	 * - referer_keyword
+	 * 		- ()
+	 * 		- ()
+	 * 		- my keyword
+	 * 		- my paid keyword
+	 * 		- ()
+	 * 		- ()
+	 *  
+	 * - referer_url : the same for all the referer types
+	 * 
+	 */
+	protected function getRefererInformation()
+	{	
+		// default values for the referer_* fields
+		$this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY;
+		$this->nameRefererAnalyzed = '';
+		$this->keywordRefererAnalyzed = '';
+		$this->refererHost = '';
+		
+		// get the urls and parse them
+		$refererUrl	= Piwik_Common::getRequestVar( 'urlref', '', 'string');
+		$currentUrl	= Piwik_Common::getRequestVar( 'url', '', 'string');
+
+		$this->refererUrlParse = @parse_url($refererUrl);
+		$this->currentUrlParse = @parse_url($currentUrl);
+		if(isset($this->refererUrlParse['host']))
+		{
+			$this->refererHost = $this->refererUrlParse['host'];
+		}
+
+		$refererDetected = false;
+		if( !empty($this->currentUrlParse['host']))
+		{
+			if(	$this->detectRefererNewsletter()
+				||	$this->detectRefererPartner()
+				||	$this->detectRefererCampaign() )
+			{
+				$refererDetected = true;
+			}
+		}
+		
+		if(!$refererDetected
+			&& !empty($this->refererUrlParse['host']) )
+		{
+			if( $this->detectRefererSearchEngine()
+				||	$this->detectRefererDirectEntry() )
+			{
+				$refererDetected = true;
+			}
+		}
+		
+		if(!empty($this->refererHost) 
+			&& !$refererDetected)
+		{
+			$this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_WEBSITE;
+			$this->nameRefererAnalyzed = $this->refererHost;
+		}
+		
+		$refererInformation = array(
+			'referer_type' 		=> $this->typeRefererAnalyzed,
+			'referer_name' 		=> $this->nameRefererAnalyzed,
+			'referer_keyword' 	=> $this->keywordRefererAnalyzed,
+			'referer_url' 		=> $refererUrl,
+		);
+		
+		return $refererInformation;
+	}
+	
+	/*
+	 * Search engine detection
+	 */
+	protected function detectRefererSearchEngine()
+	{
+		/*
+		 * A referer is a search engine if the URL's host is in the SearchEngines array
+		 * and if we found the keyword in the URL.
+		 * 
+		 * For example if someone comes from http://www.google.com/partners.html this will not
+		 * be counted as a search engines, but as a website referer from google.com (because the
+		 * keyword couldn't be found in the URL) 
+		 */
+		require "core/DataFiles/SearchEngines.php";
+		
+		if(array_key_exists($this->refererHost, $GLOBALS['Piwik_SearchEngines']))
+		{
+			$searchEngineName = $GLOBALS['Piwik_SearchEngines'][$this->refererHost][0];
+			$variableName = $GLOBALS['Piwik_SearchEngines'][$this->refererHost][1];
+			
+			if(isset($this->refererUrlParse['query']))
+			{
+				$query = $this->refererUrlParse['query'];
+
+				if($searchEngineName == 'Google Images')
+				{
+					$query = urldecode(trim(strtolower(Piwik_Common::getParameterFromQueryString($query, 'prev'))));
+					$query = str_replace('&', '&amp;', strstr($query, '?'));
+				}
+				
+				// search for keywords now &vname=keyword
+				$key = trim(strtolower(Piwik_Common::getParameterFromQueryString($query, $variableName)));
+				
+				if(!empty($key)
+					&& function_exists('iconv') 
+					&& isset($GLOBALS['Piwik_SearchEngines'][$this->refererHost][2]))
+				{
+					$charset = trim($GLOBALS['Piwik_SearchEngines'][$this->refererHost][2]);
+					
+					if(!empty($charset)) 
+					{
+						$key = htmlspecialchars(
+									@iconv(	$charset, 
+											'utf-8//TRANSLIT', 
+											htmlspecialchars_decode($key, Piwik_Common::HTML_ENCODING_QUOTE_STYLE))
+									, Piwik_Common::HTML_ENCODING_QUOTE_STYLE);
+					}
+				}
+				
+				if(!empty($key))
+				{
+					$this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_SEARCH_ENGINE;
+					$this->nameRefererAnalyzed = $searchEngineName;
+					$this->keywordRefererAnalyzed = $key;
+					
+					return true;
+				}
+			}
+		}
+	}
+	
+	/*
+	 * Newsletter analysis
+	 */
+	protected function detectRefererNewsletter()
+	{
+		if(isset($this->currentUrlParse['query']))
+		{
+			$newsletterVariableName = Piwik_LogStats_Config::getInstance()->LogStats['newsletter_var_name'];
+			$newsletterVar = Piwik_Common::getParameterFromQueryString( $this->currentUrlParse['query'], $newsletterVariableName);
+
+			if(!empty($newsletterVar))
+			{
+				$this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_NEWSLETTER;
+				$this->nameRefererAnalyzed = $newsletterVar;
+				
+				return true;
+			}
+		}
+	}
+	
+	/*
+	 * Partner analysis
+	 */
+	protected function detectRefererPartner()
+	{
+		if(isset($this->currentUrlParse['query']))
+		{		
+			$partnerVariableName = Piwik_LogStats_Config::getInstance()->LogStats['partner_var_name'];
+			$partnerVar = Piwik_Common::getParameterFromQueryString($this->currentUrlParse['query'], $partnerVariableName);
+							
+			if(!empty($partnerVar))
+			{
+				$this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_PARTNER;
+				$this->nameRefererAnalyzed = $partnerVar;
+				
+				return true;
+			}
+		}
+	}
+	
+	/*
+	 * Campaign analysis
+	 */
+	protected function detectRefererCampaign()
+	{	
+		if(isset($this->currentUrlParse['query']))
+		{		
+			$campaignVariableName = Piwik_LogStats_Config::getInstance()->LogStats['campaign_var_name'];
+			$campaignName = Piwik_Common::getParameterFromQueryString($this->currentUrlParse['query'], $campaignVariableName);
+			
+			if( !empty($campaignName))
+			{
+				$campaignKeywordVariableName = Piwik_LogStats_Config::getInstance()->LogStats['campaign_keyword_var_name'];
+				$campaignKeyword = Piwik_Common::getParameterFromQueryString($this->currentUrlParse['query'], $campaignKeywordVariableName);
+
+				$this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_CAMPAIGN;
+				$this->nameRefererAnalyzed = $campaignName;
+			
+				if(!empty($campaignKeyword))
+				{
+					$this->keywordRefererAnalyzed = $campaignKeyword;
+				}
+				
+				return true;
+			}
+		}
+	}
+	
+	
+	/*
+	 * Direct entry (referer host is similar to current host)
+	 * And we have previously tried to detect the newsletter/partner/campaign variables in the URL 
+	 * so it can only be a direct access
+	 */
+	
+	protected function detectRefererDirectEntry()
+	{
+		if(isset($this->currentUrlParse['host']))
+		{
+			$currentHost = $this->currentUrlParse['host'];
+					
+			if($currentHost == $this->refererHost)
+			{
+				$this->typeRefererAnalyzed = Piwik_Common::REFERER_TYPE_DIRECT_ENTRY;
+				return true;
+			}
+		}
+		
+	}
+	
+	/**
+	 * Returns a MD5 of all the configuration settings
+	 * @return string
+	 */
+	protected function getConfigHash( $os, $browserName, $browserVersion, $resolution, $colorDepth, $plugin_Flash, $plugin_Director, $plugin_RealPlayer, $plugin_Pdf, $plugin_WindowsMedia, $plugin_Java, $plugin_Cookie, $ip, $browserLang)
+	{
+		return md5( $os . $browserName . $browserVersion . $resolution . $colorDepth . $plugin_Flash . $plugin_Director . $plugin_RealPlayer . $plugin_Pdf . $plugin_WindowsMedia . $plugin_Java . $plugin_Cookie . $ip . $browserLang );
+	}
+	
+	/**
+	 * Returns either 
+	 * - "-1" for a known visitor
+	 * - a unique 32 char identifier @see Piwik_Common::generateUniqId()
+	 */
+	protected function getVisitorUniqueId()
+	{
+		if($this->isVisitorKnown())
+		{
+			return -1;
+		}
+		else
+		{
+			return Piwik_Common::generateUniqId();
+		}
+	}
+}
diff --git a/core/LogStats/javascriptTag.tpl b/core/LogStats/javascriptTag.tpl
new file mode 100644
index 0000000000..bb24479777
--- /dev/null
+++ b/core/LogStats/javascriptTag.tpl
@@ -0,0 +1,18 @@
+
+<!-- Piwik -->
+<a href="http://piwik.org" title="{$hrefTitle}" onclick="window.open(this.href);return(false);">
+<script type="text/javascript">
+var pkBaseURL = (("https:" == document.location.protocol) ? "https://{$piwikUrl}" : "http://{$piwikUrl}");
+document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/javascript'%3E%3C/script%3E"));
+</script>
+<script type="text/javascript">
+<!--
+	piwik_action_name = {$actionName};
+	piwik_idsite = {$idSite};
+	piwik_url = pkBaseURL + "piwik.php";
+	piwik_log(piwik_action_name, piwik_idsite, piwik_url);
+//-->
+</script><object>
+<noscript><p>{$hrefTitle} <img src="http://{$piwikUrl}piwik.php" style="border:0" alt="piwik"/></p>
+</noscript></object></a>
+<!-- /Piwik -->
\ No newline at end of file
diff --git a/core/Mail.php b/core/Mail.php
new file mode 100644
index 0000000000..db12d4eaf1
--- /dev/null
+++ b/core/Mail.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id$
+ * 
+ * @package Piwik
+ */
+
+require_once "Zend/Mail.php";
+
+/**
+ * Class for sending mails, for more information see: 
+ * http://framework.zend.com/manual/en/zend.mail.html 
+ *
+ * @package Piwik
+ */
+class Piwik_Mail extends Zend_Mail
+{
+	/**
+	 * Public constructor, default charset utf-8
+	 *
+	 * @param string $charset
+	 */
+	public function __construct($charset = 'utf-8')
+	{
+		parent::__construct($charset);
+	}
+}
diff --git a/core/Period.php b/core/Period.php
new file mode 100644
index 0000000000..50201bf583
--- /dev/null
+++ b/core/Period.php
@@ -0,0 +1,239 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Period.php 540 2008-06-29 22:44:13Z matt $
+ * 
+ * @package Piwik_Helper
+ */
+
+require_once "Period/Day.php";
+require_once "Period/Range.php";
+/**
+ * Creating a new Piwik_Period subclass: 
+ * 
+ * Every overloaded method must start with the code
+ * 		if(!$this->subperiodsProcessed)
+ *		{
+ *			$this->generate();
+ *		}
+ *	that checks whether the subperiods have already been computed.
+ *	This is for performance improvements, computing the subperiods is done a per demand basis.
+ *
+ * 
+ * @package Piwik_Helper
+ */
+abstract class Piwik_Period
+{
+	protected $subperiods = array();
+	protected $subperiodsProcessed = false;
+	protected $label = null;
+	protected $date = null;
+	
+	protected static $unknowPeriodException = "The period '%s' is not supported. Try 'day' or 'week' or 'month' or 'year'";
+	
+	public function __construct( $date )
+	{	
+		$this->checkInputDate( $date );
+		$this->date = clone $date;
+	}
+	
+	static public function factory($strPeriod, $date)
+	{
+		switch ($strPeriod) {
+			case 'day':
+				return new Piwik_Period_Day($date); 
+				break;
+		
+			case 'week':
+				require_once "Period/Week.php";
+				return new Piwik_Period_Week($date); 
+				break;
+				
+			case 'month':
+				require_once "Period/Month.php";
+				return new Piwik_Period_Month($date); 
+				break;
+				
+			case 'year':
+				require_once "Period/Year.php";
+				return new Piwik_Period_Year($date); 
+				break;
+				
+			default:
+				throw new Exception(sprintf(self::$unknowPeriodException, $strPeriod));
+				break;
+		}
+	}
+
+	/**
+	 * Returns the first day of the period
+	 *
+	 * @return Piwik_Date First day of the period
+	 */
+	public function getDateStart()
+	{
+		if(!$this->subperiodsProcessed)
+		{
+			$this->generate();
+		}
+		if(count($this->subperiods) == 0)
+		{
+			return $this->getDate();
+		}
+		$periods = $this->getSubperiods();
+		$currentPeriod = $periods[0];
+		while( $currentPeriod->getNumberOfSubperiods() > 0 )
+		{
+			$periods = $currentPeriod->getSubperiods();
+			$currentPeriod = $periods[0];
+		}
+		return $currentPeriod->getDate();
+	}
+	
+	/**
+	 * Returns the last day of the period ; can be a date in the future
+	 *
+	 * @return Piwik_Date Last day of the period 
+	 */
+	public function getDateEnd()
+	{
+		if(!$this->subperiodsProcessed)
+		{
+			$this->generate();
+		}
+		if(count($this->subperiods) == 0)
+		{
+			return $this->getDate();
+		}
+		$periods = $this->getSubperiods();
+		$currentPeriod = $periods[count($periods)-1];
+		while( $currentPeriod->getNumberOfSubperiods() > 0 )
+		{
+			$periods = $currentPeriod->getSubperiods();
+			$currentPeriod = $periods[count($periods)-1];
+		}
+		return $currentPeriod->getDate();
+	}
+	
+	public function getId()
+	{
+		return Piwik::$idPeriods[$this->getLabel()];
+	}
+
+	public function getLabel()
+	{
+		return $this->label;
+	}
+	
+	/**
+	 *
+	 * @return Piwik_Date
+	 */
+	protected function getDate()
+	{
+		return $this->date;
+	}	
+	
+	protected function checkInputDate($date)
+	{
+		if( !($date instanceof Piwik_Date))
+		{
+			throw new Exception("The date must be a Piwik_Date object. " . var_export($date,true));
+		}
+	}
+	
+	protected function generate()
+	{
+		$this->subperiodsProcessed = true;
+	}
+	
+	public function getNumberOfSubperiods()
+	{
+		if(!$this->subperiodsProcessed)
+		{
+			$this->generate();
+		}
+		return count($this->subperiods);
+	}
+	
+	/**
+	 * Returns Period_Day for a period made of days (week, month),
+	 * 			Period_Month for a period made of months (year) 
+	 * 
+	 * @return array
+	 */
+	public function getSubperiods()
+	{
+		if(!$this->subperiodsProcessed)
+		{
+			$this->generate();
+		}
+		return $this->subperiods;
+	}
+
+	/**
+	 * Add a date to the period.
+	 * 
+	 * Protected because it not yet supported to add periods after the initialization
+	 * 
+	 * @param Piwik_Date Valid Piwik_Date object
+	 */
+	protected function addSubperiod( $date )
+	{
+		$this->subperiods[] = $date;
+	}
+	
+	/**
+	 * A period is finished if all the subperiods are finished
+	 */
+	public function isFinished()
+	{
+		if(!$this->subperiodsProcessed)
+		{
+			$this->generate();
+		}
+		foreach($this->subperiods as $period)
+		{
+			if(!$period->isFinished())
+			{
+				return false;
+			}
+		}
+		return true;
+	}
+		
+	public function toString()
+	{
+		if(!$this->subperiodsProcessed)
+		{
+			$this->generate();
+		}
+		$dateString = array();
+		foreach($this->subperiods as $period)
+		{
+			$dateString[] = $period->toString();
+		}
+		return $dateString;
+	}
+	
+	public function __toString()
+	{
+		return $this->toString();
+	}
+	
+	public function get( $part= null )
+	{
+		if(!$this->subperiodsProcessed)
+		{
+			$this->generate();
+		}
+		return $this->date->get($part);
+	}
+	
+	abstract public function getPrettyString();
+}
+
+	
diff --git a/core/Period/Day.php b/core/Period/Day.php
new file mode 100644
index 0000000000..e4597945de
--- /dev/null
+++ b/core/Period/Day.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * 
+ * @package Piwik_Period
+ */
+class Piwik_Period_Day extends Piwik_Period
+{
+	protected $label = 'day';
+	
+	public function getPrettyString()
+	{
+		$out = $this->getDateStart()->toString() ;
+		return $out;
+	}
+	
+	public function isFinished()
+	{
+		$todayMidnight = Piwik_Date::today();
+		if($this->date->isEarlier($todayMidnight))
+		{
+			return true;
+		}
+	}
+	
+	public function getNumberOfSubperiods()
+	{
+		return 0;
+	}	
+	
+	public function addSubperiod( $date )
+	{
+		throw new Exception("Adding a subperiod is not supported for Piwik_Period_Day");
+	}
+	
+	public function toString()
+	{
+		return $this->date->toString("Y-m-d");
+	}
+}
diff --git a/core/Period/Month.php b/core/Period/Month.php
new file mode 100644
index 0000000000..57fd1d25c5
--- /dev/null
+++ b/core/Period/Month.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * 
+ * @package Piwik_Period
+ */
+class Piwik_Period_Month extends Piwik_Period
+{
+	protected $label = 'month';
+
+	public function getPrettyString()
+	{
+		$out = $this->getDateStart()->toString('Y-m');
+		return $out;
+	}
+	
+	protected function generate()
+	{
+		if($this->subperiodsProcessed)
+		{
+			return;
+		}
+		parent::generate();
+		
+		$date = $this->date;
+		
+		$startMonth = $date->setDay(1);
+		$currentDay = clone $startMonth;
+		while($currentDay->compareMonth($startMonth) == 0)
+		{
+			$this->addSubperiod(new Piwik_Period_Day($currentDay));
+			$currentDay = $currentDay->addDay(1);
+		}
+	}
+	
+	public function isFinished()
+	{
+		if(!$this->subperiodsProcessed)
+		{
+			$this->generate();
+		}
+		// a month is finished 
+		// if current month > month AND current year == year
+		// OR if current year > year
+		$year = $this->date->get("Y");
+		return ( date("m") > $this->date->get("m") && date("Y") == $year)
+				||  date("Y") > $year;
+	}
+}
diff --git a/core/Period/Range.php b/core/Period/Range.php
new file mode 100644
index 0000000000..03c97d3e23
--- /dev/null
+++ b/core/Period/Range.php
@@ -0,0 +1,165 @@
+<?php
+
+/**
+ * from a starting date to an ending date
+ *
+ */
+class Piwik_Period_Range extends Piwik_Period
+{
+	public function __construct( $strPeriod, $strDate )
+	{
+		$this->strPeriod = $strPeriod;
+		$this->strDate = $strDate;
+		$this->defaultEndDate = null;
+	}
+
+	public function getPrettyString()
+	{
+		$out = "From ".$this->getDateStart()->toString() . " to " . $this->getDateEnd()->toString();
+		return $out;
+	}
+
+	/**
+	 *
+	 * @param Piwik_Date $date
+	 * @param int $n
+	 * @return Piwik_Date
+	 */
+	protected function removePeriod( $date, $n )
+	{
+		switch($this->strPeriod)
+		{
+			case 'day':	
+				$startDate = $date->subDay( $n );
+			break;
+			
+			case 'week':
+				$startDate = $date->subDay( $n * 7 );					
+			break;
+			
+			case 'month':
+				$startDate = $date->subMonth( $n );					
+			break;
+			
+			case 'year':
+				$startDate = $date->subMonth( 12 * $n );					
+			break;
+			
+			default:
+				throw new Exception(sprintf(self::$unknowPeriodException, $this->strPeriod));
+			break;
+		}
+		return $startDate;
+	}
+
+	protected function getMaxN($lastN)
+	{	
+		switch($this->strPeriod)
+		{
+			case 'day':	
+				$lastN = min( $lastN, 5*365 );
+			break;
+			
+			case 'week':
+				$lastN = min( $lastN, 5*52 );				
+			break;
+			
+			case 'month':
+				$lastN = min( $lastN, 5*12 );			
+			break;
+			
+			case 'year':
+				$lastN = min( $lastN, 10 );					
+			break;
+		}
+		return $lastN;
+	}
+	
+	public function setDefaultEndDate( Piwik_Date $oDate)
+	{
+		$this->defaultEndDate = $oDate;
+	}
+	
+	protected function generate()
+	{
+		if($this->subperiodsProcessed)
+		{
+			return;
+		}
+		parent::generate();
+		
+		if(ereg('(last|previous)([0-9]*)', $this->strDate, $regs))
+		{
+			$lastN = $regs[2];
+			
+			$lastOrPrevious = $regs[1];
+			
+			if(!is_null($this->defaultEndDate))
+			{
+				$defaultEndDate = $this->defaultEndDate;
+			}
+			else
+			{
+				$defaultEndDate = Piwik_Date::today();
+			}		
+			if($lastOrPrevious == 'last')
+			{
+				$endDate = $defaultEndDate;
+			}
+			elseif($lastOrPrevious == 'previous')
+			{
+				$endDate = $this->removePeriod($defaultEndDate, 1);
+			}		
+			
+			// last1 means only one result ; last2 means 2 results so we remove only 1 to the days/weeks/etc
+			$lastN--;
+			$lastN = abs($lastN);
+			
+			$lastN = $this->getMaxN($lastN);
+			
+			$startDate = $this->removePeriod($endDate, $lastN);
+		}
+		elseif(ereg('([0-9]{4}-[0-9]{1,2}-[0-9]{1,2}),([0-9]{4}-[0-9]{1,2}-[0-9]{1,2})', $this->strDate, $regs))
+		{
+			$strDateStart = $regs[1];
+			$strDateEnd = $regs[2];
+
+			$startDate = Piwik_Date::factory($strDateStart);
+			$endDate   = Piwik_Date::factory($strDateEnd);
+		}
+		else
+		{
+			throw new Exception("The date '$this->strDate' is not a date range. Should have the following format: 'lastN' or 'previousN' or 'YYYY-MM-DD,YYYY-MM-DD'.");
+		}
+		
+		$endSubperiod = Piwik_Period::factory($this->strPeriod, $endDate);
+		
+		$arrayPeriods= array();
+		$arrayPeriods[] = $endSubperiod;
+		while($endDate->isLater($startDate) )
+		{
+			$endDate = $this->removePeriod($endDate, 1);
+			$subPeriod = Piwik_Period::factory($this->strPeriod, $endDate);
+			$arrayPeriods[] =  $subPeriod ;
+		}
+		$arrayPeriods = array_reverse($arrayPeriods);
+		foreach($arrayPeriods as $period)
+		{
+			$this->addSubperiod($period);
+		}
+	}
+	
+	function toString()
+	{
+		if(!$this->subperiodsProcessed)
+		{
+			$this->generate();
+		}
+		$range = array();
+		foreach($this->subperiods as $element)
+		{
+			$range[] = $element->toString();
+		}
+		return $range;
+	}
+}
\ No newline at end of file
diff --git a/core/Period/Week.php b/core/Period/Week.php
new file mode 100644
index 0000000000..f24002c878
--- /dev/null
+++ b/core/Period/Week.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * 
+ * @package Piwik_Period
+ */
+class Piwik_Period_Week extends Piwik_Period
+{
+	protected $label = 'week';
+	
+	public function getPrettyString()
+	{
+		$out = $this->getDateStart()->toString() . " to " . $this->getDateEnd()->toString();
+		return $out;
+	}
+	
+	protected function generate()
+	{
+		if($this->subperiodsProcessed)
+		{
+			return;
+		}
+		parent::generate();
+		$date = $this->date;
+		
+		if( $date->toString('N') > 1)
+		{
+			$date = $date->subDay($date->toString('N')-1);
+		}
+		
+		$startWeek = $date;
+		
+		$currentDay = clone $startWeek;
+		while($currentDay->compareWeek($startWeek) == 0)
+		{
+			$this->addSubperiod(new Piwik_Period_Day($currentDay) );
+			$currentDay = $currentDay->addDay(1);
+		}
+	}
+
+}
diff --git a/core/Period/Year.php b/core/Period/Year.php
new file mode 100644
index 0000000000..5ff4f083a7
--- /dev/null
+++ b/core/Period/Year.php
@@ -0,0 +1,49 @@
+<?php
+
+require_once "Period/Month.php";
+/**
+ * 
+ * @package Piwik_Period
+ */
+class Piwik_Period_Year extends Piwik_Period
+{	
+	protected $label = 'year';
+
+	public function getPrettyString()
+	{
+		$out = $this->getDateStart()->toString('Y');
+		return $out;
+	}
+	
+	protected function generate()
+	{
+		if($this->subperiodsProcessed)
+		{
+			return;
+		}
+		parent::generate();
+		
+		$year = $this->date->get("Y");
+		for($i=1; $i<=12; $i++)
+		{
+			$this->addSubperiod( new Piwik_Period_Month(
+									Piwik_Date::factory("$year-$i-01")
+								)
+							);
+		}
+	}
+	
+	function toString()
+	{
+		if(!$this->subperiodsProcessed)
+		{
+			$this->generate();
+		}
+		$stringMonth = array();
+		foreach($this->subperiods as $month)
+		{
+			$stringMonth[] = $month->get("Y")."-".$month->get("m")."-01";
+		}
+		return $stringMonth;
+	}
+}
diff --git a/core/Piwik.php b/core/Piwik.php
new file mode 100644
index 0000000000..c125b7fca6
--- /dev/null
+++ b/core/Piwik.php
@@ -0,0 +1,1045 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Piwik.php 581 2008-07-27 23:07:52Z matt $
+ * 
+ * @package Piwik
+ */
+
+require_once "Config.php";
+require_once "Zend/Db.php";
+require_once "Zend/Db/Table.php";
+require_once "Log.php";
+require_once "PluginsManager.php";
+require_once "Translate.php";
+
+/**
+ * Main piwik helper class.
+ * Contains static functions you can call from the plugins.
+ * 
+ * @package Piwik
+ */
+class Piwik
+{
+	const CLASSES_PREFIX = "Piwik_";
+	
+	public static $idPeriods =  array(
+			'day'	=> 1,
+			'week'	=> 2,
+			'month'	=> 3,
+			'year'	=> 4,
+		);
+	
+	/**
+	 * ending WITHOUT slashs
+	 */
+	static public function getPathToPiwikRoot()
+	{
+		return realpath( dirname(__FILE__). "/../" );
+	}
+	
+	/**
+	 * path without trailing slash
+	 */
+	static public function createHtAccess( $path )
+	{
+		@file_put_contents($path . "/.htaccess", "Deny from all");
+	}
+	
+	static public function mkdir( $path, $mode = 0755, $denyAccess = true )
+	{
+		if(!is_dir($path))
+		{
+			$directoryParent = Piwik::realpath(dirname($path));
+			if( is_writable($directoryParent) )
+			{
+				mkdir($path, $mode, true);
+			}
+		}
+		
+		if($denyAccess)
+		{
+			Piwik::createHtAccess($path);
+		}
+	}
+	
+	/**
+	 * Checks if directories are writable and create them if they do not exist.
+	 * 
+	 * @param array $directoriesToCheck array of directories to check - if not given default Piwik directories that needs write permission are checked
+	 * @return array direcory name => true|false (is writable)
+	 */
+	static public function checkDirectoriesWritable($directoriesToCheck = null)
+	{
+		if( $directoriesToCheck == null )		
+		{
+			$directoriesToCheck = array(
+				'/',
+				'/config',
+				'/tmp',
+				'/tmp/templates_c',
+				'/tmp/cache',
+			); 
+		}
+		
+		$resultCheck = array();
+		foreach($directoriesToCheck as $directoryToCheck)
+		{
+			if( !ereg('^'.preg_quote(PIWIK_INCLUDE_PATH), $directoryToCheck) )
+			{
+				$directoryToCheck = PIWIK_INCLUDE_PATH . $directoryToCheck;
+			}
+			
+			if(!file_exists($directoryToCheck))
+			{
+				Piwik::mkdir($directoryToCheck, 0755, false);
+			}
+			
+			$directory = Piwik::realpath($directoryToCheck);
+			$resultCheck[$directory] = false;
+			if(is_writable($directoryToCheck))
+			{
+				$resultCheck[$directory] = true;
+			}
+		}
+		return $resultCheck;
+	}
+	
+	static public function realpath($path)
+	{
+		if (file_exists($path)) 
+		{
+		    return realpath($path);
+		} 
+	    return $path;
+	}
+	
+	/**
+	 * Returns the Javascript code to be inserted on every page to track
+	 *
+	 * @param int $idSite
+	 * @param string $piwikUrl http://path/to/piwik/directory/ 
+	 * @param string $actionName
+	 * @return string
+	 */
+	static public function getJavascriptCode($idSite, $piwikUrl, $actionName = "''")
+	{	
+		$jsTag = file_get_contents( "core/LogStats/javascriptTag.tpl");
+		$jsTag = nl2br(htmlentities($jsTag));
+		$piwikUrl = preg_match('/^(http|https):\/\/(.*)$/', $piwikUrl, $matches);
+		$piwikUrl = $matches[2];
+		$jsTag = str_replace('{$actionName}', $actionName, $jsTag);
+		$jsTag = str_replace('{$idSite}', $idSite, $jsTag);
+		$jsTag = str_replace('{$piwikUrl}', $piwikUrl, $jsTag);
+		$jsTag = str_replace('{$hrefTitle}', Piwik::getRandomTitle(), $jsTag);
+		return $jsTag;
+	}
+	
+	static public function getMemoryLimitValue()
+	{
+		if($memory = ini_get('memory_limit'))
+		{
+			return substr($memory, 0, strlen($memory) - 1);
+		}
+		return false;
+	}
+	
+	static public function setMemoryLimit($minimumMemoryLimit)
+	{
+		$currentValue = self::getMemoryLimitValue();
+		if( ($currentValue === false
+			|| $currentValue < $minimumMemoryLimit )
+			&& @ini_set('memory_limit', $minimumMemoryLimit.'M'))
+		{
+			return true;
+		}
+		return false;
+	}
+	
+	static public function raiseMemoryLimitIfNecessary()
+	{
+		$minimumMemoryLimit = Zend_Registry::get('config')->General->minimum_memory_limit;
+		$memoryLimit = self::getMemoryLimitValue();
+		if($memoryLimit === false
+			|| $memoryLimit < $minimumMemoryLimit)
+		{
+			return self::setMemoryLimit($minimumMemoryLimit);
+		}
+		
+		return false;
+	}
+	
+	static public function log($message = '')
+	{
+		Zend_Registry::get('logger_message')->log($message);
+		Zend_Registry::get('logger_message')->log( "<br>" . PHP_EOL);
+	}
+	
+	
+	static public function error($message = '')
+	{
+		trigger_error($message, E_USER_ERROR);
+	}
+	
+	/**
+	 * Display the message in a nice red font with a nice icon
+	 * ... and dies
+	 */
+	static public function exitWithErrorMessage( $message )
+	{
+		$output = "<style>a{color:red;}</style>\n".
+			"<div style='color:red;font-family:Georgia;font-size:120%'>".
+			"<p><img src='themes/default/images/error_medium.png' style='vertical-align:middle; float:left;padding:20 20 20 20'>".
+			$message.
+			"</p></div>";
+		print(Piwik_Log_Formatter_ScreenFormatter::getFormattedString($output));
+		exit;
+	}
+	
+	/**
+	 * Computes the division of i1 by i2. If either i1 or i2 are not number, or if i2 has a value of zero
+	 * we return 0 to avoid the division by zero.
+	 *
+	 * @param numeric $i1
+	 * @param numeric $i2
+	 * @return numeric The result of the division or zero 
+	 */
+	static public function secureDiv( $i1, $i2 )
+	{
+	    if ( is_numeric($i1) && is_numeric($i2) && floatval($i2) != 0)
+		{ 
+			return $i1 / $i2;
+		}   
+		return 0;
+	}
+	static public function getQueryCount()
+	{
+		$profiler = Zend_Registry::get('db')->getProfiler();
+		return $profiler->getTotalNumQueries();
+	}
+	static public function getDbElapsedSecs()
+	{
+		$profiler = Zend_Registry::get('db')->getProfiler();
+		return $profiler->getTotalElapsedSecs();
+	}
+	static public function printQueryCount()
+	{
+		$totalTime = self::getDbElapsedSecs();
+		$queryCount = self::getQueryCount();
+		Piwik::log("Total queries = $queryCount (total sql time = ".round($totalTime,2)."s)");
+	}
+	
+	static public function printSqlProfilingReportLogStats( $db = null )
+	{
+		function maxSumMsFirst($a,$b)
+		{
+			return $a['sum_time_ms'] < $b['sum_time_ms'];
+		}
+		
+		if(is_null($db))
+		{
+			$db = Zend_Registry::get('db');
+			$tableName = Piwik::prefixTable('log_profiling');
+		}
+		else
+		{
+			$tableName = $db->prefixTable('log_profiling');
+		}
+		$all = $db->fetchAll('	SELECT *, sum_time_ms / count as avg_time_ms 
+								FROM '.$tableName );
+		if($all === false) 
+		{
+			return;
+		}
+		usort($all, 'maxSumMsFirst');
+		
+		$infoIndexedByQuery = array();
+		foreach($all as $infoQuery)
+		{
+			$query = $infoQuery['query'];
+			$count = $infoQuery['count'];
+			$sum_time_ms = $infoQuery['sum_time_ms'];
+			$infoIndexedByQuery[$query] = array('count' => $count, 'sumTimeMs' => $sum_time_ms);
+		}		
+		Piwik::getSqlProfilingQueryBreakdownOutput($infoIndexedByQuery);
+	}
+
+	/**
+	 * Outputs SQL Profiling reports 
+	 * It is automatically called when enabling the SQL profiling in the config file enable_sql_profiler
+	 *
+	 */
+	static function printSqlProfilingReportZend()
+	{
+		$profiler = Zend_Registry::get('db')->getProfiler();
+		
+		if(!$profiler->getEnabled())
+		{
+			throw new Exception("To display the profiler you should enable enable_sql_profiler on your config/config.ini.php file");
+		}
+		
+		$infoIndexedByQuery = array();
+		foreach($profiler->getQueryProfiles() as $query)
+		{
+			if(isset($infoIndexedByQuery[$query->getQuery()]))
+			{
+				$existing =  $infoIndexedByQuery[$query->getQuery()];
+			}
+			else
+			{
+				$existing = array( 'count' => 0, 'sumTimeMs' => 0);
+			}
+			$new = array( 'count' => $existing['count'] + 1,
+							'sumTimeMs' =>  $existing['count'] + $query->getElapsedSecs() * 1000);
+			$infoIndexedByQuery[$query->getQuery()] = $new;
+		}
+		function sortTimeDesc($a,$b)
+		{
+			return $a['sumTimeMs'] < $b['sumTimeMs'];
+		}
+		uasort( $infoIndexedByQuery, 'sortTimeDesc');
+		
+		Piwik::log('<hr><b>SQL Profiler</b>');
+		Piwik::log('<hr><b>Summary</b>');
+		$totalTime    = $profiler->getTotalElapsedSecs();
+		$queryCount   = $profiler->getTotalNumQueries();
+		$longestTime  = 0;
+		$longestQuery = null;
+		foreach ($profiler->getQueryProfiles() as $query) {
+		    if ($query->getElapsedSecs() > $longestTime) {
+		        $longestTime  = $query->getElapsedSecs();
+		        $longestQuery = $query->getQuery();
+		    }
+		}
+		$str = 'Executed ' . $queryCount . ' queries in ' . round($totalTime,3) . ' seconds' . "\n";
+		$str .= '(Average query length: ' . round($totalTime / $queryCount,3) . ' seconds)' . "\n";
+		$str .= '<br>Queries per second: ' . round($queryCount / $totalTime,1) . "\n";
+		$str .= '<br>Longest query length: ' . round($longestTime,3) . " seconds (<code>$longestQuery</code>) \n";
+		Piwik::log($str);
+		Piwik::getSqlProfilingQueryBreakdownOutput($infoIndexedByQuery);
+	}
+	
+	static private function getSqlProfilingQueryBreakdownOutput( $infoIndexedByQuery )
+	{
+		Piwik::log('<hr><b>Breakdown by query</b>');
+		$output = '';
+		foreach($infoIndexedByQuery as $query => $queryInfo) 
+		{
+			$timeMs = round($queryInfo['sumTimeMs'],1);
+			$count = $queryInfo['count'];
+			$avgTimeString = '';
+			if($count > 1) 
+			{
+				$avgTimeMs = $timeMs / $count;
+				$avgTimeString = " (average = <b>". round($avgTimeMs,1) . "ms</b>)"; 
+			}
+			$query = str_replace(array("\t","\n","\r\n","\r"), "_toberemoved_", $query);
+			$query = str_replace('_toberemoved__toberemoved_','',$query);
+			$query = str_replace('_toberemoved_', ' ',$query);
+			$output .= "Executed <b>$count</b> time". ($count==1?'':'s') ." in <b>".$timeMs."ms</b> $avgTimeString <pre>\t$query</pre>";
+		}
+		Piwik::log($output);
+	}
+	
+	static public function printTimer()
+	{
+		echo Zend_Registry::get('timer');
+	}
+	
+	static public function printMemoryUsage( $prefixString = null )
+	{
+		$memory = false;
+		if(function_exists('xdebug_memory_usage'))
+		{
+			$memory = xdebug_memory_usage();
+		}
+		elseif(function_exists('memory_get_usage'))
+		{
+			$memory = memory_get_usage();
+		}
+				
+		if($memory !== false)
+		{
+			$usage = round( $memory / 1024 / 1024, 2);
+			if(!is_null($prefixString))
+			{
+				Piwik::log($prefixString);
+			}
+			Piwik::log("Memory usage = $usage Mb");
+		}
+		else
+		{
+			Piwik::log("Memory usage function not found.");
+		}
+	}
+	
+	static public function isPhpCliMode()
+	{
+		return in_array(substr(php_sapi_name(), 0, 3), array('cgi', 'cli'));
+	}
+	
+	static public function isNumeric($value)
+	{
+		return !is_array($value) && ereg('^([-]{0,1}[0-9]{1,}[.]{0,1}[0-9]*)$', $value);
+	}
+	
+	static public function getRandomTitle()
+	{
+		$titles = array( 'Web analytics',
+						'Website analytics',
+						'Analytics',
+						'Web analytics api',
+						'Open source analytics',
+						'Open source web analytics',
+						'Free analytics',
+						'Analytics software',
+						'Free web analytics',
+						'Free web statistics',
+						'Web 2.0 analytics',
+						'Web analytic',
+						'Web statistics',
+						'Web stats',
+						'Web 2.0 stats',
+						'Statistics web 2.0',
+				);
+		$id = abs(intval(md5(substr(Piwik_Url::getCurrentHost(),7))));
+		$title = $titles[ $id % count($titles)];
+		return $title;
+	}
+	
+	static public function loadPlugins()
+	{
+		Piwik_PluginsManager::getInstance()->setLanguageToLoad(  Piwik_Translate::getInstance()->getLanguageToLoad() );
+		Piwik_PluginsManager::getInstance()->setPluginsToLoad( Zend_Registry::get('config')->Plugins->Plugins->toArray() );
+	}
+	
+	static public function installLoadedPlugins()
+	{
+		Piwik_PluginsManager::getInstance()->installLoadedPlugins();
+	}
+	
+	static public function getTableCreateSql( $tableName )
+	{
+		$tables = Piwik::getTablesCreateSql();
+		
+		if(!isset($tables[$tableName]))
+		{
+			throw new Exception("The table '$tableName' SQL creation code couldn't be found.");
+		}
+		
+		return $tables[$tableName];
+	}
+	
+	static public function getTablesCreateSql()
+	{
+		$config = Zend_Registry::get('config');
+		$prefixTables = $config->database->tables_prefix;
+		$tables = array(
+			'user' => "CREATE TABLE {$prefixTables}user (
+						  login VARCHAR(20) NOT NULL,
+						  password CHAR(32) NOT NULL,
+						  alias VARCHAR(45) NOT NULL,
+						  email VARCHAR(100) NOT NULL,
+						  token_auth CHAR(32) NOT NULL,
+						  date_registered TIMESTAMP NOT NULL,
+						  PRIMARY KEY(login),
+						  UNIQUE INDEX uniq_keytoken(token_auth)
+						)
+			",
+			
+			'access' => "CREATE TABLE {$prefixTables}access (
+						  login VARCHAR(20) NOT NULL,
+						  idsite INTEGER UNSIGNED NOT NULL,
+						  access VARCHAR(10) NULL,
+						  PRIMARY KEY(login, idsite)
+						)
+			",
+			
+			'site' => "CREATE TABLE {$prefixTables}site (
+						  idsite INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+						  name VARCHAR(90) NOT NULL,
+						  main_url VARCHAR(255) NOT NULL,
+  						  ts_created TIMESTAMP NOT NULL,
+						  PRIMARY KEY(idsite)
+						)
+			",
+			
+			'site_url' => "CREATE TABLE {$prefixTables}site_url (
+							  idsite INTEGER(10) UNSIGNED NOT NULL,
+							  url VARCHAR(255) NOT NULL,
+							  PRIMARY KEY(idsite, url)
+						)
+			",
+			
+			
+			'logger_message' => "CREATE TABLE {$prefixTables}logger_message (
+									  idlogger_message INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
+									  timestamp TIMESTAMP NULL,
+									  message TEXT NULL,
+									  PRIMARY KEY(idlogger_message)
+									)
+			",
+			
+			'logger_api_call' => "CREATE TABLE {$prefixTables}logger_api_call (
+									  idlogger_api_call INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
+									  class_name VARCHAR(255) NULL,
+									  method_name VARCHAR(255) NULL,
+									  parameter_names_default_values TEXT NULL,
+									  parameter_values TEXT NULL,
+									  execution_time FLOAT NULL,
+									  caller_ip BIGINT NULL,
+									  timestamp TIMESTAMP NULL,
+									  returned_value TEXT NULL,
+									  PRIMARY KEY(idlogger_api_call)
+									) 
+			",
+			
+			'logger_error' => "CREATE TABLE {$prefixTables}logger_error (
+									  idlogger_error INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
+									  timestamp TIMESTAMP NULL,
+									  message TEXT NULL,
+									  errno INTEGER UNSIGNED NULL,
+									  errline INTEGER UNSIGNED NULL,
+									  errfile VARCHAR(255) NULL,
+									  backtrace TEXT NULL,
+									  PRIMARY KEY(idlogger_error)
+									)
+			",
+			
+			'logger_exception' => "CREATE TABLE {$prefixTables}logger_exception (
+									  idlogger_exception INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
+									  timestamp TIMESTAMP NULL,
+									  message TEXT NULL,
+									  errno INTEGER UNSIGNED NULL,
+									  errline INTEGER UNSIGNED NULL,
+									  errfile VARCHAR(255) NULL,
+									  backtrace TEXT NULL,
+									  PRIMARY KEY(idlogger_exception)
+									)
+			",
+			
+			
+			'log_action' => "CREATE TABLE {$prefixTables}log_action (
+									  idaction INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+									  name VARCHAR(255) NOT NULL,
+  									  type TINYINT UNSIGNED NULL,
+									  PRIMARY KEY(idaction)
+						)
+			",
+			
+			'log_visit' => "CREATE TABLE {$prefixTables}log_visit (
+							  idvisit INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+							  idsite INTEGER(10) UNSIGNED NOT NULL,
+							  visitor_localtime TIME NOT NULL,
+							  visitor_idcookie CHAR(32) NOT NULL,
+							  visitor_returning TINYINT(1) NOT NULL,
+							  visit_first_action_time DATETIME NOT NULL,
+							  visit_last_action_time DATETIME NOT NULL,
+							  visit_server_date DATE NOT NULL,
+							  visit_exit_idaction INTEGER(11) NOT NULL,
+							  visit_entry_idaction INTEGER(11) NOT NULL,
+							  visit_total_actions SMALLINT(5) UNSIGNED NOT NULL,
+							  visit_total_time SMALLINT(5) UNSIGNED NOT NULL,
+							  referer_type INTEGER UNSIGNED NULL,
+							  referer_name VARCHAR(70) NULL,
+							  referer_url TEXT NOT NULL,
+							  referer_keyword VARCHAR(255) NULL,
+							  config_md5config CHAR(32) NOT NULL,
+							  config_os CHAR(3) NOT NULL,
+							  config_browser_name VARCHAR(10) NOT NULL,
+							  config_browser_version VARCHAR(20) NOT NULL,
+							  config_resolution VARCHAR(9) NOT NULL,
+							  config_color_depth TINYINT(2) UNSIGNED NOT NULL,
+							  config_pdf TINYINT(1) NOT NULL,
+							  config_flash TINYINT(1) NOT NULL,
+							  config_java TINYINT(1) NOT NULL,
+							  config_director TINYINT(1) NOT NULL,
+							  config_quicktime TINYINT(1) NOT NULL,
+							  config_realplayer TINYINT(1) NOT NULL,
+							  config_windowsmedia TINYINT(1) NOT NULL,
+							  config_cookie TINYINT(1) NOT NULL,
+							  location_ip BIGINT(11) NOT NULL,
+							  location_browser_lang VARCHAR(20) NOT NULL,
+							  location_country CHAR(3) NOT NULL,
+							  location_continent CHAR(3) NOT NULL,
+							  PRIMARY KEY(idvisit)
+							)
+			",
+			
+			'log_link_visit_action' => "CREATE TABLE {$prefixTables}log_link_visit_action (
+											  idlink_va INTEGER(11) NOT NULL AUTO_INCREMENT,
+											  idvisit INTEGER(10) UNSIGNED NOT NULL,
+											  idaction INTEGER(10) UNSIGNED NOT NULL,
+											  idaction_ref INTEGER(11) UNSIGNED NOT NULL,
+											  time_spent_ref_action INTEGER(10) UNSIGNED NOT NULL,
+											  PRIMARY KEY(idlink_va)
+											)
+			",
+			
+			'log_profiling' => "CREATE TABLE {$prefixTables}log_profiling (
+								  query TEXT NOT NULL,
+								  count INTEGER UNSIGNED NULL,
+								  sum_time_ms FLOAT NULL,
+								  UNIQUE INDEX query(query(100))
+								)
+			",
+			
+			'archive_numeric'	=> "CREATE TABLE {$prefixTables}archive_numeric (
+										idarchive INTEGER UNSIGNED NOT NULL,
+										name VARCHAR(255) NOT NULL,
+									  idsite INTEGER UNSIGNED NULL,
+									  date1 DATE NULL,
+								  	  date2 DATE NULL,
+									  period TINYINT UNSIGNED NULL,
+								  	  ts_archived DATETIME NULL,
+								  	  value FLOAT NULL,
+									  PRIMARY KEY(idarchive, name)
+									)
+			",
+			'archive_blob'	=> "CREATE TABLE {$prefixTables}archive_blob (
+									  idarchive INTEGER UNSIGNED NOT NULL,
+									  name VARCHAR(255) NOT NULL,
+									  idsite INTEGER UNSIGNED NULL,
+									  date1 DATE NULL,
+									  date2 DATE NULL,
+									  period TINYINT UNSIGNED NULL,
+									  ts_archived DATETIME NULL,
+									  value MEDIUMBLOB NULL,
+									  PRIMARY KEY(idarchive, name)
+									)
+			",
+		);
+		return $tables;
+	}
+	
+	static public function getCurrentUserLogin()
+	{
+		return Zend_Registry::get('access')->getLogin();
+	}
+	
+	static public function getCurrentUserTokenAuth()
+	{
+		return Zend_Registry::get('access')->getTokenAuth();
+	}
+	
+	/**
+	 * Returns the plugin currently being used to display the page
+	 *
+	 * @return Piwik_Plugin
+	 */
+	static public function getCurrentPlugin()
+	{
+		return Piwik_PluginsManager::getInstance()->getLoadedPlugin(Piwik::getModule());
+	}
+	
+	static public function isUserIsSuperUserOrTheUser( $theUser )
+	{
+		try{
+			self::checkUserIsSuperUserOrTheUser( $theUser );
+			return true;
+		} catch( Exception $e){
+			return false;
+		}
+	}
+	
+	// Accessible either to the user itself
+	static public function checkUserIsSuperUserOrTheUser( $theUser )
+	{
+		try{
+			if( Piwik::getCurrentUserLogin() !== $theUser)
+			{
+				// or to the super user
+				Piwik::checkUserIsSuperUser();
+			}
+		} catch( Piwik_Access_NoAccessException $e){
+			throw new Piwik_Access_NoAccessException("The user has to be either the Super User or the user '$theUser' itself.");
+		}
+	}
+	
+	static public function isUserIsSuperUser()
+	{
+		try{
+			self::checkUserIsSuperUser();
+			return true;
+		} catch( Exception $e){
+			return false;
+		}
+	}
+	
+	static public function setUserIsSuperUser()
+	{
+		Zend_Registry::get('access')->setSuperUser();
+	}
+	
+	static public function checkUserIsSuperUser()
+	{
+		Zend_Registry::get('access')->checkUserIsSuperUser();
+	}
+	
+	static public function isUserHasAdminAccess( $idSites )
+	{
+		try{
+			self::checkUserHasAdminAccess( $idSites );
+			return true;
+		} catch( Exception $e){
+			return false;
+		}
+	}
+	
+	static public function checkUserHasAdminAccess( $idSites )
+	{
+		Zend_Registry::get('access')->checkUserHasAdminAccess( $idSites );
+	}
+	
+	static public function isUserHasSomeAdminAccess()
+	{
+		try{
+			self::checkUserHasSomeAdminAccess();
+			return true;
+		} catch( Exception $e){
+			return false;
+		}
+	}
+	
+	static public function checkUserHasSomeAdminAccess()
+	{
+		Zend_Registry::get('access')->checkUserHasSomeAdminAccess();
+	}
+	
+	static public function isUserHasViewAccess( $idSites )
+	{
+		try{
+			self::checkUserHasViewAccess( $idSites );
+			return true;
+		} catch( Exception $e){
+			return false;
+		}
+	}
+	
+	static public function checkUserHasViewAccess( $idSites )
+	{
+		Zend_Registry::get('access')->checkUserHasViewAccess( $idSites );
+	}
+	
+	static public function prefixClass( $class )
+	{
+		if(substr_count($class, Piwik::CLASSES_PREFIX) > 0)
+		{
+			return $class;
+		}
+		return Piwik::CLASSES_PREFIX.$class;
+	}
+	static public function unprefixClass( $class )
+	{
+		$lenPrefix = strlen(Piwik::CLASSES_PREFIX);
+		if(substr($class, 0, $lenPrefix) == Piwik::CLASSES_PREFIX)
+		{
+			return substr($class, $lenPrefix);
+		}
+		return $class;
+	}
+
+	/**
+	 * Returns the current module read from the URL (eg. 'API', 'UserSettings', etc.)
+	 *
+	 * @return string
+	 */
+	static public function getModule()
+	{
+		return Piwik_Common::getRequestVar('module', '', 'string');
+	}
+	
+	/**
+	 * Returns the current action read from the URL
+	 *
+	 * @return string
+	 */
+	static public function getAction()
+	{
+		return Piwik_Common::getRequestVar('action', '', 'string');
+	}
+	
+	/**
+	 * returns false if the URL to redirect to is already this URL
+	 */
+	static public function redirectToModule( $newModule, $newAction = '' )
+	{
+		$currentModule = self::getModule();
+		$currentAction = self::getAction();
+	
+		if($currentModule != $newModule
+			||  $currentAction != $newAction )
+		{
+			
+			$newUrl = Piwik_URL::getCurrentUrlWithoutQueryString() 
+				. Piwik_Url::getCurrentQueryStringWithParametersModified(
+						array('module' => $newModule, 'action' => $newAction)
+				);
+	
+			Piwik_Url::redirectToUrl($newUrl);
+		}
+		return false;
+	}
+	
+	static public function prefixTable( $table )
+	{
+		$config = Zend_Registry::get('config');
+		$prefixTables = $config->database->tables_prefix;
+		return $prefixTables . $table;
+	}
+	
+	/**
+	 * Names of all the prefixed tables in piwik
+	 * Doesn't use the DB 
+	 */
+	static public function getTablesNames()
+	{
+		$aTables = array_keys(self::getTablesCreateSql());
+		$config = Zend_Registry::get('config');
+		$prefixTables = $config->database->tables_prefix;
+		$return = array();
+		foreach($aTables as $table)
+		{
+			$return[] = $prefixTables.$table;
+		}
+		return $return;
+	}
+	
+	static $tablesInstalled = null;
+	
+	static public function getTablesInstalled( $forceReload = true )
+	{
+		if(is_null(self::$tablesInstalled) 
+			|| $forceReload === true)
+		{
+			
+			$db = Zend_Registry::get('db');
+			$config = Zend_Registry::get('config');
+			$prefixTables = $config->database->tables_prefix;
+			
+			$allTables = $db->fetchCol("SHOW TABLES");
+			
+			// all the tables to be installed
+			$allMyTables = self::getTablesNames();
+			
+			// we get the intersection between all the tables in the DB and the tables to be installed
+			$tablesInstalled = array_intersect($allMyTables, $allTables);
+			
+			// at this point we have only the piwik tables which is good
+			// but we still miss the piwik generated tables (using the class Piwik_TablePartitioning)
+			
+			$allArchiveNumeric = $db->fetchCol("SHOW TABLES LIKE '".$prefixTables."archive_numeric%'");
+			$allArchiveBlob = $db->fetchCol("SHOW TABLES LIKE '".$prefixTables."archive_blob%'");
+					
+			$allTablesReallyInstalled = array_merge($tablesInstalled, $allArchiveNumeric, $allArchiveBlob);
+			
+			self::$tablesInstalled = $allTablesReallyInstalled;
+		}
+		return 	self::$tablesInstalled;
+	}
+	
+	static public function createDatabase()
+	{
+		$db = Zend_Registry::get('db');
+		$dbName = Zend_Registry::get('config')->database->dbname;
+		$db->query("CREATE DATABASE IF NOT EXISTS ".$dbName);
+	}
+	
+	static public function dropDatabase()
+	{
+		$db = Zend_Registry::get('db');
+		$dbName = Zend_Registry::get('config')->database->dbname;
+		$db->query("DROP DATABASE IF EXISTS ".$dbName);
+	}
+	
+	
+	static public function createDatabaseObject( $dbInfos = null )
+	{
+		$config = Zend_Registry::get('config');
+		
+		if(is_null($dbInfos))
+		{
+			$dbInfos = $config->database->toArray();
+		}
+		if(!isset($dbInfos['password']))
+		{
+			$dbInfos['password'] = '';
+		}
+		
+		// test with the password ='][{}!3456&&^#gegq"eQ for example
+		if(substr($dbInfos['password'],0,1) == '"'
+			&& substr($dbInfos['password'],-1,1) == '"'
+			&& strlen($dbInfos['password']) >= 2 )
+		{
+			$dbInfos['password'] = substr($dbInfos['password'], 1, -1);
+		}
+		$dbInfos['password'] = htmlspecialchars_decode($dbInfos['password']);
+		
+		$dbInfos['profiler'] = $config->Debug->enable_sql_profiler;
+		 
+		$db = Zend_Db::factory($config->database->adapter, $dbInfos);
+		$db->getConnection();
+		// see http://framework.zend.com/issues/browse/ZF-1398
+		$db->getConnection()->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
+		$db->getConnection()->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);		
+		Zend_Db_Table::setDefaultAdapter($db);
+		$db->resetConfigArray(); // we don't want this information to appear in the logs
+		Zend_Registry::set('db', $db);
+	}
+
+	static public function createLogObject()
+	{
+		require_once "Log/APICall.php";
+		require_once "Log/Exception.php";
+		require_once "Log/Error.php";
+		require_once "Log/Message.php";
+		
+		$configAPI = Zend_Registry::get('config')->log;
+		
+		$aLoggers = array(
+				'logger_api_call' => new Piwik_Log_APICall,
+				'logger_exception' => new Piwik_Log_Exception,
+				'logger_error' => new Piwik_Log_Error,
+				'logger_message' => new Piwik_Log_Message,
+			);			
+			
+		foreach($configAPI as $loggerType => $aRecordTo)
+		{
+			if(isset($aLoggers[$loggerType]))
+			{
+				$logger = $aLoggers[$loggerType];
+				
+				foreach($aRecordTo as $recordTo)
+				{
+					switch($recordTo)
+					{
+						case 'screen':
+							$logger->addWriteToScreen();
+						break;
+						
+						case 'database':
+							$logger->addWriteToDatabase();
+						break;
+						
+						case 'file':
+							$logger->addWriteToFile();		
+						break;
+						
+						default:
+							throw new Exception("TODO");
+						break;
+					}
+				}
+			}
+		}
+		
+		foreach($aLoggers as $loggerType =>$logger)
+		{
+			if($logger->getWritersCount() == 0)
+			{
+				$logger->addWriteToNull();
+			}
+			Zend_Registry::set($loggerType, $logger);
+		}
+	}
+	
+	
+	static public function createConfigObject( $pathConfigFile = null )
+	{
+		$config = new Piwik_Config($pathConfigFile);
+	}
+
+	static public function dropTables( $doNotDelete = array() )
+	{
+		$tablesAlreadyInstalled = self::getTablesInstalled();
+		$db = Zend_Registry::get('db');
+		
+		$doNotDeletePattern = "(".implode("|",$doNotDelete).")";
+		
+		foreach($tablesAlreadyInstalled as $tableName)
+		{
+			
+			if( count($doNotDelete) == 0
+				|| (!in_array($tableName,$doNotDelete)
+					&& !ereg($doNotDeletePattern,$tableName)
+					)
+				)
+			{
+				$db->query("DROP TABLE $tableName");
+			}
+		}			
+	}
+	
+	/**
+	 * Returns true if the email is a valid email
+	 * 
+	 * @param string email
+	 * @return bool
+	 */
+    static public function isValidEmailString( $email ) 
+    {
+		return (preg_match('/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9_.-]+\.[a-zA-Z]{2,4}$/', $email) > 0);
+    }
+    
+    /**
+     * Creates an entry in the User table for the "anonymous" user. 
+     * 
+     * @return void
+     */
+    static public function createAnonymousUser()
+    {
+    	// The anonymous user is the user that is assigned by default 
+    	// note that the token_auth value is anonymous, which is assigned by default as well in the Login plugin
+		$db = Zend_Registry::get('db');
+		$db->query("INSERT INTO ". Piwik::prefixTable("user") . " 
+					VALUES ( 'anonymous', '', 'anonymous', 'anonymous@example.org', 'anonymous', CURRENT_TIMESTAMP );" );
+    }
+    
+    static public function createTablesIndex()
+    {
+    	$db = Zend_Registry::get('db');
+		$prefixTables = Zend_Registry::get('config')->database->tables_prefix;
+		
+		$db->query('CREATE INDEX index_idvisit  ON '.$prefixTables.'log_link_visit_action (idvisit)');
+		$db->query('CREATE INDEX index_idaction ON '.$prefixTables.'log_action (idaction)');
+		$db->query('CREATE INDEX index_idsite ON '.$prefixTables.'log_visit (idsite)');
+		$db->query('CREATE INDEX index_visit_server_date ON '.$prefixTables.'log_visit (visit_server_date);');
+    }
+	
+    static public function createTables()
+	{
+		$db = Zend_Registry::get('db');
+		$config = Zend_Registry::get('config');
+		$prefixTables = $config->database->tables_prefix;
+
+		$tablesAlreadyInstalled = self::getTablesInstalled();
+		$tablesToCreate = self::getTablesCreateSql();
+		unset($tablesToCreate['archive_blob']);
+		unset($tablesToCreate['archive_numeric']);
+
+		foreach($tablesToCreate as $tableName => $tableSql)
+		{
+			$tableName = $prefixTables . $tableName;
+			if(!in_array($tableName, $tablesAlreadyInstalled))
+			{
+				$db->query( $tableSql );
+			}
+		}
+	}
+	
+	static public function install()
+	{
+		Piwik::mkdir(Zend_Registry::get('config')->smarty->compile_dir);
+		Piwik::mkdir(Zend_Registry::get('config')->smarty->cache_dir);
+	}
+	
+	static public function uninstall()
+	{
+		$db = Zend_Registry::get('db');
+		$db->query( "DROP TABLE IF EXISTS ". implode(", ", self::getTablesNames()) );
+	}
+}
+
diff --git a/core/Plugin.php b/core/Plugin.php
new file mode 100644
index 0000000000..a909d2ed2a
--- /dev/null
+++ b/core/Plugin.php
@@ -0,0 +1,102 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Plugin.php 581 2008-07-27 23:07:52Z matt $
+ * 
+ * @package Piwik
+ */
+
+
+/**
+ * Abstract class to define a Piwik_Plugin.
+ * Any plugin has to at least implement the abstract methods of this class.
+ * 
+ * @package Piwik
+ */
+abstract class Piwik_Plugin
+{
+	/**
+	 * Returns the plugin details
+	 * 	'name' => string             // plugin name
+	 * 	'description' => string      // 1/2 sentences description of the plugin 
+	 * 	'author' => string           // plugin author 
+	 * 	'author_homepage' => string  // author homepage (or email "mailto:youremail@example.org")
+	 * 	'homepage' => string         // plugin homepage
+	 * 	'version' => string          // plugin version number
+	 * 	'LogStatsPlugin' => bool     // should we load this plugin during the stats logging process?
+	 */
+	abstract function getInformation();
+	
+	/**
+	 * Returns the plugin name
+	 * @var string
+	 */
+	public function getName()
+	{
+		$info = $this->getInformation();
+		return $info['name'];
+	}
+	
+	/**
+	 * Returns the UserCountry part when the plugin class is Piwik_UserCountry
+	 *
+	 * @return string
+	 */
+	public function getClassName()
+	{
+		return substr(get_class($this), strlen("Piwik_"));
+	}
+	
+	/**
+	 * Returns the list of hooks registered with the methods names
+	 * @var array
+	 */
+	function getListHooksRegistered()
+	{
+		return array();
+	}
+	
+	/**
+	 * Returns the names of the required plugins
+	 * @var array
+	 */
+	public function getListRequiredPlugins()
+	{
+		return array();
+	}
+	
+	/**
+	 * Executed after loading plugin and registering translations
+	 * Useful for code that uses translated strings from the plugin.
+	 * @return void
+	 */
+	public function postLoad()
+	{
+		return;
+	}
+	
+	/**
+	 * Install the plugin
+	 * - create tables
+	 * - update existing tables
+	 * - etc.
+	 * @return void
+	*/
+	public function install()
+	{
+		return;
+	}
+	  
+	/**
+	 * Remove the created resources during the install
+	 * @return void
+	 */
+	public function uninstall()
+	{
+		return;
+	}
+}
+
diff --git a/core/PluginsFunctions/AdminMenu.php b/core/PluginsFunctions/AdminMenu.php
new file mode 100644
index 0000000000..b450eb04f4
--- /dev/null
+++ b/core/PluginsFunctions/AdminMenu.php
@@ -0,0 +1,33 @@
+<?php
+static $adminMenu = array();
+
+function Piwik_GetAdminMenu()
+{
+	global $adminMenu;
+	foreach($adminMenu as $key => &$element)
+	{
+		if(is_null($element))
+		{
+			unset($adminMenu[$key]);
+		}
+	}
+	return $adminMenu;
+}
+
+function Piwik_AddAdminMenu( $adminMenuName, $url )
+{
+	global $adminMenu;
+
+	if(!isset($adminMenu[$adminMenuName]))
+	{
+		$adminMenu[$adminMenuName] = $url;
+	}
+}
+
+function Piwik_RenameAdminMenuEntry($adminMenuOriginal, $adminMenuRenamed)
+{
+	global $adminMenu;
+	$save = $adminMenu[$adminMenuOriginal];
+	unset($adminMenu[$adminMenuOriginal]);
+	$adminMenu[$adminMenuRenamed] = $save;
+}
diff --git a/core/PluginsFunctions/Menu.php b/core/PluginsFunctions/Menu.php
new file mode 100644
index 0000000000..c120b868af
--- /dev/null
+++ b/core/PluginsFunctions/Menu.php
@@ -0,0 +1,106 @@
+<?php
+static $mainMenu = array();
+static $menuEditsToApply = array();
+static $menuRenameToApply = array();
+
+// we setup the main categories in a specific order
+$mainMenu['Dashboard'] = null;
+$mainMenu['General'] = null;
+$mainMenu['Visitors'] = null;
+$mainMenu['Actions'] = null;
+$mainMenu['Referers'] = null;
+$mainMenu['Live!'] = null;
+
+
+function Piwik_GetMenu()
+{
+	global $mainMenu;
+	global $menuEditsToApply;
+	global $menuRenameToApply;
+	
+	// we apply the list of edits we've registered so far
+	foreach($menuEditsToApply as $edit)
+	{
+		$mainMenuToEdit = $edit[0];
+		$subMenuToEdit = $edit[1];
+		$newUrl = $edit[2];
+		if(!isset($mainMenu[$mainMenuToEdit][$subMenuToEdit]))
+		{
+			Piwik_AddMenu($mainMenuToEdit, $subMenuToEdit, $newUrl);
+		}
+		else
+		{
+			$mainMenu[$mainMenuToEdit][$subMenuToEdit] = $newUrl;
+		}
+	}
+	
+	// we now apply the menu rename
+	foreach($menuRenameToApply as $rename)
+	{
+		$mainMenuOriginal = $rename[0];
+		$subMenuOriginal = $rename[1];
+		$mainMenuRenamed = $rename[2];
+		$subMenuRenamed = $rename[3];
+		if(isset($mainMenu[$mainMenuOriginal][$subMenuOriginal]))
+		{
+			$save = $mainMenu[$mainMenuOriginal][$subMenuOriginal];
+			unset($mainMenu[$mainMenuOriginal][$subMenuOriginal]);
+			$mainMenu[$mainMenuRenamed][$subMenuRenamed] = $save;
+		}
+	}	
+	
+	// we now do some cleaning on the menu
+	foreach($mainMenu as $key => &$element)
+	{
+		if(is_null($element))
+		{
+			unset($mainMenu[$key]);
+		}
+		else
+		{			
+			// we want to move some submenus in the first position
+			$priority = array('Overview','Evolution');
+			foreach($priority as $name)
+			{
+				if(isset($element[$name]))
+				{
+					$newElement = array($name => $element[$name]);
+					unset($element[$name]);
+					$element = $newElement + $element;
+				}
+			}
+			$element['_url'] = current($element);
+		}
+	}
+	return $mainMenu;
+}
+
+
+function Piwik_AddMenu( $mainMenuName, $subMenuName, $url )
+{
+	global $mainMenu;
+	
+	if(!isset($mainMenu[$mainMenuName]))
+	{
+		$mainMenu[$mainMenuName]['_url'] = $url;
+	}
+	if(!empty($subMenuName))
+	{
+		$mainMenu[$mainMenuName][$subMenuName] = $url;
+	}
+	
+}
+
+function Piwik_RenameMenuEntry($mainMenuOriginal, $subMenuOriginal, 
+								$mainMenuRenamed, $subMenuRenamed)
+{
+	global $menuRenameToApply;
+	$menuRenameToApply[] = array($mainMenuOriginal, $subMenuOriginal, 
+								$mainMenuRenamed, $subMenuRenamed);
+}
+
+function Piwik_EditMenuUrl( $mainMenuToEdit, $subMenuToEdit, $newUrl )
+{
+	global $menuEditsToApply;
+	$menuEditsToApply[] = array($mainMenuToEdit, $subMenuToEdit, $newUrl);
+} 
diff --git a/core/PluginsFunctions/Sql.php b/core/PluginsFunctions/Sql.php
new file mode 100644
index 0000000000..f169c4cf7e
--- /dev/null
+++ b/core/PluginsFunctions/Sql.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * Executes a SQL query on the DB and returns the Zend_Db_Statement object
+ * If you want to fetch data from the DB you should use the function Piwik_FetchAll()
+ * 
+ * See also http://framework.zend.com/manual/en/zend.db.statement.html
+ *
+ * @param string $sqlQuery
+ * @param array Parameters to bind in the query, array( param1 => value1, param2 => value2)
+ * @return Zend_Db_Statement
+ */
+function Piwik_Query( $sqlQuery, $parameters = array())
+{
+	return Zend_Registry::get('db')->query( $sqlQuery, $parameters);
+}
+
+/**
+ * Executes the SQL Query and fetches all the rows from the database
+ *
+ * @param string $sqlQuery
+ * @param array Parameters to bind in the query, array( param1 => value1, param2 => value2)
+ * @return array (one row in the array per row fetched in the DB)
+ */
+function Piwik_FetchAll( $sqlQuery, $parameters = array())
+{
+	return Zend_Registry::get('db')->fetchAll( $sqlQuery, $parameters );
+}
+
+function Piwik_FetchOne( $sqlQuery, $parameters = array())
+{
+	return Zend_Registry::get('db')->fetchOne( $sqlQuery, $parameters );
+}
+
diff --git a/core/PluginsFunctions/Widget.php b/core/PluginsFunctions/Widget.php
new file mode 100644
index 0000000000..65e90d84fa
--- /dev/null
+++ b/core/PluginsFunctions/Widget.php
@@ -0,0 +1,18 @@
+<?php
+
+Piwik_AddAction('Menu', 'Piwik_BuildMenu');
+
+static $widgets = array();
+
+function Piwik_GetListWidgets()
+{
+	global $widgets;
+	return $widgets;
+}
+
+function Piwik_AddWidget( $pluginName, $controllerMethodToCall, $widgetTitle )
+{
+	global $widgets;	
+	// get the plugin name from controller
+	$widgets[$pluginName][] = array( $widgetTitle, $controllerMethodToCall );
+}
diff --git a/core/PluginsManager.php b/core/PluginsManager.php
new file mode 100644
index 0000000000..88fe7fca87
--- /dev/null
+++ b/core/PluginsManager.php
@@ -0,0 +1,502 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: PluginsManager.php 583 2008-07-28 00:37:19Z matt $
+ * 
+ * @package Piwik
+ */
+
+
+require_once "Plugin.php";
+require_once "Event/Dispatcher.php";
+
+/**
+ * @package Piwik
+ */
+class Piwik_PluginsManager
+{
+	/**
+	 * @var Event_Dispatcher
+	 */
+	public $dispatcher;
+	
+	protected $pluginsToLoad = array();
+	protected $installPlugins = false;
+	protected $doLoadPlugins = true;
+	protected $languageToLoad = null;
+	protected $loadedPlugins = array();
+	
+	protected $doLoadAlwaysActivatedPlugins = true;
+	protected $pluginToAlwaysActivate = array(	'CoreHome', 
+												'CoreAdminHome',
+												'CorePluginsAdmin'
+											);
+
+	static private $instance = null;
+	
+	/**
+	 * Returns the singleton Piwik_PluginsManager
+	 *
+	 * @return Piwik_PluginsManager
+	 */
+	static public function getInstance()
+	{
+		if (self::$instance == null)
+		{			
+			$c = __CLASS__;
+			self::$instance = new $c();
+		}
+		return self::$instance;
+	}
+	
+	private function __construct()
+	{
+		$this->dispatcher = Event_Dispatcher::getInstance();
+	}
+	
+	public function isPluginAlwaysActivated( $name )
+	{
+		return in_array( $name, $this->pluginToAlwaysActivate);
+	}
+	
+	public function isPluginActivated( $name )
+	{
+		return in_array( $name, $this->pluginsToLoad)
+			|| $this->isPluginAlwaysActivated( $name );		
+	}
+	
+	/**
+	 * Reads the directories inside the plugins/ directory and returns their names in an array
+	 *
+	 * @return array
+	 */
+	public function readPluginsDirectory()
+	{
+		$pluginsName = glob( "plugins/*",GLOB_ONLYDIR);
+		$pluginsName = array_map('basename', $pluginsName);
+		return $pluginsName;
+	}
+
+	public function deactivatePlugin($pluginName)
+	{
+		$plugins = $this->pluginsToLoad;
+	
+		$key = array_search($pluginName,$plugins);
+		if($key !== false)
+		{
+			unset($plugins[$key]);
+			Zend_Registry::get('config')->Plugins = $plugins;
+		}
+		
+		try{
+			$pluginsLogStats = Zend_Registry::get('config')->Plugins_LogStats->Plugins_LogStats;
+			if(!is_null($pluginsLogStats))
+			{
+				$pluginsLogStats = $pluginsLogStats->toArray();
+				$key = array_search($pluginName,$pluginsLogStats);
+				if($key !== false)
+				{
+					unset($pluginsLogStats[$key]);
+					Zend_Registry::get('config')->Plugins_LogStats = $pluginsLogStats;
+				}
+			}
+		} catch(Exception $e) {}
+	}
+	
+	/**
+	 * TODO horrible dirty hack because the Config class is not clean enough. Needs to rewrite the Config
+	 * __set and __get in a cleaner way, also see the __destruct which writes the configuration file.
+	 *
+	 * @return array
+	 */
+	protected function getInstalledPlugins()
+	{
+		if(!class_exists('Zend_Registry'))
+		{
+			throw new Exception("Not possible to list installed plugins (case LogStats module)");
+		}
+		if(!is_null(Zend_Registry::get('config')->PluginsInstalled->PluginsInstalled))
+		{
+			return Zend_Registry::get('config')->PluginsInstalled->PluginsInstalled->toArray();
+		}
+		elseif(is_array(Zend_Registry::get('config')->PluginsInstalled))
+		{
+			return Zend_Registry::get('config')->PluginsInstalled;
+		}
+		else
+		{
+			return Zend_Registry::get('config')->PluginsInstalled->toArray();
+		}
+	}
+	
+	public function installLoadedPlugins()
+	{
+		foreach($this->getLoadedPlugins() as $plugin)
+		{
+			try {
+				$this->installPluginIfNecessary( $plugin );
+			}catch(Exception $e){
+				echo $e->getMessage();
+			}				
+		}
+	}
+	
+	protected function installPluginIfNecessary( Piwik_Plugin $plugin )
+	{
+		$pluginName = $plugin->getClassName();
+		
+		// is the plugin already installed or is it the first time we activate it?
+		$pluginsInstalled = $this->getInstalledPlugins();
+		if(!in_array($pluginName,$pluginsInstalled))
+		{
+			$this->installPlugin($plugin);
+			$pluginsInstalled[] = $pluginName;
+			Zend_Registry::get('config')->PluginsInstalled = $pluginsInstalled;	
+		}
+		
+		$information = $plugin->getInformation();
+		
+		// if the plugin is to be loaded during the statistics logging
+		if(isset($information['LogStatsPlugin'])
+			&& $information['LogStatsPlugin'] === true)
+		{
+			$pluginsLogStats = Zend_Registry::get('config')->Plugins_LogStats->Plugins_LogStats;
+			if(is_null($pluginsLogStats))
+			{
+				$pluginsLogStats = array();
+			}
+			else
+			{
+				$pluginsLogStats = $pluginsLogStats->toArray();
+			}
+			if(!in_array($pluginName, $pluginsLogStats))
+			{
+				$pluginsLogStats[] = $pluginName;
+				Zend_Registry::get('config')->Plugins_LogStats = $pluginsLogStats;
+			}
+		}
+	}
+	
+	public function activatePlugin($pluginName)
+	{
+		$plugins = Zend_Registry::get('config')->Plugins->Plugins->toArray();
+		if(in_array($pluginName,$plugins))
+		{
+			throw new Exception("Plugin '$pluginName' already activated.");
+		}
+		
+		$existingPlugins = $this->readPluginsDirectory();
+		if( array_search($pluginName,$existingPlugins) === false)
+		{
+			throw new Exception("Unable to find the plugin '$pluginName'.");
+		}
+		
+		$plugin = $this->loadPlugin($pluginName);
+		
+		$this->installPluginIfNecessary($plugin);
+		
+		// we add the plugin to the list of activated plugins
+		$plugins[] = $pluginName;
+
+		// the config file will automatically be saved with the new plugin
+		Zend_Registry::get('config')->Plugins = $plugins;
+	}
+	
+	public function setPluginsToLoad( array $pluginsToLoad )
+	{
+		// case no plugins to load
+		if(is_null($pluginsToLoad))
+		{
+			$pluginsToLoad = array();
+		}
+		$this->pluginsToLoad = $pluginsToLoad;
+		
+		$this->loadPlugins();
+	}
+	
+	public function doNotLoadPlugins()
+	{
+		$this->doLoadPlugins = false;
+	}
+
+	public function doNotLoadAlwaysActivatedPlugins()
+	{
+		$this->doLoadAlwaysActivatedPlugins = false;
+	}
+	
+	/**
+	 * Add a plugin in the loaded plugins array
+	 *
+	 * @param string plugin name without prefix (eg. 'UserCountry')
+	 * @param Piwik_Plugin $newPlugin
+	 */
+	protected function addLoadedPlugin( $pluginName, Piwik_Plugin $newPlugin )
+	{
+		$this->loadedPlugins[$pluginName] = $newPlugin;
+	}
+	
+	/**
+	 * Returns an array containing the plugins class names (eg. 'Piwik_UserCountry' and NOT 'UserCountry')
+	 *
+	 * @return array
+	 */
+	public function getLoadedPluginsName()
+	{
+		$oPlugins = $this->getLoadedPlugins();
+		$pluginNames = array_map('get_class',$oPlugins);
+		return $pluginNames;
+	}
+	
+	/**
+	 * Returns an array of key,value with the following format: array(
+	 * 		'UserCountry' => Piwik_Plugin $pluginObject,
+	 * 		'UserSettings' => Piwik_Plugin $pluginObject,
+	 * 	);
+	 *
+	 * @return array 
+	 */
+	public function getLoadedPlugins()
+	{
+		return $this->loadedPlugins;
+	}
+
+	/**
+	 * Returns the given Piwik_Plugin object 
+	 *
+	 * @param string $name
+	 * @return Piwik_Piwik
+	 */
+	public function getLoadedPlugin($name)
+	{
+		if(!isset($this->loadedPlugins[$name]))
+		{
+			throw new Exception("The plugin '$name' has not been loaded.");
+		}
+		return $this->loadedPlugins[$name];
+	}
+	/**
+	 * Load the plugins classes installed.
+	 * Register the observers for every plugin.
+	 * 
+	 */
+	public function loadPlugins()
+	{
+		$this->pluginsToLoad = array_unique($this->pluginsToLoad);
+		
+		$pluginsToLoad = $this->pluginsToLoad;
+		
+		if($this->doLoadAlwaysActivatedPlugins)
+		{
+			$pluginsToLoad = array_merge($this->pluginsToLoad, $this->pluginToAlwaysActivate);
+		}
+		
+		foreach($pluginsToLoad as $pluginName)
+		{
+			$newPlugin = $this->loadPlugin($pluginName);
+
+			// if we have to load the plugins
+			// and if this plugin is activated			
+			if($this->doLoadPlugins
+				&& $this->isPluginActivated($pluginName))
+			{
+				$this->registerTranslation( $newPlugin, $this->languageToLoad );
+				$this->addPluginObservers( $newPlugin );
+				$this->addLoadedPlugin( $pluginName, $newPlugin);
+				
+				$newPlugin->postLoad();
+			}
+		}
+	}
+	
+	/**
+	 * Loads the plugin filename and instanciates the plugin with the given name, eg. UserCountry
+	 * Do NOT give the class name ie. Piwik_UserCountry, but give the plugin name ie. UserCountry 
+	 *
+	 * @param Piwik_Plugin $pluginName
+	 */
+	public function loadPlugin( $pluginName )
+	{
+		if(isset($this->loadedPlugins[$pluginName]))
+		{
+			return $this->loadedPlugins[$pluginName];
+		}
+		$pluginFileName = $pluginName . '/' . $pluginName . ".php";
+		$pluginClassName = "Piwik_".$pluginName;
+		
+		if( !Piwik_Common::isValidFilename($pluginName))
+		{
+			throw new Exception("The plugin filename '$pluginFileName' is not a valid filename");
+		}
+		
+		$path = 'plugins/' . $pluginFileName;
+
+		// case LogStats, we don't throw the exception, we don't want to add the Zend overhead
+		if(class_exists('Zend_Loader') 
+			&& !Zend_Loader::isReadable($path))
+		{
+			throw new Exception("<b>The plugin file {$path} couldn't be found. </b><br> 
+			If you are updating from a 0.2.x version, please <a target=_blank href='http://dev.piwik.org/trac/wiki/FAQ#HowdoIupdatefrom0.2.xtothe0.3'>read the FAQ</a>!<br>
+			Found in your config/config.ini.php file:<br><code>[Plugins]</code><br><code>Plugins[] = $pluginName;</code>");
+		}
+		
+		require_once $path;
+		
+		if(!class_exists($pluginClassName))
+		{
+			throw new Exception("The class $pluginClassName couldn't be found in the file '$path'");
+		}
+		$newPlugin = new $pluginClassName;
+		
+		if(!($newPlugin instanceof Piwik_Plugin))
+		{
+			throw new Exception("The plugin $pluginClassName in the file $path must inherit from Piwik_Plugin.");
+		}
+		return $newPlugin;
+	}
+
+	public function installPlugin( Piwik_Plugin $plugin )
+	{
+		try{
+			$plugin->install();
+		} catch(Exception $e) {
+			throw new Piwik_Plugin_Exception($plugin->getName(), $e->getMessage());		}	
+	}
+	
+	public function installPlugins()
+	{
+		foreach($this->getLoadedPlugins() as $plugin)
+		{		
+			try{
+				$plugin->install();
+			} catch(Exception $e) {
+				throw new Piwik_Plugin_Exception($plugin->getName(), $e->getMessage());
+			}
+		}
+	}
+	public function setLanguageToLoad( $code )
+	{
+		$this->languageToLoad = $code;
+	}
+	
+	/**
+	 * For the given plugin, add all the observers of this plugin.
+	 */
+	private function addPluginObservers( Piwik_Plugin $plugin )
+	{
+		$hooks = $plugin->getListHooksRegistered();
+		
+		foreach($hooks as $hookName => $methodToCall)
+		{
+			$this->dispatcher->addObserver( array( $plugin, $methodToCall), $hookName );
+		}
+	}
+	public function unloadPlugin( $plugin )
+	{
+		if(!($plugin instanceof Piwik_Plugin ))
+		{
+			$plugin = $this->loadPlugin( $plugin );
+		}
+		$hooks = $plugin->getListHooksRegistered();
+			
+		foreach($hooks as $hookName => $methodToCall)
+		{
+			$success = $this->dispatcher->removeObserver( array( $plugin, $methodToCall), $hookName );
+			if($success !== true)
+			{
+				throw new Exception("Error unloading plugin for method = $methodToCall // hook = $hookName ");
+			}
+		}
+		unset($this->loadedPlugins[$plugin->getClassName()]);
+	}
+	
+	public function unloadPlugins()
+	{
+		$pluginsLoaded = $this->getLoadedPlugins();
+		foreach($pluginsLoaded as $plugin)
+		{
+			$this->unloadPlugin($plugin);
+		}
+	}
+	
+	/**
+	 * @param Piwik_Plugin $plugin
+	 * @param string $langCode
+	 */
+	protected function registerTranslation( $plugin, $langCode )
+	{
+		// we are certainly in LogStats mode, Zend is not loaded
+		if(!class_exists('Zend_Loader'))
+		{
+			return ;
+		}
+		
+		$infos = $plugin->getInformation();		
+		if(!isset($infos['translationAvailable']))
+		{
+			$infos['translationAvailable'] = false;
+		}
+		$translationAvailable = $infos['translationAvailable'];
+		
+		if(!$translationAvailable)
+		{
+			return;
+		}
+		
+		$pluginName = $plugin->getClassName();
+		
+		$path = "plugins/" . $pluginName ."/lang/%s.php";
+		
+		$defaultLangPath = sprintf($path, $langCode);
+		$defaultEnglishLangPath = sprintf($path, 'en');
+		
+		$translations = array();
+				
+		if(Zend_Loader::isReadable($defaultLangPath))
+		{
+			require $defaultLangPath;
+		}
+		elseif(Zend_Loader::isReadable($defaultEnglishLangPath))
+		{
+			require $defaultEnglishLangPath;
+		}
+		else
+		{
+			throw new Exception("Language file not found for the plugin '$pluginName'.");
+		}
+		
+		Piwik_Translate::getInstance()->addTranslationArray($translations);
+	}
+	
+}
+
+
+class Piwik_Plugin_Exception extends Exception 
+{
+	function __construct($name, $message)
+	{
+		parent::__construct("There was a problem installing the plugin ". $name . " = " . $message.
+				"<br><b>If this plugin has already been installed, and if you want to hide this message</b>, you must add the following line under the 
+				<code>[PluginsInstalled]</code> entry in your config/config.ini.php file:<br>
+				<code>PluginsInstalled[] = $name</code><br><br>" );
+	}
+}
+
+
+/**
+ * Post an event to the dispatcher which will notice the observers
+ */
+function Piwik_PostEvent( $eventName,  &$object = null, $info = array() )
+{
+	Piwik_PluginsManager::getInstance()->dispatcher->post( $object, $eventName, $info, true, false );
+}
+
+/**
+ * Register an action to execute for a given event
+ */
+function Piwik_AddAction( $hookName, $function )
+{
+	Piwik_PluginsManager::getInstance()->dispatcher->addObserver( $function, $hookName );
+}
\ No newline at end of file
diff --git a/core/Site.php b/core/Site.php
new file mode 100644
index 0000000000..12370dde65
--- /dev/null
+++ b/core/Site.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Site.php 561 2008-07-21 00:00:35Z matt $
+ * 
+ * @package Piwik_Site
+ */
+
+/**
+ * 
+ * @package Piwik_Site
+ */
+class Piwik_Site
+{
+	protected $id = null;
+	
+	protected static $infoSites = array();
+
+	function __construct($idsite)
+	{
+		$this->id = $idsite;
+		
+		if(!isset(self::$infoSites[$this->id]))
+		{
+			self::$infoSites[$this->id] = Piwik_SitesManager_API::getSiteFromId($idsite);
+		}
+	}
+	function getName()
+	{
+		return self::$infoSites[$this->id]['name'];
+	}
+	function getMainUrl()
+	{
+		return self::$infoSites[$this->id]['main_url'];
+	}
+	
+	function getId()
+	{
+		return $this->id;
+	}
+	
+	function getCreationDate()
+	{
+		$date = self::$infoSites[$this->id]['ts_created'];
+		return Piwik_Date::factory($date);
+	}
+	
+	/**
+	 * @param string comma separated idSite list
+	 * @return array of valid integer
+	 */
+	static public function getIdSitesFromIdSitesString( $string )
+	{
+		$ids = explode(',', $string);
+		$validIds = array();
+		foreach($ids as $id)
+		{
+			$id = trim($id);
+			$validIds[] = $id;
+		}
+		return $validIds;
+	}
+}
+
diff --git a/core/SmartyPlugins/function.assignTopBar.php b/core/SmartyPlugins/function.assignTopBar.php
new file mode 100644
index 0000000000..79dd80025e
--- /dev/null
+++ b/core/SmartyPlugins/function.assignTopBar.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * Enter description here...
+ *
+ * @param array $params
+ * @param Smarty $smarty
+ */
+function smarty_function_assignTopBar($params, &$smarty)
+{
+	$topBarElements = array(
+		array('CoreHome', 'Your Dashboard', array('module' => 'CoreHome', 'action' => 'index')),
+		array('Widgetize', 'Widgets',  array('module' => 'Widgetize', 'action' => 'index')), 
+		array('API', 'API', array('module' => 'API', 'action' => 'listAllAPI')),
+		array('Feedback', 'Give us Feedback!', array('module' => 'Feedback', 'action' => 'index', 'keepThis' => 'true', 'TB_iframe' => 'true', 'height' => '400', 'width' => '350'), 'title="Give us Feedback!" class="thickbox"'),
+	);
+	$smarty->assign("topBarElements", $topBarElements);
+}
\ No newline at end of file
diff --git a/core/SmartyPlugins/function.hiddenurl.php b/core/SmartyPlugins/function.hiddenurl.php
new file mode 100644
index 0000000000..c8933aa6c6
--- /dev/null
+++ b/core/SmartyPlugins/function.hiddenurl.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: function.url.php 168 2008-01-14 05:26:43Z matt $
+ * 
+ * @package SmartyPlugins
+ */
+
+require_once "Url.php";
+
+/**
+ * Smarty {hiddenurl} function plugin.
+ * Writes an input Hidden field for every parameter in the URL.
+ * Useful when using GET forms because we need to print the current parameters 
+ * in hidden input so they are to the next URL after the form is submitted.
+ *
+ * 
+ * Examples:
+ * <pre>
+ * {hiddenurl module="API"} with a URL 'index.php?action=test&module=CoreHome' will output
+ *  <input type=hidden name=action value=test>
+ *  <input type=hidden name=module value=API>
+ * </pre>
+ * 
+ * Set a value to null if you want this value not to be passed in the submitted form.
+ * 
+ * @param	array
+ * @param	Smarty
+ * @return	string
+ */
+function smarty_function_hiddenurl($params, &$smarty)
+{
+	$urlModified = Piwik_Url::getCurrentQueryStringWithParametersModified( $params );
+	$queryString = htmlspecialchars($urlModified);
+	$urlValues = Piwik_Common::getArrayFromQueryString($queryString);
+	
+	$out = '';
+	foreach($urlValues as $name => $value)
+	{
+		$out .= '<input type="hidden" name="'.$name.'" value="'.$value.'" />';
+	}
+	return $out;
+}
diff --git a/core/SmartyPlugins/function.loadJavascriptTranslations.php b/core/SmartyPlugins/function.loadJavascriptTranslations.php
new file mode 100644
index 0000000000..d470dde784
--- /dev/null
+++ b/core/SmartyPlugins/function.loadJavascriptTranslations.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ *	inserts javascript translation array into the template from given plugins
+ *  must be called with 'plugins' argument which consists of space-separated module names (i.e. plugins)
+ *
+ *
+ *  Example (use in template):
+ *
+ *  {loadJavascriptTranslations plugins='SitesManager CoreHome General'}
+ *
+ *  loads javascript array translations from main translation file ('General')
+ *  and both 'CoreHome' and 'SitesManager' plugins translations
+ *
+ *  Note: You can put noHtml=1 option in order to output pure JS code
+ * 
+ *  only translations with '_fs' suffix will be loaded
+ *
+ *  in order to use translation in your javascript use _pk_translate function
+ *  (it is always loaded with translations):
+ *
+ *  <script type="text/javascript">
+ *     alert(_pk_translate('MY_TRANSLATION_STRING'))
+ *  </script>
+ *
+ *  Note: Use translation string from your translation file WITHOUT '_js' suffix.
+ * 
+ * _pk_translate DOES NOT support printf() arguments, but you can call:
+ *
+ *   sprintf(_pk_translate('_NB_OF_EGGS'),'ten')
+ *   (where _NB_OF_EGGS is defined in translation file as i.e. 'There is %s eggs on the table')
+ * 
+ * sprintf() function is by default included when loading translations
+ */
+
+function smarty_function_loadJavascriptTranslations($params, &$smarty) 
+{
+	if(!isset($params['plugins']))
+	{
+		throw new Exception("The smarty function loadJavascriptTranslations needs a 'plugins' parameter.");
+	}
+	$translate = Piwik_Translate::getInstance();
+	$jsTranslations = $translate->getJavascriptTranslations(explode(' ',$params['plugins']));
+	
+	$jsCode = "";
+	
+	if( isset($params['noHtml']) )
+	{
+		$jsCode .= "document.write('<scr'+'ipt language=\"javascript\" src=\"libs/javascript/sprintf.js\"><\/scr'+'ipt>');\n";
+		$jsCode .= $jsTranslations;
+	}
+	else
+	{
+		$jsCode .= '<script type="text/javascript" src="libs/javascript/sprintf.js"></script>';	
+		$jsCode .= '<script type="text/javascript">';
+		$jsCode .= $jsTranslations;
+		$jsCode .= '</script>';
+	}
+	
+	return $jsCode;
+}
diff --git a/core/SmartyPlugins/function.postEvent.php b/core/SmartyPlugins/function.postEvent.php
new file mode 100644
index 0000000000..bad7819418
--- /dev/null
+++ b/core/SmartyPlugins/function.postEvent.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: function.url.php 168 2008-01-14 05:26:43Z matt $
+ * 
+ * @package SmartyPlugins
+ */
+
+require_once "Url.php";
+
+/**
+ * Posts an event from a smarty template. This event can then be hooked by another plugin.
+ * The even will be posted along with a string value that plugins can edit.
+ * This is useful to allow other plugins to add content at a specific entry point in the template.
+ * This string will be returned by the smarty function.
+ * 
+ * Examples:
+ * <pre>
+ * 		{postEvent name="template_footerUserCountry"}
+ * </pre>
+ * 
+ * Plugins can then hook on this event by using the Piwik_AddAction function: 
+ * 	Piwik_AddAction('template_footerUserCountry', 'functionToHookOnThisEvent');
+ * 
+ * @param string $name The name of the event
+ * @return string The string eventually modified by the plugins listening to this event
+ */
+function smarty_function_postEvent($params, &$smarty)
+{
+	if(!isset($params['name']))
+	{
+		throw new Exception("The smarty function postEvent needs a 'name' parameter.");
+	}
+	$eventName = $params['name'];
+	
+	$str = '';
+	Piwik_PostEvent($eventName, $str);
+	return $str;
+}
diff --git a/core/SmartyPlugins/function.url.php b/core/SmartyPlugins/function.url.php
new file mode 100644
index 0000000000..adadea3beb
--- /dev/null
+++ b/core/SmartyPlugins/function.url.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: function.url.php 525 2008-06-25 23:49:13Z matt $
+ * 
+ * @package SmartyPlugins
+ */
+
+require_once "Url.php";
+
+/**
+ * Smarty {url} function plugin.
+ * Generates a piwik URL with the specified parameters modified.
+ *
+ * Examples:
+ * <pre>
+ * {url module="API"} will rewrite the URL modifying the module GET parameter
+ * {url module="API" method="getKeywords"} will rewrite the URL modifying the parameters module=API method=getKeywords
+ * </pre>
+ * 
+ * @see Piwik_Url::getCurrentQueryStringWithParametersModified()
+ * @param $name=$value of the parameters to modify in the generated URL
+ * @return	string Something like index.php?module=X&action=Y 
+ */
+function smarty_function_url($params, &$smarty)
+{
+	return htmlspecialchars(Piwik_Url::getCurrentScriptName() . Piwik_Url::getCurrentQueryStringWithParametersModified( $params ));
+}
diff --git a/core/SmartyPlugins/modifier.sumtime.php b/core/SmartyPlugins/modifier.sumtime.php
new file mode 100644
index 0000000000..2c3e6fd94a
--- /dev/null
+++ b/core/SmartyPlugins/modifier.sumtime.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: modifier.sumtime.php 444 2008-04-11 13:38:22Z johmathe $
+ * 
+ * @package SmartyPlugins
+ */
+
+/**
+ * Returns a string that displays the number of days and hours from a number of seconds
+ * 
+ * How to use:
+ * {4200|sumtime} will display '1h 10min'
+ * 
+ * Examples:
+ * - 10 gives "10s"
+ * - 4200 gives "1h 10min"
+ * - 86400 gives "1 day"
+ * - 90600 gives "1 day 1h" (it is exactly 1day 1h 10min but we truncate)
+ * 
+ * @return string
+ * 
+ */
+function smarty_modifier_sumtime($string)
+{
+	$seconds = (double)$string;
+	$days = floor($seconds / 86400);
+	
+	$minusDays = $seconds - $days * 86400;
+	$hours = floor($minusDays / 3600);
+	
+	$minusDaysAndHours = $minusDays - $hours * 3600;
+	$minutes = floor($minusDaysAndHours / 60 );
+	
+	$minusDaysAndHoursAndMinutes = $minusDaysAndHours - $minutes * 60;
+	$secondsMod = $minusDaysAndHoursAndMinutes; // should be same as $seconds % 60 
+	
+	if($days > 0)
+	{
+		return sprintf("%d days %d hours", $days, $hours);
+	}
+	elseif($hours > 0)
+	{
+		return sprintf("%d hours %d min", $hours, $minutes);
+	}
+	else
+	{
+		return sprintf("%d min %d s", $minutes, $seconds);		
+	}
+}
+
diff --git a/core/SmartyPlugins/modifier.translate.php b/core/SmartyPlugins/modifier.translate.php
new file mode 100644
index 0000000000..9942995f66
--- /dev/null
+++ b/core/SmartyPlugins/modifier.translate.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: modifier.sumtime.php 168 2008-01-14 05:26:43Z matt $
+ * 
+ * @package SmartyPlugins
+ */
+
+/**
+ * Read the translation string from the given index (read form the selected language in Piwik).
+ * The translations strings are located either in /lang/xx.php or within the plugin lang directory.
+ * 
+ * Example:
+ *  {'General_Unknown'|translate} will be translated as 'Unknown' (see the entry in /lang/en.php)
+ * 
+ * @return string The translated string
+ */
+function smarty_modifier_translate($string)
+{
+	if(func_num_args() <= 1)
+	{
+		$aValues = array();
+	}
+	else
+	{
+		$aValues = func_get_args();
+		array_shift($aValues);
+	}
+	return vsprintf(Piwik_Translate($string), $aValues);
+}
+ 
\ No newline at end of file
diff --git a/core/SmartyPlugins/modifier.urlRewriteBasicView.php b/core/SmartyPlugins/modifier.urlRewriteBasicView.php
new file mode 100644
index 0000000000..949f47059b
--- /dev/null
+++ b/core/SmartyPlugins/modifier.urlRewriteBasicView.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: modifier.sumtime.php 168 2008-01-14 05:26:43Z matt $
+ * 
+ * @package SmartyPlugins
+ */
+
+/**
+ * Rewrites the given URL so that it looks like a URL that can be loaded directly.
+ * Useful for users who don't handle javascript / ajax, they can still use piwik with these rewritten URLs.
+ * 
+ * @return string
+ */
+function smarty_modifier_urlRewriteBasicView($parameters)
+{
+	// replace module=X by moduleToLoad=X
+	// replace action=Y by actionToLoad=Y
+	$parameters['moduleToLoad'] = $parameters['module'];
+	unset($parameters['module']);
+	
+	if(isset( $parameters['action']))
+	{
+		$parameters['actionToLoad'] = $parameters['action'];
+		unset($parameters['action']);
+	}
+	else
+	{
+		$parameters['actionToLoad'] = null;
+	}
+	$url = Piwik_Url::getCurrentQueryStringWithParametersModified($parameters);
+	
+	// add module=CoreHome&action=showInContext
+	$url = $url . '&amp;module=CoreHome&amp;action=showInContext';
+	return htmlspecialchars($url);
+}
+
diff --git a/core/SmartyPlugins/modifier.urlRewriteWithParameters.php b/core/SmartyPlugins/modifier.urlRewriteWithParameters.php
new file mode 100644
index 0000000000..e810b8200f
--- /dev/null
+++ b/core/SmartyPlugins/modifier.urlRewriteWithParameters.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: modifier.sumtime.php 168 2008-01-14 05:26:43Z matt $
+ * 
+ * @package SmartyPlugins
+ */
+
+/**
+ * Rewrites the given URL and modify the given parameters.
+ * @see Piwik_Url::getCurrentQueryStringWithParametersModified()
+ * 
+ * @return string
+ */
+function smarty_modifier_urlRewriteWithParameters($parameters)
+{
+	$url = Piwik_Url::getCurrentQueryStringWithParametersModified($parameters);
+	return htmlspecialchars($url);
+}
+
diff --git a/core/TablePartitioning.php b/core/TablePartitioning.php
new file mode 100644
index 0000000000..eda912f995
--- /dev/null
+++ b/core/TablePartitioning.php
@@ -0,0 +1,132 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: TablePartitioning.php 386 2008-03-18 19:27:54Z julien $
+ * 
+ * @package Piwik
+ */
+
+/**
+ * 
+ * NB: When a new table is partitionned using this class, we have to update the method
+ *     Piwik::getTablesInstalled() to add the new table to the list of tablename_* to fetch
+ * 
+ * @package Piwik
+ */
+abstract class Piwik_TablePartitioning
+{
+	protected $tableName = null;
+	protected $generatedTableName = null;
+	protected $timestamp = null;
+	
+	static public $tablesAlreadyInstalled = null;
+	
+	public function __construct( $tableName )
+	{
+		$this->tableName = $tableName;
+	}
+	
+	abstract protected function generateTableName() ;
+	
+	
+	public function setTimestamp( $timestamp )
+	{
+		$this->timestamp = $timestamp;
+		$this->generatedTableName = null;
+		$this->getTableName();
+	}
+		
+	public function getTableName()
+	{
+		// table name already processed
+		if(!is_null($this->generatedTableName))
+		{
+			return $this->generatedTableName;
+		}
+		
+		if(is_null($this->timestamp))
+		{
+			throw new Exception("You have to specify a timestamp for a Table Partitioning by date.");
+		}
+		
+		// generate table name
+		$this->generatedTableName = $this->generateTableName();
+		 
+		// we make sure the table already exists
+		$this->checkTableExists();
+	}
+	
+	protected function checkTableExists()
+	{
+		if(is_null(self::$tablesAlreadyInstalled))
+		{
+			self::$tablesAlreadyInstalled = Piwik::getTablesInstalled( $forceReload = false );
+		}
+		
+		if(!in_array($this->generatedTableName, self::$tablesAlreadyInstalled))
+		{
+			$db = Zend_Registry::get('db');
+			$sql = Piwik::getTableCreateSql($this->tableName);
+			
+			$config = Zend_Registry::get('config');
+			$prefixTables = $config->database->tables_prefix;
+			$sql = str_replace( $prefixTables . $this->tableName, $this->generatedTableName, $sql);
+			
+			$db->query( $sql );
+			
+			self::$tablesAlreadyInstalled[] = $this->generatedTableName;
+		}
+	}
+	
+	protected function __toString()
+	{
+		return $this->getTableName();
+	}
+}
+
+/**
+ * 
+ * @package Piwik
+ */
+class Piwik_TablePartitioning_Monthly extends Piwik_TablePartitioning
+{
+	public function __construct( $tableName )
+	{
+		parent::__construct($tableName);
+	}
+	protected function generateTableName()
+	{
+		$config = Zend_Registry::get('config');
+		$prefixTables = $config->database->tables_prefix;
+		
+		$date = date("Y_m", $this->timestamp);
+		
+		return $prefixTables . $this->tableName . "_" . $date;
+	}
+		
+}
+/**
+ * 
+ * @package Piwik
+ */
+class Piwik_TablePartitioning_Daily extends Piwik_TablePartitioning
+{
+	public function __construct( $tableName )
+	{
+		parent::__construct($tableName);
+	}
+	protected function generateTableName()
+	{
+		$config = Zend_Registry::get('config');
+		$prefixTables = $config->database->tables_prefix;
+		
+		$date = date("Y_m_d", $this->timestamp);
+		
+		return $prefixTables . $this->tableName . "_" . $date;
+	}
+		
+}
+
diff --git a/core/Timer.php b/core/Timer.php
new file mode 100644
index 0000000000..8e71b41ba5
--- /dev/null
+++ b/core/Timer.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Timer.php 444 2008-04-11 13:38:22Z johmathe $
+ * 
+ * @package Piwik_Helper
+ */
+
+/**
+ * 
+ * @package Piwik_Helper
+ */
+class Piwik_Timer
+{
+	private $m_Start;
+
+	public function __construct()
+	{
+		$this->m_Start = 0.0;
+		$this->init();
+	}
+
+	private function getMicrotime()
+	{
+		list($micro_seconds, $seconds) = explode(" ", microtime());
+		return ((float)$micro_seconds + (float)$seconds);
+	}
+
+	public function init()
+	{
+		$this->m_Start = $this->getMicrotime();
+	}
+
+	public function getTime($decimals = 2)
+	{
+		return number_format($this->getMicrotime() - $this->m_Start, $decimals, '.', '');
+	}
+	public function getTimeMs($decimals = 2)
+	{
+		return number_format(1000*($this->getMicrotime() - $this->m_Start), $decimals, '.', '');
+	}
+
+	public function __toString()
+	{
+		return "Time elapsed: ". $this->getTime() ."s";
+	}
+}
diff --git a/core/Translate.php b/core/Translate.php
new file mode 100644
index 0000000000..7f4ea56583
--- /dev/null
+++ b/core/Translate.php
@@ -0,0 +1,155 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Translate.php 526 2008-06-25 23:57:04Z matt $
+ * 
+ * @package Piwik
+ */
+
+/**
+ * @package Piwik
+ */
+class Piwik_Translate
+{
+	static private $instance = null;
+	
+	/**
+	 * Returns singleton
+	 *
+	 * @return Piwik_Translate
+	 */
+	static public function getInstance()
+	{
+		if (self::$instance == null)
+		{			
+			$c = __CLASS__;
+			self::$instance = new $c();
+		}
+		return self::$instance;
+	}
+	
+	private function __construct()
+	{
+		$translations = array();
+		
+		$language = $this->getFallbackLanguageToLoad();
+		require_once "lang/" . $language .".php";
+		$this->addTranslationArray($translations);
+		
+		$language = $this->getLanguageToLoad();
+		require_once "lang/" . $language .".php";
+		$this->addTranslationArray($translations);
+		
+		setlocale(LC_ALL, $GLOBALS['Piwik_translations']['General_Locale']);
+	}
+	
+	public function addTranslationArray($translation)
+	{
+		if(!isset($GLOBALS['Piwik_translations']))
+		{
+			$GLOBALS['Piwik_translations'] = array();
+		}
+		// we could check that no string overlap here
+		$GLOBALS['Piwik_translations'] = array_merge($GLOBALS['Piwik_translations'], $translation);
+	}
+	
+	/**
+	 * @return string the language filename prefix, eg "en" for english
+	 * @throws exception if the language set in the config file is not a valid filename
+	 */
+	public function getLanguageToLoad()
+	{
+		$language = Zend_Registry::get('config')->Language->current;
+		
+		if( Piwik_Common::isValidFilename($language))
+		{
+			return $language;
+		}
+		else
+		{
+			throw new Exception("The language selected ('$language') is not a valid language file ");
+		}
+	}
+	
+	protected function getFallbackLanguageToLoad()
+	{
+		return Zend_Registry::get('config')->Language->fallback;
+	}
+	
+	/**
+	 * Generate javascript translations array
+	 * 
+	 * @return string containing javascript code with translations array (including <script> tag)
+	 *
+	 */
+	public function getJavascriptTranslations($moduleList)
+	{
+		if( !$moduleList )
+		{
+			return '';
+		}
+		
+		$js = 'var translations = {';
+					
+		$moduleRegex = '#^(';
+		foreach($moduleList as $module)
+		{
+			$moduleRegex .= $module.'|'; 
+		}
+		$moduleRegex = substr($moduleRegex, 0, -1);
+		$moduleRegex .= ')_([^_]+)_js$#i';
+		
+		foreach($GLOBALS['Piwik_translations'] as $key => $value)
+		{
+			$matches = array();
+			
+			if( preg_match($moduleRegex,$key,$matches) ) {
+				$varName = $matches[1].'_'.$matches[2];
+				$varValue = $value;
+				
+				$js .= "".$varName.": '".str_replace("'","\\'",$varValue)."',";
+			}
+			
+			$matches = null;
+		}
+		$js = substr($js,0,-1);
+		$js .= '};';
+		$js .= 'function _pk_translate(tvar, str) { '.
+			'var s = str; if( typeof(translations[tvar]) != \'undefined\' ) s = translations[tvar];'.
+			'return s;}';
+		
+		return $js;
+	}
+}
+
+function Piwik_Translate($index)
+{
+	if(isset($GLOBALS['Piwik_translations'][$index]))
+	{
+		return $GLOBALS['Piwik_translations'][$index];
+	}
+	throw new Exception("Translation string '$index' not available.");
+}
+
+
+/**
+ * Returns translated string or given message if translation is not found.
+ * This function does not throw any exception. Use it to translate exceptions.
+ *
+ * @param string Translation string index
+ * @return string
+ */
+function Piwik_TranslateException($message)
+{
+	try {
+		return Piwik_Translate($message);		
+	}
+	catch(Exception $e) {
+		return $message;
+	}
+}
+
+
diff --git a/core/Url.php b/core/Url.php
new file mode 100644
index 0000000000..f6ece7c37d
--- /dev/null
+++ b/core/Url.php
@@ -0,0 +1,159 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Url.php 498 2008-05-29 03:08:13Z matt $
+ * 
+ * @package Piwik_Helper
+ */
+
+/**
+ * @package Piwik_Helper
+ *
+ */
+class Piwik_Url 
+{
+	static function getArrayFromCurrentQueryString()
+	{	
+		$queryString = Piwik_Url::getCurrentQueryString();
+		$queryString = htmlspecialchars($queryString);
+		$urlValues = Piwik_Common::getArrayFromQueryString($queryString);
+		return $urlValues;
+	}
+	
+	static function getCurrentQueryStringWithParametersModified( $params )
+	{
+		$urlValues = self::getArrayFromCurrentQueryString();
+
+		foreach($params as $key => $value)
+		{
+			$urlValues[$key] = $value;
+		}
+		
+		$query = http_build_query($urlValues, "", "&");
+		
+		if(strlen($query) > 0)
+		{
+			return '?'.$query;
+		}
+		else
+		{
+			return '';
+		}
+	}
+	
+	static public function redirectToUrl( $url )
+	{
+		header("Location: $url");
+		exit;
+	}
+	
+	static public function getReferer()
+	{
+		if(!empty($_SERVER['HTTP_REFERER']))
+		{
+			return $_SERVER['HTTP_REFERER'];
+		}
+		return false;
+	}
+
+	static public function getCurrentUrl()
+	{
+		return	self::getCurrentHost()
+				. self::getCurrentScriptName() 
+				. self::getCurrentQueryString();
+	}
+	
+	static public function getCurrentUrlWithoutQueryString()
+	{
+		
+		return	self::getCurrentHost()
+				. self::getCurrentScriptName() ;
+	}
+	
+	/**
+	 * Ending with /
+	 */
+	static public function getCurrentUrlWithoutFileName()
+	{
+		
+		$host = self::getCurrentHost();
+		$queryString = self::getCurrentScriptName() ;
+		
+		//add a fake letter case /test/test2/ returns /test which is not expected
+		$urlDir = dirname ($queryString . 'x');
+		// if we are in a subpath we add a trailing slash
+		if(strlen($urlDir) > 1)
+		{
+			$urlDir .= '/';
+		}
+		return $host.$urlDir;
+	}
+	
+	static public function getCurrentScriptName()
+	{
+		$url = '';
+		if( !empty($_SERVER['PATH_INFO']) ) 
+		{ 
+			$url = $_SERVER['PATH_INFO'];
+		} 
+		else if( !empty($_SERVER['REQUEST_URI']) ) 
+		{
+			if( ($pos = strpos($_SERVER['REQUEST_URI'], "?")) !== false ) 
+			{
+				$url = substr($_SERVER['REQUEST_URI'], 0, $pos);
+			} 
+			else 
+			{
+				$url = $_SERVER['REQUEST_URI'];
+			}
+		} 
+		
+		if(empty($url))
+		{
+			$url = $_SERVER['SCRIPT_NAME'];
+		}
+		return $url;
+	}
+	
+	static public function getCurrentHost()
+	{
+		if(isset($_SERVER['HTTPS'])
+			&& ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true)
+			)
+		{
+			$url = 'https';
+		}
+		else
+		{
+			$url = 'http';
+		}
+		
+		$url .= '://';
+		
+		if(isset($_SERVER['HTTP_HOST']))
+		{
+			$url .= $_SERVER['HTTP_HOST'];
+		}
+		else
+		{
+			$url .= 'unknown';
+		}
+		return $url;
+	}
+		
+	
+	static public function getCurrentQueryString()
+	{
+		$url = '';	
+		if(isset($_SERVER['QUERY_STRING'])
+			&& !empty($_SERVER['QUERY_STRING']))
+		{
+			$url .= "?".$_SERVER['QUERY_STRING'];
+		}
+		return $url;
+	}
+}
+
diff --git a/core/View.php b/core/View.php
new file mode 100644
index 0000000000..88debb9a64
--- /dev/null
+++ b/core/View.php
@@ -0,0 +1,143 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: View.php 450 2008-04-20 22:33:27Z matt $
+ * 
+ * @package Piwik_Visualization
+ */
+
+require_once 'Smarty/Smarty.class.php';
+
+require_once "iView.php";
+
+/**
+ * 
+ * @package Piwik_Visualization
+ *
+ */
+class Piwik_View implements Piwik_iView
+{
+	private $template = '';
+	private $smarty = false;
+	private $variables = array();
+	
+	public function __construct( $templateFile, $smConf = array())
+	{
+		$this->template = $templateFile;
+		$this->smarty = new Smarty();
+
+		if(count($smConf) == 0)
+		{
+			$smConf = Zend_Registry::get('config')->smarty;
+		}
+		foreach($smConf as $key => $value)
+		{
+			$this->smarty->$key = $value;
+		}
+		
+		$this->smarty->template_dir = $smConf->template_dir->toArray();
+		$this->smarty->plugins_dir = $smConf->plugins_dir->toArray();
+		$this->smarty->compile_dir = $smConf->compile_dir;
+		$this->smarty->cache_dir = $smConf->cache_dir;
+		
+		$this->smarty->load_filter('output','trimwhitespace');
+		
+		// global value accessible to all templates: the piwik base URL for the current request
+		$this->piwikUrl = Piwik_Url::getCurrentUrlWithoutFileName();
+		
+	}
+	
+	/**
+	 * Directly assigns a variable to the view script.
+	 * VAR names may not be prefixed with '_'.
+	 *	@param string $key The variable name.
+	 *	@param mixed $val The variable value.
+	 *	@return void
+	 */
+	public function __set($key, $val)
+	{
+		$this->smarty->assign($key, $val);
+	}
+
+	/**
+	 * Retrieves an assigned variable.
+	 * VAR names may not be prefixed with '_'.
+	 *	@param string $key The variable name.
+	 *	@return mixed The variable value.
+	 */
+	public function __get($key)
+	{
+		return $this->smarty->get_template_vars($key);
+	}
+
+	public function render()
+	{
+		try {
+			$this->currentModule = Piwik::getModule();
+			$this->currentPluginName = Piwik::getCurrentPlugin()->getName();
+			$this->userLogin = Piwik::getCurrentUserLogin();
+			$this->sites = Piwik_SitesManager_API::getSitesWithAtLeastViewAccess();
+			$this->url = Piwik_Url::getCurrentUrl();
+			$this->token_auth = Piwik::getCurrentUserTokenAuth();
+		} catch(Exception $e) {
+			// can fail, for example at installation (no plugin loaded yet)		
+		}
+		
+		$this->totalTimeGeneration = Zend_Registry::get('timer')->getTime();
+		try {
+			$this->totalNumberOfQueries = Piwik::getQueryCount();
+		}
+		catch(Exception $e){
+			$this->totalNumberOfQueries = 0;
+		}
+		header('Content-Type: text/html; charset=utf-8');
+		return $this->smarty->fetch($this->template);
+	}
+	
+	public function addForm( $form )
+	{
+		// Create the renderer object	
+		$renderer = new HTML_QuickForm_Renderer_ArraySmarty($this->smarty);
+		
+		// build the HTML for the form
+		$form->accept($renderer);
+		
+		// assign array with form data
+		$this->smarty->assign('form_data', $renderer->toArray());
+		$this->smarty->assign('element_list', $form->getElementList());
+	}
+	
+	public function assign($var, $value=null)
+	{
+		if (is_string($var))
+		{
+			$this->smarty->assign($var, $value);
+		}
+		elseif (is_array($var))
+		{
+			foreach ($var as $key => $value)
+			{
+				$this->smarty->assign($key, $value);
+			}
+		}
+	}
+
+/*	public function isCached($template)
+	{
+		if ($this->smarty->is_cached($template))
+		{
+			return true;
+		}
+		return false;
+	}
+
+
+	public function setCaching($caching)
+	{
+		$this->smarty->caching = $caching;
+	}
+*/
+}
\ No newline at end of file
diff --git a/core/ViewDataTable.php b/core/ViewDataTable.php
new file mode 100644
index 0000000000..525b645dab
--- /dev/null
+++ b/core/ViewDataTable.php
@@ -0,0 +1,799 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: ViewDataTable.php 581 2008-07-27 23:07:52Z matt $
+ * 
+ * @package Piwik_ViewDataTable
+ */
+
+require_once "API/Request.php";
+
+/**
+ * This class is used to load (from the API) and customize the output of a given DataTable.
+ * The main() method will create an object Piwik_iView
+ * You can customize the dataTable using the disable* methods.
+ * 
+ * Example:
+ * In the Controller of the plugin VisitorInterest
+ * <pre>
+ * 	function getNumberOfVisitsPerVisitDuration( $fetch = false)
+ *  {
+ * 		$view = Piwik_ViewDataTable::factory( 'cloud' );
+ * 		$view->init( $this->pluginName,  __FUNCTION__, 'VisitorInterest.getNumberOfVisitsPerVisitDuration' );
+ * 		$view->setColumnsToDisplay( array('label','nb_visits') );
+ * 		$view->disableSort();
+ * 		$view->disableExcludeLowPopulation();
+ * 		$view->disableOffsetInformation();
+ * 
+ *		return $this->renderView($view, $fetch);
+ * 	} 
+ * </pre>
+ * 
+ * @see factory() for all the available output (cloud tags, html table, pie chart, vertical bar chart)
+ * @package Piwik_ViewDataTable
+ *
+ */
+
+abstract class Piwik_ViewDataTable
+{
+	/**
+	 * Template file that will be loaded for this view.
+	 * Usually set in the Piwik_ViewDataTable_*
+	 *
+	 * @var string eg. 'CoreHome/templates/cloud.tpl'
+	 */
+	protected $dataTableTemplate = null;
+	
+	/**
+	 * Flag used to make sure the main() is only executed once
+	 *
+	 * @var bool
+	 */
+	protected $mainAlreadyExecuted = false;
+	
+	/**
+	 * Defines if we display the search box under the table
+	 * 
+	 * @see disableSearchBox()
+	 * @see getSearchBox()
+	 *
+	 * @var bool
+	 */
+	protected $JSsearchBox 				= true;
+	
+	/**
+	 * Defines if we display the "X-Y of Z" under the table
+	 * 
+	 * @see disableOffsetInformation()
+	 * @see getOffsetInformation()
+	 *
+	 * @var bool
+	 */
+	protected $JSoffsetInformation 		= true;
+	
+	/**
+	 * Defines if we display the "Include all population" link under the table
+	 * 
+	 * @see disableExcludeLowPopulation()
+	 * @see getExcludeLowPopulation() 
+	 *
+	 * @var bool
+	 */
+	protected $JSexcludeLowPopulation 	= true;
+	
+	/**
+	 * Defines if we include the footer after the dataTable output.
+	 * The footer contains all the extra features like the search box, the links Next/Previous, the icons to export in several formats, etc.
+	 * Not showing the footer is useful for example when you want to only display a graph without anything else.
+	 * 
+	 * @see doNotShowFooter()
+	 * @see getShowFooter() 
+	 *
+	 * @var bool
+	 */
+	protected $showFooter				= true;
+	
+	/**
+	 * Contains the values set for the parameters
+	 * @see getJavascriptVariablesToSet()
+	 *
+	 * @var array
+	 */
+	protected $variablesDefault = array();
+	
+	/**
+	 * If the current dataTable refers to a subDataTable (eg. keywordsBySearchEngineId for id=X) this variable is set to the Id
+	 *
+	 * @var bool|int
+	 */
+	protected $idSubtable = false;
+	
+	/**
+	 * Set to true when the DataTable must be loaded along with all its children subtables
+	 * Useful when searching for a pattern in the DataTable Actions (we display the full hierarchy)
+	 * 
+	 * @var bool
+	 */
+	protected $recursiveDataTableLoad   = false;
+	
+	/**
+	 * DataTable loaded from the API for this ViewDataTable.
+	 *  
+	 * @var Piwik_DataTable
+	 */
+	protected $dataTable = null; 
+		
+	/**
+	 * @see init()
+	 *
+	 * @var string
+	 */
+	protected $currentControllerAction;
+	
+	/**
+	 * @see init()
+	 *
+	 * @var string
+	 */
+	protected $currentControllerName;
+	
+	/**
+	 * @see init()
+	 *
+	 * @var string
+	 */
+	protected $actionToLoadTheSubTable = null;
+	
+	/**
+	 * @see init()
+	 *
+	 * @var string
+	 */
+	protected $moduleNameAndMethod;
+	
+	/**
+	 * This view should be an implementation of the Interface Piwik_iView
+	 * The $view object should be created in the main() method.
+	 * 
+	 * @var Piwik_iView
+	 */
+	protected $view = null;
+	
+	/**
+	 * Method to be implemented by the ViewDataTable_*.
+	 * This method should create and initialize a $this->view object @see Piwik_iView
+	 * 
+	 * @return mixed either prints the result or returns the output string
+	 */
+	abstract public function main();
+	
+	/**
+	 * Returns a Piwik_ViewDataTable_* object.
+	 * By default it will return a ViewDataTable_Html
+	 * If there is a viewDataTable parameter in the URL, a ViewDataTable of this 'viewDataTable' type will be returned.
+	 * If defaultType is specified and if there is no 'viewDataTable' in the URL, a ViewDataTable of this $defaultType will be returned.
+	 * If force is set to true, a ViewDataTable of the $defaultType will be returned in all cases.
+	 * 
+	 * @param string defaultType Any of these: table, cloud, graphPie, graphVerticalBar, graphEvolution, sparkline, generateDataChart* 
+	 * @force bool If set to true, returns a ViewDataTable of the $defaultType
+	 * 
+	 * @return Piwik_ViewDataTable 
+	 */
+	static public function factory( $defaultType = null, $force = false)
+	{
+		if(is_null($defaultType))
+		{
+			$defaultType = 'table';	
+		}
+		
+		if($force === true)
+		{
+			$type = $defaultType;
+		}
+		else
+		{
+			$type = Piwik_Common::getRequestVar('viewDataTable', $defaultType, 'string');
+		}
+		
+		switch($type)
+		{
+			case 'cloud':
+				require_once "ViewDataTable/Cloud.php";
+				return new Piwik_ViewDataTable_Cloud();			
+			break;
+			
+			case 'graphPie':
+				require_once "ViewDataTable/GenerateGraphHTML/ChartPie.php";
+				return new Piwik_ViewDataTable_GenerateGraphHTML_ChartPie();
+			break;			
+			
+			case 'graphVerticalBar':
+				require_once "ViewDataTable/GenerateGraphHTML/ChartVerticalBar.php";
+				return new Piwik_ViewDataTable_GenerateGraphHTML_ChartVerticalBar();
+			break;	
+			
+			case 'graphEvolution':
+				require_once "ViewDataTable/GenerateGraphHTML/ChartEvolution.php";
+				return new Piwik_ViewDataTable_GenerateGraphHTML_ChartEvolution();
+			break;	
+			
+			case 'sparkline':
+				require_once "ViewDataTable/Sparkline.php";
+				return new Piwik_ViewDataTable_Sparkline();
+			break;	
+			
+			case 'generateDataChartVerticalBar':
+				require_once "ViewDataTable/GenerateGraphData/ChartVerticalBar.php";
+				return new Piwik_ViewDataTable_GenerateGraphData_ChartVerticalBar();
+			break;
+						
+			case 'generateDataChartPie':
+				require_once "ViewDataTable/GenerateGraphData/ChartPie.php";
+				return new Piwik_ViewDataTable_GenerateGraphData_ChartPie();
+			break;
+			
+			case 'generateDataChartEvolution':
+				require_once "ViewDataTable/GenerateGraphData/ChartEvolution.php";
+				return new Piwik_ViewDataTable_GenerateGraphData_ChartEvolution();
+				
+			break;
+				
+			case 'table':
+			default:
+				require_once "ViewDataTable/Html.php";
+				return new Piwik_ViewDataTable_Html();
+			break;
+		}
+	}
+	
+	/**
+	 * Inits the object given the $currentControllerName, $currentControllerAction of 
+	 * the calling controller action, eg. 'Referers' 'getLongListOfKeywords'.
+	 * The initialization also requires the $moduleNameAndMethod of the API method 
+	 * to call in order to get the DataTable, eg. 'Referers.getKeywords'.
+	 * The optional $actionToLoadTheSubTable defines the method name of the API to call when there is a idSubtable.
+	 * This value would be used by the javascript code building the GET request to the API.
+	 * 
+	 * Example: 
+	 * 	For the keywords listing, a click on the row loads the subTable of the Search Engines for this row.
+	 *  In this case $actionToLoadTheSubTable = 'getSearchEnginesFromKeywordId'.
+	 *  The GET request will hit 'Referers.getSearchEnginesFromKeywordId'.
+	 *
+	 * @param string $currentControllerName eg. 'Referers'
+	 * @param string $currentControllerAction eg. 'getKeywords'
+	 * @param string $moduleNameAndMethod eg. 'Referers.getKeywords'
+	 * @param string $actionToLoadTheSubTable eg. 'getSearchEnginesFromKeywordId'
+	 * 
+	 * @return void
+	 */
+	public function init( $currentControllerName,
+						$currentControllerAction, 
+						$moduleNameAndMethod, 
+						$actionToLoadTheSubTable = null)
+	{
+		$this->currentControllerName = $currentControllerName;
+		$this->currentControllerAction = $currentControllerAction;
+		$this->moduleNameAndMethod = $moduleNameAndMethod;
+		$this->actionToLoadTheSubTable = $actionToLoadTheSubTable;
+		
+		$this->idSubtable = Piwik_Common::getRequestVar('idSubtable', false, 'int');
+		
+		$this->method = $moduleNameAndMethod;
+		
+		$this->JSsearchBox = Piwik_Common::getRequestVar('show_search', true);
+		$this->showFooter = Piwik_Common::getRequestVar('showDataTableFooter', true);
+		$this->variablesDefault['filter_excludelowpop_default'] = 'false';
+		$this->variablesDefault['filter_excludelowpop_value_default'] = 'false';	
+	}
+	
+	/**
+	 * Forces the View to use a given template.
+	 * Usually the template to use is set in the specific ViewDataTable_* 
+	 * eg. 'CoreHome/templates/cloud.tpl'
+	 *
+	 * But some users may want to force this template to some other value
+	 * 
+	 * @param string $tpl eg .'MyPlugin/templates/templateToUse.tpl'
+	 */
+	public function setTemplate( $tpl )
+	{
+		$this->dataTableTemplate = $tpl;
+	}
+		
+	/**
+	 * Returns the iView.
+	 * You can then call render() on this object.
+	 *
+	 * @return Piwik_iView
+	 * @throws exception if the view object was not created
+	 */
+	public function getView()
+	{
+		if(is_null($this->view))
+		{
+			throw new Exception('The $this->view object has not been created. 
+					It should be created in the main() method of the Piwik_ViewDataTable_* subclass you are using.');
+		}
+		return $this->view;
+	}
+
+	/**
+	 * Returns the DataTable loaded from the API
+	 *
+	 * @return Piwik_DataTable
+	 * @throws exception if not yet defined
+	 */
+	public function getDataTable()
+	{
+		if(is_null($this->dataTable))
+		{
+			throw new Exception("The DataTable requested has not been loaded yet.");
+		}
+		return $this->dataTable;
+	}
+	/**
+	 * Function called by the ViewDataTable objects in order to fetch data from the API.
+	 * The function init() must have been called before, so that the object knows which API module and action to call.
+	 * It builds the API request string and uses Piwik_API_Request to call the API.
+	 * The requested Piwik_DataTable object is stored in $this->dataTable.
+	 * 
+	 * @return void
+	 */
+	protected function loadDataTableFromAPI()
+	{		
+		// we prepare the string to give to the API Request
+		// we setup the method and format variable
+		// - we request the method to call to get this specific DataTable
+		// - the format = original specifies that we want to get the original DataTable structure itself, not rendered
+		$requestString = 'method='.$this->moduleNameAndMethod
+						.'&format=original'
+					;
+		if( $this->recursiveDataTableLoad )
+		{
+			$requestString .= '&expanded=1';
+		}
+		
+		$toSetEventually = array(
+			'filter_limit',
+			'filter_sort_column',
+			'filter_sort_order',
+			'filter_excludelowpop',
+			'filter_excludelowpop_value',
+			'filter_column', 
+			'filter_pattern',
+			'disable_generic_filters',
+			'disable_queued_filters',
+		);
+		foreach($toSetEventually as $varToSet)
+		{
+			$value = $this->getDefaultOrCurrent($varToSet);
+			if( false !== $value )
+			{
+				$requestString .= '&'.$varToSet.'='.$value;
+			}
+		}
+		
+		// We finally make the request to the API
+		$request = new Piwik_API_Request($requestString);
+		
+		// and get the DataTable structure
+		$dataTable = $request->process();
+
+		$this->dataTable = $dataTable;
+	}
+	
+	
+	/**
+	 * For convenience, the client code can call methods that are defined in a specific children class
+	 * without testing the children class type, which would trigger an error with a different children class.
+	 * 
+	 * Example:
+	 *  ViewDataTable/Html.php defines a setColumnsToDisplay(). The client code calls this methods even if
+	 *  the ViewDataTable object is a ViewDataTable_Cloud instance (he doesn't know because of the factory()). 
+	 *  But ViewDataTable_Cloud doesn't define the setColumnsToDisplay() method. 
+	 *  Because we don't want to force users to test for the object type we simply catch these
+	 *  calls when they are not defined in the child and do nothing.  
+	 *
+	 * @param string $function
+	 * @param array $args
+	 */
+	public function __call($function, $args)
+	{
+	}
+	
+	/**
+	 * Returns a unique ID for this ViewDataTable.
+	 * This unique ID is used in the Javascript code: 
+	 *  Any ajax loaded data is loaded within a DIV that has id=$unique_id 
+	 *  The jquery code then replaces the existing html div id=$unique_id in the code with this data.
+	 * 
+	 * @see datatable.js
+	 * @return string
+	 */
+	protected function getUniqIdTable()
+	{
+		// if we request a subDataTable the $this->currentControllerAction DIV ID is already there in the page
+		// we make the DIV ID really unique by appending the ID of the subtable requested
+		if( $this->idSubtable != 0 // parent DIV has a idSubtable = 0 but the html DIV must have the name of the module.action
+			&&  $this->idSubtable !== false // case there is no idSubtable 
+			)
+		{
+			// see also datatable.js (the ID has to match with the html ID created to be replaced by the result of the ajax call)
+			$uniqIdTable = 'subDataTable_' . $this->idSubtable;
+		}
+		else
+		{
+			// the $uniqIdTable variable is used as the DIV ID in the rendered HTML
+			// we use the current Controller action name as it is supposed to be unique in the rendered page 
+			$uniqIdTable = $this->currentControllerName . $this->currentControllerAction;
+		}
+		return $uniqIdTable;
+	}
+	
+	/**
+	 * This functions reads the customization values for the DataTable and returns an array (name,value) to be printed in Javascript.
+	 * This array defines things such as:
+	 * - name of the module & action to call to request data for this table
+	 * - display the search box under the table
+	 * - display the links Next & Previous under the table
+	 * - optional filters information, eg. filter_limit and filter_offset
+	 * - etc.
+	 *
+	 * The values are loaded:
+	 * - from the generic filters that are applied by default @see Piwik_API_Request::getGenericFiltersInformation()
+	 * - from the values already available in the GET array
+	 * - from the values set using methods from this class (eg. setSearchPattern(), setLimit(), etc.)
+	 * 
+	 * @return array eg. array('show_offset_information' => 0, 'show_
+	 */
+	protected function getJavascriptVariablesToSet()
+	{
+		// build javascript variables to set
+		$javascriptVariablesToSet = array();
+		
+		$genericFilters = Piwik_API_Request::getGenericFiltersInformation();
+		foreach($genericFilters as $filter)
+		{
+			foreach($filter as $filterVariableName => $filterInfo)
+			{
+				// if there is a default value for this filter variable we set it 
+				// so that it is propagated to the javascript
+				if(isset($filterInfo[1]))
+				{
+					$javascriptVariablesToSet[$filterVariableName] = $filterInfo[1];
+					
+					// we set the default specified column and Order to sort by
+					// when this javascript variable is not set already
+					// for example during an AJAX call this variable will be set in the URL
+					// so this will not be executed (and the default sorted not be used as the sorted column might have changed in the meanwhile)
+					if( false !== ($defaultValue = $this->getDefault($filterVariableName)))
+					{
+						$javascriptVariablesToSet[$filterVariableName] = $defaultValue;
+					}
+				}
+			}
+		}
+		
+		foreach($_GET as $name => $value)
+		{
+			try{
+				$requestValue = Piwik_Common::getRequestVar($name);
+			}
+			catch(Exception $e) {
+				$requestValue = '';
+			}
+			$javascriptVariablesToSet[$name] = $requestValue;
+		}
+		
+		// at this point there are some filters values we  may have not set, 
+		// case of the filter without default values and parameters set directly in this class
+		// for example setExcludeLowPopulation
+		// we go through all the $this->variablesDefault array and set the variables not set yet
+		foreach($this->variablesDefault as $name => $value)
+		{
+			if(!isset($javascriptVariablesToSet[$name] ))
+			{
+				$javascriptVariablesToSet[$name] = $value;
+			}
+		}
+				
+		$javascriptVariablesToSet['module'] = $this->currentControllerName;
+		$javascriptVariablesToSet['action'] = $this->currentControllerAction;
+		$javascriptVariablesToSet['pathToPiwik'] = Piwik_Url::getCurrentUrlWithoutFileName();
+		
+		if(!is_null($this->actionToLoadTheSubTable))
+		{
+			$javascriptVariablesToSet['actionToLoadTheSubTable'] = $this->actionToLoadTheSubTable;
+		}
+		
+//		var_dump($this->variablesDefault);
+//		var_dump($javascriptVariablesToSet); exit;
+		
+		if($this->dataTable)
+		{
+			$javascriptVariablesToSet['totalRows'] = $this->dataTable->getRowsCountBeforeLimitFilter();
+		}
+		$javascriptVariablesToSet['show_search'] = $this->getSearchBox();
+		$javascriptVariablesToSet['show_offset_information'] = $this->getOffsetInformation();
+		$javascriptVariablesToSet['show_exclude_low_population'] = $this->getExcludeLowPopulation();
+		
+		// we escape the values that will be displayed in the javascript footer of each datatable
+		// to make sure there is malicious code injected (the value are already htmlspecialchar'ed as they
+		// are loaded with Piwik_Common::getRequestVar()
+		foreach($javascriptVariablesToSet as &$value)
+		{
+			$value = addslashes($value);
+		}
+		
+		return $javascriptVariablesToSet;
+	}
+	
+	/**
+	 * Returns, for a given parameter, the value of this parameter in the REQUEST array.
+	 * If not set, returns the default value for this parameter @see getDefault()
+	 *
+	 * @param string $nameVar
+	 * @return string|mixed Value of this parameter
+	 */
+	protected function getDefaultOrCurrent( $nameVar )
+	{
+		if(isset($_REQUEST[$nameVar]))
+		{
+			return $_REQUEST[$nameVar];
+		}
+		$default = $this->getDefault($nameVar);
+		return $default;
+	}
+
+	/**
+	 * Returns the default value for a given parameter.
+	 * For example, these default values can be set using the disable* methods.
+	 * 
+	 * @param string $nameVar
+	 * @return mixed
+	 */
+	protected function getDefault($nameVar)
+	{
+		if(!isset($this->variablesDefault[$nameVar]))
+		{
+			return false;
+		}
+		return $this->variablesDefault[$nameVar];
+	}
+	
+	/**
+	 * The generic filters (limit, offset, sort by visit desc) will not be applied to this datatable.
+	 * 
+	 * @return void
+	 *
+	 */
+	public function disableGenericFilters()
+	{
+		$this->variablesDefault['disable_generic_filters'] = true;
+	}
+	/**
+	 * The "X-Y of Z" won't be displayed under this table
+	 * 
+	 * @return void
+	 *
+	 */
+	public function disableOffsetInformation()
+	{
+		$this->JSoffsetInformation = 'false';		
+	}
+	
+	/**
+	 * @see disableOffsetInformation()
+	 * 
+	 * @return bool|string If this parameter is enabled or not
+	 *
+	 */
+	protected function getOffsetInformation()
+	{
+		return $this->JSoffsetInformation;
+	}
+	
+	/**
+	 * The search box won't be displayed under this table
+	 *
+	 * @return void
+	 */
+	public function disableSearchBox()
+	{
+		$this->JSsearchBox = 'false';
+	}
+	
+	/**
+	 * @see disableSearchBox()
+	 * 
+	 * @return bool|string If this parameter is enabled or not
+	 *
+	 */
+	protected function getSearchBox()
+	{
+		return $this->JSsearchBox;
+	}
+	
+	/**
+	 * When this method is called, the output will not contain the template datatable_footer.tpl
+	 *
+	 * @return void
+	 */
+	public function doNotShowFooter()
+	{
+		$this->showFooter = false;
+	}
+	
+	/**
+	 * Returns true if the footer should be included in the template 
+	 * 
+	 * @return bool
+	 *
+	 */
+	protected function getShowFooter()
+	{
+		return $this->showFooter;
+	}
+	
+	/**
+	 * The "Include low population" link won't be displayed under this table
+	 *
+	 * @return void
+	 */
+	public function disableExcludeLowPopulation()
+	{
+		$this->JSexcludeLowPopulation = 'false';
+	}
+	
+	/**
+	 * @see disableExcludeLowPopulation()
+	 * 
+	 * @return bool|string If this parameter is enabled or not
+	 *
+	 */
+	protected function getExcludeLowPopulation()
+	{
+		return $this->JSexcludeLowPopulation;
+	}
+	
+	
+	/**
+	 * Sets the value to use for the Exclude low population filter.
+	 * 
+	 * @param int|float If a row value is less than this value, it will be removed from the dataTable
+	 * @param string The name of the column for which we compare the value to $minValue
+	 *
+	 * @return void
+	 */
+	public function setExcludeLowPopulation( $minValue = null, $columnName = null )
+	{
+		if( is_null( $minValue) ) 
+		{
+			throw new Exception("setExcludeLowPopulation() value shouldn't be null");
+		}
+		
+		if(is_null($columnName))
+		{
+			$columnName = Piwik_Archive::INDEX_NB_VISITS;
+		}
+		
+		// column to use to enable low population exclusion if != false
+		$this->variablesDefault['filter_excludelowpop_default'] 
+			= $this->variablesDefault['filter_excludelowpop']
+			= $columnName;
+		
+		// the minimum value a row must have to be returned 
+		$this->variablesDefault['filter_excludelowpop_value_default'] 
+			= $this->variablesDefault['filter_excludelowpop_value']
+			= $minValue;	
+	}
+	
+	/**
+	 * Sets the pattern to look for in the table (only rows matching the pattern will be kept)
+	 *
+	 * @param string $pattern to look for
+	 * @param string $column to compare the pattern to
+	 * 
+	 * @return void
+	 */
+	public function setSearchPattern($pattern, $column)
+	{
+		$this->variablesDefault['filter_pattern'] = $pattern;
+		$this->variablesDefault['filter_column'] = $column;
+	}
+
+	/**
+	 * Sets the maximum number of rows of the table
+	 *
+	 * @param int $limit
+	 * 
+	 * @return void
+	 */
+	public function setLimit( $limit )
+	{
+		if($limit != 0)
+		{
+			$this->variablesDefault['filter_limit'] = $limit;
+		}
+	}
+	
+	/**
+	 * Sets the dataTable column to sort by. This sorting will be applied before applying the (offset, limit) filter. 
+	 *
+	 * @param int|string $columnId eg. 'nb_visits' for some tables, or Piwik_Archive::INDEX_NB_VISITS for others
+	 * @param string $order desc or asc
+	 * 
+	 * @return void
+	 */
+	public function setSortedColumn( $columnId, $order = 'desc')
+	{
+		$this->variablesDefault['filter_sort_column']= $columnId;
+		$this->variablesDefault['filter_sort_order']= $order;
+	}
+	
+	
+	/**
+	 * Given a Piwik_DataTable_Array made of DataTable_Simple rows, returns a php array with the structure:
+	 * array(
+	 * 	array( label => X, value => Y),
+	 * 	array( label => A, value => B),
+	 * ...
+	 * )
+	 *
+	 * This is used for example for the evolution graph (last 30 days visits) or the sparklines.
+	 * 
+	 * @param Piwik_DataTable_Array $dataTableArray
+	 * @return array
+	 */
+	protected function generateDataFromDataTableArray( Piwik_DataTable_Array $dataTableArray)
+	{
+		$data = array();
+		foreach($dataTableArray->getArray() as $keyName => $table)
+		{
+			if($table instanceof Piwik_DataTable_Array)
+			{
+				throw new Exception("Operation not supported (yet)");
+			}
+			$value = false;
+			
+			$onlyRow = $table->getFirstRow();
+			if($onlyRow !== false)
+			{
+				$value = $onlyRow->getColumn('value');
+				if($value == false)
+				{
+					// TEMP
+					// quite a hack, useful in the case at this point we do have a normal row with nb_visits, nb_actions, nb_uniq_visitors, etc.
+					// instead of the dataTable_Simple row (label, value) 
+					// to do it properly we'd need to
+					// - create a filter that removes columns
+					// - apply this filter to keep only the column called nb_uniq_visitors
+					// - rename this column as 'value'
+					// and at this point the getcolumn('value') would have worked
+					// this code is executed eg. when displaying a sparkline for the last 30 days displaying the number of unique visitors coming from search engines
+					
+					//TODO solution: use a filter rename column etc.
+					
+					// another solution would be to add a method to the Referers API giving directly the integer 'visits from search engines'
+					// and we would build automatically the dataTable_array of datatatble_simple from these integers
+					// but we'd have to add this integer to be recorded during archiving etc.
+					$value = $onlyRow->getColumn('nb_uniq_visitors');
+				}
+			}
+		
+			if($value === false)
+			{
+				$value = 0;
+			}
+			$data[] = array(
+					'label' => $keyName,
+					'value' => $value
+				);
+		}
+		return $data;
+	}
+	
+}
\ No newline at end of file
diff --git a/core/ViewDataTable/Cloud.php b/core/ViewDataTable/Cloud.php
new file mode 100644
index 0000000000..c6e63bb3f4
--- /dev/null
+++ b/core/ViewDataTable/Cloud.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Cloud.php 581 2008-07-27 23:07:52Z matt $
+ * 
+ * @package Piwik_ViewDataTable
+ */
+
+require_once "Visualization/Cloud.php";
+
+/** 
+ * Reads the requested DataTable from the API, and prepares the data to give 
+ * to Piwik_Visualization_Cloud that will display the tag cloud (via the template cloud.tpl).
+ * 
+ * @package Piwik_ViewDataTable
+ *
+ */
+class Piwik_ViewDataTable_Cloud extends Piwik_ViewDataTable
+{
+	//TODO test this
+	protected $displayLogoInsteadOfLabel = false;
+	
+	/**
+	 * @see Piwik_ViewDataTable::init()
+	 */
+	function init($currentControllerName,
+						$currentControllerAction, 
+						$moduleNameAndMethod )
+	{
+		parent::init($currentControllerName,
+						$currentControllerAction, 
+						$moduleNameAndMethod );
+		$this->dataTableTemplate = 'CoreHome/templates/cloud.tpl';
+		
+		$this->disableOffsetInformation();
+		$this->disableExcludeLowPopulation();
+	}
+	
+	/**
+	 * @see Piwik_ViewDataTable::main()
+	 *
+	 */
+	public function main()
+	{
+		$this->setLimit( 30 );
+		if($this->mainAlreadyExecuted)
+		{
+			return;
+		}
+		$this->mainAlreadyExecuted = true;
+	
+		$this->loadDataTableFromAPI();
+	
+		// We apply a filter to the DataTable, decoding the label column (useful for keywords for example)
+		$filter = new Piwik_DataTable_Filter_ColumnCallbackReplace(
+									$this->dataTable, 
+									'label', 
+									'urldecode'
+								);
+		
+		
+		$view = new Piwik_View($this->dataTableTemplate);
+		
+		$words = $labelMetadata = array();
+		foreach($this->dataTable->getRows() as $row)
+		{
+			$label = $row->getColumn('label');
+			$value = $row->getColumn('nb_uniq_visitors');
+			
+			// case no unique visitors
+			if($value === false)
+			{
+				$value = $row->getColumn('nb_visits');
+			}
+			$words[$label] = $value;
+			
+			$logo = false;
+			if($this->displayLogoInsteadOfLabel)
+			{
+				$logo =  $row->getMetadata('logo');
+			}
+			
+			$labelMetadata[$label] = array( 
+				'logo' => $logo,
+				'url' => $row->getMetadata('url'),
+				'hits' => $value
+				);
+		}
+		$cloud = new Piwik_Visualization_Cloud($words);
+		$cloudValues  = $cloud->render('array');
+		
+		foreach($cloudValues as &$value)
+		{
+			$value['logoWidth'] = round(max(16, $value['percent']));
+		}
+		$view->labelMetadata = $labelMetadata;
+		$view->cloudValues = $cloudValues;
+		
+		$view->method = $this->method;
+		$view->id = $this->getUniqIdTable();
+		$view->javascriptVariablesToSet = $this->getJavascriptVariablesToSet();
+		$view->showFooter = $this->getShowFooter();
+		$this->view = $view;
+	}
+}
diff --git a/core/ViewDataTable/GenerateGraphData.php b/core/ViewDataTable/GenerateGraphData.php
new file mode 100644
index 0000000000..6174dbba9c
--- /dev/null
+++ b/core/ViewDataTable/GenerateGraphData.php
@@ -0,0 +1,140 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: GenerateGraphData.php 579 2008-07-27 00:32:59Z matt $
+ * 
+ * @package Piwik_ViewDataTable
+ */
+
+/**
+ * Reads data from the API and prepares data to give to the renderer Piwik_Visualization_Chart.
+ * This class is used to generate the data for the FLASH charts. It is given as a parameter of the SWF file.
+ * You can set the number of elements to appear in the graph using: setGraphLimit();
+ * Example:
+ * <pre>
+ * 	function getWebsites( $fetch = false)
+ * 	{
+ * 		$view = Piwik_ViewDataTable::factory();
+ * 		$view->init( $this->pluginName, 'getWebsites', 'Referers.getWebsites', 'getUrlsFromWebsiteId' );
+ * 		$view->setColumnsToDisplay( array('label','nb_visits') );
+ *		$view->setLimit(10);
+ * 		$view->setGraphLimit(12);
+ * 		return $this->renderView($view, $fetch);
+ * 	}
+ * </pre>
+ *  
+ * @package Piwik_ViewDataTable
+ *
+ */
+abstract class Piwik_ViewDataTable_GenerateGraphData extends Piwik_ViewDataTable
+{
+	/**
+	 * @see Piwik_ViewDataTable::init()
+	 */
+	function init($currentControllerName,
+						$currentControllerAction, 
+						$moduleNameAndMethod )
+	{
+		parent::init($currentControllerName, 
+						$currentControllerAction, 
+						$moduleNameAndMethod );
+	}
+	
+	/**
+	 * Number of elements to display in the graph.
+	 *
+	 * @var int
+	 */
+	protected $graphLimit = 5;
+	
+	/**
+	 * Sets the number max of elements to display (number of pie slice, vertical bars, etc.)
+	 * If the data has more elements than $limit then the last part of the data will be the sum of all the remaining data.
+	 *
+	 * @param int $limit
+	 */
+	function setGraphLimit( $limit )
+	{
+		$this->graphLimit = $limit;
+	}
+	/**
+	 * Returns numbers of elemnts to display in the graph
+	 *
+	 * @return int
+	 */
+	function getGraphLimit()
+	{
+		return $this->graphLimit;
+	}
+	
+	public function main()
+	{
+		if($this->mainAlreadyExecuted)
+		{
+			return;
+		}
+		$this->mainAlreadyExecuted = true;
+	
+		$this->setLimit(-1);
+		
+		// we load the data with the filters applied
+		$this->loadDataTableFromAPI();
+		$offsetStartSummary = $this->getGraphLimit() - 1;
+		$this->dataTable->queueFilter('Piwik_DataTable_Filter_AddSummaryRow', array($offsetStartSummary, Piwik_Translate('General_Others')));
+		$this->dataAvailable = $this->dataTable->getRowsCount() != 0;
+		
+		if(!$this->dataAvailable)
+		{
+			$this->view->customizeGraph();
+			$this->view->title(Piwik_Translate('General_NoDataForGraph'), '{font-size: 25px;}');
+		}
+		else
+		{
+			$data = $this->generateDataFromDataTable();
+			$this->view->setData($data);
+			$this->view->customizeGraph();
+		}
+	}
+	
+	/**
+	 * Returns a format friendly array from the dataTable 
+	 *
+	 * @return array
+	 */
+	protected function generateDataFromDataTable()
+	{
+		$this->dataTable->applyQueuedFilters();
+		
+		// We apply a filter to the DataTable, decoding the label column (useful for keywords for example)
+		$filter = new Piwik_DataTable_Filter_ColumnCallbackReplace(
+									$this->dataTable, 
+									'label', 
+									'urldecode'
+								);
+		$data = array();
+		foreach($this->dataTable->getRows() as $row)
+		{
+			$label = $row->getColumn('label');
+			$value = $row->getColumn('nb_uniq_visitors');
+			
+			// case no unique visitors
+			if($value === false)
+			{
+				$value = $row->getColumn('nb_visits');
+			}
+			
+			$data[] = array(
+				'label' => $label,
+				'value' => $value,
+				'url' 	=> $row->getMetadata('url'),
+			);
+		}
+		return $data;
+	}
+}
+
+
+
diff --git a/core/ViewDataTable/GenerateGraphData/ChartEvolution.php b/core/ViewDataTable/GenerateGraphData/ChartEvolution.php
new file mode 100644
index 0000000000..70bf412f57
--- /dev/null
+++ b/core/ViewDataTable/GenerateGraphData/ChartEvolution.php
@@ -0,0 +1,21 @@
+<?php
+require_once "ViewDataTable/GenerateGraphData.php";
+/**
+ * Piwik_ViewDataTable_GenerateGraphData for the Evolution graph (eg. Last 30 days visits) using Piwik_Visualization_ChartEvolution
+ * 
+ * @package Piwik_ViewDataTable
+ *
+ */
+class Piwik_ViewDataTable_GenerateGraphData_ChartEvolution extends Piwik_ViewDataTable_GenerateGraphData
+{
+	function __construct()
+	{
+		require_once "Visualization/ChartEvolution.php";
+		$this->view = new Piwik_Visualization_ChartEvolution;
+	}
+	
+	protected function generateDataFromDataTable()
+	{
+		return $this->generateDataFromDataTableArray($this->dataTable);
+	}
+}
diff --git a/core/ViewDataTable/GenerateGraphData/ChartPie.php b/core/ViewDataTable/GenerateGraphData/ChartPie.php
new file mode 100644
index 0000000000..53739067c8
--- /dev/null
+++ b/core/ViewDataTable/GenerateGraphData/ChartPie.php
@@ -0,0 +1,16 @@
+<?php
+require_once "ViewDataTable/GenerateGraphData.php";
+/**
+ * Piwik_ViewDataTable_GenerateGraphData for the pie chart, using Piwik_Visualization_ChartPie
+ * 
+ * @package Piwik_ViewDataTable
+ *
+ */
+class Piwik_ViewDataTable_GenerateGraphData_ChartPie extends Piwik_ViewDataTable_GenerateGraphData
+{
+	function __construct()
+	{
+		require_once "Visualization/ChartPie.php";
+		$this->view = new Piwik_Visualization_ChartPie;
+	}
+}
\ No newline at end of file
diff --git a/core/ViewDataTable/GenerateGraphData/ChartVerticalBar.php b/core/ViewDataTable/GenerateGraphData/ChartVerticalBar.php
new file mode 100644
index 0000000000..1bb9ea27b2
--- /dev/null
+++ b/core/ViewDataTable/GenerateGraphData/ChartVerticalBar.php
@@ -0,0 +1,16 @@
+<?php
+require_once "ViewDataTable/GenerateGraphData.php";
+/**
+ * Piwik_ViewDataTable_GenerateGraphData for the vertical bar graph, using Piwik_Visualization_ChartVerticalBar
+ * 
+ * @package Piwik_ViewDataTable
+ *
+ */
+class Piwik_ViewDataTable_GenerateGraphData_ChartVerticalBar extends Piwik_ViewDataTable_GenerateGraphData
+{
+	function __construct()
+	{
+		require_once "Visualization/ChartVerticalBar.php";
+		$this->view = new Piwik_Visualization_ChartVerticalBar;
+	}
+}
\ No newline at end of file
diff --git a/core/ViewDataTable/GenerateGraphHTML.php b/core/ViewDataTable/GenerateGraphHTML.php
new file mode 100644
index 0000000000..798bc6dd58
--- /dev/null
+++ b/core/ViewDataTable/GenerateGraphHTML.php
@@ -0,0 +1,141 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: GenerateGraphHTML.php 581 2008-07-27 23:07:52Z matt $
+ * 
+ * @package Piwik_ViewDataTable
+ */
+
+/**
+ * This class generates the HTML code to embed to flash graphs in the page.
+ * It doesn't call the API but simply prints the html snippet.
+ * 
+ * @package Piwik_ViewDataTable
+ *
+ */
+abstract class Piwik_ViewDataTable_GenerateGraphHTML extends Piwik_ViewDataTable
+{	
+	protected $width = '100%'; 
+	protected $height = 250;
+	protected $graphType = 'standard';
+	
+	/**
+	 * @see Piwik_ViewDataTable::init()
+	 *
+	 */
+	function init($currentControllerName,
+						$currentControllerAction, 
+						$moduleNameAndMethod )
+	{
+		parent::init($currentControllerName,
+						$currentControllerAction, 
+						$moduleNameAndMethod );
+		$this->dataTableTemplate = 'CoreHome/templates/graph.tpl';
+		
+		$this->disableOffsetInformation();
+		$this->disableExcludeLowPopulation();
+		$this->disableSearchBox();
+		$this->parametersToModify = array( 
+						'viewDataTable' => $this->valueParameterViewDataTable,
+						// in the case this controller is being executed by another controller
+						// eg. when being widgetized in an IFRAME
+						// we need to put in the URL of the graph data the real module and action
+						'module' => $currentControllerName, 
+						'action' => $currentControllerAction,
+		);
+	}
+	
+	/**
+	 * Sets parameters to modify in the future generated URL
+	 *
+	 * @param array $array array('nameParameter' => $newValue, ...)
+	 */
+	public function setParametersToModify($array)
+	{
+		$this->parametersToModify = array_merge($this->parametersToModify, $array);
+	}
+	
+	/**
+	 * @see Piwik_ViewDataTable::main()
+	 *
+	 */
+	public function main()
+	{
+		if($this->mainAlreadyExecuted)
+		{
+			return;
+		}
+		$this->mainAlreadyExecuted = true;
+		
+		$view = new Piwik_View($this->dataTableTemplate);
+		$this->id = $this->getUniqIdTable();
+		$view->graphType = $this->graphType;
+
+		$this->parametersToModify['action'] = $this->currentControllerAction;
+		$url = Piwik_Url::getCurrentQueryStringWithParametersModified($this->parametersToModify);
+		$view->jsInvocationTag = $this->getFlashInvocationCode($url);
+		$view->urlGraphData = $url;
+		
+		$view->formEmbedId = "formEmbed".$this->id;
+		$view->graphCodeEmbed = $this->graphCodeEmbed;
+		
+		$view->id = $this->id;
+		$view->method = $this->method;
+		$view->javascriptVariablesToSet = $this->getJavascriptVariablesToSet();
+		$view->showFooter = $this->getShowFooter();
+		$this->view = $view;
+	}
+	
+	protected function getFlashInvocationCode( $url = 'libs/open-flash-chart/data-files/nodata.txt', $use_swfobject = true  )
+	{ 
+		$width = $this->width; 
+		$height = $this->height; 
+
+		$libPathInPiwik = 'libs/open-flash-chart/';
+		$currentPath = Piwik_Url::getCurrentUrlWithoutFileName();
+		$pathToLibraryOpenChart = $currentPath . $libPathInPiwik;
+		
+		$url = Piwik_Url::getCurrentUrlWithoutQueryString() . $url;
+	    // escape the & and stuff:
+	    $url = urlencode($url);
+		
+		$obj_id = $this->id . "Chart";
+	    $div_name = $this->id . "FlashContent";
+	    	   
+	    $return = ''; 
+	    if( $use_swfobject )
+	    {
+	    	// Using library for auto-enabling Flash object on IE, disabled-Javascript proof
+		    $return .=  '
+				<div id="'. $div_name .'"></div>
+				<script type="text/javascript">
+				var so = new SWFObject("'.$pathToLibraryOpenChart.'open-flash-chart.swf", "'.$obj_id.'_swf", "'. $width . '", "' . $height . '", "9", "#FFFFFF");
+				so.addVariable("data", "'. $url . '");
+				so.addParam("allowScriptAccess", "sameDomain");
+				so.addParam("wmode", "transparent");
+				so.write("'. $div_name .'");
+				</script>
+				<noscript>
+				';
+		}
+		$urlGraph = $pathToLibraryOpenChart."open-flash-chart.swf?data=" . $url;
+		
+		$this->graphCodeEmbed .= "<div><object classid='clsid:d27cdb6e-ae6d-11cf-96b8-444553540000' codebase='http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0' width='" . $width . "' height='" . $height . "' id='". $obj_id ."' >".
+							"<param name='movie' value='".$urlGraph."' />".
+							"<param name='wmode' value='transparent' />".
+							"<param name='allowScriptAccess' value='sameDomain' /> ".
+							"<embed src='$urlGraph' allowScriptAccess='sameDomain' quality='high' bgcolor='#FFFFFF' width='". $width ."' height='". $height ."' name='open-flash-chart' type='application/x-shockwave-flash' id='". $obj_id ."' />".
+							"</object></div>";
+		$return .= $this->graphCodeEmbed;
+		
+		if ( $use_swfobject ) {
+			$return .= '</noscript>';
+		}
+		
+		return $return;
+	}
+}
+
diff --git a/core/ViewDataTable/GenerateGraphHTML/ChartEvolution.php b/core/ViewDataTable/GenerateGraphHTML/ChartEvolution.php
new file mode 100644
index 0000000000..d1db503052
--- /dev/null
+++ b/core/ViewDataTable/GenerateGraphHTML/ChartEvolution.php
@@ -0,0 +1,31 @@
+<?php
+require_once "ViewDataTable/GenerateGraphHTML.php";
+/**
+ * Generates HTML embed for the Evolution graph
+ *  
+ * @package Piwik_ViewDataTable
+ *
+ */
+class Piwik_ViewDataTable_GenerateGraphHTML_ChartEvolution extends Piwik_ViewDataTable_GenerateGraphHTML
+{
+	function __construct()
+	{
+		$this->valueParameterViewDataTable = 'generateDataChartEvolution';
+		$this->width='100%';
+		$this->height=150;
+		// used for the CSS class to apply to the DIV containing the graph
+		$this->graphType = 'evolution';		
+	}
+	
+	function init($currentControllerName,
+						$currentControllerAction, 
+						$moduleNameAndMethod )
+	{
+		parent::init($currentControllerName,
+						$currentControllerAction, 
+						$moduleNameAndMethod );
+		
+		$this->setParametersToModify(array('date' => 'last30'));
+		$this->doNotShowFooter();
+	}
+}
\ No newline at end of file
diff --git a/core/ViewDataTable/GenerateGraphHTML/ChartPie.php b/core/ViewDataTable/GenerateGraphHTML/ChartPie.php
new file mode 100644
index 0000000000..35d037c65b
--- /dev/null
+++ b/core/ViewDataTable/GenerateGraphHTML/ChartPie.php
@@ -0,0 +1,15 @@
+<?php
+require_once "ViewDataTable/GenerateGraphHTML.php";
+/**
+ * Generates HTML embed for the Pie chart
+ * 
+ * @package Piwik_ViewDataTable
+ *
+ */
+class Piwik_ViewDataTable_GenerateGraphHTML_ChartPie extends Piwik_ViewDataTable_GenerateGraphHTML
+{
+	function __construct()
+	{
+		$this->valueParameterViewDataTable = 'generateDataChartPie';
+	}
+}
\ No newline at end of file
diff --git a/core/ViewDataTable/GenerateGraphHTML/ChartVerticalBar.php b/core/ViewDataTable/GenerateGraphHTML/ChartVerticalBar.php
new file mode 100644
index 0000000000..c1e03fea78
--- /dev/null
+++ b/core/ViewDataTable/GenerateGraphHTML/ChartVerticalBar.php
@@ -0,0 +1,16 @@
+<?php
+require_once "ViewDataTable/GenerateGraphHTML.php";
+/**
+ * 
+ * Generates HTML embed for the vertical bar chart
+ * 
+ * @package Piwik_ViewDataTable
+ *
+ */
+class Piwik_ViewDataTable_GenerateGraphHTML_ChartVerticalBar extends Piwik_ViewDataTable_GenerateGraphHTML
+{
+	function __construct()
+	{
+		$this->valueParameterViewDataTable = 'generateDataChartVerticalBar';
+	}
+}
\ No newline at end of file
diff --git a/core/ViewDataTable/Html.php b/core/ViewDataTable/Html.php
new file mode 100644
index 0000000000..03d23199e3
--- /dev/null
+++ b/core/ViewDataTable/Html.php
@@ -0,0 +1,274 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Html.php 581 2008-07-27 23:07:52Z matt $
+ * 
+ * @package Piwik_ViewDataTable
+ */
+
+/**
+ * 
+ * Outputs an AJAX Table for a given DataTable.
+ * 
+ * Reads the requested DataTable from the API.
+ * 
+ * @package Piwik_ViewDataTable
+ *
+ */
+class Piwik_ViewDataTable_Html extends Piwik_ViewDataTable
+{
+	/**
+	 * Array of columns names to display
+	 *
+	 * @var array
+	 */
+	protected $columnsToDisplay = array();
+	
+	/**
+	 * Array of columns names translations
+	 *
+	 * @var array
+	 */
+	protected $columnsTranslations = array();
+
+	/**
+	 * PHP array conversion of the Piwik_DataTable 
+	 *
+	 * @var array
+	 */
+	public $arrayDataTable; // phpArray
+	
+	/**
+	 * @see Piwik_ViewDataTable::init()
+	 */
+	function init($currentControllerName,
+						$currentControllerAction, 
+						$moduleNameAndMethod,						
+						$actionToLoadTheSubTable = null )
+	{
+		parent::init($currentControllerName,
+						$currentControllerAction, 
+						$moduleNameAndMethod,						
+						$actionToLoadTheSubTable);
+		$this->dataTableTemplate = 'CoreHome/templates/datatable.tpl';
+		
+		$this->variablesDefault['enable_sort'] = true;
+	
+		// load general columns translations
+		$this->setColumnTranslation('nb_visits', Piwik_Translate('General_ColumnNbVisits'));
+		$this->setColumnTranslation('label', Piwik_Translate('General_ColumnLabel'));
+		$this->setColumnTranslation('nb_uniq_visitors', Piwik_Translate('General_ColumnNbUniqVisitors'));	
+	}
+	
+	/**
+	 * @see Piwik_ViewDataTable::main()
+	 *
+	 */
+	public function main()
+	{
+		if($this->mainAlreadyExecuted)
+		{
+			return;
+		}
+		$this->mainAlreadyExecuted = true;
+		
+//		$i=0;while($i<1500000){ $j=$i*$i;$i++;}
+		
+		$this->loadDataTableFromAPI();
+	
+		// We apply a filter to the DataTable, decoding the label column (useful for keywords for example)
+		$filter = new Piwik_DataTable_Filter_ColumnCallbackReplace(
+									$this->dataTable, 
+									'label', 
+									'urldecode'
+								);
+		
+		
+		$view = new Piwik_View($this->dataTableTemplate);
+		
+		
+		// We get the PHP array converted from the DataTable
+		$phpArray = $this->getPHPArrayFromDataTable();
+		
+		$view->arrayDataTable 	= $phpArray;
+		$view->method = $this->method;
+		
+		$columns = $this->getColumnsToDisplay($phpArray);
+		$view->dataTableColumns = $columns;
+		
+		$nbColumns = count($columns);
+		// case no data in the array we use the number of columns set to be displayed 
+		if($nbColumns == 0)
+		{
+			$nbColumns = count($this->columnsToDisplay);
+		}
+		
+		$view->nbColumns = $nbColumns;
+		
+		$view->id = $this->getUniqIdTable();
+		$view->javascriptVariablesToSet = $this->getJavascriptVariablesToSet();
+		$view->showFooter = $this->getShowFooter();
+		$this->view = $view;
+	}
+
+	/**
+	 * Returns friendly php array from the Piwik_DataTable
+	 * @see Piwik_DataTable_Renderer_Php
+	 * @return array
+	 */
+	protected function getPHPArrayFromDataTable()
+	{		
+		$renderer = Piwik_DataTable_Renderer::factory('php');
+		$renderer->setTable($this->dataTable);
+		$renderer->setSerialize( false );
+		// we get the php array from the datatable
+		// but conserving the original datatable format, which means rows 'columns', 'metadata' and 'idsubdatatable'
+		$phpArray = $renderer->originalRender();
+		return $phpArray;
+	}	
+	
+	/**
+	 * Sets the columns that will be displayed in the HTML output
+	 * By default all columns are displayed ($columnsNames = array() will display all columns)
+	 * 
+	 * @param array $columnsNames Array of column names eg. array('nb_visits','nb_hits')
+	 */
+	public function setColumnsToDisplay( $columnsNames)
+	{
+		$this->columnsToDisplay = $columnsNames;
+	}
+	
+	/**
+	 * Sets translation string for given column
+	 *
+	 * @param string $columnName column name
+	 * @param string $columnTranslation column name translation
+	 */
+	public function setColumnTranslation( $columnName, $columnTranslation )
+	{
+		$this->columnsTranslations[$columnName] = $columnTranslation;
+	}
+	
+	/**
+	 * Returns column translation if available, in other case given column name
+	 *
+	 * @param string $columnName column name
+	 */
+	public function getColumnTranslation( $columnName )
+	{
+		if( isset($this->columnsTranslations[$columnName]) )
+		{
+			return $this->columnsTranslations[$columnName];
+		}
+		else
+		{
+			return $columnName;
+		}
+	}
+	
+	/**
+	 * Sets columns translations array.
+	 *
+	 * @param array $columnsTranslations An associative array indexed by column names, eg. array('nb_visit'=>"Numer of visits")
+	 */
+	public function setColumnsTranslations( $columnsTranslations )
+	{
+		$this->columnsTranslations = $columnsTranslations;
+	}
+	
+	/**
+	 * Returns array(
+	 * 				array('id' => 1, 'name' => 'nb_visits'),
+	 * 				array('id' => 3, 'name' => 'nb_uniq_visitors'),
+	 *
+	 * @param array PHP array conversion of the data table
+	 * @return array
+	 */
+	protected function getColumnsToDisplay($phpArray)
+	{
+		$dataTableColumns = array();
+		if(count($phpArray) > 0)
+		{
+			// build column information
+			$id = 0;
+			foreach($phpArray[0]['columns'] as $columnName => $row)
+			{
+				if( $this->isColumnToDisplay( $id, $columnName) )
+				{
+					$dataTableColumns[]	= array('id' => $id, 'name' => $columnName, 'displayName' => $this->getColumnTranslation($columnName) );
+				}
+				$id++;
+			}
+		}
+		return $dataTableColumns;
+	}
+
+	/**
+	 * Returns true if the given column (id = $idColumn or name = $nameColumn) is set to be displayed.
+	 *
+	 * @param int $idColumn
+	 * @param string $nameColumn
+	 * @return bool
+	 */
+	protected function isColumnToDisplay( $idColumn, $nameColumn )
+	{
+		// we return true
+		// - we didn't set any column to display (means we display all the columns)
+		// - the column has been set as to display
+		if( count($this->columnsToDisplay) == 0
+			|| in_array($idColumn, $this->columnsToDisplay)
+			|| in_array($nameColumn, $this->columnsToDisplay))
+		{
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Sets the columns in the HTML table as not sortable (they are not clickable) 
+	 *
+	 * @return void
+	 */
+	public function disableSort()
+	{
+		$this->variablesDefault['enable_sort'] = 'false';		
+	}
+		
+	/**
+	 * Sets the search on a table to be recursive (also searches in subtables)
+	 * Works only on Actions/Downloads/Outlinks tables.
+	 *
+	 * @return bool If the pattern for a recursive search was set in the URL
+	 */
+	public function setSearchRecursive()
+	{
+		$this->variablesDefault['search_recursive'] = true;
+		return $this->setRecursiveLoadDataTableIfSearchingForPattern();
+	}
+	
+	/**
+	 * Set the flag to load the datatable recursively so we can search on subtables as well
+	 *
+	 * @return bool if recursive search is enabled
+	 */
+	protected function setRecursiveLoadDataTableIfSearchingForPattern()
+	{
+		try{
+			$requestValue = Piwik_Common::getRequestVar('filter_column_recursive');
+			$requestValue = Piwik_Common::getRequestVar('filter_pattern_recursive');
+			// if the 2 variables are set we are searching for something.
+			// we have to load all the children subtables in this case
+			
+			$this->recursiveDataTableLoad = true;
+			return true;
+		}
+		catch(Exception $e) {
+			$this->recursiveDataTableLoad = false;
+			return false;
+		}
+	}
+}
+
diff --git a/core/ViewDataTable/Sparkline.php b/core/ViewDataTable/Sparkline.php
new file mode 100644
index 0000000000..b66630669a
--- /dev/null
+++ b/core/ViewDataTable/Sparkline.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Html.php 404 2008-03-23 01:09:59Z matt $
+ * 
+ * @package Piwik_ViewDataTable
+ */
+
+
+require_once "Visualization/Sparkline.php";
+
+/**
+ * Reads the requested DataTable from the API and prepare data for the Sparkline view.
+ * 
+ * @package Piwik_ViewDataTable
+ *
+ */
+class Piwik_ViewDataTable_Sparkline extends Piwik_ViewDataTable
+{
+	
+	/**
+	 * @see Piwik_ViewDataTable::init()
+	 */
+	function init($currentControllerName,
+						$currentControllerAction, 
+						$moduleNameAndMethod )
+	{
+		parent::init($currentControllerName, 
+						$currentControllerAction, 
+						$moduleNameAndMethod );
+	}
+	
+	/**
+	 * @see Piwik_ViewDataTable::main()
+	 */
+	public function main()
+	{
+		if($this->mainAlreadyExecuted)
+		{
+			return;
+		}
+		$this->mainAlreadyExecuted = true;
+	
+		// we load the data with the filters applied
+		$this->loadDataTableFromAPI();
+		
+		$this->dataAvailable = $this->dataTable->getRowsCount() != 0;
+		
+		if(!$this->dataAvailable)
+		{
+			throw new Exception( "No data for this graph" );
+		}
+		else
+		{
+			$data = $this->generateDataFromDataTableArray($this->dataTable);
+			
+			$graph = new Piwik_Visualization_Sparkline;
+			$graph->setData($data);
+			$graph->main();
+//			var_dump($data);exit;
+			$this->view = $graph;
+		}
+	}
+}
diff --git a/core/Visualization/Chart.php b/core/Visualization/Chart.php
new file mode 100644
index 0000000000..6e58ba721f
--- /dev/null
+++ b/core/Visualization/Chart.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Chart.php 579 2008-07-27 00:32:59Z matt $
+ * 
+ * @package Piwik_Visualization
+ */
+
+require_once "Visualization/OpenFlashChart.php";
+
+/**
+ * Generates the data in the Open Flash Chart format, from the given data.
+ * Uses Open flash chart PHP library @see Piwik_Visualization_OpenFlashChart
+ * 
+ * @package Piwik_Visualization
+ */
+abstract class Piwik_Visualization_Chart extends Piwik_Visualization_OpenFlashChart
+{
+	
+	protected $dataGraph = array();
+	
+	function setData($data)
+	{
+		$this->dataGraph = $data;
+	}
+	
+	function getCount()
+	{
+		return count($this->dataGraph);
+	}
+	
+	function customizeGraph()
+	{
+		$this->set_num_decimals ( 0 );
+		$this->set_is_decimal_separator_comma( false );
+		$this->set_is_thousand_separator_disabled( true );  
+		$this->y_axis_colour = '#ffffff';
+		$this->x_axis_colour = '#596171'; 
+		$this->x_grid_colour = $this->y_grid_colour = '#E0E1E4';
+		
+		// approx 5 x labels on the graph
+		$steps = ceil($this->getCount() / 5);
+		$steps = $steps + $steps % 2; // make sure modulo 2
+		
+		$this->set_x_label_style( 10, $this->x_axis_colour, 0, $steps, $this->x_grid_colour );
+		$this->set_x_axis_steps( $steps / 2 );
+		
+		
+		$stepsY = ceil($this->getCount() / 4);
+		$this->y_label_steps( $stepsY / 3 );
+		$this->y_label_steps( 4 );
+		
+		$this->bg_colour = '#ffffff';
+		$this->set_inner_background('#ffffff');
+		
+		$this->set_tool_tip( '#x_label# <br>#val# #key# ' );
+	}
+	
+	function prepareData()
+	{		
+		$label = $data = array();
+		$max = 0;
+		foreach($this->dataGraph as $row)
+		{
+			$label[] = $row['label'];
+			$data[] = $row['value'];
+			
+			if($row['value'] > $max) 
+			{
+				$max = $row['value'];
+			}
+		}
+		$this->arrayData = $data;
+		$this->arrayLabel = $label;
+		
+		$this->arrayLabel = str_replace(","," -",$this->arrayLabel);
+		
+		$this->maxData = $max;
+		if($this->maxData > 10)
+		{
+			$this->maxData = $max + 10 - $max % 10;
+		}
+	}
+	
+}
\ No newline at end of file
diff --git a/core/Visualization/ChartEvolution.php b/core/Visualization/ChartEvolution.php
new file mode 100644
index 0000000000..066dbc9cff
--- /dev/null
+++ b/core/Visualization/ChartEvolution.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: ChartVerticalBar.php 168 2008-01-14 05:26:43Z matt $
+ * 
+ * @package Piwik_Visualization
+ */
+
+require_once "Visualization/Chart.php";
+
+/**
+ * Customize the Evolution chart style for the flash graph
+ * 
+ * @package Piwik_Visualization
+ *
+ */
+class Piwik_Visualization_ChartEvolution extends Piwik_Visualization_Chart
+{		
+	function customizeGraph()
+	{
+		parent::customizeGraph();
+		$this->prepareData();
+		$this->set_y_max( $this->maxData );
+		
+		$line_1 = new line_hollow( 1, 3, '0x3357A0' );
+		$line_1->key( 'visits', 10 );
+		
+		$i = 0;
+		foreach($this->arrayData as $value)
+		{
+			// hack until we have proper date handling
+			$spacePosition = strpos($this->arrayLabel[$i],' ');
+			if($spacePosition === false)
+			{
+				$spacePosition = strlen($this->arrayLabel[$i]);
+			}
+			
+			// generate the link on the dot, to the given day' statistics
+			$link = Piwik_Url::getCurrentScriptName() 
+							. Piwik_Url::getCurrentQueryStringWithParametersModified( array(
+										'date' => substr($this->arrayLabel[$i],0,$spacePosition),
+										'module' => 'CoreHome',
+										'action' => 'index',
+										'viewDataTable' => null// we reset the viewDataTable parameter (useless in the link)
+										));
+			
+			$line_1->add_link($value, $link );
+			$i++;
+		}
+		$this->data_sets[] = $line_1;
+		
+		$this->set_x_labels( $this->arrayLabel );
+		$this->area_hollow( 1, 3, 4,'0x3357A0',  ' visits', 10 );	
+	}
+}
\ No newline at end of file
diff --git a/core/Visualization/ChartPie.php b/core/Visualization/ChartPie.php
new file mode 100644
index 0000000000..08dc54beb0
--- /dev/null
+++ b/core/Visualization/ChartPie.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: ChartPie.php 459 2008-05-06 22:39:42Z matt $
+ * 
+ * @package Piwik_Visualization
+ */
+require_once "Visualization/Chart.php";
+
+/**
+ * 
+ * Customize the Pie chart style for the flash graph
+ * 
+ * @package Piwik_Visualization
+ */
+class Piwik_Visualization_ChartPie extends Piwik_Visualization_Chart
+{
+	function customizeGraph()
+	{
+		parent::customizeGraph();
+		
+		$this->prepareData();		
+	
+	    for($i = 0, $cnt = count($this->arrayLabel); $i < $cnt; $i++) 
+	    {
+	    	$label = $this->arrayLabel[$i];
+			$this->arrayLabel[$i] = (strlen($label) > 20 ? substr($label, 0, 20).'...' : $label);
+	    }
+	    $this->set_x_label_style( 12, $this->x_axis_colour, 0, 2, $this->bg_colour );
+		$this->pie(60,'#505050','{font-size: 12px; color: #142448}', true);
+		$this->pie_values( $this->arrayData, $this->arrayLabel );
+		$this->pie_slice_colours( array('#3C5A69','#679BB5','#695A3C','#B58E67','#969696') );
+		
+		$this->set_tool_tip( '#x_label# <br>#val# ' );
+		
+	}
+	
+}
\ No newline at end of file
diff --git a/core/Visualization/ChartVerticalBar.php b/core/Visualization/ChartVerticalBar.php
new file mode 100644
index 0000000000..bfb820f586
--- /dev/null
+++ b/core/Visualization/ChartVerticalBar.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: ChartVerticalBar.php 459 2008-05-06 22:39:42Z matt $
+ * 
+ * @package Piwik_Visualization
+ */
+
+require_once "Visualization/Chart.php";
+
+/**
+ * 
+ * Customize the Vertical bar chart style for the flash graph
+ * 
+ * @package Piwik_Visualization
+ *
+ */
+class Piwik_Visualization_ChartVerticalBar extends Piwik_Visualization_Chart
+{
+	protected $limit = 10;
+		
+	function customizeGraph()
+	{
+		parent::customizeGraph();
+		$this->prepareData();
+		$this->set_data( $this->arrayData );
+		$this->set_x_labels( $this->arrayLabel );
+		$this->set_x_label_style( 12, $this->x_axis_colour, 0, 2, $this->bg_colour );
+		$this->set_x_axis_steps( 2 );
+		$this->set_y_max( $this->maxData );
+		$this->y_label_steps( 2 );
+		$this->bar_filled( 50, '#3B5AA9', '#063E7E', 'visits', 10 );
+	}
+	
+}
\ No newline at end of file
diff --git a/core/Visualization/Cloud.php b/core/Visualization/Cloud.php
new file mode 100644
index 0000000000..46ca66208d
--- /dev/null
+++ b/core/Visualization/Cloud.php
@@ -0,0 +1,169 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Cloud.php 444 2008-04-11 13:38:22Z johmathe $
+ *
+ * @package Piwik_Visualization
+ */
+
+
+/**
+ * Generates a tag cloud from a given data array.
+ * The generated tag cloud can be in PHP format, or in HTML. 
+ *
+ * Inspired from Derek Harvey (www.derekharvey.co.uk)
+ * 
+ * @package Piwik_Visualization
+ */
+class Piwik_Visualization_Cloud
+{
+	protected $wordsArray = array();
+	
+	public $truncatingLimit = 30;
+	
+	/**
+	 * @param array array( word => 10, word2 => 50, word3 => 1)
+	 */
+	function __construct($words = false)
+	{
+		if ($words !== false && is_array($words))
+		{
+			foreach ($words as $word => $value)
+			{
+				$this->addWord($word, $value);
+			}
+		}
+	}
+	 
+	/*
+	 * Assign word to array
+	 *
+	 * @param string $word
+	 * @return string
+	 */
+	function addWord($word, $value = 1)
+	{
+		//        $word = strtolower($word);
+		if (isset($this->wordsArray[$word]))
+		{
+			$this->wordsArray[$word] += $value;
+		}
+		else
+		{
+			$this->wordsArray[$word] = $value;
+		}
+	}
+	 
+	/*
+	 * Shuffle associated names in array
+	 */
+	function shuffleCloud()
+	{
+		$keys = array_keys($this->wordsArray);
+		 
+		shuffle($keys);
+		 
+		if (count($keys) && is_array($keys))
+		{
+			$tmpArray = $this->wordsArray;
+			$this->wordsArray = array();
+			foreach ($keys as $key => $value)
+			$this->wordsArray[$value] = $tmpArray[$value];
+		}
+	}
+	 
+	/*
+	 * Calculate size of words array
+	 */
+	 
+	function getCloudSize()
+	{
+		return array_sum($this->wordsArray);
+	}
+	 
+	/*
+	 * Get the class range using a percentage
+	 *
+	 * @returns int $class
+	 */	 
+	function getClassFromPercent($percent)
+	{
+		$mapping = array(
+		95,
+		70,
+		50,
+		30,
+		15,
+		5,
+		0
+		);
+		foreach($mapping as $key => $value)
+		{
+			if($percent >= $value)
+			{
+				return $key;
+			}
+		}
+	}
+	 
+	/*
+	 * Create the HTML code for each word and apply font size.
+	 *
+	 * @returns string $spans
+	 */
+	 
+	function render($returnType = "html")
+	{
+		$this->shuffleCloud();
+
+		if($returnType == "html")
+		{
+			$return = '';
+		}
+		else
+		{
+			$return = array();
+		}
+
+		if (count($this->wordsArray) > 0)
+		{
+			$this->max = max($this->wordsArray);
+
+			$return = ($returnType == "html" ? "" : ($returnType == "array" ? array() : ""));
+			foreach ($this->wordsArray as $word => $popularity)
+			{
+				 
+				// truncating the word
+				$wordTruncated = $word;
+				if(strlen($word) > $this->truncatingLimit)
+				{
+					$wordTruncated = substr($word, 0, $this->truncatingLimit - 3).'...';
+				}
+				 
+				// computing the percentage
+				$percent = ($popularity / $this->max) * 100;
+
+				// and the CSS style value
+				$sizeRange = $this->getClassFromPercent($percent);
+
+				if ($returnType == "array")
+				{
+					$return[$word]['word'] = $word;
+					$return[$word]['wordTruncated'] = $wordTruncated;
+					$return[$word]['size'] = $sizeRange;
+					$return[$word]['percent'] = $percent;
+				}
+				else if ($returnType == "html")
+				{
+					$return .= "\n<span title='".$word."' class='word size{$sizeRange}'> &nbsp; {$wordTruncated} &nbsp; </span>";
+				}
+				//            	print( $word ."=".$percent."<br>");
+			}
+		}
+		return $return;
+	}
+}
+
diff --git a/core/Visualization/OpenFlashChart.php b/core/Visualization/OpenFlashChart.php
new file mode 100644
index 0000000000..1e85492c5c
--- /dev/null
+++ b/core/Visualization/OpenFlashChart.php
@@ -0,0 +1,1647 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: OpenFlashChart.php 566 2008-07-21 00:34:43Z matt $
+ *
+ * @package Piwik_Visualization
+ * @subpackage OFC
+ */
+
+require_once "iView.php";
+
+
+/**
+ * Original class provided by Open Flash Chart
+ *
+ * @package Piwik_Visualization
+ */
+abstract class Piwik_Visualization_OpenFlashChart implements Piwik_iView
+{
+	function __construct()
+	{
+
+		$this->data_sets = array();
+		
+		
+		$this->data = array();
+		$this->links = array();
+		$this->width = 250;
+		$this->height = 200;
+		$this->js_path = 'js/';
+		$this->swf_path = '';
+		$this->x_labels = array();
+		$this->y_min = '';
+		$this->y_max = '';
+		$this->x_min = '';
+		$this->x_max = '';
+		$this->y_steps = '';
+		$this->title = '';
+		$this->title_style = '';
+		$this->occurence = 0;
+		
+		$this->x_offset = '';
+
+		$this->x_tick_size = -1;
+
+		$this->y2_max = '';
+		$this->y2_min = '';
+
+		// GRID styles:
+		$this->x_axis_colour = '';
+		$this->x_axis_3d = '';
+		$this->x_grid_colour = '';
+		$this->x_axis_steps = 1;
+		$this->y_axis_colour = '';
+		$this->y_grid_colour = '';
+		$this->y2_axis_colour = '';
+		
+		// AXIS LABEL styles:         
+		$this->x_label_style = '';
+		$this->y_label_style = '';
+		$this->y_label_style_right = '';
+	
+	
+		// AXIS LEGEND styles:
+		$this->x_legend = '';
+		$this->x_legend_size = 20;
+		$this->x_legend_colour = '#000000';
+	
+		$this->y_legend = '';
+		$this->y_legend_right = '';
+		//$this->y_legend_size = 20;
+		//$this->y_legend_colour = '#000000';
+	
+		$this->lines = array();
+		$this->line_default['type'] = 'line';
+		$this->line_default['values'] = '3,#87421F';
+		$this->js_line_default = 'so.addVariable("line","3,#87421F");';
+		
+		$this->bg_colour = '';
+		$this->bg_image = '';
+	
+		$this->inner_bg_colour = '';
+		$this->inner_bg_colour_2 = '';
+		$this->inner_bg_angle = '';
+		
+		// PIE chart ------------
+		$this->pie = '';
+		$this->pie_values = '';
+		$this->pie_colours = '';
+		$this->pie_labels = '';
+		
+		$this->tool_tip = '';
+		
+		// which data lines are attached to the
+		// right Y axis?
+		$this->y2_lines = array();
+		
+		// Number formatting:
+		$this->y_format='';
+		$this->num_decimals='';
+		$this->is_fixed_num_decimals_forced='';
+		$this->is_decimal_separator_comma='';
+		$this->is_thousand_separator_disabled='';
+		
+		$this->output_type = '';
+		
+		//
+		// set some default value incase the user forgets
+		// to set them, so at least they see *something*
+		// even is it is only the axis and some ticks
+		//
+		$this->set_y_min( 0 );
+		$this->set_y_max( 20 );
+		$this->set_x_axis_steps( 1 );
+		$this->y_label_steps( 5 );
+	}
+
+	/**
+	* Set the unique_id to use for the flash object id.
+	*/
+	function set_unique_id()
+	{
+		$this->unique_id = uniqid(rand(), true);
+	}
+	
+	/**
+	* Get the flash object ID for the last rendered object.
+	*/
+	function get_unique_id()
+	{
+		return ($this->unique_id);
+	}
+	
+	/**
+	* Set the base path for the swfobject.js
+	*
+	* @param base_path a string argument.
+	*   The path to the swfobject.js file
+	*/
+	function set_js_path($path)
+	{
+		$this->js_path = $path;
+	}
+	
+	/**
+	* Set the base path for the open-flash-chart.swf
+	*
+	* @param path a string argument.
+	*   The path to the open-flash-chart.swf file
+	*/
+	function set_swf_path($path)
+	{
+		$this->swf_path = $path;
+	}
+
+	/**
+	* Set the type of output data.
+	*
+	* @param type a string argument.
+	*   The type of data.  Currently only type is js, or nothing.
+	*/
+	function set_output_type($type)
+	{
+		$this->output_type = $type;
+	}
+
+	/**
+	* returns the next line label for multiple lines.
+	*/
+	function next_line()
+	{
+		$line_num = '';
+		if( count( $this->lines ) > 0 )
+			$line_num = '_'. (count( $this->lines )+1);
+
+		return $line_num;
+	}
+	
+	// escape commas (,)
+	static function esc( $text )
+	{
+		// we replace the comma so it is not URL escaped
+		// if it is, flash just thinks it is a comma
+		// which is no good if we are splitting the
+		// string on commas.
+		$tmp = str_replace( ',', '#comma#', $text );
+		//$tmp = utf8_encode( $tmp );
+		// now we urlescape all dodgy characters (like & % $ etc..)
+		return urlencode( $tmp );
+	}
+
+	/**
+	* Format the text to the type of output.
+	*/
+	function format_output($function,$values)
+	{
+		if($this->output_type == 'js')
+		{
+			$tmp = 'so.addVariable("'. $function .'","'. $values . '");';
+		}
+		else
+		{
+			$tmp = '&'. $function .'='. $values .'&';
+		}
+
+		return $tmp;
+	}
+
+	/**
+	* Set the text and style of the title.
+	*
+	* @param title a string argument.
+	*   The text of the title.
+	* @param style a string.
+	*   CSS styling of the title.
+	*/
+	function set_title( $title, $style='' )
+	{
+		$this->title = $this->esc( $title );
+		if( strlen( $style ) > 0 )
+			$this->title_style = $style;
+	}
+
+	/**
+	 * Set the width of the chart.
+	 *
+	 * @param width an int argument.
+	 *   The width of the chart frame.
+	 */
+	function set_width( $width )
+	{
+		$this->width = $width;
+	}
+	
+	/**
+	 * Set the height of the chart.
+	 *
+	 * @param height an int argument.
+	 *   The height of the chart frame.
+	 */
+	function set_height( $height )
+	{
+		$this->height = $height;
+	}
+
+	/**
+	 * Set the base path of the swfobject.
+	 *
+	 * @param base a string argument.
+	 *   The base path of the swfobject.
+	 */
+	function set_base( $base='js/' )
+	{
+		$this->base = $base;
+	}
+	
+	// Number formatting:
+	function set_y_format( $val )
+	{
+		$this->y_format = $val;	
+	}
+	
+	function set_num_decimals( $val )
+	{
+		$this->num_decimals = $val;
+	}
+	
+	function set_is_fixed_num_decimals_forced( $val )
+	{
+		$this->is_fixed_num_decimals_forced = $val?'true':'false';
+	}
+	
+	function set_is_decimal_separator_comma( $val )
+	{
+		$this->is_decimal_separator_comma = $val?'true':'false';
+	}
+	
+	function set_is_thousand_separator_disabled( $val )
+	{
+		$this->is_thousand_separator_disabled = $val?'true':'false';
+	}
+
+	/**
+	 * Set the data for the chart
+	 * @param a an array argument.
+	 *   An array of the data to add to the chart.
+	 */
+	function set_data( $a )
+	{
+		$this->data[] = implode(',',$a);
+	}
+	
+	// UGH, these evil functions are making me fell ill
+	function set_links( $links )
+	{
+		// TO DO escape commas:
+		$this->links[] = implode(',',$links);
+	}
+	
+	// $val is a boolean
+	function set_x_offset( $val )
+	{
+		$this->x_offset = $val?'true':'false';
+	}
+
+	/**
+	 * Set the tooltip to be displayed on each chart item.\n
+	 * \n
+	 * Replaceable tokens that can be used in the string include: \n
+	 * #val# - The actual value of whatever the mouse is over. \n
+	 * #key# - The key string. \n
+	 * \<br>  - New line. \n
+	 * #x_label# - The X label string. \n
+	 * #x_legend# - The X axis legend text. \n
+	 * Default string is: "#x_label#<br>#val#" \n
+	 * 
+	 * @param tip a string argument.
+	 *   A formatted string to show as the tooltip.
+	 */
+	function set_tool_tip( $tip )
+	{
+		$this->tool_tip = $this->esc( $tip );
+	}
+
+	/**
+	 * Set the x axis labels
+	 *
+	 * @param a an array argument.
+	 *   An array of the x axis labels.
+	 */
+	function set_x_labels( $a )
+	{
+		$tmp = array();
+		foreach( $a as $item )
+			$tmp[] = $this->esc( $item );
+		$this->x_labels = $tmp;
+	}
+
+	/**
+	 * Set the look and feel of the x axis labels
+	 *
+	 * @param font_size an int argument.
+	 *   The font size.
+	 * @param colour a string argument.
+	 *   The hex colour value.
+	 * @param orientation an int argument.
+	 *   The orientation of the x-axis text.
+	 *   0 - Horizontal
+	 *   1 - Vertical
+	 *   2 - 45 degrees
+	 * @param step an int argument.
+	 *   Show the label on every $step label.
+	 * @param grid_colour a string argument.
+	 */
+	function set_x_label_style( $size, $colour='', $orientation=0, $step=-1, $grid_colour='' )
+	{
+		$this->x_label_style = $size;
+		
+		if( strlen( $colour ) > 0 )
+			$this->x_label_style .= ','. $colour;
+
+		if( $orientation > -1 )
+			$this->x_label_style .= ','. $orientation;
+
+		if( $step > 0 )
+			$this->x_label_style .= ','. $step;
+
+		if( strlen( $grid_colour ) > 0 )
+			$this->x_label_style .= ','. $grid_colour;
+	}
+
+	/**
+	 * Set the background colour.
+	 * @param colour a string argument.
+	 *   The hex colour value.
+	 */
+	function set_bg_colour( $colour )
+	{
+		$this->bg_colour = $colour;
+	}
+
+	/**
+	 * Set a background image.
+	 * @param url a string argument.
+	 *   The location of the image.
+	 * @param x a string argument.
+	 *   The x location of the image. 'Right', 'Left', 'Center'
+	 * @param y a string argument.
+	 *   The y location of the image. 'Top', 'Bottom', 'Middle'
+	 */
+	function set_bg_image( $url, $x='center', $y='center' )
+	{
+		$this->bg_image = $url;
+		$this->bg_image_x = $x;
+		$this->bg_image_y = $y;
+	}
+
+	/**
+	 * Attach a set of data (a line, area or bar chart) to the right Y axis.
+	 * @param data_number an int argument.
+	 *   The numbered order the data was attached using set_data.
+	 */
+	function attach_to_y_right_axis( $data_number )
+	{
+		$this->y2_lines[] = $data_number;
+	}
+
+	/**
+ 	 * Set the background colour of the grid portion of the chart.
+	 * @param col a string argument.
+	 *   The hex colour value of the background.
+	 * @param col2 a string argument.
+	 *   The hex colour value of the second colour if you want a gradient.
+	 * @param angle an int argument.
+	 *   The angle in degrees to make the gradient.
+	 */
+	function set_inner_background( $col, $col2='', $angle=-1 )
+	{
+		$this->inner_bg_colour = $col;
+		
+		if( strlen($col2) > 0 )
+			$this->inner_bg_colour_2 = $col2;
+		
+		if( $angle != -1 )
+			$this->inner_bg_angle = $angle;
+	}
+
+	/**
+	 * Internal function to build the y label style for y and y2
+	 */
+	function _set_y_label_style( $size, $colour )
+	{
+		$tmp = $size;
+		
+		if( strlen( $colour ) > 0 )
+			$tmp .= ','. $colour;
+		return $tmp;
+	}
+
+	/**
+	 * Set the look and feel of the y axis labels
+	 *
+	 * @param font_size an int argument.
+	 *   The font size.
+	 * @param colour a string argument.
+	 *   The hex colour value.
+	 */
+	function set_y_label_style( $size, $colour='' )
+	{
+		$this->y_label_style = $this->_set_y_label_style( $size, $colour );
+	}
+
+	/**
+	 * Set the look and feel of the right y axis labels
+	 *
+	 * @param font_size an int argument.
+	 *   The font size.
+	 * @param colour a string argument.
+	 *   The hex colour value.
+	 */
+	function set_y_right_label_style( $size, $colour='' )
+	{
+		$this->y_label_style_right = $this->_set_y_label_style( $size, $colour );
+	}
+
+	function set_x_max( $max )
+	{
+		$this->x_max = floatval( $max );
+	}
+
+	function set_x_min( $min )
+	{
+		$this->x_min = floatval( $min );
+	}
+
+	/**
+	 * Set the maximum value of the y axis.
+	 *
+	 * @param max an float argument.
+	 *   The maximum value.
+	 */
+	function set_y_max( $max )
+	{
+		$this->y_max = floatval( $max );
+	}
+
+	/**
+	 * Set the minimum value of the y axis.
+	 *
+	 * @param min an float argument.
+	 *   The minimum value.
+	 */
+	function set_y_min( $min )
+	{
+		$this->y_min = floatval( $min );
+	}
+
+	/**
+	 * Set the maximum value of the right y axis.
+	 *
+	 * @param max an float argument.
+	 *   The maximum value.
+	 */  
+	function set_y_right_max( $max )
+	{
+		$this->y2_max = floatval($max);
+	}
+
+	/**
+	 * Set the minimum value of the right y axis.
+	 *
+	 * @param min an float argument.
+	 *   The minimum value.
+	 */
+	function set_y_right_min( $min )
+	{
+		$this->y2_min = floatval($min);
+	}
+
+	/**
+	 * Show the y label on every $step label.
+	 *
+	 * @param val an int argument.
+	 *   Show the label on every $step label.
+	 */
+	function y_label_steps( $val )
+	{
+		 $this->y_steps = intval( $val );
+	}
+	
+	function title( $title, $style='' )
+	{
+		 $this->title = $this->esc( $title );
+		 if( strlen( $style ) > 0 )
+				 $this->title_style = $style;
+	}
+
+	/**
+	 * Set the parameters of the x legend.
+	 *
+	 * @param text a string argument.
+	 *   The text of the x legend.
+	 * @param font_size an int argument.
+	 *   The font size of the x legend text.
+	 * @param colour a string argument
+	 *   The hex value of the font colour. 
+	 */
+	function set_x_legend( $text, $size=-1, $colour='' )
+	{
+		$this->x_legend = $this->esc( $text );
+		if( $size > -1 )
+			$this->x_legend_size = $size;
+		
+		if( strlen( $colour )>0 )
+			$this->x_legend_colour = $colour;
+	}
+
+	/**
+	 * Set the size of the x label ticks.
+	 *
+	 * @param size an int argument.
+	 *   The size of the ticks in pixels.
+	 */
+	function set_x_tick_size( $size )
+	{
+		if( $size > 0 )
+				$this->x_tick_size = $size;
+	}
+
+	/**
+	 * Set how often you would like to show a tick on the x axis.
+	 *
+	 * @param steps an int argument.
+	 *   Show a tick ever $steps.
+	 */
+	function set_x_axis_steps( $steps )
+	{
+		if ( $steps > 0 )
+			$this->x_axis_steps = $steps;
+	}
+
+	/**
+	 * Set the depth in pixels of the 3D X axis slab.
+	 *
+	 * @param size an int argument.
+	 *   The depth in pixels of the 3D X axis.
+	 */
+	function set_x_axis_3d( $size )
+	{
+		if( $size > 0 )
+			$this->x_axis_3d = intval($size);
+	}
+	
+	/**
+	 * The private method of building the y legend output.
+	 */
+	function _set_y_legend( $text, $size, $colour )
+	{
+		$tmp = $text;
+	
+		if( $size > -1 )
+			$tmp .= ','. $size;
+
+		if( strlen( $colour )>0 )
+			$tmp .= ','. $colour;
+
+		return $tmp;
+		}
+
+	/**
+	 * Set the parameters of the y legend.
+	 *
+	 * @param text a string argument.
+	 *   The text of the y legend.
+	 * @param font_size an int argument.
+	 *   The font size of the y legend text.
+	 * @param colour a string argument
+	 *   The hex colour value of the font colour. 
+	 */
+	function set_y_legend( $text, $size=-1, $colour='' )
+	{
+		$this->y_legend = $this->_set_y_legend( $text, $size, $colour );
+	}
+
+	/**
+	 * Set the parameters of the right y legend.
+	 *
+	 * @param text a string argument.
+	 *   The text of the right y legend.
+	 * @param font_size an int argument.
+	 *   The font size of the right y legend text.
+	 * @param colour a string argument
+	 *   The hex value of the font colour. 
+	 */
+	function set_y_right_legend( $text, $size=-1, $colour='' )
+	{
+		$this->y_legend_right = $this->_set_y_legend( $text, $size, $colour );
+	}
+	
+	/**
+	 * Set the colour of the x axis line and grid.
+	 *
+	 * @param axis a string argument.
+	 *   The hex colour value of the x axis line.
+	 * @param grid a string argument.
+	 *   The hex colour value of the x axis grid. 
+	 */
+	function x_axis_colour( $axis, $grid='' )
+	{
+		$this->x_axis_colour = $axis;
+		$this->x_grid_colour = $grid;
+	}
+
+	/**
+	 * Set the colour of the y axis line and grid.
+	 *
+	 * @param axis a string argument.
+	 *   The hex colour value of the y axis line.
+	 * @param grid a string argument.
+	 *   The hex colour value of the y axis grid. 
+	 */
+	function y_axis_colour( $axis, $grid='' )
+	{
+		$this->y_axis_colour = $axis;
+
+		if( strlen( $grid ) > 0 )
+			$this->y_grid_colour = $grid;
+	}
+
+	/**
+	 * Set the colour of the right y axis line.
+	 *
+	 * @param colour a string argument.
+	 *   The hex colour value of the right y axis line.
+	 */
+	function y_right_axis_colour( $colour )
+	{
+		 $this->y2_axis_colour = $colour;
+	}
+
+	/**
+	 * Draw a line without markers on values.
+	 *
+	 * @param width an int argument.
+	 *   The width of the line in pixels.
+	 * @param colour a string argument.
+	 *   The hex colour value of the line.
+	 * @param text a string argument.
+	 *   The label of the line.
+	 * @param font_size an int argument.
+	 *   Font size of the label
+	 * @param circles an int argument
+	 *   Need to find out.
+	 */
+	function line( $width, $colour='', $text='', $size=-1, $circles=-1 )
+	{
+		$type = 'line'. $this->next_line();
+
+		$description = '';
+		if( $width > 0 )
+		{
+			$description .= $width;
+			$description .= ','. $colour;
+		}
+
+		if( strlen( $text ) > 0 )
+		{
+			$description.= ','. $text;
+			$description .= ','. $size;
+		}
+
+		if( $circles > 0 ) 
+			$description .= ','. $circles;
+
+		$this->lines[$type] = $description;
+	}
+
+	/**
+	 * Draw a line with solid dot markers on values.
+	 *
+	 * @param width an int argument.
+	 *   The width of the line in pixels.
+	 * @param dot_size an int argument.
+	 *   Size in pixels of the dot.
+	 * @param colour a string argument.
+	 *   The hex colour value of the line.
+	 * @param text a string argument.
+	 *   The label of the line.
+	 * @param font_size an int argument.
+	 *   Font size of the label.
+	 */
+	function line_dot( $width, $dot_size, $colour, $text='', $font_size='' )
+	{
+		$type = 'line_dot'. $this->next_line();
+
+		$description = "$width,$colour,$text";
+
+		if( strlen( $font_size ) > 0 )
+			$description .= ",$font_size,$dot_size";
+
+		$this->lines[$type] = $description;
+	}
+
+	/**
+	 * Draw a line with hollow dot markers on values.
+	 *
+	 * @param width an int argument.
+	 *   The width of the line in pixels.
+	 * @param dot_size an int argument.
+	 *   Size in pixels of the dot.
+	 * @param colour a string argument.
+	 *   The hex colour value of the line.
+	 * @param text a string argument.
+	 *   The label of the line.
+	 * @param font_size an int argument.
+	 *   Font size of the label.
+	 */
+	function line_hollow( $width, $dot_size, $colour, $text='', $font_size='' )
+	{
+		$type = 'line_hollow'. $this->next_line();
+
+		$description = "$width,$colour,$text";
+
+		if( strlen( $font_size ) > 0 )
+			$description .= ",$font_size,$dot_size";
+
+		$this->lines[$type] = $description;
+	}
+
+	/**
+	 * Draw an area chart.
+	 *
+	 * @param width an int argument.
+	 *   The width of the line in pixels.
+	 * @param dot_size an int argument.
+	 *   Size in pixels of the dot.
+	 * @param colour a string argument.
+	 *   The hex colour value of the line.
+	 * @param alpha an int argument.
+	 *   The percentage of transparency of the fill colour.
+	 * @param text a string argument.
+	 *   The label of the line.
+	 * @param font_size an int argument.
+	 *   Font size of the label.
+	 * @param fill_colour a string argument.
+	 *   The hex colour value of the fill colour.
+	 */
+	function area_hollow( $width, $dot_size, $colour, $alpha, $text='', $font_size='', $fill_colour='' )
+	{
+		$type = 'area_hollow'. $this->next_line();
+
+		$description = "$width,$dot_size,$colour,$alpha";
+
+		if( strlen( $text ) > 0 )
+			$description .= ",$text,$font_size";
+	
+		if( strlen( $fill_colour ) > 0 )
+			$description .= ','. $fill_colour;
+
+		$this->lines[$type] = $description;
+	}
+
+	/**
+	 * Draw a bar chart.
+	 *
+	 * @param alpha an int argument.
+	 *   The percentage of transparency of the bar colour.
+	 * @param colour a string argument.
+	 *   The hex colour value of the line.
+	 * @param text a string argument.
+	 *   The label of the line.
+	 * @param font_size an int argument.
+	 *   Font size of the label.
+	 */
+	function bar( $alpha, $colour='', $text='', $size=-1 )
+	{
+		$type = 'bar'. $this->next_line();
+
+		$description = $alpha .','. $colour .','. $text .','. $size;
+
+		$this->lines[$type] = $description;
+	}
+
+	/**
+	 * Draw a bar chart with an outline.
+	 *
+	 * @param alpha an int argument.
+	 *   The percentage of transparency of the bar colour.
+	 * @param colour a string argument.
+	 *   The hex colour value of the line.
+	 * @param colour_outline a strng argument.
+	 *   The hex colour value of the outline.
+	 * @param text a string argument.
+	 *   The label of the line.
+	 * @param font_size an int argument.
+	 *   Font size of the label.
+	 */
+	function bar_filled( $alpha, $colour, $colour_outline, $text='', $size=-1 )
+	{
+		$type = 'filled_bar'. $this->next_line();
+
+		$description = "$alpha,$colour,$colour_outline,$text,$size";
+
+		$this->lines[$type] = $description;
+	}
+
+	function bar_sketch( $alpha, $offset, $colour, $colour_outline, $text='', $size=-1 )
+	{
+		$type = 'bar_sketch'. $this->next_line();
+
+		$description = "$alpha,$offset,$colour,$colour_outline,$text,$size";
+
+		$this->lines[$type] = $description;
+	}
+
+	/**
+	 * Draw a 3D bar chart.
+	 *
+	 * @param alpha an int argument.
+	 *   The percentage of transparency of the bar colour.
+	 * @param colour a string argument.
+	 *   The hex colour value of the line.
+	 * @param text a string argument.
+	 *   The label of the line.
+	 * @param font_size an int argument.
+	 *   Font size of the label.
+	 */
+	function bar_3D( $alpha, $colour='', $text='', $size=-1 )
+	{
+		$type = 'bar_3d'. $this->next_line();
+
+		$description = $alpha .','. $colour .','. $text .','. $size;
+
+		$this->lines[$type] = $description;
+	}
+
+	/**
+	 * Draw a 3D bar chart that looks like glass.
+	 *
+	 * @param alpha an int argument.
+	 *   The percentage of transparency of the bar colour.
+	 * @param colour a string argument.
+	 *   The hex colour value of the line.
+	 * @param outline_colour a string argument.
+	 *   The hex colour value of the outline.
+	 * @param text a string argument.
+	 *   The label of the line.
+	 * @param font_size an int argument.
+	 *   Font size of the label.
+	 */
+	function bar_glass( $alpha, $colour, $outline_colour, $text='', $size=-1 )
+	{
+		$type = 'bar_glass'. $this->next_line();
+
+		$description = $alpha .','. $colour .','. $outline_colour .','. $text .','. $size;
+
+		$this->lines[$type] = $description;
+	}
+
+	/**
+	 * Draw a faded bar chart.
+	 *
+	 * @param alpha an int argument.
+	 *   The percentage of transparency of the bar colour.
+	 * @param colour a string argument.
+	 *   The hex colour value of the line.
+	 * @param text a string argument.
+	 *   The label of the line.
+	 * @param font_size an int argument.
+	 *   Font size of the label.
+	 */
+	function bar_fade( $alpha, $colour='', $text='', $size=-1 )
+	{
+		$type = 'bar_fade'. $this->next_line();
+
+		$description = $alpha .','. $colour .','. $text .','. $size;
+
+		$this->lines[$type] = $description;
+	}
+	
+	function candle( $data, $alpha, $line_width, $colour, $text='', $size=-1 )
+	{
+		$type = 'candle'. $this->next_line();
+
+		$description = $alpha .','. $line_width .','. $colour .','. $text .','. $size;
+
+		$this->lines[$type] = $description;
+		
+		$a = array();
+		foreach( $data as $can )
+			$a[] = $can->toString();
+			
+		$this->data[] = implode(',',$a);
+	}
+	
+	function hlc( $data, $alpha, $line_width, $colour, $text='', $size=-1 )
+	{
+		$type = 'hlc'. $this->next_line();
+
+		$description = $alpha .','. $line_width .','. $colour .','. $text .','. $size;
+
+		$this->lines[$type] = $description;
+		
+		$a = array();
+		foreach( $data as $can )
+			$a[] = $can->toString();
+			
+		$this->data[] = implode(',',$a);
+	}
+
+	function scatter( $data, $line_width, $colour, $text='', $size=-1 )
+	{
+		$type = 'scatter'. $this->next_line();
+
+		$description = $line_width .','. $colour .','. $text .','. $size;
+
+		$this->lines[$type] = $description;
+		
+		$a = array();
+		foreach( $data as $can )
+			$a[] = $can->toString();
+			
+		$this->data[] = implode(',',$a);
+	}
+
+
+	//
+	// Patch by, Jeremy Miller (14th Nov, 2007)
+	//
+	/**
+	 * Draw a pie chart.
+	 *
+	 * @param alpha an int argument.
+	 *   The percentage of transparency of the pie colour.
+	 * @param $style a string argument.
+	 *   CSS style string
+	 * @param label_colour a string argument.
+	 *   The hex colour value of the label.
+	 * @param gradient a boolean argument.
+	 *   Use a gradient true or false.
+	 * @param border_size an int argument.
+	 *   Size of the border in pixels.
+	 */
+	function pie( $alpha, $line_colour, $style, $gradient = true, $border_size = false )
+	{
+		$this->pie = $alpha.','.$line_colour.','.$style;
+		if( !$gradient )
+		{
+			$this->pie .= ','.!$gradient;
+		}
+		if ($border_size)
+		{
+			if ($gradient === false)
+			{
+				$this->pie .= ',';
+			}
+			$this->pie .= ','.$border_size;
+		}
+	}
+
+	/**
+	 * Set the values of the pie chart.
+	 *
+	 * @param values an array argument.
+	 *   An array of the values for the pie chart.
+	 * @param labels an array argument.
+	 *   An array of the labels for the pie pieces.
+	 * @param links an array argument.
+	 *   An array of the links to the pie pieces.
+	 */	
+	function pie_values( $values, $labels=array(), $links=array() )
+	{
+		$this->pie_values = implode(',',$values);
+		$this->pie_labels = implode(',',$labels);
+		$this->pie_links  = implode(",",$links);
+	}
+
+	/**
+	 * Set the pie slice colours.
+	 *
+	 * @param colours an array argument.
+	 *   The hex colour values of the pie pieces.
+	 */
+	function pie_slice_colours( $colours )
+	{
+		$this->pie_colours = implode(',',$colours);
+	}
+	
+
+	/**
+	 * Render the output.
+	 */
+	function render()
+	{
+		$tmp = array();
+		
+		//echo headers_sent() ?'yes':'no';
+		if( !headers_sent() )
+			header('content-type: text; charset: utf-8');
+
+		if($this->output_type == 'js')
+		{
+			$this->set_unique_id();
+		
+			$tmp[] = '<div id="' . $this->unique_id . '"></div>';
+			$tmp[] = '<script type="text/javascript" src="' . $this->js_path . 'swfobject.js"></script>';
+			$tmp[] = '<script type="text/javascript">';
+			$tmp[] = 'var so = new SWFObject("' . $this->swf_path . 'open-flash-chart.swf", "ofc", "'. $this->width . '", "' . $this->height . '", "9", "#FFFFFF");';
+			$tmp[] = 'so.addVariable("variables","true");';
+		}
+
+		if( strlen( $this->title ) > 0 )
+		{
+			$values = $this->title;
+			$values .= ','. $this->title_style;
+			$tmp[] = $this->format_output('title',$values);
+		}
+
+		if( strlen( $this->x_legend ) > 0 )
+		{
+			$values = $this->x_legend;
+			$values .= ','. $this->x_legend_size;
+			$values .= ','. $this->x_legend_colour;
+			$tmp[] = $this->format_output('x_legend',$values);
+		}
+	
+		if( strlen( $this->x_label_style ) > 0 )
+			$tmp[] = $this->format_output('x_label_style',$this->x_label_style);
+	
+		if( $this->x_tick_size > 0 )
+			$tmp[] = $this->format_output('x_ticks',$this->x_tick_size);
+
+		if( $this->x_axis_steps > 0 )
+			$tmp[] = $this->format_output('x_axis_steps',$this->x_axis_steps);
+
+		if( strlen( $this->x_axis_3d ) > 0 )
+			$tmp[] = $this->format_output('x_axis_3d',$this->x_axis_3d);
+		
+		if( strlen( $this->y_legend ) > 0 )
+			$tmp[] = $this->format_output('y_legend',$this->y_legend);
+		
+		if( strlen( $this->y_legend_right ) > 0 )
+			$tmp[] = $this->format_output('y2_legend',$this->y_legend_right);
+
+		if( strlen( $this->y_label_style ) > 0 )
+			$tmp[] = $this->format_output('y_label_style',$this->y_label_style);
+
+		$values = '5,10,'. $this->y_steps;
+		$tmp[] = $this->format_output('y_ticks',$values);
+
+		if( count( $this->lines ) == 0 && count($this->data_sets)==0 )
+		{
+			$tmp[] = $this->format_output($this->line_default['type'],$this->line_default['values']);	
+		}
+		else
+		{
+			foreach( $this->lines as $type=>$description )
+				$tmp[] = $this->format_output($type,$description);	
+		}
+	
+		$num = 1;
+		foreach( $this->data as $data )
+		{
+			if( $num==1 )
+			{
+				$tmp[] = $this->format_output( 'values', $data);
+			}
+			else
+			{
+				$tmp[] = $this->format_output('values_'. $num, $data);
+			}
+		
+			$num++;
+		}
+		
+		$num = 1;
+		foreach( $this->links as $link )
+		{
+			if( $num==1 )
+			{
+				$tmp[] = $this->format_output( 'links', $link);
+			}
+			else
+			{
+				$tmp[] = $this->format_output('links_'. $num, $link);
+			}
+		
+			$num++;
+		}
+
+		if( count( $this->y2_lines ) > 0 )
+		{
+			$tmp[] = $this->format_output('y2_lines',implode( ',', $this->y2_lines ));
+			//
+			// Should this be an option? I think so...
+			//
+			$tmp[] = $this->format_output('show_y2','true');
+		}
+
+		if( count( $this->x_labels ) > 0 )
+			$tmp[] = $this->format_output('x_labels',implode(',',$this->x_labels));
+		else
+		{
+			if( strlen($this->x_min) > 0 )
+				$tmp[] = $this->format_output('x_min',$this->x_min);
+				
+			if( strlen($this->x_max) > 0 )
+				$tmp[] = $this->format_output('x_max',$this->x_max);			
+		}
+		
+		$tmp[] = $this->format_output('y_min',$this->y_min);
+		$tmp[] = $this->format_output('y_max',$this->y_max);
+
+		if( strlen($this->y2_min) > 0 )
+			$tmp[] = $this->format_output('y2_min',$this->y2_min);
+			
+		if( strlen($this->y2_max) > 0 )
+			$tmp[] = $this->format_output('y2_max',$this->y2_max);
+		
+		if( strlen( $this->bg_colour ) > 0 )
+			$tmp[] = $this->format_output('bg_colour',$this->bg_colour);
+
+		if( strlen( $this->bg_image ) > 0 )
+		{
+			$tmp[] = $this->format_output('bg_image',$this->bg_image);
+			$tmp[] = $this->format_output('bg_image_x',$this->bg_image_x);
+			$tmp[] = $this->format_output('bg_image_y',$this->bg_image_y);
+		}
+
+		if( strlen( $this->x_axis_colour ) > 0 )
+		{
+			$tmp[] = $this->format_output('x_axis_colour',$this->x_axis_colour);
+			$tmp[] = $this->format_output('x_grid_colour',$this->x_grid_colour);
+		}
+
+		if( strlen( $this->y_axis_colour ) > 0 )
+			$tmp[] = $this->format_output('y_axis_colour',$this->y_axis_colour);
+
+		if( strlen( $this->y_grid_colour ) > 0 )
+			$tmp[] = $this->format_output('y_grid_colour',$this->y_grid_colour);
+  
+		if( strlen( $this->y2_axis_colour ) > 0 )
+			$tmp[] = $this->format_output('y2_axis_colour',$this->y2_axis_colour);
+		
+		if( strlen( $this->x_offset ) > 0 )
+			$tmp[] = $this->format_output('x_offset',$this->x_offset);
+
+		if( strlen( $this->inner_bg_colour ) > 0 )
+		{
+			$values = $this->inner_bg_colour;
+			if( strlen( $this->inner_bg_colour_2 ) > 0 )
+			{
+				$values .= ','. $this->inner_bg_colour_2;
+				$values .= ','. $this->inner_bg_angle;
+			}
+			$tmp[] = $this->format_output('inner_background',$values);
+		}
+	
+		if( strlen( $this->pie ) > 0 )
+		{
+			$tmp[] = $this->format_output('pie',$this->pie);
+			$tmp[] = $this->format_output('values',$this->pie_values);
+			$tmp[] = $this->format_output('pie_labels',$this->pie_labels);
+			$tmp[] = $this->format_output('colours',$this->pie_colours);
+			$tmp[] = $this->format_output('links',$this->pie_links);
+		}
+
+		if( strlen( $this->tool_tip ) > 0 )
+			$tmp[] = $this->format_output('tool_tip',$this->tool_tip);
+			
+			
+		
+		if( strlen( $this->y_format ) > 0 )
+			$tmp[] = $this->format_output('y_format',$this->y_format);
+			
+		if( strlen( $this->num_decimals ) > 0 )
+			$tmp[] = $this->format_output('num_decimals',$this->num_decimals);
+			
+		if( strlen( $this->is_fixed_num_decimals_forced ) > 0 )
+			$tmp[] = $this->format_output('is_fixed_num_decimals_forced',$this->is_fixed_num_decimals_forced);
+			
+		if( strlen( $this->is_decimal_separator_comma ) > 0 )
+			$tmp[] = $this->format_output('is_decimal_separator_comma',$this->is_decimal_separator_comma);
+			
+		if( strlen( $this->is_thousand_separator_disabled ) > 0 )
+			$tmp[] = $this->format_output('is_thousand_separator_disabled',$this->is_thousand_separator_disabled);
+
+
+		$count = 1;
+		foreach( $this->data_sets as $set )
+		{
+			$tmp[] = $set->toString( $this->output_type, $count>1?'_'.$count:'' );
+			$count++;
+		}
+		
+		if($this->output_type == 'js')
+		{
+			$tmp[] = 'so.write("' . $this->unique_id . '");';
+			$tmp[] = '</script>';
+		}
+		
+		return implode("\r\n",$tmp);
+	}
+}
+
+class line
+{
+	var $line_width;
+	var $colour;
+	var $_key;
+	var $key;
+	var $key_size;
+	// hold the data
+	var $data;
+	// extra tool tip info:
+	var $tips;
+	
+	function line( $line_width, $colour )
+	{
+		$this->var = 'line';
+		
+		$this->line_width = $line_width;
+		$this->colour = $colour;
+		$this->data = array();
+		$this->links = array();
+		$this->tips = array();
+		$this->_key = false;
+	}
+
+
+	function key( $key, $size )
+	{
+		$this->_key = true;
+		$this->key = graph::esc( $key );
+		$this->key_size = $size;
+	}
+	
+	function add( $data )
+	{
+		$this->data[] = $data;
+	}
+	
+	function add_link( $data, $link )
+	{
+		$this->data[] = $data;
+		$this->links[] = graph::esc( $link );
+	}
+	
+	function add_data_tip( $data, $tip )
+	{
+		$this->data[] = $data;
+		$this->tips[] = graph::esc( $tip );
+	}
+	
+	function add_data_link_tip( $data, $link, $tip )
+	{
+		$this->data[] = $data;
+		$this->links[] = graph::esc( $link );
+		$this->tips[] = graph::esc( $tip );
+	}
+	
+	// return the variables for this chart
+	function _get_variable_list()
+	{
+		$values = array();
+		$values[] = $this->line_width;
+		$values[] = $this->colour;
+		
+		if( $this->_key )
+		{
+			$values[] = $this->key;
+			$values[] = $this->key_size;
+		}
+		
+		return $values;
+	}
+	
+	function toString( $output_type, $set_num )
+	{
+		$values = implode( ',', $this->_get_variable_list() );
+		
+		$tmp = array();
+		
+		if( $output_type == 'js' )
+		{
+			$tmp[] = 'so.addVariable("'. $this->var.$set_num .'","'. $values . '");'; 
+
+			$tmp[] = 'so.addVariable("values'. $set_num .'","'. implode( ',', $this->data ) .'");';
+			
+			if( count( $this->links ) > 0 )
+				$tmp[] = 'so.addVariable("links'. $set_num .'","'. implode( ',', $this->links ) .'");';
+				
+			if( count( $this->tips ) > 0 )
+				$tmp[] = 'so.addVariable("tool_tips_set'. $set_num .'","'. implode( ',', $this->tips ) .'");';
+
+		}
+		else
+		{
+			$tmp[]  = '&'. $this->var. $set_num .'='. $values .'&';
+			$tmp[] = '&values'. $set_num .'='. implode( ',', $this->data ) .'&';
+			
+			if( count( $this->links ) > 0 )
+				$tmp[] = '&links'. $set_num .'='. implode( ',', $this->links ) .'&';
+				
+			if( count( $this->tips ) > 0 )
+				$tmp[] = '&tool_tips_set'. $set_num .'='. implode( ',', $this->tips ) .'&';	
+		}
+
+		return implode( "\r\n", $tmp );
+	}
+}
+
+class line_hollow extends line
+{
+	var $dot_size;
+	
+	function line_hollow( $line_width, $dot_size, $colour )
+	{
+		parent::line( $line_width, $colour );
+		$this->var = 'line_hollow';
+		$this->dot_size = $dot_size;
+	}
+	
+	// return the variables for this chart
+	function _get_variable_list()
+	{
+		$values = array();
+		$values[] = $this->line_width;
+		$values[] = $this->colour;
+		
+		if( $this->_key )
+		{
+			$values[] = $this->key;
+			$values[] = $this->key_size;
+		}
+		else
+		{
+			$values[] = '';
+			$values[] = '';
+		}
+		$values[] = $this->dot_size;
+		
+		return $values;
+	}
+}
+
+class line_dot extends line_hollow
+{
+	function line_dot( $line_width, $dot_size, $colour )
+	{
+		parent::line_hollow( $line_width, $dot_size,$colour );
+		$this->var = 'line_dot';
+	}
+}
+
+class bar
+{
+	var $colour;
+	var $alpha;
+	var $data;
+	var $links;
+	var $_key;
+	var $key;
+	var $key_size;
+	var $var;
+	// extra tool tip info:
+	var $tips;
+	
+	function bar( $alpha, $colour )
+	{
+		$this->var = 'bar';
+		
+		$this->alpha = $alpha;
+		$this->colour = $colour;
+		$this->data = array();
+		$this->links = array();
+		$this->tips = array();
+		$this->_key = false;
+	}
+
+	function key( $key, $size )
+	{
+		$this->_key = true;
+		$this->key = graph::esc( $key );
+		$this->key_size = $size;
+	}
+	
+	function add( $data )
+	{
+		$this->data[] = $data;
+	}
+
+	function add_link( $data, $link )
+	{
+		$this->data[] = $data;
+		$this->links[] = graph::esc( $link );
+	}
+	
+	function add_data_tip( $data, $tip )
+	{
+		$this->data[] = $data;
+		$this->tips[] = graph::esc( $tip );
+	}
+	
+	// return the variables for this
+	// bar chart
+	function _get_variable_list()
+	{
+		$values = array();
+		$values[] = $this->alpha;
+		$values[] = $this->colour;
+		
+		if( $this->_key )
+		{
+			$values[] = $this->key;
+			$values[] = $this->key_size;
+		}
+		
+		return $values;
+	}
+	
+	function toString( $output_type, $set_num )
+	{
+		$values = implode( ',', $this->_get_variable_list() );
+		
+		$tmp = array();
+		
+		if( $output_type == 'js' )
+		{
+			$tmp[] = 'so.addVariable("'. $this->var.$set_num .'","'. $values . '");'; 
+
+			$tmp[] = 'so.addVariable("values'. $set_num .'","'. implode( ',', $this->data ) .'");';
+			
+			if( count( $this->links ) > 0 )
+				$tmp[] = 'so.addVariable("links'. $set_num .'","'. implode( ',', $this->links ) .'");';
+				
+			if( count( $this->tips ) > 0 )
+				$tmp[] = 'so.addVariable("tool_tips_set'. $set_num .'","'. implode( ',', $this->tips ) .'");';
+
+		}
+		else
+		{
+			$tmp[]  = '&'. $this->var. $set_num .'='. $values .'&';
+			$tmp[] = '&values'. $set_num .'='. implode( ',', $this->data ) .'&';
+			
+			if( count( $this->links ) > 0 )
+				$tmp[] = '&links'. $set_num .'='. implode( ',', $this->links ) .'&';
+				
+			if( count( $this->tips ) > 0 )
+				$tmp[] = '&tool_tips_set'. $set_num .'='. implode( ',', $this->tips ) .'&';	
+		}
+
+		return implode( "\r\n", $tmp );
+	}
+	
+}
+
+class bar_3d extends bar
+{
+	function bar_3d( $alpha, $colour )
+	{
+		parent::bar( $alpha, $colour );
+		$this->var = 'bar_3d';
+	}
+}
+
+class bar_fade extends bar
+{
+	function bar_fade( $alpha, $colour )
+	{
+		parent::bar( $alpha, $colour );
+		$this->var = 'bar_fade';
+	}
+}
+
+class bar_outline extends bar
+{
+	var $outline_colour;
+	
+	function bar_outline( $alpha, $colour, $outline_colour )
+	{
+		parent::bar( $alpha, $colour );
+		$this->var = 'filled_bar';
+		$this->outline_colour = $outline_colour;
+	}
+	
+	// override the base method
+	function _get_variable_list()
+	{
+		$values = array();
+		$values[] = $this->alpha;
+		$values[] = $this->colour;
+		$values[] = $this->outline_colour;
+		
+		if( $this->_key )
+		{
+			$values[] = $this->key;
+			$values[] = $this->key_size;
+		}
+		
+		return $values;
+	}
+}
+
+class bar_glass extends bar_outline
+{
+	function bar_glass( $alpha, $colour, $outline_colour )
+	{
+		parent::bar_outline( $alpha, $colour, $outline_colour );
+		$this->var = 'bar_glass';
+	}
+}
+
+//
+// this has an outline colour and a 'jiggle' parameter
+// called offset
+//
+class bar_sketch extends bar_outline
+{
+	var $offset;
+	
+	function bar_sketch( $alpha, $offset, $colour, $outline_colour )
+	{
+		parent::bar_outline( $alpha, $colour, $outline_colour );
+		$this->var = 'bar_sketch';
+		$this->offset = $offset;
+	}
+	
+	// override the base method
+	function _get_variable_list()
+	{
+		$values = array();
+		$values[] = $this->alpha;
+		$values[] = $this->offset;
+		$values[] = $this->colour;
+		$values[] = $this->outline_colour;
+		
+		if( $this->_key )
+		{
+			$values[] = $this->key;
+			$values[] = $this->key_size;
+		}
+		
+		return $values;
+	}
+}
+
+class candle
+{
+	var $out;
+	
+	function candle( $high, $open, $close, $low )
+	{
+		$this->out = array();
+		$this->out[] = $high;
+		$this->out[] = $open;
+		$this->out[] = $close;
+		$this->out[] = $low;
+	}
+	
+	function toString()
+	{
+		return '['. implode( ',', $this->out ) .']';
+	}
+}
+
+class hlc
+{
+	var $out;
+	
+	function hlc( $high, $low, $close )
+	{
+		$this->out = array();
+		$this->out[] = $high;
+		$this->out[] = $low;
+		$this->out[] = $close;
+	}
+	
+	function toString()
+	{
+		return '['. implode( ',', $this->out ) .']';
+	}
+}
+
+class point
+{
+	var $out;
+	
+	function point( $x, $y, $size_px )
+	{
+		$this->out = array();
+		$this->out[] = $x;
+		$this->out[] = $y;
+		$this->out[] = $size_px;
+	}
+	
+	function toString()
+	{
+		return '['. implode( ',', $this->out ) .']';
+	}
+}
+
+// PIWIK SPECIAL ALIAS HACK - when updating Open Flash Chart, leave this line unchanged
+class graph extends Piwik_Visualization_OpenFlashChart {}
\ No newline at end of file
diff --git a/core/Visualization/Sparkline.php b/core/Visualization/Sparkline.php
new file mode 100644
index 0000000000..65f529f338
--- /dev/null
+++ b/core/Visualization/Sparkline.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: OpenFlashChart.php 386 2008-03-18 19:27:54Z julien $
+ *
+ * @package Piwik_Visualization
+ */
+
+require_once 'sparkline/lib/Sparkline_Line.php';
+
+
+/**
+ * Renders a sparkline image given a PHP data array.
+ * Using the Sparkline PHP Graphing Library sparkline.org 
+ * 
+ * @package Piwik_Visualization
+ */
+class Piwik_Visualization_Sparkline implements Piwik_iView
+{
+	/**
+	 * Sets data. Must have format: array( array('value' => X),array('value' =>Y ), ...)
+	 *
+	 * @param array $data
+	 */
+	function setData($data)
+	{
+		$this->data = $data;
+	}
+	
+	
+	function main()
+	{
+		$data = $this->data;
+		$sparkline = new Sparkline_Line();
+		
+		$sparkline->SetColor('lineColor', 22,44,74); // dark blue
+		$sparkline->SetColorHtml('red', '#FF7F7F');
+		$sparkline->SetColorHtml('blue', '#55AAFF');
+		$sparkline->SetColorHtml('green', '#75BF7C');
+//		$sparkline->SetDebugLevel(DEBUG_NONE);
+//		$sparkline->SetDebugLevel(DEBUG_ERROR | DEBUG_WARNING | DEBUG_STATS | DEBUG_CALLS | DEBUG_DRAW, 'log.txt');
+		
+		$data = array_reverse($data);
+		$min = $max= $last = null;
+		$i = 0;
+		
+		foreach($this->data as $row)
+		{
+			$value = $row['value'];
+					
+			$sparkline->SetData($i, $value);
+			if(	null == $min || $value <= $min[1])
+			{
+				$min = array($i, $value);
+			}
+		
+			if(null == $max || $value >= $max[1]) 
+			{
+				$max = array($i, $value);
+			}
+		
+			$last = array($i, $value);
+			
+			$i++;			
+		}
+		$sparkline->SetYMin(0);
+		$sparkline->SetPadding(2); // setpadding is additive
+		$sparkline->SetPadding(0,//13,//font height 
+					3, //4 * (strlen("$last[1]")), 
+					0, //imagefontheight(FONT_2), 
+					0);
+		$font = FONT_2;
+		$sparkline->SetFeaturePoint($min[0]-1,$min[1],'red', 5);
+		$sparkline->SetFeaturePoint($max[0]-1,$max[1],  'green', 5);
+		$sparkline->SetFeaturePoint($last[0]-1, $last[1], 'blue',5);
+		$sparkline->SetLineSize(3); // for renderresampled, linesize is on virtual image
+		$sparkline->RenderResampled(100, 20, 'lineColor');
+		
+		$this->sparkline = $sparkline;
+	}
+	
+	function render()
+	{
+		$this->sparkline->Output();
+	}
+}
\ No newline at end of file
diff --git a/core/iView.php b/core/iView.php
new file mode 100644
index 0000000000..c35edafa50
--- /dev/null
+++ b/core/iView.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: APIable.php 162 2008-01-14 04:27:21Z matt $
+ * 
+ * @package Piwik_Visualization
+ */
+
+/**
+ * Piwik_ViewDataTable must create a $view attribute which implements this interface.
+ * 
+ * @package Piwik_Visualization
+ */
+interface Piwik_iView
+{
+	/**
+	 * Outputs the data.
+	 * Either outputs html, xml, an image, nothing, etc.
+	 * 
+	 * @return mixed
+	 *
+	 */
+	function render();
+}
\ No newline at end of file
diff --git a/core/testMinimumPhpVersion.php b/core/testMinimumPhpVersion.php
new file mode 100644
index 0000000000..52c7c6d608
--- /dev/null
+++ b/core/testMinimumPhpVersion.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ * 
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id: Common.php 168 2008-01-14 05:26:43Z matt $
+ * 
+ * @package Piwik
+ */
+
+/**
+ * This file is executed before anything else. It checks the minimum Php version required to run Piwik.
+ * This is done here because on PHP4 piwik would output an error directly.
+ * Let's try to be user friendly :)
+ * 
+ * @package Piwik
+ */
+
+// we prefix the global variables
+$piwik_minimumPhpVersion = '5.1.3';
+$piwik_currentVersion = phpversion();
+
+if( version_compare($piwik_minimumPhpVersion , $piwik_currentVersion ) >= 0 )
+{
+	$piwik_errorMessage = "<p><b>To run Piwik you need at least PHP version $piwik_minimumPhpVersion </b></p> 
+				<p>Unfortunately it seems your webserver is using PHP version $piwik_currentVersion. </p>
+				<p>Please try to update your PHP version, Piwik is really worth it! Nowadays most web hosts 
+				support PHP $piwik_minimumPhpVersion. </p>";
+}					
+
+$piwik_zend_compatibility_mode = ini_get("zend.ze1_compatibility_mode");
+
+if($piwik_zend_compatibility_mode == 1)
+{
+	$piwik_errorMessage = "<p><b>Piwik is not compatible with the directive <code>zend.ze1_compatibility_mode = On</code></b></p> 
+				<p>It seems your php.ini file has <pre>zend.ze1_compatibility_mode = On</pre>It makes PHP5 behave like PHP4.
+				If you want to use Piwik you need to set <pre>zend.ze1_compatibility_mode = Off</pre> in your php.ini configuration file. You may have to ask your system administrator.</p>";
+}
+
+function Piwik_ExitWithMessage($message)
+{
+	$html = '<html>
+				<head>
+					<title>Piwik &rsaquo; Error</title>
+					<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+				<style>				
+				html { background: #eee; }				
+				body {
+					background: #fff;
+					color: #000;
+					font-family: Georgia, "Times New Roman", Times, serif;
+					margin-left: 20%;
+					margin-top: 25px;
+					margin-right: 20%;
+					padding: .2em 2em;
+				}
+				#h1 {
+					color: #006;
+					font-size: 45px;
+					font-weight: lighter;
+				}				
+				#subh1 {
+					color: #879DBD;
+					font-size: 25px;
+					font-weight: lighter;
+				}
+				p, li, dt {
+					line-height: 140%;
+					padding-bottom: 2px;
+				}
+				a { color: #006; }
+				ul, ol { padding: 5px 5px 5px 20px; }
+				#logo { margin-bottom: 2em; }
+				code { margin-left: 40px; }
+				</style>
+				</head>
+				<body>
+					<span id="h1">Piwik </span><span id="subh1"> # open source web analytics</span>
+					<p>'.$message.'</p>				
+					<ul>
+						<li><a target="_blank" href="misc/redirectToUrl.php?url=http://piwik.org">Piwik homepage</a></li>
+						<li><a target="_blank" href="misc/redirectToUrl.php?url=http://piwik.org/demo">Piwik demo</a></li>
+					</ul>
+				</body>
+				</html>';
+	echo $html;
+	exit;
+}
+
+if(isset($piwik_errorMessage))
+{
+	Piwik_ExitWithMessage($piwik_errorMessage);
+}
+
+// we now include the upgradephp package to define some functions used in piwik 
+// that may not be defined in the current php version
+require_once "libs/upgradephp/upgrade.php";
\ No newline at end of file
-- 
GitLab