Profile on sidebar
@@ -13,21 +13,48 @@ public final class AvatarLocalResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String resolve(String serverValue) {
|
public static String resolve(String serverValue) {
|
||||||
if (serverValue == null || serverValue.isBlank()) return null;
|
if (serverValue == null) return null;
|
||||||
if (isHttp(serverValue) || serverValue.startsWith("file:")) return serverValue;
|
serverValue = serverValue.trim();
|
||||||
|
if (serverValue.isEmpty()) return null;
|
||||||
|
|
||||||
String rel = serverValue.startsWith("/") ? serverValue.substring(1) : serverValue; // "avatars/..."
|
// URL کامل یا file: → همون رو برگردون
|
||||||
|
if (isHttp(serverValue) || serverValue.startsWith("file:")) {
|
||||||
|
return serverValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⬅️ مهم: برای مسیرهای لوکال، هر چیزی بعد از ? یا # را حذف کن
|
||||||
|
serverValue = stripQueryAndHash(serverValue);
|
||||||
|
|
||||||
|
// حذف اسلش ابتدایی (اگر بود)
|
||||||
|
String rel = serverValue.startsWith("/") ? serverValue.substring(1) : serverValue;
|
||||||
|
|
||||||
|
// ساخت مسیر امن داخل uploads
|
||||||
Path p = UPLOADS_ROOT.resolve(rel).normalize();
|
Path p = UPLOADS_ROOT.resolve(rel).normalize();
|
||||||
if (!p.startsWith(UPLOADS_ROOT)) return null; // امنیت مسیر
|
|
||||||
if (!Files.exists(p)) { // مهم: واقعاً وجود دارد؟
|
// جلوگیری از خروج از دایرکتوری uploads (path traversal)
|
||||||
|
if (!p.startsWith(UPLOADS_ROOT)) return null;
|
||||||
|
|
||||||
|
// وجود واقعی فایل
|
||||||
|
if (!Files.exists(p)) {
|
||||||
System.err.println("Avatar resolve: NOT FOUND -> " + p);
|
System.err.println("Avatar resolve: NOT FOUND -> " + p);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String url = p.toUri().toString(); // file:///.../uploads/avatars/....
|
|
||||||
|
// خروجی به صورت file:// URI که JavaFX Image میفهمد
|
||||||
|
String url = p.toUri().toString();
|
||||||
System.out.println("Avatar resolve: " + serverValue + " -> " + url);
|
System.out.println("Avatar resolve: " + serverValue + " -> " + url);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String stripQueryAndHash(String s) {
|
||||||
|
int q = s.indexOf('?');
|
||||||
|
if (q >= 0) s = s.substring(0, q);
|
||||||
|
int h = s.indexOf('#');
|
||||||
|
if (h >= 0) s = s.substring(0, h);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Image load(String serverValue) {
|
public static Image load(String serverValue) {
|
||||||
String url = resolve(serverValue);
|
String url = resolve(serverValue);
|
||||||
if (url == null) return null;
|
if (url == null) return null;
|
||||||
|
|||||||
@@ -1811,7 +1811,7 @@ private void addBubble(
|
|||||||
if (days > 30) {
|
if (days > 30) {
|
||||||
return "Last seen long time ago";
|
return "Last seen long time ago";
|
||||||
}
|
}
|
||||||
return "recently";
|
return "Last seen recently";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDefaultHeaderAvatarByType(String type){
|
private void setDefaultHeaderAvatarByType(String type){
|
||||||
|
|||||||
@@ -23,9 +23,16 @@ import org.to.telegramfinalproject.Client.Session;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||||
|
import org.to.telegramfinalproject.Client.Session;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
public class EditProfileController {
|
public class EditProfileController {
|
||||||
|
|
||||||
|
private File pickedAvatarFile;
|
||||||
|
|
||||||
@FXML private Pane overlayBackground;
|
@FXML private Pane overlayBackground;
|
||||||
@FXML private VBox editCard;
|
@FXML private VBox editCard;
|
||||||
|
|
||||||
@@ -108,13 +115,15 @@ public class EditProfileController {
|
|||||||
File file = fileChooser.showOpenDialog(profileImageView.getScene().getWindow());
|
File file = fileChooser.showOpenDialog(profileImageView.getScene().getWindow());
|
||||||
|
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
profileImageView.setImage(new Image(file.toURI().toString()));
|
pickedAvatarFile = file; // ← ذخیره کن برای ارسال هنگام خروج
|
||||||
// TODO: save this new image to DB or server
|
profileImageView.setImage(new Image(file.toURI().toString())); // پیشنمایش
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeEdit() {
|
private void closeEdit() {
|
||||||
MainController.getInstance().closeOverlay(editCard.getParent());
|
//MainController.getInstance().closeOverlay(editCard.getParent());
|
||||||
|
MainController.getInstance().goBack(overlayBackground);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProfileData(String name, String status, String bio, String userId, Image profileImage) {
|
public void setProfileData(String name, String status, String bio, String userId, Image profileImage) {
|
||||||
@@ -135,6 +144,55 @@ public class EditProfileController {
|
|||||||
|
|
||||||
boolean anyError = false;
|
boolean anyError = false;
|
||||||
|
|
||||||
|
// if (pickedAvatarFile != null) {
|
||||||
|
// ActionHandler.instance.uploadAvatarFor("user", null, pickedAvatarFile);
|
||||||
|
//
|
||||||
|
// if (ActionHandler.instance.wasSuccess()) {
|
||||||
|
// String url = ActionHandler.instance.getLastMessage();
|
||||||
|
// if (url != null && !url.isBlank()) {
|
||||||
|
// currentUser.put("image_url", url);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// anyError = true;
|
||||||
|
// showAlert(
|
||||||
|
// "Avatar upload failed",
|
||||||
|
// ActionHandler.instance.getLastMessage() == null ? "Unknown error" : ActionHandler.instance.getLastMessage(),
|
||||||
|
// Alert.AlertType.ERROR
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
if (pickedAvatarFile != null) {
|
||||||
|
ActionHandler.instance.uploadAvatarFor("user", null, pickedAvatarFile);
|
||||||
|
|
||||||
|
if (ActionHandler.instance.wasSuccess()) {
|
||||||
|
String url = ActionHandler.instance.getLastMessage();
|
||||||
|
if (url != null && !url.isBlank()) {
|
||||||
|
// قبلاً اینجا مستقیم bust میزدیم → مشکل ایجاد میکرد
|
||||||
|
// currentUser.put("image_url", url);
|
||||||
|
|
||||||
|
// ✔️ نسخهی امن:
|
||||||
|
String extracted = extractImageUrl(url); // از helper پایین
|
||||||
|
if (isLikelyImageUrl(extracted)) {
|
||||||
|
String finalUrl = addCacheBusterIfHttp(extracted); // فقط برای http/https
|
||||||
|
Session.currentUser.put("image_url", finalUrl);
|
||||||
|
} else {
|
||||||
|
// اگر سرور چیز عجیبی مثل "Welcome Farid" برگردوند، نادیده بگیر
|
||||||
|
System.out.println("Upload avatar returned a non-image value: " + url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
anyError = true;
|
||||||
|
showAlert(
|
||||||
|
"Avatar upload failed",
|
||||||
|
ActionHandler.instance.getLastMessage() == null ? "Unknown error" : ActionHandler.instance.getLastMessage(),
|
||||||
|
Alert.AlertType.ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- Update bio ---
|
// --- Update bio ---
|
||||||
String oldBio = currentUser.optString("bio", "");
|
String oldBio = currentUser.optString("bio", "");
|
||||||
if (!newBio.equals(oldBio)) {
|
if (!newBio.equals(oldBio)) {
|
||||||
@@ -181,8 +239,14 @@ public class EditProfileController {
|
|||||||
currentUser.optString("bio"),
|
currentUser.optString("bio"),
|
||||||
currentUser.optString("user_id"),
|
currentUser.optString("user_id"),
|
||||||
currentUser.optString("image_url", "/org/to/telegramfinalproject/Avatars/default_user_profile.png")
|
currentUser.optString("image_url", "/org/to/telegramfinalproject/Avatars/default_user_profile.png")
|
||||||
|
|
||||||
|
|
||||||
);
|
);
|
||||||
MainController.getInstance().closeOverlay(bioField.getParent().getParent());
|
// MainController mc = MainController.getInstance();
|
||||||
|
MainController.getInstance().refreshSidebarUserFromSession();
|
||||||
|
MainController.getInstance().goBack(overlayBackground);
|
||||||
|
|
||||||
|
// MainController.getInstance().closeOverlay(bioField.getParent().getParent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,4 +263,48 @@ public class EditProfileController {
|
|||||||
|
|
||||||
alert.showAndWait();
|
alert.showAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isLikelyImageUrl(String s) {
|
||||||
|
if (s == null || s.isBlank()) return false;
|
||||||
|
// URL کامل، مسیر نسبی سرور، یا file: و پسوند تصویر
|
||||||
|
return s.startsWith("http://") || s.startsWith("https://")
|
||||||
|
|| s.startsWith("file:")
|
||||||
|
|| s.startsWith("/")
|
||||||
|
|| s.matches("(?i).+\\.(png|jpe?g|gif|webp)$");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String addCacheBusterIfHttp(String url) {
|
||||||
|
if (url == null) return null;
|
||||||
|
if (url.startsWith("http://") || url.startsWith("https://")) {
|
||||||
|
return url + (url.contains("?") ? "&" : "?") + "v=" + System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
// برای مسیر لوکال/نسبی اصلاً ?v اضافه نکن (روی ویندوز مشکل میدهد)
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** اگر سرور بهجای URL، JSON یا متن دیگری داد، اینجا URL را دربیار. */
|
||||||
|
private String extractImageUrl(String raw) {
|
||||||
|
if (raw == null) return null;
|
||||||
|
String s = raw.trim();
|
||||||
|
// اگر برگشتی JSON است (بعضی سرویسها data.display_url برمیگردانند)
|
||||||
|
if (s.startsWith("{") && s.endsWith("}")) {
|
||||||
|
try {
|
||||||
|
org.json.JSONObject j = new org.json.JSONObject(s);
|
||||||
|
// هر کلیدی که سرویسات استفاده میکند را امتحان کن
|
||||||
|
if (j.has("display_url")) return j.optString("display_url", null);
|
||||||
|
if (j.has("url")) return j.optString("url", null);
|
||||||
|
if (j.has("data")) {
|
||||||
|
var d = j.optJSONObject("data");
|
||||||
|
if (d != null) {
|
||||||
|
if (d.has("display_url")) return d.optString("display_url", null);
|
||||||
|
if (d.has("url")) return d.optString("url", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
}
|
||||||
|
return s; // در غیر این صورت همان raw
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1141,4 +1141,12 @@ public class MainController {
|
|||||||
getChatPageController().showChat(entry);
|
getChatPageController().showChat(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void refreshSidebarUserFromSession() {
|
||||||
|
if (sidebarController != null && Session.currentUser != null) {
|
||||||
|
Platform.runLater(() -> sidebarController.setUserFromSession(Session.currentUser));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,12 @@ import javafx.scene.shape.Circle;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||||
|
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||||
|
import org.to.telegramfinalproject.Client.Session;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
public class MyProfileController {
|
public class MyProfileController {
|
||||||
|
|
||||||
@@ -68,6 +74,7 @@ public class MyProfileController {
|
|||||||
|
|
||||||
// === Open edit profile ===
|
// === Open edit profile ===
|
||||||
editButton.setOnAction(e -> openEditProfile());
|
editButton.setOnAction(e -> openEditProfile());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeProfile() {
|
private void closeProfile() {
|
||||||
@@ -91,9 +98,18 @@ public class MyProfileController {
|
|||||||
this.userId.setText("@" + userId);
|
this.userId.setText("@" + userId);
|
||||||
userID = userId;
|
userID = userId;
|
||||||
|
|
||||||
if (imageUrl != null && !imageUrl.isEmpty()) {
|
if (imageUrl != null && !imageUrl.isBlank()) {
|
||||||
profileImage.setImage(new Image(imageUrl));
|
Image img = AvatarLocalResolver.load(imageUrl);
|
||||||
|
if (img != null) {
|
||||||
|
profileImage.setImage(img);
|
||||||
|
} else {
|
||||||
|
profileImage.setImage(new Image(
|
||||||
|
Objects.requireNonNull(getClass().getResourceAsStream(
|
||||||
|
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"))
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Theme-specific updates ===
|
// === Theme-specific updates ===
|
||||||
@@ -133,4 +149,5 @@ public class MyProfileController {
|
|||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 499 KiB |
|
After Width: | Height: | Size: 358 KiB |
|
After Width: | Height: | Size: 358 KiB |
|
After Width: | Height: | Size: 499 KiB |
|
After Width: | Height: | Size: 499 KiB |
|
After Width: | Height: | Size: 499 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 499 KiB |
|
After Width: | Height: | Size: 2.0 KiB |