199 lines
5.8 KiB
PHP
199 lines
5.8 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* OutputHelper - Consistent output escaping utilities
|
||
|
|
*
|
||
|
|
* Provides secure HTML escaping functions to prevent XSS attacks.
|
||
|
|
* Use these functions when outputting user-controlled data.
|
||
|
|
*/
|
||
|
|
class OutputHelper {
|
||
|
|
/**
|
||
|
|
* Escape string for HTML output
|
||
|
|
*
|
||
|
|
* Use for text content inside HTML elements.
|
||
|
|
* Example: <p><?= OutputHelper::h($userInput) ?></p>
|
||
|
|
*
|
||
|
|
* @param string|null $string The string to escape
|
||
|
|
* @param int $flags htmlspecialchars flags (default: ENT_QUOTES | ENT_HTML5)
|
||
|
|
* @return string Escaped string
|
||
|
|
*/
|
||
|
|
public static function h(?string $string, int $flags = ENT_QUOTES | ENT_HTML5): string {
|
||
|
|
if ($string === null) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
return htmlspecialchars($string, $flags, 'UTF-8');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Escape string for HTML attribute context
|
||
|
|
*
|
||
|
|
* Use for values inside HTML attributes.
|
||
|
|
* Example: <input value="<?= OutputHelper::attr($userInput) ?>">
|
||
|
|
*
|
||
|
|
* @param string|null $string The string to escape
|
||
|
|
* @return string Escaped string
|
||
|
|
*/
|
||
|
|
public static function attr(?string $string): string {
|
||
|
|
if ($string === null) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
// More aggressive escaping for attribute context
|
||
|
|
return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5 | ENT_SUBSTITUTE, 'UTF-8');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Encode data as JSON for JavaScript context
|
||
|
|
*
|
||
|
|
* Use when embedding data in JavaScript.
|
||
|
|
* Example: <script>const data = <?= OutputHelper::json($data) ?>;</script>
|
||
|
|
*
|
||
|
|
* @param mixed $data The data to encode
|
||
|
|
* @param int $flags json_encode flags
|
||
|
|
* @return string JSON encoded string (safe for script context)
|
||
|
|
*/
|
||
|
|
public static function json($data, int $flags = 0): string {
|
||
|
|
// Use HEX encoding for safety in HTML context
|
||
|
|
$safeFlags = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | $flags;
|
||
|
|
return json_encode($data, $safeFlags);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* URL encode a string
|
||
|
|
*
|
||
|
|
* Use for values in URL query strings.
|
||
|
|
* Example: <a href="/search?q=<?= OutputHelper::url($query) ?>">
|
||
|
|
*
|
||
|
|
* @param string|null $string The string to encode
|
||
|
|
* @return string URL encoded string
|
||
|
|
*/
|
||
|
|
public static function url(?string $string): string {
|
||
|
|
if ($string === null) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
return rawurlencode($string);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Escape for CSS context
|
||
|
|
*
|
||
|
|
* Use for values in inline CSS.
|
||
|
|
* Example: <div style="color: <?= OutputHelper::css($color) ?>;">
|
||
|
|
*
|
||
|
|
* @param string|null $string The string to escape
|
||
|
|
* @return string Escaped string (only allows safe characters)
|
||
|
|
*/
|
||
|
|
public static function css(?string $string): string {
|
||
|
|
if ($string === null) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
// Only allow alphanumeric, hyphens, underscores, spaces, and common CSS values
|
||
|
|
if (!preg_match('/^[a-zA-Z0-9_\-\s#.,()%]+$/', $string)) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
return $string;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format a number safely
|
||
|
|
*
|
||
|
|
* Ensures output is always a valid number.
|
||
|
|
*
|
||
|
|
* @param mixed $number The number to format
|
||
|
|
* @param int $decimals Number of decimal places
|
||
|
|
* @return string Formatted number
|
||
|
|
*/
|
||
|
|
public static function number($number, int $decimals = 0): string {
|
||
|
|
return number_format((float)$number, $decimals, '.', ',');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format an integer safely
|
||
|
|
*
|
||
|
|
* @param mixed $value The value to format
|
||
|
|
* @return int Integer value
|
||
|
|
*/
|
||
|
|
public static function int($value): int {
|
||
|
|
return (int)$value;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Truncate string with ellipsis
|
||
|
|
*
|
||
|
|
* @param string|null $string The string to truncate
|
||
|
|
* @param int $length Maximum length
|
||
|
|
* @param string $suffix Suffix to add if truncated
|
||
|
|
* @return string Truncated and escaped string
|
||
|
|
*/
|
||
|
|
public static function truncate(?string $string, int $length = 100, string $suffix = '...'): string {
|
||
|
|
if ($string === null) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (mb_strlen($string, 'UTF-8') <= $length) {
|
||
|
|
return self::h($string);
|
||
|
|
}
|
||
|
|
|
||
|
|
return self::h(mb_substr($string, 0, $length, 'UTF-8')) . self::h($suffix);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format a date safely
|
||
|
|
*
|
||
|
|
* @param string|int|null $date Date string, timestamp, or null
|
||
|
|
* @param string $format PHP date format
|
||
|
|
* @return string Formatted date
|
||
|
|
*/
|
||
|
|
public static function date($date, string $format = 'Y-m-d H:i:s'): string {
|
||
|
|
if ($date === null || $date === '') {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (is_numeric($date)) {
|
||
|
|
return date($format, (int)$date);
|
||
|
|
}
|
||
|
|
|
||
|
|
$timestamp = strtotime($date);
|
||
|
|
if ($timestamp === false) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
return date($format, $timestamp);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if a string is safe for use as a CSS class name
|
||
|
|
*
|
||
|
|
* @param string $class The class name to validate
|
||
|
|
* @return bool True if safe
|
||
|
|
*/
|
||
|
|
public static function isValidCssClass(string $class): bool {
|
||
|
|
return preg_match('/^[a-zA-Z_][a-zA-Z0-9_-]*$/', $class) === 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Sanitize CSS class name(s)
|
||
|
|
*
|
||
|
|
* @param string|null $classes Space-separated class names
|
||
|
|
* @return string Sanitized class names
|
||
|
|
*/
|
||
|
|
public static function cssClass(?string $classes): string {
|
||
|
|
if ($classes === null || $classes === '') {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
$classList = explode(' ', $classes);
|
||
|
|
$validClasses = array_filter($classList, [self::class, 'isValidCssClass']);
|
||
|
|
|
||
|
|
return implode(' ', $validClasses);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Shorthand function for HTML escaping
|
||
|
|
*
|
||
|
|
* @param string|null $string The string to escape
|
||
|
|
* @return string Escaped string
|
||
|
|
*/
|
||
|
|
function h(?string $string): string {
|
||
|
|
return OutputHelper::h($string);
|
||
|
|
}
|