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 = '/(?'; /** 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) "(?[^"]+)"/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=\')(?[^\']+)(?=\')/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): )"(?[^"]+)"/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; } }