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
diff options
context:
space:
mode:
-rw-r--r--core/API/DataTableGenericFilter.php1
-rw-r--r--core/DataTable/Filter/AddSummaryRow.php9
-rwxr-xr-xcore/DataTable/Filter/ColumnCallbackAddColumn.php91
-rw-r--r--core/DataTable/Filter/ColumnCallbackAddColumnQuotient.php14
-rw-r--r--core/DataTable/Filter/ColumnCallbackReplace.php40
-rwxr-xr-xcore/DataTable/Filter/GroupBy.php90
-rw-r--r--core/DataTable/Filter/Limit.php14
-rw-r--r--core/DataTable/Renderer/Php.php7
-rw-r--r--core/Piwik.php31
-rw-r--r--core/PluginsFunctions/Sql.php24
-rw-r--r--core/ViewDataTable.php45
-rw-r--r--core/ViewDataTable/GenerateGraphData.php1
-rw-r--r--lang/en.php21
-rw-r--r--plugins/CoreAdminHome/templates/generalSettings.tpl2
-rw-r--r--plugins/CoreHome/templates/datatable.css4
-rw-r--r--plugins/CoreHome/templates/datatable.js3
-rw-r--r--plugins/CoreHome/templates/datatable.tpl2
-rw-r--r--plugins/DBStats/API.php334
-rw-r--r--plugins/DBStats/Controller.php352
-rw-r--r--plugins/DBStats/DBStats.php42
-rwxr-xr-xplugins/DBStats/MySQLMetadataProvider.php378
-rw-r--r--plugins/DBStats/templates/DBStats.tpl52
-rwxr-xr-xplugins/DBStats/templates/index.tpl154
-rw-r--r--plugins/PrivacyManager/templates/privacySettings.tpl2
24 files changed, 1538 insertions, 175 deletions
diff --git a/core/API/DataTableGenericFilter.php b/core/API/DataTableGenericFilter.php
index 624313cdab..6218fb7957 100644
--- a/core/API/DataTableGenericFilter.php
+++ b/core/API/DataTableGenericFilter.php
@@ -76,6 +76,7 @@ class Piwik_API_DataTableGenericFilter
'Limit' => array(
'filter_offset' => array('integer', '0'),
'filter_limit' => array('integer'),
+ 'keep_summary_row' => array('integer', '0'),
),
);
}
diff --git a/core/DataTable/Filter/AddSummaryRow.php b/core/DataTable/Filter/AddSummaryRow.php
index 0059428a83..14e554dba4 100644
--- a/core/DataTable/Filter/AddSummaryRow.php
+++ b/core/DataTable/Filter/AddSummaryRow.php
@@ -31,12 +31,14 @@ class Piwik_DataTable_Filter_AddSummaryRow extends Piwik_DataTable_Filter
public function __construct( $table,
$startRowToSummarize,
$labelSummaryRow = Piwik_DataTable::LABEL_SUMMARY_ROW,
- $columnToSortByBeforeTruncating = null )
+ $columnToSortByBeforeTruncating = null,
+ $deleteRows = true )
{
parent::__construct($table);
$this->startRowToSummarize = $startRowToSummarize;
$this->labelSummaryRow = $labelSummaryRow;
$this->columnToSortByBeforeTruncating = $columnToSortByBeforeTruncating;
+ $this->deleteRows = $deleteRows;
}
public function filter($table)
@@ -66,7 +68,10 @@ class Piwik_DataTable_Filter_AddSummaryRow extends Piwik_DataTable_Filter
}
$newRow->setColumns(array('label' => $this->labelSummaryRow) + $newRow->getColumns());
- $table->filter('Limit', array(0, $this->startRowToSummarize));
+ if ($this->deleteRows)
+ {
+ $table->filter('Limit', array(0, $this->startRowToSummarize));
+ }
$table->addSummaryRow($newRow);
unset($rows);
}
diff --git a/core/DataTable/Filter/ColumnCallbackAddColumn.php b/core/DataTable/Filter/ColumnCallbackAddColumn.php
new file mode 100755
index 0000000000..1216e513d3
--- /dev/null
+++ b/core/DataTable/Filter/ColumnCallbackAddColumn.php
@@ -0,0 +1,91 @@
+<?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: ColumnCallbackAddColumn.php $
+ *
+ * @category Piwik
+ * @package Piwik
+ */
+
+/**
+ * Adds a new column to every row of a DataTable based on the result of callback.
+ *
+ * @package Piwik
+ * @subpackage Piwik_DataTable
+ */
+class Piwik_DataTable_Filter_ColumnCallbackAddColumn extends Piwik_DataTable_Filter
+{
+ /**
+ * The names of the columns to pass to the callback.
+ */
+ private $columns;
+
+ /**
+ * The name of the column to add.
+ */
+ private $columnToAdd;
+
+ /**
+ * The callback to apply to each row of the DataTable. The result is added as
+ * the value of a new column.
+ */
+ private $functionToApply;
+
+ /**
+ * Extra parameters to pass to the callback.
+ */
+ private $functionParameters;
+
+ /**
+ * Constructor.
+ *
+ * @param Piwik_DataTable $table The DataTable that will be filtered.
+ * @param array|string $columns The names of the columns to pass to the callback.
+ * @param string $columnToAdd The name of the column to add.
+ * @param mixed $functionToApply The callback to apply to each row of a DataTable.
+ * @param array $functionParameters Extra parameters to pass to $functionToApply.
+ */
+ public function __construct( $table, $columns, $columnToAdd, $functionToApply, $functionParameters = array() )
+ {
+ parent::__construct($table);
+
+ if (!is_array($columns))
+ {
+ $columns = array($columns);
+ }
+
+ $this->columns = $columns;
+ $this->columnToAdd = $columnToAdd;
+ $this->functionToApply = $functionToApply;
+ $this->functionParameters = $functionParameters;
+ }
+
+ /**
+ * Executes a callback on every row of the supplied table and adds the result of
+ * the callback as a new column to each row.
+ *
+ * @param Piwik_DataTable $table The table to filter.
+ */
+ public function filter( $table )
+ {
+ foreach ($table->getRows() as $row)
+ {
+ $columnValues = array();
+ foreach ($this->columns as $column)
+ {
+ $columnValues[] = $row->getColumn($column);
+ }
+
+ $parameters = array_merge($columnValues, $this->functionParameters);
+ $value = call_user_func_array($this->functionToApply, $parameters);
+
+ $row->setColumn($this->columnToAdd, $value);
+
+ $this->filterSubTable($row);
+ }
+ }
+}
+
diff --git a/core/DataTable/Filter/ColumnCallbackAddColumnQuotient.php b/core/DataTable/Filter/ColumnCallbackAddColumnQuotient.php
index 31b8fcf9f5..eb92e7a526 100644
--- a/core/DataTable/Filter/ColumnCallbackAddColumnQuotient.php
+++ b/core/DataTable/Filter/ColumnCallbackAddColumnQuotient.php
@@ -19,13 +19,15 @@
*/
class Piwik_DataTable_Filter_ColumnCallbackAddColumnQuotient extends Piwik_DataTable_Filter
{
+ protected $table;
protected $columnValueToRead;
protected $columnNameToAdd;
protected $columnNameUsedAsDivisor;
protected $totalValueUsedAsDivisor;
protected $quotientPrecision;
protected $shouldSkipRows;
-
+ protected $getDivisorFromSummaryRow;
+
/**
* @param Piwik_DataTable $table
* @param string $columnNameToAdd
@@ -35,10 +37,12 @@ class Piwik_DataTable_Filter_ColumnCallbackAddColumnQuotient extends Piwik_DataT
* if a string is given, this string is the column name's value used as the divisor.
* @param int $quotientPrecision Division precision
* @param bool|numeric $shouldSkipRows Whether rows w/o the column to read should be skipped.
+ * @param bool $getDivisorFromSummaryRow Whether to get the divisor from the summary row or the current row.
*/
- public function __construct( $table, $columnNameToAdd, $columnValueToRead, $divisorValueOrDivisorColumnName, $quotientPrecision = 0, $shouldSkipRows = false)
+ public function __construct( $table, $columnNameToAdd, $columnValueToRead, $divisorValueOrDivisorColumnName, $quotientPrecision = 0, $shouldSkipRows = false, $getDivisorFromSummaryRow = false)
{
parent::__construct($table);
+ $this->table = $table;
$this->columnValueToRead = $columnValueToRead;
$this->columnNameToAdd = $columnNameToAdd;
if(is_numeric($divisorValueOrDivisorColumnName))
@@ -51,6 +55,7 @@ class Piwik_DataTable_Filter_ColumnCallbackAddColumnQuotient extends Piwik_DataT
}
$this->quotientPrecision = $quotientPrecision;
$this->shouldSkipRows = $shouldSkipRows;
+ $this->getDivisorFromSummaryRow = $getDivisorFromSummaryRow;
}
public function filter($table)
@@ -113,6 +118,11 @@ class Piwik_DataTable_Filter_ColumnCallbackAddColumnQuotient extends Piwik_DataT
{
return $this->totalValueUsedAsDivisor;
}
+ else if ($this->getDivisorFromSummaryRow)
+ {
+ $summaryRow = $this->table->getRowFromId(Piwik_DataTable::ID_SUMMARY_ROW);
+ return $summaryRow->getColumn($this->columnNameUsedAsDivisor);
+ }
else
{
return $row->getColumn($this->columnNameUsedAsDivisor);
diff --git a/core/DataTable/Filter/ColumnCallbackReplace.php b/core/DataTable/Filter/ColumnCallbackReplace.php
index 067ba8d8aa..cc28bcc880 100644
--- a/core/DataTable/Filter/ColumnCallbackReplace.php
+++ b/core/DataTable/Filter/ColumnCallbackReplace.php
@@ -19,36 +19,46 @@
*/
class Piwik_DataTable_Filter_ColumnCallbackReplace extends Piwik_DataTable_Filter
{
- private $columnToFilter;
+ private $columnsToFilter;
private $functionToApply;
+ private $functionParameters;
- public function __construct( $table, $columnToFilter, $functionToApply, $functionParameters = null )
+ public function __construct( $table, $columnsToFilter, $functionToApply, $functionParameters = null )
{
parent::__construct($table);
$this->functionToApply = $functionToApply;
$this->functionParameters = $functionParameters;
- $this->columnToFilter = $columnToFilter;
+
+ if (!is_array($columnsToFilter))
+ {
+ $columnsToFilter = array($columnsToFilter);
+ }
+
+ $this->columnsToFilter = $columnsToFilter;
}
public function filter($table)
{
foreach($table->getRows() as $key => $row)
{
- // 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)
+ foreach ($this->columnsToFilter as $column)
{
- $value = 0;
- }
+ // when a value is not defined, we set it to zero by default (rather than displaying '-')
+ $value = $this->getElementToReplace($row, $column);
+ if($value === false)
+ {
+ $value = 0;
+ }
- $parameters = array($value);
- if(!is_null($this->functionParameters))
- {
- $parameters = array_merge($parameters, $this->functionParameters);
+ $parameters = array($value);
+ if(!is_null($this->functionParameters))
+ {
+ $parameters = array_merge($parameters, $this->functionParameters);
+ }
+ $newValue = call_user_func_array( $this->functionToApply, $parameters);
+ $this->setElementToReplace($row, $column, $newValue);
+ $this->filterSubTable($row);
}
- $newValue = call_user_func_array( $this->functionToApply, $parameters);
- $this->setElementToReplace($row, $this->columnToFilter, $newValue);
- $this->filterSubTable($row);
}
}
diff --git a/core/DataTable/Filter/GroupBy.php b/core/DataTable/Filter/GroupBy.php
new file mode 100755
index 0000000000..030712ed63
--- /dev/null
+++ b/core/DataTable/Filter/GroupBy.php
@@ -0,0 +1,90 @@
+<?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: GroupBy.php $
+ *
+ * @category Piwik
+ * @package Piwik
+ */
+
+/**
+ * DataTable filter that will group DataTable rows together based on the results
+ * of a reduce function. Rows with the same reduce result will be summed and merged.
+ *
+ * @package Piwik
+ * @subpackage Piwik_DataTable
+ */
+class Piwik_DataTable_Filter_GroupBy extends Piwik_DataTable_Filter
+{
+ /**
+ * The name of the columns to reduce.
+ */
+ private $groupByColumn;
+
+ /**
+ * A callback that modifies the $groupByColumn of each row in some way. Rows with
+ * the same reduction result will be added together.
+ */
+ private $reduceFunction;
+
+ /**
+ * Extra parameters to pass to the reduce function.
+ */
+ private $parameters;
+
+ /**
+ * Constructor.
+ *
+ * @param Piwik_DataTable $table The DataTable to filter.
+ * @param string $groupByColumn The column name to reduce.
+ * @param mixed $reduceFunction The reduce function. This must alter the $groupByColumn in some way.
+ * @param array $parameters Extra parameters to supply to the reduce function.
+ */
+ public function __construct( $table, $groupByColumn, $reduceFunction, $parameters = array() )
+ {
+ parent::__construct($table);
+
+ $this->groupByColumn = $groupByColumn;
+ $this->reduceFunction = $reduceFunction;
+ $this->parameters = $parameters;
+ }
+
+ /**
+ * Applies the reduce function to each row and merges rows w/ the same reduce result.
+ */
+ public function filter( $table )
+ {
+ $groupByRows = array();
+ $nonGroupByRowIds = array();
+
+ foreach ($table->getRows() as $rowId => $row)
+ {
+ // reduce the group by column of this row
+ $groupByColumnValue = $row->getColumn($this->groupByColumn);
+ $parameters = array_merge(array($groupByColumnValue), $this->parameters);
+ $groupByValue = call_user_func_array($this->reduceFunction, $parameters);
+
+ if (!isset($groupByRows[$groupByValue]))
+ {
+ // if we haven't encountered this group by value before, we mark this row as a
+ // row to keep, and change the group by column to the reduced value.
+ $groupByRows[$groupByValue] = $row;
+ $row->setColumn($this->groupByColumn, $groupByValue);
+ }
+ else
+ {
+ // if we have already encountered this group by value, we add this row to the
+ // row that will be kept, and mark this one for deletion
+ $groupByRows[$groupByValue]->sumRow($row);
+ $nonGroupByRowIds[] = $rowId;
+ }
+ }
+
+ // delete the unneeded rows.
+ $table->deleteRows($nonGroupByRowIds);
+ }
+}
+
diff --git a/core/DataTable/Filter/Limit.php b/core/DataTable/Filter/Limit.php
index 34974a5756..f74eebf1ed 100644
--- a/core/DataTable/Filter/Limit.php
+++ b/core/DataTable/Filter/Limit.php
@@ -24,8 +24,9 @@ class Piwik_DataTable_Filter_Limit extends Piwik_DataTable_Filter
* @param Piwik_DataTable $table
* @param int $offset Starting row (indexed from 0)
* @param int $limit Number of rows to keep (specify -1 to keep all rows)
+ * @param bool $keepSummaryRow Whether to keep the summary row or not.
*/
- public function __construct( $table, $offset, $limit = null )
+ public function __construct( $table, $offset, $limit = null, $keepSummaryRow = false )
{
parent::__construct($table);
$this->offset = $offset;
@@ -35,6 +36,7 @@ class Piwik_DataTable_Filter_Limit extends Piwik_DataTable_Filter
$limit = -1;
}
$this->limit = $limit;
+ $this->keepSummaryRow = $keepSummaryRow;
}
public function filter($table)
@@ -43,6 +45,11 @@ class Piwik_DataTable_Filter_Limit extends Piwik_DataTable_Filter
$rowsCount = $table->getRowsCount();
+ if ($this->keepSummaryRow)
+ {
+ $summaryRow = $table->getRowFromId(Piwik_DataTable::ID_SUMMARY_ROW);
+ }
+
// we delete from 0 to offset
if($this->offset > 0)
{
@@ -53,5 +60,10 @@ class Piwik_DataTable_Filter_Limit extends Piwik_DataTable_Filter
{
$table->deleteRowsOffset( $this->limit );
}
+
+ if ($this->keepSummaryRow && $summaryRow)
+ {
+ $table->addSummaryRow($summaryRow);
+ }
}
}
diff --git a/core/DataTable/Renderer/Php.php b/core/DataTable/Renderer/Php.php
index b0c18c65a5..a7afd19212 100644
--- a/core/DataTable/Renderer/Php.php
+++ b/core/DataTable/Renderer/Php.php
@@ -190,7 +190,7 @@ class Piwik_DataTable_Renderer_Php extends Piwik_DataTable_Renderer
{
$array = array();
- foreach($table->getRows() as $row)
+ foreach($table->getRows() as $id => $row)
{
$newRow = array(
'columns' => $row->getColumns(),
@@ -198,6 +198,11 @@ class Piwik_DataTable_Renderer_Php extends Piwik_DataTable_Renderer
'idsubdatatable' => $row->getIdSubDataTable(),
);
+ if ($id == Piwik_DataTable::ID_SUMMARY_ROW)
+ {
+ $newRow['issummaryrow'] = Piwik_DataTable::ID_SUMMARY_ROW;
+ }
+
if($this->isRenderSubtables()
&& $row->getIdSubDataTable() !== null)
{
diff --git a/core/Piwik.php b/core/Piwik.php
index 9a48c2b6b6..0eef3b8beb 100644
--- a/core/Piwik.php
+++ b/core/Piwik.php
@@ -1484,19 +1484,21 @@ class Piwik
* Pretty format a memory size value
*
* @param numeric $size in bytes
+ * @param string $unit The specific unit to use, if any. If null, the unit is determined by $size.
+ * @param int $precision The precision to use when rounding.
* @return string
*/
- static public function getPrettySizeFromBytes($size)
+ static public function getPrettySizeFromBytes( $size, $unit = null, $precision = 1 )
{
if ($size == 0)
{
return '0 M';
}
- $bytes = array('','K','M','G','T');
- foreach($bytes as $val)
+ $units = array('B','K','M','G','T');
+ foreach($units as $currentUnit)
{
- if($size > 1024)
+ if ($size >= 1024 && $unit != $currentUnit)
{
$size = $size / 1024;
}
@@ -1505,9 +1507,9 @@ class Piwik
break;
}
}
- return round($size, 1)." ".$val;
+ return round($size, $precision)." ".$currentUnit;
}
-
+
/**
* Pretty format a time
*
@@ -1567,6 +1569,23 @@ class Piwik
}
return $return;
}
+
+ /**
+ * Gets a prettified string representation of a number. The result will have
+ * thousands separators and a decimal point specific to the current locale.
+ *
+ * @param numeric $value
+ * @return string
+ */
+ static public function getPrettyNumber( $value )
+ {
+ $locale = localeconv();
+
+ $decimalPoint = $locale['decimal_point'];
+ $thousandsSeparator = $locale['thousands_sep'];
+
+ return number_format($value, 0, $decimalPoint, $thousandsSeparator);
+ }
/**
* Returns the Javascript code to be inserted on every page to track
diff --git a/core/PluginsFunctions/Sql.php b/core/PluginsFunctions/Sql.php
index 16d8433957..446df10cb6 100644
--- a/core/PluginsFunctions/Sql.php
+++ b/core/PluginsFunctions/Sql.php
@@ -103,6 +103,18 @@ class Piwik_Sql
{
return self::getDb()->fetchOne($sql, $parameters);
}
+
+ /**
+ * Fetches result from the database query as an array of associative arrays.
+ *
+ * @param string $sql SQL query
+ * @param array $parameters Parameters to bind in the query, array( param1 => value1, param2 => value2)
+ * @return array
+ */
+ static public function fetchAssoc($sql, $parameters = array())
+ {
+ return self::getDb()->fetchAssoc($sql, $parameters);
+ }
/**
* Deletes all desired rows in a table, while using a limit. This function will execute a
@@ -279,6 +291,18 @@ function Piwik_FetchOne( $sqlQuery, $parameters = array())
}
/**
+ * Fetches result from the database query as an array of associative arrays.
+ *
+ * @param string $sqlQuery
+ * @param array $parameters Parameters to bind in the query, array( param1 => value1, param2 => value2)
+ * @return array
+ */
+function Piwik_FetchAssoc( $sqlQuery, $parameters = array() )
+{
+ return Piwik_Sql::fetchAssoc($sqlQuery, $parameters);
+}
+
+/**
* Deletes all desired rows in a table, while using a limit. This function will execute a
* DELETE query until there are no more rows to delete.
*
diff --git a/core/ViewDataTable.php b/core/ViewDataTable.php
index 6f68fc2b30..0461c4871a 100644
--- a/core/ViewDataTable.php
+++ b/core/ViewDataTable.php
@@ -306,6 +306,7 @@ abstract class Piwik_ViewDataTable
$this->viewProperties['uniqueId'] = $this->getUniqueIdViewDataTable();
$this->viewProperties['exportLimit'] = Piwik_Config::getInstance()->General['API_datatable_default_limit'];
$this->viewProperties['relatedReports'] = array();
+ $this->viewProperties['highlight_summary_row'] = false;
$standardColumnNameToTranslation = array_merge(
Piwik_API_API::getInstance()->getDefaultMetrics(),
@@ -465,12 +466,17 @@ abstract class Piwik_ViewDataTable
$genericFilter->filter($this->dataTable);
}
- // Finally, apply datatable filters that were queued (should be 'presentation' filters that do not affect the number of rows)
- foreach($this->queuedFilters as $filter)
+ if (!isset($this->variablesDefault['disable_queued_filters'])
+ || !$this->variablesDefault['disable_queued_filters'])
{
- $filterName = $filter[0];
- $filterParameters = $filter[1];
- $this->dataTable->filter($filterName, $filterParameters);
+ // Finally, apply datatable filters that were queued (should be 'presentation' filters that
+ // do not affect the number of rows)
+ foreach($this->queuedFilters as $filter)
+ {
+ $filterName = $filter[0];
+ $filterParameters = $filter[1];
+ $this->dataTable->filter($filterName, $filterParameters);
+ }
}
}
@@ -489,6 +495,7 @@ abstract class Piwik_ViewDataTable
$toSetEventually = array(
'filter_limit',
+ 'keep_summary_row',
'filter_sort_column',
'filter_sort_order',
'filter_excludelowpop',
@@ -919,6 +926,15 @@ abstract class Piwik_ViewDataTable
}
/**
+ * Whether or not to show the summary row on every page of results. The default behavior
+ * is to treat the summary row like any other row.
+ */
+ public function alwaysShowSummaryRow()
+ {
+ $this->variablesDefault['keep_summary_row'] = true;
+ }
+
+ /**
* Sets the value to use for the Exclude low population filter.
*
* @param int|float If a row value is less than this value, it will be removed from the dataTable
@@ -1153,6 +1169,15 @@ abstract class Piwik_ViewDataTable
}
/**
+ * Set whether to highlight the summary row or not. If not highlighted, it will
+ * look like every other row.
+ */
+ public function setHighlightSummaryRow( $highlightSummaryRow )
+ {
+ $this->viewProperties['highlight_summary_row'] = $highlightSummaryRow;
+ }
+
+ /**
* Sets columns translations array.
*
* @param array $columnsTranslations An associative array indexed by column names, eg. array('nb_visit'=>"Numer of visits")
@@ -1273,15 +1298,17 @@ abstract class Piwik_ViewDataTable
*/
public function hasReportBeenPurged()
{
- $strPeriod = Piwik_Common::getRequestVar('period');
- $strDate = Piwik_Common::getRequestVar('date');
+ $strPeriod = Piwik_Common::getRequestVar('period', false);
+ $strDate = Piwik_Common::getRequestVar('date', false);
- if ((is_null($this->dataTable) || $this->dataTable->getRowsCount() == 0))
+ if ($strPeriod !== false
+ && $strDate !== false
+ && (is_null($this->dataTable) || $this->dataTable->getRowsCount() == 0))
{
// if range, only look at the first date
if ($strPeriod == 'range')
{
- $idSite = Piwik_Common::getRequestVar('idSite');
+ $idSite = Piwik_Common::getRequestVar('idSite', '');
if ($idSite == 0 || intval($idSite) != 0)
{
$site = new Piwik_Site($idSite);
diff --git a/core/ViewDataTable/GenerateGraphData.php b/core/ViewDataTable/GenerateGraphData.php
index abc2e6d90c..e27e13daee 100644
--- a/core/ViewDataTable/GenerateGraphData.php
+++ b/core/ViewDataTable/GenerateGraphData.php
@@ -180,6 +180,7 @@ abstract class Piwik_ViewDataTable_GenerateGraphData extends Piwik_ViewDataTable
// throws exception if no view access
$this->loadDataTableFromAPI();
$this->checkStandardDataTable();
+ $this->postDataTableLoadedFromAPI();
$graphLimit = $this->getGraphLimit();
if(!empty($graphLimit))
diff --git a/lang/en.php b/lang/en.php
index db725d559b..0d78bedbaf 100644
--- a/lang/en.php
+++ b/lang/en.php
@@ -32,6 +32,8 @@ $translations = array(
'General_Reports' => 'Reports',
'General_RelatedReport' => 'Related report',
'General_RelatedReports' => 'Related reports',
+ 'General_Metric' => 'Metric',
+ 'General_Metrics' => 'Metrics',
'General_Edit' => 'Edit',
'General_Download' => 'Download',
'General_Upload' => 'Upload',
@@ -325,6 +327,8 @@ $translations = array(
'General_OneDay' => '1 day',
'General_NDays' => '%s days',
'General_MainMetrics' => 'Main metrics',
+ 'General_Rows' => 'Rows',
+ 'General_GeneralInformation' => 'General Information',
'Actions_PluginDescription' => 'Reports about the page views, the outlinks and downloads. Outlinks and Downloads tracking is automatic!',
'Actions_Actions' => 'Actions',
'Actions_SubmenuPages' => 'Pages',
@@ -396,10 +400,11 @@ $translations = array(
'PrivacyManager_AnonymizeIpDescription' => 'Select "Yes" if you want Piwik not to track fully qualified IP-Addresses.',
'PrivacyManager_AnonymizeIpMaskLengtDescription' => 'Select how many bytes of the visitors\' IPs should be masked.',
'PrivacyManager_AnonymizeIpMaskLength' => '%s byte(s) - e.g. %s',
- 'PrivacyManager_DeleteDataSettings' => 'Delete old visitor logs and reports from database',
+ 'PrivacyManager_DeleteDataSettings' => 'Delete old visitor logs and reports',
'PrivacyManager_DeleteLogInfo' => 'Logs from the following tables will be deleted: %s',
'PrivacyManager_UseDeleteLog' => 'Regularly delete old visitor logs from the database',
- 'PrivacyManager_DeleteDataDescription' => 'You can configure Piwik to regularly delete old visitor logs and/or processed reports to keep your database size small. If desired, pre-processed reports will not be deleted, only visit, pageview and conversion log data will be deleted. Or, the pre-processed reports can be deleted and the log data can be kept.',
+ 'PrivacyManager_DeleteDataDescription' => 'You can configure Piwik to regularly delete old visitor logs and/or processed reports to keep your database size small.',
+ 'PrivacyManager_DeleteDataDescription2' => 'If desired, pre-processed reports will not be deleted, only visit, pageview and conversion log data will be deleted. Or, the pre-processed reports can be deleted and the log data can be kept.',
'PrivacyManager_DeleteLogDescription2' => 'When you enable automatic log deletion, you must ensure that all previous daily reports have been processed, so that no data is lost.',
'PrivacyManager_DeleteLogsOlderThan' => 'Delete logs older than',
'PrivacyManager_DeleteDataInterval' => 'Delete old data every',
@@ -610,13 +615,21 @@ $translations = array(
'Dashboard_DashboardEmptyNotification' => 'Your Dashboard does not contain any widgets. Start by adding some widgets or just reset the dashboard to the default widget selection.',
'DBStats_PluginDescription' => 'This plugin reports the MySQL database usage by Piwik tables.',
'DBStats_DatabaseUsage' => 'Database usage',
- 'DBStats_MainDescription' => 'Piwik is storing all your web analytics data in the MySQL database. Currently, Piwik tables are using %s.',
- 'DBStats_LearnMore' => 'To learn more about how Piwik processes data and how to make Piwik work well for medium and high traffic websites, check out the documentation %s.',
+ 'DBStats_MainDescription' => 'Piwik stores all your web analytics data in a MySQL database. Currently, Piwik tables take up %s of space.',
+ 'DBStats_LearnMore' => 'To learn more about how Piwik processes data and how to make Piwik work well with medium and high traffic websites, see this documentation: %s.',
'DBStats_Table' => 'Table',
'DBStats_RowCount' => 'Row count',
'DBStats_DataSize' => 'Data size',
'DBStats_IndexSize' => 'Index size',
'DBStats_TotalSize' => 'Total size',
+ 'DBStats_DBSize' => 'DB size',
+ 'DBStats_ReportDataByYear' => 'Report Tables By Year',
+ 'DBStats_MetricDataByYear' => 'Metric Tables By Year',
+ 'DBStats_EstimatedSize' => 'Estimated size',
+ 'DBStats_TrackerTables' => 'Tracker Tables',
+ 'DBStats_ReportTables' => 'Report Tables',
+ 'DBStats_MetricTables' => 'Metric Tables',
+ 'DBStats_OtherTables' => 'Other Tables',
'ExampleAPI_PluginDescription' => 'Example Plugin: How to create an API for your plugin, to export your data in multiple formats without any special coding?',
'ExampleFeedburner_PluginDescription' => 'Example Plugin: How to display your Feedburner subscriber in a Widget in the Dashboard?',
'ExampleFeedburner_Help' => 'Help about Feed Analytics',
diff --git a/plugins/CoreAdminHome/templates/generalSettings.tpl b/plugins/CoreAdminHome/templates/generalSettings.tpl
index e11c6f2669..9471d7e8e2 100644
--- a/plugins/CoreAdminHome/templates/generalSettings.tpl
+++ b/plugins/CoreAdminHome/templates/generalSettings.tpl
@@ -165,7 +165,7 @@
{capture assign=clickDeleteLogSettings}{'PrivacyManager_DeleteDataSettings'|translate}{/capture}
<h2>{'PrivacyManager_DeleteDataSettings'|translate}</h2>
<p>
- {'PrivacyManager_DeleteDataDescription'|translate}
+ {'PrivacyManager_DeleteDataDescription'|translate} {'PrivacyManager_DeleteDataDescription2'|translate}
<br/>
<a href='{url module="PrivacyManager" action="privacySettings"}#deleteLogsAnchor'>
{'PrivacyManager_ClickHereSettings'|translate:"'$clickDeleteLogSettings'"}
diff --git a/plugins/CoreHome/templates/datatable.css b/plugins/CoreHome/templates/datatable.css
index f0fa02828c..3f05071e23 100644
--- a/plugins/CoreHome/templates/datatable.css
+++ b/plugins/CoreHome/templates/datatable.css
@@ -153,6 +153,10 @@ table.dataTable td.labelodd {
background: #fff url(images/bullet1.gif) no-repeat;
}
+.dataTable tr.highlight td {
+ background-color: #ECF9DD;
+ font-weight: bold;
+}
table.dataTable td.label,table.subActionsDataTable td.label,table.actionsDataTable td.label {
border-top: 0;
diff --git a/plugins/CoreHome/templates/datatable.js b/plugins/CoreHome/templates/datatable.js
index cb6827a766..1358ebd189 100644
--- a/plugins/CoreHome/templates/datatable.js
+++ b/plugins/CoreHome/templates/datatable.js
@@ -422,6 +422,8 @@ dataTable.prototype =
var totalRows = Number(self.param.totalRows);
offsetEndDisp = offsetEnd;
+ if (self.param.keep_summary_row) --totalRows;
+
if(offsetEnd > totalRows) offsetEndDisp = totalRows;
// only show this string if there is some rows in the datatable
@@ -439,6 +441,7 @@ dataTable.prototype =
var offsetEnd = Number(self.param.filter_offset)
+ Number(self.param.filter_limit);
var totalRows = Number(self.param.totalRows);
+ if (self.param.keep_summary_row) --totalRows;
if(offsetEnd < totalRows)
{
$(this).css('display','inline');
diff --git a/plugins/CoreHome/templates/datatable.tpl b/plugins/CoreHome/templates/datatable.tpl
index 235e8bcc30..766eb70808 100644
--- a/plugins/CoreHome/templates/datatable.tpl
+++ b/plugins/CoreHome/templates/datatable.tpl
@@ -35,7 +35,7 @@
<tbody>
{foreach from=$arrayDataTable item=row}
- <tr {if $row.idsubdatatable && $javascriptVariablesToSet.controllerActionCalledWhenRequestSubTable != null}class="subDataTable" id="{$row.idsubdatatable}"{/if}>
+ <tr {if $row.idsubdatatable && $javascriptVariablesToSet.controllerActionCalledWhenRequestSubTable != null}class="subDataTable" id="{$row.idsubdatatable}"{/if}{if isset($row.issummaryrow) && $row.issummaryrow && $properties.highlight_summary_row} class="highlight"{/if}>
{foreach from=$dataTableColumns item=column}
<td>
{include file="CoreHome/templates/datatable_cell.tpl"}
diff --git a/plugins/DBStats/API.php b/plugins/DBStats/API.php
index eb35cf86d5..d01b714d03 100644
--- a/plugins/DBStats/API.php
+++ b/plugins/DBStats/API.php
@@ -11,13 +11,23 @@
*/
/**
+ * @see plugins/DBStats/MySQLMetadataProvider.php
+ */
+require_once PIWIK_INCLUDE_PATH . '/plugins/DBStats/MySQLMetadataProvider.php';
+
+/**
* DBStats API is used to request the overall status of the Mysql tables in use by Piwik.
*
* @package Piwik_DBStats
*/
class Piwik_DBStats_API
{
+ /** Singleton instance of this class. */
static private $instance = null;
+
+ /**
+ * Gets or creates the DBStats API singleton.
+ */
static public function getInstance()
{
if (self::$instance == null)
@@ -26,109 +36,283 @@ class Piwik_DBStats_API
}
return self::$instance;
}
-
- public function getDBStatus()
+
+ /**
+ * The MySQLMetadataProvider instance that fetches table/db status information.
+ */
+ private $metadataProvider;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
{
- Piwik::checkUserIsSuperUser();
-
- if(function_exists('mysql_connect'))
- {
- $configDb = Piwik_Config::getInstance()->database;
- $link = mysql_connect($configDb['host'], $configDb['username'], $configDb['password']);
- $status = mysql_stat($link);
- mysql_close($link);
- $status = explode(" ", $status);
- }
- else
+ $this->metadataProvider = new Piwik_DBStats_MySQLMetadataProvider();
+ }
+
+ /**
+ * Forces the next table status request to issue a query by reseting the table status cache.
+ */
+ public function resetTableStatuses()
+ {
+ self::getInstance()->metadataProvider = new Piwik_DBStats_MySQLMetadataProvider();
+ }
+
+ /**
+ * Gets some general information about this Piwik installation, including the count of
+ * websites tracked, the count of users and the total space used by the database.
+ *
+ * @return array Contains the website count, user count and total space used by the database.
+ */
+ public function getGeneralInformation()
+ {
+ // calculate total size
+ $totalSpaceUsed = 0;
+ foreach ($this->metadataProvider->getAllTablesStatus() as $status)
{
- $db = Zend_Registry::get('db');
-
- $fullStatus = $db->fetchAssoc('SHOW STATUS;');
- if(empty($fullStatus)) {
- throw new Exception('Error, SHOW STATUS failed');
- }
-
- $status = array(
- 'Uptime' => $fullStatus['Uptime']['Value'],
- 'Threads' => $fullStatus['Threads_running']['Value'],
- 'Questions' => $fullStatus['Questions']['Value'],
- 'Slow queries' => $fullStatus['Slow_queries']['Value'],
- 'Flush tables' => $fullStatus['Flush_commands']['Value'],
- 'Open tables' => $fullStatus['Open_tables']['Value'],
-// 'Opens: ', // not available via SHOW STATUS
-// 'Queries per second avg' =/ // not available via SHOW STATUS
- );
+ $totalSpaceUsed += $status['Data_length'] + $status['Index_length'];
}
-
- return $status;
+
+ $siteTableStatus = $this->metadataProvider->getTableStatus('site');
+ $userTableStatus = $this->metadataProvider->getTableStatus('user');
+
+ $siteCount = $siteTableStatus['Rows'];
+ $userCount = $userTableStatus['Rows'];
+
+ return array($siteCount, $userCount, $totalSpaceUsed);
}
- public function getTableStatus($table, $field = '')
+ /**
+ * Gets general database info that is not specific to any table.
+ *
+ * @return array See http://dev.mysql.com/doc/refman/5.1/en/show-status.html .
+ */
+ public function getDBStatus()
{
Piwik::checkUserIsSuperUser();
- $db = Zend_Registry::get('db');
- // http://dev.mysql.com/doc/refman/5.1/en/show-table-status.html
- $tables = $db->fetchAll("SHOW TABLE STATUS LIKE ". $db->quote($table));
-
- if(!isset($tables[0])) {
- throw new Exception('Error, table or field not found');
- }
- if ($field == '')
- {
- return $tables[0];
- }
- else
- {
- return $tables[0][$field];
- }
+ return $this->metadataProvider->getDBStatus();
}
/**
- * Gets the result of a SHOW TABLE STATUS query for every table in the DB.
+ * Returns a datatable summarizing how data is distributed among Piwik tables.
+ *
+ * This function will group tracker tables, numeric archive tables, blob archive tables
+ * and other tables together so only four rows are shown.
*
- * @return array The table information.
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
*/
- public function getAllTablesStatus()
+ public function getDatabaseUsageSummary()
{
Piwik::checkUserIsSuperUser();
- $tablesPiwik = Piwik::getTablesInstalled();
- $result = array();
- foreach(Piwik_FetchAll("SHOW TABLE STATUS") as $t)
+ $emptyRow = array('data_size' => 0, 'index_size' => 0, 'row_count' => 0);
+ $rows = array(
+ 'tracker_data' => $emptyRow,
+ 'metric_data' => $emptyRow,
+ 'report_data' => $emptyRow,
+ 'other_data' => $emptyRow
+ );
+
+ foreach ($this->metadataProvider->getAllTablesStatus() as $status)
{
- if (in_array($t['Name'], $tablesPiwik))
+ if ($this->isNumericArchiveTable($status['Name']))
{
- $result[] = $t;
+ $rowToAddTo = &$rows['metric_data'];
}
+ else if ($this->isBlobArchiveTable($status['Name']))
+ {
+ $rowToAddTo = &$rows['report_data'];
+ }
+ else if ($this->isTrackerTable($status['Name']))
+ {
+ $rowToAddTo = &$rows['tracker_data'];
+ }
+ else
+ {
+ $rowToAddTo = &$rows['other_data'];
+ }
+
+ $rowToAddTo['data_size'] += $status['Data_length'];
+ $rowToAddTo['index_size'] += $status['Index_length'];
+ $rowToAddTo['row_count'] += $status['Rows'];
}
+ $result = new Piwik_DataTable();
+ $result->addRowsFromArrayWithIndexLabel($rows);
return $result;
}
- public function getAllTablesStatusPretty()
+ /**
+ * Returns a datatable describing how much space is taken up by each log table.
+ *
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getTrackerDataSummary()
+ {
+ Piwik::checkUserIsSuperUser();
+ return $this->getTablesSummary($this->metadataProvider->getAllLogTableStatus());
+ }
+
+ /**
+ * Returns a datatable describing how much space is taken up by each numeric
+ * archive table.
+ *
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getMetricDataSummary()
+ {
+ Piwik::checkUserIsSuperUser();
+ return $this->getTablesSummary($this->metadataProvider->getAllNumericArchiveStatus());
+ }
+
+ /**
+ * Returns a datatable describing how much space is taken up by each numeric
+ * archive table, grouped by year.
+ *
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getMetricDataSummaryByYear()
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $dataTable = $this->getMetricDataSummary();
+
+ $getTableYear = array('Piwik_DBStats_API', 'getArchiveTableYear');
+ $dataTable->filter('GroupBy', array('label', $getTableYear));
+
+ return $dataTable;
+ }
+
+ /**
+ * Returns a datatable describing how much space is taken up by each blob
+ * archive table.
+ *
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getReportDataSummary()
{
Piwik::checkUserIsSuperUser();
+ return $this->getTablesSummary($this->metadataProvider->getAllBlobArchiveStatus());
+ }
+
+ /**
+ * Returns a datatable describing how much space is taken up by each blob
+ * archive table, grouped by year.
+ *
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getReportDataSummaryByYear()
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $dataTable = $this->getReportDataSummary();
+
+ $getTableYear = array('Piwik_DBStats_API', 'getArchiveTableYear');
+ $dataTable->filter('GroupBy', array('label', $getTableYear));
- // http://dev.mysql.com/doc/refman/5.1/en/show-table-status.html
- $total = array('Name' => 'Total', 'Data_length' => 0, 'Index_length' => 0, 'Rows' => 0);
- $table = $this->getAllTablesStatus();
- foreach($table as &$t)
+ return $dataTable;
+ }
+
+ /**
+ * Returns a datatable describing how much space is taken up by 'admin' tables.
+ *
+ * An 'admin' table is a table that is not central to analytics functionality.
+ * So any table that isn't an archive table or a log table is an 'admin' table.
+ *
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getAdminDataSummary()
+ {
+ Piwik::checkUserIsSuperUser();
+ return $this->getTablesSummary($this->metadataProvider->getAllAdminTableStatus());
+ }
+
+ /**
+ * Returns a datatable describing how much total space is taken up by each
+ * individual report type.
+ *
+ * Goal reports and reports of the format .*_[0-9]+ are grouped together.
+ *
+ * @param bool $forceCache false to use the cached result, true to run the queries again and
+ * cache the result.
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getIndividualReportsSummary( $forceCache = false )
+ {
+ Piwik::checkUserIsSuperUser();
+ return $this->metadataProvider->getRowCountsAndSizeByBlobName($forceCache);
+ }
+
+ /**
+ * Returns a datatable describing how much total space is taken up by each
+ * individual metric type.
+ *
+ * Goal metrics, metrics of the format .*_[0-9]+ and 'done...' metrics are grouped together.
+ *
+ * @param bool $forceCache false to use the cached result, true to run the queries again and
+ * cache the result.
+ * @return Piwik_DataTable A datatable with three columns: 'data_size', 'index_size', 'row_count'.
+ */
+ public function getIndividualMetricsSummary( $forceCache = false )
+ {
+ Piwik::checkUserIsSuperUser();
+ return $this->metadataProvider->getRowCountsAndSizeByMetricName($forceCache);
+ }
+
+ /**
+ * Returns a datatable representation of a set of table statuses.
+ *
+ * @param array $statuses The table statuses to summarize.
+ * @return Piwik_DataTable
+ */
+ private function getTablesSummary( $statuses )
+ {
+ $dataTable = new Piwik_DataTable();
+ foreach ($statuses as $status)
{
- $total['Data_length'] += $t['Data_length'];
- $total['Index_length'] += $t['Index_length'];
- $total['Rows'] += $t['Rows'];
-
- $t['Total_length'] = Piwik::getPrettySizeFromBytes($t['Index_length']+$t['Data_length']);
- $t['Data_length'] = Piwik::getPrettySizeFromBytes($t['Data_length']);
- $t['Index_length'] = Piwik::getPrettySizeFromBytes($t['Index_length']);
- $t['Rows'] = Piwik::getPrettySizeFromBytes($t['Rows']);
+ $dataTable->addRowFromSimpleArray(array(
+ 'label' => $status['Name'],
+ 'data_size' => $status['Data_length'],
+ 'index_size' => $status['Index_length'],
+ 'row_count' => $status['Rows']
+ ));
+ }
+ return $dataTable;
+ }
+
+ /** Returns true if $name is the name of a numeric archive table, false if otherwise. */
+ private function isNumericArchiveTable( $name )
+ {
+ return strpos($name, Piwik_Common::prefixTable('archive_numeric_')) === 0;
+ }
+
+ /** Returns true if $name is the name of a blob archive table, false if otherwise. */
+ private function isBlobArchiveTable( $name )
+ {
+ return strpos($name, Piwik_Common::prefixTable('archive_blob_')) === 0;
+ }
+
+ /** Returns true if $name is the name of a log table, false if otherwise. */
+ private function isTrackerTable( $name )
+ {
+ return strpos($name, Piwik_Common::prefixTable('log_')) === 0;
+ }
+
+ /**
+ * Gets the year of an archive table from its name.
+ *
+ * @param string $tableName
+ * @param string The year.
+ *
+ * @ignore
+ */
+ public static function getArchiveTableYear( $tableName )
+ {
+ if (preg_match("/archive_(?:numeric|blob)_([0-9]+)_/", $tableName, $matches) === 0)
+ {
+ return '';
}
- $total['Total_length'] = Piwik::getPrettySizeFromBytes($total['Data_length']+$total['Index_length']);
- $total['Data_length'] = Piwik::getPrettySizeFromBytes($total['Data_length']);
- $total['Index_length'] = Piwik::getPrettySizeFromBytes($total['Index_length']);
- $total['TotalRows'] = Piwik::getPrettySizeFromBytes($total['Rows']);
- $table['Total'] = $total;
- return $table;
+ return $matches[1];
}
}
diff --git a/plugins/DBStats/Controller.php b/plugins/DBStats/Controller.php
index 91444f91ff..e0fee92c94 100644
--- a/plugins/DBStats/Controller.php
+++ b/plugins/DBStats/Controller.php
@@ -16,13 +16,357 @@
*/
class Piwik_DBStats_Controller extends Piwik_Controller_Admin
{
- function index()
+ /**
+ * Returns the index for this plugin. Shows every other report defined by this plugin,
+ * except the '...ByYear' reports. These can be loaded as related reports.
+ *
+ * Also, the 'getIndividual...Summary' reports are loaded by AJAX, as they can take
+ * a significant amount of time to load on setups w/ lots of websites.
+ */
+ public function index()
{
Piwik::checkUserIsSuperUser();
- $view = Piwik_View::factory('DBStats');
- $view->tablesStatus = Piwik_DBStats_API::getInstance()->getAllTablesStatusPretty();
+ $view = Piwik_View::factory('index');
$this->setBasicVariablesView($view);
$view->menu = Piwik_GetAdminMenu();
- echo $view->render();
+
+ $view->databaseUsageSummary = $this->getDatabaseUsageSummary(true);
+ $view->trackerDataSummary = $this->getTrackerDataSummary(true);
+ $view->metricDataSummary = $this->getMetricDataSummary(true);
+ $view->reportDataSummary = $this->getReportDataSummary(true);
+ $view->adminDataSummary = $this->getAdminDataSummary(true);
+
+ list($siteCount, $userCount, $totalSpaceUsed) = Piwik_DBStats_API::getInstance()->getGeneralInformation();
+ $view->siteCount = Piwik::getPrettyNumber($siteCount);
+ $view->userCount = Piwik::getPrettyNumber($userCount);
+ $view->totalSpaceUsed = Piwik::getPrettySizeFromBytes($totalSpaceUsed);
+
+ echo $view->render();
+ }
+
+ /**
+ * Shows a datatable that displays how much space the tracker tables, numeric
+ * archive tables, report tables and other tables take up in the MySQL database.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getDatabaseUsageSummary( $fetch = false )
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'graphPie', $orderDir = 'desc',
+ $addPercentColumn = true);
+ $view->disableOffsetInformationAndPaginationControls();
+
+ // translate the labels themselves
+ $translateSummaryLabel = array($this, 'translateSummarylabel');
+ $view->queueFilter('ColumnCallbackReplace', array(array('label'), $translateSummaryLabel),
+ $runBeforeGenericFilters = true);
+
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays the amount of space each individual log table
+ * takes up in the MySQL database.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getTrackerDataSummary( $fetch = false )
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $view = $this->getDataTableView(__FUNCTION__);
+ $view->disableOffsetInformationAndPaginationControls();
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays the amount of space each numeric archive table
+ * takes up in the MySQL database.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getMetricDataSummary( $fetch = false )
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'desc');
+ $view->addRelatedReports(array(
+ 'DBStats.getMetricDataSummaryByYear' => Piwik_Translate('DBStats_MetricDataByYear')
+ ));
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays the amount of space each numeric archive table
+ * takes up in the MySQL database, for each year of numeric data.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getMetricDataSummaryByYear( $fetch = false )
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'desc',
+ $addPercentColumn = false, $labelKey = 'CoreHome_PeriodYear');
+ $view->addRelatedReports(array('DBStats.getMetricDataSummary' => Piwik_Translate('DBStats_MetricTables')));
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays the amount of space each blob archive table
+ * takes up in the MySQL database.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getReportDataSummary( $fetch = false )
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'desc');
+ $view->addRelatedReports(array(
+ 'DBStats.getReportDataSummaryByYear' => Piwik_Translate('DBStats_ReportDataByYear')
+ ));
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays the amount of space each blob archive table
+ * takes up in the MySQL database, for each year of blob data.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getReportDataSummaryByYear( $fetch = false )
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'desc',
+ $addPercentColumn = false, $labelKey = 'CoreHome_PeriodYear');
+ $view->addRelatedReports(array('DBStats.getReportDataSummary' => Piwik_Translate('DBStats_ReportTables')));
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays how many occurances there are of each individual
+ * report type stored in the MySQL database.
+ *
+ * Goal reports and reports of the format: .*_[0-9]+ are grouped together.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getIndividualReportsSummary( $fetch = false )
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'asc',
+ $addPercentColumn = false, $labelKey = 'General_Report',
+ $sizeColumns = array('estimated_size'));
+
+ // this report table has some extra columns that shouldn't be shown
+ if ($view instanceof Piwik_ViewDataTable_HtmlTable)
+ {
+ $view->setColumnsToDisplay(array('label', 'row_count', 'estimated_size'));
+ }
+
+ $this->setIndividualSummaryFooterMessage($view);
+
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays how many occurances there are of each individual
+ * metric type stored in the MySQL database.
+ *
+ * Goal metrics, metrics of the format .*_[0-9]+ and 'done...' metrics are grouped together.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getIndividualMetricsSummary( $fetch = false )
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table', $orderDir = 'asc',
+ $addPercentColumn = false, $labelKey = 'General_Metric',
+ $sizeColumns = array('estimated_size'));
+
+ $this->setIndividualSummaryFooterMessage($view);
+
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Shows a datatable that displays the amount of space each 'admin' table takes
+ * up in the MySQL database.
+ *
+ * An 'admin' table is a table that is not central to analytics functionality.
+ * So any table that isn't an archive table or a log table is an 'admin' table.
+ *
+ * @param bool $fetch If true, the rendered HTML datatable is returned, otherwise,
+ * it is echoed.
+ */
+ public function getAdminDataSummary( $fetch = false )
+ {
+ Piwik::checkUserIsSuperUser();
+ $view = $this->getDataTableView(__FUNCTION__, $viewType = 'table');
+ $view->disableOffsetInformationAndPaginationControls();
+ return $this->renderView($view, $fetch);
+ }
+
+ /**
+ * Utility function that creates and prepares a ViewDataTable for this plugin.
+ */
+ private function getDataTableView( $function, $viewType = 'table', $orderDir = 'asc', $addPercentColumn = false,
+ $labelKey = 'DBStats_Table', $sizeColumns = array('data_size', 'index_size'),
+ $limit = 25 )
+ {
+ $columnTranslations = array(
+ 'label' => Piwik_Translate($labelKey),
+ 'year' => Piwik_Translate('CoreHome_PeriodYear'),
+ 'data_size' => Piwik_Translate('DBStats_DataSize'),
+ 'index_size' => Piwik_Translate('DBStats_IndexSize'),
+ 'total_size' => Piwik_Translate('DBStats_TotalSize'),
+ 'row_count' => Piwik_Translate('DBStats_RowCount'),
+ 'percent_total' => '%&nbsp;'.Piwik_Translate('DBStats_DBSize'),
+ 'estimated_size' => Piwik_Translate('DBStats_EstimatedSize')
+ );
+
+ $view = Piwik_ViewDataTable::factory($viewType);
+ $view->init($this->pluginName, $function, "DBStats.$function");
+ $view->setSortedColumn('label', $orderDir);
+ $view->setLimit($limit);
+ $view->setHighlightSummaryRow(true);
+ $view->disableSearchBox();
+ $view->disableExcludeLowPopulation();
+ $view->disableTagCloud();
+ $view->disableShowAllColumns();
+ $view->alwaysShowSummaryRow();
+
+ // translate columns
+ foreach ($columnTranslations as $columnName => $translation)
+ {
+ $view->setColumnTranslation($columnName, $translation);
+ }
+
+ // add total_size column (if necessary columns are present)
+ if (in_array('data_size', $sizeColumns) && in_array('index_size', $sizeColumns))
+ {
+ $getTotalTableSize = array($this, 'getTotalTableSize');
+ $view->queueFilter('ColumnCallbackAddColumn',
+ array(array('data_size', 'index_size'), 'total_size', $getTotalTableSize),
+ $runBeforeGenericFilters = true);
+
+ $sizeColumns[] = 'total_size';
+ }
+
+ $runPrettySizeFilterBeforeGeneric = false;
+ $fixedMemoryUnit = false;
+ if ($view instanceof Piwik_ViewDataTable_HtmlTable) // if displaying a table
+ {
+ $view->disableRowEvolution();
+
+ // add summary row only if displaying a table
+ $view->queueFilter('AddSummaryRow', array(0, Piwik_Translate('General_Total'), 'label', false),
+ $runBeforeGenericFilters = true);
+
+ // add other filters
+ if ($addPercentColumn && in_array('total_size', $sizeColumns))
+ {
+ $view->queueFilter('ColumnCallbackAddColumnPercentage',
+ array('percent_total', 'total_size', 'total_size', $quotientPrecision = 0, $shouldSkipRows = false,
+ $getDivisorFromSummaryRow = true),
+ $runBeforeGenericFilters = true);
+ $view->setSortedColumn('percent_total', $orderDir);
+ }
+ }
+ else if ($view instanceof Piwik_ViewDataTable_GenerateGraphData) // if displaying a graph
+ {
+ if (in_array('total_size', $sizeColumns))
+ {
+ $view->setColumnsToDisplay(array('label', 'total_size'));
+
+ // when displaying a graph, we force sizes to be shown as the same unit so axis labels
+ // will be readable. NOTE: The unit should depend on the smallest value of the data table,
+ // however there's no way to know this information, short of creating a custom filter. For
+ // now, just assume KB.
+ $fixedMemoryUnit = 'K';
+ $view->setAxisYUnit(' K');
+
+ $view->setSortedColumn('total_size', 'desc');
+
+ $runPrettySizeFilterBeforeGeneric = true;
+ }
+ else
+ {
+ $view->setColumnsToDisplay(array('label', 'row_count'));
+ $view->setAxisYUnit(' '.Piwik_Translate('General_Rows'));
+
+ $view->setSortedColumn('row_count', 'desc');
+ }
+ }
+
+ $getPrettySize = array('Piwik', 'getPrettySizeFromBytes');
+ $params = $fixedMemoryUnit === false ? array() : array($fixedMemoryUnit);
+ $view->queueFilter(
+ 'ColumnCallbackReplace', array($sizeColumns, $getPrettySize, $params), $runPrettySizeFilterBeforeGeneric);
+
+ // jqPlot will display &nbsp; as, well, '&nbsp;', so don't replace the spaces when rendering as a graph
+ if (!($view instanceof Piwik_ViewDataTable_GenerateGraphData))
+ {
+ $replaceSpaces = array($this, 'replaceColumnSpaces');
+ $view->queueFilter('ColumnCallbackReplace', array($sizeColumns, $replaceSpaces));
+ }
+
+ $getPrettyNumber = array('Piwik', 'getPrettyNumber');
+ $view->queueFilter('ColumnCallbackReplace', array(array('row_count'), $getPrettyNumber));
+
+ return $view;
+ }
+
+ /**
+ * Replaces spaces w/ &nbsp; for correct HTML output.
+ */
+ public function replaceColumnSpaces( $value )
+ {
+ return str_replace(' ', '&nbsp;', $value);
+ }
+
+ /**
+ * Row callback function that calculates a tables total size.
+ */
+ public function getTotalTableSize( $dataSize, $indexSize )
+ {
+ return $dataSize + $indexSize;
+ }
+
+ /**
+ * Column callback used to translate the column values in the database usage summary table.
+ */
+ public function translateSummarylabel( $value )
+ {
+ static $valueToTranslationStr = array(
+ 'tracker_data' => 'DBStats_TrackerTables',
+ 'report_data' => 'DBStats_ReportTables',
+ 'metric_data' => 'DBStats_MetricTables',
+ 'other_data' => 'DBStats_OtherTables'
+ );
+
+ return isset($valueToTranslationStr[$value])
+ ? Piwik_Translate($valueToTranslationStr[$value])
+ : $value;
+ }
+
+ /**
+ * Sets the footer message for the Individual...Summary reports.
+ */
+ private function setIndividualSummaryFooterMessage( $view )
+ {
+ $lastGenerated = Piwik_DBStats::getDateOfLastCachingRun();
+ if ($lastGenerated !== false)
+ {
+ $view->setFooterMessage(Piwik_Translate('Mobile_LastUpdated', $lastGenerated));
+ }
}
}
diff --git a/plugins/DBStats/DBStats.php b/plugins/DBStats/DBStats.php
index 3045810bfc..2b2f00ecae 100644
--- a/plugins/DBStats/DBStats.php
+++ b/plugins/DBStats/DBStats.php
@@ -16,6 +16,8 @@
*/
class Piwik_DBStats extends Piwik_Plugin
{
+ const TIME_OF_LAST_TASK_RUN_OPTION = 'dbstats_time_of_last_cache_task_run';
+
public function getInformation()
{
return array(
@@ -28,7 +30,10 @@ class Piwik_DBStats extends Piwik_Plugin
function getListHooksRegistered()
{
- return array('AdminMenu.add' => 'addMenu');
+ return array(
+ 'AdminMenu.add' => 'addMenu',
+ 'TaskScheduler.getScheduledTasks' => 'getScheduledTasks',
+ );
}
function addMenu()
@@ -38,4 +43,39 @@ class Piwik_DBStats extends Piwik_Plugin
Piwik::isUserIsSuperUser(),
$order = 9);
}
+
+ /**
+ * Gets all scheduled tasks executed by this plugin.
+ *
+ * @param Piwik_Event_Notification $notification notification object
+ */
+ public function getScheduledTasks($notification)
+ {
+ $tasks = &$notification->getNotificationObject();
+
+ $priority = Piwik_ScheduledTask::LOWEST_PRIORITY;
+ $cacheDataByArchiveNameReportsTask = new Piwik_ScheduledTask(
+ $this, 'cacheDataByArchiveNameReports', new Piwik_ScheduledTime_Weekly(), $priority);
+ $tasks[] = $cacheDataByArchiveNameReportsTask;
+ }
+
+ /**
+ * Caches the intermediate DataTables used in the getIndividualReportsSummary and
+ * getIndividualMetricsSummary reports in the option table.
+ */
+ public function cacheDataByArchiveNameReports()
+ {
+ $api = Piwik_DBStats_API::getInstance();
+ $api->getIndividualReportsSummary(true);
+ $api->getIndividualMetricsSummary(true);
+
+ $now = Piwik_Date::now()->getLocalized("%longYear%, %shortMonth% %day%");
+ Piwik_SetOption(self::TIME_OF_LAST_TASK_RUN_OPTION, $now);
+ }
+
+ /** Returns the date when the cacheDataByArchiveNameReports was last run. */
+ public static function getDateOfLastCachingRun()
+ {
+ return Piwik_GetOption(self::TIME_OF_LAST_TASK_RUN_OPTION);
+ }
}
diff --git a/plugins/DBStats/MySQLMetadataProvider.php b/plugins/DBStats/MySQLMetadataProvider.php
new file mode 100755
index 0000000000..53ec8c3324
--- /dev/null
+++ b/plugins/DBStats/MySQLMetadataProvider.php
@@ -0,0 +1,378 @@
+<?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: MySQLMetadataProvider.php $
+ *
+ * @category Piwik_Plugins
+ * @package Piwik_DBStats
+ */
+
+/**
+ * Utility class that provides general information about databases, including the size of
+ * the entire database, the size and row count of each table and the size and row count
+ * of each metric/report type currently stored.
+ *
+ * This class will cache the table information it retrieves from the database. In order to
+ * issue a new query instead of using this cache, you must create a new instance of this type.
+ */
+class Piwik_DBStats_MySQLMetadataProvider
+{
+ /**
+ * Cached MySQL table statuses. So we won't needlessly re-issue SHOW TABLE STATUS queries.
+ */
+ private $tableStatuses = null;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ // empty
+ }
+
+ /**
+ * Gets general database info that is not specific to any table.
+ *
+ * @return array See http://dev.mysql.com/doc/refman/5.1/en/show-status.html .
+ */
+ public function getDBStatus()
+ {
+ if (function_exists('mysql_connect'))
+ {
+ $configDb = Piwik_Config::getInstance()->database;
+ $link = mysql_connect($configDb['host'], $configDb['username'], $configDb['password']);
+ $status = mysql_stat($link);
+ mysql_close($link);
+ $status = explode(" ", $status);
+ }
+ else
+ {
+ $fullStatus = Piwik_FetchAssoc('SHOW STATUS');
+ if (empty($fullStatus))
+ {
+ throw new Exception('Error, SHOW STATUS failed');
+ }
+
+ $status = array(
+ 'Uptime' => $fullStatus['Uptime']['Value'],
+ 'Threads' => $fullStatus['Threads_running']['Value'],
+ 'Questions' => $fullStatus['Questions']['Value'],
+ 'Slow queries' => $fullStatus['Slow_queries']['Value'],
+ 'Flush tables' => $fullStatus['Flush_commands']['Value'],
+ 'Open tables' => $fullStatus['Open_tables']['Value'],
+ 'Opens' => 'unavailable', // not available via SHOW STATUS
+ 'Queries per second avg' => 'unavailable' // not available via SHOW STATUS
+ );
+ }
+
+ return $status;
+ }
+
+ /**
+ * Gets the MySQL table status of the requested Piwik table.
+ *
+ * @param string $table The name of the table. Should not be prefixed (ie, 'log_visit' is
+ * correct, 'piwik_log_visit' is not).
+ * @return array See http://dev.mysql.com/doc/refman/5.1/en/show-table-status.html .
+ */
+ public function getTableStatus( $table )
+ {
+ $prefixed = Piwik_Common::prefixTable($table);
+
+ // if we've already gotten every table status, don't issue an uneeded query
+ if (!is_null($this->tableStatuses) && isset($this->tableStatuses[$prefixed]))
+ {
+ return $this->tableStatuses[$prefixed];
+ }
+ else
+ {
+ return Piwik_FetchRow("SHOW TABLE STATUS LIKE ?", array($prefixed));
+ }
+ }
+
+ /**
+ * Gets the result of a SHOW TABLE STATUS query for every Piwik table in the DB.
+ * Non-piwik tables are ignored.
+ *
+ * @param string $matchingRegex Regex used to filter out tables whose name doesn't
+ * match it.
+ * @return array The table information. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html
+ * for specifics.
+ */
+ public function getAllTablesStatus( $matchingRegex = null )
+ {
+ if (is_null($this->tableStatuses))
+ {
+ $tablesPiwik = Piwik::getTablesInstalled();
+
+ $this->tableStatuses = array();
+ foreach(Piwik_FetchAll("SHOW TABLE STATUS") as $t)
+ {
+ if (in_array($t['Name'], $tablesPiwik))
+ {
+ $this->tableStatuses[$t['Name']] = $t;
+ }
+ }
+ }
+
+ if (is_null($matchingRegex))
+ {
+ return $this->tableStatuses;
+ }
+
+ $result = array();
+ foreach ($this->tableStatuses as $status)
+ {
+ if (preg_match($matchingRegex, $status['Name']))
+ {
+ $result[] = $status;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Returns table statuses for every log table.
+ *
+ * @return array An array of status arrays. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html.
+ */
+ public function getAllLogTableStatus()
+ {
+ $regex = "/^".Piwik_Common::prefixTable('log_')."(?!profiling)/";
+ return $this->getAllTablesStatus($regex);
+ }
+
+ /**
+ * Returns table statuses for every numeric archive table.
+ *
+ * @return array An array of status arrays. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html.
+ */
+ public function getAllNumericArchiveStatus()
+ {
+ $regex = "/^".Piwik_Common::prefixTable('archive_numeric')."_/";
+ return $this->getAllTablesStatus($regex);
+ }
+
+ /**
+ * Returns table statuses for every blob archive table.
+ *
+ * @return array An array of status arrays. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html.
+ */
+ public function getAllBlobArchiveStatus()
+ {
+ $regex = "/^".Piwik_Common::prefixTable('archive_blob')."_/";
+ return $this->getAllTablesStatus($regex);
+ }
+
+ /**
+ * Retruns table statuses for every admin table.
+ *
+ * @return array An array of status arrays. See http://dev.mysql.com/doc/refman/5.5/en/show-table-status.html.
+ */
+ public function getAllAdminTableStatus()
+ {
+ $regex = "/^".Piwik_Common::prefixTable('')."(?!archive_|(?:log_(?!profiling)))/";
+ return $this->getAllTablesStatus($regex);
+ }
+
+ /**
+ * Returns a DataTable that lists the number of rows and the estimated amount of space
+ * each blob archive type takes up in the database.
+ *
+ * Blob types are differentiated by name.
+ *
+ * @param bool $forceCache false to use the cached result, true to run the queries again and
+ * cache the result.
+ * @return Piwik_DataTable
+ */
+ public function getRowCountsAndSizeByBlobName( $forceCache = false )
+ {
+ $extraSelects = array("SUM(OCTET_LENGTH(value)) AS 'blob_size'", "SUM(LENGTH(name)) AS 'name_size'");
+ $extraCols = array('blob_size', 'name_size');
+ return $this->getRowCountsByArchiveName(
+ $this->getAllBlobArchiveStatus(), 'getEstimatedBlobArchiveRowSize', $forceCache, $extraSelects,
+ $extraCols);
+ }
+
+ /**
+ * Returns a DataTable that lists the number of rows and the estimated amount of space
+ * each metric archive type takes up in the database.
+ *
+ * Metric types are differentiated by name.
+ *
+ * @param bool $forceCache false to use the cached result, true to run the queries again and
+ * cache the result.
+ * @return Piwik_DataTable
+ */
+ public function getRowCountsAndSizeByMetricName( $forceCache = false )
+ {
+ return $this->getRowCountsByArchiveName(
+ $this->getAllNumericArchiveStatus(), 'getEstimatedRowsSize', $forceCache);
+ }
+
+ /**
+ * Utility function. Gets row count of a set of tables grouped by the 'name' column.
+ * This is the implementation of the getRowCountsAndSizeBy... functions.
+ */
+ private function getRowCountsByArchiveName( $statuses, $getRowSizeMethod, $forceCache = false,
+ $otherSelects = array(), $otherDataTableColumns = array() )
+ {
+ $extraCols = '';
+ if (!empty($otherSelects))
+ {
+ $extraCols = ', '.implode(', ', $otherSelects);
+ }
+
+ $cols = array_merge(array('row_count'), $otherDataTableColumns);
+
+ $dataTable = new Piwik_DataTable();
+ foreach ($statuses as $status)
+ {
+ $dataTableOptionName = $this->getCachedOptionName($status['Name'], 'byArchiveName');
+
+ // if option exists && !$forceCache, use the cached data, otherwise create the
+ $cachedData = Piwik_GetOption($dataTableOptionName);
+ if ($cachedData !== false && !$forceCache)
+ {
+ $table = new Piwik_DataTable();
+ $table->addRowsFromSerializedArray($cachedData);
+ }
+ else
+ {
+ // otherwise, create data table & cache it
+ $sql = "SELECT name as 'label', COUNT(*) as 'row_count'$extraCols FROM {$status['Name']} GROUP BY name";
+
+ $table = new Piwik_DataTable();
+ $table->addRowsFromSimpleArray(Piwik_FetchAll($sql));
+
+ $reduceArchiveRowName = array($this, 'reduceArchiveRowName');
+ $table->filter('GroupBy', array('label', $reduceArchiveRowName));
+
+ Piwik_SetOption($dataTableOptionName, reset($table->getSerialized()));
+ }
+
+ // add estimated_size column
+ $getEstimatedSize = array($this, $getRowSizeMethod);
+ $table->filter('ColumnCallbackAddColumn',
+ array($cols, 'estimated_size', $getEstimatedSize, array($status)));
+
+ $dataTable->addDataTable($table);
+ destroy($table);
+ }
+ return $dataTable;
+ }
+
+ /**
+ * Gets the estimated database size a count of rows takes in a table.
+ */
+ public function getEstimatedRowsSize( $row_count, $status )
+ {
+ $avgRowSize = ($status['Data_length'] + $status['Index_length']) / $status['Rows'];
+ return $avgRowSize * $row_count;
+ }
+
+ /**
+ * Gets the estimated database size a count of rows in a blob_archive table. Depends on
+ * the data table row to contain the size of all blobs & name strings in the row set it
+ * represents.
+ */
+ public function getEstimatedBlobArchiveRowSize( $row_count, $blob_size, $name_size, $status )
+ {
+ // calculate the size of each fixed size column in a blob archive table
+ static $fixedSizeColumnLength = null;
+ if (is_null($fixedSizeColumnLength))
+ {
+ $fixedSizeColumnLength = 0;
+ foreach (Piwik_FetchAll("SHOW COLUMNS FROM ".$status['Name']) as $column)
+ {
+ $columnType = $column['Type'];
+
+ if (($paren = strpos($columnType, '(')) !== false)
+ {
+ $columnType = substr($columnType, 0, $paren);
+ }
+
+ $fixedSizeColumnLength += $this->sizeOfMySQLColumn($columnType);
+ }
+ }
+
+ // calculate the average row size
+ $avgRowSize = $status['Index_length'] / $status['Rows'] + $fixedSizeColumnLength;
+
+ // calculate the row set's size
+ return $avgRowSize * $row_count + $blob_size + $name_size;
+ }
+
+ /** Returns the size in bytes of a fixed size MySQL data type. Returns 0 for unsupported data type. */
+ private function sizeOfMySQLColumn( $columnType )
+ {
+ switch (strtolower($columnType))
+ {
+ case "tinyint":
+ case "year":
+ return 1;
+ case "smallint":
+ return 2;
+ case "mediumint":
+ case "date":
+ case "time":
+ return 3;
+ case "int":
+ case "float": // assumes precision isn't used
+ case "timestamp":
+ return 4;
+ case "bigint":
+ case "double":
+ case "real":
+ case "datetime":
+ return 8;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Gets the option name used to cache the result of an intensive query.
+ */
+ private function getCachedOptionName( $tableName, $suffix )
+ {
+ return 'dbstats_cached_'.$tableName.'_'.$suffix;
+ }
+
+ /**
+ * Reduces the given metric name. Used to simplify certain reports.
+ *
+ * Some metrics, like goal metrics, can have different string names. For goal metrics,
+ * there's one name per goal ID. Grouping metrics and reports like these together
+ * simplifies the tables that display them.
+ *
+ * This function makes goal names, 'done...' names and names of the format .*_[0-9]+
+ * equivalent.
+ */
+ public function reduceArchiveRowName( $name )
+ {
+ // all 'done...' fields are considered the same
+ if (strpos($name, 'done') === 0)
+ {
+ return 'done';
+ }
+
+ // check for goal id, if present (Goals_... reports should not be reduced here, just Goal_... ones)
+ if (preg_match("/^Goal_(?:-?[0-9]+_)?(.*)/", $name, $matches))
+ {
+ $name = "Goal_*_".$matches[1];
+ }
+
+ // remove subtable id suffix, if present
+ if (preg_match("/^(.*)_[0-9]+$/", $name, $matches))
+ {
+ $name = $matches[1]."_*";
+ }
+
+ return $name;
+ }
+}
+
diff --git a/plugins/DBStats/templates/DBStats.tpl b/plugins/DBStats/templates/DBStats.tpl
deleted file mode 100644
index 686e3d1fca..0000000000
--- a/plugins/DBStats/templates/DBStats.tpl
+++ /dev/null
@@ -1,52 +0,0 @@
-{assign var=showSitesSelection value=false}
-{assign var=showPeriodSelection value=false}
-{include file="CoreAdminHome/templates/header.tpl"}
-<div style="max-width:980px;">
-
-<h2>{'DBStats_DatabaseUsage'|translate}</h2>
-{assign var=totalSize value=$tablesStatus.Total.Total_length}
-<p>{'DBStats_MainDescription'|translate:$totalSize}
-<br />
-{'DBStats_LearnMore'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/setup-auto-archiving/' target='_blank'>Piwik Auto Archiving</a>"}
-<br />
-{'PrivacyManager_DeleteDataSettings'|translate}: <a href='{url module="PrivacyManager" action="privacySettings"}#deleteLogsAnchor'>
-{capture assign=clickDeleteLogSettings}{'PrivacyManager_DeleteDataSettings'|translate}{/capture}
- {'PrivacyManager_ClickHereSettings'|translate:"'$clickDeleteLogSettings'"}
- </a>
-
-<table class="dataTable entityTable">
- <thead>
- <tr>
- <th>{'DBStats_Table'|translate}</th>
- <th>{'DBStats_RowCount'|translate}</th>
- <th>{'DBStats_DataSize'|translate}</th>
- <th>{'DBStats_IndexSize'|translate}</th>
- <th>{'DBStats_TotalSize'|translate}</th>
- </tr>
- </thead>
- <tbody id="tables">
- {foreach from=$tablesStatus key=index item=table}
- <tr {if $table.Name == 'Total'}class="highlight" style="font-weight:bold;"{/if}>
- <td>
- {$table.Name}
- </td>
- <td>
- {$table.Rows}
- </td>
- <td>
- {$table.Data_length}b
- </td>
- <td>
- {$table.Index_length}b
- </td>
- <td>
- {$table.Total_length}b
- </td>
- </tr>
- {/foreach}
- </tbody>
-</table>
-
-</div>
-
-{include file="CoreAdminHome/templates/footer.tpl"}
diff --git a/plugins/DBStats/templates/index.tpl b/plugins/DBStats/templates/index.tpl
new file mode 100755
index 0000000000..b84ab4691c
--- /dev/null
+++ b/plugins/DBStats/templates/index.tpl
@@ -0,0 +1,154 @@
+{assign var=showSitesSelection value=false}
+{assign var=showPeriodSelection value=false}
+{include file="CoreAdminHome/templates/header.tpl"}
+{loadJavascriptTranslations plugins='CoreAdminHome CoreHome'}
+
+{literal}
+<style>
+.dbstatsTable {
+ display: inline-block;
+}
+.dbstatsTable>tbody>tr>td:first-child {
+ width: 550px;
+}
+.dbstatsTable h2 {
+ width: 500px;
+}
+.adminTable.dbstatsTable a {
+ color: black;
+ text-decoration: underline;
+}
+</style>
+{/literal}
+
+<a name="databaseUsageSummary"></a>
+<h2>{'DBStats_DatabaseUsage'|translate}</h2>
+<p>
+ {'DBStats_MainDescription'|translate:$totalSpaceUsed}<br/>
+ {'DBStats_LearnMore'|translate:"<a href='?module=Proxy&action=redirect&url=http://piwik.org/docs/setup-auto-archiving/' target='_blank'>Piwik Auto Archiving</a>"}<br/>
+ <br/>
+</p>
+<table class="adminTable dbstatsTable">
+ <tbody>
+ <tr>
+ <td>{$databaseUsageSummary}</td>
+ <td>
+ <h3 style="margin-top:0">{'General_GeneralInformation'|translate}</h3><br/>
+ <p style="font-size:1.4em;padding-left:21px;line-height:1.8em">
+ <strong><em>{$userCount}</strong></em>&nbsp;{if $userCount == 1}{'UsersManager_User'|translate}{else}{'UsersManager_MenuUsers'|translate}{/if}<br/>
+ <strong><em>{$siteCount}</strong></em>&nbsp;{if $siteCount == 1}{'General_Website'|translate}{else}{'Referers_Websites'|translate}{/if}
+ </p><br/>
+ {capture assign=clickDeleteLogSettings}{'PrivacyManager_DeleteDataSettings'|translate}{/capture}
+ <h3 style="margin-top:0">{'PrivacyManager_DeleteDataSettings'|translate}</h3><br/>
+ <p>
+ {'PrivacyManager_DeleteDataDescription'|translate}
+ <br/>
+ <a href='{url module="PrivacyManager" action="privacySettings"}#deleteLogsAnchor'>
+ {'PrivacyManager_ClickHereSettings'|translate:"'$clickDeleteLogSettings'"}
+ </a>
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<br/>
+
+<a name="trackerDataSummary"></a>
+<table class="adminTable dbstatsTable">
+ <tbody>
+ <tr>
+ <td>
+ <h2>{'DBStats_TrackerTables'|translate}</h2>
+ {$trackerDataSummary}
+ </td>
+ <td>&nbsp;</td>
+ </tr>
+ </tbody>
+</table>
+
+<a name="reportDataSummary"></a>
+<table class="adminTable dbstatsTable">
+ <tbody>
+ <tr>
+ <td>
+ <h2>{'DBStats_ReportTables'|translate}</h2>
+ {$reportDataSummary}
+ </td>
+ <td>
+ <h2>{'General_Reports'|translate}</h2>
+ <div class="ajaxLoad" href="/index.php?module=DBStats&action=getIndividualReportsSummary&viewDataTable=table">
+ <span class="loadingPiwik"><img src="themes/default/images/loading-blue.gif" />{'General_LoadingData'|translate}</span>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<a name="metricDataSummary"></a>
+<table class="adminTable dbstatsTable">
+ <tbody>
+ <tr>
+ <td>
+ <h2>{'DBStats_MetricTables'|translate}</h2>
+ {$metricDataSummary}
+ </td>
+ <td>
+ <h2>{'General_Metrics'|translate}</h2>
+ <div class="ajaxLoad" href="/index.php?module=DBStats&action=getIndividualMetricsSummary&viewDataTable=table">
+ <span class="loadingPiwik"><img src="themes/default/images/loading-blue.gif" />{'General_LoadingData'|translate}</span>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<a name="adminDataSummary"></a>
+<table class="adminTable dbstatsTable">
+ <tbody>
+ <tr>
+ <td>
+ <h2>{'DBStats_OtherTables'|translate}</h2>
+ {$adminDataSummary}
+ </td>
+ <td>&nbsp;</td>
+ </tr>
+ </tbody>
+</table>
+
+{literal}
+<script type="text/javascript">
+(function( $ ){
+ $(document).ready(function() {
+ $('.ajaxLoad').each(function() {
+ var self = this,
+ reportUrl = $(this).attr('href');
+
+ // build & execute AJAX request
+ var request =
+ {
+ type: 'GET',
+ url: reportUrl,
+ dataType: 'html',
+ async: true,
+ error: piwikHelper.ajaxHandleError, // Callback when the request fails
+ data: {
+ idSite: broadcast.getValueFromUrl('idSite'),
+ period: broadcast.getValueFromUrl('period'),
+ date: broadcast.getValueFromUrl('date')
+ },
+ success: function(data) {
+ $('.loadingPiwik', self).hide();
+ $(self).html(data);
+ }
+ };
+
+ piwikHelper.queueAjaxRequest($.ajax(request));
+ });
+ });
+})( jQuery );
+</script>
+{/literal}
+
+{include file="CoreAdminHome/templates/footer.tpl"}
+
diff --git a/plugins/PrivacyManager/templates/privacySettings.tpl b/plugins/PrivacyManager/templates/privacySettings.tpl
index 9b05e18d91..1fcb6c0daa 100644
--- a/plugins/PrivacyManager/templates/privacySettings.tpl
+++ b/plugins/PrivacyManager/templates/privacySettings.tpl
@@ -72,7 +72,7 @@ See also our official guide <b><a href='http://piwik.org/privacy/' target='_blan
<a name="deleteLogsAnchor"></a>
<h2>{'PrivacyManager_DeleteDataSettings'|translate}</h2>
-<p>{'PrivacyManager_DeleteDataDescription'|translate}</p>
+<p>{'PrivacyManager_DeleteDataDescription'|translate} {'PrivacyManager_DeleteDataDescription2'|translate}</p>
<form method="post" action="{url action=saveSettings form=formDeleteSettings token_auth=$token_auth}" id="formDeleteSettings" name="formMaskLength">
<table class="adminTable" style='width:800px;'>
<tr id='deleteLogSettingEnabled'>