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