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);
+ }
+}