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:
authorbenakamoorthi <benaka.moorthi@gmail.com>2012-05-26 23:39:16 +0400
committerbenakamoorthi <benaka.moorthi@gmail.com>2012-05-26 23:39:16 +0400
commit71fc2e87eb99212ccd0da6235a8410f55c39c7b5 (patch)
tree6c1ca494613399a26eedbbff0247f46a63ecd84e
parent6add1fa233d9312651d79efb5f76fb5da62b0960 (diff)
Fixes #3004, redesigned DBStats plugin, added several new reports including database space taken up by tracker tables, database space taken up by archive blob tables, database space taken up by archive metric tables, database space taken up by individual reports & database space taken up by individual metrics.
Notes: * Added ability to highlight the summary row in ViewDataTable and the ability to always show the summary row regardless of what report page is currently being shown. * Fixed small issue w/ ViewDataTable::hasReportBeenPurged: default values should be specified in calls to getRequestVar. * Added Piwik_FetchAssoc function to PluginsFunctions/Sql.php * Augmented ColumnCallbackAddColumnQuotient filter so the divisor can be obtained from the summary row. * Modified AddSummaryRow filter so it wouldn't delete rows if desired. * Added ColumnCallbackAddColumn filer that adds a new column to each row based on the result of a callback. * Modified ColumnCallbackReplace filter so callback can operate on more than one column value if desired. * Modified Limit filter so, if desired, the summary row can be exempted from deletion. * Added GroupBy filter that groups/sums rows by the result of a callback function. * Fixed GenerateGraphData.php bug where priority filters were not called on view data table. * Added getPrettyNumber utility function. git-svn-id: http://dev.piwik.org/svn/trunk@6324 59fd770c-687e-43c8-a1e3-f5a4ff64c105
-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'>