Skip to content
Extraits de code Groupes Projets
DataTable.php 56,2 ko
Newer Older
  • Learn to ignore specific revisions
  • <?php
    /**
     * Piwik - Open source web analytics
    
    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;
    
    use Piwik\DataTable\TableNotFoundException;
    
    mattab's avatar
    mattab a validé
     * @see Common::destroy()
    
     */
    require_once PIWIK_INCLUDE_PATH . '/core/Common.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>
    
     * ### The Basics
     * 
     * 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}.
    
    diosmosis's avatar
    diosmosis a validé
     * 
    
     * 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.
    
     * 
     * ### Examples
     * 
     * **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')
     *     )));
     *     
     *     // 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::build('week', Date::factory('2013-10-18'));
    
    diosmosis's avatar
    diosmosis a validé
     * 
    
     * **Serializing & unserializing**
    
    diosmosis's avatar
    diosmosis a validé
     * 
     *     $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];
    
    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::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;
     *     }
    
    class DataTable implements DataTableInterface
    
    diosmosis's avatar
    diosmosis a validé
        const MAX_DEPTH_DEFAULT = 15;
    
    
        /** Name for metadata that describes when a report was archived. */
        const ARCHIVED_DATE_METADATA_NAME = 'archived_date';
    
    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:
         * 
         * - `'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;
    
    
        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();
    
        /**
         * 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.
         * 
         * 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->getRows() as $row) {
    
    mattab's avatar
    mattab a validé
                    Common::destroy($row);
    
                Manager::getInstance()->setTableDeleted($this->getId());
    
    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->indexNotUpToDate = true;
            $this->tableSortedBy = $columnSortedBy;
            usort($this->rows, $functionCallback);
    
            if ($this->enableRecursiveSort === true) {
                foreach ($this->getRows() as $row) {
                    if (($idSubtable = $row->getIdSubDataTable()) !== null) {
    
                        $table = Manager::getInstance()->getTable($idSubtable);
    
                        $table->enableRecursiveSort();
                        $table->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;
        }
    
        /**
    
         * 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;
        }
    
        /**
    
         * Applies a filter to this datatable.
    
    diosmosis's avatar
    diosmosis a validé
         * 
    
         * 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 (!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);
        }
    
        /**
    
    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.
    
    diosmosis's avatar
    diosmosis a validé
         * 
    
         * 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);
        }
    
        /**
    
         * 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']);
            }
            $this->queuedFilters = array();
        }
    
        /**
    
    diosmosis's avatar
    diosmosis a validé
         * 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`.
    
    diosmosis's avatar
    diosmosis a validé
         * 
         * 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 {@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, $doAggregateSubTables = true)
    
            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 {
                foreach ($tableToSum->getRows() as $row) {
    
                    $this->aggregateRowWithLabel($row, $doAggregateSubTables);
    
    diosmosis's avatar
    diosmosis a validé
         * 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)
        {
            $rowId = $this->getRowIdFromLabel($label);
    
                return $rowId;
            }
            if (is_int($rowId) && isset($this->rows[$rowId])) {
                return $this->rows[$rowId];
            }
    
            if ($rowId == self::ID_SUMMARY_ROW
                && !empty($this->summaryRow)
            ) {
                return $this->summaryRow;
            }
    
    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.
         * 
         * @param string $label `'label'` column value to look for.
         * @return int The row ID.
    
         */
        public function getRowIdFromLabel($label)
        {
            $this->rebuildIndexContinuously = true;
            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
         */
        private function rebuildIndex()
        {
    
            foreach ($this->getRows() as $id => $row) {
    
                $label = $row->getColumn('label');
                if ($label !== false) {
                    $this->rowsIndexByLabel[$label] = $id;
                }
            }
            $this->indexNotUpToDate = false;
        }
    
        /**
    
         * 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`.
         * 
         * @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.
         * 
         * 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);
            }
        }
    
        /**
    
    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é
         *
    
    diosmosis's avatar
    diosmosis a validé
         * @param $namePrefix The column name prefix.
         * @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.
    
    diosmosis's avatar
    diosmosis a validé
         * 
    
         *_ 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.
         * 
         * @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.
    
    diosmosis's avatar
    diosmosis a validé
         * @return Row|false The last row or `false` if it cannot be found.
    
         */
        public function getLastRow()
        {
            if (!is_null($this->summaryRow)) {
                return $this->summaryRow;
            }
    
            if (count($this->rows) == 0) {
                return false;
            }
    
    diosmosis's avatar
    diosmosis a validé
    
            return end($this->rows);
    
         * Returns the number of rows in the entire DataTable hierarchy. This is the number of rows in this DataTable
         * summed with the row count of each descendant subtable.
    
         *
         * @return int
         */
        public function getRowsCountRecursive()
        {
            $totalCount = 0;
            foreach ($this->rows as $row) {
                if (($idSubTable = $row->getIdSubDataTable()) !== null) {
    
                    $subTable = Manager::getInstance()->getTable($idSubTable);
    
                    $count = $subTable->getRowsCountRecursive();
                    $totalCount += $count;
                }
            }
    
            $totalCount += $this->getRowsCount();
            return $totalCount;
        }
    
        /**
    
    diosmosis's avatar
    diosmosis a validé
         * Delete a column by name in every row. This change is NOT applied recursively to all
         * subtables.
    
    diosmosis's avatar
    diosmosis a validé
         * @param string $name Column name to delete.
    
         */
        public function deleteColumn($name)
        {
            $this->deleteColumns(array($name));
        }
    
        public function __sleep()
        {
    
            return array('rows', 'summaryRow');
    
    diosmosis's avatar
    diosmosis a validé
         * Rename a column in every row. This change is applied recursively to all subtables.
    
    diosmosis's avatar
    diosmosis a validé
         * @param string $oldName Old column name.
         * @param string $newName New column name.
    
        public function renameColumn($oldName, $newName, $doRenameColumnsOfSubTables = true)
    
        {
            foreach ($this->getRows() as $row) {
                $row->renameColumn($oldName, $newName);
    
    
                if($doRenameColumnsOfSubTables) {
                    if (($idSubDataTable = $row->getIdSubDataTable()) !== null) {
                        Manager::getInstance()->getTable($idSubDataTable)->renameColumn($oldName, $newName);
                    }
    
                }
            }
            if (!is_null($this->summaryRow)) {
                $this->summaryRow->renameColumn($oldName, $newName);
            }
        }
    
        /**
    
    diosmosis's avatar
    diosmosis a validé
         * Deletes several columns by name in every row.
    
    diosmosis's avatar
    diosmosis a validé
         * @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)
        {
            foreach ($this->getRows() as $row) {
                foreach ($names as $name) {
                    $row->deleteColumn($name);
                }
                if (($idSubDataTable = $row->getIdSubDataTable()) !== null) {
    
                    Manager::getInstance()->getTable($idSubDataTable)->deleteColumns($names, $deleteRecursiveInSubtables);
    
                }
            }
            if (!is_null($this->summaryRow)) {
                foreach ($names as $name) {
                    $this->summaryRow->deleteColumn($name);
                }
            }
        }
    
        /**
    
    diosmosis's avatar
    diosmosis a validé
         * Deletes a row by ID.
    
    sgiehl's avatar
    sgiehl a validé
         *
    
    diosmosis's avatar
    diosmosis a validé
         * @param int $id The row ID.
         * @throws Exception If the row `$id` cannot be found.
    
         */
        public function deleteRow($id)
        {
            if ($id === self::ID_SUMMARY_ROW) {
                $this->summaryRow = null;
                return;
            }
            if (!isset($this->rows[$id])) {
                throw new Exception("Trying to delete unknown row with idkey = $id");
            }
            unset($this->rows[$id]);
        }
    
        /**
    
    diosmosis's avatar
    diosmosis a validé
         * Deletes rows from `$offset` to `$offset + $limit`.
    
    diosmosis's avatar
    diosmosis a validé
         * @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)
        {
            if ($limit === 0) {
                return 0;
            }
    
            $count = $this->getRowsCount();
            if ($offset >= $count) {
                return 0;
            }
    
            // if we delete until the end, we delete the summary row as well
            if (is_null($limit)
                || $limit >= $count
            ) {
                $this->summaryRow = null;
            }
    
            if (is_null($limit)) {
                $spliced = array_splice($this->rows, $offset);
            } else {
                $spliced = array_splice($this->rows, $offset, $limit);
            }
            $countDeleted = count($spliced);
            return $countDeleted;
        }
    
        /**
    
    diosmosis's avatar
    diosmosis a validé
         * Deletes a set of rows by ID.
    
    diosmosis's avatar
    diosmosis a validé
         * @param array $rowIds The list of row IDs to delete.
         * @throws Exception If a row ID cannot be found.
    
    diosmosis's avatar
    diosmosis a validé
        public function deleteRows(array $rowIds)
    
    diosmosis's avatar
    diosmosis a validé
            foreach ($rowIds as $key) {