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 / Twig.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
Twig.php
591 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
11 use Exception;
12 use Piwik\Container\StaticContainer;
13 use Piwik\DataTable\Filter\SafeDecodeLabel;
14 use Piwik\Metrics\Formatter;
15 use Piwik\Plugin\Manager;
16 use Piwik\Tracker\GoalManager;
17 use Piwik\View\RenderTokenParser;
18 use Piwik\Visualization\Sparkline;
19 use Twig_Environment;
20 use Twig_Extension_Debug;
21 use Twig_Loader_Chain;
22 use Twig_Loader_Filesystem;
23 use Twig_SimpleFilter;
24 use Twig_SimpleFunction;
25 use Twig_SimpleTest;
26
27 function piwik_filter_truncate($string, $size)
28 {
29 if (Common::mb_strlen(html_entity_decode($string)) <= $size) {
30 return $string;
31 } else {
32 preg_match('/^(&(?:[a-z\d]+|#\d+|#x[a-f\d]+);|.){'.$size.'}/i', $string, $shortenString);
33 return reset($shortenString) . "...";
34 }
35 }
36
37 function piwik_format_number($string, $minFractionDigits, $maxFractionDigits)
38 {
39 $formatter = NumberFormatter::getInstance();
40 return $formatter->format($string, $minFractionDigits, $maxFractionDigits);
41 }
42
43 function piwik_fix_lbrace($string)
44 {
45 $chars = array('{', '&#x7B;', '&#123;', '&lcub;', '&lbrace;', '&#x0007B;');
46
47 static $search;
48 static $replace;
49
50 if (!isset($search)) {
51 $search = array_map(function ($val) { return $val . $val; }, $chars);
52 }
53 if (!isset($replace)) {
54 $replace = array_map(function ($val) { return $val . '&#8291;' . $val; }, $chars);
55 }
56
57 $replacedString = str_replace($search, $replace, $string);
58
59 // try to replace characters until there are no changes
60 if ($string !== $replacedString) {
61 return piwik_fix_lbrace($replacedString);
62 }
63
64 return $string;
65 }
66
67 function piwik_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false) {
68
69 $string = twig_escape_filter($env, $string, $strategy, $charset, $autoescape);
70
71 switch ($strategy) {
72 case 'html':
73 case 'html_attr':
74 return piwik_fix_lbrace($string);
75 case 'url':
76 $encoded = rawurlencode('{');
77 return str_replace('{{', $encoded . $encoded, $string);
78 case 'css':
79 case 'js':
80 default:
81 return $string;
82 }
83 }
84
85 function piwik_format_money($amount, $idSite)
86 {
87 $currencySymbol = Site::getCurrencySymbolFor($idSite);
88 $numberFormatter = NumberFormatter::getInstance();
89 return $numberFormatter->formatCurrency($amount, $currencySymbol, GoalManager::REVENUE_PRECISION);
90 }
91
92 class PiwikTwigFilterExtension extends \Twig_Extension
93 {
94 public function getFilters()
95 {
96 return array(
97 new Twig_SimpleFilter('e', '\Piwik\piwik_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
98 new Twig_SimpleFilter('escape', '\Piwik\piwik_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe'))
99 );
100 }
101
102 /**
103 * Returns the name of the extension.
104 *
105 * @return string The extension name
106 */
107 public function getName()
108 {
109 return 'escaper2';
110 }
111 }
112
113 /**
114 * Twig class
115 *
116 */
117 class Twig
118 {
119 const SPARKLINE_TEMPLATE = '<img alt="" data-src="%s" width="%d" height="%d" />
120 <script type="text/javascript">$(function() { piwik.initSparklines(); });</script>';
121
122 /**
123 * @var Twig_Environment
124 */
125 private $twig;
126
127 private $formatter;
128
129 public function __construct()
130 {
131 $loader = $this->getDefaultThemeLoader();
132 $this->addPluginNamespaces($loader);
133
134 //get current theme
135 $manager = Plugin\Manager::getInstance();
136 $theme = $manager->getThemeEnabled();
137 $loaders = array();
138
139 $this->formatter = new Formatter();
140
141 //create loader for custom theme to overwrite twig templates
142 if ($theme && $theme->getPluginName() != \Piwik\Plugin\Manager::DEFAULT_THEME) {
143 $customLoader = $this->getCustomThemeLoader($theme);
144 if ($customLoader) {
145 //make it possible to overwrite plugin templates
146 $this->addCustomPluginNamespaces($customLoader, $theme->getPluginName());
147 $loaders[] = $customLoader;
148 }
149 }
150
151 $loaders[] = $loader;
152
153 $chainLoader = new Twig_Loader_Chain($loaders);
154
155 // Create new Twig Environment and set cache dir
156 $templatesCompiledPath = StaticContainer::get('path.tmp') . '/templates_c';
157
158 $this->twig = new Twig_Environment($chainLoader,
159 array(
160 'debug' => true, // to use {{ dump(var) }} in twig templates
161 'strict_variables' => true, // throw an exception if variables are invalid
162 'cache' => $templatesCompiledPath,
163 )
164 );
165 $this->twig->addExtension(new Twig_Extension_Debug());
166 $this->twig->clearTemplateCache();
167
168 $this->addFilter_translate();
169 $this->addFilter_urlRewriteWithParameters();
170 $this->addFilter_sumTime();
171 $this->addFilter_money();
172 $this->addFilter_truncate();
173 $this->addFilter_notification();
174 $this->addFilter_percent();
175 $this->addFilter_percentage();
176 $this->addFilter_percentEvolution();
177 $this->addFilter_prettyDate();
178 $this->addFilter_safeDecodeRaw();
179 $this->addFilter_number();
180 $this->addFilter_nonce();
181 $this->addFilter_md5();
182 $this->addFilter_onlyDomain();
183 $this->addFilter_safelink();
184 $this->twig->addFilter(new Twig_SimpleFilter('implode', 'implode'));
185 $this->twig->addFilter(new Twig_SimpleFilter('ucwords', 'ucwords'));
186 $this->twig->addFilter(new Twig_SimpleFilter('lcfirst', 'lcfirst'));
187 $this->twig->addFilter(new Twig_SimpleFilter('ucfirst', 'ucfirst'));
188
189 $this->addFunction_includeAssets();
190 $this->addFunction_linkTo();
191 $this->addFunction_sparkline();
192 $this->addFunction_postEvent();
193 $this->addFunction_isPluginLoaded();
194 $this->addFunction_getJavascriptTranslations();
195
196 $this->twig->addTokenParser(new RenderTokenParser());
197
198 $this->addTest_false();
199 $this->addTest_true();
200 $this->addTest_emptyString();
201 $this->addTest_isNumeric();
202
203 $this->twig->addExtension(new PiwikTwigFilterExtension());
204 }
205
206 private function addTest_false()
207 {
208 $test = new Twig_SimpleTest(
209 'false',
210 function ($value) {
211 return false === $value;
212 }
213 );
214 $this->twig->addTest($test);
215 }
216
217 private function addTest_true()
218 {
219 $test = new Twig_SimpleTest(
220 'true',
221 function ($value) {
222 return true === $value;
223 }
224 );
225 $this->twig->addTest($test);
226 }
227
228 private function addTest_emptyString()
229 {
230 $test = new Twig_SimpleTest(
231 'emptyString',
232 function ($value) {
233 return '' === $value;
234 }
235 );
236 $this->twig->addTest($test);
237 }
238
239 protected function addFunction_getJavascriptTranslations()
240 {
241 $getJavascriptTranslations = new Twig_SimpleFunction(
242 'getJavascriptTranslations',
243 array('Piwik\\Translate', 'getJavascriptTranslations')
244 );
245 $this->twig->addFunction($getJavascriptTranslations);
246 }
247
248 protected function addFunction_isPluginLoaded()
249 {
250 $isPluginLoadedFunction = new Twig_SimpleFunction('isPluginLoaded', function ($pluginName) {
251 return \Piwik\Plugin\Manager::getInstance()->isPluginLoaded($pluginName);
252 });
253 $this->twig->addFunction($isPluginLoadedFunction);
254 }
255
256 protected function addFunction_includeAssets()
257 {
258 $includeAssetsFunction = new Twig_SimpleFunction('includeAssets', function ($params) {
259 if (!isset($params['type'])) {
260 throw new Exception("The function includeAssets needs a 'type' parameter.");
261 }
262
263 $assetType = strtolower($params['type']);
264 switch ($assetType) {
265 case 'css':
266 return AssetManager::getInstance()->getCssInclusionDirective();
267 case 'js':
268 return AssetManager::getInstance()->getJsInclusionDirective();
269 default:
270 throw new Exception("The twig function includeAssets 'type' parameter needs to be either 'css' or 'js'.");
271 }
272 });
273 $this->twig->addFunction($includeAssetsFunction);
274 }
275
276 protected function addFunction_postEvent()
277 {
278 $postEventFunction = new Twig_SimpleFunction('postEvent', function ($eventName) {
279 // get parameters to twig function
280 $params = func_get_args();
281 // remove the first value (event name)
282 array_shift($params);
283
284 // make the first value the string that will get output in the template
285 // plugins can modify this string
286 $str = '';
287 $params = array_merge(array( &$str ), $params);
288
289 Piwik::postEvent($eventName, $params);
290 return $str;
291 }, array('is_safe' => array('html')));
292 $this->twig->addFunction($postEventFunction);
293 }
294
295 protected function addFunction_sparkline()
296 {
297 $sparklineFunction = new Twig_SimpleFunction('sparkline', function ($src) {
298 $width = Sparkline::DEFAULT_WIDTH;
299 $height = Sparkline::DEFAULT_HEIGHT;
300 return sprintf(Twig::SPARKLINE_TEMPLATE, $src, $width, $height);
301 }, array('is_safe' => array('html')));
302 $this->twig->addFunction($sparklineFunction);
303 }
304
305 protected function addFunction_linkTo()
306 {
307 $urlFunction = new Twig_SimpleFunction('linkTo', function ($params) {
308 return 'index.php' . Url::getCurrentQueryStringWithParametersModified($params);
309 });
310 $this->twig->addFunction($urlFunction);
311 }
312
313 /**
314 * @return Twig_Loader_Filesystem
315 */
316 private function getDefaultThemeLoader()
317 {
318 $themeDir = Manager::getPluginDirectory(\Piwik\Plugin\Manager::DEFAULT_THEME) . '/templates/';
319 $themeLoader = new Twig_Loader_Filesystem(array($themeDir), PIWIK_DOCUMENT_ROOT.DIRECTORY_SEPARATOR);
320
321 return $themeLoader;
322 }
323
324 /**
325 * create template loader for a custom theme
326 * @param \Piwik\Plugin $theme
327 * @return \Twig_Loader_Filesystem
328 */
329 protected function getCustomThemeLoader(Plugin $theme)
330 {
331 $pluginsDir = Manager::getPluginDirectory($theme->getPluginName());
332 $themeDir = $pluginsDir . '/templates/';
333
334 if (!file_exists($themeDir)) {
335 return false;
336 }
337 $themeLoader = new Twig_Loader_Filesystem(array($themeDir), PIWIK_DOCUMENT_ROOT.DIRECTORY_SEPARATOR);
338
339 return $themeLoader;
340 }
341
342 public function getTwigEnvironment()
343 {
344 return $this->twig;
345 }
346
347 protected function addFilter_notification()
348 {
349 $twigEnv = $this->getTwigEnvironment();
350 $notificationFunction = new Twig_SimpleFilter('notification', function ($message, $options) use ($twigEnv) {
351
352 $template = '<div style="display:none" data-role="notification" ';
353
354 foreach ($options as $key => $value) {
355 if (ctype_alpha($key)) {
356 $template .= sprintf('data-%s="%s" ', $key, twig_escape_filter($twigEnv, $value, 'html_attr'));
357 }
358 }
359
360 $template .= '>';
361
362 if (!empty($options['raw'])) {
363 $template .= piwik_fix_lbrace($message);
364 } else {
365 $template .= piwik_escape_filter($twigEnv, $message, 'html');
366 }
367
368 $template .= '</div>';
369
370 return $template;
371
372 }, array('is_safe' => array('html')));
373 $this->twig->addFilter($notificationFunction);
374 }
375
376 protected function addFilter_safeDecodeRaw()
377 {
378 $rawSafeDecoded = new Twig_SimpleFilter('rawSafeDecoded', function ($string) {
379 $string = str_replace('+', '%2B', $string);
380 $string = str_replace('&nbsp;', html_entity_decode('&nbsp;', ENT_COMPAT | ENT_HTML401, 'UTF-8'), $string);
381
382 $string = SafeDecodeLabel::decodeLabelSafe($string);
383
384 return piwik_fix_lbrace($string);
385
386 }, array('is_safe' => array('all')));
387 $this->twig->addFilter($rawSafeDecoded);
388 }
389
390 protected function addFilter_prettyDate()
391 {
392 $prettyDate = new Twig_SimpleFilter('prettyDate', function ($dateString, $period) {
393 return Period\Factory::build($period, $dateString)->getLocalizedShortString();
394 });
395 $this->twig->addFilter($prettyDate);
396 }
397
398 protected function addFilter_percentage()
399 {
400 $percentage = new Twig_SimpleFilter('percentage', function ($string, $totalValue, $precision = 1) {
401 $formatter = NumberFormatter::getInstance();
402 return $formatter->formatPercent(Piwik::getPercentageSafe($string, $totalValue, $precision), $precision);
403 });
404 $this->twig->addFilter($percentage);
405 }
406
407 protected function addFilter_percent()
408 {
409 $percentage = new Twig_SimpleFilter('percent', function ($string, $precision = 1) {
410 $formatter = NumberFormatter::getInstance();
411 return $formatter->formatPercent($string, $precision);
412 });
413 $this->twig->addFilter($percentage);
414 }
415
416 protected function addFilter_percentEvolution()
417 {
418 $percentage = new Twig_SimpleFilter('percentEvolution', function ($string) {
419 $formatter = NumberFormatter::getInstance();
420 return $formatter->formatPercentEvolution($string);
421 });
422 $this->twig->addFilter($percentage);
423 }
424
425 private function getProfessionalServicesAdvertising()
426 {
427 return StaticContainer::get('Piwik\ProfessionalServices\Advertising');
428 }
429
430 protected function addFilter_number()
431 {
432 $formatter = new Twig_SimpleFilter('number', function ($string, $minFractionDigits = 0, $maxFractionDigits = 0) {
433 return piwik_format_number($string, $minFractionDigits, $maxFractionDigits);
434 });
435 $this->twig->addFilter($formatter);
436 }
437
438 protected function addFilter_nonce()
439 {
440 $nonce = new Twig_SimpleFilter('nonce', array('Piwik\\Nonce', 'getNonce'));
441 $this->twig->addFilter($nonce);
442 }
443
444 private function addFilter_md5()
445 {
446 $md5 = new \Twig_SimpleFilter('md5', function ($value) {
447 return md5($value);
448 });
449 $this->twig->addFilter($md5);
450 }
451
452 private function addFilter_onlyDomain()
453 {
454 $domainOnly = new \Twig_SimpleFilter('domainOnly', function ($url) {
455 $parsed = parse_url($url);
456 return $parsed['scheme'] . '://' . $parsed['host'];
457 });
458 $this->twig->addFilter($domainOnly);
459 }
460
461 protected function addFilter_truncate()
462 {
463 $truncateFilter = new Twig_SimpleFilter('truncate', function ($string, $size) {
464 return piwik_filter_truncate($string, $size);
465 });
466 $this->twig->addFilter($truncateFilter);
467 }
468
469 protected function addFilter_money()
470 {
471 $moneyFilter = new Twig_SimpleFilter('money', function ($amount) {
472 if (func_num_args() != 2) {
473 throw new Exception('the money modifier expects one parameter: the idSite.');
474 }
475 $idSite = func_get_args();
476 $idSite = $idSite[1];
477 return piwik_format_money($amount, $idSite);
478 });
479 $this->twig->addFilter($moneyFilter);
480 }
481
482 protected function addFilter_sumTime()
483 {
484 $formatter = $this->formatter;
485 $sumtimeFilter = new Twig_SimpleFilter('sumtime', function ($numberOfSeconds) use ($formatter) {
486 return $formatter->getPrettyTimeFromSeconds($numberOfSeconds, true);
487 });
488 $this->twig->addFilter($sumtimeFilter);
489 }
490
491 protected function addFilter_urlRewriteWithParameters()
492 {
493 $urlRewriteFilter = new Twig_SimpleFilter('urlRewriteWithParameters', function ($parameters) {
494 $parameters['updated'] = null;
495 $url = Url::getCurrentQueryStringWithParametersModified($parameters);
496 return $url;
497 });
498 $this->twig->addFilter($urlRewriteFilter);
499 }
500
501 protected function addFilter_translate()
502 {
503 $translateFilter = new Twig_SimpleFilter('translate', function ($stringToken) {
504 if (func_num_args() <= 1) {
505 $aValues = array();
506 } else {
507 $aValues = func_get_args();
508 array_shift($aValues);
509 }
510
511 try {
512 $stringTranslated = Piwik::translate($stringToken, $aValues);
513 } catch (Exception $e) {
514 $stringTranslated = $stringToken;
515 }
516 return $stringTranslated;
517 });
518 $this->twig->addFilter($translateFilter);
519 }
520
521 private function addPluginNamespaces(Twig_Loader_Filesystem $loader)
522 {
523 $pluginManager = \Piwik\Plugin\Manager::getInstance();
524 $plugins = $pluginManager->getAllPluginsNames();
525
526 foreach ($plugins as $name) {
527 $pluginsDir = Manager::getPluginDirectory($name);
528 $path = sprintf("%s/templates/", $pluginsDir);
529 if (is_dir($path)) {
530 $loader->addPath(rtrim($path, '/'), $name);
531 }
532 }
533 }
534
535 /**
536 *
537 * Plugin-Templates can be overwritten by putting identically named templates in plugins/[theme]/templates/plugins/[plugin]/
538 *
539 */
540 private function addCustomPluginNamespaces(Twig_Loader_Filesystem $loader, $pluginName)
541 {
542 $pluginManager = \Piwik\Plugin\Manager::getInstance();
543 $plugins = $pluginManager->getAllPluginsNames();
544
545 $pluginsDir = Manager::getPluginDirectory($pluginName);
546
547 foreach ($plugins as $name) {
548 $path = sprintf("%s/templates/plugins/%s/", $pluginsDir, $name);
549 if (is_dir($path)) {
550 $loader->addPath(rtrim($path, '/'), $name);
551 }
552 }
553 }
554
555 /**
556 * Prepend relative paths with absolute Piwik path
557 *
558 * @param string $value relative path (pass by reference)
559 * @param int $key (don't care)
560 * @param string $path Piwik root
561 */
562 public static function addPiwikPath(&$value, $key, $path)
563 {
564 if ($value[0] != '/' && $value[0] != DIRECTORY_SEPARATOR) {
565 $value = $path . "/$value";
566 }
567 }
568
569 private function addFilter_safelink()
570 {
571 $safelink = new Twig_SimpleFilter('safelink', function ($url) {
572 if (!UrlHelper::isLookLikeSafeUrl($url)) {
573 return '';
574 }
575 return $url;
576 });
577 $this->twig->addFilter($safelink);
578 }
579
580 private function addTest_isNumeric()
581 {
582 $test = new Twig_SimpleTest(
583 'numeric',
584 function ($value) {
585 return is_numeric($value);
586 }
587 );
588 $this->twig->addTest($test);
589 }
590 }
591