Adds three lexical-context regexes (after-SteamID, ChatMessage author, Combat/Safety pvp subsystem) and wires the player-name branch in redact(). Includes six PHPUnit tests covering all three contexts plus the toggle-off and no-anchor-no-touch cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
4.4 KiB
PHP
110 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace IndifferentKetchup\Codex\Util\ProjectZomboid;
|
|
|
|
use IndifferentKetchup\Codex\Util\RedactorInterface;
|
|
|
|
/**
|
|
* Render-time PII filter for Project Zomboid log content.
|
|
*
|
|
* Applies up to three sequential regex passes over the raw log string,
|
|
* each controlled by a boolean toggle (all enabled by default):
|
|
*
|
|
* 1. Steam ID pass — replaces 17-digit Steam IDs with a placeholder token.
|
|
* 2. Player name pass — replaces player display names with a placeholder
|
|
* token. This pass anchors on the already-redacted Steam ID token, so
|
|
* the ordering Steam ID -> name -> coordinates is mandatory.
|
|
* 3. Coordinates pass — replaces world coordinate triplets with a placeholder
|
|
* token.
|
|
*
|
|
* All regex passes use the /u flag for Unicode safety.
|
|
*
|
|
* Replacements are not reversible; do not apply to content that must later be
|
|
* restored to its original form.
|
|
*/
|
|
class ProjectZomboidRedactor implements RedactorInterface
|
|
{
|
|
/** Regex matching a 17-digit SteamID64 anchored on the 76561198 universe prefix, with lookaround boundaries that reject embedded occurrences. */
|
|
public const string STEAM_ID_REGEX = '/(?<![A-Za-z0-9])76561198\d{9}(?![A-Za-z0-9])/u';
|
|
|
|
/** Zeroed-out SteamID64 placeholder; syntactically valid but refers to no real account. */
|
|
public const string STEAM_ID_REPLACEMENT = '76561198000000000';
|
|
|
|
/** Generic placeholder substituted for every matched player display name. */
|
|
public const string PLAYER_NAME_REPLACEMENT = '<player>';
|
|
|
|
/** Matches a double-quoted player name that immediately follows the redacted Steam ID placeholder (cmd.txt / admin.txt shape); relies on the Steam ID pass having run first. */
|
|
public const string PLAYER_AFTER_STEAMID_REGEX = '/(?<=76561198000000000) "(?<name>[^"]+)"/u';
|
|
|
|
/** Matches the author value inside a ChatMessage{...} envelope, using a fixed-length lookbehind on ", author='" and a lookahead on the closing "'" so only the bare name is replaced. */
|
|
public const string PLAYER_IN_CHATMESSAGE_REGEX = '/(?<=, author=\')(?<name>[^\']+)(?=\')/u';
|
|
|
|
/** Matches the first double-quoted player name following a Combat: or Safety: subsystem token (pvp.txt shape); does NOT redact the second name after "hit" — deferred to v2. */
|
|
public const string PLAYER_IN_PVP_SUBSYSTEM_REGEX = '/(?<=(?:Combat|Safety): )"(?<name>[^"]+)"/u';
|
|
|
|
private bool $redactSteamIds = true;
|
|
private bool $redactPlayerNames = true;
|
|
private bool $redactCoordinates = true;
|
|
|
|
/**
|
|
* Enable or disable the Steam ID redaction pass.
|
|
*
|
|
* @param bool $on Pass true to enable, false to disable.
|
|
* @return static
|
|
*/
|
|
public function redactSteamIds(bool $on): static
|
|
{
|
|
$this->redactSteamIds = $on;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Enable or disable the player-name redaction pass.
|
|
*
|
|
* @param bool $on Pass true to enable, false to disable.
|
|
* @return static
|
|
*/
|
|
public function redactPlayerNames(bool $on): static
|
|
{
|
|
$this->redactPlayerNames = $on;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Enable or disable the coordinates redaction pass.
|
|
*
|
|
* @param bool $on Pass true to enable, false to disable.
|
|
* @return static
|
|
*/
|
|
public function redactCoordinates(bool $on): static
|
|
{
|
|
$this->redactCoordinates = $on;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Redact PII from the given Project Zomboid log content.
|
|
*
|
|
* Passes are applied in the mandatory order: Steam ID -> player name ->
|
|
* coordinates. See class docblock for rationale.
|
|
*
|
|
* @param string $content Raw log content that may contain PII.
|
|
* @return string Content with enabled PII categories replaced by tokens.
|
|
*/
|
|
public function redact(string $content): string
|
|
{
|
|
if ($this->redactSteamIds) {
|
|
$content = preg_replace(self::STEAM_ID_REGEX, self::STEAM_ID_REPLACEMENT, $content);
|
|
}
|
|
if ($this->redactPlayerNames) {
|
|
$content = preg_replace(self::PLAYER_AFTER_STEAMID_REGEX, ' "' . self::PLAYER_NAME_REPLACEMENT . '"', $content);
|
|
$content = preg_replace(self::PLAYER_IN_CHATMESSAGE_REGEX, self::PLAYER_NAME_REPLACEMENT, $content);
|
|
$content = preg_replace(self::PLAYER_IN_PVP_SUBSYSTEM_REGEX, '"' . self::PLAYER_NAME_REPLACEMENT . '"', $content);
|
|
}
|
|
if ($this->redactCoordinates) {
|
|
// Coordinates pass added in Task 4
|
|
}
|
|
return $content;
|
|
}
|
|
}
|