diff --git a/core/ArchiveProcessor/Period.php b/core/ArchiveProcessor/Period.php index 8a87c1fd1e7d96557d77db8750c97dd925e828e1..a885bfefde9533a856d1392830199876cdaece29 100644 --- a/core/ArchiveProcessor/Period.php +++ b/core/ArchiveProcessor/Period.php @@ -164,7 +164,7 @@ class Period extends ArchiveProcessor { $table = new DataTable(); if (!empty($columnAggregationOperations)) { - $table->setColumnAggregationOperations($columnAggregationOperations); + $table->metadata[DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME] = $columnAggregationOperations; } $data = $this->archiver->getDataTableExpanded($name, $idSubTable = null, $depth = null, $addMetadataSubtableId = false); diff --git a/core/DataTable.php b/core/DataTable.php index 208e2173506e71f895c2c5dac7f22122bc6a5c11..e985f09b7591dee385acefd8d0345168af061386 100644 --- a/core/DataTable.php +++ b/core/DataTable.php @@ -29,11 +29,14 @@ require_once PIWIK_INCLUDE_PATH . '/core/Common.php'; /** * The primary data structure used to store analytics data in Piwik. * + * <a name="class-desc-the-basics"></a> * ### The Basics * * DataTables consist of rows and each row consists of columns. A column value can be * be a numeric, string or array. * + * Every row has an ID. The ID is either the index of the row or [ID_SUMMARY_ROW](#ID_SUMMARY_ROW). + * * DataTables are hierarchical data structures. Each row can also contain an additional * nested sub-DataTable. * @@ -100,9 +103,64 @@ require_once PIWIK_INCLUDE_PATH . '/core/Common.php'; * ### Examples * * **Populating a DataTable** + * + * // adding one row at a time + * $dataTable = new DataTable(); + * $dataTable->addRow(new Row(array( + * Row::COLUMNS => array('label' => 'thing1', 'nb_visits' => 1, 'nb_actions' => 1), + * Row::METADATA => array('url' => 'http://thing1.com') + * ))); + * $dataTable->addRow(new Row(array( + * Row::COLUMNS => array('label' => 'thing2', 'nb_visits' => 2, 'nb_actions' => 2), + * Row::METADATA => array('url' => 'http://thing2.com') + * ))); + * + * // using an array of rows + * $dataTable = new DataTable(); + * $dataTable->addRowsFromArray(array( + * array( + * Row::COLUMNS => array('label' => 'thing1', 'nb_visits' => 1, 'nb_actions' => 1), + * Row::METADATA => array('url' => 'http://thing1.com') + * ), + * array( + * Row::COLUMNS => array('label' => 'thing2', 'nb_visits' => 2, 'nb_actions' => 2), + * Row::METADATA => array('url' => 'http://thing2.com') + * ) + * )); + * + * // using a "simple" array + * $dataTable->addRowsFromSimpleArray(array( + * array('label' => 'thing1', 'nb_visits' => 1, 'nb_actions' => 1), + * array('label' => 'thing2', 'nb_visits' => 2, 'nb_actions' => 2) + * )); + * + * **Getting & setting metadata** + * + * $dataTable = \Piwik\Plugins\Referrers\API::getInstance()->getSearchEngines($idSite = 1, $period = 'day', $date = '2007-07-24'); + * $oldPeriod = $dataTable->metadata['period']; + * $dataTable->metadata['period'] = Period::factory('week', Date::factory('2013-10-18')); + * * **Serializing & unserializing** + * + * $maxRowsInTable = Config::getInstance()->General['datatable_archiving_maximum_rows_standard'];j + * + * $dataTable = // ... build by aggregating visits ... + * $serializedData = $dataTable->getSerialized($maxRowsInTable, $maxRowsInSubtable = $maxRowsInTable, + * $columnToSortBy = Metrics::INDEX_NB_VISITS); + * + * $serializedDataTable = $serializedData['0']; + * $serailizedSubTable = $serializedData[$idSubtable]; + * * **Filtering for an API method** - * ??? TODO + * + * public function getMyReport($idSite, $period, $date, $segment = false, $expanded = false) + * { + * $dataTable = Archive::getDataTableFromArchive('MyPlugin_MyReport', $idSite, $period, $date, $segment, $expanded); + * $dataTable->filter('Sort', array(Metrics::INDEX_NB_VISITS, 'desc', $naturalSort = false, $expanded)); + * $dataTable->queueFilter('ReplaceColumnNames'); + * $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', __NAMESPACE__ . '\getUrlFromLabelForMyReport')); + * return $dataTable; + * } * * @package Piwik * @subpackage DataTable @@ -111,12 +169,38 @@ require_once PIWIK_INCLUDE_PATH . '/core/Common.php'; */ class DataTable implements DataTableInterface { + const MAX_DEPTH_DEFAULT = 15; + /** Name for metadata that describes when a report was archived. */ const ARCHIVED_DATE_METADATA_NAME = 'archived_date'; - const MAX_DEPTH_DEFAULT = 15; + /** Name for metadata that describes which columns are empty and should not be shown. */ const EMPTY_COLUMNS_METADATA_NAME = 'empty_column'; + /** Name for metadata that describes the number of rows that existed before the Limit filter was applied. */ + const TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME = 'total_rows_before_limit'; + + /** + * Name for metadata that describes how individual columns should be aggregated when [addDataTable](#addDataTable) + * or [DataTable\Row::sumRow](#) is called. + * + * This metadata value must be an array that maps column names with valid operations. Valid aggregation operations are: + * + * - `'skip'`: do nothing + * - `'max'`: does `max($column1, $column2)` + * - `'min'`: does `min($column1, $column2)` + * - `'sum'`: does `$column1 + $column2` + * + * See [addDataTable](#addDataTable) and [DataTable\Row::sumRow](#) for more information. + */ + const COLUMN_AGGREGATION_OPS_METADATA_NAME = 'column_aggregation_ops'; + + /** The ID of the Summary Row. */ + const ID_SUMMARY_ROW = -1; + + /** The original label of the Summary Row. */ + const LABEL_SUMMARY_ROW = -1; + /** * Maximum nesting level. */ @@ -207,7 +291,9 @@ class DataTable implements DataTableInterface protected $summaryRow = null; /** - * Table metadata. + * Table metadata. Read [this](#class-desc-the-basics) to learn more. + * + * Any data that describes the data held in the table's rows should go here. * * @var array */ @@ -222,24 +308,19 @@ class DataTable implements DataTableInterface protected $maximumAllowedRows = 0; /** - * The operations that should be used when aggregating columns from multiple rows. - * @see self::addDataTable() and DataTable\Row::sumRow() - */ - protected $columnAggregationOperations = array(); - - const ID_SUMMARY_ROW = -1; - const LABEL_SUMMARY_ROW = -1; - - /** - * Builds the DataTable, registers itself to the manager + * Constructor. Creates an empty DataTable. */ public function __construct() { + // registers this instance to the manager $this->currentId = Manager::getInstance()->addTable($this); + + // initialize some metadata + $this->metadata[self::COLUMN_AGGREGATION_OPS_METADATA_NAME] = array(); } /** - * At destruction we free all memory + * Destructor. Makes sure DataTable memory will be cleaned up. */ public function __destruct() { @@ -259,10 +340,11 @@ class DataTable implements DataTableInterface } /** - * Sort the dataTable rows using the php callback function + * Sorts the DataTable rows using the supplied callback function. * - * @param string $functionCallback - * @param string $columnSortedBy The column name. Used to then ask the datatable what column are you sorted by + * @param string $functionCallback A comparison callback compatible with `usort`. + * @param string $columnSortedBy The column name `$functionCallback` sorts by. This is stored + * so we can determine how the DataTable is sorted in the future. */ public function sort($functionCallback, $columnSortedBy) { @@ -282,9 +364,11 @@ class DataTable implements DataTableInterface } /** - * Returns the name of the column the tables is sorted by + * Returns the name of the column this table was sorted by (if any). * - * @return bool|string + * See [sort](#sort). + * + * @return false|string The sorted column name or false if none. */ public function getSortedByColumnName() { @@ -292,8 +376,8 @@ class DataTable implements DataTableInterface } /** - * Enables the recursive sort. Means that when using $table->sort() - * it will also sort all subtables using the same callback + * Enables recursive sorting. If this method is called [sort](#sort) will also sort all + * subtables. */ public function enableRecursiveSort() { @@ -301,8 +385,8 @@ class DataTable implements DataTableInterface } /** - * Enables the recursive filter. Means that when using $table->filter() - * it will also filter all subtables using the same callback + * Enables recursive filtering. If this method is called then the [filter](#filter) method + * will apply filters to every subtable in addition to this instance. */ public function enableRecursiveFilters() { @@ -310,33 +394,15 @@ class DataTable implements DataTableInterface } /** - * Returns the number of rows before we applied the limit filter - * - * @return int - */ - public function getRowsCountBeforeLimitFilter() - { - $toReturn = $this->rowsCountBeforeLimitFilter; - if ($toReturn == 0) { - return $this->getRowsCount(); - } - return $toReturn; - } - - /** - * Saves the current number of rows - */ - public function setRowsCountBeforeLimitFilter() - { - $this->rowsCountBeforeLimitFilter = $this->getRowsCount(); - } - - /** - * Apply a filter to this datatable + * Applies filter to this datatable. + * + * If [enableRecursiveFilters](#enableRecursiveFilters) was called, the filter will be applied + * to all subtables as well. * - * @param string|Closure $className Class name, eg. "Sort" or "Sort". - * If this variable is a closure, it will get executed immediately. - * @param array $parameters Array of parameters to the filter, eg. array('nb_visits', 'asc') + * @param string|Closure $className Class name, eg. "Sort" or "Sort". If no namespace is supplied, + * `Piwik\DataTable\Filter` is assumed. This parameter can also be + * a closure that takes a DataTable as its first parameter. + * @param array $parameters Array of parameters pass to the filter in addition to the table. */ public function filter($className, $parameters = array()) { @@ -363,10 +429,13 @@ class DataTable implements DataTableInterface } /** - * Queue a DataTable_Filter that will be applied when applyQueuedFilters() is called. - * (just before sending the datatable back to the browser (or API, etc.) + * Adds a filter and a list of parameters to the list of queued filters. These filters will be + * executed when [applyQueuedFilters](#applyQueuedFilters) is called. + * + * Filters that prettify the output or don't need the full set of rows should be queued. This + * way they will be run after the table is truncated which will result in better performance. * - * @param string $className The class name of the filter, eg. Limit + * @param string|Closure $className The class name of the filter, eg. Limit * @param array $parameters The parameters to give to the filter, eg. array( $offset, $limit) for the filter Limit */ public function queueFilter($className, $parameters = array()) @@ -378,8 +447,8 @@ class DataTable implements DataTableInterface } /** - * Apply all filters that were previously queued to this table - * @see queueFilter() + * Applies all filters that were previously queued to the table. See [queueFilter](#queueFilter) + * for more information. */ public function applyQueuedFilters() { @@ -390,16 +459,18 @@ class DataTable implements DataTableInterface } /** - * Adds a new DataTable to this DataTable - * Go through all the rows of the new DataTable and applies the algorithm: - * - if a row in $table doesnt exist in $this we add the new row to $this - * - if a row exists in both $table and $this we sum the columns values into $this - * - if a row in $this doesnt exist in $table we add in $this the row of $table without modification - * - * A common row to 2 DataTable is defined by the same label - * - * @example tests/core/DataTable.test.php - * + * Sums a DataTable to this one. + * + * This method will sum rows that have the same label. If a row is found in `$tableToSum` whose + * label is not found in `$this`, the row will be added to `$this` DataTable. + * + * If the subtables for this table are loaded, they will be summed as well. + * + * Rows are summed together by summing individual columns. By default columns are summed by + * adding one column value to another. Some columns cannot be aggregated this way. In these + * cases, the [COLUMN_AGGREGATION_OPS_METADATA_NAME](#COLUMN_AGGREGATION_OPS_METADATA_NAME) + * metadata can be used to specify a different type of operation. + * * @param \Piwik\DataTable $tableToSum */ public function addDataTable(DataTable $tableToSum) @@ -422,7 +493,8 @@ class DataTable implements DataTableInterface // then we have to recursively sum the subtables if (($idSubTable = $row->getIdSubDataTable()) !== null) { $subTable = Manager::getInstance()->getTable($idSubTable); - $subTable->setColumnAggregationOperations($this->columnAggregationOperations); + $subTable->metadata[self::COLUMN_AGGREGATION_OPS_METADATA_NAME] + = $this->metadata[self::COLUMN_AGGREGATION_OPS_METADATA_NAME]; $rowFound->sumSubtable($subTable); } } @@ -430,10 +502,13 @@ class DataTable implements DataTableInterface } /** - * Returns the Row that has a column 'label' with the value $label - * - * @param string $label Value of the column 'label' of the row to return - * @return \Piwik\DataTable\Row|bool The row if found, false otherwise + * Returns the Row whose `'label'` column is equal to `$label`. + * + * This method executes in constant time except for the first call which caches row + * label => row ID mappings. + * + * @param string $label `'label'` column value to look for. + * @return Row|false The row if found, false if otherwise. */ public function getRowFromLabel($label) { @@ -453,10 +528,13 @@ class DataTable implements DataTableInterface } /** - * Returns the row id for the givel label + * Returns the row id for the row whose `'label'` column is equal to `$label`. * - * @param string $label Value of the column 'label' of the row to return - * @return int|Row + * This method executes in constant time except for the first call which caches row + * label => row ID mappings. + * + * @param string $label `'label'` column value to look for. + * @return int The row ID. */ public function getRowIdFromLabel($label) { @@ -468,7 +546,7 @@ class DataTable implements DataTableInterface if ($label === self::LABEL_SUMMARY_ROW && !is_null($this->summaryRow) ) { - return $this->summaryRow; + return self::ID_SUMMARY_ROW; } $label = (string)$label; @@ -479,11 +557,10 @@ class DataTable implements DataTableInterface } /** - * Get an empty table with the same properties as this one - * - * @param bool $keepFilters + * Returns an empty DataTable with the same metadata and queued filters as `$this` one. * - * @return \Piwik\DataTable + * @param bool $keepFilters Whether to pass the queued filter list to the new DataTable or not. + * @return DataTable */ public function getEmptyClone($keepFilters = true) { @@ -510,10 +587,10 @@ class DataTable implements DataTableInterface } /** - * Returns the ith row in the array + * Returns a row by ID. The ID is either the index of the row or [ID_SUMMARY_ROW](#ID_SUMMARY_ROW). * - * @param int $id - * @return \Piwik\DataTable\Row or false if not found + * @param int $id The row ID. + * @return Row|false The Row or false if not found. */ public function getRowFromId($id) { @@ -529,10 +606,10 @@ class DataTable implements DataTableInterface } /** - * Returns a row that has the subtable ID matching the parameter - * - * @param int $idSubTable - * @return \Piwik\DataTable\Row|bool false if not found + * Returns the row that has a subtable with ID matching `$idSubtable`. + * + * @param int $idSubTable The subtable ID. + * @return Row|false The row or false if not found */ public function getRowFromIdSubDataTable($idSubTable) { @@ -546,10 +623,14 @@ class DataTable implements DataTableInterface } /** - * Add a row to the table and rebuild the index if necessary + * Adds a row to this table. + * + * If [setMaximumAllowedRows](#setMaximumAllowedRows) was called and the current row count is + * at the maximum, the new row will be summed to the summary row. If there is no summary row, + * this row is set as the summary row. * - * @param \Piwik\DataTable\Row $row to add at the end of the array - * @return \Piwik\DataTable\Row + * @param Row $row + * @return Row `$row` or the summary row if we're at the maximum number of rows. */ public function addRow(Row $row) { @@ -581,10 +662,12 @@ class DataTable implements DataTableInterface } /** - * Sets the summary row (a dataTable can have only one summary row) + * Sets the summary row. + * + * Note: A dataTable can have only one summary row. * * @param Row $row - * @return Row Returns $row. + * @return Row Returns `$row`. */ public function addSummaryRow(Row $row) { @@ -604,7 +687,7 @@ class DataTable implements DataTableInterface } /** - * Returns the dataTable ID + * Returns the DataTable ID. * * @return int */ @@ -614,9 +697,12 @@ class DataTable implements DataTableInterface } /** - * Adds a new row from a PHP array data structure + * Adds a new row from an array. + * + * You can add Row metadata with this method. * - * @param array $row eg. array(Row::COLUMNS => array( 'visits' => 13, 'test' => 'toto'),) + * @param array $row eg. array(Row::COLUMNS => array('visits' => 13, 'test' => 'toto'), + * Row::METADATA => array('mymetadata' => 'myvalue')) */ public function addRowFromArray($row) { @@ -624,7 +710,9 @@ class DataTable implements DataTableInterface } /** - * Adds a new row a PHP array data structure + * Adds a new row a from an array of column values. + * + * Row metadata cannot be added with this method. * * @param array $row eg. array('name' => 'google analytics', 'license' => 'commercial') */ @@ -634,9 +722,9 @@ class DataTable implements DataTableInterface } /** - * Returns the array of Row + * Returns the array of Rows. * - * @return Row[] + * @return array */ public function getRows() { @@ -648,10 +736,10 @@ class DataTable implements DataTableInterface } /** - * Returns the array containing all rows values for the requested column + * Returns an array containing all column values for the requested column. * - * @param string $name - * @return array + * @param string $name The column name. + * @return array The array of column values. */ public function getColumn($name) { @@ -663,12 +751,12 @@ class DataTable implements DataTableInterface } /** - * Returns the array containing all rows values of all columns which name starts with $name + * Returns an array containing all column values of columns whose name starts with `$name`. * - * @param $name - * @return array + * @param $namePrefix The column name prefix. + * @return array The array of column values. */ - public function getColumnsStartingWith($name) + public function getColumnsStartingWith($namePrefix) { $columnValues = array(); foreach ($this->getRows() as $row) { @@ -685,8 +773,11 @@ class DataTable implements DataTableInterface /** * Returns the list of columns the rows in this datatable contain. This will return the * columns of the first row with data and assume they occur in every other row as well. - * - * @return array + * + * Note: If column names still use their in-database INDEX values (@see Metrics), they + * will be converted to their string name in the array result. + * + * @return array Array of string column names. */ public function getColumns() { @@ -710,9 +801,9 @@ class DataTable implements DataTableInterface } /** - * Returns an array containing the rows Metadata values - * - * @param string $name Metadata column to return + * Returns an array containing the requested metadata value of each row. + * + * @param string $name The metadata column to return. * @return array */ public function getRowsMetadata($name) @@ -725,8 +816,8 @@ class DataTable implements DataTableInterface } /** - * Returns the number of rows in the table - * + * Returns the number of rows in the table including the summary row. + * * @return int */ public function getRowsCount() @@ -739,9 +830,9 @@ class DataTable implements DataTableInterface } /** - * Returns the first row of the DataTable + * Returns the first row of the DataTable. * - * @return \Piwik\DataTable\Row + * @return Row|false The first row or `false` if it cannot be found. */ public function getFirstRow() { @@ -751,14 +842,14 @@ class DataTable implements DataTableInterface } return false; } - $row = array_slice($this->rows, 0, 1); - return $row[0]; + return reset($this->rows); } /** - * Returns the last row of the DataTable + * Returns the last row of the DataTable. If there is a summary row, it + * will always be considered the last row. * - * @return Row + * @return Row|false The last row or `false` if it cannot be found. */ public function getLastRow() { @@ -769,13 +860,13 @@ class DataTable implements DataTableInterface if (count($this->rows) == 0) { return false; } - $row = array_slice($this->rows, -1); - return $row[0]; + + return end($this->rows); } /** - * Returns the sum of the number of rows of all the subtables - * + the number of rows in the parent table + * Returns the number of rows in this DataTable summed with the row count of each subtable + * in the DataTable hierarchy. This includes the subtables of subtables and further descendants. * * @return int */ @@ -795,9 +886,10 @@ class DataTable implements DataTableInterface } /** - * Delete a given column $name in all the rows + * Delete a column by name in every row. This change is NOT applied recursively to all + * subtables. * - * @param string $name + * @param string $name Column name to delete. */ public function deleteColumn($name) { @@ -810,10 +902,10 @@ class DataTable implements DataTableInterface } /** - * Rename a column in all rows + * Rename a column in every row. This change is applied recursively to all subtables. * - * @param string $oldName Old column name - * @param string $newName New column name + * @param string $oldName Old column name. + * @param string $newName New column name. */ public function renameColumn($oldName, $newName) { @@ -829,10 +921,10 @@ class DataTable implements DataTableInterface } /** - * Delete columns by name in all rows + * Deletes several columns by name in every row. * - * @param array $names - * @param bool $deleteRecursiveInSubtables + * @param array $names List of column names to delete. + * @param bool $deleteRecursiveInSubtables Whether to apply this change to all subtables or not. */ public function deleteColumns($names, $deleteRecursiveInSubtables = false) { @@ -852,12 +944,10 @@ class DataTable implements DataTableInterface } /** - * Deletes the ith row - * - * @param int $id + * Deletes a row by ID. * - * @throws Exception if the row $id cannot be found - * @return void + * @param int $id The row ID. + * @throws Exception If the row `$id` cannot be found. */ public function deleteRow($id) { @@ -872,12 +962,12 @@ class DataTable implements DataTableInterface } /** - * Deletes all row from offset, offset + limit. - * If limit is null then limit = $table->getRowsCount() + * Deletes rows from `$offset` to `$offset + $limit`. * - * @param int $offset - * @param int $limit - * @return int + * @param int $offset The offset to start deleting rows from. + * @param int|null $limit The number of rows to delete. If `null` all rows after the offset + * will be removed. + * @return int The number of rows deleted. */ public function deleteRowsOffset($offset, $limit = null) { @@ -907,21 +997,22 @@ class DataTable implements DataTableInterface } /** - * Deletes the rows from the list of rows ID + * Deletes a set of rows by ID. * - * @param array $aKeys ID of the rows to delete - * @throws Exception if any of the row to delete couldn't be found + * @param array $rowIds The list of row IDs to delete. + * @throws Exception If a row ID cannot be found. */ - public function deleteRows(array $aKeys) + public function deleteRows(array $rowIds) { - foreach ($aKeys as $key) { + foreach ($rowIds as $key) { $this->deleteRow($key); } } /** - * Returns a simple output of the DataTable for easy visualization - * Example: echo $datatable; + * Returns a string representation of this DataTable for convenient viewing. + * + * Note: This uses the Html DataTable renderer. * * @return string */ @@ -933,9 +1024,13 @@ class DataTable implements DataTableInterface } /** - * Returns true if both DataTable are exactly the same. - * Used in unit tests. + * Returns true if both DataTable instances are exactly the same. * + * DataTables are equal if they have the same number of rows, if + * each row has a label that exists in the other table, and if each row + * is equal to the row in the other table with the same label. The order + * of rows is not important. + * * @param \Piwik\DataTable $table1 * @param \Piwik\DataTable $table2 * @return bool @@ -965,37 +1060,34 @@ class DataTable implements DataTableInterface } /** - * The serialization returns a one dimension array containing all the - * serialized DataTable contained in this DataTable. - * We save DataTable in serialized format in the Database. - * Each row of this returned PHP array will be a row in the DB table. - * At the end of the method execution, the dataTable may be truncated (if $maximum* parameters are set). + * Serializes an entire DataTable hierarchy and returns the array of serialized DataTables. + * + * The first element in the returned array will be the serialized representation of this DataTable. + * Every subsequent element will be a serialized subtable. + * + * This DataTable and subtables can optionally be truncated before being serialized. In most + * cases where DataTables can become quite large, they should be truncated before being persisted + * in an archive. * - * The keys of the array are very important as they are used to define the DataTable + * The result of this method is intended for use with the [ArchiveProcessor::insertBlobRecord](#) method. * - * IMPORTANT: The main table (level 0, parent of all tables) will always be indexed by 0 - * even it was created after some other tables. - * It also means that all the parent tables (level 0) will be indexed with 0 in their respective - * serialized arrays. You should never lookup a parent table using the getTable( $id = 0) as it - * won't work. + * @throws Exception If infinite recursion detected. This will occur if a table's subtable is one of its parent tables. + * @param int $maximumRowsInDataTable If not null, defines the maximum number of rows allowed in the serialized DataTable. + * @param int $maximumRowsInSubDataTable If not null, defines the maximum number of rows allowed in serialized subtables. + * @param string $columnToSortByBeforeTruncation The column to sort by before truncating, eg, `Metrics::INDEX_NB_VISITS`. + * @return array The array of serialized DataTables: + * array( + * // this DataTable (the root) + * 0 => 'eghuighahgaueytae78yaet7yaetae', * - * @throws Exception if an infinite recursion is found (a table row's has a subtable that is one of its parent table) - * @param int $maximumRowsInDataTable If not null, defines the number of rows maximum of the serialized dataTable - * @param int $maximumRowsInSubDataTable If not null, defines the number of rows maximum of the serialized subDataTable - * @param string $columnToSortByBeforeTruncation Column to sort by before truncation - * @return array Serialized arrays - * array( // Datatable level0 - * 0 => 'eghuighahgaueytae78yaet7yaetae', + * // a subtable + * 1 => 'gaegae gh gwrh guiwh uigwhuige', * - * // first Datatable level1 - * 1 => 'gaegae gh gwrh guiwh uigwhuige', - * - * //second Datatable level1 - * 2 => 'gqegJHUIGHEQjkgneqjgnqeugUGEQHGUHQE', - * - * //first Datatable level3 (child of second Datatable level1 for example) - * 3 => 'eghuighahgaueytae78yaet7yaetaeGRQWUBGUIQGH&QE', - * ); + * // another subtable + * 2 => 'gqegJHUIGHEQjkgneqjgnqeugUGEQHGUHQE', + * + * // etc. + * ); */ public function getSerialized($maximumRowsInDataTable = null, $maximumRowsInSubDataTable = null, @@ -1047,15 +1139,14 @@ class DataTable implements DataTableInterface } /** - * Load a serialized string of a datatable. - * - * Does not load recursively all the sub DataTable. - * They will be loaded only when requesting them specifically. + * Adds a set of rows from a serialized DataTable string. * - * The function creates all the necessary DataTable\Row - * - * @param string $stringSerialized string of serialized datatable - * @throws Exception + * See [serialize](#serialize). + * + * @param string $stringSerialized A serialized DataTable string in the format of a string in the + * array returned by [serialize](#serialize). This function will + * successfully load DataTables serialized by Piwik 1.X. + * @throws Exception if `$stringSerialized` is invalid. */ public function addRowsFromSerializedArray($stringSerialized) { @@ -1069,18 +1160,20 @@ class DataTable implements DataTableInterface } /** - * Loads the DataTable from a PHP array data structure + * Adds many rows from an array. + * + * You can add Row metadata with this method. * * @param array $array Array with the following structure - * array( + * array( * // row1 * array( - * Row::COLUMNS => array( col1_name => value1, col2_name => value2, ...), - * Row::METADATA => array( metadata1_name => value1, ...), // see Row + * Row::COLUMNS => array( col1_name => value1, col2_name => value2, ...), + * Row::METADATA => array( metadata1_name => value1, ...), // see Row * ), * // row2 * array( ... ), - * ) + * ) */ public function addRowsFromArray($array) { @@ -1097,9 +1190,9 @@ class DataTable implements DataTableInterface } /** - * Loads the data from a simple php array. - * Basically maps a simple multidimensional php array to a DataTable. - * Not recursive (if a row contains a php array itself, it won't be loaded) + * Adds many rows from an array containing arrays of column values. + * + * Row metadata cannot be added with this method. * * @param array $array Array with the simple structure: * array( @@ -1107,7 +1200,6 @@ class DataTable implements DataTableInterface * array( col1_name => valueB, col2_name => valueD, ...), * ) * @throws Exception - * @return void */ public function addRowsFromSimpleArray($array) { @@ -1200,7 +1292,6 @@ class DataTable implements DataTableInterface * array( Row::COLUMNS => array('label' => LABEL2, 'value' => Y)), * ) * - * * @param array $array Indexed array, two formats are supported * @param array|null $subtablePerLabel An indexed array of up to one DataTable to associate as a sub table * @return \Piwik\DataTable @@ -1226,9 +1317,13 @@ class DataTable implements DataTableInterface } /** - * Sets the maximum nesting level to at least a certain value. If the current value is + * Sets the maximum depth level to at least a certain value. If the current value is * greater than the supplied level, the maximum nesting level is not changed. - * + * + * The maximum depth level determines the maximum number of subtable levels in the + * DataTable tree. For example, if it is set to `2`, this DataTable is allowed to + * have subtables, but the subtables are not. + * * @param int $atLeastLevel */ public static function setMaximumDepthLevelAllowedAtLeast($atLeastLevel) @@ -1239,21 +1334,11 @@ class DataTable implements DataTableInterface } } - /** - * Returns all table metadata. - * - * @return array - */ - public function getAllTableMetadata() - { - return $this->metadata; - } - /** * Returns metadata by name. * * @param string $name The metadata name. - * @return mixed + * @return mixed|false The metadata value or false if it cannot be found. */ public function getMetadata($name) { @@ -1277,9 +1362,9 @@ class DataTable implements DataTableInterface /** * Sets the maximum number of rows allowed in this datatable (including the summary * row). If adding more then the allowed number of rows is attempted, the extra - * rows are added to the summary row. + * rows are summed to the summary row. * - * @param int|null $maximumAllowedRows + * @param int $maximumAllowedRows If `0`, the maximum number of rows is unset. */ public function setMaximumAllowedRows($maximumAllowedRows) { @@ -1290,18 +1375,22 @@ class DataTable implements DataTableInterface * Traverses a DataTable tree using an array of labels and returns the row * it finds or false if it cannot find one, and the number of segments of * the path successfully walked. + * * If $missingRowColumns is supplied, the specified path is created. When * a subtable is encountered w/o the queried label, a new row is created * with the label, and a subtable is added to the row. * - * @param array $path The path to walk. An array of label values. - * @param array|bool $missingRowColumns - * The default columns to use when creating new arrays. + * Read [http://en.wikipedia.org/wiki/Tree_(data_structure)#Traversal_methods](http://en.wikipedia.org/wiki/Tree_(data_structure)#Traversal_methods) + * for more information about tree walking. + * + * @param array $path The path to walk. An array of label values. The first element + * refers to a row in this DataTable, the second in a subtable of + * the first row, the third a subtable of the second row, etc. + * @param array|bool $missingRowColumns The default columns to use when creating new rows. * If this parameter is supplied, new rows will be - * created if labels cannot be found. - * @param int $maxSubtableRows The maximum number of allowed rows in new - * subtables. - * + * created for path labels that cannot be found. + * @param int $maxSubtableRows The maximum number of allowed rows in new subtables. New + * subtables are only created if `$missingRowColumns` is provided. * @return array First element is the found row or false. Second element is * the number of path segments walked. If a row is found, this * will be == to count($path). Otherwise, it will be the index @@ -1347,7 +1436,8 @@ class DataTable implements DataTableInterface { $table = new DataTable(); $table->setMaximumAllowedRows($maxSubtableRows); - $table->setColumnAggregationOperations($this->columnAggregationOperations); + $table->metadata[self::COLUMN_AGGREGATION_OPS_METADATA_NAME] + = $this->metadata[self::COLUMN_AGGREGATION_OPS_METADATA_NAME]; $next->setSubtable($table); // Summary row, has no metadata $next->deleteMetadata(); @@ -1359,15 +1449,17 @@ class DataTable implements DataTableInterface } /** - * Returns a new DataTable that contains the rows of each of this table's subtables. + * Returns a new DataTable in which the rows of this table are replaced with its subtable's + * rows. * - * @param string|bool $labelColumn If supplied the label of the parent row will be - * added to a new column in each subtable row. If set to, - * 'label' each subtable row's label will be prepended w/ - * the parent row's label. + * @param string|bool $labelColumn If supplied the label of the parent row will be added to + * a new column in each subtable row. + * + * If set to, 'label' each subtable row's label will be prepended + * w/ the parent row's label. So `'child_label'` becomes + * `'parent_label - child_label'`. * @param bool $useMetadataColumn If true and if $labelColumn is supplied, the parent row's - * label will be added as metadata. - * + * label will be added as metadata and not a new column. * @return \Piwik\DataTable */ public function mergeSubtables($labelColumn = false, $useMetadataColumn = false) @@ -1418,7 +1510,9 @@ class DataTable implements DataTableInterface /** * Returns a new DataTable created with data from a 'simple' array. - * + * + * See [addRowsFromSimpleArray](#addRowsFromSimpleArray). + * * @param array $array * @return \Piwik\DataTable */ @@ -1430,38 +1524,10 @@ class DataTable implements DataTableInterface } /** - * Set the aggregation operation for a column, e.g. "min". - * @see self::addDataTable() and DataTable\Row::sumRow() - * - * @param string $columnName - * @param string $operation - */ - public function setColumnAggregationOperation($columnName, $operation) - { - $this->columnAggregationOperations[$columnName] = $operation; - } - - /** - * Set multiple aggregation operations at once. - * @param array $operations format: column name => operation - */ - public function setColumnAggregationOperations($operations) - { - foreach ($operations as $columnName => $operation) { - $this->setColumnAggregationOperation($columnName, $operation); - } - } - - /** - * Get the configured column aggregation operations - */ - public function getColumnAggregationOperations() - { - return $this->columnAggregationOperations; - } - - /** - * Creates a new DataTable instance from a serialize()'d array of rows. + * Creates a new DataTable instance from a serialized DataTable string. + * + * See [getSerialized](#getSerialized) and [addRowsFromSerializedArray](#addRowsFromSerializedArray) + * for more information on DataTable serialization. * * @param string $data * @return \Piwik\DataTable diff --git a/core/DataTable/Filter/AddSummaryRow.php b/core/DataTable/Filter/AddSummaryRow.php index 1aa03257999950fe398ee1ad4dd59ddc4aa01c94..0a838d5b13177b00041231cad768c3dbaa294fe5 100644 --- a/core/DataTable/Filter/AddSummaryRow.php +++ b/core/DataTable/Filter/AddSummaryRow.php @@ -78,10 +78,10 @@ class AddSummaryRow extends Filter //FIXME: I'm not sure why it could return false, but it was reported in: http://forum.piwik.org/read.php?2,89324,page=1#msg-89442 if ($summaryRow) { - $newRow->sumRow($summaryRow, $enableCopyMetadata = false, $table->getColumnAggregationOperations()); + $newRow->sumRow($summaryRow, $enableCopyMetadata = false, $table->metadata[DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME]); } } else { - $newRow->sumRow($rows[$i], $enableCopyMetadata = false, $table->getColumnAggregationOperations()); + $newRow->sumRow($rows[$i], $enableCopyMetadata = false, $table->metadata[DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME]); } } diff --git a/core/DataTable/Filter/GroupBy.php b/core/DataTable/Filter/GroupBy.php index e33a2e7c9cc2158359f9c3e82120129253510b90..0ee3fa6e6c4885cf7b3417d087c7281bfaba25d0 100755 --- a/core/DataTable/Filter/GroupBy.php +++ b/core/DataTable/Filter/GroupBy.php @@ -87,7 +87,7 @@ class GroupBy extends Filter } 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, $copyMeta = true, $table->getColumnAggregationOperations()); + $groupByRows[$groupByValue]->sumRow($row, $copyMeta = true, $table->metadata[DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME]); $nonGroupByRowIds[] = $rowId; } } diff --git a/core/DataTable/Filter/Limit.php b/core/DataTable/Filter/Limit.php index db79c8a1eea5c676ef79b25ca80fe4c3c20b5eed..501d5fbcb30c55138fdf5d1b8d5b882e97fe8b34 100644 --- a/core/DataTable/Filter/Limit.php +++ b/core/DataTable/Filter/Limit.php @@ -45,7 +45,7 @@ class Limit extends Filter */ public function filter($table) { - $table->setRowsCountBeforeLimitFilter(); + $table->metadata[DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME] = $table->getRowsCount(); if ($this->keepSummaryRow) { $summaryRow = $table->getRowFromId(DataTable::ID_SUMMARY_ROW); diff --git a/core/DataTable/Row.php b/core/DataTable/Row.php index 3bead313f3ec12ecc10c5d3280798370b460e220..36f057586f92f1fd05c3a417d6d042a00fa18505 100644 --- a/core/DataTable/Row.php +++ b/core/DataTable/Row.php @@ -287,7 +287,8 @@ class Row $thisSubTable = new DataTable(); $this->addSubtable($thisSubTable); } - $thisSubTable->setColumnAggregationOperations($subTable->getColumnAggregationOperations()); + $thisSubTable->metadata[DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME] + = $subTable->metadata[DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME]; $thisSubTable->addDataTable($subTable); } diff --git a/core/DataTable/Row/DataTableSummaryRow.php b/core/DataTable/Row/DataTableSummaryRow.php index 7e1d939da28e62a63da34e3a9251cd202dd54d66..86032814563feba9cc5005e53f4aba6a189e69d5 100644 --- a/core/DataTable/Row/DataTableSummaryRow.php +++ b/core/DataTable/Row/DataTableSummaryRow.php @@ -59,7 +59,7 @@ class DataTableSummaryRow extends Row private function sumTable($table) { foreach ($table->getRows() as $row) { - $this->sumRow($row, $enableCopyMetadata = false, $table->getColumnAggregationOperations()); + $this->sumRow($row, $enableCopyMetadata = false, $table->metadata[DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME]); } } } diff --git a/core/Plugin/Visualization.php b/core/Plugin/Visualization.php index bcc8069726af5fafb172697d1107ccc0e46f224d..86138a71d59ea411c4ec69895c610bf109f7b7f6 100644 --- a/core/Plugin/Visualization.php +++ b/core/Plugin/Visualization.php @@ -175,7 +175,7 @@ class Visualization extends ViewDataTable // deal w/ table metadata if ($this->dataTable instanceof DataTable) { - $this->config->metadata = $this->dataTable->getAllTableMetadata(); + $this->config->metadata = $this->dataTable->metadata; if (isset($this->config->metadata[DataTable::ARCHIVED_DATE_METADATA_NAME])) { $this->config->report_last_updated_message = $this->makePrettyArchivedOnText(); @@ -372,7 +372,8 @@ class Visualization extends ViewDataTable !($this->dataTable instanceof DataTable\Map) && empty($javascriptVariablesToSet['totalRows']) ) { - $javascriptVariablesToSet['totalRows'] = $this->dataTable->getRowsCountBeforeLimitFilter(); + $javascriptVariablesToSet['totalRows'] = + $this->dataTable->getMetadata(DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME) ?: $this->dataTable->getRowsCount(); } $deleteFromJavascriptVariables = array( diff --git a/plugins/Actions/Archiver.php b/plugins/Actions/Archiver.php index 99cabed9b3effc812d328e8aa758f8e000c7eb06..e9e298ceb919a6021bc035f07df5645f88e8aa95 100644 --- a/plugins/Actions/Archiver.php +++ b/plugins/Actions/Archiver.php @@ -166,7 +166,7 @@ class Archiver extends \Piwik\Plugin\Archiver || $type == Action::TYPE_ACTION_NAME ) { // for page urls and page titles, performance metrics exist and have to be aggregated correctly - $dataTable->setColumnAggregationOperations(self::$actionColumnAggregationOperations); + $dataTable->metadata[DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME] = self::$actionColumnAggregationOperations; } $this->actionsTablesByType[$type] = $dataTable; diff --git a/plugins/UserSettings/API.php b/plugins/UserSettings/API.php index 90dd4752c788263113dfd79c04840d256d7f4f26..d01b50793f2c89b597d0817f8cada22a2c38de7a 100644 --- a/plugins/UserSettings/API.php +++ b/plugins/UserSettings/API.php @@ -219,7 +219,7 @@ class API extends \Piwik\Plugin\API // When Truncate filter is applied, it will call AddSummaryRow which tries to sum all rows. // We tell the object to skip the column nb_visits_percentage when aggregating (since it's not correct to sum % values) - $table->setColumnAggregationOperation('nb_visits_percentage', 'skip'); + $table->metadata[DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME]['nb_visits_percentage'] = 'skip'; // The filter must be applied now so that the new column can // be sorted by the generic filters (applied right after this loop exits) diff --git a/tests/PHPUnit/BaseFixture.php b/tests/PHPUnit/BaseFixture.php index 3ab347fd31d77c3e329f22ee4c3cc15ad8baf118..1d9c8b731f9138e1c91dd33741057f80991384c0 100644 --- a/tests/PHPUnit/BaseFixture.php +++ b/tests/PHPUnit/BaseFixture.php @@ -383,6 +383,7 @@ abstract class Test_Piwik_BaseFixture extends PHPUnit_Framework_Assert // run the command exec($cmd, $output, $result); + echo "OUTPUT: ".implode("\n", $output); if ($result !== 0) { throw new Exception("log importer failed: " . implode("\n", $output) . "\n\ncommand used: $cmd"); } diff --git a/tests/PHPUnit/Core/DataTable/Filter/LimitTest.php b/tests/PHPUnit/Core/DataTable/Filter/LimitTest.php index 3b786b0f011ff7bc788a770d1d6981b2f55fa3ec..073c66061aa4da121fb6e051751bbc45fea75140 100644 --- a/tests/PHPUnit/Core/DataTable/Filter/LimitTest.php +++ b/tests/PHPUnit/Core/DataTable/Filter/LimitTest.php @@ -49,7 +49,7 @@ class DataTable_Filter_LimitTest extends PHPUnit_Framework_TestCase $this->assertEquals(3, $table->getRowsCount()); $this->assertEquals(2, $table->getFirstRow()->getColumn('idRow')); $this->assertEquals(4, $table->getLastRow()->getColumn('idRow')); - $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter()); + $this->assertEquals(10, $table->metadata[DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME]); } /** @@ -65,7 +65,7 @@ class DataTable_Filter_LimitTest extends PHPUnit_Framework_TestCase $this->assertEquals(7, $table->getRowsCount()); $this->assertEquals(2, $table->getFirstRow()->getColumn('idRow')); $this->assertEquals(8, $table->getLastRow()->getColumn('idRow')); - $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter()); + $this->assertEquals(10, $table->metadata[DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME]); } /** @@ -76,13 +76,13 @@ class DataTable_Filter_LimitTest extends PHPUnit_Framework_TestCase $offset = 0; $limit = 10; $table = $this->getDataTableCount10(); - $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter()); + $this->assertEquals(10, $table->metadata[DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME]); $filter = new Limit($table, $offset, $limit); $filter->filter($table); $this->assertEquals(10, $table->getRowsCount()); $this->assertEquals(0, $table->getFirstRow()->getColumn('idRow')); $this->assertEquals(9, $table->getLastRow()->getColumn('idRow')); - $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter()); + $this->assertEquals(10, $table->metadata[DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME]); } /** @@ -93,13 +93,13 @@ class DataTable_Filter_LimitTest extends PHPUnit_Framework_TestCase $offset = 5; $limit = 20; $table = $this->getDataTableCount10(); - $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter()); + $this->assertEquals(10, $table->metadata[DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME]); $filter = new Limit($table, $offset, $limit); $filter->filter($table); $this->assertEquals(5, $table->getRowsCount()); $this->assertEquals(5, $table->getFirstRow()->getColumn('idRow')); $this->assertEquals(9, $table->getLastRow()->getColumn('idRow')); - $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter()); + $this->assertEquals(10, $table->metadata[DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME]); } /** @@ -114,7 +114,7 @@ class DataTable_Filter_LimitTest extends PHPUnit_Framework_TestCase $this->assertEquals(9, $table->getRowsCount()); $this->assertEquals(1, $table->getFirstRow()->getColumn('idRow')); $this->assertEquals(9, $table->getLastRow()->getColumn('idRow')); - $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter()); + $this->assertEquals(10, $table->metadata[DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME]); } /** @@ -130,7 +130,7 @@ class DataTable_Filter_LimitTest extends PHPUnit_Framework_TestCase $this->assertEquals(1, $table->getRowsCount()); $this->assertEquals(9, $table->getFirstRow()->getColumn('idRow')); $this->assertEquals(9, $table->getLastRow()->getColumn('idRow')); - $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter()); + $this->assertEquals(10, $table->metadata[DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME]); } /** @@ -146,7 +146,7 @@ class DataTable_Filter_LimitTest extends PHPUnit_Framework_TestCase $this->assertEquals(1, $table->getRowsCount()); $this->assertEquals(9, $table->getFirstRow()->getColumn('idRow')); $this->assertEquals(9, $table->getLastRow()->getColumn('idRow')); - $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter()); + $this->assertEquals(10, $table->metadata[DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME]); } /** @@ -162,7 +162,7 @@ class DataTable_Filter_LimitTest extends PHPUnit_Framework_TestCase $this->assertEquals(2, $table->getRowsCount()); $this->assertEquals(8, $table->getFirstRow()->getColumn('idRow')); $this->assertEquals(9, $table->getLastRow()->getColumn('idRow')); - $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter()); + $this->assertEquals(10, $table->metadata[DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME]); } /** @@ -176,7 +176,7 @@ class DataTable_Filter_LimitTest extends PHPUnit_Framework_TestCase $filter = new Limit($table, $offset, $limit); $filter->filter($table); $this->assertEquals(0, $table->getRowsCount()); - $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter()); + $this->assertEquals(10, $table->metadata[DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME]); } /** @@ -190,7 +190,7 @@ class DataTable_Filter_LimitTest extends PHPUnit_Framework_TestCase $filter = new Limit($table, $offset, $limit); $filter->filter($table); $this->assertEquals(0, $table->getRowsCount()); - $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter()); + $this->assertEquals(10, $table->metadata[DataTable::TOTAL_ROWS_BEFORE_LIMIT_METADATA_NAME]); } /**