Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authormatt <matt@59fd770c-687e-43c8-a1e3-f5a4ff64c105>2010-06-23 07:45:36 +0400
committermatt <matt@59fd770c-687e-43c8-a1e3-f5a4ff64c105>2010-06-23 07:45:36 +0400
commit7e1b5d6b762340cbff1bb928d15815980c7649a7 (patch)
treee07da179b9e1372866d2349777bd1cc6b4c9e8cf /core
parent999f46479294713104c962bfe7469e9b6e7a4bbf (diff)
parentc98ea06f2cccec81c6ccce49162a583494e44d91 (diff)
Diffstat (limited to 'core')
-rw-r--r--core/API/DataTableGenericFilter.php23
-rw-r--r--core/API/DocumentationGenerator.php2
-rw-r--r--core/API/Proxy.php11
-rw-r--r--core/API/Request.php29
-rw-r--r--core/API/ResponseBuilder.php83
-rw-r--r--core/Access.php50
-rw-r--r--core/Archive.php2
-rw-r--r--core/Archive/Array/IndexedByDate.php16
-rw-r--r--core/Archive/Array/IndexedBySite.php2
-rw-r--r--core/Archive/Single.php24
-rw-r--r--core/ArchiveProcessing.php256
-rw-r--r--core/ArchiveProcessing/Day.php44
-rw-r--r--core/ArchiveProcessing/Period.php80
-rw-r--r--core/CacheFile.php9
-rw-r--r--core/Common.php229
-rw-r--r--core/Config.php90
-rw-r--r--core/Controller.php280
-rw-r--r--core/Cookie.php22
-rw-r--r--core/DataFiles/SearchEngines.php276
-rw-r--r--core/DataTable.php17
-rw-r--r--core/DataTable/Filter/AddColumnsWhenShowAllColumns.php8
-rw-r--r--core/DataTable/Filter/ColumnCallbackAddColumnPercentage.php53
-rw-r--r--core/DataTable/Filter/ColumnCallbackAddColumnQuotient.php80
-rw-r--r--core/DataTable/Filter/ColumnCallbackAddMetadata.php11
-rw-r--r--core/DataTable/Filter/ColumnCallbackDeleteRow.php3
-rw-r--r--core/DataTable/Filter/ColumnCallbackReplace.php8
-rw-r--r--core/DataTable/Filter/ColumnDelete.php36
-rw-r--r--core/DataTable/Filter/ReplaceColumnNames.php5
-rw-r--r--core/DataTable/Filter/Sort.php4
-rw-r--r--core/DataTable/Filter/UpdateColumnsWhenShowAllGoals.php48
-rw-r--r--core/DataTable/Manager.php12
-rw-r--r--core/DataTable/Renderer.php54
-rw-r--r--core/DataTable/Renderer/Console.php26
-rw-r--r--core/DataTable/Renderer/Csv.php24
-rw-r--r--core/DataTable/Renderer/Html.php8
-rw-r--r--core/DataTable/Renderer/Json.php23
-rw-r--r--core/DataTable/Renderer/Php.php14
-rw-r--r--core/DataTable/Renderer/Rss.php13
-rw-r--r--core/DataTable/Renderer/Tsv.php34
-rw-r--r--core/DataTable/Renderer/Xml.php14
-rw-r--r--core/DataTable/Row.php2
-rw-r--r--core/Date.php425
-rw-r--r--core/Db/Adapter.php (renamed from core/Db.php)87
-rw-r--r--core/Db/Adapter/Mysqli.php (renamed from core/Db/Mysqli.php)49
-rw-r--r--core/Db/Adapter/Pdo/Mssql.php253
-rw-r--r--core/Db/Adapter/Pdo/Mysql.php (renamed from core/Db/Pdo/Mysql.php)47
-rw-r--r--core/Db/Adapter/Pdo/Pgsql.php (renamed from core/Db/Pdo/Pgsql.php)84
-rw-r--r--core/Db/Schema.php282
-rw-r--r--core/Db/Schema/Myisam.php484
-rw-r--r--core/ExceptionHandler.php2
-rw-r--r--core/Form.php14
-rw-r--r--core/FrontController.php35
-rw-r--r--core/Http.php385
-rw-r--r--core/Log.php25
-rw-r--r--core/Log/APICall.php10
-rw-r--r--core/Log/Error.php10
-rw-r--r--core/Log/Exception.php4
-rw-r--r--core/Log/Message.php2
-rw-r--r--core/Nonce.php81
-rw-r--r--core/Option.php17
-rw-r--r--core/Period.php32
-rw-r--r--core/Period/Day.php9
-rw-r--r--core/Period/Month.php14
-rw-r--r--core/Period/Range.php11
-rw-r--r--core/Period/Year.php2
-rw-r--r--core/Piwik.php2018
-rw-r--r--core/Plugin.php32
-rw-r--r--core/PluginsFunctions/AdminMenu.php50
-rw-r--r--core/PluginsFunctions/Menu.php20
-rw-r--r--core/PluginsFunctions/Sql.php85
-rw-r--r--core/PluginsFunctions/WidgetsList.php33
-rw-r--r--core/PluginsManager.php81
-rw-r--r--core/Session.php72
-rw-r--r--core/Site.php35
-rw-r--r--core/Smarty.php3
-rw-r--r--core/SmartyPlugins/function.ajaxErrorDiv.php30
-rw-r--r--core/SmartyPlugins/function.ajaxLoadingDiv.php35
-rw-r--r--core/SmartyPlugins/function.ajaxRequestErrorDiv.php22
-rw-r--r--core/SmartyPlugins/function.assignTopBar.php5
-rw-r--r--core/SmartyPlugins/modifier.inlineHelp.php26
-rw-r--r--core/SmartyPlugins/modifier.money.php27
-rw-r--r--core/SmartyPlugins/modifier.stripeol.php32
-rw-r--r--core/SmartyPlugins/modifier.translate.php14
-rw-r--r--core/SmartyPlugins/modifier.urlRewriteWithParameters.php1
-rw-r--r--core/SmartyPlugins/outputfilter.ajaxcdn.php4
-rw-r--r--core/TablePartitioning.php12
-rw-r--r--core/Tracker.php52
-rw-r--r--core/Tracker/Action.php51
-rw-r--r--core/Tracker/Config.php41
-rw-r--r--core/Tracker/Db.php19
-rw-r--r--core/Tracker/Generator.php621
-rw-r--r--core/Tracker/Generator/Tracker.php53
-rw-r--r--core/Tracker/Generator/Visit.php44
-rw-r--r--core/Tracker/GoalManager.php33
-rw-r--r--core/Tracker/Visit.php245
-rw-r--r--core/Tracker/javascriptTag.tpl2
-rw-r--r--core/Translate.php26
-rw-r--r--core/UpdateCheck.php15
-rw-r--r--core/Updater.php99
-rw-r--r--core/Updates.php (renamed from core/iUpdate.php)17
-rw-r--r--core/Updates/0.2.10.php40
-rw-r--r--core/Updates/0.2.12.php21
-rw-r--r--core/Updates/0.2.13.php16
-rw-r--r--core/Updates/0.2.24.php21
-rw-r--r--core/Updates/0.2.27.php20
-rw-r--r--core/Updates/0.2.32.php37
-rw-r--r--core/Updates/0.2.33.php19
-rw-r--r--core/Updates/0.2.34.php4
-rw-r--r--core/Updates/0.2.35.php15
-rw-r--r--core/Updates/0.2.37.php15
-rw-r--r--core/Updates/0.4.1.php19
-rw-r--r--core/Updates/0.4.2.php21
-rw-r--r--core/Updates/0.4.3.php65
-rw-r--r--core/Updates/0.4.4.php12
-rw-r--r--core/Updates/0.4.php22
-rw-r--r--core/Updates/0.5.4.php75
-rw-r--r--core/Updates/0.5.5.php46
-rw-r--r--core/Updates/0.5.php31
-rw-r--r--core/Updates/0.6-rc1.php68
-rw-r--r--core/Updates/0.6.2.php47
-rw-r--r--core/Updates/0.6.3.php54
-rw-r--r--core/Url.php131
-rw-r--r--core/Version.php2
-rw-r--r--core/View.php93
-rw-r--r--core/ViewDataTable.php103
-rw-r--r--core/ViewDataTable/Cloud.php6
-rw-r--r--core/ViewDataTable/GenerateGraphData.php8
-rw-r--r--core/ViewDataTable/GenerateGraphData/ChartEvolution.php54
-rw-r--r--core/ViewDataTable/GenerateGraphHTML.php26
-rw-r--r--core/ViewDataTable/GenerateGraphHTML/ChartEvolution.php21
-rw-r--r--core/ViewDataTable/HtmlTable.php14
-rw-r--r--core/ViewDataTable/HtmlTable/Goals.php122
-rw-r--r--core/Visualization/Chart.php11
-rw-r--r--core/Visualization/Sparkline.php25
-rw-r--r--core/testMinimumPhpVersion.php6
135 files changed, 6450 insertions, 3184 deletions
diff --git a/core/API/DataTableGenericFilter.php b/core/API/DataTableGenericFilter.php
index d339a080e7..c8843059c6 100644
--- a/core/API/DataTableGenericFilter.php
+++ b/core/API/DataTableGenericFilter.php
@@ -58,7 +58,8 @@ class Piwik_API_DataTableGenericFilter
'filter_add_columns_when_show_all_columns' => array('integer')
),
'UpdateColumnsWhenShowAllGoals' => array(
- 'filter_update_columns_when_show_all_goals' => array('integer')
+ 'filter_update_columns_when_show_all_goals' => array('integer'),
+ 'filter_only_display_idgoal' => array('integer', Piwik_DataTable_Filter_UpdateColumnsWhenShowAllGoals::GOALS_OVERVIEW),
),
'Sort' => array(
'filter_sort_column' => array('string'),
@@ -84,15 +85,23 @@ class Piwik_API_DataTableGenericFilter
if($datatable instanceof Piwik_DataTable_Array )
{
$tables = $datatable->getArray();
+ $filterWasApplied = false;
foreach($tables as $table)
{
- $this->applyGenericFilters($table);
+ $filterWasApplied = $this->applyGenericFilters($table);
+ // if no generic filter was applied to the first table, we can return
+ // as no filter would be applied to any other dataTable
+ if(!$filterWasApplied)
+ {
+ return;
+ }
}
return;
}
$genericFilters = self::getGenericFiltersInformation();
+ $filterApplied = false;
foreach($genericFilters as $filterName => $parameters)
{
$filterParameters = array();
@@ -123,18 +132,14 @@ class Piwik_API_DataTableGenericFilter
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);
- // use Reflection to create a new instance of the filter, given parameters $filterParameters
- $reflectionObj = new ReflectionClass($class);
- $filter = $reflectionObj->newInstanceArgs($filterParameters);
+ $datatable->filter($filterName, $filterParameters);
+ $filterApplied = true;
}
}
+ return $filterApplied;
}
}
diff --git a/core/API/DocumentationGenerator.php b/core/API/DocumentationGenerator.php
index 8a1d71de9f..0d959171fb 100644
--- a/core/API/DocumentationGenerator.php
+++ b/core/API/DocumentationGenerator.php
@@ -96,7 +96,7 @@ class Piwik_API_DocumentationGenerator
$str .= "</span>";
}
$str .= '</small>';
- $str .= "\n<br>";
+ $str .= "\n<br />";
}
}
return $str;
diff --git a/core/API/Proxy.php b/core/API/Proxy.php
index 26c11288c9..9bd12e25b4 100644
--- a/core/API/Proxy.php
+++ b/core/API/Proxy.php
@@ -12,6 +12,9 @@
/**
* To differentiate between "no value" and default value of null
+ *
+ * @package Piwik
+ * @subpackage Piwik_API
*/
class Piwik_API_Proxy_NoDefaultValue {}
@@ -215,8 +218,8 @@ class Piwik_API_Proxy
}
}
} 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.");
- }
+ throw new Exception(Piwik_TranslateException('General_ExceptionVariableNotFound', array($name)));
+ }
$finalParameters[] = $requestValue;
}
return $finalParameters;
@@ -232,7 +235,7 @@ class Piwik_API_Proxy
$module = self::getModuleNameFromClassName($fileName);
$path = PIWIK_INCLUDE_PATH . '/plugins/' . $module . '/API.php';
- if(Zend_Loader::isReadable($path))
+ if(is_readable($path))
{
require_once $path; // prefixed by PIWIK_INCLUDE_PATH
}
@@ -282,7 +285,7 @@ class Piwik_API_Proxy
{
if(!$this->isMethodAvailable($className, $methodName))
{
- throw new Exception("The method '$methodName' does not exist or is not available in the module '".$className."'.");
+ throw new Exception(Piwik_TranslateException('General_ExceptionMethodNotFound', array($methodName,$className)));
}
}
diff --git a/core/API/Request.php b/core/API/Request.php
index b6d5f09f2a..c0ad14d5fc 100644
--- a/core/API/Request.php
+++ b/core/API/Request.php
@@ -87,7 +87,7 @@ class Piwik_API_Request
$outputFormat = strtolower(Piwik_Common::getRequestVar('format', 'xml', 'string', $this->request));
// create the response
- $response = new Piwik_API_ResponseBuilder($this->request, $outputFormat);
+ $response = new Piwik_API_ResponseBuilder($outputFormat, $this->request);
try {
// read parameters
@@ -101,13 +101,7 @@ class Piwik_API_Request
}
$module = "Piwik_" . $module . "_API";
- // if a token_auth is specified in the API request, we load the right permissions
- $token_auth = Piwik_Common::getRequestVar('token_auth', '', 'string', $this->request);
- if($token_auth)
- {
- Piwik_PostEvent('API.Request.authenticate', $token_auth);
- Zend_Registry::get('access')->reloadAccess();
- }
+ self::reloadAuthUsingTokenAuth($this->request);
// call the method
$returnedValue = Piwik_API_Proxy::getInstance()->call($module, $method, $this->request);
@@ -120,6 +114,25 @@ class Piwik_API_Request
}
/**
+ * If the token_auth is found in the $request parameter,
+ * the current session will be authenticated using this token_auth.
+ * It will overwrite the previous Auth object.
+ *
+ * @param $request If null, uses the default request ($_GET)
+ * @return void
+ */
+ static public function reloadAuthUsingTokenAuth($request = null)
+ {
+ // if a token_auth is specified in the API request, we load the right permissions
+ $token_auth = Piwik_Common::getRequestVar('token_auth', '', 'string', $request);
+ if($token_auth)
+ {
+ Piwik_PostEvent('API.Request.authenticate', $token_auth);
+ Zend_Registry::get('access')->reloadAccess();
+ }
+ }
+
+ /**
* Returns array( $class, $method) from the given string $class.$method
*
* @return array
diff --git a/core/API/ResponseBuilder.php b/core/API/ResponseBuilder.php
index a632163108..1d94dc9888 100644
--- a/core/API/ResponseBuilder.php
+++ b/core/API/ResponseBuilder.php
@@ -19,7 +19,7 @@ class Piwik_API_ResponseBuilder
private $request = null;
private $outputFormat = null;
- public function __construct($request, $outputFormat)
+ public function __construct($outputFormat, $request = array())
{
$this->request = $request;
$this->outputFormat = $outputFormat;
@@ -49,11 +49,17 @@ class Piwik_API_ResponseBuilder
*
* @throws Exception If an object/resource is returned, if any of conversion fails, etc.
*
- * @param mixed The initial returned value, before post process
+ * @param mixed The initial returned value, before post process. If set to null, success response is returned.
* @return mixed Usually a string, but can still be a PHP data structure if the format requested is 'original'
*/
- public function getResponse($value)
+ public function getResponse($value = null)
{
+ // when null or void is returned from the api call, we handle it as a successful operation
+ if(!isset($value))
+ {
+ return $this->handleSuccess();
+ }
+
// 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
@@ -73,12 +79,6 @@ class Piwik_API_ResponseBuilder
return $this->handleArray($value);
}
- // when null or void is returned from the api call, we handle it as a successful operation
- if(!isset($value))
- {
- return $this->handleSuccess();
- }
-
// original data structure requested, we return without process
if( $this->outputFormat == 'original' )
{
@@ -104,41 +104,30 @@ class Piwik_API_ResponseBuilder
*/
public function getResponseException(Exception $e)
{
- $message = htmlentities($e->getMessage(), ENT_COMPAT, "UTF-8");
- switch($this->outputFormat)
+ $format = strtolower($this->outputFormat);
+
+ if( $format == 'original' )
{
- case 'original':
- throw $e;
- break;
- case 'xml':
- @header("Content-Type: text/xml;charset=utf-8");
- $return =
- "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" .
- "<result>\n".
- "\t<error message=\"".$message."\" />\n".
- "</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":"'.$message.'"}';
- break;
- case 'php':
- $return = array('result' => 'error', 'message' => $message);
- if($this->caseRendererPHPSerialize())
- {
- $return = serialize($return);
- }
- break;
- case 'html':
- $return = nl2br($message);
- break;
- default:
- $return = 'Error: '.$message;
- break;
+ throw $e;
}
- return $return;
+
+ try
+ {
+ $renderer = Piwik_DataTable_Renderer::factory($format);
+
+ } catch (Exception $e) {
+
+ return "Error: " . $e->getMessage();
+ }
+
+ $renderer->setException($e);
+
+ if($format == 'php')
+ {
+ $renderer->setSerialize($this->caseRendererPHPSerialize());
+ }
+
+ return $renderer->renderException();
}
/**
@@ -202,6 +191,10 @@ class Piwik_API_ResponseBuilder
{
$renderer->setTableId($this->request['method']);
}
+ else if($format == 'csv')
+ {
+ $renderer->setConvertToUnicode( Piwik_Common::getRequestVar('convertToUnicode', true, 'int') );
+ }
return $renderer->render();
}
@@ -226,7 +219,7 @@ class Piwik_API_ResponseBuilder
"</result>";
break;
case 'json':
- @header( "Content-type: application/json" );
+ @header( "Content-Type: application/json" );
$return = '{"result":"success", "message":"'.$message.'"}';
break;
case 'php':
@@ -238,7 +231,7 @@ class Piwik_API_ResponseBuilder
break;
case 'csv':
- header("Content-type: application/vnd.ms-excel");
+ header("Content-Type: application/vnd.ms-excel");
header("Content-Disposition: attachment; filename=piwik-report-export.csv");
$return = "message\n".$message;
break;
@@ -296,5 +289,5 @@ class Piwik_API_ResponseBuilder
$dataTable->addRowsFromSimpleArray($array);
return $this->getRenderedDataTable($dataTable);
}
- }
+ }
}
diff --git a/core/Access.php b/core/Access.php
index 2485a7658b..4d08347b44 100644
--- a/core/Access.php
+++ b/core/Access.php
@@ -146,10 +146,7 @@ class Piwik_Access
// 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 = Piwik_FetchAll("SELECT access, t2.idsite
- FROM ".Piwik::prefixTable('access'). " as t1
- JOIN ".Piwik::prefixTable('site')." as t2 USING (idsite) ".
- " WHERE login = ?", $this->login);
+ $accessRaw = Piwik_FetchAll(self::getSqlAccessSite("access, t2.idsite"), $this->login);
foreach($accessRaw as $access)
{
$this->idsitesByAccess[$access['access']][] = $access['idsite'];
@@ -158,6 +155,20 @@ class Piwik_Access
}
/**
+ * Returns the SQL query joining sites and access table for a given login
+ *
+ * @param $select eg. "MIN(ts_created)"
+ * @return string SQL query
+ */
+ static public function getSqlAccessSite($select)
+ {
+ return "SELECT ". $select ."
+ FROM ".Piwik_Common::prefixTable('access'). " as t1
+ JOIN ".Piwik_Common::prefixTable('site')." as t2 USING (idsite) ".
+ " WHERE login = ?";
+ }
+
+ /**
* Reload super user access
*
* @return bool
@@ -165,7 +176,7 @@ class Piwik_Access
protected function reloadAccessSuperUser()
{
$this->isSuperUser = true;
- $this->idsitesByAccess['superuser'] = Piwik_SitesManager_API::getAllSitesId();
+ $this->idsitesByAccess['superuser'] = Piwik_SitesManager_API::getInstance()->getAllSitesId();
return true;
}
@@ -173,9 +184,17 @@ class Piwik_Access
* We bypass the normal auth method and give the current user Super User rights.
* This should be very carefully used.
*/
- public function setSuperUser()
+ public function setSuperUser($bool = true)
{
- $this->reloadAccessSuperUser();
+ if($bool)
+ {
+ $this->reloadAccessSuperUser();
+ }
+ else
+ {
+ $this->isSuperUser = false;
+ $this->idsitesByAccess['superuser'] = array();
+ }
}
/**
@@ -223,8 +242,7 @@ class Piwik_Access
$this->idsitesByAccess['superuser'])
);
}
-
-
+
/**
* Returns an array of ID sites for which the user has an ADMIN access.
*
@@ -261,7 +279,7 @@ class Piwik_Access
{
if($this->isSuperUser === false)
{
- throw new Piwik_Access_NoAccessException("You can't access this resource as it requires a 'superuser' access.");
+ throw new Piwik_Access_NoAccessException(Piwik_TranslateException('General_ExceptionPrivilege', array("'superuser'")));
}
}
@@ -272,10 +290,14 @@ class Piwik_Access
*/
public function checkUserHasSomeAdminAccess()
{
+ if($this->isSuperUser())
+ {
+ return;
+ }
$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.");
+ throw new Piwik_Access_NoAccessException(Piwik_TranslateException('General_ExceptionPrivilegeAtLeastOneWebsite', array('admin')));
}
}
@@ -289,7 +311,7 @@ class Piwik_Access
$idSitesAccessible = $this->getSitesIdWithAtLeastViewAccess();
if(count($idSitesAccessible) == 0)
{
- throw new Piwik_Access_NoAccessException("You can't access this resource as it requires a 'view' access for at least one website.");
+ throw new Piwik_Access_NoAccessException(Piwik_TranslateException('General_ExceptionPrivilegeAtLeastOneWebsite', array('view')));
}
}
@@ -315,7 +337,7 @@ class Piwik_Access
{
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.");
+ throw new Piwik_Access_NoAccessException(Piwik_TranslateException('General_ExceptionPrivilegeAccessWebsite', array("'admin'", $idsite)));
}
}
}
@@ -344,7 +366,7 @@ class Piwik_Access
{
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.");
+ throw new Piwik_Access_NoAccessException(Piwik_TranslateException('General_ExceptionPrivilegeAccessWebsite', array("'view'", $idsite)));
}
}
}
diff --git a/core/Archive.php b/core/Archive.php
index bcc86237a3..17900f51a5 100644
--- a/core/Archive.php
+++ b/core/Archive.php
@@ -120,7 +120,7 @@ abstract class Piwik_Archive
{
if($idSite === 'all')
{
- $sites = Piwik_SitesManager_API::getSitesIdWithAtLeastViewAccess();
+ $sites = Piwik_SitesManager_API::getInstance()->getSitesIdWithAtLeastViewAccess();
}
else
{
diff --git a/core/Archive/Array/IndexedByDate.php b/core/Archive/Array/IndexedByDate.php
index 53a0369153..8629ef9672 100644
--- a/core/Archive/Array/IndexedByDate.php
+++ b/core/Archive/Array/IndexedByDate.php
@@ -25,7 +25,7 @@ class Piwik_Archive_Array_IndexedByDate extends Piwik_Archive_Array
*/
function __construct(Piwik_Site $oSite, $strPeriod, $strDate)
{
- $rangePeriod = new Piwik_Period_Range($strPeriod, $strDate);
+ $rangePeriod = new Piwik_Period_Range($strPeriod, $strDate, $oSite->getTimezone());
foreach($rangePeriod->getSubperiods() as $subPeriod)
{
$startDate = $subPeriod->getDateStart();
@@ -42,6 +42,13 @@ class Piwik_Archive_Array_IndexedByDate extends Piwik_Archive_Array
return 'date';
}
+ /**
+ * Adds metadata information to the Piwik_DataTable_Array
+ * using the information given by the Archive
+ *
+ * @param Piwik_DataTable_Array $table
+ * @param Piwik_Archive $archive
+ */
protected function loadMetadata(Piwik_DataTable_Array $table, $archive)
{
$table->metadata[$archive->getPrettyDate()] = array(
@@ -91,21 +98,22 @@ class Piwik_Archive_Array_IndexedByDate extends Piwik_Archive_Array
$arrayValues = array();
foreach($queries as $table => $aIds)
{
- $inIds = implode(', ', $aIds);
+ $inIds = implode(', ', array_filter($aIds));
if(empty($inIds))
{
// Probable timezone configuration error, i.e., mismatch between PHP and MySQL server.
continue;
}
- $sql = "SELECT value, name, UNIX_TIMESTAMP(date1) as timestamp
+ $sql = "SELECT value, name, date1 as startDate
FROM $table
WHERE idarchive IN ( $inIds )
AND name IN ( $inNames )";
$values = $db->fetchAll($sql);
foreach($values as $value)
{
- $arrayValues[$value['timestamp']][$value['name']] = (float)$value['value'];
+ $timestamp = Piwik_Date::factory($value['startDate'])->getTimestamp();
+ $arrayValues[$timestamp][$value['name']] = (float)$value['value'];
}
}
diff --git a/core/Archive/Array/IndexedBySite.php b/core/Archive/Array/IndexedBySite.php
index 22cbd88dbd..a5d4cf6163 100644
--- a/core/Archive/Array/IndexedBySite.php
+++ b/core/Archive/Array/IndexedBySite.php
@@ -112,7 +112,7 @@ class Piwik_Archive_Array_IndexedBySite extends Piwik_Archive_Array
}
$archiveIds[] = $archive->getIdArchive();
}
- return implode(', ', $archiveIds);
+ return implode(', ', array_filter($archiveIds));
}
private function getNumericTableName()
diff --git a/core/Archive/Single.php b/core/Archive/Single.php
index 464ecf7f31..991ecd7a67 100644
--- a/core/Archive/Single.php
+++ b/core/Archive/Single.php
@@ -134,9 +134,12 @@ class Piwik_Archive_Single extends Piwik_Archive
{
if(!is_null($this->archiveProcessing))
{
- return $this->archiveProcessing->getTimestampStartDate();
+ $timestamp = $this->archiveProcessing->getTimestampStartDate();
+ if(!empty($timestamp))
+ {
+ return $timestamp;
+ }
}
-
return $this->period->getDateStart()->getTimestamp();
}
@@ -152,18 +155,20 @@ class Piwik_Archive_Single extends Piwik_Archive
{
$this->isThereSomeVisits = false;
$this->alreadyChecked = true;
-
+ $logMessage = "Preparing archive: ";
// 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;
+ Piwik::log("$logMessage skipped, archive is before the website was created.");
+ 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() ) )
+ if( $this->period->getDateStart()->subDay(2)->isLater( Piwik_Date::today() ) )
{
+ Piwik::log("$logMessage skipped, archive is after today.");
return;
}
@@ -173,12 +178,17 @@ class Piwik_Archive_Single extends Piwik_Archive
$archiveProcessing->setSite($this->site);
$archiveProcessing->setPeriod($this->period);
$idArchive = $archiveProcessing->loadArchive();
- if($idArchive === null)
+ if(empty($idArchive))
{
+ Piwik::log("$logMessage not archived yet, starting processing...");
$archiveJustProcessed = true;
$archiveProcessing->launchArchiving();
$idArchive = $archiveProcessing->getIdArchive();
}
+ else
+ {
+ Piwik::log("$logMessage archive already processed [id = $idArchive]...");
+ }
$this->isThereSomeVisits = $archiveProcessing->isThereSomeVisits;
$this->idArchive = $idArchive;
$this->archiveProcessing = $archiveProcessing;
@@ -442,7 +452,7 @@ class Piwik_Archive_Single extends Piwik_Archive
if($data === false
&& $idSubTable !== null)
{
- throw new Exception("You are requesting a precise subTable but there is not such data in the Archive.");
+ throw new Exception(Piwik_TranslateException('General_ExceptionSubtableNotFoundInArchive'));
}
return $table;
diff --git a/core/ArchiveProcessing.php b/core/ArchiveProcessing.php
index 91eed568a4..23dca365cf 100644
--- a/core/ArchiveProcessing.php
+++ b/core/ArchiveProcessing.php
@@ -45,6 +45,14 @@ abstract class Piwik_ArchiveProcessing
* @var int
*/
const DONE_ERROR = 2;
+
+ /**
+ * Flag indicates the archive is over a period that is not finished, eg. the current day, current week, etc.
+ * Archives flagged will be regularly purged from the DB.
+ *
+ * @var int
+ */
+ const DONE_OK_TEMPORARY = 3;
/**
* Idarchive in the DB for the requested archive
@@ -96,11 +104,11 @@ abstract class Piwik_ArchiveProcessing
protected $tableArchiveBlob;
/**
- * Maximum timestamp above which a given archive is considered out of date
+ * Minimum timestamp looked at for processed archives
*
* @var int
*/
- protected $maxTimestampArchive;
+ protected $minDatetimeArchiveProcessedUTC = false;
/**
* Compress blobs
@@ -110,6 +118,13 @@ abstract class Piwik_ArchiveProcessing
protected $compressBlob;
/**
+ * Is the current archive temporary. ie.
+ * - today
+ * - current week / month / year
+ */
+ protected $temporaryArchive;
+
+ /**
* Id of the current site
* Can be accessed by plugins (that is why it's public)
*
@@ -121,7 +136,7 @@ abstract class Piwik_ArchiveProcessing
* Period of the current archive
* Can be accessed by plugins (that is why it's public)
*
- * @var Piwik_Period
+ * @var $period Piwik_Period
*/
public $period = null;
@@ -134,14 +149,14 @@ abstract class Piwik_ArchiveProcessing
public $site = null;
/**
- * Starting date @see Piwik_Date::toString()
+ * Starting datetime in UTC
*
* @var string
*/
- public $strDateStart;
+ public $startDatetimeUTC;
/**
- * Ending date @see Piwik_Date::toString()
+ * Ending date in UTC
*
* @var string
*/
@@ -183,6 +198,9 @@ abstract class Piwik_ArchiveProcessing
*/
public $isThereSomeVisits = false;
+ protected $startTimestampUTC;
+ protected $endTimestampUTC;
+
/**
* Constructor
*/
@@ -218,61 +236,142 @@ abstract class Piwik_ArchiveProcessing
return $process;
}
+ const OPTION_TODAY_ARCHIVE_TTL = 'todayArchiveTimeToLive';
+ const OPTION_BROWSER_TRIGGER_ARCHIVING = 'enableBrowserTriggerArchiving';
+
+ static public function setTodayArchiveTimeToLive($timeToLiveSeconds)
+ {
+ $timeToLiveSeconds = (int)$timeToLiveSeconds;
+ if($timeToLiveSeconds <= 0)
+ {
+ throw new Exception(Piwik_TranslateException('General_ExceptionInvalidArchiveTimeToLive'));
+ }
+ Piwik_SetOption(self::OPTION_TODAY_ARCHIVE_TTL, $timeToLiveSeconds, $autoload = true);
+ }
+
+ static public function getTodayArchiveTimeToLive()
+ {
+ $timeToLive = Piwik_GetOption(self::OPTION_TODAY_ARCHIVE_TTL);
+ if($timeToLive !== false)
+ {
+ return $timeToLive;
+ }
+ return Zend_Registry::get('config')->General->time_before_today_archive_considered_outdated;
+ }
+
+ static public function setBrowserTriggerArchiving($enabled)
+ {
+ if(!is_bool($enabled))
+ {
+ throw new Exception('Browser trigger archiving must be set to true or false.');
+ }
+ Piwik_SetOption(self::OPTION_BROWSER_TRIGGER_ARCHIVING, (int)$enabled, $autoload = true);
+
+ }
+ static public function isBrowserTriggerArchivingEnabled()
+ {
+ $browserArchivingEnabled = Piwik_GetOption(self::OPTION_BROWSER_TRIGGER_ARCHIVING);
+ if($browserArchivingEnabled !== false)
+ {
+ return (bool)$browserArchivingEnabled;
+ }
+ return (bool)Zend_Registry::get('config')->General->enable_browser_archiving_triggering;
+ }
+
public function getIdArchive()
{
return $this->idArchive;
}
/**
- * Inits the object
+ * Sets object attributes that will be used throughout the process
*/
- protected function loadArchiveProperties()
- {
+ public function init()
+ {
$this->idsite = $this->site->getId();
-
$this->periodId = $this->period->getId();
-
- $this->dateStart = $this->period->getDateStart();
- $this->dateEnd = $this->period->getDateEnd();
+
+ $dateStartLocalTimezone = $this->period->getDateStart();
+ $dateEndLocalTimezone = $this->period->getDateEnd();
$this->tableArchiveNumeric = new Piwik_TablePartitioning_Monthly('archive_numeric');
$this->tableArchiveNumeric->setIdSite($this->idsite);
- $this->tableArchiveNumeric->setTimestamp($this->dateStart->get());
+ $this->tableArchiveNumeric->setTimestamp($dateStartLocalTimezone->getTimestamp());
$this->tableArchiveBlob = new Piwik_TablePartitioning_Monthly('archive_blob');
$this->tableArchiveBlob->setIdSite($this->idsite);
- $this->tableArchiveBlob->setTimestamp($this->dateStart->get());
+ $this->tableArchiveBlob->setTimestamp($dateStartLocalTimezone->getTimestamp());
+
+ $dateStartUTC = $dateStartLocalTimezone->setTimezone($this->site->getTimezone());
+ $dateEndUTC = $dateEndLocalTimezone->setTimezone($this->site->getTimezone());
+ $this->startDatetimeUTC = $dateStartUTC->getDateStartUTC();
+ $this->endDatetimeUTC = $dateEndUTC->getDateEndUTC();
- $this->strDateStart = $this->dateStart->toString();
- $this->strDateEnd = $this->dateEnd->toString();
+ $this->startTimestampUTC = $dateStartUTC->getTimestamp();
+ $this->endTimestampUTC = strtotime($this->endDatetimeUTC);
+ $this->minDatetimeArchiveProcessedUTC = $this->getMinTimeArchivedProcessed();
+ $db = Zend_Registry::get('db');
+ $this->compressBlob = $db->hasBlobDataType();
+ }
+
+ public function getStartDatetimeUTC()
+ {
+ return $this->startDatetimeUTC;
+ }
+
+ public function getEndDatetimeUTC()
+ {
+ return $this->endDatetimeUTC;
+ }
+
+ public function isArchiveTemporary()
+ {
+ return $this->temporaryArchive;
+ }
+
+ /**
+ * Returns the minimum archive processed datetime to look at
+ *
+ * @return string Datetime string, or false if must look at any archive available
+ */
+ public function getMinTimeArchivedProcessed()
+ {
+ $this->temporaryArchive = false;
// 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;
+ // we set this minDatetimeArchiveProcessedUTC that defines the lifetime value of today's archive
if( $this->period->getNumberOfSubperiods() == 0
- && $this->period->toString() == date("Y-m-d")
+ && $this->startTimestampUTC <= time() && $this->endTimestampUTC > time()
)
{
- $this->maxTimestampArchive = time() - Zend_Registry::get('config')->General->time_before_today_archive_considered_outdated;
+ $this->temporaryArchive = true;
+ $minDatetimeArchiveProcessedUTC = time() - self::getTodayArchiveTimeToLive();
+ // see #1150; if new archives are not triggered from the browser,
+ // we still want to try and return the latest archive available for today (rather than return nothing)
+ if($this->isArchivingDisabled())
+ {
+ return false;
+ }
}
// 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
+ // recent enough means minDatetimeArchiveProcessedUTC = 00:00:01 this morning
else
{
- if($this->period->isFinished())
+ if($this->endTimestampUTC <= time())
{
- $this->maxTimestampArchive = $this->period->getDateEnd()->setTime('00:00:00')->addDay(1)->getTimestamp();
+ $minDatetimeArchiveProcessedUTC = $this->endTimestampUTC+1;
}
else
{
- $this->maxTimestampArchive = Piwik_Date::today()->getTimestamp();
+ $this->temporaryArchive = true;
+ $minDatetimeArchiveProcessedUTC = Piwik_Date::today()
+ ->setTimezone($this->site->getTimezone())
+ ->getTimestamp();
}
}
-
- $db = Zend_Registry::get('db');
- $this->compressBlob = $db->hasBlobDataType();
+ return $minDatetimeArchiveProcessedUTC;
}
/**
@@ -286,7 +385,7 @@ abstract class Piwik_ArchiveProcessing
*/
public function loadArchive()
{
- $this->loadArchiveProperties();
+ $this->init();
$this->idArchive = $this->isArchived();
if($this->idArchive === false
@@ -327,10 +426,19 @@ abstract class Piwik_ArchiveProcessing
{
$this->loadNextIdarchive();
$this->insertNumericRecord('done', Piwik_ArchiveProcessing::DONE_ERROR);
- $this->logTable = Piwik::prefixTable('log_visit');
- $this->logVisitActionTable = Piwik::prefixTable('log_link_visit_action');
- $this->logActionTable = Piwik::prefixTable('log_action');
- $this->logConversionTable = Piwik::prefixTable('log_conversion');
+ $this->logTable = Piwik_Common::prefixTable('log_visit');
+ $this->logVisitActionTable = Piwik_Common::prefixTable('log_link_visit_action');
+ $this->logActionTable = Piwik_Common::prefixTable('log_action');
+ $this->logConversionTable = Piwik_Common::prefixTable('log_conversion');
+
+ $temporary = 'definitive archive';
+ if($this->isArchiveTemporary())
+ {
+ $temporary = 'temporary archive';
+ }
+ Piwik::log("Processing archive '" . $this->period->getLabel() . "',
+ idsite = ". $this->idsite." ($temporary) -
+ UTC datetime [".$this->startDatetimeUTC." -> ".$this->endDatetimeUTC." ]...");
}
/**
@@ -343,12 +451,17 @@ abstract class Piwik_ArchiveProcessing
{
// delete the first done = ERROR
Piwik_Query("/* SHARDING_ID_SITE = ".$this->idsite." */
- DELETE FROM ".$this->tableArchiveNumeric->getTableName()."
- WHERE idarchive = ? AND name = 'done'",
+ DELETE FROM ".$this->tableArchiveNumeric->getTableName()."
+ WHERE idarchive = ? AND name = 'done'",
array($this->idArchive)
- );
+ );
- $this->insertNumericRecord('done', Piwik_ArchiveProcessing::DONE_OK);
+ $flag = Piwik_ArchiveProcessing::DONE_OK;
+ if($this->isArchiveTemporary())
+ {
+ $flag = Piwik_ArchiveProcessing::DONE_OK_TEMPORARY;
+ }
+ $this->insertNumericRecord('done', $flag);
Piwik_DataTable_Manager::getInstance()->deleteAll();
}
@@ -400,14 +513,8 @@ abstract class Piwik_ArchiveProcessing
*/
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;
}
-
// exposing the number of visits publicly (number used to compute conversions rates)
protected $nb_visits = null;
@@ -438,7 +545,9 @@ abstract class Piwik_ArchiveProcessing
protected function loadNextIdarchive()
{
$db = Zend_Registry::get('db');
- $id = $db->fetchOne("/* SHARDING_ID_SITE = ".$this->idsite." */ SELECT max(idarchive) FROM ".$this->tableArchiveNumeric->getTableName());
+ $id = $db->fetchOne("/* SHARDING_ID_SITE = ".$this->idsite." */
+ SELECT max(idarchive)
+ FROM ".$this->tableArchiveNumeric->getTableName());
if(empty($id))
{
$id = 0;
@@ -498,23 +607,31 @@ abstract class Piwik_ArchiveProcessing
*/
protected function insertRecord($record)
{
+
// table to use to save the data
if(is_numeric($record->value))
{
+ // We choose not to record records with a value of 0
+ if($record->value == 0)
+ {
+ return;
+ }
$table = $this->tableArchiveNumeric;
}
else
{
$table = $this->tableArchiveBlob;
}
-
- $query = "INSERT INTO ".$table->getTableName()." (idarchive, idsite, date1, date2, period, ts_archived, name, value)
+
+ // ignore duplicate idarchive
+ // @see http://dev.piwik.org/trac/ticket/987
+ $query = "INSERT IGNORE INTO ".$table->getTableName()." (idarchive, idsite, date1, date2, period, ts_archived, name, value)
VALUES (?,?,?,?,?,?,?,?)";
Piwik_Query($query,
array( $this->idArchive,
$this->idsite,
- $this->strDateStart,
- $this->strDateEnd,
+ $this->period->getDateStart()->toString('Y-m-d'),
+ $this->period->getDateEnd()->toString('Y-m-d'),
$this->periodId,
date("Y-m-d H:i:s"),
$record->name,
@@ -528,7 +645,7 @@ abstract class Piwik_ArchiveProcessing
* 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 today, the archive was computed less than minDatetimeArchiveProcessedUTC 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
*
@@ -537,20 +654,27 @@ abstract class Piwik_ArchiveProcessing
protected function isArchived()
{
$bindSQL = array( $this->idsite,
- $this->strDateStart,
- $this->strDateEnd,
- $this->periodId,
- );
- $timeStampWhere = " AND UNIX_TIMESTAMP(ts_archived) >= ? ";
- $bindSQL[] = $this->maxTimestampArchive;
+ $this->period->getDateStart()->toString('Y-m-d'),
+ $this->period->getDateEnd()->toString('Y-m-d'),
+ $this->periodId,
+ );
+
+ $timeStampWhere = '';
- $sqlQuery = " SELECT idarchive, value, name, UNIX_TIMESTAMP(date1) as timestamp
+ if($this->minDatetimeArchiveProcessedUTC)
+ {
+ $timeStampWhere = " AND ts_archived >= ? ";
+ $bindSQL[] = Piwik_Date::factory($this->minDatetimeArchiveProcessedUTC)->getDatetime();
+ }
+
+ $sqlQuery = " SELECT idarchive, value, name, date1 as startDate
FROM ".$this->tableArchiveNumeric->getTableName()."
WHERE idsite = ?
AND date1 = ?
AND date2 = ?
AND period = ?
AND ( (name = 'done' AND value = ".Piwik_ArchiveProcessing::DONE_OK.")
+ OR (name = 'done' AND value = ".Piwik_ArchiveProcessing::DONE_OK_TEMPORARY.")
OR name = 'nb_visits')
$timeStampWhere
ORDER BY ts_archived DESC";
@@ -567,7 +691,7 @@ abstract class Piwik_ArchiveProcessing
if($result['name'] == 'done')
{
$idarchive = $result['idarchive'];
- $this->timestampDateStart = $result['timestamp'];
+ $this->timestampDateStart = Piwik_Date::factory($result['startDate'])->getTimestamp();
break;
}
}
@@ -579,7 +703,7 @@ abstract class Piwik_ArchiveProcessing
return false;
}
- // we look for the nb_visits result for this more recent archive
+ // we look for the nb_visits result for this most recent archive
foreach($results as $result)
{
if($result['name'] == 'nb_visits'
@@ -599,19 +723,11 @@ abstract class Piwik_ArchiveProcessing
*/
protected function isArchivingDisabled()
{
- static $archivingIsDisabled = null;
- if(is_null($archivingIsDisabled))
+ if(!self::isBrowserTriggerArchivingEnabled()
+ && !Piwik_Common::isPhpCliMode())
{
- $archivingIsDisabled = false;
- $enableBrowserArchivingTriggering = (bool)Zend_Registry::get('config')->General->enable_browser_archiving_triggering;
- if($enableBrowserArchivingTriggering == false)
- {
- if( !Piwik_Common::isPhpCliMode())
- {
- $archivingIsDisabled = true;
- }
- }
+ return true;
}
- return $archivingIsDisabled;
+ return false;
}
}
diff --git a/core/ArchiveProcessing/Day.php b/core/ArchiveProcessing/Day.php
index df823b978b..5193a39562 100644
--- a/core/ArchiveProcessing/Day.php
+++ b/core/ArchiveProcessing/Day.php
@@ -44,12 +44,12 @@ class Piwik_ArchiveProcessing_Day extends Piwik_ArchiveProcessing
sum(case visit_total_actions when 1 then 1 else 0 end) as bounce_count,
sum(case visit_goal_converted when 1 then 1 else 0 end) as nb_visits_converted
FROM ".$this->logTable."
- WHERE visit_server_date = ?
+ WHERE visit_last_action_time >= ?
+ AND visit_last_action_time <= ?
AND idsite = ?
- GROUP BY visit_server_date
ORDER BY NULL";
- $row = $this->db->fetchRow($query, array($this->strDateStart,$this->idsite ) );
- if($row === false || $row === null)
+ $row = $this->db->fetchRow($query, array($this->getStartDatetimeUTC(), $this->getEndDatetimeUTC(), $this->idsite ) );
+ if($row === false || $row === null || $row['nb_visits'] == 0)
{
return;
}
@@ -87,9 +87,10 @@ class Piwik_ArchiveProcessing_Day extends Piwik_ArchiveProcessing
{
$query = "SELECT $select
FROM ".$this->logTable."
- WHERE visit_server_date = ?
- AND idsite = ?";
- $data = $this->db->fetchRow($query, array( $this->strDateStart, $this->idsite ));
+ WHERE visit_last_action_time >= ?
+ AND visit_last_action_time <= ?
+ AND idsite = ?";
+ $data = $this->db->fetchRow($query, array( $this->getStartDatetimeUTC(), $this->getEndDatetimeUTC(), $this->idsite ));
foreach($data as $label => &$count)
{
@@ -152,11 +153,12 @@ class Piwik_ArchiveProcessing_Day extends Piwik_ArchiveProcessing
sum(case visit_total_actions when 1 then 1 else 0 end) as bounce_count,
sum(case visit_goal_converted when 1 then 1 else 0 end) as nb_visits_converted
FROM ".$this->logTable."
- WHERE visit_server_date = ?
- AND idsite = ?
+ WHERE visit_last_action_time >= ?
+ AND visit_last_action_time <= ?
+ AND idsite = ?
GROUP BY label
ORDER BY NULL";
- $query = $this->db->query($query, array( $this->strDateStart, $this->idsite ) );
+ $query = $this->db->query($query, array( $this->getStartDatetimeUTC(), $this->getEndDatetimeUTC(), $this->idsite ) );
$interest = array();
while($row = $query->fetch())
@@ -327,11 +329,12 @@ class Piwik_ArchiveProcessing_Day extends Piwik_ArchiveProcessing
sum(revenue) as revenue
$segments
FROM ".$this->logConversionTable."
- WHERE visit_server_date = ?
- AND idsite = ?
+ WHERE server_time >= ?
+ AND server_time <= ?
+ AND idsite = ?
GROUP BY idgoal $segments
ORDER BY NULL";
- $query = $this->db->query($query, array( $this->strDateStart, $this->idsite ));
+ $query = $this->db->query($query, array( $this->getStartDatetimeUTC(), $this->getEndDatetimeUTC(), $this->idsite ));
return $query;
}
@@ -342,15 +345,20 @@ class Piwik_ArchiveProcessing_Day extends Piwik_ArchiveProcessing
sum(revenue) as revenue,
$segment as label
FROM ".$this->logConversionTable."
- WHERE visit_server_date = ?
- AND idsite = ?
+ WHERE server_time >= ?
+ AND server_time <= ?
+ AND idsite = ?
GROUP BY idgoal, label
ORDER BY NULL";
- $query = $this->db->query($query, array( $this->strDateStart, $this->idsite ));
+ $query = $this->db->query($query, array( $this->getStartDatetimeUTC(), $this->getEndDatetimeUTC(), $this->idsite ));
return $query;
}
/**
+ * Given an array of stats, it will process the sum of goal conversions
+ * and sum of revenue and add it in the stats array in two new fields.
+ *
+ * @param $interestByLabel Passed by reference, it will be modified as follows:
* Input:
* array(
* LABEL => array( Piwik_Archive::INDEX_NB_VISITS => X,
@@ -362,10 +370,12 @@ class Piwik_ArchiveProcessing_Day extends Piwik_ArchiveProcessing
* LABEL2 => array( Piwik_Archive::INDEX_NB_VISITS => Y, [...] )
* );
*
+ *
* Output:
* array(
* LABEL => array( Piwik_Archive::INDEX_NB_VISITS => X,
- *
+ * Piwik_Archive::INDEX_NB_CONVERSIONS => Y, // sum of all conversions
+ * Piwik_Archive::INDEX_REVENUE => Z, // sum of all revenue
* Piwik_Archive::INDEX_GOALS => array(
* idgoal1 => array( [...] ),
* idgoal2 => array( [...] ),
diff --git a/core/ArchiveProcessing/Period.php b/core/ArchiveProcessing/Period.php
index 7f40c744e7..37b65cba70 100644
--- a/core/ArchiveProcessing/Period.php
+++ b/core/ArchiveProcessing/Period.php
@@ -156,6 +156,11 @@ class Piwik_ArchiveProcessing_Period extends Piwik_ArchiveProcessing
* 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
+ * @param array (current_column_name => new_column_name) for columns that must change names when summed (eg. unique visitors go from nb_uniq_visitors to sum_daily_nb_uniq_visitors)
+ * @param int Max row count of parent datatable to archive
+ * @param int Max row count of children datatable(s) to archive
+ * @param string Column name to sort by, before truncating rows (ie. if there are more rows than the specified max row count)
+ *
* @return array array (
* nameTable1 => number of rows,
* nameTable2 => number of rows,
@@ -283,10 +288,14 @@ class Piwik_ArchiveProcessing_Period extends Piwik_ArchiveProcessing
protected function computeNbUniqVisitors()
{
- $query = "SELECT count(distinct visitor_idcookie) as nb_uniq_visitors FROM ".$this->logTable."
- WHERE visit_server_date >= ? AND visit_server_date <= ? AND idsite = ?";
+ $query = "
+ SELECT count(distinct visitor_idcookie) as nb_uniq_visitors
+ FROM ".$this->logTable."
+ WHERE visit_last_action_time >= ?
+ AND visit_last_action_time <= ?
+ AND idsite = ?";
- return Zend_Registry::get('db')->fetchOne($query, array( $this->strDateStart, $this->strDateEnd, $this->idsite ));
+ return Zend_Registry::get('db')->fetchOne($query, array( $this->getStartDatetimeUTC(), $this->getEndDatetimeUTC(), $this->idsite ));
}
/**
@@ -297,46 +306,53 @@ class Piwik_ArchiveProcessing_Period extends Piwik_ArchiveProcessing
{
parent::postCompute();
+ foreach($this->archives as $archive)
+ {
+ destroy($archive);
+ }
+ $this->archives = array();
+
$blobTable = $this->tableArchiveBlob->getTableName();
$numericTable = $this->tableArchiveNumeric->getTableName();
- // delete out of date records maximum once per day (DELETE request is costly)
$key = 'lastPurge_' . $blobTable;
$timestamp = Piwik_GetOption($key);
if(!$timestamp
- || $timestamp < time() - 86400 )
+ || $timestamp < time() - 86400)
{
- // we delete out of date daily archives from table, maximum once per day
- // those for day N that were processed on day N (means the archives are only partial as the day wasn't finished)
- $query = "/* SHARDING_ID_SITE = ".$this->idsite." */ DELETE
- FROM %s
- WHERE period = ?
- AND date1 = DATE(ts_archived)
- AND DATE(ts_archived) <> CURRENT_DATE()
- ";
- Piwik_Query(sprintf($query, $blobTable), Piwik::$idPeriods['day']);
- Piwik_Query(sprintf($query, $numericTable), Piwik::$idPeriods['day']);
-
- // we delete out of date Period records (week/month/etc)
- // 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)
- $query = " DELETE
- FROM %s
- WHERE period > ?
- AND DATE(ts_archived) <= date2
- AND date(ts_archived) < date_sub(CURRENT_DATE(), INTERVAL 1 DAY)
- ";
+ Piwik_SetOption($key, time());
- Piwik_Query(sprintf($query, $blobTable), Piwik::$idPeriods['day']);
- Piwik_Query(sprintf($query, $numericTable), Piwik::$idPeriods['day']);
+ // we delete out of date daily archives from table, maximum once per day
+ // we only delete archives processed that are older than 1 day, to not delete archives we just processed
+ $yesterday = Piwik_Date::factory('yesterday')->getDateTime();
+ $result = Piwik_FetchAll("
+ SELECT idarchive
+ FROM $numericTable
+ WHERE name='done'
+ AND value = ". Piwik_ArchiveProcessing::DONE_OK_TEMPORARY ."
+ AND ts_archived < ?", array($yesterday));
- Piwik_SetOption($key, time());
+ $idArchivesToDelete = array();
+ if(!empty($result))
+ {
+ foreach($result as $row) {
+ $idArchivesToDelete[] = $row['idarchive'];
+ }
+ $query = "/* SHARDING_ID_SITE = ".$this->idsite." */
+ DELETE
+ FROM %s
+ WHERE idarchive IN (".implode(',',$idArchivesToDelete).")
+ ";
+
+ Piwik_Query(sprintf($query, $blobTable));
+ Piwik_Query(sprintf($query, $numericTable));
+ }
+ Piwik::log("Purging temporary archives: done [ purged archives older than $yesterday from $blobTable and $numericTable ] [Deleted IDs: ". implode(',',$idArchivesToDelete)."]");
}
-
- foreach($this->archives as $archive)
+ else
{
- destroy($archive);
+ Piwik::log("Purging temporary archives: skipped.");
}
- $this->archives = array();
+
}
}
diff --git a/core/CacheFile.php b/core/CacheFile.php
index 8d877abddc..6a3f95cf9e 100644
--- a/core/CacheFile.php
+++ b/core/CacheFile.php
@@ -28,7 +28,6 @@ class Piwik_CacheFile
function __construct($directory)
{
$this->cachePath = PIWIK_USER_PATH . '/tmp/cache/' . $directory . '/';
-// echo $this->cachePath;exit;
}
/**
@@ -112,4 +111,12 @@ class Piwik_CacheFile
}
return false;
}
+
+ /**
+ * A function to delete all cache entries in the directory
+ */
+ function deleteAll()
+ {
+ Piwik::unlinkRecursive($this->cachePath, $deleteRootToo = false);
+ }
}
diff --git a/core/Common.php b/core/Common.php
index b1f152b95c..e972cc3a70 100644
--- a/core/Common.php
+++ b/core/Common.php
@@ -47,7 +47,7 @@ class Piwik_Common
static $prefixTable = null;
if(is_null($prefixTable))
{
- if(defined('PIWIK_TRACKER_MODE') && PIWIK_TRACKER_MODE)
+ if(!empty($GLOBALS['PIWIK_TRACKER_MODE']))
{
$prefixTable = Piwik_Tracker_Config::getInstance()->database['tables_prefix'];
}
@@ -84,23 +84,40 @@ class Piwik_Common
{
return $cacheContent;
}
- if(defined('PIWIK_TRACKER_MODE')
- && PIWIK_TRACKER_MODE)
+ if(!empty($GLOBALS['PIWIK_TRACKER_MODE']))
{
require_once PIWIK_INCLUDE_PATH . '/core/PluginsManager.php';
require_once PIWIK_INCLUDE_PATH . '/core/Translate.php';
require_once PIWIK_INCLUDE_PATH . '/core/Option.php';
- Zend_Registry::set('db', Piwik_Tracker::getDatabase());
- Piwik::createAccessObject();
- Piwik::createConfigObject();
- Piwik::setUserIsSuperUser();
+ try {
+ $access = Zend_Registry::get('access');
+ } catch (Exception $e) {
+ Piwik::createAccessObject();
+ }
+ try {
+ $config = Zend_Registry::get('config');
+ } catch (Exception $e) {
+ Piwik::createConfigObject();
+ }
+ try {
+ $db = Zend_Registry::get('db');
+ } catch (Exception $e) {
+ Piwik::createDatabaseObject();
+ }
+
$pluginsManager = Piwik_PluginsManager::getInstance();
- $pluginsManager->setPluginsToLoad( Zend_Registry::get('config')->Plugins->Plugins->toArray() );
+ $pluginsManager->loadPlugins( Zend_Registry::get('config')->Plugins->Plugins->toArray() );
}
+ $isSuperUser = Piwik::isUserIsSuperUser();
+ Piwik::setUserIsSuperUser();
$content = array();
Piwik_PostEvent('Common.fetchWebsiteAttributes', $content, $idSite);
+
+ // we remove the temporary Super user privilege
+ Piwik::setUserIsSuperUser($isSuperUser);
+
// if nothing is returned from the plugins, we don't save the content
// this is not expected: all websites are expected to have at least one URL
if(!empty($content))
@@ -140,6 +157,16 @@ class Piwik_Common
}
/**
+ * Deletes all Tracker cache files
+ */
+ static public function deleteAllCache()
+ {
+ $cache = new Piwik_CacheFile('tracker');
+ $cache->deleteAll();
+ }
+
+
+ /**
* 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
*
@@ -190,7 +217,6 @@ class Piwik_Common
/**
* 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)
@@ -243,6 +269,35 @@ class Piwik_Common
}
/**
+ * Builds a URL from the result of parse_url function
+ * Copied from the PHP comments at http://php.net/parse_url
+ * @param array
+ */
+ static public function getParseUrlReverse($parsed)
+ {
+ if (!is_array($parsed))
+ {
+ return false;
+ }
+
+ $uri = !empty($parsed['scheme']) ? $parsed['scheme'].':'.((strtolower($parsed['scheme']) == 'mailto') ? '' : '//') : '';
+ $uri .= !empty($parsed['user']) ? $parsed['user'].(!empty($parsed['pass']) ? ':'.$parsed['pass'] : '').'@' : '';
+ $uri .= !empty($parsed['host']) ? $parsed['host'] : '';
+ $uri .= !empty($parsed['port']) ? ':'.$parsed['port'] : '';
+
+ if (!empty($parsed['path']))
+ {
+ $uri .= (substr($parsed['path'], 0, 1) == '/')
+ ? $parsed['path']
+ : ((!empty($uri) ? '/' : '' ) . $parsed['path']);
+ }
+
+ $uri .= !empty($parsed['query']) ? '?'.$parsed['query'] : '';
+ $uri .= !empty($parsed['fragment']) ? '#'.$parsed['fragment'] : '';
+ return $uri;
+ }
+
+ /**
* Create directory if permitted
*
* @param string $path
@@ -272,10 +327,11 @@ class Piwik_Common
* Apache-specific; for IIS @see web.config
*
* @param string $path without trailing slash
+ * @param string $content
*/
- static public function createHtAccess( $path )
+ static public function createHtAccess( $path, $content = "<Files \"*\">\nDeny from all\n</Files>\n" )
{
- @file_put_contents($path . '/.htaccess', 'Deny from all');
+ @file_put_contents($path . '/.htaccess', $content);
}
/**
@@ -355,8 +411,8 @@ class Piwik_Common
{
$value = self::sanitizeInputValue($value);
- // Undo the damage caused by magic_quotes -- only before php 5.3 as it is now deprecated
- if ( version_compare(phpversion(), '5.3') === -1
+ // Undo the damage caused by magic_quotes; deprecated in php 5.3 but not removed until php 6
+ if ( version_compare(phpversion(), '6') === -1
&& get_magic_quotes_gpc())
{
$value = stripslashes($value);
@@ -508,6 +564,30 @@ class Piwik_Common
}
/**
+ * Unserialize (serialized) array
+ *
+ * @param string
+ * @return array or original string if not unserializable
+ */
+ public static function unserialize_array( $str )
+ {
+ // we set the unserialized version only for arrays as you can have set a serialized string on purpose
+ if (preg_match('/^a:[0-9]+:{/', $str)
+ && !preg_match('/(^|;|{|})O:[0-9]+:"/', $str)
+ && strpos($str, "\0") === false)
+ {
+ if( ($arrayValue = @unserialize($str)) !== false
+ && is_array($arrayValue) )
+ {
+ return $arrayValue;
+ }
+ }
+
+ // return original string
+ return $str;
+ }
+
+ /**
* Returns a 32 characters long uniq ID
*
* @return string 32 chars
@@ -518,13 +598,47 @@ class Piwik_Common
}
/**
+ * Get salt from [superuser] section
+ *
+ * @return string
+ */
+ static public function getSalt()
+ {
+ static $salt = null;
+ if(is_null($salt))
+ {
+ if(!empty($GLOBALS['PIWIK_TRACKER_MODE']))
+ {
+ $salt = Piwik_Tracker_Config::getInstance()->superuser['salt'];
+ }
+ else
+ {
+ $config = Zend_Registry::get('config');
+ if($config !== false)
+ {
+ $salt = $config->superuser->salt;
+ }
+ }
+ }
+ return $salt;
+ }
+
+ /**
* Convert dotted IP to a stringified integer representation
*
* @return string ip
*/
static public function getIp()
{
- return sprintf("%u", ip2long(self::getIpString()));
+ $ip = self::getIpString();
+
+ // accept ipv4-mapped addresses
+ if(strpos($ip, '::ffff:') === 0)
+ {
+ $ip = substr($ip, 7);
+ }
+
+ return sprintf("%u", ip2long($ip));
}
/**
@@ -534,38 +648,34 @@ class Piwik_Common
*/
static public function getIpString()
{
- if(isset($_SERVER['HTTP_CLIENT_IP'])
- && ($ip = self::getFirstIpFromList($_SERVER['HTTP_CLIENT_IP']))
- && strpos($ip, "unknown") === false)
- {
- return $ip;
- }
- elseif(isset($_SERVER['HTTP_X_FORWARDED_FOR'])
- && $ip = self::getFirstIpFromList($_SERVER['HTTP_X_FORWARDED_FOR'])
- && isset($ip)
- && !empty($ip)
- && strpos($ip, "unknown")===false )
- {
- return $ip;
- }
- elseif( isset($_SERVER['HTTP_CLIENT_IP'])
- && strlen( self::getFirstIpFromList($_SERVER['HTTP_CLIENT_IP']) ) != 0 )
- {
- return self::getFirstIpFromList($_SERVER['HTTP_CLIENT_IP']);
- }
- else if( isset($_SERVER['HTTP_X_FORWARDED_FOR'])
- && strlen ($ip = self::getFirstIpFromList($_SERVER['HTTP_X_FORWARDED_FOR'])) != 0)
+ // note: these may be spoofed
+ static $clientHeaders = array(
+ // ISP proxy
+ 'HTTP_CLIENT_IP',
+
+ // de facto standard
+ 'HTTP_X_FORWARDED_FOR',
+ );
+
+ foreach($clientHeaders as $clientHeader)
{
- return $ip;
+ if(!empty($_SERVER[$clientHeader]))
+ {
+ $ip = self::getFirstIpFromList($_SERVER[$clientHeader]);
+ if(!empty($ip) && stripos($ip, 'unknown') === false)
+ {
+ return $ip;
+ }
+ }
}
- elseif(isset($_SERVER['REMOTE_ADDR']))
+
+ // default
+ if(isset($_SERVER['REMOTE_ADDR']))
{
return self::getFirstIpFromList($_SERVER['REMOTE_ADDR']);
}
- else
- {
- return '0.0.0.0';
- }
+
+ return '0.0.0.0';
}
/**
@@ -575,7 +685,7 @@ class Piwik_Common
*
* @return string first element before ','
*/
- static private function getFirstIpFromList($ip)
+ static public function getFirstIpFromList($ip)
{
$p = strpos($ip, ',');
if($p!==false)
@@ -657,14 +767,23 @@ class Piwik_Common
}
/**
- * Returns the visitor country based only on the Browser 'accepted language' information
+ * Returns the visitor country based on the Browser 'accepted language'
+ * information, but provides a hook for geolocation via IP address.
*
* @param string $lang browser lang
* @param bool If set to true, some assumption will be made and detection guessed more often, but accuracy could be affected
+ * @param string $ip
* @return string 2 letter ISO code
*/
- static public function getCountry( $lang, $enableLanguageToCountryGuess )
+ static public function getCountry( $lang, $enableLanguageToCountryGuess, $ip )
{
+ $country = null;
+ Piwik_PostEvent('Common.getCountry', $country, $ip);
+ if($country)
+ {
+ return $country;
+ }
+
if(empty($lang) || strlen($lang) < 2)
{
return 'xx';
@@ -797,13 +916,26 @@ class Piwik_Common
{
return false;
}
+ // some search engines (eg. Bing Images) use the same domain
+ // as an existing search engine (eg. Bing), we must also use the url path
+ $refererPath = '';
+ if(isset($refererParsed['path']))
+ {
+ $refererPath = $refererParsed['path'];
+ }
+ // no search query
if(!isset($refererParsed['query']))
{
return false;
}
require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/SearchEngines.php';
- if(!array_key_exists($refererHost, $GLOBALS['Piwik_SearchEngines']))
+ $refererHostPath = $refererHost . $refererPath;
+ if(array_key_exists($refererHostPath, $GLOBALS['Piwik_SearchEngines']))
+ {
+ $refererHost = $refererHostPath;
+ }
+ elseif(!array_key_exists($refererHost, $GLOBALS['Piwik_SearchEngines']))
{
return false;
}
@@ -824,10 +956,12 @@ class Piwik_Common
}
$query = $refererParsed['query'];
- if($searchEngineName == 'Google Images')
+ if($searchEngineName == 'Google Images'
+ || ($searchEngineName == 'Google' && strpos($refererUrl, '/imgres') !== false) )
{
$query = urldecode(trim(strtolower(self::getParameterFromQueryString($query, 'prev'))));
$query = str_replace('&', '&amp;', strstr($query, '?'));
+ $searchEngineName = 'Google Images';
}
foreach($variableNames as $variableName)
@@ -892,7 +1026,8 @@ class Piwik_Common
*/
static public function isPhpCliMode()
{
+ $remoteAddr = @$_SERVER['REMOTE_ADDR'];
return PHP_SAPI == 'cli' ||
- (substr(PHP_SAPI, 0, 3) == 'cgi' && @$_SERVER['REMOTE_ADDR'] == '');
+ (substr(PHP_SAPI, 0, 3) == 'cgi' && empty($remoteAddr));
}
}
diff --git a/core/Config.php b/core/Config.php
index 84851053ea..4801875131 100644
--- a/core/Config.php
+++ b/core/Config.php
@@ -20,6 +20,7 @@
* will read the value minimumMemoryLimit under the [General] section of the config file
*
* @package Piwik
+ * @subpackage Piwik_Config
*/
class Piwik_Config
{
@@ -37,7 +38,8 @@ class Piwik_Config
protected $pathIniFileDefaultConfig = null;
protected $configFileUpdated = false;
protected $doWriteFileWhenUpdated = true;
- protected $cachedConfigArray = array();
+ protected $cachedConfigArray = array();
+ protected $isTestEnvironment = false;
/**
* Storing the correct cwd() because the value is not correct in the destructor
@@ -104,12 +106,25 @@ class Piwik_Config
public function init()
{
- $this->defaultConfig = new Zend_Config_Ini($this->pathIniFileDefaultConfig, null, true);
- if(!Zend_Loader::isReadable($this->pathIniFileUserConfig))
+ if(!is_readable($this->pathIniFileDefaultConfig))
{
- throw new Exception("The configuration file {$this->pathIniFileUserConfig} has not been found.");
+ Piwik_ExitWithMessage(Piwik_TranslateException('General_ExceptionConfigurationFileNotFound', array($this->pathIniFileDefaultConfig)));
+ }
+ $this->defaultConfig = new Piwik_Config_Ini($this->pathIniFileDefaultConfig, null, true);
+ if(is_null($this->defaultConfig) || count($this->defaultConfig->toArray()) == 0)
+ {
+ Piwik_ExitWithMessage(Piwik_TranslateException('General_ExceptionUnreadableFileDisabledMethod', array($this->pathIniFileDefaultConfig, "parse_ini_file()")));
+ }
+
+ if(!is_readable($this->pathIniFileUserConfig))
+ {
+ throw new Exception(Piwik_TranslateException('General_ExceptionConfigurationFileNotFound', array($this->pathIniFileUserConfig)));
+ }
+ $this->userConfig = new Piwik_Config_Ini($this->pathIniFileUserConfig, null, true);
+ if(is_null($this->userConfig) || count($this->userConfig->toArray()) == 0)
+ {
+ Piwik_ExitWithMessage(Piwik_TranslateException('General_ExceptionUnreadableFileDisabledMethod', array($this->pathIniFileUserConfig, "parse_ini_file()")));
}
- $this->userConfig = new Zend_Config_Ini($this->pathIniFileUserConfig, null, true);
}
/**
@@ -154,16 +169,35 @@ class Piwik_Config
$configFile .= "\n";
}
chdir($this->correctCwd);
- file_put_contents($this->pathIniFileUserConfig, $configFile );
+ @file_put_contents($this->pathIniFileUserConfig, $configFile );
}
}
+ public function isFileWritable()
+ {
+ return is_writable($this->pathIniFileUserConfig);
+ }
+
/**
* If called, we use the database_tests credentials
*/
public function setTestEnvironment()
{
+ $this->isTestEnvironment = true;
$this->database = $this->database_tests->toArray();
+ // for unit tests, we set that no plugin is installed. This will force
+ // the test initialization to create the plugins tables, execute ALTER queries, etc.
+ $this->PluginsInstalled = array();
+ $this->disableSavingConfigurationFileUpdates();
+ }
+
+ /**
+ * Is the config file set to use the test values?
+ * @return bool
+ */
+ public function isTestEnvironment()
+ {
+ return $this->isTestEnvironment;
}
/**
@@ -294,3 +328,47 @@ class Piwik_Config
return $this->cachedConfigArray[$name];
}
}
+
+/**
+ * Subclasses Zend_Config_Ini so we can use our own parse_ini_file() wrapper.
+ *
+ * @package Piwik
+ * @subpackage Piwik_Config
+ */
+class Piwik_Config_Ini extends Zend_Config_Ini
+{
+ /**
+ * Handle any errors from parse_ini_file
+ *
+ * @param integer $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param integer $errline
+ */
+ public function _parseFileErrorHandler($errno, $errstr, $errfile, $errline)
+ {
+ $this->_loadFileErrorHandler($errno, $errstr, $errfile, $errline);
+ }
+
+ /**
+ * Load ini file configuration
+ *
+ * Derived from Zend_Config_Ini->_loadIniFile() and Zend_Config_Ini->_parseIniFile()
+ * @license New BSD License
+ *
+ * @param string $filename
+ * @return array
+ */
+ protected function _loadIniFile($filename)
+ {
+ set_error_handler(array($this, '_parseFileErrorHandler'));
+ $iniArray = _parse_ini_file($filename, true);
+ restore_error_handler();
+ // Check if there was an error while loading the file
+ if ($this->_loadFileErrorStr !== null) {
+ throw new Zend_Config_Exception($this->_loadFileErrorStr);
+ }
+
+ return $iniArray;
+ }
+}
diff --git a/core/Controller.php b/core/Controller.php
index 04d950fa80..f3c9fce465 100644
--- a/core/Controller.php
+++ b/core/Controller.php
@@ -37,6 +37,8 @@ abstract class Piwik_Controller
* @var Piwik_Date|null
*/
protected $date;
+ protected $idSite;
+ protected $site = null;
/**
* Builds the controller object, reads the date from the request, extracts plugin name from
@@ -45,11 +47,12 @@ abstract class Piwik_Controller
{
$aPluginName = explode('_', get_class($this));
$this->pluginName = $aPluginName[1];
- $this->strDate = Piwik_Common::getRequestVar('date', 'yesterday', 'string');
- try{
- // the date looks like YYYY-MM-DD we can build it
- $this->date = Piwik_Date::factory($this->strDate);
- $this->strDate = $this->date->toString();
+ $date = Piwik_Common::getRequestVar('date', 'yesterday', 'string');
+ try {
+ $this->idSite = Piwik_Common::getRequestVar('idSite', false, 'int');
+ $this->site = new Piwik_Site($this->idSite);
+ $date = $this->getDateParameterInTimezone($date, $this->site->getTimezone());
+ $this->setDate($date);
} catch(Exception $e){
// the date looks like YYYY-MM-DD,YYYY-MM-DD or other format
$this->date = null;
@@ -57,6 +60,48 @@ abstract class Piwik_Controller
}
/**
+ * Helper method to convert "today" or "yesterday" to the default timezone specified.
+ * If the date is absolute, ie. YYYY-MM-DD, it will not be converted to the timezone
+ * @param $date today, yesterday, YYYY-MM-DD
+ * @param $defaultTimezone
+ * @return Piwik_Date
+ */
+ protected function getDateParameterInTimezone($date, $defaultTimezone )
+ {
+ $timezone = null;
+ // if the requested date is not YYYY-MM-DD, we need to ensure
+ // it is relative to the website's timezone
+ if(in_array($date, array('today', 'yesterday')))
+ {
+ // today is at midnight; we really want to get the time now, so that
+ // * if the website is UTC+12 and it is 5PM now in UTC, the calendar will allow to select the UTC "tomorrow"
+ // * if the website is UTC-12 and it is 5AM now in UTC, the calendar will allow to select the UTC "yesterday"
+ if($date == 'today')
+ {
+ $date = 'now';
+ }
+ elseif($date == 'yesterday')
+ {
+ $date = 'yesterdaySameTime';
+ }
+ $timezone = $defaultTimezone;
+ }
+ return Piwik_Date::factory($date, $timezone);
+ }
+
+ /**
+ * Sets the date to be used by all other methods in the controller.
+ * If the date has to be modified, it should be called just after the controller construct
+ * @param $date
+ * @return void
+ */
+ protected function setDate(Piwik_Date $date)
+ {
+ $this->date = $date;
+ $this->strDate = $this->date->toString();
+ }
+
+ /**
* Returns the name of the default method that will be called
* when visiting: index.php?module=PluginName without the action parameter
*
@@ -181,14 +226,13 @@ abstract class Piwik_Controller
{
$period = $paramsToSet['period'];
}
- $last30Relative = new Piwik_Period_Range($period, $range );
+ $last30Relative = new Piwik_Period_Range($period, $range, $this->site->getTimezone() );
$last30Relative->setDefaultEndDate(Piwik_Date::factory($endDate));
$paramDate = $last30Relative->getDateStart()->toString() . "," . $last30Relative->getDateEnd()->toString();
$params = array_merge($paramsToSet , array( 'date' => $paramDate ) );
-
return $params;
}
@@ -228,6 +272,37 @@ abstract class Piwik_Controller
return $url;
}
+ /**
+ * Sets the first date available in the calendar
+ * @param $minDate
+ * @param $view
+ * @return void
+ */
+ protected function setMinDateView(Piwik_Date $minDate, $view)
+ {
+ $view->minDateYear = $minDate->toString('Y');
+ $view->minDateMonth = $minDate->toString('m');
+ $view->minDateDay = $minDate->toString('d');
+ }
+
+ /**
+ * Sets "today" in the calendar. Today does not always mean "UTC" today, eg. for websites in UTC+12.
+ * @param $maxDate
+ * @param $view
+ * @return void
+ */
+ protected function setMaxDateView(Piwik_Date $maxDate, $view)
+ {
+ $view->maxDateYear = $maxDate->toString('Y');
+ $view->maxDateMonth = $maxDate->toString('m');
+ $view->maxDateDay = $maxDate->toString('d');
+ }
+
+ /**
+ * Sets general variables to the view that are used by various templates and Javascript
+ * @param $view
+ * @return void
+ */
protected function setGeneralVariablesView($view)
{
$view->date = $this->strDate;
@@ -236,29 +311,35 @@ abstract class Piwik_Controller
$this->setPeriodVariablesView($view);
$period = Piwik_Period::factory(Piwik_Common::getRequestVar('period'), Piwik_Date::factory($this->strDate));
$view->prettyDate = $period->getLocalizedLongString();
- $idSite = Piwik_Common::getRequestVar('idSite');
- $view->idSite = $idSite;
- $site = new Piwik_Site($idSite);
- $view->siteName = $site->getName();
- $view->siteMainUrl = $site->getMainUrl();
+ $view->idSite = $this->idSite;
+ if(is_null($this->site))
+ {
+ throw new Exception("invalid website");
+ }
+ $view->siteName = $this->site->getName();
+ $view->siteMainUrl = $this->site->getMainUrl();
- $minDate = $site->getCreationDate();
- $view->minDateYear = $minDate->toString('Y');
- $view->minDateMonth = $minDate->toString('m');
- $view->minDateDay = $minDate->toString('d');
+ $datetimeMinDate = $this->site->getCreationDate()->getDatetime();
+ $minDate = Piwik_Date::factory($datetimeMinDate, $this->site->getTimezone());
+ $this->setMinDateView($minDate, $view);
- $maxDate = Piwik_Date::factory('today');
- $view->maxDateYear = $maxDate->toString('Y');
- $view->maxDateMonth = $maxDate->toString('m');
- $view->maxDateDay = $maxDate->toString('d');
+ $maxDate = Piwik_Date::factory('now', $this->site->getTimezone());
+ $this->setMaxDateView($maxDate, $view);
+ $view->currentAdminMenuName = Piwik_GetCurrentAdminMenuName();
$view->debugTrackVisitsInsidePiwikUI = Zend_Registry::get('config')->Debug->track_visits_inside_piwik_ui;
+ $view->isSuperUser = Zend_Registry::get('access')->isSuperUser();
} catch(Exception $e) {
self::redirectToIndex(Piwik::getModule(), Piwik::getAction());
}
}
+ /**
+ * Sets general period variables (available periods, current period, period labels) used by templates
+ * @param $view
+ * @return void
+ */
public static function setPeriodVariablesView($view)
{
if(isset($view->period))
@@ -288,44 +369,137 @@ abstract class Piwik_Controller
$view->periodsNames = $periodNames;
}
- function redirectToIndex($moduleToRedirect, $actionToRedirect)
+ /**
+ * Helper method used to redirect the current http request to another module/action
+ * If specified, will also redirect to a given website, period and /or date
+ *
+ * @param $moduleToRedirect eg. "MultiSites"
+ * @param $actionToRedirect eg. "index"
+ * @param $websiteId eg. 1
+ * @param $defaultPeriod eg. "day"
+ * @param $defaultDate eg. "today"
+ * @return issues a http header redirect and exits
+ */
+ function redirectToIndex($moduleToRedirect, $actionToRedirect, $websiteId = null, $defaultPeriod = null, $defaultDate = null)
{
- $sitesId = Piwik_SitesManager_API::getSitesIdWithAtLeastViewAccess();
- if(!empty($sitesId))
+ if(is_null($websiteId))
{
- $firstSiteId = $sitesId[0];
- $firstSite = new Piwik_Site($firstSiteId);
- if ($firstSite->getCreationDate()->isToday())
- {
- $defaultDate = 'today';
- }
- else
- {
- $defaultDate = Zend_Registry::get('config')->General->default_day;
- }
- $defaultPeriod = Zend_Registry::get('config')->General->default_period;
- header("Location:index.php?module=".$moduleToRedirect."&action=".$actionToRedirect."&idSite=$firstSiteId&period=$defaultPeriod&date=$defaultDate");
+ $websiteId = $this->getDefaultWebsiteId();
}
- else
+ if(is_null($defaultDate))
{
- if(Piwik::isUserIsSuperUser())
- {
- Piwik_ExitWithMessage("Error: no website were found in this Piwik installation.
- <br>Check the table '". Piwik::prefixTable('site') ."' that should contain your Piwik websites.", false, true);
- }
- $currentLogin = Piwik::getCurrentUserLogin();
- if(!empty($currentLogin)
- && $currentLogin != 'anonymous')
- {
- $errorMessage = sprintf(Piwik_Translate('CoreHome_NoPrivileges'),$currentLogin);
- $errorMessage .= "<br /><br />&nbsp;&nbsp;&nbsp;<b><a href='index.php?module=". Zend_Registry::get('auth')->getName() ."&amp;action=logout'>&rsaquo; ". Piwik_Translate('General_Logout'). "</a></b><br />";
- Piwik_ExitWithMessage($errorMessage, false, true);
- }
- else
- {
- Piwik_FrontController::dispatch('Login', false);
- }
+ $defaultDate = $this->getDefaultDate();
+ }
+ if(is_null($defaultPeriod))
+ {
+ $defaultPeriod = $this->getDefaultPeriod();
}
+
+ if($websiteId) {
+ header("Location:index.php?module=".$moduleToRedirect
+ ."&action=".$actionToRedirect
+ ."&idSite=".$websiteId
+ ."&period=".$defaultPeriod
+ ."&date=".$defaultDate);
+ exit;
+ }
+
+ if(Piwik::isUserIsSuperUser())
+ {
+ Piwik_ExitWithMessage("Error: no website were found in this Piwik installation.
+ <br />Check the table '". Piwik_Common::prefixTable('site') ."' that should contain your Piwik websites.", false, true);
+ }
+
+ $currentLogin = Piwik::getCurrentUserLogin();
+ if(!empty($currentLogin)
+ && $currentLogin != 'anonymous')
+ {
+ $errorMessage = sprintf(Piwik_Translate('CoreHome_NoPrivileges'),$currentLogin);
+ $errorMessage .= "<br /><br />&nbsp;&nbsp;&nbsp;<b><a href='index.php?module=". Zend_Registry::get('auth')->getName() ."&amp;action=logout'>&rsaquo; ". Piwik_Translate('General_Logout'). "</a></b><br />";
+ Piwik_ExitWithMessage($errorMessage, false, true);
+ }
+
+ Piwik_FrontController::dispatch(Piwik::getLoginPluginName(), false);
exit;
}
+
+
+ /**
+ * Returns default website that Piwik should load
+ * @return Piwik_Site
+ */
+ protected function getDefaultWebsiteId()
+ {
+ $defaultWebsiteId = false;
+
+ // User preference: default website ID to load
+ $defaultReport = Piwik_UsersManager_API::getInstance()->getUserPreference(Piwik::getCurrentUserLogin(), Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT);
+ if(is_numeric($defaultReport))
+ {
+ $defaultWebsiteId = $defaultReport;
+ }
+
+ Piwik_PostEvent( 'Controller.getDefaultWebsiteId', $defaultWebsiteId );
+
+ if($defaultWebsiteId)
+ {
+ return $defaultWebsiteId;
+ }
+
+ $sitesId = Piwik_SitesManager_API::getInstance()->getSitesIdWithAtLeastViewAccess();
+ if(!empty($sitesId))
+ {
+ return $sitesId[0];
+ }
+ return false;
+ }
+
+ /**
+ * Returns default date for Piwik reports
+ * @return string today, 2010-01-01, etc.
+ */
+ protected function getDefaultDate()
+ {
+ $userSettingsDate = Piwik_UsersManager_API::getInstance()->getUserPreference(Piwik::getCurrentUserLogin(), Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT_DATE);
+ if($userSettingsDate === false)
+ {
+ return Zend_Registry::get('config')->General->default_day;
+ }
+ if($userSettingsDate == 'yesterday')
+ {
+ return $userSettingsDate;
+ }
+ return 'today';
+ }
+
+ /**
+ * Returns default date for Piwik reports
+ * @return string today, 2010-01-01, etc.
+ */
+ protected function getDefaultPeriod()
+ {
+ $userSettingsDate = Piwik_UsersManager_API::getInstance()->getUserPreference(Piwik::getCurrentUserLogin(), Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT_DATE);
+ if($userSettingsDate === false)
+ {
+ return Zend_Registry::get('config')->General->default_period;
+ }
+ if(in_array($userSettingsDate, array('today','yesterday')))
+ {
+ return 'day';
+ }
+ return $userSettingsDate;
+ }
+
+ /**
+ * Checks that the specified token matches the current logged in user token
+ * Protection against CSRF
+ *
+ * @return throws exception if token doesn't match
+ */
+ protected function checkTokenInUrl()
+ {
+ if(Piwik_Common::getRequestVar('token_auth', false) != Piwik::getCurrentUserTokenAuth()) {
+ throw new Piwik_Access_NoAccessException(Piwik_TranslateException('General_ExceptionInvalidToken'));
+ }
+ }
}
diff --git a/core/Cookie.php b/core/Cookie.php
index 3da0fe5cd4..215f985450 100644
--- a/core/Cookie.php
+++ b/core/Cookie.php
@@ -46,11 +46,12 @@ class Piwik_Cookie
*
* @param string cookie Name
* @param int The timestamp after which the cookie will expire, eg time() + 86400
+ * @param string The path on the server in which the cookie will be available on.
*/
- public function __construct( $cookieName, $expire = null)
+ public function __construct( $cookieName, $expire = null, $path = null)
{
$this->name = $cookieName;
-
+ $this->path = $path;
$this->expire = $expire;
if(is_null($expire)
|| !is_numeric($expire)
@@ -59,6 +60,7 @@ class Piwik_Cookie
$this->expire = $this->getDefaultExpire();
}
+
if($this->isCookieFound())
{
$this->loadContentFromCookie();
@@ -139,7 +141,7 @@ class Piwik_Cookie
public function save()
{
$this->setP3PHeader();
- $this->setCookie( $this->name, $this->generateContentString(), $this->expire);
+ $this->setCookie( $this->name, $this->generateContentString(), $this->expire, $this->path);
}
/**
@@ -164,13 +166,7 @@ class Piwik_Cookie
$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;
- }
+ $varValue = Piwik_Common::unserialize_array($varValue);
}
$this->set($varName, $varValue);
@@ -245,12 +241,12 @@ class Piwik_Cookie
*/
public function __toString()
{
- $str = "<-- Content of the cookie '{$this->name}' <br>\n";
+ $str = "&lt;-- 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 .= $name . " = " . var_export($this->get($name), true) . "<br />\n";
}
- $str .= "--> <br>\n";
+ $str .= "--&gt; <br />\n";
return $str;
}
diff --git a/core/DataFiles/SearchEngines.php b/core/DataFiles/SearchEngines.php
index 7239247aea..6d467076df 100644
--- a/core/DataFiles/SearchEngines.php
+++ b/core/DataFiles/SearchEngines.php
@@ -1,37 +1,37 @@
<?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$
- *
+ *
* @category Piwik
* @package DataFiles
*/
/**
* 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
*
* See also: http://piwik.org/faq/general/#faq_39
- *
+ *
* Detail of a line:
* Url => array( SearchEngineName, KeywordParameter, [path containing the keyword], [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
+ * 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
- * To help Piwik link directly the search engine result page for the keyword, specify the third entry in the array
+ * To help Piwik link directly the search engine result page for the keyword, specify the third entry in the array
* using the macro {k} that will automatically be replaced by the keyword.
- *
+ *
* A simple example is:
* 'www.google.com' => array('Google', 'q', 'search?q={k}'),
- *
+ *
* A more complicated example, with an array of possible variable names, and a custom charset:
* 'www.baidu.com' => array('Baidu', array('wd', 'word', 'kw'), 's?wd={k}', 'gb2312'),
*/
@@ -50,10 +50,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// 1und1
'portal.1und1.de' => array('1und1', 'search'),
-
- // 3271
- 'nmsearch.3721.com' => array('3271', 'p'),
- 'seek.3721.com' => array('3271', 'p'),
+ 'search.1und1.de' => array('1und1', 'su', 'search/web/?mc=suche%40web%40home.suche%40web&allparams=&smode=&su={k}&search=Suche&webRb='),
// A9
'www.a9.com' => array('A9', ''),
@@ -72,9 +69,6 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// Acoon
'www.acoon.de' => array('Acoon', 'begriff'),
- // Acont
- 'acont.de' => array('Acont', 'query'),
-
// Alexa
'www.alexa.com' => array('Alexa', 'q', 'search?q={k}'),
'alexa.com' => array('Alexa', 'q'),
@@ -126,7 +120,6 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'www.aolrecherches.aol.fr' => array('AOL', array('query', 'q')),
'www.aolimages.aol.fr' => array('AOL', array('query', 'q')),
'www.recherche.aol.fr' => array('AOL', array('query', 'q')),
- 'aolsearcht.aol.com' => array('AOL', array('query', 'q')),
'find.web.aol.com' => array('AOL', array('query', 'q')),
'recherche.aol.ca' => array('AOL', array('query', 'q')),
'aolsearch.aol.co.uk' => array('AOL', array('query', 'q')),
@@ -156,6 +149,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// Ask
'www.ask.com' => array('Ask', array('ask', 'q'), 'web?q={k}'),
'web.ask.com' => array('Ask', array('ask', 'q')),
+ 'images.ask.com' => array('Ask', 'q'),
'ask.reference.com' => array('Ask', 'q'),
'www.ask.co.uk' => array('Ask', 'q'),
'uk.ask.com' => array('Ask', 'q'),
@@ -198,6 +192,9 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// Bing
'www.bing.com' => array('Bing', 'q', 'search?q={k}'),
+ // Bing Images
+ 'www.bing.com/images/search'=> array('Bing Images', 'q', 'search?q={k}'),
+
// Blogdigger
'www.blogdigger.com' => array('Blogdigger', 'q'),
@@ -236,9 +233,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// Comcast
'www.comcast.net' => array('Comcast', 'query'),
'search.comcast.net' => array('Comcast', 'q'),
-
- // Comet systems
- 'search.cometsystems.com' => array('CometSystems', 'q'),
+ 'search3.comcast.com' => array('Comcast', 'url'),
// Compuserve
'suche.compuserve.de' => array('Compuserve.de (Powered by Google)', 'q'),
@@ -274,6 +269,9 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// Deskfeeds
'www.deskfeeds.com' => array('Deskfeeds', 'sx'),
+ // Digg
+ 'digg.com' => array('Digg', 's', 'search?s={k}'),
+
// Dino
'www.dino-online.de' => array('Dino', 'query'),
@@ -290,9 +288,16 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'search.dogpile.com' => array('Dogpile', 'q'),
'nbci.dogpile.com' => array('Dogpile', 'q'),
+ // DuckDuckGo
+ 'duckduckgo.com' => array('DuckDuckGo', 'q', '?q={k}'),
+
// earthlink
'search.earthlink.net' => array('Earthlink', 'q'),
+ // Ecosia (powered by Bing)
+ 'ecosia.org' => array('Ecosia', 'q', 'search.php?q={k}'),
+ 'www.ecosia.org' => array('Ecosia', 'q'),
+
// Eniro
'www.eniro.se' => array('Eniro', 'q'),
@@ -323,6 +328,9 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// eo
'eo.st' => array('eo', 'q'),
+ // Facebook
+ 'www.facebook.com' => array('Facebook', 'q', 'search/?q={k}'),
+
// Feedminer
'www.feedminer.com' => array('Feedminer', 'q'),
@@ -335,6 +343,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// Fireball
'suche.fireball.de' => array('Fireball', 'query'),
+ 'www.fireball.de' => array('Fireball', 'q'),
// Firstfind
'www.firstsfind.com' => array('Firstsfind', 'qry'),
@@ -345,6 +354,13 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// Flix
'www.flix.de' => array('Flix.de', 'keyword'),
+ // Forestle
+ 'de.forestle.org' => array('Forestle', 'q', 'search.php?q={k}'),
+ 'at.forestle.org' => array('Forestle', 'q', 'search.php?q={k}'),
+ 'ch.forestle.org' => array('Forestle', 'q', 'search.php?q={k}'),
+ 'us.forestle.org' => array('Forestle', 'q', 'search.php?q={k}'),
+ 'fr.forestle.org' => array('Forestle', 'q', 'search.php?q={k}'),
+
// Free
'search.free.fr' => array('Free', 'q'),
'search1-2.free.fr' => array('Free', 'q'),
@@ -365,14 +381,13 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'froogle.google.co.uk' => array('Google (Froogle)', 'q'),
// GAIS
- 'gais.cs.ccu.edu.tw' => array('GAIS)', 'query'),
+ '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'),
@@ -396,7 +411,10 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'www.gogole.com' => array('Google', 'q'),
'www.gppgle.com' => array('Google', 'q'),
'go.google.com' => array('Google', 'q'),
+ 'www.google.ad' => array('Google', 'q'),
'www.google.ae' => array('Google', 'q'),
+ 'www.google.am' => array('Google', 'q'),
+ 'www.google.it.ao' => array('Google', 'q'),
'www.google.as' => array('Google', 'q'),
'www.google.at' => array('Google', 'q'),
'wwwgoogle.at' => array('Google', 'q'),
@@ -405,21 +423,28 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'www.google.az' => array('Google', 'q'),
'www.google.ba' => array('Google', 'q'),
'www.google.be' => array('Google', 'q'),
+ 'www.google.bf' => array('Google', 'q'),
'www.google.bg' => array('Google', 'q'),
'google.bg' => array('Google', 'q'),
'www.google.bi' => array('Google', 'q'),
+ 'www.google.bj' => array('Google', 'q'),
+ 'www.google.bs' => array('Google', 'q'),
'www.google.ca' => array('Google', 'q'),
'ww.google.ca' => array('Google', 'q'),
'w.google.ca' => array('Google', 'q'),
+ 'www.google.cat' => array('Google', 'q'),
'www.google.cc' => array('Google', 'q'),
'www.google.cd' => array('Google', 'q'),
+ 'google.cf' => 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'),
+ 'google.co.ck' => array('Google', 'q'),
'www.google.cl' => array('Google', 'q'),
'www.google.cn' => array('Google', 'q'),
+ 'google.cm' => array('Google', 'q'),
'www.google.co' => array('Google', 'q'),
'www.google.cz' => array('Google', 'q'),
'wwwgoogle.cz' => array('Google', 'q'),
@@ -427,11 +452,14 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'ww.google.de' => array('Google', 'q'),
'w.google.de' => array('Google', 'q'),
'wwwgoogle.de' => array('Google', 'q'),
+ 'google.dm' => array('Google', 'q'),
+ 'google.dz' => array('Google', 'q'),
'www.google.ee' => 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.googel.fi' => array('Google', 'q'),
'www.google.fm' => array('Google', 'q'),
'gogole.fr' => array('Google', 'q'),
'www.gogole.fr' => array('Google', 'q'),
@@ -441,35 +469,53 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'www.google.fr' => array('Google', 'q'),
'www.google.fr.' => array('Google', 'q'),
'google.fr' => array('Google', 'q'),
- 'www.google.gg' => array('Google', 'q'),
+ 'www.google.ga' => array('Google', 'q'),
'google.ge' => array('Google', 'q'),
'w.google.ge' => array('Google', 'q'),
'ww.google.ge' => array('Google', 'q'),
'www.google.ge' => array('Google', 'q'),
+ 'www.google.gg' => array('Google', 'q'),
'google.gr' => array('Google', 'q'),
- 'www.googel.fi' => array('Google', 'q'),
'www.google.gl' => array('Google', 'q'),
'www.google.gm' => array('Google', 'q'),
+ 'www.google.gp' => array('Google', 'q'),
'www.google.gr' => array('Google', 'q'),
+ 'www.google.gy' => array('Google', 'q'),
'www.google.hn' => array('Google', 'q'),
'www.google.hr' => array('Google', 'q'),
+ 'www.google.ht' => array('Google', 'q'),
'www.google.hu' => array('Google', 'q'),
'www.google.ie' => array('Google', 'q'),
+ 'www.google.im' => array('Google', 'q'),
'www.google.is' => array('Google', 'q'),
'www.google.it' => array('Google', 'q'),
+ 'www.google.je' => array('Google', 'q'),
'www.google.jo' => array('Google', 'q'),
+ 'www.google.ki' => array('Google', 'q'),
+ 'www.google.kg' => array('Google', 'q'),
'www.google.kz' => array('Google', 'q'),
+ 'www.google.la' => array('Google', 'q'),
'www.google.li' => array('Google', 'q'),
'www.google.lk' => array('Google', 'q'),
'www.google.lt' => array('Google', 'q'),
'www.google.lu' => array('Google', 'q'),
'www.google.lv' => array('Google', 'q'),
'www.google.md' => array('Google', 'q'),
+ 'www.google.me' => array('Google', 'q'),
+ 'www.google.mg' => array('Google', 'q'),
+ 'www.google.mk' => array('Google', 'q'),
+ 'www.google.ml' => array('Google', 'q'),
+ 'www.google.mn' => array('Google', 'q'),
'www.google.ms' => array('Google', 'q'),
'www.google.mu' => array('Google', 'q'),
+ 'www.google.mv' => array('Google', 'q'),
'www.google.mw' => array('Google', 'q'),
+ 'www.google.ne' => array('Google', 'q'),
'www.google.nl' => array('Google', 'q'),
'www.google.no' => array('Google', 'q'),
+ 'www.google.nr' => array('Google', 'q'),
+ 'www.google.nu' => array('Google', 'q'),
+ 'www.google.ps' => array('Google', 'q'),
'www.google.pl' => array('Google', 'q'),
'www.google.pn' => array('Google', 'q'),
'www.google.pt' => array('Google', 'q'),
@@ -477,16 +523,26 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'www.google.rs' => array('Google', 'q'),
'www.google.ru' => array('Google', 'q'),
'www.google.rw' => array('Google', 'q'),
+ 'www.google.sc' => array('Google', 'q'),
'www.google.se' => array('Google', 'q'),
'www.google.sh' => array('Google', 'q'),
'www.google.si' => array('Google', 'q'),
'www.google.sk' => array('Google', 'q'),
'www.google.sm' => array('Google', 'q'),
'www.google.sn' => array('Google', 'q'),
+ 'www.google.st' => array('Google', 'q'),
'www.google.td' => array('Google', 'q'),
+ 'www.google.tg' => array('Google', 'q'),
+ 'www.google.tk' => array('Google', 'q'),
+ 'www.google.tl' => array('Google', 'q'),
+ 'www.google.tm' => array('Google', 'q'),
+ 'www.google.to' => array('Google', 'q'),
'www.google.tt' => array('Google', 'q'),
'www.google.uz' => array('Google', 'q'),
+ 'www.google.vu' => array('Google', 'q'),
'www.google.vg' => array('Google', 'q'),
+ 'www.google.ws' => array('Google', 'q'),
+ 'www.google.co.bw' => array('Google', 'q'),
'www.google.co.cr' => array('Google', 'q'),
'www.google.co.gg' => array('Google', 'q'),
'www.google.co.hu' => array('Google', 'q'),
@@ -499,23 +555,36 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'www.google.co.ke' => array('Google', 'q'),
'www.google.co.kr' => array('Google', 'q'),
'www.google.co.ma' => array('Google', 'q'),
+ 'www.google.co.mz' => array('Google', 'q'),
'www.google.co.nz' => array('Google', 'q'),
'www.google.co.th' => array('Google', 'q'),
+ 'www.google.co.tz' => array('Google', 'q'),
+ 'www.google.co.ug' => array('Google', 'q'),
'www.google.co.uk' => array('Google', 'q'),
+ 'www.google.co.uz' => array('Google', 'q'),
+ 'www.google.co.vi' => array('Google', 'q'),
'www.google.co.ve' => array('Google', 'q'),
'www.google.co.za' => array('Google', 'q'),
+ 'www.google.co.zm' => array('Google', 'q'),
'www.google.co.zw' => array('Google', 'q'),
+ 'www.google.com.af' => array('Google', 'q'),
+ 'www.google.com.ag' => array('Google', 'q'),
+ 'www.google.com.ai' => array('Google', 'q'),
'www.google.com.ar' => array('Google', 'q'),
'www.google.com.au' => array('Google', 'q'),
+ 'www.google.com.bd' => array('Google', 'q'),
'www.google.com.bh' => array('Google', 'q'),
+ 'www.google.com.bn' => array('Google', 'q'),
'www.google.com.bo' => array('Google', 'q'),
'www.google.com.br' => array('Google', 'q'),
'www.google.com.by' => array('Google', 'q'),
+ 'www.google.com.bz' => array('Google', 'q'),
'www.google.com.co' => array('Google', 'q'),
'www.google.com.cu' => array('Google', 'q'),
'www.google.com.do' => array('Google', 'q'),
'www.google.com.ec' => array('Google', 'q'),
'www.google.com.eg' => array('Google', 'q'),
+ 'www.google.com.et' => array('Google', 'q'),
'www.google.com.fj' => array('Google', 'q'),
'www.google.com.gh' => array('Google', 'q'),
'www.google.com.gi' => array('Google', 'q'),
@@ -523,7 +592,9 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'www.google.com.gt' => array('Google', 'q'),
'www.google.com.hk' => array('Google', 'q'),
'www.google.com.jm' => array('Google', 'q'),
+ 'www.google.com.kh' => array('Google', 'q'),
'www.google.com.kw' => array('Google', 'q'),
+ 'www.google.com.lb' => array('Google', 'q'),
'www.google.com.ly' => array('Google', 'q'),
'www.google.com.mt' => array('Google', 'q'),
'www.google.com.mx' => array('Google', 'q'),
@@ -544,8 +615,11 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'www.google.com.qa' => array('Google', 'q'),
'www.google.com.ru' => array('Google', 'q'),
'www.google.com.sa' => array('Google', 'q'),
+ 'www.google.com.sb' => array('Google', 'q'),
'www.google.com.sg' => array('Google', 'q'),
+ 'www.google.com.sl' => array('Google', 'q'),
'www.google.com.sv' => array('Google', 'q'),
+ 'www.google.com.tj' => array('Google', 'q'),
'www.google.com.tr' => array('Google', 'q'),
'www.google.com.tw' => array('Google', 'q'),
'www.google.com.ua' => array('Google', 'q'),
@@ -594,6 +668,9 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'blogsearch.google.co.in' => array('Google Blogsearch', 'q'),
'blogsearch.google.co.uk' => array('Google Blogsearch', 'q'),
+ // Google Custom Search
+ 'www.google.com/cse' => array('Google Custom Search', 'q'),
+
// Google translation
'translate.google.com' => array('Google Translations', 'q'),
@@ -690,7 +767,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// Goyellow.de
'www.goyellow.de' => array('GoYellow.de', 'MDN'),
- // Gule Sider:
+ // Gule Sider
'www.gulesider.no' => array('Gule Sider', 'q'),
// HighBeam
@@ -726,9 +803,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'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'),
+ 'www.ilse.nl' => array('Ilse NL', 'search_for', '?search_for={k}'),
// Iwon
'search.iwon.com' => array('Iwon', 'searchfor'),
@@ -885,23 +960,23 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'ariadna.elmundo.es' => array('El Mundo', 'q'),
// MySpace
- 'searchservice.myspace.com' => array('MySpace', 'qry'),
+ 'searchservice.myspace.com' => array('MySpace', 'qry', 'index.cfm?fuseaction=sitesearch.results&type=Web&qry={k}'),
- // MyWebSearch
- 'kf.mysearch.myway.com' => array('MyWebSearch', 'searchfor'),
+ // MySearch / MyWay / MyWebSearch (default: powered by Ask.com)
+ 'www.mysearch.com' => array('MyWebSearch', 'searchfor', 'search/Ajmain.jhtml?searchfor={k}'),
'ms114.mysearch.com' => array('MyWebSearch', 'searchfor'),
'ms146.mysearch.com' => array('MyWebSearch', 'searchfor'),
- 'mysearch.myway.com' => array('MyWebSearch', 'searchfor'),
- 'searchfr.myway.com' => array('MyWebSearch', 'searchfor'),
+ 'kf.mysearch.myway.com' => array('MyWebSearch', 'searchfor'),
'ki.mysearch.myway.com' => array('MyWebSearch', 'searchfor'),
- 'search.mywebsearch.com' => array('MyWebSearch', 'searchfor'),
- 'www.mywebsearch.com' => array('MyWebSearch', 'searchfor'),
+ 'search.myway.com' => array('MyWebSearch', 'searchfor'),
+ 'search.mywebsearch.com' => array('MyWebSearch', 'searchfor', 'mywebsearch/Ajmain.jhtml?searchfor={k}'),
+
// Najdi
'www.najdi.si' => array('Najdi.si', 'q'),
// Naver
- 'search.naver.com' => array('Naver', 'query'),
+ 'search.naver.com' => array('Naver', 'query', 'search.naver?query={k}', 'x-windows-949'),
// Needtofind
'ko.search.need2find.com' => array('Needtofind', 'searchfor'),
@@ -939,7 +1014,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// Onet
'szukaj.onet.pl' => array('Onet.pl', 'qt'),
- // Online.no:
+ // Online.no
'www.online.no' => array('Online.no', 'q'),
'online.no' => array('Online.no', 'q'),
@@ -951,7 +1026,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'bbs2.openfind.com.tw' => array('Openfind (BBS)', 'query'),
'news.openfind.com.tw' => array('Openfind (News)', 'query'),
- // Opplysningen 1881:
+ // Opplysningen 1881
'www.1881.no' => array('Opplysningen 1881', 'Query'),
// Overture
@@ -981,9 +1056,9 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'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.ch' => array('Qualigo', 'q'),
+ 'www.qualigo.de' => array('Qualigo', 'q'),
'www.qualigo.nl' => array('Qualigo', 'q'),
// Rambler
@@ -994,6 +1069,11 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// Reacteur.com
'www.reacteur.com' => array('Reacteur', 'kw'),
+ // RPMFind
+ 'www.rpmfind.net' => array('rpmfind', 'query', 'linux/rpm2html/search.php?query={k}'),
+ 'rpmfind.net' => array('rpmfind', 'query'),
+ 'fr2.rpmfind.net' => array('rpmfind', 'query'),
+
// Sapo
'pesquisa.sapo.pt' => array('Sapo', 'q'),
@@ -1003,36 +1083,13 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// Search.ch
'www.search.ch' => array('Search.ch', 'q'),
- // Search a lot
- 'www.searchalot.com' => array('Searchalot', 'query'),
+ // Searchalot
+ 'www.searchalot.com' => array('Searchalot', 'q', '?q={k}'),
+ 'searchalot.com' => array('Searchalot', 'q'),
// 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'),
@@ -1046,13 +1103,13 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'bg.setooz.com' => array('Setooz', 'query'),
'el.setooz.com' => array('Setooz', 'query'),
'et.setooz.com' => array('Setooz', 'query'),
- 'lv.setooz.com' => array('Setooz', 'query'),
- 'lt.setooz.com' => array('Setooz', 'query'),
+ 'fi.setooz.com' => array('Setooz', 'query'),
'hu.setooz.com' => array('Setooz', 'query'),
+ 'lt.setooz.com' => array('Setooz', 'query'),
+ 'lv.setooz.com' => array('Setooz', 'query'),
'no.setooz.com' => array('Setooz', 'query'),
'pl.setooz.com' => array('Setooz', 'query'),
'sk.setooz.com' => array('Setooz', 'query'),
- 'fi.setooz.com' => array('Setooz', 'query'),
'sv.setooz.com' => array('Setooz', 'query'),
'tr.setooz.com' => array('Setooz', 'query'),
'uk.setooz.com' => array('Setooz', 'query'),
@@ -1069,8 +1126,11 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// Skynet
'search.skynet.be' => array('Skynet', 'keywords'),
+ // Sogou
+ 'www.sogou.com' => array('Sogou', 'query', 'web?query={k}'),
+
// soso.com
- 'www.soso.com' => array('Soso', 'w'),
+ 'www.soso.com' => array('Soso', 'w', 'q?w={k}', 'gb2312'),
// Sphere
'www.sphere.com' => array('Sphere', 'q'),
@@ -1109,8 +1169,12 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'search-dyn.tiscali.de' => array('Tiscali', 'key'),
'hledani.tiscali.cz' => array('Tiscali', 'query', false, 'windows-1250'),
+ // Tixuma
+ 'www.tixuma.de' => array('Tixuma', 'sc', 'index.php?mp=search&stp=&sc={k}&tg=0'),
+
// T-Online
'suche.t-online.de' => array('T-Online', 'q'),
+ 'navigationshilfe.t-online.de'=> array('T-Online', 'q', 'dtag/dns/results?mode=search_top&q={k}'),
// Trouvez.com
'www.trouvez.com' => array('Trouvez.com', 'query'),
@@ -1156,9 +1220,31 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'suche.web.de' => array('Web.de (Websuche)', 'su'),
'dir.web.de' => array('Web.de (Directory)', 'su'),
+ // 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'),
+
// Webtip
'www.webtip.de' => array('Webtip', 'keyword'),
+ // Wedoo
+ 'fr.wedoo.com' => array('Wedoo', 'keyword'),
+
+ // Witch
+ 'www.witch.de' => array('Witch', 'search'),
+
+ // WXS
+ 'wxsl.nl' => array('Planet Internet', 'q'),
+
+ // WWW
+ 'search.www.ee' => array('www värav', 'query'),
+
// X-recherche
'www.x-recherche.com' => array('X-Recherche', 'mots'),
@@ -1174,28 +1260,32 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'au.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'),
+ 'cade.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'),
+ 'de.search.yahoo.com' => array('Yahoo!', 'p'),
'es.search.yahoo.com' => array('Yahoo!', 'p'),
+ 'espanol.search.yahoo.com' => array('Yahoo!', 'p'),
+ 'fr.search.yahoo.com' => array('Yahoo!', 'p'),
+ 'hk.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'),
'qc.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'),
+ 'ru.search.yahoo.com' => array('Yahoo!', 'p'),
'se.search.yahoo.com' => array('Yahoo!', 'p'),
+ 'tw.search.yahoo.com' => array('Yahoo!', 'p'),
+ 'uk.search.yahoo.com' => array('Yahoo!', 'p'),
'us.search.yahoo.com' => array('Yahoo!', 'p'),
- 'ru.search.yahoo.com' => array('Yahoo!', 'p'),
- 'www.yahoo.com.cn' => array('Yahoo!', 'p'),
+ 'search.cn.yahoo.com' => array('Yahoo!', 'p'),
+ 'one.cn.yahoo.com' => array('Yahoo!', 'p'),
+ 'cns.3721.com' => array('Yahoo!', 'p'), // acquired by Yahoo!
'au.yhs.search.yahoo.com' => array('Yahoo!', 'p', 'avg/search?p={k}'),
-
+ 'de.yhs.search.yahoo.com' => array('Yahoo!', 'p', 'avg/search?p={k}'),
+ 'us.yhs.search.yahoo.com' => array('Yahoo!', 'p', 'avg/search?p={k}'),
'de.dir.yahoo.com' => array('Yahoo! Webverzeichnis', ''),
'cf.dir.yahoo.com' => array('Yahoo! Directory', ''),
'fr.dir.yahoo.com' => array('Yahoo! Directory', ''),
@@ -1205,6 +1295,7 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
// Yandex
'yandex.ru' => array('Yandex', 'text', 'yandsearch?text={k}'),
+ 'yandex.ua' => array('Yandex', 'text'),
'www.yandex.ru' => array('Yandex', 'text'),
'search.yaca.yandex.ru' => array('Yandex', 'text'),
'ya.ru' => array('Yandex', 'text'),
@@ -1224,33 +1315,6 @@ if(!isset($GLOBALS['Piwik_SearchEngines'] ))
'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'),
-
- // WWW
- 'search.www.ee' => array('www värav', 'query'),
-
- // WXS
- 'wxsl.nl' => array('Planet Internet', 'q'),
-
// Zoek
'www3.zoek.nl' => array('Zoek', 'q'),
diff --git a/core/DataTable.php b/core/DataTable.php
index a5590e1cbb..13f7f1e3ff 100644
--- a/core/DataTable.php
+++ b/core/DataTable.php
@@ -588,14 +588,13 @@ class Piwik_DataTable
*/
public function getRowsCount()
{
- $count = count($this->rows);
if(is_null($this->summaryRow))
{
- return $count;
+ return count($this->rows);
}
else
{
- return $count + 1;
+ return count($this->rows) + 1;
}
}
@@ -820,10 +819,7 @@ class Piwik_DataTable
$table1->rebuildIndex();
$table2->rebuildIndex();
- $countrows1 = $table1->getRowsCount();
- $countrows2 = $table2->getRowsCount();
-
- if($countrows1 != $countrows2)
+ if($table1->getRowsCount() != $table2->getRowsCount())
{
return false;
}
@@ -831,11 +827,8 @@ class Piwik_DataTable
foreach($rows1 as $row1)
{
$row2 = $table2->getRowFromLabel($row1->getColumn('label'));
- if($row2 === false)
- {
- return false;
- }
- if( !Piwik_DataTable_Row::isEqual($row1,$row2) )
+ if($row2 === false
+ || !Piwik_DataTable_Row::isEqual($row1,$row2))
{
return false;
}
diff --git a/core/DataTable/Filter/AddColumnsWhenShowAllColumns.php b/core/DataTable/Filter/AddColumnsWhenShowAllColumns.php
index 1e1e703b66..1b126e42a5 100644
--- a/core/DataTable/Filter/AddColumnsWhenShowAllColumns.php
+++ b/core/DataTable/Filter/AddColumnsWhenShowAllColumns.php
@@ -17,7 +17,13 @@
class Piwik_DataTable_Filter_AddColumnsWhenShowAllColumns extends Piwik_DataTable_Filter
{
protected $roundPrecision = 1;
- public function __construct( $table )
+
+ /**
+ * @param $table
+ * @param $enable Automatically set to true when filter_add_columns_when_show_all_columns is found in the API request
+ * @return void
+ */
+ public function __construct( $table, $enable = true )
{
parent::__construct($table);
$this->filter();
diff --git a/core/DataTable/Filter/ColumnCallbackAddColumnPercentage.php b/core/DataTable/Filter/ColumnCallbackAddColumnPercentage.php
index 90ea6b9ee5..d63d8298e3 100644
--- a/core/DataTable/Filter/ColumnCallbackAddColumnPercentage.php
+++ b/core/DataTable/Filter/ColumnCallbackAddColumnPercentage.php
@@ -19,61 +19,16 @@
* You can also specify the precision of the percentage value to be displayed (defaults to 0, eg "11%")
*
* Usage:
- * $nbVisits = Piwik_VisitsSummary_API::getVisits($idSite, $period, $date);
+ * $nbVisits = Piwik_VisitsSummary_API::getInstance()->getVisits($idSite, $period, $date);
* $dataTable->queueFilter('ColumnCallbackAddColumnPercentage', array('nb_visits', 'nb_visits_percentage', $nbVisits, 1));
*
* @package Piwik
* @subpackage Piwik_DataTable
*/
-class Piwik_DataTable_Filter_ColumnCallbackAddColumnPercentage extends Piwik_DataTable_Filter
+class Piwik_DataTable_Filter_ColumnCallbackAddColumnPercentage extends Piwik_DataTable_Filter_ColumnCallbackAddColumnQuotient
{
- private $columnValueToRead;
- private $columnNamePercentageToAdd;
- private $columnNameUsedAsDivisor;
- private $totalValueUsedAsDivisor;
- private $percentagePrecision;
-
- /**
- * @param Piwik_DataTable $table
- * @param string $columnValueToRead
- * @param string $columnNamePercentageToAdd
- * @param numeric|string $totalValueUsedToComputePercentageOrColumnName
- * if a numeric value is given, we use this value as the divisor to process the percentage.
- * if a string is given, this string is the column name's value used as the divisor.
- * @param int $percentagePrecision precision 0 means "11", 1 means "11.2"
- */
- public function __construct( $table, $columnValueToRead, $columnNamePercentageToAdd, $totalValueUsedToComputePercentageOrColumnName, $percentagePrecision = 0 )
+ protected function formatValue($value, $divisor)
{
- parent::__construct($table);
- $this->columnValueToRead = $columnValueToRead;
- $this->columnNamePercentageToAdd = $columnNamePercentageToAdd;
- if(is_numeric($totalValueUsedToComputePercentageOrColumnName))
- {
- $this->totalValueUsedAsDivisor = $totalValueUsedToComputePercentageOrColumnName;
- }
- else
- {
- $this->columnNameUsedAsDivisor = $totalValueUsedToComputePercentageOrColumnName;
- }
- $this->percentagePrecision = $percentagePrecision;
- $this->filter();
- }
-
- protected function filter()
- {
- foreach($this->table->getRows() as $key => $row)
- {
- $value = $row->getColumn($this->columnValueToRead);
- if(!is_null($this->totalValueUsedAsDivisor))
- {
- $divisor = $this->totalValueUsedAsDivisor;
- }
- else
- {
- $divisor = $row->getColumn($this->columnNameUsedAsDivisor);
- }
- $percentage = Piwik::getPercentageSafe($value, $divisor, $this->percentagePrecision);
- $row->addColumn($this->columnNamePercentageToAdd, $percentage);
- }
+ return Piwik::getPercentageSafe($value, $divisor, $this->quotientPrecision) . '%';
}
}
diff --git a/core/DataTable/Filter/ColumnCallbackAddColumnQuotient.php b/core/DataTable/Filter/ColumnCallbackAddColumnQuotient.php
new file mode 100644
index 0000000000..1e389360dc
--- /dev/null
+++ b/core/DataTable/Filter/ColumnCallbackAddColumnQuotient.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$
+ *
+ * @category Piwik
+ * @package Piwik
+ */
+
+/**
+ * Adds a new column that is a division of two columns of the current row.
+ * Useful to process bounce rates, exit rates, average time on page, etc.
+ *
+ * @package Piwik
+ * @subpackage Piwik_DataTable
+ */
+class Piwik_DataTable_Filter_ColumnCallbackAddColumnQuotient extends Piwik_DataTable_Filter
+{
+ protected $columnValueToRead;
+ protected $columnNameToAdd;
+ protected $columnNameUsedAsDivisor;
+ protected $totalValueUsedAsDivisor;
+ protected $quotientPrecision;
+
+ /**
+ * @param Piwik_DataTable $table
+ * @param string $columnValueToRead
+ * @param string $columnNameToAdd
+ * @param numeric|string $divisorValueOrDivisorColumnName
+ * if a numeric value is given, we use this value as the divisor to process the percentage.
+ * if a string is given, this string is the column name's value used as the divisor.
+ * @param numeric $quotientPrecision Division precision
+ */
+ public function __construct( $table, $columnNameToAdd, $columnValueToRead, $divisorValueOrDivisorColumnName, $quotientPrecision = 0)
+ {
+ parent::__construct($table);
+ $this->columnValueToRead = $columnValueToRead;
+ $this->columnNameToAdd = $columnNameToAdd;
+ if(is_numeric($divisorValueOrDivisorColumnName))
+ {
+ $this->totalValueUsedAsDivisor = $divisorValueOrDivisorColumnName;
+ }
+ else
+ {
+ $this->columnNameUsedAsDivisor = $divisorValueOrDivisorColumnName;
+ }
+ $this->filter();
+ }
+
+ protected function filter()
+ {
+ foreach($this->table->getRows() as $key => $row)
+ {
+ $value = $row->getColumn($this->columnValueToRead);
+ if(!is_null($this->totalValueUsedAsDivisor))
+ {
+ $divisor = $this->totalValueUsedAsDivisor;
+ }
+ else
+ {
+ $divisor = $row->getColumn($this->columnNameUsedAsDivisor);
+ }
+ $formattedValue = $this->formatValue($value, $divisor);
+ $row->addColumn($this->columnNameToAdd, $formattedValue);
+ }
+ }
+
+ protected function formatValue($value, $divisor)
+ {
+ $quotient = 0;
+ if($divisor > 0 && $value > 0)
+ {
+ $quotient = round($value / $divisor, $this->quotientPrecision);
+ }
+ return $quotient;
+ }
+}
diff --git a/core/DataTable/Filter/ColumnCallbackAddMetadata.php b/core/DataTable/Filter/ColumnCallbackAddMetadata.php
index bef01a9945..d90079e3f1 100644
--- a/core/DataTable/Filter/ColumnCallbackAddMetadata.php
+++ b/core/DataTable/Filter/ColumnCallbackAddMetadata.php
@@ -27,7 +27,7 @@ class Piwik_DataTable_Filter_ColumnCallbackAddMetadata extends Piwik_DataTable_F
private $functionParameters;
private $metadataToAdd;
- public function __construct( $table, $columnToRead, $metadataToAdd, $functionToApply, $functionParameters = null )
+ public function __construct( $table, $columnToRead, $metadataToAdd, $functionToApply = null, $functionParameters = null )
{
parent::__construct($table);
$this->functionToApply = $functionToApply;
@@ -47,7 +47,14 @@ class Piwik_DataTable_Filter_ColumnCallbackAddMetadata extends Piwik_DataTable_F
{
$parameters = array_merge($parameters, $this->functionParameters);
}
- $newValue = call_user_func_array( $this->functionToApply, $parameters);
+ if(!is_null($this->functionToApply))
+ {
+ $newValue = call_user_func_array( $this->functionToApply, $parameters);
+ }
+ else
+ {
+ $newValue = $oldValue;
+ }
$row->addMetadata($this->metadataToAdd, $newValue);
}
}
diff --git a/core/DataTable/Filter/ColumnCallbackDeleteRow.php b/core/DataTable/Filter/ColumnCallbackDeleteRow.php
index c9ed614bb0..14468b14ef 100644
--- a/core/DataTable/Filter/ColumnCallbackDeleteRow.php
+++ b/core/DataTable/Filter/ColumnCallbackDeleteRow.php
@@ -34,8 +34,7 @@ class Piwik_DataTable_Filter_ColumnCallbackDeleteRow extends Piwik_DataTable_Fil
foreach($this->table->getRows() as $key => $row)
{
$columnValue = $row->getColumn($this->columnToFilter);
- if( $columnValue !== false
- && !call_user_func( $this->function, $columnValue))
+ if( !call_user_func( $this->function, $columnValue))
{
$this->table->deleteRow($key);
}
diff --git a/core/DataTable/Filter/ColumnCallbackReplace.php b/core/DataTable/Filter/ColumnCallbackReplace.php
index 6b286f228c..0c0e05b67e 100644
--- a/core/DataTable/Filter/ColumnCallbackReplace.php
+++ b/core/DataTable/Filter/ColumnCallbackReplace.php
@@ -35,7 +35,13 @@ class Piwik_DataTable_Filter_ColumnCallbackReplace extends Piwik_DataTable_Filte
{
foreach($this->table->getRows() as $key => $row)
{
- $parameters = array($this->getElementToReplace($row, $this->columnToFilter));
+ // when a value is not defined, we set it to zero by default (rather than displaying '-')
+ $value = $this->getElementToReplace($row, $this->columnToFilter);
+ if($value === false)
+ {
+ $value = 0;
+ }
+ $parameters = array($value);
if(!is_null($this->functionParameters))
{
$parameters = array_merge($parameters, $this->functionParameters);
diff --git a/core/DataTable/Filter/ColumnDelete.php b/core/DataTable/Filter/ColumnDelete.php
new file mode 100644
index 0000000000..c300f11d6d
--- /dev/null
+++ b/core/DataTable/Filter/ColumnDelete.php
@@ -0,0 +1,36 @@
+<?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$
+ *
+ * @category Piwik
+ * @package Piwik
+ */
+
+/**
+ * Deletes a column from a datatable
+ *
+ * @package Piwik
+ * @subpackage Piwik_DataTable
+ */
+class Piwik_DataTable_Filter_ColumnDelete extends Piwik_DataTable_Filter
+{
+ private $columnToFilter;
+ private $functionToApply;
+
+ public function __construct( $table, $columnToDelete )
+ {
+ parent::__construct($table);
+ $this->columnToDelete = $columnToDelete;
+ $this->filter();
+ }
+
+ protected function filter()
+ {
+ $this->table->deleteColumn($this->columnToDelete);
+ }
+
+}
diff --git a/core/DataTable/Filter/ReplaceColumnNames.php b/core/DataTable/Filter/ReplaceColumnNames.php
index 8671f2f81a..3865cace7c 100644
--- a/core/DataTable/Filter/ReplaceColumnNames.php
+++ b/core/DataTable/Filter/ReplaceColumnNames.php
@@ -77,9 +77,10 @@ class Piwik_DataTable_Filter_ReplaceColumnNames extends Piwik_DataTable_Filter
$newColumns = array();
foreach($columns as $columnName => $columnValue)
{
- if(isset(Piwik_Archive::$mappingFromIdToName[$columnName]))
+ if(isset($this->mappingToApply[$columnName]))
{
- $columnName = Piwik_Archive::$mappingFromIdToName[$columnName];
+ $columnName = $this->mappingToApply[$columnName];
+
if($columnName == 'goals')
{
$newSubColumns = array();
diff --git a/core/DataTable/Filter/Sort.php b/core/DataTable/Filter/Sort.php
index eabf22415d..2b5dbff5ea 100644
--- a/core/DataTable/Filter/Sort.php
+++ b/core/DataTable/Filter/Sort.php
@@ -161,6 +161,10 @@ class Piwik_DataTable_Filter_Sort extends Piwik_DataTable_Filter
return;
}
$row = current($rows);
+ if($row === false)
+ {
+ return;
+ }
$this->columnToSort = $this->selectColumnToSort($row);
$value = $row->getColumn($this->columnToSort);
diff --git a/core/DataTable/Filter/UpdateColumnsWhenShowAllGoals.php b/core/DataTable/Filter/UpdateColumnsWhenShowAllGoals.php
index 47caecf80d..26e296e2e2 100644
--- a/core/DataTable/Filter/UpdateColumnsWhenShowAllGoals.php
+++ b/core/DataTable/Filter/UpdateColumnsWhenShowAllGoals.php
@@ -16,12 +16,22 @@
*/
class Piwik_DataTable_Filter_UpdateColumnsWhenShowAllGoals extends Piwik_DataTable_Filter
{
+ const GOALS_OVERVIEW = -1;
+ const GOALS_FULL_TABLE = 0;
+
protected $mappingIdToNameGoal;
- public function __construct( $table, $mappingToApply = null )
+ /**
+ * @param $table
+ * @param $enable Automatically set to true when filter_update_columns_when_show_all_goals is found in the API request
+ * @param $processOnlyIdGoal
+ * @return unknown_type
+ */
+ public function __construct( $table, $enable = true, $processOnlyIdGoal )
{
parent::__construct($table);
$this->mappingIdToNameGoal = Piwik_Archive::$mappingFromIdToNameGoal;
+ $this->processOnlyIdGoal = $processOnlyIdGoal;
$this->filter();
}
@@ -36,14 +46,13 @@ class Piwik_DataTable_Filter_UpdateColumnsWhenShowAllGoals extends Piwik_DataTab
$newColumns = array();
$nbVisits = 0;
- // visits could be undefined when there is a convertion but no visit
+ // visits could be undefined when there is a conversion but no visit
if(isset($currentColumns[Piwik_Archive::INDEX_NB_VISITS]))
{
$nbVisits = $currentColumns[Piwik_Archive::INDEX_NB_VISITS];
}
$newColumns['nb_visits'] = $nbVisits;
$newColumns['label'] = $currentColumns['label'];
-
if(isset($currentColumns[Piwik_Archive::INDEX_GOALS]))
{
$nbVisitsConverted = $revenue = 0;
@@ -61,6 +70,7 @@ class Piwik_DataTable_Filter_UpdateColumnsWhenShowAllGoals extends Piwik_DataTab
{
$conversionRate = round(100 * $nbVisitsConverted / $nbVisits, $roundingPrecision);
}
+ $newColumns['goals_conversion_rate'] = $conversionRate;
if($nbVisits == 0)
{
@@ -70,8 +80,16 @@ class Piwik_DataTable_Filter_UpdateColumnsWhenShowAllGoals extends Piwik_DataTab
{
$revenuePerVisit = round( $revenue / $nbVisits, $roundingPrecision );
}
+ $newColumns['revenue_per_visit'] = $revenuePerVisit;
foreach($currentColumns[Piwik_Archive::INDEX_GOALS] as $goalId => $columnValue)
{
+ if($this->processOnlyIdGoal > self::GOALS_FULL_TABLE
+ && $this->processOnlyIdGoal != $goalId)
+ {
+ continue;
+ }
+
+ // Goal Conversion rate
$name = 'goal_' . $goalId . '_conversion_rate';
if($nbVisits == 0)
{
@@ -84,12 +102,32 @@ class Piwik_DataTable_Filter_UpdateColumnsWhenShowAllGoals extends Piwik_DataTab
$newColumns[$name] = $value;
$expectedColumns[$name] = true;
+ // When the table is displayed by clicking on the flag icon, we only display the columns
+ // Visits, Conversions, Per goal conversion rate, Revenue
+ if($this->processOnlyIdGoal == self::GOALS_OVERVIEW)
+ {
+ continue;
+ }
+
+ // Goal Conversions
$name = 'goal_' . $goalId . '_nb_conversions';
$newColumns[$name] = $columnValue[Piwik_Archive::INDEX_GOAL_NB_CONVERSIONS];
$expectedColumns[$name] = true;
+
+ // Goal Revenue per visit
+ $name = 'goal_' . $goalId . '_revenue_per_visit';
+ if($nbVisits == 0)
+ {
+ $value = $invalidDivision;
+ }
+ else
+ {
+ $revenuePerVisit = round( $columnValue[Piwik_Archive::INDEX_GOAL_REVENUE] / $nbVisits, $roundingPrecision );
+ }
+ $newColumns[$name] = $revenuePerVisit;
+ $expectedColumns[$name] = true;
+
}
- $newColumns['revenue_per_visit'] = $revenuePerVisit;
- $newColumns['goals_conversion_rate'] = $conversionRate;
}
$row->setColumns($newColumns);
diff --git a/core/DataTable/Manager.php b/core/DataTable/Manager.php
index 8e3506187e..54976f014c 100644
--- a/core/DataTable/Manager.php
+++ b/core/DataTable/Manager.php
@@ -122,22 +122,22 @@ class Piwik_DataTable_Manager
*/
public function dumpAllTables()
{
- echo "<hr>Piwik_DataTable_Manager->dumpAllTables()<br>";
+ echo "<hr />Piwik_DataTable_Manager->dumpAllTables()<br />";
foreach($this->tables as $id => $table)
{
if(!($table instanceof Piwik_DataTable ))
{
- echo "Error table $id is not instance of datatable<br>";
+ echo "Error table $id is not instance of datatable<br />";
var_dump($table);
}
else
{
- echo "<hr>";
- echo "Table (index=$id) TableId = ". $table->getId() . "<br>";
+ echo "<hr />";
+ echo "Table (index=$id) TableId = ". $table->getId() . "<br />";
echo $table;
- echo "<br>";
+ echo "<br />";
}
}
- echo "<br>-- End Piwik_DataTable_Manager->dumpAllTables()<hr>";
+ echo "<br />-- End Piwik_DataTable_Manager->dumpAllTables()<hr />";
}
}
diff --git a/core/DataTable/Renderer.php b/core/DataTable/Renderer.php
index c9458f16a3..5f1a42671f 100644
--- a/core/DataTable/Renderer.php
+++ b/core/DataTable/Renderer.php
@@ -24,8 +24,13 @@
abstract class Piwik_DataTable_Renderer
{
protected $table;
+ protected $exception;
protected $renderSubTables = false;
+ public function __construct()
+ {
+ }
+
public function setRenderSubTables($enableRenderSubTable)
{
$this->renderSubTables = (bool)$enableRenderSubTable;
@@ -44,6 +49,13 @@ abstract class Piwik_DataTable_Renderer
abstract public function render();
/**
+ * Computes the exception output and returns the string/binary
+ *
+ * @return string
+ */
+ abstract public function renderException();
+
+ /**
* @see render()
* @return string
*/
@@ -67,6 +79,19 @@ abstract class Piwik_DataTable_Renderer
}
/**
+ * Set the Exception to be rendered
+ * @param Exception $exception to be rendered
+ */
+ public function setException($exception)
+ {
+ if(!($exception instanceof Exception))
+ {
+ throw new Exception("The exception renderer accepts only an Exception object.");
+ }
+ $this->exception = $exception;
+ }
+
+ /**
* Returns the DataTable associated to the output format $name
*
* @throws exception If the renderer is unknown
@@ -75,18 +100,25 @@ abstract class Piwik_DataTable_Renderer
static public function factory( $name )
{
$name = ucfirst(strtolower($name));
- $path = PIWIK_INCLUDE_PATH .'/core/DataTable/Renderer/'.$name.'.php';
$className = 'Piwik_DataTable_Renderer_' . $name;
- if( Piwik_Common::isValidFilename($name)
- && Zend_Loader::isReadable($path) )
- {
- require_once $path; // prefixed by PIWIK_INCLUDE_PATH
+ try {
+ Piwik_Loader::autoload($className);
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.");
+ } catch(Exception $e) {
+ $availableRenderers = 'xml, json, csv, tsv, html, php, original';
+ throw new Exception(Piwik_TranslateException('General_ExceptionInvalidRendererFormat', array($name, $availableRenderers)));
}
- }
-}
+ }
+
+ /**
+ * Returns $rawData after all applicable characters have been converted to HTML entities.
+ *
+ * @param String $rawData to be converted
+ * @return String
+ */
+ static protected function renderHtmlEntities( $rawData )
+ {
+ return htmlentities($rawData, ENT_COMPAT, "UTF-8");
+ }
+} \ No newline at end of file
diff --git a/core/DataTable/Renderer/Console.php b/core/DataTable/Renderer/Console.php
index 0929de6744..7d0fb5f001 100644
--- a/core/DataTable/Renderer/Console.php
+++ b/core/DataTable/Renderer/Console.php
@@ -25,6 +25,12 @@ class Piwik_DataTable_Renderer_Console extends Piwik_DataTable_Renderer
return $this->renderTable($this->table);
}
+ function renderException()
+ {
+ $exceptionMessage = self::renderHtmlEntities($this->exception->getMessage());
+ return 'Error: '.$exceptionMessage;
+ }
+
function setPrefixRow($str)
{
$this->prefixRows = $str;
@@ -32,25 +38,25 @@ class Piwik_DataTable_Renderer_Console extends Piwik_DataTable_Renderer
protected function renderDataTableArray(Piwik_DataTable_Array $tableArray, $prefix )
{
- $output = "Piwik_DataTable_Array<hr>";
+ $output = "Piwik_DataTable_Array<hr />";
$prefix = $prefix . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
foreach($tableArray->getArray() as $descTable => $table)
{
- $output .= $prefix . "<b>". $descTable. "</b><br>";
+ $output .= $prefix . "<b>". $descTable. "</b><br />";
$output .= $prefix . $this->renderTable($table, $prefix . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;');
- $output .= "<hr>";
+ $output .= "<hr />";
}
- $output .= "Metadata<br>";
+ $output .= "Metadata<br />";
foreach($tableArray->metadata as $id => $metadata)
{
- $output .= "<br>";
- $output .= $prefix . " <b>$id</b> <br>";
+ $output .= "<br />";
+ $output .= $prefix . " <b>$id</b><br />";
foreach($metadata as $name => $value)
{
$output .= $prefix . $prefix . "$name => $value";
}
}
- $output .= "<hr>";
+ $output .= "<hr />";
return $output;
}
@@ -63,7 +69,7 @@ class Piwik_DataTable_Renderer_Console extends Piwik_DataTable_Renderer
if($table->getRowsCount() == 0)
{
- return "Empty table <br>\n";
+ return "Empty table<br />\n";
}
static $depth=0;
@@ -103,7 +109,7 @@ class Piwik_DataTable_Renderer_Console extends Piwik_DataTable_Renderer
$output.= str_repeat($this->prefixRows, $depth)
. "- $i [".$columns."] [".$metadata."] [idsubtable = "
- . $row->getIdSubDataTable()."]<br>\n";
+ . $row->getIdSubDataTable()."]<br />\n";
if($row->getIdSubDataTable() !== null)
{
@@ -116,7 +122,7 @@ class Piwik_DataTable_Renderer_Console extends Piwik_DataTable_Renderer
$prefix . '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'
);
} catch(Exception $e) {
- $output.= "-- Sub DataTable not loaded<br>\n";
+ $output.= "-- Sub DataTable not loaded<br />\n";
}
$depth--;
}
diff --git a/core/DataTable/Renderer/Csv.php b/core/DataTable/Renderer/Csv.php
index e756b518e6..3ff46a3dd4 100644
--- a/core/DataTable/Renderer/Csv.php
+++ b/core/DataTable/Renderer/Csv.php
@@ -31,7 +31,7 @@ class Piwik_DataTable_Renderer_Csv extends Piwik_DataTable_Renderer
*
* @var string
*/
- public $separator = ',';
+ public $separator = ",";
/**
* Line end
@@ -61,11 +61,27 @@ class Piwik_DataTable_Renderer_Csv extends Piwik_DataTable_Renderer
*/
public $exportIdSubtable = true;
- function render()
+ public function render()
{
return $this->output($this->renderTable($this->table));
}
+ function renderException()
+ {
+ $exceptionMessage = self::renderHtmlEntities($this->exception->getMessage());
+ return 'Error: '.$exceptionMessage;
+ }
+
+ public function setConvertToUnicode($bool)
+ {
+ $this->convertToUnicode = $bool;
+ }
+
+ public function setSeparator($separator)
+ {
+ $this->separator = $separator;
+ }
+
protected function renderTable($table)
{
if($table instanceof Piwik_DataTable_Array)
@@ -234,7 +250,7 @@ class Piwik_DataTable_Renderer_Csv extends Piwik_DataTable_Renderer
$value = 0;
}
if(strpos($value, '"') !== false
- || strpos($value, ',') !== false )
+ || strpos($value, $this->separator) !== false )
{
$value = '"'. str_replace('"', '""', $value). '"';
}
@@ -248,7 +264,7 @@ class Piwik_DataTable_Renderer_Csv extends Piwik_DataTable_Renderer
return 'No data available';
}
// silent fail otherwise unit tests fail
- @header("Content-type: application/vnd.ms-excel");
+ @header("Content-Type: application/vnd.ms-excel");
@header("Content-Disposition: attachment; filename=piwik-report-export.csv");
if($this->convertToUnicode
&& function_exists('mb_convert_encoding'))
diff --git a/core/DataTable/Renderer/Html.php b/core/DataTable/Renderer/Html.php
index 62739fd035..2ebc36c0ce 100644
--- a/core/DataTable/Renderer/Html.php
+++ b/core/DataTable/Renderer/Html.php
@@ -38,6 +38,12 @@ class Piwik_DataTable_Renderer_Html extends Piwik_DataTable_Renderer
return $this->renderTable($this->table);
}
+ function renderException()
+ {
+ $exceptionMessage = self::renderHtmlEntities($this->exception->getMessage());
+ return nl2br($exceptionMessage);
+ }
+
protected function renderTable($table)
{
if($table instanceof Piwik_DataTable_Array)
@@ -96,7 +102,7 @@ class Piwik_DataTable_Renderer_Html extends Piwik_DataTable_Renderer
if(count($metadata) != 0)
{
$someMetadata = true;
- $metadata = implode("<br>", $metadata);
+ $metadata = implode("<br />", $metadata);
$this->tableStructure[$i]['_metadata'] = $metadata;
}
diff --git a/core/DataTable/Renderer/Json.php b/core/DataTable/Renderer/Json.php
index 5febed6a88..0ce45db6ba 100644
--- a/core/DataTable/Renderer/Json.php
+++ b/core/DataTable/Renderer/Json.php
@@ -21,9 +21,21 @@ class Piwik_DataTable_Renderer_Json extends Piwik_DataTable_Renderer
{
public function render()
{
+ Piwik_DataTable_Renderer_Json::renderHeader();
return $this->renderTable($this->table);
}
+ function renderException()
+ {
+ Piwik_DataTable_Renderer_Json::renderHeader();
+
+ $exceptionMessage = self::renderHtmlEntities($this->exception->getMessage());
+ $exceptionMessage = str_replace("\n", "", $exceptionMessage);
+ $exceptionMessage = '{"result":"error", "message":"'.$exceptionMessage.'"}';
+
+ return $this->jsonpWrap($exceptionMessage);
+ }
+
protected function renderTable($table)
{
$renderer = new Piwik_DataTable_Renderer_Php();
@@ -38,6 +50,11 @@ class Piwik_DataTable_Renderer_Json extends Piwik_DataTable_Renderer
}
$str = json_encode($array);
+ return $this->jsonpWrap($str);
+ }
+
+ protected function jsonpWrap($str)
+ {
if(($jsonCallback = Piwik_Common::getRequestVar('jsoncallback', false)) !== false)
{
if(preg_match('/^[0-9a-zA-Z]*$/', $jsonCallback) > 0)
@@ -45,6 +62,12 @@ class Piwik_DataTable_Renderer_Json extends Piwik_DataTable_Renderer
$str = $jsonCallback . "(" . $str . ")";
}
}
+
return $str;
}
+
+ static private function renderHeader ()
+ {
+ @header( "Content-Type: application/json" );
+ }
}
diff --git a/core/DataTable/Renderer/Php.php b/core/DataTable/Renderer/Php.php
index b7d5f216da..18f24452e0 100644
--- a/core/DataTable/Renderer/Php.php
+++ b/core/DataTable/Renderer/Php.php
@@ -64,6 +64,20 @@ class Piwik_DataTable_Renderer_Php extends Piwik_DataTable_Renderer
return $toReturn;
}
+ function renderException()
+ {
+ $exceptionMessage = self::renderHtmlEntities($this->exception->getMessage());
+
+ $return = array('result' => 'error', 'message' => $exceptionMessage);
+
+ if($this->serialize)
+ {
+ $return = serialize($return);
+ }
+
+ return $return;
+ }
+
/**
* Produces a flat php array from the DataTable, putting "columns" and "metadata" on the same level.
*
diff --git a/core/DataTable/Renderer/Rss.php b/core/DataTable/Renderer/Rss.php
index 4dbfa58b7c..9c07515678 100644
--- a/core/DataTable/Renderer/Rss.php
+++ b/core/DataTable/Renderer/Rss.php
@@ -25,6 +25,12 @@ class Piwik_DataTable_Renderer_Rss extends Piwik_DataTable_Renderer
return $this->renderTable($this->table);
}
+ function renderException()
+ {
+ $exceptionMessage = self::renderHtmlEntities($this->exception->getMessage());
+ return 'Error: '.$exceptionMessage;
+ }
+
protected function renderTable($table)
{
if(!($table instanceof Piwik_DataTable_Array)
@@ -47,8 +53,9 @@ class Piwik_DataTable_Renderer_Rss extends Piwik_DataTable_Renderer
$site = $table->metadata[$date]['site'];
$pudDate = date('r', $timestamp);
- $dateUrl = date('Y-m-d', $timestamp);
- $thisPiwikUrl = htmlentities($piwikUrl . "&date=$dateUrl");
+
+ $dateInSiteTimezone = Piwik_Date::factory($timestamp)->setTimezone($site->getTimezone())->toString('Y-m-d');
+ $thisPiwikUrl = htmlentities($piwikUrl . "&date=$dateInSiteTimezone");
$siteName = $site->getName();
$title = $siteName . " on ". $date;
@@ -98,7 +105,7 @@ class Piwik_DataTable_Renderer_Rss extends Piwik_DataTable_Renderer
{
if($table->getRowsCount() == 0)
{
- return "<b><i>Empty table</i></b> <br>\n";
+ return "<b><i>Empty table</i></b><br />\n";
}
$i = 1;
diff --git a/core/DataTable/Renderer/Tsv.php b/core/DataTable/Renderer/Tsv.php
new file mode 100644
index 0000000000..1af22ca122
--- /dev/null
+++ b/core/DataTable/Renderer/Tsv.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$
+ *
+ * @category Piwik
+ * @package Piwik
+ */
+
+/**
+ * TSV export
+ *
+ * Excel doesn't import CSV properly, it expects TAB separated values by default.
+ * TSV is therefore the 'CSV' that is Excel compatible
+ *
+ * @package Piwik
+ * @subpackage Piwik_DataTable
+ */
+class Piwik_DataTable_Renderer_Tsv extends Piwik_DataTable_Renderer_Csv
+{
+ function __construct()
+ {
+ parent::__construct();
+ $this->setSeparator("\t");
+ }
+
+ function render()
+ {
+ return parent::render();
+ }
+}
diff --git a/core/DataTable/Renderer/Xml.php b/core/DataTable/Renderer/Xml.php
index 9eb2c30bbf..828b890ab7 100644
--- a/core/DataTable/Renderer/Xml.php
+++ b/core/DataTable/Renderer/Xml.php
@@ -27,6 +27,20 @@ class Piwik_DataTable_Renderer_Xml extends Piwik_DataTable_Renderer
return $this->renderTable($this->table);
}
+ function renderException()
+ {
+ $exceptionMessage = self::renderHtmlEntities($this->exception->getMessage());
+
+ @header("Content-Type: text/xml;charset=utf-8");
+ $return =
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" .
+ "<result>\n".
+ "\t<error message=\"".$exceptionMessage."\" />\n".
+ "</result>";
+
+ return $return;
+ }
+
protected function getArrayFromDataTable($table)
{
$renderer = new Piwik_DataTable_Renderer_Php();
diff --git a/core/DataTable/Row.php b/core/DataTable/Row.php
index cb8a20959b..1a07bd80a4 100644
--- a/core/DataTable/Row.php
+++ b/core/DataTable/Row.php
@@ -122,7 +122,7 @@ class Piwik_DataTable_Row
$metadata[] = "'$name' => $value";
}
$metadata = implode(", ", $metadata);
- $output = "# [".$columns."] [".$metadata."] [idsubtable = " . $this->getIdSubDataTable()."]<br>\n";
+ $output = "# [".$columns."] [".$metadata."] [idsubtable = " . $this->getIdSubDataTable()."]<br />\n";
return $output;
}
diff --git a/core/Date.php b/core/Date.php
index f6aefce606..87fd5d55a0 100644
--- a/core/Date.php
+++ b/core/Date.php
@@ -18,66 +18,192 @@
class Piwik_Date
{
/**
+ * Builds a Piwik_Date object
+ *
+ * @param int timestamp
+ */
+ protected function __construct( $timestamp, $timezone = 'UTC')
+ {
+ if(!is_int( $timestamp ))
+ {
+ throw new Exception("Piwik_Date is expecting a unix timestamp");
+ }
+ $this->timezone = $timezone;
+ $this->timestamp = $timestamp ;
+ }
+
+
+ /**
* Returns a Piwik_Date objects.
- * Accepts strings 'today' 'yesterday' or any YYYY-MM-DD or timestamp
*
- * @param string $strDate
- * @return Piwik_Date
+ * @param string $strDate 'today' 'yesterday' or any YYYY-MM-DD or timestamp
+ * @param string $timezone if specified, the dateString will be relative to this $timezone.
+ * For example, today in UTC+12 will be a timestamp in the future for UTC.
+ * This is different from using ->setTimezone()
+ * @return Piwik_Date
*/
- static public function factory($dateString)
+ static public function factory($dateString, $timezone = null)
{
- if($dateString == 'today')
+ if($dateString == 'now')
+ {
+ $date = self::now();
+ }
+ elseif($dateString == 'today')
{
- return self::today();
+ $date = self::today();
}
- if($dateString == 'yesterday')
+ elseif($dateString == 'yesterday')
{
- return self::yesterday();
+ $date = self::yesterday();
}
- if (!is_int($dateString)
+ elseif($dateString == 'yesterdaySameTime')
+ {
+ $date = self::yesterdaySameTime();
+ }
+ elseif (!is_int($dateString)
&& ($dateString = strtotime($dateString)) === false)
{
- throw new Exception("Date format must be: YYYY-MM-DD, or 'today' or 'yesterday' or any keyword supported by the strtotime function (see http://php.net/strtotime for more information)");
+ throw new Exception(Piwik_TranslateException('General_ExceptionInvalidDateFormat', array("YYYY-MM-DD, or 'today' or 'yesterday'", "strtotime", "http://php.net/strtotime")));
+ }
+ else
+ {
+ $date = new Piwik_Date($dateString);
+ }
+ if(is_null($timezone))
+ {
+ return $date;
+ }
+
+ // manually adjust for UTC timezones
+ $utcOffset = self::extractUtcOffset($timezone);
+ if($utcOffset !== false)
+ {
+ return $date->addHour($utcOffset);
}
- return new Piwik_Date($dateString);
+
+ date_default_timezone_set($timezone);
+ $datetime = $date->getDatetime();
+ date_default_timezone_set('UTC');
+
+ $date = Piwik_Date::factory(strtotime($datetime));
+
+ return $date;
}
+ /*
+ * The stored timestamp is always UTC based.
+ * The returned timestamp via getTimestamp() will have the conversion applied
+ */
protected $timestamp = null;
+
+ /*
+ * Timezone the current date object is set to.
+ * Timezone will only affect the returned timestamp via getTimestamp()
+ */
+ protected $timezone = 'UTC';
+
+ const DATE_TIME_FORMAT = 'Y-m-d H:i:s';
+
+ /**
+ * Returns the datetime start in UTC
+ *
+ * @return string
+ */
+ function getDateStartUTC()
+ {
+ $dateStartUTC = date('Y-m-d', $this->timestamp);
+ $date = Piwik_Date::factory($dateStartUTC)->setTimezone($this->timezone);
+ return $date->toString(self::DATE_TIME_FORMAT);
+ }
+ /**
+ * Returns the datetime of the current timestamp
+ *
+ * @return string
+ */
+ function getDatetime()
+ {
+ return $this->toString(self::DATE_TIME_FORMAT);
+ }
+
+ /**
+ * Returns the datetime end in UTC
+ *
+ * @return string
+ */
+ function getDateEndUTC()
+ {
+ $dateEndUTC = date('Y-m-d 23:59:59', $this->timestamp);
+ $date = Piwik_Date::factory($dateEndUTC)->setTimezone($this->timezone);
+ return $date->toString(self::DATE_TIME_FORMAT);
+ }
+
/**
- * Returns the unix timestamp of the date
- *
- * @return int
+ * Returns a new date object, copy of $this, with the timezone set
+ * This timezone is used to offset the UTC timestamp returned by @see getTimestamp()
+ * Doesn't modify $this
+ *
+ * @param string $timezone 'UTC', 'Europe/London', ...
*/
- public function getTimestamp()
+ public function setTimezone($timezone)
{
- return $this->timestamp;
+ return new Piwik_Date($this->timestamp, $timezone);
}
/**
- * Builds a Piwik_Date object
+ * Helper function that returns the offset in the timezone string 'UTC+14'
+ * Returns false if the timezone is not UTC+X or UTC-X
*
- * @param int timestamp
+ * @param $timezone
+ * @return int or false
*/
- protected function __construct( $date )
+ static protected function extractUtcOffset($timezone)
{
- if(!is_int( $date ))
+ if($timezone == 'UTC')
{
- throw new Exception("Piwik_Date is expecting a unix timestamp");
+ return 0;
+ }
+ $start = substr($timezone, 0, 4);
+ if($start != 'UTC-'
+ && $start != 'UTC+')
+ {
+ return false;
}
- $this->timestamp = $date ;
+ $offset = (float)substr($timezone, 4);
+ if($start == 'UTC-') {
+ $offset = -$offset;
+ }
+ return $offset;
}
/**
- * 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
+ * Returns the unix timestamp of the date in UTC,
+ * converted from the date timezone
+ *
+ * @return int
*/
- public function setTime($time)
+ public function getTimestamp()
{
- return new Piwik_Date( strtotime( $this->get("j F Y") . " $time"));
+ $utcOffset = self::extractUtcOffset($this->timezone);
+ if($utcOffset !== false) {
+ return (int)($this->timestamp - $utcOffset * 3600);
+ }
+ // @fixme
+ // The following code seems clunky - I thought the DateTime php class would allow to return timestamps
+ // after applying the timezone offset. Instead, the underlying timestamp is not changed.
+ // I decided to get the date without the timezone information, and create the timestamp from the truncated string.
+ // Unit tests pass (@see Date.test.php) but I'm pretty sure this is not the right way to do it
+ date_default_timezone_set($this->timezone);
+ $dtzone = timezone_open('UTC');
+ $time = date('r', $this->timestamp);
+ $dtime = date_create($time);
+ date_timezone_set($dtime, $dtzone);
+ $dateWithTimezone = date_format($dtime, 'r');
+ $dateWithoutTimezone = substr($dateWithTimezone, 0, -6);
+ $timestamp = strtotime($dateWithoutTimezone);
+ date_default_timezone_set('UTC');
+
+ return (int)$timestamp;
}
/**
@@ -113,7 +239,7 @@ class Piwik_Date
{
return date($part, $this->getTimestamp());
}
-
+
/**
* @see toString()
*
@@ -125,6 +251,114 @@ class Piwik_Date
}
/**
+ * 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->toString('Y-m-d') === Piwik_Date::factory('today', $this->timezone)->toString('Y-m-d');
+ }
+
+ /**
+ * Returns a date object set to now (same as today, except that the time is also set)
+ *
+ * @return Piwik_Date
+ */
+ static public function now()
+ {
+ return new Piwik_date(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"));
+ }
+
+ /**
+ * Returns a date object set to yesterday same time of day
+ *
+ * @return Piwik_Date
+ */
+ static public function yesterdaySameTime()
+ {
+ return new Piwik_Date(strtotime("yesterday ".date('H:i:s')));
+ }
+
+ /**
+ * 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
+ */
+ public function setTime($time)
+ {
+ return new Piwik_Date( strtotime( date("Y-m-d", $this->timestamp) . " $time"), $this->timezone);
+ }
+
+ /**
* Sets a new day
* Returned is the new date object
* Doesn't modify $this
@@ -134,7 +368,7 @@ class Piwik_Date
*/
public function setDay( $day )
{
- $ts = $this->getTimestamp();
+ $ts = $this->timestamp;
$result = mktime(
date('H', $ts),
date('i', $ts),
@@ -143,7 +377,7 @@ class Piwik_Date
1,
date('Y', $ts)
);
- return new Piwik_Date( $result );
+ return new Piwik_Date( $result, $this->timezone );
}
/**
@@ -156,7 +390,7 @@ class Piwik_Date
*/
public function setYear( $year )
{
- $ts = $this->getTimestamp();
+ $ts = $this->timestamp;
$result = mktime(
date('H', $ts),
date('i', $ts),
@@ -165,11 +399,9 @@ class Piwik_Date
date('j', $ts),
$year
);
- return new Piwik_Date( $result );
+ return new Piwik_Date( $result, $this->timezone );
}
-
-
/**
* Subtracts days from the existing date object and returns a new Piwik_Date object
* Returned is the new date object
@@ -183,8 +415,8 @@ class Piwik_Date
{
return clone $this;
}
- $ts = strtotime("-$n day", $this->getTimestamp());
- return new Piwik_Date( $ts );
+ $ts = strtotime("-$n day", $this->timestamp);
+ return new Piwik_Date( $ts, $this->timezone );
}
/**
@@ -200,7 +432,7 @@ class Piwik_Date
{
return clone $this;
}
- $ts = $this->getTimestamp();
+ $ts = $this->timestamp;
$result = mktime(
date('H', $ts),
date('i', $ts),
@@ -209,23 +441,8 @@ class Piwik_Date
1, // we set the day to 1
date('Y', $ts)
);
- return new Piwik_Date( $result );
+ return new Piwik_Date( $result, $this->timezone );
}
-
- /**
- * 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 date string, given a template.
@@ -247,12 +464,12 @@ class Piwik_Date
"%longDay%" => Piwik_Translate('General_LongDay_'.$dayOfWeek),
"%longYear%" => $this->toString('Y'),
"%shortYear%" => $this->toString('y'),
- "%time%" => $this->toString('H:i:s T')
+ "%time%" => $this->toString('H:i:s')
);
$out = str_replace(array_keys($patternToValue), array_values($patternToValue), $template);
return $out;
}
-
+
/**
* Adds days to the existing date object.
* Returned is the new date object
@@ -263,82 +480,44 @@ class Piwik_Date
*/
public function addDay( $n )
{
- $ts = strtotime("+$n day", $this->getTimestamp());
- return new Piwik_Date( $ts );
+ $ts = strtotime("+$n day", $this->timestamp);
+ return new Piwik_Date( $ts, $this->timezone );
}
-
- /**
- * 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
+ * Adds hours to the existing date object.
+ * Returned is the new date object
+ * Doesn't modify $this
+ *
+ * @param int Number of hours to add
+ * @return Piwik_Date new date
*/
- function compareMonth( Piwik_Date $date )
+ public function addHour( $n )
{
- $currentMonth = date('n', $this->getTimestamp());
- $toCompareMonth = date('n', $date->getTimestamp());
- if( $currentMonth == $toCompareMonth)
+ $minutes = 0;
+ if($n != round($n))
{
- return 0;
+ $minutes = abs($n - floor($n)) * 60;
+ $n = floor($n);
}
- if( $currentMonth < $toCompareMonth)
+ if($n > 0 )
{
- return -1;
+ $n = '+'.$n;
}
- return 1;
+ $ts = strtotime("$n hour $minutes minutes", $this->timestamp);
+ return new Piwik_Date( $ts, $this->timezone );
}
-
- /**
- * 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()
+ * Substract hour to the existing date object.
+ * Returned is the new date object
+ * Doesn't modify $this
+ *
+ * @param int Number of hours to substract
+ * @return Piwik_Date new date
+ */
+ public function subHour( $n )
{
- return new Piwik_Date(strtotime("yesterday"));
+ return $this->addHour(-$n);
}
}
diff --git a/core/Db.php b/core/Db/Adapter.php
index a8efb337a8..7a3b4fccf6 100644
--- a/core/Db.php
+++ b/core/Db/Adapter.php
@@ -1,43 +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$
- *
+ *
* @category Piwik
* @package Piwik
*/
/**
* @package Piwik
+ * @subpackage Piwik_Db
*/
-class Piwik_Db
+class Piwik_Db_Adapter
{
/**
- * Get adapter class name
+ * Create adapter
*
- * @param string $adapterName
- * @return string
+ * @param string $adapterName database adapter name
+ * @param array $dbInfos database connection info
+ * @return mixed (Piwik_Db_Adapter_Mysqli, Piwik_Db_Adapter_Pdo_Mysql, etc)
*/
- private static function getAdapterClassName($adapterName)
+ public static function factory($adapterName, & $dbInfos)
{
- return 'Piwik_Db_' . str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($adapterName))));
+ if($dbInfos['port'][0] == '/')
+ {
+ $dbInfos['unix_socket'] = $dbInfos['port'];
+ unset($dbInfos['host']);
+ unset($dbInfos['port']);
+ }
+
+ // not used by Zend Framework
+ unset($dbInfos['tables_prefix']);
+ unset($dbInfos['adapter']);
+ unset($dbInfos['schema']);
+
+ $className = self::getAdapterClassName($adapterName);
+ $adapter = new $className($dbInfos);
+ $adapter->getConnection();
+
+ Zend_Db_Table::setDefaultAdapter($adapter);
+
+ // we don't want the connection information to appear in the logs
+ $adapter->resetConfig();
+
+ return $adapter;
}
/**
- * Create adapter
+ * Get adapter class name
*
* @param string $adapterName
- * @oaran array $config
- * @return mixed (Piwik_Db_Mysqli, Piwik_Db_Pdo_Mysql, etc)
+ * @return string
*/
- public static function factory($adapterName, $config)
+ private static function getAdapterClassName($adapterName)
{
- $className = self::getAdapterClassName($adapterName);
- $adapter = new $className($config);
- return $adapter;
+ return 'Piwik_Db_Adapter_' . str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($adapterName))));
}
/**
@@ -59,24 +79,41 @@ class Piwik_Db
*/
public static function getAdapters()
{
- $path = PIWIK_INCLUDE_PATH . '/core/Db';
- $pathLength = strlen($path) + 1;
- $adapters = Piwik::globr($path, '*.php');
- $adapterNames = array();
- foreach($adapters as $adapter)
+ static $adapterNames = array(
+ // currently supported by Piwik
+ 'Pdo_Mysql',
+ 'Mysqli',
+
+ // other adapters supported by Zend_Db
+// 'Pdo_Pgsql',
+// 'Pdo_Mssql',
+// 'Sqlsrv',
+// 'Pdo_Ibm',
+// 'Db2',
+// 'Pdo_Oci',
+// 'Oracle',
+ );
+
+ $adapters = array();
+
+ foreach($adapterNames as $adapterName)
{
- $adapterName = str_replace('/', '_', substr($adapter, $pathLength, -strlen('.php')));
- $className = 'Piwik_Db_'.$adapterName;
+ $className = 'Piwik_Db_Adapter_'.$adapterName;
if(call_user_func(array($className, 'isEnabled')))
{
- $adapterNames[strtoupper($adapterName)] = call_user_func(array($className, 'getDefaultPort'));
+ $adapters[strtoupper($adapterName)] = call_user_func(array($className, 'getDefaultPort'));
}
}
- return $adapterNames;
+
+ return $adapters;
}
}
-interface Piwik_Db_iAdapter
+/**
+ * @package Piwik
+ * @subpackage Piwik_Db
+ */
+interface Piwik_Db_Adapter_Interface
{
/**
* Reset the configuration variables in this adapter.
diff --git a/core/Db/Mysqli.php b/core/Db/Adapter/Mysqli.php
index 6f62f9f546..37bb46540f 100644
--- a/core/Db/Mysqli.php
+++ b/core/Db/Adapter/Mysqli.php
@@ -12,8 +12,9 @@
/**
* @package Piwik
+ * @subpackage Piwik_Db
*/
-class Piwik_Db_Mysqli extends Zend_Db_Adapter_Mysqli implements Piwik_Db_iAdapter
+class Piwik_Db_Adapter_Mysqli extends Zend_Db_Adapter_Mysqli implements Piwik_Db_Adapter_Interface
{
public function __construct($config)
{
@@ -43,15 +44,29 @@ class Piwik_Db_Mysqli extends Zend_Db_Adapter_Mysqli implements Piwik_Db_iAdapte
*/
public function checkServerVersion()
{
- $databaseVersion = $this->getServerVersion();
+ $serverVersion = $this->getServerVersion();
$requiredVersion = Zend_Registry::get('config')->General->minimum_mysql_version;
- if(version_compare($databaseVersion, $requiredVersion) === -1)
+ if(version_compare($serverVersion, $requiredVersion) === -1)
{
- throw new Exception(Piwik_TranslateException('General_ExceptionDatabaseVersion', array('MySQL', $databaseVersion, $requiredVersion)));
+ throw new Exception(Piwik_TranslateException('General_ExceptionDatabaseVersion', array('MySQL', $serverVersion, $requiredVersion)));
}
}
/**
+ * Check client version compatibility against database server
+ */
+ public function checkClientVersion()
+ {
+ $serverVersion = $this->getServerVersion();
+ $clientVersion = $this->getClientVersion();
+ if(version_compare($serverVersion, '5') >= 0
+ && version_compare($clientVersion, '5') < 0)
+ {
+ throw new Exception(Piwik_TranslateException('General_ExceptionIncompatibleClientServerVersions', array('MySQL', $clientVersion, $serverVersion)));
+ }
+ }
+
+ /**
* Returns true if this adapter's required extensions are enabled
*
* @return bool
@@ -59,7 +74,7 @@ class Piwik_Db_Mysqli extends Zend_Db_Adapter_Mysqli implements Piwik_Db_iAdapte
public static function isEnabled()
{
$extensions = @get_loaded_extensions();
- return in_array('mysqli', $extensions) && function_exists('mysqli_set_charset');
+ return in_array('mysqli', $extensions);
}
/**
@@ -81,6 +96,15 @@ class Piwik_Db_Mysqli extends Zend_Db_Adapter_Mysqli implements Piwik_Db_iAdapte
*/
public function isErrNo($e, $errno)
{
+ if(is_null($this->_connection))
+ {
+ if(preg_match('/(?:\[|\s)([0-9]{4})(?:\]|\s)/', $e->getMessage(), $match))
+ {
+ return $match[1] == $errno;
+ }
+ return mysqli_connect_errno() == $errno;
+ }
+
return mysqli_errno($this->_connection) == $errno;
}
@@ -114,4 +138,19 @@ class Piwik_Db_Mysqli extends Zend_Db_Adapter_Mysqli implements Piwik_Db_iAdapte
$charset = mysqli_character_set_name($this->_connection);
return $charset === 'utf8';
}
+
+ /**
+ * Get client version
+ *
+ * @return string
+ */
+ public function getClientVersion()
+ {
+ $this->_connect();
+ $version = $this->_connection->server_version;
+ $major = (int) ($version / 10000);
+ $minor = (int) ($version % 10000 / 100);
+ $revision = (int) ($version % 100);
+ return $major . '.' . $minor . '.' . $revision;
+ }
}
diff --git a/core/Db/Adapter/Pdo/Mssql.php b/core/Db/Adapter/Pdo/Mssql.php
new file mode 100644
index 0000000000..7246c3838e
--- /dev/null
+++ b/core/Db/Adapter/Pdo/Mssql.php
@@ -0,0 +1,253 @@
+<?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$
+ *
+ * @category Piwik
+ * @package Piwik
+ */
+
+/**
+ * @package Piwik
+ * @subpackage Piwik_Db
+ */
+class Piwik_Db_Pdo_Mssql extends Zend_Db_Adapter_Pdo_Mssql implements Piwik_Db_Adapter_Interface
+{
+ /**
+ * Returns connection handle
+ *
+ * @return resource
+ */
+ public function getConnection()
+ {
+ // if we already have a PDO object, no need to re-connect.
+ if ($this->_connection)
+ {
+ return $this->_connection;
+ }
+
+ $this->_pdoType = "sqlsrv";
+ // get the dsn first, because some adapters alter the $_pdoType
+ //$dsn = $this->_dsn();
+
+ // check for PDO extension
+ if (!extension_loaded('pdo'))
+ {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception('The PDO extension is required for this adapter but the extension is not loaded');
+ }
+
+ // check the PDO driver is available
+ if (!in_array($this->_pdoType, PDO::getAvailableDrivers()))
+ {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception('The ' . $this->_pdoType . ' driver is not currently installed');
+ }
+
+ // create PDO connection
+ $q = $this->_profiler->queryStart('connect', Zend_Db_Profiler::CONNECT);
+
+ // add the persistence flag if we find it in our config array
+ if (isset($this->_config['persistent']) && ($this->_config['persistent'] == true))
+ {
+ $this->_config['driver_options'][PDO::ATTR_PERSISTENT] = true;
+ }
+
+ try {
+ $serverName = $this->_config["host"];
+ $database = $this->_config["dbname"];
+ if(is_null($database))
+ {
+ $database = 'master';
+ }
+ $uid = $this->_config['username'];
+ $pwd = $this->_config['password'];
+ if($this->_config["port"] != "")
+ {
+ $serverName = $serverName.",".$this->_config["port"];
+ }
+
+ $this->_connection = new PDO( "sqlsrv:$serverName", $uid, $pwd, array( 'Database' => $database ));
+
+
+ if( $this->_connection === false )
+ {
+ die( self::FormatErrors( sqlsrv_errors() ) );
+ }
+
+ /*
+ $this->_connection = new PDO(
+ $dsn,
+ $this->_config['username'],
+ $this->_config['password'],
+ $this->_config['driver_options']
+ );
+ */
+
+ $this->_profiler->queryEnd($q);
+
+ // set the PDO connection to perform case-folding on array keys, or not
+ $this->_connection->setAttribute(PDO::ATTR_CASE, $this->_caseFolding);
+ $this->_connection->setAttribute(PDO::SQLSRV_ENCODING_UTF8, true);
+
+
+ // always use exceptions.
+ $this->_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+ return $this->_connection;
+ } catch (PDOException $e) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Reset the configuration variables in this adapter.
+ */
+ public function resetConfig()
+ {
+ $this->_config = array();
+ }
+
+ /**
+ * Return default port.
+ *
+ * @return int
+ */
+ public static function getDefaultPort()
+ {
+ return 1433;
+ }
+
+ /**
+ * Check MSSQL version
+ */
+ public function checkServerVersion()
+ {
+ $serverVersion = $this->getServerVersion();
+ $requiredVersion = Zend_Registry::get('config')->General->minimum_mssql_version;
+ if(version_compare($serverVersion, $requiredVersion) === -1)
+ {
+ throw new Exception(Piwik_TranslateException('General_ExceptionDatabaseVersion', array('MSSQL', $serverVersion, $requiredVersion)));
+ }
+
+ }
+
+ public function getServerVersion()
+ {
+ try
+ {
+ $stmt = $this->query("SELECT CAST(SERVERPROPERTY('productversion') as VARCHAR) as productversion");
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+ if (count($result))
+ {
+ return $result[0][0];
+ }
+ }
+ catch (PDOException $e)
+ {
+ }
+
+ return null;
+ }
+
+ /**
+ * Check client version compatibility against database server
+ */
+ public function checkClientVersion()
+ {
+ $serverVersion = $this->getServerVersion();
+ $clientVersion = $this->getClientVersion();
+ if(version_compare($serverVersion, '10') >= 0
+ && version_compare($clientVersion, '10') < 0)
+ {
+ throw new Exception(Piwik_TranslateException('General_ExceptionIncompatibleClientServerVersions', array('MSSQL', $clientVersion, $serverVersion)));
+ }
+ }
+
+ /**
+ * Returns true if this adapter's required extensions are enabled
+ *
+ * @return bool
+ */
+ public static function isEnabled()
+ {
+ $extensions = @get_loaded_extensions();
+ return in_array('PDO', $extensions) && in_array('pdo_sqlsrv', $extensions);
+ }
+
+ /**
+ * Returns true if this adapter supports blobs as fields
+ *
+ * @return bool
+ */
+ public function hasBlobDataType()
+ {
+ return true;
+ }
+
+ /**
+ * Test error number
+ *
+ * @param Exception $e
+ * @param string $errno
+ * @return bool
+ */
+ public function isErrNo($e, $errno)
+ {
+ if(preg_match('/(?:\[|\s)([0-9]{4})(?:\]|\s)/', $e->getMessage(), $match))
+ {
+ return $match[1] == $errno;
+ }
+ return false;
+ }
+
+ /**
+ * Is the connection character set equal to utf8?
+ *
+ * @return bool
+ */
+ public function isConnectionUTF8()
+ {
+ //check the getconnection, it's specified on the connection string.
+ return true;
+ }
+
+ /**
+ * Retrieve client version in PHP style
+ *
+ * @return string
+ */
+ public function getClientVersion()
+ {
+ $this->_connect();
+ try
+ {
+ $version = $this->_connection->getAttribute(PDO::ATTR_CLIENT_VERSION);
+ $requiredVersion = Zend_Registry::get('config')->General->minimum_mssql_client_version;
+ if(version_compare($version['DriverVer'], $requiredVersion) === -1)
+ {
+ throw new Exception(Piwik_TranslateException('General_ExceptionDatabaseVersion', array('MSSQL', $serverVersion, $requiredVersion)));
+ }
+ else
+ {
+ return $version['DriverVer'];
+ }
+ }
+ catch (PDOException $e)
+ {
+ // In case of the driver doesn't support getting attributes
+ }
+
+ return null;
+ }
+}
diff --git a/core/Db/Pdo/Mysql.php b/core/Db/Adapter/Pdo/Mysql.php
index 86f804e76b..4e8f8793a1 100644
--- a/core/Db/Pdo/Mysql.php
+++ b/core/Db/Adapter/Pdo/Mysql.php
@@ -12,8 +12,9 @@
/**
* @package Piwik
+ * @subpackage Piwik_Db
*/
-class Piwik_Db_Pdo_Mysql extends Zend_Db_Adapter_Pdo_Mysql implements Piwik_Db_iAdapter
+class Piwik_Db_Adapter_Pdo_Mysql extends Zend_Db_Adapter_Pdo_Mysql implements Piwik_Db_Adapter_Interface
{
/**
* Returns connection handle
@@ -67,15 +68,29 @@ class Piwik_Db_Pdo_Mysql extends Zend_Db_Adapter_Pdo_Mysql implements Piwik_Db_i
*/
public function checkServerVersion()
{
- $databaseVersion = $this->getServerVersion();
+ $serverVersion = $this->getServerVersion();
$requiredVersion = Zend_Registry::get('config')->General->minimum_mysql_version;
- if(version_compare($databaseVersion, $requiredVersion) === -1)
+ if(version_compare($serverVersion, $requiredVersion) === -1)
{
- throw new Exception(Piwik_TranslateException('General_ExceptionDatabaseVersion', array('MySQL', $databaseVersion, $requiredVersion)));
+ throw new Exception(Piwik_TranslateException('General_ExceptionDatabaseVersion', array('MySQL', $serverVersion, $requiredVersion)));
}
}
/**
+ * Check client version compatibility against database server
+ */
+ public function checkClientVersion()
+ {
+ $serverVersion = $this->getServerVersion();
+ $clientVersion = $this->getClientVersion();
+ if(version_compare($serverVersion, '5') >= 0
+ && version_compare($clientVersion, '5') < 0)
+ {
+ throw new Exception(Piwik_TranslateException('General_ExceptionIncompatibleClientServerVersions', array('MySQL', $clientVersion, $serverVersion)));
+ }
+ }
+
+ /**
* Returns true if this adapter's required extensions are enabled
*
* @return bool
@@ -83,7 +98,7 @@ class Piwik_Db_Pdo_Mysql extends Zend_Db_Adapter_Pdo_Mysql implements Piwik_Db_i
public static function isEnabled()
{
$extensions = @get_loaded_extensions();
- return in_array('PDO', $extensions) && in_array('pdo_mysql', $extensions);
+ return in_array('PDO', $extensions) && in_array('pdo_mysql', $extensions) && in_array('mysql', PDO::getAvailableDrivers());
}
/**
@@ -105,7 +120,7 @@ class Piwik_Db_Pdo_Mysql extends Zend_Db_Adapter_Pdo_Mysql implements Piwik_Db_i
*/
public function isErrNo($e, $errno)
{
- if(preg_match('/ ([0-9]{4}) /', $e->getMessage(), $match))
+ if(preg_match('/(?:\[|\s)([0-9]{4})(?:\]|\s)/', $e->getMessage(), $match))
{
return $match[1] == $errno;
}
@@ -123,4 +138,24 @@ class Piwik_Db_Pdo_Mysql extends Zend_Db_Adapter_Pdo_Mysql implements Piwik_Db_i
$charset = $charsetInfo[0]['Value'];
return $charset === 'utf8';
}
+
+ /**
+ * Retrieve client version in PHP style
+ *
+ * @return string
+ */
+ public function getClientVersion()
+ {
+ $this->_connect();
+ try {
+ $version = $this->_connection->getAttribute(PDO::ATTR_CLIENT_VERSION);
+ $matches = null;
+ if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) {
+ return $matches[1];
+ }
+ } catch (PDOException $e) {
+ // In case of the driver doesn't support getting attributes
+ }
+ return null;
+ }
}
diff --git a/core/Db/Pdo/Pgsql.php b/core/Db/Adapter/Pdo/Pgsql.php
index 8c3cf0a6c5..f83a023b01 100644
--- a/core/Db/Pdo/Pgsql.php
+++ b/core/Db/Adapter/Pdo/Pgsql.php
@@ -12,8 +12,9 @@
/**
* @package Piwik
+ * @subpackage Piwik_Db
*/
-class Piwik_Db_Pdo_Pgsql extends Zend_Db_Adapter_Pdo_Pgsql implements Piwik_Db_iAdapter
+class Piwik_Db_Adapter_Pdo_Pgsql extends Zend_Db_Adapter_Pdo_Pgsql implements Piwik_Db_Adapter_Interface
{
/**
* Reset the configuration variables in this adapter.
@@ -39,11 +40,18 @@ class Piwik_Db_Pdo_Pgsql extends Zend_Db_Adapter_Pdo_Pgsql implements Piwik_Db_i
public function checkServerVersion()
{
$databaseVersion = $this->getServerVersion();
- $requiredVersion = Zend_Registry::get('config')->General->minimum_pgsql_version;
- if(version_compare($databaseVersion, $requiredVersion) === -1)
- {
- throw new Exception(Piwik_TranslateException('General_ExceptionDatabaseVersion', array('PostgreSQL', $databaseVersion, $requiredVersion)));
- }
+ $requiredVersion = Zend_Registry::get('config')->General->minimum_pgsql_version;
+ if(version_compare($databaseVersion, $requiredVersion) === -1)
+ {
+ throw new Exception(Piwik_TranslateException('General_ExceptionDatabaseVersion', array('PostgreSQL', $databaseVersion, $requiredVersion)));
+ }
+ }
+
+ /**
+ * Check client version compatibility against database server
+ */
+ public function checkClientVersion()
+ {
}
/**
@@ -53,10 +61,6 @@ class Piwik_Db_Pdo_Pgsql extends Zend_Db_Adapter_Pdo_Pgsql implements Piwik_Db_i
*/
public static function isEnabled()
{
- /**
- * @todo This adapter is incomplete.
- */
- return false;
$extensions = @get_loaded_extensions();
return in_array('PDO', $extensions) && in_array('pdo_pgsql', $extensions);
}
@@ -76,32 +80,6 @@ class Piwik_Db_Pdo_Pgsql extends Zend_Db_Adapter_Pdo_Pgsql implements Piwik_Db_i
}
/**
- * Pre-process SQL to handle MySQL-isms
- *
- * @return string
- */
- public function preprocessSql($query)
- {
- $search = array(
- // In MySQL, OPTION is still a reserved keyword; Piwik uses
- // backticking in case table_prefix is empty.
- '`',
-
- // MySQL implicitly does 'ORDER BY column' when there's a
- // 'GROUP BY column'; Piwik uses 'ORDER BY NULL' when order
- // doesn't matter, for better performance.
- 'ORDER BY NULL',
- );
-
- $replace = array(
- '',
- '',
- );
-
- $query = str_replace($search, $replace, $query);
- }
-
- /**
* Test error number
*
* @param Exception $e
@@ -128,6 +106,10 @@ class Piwik_Db_Pdo_Pgsql extends Zend_Db_Adapter_Pdo_Pgsql implements Piwik_Db_i
// PostgreSQL: column "%s" of relation "%s" already exists
'1060' => '42701',
+ // MySQL: Duplicate key name '%s'
+ // PostgreSQL: relation "%s" already exists
+ '1061' => '42P07',
+
// MySQL: Duplicate entry '%s' for key '%s'
// PostgreSQL: duplicate key violates unique constraint
'1062' => '23505',
@@ -160,24 +142,22 @@ class Piwik_Db_Pdo_Pgsql extends Zend_Db_Adapter_Pdo_Pgsql implements Piwik_Db_i
}
/**
- * Returns a list of the tables in the database.
+ * Retrieve client version in PHP style
*
- * Replaces parent::listTables() which uses subqueries.
- * @see ZF-8046
- *
- * @return array
+ * @return string
*/
- public function listTables()
+ public function getClientVersion()
{
- $sql = "SELECT c.relname AS table_name "
- . "FROM pg_catalog.pg_class c "
- . "JOIN pg_catalog.pg_roles r ON r.oid = c.relowner "
- . "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace "
- . "WHERE n.nspname <> 'pg_catalog' "
- . "AND n.nspname !~ '^pg_toast' "
- . "AND pg_catalog.pg_table_is_visible(c.oid) "
- . "AND c.relkind = 'r' ";
-
- return $this->fetchCol($sql);
+ $this->_connect();
+ try {
+ $version = $this->_connection->getAttribute(PDO::ATTR_CLIENT_VERSION);
+ $matches = null;
+ if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) {
+ return $matches[1];
+ }
+ } catch (PDOException $e) {
+ // In case of the driver doesn't support getting attributes
+ }
+ return null;
}
}
diff --git a/core/Db/Schema.php b/core/Db/Schema.php
new file mode 100644
index 0000000000..7afe91d0e8
--- /dev/null
+++ b/core/Db/Schema.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$
+ *
+ * @category Piwik
+ * @package Piwik
+ */
+
+/**
+ * Schema abstraction
+ *
+ * Note: no relation to the ZF proposals for Zend_Db_Schema_Manager
+ *
+ * @package Piwik
+ * @subpackage Piwik_Db
+ */
+class Piwik_Db_Schema
+{
+ static private $instance = null;
+
+ private $schema = null;
+
+ /**
+ * Returns the singleton Piwik_Db_Schema
+ *
+ * @return Piwik_Db_Schema
+ */
+ static public function getInstance()
+ {
+ if (self::$instance === null)
+ {
+ $c = __CLASS__;
+ self::$instance = new $c();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Get schema class name
+ *
+ * @param string $schemaName
+ * @return string
+ */
+ private static function getSchemaClassName($schemaName)
+ {
+ return 'Piwik_Db_Schema_' . str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($schemaName))));
+ }
+
+ /**
+ * Get list of schemas
+ *
+ * @return array
+ */
+ public static function getSchemas($adapterName)
+ {
+ static $allSchemaNames = array(
+ // MySQL storage engines
+ 'MYSQL' => array(
+ 'Myisam',
+// 'Innodb',
+// 'Infinidb',
+ ),
+
+ // Microsoft SQL Server
+// 'MSSQL' => array( 'Mssql' ),
+
+ // PostgreSQL
+// 'PDO_PGSQL' => array( 'Pgsql' ),
+
+ // IBM DB2
+// 'IBM' => array( 'Ibm' ),
+
+ // Oracle
+// 'OCI' => array( 'Oci' ),
+ );
+
+ $adapterName = strtoupper($adapterName);
+ switch($adapterName)
+ {
+ case 'PDO_MYSQL':
+ case 'MYSQLI':
+ $adapterName = 'MYSQL';
+ break;
+
+ case 'PDO_MSSQL':
+ case 'SQLSRV':
+ $adapterName = 'MSSQL';
+ break;
+
+ case 'PDO_IBM':
+ case 'DB2':
+ $adapterName = 'IBM';
+ break;
+
+ case 'PDO_OCI':
+ case 'ORACLE':
+ $adapterName = 'OCI';
+ break;
+ }
+ $schemaNames = $allSchemaNames[$adapterName];
+
+ $schemas = array();
+
+ foreach($schemaNamess as $schemaName)
+ {
+ $className = 'Piwik_Db_Schema_'.$schemaName;
+ if(call_user_func(array($className, 'isAvailable')))
+ {
+ $schemas[] = $schemaName;
+ }
+ }
+
+ return $schemas;
+ }
+
+ /**
+ * Load schema
+ */
+ private function loadSchema()
+ {
+ $schema = null;
+ Piwik_PostEvent('Schema.loadSchema', $schema);
+ if($schema === null)
+ {
+ $config = Zend_Registry::get('config');
+ $dbInfos = $config->database->toArray();
+ if(isset($dbInfos['schema']))
+ {
+ $schemaName = $dbInfos['schema'];
+ }
+ else
+ {
+ $schemaName = 'Myisam';
+ }
+ $className = self::getSchemaClassName($schemaName);
+ $schema = new $className();
+ }
+ $this->schema = $schema;
+ }
+
+ /**
+ * Returns an instance that subclasses Piwik_Db_Schema
+ *
+ * @return Piwik_Db_Schema_Interface
+ */
+ private function getSchema()
+ {
+ if ($this->schema === null)
+ {
+ $this->loadSchema();
+ }
+ return $this->schema;
+ }
+
+ /**
+ * Get the SQL to create a specific Piwik table
+ *
+ * @return string SQL
+ */
+ public function getTableCreateSql( $tableName )
+ {
+ return $this->getSchema()->getTableCreateSql($tableName);
+ }
+
+ /**
+ * Get the SQL to create Piwik tables
+ *
+ * @return array of strings containing SQL
+ */
+ public function getTablesCreateSql()
+ {
+ return $this->getSchema()->getTablesCreateSql();
+ }
+
+ /**
+ * Create database
+ */
+ public function createDatabase( $dbName = null )
+ {
+ $this->getSchema()->createDatabase($dbName);
+ }
+
+ /**
+ * Drop database
+ */
+ public function dropDatabase()
+ {
+ $this->getSchema()->dropDatabase();
+ }
+
+ /**
+ * Create all tables
+ */
+ public function createTables()
+ {
+ $this->getSchema()->createTables();
+ }
+
+ /**
+ * Creates an entry in the User table for the "anonymous" user.
+ */
+ public function createAnonymousUser()
+ {
+ $this->getSchema()->createAnonymousUser();
+ }
+
+ /**
+ * Truncate all tables
+ */
+ public function truncateAllTables()
+ {
+ $this->getSchema()->truncateAllTables();
+ }
+
+ /**
+ * Drop specific tables
+ */
+ public function dropTables( $doNotDelete = array() )
+ {
+ $this->getSchema()->dropTables($doNotDelete);
+ }
+
+ /**
+ * Names of all the prefixed tables in piwik
+ * Doesn't use the DB
+ *
+ * @return array Table names
+ */
+ public function getTablesNames()
+ {
+ return $this->getSchema()->getTablesNames();
+ }
+
+ /**
+ * Get list of tables installed
+ *
+ * @param bool $forceReload Invalidate cache
+ * @param string $idSite
+ * @return array Tables installed
+ */
+ public function getTablesInstalled($forceReload = true, $idSite = null)
+ {
+ return $this->getSchema()->getTablesInstalled($forceReload, $idSite);
+ }
+
+ /**
+ * Returns true if Piwik tables exist
+ *
+ * @return bool True if tables exist; false otherwise
+ */
+ public function hasTables()
+ {
+ return $this->getSchema()->hasTables();
+ }
+}
+
+/**
+ * @package Piwik
+ */
+interface Piwik_Db_Schema_Interface
+{
+ static public function isAvailable();
+
+ public function getTableCreateSql($tableName);
+ public function getTablesCreateSql();
+
+ public function createDatabase( $dbName = null );
+ public function dropDatabase();
+
+ public function createTables();
+ public function createAnonymousUser();
+ public function truncateAllTables();
+ public function dropTables( $doNotDelete = array() );
+
+ public function getTablesNames();
+ public function getTablesInstalled($forceReload = true, $idSite = null);
+ public function hasTables();
+}
diff --git a/core/Db/Schema/Myisam.php b/core/Db/Schema/Myisam.php
new file mode 100644
index 0000000000..790e07db38
--- /dev/null
+++ b/core/Db/Schema/Myisam.php
@@ -0,0 +1,484 @@
+<?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$
+ *
+ * @category Piwik
+ * @package Piwik
+ */
+
+/**
+ * MySQL schema
+ *
+ * @package Piwik
+ * @subpackage Piwik_Db
+ */
+class Piwik_Db_Schema_Myisam implements Piwik_Db_Schema_Interface
+{
+ /**
+ * Is this MySQL storage engine available?
+ *
+ * @param string $engineName
+ * @return bool True if available and enabled; false otherwise
+ */
+ static private function hasStorageEngine($engineName)
+ {
+ $db = Zend_Registry::get('db');
+ $allEngines = $db->fetchAssoc('SHOW ENGINES');
+ if(key_exists($engineName, $allEngines))
+ {
+ $support = $allEngines[$engineName]['Support'];
+ return $support == 'DEFAULT' || $support == 'YES';
+ }
+ return false;
+ }
+
+ /**
+ * Is this schema available?
+ *
+ * @return bool True if schema is available; false otherwise
+ */
+ static public function isAvailable()
+ {
+ return self::hasStorageEngine('MyISAM');
+ }
+
+ /**
+ * Get the SQL to create Piwik tables
+ *
+ * @return array of strings containing SQL
+ */
+ public function getTablesCreateSql()
+ {
+ $config = Zend_Registry::get('config');
+ $prefixTables = $config->database->tables_prefix;
+ $tables = array(
+ 'user' => "CREATE TABLE {$prefixTables}user (
+ login VARCHAR(100) 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 NULL,
+ PRIMARY KEY(login),
+ UNIQUE KEY uniq_keytoken(token_auth)
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ 'access' => "CREATE TABLE {$prefixTables}access (
+ login VARCHAR(100) NOT NULL,
+ idsite INTEGER UNSIGNED NOT NULL,
+ access VARCHAR(10) NULL,
+ PRIMARY KEY(login, idsite)
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ '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 NULL,
+ timezone VARCHAR( 50 ) NOT NULL,
+ currency CHAR( 3 ) NOT NULL,
+ excluded_ips TEXT NOT NULL,
+ excluded_parameters VARCHAR ( 255 ) NOT NULL,
+ PRIMARY KEY(idsite)
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ 'site_url' => "CREATE TABLE {$prefixTables}site_url (
+ idsite INTEGER(10) UNSIGNED NOT NULL,
+ url VARCHAR(255) NOT NULL,
+ PRIMARY KEY(idsite, url)
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ 'goal' => " CREATE TABLE `{$prefixTables}goal` (
+ `idsite` int(11) NOT NULL,
+ `idgoal` int(11) NOT NULL,
+ `name` varchar(50) NOT NULL,
+ `match_attribute` varchar(20) NOT NULL,
+ `pattern` varchar(255) NOT NULL,
+ `pattern_type` varchar(10) NOT NULL,
+ `case_sensitive` tinyint(4) NOT NULL,
+ `revenue` float NOT NULL,
+ `deleted` tinyint(4) NOT NULL default '0',
+ PRIMARY KEY (`idsite`,`idgoal`)
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ '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)
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ '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 INT UNSIGNED NULL,
+ timestamp TIMESTAMP NULL,
+ returned_value TEXT NULL,
+ PRIMARY KEY(idlogger_api_call)
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ '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)
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ '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)
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ 'log_action' => "CREATE TABLE {$prefixTables}log_action (
+ idaction INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ name TEXT,
+ hash INTEGER(10) UNSIGNED NOT NULL,
+ type TINYINT UNSIGNED NULL,
+ PRIMARY KEY(idaction),
+ INDEX index_type_hash (type, hash)
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ '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_url INTEGER(11) NOT NULL,
+ visit_entry_idaction_url INTEGER(11) NOT NULL,
+ visit_total_actions SMALLINT(5) UNSIGNED NOT NULL,
+ visit_total_time SMALLINT(5) UNSIGNED NOT NULL,
+ visit_goal_converted TINYINT(1) 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_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_gears TINYINT(1) NOT NULL,
+ config_silverlight TINYINT(1) NOT NULL,
+ config_cookie TINYINT(1) NOT NULL,
+ location_ip INT UNSIGNED NOT NULL,
+ location_browser_lang VARCHAR(20) NOT NULL,
+ location_country CHAR(3) NOT NULL,
+ location_continent CHAR(3) NOT NULL,
+ PRIMARY KEY(idvisit),
+ INDEX index_idsite_idvisit (idsite, idvisit),
+ INDEX index_idsite_date_config (idsite, visit_server_date, config_md5config(8)),
+ INDEX index_idsite_datetime_config (idsite, visit_last_action_time, config_md5config(8))
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ 'log_conversion' => "CREATE TABLE `{$prefixTables}log_conversion` (
+ idvisit int(10) unsigned NOT NULL,
+ idsite int(10) unsigned NOT NULL,
+ visitor_idcookie char(32) NOT NULL,
+ server_time datetime NOT NULL,
+ idaction_url int(11) default NULL,
+ idlink_va int(11) default NULL,
+ referer_idvisit int(10) unsigned default NULL,
+ referer_visit_server_date date default NULL,
+ referer_type int(10) unsigned default NULL,
+ referer_name varchar(70) default NULL,
+ referer_keyword varchar(255) default NULL,
+ visitor_returning tinyint(1) NOT NULL,
+ location_country char(3) NOT NULL,
+ location_continent char(3) NOT NULL,
+ url text NOT NULL,
+ idgoal int(10) unsigned NOT NULL,
+ revenue float default NULL,
+ PRIMARY KEY (idvisit, idgoal),
+ INDEX index_idsite_datetime ( idsite, server_time )
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ '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_url INTEGER(10) UNSIGNED NOT NULL,
+ idaction_url_ref INTEGER(10) UNSIGNED NOT NULL,
+ idaction_name INTEGER(10) UNSIGNED,
+ time_spent_ref_action INTEGER(10) UNSIGNED NOT NULL,
+ PRIMARY KEY(idlink_va),
+ INDEX index_idvisit(idvisit)
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ 'log_profiling' => "CREATE TABLE {$prefixTables}log_profiling (
+ query TEXT NOT NULL,
+ count INTEGER UNSIGNED NULL,
+ sum_time_ms FLOAT NULL,
+ UNIQUE KEY query(query(100))
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ 'option' => "CREATE TABLE `{$prefixTables}option` (
+ option_name VARCHAR( 64 ) NOT NULL,
+ option_value LONGTEXT NOT NULL,
+ autoload TINYINT NOT NULL DEFAULT '1',
+ PRIMARY KEY ( option_name )
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ '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),
+ INDEX index_idsite_dates_period(idsite, date1, date2, period, ts_archived),
+ INDEX index_period_archived(period, ts_archived)
+ ) DEFAULT CHARSET=utf8
+ ",
+
+ '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),
+ INDEX index_period_archived(period, ts_archived)
+ ) DEFAULT CHARSET=utf8
+ ",
+ );
+ return $tables;
+ }
+
+ /**
+ * Get the SQL to create a specific Piwik table
+ *
+ * @param string $tableName
+ * @return string SQL
+ */
+ 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];
+ }
+
+ /**
+ * Names of all the prefixed tables in piwik
+ * Doesn't use the DB
+ *
+ * @return array Table names
+ */
+ public function getTablesNames()
+ {
+ $aTables = array_keys($this->getTablesCreateSql());
+ $config = Zend_Registry::get('config');
+ $prefixTables = $config->database->tables_prefix;
+ $return = array();
+ foreach($aTables as $table)
+ {
+ $return[] = $prefixTables.$table;
+ }
+ return $return;
+ }
+
+ private $tablesInstalled = null;
+
+ /**
+ * Get list of tables installed
+ *
+ * @param bool $forceReload Invalidate cache
+ * @param string $idSite
+ * @return array Tables installed
+ */
+ public function getTablesInstalled($forceReload = true, $idSite = null)
+ {
+ if(is_null($this->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 = $this->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)
+ $idSiteInSql = "no";
+ if(!is_null($idSite))
+ {
+ $idSiteInSql = $idSite;
+ }
+ $allArchiveNumeric = $db->fetchCol("/* SHARDING_ID_SITE = ".$idSiteInSql." */
+ SHOW TABLES LIKE '".$prefixTables."archive_numeric%'");
+ $allArchiveBlob = $db->fetchCol("/* SHARDING_ID_SITE = ".$idSiteInSql." */
+ SHOW TABLES LIKE '".$prefixTables."archive_blob%'");
+
+ $allTablesReallyInstalled = array_merge($tablesInstalled, $allArchiveNumeric, $allArchiveBlob);
+
+ $this->tablesInstalled = $allTablesReallyInstalled;
+ }
+ return $this->tablesInstalled;
+ }
+
+ /**
+ * Do tables exist?
+ *
+ * @return bool True if tables exist; false otherwise
+ */
+ public function hasTables()
+ {
+ return count($this->getTablesInstalled()) != 0;
+ }
+
+ /**
+ * Create database
+ *
+ * @param string $dbName
+ */
+ public function createDatabase( $dbName = null )
+ {
+ if(is_null($dbName))
+ {
+ $dbName = Zend_Registry::get('config')->database->dbname;
+ }
+ Piwik_Exec("CREATE DATABASE IF NOT EXISTS ".$dbName);
+ }
+
+ /**
+ * Drop database
+ */
+ public function dropDatabase()
+ {
+ $dbName = Zend_Registry::get('config')->database->dbname;
+ Piwik_Exec("DROP DATABASE IF EXISTS " . $dbName);
+
+ }
+
+ /**
+ * Create all tables
+ */
+ public function createTables()
+ {
+ $db = Zend_Registry::get('db');
+ $config = Zend_Registry::get('config');
+ $prefixTables = $config->database->tables_prefix;
+
+ $tablesAlreadyInstalled = $this->getTablesInstalled();
+ $tablesToCreate = $this->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 );
+ }
+ }
+ }
+
+ /**
+ * Creates an entry in the User table for the "anonymous" user.
+ */
+ 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_Common::prefixTable("user") . "
+ VALUES ( 'anonymous', '', 'anonymous', 'anonymous@example.org', 'anonymous', '".Piwik_Date::factory('now')->getDatetime()."' );" );
+ }
+
+ /**
+ * Truncate all tables
+ */
+ public function truncateAllTables()
+ {
+ $tablesAlreadyInstalled = $this->getTablesInstalled($forceReload = true);
+ foreach($tablesAlreadyInstalled as $table)
+ {
+ Piwik_Query("TRUNCATE `$table`");
+ }
+ }
+
+ /**
+ * Drop specific tables
+ *
+ * @param array $doNotDelete Names of tables to not delete
+ */
+ public function dropTables( $doNotDelete = array() )
+ {
+ $tablesAlreadyInstalled = $this->getTablesInstalled();
+ $db = Zend_Registry::get('db');
+
+ $doNotDeletePattern = '/('.implode('|',$doNotDelete).')/';
+
+ foreach($tablesAlreadyInstalled as $tableName)
+ {
+ if( count($doNotDelete) == 0
+ || (!in_array($tableName,$doNotDelete)
+ && !preg_match($doNotDeletePattern,$tableName)
+ )
+ )
+ {
+ $db->query("DROP TABLE `$tableName`");
+ }
+ }
+ }
+}
diff --git a/core/ExceptionHandler.php b/core/ExceptionHandler.php
index f5554ad89c..7c8d070ec0 100644
--- a/core/ExceptionHandler.php
+++ b/core/ExceptionHandler.php
@@ -32,7 +32,7 @@ function Piwik_ExceptionHandler(Exception $exception)
$formatter = new Piwik_Log_Exception_Formatter_ScreenFormatter();
$message = $formatter->format($event);
- $message .= "<br><br>And this exception raised another exception \"". $e->getMessage()."\"";
+ $message .= "<br /><br />And this exception raised another exception \"". $e->getMessage()."\"";
Piwik::exitWithErrorMessage( $message );
}
diff --git a/core/Form.php b/core/Form.php
index 6c00803938..4dfbdc087e 100644
--- a/core/Form.php
+++ b/core/Form.php
@@ -23,13 +23,13 @@ abstract class Piwik_Form extends HTML_QuickForm
{
protected $a_formElements = array();
- function __construct( $action = '' )
+ function __construct( $action = '', $attributes = '' )
{
if(empty($action))
{
$action = Piwik_Url::getCurrentQueryString();
}
- parent::HTML_QuickForm('form', 'POST', $action);
+ parent::HTML_QuickForm('form', 'POST', $action, $target='', $attributes);
$this->registerRule( 'checkEmail', 'function', 'Piwik_Form_isValidEmailString');
$this->registerRule( 'fieldHaveSameValue', 'function', 'Piwik_Form_fieldHaveSameValue');
@@ -96,6 +96,16 @@ abstract class Piwik_Form extends HTML_QuickForm
}
}
}
+ function setSelected( $nameElement, $value )
+ {
+ foreach( $this->_elements as $key => $value)
+ {
+ if($value->_attributes['name'] == $nameElement)
+ {
+ $this->_elements[$key]->_attributes['selected'] = 'selected';
+ }
+ }
+ }
}
function Piwik_Form_fieldHaveSameValue($element, $value, $arg)
diff --git a/core/FrontController.php b/core/FrontController.php
index 9d691d4421..22c99c84b2 100644
--- a/core/FrontController.php
+++ b/core/FrontController.php
@@ -10,9 +10,6 @@
* @package Piwik
*/
-// no direct access
-defined('PIWIK_INCLUDE_PATH') or die;
-
/**
* @see core/PluginsManager.php
* @see core/Translate.php
@@ -108,7 +105,7 @@ class Piwik_FrontController
if(!class_exists($controllerClassName, false))
{
$moduleController = PIWIK_INCLUDE_PATH . '/plugins/' . $module . '/Controller.php';
- if( !Zend_Loader::isReadable($moduleController))
+ if(!is_readable($moduleController))
{
throw new Exception("Module controller $moduleController not found!");
}
@@ -163,13 +160,14 @@ class Piwik_FrontController
try {
Piwik::printSqlProfilingReportZend();
Piwik::printQueryCount();
+/*
+ if(Piwik::getModule() !== 'API')
+ {
+ Piwik::printMemoryUsage();
+ Piwik::printTimer();
+ }
+ */
} catch(Exception $e) {}
-
- if(Piwik::getModule() !== 'API')
- {
-// Piwik::printMemoryUsage();
-// Piwik::printTimer();
- }
}
/**
@@ -206,16 +204,21 @@ class Piwik_FrontController
}
$pluginsManager = Piwik_PluginsManager::getInstance();
- $pluginsManager->setPluginsToLoad( Zend_Registry::get('config')->Plugins->Plugins->toArray() );
+ $pluginsManager->loadPlugins( Zend_Registry::get('config')->Plugins->Plugins->toArray() );
if($exceptionToThrow)
{
throw $exceptionToThrow;
}
- Piwik_Translate::getInstance()->loadUserTranslation();
- Piwik::createDatabaseObject();
+ try {
+ Piwik::createDatabaseObject();
+ } catch(Exception $e) {
+ Piwik_PostEvent('FrontController.badConfigurationFile', $e);
+ throw $e;
+ }
+
Piwik::createLogObject();
// creating the access object, so that core/Updates/* can enforce Super User and use some APIs
@@ -230,16 +233,18 @@ class Piwik_FrontController
$authAdapter = Zend_Registry::get('auth');
} catch(Exception $e){
throw new Exception("Authentication object 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>
+ <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");
}
Zend_Registry::get('access')->reloadAccess($authAdapter);
+ Piwik_Translate::getInstance()->loadUserTranslation();
Piwik::raiseMemoryLimitIfNecessary();
$pluginsManager->setLanguageToLoad( Piwik_Translate::getInstance()->getLanguageToLoad() );
+ $pluginsManager->loadTranslations();
$pluginsManager->postLoadPlugins();
Piwik_PostEvent('FrontController.checkForUpdates');
diff --git a/core/Http.php b/core/Http.php
new file mode 100644
index 0000000000..9ca4d229e4
--- /dev/null
+++ b/core/Http.php
@@ -0,0 +1,385 @@
+<?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$
+ *
+ * @category Piwik
+ * @package Piwik
+ */
+
+/**
+ * Server-side http client to retrieve content from remote servers, and optionally save to a local file.
+ * Used to check for the latest Piwik version and download updates.
+ *
+ * @package Piwik
+ */
+class Piwik_Http
+{
+ /**
+ * Get "best" available transport method for sendHttpRequest() calls.
+ *
+ * @return string
+ */
+ static public function getTransportMethod()
+ {
+ $method = 'curl';
+ if(!extension_loaded('curl'))
+ {
+ $method = 'stream';
+ if(@ini_get('allow_url_fopen') != '1')
+ {
+ $method = 'socket';
+ if(!function_exists('fsockopen'))
+ {
+ return null;
+ }
+ }
+ }
+ return $method;
+ }
+
+ /**
+ * Sends http request ensuring the request will fail before $timeout seconds
+ *
+ * If no $destinationPath is specified, the trimmed response (without header) is returned as a string.
+ * If a $destinationPath is specified, the response (without header) is saved to a file.
+ *
+ * @param string $aUrl
+ * @param int $timeout
+ * @param string $userAgent
+ * @param string $destinationPath
+ * @param int $followDepth
+ * @return bool true (or string) on success; false on HTTP response error code (1xx or 4xx)
+ * @throws Exception for all other errors
+ */
+ static public function sendHttpRequest($aUrl, $timeout, $userAgent = null, $destinationPath = null, $followDepth = 0)
+ {
+ // create output file
+ $file = null;
+ if($destinationPath)
+ {
+ if (($file = @fopen($destinationPath, 'wb')) === false || !is_resource($file))
+ {
+ throw new Exception('Error while creating the file: ' . $destinationPath);
+ }
+ }
+
+ return self::sendHttpRequestBy(self::getTransportMethod(), $aUrl, $timeout, $userAgent, $destinationPath, $file, $followDepth);
+ }
+
+ /**
+ * Sends http request using the specified transport method
+ *
+ * @param string $method
+ * @param string $aUrl
+ * @param int $timeout
+ * @param string $userAgent
+ * @param string $destinationPath
+ * @param resource $file
+ * @param int $followDepth
+ * @return bool true (or string) on success; false on HTTP response error code (1xx or 4xx)
+ * @throws Exception for all other errors
+ */
+ static public function sendHttpRequestBy($method = 'socket', $aUrl, $timeout, $userAgent = null, $destinationPath = null, $file = null, $followDepth = 0)
+ {
+ if ($followDepth > 5)
+ {
+ throw new Exception('Too many redirects ('.$followDepth.')');
+ }
+
+ $contentLength = 0;
+ $fileLength = 0;
+
+ if($method == 'socket')
+ {
+ // initialization
+ $url = @parse_url($aUrl);
+ if($url === false || !isset($url['scheme']))
+ {
+ throw new Exception('Malformed URL: '.$aUrl);
+ }
+
+ if($url['scheme'] != 'http')
+ {
+ throw new Exception('Invalid protocol/scheme: '.$url['scheme']);
+ }
+ $host = $url['host'];
+ $port = isset($url['port)']) ? $url['port'] : 80;
+ $path = isset($url['path']) ? $url['path'] : '/';
+ if(isset($url['query']))
+ {
+ $path .= '?'.$url['query'];
+ }
+ $errno = null;
+ $errstr = null;
+
+ // connection attempt
+ if (($fsock = @fsockopen($host, $port, $errno, $errstr, $timeout)) === false || !is_resource($fsock))
+ {
+ if(is_resource($file)) { @fclose($file); }
+ throw new Exception("Error while connecting to: $host. Please try again later. $errstr");
+ }
+
+ // send HTTP request header
+ fwrite($fsock,
+ "GET $path HTTP/1.0\r\n"
+ ."Host: $host".($port != 80 ? ':'.$port : '')."\r\n"
+ ."User-Agent: Piwik/".Piwik_Version::VERSION.($userAgent ? " $userAgent" : '')."\r\n"
+ .'Referer: http://'.Piwik_Common::getIpString()."/\r\n"
+ ."Connection: close\r\n"
+ ."\r\n"
+ );
+
+ $streamMetaData = array('timed_out' => false);
+ @stream_set_blocking($fsock, true);
+ @stream_set_timeout($fsock, $timeout);
+
+ // process header
+ $status = null;
+ $expectRedirect = false;
+
+ while(!feof($fsock))
+ {
+ $line = fgets($fsock, 4096);
+
+ $streamMetaData = @stream_get_meta_data($fsock);
+ if($streamMetaData['timed_out'])
+ {
+ if(is_resource($file)) { @fclose($file); }
+ @fclose($fsock);
+ throw new Exception('Timed out waiting for server response');
+ }
+
+ // a blank line marks the end of the server response header
+ if(rtrim($line, "\r\n") == '')
+ {
+ break;
+ }
+
+ // parse first line of server response header
+ if(!$status)
+ {
+ // expect first line to be HTTP response status line, e.g., HTTP/1.1 200 OK
+ if(!preg_match('~^HTTP/(\d\.\d)\s+(\d+)(\s*.*)?~', $line, $m))
+ {
+ if(is_resource($file)) { @fclose($file); }
+ @fclose($fsock);
+ throw new Exception('Expected server response code. Got '.rtrim($line, "\r\n"));
+ }
+
+ $status = (integer) $m[2];
+
+ // Informational 1xx or Client Error 4xx
+ if ($status < 200 || $status >= 400)
+ {
+ if(is_resource($file)) { @fclose($file); }
+ @fclose($fsock);
+ return false;
+ }
+
+ continue;
+ }
+
+ // handle redirect
+ if(preg_match('/^Location:\s*(.+)/', rtrim($line, "\r\n"), $m))
+ {
+ if(is_resource($file)) { @fclose($file); }
+ @fclose($fsock);
+ // Successful 2xx vs Redirect 3xx
+ if($status < 300)
+ {
+ throw new Exception('Unexpected redirect to Location: '.rtrim($line).' for status code '.$status);
+ }
+ return self::sendHttpRequestBy($method, trim($m[1]), $timeout, $userAgent, $pathDestination, $file, $followDepth+1);
+ }
+
+ // save expected content length for later verification
+ if(preg_match('/^Content-Length:\s*(\d+)/', $line, $m))
+ {
+ $contentLength = (integer) $m[1];
+ }
+ }
+
+ if(feof($fsock))
+ {
+ throw new Exception('Unexpected end of transmission');
+ }
+
+ // process content/body
+ $response = '';
+
+ while(!feof($fsock))
+ {
+ $line = fread($fsock, 8192);
+
+ $streamMetaData = @stream_get_meta_data($fsock);
+ if($streamMetaData['timed_out'])
+ {
+ if(is_resource($file)) { @fclose($file); }
+ @fclose($fsock);
+ throw new Exception('Timed out waiting for server response');
+ }
+
+ $fileLength += strlen($line);
+
+ if(is_resource($file))
+ {
+ // save to file
+ fwrite($file, $line);
+ }
+ else
+ {
+ // concatenate to response string
+ $response .= $line;
+ }
+ }
+
+ // determine success or failure
+ @fclose(@$fsock);
+ }
+ else if($method == 'stream')
+ {
+ $response = false;
+
+ // we make sure the request takes less than a few seconds to fail
+ // we create a stream_context (works in php >= 5.2.1)
+ // we also set the socket_timeout (for php < 5.2.1)
+ $default_socket_timeout = @ini_get('default_socket_timeout');
+ @ini_set('default_socket_timeout', $timeout);
+
+ $ctx = null;
+ if(function_exists('stream_context_create')) {
+ $stream_options = array(
+ 'http' => array(
+ 'header' => 'User-Agent: Piwik/'.Piwik_Version::VERSION.($userAgent ? " $userAgent" : '')."\r\n"
+ .'Referer: http://'.Piwik_Common::getIpString()."/\r\n",
+ 'max_redirects' => 5, // PHP 5.1.0
+ 'timeout' => $timeout, // PHP 5.2.1
+ )
+ );
+ $ctx = stream_context_create($stream_options);
+ }
+
+ $response = @file_get_contents($aUrl, 0, $ctx);
+ $fileLength = strlen($response);
+
+ if(is_resource($file))
+ {
+ // save to file
+ fwrite($file, $response);
+ }
+
+ // restore the socket_timeout value
+ if(!empty($default_socket_timeout))
+ {
+ @ini_set('default_socket_timeout', $default_socket_timeout);
+ }
+ }
+ else if($method == 'curl')
+ {
+ $ch = @curl_init();
+
+ $curl_options = array(
+ // internal to ext/curl
+ CURLOPT_BINARYTRANSFER => is_resource($file),
+
+ // curl options (sorted oldest to newest)
+ CURLOPT_URL => $aUrl,
+ CURLOPT_REFERER => 'http://'.Piwik_Common::getIpString(),
+ CURLOPT_USERAGENT => 'Piwik/'.Piwik_Version::VERSION.($userAgent ? " $userAgent" : ''),
+ CURLOPT_HEADER => false,
+ CURLOPT_CONNECTTIMEOUT => $timeout,
+ );
+ @curl_setopt_array($ch, $curl_options);
+
+ /*
+ * as of php 5.2.0, CURLOPT_FOLLOWLOCATION can't be set if
+ * in safe_mode or open_basedir is set
+ */
+ if((string)ini_get('safe_mode') == '' && ini_get('open_basedir') == '')
+ {
+ $curl_options = array(
+ // curl options (sorted oldest to newest)
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_MAXREDIRS => 5,
+ );
+ @curl_setopt_array($ch, $curl_options);
+ }
+
+ if(is_resource($file))
+ {
+ // write output directly to file
+ @curl_setopt($ch, CURLOPT_FILE, $file);
+ }
+ else
+ {
+ // internal to ext/curl
+ @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ }
+
+ ob_start();
+ $response = @curl_exec($ch);
+ ob_end_clean();
+
+ if($response === true)
+ {
+ $response = '';
+ }
+ else if($response === false)
+ {
+ $errstr = curl_error($ch);
+ if($errstr != '')
+ {
+ throw new Exception('curl_exec: '.$errstr);
+ }
+ $response = '';
+ }
+
+ $contentLength = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
+ $fileLength = is_resource($file) ? curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD) : strlen($response);
+
+ @curl_close($ch);
+ unset($ch);
+ }
+ else
+ {
+ throw new Exception('Invalid request method: '.$method);
+ }
+
+ if(is_resource($file))
+ {
+ fflush($file);
+ @fclose($file);
+
+ $fileSize = filesize($destinationPath);
+ if((($contentLength > 0) && ($fileLength != $contentLength)) || ($fileSize != $fileLength))
+ {
+ throw new Exception('File size error: '.$destinationPath.'; expected '.$contentLength.' bytes; received '.$fileLength.' bytes; saved '.$fileSize.' bytes to file');
+ }
+ return true;
+ }
+
+ if(($contentLength > 0) && ($fileLength != $contentLength))
+ {
+ throw new Exception('Content length error: expected '.$contentLength.' bytes; received '.$fileLength.' bytes');
+ }
+ return trim($response);
+ }
+
+ /**
+ * Fetch the file at $url in the destination $pathDestination
+ * @param string $url
+ * @param string $pathDestination
+ * @param int $tries
+ * @return true on success, throws Exception on failure
+ */
+ static public function fetchRemoteFile($url, $pathDestination, $tries = 0)
+ {
+ @ignore_user_abort(true);
+ Piwik::setMaxExecutionTime(0);
+ return self::sendHttpRequest($url, 10, 'Update', $pathDestination, $tries);
+ }
+}
diff --git a/core/Log.php b/core/Log.php
index 0be03c98d0..15481b315d 100644
--- a/core/Log.php
+++ b/core/Log.php
@@ -42,7 +42,7 @@ abstract class Piwik_Log extends Zend_Log
$this->fileFormatter = $fileFormatter;
$this->screenFormatter = $screenFormatter;
- $this->logToDatabaseTableName = Piwik::prefixTable($logToDatabaseTableName);
+ $this->logToDatabaseTableName = Piwik_Common::prefixTable($logToDatabaseTableName);
$this->logToDatabaseColumnMapping = $logToDatabaseColumnMapping;
}
@@ -61,7 +61,6 @@ abstract class Piwik_Log extends Zend_Log
function addWriteToNull()
{
- Zend_Loader::loadClass('Zend_Log_Writer_Null');
$this->addWriter( new Zend_Log_Writer_Null );
}
@@ -90,7 +89,7 @@ abstract class Piwik_Log extends Zend_Log
/**
* Log an event
*/
- public function log($event, $priority)
+ public function log($event, $priority, $extras = null)
{
// sanity checks
if (empty($this->_writers)) {
@@ -139,8 +138,7 @@ class Piwik_Log_Formatter_FileFormatter implements Zend_Log_Formatter_Interface
}
$ts = $event['timestamp'];
unset($event['timestamp']);
- $str = $ts . ' ' . implode(" ", $event) . "\n";
- return $str;
+ return $ts . ' ' . implode(" ", $event) . "\n";
}
}
@@ -156,11 +154,10 @@ class Piwik_Log_Formatter_ScreenFormatter implements Zend_Log_Formatter_Interfac
// no injection in error messages, backtrace when displayed on screen
return array_map('htmlspecialchars', $event);
}
-
+
function format($string)
{
- $string = self::getFormattedString($string);
- return $string;
+ return self::getFormattedString($string);
}
static public function getFormattedString($string)
@@ -168,7 +165,17 @@ class Piwik_Log_Formatter_ScreenFormatter implements Zend_Log_Formatter_Interfac
if(Piwik_Common::isPhpCliMode())
{
$string = str_replace(array('<br>','<br />','<br/>'), "\n", $string);
- $string = strip_tags($string);
+ if(is_array($string))
+ {
+ for($i=0; $i< count($string); $i++)
+ {
+ $string[$i] = strip_tags($string[$i]);
+ }
+ }
+ else
+ {
+ $string = strip_tags($string);
+ }
}
return $string;
}
diff --git a/core/Log/APICall.php b/core/Log/APICall.php
index ae390798b4..8148c77014 100644
--- a/core/Log/APICall.php
+++ b/core/Log/APICall.php
@@ -47,7 +47,7 @@ class Piwik_Log_APICall extends Piwik_Log
$event['execution_time'] = $executionTime;
$event['returned_value'] = is_array($returnedValue) ? serialize($returnedValue) : $returnedValue;
- parent::log($event, Piwik_Log::INFO);
+ parent::log($event, Piwik_Log::INFO, null);
}
}
@@ -67,8 +67,8 @@ class Piwik_Log_APICall_Formatter_ScreenFormatter extends Piwik_Log_Formatter_Sc
*/
public function format($event)
{
- $str = "\n<br> ";
- $str .= "Called: {$event['class_name']}.{$event['method_name']} (took {$event['execution_time']}ms) \n<br>";
+ $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']);
@@ -90,10 +90,10 @@ class Piwik_Log_APICall_Formatter_ScreenFormatter extends Piwik_Log_Formatter_Sc
$i++;
}
- $str .= "\n<br> ";
+ $str .= "\n<br /> ";
// $str .= "Returned: ".$this->formatValue($event['returned_value']);
- $str .= "\n<br> ";
+ $str .= "\n<br /> ";
return parent::format($str);
}
diff --git a/core/Log/Error.php b/core/Log/Error.php
index 7c41fa91e6..a6f71ac718 100644
--- a/core/Log/Error.php
+++ b/core/Log/Error.php
@@ -51,7 +51,7 @@ class Piwik_Log_Error extends Piwik_Log
$event['errline'] = $errline;
$event['backtrace'] = $backtrace;
- parent::log($event, Piwik_Log::ERR);
+ parent::log($event, Piwik_Log::ERR, null);
}
}
@@ -106,10 +106,10 @@ class Piwik_Log_Error_Formatter_ScreenFormatter extends Piwik_Log_Formatter_Scre
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>";
+ $strReturned .= "<br /><br />Backtrace --&gt;<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
index 6ae10b64cd..fe3e6ef98e 100644
--- a/core/Log/Exception.php
+++ b/core/Log/Exception.php
@@ -52,7 +52,7 @@ class Piwik_Log_Exception extends Piwik_Log
$event['errline'] = $exception->getLine();
$event['backtrace'] = $exception->getTraceAsString();
- parent::log($event, Piwik_Log::CRIT);
+ parent::log($event, Piwik_Log::CRIT, null);
}
}
@@ -80,7 +80,7 @@ class Piwik_Log_Exception_Formatter_ScreenFormatter extends Piwik_Log_Formatter_
$backtrace = $event['backtrace'] ;
$outputFormat = strtolower(Piwik_Common::getRequestVar('format', 'html', 'string'));
- $response = new Piwik_API_ResponseBuilder(null, $outputFormat);
+ $response = new Piwik_API_ResponseBuilder($outputFormat);
$message = $response->getResponseException(new Exception($errstr));
return parent::format($message);
}
diff --git a/core/Log/Message.php b/core/Log/Message.php
index 239fd1bc53..f5b1120a2e 100644
--- a/core/Log/Message.php
+++ b/core/Log/Message.php
@@ -38,7 +38,7 @@ class Piwik_Log_Message extends Piwik_Log
{
$event = array();
$event['message'] = $message;
- parent::log($event, Piwik_Log::INFO);
+ parent::log($event, Piwik_Log::INFO, null);
}
}
diff --git a/core/Nonce.php b/core/Nonce.php
new file mode 100644
index 0000000000..632ed55b07
--- /dev/null
+++ b/core/Nonce.php
@@ -0,0 +1,81 @@
+<?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$
+ *
+ * @category Piwik
+ * @package Piwik
+ */
+
+/**
+ * Nonce class.
+ *
+ * A cryptographic nonce -- "number used only once" -- is often recommended as part of a robust defense against cross-site request forgery (CSRF/XSRF).
+ * Desrable characteristics: limited lifetime, uniqueness, unpredictability (pseudo-randomness).
+ *
+ * We use a session-dependent nonce with a configurable expiration that combines and hashes:
+ * - a private salt because it's non-public
+ * - time() because it's unique
+ * - a mix of PRNGs (pseudo-random number generators) to increase entropy and make it less predictable
+ *
+ * @package Piwik
+ */
+class Piwik_Nonce
+{
+ /**
+ * Generate nonce
+ *
+ * @param string $id Unique id to avoid namespace conflicts, e.g., ModuleName.ActionName
+ * @param int $ttl Optional time-to-live in seconds; default is 5 minutes
+ * @return string Nonce
+ */
+ static public function getNonce($id, $ttl = 300)
+ {
+ // save session-dependent nonce
+ $ns = new Zend_Session_Namespace($id);
+ $nonce = $ns->nonce;
+
+ // re-use an unexpired nonce (a small deviation from the "used only once" principle, so long as we do not reset the expiration)
+ // to handle browser pre-fetch or double fetch caused by some browser add-ons/extensions
+ if(empty($nonce))
+ {
+ // generate a new nonce
+ $nonce = md5(Piwik_Common::getSalt() . time() . Piwik_Common::generateUniqId());
+ $ns->nonce = $nonce;
+ $ns->setExpirationSeconds($ttl, 'nonce');
+ }
+
+ return $nonce;
+ }
+
+ /**
+ * Verify nonce and check referrer (if present, i.e., it may be suppressed by the browser or a proxy/network).
+ *
+ * @param string $id Unique id
+ * @param string $cnonce Nonce sent to client
+ * @return bool true if valid; false otherwise
+ */
+ static public function verifyNonce($id, $cnonce)
+ {
+ $ns = new Zend_Session_Namespace($id);
+ $nonce = $ns->nonce;
+
+ // validate token
+ if(empty($cnonce) || $cnonce !== $nonce)
+ {
+ return false;
+ }
+
+ // validate referer
+ $referer = Piwik_Url::getReferer();
+ if(!empty($referer) && (Piwik_Url::getLocalReferer() === false))
+ {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/core/Option.php b/core/Option.php
index da1905dc82..75244c7f3a 100644
--- a/core/Option.php
+++ b/core/Option.php
@@ -54,7 +54,7 @@ class Piwik_Option
return $this->all[$name];
}
$value = Piwik_FetchOne( 'SELECT option_value
- FROM `' . Piwik::prefixTable('option') . '`
+ FROM `' . Piwik_Common::prefixTable('option') . '`
WHERE option_name = ?', $name);
if($value === false)
{
@@ -74,7 +74,7 @@ class Piwik_Option
public function set($name, $value, $autoload = 0)
{
$autoload = (int)$autoload;
- Piwik_Query('INSERT INTO `'. Piwik::prefixTable('option') . '` (option_name, option_value, autoload) '.
+ Piwik_Query('INSERT INTO `'. Piwik_Common::prefixTable('option') . '` (option_name, option_value, autoload) '.
' VALUES (?, ?, ?) '.
' ON DUPLICATE KEY UPDATE option_value = ?',
array($name, $value, $autoload, $value));
@@ -89,7 +89,7 @@ class Piwik_Option
return;
}
$all = Piwik_FetchAll('SELECT option_value, option_name
- FROM `'. Piwik::prefixTable('option') . '`
+ FROM `'. Piwik_Common::prefixTable('option') . '`
WHERE autoload = 1');
foreach($all as $option)
{
@@ -97,6 +97,17 @@ class Piwik_Option
}
$loaded = true;
}
+
+ /**
+ * Clears the cache
+ * Used in unit tests to reset the state of the object between tests
+ *
+ * @return void
+ */
+ public function clearCache()
+ {
+ $this->all = array();
+ }
}
function Piwik_GetOption($name)
diff --git a/core/Period.php b/core/Period.php
index 7fd59b84e8..4e5e6e37bb 100644
--- a/core/Period.php
+++ b/core/Period.php
@@ -30,8 +30,7 @@ abstract class Piwik_Period
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'";
+ static protected $errorAvailablePeriods = 'day, week, month, year';
public function __construct( $date )
{
@@ -44,7 +43,7 @@ abstract class Piwik_Period
* @param $date Piwik_Date object
* @return Piwik_Period
*/
- static public function factory($strPeriod, $date)
+ static public function factory($strPeriod, Piwik_Date $date)
{
switch ($strPeriod) {
case 'day':
@@ -64,11 +63,12 @@ abstract class Piwik_Period
break;
default:
- throw new Exception(sprintf(self::$unknowPeriodException, $strPeriod));
+ throw new Exception(Piwik_TranslateException('General_ExceptionInvalidPeriod', array($strPeriod, self::$errorAvailablePeriods)));
break;
}
}
+
/**
* Returns the first day of the period
*
@@ -186,25 +186,6 @@ abstract class Piwik_Period
$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)
@@ -221,8 +202,7 @@ abstract class Piwik_Period
public function __toString()
{
- $elements = $this->toString();
- return implode(",", $elements);
+ return implode(",", $this->toString());
}
public function get( $part= null )
@@ -231,7 +211,7 @@ abstract class Piwik_Period
{
$this->generate();
}
- return $this->date->get($part);
+ return $this->date->toString($part);
}
abstract public function getPrettyString();
diff --git a/core/Period/Day.php b/core/Period/Day.php
index 32686a0f51..a5eda257ed 100644
--- a/core/Period/Day.php
+++ b/core/Period/Day.php
@@ -41,15 +41,6 @@ class Piwik_Period_Day extends Piwik_Period
return $out;
}
- public function isFinished()
- {
- $todayMidnight = Piwik_Date::today();
- if($this->date->isEarlier($todayMidnight))
- {
- return true;
- }
- }
-
public function getNumberOfSubperiods()
{
return 0;
diff --git a/core/Period/Month.php b/core/Period/Month.php
index 40fda847ca..1918ec3650 100644
--- a/core/Period/Month.php
+++ b/core/Period/Month.php
@@ -55,18 +55,4 @@ class Piwik_Period_Month extends Piwik_Period
$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
index ba30cb9f36..2f1b33bdad 100644
--- a/core/Period/Range.php
+++ b/core/Period/Range.php
@@ -18,11 +18,12 @@
*/
class Piwik_Period_Range extends Piwik_Period
{
- public function __construct( $strPeriod, $strDate )
+ public function __construct( $strPeriod, $strDate, $timezone = 'UTC' )
{
$this->strPeriod = $strPeriod;
$this->strDate = $strDate;
$this->defaultEndDate = null;
+ $this->timezone = $timezone;
}
public function getLocalizedShortString()
{
@@ -71,10 +72,6 @@ class Piwik_Period_Range extends Piwik_Period
case 'year':
$startDate = $date->subMonth( 12 * $n );
break;
-
- default:
- throw new Exception(sprintf(self::$unknowPeriodException, $this->strPeriod));
- break;
}
return $startDate;
}
@@ -125,7 +122,7 @@ class Piwik_Period_Range extends Piwik_Period
}
else
{
- $defaultEndDate = Piwik_Date::today();
+ $defaultEndDate = Piwik_Date::factory('now', $this->timezone);
}
if($lastOrPrevious == 'last')
{
@@ -154,7 +151,7 @@ class Piwik_Period_Range extends Piwik_Period
}
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'.");
+ throw new Exception(Piwik_TranslateException('General_ExceptionInvalidDateRange', array($this->strDate, ' \'lastN\', \'previousN\', \'YYYY-MM-DD,YYYY-MM-DD\'')));
}
$endSubperiod = Piwik_Period::factory($this->strPeriod, $endDate);
diff --git a/core/Period/Year.php b/core/Period/Year.php
index 7e04b8c898..19500be9ed 100644
--- a/core/Period/Year.php
+++ b/core/Period/Year.php
@@ -43,7 +43,7 @@ class Piwik_Period_Year extends Piwik_Period
}
parent::generate();
- $year = $this->date->get("Y");
+ $year = $this->date->toString("Y");
for($i=1; $i<=12; $i++)
{
$this->addSubperiod( new Piwik_Period_Month(
diff --git a/core/Piwik.php b/core/Piwik.php
index af7c5f083c..67fd9a24c4 100644
--- a/core/Piwik.php
+++ b/core/Piwik.php
@@ -10,69 +10,279 @@
* @package Piwik
*/
-// no direct access
-defined('PIWIK_INCLUDE_PATH') or die;
-
/**
* @see core/Translate.php
*/
require_once PIWIK_INCLUDE_PATH . '/core/Translate.php';
/**
+ * @see mysqli_set_charset
+ * @see parse_ini_file
+ */
+require_once PIWIK_INCLUDE_PATH . '/libs/upgradephp/common.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,
);
-
+
+/*
+ * Prefix/unprefix class name
+ */
+
+ /**
+ * Prefix class name (if needed)
+ *
+ * @param string $class
+ * @return string
+ */
+ static public function prefixClass( $class )
+ {
+ if(substr_count($class, Piwik::CLASSES_PREFIX) > 0)
+ {
+ return $class;
+ }
+ return Piwik::CLASSES_PREFIX.$class;
+ }
+
+ /**
+ * Unprefix class name (if needed)
+ *
+ * @param string $class
+ * @return string
+ */
+ static public function unprefixClass( $class )
+ {
+ $lenPrefix = strlen(Piwik::CLASSES_PREFIX);
+ if(substr($class, 0, $lenPrefix) == Piwik::CLASSES_PREFIX)
+ {
+ return substr($class, $lenPrefix);
+ }
+ return $class;
+ }
+
+/*
+ * Installation / Uninstallation
+ */
+
+ /**
+ * Installation helper
+ */
+ static public function install()
+ {
+ Piwik_Common::mkdir(Zend_Registry::get('config')->smarty->compile_dir);
+ }
+
+ /**
+ * Uninstallation helper
+ */
+ static public function uninstall()
+ {
+ Piwik_Db_Schema::getInstance()->dropTables();
+ }
+
+ /**
+ * Returns true if Piwik is installed
+ *
+ * @since 0.6.3
+ *
+ * @return bool True if installed; false otherwise
+ */
+ static public function isInstalled()
+ {
+ return Piwik_Db_Schema::getInstance()->hasTables();
+ }
+
+/*
+ * File and directory operations
+ */
+
+ /**
+ * Copy recursively from $source to $target.
+ *
+ * @param string $source eg. './tmp/latest'
+ * @param string $target eg. '.'
+ * @param bool $excludePhp
+ */
+ static public function copyRecursive($source, $target, $excludePhp=false )
+ {
+ if ( is_dir( $source ) )
+ {
+ @mkdir( $target );
+ $d = dir( $source );
+ while ( false !== ( $entry = $d->read() ) )
+ {
+ if ( $entry == '.' || $entry == '..' )
+ {
+ continue;
+ }
+
+ $sourcePath = $source . '/' . $entry;
+ if ( is_dir( $sourcePath ) )
+ {
+ self::copyRecursive( $sourcePath, $target . '/' . $entry, $excludePhp );
+ continue;
+ }
+ $destPath = $target . '/' . $entry;
+ self::copy($sourcePath, $destPath, $excludePhp);
+ }
+ $d->close();
+ }
+ else
+ {
+ self::copy($source, $target, $excludePhp);
+ }
+ }
+
+ /**
+ * Copy individual file from $source to $target.
+ *
+ * @param string $source eg. './tmp/latest/index.php'
+ * @param string $target eg. './index.php'
+ * @param bool $excludePhp
+ * @return bool
+ */
+ static public function copy($source, $dest, $excludePhp=false)
+ {
+ static $phpExtensions = array('php', 'tpl');
+
+ if($excludePhp)
+ {
+ $path_parts = pathinfo($source);
+ if(in_array($path_parts['extension'], $phpExtensions))
+ {
+ return true;
+ }
+ }
+
+ if(!@copy( $source, $dest ))
+ {
+ @chmod($dest, 0755);
+ if(!@copy( $source, $dest ))
+ {
+ throw new Exception("
+ Error while copying file to <code>$dest</code>. <br />
+ Please check that the web server has enough permission to overwrite this file. <br />
+ For example, on a linux server, if your apache user is www-data you can try to execute:<br />
+ <code>chown -R www-data:www-data ".Piwik_Common::getPathToPiwikRoot()."</code><br />
+ <code>chmod -R 0755 ".Piwik_Common::getPathToPiwikRoot()."</code><br />
+ ");
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Recursively delete a directory
+ *
+ * @param string $dir Directory name
+ * @param boolean $deleteRootToo Delete specified top-level directory as well
+ */
+ static public function unlinkRecursive($dir, $deleteRootToo)
+ {
+ if(!$dh = @opendir($dir))
+ {
+ return;
+ }
+ while (false !== ($obj = readdir($dh)))
+ {
+ if($obj == '.' || $obj == '..')
+ {
+ continue;
+ }
+
+ if (!@unlink($dir . '/' . $obj))
+ {
+ self::unlinkRecursive($dir.'/'.$obj, true);
+ }
+ }
+ closedir($dh);
+ if ($deleteRootToo)
+ {
+ @rmdir($dir);
+ }
+ return;
+ }
+
+ /**
+ * Recursively find pathnames that match a pattern
+ * @see glob()
+ *
+ * @param string $sDir directory
+ * @param string $sPattern pattern
+ * @param int $nFlags glob() flags
+ * @return array
+ */
+ public static function globr($sDir, $sPattern, $nFlags = NULL)
+ {
+ if(($aFiles = glob("$sDir/$sPattern", $nFlags)) == false)
+ {
+ $aFiles = array();
+ }
+ if(($aDirs = glob("$sDir/*", GLOB_ONLYDIR)) != false)
+ {
+ foreach ($aDirs as $sSubDir)
+ {
+ $aSubFiles = self::globr($sSubDir, $sPattern, $nFlags);
+ $aFiles = array_merge($aFiles, $aSubFiles);
+ }
+ }
+ return $aFiles;
+ }
+
/**
* Checks that the directories Piwik needs write access are actually writable
* Displays a nice error page if permissions are missing on some directories
+ *
+ * @param array $directoriesToCheck Array of directory names to check
*/
static public function checkDirectoriesWritableOrDie( $directoriesToCheck = null )
{
$resultCheck = Piwik::checkDirectoriesWritable( $directoriesToCheck );
- if( array_search(false, $resultCheck) !== false )
- {
- $directoryList = '';
- foreach($resultCheck as $dir => $bool)
+ if( array_search(false, $resultCheck) === false )
+ {
+ return;
+ }
+ $directoryList = '';
+ foreach($resultCheck as $dir => $bool)
+ {
+ $realpath = Piwik_Common::realpath($dir);
+ if(!empty($realpath) && $bool === false)
{
- $realpath = Piwik_Common::realpath($dir);
- if(!empty($realpath) && $bool === false)
- {
- $directoryList .= "<code>chmod 777 $realpath</code><br>";
- }
+ $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, false, true);
}
+ $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, false, true);
}
-
+
/**
* 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 )
+ if( $directoriesToCheck == null )
{
$directoriesToCheck = array(
'/config',
@@ -80,9 +290,9 @@ class Piwik
'/tmp/templates_c',
'/tmp/cache',
'/tmp/latest',
- );
+ );
}
-
+
$resultCheck = array();
foreach($directoriesToCheck as $directoryToCheck)
{
@@ -90,12 +300,12 @@ class Piwik
{
$directoryToCheck = PIWIK_USER_PATH . $directoryToCheck;
}
-
+
if(!file_exists($directoryToCheck))
{
Piwik_Common::mkdir($directoryToCheck, 0755, false);
}
-
+
$directory = Piwik_Common::realpath($directoryToCheck);
$resultCheck[$directory] = false;
if($directory !== false // realpath() returns FALSE on failure
@@ -106,29 +316,168 @@ class Piwik
}
return $resultCheck;
}
-
+
/**
- * Returns the Javascript code to be inserted on every page to track
+ * Generate .htaccess files at runtime to avoid permission problems.
+ */
+ static public function createHtAccessFiles()
+ {
+ // deny access to these folders
+ $directoriesToProtect = array(
+ '/config',
+ '/core',
+ '/lang',
+ );
+ foreach($directoriesToProtect as $directoryToProtect)
+ {
+ Piwik_Common::createHtAccess(PIWIK_INCLUDE_PATH . $directoryToProtect);
+ }
+
+ // more selective allow/deny filters
+ $allowAny = "<Files \"*\">\nAllow from all\nSatisfy any\n</Files>\n";
+ $allowStaticAssets = "<Files ~ \"\\.(test\.php|gif|ico|jpg|png|js|css|swf)$\">\nSatisfy any\nAllow from all\n</Files>\n";
+ $denyDirectPhp = "<Files ~ \"\\.(php|php4|php5|inc|tpl)$\">\nDeny from all\n</Files>\n";
+ $directoriesToProtect = array(
+ '/js' => $allowAny,
+ '/libs' => $denyDirectPhp . $allowStaticAssets,
+ '/plugins' => $denyDirectPhp . $allowStaticAssets,
+ '/themes' => $denyDirectPhp . $allowStaticAssets,
+ );
+ foreach($directoriesToProtect as $directoryToProtect => $content)
+ {
+ Piwik_Common::createHtAccess(PIWIK_INCLUDE_PATH . $directoryToProtect, $content);
+ }
+ }
+
+ /**
+ * Generate web.config files at runtime
*
- * @param int $idSite
- * @param string $piwikUrl http://path/to/piwik/directory/
- * @param string $actionName
- * @return string
+ * Note: for IIS 7 and above
*/
- static public function getJavascriptCode($idSite, $piwikUrl, $actionName = "''")
- {
- $jsTag = file_get_contents( PIWIK_INCLUDE_PATH . "/core/Tracker/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 createWebConfigFiles()
+ {
+ @file_put_contents(PIWIK_INCLUDE_PATH . '/web.config',
+'<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+ <system.webServer>
+ <security>
+ <requestFiltering>
+ <hiddenSegments>
+ <add segment="config" />
+ <add segment="core" />
+ <add segment="lang" />
+ </hiddenSegments>
+ <fileExtensions>
+ <add fileExtension=".tpl" allowed="false" />
+ </fileExtensions>
+ </requestFiltering>
+ </security>
+ <directoryBrowse enabled="false" />
+ <defaultDocument>
+ <files>
+ <remove value="index.php" />
+ <add value="index.php" />
+ </files>
+ </defaultDocument>
+ </system.webServer>
+</configuration>');
+
+ // deny direct access to .php files
+ $directoriesToProtect = array(
+ '/libs',
+ '/plugins',
+ );
+ foreach($directoriesToProtect as $directoryToProtect)
+ {
+ @file_put_contents(PIWIK_INCLUDE_PATH . $directoryToProtect . '/web.config',
+'<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+ <system.webServer>
+ <security>
+ <requestFiltering>
+ <denyUrlSequences>
+ <add sequence=".php" />
+ </denyUrlSequences>
+ </requestFiltering>
+ </security>
+ </system.webServer>
+</configuration>');
+ }
}
/**
+ * Get file integrity information (in PIWIK_INCLUDE_PATH).
+ *
+ * @return array(bool, string, ...) Return code (true/false), followed by zero or more error messages
+ */
+ static public function getFileIntegrityInformation()
+ {
+ $exclude = array(
+ 'robots.txt',
+ );
+ $messages = array();
+ $messages[] = true;
+
+ // ignore dev environments
+ if(file_exists(PIWIK_INCLUDE_PATH . '/.svn'))
+ {
+ $messages[] = Piwik_Translate('General_WarningFileIntegritySkipped');
+ return $messages;
+ }
+
+ $manifest = PIWIK_INCLUDE_PATH . '/config/manifest.inc.php';
+ if(!file_exists($manifest))
+ {
+ $messages[] = Piwik_Translate('General_WarningFileIntegrityNoManifest');
+ return $messages;
+ }
+
+ require_once $manifest;
+
+ $files = Manifest::$files;
+
+ $hasMd5file = function_exists('md5_file');
+ foreach($files as $path => $props)
+ {
+ if(in_array($path, $exclude))
+ {
+ continue;
+ }
+
+ $file = PIWIK_INCLUDE_PATH . '/' . $path;
+
+ if(!file_exists($file))
+ {
+ $messages[] = Piwik_Translate('General_ExceptionMissingFile', $file);
+ }
+ else if(filesize($file) != $props[0])
+ {
+ $messages[] = Piwik_Translate('General_ExceptionFilesizeMismatch', array($file, $props[0], filesize($file)));
+ }
+ else if($hasMd5file && (@md5_file($file) !== $props[1]))
+ {
+ $messages[] = Piwik_Translate('General_ExceptionFileIntegrity', $file);
+ }
+ }
+
+ if(count($messages) > 1)
+ {
+ $messages[0] = false;
+ }
+
+ if(!$hasMd5file)
+ {
+ $messages[] = Piwik_Translate('General_WarningFileIntegrityNoMd5file');
+ }
+
+ return $messages;
+ }
+
+/*
+ * PHP environment settings
+ */
+
+ /**
* Set maximum script execution time.
*
* @param int max execution time in seconds (0 = no limit)
@@ -140,15 +489,46 @@ class Piwik
@set_time_limit($executionTime);
}
+ /**
+ * Get php memory_limit
+ *
+ * Prior to PHP 5.2.1, or on Windows, --enable-memory-limit is not a
+ * compile-time default, so ini_get('memory_limit') may return false.
+ *
+ * @see http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes
+ * @return int memory limit in megabytes
+ */
static public function getMemoryLimitValue()
{
if($memory = ini_get('memory_limit'))
{
- return substr($memory, 0, strlen($memory) - 1);
+ // handle shorthand byte options (case-insensitive)
+ $shorthandByteOption = substr($memory, -1);
+ switch($shorthandByteOption)
+ {
+ case 'G':
+ case 'g':
+ return substr($memory, 0, -1) * 1024;
+ case 'M':
+ case 'm':
+ return substr($memory, 0, -1);
+ case 'K':
+ case 'k':
+ return substr($memory, 0, -1) / 1024;
+ }
+ return $memory / 1048576;
}
return false;
}
-
+
+ /**
+ * Set PHP memory limit
+ *
+ * Note: system settings may prevent scripts from overriding the master value
+ *
+ * @param int $minimumMemoryLimit
+ * @return bool true if set; false otherwise
+ */
static public function setMemoryLimit($minimumMemoryLimit)
{
$currentValue = self::getMemoryLimitValue();
@@ -160,7 +540,12 @@ class Piwik
}
return false;
}
-
+
+ /**
+ * Raise PHP memory limit if below the minimum required
+ *
+ * @return bool true if set; false otherwise
+ */
static public function raiseMemoryLimitIfNecessary()
{
$minimumMemoryLimit = Zend_Registry::get('config')->General->minimum_memory_limit;
@@ -170,22 +555,25 @@ class Piwik
{
return self::setMemoryLimit($minimumMemoryLimit);
}
-
+
return false;
}
-
+
+/*
+ * Logging and error handling
+ */
+
static public function log($message = '')
{
Zend_Registry::get('logger_message')->logEvent($message);
- Zend_Registry::get('logger_message')->logEvent( "<br>" . PHP_EOL);
+ Zend_Registry::get('logger_message')->logEvent( "<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
@@ -194,46 +582,54 @@ class Piwik
{
$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'>".
+ "<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;
}
-
+
+/*
+ * Profiling
+ */
+
/**
- * 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.
+ * Get total number of queries
*
- * @param numeric $i1
- * @param numeric $i2
- * @return numeric The result of the division or zero
+ * @return int number of queries
*/
- 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();
}
+
+ /**
+ * Get total elapsed time (in seconds)
+ *
+ * @return int elapsed time
+ */
static public function getDbElapsedSecs()
{
$profiler = Zend_Registry::get('db')->getProfiler();
return $profiler->getTotalElapsedSecs();
}
+
+ /**
+ * Print number of queries and elapsed time
+ */
static public function printQueryCount()
{
$totalTime = self::getDbElapsedSecs();
$queryCount = self::getQueryCount();
Piwik::log("Total queries = $queryCount (total sql time = ".round($totalTime,2)."s)");
}
-
+
+ /**
+ * Print profiling report for the tracker
+ *
+ * @param Piwik_Tracker_Db $db Tracker database object (or null)
+ */
static public function printSqlProfilingReportTracker( $db = null )
{
if(!function_exists('maxSumMsFirst'))
@@ -243,20 +639,20 @@ class Piwik
return $a['sum_time_ms'] < $b['sum_time_ms'];
}
}
-
+
if(is_null($db))
{
$db = Piwik_Tracker::getDatabase();
}
$tableName = Piwik_Common::prefixTable('log_profiling');
-
+
$all = $db->fetchAll('SELECT * FROM '.$tableName );
- if($all === false)
+ if($all === false)
{
return;
}
uasort($all, 'maxSumMsFirst');
-
+
$infoIndexedByQuery = array();
foreach($all as $infoQuery)
{
@@ -264,24 +660,23 @@ class Piwik
$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
+ * 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)
{
@@ -306,9 +701,9 @@ class Piwik
}
}
uasort( $infoIndexedByQuery, 'sortTimeDesc');
-
- Piwik::log('<hr><b>SQL Profiler</b>');
- Piwik::log('<hr><b>Summary</b>');
+
+ Piwik::log('<hr /><b>SQL Profiler</b>');
+ Piwik::log('<hr /><b>Summary</b>');
$totalTime = $profiler->getTotalElapsedSecs();
$queryCount = $profiler->getTotalNumQueries();
$longestTime = 0;
@@ -321,44 +716,63 @@ class Piwik
}
$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";
+ $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);
}
-
+
+ /**
+ * Log a breakdown by query
+ *
+ * @param array $infoIndexedByQuery
+ */
static private function getSqlProfilingQueryBreakdownOutput( $infoIndexedByQuery )
{
- Piwik::log('<hr><b>Breakdown by query</b>');
+ Piwik::log('<hr /><b>Breakdown by query</b>');
$output = '';
- foreach($infoIndexedByQuery as $query => $queryInfo)
+ foreach($infoIndexedByQuery as $query => $queryInfo)
{
$timeMs = round($queryInfo['sumTimeMs'],1);
$count = $queryInfo['count'];
$avgTimeString = '';
- if($count > 1)
+ if($count > 1)
{
$avgTimeMs = $timeMs / $count;
- $avgTimeString = " (average = <b>". round($avgTimeMs,1) . "ms</b>)";
+ $avgTimeString = " (average = <b>". round($avgTimeMs,1) . "ms</b>)";
}
$query = preg_replace('/([\t\n\r ]+)/', ' ', $query);
$output .= "Executed <b>$count</b> time". ($count==1?'':'s') ." in <b>".$timeMs."ms</b> $avgTimeString <pre>\t$query</pre>";
}
Piwik::log($output);
}
-
+
+ /**
+ * Print timer
+ */
static public function printTimer()
{
echo Zend_Registry::get('timer');
}
- static public function printMemoryLeak($prefix = '', $suffix = '<br>')
+ /**
+ * Print memory leak
+ *
+ * @param string $prefix
+ * @param string $suffix
+ */
+ static public function printMemoryLeak($prefix = '', $suffix = '<br />')
{
echo $prefix;
echo Zend_Registry::get('timer')->getMemoryLeak();
echo $suffix;
}
-
+
+ /**
+ * Print memory usage
+ *
+ * @param string $prefixString
+ */
static public function printMemoryUsage( $prefixString = null )
{
$memory = false;
@@ -370,7 +784,7 @@ class Piwik
{
$memory = memory_get_usage();
}
-
+
if($memory !== false)
{
$usage = round( $memory / 1024 / 1024, 2);
@@ -385,385 +799,220 @@ class Piwik
Piwik::log("Memory usage function not found.");
}
}
-
- static public function getPrettySizeFromBytes($size)
+
+/*
+ * Amounts, Percentages, Currency, Time, Math Operations, and Pretty Printing
+ */
+
+ /**
+ * 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 )
{
- $bytes = array('','K','M','G','T');
- foreach($bytes as $val)
+ if ( is_numeric($i1) && is_numeric($i2) && floatval($i2) != 0)
{
- if($size > 1024)
- {
- $size = $size / 1024;
- }
- else
- {
- break;
- }
+ return $i1 / $i2;
}
- return round($size, 1)." ".$val;
+ return 0;
}
/**
- * Returns true if PHP was invoked as CGI or command-line interface (shell)
+ * Safely compute a percentage. Return 0 to avoid division by zero.
*
- * @deprecated deprecated in 0.4.4
- * @see Piwik_Common::isPhpCliMode()
- * @return bool true if PHP invoked as a CGI or from CLI
+ * @param numeric $dividend
+ * @param numeric $divisor
+ * @param int $precision
+ * @return numeric
*/
- static public function isPhpCliMode()
+ static public function getPercentageSafe($dividend, $divisor, $precision = 0)
{
- return Piwik_Common::isPhpCliMode();
+ if($divisor == 0)
+ {
+ return 0;
+ }
+ return round(100 * $dividend / $divisor, $precision);
}
-
- static public function getCurrency()
+
+ /**
+ * Get currency symbol for a site
+ *
+ * @param int $idSite
+ * @return string
+ */
+ static public function getCurrency($idSite)
{
- static $symbol = null;
- if(is_null($symbol))
+ static $symbols = null;
+ if(is_null($symbols))
{
- $symbol = trim(Zend_Registry::get('config')->General->default_currency);
+ $symbols = Piwik_SitesManager_API::getInstance()->getCurrencySymbols();
}
- return $symbol;
+ $site = new Piwik_Site($idSite);
+ return $symbols[$site->getCurrency()];
}
- static public function getPrettyMoney($value)
+ /**
+ * Pretty format monetary value for a site
+ *
+ * @param numeric $value
+ * @param int $idSite
+ * @return string
+ */
+ static public function getPrettyMoney($value, $idSite)
{
- $symbol = self::getCurrency();
- return sprintf("$symbol%.2f", $value);
+ $currencyBefore = self::getCurrency($idSite);
+ $currencyAfter = '';
+
+ // manually put the currency symbol after the amount for euro
+ // (maybe more currencies prefer this notation?)
+ if(in_array($currencyBefore,array('€')))
+ {
+ $currencyAfter = '&nbsp;'.$currencyBefore;
+ $currencyBefore = '';
+ }
+ return sprintf("$currencyBefore&nbsp;%s$currencyAfter", $value);
}
-
- static public function getPercentageSafe($dividend, $divisor, $precision = 0)
+
+ /**
+ * Pretty format a memory size value
+ *
+ * @param numeric $size in bytes
+ * @return string
+ */
+ static public function getPrettySizeFromBytes($size)
{
- if($divisor == 0)
+ $bytes = array('','K','M','G','T');
+ foreach($bytes as $val)
{
- return 0;
+ if($size > 1024)
+ {
+ $size = $size / 1024;
+ }
+ else
+ {
+ break;
+ }
}
- return round(100 * $dividend / $divisor, $precision);
+ return round($size, 1)." ".$val;
}
-
+
+ /**
+ * Pretty format a time
+ *
+ * @param numeric $numberOfSeconds
+ * @return string
+ */
static public function getPrettyTimeFromSeconds($numberOfSeconds)
{
$numberOfSeconds = (double)$numberOfSeconds;
$days = floor($numberOfSeconds / 86400);
-
+
$minusDays = $numberOfSeconds - $days * 86400;
$hours = floor($minusDays / 3600);
-
+
$minusDaysAndHours = $minusDays - $hours * 3600;
$minutes = floor($minusDaysAndHours / 60 );
-
+
$seconds = $minusDaysAndHours - $minutes * 60;
-
+
if($days > 0)
{
- return sprintf("%d days %d hours", $days, $hours);
+ $return = sprintf(Piwik_Translate('General_DaysHours'), $days, $hours);
}
elseif($hours > 0)
{
- return sprintf("%d hours %d min", $hours, $minutes);
+ $return = sprintf(Piwik_Translate('General_HoursMinutes'), $hours, $minutes);
}
elseif($minutes > 0)
{
- return sprintf("%d&nbsp;min&nbsp;%ds", $minutes, $seconds);
+ $return = sprintf(Piwik_Translate('General_MinutesSeconds'), $minutes, $seconds);
}
else
{
- return sprintf("%ds", $seconds);
+ $return = sprintf(Piwik_Translate('General_Seconds'), $seconds);
}
+ return str_replace(' ', '&nbsp;', $return);
}
-
- static public function getRandomTitle()
- {
- $titles = array( 'Web analytics',
- 'Analytics',
- 'Web analytics api',
- 'Open source analytics',
- 'Open source web analytics',
- 'Google Analytics alternative',
- 'open source Google Analytics',
- 'Free analytics',
- 'Analytics software',
- 'Free web analytics',
- 'Free web statistics',
- 'Web 2.0 analytics',
- 'Statistics web 2.0',
- );
- $id = abs(intval(md5(substr(Piwik_Url::getCurrentHost(),7))));
- $title = $titles[ $id % count($titles)];
- return $title;
- }
-
- static public function getTableCreateSql( $tableName )
+
+ /**
+ * 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 = "''")
{
- $tables = Piwik::getTablesCreateSql();
-
- if(!isset($tables[$tableName]))
- {
- throw new Exception("The table '$tableName' SQL creation code couldn't be found.");
- }
-
- return $tables[$tableName];
+ $jsTag = file_get_contents( PIWIK_INCLUDE_PATH . "/core/Tracker/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 getTablesCreateSql()
+
+ /**
+ * Generate a title for image tags
+ *
+ * @return string
+ */
+ static public function getRandomTitle()
{
- $config = Zend_Registry::get('config');
- $prefixTables = $config->database->tables_prefix;
- $tables = array(
- 'user' => "CREATE TABLE {$prefixTables}user (
- login VARCHAR(100) 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 DEFAULT CURRENT_TIMESTAMP NOT NULL,
- PRIMARY KEY(login),
- UNIQUE INDEX uniq_keytoken(token_auth)
- ) DEFAULT CHARSET=utf8
- ",
-
- 'access' => "CREATE TABLE {$prefixTables}access (
- login VARCHAR(100) NOT NULL,
- idsite INTEGER UNSIGNED NOT NULL,
- access VARCHAR(10) NULL,
- PRIMARY KEY(login, idsite)
- ) DEFAULT CHARSET=utf8
- ",
-
- '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 DEFAULT CURRENT_TIMESTAMP NOT NULL,
- PRIMARY KEY(idsite)
- ) DEFAULT CHARSET=utf8
- ",
-
- 'site_url' => "CREATE TABLE {$prefixTables}site_url (
- idsite INTEGER(10) UNSIGNED NOT NULL,
- url VARCHAR(255) NOT NULL,
- PRIMARY KEY(idsite, url)
- ) DEFAULT CHARSET=utf8
- ",
-
- 'goal' => " CREATE TABLE `{$prefixTables}goal` (
- `idsite` int(11) NOT NULL,
- `idgoal` int(11) NOT NULL,
- `name` varchar(50) NOT NULL,
- `match_attribute` varchar(20) NOT NULL,
- `pattern` varchar(255) NOT NULL,
- `pattern_type` varchar(10) NOT NULL,
- `case_sensitive` tinyint(4) NOT NULL,
- `revenue` float NOT NULL,
- `deleted` tinyint(4) NOT NULL default '0',
- PRIMARY KEY (`idsite`,`idgoal`)
- ) DEFAULT CHARSET=utf8
- ",
-
- '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)
- ) DEFAULT CHARSET=utf8
- ",
-
- '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 UNSIGNED NULL,
- timestamp TIMESTAMP NULL,
- returned_value TEXT NULL,
- PRIMARY KEY(idlogger_api_call)
- ) DEFAULT CHARSET=utf8
- ",
-
- '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)
- ) DEFAULT CHARSET=utf8
- ",
-
- '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)
- ) DEFAULT CHARSET=utf8
- ",
-
-
- 'log_action' => "CREATE TABLE {$prefixTables}log_action (
- idaction INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
- name VARCHAR(255) NOT NULL,
- hash INTEGER(10) UNSIGNED NOT NULL,
- type TINYINT UNSIGNED NULL,
- PRIMARY KEY(idaction),
- INDEX index_type_hash (type, hash)
- ) DEFAULT CHARSET=utf8
- ",
-
- '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_url INTEGER(11) NOT NULL,
- visit_entry_idaction_url INTEGER(11) NOT NULL,
- visit_total_actions SMALLINT(5) UNSIGNED NOT NULL,
- visit_total_time SMALLINT(5) UNSIGNED NOT NULL,
- visit_goal_converted TINYINT(1) 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_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_gears TINYINT(1) NOT NULL,
- config_silverlight TINYINT(1) NOT NULL,
- config_cookie TINYINT(1) NOT NULL,
- location_ip BIGINT UNSIGNED NOT NULL,
- location_browser_lang VARCHAR(20) NOT NULL,
- location_country CHAR(3) NOT NULL,
- location_continent CHAR(3) NOT NULL,
- PRIMARY KEY(idvisit),
- INDEX index_idsite_date (idsite, visit_server_date)
- ) DEFAULT CHARSET=utf8
- ",
-
- 'log_conversion' => "CREATE TABLE `{$prefixTables}log_conversion` (
- `idvisit` int(10) unsigned NOT NULL,
- `idsite` int(10) unsigned NOT NULL,
- `visitor_idcookie` char(32) NOT NULL,
- `server_time` datetime NOT NULL,
- `visit_server_date` date NOT NULL,
- `idaction_url` int(11) default NULL,
- `idlink_va` int(11) default NULL,
- `referer_idvisit` int(10) unsigned default NULL,
- `referer_visit_server_date` date default NULL,
- `referer_type` int(10) unsigned default NULL,
- `referer_name` varchar(70) default NULL,
- `referer_keyword` varchar(255) default NULL,
- `visitor_returning` tinyint(1) NOT NULL,
- `location_country` char(3) NOT NULL,
- `location_continent` char(3) NOT NULL,
- `url` text NOT NULL,
- `idgoal` int(10) unsigned NOT NULL,
- `revenue` float default NULL,
- PRIMARY KEY (`idvisit`,`idgoal`),
- KEY `index_idsite_date` (`idsite`,`visit_server_date`)
- ) DEFAULT CHARSET=utf8
- ",
-
- '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_url INTEGER(10) UNSIGNED NOT NULL,
- idaction_url_ref INTEGER(10) UNSIGNED NOT NULL,
- idaction_name INTEGER(10) UNSIGNED,
- time_spent_ref_action INTEGER(10) UNSIGNED NOT NULL,
- PRIMARY KEY(idlink_va),
- INDEX index_idvisit(idvisit)
- ) DEFAULT CHARSET=utf8
- ",
-
- '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))
- ) DEFAULT CHARSET=utf8
- ",
-
- 'option' => "CREATE TABLE `{$prefixTables}option` (
- option_name VARCHAR( 64 ) NOT NULL ,
- option_value LONGTEXT NOT NULL ,
- autoload TINYINT NOT NULL DEFAULT '1',
- PRIMARY KEY ( option_name )
- ) DEFAULT CHARSET=utf8
- ",
-
- '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),
- KEY `index_all` (`idsite`,`date1`,`date2`,`name`,`ts_archived`)
- ) DEFAULT CHARSET=utf8
- ",
- '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),
- KEY `index_all` (`idsite`,`date1`,`date2`,`name`,`ts_archived`)
- ) DEFAULT CHARSET=utf8
- ",
+ static $titles = array(
+ 'Web analytics',
+ 'Analytics',
+ 'Real time web analytics',
+ 'Real time analytics',
+ 'Open source analytics',
+ 'Open source web analytics',
+ 'Google Analytics alternative',
+ 'Open source Google Analytics',
+ 'Free analytics',
+ 'Analytics software',
+ 'Free web analytics',
+ 'Free web statistics',
);
- return $tables;
+ $id = abs(intval(md5(Piwik_Url::getCurrentHost())));
+ $title = $titles[ $id % count($titles)];
+ return $title;
}
-
+
+/*
+ * Access
+ */
+
+ /**
+ * Get current user login
+ *
+ * @return string
+ */
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
+ * Get current user's token auth
*
- * @return Piwik_Plugin
+ * @return string
*/
- static public function getCurrentPlugin()
+ static public function getCurrentUserTokenAuth()
{
- return Piwik_PluginsManager::getInstance()->getLoadedPlugin(Piwik::getModule());
+ return Zend_Registry::get('access')->getTokenAuth();
}
-
+
/**
* Returns true if the current user is either the super user, or the user $theUser
* Used when modifying user preference: this usually requires super user or being the user itself.
- *
+ *
* @param string $theUser
* @return bool
*/
@@ -776,8 +1025,10 @@ class Piwik
return false;
}
}
-
+
/**
+ * Check that current user is either the specified user or the superuser
+ *
* @param string $theUser
* @throws exception if the user is neither the super user nor the user $theUser
*/
@@ -793,9 +1044,10 @@ class Piwik
throw new Piwik_Access_NoAccessException("The user has to be either the Super User or the user '$theUser' itself.");
}
}
-
+
/**
* Returns true if the current user is the Super User
+ *
* @return bool
*/
static public function isUserIsSuperUser()
@@ -807,21 +1059,32 @@ class Piwik
return false;
}
}
-
+
/**
* Helper method user to set the current as Super User.
* This should be used with great care as this gives the user all permissions.
*/
- static public function setUserIsSuperUser()
+ static public function setUserIsSuperUser( $bool = true )
{
- Zend_Registry::get('access')->setSuperUser();
+ Zend_Registry::get('access')->setSuperUser($bool);
}
-
+
+ /**
+ * Check that user is the superuser
+ *
+ * @throws Exception if not the superuser
+ */
static public function checkUserIsSuperUser()
{
Zend_Registry::get('access')->checkUserIsSuperUser();
}
-
+
+ /**
+ * Returns true if the user has admin access to the sites
+ *
+ * @param mixed $idSites
+ * @return bool
+ */
static public function isUserHasAdminAccess( $idSites )
{
try{
@@ -831,12 +1094,23 @@ class Piwik
return false;
}
}
-
+
+ /**
+ * Check user has admin access to the sites
+ *
+ * @param mixed $idSites
+ * @throws Exception if user doesn't have admin access to the sites
+ */
static public function checkUserHasAdminAccess( $idSites )
{
Zend_Registry::get('access')->checkUserHasAdminAccess( $idSites );
}
-
+
+ /**
+ * Returns true if the user has admin access to any sites
+ *
+ * @return bool
+ */
static public function isUserHasSomeAdminAccess()
{
try{
@@ -846,17 +1120,23 @@ class Piwik
return false;
}
}
-
+
+ /**
+ * Check user has admin access to any sites
+ *
+ * @throws Exception if user doesn't have admin access to any sites
+ */
static public function checkUserHasSomeAdminAccess()
{
Zend_Registry::get('access')->checkUserHasSomeAdminAccess();
}
-
- static public function checkUserHasSomeViewAccess()
- {
- Zend_Registry::get('access')->checkUserHasSomeViewAccess();
- }
-
+
+ /**
+ * Returns true if the user has view access to the sites
+ *
+ * @param mixed $idSites
+ * @return bool
+ */
static public function isUserHasViewAccess( $idSites )
{
try{
@@ -866,635 +1146,139 @@ class Piwik
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
+ * Check user has view access to the sites
*
- * @return string
+ * @param mixed $idSites
+ * @throws Exception if user doesn't have view access to sites
*/
- 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 = '' )
+ static public function checkUserHasViewAccess( $idSites )
{
- $currentModule = self::getModule();
- $currentAction = self::getAction();
-
- if($currentModule != $newModule
- || $currentAction != $newAction )
- {
-
- $newUrl = 'index.php' . Piwik_Url::getCurrentQueryStringWithParametersModified(
- array('module' => $newModule, 'action' => $newAction)
- );
-
- Piwik_Url::redirectToUrl($newUrl);
- }
- return false;
+ Zend_Registry::get('access')->checkUserHasViewAccess( $idSites );
}
/**
- * Get "best" available transport method for sendHttpRequest() calls.
+ * Returns true if the user has view access to any sites
+ *
+ * @return bool
*/
- static public function getTransportMethod()
+ static public function isUserHasSomeViewAccess()
{
- $method = 'curl';
- if(!extension_loaded('curl'))
- {
- $method = 'stream';
- if(@ini_get('allow_url_fopen') != '1')
- {
- $method = 'socket';
- if(preg_match('/(^|,|\s)fsockopen($|,|\s)/', @ini_get('disable_functions')))
- {
- return null;
- }
- }
+ try{
+ self::checkUserHasSomeViewAccess();
+ return true;
+ } catch( Exception $e){
+ return false;
}
- return $method;
}
/**
- * Sends http request ensuring the request will fail before $timeout seconds
+ * Check user has view access to any sites
*
- * If no $destinationPath is specified, the trimmed response (without header) is returned as a string.
- * If a $destinationPath is specified, the response (without header) is saved to a file.
- *
- * @param string $aUrl
- * @param int $timeout
- * @param string $userAgent
- * @param string $destinationPath
- * @param int $followDepth
- * @return true (or string) on success; false on HTTP response error code (1xx or 4xx); throws exception on all other errors
+ * @throws Exception if user doesn't have view access to any sites
*/
- static public function sendHttpRequest($aUrl, $timeout, $userAgent = null, $destinationPath = null, $followDepth = 0)
+ static public function checkUserHasSomeViewAccess()
{
- // create output file
- $file = null;
- if($destinationPath)
- {
- if (($file = @fopen($destinationPath, 'wb')) === false || !is_resource($file))
- {
- throw new Exception('Error while creating the file: ' . $destinationPath);
- }
- }
-
- return self::sendHttpRequestBy(self::getTransportMethod(), $aUrl, $timeout, $userAgent, $destinationPath, $file, $followDepth);
+ Zend_Registry::get('access')->checkUserHasSomeViewAccess();
}
- static public function sendHttpRequestBy($method = 'socket', $aUrl, $timeout, $userAgent = null, $destinationPath = null, $file = null, $followDepth = 0)
- {
- if ($followDepth > 3)
- {
- throw new Exception('Too many redirects ('.$followDepth.')');
- }
-
- $contentLength = 0;
-
- if($method == 'socket')
- {
- // initialization
- $url = @parse_url($aUrl);
- if($url === false || !isset($url['scheme']))
- {
- throw new Exception('Malformed URL: '.$aUrl);
- }
-
- if($url['scheme'] != 'http')
- {
- throw new Exception('Invalid protocol/scheme: '.$url['scheme']);
- }
- $host = $url['host'];
- $port = isset($url['port)']) ? $url['port'] : 80;
- $path = isset($url['path']) ? $url['path'] : '/';
- if(isset($url['query']))
- {
- $path .= '?'.$url['query'];
- }
- $errno = null;
- $errstr = null;
-
- // connection attempt
- if (($fsock = @fsockopen($host, $port, $errno, $errstr, $timeout)) === false || !is_resource($fsock))
- {
- if(is_resource($file)) { @fclose($file); }
- throw new Exception("Error while connecting to: $host. Please try again later. $errstr");
- }
-
- // send HTTP request header
- fwrite($fsock,
- "GET $path HTTP/1.0\r\n"
- ."Host: $host".($port != 80 ? ':'.$port : '')."\r\n"
- ."User-Agent: Piwik/".Piwik_Version::VERSION.($userAgent ? " $userAgent" : '')."\r\n"
- .'Referer: http://'.Piwik_Common::getIpString()."/\r\n"
- ."Connection: close\r\n"
- ."\r\n"
- );
-
- $streamMetaData = array('timed_out' => false);
- @stream_set_blocking($fsock, true);
- @stream_set_timeout($fsock, $timeout);
-
- // process header
- $status = null;
- $expectRedirect = false;
- $fileLength = 0;
-
- while (!feof($fsock))
- {
- $line = fgets($fsock, 4096);
-
- $streamMetaData = @stream_get_meta_data($fsock);
- if($streamMetaData['timed_out'])
- {
- if(is_resource($file)) { @fclose($file); }
- @fclose($fsock);
- throw new Exception('Timed out waiting for server response');
- }
-
- // a blank line marks the end of the server response header
- if(rtrim($line, "\r\n") == '')
- {
- break;
- }
-
- // parse first line of server response header
- if(!$status)
- {
- // expect first line to be HTTP response status line, e.g., HTTP/1.1 200 OK
- if(!preg_match('~^HTTP/(\d\.\d)\s+(\d+)(\s*.*)?~', $line, $m))
- {
- if(is_resource($file)) { @fclose($file); }
- @fclose($fsock);
- throw new Exception('Expected server response code. Got '.rtrim($line, "\r\n"));
- }
-
- $status = (integer) $m[2];
-
- // Informational 1xx or Client Error 4xx
- if ($status < 200 || $status >= 400)
- {
- if(is_resource($file)) { @fclose($file); }
- @fclose($s);
- return false;
- }
-
- continue;
- }
-
- // handle redirect
- if(preg_match('/^Location:\s*(.+)/', rtrim($line, "\r\n"), $m))
- {
- if(is_resource($file)) { @fclose($file); }
- @fclose($s);
- // Successful 2xx vs Redirect 3xx
- if($status < 300)
- {
- throw new Exception('Unexpected redirect to Location: '.rtrim($line).' for status code '.$status);
- }
- return self::sendHttpRequest(trim($m[1]), $pathDestination, $tries+1);
- }
-
- // save expected content length for later verification
- if(preg_match('/^Content-Length:\s*(\d+)/', $line, $m))
- {
- $contentLength = (integer) $m[1];
- }
- }
-
- if(feof($fsock))
- {
- throw new Exception('Unexpected end of transmission');
- }
-
- // process content/body
- $response = '';
-
- while (!feof($fsock))
- {
- $line = fread($fsock, 8192);
-
- $streamMetaData = @stream_get_meta_data($fsock);
- if($streamMetaData['timed_out'])
- {
- if(is_resource($file)) { @fclose($file); }
- @fclose($fsock);
- throw new Exception('Timed out waiting for server response');
- }
-
- if(is_resource($file))
- {
- // save to file
- $fileLength += fwrite($file, $line);
- }
- else
- {
- // concatenate to response string
- $response .= $line;
- }
- }
-
- // determine success or failure
- @fclose(@$fsock);
- }
- else if($method == 'stream')
- {
- $response = false;
-
- // we make sure the request takes less than a few seconds to fail
- // we create a stream_context (works in php >= 5.2.1)
- // we also set the socket_timeout (for php < 5.2.1)
- $default_socket_timeout = @ini_get('default_socket_timeout');
- @ini_set('default_socket_timeout', $timeout);
-
- $ctx = null;
- if(function_exists('stream_context_create')) {
- $stream_options = array(
- 'http' => array(
- 'header' => 'User-Agent: Piwik/'.Piwik_Version::VERSION.($userAgent ? " $userAgent" : '')."\r\n"
- .'Referer: http://'.Piwik_Common::getIpString()."/\r\n",
- 'max_redirects' => 3, // PHP 5.1.0
- 'timeout' => $timeout, // PHP 5.2.1
- )
- );
- $ctx = stream_context_create($stream_options);
- }
-
- $response = @file_get_contents($aUrl, 0, $ctx);
- if(is_resource($file))
- {
- // save to file
- fwrite($file, $response);
- }
-
- // restore the socket_timeout value
- if(!empty($default_socket_timeout))
- {
- @ini_set('default_socket_timeout', $default_socket_timeout);
- }
- }
- else if($method == 'curl')
- {
- $ch = @curl_init();
-
- $curl_options = array(
- CURLOPT_URL => $aUrl,
- CURLOPT_HEADER => false,
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_TIMEOUT => $timeout,
- CURLOPT_BINARYTRANSFER => is_resource($file),
- CURLOPT_FOLLOWLOCATION => true,
- CURLOPT_MAXREDIRS => 3,
- CURLOPT_USERAGENT => 'Piwik/'.Piwik_Version::VERSION.($userAgent ? " $userAgent" : ''),
- CURLOPT_REFERER => 'http://'.Piwik_Common::getIpString(),
- );
- @curl_setopt_array($ch, $curl_options);
-
- $response = @curl_exec($ch);
- if(is_resource($file))
- {
- // save to file
- fwrite($file, $response);
- }
-
- @curl_close($ch);
- unset($ch);
- }
- else
- {
- throw new Exception('Invalid request method: '.$method);
- }
-
- if(is_resource($file))
- {
- fflush($file);
- @fclose($file);
- if($contentLength && (($fileLength != $contentLength) || (filesize($destinationPath) != $contentLength)))
- {
- throw new Exception('File size error: '.$destinationPath.'; expected '.$contentLength.' bytes; received '.$fileLength.' bytes');
- }
- return true;
- }
-
- if($contentLength && strlen($response) != $contentLength)
- {
- throw new Exception('Content length error: expected '.$contentLength.' bytes; received '.$fileLength.' bytes');
- }
- return trim($response);
- }
+/*
+ * Current module, action, plugin
+ */
/**
- * Fetch the file at $url in the destination $pathDestination
- * @param string $url
- * @param string $pathDestination
- * @param int $tries
- * @return true on success, throws Exception on failure
+ * Returns the name of the Login plugin currently being used.
+ * Must be used since it is not allowed to hardcode 'Login' in URLs
+ * in case another Login plugin is being used.
+ *
+ * @return string
*/
- static public function fetchRemoteFile($url, $pathDestination, $tries = 0)
+ static public function getLoginPluginName()
{
- return self::sendHttpRequest($url, 10, 'Update', $pathDestination, $tries);
+ return Zend_Registry::get('auth')->getName();
}
/**
- * Recursively delete a directory
+ * Returns the plugin currently being used to display the page
*
- * @param string $dir Directory name
- * @param boolean $deleteRootToo Delete specified top-level directory as well
- */
- static public function unlinkRecursive($dir, $deleteRootToo)
- {
- if(!$dh = @opendir($dir))
- {
- return;
- }
- while (false !== ($obj = readdir($dh)))
- {
- if($obj == '.' || $obj == '..')
- {
- continue;
- }
-
- if (!@unlink($dir . '/' . $obj))
- {
- self::unlinkRecursive($dir.'/'.$obj, true);
- }
- }
- closedir($dh);
- if ($deleteRootToo)
- {
- @rmdir($dir);
- }
- return;
- }
-
- /**
- * Copy recursively from $source to $target.
- *
- * @param string $source eg. './tmp/latest'
- * @param string $target eg. '.'
- * @param bool $excludePhp
+ * @return Piwik_Plugin
*/
- static public function copyRecursive($source, $target, $excludePhp=false )
+ static public function getCurrentPlugin()
{
- if ( is_dir( $source ) )
- {
- @mkdir( $target );
- $d = dir( $source );
- while ( false !== ( $entry = $d->read() ) )
- {
- if ( $entry == '.' || $entry == '..' )
- {
- continue;
- }
-
- $sourcePath = $source . '/' . $entry;
- if ( is_dir( $sourcePath ) )
- {
- self::copyRecursive( $sourcePath, $target . '/' . $entry, $excludePhp );
- continue;
- }
- $destPath = $target . '/' . $entry;
- self::copy($sourcePath, $destPath, $excludePhp);
- }
- $d->close();
- }
- else
- {
- self::copy($source, $target, $excludePhp);
- }
+ return Piwik_PluginsManager::getInstance()->getLoadedPlugin(Piwik::getModule());
}
-
+
/**
- * Copy individual file from $source to $target.
- *
- * @param string $source eg. './tmp/latest/index.php'
- * @param string $target eg. './index.php'
- * @param bool $excludePhp
- * @return bool
+ * Returns the current module read from the URL (eg. 'API', 'UserSettings', etc.)
+ *
+ * @return string
*/
- static public function copy($source, $dest, $excludePhp=false)
+ static public function getModule()
{
- static $phpExtensions = array('php', 'tpl');
-
- if($excludePhp)
- {
- $path_parts = pathinfo($source);
- if(in_array($path_parts['extension'], $phpExtensions))
- {
- return true;
- }
- }
-
- if(!@copy( $source, $dest ))
- {
- @chmod($dest, 0755);
- if(!@copy( $source, $dest ))
- {
- throw new Exception("
- Error while copying file to <code>$dest</code>. <br />
- Please check that the web server has enough permission to overwrite this file. <br/>
- For example, on a linux server, if your apache user is www-data you can try to execute:<br>
- <code>chown -R www-data:www-data ".Piwik_Common::getPathToPiwikRoot()."</code><br>
- <code>chmod -R 0755 ".Piwik_Common::getPathToPiwikRoot()."</code><br>
- ");
- }
- }
- return true;
+ return Piwik_Common::getRequestVar('module', '', 'string');
}
/**
- * Recursively find pathnames that match a pattern
- * @see glob()
+ * Returns the current action read from the URL
*
- * @param string $sDir directory
- * @param string $sPattern pattern
- * @param int $nFlags glob() flags
- * @return array
+ * @return string
*/
- public static function globr($sDir, $sPattern, $nFlags = NULL)
+ static public function getAction()
{
- if(($aFiles = glob("$sDir/$sPattern", $nFlags)) == false)
- {
- $aFiles = array();
- }
- if(($aDirs = glob("$sDir/*", GLOB_ONLYDIR)) != false)
- {
- foreach ($aDirs as $sSubDir)
- {
- $aSubFiles = self::globr($sSubDir, $sPattern, $nFlags);
- $aFiles = array_merge($aFiles, $aSubFiles);
- }
- }
- return $aFiles;
+ return Piwik_Common::getRequestVar('action', '', 'string');
}
/**
- * API was simplified in 0.2.27, but we maintain backward compatibility
- * when calling Piwik::prefixTable
+ * Redirect to module (and action)
*
- * @deprecated as of 0.2.27
+ * @param string $newModule
+ * @param string $newAction
+ * @return bool false if the URL to redirect to is already this URL
*/
- static public function prefixTable( $table )
+ static public function redirectToModule( $newModule, $newAction = '', $parameters = array() )
{
- return Piwik_Common::prefixTable($table);
+ $newUrl = 'index.php' . Piwik_Url::getCurrentQueryStringWithParametersModified(
+ array('module' => $newModule, 'action' => $newAction)
+ + $parameters
+ );
+ Piwik_Url::redirectToUrl($newUrl);
}
-
+
+/*
+ * Global database object
+ */
+
/**
- * Names of all the prefixed tables in piwik
- * Doesn't use the DB
+ * Create database object and connect to database
*/
- 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, $idSite = null)
- {
- 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)
- $idSiteInSql = "no";
- if(!is_null($idSite))
- {
- $idSiteInSql = $idSite;
- }
- $allArchiveNumeric = $db->fetchCol("/* SHARDING_ID_SITE = ".$idSiteInSql." */
- SHOW TABLES LIKE '".$prefixTables."archive_numeric%'");
- $allArchiveBlob = $db->fetchCol("/* SHARDING_ID_SITE = ".$idSiteInSql." */
- SHOW TABLES LIKE '".$prefixTables."archive_blob%'");
-
- $allTablesReallyInstalled = array_merge($tablesInstalled, $allArchiveNumeric, $allArchiveBlob);
-
- self::$tablesInstalled = $allTablesReallyInstalled;
- }
- return self::$tablesInstalled;
- }
-
- static public function createDatabase( $dbName = null )
- {
- if(is_null($dbName))
- {
- $dbName = Zend_Registry::get('config')->database->dbname;
- }
- Piwik_Exec("CREATE DATABASE IF NOT EXISTS ".$dbName);
- }
-
- static public function dropDatabase()
- {
- $dbName = Zend_Registry::get('config')->database->dbname;
- Piwik_Exec("DROP DATABASE IF EXISTS " . $dbName);
- }
-
static public function createDatabaseObject( $dbInfos = null )
{
$config = Zend_Registry::get('config');
-
+
if(is_null($dbInfos))
{
$dbInfos = $config->database->toArray();
}
-
+
$dbInfos['profiler'] = $config->Debug->enable_sql_profiler;
-
+
$db = null;
Piwik_PostEvent('Reporting.createDatabase', $db);
if(is_null($db))
{
- if($dbInfos['port'][0] == '/')
- {
- $dbInfos['unix_socket'] = $dbInfos['port'];
- unset($dbInfos['host']);
- unset($dbInfos['port']);
- }
-
- // not used by Zend Framework
- unset($dbInfos['tables_prefix']);
- unset($dbInfos['adapter']);
-
- $db = Piwik_Db::factory($config->database->adapter, $dbInfos);
- $db->getConnection();
-
- Zend_Db_Table::setDefaultAdapter($db);
- $db->resetConfig(); // we don't want this information to appear in the logs
+ $adapter = $dbInfos['adapter'];
+ $db = Piwik_Db_Adapter::factory($adapter, $dbInfos);
}
Zend_Registry::set('db', $db);
}
-
- static public function disconnectDatabase()
- {
- Zend_Registry::get('db')->closeConnection();
- }
-
+
/**
- * Returns the MySQL database server version
- *
- * @deprecated 0.4.4
+ * Disconnect from database
*/
- static public function getMysqlVersion()
+ static public function disconnectDatabase()
{
- return Piwik_FetchOne("SELECT VERSION()");
+ Zend_Registry::get('db')->closeConnection();
}
/**
@@ -1520,23 +1304,30 @@ class Piwik
return Zend_Registry::get('db')->isConnectionUTF8();
}
+/*
+ * Global log object
+ */
+
+ /**
+ * Create log object
+ */
static public function createLogObject()
{
$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)
@@ -1544,15 +1335,15 @@ class Piwik
case 'screen':
$logger->addWriteToScreen();
break;
-
+
case 'database':
$logger->addWriteToDatabase();
break;
-
+
case 'file':
- $logger->addWriteToFile();
+ $logger->addWriteToFile();
break;
-
+
default:
throw new Exception("'$recordTo' is not a valid Log type. Valid logger types are: screen, database, file.");
break;
@@ -1560,7 +1351,7 @@ class Piwik
}
}
}
-
+
foreach($aLoggers as $loggerType =>$logger)
{
if($logger->getWritersCount() == 0)
@@ -1570,7 +1361,16 @@ class Piwik
Zend_Registry::set($loggerType, $logger);
}
}
-
+
+/*
+ * Global config object
+ */
+
+ /**
+ * Create configuration object
+ *
+ * @param string $pathConfigFile
+ */
static public function createConfigObject( $pathConfigFile = null )
{
$config = new Piwik_Config($pathConfigFile);
@@ -1578,93 +1378,181 @@ class Piwik
$config->init();
}
+/*
+ * Global access object
+ */
+
+ /**
+ * Create access object
+ */
static public function createAccessObject()
{
Zend_Registry::set('access', new Piwik_Access());
}
-
- 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)
- && !preg_match($doNotDeletePattern,$tableName)
- )
- )
- {
- $db->query("DROP TABLE `$tableName`");
- }
- }
- }
-
+
+/*
+ * User input validation
+ */
+
/**
* Returns true if the email is a valid email
- *
+ *
* @param string email
* @return bool
*/
- static public function isValidEmailString( $email )
+ 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.
+ * Returns true if the login is valid.
+ * Warning: does not check if the login already exists! You must use UsersManager_API->userExists as well.
+ *
+ * @param string $login
+ * @return bool or throws exception
*/
- static public function createAnonymousUser()
+ static public function checkValidLoginString( $userLogin )
+ {
+ $loginMinimumLength = 3;
+ $loginMaximumLength = 100;
+ $l = strlen($userLogin);
+ if(!($l >= $loginMinimumLength
+ && $l <= $loginMaximumLength
+ && (preg_match('/^[A-Za-z0-9_.-]*$/', $userLogin) > 0))
+ )
+ {
+ throw new Exception(Piwik_TranslateException('UsersManager_ExceptionInvalidLoginFormat', array($loginMinimumLength, $loginMaximumLength)));
+ }
+ }
+
+/*
+ * Date / Timezone
+ */
+
+ /**
+ * Returns true if the current php version supports timezone manipulation
+ * (most likely if php >= 5.2)
+ *
+ * @return bool
+ */
+ static public function isTimezoneSupportEnabled()
{
- // 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 );" );
+ return
+ function_exists( 'date_create' ) &&
+ function_exists( 'date_default_timezone_set' ) &&
+ function_exists( 'timezone_identifiers_list' ) &&
+ function_exists( 'timezone_open' ) &&
+ function_exists( 'timezone_offset_get' );
}
-
- static public function createTables()
+
+/*
+ * Database and table definition methods
+ */
+
+ /**
+ * Is the schema available?
+ *
+ * @return bool True if schema is available; false otherwise
+ */
+ static public function isAvailable()
{
- $db = Zend_Registry::get('db');
- $config = Zend_Registry::get('config');
- $prefixTables = $config->database->tables_prefix;
+ return Piwik_Db_Schema::getInstance()->isAvailable();
+ }
- $tablesAlreadyInstalled = self::getTablesInstalled();
- $tablesToCreate = self::getTablesCreateSql();
- unset($tablesToCreate['archive_blob']);
- unset($tablesToCreate['archive_numeric']);
+ /**
+ * Get the SQL to create a specific Piwik table
+ *
+ * @param string $tableName
+ * @return string SQL
+ */
+ static public function getTableCreateSql( $tableName )
+ {
+ return Piwik_Db_Schema::getInstance()->getTableCreateSql($tableName);
+ }
- foreach($tablesToCreate as $tableName => $tableSql)
- {
- $tableName = $prefixTables . $tableName;
- if(!in_array($tableName, $tablesAlreadyInstalled))
- {
- $db->query( $tableSql );
- }
- }
+ /**
+ * Get the SQL to create Piwik tables
+ *
+ * @return array of strings containing SQL
+ */
+ static public function getTablesCreateSql()
+ {
+ return Piwik_Db_Schema::getInstance()->getTablesCreateSql();
+ }
+
+ /**
+ * Create database
+ *
+ * @param string $dbName
+ */
+ static public function createDatabase( $dbName = null )
+ {
+ Piwik_Db_Schema::getInstance()->createDatabase($dbName);
}
-
+
+ /**
+ * Drop database
+ */
+ static public function dropDatabase()
+ {
+ Piwik_Db_Schema::getInstance()->dropDatabase();
+ }
+
+ /**
+ * Create all tables
+ */
+ static public function createTables()
+ {
+ Piwik_Db_Schema::getInstance()->createTables();
+ }
+
+ /**
+ * Creates an entry in the User table for the "anonymous" user.
+ */
+ static public function createAnonymousUser()
+ {
+ Piwik_Db_Schema::getInstance()->createAnonymousUser();
+ }
+
+ /**
+ * Truncate all tables
+ */
static public function truncateAllTables()
{
- $tablesAlreadyInstalled = self::getTablesInstalled($forceReload = true);
- foreach($tablesAlreadyInstalled as $table)
- {
- Piwik_Query("TRUNCATE `$table`");
- }
+ Piwik_Db_Schema::getInstance()->truncateAllTables();
}
-
- static public function install()
+
+ /**
+ * Drop specific tables
+ *
+ * @param array $doNotDelete Names of tables to not delete
+ */
+ static public function dropTables( $doNotDelete = array() )
{
- Piwik_Common::mkdir(Zend_Registry::get('config')->smarty->compile_dir);
+ Piwik_Db_Schema::getInstance()->dropTables($doNotDelete);
}
-
- static public function uninstall()
+
+ /**
+ * Names of all the prefixed tables in piwik
+ * Doesn't use the DB
+ *
+ * @return array Table names
+ */
+ static public function getTablesNames()
+ {
+ return Piwik_Db_Schema::getInstance()->getTablesNames();
+ }
+
+ /**
+ * Get list of tables installed
+ *
+ * @param bool $forceReload Invalidate cache
+ * @param string $idSite
+ * @return array Tables installed
+ */
+ static public function getTablesInstalled($forceReload = true, $idSite = null)
{
- $db = Zend_Registry::get('db');
- $db->query( "DROP TABLE IF EXISTS ". implode(", ", self::getTablesNames()) );
+ return Piwik_Db_Schema::getInstance()->getTablesInstalled($forceReload, $idSite);
}
}
diff --git a/core/Plugin.php b/core/Plugin.php
index d0d118180a..82ac6c523f 100644
--- a/core/Plugin.php
+++ b/core/Plugin.php
@@ -20,14 +20,14 @@ 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
+ * 'description' => string // 1-2 sentence description of the plugin
+ * 'author' => string // plugin author
+ * 'author_homepage' => string // author homepage URL (or email "mailto:youremail@example.org")
+ * 'homepage' => string // plugin homepage URL
+ * 'version' => string // plugin version number; examples and 3rd party plugins must not use Piwik_Version::VERSION;
+ * // 3rd party plugins must increment the version number with each plugin release
* 'translationAvailable' => bool // is there a translation file in plugins/your-plugin/lang/* ?
- * 'TrackerPlugin' => bool // should we load this plugin during the stats logging process?
+ * 'TrackerPlugin' => bool // should we load this plugin during the stats logging process?
*/
abstract function getInformation();
@@ -69,17 +69,6 @@ abstract class Piwik_Plugin
}
/**
- * Returns the plugin name
- *
- * @return string
- */
- public function getName()
- {
- $info = $this->getInformation();
- return $info['name'];
- }
-
- /**
* Returns the plugin version number
*
* @return string
@@ -91,12 +80,13 @@ abstract class Piwik_Plugin
}
/**
- * Returns the UserCountry part when the plugin class is Piwik_UserCountry
+ * Returns the plugin's base name without the "Piwik_" prefix,
+ * e.g., "UserCountry" when the plugin class is "Piwik_UserCountry"
*
* @return string
*/
- public function getClassName()
+ final public function getClassName()
{
- return substr(get_class($this), strlen("Piwik_"));
+ return Piwik::unprefixClass(get_class($this));
}
}
diff --git a/core/PluginsFunctions/AdminMenu.php b/core/PluginsFunctions/AdminMenu.php
index e926c77d7f..44142e9841 100644
--- a/core/PluginsFunctions/AdminMenu.php
+++ b/core/PluginsFunctions/AdminMenu.php
@@ -16,6 +16,7 @@
class Piwik_AdminMenu
{
private $adminMenu = null;
+ private $adminMenuOrdered = null;
static private $instance = null;
/**
@@ -36,31 +37,37 @@ class Piwik_AdminMenu
*/
public function get()
{
- if(!is_null($this->adminMenu))
+ if(!is_null($this->adminMenuOrdered))
{
- return;
+ return $this->adminMenuOrdered;
}
Piwik_PostEvent('AdminMenu.add');
-
- foreach($this->adminMenu as $key => &$element)
+
+ $this->adminMenuOrdered = array();
+ ksort($this->adminMenu);
+ foreach($this->adminMenu as $order => $menu)
{
- if(is_null($element))
- {
- unset($this->adminMenu[$key]);
- }
+ foreach($menu as $key => &$element)
+ {
+ if(!is_null($element))
+ {
+ $this->adminMenuOrdered[$key] = $element;
+ }
+ }
}
- return $this->adminMenu;
+ return $this->adminMenuOrdered;
}
/*
*
*/
- public function add($adminMenuName, $url)
+ public function add($adminMenuName, $url, $displayedForCurrentUser, $order)
{
- if(!isset($this->adminMenu[$adminMenuName]))
+ if($displayedForCurrentUser
+ && !isset($this->adminMenu[$adminMenuName]))
{
- $this->adminMenu[$adminMenuName] = $url;
+ $this->adminMenu[$order][$adminMenuName] = $url;
}
}
@@ -74,15 +81,30 @@ class Piwik_AdminMenu
$this->adminMenu[$adminMenuRenamed] = $save;
}
}
+function Piwik_GetCurrentAdminMenuName()
+{
+ $menu = Piwik_GetAdminMenu();
+ $currentModule = Piwik::getModule();
+ $currentAction = Piwik::getAction();
+ foreach($menu as $name => $parameters)
+ {
+ if($parameters['module'] == $currentModule
+ && $parameters['action'] == $currentAction)
+ {
+ return $name;
+ }
+ }
+ return false;
+}
function Piwik_GetAdminMenu()
{
return Piwik_AdminMenu::getInstance()->get();
}
-function Piwik_AddAdminMenu( $adminMenuName, $url )
+function Piwik_AddAdminMenu( $adminMenuName, $url, $displayedForCurrentUser = true, $order = 10 )
{
- return Piwik_AdminMenu::getInstance()->add($adminMenuName, $url);
+ return Piwik_AdminMenu::getInstance()->add($adminMenuName, $url, $displayedForCurrentUser, $order);
}
function Piwik_RenameAdminMenuEntry($adminMenuOriginal, $adminMenuRenamed)
diff --git a/core/PluginsFunctions/Menu.php b/core/PluginsFunctions/Menu.php
index d2b33675c6..73041c1d6d 100644
--- a/core/PluginsFunctions/Menu.php
+++ b/core/PluginsFunctions/Menu.php
@@ -60,6 +60,21 @@ class Piwik_Menu
}
}
+ function isUrlFound($url)
+ {
+ $menu = Piwik_Menu::getInstance()->get();
+ foreach($menu as $mainMenuName => $subMenus)
+ {
+ foreach($subMenus as $subMenuName => $menuUrl)
+ {
+ if($menuUrl == $url)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
/*
*
*/
@@ -163,6 +178,11 @@ class Piwik_Menu
}
}
+function Piwik_IsMenuUrlFound($url)
+{
+ return Piwik_Menu::getInstance()->isUrlFound($url);
+}
+
function Piwik_GetMenu()
{
return Piwik_Menu::getInstance()->get();
diff --git a/core/PluginsFunctions/Sql.php b/core/PluginsFunctions/Sql.php
index 5cd5ff6784..aa219d619b 100644
--- a/core/PluginsFunctions/Sql.php
+++ b/core/PluginsFunctions/Sql.php
@@ -11,15 +11,64 @@
*/
/**
+ * SQL wrapper
+ *
* @package PluginsFunctions
*/
class Piwik_Sql
{
+ static private function getDb()
+ {
+ $db = null;
+ if(!empty($GLOBALS['PIWIK_TRACKER_MODE']))
+ {
+ $db = Piwik_Tracker::getDatabase();
+ }
+ if($db === null)
+ {
+ $db = Zend_Registry::get('db');
+ }
+ return $db;
+ }
+
+ static public function exec($sql)
+ {
+ return self::getDb()->exec($sql);
+ }
+
+ static public function query($sql, $parameters = array())
+ {
+ return self::getDb()->query($sql, $parameters);
+ }
+
+ static public function fetchAll($sql, $parameters = array())
+ {
+ return self::getDb()->fetchAll($sql, $parameters);
+ }
+
+ static public function fetchRow($sql, $parameters = array())
+ {
+ return self::getDb()->fetchRow($sql, $parameters);
+ }
+
+ static public function fetchOne($sql, $parameters = array())
+ {
+ return self::getDb()->fetchOne($sql, $parameters);
+ }
}
-function Piwik_Exec( $sqlQuery )
+/**
+ * Executes an unprepared SQL query on the DB. Recommended for DDL statements, e.g., CREATE/DROP/ALTER.
+ * The return result is DBMS-specific. For MySQLI, it returns the number of rows affected. For PDO, it returns the Zend_Db_Statement object
+ * If you want to fetch data from the DB you should use the function Piwik_FetchAll()
+ *
+ * @param string $sqlQuery
+ * @param array Parameters to bind in the query, array( param1 => value1, param2 => value2)
+ * @return integer|Zend_Db_Statement
+ */
+function Piwik_Exec($sqlQuery)
{
- return Zend_Registry::get('db')->exec( $sqlQuery );
+ return Piwik_Sql::exec($sqlQuery);
}
/**
@@ -32,25 +81,43 @@ function Piwik_Exec( $sqlQuery )
* @param array Parameters to bind in the query, array( param1 => value1, param2 => value2)
* @return Zend_Db_Statement
*/
-function Piwik_Query( $sqlQuery, $parameters = array())
+function Piwik_Query($sqlQuery, $parameters = array())
{
- return Zend_Registry::get('db')->query( $sqlQuery, $parameters);
+ return Piwik_Sql::query($sqlQuery, $parameters);
}
/**
- * Executes the SQL Query and fetches all the rows from the database
+ * Executes the SQL Query and fetches all the rows from the database query
*
* @param string $sqlQuery
- * @param array Parameters to bind in the query, array( param1 => value1, param2 => value2)
+ * @param array $parameters 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 );
+ return Piwik_Sql::fetchAll($sqlQuery, $parameters);
}
-function Piwik_FetchOne( $sqlQuery, $parameters = array())
+/**
+ * Fetches first row of result from the database query
+ *
+ * @param string $sqlQuery
+ * @param array $parameters Parameters to bind in the query, array( param1 => value1, param2 => value2)
+ * @return array
+ */
+function Piwik_FetchRow($sqlQuery, $parameters = array())
{
- return Zend_Registry::get('db')->fetchOne( $sqlQuery, $parameters );
+ return Piwik_Sql::fetchRow($sqlQuery, $parameters);
}
+/**
+ * Fetches first column of first row of result from the database query
+ *
+ * @param string $sqlQuery
+ * @param array $parameters Parameters to bind in the query, array( param1 => value1, param2 => value2)
+ * @return string
+ */
+function Piwik_FetchOne( $sqlQuery, $parameters = array())
+{
+ return Piwik_Sql::fetchOne($sqlQuery, $parameters);
+}
diff --git a/core/PluginsFunctions/WidgetsList.php b/core/PluginsFunctions/WidgetsList.php
index ce1b7a417f..1ab172a645 100644
--- a/core/PluginsFunctions/WidgetsList.php
+++ b/core/PluginsFunctions/WidgetsList.php
@@ -16,10 +16,15 @@
class Piwik_WidgetsList
{
static protected $widgets = null;
+ static protected $hookCalled = false;
static function get()
{
- Piwik_PostEvent('WidgetsList.add');
+ if(!self::$hookCalled)
+ {
+ self::$hookCalled = true;
+ Piwik_PostEvent('WidgetsList.add');
+ }
return self::$widgets;
}
@@ -28,6 +33,10 @@ class Piwik_WidgetsList
$widgetCategory = Piwik_Translate($widgetCategory);
$widgetName = Piwik_Translate($widgetName);
$widgetUniqueId = 'widget' . $controllerName . $controllerAction;
+ foreach($customParameters as $name => $value)
+ {
+ $widgetUniqueId .= $name . $value;
+ }
self::$widgets[$widgetCategory][] = array(
'name' => $widgetName,
'uniqueId' => $widgetUniqueId,
@@ -36,6 +45,23 @@ class Piwik_WidgetsList
) + $customParameters
);
}
+
+ static function isDefined($controllerName, $controllerAction)
+ {
+ $widgetsList = self::get();
+ foreach($widgetsList as $widgetCategory => $widgets)
+ {
+ foreach($widgets as $widget)
+ {
+ if($widget['parameters']['module'] == $controllerName
+ && $widget['parameters']['action'] == $controllerAction)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
function Piwik_GetWidgetsList()
@@ -47,3 +73,8 @@ function Piwik_AddWidget( $widgetCategory, $widgetName, $controllerName, $contro
{
Piwik_WidgetsList::add($widgetCategory, $widgetName, $controllerName, $controllerAction, $customParameters);
}
+
+function Piwik_IsWidgetDefined($controllerName, $controllerAction)
+{
+ return Piwik_WidgetsList::isDefined($controllerName, $controllerAction);
+}
diff --git a/core/PluginsManager.php b/core/PluginsManager.php
index 5e2c3e20d7..d31289ac23 100644
--- a/core/PluginsManager.php
+++ b/core/PluginsManager.php
@@ -10,9 +10,6 @@
* @package Piwik
*/
-// no direct access
-defined('PIWIK_INCLUDE_PATH') or die;
-
/**
* @see core/PluginsFunctions/Menu.php
* @see core/PluginsFunctions/AdminMenu.php
@@ -42,7 +39,13 @@ class Piwik_PluginsManager
protected $loadedPlugins = array();
protected $doLoadAlwaysActivatedPlugins = true;
- protected $pluginToAlwaysActivate = array( 'CoreHome', 'CoreUpdater', 'CoreAdminHome', 'CorePluginsAdmin' );
+ protected $pluginToAlwaysActivate = array( 'CoreHome',
+ 'CoreUpdater',
+ 'CoreAdminHome',
+ 'CorePluginsAdmin',
+ 'Installation',
+ 'SitesManager',
+ 'UsersManager' );
static private $instance = null;
@@ -112,7 +115,7 @@ class Piwik_PluginsManager
if($key !== false)
{
unset($pluginsTracker[$key]);
- Zend_Registry::get('config')->Plugins_Tracker = $pluginsTracker;
+ Zend_Registry::get('config')->Plugins_Tracker = array('Plugins_Tracker' => $pluginsTracker);
}
}
}
@@ -154,7 +157,7 @@ class Piwik_PluginsManager
Zend_Registry::get('config')->Plugins = $plugins;
}
- public function setPluginsToLoad( array $pluginsToLoad )
+ public function loadPlugins( array $pluginsToLoad )
{
// case no plugins to load
if(is_null($pluginsToLoad))
@@ -162,7 +165,7 @@ class Piwik_PluginsManager
$pluginsToLoad = array();
}
$this->pluginsToLoad = $pluginsToLoad;
- $this->loadPlugins();
+ $this->reloadPlugins();
}
public function doNotLoadPlugins()
@@ -174,13 +177,22 @@ class Piwik_PluginsManager
{
$this->doLoadAlwaysActivatedPlugins = false;
}
-
- public function postLoadPlugins()
+
+ public function loadTranslations()
{
$plugins = $this->getLoadedPlugins();
+
foreach($plugins as $plugin)
{
$this->loadTranslation( $plugin, $this->languageToLoad );
+ }
+ }
+
+ public function postLoadPlugins()
+ {
+ $plugins = $this->getLoadedPlugins();
+ foreach($plugins as $plugin)
+ {
$plugin->postLoad();
}
}
@@ -192,9 +204,7 @@ class Piwik_PluginsManager
*/
public function getLoadedPluginsName()
{
- $oPlugins = $this->getLoadedPlugins();
- $pluginNames = array_map('get_class',$oPlugins);
- return $pluginNames;
+ return array_map('get_class', $this->getLoadedPlugins());
}
/**
@@ -230,7 +240,7 @@ class Piwik_PluginsManager
* Register the observers for every plugin.
*
*/
- public function loadPlugins()
+ private function reloadPlugins()
{
$this->pluginsToLoad = array_unique($this->pluginsToLoad);
@@ -248,17 +258,17 @@ class Piwik_PluginsManager
&& $this->isPluginActivated($pluginName))
{
$this->addPluginObservers( $newPlugin );
- $this->addLoadedPlugin( $pluginName, $newPlugin);
}
}
}
}
/**
- * Loads the plugin filename and instanciates the plugin with the given name, eg. UserCountry
+ * Loads the plugin filename and instantiates 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
+ * @param string $pluginName
+ * @return Piwik_Plugin
*/
public function loadPlugin( $pluginName )
{
@@ -278,7 +288,8 @@ class Piwik_PluginsManager
if(!file_exists($path))
{
- throw new Exception("Unable to load plugin '$pluginName' because '$path' couldn't be found.");
+ throw new Exception("Unable to load plugin '$pluginName' because '$path' couldn't be found.
+ You can manually uninstall the plugin by removing the line <code>Plugins[] = $pluginName</code> from the Piwik config file.");
}
// Don't remove this.
@@ -295,6 +306,9 @@ class Piwik_PluginsManager
{
throw new Exception("The plugin $pluginClassName in the file $path must inherit from Piwik_Plugin.");
}
+
+ $this->addLoadedPlugin( $pluginName, $newPlugin);
+
return $newPlugin;
}
@@ -347,7 +361,7 @@ class Piwik_PluginsManager
try{
$plugin->install();
} catch(Exception $e) {
- throw new Piwik_PluginsManager_PluginException($plugin->getName(), $plugin->getClassName(), $e->getMessage()); }
+ throw new Piwik_PluginsManager_PluginException($plugin->getClassName(), $e->getMessage()); }
}
@@ -381,12 +395,12 @@ class Piwik_PluginsManager
*/
private function loadTranslation( $plugin, $langCode )
{
- // we are certainly in Tracker mode, Zend is not loaded
- if(!class_exists('Zend_Loader', false))
+ // we are in Tracker mode if Piwik_Loader is not (yet) loaded
+ if(!class_exists('Piwik_Loader', false))
{
return ;
}
-
+
$infos = $plugin->getInformation();
if(!isset($infos['translationAvailable']))
{
@@ -398,7 +412,7 @@ class Piwik_PluginsManager
{
return;
}
-
+
$pluginName = $plugin->getClassName();
$path = PIWIK_INCLUDE_PATH . '/plugins/' . $pluginName .'/lang/%s.php';
@@ -436,13 +450,6 @@ class Piwik_PluginsManager
return $pluginNames;
}
- public function getInstalledPlugins()
- {
- $plugins = $this->getLoadedPlugins();
- $installed = $this->getInstalledPluginsName();
- return array_intersect_key($plugins, array_combine($installed, array_fill(0, count($installed), 1)));
- }
-
private function installPluginIfNecessary( Piwik_Plugin $plugin )
{
$pluginName = $plugin->getClassName();
@@ -486,18 +493,24 @@ class Piwik_PluginsManager
*/
class Piwik_PluginsManager_PluginException extends Exception
{
- function __construct($pluginName, $className, $message)
+ function __construct($pluginName, $message)
{
parent::__construct("There was a problem installing the plugin ". $pluginName . ": " . $message. "
If this plugin has already been installed, and if you want to hide this message</b>, you must add the following line under the
[PluginsInstalled]
entry in your config/config.ini.php file:
- PluginsInstalled[] = $className" );
+ PluginsInstalled[] = $pluginName" );
}
}
/**
* Post an event to the dispatcher which will notice the observers
+ *
+ * @param $eventName The event name
+ * @param $object Object, array or string that the listeners can read and/or modify.
+ * Listeners can call $object =& $notification->getNotificationObject(); to fetch and then modify this variable.
+ * @param $info Additional array of data that can be used by the listeners, but not edited
+ * @return void
*/
function Piwik_PostEvent( $eventName, &$object = null, $info = array() )
{
@@ -530,11 +543,11 @@ class Piwik_Event_Notification extends Event_Notification
$className = is_object($callback[0]) ? get_class($callback[0]) : $callback[0];
$method = $callback[1];
- echo "after $className -> $method <br>";
+ echo "after $className -> $method <br />";
echo "-"; Piwik::printTimer();
- echo "<br>";
+ echo "<br />";
echo "-"; Piwik::printMemoryLeak();
- echo "<br>";
+ echo "<br />";
}
}
}
diff --git a/core/Session.php b/core/Session.php
new file mode 100644
index 0000000000..49969f2066
--- /dev/null
+++ b/core/Session.php
@@ -0,0 +1,72 @@
+<?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$
+ *
+ * @category Piwik
+ * @package Piwik
+ */
+
+/**
+ * Session initialization.
+ *
+ * @package Piwik
+ */
+class Piwik_Session extends Zend_Session
+{
+ public static function start($options = false)
+ {
+ // don't use the default: PHPSESSID
+ $sessionName = defined('PIWIK_SESSION_NAME') ? PIWIK_SESSION_NAME : 'PIWIK_SESSID';
+ @ini_set('session.name', $sessionName);
+
+ // we consider this a misconfiguration (i.e., Piwik doesn't implement user-defined session handler functions)
+ if(ini_get('session.save_handler') == 'user')
+ {
+ @ini_set('session.save_handler', 'files');
+ @ini_set('session.save_path', '');
+ }
+
+ // for "files", we want a writeable folder;
+ // for shared hosting, we assume the web server has been securely configured to prevent local session file hijacking
+ if(ini_get('session.save_handler') == 'files')
+ {
+ $sessionPath = ini_get('session.save_path');
+ if(preg_match('/^[0-9]+;(.*)/', $sessionPath, $matches))
+ {
+ $sessionPath = $matches[1];
+ }
+ if(ini_get('safe_mode') || ini_get('open_basedir') || empty($sessionPath) || !@is_readable($sessionPath) || !@is_writable($sessionPath))
+ {
+ $sessionPath = PIWIK_USER_PATH . '/tmp/sessions';
+ $ok = true;
+
+ if(!is_dir($sessionPath))
+ {
+ @mkdir($sessionPath, 0755, true);
+ if(!is_dir($sessionPath))
+ {
+ // Unable to mkdir $sessionPath
+ $ok = false;
+ }
+ }
+ else if(!@is_writable($sessionPath))
+ {
+ // $sessionPath is not writable
+ $ok = false;
+ }
+
+ if($ok)
+ {
+ @ini_set('session.save_path', $sessionPath);
+ }
+ // else rely on default setting (assuming it is configured to a writeable folder)
+ }
+ }
+
+ Zend_Session::start();
+ }
+}
diff --git a/core/Site.php b/core/Site.php
index d692c470ae..d5915e30b3 100644
--- a/core/Site.php
+++ b/core/Site.php
@@ -25,13 +25,19 @@ class Piwik_Site
$this->id = $idsite;
if(!isset(self::$infoSites[$this->id]))
{
- self::$infoSites[$this->id] = Piwik_SitesManager_API::getSiteFromId($idsite);
+ self::$infoSites[$this->id] = Piwik_SitesManager_API::getInstance()->getSiteFromId($idsite);
}
}
function __toString()
{
- return "site id=".$this->getId().", name=".$this->getName();
+ return "site id=".$this->getId().",
+ name=".$this->getName() .",
+ url = ". $this->getMainUrl() .",
+ IPs excluded = ".$this->getExcludedIps().",
+ timezone = ".$this->getTimezone().",
+ currency = ".$this->getCurrency().",
+ creation date = ".$this->getCreationDate();
}
function getName()
@@ -54,6 +60,26 @@ class Piwik_Site
$date = self::$infoSites[$this->id]['ts_created'];
return Piwik_Date::factory($date);
}
+
+ function getTimezone()
+ {
+ return self::$infoSites[$this->id]['timezone'];
+ }
+
+ function getCurrency()
+ {
+ return self::$infoSites[$this->id]['currency'];
+ }
+
+ function getExcludedIps()
+ {
+ return self::$infoSites[$this->id]['excluded_ips'];
+ }
+
+ function getExcludedQueryParameters()
+ {
+ return self::$infoSites[$this->id]['excluded_parameters'];
+ }
/**
* @param string comma separated idSite list
@@ -70,4 +96,9 @@ class Piwik_Site
}
return $validIds;
}
+
+ static public function clearCache()
+ {
+ self::$infoSites = array();
+ }
}
diff --git a/core/Smarty.php b/core/Smarty.php
index a5b8165583..cc5c000bbf 100644
--- a/core/Smarty.php
+++ b/core/Smarty.php
@@ -10,9 +10,6 @@
* @package Piwik
*/
-// no direct access
-defined('PIWIK_INCLUDE_PATH') or die;
-
/**
* @see libs/Smarty/Smarty.class.php
* @link http://smarty.net
diff --git a/core/SmartyPlugins/function.ajaxErrorDiv.php b/core/SmartyPlugins/function.ajaxErrorDiv.php
new file mode 100644
index 0000000000..b143fae09e
--- /dev/null
+++ b/core/SmartyPlugins/function.ajaxErrorDiv.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$
+ *
+ * @category Piwik
+ * @package SmartyPlugins
+ */
+
+/**
+ * Outputs the generic Ajax error div (displayed when ajax requests are throwing exceptions and returning error messages)
+ *
+ * @param id=$ID_NAME ID of the HTML div, defaults to ajaxError
+ * @return string Html of the error message div, hidden by defayult
+ */
+function smarty_function_ajaxErrorDiv($params, &$smarty)
+{
+ if(empty($params['id']))
+ {
+ $id = 'ajaxError';
+ }
+ else
+ {
+ $id = $params['id'];
+ }
+ return '<div class="ajaxError" id="'.$id.'" style="display:none"></div>';
+}
diff --git a/core/SmartyPlugins/function.ajaxLoadingDiv.php b/core/SmartyPlugins/function.ajaxLoadingDiv.php
new file mode 100644
index 0000000000..86b624f7f6
--- /dev/null
+++ b/core/SmartyPlugins/function.ajaxLoadingDiv.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$
+ *
+ * @category Piwik
+ * @package SmartyPlugins
+ */
+
+/**
+ * Outputs the generic Ajax Loading div (displayed when ajax requests are triggered)
+ *
+ * @param id=$ID_NAME ID of the HTML div, defaults to ajaxLoading
+ * @return string Html of the Loading... div
+ */
+function smarty_function_ajaxLoadingDiv($params, &$smarty)
+{
+ if(empty($params['id']))
+ {
+ $id = 'ajaxLoading';
+ }
+ else
+ {
+ $id = $params['id'];
+ }
+ return '<div id="'.$id.'" style="display:none">'.
+ '<div id="loadingPiwik"><img src="themes/default/images/loading-blue.gif" alt="" /> '.
+ Piwik_Translate('General_LoadingData') .
+ ' </div>'.
+ '</div>';
+ ;
+}
diff --git a/core/SmartyPlugins/function.ajaxRequestErrorDiv.php b/core/SmartyPlugins/function.ajaxRequestErrorDiv.php
new file mode 100644
index 0000000000..d0878ae00c
--- /dev/null
+++ b/core/SmartyPlugins/function.ajaxRequestErrorDiv.php
@@ -0,0 +1,22 @@
+<?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$
+ *
+ * @category Piwik
+ * @package SmartyPlugins
+ */
+
+/**
+ * Outputs the generic Ajax request error div
+ * will be displayed when the ajax request fails (connectivity, server error, etc)
+ *
+ * @return string Html of the div
+ */
+function smarty_function_ajaxRequestErrorDiv()
+{
+ return '<div id="loadingError">'.Piwik_Translate('General_ErrorRequest').'</div>';
+}
diff --git a/core/SmartyPlugins/function.assignTopBar.php b/core/SmartyPlugins/function.assignTopBar.php
index 4d5beb44fc..e5313fa179 100644
--- a/core/SmartyPlugins/function.assignTopBar.php
+++ b/core/SmartyPlugins/function.assignTopBar.php
@@ -21,10 +21,11 @@ function smarty_function_assignTopBar($params, &$smarty)
{
$topBarElements = array();
$elements = array(
- array('CoreHome', Piwik_Translate('General_YourDashboard'), array('module' => 'CoreHome', 'action' => 'index')),
+ array('CoreHome', Piwik_Translate('General_Dashboard'), array('module' => 'CoreHome', 'action' => 'index')),
+ array('MultiSites', Piwik_Translate('General_MultiSitesSummary'), array('module' => 'MultiSites', 'action' => 'index')),
array('Widgetize', Piwik_Translate('General_Widgets'), array('module' => 'Widgetize', 'action' => 'index')),
array('API', Piwik_Translate('General_API'), array('module' => 'API', 'action' => 'listAllAPI')),
- array('Feedback', Piwik_Translate('General_GiveUsYourFeedback'), array('module' => 'Feedback', 'action' => 'index', 'keepThis' => 'true', 'TB_iframe' => 'true', 'height' => '400', 'width' => '350'), 'title="'.Piwik_Translate('General_GiveUsYourFeedback').'" class="thickbox"'),
+ array('Feedback', Piwik_Translate('General_GiveUsYourFeedback'), array('module' => 'Feedback', 'action' => 'index'), 'id="topbar-feedback"'),
);
foreach($elements as $element)
diff --git a/core/SmartyPlugins/modifier.inlineHelp.php b/core/SmartyPlugins/modifier.inlineHelp.php
new file mode 100644
index 0000000000..bbd9892e61
--- /dev/null
+++ b/core/SmartyPlugins/modifier.inlineHelp.php
@@ -0,0 +1,26 @@
+<?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$
+ *
+ * @category Piwik
+ * @package SmartyPlugins
+ */
+
+/**
+ * Displays inline help using the jquery UI CSS
+ */
+function smarty_modifier_inlineHelp($text)
+{
+ return
+ '<div class="ui-widget">'.
+ '<div class="ui-inline-help ui-state-highlight ui-corner-all">'.
+ '<p style="font-size:8pt;"><span class="ui-icon ui-icon-info" style="float:left;margin-right:.3em;"></span>'.
+ $text.
+ '</p>'.
+ '</div>'.
+ '</div>';
+}
diff --git a/core/SmartyPlugins/modifier.money.php b/core/SmartyPlugins/modifier.money.php
new file mode 100644
index 0000000000..52acffd4fd
--- /dev/null
+++ b/core/SmartyPlugins/modifier.money.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$
+ *
+ * @category Piwik
+ * @package SmartyPlugins
+ */
+
+/**
+ * Prints money, given the currency symbol.
+ *
+ * @return string The amount with the currency symbol
+ */
+function smarty_modifier_money($amount)
+{
+ if(func_num_args() != 2)
+ {
+ throw new Exception('the smarty modifier money expects one parameter: the idSite.');
+ }
+ $idSite = func_get_args();
+ $idSite = $idSite[1];
+ return Piwik::getPrettyMoney($amount, $idSite);
+}
diff --git a/core/SmartyPlugins/modifier.stripeol.php b/core/SmartyPlugins/modifier.stripeol.php
new file mode 100644
index 0000000000..d9e70f810d
--- /dev/null
+++ b/core/SmartyPlugins/modifier.stripeol.php
@@ -0,0 +1,32 @@
+<?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$
+ *
+ * @category Piwik
+ * @package SmartyPlugins
+ */
+
+/**
+ * Smarty stripeol modifier plugin
+ *
+ * Type: modifier<br>
+ * Name: stripeol<br>
+ * Purpose: Replace all end-of-line characters with platform specific string.<br>
+ * Example: {$var|stripeol}
+ * Date: March 10th, 2010
+ * @author anthon (at) piwik.org
+ * @version 1.0
+ * @param string
+ * @param string
+ * @return string
+ */
+function smarty_modifier_stripeol($text)
+{
+ return preg_replace('!(\r\n|\r|\n)!', PHP_EOL, $text);
+}
+
+/* vim: set expandtab: */
diff --git a/core/SmartyPlugins/modifier.translate.php b/core/SmartyPlugins/modifier.translate.php
index 3c3a7346cc..09a2ecdf89 100644
--- a/core/SmartyPlugins/modifier.translate.php
+++ b/core/SmartyPlugins/modifier.translate.php
@@ -11,13 +11,19 @@
*/
/**
- * 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.
+ * Translates in the currently selected language the specified translation $stringToken
+ * Translations strings are located either in /lang/xx.php or within the plugin lang directory.
*
- * Example:
+ * Usage:
* {'General_Unknown'|translate} will be translated as 'Unknown' (see the entry in /lang/en.php)
*
- * @return string The translated string
+ * Usage with multiple substrings to be replaced in the translation string:
+ * - in lang/en.php you would find:
+ * 'VisitorInterest_BetweenXYMinutes' => '%1s-%2s min',
+ * - in the smarty template you would then translate the string, passing the two parameters:
+ * {'VisitorInterest_BetweenXYMinutes'|translate:$min:$max}
+ *
+ * @return string The translated string, with optional substrings parameters replaced
*/
function smarty_modifier_translate($stringToken)
{
diff --git a/core/SmartyPlugins/modifier.urlRewriteWithParameters.php b/core/SmartyPlugins/modifier.urlRewriteWithParameters.php
index e2077b493c..7f42333820 100644
--- a/core/SmartyPlugins/modifier.urlRewriteWithParameters.php
+++ b/core/SmartyPlugins/modifier.urlRewriteWithParameters.php
@@ -18,6 +18,7 @@
*/
function smarty_modifier_urlRewriteWithParameters($parameters)
{
+ $parameters['updated'] = null;
$url = Piwik_Url::getCurrentQueryStringWithParametersModified($parameters);
return htmlspecialchars($url);
}
diff --git a/core/SmartyPlugins/outputfilter.ajaxcdn.php b/core/SmartyPlugins/outputfilter.ajaxcdn.php
index 7c2de1e65e..9d010f7475 100644
--- a/core/SmartyPlugins/outputfilter.ajaxcdn.php
+++ b/core/SmartyPlugins/outputfilter.ajaxcdn.php
@@ -39,14 +39,18 @@ function smarty_outputfilter_ajaxcdn($source, &$smarty)
$swfobject_version = Zend_Registry::get('config')->General->swfobject_version;
$pattern = array(
+ '~<link rel="stylesheet" type="text/css" href="libs/jquery/themes/([^"]*)" class="ui-theme" />~',
'~<script type="text/javascript" src="libs/jquery/jquery\.js([^"]*)">~',
'~<script type="text/javascript" src="libs/jquery/jquery-ui\.js([^"]*)">~',
+ '~<script type="text/javascript" src="libs/jquery/jquery-ui-18n\.js([^"]*)">~',
'~<script type="text/javascript" src="libs/swfobject/swfobject\.js([^"]*)">~',
);
$replace = array(
+ '<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/'.$jqueryui_version.'/themes/\\1" class="ui-theme" />',
'<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/'.$jquery_version.'/jquery.min.js">',
'<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/'.$jqueryui_version.'/jquery-ui.min.js">',
+ '<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/'.$jqueryui_version.'/i18n/jquery-ui-18n.min.js">',
'<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/swfobject/'.$swfobject_version.'/swfobject.js">',
);
diff --git a/core/TablePartitioning.php b/core/TablePartitioning.php
index 1edcbb4ef4..d74eefff3b 100644
--- a/core/TablePartitioning.php
+++ b/core/TablePartitioning.php
@@ -108,11 +108,7 @@ class Piwik_TablePartitioning_Monthly extends Piwik_TablePartitioning
protected function generateTableName()
{
$config = Zend_Registry::get('config');
- $prefixTables = $config->database->tables_prefix;
-
- $date = date("Y_m", $this->timestamp);
-
- return $prefixTables . $this->tableName . "_" . $date;
+ return $config->database->tables_prefix . $this->tableName . "_" . date("Y_m", $this->timestamp);
}
}
@@ -131,10 +127,6 @@ class Piwik_TablePartitioning_Daily extends Piwik_TablePartitioning
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;
+ return $config->database->tables_prefix . $this->tableName . "_" . date("Y_m_d", $this->timestamp);
}
}
diff --git a/core/Tracker.php b/core/Tracker.php
index e49ecc06c2..d2284c4528 100644
--- a/core/Tracker.php
+++ b/core/Tracker.php
@@ -35,6 +35,7 @@ class Piwik_Tracker
const STATE_LOGGING_DISABLE = 10;
const STATE_EMPTY_REQUEST = 11;
const STATE_TRACK_ONLY = 12;
+ const STATE_NOSCRIPT_REQUEST = 13;
const COOKIE_INDEX_IDVISITOR = 1;
const COOKIE_INDEX_TIMESTAMP_LAST_ACTION = 2;
@@ -110,7 +111,7 @@ class Piwik_Tracker
case self::STATE_EMPTY_REQUEST:
printDebug("Empty request => 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.";
+ echo "<a href='/'>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:
@@ -121,6 +122,7 @@ class Piwik_Tracker
printDebug("Data push, tracking only");
break;
+ case self::STATE_NOSCRIPT_REQUEST:
case self::STATE_NOTHING_TO_NOTICE:
default:
printDebug("Nothing to notice => default behaviour");
@@ -131,8 +133,10 @@ class Piwik_Tracker
if($GLOBALS['PIWIK_TRACKER_DEBUG'] === true)
{
- self::$db->recordProfiling();
- Piwik::printSqlProfilingReportTracker(self::$db);
+ if(isset(self::$db)) {
+ self::$db->recordProfiling();
+ Piwik::printSqlProfilingReportTracker(self::$db);
+ }
}
self::disconnectDatabase();
@@ -237,7 +241,7 @@ class Piwik_Tracker
if( !isset($GLOBALS['PIWIK_TRACKER_DEBUG']) || !$GLOBALS['PIWIK_TRACKER_DEBUG'] )
{
$trans_gif_64 = "R0lGODlhAQABAIAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
- header("Content-type: image/gif");
+ header("Content-Type: image/gif");
print(base64_decode($trans_gif_64));
}
}
@@ -281,7 +285,7 @@ class Piwik_Tracker
&& count($pluginsTracker) != 0)
{
Piwik_PluginsManager::getInstance()->doNotLoadAlwaysActivatedPlugins();
- Piwik_PluginsManager::getInstance()->setPluginsToLoad( $pluginsTracker['Plugins_Tracker'] );
+ Piwik_PluginsManager::getInstance()->loadPlugins( $pluginsTracker['Plugins_Tracker'] );
printDebug("Loading plugins: { ". implode(",", $pluginsTracker['Plugins_Tracker']) . "}");
}
@@ -328,10 +332,15 @@ class Piwik_Tracker
protected function handleEmptyRequest()
{
- if( count($this->request) == 0)
+ $countParameters = count($this->request);
+ if($countParameters == 0)
{
$this->setState(self::STATE_EMPTY_REQUEST);
}
+ if($countParameters == 1 )
+ {
+ $this->setState(self::STATE_NOSCRIPT_REQUEST);
+ }
}
protected function handleDisabledTracker()
@@ -344,19 +353,22 @@ class Piwik_Tracker
}
}
-function printDebug( $info = '' )
+if(!function_exists('printDebug'))
{
- if(isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG'])
- {
- if(is_array($info))
- {
- print("<pre>");
- print(var_export($info,true));
- print("</pre>");
- }
- else
- {
- print($info . "<br>\n");
- }
- }
+ function printDebug( $info = '' )
+ {
+ if(isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG'])
+ {
+ if(is_array($info))
+ {
+ print("<pre>");
+ print(var_export($info,true));
+ print("</pre>");
+ }
+ else
+ {
+ print($info . "<br />\n");
+ }
+ }
+ }
}
diff --git a/core/Tracker/Action.php b/core/Tracker/Action.php
index f7a9445058..e2641f35f1 100644
--- a/core/Tracker/Action.php
+++ b/core/Tracker/Action.php
@@ -65,6 +65,8 @@ class Piwik_Tracker_Action implements Piwik_Tracker_Action_Interface
private $actionType;
private $actionUrl;
+ static private $queryParametersToExclude = array('phpsessid', 'jsessionid', 'sessionid', 'aspsessionid');
+
public function setRequest($requestArray)
{
$this->request = $requestArray;
@@ -117,17 +119,54 @@ class Piwik_Tracker_Action implements Piwik_Tracker_Action_Interface
protected function setActionName($name)
{
+ $name = $this->truncate($name);
$this->actionName = $name;
}
+
protected function setActionType($type)
{
$this->actionType = $type;
}
+
protected function setActionUrl($url)
{
+ $url = self::excludeQueryParametersFromUrl($url, $this->idSite);
+ $url = $this->truncate($url);
$this->actionUrl = $url;
}
+ static public function excludeQueryParametersFromUrl($originalUrl, $idSite)
+ {
+ $website = Piwik_Common::getCacheWebsiteAttributes( $idSite );
+ $originalUrl = Piwik_Common::unsanitizeInputValue($originalUrl);
+ $parsedUrl = @parse_url($originalUrl);
+ if(empty($parsedUrl['query']))
+ {
+ return $originalUrl;
+ }
+ $excludedParameters = isset($website['excluded_parameters']) ? $website['excluded_parameters'] : array();
+ $parametersToExclude = array_merge($excludedParameters, self::$queryParametersToExclude);
+
+ $parametersToExclude = array_map('strtolower', $parametersToExclude);
+ $queryParameters = Piwik_Common::getArrayFromQueryString($parsedUrl['query']);
+
+ $validQuery = '';
+ $separator = '&';
+ foreach($queryParameters as $name => $value)
+ {
+ if(!in_array(strtolower($name), $parametersToExclude))
+ {
+ $validQuery .= $name.'='.$value.$separator;
+ }
+ }
+ $parsedUrl['query'] = substr($validQuery,0,-strlen($separator));
+ $url = Piwik_Common::getParseUrlReverse($parsedUrl);
+ printDebug('Excluded parameters "'.implode(',',$excludedParameters).'" from URL.
+ Before was <br/><code>"'.$originalUrl.'"</code>, <br/>
+ After is <br/><code>"'.$url.'"</code>');
+ return $url;
+ }
+
public function init()
{
$info = $this->extractUrlAndActionNameFromRequest();
@@ -136,6 +175,12 @@ class Piwik_Tracker_Action implements Piwik_Tracker_Action_Interface
$this->setActionUrl($info['url']);
}
+ protected function truncate( $label )
+ {
+ $limit = Piwik_Tracker_Config::getInstance()->Tracker['page_maximum_length'];
+ return substr($label, 0, $limit);
+ }
+
/**
* Loads the idaction of the current action name and the current action url.
* These idactions are used in the visitor logging table to link the visit information
@@ -288,8 +333,10 @@ class Piwik_Tracker_Action implements Piwik_Tracker_Action_Interface
$actionType = self::TYPE_ACTION_URL;
$url = Piwik_Common::getRequestVar( 'url', '', 'string', $this->request);
- // get the delimiter, by default '/'
- $actionCategoryDelimiter = Piwik_Tracker_Config::getInstance()->General['action_category_delimiter'];
+ // get the delimiter, by default '/'; BC, we read the old action_category_delimiter first (see #1067)
+ $actionCategoryDelimiter = isset(Piwik_Tracker_Config::getInstance()->General['action_category_delimiter'])
+ ? Piwik_Tracker_Config::getInstance()->General['action_category_delimiter']
+ : Piwik_Tracker_Config::getInstance()->General['action_url_category_delimiter'];
// create an array of the categories delimited by the delimiter
$split = explode($actionCategoryDelimiter, $actionName);
diff --git a/core/Tracker/Config.php b/core/Tracker/Config.php
index 1fd8a74a19..8d9e6f7499 100644
--- a/core/Tracker/Config.php
+++ b/core/Tracker/Config.php
@@ -48,36 +48,39 @@ class Piwik_Tracker_Config
* @var array
*/
public $config = array();
- protected $init = false;
+ protected $initialized = false;
public function init($pathIniFileUser = null, $pathIniFileGlobal = null)
{
+ if(is_null($pathIniFileGlobal))
+ {
+ $pathIniFileGlobal = PIWIK_USER_PATH . '/config/global.ini.php';
+ }
+ $this->configGlobal = _parse_ini_file($pathIniFileGlobal, true);
+
if(is_null($pathIniFileUser))
{
$pathIniFileUser = PIWIK_USER_PATH . '/config/config.ini.php';
}
- if(is_null($pathIniFileGlobal))
+ $this->configUser = _parse_ini_file($pathIniFileUser, true);
+ if($this->configUser)
{
- $pathIniFileGlobal = PIWIK_USER_PATH . '/config/global.ini.php';
- }
- $this->configUser = parse_ini_file($pathIniFileUser, true);
- $this->configGlobal = parse_ini_file($pathIniFileGlobal, true);
-
- foreach($this->configUser as $section => &$sectionValues)
- {
- foreach($sectionValues as $name => &$value)
- {
- if(is_array($value))
- {
- $value = array_map("html_entity_decode", $value);
- }
- else
+ foreach($this->configUser as $section => &$sectionValues)
+ {
+ foreach($sectionValues as $name => &$value)
{
- $value = html_entity_decode($value);
+ if(is_array($value))
+ {
+ $value = array_map("html_entity_decode", $value);
+ }
+ else
+ {
+ $value = html_entity_decode($value);
+ }
}
}
}
- $this->init = true;
+ $this->initialized = true;
}
/**
@@ -90,7 +93,7 @@ class Piwik_Tracker_Config
*/
public function __get( $name )
{
- if(!$this->init)
+ if(!$this->initialized)
{
$this->init();
}
diff --git a/core/Tracker/Db.php b/core/Tracker/Db.php
index 01291712d1..331fb3593a 100644
--- a/core/Tracker/Db.php
+++ b/core/Tracker/Db.php
@@ -155,6 +155,25 @@ abstract class Piwik_Tracker_Db
}
/**
+ * This function is a proxy to fetch(), used to maintain compatibility with Zend_Db interface
+ * @see fetch()
+ */
+ public function fetchOne( $query, $parameters = array() )
+ {
+ $result = $this->fetch($query, $parameters);
+ return is_array($result) && !empty($result) ? reset($result) : false;
+ }
+
+ /**
+ * This function is a proxy to fetch(), used to maintain compatibility with Zend_Db + PDO interface
+ * @see fetch()
+ */
+ public function exec( $query, $parameters = array() )
+ {
+ return $this->fetch($query, $parameters);
+ }
+
+ /**
* Return number of affected rows in last query
*
* @param mixed $queryResult Result from query()
diff --git a/core/Tracker/Generator.php b/core/Tracker/Generator.php
deleted file mode 100644
index 7a6dfdfe21..0000000000
--- a/core/Tracker/Generator.php
+++ /dev/null
@@ -1,621 +0,0 @@
-<?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$
- *
- * @category Piwik
- * @package Piwik
- */
-
-/**
- * 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
- * - 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
- * @subpackage Piwik_Tracker
- *
- * "Le Generator, il est trop Fort!"
- * - Random fan
- */
-class Piwik_Tracker_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 = $_POST = array();
-
- // init GET and REQUEST to the empty array
- $this->setFakeRequest();
-
- Piwik::createConfigObject(PIWIK_USER_PATH . '/config/config.ini.php');
- Zend_Registry::get('config')->disableSavingConfigurationFileUpdates();
-
- // setup database
- Piwik::createDatabaseObject();
-
- Piwik_Tracker_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
- */
- 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
- */
- 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_Tracker_Db::disableProfiling();
- }
-
- /**
- * This is called at the end of the Generator script.
- * Calls the Profiler output if the profiler is enabled.
- */
- public function end()
- {
- if($this->profiling)
- {
- Piwik::printSqlProfilingReportTracker();
- }
- Piwik_Tracker::disconnectDatabase();
- }
-
- /**
- * 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/
- */
- 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('download', 'link');
-
- // 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_Tracker_Config::getInstance()->Tracker['campaign_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 PIWIK_INCLUDE_PATH . '/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 PIWIK_INCLUDE_PATH . '/misc/generateVisitsData/UserAgent.php';
- require_once PIWIK_INCLUDE_PATH . '/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
- */
- protected function initProfiler()
- {
- /*
- * Inits the profiler
- */
- if($this->profiling)
- {
- if($this->reinitProfilingAtEveryRequest)
- {
- $all = Piwik_Query('TRUNCATE TABLE '.Piwik::prefixTable('log_profiling').'' );
- }
- }
- }
- /**
- * Launches the process and generates an exact number of nbVisitors
- * 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( $nbVisitors, $nbActionsMaxPerVisit )
- {
- $nbActionsTotal = 0;
- for($i = 0; $i < $nbVisitors; $i++)
- {
- $nbActions = mt_rand(1, $nbActionsMaxPerVisit);
- Piwik_Tracker_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.
- */
- 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( 'java' ,$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( 'gears' ,$this->getRandom01());
- $this->setCurrentRequest( 'ag' ,$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.)
- */
- protected function generateActionVisit()
- {
- // we don't keep the previous action values
- // reinit them to empty string
- $this->setCurrentRequest( 'download', '');
- $this->setCurrentRequest( 'link', '');
- $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
- $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_Tracker_Config::getInstance()->Tracker['campaign_var_name']
- && mt_rand(0,1)==0)
- {
- $url .= '&'. Piwik_Tracker_Config::getInstance()->Tracker['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);
- }
- }
-
- $this->setCurrentRequest( 'url' ,$url);
-
- // setup the title of the page
- $this->setCurrentRequest( 'action_name',$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
- */
- protected function setFakeRequest()
- {
- $_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 Tracker class and call the method to launch the recording
- *
- * This will save the visit in the database
- */
- protected function saveVisit()
- {
- $this->setFakeRequest();
- $process = new Piwik_Tracker_Generator_Tracker();
- $process->main();
- unset($process);
- }
-}
diff --git a/core/Tracker/Generator/Tracker.php b/core/Tracker/Generator/Tracker.php
deleted file mode 100644
index 92bd002d59..0000000000
--- a/core/Tracker/Generator/Tracker.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?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$
- *
- * @category Piwik
- * @package Piwik
- */
-
-/**
- * Fake Piwik_Tracker 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 Tracker Visit object to use so we use our own Tracker_visit @see Piwik_Tracker_Generator_Visit
- *
- * @package Piwik
- * @subpackage Piwik_Tracker
- */
-class Piwik_Tracker_Generator_Tracker extends Piwik_Tracker
-{
- /**
- * Does nothing instead of sending headers
- */
- protected function sendHeader($header)
- {
- }
-
- /**
- * Does nothing instead of displaying a 1x1 transparent pixel GIF
- */
- protected function end()
- {
- }
-
- /**
- * Returns our 'generator home made' Piwik_Tracker_Generator_Visit object.
- *
- * @return Piwik_Tracker_Generator_Visit
- */
- protected function getNewVisitObject()
- {
- $visit = new Piwik_Tracker_Generator_Visit();
- return $visit;
- }
-
- static function disconnectDatabase()
- {
- return;
- }
-}
diff --git a/core/Tracker/Generator/Visit.php b/core/Tracker/Generator/Visit.php
deleted file mode 100644
index 2937ebfe0d..0000000000
--- a/core/Tracker/Generator/Visit.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?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$
- *
- * @category Piwik
- * @package Piwik
- */
-
-/**
- * Fake Piwik_Tracker_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
- * @subpackage Piwik_Tracker
- */
-class Piwik_Tracker_Generator_Visit extends Piwik_Tracker_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 updateCookie()
- {
- @parent::updateCookie();
- }
-}
diff --git a/core/Tracker/GoalManager.php b/core/Tracker/GoalManager.php
index f1ff810891..29e6d508db 100644
--- a/core/Tracker/GoalManager.php
+++ b/core/Tracker/GoalManager.php
@@ -52,7 +52,7 @@ class Piwik_Tracker_GoalManager
return $goal;
}
}
- throw new Exception("The goal id = $idGoal couldn't be found.");
+ throw new Exception(Piwik_TranslateException('General_ExceptionGoalNotFound', array($idGoal)));
}
static public function getGoalIds( $idSite )
@@ -71,14 +71,14 @@ class Piwik_Tracker_GoalManager
return Piwik_PluginsManager::getInstance()->isPluginActivated('Goals');
}
- //TODO does this code work for manually triggered goals, with custom revenue?
function detectGoalsMatchingUrl($idSite, $action)
{
if(!$this->isGoalPluginEnabled())
{
return false;
}
- $url = $action->getActionUrl();
+ $sanitizedUrl = $action->getActionUrl();
+ $url = htmlspecialchars_decode($sanitizedUrl);
$actionType = $action->getActionType();
$goals = $this->getGoalDefinitions($idSite);
foreach($goals as $goal)
@@ -129,12 +129,12 @@ class Piwik_Tracker_GoalManager
$match = ($matched == 0);
break;
default:
- throw new Exception("Pattern type $pattern_type not valid.");
+ throw new Exception(Piwik_TranslateException('General_ExceptionInvalidGoalPattern', array($pattern_type)));
break;
}
if($match)
{
- $goal['url'] = $url;
+ $goal['url'] = $sanitizedUrl;
$this->convertedGoals[] = $goal;
}
}
@@ -154,7 +154,9 @@ class Piwik_Tracker_GoalManager
return false;
}
$goal = $goals[$idGoal];
- $goal['url'] = Piwik_Common::getRequestVar( 'url', '', 'string', $request);
+
+ $url = Piwik_Common::getRequestVar( 'url', '', 'string', $request);
+ $goal['url'] = Piwik_Tracker_Action::excludeQueryParametersFromUrl($url, $idSite);
$goal['revenue'] = Piwik_Common::getRequestVar('revenue', $goal['revenue'], 'float', $request);
$this->convertedGoals[] = $goal;
return true;
@@ -162,15 +164,22 @@ class Piwik_Tracker_GoalManager
function recordGoals($visitorInformation, $action)
{
- $location_country = isset($visitorInformation['location_country']) ? $visitorInformation['location_country'] : Piwik_Common::getCountry(Piwik_Common::getBrowserLanguage(), $enableLanguageToCountryGuess = Piwik_Tracker_Config::getInstance()->Tracker['enable_language_to_country_guess']);
- $location_continent = isset($visitorInformation['location_continent']) ? $visitorInformation['location_continent'] : Piwik_Common::getContinent($location_country);
+ $location_country = isset($visitorInformation['location_country'])
+ ? $visitorInformation['location_country']
+ : Piwik_Common::getCountry(
+ Piwik_Common::getBrowserLanguage(),
+ $enableLanguageToCountryGuess = Piwik_Tracker_Config::getInstance()->Tracker['enable_language_to_country_guess'], $visitorInformation['location_ip']
+ );
+
+ $location_continent = isset($visitorInformation['location_continent'])
+ ? $visitorInformation['location_continent']
+ : Piwik_Common::getContinent($location_country);
$goal = array(
'idvisit' => $visitorInformation['idvisit'],
'idsite' => $visitorInformation['idsite'],
'visitor_idcookie' => $visitorInformation['visitor_idcookie'],
'server_time' => Piwik_Tracker::getDatetimeFromTimestamp($visitorInformation['visit_last_action_time']),
- 'visit_server_date' => $visitorInformation['visit_server_date'],
'location_country' => $location_country,
'location_continent'=> $location_continent,
'visitor_returning' => $this->cookie->get( Piwik_Tracker::COOKIE_INDEX_VISITOR_RETURNING ),
@@ -207,14 +216,14 @@ class Piwik_Tracker_GoalManager
try {
Piwik_Tracker::getDatabase()->query(
- "INSERT INTO " . Piwik_Common::prefixTable('log_conversion') . " ($fields)
+ "INSERT IGNORE INTO " . Piwik_Common::prefixTable('log_conversion') . " ($fields)
VALUES ($bindFields) ", array_values($newGoal)
);
} catch( Exception $e) {
- if(Piwik_Tracker::isErrNo($e, '1062'))
+ if(Piwik_Tracker::getDatabase()->isErrNo($e, '1062'))
{
// integrity violation when same visit converts to the same goal twice
- printDebug("--> Goal already recorded for this (idvisit, idgoal)");
+ printDebug("--&gt; Goal already recorded for this (idvisit, idgoal)");
}
else
{
diff --git a/core/Tracker/Visit.php b/core/Tracker/Visit.php
index 18c636f483..b216f742e7 100644
--- a/core/Tracker/Visit.php
+++ b/core/Tracker/Visit.php
@@ -59,9 +59,10 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
$this->request = $requestArray;
$idsite = Piwik_Common::getRequestVar('idsite', 0, 'int', $this->request);
+ Piwik_PostEvent('Tracker.setRequest.idSite', $idsite);
if($idsite <= 0)
{
- throw new Exception("The 'idsite' in the request is invalid.");
+ throw new Exception(Piwik_TranslateException('General_ExceptionInvalidIdsite'));
}
$this->idsite = $idsite;
}
@@ -88,6 +89,9 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
*/
public function handle()
{
+ // the IP is needed by isExcluded() and GoalManager->recordGoals()
+ $this->visitorInfo['location_ip'] = Piwik_Common::getIp();
+
if($this->isExcluded())
{
return;
@@ -106,6 +110,7 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
// if we find a idgoal in the URL, but then the goal is not valid, this is most likely a fake request
if(!$someGoalsConverted)
{
+ printDebug('Invalid goal tracking request for goal id = '.$idGoal);
unset($goalManager);
return;
}
@@ -127,9 +132,10 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
$isLastActionInTheSameVisit = $this->isLastActionInTheSameVisit();
// Known visit when:
- // - the visitor has the Piwik cookie with the idcookie ID used by Piwik to match the visitor
- // OR
- // - the visitor doesn't have the Piwik cookie but could be match using heuristics @see recognizeTheVisitor()
+ // ( - the visitor has the Piwik cookie with the idcookie ID used by Piwik to match the visitor
+ // OR
+ // - the visitor doesn't have the Piwik cookie but could be match using heuristics @see recognizeTheVisitor()
+ // )
// AND
// - the last page view for this visitor was less than 30 minutes ago @see isLastActionInTheSameVisit()
if( $this->isVisitorKnown()
@@ -232,26 +238,31 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
visit_total_actions = visit_total_actions + 1, ";
$this->visitorInfo['visit_exit_idaction_url'] = $actionUrlId;
}
- $result = Piwik_Tracker::getDatabase()->query("/* SHARDING_ID_SITE = ". $this->idsite ." */
+ $sqlQuery = "/* SHARDING_ID_SITE = ". $this->idsite ." */
UPDATE ". Piwik_Common::prefixTable('log_visit')."
SET $sqlActionIdUpdate
$sqlUpdateGoalConverted
visit_last_action_time = ?,
- visit_total_time = UNIX_TIMESTAMP(visit_last_action_time) - UNIX_TIMESTAMP(visit_first_action_time)
- WHERE idvisit = ?
+ visit_total_time = ?
+ WHERE idsite = ?
+ AND idvisit = ?
AND visitor_idcookie = ?
- LIMIT 1",
- array( $datetimeServer,
- $this->visitorInfo['idvisit'],
- $this->visitorInfo['visitor_idcookie'] )
- );
+ LIMIT 1";
+ $sqlBind = array( $datetimeServer,
+ $visitTotalTime = $this->getCurrentTimestamp() - $this->visitorInfo['visit_first_action_time'],
+ $this->idsite,
+ $this->visitorInfo['idvisit'],
+ $this->visitorInfo['visitor_idcookie'] );
+
+ $result = Piwik_Tracker::getDatabase()->query($sqlQuery, $sqlBind);
+
+ printDebug('Updating visitor with idvisit='.$this->visitorInfo['idvisit'].', setting visit_last_action_time='.$datetimeServer.' and visit_total_time='.$visitTotalTime);
if(Piwik_Tracker::getDatabase()->rowCount($result) == 0)
{
throw new Piwik_Tracker_Visit_VisitorNotFoundInDatabase("The visitor with visitor_idcookie=".$this->visitorInfo['visitor_idcookie']." and idvisit=".$this->visitorInfo['idvisit']." wasn't found in the DB, we fallback to a new visitor");
}
$this->visitorInfo['idsite'] = $this->idsite;
- $this->visitorInfo['visit_server_date'] = $this->getCurrentDate();
// will be updated in cookie
$this->visitorInfo['time_spent_ref_action'] = $serverTime - $this->visitorInfo['visit_last_action_time'];
@@ -275,7 +286,6 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
.':'. Piwik_Common::getRequestVar( 'm', $this->getCurrentDate("i"), 'int', $this->request)
.':'. Piwik_Common::getRequestVar( 's', $this->getCurrentDate("s"), 'int', $this->request);
$serverTime = $this->getCurrentTimestamp();
- $serverDate = $this->getCurrentDate();
$idcookie = $this->getVisitorIdcookie();
$returningVisitor = $this->isVisitorKnown() ? 1 : 0;
@@ -283,47 +293,49 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
$defaultTimeOnePageVisit = Piwik_Tracker_Config::getInstance()->Tracker['default_time_one_page_visit'];
$userInfo = $this->getUserSettingsInformation();
- $country = Piwik_Common::getCountry($userInfo['location_browser_lang'], $enableLanguageToCountryGuess = Piwik_Tracker_Config::getInstance()->Tracker['enable_language_to_country_guess']);
+ $country = Piwik_Common::getCountry($userInfo['location_browser_lang'],
+ $enableLanguageToCountryGuess = Piwik_Tracker_Config::getInstance()->Tracker['enable_language_to_country_guess'],
+ $this->getVisitorIp());
$refererInfo = $this->getRefererInformation();
/**
* Save the visitor
*/
$this->visitorInfo = array(
- 'idsite' => $this->idsite,
- 'visitor_localtime' => $localTime,
- 'visitor_idcookie' => $idcookie,
- 'visitor_returning' => $returningVisitor,
- 'visit_first_action_time' => Piwik_Tracker::getDatetimeFromTimestamp($serverTime),
- 'visit_last_action_time' => Piwik_Tracker::getDatetimeFromTimestamp($serverTime),
- 'visit_server_date' => $serverDate,
- 'visit_entry_idaction_url' => $actionUrlId,
- 'visit_exit_idaction_url' => $actionUrlId,
- 'visit_total_actions' => 1,
- 'visit_total_time' => $defaultTimeOnePageVisit,
- 'visit_goal_converted' => $someGoalsConverted ? 1: 0,
- '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_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_gears' => $userInfo['config_gears'],
- 'config_silverlight' => $userInfo['config_silverlight'],
- 'config_cookie' => $userInfo['config_cookie'],
- 'location_ip' => $userInfo['location_ip'],
- 'location_browser_lang' => $userInfo['location_browser_lang'],
- 'location_country' => $country,
+ 'idsite' => $this->idsite,
+ 'visitor_localtime' => $localTime,
+ 'visitor_idcookie' => $idcookie,
+ 'visitor_returning' => $returningVisitor,
+ 'visit_server_date' => $this->getCurrentDate(),
+ 'visit_first_action_time' => Piwik_Tracker::getDatetimeFromTimestamp($serverTime),
+ 'visit_last_action_time' => Piwik_Tracker::getDatetimeFromTimestamp($serverTime),
+ 'visit_entry_idaction_url' => $actionUrlId,
+ 'visit_exit_idaction_url' => $actionUrlId,
+ 'visit_total_actions' => 1,
+ 'visit_total_time' => $defaultTimeOnePageVisit,
+ 'visit_goal_converted' => $someGoalsConverted ? 1: 0,
+ '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_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_gears' => $userInfo['config_gears'],
+ 'config_silverlight' => $userInfo['config_silverlight'],
+ 'config_cookie' => $userInfo['config_cookie'],
+ 'location_ip' => $this->getVisitorIp(),
+ 'location_browser_lang' => $userInfo['location_browser_lang'],
+ 'location_country' => $country,
);
Piwik_PostEvent('Tracker.newVisitorInformation', $this->visitorInfo);
@@ -389,8 +401,9 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
*/
protected function getVisitorIp()
{
- return Piwik_Common::getIp();
+ return $this->visitorInfo['location_ip'];
}
+
/**
* Returns the visitor's browser (user agent)
@@ -433,20 +446,62 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
*/
protected function isExcluded()
{
+ $excluded = false;
+
$ip = $this->getVisitorIp();
$ua = $this->getUserAgent();
/*
- * Live/Bing bot and Googlebot are evolving to detect cloaked websites.
+ * Live/Bing/MSN bot and Googlebot are evolving to detect cloaked websites.
* As a result, these sophisticated bots exhibit characteristics of
* browsers (cookies enabled, executing JavaScript, etc).
*/
- $excluded = preg_match('/65\.55/', long2ip($ip)) // Live/Bing
- || preg_match('/Googlebot/', $ua); // Googlebot
-
- /* custom filters can override the built-in filter above */
+ $dotIp = long2ip($ip);
+ if (strpos($dotIp, '65.55') === 0 // Live/Bing
+ || strpos($dotIp, '207.46') === 0 // MSN
+ || strpos($ua, 'Googlebot') !== false) // Googlebot
+ {
+ printDebug('Search bot detected, visit excluded');
+ $excluded = true;
+ }
+
+
+ /*
+ * Requests built with piwik.js will contain a rec=1 parameter. This is used as
+ * an indication that the request is made by a JS enabled device. By default, Piwik
+ * doesn't track non-JS visitors.
+ */
+ if(!$excluded)
+ {
+ $parameterForceRecord = 'rec';
+ $toRecord = Piwik_Common::getRequestVar($parameterForceRecord, false, 'int');
+ if(!$toRecord)
+ {
+ printDebug('GET parameter '.$parameterForceRecord.' not found in URL, request excluded');
+ $excluded = true;
+ }
+ }
+
+ /* custom filters can override the built-in filters above */
Piwik_PostEvent('Tracker.Visit.isExcluded', $excluded);
-
+
+ /*
+ * Following exclude operations happen after the hook.
+ * These are of higher priority and should not be overwritten by plugins.
+ */
+
+ // Checking if the Piwik ignore cookie is set
+ if(!$excluded)
+ {
+ $excluded = $this->isIgnoreCookieFound();
+ }
+
+ // Checking for excluded IPs
+ if(!$excluded)
+ {
+ $excluded = $this->isVisitorIpExcluded($ip);
+ }
+
if($excluded)
{
printDebug("Visitor excluded.");
@@ -455,8 +510,47 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
return false;
}
+
+ /**
+ * Looks for the ignore cookie that users can set in the Piwik admin screen.
+ * @return bool
+ */
+ protected function isIgnoreCookieFound()
+ {
+ $cookie = new Piwik_Cookie($this->getIgnoreVisitsCookieName());
+ if($cookie->isCookieFound())
+ {
+ printDebug('Piwik ignore cookie was found, visit not tracked.');
+ return true;
+ }
+ return false;
+ }
/**
+ * Checks if the visitor ip is in the excluded list
+ *
+ * @param $ip Long IP
+ * @return bool
+ */
+ protected function isVisitorIpExcluded($ip)
+ {
+ $websiteAttributes = Piwik_Common::getCacheWebsiteAttributes( $this->idsite );
+ if(!empty($websiteAttributes['excluded_ips']))
+ {
+ foreach($websiteAttributes['excluded_ips'] as $ipRange)
+ {
+ if($ip >= $ipRange[0]
+ && $ip <= $ipRange[1])
+ {
+ printDebug('Visitor IP '.long2ip($ip).' is excluded from being tracked');
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
* Returns the cookie name used for the Piwik Tracker cookie
*
* @return string
@@ -467,13 +561,33 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
}
/**
+ * Returns the cookie name used to ignore/exclude webmaster visits
+ *
+ * @return string
+ */
+ protected function getIgnoreVisitsCookieName()
+ {
+ return Piwik_Tracker_Config::getInstance()->Tracker['ignore_visits_cookie_name'];
+ }
+
+ /**
* Returns the cookie expiration date.
*
* @return int
*/
protected function getCookieExpire()
{
- return time() + Piwik_Tracker_Config::getInstance()->Tracker['cookie_expire'];
+ return $this->getCurrentTimestamp() + Piwik_Tracker_Config::getInstance()->Tracker['cookie_expire'];
+ }
+
+ /**
+ * Returns cookie path
+ *
+ * @return string
+ */
+ protected function getCookiePath()
+ {
+ return Piwik_Tracker_Config::getInstance()->Tracker['cookie_path'];
}
/**
@@ -508,7 +622,7 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
protected function recognizeTheVisitor()
{
$this->visitorKnown = false;
- $this->setCookie( new Piwik_Cookie( $this->getCookieName(), $this->getCookieExpire() ) );
+ $this->setCookie( new Piwik_Cookie( $this->getCookieName(), $this->getCookieExpire(), $this->getCookiePath() ) );
/*
* Case the visitor has the piwik cookie.
@@ -551,8 +665,8 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
$visitRow = Piwik_Tracker::getDatabase()->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,
+ visit_last_action_time,
+ visit_first_action_time,
idvisit,
visit_exit_idaction_url
FROM ".Piwik_Common::prefixTable('log_visit').
@@ -566,14 +680,14 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
&& 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['visit_last_action_time'] = strtotime($visitRow['visit_last_action_time']);
+ $this->visitorInfo['visit_first_action_time'] = strtotime($visitRow['visit_first_action_time']);
$this->visitorInfo['idvisit'] = $visitRow['idvisit'];
$this->visitorInfo['visit_exit_idaction_url'] = $visitRow['visit_exit_idaction_url'];
$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']).") ");
+ 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']).", first action = ".date("r", $this->visitorInfo['visit_first_action_time']) .")");
}
}
}
@@ -614,8 +728,6 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
$resolution = Piwik_Common::getRequestVar('res', 'unknown', 'string', $this->request);
- $ip = $this->getVisitorIp();
-
$browserLang = Piwik_Common::getBrowserLanguage();
$configurationHash = $this->getConfigHash(
@@ -633,7 +745,7 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
$plugin_Gears,
$plugin_Silverlight,
$plugin_Cookie,
- $ip,
+ $this->getVisitorIp(),
$browserLang);
$this->userSettingsInformation = array(
@@ -652,7 +764,6 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface
'config_gears' => $plugin_Gears,
'config_silverlight' => $plugin_Silverlight,
'config_cookie' => $plugin_Cookie,
- 'location_ip' => $ip,
'location_browser_lang' => $browserLang,
);
diff --git a/core/Tracker/javascriptTag.tpl b/core/Tracker/javascriptTag.tpl
index 6730f1b1b7..f0370aca3d 100644
--- a/core/Tracker/javascriptTag.tpl
+++ b/core/Tracker/javascriptTag.tpl
@@ -8,5 +8,5 @@ try {
piwikTracker.trackPageView();
piwikTracker.enableLinkTracking();
} catch( err ) {}
-</script><noscript><p><img src="http://{$piwikUrl}piwik.php?idsite={$idSite}" style="border:0" alt=""/></p></noscript>
+</script><noscript><p><img src="http://{$piwikUrl}piwik.php?idsite={$idSite}" style="border:0" alt="" /></p></noscript>
<!-- End Piwik Tag -->
diff --git a/core/Translate.php b/core/Translate.php
index 9d4b3a3efb..adc3379cf4 100644
--- a/core/Translate.php
+++ b/core/Translate.php
@@ -47,8 +47,12 @@ class Piwik_Translate
{
return;
}
-
- require PIWIK_INCLUDE_PATH . '/lang/' . $language . '.php';
+ $path = PIWIK_INCLUDE_PATH . '/lang/' . $language . '.php';
+ if(!is_readable($path))
+ {
+ throw new Exception(Piwik_TranslateException('General_ExceptionLanguageFileNotFound', array($language)));
+ }
+ require $path;
$this->mergeTranslationArray($translations);
$this->setLocale();
}
@@ -74,9 +78,11 @@ class Piwik_Translate
{
return $language;
}
+
Piwik_PostEvent('Translate.getLanguageToLoad', $language);
- if(is_null($language) || empty($language))
+ $language = Piwik_Common::getRequestVar('language', is_null($language) ? '' : $language, 'string');
+ if(empty($language))
{
$language = Zend_Registry::get('config')->General->default_language;
}
@@ -110,7 +116,7 @@ class Piwik_Translate
$moduleRegex .= $module.'|';
}
$moduleRegex = substr($moduleRegex, 0, -1);
- $moduleRegex .= ')_([^_]+)_js$#i';
+ $moduleRegex .= ')_.*_js$#i';
foreach($GLOBALS['Piwik_translations'] as $key => $value)
{
@@ -124,14 +130,22 @@ class Piwik_Translate
'for(var i in translations) { piwik_translations[i] = translations[i];} ';
$js .= 'function _pk_translate(translationStringId) { '.
'if( typeof(piwik_translations[translationStringId]) != \'undefined\' ){ return piwik_translations[translationStringId]; }'.
- 'return "The string "+translationStringId+" was not loaded in javascript. Make sure it is prefixed with _js";}';
+ 'return "The string "+translationStringId+" was not loaded in javascript. Make sure it is suffixed with _js and that you called {loadJavascriptTranslations plugins=\'\$YOUR_PLUGIN_NAME\'} before your javascript code.";}';
return $js;
}
+ /**
+ * Set locale
+ *
+ * @see http://php.net/setlocale
+ */
private function setLocale()
{
- setlocale(LC_ALL, $GLOBALS['Piwik_translations']['General_Locale']);
+ $locale = $GLOBALS['Piwik_translations']['General_Locale'];
+ $locale_variant = str_replace('UTF-8', 'UTF8', $locale);
+ setlocale(LC_ALL, $locale, $locale_variant);
+ setlocale(LC_CTYPE, '');
}
}
diff --git a/core/UpdateCheck.php b/core/UpdateCheck.php
index c5ebe07874..8e7b09b581 100644
--- a/core/UpdateCheck.php
+++ b/core/UpdateCheck.php
@@ -17,7 +17,7 @@
*/
class Piwik_UpdateCheck
{
- const CHECK_INTERVAL = 86400;
+ const CHECK_INTERVAL = 28800; // every 8 hours
const LAST_TIME_CHECKED = 'UpdateCheck_LastTimeChecked';
const LATEST_VERSION = 'UpdateCheck_LatestVersion';
const PIWIK_HOST = 'http://api.piwik.org/1.0/getLatestVersion/';
@@ -25,29 +25,34 @@ class Piwik_UpdateCheck
/**
* Check for a newer version
+ *
+ * @param bool $force Force check
*/
- public static function check()
+ public static function check($force = false)
{
$lastTimeChecked = Piwik_GetOption(self::LAST_TIME_CHECKED);
- if($lastTimeChecked === false
+ if($force || $lastTimeChecked === false
|| time() - self::CHECK_INTERVAL > $lastTimeChecked )
{
+ // set the time checked first, so that parallel Piwik requests don't all trigger the http requests
+ Piwik_SetOption(self::LAST_TIME_CHECKED, time(), $autoload = 1);
$parameters = array(
'piwik_version' => Piwik_Version::VERSION,
'php_version' => phpversion(),
'url' => Piwik_Url::getCurrentUrlWithoutQueryString(),
'trigger' => Piwik_Common::getRequestVar('module','','string'),
+ 'timezone' => Piwik_SitesManager_API::getInstance()->getDefaultTimezone(),
);
$url = self::PIWIK_HOST . "?" . http_build_query($parameters, '', '&');
$timeout = self::SOCKET_TIMEOUT;
try {
- $latestVersion = Piwik::sendHttpRequest($url, $timeout);
+ $latestVersion = Piwik_Http::sendHttpRequest($url, $timeout);
Piwik_SetOption(self::LATEST_VERSION, $latestVersion);
} catch(Exception $e) {
// e.g., disable_functions = fsockopen; allow_url_open = Off
+ Piwik_SetOption(self::LATEST_VERSION, '');
}
- Piwik_SetOption(self::LAST_TIME_CHECKED, time(), $autoload = 1);
}
}
diff --git a/core/Updater.php b/core/Updater.php
index 695826daa5..b45a16436f 100644
--- a/core/Updater.php
+++ b/core/Updater.php
@@ -10,9 +10,6 @@
* @package Piwik
*/
-// no direct access
-defined('PIWIK_INCLUDE_PATH') or die;
-
/**
* @see core/Option.php
*/
@@ -61,13 +58,23 @@ class Piwik_Updater
public function recordComponentSuccessfullyUpdated($name, $version)
{
try {
- Piwik_SetOption('version_'.$name, $version, $autoload = 1);
+ Piwik_SetOption($this->getNameInOptionTable($name), $version, $autoload = 1);
} catch(Exception $e) {
// case when the option table is not yet created (before 0.2.10)
}
}
/**
+ * Returns the flag name to use in the option table to record current schema version
+ * @param string $name
+ * @return string
+ */
+ private function getNameInOptionTable($name)
+ {
+ return 'version_'.$name;
+ }
+
+ /**
* Returns a list of components (core | plugin) that need to run through the upgrade process.
*
* @return array( componentName => array( file1 => version1, [...]), [...])
@@ -80,34 +87,79 @@ class Piwik_Updater
}
/**
+ * Component has a new version?
+ *
+ * @param string $componentName
+ * @return bool TRUE if compoment is to be updated; FALSE if not
+ */
+ public function hasNewVersion($componentName)
+ {
+ return isset($this->componentsWithNewVersion) &&
+ isset($this->componentsWithNewVersion[$componentName]);
+ }
+
+ /**
+ * Returns the list of SQL queries that would be executed during the update
+ *
+ * @return array of SQL queries
+ */
+ public function getSqlQueriesToExecute()
+ {
+ $queries = array();
+ foreach($this->componentsWithUpdateFile as $componentName => $componentUpdateInfo)
+ {
+ foreach($componentUpdateInfo as $file => $fileVersion)
+ {
+ require_once $file; // prefixed by PIWIK_INCLUDE_PATH
+
+ $className = $this->getUpdateClassName($componentName, $fileVersion);
+ if(class_exists($className, false))
+ {
+ $queriesForComponent = call_user_func( array($className, 'getSql'));
+ foreach($queriesForComponent as $query => $error) {
+ $queries[] = $query.';';
+ }
+ }
+ }
+ // unfortunately had to extract this query from the Piwik_Option class
+ $queries[] = 'UPDATE '.Piwik_Common::prefixTable('option').'
+ SET option_value = "' .$fileVersion.'"
+ WHERE option_name = "'. $this->getNameInOptionTable($componentName).'";';
+ }
+ return $queries;
+ }
+
+ private function getUpdateClassName($componentName, $fileVersion)
+ {
+ $suffix = strtolower(str_replace(array('-','.'), '_', $fileVersion));
+ if($componentName == 'core')
+ {
+ return 'Piwik_Updates_' . $suffix;
+ }
+ return 'Piwik_'. $componentName .'_Updates_' . $suffix;
+ }
+
+ /**
* Update the named component
*
- * @param string $name
+ * @param string $componentName 'core', or plugin name
* @return array of warning strings if applicable
*/
- public function update($name)
+ public function update($componentName)
{
$warningMessages = array();
- foreach($this->componentsWithUpdateFile[$name] as $file => $fileVersion)
+ foreach($this->componentsWithUpdateFile[$componentName] as $file => $fileVersion)
{
try {
require_once $file; // prefixed by PIWIK_INCLUDE_PATH
- if($name == 'core')
- {
- $className = 'Piwik_Updates_' . str_replace('.', '_', $fileVersion);
- }
- else
- {
- $className = 'Piwik_'. $name .'_Updates_' . str_replace('.', '_', $fileVersion);
- }
-
+ $className = $this->getUpdateClassName($componentName, $fileVersion);
if(class_exists($className, false))
{
call_user_func( array($className, 'update') );
}
- $this->recordComponentSuccessfullyUpdated($name, $fileVersion);
+ $this->recordComponentSuccessfullyUpdated($componentName, $fileVersion);
} catch( Piwik_Updater_UpdateErrorException $e) {
throw $e;
} catch( Exception $e) {
@@ -116,7 +168,7 @@ class Piwik_Updater
}
// to debug, create core/Updates/X.php, update the core/Version.php, throw an Exception in the try, and comment the following line
- $this->recordComponentSuccessfullyUpdated($name, $this->componentsWithNewVersion[$name][self::INDEX_NEW_VERSION]);
+ $this->recordComponentSuccessfullyUpdated($componentName, $this->componentsWithNewVersion[$componentName][self::INDEX_NEW_VERSION]);
return $warningMessages;
}
@@ -145,12 +197,16 @@ class Piwik_Updater
$files = glob( $pathToUpdates );
if($files === false)
{
- continue;
+ $files = array();
}
+
foreach( $files as $file)
{
$fileVersion = basename($file, '.php');
- if(version_compare($currentVersion, $fileVersion) == -1)
+ if( // if the update is from a newer version
+ version_compare($currentVersion, $fileVersion) == -1
+ // but we don't execute updates from non existing future releases
+ && version_compare($fileVersion, $newVersion) <= 0)
{
$componentsWithUpdateFile[$name][$file] = $fileVersion;
}
@@ -246,7 +302,8 @@ class Piwik_Updater
try {
Piwik_Exec( $update );
} catch(Exception $e) {
- if(($ignoreError === false) || !Zend_Registry::get('db')->isErrNo($e, $ignoreError))
+ if(($ignoreError === false)
+ || !Zend_Registry::get('db')->isErrNo($e, $ignoreError))
{
$message = $file .":\nError trying to execute the query '". $update ."'.\nThe error was: ". $e->getMessage();
throw new Piwik_Updater_UpdateErrorException($message);
diff --git a/core/iUpdate.php b/core/Updates.php
index 5b9888558c..3d5e4df537 100644
--- a/core/iUpdate.php
+++ b/core/Updates.php
@@ -11,15 +11,26 @@
*/
/**
- * Interface to be implemented by update scripts
+ * Abstract class for update scripts
*
* @example core/Updates/0.4.2.php
* @package Piwik
*/
-interface Piwik_iUpdate
+abstract class Piwik_Updates
{
/**
+ * Return SQL to be executed in this update
+ *
+ * @param string Adapter name
+ * @return array
+ */
+ static function getSql($adapter = 'PDO_MYSQL')
+ {
+ return array();
+ }
+
+ /**
* Incremental version update
*/
- static function update();
+ abstract static function update();
}
diff --git a/core/Updates/0.2.10.php b/core/Updates/0.2.10.php
index ef64070237..6caf21ae46 100644
--- a/core/Updates/0.2.10.php
+++ b/core/Updates/0.2.10.php
@@ -13,14 +13,46 @@
/**
* @package Updates
*/
-class Piwik_Updates_0_2_10 implements Piwik_iUpdate
+class Piwik_Updates_0_2_10 extends Piwik_Updates
{
- static function update()
+ static function getSql($adapter = 'PDO_MYSQL')
{
$tables = Piwik::getTablesCreateSql();
- Piwik_Updater::updateDatabase(__FILE__, array(
+
+ return array(
$tables['option'] => false,
- ));
+
+ // 0.1.7 [463]
+ 'ALTER IGNORE TABLE `'. Piwik_Common::prefixTable('log_visit') .'`
+ CHANGE `location_provider` `location_provider` VARCHAR( 100 ) DEFAULT NULL' => '1054',
+
+ // 0.1.7 [470]
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('logger_api_call') .'`
+ CHANGE `parameter_names_default_values` `parameter_names_default_values` TEXT,
+ CHANGE `parameter_values` `parameter_values` TEXT,
+ CHANGE `returned_value` `returned_value` TEXT' => false,
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('logger_error') .'`
+ CHANGE `message` `message` TEXT' => false,
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('logger_exception') .'`
+ CHANGE `message` `message` TEXT' => false,
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('logger_message') .'`
+ CHANGE `message` `message` TEXT' => false,
+
+ // 0.2.2 [489]
+ 'ALTER IGNORE TABLE `'. Piwik_Common::prefixTable('site') .'`
+ CHANGE `feedburnerName` `feedburnerName` VARCHAR( 100 ) DEFAULT NULL' => '1054',
+ );
+ }
+
+ static function update()
+ {
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
+
+ $obsoleteFile = '/plugins/ExamplePlugin/API.php';
+ if(file_exists(PIWIK_INCLUDE_PATH . $obsoleteFile))
+ {
+ @unlink(PIWIK_INCLUDE_PATH . $obsoleteFile);
+ }
$obsoleteDirectories = array(
'/plugins/AdminHome',
diff --git a/core/Updates/0.2.12.php b/core/Updates/0.2.12.php
index 5227f68586..3a4b33ac8d 100644
--- a/core/Updates/0.2.12.php
+++ b/core/Updates/0.2.12.php
@@ -13,15 +13,24 @@
/**
* @package Updates
*/
-class Piwik_Updates_0_2_12 implements Piwik_iUpdate
+class Piwik_Updates_0_2_12 extends Piwik_Updates
{
- static function update()
+ static function getSql($adapter = 'PDO_MYSQL')
{
- Piwik_Updater::updateDatabase(__FILE__, array(
- 'ALTER TABLE `'. Piwik::prefixTable('site') .'`
+ return array(
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('site') .'`
CHANGE `ts_created` `ts_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL' => false,
- 'ALTER TABLE `'. Piwik::prefixTable('log_visit') .'`
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('log_visit') .'`
DROP `config_color_depth`' => false,
- ));
+
+ // 0.2.12 [673]
+ // Note: requires INDEX privilege
+ 'DROP INDEX index_idaction ON `'. Piwik_Common::prefixTable('log_action') .'`' => '1091',
+ );
+ }
+
+ static function update()
+ {
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
}
}
diff --git a/core/Updates/0.2.13.php b/core/Updates/0.2.13.php
index fb8a875f54..11f49ff709 100644
--- a/core/Updates/0.2.13.php
+++ b/core/Updates/0.2.13.php
@@ -13,14 +13,20 @@
/**
* @package Updates
*/
-class Piwik_Updates_0_2_13 implements Piwik_iUpdate
+class Piwik_Updates_0_2_13 extends Piwik_Updates
{
- static function update()
+ static function getSql($adapter = 'PDO_MYSQL')
{
$tables = Piwik::getTablesCreateSql();
- Piwik_Updater::updateDatabase(__FILE__, array(
- 'DROP TABLE IF EXISTS `'. Piwik::prefixTable('option') .'`' => false,
+
+ return array(
+ 'DROP TABLE IF EXISTS `'. Piwik_Common::prefixTable('option') .'`' => false,
$tables['option'] => false,
- ));
+ );
+ }
+
+ static function update()
+ {
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
}
}
diff --git a/core/Updates/0.2.24.php b/core/Updates/0.2.24.php
index a5a19837e6..81a0e4df22 100644
--- a/core/Updates/0.2.24.php
+++ b/core/Updates/0.2.24.php
@@ -13,17 +13,22 @@
/**
* @package Updates
*/
-class Piwik_Updates_0_2_24 implements Piwik_iUpdate
+class Piwik_Updates_0_2_24 extends Piwik_Updates
{
- static function update()
+ static function getSql($adapter = 'PDO_MYSQL')
{
- Piwik_Updater::updateDatabase(__FILE__, array(
+ return array(
'CREATE INDEX index_type_name
- ON '. Piwik::prefixTable('log_action') .' (type, name(15))' => false,
+ ON '. Piwik_Common::prefixTable('log_action') .' (type, name(15))' => false,
'CREATE INDEX index_idsite_date
- ON '. Piwik::prefixTable('log_visit') .' (idsite, visit_server_date)' => false,
- 'DROP INDEX index_idsite ON '. Piwik::prefixTable('log_visit') => false,
- 'DROP INDEX index_visit_server_date ON '. Piwik::prefixTable('log_visit') => false,
- ));
+ ON '. Piwik_Common::prefixTable('log_visit') .' (idsite, visit_server_date)' => false,
+ 'DROP INDEX index_idsite ON '. Piwik_Common::prefixTable('log_visit') => false,
+ 'DROP INDEX index_visit_server_date ON '. Piwik_Common::prefixTable('log_visit') => false,
+ );
+ }
+
+ static function update()
+ {
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
}
}
diff --git a/core/Updates/0.2.27.php b/core/Updates/0.2.27.php
index 3424216245..57cf3465d6 100644
--- a/core/Updates/0.2.27.php
+++ b/core/Updates/0.2.27.php
@@ -13,12 +13,17 @@
/**
* @package Updates
*/
-class Piwik_Updates_0_2_27 implements Piwik_iUpdate
+class Piwik_Updates_0_2_27 extends Piwik_Updates
{
- static function update()
+ static function getSql($adapter = 'PDO_MYSQL')
{
- $sqlarray[ 'ALTER TABLE `'. Piwik::prefixTable('log_visit') .'`
- ADD `visit_goal_converted` VARCHAR( 1 ) NOT NULL AFTER `visit_total_time`' ] = false;
+ $sqlarray = array(
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('log_visit') .'`
+ ADD `visit_goal_converted` VARCHAR( 1 ) NOT NULL AFTER `visit_total_time`' => false,
+ // 0.2.27 [826]
+ 'ALTER IGNORE TABLE `'. Piwik_Common::prefixTable('log_visit') .'`
+ CHANGE `visit_goal_converted` `visit_goal_converted` TINYINT(1) NOT NULL' => false,
+ );
$tables = Piwik::getTablesCreateSql();
$sqlarray[ $tables['log_conversion'] ] = false;
@@ -33,6 +38,11 @@ class Piwik_Updates_0_2_27 implements Piwik_iUpdate
}
}
- Piwik_Updater::updateDatabase(__FILE__, $sqlarray);
+ return $sqlarray;
+ }
+
+ static function update()
+ {
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
}
}
diff --git a/core/Updates/0.2.32.php b/core/Updates/0.2.32.php
new file mode 100644
index 0000000000..5594b6f521
--- /dev/null
+++ b/core/Updates/0.2.32.php
@@ -0,0 +1,37 @@
+<?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$
+ *
+ * @category Piwik
+ * @package Updates
+ */
+
+/**
+ * @package Updates
+ */
+class Piwik_Updates_0_2_32 extends Piwik_Updates
+{
+ static function getSql($adapter = 'PDO_MYSQL')
+ {
+ return array(
+ // 0.2.32 [941]
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('access') .'`
+ CHANGE `login` `login` VARCHAR( 100 ) NOT NULL' => false,
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('user') .'`
+ CHANGE `login` `login` VARCHAR( 100 ) NOT NULL' => false,
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('user_dashboard') .'`
+ CHANGE `login` `login` VARCHAR( 100 ) NOT NULL' => '1146',
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('user_language') .'`
+ CHANGE `login` `login` VARCHAR( 100 ) NOT NULL' => '1146',
+ );
+ }
+
+ static function update()
+ {
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
+ }
+}
diff --git a/core/Updates/0.2.33.php b/core/Updates/0.2.33.php
index ce936f5731..ed94e78fb8 100644
--- a/core/Updates/0.2.33.php
+++ b/core/Updates/0.2.33.php
@@ -13,10 +13,18 @@
/**
* @package Updates
*/
-class Piwik_Updates_0_2_33 implements Piwik_iUpdate
+class Piwik_Updates_0_2_33 extends Piwik_Updates
{
- static function update()
+ static function getSql($adapter = 'PDO_MYSQL')
{
+ $sqlarray = array(
+ // 0.2.33 [1020]
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('user_dashboard') .'`
+ CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci ' => '1146',
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('user_language') .'`
+ CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci ' => '1146',
+ );
+
// alter table to set the utf8 collation
$tablesToAlter = Piwik::getTablesInstalled(true);
foreach($tablesToAlter as $table) {
@@ -24,6 +32,11 @@ class Piwik_Updates_0_2_33 implements Piwik_iUpdate
CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci ' ] = false;
}
- Piwik_Updater::updateDatabase(__FILE__, $sqlarray);
+ return $sqlarray;
+ }
+
+ static function update()
+ {
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
}
}
diff --git a/core/Updates/0.2.34.php b/core/Updates/0.2.34.php
index a36b284589..ca7ae16211 100644
--- a/core/Updates/0.2.34.php
+++ b/core/Updates/0.2.34.php
@@ -13,13 +13,13 @@
/**
* @package Updates
*/
-class Piwik_Updates_0_2_34 implements Piwik_iUpdate
+class Piwik_Updates_0_2_34 extends Piwik_Updates
{
static function update()
{
// force regeneration of cache files following #648
Piwik::setUserIsSuperUser();
- $allSiteIds = Piwik_SitesManager_API::getAllSitesId();
+ $allSiteIds = Piwik_SitesManager_API::getInstance()->getAllSitesId();
Piwik_Common::regenerateCacheWebsiteAttributes($allSiteIds);
}
}
diff --git a/core/Updates/0.2.35.php b/core/Updates/0.2.35.php
index 3fbeb4ab87..4ceadcf30d 100644
--- a/core/Updates/0.2.35.php
+++ b/core/Updates/0.2.35.php
@@ -13,13 +13,18 @@
/**
* @package Updates
*/
-class Piwik_Updates_0_2_35 implements Piwik_iUpdate
+class Piwik_Updates_0_2_35 extends Piwik_Updates
{
- static function update()
+ static function getSql($adapter = 'PDO_MYSQL')
{
- Piwik_Updater::updateDatabase(__FILE__, array(
- 'ALTER TABLE `'. Piwik::prefixTable('user_dashboard') .'`
+ return array(
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('user_dashboard') .'`
CHANGE `layout` `layout` TEXT NOT NULL' => false,
- ));
+ );
+ }
+
+ static function update()
+ {
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
}
}
diff --git a/core/Updates/0.2.37.php b/core/Updates/0.2.37.php
index ed1a36160d..51d77a6c32 100644
--- a/core/Updates/0.2.37.php
+++ b/core/Updates/0.2.37.php
@@ -13,14 +13,19 @@
/**
* @package Updates
*/
-class Piwik_Updates_0_2_37 implements Piwik_iUpdate
+class Piwik_Updates_0_2_37 extends Piwik_Updates
{
- static function update()
+ static function getSql($adapter = 'PDO_MYSQL')
{
- Piwik_Updater::updateDatabase(__FILE__, array(
- 'DELETE FROM `'. Piwik::prefixTable('user_dashboard') ."`
+ return array(
+ 'DELETE FROM `'. Piwik_Common::prefixTable('user_dashboard') ."`
WHERE layout LIKE '%.getLastVisitsGraph%'
OR layout LIKE '%.getLastVisitsReturningGraph%'" => false,
- ));
+ );
+ }
+
+ static function update()
+ {
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
}
}
diff --git a/core/Updates/0.4.1.php b/core/Updates/0.4.1.php
index 93fe25064c..ec1995e399 100644
--- a/core/Updates/0.4.1.php
+++ b/core/Updates/0.4.1.php
@@ -13,15 +13,20 @@
/**
* @package Updates
*/
-class Piwik_Updates_0_4_1 implements Piwik_iUpdate
+class Piwik_Updates_0_4_1 extends Piwik_Updates
{
- static function update()
+ static function getSql($adapter = 'PDO_MYSQL')
{
- Piwik_Updater::updateDatabase(__FILE__, array(
- 'ALTER TABLE `'. Piwik::prefixTable('log_conversion') .'`
+ return array(
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('log_conversion') .'`
CHANGE `idlink_va` `idlink_va` INT(11) DEFAULT NULL' => false,
- 'ALTER TABLE `'. Piwik::prefixTable('log_conversion') .'`
- CHANGE `idaction` `idaction` INT(11) DEFAULT NULL' => false,
- ));
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('log_conversion') .'`
+ CHANGE `idaction` `idaction` INT(11) DEFAULT NULL' => '1054',
+ );
+ }
+
+ static function update()
+ {
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
}
}
diff --git a/core/Updates/0.4.2.php b/core/Updates/0.4.2.php
index ef91786033..9be470e46a 100644
--- a/core/Updates/0.4.2.php
+++ b/core/Updates/0.4.2.php
@@ -13,19 +13,24 @@
/**
* @package Updates
*/
-class Piwik_Updates_0_4_2 implements Piwik_iUpdate
+class Piwik_Updates_0_4_2 extends Piwik_Updates
{
- // when restoring (possibly) previousy dropped columns, ignore mysql code error 1060: duplicate column
- static function update()
+ static function getSql($adapter = 'PDO_MYSQL')
{
- Piwik_Updater::updateDatabase(__FILE__, array(
- 'ALTER TABLE `'. Piwik::prefixTable('log_visit') .'`
+ return array(
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('log_visit') .'`
ADD `config_java` TINYINT(1) NOT NULL AFTER `config_flash`' => '1060',
- 'ALTER TABLE `'. Piwik::prefixTable('log_visit') .'`
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('log_visit') .'`
ADD `config_quicktime` TINYINT(1) NOT NULL AFTER `config_director`' => '1060',
- 'ALTER TABLE `'. Piwik::prefixTable('log_visit') .'`
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('log_visit') .'`
ADD `config_gears` TINYINT(1) NOT NULL AFTER `config_windowsmedia`,
ADD `config_silverlight` TINYINT(1) NOT NULL AFTER `config_gears`' => false,
- ));
+ );
+ }
+
+ // when restoring (possibly) previousy dropped columns, ignore mysql code error 1060: duplicate column
+ static function update()
+ {
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
}
}
diff --git a/core/Updates/0.4.3.php b/core/Updates/0.4.3.php
deleted file mode 100644
index b4524186a1..0000000000
--- a/core/Updates/0.4.3.php
+++ /dev/null
@@ -1,65 +0,0 @@
-<?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$
- *
- * @category Piwik
- * @package Updates
- */
-
-/**
- * @package Updates
- */
-class Piwik_Updates_0_4_3 implements Piwik_iUpdate
-{
- static function update()
- {
- Piwik_Updater::updateDatabase(__FILE__, array(
- // 0.1.7 [463]
- 'ALTER IGNORE TABLE `'. Piwik::prefixTable('log_visit') .'`
- CHANGE `location_provider` `location_provider` VARCHAR( 100 ) DEFAULT NULL' => '1054',
- // 0.1.7 [470]
- 'ALTER TABLE `'. Piwik::prefixTable('logger_api_call') .'`
- CHANGE `parameter_names_default_values` `parameter_names_default_values` TEXT,
- CHANGE `parameter_values` `parameter_values` TEXT,
- CHANGE `returned_value` `returned_value` TEXT' => false,
- 'ALTER TABLE `'. Piwik::prefixTable('logger_error') .'`
- CHANGE `message` `message` TEXT' => false,
- 'ALTER TABLE `'. Piwik::prefixTable('logger_exception') .'`
- CHANGE `message` `message` TEXT' => false,
- 'ALTER TABLE `'. Piwik::prefixTable('logger_message') .'`
- CHANGE `message` `message` TEXT' => false,
- // 0.2.2 [489]
- 'ALTER IGNORE TABLE `'. Piwik::prefixTable('site') .'`
- CHANGE `feedburnerName` `feedburnerName` VARCHAR( 100 ) DEFAULT NULL' => '1054',
- // 0.2.12 [673]
- // Note: requires INDEX privilege
- 'DROP INDEX index_idaction ON `'. Piwik::prefixTable('log_action') .'`' => '1091',
- // 0.2.27 [826]
- 'ALTER IGNORE TABLE `'. Piwik::prefixTable('log_visit') .'`
- CHANGE `visit_goal_converted` `visit_goal_converted` TINYINT(1) NOT NULL' => false,
- // 0.2.32 [941]
- 'ALTER TABLE `'. Piwik::prefixTable('access') .'`
- CHANGE `login` `login` VARCHAR( 100 ) NOT NULL' => false,
- 'ALTER TABLE `'. Piwik::prefixTable('user') .'`
- CHANGE `login` `login` VARCHAR( 100 ) NOT NULL' => false,
- 'ALTER TABLE `'. Piwik::prefixTable('user_dashboard') .'`
- CHANGE `login` `login` VARCHAR( 100 ) NOT NULL' => '1146',
- 'ALTER TABLE `'. Piwik::prefixTable('user_language') .'`
- CHANGE `login` `login` VARCHAR( 100 ) NOT NULL' => '1146',
- // 0.2.33 [1020]
- 'ALTER TABLE `'. Piwik::prefixTable('user_dashboard') .'`
- CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci ' => '1146',
- 'ALTER TABLE `'. Piwik::prefixTable('user_language') .'`
- CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci ' => '1146',
- // 0.4 [1140]
- 'ALTER TABLE `'. Piwik::prefixTable('log_visit') .'`
- CHANGE `location_ip` `location_ip` BIGINT UNSIGNED NOT NULL' => false,
- 'ALTER TABLE `'. Piwik::prefixTable('logger_api_call') .'`
- CHANGE `caller_ip` `caller_ip` BIGINT UNSIGNED' => false,
- ));
- }
-}
diff --git a/core/Updates/0.4.4.php b/core/Updates/0.4.4.php
index e6c986a0b4..e29c07eed8 100644
--- a/core/Updates/0.4.4.php
+++ b/core/Updates/0.4.4.php
@@ -13,14 +13,18 @@
/**
* @package Updates
*/
-class Piwik_Updates_0_4_4 implements Piwik_iUpdate
+class Piwik_Updates_0_4_4 extends Piwik_Updates
{
static function update()
{
- $obsoleteFile = '/libs/open-flash-chart/php-ofc-library/ofc_upload_image.php';
- if(file_exists(PIWIK_DOCUMENT_ROOT . $obsoleteFile))
+ $obsoleteFile = PIWIK_DOCUMENT_ROOT . '/libs/open-flash-chart/php-ofc-library/ofc_upload_image.php';
+ if(file_exists($obsoleteFile))
{
- @unlink(PIWIK_DOCUMENT_ROOT . $obsoleteFile);
+ $rc = @unlink($obsoleteFile);
+ if(!$rc)
+ {
+ throw new Exception(Piwik_TranslateException('General_ExceptionUndeletableFile', array($obsoleteFile)));
+ }
}
}
}
diff --git a/core/Updates/0.4.php b/core/Updates/0.4.php
index a0ac958c11..cdd9911bdd 100644
--- a/core/Updates/0.4.php
+++ b/core/Updates/0.4.php
@@ -13,19 +13,25 @@
/**
* @package Updates
*/
-class Piwik_Updates_0_4 implements Piwik_iUpdate
+class Piwik_Updates_0_4 extends Piwik_Updates
{
- static function update()
+ static function getSql($adapter = 'PDO_MYSQL')
{
- Piwik_Updater::updateDatabase(__FILE__, array(
- 'UPDATE `'. Piwik::prefixTable('log_visit') .'`
+ return array(
+ // 0.4 [1140]
+ 'UPDATE `'. Piwik_Common::prefixTable('log_visit') .'`
SET location_ip=location_ip+CAST(POW(2,32) AS UNSIGNED) WHERE location_ip < 0' => false,
- 'ALTER TABLE `'. Piwik::prefixTable('log_visit') .'`
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('log_visit') .'`
CHANGE `location_ip` `location_ip` BIGINT UNSIGNED NOT NULL' => false,
- 'UPDATE `'. Piwik::prefixTable('logger_api_call') .'`
+ 'UPDATE `'. Piwik_Common::prefixTable('logger_api_call') .'`
SET caller_ip=caller_ip+CAST(POW(2,32) AS UNSIGNED) WHERE caller_ip < 0' => false,
- 'ALTER TABLE `'. Piwik::prefixTable('logger_api_call') .'`
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('logger_api_call') .'`
CHANGE `caller_ip` `caller_ip` BIGINT UNSIGNED' => false,
- ));
+ );
+ }
+
+ static function update()
+ {
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
}
}
diff --git a/core/Updates/0.5.4.php b/core/Updates/0.5.4.php
new file mode 100644
index 0000000000..859301e3ff
--- /dev/null
+++ b/core/Updates/0.5.4.php
@@ -0,0 +1,75 @@
+<?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$
+ *
+ * @category Piwik
+ * @package Updates
+ */
+
+/**
+ * @package Updates
+ */
+class Piwik_Updates_0_5_4 extends Piwik_Updates
+{
+ static function getSql($adapter = 'PDO_MYSQL')
+ {
+ return array(
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('log_action') .'`
+ CHANGE `name` `name` TEXT' => false,
+ );
+ }
+
+ static function update()
+ {
+ $config = Zend_Registry::get('config');
+ $salt = Piwik_Common::generateUniqId();
+ if(!isset($config->superuser->salt))
+ {
+ try {
+ if(is_writable( Piwik_Config::getDefaultUserConfigPath() ))
+ {
+ $superuser_info = $config->superuser->toArray();
+ $superuser_info['salt'] = $salt;
+ $config->superuser = $superuser_info;
+
+ $config->__destruct();
+ Piwik::createConfigObject();
+ }
+ else
+ {
+ throw new Exception('mandatory update failed');
+ }
+ } catch(Exception $e) {
+ throw new Piwik_Updater_UpdateErrorException("Please edit your config/config.ini.php file and add below <code>[superuser]</code> the following line: <br /><code>salt = $salt</code>");
+ }
+ }
+
+ $config = Zend_Registry::get('config');
+ $plugins = $config->Plugins->toArray();
+ if(!in_array('MultiSites', $plugins))
+ {
+ try {
+ if(is_writable( Piwik_Config::getDefaultUserConfigPath() ))
+ {
+ $plugins[] = 'MultiSites';
+ $config->Plugins = $plugins;
+
+ $config->__destruct();
+ Piwik::createConfigObject();
+ }
+ else
+ {
+ throw new Exception('optional update failed');
+ }
+ } catch(Exception $e) {
+ throw new Exception("You can now enable the new MultiSites plugin in the Plugins screen in the Piwik admin!");
+ }
+ }
+
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
+ }
+}
diff --git a/core/Updates/0.5.5.php b/core/Updates/0.5.5.php
new file mode 100644
index 0000000000..a82cf44cd7
--- /dev/null
+++ b/core/Updates/0.5.5.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$
+ *
+ * @category Piwik
+ * @package Updates
+ */
+
+/**
+ * @package Updates
+ */
+class Piwik_Updates_0_5_5 extends Piwik_Updates
+{
+ static function getSql($adapter = 'PDO_MYSQL')
+ {
+ $sqlarray = array(
+ 'DROP INDEX index_idsite_date ON ' . Piwik_Common::prefixTable('log_visit') => '1091',
+ 'CREATE INDEX index_idsite_date_config ON ' . Piwik_Common::prefixTable('log_visit') . ' (idsite, visit_server_date, config_md5config(8))' => '1061',
+ );
+
+ $tables = Piwik::getTablesInstalled();
+ foreach($tables as $tableName)
+ {
+ if(preg_match('/archive_/', $tableName) == 1)
+ {
+ $sqlarray[ 'DROP INDEX index_all ON '. $tableName ] = '1091';
+ }
+ if(preg_match('/archive_numeric_/', $tableName) == 1)
+ {
+ $sqlarray[ 'CREATE INDEX index_idsite_dates_period ON '. $tableName .' (idsite, date1, date2, period)' ] = '1061';
+ }
+ }
+
+ return $sqlarray;
+ }
+
+ static function update()
+ {
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
+
+ }
+}
diff --git a/core/Updates/0.5.php b/core/Updates/0.5.php
index 108e651617..5d23f8a811 100644
--- a/core/Updates/0.5.php
+++ b/core/Updates/0.5.php
@@ -13,21 +13,26 @@
/**
* @package Updates
*/
-class Piwik_Updates_0_5 implements Piwik_iUpdate
+class Piwik_Updates_0_5 extends Piwik_Updates
{
+ static function getSql($adapter = 'PDO_MYSQL')
+ {
+ return array(
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('log_action') . ' ADD COLUMN `hash` INTEGER(10) UNSIGNED NOT NULL AFTER `name`;' => '1060',
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('log_visit') . ' CHANGE visit_exit_idaction visit_exit_idaction_url INTEGER(11) NOT NULL;' => '1054',
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('log_visit') . ' CHANGE visit_entry_idaction visit_entry_idaction_url INTEGER(11) NOT NULL;' => '1054',
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('log_link_visit_action') . ' CHANGE `idaction_ref` `idaction_url_ref` INTEGER(10) UNSIGNED NOT NULL;' => '1054',
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('log_link_visit_action') . ' CHANGE `idaction` `idaction_url` INTEGER(10) UNSIGNED NOT NULL;' => '1054',
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('log_link_visit_action') . ' ADD COLUMN `idaction_name` INTEGER(10) UNSIGNED AFTER `idaction_url_ref`;' => '1060',
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('log_conversion') . ' CHANGE `idaction` `idaction_url` INTEGER(11) UNSIGNED NOT NULL;' => '1054',
+ 'UPDATE ' . Piwik_Common::prefixTable('log_action') . ' SET `hash` = CRC32(name);' => false,
+ 'CREATE INDEX index_type_hash ON ' . Piwik_Common::prefixTable('log_action') . ' (type, hash);' => '1061',
+ 'DROP INDEX index_type_name ON ' . Piwik_Common::prefixTable('log_action') . ';' => '1091',
+ );
+ }
+
static function update()
{
- Piwik_Updater::updateDatabase(__FILE__, array(
- 'ALTER TABLE ' . Piwik::prefixTable('log_action'). ' ADD COLUMN `hash` INTEGER(10) UNSIGNED NOT NULL AFTER `name`;' => false,
- 'UPDATE '. Piwik::prefixTable('log_action'). ' SET `hash` = CRC32(name);' => false,
- 'CREATE INDEX index_type_hash ON '. Piwik::prefixTable('log_action') .' (type, hash);' => false,
- 'DROP INDEX index_type_name ON '. Piwik::prefixTable('log_action') .';' => false,
- 'ALTER TABLE '. Piwik::prefixTable('log_visit') .' CHANGE visit_exit_idaction visit_exit_idaction_url INTEGER(11) NOT NULL;' => false,
- 'ALTER TABLE '. Piwik::prefixTable('log_visit') .' CHANGE visit_entry_idaction visit_entry_idaction_url INTEGER(11) NOT NULL;' => false,
- 'ALTER TABLE ' . Piwik::prefixTable('log_link_visit_action'). ' CHANGE `idaction_ref` `idaction_url_ref` INTEGER(10) UNSIGNED NOT NULL;' => false,
- 'ALTER TABLE ' . Piwik::prefixTable('log_link_visit_action'). ' CHANGE `idaction` `idaction_url` INTEGER(10) UNSIGNED NOT NULL;' => false,
- 'ALTER TABLE ' . Piwik::prefixTable('log_link_visit_action'). ' ADD COLUMN `idaction_name` INTEGER(10) UNSIGNED AFTER `idaction_url_ref`;' => false,
- 'ALTER TABLE ' . Piwik::prefixTable('log_conversion'). ' CHANGE `idaction` `idaction_url` INTEGER(11) UNSIGNED NOT NULL;' => false,
- ));
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
}
}
diff --git a/core/Updates/0.6-rc1.php b/core/Updates/0.6-rc1.php
new file mode 100644
index 0000000000..14929092ee
--- /dev/null
+++ b/core/Updates/0.6-rc1.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$
+ *
+ * @category Piwik
+ * @package Updates
+ */
+
+/**
+ * @package Updates
+ */
+class Piwik_Updates_0_6_rc1 extends Piwik_Updates
+{
+ static function getSql($adapter = 'PDO_MYSQL')
+ {
+ $defaultTimezone = 'UTC';
+ $defaultCurrency = 'USD';
+ return array(
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('user') . ' CHANGE date_registered date_registered TIMESTAMP NULL' => false,
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('site') . ' CHANGE ts_created ts_created TIMESTAMP NULL' => false,
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('site') . ' ADD `timezone` VARCHAR( 50 ) NOT NULL AFTER `ts_created` ;' => false,
+ 'UPDATE ' . Piwik_Common::prefixTable('site') . ' SET `timezone` = "'.$defaultTimezone.'";' => false,
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('site') . ' ADD currency CHAR( 3 ) NOT NULL AFTER `timezone` ;' => false,
+ 'UPDATE ' . Piwik_Common::prefixTable('site') . ' SET `currency` = "'.$defaultCurrency.'";' => false,
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('site') . ' ADD `excluded_ips` TEXT NOT NULL AFTER `currency` ;' => false,
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('site') . ' ADD excluded_parameters VARCHAR( 255 ) NOT NULL AFTER `excluded_ips` ;' => false,
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('log_visit') . ' ADD INDEX `index_idsite_datetime_config` ( `idsite` , `visit_last_action_time` , `config_md5config` ( 8 ) ) ;' => false,
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('log_visit') . ' ADD INDEX index_idsite_idvisit (idsite, idvisit) ;' => false,
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('log_conversion') . ' DROP INDEX index_idsite_date' => false,
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('log_conversion') . ' DROP visit_server_date;' => false,
+ 'ALTER TABLE ' . Piwik_Common::prefixTable('log_conversion') . ' ADD INDEX index_idsite_datetime ( `idsite` , `server_time` )' => false,
+ );
+ }
+
+ static function update()
+ {
+ // first we disable the plugins and keep an array of warnings messages
+ $pluginsToDisableMessage = array(
+ 'SearchEnginePosition' => "SearchEnginePosition plugin was disabled, because it is not compatible with the new Piwik 0.6. \n You can download the latest version of the plugin, compatible with Piwik 0.6.\n<a target='_blank' href='misc/redirectToUrl.php?url=http://dev.piwik.org/trac/ticket/502'>Click here.</a>",
+ 'GeoIP' => "GeoIP plugin was disabled, because it is not compatible with the new Piwik 0.6. \nYou can download the latest version of the plugin, compatible with Piwik 0.6.\n<a target='_blank' href='misc/redirectToUrl.php?url=http://dev.piwik.org/trac/ticket/45'>Click here.</a>"
+ );
+ $disabledPlugins = array();
+ foreach($pluginsToDisableMessage as $pluginToDisable => $warningMessage)
+ {
+ if(Piwik_PluginsManager::getInstance()->isPluginActivated($pluginToDisable))
+ {
+ Piwik_PluginsManager::getInstance()->deactivatePlugin($pluginToDisable);
+ $disabledPlugins[] = $warningMessage;
+ }
+ }
+
+ // Run the SQL
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
+
+ // Outputs warning message, pointing users to the plugin download page
+ if(!empty($disabledPlugins))
+ {
+ throw new Exception("The following plugins were disabled during the upgrade:"
+ ."<ul><li>" .
+ implode('</li><li>', $disabledPlugins) .
+ "</li></ul>");
+ }
+ }
+}
diff --git a/core/Updates/0.6.2.php b/core/Updates/0.6.2.php
new file mode 100644
index 0000000000..5c564df099
--- /dev/null
+++ b/core/Updates/0.6.2.php
@@ -0,0 +1,47 @@
+<?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$
+ *
+ * @category Piwik
+ * @package Updates
+ */
+
+/**
+ * @package Updates
+ */
+class Piwik_Updates_0_6_2 extends Piwik_Updates
+{
+ static function update()
+ {
+ $obsoleteFiles = array(
+ PIWIK_INCLUDE_PATH . '/core/Db/Mysqli.php',
+ );
+ foreach($obsoleteFiles as $obsoleteFile)
+ {
+ if(file_exists($obsoleteFile))
+ {
+ @unlink($obsoleteFile);
+ }
+ }
+
+ $obsoleteDirectories = array(
+ PIWIK_INCLUDE_PATH . '/core/Db/Pdo',
+ );
+ foreach($obsoleteDirectories as $dir)
+ {
+ if(file_exists($dir))
+ {
+ Piwik::unlinkRecursive($dir, true);
+ }
+ }
+
+ // force regeneration of cache files
+ Piwik::setUserIsSuperUser();
+ $allSiteIds = Piwik_SitesManager_API::getInstance()->getAllSitesId();
+ Piwik_Common::regenerateCacheWebsiteAttributes($allSiteIds);
+ }
+}
diff --git a/core/Updates/0.6.3.php b/core/Updates/0.6.3.php
new file mode 100644
index 0000000000..d4e5a8b38a
--- /dev/null
+++ b/core/Updates/0.6.3.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$
+ *
+ * @category Piwik
+ * @package Updates
+ */
+
+/**
+ * @package Updates
+ */
+class Piwik_Updates_0_6_3 extends Piwik_Updates
+{
+ static function getSql($adapter = 'PDO_MYSQL')
+ {
+ return array(
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('log_visit') .'`
+ CHANGE `location_ip` `location_ip` INT UNSIGNED NOT NULL' => false,
+ 'ALTER TABLE `'. Piwik_Common::prefixTable('logger_api_call') .'`
+ CHANGE `caller_ip` `caller_ip` INT UNSIGNED' => false,
+ );
+ }
+
+ static function update()
+ {
+ $config = Zend_Registry::get('config');
+ $dbInfos = $config->database->toArray();
+ if(!isset($dbInfos['schema']))
+ {
+ try {
+ if(is_writable( Piwik_Config::getDefaultUserConfigPath() ))
+ {
+ $dbInfos['schema'] = 'Myisam';
+ $config->database = $dbInfos;
+
+ $config->__destruct();
+ Piwik::createConfigObject();
+ }
+ else
+ {
+ throw new Exception('mandatory update failed');
+ }
+ } catch(Exception $e) {
+ throw new Piwik_Updater_UpdateErrorException("Please edit your config/config.ini.php file and add below <code>[database]</code> the following line: <br /><code>schema = Myisam</code>");
+ }
+ }
+
+ Piwik_Updater::updateDatabase(__FILE__, self::getSql());
+ }
+}
diff --git a/core/Url.php b/core/Url.php
index 7ba48bbc99..cc6164ac26 100644
--- a/core/Url.php
+++ b/core/Url.php
@@ -1,11 +1,11 @@
<?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$
- *
+ *
* @category Piwik
* @package Piwik
*/
@@ -26,11 +26,12 @@ class Piwik_Url
*/
static public function getCurrentUrl()
{
- return self::getCurrentHost()
- . self::getCurrentScriptName()
- . self::getCurrentQueryString();
+ return self::getCurrentScheme() . '://'
+ . self::getCurrentHost()
+ . self::getCurrentScriptName()
+ . self::getCurrentQueryString();
}
-
+
/**
* If current URL is "http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"
* will return "http://example.org/dir1/dir2/index.php"
@@ -39,10 +40,11 @@ class Piwik_Url
*/
static public function getCurrentUrlWithoutQueryString()
{
- return self::getCurrentHost()
- . self::getCurrentScriptName() ;
+ return self::getCurrentScheme() . '://'
+ . self::getCurrentHost()
+ . self::getCurrentScriptName();
}
-
+
/**
* If current URL is "http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"
* will return "http://example.org/dir1/dir2/"
@@ -51,9 +53,9 @@ class Piwik_Url
*/
static public function getCurrentUrlWithoutFileName()
{
- $host = self::getCurrentHost();
- $urlDir = self::getCurrentScriptPath();
- return $host.$urlDir;
+ return self::getCurrentScheme() . '://'
+ . self::getCurrentHost()
+ . self::getCurrentScriptPath();
}
/**
@@ -76,7 +78,7 @@ class Piwik_Url
}
return $urlDir;
}
-
+
/**
* If current URL is "http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"
* will return "/dir1/dir2/index.php"
@@ -106,41 +108,56 @@ class Piwik_Url
{
$url = $_SERVER['SCRIPT_NAME'];
}
+
+ if($url[0] !== '/')
+ {
+ $url = '/' . $url;
+ }
return $url;
}
/**
- * If current URL is "http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"
- * will return "http://example.org"
+ * If the current URL is 'http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"
+ * will return 'http'
*
- * @return string
+ * @return string 'https' or 'http'
*/
- static public function getCurrentHost()
+ static public function getCurrentScheme()
{
if(isset($_SERVER['HTTPS'])
- && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true)
+ && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true)
)
{
- $url = 'https';
+ $scheme = 'https';
}
- else
+ else
{
- $url = 'http';
+ $scheme = 'http';
}
-
- $url .= '://';
-
- if(isset($_SERVER['HTTP_HOST']))
+ return $scheme;
+ }
+
+ /**
+ * If current URL is "http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"
+ * will return "http://example.org"
+ *
+ * @return string
+ */
+ static public function getCurrentHost()
+ {
+ if (!empty($_SERVER['HTTP_X_FORWARDED_HOST']))
{
- $url .= $_SERVER['HTTP_HOST'];
+ return Piwik_Common::getFirstIpFromList($_SERVER['HTTP_X_FORWARDED_HOST']);
}
- else
+
+ if(isset($_SERVER['HTTP_HOST']))
{
- $url .= 'unknown';
+ return $_SERVER['HTTP_HOST'];
}
- return $url;
+
+ return 'unknown';
}
-
+
/**
* If current URL is "http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"
* will return "?param1=value1&param2=value2"
@@ -157,7 +174,7 @@ class Piwik_Url
}
return $url;
}
-
+
/**
* If current URL is "http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"
* will return
@@ -173,7 +190,7 @@ class Piwik_Url
$urlValues = Piwik_Common::getArrayFromQueryString($queryString);
return $urlValues;
}
-
+
/**
* Given an array of name-values, it will return the current query string
* with the new requested parameter key-values;
@@ -196,7 +213,7 @@ class Piwik_Url
}
return '';
}
-
+
/**
* Given an array of parameters name->value, returns the query string.
* Also works with array values using the php array syntax for GET parameters.
@@ -228,7 +245,7 @@ class Piwik_Url
$query = substr($query, 0, -1);
return $query;
}
-
+
/**
* Redirects the user to the Referer if found.
* If the user doesn't have a referer set, it redirects to the current URL without query string.
@@ -242,7 +259,7 @@ class Piwik_Url
}
self::redirectToUrl(self::getCurrentUrlWithoutQueryString());
}
-
+
/**
* Redirects the user to the specified URL
*
@@ -253,7 +270,7 @@ class Piwik_Url
header("Location: $url");
exit;
}
-
+
/**
* Returns the HTTP_REFERER header, false if not found.
*
@@ -267,4 +284,46 @@ class Piwik_Url
}
return false;
}
+
+ /**
+ * Is the URL on the same host and in the same script path?
+ *
+ * @param string $url
+ * @return bool True if local; false otherwise.
+ */
+ static public function isLocalUrl($url)
+ {
+ // handle case-sensitivity differences
+ $pathContains = strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' ? 'stripos' : 'strpos';
+
+ // test the scheme/protocol portion of the reconstructed "current" URL
+ if(stripos($url, 'http://') === 0 || stripos($url, 'https://') === 0)
+ {
+ // determine the offset to begin the comparison
+ $offset = strpos($url, '://');
+ $current = strstr(self::getCurrentUrlWithoutFileName(), '://');
+ if($pathContains($url, $current, $offset) === $offset)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get local referer, i.e., on the same host and in the same script path.
+ *
+ * @return string|false
+ */
+ static public function getLocalReferer()
+ {
+ // verify that the referer contains the current URL (minus the filename & query parameters), http://example.org/dir1/dir2/
+ $referer = self::getReferer();
+ if($referer !== false && self::isLocalUrl($referer)) {
+ return $referer;
+ }
+
+ return false;
+ }
}
diff --git a/core/Version.php b/core/Version.php
index 26e24c4dfc..a6031b12a7 100644
--- a/core/Version.php
+++ b/core/Version.php
@@ -17,5 +17,5 @@
*/
final class Piwik_Version
{
- const VERSION = '0.5';
+ const VERSION = '0.6.3';
}
diff --git a/core/View.php b/core/View.php
index 24b7339738..6757a101dd 100644
--- a/core/View.php
+++ b/core/View.php
@@ -9,7 +9,6 @@
* @category Piwik
* @package Piwik
*/
-
/*
* Transition for pre-Piwik 0.4.4
* @todo Remove this post-1.0
@@ -34,7 +33,8 @@ class Piwik_View implements Piwik_iView
private $template = '';
private $smarty = false;
private $variables = array();
-
+ private $contentType = 'text/html; charset=utf-8';
+
public function __construct( $templateFile, $smConf = array(), $filter = true )
{
$this->template = $templateFile;
@@ -50,7 +50,7 @@ class Piwik_View implements Piwik_iView
}
$this->smarty->template_dir = $smConf->template_dir->toArray();
- array_walk($this->smarty->template_dir, array("Piwik_View","addPiwikPath"), PIWIK_USER_PATH);
+ array_walk($this->smarty->template_dir, array("Piwik_View","addPiwikPath"), PIWIK_INCLUDE_PATH);
$this->smarty->plugins_dir = $smConf->plugins_dir->toArray();
array_walk($this->smarty->plugins_dir, array("Piwik_View","addPiwikPath"), PIWIK_INCLUDE_PATH);
@@ -61,8 +61,12 @@ class Piwik_View implements Piwik_iView
$this->smarty->cache_dir = $smConf->cache_dir;
Piwik_View::addPiwikPath($this->smarty->cache_dir, null, PIWIK_USER_PATH);
- $this->smarty->error_reporting = $smConf->debugging;
- $this->smarty->error_reporting = $smConf->error_reporting;
+ $error_reporting = $smConf->error_reporting;
+ if($error_reporting != (string)(int)$error_reporting)
+ {
+ $error_reporting = self::bitwise_eval($error_reporting);
+ }
+ $this->smarty->error_reporting = $error_reporting;
$this->smarty->assign('tag', 'piwik=' . Piwik_Version::VERSION);
if($filter)
@@ -100,17 +104,20 @@ class Piwik_View implements Piwik_iView
return $this->smarty->get_template_vars($key);
}
+ /**
+ * Render view
+ */
public function render()
{
try {
$this->currentModule = Piwik::getModule();
- $this->currentPluginName = Piwik::getCurrentPlugin()->getName();
+ $this->currentPluginName = Piwik::getCurrentPlugin()->getClassName();
$this->userLogin = Piwik::getCurrentUserLogin();
$showWebsiteSelectorInUserInterface = Zend_Registry::get('config')->General->show_website_selector_in_user_interface;
if($showWebsiteSelectorInUserInterface)
{
- $sites = Piwik_SitesManager_API::getSitesWithAtLeastViewAccess();
+ $sites = Piwik_SitesManager_API::getInstance()->getSitesWithAtLeastViewAccess();
usort($sites, create_function('$site1, $site2', 'return strcasecmp($site1["name"], $site2["name"]);'));
$this->sites = $sites;
}
@@ -122,7 +129,7 @@ class Piwik_View implements Piwik_iView
$this->piwik_version = Piwik_Version::VERSION;
$this->latest_version_available = Piwik_UpdateCheck::isNewestVersionAvailable();
- $this->loginModule = Zend_Registry::get('auth')->getName();
+ $this->loginModule = Piwik::getLoginPluginName();
} catch(Exception $e) {
// can fail, for example at installation (no plugin loaded yet)
}
@@ -135,13 +142,28 @@ class Piwik_View implements Piwik_iView
$this->totalNumberOfQueries = 0;
}
- @header('Content-Type: text/html; charset=utf-8');
+ @header('Content-Type: '.$this->contentType);
@header("Pragma: ");
@header("Cache-Control: no-store, must-revalidate");
return $this->smarty->fetch($this->template);
}
-
+
+ /**
+ * Set Content-Type field in HTTP response
+ *
+ * @param string $contentType
+ */
+ public function setContentType( $contentType )
+ {
+ $this->contentType = $contentType;
+ }
+
+ /**
+ * Add form to view
+ *
+ * @param Piwik_Form $form
+ */
public function addForm( $form )
{
// Create the renderer object
@@ -154,7 +176,13 @@ class Piwik_View implements Piwik_iView
$this->smarty->assign('form_data', $renderer->toArray());
$this->smarty->assign('element_list', $form->getElementList());
}
-
+
+ /**
+ * Assign value to a variable for use in Smarty template
+ *
+ * @param string|array $var
+ * @param mixed $value
+ */
public function assign($var, $value=null)
{
if (is_string($var))
@@ -170,6 +198,9 @@ class Piwik_View implements Piwik_iView
}
}
+ /**
+ * Clear compiled Smarty templates
+ */
public function clearCompiledTemplates()
{
$this->smarty->clear_compiled_tpl();
@@ -192,6 +223,13 @@ class Piwik_View implements Piwik_iView
}
*/
+ /**
+ * Prepend relative paths with absolute Piwik path
+ *
+ * @param string $value relative path (pass by reference)
+ * @param int $key (don't care)
+ * @param string $path Piwik root
+ */
static public function addPiwikPath(&$value, $key, $path)
{
if($value[0] != '/' && $value[0] != DIRECTORY_SEPARATOR)
@@ -201,6 +239,33 @@ class Piwik_View implements Piwik_iView
}
/**
+ * Evaluate expression containing only bitwise operators.
+ * Replaces defined constants with corresponding values.
+ * Does not use eval() or create_function().
+ *
+ * @param string $expression Expression.
+ * @return string
+ */
+ static public function bitwise_eval($expression)
+ {
+ // replace defined constants
+ $buf = get_defined_constants(true);
+
+ // use only the 'Core' PHP constants, e.g., E_ALL, E_STRICT, ...
+ $consts = isset($buf['Core']) ? $buf['Core'] : (isset($buf['mhash']) ? $buf['mhash'] : $buf['internal']);
+ $expression = str_replace(' ', '', strtr($expression, $consts));
+
+ // bitwise operators in order of precedence (highest to lowest)
+ // @todo: boolean ! (NOT) and parentheses aren't handled
+ $expression = preg_replace_callback('/~(-?[0-9]+)/', create_function('$matches', 'return (string)((~(int)$matches[1]));'), $expression);
+ $expression = preg_replace_callback('/(-?[0-9]+)&(-?[0-9]+)/', create_function('$matches', 'return (string)((int)$matches[1]&(int)$matches[2]);'), $expression);
+ $expression = preg_replace_callback('/(-?[0-9]+)\^(-?[0-9]+)/', create_function('$matches', 'return (string)((int)$matches[1]^(int)$matches[2]);'), $expression);
+ $expression = preg_replace_callback('/(-?[0-9]+)\|(-?[0-9]+)/', create_function('$matches', 'return (string)((int)$matches[1]|(int)$matches[2]);'), $expression);
+
+ return (string)((int)$expression & PHP_INT_MAX);
+ }
+
+ /**
* View factory method
*
* @param $templateName Template name (e.g., 'index')
@@ -211,10 +276,10 @@ class Piwik_View implements Piwik_iView
Piwik_PostEvent('View.getViewType', $viewType);
// get caller
- $bt = debug_backtrace();
- if(!isset($bt[0]))
+ $bt = @debug_backtrace();
+ if($bt === null || !isset($bt[0]))
{
- throw new Exception("View factory cannot be invoked directly");
+ throw new Exception("View factory cannot be invoked");
}
$path = dirname($bt[0]['file']);
diff --git a/core/ViewDataTable.php b/core/ViewDataTable.php
index 5608dd152f..703044d78b 100644
--- a/core/ViewDataTable.php
+++ b/core/ViewDataTable.php
@@ -82,6 +82,14 @@ abstract class Piwik_ViewDataTable
*/
protected $dataTable = null;
+
+ /**
+ * List of filters to apply after the data has been loaded from the API
+ *
+ * @var array
+ */
+ protected $queuedFilters = array();
+
/**
* @see init()
* @var string
@@ -121,8 +129,19 @@ abstract class Piwik_ViewDataTable
*/
protected $columnsTranslations = array();
-
+ /**
+ * Array of columns set to display
+ *
+ * @var array
+ */
protected $columnsToDisplay = array();
+
+ /**
+ * Variable that is used as the DIV ID in the rendered HTML
+ *
+ * @var string
+ */
+ protected $uniqIdTable = null;
/**
* Method to be implemented by the ViewDataTable_*.
@@ -251,7 +270,7 @@ abstract class Piwik_ViewDataTable
$this->viewProperties['show_all_views_icons'] = Piwik_Common::getRequestVar('show_all_views_icons', true);
$this->viewProperties['show_export_as_image_icon'] = Piwik_Common::getRequestVar('show_export_as_image_icon', false);
$this->viewProperties['show_exclude_low_population'] = Piwik_Common::getRequestVar('show_exclude_low_population', true);
- $this->viewProperties['show_offset_information'] = Piwik_Common::getRequestVar('show_offset_information', true);;
+ $this->viewProperties['show_offset_information'] = Piwik_Common::getRequestVar('show_offset_information', true);
$this->viewProperties['show_footer'] = Piwik_Common::getRequestVar('show_footer', true);
$this->viewProperties['show_footer_icons'] = ($this->idSubtable == false);
$this->viewProperties['apiMethodToRequestDataTable'] = $this->apiMethodToRequestDataTable;
@@ -344,6 +363,21 @@ abstract class Piwik_ViewDataTable
}
/**
+ * Hook called after the dataTable has been loaded from the API
+ * Can be used to add, delete or modify the data freshly loaded
+ */
+ protected function postDataTableLoadedFromAPI()
+ {
+ // Apply datatable filters that were queued by the controllers
+ foreach($this->queuedFilters as $filter)
+ {
+ $filterName = $filter[0];
+ $filterParameters = $filter[1];
+ $this->dataTable->filter($filterName, $filterParameters);
+ }
+ }
+
+ /**
* @return string URL to call the API, eg. "method=Referers.getKeywords&period=day&date=yesterday"...
*/
protected function getRequestString()
@@ -415,7 +449,7 @@ abstract class Piwik_ViewDataTable
* @see datatable.js
* @return string
*/
- protected function getUniqueIdViewDataTable()
+ protected function loadUniqueIdViewDataTable()
{
// 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
@@ -434,6 +468,27 @@ abstract class Piwik_ViewDataTable
}
return $uniqIdTable;
}
+
+ /**
+ * Sets the $uniqIdTable variable that is used as the DIV ID in the rendered HTML
+ */
+ public function setUniqueIdViewDataTable($uniqIdTable)
+ {
+ $this->viewProperties['uniqueId'] = $uniqIdTable;
+ $this->uniqIdTable = $uniqIdTable;
+ }
+
+ /**
+ * Returns current value of $uniqIdTable variable that is used as the DIV ID in the rendered HTML
+ */
+ public function getUniqueIdViewDataTable()
+ {
+ if( $this->uniqIdTable == null )
+ {
+ $this->uniqIdTable = $this->loadUniqueIdViewDataTable();
+ }
+ return $this->uniqIdTable;
+ }
/**
* Returns array of properties, eg. "show_footer", "show_search", etc.
@@ -721,7 +776,7 @@ abstract class Piwik_ViewDataTable
*/
public function setLimit( $limit )
{
- if($limit != 0)
+ if($limit !== 0)
{
$this->variablesDefault['filter_limit'] = $limit;
}
@@ -739,6 +794,15 @@ abstract class Piwik_ViewDataTable
$this->variablesDefault['filter_sort_order'] = $order;
}
+ /**
+ * Returns the column name on which the table will be sorted
+ *
+ * @return string
+ */
+ public function getSortedColumn()
+ {
+ return $this->variablesDefault['filter_sort_column'];
+ }
/**
* Sets translation string for given column
@@ -746,9 +810,10 @@ abstract class Piwik_ViewDataTable
* @param string $columnName column name
* @param string $columnTranslation column name translation
*/
- public function setColumnTranslation( $columnName, $columnTranslation )
+ public function setColumnTranslation( $columnName, $columnTranslation, $columnDescription = false )
{
$this->columnsTranslations[$columnName] = $columnTranslation;
+ $this->columnsDescriptions[$columnName] = $columnDescription;
}
/**
@@ -762,10 +827,20 @@ abstract class Piwik_ViewDataTable
{
return html_entity_decode($this->columnsTranslations[$columnName], ENT_COMPAT, 'UTF-8');
}
- else
+ return $columnName;
+ }
+
+ /**
+ * Returns column description, or false
+ * @param string $columnName column name
+ */
+ public function getColumnDescription( $columnName )
+ {
+ if( !empty($this->columnsDescriptions[$columnName]) )
{
- return $columnName;
+ return html_entity_decode($this->columnsDescriptions[$columnName], ENT_COMPAT, 'UTF-8');
}
+ return false;
}
/**
@@ -830,4 +905,18 @@ abstract class Piwik_ViewDataTable
}
$this->variablesDefault[$parameter] = $value;
}
+
+ /**
+ * Queues a Datatable filter, that will be applied once the datatable is loaded from the API.
+ * Useful when the controller needs to add columns, or decorate existing columns, when these filters don't
+ * necessarily make sense directly in the API.
+ *
+ * @param $filterName
+ * @param $parameters
+ * @return void
+ */
+ public function queueFilter($filterName, $parameters)
+ {
+ $this->queuedFilters[] = array($filterName, $parameters);
+ }
}
diff --git a/core/ViewDataTable/Cloud.php b/core/ViewDataTable/Cloud.php
index f819ef6c3c..c7d68be64a 100644
--- a/core/ViewDataTable/Cloud.php
+++ b/core/ViewDataTable/Cloud.php
@@ -36,11 +36,13 @@ class Piwik_ViewDataTable_Cloud extends Piwik_ViewDataTable
*/
function init($currentControllerName,
$currentControllerAction,
- $apiMethodToRequestDataTable )
+ $apiMethodToRequestDataTable,
+ $controllerActionCalledWhenRequestSubTable = null)
{
parent::init($currentControllerName,
$currentControllerAction,
- $apiMethodToRequestDataTable );
+ $apiMethodToRequestDataTable,
+ $controllerActionCalledWhenRequestSubTable);
$this->dataTableTemplate = 'CoreHome/templates/cloud.tpl';
$this->disableOffsetInformation();
$this->disableExcludeLowPopulation();
diff --git a/core/ViewDataTable/GenerateGraphData.php b/core/ViewDataTable/GenerateGraphData.php
index d3b1251f73..4a2fcce75e 100644
--- a/core/ViewDataTable/GenerateGraphData.php
+++ b/core/ViewDataTable/GenerateGraphData.php
@@ -87,8 +87,14 @@ abstract class Piwik_ViewDataTable_GenerateGraphData extends Piwik_ViewDataTable
}
$this->mainAlreadyExecuted = true;
- @header( "Content-type: application/json" );
+ if (!Zend_Registry::get('config')->General->serve_widget_and_data)
+ {
+ @header( "Content-Type: application/json" );
+ }
+ // Graphs require the full dataset, setting limit to null (same as 'no limit')
+ $this->setLimit(null);
+
// the queued filters will be manually applied later. This is to ensure that filtering using search
// will be done on the table before the labels are enhanced (see ReplaceColumnNames)
$this->disableQueuedFilters();
diff --git a/core/ViewDataTable/GenerateGraphData/ChartEvolution.php b/core/ViewDataTable/GenerateGraphData/ChartEvolution.php
index 5ed804c3ad..8c8d961ade 100644
--- a/core/ViewDataTable/GenerateGraphData/ChartEvolution.php
+++ b/core/ViewDataTable/GenerateGraphData/ChartEvolution.php
@@ -28,11 +28,11 @@ class Piwik_ViewDataTable_GenerateGraphData_ChartEvolution extends Piwik_ViewDat
$this->view = new Piwik_Visualization_Chart_Evolution();
}
- protected function guessUnitFromRequestedColumnNames($requestedColumnNames)
+ protected function guessUnitFromRequestedColumnNames($requestedColumnNames, $idSite)
{
$nameToUnit = array(
'_rate' => '%',
- '_revenue' => Piwik::getCurrency(),
+ '_revenue' => Piwik::getCurrency($idSite),
);
foreach($requestedColumnNames as $columnName)
{
@@ -119,11 +119,12 @@ class Piwik_ViewDataTable_GenerateGraphData_ChartEvolution extends Piwik_ViewDat
$yAxisLabelToValueCleaned[$yAxisLabel][] = $columnValue;
}
}
+ $idSite = Piwik_Common::getRequestVar('idSite');
$unit = $this->yAxisUnit;
if(empty($unit))
{
- $unit = $this->guessUnitFromRequestedColumnNames($requestedColumnNames);
+ $unit = $this->guessUnitFromRequestedColumnNames($requestedColumnNames, $idSite);
}
$this->view->setAxisXLabels($xLabels);
@@ -145,24 +146,63 @@ class Piwik_ViewDataTable_GenerateGraphData_ChartEvolution extends Piwik_ViewDat
if($this->isLinkEnabled())
{
$axisXOnClick = array();
+ $queryStringAsHash = $this->getQueryStringAsHash();
foreach($this->dataTable->metadata as $idDataTable => $metadataDataTable)
{
$period = $metadataDataTable['period'];
$dateInUrl = $period->getDateStart();
+ $parameters = array(
+ 'idSite' => $idSite,
+ 'period' => $period->getLabel(),
+ 'date' => $dateInUrl->toString()
+ );
+ $hash = '';
+ if(!empty($queryStringAsHash))
+ {
+ $hash = '#' . Piwik_Url::getQueryStringFromParameters( $queryStringAsHash + $parameters);
+ }
$link = Piwik_Url::getCurrentUrlWithoutQueryString() .
'?' .
Piwik_Url::getQueryStringFromParameters( array(
'module' => 'CoreHome',
'action' => 'index',
- 'idSite' => Piwik_Common::getRequestVar('idSite'),
- 'period' => $period->getLabel(),
- 'date' => $dateInUrl,
- ));
+ ) + $parameters)
+ . $hash;
$axisXOnClick[] = $link;
}
$this->view->setAxisXOnClick($axisXOnClick);
}
}
+
+ /**
+ * We link the graph dots to the same report as currently being displayed (only the date would change).
+ *
+ * In some cases the widget is loaded within a report that doesn't exist as such.
+ * For example, the dashboards loads the 'Last visits graph' widget which can't be directly linked to.
+ * Instead, the graph must link back to the dashboard.
+ *
+ * In other cases, like Visitors>Overview or the Goals graphs, we can link the graph clicks to the same report.
+ *
+ * To detect whether or not we can link to a report, we simply check if the current URL from which it was loaded
+ * belongs to the menu or not. If it doesn't belong to the menu, we do not append the hash to the URL,
+ * which results in loading the dashboard.
+ *
+ * @return array Query string array to append to the URL hash or false if the dashboard should be displayed
+ */
+ private function getQueryStringAsHash()
+ {
+ $queryString = Piwik_Url::getArrayFromCurrentQueryString();
+ $piwikParameters = array('idSite', 'date', 'period', 'XDEBUG_SESSION_START', 'KEY');
+ foreach($piwikParameters as $parameter)
+ {
+ unset($queryString[$parameter]);
+ }
+ if(Piwik_IsMenuUrlFound($queryString))
+ {
+ return $queryString;
+ }
+ return false;
+ }
private function isLinkEnabled()
{
diff --git a/core/ViewDataTable/GenerateGraphHTML.php b/core/ViewDataTable/GenerateGraphHTML.php
index 1c3bad57f3..be7e774a0a 100644
--- a/core/ViewDataTable/GenerateGraphHTML.php
+++ b/core/ViewDataTable/GenerateGraphHTML.php
@@ -28,12 +28,13 @@ abstract class Piwik_ViewDataTable_GenerateGraphHTML extends Piwik_ViewDataTable
*/
function init($currentControllerName,
$currentControllerAction,
- $apiMethodToRequestDataTable )
+ $apiMethodToRequestDataTable,
+ $controllerActionCalledWhenRequestSubTable = null)
{
parent::init($currentControllerName,
$currentControllerAction,
- $apiMethodToRequestDataTable );
-
+ $apiMethodToRequestDataTable,
+ $controllerActionCalledWhenRequestSubTable);
$this->dataTableTemplate = 'CoreHome/templates/graph.tpl';
$this->disableOffsetInformation();
@@ -67,7 +68,8 @@ abstract class Piwik_ViewDataTable_GenerateGraphHTML extends Piwik_ViewDataTable
/**
* We persist the parametersToModify values in the javascript footer.
- * This is used by the "export links" that use the "date" attribute from the json properties array in the datatable footer.
+ * This is used by the "export links" that use the "date" attribute
+ * from the json properties array in the datatable footer.
*/
protected function getJavascriptVariablesToSet()
{
@@ -125,9 +127,19 @@ abstract class Piwik_ViewDataTable_GenerateGraphHTML extends Piwik_ViewDataTable
foreach($this->parametersToModify as $key => $val)
{
- if (is_array($val)) {
+ // We do not forward filter data to the graph controller.
+ // This would cause the graph to have filter_limit=5 set by default,
+ // which would break them (graphs need the full dataset to build the "Others" aggregate value)
+ if(strpos($key, 'filter_') !== false)
+ {
+ continue;
+ }
+ if (is_array($val))
+ {
$_GET[$key] = unserialize(serialize($val));
- } else {
+ }
+ else
+ {
$_GET[$key] = $val;
}
}
@@ -135,7 +147,7 @@ abstract class Piwik_ViewDataTable_GenerateGraphHTML extends Piwik_ViewDataTable
$_GET = $saveGet;
- return str_replace(array("\r", "\n", "'"), array('', '', "\\'"), $content);
+ return str_replace(array("\r", "\n", "'", '\"'), array('', '', "\\'", '\\\"'), $content);
}
protected function getFlashParameters()
diff --git a/core/ViewDataTable/GenerateGraphHTML/ChartEvolution.php b/core/ViewDataTable/GenerateGraphHTML/ChartEvolution.php
index fae0235bfb..78b990e62b 100644
--- a/core/ViewDataTable/GenerateGraphHTML/ChartEvolution.php
+++ b/core/ViewDataTable/GenerateGraphHTML/ChartEvolution.php
@@ -38,17 +38,34 @@ class Piwik_ViewDataTable_GenerateGraphHTML_ChartEvolution extends Piwik_ViewDat
function init($currentControllerName,
$currentControllerAction,
- $apiMethodToRequestDataTable )
+ $apiMethodToRequestDataTable,
+ $controllerActionCalledWhenRequestSubTable = null)
{
parent::init($currentControllerName,
$currentControllerAction,
- $apiMethodToRequestDataTable );
+ $apiMethodToRequestDataTable,
+ $controllerActionCalledWhenRequestSubTable);
$this->setParametersToModify(array('date' => Piwik_Common::getRequestVar('date', 'last30', 'string')));
$this->disableShowAllViewsIcons();
$this->disableShowTable();
}
+
+ /**
+ * We ensure that the graph for a given Goal has a different ID than the 'Goals Overview' graph
+ * so that both can display on the dashboard at the same time
+ */
+ public function getUniqueIdViewDataTable()
+ {
+ $id = parent::getUniqueIdViewDataTable();
+ if(isset($this->parametersToModify['idGoal']))
+ {
+ $id .= $this->parametersToModify['idGoal'];
+ }
+ return $id;
+ }
+
/**
* Sets the columns that will be displayed on output evolution chart
* By default all columns are displayed ($columnsNames = array() will display all columns)
diff --git a/core/ViewDataTable/HtmlTable.php b/core/ViewDataTable/HtmlTable.php
index d40ed84696..cac953024c 100644
--- a/core/ViewDataTable/HtmlTable.php
+++ b/core/ViewDataTable/HtmlTable.php
@@ -41,7 +41,7 @@ class Piwik_ViewDataTable_HtmlTable extends Piwik_ViewDataTable
function init($currentControllerName,
$currentControllerAction,
$apiMethodToRequestDataTable,
- $controllerActionCalledWhenRequestSubTable = null )
+ $controllerActionCalledWhenRequestSubTable = null)
{
parent::init($currentControllerName,
$currentControllerAction,
@@ -82,14 +82,6 @@ class Piwik_ViewDataTable_HtmlTable extends Piwik_ViewDataTable
}
/**
- * Hook called after the dataTable has been loaded from the API
- * Can be used to add, delete or modify the data freshly loaded
- */
- protected function postDataTableLoadedFromAPI()
- {
- }
-
- /**
* @return Piwik_View with all data set
*/
protected function buildView()
@@ -103,10 +95,11 @@ class Piwik_ViewDataTable_HtmlTable extends Piwik_ViewDataTable
else
{
$columns = $this->getColumnsToDisplay();
- $columnTranslations = array();
+ $columnTranslations = $columnDescriptions = array();
foreach($columns as $columnName)
{
$columnTranslations[$columnName] = $this->getColumnTranslation($columnName);
+ $columnDescriptions[$columnName] = $this->getColumnDescription($columnName);
}
$nbColumns = count($columns);
// case no data in the array we use the number of columns set to be displayed
@@ -118,6 +111,7 @@ class Piwik_ViewDataTable_HtmlTable extends Piwik_ViewDataTable
$view->arrayDataTable = $this->getPHPArrayFromDataTable();
$view->dataTableColumns = $columns;
$view->columnTranslations = $columnTranslations;
+ $view->columnDescriptions = $columnDescriptions;
$view->nbColumns = $nbColumns;
$view->defaultWhenColumnValueNotDefined = '-';
}
diff --git a/core/ViewDataTable/HtmlTable/Goals.php b/core/ViewDataTable/HtmlTable/Goals.php
index a4627e0cd5..8bac47b4b1 100644
--- a/core/ViewDataTable/HtmlTable/Goals.php
+++ b/core/ViewDataTable/HtmlTable/Goals.php
@@ -23,14 +23,35 @@ class Piwik_ViewDataTable_HtmlTable_Goals extends Piwik_ViewDataTable_HtmlTable
public function main()
{
+ $this->idSite = Piwik_Common::getRequestVar('idSite', null, 'int');
+ $this->processOnlyIdGoal = Piwik_Common::getRequestVar('filter_only_display_idgoal', 0, 'int');
$this->viewProperties['show_exclude_low_population'] = true;
$this->viewProperties['show_goals'] = true;
- $this->setColumnsToDisplay( array( 'label',
- 'nb_visits',
- 'goals_conversion_rate',
- 'goal_%s_conversion_rate',
- 'revenue_per_visit',
- ));
+
+ $this->setColumnsTranslations( array(
+ 'goal_%s_conversion_rate' => '%s conversion rate',
+ 'goal_%s_nb_conversions' => '%s conversions',
+ 'goal_%s_revenue_per_visit' => '%s revenue per visit',
+ ));
+
+ $this->setColumnsToDisplay( array(
+ 'label',
+ 'nb_visits',
+ 'goal_%s_nb_conversions',
+ 'goal_%s_conversion_rate',
+ 'goal_%s_revenue_per_visit',
+ 'goals_conversion_rate',
+ 'revenue_per_visit',
+ ));
+
+ // We ensure that the 'Sort by' column is actually displayed in the table
+ // eg. most daily reports sort by nb_uniq_visitors but this column is not displayed in the Goals table
+ $columnsToDisplay = $this->getColumnsToDisplay();
+ $columnToSortBy = $this->getSortedColumn();
+ if(!in_array($columnToSortBy, $columnsToDisplay))
+ {
+ $this->setSortedColumn('nb_visits', 'desc');
+ }
parent::main();
}
@@ -39,35 +60,49 @@ class Piwik_ViewDataTable_HtmlTable_Goals extends Piwik_ViewDataTable_HtmlTable
$this->controllerActionCalledWhenRequestSubTable = null;
}
- protected function getRequestString()
- {
- $requestString = parent::getRequestString();
- return $requestString . '&filter_update_columns_when_show_all_goals=1';
- }
-
- protected $columnsToPercentageFilter = array();
-
- private function getIdSite()
- {
- return Piwik_Common::getRequestVar('idSite', null, 'int');
- }
-
public function setColumnsToDisplay($columnsNames)
{
$newColumnsNames = array();
+ $goals = array();
+ $idSite = $this->getIdSite();
+ if($idSite)
+ {
+ $goals = Piwik_Goals_API::getInstance()->getGoals( $idSite );
+ }
foreach($columnsNames as $columnName)
{
- if($columnName == 'goal_%s_conversion_rate')
+ if(in_array($columnName, array('goal_%s_conversion_rate', 'goal_%s_nb_conversions', 'goal_%s_revenue_per_visit')))
{
- $goals = Piwik_Goals_API::getGoals( $this->getIdSite() );
foreach($goals as $goal)
{
$idgoal = $goal['idgoal'];
- $name = $goal['name'];
- $columnName = 'goal_'.$idgoal.'_conversion_rate';
- $newColumnsNames[] = $columnName;
- $this->setColumnTranslation($columnName, $name);
- $this->columnsToPercentageFilter[] = $columnName;
+ if($this->processOnlyIdGoal > Piwik_DataTable_Filter_UpdateColumnsWhenShowAllGoals::GOALS_FULL_TABLE
+ && $this->processOnlyIdGoal != $idgoal)
+ {
+ continue;
+ }
+ $name = Piwik_Translate($this->getColumnTranslation($columnName), $goal['name']);
+ $columnNameGoal = str_replace('%s', $idgoal, $columnName);
+ $this->setColumnTranslation($columnNameGoal, $name);
+ if(strstr($columnNameGoal, '_rate') !== false)
+ {
+ $this->columnsToPercentageFilter[] = $columnNameGoal;
+ }
+ // For the goal table (when the flag icon is clicked), we only display the per Goal Conversion rate
+ elseif($this->processOnlyIdGoal == Piwik_DataTable_Filter_UpdateColumnsWhenShowAllGoals::GOALS_OVERVIEW)
+ {
+ continue;
+ }
+
+ if(strstr($columnNameGoal, '_revenue') !== false)
+ {
+ $this->columnsToRevenueFilter[] = $columnNameGoal;
+ }
+ else
+ {
+ $this->columnsToConversionFilter[] = $columnNameGoal;
+ }
+ $newColumnsNames[] = $columnNameGoal;
}
}
else
@@ -78,14 +113,45 @@ class Piwik_ViewDataTable_HtmlTable_Goals extends Piwik_ViewDataTable_HtmlTable
parent::setColumnsToDisplay($newColumnsNames);
}
+ protected function getRequestString()
+ {
+ $requestString = parent::getRequestString();
+ if($this->processOnlyIdGoal > Piwik_DataTable_Filter_UpdateColumnsWhenShowAllGoals::GOALS_FULL_TABLE)
+ {
+ $requestString .= "&filter_only_display_idgoal=".$this->processOnlyIdGoal;
+ }
+ return $requestString . '&filter_update_columns_when_show_all_goals=1';
+ }
+
+ protected $columnsToPercentageFilter = array();
+ protected $columnsToRevenueFilter = array();
+ protected $columnsToConversionFilter = array();
+ protected $idSite = false;
+
+ private function getIdSite()
+ {
+ return $this->idSite;
+ }
+
protected function postDataTableLoadedFromAPI()
{
parent::postDataTableLoadedFromAPI();
$this->columnsToPercentageFilter[] = 'goals_conversion_rate';
foreach($this->columnsToPercentageFilter as $columnName)
{
- $this->dataTable->filter('ColumnCallbackReplace', array($columnName, create_function('$rate', 'return $rate."%";')));
+ $this->dataTable->filter('ColumnCallbackReplace', array($columnName, create_function('$rate', 'return sprintf("%.1f",$rate)."%";')));
+ }
+ $this->columnsToRevenueFilter[] = 'revenue_per_visit';
+ foreach($this->columnsToRevenueFilter as $columnName)
+ {
+ $this->dataTable->filter('ColumnCallbackReplace', array($columnName, create_function('$value', 'return sprintf("%.1f",$value);')));
+ $this->dataTable->filter('ColumnCallbackReplace', array($columnName, array("Piwik", "getPrettyMoney"), array($this->getIdSite())));
+ }
+
+ foreach($this->columnsToConversionFilter as $columnName)
+ {
+ // this ensures that the value is set to zero for all rows where the value was not set (no conversion)
+ $this->dataTable->filter('ColumnCallbackReplace', array($columnName, create_function('$value', 'return $value;')));
}
- $this->dataTable->filter('ColumnCallbackReplace', array('revenue_per_visit', array("Piwik", "getPrettyMoney")));
}
}
diff --git a/core/Visualization/Chart.php b/core/Visualization/Chart.php
index 9efd164aa9..c795522d8d 100644
--- a/core/Visualization/Chart.php
+++ b/core/Visualization/Chart.php
@@ -10,9 +10,6 @@
* @package Piwik
*/
-// no direct access
-defined('PIWIK_INCLUDE_PATH') or die;
-
/**
* @see libs/open-flash-chart/php-ofc-library/open-flash-chart.php
* @link http://teethgrinder.co.uk/open-flash-chart-2/
@@ -128,8 +125,12 @@ abstract class Piwik_Visualization_Chart implements Piwik_iView
public function render()
{
- @header("Pragma: ");
- @header("Cache-Control: no-store, must-revalidate");
+ if(Piwik_Url::getCurrentScheme() == 'https' ||
+ Zend_Registry::get('config')->General->reverse_proxy)
+ {
+ @header("Pragma: ");
+ @header("Cache-Control: must-revalidate");
+ }
return $this->chart->toPrettyString();
}
diff --git a/core/Visualization/Sparkline.php b/core/Visualization/Sparkline.php
index 53110906f4..3b17e8fceb 100644
--- a/core/Visualization/Sparkline.php
+++ b/core/Visualization/Sparkline.php
@@ -10,9 +10,6 @@
* @package Piwik
*/
-// no direct access
-defined('PIWIK_INCLUDE_PATH') or die;
-
/**
* @see libs/sparkline/lib/Sparkline_Line.php
* @link http://sparkline.org
@@ -52,18 +49,22 @@ class Piwik_Visualization_Sparkline implements Piwik_iView
$width = self::getWidth();
$height = self::getHeight();
- $data = $this->values;
$sparkline = new Sparkline_Line();
$sparkline->SetColor('lineColor', 22, 44, 74); // dark blue
$sparkline->SetColorHtml('red', '#FF7F7F');
$sparkline->SetColorHtml('blue', '#55AAFF');
$sparkline->SetColorHtml('green', '#75BF7C');
- $data = array_reverse($data);
$min = $max = $last = null;
$i = 0;
foreach($this->values as $value)
{
+ // 50% should be plotted as 50
+ $toRemove = '%';
+ if(strpos($value, $toRemove) !== false)
+ {
+ $value = str_replace($toRemove, '', $value);
+ }
$sparkline->SetData($i, $value);
if( null == $min || $value <= $min[1])
{
@@ -77,14 +78,12 @@ class Piwik_Visualization_Sparkline implements Piwik_iView
$i++;
}
$sparkline->SetYMin(0);
- $sparkline->setYMax($max[1] + 0.5); // the +0.5 seems to be mandatory to not lose some pixels when value = max
- $sparkline->SetPadding( 3, 0, 2, 0);
- $font = FONT_2;
- // the -0.5 is a hack as the sparkline samping rendering is obviously slightly bugged
- // (see also fix marked as //FIX FROM PIWIK in libs/sparkline/lib/Sparkline.php)
- $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->SetYMax($max[1]);
+ $sparkline->SetPadding( 3, 0, 2, 0 ); // top, right, bottom, left
+// $font = FONT_2;
+ $sparkline->SetFeaturePoint($min[0], $min[1], 'red', 5);
+ $sparkline->SetFeaturePoint($max[0], $max[1], 'green', 5);
+ $sparkline->SetFeaturePoint($last[0], $last[1], 'blue', 5);
$sparkline->SetLineSize(3); // for renderresampled, linesize is on virtual image
$ratio = 1;
// var_dump($min);var_dump($max);var_dump($lasts);exit;
diff --git a/core/testMinimumPhpVersion.php b/core/testMinimumPhpVersion.php
index 36a6ac1f1f..bd28c3fe72 100644
--- a/core/testMinimumPhpVersion.php
+++ b/core/testMinimumPhpVersion.php
@@ -10,9 +10,6 @@
* @package Piwik
*/
-// no direct access
-defined('PIWIK_INCLUDE_PATH') or die;
-
/**
* This file is executed before anything else.
* It checks the minimum PHP version required to run Piwik.
@@ -48,7 +45,7 @@ function Piwik_ExitWithMessage($message, $optionalTrace = false, $optionalLinks
{
if($optionalTrace)
{
- $optionalTrace = '<font color="#888888">Backtrace:<br/><pre>'.$optionalTrace.'</pre></font>';
+ $optionalTrace = '<font color="#888888">Backtrace:<br /><pre>'.$optionalTrace.'</pre></font>';
}
if($optionalLinks)
{
@@ -69,6 +66,7 @@ function Piwik_ExitWithMessage($message, $optionalTrace = false, $optionalLinks
exit;
}
+// added in PHP 4.3.0
if (!function_exists('file_get_contents'))
{
function file_get_contents($filename)