self::MAX_LENGTH ? mb_substr($name, 0, self::MAX_LENGTH) : $name; } /** * The stored name, or '' when nothing is known about this file – which is the normal case for * everything uploaded before sidecars existed. * * The absence is checked rather than suppressed with `@`: index.php installs an error handler * that turns every warning into an exception, and it does not consult `error_reporting()`, so * `@` does not silence it. A missing sidecar would otherwise fail the whole listing. */ public static function read(string $dir, string $file): string { $path = $dir . '/' . $file . '.json'; if (!is_file($path)) { return ''; } $raw = file_get_contents($path); if ($raw === false) { return ''; } $data = json_decode($raw, true); return is_array($data) && is_string($data['name'] ?? null) ? self::sanitize($data['name']) : ''; } /** Writes the sidecar; an empty name writes nothing and removes nothing. */ public static function write(string $dir, string $file, string $name): void { $clean = self::sanitize($name); if ($clean === '') { return; } $path = $dir . '/' . $file . '.json'; $tmp = $path . '.' . bin2hex(random_bytes(4)) . '.tmp'; $json = json_encode(['name' => $clean], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($json === false || file_put_contents($tmp, $json, LOCK_EX) === false || !rename($tmp, $path)) { if (is_file($tmp)) { unlink($tmp); } return; // A nameless file is a small loss; failing the upload over it would be worse. } chmod($path, 0664); } /** Removing a file that has no sidecar is the normal case, not a failure. */ public static function delete(string $dir, string $file): void { $path = $dir . '/' . $file . '.json'; if (is_file($path)) { unlink($path); } } }