PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.0.7
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.0.7
5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / NumberFormatter.php
matomo / app / core Last commit date
API 2 years ago Access 2 years ago Application 2 years ago Archive 2 years ago ArchiveProcessor 2 years ago Archiver 2 years ago AssetManager 2 years ago Auth 2 years ago Category 2 years ago Changes 2 years ago CliMulti 2 years ago Columns 2 years ago Concurrency 2 years ago Config 2 years ago Container 2 years ago CronArchive 2 years ago DataAccess 2 years ago DataFiles 2 years ago DataTable 2 years ago Db 2 years ago DeviceDetector 2 years ago Email 2 years ago Exception 2 years ago Http 2 years ago Intl 2 years ago Log 2 years ago Mail 2 years ago Measurable 2 years ago Menu 2 years ago Metrics 2 years ago Notification 2 years ago Period 2 years ago Plugin 2 years ago ProfessionalServices 2 years ago Report 2 years ago ReportRenderer 2 years ago Scheduler 2 years ago Segment 2 years ago Session 2 years ago Settings 2 years ago Tracker 2 years ago Translation 2 years ago Twig 2 years ago UpdateCheck 2 years ago Updater 2 years ago Updates 2 years ago Validators 2 years ago View 2 years ago ViewDataTable 2 years ago Visualization 2 years ago Widget 2 years ago .htaccess 2 years ago Access.php 2 years ago Archive.php 2 years ago ArchiveProcessor.php 2 years ago AssetManager.php 2 years ago Auth.php 2 years ago AuthResult.php 2 years ago BaseFactory.php 2 years ago Cache.php 2 years ago CacheId.php 2 years ago CliMulti.php 2 years ago Common.php 2 years ago Config.php 2 years ago Console.php 2 years ago Context.php 2 years ago Cookie.php 2 years ago CronArchive.php 2 years ago DI.php 2 years ago DataArray.php 2 years ago DataTable.php 2 years ago Date.php 2 years ago Db.php 2 years ago DbHelper.php 2 years ago Development.php 2 years ago ErrorHandler.php 2 years ago EventDispatcher.php 2 years ago ExceptionHandler.php 2 years ago FileIntegrity.php 2 years ago Filechecks.php 2 years ago Filesystem.php 2 years ago FrontController.php 2 years ago Http.php 2 years ago IP.php 2 years ago Log.php 2 years ago LogDeleter.php 2 years ago Mail.php 2 years ago Metrics.php 2 years ago NoAccessException.php 2 years ago Nonce.php 2 years ago Notification.php 2 years ago NumberFormatter.php 2 years ago Option.php 2 years ago Period.php 2 years ago Piwik.php 2 years ago Plugin.php 2 years ago Profiler.php 2 years ago ProxyHeaders.php 2 years ago ProxyHttp.php 2 years ago QuickForm2.php 2 years ago RankingQuery.php 2 years ago ReportRenderer.php 2 years ago Request.php 2 years ago Segment.php 2 years ago Sequence.php 2 years ago Session.php 2 years ago SettingsPiwik.php 2 years ago SettingsServer.php 2 years ago Singleton.php 2 years ago Site.php 2 years ago SiteContentDetector.php 2 years ago SupportedBrowser.php 2 years ago TCPDF.php 2 years ago Theme.php 2 years ago Timer.php 2 years ago Tracker.php 2 years ago Twig.php 2 years ago Unzip.php 2 years ago UpdateCheck.php 2 years ago Updater.php 2 years ago UpdaterErrorException.php 2 years ago Updates.php 2 years ago Url.php 2 years ago UrlHelper.php 2 years ago Version.php 2 years ago View.php 2 years ago bootstrap.php 2 years ago dispatch.php 2 years ago testMinimumPhpVersion.php 2 years ago
NumberFormatter.php
268 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 *
9 */
10 namespace Piwik;
11
12 use Piwik\Container\StaticContainer;
13 use Piwik\Translation\Translator;
14 /**
15 * Class NumberFormatter
16 *
17 * Used to format numbers according to current language
18 */
19 class NumberFormatter
20 {
21 /** @var Translator */
22 protected $translator;
23 /** @var array cached patterns per language */
24 protected $patterns;
25 /** @var array cached symbols per language */
26 protected $symbols;
27 /**
28 * Loads all required data from Intl plugin
29 *
30 * TODO: instead of going directly through Translator, there should be a specific class
31 * that gets needed characters (ie, NumberFormatSource). The default implementation
32 * can use the Translator. This will make it easier to unit test NumberFormatter,
33 * w/o needing the Piwik Environment.
34 *
35 * @return NumberFormatter
36 */
37 public function __construct(Translator $translator)
38 {
39 $this->translator = $translator;
40 }
41 /**
42 * Parses the given pattern and returns patterns for positive and negative numbers
43 *
44 * @param string $pattern
45 * @return array
46 */
47 protected function parsePattern($pattern)
48 {
49 $patterns = explode(';', $pattern);
50 if (!isset($patterns[1])) {
51 // No explicit negative pattern was provided, construct it.
52 $patterns[1] = '-' . $patterns[0];
53 }
54 return $patterns;
55 }
56 /**
57 * Formats a given number or percent value (if $value starts or ends with a %)
58 *
59 * @param string|int|float $value
60 * @param int $maximumFractionDigits
61 * @param int $minimumFractionDigits
62 * @return mixed|string
63 */
64 public function format($value, $maximumFractionDigits = 0, $minimumFractionDigits = 0)
65 {
66 if (is_string($value) && trim($value, '%') != $value) {
67 return $this->formatPercent($value, $maximumFractionDigits, $minimumFractionDigits);
68 }
69 return $this->formatNumber($value, $maximumFractionDigits, $minimumFractionDigits);
70 }
71 /**
72 * Formats a given number
73 *
74 * @see \Piwik\NumberFormatter::format()
75 *
76 * @param string|int|float $value
77 * @param int $maximumFractionDigits
78 * @param int $minimumFractionDigits
79 * @return mixed|string
80 */
81 public function formatNumber($value, $maximumFractionDigits = 0, $minimumFractionDigits = 0)
82 {
83 $pattern = $this->getPattern($value, 'Intl_NumberFormatNumber');
84 return $this->formatNumberWithPattern($pattern, $value, $maximumFractionDigits, $minimumFractionDigits);
85 }
86 /**
87 * Formats given number as percent value
88 * @param string|int|float $value
89 * @param int $maximumFractionDigits
90 * @param int $minimumFractionDigits
91 * @return mixed|string
92 */
93 public function formatPercent($value, $maximumFractionDigits = 0, $minimumFractionDigits = 0)
94 {
95 $newValue = trim($value, " \x00\v%");
96 if (!is_numeric($newValue)) {
97 return $value;
98 }
99 $pattern = $this->getPattern($value, 'Intl_NumberFormatPercent');
100 return $this->formatNumberWithPattern($pattern, $newValue, $maximumFractionDigits, $minimumFractionDigits);
101 }
102 /**
103 * Formats given number as percent value, but keep the leading + sign if found
104 *
105 * @param $value
106 * @return string
107 */
108 public function formatPercentEvolution($value)
109 {
110 $isPositiveEvolution = !empty($value) && ($value > 0 || substr($value, 0, 1) === '+');
111 $formatted = self::formatPercent($value);
112 if ($isPositiveEvolution) {
113 // $this->symbols has already been initialized from formatPercent().
114 $language = $this->translator->getCurrentLanguage();
115 return $this->symbols[$language]['+'] . $formatted;
116 }
117 return $formatted;
118 }
119 /**
120 * Formats given number as percent value
121 * @param string|int|float $value
122 * @param string $currency
123 * @param int $precision
124 * @return mixed|string
125 */
126 public function formatCurrency($value, $currency, $precision = 2)
127 {
128 $newValue = trim(strval($value), " \x00\v{$currency}");
129 if (!is_numeric($newValue)) {
130 return $value;
131 }
132 $pattern = $this->getPattern($value, 'Intl_NumberFormatCurrency');
133 if ($newValue == round($newValue)) {
134 // if no fraction digits available, don't show any
135 $value = $this->formatNumberWithPattern($pattern, $newValue, 0, 0);
136 } else {
137 // show given count of fraction digits otherwise
138 $value = $this->formatNumberWithPattern($pattern, $newValue, $precision, $precision);
139 }
140 return str_replace('¤', $currency, $value);
141 }
142 /**
143 * Returns the relevant pattern for the given number.
144 *
145 * @param string $value
146 * @param string $translationId
147 * @return string
148 */
149 protected function getPattern($value, $translationId)
150 {
151 $language = $this->translator->getCurrentLanguage();
152 if (!isset($this->patterns[$language][$translationId])) {
153 $this->patterns[$language][$translationId] = $this->parsePattern($this->translator->translate($translationId));
154 }
155 list($positivePattern, $negativePattern) = $this->patterns[$language][$translationId];
156 $negative = $this->isNegative($value);
157 return $negative ? $negativePattern : $positivePattern;
158 }
159 /**
160 * Formats the given number with the given pattern
161 *
162 * @param string $pattern
163 * @param string|int|float $value
164 * @param int $maximumFractionDigits
165 * @param int $minimumFractionDigits
166 * @return mixed|string
167 */
168 protected function formatNumberWithPattern($pattern, $value, $maximumFractionDigits = 0, $minimumFractionDigits = 0)
169 {
170 if (!is_numeric($value)) {
171 return $value;
172 }
173 $usesGrouping = strpos($pattern, ',') !== false;
174 // if pattern has number groups, parse them.
175 if ($usesGrouping) {
176 preg_match('/#+0/', $pattern, $primaryGroupMatches);
177 $primaryGroupSize = $secondaryGroupSize = strlen($primaryGroupMatches[0]);
178 $numberGroups = explode(',', $pattern);
179 // check for distinct secondary group size.
180 if (count($numberGroups) > 2) {
181 $secondaryGroupSize = strlen($numberGroups[1]);
182 }
183 }
184 // Ensure that the value is positive and has the right number of digits.
185 $negative = $this->isNegative($value);
186 $signMultiplier = $negative ? '-1' : '1';
187 $value = $value / $signMultiplier;
188 $value = round($value, $maximumFractionDigits);
189 // Split the number into major and minor digits.
190 $valueParts = explode('.', $value);
191 $majorDigits = $valueParts[0];
192 // Account for maximumFractionDigits = 0, where the number won't
193 // have a decimal point, and $valueParts[1] won't be set.
194 $minorDigits = isset($valueParts[1]) ? $valueParts[1] : '';
195 if ($usesGrouping) {
196 // Reverse the major digits, since they are grouped from the right.
197 $majorDigits = array_reverse(str_split($majorDigits));
198 // Group the major digits.
199 $groups = array();
200 $groups[] = array_splice($majorDigits, 0, $primaryGroupSize);
201 while (!empty($majorDigits)) {
202 $groups[] = array_splice($majorDigits, 0, $secondaryGroupSize);
203 }
204 // Reverse the groups and the digits inside of them.
205 $groups = array_reverse($groups);
206 foreach ($groups as &$group) {
207 $group = implode(array_reverse($group));
208 }
209 // Reconstruct the major digits.
210 $majorDigits = implode(',', $groups);
211 }
212 if ($minimumFractionDigits <= $maximumFractionDigits) {
213 // Strip any trailing zeroes.
214 $minorDigits = rtrim($minorDigits, '0');
215 if (strlen($minorDigits) < $minimumFractionDigits) {
216 // Now there are too few digits, re-add trailing zeroes
217 // until the desired length is reached.
218 $neededZeroes = $minimumFractionDigits - strlen($minorDigits);
219 $minorDigits .= str_repeat('0', $neededZeroes);
220 }
221 }
222 // Assemble the final number and insert it into the pattern.
223 $value = $minorDigits ? $majorDigits . '.' . $minorDigits : $majorDigits;
224 $value = preg_replace('/#(?:[\\.,]#+)*0(?:[,\\.][0#]+)*/', $value, $pattern);
225 // Localize the number.
226 $value = $this->replaceSymbols($value);
227 return $value;
228 }
229 /**
230 * Replaces number symbols with their localized equivalents.
231 *
232 * @param string $value The value being formatted.
233 *
234 * @return string
235 *
236 * @see http://cldr.unicode.org/translation/number-symbols
237 */
238 protected function replaceSymbols($value)
239 {
240 $language = $this->translator->getCurrentLanguage();
241 if (!isset($this->symbols[$language])) {
242 $this->symbols[$language] = array('.' => $this->translator->translate('Intl_NumberSymbolDecimal'), ',' => $this->translator->translate('Intl_NumberSymbolGroup'), '+' => $this->translator->translate('Intl_NumberSymbolPlus'), '-' => $this->translator->translate('Intl_NumberSymbolMinus'), '%' => $this->translator->translate('Intl_NumberSymbolPercent'));
243 }
244 return strtr($value, $this->symbols[$language]);
245 }
246 /**
247 * @param $value
248 * @return bool
249 */
250 protected function isNegative($value)
251 {
252 return $value < 0;
253 }
254 /**
255 * @deprecated
256 * @return self
257 */
258 public static function getInstance()
259 {
260 return StaticContainer::get(\Piwik\NumberFormatter::class);
261 }
262 public function clearCache()
263 {
264 $this->patterns = [];
265 $this->symbols = [];
266 }
267 }
268