diff --git a/system/Debug/BaseExceptionHandler.php b/system/Debug/BaseExceptionHandler.php index 33dd126ff1ec..6a5b5e47e5c7 100644 --- a/system/Debug/BaseExceptionHandler.php +++ b/system/Debug/BaseExceptionHandler.php @@ -182,8 +182,15 @@ protected static function highlightFile(string $file, int $lineNumber, int $line $source = str_replace(["\r\n", "\r"], "\n", $source); $source = explode("\n", highlight_string($source, true)); - $source = str_replace('
', "\n", $source[1]); - $source = explode("\n", str_replace("\r\n", "\n", $source)); + + if (PHP_VERSION_ID < 80300) { + $source = str_replace('
', "\n", $source[1]); + $source = explode("\n", str_replace("\r\n", "\n", $source)); + } else { + // We have to remove these tags since we're preparing the result + // ourselves and these tags are added manually at the end. + $source = str_replace(['
', '
'], '', $source); + } // Get just the part to show $start = max($lineNumber - (int) round($lines / 2), 0); @@ -199,7 +206,7 @@ protected static function highlightFile(string $file, int $lineNumber, int $line // of open and close span tags on one line, we need // to ensure we can close them all to get the lines // showing correctly. - $spans = 1; + $spans = 0; foreach ($source as $n => $row) { $spans += substr_count($row, '' . $format . ' %s', $n + $start + 1, $row) . "\n"; + // We're closing only one span tag we added manually line before, + // so we have to increment $spans count to close this tag later. + $spans++; } } diff --git a/system/Test/IniTestTrait.php b/system/Test/IniTestTrait.php new file mode 100644 index 000000000000..5820cfc1a71e --- /dev/null +++ b/system/Test/IniTestTrait.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Test; + +trait IniTestTrait +{ + private array $iniSettings = []; + + private function backupIniValues(array $keys): void + { + foreach ($keys as $key) { + $this->iniSettings[$key] = ini_get($key); + } + } + + private function restoreIniValues(): void + { + foreach ($this->iniSettings as $key => $value) { + ini_set($key, $value); + } + + $this->iniSettings = []; + } +} diff --git a/tests/_support/Debug/highlightFile.html b/tests/_support/Debug/highlightFile.html new file mode 100644 index 000000000000..a9eef9fe5b2c --- /dev/null +++ b/tests/_support/Debug/highlightFile.html @@ -0,0 +1,16 @@ +
 9  * the LICENSE file that was distributed with this source code.
+10  */
+11 
+12 namespace Tests\Support\Controllers;
+13 
+14 use CodeIgniter\Controller;
+15 
+16 class Hello extends Controller
+17 {
+18     public function index()
+19     {
+20         return 'Hello';
+21     }
+22 }
+23 
+
\ No newline at end of file diff --git a/tests/_support/Debug/highlightFile_pre_80000.html b/tests/_support/Debug/highlightFile_pre_80000.html new file mode 100644 index 000000000000..4b4ad7cecf34 --- /dev/null +++ b/tests/_support/Debug/highlightFile_pre_80000.html @@ -0,0 +1,16 @@ +
 9  * the LICENSE file that was distributed with this source code.
+10  */
+11 
+12 namespace Tests\Support\Controllers;
+13 
+14 use CodeIgniter\Controller;
+15 
+16 class Hello extends Controller
+17 {
+18     public function index()
+19     {
+20         return 'Hello';
+21     }
+22 }
+23 
+
\ No newline at end of file diff --git a/tests/_support/Debug/highlightFile_pre_80300.html b/tests/_support/Debug/highlightFile_pre_80300.html new file mode 100644 index 000000000000..4fb3633f2861 --- /dev/null +++ b/tests/_support/Debug/highlightFile_pre_80300.html @@ -0,0 +1,16 @@ +
 9  * the LICENSE file that was distributed with this source code.
+10  */
+11 
+12 namespace Tests\Support\Controllers;
+13 
+14 use CodeIgniter\Controller;
+15 
+16 class Hello extends Controller
+17 {
+18     public function index()
+19     {
+20         return 'Hello';
+21     }
+22 }
+23 
+
\ No newline at end of file diff --git a/tests/system/Debug/ExceptionHandlerTest.php b/tests/system/Debug/ExceptionHandlerTest.php index 1321f67195f3..8dcad8e6c338 100644 --- a/tests/system/Debug/ExceptionHandlerTest.php +++ b/tests/system/Debug/ExceptionHandlerTest.php @@ -14,6 +14,7 @@ use App\Controllers\Home; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Test\CIUnitTestCase; +use CodeIgniter\Test\IniTestTrait; use CodeIgniter\Test\StreamFilterTrait; use Config\Exceptions as ExceptionsConfig; use Config\Services; @@ -27,6 +28,7 @@ final class ExceptionHandlerTest extends CIUnitTestCase { use StreamFilterTrait; + use IniTestTrait; private ExceptionHandler $handler; @@ -237,4 +239,33 @@ public function testMaskSensitiveDataTraceDataKey(): void $this->assertSame('/var/www/CodeIgniter4/app/Controllers/Home.php', $newTrace[0]['file']); } + + public function testHighlightFile(): void + { + $this->backupIniValues([ + 'highlight.comment', 'highlight.default', 'highlight.html', 'highlight.keyword', 'highlight.string', + ]); + + $highlightFile = $this->getPrivateMethodInvoker($this->handler, 'highlightFile'); + $result = $highlightFile(SUPPORTPATH . 'Controllers' . DIRECTORY_SEPARATOR . 'Hello.php', 16); + + switch (true) { + case PHP_VERSION_ID < 80000: + $resultFile = 'highlightFile_pre_80000.html'; + break; + + case PHP_VERSION_ID < 80300: + $resultFile = 'highlightFile_pre_80300.html'; + break; + + default: + $resultFile = 'highlightFile.html'; + } + + $expected = file_get_contents(SUPPORTPATH . 'Debug' . DIRECTORY_SEPARATOR . $resultFile); + + $this->assertSame($expected, $result); + + $this->restoreIniValues(); + } } diff --git a/tests/system/Test/IniTestTraitTest.php b/tests/system/Test/IniTestTraitTest.php new file mode 100644 index 000000000000..e1c9a9a6f7c0 --- /dev/null +++ b/tests/system/Test/IniTestTraitTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Test; + +use ReflectionException; + +/** + * @internal + * + * @group Others + */ +final class IniTestTraitTest extends CIUnitTestCase +{ + use IniTestTrait; + + /** + * @throws ReflectionException + */ + public function testBackupAndRestoreIniValues(): void + { + $this->backupIniValues(['highlight.default']); + $backup = $this->getPrivateProperty($this, 'iniSettings'); + $this->assertSame('#0000BB', $backup['highlight.default']); + + ini_set('highlight.default', '#FFFFFF'); + $this->assertSame('#FFFFFF', ini_get('highlight.default')); + + $this->restoreIniValues(); + $this->assertSame('#0000BB', ini_get('highlight.default')); + + $backup = $this->getPrivateProperty($this, 'iniSettings'); + $this->assertSame([], $backup); + } +}