Skip to content
Extraits de code Groupes Projets
DataTable.php 56,5 ko
Newer Older
  • Learn to ignore specific revisions
  • 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) {
    
    diosmosis's avatar
    diosmosis a validé
         * Returns a string representation of this DataTable for convenient viewing.
    
         * _Note: This uses the **html** DataTable renderer._
    
         *
         * @return string
         */
        public function __toString()
        {
    
            $renderer->setTable($this);
            return (string)$renderer;
        }
    
        /**
    
    diosmosis's avatar
    diosmosis a validé
         * Returns true if both DataTable instances are exactly the same.
    
    diosmosis's avatar
    diosmosis a validé
         * 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
    
        public static function isEqual(DataTable $table1, DataTable $table2)
    
        {
            $rows1 = $table1->getRows();
    
            $table1->rebuildIndex();
            $table2->rebuildIndex();
    
            if ($table1->getRowsCount() != $table2->getRowsCount()) {
                return false;
            }
    
            foreach ($rows1 as $row1) {
                $row2 = $table2->getRowFromLabel($row1->getColumn('label'));
                if ($row2 === false
    
    diosmosis's avatar
    diosmosis a validé
         * Serializes an entire DataTable hierarchy and returns the array of serialized DataTables.
    
    diosmosis's avatar
    diosmosis a validé
         * The first element in the returned array will be the serialized representation of this DataTable.
         * Every subsequent element will be a serialized subtable.
    
    diosmosis's avatar
    diosmosis a validé
         * 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 result of this method is intended for use with the {@link ArchiveProcessor::insertBlobRecord()} method.
    
    diosmosis's avatar
    diosmosis a validé
         * @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:
    
    diosmosis's avatar
    diosmosis a validé
         *                   array(
         *                       // this DataTable (the root)
         *                       0 => 'eghuighahgaueytae78yaet7yaetae',
    
    diosmosis's avatar
    diosmosis a validé
         *                       // a subtable
         *                       1 => 'gaegae gh gwrh guiwh uigwhuige',
    
    diosmosis's avatar
    diosmosis a validé
         *                       // another subtable
         *                       2 => 'gqegJHUIGHEQjkgneqjgnqeugUGEQHGUHQE',
    
    diosmosis's avatar
    diosmosis a validé
         *                       // etc.
         *                   );
    
         */
        public function getSerialized($maximumRowsInDataTable = null,
                                      $maximumRowsInSubDataTable = null,
                                      $columnToSortByBeforeTruncation = null)
        {
            static $depth = 0;
    
            if ($depth > self::$maximumDepthLevelAllowed) {
                $depth = 0;
    
                throw new Exception("Maximum recursion level of " . self::$maximumDepthLevelAllowed . " reached. Maybe you have set a DataTable\Row with an associated DataTable belonging already to one of its parent tables?");
    
            }
            if (!is_null($maximumRowsInDataTable)) {
    
                    array($maximumRowsInDataTable - 1,
    
                          $columnToSortByBeforeTruncation,
                          $filterRecursive = false)
    
                );
            }
    
            // For each row, get the serialized row
            // If it is associated to a sub table, get the serialized table recursively ;
            // but returns all serialized tables and subtable in an array of 1 dimension
            $aSerializedDataTable = array();
            foreach ($this->rows as $row) {
    
                $subTable = $row->getSubtable();
    
                if ($subTable) {
    
                    $depth++;
                    $aSerializedDataTable = $aSerializedDataTable + $subTable->getSerialized($maximumRowsInSubDataTable, $maximumRowsInSubDataTable, $columnToSortByBeforeTruncation);
                    $depth--;
    
                }
            }
            // we load the current Id of the DataTable
            $forcedId = $this->getId();
    
            // if the datatable is the parent we force the Id at 0 (this is part of the specification)
            if ($depth == 0) {
                $forcedId = 0;
            }
    
            // we then serialize the rows and store them in the serialized dataTable
            $addToRows = array(self::ID_SUMMARY_ROW => $this->summaryRow);
    
            $aSerializedDataTable[$forcedId] = serialize($this->rows + $addToRows);
            foreach ($this->rows as &$row) {
                $row->cleanPostSerialize();
            }
    
            return $aSerializedDataTable;
        }
    
        /**
    
    diosmosis's avatar
    diosmosis a validé
         * Adds a set of rows from a serialized DataTable string.
    
         * See {@link serialize()}.
    
         * _Note: This function will successfully load DataTables serialized by Piwik 1.X._
    
         * @param string $stringSerialized A string with the format of a string in the array returned by
         *                                 {@link serialize()}.
    
    diosmosis's avatar
    diosmosis a validé
         * @throws Exception if `$stringSerialized` is invalid.
    
         */
        public function addRowsFromSerializedArray($stringSerialized)
        {
    
            require_once PIWIK_INCLUDE_PATH . "/core/DataTable/Bridges.php";
    
            $serialized = unserialize($stringSerialized);
            if ($serialized === false) {
                throw new Exception("The unserialization has failed!");
            }
            $this->addRowsFromArray($serialized);
        }
    
        /**
    
         * Adds multiple rows from an array.
    
         * You can add row metadata with this method.
    
         * @param array $array Array with the following structure
    
    diosmosis's avatar
    diosmosis a validé
         *                         array(
    
    diosmosis's avatar
    diosmosis a validé
         *                                 Row::COLUMNS => array( col1_name => value1, col2_name => value2, ...),
         *                                 Row::METADATA => array( metadata1_name => value1,  ...), // see Row
    
    diosmosis's avatar
    diosmosis a validé
         *                         )
    
         */
        public function addRowsFromArray($array)
        {
            foreach ($array as $id => $row) {
                if (is_array($row)) {
    
                }
                if ($id == self::ID_SUMMARY_ROW) {
                    $this->summaryRow = $row;
                } else {
                    $this->addRow($row);
                }
            }
        }
    
        /**
    
         * Adds multiple rows from an array containing arrays of column values.
    
    diosmosis's avatar
    diosmosis a validé
         * Row metadata cannot be added with this method.
    
         * @param array $array Array with the following structure:
    
         *                       array(
         *                             array( col1_name => valueA, col2_name => valueC, ...),
         *                             array( col1_name => valueB, col2_name => valueD, ...),
         *                       )
    
         * @throws Exception if `$array` is in an incorrect format.
    
         */
        public function addRowsFromSimpleArray($array)
        {
            if (count($array) === 0) {
                return;
            }
    
            // we define an exception we may throw if at one point we notice that we cannot handle the data structure
            $e = new Exception(" Data structure returned is not convertible in the requested format." .
                " Try to call this method with the parameters '&format=original&serialize=1'" .
                "; you will get the original php data structure serialized." .
                " The data structure looks like this: \n \$data = " . var_export($array, true) . "; ");
    
            // first pass to see if the array has the structure
            // array(col1_name => val1, col2_name => val2, etc.)
            // with val* that are never arrays (only strings/numbers/bool/etc.)
            // if we detect such a "simple" data structure we convert it to a row with the correct columns' names
            $thisIsNotThatSimple = false;
    
            foreach ($array as $columnValue) {
                if (is_array($columnValue) || is_object($columnValue)) {
                    $thisIsNotThatSimple = true;
                    break;
                }
            }
            if ($thisIsNotThatSimple === false) {
                // case when the array is indexed by the default numeric index
                if (array_keys($array) == array_keys(array_fill(0, count($array), true))) {
                    foreach ($array as $row) {
    
                        $this->addRow(new Row(array(Row::COLUMNS => array($row))));
    
                    $this->addRow(new Row(array(Row::COLUMNS => $array)));
    
                }
                // we have converted our simple array to one single row
                // => we exit the method as the job is now finished
                return;
            }
    
            foreach ($array as $key => $row) {
                // stuff that looks like a line
                if (is_array($row)) {
                    /**
                     * We make sure we can convert this PHP array without losing information.
                     * We are able to convert only simple php array (no strings keys, no sub arrays, etc.)
                     *
                     */
    
                    // if the key is a string it means that some information was contained in this key.
                    // it cannot be lost during the conversion. Because we are not able to handle properly
                    // this key, we throw an explicit exception.
                    if (is_string($key)) {
                        throw $e;
                    }
                    // if any of the sub elements of row is an array we cannot handle this data structure...
                    foreach ($row as $subRow) {
                        if (is_array($subRow)) {
                            throw $e;
                        }
                    }
    
                    $row = new Row(array(Row::COLUMNS => $row));
    
                } // other (string, numbers...) => we build a line from this value
                else {
    
                    $row = new Row(array(Row::COLUMNS => array($key => $row)));
    
         * Rewrites the input `$array`
    
         *     array (
         *         LABEL => array(col1 => X, col2 => Y),
         *         LABEL2 => array(col1 => X, col2 => Y),
         *     )
    
         * to a DataTable with rows that look like:
    
         *     array (
         *         array( Row::COLUMNS => array('label' => LABEL, col1 => X, col2 => Y)),
         *         array( Row::COLUMNS => array('label' => LABEL2, col1 => X, col2 => Y)),
         *     )
    
         * Will also convert arrays like:
         *
    
         *     array (
         *         LABEL => X,
         *         LABEL2 => Y,
         *     )
    
         *     array (
         *         array( Row::COLUMNS => array('label' => LABEL, 'value' => X)),
         *         array( Row::COLUMNS => array('label' => LABEL2, 'value' => Y)),
         *     )
    
         * @param array $array Indexed array, two formats supported, see above.
         * @param array|null $subtablePerLabel An array mapping label values with DataTable instances to associate as a subtable.
    
        public static function makeFromIndexedArray($array, $subtablePerLabel = null)
    
            foreach ($array as $label => $row) {
    
                // Support the case of an $array of single values
    
                if (!is_array($row)) {
                    $row = array('value' => $row);
                }
    
                // Put the 'label' column first
    
                $cleanRow[Row::COLUMNS] = array('label' => $label) + $row;
    
                // Assign subtable if specified
                if (isset($subtablePerLabel[$label])) {
    
                    $cleanRow[Row::DATATABLE_ASSOCIATED] = $subtablePerLabel[$label];
    
                $table->addRow(new Row($cleanRow));
    
    diosmosis's avatar
    diosmosis a validé
         * Sets the maximum depth level to at least a certain value. If the current value is
    
         * greater than `$atLeastLevel`, the maximum nesting level is not changed.
    
    diosmosis's avatar
    diosmosis a validé
         * 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.
    
        public static function setMaximumDepthLevelAllowedAtLeast($atLeastLevel)
    
        {
            self::$maximumDepthLevelAllowed = max($atLeastLevel, self::$maximumDepthLevelAllowed);
            if (self::$maximumDepthLevelAllowed < 1) {
                self::$maximumDepthLevelAllowed = 1;
            }
        }
    
        /**
         * Returns metadata by name.
         *
         * @param string $name The metadata name.
    
         * @return mixed|false The metadata value or `false` if it cannot be found.
    
         */
        public function getMetadata($name)
        {
            if (!isset($this->metadata[$name])) {
                return false;
            }
            return $this->metadata[$name];
        }
    
        /**
         * Sets a metadata value by name.
         *
         * @param string $name The metadata name.
         * @param mixed $value
         */
        public function setMetadata($name, $value)
        {
            $this->metadata[$name] = $value;
        }
    
    
        /**
         * Returns all table metadata.
         *
         * @return array
         */
        public function getAllTableMetadata()
        {
            return $this->metadata;
        }
    
    
        /**
         * Sets several metadata values by name.
    
         * @param array $values Array mapping metadata names with metadata values.
         */
        public function setMetadataValues($values)
        {
            foreach ($values as $name => $value) {
                $this->metadata[$name] = $value;
            }
        }
    
        /**
    
         * Sets metadata, erasing existing values.
    
         * @param array $values Array mapping metadata names with metadata values.
         */
        public function setAllTableMetadata($metadata)
        {
            $this->metadata = $metadata;
        }
    
    
        /**
         * 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
    
    diosmosis's avatar
    diosmosis a validé
         * rows are summed to the summary row.
    
    diosmosis's avatar
    diosmosis a validé
         * @param int $maximumAllowedRows If `0`, the maximum number of rows is unset.
    
         */
        public function setMaximumAllowedRows($maximumAllowedRows)
        {
            $this->maximumAllowedRows = $maximumAllowedRows;
        }
    
        /**
         * Traverses a DataTable tree using an array of labels and returns the row
    
         * it finds or `false` if it cannot find one. The number of path segments that
    
         * were successfully walked is also returned.
    
         * If `$missingRowColumns` is supplied, the specified path is created. When
         * a subtable is encountered w/o the required label, a new row is created
         * with the label, and a new subtable is added to the row.
    
    diosmosis's avatar
    diosmosis a validé
         * 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.
    
    diosmosis's avatar
    diosmosis a validé
         * @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.
    
    sgiehl's avatar
    sgiehl a validé
         *                                      If this parameter is supplied, new rows will be
    
    diosmosis's avatar
    diosmosis a validé
         *                                      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
    
         *               of the path segment that we could not find.
         */
        public function walkPath($path, $missingRowColumns = false, $maxSubtableRows = 0)
        {
            $pathLength = count($path);
    
            $table = $this;
            $next = false;
            for ($i = 0; $i < $pathLength; ++$i) {
                $segment = $path[$i];
    
                $next = $table->getRowFromLabel($segment);
                if ($next === false) {
                    // if there is no table to advance to, and we're not adding missing rows, return false
                    if ($missingRowColumns === false) {
                        return array(false, $i);
                    } else // if we're adding missing rows, add a new row
                    {
    
                        $row->setColumns(array('label' => $segment) + $missingRowColumns);
    
                        $next = $table->addRow($row);
    
                        if ($next !== $row) // if the row wasn't added, the table is full
                        {
                            // Summary row, has no metadata
                            $next->deleteMetadata();
                            return array($next, $i);
                        }
                    }
                }
    
                $table = $next->getSubtable();
                if ($table === false) {
                    // if the row has no table (and thus no child rows), and we're not adding
                    // missing rows, return false
                    if ($missingRowColumns === false) {
                        return array(false, $i);
                    } else if ($i != $pathLength - 1) // create subtable if missing, but only if not on the last segment
                    {
    
                        $table->setMaximumAllowedRows($maxSubtableRows);
    
    diosmosis's avatar
    diosmosis a validé
                        $table->metadata[self::COLUMN_AGGREGATION_OPS_METADATA_NAME]
    
    diosmosis's avatar
    diosmosis a validé
                            = $this->getMetadata(self::COLUMN_AGGREGATION_OPS_METADATA_NAME);
    
                        $next->setSubtable($table);
                        // Summary row, has no metadata
                        $next->deleteMetadata();
                    }
                }
            }
    
            return array($next, $i);
        }
    
        /**
    
         * Returns a new DataTable in which the rows of this table are replaced with the aggregatated rows of all its subtables.
    
    diosmosis's avatar
    diosmosis a validé
         * @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
    
    diosmosis's avatar
    diosmosis a validé
         *                                 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
    
    diosmosis's avatar
    diosmosis a validé
         *                                label will be added as metadata and not a new column.
    
         */
        public function mergeSubtables($labelColumn = false, $useMetadataColumn = false)
        {
    
            foreach ($this->getRows() as $row) {
                $subtable = $row->getSubtable();
                if ($subtable !== false) {
                    $parentLabel = $row->getColumn('label');
    
                    // add a copy of each subtable row to the new datatable
                    foreach ($subtable->getRows() as $id => $subRow) {
                        $copy = clone $subRow;
    
                        // if the summary row, add it to the existing summary row (or add a new one)
                        if ($id == self::ID_SUMMARY_ROW) {
                            $existing = $result->getRowFromId(self::ID_SUMMARY_ROW);
                            if ($existing === false) {
                                $result->addSummaryRow($copy);
                            } else {
    
    diosmosis's avatar
    diosmosis a validé
                                $existing->sumRow($copy, $copyMeta = true, $this->getMetadata(self::COLUMN_AGGREGATION_OPS_METADATA_NAME));
    
                            }
                        } else {
                            if ($labelColumn !== false) {
                                // if we're modifying the subtable's rows' label column, then we make
                                // sure to prepend the existing label w/ the parent row's label. otherwise
                                // we're just adding the parent row's label as a new column/metadata.
                                $newLabel = $parentLabel;
                                if ($labelColumn == 'label') {
                                    $newLabel .= ' - ' . $copy->getColumn('label');
                                }
    
                                // modify the child row's label or add new column/metadata
                                if ($useMetadataColumn) {
                                    $copy->setMetadata($labelColumn, $newLabel);
                                } else {
                                    $copy->setColumn($labelColumn, $newLabel);
                                }
                            }
    
                            $result->addRow($copy);
                        }
                    }
                }
            }
            return $result;
        }
    
        /**
         * Returns a new DataTable created with data from a 'simple' array.
    
         * See {@link addRowsFromSimpleArray()}.
    
         */
        public static function makeFromSimpleArray($array)
        {
    
            $dataTable->addRowsFromSimpleArray($array);
            return $dataTable;
        }
    
    diosmosis's avatar
    diosmosis a validé
         * Creates a new DataTable instance from a serialized DataTable string.
    
         * See {@link getSerialized()} and {@link addRowsFromSerializedArray()}
    
    diosmosis's avatar
    diosmosis a validé
         * for more information on DataTable serialization.
    
         */
        public static function fromSerializedArray($data)
        {
    
            $result->addRowsFromSerializedArray($data);
            return $result;
        }
    
    
        /**
         * Aggregates the $row columns to this table.
         *
         * $row must have a column "label". The $row will be summed to this table's row with the same label.
         *
         * @param $row
         * @throws \Exception
         */
    
        protected function aggregateRowWithLabel(Row $row, $doAggregateSubTables = true)
    
        {
            $labelToLookFor = $row->getColumn('label');
            if ($labelToLookFor === false) {
                throw new Exception("Label column not found in the table to add in addDataTable()");
            }
            $rowFound = $this->getRowFromLabel($labelToLookFor);
            if ($rowFound === false) {
                if ($labelToLookFor === self::LABEL_SUMMARY_ROW) {
                    $this->addSummaryRow($row);
                } else {
                    $this->addRow($row);
                }
            } else {
                $rowFound->sumRow($row, $copyMeta = true, $this->getMetadata(self::COLUMN_AGGREGATION_OPS_METADATA_NAME));
    
    
                    // if the row to add has a subtable whereas the current row doesn't
                    // we simply add it (cloning the subtable)
                    // if the row has the subtable already
                    // then we have to recursively sum the subtables
    
                    $subTable = $row->getSubtable();
                    if ($subTable) {
    
                        $subTable->metadata[self::COLUMN_AGGREGATION_OPS_METADATA_NAME]
                            = $this->getMetadata(self::COLUMN_AGGREGATION_OPS_METADATA_NAME);
                        $rowFound->sumSubtable($subTable);
                    }
    
                }
            }
        }
    
        /**
         * @param $row
         */
        protected function aggregateRowFromSimpleTable($row)
        {
            if ($row === false) {
                return;
    
            }
            $thisRow = $this->getFirstRow();
            if ($thisRow === false) {
                $thisRow = new Row;
                $this->addRow($thisRow);
            }
            $thisRow->sumRow($row, $copyMeta = true, $this->getMetadata(self::COLUMN_AGGREGATION_OPS_METADATA_NAME));
        }
    
        /**
         * Unsets all queued filters.
         */
        public function clearQueuedFilters()
        {
            $this->queuedFilters = array();
        }
    
    
        /**
         * @return \ArrayIterator|Row[]
         */
    
            return new \ArrayIterator($this->getRows());
    
    
        public function offsetExists($offset)
        {
            $row = $this->getRowFromId($offset);
    
            return false !== $row;
        }
    
        public function offsetGet($offset)
        {
            return $this->getRowFromId($offset);
        }
    
        public function offsetSet($offset, $value)
        {
            $this->rows[$offset] = $value;
        }
    
        public function offsetUnset($offset)
        {
            $this->deleteRow($offset);
        }