['Valid image is required']]); } if (!is_allowed_upload($_FILES['avatar']['tmp_name'], $_FILES['avatar']['name'], true)) { json_error(422, 'Validation failed', ['avatar' => ['Only image files are allowed']]); } $dir = $config['avatars_dir']; if (!is_dir($dir) && !mkdir($dir, 0775, true) && !is_dir($dir)) { json_error(500, 'Storage directory unavailable'); } $safe = preg_replace('/[^A-Za-z0-9._-]/', '_', basename($_FILES['avatar']['name'])); $filename = $user['id'] . '-' . time() . '-' . $safe; if (!move_uploaded_file($_FILES['avatar']['tmp_name'], $dir . '/' . $filename)) { json_error(500, 'Failed to store avatar'); } // Drop the previous upload; an external (social) avatar is just a URL, nothing to delete. $old = (string) ($user['avatar'] ?? ''); if ($old !== '' && !str_starts_with($old, 'http') && is_file($dir . '/' . basename($old))) { @unlink($dir . '/' . basename($old)); } $pdo->prepare('UPDATE auth_users SET avatar = ? WHERE id = ?')->execute([$filename, $user['id']]); $stmt = $pdo->prepare('SELECT * FROM auth_users WHERE id = ? LIMIT 1'); $stmt->execute([$user['id']]); json_data(present_user($stmt->fetch()));