Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions src/Settings/Blocks/Fields/IpList.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,8 @@ class IpList extends Field
public function getValueSanitizer(): callable
{
return function ($value) {
$ips = [];

if (! is_array($value)) {
$ips = explode("\n", $value);
}

$ips = is_array($value) ? $value : (is_string($value) ? explode("\n", $value) : []);
$ips = array_filter($ips, 'is_string');
$ips = array_map('trim', $ips);
$ips = array_filter($ips, function ($ip) {
return filter_var($ip, FILTER_VALIDATE_IP) !== false;
Expand Down
16 changes: 16 additions & 0 deletions tests/Browser/phpRegression.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { test, expect } from '@playwright/test';
import { execFileSync } from 'node:child_process';
import { readdirSync } from 'node:fs';
import { basename, resolve } from 'node:path';

// Each script uses its own temporary options table, so these checks can run
// alongside browser tests without changing their WordPress settings.
for (const file of readdirSync(resolve('tests/Regression')).filter(name => name.endsWith('.php'))) {
test(`WordPress regression: ${file}`, () => {
const output = execFileSync(resolve('node_modules/.bin/wp-env'), [
'run', 'cli', 'wp', 'eval-file',
`wp-content/plugins/${basename(process.cwd())}/tests/Regression/${file}`,
], { encoding: 'utf8', timeout: 45000 });
expect(output).toContain('Regression checks passed');
});
}
26 changes: 26 additions & 0 deletions tests/Regression/ip-sanitization.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

require __DIR__ . '/../Support/isolated-options.php';

sa_with_isolated_options(static function () {
$key = SimpleAnalytics\SettingName::EXCLUDED_IP_ADDRESSES;
$field = new SimpleAnalytics\Settings\Blocks\Fields\IpList($key, 'IP addresses');
$sanitize = $field->getValueSanitizer();
register_setting('simpleanalytics-ignore-rules', $key, [
'type' => $field->getValueType(),
'sanitize_callback' => $sanitize,
]);

delete_option($key);
update_option($key, "203.0.113.10\n2001:db8::1");
$expected = ['203.0.113.10', '2001:db8::1'];
sa_assert(get_option($key) === $expected, 'First save must retain addresses after double sanitization.');
sa_assert($sanitize($expected) === $expected, 'Sanitizing an array must preserve valid addresses.');
sa_assert($sanitize($sanitize($expected)) === $expected, 'Sanitization must be idempotent.');
sa_assert($sanitize([' 203.0.113.10 ', '203.0.113.10', 'invalid', [], null, new stdClass()]) === ['203.0.113.10'], 'Reject malformed items and remove duplicates.');
foreach ([null, false, 123, new stdClass(), ''] as $invalid) {
sa_assert($sanitize($invalid) === [], 'Malformed or empty input must produce an empty list.');
}
update_option($key, "203.0.113.20\r\ninvalid\r\n203.0.113.20");
sa_assert(get_option($key) === ['203.0.113.20'], 'Existing options must still accept textarea updates.');
});
39 changes: 39 additions & 0 deletions tests/Support/isolated-options.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/** Run only through wp eval-file against the local wp-env installation. */
function sa_assert($condition, string $message): void
{
if (! $condition) throw new RuntimeException($message);
}

function sa_with_isolated_options(callable $test): void
{
global $wpdb, $wp_object_cache;

sa_assert(! wp_using_ext_object_cache(), 'Regression tests require the default object cache.');
$originalTable = $wpdb->options;
$originalCache = $wp_object_cache;
$table = 'sa_regression_options';
$guard = static function ($sql) use ($table) {
if (! preg_match('/^\s*(SELECT|SHOW|DESCRIBE|EXPLAIN)\b/i', $sql) && strpos($sql, $table) === false) {
throw new RuntimeException('Refusing a write outside the temporary test table.');
}
return $sql;
};
add_filter('query', $guard, 9999);

try {
sa_assert($wpdb->query("CREATE TEMPORARY TABLE $table LIKE $originalTable") !== false, $wpdb->last_error);
sa_assert($wpdb->query("INSERT INTO $table SELECT * FROM $originalTable") !== false, $wpdb->last_error);
$wpdb->options = $table;
$wp_object_cache = new WP_Object_Cache();
wp_cache_switch_to_blog(get_current_blog_id());
$test();
echo "Regression checks passed\n";
} finally {
$wpdb->options = $originalTable;
$wp_object_cache = $originalCache;
remove_filter('query', $guard, 9999);
}
// The database connection automatically drops the temporary table on exit.
}