diff --git a/ProjectLighthouse.Localization/Privacy.resx b/ProjectLighthouse.Localization/Privacy.resx new file mode 100644 index 00000000..b59a888a --- /dev/null +++ b/ProjectLighthouse.Localization/Privacy.resx @@ -0,0 +1,42 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Enable commenting on your profile. + + + Disable commenting on your profile. + + + You have not blocked any users. + + + You have blocked {0} user(s). + + + Share your {0} with everyone! + + + Only share your {0} with users who are playing in-game. + + + Only share your {0} with users who are signed into the website or playing in-game. + + \ No newline at end of file diff --git a/ProjectLighthouse.Localization/ProjectLighthouse.Localization.csproj b/ProjectLighthouse.Localization/ProjectLighthouse.Localization.csproj index 69348286..88e0b598 100644 --- a/ProjectLighthouse.Localization/ProjectLighthouse.Localization.csproj +++ b/ProjectLighthouse.Localization/ProjectLighthouse.Localization.csproj @@ -48,6 +48,10 @@ ResXFileCodeGenerator Moderation.Designer.cs + + ResXFileCodeGenerator + Privacy.Designer.cs + diff --git a/ProjectLighthouse.Localization/StringLists/PrivacyStrings.cs b/ProjectLighthouse.Localization/StringLists/PrivacyStrings.cs new file mode 100644 index 00000000..25d4c0b3 --- /dev/null +++ b/ProjectLighthouse.Localization/StringLists/PrivacyStrings.cs @@ -0,0 +1,16 @@ +namespace LBPUnion.ProjectLighthouse.Localization.StringLists; + +public static class PrivacyStrings +{ + public static readonly TranslatableString BlockedUsers = create("blocked_users"); + public static readonly TranslatableString NoBlockedUsers = create("no_blocked_users"); + + public static readonly TranslatableString EnableComments = create("enable_comments"); + public static readonly TranslatableString DisableComments = create("disable_comments"); + + public static readonly TranslatableString PrivacyAll = create("privacy_all"); + public static readonly TranslatableString PrivacyPSN = create("privacy_psn"); + public static readonly TranslatableString PrivacyGame = create("privacy_game"); + + private static TranslatableString create(string key) => new(TranslationAreas.Privacy, key); +} \ No newline at end of file diff --git a/ProjectLighthouse.Localization/TranslationAreas.cs b/ProjectLighthouse.Localization/TranslationAreas.cs index 106be35f..26df78c7 100644 --- a/ProjectLighthouse.Localization/TranslationAreas.cs +++ b/ProjectLighthouse.Localization/TranslationAreas.cs @@ -13,4 +13,5 @@ public enum TranslationAreas ModPanel, TwoFactor, Moderation, + Privacy, } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Extensions/FormattingExtensions.cs b/ProjectLighthouse.Servers.Website/Extensions/FormattingExtensions.cs index c90e5e51..42ba1bea 100644 --- a/ProjectLighthouse.Servers.Website/Extensions/FormattingExtensions.cs +++ b/ProjectLighthouse.Servers.Website/Extensions/FormattingExtensions.cs @@ -5,7 +5,7 @@ namespace LBPUnion.ProjectLighthouse.Servers.Website.Extensions; public static class FormattingExtensions { - public static string GetLevelLockIcon(this SlotEntity slot) => slot.InitiallyLocked ? "icon lock" : ""; + public static string GetLevelLockIcon(this SlotEntity slot) => slot.InitiallyLocked ? "ui icon lock" : ""; public static string ToHtmlColor(this PermissionLevel permissionLevel) { diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/CommentsPartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/CommentsPartial.cshtml index 18d66580..aa986c03 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/CommentsPartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/CommentsPartial.cshtml @@ -1,5 +1,4 @@ @using System.Web -@using System.IO @using LBPUnion.ProjectLighthouse.Database @using LBPUnion.ProjectLighthouse.Localization @using LBPUnion.ProjectLighthouse.Servers.Website.Extensions @@ -17,11 +16,11 @@
@if (Model.Comments.Count == 0 && Model.CommentsEnabled) { -

There are no comments.

+ There are no comments. } else if (!Model.CommentsEnabled) { -

Comments are disabled.

+ Comments are disabled. } else { @@ -51,10 +50,9 @@ CommentEntity comment = commentAndReaction.Key; int yourThumb = commentAndReaction.Value?.Rating ?? 0; DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000).ToLocalTime(); - StringWriter messageWriter = new(); - HttpUtility.HtmlDecode(comment.GetCommentMessage(Database), messageWriter); - string decodedMessage = messageWriter.ToString(); + string decodedMessage = HttpUtility.HtmlDecode(comment.GetCommentMessage(Database)); + string? url = Url.RouteUrl(ViewContext.RouteData.Values); if (url == null) continue; diff --git a/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml index 78b0a919..d6e6ca2d 100644 --- a/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml @@ -58,129 +58,139 @@ @await Model.Slot.ToHtml(Html, ViewData, Model.User, $"~/slot/{Model.Slot?.SlotId}", language, timeZone, isMobile)
-
-
-
-

Description

-

@HttpUtility.HtmlDecode(string.IsNullOrEmpty(Model.Slot?.Description) ? "This level has no description." : Model.Slot.Description)

-
+@if (!Model.CanViewSlot) +{ +
+

+ The creator's privacy settings prevent you from viewing this page. +

- @if (isMobile) - { -
- } -
-
-

Tags

- @{ - string[] authorLabels; - if (Model.Slot?.GameVersion == GameVersion.LittleBigPlanet1) - { - authorLabels = Model.Slot.LevelTags(Database); - } - else - { - authorLabels = Model.Slot?.AuthorLabels.Split(",", StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty(); - } - if (authorLabels.Length == 0) - { -

This level has no tags.

- } - else - { - foreach (string label in authorLabels.Where(label => !string.IsNullOrEmpty(label))) +} +else +{ +
+
+
+

Description

+

@HttpUtility.HtmlDecode(string.IsNullOrEmpty(Model.Slot?.Description) ? "This level has no description." : Model.Slot.Description)

+
+
+ @if (isMobile) + { +
+ } +
+
+

Tags

+ @{ + string[] authorLabels; + if (Model.Slot?.GameVersion == GameVersion.LittleBigPlanet1) { -
@LabelHelper.TranslateTag(label)
+ authorLabels = Model.Slot.LevelTags(Database); } - } - } -
-
- @if (isMobile) - { -
- } -
- -
- @{ - string outerDiv = isMobile ? - "horizontal-scroll" : - "three wide column"; - string innerDiv = isMobile ? - "ui top attached tabular menu horizontal-scroll" : - "ui vertical fluid tabular menu"; - } - - - @{ - string divLength = isMobile ? "sixteen" : "thirteen"; - } -
-
- @await Html.PartialAsync("Partials/CommentsPartial", new ViewDataDictionary(ViewData.WithLang(language).WithTime(timeZone)) - { { "PageOwner", Model.Slot?.CreatorId }, }) -
-
-
- @if (Model.Photos.Count != 0) - { -
- @foreach (PhotoEntity photo in Model.Photos) + else + { + authorLabels = Model.Slot?.AuthorLabels.Split(",", StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty(); + } + if (authorLabels.Length == 0) + { +

This level has no tags.

+ } + else + { + foreach (string label in authorLabels.Where(label => !string.IsNullOrEmpty(label))) { - string width = isMobile ? "sixteen" : "eight"; - bool canDelete = Model.User != null && (Model.User.IsModerator || Model.User.UserId == photo.CreatorId); -
- @await photo.ToHtml(Html, ViewData, language, timeZone, canDelete) -
+
@LabelHelper.TranslateTag(label)
} -
- @if (isMobile) - { -
} } - else - { -

This level has no photos yet.

- } -
-
- @await Html.PartialAsync("Partials/ReviewPartial", new ViewDataDictionary(ViewData) - { - { - "isMobile", isMobile - }, - { - "CanDelete", Model.User?.IsModerator ?? false - }, - }) + @if (isMobile) + { +
+ } +
+ +
+ @{ + string outerDiv = isMobile ? "horizontal-scroll" : "three wide column"; + string innerDiv = isMobile ? "ui top attached tabular menu horizontal-scroll" : "ui vertical fluid tabular menu"; + } + -
-
- @await Html.PartialAsync("Partials/LeaderboardPartial", - ViewData.WithLang(language).WithTime(timeZone).CanDelete(Model.User?.IsModerator ?? false)) + + @{ + string divLength = isMobile ? "sixteen" : "thirteen"; + } +
+
+ @await Html.PartialAsync("Partials/CommentsPartial", new ViewDataDictionary(ViewData.WithLang(language).WithTime(timeZone)) + { + { + "PageOwner", Model.Slot?.CreatorId + }, + }) +
+
+
+ @if (Model.Photos.Count != 0) + { +
+ @foreach (PhotoEntity photo in Model.Photos) + { + string width = isMobile ? "sixteen" : "eight"; + bool canDelete = Model.User != null && (Model.User.IsModerator || Model.User.UserId == photo.CreatorId); +
+ @await photo.ToHtml(Html, ViewData, language, timeZone, canDelete) +
+ } +
+ @if (isMobile) + { +
+ } + } + else + { +

This level has no photos yet.

+ } + +
+
+
+ @await Html.PartialAsync("Partials/ReviewPartial", new ViewDataDictionary(ViewData) + { + { + "isMobile", isMobile + }, + { + "CanDelete", Model.User?.IsModerator ?? false + }, + }) +
+
+
+ @await Html.PartialAsync("Partials/LeaderboardPartial", ViewData.WithLang(language).WithTime(timeZone).CanDelete(Model.User?.IsModerator ?? false)) +
-
+} @if (isMobile) { diff --git a/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml.cs index ebdd9076..4fc956b6 100644 --- a/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml.cs @@ -22,6 +22,8 @@ public class SlotPage : BaseLayout public bool CommentsEnabled; public readonly bool ReviewsEnabled = ServerConfiguration.Instance.UserGeneratedContentLimits.LevelReviewsEnabled; + public bool CanViewSlot; + public SlotEntity? Slot; public SlotPage(DatabaseContext database) : base(database) {} @@ -34,27 +36,11 @@ public class SlotPage : BaseLayout if (slot == null) return this.NotFound(); System.Diagnostics.Debug.Assert(slot.Creator != null); + bool isAuthenticated = this.User != null; + bool isOwner = slot.Creator == this.User || this.User != null && this.User.IsModerator; + // Determine if user can view slot according to creator's privacy settings - if (this.User == null || !this.User.IsAdmin) - { - switch (slot.Creator.ProfileVisibility) - { - case PrivacyType.PSN: - { - if (this.User != null) return this.NotFound(); - - break; - } - case PrivacyType.Game: - { - if (this.User == null || slot.Creator != this.User) return this.NotFound(); - - break; - } - case PrivacyType.All: break; - default: throw new ArgumentOutOfRangeException(); - } - } + this.CanViewSlot = slot.Creator.LevelVisibility.CanAccess(isAuthenticated, isOwner); if ((slot.Hidden || slot.SubLevel && (this.User == null && this.User != slot.Creator)) && !(this.User?.IsModerator ?? false)) return this.NotFound(); diff --git a/ProjectLighthouse.Servers.Website/Pages/SlotsPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/SlotsPage.cshtml.cs index d627ff6a..b7978b88 100644 --- a/ProjectLighthouse.Servers.Website/Pages/SlotsPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/SlotsPage.cshtml.cs @@ -72,7 +72,6 @@ public class SlotsPage : BaseLayout if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) return this.Redirect($"/slots/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}"); this.Slots = await slots - .Where(p => p.Creator!.LevelVisibility == PrivacyType.All) // TODO: change check for when user is logged in .OrderByDescending(p => p.FirstUploaded) .Skip(pageNumber * ServerStatics.PageSize) .Take(ServerStatics.PageSize) diff --git a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml index 97fb44e7..ffd3d5fb 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml @@ -29,7 +29,7 @@ Reason: "@Model.ProfileUser.BannedReason" } - else + else {

This user has been banned for violating the Terms of Service. Remember to follow the rules!

} @@ -39,20 +39,20 @@
@await Html.PartialAsync("Partials/UserCardPartial", Model.ProfileUser, new ViewDataDictionary(ViewData) - { - { - "ShowLink", false - }, - { - "IsMobile", Model.Request.IsMobile() - }, - { - "Language", Model.GetLanguage() - }, - { - "TimeZone", Model.GetTimeZone() - }, - }) + { + { + "ShowLink", false + }, + { + "IsMobile", Model.Request.IsMobile() + }, + { + "Language", Model.GetLanguage() + }, + { + "TimeZone", Model.GetTimeZone() + }, + })

@@ -87,11 +87,18 @@ } } + @if (Model.ProfileUser == Model.User) + { + + + Privacy Settings + + } @if (Model.ProfileUser == Model.User || (Model.User?.IsModerator ?? false)) { - Settings + Profile Settings } @if (Model.ProfileUser == Model.User) @@ -106,173 +113,200 @@ {
} -
-
-

@Model.Translate(ProfileStrings.Biography)

- @if (string.IsNullOrWhiteSpace(Model.ProfileUser.Biography)) - { -

@Model.Translate(ProfileStrings.NoBiography, Model.ProfileUser.Username)

- } - else - { -

@HttpUtility.HtmlDecode(Model.ProfileUser.Biography)

- } -
-
- @if (isMobile) + @if (Model.CanViewProfile) { -
- } -
-
-

@Model.Translate(GeneralStrings.RecentActivity)

-

@Model.Translate(GeneralStrings.Soon)

-
-
- @if (isMobile) - { -
- } -
- -
- @{ - string outerDiv = isMobile ? - "horizontal-scroll" : - "three wide column"; - string innerDiv = isMobile ? - "ui top attached tabular menu horizontal-scroll" : - "ui vertical fluid tabular menu"; - } - - @{ - string divLength = isMobile ? "sixteen" : "thirteen"; - } -
-
- @if (Model.ProfileUser.IsBanned) - { -
-

Comments are disabled because the user is banned.

-
- } - else - { - @await Html.PartialAsync("Partials/CommentsPartial", new ViewDataDictionary(ViewData.WithLang(language).WithTime(timeZone)) - { {"PageOwner", Model.ProfileUser.UserId}, }) - } -
-
-
- @if (Model.Photos != null && Model.Photos.Count != 0) +
+
+

@Model.Translate(ProfileStrings.Biography)

+ @if (string.IsNullOrWhiteSpace(Model.ProfileUser.Biography)) { -
- @foreach (PhotoEntity photo in Model.Photos) - { - string width = isMobile ? "sixteen" : "eight"; - bool canDelete = Model.User != null && (Model.User.IsModerator || Model.User.UserId == photo.CreatorId); -
- @await photo.ToHtml(Html, ViewData, language, timeZone, canDelete) -
- } -
- @if (isMobile) - { -
- } +

@Model.Translate(ProfileStrings.NoBiography, Model.ProfileUser.Username)

} else { -

This user hasn't uploaded any photos

+

@HttpUtility.HtmlDecode(Model.ProfileUser.Biography)

}
-
-
- @if (Model.Slots?.Count == 0) - { -

This user hasn't published any levels

- } - @foreach (SlotEntity slot in Model.Slots ?? new List()) - { -
- @await slot.ToHtml(Html, ViewData, Model.User, $"~/user/{Model.ProfileUser.UserId}#levels", language, timeZone, isMobile, true) -
- } -
-
-
-
+ @if (isMobile) + { +
+ } +
+
+

@Model.Translate(GeneralStrings.RecentActivity)

@Model.Translate(GeneralStrings.Soon)

- @if (Model.User == Model.ProfileUser) + @if (isMobile) { -
-
- @if (Model.HeartedSlots?.Count == 0) - { -

You haven't hearted any levels

- } - else - { -

You have hearted @(Model.HeartedSlots?.Count) levels

- } - @foreach (SlotEntity slot in Model.HeartedSlots ?? new List()) - { -
- @await slot.ToHtml(Html, ViewData, Model.User, $"~/user/{Model.ProfileUser.UserId}#hearted", language, timeZone, isMobile, true) -
- } -
-
-
-
- @if (Model.QueuedSlots?.Count == 0) - { -

You haven't queued any levels

- } - else - { -

There are @(Model.QueuedSlots?.Count) levels in your queue

- } - @foreach (SlotEntity slot in Model.QueuedSlots ?? new List()) - { -
- @await slot.ToHtml(Html, ViewData, Model.User, $"~/user/{Model.ProfileUser.UserId}#queued", language, timeZone, isMobile, true) -
- } -
-
- } -
+
+ } + }
+@if (!Model.CanViewProfile) +{ +
+

+ The user's privacy settings prevent you from viewing this page. +

+
+} +else +{ +
+ @{ + string outerDiv = isMobile ? "horizontal-scroll" : "three wide column"; + string innerDiv = isMobile ? "ui top attached tabular menu horizontal-scroll" : "ui vertical fluid tabular menu"; + } + + @{ + string divLength = isMobile ? "sixteen" : "thirteen"; + } +
+
+ @if (Model.ProfileUser.IsBanned) + { +
+

+ Comments are disabled because the user is banned. +

+
+ } + else + { + @await Html.PartialAsync("Partials/CommentsPartial", new ViewDataDictionary(ViewData.WithLang(language).WithTime(timeZone)) + { + { + "PageOwner", Model.ProfileUser.UserId + }, + }) + } +
+
+
+ @if (Model.Photos != null && Model.Photos.Count != 0) + { +
+ @foreach (PhotoEntity photo in Model.Photos) + { + string width = isMobile ? "sixteen" : "eight"; + bool canDelete = Model.User != null && (Model.User.IsModerator || Model.User.UserId == photo.CreatorId); +
+ @await photo.ToHtml(Html, ViewData, language, timeZone, canDelete) +
+ } +
+ @if (isMobile) + { +
+ } + } + else + { +

This user hasn't uploaded any photos

+ } +
+
+
+ @if (!Model.CanViewSlots) + { +
+

+ The user's privacy settings prevent you from viewing this page. +

+
+ } + else + { +
+ @if (Model.Slots?.Count == 0) + { +

This user hasn't published any levels

+ } + @foreach (SlotEntity slot in Model.Slots ?? new List()) + { +
+ @await slot.ToHtml(Html, ViewData, Model.User, $"~/user/{Model.ProfileUser.UserId}#levels", language, timeZone, isMobile, true) +
+ } +
+ } +
+
+
+

@Model.Translate(GeneralStrings.Soon)

+
+
+ @if (Model.User == Model.ProfileUser) + { +
+
+ @if (Model.HeartedSlots?.Count == 0) + { +

You haven't hearted any levels

+ } + else + { +

You have hearted @(Model.HeartedSlots?.Count) levels

+ } + @foreach (SlotEntity slot in Model.HeartedSlots ?? new List()) + { +
+ @await slot.ToHtml(Html, ViewData, Model.User, $"~/user/{Model.ProfileUser.UserId}#hearted", language, timeZone, isMobile, true) +
+ } +
+
+
+
+ @if (Model.QueuedSlots?.Count == 0) + { +

You haven't queued any levels

+ } + else + { +

There are @(Model.QueuedSlots?.Count) levels in your queue

+ } + @foreach (SlotEntity slot in Model.QueuedSlots ?? new List()) + { +
+ @await slot.ToHtml(Html, ViewData, Model.User, $"~/user/{Model.ProfileUser.UserId}#queued", language, timeZone, isMobile, true) +
+ } +
+
+ } +
+
+} + @if (Model.User != null && Model.User.IsModerator) {
@@ -289,25 +323,25 @@ } - @if (Model.ProfileUser.CommentsEnabled) + + + + @if (!Model.CommentsDisabledByModerator) { } - - - - + @if (Model.User.IsAdmin) { @await Html.PartialAsync("Partials/AdminSetGrantedSlotsFormPartial", Model.ProfileUser) @@ -364,4 +398,4 @@ if (selectedElement != null) { let sidebarEle = document.querySelector("[target=" + selectedElement.id + "]") setVisible(sidebarEle); } - + \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs index b4ce0190..35f7919b 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs @@ -6,6 +6,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Interaction; using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Levels; +using LBPUnion.ProjectLighthouse.Types.Moderation.Cases; using LBPUnion.ProjectLighthouse.Types.Users; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -17,6 +18,7 @@ public class UserPage : BaseLayout public Dictionary Comments = new(); public bool CommentsEnabled; + public bool CommentsDisabledByModerator; public bool IsProfileUserHearted; @@ -29,35 +31,24 @@ public class UserPage : BaseLayout public List? QueuedSlots; public UserEntity? ProfileUser; + + public bool CanViewProfile; + public bool CanViewSlots; + public UserPage(DatabaseContext database) : base(database) - {} + { } public async Task OnGet([FromRoute] int userId) { this.ProfileUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == userId); if (this.ProfileUser == null) return this.NotFound(); + bool isAuthenticated = this.User != null; + bool isOwner = this.ProfileUser == this.User || this.User != null && this.User.IsModerator; + // Determine if user can view profile according to profileUser's privacy settings - if (this.User == null || !this.User.IsAdmin) - { - switch (this.ProfileUser.ProfileVisibility) - { - case PrivacyType.PSN: - { - if (this.User != null) return this.NotFound(); - - break; - } - case PrivacyType.Game: - { - if (this.ProfileUser != this.User) return this.NotFound(); - - break; - } - case PrivacyType.All: break; - default: throw new ArgumentOutOfRangeException(); - } - } + this.CanViewProfile = this.ProfileUser.ProfileVisibility.CanAccess(isAuthenticated, isOwner); + this.CanViewSlots = this.ProfileUser.LevelVisibility.CanAccess(isAuthenticated, isOwner); this.Photos = await this.Database.Photos.Include(p => p.Slot) .Include(p => p.PhotoSubjects) @@ -91,21 +82,24 @@ public class UserPage : BaseLayout .ToListAsync(); } - this.CommentsEnabled = ServerConfiguration.Instance.UserGeneratedContentLimits.LevelCommentsEnabled && this.ProfileUser.CommentsEnabled; + this.CommentsEnabled = ServerConfiguration.Instance.UserGeneratedContentLimits.LevelCommentsEnabled && + this.ProfileUser.CommentsEnabled; if (this.CommentsEnabled) { - List blockedUsers = this.User == null ? new List() : await - (from blockedProfile in this.Database.BlockedProfiles - where blockedProfile.UserId == this.User.UserId - select blockedProfile.BlockedUserId).ToListAsync(); - + List blockedUsers = this.User == null + ? new List() + : await ( + from blockedProfile in this.Database.BlockedProfiles + where blockedProfile.UserId == this.User.UserId + select blockedProfile.BlockedUserId).ToListAsync(); + this.Comments = await this.Database.Comments.Include(p => p.Poster) .OrderByDescending(p => p.Timestamp) .Where(p => p.TargetId == userId && p.Type == CommentType.Profile) .Where(p => !blockedUsers.Contains(p.PosterUserId)) .Take(50) - .ToDictionaryAsync(c => c, _ => (RatedCommentEntity?) null); + .ToDictionaryAsync(c => c, _ => (RatedCommentEntity?)null); } else { @@ -122,13 +116,17 @@ public class UserPage : BaseLayout this.Comments[kvp.Key] = reaction; } - this.IsProfileUserHearted = await this.Database.HeartedProfiles - .Where(h => h.HeartedUserId == this.ProfileUser.UserId) + this.IsProfileUserHearted = await this.Database.HeartedProfiles.Where(h => h.HeartedUserId == this.ProfileUser.UserId) .Where(h => h.UserId == this.User.UserId) .AnyAsync(); this.IsProfileUserBlocked = await this.Database.IsUserBlockedBy(this.ProfileUser.UserId, this.User.UserId); - + + this.CommentsDisabledByModerator = await this.Database.Cases.Where(c => c.AffectedId == this.ProfileUser.UserId) + .Where(c => c.Type == CaseType.UserDisableComments) + .Where(c => c.DismissedAt == null) + .AnyAsync(); + return this.Page(); } } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/UserPrivacyPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/UserPrivacyPage.cshtml new file mode 100644 index 00000000..0530be3a --- /dev/null +++ b/ProjectLighthouse.Servers.Website/Pages/UserPrivacyPage.cshtml @@ -0,0 +1,101 @@ +@page "/user/{userId:int}/privacy" +@using LBPUnion.ProjectLighthouse.Extensions +@using LBPUnion.ProjectLighthouse.Localization.StringLists +@using LBPUnion.ProjectLighthouse.Types.Entities.Profile +@using LBPUnion.ProjectLighthouse.Types.Users +@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.UserPrivacyPage + +@{ + Layout = "Layouts/BaseLayout"; + Model.Title = Model.Translate(ProfileStrings.Title, Model.ProfileUser!.Username); + Model.ShowTitleInPage = false; + + bool isMobile = Request.IsMobile(); +} + +
+
+

@Model.ProfileUser.Username's Privacy Settings

+
+ @Html.AntiForgeryToken() +
+

Profile Privacy

+
+ + +
+
+ + +
+

Level Privacy

+
+ + +
+
+ + Discard Changes +
+
+ +
+

Blocked Users

+ @if (Model.BlockedUsers.Count == 0) + { + @Model.Translate(PrivacyStrings.NoBlockedUsers) + } + else + { +

@Model.Translate(PrivacyStrings.BlockedUsers, Model.BlockedUsers.Count)

+ } + @foreach (UserEntity user in Model.BlockedUsers) + { +
+ @await Html.PartialAsync("Partials/UserCardPartial", user, new ViewDataDictionary(ViewData) + { + { + "ShowLink", true + }, + { + "IsMobile", isMobile + }, + { + "Language", Model.GetLanguage() + }, + { + "TimeZone", Model.GetTimeZone() + }, + }) +
+ } +
+
+
\ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/UserPrivacyPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/UserPrivacyPage.cshtml.cs new file mode 100644 index 00000000..ea51a640 --- /dev/null +++ b/ProjectLighthouse.Servers.Website/Pages/UserPrivacyPage.cshtml.cs @@ -0,0 +1,78 @@ +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; +using LBPUnion.ProjectLighthouse.Types.Moderation.Cases; +using LBPUnion.ProjectLighthouse.Types.Users; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages; + +public class UserPrivacyPage : BaseLayout +{ + public List BlockedUsers = new(); + + public bool CommentsDisabledByModerator; + + public UserEntity? ProfileUser; + + public UserPrivacyPage(DatabaseContext database) : base(database) + { } + + public async Task OnGet([FromRoute] int userId) + { + this.ProfileUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == userId); + if (this.ProfileUser == null) return this.NotFound(); + + if (this.User == null) return this.Redirect("~/login"); + if (this.User != this.ProfileUser) return this.Redirect("~/user/" + userId); + + this.BlockedUsers = await this.Database.BlockedProfiles.Where(b => b.UserId == this.ProfileUser.UserId) + .Select(b => b.BlockedUser) + .ToListAsync(); + + this.CommentsDisabledByModerator = await this.Database.Cases.Where(c => c.AffectedId == this.ProfileUser.UserId) + .Where(c => c.Type == CaseType.UserDisableComments) + .Where(c => c.DismissedAt == null) + .AnyAsync(); + + return this.Page(); + } + + public async Task OnPost([FromRoute] int userId, [FromForm] string profilePrivacyLevel, [FromForm] bool profileCommentsEnabled, [FromForm] string slotPrivacyLevel) + { + this.ProfileUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == userId); + if (this.ProfileUser == null) return this.NotFound(); + + if (this.User == null) return this.Redirect("~/login"); + if (this.User != this.ProfileUser) return this.Redirect("~/user/" + userId); + + this.CommentsDisabledByModerator = await this.Database.Cases.Where(c => c.AffectedId == this.ProfileUser.UserId) + .Where(c => c.Type == CaseType.UserDisableComments) + .Where(c => c.DismissedAt == null) + .AnyAsync(); + + if (!this.CommentsDisabledByModerator) + { + this.ProfileUser.CommentsEnabled = profileCommentsEnabled; + } + + this.ProfileUser.ProfileVisibility = PrivacyTypeFromString(profilePrivacyLevel); + this.ProfileUser.LevelVisibility = PrivacyTypeFromString(slotPrivacyLevel); + + await this.Database.SaveChangesAsync(); + + return this.Redirect($"~/user/{userId}"); + } + + private static PrivacyType PrivacyTypeFromString(string type) + { + return type switch + { + "all" => PrivacyType.All, + "psn" => PrivacyType.PSN, + "game" => PrivacyType.Game, + _ => PrivacyType.All, + }; + } +} \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml index bc610ee4..c0242301 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml @@ -44,7 +44,7 @@ function onSubmit(e){
-

@Model.ProfileUser.Username's Settings

+

@Model.ProfileUser.Username's Profile Settings

@Html.AntiForgeryToken() diff --git a/ProjectLighthouse.Servers.Website/Pages/UsersPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/UsersPage.cshtml.cs index ec08d78c..51e8ad2a 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UsersPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/UsersPage.cshtml.cs @@ -37,8 +37,8 @@ public class UsersPage : BaseLayout if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) return this.Redirect($"/users/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}"); - this.Users = await this.Database.Users.Where(u => u.PermissionLevel != PermissionLevel.Banned && u.Username.Contains(this.SearchValue)) - .Where(u => u.ProfileVisibility == PrivacyType.All) // TODO: change check for when user is logged in + this.Users = await this.Database.Users.Where(u => u.Username.Contains(this.SearchValue)) + .Where(u => u.PermissionLevel != PermissionLevel.Banned) .OrderByDescending(b => b.UserId) .Skip(pageNumber * ServerStatics.PageSize) .Take(ServerStatics.PageSize) diff --git a/ProjectLighthouse/Types/Users/PrivacyType.cs b/ProjectLighthouse/Types/Users/PrivacyType.cs index ead38638..d9122a38 100644 --- a/ProjectLighthouse/Types/Users/PrivacyType.cs +++ b/ProjectLighthouse/Types/Users/PrivacyType.cs @@ -1,3 +1,6 @@ +using LBPUnion.ProjectLighthouse.Localization; +using LBPUnion.ProjectLighthouse.Localization.StringLists; + namespace LBPUnion.ProjectLighthouse.Types.Users; /// @@ -21,6 +24,17 @@ public enum PrivacyType public static class PrivacyTypeExtensions { + public static TranslatableString ToReadableString(this PrivacyType type) + { + return type switch + { + PrivacyType.All => PrivacyStrings.PrivacyAll, + PrivacyType.PSN => PrivacyStrings.PrivacyPSN, + PrivacyType.Game => PrivacyStrings.PrivacyGame, + _ => null, + }; + } + public static string ToSerializedString(this PrivacyType type) => type.ToString().ToLower(); @@ -34,4 +48,15 @@ public static class PrivacyTypeExtensions _ => null, }; } + + public static bool CanAccess(this PrivacyType type, bool authenticated, bool owner) + { + return type switch + { + PrivacyType.All => true, + PrivacyType.PSN => authenticated, + PrivacyType.Game => authenticated && owner, + _ => false, + }; + } } \ No newline at end of file