Skip to content
Extraits de code Groupes Projets
DataTable.php 64 ko
Newer Older
 * Piwik - free/libre analytics platform
robocoder's avatar
robocoder a validé
 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
use Piwik\DataTable\DataTableInterface;
use Piwik\DataTable\Manager;
use Piwik\DataTable\Renderer\Html;
use Piwik\DataTable\Row;
use Piwik\DataTable\Row\DataTableSummaryRow;
mattab's avatar
mattab a validé

mattab's avatar
mattab a validé
 * @see Common::destroy()
 */
require_once PIWIK_INCLUDE_PATH . '/core/Common.php';
require_once PIWIK_INCLUDE_PATH . "/core/DataTable/Bridges.php";
 * The primary data structure used to store analytics data in Piwik.
diosmosis's avatar
diosmosis a validé
 * <a name="class-desc-the-basics"></a>
 * DataTables consist of rows and each row consists of columns. A column value can be
 * a numeric, a string or an array.
 * Every row has an ID. The ID is either the index of the row or {@link ID_SUMMARY_ROW}.
 * DataTables are hierarchical data structures. Each row can also contain an additional
 * nested sub-DataTable (commonly referred to as a 'subtable').
 * Both DataTables and DataTable rows can hold **metadata**. _DataTable metadata_ is information
 * regarding all the data, such as the site or period that the data is for. _Row metadata_
 * is information regarding that row, such as a browser logo or website URL.
 * Finally, all DataTables contain a special _summary_ row. This row, if it exists, is
 * always at the end of the DataTable.
 * ### Populating DataTables
 * Data can be added to DataTables in three different ways. You can either:
 * 1. create rows one by one and add them through {@link addRow()} then truncate if desired,
 * 2. create an array of DataTable\Row instances or an array of arrays and add them using
 *    {@link addRowsFromArray()} or {@link addRowsFromSimpleArray()}
 *    then truncate if desired,
 * 3. or set the maximum number of allowed rows (with {@link setMaximumAllowedRows()})
 *    and add rows one by one.
 * If you want to eventually truncate your data (standard practice for all Piwik plugins),
 * the third method is the most memory efficient. It is, unfortunately, not always possible
 * to use since it requires that the data be sorted before adding.
 * ### Manipulating DataTables
 * There are two ways to manipulate a DataTable. You can either:
 * 1. manually iterate through each row and manipulate the data,
 * 2. or you can use predefined filters.
 * A filter is a class that has a 'filter' method which will manipulate a DataTable in
 * some way. There are several predefined Filters that allow you to do common things,
 * such as,
 * - add a new column to each row,
 * - add new metadata to each row,
 * - modify an existing column value for each row,
 * - sort an entire DataTable,
 * - and more.
 * Using these filters instead of writing your own code will increase code clarity and
 * reduce code redundancy. Additionally, filters have the advantage that they can be
 * applied to DataTable\Map instances. So you can visit every DataTable in a {@link DataTable\Map}
 * without having to write a recursive visiting function.
 * All predefined filters exist in the **Piwik\DataTable\BaseFilter** namespace.
 * _Note: For convenience, [anonymous functions](http://www.php.net/manual/en/functions.anonymous.php)
 * can be used as DataTable filters._
 * ### Applying Filters
 * Filters can be applied now (via {@link filter()}), or they can be applied later (via
 * {@link queueFilter()}).
 * Filters that sort rows or manipulate the number of rows should be applied right away.
 * Non-essential, presentation filters should be queued.
 * - See **{@link ArchiveProcessor}** to learn how DataTables are persisted.
 * **Populating a DataTable**
diosmosis's avatar
diosmosis a validé
 *     // 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')
 *     )));
diosmosis's avatar
diosmosis a validé
 *     // 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')
 *         )
 *     ));
diosmosis's avatar
diosmosis a validé
 *     // 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)
 *     ));
diosmosis's avatar
diosmosis a validé
 * **Getting & setting metadata**
diosmosis's avatar
diosmosis a validé
 *     $dataTable = \Piwik\Plugins\Referrers\API::getInstance()->getSearchEngines($idSite = 1, $period = 'day', $date = '2007-07-24');
 *     $oldPeriod = $dataTable->metadata['period'];
 *     $dataTable->metadata['period'] = Period\Factory::build('week', Date::factory('2013-10-18'));
 * **Serializing & unserializing**
