/** Build DataTable from array and archive it */ private function archiveDataArray($keyword, &$data) { $dataTable = new Piwik_DataTable(); foreach ($data as &$row) { $dataTable->addRow(new Piwik_SiteUsers_ExtendedDataTableRow(array(Piwik_DataTable_Row::COLUMNS => $row))); } $name = 'SiteUsers_' . $keyword; $this->archiveProcessing->insertBlobRecord($name, $dataTable->getSerialized()); destroy($dataTable); }
function archiveDay($notification) { /** * @var Piwik_ArchiveProcessing_Day */ $archiveProcessing = $notification->getNotificationObject(); $funnelDefinitions = Piwik_Funnels_API::getInstance()->getFunnels($archiveProcessing->idsite); $funnelDefinitions = $this->initializeStepData($funnelDefinitions); list($funnelDefinitions, $total) = $this->storeRefAndNextUrls($funnelDefinitions, $archiveProcessing); // Add the calculations of dropout foreach ($funnelDefinitions as $funnelDefinition) { $last_index = count($funnelDefinition['steps']) - 1; $idFunnel = $funnelDefinition['idfunnel']; $idGoal = $funnelDefinition['idgoal']; // get the goal conversions grouped by the converting action $goal_query = $archiveProcessing->queryConversionsBySegment('idaction_url'); $goalConversions = array(); while ($row = $goal_query->fetch()) { if ($row['idgoal'] == $idGoal) { $goalConversions[$row['idaction_url']] = $row['nb_conversions']; } } for ($i = 0; $i <= $last_index; $i++) { $current_step =& $funnelDefinition['steps'][$i]; $idStep = $current_step['idstep']; // record number of actions for the step $recordName = Piwik_Funnels::getRecordName('nb_actions', $idFunnel, $idStep); $archiveProcessing->insertNumericRecord($recordName, $current_step['nb_actions']); # Remove the previous step urls from the idaction_url_ref array and add their actions to the # count of actions moving from the previous step to this one if ($i > 0) { $previous_step = $funnelDefinition['steps'][$i - 1]; $nb_prev_step_actions = 0; foreach ($previous_step['idaction_url'] as $key => $value) { if (isset($current_step['idaction_url_ref'][$key])) { $nb_prev_step_actions += $current_step['idaction_url_ref'][$key]['value']; unset($current_step['idaction_url_ref'][$key]); } } $recordName = Piwik_Funnels::getRecordName('nb_next_step_actions', $idFunnel, $previous_step['idstep']); $archiveProcessing->insertNumericRecord($recordName, $nb_prev_step_actions); // calculate a percent of people continuing from the previous step to this $recordName = Piwik_Funnels::getRecordName('percent_next_step_actions', $idFunnel, $previous_step['idstep']); $archiveProcessing->insertNumericRecord($recordName, $this->percent($nb_prev_step_actions, $previous_step['nb_actions'])); } if ($i < $last_index) { $next_step = $funnelDefinition['steps'][$i + 1]; # Remove this step's next actions that are actions for the next funnel step foreach ($current_step['idaction_url_next'] as $key => $value) { if (isset($next_step['idaction_url'][$key])) { unset($current_step['idaction_url_next'][$key]); } } # Archive the next urls that aren't funnel steps $idActionNext = new Piwik_DataTable(); $exitCount = 0; foreach ($current_step['idaction_url_next'] as $id => $data) { $idActionNext->addRowFromSimpleArray($data); $exitCount += $data['value']; } $recordName = Piwik_Funnels::getRecordName('idaction_url_next', $idFunnel, $idStep); $archiveProcessing->insertBlobRecord($recordName, $idActionNext->getSerialized()); destroy($idActionNext); # and a sum of exit actions $recordName = Piwik_Funnels::getRecordName('nb_exit', $idFunnel, $idStep); $archiveProcessing->insertNumericRecord($recordName, $exitCount); } // Archive the referring urls that aren't funnel steps $idActionRef = new Piwik_DataTable(); $entryCount = 0; foreach ($current_step['idaction_url_ref'] as $id => $data) { $idActionRef->addRowFromSimpleArray($data); $entryCount += $data['value']; } $recordName = Piwik_Funnels::getRecordName('idaction_url_ref', $idFunnel, $idStep); $archiveProcessing->insertBlobRecord($recordName, $idActionRef->getSerialized()); destroy($idActionRef); # and a sum of entry actions $recordName = Piwik_Funnels::getRecordName('nb_entry', $idFunnel, $idStep); $archiveProcessing->insertNumericRecord($recordName, $entryCount); } // For the last step, the comparison is the goal itself $last_step = $funnelDefinition['steps'][$last_index]; $idStep = $last_step['idstep']; $recordName = Piwik_Funnels::getRecordName('nb_next_step_actions', $idFunnel, $idStep); $nb_goal_actions = 0; foreach ($goalConversions as $key => $value) { if (isset($last_step['idaction_url_next'][$key])) { $nb_goal_actions += $last_step['idaction_url_next'][$key]['value']; unset($last_step['idaction_url_next'][$key]); } } $archiveProcessing->insertNumericRecord($recordName, $nb_goal_actions); $recordName = Piwik_Funnels::getRecordName('percent_next_step_actions', $idFunnel, $idStep); $archiveProcessing->insertNumericRecord($recordName, $this->percent($nb_goal_actions, $last_step['nb_actions'])); # Archive the next urls that aren't funnel steps $idActionNext = new Piwik_DataTable(); $exitCount = 0; foreach ($last_step['idaction_url_next'] as $id => $data) { $idActionNext->addRowFromSimpleArray($data); $exitCount += $data['value']; } $recordName = Piwik_Funnels::getRecordName('idaction_url_next', $idFunnel, $idStep); $archiveProcessing->insertBlobRecord($recordName, $idActionNext->getSerialized()); destroy($idActionNext); # and a sum of exit actions $recordName = Piwik_Funnels::getRecordName('nb_exit', $idFunnel, $idStep); $archiveProcessing->insertNumericRecord($recordName, $exitCount); // Archive the total funnel actions $recordName = Piwik_Funnels::getRecordName('nb_actions', $idFunnel, false); $archiveProcessing->insertNumericRecord($recordName, $total); // What percent of people who visited the first funnel step converted at the end of the funnel? $recordName = Piwik_Funnels::getRecordName('conversion_rate', $idFunnel, false); $archiveProcessing->insertNumericRecord($recordName, $this->percent($nb_goal_actions, $funnelDefinition['steps'][0]['nb_actions'])); } }
/** * @group Core * @group DataTable */ public function testGetSerializedCallsCleanPostSerialize() { $mockedDataTableRow = $this->getMock('Piwik_DataTable_Row', array('cleanPostSerialize')); $mockedDataTableRow->expects($this->once())->method('cleanPostSerialize'); $dataTableBeingSerialized = new Piwik_DataTable(); $dataTableBeingSerialized->addRow($mockedDataTableRow); $dataTableBeingSerialized->getSerialized(); }
/** * General tests that tries to test the normal behaviour of DataTable * * We create some tables, add rows, some of the rows link to sub tables * * Then we serialize everything, and we check that the unserialize give the same object back */ function test_general() { /* * create some fake tables to make sure that the serialized array of the first TABLE * does not take in consideration those tables */ $useless1 = new Piwik_DataTable(); $useless1->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array(13))); /* * end fake tables */ /* * MAIN TABLE */ $table = new Piwik_DataTable(); $subtable = new Piwik_DataTable(); $idtable = $table->getId(); $idsubtable = $subtable->getId(); /* * create some fake tables to make sure that the serialized array of the first TABLE * does not take in consideration those tables * -> we check that the DataTable_Manager is not impacting DataTable */ $useless2 = new Piwik_DataTable(); $useless1->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array(8487))); $useless3 = new Piwik_DataTable(); $useless3->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array(8487))); /* * end fake tables */ $row = array(Piwik_DataTable_Row::COLUMNS => array(0 => 1554, 1 => 42, 2 => 657, 3 => 155744), Piwik_DataTable_Row::METADATA => array('logo' => 'test.png')); $row = new Piwik_DataTable_Row($row); $table->addRow($row); $table->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array(0 => 1554, 1 => 42), Piwik_DataTable_Row::METADATA => array('url' => 'piwik.org'))); $table->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array(0 => 787877888787), Piwik_DataTable_Row::METADATA => array('url' => 'OUPLA ADDED'), Piwik_DataTable_Row::DATATABLE_ASSOCIATED => $subtable)); /* * SUB TABLE */ $row = array(Piwik_DataTable_Row::COLUMNS => array(0 => 1554), Piwik_DataTable_Row::METADATA => array('searchengine' => 'google')); $subtable->addRowFromArray($row); $row = array(Piwik_DataTable_Row::COLUMNS => array(0 => 84894), Piwik_DataTable_Row::METADATA => array('searchengine' => 'yahoo')); $subtable->addRowFromArray($row); $row = array(Piwik_DataTable_Row::COLUMNS => array(0 => 4898978989), Piwik_DataTable_Row::METADATA => array('searchengine' => 'ask')); $subtable->addRowFromArray($row); /* * SUB SUB TABLE */ $subsubtable = new Piwik_DataTable(); $subsubtable->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array(245), Piwik_DataTable_Row::METADATA => array('yes' => 'subsubmetadata1'))); $subsubtable->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array(13), Piwik_DataTable_Row::METADATA => array('yes' => 'subsubmetadata2'))); $row = array(Piwik_DataTable_Row::COLUMNS => array(0 => 666666666666666), Piwik_DataTable_Row::METADATA => array('url' => 'NEW ROW ADDED'), Piwik_DataTable_Row::DATATABLE_ASSOCIATED => $subsubtable); $subtable->addRowFromArray($row); $idsubsubtable = $subsubtable->getId(); $serialized = $table->getSerialized(); $this->assertEqual(array_keys($serialized), array($idsubsubtable, $idsubtable, 0)); $tableAfter = new Piwik_DataTable(); $tableAfter->addRowsFromSerializedArray($serialized[0]); $this->assertEqual($table->getRows(), $tableAfter->getRows()); $subsubtableAfter = new Piwik_DataTable(); $subsubtableAfter->addRowsFromSerializedArray($serialized[$idsubsubtable]); $this->assertEqual($subsubtable->getRows(), $subsubtableAfter->getRows()); $this->assertEqual($table, Piwik_DataTable_Manager::getInstance()->getTable($idtable)); $this->assertEqual($subsubtable, Piwik_DataTable_Manager::getInstance()->getTable($idsubsubtable)); }
/** * @param Piwik_ArchiveProcessing_Day $archiveProcessing */ function archiveGeneralGoalMetrics($archiveProcessing) { // extra aggregate selects for the visits to conversion report $visitToConvExtraCols = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect('visitor_count_visits', self::$visitCountRanges, 'log_conversion', 'vcv'); // extra aggregate selects for the days to conversion report $daysToConvExtraCols = Piwik_ArchiveProcessing_Day::buildReduceByRangeSelect('visitor_days_since_first', self::$daysToConvRanges, 'log_conversion', 'vdsf'); $query = $archiveProcessing->queryConversionsByDimension(array(), '', array_merge($visitToConvExtraCols, $daysToConvExtraCols)); if ($query === false) { return; } $goals = array(); $visitsToConvReport = array(); $daysToConvReport = array(); // Get a standard empty goal row $overall = $archiveProcessing->getNewGoalRow($idGoal = 1); while ($row = $query->fetch()) { $idgoal = $row['idgoal']; if (!isset($goals[$idgoal])) { $goals[$idgoal] = $archiveProcessing->getNewGoalRow($idgoal); $visitsToConvReport[$idgoal] = new Piwik_DataTable(); $daysToConvReport[$idgoal] = new Piwik_DataTable(); } $archiveProcessing->updateGoalStats($row, $goals[$idgoal]); // We don't want to sum Abandoned cart metrics in the overall revenue/conversions/converted visits // since it is a "negative conversion" if ($idgoal != Piwik_Tracker_GoalManager::IDGOAL_CART) { $archiveProcessing->updateGoalStats($row, $overall); } // map the goal + visit number of a visitor with the # of conversions that happened on that visit $table = $archiveProcessing->getSimpleDataTableFromRow($row, Piwik_Archive::INDEX_NB_CONVERSIONS, 'vcv'); $visitsToConvReport[$idgoal]->addDataTable($table); // map the goal + day number of a visit with the # of conversion that happened on that day $table = $archiveProcessing->getSimpleDataTableFromRow($row, Piwik_Archive::INDEX_NB_CONVERSIONS, 'vdsf'); $daysToConvReport[$idgoal]->addDataTable($table); } // these data tables hold reports for every goal of a site $visitsToConvOverview = new Piwik_DataTable(); $daysToConvOverview = new Piwik_DataTable(); // Stats by goal, for all visitors foreach ($goals as $idgoal => $values) { foreach ($values as $metricId => $value) { $metricName = Piwik_Archive::$mappingFromIdToNameGoal[$metricId]; $recordName = self::getRecordName($metricName, $idgoal); $archiveProcessing->insertNumericRecord($recordName, $value); } $conversion_rate = $this->getConversionRate($values[Piwik_Archive::INDEX_GOAL_NB_VISITS_CONVERTED], $archiveProcessing); $recordName = self::getRecordName('conversion_rate', $idgoal); $archiveProcessing->insertNumericRecord($recordName, $conversion_rate); // if the goal is not a special goal (like ecommerce) add it to the overview report if ($idgoal !== Piwik_Tracker_GoalManager::IDGOAL_CART && $idgoal !== Piwik_Tracker_GoalManager::IDGOAL_ORDER) { $visitsToConvOverview->addDataTable($visitsToConvReport[$idgoal]); $daysToConvOverview->addDataTable($daysToConvReport[$idgoal]); } // visit count until conversion stats $archiveProcessing->insertBlobRecord(self::getRecordName(self::VISITS_UNTIL_RECORD_NAME, $idgoal), $visitsToConvReport[$idgoal]->getSerialized()); // day count until conversion stats $archiveProcessing->insertBlobRecord(self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME, $idgoal), $daysToConvReport[$idgoal]->getSerialized()); } // archive overview reports $archiveProcessing->insertBlobRecord(self::getRecordName(self::VISITS_UNTIL_RECORD_NAME), $visitsToConvOverview->getSerialized()); $archiveProcessing->insertBlobRecord(self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME), $daysToConvOverview->getSerialized()); // Stats for all goals $totalAllGoals = array(self::getRecordName('conversion_rate') => $this->getConversionRate($archiveProcessing->getNumberOfVisitsConverted(), $archiveProcessing), self::getRecordName('nb_conversions') => $overall[Piwik_Archive::INDEX_GOAL_NB_CONVERSIONS], self::getRecordName('nb_visits_converted') => $archiveProcessing->getNumberOfVisitsConverted(), self::getRecordName('revenue') => $overall[Piwik_Archive::INDEX_GOAL_REVENUE]); foreach ($totalAllGoals as $recordName => $value) { $archiveProcessing->insertNumericRecord($recordName, $value); } }
/** * Helper function that returns the serialized DataTable of the given PHP array. * The array must have the format of Piwik_DataTable::addRowsFromArrayWithIndexLabel() * Example: array ( * LABEL => array(col1 => X, col2 => Y), * LABEL2 => array(col1 => X, col2 => Y), * ) * * @param array $array at the given format * @return array Array with one element: the serialized data table string */ public function getDataTableSerialized($array) { $table = new Piwik_DataTable(); $table->addRowsFromArrayWithIndexLabel($array); $toReturn = $table->getSerialized(); return $toReturn; }
/** * Build DataTable from array and archive it * @return id of the datatable */ private function archiveDataArray($keyword, &$data, $addSearchTermMetaData = false, $addUrlMetaData = false) { $dataTable = new Piwik_DataTable(); foreach ($data as &$row) { $rowData = array(Piwik_DataTable_Row::COLUMNS => $row); if ($addSearchTermMetaData) { $rowData[Piwik_DataTable_Row::METADATA] = array('idSearch' => $row[$addSearchTermMetaData], 'searchTerm' => $row[self::SEARCH_TERM]); } if ($addUrlMetaData) { $rowData[Piwik_DataTable_Row::METADATA]['url'] = $row[self::URL]; } $dataTable->addRow(new Piwik_SiteSearch_ExtendedDataTableRow($rowData)); } $id = $dataTable->getId(); $name = 'SiteSearch_' . $keyword; $this->archiveProcessing->insertBlobRecord($name, $dataTable->getSerialized()); destroy($dataTable); return $id; }
/** * General tests that tries to test the normal behaviour of DataTable * * We create some tables, add rows, some of the rows link to sub tables * * Then we serialize everything, and we check that the unserialize give the same object back */ function test_general() { /* * create some fake tables to make sure that the serialized array of the first TABLE * does not take in consideration those tables */ $useless1 = new Piwik_DataTable(); $useless1->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array(13))); /* * end fake tables */ /* * MAIN TABLE */ $table = new Piwik_DataTable(); $subtable = new Piwik_DataTable(); $idtable = $table->getId(); $idsubtable = $subtable->getId(); /* * create some fake tables to make sure that the serialized array of the first TABLE * does not take in consideration those tables * -> we check that the DataTable_Manager is not impacting DataTable */ $useless2 = new Piwik_DataTable(); $useless1->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array(8487))); $useless3 = new Piwik_DataTable(); $useless3->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array(8487))); /* * end fake tables */ $row = array(Piwik_DataTable_Row::COLUMNS => array(0 => 1554, 1 => 42, 2 => 657, 3 => 155744), Piwik_DataTable_Row::METADATA => array('logo' => 'test.png')); $row = new Piwik_DataTable_Row($row); $table->addRow($row); $table->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array(0 => 1554, 1 => 42), Piwik_DataTable_Row::METADATA => array('url' => 'piwik.org'))); $table->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array(0 => 787877888787.0), Piwik_DataTable_Row::METADATA => array('url' => 'OUPLA ADDED'), Piwik_DataTable_Row::DATATABLE_ASSOCIATED => $subtable)); /* * SUB TABLE */ $row = array(Piwik_DataTable_Row::COLUMNS => array(0 => 1554), Piwik_DataTable_Row::METADATA => array('searchengine' => 'google')); $subtable->addRowFromArray($row); $row = array(Piwik_DataTable_Row::COLUMNS => array(0 => 84894), Piwik_DataTable_Row::METADATA => array('searchengine' => 'yahoo')); $subtable->addRowFromArray($row); $row = array(Piwik_DataTable_Row::COLUMNS => array(0 => 4898978989.0), Piwik_DataTable_Row::METADATA => array('searchengine' => 'ask')); $subtable->addRowFromArray($row); /* * SUB SUB TABLE */ $subsubtable = new Piwik_DataTable(); $subsubtable->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array(245), Piwik_DataTable_Row::METADATA => array('yes' => 'subsubmetadata1'))); $subsubtable->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array(13), Piwik_DataTable_Row::METADATA => array('yes' => 'subsubmetadata2'))); $row = array(Piwik_DataTable_Row::COLUMNS => array(0 => 666666666666666.0), Piwik_DataTable_Row::METADATA => array('url' => 'NEW ROW ADDED'), Piwik_DataTable_Row::DATATABLE_ASSOCIATED => $subsubtable); $subtable->addRowFromArray($row); $idsubsubtable = $subsubtable->getId(); $serialized = $table->getSerialized(); $this->assertEqual(array_keys($serialized), array($idsubsubtable, $idsubtable, 0)); // In the next test we compare an unserialized datatable with its original instance. // The unserialized datatable rows will have positive DATATABLE_ASSOCIATED ids. // Positive DATATABLE_ASSOCIATED ids mean that the associated sub-datatables are not loaded in memory. // In this case, this is NOT true: we know that the sub-datatable is loaded in memory. // HOWEVER, because of datatable id conflicts happening in the datatable manager, it is not yet // possible to know, after unserializing a datatable, if its sub-datatables are loaded in memory. $expectedTableRows = array(); foreach ($table->getRows() as $currentRow) { $expectedTableRow = clone $currentRow; $currentRowAssociatedDatatableId = $currentRow->c[Piwik_DataTable_Row::DATATABLE_ASSOCIATED]; if ($currentRowAssociatedDatatableId != null) { // making DATATABLE_ASSOCIATED ids positive $expectedTableRow->c[Piwik_DataTable_Row::DATATABLE_ASSOCIATED] = -1 * $currentRowAssociatedDatatableId; } $expectedTableRows[] = $expectedTableRow; } $tableAfter = new Piwik_DataTable(); $tableAfter->addRowsFromSerializedArray($serialized[0]); $this->assertEqual($expectedTableRows, $tableAfter->getRows()); $subsubtableAfter = new Piwik_DataTable(); $subsubtableAfter->addRowsFromSerializedArray($serialized[$idsubsubtable]); $this->assertEqual($subsubtable->getRows(), $subsubtableAfter->getRows()); $this->assertEqual($table, Piwik_DataTable_Manager::getInstance()->getTable($idtable)); $this->assertEqual($subsubtable, Piwik_DataTable_Manager::getInstance()->getTable($idsubsubtable)); }
/** * Utility function. Gets row count of a set of tables grouped by the 'name' column. * This is the implementation of the getRowCountsAndSizeBy... functions. */ private function getRowCountsByArchiveName($statuses, $getRowSizeMethod, $forceCache = false, $otherSelects = array(), $otherDataTableColumns = array()) { $extraCols = ''; if (!empty($otherSelects)) { $extraCols = ', ' . implode(', ', $otherSelects); } $cols = array_merge(array('row_count'), $otherDataTableColumns); $dataTable = new Piwik_DataTable(); foreach ($statuses as $status) { $dataTableOptionName = $this->getCachedOptionName($status['Name'], 'byArchiveName'); // if option exists && !$forceCache, use the cached data, otherwise create the $cachedData = Piwik_GetOption($dataTableOptionName); if ($cachedData !== false && !$forceCache) { $table = new Piwik_DataTable(); $table->addRowsFromSerializedArray($cachedData); } else { // otherwise, create data table & cache it $sql = "SELECT name as 'label', COUNT(*) as 'row_count'{$extraCols} FROM {$status['Name']} GROUP BY name"; $table = new Piwik_DataTable(); $table->addRowsFromSimpleArray(Piwik_FetchAll($sql)); $reduceArchiveRowName = array($this, 'reduceArchiveRowName'); $table->filter('GroupBy', array('label', $reduceArchiveRowName)); $serializedTables = $table->getSerialized(); $serializedTable = reset($serializedTables); Piwik_SetOption($dataTableOptionName, $serializedTable); } // add estimated_size column $getEstimatedSize = array($this, $getRowSizeMethod); $table->filter('ColumnCallbackAddColumn', array($cols, 'estimated_size', $getEstimatedSize, array($status))); $dataTable->addDataTable($table); destroy($table); } return $dataTable; }
/** * Helper function that returns the multiple serialized DataTable of the given PHP array. * The DataTable here associates a subtable to every row of the level 0 array. * This is used for example for search engines. Every search engine (level 0) has a subtable containing the * keywords. * * The $arrayLevel0 must have the format * Example: array ( * LABEL => array(col1 => X, col2 => Y), * LABEL2 => array(col1 => X, col2 => Y), * ) * * The $subArrayLevel1ByKey must have the format * Example: array( * LABEL => #Piwik_DataTable_ForLABEL, * LABEL2 => #Piwik_DataTable_ForLABEL2, * ) * * * @param array $arrayLevel0 * @param array of Piwik_DataTable $subArrayLevel1ByKey * @return array Array with N elements: the strings of the datatable serialized */ public function getDataTablesSerialized($arrayLevel0, $subArrayLevel1ByKey) { $tablesByLabel = array(); foreach ($arrayLevel0 as $label => $aAllRowsForThisLabel) { $table = new Piwik_DataTable(); $table->loadFromArrayLabelIsKey($aAllRowsForThisLabel); $tablesByLabel[$label] = $table; } $parentTableLevel0 = new Piwik_DataTable(); $parentTableLevel0->loadFromArrayLabelIsKey($subArrayLevel1ByKey, $tablesByLabel); // echo $parentTableLevel0; $toReturn = $parentTableLevel0->getSerialized(); return $toReturn; }