PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.1.6
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.1.6
5.13.0 5.12.1 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 1 year 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 1 year 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 1 year ago Translation 2 years ago Twig 2 years ago UpdateCheck 2 years ago Updater 1 year ago Updates 1 year 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 1 year ago DbHelper.php 1 year 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 1 year ago Version.php 1 year ago View.php 2 years ago bootstrap.php 2 years ago dispatch.php 2 years ago testMinimumPhpVersion.php 2 years ago
NumberFormatter.php
267 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik;
10
11 use Piwik\Container\StaticContainer;
12 use Piwik\Translation\Translator;
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 /** @var array cached patterns per language */
23 protected $patterns;
24 /** @var array cached symbols per language */
25 protected $symbols;
26 /**
27 * Loads all required data from Intl plugin
28 *
29 * TODO: instead of going directly through Translator, there should be a specific class
30 * that gets needed characters (ie, NumberFormatSource). The default implementation
31 * can use the Translator. This will make it easier to unit test NumberFormatter,
32 * w/o needing the Piwik Environment.
33 *
34 * @return NumberFormatter
35 */
36 public function __construct(Translator $translator)
37 {
38 $this->translator = $translator;
39 }
40 /**
41 * Parses the given pattern and returns patterns for positive and negative numbers
42 *
43 * @param string $pattern
44 * @return array
45 */
46 protected function parsePattern($pattern)
47 {
48 $patterns = explode(';', $pattern);
49 if (!isset($patterns[1])) {
50 // No explicit negative pattern was provided, construct it.
51 $patterns[1] = '-' . $patterns[0];
52 }
53 return $patterns;
54 }
55 /**
56 * Formats a given number or percent value (if $value starts or ends with a %)
57 *
58 * @param string|int|float $value
59 * @param int $maximumFractionDigits
60 * @param int $minimumFractionDigits
61 * @return mixed|string
62 */
63 public function format($value, $maximumFractionDigits = 0, $minimumFractionDigits = 0)
64 {
65 if (is_string($value) && trim($value, '%') != $value) {
66 return $this->formatPercent($value, $maximumFractionDigits, $minimumFractionDigits);
67 }
68 return $this->formatNumber($value, $maximumFractionDigits, $minimumFractionDigits);
69 }
70 /**
71 * Formats a given number
72 *
73 * @see \Piwik\NumberFormatter::format()
74 *
75 * @param string|int|float $value
76 * @param int $maximumFractionDigits
77 * @param int $minimumFractionDigits
78 * @return mixed|string
79 */
80 public function formatNumber($value, $maximumFractionDigits = 0, $minimumFractionDigits = 0)
81 {
82 $pattern = $this->getPattern($value, 'Intl_NumberFormatNumber');
83 return $this->formatNumberWithPattern($pattern, $value, $maximumFractionDigits, $minimumFractionDigits);
84 }
85 /**
86 * Formats given number as percent value
87 * @param string|int|float $value
88 * @param int $maximumFractionDigits
89 * @param int $minimumFractionDigits
90 * @return mixed|string
91 */
92 public function formatPercent($value, $maximumFractionDigits = 0, $minimumFractionDigits = 0)
93 {
94 $newValue = trim($value, " \x00\v%");
95 if (!is_numeric($newValue)) {
96 return $value;
97 }
98 $pattern = $this->getPattern($value, 'Intl_NumberFormatPercent');
99 return $this->formatNumberWithPattern($pattern, $newValue, $maximumFractionDigits, $minimumFractionDigits);
100 }
101 /**
102 * Formats given number as percent value, but keep the leading + sign if found
103 *
104 * @param $value
105 * @return string
106 */
107 public function formatPercentEvolution($value)
108 {
109 $isPositiveEvolution = !empty($value) && ($value > 0 || substr($value, 0, 1) === '+');
110 $formatted = self::formatPercent($value);
111 if ($isPositiveEvolution) {
112 // $this->symbols has already been initialized from formatPercent().
113 $language = $this->translator->getCurrentLanguage();
114 return $this->symbols[$language]['+'] . $formatted;
115 }
116 return $formatted;
117 }
118 /**
119 * Formats given number as percent value
120 * @param string|int|float $value
121 * @param string $currency
122 * @param int $precision
123 * @return mixed|string
124 */
125 public function formatCurrency($value, $currency, $precision = 2)
126 {
127 $newValue = trim(strval($value), " \x00\v{$currency}");
128 if (!is_numeric($newValue)) {
129 return $value;
130 }
131 $pattern = $this->getPattern($value, 'Intl_NumberFormatCurrency');
132 if ($newValue == round($newValue)) {
133 // if no fraction digits available, don't show any
134 $value = $this->formatNumberWithPattern($pattern, $newValue, 0, 0);
135 } else {
136 // show given count of fraction digits otherwise
137 $value = $this->formatNumberWithPattern($pattern, $newValue, $precision, $precision);
138 }
139 return str_replace('¤', $currency, $value);
140 }
141 /**
142 * Returns the relevant pattern for the given number.
143 *
144 * @param string $value
145 * @param string $translationId
146 * @return string
147 */
148 protected function getPattern($value, $translationId)
149 {
150 $language = $this->translator->getCurrentLanguage();
151 if (!isset($this->patterns[$language][$translationId])) {
152 $this->patterns[$language][$translationId] = $this->parsePattern($this->translator->translate($translationId));
153 }
154 list($positivePattern, $negativePattern) = $this->patterns[$language][$translationId];
155 $negative = $this->isNegative($value);
156 return $negative ? $negativePattern : $positivePattern;
157 }
158 /**
159 * Formats the given number with the given pattern
160 *
161 * @param string $pattern
162 * @param string|int|float $value
163 * @param int $maximumFractionDigits
164 * @param int $minimumFractionDigits
165 * @return mixed|string
166 */
167 protected function formatNumberWithPattern($pattern, $value, $maximumFractionDigits = 0, $minimumFractionDigits = 0)
168 {
169 if (!is_numeric($value)) {
170 return $value;
171 }
172 $usesGrouping = strpos($pattern, ',') !== false;
173 // if pattern has number groups, parse them.
174 if ($usesGrouping) {
175 preg_match('/#+0/', $pattern, $primaryGroupMatches);
176 $primaryGroupSize = $secondaryGroupSize = strlen($primaryGroupMatches[0]);
177 $numberGroups = explode(',', $pattern);
178 // check for distinct secondary group size.
179 if (count($numberGroups) > 2) {
180 $secondaryGroupSize = strlen($numberGroups[1]);
181 }
182 }
183 // Ensure that the value is positive and has the right number of digits.
184 $negative = $this->isNegative($value);
185 $signMultiplier = $negative ? '-1' : '1';
186 $value = $value / $signMultiplier;
187 $value = round($value, $maximumFractionDigits);
188 // Split the number into major and minor digits.
189 $valueParts = explode('.', $value);
190 $majorDigits = $valueParts[0];
191 // Account for maximumFractionDigits = 0, where the number won't
192 // have a decimal point, and $valueParts[1] won't be set.
193 $minorDigits = isset($valueParts[1]) ? $valueParts[1] : '';
194 if ($usesGrouping) {
195 // Reverse the major digits, since they are grouped from the right.
196 $majorDigits = array_reverse(str_split($majorDigits));
197 // Group the major digits.
198 $groups = array();
199 $groups[] = array_splice($majorDigits, 0, $primaryGroupSize);
200 while (!empty($majorDigits)) {
201 $groups[] = array_splice($majorDigits, 0, $secondaryGroupSize);
202 }
203 // Reverse the groups and the digits inside of them.
204 $groups = array_reverse($groups);
205 foreach ($groups as &$group) {
206 $group = implode(array_reverse($group));
207 }
208 // Reconstruct the major digits.
209 $majorDigits = implode(',', $groups);
210 }
211 if ($minimumFractionDigits <= $maximumFractionDigits) {
212 // Strip any trailing zeroes.
213 $minorDigits = rtrim($minorDigits, '0');
214 if (strlen($minorDigits) < $minimumFractionDigits) {
215 // Now there are too few digits, re-add trailing zeroes
216 // until the desired length is reached.
217 $neededZeroes = $minimumFractionDigits - strlen($minorDigits);
218 $minorDigits .= str_repeat('0', $neededZeroes);
219 }
220 }
221 // Assemble the final number and insert it into the pattern.
222 $value = strlen($minorDigits) ? $majorDigits . '.' . $minorDigits : $majorDigits;
223 $value = preg_replace('/#(?:[\\.,]#+)*0(?:[,\\.][0#]+)*/', $value, $pattern);
224 // Localize the number.
225 $value = $this->replaceSymbols($value);
226 return $value;
227 }
228 /**
229 * Replaces number symbols with their localized equivalents.
230 *
231 * @param string $value The value being formatted.
232 *
233 * @return string
234 *
235 * @see http://cldr.unicode.org/translation/number-symbols
236 */
237 protected function replaceSymbols($value)
238 {
239 $language = $this->translator->getCurrentLanguage();
240 if (!isset($this->symbols[$language])) {
241 $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'));
242 }
243 return strtr($value, $this->symbols[$language]);
244 }
245 /**
246 * @param $value
247 * @return bool
248 */
249 protected function isNegative($value)
250 {
251 return $value < 0;
252 }
253 /**
254 * @deprecated
255 * @return self
256 */
257 public static function getInstance()
258 {
259 return StaticContainer::get(\Piwik\NumberFormatter::class);
260 }
261 public function clearCache()
262 {
263 $this->patterns = [];
264 $this->symbols = [];
265 }
266 }
267