Exhibit B-1 Evidence: commit 141fe03
Committed · 141fe030db3f18891c7780ad395eb78e118bae60
PHP notices printed above every page
The repository itself is private; this page carries the commit as it is recorded there.
Commit message
The change
Text files only, as recorded in git. Lines starting with + were added, lines with − removed.
Files changed
---
e2e/tests/b-php8.spec.ts | 21 ++++++++++++++++
site/wp-content/hollis-config.php | 15 ++++++++----
site/wp-content/themes/hollis-child/functions.php | 28 +---------------------
tests/phpunit/repair/Php8DeprecationsTest.php | 56 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 89 insertions(+), 31 deletions(-)
diff --git a/e2e/tests/b-php8.spec.ts b/e2e/tests/b-php8.spec.ts
new file mode 100644
index 0000000..9971548
--- /dev/null
+++ b/e2e/tests/b-php8.spec.ts
@@ -0,0 +1,21 @@
+import { test, expect } from '@playwright/test';
+import { BEFORE, AFTER } from './helpers';
+
+// Exhibit B: PHP 8 errors as a visitor meets them.
+
+test('B-1 before: PHP deprecation notices are printed above the page and the log is public', async ({ page, request }) => {
+ await page.goto(BEFORE + '/');
+ await expect(page.locator('body')).toContainText('Deprecated:');
+ const log = await request.get(BEFORE + '/wp-content/debug.log');
+ expect(log.status()).toBe(200);
+ expect(await log.text()).toContain('PHP Deprecated');
+});
+
+test('B-1 after: no notice on any page and no public debug log', async ({ page, request }) => {
+ for (const path of ['/', '/our-team/', '/services/', '/request-appointment/', '/news/']) {
+ await page.goto(AFTER + path);
+ await expect(page.locator('body')).not.toContainText(/Deprecated:|Warning:|Notice:/);
+ }
+ const log = await request.get(AFTER + '/wp-content/debug.log');
+ expect(log.status()).toBe(404);
+});
diff --git a/site/wp-content/hollis-config.php b/site/wp-content/hollis-config.php
index a170ccf..014eae1 100644
--- a/site/wp-content/hollis-config.php
+++ b/site/wp-content/hollis-config.php
@@ -1,13 +1,20 @@
<?php
/**
- * Site configuration, required from wp-config.php.
+ * Site configuration, required from wp-config.php. Values come from the environment (docker-compose.yml).
*
* @package Hollis
*/
-// Turned on while debugging the 2024 host move, never turned off.
-define( 'WP_DEBUG_DISPLAY', true );
-define( 'WP_DEBUG_LOG', true );
+// Errors are logged outside the web root and never printed into pages.
+// WP_DEBUG stays on so the log proves the site runs without notices.
+if ( ! defined( 'WP_DEBUG_DISPLAY' ) ) {
+ define( 'WP_DEBUG_DISPLAY', false );
+}
+if ( ! defined( 'WP_DEBUG_LOG' ) ) {
+ define( 'WP_DEBUG_LOG', '/var/www/logs/' . basename( dirname( __DIR__ ) ) . '.log' );
+}
+// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- WordPress's own settings.
define( 'WP_AUTO_UPDATE_CORE', false );
define( 'AUTOMATIC_UPDATER_DISABLED', true );
+// phpcs:enable
diff --git a/site/wp-content/themes/hollis-child/functions.php b/site/wp-content/themes/hollis-child/functions.php
index 048b342..bc40d13 100644
--- a/site/wp-content/themes/hollis-child/functions.php
+++ b/site/wp-content/themes/hollis-child/functions.php
@@ -27,32 +27,6 @@ function hollis_child_assets() {
}
add_action( 'wp_enqueue_scripts', 'hollis_child_assets', 20 );
-/**
- * Old settings object for the header tagline.
- */
-class Hollis_Child_Settings {
- public $phone = '(555) 010-4417';
-}
-
-/**
- * Formats the phone number for the header link.
- */
-function hollis_child_format_phone( $prefix = '+1', $number ) {
- $digits = preg_replace( '/\D/', '', $number );
- return "${prefix}${digits}";
-}
-
-/**
- * Header tagline, from the 2019 theme options.
- */
-function hollis_child_tagline() {
- $settings = new Hollis_Child_Settings();
- $settings->tagline = get_option( 'hollis_child_options' );
- $legacy = is_array( $settings->tagline ) ? $settings->tagline : array();
- return trim( $legacy['tagline'] ?? null ) . ' ' . strlen( $legacy['subtitle'] ?? null );
-}
-add_action( 'wp_head', 'hollis_child_tagline' );
-
/**
* Practice contact details used by the parent theme's header and footer.
*
@@ -64,7 +38,7 @@ function hollis_child_contact( $contact ) {
$contact,
array(
'phone' => '(555) 010-4417',
- 'phone_href' => 'tel:' . hollis_child_format_phone( '+1', '555 010 4417' ),
+ 'phone_href' => 'tel:+15550104417',
'address' => hollis_child_is_ko() ? '418 Mill Race Road, Alder Bend (가상의 마을)' : '418 Mill Race Road, Alder Bend',
'cta_url' => home_url( '/request-appointment/' ),
)
diff --git a/tests/phpunit/repair/Php8DeprecationsTest.php b/tests/phpunit/repair/Php8DeprecationsTest.php
new file mode 100644
index 0000000..11e255a
--- /dev/null
+++ b/tests/phpunit/repair/Php8DeprecationsTest.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Exhibit B-1: the code runs on PHP 8.3 without deprecation notices, and errors never reach the page.
+ *
+ * @package Hollis\Tests
+ */
+
+/**
+ * PHP 8 deprecations.
+ */
+class Php8DeprecationsTest extends WP_UnitTestCase {
+
+ /**
+ * Every PHP file of the repaired code.
+ *
+ * @return string[]
+ */
+ private function php_files() {
+ $files = array();
+ $it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( HOLLIS_CODE, FilesystemIterator::SKIP_DOTS ) );
+ foreach ( $it as $file ) {
+ if ( 'php' === $file->getExtension() ) {
+ $files[] = $file->getPathname();
+ }
+ }
+ return $files;
+ }
+
+ public function test_no_file_has_compile_time_deprecations() {
+ $files = $this->php_files();
+ $this->assertGreaterThan( 10, count( $files ) );
+ foreach ( $files as $file ) {
+ // A fresh PHP process compiles each file, so notices raised at compile time are seen too.
+ $out = shell_exec( escapeshellarg( PHP_BINARY ) . ' -d error_reporting=-1 -d display_errors=1 -l ' . escapeshellarg( $file ) . ' 2>&1' );
+ $this->assertStringNotContainsString( 'Deprecated', (string) $out, $out );
+ $this->assertStringContainsString( 'No syntax errors', (string) $out, $out );
+ }
+ }
+
+ public function test_page_header_renders_without_notices() {
+ // Deprecations, notices and warnings become exceptions in this suite (phpunit.xml.dist).
+ $this->go_to( home_url( '/our-team/' ) );
+ ob_start();
+ get_header();
+ get_footer();
+ $html = ob_get_clean();
+ $this->assertStringContainsString( 'Hollis Creek', $html );
+ $this->assertStringNotContainsString( 'Deprecated:', $html );
+ }
+
+ public function test_errors_are_logged_outside_the_web_root_and_not_displayed() {
+ $config = file_get_contents( HOLLIS_CODE . '/hollis-config.php' );
+ $this->assertMatchesRegularExpression( "/define\(\s*'WP_DEBUG_DISPLAY',\s*false\s*\)/", $config );
+ $this->assertMatchesRegularExpression( "#define\(\s*'WP_DEBUG_LOG',\s*'/var/www/logs/#", $config );
+ }
+}
The proving test
tests/phpunit/repair/Php8DeprecationsTest.php
tests/phpunit/repair/Php8DeprecationsTest.php, as committed.
<?php
/**
* Exhibit B-1: the code runs on PHP 8.3 without deprecation notices, and errors never reach the page.
*
* @package Hollis\Tests
*/
/**
* PHP 8 deprecations.
*/
class Php8DeprecationsTest extends WP_UnitTestCase {
/**
* Every PHP file of the repaired code.
*
* @return string[]
*/
private function php_files() {
$files = array();
$it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( HOLLIS_CODE, FilesystemIterator::SKIP_DOTS ) );
foreach ( $it as $file ) {
if ( 'php' === $file->getExtension() ) {
$files[] = $file->getPathname();
}
}
return $files;
}
public function test_no_file_has_compile_time_deprecations() {
$files = $this->php_files();
$this->assertGreaterThan( 10, count( $files ) );
foreach ( $files as $file ) {
// A fresh PHP process compiles each file, so notices raised at compile time are seen too.
$out = shell_exec( escapeshellarg( PHP_BINARY ) . ' -d error_reporting=-1 -d display_errors=1 -l ' . escapeshellarg( $file ) . ' 2>&1' );
$this->assertStringNotContainsString( 'Deprecated', (string) $out, $out );
$this->assertStringContainsString( 'No syntax errors', (string) $out, $out );
}
}
public function test_page_header_renders_without_notices() {
// Deprecations, notices and warnings become exceptions in this suite (phpunit.xml.dist).
$this->go_to( home_url( '/our-team/' ) );
ob_start();
include get_template_directory() . '/header.php';
include get_template_directory() . '/footer.php';
$html = ob_get_clean();
$this->assertStringContainsString( 'Hollis Creek', $html );
$this->assertStringNotContainsString( 'Deprecated:', $html );
}
public function test_migration_deletes_a_leftover_public_debug_log() {
$file = WP_CONTENT_DIR . '/debug.log';
file_put_contents( $file, "PHP Deprecated: something in /var/www/after/wp-content/themes/x.php
" );
$results = hollis_migrations_run();
$this->assertStringContainsString( 'deleted wp-content/debug.log', $results['remove-public-debug-log'] );
$this->assertFileDoesNotExist( $file );
$this->assertSame( 'no public debug.log', hollis_migrations_run()['remove-public-debug-log'] );
}
public function test_errors_are_logged_outside_the_web_root_and_not_displayed() {
$config = file_get_contents( HOLLIS_CODE . '/hollis-config.php' );
$this->assertMatchesRegularExpression( "/define\(\s*'WP_DEBUG_DISPLAY',\s*false\s*\)/", $config );
$this->assertMatchesRegularExpression( "#define\(\s*'WP_DEBUG_LOG',\s*'/var/www/logs/#", $config );
}
}
e2e/tests/b-php8.spec.ts
e2e/tests/b-php8.spec.ts, as committed.
import { test, expect } from '@playwright/test';
import { BEFORE, AFTER } from './helpers';
// Exhibit B: PHP 8 errors as a visitor meets them.
test('B-3 before: the Insurance page is a white screen with a fatal error', async ({ page }) => {
const res = await page.goto(BEFORE + '/insurance/');
expect(res!.status()).toBe(500);
await expect(page.locator('body')).toContainText('There has been a critical error on this website.');
await expect(page.getByRole('heading', { name: 'Insurance and payment' })).toHaveCount(0);
await expect(page.getByRole('table')).toHaveCount(0);
});
test('B-3 after: the Insurance page shows the plans, sorted by name', async ({ page }) => {
const res = await page.goto(AFTER + '/insurance/');
expect(res!.status()).toBe(200);
await expect(page.getByRole('heading', { level: 1, name: 'Insurance and payment' })).toBeVisible();
const table = page.getByRole('table', { name: 'Insurance we work with' });
const plans = await table.locator('tbody tr td:first-child').allTextContents();
expect(plans.length).toBe(7);
expect(plans).toEqual([...plans].sort((a, b) => a.localeCompare(b)));
expect(plans[0]).toBe('Alder County Employees Plan');
});
test('B-1 before: PHP deprecation notices are printed above the page and the log is public', async ({ page, request }) => {
await page.goto(BEFORE + '/');
await expect(page.locator('body')).toContainText('Deprecated:');
const log = await request.get(BEFORE + '/wp-content/debug.log');
expect(log.status()).toBe(200);
expect(await log.text()).toContain('PHP Deprecated');
});
test('B-1 after: no notice on any page and no public debug log', async ({ page, request }) => {
for (const path of ['/', '/our-team/', '/services/', '/request-appointment/', '/news/']) {
await page.goto(AFTER + path);
await expect(page.locator('body')).not.toContainText(/Deprecated:|Warning:|Notice:/);
}
const log = await request.get(AFTER + '/wp-content/debug.log');
expect(log.status()).toBe(404);
});