Merge pull request #20452 from jaapjansma/dev_financials_6_contactsummary
[civicrm-core.git] / CRM / Core / Smarty.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * Fix for bug CRM-392. Not sure if this is the best fix or it will impact
20 * other similar PEAR packages. doubt it
21 */
22 if (!class_exists('Smarty')) {
23 require_once 'Smarty/Smarty.class.php';
24 }
25
26 /**
27 *
28 */
29 class CRM_Core_Smarty extends Smarty {
30 const
31 // use print.tpl and bypass the CMS. Civi prints a valid html file
32 PRINT_PAGE = 1,
33 // this and all the below bypasses the CMS html surrounding it and assumes we will embed this within other pages
34 PRINT_SNIPPET = 2,
35 // sends the generated html to the chosen pdf engine
36 PRINT_PDF = 3,
37 // this options also skips the enclosing form html and does not
38 // generate any of the hidden fields, most notably qfKey
39 // this is typically used in ajax scripts to embed form snippets based on user choices
40 PRINT_NOFORM = 4,
41 // this prints a complete form and also generates a qfKey, can we replace this with
42 // snippet = 2?? Does the constant _NOFFORM do anything?
43 PRINT_QFKEY = 5,
44 // Note: added in v 4.3 with the value '6'
45 // Value changed in 4.5 to 'json' for better readability
46 // @see CRM_Core_Page_AJAX::returnJsonResponse
47 PRINT_JSON = 'json';
48
49 /**
50 * We only need one instance of this object. So we use the singleton
51 * pattern and cache the instance in this variable
52 *
53 * @var object
54 */
55 static private $_singleton = NULL;
56
57 /**
58 * Backup frames.
59 *
60 * A list of variables ot save temporarily in format (string $name => mixed $value).
61 *
62 * @var array
63 */
64 private $backupFrames = [];
65
66 /**
67 * Class constructor.
68 *
69 * @return CRM_Core_Smarty
70 */
71 public function __construct() {
72 parent::__construct();
73 }
74
75 private function initialize() {
76 $config = CRM_Core_Config::singleton();
77
78 if (isset($config->customTemplateDir) && $config->customTemplateDir) {
79 $this->template_dir = array_merge([$config->customTemplateDir],
80 $config->templateDir
81 );
82 }
83 else {
84 $this->template_dir = $config->templateDir;
85 }
86 $this->compile_dir = CRM_Utils_File::addTrailingSlash(CRM_Utils_File::addTrailingSlash($config->templateCompileDir) . $this->getLocale());
87 CRM_Utils_File::createDir($this->compile_dir);
88 CRM_Utils_File::restrictAccess($this->compile_dir);
89
90 // check and ensure it is writable
91 // else we sometime suppress errors quietly and this results
92 // in blank emails etc
93 if (!is_writable($this->compile_dir)) {
94 echo "CiviCRM does not have permission to write temp files in {$this->compile_dir}, Exiting";
95 exit();
96 }
97
98 //Check for safe mode CRM-2207
99 if (ini_get('safe_mode')) {
100 $this->use_sub_dirs = FALSE;
101 }
102 else {
103 $this->use_sub_dirs = TRUE;
104 }
105
106 $customPluginsDir = NULL;
107 if (isset($config->customPHPPathDir)) {
108 $customPluginsDir
109 = $config->customPHPPathDir . DIRECTORY_SEPARATOR .
110 'CRM' . DIRECTORY_SEPARATOR .
111 'Core' . DIRECTORY_SEPARATOR .
112 'Smarty' . DIRECTORY_SEPARATOR .
113 'plugins' . DIRECTORY_SEPARATOR;
114 if (!file_exists($customPluginsDir)) {
115 $customPluginsDir = NULL;
116 }
117 }
118
119 $pkgsDir = Civi::paths()->getVariable('civicrm.packages', 'path');
120 $smartyDir = $pkgsDir . DIRECTORY_SEPARATOR . 'Smarty' . DIRECTORY_SEPARATOR;
121 $pluginsDir = __DIR__ . DIRECTORY_SEPARATOR . 'Smarty' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR;
122
123 if ($customPluginsDir) {
124 $this->plugins_dir = [$customPluginsDir, $smartyDir . 'plugins', $pluginsDir];
125 }
126 else {
127 $this->plugins_dir = [$smartyDir . 'plugins', $pluginsDir];
128 }
129
130 $this->compile_check = $this->isCheckSmartyIsCompiled();
131
132 // add the session and the config here
133 $session = CRM_Core_Session::singleton();
134
135 $this->assign_by_ref('config', $config);
136 $this->assign_by_ref('session', $session);
137
138 $tsLocale = CRM_Core_I18n::getLocale();
139 $this->assign('tsLocale', $tsLocale);
140
141 // CRM-7163 hack: we don’t display langSwitch on upgrades anyway
142 if (!CRM_Core_Config::isUpgradeMode()) {
143 $this->assign('langSwitch', CRM_Core_I18n::uiLanguages());
144 }
145
146 $this->register_function('crmURL', ['CRM_Utils_System', 'crmURL']);
147 $this->load_filter('pre', 'resetExtScope');
148
149 $this->assign('crmPermissions', new CRM_Core_Smarty_Permissions());
150
151 if ($config->debug) {
152 $this->error_reporting = E_ALL;
153 }
154 }
155
156 /**
157 * Static instance provider.
158 *
159 * Method providing static instance of SmartTemplate, as
160 * in Singleton pattern.
161 *
162 * @return \CRM_Core_Smarty
163 */
164 public static function &singleton() {
165 if (!isset(self::$_singleton)) {
166 self::$_singleton = new CRM_Core_Smarty();
167 self::$_singleton->initialize();
168
169 self::registerStringResource();
170 }
171 return self::$_singleton;
172 }
173
174 /**
175 * Executes & returns or displays the template results
176 *
177 * @param string $resource_name
178 * @param string $cache_id
179 * @param string $compile_id
180 * @param bool $display
181 *
182 * @return bool|mixed|string
183 */
184 public function fetch($resource_name, $cache_id = NULL, $compile_id = NULL, $display = FALSE) {
185 if (preg_match('/^(\s+)?string:/', $resource_name)) {
186 $old_security = $this->security;
187 $this->security = TRUE;
188 }
189 $output = parent::fetch($resource_name, $cache_id, $compile_id, $display);
190 if (isset($old_security)) {
191 $this->security = $old_security;
192 }
193 return $output;
194 }
195
196 /**
197 * Fetch a template (while using certain variables)
198 *
199 * @param string $resource_name
200 * @param array $vars
201 * (string $name => mixed $value) variables to export to Smarty.
202 * @throws Exception
203 * @return bool|mixed|string
204 */
205 public function fetchWith($resource_name, $vars) {
206 $this->pushScope($vars);
207 try {
208 $result = $this->fetch($resource_name);
209 }
210 catch (Exception $e) {
211 // simulate try { ... } finally { ... }
212 $this->popScope();
213 throw $e;
214 }
215 $this->popScope();
216 return $result;
217 }
218
219 /**
220 * @param string $name
221 * @param $value
222 */
223 public function appendValue($name, $value) {
224 $currentValue = $this->get_template_vars($name);
225 if (!$currentValue) {
226 $this->assign($name, $value);
227 }
228 else {
229 if (strpos($currentValue, $value) === FALSE) {
230 $this->assign($name, $currentValue . $value);
231 }
232 }
233 }
234
235 public function clearTemplateVars() {
236 foreach (array_keys($this->_tpl_vars) as $key) {
237 if ($key == 'config' || $key == 'session') {
238 continue;
239 }
240 unset($this->_tpl_vars[$key]);
241 }
242 }
243
244 public static function registerStringResource() {
245 require_once 'CRM/Core/Smarty/resources/String.php';
246 civicrm_smarty_register_string_resource();
247 }
248
249 /**
250 * @param $path
251 */
252 public function addTemplateDir($path) {
253 if (is_array($this->template_dir)) {
254 array_unshift($this->template_dir, $path);
255 }
256 else {
257 $this->template_dir = [$path, $this->template_dir];
258 }
259
260 }
261
262 /**
263 * Temporarily assign a list of variables.
264 *
265 * ```
266 * $smarty->pushScope(array(
267 * 'first_name' => 'Alice',
268 * 'last_name' => 'roberts',
269 * ));
270 * $html = $smarty->fetch('view-contact.tpl');
271 * $smarty->popScope();
272 * ```
273 *
274 * @param array $vars
275 * (string $name => mixed $value).
276 * @return CRM_Core_Smarty
277 * @see popScope
278 */
279 public function pushScope($vars) {
280 $oldVars = $this->get_template_vars();
281 $backupFrame = [];
282 foreach ($vars as $key => $value) {
283 $backupFrame[$key] = $oldVars[$key] ?? NULL;
284 }
285 $this->backupFrames[] = $backupFrame;
286
287 $this->assignAll($vars);
288
289 return $this;
290 }
291
292 /**
293 * Remove any values that were previously pushed.
294 *
295 * @return CRM_Core_Smarty
296 * @see pushScope
297 */
298 public function popScope() {
299 $this->assignAll(array_pop($this->backupFrames));
300 return $this;
301 }
302
303 /**
304 * @param array $vars
305 * (string $name => mixed $value).
306 * @return CRM_Core_Smarty
307 */
308 public function assignAll($vars) {
309 foreach ($vars as $key => $value) {
310 $this->assign($key, $value);
311 }
312 return $this;
313 }
314
315 /**
316 * Get the locale for translation.
317 *
318 * @return string
319 */
320 private function getLocale() {
321 $tsLocale = CRM_Core_I18n::getLocale();
322 if (!empty($tsLocale)) {
323 return $tsLocale;
324 }
325
326 $config = CRM_Core_Config::singleton();
327 if (!empty($config->lcMessages)) {
328 return $config->lcMessages;
329 }
330
331 return 'en_US';
332 }
333
334 /**
335 * Get the compile_check value.
336 *
337 * @return bool
338 */
339 private function isCheckSmartyIsCompiled() {
340 // check for define in civicrm.settings.php as FALSE, otherwise returns TRUE
341 return CRM_Utils_Constant::value('CIVICRM_TEMPLATE_COMPILE_CHECK', TRUE);
342 }
343
344 }