From b27bcdd42b464a9338f457b5c28ff2dbf890ad55 Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Thu, 12 Jan 2023 17:42:00 +1300 Subject: [PATCH] Ensure useful exception thrown for all Smarty errors from user strings --- CRM/Core/Smarty.php | 18 ++++++++++++++++++ CRM/Utils/String.php | 8 +++++++- tests/phpunit/CRM/Utils/StringTest.php | 14 ++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CRM/Core/Smarty.php b/CRM/Core/Smarty.php index 4874e2b542..5c0d1d884a 100644 --- a/CRM/Core/Smarty.php +++ b/CRM/Core/Smarty.php @@ -19,6 +19,9 @@ * Fix for bug CRM-392. Not sure if this is the best fix or it will impact * other similar PEAR packages. doubt it */ + +use Civi\Core\Event\SmartyErrorEvent; + if (!class_exists('Smarty')) { require_once 'Smarty/Smarty.class.php'; } @@ -196,6 +199,21 @@ class CRM_Core_Smarty extends Smarty { return $output; } + /** + * Handle smarty error in one off string. + * + * @param int $errorNumber + * @param string $errorMessage + * + * @throws \CRM_Core_Exception + */ + public function handleSmartyError(int $errorNumber, string $errorMessage): void { + $event = new SmartyErrorEvent($errorNumber, $errorMessage); + \Civi::dispatcher()->dispatch('civi.smarty.error', $event); + restore_error_handler(); + throw new \CRM_Core_Exception('Message was not parsed due to invalid smarty syntax : ' . $errorMessage); + } + /** * Ensure these variables are set to make it easier to access them without e-notice. * diff --git a/CRM/Utils/String.php b/CRM/Utils/String.php index e654e862a7..69f74151a0 100644 --- a/CRM/Utils/String.php +++ b/CRM/Utils/String.php @@ -1026,6 +1026,10 @@ class CRM_Utils_String { * @param string $templateString * * @return string + * + * @noinspection PhpDocRedundantThrowsInspection + * + * @throws \CRM_Core_Exception */ public static function parseOneOffStringThroughSmarty($templateString) { if (!CRM_Utils_String::stringContainsTokens($templateString)) { @@ -1034,13 +1038,15 @@ class CRM_Utils_String { } $smarty = CRM_Core_Smarty::singleton(); $cachingValue = $smarty->caching; + set_error_handler([$smarty, 'handleSmartyError'], E_USER_ERROR); $smarty->caching = 0; $smarty->assign('smartySingleUseString', $templateString); // Do not escape the smartySingleUseString as that is our smarty template // and is likely to contain html. $templateString = (string) $smarty->fetch('string:{eval var=$smartySingleUseString|smarty:nodefaults}'); $smarty->caching = $cachingValue; - $smarty->assign('smartySingleUseString', NULL); + $smarty->assign('smartySingleUseString'); + restore_error_handler(); return $templateString; } diff --git a/tests/phpunit/CRM/Utils/StringTest.php b/tests/phpunit/CRM/Utils/StringTest.php index 1e4cfdf5b8..43850aaa48 100644 --- a/tests/phpunit/CRM/Utils/StringTest.php +++ b/tests/phpunit/CRM/Utils/StringTest.php @@ -442,4 +442,18 @@ class CRM_Utils_StringTest extends CiviUnitTestCase { $this->assertFalse(CRM_Utils_String::unserialize($str)); } + /** + * Test that we get a meaningful error if Smarty syntax is wrong. + */ + public function testSmartyExceptionHandling(): void { + try { + CRM_Utils_String::parseOneOffStringThroughSmarty('{if}'); + } + catch (CRM_Core_Exception $e) { + $this->assertStringStartsWith('Message was not parsed due to invalid smarty syntax', $e->getMessage()); + return; + } + $this->fail('Exception expected'); + } + } -- 2.25.1