Teknik is a suite of services with attractive and functional interfaces. https://www.teknik.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Twig.php 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik;
  10. use Exception;
  11. use Piwik\DataTable\Filter\SafeDecodeLabel;
  12. use Piwik\Period\Range;
  13. use Piwik\Translate;
  14. use Piwik\View\RenderTokenParser;
  15. use Piwik\Visualization\Sparkline;
  16. use Twig_Environment;
  17. use Twig_Extension_Debug;
  18. use Twig_Loader_Chain;
  19. use Twig_Loader_Filesystem;
  20. use Twig_SimpleFilter;
  21. use Twig_SimpleFunction;
  22. /**
  23. * Twig class
  24. *
  25. */
  26. class Twig
  27. {
  28. const SPARKLINE_TEMPLATE = '<img alt="" data-src="%s" width="%d" height="%d" />
  29. <script type="text/javascript">$(function() { piwik.initSparklines(); });</script>';
  30. /**
  31. * @var Twig_Environment
  32. */
  33. private $twig;
  34. public function __construct()
  35. {
  36. $loader = $this->getDefaultThemeLoader();
  37. $this->addPluginNamespaces($loader);
  38. //get current theme
  39. $manager = Plugin\Manager::getInstance();
  40. $theme = $manager->getThemeEnabled();
  41. $loaders = array();
  42. //create loader for custom theme to overwrite twig templates
  43. if($theme && $theme->getPluginName() != \Piwik\Plugin\Manager::DEFAULT_THEME) {
  44. $customLoader = $this->getCustomThemeLoader($theme);
  45. if ($customLoader) {
  46. //make it possible to overwrite plugin templates
  47. $this->addCustomPluginNamespaces($customLoader, $theme->getPluginName());
  48. $loaders[] = $customLoader;
  49. }
  50. }
  51. $loaders[] = $loader;
  52. $chainLoader = new Twig_Loader_Chain($loaders);
  53. // Create new Twig Environment and set cache dir
  54. $templatesCompiledPath = PIWIK_USER_PATH . '/tmp/templates_c';
  55. $templatesCompiledPath = SettingsPiwik::rewriteTmpPathWithInstanceId($templatesCompiledPath);
  56. $this->twig = new Twig_Environment($chainLoader,
  57. array(
  58. 'debug' => true, // to use {{ dump(var) }} in twig templates
  59. 'strict_variables' => true, // throw an exception if variables are invalid
  60. 'cache' => $templatesCompiledPath,
  61. )
  62. );
  63. $this->twig->addExtension(new Twig_Extension_Debug());
  64. $this->twig->clearTemplateCache();
  65. $this->addFilter_translate();
  66. $this->addFilter_urlRewriteWithParameters();
  67. $this->addFilter_sumTime();
  68. $this->addFilter_money();
  69. $this->addFilter_truncate();
  70. $this->addFilter_notification();
  71. $this->addFilter_percentage();
  72. $this->addFilter_prettyDate();
  73. $this->addFilter_safeDecodeRaw();
  74. $this->twig->addFilter(new Twig_SimpleFilter('implode', 'implode'));
  75. $this->twig->addFilter(new Twig_SimpleFilter('ucwords', 'ucwords'));
  76. $this->addFunction_includeAssets();
  77. $this->addFunction_linkTo();
  78. $this->addFunction_sparkline();
  79. $this->addFunction_postEvent();
  80. $this->addFunction_isPluginLoaded();
  81. $this->addFunction_getJavascriptTranslations();
  82. $this->twig->addTokenParser(new RenderTokenParser());
  83. }
  84. protected function addFunction_getJavascriptTranslations()
  85. {
  86. $getJavascriptTranslations = new Twig_SimpleFunction(
  87. 'getJavascriptTranslations',
  88. array('Piwik\\Translate', 'getJavascriptTranslations')
  89. );
  90. $this->twig->addFunction($getJavascriptTranslations);
  91. }
  92. protected function addFunction_isPluginLoaded()
  93. {
  94. $isPluginLoadedFunction = new Twig_SimpleFunction('isPluginLoaded', function ($pluginName) {
  95. return \Piwik\Plugin\Manager::getInstance()->isPluginLoaded($pluginName);
  96. });
  97. $this->twig->addFunction($isPluginLoadedFunction);
  98. }
  99. protected function addFunction_includeAssets()
  100. {
  101. $includeAssetsFunction = new Twig_SimpleFunction('includeAssets', function ($params) {
  102. if (!isset($params['type'])) {
  103. throw new Exception("The function includeAssets needs a 'type' parameter.");
  104. }
  105. $assetType = strtolower($params['type']);
  106. switch ($assetType) {
  107. case 'css':
  108. return AssetManager::getInstance()->getCssInclusionDirective();
  109. case 'js':
  110. return AssetManager::getInstance()->getJsInclusionDirective();
  111. default:
  112. throw new Exception("The twig function includeAssets 'type' parameter needs to be either 'css' or 'js'.");
  113. }
  114. });
  115. $this->twig->addFunction($includeAssetsFunction);
  116. }
  117. protected function addFunction_postEvent()
  118. {
  119. $postEventFunction = new Twig_SimpleFunction('postEvent', function ($eventName) {
  120. // get parameters to twig function
  121. $params = func_get_args();
  122. // remove the first value (event name)
  123. array_shift($params);
  124. // make the first value the string that will get output in the template
  125. // plugins can modify this string
  126. $str = '';
  127. $params = array_merge( array( &$str ), $params);
  128. Piwik::postEvent($eventName, $params);
  129. return $str;
  130. }, array('is_safe' => array('html')));
  131. $this->twig->addFunction($postEventFunction);
  132. }
  133. protected function addFunction_sparkline()
  134. {
  135. $sparklineFunction = new Twig_SimpleFunction('sparkline', function ($src) {
  136. $width = Sparkline::DEFAULT_WIDTH;
  137. $height = Sparkline::DEFAULT_HEIGHT;
  138. return sprintf(Twig::SPARKLINE_TEMPLATE, $src, $width, $height);
  139. }, array('is_safe' => array('html')));
  140. $this->twig->addFunction($sparklineFunction);
  141. }
  142. protected function addFunction_linkTo()
  143. {
  144. $urlFunction = new Twig_SimpleFunction('linkTo', function ($params) {
  145. return 'index.php' . Url::getCurrentQueryStringWithParametersModified($params);
  146. });
  147. $this->twig->addFunction($urlFunction);
  148. }
  149. /**
  150. * @return Twig_Loader_Filesystem
  151. */
  152. private function getDefaultThemeLoader()
  153. {
  154. $themeLoader = new Twig_Loader_Filesystem(array(
  155. sprintf("%s/plugins/%s/templates/", PIWIK_INCLUDE_PATH, \Piwik\Plugin\Manager::DEFAULT_THEME)
  156. ));
  157. return $themeLoader;
  158. }
  159. /**
  160. * create template loader for a custom theme
  161. * @param \Piwik\Plugin $theme
  162. * @return \Twig_Loader_Filesystem
  163. */
  164. protected function getCustomThemeLoader(Plugin $theme){
  165. if(!file_exists(sprintf("%s/plugins/%s/templates/", PIWIK_INCLUDE_PATH, $theme->getPluginName()))){
  166. return false;
  167. }
  168. $themeLoader = new Twig_Loader_Filesystem(array(
  169. sprintf("%s/plugins/%s/templates/", PIWIK_INCLUDE_PATH, $theme->getPluginName())
  170. ));
  171. return $themeLoader;
  172. }
  173. public function getTwigEnvironment()
  174. {
  175. return $this->twig;
  176. }
  177. protected function addFilter_notification()
  178. {
  179. $twigEnv = $this->getTwigEnvironment();
  180. $notificationFunction = new Twig_SimpleFilter('notification', function ($message, $options) use ($twigEnv) {
  181. $template = '<div style="display:none" data-role="notification" ';
  182. foreach ($options as $key => $value) {
  183. if (ctype_alpha($key)) {
  184. $template .= sprintf('data-%s="%s" ', $key, twig_escape_filter($twigEnv, $value, 'html_attr'));
  185. }
  186. }
  187. $template .= '>';
  188. if (!empty($options['raw'])) {
  189. $template .= $message;
  190. } else {
  191. $template .= twig_escape_filter($twigEnv, $message, 'html');
  192. }
  193. $template .= '</div>';
  194. return $template;
  195. }, array('is_safe' => array('html')));
  196. $this->twig->addFilter($notificationFunction);
  197. }
  198. protected function addFilter_safeDecodeRaw()
  199. {
  200. $rawSafeDecoded = new Twig_SimpleFilter('rawSafeDecoded', function ($string) {
  201. $string = str_replace('+', '%2B', $string);
  202. return SafeDecodeLabel::decodeLabelSafe($string);
  203. }, array('is_safe' => array('all')));
  204. $this->twig->addFilter($rawSafeDecoded);
  205. }
  206. protected function addFilter_prettyDate()
  207. {
  208. $prettyDate = new Twig_SimpleFilter('prettyDate', function ($dateString, $period) {
  209. return Range::factory($period, $dateString)->getLocalizedShortString();
  210. });
  211. $this->twig->addFilter($prettyDate);
  212. }
  213. protected function addFilter_percentage()
  214. {
  215. $percentage = new Twig_SimpleFilter('percentage', function ($string, $totalValue, $precision = 1) {
  216. return Piwik::getPercentageSafe($string, $totalValue, $precision) . '%';
  217. });
  218. $this->twig->addFilter($percentage);
  219. }
  220. protected function addFilter_truncate()
  221. {
  222. $truncateFilter = new Twig_SimpleFilter('truncate', function ($string, $size) {
  223. if (strlen($string) < $size) {
  224. return $string;
  225. } else {
  226. $array = str_split($string, $size);
  227. return array_shift($array) . "...";
  228. }
  229. });
  230. $this->twig->addFilter($truncateFilter);
  231. }
  232. protected function addFilter_money()
  233. {
  234. $moneyFilter = new Twig_SimpleFilter('money', function ($amount) {
  235. if (func_num_args() != 2) {
  236. throw new Exception('the money modifier expects one parameter: the idSite.');
  237. }
  238. $idSite = func_get_args();
  239. $idSite = $idSite[1];
  240. return MetricsFormatter::getPrettyMoney($amount, $idSite);
  241. });
  242. $this->twig->addFilter($moneyFilter);
  243. }
  244. protected function addFilter_sumTime()
  245. {
  246. $sumtimeFilter = new Twig_SimpleFilter('sumtime', function ($numberOfSeconds) {
  247. return MetricsFormatter::getPrettyTimeFromSeconds($numberOfSeconds);
  248. });
  249. $this->twig->addFilter($sumtimeFilter);
  250. }
  251. protected function addFilter_urlRewriteWithParameters()
  252. {
  253. $urlRewriteFilter = new Twig_SimpleFilter('urlRewriteWithParameters', function ($parameters) {
  254. $parameters['updated'] = null;
  255. $url = Url::getCurrentQueryStringWithParametersModified($parameters);
  256. return $url;
  257. });
  258. $this->twig->addFilter($urlRewriteFilter);
  259. }
  260. protected function addFilter_translate()
  261. {
  262. $translateFilter = new Twig_SimpleFilter('translate', function ($stringToken) {
  263. if (func_num_args() <= 1) {
  264. $aValues = array();
  265. } else {
  266. $aValues = func_get_args();
  267. array_shift($aValues);
  268. }
  269. try {
  270. $stringTranslated = Piwik::translate($stringToken, $aValues);
  271. } catch (Exception $e) {
  272. $stringTranslated = $stringToken;
  273. }
  274. return $stringTranslated;
  275. });
  276. $this->twig->addFilter($translateFilter);
  277. }
  278. private function addPluginNamespaces(Twig_Loader_Filesystem $loader)
  279. {
  280. $plugins = \Piwik\Plugin\Manager::getInstance()->getAllPluginsNames();
  281. foreach ($plugins as $name) {
  282. $path = sprintf("%s/plugins/%s/templates/", PIWIK_INCLUDE_PATH, $name);
  283. if (is_dir($path)) {
  284. $loader->addPath(PIWIK_INCLUDE_PATH . '/plugins/' . $name . '/templates', $name);
  285. }
  286. }
  287. }
  288. /**
  289. *
  290. * Plugin-Templates can be overwritten by putting identically named templates in plugins/[theme]/templates/plugins/[plugin]/
  291. *
  292. */
  293. private function addCustomPluginNamespaces(Twig_Loader_Filesystem $loader, $pluginName)
  294. {
  295. $plugins = \Piwik\Plugin\Manager::getInstance()->getAllPluginsNames();
  296. foreach ($plugins as $name) {
  297. $path = sprintf("%s/plugins/%s/templates/plugins/%s/", PIWIK_INCLUDE_PATH, $pluginName, $name);
  298. if (is_dir($path)) {
  299. $loader->addPath(PIWIK_INCLUDE_PATH . '/plugins/' . $pluginName . '/templates/plugins/'. $name , $name);
  300. }
  301. }
  302. }
  303. /**
  304. * Prepend relative paths with absolute Piwik path
  305. *
  306. * @param string $value relative path (pass by reference)
  307. * @param int $key (don't care)
  308. * @param string $path Piwik root
  309. */
  310. public static function addPiwikPath(&$value, $key, $path)
  311. {
  312. if ($value[0] != '/' && $value[0] != DIRECTORY_SEPARATOR) {
  313. $value = $path . "/$value";
  314. }
  315. }
  316. }