/** * This method can be used by subclasses to iterate over data tables that might be * data table arrays. It calls back the template method self::doManipulate for each table. * This way, data table arrays can be handled in a transparent fashion. * * @param Piwik_DataTable_Array|Piwik_DataTable $dataTable * @throws Exception * @return Piwik_DataTable_Array|Piwik_DataTable */ protected function manipulate($dataTable) { if ($dataTable instanceof Piwik_DataTable_Array) { $newTableArray = new Piwik_DataTable_Array(); $newTableArray->metadata = $dataTable->metadata; $newTableArray->setKeyName($dataTable->getKeyName()); foreach ($dataTable->getArray() as $date => $subTable) { // for period=week, the label is "2011-08-15 to 2011-08-21", which is // an invalid date parameter => only use the first date // in other languages the whole string does not start with the date so // we need to preg match it. if (!preg_match('/[0-9]{4}(-[0-9]{2})?(-[0-9]{2})?/', $date, $match)) { throw new Exception("Could not recognize date: {$date}"); } $dateForApiRequest = $match[0]; $subTable = $this->doManipulate($subTable, $dateForApiRequest); $newTableArray->addTable($subTable, $date); } return $newTableArray; } if ($dataTable instanceof Piwik_DataTable) { return $this->doManipulate($dataTable); } return $dataTable; }
/** Prepare metrics toggles with spark lines */ protected function getMetricsToggles($controller) { // calculate meta-metrics $subDataTables = $this->dataTable->getArray(); $firstDataTable = current($subDataTables); $firstDataTableRow = $firstDataTable->getFirstRow(); $lastDataTable = end($subDataTables); $lastDataTableRow = $lastDataTable->getFirstRow(); $maxValues = array(); $minValues = array(); foreach ($subDataTables as $subDataTable) { // $subDataTable is the report for one period, it has only one row $firstRow = $subDataTable->getFirstRow(); foreach ($this->availableMetrics as $metric => $label) { $value = $firstRow ? floatval($firstRow->getColumn($metric)) : 0; if (!isset($minValues[$metric]) || $minValues[$metric] > $value) { $minValues[$metric] = $value; } if (!isset($maxValues[$metric]) || $maxValues[$metric] < $value) { $maxValues[$metric] = $value; } } } $chart = new Piwik_Visualization_Chart_Evolution(); $colors = $chart->getSeriesColors(); // put together metric info $i = 0; $metrics = array(); foreach ($this->availableMetrics as $metric => $label) { if ($maxValues[$metric] == 0 && !$this instanceof Piwik_CoreHome_DataTableAction_MultiRowEvolution) { // series with only 0 cause trouble in js continue; } $first = $firstDataTableRow ? floatval($firstDataTableRow->getColumn($metric)) : 0; $last = $lastDataTableRow ? floatval($lastDataTableRow->getColumn($metric)) : 0; $changePercent = $first > 0 ? round($last / $first * 100 - 100) : 100; $changePercentHtml = $changePercent . '%'; if ($changePercent > 0) { $changePercentHtml = '+' . $changePercentHtml; $changeClass = 'up'; $changeImage = 'arrow_up'; } else { $changeClass = $changePercent < 0 ? 'down' : 'nochange'; $changeImage = $changePercent < 0 ? 'arrow_down' : false; } $changePercentHtml = '<span class="' . $changeClass . '">' . ($changeImage ? '<img src="plugins/MultiSites/images/' . $changeImage . '.png" /> ' : '') . $changePercentHtml . '</span>'; $details = Piwik_Translate('RowEvolution_MetricDetailsText', array($minValues[$metric], $maxValues[$metric], $changePercentHtml)); $color = $colors[$i % count($colors)]; $metrics[] = array('label' => $label, 'color' => $color, 'details' => $details, 'sparkline' => $this->getSparkline($metric, $controller)); $i++; } return $metrics; }
/** * Computes the output of the given array of data tables * * @param Piwik_DataTable_Array $tableArray data tables to render * @param string $prefix prefix to output before table data * @return string */ protected function renderDataTableArray(Piwik_DataTable_Array $tableArray, $prefix) { $output = "Piwik_DataTable_Array<hr />"; $prefix = $prefix . ' '; foreach ($tableArray->getArray() as $descTable => $table) { $output .= $prefix . "<b>" . $descTable . "</b><br />"; $output .= $prefix . $this->renderTable($table, $prefix . ' '); $output .= "<hr />"; } $output .= "Metadata<br />"; foreach ($tableArray->metadata as $id => $metadata) { $output .= "<br />"; $output .= $prefix . " <b>{$id}</b><br />"; foreach ($metadata as $name => $value) { $output .= $prefix . $prefix . "{$name} => {$value}"; } } $output .= "<hr />"; return $output; }
/** * Enhance a $dataTable using metadata : * * - remove metrics based on $reportMetadata['metrics'] * - add 0 valued metrics if $dataTable doesn't provide all $reportMetadata['metrics'] * - format metric values to a 'human readable' format * - extract row metadata to a separate Piwik_DataTable_Simple|Piwik_DataTable_Array : $rowsMetadata * - translate metric names to a separate array : $columns * * @param int $idSite enables monetary value formatting based on site currency * @param Piwik_DataTable|Piwik_DataTable_Array $dataTable * @param array $reportMetadata * @param boolean $hasDimension * @return array Piwik_DataTable_Simple|Piwik_DataTable_Array $newReport with human readable format & array $columns list of translated column names & Piwik_DataTable_Simple|Piwik_DataTable_Array $rowsMetadata **/ private function handleTableReport($idSite, $dataTable, &$reportMetadata, $hasDimension) { $columns = $reportMetadata['metrics']; if ($hasDimension) { $columns = array_merge(array('label' => $reportMetadata['dimension']), $columns); if (isset($reportMetadata['processedMetrics'])) { $processedMetricsAdded = $this->getDefaultProcessedMetrics(); foreach ($processedMetricsAdded as $processedMetricId => $processedMetricTranslation) { // this processed metric can be displayed for this report if (isset($reportMetadata['processedMetrics'][$processedMetricId])) { $columns[$processedMetricId] = $processedMetricTranslation; } } } // Display the global Goal metrics if (isset($reportMetadata['metricsGoal'])) { $metricsGoalDisplay = array('revenue'); // Add processed metrics to be displayed for this report foreach ($metricsGoalDisplay as $goalMetricId) { if (isset($reportMetadata['metricsGoal'][$goalMetricId])) { $columns[$goalMetricId] = $reportMetadata['metricsGoal'][$goalMetricId]; } } } if (isset($reportMetadata['processedMetrics'])) { // Add processed metrics $dataTable->filter('AddColumnsProcessedMetrics', array($deleteRowsWithNoVisit = false)); } } // $dataTable is an instance of Piwik_DataTable_Array when multiple periods requested if ($dataTable instanceof Piwik_DataTable_Array) { // Need a new Piwik_DataTable_Array to store the 'human readable' values $newReport = new Piwik_DataTable_Array(); $newReport->setKeyName("prettyDate"); $dataTableMetadata = $dataTable->metadata; $newReport->metadata = $dataTableMetadata; // Need a new Piwik_DataTable_Array to store report metadata $rowsMetadata = new Piwik_DataTable_Array(); $rowsMetadata->setKeyName("prettyDate"); // Process each Piwik_DataTable_Simple entry foreach ($dataTable->getArray() as $label => $simpleDataTable) { list($enhancedSimpleDataTable, $rowMetadata) = $this->handleSimpleDataTable($idSite, $simpleDataTable, $columns, $hasDimension); $period = $dataTableMetadata[$label]['period']->getLocalizedLongString(); $newReport->addTable($enhancedSimpleDataTable, $period); $rowsMetadata->addTable($rowMetadata, $period); } } else { list($newReport, $rowsMetadata) = $this->handleSimpleDataTable($idSite, $dataTable, $columns, $hasDimension); } return array($newReport, $columns, $rowsMetadata); }
/** * Utility function used by getAll. Performs a binary filter of two * DataTables in order to correctly calculate evolution metrics. * * @param Piwik_DataTable|Piwik_DataTable_Array $currentData * @param Piwik_DataTable|Piwik_DataTable_Array $pastData * @param array $fields The array of string fields to calculate evolution * metrics for. */ private function calculateEvolutionPercentages($currentData, $pastData, $fields) { if ($currentData instanceof Piwik_DataTable_Array) { $pastArray = $pastData->getArray(); foreach ($currentData->getArray() as $label => $subTable) { $this->calculateEvolutionPercentages($subTable, current($pastArray), $fields); next($pastArray); } } else { foreach ($fields as $field) { $currentData->filter('Piwik_MultiSites_CalculateEvolutionFilter', array($pastData, $this->evolutionColumnNames[$field], $field, $quotientPrecision = 2)); } } }
/** * Given the Row evolution dataTable, and the associated metadata, * enriches the metadata with min/max values, and % change between the first period and the last one * @param array $metadata * @param Piwik_DataTable_Array $dataTable */ private function enhanceRowEvolutionMetaData(&$metadata, $dataTable) { // prepare result array for metrics $metricsResult = array(); foreach ($metadata['metrics'] as $metric => $name) { $metricsResult[$metric] = array('name' => $name); if (!empty($metadata['logos'][$metric])) { $metricsResult[$metric]['logo'] = $metadata['logos'][$metric]; } } unset($metadata['logos']); $subDataTables = $dataTable->getArray(); $firstDataTable = current($subDataTables); $firstDataTableRow = $firstDataTable->getFirstRow(); $lastDataTable = end($subDataTables); $lastDataTableRow = $lastDataTable->getFirstRow(); // Process min/max values $firstNonZeroFound = array(); foreach ($subDataTables as $subDataTable) { // $subDataTable is the report for one period, it has only one row $firstRow = $subDataTable->getFirstRow(); foreach ($metadata['metrics'] as $metric => $label) { $value = $firstRow ? floatval($firstRow->getColumn($metric)) : 0; if ($value > 0) { $firstNonZeroFound[$metric] = true; } else { if (!isset($firstNonZeroFound[$metric])) { continue; } } if (!isset($metricsResult[$metric]['min']) || $metricsResult[$metric]['min'] > $value) { $metricsResult[$metric]['min'] = $value; } if (!isset($metricsResult[$metric]['max']) || $metricsResult[$metric]['max'] < $value) { $metricsResult[$metric]['max'] = $value; } } } // Process % change between first/last values foreach ($metadata['metrics'] as $metric => $label) { $first = $firstDataTableRow ? floatval($firstDataTableRow->getColumn($metric)) : 0; $last = $lastDataTableRow ? floatval($lastDataTableRow->getColumn($metric)) : 0; if ($first == 0) { continue; } else { $change = round($last / $first * 100 - 100); } if ($change > 0) { $change = '+' . $change; } $change = $change . '%'; $metricsResult[$metric]['change'] = $change; } $metadata['metrics'] = $metricsResult; }
/** * Performs a binary filter of two * DataTables in order to correctly calculate evolution metrics. * * @param Piwik_DataTable|Piwik_DataTable_Array $currentData * @param Piwik_DataTable|Piwik_DataTable_Array $pastData * @param array $fields The array of string fields to calculate evolution * metrics for. */ private function calculateEvolutionPercentages($currentData, $pastData, $apiMetrics) { if ($currentData instanceof Piwik_DataTable_Array) { $pastArray = $pastData->getArray(); foreach ($currentData->getArray() as $subTable) { $this->calculateEvolutionPercentages($subTable, current($pastArray), $apiMetrics); next($pastArray); } } else { foreach ($apiMetrics as $metricSettings) { $currentData->filter('Piwik_MultiSites_CalculateEvolutionFilter', array($pastData, $metricSettings[self::METRIC_EVOLUTION_COL_NAME_KEY], $metricSettings[self::METRIC_RECORD_NAME_KEY], $quotientPrecision = 2)); } } }
/** * Computes the output of the given data table array * * @param Piwik_DataTable_Array $table * @param array $allColumns * @return string */ protected function renderDataTableArray($table, &$allColumns = array()) { $str = ''; foreach ($table->getArray() as $currentLinePrefix => $dataTable) { $returned = explode("\n", $this->renderTable($dataTable, $allColumns)); // get rid of the columns names $returned = array_slice($returned, 1); // case empty datatable we dont print anything in the CSV export // when in xml we would output <result date="2008-01-15" /> if (!empty($returned)) { foreach ($returned as &$row) { $row = $currentLinePrefix . $this->separator . $row; } $str .= "\n" . implode("\n", $returned); } } // prepend table key to column list $allColumns = array_merge(array($table->getKeyName() => true), $allColumns); // add header to output string $str = $this->getHeaderLine(array_keys($allColumns)) . $str; return $str; }
public function getSiteSearchCategories($idSite, $period, $date, $segment = false) { Piwik_Actions::checkCustomVariablesPluginEnabled(); $customVariables = Piwik_CustomVariables_API::getInstance()->getCustomVariables($idSite, $period, $date, $segment, $expanded = false, $_leavePiwikCoreVariables = true); $customVarNameToLookFor = Piwik_Tracker_Action::CVAR_KEY_SEARCH_CATEGORY; $dataTable = new Piwik_DataTable(); // Handle case where date=last30&period=day // TODO: this logic should really be refactored somewhere, this is ugly! if ($customVariables instanceof Piwik_DataTable_Array) { $dataTable = new Piwik_DataTable_Array(); $dataTable->metadata = $customVariables->metadata; $dataTable->setKeyName($customVariables->getKeyName()); $customVariableDatatables = $customVariables->getArray(); $dataTables = $dataTable->getArray(); foreach ($customVariableDatatables as $key => $customVariableTableForDate) { // we do not enter the IF, in the case idSite=1,3 AND period=day&date=datefrom,dateto, if (isset($dataTable->metadata[$key]['period'])) { $row = $customVariableTableForDate->getRowFromLabel($customVarNameToLookFor); if ($row) { $dateRewrite = $dataTable->metadata[$key]['period']->getDateStart()->toString(); $idSubtable = $row->getIdSubDataTable(); $categories = Piwik_CustomVariables_API::getInstance()->getCustomVariablesValuesFromNameId($idSite, $period, $dateRewrite, $idSubtable, $segment); $dataTable->addTable($categories, $key); } } } } elseif ($customVariables instanceof Piwik_DataTable) { $row = $customVariables->getRowFromLabel($customVarNameToLookFor); if ($row) { $idSubtable = $row->getIdSubDataTable(); $dataTable = Piwik_CustomVariables_API::getInstance()->getCustomVariablesValuesFromNameId($idSite, $period, $date, $idSubtable, $segment); } } $this->filterActionsDataTable($dataTable); $this->addPagesPerSearchColumn($dataTable, $columnToRead = 'nb_actions'); return $dataTable; }
/** * Computes the output for the given data table array * * @param Piwik_DataTable_Array $table * @param array $array * @param string $prefixLines * @return string */ protected function renderDataTableArray($table, $array, $prefixLines = "") { // CASE 1 //array // 'day1' => string '14' (length=2) // 'day2' => string '6' (length=1) $firstTable = current($array); if (!is_array($firstTable)) { $xml = ''; $nameDescriptionAttribute = $table->getKeyName(); foreach ($array as $valueAttribute => $value) { if (empty($value)) { $xml .= $prefixLines . "\t<result {$nameDescriptionAttribute}=\"{$valueAttribute}\" />\n"; } elseif ($value instanceof Piwik_DataTable_Array) { $out = $this->renderTable($value, true); //TODO somehow this code is not tested, cover this case $xml .= "\t<result {$nameDescriptionAttribute}=\"{$valueAttribute}\">\n{$out}</result>\n"; } else { $xml .= $prefixLines . "\t<result {$nameDescriptionAttribute}=\"{$valueAttribute}\">" . self::formatValueXml($value) . "</result>\n"; } } return $xml; } $subTables = $table->getArray(); $firstTable = current($subTables); // CASE 2 //array // 'day1' => // array // 'nb_uniq_visitors' => string '18' // 'nb_visits' => string '101' // 'day2' => // array // 'nb_uniq_visitors' => string '28' // 'nb_visits' => string '11' if ($firstTable instanceof Piwik_DataTable_Simple) { $xml = ''; $nameDescriptionAttribute = $table->getKeyName(); foreach ($array as $valueAttribute => $dataTableSimple) { if (count($dataTableSimple) == 0) { $xml .= $prefixLines . "\t<result {$nameDescriptionAttribute}=\"{$valueAttribute}\" />\n"; } else { if (is_array($dataTableSimple)) { $dataTableSimple = "\n" . $this->renderDataTableSimple($dataTableSimple, $prefixLines . "\t") . $prefixLines . "\t"; } $xml .= $prefixLines . "\t<result {$nameDescriptionAttribute}=\"{$valueAttribute}\">" . $dataTableSimple . "</result>\n"; } } return $xml; } // CASE 3 //array // 'day1' => // array // 0 => // array // 'label' => string 'phpmyvisites' // 'nb_uniq_visitors' => int 11 // 'nb_visits' => int 13 // 1 => // array // 'label' => string 'phpmyvisits' // 'nb_uniq_visitors' => int 2 // 'nb_visits' => int 2 // 'day2' => // array // 0 => // array // 'label' => string 'piwik' // 'nb_uniq_visitors' => int 121 // 'nb_visits' => int 130 // 1 => // array // 'label' => string 'piwik bis' // 'nb_uniq_visitors' => int 20 // 'nb_visits' => int 120 if ($firstTable instanceof Piwik_DataTable) { $xml = ''; $nameDescriptionAttribute = $table->getKeyName(); foreach ($array as $keyName => $arrayForSingleDate) { $dataTableOut = $this->renderDataTable($arrayForSingleDate, $prefixLines . "\t"); if (empty($dataTableOut)) { $xml .= $prefixLines . "\t<result {$nameDescriptionAttribute}=\"{$keyName}\" />\n"; } else { $xml .= $prefixLines . "\t<result {$nameDescriptionAttribute}=\"{$keyName}\">\n"; $xml .= $dataTableOut; $xml .= $prefixLines . "\t</result>\n"; } } return $xml; } if ($firstTable instanceof Piwik_DataTable_Array) { $xml = ''; $tables = $table->getArray(); $nameDescriptionAttribute = $table->getKeyName(); foreach ($tables as $valueAttribute => $tableInArray) { $out = $this->renderTable($tableInArray, true, $prefixLines . "\t"); $xml .= $prefixLines . "\t<result {$nameDescriptionAttribute}=\"{$valueAttribute}\">\n" . $out . $prefixLines . "\t</result>\n"; } return $xml; } }
/** * Given a Piwik_DataTable_Array made of DataTable_Simple rows, returns a php array with the structure: * array( * array( label => X, value => Y), * array( label => A, value => B), * ... * ) * * This is used for example for the evolution graph (last 30 days visits) or the sparklines. * * @param Piwik_DataTable_Array $dataTableArray * @return array */ protected function generateDataFromDataTableArray(Piwik_DataTable_Array $dataTableArray) { // we have to fill a $data array with each row = array('label' => X, 'value' => y) $data = array(); foreach ($dataTableArray->getArray() as $keyName => $table) { $value = false; $onlyRow = $table->getFirstRow(); if ($onlyRow !== false) { $value = $onlyRow->getColumn('value'); if ($value == false) { // TEMP // quite a hack, useful in the case at this point we do have a normal row with nb_visits, nb_actions, nb_uniq_visitors, etc. // instead of the dataTable_Simple row (label, value) // to do it properly we'd need to // - create a filter that removes columns // - apply this filter to keep only the column called nb_uniq_visitors // - rename this column as 'value' // and at this point the getcolumn('value') would have worked // this code is executed eg. when displaying a sparkline for the last 30 days displaying the number of unique visitors coming from search engines //TODO solution: use a filter rename column etc. // another solution would be to add a method to the Referers API giving directly the integer 'visits from search engines' // and we would build automatically the dataTable_array of datatatble_simple from these integers // but we'd have to add this integer to be recorded during archiving etc. $value = $onlyRow->getColumn('nb_uniq_visitors'); } } if ($value === false) { $value = 0; } $data[] = array('label' => $keyName, 'value' => $value); } return $data; }