API
1 year ago
Access
1 year ago
Application
1 year ago
Archive
1 year ago
ArchiveProcessor
1 year ago
Archiver
2 years ago
AssetManager
1 year ago
Auth
1 year ago
Category
2 years ago
Changes
1 year ago
CliMulti
1 year ago
Columns
1 year ago
Concurrency
1 year ago
Config
1 year ago
Container
1 year ago
CronArchive
1 year ago
DataAccess
1 year ago
DataFiles
2 years ago
DataTable
1 year ago
Db
1 year ago
DeviceDetector
1 year ago
Email
2 years ago
Exception
1 year ago
Http
1 year ago
Intl
1 year ago
Log
2 years ago
Mail
1 year ago
Measurable
1 year ago
Menu
1 year ago
Metrics
1 year ago
Notification
1 year ago
Period
1 year ago
Plugin
1 year ago
ProfessionalServices
1 year ago
Report
1 year ago
ReportRenderer
1 year ago
Scheduler
1 year ago
Segment
1 year ago
Session
1 year ago
Settings
1 year ago
Tracker
1 year ago
Translation
1 year ago
Twig
1 year ago
UpdateCheck
1 year ago
Updater
1 year ago
Updates
1 year ago
Validators
1 year ago
View
1 year ago
ViewDataTable
1 year ago
Visualization
1 year ago
Widget
1 year ago
.htaccess
2 years ago
Access.php
1 year ago
Archive.php
1 year ago
ArchiveProcessor.php
1 year ago
AssetManager.php
1 year ago
Auth.php
2 years ago
AuthResult.php
2 years ago
BaseFactory.php
2 years ago
Cache.php
2 years ago
CacheId.php
1 year ago
CliMulti.php
1 year ago
Common.php
1 year ago
Config.php
1 year ago
Console.php
1 year ago
Context.php
2 years ago
Cookie.php
1 year ago
CronArchive.php
1 year ago
DI.php
1 year ago
DataArray.php
1 year ago
DataTable.php
1 year ago
Date.php
1 year ago
Db.php
1 year ago
DbHelper.php
1 year ago
Development.php
1 year ago
ErrorHandler.php
1 year ago
EventDispatcher.php
1 year ago
ExceptionHandler.php
1 year ago
FileIntegrity.php
1 year ago
Filechecks.php
1 year ago
Filesystem.php
1 year ago
FrontController.php
1 year ago
Http.php
1 year ago
IP.php
1 year ago
Log.php
2 years ago
LogDeleter.php
1 year ago
Mail.php
1 year ago
Metrics.php
1 year ago
NoAccessException.php
2 years ago
Nonce.php
1 year ago
Notification.php
1 year ago
NumberFormatter.php
1 year ago
Option.php
1 year ago
Period.php
1 year ago
Piwik.php
1 year ago
Plugin.php
1 year ago
Process.php
1 year ago
Profiler.php
1 year ago
ProxyHeaders.php
2 years ago
ProxyHttp.php
1 year ago
QuickForm2.php
1 year ago
RankingQuery.php
1 year ago
ReportRenderer.php
1 year ago
Request.php
1 year ago
Segment.php
1 year ago
Sequence.php
2 years ago
Session.php
1 year ago
SettingsPiwik.php
1 year ago
SettingsServer.php
1 year ago
Singleton.php
2 years ago
Site.php
1 year ago
SiteContentDetector.php
1 year ago
SupportedBrowser.php
2 years ago
TCPDF.php
1 year ago
Theme.php
1 year ago
Timer.php
2 years ago
Tracker.php
1 year ago
Twig.php
1 year ago
Unzip.php
1 year ago
UpdateCheck.php
1 year ago
Updater.php
1 year ago
UpdaterErrorException.php
2 years ago
Updates.php
1 year ago
Url.php
1 year ago
UrlHelper.php
1 year ago
Version.php
1 year ago
View.php
1 year ago
bootstrap.php
1 year ago
dispatch.php
2 years ago
testMinimumPhpVersion.php
2 years ago
Twig.php
562 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 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\Translation\Translator; |
| 18 | use Piwik\Twig\Extension\EscapeFilter; |
| 19 | use Piwik\View\RenderTokenParser; |
| 20 | use Piwik\Visualization\Sparkline; |
| 21 | use Matomo\Dependencies\Twig\Environment; |
| 22 | use Matomo\Dependencies\Twig\Extension\DebugExtension; |
| 23 | use Matomo\Dependencies\Twig\Loader\ChainLoader; |
| 24 | use Matomo\Dependencies\Twig\Loader\FilesystemLoader; |
| 25 | use Matomo\Dependencies\Twig\TwigFilter; |
| 26 | use Matomo\Dependencies\Twig\TwigFunction; |
| 27 | use Matomo\Dependencies\Twig\TwigTest; |
| 28 | function piwik_filter_truncate($string, $size) |
| 29 | { |
| 30 | if (mb_strlen(html_entity_decode($string)) <= $size) { |
| 31 | return $string; |
| 32 | } else { |
| 33 | preg_match('/^(&(?:[a-z\\d]+|#\\d+|#x[a-f\\d]+);|.){' . $size . '}/i', $string, $shortenString); |
| 34 | return reset($shortenString) . "..."; |
| 35 | } |
| 36 | } |
| 37 | function piwik_format_number($string, $minFractionDigits, $maxFractionDigits) |
| 38 | { |
| 39 | $formatter = \Piwik\NumberFormatter::getInstance(); |
| 40 | return $formatter->format($string, $minFractionDigits, $maxFractionDigits); |
| 41 | } |
| 42 | function piwik_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = \false) |
| 43 | { |
| 44 | $string = \Matomo\Dependencies\twig_escape_filter($env, $string, $strategy, $charset, $autoescape); |
| 45 | switch ($strategy) { |
| 46 | case 'url': |
| 47 | $encoded = rawurlencode('{'); |
| 48 | return str_replace('{{', $encoded . $encoded, $string); |
| 49 | default: |
| 50 | return $string; |
| 51 | } |
| 52 | } |
| 53 | function piwik_format_money($amount, $idSite) |
| 54 | { |
| 55 | $currencySymbol = \Piwik\Site::getCurrencySymbolFor($idSite); |
| 56 | $numberFormatter = \Piwik\NumberFormatter::getInstance(); |
| 57 | return $numberFormatter->formatCurrency($amount, $currencySymbol, GoalManager::REVENUE_PRECISION); |
| 58 | } |
| 59 | /** |
| 60 | * Twig class |
| 61 | * |
| 62 | */ |
| 63 | class Twig |
| 64 | { |
| 65 | public const SPARKLINE_TEMPLATE = '<img loading="lazy" alt="" data-src="%s" width="%d" height="%d" /> |
| 66 | <script type="text/javascript">$(function() { piwik.initSparklines(); });</script>'; |
| 67 | /** |
| 68 | * @var Environment |
| 69 | */ |
| 70 | private $twig; |
| 71 | private $formatter; |
| 72 | public function __construct() |
| 73 | { |
| 74 | $loader = $this->getDefaultThemeLoader(); |
| 75 | $this->addPluginNamespaces($loader); |
| 76 | //get current theme |
| 77 | $manager = \Piwik\Plugin\Manager::getInstance(); |
| 78 | $theme = $manager->getThemeEnabled(); |
| 79 | $loaders = array(); |
| 80 | $this->formatter = new Formatter(); |
| 81 | //create loader for custom theme to overwrite twig templates |
| 82 | if ($theme && $theme->getPluginName() != \Piwik\Plugin\Manager::DEFAULT_THEME) { |
| 83 | $customLoader = $this->getCustomThemeLoader($theme); |
| 84 | if ($customLoader) { |
| 85 | //make it possible to overwrite plugin templates |
| 86 | $this->addCustomPluginNamespaces($customLoader, $theme->getPluginName()); |
| 87 | $loaders[] = $customLoader; |
| 88 | } |
| 89 | } |
| 90 | $loaders[] = $loader; |
| 91 | $chainLoader = new ChainLoader($loaders); |
| 92 | // Create new Twig Environment and set cache dir |
| 93 | $cache = StaticContainer::get('twig.cache'); |
| 94 | $this->twig = new Environment($chainLoader, array( |
| 95 | 'debug' => \true, |
| 96 | // to use {{ dump(var) }} in twig templates |
| 97 | 'strict_variables' => \true, |
| 98 | // throw an exception if variables are invalid |
| 99 | 'cache' => $cache, |
| 100 | )); |
| 101 | $this->twig->addExtension(new DebugExtension()); |
| 102 | $this->addFilterTranslate(); |
| 103 | $this->addFilterListings(); |
| 104 | $this->addFilterUrlRewriteWithParameters(); |
| 105 | $this->addFilterSumTime(); |
| 106 | $this->addFilterMoney(); |
| 107 | $this->addFilterTruncate(); |
| 108 | $this->addFilterNotification(); |
| 109 | $this->addFilterPercent(); |
| 110 | $this->addFilterPercentage(); |
| 111 | $this->addFilterPercentEvolution(); |
| 112 | $this->addFilterPrettyDate(); |
| 113 | $this->addFilterSafeDecodeRaw(); |
| 114 | $this->addFilterNumber(); |
| 115 | $this->addFilterAnonymiseSystemInfo(); |
| 116 | $this->addFilterNonce(); |
| 117 | $this->addFilterMd5(); |
| 118 | $this->addFilterOnlyDomain(); |
| 119 | $this->addFilterSafelink(); |
| 120 | $this->addFilterTrackMatomoLink(); |
| 121 | $this->addFilterImplode(); |
| 122 | $this->twig->addFilter(new TwigFilter('ucwords', 'ucwords')); |
| 123 | $this->twig->addFilter(new TwigFilter('lcfirst', 'lcfirst')); |
| 124 | $this->twig->addFilter(new TwigFilter('ucfirst', 'ucfirst')); |
| 125 | $this->twig->addFilter(new TwigFilter('preg_replace', function ($subject, $pattern, $replacement) { |
| 126 | return preg_replace($pattern, $replacement, $subject); |
| 127 | })); |
| 128 | $this->addFunctionExternalLink(); |
| 129 | $this->addFunctionExternalRawLink(); |
| 130 | $this->addFunctionIncludeAssets(); |
| 131 | $this->addFunctionLinkTo(); |
| 132 | $this->addFunctionSparkline(); |
| 133 | $this->addFunctionPostEvent(); |
| 134 | $this->addFunctionIsPluginLoaded(); |
| 135 | $this->addFunctionGetJavascriptTranslations(); |
| 136 | $this->twig->addTokenParser(new RenderTokenParser()); |
| 137 | $this->addTestFalse(); |
| 138 | $this->addTestTrue(); |
| 139 | $this->addTestEmptyString(); |
| 140 | $this->addTestIsNumeric(); |
| 141 | $this->twig->addExtension(new EscapeFilter()); |
| 142 | } |
| 143 | private function addTestFalse() |
| 144 | { |
| 145 | $test = new TwigTest('false', function ($value) { |
| 146 | return \false === $value; |
| 147 | }); |
| 148 | $this->twig->addTest($test); |
| 149 | } |
| 150 | private function addTestTrue() |
| 151 | { |
| 152 | $test = new TwigTest('true', function ($value) { |
| 153 | return \true === $value; |
| 154 | }); |
| 155 | $this->twig->addTest($test); |
| 156 | } |
| 157 | private function addTestEmptyString() |
| 158 | { |
| 159 | $test = new TwigTest('emptyString', function ($value) { |
| 160 | return '' === $value; |
| 161 | }); |
| 162 | $this->twig->addTest($test); |
| 163 | } |
| 164 | protected function addFunctionGetJavascriptTranslations() |
| 165 | { |
| 166 | $getJavascriptTranslations = new TwigFunction('getJavascriptTranslations', array(StaticContainer::get('Piwik\\Translation\\Translator'), 'getJavascriptTranslations')); |
| 167 | $this->twig->addFunction($getJavascriptTranslations); |
| 168 | } |
| 169 | protected function addFunctionIsPluginLoaded() |
| 170 | { |
| 171 | $isPluginLoadedFunction = new TwigFunction('isPluginLoaded', function ($pluginName) { |
| 172 | return \Piwik\Plugin\Manager::getInstance()->isPluginLoaded($pluginName); |
| 173 | }); |
| 174 | $this->twig->addFunction($isPluginLoadedFunction); |
| 175 | } |
| 176 | protected function addFunctionIncludeAssets() |
| 177 | { |
| 178 | $includeAssetsFunction = new TwigFunction('includeAssets', function ($params) { |
| 179 | if (!isset($params['type'])) { |
| 180 | throw new Exception("The function includeAssets needs a 'type' parameter."); |
| 181 | } |
| 182 | $assetType = strtolower($params['type']); |
| 183 | $deferJs = boolval($params['defer'] ?? \false); |
| 184 | switch ($assetType) { |
| 185 | case 'css': |
| 186 | return \Piwik\AssetManager::getInstance()->getCssInclusionDirective(); |
| 187 | case 'js': |
| 188 | return \Piwik\AssetManager::getInstance()->getJsInclusionDirective($deferJs); |
| 189 | default: |
| 190 | throw new Exception("The twig function includeAssets 'type' parameter needs to be either 'css' or 'js'."); |
| 191 | } |
| 192 | }); |
| 193 | $this->twig->addFunction($includeAssetsFunction); |
| 194 | } |
| 195 | protected function addFunctionPostEvent() |
| 196 | { |
| 197 | $postEventFunction = new TwigFunction('postEvent', function ($eventName) { |
| 198 | // get parameters to twig function |
| 199 | $params = func_get_args(); |
| 200 | // remove the first value (event name) |
| 201 | array_shift($params); |
| 202 | // make the first value the string that will get output in the template |
| 203 | // plugins can modify this string |
| 204 | $str = ''; |
| 205 | $params = array_merge(array(&$str), $params); |
| 206 | \Piwik\Piwik::postEvent($eventName, $params); |
| 207 | return $str; |
| 208 | }, array('is_safe' => array('html'))); |
| 209 | $this->twig->addFunction($postEventFunction); |
| 210 | } |
| 211 | protected function addFunctionSparkline() |
| 212 | { |
| 213 | $sparklineFunction = new TwigFunction('sparkline', function ($src) { |
| 214 | $width = Sparkline::DEFAULT_WIDTH; |
| 215 | $height = Sparkline::DEFAULT_HEIGHT; |
| 216 | return sprintf(\Piwik\Twig::SPARKLINE_TEMPLATE, $src, $width, $height); |
| 217 | }, array('is_safe' => array('html'))); |
| 218 | $this->twig->addFunction($sparklineFunction); |
| 219 | } |
| 220 | protected function addFunctionLinkTo() |
| 221 | { |
| 222 | $urlFunction = new TwigFunction('linkTo', function ($params) { |
| 223 | return 'index.php' . \Piwik\Url::getCurrentQueryStringWithParametersModified($params); |
| 224 | }); |
| 225 | $this->twig->addFunction($urlFunction); |
| 226 | } |
| 227 | /** |
| 228 | * Build an external link for a URL |
| 229 | * |
| 230 | * Usage: |
| 231 | * externallink(url) |
| 232 | * |
| 233 | */ |
| 234 | private function addFunctionExternalLink() |
| 235 | { |
| 236 | $externalLink = new TwigFunction('externallink', function ($url) { |
| 237 | // Add tracking parameters if a matomo.org link |
| 238 | $url = \Piwik\Url::addCampaignParametersToMatomoLink($url); |
| 239 | return "<a target='_blank' rel='noreferrer noopener' href='" . $url . "'>"; |
| 240 | }); |
| 241 | $this->twig->addFunction($externalLink); |
| 242 | } |
| 243 | private function addFunctionExternalRawLink() |
| 244 | { |
| 245 | $externalRawLink = new TwigFunction('externalrawlink', function ($url) { |
| 246 | // Add tracking parameters if a matomo.org link |
| 247 | return \Piwik\Url::addCampaignParametersToMatomoLink($url); |
| 248 | }); |
| 249 | $this->twig->addFunction($externalRawLink); |
| 250 | } |
| 251 | /** |
| 252 | * @return FilesystemLoader |
| 253 | */ |
| 254 | private function getDefaultThemeLoader() |
| 255 | { |
| 256 | $themeDir = Manager::getPluginDirectory(\Piwik\Plugin\Manager::DEFAULT_THEME) . '/templates/'; |
| 257 | $themeLoader = new FilesystemLoader(array($themeDir), PIWIK_DOCUMENT_ROOT . \DIRECTORY_SEPARATOR); |
| 258 | return $themeLoader; |
| 259 | } |
| 260 | /** |
| 261 | * create template loader for a custom theme |
| 262 | * @param \Piwik\Plugin $theme |
| 263 | * @return FilesystemLoader|bool |
| 264 | */ |
| 265 | protected function getCustomThemeLoader(\Piwik\Plugin $theme) |
| 266 | { |
| 267 | $pluginsDir = Manager::getPluginDirectory($theme->getPluginName()); |
| 268 | $themeDir = $pluginsDir . '/templates/'; |
| 269 | if (!file_exists($themeDir)) { |
| 270 | return \false; |
| 271 | } |
| 272 | $themeLoader = new FilesystemLoader(array($themeDir), PIWIK_DOCUMENT_ROOT . \DIRECTORY_SEPARATOR); |
| 273 | return $themeLoader; |
| 274 | } |
| 275 | public function getTwigEnvironment() |
| 276 | { |
| 277 | return $this->twig; |
| 278 | } |
| 279 | protected function addFilterNotification() |
| 280 | { |
| 281 | $twigEnv = $this->getTwigEnvironment(); |
| 282 | $notificationFunction = new TwigFilter('notification', function ($message, $options) use($twigEnv) { |
| 283 | $template = '<div style="display:none" data-role="notification" '; |
| 284 | foreach ($options as $key => $value) { |
| 285 | if (ctype_alpha($key)) { |
| 286 | $template .= sprintf('data-%s="%s" ', $key, \Matomo\Dependencies\twig_escape_filter($twigEnv, $value, 'html_attr')); |
| 287 | } |
| 288 | } |
| 289 | $template .= '>'; |
| 290 | if (!empty($options['raw'])) { |
| 291 | $template .= $message; |
| 292 | } else { |
| 293 | $template .= piwik_escape_filter($twigEnv, $message, 'html'); |
| 294 | } |
| 295 | $template .= '</div>'; |
| 296 | return $template; |
| 297 | }, array('is_safe' => array('html'))); |
| 298 | $this->twig->addFilter($notificationFunction); |
| 299 | } |
| 300 | protected function addFilterSafeDecodeRaw() |
| 301 | { |
| 302 | $rawSafeDecoded = new TwigFilter('rawSafeDecoded', function ($string) { |
| 303 | if ($string === null) { |
| 304 | return ''; |
| 305 | } |
| 306 | $string = str_replace('+', '%2B', $string); |
| 307 | $string = str_replace(' ', html_entity_decode(' ', \ENT_COMPAT | \ENT_HTML401, 'UTF-8'), $string); |
| 308 | $string = SafeDecodeLabel::decodeLabelSafe($string); |
| 309 | return $string; |
| 310 | }, array('is_safe' => array('all'))); |
| 311 | $this->twig->addFilter($rawSafeDecoded); |
| 312 | } |
| 313 | protected function addFilterPrettyDate() |
| 314 | { |
| 315 | $prettyDate = new TwigFilter('prettyDate', function ($dateString, $period) { |
| 316 | return \Piwik\Period\Factory::build($period, $dateString)->getLocalizedShortString(); |
| 317 | }); |
| 318 | $this->twig->addFilter($prettyDate); |
| 319 | } |
| 320 | protected function addFilterPercentage() |
| 321 | { |
| 322 | $percentage = new TwigFilter('percentage', function ($string, $totalValue, $precision = 1) { |
| 323 | $formatter = \Piwik\NumberFormatter::getInstance(); |
| 324 | return $formatter->formatPercent(\Piwik\Piwik::getPercentageSafe($string, $totalValue, $precision), $precision); |
| 325 | }); |
| 326 | $this->twig->addFilter($percentage); |
| 327 | } |
| 328 | protected function addFilterPercent() |
| 329 | { |
| 330 | $percentage = new TwigFilter('percent', function ($string, $precision = 1) { |
| 331 | $formatter = \Piwik\NumberFormatter::getInstance(); |
| 332 | return $formatter->formatPercent($string, $precision); |
| 333 | }); |
| 334 | $this->twig->addFilter($percentage); |
| 335 | } |
| 336 | protected function addFilterPercentEvolution() |
| 337 | { |
| 338 | $percentage = new TwigFilter('percentEvolution', function ($string) { |
| 339 | $formatter = \Piwik\NumberFormatter::getInstance(); |
| 340 | return $formatter->formatPercentEvolution($string); |
| 341 | }); |
| 342 | $this->twig->addFilter($percentage); |
| 343 | } |
| 344 | private function getProfessionalServicesAdvertising() |
| 345 | { |
| 346 | return StaticContainer::get('Piwik\\ProfessionalServices\\Advertising'); |
| 347 | } |
| 348 | protected function addFilterNumber() |
| 349 | { |
| 350 | $formatter = new TwigFilter('number', function ($string, $minFractionDigits = 0, $maxFractionDigits = 0) { |
| 351 | return piwik_format_number($string, $minFractionDigits, $maxFractionDigits); |
| 352 | }); |
| 353 | $this->twig->addFilter($formatter); |
| 354 | } |
| 355 | protected function addFilterAnonymiseSystemInfo() |
| 356 | { |
| 357 | $formatter = new TwigFilter('anonymiseSystemInfo', function ($string) { |
| 358 | if ($string === null) { |
| 359 | return ''; |
| 360 | } |
| 361 | if ($string === \false || $string === \true) { |
| 362 | return (int) $string; |
| 363 | } |
| 364 | $string = str_replace([PIWIK_DOCUMENT_ROOT, str_replace('/', '\\/', PIWIK_DOCUMENT_ROOT)], '$DOC_ROOT', $string); |
| 365 | $string = str_replace([PIWIK_USER_PATH, str_replace('/', '\\/', PIWIK_USER_PATH)], '$USER_PATH', $string); |
| 366 | $string = str_replace([PIWIK_INCLUDE_PATH, str_replace('/', '\\/', PIWIK_INCLUDE_PATH)], '$INCLUDE_PATH', $string); |
| 367 | // replace anything token like |
| 368 | $string = preg_replace('/[[:xdigit:]]{31,80}/', 'TOKEN_REPLACED', $string); |
| 369 | // just in case it was somehow show in a text |
| 370 | if (\Piwik\SettingsPiwik::isMatomoInstalled()) { |
| 371 | $string = str_replace(\Piwik\SettingsPiwik::getPiwikUrl(), '$MATOMO_URL', $string); |
| 372 | $string = str_replace(\Piwik\SettingsPiwik::getSalt(), '$MATOMO_SALT', $string); |
| 373 | } |
| 374 | return $string; |
| 375 | }); |
| 376 | $this->twig->addFilter($formatter); |
| 377 | } |
| 378 | protected function addFilterNonce() |
| 379 | { |
| 380 | $nonce = new TwigFilter('nonce', array('Piwik\\Nonce', 'getNonce')); |
| 381 | $this->twig->addFilter($nonce); |
| 382 | } |
| 383 | private function addFilterMd5() |
| 384 | { |
| 385 | $md5 = new TwigFilter('md5', function ($value) { |
| 386 | return md5($value); |
| 387 | }); |
| 388 | $this->twig->addFilter($md5); |
| 389 | } |
| 390 | private function addFilterOnlyDomain() |
| 391 | { |
| 392 | $domainOnly = new TwigFilter('domainOnly', function ($url) { |
| 393 | $parsed = parse_url($url); |
| 394 | return $parsed['scheme'] . '://' . $parsed['host']; |
| 395 | }); |
| 396 | $this->twig->addFilter($domainOnly); |
| 397 | } |
| 398 | protected function addFilterTruncate() |
| 399 | { |
| 400 | $truncateFilter = new TwigFilter('truncate', function ($string, $size) { |
| 401 | return piwik_filter_truncate($string, $size); |
| 402 | }); |
| 403 | $this->twig->addFilter($truncateFilter); |
| 404 | } |
| 405 | protected function addFilterMoney() |
| 406 | { |
| 407 | $moneyFilter = new TwigFilter('money', function ($amount) { |
| 408 | if (func_num_args() != 2) { |
| 409 | throw new Exception('the money modifier expects one parameter: the idSite.'); |
| 410 | } |
| 411 | $idSite = func_get_args(); |
| 412 | $idSite = $idSite[1]; |
| 413 | return piwik_format_money($amount, $idSite); |
| 414 | }); |
| 415 | $this->twig->addFilter($moneyFilter); |
| 416 | } |
| 417 | protected function addFilterSumTime() |
| 418 | { |
| 419 | $formatter = $this->formatter; |
| 420 | $sumtimeFilter = new TwigFilter('sumtime', function ($numberOfSeconds) use($formatter) { |
| 421 | return $formatter->getPrettyTimeFromSeconds($numberOfSeconds, \true); |
| 422 | }); |
| 423 | $this->twig->addFilter($sumtimeFilter); |
| 424 | } |
| 425 | protected function addFilterUrlRewriteWithParameters() |
| 426 | { |
| 427 | $urlRewriteFilter = new TwigFilter('urlRewriteWithParameters', function ($parameters) { |
| 428 | $parameters['updated'] = null; |
| 429 | $url = \Piwik\Url::getCurrentQueryStringWithParametersModified($parameters); |
| 430 | return $url; |
| 431 | }); |
| 432 | $this->twig->addFilter($urlRewriteFilter); |
| 433 | } |
| 434 | protected function addFilterTranslate() |
| 435 | { |
| 436 | $translateFilter = new TwigFilter('translate', function ($stringToken) { |
| 437 | if (func_num_args() <= 1) { |
| 438 | $aValues = array(); |
| 439 | } else { |
| 440 | $aValues = func_get_args(); |
| 441 | array_shift($aValues); |
| 442 | } |
| 443 | try { |
| 444 | $stringTranslated = \Piwik\Piwik::translate($stringToken, $aValues); |
| 445 | } catch (Exception $e) { |
| 446 | $stringTranslated = $stringToken; |
| 447 | } |
| 448 | return $stringTranslated; |
| 449 | }); |
| 450 | $this->twig->addFilter($translateFilter); |
| 451 | } |
| 452 | protected function addFilterListings() |
| 453 | { |
| 454 | $andListing = new TwigFilter('andListing', function ($items) { |
| 455 | if (!is_array($items)) { |
| 456 | return $items; |
| 457 | // don't do anything if input data is incorrect |
| 458 | } |
| 459 | return StaticContainer::get(Translator::class)->createAndListing($items); |
| 460 | }); |
| 461 | $this->twig->addFilter($andListing); |
| 462 | $orListing = new TwigFilter('orListing', function ($items) { |
| 463 | if (!is_array($items)) { |
| 464 | return $items; |
| 465 | // don't do anything if input data is incorrect |
| 466 | } |
| 467 | return StaticContainer::get(Translator::class)->createOrListing($items); |
| 468 | }); |
| 469 | $this->twig->addFilter($orListing); |
| 470 | } |
| 471 | private function addPluginNamespaces(FilesystemLoader $loader) |
| 472 | { |
| 473 | $pluginManager = \Piwik\Plugin\Manager::getInstance(); |
| 474 | $plugins = $pluginManager->getAllPluginsNames(); |
| 475 | foreach ($plugins as $name) { |
| 476 | $pluginsDir = Manager::getPluginDirectory($name); |
| 477 | $path = sprintf("%s/templates/", $pluginsDir); |
| 478 | if (is_dir($path)) { |
| 479 | $loader->addPath(rtrim($path, '/'), $name); |
| 480 | } |
| 481 | } |
| 482 | } |
| 483 | /** |
| 484 | * |
| 485 | * Plugin-Templates can be overwritten by putting identically named templates in plugins/[theme]/templates/plugins/[plugin]/ |
| 486 | * |
| 487 | */ |
| 488 | private function addCustomPluginNamespaces(FilesystemLoader $loader, $pluginName) |
| 489 | { |
| 490 | $pluginManager = \Piwik\Plugin\Manager::getInstance(); |
| 491 | $plugins = $pluginManager->getAllPluginsNames(); |
| 492 | $pluginsDir = Manager::getPluginDirectory($pluginName); |
| 493 | foreach ($plugins as $name) { |
| 494 | $path = sprintf("%s/templates/plugins/%s/", $pluginsDir, $name); |
| 495 | if (is_dir($path)) { |
| 496 | $loader->addPath(rtrim($path, '/'), $name); |
| 497 | } |
| 498 | } |
| 499 | } |
| 500 | /** |
| 501 | * Prepend relative paths with absolute Piwik path |
| 502 | * |
| 503 | * @param string $value relative path (pass by reference) |
| 504 | * @param int $key (don't care) |
| 505 | * @param string $path Piwik root |
| 506 | */ |
| 507 | public static function addPiwikPath(&$value, $key, $path) |
| 508 | { |
| 509 | if ($value[0] != '/' && $value[0] != \DIRECTORY_SEPARATOR) { |
| 510 | $value = $path . "/{$value}"; |
| 511 | } |
| 512 | } |
| 513 | private function addFilterSafelink() |
| 514 | { |
| 515 | $safelink = new TwigFilter('safelink', function ($url) { |
| 516 | if (!\Piwik\UrlHelper::isLookLikeSafeUrl($url)) { |
| 517 | return ''; |
| 518 | } |
| 519 | return $url; |
| 520 | }); |
| 521 | $this->twig->addFilter($safelink); |
| 522 | } |
| 523 | /** |
| 524 | * Modify any links to matomo domains to add campaign tracking parameters |
| 525 | * |
| 526 | * Typical usage: |
| 527 | * |
| 528 | * Apply default campaign tracking parameters: |
| 529 | * {{ 'https://matomo.org/faq/123'|trackmatomolink }} |
| 530 | * |
| 531 | * Apply custom campaign tracking parameters: |
| 532 | * {{ 'https://matomo.org/faq/123'|trackmatomolink('SomeCampaign', 'SomeSource', 'SomeMedium') }} |
| 533 | * |
| 534 | */ |
| 535 | private function addFilterTrackMatomoLink() |
| 536 | { |
| 537 | $trackLink = new TwigFilter('trackmatomolink', function ($url) { |
| 538 | $params = func_get_args(); |
| 539 | array_shift($params); |
| 540 | $campaign = count($params) > 0 ? $params[0] : null; |
| 541 | $source = count($params) > 1 ? $params[1] : null; |
| 542 | $medium = count($params) > 2 ? $params[2] : null; |
| 543 | return \Piwik\Url::addCampaignParametersToMatomoLink($url, $campaign, $source, $medium); |
| 544 | }); |
| 545 | $this->twig->addFilter($trackLink); |
| 546 | } |
| 547 | private function addFilterImplode() |
| 548 | { |
| 549 | $implode = new TwigFilter('implode', function ($value, $separator) { |
| 550 | return implode($separator, $value); |
| 551 | }); |
| 552 | $this->twig->addFilter($implode); |
| 553 | } |
| 554 | private function addTestIsNumeric() |
| 555 | { |
| 556 | $test = new TwigTest('numeric', function ($value) { |
| 557 | return is_numeric($value); |
| 558 | }); |
| 559 | $this->twig->addTest($test); |
| 560 | } |
| 561 | } |
| 562 |