diff --git a/CHANGELOG.md b/CHANGELOG.md index 222d589c..ffc18d0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- You can now use `$smarty->setPHP7CompatMode()` to activate php7 compatibility mode when running PHP8 + ### Changed - Switch CI from Travis to Github CI - Updated unit tests to avoid skipped and risky test warnings diff --git a/libs/Smarty.class.php b/libs/Smarty.class.php index 06927b74..0257f365 100644 --- a/libs/Smarty.class.php +++ b/libs/Smarty.class.php @@ -640,6 +640,12 @@ class Smarty extends Smarty_Internal_TemplateBase 'cache_dir' => 'CacheDir', ); + /** + * PHP7 Compatibility mode + * @var bool + */ + private $php7CompatMode = false; + /** * Initialize new Smarty object */ @@ -1369,4 +1375,23 @@ class Smarty extends Smarty_Internal_TemplateBase $isConfig ? $this->_joined_config_dir = join('#', $this->config_dir) : $this->_joined_template_dir = join('#', $this->template_dir); } + + /** + * Activates PHP7 compatibility mode: + * - converts E_WARNINGS for "undefined array key" and "trying to read property of null" errors to E_NOTICE + * + * @void + */ + public function setPHP7CompatMode(): void { + $this->php7CompatMode = true; + } + + /** + * Indicates if PHP7 compatibility mode is set. + * @bool + */ + public function getPHP7CompatMode(): bool { + return $this->php7CompatMode; + } + } diff --git a/libs/sysplugins/smarty_internal_errorhandler.php b/libs/sysplugins/smarty_internal_errorhandler.php index 56dca18f..04f1c7f2 100644 --- a/libs/sysplugins/smarty_internal_errorhandler.php +++ b/libs/sysplugins/smarty_internal_errorhandler.php @@ -1,55 +1,54 @@ previousErrorHandler = set_error_handler([$this, 'handleError']); + } + + /** + * Disable error handler + */ + public function deactivate() { + restore_error_handler(); + $this->previousErrorHandler = null; } /** @@ -65,49 +64,21 @@ class Smarty_Internal_ErrorHandler * * @return bool */ - public static function mutingErrorHandler($errno, $errstr, $errfile, $errline, $errcontext = array()) + public function handleError($errno, $errstr, $errfile, $errline, $errcontext = []) { - $_is_muted_directory = false; - // add the SMARTY_DIR to the list of muted directories - if (!isset(self::$mutedDirectories[ SMARTY_DIR ])) { - $smarty_dir = realpath(SMARTY_DIR); - if ($smarty_dir !== false) { - self::$mutedDirectories[ SMARTY_DIR ] = - array('file' => $smarty_dir, 'length' => strlen($smarty_dir),); - } + if ($this->allowUndefinedVars && $errstr == 'Attempt to read property "value" on null') { + return; // suppresses this error } - // walk the muted directories and test against $errfile - foreach (self::$mutedDirectories as $key => &$dir) { - if (!$dir) { - // resolve directory and length for speedy comparisons - $file = realpath($key); - if ($file === false) { - // this directory does not exist, remove and skip it - unset(self::$mutedDirectories[ $key ]); - continue; - } - $dir = array('file' => $file, 'length' => strlen($file),); - } - if (!strncmp($errfile, $dir[ 'file' ], $dir[ 'length' ])) { - $_is_muted_directory = true; - break; - } - } - // pass to next error handler if this error did not occur inside SMARTY_DIR - // or the error was within smarty but masked to be ignored - if (!$_is_muted_directory || ($errno && $errno & error_reporting())) { - if (self::$previousErrorHandler) { - return call_user_func( - self::$previousErrorHandler, - $errno, - $errstr, - $errfile, - $errline, - $errcontext - ); - } else { - return false; - } + + if ($this->allowUndefinedArrayKeys && preg_match( + '/^(Undefined array key|Trying to access array offset on value of type null)/', + $errstr + )) { + return; // suppresses this error } + + // pass all other errors through to the previous error handler or to the default PHP error handler + return $this->previousErrorHandler ? + call_user_func($this->previousErrorHandler, $errno, $errstr, $errfile, $errline, $errcontext) : false; } } diff --git a/libs/sysplugins/smarty_internal_templatebase.php b/libs/sysplugins/smarty_internal_templatebase.php index 200c11bb..b8d444e3 100644 --- a/libs/sysplugins/smarty_internal_templatebase.php +++ b/libs/sysplugins/smarty_internal_templatebase.php @@ -199,6 +199,12 @@ abstract class Smarty_Internal_TemplateBase extends Smarty_Internal_Data try { $_smarty_old_error_level = isset($smarty->error_reporting) ? error_reporting($smarty->error_reporting) : null; + + if ($smarty->getPHP7CompatMode()) { + $errorHandler = new Smarty_Internal_ErrorHandler(); + $errorHandler->activate(); + } + if ($this->_objType === 2) { /* @var Smarty_Internal_Template $this */ $template->tplFunctions = $this->tplFunctions; @@ -242,6 +248,11 @@ abstract class Smarty_Internal_TemplateBase extends Smarty_Internal_Data } } } + + if (isset($errorHandler)) { + $errorHandler->deactivate(); + } + if (isset($_smarty_old_error_level)) { error_reporting($_smarty_old_error_level); } @@ -250,9 +261,13 @@ abstract class Smarty_Internal_TemplateBase extends Smarty_Internal_Data while (ob_get_level() > $level) { ob_end_clean(); } - if (isset($_smarty_old_error_level)) { - error_reporting($_smarty_old_error_level); - } + if (isset($errorHandler)) { + $errorHandler->deactivate(); + } + + if (isset($_smarty_old_error_level)) { + error_reporting($_smarty_old_error_level); + } throw $e; } } diff --git a/tests/UnitTests/A_2/UndefinedTemplateVar/UndefinedTemplateVarTest.php b/tests/UnitTests/A_2/UndefinedTemplateVar/UndefinedTemplateVarTest.php index b7aaf43c..090fcb47 100644 --- a/tests/UnitTests/A_2/UndefinedTemplateVar/UndefinedTemplateVarTest.php +++ b/tests/UnitTests/A_2/UndefinedTemplateVar/UndefinedTemplateVarTest.php @@ -16,7 +16,6 @@ class UndefinedTemplateVarTest extends PHPUnit_Smarty public function setUp(): void { $this->setUpSmarty(dirname(__FILE__)); - error_reporting(E_ALL | E_STRICT); } public function testInit() @@ -48,9 +47,9 @@ class UndefinedTemplateVarTest extends PHPUnit_Smarty $this->assertEquals($e1, $e2); } - /** - * Test Error suppression template object fetched by Smarty object - */ + /** + * Test Error suppression template object fetched by Smarty object + */ public function testErrorDisabledTplObject_2() { $e1 = error_reporting(); @@ -66,27 +65,73 @@ class UndefinedTemplateVarTest extends PHPUnit_Smarty */ public function testError() { - $exceptionThrown = false; + $exceptionThrown = false; try { - $e1 = error_reporting(); - $this->assertEquals('undefined = ', $this->smarty->fetch('001_main.tpl')); - $e2 = error_reporting(); - $this->assertEquals($e1, $e2); + $e1 = error_reporting(); + $this->assertEquals('undefined = ', $this->smarty->fetch('001_main.tpl')); + $e2 = error_reporting(); + $this->assertEquals($e1, $e2); } catch (Exception $e) { - $exceptionThrown = true; - $this->assertStringStartsWith('Undefined ', $e->getMessage()); - $this->assertTrue(in_array( - get_class($e), - array( - 'PHPUnit_Framework_Error_Warning', - 'PHPUnit_Framework_Error_Notice', - 'PHPUnit\Framework\Error\Warning', - 'PHPUnit\Framework\Error\Notice', - ) - )); + $exceptionThrown = true; + $this->assertStringStartsWith('Undefined ', $e->getMessage()); + $this->assertTrue(in_array( + get_class($e), + [ + 'PHPUnit\Framework\Error\Warning', + 'PHPUnit\Framework\Error\Notice', + ] + )); } - $this->assertTrue($exceptionThrown); + $this->assertTrue($exceptionThrown); } + + public function testUndefinedSimpleVar() { + $this->smarty->setErrorReporting(E_ALL & ~E_NOTICE); + $this->smarty->setPHP7CompatMode(); + $tpl = $this->smarty->createTemplate('string:a{if $undef}def{/if}b'); + $this->assertEquals("ab", $this->smarty->fetch($tpl)); + } + + public function testUndefinedArrayIndex() { + $this->smarty->setErrorReporting(E_ALL & ~E_NOTICE); + $this->smarty->setPHP7CompatMode(); + $tpl = $this->smarty->createTemplate('string:a{if $ar.undef}def{/if}b'); + $tpl->assign('ar', []); + $this->assertEquals("ab", $this->smarty->fetch($tpl)); + } + + public function testUndefinedArrayIndexDeep() { + $this->smarty->setErrorReporting(E_ALL & ~E_NOTICE); + $this->smarty->setPHP7CompatMode(); + $tpl = $this->smarty->createTemplate('string:a{if $ar.undef.nope.neither}def{/if}b'); + $tpl->assign('ar', []); + $this->assertEquals("ab", $this->smarty->fetch($tpl)); + } + + public function testUndefinedArrayIndexError() + { + $exceptionThrown = false; + + try { + $tpl = $this->smarty->createTemplate('string:a{if $ar.undef}def{/if}b'); + $tpl->assign('ar', []); + $this->smarty->fetch($tpl); + } catch (Exception $e) { + + $exceptionThrown = true; + $this->assertStringStartsWith('Undefined ', $e->getMessage()); + $this->assertTrue(in_array( + get_class($e), + [ + 'PHPUnit\Framework\Error\Warning', + 'PHPUnit\Framework\Error\Notice', + ] + )); + } + $this->assertTrue($exceptionThrown); + } + + } diff --git a/tests/UnitTests/TemplateSource/TagTests/PluginFunction/PluginFunctionHtmlCheckboxesTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginFunction/PluginFunctionHtmlCheckboxesTest.php index 5ea998fd..ee277d5f 100644 --- a/tests/UnitTests/TemplateSource/TagTests/PluginFunction/PluginFunctionHtmlCheckboxesTest.php +++ b/tests/UnitTests/TemplateSource/TagTests/PluginFunction/PluginFunctionHtmlCheckboxesTest.php @@ -285,12 +285,7 @@ class PluginFunctionHtmlCheckboxesTest extends PHPUnit_Smarty $this->_errors = array(); set_error_handler(array($this, 'error_handler')); - $n = "\n"; - $expected = '
' - . $n . '
' - . $n . '
' - . $n . '
'; - + $this->smarty->setPHP7CompatMode(); $tpl = $this->smarty->createTemplate('eval:{html_checkboxes name="id" options=$cust_radios selected=$customer_id separator="
"}'); $tpl->assign('customer_id', new _object_noString(1001)); $tpl->assign('cust_radios', array( @@ -312,12 +307,6 @@ class PluginFunctionHtmlCheckboxesTest extends PHPUnit_Smarty $this->_errors = array(); set_error_handler(array($this, 'error_handler')); - $n = "\n"; - $expected = '
' - . $n . '
' - . $n . '
' - . $n . '
'; - $tpl = $this->smarty->createTemplate('eval:{html_checkboxes name="id" options=$cust_radios selected=$customer_id separator="
"}'); $tpl->assign('customer_id', 1001); $tpl->assign('cust_radios', array(