diosmosis's avatar
diosmosis a validé
 *     $maxRowsInTable = Config::getInstance()->General['datatable_archiving_maximum_rows_standard'];j
diosmosis's avatar
diosmosis a validé
 *     $dataTable = // ... build by aggregating visits ...
 *     $serializedData = $dataTable->getSerialized($maxRowsInTable, $maxRowsInSubtable = $maxRowsInTable,
 *                                                 $columnToSortBy = Metrics::INDEX_NB_VISITS);
 *     $serializedDataTable = $serializedData[0];
diosmosis's avatar
diosmosis a validé
 *     $serailizedSubTable = $serializedData[$idSubtable];
 * **Filtering for an API method**
diosmosis's avatar
diosmosis a validé
 *     public function getMyReport($idSite, $period, $date, $segment = false, $expanded = false)
 *     {
 *         $dataTable = Archive::createDataTableFromArchive('MyPlugin_MyReport', $idSite, $period, $date, $segment, $expanded);
diosmosis's avatar
diosmosis a validé
 *         $dataTable->filter('Sort', array(Metrics::INDEX_NB_VISITS, 'desc', $naturalSort = false, $expanded));
 *         $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', __NAMESPACE__ . '\getUrlFromLabelForMyReport'));
 *         return $dataTable;
 *     }
class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess
diosmosis's avatar
diosmosis a validé
    const MAX_DEPTH_DEFAULT = 15;

    /** Name for metadata that describes when a report was archived. */
mattab's avatar
mattab a validé
    const ARCHIVED_DATE_METADATA_NAME = 'ts_archived';
diosmosis's avatar
diosmosis a validé

    /** Name for metadata that describes which columns are empty and should not be shown. */
    const EMPTY_COLUMNS_METADATA_NAME = 'empty_column';

diosmosis's avatar
diosmosis a validé
    /** 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 {@link addDataTable()}
     * or {@link Piwik\DataTable\Row::sumRow()} is called.
diosmosis's avatar
diosmosis a validé
     * This metadata value must be an array that maps column names with valid operations. Valid aggregation operations are:
diosmosis's avatar
diosmosis a validé
     * - `'skip'`: do nothing
     * - `'max'`: does `max($column1, $column2)`
     * - `'min'`: does `min($column1, $column2)`
     * - `'sum'`: does `$column1 + $column2`
     * See {@link addDataTable()} and {@link DataTable\Row::sumRow()} for more information.
diosmosis's avatar
diosmosis a validé
     */
    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;

     * Name for metadata that contains extra {@link Piwik\Plugin\ProcessedMetric}s for a DataTable.
     * These metrics will be added in addition to the ones specified in the table's associated
     * {@link Piwik\Plugin\Report} class.
     */
    const EXTRA_PROCESSED_METRICS_METADATA_NAME = 'extra_processed_metrics';

    private static $maximumDepthLevelAllowed = self::MAX_DEPTH_DEFAULT;
     */
    protected $rows = array();

    /**
     * Id assigned to the DataTable, used to lookup the table using the DataTable_Manager
     *
     * @var int
     */
    protected $currentId;

    /**
     * Current depth level of this data table
     * 0 is the parent data table
     *
     * @var int
     */
    protected $depthLevel = 0;

    /**
     * This flag is set to false once we modify the table in a way that outdates the index
     *
     * @var bool
     */
    protected $indexNotUpToDate = true;

    /**
     * This flag sets the index to be rebuild whenever a new row is added,
     * as opposed to re-building the full index when getRowFromLabel is called.
     * This is to optimize and not rebuild the full Index in the case where we
     * add row, getRowFromLabel, addRow, getRowFromLabel thousands of times.
     *
     * @var bool
     */
    protected $rebuildIndexContinuously = false;

    /**
     * Column name of last time the table was sorted
     *
     * @var string
     */
    protected $tableSortedBy = false;

    /**
     *
     * @var array
     */
    protected $queuedFilters = array();

    /**
     * List of disabled filter names eg 'Limit' or 'Sort'
     *
     * @var array
     */
    protected $disabledFilters = array();

    /**
     * We keep track of the number of rows before applying the LIMIT filter that deletes some rows
     *
     * @var int
     */
    protected $rowsCountBeforeLimitFilter = 0;

    /**
     * Defaults to false for performance reasons (most of the time we don't need recursive sorting so we save a looping over the dataTable)
     *
     * @var bool
     */
    protected $enableRecursiveSort = false;

    /**
     * When the table and all subtables are loaded, this flag will be set to true to ensure filters are applied to all subtables
     *
     * @var bool
     */
    protected $enableRecursiveFilters = false;

    /**
     * @var array
     */
    protected $rowsIndexByLabel = array();

    /**
diosmosis's avatar
diosmosis a validé
     * Table metadata. Read [this](#class-desc-the-basics) to learn more.
diosmosis's avatar
diosmosis a validé
     * Any data that describes the data held in the table's rows should go here.

    /**
     * Maximum number of rows allowed in this datatable (including the summary row).
     * If adding more rows is attempted, the extra rows get summed to the summary row.
     *
     * @var int
     */
    protected $maximumAllowedRows = 0;

