From 244313a76a5819332978a0cbe81bd4d9b300e7cc Mon Sep 17 00:00:00 2001 From: Yury Pikhtarev Date: Sat, 21 Jun 2025 02:49:12 +0400 Subject: [PATCH] feat(language): add new language variable for migration file and enhance template fallback logic - Introduced a new language variable 'MIGRATIONS_FILE' to improve migration management clarity. - Updated template handling to provide graceful fallbacks for missing language and regular variables, ensuring better user experience and debugging capabilities. - Added comprehensive unit tests to validate the new fallback logic and ensure existing functionality remains intact. --- library/language/source/main.php | 1 + src/Legacy/Template.php | 4 +- .../Legacy/TemplateGracefulFallbackTest.php | 346 ++++++++++++++++++ 3 files changed, 349 insertions(+), 2 deletions(-) create mode 100644 tests/Unit/Legacy/TemplateGracefulFallbackTest.php diff --git a/library/language/source/main.php b/library/language/source/main.php index 5228ede0b..aed625dad 100644 --- a/library/language/source/main.php +++ b/library/language/source/main.php @@ -1962,6 +1962,7 @@ $lang['MIGRATIONS_APPLIED'] = 'Applied Migrations'; $lang['MIGRATIONS_PENDING'] = 'Pending Migrations'; $lang['MIGRATIONS_VERSION'] = 'Version'; $lang['MIGRATIONS_NAME'] = 'Migration Name'; +$lang['MIGRATIONS_FILE'] = 'Migration File'; $lang['MIGRATIONS_APPLIED_AT'] = 'Applied At'; $lang['MIGRATIONS_COMPLETED_AT'] = 'Completed At'; $lang['MIGRATIONS_CURRENT_VERSION'] = 'Current Version'; diff --git a/src/Legacy/Template.php b/src/Legacy/Template.php index a65e3b81a..83ec25199 100644 --- a/src/Legacy/Template.php +++ b/src/Legacy/Template.php @@ -421,7 +421,7 @@ class Template // Append the variable reference. $varref .= "['$varname']"; - $varref = ""; + $varref = ""; return $varref; } @@ -766,7 +766,7 @@ class Template $code = str_replace($search, $replace, $code); } // This will handle the remaining root-level varrefs - $code = preg_replace('#\{(L_([a-z0-9\-_]+?))\}#i', '', $code); + $code = preg_replace('#\{(L_([a-z0-9\-_]+?))\}#i', '', $code); $code = preg_replace('#\{(\$[a-z_][a-z0-9_$\->\'\"\.\[\]]*?)\}#i', '', $code); $code = preg_replace('#\{(\#([a-z_][a-z0-9_]*?)\#)\}#i', '', $code); $code = preg_replace('#\{([a-z0-9\-_]+?)\}#i', '', $code); diff --git a/tests/Unit/Legacy/TemplateGracefulFallbackTest.php b/tests/Unit/Legacy/TemplateGracefulFallbackTest.php new file mode 100644 index 000000000..626188b82 --- /dev/null +++ b/tests/Unit/Legacy/TemplateGracefulFallbackTest.php @@ -0,0 +1,346 @@ +', '|', ' ']; + return str_replace($s, '_', trim($fname)); + } + } + + if (!function_exists('config')) { + function config() + { + return new class { + public function get($key, $default = null) + { + // Return sensible defaults for template configuration + return match ($key) { + 'xs_use_cache' => 0, + 'default_lang' => 'en', + default => $default + }; + } + }; + } + } + + // Create a temporary directory for templates and cache + $this->tempDir = createTempDirectory(); + $this->templateDir = $this->tempDir . '/templates'; + $this->cacheDir = $this->tempDir . '/cache'; + + mkdir($this->templateDir, 0755, true); + mkdir($this->cacheDir, 0755, true); + + // Set up global language array for testing + global $lang; + $lang = [ + 'EXISTING_KEY' => 'This key exists', + 'ANOTHER_KEY' => 'Another existing key' + ]; + + // Create template instance + $this->template = new Template($this->templateDir); + $this->template->cachedir = $this->cacheDir . '/'; + $this->template->use_cache = 0; // Disable caching for tests +}); + +afterEach(function () { + // Clean up + if (isset($this->tempDir)) { + removeTempDirectory($this->tempDir); + } + + // Reset global state + resetGlobalState(); +}); + +describe('Template Text Compilation - Graceful Fallback', function () { + + it('debugs compiled output for troubleshooting', function () { + $template = '{L_MISSING_KEY}'; + $compiled = $this->template->_compile_text($template); + + // Show the actual compiled output for debugging + expect($compiled)->toBeString(); + + // Print the compiled code to understand the issue + error_log("Compiled template: " . $compiled); + }); + + it('shows missing language variables as original syntax', function () { + $template = '{L_MISSING_KEY}'; + $compiled = $this->template->_compile_text($template); + + // Extract and execute the PHP code + ob_start(); + global $lang; + $L = &$lang; + $V = []; + + // Execute the compiled template properly + eval('?>' . $compiled); + $output = ob_get_clean(); + + expect($output)->toBe('L_MISSING_KEY'); + }); + + it('shows existing language variables correctly', function () { + $template = '{L_EXISTING_KEY}'; + $compiled = $this->template->_compile_text($template); + + // Extract and execute the PHP code + ob_start(); + global $lang; + $L = &$lang; + $V = []; + + // Execute the compiled template properly + eval('?>' . $compiled); + $output = ob_get_clean(); + + expect($output)->toBe('This key exists'); + }); + + it('shows missing regular variables as original syntax', function () { + $template = '{MISSING_VAR}'; + $compiled = $this->template->_compile_text($template); + + // Extract and execute the PHP code + ob_start(); + global $lang; + $L = &$lang; + $V = []; + eval('?>' . $compiled); + $output = ob_get_clean(); + + expect($output)->toBe(''); + }); + + it('shows existing regular variables correctly', function () { + $template = '{EXISTING_VAR}'; + $compiled = $this->template->_compile_text($template); + + // Extract and execute the PHP code + ob_start(); + global $lang; + $L = &$lang; + $V = ['EXISTING_VAR' => 'This variable exists']; + eval('?>' . $compiled); + $output = ob_get_clean(); + + expect($output)->toBe('This variable exists'); + }); + + it('shows missing constants as original syntax', function () { + $template = '{#MISSING_CONSTANT#}'; + $compiled = $this->template->_compile_text($template); + + // Extract and execute the PHP code + ob_start(); + global $lang; + $L = &$lang; + $V = []; + eval('?>' . $compiled); + $output = ob_get_clean(); + + expect($output)->toBe(''); + }); + + it('shows existing constants correctly', function () { + // Define a test constant + if (!defined('TEST_CONSTANT')) { + define('TEST_CONSTANT', 'This constant exists'); + } + + $template = '{#TEST_CONSTANT#}'; + $compiled = $this->template->_compile_text($template); + + // Extract and execute the PHP code + ob_start(); + global $lang; + $L = &$lang; + $V = []; + eval('?>' . $compiled); + $output = ob_get_clean(); + + expect($output)->toBe('This constant exists'); + }); + + it('handles mixed existing and missing variables correctly', function () { + $template = '{L_EXISTING_KEY} - {L_MISSING_KEY} - {EXISTING_VAR} - {MISSING_VAR}'; + $compiled = $this->template->_compile_text($template); + + // Extract and execute the PHP code + ob_start(); + global $lang; + $L = &$lang; + $V = ['EXISTING_VAR' => 'Variable exists']; + + // Execute the compiled template properly + eval('?>' . $compiled); + $output = ob_get_clean(); + + expect($output)->toBe('This key exists - L_MISSING_KEY - Variable exists - '); + }); + + it('handles PHP variables correctly without fallback', function () { + $template = '{$test_var}'; + $compiled = $this->template->_compile_text($template); + + // Extract and execute the PHP code + ob_start(); + global $lang; + $L = &$lang; + $V = []; + $test_var = 'PHP variable value'; + eval('?>' . $compiled); + $output = ob_get_clean(); + + expect($output)->toBe('PHP variable value'); + }); + + it('handles undefined PHP variables gracefully', function () { + $template = '{$undefined_var}'; + $compiled = $this->template->_compile_text($template); + + // Extract and execute the PHP code + ob_start(); + global $lang; + $L = &$lang; + $V = []; + // Note: $undefined_var is not defined + eval('?>' . $compiled); + $output = ob_get_clean(); + + // PHP variables that don't exist should show empty string (original behavior) + expect($output)->toBe(''); + }); + +}); + +describe('Template Block Variable Fallback', function () { + + it('shows missing block variables as original syntax', function () { + $namespace = 'testblock'; + $varname = 'MISSING_VAR'; + + $result = $this->template->generate_block_varref($namespace . '.', $varname); + + // The result should be PHP code that shows the missing variable syntax without braces + expect($result)->toContain('testblock.MISSING_VAR'); + }); + + it('generates correct PHP code for block variable fallback', function () { + $namespace = 'news'; + $varname = 'TITLE'; + + $result = $this->template->generate_block_varref($namespace . '.', $varname); + + // Should contain the fallback syntax without braces + expect($result)->toContain('news.TITLE'); + expect($result)->toContain('template->_compile_text($template); + + // Verify the compiled PHP code contains the expected fallback logic + expect($compiled)->toContain("isset(\$L['MISSING_KEY'])"); + expect($compiled)->toContain("'L_MISSING_KEY'"); + }); + + it('compiles regular variables with proper fallback code', function () { + $template = '{MISSING_VAR}'; + $compiled = $this->template->_compile_text($template); + + // Verify the compiled PHP code contains the expected fallback logic + expect($compiled)->toContain("isset(\$V['MISSING_VAR'])"); + expect($compiled)->toContain("''"); + }); + + it('compiles constants with proper fallback code', function () { + $template = '{#MISSING_CONSTANT#}'; + $compiled = $this->template->_compile_text($template); + + // Verify the compiled PHP code contains the expected fallback logic + expect($compiled)->toContain("defined('MISSING_CONSTANT')"); + expect($compiled)->toContain("''"); + }); + +}); + +describe('Real-world Example - Admin Migrations', function () { + + it('handles the original L_MIGRATIONS_FILE error gracefully', function () { + // The exact template that was causing the error + $template = '{L_MIGRATIONS_FILE}'; + $compiled = $this->template->_compile_text($template); + + // Execute the compiled template + ob_start(); + global $lang; + $L = &$lang; + $V = []; + + // Execute the compiled template properly + eval('?>' . $compiled); + $output = ob_get_clean(); + + // Should show the fallback without braces instead of throwing an error + expect($output)->toContain('L_MIGRATIONS_FILE'); + expect($output)->toContain('