From a3b85f3fc8558c113fc851f2ab70eed876e5a0cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Ho=CC=88=C3=9Fl?= Date: Wed, 24 Dec 2025 08:02:55 -0500 Subject: [PATCH 1/7] Patch simpletest --- .github/workflows/ci.yml | 3 + composer.json | 11 +- tests/patches/simpletest-errors.php | 265 ++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+), 9 deletions(-) create mode 100644 tests/patches/simpletest-errors.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af72cbab..0ffd0164 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,5 +32,8 @@ jobs: - name: Configure simpletest run: cp test-settings.sample.php test-settings.php + - name: Fix PHP 8.5 imcompatibility in simpletest + run: cp tests/patches/simpletest-errors.php vendor/simpletest/simpletest/src/errors.php + - name: Execute Unit tests run: php tests/index.php diff --git a/composer.json b/composer.json index cfb71517..af88c553 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ }, "require-dev": { "cerdic/css-tidy": "^1.7 || ^2.0", - "simpletest/simpletest": "dev-master" + "simpletest/simpletest": "v1.3.0" }, "autoload": { "psr-0": { "HTMLPurifier": "library/" }, @@ -34,12 +34,5 @@ }, "config": { "sort-packages": true - }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/ezyang/simpletest.git", - "no-api": true - } - ] + } } diff --git a/tests/patches/simpletest-errors.php b/tests/patches/simpletest-errors.php new file mode 100644 index 00000000..aa836a27 --- /dev/null +++ b/tests/patches/simpletest-errors.php @@ -0,0 +1,265 @@ +createErrorQueue(); + set_error_handler('SimpleTestErrorHandler'); + parent::invoke($method); + restore_error_handler(); + $queue->tally(); + } + + /** + * Wires up the error queue for a single test. + * + * @return SimpleErrorQueue queue connected to the test + */ + protected function createErrorQueue() + { + $context = SimpleTest::getContext(); + $test = $this->getTestCase(); + $queue = $context->get('SimpleErrorQueue'); + $queue->setTestCase($test); + + return $queue; + } +} + +/** + * Error queue used to record trapped errors. + */ +class SimpleErrorQueue +{ + private $queue; + private $expectation_queue; + private $test; + + /** + * Starts with an empty queue. + */ + public function __construct() + { + $this->clear(); + } + + /** + * Discards the contents of the error queue. + */ + public function clear() + { + $this->queue = []; + $this->expectation_queue = []; + } + + /** + * Sets the currently running test case. + * + * @param SimpleTestCase $test test case to send messages to + */ + public function setTestCase($test) + { + $this->test = $test; + } + + /** + * Sets up an expectation of an error. + * If this is not fulfilled at the end of the test, a failure will occour. + * If the error does happen, then this will cancel it out and send a pass message. + * + * @param SimpleExpectation $expected expected error match + * @param string $message message to display + */ + public function expectError($expected, $message) + { + array_push($this->expectation_queue, [$expected, $message]); + } + + /** + * Adds an error to the front of the queue. + * + * @param int $severity PHP error code + * @param string $content text of error + * @param string $filename file error occoured in + * @param int $line line number of error + */ + public function add($severity, $content, $filename, $line) + { + $content = str_replace('%', '%%', $content); + $this->testLatestError($severity, $content, $filename, $line); + } + + /** + * Any errors still in the queue are sent to the test case. + * Any unfulfilled expectations trigger failures. + */ + public function tally() + { + while ($x = $this->extract()) { + list($severity, $message, $file, $line) = $x; + $severity = $this->getSeverityAsString($severity); + $this->test->error($severity, $message, $file, $line); + } + while ($x = $this->extractExpectation()) { + list($expected, $message) = $x; + $this->test->assert($expected, false, '%s -> Expected error not caught'); + } + } + + /** + * Tests the error against the most recent expected error. + * + * @param int $severity PHP error code + * @param string $content text of error + * @param string $filename file error occoured in + * @param int $line line number of error + */ + protected function testLatestError($severity, $content, $filename, $line) + { + $expectation = $this->extractExpectation(); + + if (false === $expectation) { + $this->test->error($severity, $content, $filename, $line); + } else { + list($expected, $message) = $expectation; + + $errorMessage = sprintf( + 'PHP error [%s] severity [%s] in [%s] line [%s]', + $content, + $this->getSeverityAsString($severity), + $filename, + $line + ); + + $this->test->assert($expected, $content, sprintf($message, '%s'.$errorMessage)); + } + } + + /** + * Pulls the earliest error from the queue. + * + * The list of error informations contains: + * - severity as the PHP error code, + * - the error message, + * - the file with the error, + * - the line number and + * - a list of PHP super global arrays. + * + * @return mixed false if none, or a list of error information + */ + public function extract() + { + if (count($this->queue)) { + return array_shift($this->queue); + } + + return false; + } + + /** + * Pulls the earliest expectation from the queue. + * + * @return SimpleExpectation false if none + */ + protected function extractExpectation() + { + if (count($this->expectation_queue)) { + return array_shift($this->expectation_queue); + } + + return false; + } + + /** + * Converts an error code into it's string representation. + * + * @param $severity PHP integer error code + * + * @return string version of error code + */ + public static function getSeverityAsString($severity) + { + static $map = [ + E_ERROR => 'E_ERROR', + E_WARNING => 'E_WARNING', + E_PARSE => 'E_PARSE', + E_NOTICE => 'E_NOTICE', + E_CORE_ERROR => 'E_CORE_ERROR', + E_CORE_WARNING => 'E_CORE_WARNING', + E_COMPILE_ERROR => 'E_COMPILE_ERROR', + E_COMPILE_WARNING => 'E_COMPILE_WARNING', + E_USER_WARNING => 'E_USER_WARNING', + E_USER_NOTICE => 'E_USER_NOTICE', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', // PHP 5.2 + E_DEPRECATED => 'E_DEPRECATED', // PHP 5.3 + E_USER_DEPRECATED => 'E_USER_DEPRECATED', // PHP 5.3 + E_ALL => 'E_ALL', + ]; + + // deprecated since PHP 8.4 + if (PHP_VERSION_ID < 80400) { + $map[E_USER_ERROR] = 'E_USER_ERROR'; + $map[E_STRICT] = 'E_STRICT'; + } + + return $map[$severity]; + } +} + +/** + * Error handler that simply stashes any errors into the global error queue. + * Simulates the existing behaviour with respect to logging errors, + * but this feature may be removed in future. + * + * @param $severity PHP error code + * @param $message text of error + * @param $file file error occoured in + * @param $line line number of error + * @param $context error Context + */ +function SimpleTestErrorHandler($severity, $message, $file = null, $line = null, $context = null) +{ + $severity = $severity & error_reporting(); + if ($severity) { + restore_error_handler(); + + $queue = SimpleTest::getContext()->get('SimpleErrorQueue'); + $queue->add($severity, $message, $file, $line); + + set_error_handler('SimpleTestErrorHandler'); + } + + return true; +} + +function simpletest_trigger_error(string $message, int $errorLevel = E_USER_NOTICE) +{ + if (PHP_VERSION_ID >= 80400 && E_USER_ERROR === $errorLevel) { + throw new ErrorException($message, $errorLevel); + } + trigger_error($message, $errorLevel); +} From 007c19b3a2cce5193cc4cb574f54ee13566a6179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Ho=CC=88=C3=9Fl?= Date: Wed, 24 Dec 2025 08:06:53 -0500 Subject: [PATCH 2/7] Test --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ffd0164..e023c250 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,9 +8,8 @@ jobs: linux_tests: runs-on: ubuntu-latest strategy: - fail-fast: true matrix: - php: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] + php: ['8.3', '8.4', '8.5'] name: PHP ${{ matrix.php }} From 5a64d3d02440cfc719614700778bce0764480633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Ho=CC=88=C3=9Fl?= Date: Wed, 24 Dec 2025 08:10:13 -0500 Subject: [PATCH 3/7] Strip down CI Pipeline --- .github/workflows/lint-pr.yml | 19 ------------------- .github/workflows/release.yml | 29 ----------------------------- 2 files changed, 48 deletions(-) delete mode 100644 .github/workflows/lint-pr.yml delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/lint-pr.yml b/.github/workflows/lint-pr.yml deleted file mode 100644 index 597a6ed7..00000000 --- a/.github/workflows/lint-pr.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: "Lint PR" - -on: - pull_request_target: - types: - - opened - - edited - - synchronize - -jobs: - main: - name: Validate PR title - - runs-on: ubuntu-latest - - steps: - - uses: amannn/action-semantic-pull-request@v4 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index aacd1269..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: release - -on: - workflow_dispatch: - -jobs: - release: - runs-on: ubuntu-latest - - name: Release - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 8.2 - - - name: Run automated release process with semantic-release - uses: cycjimmy/semantic-release-action@v4 - with: - extra_plugins: | - @semantic-release/changelog - @semantic-release/git - @semantic-release/exec - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 02d7f1a83b6bba9540a1245214d7f3b58d772e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Ho=CC=88=C3=9Fl?= Date: Wed, 24 Dec 2025 08:10:20 -0500 Subject: [PATCH 4/7] Fix PHP 8.5 issue --- .../HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php b/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php index 42d51444..822ba7bd 100644 --- a/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php +++ b/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php @@ -74,7 +74,7 @@ public function handleElement(&$token) if ($current instanceof HTMLPurifier_Token_End && $current->name === 'span') { // Mark closing span tag for deletion - $this->markForDeletion->attach($current); + $this->markForDeletion->offsetSet($current); // Delete open span tag $token = false; } @@ -85,8 +85,8 @@ public function handleElement(&$token) */ public function handleEnd(&$token) { - if ($this->markForDeletion->contains($token)) { - $this->markForDeletion->detach($token); + if ($this->markForDeletion->offsetExists($token)) { + $this->markForDeletion->offsetUnset($token); $token = false; } } From edf4b4c7b6fb75ef61b740d890de75c5f2fc2ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Ho=CC=88=C3=9Fl?= Date: Wed, 24 Dec 2025 08:16:39 -0500 Subject: [PATCH 5/7] Support null as scheme --- library/HTMLPurifier/URISchemeRegistry.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/HTMLPurifier/URISchemeRegistry.php b/library/HTMLPurifier/URISchemeRegistry.php index 4ac8a0b7..60f017cf 100644 --- a/library/HTMLPurifier/URISchemeRegistry.php +++ b/library/HTMLPurifier/URISchemeRegistry.php @@ -33,7 +33,7 @@ public static function instance($prototype = null) /** * Retrieves a scheme validator object - * @param string $scheme String scheme name like http or mailto + * @param string|null $scheme String scheme name like http or mailto * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return HTMLPurifier_URIScheme @@ -52,6 +52,9 @@ public function getScheme($scheme, $config, $context) return; } + if ($scheme === null) { + return; + } if (isset($this->schemes[$scheme])) { return $this->schemes[$scheme]; } From 77e5b4c8b538b1fa238d7f755201a7f632d5a495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Ho=CC=88=C3=9Fl?= Date: Wed, 24 Dec 2025 08:20:47 -0500 Subject: [PATCH 6/7] Use assertSame instead of assertReference --- tests/HTMLPurifier/DefinitionCacheFactoryTest.php | 2 +- tests/HTMLPurifier/HTMLModuleTest.php | 4 ++-- tests/HTMLPurifierTest.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/HTMLPurifier/DefinitionCacheFactoryTest.php b/tests/HTMLPurifier/DefinitionCacheFactoryTest.php index b1ca27a0..8b315380 100644 --- a/tests/HTMLPurifier/DefinitionCacheFactoryTest.php +++ b/tests/HTMLPurifier/DefinitionCacheFactoryTest.php @@ -49,7 +49,7 @@ public function test_create_recycling() { $cache = $this->factory->create('Test', $this->config); $cache2 = $this->factory->create('Test', $this->config); - $this->assertReference($cache, $cache2); + $this->assertSame($cache, $cache2); } public function test_create_invalid() diff --git a/tests/HTMLPurifier/HTMLModuleTest.php b/tests/HTMLPurifier/HTMLModuleTest.php index f4167aac..cb387c74 100644 --- a/tests/HTMLPurifier/HTMLModuleTest.php +++ b/tests/HTMLPurifier/HTMLModuleTest.php @@ -39,7 +39,7 @@ public function test_addElement() $this->assertIdentical($module, $module2); $this->assertIdentical($def, $def2); - $this->assertReference($def, $module->info['a']); + $this->assertSame($def, $module->info['a']); } @@ -112,7 +112,7 @@ public function test_addBlankElement() $def2 = new HTMLPurifier_ElementDef(); $def2->standalone = false; - $this->assertReference($module->info['a'], $def); + $this->assertSame($module->info['a'], $def); $this->assertIdentical($def, $def2); } diff --git a/tests/HTMLPurifierTest.php b/tests/HTMLPurifierTest.php index a51038eb..30c4999b 100644 --- a/tests/HTMLPurifierTest.php +++ b/tests/HTMLPurifierTest.php @@ -43,7 +43,7 @@ public function testGetInstance() { $purifier = HTMLPurifier::getInstance(); $purifier2 = HTMLPurifier::getInstance(); - $this->assertReference($purifier, $purifier2); + $this->assertSame($purifier, $purifier2); } public function testMakeAbsolute() From cf91170090aca639c42749889a3ca2f757ca92cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Ho=CC=88=C3=9Fl?= Date: Wed, 24 Dec 2025 08:22:41 -0500 Subject: [PATCH 7/7] Re-Enable older PHP Versions --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e023c250..0ffd0164 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,8 +8,9 @@ jobs: linux_tests: runs-on: ubuntu-latest strategy: + fail-fast: true matrix: - php: ['8.3', '8.4', '8.5'] + php: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] name: PHP ${{ matrix.php }}