self::ITEMS_SKU_RECORD_NAME, self::NAME_FIELD => self::ITEMS_NAME_RECORD_NAME, self::CATEGORY_FIELD => self::ITEMS_CATEGORY_RECORD_NAME ); /** * Array containing one DataArray for each Ecommerce items dimension (name/sku/category abandoned carts and orders) * @var array */ protected $itemReports = array(); public function aggregateDayReport() { $this->aggregateGeneralGoalMetrics(); $this->aggregateEcommerceItems(); $this->getProcessor()->processDependentArchive('Goals', VisitFrequencyAPI::NEW_VISITOR_SEGMENT); $this->getProcessor()->processDependentArchive('Goals', VisitFrequencyAPI::RETURNING_VISITOR_SEGMENT); } protected function aggregateGeneralGoalMetrics() { $prefixes = array( self::VISITS_UNTIL_RECORD_NAME => 'vcv', self::DAYS_UNTIL_CONV_RECORD_NAME => 'vdsf', ); $selects = array(); $selects = array_merge($selects, LogAggregator::getSelectsFromRangedColumn( self::VISITS_COUNT_FIELD, self::$visitCountRanges, self::LOG_CONVERSION_TABLE, $prefixes[self::VISITS_UNTIL_RECORD_NAME] )); $selects = array_merge($selects, LogAggregator::getSelectsFromRangedColumn( self::DAYS_SINCE_FIRST_VISIT_FIELD, self::$daysToConvRanges, self::LOG_CONVERSION_TABLE, $prefixes[self::DAYS_UNTIL_CONV_RECORD_NAME] )); $query = $this->getLogAggregator()->queryConversionsByDimension(array(), false, $selects); if ($query === false) { return; } $totalConversions = $totalRevenue = 0; $goals = new DataArray(); $visitsToConversions = $daysToConversions = array(); $conversionMetrics = $this->getLogAggregator()->getConversionsMetricFields(); while ($row = $query->fetch()) { $idGoal = $row['idgoal']; unset($row['idgoal']); unset($row['label']); $values = array(); foreach ($conversionMetrics as $field => $statement) { $values[$field] = $row[$field]; } $goals->sumMetrics($idGoal, $values); if (empty($visitsToConversions[$idGoal])) { $visitsToConversions[$idGoal] = new DataTable(); } $array = LogAggregator::makeArrayOneColumn($row, Metrics::INDEX_NB_CONVERSIONS, $prefixes[self::VISITS_UNTIL_RECORD_NAME]); $visitsToConversions[$idGoal]->addDataTable(DataTable::makeFromIndexedArray($array)); if (empty($daysToConversions[$idGoal])) { $daysToConversions[$idGoal] = new DataTable(); } $array = LogAggregator::makeArrayOneColumn($row, Metrics::INDEX_NB_CONVERSIONS, $prefixes[self::DAYS_UNTIL_CONV_RECORD_NAME]); $daysToConversions[$idGoal]->addDataTable(DataTable::makeFromIndexedArray($array)); // We don't want to sum Abandoned cart metrics in the overall revenue/conversions/converted visits // since it is a "negative conversion" if ($idGoal != GoalManager::IDGOAL_CART) { $totalConversions += $row[Metrics::INDEX_GOAL_NB_CONVERSIONS]; $totalRevenue += $row[Metrics::INDEX_GOAL_REVENUE]; } } // Stats by goal, for all visitors $numericRecords = $this->getConversionsNumericMetrics($goals); $this->getProcessor()->insertNumericRecords($numericRecords); $this->insertReports(self::VISITS_UNTIL_RECORD_NAME, $visitsToConversions); $this->insertReports(self::DAYS_UNTIL_CONV_RECORD_NAME, $daysToConversions); // Stats for all goals $nbConvertedVisits = $this->getProcessor()->getNumberOfVisitsConverted(); $metrics = array( self::getRecordName('nb_conversions') => $totalConversions, self::getRecordName('nb_visits_converted') => $nbConvertedVisits, self::getRecordName('revenue') => $totalRevenue, ); $this->getProcessor()->insertNumericRecords($metrics); } protected function getConversionsNumericMetrics(DataArray $goals) { $numericRecords = array(); $goals = $goals->getDataArray(); foreach ($goals as $idGoal => $array) { foreach ($array as $metricId => $value) { $metricName = Metrics::$mappingFromIdToNameGoal[$metricId]; $recordName = self::getRecordName($metricName, $idGoal); $numericRecords[$recordName] = $value; } } return $numericRecords; } /** * @param string $recordName 'nb_conversions' * @param int|bool $idGoal idGoal to return the metrics for, or false to return overall * @return string Archive record name */ public static function getRecordName($recordName, $idGoal = false) { $idGoalStr = ''; if ($idGoal !== false) { $idGoalStr = $idGoal . "_"; } return 'Goal_' . $idGoalStr . $recordName; } protected function insertReports($recordName, $visitsToConversions) { foreach ($visitsToConversions as $idGoal => $table) { $record = self::getRecordName($recordName, $idGoal); $this->getProcessor()->insertBlobRecord($record, $table->getSerialized()); } $overviewTable = $this->getOverviewFromGoalTables($visitsToConversions); $this->getProcessor()->insertBlobRecord(self::getRecordName($recordName), $overviewTable->getSerialized()); } protected function getOverviewFromGoalTables($tableByGoal) { $overview = new DataTable(); foreach ($tableByGoal as $idGoal => $table) { if ($this->isStandardGoal($idGoal)) { $overview->addDataTable($table); } } return $overview; } protected function isStandardGoal($idGoal) { return !in_array($idGoal, $this->getEcommerceIdGoals()); } protected function aggregateEcommerceItems() { $this->initItemReports(); foreach ($this->getItemsDimensions() as $dimension) { $query = $this->getLogAggregator()->queryEcommerceItems($dimension); if ($query == false) { continue; } $this->aggregateFromEcommerceItems($query, $dimension); } $this->insertItemReports(); return true; } protected function initItemReports() { foreach ($this->getEcommerceIdGoals() as $ecommerceType) { foreach ($this->dimensionRecord as $dimension => $record) { $this->itemReports[$dimension][$ecommerceType] = new DataArray(); } } } protected function insertItemReports() { /** @var DataArray $array */ foreach ($this->itemReports as $dimension => $itemAggregatesByType) { foreach ($itemAggregatesByType as $ecommerceType => $itemAggregate) { $recordName = $this->dimensionRecord[$dimension]; if ($ecommerceType == GoalManager::IDGOAL_CART) { $recordName = self::getItemRecordNameAbandonedCart($recordName); } $table = $itemAggregate->asDataTable(); $this->getProcessor()->insertBlobRecord($recordName, $table->getSerialized()); } } } protected function getItemsDimensions() { $dimensions = array_keys($this->dimensionRecord); foreach ($this->getItemExtraCategories() as $category) { $dimensions[] = $category; } return $dimensions; } protected function getItemExtraCategories() { return array(self::CATEGORY2_FIELD, self::CATEGORY3_FIELD, self::CATEGORY4_FIELD, self::CATEGORY5_FIELD); } protected function isItemExtraCategory($field) { return in_array($field, $this->getItemExtraCategories()); } protected function aggregateFromEcommerceItems($query, $dimension) { while ($row = $query->fetch()) { $ecommerceType = $row['ecommerceType']; $label = $this->cleanupRowGetLabel($row, $dimension); if ($label === false) { continue; } // Aggregate extra categories in the Item categories array if ($this->isItemExtraCategory($dimension)) { $array = $this->itemReports[self::CATEGORY_FIELD][$ecommerceType]; } else { $array = $this->itemReports[$dimension][$ecommerceType]; } $this->roundColumnValues($row); $array->sumMetrics($label, $row); } } protected function cleanupRowGetLabel(&$row, $currentField) { $label = $row['label']; if (empty($label)) { // An empty additional category -> skip this iteration if ($this->isItemExtraCategory($currentField)) { return false; } $label = "Value not defined"; // Product Name/Category not defined" if (\Piwik\Plugin\Manager::getInstance()->isPluginActivated('CustomVariables')) { $label = \Piwik\Plugins\CustomVariables\Archiver::LABEL_CUSTOM_VALUE_NOT_DEFINED; } } if ($row['ecommerceType'] == GoalManager::IDGOAL_CART) { // abandoned carts are the numner of visits with an abandoned cart $row[Metrics::INDEX_ECOMMERCE_ORDERS] = $row[Metrics::INDEX_NB_VISITS]; } unset($row[Metrics::INDEX_NB_VISITS]); unset($row['label']); unset($row['labelIdAction']); unset($row['ecommerceType']); return $label; } protected function roundColumnValues(&$row) { $columnsToRound = array( Metrics::INDEX_ECOMMERCE_ITEM_REVENUE, Metrics::INDEX_ECOMMERCE_ITEM_QUANTITY, Metrics::INDEX_ECOMMERCE_ITEM_PRICE, Metrics::INDEX_ECOMMERCE_ITEM_PRICE_VIEWED, ); foreach ($columnsToRound as $column) { if (isset($row[$column]) && $row[$column] == round($row[$column]) ) { $row[$column] = round($row[$column]); } } } protected function getEcommerceIdGoals() { return array(GoalManager::IDGOAL_CART, GoalManager::IDGOAL_ORDER); } public static function getItemRecordNameAbandonedCart($recordName) { return $recordName . '_Cart'; } /** * @internal param $this->getProcessor() */ public function aggregateMultipleReports() { /* * Archive Ecommerce Items */ $dataTableToSum = $this->dimensionRecord; foreach ($this->dimensionRecord as $recordName) { $dataTableToSum[] = self::getItemRecordNameAbandonedCart($recordName); } $columnsAggregationOperation = null; $this->getProcessor()->aggregateDataTableRecords($dataTableToSum, $maximumRowsInDataTableLevelZero = null, $maximumRowsInSubDataTable = null, $columnToSortByBeforeTruncation = null, $columnsAggregationOperation, $columnsToRenameAfterAggregation = null, $countRowsRecursive = array()); /* * Archive General Goal metrics */ $goalIdsToSum = GoalManager::getGoalIds($this->getProcessor()->getParams()->getSite()->getId()); //Ecommerce $goalIdsToSum[] = GoalManager::IDGOAL_ORDER; $goalIdsToSum[] = GoalManager::IDGOAL_CART; //bug here if idgoal=1 // Overall goal metrics $goalIdsToSum[] = false; $fieldsToSum = array(); foreach ($goalIdsToSum as $goalId) { $metricsToSum = Goals::getGoalColumns($goalId); foreach ($metricsToSum as $metricName) { $fieldsToSum[] = self::getRecordName($metricName, $goalId); } } $this->getProcessor()->aggregateNumericMetrics($fieldsToSum); $columnsAggregationOperation = null; foreach ($goalIdsToSum as $goalId) { // sum up the visits to conversion data table & the days to conversion data table $this->getProcessor()->aggregateDataTableRecords( array(self::getRecordName(self::VISITS_UNTIL_RECORD_NAME, $goalId), self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME, $goalId)), $maximumRowsInDataTableLevelZero = null, $maximumRowsInSubDataTable = null, $columnToSortByBeforeTruncation = null, $columnsAggregationOperation, $columnsToRenameAfterAggregation = null, $countRowsRecursive = array()); } $columnsAggregationOperation = null; // sum up goal overview reports $this->getProcessor()->aggregateDataTableRecords( array(self::getRecordName(self::VISITS_UNTIL_RECORD_NAME), self::getRecordName(self::DAYS_UNTIL_CONV_RECORD_NAME)), $maximumRowsInDataTableLevelZero = null, $maximumRowsInSubDataTable = null, $columnToSortByBeforeTruncation = null, $columnsAggregationOperation, $columnsToRenameAfterAggregation = null, $countRowsRecursive = array()); $this->getProcessor()->processDependentArchive('Goals', VisitFrequencyAPI::NEW_VISITOR_SEGMENT); $this->getProcessor()->processDependentArchive('Goals', VisitFrequencyAPI::RETURNING_VISITOR_SEGMENT); } }