diff options
author | sgiehl <stefan@piwik.org> | 2015-09-25 00:26:36 +0300 |
---|---|---|
committer | sgiehl <stefan@piwik.org> | 2015-10-11 12:08:57 +0300 |
commit | 3e02a250be06c5295ae821cfeeaa8619e27b6333 (patch) | |
tree | 852dd8648592b59e18ed0723663ea6a8502673b6 | |
parent | 0a92194e024b365bbe4778593ce4a2c9f68530fd (diff) |
added first version of numberformatter & use it for datatables
-rw-r--r-- | core/Intl/Locale.php | 2 | ||||
-rw-r--r-- | core/NumberFormatter.php | 215 | ||||
-rwxr-xr-x | core/Twig.php | 15 | ||||
-rw-r--r-- | plugins/CoreHome/templates/_dataTableCell.twig | 4 |
4 files changed, 233 insertions, 3 deletions
diff --git a/core/Intl/Locale.php b/core/Intl/Locale.php index 0c7676569a..05cf51a8e3 100644 --- a/core/Intl/Locale.php +++ b/core/Intl/Locale.php @@ -31,6 +31,8 @@ class Locale setlocale(LC_ALL, $newLocale); setlocale(LC_CTYPE, ''); + // Always use english for numbers. otherwise the decimal separator might get localized when casting a float to string + setlocale(LC_NUMERIC, array('en_US.UTF-8', 'en-US')); } public static function setDefaultLocale() diff --git a/core/NumberFormatter.php b/core/NumberFormatter.php new file mode 100644 index 0000000000..d2d56f189a --- /dev/null +++ b/core/NumberFormatter.php @@ -0,0 +1,215 @@ +<?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; + +use Piwik\Container\StaticContainer; + +/** + * Class NumberFormatter + * + * Used to format numbers according to current language + */ +class NumberFormatter extends Singleton +{ + /** @var string language specific pattern for positive numbers */ + protected $patternPositive; + + /** @var string language specific pattern for negative numbers */ + protected $patternNegative; + + /** @var string language specific pattern for percent numbers */ + protected $patternPercent; + + /** @var string language specific plus sign */ + protected $symbolPlus; + + /** @var string language specific minus sign */ + protected $symbolMinus; + + /** @var string language specific percent sign */ + protected $symbolPercent; + + /** @var string language specific symbol used as decimal separator */ + protected $symbolDecimal; + + /** @var string language specific symbol used as group separator */ + protected $symbolGroup; + + /** @var bool indicates if language uses grouping for numbers */ + protected $usesGrouping; + + /** @var int language specific size for primary group numbers */ + protected $primaryGroupSize; + + /** @var int language specific size for secondary group numbers */ + protected $secondaryGroupSize; + + /** + * @return NumberFormatter + */ + public static function getInstance() + { + return StaticContainer::get('Piwik\NumberFormatter'); + } + + /** + * Loads all required data from Intl plugin + */ + public function __construct() + { + $this->patternPositive = Piwik::translate('Intl_NumberFormat'); + $this->patternNegative = Piwik::translate('Intl_NumberFormatNegative'); + $this->patternPercent = Piwik::translate('Intl_NumberFormatPercent'); + $this->symbolPlus = Piwik::translate('Intl_NumberSymbolPlus'); + $this->symbolMinus = Piwik::translate('Intl_NumberSymbolMinus'); + $this->symbolPercent = Piwik::translate('Intl_NumberSymbolPercent'); + $this->symbolGroup = Piwik::translate('Intl_NumberSymbolGroup'); + $this->symbolDecimal = Piwik::translate('Intl_NumberSymbolDecimal'); + + $this->usesGrouping = (strpos($this->patternPositive, ',') !== false); + // if pattern has number groups, parse them. + if ($this->usesGrouping) { + preg_match('/#+0/', $this->patternPositive, $primaryGroupMatches); + $this->primaryGroupSize = $this->secondaryGroupSize = strlen($primaryGroupMatches[0]); + $numberGroups = explode(',', $this->patternPositive); + // check for distinct secondary group size. + if (count($numberGroups) > 2) { + $this->secondaryGroupSize = strlen($numberGroups[1]); + } + } + } + + /** + * Formats a given number + * + * @param string|int|float $value + * @param int $maximumFractionDigits + * @param int $minimumFractionDigits + * @return mixed|string + */ + public function format($value, $maximumFractionDigits=0, $minimumFractionDigits=0) + { + $negative = (bccomp('0', $value, 12) == 1); + $pattern = $negative ? $this->patternNegative : $this->patternPositive; + return $this->formatNumberWithPattern($pattern, $value, $maximumFractionDigits, $minimumFractionDigits); + } + + /** + * Formats a given number + * + * @see \Piwik\NumberFormatter::format() + * + * @param string|int|float $value + * @param int $maximumFractionDigits + * @param int $minimumFractionDigits + * @return mixed|string + */ + public function formatNumber($value, $maximumFractionDigits=0, $minimumFractionDigits=0) + { + return $this->format($value, $maximumFractionDigits, $minimumFractionDigits); + } + + /** + * Formats given number as percent value + * @param string|int|float $value + * @param int $maximumFractionDigits + * @param int $minimumFractionDigits + * @return mixed|string + */ + public function formatPercent($value, $maximumFractionDigits=0, $minimumFractionDigits=0) + { + $newValue = trim($value, " \0\x0B%"); + if (!is_numeric($newValue)) { + return $value; + } + return $this->formatNumberWithPattern($this->patternPercent, $newValue, $maximumFractionDigits, $minimumFractionDigits); + } + + /** + * Formats the given number with the given pattern + * + * @param string $pattern + * @param string|int|float $value + * @param int $maximumFractionDigits + * @param int $minimumFractionDigits + * @return mixed|string + */ + protected function formatNumberWithPattern($pattern, $value, $maximumFractionDigits=0, $minimumFractionDigits=0) + { + $orig = $value; + if (!is_numeric($value)) { + return $value; + } + // Ensure that the value is positive and has the right number of digits. + $negative = (bccomp('0', $value, 12) == 1); + $signMultiplier = $negative ? '-1' : '1'; + $value = bcdiv($value, $signMultiplier, $maximumFractionDigits); + // Split the number into major and minor digits. + $valueParts = explode('.', $value); + $majorDigits = $valueParts[0]; + // Account for maximumFractionDigits = 0, where the number won't + // have a decimal point, and $valueParts[1] won't be set. + $minorDigits = isset($valueParts[1]) ? $valueParts[1] : ''; + if ($this->usesGrouping) { + // Reverse the major digits, since they are grouped from the right. + $majorDigits = array_reverse(str_split($majorDigits)); + // Group the major digits. + $groups = []; + $groups[] = array_splice($majorDigits, 0, $this->primaryGroupSize); + while (!empty($majorDigits)) { + $groups[] = array_splice($majorDigits, 0, $this->secondaryGroupSize); + } + // Reverse the groups and the digits inside of them. + $groups = array_reverse($groups); + foreach ($groups as &$group) { + $group = implode(array_reverse($group)); + } + // Reconstruct the major digits. + $majorDigits = implode(',', $groups); + } + if ($minimumFractionDigits < $maximumFractionDigits) { + // Strip any trailing zeroes. + $minorDigits = rtrim($minorDigits, '0'); + if (strlen($minorDigits) < $minimumFractionDigits) { + // Now there are too few digits, re-add trailing zeroes + // until the desired length is reached. + $neededZeroes = $minimumFractionDigits - strlen($minorDigits); + $minorDigits .= str_repeat('0', $neededZeroes); + } + } + // Assemble the final number and insert it into the pattern. + $value = $minorDigits ? $majorDigits . '.' . $minorDigits : $majorDigits; + $value = preg_replace('/#(?:[\.,]#+)*0(?:[,\.][0#]+)*/', $value, $pattern); + // Localize the number. + $value = $this->replaceSymbols($value); + return $value; + } + + + /** + * Replaces number symbols with their localized equivalents. + * + * @param string $value The value being formatted. + * + * @return string + * + * @see http://cldr.unicode.org/translation/number-symbols + */ + protected function replaceSymbols($value) + { + $replacements = [ + '.' => $this->symbolDecimal, + ',' => $this->symbolGroup, + '+' => $this->symbolPlus, + '-' => $this->symbolMinus, + '%' => $this->symbolPercent, + ]; + return strtr($value, $replacements); + } +}
\ No newline at end of file diff --git a/core/Twig.php b/core/Twig.php index 09720eefdd..7267e98fed 100755 --- a/core/Twig.php +++ b/core/Twig.php @@ -86,6 +86,7 @@ class Twig $this->addFilter_percentage(); $this->addFilter_prettyDate(); $this->addFilter_safeDecodeRaw(); + $this->addFilter_number(); $this->twig->addFilter(new Twig_SimpleFilter('implode', 'implode')); $this->twig->addFilter(new Twig_SimpleFilter('ucwords', 'ucwords')); $this->twig->addFilter(new Twig_SimpleFilter('lcfirst', 'lcfirst')); @@ -272,11 +273,23 @@ class Twig protected function addFilter_percentage() { $percentage = new Twig_SimpleFilter('percentage', function ($string, $totalValue, $precision = 1) { - return Piwik::getPercentageSafe($string, $totalValue, $precision) . '%'; + return NumberFormatter::getInstance()->formatPercent(Piwik::getPercentageSafe($string, $totalValue, $precision), $precision); }); $this->twig->addFilter($percentage); } + protected function addFilter_number() + { + $formatter = new Twig_SimpleFilter('number', function ($string, $minFractionDigits = 0, $maxFractionDigits = 0) { + // if a leading/trailing percent sign is found, format as percent number + if ($string != trim($string, '%')) { + return NumberFormatter::getInstance()->formatPercent($string, $minFractionDigits, $maxFractionDigits); + } + return NumberFormatter::getInstance()->format($string, $minFractionDigits, $maxFractionDigits); + }); + $this->twig->addFilter($formatter); + } + protected function addFilter_truncate() { $truncateFilter = new Twig_SimpleFilter('truncate', function ($string, $size) { diff --git a/plugins/CoreHome/templates/_dataTableCell.twig b/plugins/CoreHome/templates/_dataTableCell.twig index 40e61541f3..4b42afd9b4 100644 --- a/plugins/CoreHome/templates/_dataTableCell.twig +++ b/plugins/CoreHome/templates/_dataTableCell.twig @@ -25,7 +25,7 @@ {% if siteTotal and siteTotal > reportTotal %} {% set totalPercentage = row.getColumn(column)|percentage(siteTotal, 1) %} - {% set totalRatioTooltip = 'General_TotalRatioTooltip'|translate(totalPercentage, siteTotal, metricTitle) %} + {% set totalRatioTooltip = 'General_TotalRatioTooltip'|translate(totalPercentage, siteTotal|number(2,0), metricTitle) %} {% else %} {% set totalRatioTooltip = '' %} {% endif %} @@ -41,7 +41,7 @@ {{ piwik.logoHtml(row.getMetadata(), row.getColumn('label')) }} {% if row.getMetadata('html_label_prefix') %}<span class='label-prefix'>{{ row.getMetadata('html_label_prefix') | raw }} </span>{% endif -%} {%- if row.getMetadata('html_label_suffix') %}<span class='label-suffix'>{{ row.getMetadata('html_label_suffix') | raw }}</span>{% endif -%} -{% endif %}<span class="value">{% if row.getColumn(column) %}{{- row.getColumn(column)|raw -}}{% else %}-{% endif %}</span> +{% endif %}<span class="value">{% if row.getColumn(column) %}{{- row.getColumn(column)|number(2,0)|raw -}}{% else %}-{% endif %}</span> {% if column=='label' %}</span>{% endif %} {% if not row.getIdSubDataTable() and column=='label' and row.getMetadata('url') %} </a> |