diosmosis's avatar
diosmosis a validé
     * Constructor. Creates an empty DataTable.
diosmosis's avatar
diosmosis a validé
        // registers this instance to the manager
mattab's avatar
mattab a validé
        $this->currentId = Manager::getInstance()->addTable($this);
diosmosis's avatar
diosmosis a validé
     * Destructor. Makes sure DataTable memory will be cleaned up.
     */
    public function __destruct()
    {
        static $depth = 0;
        // destruct can be called several times
        if ($depth < self::$maximumDepthLevelAllowed
            && isset($this->rows)
        ) {
            $depth++;
            foreach ($this->rows as $row) {
mattab's avatar
mattab a validé
                Common::destroy($row);
                Common::destroy($this->summaryRow);
            }
            Manager::getInstance()->setTableDeleted($this->currentId);
    /**
     * Clone. Called when cloning the datatable. We need to make sure to create a new datatableId.
     * If we do not increase tableId it can result in segmentation faults when destructing a datatable.
     */
    public function __clone()
    {
        // registers this instance to the manager
        $this->currentId = Manager::getInstance()->addTable($this);
    }

    public function setLabelsHaveChanged()
    {
        $this->indexNotUpToDate = true;
    }

    /**
     * @ignore
     * does not update the summary row!
     */
    public function setRows($rows)
    {
        unset($this->rows);
        $this->rows = $rows;
        $this->indexNotUpToDate = true;
    }

diosmosis's avatar
diosmosis a validé
     * Sorts the DataTable rows using the supplied callback function.
     * @param string $functionCallback A comparison callback compatible with {@link usort}.
diosmosis's avatar
diosmosis a validé
     * @param string $columnSortedBy The column name `$functionCallback` sorts by. This is stored
     *                               so we can determine how the DataTable was sorted in the future.
     */
    public function sort($functionCallback, $columnSortedBy)
    {
        $this->setTableSortedBy($columnSortedBy);

        usort($this->rows, $functionCallback);

        if ($this->isSortRecursiveEnabled()) {
            foreach ($this->getRowsWithoutSummaryRow() as $row) {
                $subTable = $row->getSubtable();
                if ($subTable) {
                    $subTable->enableRecursiveSort();
                    $subTable->sort($functionCallback, $columnSortedBy);
diosmosis's avatar
diosmosis a validé
     * Returns the name of the column this table was sorted by (if any).
     * See {@link sort()}.
diosmosis's avatar
diosmosis a validé
     *
     * @return false|string The sorted column name or false if none.
     */
    public function getSortedByColumnName()
    {
        return $this->tableSortedBy;
    }

    /**
     * Enables recursive sorting. If this method is called {@link sort()} will also sort all
diosmosis's avatar
diosmosis a validé
     * subtables.
     */
    public function enableRecursiveSort()
    {
        $this->enableRecursiveSort = true;
    }

    /**
     * @ignore
     */
    public function isSortRecursiveEnabled()
    {
        return $this->enableRecursiveSort === true;
    }

    /**
     * @ignore
     */
    public function setTableSortedBy($column)
    {
        $this->indexNotUpToDate = true;
        $this->tableSortedBy = $column;
    }

     * Enables recursive filtering. If this method is called then the {@link filter()} method
diosmosis's avatar
diosmosis a validé
     * will apply filters to every subtable in addition to this instance.
     */
    public function enableRecursiveFilters()
    {
        $this->enableRecursiveFilters = true;
    }

    /**
     * @ignore
     */
    public function disableRecursiveFilters()
    {
        $this->enableRecursiveFilters = false;
    }

     * Applies a filter to this datatable.
     * If {@link enableRecursiveFilters()} was called, the filter will be applied
diosmosis's avatar
diosmosis a validé
     * to all subtables as well.
     * @param string|Closure $className Class name, eg. `"Sort"` or "Piwik\DataTable\Filters\Sort"`. If no
     *                                  namespace is supplied, `Piwik\DataTable\BaseFilter` is assumed. This parameter
     *                                  can also be a closure that takes a DataTable as its first parameter.
     * @param array $parameters Array of extra parameters to pass to the filter.
     */
    public function filter($className, $parameters = array())
    {
        if ($className instanceof \Closure
            || is_array($className)
        ) {
            array_unshift($parameters, $this);
            call_user_func_array($className, $parameters);
        if (in_array($className, $this->disabledFilters)) {
            return;
        }

        if (!class_exists($className, true)) {
            $className = 'Piwik\DataTable\Filter\\' . $className;
        }
        $reflectionObj = new ReflectionClass($className);

        // the first parameter of a filter is the DataTable
        // we add the current datatable as the parameter
        $parameters = array_merge(array($this), $parameters);

        $filter = $reflectionObj->newInstanceArgs($parameters);

        $filter->enableRecursive($this->enableRecursiveFilters);

        $filter->filter($this);
    }

    /**
     * Applies a filter to all subtables but not to this datatable.
     *
     * @param string|Closure $className Class name, eg. `"Sort"` or "Piwik\DataTable\Filters\Sort"`. If no
     *                                  namespace is supplied, `Piwik\DataTable\BaseFilter` is assumed. This parameter
     *                                  can also be a closure that takes a DataTable as its first parameter.
     * @param array $parameters Array of extra parameters to pass to the filter.
     */
    public function filterSubtables($className, $parameters = array())
    {
        foreach ($this->getRowsWithoutSummaryRow() as $row) {
            $subtable = $row->getSubtable();
            if ($subtable) {
                $subtable->filter($className, $parameters);
                $subtable->filterSubtables($className, $parameters);
            }
        }
    }

Thomas Steur's avatar
Thomas Steur a validé
     * Adds a filter and a list of parameters to the list of queued filters of all subtables. These filters will be
     * executed when {@link applyQueuedFilters()} is called.
Thomas Steur's avatar
Thomas Steur a validé
     * Filters that prettify the column values 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|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 Limit filter.
     */
    public function queueFilterSubtables($className, $parameters = array())
    {
        foreach ($this->getRowsWithoutSummaryRow() as $row) {
            $subtable = $row->getSubtable();
            if ($subtable) {
                $subtable->queueFilter($className, $parameters);
                $subtable->queueFilterSubtables($className, $parameters);
            }
        }
    }

diosmosis's avatar
diosmosis a validé
     * Adds a filter and a list of parameters to the list of queued filters. These filters will be
     * executed when {@link applyQueuedFilters()} is called.
     * Filters that prettify the column values or don't need the full set of rows should be queued. This
diosmosis's avatar
diosmosis a validé
     * way they will be run after the table is truncated which will result in better performance.
     * @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 Limit filter.
     */
    public function queueFilter($className, $parameters = array())
    {
        if (!is_array($parameters)) {
            $parameters = array($parameters);
        }
        $this->queuedFilters[] = array('className' => $className, 'parameters' => $parameters);
    }

    /**
     * Disable a specific filter to run on this DataTable in case you have already applied this filter or if you will
     * handle this filter manually by using a custom filter. Be aware if you disable a given filter, that filter won't
     * be ever executed. Even if another filter calls this filter on the DataTable.
     *
     * @param string $className  eg 'Limit' or 'Sort'. Passing a `Closure` or an `array($class, $methodName)` is not
     *                           supported yet. We check for exact match. So if you disable 'Limit' and
     *                           call `->filter('Limit')` this filter won't be executed. If you call
     *                           `->filter('Piwik\DataTable\Filter\Limit')` that filter will be executed. See it as a
     *                           feature.
     * @ignore
     */
    public function disableFilter($className)
    {
        $this->disabledFilters[] = $className;
    }

     * Applies all filters that were previously queued to the table. See {@link queueFilter()}
diosmosis's avatar
diosmosis a validé
     * for more information.
     */
    public function applyQueuedFilters()
    {
        foreach ($this->queuedFilters as $filter) {
            $this->filter($filter['className'], $filter['parameters']);
        }
Matthieu Aubry's avatar
Matthieu Aubry a validé
        $this->clearQueuedFilters();
diosmosis's avatar
diosmosis a validé
     * Sums a DataTable to this one.
diosmosis's avatar
diosmosis a validé
     * 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`.
diosmosis's avatar
diosmosis a validé
     * If the subtables for this table are loaded, they will be summed as well.
diosmosis's avatar
diosmosis a validé
     * 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 {@link COLUMN_AGGREGATION_OPS_METADATA_NAME}
diosmosis's avatar
diosmosis a validé
     * metadata can be used to specify a different type of operation.
     * @param \Piwik\DataTable $tableToSum
    public function addDataTable(DataTable $tableToSum)
        if ($tableToSum instanceof Simple) {
            if ($tableToSum->getRowsCount() > 1) {
                throw new Exception("Did not expect a Simple table with more than one row in addDataTable()");
            }
            $row = $tableToSum->getFirstRow();
            $this->aggregateRowFromSimpleTable($row);
        } else {
            $columnAggregationOps = $this->getMetadata(self::COLUMN_AGGREGATION_OPS_METADATA_NAME);
            foreach ($tableToSum->getRowsWithoutSummaryRow() as $row) {
                $this->aggregateRowWithLabel($row, $columnAggregationOps);
            }
            // we do not use getRows() as this method might get called 100k times when aggregating many datatables and
            // this takes a lot of time.
            $row = $tableToSum->getRowFromId(DataTable::ID_SUMMARY_ROW);
            if ($row) {
                $this->aggregateRowWithLabel($row, $columnAggregationOps);
diosmosis's avatar
diosmosis a validé
     * Returns the Row whose `'label'` column is equal to `$label`.
diosmosis's avatar
diosmosis a validé
     * This method executes in constant time except for the first call which caches row
     * label => row ID mappings.
diosmosis's avatar
diosmosis a validé
     * @param string $label `'label'` column value to look for.
     * @return Row|false The row if found, `false` if otherwise.
     */
    public function getRowFromLabel($label)
    {
        $rowId = $this->getRowIdFromLabel($label);
        if (is_int($rowId) && isset($this->rows[$rowId])) {
            return $this->rows[$rowId];
        }
        if ($rowId == self::ID_SUMMARY_ROW
            && !empty($this->summaryRow)
        ) {
            return $this->summaryRow;
        }
        if ($rowId instanceof Row) {
            return $rowId;
        }
diosmosis's avatar
diosmosis a validé
     * Returns the row id for the row whose `'label'` column is equal to `$label`.
diosmosis's avatar
diosmosis a validé
     * This method executes in constant time except for the first call which caches row
     * label => row ID mappings.
diosmosis's avatar
diosmosis a validé
     * @param string $label `'label'` column value to look for.
     * @return int The row ID.
     */
    public function getRowIdFromLabel($label)
    {
        if ($this->indexNotUpToDate) {
            $this->rebuildIndex();
        }

        if ($label === self::LABEL_SUMMARY_ROW
            && !is_null($this->summaryRow)
        ) {
diosmosis's avatar
diosmosis a validé
            return self::ID_SUMMARY_ROW;
        $label = (string) $label;

        if (!isset($this->rowsIndexByLabel[$label])) {
            return false;
        }
        return $this->rowsIndexByLabel[$label];
    }

    /**
diosmosis's avatar
diosmosis a validé
     * Returns an empty DataTable with the same metadata and queued filters as `$this` one.
sgiehl's avatar
sgiehl a validé
     *
diosmosis's avatar
diosmosis a validé
     * @param bool $keepFilters Whether to pass the queued filter list to the new DataTable or not.
     * @return DataTable
    public function getEmptyClone($keepFilters = true)
        if ($keepFilters) {
            $clone->queuedFilters = $this->queuedFilters;
        }
        $clone->metadata = $this->metadata;
        return $clone;
    }

    /**
     * Rebuilds the index used to lookup a row by label
    public function rebuildIndex()
        $this->rowsIndexByLabel = array();
        $this->rebuildIndexContinuously = true;

        foreach ($this->rows as $id => $row) {
            $label = $row->getColumn('label');
            if ($label !== false) {
                $this->rowsIndexByLabel[$label] = $id;
            }
        }
        if ($this->summaryRow) {
            $label = $this->summaryRow->getColumn('label');
            if ($label !== false) {
                $this->rowsIndexByLabel[$label] = DataTable::ID_SUMMARY_ROW;
            }
        }

     * Returns a row by ID. The ID is either the index of the row or {@link ID_SUMMARY_ROW}.
diosmosis's avatar
diosmosis a validé
     * @param int $id The row ID.
     * @return Row|false The Row or false if not found.
     */
    public function getRowFromId($id)
    {
        if (!isset($this->rows[$id])) {
            if ($id == self::ID_SUMMARY_ROW
                && !is_null($this->summaryRow)
            ) {
                return $this->summaryRow;
            }
            return false;
        }
        return $this->rows[$id];
    }

    /**
diosmosis's avatar
diosmosis a validé
     * Returns the row that has a subtable with ID matching `$idSubtable`.
diosmosis's avatar
diosmosis a validé
     * @param int $idSubTable The subtable ID.
     * @return Row|false The row or false if not found
     */
    public function getRowFromIdSubDataTable($idSubTable)
    {
        $idSubTable = (int)$idSubTable;
        foreach ($this->rows as $row) {
            if ($row->getIdSubDataTable() === $idSubTable) {
                return $row;
            }
        }
        return false;
    }

    /**
diosmosis's avatar
diosmosis a validé
     * Adds a row to this table.
     * If {@link setMaximumAllowedRows()} was called and the current row count is
diosmosis's avatar
diosmosis a validé
     * 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.
diosmosis's avatar
diosmosis a validé
     * @param Row $row
     * @return Row `$row` or the summary row if we're at the maximum number of rows.
    {
        // if there is a upper limit on the number of allowed rows and the table is full,
        // add the new row to the summary row
        if ($this->maximumAllowedRows > 0
            && $this->getRowsCount() >= $this->maximumAllowedRows - 1
        ) {
            if ($this->summaryRow === null) {
                // create the summary row if necessary

Benaka Moorthi's avatar
Benaka Moorthi a validé
                $columns = array('label' => self::LABEL_SUMMARY_ROW) + $row->getColumns();
                $this->addSummaryRow(new Row(array(Row::COLUMNS => $columns)));
diosmosis's avatar
diosmosis a validé
                $this->summaryRow->sumRow(
                    $row, $enableCopyMetadata = false, $this->getMetadata(self::COLUMN_AGGREGATION_OPS_METADATA_NAME));
            }
            return $this->summaryRow;
        }

        $this->rows[] = $row;
        if (!$this->indexNotUpToDate
            && $this->rebuildIndexContinuously
        ) {
            $label = $row->getColumn('label');
            if ($label !== false) {
                $this->rowsIndexByLabel[$label] = count($this->rows) - 1;
            }
        }
        return $row;
    }

    /**
diosmosis's avatar
diosmosis a validé
     * Sets the summary row.
     * _Note: A DataTable can have only one summary row._
diosmosis's avatar
diosmosis a validé
     * @return Row Returns `$row`.
    public function addSummaryRow(Row $row)

        // add summary row to index
        if (!$this->indexNotUpToDate
            && $this->rebuildIndexContinuously
        ) {
            $label = $row->getColumn('label');
            if ($label !== false) {
                $this->rowsIndexByLabel[$label] = self::ID_SUMMARY_ROW;
            }
        }
diosmosis's avatar
diosmosis a validé
     * Returns the DataTable ID.
     *
     * @return int
     */
    public function getId()
    {
        return $this->currentId;
    }

    /**
diosmosis's avatar
diosmosis a validé
     * 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'),
     *                              Row::METADATA => array('mymetadata' => 'myvalue'))`
     */
    public function addRowFromArray($row)
    {
        $this->addRowsFromArray(array($row));
    }

    /**
diosmosis's avatar
diosmosis a validé
     * Adds a new row a from an array of column values.
diosmosis's avatar
diosmosis a validé
     * Row metadata cannot be added with this method.
     * @param array $row eg. `array('name' => 'google analytics', 'license' => 'commercial')`
     */
    public function addRowFromSimpleArray($row)
    {
        $this->addRowsFromSimpleArray(array($row));
    }

    /**
diosmosis's avatar
diosmosis a validé
     * Returns the array of Rows.
     */
    public function getRows()
    {
        if (is_null($this->summaryRow)) {
            return $this->rows;
        } else {
            return $this->rows + array(self::ID_SUMMARY_ROW => $this->summaryRow);
        }
    }

    /**
     * @ignore
     */
    public function getRowsWithoutSummaryRow()
    {
        return $this->rows;
    }

    /**
     * @ignore
     */
    public function getRowsCountWithoutSummaryRow()
    {
        return count($this->rows);
    }

diosmosis's avatar
diosmosis a validé
     * Returns an array containing all column values for the requested column.
diosmosis's avatar
diosmosis a validé
     * @param string $name The column name.
     * @return array The array of column values.
     */
    public function getColumn($name)
    {
        $columnValues = array();
        foreach ($this->getRows() as $row) {
            $columnValues[] = $row->getColumn($name);
        }
        return $columnValues;
    }

mattab's avatar
mattab a validé
    /**
diosmosis's avatar
diosmosis a validé
     * Returns an array containing all column values of columns whose name starts with `$name`.
mattab's avatar
mattab a validé
     *
Matthieu Napoli's avatar
Matthieu Napoli a validé
     * @param string $namePrefix The column name prefix.
diosmosis's avatar
diosmosis a validé
     * @return array The array of column values.
mattab's avatar
mattab a validé
     */
diosmosis's avatar
diosmosis a validé
    public function getColumnsStartingWith($namePrefix)
mattab's avatar
mattab a validé
    {
        $columnValues = array();
        foreach ($this->getRows() as $row) {
            $columns = $row->getColumns();
mattab's avatar
mattab a validé
            foreach ($columns as $column => $value) {
diosmosis's avatar
diosmosis a validé
                if (strpos($column, $namePrefix) === 0) {
mattab's avatar
mattab a validé
                    $columnValues[] = $row->getColumn($column);
                }
            }
        }
        return $columnValues;
    }
     * Returns the names of every column this DataTable contains. This method will return the
     * columns of the first row with data and will assume they occur in every other row as well.
     *_ 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._
diosmosis's avatar
diosmosis a validé
     * @return array Array of string column names.
        foreach ($this->getRows() as $row) {
            $columns = $row->getColumns();
            if (!empty($columns)) {

        // make sure column names are not DB index values
        foreach ($result as &$column) {
            if (isset(Metrics::$mappingFromIdToName[$column])) {
                $column = Metrics::$mappingFromIdToName[$column];
            }
        }

        return $result;
mattab's avatar
mattab a validé

diosmosis's avatar
diosmosis a validé
     * Returns an array containing the requested metadata value of each row.
diosmosis's avatar
diosmosis a validé
     * @param string $name The metadata column to return.
     * @return array
     */
    public function getRowsMetadata($name)
    {
        $metadataValues = array();
        foreach ($this->getRows() as $row) {
            $metadataValues[] = $row->getMetadata($name);
        }
        return $metadataValues;
    }

    /**
diosmosis's avatar
diosmosis a validé
     * Returns the number of rows in the table including the summary row.
     * @return int
     */
    public function getRowsCount()
    {
        if (is_null($this->summaryRow)) {
            return count($this->rows);
        } else {
            return count($this->rows) + 1;
        }
    }

    /**
diosmosis's avatar
diosmosis a validé
     * Returns the first row of the DataTable.
diosmosis's avatar
diosmosis a validé
     * @return Row|false The first row or `false` if it cannot be found.
     */
    public function getFirstRow()
    {
        if (count($this->rows) == 0) {
            if (!is_null($this->summaryRow)) {
                return $this->summaryRow;
            }
            return false;
        }
diosmosis's avatar
diosmosis a validé
        return reset($this->rows);
diosmosis's avatar
diosmosis a validé
     * Returns the last row of the DataTable. If there is a summary row, it
     * will always be considered the last row.