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