| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\Archive; |
|
|
| use Piwik\DataTable; |
| use Piwik\DataTable\Row; |
| use Piwik\Segment; |
| use Piwik\Site; |
|
|
| |
| |
| |
| |
| |
| |
| class DataTableFactory |
| { |
| public const TABLE_METADATA_SEGMENT_INDEX = 'segment'; |
| public const TABLE_METADATA_SEGMENT_PRETTY_INDEX = 'segmentPretty'; |
|
|
| |
| |
| |
| private $dataNames; |
|
|
| |
| |
| |
| private $dataType; |
|
|
| |
| |
| |
| |
| |
| |
| |
| private $expandDataTable = false; |
|
|
| |
| |
| |
| |
| |
| |
| private $addMetadataSubtableId = false; |
|
|
| |
| |
| |
| |
| |
| |
| private $maxSubtableDepth = null; |
|
|
| |
| |
| |
| private $sitesId; |
|
|
| |
| |
| |
| private $periods; |
|
|
| |
| |
| |
| private $segment; |
|
|
| |
| |
| |
| |
| |
| private $idSubtable = null; |
|
|
| |
| |
| |
| private $defaultRow; |
|
|
| public const TABLE_METADATA_SITE_INDEX = 'site'; |
| public const TABLE_METADATA_PERIOD_INDEX = 'period'; |
|
|
| public function __construct($dataNames, $dataType, $sitesId, $periods, Segment $segment, $defaultRow) |
| { |
| $this->dataNames = $dataNames; |
| $this->dataType = $dataType; |
| $this->sitesId = $sitesId; |
|
|
| |
| $this->periods = $periods; |
| $this->segment = $segment; |
| $this->defaultRow = $defaultRow; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public static function getSiteIdFromMetadata(DataTable $table) |
| { |
| $site = $table->getMetadata(self::TABLE_METADATA_SITE_INDEX); |
| if (empty($site)) { |
| return null; |
| } else { |
| return $site->getId(); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function expandDataTable($maxSubtableDepth = null, $addMetadataSubtableId = false) |
| { |
| $this->expandDataTable = true; |
| $this->maxSubtableDepth = $maxSubtableDepth; |
| $this->addMetadataSubtableId = $addMetadataSubtableId; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public function useSubtable($idSubtable) |
| { |
| if (count($this->dataNames) !== 1) { |
| throw new \Exception("DataTableFactory: Getting subtables for multiple records in one" |
| . " archive query is not currently supported."); |
| } |
|
|
| $this->idSubtable = $idSubtable; |
| } |
|
|
| private function isNumericDataType() |
| { |
| return $this->dataType == 'numeric'; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function make($index, $resultIndices, $keyMetadata = null) |
| { |
| $keyMetadata = $keyMetadata ?: $this->getDefaultMetadata(); |
|
|
| if (empty($resultIndices)) { |
| |
| |
| if ( |
| empty($index) |
| && $this->isNumericDataType() |
| ) { |
| $index = $this->defaultRow; |
| } |
|
|
| $dataTable = $this->createDataTable($index, $keyMetadata); |
| } else { |
| $dataTable = $this->createDataTableMapFromIndex($index, $resultIndices, $keyMetadata); |
| } |
|
|
| return $dataTable; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function makeMerged($index, $resultIndices) |
| { |
| if (!$this->isNumericDataType()) { |
| throw new \Exception('This method is supposed to work with non-numeric data types but it is not tested. To use it, remove this exception and write tests to be sure it works.'); |
| } |
|
|
| $hasSiteIndex = isset($resultIndices[self::TABLE_METADATA_SITE_INDEX]); |
| $hasPeriodIndex = isset($resultIndices[self::TABLE_METADATA_PERIOD_INDEX]); |
|
|
| $isNumeric = $this->isNumericDataType(); |
| |
| $useSimpleDataTable = !$hasSiteIndex && $isNumeric; |
|
|
| if (!$hasSiteIndex) { |
| $firstIdSite = reset($this->sitesId); |
| $index = array($firstIdSite => $index); |
| } |
|
|
| if ($hasPeriodIndex) { |
| $dataTable = $this->makeMergedTableWithPeriodAndSiteIndex($index, $resultIndices, $useSimpleDataTable, $isNumeric); |
| } else { |
| $dataTable = $this->makeMergedWithSiteIndex($index, $useSimpleDataTable, $isNumeric); |
| } |
|
|
| return $dataTable; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| private function makeFromBlobRow($blobRow, $keyMetadata) |
| { |
| if ($blobRow === false) { |
| $table = new DataTable(); |
| $table->setAllTableMetadata($keyMetadata); |
| $this->setPrettySegmentMetadata($table); |
| return $table; |
| } |
|
|
| if (count($this->dataNames) === 1) { |
| return $this->makeDataTableFromSingleBlob($blobRow, $keyMetadata); |
| } else { |
| return $this->makeIndexedByRecordNameDataTable($blobRow, $keyMetadata); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| private function makeDataTableFromSingleBlob($blobRow, $keyMetadata) |
| { |
| $recordName = reset($this->dataNames); |
| if ($this->idSubtable !== null) { |
| $recordName .= '_' . $this->idSubtable; |
| } |
|
|
| if (!empty($blobRow[$recordName])) { |
| $table = DataTable::fromSerializedArray($blobRow[$recordName]); |
| } else { |
| $table = new DataTable(); |
| } |
|
|
| |
| $table->setAllTableMetadata(array_merge($table->getAllTableMetadata(), DataCollection::getDataRowMetadata($blobRow), $keyMetadata)); |
| $this->setPrettySegmentMetadata($table); |
|
|
| if ($this->expandDataTable) { |
| $table->enableRecursiveFilters(); |
| $this->setSubtables($table, $blobRow); |
| } |
|
|
| return $table; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| private function makeIndexedByRecordNameDataTable($blobRow, $keyMetadata) |
| { |
| $table = new DataTable\Map(); |
| $table->setKeyName('recordName'); |
|
|
| $tableMetadata = array_merge(DataCollection::getDataRowMetadata($blobRow), $keyMetadata); |
|
|
| foreach ($blobRow as $name => $blob) { |
| $newTable = DataTable::fromSerializedArray($blob); |
| $newTable->setAllTableMetadata(array_merge($newTable->getAllTableMetadata(), $tableMetadata)); |
| $this->setPrettySegmentMetadata($newTable); |
|
|
| $table->addTable($newTable, $name); |
| } |
|
|
| return $table; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| private function createDataTableMapFromIndex($index, $resultIndices, $keyMetadata) |
| { |
| $result = new DataTable\Map(); |
| $result->setKeyName(reset($resultIndices)); |
| $resultIndex = key($resultIndices); |
|
|
| array_shift($resultIndices); |
|
|
| $hasIndices = !empty($resultIndices); |
|
|
| foreach ($index as $label => $value) { |
| $keyMetadata[$resultIndex] = $this->createTableIndexMetadata($resultIndex, $label); |
|
|
| if ($hasIndices) { |
| $newTable = $this->createDataTableMapFromIndex($value, $resultIndices, $keyMetadata); |
| } else { |
| $newTable = $this->createDataTable($value, $keyMetadata); |
| } |
|
|
| $result->addTable($newTable, $this->prettifyIndexLabel($resultIndex, $label)); |
| } |
|
|
| return $result; |
| } |
|
|
| private function createTableIndexMetadata($resultIndex, $label) |
| { |
| if ($resultIndex === DataTableFactory::TABLE_METADATA_SITE_INDEX) { |
| return new Site($label); |
| } elseif ($resultIndex === DataTableFactory::TABLE_METADATA_PERIOD_INDEX) { |
| return $this->periods[$label]; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| private function createDataTable($data, $keyMetadata) |
| { |
| if ($this->dataType == 'blob') { |
| $result = $this->makeFromBlobRow($data, $keyMetadata); |
| } else { |
| $result = $this->makeFromMetricsArray($data, $keyMetadata); |
| } |
|
|
| return $result; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| private function setSubtables($dataTable, $blobRow, $treeLevel = 0) |
| { |
| if ( |
| $this->maxSubtableDepth |
| && $treeLevel >= $this->maxSubtableDepth |
| ) { |
| |
| foreach ($dataTable->getRowsWithoutSummaryRow() as $row) { |
| $row->removeSubtable(); |
| } |
| $summaryRow = $dataTable->getRowFromId(DataTable::ID_SUMMARY_ROW); |
| if ($summaryRow) { |
| $summaryRow->removeSubtable(); |
| } |
|
|
| return; |
| } |
|
|
| $dataName = reset($this->dataNames); |
|
|
| foreach ($dataTable->getRows() as $row) { |
| $sid = $row->getIdSubDataTable(); |
| if ($sid === null) { |
| continue; |
| } |
|
|
| $blobName = $dataName . "_" . $sid; |
| if (!empty($blobRow[$blobName])) { |
| $subtable = DataTable::fromSerializedArray($blobRow[$blobName]); |
| $subtable->setMetadata(self::TABLE_METADATA_PERIOD_INDEX, $dataTable->getMetadata(self::TABLE_METADATA_PERIOD_INDEX)); |
| $subtable->setMetadata(self::TABLE_METADATA_SITE_INDEX, $dataTable->getMetadata(self::TABLE_METADATA_SITE_INDEX)); |
| $subtable->setMetadata(self::TABLE_METADATA_SEGMENT_INDEX, $dataTable->getMetadata(self::TABLE_METADATA_SEGMENT_INDEX)); |
| $subtable->setMetadata(self::TABLE_METADATA_SEGMENT_PRETTY_INDEX, $dataTable->getMetadata(self::TABLE_METADATA_SEGMENT_PRETTY_INDEX)); |
| $subtable->setMetadata(DataTable::ARCHIVED_DATE_METADATA_NAME, $dataTable->getMetadata(DataTable::ARCHIVED_DATE_METADATA_NAME)); |
|
|
| $this->setSubtables($subtable, $blobRow, $treeLevel + 1); |
|
|
| |
| |
| if ($this->addMetadataSubtableId) { |
| |
| |
| $row->addMetadata('idsubdatatable_in_db', $row->getIdSubDataTable()); |
| } |
|
|
| $row->setSubtable($subtable); |
| } |
| } |
| } |
|
|
| private function getDefaultMetadata() |
| { |
| return array( |
| DataTableFactory::TABLE_METADATA_SITE_INDEX => new Site(reset($this->sitesId)), |
| DataTableFactory::TABLE_METADATA_PERIOD_INDEX => reset($this->periods), |
| DataTableFactory::TABLE_METADATA_SEGMENT_INDEX => $this->segment->getString(), |
| DataTableFactory::TABLE_METADATA_SEGMENT_PRETTY_INDEX => $this->segment->getString(), |
| ); |
| } |
|
|
| public function getTableMetadataFor($idSite, $period) |
| { |
| return [ |
| DataTableFactory::TABLE_METADATA_SITE_INDEX => new Site($idSite), |
| DataTableFactory::TABLE_METADATA_PERIOD_INDEX => $period, |
| DataTableFactory::TABLE_METADATA_SEGMENT_INDEX => $this->segment->getString(), |
| DataTableFactory::TABLE_METADATA_SEGMENT_PRETTY_INDEX => $this->segment->getString(), |
| ]; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| private function prettifyIndexLabel($labelType, $label) |
| { |
| if ($labelType == self::TABLE_METADATA_PERIOD_INDEX) { |
| $period = $this->periods[$label]; |
| $label = $period->getLabel(); |
| if ($label === 'week' || $label === 'range') { |
| return $period->getRangeString(); |
| } |
|
|
| return $period->getPrettyString(); |
| } |
| return $label; |
| } |
|
|
| |
| |
| |
| |
| private function makeFromMetricsArray($data, $keyMetadata) |
| { |
| $table = new DataTable\Simple(); |
|
|
| if (!empty($data)) { |
| $table->setAllTableMetadata(array_merge($table->getAllTableMetadata(), DataCollection::getDataRowMetadata($data), $keyMetadata)); |
| $this->setPrettySegmentMetadata($table); |
|
|
| DataCollection::removeMetadataFromDataRow($data); |
|
|
| $table->addRow(new Row(array(Row::COLUMNS => $data))); |
| } else { |
| |
| |
| |
| |
| |
| if ( |
| count($this->dataNames) == 1 |
| && $this->isNumericDataType() |
| ) { |
| $name = reset($this->dataNames); |
| $table->addRow(new Row(array(Row::COLUMNS => array($name => 0)))); |
| } |
|
|
| $table->setAllTableMetadata(array_merge($table->getAllTableMetadata(), $keyMetadata)); |
| $this->setPrettySegmentMetadata($table); |
| } |
|
|
| $result = $table; |
| return $result; |
| } |
|
|
| private function makeMergedTableWithPeriodAndSiteIndex($index, $resultIndices, $useSimpleDataTable, $isNumeric) |
| { |
| $map = new DataTable\Map(); |
| $map->setKeyName($resultIndices[self::TABLE_METADATA_PERIOD_INDEX]); |
|
|
| |
| $tables = array(); |
|
|
| foreach ($this->periods as $range => $period) { |
| |
| |
| $metadata = array(self::TABLE_METADATA_PERIOD_INDEX => $period); |
|
|
| if ($useSimpleDataTable) { |
| $table = new DataTable\Simple(); |
| } else { |
| $table = new DataTable(); |
| } |
|
|
| $table->setAllTableMetadata(array_merge($table->getAllTableMetadata(), $metadata)); |
| $this->setPrettySegmentMetadata($table); |
| $map->addTable($table, $this->prettifyIndexLabel(self::TABLE_METADATA_PERIOD_INDEX, $range)); |
|
|
| $tables[$range] = $table; |
| } |
|
|
| foreach ($index as $idsite => $table) { |
| $rowMeta = array('idsite' => $idsite); |
|
|
| foreach ($table as $range => $row) { |
| if (!empty($row)) { |
| $tables[$range]->addRow(new Row(array( |
| Row::COLUMNS => $row, |
| Row::METADATA => $rowMeta))); |
| } elseif ($isNumeric) { |
| $tables[$range]->addRow(new Row(array( |
| Row::COLUMNS => $this->defaultRow, |
| Row::METADATA => $rowMeta))); |
| } |
| } |
| } |
|
|
| return $map; |
| } |
|
|
| private function makeMergedWithSiteIndex($index, $useSimpleDataTable, $isNumeric) |
| { |
| if ($useSimpleDataTable) { |
| $table = new DataTable\Simple(); |
| } else { |
| $table = new DataTable(); |
| } |
|
|
| $table->setAllTableMetadata(array(DataTableFactory::TABLE_METADATA_PERIOD_INDEX => reset($this->periods))); |
| $this->setPrettySegmentMetadata($table); |
|
|
| foreach ($index as $idsite => $row) { |
| $meta = array(); |
| if (isset($row[DataCollection::METADATA_CONTAINER_ROW_KEY])) { |
| $meta = $row[DataCollection::METADATA_CONTAINER_ROW_KEY]; |
| } |
| $meta['idsite'] = $idsite; |
|
|
| if (!empty($row)) { |
| $table->addRow(new Row(array( |
| Row::COLUMNS => $row, |
| Row::METADATA => $meta))); |
| } elseif ($isNumeric) { |
| $table->addRow(new Row(array( |
| Row::COLUMNS => $this->defaultRow, |
| Row::METADATA => $meta))); |
| } |
| } |
|
|
| return $table; |
| } |
|
|
| private function setPrettySegmentMetadata(DataTable $table) |
| { |
| $site = $table->getMetadata(self::TABLE_METADATA_SITE_INDEX); |
| $idSite = $site ? $site->getId() : false; |
|
|
| $segmentPretty = $this->segment->getStoredSegmentName($idSite); |
|
|
| $table->setMetadata('segment', $this->segment->getString()); |
| $table->setMetadata('segmentPretty', $segmentPretty); |
| } |
| } |
|
|