$paths Absolute path => placeholder; defaults to WordPress paths. */ public static function redact( string $text, array $paths = array() ): string { if ( array() === $paths ) { $paths = self::default_paths(); } // Longest path first so ABSPATH does not swallow WP_CONTENT_DIR. uksort( $paths, static function ( string $a, string $b ): int { return strlen( $b ) <=> strlen( $a ); } ); foreach ( $paths as $path => $placeholder ) { $path = rtrim( $path, '/\\' ); if ( '' !== $path ) { $text = str_replace( $path, $placeholder, $text ); } } $patterns = array( // URL query strings may carry tokens or personal data. '#(https?://[^\s?"\']+)\?[^\s"\']*#i' => '$1?{query}', '/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i' => '{email}', '/\b(?:\d{1,3}\.){3}\d{1,3}\b/' => '{ip}', '/\b(?:[A-F0-9]{1,4}:){2,7}[A-F0-9]{1,4}\b/i' => '{ip}', // Long hex / base64-like secrets (API keys, nonces, hashes). '/\b[A-Za-z0-9_\-]{32,}\b/' => '{secret}', ); foreach ( $patterns as $pattern => $replacement ) { $replaced = preg_replace( $pattern, $replacement, $text ); $text = is_string( $replaced ) ? $replaced : $text; } return $text; } /** * WordPress directory placeholders. * * @return array */ private static function default_paths(): array { $paths = array(); if ( defined( 'ABSPATH' ) ) { $paths[ ABSPATH ] = '{root}'; } if ( defined( 'WP_CONTENT_DIR' ) ) { $paths[ WP_CONTENT_DIR ] = '{content}'; } return $paths; } }