1<?php
2/**
3 * brings Smarty functionality into Tiki
4 *
5 * this script may only be included, it will die if called directly.
6 *
7 * @package TikiWiki
8 * @subpackage lib\init
9 * @copyright (c) Copyright by authors of the Tiki Wiki CMS Groupware Project. All Rights Reserved. See copyright.txt for details and a complete list of authors.
10 * @licence Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
11 */
12// $Id$
13
14// die if called directly.
15if (strpos($_SERVER['SCRIPT_NAME'], basename(__FILE__)) !== false) {
16	header('location: index.php');
17	exit;
18}
19
20require_once __DIR__ . '/../setup/third_party.php';
21
22/**
23 * extends Smarty_Security
24 * @package TikiWiki\lib\init
25 */
26class Tiki_Security_Policy extends Smarty_Security
27{
28	/**
29	 * needs a proper description
30	 * @var array $secure_dir
31	 */
32
33	public $trusted_uri = [];
34
35	public $secure_dir = [];
36
37	/**
38	 * needs a proper description
39	 * @param Smarty $smarty
40	 */
41	public function __construct($smarty)
42	{
43		if (class_exists("TikiLib")) {
44			$tikilib = TikiLib::lib('tiki');
45			// modlib defines zone_is_empty which must exist before smarty initializes to fix bug with smarty autoloader after version 3.1.21
46			TikiLib::lib('mod');
47		}
48
49		parent::__construct($smarty);
50
51
52		//With phpunit and command line these don't exist yet for some reason
53		if (isset($tikilib) && method_exists($tikilib, "get_preference")) {
54			global $url_host;
55			$this->trusted_uri[] = '#' . preg_quote("http://$url_host", '$#') . '#';
56			$this->trusted_uri[] = '#' . preg_quote("https://$url_host", '$#') . '#';
57
58			$functions = array_filter($tikilib->get_preference('smarty_security_functions', [], true));
59			$modifiers = array_filter($tikilib->get_preference('smarty_security_modifiers', [], true));
60			$dirs = array_filter($tikilib->get_preference('smarty_security_dirs', [], true));
61
62			$cdns = preg_split('/\s+/', $tikilib->get_preference('tiki_cdn', ''));
63			$cdns_ssl = preg_split('/\s+/', $tikilib->get_preference('tiki_cdn_ssl', ''));
64			$cdn_uri = array_filter(array_merge($cdns, $cdns_ssl));
65			foreach ($cdn_uri as $uri) {
66				$this->trusted_uri[] = '#' . preg_quote($uri) . '$#';
67			}
68		} else {
69			$functions = [];
70			$modifiers = [];
71			$dirs = [];
72		}
73
74		// Add defaults
75		$this->php_modifiers = array_merge([
76			'addslashes',
77			'array_filter',
78			'array_reverse',
79			'count',
80			'escape',
81			'explode',
82			'htmlentities',
83			'implode',
84			'is_array',
85			'json_decode',
86			'json_encode',
87			'md5',
88			'nl2br',
89			'preg_split',
90			'strip_tags',
91			'stristr',
92			'strpos',
93			'substr',
94			'tra',
95			'trim',
96			'ucfirst',
97			'ucwords',
98			'urlencode',
99			'var_dump',
100		], $modifiers);
101
102		$this->php_functions = array_merge(['isset',
103			'array',		// not needed? use {$value = []}
104			'array_rand',
105			'array_key_exists',
106			'basename',
107			'count',
108			'empty',
109			'ereg',			// deprecated and removed in php7+ use preg functions instead
110			'in_array',
111			'is_array',
112			'is_numeric',
113			'json_encode',
114			'min',
115			'max',
116			'nl2br',
117			'preg_match',
118			'preg_match_all',
119			'preg_replace',
120			'sizeof',
121			'strlen',
122			'stristr',
123			'strpos',
124			'strstr',
125			'str_replace',
126			'strtolower',
127			'time',
128			'tra',
129			'trim',
130			'zone_is_empty',
131		], $functions);
132
133		$this->secure_dir = array_merge($this->secure_dir, $dirs);
134	}
135}
136
137/**
138 * extends Smarty.
139 *
140 * Centralizing overrides here will avoid problems when upgrading to newer versions of the Smarty library.
141 * @package TikiWiki\lib\init
142 */
143class Smarty_Tiki extends Smarty
144{
145	/**
146	 * needs a proper description
147	 * @var array|null
148	 */
149	public $url_overriding_prefix_stack = null;
150	/**
151	 * needs a proper description
152	 * @var null
153	 */
154	public $url_overriding_prefix = null;
155	/**
156	 * needs a proper description
157	 * @var null|string
158	 */
159	public $main_template_dir = null;
160
161	/**
162	 * needs a proper description
163	 */
164	public function __construct()
165	{
166		parent::__construct();
167		global $prefs;
168
169		$this->initializePaths();
170
171		$this->setConfigDir(null);
172		if (! isset($prefs['smarty_compilation'])) {
173			$prefs['smarty_compilation'] = '';
174		}
175		$this->compile_check = ( $prefs['smarty_compilation'] != 'never' );
176		$this->force_compile = ( $prefs['smarty_compilation'] == 'always' );
177		$this->assign('app_name', 'Tiki');
178
179		if (! isset($prefs['smarty_security']) || $prefs['smarty_security'] == 'y') {
180			$this->enableSecurity('Tiki_Security_Policy');
181		} else {
182			$this->disableSecurity();
183		}
184		$this->use_sub_dirs = false;
185		$this->url_overriding_prefix_stack = [];
186		if (! empty($prefs['smarty_notice_reporting']) and $prefs['smarty_notice_reporting'] === 'y') {
187			$this->error_reporting = E_ALL;
188		} else {
189			$this->error_reporting = E_ALL ^ E_NOTICE;
190		}
191		if (! empty($prefs['smarty_cache_perms'])) {
192			$this->_file_perms = (int) $prefs['smarty_cache_perms'];
193		}
194
195		$this->loadFilter('pre', 'tr');
196		$this->loadFilter('pre', 'jq');
197
198		include_once(__DIR__ . '/../smarty_tiki/resource.tplwiki.php');
199		$this->registerResource('tplwiki', ['smarty_resource_tplwiki_source', 'smarty_resource_tplwiki_timestamp', 'smarty_resource_tplwiki_secure', 'smarty_resource_tplwiki_trusted']);
200
201		include_once(__DIR__ . '/../smarty_tiki/resource.wiki.php');
202		$this->registerResource('wiki', ['smarty_resource_wiki_source', 'smarty_resource_wiki_timestamp', 'smarty_resource_wiki_secure', 'smarty_resource_wiki_trusted']);
203
204		global $prefs;
205		// Assign the prefs array in smarty, by reference
206		$this->assignByRef('prefs', $prefs);
207
208		if (! empty($prefs['log_tpl']) && $prefs['log_tpl'] === 'y') {
209			$this->loadFilter('pre', 'log_tpl');
210		}
211		if (! empty($prefs['feature_sefurl_filter']) && $prefs['feature_sefurl_filter'] === 'y') {
212			require_once('tiki-sefurl.php');
213			$this->registerFilter('output', 'filter_out_sefurl');
214		}
215
216		// restore tiki's own escape function
217		$this->loadPlugin('smarty_modifier_escape');
218		$this->registerPlugin('modifier', 'escape', 'smarty_modifier_escape');
219	}
220
221	/**
222	 * Fetch templates from plugins (smarty plugins, wiki plugins, modules, ...) that may need to :
223	 * - temporarily override some smarty vars,
224	 * - prefix their self_link / button / query URL arguments
225	 *
226	 * @param      $_smarty_tpl_file
227	 * @param null $override_vars
228	 *
229	 * @return string
230	 */
231	public function plugin_fetch($_smarty_tpl_file, &$override_vars = null)
232	{
233		$smarty_orig_values = [];
234		if (is_array($override_vars)) {
235			foreach ($override_vars as $k => $v) {
236				$smarty_orig_values[ $k ] = $this->getTemplateVars($k);
237				$this->assignByRef($k, $override_vars[ $k ]);
238			}
239		}
240
241		$return = $this->fetch($_smarty_tpl_file);
242
243		// Restore original values of smarty variables
244		if (count($smarty_orig_values) > 0) {
245			foreach ($smarty_orig_values as $k => $v) {
246				$this->assignByRef($k, $smarty_orig_values[ $k ]);
247			}
248		}
249
250		unset($smarty_orig_values);
251		return $return;
252	}
253
254	/**
255	 * needs a proper description
256	 * @param null $_smarty_tpl_file
257	 * @param null $_smarty_cache_id
258	 * @param null $_smarty_compile_id
259	 * @param null $parent
260	 * @param bool $_smarty_display
261	 * @param bool $merge_tpl_vars
262	 * @param bool $no_output_filter
263	 * @return string
264	 */
265	public function fetch($_smarty_tpl_file = null, $_smarty_cache_id = null, $_smarty_compile_id = null, $parent = null, $_smarty_display = false, $merge_tpl_vars = true, $no_output_filter = false)
266	{
267		if (strpos($_smarty_tpl_file, 'extends:') === 0) {
268			// temporarily disable extends_recursion which restores smarty < 3.1.28 behaviour
269			// see note at vendor_bundled/vendor/smarty/smarty/libs/Smarty.class.php:296 for more
270
271			$this->extends_recursion = false;
272		}
273		$this->muteExpectedErrors();
274		$this->refreshLanguage();
275
276		$this->assign_layout_sections($_smarty_tpl_file, $_smarty_cache_id, $_smarty_compile_id, $parent);
277
278		$_smarty_tpl_file = $this->get_filename($_smarty_tpl_file);
279
280		if ($_smarty_display) {
281			$html = parent::display($_smarty_tpl_file, $_smarty_cache_id, $_smarty_compile_id, $parent);
282		} else {
283			$html = parent::fetch($_smarty_tpl_file, $_smarty_cache_id, $_smarty_compile_id, $parent);
284		}
285
286		if (! $this->extends_recursion) {
287			$this->extends_recursion = true;
288		}
289
290		return $html;
291	}
292
293	/**
294	 * Clears the value of an assigned variable
295	 * @param $var mixed
296	 * @return Smarty_Internal_Data
297	 */
298	public function clear_assign($var)
299	{
300		return parent::clearAssign($var);
301	}
302
303	/**
304	 * This is used to assign() values to the templates by reference instead of making a copy.
305	 * @param $var string
306	 * @param $value mixed
307	 * @return Smarty_Internal_Data
308	 */
309	public function assign_by_ref($var, &$value)
310	{
311		return parent::assignByRef($var, $value);
312	}
313
314	/**
315	 * fetch in a specific language  without theme consideration
316	 * @param      $lg
317	 * @param      $_smarty_tpl_file
318	 * @param null $_smarty_cache_id
319	 * @param null $_smarty_compile_id
320	 * @param bool $_smarty_display
321	 * @return mixed
322	 */
323	public function fetchLang($lg, $_smarty_tpl_file, $_smarty_cache_id = null, $_smarty_compile_id = null)
324	{
325		global $prefs;
326
327		$_smarty_tpl_file = $this->get_filename($_smarty_tpl_file);
328
329		$lgSave = $prefs['language'];
330		$prefs['language'] = $lg;
331		$this->refreshLanguage();
332		$res = parent::fetch($_smarty_tpl_file, $_smarty_cache_id, $_smarty_compile_id);
333		$prefs['language'] = $lgSave; // Restore the language of the user triggering the notification
334		$this->refreshLanguage();
335
336		return preg_replace("/^[ \t]*/", '', $res);
337	}
338
339	/**
340	 * needs a proper description
341	 * @param null   $resource_name
342	 * @param null   $cache_id
343	 * @param null   $compile_id
344	 * @param null   $parent
345	 * @param string $content_type
346	 * @return Purified|void
347	 */
348	public function display($resource_name = null, $cache_id = null, $compile_id = null, $parent = null, $content_type = 'text/html; charset=utf-8')
349	{
350
351		global $prefs;
352		$this->muteExpectedErrors();
353		if (! empty($prefs['feature_htmlpurifier_output']) and $prefs['feature_htmlpurifier_output'] == 'y') {
354			static $loaded = false;
355			static $purifier = null;
356			if (! $loaded) {
357				require_once('lib/htmlpurifier_tiki/HTMLPurifier.tiki.php');
358				$config = getHTMLPurifierTikiConfig();
359				$purifier = new HTMLPurifier($config);
360				$loaded = true;
361			}
362		}
363
364		/**
365		 * Add security headers. By default there headers are not sent.
366		 * To change go to admin > security > site access
367		 */
368		if (! headers_sent()) {
369			if (! isset($prefs['http_header_frame_options'])) {
370				$frame = false;
371			} else {
372				$frame = $prefs['http_header_frame_options'];
373			}
374			if (! isset($prefs['http_header_xss_protection'])) {
375				$xss = false;  // prevent smarty E_NOTICE
376			} else {
377				$xss = $prefs['http_header_xss_protection'];
378			}
379
380			if (! isset($prefs['http_header_content_type_options'])) {
381				$content_type_options = false;  // prevent smarty E_NOTICE
382			} else {
383				$content_type_options = $prefs['http_header_content_type_options'];
384			}
385
386			if (! isset($prefs['http_header_content_security_policy'])) {
387				$content_security_policy = false;  // prevent smarty E_NOTICE
388			} else {
389				$content_security_policy = $prefs['http_header_content_security_policy'];
390			}
391
392			if (! isset($prefs['http_header_strict_transport_security'])) {
393				$strict_transport_security = false;  // prevent smarty E_NOTICE
394			} else {
395				$strict_transport_security = $prefs['http_header_strict_transport_security'];
396			}
397
398			if (! isset($prefs['http_header_public_key_pins'])) {
399				$public_key_pins = false;  // prevent smarty E_NOTICE
400			} else {
401				$public_key_pins = $prefs['http_header_public_key_pins'];
402			}
403
404			if ($frame == 'y') {
405					$header_value = $prefs['http_header_frame_options_value'];
406					header('X-Frame-Options: ' . $header_value);
407			}
408			if ($xss == 'y') {
409					$header_value = $prefs['http_header_xss_protection_value'];
410					header('X-XSS-Protection: ' . $header_value);
411			}
412			if ($content_type_options == 'y') {
413				header('X-Content-Type-Options: nosniff');
414			}
415			if ($content_security_policy == 'y') {
416				$header_value = $prefs['http_header_content_security_policy_value'];
417				header('Content-Security-Policy: ' . $header_value);
418			}
419
420			if ($strict_transport_security == 'y') {
421				$header_value = $prefs['http_header_strict_transport_security_value'];
422				header('Strict-Transport-Security: ' . $header_value);
423			}
424
425			if ($public_key_pins == 'y') {
426				$header_value = $prefs['http_header_public_key_pins_value'];
427				header('Public-Key-Pins: ' . $header_value);
428			}
429		}
430
431		/**
432		 * By default, display is used with text/html content in UTF-8 encoding
433		 * If you want to output other data from smarty,
434		 * - either use fetch() / fetchLang()
435		 * - or set $content_type to '' (empty string) or another content type.
436		 */
437		if ($content_type != '' && ! headers_sent()) {
438			header('Content-Type: ' . $content_type);
439		}
440
441		if (function_exists('current_object') && $obj = current_object()) {
442			$attributes = TikiLib::lib('attribute')->get_attributes($obj['type'], $obj['object']);
443			if (isset($attributes['tiki.object.layout'])) {
444				$prefs['site_layout'] = $attributes['tiki.object.layout'];
445			}
446		}
447
448		$this->refreshLanguage();
449
450		TikiLib::events()->trigger('tiki.process.render', []);
451
452		$this->assign_layout_sections($resource_name, $cache_id, $compile_id, $parent);
453
454		if (! empty($prefs['feature_htmlpurifier_output']) and $prefs['feature_htmlpurifier_output'] == 'y') {
455			return $purifier->purify(parent::display($resource_name, $cache_id, $compile_id));
456		} else {
457			return parent::display($resource_name, $cache_id, $compile_id);
458		}
459	}
460
461	/**
462	 * Since Smarty 3.1.23, display no longer calls fetch function, so we need to have this Tiki layout section assignment
463	 * and modules loading called in both places
464	 */
465	private function assign_layout_sections($_smarty_tpl_file, $_smarty_cache_id, $_smarty_compile_id, $parent)
466	{
467		global $prefs;
468
469		if (($tpl = $this->getTemplateVars('mid')) && ( $_smarty_tpl_file == 'tiki.tpl' || $_smarty_tpl_file == 'tiki-print.tpl' || $_smarty_tpl_file == 'tiki_full.tpl' )) {
470			// Set the last mid template to be used by AJAX to simulate a 'BACK' action
471			if (isset($_SESSION['last_mid_template'])) {
472				$this->assign('last_mid_template', $_SESSION['last_mid_template']);
473				$this->assign('last_mid_php', $_SESSION['last_mid_php']);
474			}
475			$_SESSION['last_mid_template'] = $tpl;
476			$_SESSION['last_mid_php'] = $_SERVER['REQUEST_URI'];
477
478			// set the first part of the browser title for admin pages
479			if (null === $this->getTemplateVars('headtitle')) {
480				$script_name = basename($_SERVER['SCRIPT_NAME']);
481				if ($script_name === 'route.php' && ! empty($inclusion)) {
482					$script_name = $inclusion;
483				}
484				if ($script_name != 'tiki-admin.php' && strpos($script_name, 'tiki-admin') === 0) {
485					$str = substr($script_name, 10, strpos($script_name, '.php') - 10);
486					$str = ucwords(trim(str_replace('_', ' ', $str)));
487					$this->assign('headtitle', 'Admin ' . $str);
488				} elseif (strpos($script_name, 'tiki-list') === 0) {
489					$str = substr($script_name, 9, strpos($script_name, '.php') - 9);
490					$str = ucwords(trim(str_replace('_', ' ', $str)));
491					$this->assign('headtitle', 'List ' . $str);
492				} elseif (strpos($script_name, 'tiki-view') === 0) {
493					$str = substr($script_name, 9, strpos($script_name, '.php') - 9);
494					$str = ucwords(trim(str_replace('_', ' ', $str)));
495					$this->assign('headtitle', 'View ' . $str);
496				} elseif ($prefs['urlIndex'] && strpos($script_name, $prefs['urlIndex']) === 0) {
497					$this->assign('headtitle', tra($prefs['urlIndexBrowserTitle']));	// Viewing Custom Homepage
498				} else { // still not set? guess...
499					$str = str_replace(['tiki-', '.php', '_'], ['', '', ' '], $script_name);
500					$str = ucwords($str);
501					$this->assign('headtitle', tra($str));	// for files where no title has been set or can be reliably calculated - translators: please add comments here as you find them
502				}
503			}
504
505			if ($_smarty_tpl_file == 'tiki-print.tpl') {
506				$this->assign('print_page', 'y');
507			}
508			$data = $this->fetch($tpl, $_smarty_cache_id, $_smarty_compile_id, $parent);//must get the mid because the modules can overwrite smarty variables
509
510			$this->assign('mid_data', $data);
511		} elseif ($_smarty_tpl_file == 'confirm.tpl' || $_smarty_tpl_file == 'error.tpl' || $_smarty_tpl_file == 'error_ticket.tpl' || $_smarty_tpl_file == 'error_simple.tpl') {
512			if (! empty(ob_get_status())) {
513				ob_end_clean(); // Empty existing Output Buffer that may have been created in smarty before the call of this confirm / error* template
514			}
515			if ($prefs['feature_obzip'] == 'y') {
516				ob_start('ob_gzhandler');
517			}
518		}
519
520		if (! defined('TIKI_IN_INSTALLER') && ! defined('TIKI_IN_TEST')) {
521			require_once 'tiki-modules.php';
522		}
523	}
524
525	/**
526	 * Returns the file path associated to the template name
527	 * Check if the path is a template inside one of the template dirs and not an arbitrary file
528	 * @param $template
529	 * @return string
530	 */
531	public function get_filename($template)
532	{
533		if (substr($template, 0, 5) === 'file:') {
534			$template = substr($template, 5);
535		}
536
537		// could be extends: or something else?
538		if (preg_match('/^[a-z]+\:/', $template)) {
539			return $template;
540		}
541
542		//get the list of template directories
543		$dirs = array_merge(
544			$this->getTemplateDir(),
545			['temp/cache'],
546			$this->security_policy ? array_map('realpath', $this->security_policy->secure_dir) : []
547		);
548
549		// sanity check
550		if (file_exists($template)) {
551			$valid_path = false;
552			foreach ($dirs as $dir) {
553				$dirPath = realpath($dir);
554				if ($dirPath === false) {
555					continue;
556				}
557
558				if (strpos(realpath($template), $dirPath) === 0) {
559					$valid_path = true;
560					break;
561				}
562			}
563			if (! $valid_path) {
564				Feedback::error(tr("Invalid template name: %0", $template));
565				return "";
566			}
567			return $template;
568		}
569
570		//go through directories in search of the template
571		foreach ($dirs as $dir) {
572			if (file_exists($dir . $template)) {
573				return $dir . $template;
574			}
575		}
576		return "";
577	}
578
579	/**
580	 * needs a proper description
581	 * @param $url_arguments_prefix
582	 * @param $arguments_list
583	 */
584	public function set_request_overriders($url_arguments_prefix, $arguments_list)
585	{
586		$this->url_overriding_prefix_stack[] = [ $url_arguments_prefix . '-', $arguments_list ];
587		$this->url_overriding_prefix =& $this->url_overriding_prefix_stack[ count($this->url_overriding_prefix_stack) - 1 ];
588	}
589
590	/**
591	 * needs a proper description
592	 * @param $url_arguments_prefix
593	 * @param $arguments_list
594	 */
595	public function remove_request_overriders($url_arguments_prefix, $arguments_list)
596	{
597		$last_override_prefix = empty($this->url_overriding_prefix_stack) ? false : array_pop($this->url_overriding_prefix_stack);
598		if (! is_array($last_override_prefix) || $url_arguments_prefix . '-' != $last_override_prefix[0]) {
599			trigger_error('URL Overriding prefix stack is in a bad state', E_USER_ERROR);
600		}
601		$this->url_overriding_prefix =& $this->url_overriding_prefix_stack[ count($this->url_overriding_prefix_stack) - 1 ];
602		;
603	}
604
605	public function refreshLanguage()
606	{
607		global $tikidomain, $prefs;
608
609		$lang = $prefs['language'];
610		if (empty($lang)) {
611			$lang = 'default';
612		}
613
614		if (! empty($prefs['site_layout'])) {
615			$layout = $prefs['site_layout'];
616		} else {
617			$layout = 'classic';
618		}
619
620		$this->setCompileId("$lang-$tikidomain-$layout");
621		$this->initializePaths();
622	}
623
624	/*
625	Add smarty template paths from where tpl files should be loaded. This function also gets called from lib/setup/theme.php to initialize theme specific paths
626	*/
627	public function initializePaths()
628	{
629		global $prefs, $tikidomainslash, $section;
630
631		if (! $this->main_template_dir) {
632			// First run only
633			$this->main_template_dir = TIKI_PATH . '/templates/';
634			$this->setCompileDir(TIKI_PATH . "/temp/templates_c");
635			$this->setPluginsDir(
636				[	// the directory order must be like this to overload a plugin
637					TIKI_PATH . '/' . TIKI_SMARTY_DIR,
638					SMARTY_DIR . 'plugins'
639				]
640			);
641		}
642
643		$this->setTemplateDir([]);
644
645		// when called from release.php TikiLib isn't initialised so we can ignore the themes and addons
646		if (class_exists('TikiLib')) {
647			// Theme templates
648			$themelib = TikiLib::lib('theme');
649			if (! empty($prefs['theme']) && ! in_array($prefs['theme'], ['custom_url'])) {
650				$theme_path = $themelib->get_theme_path($prefs['theme'], $prefs['theme_option'], '', 'templates'); // path to the theme options
651				$this->addTemplateDir(TIKI_PATH . "/$theme_path/");
652				//if theme_admin is empty, use main theme and site_layout instead of site_layout_admin
653				if ($section != "admin" || empty($prefs['theme_admin'])) {
654					$layout = TIKI_PATH . "/$theme_path/" . 'layouts/' . $prefs['site_layout'] . '/';
655					if (! is_readable($layout)) {
656						$layout = TIKI_PATH . "/$theme_path/" . 'layouts/basic/';
657					}
658					$this->addTemplateDir($layout);
659				} else {
660					$layout = TIKI_PATH . "/$theme_path/" . 'layouts/' . $prefs['site_layout_admin'] . '/';
661					if (! is_readable($layout)) {
662						$layout = TIKI_PATH . "/$theme_path/" . 'layouts/basic/';
663					}
664					$this->addTemplateDir($layout);
665				}
666				$this->addTemplateDir(TIKI_PATH . "/$theme_path/" . 'layouts/');
667
668				$main_theme_path = $themelib->get_theme_path($prefs['theme'], '', '', 'templates'); // path to the main theme
669				$this->addTemplateDir(TIKI_PATH . "/$main_theme_path/");
670				//if theme_admin is empty, use main theme and site_layout instead of site_layout_admin
671				if ($section != "admin" || empty($prefs['theme_admin'])) {
672					$layout = TIKI_PATH . "/$main_theme_path/" . 'layouts/' . $prefs['site_layout'] . '/';
673					if (! is_readable($layout)) {
674						$layout = TIKI_PATH . "/$main_theme_path/" . 'layouts/basic/';
675					}
676					$this->addTemplateDir($layout);
677				} else {
678					$layout = TIKI_PATH . "/$main_theme_path/" . 'layouts/' . $prefs['site_layout_admin'] . '/';
679					if (! is_readable($layout)) {
680						$layout = TIKI_PATH . "/$main_theme_path/" . 'layouts/basic/';
681					}
682					$this->addTemplateDir($layout);
683				}
684			}
685			// Tikidomain main template folder
686			if (! empty($tikidomainslash)) {
687				$this->addTemplateDir(TIKI_PATH . "/themes/{$tikidomainslash}templates/"); // This dir is for all the themes in the tikidomain
688				$this->addTemplatedir($this->main_template_dir . '/' . $tikidomainslash); // legacy tpls just in case, for example: /templates/mydomain.ltd/
689			}
690
691			$this->addTemplateDir(TIKI_PATH . "/themes/templates/"); //This dir stores templates for all the themes
692
693			//Addon templates
694			foreach (\Tiki\Package\ExtensionManager::getPaths() as $path) {
695				$this->addTemplateDir($path . '/templates/');
696			}
697		}
698
699		//Layout templates
700		if (! empty($prefs['site_layout']) && ($section != "admin" || empty($prefs['theme_admin']))) { //use the admin layout if in the admin section
701			$layout = $this->main_template_dir . '/layouts/' . $prefs['site_layout'] . '/';
702			if (! is_readable($layout)) {
703				$layout = $this->main_template_dir . '/layouts/basic/';
704			}
705			$this->addTemplateDir($layout);
706		} elseif (! empty($prefs['site_layout_admin'])) {
707			$layout = $this->main_template_dir . '/layouts/' . $prefs['site_layout_admin'] . '/';
708			if (! is_readable($layout)) {
709				$layout = $this->main_template_dir . '/layouts/basic/';
710			}
711			$this->addTemplateDir($layout);
712		}
713		$this->addTemplateDir($this->main_template_dir . '/layouts/');
714		$this->addTemplateDir($this->main_template_dir);
715
716		//Test templates
717		$this->addTemplateDir(TIKI_PATH . '/lib/test/core/Search/');
718	}
719
720	/**
721	 * When calling directly smarty functions, from PHP, you need to provide a object of type Smarty_Internal_Template
722	 * The method signature for smarty functions is: smarty_function_xxxx($params, Smarty_Internal_Template $template)
723	 *
724	 * @return Smarty_Internal_Template
725	 */
726	public function getEmptyInternalTemplate()
727	{
728		$tpl = new Smarty_Internal_Template('empty', $this);
729		return $tpl;
730	}
731}
732