mixed $value).
*
* @var array
*/
private $backupFrames = [];
/**
* Class constructor.
*
* @return CRM_Core_Smarty
*/
public function __construct() {
parent::__construct();
}
private function initialize() {
$config = CRM_Core_Config::singleton();
if (isset($config->customTemplateDir) && $config->customTemplateDir) {
$this->template_dir = array_merge([$config->customTemplateDir],
$config->templateDir
);
}
else {
$this->template_dir = $config->templateDir;
}
$this->compile_dir = CRM_Utils_File::addTrailingSlash(CRM_Utils_File::addTrailingSlash($config->templateCompileDir) . $this->getLocale());
CRM_Utils_File::createDir($this->compile_dir);
CRM_Utils_File::restrictAccess($this->compile_dir);
// check and ensure it is writable
// else we sometime suppress errors quietly and this results
// in blank emails etc
if (!is_writable($this->compile_dir)) {
echo "CiviCRM does not have permission to write temp files in {$this->compile_dir}, Exiting";
exit();
}
$this->use_sub_dirs = TRUE;
$customPluginsDir = NULL;
if (!empty($config->customPHPPathDir) || $config->customPHPPathDir === '0') {
$customPluginsDir
= $config->customPHPPathDir . DIRECTORY_SEPARATOR .
'CRM' . DIRECTORY_SEPARATOR .
'Core' . DIRECTORY_SEPARATOR .
'Smarty' . DIRECTORY_SEPARATOR .
'plugins' . DIRECTORY_SEPARATOR;
if (!file_exists($customPluginsDir)) {
$customPluginsDir = NULL;
}
}
$pkgsDir = Civi::paths()->getVariable('civicrm.packages', 'path');
$smartyDir = $pkgsDir . DIRECTORY_SEPARATOR . 'Smarty' . DIRECTORY_SEPARATOR;
$pluginsDir = __DIR__ . DIRECTORY_SEPARATOR . 'Smarty' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR;
if ($customPluginsDir) {
$this->plugins_dir = [$customPluginsDir, $smartyDir . 'plugins', $pluginsDir];
}
else {
$this->plugins_dir = [$smartyDir . 'plugins', $pluginsDir];
}
$this->compile_check = $this->isCheckSmartyIsCompiled();
// add the session and the config here
$session = CRM_Core_Session::singleton();
$this->assign_by_ref('config', $config);
$this->assign_by_ref('session', $session);
$tsLocale = CRM_Core_I18n::getLocale();
$this->assign('tsLocale', $tsLocale);
// CRM-7163 hack: we don’t display langSwitch on upgrades anyway
if (!CRM_Core_Config::isUpgradeMode()) {
$this->assign('langSwitch', CRM_Core_I18n::uiLanguages());
}
$this->register_function('crmURL', ['CRM_Utils_System', 'crmURL']);
if (CRM_Utils_Constant::value('CIVICRM_SMARTY_DEFAULT_ESCAPE')) {
// When default escape is enabled if the core escape is called before
// any custom escaping is done the modifier_escape function is not
// found, so require_once straight away. Note this was hit on the basic
// contribution dashboard from RecentlyViewed.tpl
require_once 'Smarty/plugins/modifier.escape.php';
if (!isset($this->_plugins['modifier']['escape'])) {
$this->register_modifier('escape', ['CRM_Core_Smarty', 'escape']);
}
$this->default_modifiers[] = 'escape:"htmlall"';
}
$this->load_filter('pre', 'resetExtScope');
$this->assign('crmPermissions', new CRM_Core_Smarty_Permissions());
if ($config->debug) {
$this->error_reporting = E_ALL;
}
}
/**
* Static instance provider.
*
* Method providing static instance of SmartTemplate, as
* in Singleton pattern.
*
* @return \CRM_Core_Smarty
*/
public static function &singleton() {
if (!isset(self::$_singleton)) {
self::$_singleton = new CRM_Core_Smarty();
self::$_singleton->initialize();
self::registerStringResource();
}
return self::$_singleton;
}
/**
* Executes & returns or displays the template results
*
* @param string $resource_name
* @param string $cache_id
* @param string $compile_id
* @param bool $display
*
* @return bool|mixed|string
*/
public function fetch($resource_name, $cache_id = NULL, $compile_id = NULL, $display = FALSE) {
if (preg_match('/^(\s+)?string:/', $resource_name)) {
$old_security = $this->security;
$this->security = TRUE;
}
$output = parent::fetch($resource_name, $cache_id, $compile_id, $display);
if (isset($old_security)) {
$this->security = $old_security;
}
return $output;
}
/**
* Ensure these variables are set to make it easier to access them without e-notice.
*
* @param array $variables
*/
public function ensureVariablesAreAssigned(array $variables): void {
foreach ($variables as $variable) {
if (!isset($this->get_template_vars()[$variable])) {
$this->assign($variable);
}
}
}
/**
* Fetch a template (while using certain variables)
*
* @param string $resource_name
* @param array $vars
* (string $name => mixed $value) variables to export to Smarty.
* @throws Exception
* @return bool|mixed|string
*/
public function fetchWith($resource_name, $vars) {
$this->pushScope($vars);
try {
$result = $this->fetch($resource_name);
}
catch (Exception $e) {
// simulate try { ... } finally { ... }
$this->popScope();
throw $e;
}
$this->popScope();
return $result;
}
/**
* @param string $name
* @param $value
*/
public function appendValue($name, $value) {
$currentValue = $this->get_template_vars($name);
if (!$currentValue) {
$this->assign($name, $value);
}
else {
if (strpos($currentValue, $value) === FALSE) {
$this->assign($name, $currentValue . $value);
}
}
}
public function clearTemplateVars() {
foreach (array_keys($this->_tpl_vars) as $key) {
if ($key == 'config' || $key == 'session') {
continue;
}
unset($this->_tpl_vars[$key]);
}
}
public static function registerStringResource() {
require_once 'CRM/Core/Smarty/resources/String.php';
civicrm_smarty_register_string_resource();
}
/**
* @param $path
*/
public function addTemplateDir($path) {
if (is_array($this->template_dir)) {
array_unshift($this->template_dir, $path);
}
else {
$this->template_dir = [$path, $this->template_dir];
}
}
/**
* Temporarily assign a list of variables.
*
* ```
* $smarty->pushScope(array(
* 'first_name' => 'Alice',
* 'last_name' => 'roberts',
* ));
* $html = $smarty->fetch('view-contact.tpl');
* $smarty->popScope();
* ```
*
* @param array $vars
* (string $name => mixed $value).
* @return CRM_Core_Smarty
* @see popScope
*/
public function pushScope($vars) {
$oldVars = $this->get_template_vars();
$backupFrame = [];
foreach ($vars as $key => $value) {
$backupFrame[$key] = $oldVars[$key] ?? NULL;
}
$this->backupFrames[] = $backupFrame;
$this->assignAll($vars);
return $this;
}
/**
* Remove any values that were previously pushed.
*
* @return CRM_Core_Smarty
* @see pushScope
*/
public function popScope() {
$this->assignAll(array_pop($this->backupFrames));
return $this;
}
/**
* @param array $vars
* (string $name => mixed $value).
* @return CRM_Core_Smarty
*/
public function assignAll($vars) {
foreach ($vars as $key => $value) {
$this->assign($key, $value);
}
return $this;
}
/**
* Get the locale for translation.
*
* @return string
*/
private function getLocale() {
$tsLocale = CRM_Core_I18n::getLocale();
if (!empty($tsLocale)) {
return $tsLocale;
}
$config = CRM_Core_Config::singleton();
if (!empty($config->lcMessages)) {
return $config->lcMessages;
}
return 'en_US';
}
/**
* Get the compile_check value.
*
* @return bool
*/
private function isCheckSmartyIsCompiled() {
// check for define in civicrm.settings.php as FALSE, otherwise returns TRUE
return CRM_Utils_Constant::value('CIVICRM_TEMPLATE_COMPILE_CHECK', TRUE);
}
/**
* Smarty escape modifier plugin.
*
* This replaces the core smarty modifier and basically does a lot of
* early-returning before calling the core function.
*
* It early returns on patterns that are common 'no-escape' patterns
* in CiviCRM - this list can be honed over time.
*
* It also logs anything that is actually escaped. Since this only kicks
* in when CIVICRM_SMARTY_DEFAULT_ESCAPE is defined it is ok to be aggressive
* about logging as we mostly care about developers using it at this stage.
*
* Note we don't actually use 'htmlall' anywhere in our tpl layer yet so
* anything coming in with this be happening because of the default modifier.
*
* Also note the right way to opt a field OUT of escaping is
* ``{$fieldName|smarty:nodefaults}``
* This should be used for fields with known html AND for fields where
* we are doing empty or isset checks - as otherwise the value is passed for
* escaping first so you still get an enotice for 'empty' or a fatal for 'isset'
*
* Type: modifier
* Name: escape
* Purpose: Escape the string according to escapement type
*
* @link http://smarty.php.net/manual/en/language.modifier.escape.php
* escape (Smarty online manual)
* @author Monte Ohrt
*
* @param string $string
* @param string $esc_type
* @param string $char_set
*
* @return string
*/
public static function escape($string, $esc_type = 'html', $char_set = 'UTF-8') {
// CiviCRM variables are often arrays - just handle them.
// The early return on booleans & numbers is mostly to prevent them being
// logged as 'changed' when they are cast to a string.
if (!is_scalar($string) || empty($string) || is_bool($string) || is_numeric($string) || $esc_type === 'none') {
return $string;
}
if ($esc_type === 'htmlall') {
// 'htmlall' is the nothing-specified default.
// Don't escape things we think quickform added.
if (strpos($string, '') === 0
|| strpos($string, '