PDF rausgenommen

This commit is contained in:
aschwarz
2023-01-23 11:03:31 +01:00
parent 82d562a322
commit a6523903eb
28078 changed files with 4247552 additions and 2 deletions

View File

@ -0,0 +1,317 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Metrics;
use Piwik\Archive\DataTableFactory;
use Piwik\Common;
use Piwik\DataTable;
use Piwik\NumberFormatter;
use Piwik\Piwik;
use Piwik\Plugin\ArchivedMetric;
use Piwik\Plugin\Metric;
use Piwik\Plugin\ProcessedMetric;
use Piwik\Plugin\Report;
use Piwik\Site;
use Piwik\Tracker\GoalManager;
/**
* Contains methods to format metric values. Passed to the {@link \Piwik\Plugin\Metric::format()}
* method when formatting Metrics.
*/
class Formatter
{
const PROCESSED_METRICS_FORMATTED_FLAG = 'processed_metrics_formatted';
private $decimalPoint = null;
private $thousandsSeparator = null;
/**
* Returns a prettified string representation of a number. The result will have
* thousands separators and a decimal point specific to the current locale, eg,
* `'1,000,000.05'` or `'1.000.000,05'`.
*
* @param number $value
* @return string
* @api
*/
public function getPrettyNumber($value, $precision = 0)
{
if ($this->decimalPoint === null) {
$locale = localeconv();
$this->decimalPoint = $locale['decimal_point'];
$this->thousandsSeparator = $locale['thousands_sep'];
}
return number_format($value, $precision, $this->decimalPoint, $this->thousandsSeparator);
}
/**
* Returns a prettified time value (in seconds).
*
* @param int $numberOfSeconds The number of seconds.
* @param bool $displayTimeAsSentence If set to true, will output `"5min 17s"`, if false `"00:05:17"`.
* @param bool $round Whether to round to the nearest second or not.
* @return string
* @api
*/
public function getPrettyTimeFromSeconds($numberOfSeconds, $displayTimeAsSentence = false, $round = false)
{
$numberOfSeconds = $round ? (int)$numberOfSeconds : (float)$numberOfSeconds;
$isNegative = false;
if ($numberOfSeconds < 0) {
$numberOfSeconds = -1 * $numberOfSeconds;
$isNegative = true;
}
// Display 01:45:17 time format
if ($displayTimeAsSentence === false) {
$days = floor($numberOfSeconds / 86400);
$hours = floor(($reminder = ($numberOfSeconds - $days * 86400)) / 3600);
$minutes = floor(($reminder = ($reminder - $hours * 3600)) / 60);
$seconds = floor($reminder - $minutes * 60);
if ($days == 0) {
$time = sprintf("%02s", $hours) . ':' . sprintf("%02s", $minutes) . ':' . sprintf("%02s", $seconds);
} else {
$time = sprintf(Piwik::translate('Intl_NDays'), $days) . " " . sprintf("%02s", $hours) . ':' . sprintf("%02s", $minutes) . ':' . sprintf("%02s", $seconds);
}
$centiSeconds = ($numberOfSeconds * 100) % 100;
if ($centiSeconds) {
$time .= '.' . sprintf("%02s", $centiSeconds);
}
if ($isNegative) {
$time = '-' . $time;
}
return $time;
}
$secondsInYear = 86400 * 365.25;
$years = floor($numberOfSeconds / $secondsInYear);
$minusYears = $numberOfSeconds - $years * $secondsInYear;
$days = floor($minusYears / 86400);
$minusDays = $numberOfSeconds - $days * 86400;
$hours = floor($minusDays / 3600);
$minusDaysAndHours = $minusDays - $hours * 3600;
$minutes = floor($minusDaysAndHours / 60);
$seconds = $minusDaysAndHours - $minutes * 60;
$precision = ($seconds > 0 && $seconds < 0.01 ? 3 : 2);
$seconds = NumberFormatter::getInstance()->formatNumber(round($seconds, $precision), $precision);
if ($years > 0) {
$return = sprintf(Piwik::translate('General_YearsDays'), $years, $days);
} elseif ($days > 0) {
$return = sprintf(Piwik::translate('General_DaysHours'), $days, $hours);
} elseif ($hours > 0) {
$return = sprintf(Piwik::translate('General_HoursMinutes'), $hours, $minutes);
} elseif ($minutes > 0) {
$return = sprintf(Piwik::translate('General_MinutesSeconds'), $minutes, $seconds);
} else {
$return = sprintf(Piwik::translate('Intl_NSecondsShort'), $seconds);
}
if ($isNegative) {
$return = '-' . $return;
}
return $return;
}
/**
* Returns a prettified memory size value.
*
* @param number $size The size in bytes.
* @param string $unit The specific unit to use, if any. If null, the unit is determined by $size.
* @param int $precision The precision to use when rounding.
* @return string eg, `'128 M'` or `'256 K'`.
* @api
*/
public function getPrettySizeFromBytes($size, $unit = null, $precision = 1)
{
if ($size == 0) {
return '0 M';
}
list($size, $sizeUnit) = $this->getPrettySizeFromBytesWithUnit($size, $unit, $precision);
return $size . " " . $sizeUnit;
}
/**
* Returns a pretty formatted monetary value using the currency associated with a site.
*
* @param int|string $value The monetary value to format.
* @param int $idSite The ID of the site whose currency will be used.
* @return string
* @api
*/
public function getPrettyMoney($value, $idSite)
{
$space = ' ';
$currencySymbol = Site::getCurrencySymbolFor($idSite);
$currencyBefore = $currencySymbol . $space;
$currencyAfter = '';
// (maybe more currencies prefer this notation?)
$currencySymbolToAppend = array('€', 'kr', 'zł');
// manually put the currency symbol after the amount
if (in_array($currencySymbol, $currencySymbolToAppend)) {
$currencyAfter = $space . $currencySymbol;
$currencyBefore = '';
}
// if the input is a number (it could be a string or INPUT form),
// and if this number is not an int, we round to precision 2
if (is_numeric($value)) {
if ($value == round($value)) {
// 0.0 => 0
$value = round($value);
} else {
$precision = GoalManager::REVENUE_PRECISION;
$value = sprintf("%01." . $precision . "f", $value);
}
}
$prettyMoney = $currencyBefore . $value . $currencyAfter;
return $prettyMoney;
}
/**
* Returns a percent string from a quotient value. Forces the use of a '.'
* decimal place.
*
* @param float $value
* @return string
* @api
*/
public function getPrettyPercentFromQuotient($value)
{
$result = ($value * 100) . '%';
return Common::forceDotAsSeparatorForDecimalPoint($result);
}
/**
* Formats all metrics, including processed metrics, for a DataTable. Metrics to format
* are found through report metadata and DataTable metadata.
*
* @param DataTable $dataTable The table to format metrics for.
* @param Report|null $report The report the table belongs to.
* @param string[]|null $metricsToFormat Whitelist of names of metrics to format.
* @param boolean $formatAll If true, will also apply formatting to non-processed metrics like revenue.
* This parameter is not currently supported and subject to change.
* @api
*/
public function formatMetrics(DataTable $dataTable, Report $report = null, $metricsToFormat = null, $formatAll = false)
{
$metrics = $this->getMetricsToFormat($dataTable, $report);
if (empty($metrics)
|| $dataTable->getMetadata(self::PROCESSED_METRICS_FORMATTED_FLAG)
) {
return;
}
$dataTable->setMetadata(self::PROCESSED_METRICS_FORMATTED_FLAG, true);
if ($metricsToFormat !== null) {
$metricMatchRegex = $this->makeRegexToMatchMetrics($metricsToFormat);
$metrics = array_filter($metrics, function ($metric) use ($metricMatchRegex) {
/** @var ProcessedMetric|ArchivedMetric $metric */
return preg_match($metricMatchRegex, $metric->getName());
});
}
foreach ($metrics as $name => $metric) {
if (!$metric->beforeFormat($report, $dataTable)) {
continue;
}
foreach ($dataTable->getRows() as $row) {
$columnValue = $row->getColumn($name);
if ($columnValue !== false) {
$row->setColumn($name, $metric->format($columnValue, $this));
}
}
}
foreach ($dataTable->getRows() as $row) {
$subtable = $row->getSubtable();
if (!empty($subtable)) {
$this->formatMetrics($subtable, $report, $metricsToFormat);
}
}
$idSite = DataTableFactory::getSiteIdFromMetadata($dataTable);
if (empty($idSite)) {
// possible when using search in visualization
$idSite = Common::getRequestVar('idSite', 0, 'int');
}
// @todo for matomo 4, should really use the Metric class to house this kind of logic
// format other metrics
if ($formatAll) {
foreach ($dataTable->getRows() as $row) {
foreach ($row->getColumns() as $column => $columnValue) {
if (strpos($column, 'revenue') === false
|| !is_numeric($columnValue)
) {
continue;
}
if ($columnValue !== false) {
$row->setColumn($column, $this->getPrettyMoney($columnValue, $idSite));
}
}
}
}
}
protected function getPrettySizeFromBytesWithUnit($size, $unit = null, $precision = 1)
{
$units = array('B', 'K', 'M', 'G', 'T');
$numUnits = count($units) - 1;
$currentUnit = null;
foreach ($units as $idx => $currentUnit) {
if ($unit && $unit !== $currentUnit) {
$size = $size / 1024;
} elseif ($unit && $unit === $currentUnit) {
break;
} elseif ($size >= 1024 && $idx != $numUnits) {
$size = $size / 1024;
} else {
break;
}
}
$size = round($size, $precision);
return array($size, $currentUnit);
}
private function makeRegexToMatchMetrics($metricsToFormat)
{
$metricsRegexParts = array();
foreach ($metricsToFormat as $metricFilter) {
if ($metricFilter[0] == '/') {
$metricsRegexParts[] = '(?:' . substr($metricFilter, 1, strlen($metricFilter) - 2) . ')';
} else {
$metricsRegexParts[] = preg_quote($metricFilter);
}
}
return '/^' . implode('|', $metricsRegexParts) . '$/';
}
/**
* @param DataTable $dataTable
* @param Report $report
* @return Metric[]
*/
private function getMetricsToFormat(DataTable $dataTable, Report $report = null)
{
return Report::getMetricsForTable($dataTable, $report, $baseType = 'Piwik\\Plugin\\Metric');
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Metrics\Formatter;
use Piwik\Metrics\Formatter;
/**
* Metrics formatter that formats for HTML output. Uses non-breaking spaces in formatted values
* so text will stay unbroken in HTML views.
*/
class Html extends Formatter
{
public function getPrettyTimeFromSeconds($numberOfSeconds, $displayTimeAsSentence = true, $round = false)
{
$result = parent::getPrettyTimeFromSeconds($numberOfSeconds, $displayTimeAsSentence, $round);
$result = $this->replaceSpaceWithNonBreakingSpace($result);
return $result;
}
public function getPrettySizeFromBytes($size, $unit = null, $precision = 1)
{
$result = parent::getPrettySizeFromBytes($size, $unit, $precision);
$result = $this->replaceSpaceWithNonBreakingSpace($result);
return $result;
}
public function getPrettyMoney($value, $idSite)
{
$result = parent::getPrettyMoney($value, $idSite);
$result = $this->replaceSpaceWithNonBreakingSpace($result);
return $result;
}
private function replaceSpaceWithNonBreakingSpace($value)
{
return str_replace(' ', '&nbsp;', $value);
}
}

View File

@ -0,0 +1,237 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Metrics;
use Piwik\DataTable;
use Piwik\DataTable\Row;
use Piwik\Metrics;
use Piwik\Plugin\Metric;
use Piwik\Plugin\Report;
class Sorter
{
/**
* @var Sorter\Config
*/
private $config;
public function __construct(Sorter\Config $config)
{
$this->config = $config;
}
/**
* Sorts the DataTable rows using the supplied callback function.
*
* @param DataTable $table The table to sort.
*/
public function sort(DataTable $table)
{
// all that code is in here and not in separate methods for best performance. It does make a difference once
// php has to copy many (eg 50k) rows otherwise.
$table->setTableSortedBy($this->config->primaryColumnToSort);
$rows = $table->getRowsWithoutSummaryRow();
// we need to sort rows that have a value separately from rows that do not have a value since we always want
// to append rows that do not have a value at the end.
$rowsWithValues = array();
$rowsWithoutValues = array();
$valuesToSort = array();
foreach ($rows as $key => $row) {
$value = $this->getColumnValue($row);
if (isset($value)) {
$valuesToSort[] = $value;
$rowsWithValues[] = $row;
} else {
$rowsWithoutValues[] = $row;
}
}
unset($rows);
if ($this->config->isSecondaryColumnSortEnabled && $this->config->secondaryColumnToSort) {
$secondaryValues = array();
foreach ($rowsWithValues as $key => $row) {
$secondaryValues[$key] = $row->getColumn($this->config->secondaryColumnToSort);
}
array_multisort($valuesToSort, $this->config->primarySortOrder, $this->config->primarySortFlags, $secondaryValues, $this->config->secondarySortOrder, $this->config->secondarySortFlags, $rowsWithValues);
} else {
array_multisort($valuesToSort, $this->config->primarySortOrder, $this->config->primarySortFlags, $rowsWithValues);
}
if (!empty($rowsWithoutValues) && $this->config->secondaryColumnToSort) {
$secondaryValues = array();
foreach ($rowsWithoutValues as $key => $row) {
$secondaryValues[$key] = $row->getColumn($this->config->secondaryColumnToSort);
}
array_multisort($secondaryValues, $this->config->secondarySortOrder, $this->config->secondarySortFlags, $rowsWithoutValues);
}
unset($secondaryValues);
foreach ($rowsWithoutValues as $row) {
$rowsWithValues[] = $row;
}
$table->setRows(array_values($rowsWithValues));
}
private function getColumnValue(Row $row)
{
$value = $row->getColumn($this->config->primaryColumnToSort);
if ($value === false || is_array($value)) {
return null;
}
return $value;
}
/**
* @param string $order 'asc' or 'desc'
* @return int
*/
public function getPrimarySortOrder($order)
{
if ($order === 'asc') {
return SORT_ASC;
}
return SORT_DESC;
}
/**
* @param string $order 'asc' or 'desc'
* @param string|int $secondarySortColumn column name or column id
* @return int
*/
public function getSecondarySortOrder($order, $secondarySortColumn)
{
if ($secondarySortColumn === 'label') {
$secondaryOrder = SORT_ASC;
if ($order === 'asc') {
$secondaryOrder = SORT_DESC;
}
return $secondaryOrder;
}
return $this->getPrimarySortOrder($order);
}
/**
* Detect the column to be used for sorting
*
* @param DataTable $table
* @param string|int $columnToSort column name or column id
* @return int
*/
public function getPrimaryColumnToSort(DataTable $table, $columnToSort)
{
// we fallback to nb_visits in case columnToSort does not exist
$columnsToCheck = array($columnToSort, 'nb_visits');
$row = $table->getFirstRow();
foreach ($columnsToCheck as $column) {
$column = Metric::getActualMetricColumn($table, $column);
if ($row->hasColumn($column)) {
// since getActualMetricColumn() returns a default value, we need to make sure it actually has that column
return $column;
}
}
return $columnToSort;
}
/**
* Detect the secondary sort column to be used for sorting
*
* @param Row $row
* @param int|string $primaryColumnToSort
* @return int
*/
public function getSecondaryColumnToSort(Row $row, $primaryColumnToSort)
{
$defaultSecondaryColumn = array(Metrics::INDEX_NB_VISITS, 'nb_visits');
if (in_array($primaryColumnToSort, $defaultSecondaryColumn)) {
// if sorted by visits, then sort by label as a secondary column
$column = 'label';
$value = $row->hasColumn($column);
if ($value !== false) {
return $column;
}
return null;
}
if ($primaryColumnToSort !== 'label') {
// we do not add this by default to make sure we do not sort by label as a first and secondary column
$defaultSecondaryColumn[] = 'label';
}
foreach ($defaultSecondaryColumn as $column) {
$value = $row->hasColumn($column);
if ($value !== false) {
return $column;
}
}
}
/**
* @param DataTable $table
* @param string|int $columnToSort A column name or column id. Make sure that column actually exists in the row.
* You might want to get a valid column via {@link getPrimaryColumnToSort()} or
* {@link getSecondaryColumnToSort()}
* @return int
*/
public function getBestSortFlags(DataTable $table, $columnToSort)
{
// when column is label we always to sort by string or natural
if (isset($columnToSort) && $columnToSort !== 'label') {
foreach ($table->getRowsWithoutSummaryRow() as $row) {
$value = $row->getColumn($columnToSort);
if ($value !== false && $value !== null && !is_array($value)) {
if (is_numeric($value)) {
$sortFlags = SORT_NUMERIC;
} else {
$sortFlags = $this->getStringSortFlags();
}
return $sortFlags;
}
}
}
return $this->getStringSortFlags();
}
private function getStringSortFlags()
{
if ($this->config->naturalSort) {
$sortFlags = SORT_NATURAL | SORT_FLAG_CASE;
} else {
$sortFlags = SORT_STRING | SORT_FLAG_CASE;
}
return $sortFlags;
}
}

View File

@ -0,0 +1,24 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Metrics\Sorter;
class Config
{
public $naturalSort = false;
public $primaryColumnToSort;
public $primarySortFlags;
public $primarySortOrder;
public $secondaryColumnToSort;
public $secondarySortOrder;
public $secondarySortFlags;
public $isSecondaryColumnSortEnabled = true;
}