|
| 1 | +<?php |
| 2 | +/** |
| 3 | + * TicketsCAD PHP Compatibility Layer |
| 4 | + * |
| 5 | + * Polyfills functions removed or deprecated across PHP versions. |
| 6 | + * Include this FIRST — before any other code runs. |
| 7 | + * |
| 8 | + * Supports: PHP 7.0 through 8.4+ |
| 9 | + * |
| 10 | + * Changes by PHP version: |
| 11 | + * PHP 8.4: Implicit nullable parameters deprecated |
| 12 | + * PHP 8.2: utf8_encode/utf8_decode REMOVED, ${var} interpolation deprecated |
| 13 | + * PHP 8.1: strftime() deprecated, FILTER_SANITIZE_STRING removed |
| 14 | + * PHP 8.0: each() REMOVED, create_function() REMOVED, stricter type juggling |
| 15 | + * PHP 7.4: array_key_exists() on objects deprecated |
| 16 | + * PHP 7.2: each() deprecated, create_function() deprecated |
| 17 | + * |
| 18 | + * @package TicketsCAD |
| 19 | + * @since v3.44.2 |
| 20 | + */ |
| 21 | + |
| 22 | +// ═══════════════════════════════════════════════════════════════ |
| 23 | +// PHP 8.2+ — utf8_encode / utf8_decode REMOVED |
| 24 | +// These were thin wrappers for ISO-8859-1 ↔ UTF-8 conversion. |
| 25 | +// Polyfill using mb_convert_encoding or manual byte conversion. |
| 26 | +// ═══════════════════════════════════════════════════════════════ |
| 27 | + |
| 28 | +if (!function_exists('utf8_encode')) { |
| 29 | + function utf8_encode($string) { |
| 30 | + if (function_exists('mb_convert_encoding')) { |
| 31 | + return mb_convert_encoding($string, 'UTF-8', 'ISO-8859-1'); |
| 32 | + } |
| 33 | + // Manual fallback: convert each byte > 0x7F to UTF-8 two-byte sequence |
| 34 | + $output = ''; |
| 35 | + $len = strlen($string); |
| 36 | + for ($i = 0; $i < $len; $i++) { |
| 37 | + $byte = ord($string[$i]); |
| 38 | + if ($byte < 0x80) { |
| 39 | + $output .= $string[$i]; |
| 40 | + } else { |
| 41 | + $output .= chr(0xC0 | ($byte >> 6)) . chr(0x80 | ($byte & 0x3F)); |
| 42 | + } |
| 43 | + } |
| 44 | + return $output; |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +if (!function_exists('utf8_decode')) { |
| 49 | + function utf8_decode($string) { |
| 50 | + if (function_exists('mb_convert_encoding')) { |
| 51 | + return mb_convert_encoding($string, 'ISO-8859-1', 'UTF-8'); |
| 52 | + } |
| 53 | + // Manual fallback |
| 54 | + $output = ''; |
| 55 | + $len = strlen($string); |
| 56 | + for ($i = 0; $i < $len; $i++) { |
| 57 | + $byte = ord($string[$i]); |
| 58 | + if ($byte < 0x80) { |
| 59 | + $output .= $string[$i]; |
| 60 | + } elseif (($byte & 0xE0) === 0xC0) { |
| 61 | + $next = ($i + 1 < $len) ? ord($string[++$i]) : 0; |
| 62 | + $codepoint = (($byte & 0x1F) << 6) | ($next & 0x3F); |
| 63 | + $output .= ($codepoint < 256) ? chr($codepoint) : '?'; |
| 64 | + } else { |
| 65 | + // 3-byte or 4-byte sequences map to '?' in ISO-8859-1 |
| 66 | + if (($byte & 0xF0) === 0xE0) { $i += 2; } |
| 67 | + elseif (($byte & 0xF8) === 0xF0) { $i += 3; } |
| 68 | + $output .= '?'; |
| 69 | + } |
| 70 | + } |
| 71 | + return $output; |
| 72 | + } |
| 73 | +} |
| 74 | + |
| 75 | +// ═══════════════════════════════════════════════════════════════ |
| 76 | +// PHP 8.1+ — FILTER_SANITIZE_STRING removed |
| 77 | +// ═══════════════════════════════════════════════════════════════ |
| 78 | + |
| 79 | +if (!defined('FILTER_SANITIZE_STRING')) { |
| 80 | + define('FILTER_SANITIZE_STRING', FILTER_DEFAULT); |
| 81 | +} |
| 82 | + |
| 83 | +// ═══════════════════════════════════════════════════════════════ |
| 84 | +// PHP 8.0+ — each() REMOVED |
| 85 | +// Used by very old code to iterate arrays. Replace with foreach |
| 86 | +// where possible, but polyfill for code we can't easily change. |
| 87 | +// ═══════════════════════════════════════════════════════════════ |
| 88 | + |
| 89 | +if (!function_exists('each')) { |
| 90 | + function each(&$array) { |
| 91 | + $value = current($array); |
| 92 | + $key = key($array); |
| 93 | + if ($key === null) { |
| 94 | + return false; |
| 95 | + } |
| 96 | + next($array); |
| 97 | + return array(1 => $value, 'value' => $value, 0 => $key, 'key' => $key); |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +// ═══════════════════════════════════════════════════════════════ |
| 102 | +// PHP 8.0+ — create_function() REMOVED |
| 103 | +// Polyfill wraps eval() — not ideal but maintains BC. |
| 104 | +// New code should never use this. |
| 105 | +// ═══════════════════════════════════════════════════════════════ |
| 106 | + |
| 107 | +if (!function_exists('create_function')) { |
| 108 | + function create_function($args, $code) { |
| 109 | + static $counter = 0; |
| 110 | + $funcName = '__compat_lambda_' . (++$counter); |
| 111 | + eval("function {$funcName}({$args}) { {$code} }"); |
| 112 | + return $funcName; |
| 113 | + } |
| 114 | +} |
| 115 | + |
| 116 | +// ═══════════════════════════════════════════════════════════════ |
| 117 | +// PHP 8.1+ — strftime() DEPRECATED (E_DEPRECATED warnings) |
| 118 | +// Many legacy date formatting calls use strftime. Suppress the |
| 119 | +// deprecation warning since the function still works through 8.x. |
| 120 | +// ═══════════════════════════════════════════════════════════════ |
| 121 | + |
| 122 | +// We don't polyfill strftime() because it still works in PHP 8.x, |
| 123 | +// just throws E_DEPRECATED. The error_reporting below handles it. |
| 124 | +// A full replacement would require IntlDateFormatter which may not |
| 125 | +// be available on all systems. |
| 126 | + |
| 127 | +// ═══════════════════════════════════════════════════════════════ |
| 128 | +// PHP 7.2+ — Suppress deprecation notices from legacy code |
| 129 | +// This prevents E_DEPRECATED from breaking JSON output or causing |
| 130 | +// 500 errors when the error handler is strict. |
| 131 | +// ═══════════════════════════════════════════════════════════════ |
| 132 | + |
| 133 | +// Only suppress deprecation notices — all real errors still reported |
| 134 | +$currentLevel = error_reporting(); |
| 135 | +if ($currentLevel & E_DEPRECATED) { |
| 136 | + error_reporting($currentLevel & ~E_DEPRECATED); |
| 137 | +} |
| 138 | + |
| 139 | +// ═══════════════════════════════════════════════════════════════ |
| 140 | +// PHP 8.0+ — str_contains / str_starts_with / str_ends_with |
| 141 | +// These are useful new functions. Polyfill for PHP < 8.0. |
| 142 | +// ═══════════════════════════════════════════════════════════════ |
| 143 | + |
| 144 | +if (!function_exists('str_contains')) { |
| 145 | + function str_contains(string $haystack, string $needle): bool { |
| 146 | + return $needle === '' || strpos($haystack, $needle) !== false; |
| 147 | + } |
| 148 | +} |
| 149 | + |
| 150 | +if (!function_exists('str_starts_with')) { |
| 151 | + function str_starts_with(string $haystack, string $needle): bool { |
| 152 | + return $needle === '' || strncmp($haystack, $needle, strlen($needle)) === 0; |
| 153 | + } |
| 154 | +} |
| 155 | + |
| 156 | +if (!function_exists('str_ends_with')) { |
| 157 | + function str_ends_with(string $haystack, string $needle): bool { |
| 158 | + return $needle === '' || substr($haystack, -strlen($needle)) === $needle; |
| 159 | + } |
| 160 | +} |
| 161 | + |
| 162 | +// ═══════════════════════════════════════════════════════════════ |
| 163 | +// PHP 8.0+ — Stricter error handling for null parameters |
| 164 | +// Many legacy functions pass null where string is expected. |
| 165 | +// Set up a custom error handler to silently convert these. |
| 166 | +// ═══════════════════════════════════════════════════════════════ |
| 167 | + |
| 168 | +if (PHP_MAJOR_VERSION >= 8) { |
| 169 | + $previousHandler = set_error_handler(function ($severity, $message, $file, $line) use (&$previousHandler) { |
| 170 | + // Suppress "passing null to non-nullable parameter" deprecation |
| 171 | + if ($severity === E_DEPRECATED && strpos($message, 'Passing null to parameter') !== false) { |
| 172 | + return true; // Suppress |
| 173 | + } |
| 174 | + // Suppress "strftime() is deprecated" (PHP 8.1+) |
| 175 | + if ($severity === E_DEPRECATED && strpos($message, 'strftime()') !== false) { |
| 176 | + return true; // Suppress |
| 177 | + } |
| 178 | + // Suppress "implicitly nullable parameter" (PHP 8.4+) |
| 179 | + if ($severity === E_DEPRECATED && strpos($message, 'implicitly nullable') !== false) { |
| 180 | + return true; // Suppress |
| 181 | + } |
| 182 | + // Suppress "Use of ${var} in strings is deprecated" (PHP 8.2+) |
| 183 | + if ($severity === E_DEPRECATED && strpos($message, '${') !== false) { |
| 184 | + return true; // Suppress |
| 185 | + } |
| 186 | + // Pass everything else to the previous handler or PHP's default |
| 187 | + if ($previousHandler) { |
| 188 | + return call_user_func($previousHandler, $severity, $message, $file, $line); |
| 189 | + } |
| 190 | + return false; // Let PHP handle it |
| 191 | + }); |
| 192 | +} |
| 193 | + |
| 194 | +// ═══════════════════════════════════════════════════════════════ |
| 195 | +// PHP 7.0+ — Null coalescing for array access safety |
| 196 | +// Helper function for legacy code that uses array_key_exists on |
| 197 | +// potentially null arrays. |
| 198 | +// ═══════════════════════════════════════════════════════════════ |
| 199 | + |
| 200 | +if (!function_exists('array_get')) { |
| 201 | + /** |
| 202 | + * Safely get a value from an array with a default. |
| 203 | + * Works with null arrays and missing keys. |
| 204 | + */ |
| 205 | + function array_get($array, $key, $default = null) { |
| 206 | + if (!is_array($array)) return $default; |
| 207 | + return array_key_exists($key, $array) ? $array[$key] : $default; |
| 208 | + } |
| 209 | +} |
| 210 | + |
| 211 | +// ═══════════════════════════════════════════════════════════════ |
| 212 | +// PHP 8.0+ — mysqli_result changes |
| 213 | +// fetch_assoc() returns null instead of false on empty result. |
| 214 | +// This doesn't break while() loops but may break strict === false |
| 215 | +// checks. No polyfill needed, but document for awareness. |
| 216 | +// ═══════════════════════════════════════════════════════════════ |
| 217 | + |
| 218 | +// No polyfill needed — while($row = $result->fetch_assoc()) works |
| 219 | +// the same with both null and false as the loop terminator. |
| 220 | + |
| 221 | +// ═══════════════════════════════════════════════════════════════ |
| 222 | +// Startup diagnostic — log PHP version on first load |
| 223 | +// ═══════════════════════════════════════════════════════════════ |
| 224 | + |
| 225 | +// Log once per session to help with debugging |
| 226 | +if (function_exists('session_status') && session_status() === PHP_SESSION_NONE) { |
| 227 | + // Don't start session here — let the caller do it |
| 228 | +} elseif (isset($_SESSION) && !isset($_SESSION['_compat_logged'])) { |
| 229 | + $_SESSION['_compat_logged'] = true; |
| 230 | + // Optionally log: error_log("TicketsCAD: PHP " . PHP_VERSION . " compat layer loaded"); |
| 231 | +} |
0 commit comments